SPH
Analysis.cpp
Go to the documentation of this file.
1 #include "post/Analysis.h"
2 #include "io/Column.h"
3 #include "io/Logger.h"
4 #include "io/Output.h"
7 #include "objects/geometry/Box.h"
9 #include "post/MarchingCubes.h"
10 #include "post/Point.h"
11 #include "post/TwoBody.h"
12 #include "quantities/Storage.h"
13 #include "sph/initial/MeshDomain.h"
14 #include "sph/kernel/Kernel.h"
15 #include "system/Factory.h"
16 #include "thread/Scheduler.h"
17 #include <numeric>
18 #include <set>
19 
21 
22 Array<Size> Post::findNeighbourCounts(const Storage& storage, const Float particleRadius) {
26  finder->build(SEQUENTIAL, r);
27  Array<Size> counts(r.size());
28  for (Size i = 0; i < r.size(); ++i) {
29  counts[i] = finder->findAll(i, r[i][H] * particleRadius, neighs);
30  }
31  return counts;
32 }
33 
34 // Checks if two particles belong to the same component
35 struct ComponentChecker : public Polymorphic {
36  virtual bool belong(const Size UNUSED(i), const Size UNUSED(j)) {
37  // by default, any two particles within the search radius belong to the same component
38  return true;
39  }
40 };
41 
42 static Size findComponentsImpl(ComponentChecker& checker,
44  const Float radius,
45  Array<Size>& indices) {
46  // initialize stuff
47  indices.resize(r.size());
48  const Size unassigned = Size(-1);
49  indices.fill(unassigned);
50  Size componentIdx = 0;
51 
52  Array<Size> stack;
54 
56  // the build time is probably negligible compared to the actual search of components, so let's just use
57  // the sequential execution here
58  finder->build(SEQUENTIAL, r);
59 
60  for (Size i = 0; i < r.size(); ++i) {
61  if (indices[i] == unassigned) {
62  indices[i] = componentIdx;
63  stack.push(i);
64  // find new neigbours recursively until we find all particles in the component
65  while (!stack.empty()) {
66  const Size index = stack.pop();
67  finder->findAll(index, r[index][H] * radius, neighs);
68  for (auto& n : neighs) {
69  if (!checker.belong(index, n.index)) {
70  // do not count as neighbours
71  continue;
72  }
73  if (indices[n.index] == unassigned) {
74  indices[n.index] = componentIdx;
75  stack.push(n.index);
76  }
77  }
78  }
79  componentIdx++;
80  }
81  }
82 
83  return componentIdx;
84 }
85 
87  const Float radius,
88  const Flags<ComponentFlag> flags,
89  Array<Size>& indices) {
90  SPH_ASSERT(radius > 0._f);
91 
92  AutoPtr<ComponentChecker> checker = makeAuto<ComponentChecker>();
93 
94  if (flags.has(ComponentFlag::SEPARATE_BY_FLAG)) {
95  class FlagComponentChecker : public ComponentChecker {
97 
98  public:
99  explicit FlagComponentChecker(const Storage& storage) {
100  flag = storage.getValue<Size>(QuantityId::FLAG);
101  }
102  virtual bool belong(const Size i, const Size j) override {
103  return flag[i] == flag[j];
104  }
105  };
106  checker = makeAuto<FlagComponentChecker>(storage);
107  }
108 
110  Size componentCnt = findComponentsImpl(*checker, r, radius, indices);
111 
112  if (flags.has(ComponentFlag::ESCAPE_VELOCITY)) {
113  // now we have to merge components with relative velocity lower than the (mutual) escape velocity
114 
115  // first, compute the total mass and the average velocity of each component
116  Array<Float> masses(componentCnt);
117  Array<Vector> positions(componentCnt);
118  Array<Vector> velocities(componentCnt);
119  Array<Float> volumes(componentCnt);
120 
121  masses.fill(0._f);
122  positions.fill(Vector(0._f));
123  velocities.fill(Vector(0._f));
124  volumes.fill(0._f);
125 
128 
129  for (Size i = 0; i < r.size(); ++i) {
130  masses[indices[i]] += m[i];
131  positions[indices[i]] += m[i] * r[i];
132  velocities[indices[i]] += m[i] * v[i];
133  volumes[indices[i]] += pow<3>(r[i][H]);
134  }
135  for (Size k = 0; k < componentCnt; ++k) {
136  SPH_ASSERT(masses[k] > 0._f);
137  positions[k] /= masses[k];
138  positions[k][H] = cbrt(3._f * volumes[k] / (4._f * PI));
139  velocities[k] /= masses[k];
140  }
141 
142  // Helper checker connecting components with relative velocity lower than v_esc
143  struct EscapeVelocityComponentChecker : public ComponentChecker {
147  Float radius;
148 
149  virtual bool belong(const Size i, const Size j) override {
150  const Float dv = getLength(v[i] - v[j]);
151  const Float dr = getLength(r[i] - r[j]);
152  const Float m_tot = m[i] + m[j];
153  const Float v_esc = sqrt(2._f * Constants::gravity * m_tot / dr);
154  return dv < v_esc;
155  }
156  };
157  EscapeVelocityComponentChecker velocityChecker;
158  velocityChecker.r = positions;
159  velocityChecker.v = velocities;
160  velocityChecker.m = masses;
161  velocityChecker.radius = radius;
162 
163  // run the component finder again, this time for the components found in the first step
164  Array<Size> velocityIndices;
165  componentCnt = findComponentsImpl(velocityChecker, positions, 50._f, velocityIndices);
166 
167  // We should keep merging the components, as now we could have created a new component that was
168  // previously undetected (three bodies bound to their center of gravity, where each two bodies move
169  // faster than the escape velocity of those two bodies). That is not very probable, though, so we end
170  // the process here.
171 
172  // Last thing - we have to reindex the components found in the first step, using the indices from the
173  // second step.
174  SPH_ASSERT(r.size() == indices.size());
175  for (Size i = 0; i < r.size(); ++i) {
176  indices[i] = velocityIndices[indices[i]];
177  }
178  }
179 
180 #ifdef SPH_DEBUG
181  std::set<Size> uniqueIndices;
182  for (Size i : indices) {
183  uniqueIndices.insert(i);
184  }
185  SPH_ASSERT(uniqueIndices.size() == componentCnt);
186  Size counter = 0;
187  for (Size i : uniqueIndices) {
188  SPH_ASSERT(i == counter);
189  counter++;
190  }
191 
192 #endif
193 
194  if (flags.has(ComponentFlag::SORT_BY_MASS)) {
195  Array<Float> componentMass(componentCnt);
196  componentMass.fill(0._f);
198  for (Size i = 0; i < indices.size(); ++i) {
199  componentMass[indices[i]] += m[i];
200  }
201  Order mapping(componentCnt);
202  mapping.shuffle([&componentMass](Size i, Size j) { return componentMass[i] > componentMass[j]; });
203  mapping = mapping.getInverted();
204 
205  Array<Size> realIndices(indices.size());
206  for (Size i = 0; i < indices.size(); ++i) {
207  realIndices[i] = mapping[indices[i]];
208  }
209 
210  indices = copyable(realIndices);
211  }
212 
213  return componentCnt;
214 }
215 
217  const Float particleRadius,
218  const Flags<ComponentFlag> flags) {
219  Array<Size> componentIdxs;
220  Post::findComponents(storage, particleRadius, flags | ComponentFlag::SORT_BY_MASS, componentIdxs);
221 
222  // get the indices of the largest component (with index 0)
223  Array<Size> idxs;
224  for (Size i = 0; i < componentIdxs.size(); ++i) {
225  if (componentIdxs[i] == 0) {
226  idxs.push(i);
227  }
228  }
229  return idxs;
230 }
231 
232 
233 /*static Storage clone(const Storage& storage) {
234  Storage cloned;
235  const Array<Vector>& r = storage.getValue<Vector>(QuantityId::POSITION);
236  cloned.insert<Vector>(QuantityId::POSITION, OrderEnum::FIRST, r.clone());
237 
238  const Array<Vector>& v = storage.getDt<Vector>(QuantityId::POSITION);
239  cloned.getDt<Vector>(QuantityId::POSITION) = v.clone();
240 
241  if (storage.has(QuantityId::MASS)) {
242  const Array<Float>& m = storage.getValue<Float>(QuantityId::MASS);
243  cloned.insert<Float>(QuantityId::MASS, OrderEnum::ZERO, m.clone());
244  } else {
245  ArrayView<const Float> rho = storage.getValue<Float>(QuantityId::DENSITY);
246  Float rhoAvg = 0._f;
247  for (Size i = 0; i < r.size(); ++i) {
248  rhoAvg += rho[i];
249  }
250  rhoAvg /= r.size();
251 
253  const Float m = sphereVolume(5.e3_f) * rhoAvg / r.size();
254  cloned.insert<Float>(QuantityId::MASS, OrderEnum::ZERO, m);
255  }
256 
257  return cloned;
258 }*/
259 
260 /*Storage Post::findFutureBodies2(const Storage& storage, ILogger& logger) {
261  Array<Vector> r = storage.getValue<Vector>(QuantityId::POSITION).clone();
262  Array<Vector> v = storage.getDt<Vector>(QuantityId::POSITION).clone();
263  const Float m = sphereVolume(5.e3_f) * 2700.f / r.size();
264  Float W_tot = 0._f;
265  for (Size i = 0; i < r.size(); ++i) {
266  for (Size j = i + 1; j < r.size(); ++j) {
267  W_tot += Constants::gravity * sqr(m) / getLength(r[i] - r[j]);
268  SPH_ASSERT(isReal(W_tot));
269  }
270  }
271 
272  Size iteration = 0;
273  while (true) {
274  // find velocity of COM
275  Vector v0(0._f);
276  for (Size i = 0; i < v.size(); ++i) {
277  v0 += v[i];
278  }
279  v0 /= v.size();
280 
281  // find kinetic energies
282  Float K_tot = 0._f;
283  Float K_largest = 0._f;
284  Size idx_largest = 0;
285  for (Size i = 0; i < r.size(); ++i) {
286  const Float k = 0.5_f * m * getSqrLength(v[i] - v0);
287  K_tot += k;
288  if (k > K_largest) {
289  K_largest = k;
290  idx_largest = i;
291  }
292  }
293 
294  logger.write("Iteration ", iteration++, ", W = ", W_tot, " / K = ", K_tot);
295  if (K_tot > W_tot) {
296  for (Size i = 0; i < r.size(); ++i) {
297  if (i != idx_largest) {
298  W_tot -= Constants::gravity * sqr(m) / getLength(r[i] - r[idx_largest]);
299  }
300  SPH_ASSERT(W_tot > 0._f);
301  }
302 
303  r.remove(idx_largest);
304  v.remove(idx_largest);
305  } else {
306  break;
307  }
308  }
309 
310  logger.write("Find largest remnant with ", r.size(), " particles");
311  return clone(storage);
312 }
313 
314 Storage Post::findFutureBodies(const Storage& storage, const Float particleRadius, ILogger& logger) {
315  Storage cloned = clone(storage);
316  Size numComponents = 0, prevNumComponents;
317  Size iter = 0;
318  do {
319  Array<Size> indices;
320  prevNumComponents = numComponents;
321 
322  logger.write(
323  "Iteration ", iter, ": number of bodies: ", iter == 0 ? storage.getParticleCnt() : numComponents);
324 
325  // do merging the first iteration, the follow with energy considerations
326  ComponentConnectivity connectivity =
327  (iter == 0) ? ComponentConnectivity::OVERLAP : ComponentConnectivity::ESCAPE_VELOCITY;
328  numComponents = findComponents(cloned, particleRadius, connectivity, indices);
329 
330  Array<Vector> r_new(numComponents);
331  Array<Vector> v_new(numComponents);
332  Array<Float> h_new(numComponents);
333  Array<Float> m_new(numComponents);
334  r_new.fill(Vector(0._f));
335  v_new.fill(Vector(0._f));
336  h_new.fill(0._f);
337  m_new.fill(0._f);
338 
339 
340  ArrayView<const Vector> r = cloned.getValue<Vector>(QuantityId::POSITION);
341  ArrayView<const Vector> v = cloned.getDt<Vector>(QuantityId::POSITION);
342  ArrayView<const Float> m = cloned.getValue<Float>(QuantityId::MASS);
343 
344  for (Size i = 0; i < r.size(); ++i) {
345  m_new[indices[i]] += m[i];
346  r_new[indices[i]] += m[i] * r[i];
347  h_new[indices[i]] += pow<3>(r[i][H]);
348  v_new[indices[i]] += m[i] * v[i];
349  }
350  for (Size i = 0; i < numComponents; ++i) {
351  SPH_ASSERT(m_new[i] != 0._f);
352  r_new[i] /= m_new[i];
353  r_new[i][H] = root<3>(h_new[i]);
354  v_new[i] /= m_new[i];
355  }
356 
357  cloned.getValue<Vector>(QuantityId::POSITION) = std::move(r_new);
358  cloned.getDt<Vector>(QuantityId::POSITION) = std::move(v_new);
359  cloned.getValue<Float>(QuantityId::MASS) = std::move(m_new);
360 
361  iter++;
362  } while (numComponents != prevNumComponents);
363 
364  return cloned;
365 }*/
366 
367 Array<Post::MoonEnum> Post::findMoons(const Storage& storage, const Float radius, const Float limit) {
368  // first, find the larget one
370  const auto largestIter = std::max_element(m.begin(), m.end());
371  const Float largestM = *largestIter;
372  const Size largestIdx = std::distance(m.begin(), largestIter);
373 
374  Array<MoonEnum> statuses(m.size());
375 #ifdef SPH_DEBUG
376  statuses.fill(MoonEnum(-1));
377 #endif
378  statuses[largestIdx] = MoonEnum::LARGEST_FRAGMENT;
379 
380  // find the ellipse for all bodies
383  for (Size i = 0; i < m.size(); ++i) {
384  if (i == largestIdx) {
385  continue;
386  }
387 
388  // check for observability
389  if (r[i][H] < limit * r[largestIdx][H]) {
390  statuses[i] = MoonEnum::UNOBSERVABLE;
391  continue;
392  }
393 
394  // compute the orbital elements
396  m[i] + largestM, m[i] * largestM / (m[i] + largestM), r[i] - r[largestIdx], v[i] - v[largestIdx]);
397 
398  if (!elements) {
399  // not bound, mark as ejected body
400  statuses[i] = MoonEnum::RUNAWAY;
401  } else {
402  // if the pericenter is closer than the sum of radii, mark as impactor
403  if (elements->pericenterDist() < radius * (r[i][H] + r[largestIdx][H])) {
404  statuses[i] = MoonEnum::IMPACTOR;
405  } else {
406  // bound and not on collisional trajectory
407  statuses[i] = MoonEnum::MOON;
408  }
409  }
410  }
411 
412  return statuses;
413 }
414 
418  const Size i,
419  const Float radius,
420  const Float limit) {
421  SPH_ASSERT(std::is_sorted(m.begin(), m.end(), std::greater<Float>{}));
422  SPH_ASSERT(r.size() == m.size());
423 
424  Size count = 0;
425  for (Size j = i + 1; j < r.size(); ++j) {
426  if (m[j] < limit * m[i]) {
427  break;
428  }
429 
431  m[i] + m[j], m[i] * m[j] / (m[i] + m[j]), r[i] - r[j], v[i] - v[j]);
432 
433  if (elements && elements->pericenterDist() > radius * (r[i][H] + r[j][H])) {
434  count++;
435  }
436  }
437 
438  return count;
439 }
440 
441 Array<Post::Tumbler> Post::findTumblers(const Storage& storage, const Float limit) {
442  Array<Tumbler> tumblers;
445 
446  for (Size i = 0; i < omega.size(); ++i) {
447  const Vector L = I[i] * omega[i];
448  if (omega[i] == Vector(0._f)) {
449  continue;
450  }
451  const Float cosBeta = dot(L, omega[i]) / (getLength(L) * getLength(omega[i]));
452  SPH_ASSERT(cosBeta >= -1._f && cosBeta <= 1._f);
453  const Float beta = acos(cosBeta);
454  if (beta > limit) {
455  tumblers.push(Tumbler{ i, beta });
456  }
457  }
458  return tumblers;
459 }
460 
463  ArrayView<const Size> idxs) {
464  Vector r_com(0._f);
465  Float m_tot = 0._f;
466  auto functor = [&m_tot, &r_com, m, r](Size i) {
467  r_com += m[i] * r[i];
468  m_tot += m[i];
469  };
470  if (idxs) {
471  for (Size i : idxs) {
472  functor(i);
473  }
474  } else {
475  for (Size i = 0; i < r.size(); ++i) {
476  functor(i);
477  }
478  }
479  r_com /= m_tot;
480  r_com[H] = 0._f;
481  return r_com;
482 }
483 
486  const Vector& r0,
487  ArrayView<const Size> idxs) {
489 
490  auto functor = [&I, r, m, &r0](Size i) {
491  const Vector dr = r[i] - r0;
492  I += m[i] * (SymmetricTensor::identity() * getSqrLength(dr) - symmetricOuter(dr, dr));
493  };
494  if (idxs) {
495  for (Size i : idxs) {
496  functor(i);
497  }
498  } else {
499  for (Size i = 0; i < r.size(); ++i) {
500  functor(i);
501  }
502  }
503  return I;
504 }
505 
508  ArrayView<const Size> idxs) {
509 
510  const Vector r_com = getCenterOfMass(m, r, idxs);
511  return getInertiaTensor(m, r, r_com, idxs);
512 }
513 
517  const Vector& r0,
518  const Vector& v0,
519  ArrayView<const Size> idxs) {
520  SymmetricTensor I = getInertiaTensor(m, r, r0, idxs);
521  Vector L(0._f);
522  auto functor = [&L, m, r, v, &r0, &v0](const Size i) { //
523  L += m[i] * cross(r[i] - r0, v[i] - v0);
524  };
525 
526  if (idxs) {
527  for (Size i : idxs) {
528  functor(i);
529  }
530  } else {
531  for (Size i = 0; i < r.size(); ++i) {
532  functor(i);
533  }
534  }
535  // L = I * omega => omega = I^-1 * L)
536  const SymmetricTensor I_inv = I.inverse();
537  SPH_ASSERT(isReal(I_inv));
538  return I_inv * L;
539 }
540 
544  ArrayView<const Size> idxs) {
545  const Vector r_com = getCenterOfMass(m, r, idxs);
546  const Vector v_com = getCenterOfMass(m, v, idxs);
547  return getAngularFrequency(m, r, v, r_com, v_com, idxs);
548 }
549 
550 Float Post::getSphericity(IScheduler& scheduler, const Storage& storage, const Float resolution) {
551  const Box boundingBox = getBoundingBox(storage);
552  McConfig config;
553  config.gridResolution = resolution * maxElement(boundingBox.size());
554  config.surfaceLevel = 0.15_f;
555  Array<Triangle> mesh = getSurfaceMesh(scheduler, storage, config);
556  Float area = 0._f;
557  for (const Triangle& triangle : mesh) {
558  area += triangle.area();
559  }
560  SPH_ASSERT(area > 0._f);
561 
562  MeshParams params;
563  params.precomputeInside = false;
564  MeshDomain domain(scheduler, std::move(mesh), params);
565  const Float volume = domain.getVolume();
566  SPH_ASSERT(volume > 0._f);
567 
568  // https://en.wikipedia.org/wiki/Sphericity
569  return pow(PI * sqr(6._f * volume), 1._f / 3._f) / area;
570 }
571 
572 bool Post::HistPoint::operator==(const HistPoint& other) const {
573  return value == other.value && count == other.count;
574 }
575 
577 template <typename TValueFunctor>
578 static Array<Float> processParticleCutoffs(const Storage& storage,
579  const Post::HistogramParams& params,
580  const TValueFunctor& functor) {
583 
584  // only require mass/velocity if the correpsonding cutoff is specified
585  if (params.massCutoff > 0._f) {
586  m = storage.getValue<Float>(QuantityId::MASS);
587  }
588  if (params.velocityCutoff < INFTY) {
589  v = storage.getDt<Vector>(QuantityId::POSITION);
590  }
591  Array<Float> filtered;
592  for (Size i = 0; i < storage.getParticleCnt(); ++i) {
593  if (m && m[i] < params.massCutoff) {
594  continue;
595  }
596  if (v && getLength(v[i]) > params.velocityCutoff) {
597  continue;
598  }
599  if (params.validator(i)) {
600  filtered.push(functor(i));
601  }
602  }
603  return filtered;
604 }
605 
607 static Array<Float> getParticleValues(const Storage& storage,
608  const Post::HistogramParams& params,
609  const Post::HistogramId id) {
610 
611  switch (id) {
614  return processParticleCutoffs(storage, params, [r](Size i) { return r[i][H]; });
615  }
618  return processParticleCutoffs(storage, params, [m, &params](Size i) {
619  return root<3>(3._f * m[i] / (params.referenceDensity * 4._f * PI));
620  });
621  }
624  return processParticleCutoffs(storage, params, [v](Size i) { return getLength(v[i]); });
625  }
627  if (!storage.has(QuantityId::ANGULAR_FREQUENCY)) {
628  return {};
629  }
631  return processParticleCutoffs(storage, params, [omega](Size i) {
632  const Float w = getLength(omega[i]);
633  if (w == 0._f) {
634  // placeholder for zero frequency to avoid division by zero
635  return 0._f;
636  } else {
637  return 2._f * PI / (3600._f * w);
638  }
639  });
640  }
642  if (!storage.has(QuantityId::ANGULAR_FREQUENCY)) {
643  return {};
644  }
646  return processParticleCutoffs(storage, params, [omega](Size i) {
647  const Float w = getLength(omega[i]);
648  return 3600._f * 24._f * w / (2._f * PI);
649  });
650  }
652  if (!storage.has(QuantityId::ANGULAR_FREQUENCY)) {
653  return {};
654  }
656  return processParticleCutoffs(storage, params, [omega](Size i) {
657  const Float w = getLength(omega[i]);
658  if (w == 0._f) {
659  return 0._f;
660  } else {
661  return acos(omega[i][Z] / w);
662  }
663  });
664  }
665  default:
666  const QuantityId quantityId = QuantityId(id);
667  SPH_ASSERT((int)quantityId >= 0);
669  Array<Float> values = storage.getValue<Float>(quantityId).clone();
670  return processParticleCutoffs(storage, params, [&values](Size i) { return values[i]; });
671  }
672 }
673 
675 static Array<Size> processComponentCutoffs(const Storage& storage,
676  ArrayView<const Size> components,
677  const Size numComponents,
678  const Post::HistogramParams& params) {
681  Array<Vector> velocities(numComponents);
682  Array<Float> masses(numComponents);
683  velocities.fill(Vector(0._f));
684  masses.fill(0._f);
685 
686  for (Size i = 0; i < m.size(); ++i) {
687  velocities[components[i]] += m[i] * v[i];
688  masses[components[i]] += m[i];
689  }
690 
691  Array<Size> toRemove;
692  for (Size idx = 0; idx < numComponents; ++idx) {
693  SPH_ASSERT(masses[idx] > 0._f);
694  velocities[idx] /= masses[idx];
695 
696  if (masses[idx] < params.massCutoff || getLength(velocities[idx]) > params.velocityCutoff) {
697  toRemove.push(idx);
698  }
699  }
700  return toRemove;
701 }
702 
704 struct MissingQuantityException : public std::exception {
705 private:
706  std::string message;
707 
708 public:
710  message = "Attempting to access missing quantity " + getMetadata(id).quantityName;
711  }
712 
713 
714  virtual const char* what() const noexcept override {
715  return message.c_str();
716  }
717 };
718 
720 static Array<Float> getComponentValues(const Storage& storage,
721  const Post::HistogramParams& params,
722  const Post::HistogramId id) {
723 
724  Array<Size> components;
725  const Size numComponents =
726  findComponents(storage, params.components.radius, params.components.flags, components);
727 
728  Array<Size> toRemove = processComponentCutoffs(storage, components, numComponents, params);
729 
730  switch (id) {
733  // compute volume of the body
736  if (id == Post::HistogramId::RADII) {
737  if (storage.has(QuantityId::DENSITY)) {
738  rho = storage.getValue<Float>(QuantityId::DENSITY);
739  } else {
741  }
742  }
743 
744  Array<Float> values(numComponents);
745  values.fill(0._f);
746  for (Size i = 0; i < m.size(); ++i) {
747  Float density;
749  density = params.referenceDensity;
750  } else {
751  density = rho[i];
752  }
753  SPH_ASSERT(m[i] > 0._f && density > 0._f);
754  values[components[i]] += m[i] / density;
755  }
756 
757  // remove the components we cut off (in reverse to avoid invalidating indices)
758  values.remove(toRemove);
759 
760  // compute equivalent radii from volumes
761  Array<Float> radii(values.size());
762  for (Size i = 0; i < values.size(); ++i) {
763  radii[i] = root<3>(3._f * values[i] / (4._f * PI));
764  SPH_ASSERT(isReal(radii[i]) && radii[i] > 0._f, values[i]);
765  }
766  return radii;
767  }
769  // compute velocity as weighted average
772  Array<Vector> sumV(numComponents);
773  Array<Float> weights(numComponents);
774  sumV.fill(Vector(0._f));
775  weights.fill(0._f);
776  for (Size i = 0; i < m.size(); ++i) {
777  const Size componentIdx = components[i];
778  sumV[componentIdx] += m[i] * v[i];
779  weights[componentIdx] += m[i];
780  }
781 
782  // remove the components we cut off (in reverse to avoid invalidating indices)
783  sumV.remove(toRemove);
784  weights.remove(toRemove);
785 
786  Array<Float> velocities(sumV.size());
787  for (Size i = 0; i < sumV.size(); ++i) {
788  SPH_ASSERT(weights[i] != 0._f);
789  velocities[i] = getLength(sumV[i] / weights[i]);
790  }
791  return velocities;
792  }
793  default:
795  }
796 }
797 
799 static Array<Float> getValues(const Storage& storage,
800  const Post::ExtHistogramId id,
801  const Post::HistogramSource source,
802  const Post::HistogramParams& params) {
803  Array<Float> values;
804  if (source == Post::HistogramSource::PARTICLES) {
805  values = getParticleValues(storage, params, id);
806  SPH_ASSERT(values.size() <= storage.getParticleCnt()); // can be < due to cutoffs
807  } else {
808  values = getComponentValues(storage, params, id);
809  SPH_ASSERT(values.size() <= storage.getParticleCnt());
810  }
811  return values;
812 }
813 
815  const ExtHistogramId id,
816  const HistogramSource source,
817  const Post::HistogramParams& params) {
818 
819  Array<Float> values = getValues(storage, id, source, params);
820  if (values.empty()) {
821  return {}; // no values, trivially empty histogram
822  }
823  std::sort(values.begin(), values.end());
824 
825  Interval range = params.range;
826  if (range.empty()) {
827  for (Size i = 0; i < values.size(); ++i) {
828  range.extend(values[i]);
829  }
830  }
831  SPH_ASSERT(!range.empty());
832 
833  Array<HistPoint> histogram;
834  Size count = 1;
835  Float lastR = INFTY;
836 
837  // iterate in reverse order - from largest radii to smallest ones
838  for (int i = values.size() - 1; i >= 0; --i) {
839  if (values[i] < lastR) {
840  if (range.contains(values[i])) {
841  histogram.push(HistPoint{ values[i], count });
842  }
843  lastR = values[i];
844  }
845  count++;
846  }
847  SPH_ASSERT(histogram.size() > 0);
848 
849  return histogram;
850 }
851 
853  const ExtHistogramId id,
854  const HistogramSource source,
855  const HistogramParams& params) {
856 
857  Array<Float> values = getValues(storage, id, source, params);
858  return getDifferentialHistogram(values, params);
859 }
860 
862  const HistogramParams& params) {
863 
864  Interval range = params.range;
865  if (range.empty()) {
866  for (Size i = 0; i < values.size(); ++i) {
867  range.extend(values[i]);
868  }
869  // extend slightly, so that the min/max value is strictly inside the interval
870  range.extend(range.lower() - EPS * range.size());
871  range.extend(range.upper() + EPS * range.size());
872  }
873  SPH_ASSERT(!range.empty());
874  SPH_ASSERT(isReal(range.lower()) && isReal(range.upper()));
875 
876  Size binCnt = params.binCnt;
877  if (binCnt == 0) {
878  // estimate initial bin count as sqrt of component count
879  binCnt = Size(0.5 * sqrt(Float(values.size())));
880  }
881 
882  Array<Size> sfd(binCnt);
883  sfd.fill(0);
884  // check for case where only one body/particle exists
885  const bool singular = range.size() == 0;
886  for (Size i = 0; i < values.size(); ++i) {
887  // get bin index
888  Size binIdx;
889  if (singular) {
890  binIdx = 0; // just add everything into the first bin to get some reasonable output
891  } else {
892  const Float floatIdx = binCnt * (values[i] - range.lower()) / range.size();
893  if (floatIdx >= 0._f && floatIdx < binCnt) {
894  binIdx = Size(floatIdx);
895  } else {
896  // out of range, skip
897  // this should not happen if the range was determined
898  SPH_ASSERT(!params.range.empty(), floatIdx, binCnt);
899  continue;
900  }
901  }
902  sfd[binIdx]++;
903  }
904  // convert to HistPoints
905  Array<HistPoint> histogram(binCnt);
906  for (Size i = 0; i < binCnt; ++i) {
907  const Float centerIdx = i + int(params.centerBins) * 0.5_f;
908  histogram[i] = { range.lower() + (centerIdx * range.size()) / binCnt, sfd[i] };
909  SPH_ASSERT(isReal(histogram[i].value), sfd[i], range);
910  }
911  return histogram;
912 }
913 
915  SPH_ASSERT(points.size() >= 2);
916  Float x = 0._f, x2 = 0._f;
917  Float y = 0._f, y2 = 0._f;
918  Float xy = 0._f;
919  for (PlotPoint p : points) {
920  x += p.x;
921  x2 += sqr(p.x);
922  y += p.y;
923  y2 += sqr(p.y);
924  xy += p.x * p.y;
925  }
926 
927  const Size n = points.size();
928  const Float denom = n * x2 - sqr(x);
929  SPH_ASSERT(denom > 0._f);
930  const Float b = (y * x2 - x * xy) / denom;
931  const Float a = (n * xy - x * y) / denom;
932  return LinearFunction(a, b);
933 }
934 
936  SPH_ASSERT(points.size() >= 3);
937  Array<Vector> xs(points.size());
938  Array<Float> ys(points.size());
939  for (Size k = 0; k < xs.size(); ++k) {
940  xs[k] = Vector(1._f, points[k].x, sqr(points[k].x));
941  ys[k] = points[k].y;
942  }
944  Vector xTy = Vector(0._f);
945 
946  for (Size k = 0; k < xs.size(); ++k) {
947  for (Size i = 0; i < 3; ++i) {
948  for (Size j = 0; j < 3; ++j) {
949  xTx(i, j) += xs[k][j] * xs[k][i];
950  }
951  xTy[i] += xs[k][i] * ys[k];
952  }
953  }
954  SPH_ASSERT(xTx.determinant() != 0._f);
955 
956  const Vector result = xTx.inverse() * xTy;
957  return QuadraticFunction(result[2], result[1], result[0]);
958 }
959 
Various function for interpretation of the results of a simulation.
INLINE bool isReal(const AntisymmetricTensor &t)
INLINE CopyableArray< T, TAllocator, TCounter > copyable(const Array< T, TAllocator, TCounter > &array)
Definition: Array.h:558
#define SPH_ASSERT(x,...)
Definition: Assert.h:94
#define NOT_IMPLEMENTED
Helper macro marking missing implementation.
Definition: Assert.h:100
NAMESPACE_SPH_BEGIN
Definition: BarnesHut.cpp:13
Object representing a three-dimensional axis-aligned box.
Object finding nearest neighbours by evaluating all partice pairs.
Object for printing quantity values into output.
const float radius
Definition: CurveDialog.cpp:18
INLINE Float maxElement(const T &value)
Returns maximum element, simply the value iself by default.
Definition: Generic.h:29
uint32_t Size
Integral type used to index arrays (by default).
Definition: Globals.h:16
double Float
Precision used withing the code. Use Float instead of float or double where precision is important.
Definition: Globals.h:13
Helper objects allowing to iterate in reverse, iterate over multiple containeres, etc.
SPH kernels.
Logging routines of the run.
Array< Triangle > getSurfaceMesh(IScheduler &scheduler, const Storage &storage, const McConfig &config)
Returns the triangle mesh of the body surface (or surfaces of bodies).
constexpr Float EPS
Definition: MathUtils.h:30
constexpr INLINE T sqr(const T &f) noexcept
Return a squared value.
Definition: MathUtils.h:67
INLINE Float cbrt(const Float f)
Returns a cubed root of a value.
Definition: MathUtils.h:84
INLINE T sqrt(const T f)
Return a squared root of a value.
Definition: MathUtils.h:78
constexpr INLINE Float pow< 3 >(const Float v)
Definition: MathUtils.h:132
constexpr INLINE Float pow(const Float v)
Power for floats.
constexpr Float INFTY
Definition: MathUtils.h:38
INLINE Float root< 3 >(const Float f)
Definition: MathUtils.h:105
INLINE T acos(const T f)
Definition: MathUtils.h:306
constexpr Float PI
Mathematical constants.
Definition: MathUtils.h:361
Domain represented by triangular mesh.
#define UNUSED(x)
Definition: Object.h:37
#define NAMESPACE_SPH_END
Definition: Object.h:12
Saving and loading particle data.
QuantityMetadata getMetadata(const QuantityId key)
Returns the quantity information using quantity ID.
Definition: QuantityIds.cpp:27
QuantityId
Unique IDs of basic quantities of SPH particles.
Definition: QuantityIds.h:19
@ FLAG
ID of original body, used to implement discontinuities between bodies in SPH.
@ MOMENT_OF_INERTIA
Moment of inertia of particles, analogy of particle masses for rotation.
@ POSITION
Positions (velocities, accelerations) of particles, always a vector quantity,.
@ DENSITY
Density, always a scalar quantity.
@ MASS
Paricles masses, always a scalar quantity.
SequentialScheduler SEQUENTIAL
Global instance of the sequential scheduler.
Definition: Scheduler.cpp:43
Interface for executing tasks (potentially) asynchronously.
Box getBoundingBox(const Storage &storage, const Float radius)
Convenience function to get the bounding box of all particles.
Definition: Storage.cpp:832
Container for storing particle quantities and materials.
INLINE SymmetricTensor symmetricOuter(const Vector &v1, const Vector &v2)
SYMMETRIZED outer product of two vectors.
Simple algorithm for finding nearest neighbours using spatial partitioning of particles.
INLINE Float getLength(const Vector &v)
Returns the length of the vector. Enabled only for vectors of floating-point precision.
Definition: Vector.h:579
INLINE Float distance(const Vector &r, const Vector &axis)
Returns the distance of vector from given axis. The axis is assumed to be normalized.
Definition: Vector.h:827
INLINE Float getSqrLength(const Vector &v)
Definition: Vector.h:574
INLINE BasicVector< float > cross(const BasicVector< float > &v1, const BasicVector< float > &v2)
Cross product between two vectors.
Definition: Vector.h:559
BasicVector< Float > Vector
Definition: Vector.h:539
INLINE float dot(const BasicVector< float > &v1, const BasicVector< float > &v2)
Make sure the vector is trivially constructible and destructible, needed for fast initialization of a...
Definition: Vector.h:548
@ H
Definition: Vector.h:25
@ Z
Definition: Vector.h:24
INLINE Float determinant() const
Computes determinant of the matrix.
Definition: AffineMatrix.h:80
static AffineMatrix null()
Definition: AffineMatrix.h:128
AffineMatrix inverse() const
Definition: AffineMatrix.h:86
Object providing safe access to continuous memory of data.
Definition: ArrayView.h:17
INLINE TCounter size() const
Definition: ArrayView.h:101
INLINE Iterator< StorageType > begin()
Definition: ArrayView.h:55
INLINE Iterator< StorageType > end()
Definition: ArrayView.h:63
void resize(const TCounter newSize)
Resizes the array to new size.
Definition: Array.h:215
INLINE Iterator< StorageType > end() noexcept
Definition: Array.h:462
INLINE void push(U &&u)
Adds new element to the end of the array, resizing the array if necessary.
Definition: Array.h:306
void remove(const TCounter idx)
Removes an element with given index from the array.
Definition: Array.h:383
void fill(const T &t)
Sets all elements of the array to given value.
Definition: Array.h:187
void insert(const TCounter position, U &&value)
Inserts a new element to given position in the array.
Definition: Array.h:345
INLINE T pop()
Removes the last element from the array and return its value.
Definition: Array.h:375
INLINE TCounter size() const noexcept
Definition: Array.h:193
INLINE bool empty() const noexcept
Definition: Array.h:201
INLINE Iterator< StorageType > begin() noexcept
Definition: Array.h:450
Helper object defining three-dimensional interval (box).
Definition: Box.h:17
INLINE Vector size() const
Returns box dimensions.
Definition: Box.h:106
Wrapper of an integral value providing functions for reading and modifying individual bits.
Definition: Flags.h:20
constexpr INLINE bool has(const TEnum flag) const
Checks if the object has a given flag.
Definition: Flags.h:77
void build(IScheduler &scheduler, ArrayView< const Vector > points)
Constructs the struct with an array of vectors.
virtual Size findAll(const Size index, const Float radius, Array< NeighbourRecord > &neighbours) const =0
Finds all neighbours within given radius from the point given by index.
Interface that allows unified implementation of sequential and parallelized versions of algorithms.
Definition: Scheduler.h:27
Object representing a 1D interval of real numbers.
Definition: Interval.h:17
INLINE Float lower() const
Returns lower bound of the interval.
Definition: Interval.h:74
INLINE void extend(const Float &value)
Extends the interval to contain given value.
Definition: Interval.h:41
INLINE Float upper() const
Returns upper bound of the interval.
Definition: Interval.h:79
INLINE Float size() const
Returns the size of the interval.
Definition: Interval.h:89
INLINE bool contains(const Float &value) const
Checks whether value is inside the interval.
Definition: Interval.h:55
INLINE bool empty() const
Returns true if the interval is empty (default constructed).
Definition: Interval.h:104
Domain represented by triangular mesh.
Definition: MeshDomain.h:30
virtual Float getVolume() const override
Returns the total volume of the domain.
Definition: MeshDomain.h:55
Wrapper of type value of which may or may not be present.
Definition: Optional.h:23
Permutation, i.e. (discrete) invertible function int->int.
Definition: Order.h:18
void shuffle(TBinaryPredicate &&predicate)
Shuffles the order using a binary predicate.
Definition: Order.h:47
Order getInverted() const
Returns the inverted order.
Definition: Order.h:52
Class representing an ordinary 1D linear function.
Definition: Analysis.h:311
static const Settings & getDefaults()
\brief Returns a reference to object containing default values of all settings.
Container storing all quantities used within the simulations.
Definition: Storage.h:230
Array< TValue > & getDt(const QuantityId key)
Retrieves a quantity derivative from the storage, given its key and value type.
Definition: Storage.cpp:217
Size getParticleCnt() const
Returns the number of particles.
Definition: Storage.cpp:445
bool has(const QuantityId key) const
Checks if the storage contains quantity with given key.
Definition: Storage.cpp:130
Array< TValue > & getValue(const QuantityId key)
Retrieves a quantity values from the storage, given its key and value type.
Definition: Storage.cpp:191
Symmetric tensor of 2nd order.
INLINE SymmetricTensor inverse() const
static INLINE SymmetricTensor identity()
Returns an identity tensor.
static INLINE SymmetricTensor null()
Returns a tensor with all zeros.
Triangle.h.
Definition: Triangle.h:14
2D point and other primitives for 2D geometry
Creating code components based on values from settings.
constexpr Float gravity
Gravitational constant (CODATA 2014)
Definition: Constants.h:29
AutoPtr< ISymmetricFinder > getFinder(const RunSettings &settings)
Definition: Factory.cpp:156
Optional< Elements > computeOrbitalElements(const Float M, const Float mu, const Vector &r, const Vector &v)
Computes the orbital elements, given position and velocity of a body.
Definition: TwoBody.cpp:37
Array< MoonEnum > findMoons(const Storage &storage, const Float radius=1._f, const Float limit=0._f)
Find a potential satellites of the largest body.
Definition: Analysis.cpp:367
Array< HistPoint > getDifferentialHistogram(ArrayView< const Float > values, const HistogramParams &params)
Computes the differential histogram from given values.
Definition: Analysis.cpp:861
Array< Size > findNeighbourCounts(const Storage &storage, const Float particleRadius)
Finds the number of neighbours of each particle.
Definition: Analysis.cpp:22
HistogramSource
Source data used to construct the histogram.
Definition: Analysis.h:217
@ PARTICLES
Radii of individual particles, considering particles as spheres (N-body framework)
LinearFunction getLinearFit(ArrayView< const PlotPoint > points)
Finds a linear fit to a set of points.
Definition: Analysis.cpp:914
QuadraticFunction getQuadraticFit(ArrayView< const PlotPoint > points)
Definition: Analysis.cpp:935
Array< Size > findLargestComponent(const Storage &storage, const Float particleRadius, const Flags< ComponentFlag > flags)
Returns the indices of particles belonging to the largest remnant.
Definition: Analysis.cpp:216
Vector getAngularFrequency(ArrayView< const Float > m, ArrayView< const Vector > r, ArrayView< const Vector > v, const Vector &r0, const Vector &v0, ArrayView< const Size > idxs=nullptr)
Computes the immediate vector of angular frequency of a rigid body.
Definition: Analysis.cpp:514
Size findComponents(const Storage &storage, const Float particleRadius, const Flags< ComponentFlag > flags, Array< Size > &indices)
Finds and marks connected components (a.k.a. separated bodies) in the array of vertices.
Definition: Analysis.cpp:86
Vector getCenterOfMass(ArrayView< const Float > m, ArrayView< const Vector > r, ArrayView< const Size > idxs=nullptr)
Computes the center of mass.
Definition: Analysis.cpp:461
Array< Tumbler > findTumblers(const Storage &storage, const Float limit)
Find all tumbling asteroids.
Definition: Analysis.cpp:441
SymmetricTensor getInertiaTensor(ArrayView< const Float > m, ArrayView< const Vector > r, const Vector &r0, ArrayView< const Size > idxs=nullptr)
Computes the total inertia tensor of particles with respect to given center.
Definition: Analysis.cpp:484
MoonEnum
Potential relationship of the body with a respect to the largest remnant (fragment).
Definition: Analysis.h:96
Array< HistPoint > getCumulativeHistogram(const Storage &storage, const ExtHistogramId id, const HistogramSource source, const HistogramParams &params)
Computes cumulative (integral) histogram of particles in the storage.
Definition: Analysis.cpp:814
HistogramId
Quantity from which the histogram is constructed.
Definition: Analysis.h:191
@ VELOCITIES
Particle velocities.
@ ROTATIONAL_AXIS
Distribution of axis directions, from -pi to pi.
@ ROTATIONAL_FREQUENCY
Rotational frequency in revs/day.
@ RADII
Particle radii or equivalent radii of components.
Float getSphericity(IScheduler &scheduler, const Storage &strorage, const Float resolution=0.05_f)
Computes the sphericity coefficient of a body.
Definition: Analysis.cpp:550
Size findMoonCount(ArrayView< const Float > m, ArrayView< const Vector > r, ArrayView< const Vector > v, const Size i, const Float radius=1._f, const Float limit=0._f)
Find the number of moons of given body.
Definition: Analysis.cpp:415
virtual bool belong(const Size UNUSED(i), const Size UNUSED(j))
Definition: Analysis.cpp:36
Float gridResolution
Absolute size of each produced triangle.
Float surfaceLevel
bool precomputeInside
If true, cached volume is created to allow fast calls of contains.
Definition: MeshDomain.h:22
MissingQuantityException(const QuantityId id)
Definition: Analysis.cpp:709
virtual const char * what() const noexcept override
Definition: Analysis.cpp:714
Point in 2D plot.
Definition: Point.h:16
Base class for all polymorphic objects.
Definition: Object.h:88
Point in the histogram.
Definition: Analysis.h:277
Size count
Number of particles/components.
Definition: Analysis.h:282
Float value
Value of the quantity.
Definition: Analysis.h:279
bool operator==(const HistPoint &other) const
Definition: Analysis.cpp:572
Float radius
Radius of particles in units of their smoothing lengths.
Definition: Analysis.h:263
Flags< Post::ComponentFlag > flags
Determines how the particles are clustered into the components.
Definition: Analysis.h:266
Parameters of the histogram.
Definition: Analysis.h:227
Float velocityCutoff
Cutoff value (upper bound) of particle velocity for inclusion in the histogram.
Definition: Analysis.h:253
Float referenceDensity
Reference density, used when computing particle radii from their masses.
Definition: Analysis.h:240
Function< bool(Size index)> validator
Function used for inclusiong/exclusion of values in the histogram.
Definition: Analysis.h:273
Float massCutoff
Cutoff value (lower bound) of particle mass for inclusion in the histogram.
Definition: Analysis.h:246
Interval range
Range of values from which the histogram is constructed.
Definition: Analysis.h:232
struct Post::HistogramParams::ComponentParams components
Size binCnt
Number of histogram bins.
Definition: Analysis.h:237
std::string quantityName
Full name of the quantity (i.e. 'Density', 'Deviatoric stress', ...)
Definition: QuantityIds.h:242