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