1 #include "sph/initial/Distribution.h"
2 #include "math/Functional.h"
3 #include "math/Morton.h"
4 #include "math/rng/VectorRng.h"
5 #include "objects/finders/UniformGrid.h"
6 #include "objects/geometry/Domain.h"
7 #include "objects/wrappers/Optional.h"
8 #include "quantities/Quantity.h"
9 #include "quantities/Storage.h"
10 #include "sph/boundary/Boundary.h"
11 #include "system/Profiler.h"
12 #include "thread/ThreadLocal.h"
13 
14 NAMESPACE_SPH_BEGIN
15 
16 //-----------------------------------------------------------------------------------------------------------
17 // RandomDistribution implementation
18 //-----------------------------------------------------------------------------------------------------------
19 
RandomDistribution(AutoPtr<IRng> && rng)20 RandomDistribution::RandomDistribution(AutoPtr<IRng>&& rng)
21     : rng(std::move(rng)) {}
22 
RandomDistribution(const Size seed)23 RandomDistribution::RandomDistribution(const Size seed)
24     : rng(makeRng<UniformRng>(seed)) {}
25 
generate(IScheduler & UNUSED (scheduler),const Size n,const IDomain & domain) const26 Array<Vector> RandomDistribution::generate(IScheduler& UNUSED(scheduler),
27     const Size n,
28     const IDomain& domain) const {
29     const Box bounds = domain.getBoundingBox();
30     VectorRng<IRng&> boxRng(*rng);
31     Array<Vector> vecs(0, n);
32     // use homogeneous smoothing lenghs regardless of actual spatial variability of particle concentration
33     const Float volume = domain.getVolume();
34     const Float h = root<3>(volume / n);
35     Size found = 0;
36     for (Size i = 0; i < 1e5 * n && found < n; ++i) {
37         Vector w = boxRng() * bounds.size() + bounds.lower();
38         w[H] = h;
39         if (domain.contains(w)) {
40             vecs.push(w);
41             ++found;
42         }
43     }
44     return vecs;
45 }
46 
47 //-----------------------------------------------------------------------------------------------------------
48 // StratifiedDistribution implementation
49 //-----------------------------------------------------------------------------------------------------------
50 
findStep(const Box & bounds,const Size n)51 static Float findStep(const Box& bounds, const Size n) {
52     const Vector size = bounds.size();
53     Float step = maxElement(size);
54     Size particlesPerRegion = n;
55     while (particlesPerRegion > 1000) {
56         step /= 2;
57         const Size numRegions = Size(ceil(size[X] / step) * ceil(size[Y] / step) * ceil(size[Z] / step));
58         particlesPerRegion = n / numRegions;
59     }
60     return step;
61 }
62 
StratifiedDistribution(const Size seed)63 StratifiedDistribution::StratifiedDistribution(const Size seed)
64     : rng(makeRng<UniformRng>(seed)) {}
65 
generate(IScheduler & UNUSED (scheduler),const Size n,const IDomain & domain) const66 Array<Vector> StratifiedDistribution::generate(IScheduler& UNUSED(scheduler),
67     const Size n,
68     const IDomain& domain) const {
69     VectorRng<IRng&> boxRng(*rng);
70     Array<Vector> vecs(0, n);
71     const Float volume = domain.getVolume();
72     const Float h = root<3>(volume / n);
73     Size found = 0;
74     const Box bounds = domain.getBoundingBox();
75     const Vector step(findStep(bounds, n));
76     for (Size i = 0; i < 1e5 * n && found < n; ++i) {
77         bounds.iterate(step, [h, &step, &vecs, &found, &domain, &boxRng](const Vector& r) {
78             const Box box(r, r + step);
79             Vector w = boxRng() * box.size() + box.lower();
80             w[H] = h;
81             if (domain.contains(w)) {
82                 vecs.push(w);
83                 ++found;
84             }
85         });
86     }
87     return vecs;
88 }
89 
90 //-----------------------------------------------------------------------------------------------------------
91 // CubicPacking implementation
92 //-----------------------------------------------------------------------------------------------------------
93 
generate(IScheduler & UNUSED (scheduler),const Size n,const IDomain & domain) const94 Array<Vector> CubicPacking::generate(IScheduler& UNUSED(scheduler),
95     const Size n,
96     const IDomain& domain) const {
97     PROFILE_SCOPE("CubicPacking::generate")
98     SPH_ASSERT(n > 0);
99     const Float volume = domain.getVolume();
100     const Float particleDensity = Float(n) / volume;
101 
102     // interparticle distance based on density
103     const Float h = 1._f / root<3>(particleDensity);
104     SPH_ASSERT(isReal(h));
105 
106     const Box boundingBox = domain.getBoundingBox();
107     const Vector step(h);
108     const Box box(boundingBox.lower() + 0.5_f * step, boundingBox.upper());
109     Array<Vector> vecs;
110     box.iterate(step, [&vecs, &domain, h](Vector&& v) {
111         if (domain.contains(v)) {
112             v[H] = h;
113             vecs.push(std::move(v));
114         }
115     });
116     return vecs;
117 }
118 
119 //-----------------------------------------------------------------------------------------------------------
120 // HexagonalPacking implementation
121 //-----------------------------------------------------------------------------------------------------------
122 
HexagonalPacking(const Flags<Options> flags)123 HexagonalPacking::HexagonalPacking(const Flags<Options> flags)
124     : flags(flags) {}
125 
generate(IScheduler & UNUSED (scheduler),const Size n,const IDomain & domain) const126 Array<Vector> HexagonalPacking::generate(IScheduler& UNUSED(scheduler),
127     const Size n,
128     const IDomain& domain) const {
129     VERBOSE_LOG
130     SPH_ASSERT(n > 0);
131     const Float volume = domain.getVolume();
132     const Float particleDensity = Float(n) / volume;
133 
134     // interparticle distance based on density
135     const Float h = 1._f / root<3>(particleDensity);
136     const Float dx = (flags.has(Options::SPH5_COMPATIBILITY) ? 1._f : 1.1_f) * h;
137     const Float dy = sqrt(3._f) * 0.5_f * dx;
138     const Float dz = sqrt(6._f) / 3._f * dx;
139 
140     const Box boundingBox = domain.getBoundingBox();
141     SPH_ASSERT(boundingBox.volume() > 0._f && boundingBox.volume() < pow<3>(LARGE));
142     const Vector step(dx, dy, dz);
143     const Box box = flags.has(Options::SPH5_COMPATIBILITY)
144                         ? boundingBox
145                         : Box(boundingBox.lower() + 0.5_f * step, boundingBox.upper());
146     Array<Vector> vecs;
147     const Float deltaX = 0.5_f * dx;
148     const Float deltaY = sqrt(3._f) / 6._f * dx;
149 
150     this->startProgress(n);
151 
152     std::atomic_bool shouldContinue{ true };
153     box.iterateWithIndices(step, [&](Indices&& idxs, Vector&& v) {
154         if (!shouldContinue) {
155             return;
156         }
157 
158         if (idxs[2] % 2 == 0) {
159             if (idxs[1] % 2 == 1) {
160                 v[X] += deltaX;
161             }
162         } else {
163             if (idxs[1] % 2 == 0) {
164                 v[X] += deltaX;
165             }
166             v[Y] += deltaY;
167         }
168         if (domain.contains(v)) {
169             v[H] = h;
170             vecs.push(std::move(v));
171 
172             if (!this->tickProgress()) {
173                 shouldContinue = false;
174             }
175         }
176     });
177     if (flags.has(Options::SORTED)) {
178         // sort by Morton code
179         std::sort(vecs.begin(), vecs.end(), [&box](Vector& v1, Vector& v2) {
180             // compute relative coordinates in bounding box
181             const Vector vr1 = (v1 - box.lower()) / box.size();
182             const Vector vr2 = (v2 - box.lower()) / box.size();
183             return morton(vr1) > morton(vr2);
184         });
185     }
186     if (flags.has(Options::CENTER)) {
187         SPH_ASSERT(!vecs.empty());
188         Vector com(0._f);
189         for (const Vector& v : vecs) {
190             com += v;
191         }
192         com /= vecs.size();
193         // match center of mass to center of domain
194         Vector delta = domain.getCenter() - com;
195         delta[H] = 0._f;
196         for (Vector& v : vecs) {
197             v += delta;
198         }
199     }
200     return vecs;
201 }
202 
203 //-----------------------------------------------------------------------------------------------------------
204 // DiehlDistribution implementation
205 //-----------------------------------------------------------------------------------------------------------
206 
DiehlDistribution(const DiehlParams & params)207 DiehlDistribution::DiehlDistribution(const DiehlParams& params)
208     : params(params) {}
209 
210 namespace {
211 
212 class ForwardingDomain : public IDomain {
213 private:
214     const IDomain& domain;
215 
216 public:
ForwardingDomain(const IDomain & domain)217     explicit ForwardingDomain(const IDomain& domain)
218         : domain(domain) {}
219 
getCenter() const220     virtual Vector getCenter() const override {
221         return domain.getCenter();
222     }
223 
getBoundingBox() const224     virtual Box getBoundingBox() const override {
225         return domain.getBoundingBox();
226     }
227 
getVolume() const228     virtual Float getVolume() const override {
229         return domain.getVolume();
230     }
231 
getSurfaceArea() const232     virtual Float getSurfaceArea() const override {
233         return domain.getSurfaceArea();
234     }
235 
contains(const Vector & v) const236     virtual bool contains(const Vector& v) const override {
237         return domain.contains(v);
238     }
239 
getSubset(ArrayView<const Vector> vs,Array<Size> & output,const SubsetType type) const240     virtual void getSubset(ArrayView<const Vector> vs,
241         Array<Size>& output,
242         const SubsetType type) const override {
243         return domain.getSubset(vs, output, type);
244     }
245 
getDistanceToBoundary(ArrayView<const Vector> vs,Array<Float> & distances) const246     virtual void getDistanceToBoundary(ArrayView<const Vector> vs, Array<Float>& distances) const override {
247         domain.getDistanceToBoundary(vs, distances);
248     }
249 
project(ArrayView<Vector> vs,Optional<ArrayView<Size>> indices=NOTHING) const250     virtual void project(ArrayView<Vector> vs, Optional<ArrayView<Size>> indices = NOTHING) const override {
251         domain.project(vs, indices);
252     }
addGhosts(ArrayView<const Vector> vs,Array<Ghost> & ghosts,const Float radius=2._f,const Float eps=0.05_f) const253     virtual void addGhosts(ArrayView<const Vector> vs,
254         Array<Ghost>& ghosts,
255         const Float radius = 2._f,
256         const Float eps = 0.05_f) const override {
257         domain.addGhosts(vs, ghosts, radius, eps);
258     }
259 };
260 
261 } // namespace
262 
263 /// Renormalizes particle density so that integral matches expected particle count.
264 ///
265 /// Uses iterative approach, finding normalization coefficient until the different between the expected and
266 /// the final number of particles is less than the error.
267 /// \param domain in which the particles are created
268 /// \param n Input/output parameter - Takes the expected number of particles and returns the final number.
269 /// \param error Maximum difference between the expected and the final number of particles.
270 /// \param density Input (unnormalized) particle density
271 /// \return Functor representing the renormalized density
272 template <typename TDensity>
renormalizeDensity(const IDomain & domain,Size & n,const Size error,TDensity & density)273 static auto renormalizeDensity(const IDomain& domain, Size& n, const Size error, TDensity& density) {
274     VERBOSE_LOG
275 
276     Float multiplier = n / domain.getVolume();
277     auto actDensity = [&domain, &density, &multiplier](const Vector& v) {
278         if (domain.contains(v)) {
279             return multiplier * density(v);
280         } else {
281             return 0._f;
282         }
283     };
284 
285     Integrator<HaltonQrng> mc(domain);
286     Size cnt = 0;
287     Float particleCnt;
288     for (particleCnt = mc.integrate(actDensity); abs(particleCnt - n) > error;) {
289         const Float ratio = n / max(particleCnt, 1._f);
290         SPH_ASSERT(ratio > EPS, ratio);
291         multiplier *= ratio;
292         particleCnt = mc.integrate(actDensity);
293         if (cnt++ > 100) { // break potential infinite loop
294             break;
295         }
296     }
297     n = Size(particleCnt);
298     // create a different functor that captures multiplier by value, so we can return it from the function
299     return [&domain, &density, multiplier](const Vector& v) {
300         if (domain.contains(v)) {
301             return multiplier * density(v);
302         } else {
303             return 0._f;
304         }
305     };
306 }
307 
308 /// Generate the initial positions in Diehl's distribution.
309 /// \param domain Domain in which the particles are created
310 /// \param N Number of particles to create
311 /// \param density Functor describing the particle density (concentration).
312 template <typename TDensity>
generateInitial(const IDomain & domain,const Size N,TDensity && density)313 static Storage generateInitial(const IDomain& domain, const Size N, TDensity&& density) {
314     Box boundingBox = domain.getBoundingBox();
315     VectorPdfRng<HaltonQrng> rng(boundingBox, density);
316 
317     Array<Vector> r(0, N);
318     for (Size i = 0; i < N; ++i) {
319         Vector pos = rng();
320         const Float n = density(pos);
321         pos[H] = 1._f / root<3>(n);
322         SPH_ASSERT(isReal(pos));
323         r.push(pos);
324     }
325 
326     // create a dummy storage so that we can use the functionality of GhostParticles
327     Storage storage;
328     storage.insert<Vector>(QuantityId::POSITION, OrderEnum::ZERO, std::move(r));
329     return storage;
330 }
331 
generate(IScheduler & scheduler,const Size expectedN,const IDomain & domain) const332 Array<Vector> DiehlDistribution::generate(IScheduler& scheduler,
333     const Size expectedN,
334     const IDomain& domain) const {
335     VERBOSE_LOG
336 
337     Size N = expectedN;
338     auto actDensity = renormalizeDensity(domain, N, params.maxDifference, params.particleDensity);
339     SPH_ASSERT(abs(int(N) - int(expectedN)) <= int(params.maxDifference));
340 
341     // generate initial particle positions
342     Storage storage = generateInitial(domain, N, actDensity);
343     Array<Vector>& r = storage.getValue<Vector>(QuantityId::POSITION);
344 
345     GhostParticles bc(makeAuto<ForwardingDomain>(domain), 2._f, EPS);
346 
347     UniformGridFinder finder;
348     ThreadLocal<Array<NeighborRecord>> neighs(scheduler);
349     finder.build(scheduler, r);
350 
351     const Float correction = params.strength / (1._f + params.small);
352     // radius of search, does not have to be equal to radius of used SPH kernel
353     const Float kernelRadius = 2._f;
354 
355     this->startProgress(params.numOfIters);
356 
357     Array<Vector> deltas(N);
358     for (Size k = 0; k < params.numOfIters; ++k) {
359         VerboseLogGuard guard("DiehlDistribution::generate - iteration " + toString(k));
360 
361         // notify caller, if requested
362         if (!this->tickProgress(r)) {
363             break;
364         }
365 
366         // gradually decrease the strength of particle dislocation
367         const Float converg = 1._f / sqrt(Float(k + 1));
368 
369         // add ghost particles
370         bc.initialize(storage);
371 
372         // reconstruct finder to allow for variable topology of particles (we need to reset the internal
373         // arrayview as we added ghosts)
374         finder.build(scheduler, r);
375 
376         // clean up the previous displacements
377         deltas.fill(Vector(0._f));
378 
379         auto lambda = [&](const Size i, Array<NeighborRecord>& neighsTl) {
380             const Float rhoi = actDensity(r[i]); // average particle density
381             // average interparticle distance at given point
382             const Float neighborRadius = kernelRadius / root<3>(rhoi);
383             finder.findAll(i, neighborRadius, neighsTl);
384 
385             for (Size j = 0; j < neighsTl.size(); ++j) {
386                 const Size k = neighsTl[j].index;
387                 const Vector diff = r[k] - r[i];
388                 const Float lengthSqr = getSqrLength(diff);
389                 // for ghost particles, just copy the density (density outside the domain is always 0)
390                 const Float rhok = (k >= N) ? rhoi : actDensity(r[k]);
391                 if (rhoi == 0._f || rhok == 0._f) {
392                     // outside of the domain? do not move
393                     continue;
394                 }
395                 // average kernel radius to allow for the gradient of particle density
396                 const Float h = kernelRadius * (0.5_f / root<3>(rhoi) + 0.5_f / root<3>(rhok));
397                 if (lengthSqr > h * h || lengthSqr == 0) {
398                     continue;
399                 }
400                 const Float hSqrInv = 1._f / (h * h);
401                 const Float length = getLength(diff);
402                 SPH_ASSERT(length != 0._f);
403                 const Vector diffUnit = diff / length;
404                 const Float t =
405                     converg * h *
406                     (params.strength / (params.small + getSqrLength(diff) * hSqrInv) - correction);
407                 deltas[i] += diffUnit * min(t, h); // clamp the displacement to particle distance
408                 SPH_ASSERT(isReal(deltas[i]));
409             }
410             deltas[i][H] = 0._f; // do not affect smoothing lengths
411         };
412         parallelFor(scheduler, neighs, 0, N, 100, lambda);
413 
414         // apply the computed displacements; note that r might be larger than deltas due to ghost particles -
415         // we don't need to move them
416         for (Size i = 0; i < deltas.size(); ++i) {
417             r[i] -= deltas[i];
418         }
419 
420         // remove the ghosts
421         bc.finalize(storage);
422 
423         // project particles outside of the domain to the boundary
424         // (there shouldn't be any, but it may happen for large strengths / weird boundaries)
425         domain.project(r);
426     }
427 
428 #ifdef SPH_DEBUG
429     for (Size i = 0; i < N; ++i) {
430         SPH_ASSERT(isReal(r[i]) && r[i][H] > 1.e-20_f);
431     }
432 #endif
433     // extract the buffers from the storage
434     Array<Vector> positions = std::move(storage.getValue<Vector>(QuantityId::POSITION));
435     return positions;
436 }
437 
438 //-----------------------------------------------------------------------------------------------------------
439 // ParametrizedSpiralingDistribution implementation
440 //-----------------------------------------------------------------------------------------------------------
441 
ParametrizedSpiralingDistribution(const Size seed)442 ParametrizedSpiralingDistribution::ParametrizedSpiralingDistribution(const Size seed)
443     : seed(seed) {}
444 
generate(IScheduler & UNUSED (scheduler),const Size n,const IDomain & domain) const445 Array<Vector> ParametrizedSpiralingDistribution::generate(IScheduler& UNUSED(scheduler),
446     const Size n,
447     const IDomain& domain) const {
448     const Vector center = domain.getCenter();
449     const Float volume = domain.getVolume();
450     const Box bbox = domain.getBoundingBox();
451     const Float R = 0.5_f * maxElement(bbox.size());
452 
453     // interparticle distance based on density
454     const Float h = root<3>(volume / n);
455     const Size numShells = Size(R / h);
456     Array<Float> shells(numShells);
457     Float total = 0._f;
458     for (Size i = 0; i < numShells; ++i) {
459         shells[i] = sqr((i + 1) * h);
460         total += shells[i];
461     }
462     SPH_ASSERT(isReal(total));
463 
464     Float mult = Float(n) / total;
465     for (Size i = 0; i < numShells; ++i) {
466         shells[i] *= mult;
467     }
468 
469     this->startProgress(n);
470 
471     Array<Vector> pos;
472     Float phi = 0._f;
473     Size shellIdx = 0;
474     UniformRng rng(seed);
475     for (Float r = h; r <= R; r += h, shellIdx++) {
476         Vector dir = sampleUnitSphere(rng);
477         Float rot = 2._f * PI * rng();
478         AffineMatrix rotator = AffineMatrix::rotateAxis(dir, rot);
479         const Size m = Size(ceil(shells[shellIdx]));
480         for (Size k = 1; k < m; ++k) {
481             const Float hk = -1._f + 2._f * Float(k) / m;
482             const Float theta = acos(hk);
483             phi += 3.8_f / sqrt(m * (1._f - sqr(hk)));
484             Vector v = center + rotator * sphericalToCartesian(r, theta, phi);
485             if (domain.contains(v)) {
486                 v[H] = h; // 0.66_f * sqrt(sphereSurfaceArea(r) / m);
487                 SPH_ASSERT(isReal(v));
488                 pos.push(v);
489 
490                 if (!this->tickProgress()) {
491                     return {};
492                 }
493             }
494         }
495     }
496     return pos;
497 }
498 //-----------------------------------------------------------------------------------------------------------
499 // LinearDistribution implementation
500 //-----------------------------------------------------------------------------------------------------------
501 
generate(IScheduler & UNUSED (scheduler),const Size n,const IDomain & domain) const502 Array<Vector> LinearDistribution::generate(IScheduler& UNUSED(scheduler),
503     const Size n,
504     const IDomain& domain) const {
505     const Float center = domain.getCenter()[X];
506     const Float radius = 0.5_f * domain.getBoundingBox().size()[X];
507     Array<Vector> vs(0, n);
508     const Float dx = 2._f * radius / (n - 1);
509     for (Size i = 0; i < n; ++i) {
510         const Float x = center - radius + (2._f * radius * i) / (n - 1);
511         vs.push(Vector(x, 0._f, 0._f, 1.5_f * dx)); // smoothing length = interparticle distance
512     }
513     return vs;
514 }
515 
516 NAMESPACE_SPH_END
517