Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:16:36

0001 //
0002 // ********************************************************************
0003 // * License and Disclaimer                                           *
0004 // *                                                                  *
0005 // * The  Geant4 software  is  copyright of the Copyright Holders  of *
0006 // * the Geant4 Collaboration.  It is provided  under  the terms  and *
0007 // * conditions of the Geant4 Software License,  included in the file *
0008 // * LICENSE and available at  http://cern.ch/geant4/license .  These *
0009 // * include a list of copyright holders.                             *
0010 // *                                                                  *
0011 // * Neither the authors of this software system, nor their employing *
0012 // * institutes,nor the agencies providing financial support for this *
0013 // * work  make  any representation or  warranty, express or implied, *
0014 // * regarding  this  software system or assume any liability for its *
0015 // * use.  Please see the license in the file  LICENSE  and URL above *
0016 // * for the full disclaimer and the limitation of liability.         *
0017 // *                                                                  *
0018 // * This  code  implementation is the result of  the  scientific and *
0019 // * technical work of the GEANT4 collaboration.                      *
0020 // * By using,  copying,  modifying or  distributing the software (or *
0021 // * any work based  on the software)  you  agree  to acknowledge its *
0022 // * use  in  resulting  scientific  publications,  and indicate your *
0023 // * acceptance of all terms of the Geant4 Software license.          *
0024 // ********************************************************************
0025 //
0026 
0027 // --------------------------------------------------------------------
0028 // Implementation for FastAerosol class
0029 // Author: A.Knaian (ara@nklabs.com), N.MacFadden (natemacfadden@gmail.com)
0030 // --------------------------------------------------------------------
0031 
0032 #include "FastAerosol.hh"
0033 
0034 #include "G4SystemOfUnits.hh"
0035 #include "G4GeometryTolerance.hh"
0036 
0037 #include <numeric>  // for summing vectors with accumulate
0038 
0039 // multithreaded safety
0040 //#include <atomic>
0041 #include "G4AutoLock.hh"
0042 namespace
0043 {
0044  G4Mutex gridMutex = G4MUTEX_INITIALIZER;
0045  G4Mutex sphereMutex = G4MUTEX_INITIALIZER;
0046 }
0047 
0048 ///////////////////////////////////////////////////////////////////////////////
0049 //
0050 // Constructor - check inputs
0051 //             - initialize grid
0052 //             - cache values
0053 //
0054 FastAerosol::FastAerosol(const G4String& pName, G4VSolid* pCloud,
0055                  G4double pR, G4double pMinD, G4double pAvgNumDens,
0056               G4double pdR,
0057                             std::function<G4double (G4ThreeVector)> pDistribution)
0058     : fName(pName), fCloud(pCloud), fMinD(pMinD), fDistribution(pDistribution)
0059 {
0060  kCarTolerance = G4GeometryTolerance::GetInstance()->GetSurfaceTolerance();
0061 
0062  // get and set bounding box dimensions
0063  G4ThreeVector minBounding, maxBounding;
0064  fCloud->BoundingLimits(minBounding, maxBounding);
0065  G4ThreeVector halfSizeVec = 0.5*(maxBounding - minBounding);
0066 
0067  G4double pX = halfSizeVec[0];
0068  G4double pY = halfSizeVec[1];
0069  G4double pZ = halfSizeVec[2];
0070 
0071  if (pX < 2*kCarTolerance ||
0072        pY < 2*kCarTolerance ||
0073      pZ < 2*kCarTolerance)  // limit to thickness of surfaces
0074     {
0075      std::ostringstream message;
0076      message << "Dimensions too small for cloud: " 
0077               << GetName() << "!" << G4endl
0078           << "     fDx, fDy, fDz = " << pX << ", " << pY << ", " << pZ;
0079         G4Exception("FastAerosol::FastAerosol()", "GeomSolids0002",
0080                     FatalException, message);
0081     }
0082     fDx = pX;
0083     fDy = pY;
0084     fDz = pZ;
0085 
0086 
0087     // check and set droplet radius
0088     if (pR < 0.0)
0089     {
0090         std::ostringstream message;
0091         message << "Invalid radius for cloud: " << GetName() << "!" << G4endl
0092                 << "        Radius must be positive."
0093                 << "        Inputs: pR = " << pR;
0094         G4Exception("FastAerosol::FastAerosol()", "GeomSolids0002",
0095                     FatalErrorInArgument, message);
0096     }
0097     fR = pR;
0098     fR2 = fR*fR;
0099 
0100 
0101     // check and set droplet radius safety
0102     if (pdR < 0.0 || pdR > fR)
0103     {
0104         std::ostringstream message;
0105         message << "Invalid sphericality measure for cloud: " << GetName() << "!" << G4endl
0106                 << "        Radius uncertainty must be between 0 and fR."
0107                 << "        Inputs: pdR = " << pdR
0108                 << "        Inputs: pR = " << pR;
0109         G4Exception("FastAerosol::FastAerosol()", "GeomSolids0002",
0110                     FatalErrorInArgument, message);
0111     }
0112     fdR = pdR;
0113 
0114 
0115     // check and set number density
0116     if (pAvgNumDens <= 0)
0117     {
0118         std::ostringstream message;
0119         message << "Invalid average number density for cloud: " << GetName() << "!" << G4endl
0120                 << "     pAvgNumDens = " << pAvgNumDens;
0121         G4Exception("FastAerosol::FastAerosol()", "GeomSolids0002",
0122                     FatalException, message);
0123     }
0124     fAvgNumDens = pAvgNumDens;
0125 
0126 
0127     // Set collision limit for collsion between equal sized balls with fMinD between them
0128     // no droplets will be placed closer than this distance forom each other
0129     fCollisionLimit2 = (2*fR + fMinD)*(2*fR + fMinD);
0130 
0131     // set maximum number of droplet that we are allowed to skip before halting with an error
0132     fMaxDropCount = (G4int)floor(fAvgNumDens*(fCloud->GetCubicVolume())*(0.01*fMaxDropPercent)); 
0133 
0134     // initialize grid variables
0135     InitializeGrid(); 
0136 
0137     
0138     // begin cache of voxelized circles and spheres
0139     // see header for more details on these data structures
0140     G4AutoLock lockSphere(&sphereMutex);    // lock for multithreaded safety. Likely not needed here, but doesn't hurt
0141 
0142     fCircleCollection.push_back({{0, 0}});  // the R=0 circle only has one point {{x1,y1}} = {{0,0}}
0143     fSphereCollection.push_back({{{0}}});   // the R=0 sphere only has one point {{{z1}}}  = {{{0}}}
0144     
0145     fMaxCircleR = 0;
0146     fMaxSphereR = 0;
0147 
0148     MakeSphere(fPreSphereR);
0149 
0150     lockSphere.unlock();                    // unlock
0151 
0152     // vector search radius. In terms of voxel width, how far do you search for droplets in vector search
0153     // you need to search a larger area if fR is larger than one grid (currently disabled)
0154     fVectorSearchRadius = (G4int)ceill(fR/fGridPitch);
0155 }
0156 
0157 ///////////////////////////////////////////////////////////////////////////////
0158 //
0159 // Alternative Constructor
0160 //
0161 // Same as standard constructor except with a uniform droplet distribution
0162 //
0163 FastAerosol::FastAerosol(const G4String& pName, G4VSolid* pCloud, G4double pR, G4double pMinD, G4double pNumDens, G4double pdR):
0164     FastAerosol(pName, pCloud, pR, pMinD, pNumDens, pdR,
0165                 [](G4ThreeVector) {return 1.0;})
0166 {}
0167 
0168 ///////////////////////////////////////////////////////////////////////////////
0169 //
0170 // Alternative Constructor
0171 //
0172 // Same as standard constructor except with a uniform droplet distribution and
0173 // assuming no uncertainty in sphericality
0174 //
0175 FastAerosol::FastAerosol(const G4String& pName, G4VSolid* pCloud, G4double pR, G4double pMinD, G4double pNumDens): FastAerosol(pName, pCloud, pR, pMinD, pNumDens, 0.0,[](G4ThreeVector) {return 1.0;})
0176 {}
0177 
0178 ///////////////////////////////////////////////////////////////////////////////
0179 //
0180 // Initialize grid
0181 //
0182 // Sets grids to initial values and calculates expected number of droplets
0183 // for each voxel
0184 //
0185 void FastAerosol::InitializeGrid() 
0186 {
0187  // set pitch so, on average, fDropletsPerVoxel droplets are in each voxel
0188  fGridPitch = std::pow(fDropletsPerVoxel/fAvgNumDens,1.0/3.0);
0189 
0190  // if a voxel has center farther than this distance from the bulk outside,
0191  // we know it is fully contained in the bulk
0192  fEdgeDistance = fGridPitch*std::sqrt(3.0)/2.0 + fR;
0193 
0194  // set number of grid cells
0195  fNx = (G4int)ceill(2*fDx / fGridPitch);
0196  fNy = (G4int)ceill(2*fDy / fGridPitch);
0197  fNz = (G4int)ceill(2*fDz / fGridPitch);
0198  fNumGridCells = (long) fNx*fNy*fNz;
0199  fNxy = fNx*fNy;
0200 
0201  // try... catch... because vectors can only be so long
0202  // thus you can't set grid too fine
0203  try
0204  {
0205    std::vector<G4ThreeVector> emptyVoxel{};
0206    fGrid.resize(fNumGridCells, emptyVoxel);
0207    fGridMean.resize(fNumGridCells, 0);
0208    fGridValid = new std::atomic<bool>[fNumGridCells];
0209   }
0210   catch ( const std::bad_alloc&)
0211   {
0212    std::ostringstream message;
0213    message << "Out of memory! Grid pitch too small for cloud: " 
0214            << GetName() << "!" << G4endl
0215        << "        Asked for fNumGridCells = " 
0216        << fNumGridCells << G4endl
0217        << "        each with element of size " 
0218        <<   sizeof(std::vector<G4ThreeVector>) << G4endl
0219        << "        each with element of size " << sizeof(bool) 
0220        << G4endl
0221        << "        but the max size is " << fGrid.max_size() << "!";
0222    G4Exception("FastAerosol::FastAerosol()", "GeomSolids0002",
0223         FatalErrorInArgument, message); }
0224     
0225  // initialize grid cache
0226  G4ThreeVector voxelCenter;
0227  G4bool valid;
0228 
0229  for (int i=0; i<fNumGridCells; i++)
0230  {
0231   voxelCenter = fGridPitch*(GetIndexCoord(i)+G4ThreeVector(0.5,0.5,0.5)); // center of grid at index i with assuming minimum voxel is at [0,fGridPitch]x[0,fGridPitch]x[0,fGridPitch]
0232   voxelCenter -= G4ThreeVector(fDx,fDy,fDz);
0233   
0234  //shift all voxels so that aerosol center is properly at the origin
0235  // whether or not the grid is 'finished'
0236  // fGridValid[i] is true if we don't plan on populating
0237  // more droplets in the voxel
0238  // false if otherwise
0239  valid = (fCloud->Inside(voxelCenter) != kInside && fCloud->DistanceToIn(voxelCenter) >= fEdgeDistance);
0240 
0241  if (valid) // will not populate the voxel
0242   {
0243    // have to store 'valid' in this way so that the value is atomic and respects multithreadedness
0244    fGridValid[i].store(true, std::memory_order_release);
0245   }
0246   else  // might need to populate the voxel
0247   {
0248    fGridValid[i].store(false, std::memory_order_release);
0249 
0250    // find the overlap of the voxel with the bulk
0251    G4double volScaling=1.0;
0252    G4bool edgeVoxel =  ( kInside != fCloud->Inside(voxelCenter) || fCloud->DistanceToOut(voxelCenter) < fEdgeDistance );
0253    if (edgeVoxel)
0254     {
0255      volScaling = VoxelOverlap(voxelCenter, 100, 0.0);
0256             }
0257    // calculates number of droplets based off of voxel center - not ideal
0258     fGridMean[i] = std::max(0.0, volScaling*fDistribution(voxelCenter));
0259         }
0260     }
0261 
0262 // must scale fGridMean[i] so that the desired number densities are actualy achieved
0263 // this is because no restrictions are applied to fDistribution
0264 G4double tempScaling = (fCloud->GetCubicVolume())*fAvgNumDens/accumulate(fGridMean.begin(), fGridMean.end(), 0.0);
0265     for (int i=0; i<fNumGridCells; i++) {fGridMean[i] *= tempScaling;}
0266 }
0267 
0268 ///////////////////////////////////////////////////////////////////////////////
0269 //
0270 // Calculate the overlap of the voxel with the aerosol bulk
0271 //
0272 // The method is largely copied from G4VSolid::EstimateCubicVolume
0273 //
0274 G4double FastAerosol::VoxelOverlap(G4ThreeVector voxelCenter, G4int nStat, G4double epsilon)
0275 {
0276  G4double cenX = voxelCenter.x();
0277  G4double cenY = voxelCenter.y();
0278  G4double cenZ = voxelCenter.z();
0279     
0280  G4int iInside=0;
0281  G4double px,py,pz;
0282  G4ThreeVector p;
0283  G4bool in;
0284 
0285  if(nStat < 100)    nStat   = 100;
0286  if(epsilon > 0.01) epsilon = 0.01;
0287 
0288  for(G4int i = 0; i < nStat; i++ )
0289  {
0290   px = cenX+(fGridPitch-epsilon)*(G4UniformRand()-0.5);
0291   py = cenY+(fGridPitch-epsilon)*(G4UniformRand()-0.5);
0292   pz = cenZ+(fGridPitch-epsilon)*(G4UniformRand()-0.5);
0293   p  = G4ThreeVector(px,py,pz);
0294   in = (fCloud->Inside(p) == kInside) && (fCloud->DistanceToOut(p) >= fR);
0295   if(in) iInside++;    
0296  }
0297  
0298  return (double)iInside/nStat;
0299 }
0300 
0301 ///////////////////////////////////////////////////////////////////////////////
0302 //
0303 // Populate all voxels
0304 //
0305 // Allows for partially populated clouds.
0306 //
0307 void FastAerosol::PopulateAllGrids() 
0308 {
0309  unsigned int gi;
0310 
0311  for (int xi=0; xi<fNx; xi++)
0312   {
0313    for (int yi=0; yi<fNy; yi++)
0314     {
0315      for (int zi=0; zi<fNz; zi++)
0316     {
0317       // PopulateGrid takes unsigned arguments
0318       // fNx, fNy, and fNz  are signed so that some other algebra works
0319       // thus we iterate with xi, yi, and zi signed and then cast them
0320       PopulateGrid((unsigned)xi,(unsigned)yi,(unsigned)zi,gi);
0321      }
0322       }
0323   }
0324 }
0325 
0326 ///////////////////////////////////////////////////////////////////////////////
0327 //
0328 // Populate a single grid
0329 //
0330 // If grid is already populated, does nothing.
0331 //
0332 void FastAerosol::PopulateGrid(unsigned int xi, unsigned int yi, unsigned int zi, unsigned int& gi) 
0333 {
0334  gi = GetGridIndex(xi,yi,zi);
0335     
0336  // Check if this grid needs update
0337  bool tmpValid = fGridValid[gi].load(std::memory_order_acquire);    
0338  // have to do this weirdly because we are using atomic variables so that multithreadedness is safe
0339  std::atomic_thread_fence(std::memory_order_acquire);
0340     
0341  if (!tmpValid) // if true then either outside the bulk or in a voxel that is already populated
0342  {
0343   G4AutoLock lockGrid(&gridMutex);
0344  // Check again now that we have the lock - in case grid became valid after first check and before lock
0345   tmpValid = fGridValid[gi].load(std::memory_order_acquire);
0346 
0347   if (!tmpValid)
0348    {
0349 // uniquely set the seed to randomly place droplets
0350 // changing global seed gaurantees a totally new batch of seeds
0351     fCloudEngine.setSeed((long)gi + (long)fNumGridCells*(long)fSeed);
0352 
0353 // find if the voxel is near the bulk edge. In that case, we need to check whether a placed droplet is inside the bulk
0354 // if not an edge voxel, we can place droplets by only considering interference between with other droplets
0355   G4ThreeVector voxelCenter = fGridPitch*G4ThreeVector(xi+0.5,yi+0.5,zi+0.5)-G4ThreeVector(fDx,fDy,fDz);
0356  G4bool edgeVoxel = ( kInside != fCloud->Inside(voxelCenter) || fCloud->DistanceToOut(voxelCenter) < fEdgeDistance );
0357             
0358  // number of droplets to place
0359   unsigned int numDropletsToPlace = CLHEP::RandPoisson::shoot(&fCloudEngine, fGridMean[gi]);
0360 
0361  // actually add the points
0362  G4ThreeVector point;
0363 
0364  while (numDropletsToPlace > 0)
0365   {
0366    // find a new point not overlapping with the others
0367    if (FindNewPoint(edgeVoxel, fGridPitch, fGridPitch, fGridPitch, (G4double)xi*fGridPitch-fDx, (G4double)yi*fGridPitch-fDy, (G4double)zi*fGridPitch-fDz, point) )
0368    {
0369     fGrid[gi].push_back(point);
0370     fNumDroplets++;
0371     }
0372    numDropletsToPlace--;
0373  }
0374 
0375 // Memory fence to ensure sequential consistency,
0376 // because fGridValid is read without a lock
0377 // Voxel data update must be complete before updating fGridValid
0378             std::atomic_thread_fence(std::memory_order_release);
0379 
0380 fGridValid[gi].store(true, std::memory_order_release);  // set the grid as populated
0381  }
0382 
0383 lockGrid.unlock();
0384  }
0385 }
0386 
0387 ///////////////////////////////////////////////////////////////////////////////
0388 //
0389 // Find a new droplet position in a box of full (not half) widths dX, dY, and dZ
0390 // with minimum corner at (minX, minY, minZ).
0391 //
0392 // ----------------------------------------------------------------------------
0393 //
0394 // Ex: FindNewPoint(fGridPitch, fGridPitch, fGridPitch,
0395 //                   (G4double)xi*fGridPitch-fDx, (G4double)yi*fGridPitch-fDy,
0396 //                   (G4double)zi*fGridPitch-fDz);
0397 //
0398 // 1) CLHEP::RandFlat::shoot(&fCloudEngine)*fGridPitch shoots a point in range
0399 //    [0, fGridPitch]
0400 //
0401 // 1.5) Note that the minimum x value of the (xi, yi, zi) grid is
0402 //      xi*fGridPitch-fDx
0403 //
0404 // 2) xi*fGridPitch-fDx + (1) adds the minimum x value of the (xi, yi, zi)
0405 //    voxel
0406 //
0407 // ----------------------------------------------------------------------------
0408 //
0409 // Ex: FindNewPoint(2.0*fDx, 2.0*fDy, 2.0*fDz, -fDx, -fDy, -fDz);
0410 //
0411 // 1) CLHEP::RandFlat::shoot(&fCloudEngine)*2.0*fDx shoots a point in
0412 //     range [0, 2.0*fDx]
0413 //
0414 // 2) add -fDx to change range into [-fDx, fDx]
0415 //
0416 G4bool FastAerosol::FindNewPoint(G4bool edgeVoxel, G4double dX, G4double dY, G4double dZ, G4double minX, G4double minY, G4double minZ, G4ThreeVector &foundPt)
0417  {
0418  G4int tries = 0;   
0419  // counter of tries. Give up after fNumNewPointTries (you likely asked for too dense in this case)
0420 
0421  G4double x, y, z;
0422 
0423  G4ThreeVector point;
0424  G4bool placedOutside = false;  
0425  // variable whether or not the point was placed outside. Used for edgeVoxel checking
0426 
0427  // Generate a droplet and check if it overlaps with existing droplets
0428  do {
0429      tries++;
0430      if (tries > fNumNewPointTries)     
0431       // skip if we tried more than fNumNewPointTries
0432     {
0433      fNumDropped++;
0434      if (fNumDropped < fMaxDropCount)   
0435      // return error if we skipped more than fMaxDropCount droplets
0436      {
0437        return false;
0438      }
0439 
0440     std::ostringstream message;
0441     message << "Threw out too many droplets for cloud: " 
0442             << GetName()  << G4endl
0443         << "        Tried to place individual droplest " 
0444         << fNumNewPointTries << " times." << G4endl
0445         << "        This failed for " << fMaxDropCount 
0446         << " droplets.";
0447         G4Exception("FastAerosol::FindNewPoint()", "GeomSolids0002",
0448         FatalErrorInArgument, message);
0449         }
0450 
0451       x = minX + CLHEP::RandFlat::shoot(&fCloudEngine)*dX;
0452       y = minY + CLHEP::RandFlat::shoot(&fCloudEngine)*dY;
0453       z = minZ + CLHEP::RandFlat::shoot(&fCloudEngine)*dZ;
0454 
0455     if (edgeVoxel)
0456      {
0457       point = G4ThreeVector(x,y,z);
0458       placedOutside = (fCloud->Inside(point) != kInside) || (fCloud->DistanceToOut(point) < fR);
0459         }
0460     }
0461     while (CheckCollision(x,y,z) || placedOutside);
0462 
0463     foundPt = G4ThreeVector(x,y,z);
0464     return true;  
0465 }
0466 
0467 ///////////////////////////////////////////////////////////////////////////////
0468 //
0469 // Get absolutely nearest droplet
0470 //
0471 // Maximum search radius is maxSearch
0472 //
0473 // Start in grid of point p, check if any speres are in there
0474 // and then go out one more (spherically-shaped) level to the surrounding grids
0475 // and so on, until the whole volume has been checked or a droplet has been found
0476 // in general as you go out, you always need to check one more level out to make sure that
0477 // the one you have (at the closer level) is the actually the nearest one
0478 //
0479 bool FastAerosol::GetNearestDroplet(const G4ThreeVector &p, G4ThreeVector &center, G4double &minRealDistance, G4double maxSearch, G4VSolid* droplet, std::function<G4RotationMatrix (G4ThreeVector)> rotation)
0480 {
0481  G4double cloudDistance = fCloud->DistanceToIn(p);
0482 
0483  // starting radius/diam of voxel layer
0484  G4int searchRad = (G4int)floor(0.5+cloudDistance/fGridPitch);  // no reason to search shells totally outside cloud
0485 
0486  // find the voxel containing p
0487  int xGrid, yGrid, zGrid;
0488  GetGrid(p, xGrid, yGrid, zGrid);   
0489  // it is OK if the starting point is outside the volume
0490 
0491  // initialize the containers for all candidate closest droplets and their respective distances
0492  std::vector<G4ThreeVector> candidates;
0493  std::vector<G4double> distances;
0494  // initialize distances to indicate that no droplet has yet been found
0495  G4double minDistance = kInfinity;
0496  G4double maxCheckDistance = kInfinity;
0497 
0498  bool unsafe = true;
0499 
0500  while (unsafe)
0501  {
0502   // limit maximum search radius
0503   if (searchRad*fGridPitch > maxSearch+fGridPitch) { return false; }
0504 
0505    SearchSphere(searchRad, minDistance, candidates, distances, xGrid, yGrid, zGrid, p);
0506   maxCheckDistance = minDistance+fdR;
0507 
0508   // theory says that, to safely have searched for centers up to some maxCheckDistance, we must search voxelized spheres at least up to ceil(0.25+maxCheckDistance/fGridPitch)
0509         // *** unsure if fully accurate. Calculations for 2D, not 3D... ***
0510  unsafe = searchRad < std::ceil(0.25+maxCheckDistance/fGridPitch);
0511 
0512  searchRad++;
0513  }
0514 
0515 // delete any collected droplets that are too far out. Check all candidates to see what is closest
0516  unsigned int index = 0;
0517 
0518  G4ThreeVector tempCenter;
0519  G4double tempDistance;
0520 
0521  minRealDistance = kInfinity;
0522 
0523  while (index < distances.size())
0524  {
0525   if (distances[index]>maxCheckDistance)
0526    {
0527     candidates.erase(candidates.begin()+index);
0528     distances.erase(distances.begin()+index);
0529    }
0530   else
0531   {
0532    tempCenter = candidates[index];
0533    tempDistance = droplet->DistanceToIn( rotation(tempCenter).inverse()*(p - tempCenter) );
0534 
0535    if (tempDistance < minRealDistance)
0536     {
0537       minRealDistance = tempDistance;
0538       center = tempCenter;
0539     }
0540     index++; // move onto next droplet
0541     }
0542   }
0543 
0544   return true;
0545 }
0546 
0547 ///////////////////////////////////////////////////////////////////////////////
0548 //
0549 // Search sphere of radius R for droplets
0550 //
0551 // Saves any droplets to "center" if they are closer than minDistance
0552 // (updating this minimum found distance at each interval).
0553 //
0554 // Returns if minDistance<infinity (i.e., if we have yet found a droplet)
0555 //
0556 void FastAerosol::SearchSphere(G4int searchRad, G4double &minDistance, std::vector<G4ThreeVector> &candidates, std::vector<G4double> &distances, G4int xGrid, G4int yGrid, G4int zGrid, const G4ThreeVector &p)
0557 {
0558  // search variables
0559  G4int xSearch, ySearch, zSearch;       
0560  // x, y, and z coordinates of the currently searching voxel 
0561  // in the shell voxel layer variables
0562  fSphereType shell; // the shell that we are searching for droplets
0563 
0564 // we pre-calculate spheres up to radius fPreSphereR to speed up calculations
0565 // any sphere smaller than that does not need to use locks
0566 if (searchRad > fPreSphereR)
0567  {
0568   // need to consider making a sphere
0569   G4AutoLock lockSphere(&sphereMutex);
0570   if (searchRad > fMaxSphereR) 
0571    {
0572     // actually need to make a sphere
0573     shell = MakeSphere(searchRad);
0574    }
0575 else
0576   {
0577     // we have previously made a sphere large enough
0578     shell = fSphereCollection[searchRad];
0579    }
0580   lockSphere.unlock();
0581  }
0582  else
0583  {
0584   shell = fSphereCollection[searchRad];
0585  }
0586         
0587  // search spherical voxel layer
0588  for (int i=0; i<(2*searchRad+1); i++) 
0589  {
0590   xSearch = xGrid+i-searchRad;
0591   if (0<=xSearch && xSearch<fNx)
0592    {
0593     for (int j=0; j<(2*searchRad+1); j++)
0594      {
0595        ySearch = yGrid+j-searchRad;
0596 
0597        if (0<=ySearch && ySearch<fNy)
0598     {
0599       for (auto it = shell[i][j].begin(); it != shell[i][j].end(); ++it) {
0600       zSearch = *it+zGrid;
0601 
0602       if (0<=zSearch && zSearch<fNz)
0603         {                       GetNearestDropletInsideGrid(minDistance, candidates, distances, (unsigned)xSearch, (unsigned)ySearch, (unsigned)zSearch, p);
0604         }
0605     }
0606     }
0607       }
0608     }
0609    }
0610 }
0611 
0612 ///////////////////////////////////////////////////////////////////////////////
0613 //
0614 // Make voxelized spheres up to radius R for fSphereCollection
0615 //
0616 // These spheres are used for searching for droplets
0617 //
0618 // To create them, we first create a helper half-circle "zr" for which each
0619 // voxel/point in zr represents a layer of our sphere. The 2nd coordinate of
0620 // zr represents the radius of the layer and the 1st coordinate denotes the
0621 // layer's displacement from the origin along the z-axis (layers are perp to
0622 // z in our convention).
0623 //
0624 std::vector<std::vector<std::vector<int>>> FastAerosol::MakeSphere(G4int R) {
0625 // inductively make smaller spheres since fSphereCollection organizes by index
0626 if (fMaxSphereR<(R-1)) { MakeSphere(R-1);}
0627 
0628 std::vector<int> z;                             
0629 // z-coordinates of voxels in (x,y) of sphere
0630 
0631 std::vector<std::vector<int>> y(2*R+1, z);          
0632 // plane YZ of sphere
0633 
0634 fSphereType x(2*R+1, y); // entire sphere
0635 
0636 x[R][R].push_back(R);// add (0,0,R)
0637 x[R][R].push_back(-R);// add (0,0,-R)
0638 fCircleType zr = MakeHalfCircle(R);
0639 // break sphere into a collection of circles centered at (0,0,z) for different -R<=z<=R
0640 
0641 for (auto it1 = zr.begin(); it1 != zr.end(); ++it1)
0642 {
0643  std::vector<int> pt1 = *it1;
0644  fCircleType circ = MakeCircle(pt1[1]); // make the circle
0645 
0646   for (auto it2 = circ.begin(); it2 != circ.end(); ++it2) 
0647     {
0648      std::vector<int> pt2 = *it2;
0649       x[pt2[0]+R][pt2[1]+R].push_back(pt1[0]);
0650      }
0651 }
0652 
0653  fSphereCollection.push_back(x);
0654  fMaxSphereR = R;
0655  return x;
0656 }
0657 
0658 ///////////////////////////////////////////////////////////////////////////////
0659 //
0660 // Make voxelized circles up to radius R for fCircleCollection
0661 //
0662 // These circles are used to create voxelized spheres (these spheres are then
0663 // used for searching for droplets). This just ports the calculation to making
0664 // a half-circle, adding the points (R,0) and (0,R), and reflecting the half-
0665 // circle along the x-axis.
0666 //
0667 std::vector<std::vector<int>> FastAerosol::MakeCircle(G4int R) 
0668 {
0669 // inductively make smaller circles since fCircleCollection organizes by index
0670  if (fMaxCircleR<R) 
0671   {
0672    if (fMaxCircleR<(R-1)) {MakeCircle(R-1);}
0673    fCircleType voxels = {{R, 0}, {-R, 0}};      
0674    // add (R,0) and (-R,0) since half circle excludes them
0675    fCircleType halfVoxels = MakeHalfCircle(R);
0676 
0677    // join the voxels, halfVoxels, and -halfVoxels
0678    for (auto it = halfVoxels.begin(); it != halfVoxels.end(); ++it) 
0679     {
0680      std::vector<int> pt = *it;
0681      voxels.push_back(pt);
0682      voxels.push_back({-pt[0], -pt[1]});
0683     }
0684     fCircleCollection.push_back(voxels);
0685     fMaxCircleR++;
0686   }
0687     
0688  return fCircleCollection[R];
0689 }
0690 
0691 ///////////////////////////////////////////////////////////////////////////////
0692 //
0693 // Make half circle by a modified midpoint circle algorithm
0694 //
0695 // Modifies the algorithm so it doesn't have 'holes' when making many circles
0696 // 
0697 // See B. Roget, J. Sitaraman, "Wall distance search algorithim using 
0698 // voxelized marching spheres," Journal of Computational Physics, Vol. 241,
0699 // May 2013, pp. 76-94, https://doi.org/10.1016/j.jcp.2013.01.035
0700 //
0701 std::vector<std::vector<int>> FastAerosol::MakeHalfCircle(G4int R)
0702  {
0703 // makes an octant of a voxelized circle in the 1st quadrant starting at y-axis
0704  fCircleType voxels = {{0, R}}; // hard code inclusion of (0,R)
0705  int x = 1;
0706  int y = R;
0707  int dx = 4-R;      
0708  // measure of whether (x+1,y) will be outside the sphere
0709  int dxup = 1;      
0710  // measure of whether (x+1,y+1) will be outside the sphere
0711  while (x<y) 
0712  {
0713   // mirror the points to be added to change octant->upper half plane
0714   voxels.push_back({ x, y});
0715   voxels.push_back({ y, x});
0716   voxels.push_back({-x, y});
0717  voxels.push_back({-y, x});
0718   // if next point outside the sphere, de-increment y
0719   if (dx>0) 
0720    {
0721   // if dxup<=0, the layer above just moves to the right
0722   // this would create a hole at (x+1,y) unless we add it
0723     dxup = dx + 2*y -2*R-1;
0724     if ((dxup<=0) && (x+1<y))
0725      {
0726     voxels.push_back({ 1+x,   y});
0727     voxels.push_back({   y, 1+x});
0728     voxels.push_back({-1-x,   y});
0729     voxels.push_back({  -y, 1+x});
0730      }
0731 
0732     dx += 4-2*y;
0733     y--;
0734    }
0735 
0736   dx += 1+2*x;
0737   x++;
0738  }
0739  
0740  // add points on y=+-x
0741  if (x==y || (x==y+1 && dxup<=0)) 
0742  {
0743   voxels.push_back({x,x});
0744   voxels.push_back({-x,x});
0745  }
0746 
0747  return voxels;
0748 }
0749 
0750 ///////////////////////////////////////////////////////////////////////////////
0751 //
0752 // Get nearest droplet inside a voxel at (xGrid, yGrid, zGrid)
0753 //
0754 void FastAerosol::GetNearestDropletInsideGrid(G4double &minDistance, std::vector<G4ThreeVector> &candidates, std::vector<G4double> &distances, unsigned int xGrid, unsigned int yGrid, unsigned int zGrid, const G4ThreeVector &p) 
0755 {
0756  unsigned int gi;
0757  PopulateGrid(xGrid, yGrid, zGrid, gi);
0758 
0759  // initialize values
0760  G4double foundDistance;
0761 //  std::vector<G4ThreeVector>::iterator bestPt;
0762     
0763  // find closest droplet
0764  for (auto it = fGrid[gi].begin(); it != fGrid[gi].end(); ++it) 
0765   {
0766    foundDistance = std::sqrt((p-*it).mag2());
0767 
0768    if (foundDistance < minDistance+fdR)
0769     {
0770     minDistance = std::min(minDistance, foundDistance);
0771     candidates.push_back(*it);
0772     distances.push_back(foundDistance);
0773     }
0774  }
0775 }
0776 
0777 ///////////////////////////////////////////////////////////////////////////////
0778 //
0779 // Get nearest droplet along vector
0780 //
0781 // Maximum search radius is fVectorSearchRadius
0782 // Maximum distance travelled along vector is maxSearch
0783 //
0784 // 1) Find the closest voxel along v that is inside the bulk
0785 // 2) Search for droplets in a box width (2*fVectorSearchRadius+1) around this voxel
0786 // that our ray would  intersect
0787 // 3) If so, save the droplet that is closest along the ray to p and return true
0788 // 4) If not, step 0.99*fGridPitch along v until in a new voxel
0789 // 5) If outside bulk, jump until inside bulk and then start again from 2
0790 // 6) If inside bulk, search the new voxels in a box centered of width (2*fVectorSearchRadius+1)
0791 // centered at our new point
0792 // 7) If we find a point, do as in (3). If not, do as in (4)
0793 //
0794 // This is searching box-shaped regions of voxels, an approach that is only valid
0795 // for droplets which fit inside a voxel, which is one reason why the code checks
0796 // to make sure that fR < fGridPitch at initialization.
0797 //
0798 bool FastAerosol::GetNearestDroplet(const G4ThreeVector &p, const G4ThreeVector &normalizedV, G4ThreeVector &center, G4double &minDistance, G4double maxSearch, G4VSolid* droplet, std::function<G4RotationMatrix (G4ThreeVector)> rotation)
0799 {
0800  // get the grid box this initial position is inside
0801  int xGrid, yGrid, zGrid;
0802  GetGrid(p, xGrid, yGrid, zGrid);
0803 
0804  // initialize some values
0805  G4ThreeVector currentP = p;
0806  G4double distanceAlongVector = 0.0;
0807  G4double deltaDistanceAlongVector;
0808 
0809  int newXGrid = 0; int newYGrid = 0; int newZGrid = 0;
0810  int deltaX = 0; int deltaY = 0; int deltaZ = 0;
0811  int edgeX = 0 ; int edgeY = 0; int edgeZ = 0;
0812  G4bool boxSearch = true;
0813     
0814  G4double distanceToCloud;
0815     
0816  minDistance = kInfinity;
0817 
0818  // actual search
0819  while(true)
0820   {
0821    deltaDistanceAlongVector = 0.0;
0822    // Jump gaps
0823    distanceToCloud = DistanceToCloud(currentP,normalizedV);
0824    if (distanceToCloud == kInfinity)
0825     {return(minDistance != kInfinity);}
0826       else if (distanceToCloud > fGridPitch)
0827         {
0828         deltaDistanceAlongVector = distanceToCloud-fGridPitch;
0829         distanceAlongVector += deltaDistanceAlongVector;
0830         boxSearch = true;// if jumping gap, use box search
0831             currentP += deltaDistanceAlongVector*normalizedV;   
0832             // skip gaps
0833         }
0834 
0835     // Search for droplets
0836     if (distanceAlongVector > maxSearch)// quit if will jump too far
0837      {
0838      return(minDistance != kInfinity);
0839       }
0840      else
0841        {
0842         if (boxSearch)  // we jumped
0843             {
0844              GetGrid(currentP, xGrid, yGrid, zGrid);
0845               GetNearestDropletInsideRegion(minDistance,     center, xGrid, yGrid, zGrid,
0846                                       fVectorSearchRadius, fVectorSearchRadius, fVectorSearchRadius, p, normalizedV,    droplet, rotation);
0847             }
0848         else            // didn't jump
0849         {
0850         // Searching endcaps
0851         // =================
0852         if (deltaX != 0)
0853         {
0854         GetNearestDropletInsideRegion(minDistance, center, 
0855         edgeX, yGrid, zGrid, 0, fVectorSearchRadius,  fVectorSearchRadius, p, normalizedV,
0856         droplet, rotation);}
0857 
0858          if (deltaY != 0)
0859          {
0860           GetNearestDropletInsideRegion(minDistance, center,
0861             xGrid, edgeY, zGrid, fVectorSearchRadius, 0, fVectorSearchRadius, p, normalizedV, droplet, rotation);
0862          }
0863 
0864          if (deltaZ != 0)
0865          {
0866           GetNearestDropletInsideRegion(minDistance, center,    xGrid, yGrid, edgeZ, fVectorSearchRadius, fVectorSearchRadius, 0, p, normalizedV, droplet, rotation);
0867          }
0868 
0869 // Search bars
0870 // ===========
0871 // (a bar joins two endcaps)
0872 if (deltaX != 0 && deltaY != 0)
0873 {                   GetNearestDropletInsideRegion(minDistance, center, edgeX, edgeY, zGrid,
0874                               0, 0, fVectorSearchRadius, p, normalizedV,
0875                   droplet, rotation);
0876                 }
0877 
0878  if (deltaX != 0 && deltaZ != 0)
0879  {                    GetNearestDropletInsideRegion(minDistance, center, edgeX, yGrid, edgeZ,     0, fVectorSearchRadius, 0, p, normalizedV, droplet, rotation);}
0880 
0881 if (deltaY != 0 && deltaZ != 0)
0882  {
0883  GetNearestDropletInsideRegion(minDistance, center, 
0884                                xGrid, edgeY, edgeZ,
0885                                fVectorSearchRadius, 0, 0,
0886                                p, normalizedV,droplet, rotation);
0887  }
0888 
0889 // Search corner
0890 // =============
0891  if (deltaX != 0 && deltaY != 0 && deltaZ != 0) {                    GetNearestDropletInsideRegion(minDistance, center,          edgeX, edgeY, edgeZ, 0, 0, 0, p, normalizedV, droplet, rotation);}
0892 
0893 // Update position
0894 // ================================
0895 xGrid = newXGrid; yGrid = newYGrid; zGrid = newZGrid;
0896 }
0897             
0898 // Check if we are done
0899 // ====================
0900 if (distanceAlongVector>minDistance+fdR) {return(true);}
0901             
0902 // walk along the grid
0903 // advance by 0.99 fGridPitch  (0.99 rather than 1.0 to avoid issues 
0904 //caused by rounding errors for lines parallel to the grid and based on a grid boundary)
0905 
0906 while (true) {
0907  deltaDistanceAlongVector = fGridPitch*0.99;
0908  distanceAlongVector += deltaDistanceAlongVector;
0909 
0910  // exit returning false if we have traveled more than MaxVectorFollowingDistance
0911  if (distanceAlongVector > maxSearch) { return(false); }
0912 
0913  currentP += deltaDistanceAlongVector*normalizedV;
0914  GetGrid(currentP, newXGrid, newYGrid, newZGrid);
0915 
0916  if ((newXGrid != xGrid) || (newYGrid != yGrid) || (newZGrid != zGrid)) 
0917  {
0918   deltaX = newXGrid - xGrid; 
0919   edgeX = xGrid+deltaX*(1+fVectorSearchRadius);
0920   deltaY = newYGrid - yGrid; 
0921   edgeY = yGrid+deltaY*(1+fVectorSearchRadius);
0922   deltaZ = newZGrid - zGrid; 
0923   edgeZ = zGrid+deltaZ*(1+fVectorSearchRadius);
0924 
0925   break;
0926   }
0927  }
0928   boxSearch = false;
0929   } 
0930   }
0931 
0932 return false;
0933 }
0934 
0935 ///////////////////////////////////////////////////////////////////////////////
0936 //
0937 // Get nearest droplet (along a vector normalizedV) inside a voxel centered at
0938 // (xGridCenter, yGridCenter, zGridCenter) of width (xWidth, yWidth, zWidth)
0939 //
0940 // This searches box-shaped regions.
0941 //
0942 void FastAerosol::GetNearestDropletInsideRegion(G4double &minDistance, G4ThreeVector &center, int xGridCenter, int yGridCenter, int zGridCenter, int xWidth, int yWidth, int zWidth, const G4ThreeVector &p, const G4ThreeVector &normalizedV, G4VSolid* droplet, std::function<G4RotationMatrix (G4ThreeVector)> rotation) 
0943 {
0944  for (int xGrid = (xGridCenter-xWidth); xGrid<=(xGridCenter+xWidth); xGrid++)
0945  {
0946   if (0<=xGrid && xGrid<fNx)
0947     {
0948      for (int yGrid = (yGridCenter-yWidth); yGrid<=(yGridCenter+yWidth); yGrid++)
0949     {
0950       if (0<=yGrid && yGrid<fNy)
0951        {
0952         for (int zGrid = (zGridCenter-zWidth); zGrid<=(zGridCenter+zWidth); zGrid++)
0953          {
0954         if (0<=zGrid && zGrid<fNz)
0955           {
0956                             GetNearestDropletInsideGrid(minDistance, center, (unsigned)xGrid, (unsigned)yGrid, (unsigned)zGrid, p, normalizedV, droplet, rotation);
0957            }
0958           }
0959           }
0960     }
0961      }
0962   }
0963 }
0964 
0965 ///////////////////////////////////////////////////////////////////////////////
0966 //
0967 // Get nearest droplet inside a voxel at (xGrid, yGrid, zGrid) along a vector
0968 //
0969 // Return the closest one, as measured along the line
0970 //
0971 void FastAerosol::GetNearestDropletInsideGrid(G4double &minDistance, G4ThreeVector &center, unsigned int xGrid, unsigned int yGrid, unsigned int zGrid, const G4ThreeVector &p, const G4ThreeVector &normalizedV, G4VSolid* droplet, std::function<G4RotationMatrix (G4ThreeVector)> rotation) 
0972 {
0973  unsigned int gi;
0974  PopulateGrid(xGrid, yGrid, zGrid, gi);
0975 
0976  G4double foundDistance;
0977     
0978  // find closest droplet
0979  for (auto it = fGrid[gi].begin(); it != fGrid[gi].end(); ++it) 
0980   {
0981 // could have the following check to see if the ray pierces the bounding sphere. Currently seems like unnecessary addition
0982 /*
0983 deltaP = *it-p;
0984 foundDistance = normalizedV.dot(deltaP);
0985 
0986 if ((0<=foundDistance) && ((deltaP - normalizedV*foundDistance).mag2() < fR2)) {}
0987 */
0988 
0989  G4RotationMatrix irotm = rotation(*it).inverse();
0990 
0991   G4ThreeVector relPos = irotm*(p - *it);
0992 
0993   if (droplet->Inside(relPos) == kInside)
0994   {
0995    minDistance = 0;
0996    center = *it;
0997   }
0998   else
0999   {
1000    foundDistance = droplet->DistanceToIn( relPos , irotm*normalizedV);
1001 
1002     if (foundDistance<minDistance)
1003     {
1004      minDistance = foundDistance;
1005      center = *it;
1006      }
1007    }
1008  }
1009 }
1010 
1011 ///////////////////////////////////////////////////////////////////////////////
1012 //
1013 // Check if a droplet at the indicated point would collide with an existing droplet
1014 //
1015 // Search neighboring voxels, too. Returns true if there is a collision
1016 //
1017 bool FastAerosol::CheckCollision(G4double x, G4double y, G4double z) 
1018 {
1019  G4ThreeVector p(x,y,z);
1020     
1021  std::pair<int, int> minMaxXGrid, minMaxYGrid, minMaxZGrid;
1022 
1023  int xGrid, yGrid, zGrid;
1024  GetGrid(p, xGrid, yGrid, zGrid);
1025 
1026  minMaxXGrid = GetMinMaxSide(xGrid, fNx);
1027  minMaxYGrid = GetMinMaxSide(yGrid, fNy);
1028  minMaxZGrid = GetMinMaxSide(zGrid, fNz);
1029 
1030  for (int xi=minMaxXGrid.first; xi <= minMaxXGrid.second; xi++) {
1031   for (int yi = minMaxYGrid.first; yi <= minMaxYGrid.second; yi++) {
1032     for (int zi = minMaxZGrid.first; zi <= minMaxZGrid.second; zi++) {
1033     if (CheckCollisionInsideGrid(x, y, z, (unsigned)xi, (unsigned)yi, (unsigned)zi)) {
1034         fNumCollisions++;  // log number of collisions for statistics print
1035         return(true);
1036         }
1037             }
1038         }
1039     }
1040     return(false);
1041 }
1042 
1043 ///////////////////////////////////////////////////////////////////////////////
1044 //
1045 // Check if a droplet at the indicated point would collide with an existing
1046 // droplet inside a specific grid
1047 //
1048 // Note that you don't need to lock the mutex since this is only called by code
1049 // that already has the mutex (always called by PopulateGrid).
1050 //
1051 bool FastAerosol::CheckCollisionInsideGrid(G4double x, G4double y, G4double z, unsigned int xi, unsigned int yi, unsigned int zi) 
1052 {
1053  std::vector<G4ThreeVector> *thisGrid = &(fGrid[GetGridIndex(xi, yi, zi)]);
1054  unsigned int numel = (unsigned int)thisGrid->size();
1055 
1056  for (unsigned int i=0; i < numel; i++) 
1057  {
1058   if (CheckCollisionWithDroplet(x, y, z, (*thisGrid)[i]))
1059    return(true);
1060  }
1061 
1062  return(false);
1063 }
1064 
1065 ///////////////////////////////////////////////////////////////////////////////
1066 //
1067 // Check for collsion with a specific droplet
1068 //
1069 bool FastAerosol::CheckCollisionWithDroplet(G4double x, G4double y, G4double z, G4ThreeVector p ) 
1070 {
1071 return( std::pow(x-p.x(), 2.0) + std::pow(y-p.y(), 2.0) + std::pow(z-p.z(), 2.0) < fCollisionLimit2 );
1072 }
1073 
1074 ///////////////////////////////////////////////////////////////////////////////
1075 //
1076 // Save droplet positions to a file for visualization and analysis
1077 //
1078 void FastAerosol::SaveToFile(const char* filename)
1079 {
1080  G4cout << "Saving droplet positions to " << filename << "..." << G4endl;
1081  std::ofstream file;
1082  file.open(filename);
1083 
1084  std::vector<G4ThreeVector> voxel;
1085  G4ThreeVector pt;
1086 
1087  for (auto it1 = fGrid.begin(); it1 != fGrid.end(); ++it1) 
1088       {
1089     voxel = *it1;
1090 
1091      for (auto it2 = voxel.begin(); it2 != voxel.end(); ++it2) 
1092           {
1093         pt = *it2;
1094             
1095         double x = pt.x();
1096         double y = pt.y();
1097         double z = pt.z();
1098         file << std::setprecision(15) << x/mm << "," 
1099              << y/mm << "," << z/mm << "\n";
1100         }
1101     }
1102  file.close();
1103 }