1 #include "Sph.h"
2 #include <iostream>
3 
4 using namespace Sph;
5 
6 /// Custom writer of simulation log
7 class LogWriter : public ILogWriter {
8 private:
9     String runName;
10 
11 public:
LogWriter(const RunSettings & settings)12     LogWriter(const RunSettings& settings)
13         : ILogWriter(makeAuto<StdOutLogger>(), 1._f) {
14         runName = settings.get<String>(RunSettingsId::RUN_NAME);
15     }
16 
write(const Storage & UNUSED (storage),const Statistics & stats)17     virtual void write(const Storage& UNUSED(storage), const Statistics& stats) override {
18         // Timestep number and current run time
19         const int index = stats.get<int>(StatisticsId::INDEX);
20         const Float time = stats.get<Float>(StatisticsId::RUN_TIME);
21 
22         logger->write(runName, " #", index, "  time = ", time);
23     }
24 };
25 
26 // First phase - sets up the colliding bodies and runs the SPH simulation of the fragmentation.
27 class Fragmentation : public IRun {
28 public:
setUp(SharedPtr<Storage> storage)29     virtual void setUp(SharedPtr<Storage> storage) override {
30         settings.set(RunSettingsId::RUN_NAME, "Fragmentation"_s);
31 
32         // Define the parameters of the target
33         const Float targetRadius = 1.e5_f; // R_pb = 100km;
34         BodySettings targetBody;
35         targetBody
36             .set(BodySettingsId::PARTICLE_COUNT, 10000) // N_pb = 10000
37             .set(BodySettingsId::BODY_RADIUS, targetRadius);
38 
39         // Define the parameters of the impactor
40         const Float impactorRadius = 5.e4_f;
41         BodySettings impactorBody;
42         impactorBody
43             .set(BodySettingsId::BODY_RADIUS, impactorRadius) // R_imp = 50km
44             .set(BodySettingsId::PARTICLE_COUNT, 1250);       // N_imp = 1250
45 
46         // Compute position of the impactor from the impact angle
47         const Float impactAngle = 15._f * DEG_TO_RAD;
48         Vector impactorPosition =
49             (targetRadius + impactorRadius) * Vector(Sph::cos(impactAngle), Sph::sin(impactAngle), 0._f);
50         // Move the impactor to the right, so bodies are not in contact at the start of the simulation
51         impactorPosition[X] += 0.2_f * targetRadius;
52 
53         impactorBody.set(BodySettingsId::BODY_CENTER, impactorPosition);
54 
55         InitialConditions ic(settings);
56         ic.addMonolithicBody(*storage, targetBody);
57 
58         // Using BodyView returned from addMonolithicBody, we can add velocity to the impactor
59         BodyView impactor = ic.addMonolithicBody(*storage, impactorBody);
60         impactor.addVelocity(Vector(-1.e3_f, 0._f, 0._f));
61 
62         // Limit time step by CFL criterion
63         settings.set(RunSettingsId::TIMESTEPPING_CRITERION, TimeStepCriterionEnum::COURANT);
64 
65         // Run for 100s.
66         settings.set(RunSettingsId::RUN_END_TIME, 100._f);
67 
68         // Save the initial (pre-impact) configuration
69         BinaryOutput io(Path("start.ssf"));
70         Statistics stats;
71         stats.set(StatisticsId::RUN_TIME, 0._f);
72         io.dump(*storage, stats);
73 
74         // Setup custom logging
75         logWriter = makeAuto<LogWriter>(settings);
76     }
77 
tearDown(const Storage & storage,const Statistics & stats)78     virtual void tearDown(const Storage& storage, const Statistics& stats) override {
79         // Save the result of the fragmentation phase
80         BinaryOutput io(Path("fragmented.ssf"));
81         io.dump(storage, stats);
82     }
83 };
84 
85 // Second phase - uses the results from the SPH simulation as an input for N-body simulation.
86 class Reaccumulation : public IRun {
87 public:
88     // Here we do not create new particles, the storage already contains particles computed by the
89     // fragmentation phase. Instead we convert the SPH particles to solid spheres (hand-off).
setUp(SharedPtr<Storage> storage)90     virtual void setUp(SharedPtr<Storage> storage) override {
91         settings.set(RunSettingsId::RUN_NAME, "Reaccumulation"_s);
92 
93         // Create an 'empty' material with default parameters. Note that we do not have to do that in other
94         // examples, as it is done inside InitialConditions object.
95         AutoPtr<IMaterial> material = makeAuto<NullMaterial>(BodySettings::getDefaults());
96 
97         // Create a new (empty) particle storage with our material.
98         Storage spheres(std::move(material));
99 
100         // Get positions and smoothing lengths of SPH particles;
101         // the smoothing lengths can be accessed using r_sph[i][H].
102         Array<Vector>& r_sph = storage->getValue<Vector>(QuantityId::POSITION);
103 
104         // Get velocities of SPH particles (= derivatives of positions)
105         Array<Vector>& v_sph = storage->getDt<Vector>(QuantityId::POSITION);
106 
107         // Get masses and densities of SPH particles
108         Array<Float>& m_sph = storage->getValue<Float>(QuantityId::MASS);
109         Array<Float>& rho_sph = storage->getValue<Float>(QuantityId::DENSITY);
110 
111 
112         // Insert the positions of spheres into the new particle storage. We also want to initialize
113         // velocities and accelerations of the sphere, hence OrderEnum::SECOND.
114         // Note that Array is non-copyable, so we have to clone the data.
115         spheres.insert<Vector>(QuantityId::POSITION, OrderEnum::SECOND, r_sph.clone());
116 
117         // Copy also the velocities. Since the velocities were already initialized above (to zeros),
118         // we can simply override the array.
119         spheres.getDt<Vector>(QuantityId::POSITION) = v_sph.clone();
120 
121         // Insert the masses. Masses of spheres correspond to masses of SPH particles, so just copy them.
122         // Note that masses are constant during the simulation (there are no mass derivatives), hence
123         // OrderEnum::ZERO.
124         spheres.insert<Float>(QuantityId::MASS, OrderEnum::ZERO, m_sph.clone());
125 
126         // In N-body simulation, we also need the angular frequencies of particles - initialize them to zero.
127         spheres.insert<Vector>(QuantityId::ANGULAR_FREQUENCY, OrderEnum::ZERO, Vector(0._f));
128 
129         // Finally, we need to set the radii of the created spheres. Let's choose the radii so that the
130         // volume of the sphere is the same as the volume of the SPH particles.
131         Array<Vector>& r_nbody = spheres.getValue<Vector>(QuantityId::POSITION);
132         for (Size i = 0; i < r_nbody.size(); ++i) {
133             r_nbody[i][H] = Sph::cbrt(3._f * m_sph[i] / (4._f * PI * rho_sph[i]));
134         }
135 
136         // Once all quantities needed for N-body simulation are set up, replace the particles originally
137         // passed into the function; from this point, we do not need SPH data anymore, so we can deallocate
138         // them.
139         *storage = std::move(spheres);
140 
141 
142         // Here we need to select a non-default solver - instead of SPH solver, use N-body solver
143         settings.set(RunSettingsId::COLLISION_HANDLER, CollisionHandlerEnum::MERGE_OR_BOUNCE)
144             .set(RunSettingsId::COLLISION_OVERLAP, OverlapEnum::PASS_OR_MERGE)
145             .set(RunSettingsId::GRAVITY_KERNEL, GravityKernelEnum::SOLID_SPHERES);
146 
147         solver = makeAuto<HardSphereSolver>(*scheduler, settings);
148 
149         // For manually created solvers, it is necessary to call function create for every material in the
150         // storage. In this case, we only have one "sort" of particles, so we call the function just once for
151         // material with index 0.
152         solver->create(*storage, storage->getMaterial(0));
153 
154         // Use the leaf-frog integrator
155         settings.set(RunSettingsId::TIMESTEPPING_INTEGRATOR, TimesteppingEnum::LEAP_FROG);
156 
157         // Limit the time step by accelerations. By default, the time step is also limited by CFL criterion,
158         // which requires sound speed computed for every particle. Here, we do not store the sound speed, so
159         // the code would report a runtime error if we used the CFL criterion.
160         settings.set(RunSettingsId::TIMESTEPPING_CRITERION, TimeStepCriterionEnum::ACCELERATION)
161             .set(RunSettingsId::TIMESTEPPING_MAX_TIMESTEP, 10._f);
162 
163         // Set output quantities
164         settings.set(RunSettingsId::RUN_OUTPUT_QUANTITIES,
165             OutputQuantityFlag::POSITION | OutputQuantityFlag::VELOCITY | OutputQuantityFlag::MASS |
166                 OutputQuantityFlag::INDEX | OutputQuantityFlag::SMOOTHING_LENGTH);
167 
168         // Run for 6 hours
169         settings.set(RunSettingsId::RUN_END_TIME, 60._f * 60._f * 6._f);
170 
171         // Setup custom logging
172         logWriter = makeAuto<LogWriter>(settings);
173     }
174 
tearDown(const Storage & storage,const Statistics & stats)175     virtual void tearDown(const Storage& storage, const Statistics& stats) override {
176         // Save the result of the reaccumulation phase
177         BinaryOutput io(Path("reaccumulated.ssf"));
178         io.dump(storage, stats);
179     }
180 };
181 
main()182 int main() {
183     try {
184         Storage storage;
185 
186         // Set up and run the fragmentation phase
187         Fragmentation fragmentation;
188         fragmentation.run(storage);
189 
190         // Now storage contains SPH particles, we can pass them as an input to to the reaccumulation phase
191         Reaccumulation reaccumulation;
192         reaccumulation.run(storage);
193 
194     } catch (const Exception& e) {
195         std::cout << "Error during simulation: " << e.what() << std::endl;
196         return -1;
197     }
198     return 0;
199 }
200