/*! \file search.cxx * \brief this file contains routines that search particle list using FOF routines */ #include //-- Suboutines that search particle list #include "stf.h" #include "swiftinterface.h" #include "logging.h" #include "timer.h" /// \name Searches full system //@{ /*! Search full system without finding outliers first Here search simulation for FOF haloes (either 3d or 6d). Note for 6D search, first 3d FOF halos are found, then velocity scale is set by largest 3DFOF halo, and 6DFOF search is done. If all particles searched but also want to treat baryons differently, then a FOF search is run linking DM particles and DM+baryons but not the other way around (no baryon+DM). That is DM particles are basis for generating links but NOT gas/star/bh particles Also start of implementation to keep the 3DFOF envelopes as separate structures. \todo 3DFOF envelop kept as separate structures is NOT fully tested nor truly implemented just yet. \todo OpenMP parallel finding likely has other opmisations that can be implemented to reduce compute time. */ Int_t* SearchFullSet(Options &opt, const Int_t nbodies, vector &Part, Int_t &numgroups) { Int_t i, *pfof = NULL, *pfoftemp = NULL, minsize; FOFcompfunc fofcmp; FOFcheckfunc fofcheck; fstream Fout; char fname[2000]; Double_t param[20]; Double_t *period=NULL; Double_t vscale2,mtotregion,vx,vy,vz; Double_t *vscale2array = NULL; Coordinate vmean(0,0,0); int maxnthreads,nthreads=1,tid; Int_tree_t *Len = NULL, *Head =NULL, *Next = NULL, *Tail = NULL; Int_t *storetype = NULL,*storeorgIndex = NULL; Int_t *ids, *numingroup=NULL, *noffset = NULL; Int_t *id_3dfof_of_6dfof = NULL; Int_t ng,npartingroups; Int_t totalgroups; Double_t time1,time2, time3; KDTree *tree = NULL; KDTree **tree3dfofomp = NULL; Int_t *p3dfofomp = NULL; int iorder = 1; #ifndef USEMPI int ThisTask=0,NProcs=1; Int_t Nlocal=nbodies; #endif #ifdef USEOPENMP #pragma omp parallel { if (omp_get_thread_num()==0) maxnthreads=omp_get_num_threads(); if (omp_get_thread_num()==0) nthreads=omp_get_num_threads(); } OMP_Domain *ompdomain; int numompregions = ceil(nbodies/(float)opt.openmpfofsize); bool runompfof = (numompregions>=2 && nthreads > 1 && opt.iopenmpfof == 1); #endif if (opt.p>0) { period=new Double_t[3]; for (int j=0;j<3;j++) period[j]=opt.p; } psldata=new StrucLevelData; minsize=opt.HaloMinSize; #ifdef USEMPI //if using MPI, lower minimum number if (NProcs>1) { minsize=MinNumMPI; iorder = 0; } #endif MEMORY_USAGE_REPORT(debug); LOG(info) << "Starting FOF search of entire particle data set"; vr::Timer fof_timer; param[0]=tree->TPHYS; param[1]=(opt.ellxscale*opt.ellxscale)*(opt.ellphys*opt.ellphys)*(opt.ellhalophysfac*opt.ellhalophysfac); param[6]=param[1]; LOG(info) << "Building trees"; #ifdef USEOPENMP //if using openmp produce tree with large buckets as a decomposition of the local mpi domain //to then run local fof searches on each domain before stitching if (runompfof) { vr::Timer t; Double_t rdist = sqrt(param[1]); //determine the omp regions; tree = new KDTree(Part.data(),nbodies,opt.openmpfofsize,tree->TPHYS,tree->KEPAN,100); tree->OverWriteInputOrder(); numompregions=tree->GetNumLeafNodes(); ompdomain = OpenMPBuildDomains(opt, numompregions, tree, rdist); storeorgIndex = new Int_t[nbodies]; for (i=0;iTPHYS,tree->KEPAN,1000,0,0,0,period); tree->OverWriteInputOrder(); LOG(info) << "Finished building single trees in " << t; } LOG(info) << "Search particles using 3DFOF in physical space"; LOG(info) << "Parameters used are : ellphys="<1) {fofcmp=&FOF3dDM;param[7]=DARKTYPE;fofcheck=FOFchecktype;} else fofcmp=&FOF3d; #ifdef USEMPI //if using mpi no need to locally sort just yet and might as well return the Head, Len, Next arrays Head=new Int_tree_t[nbodies];Next=new Int_tree_t[nbodies]; #else Head=NULL;Next=NULL; #endif MEMORY_USAGE_REPORT(debug); #ifdef USEOPENMP //if enough regions then search each individually //then link across omp domains Int_t ompminsize = 2; if (runompfof){ Int_t orgIndex, omp_import_total; int omptask; Double_t rdist = sqrt(param[1]); pfof = new Int_t[nbodies]; for (i=0;i 0) { //then for each omp region determine the particles to "import" from other omp regions ompimport = OpenMPImportParticles(opt, nbodies, Part, pfof, storeorgIndex, numompregions, ompdomain, rdist, omp_nrecv_total, omp_nrecv_offset, omp_import_total); if (omp_import_total > 0) { OpenMPLinkAcross(opt, nbodies, Part, pfof, storeorgIndex, Head, Next, param, fofcheck, numompregions, ompdomain, tree3dfofomp, omp_nrecv_total, omp_nrecv_offset, ompimport); delete[] ompimport; } } //free memory #ifndef USEMPI delete[] Head; delete[] Next; #endif delete[] omp_nrecv_total; delete[] omp_nrecv_offset; #pragma omp parallel default(shared) \ private(i) { #pragma omp for schedule(dynamic) nowait for (i=0;i 0) { numgroups = OpenMPResortParticleandGroups(nbodies, Part, pfof, minsize); } //and reallocate tree if required (that is only if not using MPI but searching for substructure #if !defined(USEMPI) && defined(STRUCDEN) if (numgroups>0 && (opt.iSubSearch==1&&opt.foftype!=FOF6DCORE)) #endif tree = new KDTree(Part.data(),nbodies,opt.Bsize,tree->TPHYS,tree->KEPAN,1000,0,0,0,period); //if running MPI then need to pudate the head, next info #ifdef USEMPI OpenMPHeadNextUpdate(nbodies, Part, numgroups, pfof, Head, Next); #endif } else #endif // USEOPENMP { vr::Timer t; //posible alteration for all particle search if (opt.partsearchtype==PSTALL && opt.iBaryonSearch>1) { pfof=tree->FOFCriterionSetBasisForLinks(fofcmp,param,numgroups,minsize, iorder,0,FOFchecktype,Head,Next); } else { pfof=tree->FOF(sqrt(param[1]),numgroups,minsize,iorder,Head,Next); } LOG(info) << "Finished FOF in " << t; } MEMORY_USAGE_REPORT(debug); #ifndef USEMPI totalgroups=numgroups; //if this flag is set, calculate localfield value here for particles possibly resident in a field structure #ifdef STRUCDEN if (numgroups>0 && (opt.iSubSearch==1&&opt.foftype!=FOF6DCORE)) { storetype=new Int_t[nbodies]; Int_t numinstrucs=0,numlocalden=0; for (i=0;i=1 && opt.partsearchtype==PSTALL)) { // #ifdef HIGHRES // numingroup=BuildNumInGroupTyped(nbodies,numgroups,pfof,Part.data(),DARKTYPE); // for (i=0;i=MINSUBSIZE); // else Part[i].SetType(-1); // numlocalden += (Part[i].GetType()>0); // } // delete[] numingroup; // numingroup = NULL; // #else numingroup=BuildNumInGroup(nbodies, numgroups, pfof); for (i=0;i=MINSUBSIZE)); numlocalden += (Part[i].GetType()>0); } if (opt.fofbgtype>FOF6D) { delete[] numingroup; numingroup = NULL; } // #endif } //otherwise set type to group value for dark matter else { numingroup=BuildNumInGroupTyped(nbodies,numgroups,pfof,Part.data(),DARKTYPE); for (i=0;i=MINSUBSIZE); else Part[i].SetType(-1); numlocalden += (Part[i].GetType()>0); } delete[] numingroup; numingroup = NULL; } for (i=0;i0);} LOG(debug) << "Number of particles in large subhalo searchable structures " << numinstrucs; LOG(debug) << "Number of particles that will have local velocity densitites calculated "<< numlocalden; if (numlocalden>0) GetVelocityDensity(opt, nbodies, Part.data(), tree); for (i=0;i0); LOG(trace) << "Found locally " << numgroups << " with lower min size of " << minsize << ", with " << sum << " particles in all groups"; } if (numgroups) { numingroup=BuildNumInGroup(nbodies, numgroups, pfof); for (i=0;i1) { links_across=MPILinkAcross(nbodies, tree, Part.data(), pfof, Len, Head, Next, param[1], fofcheck, param); } else { links_across=MPILinkAcross(nbodies, tree, Part.data(), pfof, Len, Head, Next, param[1]); } LOG(trace) << "Found " << links_across << " links to particles on other mpi domains "; MPI_Allreduce(&links_across, &links_across_total, 1, MPI_Int_t, MPI_SUM, MPI_COMM_WORLD); MPIUpdateExportList(nbodies,Part.data(),pfof,Len); }while(links_across_total>0); LOG_RANK0(info) << "Finished linking across MPI domains in " << mpi_timer; delete[] FoFDataIn; delete[] FoFDataGet; delete[] PartDataIn; delete[] PartDataGet; //reorder local particle array and delete memory associated with Head arrays, only need to keep Particles, pfof and some id and idexing information delete tree; delete[] Head; delete[] Next; delete[] Len; //Now redistribute groups so that they are local to a processor (also orders the group ids according to size opt.HaloMinSize=MinNumOld;//reset minimum size Int_t newnbodies=MPIGroupExchange(opt, nbodies, Part.data(), pfof); //once groups are local, can free up memory. Might need to increase size //of vector if (NmemlocalNlocal) {Part.resize(Nlocal);Nmemlocal=Nlocal;} LOG(info) << "MPI thread " << ThisTask << " has found " << numgroups; //free up memory now that only need to store pfof and global ids totalgroups=0; for (int j=0;j=2) { minsize=opt.HaloMinSize; Int_t sum=0, maxgroupsize=0; for (i=0;i0&&(opt.iSubSearch==1&&opt.foftype!=FOF6DCORE)) { storetype=new Int_t[Nlocal]; Int_t numinstrucs=0,numlocalden=0; for (i=0;i=1 && opt.partsearchtype==PSTALL)) { // #ifdef HIGHRES // numingroup=BuildNumInGroupTyped(Nlocal,numgroups,pfof,Part.data(),DARKTYPE); // for (i=0;i=MINSUBSIZE); // else Part[i].SetType(-1); // numlocalden += (Part[i].GetType()>0); // } // #else numingroup=BuildNumInGroup(Nlocal, numgroups, pfof); for (i=0;i=MINSUBSIZE)); numlocalden += (Part[i].GetType()>0); } // #endif delete[] numingroup; numingroup=NULL; } //otherwise set type to group value for dark matter else { numingroup=BuildNumInGroupTyped(Nlocal,numgroups,pfof,Part.data(),DARKTYPE); for (i=0;i=MINSUBSIZE); else Part[i].SetType(-1); numlocalden += (Part[i].GetType()>0); } delete[] numingroup; numingroup=NULL; } for (i=0;i0);} Int_t numlocalden_total; MPI_Allreduce(&numlocalden, &numlocalden_total, 1, MPI_Int_t, MPI_SUM, MPI_COMM_WORLD); if (numlocalden_total > 0) { LOG(debug) << "Found " << numlocalden << " particles for which density must be calculated"; LOG(info) << "Going to build tree"; tree=new KDTree(Part.data(),Nlocal,opt.Bsize,tree->TPHYS,tree->KEPAN,100,0,0,0,period); GetVelocityDensity(opt, Nlocal, Part.data(),tree); delete tree; } // Delete exported particles assert(Part.size()==Nlocal); for (i = Nlocal-1; i >= 0; i--) if (Part[i].GetPotential() ==1.0) Part.pop_back(); else break; Nlocal = Part.size(); Int_t * tmppfof = new Int_t [Nlocal]; for (i=0;i0&&numgroups>0) { if (numgroups>0)AdjustStructureForPeriod(opt,Nlocal,Part,numgroups,pfof); } delete[] period; //have now 3dfof groups local to a MPI thread and particles are back in index order that will be used from now on //note that from on, use Nlocal, which is altered in mpi but set to nbodies in non-mpi vr::Timer fof6d_timer; if (opt.fofbgtype<=FOF6D && totalgroups>0) { ///\todo In the 6DFOF, particles are sorted into group order but then resorted back into input index order ///this is not necessary if the tipsy still .grp array does not need to be constructed. ///we would removing storing the old ids alter how the local group lists are stiched together into the larger array ///and remove a slow sort back into original ID order //now if 6dfof search to overcome issues with 3DFOF by searching only particles that have been previously linked by the 3dfof //adjust physical linking length param[1]*=opt.ellhalo6dxfac*opt.ellhalo6dxfac; param[6]=param[1]; //set min size and fof criterion minsize=opt.HaloMinSize; if (opt.fofbgtype!=FOFSTNOSUBSET) fofcmp=&FOF6d; else fofcmp=&FOFStream; Int_t iend=0; npartingroups=0; //if local mpi has numgroups > 0 then sort particles for 6dfof search if (numgroups > 0) { LOG(info) << "Sorting particles for 6dfof/phase-space search"; //sort particles so that largest group is first, 2nd next, etc with untagged at end. storetype=new Int_t[Nlocal]; if (numingroup==NULL) numingroup=new Int_t[numgroups+1]; noffset=new Int_t[numgroups+1]; for (i=0;i<=numgroups;i++) numingroup[i]=noffset[i]=0; for (i=0;i0)*pfof[i]); npartingroups+=(Int_t)(pfof[i]>0); iend+=(pfof[i]==1); numingroup[pfof[i]]++; } for (i=2;i<=numgroups;i++) noffset[i]=noffset[i-1]+numingroup[i-1]; qsort(Part.data(), Nlocal, sizeof(Particle), PIDCompare); //sort(Part.begin(),Part.end(),PIDCompareVec); for (i=0;i0) { vscale2=mtotregion=vx=vy=vz=0; for (i=0;i0) vscale2/=mtotregion; } #ifdef USEMPI Double_t mpi_vscale2; MPI_Allreduce(&vscale2,&mpi_vscale2,1,MPI_Real_t,MPI_MAX,MPI_COMM_WORLD); vscale2=mpi_vscale2; #endif //to account for fact that dispersion may not be high enough to link all outlying regions //scale by 6d velocity factor vscale2*=opt.ellhalo6dvfac*opt.ellhalo6dvfac; //set the velocity scale param[2]=(vscale2); param[7]=param[2]; LOG(info) << "Search " << npartingroups << " particles using 6DFOF with uniform velocity scale"; LOG(info) << "Parameters used are : ellphys=" << std::sqrt(param[6]) << " Lunits, ellvel=" << std::sqrt(param[7]) << " Vunits"; } //otherwise each object has its own velocity scale else if(opt.fofbgtype==FOF6DADAPTIVE || opt.iKeepFOF){ //if local mpi domain has groups, proceed wih calculation //of velocity scales vscale2array=new Double_t[numgroups+1]; if (numgroups > 0) { #ifdef USEOPENMP #pragma omp parallel default(shared) \ private(i,vscale2,mtotregion,vx,vy,vz,vmean) { #pragma omp for schedule(dynamic,1) nowait #endif for (i=1;i<=numgroups;i++) { vscale2=mtotregion=vx=vy=vz=0; for (Int_t j=0;j 0) { #ifdef USEOPENMP #pragma omp parallel default(shared) \ private(i,tid,xscaling,vscaling) { #pragma omp for schedule(dynamic,1) nowait #endif for (i=1;i<=iend;i++) { #ifdef USEOPENMP tid=omp_get_thread_num(); #else tid=0; #endif //if adaptive 6dfof, set params if (opt.fofbgtype==FOF6DADAPTIVE) paramomp[2+tid*20]=paramomp[7+tid*20]=vscale2array[i]; //scale particle positions xscaling=1.0/sqrt(paramomp[1+tid*20]); vscaling=1.0/sqrt(paramomp[2+tid*20]); for (Int_t j=0;jTPHS,tree->KEPAN,100); pfofomp[i]=treeomp[tid]->FOF(1.0,ngomp[i],minsize,1,&Head[noffset[i]],&Next[noffset[i]],&Tail[noffset[i]],&Len[noffset[i]]); delete treeomp[tid]; for (Int_t j=0;j0) { LOG_RANK0(trace) << "Storing the 3D fof envelopes of the 6d fof structures found"; Int_t *pfof6dfof=new Int_t[Nlocal]; for (i=0;i0)*ng; } ng+=ngomp[i]; delete[] pfofomp[i]; } //now that all groups have been processed, need to adjust the number of 3DFOF groups as a 6dFOF group can span a entire 3DFOF group opt.num3dfof = numgroups; // store whether the 3dfof group remains int *i3dfofleft = new int[numgroups+1]; //store 3dfof id of a 6dfof group Int_t *id3dfof = new Int_t[ng+1]; // used to quickly access myigmid Int_t *n3dfofoffset = new Int_t[numgroups+1]; n3dfofoffset[0] = n3dfofoffset[1] = 0; for (i=2;i<=numgroups;i++) n3dfofoffset[i] = n3dfofoffset[i-1] + ngomp[i-1]; LOG(debug) << "Before 6dfof search had " << numgroups << " 3dfof groups "; Int_t numin3dfof; for (i=1;i<=numgroups;i++) { numin3dfof=0; for (int j = 0; j < numingroup[i]; j++) numin3dfof += (pfof6dfof[ids[Part[noffset[i]+j].GetID()+noffset[i]]]==0); if (numin3dfof==0) { opt.num3dfof--; i3dfofleft[i]=0; } else i3dfofleft[i]=1; } LOG(debug) << "Now have " << opt.num3dfof; // now have corrected the number of opt.num3dfof. // allocate the array storing the id of the 3dfof to which the 6dfof belongs id_3dfof_of_6dfof = new Int_t [opt.num3dfof + ng + 1]; for (i = 1; i <= opt.num3dfof; i++) id_3dfof_of_6dfof[i]=0; // now have corrected the number of opt.num3dfof. Adjust original pfof opt.num3dfof = 0; for (i=1;i<=numgroups;i++) { // if 3dfof remains if (i3dfofleft[i] == 1) { opt.num3dfof++; for (int j = 0; j < numingroup[i]; j++) pfof[ids[Part[noffset[i]+j].GetID()+noffset[i]]]=opt.num3dfof; for (int j=0;j 0){ ng=0; for (i=0;i0)*ng; } ng+=ngomp[i]; delete[] pfofomp[i]; } } else { for (i=0;i0 && opt.iKeepFOF==0) { LOG(debug) << "Reordering " << ng << " groups "; numingroup=BuildNumInGroup(Nlocal, ng, pfof); Int_t **pglist=BuildPGList(Nlocal, ng, numingroup, pfof); ReorderGroupIDs(ng, ng, numingroup, pfof, pglist); for (i=1;i<=ng;i++) delete[] pglist[i]; delete[] pglist; delete[] numingroup; } ///\todo only run this sort if necessary to keep id order for (i=0;i=1) { CheckUnboundGroups(opt,Nlocal,Part.data(),numgroups,pfof); #ifdef USEMPI LOG_RANK0(info) << "After unnbinding halos"; //update number of groups if extra secondary search done if (opt.fofbgtype>FOF6D) { LOG(info) << "MPI thread " << ThisTask << " has found " << numgroups; MPI_Allgather(&numgroups, 1, MPI_Int_t, mpi_ngroups, 1, MPI_Int_t, MPI_COMM_WORLD); //free up memory now that only need to store pfof and global ids if (ThisTask==0) { int totalgroups=0; for (int j=0;jAllocate(numgroups); psldata->Initialize(); for (i=0;i0) { if (psldata->gidhead[pfof[i]]==NULL) { //set the group id head pointer to the address within the pfof array psldata->gidhead[pfof[i]]=&pfof[i]; //set particle pointer to particle address psldata->Phead[pfof[i]]=&Part[i]; //set the parent pointers to appropriate addresss such that the parent and uber parent are the same as the groups head psldata->gidparenthead[pfof[i]]=&pfof[i]; psldata->giduberparenthead[pfof[i]]=&pfof[i]; //set structure type psldata->stypeinlevel[pfof[i]]=HALOSTYPE; } } } psldata->stype=HALOSTYPE; } else { // initialize the 3dfof level of the hierarchy psldata->Allocate(opt.num3dfof); psldata->Initialize(); psldata->stype = FOF3DTYPE; for (i = 0; i < Nlocal; i++) { if ((pfof[i] <= opt.num3dfof) && (pfof[i] > 0)) { if (psldata->gidhead[pfof[i]] == NULL) { psldata->gidhead[pfof[i]] = &pfof[i]; psldata->Phead[pfof[i]] = &Part[i]; psldata->gidparenthead[pfof[i]] = &pfof[i]; psldata->giduberparenthead[pfof[i]] = &pfof[i]; psldata->stypeinlevel[pfof[i]] = FOF3DTYPE; } } } //adjust ng to 6dfof number ng=numgroups-opt.num3dfof; if (ng>0) { //reorder in descending 6dfof group size order only if keeping FOF numingroup=BuildNumInGroup(Nlocal, numgroups, pfof); Int_t **pglist=BuildPGList(Nlocal, numgroups, numingroup, pfof); Int_t *value6d3d=new Int_t[numgroups+1]; //store size of largest 6dfof group to offset 3dfof when ordering 6d, leaving 3d order unchanged Int_t maxval=0; for (i=opt.num3dfof+1;i<=numgroups;i++) if (maxvalnextlevel = new StrucLevelData(); psldata->nextlevel->Phead = new Particle*[ng+1]; psldata->nextlevel->gidhead = new Int_t*[ng+1]; psldata->nextlevel->Pparenthead = new Particle*[ng+1]; psldata->nextlevel->gidparenthead = new Int_t*[ng+1]; psldata->nextlevel->giduberparenthead = new Int_t*[ng+1]; psldata->nextlevel->stypeinlevel = new Int_t[ng+1]; psldata->nextlevel->stype = HALOSTYPE; psldata->nextlevel->nsinlevel = ng; psldata->nextlevel->nextlevel = NULL; for (i = 0; i <= ng; i++) { psldata->nextlevel->Phead[i] = NULL; psldata->nextlevel->gidhead[i] = NULL; psldata->nextlevel->gidparenthead[i] = NULL; psldata->nextlevel->Pparenthead[i] = NULL; psldata->nextlevel->giduberparenthead[i] = NULL; psldata->nextlevel->stypeinlevel[i] = 0; } for (i = 0; i < Nlocal; i++) { if (pfof[i] > opt.num3dfof) { if (psldata->nextlevel->gidhead[pfof[i]-opt.num3dfof] == NULL) { psldata->nextlevel->gidhead[pfof[i]-opt.num3dfof] = &pfof[i]; psldata->nextlevel->Phead[pfof[i]-opt.num3dfof] = &Part[i]; ///for 6dfof groups that are contained in a 3dfof group make sure to point to head if (id_3dfof_of_6dfof[pfof[i]] > 0) { psldata->nextlevel->gidparenthead[pfof[i]-opt.num3dfof] = psldata->gidhead[id_3dfof_of_6dfof[pfof[i]]]; psldata->nextlevel->Pparenthead[pfof[i]-opt.num3dfof] = psldata->Phead[id_3dfof_of_6dfof[pfof[i]]]; psldata->nextlevel->giduberparenthead[pfof[i]-opt.num3dfof] = psldata->gidhead[id_3dfof_of_6dfof[pfof[i]]]; } //if not in 3dfof then halo is its own uberparent (don't need to set particle head as NULL is fine) else { psldata->nextlevel->gidparenthead[pfof[i]-opt.num3dfof] = &pfof[i]; psldata->nextlevel->giduberparenthead[pfof[i]-opt.num3dfof] = &pfof[i]; psldata->nextlevel->Pparenthead[pfof[i]-opt.num3dfof] = &Part[i]; } psldata->nextlevel->stypeinlevel[pfof[i]-opt.num3dfof] = HALOSTYPE; } } } }//end of ng>0 check } //get memory usage MEMORY_USAGE_REPORT(debug); LOG(info) << "Done storing halo substructure level data"; return pfof; } void AdjustStructureForPeriod(Options &opt, const Int_t nbodies, vector &Part, Int_t numgroups, Int_t *pfof) { Int_t i,j, gid; //Int_t *numingroup, **pglist; //Coordinate c; double diff; #ifndef USEMPI int ThisTask=0,NProcs=1; #endif vector refpos(numgroups+1); vector irefpos(numgroups+1); LOG(info) << "Adjusting for period " << opt.p; for (i=1;i<=numgroups;i++) irefpos[i]=-1; for (i=0;i0.5*opt.p) Part[i].SetPosition(j, Part[i].GetPosition(j)+opt.p); } } #ifdef USEOPENMP } #endif } //@} ///\name Search using outliers from background velocity distribution. //@{ ///Search subset /*! \todo there are issues with locality for iterative search, significance check, regarding MPI threads. Currently these processes assume all necessary particles are locally accessible to the threads domain. For iSingleHalo==0, that is a full search of all fof halos has been done, that is the case as halos are localized to a MPI domain before the subsearch is made. However, in the case a of a single halo which has been broken up into several mpi domains, I must think carefully how the search should be localized. It should definitely be localized prior to CheckSignificance and the search window across mpi domains should use the larger physical search window used by the iterative search if that has been called. */ Int_t* SearchSubset(Options &opt, const Int_t nbodies, const Int_t nsubset, Particle *Partsubset, Int_t &numgroups, Int_t sublevel, Int_t *pnumcores) { KDTree *tree; Int_t *pfof, i, ii; FOFcompfunc fofcmp; fstream Fout; char fname[200]; Double_t param[20]; int nsearch=opt.Nvel; Int_t **nnID; Double_t **dist2; int nthreads=1,maxnthreads,tid; Int_t *numingroup, **pglist; Int_tree_t *GroupTail, *Head, *Next; Int_t bgoffset, *pfofbg, numgroupsbg=0; int maxhalocoresublevel; Int_t numsubs=0; //initialize numgroups=0; if (pnumcores!=NULL) *pnumcores=0; #ifndef USEMPI int ThisTask=0,NProcs=1; #endif #ifdef USEMPI //if using MPI on a single halo, lower minimum number if (opt.iSingleHalo) opt.MinSize=MinNumMPI; #endif //set the maximum sublevel for halo core search. Unless star particles really should only be doing this at first sublevel maxhalocoresublevel=opt.maxnlevelcoresearch; if (opt.partsearchtype==PSTSTAR) maxhalocoresublevel=100; int minsize=opt.MinSize; //for parallel environment store maximum number of threads #ifdef USEOPENMP #pragma omp parallel { if (omp_get_thread_num()==0) maxnthreads=omp_get_num_threads(); if (omp_get_thread_num()==0) nthreads=omp_get_num_threads(); } #endif //set parameters param[1]=(opt.ellxscale*opt.ellxscale)*(opt.ellphys*opt.ellphys); param[2]=(opt.ellvscale*opt.ellvscale)*(opt.ellvel*opt.ellvel); param[6]=(opt.ellxscale*opt.ellxscale)*(opt.ellphys*opt.ellphys); param[7]=(opt.Vratio); // Need to sort particles as during MPI particle sendrecv the order // might change and can produce sightly different results vector storetype(nsubset); //First store the index of this particle in the type data for(int i = 0; i < nsubset; i++) { storetype[i] = Partsubset[i].GetType(); Partsubset[i].SetType(i); } //Sort the particle data based on the particle IDs qsort(Partsubset, nsubset, sizeof(Particle), PIDCompare); //Store the index in another array and reset the type data vector storeindx(nsubset); for(int i = 0; i < nsubset; i++){ storeindx[i] = Partsubset[i].GetType(); Partsubset[i].SetType(storetype[storeindx[i]]); } if (opt.foftype==FOF6DSUBSET) { param[2] = opt.HaloSigmaV*(opt.halocorevfac * opt.halocorevfac); param[7] = param[2]; } param[8]=cos(opt.thetaopen*M_PI); param[9]=opt.ellthreshold; //if iterating slightly increase constraints and decrease minimum number if (opt.iiterflag && opt.foftype==FOFSTPROB) { LOG(trace) << "Increasing thresholds to search for initial list"; if (opt.foftype==FOF6DSUBSET) param[7]*=opt.vfac*opt.vfac; else param[7]*=opt.vfac; param[8]=cos(opt.thetaopen*M_PI*opt.thetafac); param[9]=opt.ellthreshold*opt.ellfac; minsize*=opt.nminfac; } //Set fof type //@{ if (opt.foftype==FOFSTPROB) { LOG(trace) << "FOFSTPROB which uses: ellphys, vratio, thetaopen, and ellthreshold"; LOG(trace) << "Parameters used are : ellphys=" << std::sqrt(param[6]) << " Lunits, vratio=" << param[7] << ", cos(thetaopen)=" << param[8] << ", " << param[9] << " is outlier threshold"; fofcmp=&FOFStreamwithprob; } else if (opt.foftype==FOF6DSUBSET) { LOG(trace) << "FOF6D uses ellphys and ellvel"; LOG(trace) << "Parameters used are : ellphys=" << std::sqrt(param[6]) <<" Lunits, ellvel=" << std::sqrt(param[7]) << " Vunits"; fofcmp=&FOF6d; } else if (opt.foftype==FOFSTPROBNN) { LOG(trace) << "FOFSTPROBNN which uses: ellphys, vratio, thetaopen, and ellthreshold"; LOG(trace) << "Parameters used are : ellphys=" << std::sqrt(param[6]) << " Lunits, vratio=" << param[7] << ", cos(thetaopen)=" << param[8] << ", " << param[9] << " is outlier threshold"; LOG(trace) << "Search is limited to nearest neighbours"; fofcmp=&FOFStreamwithprobNN; } else if (opt.foftype==FOFSTPROBNNLX) { LOG(trace) << "FOFSTPROBNNLX which uses: ellphys, vratio, thetaopen, and ellthreshold"; LOG(trace) << "Parameters used are : ellphys=" << std::sqrt(param[6]) << " Lunits, vratio=" << param[7] << ", cos(thetaopen)=" << param[8] <<", " << param[9] << " is outlier threshold"; LOG(trace) << "Search is limited to nearest neighbours"; fofcmp=&FOFStreamwithprobNNLX; } else if (opt.foftype==FOFSTPROBNNNODIST) { LOG(trace) << "FOFSTPROBNN which uses: vratio, thetaopen, and ellthreshold"; LOG(trace) << "Parameters used are : vratio=" << param[7] << ", cos(thetaopen)=" << param[8] << ", " << param[9] <<" is outlier threshold"; LOG(trace) << "Search is limited to nearest neighbours"; fofcmp=&FOFStreamwithprobNNNODIST; } else if (opt.foftype==FOF6DCORE) { LOG(trace) << "FOF6DCORE which identifies phase-space dense regions and assigns particles, ie core identification and growth"; //just build tree and initialize the pfof array tree=new KDTree(Partsubset,nsubset,opt.Bsize,tree->TPHYS); numgroups=0; pfof=new Int_t[nsubset]; for (i=0;iTPHYS); param[0]=tree->GetTreeType(); //if large enough for statistically significant structures to be found then search. This is a robust search if (nsubset>=MINSUBSIZE) { LOG(trace) << "Now search ..."; pfof=tree->FOFCriterion(fofcmp,param,numgroups,minsize,1,1,FOFchecksub); } else { numgroups=0; pfof=new Int_t[nsubset]; for (i=0;iTPHYS,tree->KEPAN,1000,1); LOG(trace) << "Finding nearest neighbours"; nnID=new Int_t*[nsubset]; for (i=0;iFindNearest(i,nnID[i],dist2[tid],nsearch); } #ifdef USEOPENMP } #endif LOG(trace) << "Done"; LOG(trace) << "Searching nearest neighbours"; pfof=tree->FOFNNCriterion(fofcmp,param,nsearch,nnID,numgroups,minsize); for (i=0;i0 && opt.foftype!=FOF6DCORE) { Int_t ng=numgroups; int mergers; int *igflag,*ilflag; Int_t newlinks, intergrouplinks,*newlinksIndex,*intergrouplinksIndex; Int_t pid,ppid, ss,tail,startpoint; Int_t numgrouplinks,*numgrouplinksIndex,**newIndex,*oldnumingroup,**newintergroupIndex,**intergroupgidIndex; //to slowly expand search, must declare several arrays making it easier to move along a group. numingroup=BuildNumInGroup(nsubset, numgroups, pfof); //here the lists are index order, not id order pglist=BuildPGList(nsubset, numgroups, numingroup, pfof, Partsubset); Head=BuildHeadArray(nsubset,numgroups,numingroup,pglist); Next=BuildNextArray(nsubset,numgroups,numingroup,pglist); GroupTail=BuildGroupTailArray(nsubset,numgroups,numingroup,pglist); newlinksIndex=new Int_t[nsubset]; intergrouplinksIndex=new Int_t[numgroups+1]; igflag=new int[numgroups+1]; ilflag=new int[numgroups+1]; for (i=1;i<=numgroups;i++) igflag[i]=0; // NOTE for openmp, only significantly faster if nnID is 1 D array for reduction purposes // However, this means that in this MPI domain, only nthreads < 2^32 / nsubset can be used since larger numbers require 64 bit array addressing // This should not be an issue but a check is placed anyway #ifdef USEOPENMP #pragma omp parallel { if (omp_get_thread_num()==0) maxnthreads=omp_get_num_threads(); if (omp_get_thread_num()==0) nthreads=omp_get_num_threads(); } if (log((double)nthreads*nsubset)/log(2.0)>32.0) nthreads=(max(1,(int)(pow((double)2,32)/(double)nsubset))); omp_set_num_threads(nthreads); #endif nnID=new Int_t*[1]; nnID[0]=new Int_t[nsubset*nthreads]; numgrouplinksIndex=new Int_t[numgroups+1]; oldnumingroup=new Int_t[numgroups+1]; newIndex=new Int_t*[numgroups+1]; //first search groups near cell size as subhaloes near this scale may have central regions defining background and as such these particles will not appear to be //distinct outliers (will probably have ell~1-2) thus resulting in an underestimate in the mass and maximum circular velocity for compact massive (sub)halos //this linking uses a smaller physical linking length and searchs already tagged particles for any untagged particles included in the larger subset made with the lower ellthreshold //so long as one particle lies about the high ell threshold, the particles can be check to see if they lie in the same phase-space. param[1]=(opt.ellxscale*opt.ellxscale)*(opt.ellphys*opt.ellphys); param[2]=(opt.ellvscale*opt.ellvscale)*(opt.ellvel*opt.ellvel); param[6]=(opt.ellxscale*opt.ellxscale)*(opt.ellphys*opt.ellphys); param[7]=(opt.Vratio); param[8]=cos(opt.thetaopen*M_PI); param[9]=opt.ellthreshold; //if (opt.foftype==FOF6DSUBSET) param[7]/=opt.vfac*opt.vfac; fofcmp=&FOFStreamwithprobIterative; LOG(trace) << "Begin iterative search"; LOG(trace) << "Parameters used are : ellphys=" << std::sqrt(param[6]) << " Lunits, vratio=" << param[7] << ", cos(thetaopen)=" << param[8] << ", " << param[9] <<" is outlier threshold"; //first identify all particles to be searched newlinks=0; for (i=1;i<=numgroups;i++) if (numingroup[i]>opt.Ncell*0.1&&igflag[i]==0) { ss=Head[pglist[i][0]]; do {newlinksIndex[newlinks++]=ss;} while((ss = Next[ss]) >= 0); } //initialize nnID list so that if particle tagged no need to check comparison function for (ii=0;ii that the pfofvalue of the reference particle, its nnID value is set to the reference //particle's group id, otherwise, its left alone (unless its untagged) //if number of searches is large, run search in parallel SearchForNewLinks(nsubset, tree, Partsubset, pfof, fofcmp, param, newlinks, newlinksIndex, nnID, nthreads); tid=0; //determine groups //newIndex=DetermineNewLinks(nsubset, Partsubset, pfof, numgroups, newlinks, newlinksIndex, numgrouplinksIndex, nnID[tid]); DetermineNewLinks(nsubset, Partsubset, pfof, numgroups, newlinks, newlinksIndex, numgrouplinksIndex, nnID[tid],&newIndex); //link all previously unlinked particles for large groups by searching for each group the number of links for that group and linking them (if unlinked) LinkUntagged(Partsubset, numgroups, pfof, numingroup, pglist, newlinks, numgrouplinksIndex, newIndex, Head, Next, GroupTail,nnID[tid]); for (Int_t j=1;j<=numgroups;j++) delete[] newIndex[j]; for (i=0;i=param[9]){newlinksIndex[newlinks++]=ss;}} while((ss = Next[ss]) >= 0); } //now continue to search all new links till there are no more links found tid=0; do { SearchForNewLinks(nsubset, tree, Partsubset, pfof, fofcmp, param, newlinks, newlinksIndex, nnID, nthreads); DetermineNewLinks(nsubset, Partsubset, pfof, numgroups, newlinks, newlinksIndex, numgrouplinksIndex, nnID[tid],&newIndex); LinkUntagged(Partsubset, numgroups, pfof, numingroup, pglist, newlinks, numgrouplinksIndex, newIndex, Head, Next, GroupTail,nnID[tid]); for (Int_t j=1;j<=numgroups;j++) delete[] newIndex[j]; }while(newlinks); //then search for intergroup links. When merging, identify all intergroup links and define merging criterion based on mass ratios, number of links, old mass (prior to expansion), etc LOG(trace) << "Searching for intergroup links"; newintergroupIndex=new Int_t*[numgroups+1]; intergroupgidIndex=new Int_t*[numgroups+1]; newlinks=0; tid=0; for (i=1;i<=numgroups;i++) if (igflag[i]==0) { startpoint=Head[pglist[i][0]]; ss=startpoint; do {if (Partsubset[ss].GetDensity()>=param[9]){newlinksIndex[newlinks++]=ss;}} while((ss = Next[ss]) >= 0); } for (ii=0;ii that the pfofvalue of the reference particle, its nnID value is set to the reference //particle's group id, otherwise, its left alone (unless its untagged) //if number of searches is large, run search in parallel SearchForNewLinks(nsubset, tree, Partsubset, pfof, fofcmp, param, newlinks, newlinksIndex, nnID, nthreads); DetermineGroupLinks(nsubset, Partsubset, pfof, numgroups, newlinks, newlinksIndex, numgrouplinksIndex, nnID[tid], &newIndex); DetermineGroupMergerConnections(Partsubset, numgroups, pfof, ilflag, numgrouplinksIndex, intergrouplinksIndex, nnID[tid], &newIndex, &newintergroupIndex, &intergroupgidIndex); newlinks=0; mergers+=MergeGroups(opt, Partsubset, numgroups, pfof, numingroup, oldnumingroup, pglist, numgrouplinksIndex, &intergroupgidIndex, &newintergroupIndex, intergrouplinksIndex, Head, Next, GroupTail, igflag, nnID[tid], newlinks, newlinksIndex); }while(mergers); param[1]=(opt.ellxscale*opt.ellxscale)*(opt.ellphys*opt.ellphys)*opt.ellxfac*opt.ellxfac;//increase physical linking length slightly again param[6]=(opt.ellxscale*opt.ellxscale)*(opt.ellphys*opt.ellphys)*opt.ellxfac*opt.ellxfac; param[7]=(opt.Vratio)*opt.vfac; param[8]=cos(opt.thetaopen*M_PI*opt.thetafac); param[9]=opt.ellthreshold*opt.ellfac; LOG(trace) << "Begin second expanded search with large linking length "; LOG(trace) << "FOFSTPROB which uses: ellphys, vratio, thetaopen, and ellthreshold"; LOG(trace) << "Parameters used are : ellphys=" << std::sqrt(param[6]) << " Lunits, vratio=" << param[7] << ", cos(thetaopen)=" << param[8] << ", " << param[9] << " is outlier threshold"; //initialization, for merging, store old size prior to expanded search, set nnID newlinks=0; for (i=1;i<=numgroups;i++) if (igflag[i]==0) { startpoint=Head[pglist[i][0]]; ss=startpoint; do {if (Partsubset[ss].GetPotential()>=param[9]){newlinksIndex[newlinks++]=ss;}} while((ss = Next[ss]) >= 0); } //now continue to search all new links till there are no more links found tid=0; do { SearchForNewLinks(nsubset, tree, Partsubset, pfof, fofcmp, param, newlinks, newlinksIndex, nnID, nthreads); DetermineNewLinks(nsubset, Partsubset, pfof, numgroups, newlinks, newlinksIndex, numgrouplinksIndex, nnID[tid],&newIndex); LinkUntagged(Partsubset, numgroups, pfof, numingroup, pglist, newlinks, numgrouplinksIndex, newIndex, Head, Next, GroupTail,nnID[tid]); for (Int_t j=1;j<=numgroups;j++) delete[] newIndex[j]; }while(newlinks); //release memory for (i=1;i<=numgroups;i++)delete[] pglist[i]; delete[] Head; delete[] Next; delete[] GroupTail; delete[] newlinksIndex; delete[] numgrouplinksIndex; delete[] newIndex; delete[] oldnumingroup; delete[] igflag; delete[] nnID[0]; delete[] nnID; delete[] intergrouplinksIndex; delete[] intergroupgidIndex; delete[] newintergroupIndex; delete[] ilflag; #ifdef USEOPENMP #pragma omp parallel { omp_set_num_threads(maxnthreads); } nthreads=maxnthreads; #endif //adjust groups for (i=1;i<=numgroups;i++) numingroup[i]=0; for (i=0;i=1;i--) { if (numingroup[i]==0) ng--; else pglist[i]=new Int_t[numingroup[i]]; numingroup[i]=0; } for (i=0;i0) pglist[pfof[i]][numingroup[pfof[i]]++]=i; if (ng) ReorderGroupIDs(numgroups, ng, numingroup, pfof, pglist); for (i=1;i<=numgroups;i++) if (numingroup[i]>0) delete[] pglist[i]; delete[] pglist; delete[] numingroup; numgroups=ng; LOG(trace) << "After expanded search there are now " << ng << " groups"; } //once substructure groups are found, ensure all substructure groups are significant if (numgroups) { numingroup=BuildNumInGroup(nsubset, numgroups, pfof); //pglist is constructed without assuming particles are in index order pglist=BuildPGList(nsubset, numgroups, numingroup, pfof, Partsubset); // numgroups can change in CheckSignificance, which leads to a memleak auto pglist_numgroups = numgroups; CheckSignificance(opt,nsubset,Partsubset,numgroups,numingroup,pfof,pglist); for (i=1;i<=pglist_numgroups;i++) delete[] pglist[i]; delete[] pglist; delete[] numingroup; } if (numgroups>0) { LOG(trace) << numgroups << " substructures found"; } else { LOG(trace) << "NO SUBSTRUCTURES FOUND"; } //now search particle list for large compact substructures that are considered part of the background when using smaller grids if (nsubset>=MINSUBSIZE && opt.iLargerCellSearch && opt.foftype!=FOF6DCORE) { //first have to delete tree used in search so that particles are in original particle order //then construct a new grid with much larger cells so that new bg velocity dispersion can be estimated LOG(trace) << "Entering large cell search " << opt.iLargerCellSearch; delete tree; Int_t ngrid; Coordinate *gvel; Matrix *gveldisp; GridCell *grid; Double_t nf, ncl=opt.Ncell; //adjust ncellfac locally nf = std::min(opt.Ncellfac * Double_t{8}, Double_t{MAXCELLFRACTION}); opt.Ncell=nf*nsubset; //ONLY calculate grid quantities if substructures have been found if (numgroups>0) { tree=InitializeTreeGrid(opt,nsubset,Partsubset); ngrid=tree->GetNumLeafNodes(); LOG(trace) << "bg search using " << ngrid << " grid cells, with each node containing ~" << (opt.Ncell = nsubset / ngrid) << " particles"; grid=new GridCell[ngrid]; FillTreeGrid(opt, nsubset, ngrid, tree, Partsubset, grid); gvel=GetCellVel(opt,nsubset,Partsubset,ngrid,grid); gveldisp=GetCellVelDisp(opt,nsubset,Partsubset,ngrid,grid,gvel); GetDenVRatio(opt,nsubset,Partsubset,ngrid,grid,gvel,gveldisp); GetOutliersValues(opt,nsubset,Partsubset,-1); } ///produce tree to search for 6d phase space structures tree=new KDTree(Partsubset,nsubset,opt.Bsize,tree->TPHYS); //now begin fof6d search for large background objects that are missed using smaller grid cells ONLY IF substructures have been found //this search can identify merger excited radial shells so for the moment, disabled if (numgroups>0) { bgoffset=0; minsize=ncl*0.2; //use same linking length to find substructures param[1]=(opt.ellxscale*opt.ellxscale)*(opt.ellphys*opt.ellphys)*(opt.ellxfac*opt.ellxfac); param[6]=param[1]; //velocity linking length from average sigmav from grid param[7]=opt.HaloLocalSigmaV; param[8]=cos(opt.thetaopen*M_PI); param[9]=opt.ellthreshold*opt.ellfac; LOG(trace) << "FOF6D uses ellphys and ellvel"; LOG(trace) << "Parameters used are : ellphys=" << std::sqrt(param[6]) << " Lunits, ellvel=" << std::sqrt(param[7]) << " Vunits"; LOG(trace) << param[9] << " is outlier threshold and contains more than " << minsize; //then reset to find large subhalo cores close to cell size limit fofcmp=&FOF6dbgup; //here this ensures that particles belong to a 6dfof substructure that is composed of particles //which are considered dynamical outliers using a very large grid pfofbg=tree->FOFCriterion(fofcmp,param,numgroupsbg,minsize,1,1,FOFchecksub); //now combine results such that if particle already belongs to a substructure ignore //otherwise leave tagged. Then check if these new background substructures share enough links with the //other substructures that they should be joined. if (numgroupsbg>=bgoffset+1) { int mergers; int *igflag,*ilflag; Int_t newlinks, oldlinks,intergrouplinks,*newlinksIndex,*oldlinksIndex,*intergrouplinksIndex; Int_t pid,ppid, ss,tail,startpoint; Int_t numgrouplinks,*numgrouplinksIndex,**newIndex,**newintergroupIndex,**intergroupgidIndex; Int_t ng=numgroups+(numgroupsbg-bgoffset),oldng=numgroups; for (i=0;i0) pfof[i]=oldng+(pfofbg[i]-bgoffset); numgroups+=(numgroupsbg-bgoffset); LOG(trace) << "Found " << numgroups; // numingroup=BuildNumInGroup(nsubset, numgroups, pfof); pglist=BuildPGList(nsubset, numgroups, numingroup, pfof, Partsubset); Head=BuildHeadArray(nsubset,numgroups,numingroup,pglist); Next=BuildNextArray(nsubset,numgroups,numingroup,pglist); GroupTail=BuildGroupTailArray(nsubset,numgroups,numingroup,pglist); newlinksIndex=new Int_t[nsubset]; oldlinksIndex=new Int_t[nsubset]; intergrouplinksIndex=new Int_t[numgroups+1]; igflag=new int[numgroups+1]; ilflag=new int[numgroups+1]; for (i=1;i<=numgroups;i++) igflag[i]=0; #ifdef USEOPENMP #pragma omp parallel { if (omp_get_thread_num()==0) maxnthreads=omp_get_num_threads(); if (omp_get_thread_num()==0) nthreads=omp_get_num_threads(); } if (log((double)nthreads*nsubset)/log(2.0)>32.0) nthreads=(max(1,(int)(pow((double)2,32)/(double)nsubset))); omp_set_num_threads(nthreads); #endif nnID=new Int_t*[1]; nnID[0]=new Int_t[nsubset*nthreads]; numgrouplinksIndex=new Int_t[numgroups+1]; newIndex=new Int_t*[numgroups+1]; //initialize nnID list so that if particle tagged no need to check comparison function for (ii=0;ii= 0); } for (ii=0;ii0) { startpoint=Head[pglist[i][0]]; ss=startpoint; do {{newlinksIndex[newlinks++]=ss;}} while((ss = Next[ss]) >= 0); } else igflag[i]=1; for (ii=0;ii0) { startpoint=Head[pglist[i][0]]; ss=startpoint; do {{newlinksIndex[newlinks++]=ss;}} while((ss = Next[ss]) >= 0); } else igflag[i]=1; fofcmp=&FOFStreamwithprob; param[1]=(opt.ellxscale*opt.ellxscale)*(opt.ellphys*opt.ellphys)*opt.ellxfac*opt.ellxfac;//increase physical linking length slightly param[6]=(opt.ellxscale*opt.ellxscale)*(opt.ellphys*opt.ellphys)*opt.ellxfac*opt.ellxfac; param[7]=(opt.Vratio)*opt.vfac; param[8]=cos(opt.thetaopen*M_PI*opt.thetafac); param[9]=-3.0; mergers=0; SearchForNewLinks(nsubset, tree, Partsubset, pfof, fofcmp, param, newlinks, newlinksIndex, nnID, nthreads); for (ii=0;ii=1;i--) { if (numingroup[i]==0) ng--; else pglist[i]=new Int_t[numingroup[i]]; numingroup[i]=0; } for (i=0;i0) pglist[pfof[i]][numingroup[pfof[i]]++]=i; if (ng) ReorderGroupIDs(numgroups, ng, numingroup, pfof, pglist); for (i=1;i<=numgroups;i++) if (numingroup[i]>0) delete[] pglist[i]; delete[] pglist; delete[] numingroup; numgroups=ng; LOG(trace) << "After expanded search there are now " << ng << " groups"; numgroupsbg=0; } else { LOG(trace) << "No large background substructure groups found"; } delete[] pfofbg; } //output results of search if (numgroups>0) { LOG(trace) << numgroups << " substructures found after large grid search"; } else { LOG(trace) << "NO SUBSTRUCTURES FOUND"; } } numsubs=numgroups; //ONCE ALL substructures are found, search for cores of major mergers with minimum size set by cell size since grid is quite large after bg search //for missing large substructure cores if((opt.iHaloCoreSearch>0&&((!opt.iSingleHalo&&sublevel<=maxhalocoresublevel)||(opt.iSingleHalo&&sublevel==0)))||opt.foftype==FOF6DCORE) { LOG(trace) << "Beginning 6dfof core search to find multiple cores"; bgoffset=1; //if adaptive core linking then need to calculate dispersion tensors in configuration and velocity space if (opt.iAdaptiveCoreLinking) { double xscale2; Double_t XX, YY, ZZ; Double_t VOL; Double_t nn; //calculate inertia tensor (assumes that system is alread in CM frame ///\todo should add checks to see if system is in CM frame and if it is not, move it to that frame Matrix myeigvec, myI; CalcPosSigmaTensor(nsubset, Partsubset, XX, YY, ZZ, myeigvec, myI, -1); xscale2=sqrt(XX+YY+ZZ); //volume of configuration ellipsoid VOL = 4/3.0 * acos(-1) * sqrt(XX*XX*XX)*pow(opt.halocoresigmafac,3.0); //number density nn = pow(VOL/nsubset,1./3.); //now adjust linking length //param[1] = xscale2 * nn * nn * opt.halocorexfac * opt.halocorexfac; param[1] = nn*nn*opt.halocorexfac * opt.halocorexfac; param[6] = param[1]; //now the same for velocity space ///\todo still need to look at optimal scaling function for velocities CalcVelSigmaTensor(nsubset, Partsubset, XX, YY, ZZ, myeigvec, myI, -1); VOL = 4/3.0 * acos(-1) * sqrt(XX*YY*ZZ); nn = pow(VOL/nsubset,1./3.); param[2] = XX*(opt.halocorevfac * opt.halocorevfac); param[7] = param[2]; } //not adaptive, using halo based linking lengths and halo dispersion else { if ((opt.iKeepFOF) && (opt.ellhalo6dxfac > 0)) param[1] = (opt.ellxscale * opt.ellphys * opt.ellhalo6dxfac * opt.halocorexfac); else param[1] = (opt.ellxscale * opt.ellphys * opt.ellhalophysfac * opt.halocorexfac); //if at a sublevel then start at a higher density threshold param[1] *= pow(opt.halocorexfac,sublevel-1); param[1] = param[1] * param[1]; param[6] = param[1]; //velocity linking length from average sigmav from FINE SCALE grid param[2] = opt.HaloSigmaV * (opt.halocorevfac * opt.halocorevfac); param[7] = param[2]; } //set minsize of the core. //for a galaxy search (ie: PSTSTAR, we set this to the min search size, //but for others, smaller (DM) substructures have already been cleaned. if (opt.partsearchtype!=PSTSTAR) { minsize=nsubset*opt.halocorenfac*pow(opt.halocorenumfaciter,sublevel-1); minsize=max(minsize,opt.MinSize); } else if (opt.foftype==FOF6DCORE || opt.partsearchtype==PSTSTAR){ minsize=opt.MinSize; } LOG(trace) << "Parameters used are : ellphys=" << std::sqrt(param[6]) << " Lunits, ellvel=" << std::sqrt(param[7]) <<" Vunits with minimum size of " << minsize; //start first 6d search fofcmp=&FOF6d; //here search for 6dfof groups, return ordered list. Also pass function check to ignore already tagged particles int iorder=1,icheck=1,numloops=0,numactiveloops=0; //for ignoring already tagged particles can just use the oultier potential and FOFcheckbg for (i=0;iFOFCriterion(fofcmp,param,numgroupsbg,minsize,iorder,icheck,FOFcheckbg); for (i=0;i dispfac(numgroupsbg+1); //store the level a core is found at vector corelevel(numgroupsbg+1); //now if searching for cores in fully adaptive fashion then process core (which will keep changing) till //no cores are found for (i=1;i<=numgroupsbg;i++) dispfac[i]=1.0; for (i=1;i<=numgroupsbg;i++) corelevel[i]=numactiveloops; if (opt.halocorenumloops>1) { //store the old velocity dispersion Double_t halocoreveldisp = param[7]; //set the factor by which the minimum size is increased by //store the old number of groups and update the pfofbg list Int_t newnumgroupsbg=numgroupsbg; Int_t *pfofbgnew=new Int_t[nsubset]; Int_t pid; //first copy pfofbg information newnumgroupsbg=numgroupsbg; for (i=0;i0)); pfofbg=tree->FOFCriterion(fofcmp,param,numgroupsbg,minsize,iorder,icheck,FOFcheckbg); //now if numgroupsbg is greater than one, need to update the pfofbgnew array if (numgroupsbg>1) { numactiveloops++; for (i=0;i 1 then keep but offset id else if (pfofbg[pid]>1) pfofbgnew[pid]=pfofbg[pid]-1+newnumgroupsbg; } newnumgroupsbg+=numgroupsbg-1; } }while (numgroupsbg > 0 && numloops=bgoffset+1) { LOG(trace) << "Number of cores: " << numgroupsbg; //if cores are found, two options //a pnumcores pointer is passed, store the number of cores in this pointer assumes halo ids will not be reordered, making it easy to flag cores //if opt.iHaloCoreSearch==2 then start searching the halo to identify which particles belong to which core by linking particles to their closest //tagged neighbour in phase-space. if(pnumcores!=NULL) *pnumcores=numgroupsbg; if (opt.iHaloCoreSearch>=2) { HaloCoreGrowth(opt, nsubset, Partsubset, pfof, pfofbg, numgroupsbg, param,dispfac,numactiveloops,corelevel,nthreads); if(pnumcores!=NULL) *pnumcores=numgroupsbg; if (numgroupsbg>=bgoffset+1) { for (i=0;ibgoffset) pfof[i]=numgroups+(pfofbg[i]-bgoffset); numgroupsbg-=bgoffset; numgroups+=numgroupsbg; } LOG(trace) << "After 6dfof core search and assignment there are " << numgroups << " groups"; } } else { LOG(trace) << "No excess cores found indicating mergers"; } delete[] pfofbg; } if (numgroups>0 && opt.coresubmergemindist>0 && nsubset>=MINSUBSIZE) MergeSubstructuresPhase(opt, nsubset, Partsubset, pfof, numgroups, numsubs, numgroupsbg); RemoveSpuriousDynamicalSubstructures(opt,nsubset, pfof, numgroups, numsubs, numgroupsbg); #ifdef USEMPI //now if substructures are subsubstructures, then the region of interest has already been localized to a single MPI domain //so do not need to search other mpi domains if (sublevel==0) { //if using MPI must determine which local particles need to be exported to other threads and used to search //that threads particles. This is done by seeing if the any particles have a search radius that overlaps with //the boundaries of another threads domain. Then once have exported particles must search local particles //relative to these exported particles. Iterate over search till no new links are found. //First have barrier to ensure that all mpi tasks have finished the local search MPI_Barrier(MPI_COMM_WORLD); //To make it easy to search the local particle arrays, build several arrays, in particular, Head, Next, Len arrays Int_tree_t *Len; numingroup=BuildNumInGroup(nsubset, numgroups, pfof); pglist=BuildPGList(nsubset, numgroups, numingroup, pfof,Partsubset); Len=BuildLenArray(nsubset,numgroups,numingroup,pglist); Head=BuildHeadArray(nsubset,numgroups,numingroup,pglist); Next=BuildNextArray(nsubset,numgroups,numingroup,pglist); for (i=1;i<=numgroups;i++)delete[] pglist[i]; delete[] pglist; //Also must ensure that group ids do not overlap between mpi threads so adjust group ids MPI_Allgather(&numgroups, 1, MPI_Int_t, mpi_ngroups, 1, MPI_Int_t, MPI_COMM_WORLD); MPIAdjustLocalGroupIDs(nsubset, pfof); //then determine export particles, declare arrays used to export data #ifdef MPIREDUCEMEM if (opt.impiusemesh) MPIGetExportNumUsingMesh(opt, nbodies, Partsubset, sqrt(param[1])); else MPIGetExportNum(nbodies, Partsubset, sqrt(param[1])); #endif //then determine export particles, declare arrays used to export data PartDataIn = new Particle[NExport]; PartDataGet = new Particle[NExport]; FoFDataIn = new fofdata_in[NExport]; FoFDataGet = new fofdata_in[NExport]; //I have adjusted FOF data structure to have local group length and also seperated the export particles from export fof data //the reason is that will have to update fof data in iterative section but don't need to update particle information. if (opt.impiusemesh) MPIBuildParticleExportListUsingMesh(opt, nsubset, Partsubset, pfof, Len, sqrt(param[1])); else MPIBuildParticleExportList(opt, nsubset, Partsubset, pfof, Len, sqrt(param[1])); //Now that have FoFDataGet (the exported particles) must search local volume using said particles //This is done by finding all particles in the search volume and then checking if those particles meet the FoF criterion //One must keep iterating till there are no new links. //Wonder if i don't need another loop and a final check Int_t links_across,links_across_total; do { links_across=MPILinkAcross(nsubset, tree, Partsubset, pfof, Len, Head, Next, param[1], fofcmp, param); MPIUpdateExportList(nsubset, Partsubset,pfof,Len); MPI_Allreduce(&links_across, &links_across_total, 1, MPI_Int_t, MPI_SUM, MPI_COMM_WORLD); }while(links_across_total>0); //reorder local particle array and delete memory associated with Head arrays, only need to keep Particles, pfof and some id and idexing information delete tree; delete[] Head; delete[] Next; delete[] Len; delete[] numingroup; delete[] FoFDataIn; delete[] FoFDataGet; delete[] PartDataIn; delete[] PartDataGet; //Now redistribute groups so that they are local to a processor (also orders the group ids according to size if (opt.iSingleHalo) opt.MinSize=MinNumOld;//reset minimum size Int_t newnbodies=MPIGroupExchange(opt, nsubset,Partsubset,pfof); ///\todo need to clean up this mpi section for single halo /* #ifndef MPIREDUCEMEM //once groups are local, can free up memory delete[] Partsubset; delete[] pfof; //delete[] mpi_idlist;//since particles have now moved, must generate new list //mpi_idlist=new Int_t[newnbodies]; pfof=new Int_t[newnbodies]; //store new particle data in mpi_Part1 as external variable ensures memory allocated is not deallocated when function returns mpi_Part1=new Particle[newnbodies]; Partsubset=mpi_Part1; #endif */ ///\todo Before final compilation of data, should have unbind here but must adjust unbind so it ///does not call reordergroupids in it though it might be okay. //And compile the information and remove groups smaller than minsize numgroups=MPICompileGroups(opt, newnbodies,Partsubset,pfof,opt.MinSize); MPI_Barrier(MPI_COMM_WORLD); LOG(info) << "MPI thread " << ThisTask << " found "<< numgroups; //free up memory now that only need to store pfof and global ids if (ThisTask==0) { int totalgroups=0; for (int j=0;j tmpfof(nsubset); for (i = 0; i < nsubset; i++){ tmpfof[storeindx[i]] = pfof[i]; Partsubset[i].SetType(storeindx[i]); } // Sort base on the type qsort(Partsubset, nsubset, sizeof(Particle), TypeCompare); //Reset the typedata and set the ID for (i = 0; i < nsubset; i++){ pfof[i] = tmpfof[i]; Partsubset[i].SetType(storetype[i]); Partsubset[i].SetID(i); } LOG(trace) << "Done search for substructure in this subset"; return pfof; } //search for unassigned background particles if cores have been found. void HaloCoreGrowth(Options &opt, const Int_t nsubset, Particle *&Partsubset, Int_t *&pfof, Int_t *&pfofbg, Int_t &numgroupsbg, Double_t param[], vector &dispfac, int numactiveloops, vector &corelevel, int nthreads){ //for simplicity make a new particle array storing core particles Int_t nincore=0,nbucket=opt.Bsize,pid, pidcore; Particle *Pcore,*Pval; KDTree *tcore; Coordinate x1; Double_t D2, dval, mval, weight; vector mcore(numgroupsbg+1, 0.0); vector ncore(numgroupsbg+1, 0); vector newcore(numgroupsbg+1, 0); Int_t newnumgroupsbg=0; int nsearch=opt.Nvel; int mincoresize; int tid,i; Int_t **nnID; Double_t **dist2; PriorityQueue *pq; Int_t nactivepart=nsubset; vector noffset(numgroupsbg+1,0); //determine the weights for the cores dispersions factors for (i=0;i0) { nincore++; mcore[pfofbg[i]]++; ncore[pfofbg[i]]++; } } if (LOG_ENABLED(trace)) { LOG(trace) << "Mass ratios of cores are:"; for (i=1;i<=numgroupsbg;i++) { LOG(trace) << " " << i << " " << mcore[i] << " " << mcore[i] / mcore[1] << " " << ncore[i] << " " << dispfac[i]; } } //if number of particles in core less than number in subset then start assigning particles if (nincore dist(nthreads,GMatrix(6,1)); vector cmphase(numgroupsbg+1,GMatrix(6,1)); vector invdisp(numgroupsbg+1,GMatrix(6,6)); GMatrix coredist(6,1); Int_t nactive=0; //store particles Pcore=new Particle[nincore]; nincore=0; for (i=0;i0) { Pcore[nincore]=Partsubset[i]; Pcore[nincore].SetType(pfofbg[Partsubset[i].GetID()]); nincore++; } qsort(Pcore,nincore,sizeof(Particle),TypeCompare); noffset[0]=noffset[1]=0; for (i=2;i<=numgroupsbg;i++) noffset[i]=noffset[i-1]+ncore[i-1]; //now get centre of masses and dispersions for (i=1;i<=numgroupsbg;i++) { cmphase[i]=CalcPhaseCM(ncore[i], &Pcore[noffset[i]]); for (int j=0;j=2) for (i=1;i<=numgroupsbg;i++) dispfac[i]=1.0; for (Int_t iloop=numactiveloops;iloop>=0;iloop--) { #ifdef USEOPENMP //if particle number large enough to warrant parallel search if (nactivepart>ompperiodnum) { int nreduce=0; #pragma omp parallel default(shared) \ private(i,tid,Pval,D2,dval,mval,pid,weight) { #pragma omp for reduction(+:nreduce) for (i=0;iGetType()GetID(); if (pfofbg[pid]==0 && pfof[pid]==0) { mval=mcore[1]; for (int k=0;k<6;k++) dist[tid](k,0)=Pval->GetPhase(k)-cmphase[1](k,0); dval=(dist[tid].Transpose()*invdisp[1]*dist[tid])(0,0); pfofbg[pid]=1; for (int j=2;j<=numgroupsbg;j++) { if (mcore[j]>0 && corelevel[j]>=iloop){ weight = 1.0/sqrt(mcore[j]/mval); for (int k=0;k<6;k++) dist[tid](k,0)=Pval->GetPhase(k)-cmphase[j](k,0); D2=(dist[tid].Transpose()*invdisp[j]*dist[tid])(0,0) * weight; if (dval*dispfac[pfofbg[pid]]>D2*dispfac[j]) { dval=D2; mval=mcore[j]; pfofbg[pid]=j; } } } //if particle assigned to a core remove from search Pval->SetType(-1); nreduce++; } } } nactivepart-=nreduce; } else { #endif for (i=0;iGetType()GetID(); if (pfofbg[pid]==0 && pfof[pid]==0) { mval=mcore[1]; for (int k=0;k<6;k++) dist[tid](k,0)=Pval->GetPhase(k)-cmphase[1](k,0); dval=(dist[tid].Transpose()*invdisp[1]*dist[tid])(0,0); pfofbg[pid]=1; for (int j=2;j<=numgroupsbg;j++) { if (mcore[j]>0 && corelevel[j]>=iloop){ weight = 1.0/sqrt(mcore[j]/mval); for (int k=0;k<6;k++) dist[tid](k,0)=Pval->GetPhase(k)-cmphase[j](k,0); D2=(dist[tid].Transpose()*invdisp[j]*dist[tid])(0,0) * weight; if (dval*dispfac[pfofbg[pid]]>D2*dispfac[j]) { dval=D2; mval=mcore[j]; pfofbg[pid]=j; } } } Pval->SetType(-1); nactivepart--; } } #ifdef USEOPENMP } #endif //otherwise, recalculate dispersions if (opt.iPhaseCoreGrowth>=2) { nincore=0; for (i=0;i0) nincore++; Pcore=new Particle[nincore]; for (i=1;i<=numgroupsbg;i++) ncore[i]=0; nincore=0; for (i=0;i0) { Pcore[nincore]=Partsubset[i]; Pcore[nincore].SetType(pfofbg[Partsubset[i].GetID()]); nincore++; ncore[pfofbg[Partsubset[i].GetID()]]++; } qsort(Pcore,nincore,sizeof(Particle),TypeCompare); noffset[0]=noffset[1]=0; for (i=2;i<=numgroupsbg;i++) noffset[i]=noffset[i-1]+ncore[i-1]; //now get centre of masses and dispersions for (i=1;i<=numgroupsbg;i++) if (corelevel[i]>=iloop) { cmphase[i]=CalcPhaseCM(ncore[i], &Pcore[noffset[i]]); for (int j=0;jnincore) nsearch=nincore; if (nbucket>=nincore/8) nbucket=max(1,(int)nincore/8); Pcore=new Particle[nincore]; nincore=0; for (i=0;i0) { Pcore[nincore]=Partsubset[i]; Pcore[nincore].SetType(pfofbg[Partsubset[i].GetID()]); nincore++; } tcore=new KDTree(Pcore,nincore,opt.Bsize,tcore->TPHYS); nnID=new Int_t*[nthreads]; dist2=new Double_t*[nthreads]; for (i=0;iompperiodnum) { #pragma omp parallel default(shared) \ private(i,tid,Pval,x1,D2,dval,mval,pid,pidcore) { #pragma omp for //for each particle in the subset if not assigned to any group (core or substructure) then assign particle //this is done using either a simple distance/sigmax+velocity distance/sigmav calculation to the nearest core particles //or if a more complex routine is required then ... for (i=0;iGetID(); if (pfofbg[pid]==0 && pfof[pid]==0) { x1=Coordinate(Pval->GetPosition()); tcore->FindNearestPos(x1, nnID[tid], dist2[tid],nsearch); dval=0; pidcore=nnID[tid][0]; //calculat distance from current particle to core particle for (int k=0;k<3;k++) { dval+=(Pval->GetPosition(k)-Pcore[pidcore].GetPosition(k))*(Pval->GetPosition(k)-Pcore[pidcore].GetPosition(k))/param[6]+(Pval->GetVelocity(k)-Pcore[pidcore].GetVelocity(k))*(Pval->GetVelocity(k)-Pcore[pidcore].GetVelocity(k))/param[7]; } //get the core particle mass ratio mval=mcore[Pcore[pidcore].GetType()]; pfofbg[pid]=Pcore[pidcore].GetType(); //now initialized to first core particle, examine the rest to see if one is closer for (int j=1;jGetPosition(k)-Pcore[pidcore].GetPosition(k))*(Pval->GetPosition(k)-Pcore[pidcore].GetPosition(k))/param[6]+(Pval->GetVelocity(k)-Pcore[pidcore].GetVelocity(k))*(Pval->GetVelocity(k)-Pcore[pidcore].GetVelocity(k))/param[7]; } //if distance * mass weight is smaller than current distance, reassign particle if (dval>D2*mval/mcore[Pcore[pidcore].GetType()]) {dval=D2;mval=mcore[Pcore[pidcore].GetType()];pfofbg[pid]=Pcore[pidcore].GetType();} } } } } } else { #endif for (i=0;iGetID(); if (pfofbg[pid]==0 && pfof[pid]==0) { x1=Coordinate(Pval->GetPosition()); tcore->FindNearestPos(x1, nnID[tid], dist2[tid],nsearch); dval=0; pidcore=nnID[tid][0]; for (int k=0;k<3;k++) { dval+=(Pval->GetPosition(k)-Pcore[pidcore].GetPosition(k))*(Pval->GetPosition(k)-Pcore[pidcore].GetPosition(k))/param[6]+(Pval->GetVelocity(k)-Pcore[pidcore].GetVelocity(k))*(Pval->GetVelocity(k)-Pcore[pidcore].GetVelocity(k))/param[7]; } mval=mcore[Pcore[pidcore].GetType()]; pfofbg[pid]=Pcore[pidcore].GetType(); for (int j=1;jGetPosition(k)-Pcore[pidcore].GetPosition(k))*(Pval->GetPosition(k)-Pcore[pidcore].GetPosition(k))/param[6]+(Pval->GetVelocity(k)-Pcore[pidcore].GetVelocity(k))*(Pval->GetVelocity(k)-Pcore[pidcore].GetVelocity(k))/param[7]; } if (dval>D2*mval/mcore[Pcore[pidcore].GetType()]) {dval=D2;mval=mcore[Pcore[pidcore].GetType()];pfofbg[pid]=Pcore[pidcore].GetType();} } } } #ifdef USEOPENMP } #endif //clean up memory delete tcore; delete[] Pcore; for (i=0;i0 && mcore[pfofbg[i]]==0) pfofbg[i]=0; ncore[pfofbg[i]]++; } pq=new PriorityQueue(numgroupsbg); newnumgroupsbg=0; for (i=1;i<=numgroupsbg;i++){ if (ncore[i]Push(i,(Double_t)ncore[i]); } //if no large enough cores are left, then halt if (newnumgroupsbg<=1) { numgroupsbg=0; delete pq; return; } newnumgroupsbg=0; for (i=1;i<=numgroupsbg;i++){ if (pq->TopPriority()>=mincoresize) newcore[pq->TopQueue()]=++newnumgroupsbg; pq->Pop(); } for (i=0;i0 && ncore[pfofbg[pid]]>0) pfofbg[pid]=newcore[pfofbg[pid]]; else if (pfofbg[pid]>0 && ncore[pfofbg[pid]]==0) pfofbg[pid]=0; } numgroupsbg=newnumgroupsbg; delete pq; } } //Merge any groups that overlap in phase-space void MergeSubstructuresCoresPhase(Options &opt, const Int_t nsubset, Particle *&Partsubset, Int_t *&pfof, Int_t &numsubs, Int_t &numcores) { //get the phase centres of objects and see if they overlap Int_t pfofval, imerge, numlargesubs=0, newnumcores=numcores; Double_t disp, dist2, mindist2, fdist2=pow(opt.coresubmergemindist,2.0); Coordinate pos; vector numingroup, noffset, taggedsubs; vector subs, cores; KDTree *tree; //vector phasetensorsubs(numsubs,GMatrix(6,6)), phasetensorcores(numcores,GMatrix(6,6)); vector sigXsubs(numsubs), sigVsubs(numsubs), sigXcores(numcores), sigVcores(numcores); struct indexfof { Int_t fofval; Int_t index; }; vector indexing; subs.resize(numsubs); cores.resize(numcores); numingroup.resize(numsubs+numcores+1); noffset.resize(numsubs+numcores+1); indexing.resize(nsubset); for (auto &x:sigXsubs) x=0; for (auto &x:sigVsubs) x=0; for (auto &x:sigXcores) x=0; for (auto &x:sigVcores) x=0; //get center of mass in phase-space for (auto i=0;iTPHYS,tree->KEPAN,100,0,0,0); //tree = new KDTree(subs.data(),numlargesubs,1,tree->TPHYS,tree->KEPAN,100,0,0,0); //check all cores to see if they overlap significantly with substructures newnumcores=0; for (auto i=0;iSearchBallPosTagged(pos, sigXcores[i]*fdist2); if (taggedsubs.size()==0) { newnumcores++; cores[i].SetID(newnumcores+numsubs); continue; } //if objects are within search window of core, get min phase distance imerge=-1; mindist2=MAXVALUE; for (auto j=0;j0) { for (auto i=0;i numingroup, noffset, taggedsubs; struct mergeinfo { Int_t originalpfofval; Int_t pfofval; Int_t numingroup; int type; int nummerged; bool ismerged; int mergeindex; vector mergedlist; mergeinfo(){ nummerged = 0; ismerged = false; mergeindex = -1; }; }; vector subs; vector minfo; KDTree *tree; //vector phasetensorsubs(numgroups+1,GMatrix(6,6)); vector sigXsubs(numgroups+1), sigVsubs(numgroups+1); Double_t searchdist; struct indexfof { Int_t fofval; Int_t index; }; vector indexing; int idoffset, idtagged; subs.resize(numgroups+1); minfo.resize(numgroups+1); idoffset = 0; idtagged = -1; numingroup.resize(numgroups+1); noffset.resize(numgroups+1); indexing.resize(nsubset); for (auto &x:sigXsubs) x=0; for (auto &x:sigVsubs) x=0; for (auto &x:numingroup) x=0; //get center of mass in phase-space for (auto i=0;inumsubs)); subs[i].SetPID(i); subs[i].SetID(i); minfo[i].originalpfofval = i; minfo[i].pfofval = i; minfo[i].type = subs[i].GetType(); minfo[i].numingroup = numingroup[i]; for (auto k=0;k<6;k++) subs[i].SetPhase(k,subs[i].GetPhase(k)/subs[i].GetPotential()); } //sort indices by original fof value sort(indexing.begin(), indexing.end(), [](const indexfof &a, const indexfof &b){ return a.fofval < b.fofval; }); //get the dispersions for (auto i=0;iTPHYS,tree->KEPAN,100,0,0,0); //first check all cores to see if they overlap significantly with dynamically distince //substructures. Since cores are after subs in id value, this removes a core //and adds particles to a substructure for (auto i=0;iSearchBallPosTagged(i, searchdist); if (taggedsubs.size()<=1) continue; //if objects are within search window of core, get min phase distance imerge=-1; mindist2=MAXVALUE; for (auto j=0;j b.numingroup) return true; else if (a.numingroup < b.numingroup) return false; else { return (a.originalpfofval < b.originalpfofval); } } else return false; }); //store old to new pfof values map oldtonewindex; for (auto i=0;i= 0) newnumgroups++; minfo[i].pfofval = newnumgroups; if (minfo[i].type == 1) newnumcores++; } //update the values to new pfof values for (auto i=0;i 0) { for (auto &mergedgroup:minfo[i].mergedlist) mergedgroup = oldtonewindex[mergedgroup]; } } //now update the pfof array as necessary for (auto i=0;i0) { pfofval = minfo[i].pfofval; for (auto &mergedgroup:minfo[i].mergedlist) { index1 = minfo[mergedgroup].originalpfofval; for (auto j=noffset[index1];j=nsubset*opt.minfracsubsizeforremoval && numinlargest>=nsubset*opt.minfracsubsizeforremoval) { LOG(trace) << "Removing a large substructure " << nsubset << " " << numgroups << " " << numsubs << " " << numcores << " and size is " << numinsub << " " << numinlargest; numgroups--; numsubs--; for (auto i=0;i0) pfof[i]--; } } int setNthreads(){ return 0; } ///adjust to phase centre inline void AdjustSubPartToPhaseCM(Int_t num, Particle *subPart, GMatrix &cmphase) { int nthreads = 1; #ifdef USEOPENMP #pragma omp parallel for \ default(shared) \ num_threads(nthreads) if (num > ompperiodnum) #endif for (auto j=0;j=MINSUBSIZE&&opt.foftype!=FOF6DCORE) { //now if object is large enough for phase-space decomposition and search, compare local field to bg field opt.Ncell=opt.Ncellfac*subnumingroup; //if ncell is such that uncertainty would be greater than 0.5% based on Poisson noise, increase ncell till above unless cell would contain >25% while (opt.Ncellopt.Ncell) opt.Ncell*=2; tree=InitializeTreeGrid(opt,subnumingroup,subPart); ngrid=tree->GetNumLeafNodes(); grid=new GridCell[ngrid]; FillTreeGrid(opt, subnumingroup, ngrid, tree, subPart, grid); gvel=GetCellVel(opt,subnumingroup,subPart,ngrid,grid); gveldisp=GetCellVelDisp(opt,subnumingroup,subPart,ngrid,grid,gvel); opt.HaloLocalSigmaV=0; for (auto j=0;jopt.HaloVelDispScale) opt.HaloVelDispScale=opt.HaloSigmaV; #ifdef HALOONLYDEN GetVelocityDensity(opt,subnumingroup,subPart); #endif GetDenVRatio(opt,subnumingroup, subPart, ngrid, grid, gvel, gveldisp); GetOutliersValues(opt,subnumingroup, subPart, sublevel); } //otherwise only need to calculate a velocity scale for merger separation else { Matrix eigvec(0.),I(0.); Double_t sigma2x,sigma2y,sigma2z; CalcVelSigmaTensor(subnumingroup, subPart, sigma2x, sigma2y, sigma2z, eigvec, I); opt.HaloLocalSigmaV=opt.HaloSigmaV=pow(sigma2x*sigma2y*sigma2z,1.0/3.0); } } inline void CleanAndUpdateGroupsFromSubSearch(Options &opt, Int_t &subnumingroup, Particle *subPart, Int_t *&subpfof, Int_t &subngroup, Int_t *&subsubnumingroup, Int_t **&subsubpglist, Int_t &numcores, Int_t *&subpglist, Int_t *&pfof, Int_t &ngroup, Int_t &ngroupidoffset) { bool iunbindflag; Int_t ng=subngroup; Int_t *coreflag; if (subngroup == 0) return; subsubnumingroup = BuildNumInGroup(subnumingroup, subngroup, subpfof); subsubpglist = BuildPGList(subnumingroup, subngroup, subsubnumingroup, subpfof); if (opt.uinfo.unbindflag&&subngroup>0) { //if also keeping track of cores then must allocate coreflag std::vector coreflag; if (numcores>0 && opt.iHaloCoreSearch>=1) { coreflag.resize(subngroup + 1); for (auto icore=1;icore<=subngroup;icore++) coreflag[icore]=1+(icore>subngroup-numcores); } iunbindflag = CheckUnboundGroups(opt, subnumingroup, subPart, subngroup, subpfof, subsubnumingroup, subsubpglist, 1, coreflag.empty() ? nullptr : coreflag.data()); if (iunbindflag) { for (auto j=1;j<=ng;j++) delete[] subsubpglist[j]; delete[] subsubnumingroup; delete[] subsubpglist; if (subngroup>0) { subsubnumingroup = BuildNumInGroup(subnumingroup, subngroup, subpfof); subsubpglist = BuildPGList(subnumingroup, subngroup, subsubnumingroup, subpfof); } //if need to update number of cores, if (numcores>0 && opt.iHaloCoreSearch>=1) { numcores=0; for (auto icore=1;icore<=subngroup;icore++) numcores += (coreflag[icore]==2); } } } for (auto j=0;j0) pfof[subpglist[j]]=ngroup+ngroupidoffset+subpfof[j]; } //ngroupidoffset+=subngroup; //now alter subsubpglist so that index pointed is global subset index as global subset is used to get the particles to be searched for subsubstructure for (auto j=1;j<=subngroup;j++) { for (auto k=0;k &ngroupidoffset_old, vector &ngroupidoffset_new) { //now adjust the group ids to the new offsets. ngroupidoffset += ns; for (auto i=2;i<=activenumgroups;i++) ngroupidoffset_new[i] = ngroupidoffset_new[i-1]+subngroup[i-1]; #ifdef USEOPENMP #pragma omp parallel for \ default(shared) schedule(static) if (activenumgroups > 2) #endif for (auto i=1;i<=activenumgroups;i++) { if (subngroup[i]==0) continue; for (auto j=0;j &Partsubset, Int_t *&pfof, Int_t &ngroup, Int_t &nhalos, PropData *pdata) { //now build a sublist of groups to search for substructure Int_t nsubsearch, oldnsubsearch,sublevel,maxsublevel,ngroupidoffset,ngroupidoffsetold,ngrid; bool iflag,iunbindflag; Particle *subPart; Int_t firstgroup,firstgroupoffset; Int_t ng,*numingroup,**pglist; Int_t *subpfof,*subngroup; Int_t *subnumingroup,**subpglist; Int_t **subsubnumingroup, ***subsubpglist; Int_t *numcores,*coreflag; Int_t *subpfofold; vector ngroupidoffset_old, ngroupidoffset_new; vector ompactivesubgroups; //variables to keep track of structure level, pfof values (ie group ids) and their parent structure //use to point to current level StrucLevelData *pcsld; //use to store total number in sublevel; Int_t ns; int minsizeforsubsearch = opt.MinSize*2; #ifndef USEMPI int ThisTask=0,NProcs=1; #endif LOG(info) << "Beginning substructure search"; MEMORY_USAGE_REPORT(debug); if (ngroup>0) { //point to current structure level pcsld=psldata; //ns=0; //if a single halo was not passed then store fof halos for unbinding purposes if (!opt.iSingleHalo) nhalos=ngroup; nsubsearch=ngroup;sublevel=1;ngroupidoffset=ngroupidoffsetold=0; if (opt.iBaryonSearch>=1 && opt.partsearchtype==PSTALL) numingroup=BuildNumInGroupTyped(nsubset, ngroup, pfof, Partsubset.data(), DARKTYPE); else numingroup=BuildNumInGroup(nsubset, ngroup, pfof); //since initially groups in order find index of smallest group that can be searched for substructure //for (Int_t i=1;i<=ngroup;i++) if (numingroup[i]nextlevel; nsubsearch=ngroup-opt.num3dfof; } vector indicestosearch; for (Int_t i=firstgroup;i<=ngroup;i++) if (numingroup[i]>=minsizeforsubsearch) {indicestosearch.push_back(i);} nsubsearch = indicestosearch.size(); iflag=(nsubsearch>0); if (iflag) { if (opt.iBaryonSearch>=1 && opt.partsearchtype==PSTALL) pglist=BuildPGListTyped(nsubset, ngroup, numingroup, pfof,Partsubset.data(),DARKTYPE); // #ifdef HIGHRES // else pglist=BuildPGListTyped(nsubset, ngroup, numingroup, pfof,Partsubset.data(),DARKTYPE); // #else else pglist=BuildPGList(nsubset, ngroup, numingroup, pfof); // #endif //now store group ids of (sub)structures that will be searched for (sub)substructure. //since at level zero, the particle group list that is going to be used to calculate the background, outliers and searched through is simple pglist here //also the group size is simple numingroup subnumingroup=new Int_t[nsubsearch+1]; subpglist=new Int_t*[nsubsearch+1]; for (Int_t i=1;i<=nsubsearch;i++) { subnumingroup[i]=numingroup[indicestosearch[i-1]]; subpglist[i]=new Int_t[subnumingroup[i]]; for (Int_t j=0;j0) { Int_t oldns = ns; ns = 0; Options opt2; #pragma omp parallel for \ default(shared) private(subPart, subpfof, opt2) schedule(dynamic) \ reduction(+:ns) for (auto iomp=0;iomp0) { pcsld->nextlevel=new StrucLevelData(); //pcsld->nextlevel->Initialize(ns); pcsld->nextlevel->Phead=new Particle*[ns+1]; pcsld->nextlevel->gidhead=new Int_t*[ns+1]; pcsld->nextlevel->Pparenthead=new Particle*[ns+1]; pcsld->nextlevel->gidparenthead=new Int_t*[ns+1]; pcsld->nextlevel->giduberparenthead=new Int_t*[ns+1]; pcsld->nextlevel->stypeinlevel=new Int_t[ns+1]; pcsld->nextlevel->stype=HALOSTYPE+SUBSTYPE*sublevel; pcsld->nextlevel->nsinlevel=ns; Int_t nscount=1; for (Int_t i=1;i<=oldnsubsearch;i++) { Int_t ii=0,iindex; Int_t *gidparentheadval,*giduberparentheadval; Particle *Pparentheadval; //here adjust head particle of parent structure if necessary. Search for first instance where //the pfof value of the particles originally associated with the parent structure have a value while (pfof[subpglist[i][ii]]>ngroup+ngroupidoffset-ns && iingroup+ngroupidoffset-ns) { //in the case that no particles remain part of the parent (sub)structure //then remove the (sub)structure from the current structure level, add the new structures to the next structure level //first find index of (sub)structure Particle *Pval=&Partsubset[subpglist[i][0]]; iindex=0; while (pcsld->Phead[iindex++]!=Pval); //store the parent/uber parent info if not a field halo if(sublevel==1&&opt.iKeepFOF==0) { Pparentheadval=NULL; gidparentheadval=NULL; giduberparentheadval=NULL; } else { Pparentheadval=pcsld->Phead[iindex]; gidparentheadval=pcsld->gidhead[iindex]; giduberparentheadval=pcsld->giduberparenthead[iindex]; } //now copy information for (Int_t jj=iindex;jjnsinlevel;jj++) { pcsld->Phead[jj]=pcsld->Phead[jj+1]; pcsld->gidhead[jj]=pcsld->gidhead[jj+1]; pcsld->Pparenthead[jj]=pcsld->Pparenthead[jj+1]; pcsld->gidparenthead[jj]=pcsld->gidparenthead[jj+1]; pcsld->giduberparenthead[jj]=pcsld->giduberparenthead[jj+1]; pcsld->stypeinlevel[jj]=pcsld->stypeinlevel[jj+1]; } //decrease number of structures in level, adjust ids, offsets, etc for (Int_t jj=0;jjsubpfofold[i]) pfof[jj]--; pcsld->nsinlevel--; ngroupidoffsetold--; } else { //otherwise, just a matter of updating some pointers //store index in the structure list to access the parent (sub)structure //iindex=pfof[subpglist[i][ii]]-ngroupidoffsetold-firstgroupoffset; //don't need to offset group as already taken care of. iindex=pfof[subpglist[i][ii]]-ngroupidoffsetold; pcsld->gidhead[iindex]=&pfof[subpglist[i][ii]]; pcsld->Phead[iindex]=&Partsubset[subpglist[i][ii]]; //only for field haloes does the gidparenthead and giduberparenthead need to be adjusted //but only if 3DFOFs are not kept as uber parents if(sublevel==1&&opt.iKeepFOF==0) { pcsld->gidparenthead[iindex]=&pfof[subpglist[i][ii]]; pcsld->giduberparenthead[iindex]=&pfof[subpglist[i][ii]]; } Pparentheadval=&Partsubset[subpglist[i][ii]]; gidparentheadval=pcsld->gidhead[iindex]; giduberparentheadval=pcsld->giduberparenthead[iindex]; } for (Int_t j=1;j<=subngroup[i];j++) { //need to restructure pointers so that they point to what the parent halo //points to and point to the head of the structure //this is after viable structures have been identfied pcsld->nextlevel->Phead[nscount]=&Partsubset[subsubpglist[i][j][0]]; pcsld->nextlevel->gidhead[nscount]=&pfof[subsubpglist[i][j][0]]; pcsld->nextlevel->Pparenthead[nscount]=Pparentheadval; pcsld->nextlevel->gidparenthead[nscount]=gidparentheadval; pcsld->nextlevel->giduberparenthead[nscount]=giduberparentheadval; //if multiple core region then set the appropriate structure level if (j>=(subngroup[i]-numcores[i])+1) pcsld->nextlevel->stypeinlevel[nscount]=HALOSTYPE+10*(sublevel-1)+HALOCORESTYPE; else pcsld->nextlevel->stypeinlevel[nscount]=HALOSTYPE+10*sublevel; nscount++; } } ngroupidoffsetold+=pcsld->nsinlevel; pcsld=pcsld->nextlevel; } LOG(debug) << "Finished searching substructures to sublevel " << sublevel; sublevel++; minsizeforsubsearch=min(minsizeforsubsearch*2,MINSUBSIZE); for (Int_t i=1;i<=oldnsubsearch;i++) delete[] subpglist[i]; delete[] subpglist; delete[] subnumingroup; nsubsearch=0; //after looping over all level sublevel substructures adjust nsubsearch, set subpglist subnumingroup, so that can move to next level. for (Int_t i=1;i<=oldnsubsearch;i++) for (Int_t j=1;j<=subngroup[i];j++) if (subsubnumingroup[i][j]>=minsizeforsubsearch) nsubsearch++; if (nsubsearch>0) { subnumingroup=new Int_t[nsubsearch+1]; subpglist=new Int_t*[nsubsearch+1]; nsubsearch=1; for (Int_t i=1;i<=oldnsubsearch;i++) { for (Int_t j=1;j<=subngroup[i];j++) if (subsubnumingroup[i][j]>=minsizeforsubsearch) { subnumingroup[nsubsearch]=subsubnumingroup[i][j]; subpglist[nsubsearch]=new Int_t[subnumingroup[nsubsearch]]; for (Int_t k=0;k0) { for (Int_t j=1;j<=subngroup[i];j++) delete[] subsubpglist[i][j]; delete[] subsubnumingroup[i]; delete[] subsubpglist[i]; } } delete[] subsubnumingroup; delete[] subsubpglist; delete[] subngroup; delete[] numcores; delete[] subpfofold; LOG(debug) << "Finished storing next level of substructures to be searched for subsubstructure"; } ngroup+=ngroupidoffset; LOG(info) << "Done searching substructure to " << sublevel - 1 << " sublevels"; } else delete[] numingroup; //if not an idividual halo and want bound haloes after substructure search (and not searching for baryons afterwards) //unbind halo population if (!opt.iSingleHalo&&opt.iBoundHalos>1&&!opt.iBaryonSearch) { //begin by storing information of the current hierarchy Int_t nhaloidoffset=0,nhierarchy,gidval; Int_t *nsub,*parentgid,*uparentgid,*stype; StrucLevelData *ppsldata,**papsldata; ng=nhalos; numingroup=BuildNumInGroup(nsubset, ngroup, pfof); pglist=BuildPGList(nsubset, ngroup, numingroup, pfof); //store the parent and uber parent info nsub=new Int_t[ngroup+1]; parentgid=new Int_t[ngroup+1]; uparentgid=new Int_t[ngroup+1]; stype=new Int_t[ngroup+1]; GetHierarchy(opt,ngroup,nsub,parentgid,uparentgid,stype); //store pointer to the various substructure levels ppsldata=psldata; nhierarchy=0; //determine the number of levels in the hierarchy while (ppsldata->nextlevel!=NULL){nhierarchy++;ppsldata=ppsldata->nextlevel;} ppsldata=psldata; papsldata=new StrucLevelData*[nhierarchy]; nhierarchy=0; while (ppsldata!=NULL) {papsldata[nhierarchy++]=ppsldata;ppsldata=ppsldata->nextlevel;} if(CheckUnboundGroups(opt,nsubset,Partsubset.data(),nhalos,pfof,numingroup,pglist,0)) { //if haloes adjusted then need to update the StrucLevelData //first update just halos (here ng=old nhalos) //by setting NULL values in structure level and moving all the unbound halos the end of array for (Int_t i=1;i<=ng;i++) { if (numingroup[i]==0) { psldata->Phead[i]=NULL; psldata->gidhead[i]=NULL; } else { psldata->Phead[i]=&Partsubset[pglist[i][0]]; psldata->gidhead[i]=&pfof[pglist[i][0]]; //set the parent pointers to appropriate addresss such that the parent and uber parent are the same as the groups head psldata->gidparenthead[i]=psldata->gidhead[i]; psldata->giduberparenthead[i]=psldata->gidhead[i]; } } for (Int_t i=1,j=0;i<=nhalos;i++) { if (psldata->Phead[i]==NULL) { j=i+1;while(psldata->Phead[j]==NULL) j++; //copy the first non-NULL pointer to current NULL pointers, //ie: for a non-existing structure which has been unbound, remove it from the structure list //by copying the pointers of the next still viable structure to that address and setting //the pointers at the new position, j, to NULL psldata->Phead[i]=psldata->Phead[j]; psldata->gidhead[i]=psldata->gidhead[j]; psldata->gidparenthead[i]=psldata->gidparenthead[j]; psldata->giduberparenthead[i]=psldata->giduberparenthead[j]; psldata->stypeinlevel[i]=psldata->stypeinlevel[j]; psldata->Phead[j]=NULL; psldata->gidhead[j]=NULL; psldata->gidparenthead[j]=NULL; psldata->giduberparenthead[j]=NULL; psldata->stypeinlevel[j]=BGTYPE; } } psldata->nsinlevel=nhalos; //then adjust sublevels so that they point to the appropriate parent and uberparent values for (Int_t i=nhierarchy-1;i>0;i--){ for (int j=1;j<=papsldata[i]->nsinlevel;j++) { gidval=(*papsldata[i]->gidhead[j]); if (parentgid[gidval]!=GROUPNOPARENT) { if (numingroup[parentgid[gidval]]>0) papsldata[i]->gidparenthead[j]=&pfof[pglist[parentgid[gidval]][0]]; else papsldata[i]->gidparenthead[j]=NULL; } if (uparentgid[gidval]!=GROUPNOPARENT) { if (numingroup[uparentgid[gidval]]>0) papsldata[i]->giduberparenthead[j]=&pfof[pglist[uparentgid[gidval]][0]]; else papsldata[i]->giduberparenthead[j]=NULL; } } } //if inclusive halo masses calculated then also need to adjust the ordering of these properties if (opt.iInclusiveHalo) ReorderInclusiveMasses(ng,nhalos,numingroup,pdata); //adjust halo ids ReorderGroupIDs(ng,nhalos, numingroup, pfof,pglist); nhaloidoffset=ng-nhalos; for (Int_t i=0;ing) pfof[i]-=nhaloidoffset; } for (Int_t i=1;i<=ngroup;i++) delete[] pglist[i]; delete[] pglist; delete[] numingroup; delete[] nsub; delete[] parentgid; delete[] uparentgid; delete[] papsldata; ngroup-=nhaloidoffset; } } //update the number of local groups found #ifdef USEMPI MPI_Barrier(MPI_COMM_WORLD); MPI_Allgather(&ngroup, 1, MPI_Int_t, mpi_ngroups, 1, MPI_Int_t, MPI_COMM_WORLD); #endif LOG(info) << "Found a total of " << ngroup; MEMORY_USAGE_REPORT(debug); } /*! Check significance of group using significance parameter. A group is considered significant if (ave/expected ave) (and possibly (max-min)/varexpected] is significant relative to Poisson noise. If a group is not start removing particle with lowest ell value. */ int CheckSignificance(Options &opt, const Int_t nsubset, Particle *Partsubset, Int_t &numgroups, Int_t *numingroup, Int_t *pfof, Int_t **pglist) { Int_t i; Double_t ellaveexp,ellvarexp, ellvallim; Double_t *aveell,*varell,*maxell,*minell, *ptypefrac, *betaave; Int_t iflag=0,ng=numgroups; aveell=new Double_t[numgroups+1]; maxell=new Double_t[numgroups+1]; minell=new Double_t[numgroups+1]; //varell=new Double_t[numgroups+1]; betaave=new Double_t[numgroups+1]; /* if (opt.iiterflag) ellvallim=opt.ellthreshold*opt.ellfac; else ellvallim=opt.ellthreshold; */ ellvallim=opt.ellthreshold; ellaveexp=sqrt(2.0/M_PI)*exp(-ellvallim*ellvallim)*exp(0.5*ellvallim*ellvallim)/(1.0-gsl_sf_erf(ellvallim/sqrt(2.0))); //first adjust system and store pList for each group so one can access pfof appropriately LOG(debug) << "Checking that groups have a significance level of " << opt.siglevel << " and contain more than " << opt.MinSize << " members"; for (i=0;i<=numgroups;i++) {aveell[i]=0.;maxell[i]=-MAXVALUE;minell[i]=MAXVALUE;//numingroup[i]=0; } for (i=0;iellvalue)minell[gid]=ellvalue; } for (i=1;i<=numgroups;i++) { aveell[i]/=(Double_t)numingroup[i]; betaave[i]=(aveell[i]/ellaveexp-1.0)*sqrt((Double_t)numingroup[i]); //flag indicating that group ids need to be adjusted if(betaave[i]vminell&&Partsubset[pglist[i][j]].GetPotential() &Part, Int_t *&pfofdark, Int_t &ngroupdark, Int_t &nhalos, int ihaloflag, int iinclusive, PropData *pdata) { KDTree *tree; std::vector period; Int_t *pfofbaryons, *pfofall, *pfofold; Int_t i,pindex,npartingroups,ng,nghalos,nhalosold=nhalos, baryonfofold; Int_t *ids, *storeval,*storeval2; Double_t D2,dval,rval; Coordinate x1; Particle p1; int icheck; FOFcompfunc fofcmp; Double_t param[20]; int nsearch=opt.Nvel; Int_t *nnID=NULL,*numingroup; Double_t *dist2=NULL, *localdist; int nthreads=1,maxnthreads,tid; int minsize; Int_t nparts=ndark+nbaryons; Int_t nhierarchy=1,gidval; StrucLevelData *ppsldata,**papsldata; Int_t nparts_tot, ndark_tot; #ifndef USEMPI int ThisTask=0,NProcs=1; #endif #ifdef USEMPI MPI_Allreduce(&nparts, &nparts_tot, 1, MPI_Int_t, MPI_SUM, MPI_COMM_WORLD); MPI_Allreduce(&ndark, &ndark_tot, 1, MPI_Int_t, MPI_SUM, MPI_COMM_WORLD); #else nparts_tot = nparts; ndark_tot = ndark; #endif if (nparts_tot == ndark_tot) { LOG_RANK0(info) << "Requested baryon search but no baryons loaded. Skipping"; pfofall=pfofdark; return pfofall; } if (opt.partsearchtype==PSTALL && nparts == ndark){ LOG(info) << "No local baryons to assign. Skipping search."; #ifdef USEMPI // if MPI and also unbinding then there is all gather that is invoked. //as number of groups could have changed has due to unbinding. if (opt.uinfo.unbindflag) { LOG(info) << "MPI thread " << ThisTask << " has found " << ngroupdark; MPI_Allgather(&ngroupdark, 1, MPI_Int_t, mpi_ngroups, 1, MPI_Int_t, MPI_COMM_WORLD); //free up memory now that only need to store pfof and global ids if (ThisTask==0) { int totalgroups=0; for (int j=0;j0) { period = std::vector(3, opt.p); } //sort dark matter particles so that particles belonging to a group are first, then all other dm particles LOG(info) << "Sort particles so that tree only uses particles in groups " << npartingroups; //search all dm particles in structures for (i=0;i1)); qsort(Part.data(), ndark, sizeof(Particle), PotCompare); //sort(Part.begin(),Part.begin()+ndark,PotCompareVec); ids=new Int_t[ndark+1]; //store the original order of the dark matter particles for (i=0;i0) { //set parameters, first to some fraction of halo linking length param[1]=(opt.ellxscale*opt.ellxscale)*(opt.ellphys*opt.ellphys)*(opt.ellhalophysfac*opt.ellhalophysfac); param[6]=param[1]; //also check to see if velocity scale still zero find dispersion of largest halo //otherwise search uses the largest average "local" velocity dispersion of halo identified in the mpi domain. if (opt.HaloVelDispScale==0) { Double_t mtotregion,vx,vy,vz; Coordinate vmean; mtotregion=vx=vy=vz=0; for (i=0;iTPHYS,tree->KEPAN,100,0,0,0,period.data()); //allocate memory for search //find the closest dm particle that belongs to the largest dm group and associate the baryon with that group (including phase-space window) LOG(debug) << "Searching ..."; #ifdef USEOPENMP #pragma omp parallel default(shared) \ private(i,tid,p1,pindex,x1,D2,dval,rval,icheck,nnID,dist2,baryonfofold) { nnID=new Int_t[nsearch]; dist2=new Double_t[nsearch]; #pragma omp for #else nnID=new Int_t[nsearch]; dist2=new Double_t[nsearch]; #endif for (i=0;iFindNearestPos(x1, nnID, dist2,nsearch); if (dist2[0]nhalos)||(pfofdark[pindex]==baryonfofold)); else icheck=(numingroup[pfofbaryons[i]]D2) { dval=D2;pfofbaryons[i]=pfofdark[pindex]; rval=dist2[j]; #ifdef USEMPI if (opt.partsearchtype!=PSTALL) localdist[i]=dval; #endif } } } } } } delete[] nnID; delete[] dist2; #ifdef USEOPENMP } #endif } #ifdef USEMPI //if mpi then baryons are not necessarily local if opt.partsearchtype!=PSTALL //in that case must search other mpi domains. //if all particles are searched then just need to reset the particle order ///\todo need to update this for mpi vector if (opt.partsearchtype!=PSTALL) { LOG(debug) << "Finished local search"; MPI_Barrier(MPI_COMM_WORLD); //determine all tagged dark matter particles that have search areas that overlap another mpi domain if (opt.impiusemesh) MPIGetExportNumUsingMesh(opt, npartingroups, Part.data(), sqrt(param[1])); else MPIGetExportNum(npartingroups, Part.data(), sqrt(param[1])); //to store local mpi task mpi_foftask=MPISetTaskID(nbaryons); //then determine export particles, declare arrays used to export data PartDataIn = new Particle[NExport+1]; PartDataGet = new Particle[NImport+1]; FoFDataIn = new fofdata_in[NExport+1]; FoFDataGet = new fofdata_in[NImport+1]; //exchange particles MPIBuildParticleExportBaryonSearchList(opt, npartingroups, Part.data(), pfofdark, ids, numingroup, sqrt(param[1])); //now dark matter particles associated with a group existing on another mpi domain are local and can be searched. NExport=MPISearchBaryons(nbaryons, Pbaryons, pfofbaryons, numingroup, localdist, nsearch, param, period.data()); //reset order delete tree; for (i=0;i0) delete tree; for (i=0;i0) delete tree; for (i=0;i0) { LOG(debug) << "Starting unbind of dm+baryons"; //build new arrays based on pfofall. Int_t *ningall,**pglistall; ningall=BuildNumInGroup(nparts, ngroupdark, pfofall); pglistall=BuildPGList(nparts, ngroupdark, ningall, pfofall); //first repoint everything in the structure pointer to pfofall. See GetHierarchy for some guidance on how to parse the //the structure data nhierarchy=1; ppsldata=psldata; //determine the number of levels in the hierarchy while (ppsldata->nextlevel!=NULL){nhierarchy++;ppsldata=ppsldata->nextlevel;} ppsldata=psldata; papsldata=new StrucLevelData*[nhierarchy]; nhierarchy=0; while (ppsldata!=NULL) {papsldata[nhierarchy++]=ppsldata;ppsldata=ppsldata->nextlevel;} //now parse hierarchy and repoint stuff to the pfofall pointer instead of pfof if (opt.partsearchtype!=PSTALL) { for (i=nhierarchy-1;i>=0;i--){ for (int j=1;j<=papsldata[i]->nsinlevel;j++) { gidval=(*papsldata[i]->gidhead[j]); papsldata[i]->gidhead[j]=&pfofall[pglistall[gidval][0]]; if (papsldata[i]->gidparenthead[j]!=NULL) { gidval=(*papsldata[i]->gidparenthead[j]); if (numingroup[gidval]>0) papsldata[i]->gidparenthead[j]=&pfofall[pglistall[gidval][0]]; else papsldata[i]->gidparenthead[j]=NULL; } if (papsldata[i]->giduberparenthead[j]!=NULL) { gidval=(*papsldata[i]->giduberparenthead[j]); if (numingroup[gidval]>0) papsldata[i]->giduberparenthead[j]=&pfofall[pglistall[gidval][0]]; } else papsldata[i]->giduberparenthead[j]=NULL; } } } //store old number of groups ng=ngroupdark; //if any structures have been changed need to update the structure pointers. //For now, do NOT reorder group ids based on number of particles that a group is composed of //as need to adjust the hierarchy data structure. //before unbinding store the parent and uberparent of an object Int_t *nsub,*parentgid,*uparentgid,*stype; nsub=new Int_t[ngroupdark+1]; parentgid=new Int_t[ngroupdark+1]; uparentgid=new Int_t[ngroupdark+1]; stype=new Int_t[ngroupdark+1]; GetHierarchy(opt,ngroupdark,nsub,parentgid,uparentgid,stype); //store the old pfof values of all so that in case particle unbound from a //substructure they are reassigned to the uber parent halo pfofold=new Int_t[nparts]; for (i=0;inhalos) { if (ningall[uparentgid[pfofold[Part[i].GetID()]]]>0) { pfofall[Part[i].GetID()]=uparentgid[pfofold[Part[i].GetID()]]; ningall[uparentgid[pfofold[Part[i].GetID()]]]++; } } } //must rebuild pglistall for (i=1;i<=ng;i++) delete[] pglistall[i]; delete[] pglistall; pglistall=BuildPGList(nparts, ng, ningall, pfofall); //now adjust the structure pointers after unbinding where groups are NOT reordered //first find groups that have been removed, tag their head as NULL //otherwise update the head, parent and uber parent int nlevel=0,ninleveloff=0,ii; for (i=1;i<=ng;i++) { if (i-ninleveloff>papsldata[nlevel]->nsinlevel) { ninleveloff+=papsldata[nlevel]->nsinlevel; nlevel++; } ii=i-ninleveloff; if (ningall[i]==0) { papsldata[nlevel]->Phead[ii]=NULL; papsldata[nlevel]->gidhead[ii]=NULL; papsldata[nlevel]->gidparenthead[ii]=NULL; papsldata[nlevel]->giduberparenthead[ii]=NULL; } else { papsldata[nlevel]->Phead[ii]=&Part[pglistall[i][0]]; papsldata[nlevel]->gidhead[ii]=&pfofall[pglistall[i][0]]; if (parentgid[i]!=GROUPNOPARENT) { if (ningall[parentgid[i]]>0) papsldata[nlevel]->gidparenthead[ii]=&pfofall[pglistall[parentgid[i]][0]]; else papsldata[nlevel]->gidparenthead[ii]=NULL; } if (uparentgid[i]!=GROUPNOPARENT) { if (ningall[uparentgid[i]]>0) papsldata[nlevel]->giduberparenthead[ii]=&pfofall[pglistall[uparentgid[i]][0]]; else papsldata[nlevel]->giduberparenthead[ii]=NULL; } } } //then clean up the structure list so that removed groups no longer in structure list for (i=nhierarchy-1;i>=0;i--) { Int_t ninlevel=0, iend=papsldata[i]->nsinlevel; for (Int_t k=1;k<=papsldata[i]->nsinlevel;k++) if (papsldata[i]->Phead[k]!=NULL) ninlevel++; for (Int_t k=1,j=0;k<=ninlevel;k++) { if (papsldata[i]->Phead[k]==NULL) { //j=k+1;while(papsldata[i]->Phead[j]==NULL) j++; //find last entry that is not NULL j=iend;while(papsldata[i]->Phead[j]==NULL) j--; iend--; //copy the first non-NULL pointer to current NULL pointers, //ie: for a non-existing structure which has been unbound, remove it from the structure list //by copying the pointers of the next still viable structure to that address and setting //the pointers at the new position, j, to NULL papsldata[i]->Phead[k]=papsldata[i]->Phead[j]; papsldata[i]->gidhead[k]=papsldata[i]->gidhead[j]; papsldata[i]->gidparenthead[k]=papsldata[i]->gidparenthead[j]; papsldata[i]->giduberparenthead[k]=papsldata[i]->giduberparenthead[j]; papsldata[i]->stypeinlevel[k]=papsldata[i]->stypeinlevel[j]; papsldata[i]->Phead[j]=NULL; papsldata[i]->gidhead[j]=NULL; papsldata[i]->gidparenthead[j]=NULL; papsldata[i]->giduberparenthead[j]=NULL; papsldata[i]->stypeinlevel[j]=BGTYPE; } } papsldata[i]->nsinlevel=ninlevel; } LOG(debug) << "Reorder after finding baryons and unbinding, previously had " << ng << " groups and now have " << ngroupdark; //reorder group ids, keeping the ordering unchanged. map remap; Int_t newng=0, oldpid, newpid; remap[0]=0; for (i=1;i<=ng;i++) { if (ningall[i]>0) { newng++; remap[i]=newng; } else remap[i]=0; } for (i=0;i=0;i--) papsldata[i]=NULL; delete[] papsldata; } else { delete[] ningall; for (i=1;i<=ng;i++) delete[] pglistall[i]; delete[] pglistall; } delete[] pfofold; delete[] nsub; delete[] parentgid; delete[] uparentgid; delete[] stype; } //get memory usage MEMORY_USAGE_REPORT(debug); #ifdef USEMPI //if number of groups has changed then update if (opt.uinfo.unbindflag) { LOG(info) << "MPI thread " << ThisTask << " has found " << ngroupdark; MPI_Allgather(&ngroupdark, 1, MPI_Int_t, mpi_ngroups, 1, MPI_Int_t, MPI_COMM_WORLD); //free up memory now that only need to store pfof and global ids if (ThisTask==0) { int totalgroups=0; for (int j=0;jnextlevel!=NULL){nhierarchy++;ppsldata=ppsldata->nextlevel;} for (Int_t i=1;i<=ngroups;i++) nsub[i]=0; for (Int_t i=1;i<=ngroups;i++) parentgid[i]=GROUPNOPARENT; for (Int_t i=1;i<=ngroups;i++) uparentgid[i]=GROUPNOPARENT; ppsldata=psldata; papsldata=new StrucLevelData*[nhierarchy]; nhierarchy=0; while (ppsldata!=NULL) {papsldata[nhierarchy++]=ppsldata;ppsldata=ppsldata->nextlevel;} for (int i=nhierarchy-1;i>=1;i--){ //store number of substructures for (int j=1;j<=papsldata[i]->nsinlevel;j++) { if (papsldata[i]->gidparenthead[j]!=NULL&&papsldata[i]->gidparenthead[j]!=papsldata[i]->gidhead[j]) nsub[*(papsldata[i]->gidparenthead[j])]++; } //then add these to parent substructure for (int j=1;j<=papsldata[i]->nsinlevel;j++) { if (papsldata[i]->gidparenthead[j]!=NULL&&papsldata[i]->gidparenthead[j]!=papsldata[i]->gidhead[j]){ nsub[*(papsldata[i]->gidparenthead[j])]+=nsub[*(papsldata[i]->gidhead[j])]; parentgid[*(papsldata[i]->gidhead[j])]=*(papsldata[i]->gidparenthead[j]); } if (papsldata[i]->giduberparenthead[j]!=NULL &&papsldata[i]->gidparenthead[j]!=papsldata[i]->gidhead[j]) uparentgid[*(papsldata[i]->gidhead[j])]=*(papsldata[i]->giduberparenthead[j]); stype[*(papsldata[i]->gidhead[j])]=(papsldata[i]->stypeinlevel[j]); } } //store field structures (top treel level) types for (int j=1;j<=papsldata[0]->nsinlevel;j++) { stype[*(papsldata[0]->gidhead[j])]=(papsldata[0]->stypeinlevel[j]); } for (int i=0;iopt.num3dfof) pdata[i].hostfofid=GROUPNOPARENT; #ifdef USEMPI pdata[i].hostid+=haloidoffset; pdata[i].directhostid+=haloidoffset; if(opt.iKeepFOF && uparentgid[i]<=opt.num3dfof) pdata[i].hostfofid+=haloidoffset; #endif } else { pdata[i].hostid=GROUPNOPARENT; pdata[i].directhostid=GROUPNOPARENT; pdata[i].hostfofid=GROUPNOPARENT; } pdata[i].stype=stype[i]; pdata[i].numsubs=nsub[i]; } #ifdef USEOPENMP } #endif } //@} /// \name Routines used for interative search //@{ ///for halo mergers check, get mean velocity dispersion inline Double_t GetVelDisp(Particle *Part, Int_t numgroups, Int_t *numingroup, Int_t **pglist){ Int_t i; Double_t mt,sigmav=0; Coordinate cmval; Matrix dispmatrix; #ifdef USEOPENMP #pragma omp parallel default(shared) \ private(i,mt,cmval,dispmatrix) { #pragma omp for reduction(+:sigmav) #endif for (i=1;i<=numgroups;i++) { for (int k=0;k<3;k++) cmval[k]=0.; mt=0.; for (Int_t j=0;j0&&pfof[ppid]==0) {newlinksIndex[newlinks++]=j;numgrouplinksIndex[nnID[ppid]]++;} } } for (Int_t j=1;j<=numgroups;j++) { (*newIndex)[j]=new Int_t[numgrouplinksIndex[j]+1]; numgrouplinksIndex[j]=0; } for (Int_t j=0;j0&&pfof[ppid]==0) {(*newIndex)[nnID[ppid]][numgrouplinksIndex[nnID[ppid]]++]=j;} } } ///Create array for each group listing all possibly intergroup links inline void DetermineGroupLinks(Int_t nsubset, Particle *Partsubset, Int_t *pfof, Int_t numgroups, Int_t &newlinks, Int_t *newlinksIndex, Int_t *numgrouplinksIndex, Int_t *nnID, Int_t ***newIndex) { Int_t i,pid,ppid; //store number of links between groups in newIndex for (i=1;i<=numgroups;i++) numgrouplinksIndex[i]=0; //first determine number of links a group has with particles identified as belonging to another group for (i=0;i-1) {numgrouplinksIndex[nnID[ppid]]++;} } //then go through list and store all these particles for (i=1;i<=numgroups;i++) { (*newIndex)[i]=new Int_t[numgrouplinksIndex[i]+1]; numgrouplinksIndex[i]=0; } for (i=0;i-1) {(*newIndex)[nnID[ppid]][numgrouplinksIndex[nnID[ppid]]++]=newlinksIndex[i];} } } ///once Group links are found, reduce the data so that one can determine for each group, number of other groups linked, their gids and the number of that group linked ///these are stored in numgrouplinksIndex, intergroupgidIndex, & newintergroupIndex inline void DetermineGroupMergerConnections(Particle *Partsubset, Int_t numgroups, Int_t *pfof, int *ilflag, Int_t *numgrouplinksIndex, Int_t *intergrouplinksIndex, Int_t *nnID, Int_t ***newIndex, Int_t ***newintergroupIndex, Int_t ***intergroupgidIndex) { Int_t i,ii,pid,ppid, intergrouplinks; //ilflag ensures that group only assocaited once to current group being searched for intergroup links for (i=1;i<=numgroups;i++) ilflag[i]=0; for (i=1;i<=numgroups;i++) { intergrouplinks=0; for (ii=0;ii0) { (*newintergroupIndex)[i]=new Int_t[intergrouplinks]; (*intergroupgidIndex)[i]=new Int_t[intergrouplinks]; for (ii=0;iiompsearchnum) { for (ii=0;iiSearchCriterion(newlinksIndex[ii], fofcmp,param, pfof[Partsubset[newlinksIndex[ii]].GetID()], &nnID[0][tid*nsubset], &dist2[0][tid*nsubset]); } } #pragma omp parallel default(shared) \ private(ii,tid) { #pragma omp for for (ii=0;ii0) { if (nnID[0][ii+j*nsubset]SearchCriterion(newlinksIndex[ii], fofcmp,param, pfof[Partsubset[newlinksIndex[ii]].GetID()], nnID[tid], dist2[tid]); } } } inline void SearchForNewLinks(Int_t nsubset, KDTree *tree, Particle *Partsubset, Int_t *pfof, FOFcompfunc &fofcmp, Double_t *param, Int_t newlinks, Int_t *newlinksIndex, Int_t **nnID, int nthreads) { Int_t i,ii; int tid; #ifdef USEOPENMP if (newlinks>ompsearchnum) { for (ii=0;iiSearchCriterion(newlinksIndex[ii], fofcmp,param, pfof[Partsubset[newlinksIndex[ii]].GetID()], &nnID[0][tid*nsubset]); } } #pragma omp parallel default(shared) \ private(ii,tid) { #pragma omp for for (ii=0;ii0) { if (nnID[0][ii+j*nsubset]SearchCriterion(newlinksIndex[ii], fofcmp,param, pfof[Partsubset[newlinksIndex[ii]].GetID()], nnID[tid]); } } } ///This routine links particle's stored in newIndex to a group. Routine assumes that each group's list of new particle's is independent. inline void LinkUntagged(Particle *Partsubset, Int_t numgroups, Int_t *pfof, Int_t *numingroup, Int_t **pglist, Int_t newlinks, Int_t *numgrouplinksIndex, Int_t **newIndex, Int_tree_t *Head, Int_tree_t *Next, Int_tree_t *GroupTail, Int_t *nnID) { Int_t i,pindex,tail,pid,ppid; if (newlinks>0) { #ifdef USEOPENMP #pragma omp parallel default(shared) \ private(i,pindex,tail,pid,ppid) { #pragma omp for schedule(dynamic,1) nowait #endif for (i=1;i<=numgroups;i++) if (numgrouplinksIndex[i]>0) { pindex=pglist[i][0]; tail=GroupTail[i]; for (Int_t k=0;k0) { intergrouplinks=0; for (Int_t j=0;jopt.fmerge*oldnumingroup[gidjoin]); if (merge) { mergers++; intergrouplinksIndex[gidjoin]=0;numingroup[gidjoin]=0; igflag[gidjoin]=1; pindex=pglist[i][0]; sindex=pglist[gidjoin][0];lindex=pindex; gid=pfof[Partsubset[Head[lindex]].GetID()]; //first adjust length of the larger group tail=GroupTail[i]; //then adjust the next of the tail of the larger group Next[tail]=Head[sindex]; ss=Head[sindex]; //then adjust the group id, head and group length of the smaller group do { pfof[Partsubset[ss].GetID()]=gid; nnID[Partsubset[ss].GetID()]=gid; Head[ss]=Head[lindex]; intergrouplinks++; newlinksIndex[newlinks++]=ss; } while((ss = Next[ss]) >= 0); GroupTail[i]=GroupTail[gidjoin]; if (numgrouplinksIndex[gidjoin]>0) {delete[] (*newintergroupIndex)[gidjoin];delete[] (*intergroupgidIndex)[gidjoin];} } } } numingroup[i]+=intergrouplinks; delete[] (*newintergroupIndex)[i];delete[] (*intergroupgidIndex)[i]; } return mergers; } ///This routine merges candidate halo groups together during the merger check so long as the groups share enough links compared to the groups original size given in oldnumingroup ///or one group is significantly larger than the other (in which case the secondary object is probably a substructure of the other) inline Int_t MergeHaloGroups(Options &opt, Particle *Partsubset, Int_t numgroups, Int_t *pfof, Int_t *numingroup, Int_t *oldnumingroup, Int_t **pglist, Int_t *numgrouplinksIndex, Int_t ***intergroupgidIndex, Int_t ***newintergroupIndex, Int_t *intergrouplinksIndex, Int_t *Head, Int_t *Next, Int_t *GroupTail, int *igflag, Int_t *nnID, Int_t &newlinks, Int_t *newlinksIndex) { Int_t i,gid,gidjoin,pindex,lindex,sindex,tail,ss,intergrouplinks,mergers=0; for (i=1;i<=numgroups;i++) if (igflag[i]==0&&numgrouplinksIndex[i]>0) { intergrouplinks=0; for (Int_t j=0;jopt.fmergebg*oldnumingroup[gidjoin])||((Double_t)oldnumingroup[gidjoin]/(Double_t)oldnumingroup[i]= 0); GroupTail[i]=GroupTail[gidjoin]; if (numgrouplinksIndex[gidjoin]>0) {delete[] (*newintergroupIndex)[gidjoin];delete[] (*intergroupgidIndex)[gidjoin];} } } } numingroup[i]+=intergrouplinks; delete[] (*newintergroupIndex)[i];delete[] (*intergroupgidIndex)[i]; } return mergers; } //@}