SPH
ParticleJobs.cpp
Go to the documentation of this file.
2 #include "io/LogWriter.h"
3 #include "io/Logger.h"
5 #include "post/Analysis.h"
6 #include "post/Compare.h"
7 #include "post/TwoBody.h"
8 #include "quantities/Quantity.h"
9 #include "run/IRun.h"
10 #include "sph/Materials.h"
11 #include "sph/initial/Initial.h"
12 #include "system/Factory.h"
13 #include "system/Settings.impl.h"
14 
16 
17 static void renumberFlags(const Storage& main, Storage& other) {
18  if (!main.has(QuantityId::FLAG) || !other.has(QuantityId::FLAG)) {
19  return;
20  }
21 
22  ArrayView<const Size> flags1 = main.getValue<Size>(QuantityId::FLAG);
23  const Size offset = *std::max_element(flags1.begin(), flags1.end()) + 1;
24 
26  for (Size& f : flags2) {
27  f += offset;
28  }
29 }
30 
31 //-----------------------------------------------------------------------------------------------------------
32 // JoinParticlesJob
33 //-----------------------------------------------------------------------------------------------------------
34 
36  VirtualSettings connector;
37  addGenericCategory(connector, instName);
38 
39  VirtualSettings::Category& cat = connector.addCategory("Merging");
40  cat.connect("Offset [km]", "offset", offset).setUnits(1.e3_f);
41  cat.connect("Add velocity [km/s]", "velocity", velocity).setUnits(1.e3_f);
42  cat.connect("Move to COM", "com", moveToCom)
43  .setTooltip(
44  "If true, the particles are moved so that their center of mass lies at the origin and their "
45  "velocities are modified so that the total momentum is zero.");
46  cat.connect("Make flags unique", "unique_flags", uniqueFlags)
47  .setTooltip(
48  "If true, the particle flags of the second input state are renumbered to avoid overlap with "
49  "flags of the first input. This is necessary to properly separate the input bodies.");
50 
51  return connector;
52 }
53 
54 void JoinParticlesJob::evaluate(const RunSettings& UNUSED(global), IRunCallbacks& callbacks) {
55  SharedPtr<ParticleData> input1 = this->getInput<ParticleData>("particles A");
56  SharedPtr<ParticleData> input2 = this->getInput<ParticleData>("particles B");
57 
58  ArrayView<Vector> r, v, dv;
59  tie(r, v, dv) = input2->storage.getAll<Vector>(QuantityId::POSITION);
60  offset[H] = 0._f; // can contain garbage
61  velocity[H] = 0._f;
62  for (Size i = 0; i < r.size(); ++i) {
63  r[i] += offset;
64  v[i] += velocity;
65  }
66 
67  if (uniqueFlags) {
68  renumberFlags(input1->storage, input2->storage);
69  }
70 
71  input1->storage.merge(std::move(input2->storage));
72 
73  if (moveToCom) {
75  }
76 
77  result = input1;
78  callbacks.onSetUp(result->storage, result->stats);
79 }
80 
81 
82 static JobRegistrar sRegisterParticleJoin(
83  "join",
84  "particle operators",
85  [](const std::string& name) { return makeAuto<JoinParticlesJob>(name); },
86  "Simply adds particles from two inputs into a single particle state. Optionally, positions and "
87  "velocities of particles in the second state may be shifted.");
88 
89 //-----------------------------------------------------------------------------------------------------------
90 // OrbitParticlesJob
91 //-----------------------------------------------------------------------------------------------------------
92 
94  VirtualSettings connector;
95  addGenericCategory(connector, instName);
96 
97  VirtualSettings::Category& cat = connector.addCategory("Ellipse");
98  cat.connect("semi-major axis [km]", "a", a).setUnits(1.e3_f);
99  cat.connect("eccentricity []", "e", e);
100  cat.connect("initial proper anomaly [deg]", "v", v).setUnits(DEG_TO_RAD);
101 
102  return connector;
103 }
104 
106  SharedPtr<ParticleData> input1 = this->getInput<ParticleData>("particles A");
107  SharedPtr<ParticleData> input2 = this->getInput<ParticleData>("particles B");
108 
112 
113  const Float m_tot =
114  std::accumulate(m1.begin(), m1.end(), 0._f) + std::accumulate(m2.begin(), m2.end(), 0._f);
115  const Float n = Kepler::meanMotion(a, m_tot);
116  const Vector dr = Kepler::position(a, e, u);
117  const Vector dv = Kepler::velocity(a, e, u, n);
118 
121 
122  for (Size i = 0; i < r.size(); ++i) {
123  r[i] += dr;
124  v[i] += dv;
125  }
126 
127  renumberFlags(input1->storage, input2->storage);
128  input1->storage.merge(std::move(input2->storage));
129  input2->storage.removeAll();
130 
132 
133  result = input1;
134  callbacks.onSetUp(result->storage, result->stats);
135 }
136 
137 
138 static JobRegistrar sRegisterParticleOrbit(
139  "orbit",
140  "particle operators",
141  [](const std::string& name) { return makeAuto<OrbitParticlesJob>(name); },
142  "Puts two input bodies on an elliptical trajectory around their common center of gravity. The orbit is "
143  "defined by the semi-major axis and the eccentricity and it lies in the z=0 plane.");
144 
145 
146 //-----------------------------------------------------------------------------------------------------------
147 // JoinParticlesJob
148 //-----------------------------------------------------------------------------------------------------------
149 
151  VirtualSettings connector;
152  addGenericCategory(connector, instName);
153 
154  VirtualSettings::Category& defCat = connector.addCategory("Slots");
155  defCat.connect("Number of slots", "slot_cnt", slotCnt);
156 
157  VirtualSettings::Category& cat = connector.addCategory("Merging");
158  cat.connect("Move to COM", "com", moveToCom)
159  .setTooltip(
160  "If true, the particles are moved so that their center of mass lies at the origin and their "
161  "velocities are modified so that the total momentum is zero.");
162  cat.connect("Make flags unique", "unique_flags", uniqueFlags)
163  .setTooltip(
164  "If true, the particle flags of the states are renumbered to avoid overlap with "
165  "flags of other inputs. This is necessary to properly separate the input bodies.");
166 
167  return connector;
168 }
169 
171  SharedPtr<ParticleData> main = this->getInput<ParticleData>("particles 1");
172  for (int i = 1; i < slotCnt; ++i) {
173  SharedPtr<ParticleData> other = this->getInput<ParticleData>("particles " + std::to_string(i + 1));
174  if (uniqueFlags) {
175  renumberFlags(main->storage, other->storage);
176  }
177  main->storage.merge(std::move(other->storage));
178  other->storage.removeAll();
179  }
180 
181  if (moveToCom) {
182  moveToCenterOfMassSystem(main->storage);
183  }
184 
185  result = main;
186  callbacks.onSetUp(result->storage, result->stats);
187 }
188 
189 static JobRegistrar sRegisterParticleMultiJoin(
190  "multi join",
191  "particle operators",
192  [](const std::string& name) { return makeAuto<MultiJoinParticlesJob>(name); },
193  "Joins multiple particle sources into a single states.");
194 
195 
196 //-----------------------------------------------------------------------------------------------------------
197 // TransformParticlesJob
198 //-----------------------------------------------------------------------------------------------------------
199 
201  VirtualSettings connector;
202  addGenericCategory(connector, instName);
203 
204  VirtualSettings::Category& posCat = connector.addCategory("Positions");
205  posCat.connect("Translate [km]", "offset", positions.offset).setUnits(1.e3_f);
206  posCat.connect("Yaw angle [deg]", "yaw", positions.angles[0]).setUnits(DEG_TO_RAD);
207  posCat.connect("Pitch angle [deg]", "pitch", positions.angles[1]).setUnits(DEG_TO_RAD);
208  posCat.connect("Roll angle [deg]", "roll", positions.angles[2]).setUnits(DEG_TO_RAD);
209 
210  VirtualSettings::Category& velCat = connector.addCategory("Velocities");
211  velCat.connect("Add velocity [km/s]", "velocity_offset", velocities.offset).setUnits(1.e3_f);
212  velCat.connect("Add spin [rev/day]", "spin", spin).setUnits(2._f * PI / (3600._f * 24._f));
213  velCat.connect("Multiplier", "velocity_mult", velocities.mult);
214 
215  return connector;
216 }
217 
219  result = this->getInput<ParticleData>("particles");
220 
221  AffineMatrix rotator = AffineMatrix::rotateX(positions.angles[0]) *
222  AffineMatrix::rotateY(positions.angles[1]) *
223  AffineMatrix::rotateZ(positions.angles[2]);
224 
225  AffineMatrix positionTm = rotator;
226  positionTm.translate(positions.offset);
227 
228  // using same TM for positions and velocities is correct for orthogonal matrices
229  AffineMatrix velocityTm = rotator * AffineMatrix::scale(Vector(velocities.mult));
230  velocityTm.translate(velocities.offset);
231 
234 
235  for (Size i = 0; i < r.size(); ++i) {
236  const Float h = r[i][H];
237  r[i] = positionTm * r[i];
238  r[i][H] = h;
239 
240  v[i] = velocityTm * v[i];
241  v[i][H] = 0._f;
242  }
243 
244  if (spin != Vector(0._f)) {
245  const Vector r_com = getCenterOfMass(result->storage);
246  for (Size i = 0; i < r.size(); ++i) {
247  v[i] += cross(spin, r[i] - r_com);
248  }
249  }
250 
251  callbacks.onSetUp(result->storage, result->stats);
252 }
253 
254 
255 static JobRegistrar sRegisterParticleTransform(
256  "transform",
257  "particle operators",
258  [](const std::string& name) { return makeAuto<TransformParticlesJob>(name); },
259  "Modifies positions and velocities of the input particles.");
260 
261 //-----------------------------------------------------------------------------------------------------------
262 // CenterParticlesJob
263 //-----------------------------------------------------------------------------------------------------------
264 
266  VirtualSettings connector;
267  addGenericCategory(connector, instName);
268 
269  VirtualSettings::Category& centerCat = connector.addCategory("Center");
270  centerCat.connect("Move to CoM", "positions", centerPositions);
271  centerCat.connect("Set zero momentum", "velocities", centerVelocities);
272 
273  return connector;
274 }
275 
277  result = this->getInput<ParticleData>("particles");
278  Storage& storage = result->storage;
279 
280  Array<Float> m;
281  if (storage.has(QuantityId::MASS)) {
282  m = storage.getValue<Float>(QuantityId::MASS).clone();
283  } else {
284  m.resize(storage.getParticleCnt());
285  m.fill(1._f);
286  }
287  if (centerPositions) {
289  }
290  if (centerVelocities) {
292  }
293 
294  callbacks.onSetUp(result->storage, result->stats);
295 }
296 
297 
298 static JobRegistrar sRegisterCenterTransform(
299  "center",
300  "particle operators",
301  [](const std::string& name) { return makeAuto<CenterParticlesJob>(name); },
302  "Moves particle positions and/or velocities to center-of-mass frame.");
303 
304 
305 //-----------------------------------------------------------------------------------------------------------
306 // ChangeMaterialJob
307 //-----------------------------------------------------------------------------------------------------------
308 
309 static RegisterEnum<ChangeMaterialSubset> sSubsetType({
310  { ChangeMaterialSubset::ALL, "all", "Change material of all particles." },
312  "material_id",
313  "Change material of particles with specific material ID." },
314  { ChangeMaterialSubset::INSIDE_DOMAIN, "inside_domain", "Change material of particles in given domain." },
315 });
316 
318  VirtualSettings connector;
319  addGenericCategory(connector, instName);
320 
321  VirtualSettings::Category& cat = connector.addCategory("Change material");
322  cat.connect("Subset", "subset", type);
323  cat.connect("Material ID", "mat_id", matId).setEnabler([this] {
325  });
326 
327  return connector;
328 }
329 
331  SharedPtr<ParticleData> input = this->getInput<ParticleData>("particles");
332  SharedPtr<IMaterial> material = this->getInput<IMaterial>("material");
333 
334  switch (ChangeMaterialSubset(type)) {
336  for (Size i = 0; i < input->storage.getMaterialCnt(); ++i) {
337  input->storage.setMaterial(i, material);
338  }
339  break;
341  input->storage.setMaterial(matId, material);
342  break;
344  SharedPtr<IDomain> domain = this->getInput<IDomain>("domain");
346  Array<Size> toChange, toKeep;
347  for (Size i = 0; i < r.size(); ++i) {
348  if (domain->contains(r[i])) {
349  toChange.push(i);
350  } else {
351  toKeep.push(i);
352  }
353  }
354 
355  Storage changed = input->storage.clone(VisitorEnum::ALL_BUFFERS);
358 
359  for (Size i = 0; i < changed.getMaterialCnt(); ++i) {
360  changed.setMaterial(i, material);
361  }
362  input->storage.merge(std::move(changed));
363  break;
364  }
365  }
366 
367  result = input;
368 }
369 
370 
371 static JobRegistrar sRegisterChangeMaterial(
372  "change material",
373  "changer",
374  "particle operators",
375  [](const std::string& name) { return makeAuto<ChangeMaterialJob>(name); },
376  "Changes the material of all or a subset of the input particles.");
377 
378 //-----------------------------------------------------------------------------------------------------------
379 // CollisionGeometrySetup
380 //-----------------------------------------------------------------------------------------------------------
381 
382 // clang-format off
383 template <>
384 AutoPtr<CollisionGeometrySettings> CollisionGeometrySettings::instance(new CollisionGeometrySettings{
385  { CollisionGeometrySettingsId::IMPACTOR_OPTIMIZE, "impactor.optimize", true,
386  "If true, some quantities of the impactor particles are not taken into account when computing the required "
387  "time step. Otherwise, the time step might be unnecessarily too low, as the quantities in the impactor change "
388  "rapidly. Note that this does not affect CFL criterion. It should be always set to false for collisions"
389  "of similar-sized bodies."},
390  { CollisionGeometrySettingsId::IMPACTOR_OFFSET, "impactor.offset", 4._f,
391  "Initial distance of the impactor from the target in units of smoothing length. The impactor should "
392  "not be in contact with the target at the start of the simulation, so the value should be always larger "
393  "than the radius of the selected kernel." },
394  { CollisionGeometrySettingsId::IMPACT_SPEED, "impact.speed", 5.e3_f,
395  "Relative impact speed (or absolute speed of the impactor if center-of-mass system is set to false) "
396  "in meters per second." },
397  { CollisionGeometrySettingsId::IMPACT_ANGLE, "impact.angle", 45._f,
398  "Impact angle, i.e. angle between normal at the point of impact and the velocity vector of the impactor. "
399  "It can be negative to simulate retrograde impact. The angle is in degrees. "},
400  { CollisionGeometrySettingsId::CENTER_OF_MASS_FRAME, "center_of_mass_frame", false,
401  "If true, colliding bodies are moved to the center-of-mass system, otherwise the target is located "
402  "at origin and has zero velocity." },
403 });
404 // clang-format on
405 
407 
411 static Sphere getBoundingSphere(const Storage& storage) {
413  Sphere sphere(Vector(0._f), 0._f);
414  for (Size i = 0; i < r.size(); ++i) {
415  sphere.center() += r[i];
416  }
417  sphere.center() /= r.size();
418 
419  for (Size i = 0; i < r.size(); ++i) {
420  sphere.radius() = max(sphere.radius(), getLength(r[i] - sphere.center()));
421  }
422  return sphere;
423 }
424 
425 static void displace(Storage& storage, const Vector& offset) {
427  Vector fixedOffset = offset;
428  fixedOffset[H] = 0._f;
429  for (Size i = 0; i < r.size(); ++i) {
430  r[i] += fixedOffset;
431  }
432 }
433 
435  const CollisionGeometrySettings& overrides)
436  : IParticleJob(name) {
437  geometry.addEntries(overrides);
438 }
439 
441  VirtualSettings connector;
442  addGenericCategory(connector, instName);
443  VirtualSettings::Category& positionCat = connector.addCategory("Collision geometry");
444  positionCat.connect<Float>("Impact angle [deg]", geometry, CollisionGeometrySettingsId::IMPACT_ANGLE);
445  positionCat.connect<Float>("Impact velocity [km/s]", geometry, CollisionGeometrySettingsId::IMPACT_SPEED)
446  .setUnits(1.e3_f);
447  positionCat.connect<Float>("Impactor offset [h]", geometry, CollisionGeometrySettingsId::IMPACTOR_OFFSET);
448  positionCat.connect<bool>(
449  "Move to CoM frame", geometry, CollisionGeometrySettingsId::CENTER_OF_MASS_FRAME);
450 
451  return connector;
452 }
453 
455  Storage target = std::move(this->getInput<ParticleData>("target")->storage);
456  Storage impactor = std::move(this->getInput<ParticleData>("impactor")->storage);
457  SPH_ASSERT(target.isValid());
458  SPH_ASSERT(impactor.isValid());
459 
460  const Sphere targetSphere = getBoundingSphere(target);
461  const Sphere impactorSphere = getBoundingSphere(impactor);
462 
463  // move target to origin
464  displace(target, -targetSphere.center());
465 
466  // move impactor to impact angle
467  const Float impactorDistance = targetSphere.radius() + impactorSphere.radius();
468 
469  const Float h = target.getValue<Vector>(QuantityId::POSITION)[0][H];
471  SPH_ASSERT(phi >= -PI && phi <= PI, phi);
472 
474  const Float x = impactorDistance * cos(phi) + offset * h;
475  const Float y = impactorDistance * sin(phi);
476  displace(impactor, -impactorSphere.center() + Vector(x, y, 0._f));
477 
478  const Float v_imp = geometry.get<Float>(CollisionGeometrySettingsId::IMPACT_SPEED);
480  for (Size i = 0; i < v.size(); ++i) {
481  v[i][X] -= v_imp;
482  }
483 
484  // renumber flags of impactor to separate the bodies
485  if (target.has(QuantityId::FLAG) && impactor.has(QuantityId::FLAG)) {
486  ArrayView<Size> targetFlags = target.getValue<Size>(QuantityId::FLAG);
487  ArrayView<Size> impactorFlags = impactor.getValue<Size>(QuantityId::FLAG);
488  const Size flagShift = *std::max_element(targetFlags.begin(), targetFlags.end()) + 1;
489  for (Size i = 0; i < impactorFlags.size(); ++i) {
490  impactorFlags[i] += flagShift;
491  }
492  }
493 
494  target.merge(std::move(impactor));
495 
497  moveToCenterOfMassSystem(target);
498  }
499 
500  // merge bodies to single storage
501  result = makeShared<ParticleData>();
502  result->storage = std::move(target);
503 }
504 
505 static JobRegistrar sRegisterCollisionSetup(
506  "collision setup",
507  "setup",
508  "particle operators",
509  [](const std::string& name) { return makeAuto<CollisionGeometrySetup>(name); },
510  "Adds two input particle states (bodies) into a single state, moving the second body (impactor) to a "
511  "position specified by the impact angle and adding an impact velocity to the impactor.");
512 
513 
514 // ----------------------------------------------------------------------------------------------------------
515 // SmoothedToSolidHandoff
516 // ----------------------------------------------------------------------------------------------------------
517 
518 static RegisterEnum<HandoffRadius> sHandoffRadius({
520  "equal_volume",
521  "Assume equal volume for solid spheres; r_solid = m / (4/3 pi rho_sph)^(1/3)." },
523  "smoothing_length",
524  "Use a multiple of the smoothing length; r_solid = multiplier * h." },
525 });
526 
528  VirtualSettings connector;
529  addGenericCategory(connector, instName);
530 
531  VirtualSettings::Category& category = connector.addCategory("Handoff options");
532  category.connect("Radius", "radius", type)
533  .setTooltip("Determines how to compute the radii of the solid spheres. Can be one of:\n" +
534  EnumMap::getDesc<HandoffRadius>());
535  category.connect("Radius multiplier", "radiusMultiplier", radiusMultiplier).setEnabler([this] {
537  });
538 
539  return connector;
540 }
541 
543 
544  // we don't need any material, so just pass some dummy
545  Storage spheres(makeAuto<NullMaterial>(EMPTY_SETTINGS));
546  Storage input = std::move(this->getInput<ParticleData>("particles")->storage);
547 
548  // clone required quantities
549  spheres.insert<Vector>(
553 
554  // radii handoff
558  SPH_ASSERT(r_sphere.size() == rho.size());
559  for (Size i = 0; i < r_sphere.size(); ++i) {
560  switch (HandoffRadius(type)) {
562  r_sphere[i][H] = cbrt(3._f * m[i] / (4._f * PI * rho[i]));
563  break;
565  r_sphere[i][H] = radiusMultiplier * r_sphere[i][H];
566  break;
567  default:
569  }
570  }
571 
572  // remove all sublimated particles
573  Array<Size> toRemove;
575  for (Size matId = 0; matId < input.getMaterialCnt(); ++matId) {
576  MaterialView mat = input.getMaterial(matId);
577  const Float u_max = mat->getParam<Float>(BodySettingsId::TILLOTSON_SUBLIMATION);
578  for (Size i : mat.sequence()) {
579  if (u[i] > u_max) {
580  toRemove.push(i);
581  }
582  }
583  }
584  spheres.remove(toRemove);
585 
586  moveToCenterOfMassSystem(spheres);
587 
588  result = makeShared<ParticleData>();
589  result->storage = std::move(spheres);
590 }
591 
592 static JobRegistrar sRegisterHandoff(
593  "smoothed-to-solid handoff",
594  "handoff",
595  "particle operators",
596  [](const std::string& name) { return makeAuto<SmoothedToSolidHandoff>(name); },
597  "Converts smoothed particles, an output of SPH simulaion, into hard spheres that can be hand off to the "
598  "N-body simulation.");
599 
600 
601 // ----------------------------------------------------------------------------------------------------------
602 // ExtractComponentJob
603 // ----------------------------------------------------------------------------------------------------------
604 
606  VirtualSettings connector;
607  addGenericCategory(connector, instName);
608  VirtualSettings::Category& category = connector.addCategory("Component");
609  category.connect("Component index", "index", componentIdx);
610  category.connect("Connectivity factor", "factor", factor);
611  category.connect("Move to CoM", "center", center);
612  return connector;
613 }
614 
616  Storage storage = std::move(this->getInput<ParticleData>("particles")->storage);
617 
618  // allow using this for storage without masses --> add ad hoc mass if it's missing
619  if (!storage.has(QuantityId::MASS)) {
620  storage.insert<Float>(QuantityId::MASS, OrderEnum::ZERO, 1._f);
621  }
622 
623  Array<Size> components;
624  Post::findComponents(storage, factor, Post::ComponentFlag::SORT_BY_MASS, components);
625 
626  Array<Size> toRemove;
627  for (Size i = 0; i < components.size(); ++i) {
628  if (int(components[i]) != componentIdx) {
629  // not LR
630  toRemove.push(i);
631  }
632  }
633  storage.remove(toRemove, Storage::IndicesFlag::INDICES_SORTED);
634 
635  if (center) {
636  moveToCenterOfMassSystem(storage);
637  }
638 
639  result = makeShared<ParticleData>();
640  result->storage = std::move(storage);
641 }
642 
643 static JobRegistrar sRegisterExtractComponent(
644  "extract component",
645  "extractor",
646  "particle operators",
647  [](const std::string& name) { return makeAuto<ExtractComponentJob>(name); },
648  "Preserves all particles belonging to the largest body in the input particle state (or optionally the "
649  "n-th largest body) and removes all other particles. This modifier is useful to separate the largest "
650  "remnant or the largest fragment in the result of a simulation.");
651 
652 // ----------------------------------------------------------------------------------------------------------
653 // RemoveParticleJob
654 // ----------------------------------------------------------------------------------------------------------
655 
657  VirtualSettings connector;
658  addGenericCategory(connector, instName);
659 
660  VirtualSettings::Category& category = connector.addCategory("Removal");
661  category.connect("Remove damaged", "damaged.use", removeDamaged);
662  category.connect("Damage limit", "damaged.limit", damageLimit).setEnabler([&] { //
663  return removeDamaged;
664  });
665  category.connect("Remove expanded", "expanded.use", removeExpanded);
666  category.connect("Energy limit", "expanded.limit", energyLimit).setEnabler([&] {
667  return removeExpanded;
668  });
669  return connector;
670 }
671 
673  Storage storage = std::move(this->getInput<ParticleData>("particles")->storage);
674  std::set<Size> removeSet;
675  if (removeDamaged) {
677  for (Size i = 0; i < d.size(); ++i) {
678  if (d[i] >= damageLimit) {
679  removeSet.insert(i);
680  }
681  }
682  }
683  if (removeExpanded) {
685  for (Size i = 0; i < u.size(); ++i) {
686  if (u[i] >= energyLimit) {
687  removeSet.insert(i);
688  }
689  }
690  }
691  Array<Size> toRemove(removeSet.size());
692  std::copy(removeSet.begin(), removeSet.end(), toRemove.begin());
693  storage.remove(toRemove, Storage::IndicesFlag::INDICES_SORTED);
694 
695  result = makeShared<ParticleData>();
696  result->storage = std::move(storage);
697 }
698 
699 static JobRegistrar sRemoveParticles(
700  "remove particles",
701  "remover",
702  "particle operators",
703  [](const std::string& name) { return makeAuto<RemoveParticlesJob>(name); },
704  "Removes all particles matching given conditions.");
705 
706 
707 // ----------------------------------------------------------------------------------------------------------
708 // MergeComponentsJob
709 // ----------------------------------------------------------------------------------------------------------
710 
711 static RegisterEnum<ConnectivityEnum> sConnectivity({
712  { ConnectivityEnum::OVERLAP, "overlap", "Overlap" },
713  { ConnectivityEnum::ESCAPE_VELOCITY, "escape velocity", "Escape velocity" },
714 });
715 
717  VirtualSettings connector;
718  addGenericCategory(connector, instName);
719  VirtualSettings::Category& category = connector.addCategory("Component");
720  category.connect("Connectivity factor", "factor", factor);
721  category.connect("Component definition", "definition", connectivity);
722  return connector;
723 }
724 
726  SharedPtr<ParticleData> particles = this->getInput<ParticleData>("particles");
727  Storage& input = particles->storage;
728 
729  // allow using this for storage without masses --> add ad hoc mass if it's missing
730  if (!input.has(QuantityId::MASS)) {
732  }
733 
734  Array<Size> components;
738  }
739  const Size componentCount = Post::findComponents(input, factor, flags, components);
740 
744 
745  Array<Float> mc(componentCount);
746  Array<Vector> rc(componentCount), vc(componentCount);
747  Array<Float> hc(componentCount);
748 
749  mc.fill(0._f);
750  rc.fill(Vector(0._f));
751  vc.fill(Vector(0._f));
752  hc.fill(0._f);
753 
754  for (Size i = 0; i < m.size(); ++i) {
755  const Size ci = components[i];
756  mc[ci] += m[i];
757  rc[ci] += m[i] * r[i];
758  vc[ci] += m[i] * v[i];
759  hc[ci] += pow<3>(r[i][H]);
760  }
761 
762  for (Size ci = 0; ci < componentCount; ++ci) {
763  rc[ci] /= mc[ci];
764  vc[ci] /= mc[ci];
765  rc[ci][H] = cbrt(hc[ci]);
766  vc[ci][H] = 0._f;
767  }
768 
769  Storage output;
770  output.insert<Float>(QuantityId::MASS, OrderEnum::ZERO, std::move(mc));
771  output
773  .getDt<Vector>() = std::move(vc);
774 
775  result = particles;
776  result->storage = std::move(output);
777 }
778 
779 static JobRegistrar sRegisterMergeComponents(
780  "merge components",
781  "merger",
782  "particle operators",
783  [](const std::string& name) { return makeAuto<MergeComponentsJob>(name); },
784  "Merges all overlapping particles into larger spheres, preserving the total mass and volume of "
785  "particles. Other quantities are handled as intensive, i.e. they are computed using weighted average.");
786 
787 // ----------------------------------------------------------------------------------------------------------
788 // ExtractParticlesInDomainJob
789 // ----------------------------------------------------------------------------------------------------------
790 
792  VirtualSettings connector;
793  addGenericCategory(connector, instName);
794  VirtualSettings::Category& category = connector.addCategory("Misc");
795  category.connect("Move to CoM", "center", center);
796  return connector;
797 }
798 
800  IRunCallbacks& UNUSED(callbacks)) {
801  SharedPtr<ParticleData> data = this->getInput<ParticleData>("particles");
802  SharedPtr<IDomain> domain = this->getInput<IDomain>("domain");
803  Storage& storage = data->storage;
804 
806  Array<Size> toRemove;
807  for (Size i = 0; i < r.size(); ++i) {
808  if (!domain->contains(r[i])) {
809  toRemove.push(i);
810  }
811  }
812  storage.remove(toRemove, Storage::IndicesFlag::INDICES_SORTED);
813 
814  if (center) {
815  moveToCenterOfMassSystem(storage);
816  }
817 
818  result = data;
819 }
820 
821 static JobRegistrar sRegisterExtractInDomain(
822  "extract particles in domain",
823  "extractor",
824  "particle operators",
825  [](const std::string& name) { return makeAuto<ExtractParticlesInDomainJob>(name); },
826  "Preserves only particles inside the given shape, particles outside the shape are removed.");
827 
828 // ----------------------------------------------------------------------------------------------------------
829 // EmplaceComponentsAsFlagsJob
830 // ----------------------------------------------------------------------------------------------------------
831 
833  VirtualSettings connector;
834  addGenericCategory(connector, instName);
835  VirtualSettings::Category& category = connector.addCategory("Component");
836  category.connect("Connectivity factor", "factor", factor);
837  return connector;
838 }
839 
841  IRunCallbacks& UNUSED(callbacks)) {
842  Storage fragments = std::move(this->getInput<ParticleData>("fragments")->storage);
843 
844  Array<Size> components;
845  Post::findComponents(fragments, factor, Post::ComponentFlag::SORT_BY_MASS, components);
846 
847  Storage original = std::move(this->getInput<ParticleData>("original")->storage);
848  if (!original.has(QuantityId::FLAG)) {
850  }
851  ArrayView<Size> flags = original.getValue<Size>(QuantityId::FLAG);
852  if (flags.size() != components.size()) {
853  throw InvalidSetup("Inputs have different numbers of particles");
854  }
855 
856  for (Size i = 0; i < flags.size(); ++i) {
857  flags[i] = components[i];
858  }
859 
860  result = makeShared<ParticleData>();
861  result->storage = std::move(original);
862 }
863 
864 static JobRegistrar sRegisterEmplaceComponents(
865  "emplace components",
866  "emplacer",
867  "particle operators",
868  [](const std::string& name) { return makeAuto<EmplaceComponentsAsFlagsJob>(name); },
869  "This modifier detects components (i.e. separated bodies) in the \"fragments\" particle input and stores "
870  "the indices of the components as flags to the other particle input \"original\". This is useful to "
871  "visualize the particles belonging to different fragments in the initial conditions of the simulation.");
872 
873 
874 // ----------------------------------------------------------------------------------------------------------
875 // SubsampleJob
876 // ----------------------------------------------------------------------------------------------------------
877 
879  VirtualSettings connector;
880  addGenericCategory(connector, instName);
881  VirtualSettings::Category& category = connector.addCategory("Subsampling");
882  category.connect("Fraction", "fraction", fraction);
883  return connector;
884 }
885 
886 void SubsampleJob::evaluate(const RunSettings& global, IRunCallbacks& UNUSED(callbacks)) {
887  SharedPtr<ParticleData> input = this->getInput<ParticleData>("particles");
888  AutoPtr<IRng> rng = Factory::getRng(global);
889 
890  std::set<Size> generated;
891  const Size particleCnt = input->storage.getParticleCnt();
892  const Size targetCnt = clamp(Size((1._f - fraction) * particleCnt), 0u, particleCnt - 1);
893  while (generated.size() < targetCnt) {
894  generated.insert(Size(rng() * particleCnt));
895  }
896  Array<Size> toRemove;
897  for (Size i : generated) {
898  toRemove.push(i);
899  }
900 
902 
904  for (Size i = 0; i < m.size(); ++i) {
905  m[i] /= fraction;
906  }
907 
908  result = input;
909 }
910 
911 static JobRegistrar sRegisterSubsampler(
912  "subsampler",
913  "particle operators",
914  [](const std::string& name) { return makeAuto<SubsampleJob>(name); },
915  "Preserves a fraction of randomly selected particles, removes the other particles.");
916 
917 
918 // ----------------------------------------------------------------------------------------------------------
919 // CompareJob
920 // ----------------------------------------------------------------------------------------------------------
921 
922 static RegisterEnum<CompareMode> sCompare({
924  "particle_wise",
925  "States must have the same number of particles. Compares all quantities of particles at "
926  "corresponding indices. Viable for SPH simulations." },
928  "large_particles_only",
929  "Compares only large particles in the states. The number of particles may be different and the "
930  "indices of particles do not have to match. Viable for N-body simulations with merging." },
931 });
932 
933 
935  VirtualSettings connector;
936  addGenericCategory(connector, instName);
937 
938  auto nbodyEnabler = [&] { return CompareMode(mode) == CompareMode::LARGE_PARTICLES_ONLY; };
939 
940  VirtualSettings::Category& compareCat = connector.addCategory("Comparison");
941  compareCat.connect("Compare mode", "compare_mode", mode);
942  compareCat.connect("Tolerance", "eps", eps);
943  compareCat.connect("Fraction", "fraction", fraction).setEnabler(nbodyEnabler);
944  compareCat.connect("Max deviation [km]", "max_deviation", maxDeviation)
945  .setUnits(1.e3_f)
946  .setEnabler(nbodyEnabler);
947 
948  return connector;
949 }
950 
951 void CompareJob::evaluate(const RunSettings& UNUSED(global), IRunCallbacks& UNUSED(callbacks)) {
952  Storage& test = this->getInput<ParticleData>("test particles")->storage;
953  Storage& ref = this->getInput<ParticleData>("reference particles")->storage;
954 
956  switch (CompareMode(mode)) {
958  result = Post::compareParticles(test, ref, eps);
959  break;
961  result = Post::compareLargeSpheres(test, ref, fraction, maxDeviation, eps);
962  break;
963  default:
965  }
966  if (!result) {
967  throw InvalidSetup(result.error());
968  }
969 }
970 
971 static JobRegistrar sRegisterCompare(
972  "compare",
973  "particle operators",
974  [](const std::string& name) { return makeAuto<CompareJob>(name); },
975  "Compares two states. If a difference is found, it is shown as an error dialog.");
976 
977 
Various function for interpretation of the results of a simulation.
#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
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
Basic interface defining a single run.
Vector moveToCenterOfMassSystem(ArrayView< const Float > m, ArrayView< Vector > r)
Modifies particle positions so that their center of mass lies at the origin.
Definition: Initial.cpp:363
Generating initial conditions of SPH particles.
VirtualSettings::Category & addGenericCategory(VirtualSettings &connector, std::string &instanceName)
Adds a common settings category, used by all jobs.
Definition: Job.cpp:43
Logging routines of the run.
SPH-specific implementation of particle materials.
constexpr INLINE T max(const T &f1, const T &f2)
Definition: MathBasic.h:20
constexpr INLINE T clamp(const T &f, const T &f1, const T &f2)
Definition: MathBasic.h:35
INLINE Float cbrt(const Float f)
Returns a cubed root of a value.
Definition: MathUtils.h:84
INLINE T sin(const T f)
Definition: MathUtils.h:296
INLINE T cos(const T f)
Definition: MathUtils.h:291
constexpr INLINE Float pow< 3 >(const Float v)
Definition: MathUtils.h:132
constexpr Float DEG_TO_RAD
Definition: MathUtils.h:369
constexpr Float PI
Mathematical constants.
Definition: MathUtils.h:361
#define UNUSED(x)
Definition: Object.h:37
#define NAMESPACE_SPH_END
Definition: Object.h:12
const SuccessTag SUCCESS
Global constant for successful outcome.
Definition: Outcome.h:141
ConnectivityEnum
Definition: ParticleJobs.h:311
ChangeMaterialSubset
Definition: ParticleJobs.h:140
@ IMPACT_ANGLE
Impact angle in degrees, i.e. angle between velocity vector and normal at the impact point.
@ IMPACT_SPEED
Impact speed in m/s.
HandoffRadius
Determines how to compute the radii of the spheres.
Definition: ParticleJobs.h:228
@ EQUAL_VOLUME
The created sphere has the same volume as the SPH particles (=mass/density)
@ SMOOTHING_LENGTH
The radius is proportional to the smoothing length of the particles.
CompareMode
Definition: ParticleJobs.h:404
@ LARGE_PARTICLES_ONLY
@ FLAG
ID of original body, used to implement discontinuities between bodies in SPH.
@ DAMAGE
Damage.
@ POSITION
Positions (velocities, accelerations) of particles, always a vector quantity,.
@ ENERGY
Specific internal energy, always a scalar quantity.
@ DENSITY
Density, always a scalar quantity.
@ MASS
Paricles masses, always a scalar quantity.
Holder of quantity values and their temporal derivatives.
@ SECOND
Quantity with 1st and 2nd derivative.
@ ZERO
Quantity without derivatives, or "zero order" of quantity.
Object representing a three-dimensional sphere.
StaticArray< T0 &, sizeof...(TArgs)+1 > tie(T0 &t0, TArgs &... rest)
Creates a static array from a list of l-value references.
Definition: StaticArray.h:281
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 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
@ H
Definition: Vector.h:25
@ X
Definition: Vector.h:22
int main(int argc, char *argv[])
Definition: main.cpp:7
static AffineMatrix scale(const Vector &scaling)
Definition: AffineMatrix.h:136
INLINE AffineMatrix & translate(const Vector &t)
Definition: AffineMatrix.h:58
static AffineMatrix rotateY(const Float angle)
Definition: AffineMatrix.h:147
static AffineMatrix rotateX(const Float angle)
Definition: AffineMatrix.h:141
static AffineMatrix rotateZ(const Float angle)
Definition: AffineMatrix.h:153
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 void push(U &&u)
Adds new element to the end of the array, resizing the array if necessary.
Definition: Array.h:306
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 TCounter size() const noexcept
Definition: Array.h:193
INLINE Iterator< StorageType > begin() noexcept
Definition: Array.h:450
Wrapper of pointer that deletes the resource from destructor.
Definition: AutoPtr.h:15
virtual VirtualSettings getSettings() override
Returns a settings object which allows to query and modify the state of the job.
virtual void evaluate(const RunSettings &global, IRunCallbacks &callbacks) override
Runs the operation provided by the job.
virtual void evaluate(const RunSettings &global, IRunCallbacks &callbacks) override
Runs the operation provided by the job.
virtual VirtualSettings getSettings() override
Returns a settings object which allows to query and modify the state of the job.
virtual void evaluate(const RunSettings &global, IRunCallbacks &UNUSED(callbacks)) override
CollisionGeometrySetup(const std::string &name, const CollisionGeometrySettings &overrides=EMPTY_SETTINGS)
virtual VirtualSettings getSettings() override
Returns a settings object which allows to query and modify the state of the job.
virtual VirtualSettings getSettings() override
Returns a settings object which allows to query and modify the state of the job.
virtual void evaluate(const RunSettings &global, IRunCallbacks &UNUSED(callbacks)) override
virtual VirtualSettings getSettings() override
Returns a settings object which allows to query and modify the state of the job.
virtual void evaluate(const RunSettings &global, IRunCallbacks &callbacks) override
Runs the operation provided by the job.
EntryControl & setEnabler(const Enabler &newEnabler)
Adds or replaces the enabler functor of the entry.
EntryControl & setUnits(const Float newMult)
Sets units in which the entry is stored.
EntryControl & setTooltip(const std::string &newTooltip)
Adds or replaces the previous tooltip associanted with the entry.
virtual void evaluate(const RunSettings &global, IRunCallbacks &callbacks) override
Runs the operation provided by the job.
virtual VirtualSettings getSettings() override
Returns a settings object which allows to query and modify the state of the job.
virtual VirtualSettings getSettings() override
Returns a settings object which allows to query and modify the state of the job.
virtual void evaluate(const RunSettings &global, IRunCallbacks &callbacks) override
Runs the operation provided by the job.
virtual bool contains(const Vector &v) const =0
Checks if the given point lies inside the domain.
std::string instName
Definition: Job.h:100
Base class for all jobs providing particle data.
Definition: Job.h:242
SharedPtr< ParticleData > result
Data filled by the job when it finishes.
Definition: Job.h:245
Callbacks executed by the simulation to provide feedback to the user.
Definition: IRun.h:27
virtual void onSetUp(const Storage &storage, Statistics &stats)=0
Called right before the run starts, i.e. after initial conditions are set up.
Thrown when components of the run are mutually incompatible.
Definition: Exceptions.h:24
virtual VirtualSettings getSettings() override
Returns a settings object which allows to query and modify the state of the job.
virtual void evaluate(const RunSettings &global, IRunCallbacks &callbacks) override
Runs the operation provided by the job.
Non-owning wrapper of a material and particles with this material.
Definition: IMaterial.h:30
INLINE IndexSequence sequence()
Returns iterable index sequence.
Definition: IMaterial.h:75
virtual VirtualSettings getSettings() override
Returns a settings object which allows to query and modify the state of the job.
virtual void evaluate(const RunSettings &global, IRunCallbacks &callbacks) override
Runs the operation provided by the job.
virtual void evaluate(const RunSettings &global, IRunCallbacks &callbacks) override
Runs the operation provided by the job.
virtual VirtualSettings getSettings() override
Returns a settings object which allows to query and modify the state of the job.
virtual void evaluate(const RunSettings &global, IRunCallbacks &callbacks) override
Runs the operation provided by the job.
virtual VirtualSettings getSettings() override
Returns a settings object which allows to query and modify the state of the job.
virtual VirtualSettings getSettings() override
Returns a settings object which allows to query and modify the state of the job.
virtual void evaluate(const RunSettings &global, IRunCallbacks &callbacks) override
Runs the operation provided by the job.
TValue get(const TEnum idx, std::enable_if_t<!std::is_enum< std::decay_t< TValue >>::value, int >=0) const
Returns a value of given type from the settings.
Definition: Settings.h:326
void addEntries(const Settings &settings)
Adds entries from different Settings object into this one, overriding current entries.
Definition: Settings.h:297
virtual VirtualSettings getSettings() override
Returns a settings object which allows to query and modify the state of the job.
virtual void evaluate(const RunSettings &global, IRunCallbacks &UNUSED(callbacks)) override
Definition: Sphere.h:18
INLINE Vector center() const
Definition: Sphere.h:37
INLINE Float radius() const
Definition: Sphere.h:45
Container storing all quantities used within the simulations.
Definition: Storage.h:230
Size getMaterialCnt() const
Return the number of materials in the storage.
Definition: Storage.cpp:437
void merge(Storage &&other)
Merges another storage into this object.
Definition: Storage.cpp:472
Outcome isValid(const Flags< ValidFlag > flags=ValidFlag::COMPLETE) const
Checks whether the storage is in valid state.
Definition: Storage.cpp:609
void setMaterial(const Size matIdx, const SharedPtr< IMaterial > &material)
Modifies material with given index.
Definition: Storage.cpp:377
Quantity & insert(const QuantityId key, const OrderEnum order, const TValue &defaultValue)
Creates a quantity in the storage, given its key, value type and order.
Definition: Storage.cpp:270
StaticArray< Array< TValue > &, 3 > getAll(const QuantityId key)
Retrieves quantity buffers from the storage, given its key and value type.
Definition: Storage.cpp:163
Array< TValue > & getDt(const QuantityId key)
Retrieves a quantity derivative from the storage, given its key and value type.
Definition: Storage.cpp:217
void removeAll()
Removes all particles with all quantities (including materials) from the storage.
Definition: Storage.cpp:810
Size getParticleCnt() const
Returns the number of particles.
Definition: Storage.cpp:445
@ INDICES_SORTED
Use if the given array is already sorted (optimization)
void remove(ArrayView< const Size > idxs, const Flags< IndicesFlag > flags=EMPTY_FLAGS)
Removes specified particles from the storage.
Definition: Storage.cpp:756
MaterialView getMaterial(const Size matIdx) const
Returns an object containing a reference to given material.
Definition: Storage.cpp:366
bool has(const QuantityId key) const
Checks if the storage contains quantity with given key.
Definition: Storage.cpp:130
Storage clone(const Flags< VisitorEnum > buffers) const
Clones specified buffers of the storage.
Definition: Storage.cpp:563
Array< TValue > & getValue(const QuantityId key)
Retrieves a quantity values from the storage, given its key and value type.
Definition: Storage.cpp:191
virtual VirtualSettings getSettings() override
Returns a settings object which allows to query and modify the state of the job.
virtual void evaluate(const RunSettings &global, IRunCallbacks &UNUSED(callbacks)) override
virtual void evaluate(const RunSettings &global, IRunCallbacks &callbacks) override
Runs the operation provided by the job.
virtual VirtualSettings getSettings() override
Returns a settings object which allows to query and modify the state of the job.
EntryControl & connect(const std::string &name, const std::string &key, TValue &value)
Connects to given reference.
Holds a map of virtual entries, associated with a unique name.
Category & addCategory(const std::string &name)
Creates a new category of entries.
Creating code components based on values from settings.
const EmptySettingsTag EMPTY_SETTINGS
Definition: Settings.h:32
@ TILLOTSON_SUBLIMATION
Specific sublimation energy.
AutoPtr< IRng > getRng(const RunSettings &settings)
Definition: Factory.cpp:621
Float trueAnomalyToEccentricAnomaly(const Float v, const Float e)
Computes the eccentric anomaly from the true anomaly and the eccentricity.
Definition: TwoBody.cpp:76
Float meanMotion(const Float a, const Float m_total)
Computes the mean motion from the Kepler's 3rd law.
Definition: TwoBody.cpp:90
Vector position(const Float a, const Float e, const Float u)
Computes the position on the elliptic trajectory.
Definition: TwoBody.cpp:82
Vector velocity(const Float a, const Float e, const Float u, const Float n)
Computes the velocity vector on the elliptic trajectory.
Definition: TwoBody.cpp:86
Outcome compareLargeSpheres(const Storage &test, const Storage &ref, const Float fraction, const Float maxDeviation, const Float eps=1.e-6_f)
Compares the positions, velocities and radii of the largest particles in given states.
Definition: Compare.cpp:101
Outcome compareParticles(const Storage &test, const Storage &ref, const Float eps=1.e-6_f)
Compares particle positions and state quantities of two storages.
Definition: Compare.cpp:9
@ OVERLAP
Specifies that overlapping particles belong into the same component.
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
Helper class, allowing to register job into the global list of jobs.
Definition: Job.h:203
Storage storage
Holds all particle positions and other quantities.
Definition: Job.h:35
Statistics stats
Final statistics of the simulation.
Definition: Job.h:38
Helper class for adding individual enums to the enum map.
Definition: EnumMap.h:175