ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/group/branches/new_design/OOPSE-2.0/src/brains/SimInfo.cpp
(Generate patch)

Comparing branches/new_design/OOPSE-2.0/src/brains/SimInfo.cpp (file contents):
Revision 1738 by tim, Sat Nov 13 05:08:12 2004 UTC vs.
Revision 1901 by tim, Tue Jan 4 22:18:36 2005 UTC

# Line 31 | Line 31
31   */
32  
33   #include <algorithm>
34 + #include <set>
35  
36   #include "brains/SimInfo.hpp"
37 + #include "math/Vector3.hpp"
38 + #include "primitives/Molecule.hpp"
39 + #include "UseTheForce/doForces_interface.h"
40 + #include "UseTheForce/notifyCutoffs_interface.h"
41   #include "utils/MemoryUtils.hpp"
42 + #include "utils/simError.h"
43  
44 + #ifdef IS_MPI
45 + #include "UseTheForce/mpiComponentPlan.h"
46 + #include "UseTheForce/DarkSide/simParallel_interface.h"
47 + #endif
48 +
49   namespace oopse {
50  
51 < SimInfo::SimInfo(const std::vector<std::pair<MoleculeStamp*, int> >& molStampPairs,
52 <                                ForceField* ff, Globals* globals) :
53 <                                forceField_(ff), globals_(globals), nAtoms_(0), nBonds_(0),
54 <                                nBends_(0), nTorsions_(0), nRigidBodies_(0), nIntegrableObjects_(0),
55 <                                nCutoffGroups_(0), nConstraints_(0), nZConstraint_(0), sman_(NULL),
56 <                                fortranInitialized_(false) {
51 > SimInfo::SimInfo(std::vector<std::pair<MoleculeStamp*, int> >& molStampPairs,
52 >                                ForceField* ff, Globals* simParams) :
53 >                                forceField_(ff), simParams_(simParams),
54 >                                ndf_(0), ndfRaw_(0), ndfTrans_(0), nZconstraint_(0),
55 >                                nGlobalMols_(0), nGlobalAtoms_(0), nGlobalCutoffGroups_(0),
56 >                                nGlobalIntegrableObjects_(0), nGlobalRigidBodies_(0),
57 >                                nAtoms_(0), nBonds_(0),  nBends_(0), nTorsions_(0), nRigidBodies_(0),
58 >                                nIntegrableObjects_(0),  nCutoffGroups_(0), nConstraints_(0),
59 >                                sman_(NULL), fortranInitialized_(false) {
60  
61 +            
62      std::vector<std::pair<MoleculeStamp*, int> >::iterator i;
48    int nCutoffAtoms; // number of atoms belong to cutoff groups
49    int ngroups;          //total cutoff groups defined in meta-data file
63      MoleculeStamp* molStamp;
64      int nMolWithSameStamp;
65 <    CutoffGroupStamp* cgStamp;
66 <    int nAtomsIngroups;
67 <    int nCutoffGroupsInStamp;    
68 <
69 <    nGlobalAtoms_ =  0;
57 <    ngroups = 0;
65 >    int nCutoffAtoms = 0; // number of atoms belong to cutoff groups
66 >    int nGroups = 0;          //total cutoff groups defined in meta-data file
67 >    CutoffGroupStamp* cgStamp;    
68 >    RigidBodyStamp* rbStamp;
69 >    int nRigidAtoms = 0;
70      
71      for (i = molStampPairs.begin(); i !=molStampPairs.end(); ++i) {
72          molStamp = i->first;
73          nMolWithSameStamp = i->second;
74          
75          addMoleculeStamp(molStamp, nMolWithSameStamp);
76 <        
76 >
77 >        //calculate atoms in molecules
78          nGlobalAtoms_ += molStamp->getNAtoms() *nMolWithSameStamp;  
79 +
80 +
81 +        //calculate atoms in cutoff groups
82 +        int nAtomsInGroups = 0;
83 +        int nCutoffGroupsInStamp = molStamp->getNCutoffGroups();
84          
67        nAtomsIngroups = 0;
68        nCutoffGroupsInStamp = molStamp->getNCutoffGroups();
69        
85          for (int j=0; j < nCutoffGroupsInStamp; j++) {
86              cgStamp = molStamp->getCutoffGroup(j);
87 <            nAtomsIngroups += cgStamp->getNMembers();
87 >            nAtomsInGroups += cgStamp->getNMembers();
88          }
89  
90 <        ngroups += *nMolWithSameStamp;
91 <        nCutoffAtoms += nAtomsIngroups * nMolWithSameStamp;                
90 >        nGroups += nCutoffGroupsInStamp * nMolWithSameStamp;
91 >        nCutoffAtoms += nAtomsInGroups * nMolWithSameStamp;            
92 >
93 >        //calculate atoms in rigid bodies
94 >        int nAtomsInRigidBodies = 0;
95 >        int nRigidBodiesInStamp = molStamp->getNCutoffGroups();
96 >        
97 >        for (int j=0; j < nRigidBodiesInStamp; j++) {
98 >            rbStamp = molStamp->getRigidBody(j);
99 >            nAtomsInRigidBodies += rbStamp->getNMembers();
100 >        }
101 >
102 >        nGlobalRigidBodies_ += nRigidBodiesInStamp * nMolWithSameStamp;
103 >        nRigidAtoms += nAtomsInRigidBodies * nMolWithSameStamp;            
104 >        
105      }
106  
107      //every free atom (atom does not belong to cutoff groups) is a cutoff group
108      //therefore the total number of cutoff groups in the system is equal to
109      //the total number of atoms minus number of atoms belong to cutoff group defined in meta-data
110      //file plus the number of cutoff groups defined in meta-data file
111 <    nGlobalCutoffGroups_ = nGlobalAtoms_ - nCutoffAtoms + ngroups;
111 >    nGlobalCutoffGroups_ = nGlobalAtoms_ - nCutoffAtoms + nGroups;
112  
113 <    //initialize globalGroupMembership_, every element of this array will be 0
114 <    globalGroupMembership_.insert(globalGroupMembership_.end(), nGlobalAtoms_, 0);
113 >    //every free atom (atom does not belong to rigid bodies) is an integrable object
114 >    //therefore the total number of  integrable objects in the system is equal to
115 >    //the total number of atoms minus number of atoms belong to  rigid body defined in meta-data
116 >    //file plus the number of  rigid bodies defined in meta-data file
117 >    nGlobalIntegrableObjects_ = nGlobalAtoms_ - nRigidAtoms + nGlobalRigidBodies_;
118  
119      nGlobalMols_ = molStampIds_.size();
120  
# Line 99 | Line 130 | SimInfo::~SimInfo() {
130      MemoryUtils::deleteVectorOfPointer(moleculeStamps_);
131      
132      delete sman_;
133 <    delete globals_;
133 >    delete simParams_;
134      delete forceField_;
135  
136   }
# Line 109 | Line 140 | bool SimInfo::addMolecule(Molecule* mol) {
140      MoleculeIterator i;
141  
142      i = molecules_.find(mol->getGlobalIndex());
143 <    if (i != molecules_.end() ) {
143 >    if (i == molecules_.end() ) {
144  
145 <        molecules_.insert(make_pair(mol->getGlobalIndex(), mol));
145 >        molecules_.insert(std::make_pair(mol->getGlobalIndex(), mol));
146          
147          nAtoms_ += mol->getNAtoms();
148          nBonds_ += mol->getNBonds();
# Line 120 | Line 151 | bool SimInfo::addMolecule(Molecule* mol) {
151          nRigidBodies_ += mol->getNRigidBodies();
152          nIntegrableObjects_ += mol->getNIntegrableObjects();
153          nCutoffGroups_ += mol->getNCutoffGroups();
154 <        nConstraints_ += mol->getNConstraints();
154 >        nConstraints_ += mol->getNConstraintPairs();
155  
156 +        addExcludePairs(mol);
157 +        
158          return true;
159      } else {
160          return false;
# Line 143 | Line 176 | bool SimInfo::removeMolecule(Molecule* mol) {
176          nRigidBodies_ -= mol->getNRigidBodies();
177          nIntegrableObjects_ -= mol->getNIntegrableObjects();
178          nCutoffGroups_ -= mol->getNCutoffGroups();
179 <        nConstraints_ -= mol->getNConstraints();
179 >        nConstraints_ -= mol->getNConstraintPairs();
180  
181 +        removeExcludePairs(mol);
182          molecules_.erase(mol->getGlobalIndex());
183  
184          delete mol;
# Line 206 | Line 240 | void SimInfo::calcNdf() {
240  
241      // nZconstraints_ is global, as are the 3 COM translations for the
242      // entire system:
243 <    ndf_ = ndf_ - 3 - nZconstraints_;
243 >    ndf_ = ndf_ - 3 - nZconstraint_;
244  
245   }
246  
# Line 257 | Line 291 | void SimInfo::calcNdfTrans() {
291      ndfTrans_ = ndfTrans_local;
292   #endif
293  
294 <    ndfTrans_ = ndfTrans_ - 3 - nZconstraints_;
294 >    ndfTrans_ = ndfTrans_ - 3 - nZconstraint_;
295  
296   }
297  
# Line 289 | Line 323 | void SimInfo::addExcludePairs(Molecule* mol) {
323          exclude_.addPair(b, c);        
324      }
325  
326 <    for (torsion= mol->beginTorsion(torsionIter); torsion != NULL; torsion = mol->nextBond(torsionIter)) {
326 >    for (torsion= mol->beginTorsion(torsionIter); torsion != NULL; torsion = mol->nextTorsion(torsionIter)) {
327          a = torsion->getAtomA()->getGlobalIndex();
328          b = torsion->getAtomB()->getGlobalIndex();        
329          c = torsion->getAtomC()->getGlobalIndex();        
# Line 334 | Line 368 | void SimInfo::removeExcludePairs(Molecule* mol) {
368          exclude_.removePair(b, c);        
369      }
370  
371 <    for (torsion= mol->beginTorsion(torsionIter); torsion != NULL; torsion = mol->nextBond(torsionIter)) {
371 >    for (torsion= mol->beginTorsion(torsionIter); torsion != NULL; torsion = mol->nextTorsion(torsionIter)) {
372          a = torsion->getAtomA()->getGlobalIndex();
373          b = torsion->getAtomB()->getGlobalIndex();        
374          c = torsion->getAtomC()->getGlobalIndex();        
# Line 355 | Line 389 | void SimInfo::addMoleculeStamp(MoleculeStamp* molStamp
389      int curStampId;
390  
391      //index from 0
392 <    curStampId = molStampIds_.size();
392 >    curStampId = moleculeStamps_.size();
393  
394      moleculeStamps_.push_back(molStamp);
395 <    molStampIds_.insert(molStampIds_.end(), nmol, curStampId)
395 >    molStampIds_.insert(molStampIds_.end(), nmol, curStampId);
396   }
397  
398   void SimInfo::update() {
# Line 371 | Line 405 | void SimInfo::update() {
405  
406      setupFortranSim();
407  
408 +    //setup fortran force field
409 +    /** @deprecate */    
410 +    int isError = 0;
411 +    initFortranFF( &fInfo_.SIM_uses_RF , &isError );
412 +    if(isError){
413 +        sprintf( painCave.errMsg,
414 +         "ForceField error: There was an error initializing the forceField in fortran.\n" );
415 +        painCave.isFatal = 1;
416 +        simError();
417 +    }
418 +  
419 +    
420      setupCutoff();
421  
376    //notify fortran whether reaction field is used or not. It is deprecated now
377    //int isError = 0;
378    //initFortranFF( &useReactionField, &isError );
379
380    //if(isError){
381    //    sprintf( painCave.errMsg,
382    //    "SimCreator::initFortran() error: There was an error initializing the forceField in fortran.\n" );
383    //    painCave.isFatal = 1;
384    //    simError();
385    //}
386    
422      calcNdf();
423      calcNdfRaw();
424      calcNdfTrans();
# Line 392 | Line 427 | std::set<AtomType*> SimInfo::getUniqueAtomTypes() {
427   }
428  
429   std::set<AtomType*> SimInfo::getUniqueAtomTypes() {
430 <    typename SimInfo::MoleculeIterator mi;
430 >    SimInfo::MoleculeIterator mi;
431      Molecule* mol;
432 <    typename Molecule::AtomIterator ai;
432 >    Molecule::AtomIterator ai;
433      Atom* atom;
434      std::set<AtomType*> atomTypes;
435  
# Line 426 | Line 461 | void SimInfo::setupSimType() {
461      int useFLARB = 0; //it is not in AtomType yet
462      int useDirectionalAtom = 0;    
463      int useElectrostatics = 0;
464 <    //usePBC and useRF are from globals
465 <    bool usePBC = globals_->getPBC();
466 <    bool useRF = globals_->getUseRF();
464 >    //usePBC and useRF are from simParams
465 >    int usePBC = simParams_->getPBC();
466 >    int useRF = simParams_->getUseRF();
467  
468      //loop over all of the atom types
469      for (i = atomTypes.begin(); i != atomTypes.end(); ++i) {
470 <        useLennardJones |= i->isLennardJones();
471 <        useElectrostatic |= i->isElectrostatic();
472 <        useEAM |= i->isEAM();
473 <        useCharge |= i->isCharge();
474 <        useDirectional |= i->isDirectional();
475 <        useDipole |= i->isDipole();
476 <        useGayBerne |= i->isGayBerne();
477 <        useSticky |= i->isSticky();
478 <        useShape |= i->isShape();
470 >        useLennardJones |= (*i)->isLennardJones();
471 >        useElectrostatic |= (*i)->isElectrostatic();
472 >        useEAM |= (*i)->isEAM();
473 >        useCharge |= (*i)->isCharge();
474 >        useDirectional |= (*i)->isDirectional();
475 >        useDipole |= (*i)->isDipole();
476 >        useGayBerne |= (*i)->isGayBerne();
477 >        useSticky |= (*i)->isSticky();
478 >        useShape |= (*i)->isShape();
479      }
480  
481      if (useSticky || useDipole || useGayBerne || useShape) {
# Line 506 | Line 541 | void SimInfo::setupSimType() {
541      fInfo_.SIM_uses_RF = useRF;
542  
543      if( fInfo_.SIM_uses_Dipoles && fInfo_.SIM_uses_RF) {
544 <        fInfo_.dielect = dielectric;
544 >
545 >        if (simParams_->haveDielectric()) {
546 >            fInfo_.dielect = simParams_->getDielectric();
547 >        } else {
548 >            sprintf(painCave.errMsg,
549 >                    "SimSetup Error: No Dielectric constant was set.\n"
550 >                    "\tYou are trying to use Reaction Field without"
551 >                    "\tsetting a dielectric constant!\n");
552 >            painCave.isFatal = 1;
553 >            simError();
554 >        }
555 >        
556      } else {
557          fInfo_.dielect = 0.0;
558      }
# Line 528 | Line 574 | void SimInfo::setupFortranSim() {
574  
575      //calculate mass ratio of cutoff group
576      std::vector<double> mfact;
577 <    typename SimInfo::MoleculeIterator mi;
577 >    SimInfo::MoleculeIterator mi;
578      Molecule* mol;
579 <    typename Molecule::CutoffGroupIterator ci;
579 >    Molecule::CutoffGroupIterator ci;
580      CutoffGroup* cg;
581 <    typename Molecule::AtomIterator ai;
581 >    Molecule::AtomIterator ai;
582      Atom* atom;
583      double totalMass;
584  
# Line 566 | Line 612 | void SimInfo::setupFortranSim() {
612      //molMembershipArray is filled by SimCreator    
613      std::vector<int> molMembershipArray(nGlobalAtoms_);
614      for (int i = 0; i < nGlobalAtoms_; i++) {
615 <        molMembershipArray.push_back(globalMolMembership_[i] + 1);
615 >        molMembershipArray[i] = globalMolMembership_[i] + 1;
616      }
617      
618      //setup fortran simulation
619      //gloalExcludes and molMembershipArray should go away (They are never used)
620      //why the hell fortran need to know molecule?
621      //OOPSE = Object-Obfuscated Parallel Simulation Engine
622 <    
623 <    setFortranSim( &fInfo_, &nGlobalAtoms_, &nAtoms_, &identArray[0], &nExclude, exclude_->getExcludeList(),
624 <                  &nGlobalExcludes, globalExcludes, molMembershipArray,
622 >    int nGlobalExcludes = 0;
623 >    int* globalExcludes = NULL;
624 >    int* excludeList = exclude_.getExcludeList();
625 >    setFortranSim( &fInfo_, &nGlobalAtoms_, &nAtoms_, &identArray[0], &nExclude, excludeList ,
626 >                  &nGlobalExcludes, globalExcludes, &molMembershipArray[0],
627                    &mfact[0], &nCutoffGroups_, &fortranGlobalGroupMembership[0], &isError);
628  
629      if( isError ){
# Line 601 | Line 649 | void SimInfo::setupFortranParallel() {
649      //SimInfo is responsible for creating localToGlobalAtomIndex and localToGlobalGroupIndex
650      std::vector<int> localToGlobalAtomIndex(getNAtoms(), 0);
651      std::vector<int> localToGlobalCutoffGroupIndex;
652 <    typename SimInfo::MoleculeIterator mi;
653 <    typename Molecule::AtomIterator ai;
654 <    typename Molecule::CutoffGroupIterator ci;
652 >    SimInfo::MoleculeIterator mi;
653 >    Molecule::AtomIterator ai;
654 >    Molecule::CutoffGroupIterator ci;
655      Molecule* mol;
656      Atom* atom;
657      CutoffGroup* cg;
# Line 632 | Line 680 | void SimInfo::setupFortranParallel() {
680      parallelData.nGroupsGlobal = getNGlobalCutoffGroups();
681      parallelData.nGroupsLocal = getNCutoffGroups();
682      parallelData.myNode = worldRank;
683 <    MPI_Comm_size(MPI_COMM_WORLD, &(parallelData->nProcessors));
683 >    MPI_Comm_size(MPI_COMM_WORLD, &(parallelData.nProcessors));
684  
685      //pass mpiSimData struct and index arrays to fortran
686 <    setFsimParallel(parallelData, &(parallelData->nAtomsLocal),
687 <                    &localToGlobalAtomIndex[0],  &(parallelData->nGroupsLocal),
686 >    setFsimParallel(&parallelData, &(parallelData.nAtomsLocal),
687 >                    &localToGlobalAtomIndex[0],  &(parallelData.nGroupsLocal),
688                      &localToGlobalCutoffGroupIndex[0], &isError);
689  
690      if (isError) {
# Line 657 | Line 705 | double SimInfo::calcMaxCutoffRadius() {
705   double SimInfo::calcMaxCutoffRadius() {
706  
707  
708 <    std::vector<AtomType*> atomTypes;
709 <    std::vector<AtomType*>::iterator i;
708 >    std::set<AtomType*> atomTypes;
709 >    std::set<AtomType*>::iterator i;
710      std::vector<double> cutoffRadius;
711  
712      //get the unique atom types
# Line 669 | Line 717 | double SimInfo::calcMaxCutoffRadius() {
717          cutoffRadius.push_back(forceField_->getRcutFromAtomType(*i));
718      }
719  
720 <    double maxCutoffRadius = std::max_element(cutoffRadius.begin(), cutoffRadius.end());
720 >    double maxCutoffRadius = *(std::max_element(cutoffRadius.begin(), cutoffRadius.end()));
721   #ifdef IS_MPI
722      //pick the max cutoff radius among the processors
723   #endif
# Line 683 | Line 731 | void SimInfo::setupCutoff() {
731      
732      if (fInfo_.SIM_uses_Charges | fInfo_.SIM_uses_Dipoles | fInfo_.SIM_uses_RF) {
733          
734 <        if (!globals_->haveRcut()){
734 >        if (!simParams_->haveRcut()){
735              sprintf(painCave.errMsg,
736                  "SimCreator Warning: No value was set for the cutoffRadius.\n"
737                  "\tOOPSE will use a default value of 15.0 angstroms"
# Line 692 | Line 740 | void SimInfo::setupCutoff() {
740              simError();
741              rcut_ = 15.0;
742          } else{
743 <            rcut_ = globals_->getRcut();
743 >            rcut_ = simParams_->getRcut();
744          }
745  
746 <        if (!globals_->haveRsw()){
746 >        if (!simParams_->haveRsw()){
747              sprintf(painCave.errMsg,
748                  "SimCreator Warning: No value was set for switchingRadius.\n"
749                  "\tOOPSE will use a default value of\n"
# Line 704 | Line 752 | void SimInfo::setupCutoff() {
752              simError();
753              rsw_ = 0.95 * rcut_;
754          } else{
755 <            rsw_ = globals_->getRsw();
755 >            rsw_ = simParams_->getRsw();
756          }
757  
758      } else {
759          // if charge, dipole or reaction field is not used and the cutofff radius is not specified in
760          //meta-data file, the maximum cutoff radius calculated from forcefiled will be used
761          
762 <        if (globals_->haveRcut()) {
763 <            rcut_ = globals_->getRcut();
762 >        if (simParams_->haveRcut()) {
763 >            rcut_ = simParams_->getRcut();
764          } else {
765              //set cutoff radius to the maximum cutoff radius based on atom types in the whole system
766              rcut_ = calcMaxCutoffRadius();
767          }
768  
769 <        if (globals_->haveRsw()) {
770 <            rsw_  = globals_->getRsw()
769 >        if (simParams_->haveRsw()) {
770 >            rsw_  = simParams_->getRsw();
771          } else {
772              rsw_ = rcut_;
773          }
# Line 756 | Line 804 | GenericData* SimInfo::getPropertyByName(const std::str
804      return properties_.getPropertyByName(propName);
805   }
806  
807 + void SimInfo::setSnapshotManager(SnapshotManager* sman) {
808 +    sman_ = sman;
809  
810 < std::ostream& operator <<(ostream& o, SimInfo& info) {
810 >    Molecule* mol;
811 >    RigidBody* rb;
812 >    Atom* atom;
813 >    SimInfo::MoleculeIterator mi;
814 >    Molecule::RigidBodyIterator rbIter;
815 >    Molecule::AtomIterator atomIter;;
816 >
817 >    for (mol = beginMolecule(mi); mol != NULL; mol = nextMolecule(mi)) {
818 >        
819 >        for (atom = mol->beginAtom(atomIter); atom != NULL; atom = mol->nextAtom(atomIter)) {
820 >            atom->setSnapshotManager(sman_);
821 >        }
822 >        
823 >        for (rb = mol->beginRigidBody(rbIter); rb != NULL; rb = mol->nextRigidBody(rbIter)) {
824 >            rb->setSnapshotManager(sman_);
825 >        }
826 >    }    
827 >    
828 > }
829  
830 + Vector3d SimInfo::getComVel(){
831 +    SimInfo::MoleculeIterator i;
832 +    Molecule* mol;
833 +
834 +    Vector3d comVel(0.0);
835 +    double totalMass = 0.0;
836 +    
837 +
838 +    for (mol = beginMolecule(i); mol != NULL; mol = nextMolecule(i)) {
839 +        double mass = mol->getMass();
840 +        totalMass += mass;
841 +        comVel += mass * mol->getComVel();
842 +    }  
843 +
844 + #ifdef IS_MPI
845 +    double tmpMass = totalMass;
846 +    Vector3d tmpComVel(comVel);    
847 +    MPI_Allreduce(&tmpMass,&totalMass,1,MPI_DOUBLE,MPI_SUM, MPI_COMM_WORLD);
848 +    MPI_Allreduce(tmpComVel.getArrayPointer(), comVel.getArrayPointer(),3,MPI_DOUBLE,MPI_SUM, MPI_COMM_WORLD);
849 + #endif
850 +
851 +    comVel /= totalMass;
852 +
853 +    return comVel;
854 + }
855 +
856 + Vector3d SimInfo::getCom(){
857 +    SimInfo::MoleculeIterator i;
858 +    Molecule* mol;
859 +
860 +    Vector3d com(0.0);
861 +    double totalMass = 0.0;
862 +    
863 +    for (mol = beginMolecule(i); mol != NULL; mol = nextMolecule(i)) {
864 +        double mass = mol->getMass();
865 +        totalMass += mass;
866 +        com += mass * mol->getCom();
867 +    }  
868 +
869 + #ifdef IS_MPI
870 +    double tmpMass = totalMass;
871 +    Vector3d tmpCom(com);    
872 +    MPI_Allreduce(&tmpMass,&totalMass,1,MPI_DOUBLE,MPI_SUM, MPI_COMM_WORLD);
873 +    MPI_Allreduce(tmpCom.getArrayPointer(), com.getArrayPointer(),3,MPI_DOUBLE,MPI_SUM, MPI_COMM_WORLD);
874 + #endif
875 +
876 +    com /= totalMass;
877 +
878 +    return com;
879 +
880 + }        
881 +
882 + std::ostream& operator <<(std::ostream& o, SimInfo& info) {
883 +
884      return o;
885   }
886  
887   }//end namespace oopse
888 +

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines