/*! \file unbind.cxx * \brief this file contains routines to check if groups are self-bound and if not unbind them as requried \todo Need to improve the gravity calculation (ie: for tree potential use something other than just monopole and also apply corrections if necessary). \todo Need to clean up unbind proceedure, ensure its mpi compatible and can be combined with a pglist output easily */ #include "logging.h" #include "stf.h" #include "timer.h" ///\name Tree-Potential routines //@{ ///subroutine that generates node list for tree gravity calculation void GetNodeList(Node *np, Int_t &ncell, Node **nodelist, const Int_t bsize){ nodelist[ncell]=np; nodelist[ncell]->SetID(ncell); if (np->GetCount()>bsize){ ncell++;GetNodeList(((SplitNode*)np)->GetLeft(),ncell,nodelist,bsize); ncell++;GetNodeList(((SplitNode*)np)->GetRight(),ncell,nodelist,bsize); } //else ncell++; } ///subroutine that marks a cell for a given particle in tree-walk inline void MarkCell(Node *np, Int_t *marktreecell, Int_t *markleafcell, Int_t &ntreecell, Int_t &nleafcell, Double_t *r2val, const Int_t bsize, Double_t *cR2max, Coordinate *cm, Double_t *cmtot, const Coordinate &xpos, Double_t eps2){ Int_t nid=np->GetID(); Double_t r2; r2=0; //determine the distance from cells cm to particle for (int k=0;k<3;k++)r2+=(cm[nid][k]-xpos[k])*(cm[nid][k]-xpos[k]); //if the particle is not distant enough to treat cell (and all subcells) as a mono (or quad) pole mass then //enter the cell so long as it is not a leaf node (or minimum cell size) //if it is a minimum cell size, the cell is marked with a ileafflag if (r2GetCount()>bsize){ MarkCell(((SplitNode*)np)->GetLeft(),marktreecell,markleafcell,ntreecell,nleafcell,r2val,bsize,cR2max,cm,cmtot,xpos,eps2); MarkCell(((SplitNode*)np)->GetRight(),marktreecell,markleafcell,ntreecell,nleafcell,r2val,bsize,cR2max,cm,cmtot,xpos,eps2); } else markleafcell[nleafcell++]=nid; } else { r2val[ntreecell]=cmtot[nid]/sqrt(r2+eps2); marktreecell[ntreecell++]=nid; } } //@} //@{ inline bool CheckGroupForBoundness(Options &opt, Double_t &Efrac, Double_t &maxE, Int_t ning) { bool unbindcheck; if (opt.uinfo.unbindtype==USYSANDPART) { if(((Efrac0))&&(ning>=opt.MinSize)) unbindcheck=true; else unbindcheck=false; } else if (opt.uinfo.unbindtype==UPART) { if ((maxE>0)&&(ning>=opt.MinSize))unbindcheck=true; else unbindcheck=false; } return unbindcheck; } inline void FillUnboundArrays(Options &opt, int maxunbindsize, Int_t ning, Particle *groupPart, const Double_t &Efrac, Int_t *&nEplusid, int *&Eplusflag, Int_t &nEplus, bool &unbindcheck ) { nEplus=0; if (unbindcheck==false) return; //if just looking at particle then add to removal list till energy >0 if (opt.uinfo.unbindtype==UPART) { for (auto j=0;j0) { nEplusid[nEplus++]=ning-1-j; Eplusflag[ning-1-j]=1; } else break; } } //otherwise, remove all positive energies and also if Efrac< minEfrac, keep adding to removal list else if (opt.uinfo.unbindtype==USYSANDPART) { Int_t nEfrac=0; if (Efrac0 || nEplus~log(numingroup[i]) particles. //we set the limit at 2*log(numingroup[i]) to account for overhead in producing tree and calculating new potential iunbindsizeflag=(nEplus<2.0*log((double)nig)); if (iunbindsizeflag==0) Potential(opt, nig, groupPart); else { for (auto k=0;k0) for (auto j=0;jmaxE) maxE = groupPart[j].GetDensity(); Efrac+=(Ti+groupPart[j].GetPotential()<0); if (groupPart[j].GetDensity()>0) nunbound++; } Efrac/=(Double_t)ning; //if object is not mostly unbound, then sort as will iteratively unbind if (nunbound0) pglist[pfof[i]][numingroup[pfof[i]]++]=i; if (ireorder==1 && iflag&&ngroup>0) { if (groupflag!=NULL) { //allocate memory to store the group value which is a combination of the groupflag value and numingroup Double_t *groupvalue=new Double_t[ng+1]; for (Int_t i=0;i<=ng;i++) { if (numingroup[i]==0) {groupvalue[i]=groupflag[i]=0;} else { if (groupflag[i]==1) groupvalue[i]=numingroup[i]; else groupvalue[i]=1.0/(Double_t)numingroup[i]; } } //and reorder it if reordering group ids if (ireorder) ReorderGroupIDsAndArraybyValue(ng,ngroup,numingroup,pfof,pglist,groupvalue,groupflag); delete[] groupvalue; } else ReorderGroupIDs(ng,ngroup,numingroup,pfof,pglist); } delete[] noffset; #else //if groupflags are provided then explicitly reorder here if required, otherwise internal reordering within unbind. if (groupflag!=NULL) iflag = Unbind(opt, gPart, ngroup, numingroup,pfof,pglist,0); else iflag = Unbind(opt, gPart, ngroup, numingroup,pfof,pglist,ireorder); //if keeping track of a flag, set flag to 0 if group no longer present if (ireorder==1 && iflag&&ngroup>0) { if (groupflag!=NULL) { //allocate memory to store the group value which is a combination of the groupflag value and numingroup Double_t *groupvalue=new Double_t[ng+1]; for (Int_t i=0;i<=ng;i++) { if (numingroup[i]==0) {groupvalue[i]=groupflag[i]=0;} else { if (groupflag[i]==1) groupvalue[i]=numingroup[i]; else groupvalue[i]=1.0/(Double_t)numingroup[i]; } } //and reorder it if reordering group ids if (ireorder) { ReorderGroupIDsAndArraybyValue(ng,ngroup,numingroup,pfof,pglist,groupvalue,groupflag); } delete[] groupvalue; } else { ReorderGroupIDs(ng,ngroup,numingroup,pfof,pglist); } } for (Int_t i=1;i<=ng;i++) delete[] gPart[i];delete[] gPart; #endif if (pglistflag) {for (Int_t i=1;i<=ng;i++) delete[] pglist[i];delete[] pglist;} if (ningflag) delete[] numingroup; LOG(debug) << "Finished unbinding in " << timer << ". Number of groups remaining: " << ngroup; return iflag; } ///Calculate potential of groups inline void CalculatePotentials(Options &opt, Particle **gPart, Int_t &numgroups, Int_t *numingroup) { int maxnthreads,nthreads=1; #ifndef USEMPI int ThisTask=0,NProcs=1; #endif if (!opt.uinfo.icalculatepotential) return; //for parallel environment store maximum number of threads nthreads=1; #ifdef USEOPENMP #pragma omp parallel { if (omp_get_thread_num()==0) maxnthreads=nthreads=omp_get_num_threads(); } #endif //for each group calculate potential //if group is small calculate potentials using PP //here openmp is over groups since each group is small #ifdef USEOPENMP #pragma omp parallel default(shared) { #pragma omp for schedule(dynamic,1) nowait #endif for (auto i=1;i<=numgroups;i++) { if (numingroup[i]<=POTPPCALCNUM) { if (numingroup[i]<0) continue; PotentialPP(opt, numingroup[i], gPart[i]); } } #ifdef USEOPENMP } #endif //reset number of threads to maximum number #ifdef USEOPENMP #pragma omp master { omp_set_num_threads(maxnthreads); } nthreads=maxnthreads; #endif for (auto i=1;i<=numgroups;i++) { if (numingroup[i]<0) continue; if (numingroup[i]>POTPPCALCNUM) { Potential(opt, numingroup[i], gPart[i]); } } } ///Calculate potential of groups, assumes particle list is ordered by group ///and accessed by numingroup and noffset; inline void CalculatePotentials(Options &opt, Particle *gPart, Int_t &numgroups, Int_t *numingroup, Int_t *noffset) { int maxnthreads,nthreads=1; #ifndef USEMPI int ThisTask=0,NProcs=1; #endif if (!opt.uinfo.icalculatepotential) return; //for parallel environment store maximum number of threads nthreads=1; #ifdef USEOPENMP #pragma omp parallel { if (omp_get_thread_num()==0) maxnthreads=nthreads=omp_get_num_threads(); } #endif //for each group calculate potential //if group is small calculate potentials using PP //here openmp is over groups since each group is small #ifdef USEOPENMP #pragma omp parallel default(shared) { #pragma omp for schedule(dynamic,1) nowait #endif for (auto i=1;i<=numgroups;i++) { if (numingroup[i]<=POTPPCALCNUM) { if (numingroup[i]<0) continue; PotentialPP(opt, numingroup[i], &gPart[noffset[i]]); } } #ifdef USEOPENMP } #endif //reset number of threads to maximum number #ifdef USEOPENMP #pragma omp master { omp_set_num_threads(maxnthreads); } nthreads=maxnthreads; #endif for (auto i=1;i<=numgroups;i++) { if (numingroup[i]<0) continue; if (numingroup[i]>POTPPCALCNUM) { Potential(opt, numingroup[i], &gPart[noffset[i]]); } } } ///loop over groups and get velocity frame inline void CalculateBindingReferenceFrame(Options &opt, Particle **gPart, Int_t &numgroups, Int_t *numingroup, Double_t *&gmass, Coordinate *&cmvel) { Double_t potmin,menc; Int_t npot,ipotmin; Coordinate potpos; Int_t *storeval; //if using standard frame, then using CMVEL of the entire structure if (opt.uinfo.fracpotref==1.0) { #ifdef USEOPENMP #pragma omp parallel default(shared) { #pragma omp for schedule(dynamic,1) nowait #endif for (auto i=1;i<=numgroups;i++) { for (auto k=0;k<3;k++) cmvel[i][k]=0; for (auto j=0;j0) { unbindloops=0; oldnumingroup = numingroup[i]; GetBoundFractionAndMaxE(opt, numingroup[i], gPart[i], cmvel[i], Efrac, maxE,nunbound); if (nunbound>=opt.uinfo.maxunboundfracforiterativeunbind*numingroup[i]) { for (j=0;j0 //otherwise, end unbinding. if (nEplusopt.uinfo.maxallowedunboundfrac*oldnumingroup) { oldnumingroup=numingroup[i]; sortflag=true; } //recalculate kinetic energies since cmvel has changed GetBoundFractionAndMaxE(opt, numingroup[i], gPart[i], cmvel[i], Efrac, maxE,nunbound, sortflag); maxunbindsize=(Int_t)(opt.uinfo.maxunbindfrac*nunbound+1); unbindcheck = CheckGroupForBoundness(opt,Efrac,maxE,numingroup[i]); FillUnboundArrays(opt, maxunbindsize, numingroup[i], gPart[i], Efrac, nEplusid, Eplusflag, nEplus, unbindcheck); } } //if group too small remove entirely AdjustPGListForUnbinding(unbindloops,numingroup[i],pglist[i],gPart[i]); RemoveGroup(opt, numingroup[i], pfof, gPart[i], iunbindflag); delete[] nEplusid; delete[] Eplusflag; } } #ifdef USEOPENMP } #endif for (i=1;i<=numgroups;i++) if (numingroup[i]>=ompunbindnum) { unbindloops=0; oldnumingroup = numingroup[i]; GetBoundFractionAndMaxE(opt, numingroup[i], gPart[i], cmvel[i], Efrac, maxE, nunbound); //if amount unbound is very large, just remove group entirely if (nunbound>=opt.uinfo.maxunboundfracforiterativeunbind*numingroup[i]) { for (j=0;j0 //otherwise, end unbinding. if (nEplusopt.uinfo.maxallowedunboundfrac*oldnumingroup) { oldnumingroup=numingroup[i]; sortflag=true; } //recalculate kinetic energies since cmvel has changed GetBoundFractionAndMaxE(opt, numingroup[i], gPart[i], cmvel[i], Efrac, maxE, nunbound,sortflag); //determine if any particle number of particle with positive energy upto opt.uinfo.maxunbindfrac*numingroup+1 maxunbindsize=(Int_t)(opt.uinfo.maxunbindfrac*nunbound+1); unbindcheck = CheckGroupForBoundness(opt,Efrac,maxE,numingroup[i]); FillUnboundArrays(opt, maxunbindsize, numingroup[i], gPart[i], Efrac, nEplusid, Eplusflag, nEplus, unbindcheck); } } AdjustPGListForUnbinding(unbindloops,numingroup[i],pglist[i],gPart[i]); RemoveGroup(opt, numingroup[i], pfof, gPart[i], iunbindflag); delete[] nEplusid; delete[] Eplusflag; } } for (i=1;i<=numgroups;i++) if (numingroup[i]==0) ng--; if (ireorder==1 && iunbindflag&&ng>0) ReorderGroupIDs(numgroups,ng,numingroup,pfof,pglist); delete[] cmvel; delete[] gmass; numgroups=ng; //return if any unbinding done indicating groups have been reordered if (iunbindflag) return 1; else return 0; } /// Calculates the gravitational potential using a kd-tree and monopole expansion ///\todo need ewald correction for periodic systems and also use more than monopole. void Potential(Options &opt, Int_t nbodies, Particle *Part, Double_t *potV) { Potential(opt, nbodies, Part); for (auto i=0;iTPHYS, tree->KEPAN, 100, 0, 0, 0, NULL, NULL, runomp); if (part != Part) tree->OverWriteInputOrder(); PotentialTree(opt, nbodies, part, tree); //and assign potentials back if running approximate potential calculation //i.e., particle pointer does not point to original particle pointer if (part != Part) { nsearch = min(4,(int)ceil(mr+1)); PotentialInterpolate(opt, oldnbodies, Part, part, tree, mr, nsearch); } delete tree; //if iterating then run again but new shuffled indices //to better sample low potential regions // if (opt.uinfo.approxpotiterate > 0 && part != Part && nbodies<0.5*oldnbodies) { // int numloops = 0; // vector potindex(oldnbodies); // double newpotsum, oldpotsum, deltapot, oldminpot, newminpot, deltaminpot; // double oldsubpotsum, newsubpotsum; // // double deltapot2; // oldpotsum = 0; // oldminpot = newminpot = 0; // for (auto i=0;iTPHYS, tree->KEPAN, // 100, 0, 0, 0, NULL, NULL, runomp); // tree->OverWriteInputOrder(); // PotentialTree(opt, nbodies, part, tree); // PotentialInterpolate(opt, oldnbodies, Part, part, tree, mr, nsearch); // delete tree; // // // for (auto i=0;iTPHYS, tree->KEPAN, // // 100, 0, 0, 0, NULL, NULL, runomp); // // tree->OverWriteInputOrder(); // // PotentialTree(opt, nbodies, part, tree); // // Particle *pblah = &Part[nbodies/2]; // // PotentialInterpolate(opt, oldnbodies-nbodies/2, pblah, part, tree, mr, nsearch); // // pblah = NULL; // // delete tree; // // for (auto i=0;iopt.uinfo.approxpotrelerr || deltaminpot>opt.uinfo.approxpotrelerr) && numloops opt.uinfo.approxpotminnum) { newnbodies = max((Int_t)(opt.uinfo.approxpotnumfrac*nbodies), (Int_t)opt.uinfo.approxpotminnum); } if (newnbodies < 0.5*nbodies) { if (opt.uinfo.approxpotmethod == POTAPPROXMETHODTREE) { //build tree that contains leaf nodes containing the desired //number of particles per leaf node Int_t bsize = ceil(nbodies/(float)newnbodies); KDTree *tree = new KDTree(Part, nbodies, bsize, tree->TPHYS,tree->KEPAN,100); //first get all local leaf nodes; newnbodies = tree->GetNumLeafNodes(); newpart = new Particle[newnbodies]; mr = (double)nbodies/(double)newnbodies; Node *node; vector leafnodes(newnbodies); Int_t inode=0, ipart=0; while (ipartFindLeafNode(ipart); leafnodes[inode].id = inode; leafnodes[inode].istart = node->GetStart(); leafnodes[inode].iend = node->GetEnd(); leafnodes[inode].numtot = node->GetCount(); ipart+=leafnodes[inode].numtot; inode++; } node=NULL; #ifdef USEOPENMP #pragma omp parallel for default(shared) schedule(dynamic) #endif for (auto i=0;i indices(nbodies); for (auto i=0;i POTOMPCALCNUM); nthreads = omp_get_max_threads(); #endif ncell=tree->GetNumNodes(); root=tree->GetRoot(); //to store particles in a given node start=new Int_t[ncell]; end=new Int_t[ncell]; //distance calculations used to determine when one uses cell or when one uses particles cmtot=new Double_t[ncell]; cBmax=new Double_t[ncell]; cR2max=new Double_t[ncell]; cellcm=new Coordinate[ncell]; //to store note list nodelist=new Node*[ncell]; //search tree marktreecell=new Int_t*[nthreads]; markleafcell=new Int_t*[nthreads]; r2val=new Double_t*[nthreads]; npomp=new Node*[nthreads]; for (auto j=0;jGetStart(); end[j]=(nodelist[j])->GetEnd(); cellcm[j][0]=cellcm[j][1]=cellcm[j][2]=0.; cmtot[j]=0; for (auto k=start[j];kGetRoot(); Part[j].SetPotential(0.); ntreecell=nleafcell=0; Coordinate xpos(Part[j].GetPosition()); MarkCell(npomp[tid],marktreecell[tid], markleafcell[tid],ntreecell,nleafcell,r2val[tid],bsize, cR2max, cellcm, cmtot, xpos, eps2); for (auto k=0;k nn; vector dist2; Double_t pot, wsum, w; runomp = (nbodies > POTOMPCALCNUM); #ifdef USEOPENMP #pragma omp parallel default(shared) private(nn, dist2, wsum, pot) \ if (runomp) { #endif nn.resize(nsearch); dist2.resize(nsearch); #ifdef USEOPENMP #pragma omp for schedule(static) #endif for (auto i=0; iFindNearestPos(Part[i].GetPosition(), nn.data(), dist2.data(), nsearch); pot = 0; wsum = 0; if (dist2[0] == 0) { pot = interpolatepart[nn[0]].GetPotential(); } else { for (auto j=0;j