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