1 #pragma once
2 
3 /// \file Analysis.h
4 /// \brief Various function for interpretation of the results of a simulation
5 /// \author Pavel Sevecek (sevecek at sirrah.troja.mff.cuni.cz)
6 /// \date 2016-2021
7 
8 #include "objects/finders/NeighborFinder.h"
9 #include "objects/wrappers/Expected.h"
10 #include "objects/wrappers/ExtendedEnum.h"
11 #include "objects/wrappers/Function.h"
12 #include "system/Settings.h"
13 
14 NAMESPACE_SPH_BEGIN
15 
16 class Storage;
17 class Path;
18 class ILogger;
19 struct PlotPoint;
20 enum class QuantityId;
21 namespace Post {
22 enum class HistogramId;
23 }
24 
25 SPH_EXTEND_ENUM(QuantityId, Post::HistogramId);
26 
27 namespace Post {
28 
29 /// \brief Finds the number of neighbors of each particle.
30 ///
31 /// Note that each particle searches neighbors up to the distance given by their smoothing length, so the
32 /// relation "A is a neighbor of B" might not be symmetrical.
33 /// \param storage Storage containing the particles.
34 /// \param particleRadius Size of particles in smoothing lengths.
35 /// \return Number of neighbors of each particle.
36 Array<Size> findNeighborCounts(const Storage& storage, const Float particleRadius);
37 
38 
39 enum class ComponentFlag {
40     /// Specifies that overlapping particles belong into the same component
41     OVERLAP = 0,
42 
43     /// Specifies that particles with different flag belong to different component, even if they overlap.
44     SEPARATE_BY_FLAG = 1 << 0,
45 
46     /// Specifies that the gravitationally bound particles belong to the same component. If used, the
47     /// components are constructed in two steps; first, the initial components are created from overlapping
48     /// particles. Then each pair of components is merged if their relative velocity is less then the escape
49     /// velocity.
50     ESCAPE_VELOCITY = 1 << 1,
51 
52     /// If used, components are sorted by the total mass of the particles, i.e. component with index 0 will
53     /// correspond to the largest remnant, index 1 will be the second largest body, etc.
54     SORT_BY_MASS = 1 << 2,
55 };
56 
57 /// \brief Finds and marks connected components (a.k.a. separated bodies) in the array of vertices.
58 ///
59 /// It is a useful function for finding bodies in simulations where we do not merge particles.
60 /// \param storage Storage containing the particles. Must also contain QuantityId::FLAG if
61 ///                SEPARATE_BY_FLAG option is used.
62 /// \param particleRadius Size of particles in smoothing lengths.
63 /// \param flags Flags specifying connectivity of components, etc; see emum \ref ComponentFlag.
64 /// \param indices[out] Array of indices from 0 to n-1, where n is the number of components. In the array,
65 ///                     i-th index corresponds to component to which i-th particle belongs.
66 /// \return Number of components
67 Size findComponents(const Storage& storage,
68     const Float particleRadius,
69     const Flags<ComponentFlag> flags,
70     Array<Size>& indices);
71 
72 /// \brief Returns the indices of particles belonging to the largest remnant.
73 ///
74 /// The returned indices are sorted.
75 Array<Size> findLargestComponent(const Storage& storage,
76     const Float particleRadius,
77     const Flags<ComponentFlag> flags);
78 
79 
80 struct Tumbler {
81     /// Index of particle (body)
82     Size index;
83 
84     /// Angle between the current angular velocity and the angular momentum
85     Float beta;
86 };
87 
88 /// \brief Find all tumbling asteroids.
89 ///
90 /// Limit specifies the required misalignment angle (in radians) for asteroid to be considered a tumbler.
91 /// Note that tumbling begins to be detectable for the misalignment angle larger than 15 degrees with high
92 /// accuracy data (Henych, 2013).
93 Array<Tumbler> findTumblers(const Storage& storage, const Float limit);
94 
95 /// \brief Potential relationship of the body with a respect to the largest remnant (fragment).
96 enum class MoonEnum {
97     LARGEST_FRAGMENT, ///< This is the largest fragment (or remnant, depending on definition)
98     RUNAWAY,          ///< Body is on hyperbolic trajectory, ejected away from the largest remnant (fragment)
99     MOON,             ///< Body is on elliptical trajectory, it is a potential sattelite
100     IMPACTOR,         ///< Body is on collisional course with the largest remnant (fragment)
101     UNOBSERVABLE,     ///< Body is smaller than the user-defined observational limit
102 };
103 
104 /// \brief Find a potential satellites of the largest body.
105 ///
106 /// The sattelites are mainly determined using the total energy of the bodies; if the energy is negative,
107 /// the body is likely to be a sattelite. Note that the N-body interactions can eject bodies with negative
108 /// energy away from the largest remnant (fragment), and also make a body with positive energy a future
109 /// satellite (by decreasing the energy during close encounter with 3rd body). If the energy of a body is
110 /// negative, a Keplerian ellipse (assuming 2-body problem) is computed and based on this ellipse, the
111 /// body is either marked as moon or as impactor.
112 ///
113 /// The function is mainly intended for post-reaccumulation analysis of the created asteroidal family.
114 /// Satellites determined during reaccumulation can be very uncertain, as explained above. The storage
115 /// must contains positions (with velocities) and masses of the bodies. The bodies do not have to be
116 /// sorted, the largest remnant (or fragment) is defined as the most massive body.
117 ///
118 /// \param storage Storage containing bodies of the asteroidal family
119 /// \param radius Relative radius of bodies, multiplying the radius stored in h-component. Used only
120 ///               in collision detection; value 0 turns it off completely.
121 /// \param limit Relative observational limit, with a respect to the largest remnant (fragment). Bodies
122 ///              with radius smaller than \f${\rm limit} \cdot R_{\rm lr}\f$ are marked as UNOBSERVABLE,
123 ///              regardless their bound status. Must be in interval [0, 1>.
124 /// \return Array of the same size of storage, marking each body in the storage; see MoonEnum.
125 Array<MoonEnum> findMoons(const Storage& storage, const Float radius = 1._f, const Float limit = 0._f);
126 
127 /// \brief Find the number of moons of given body.
128 ///
129 /// \param m Masses of bodies, sorted in descending order.
130 /// \param r Positions and radii of bodies, sorted by mass (in descending order)
131 /// \param v Velocities of bodies, sorted by mass (in descending order)
132 /// \param i Index of the queried body
133 /// \param radius Radius multiplier, may be used to exclude moons with pericenter very close to the body.
134 /// \param limit Limiting mass radio, moons with masses lower than limit*m[i] are excluded.
135 Size findMoonCount(ArrayView<const Float> m,
136     ArrayView<const Vector> r,
137     ArrayView<const Vector> v,
138     const Size i,
139     const Float radius = 1._f,
140     const Float limit = 0._f);
141 
142 /// \brief Computes the center of mass.
143 Vector getCenterOfMass(ArrayView<const Float> m,
144     ArrayView<const Vector> r,
145     ArrayView<const Size> idxs = nullptr);
146 
147 /// \brief Computes the total inertia tensor of particles with respect to given center.
148 SymmetricTensor getInertiaTensor(ArrayView<const Float> m,
149     ArrayView<const Vector> r,
150     const Vector& r0,
151     ArrayView<const Size> idxs = nullptr);
152 
153 /// \brief Computes the total inertia tensor of particle with respect to their center of mass.
154 SymmetricTensor getInertiaTensor(ArrayView<const Float> m,
155     ArrayView<const Vector> r,
156     ArrayView<const Size> idxs = nullptr);
157 
158 /// \brief Computes the immediate vector of angular frequency of a rigid body.
159 Vector getAngularFrequency(ArrayView<const Float> m,
160     ArrayView<const Vector> r,
161     ArrayView<const Vector> v,
162     const Vector& r0,
163     const Vector& v0,
164     ArrayView<const Size> idxs = nullptr);
165 
166 Vector getAngularFrequency(ArrayView<const Float> m,
167     ArrayView<const Vector> r,
168     ArrayView<const Vector> v,
169     ArrayView<const Size> idxs = nullptr);
170 
171 /// \brief Computes the sphericity coefficient of a body.
172 ///
173 /// Sphericity is defined as the ratio of the surface area of a sphere (with the same volume as the given
174 /// body) to the surface area of the body (\cite Wadell_1935). This value is equal to 1 for sphere (and only
175 /// for sphere); the lower the value, the more the body is "aspheric". The sphericity is useful for
176 /// quantification of "roundness"; the ratio of semi-axes, for example, can be easily 1 even for cube,
177 /// definitely not a round body.
178 ///
179 /// Note that the sphericity depends on resolution, small-scale topograpy will always cause the sphericity to
180 /// decrease! When comparing sphericities, it is necessary to use the same resolution for the compared bodies.
181 /// \param scheduler Scheduler potentially used for parallelization.
182 /// \param storage Storage containing particle positions, optionally also masses and densities.
183 /// \param resolution Relative resolution used to compute the sphericity.
184 /// \return Wadell's sphericity value for the body.
185 Float getSphericity(IScheduler& scheduler, const Storage& strorage, const Float resolution = 0.05_f);
186 
187 /// \brief Quantity from which the histogram is constructed
188 ///
189 /// Beside the listed options, any QuantityId can be used, by casting to HistogramId enum. All values of
190 /// HistogramId has to be negative to avoid clashes.
191 enum class HistogramId {
192     /// Particle radii or equivalent radii of components
193     RADII = -1,
194 
195     /// Radii determined from particle masses and given reference density. This generally given more
196     /// 'discretized' SFD, as masses of SPH particles are constant during the run, whereas radii
197     /// (smoothing lenghts) or densities may change.
198     EQUIVALENT_MASS_RADII = -2,
199 
200     /// Particle velocities
201     VELOCITIES = -3,
202 
203     /// Rotational frequency in revs/day
204     ROTATIONAL_FREQUENCY = -4,
205 
206     /// Rotational periods of particles (in hours)
207     /// \todo it should be in code units and then converted to hours on output!
208     ROTATIONAL_PERIOD = -5,
209 
210     /// Distribution of axis directions, from -pi to pi
211     ROTATIONAL_AXIS = -6,
212 };
213 
214 using ExtHistogramId = ExtendedEnum<HistogramId>;
215 
216 /// \brief Source data used to construct the histogram.
217 enum class HistogramSource {
218     /// Equivalent radii of connected chunks of particles (SPH framework)
219     COMPONENTS,
220 
221     /// Radii of individual particles, considering particles as spheres (N-body framework)
222     PARTICLES,
223 };
224 
225 
226 /// \brief Parameters of the histogram
227 struct HistogramParams {
228 
229     /// \brief Range of values from which the histogram is constructed.
230     ///
231     /// Unbounded range means the range is selected based on the source data.
232     Interval range;
233 
234     /// \brief Number of histogram bins.
235     ///
236     /// 0 means the number is selected based on the source data. Used only by differential SFD.
237     Size binCnt = 0;
238 
239     /// \brief Reference density, used when computing particle radii from their masses.
240     Float referenceDensity = 2700._f;
241 
242     /// \brief Cutoff value (lower bound) of particle mass for inclusion in the histogram.
243     ///
244     /// Particles with masses below this value are considered "below observational limit".
245     /// Applicable for both component and particle histogram.
246     Float massCutoff = 0._f;
247 
248     /// \brief Cutoff value (upper bound) of particle velocity for inclusion in the histogram
249     ///
250     /// Particles moving faster than the cutoff are considered as fragments of projectile and excluded from
251     /// histogram, as they are (most probably) not part of any observed family. Applicable for both component
252     /// and particle histogram.
253     Float velocityCutoff = INFTY;
254 
255     /// If true, the bin values of the differential histogram are in the centers of the corresponding
256     /// intervals, otherwise they correspond to the lower bound of the interval.
257     bool centerBins = true;
258 
259     /// \brief Parameters used by histogram of components
260     struct ComponentParams {
261 
262         /// Radius of particles in units of their smoothing lengths.
263         Float radius = 2._f;
264 
265         /// Determines how the particles are clustered into the components.
266         Flags<Post::ComponentFlag> flags = Post::ComponentFlag::OVERLAP;
267 
268     } components;
269 
270     /// \brief Function used for inclusiong/exclusion of values in the histogram.
271     ///
272     /// Works only for particle histograms.
273     Function<bool(Size index)> validator = [](Size UNUSED(index)) { return true; };
274 };
275 
276 /// \brief Point in the histogram
277 struct HistPoint {
278     /// Value of the quantity
279     Float value;
280 
281     /// Number of particles/components
282     Size count;
283 
284     bool operator==(const HistPoint& other) const;
285 };
286 
287 /// \brief Computes the differential histogram from given values.
288 ///
289 /// Note that only bin count and input range are used from the histogram parameters. No cut-offs are applied.
290 Array<HistPoint> getDifferentialHistogram(ArrayView<const Float> values, const HistogramParams& params);
291 
292 /// \brief Computes the differential histogram of particles in the storage.
293 Array<HistPoint> getDifferentialHistogram(const Storage& storage,
294     const ExtHistogramId id,
295     const HistogramSource source,
296     const HistogramParams& params);
297 
298 /// \brief Computes cumulative (integral) histogram of particles in the storage.
299 ///
300 /// \param storage Storage containing particle data.
301 /// \param id Specifies the quantity for which the histogram is constructed.
302 /// \param source Specifies the input bodies, see \ref HistogramSource.
303 /// \param params Parameters of the histogram.
304 Array<HistPoint> getCumulativeHistogram(const Storage& storage,
305     const ExtHistogramId id,
306     const HistogramSource source,
307     const HistogramParams& params);
308 
309 
310 /// \brief Class representing an ordinary 1D linear function
311 class LinearFunction {
312 private:
313     Float a, b;
314 
315 public:
316     /// \brief Creates a new linear function.
317     ///
318     /// \param slope Slope of the function (atan of the angle between the line and the x-axis).
319     /// \param offset Offset in y-direction (value of the function for x=0).
LinearFunction(const Float slope,const Float offset)320     LinearFunction(const Float slope, const Float offset)
321         : a(slope)
322         , b(offset) {}
323 
324     /// \brief Evaluates the linear function for given value.
operator()325     INLINE Float operator()(const Float x) const {
326         return a * x + b;
327     }
328 
329     /// \brief Returns the slope of the function.
slope()330     Float slope() const {
331         return a;
332     }
333 
334     /// \brief Returns the offset in y-direction.
offset()335     Float offset() const {
336         return b;
337     }
338 
339     /// \brief Finds a value of x such that f(x) = y for given y.
340     ///
341     /// Slope of the function must not be zero.
solve(const Float y)342     Float solve(const Float y) const {
343         SPH_ASSERT(a != 0._f);
344         return (y - b) / a;
345     }
346 };
347 
348 /// \brief Finds a linear fit to a set of points.
349 ///
350 /// The set of points must have at least two elements and they must not coincide.
351 /// \return Function representing the linear fit.
352 LinearFunction getLinearFit(ArrayView<const PlotPoint> points);
353 
354 
355 class QuadraticFunction {
356 private:
357     Float a, b, c;
358 
359 public:
360     /// y = a*x^2 + b*x + c
QuadraticFunction(const Float a,const Float b,const Float c)361     QuadraticFunction(const Float a, const Float b, const Float c)
362         : a(a)
363         , b(b)
364         , c(c) {}
365 
operator()366     INLINE Float operator()(const Float x) const {
367         return (a * x + b) * x + c;
368     }
369 
quadratic()370     Float quadratic() const {
371         return a;
372     }
linear()373     Float linear() const {
374         return b;
375     }
constant()376     Float constant() const {
377         return c;
378     }
379 
380     /// \brief Returns solutions of a quadratic equation y = a*x^2 + b*x + c
381     ///
382     /// Returned array contains 0, 1 or 2 values, depending on the number of real solutions of the equation.
383     /// If two solutions exist, the first element of the array is always the smaller solution.
solve(const Float y)384     StaticArray<Float, 2> solve(const Float y) const {
385         SPH_ASSERT(a != 0);
386         const Float disc = sqr(b) - 4._f * a * (c - y);
387         if (disc < 0._f) {
388             return EMPTY_ARRAY;
389         } else if (disc == 0._f) {
390             return { -b / (2._f * a) };
391         } else {
392             const Float sqrtDisc = sqrt(disc);
393             Float x1 = (-b - sqrtDisc) / (2._f * a);
394             Float x2 = (-b + sqrtDisc) / (2._f * a);
395             if (x1 > x2) {
396                 std::swap(x1, x2);
397             }
398             return { x1, x2 };
399         }
400     }
401 };
402 
403 QuadraticFunction getQuadraticFit(ArrayView<const PlotPoint> points);
404 
405 } // namespace Post
406 
407 NAMESPACE_SPH_END
408