1 /// Just a cluster of auxiliary tools, used for analysis of the result.
2 ///
3 /// Currently fixed for my machine, sorry :(
4 
5 #include "io/FileManager.h"
6 #include "io/FileSystem.h"
7 #include "io/Logger.h"
8 #include "io/Output.h"
9 #include "objects/Exceptions.h"
10 #include "objects/containers/ArrayRef.h"
11 #include "physics/Functions.h"
12 #include "post/Analysis.h"
13 #include "post/StatisticTests.h"
14 #include "quantities/Quantity.h"
15 #include "quantities/Storage.h"
16 #include "quantities/Utility.h"
17 #include "sph/initial/Initial.h"
18 #include "system/Factory.h"
19 #include "system/Process.h"
20 #include "system/Statistics.h"
21 #include "thread/Pool.h"
22 #include <fstream>
23 #include <iostream>
24 #include <mutex>
25 
26 using namespace Sph;
27 
parsePkdgravOutput(const Path & path)28 static Expected<Storage> parsePkdgravOutput(const Path& path) {
29     Storage storage;
30     Statistics stats;
31     PkdgravInput io;
32     Outcome result = io.load(path, storage, stats);
33     if (!result) {
34         return makeUnexpected<Storage>(result.error());
35     } else {
36         return storage;
37     }
38 }
39 
pkdgravToSfd(const Path & filePath,const Path & sfdPath)40 int pkdgravToSfd(const Path& filePath, const Path& sfdPath) {
41     std::cout << "Processing pkdgrav file ... " << std::endl;
42     Expected<Storage> storage = parsePkdgravOutput(filePath);
43     if (!storage) {
44         std::wcout << "Invalid file: " << storage.error() << std::endl;
45         return 0;
46     }
47     Post::HistogramParams params;
48     Array<Post::HistPoint> sfd = Post::getCumulativeHistogram(
49         storage.value(), Post::HistogramId::EQUIVALENT_MASS_RADII, Post::HistogramSource::PARTICLES, params);
50     FileLogger logRadiiSfd(sfdPath, EMPTY_FLAGS);
51     for (Post::HistPoint& p : sfd) {
52         logRadiiSfd.write(p.value, "  ", p.count);
53     }
54     return 0;
55 }
56 
pkdgravToOmega(const Path & filePath,const Path & omegaPath)57 int pkdgravToOmega(const Path& filePath, const Path& omegaPath) {
58     std::cout << "Processing pkdgrav file ... " << std::endl;
59     Expected<Storage> storage = parsePkdgravOutput(filePath);
60     if (!storage) {
61         std::wcout << "Invalid file: " << storage.error() << std::endl;
62         return 0;
63     }
64     /*Post::HistogramParams params;
65     params.source = Post::HistogramParams::Source::PARTICLES;
66     params.id = Post::HistogramId::ANGULAR_VELOCITIES;
67     params.binCnt = 50;
68     params.validator = [](const Float value) { return value > 0._f; };*/
69 
70     // Array<Post::SfdPoint> sfd = Post::getDifferentialSfd(storage.value(), params);
71     FileLogger logOmegaSfd(omegaPath, EMPTY_FLAGS);
72     /*for (Post::SfdPoint& p : sfd) {
73         logOmegaSfd.write(p.value, "  ", p.count);
74     }*/
75     ArrayView<Vector> omega = storage->getValue<Vector>(QuantityId::ANGULAR_FREQUENCY);
76     std::sort(
77         omega.begin(), omega.end(), [](Vector& v1, Vector& v2) { return getLength(v1) > getLength(v2); });
78     for (Vector v : omega) {
79         if (getLength(v) != 0._f) {
80             logOmegaSfd.write(getLength(v));
81         }
82     }
83     return 0;
84 }
85 
pkdgravToMoons(const Path & filePath,const Float limit)86 int pkdgravToMoons(const Path& filePath, const Float limit) {
87     std::cout << "Processing pkdgrav file ... " << std::endl;
88     Expected<Storage> storage = parsePkdgravOutput(filePath);
89     if (!storage) {
90         std::cout << "Invalid file: " << storage.error() << std::endl;
91         return 0;
92     }
93     /// \todo use correct radius here, we assume that very close ecounters will eventually collide
94     Array<Post::MoonEnum> moons = Post::findMoons(storage.value(), 1.2_f, limit);
95     Size moonCnt = std::count(moons.begin(), moons.end(), Post::MoonEnum::MOON);
96     std::cout << "Moon count = " << moonCnt << std::endl;
97     return 0;
98 }
99 
ssfToSfd(const Post::HistogramSource source,const Path & filePath,const Path & sfdPath)100 int ssfToSfd(const Post::HistogramSource source, const Path& filePath, const Path& sfdPath) {
101     std::cout << "Processing SPH file ... " << std::endl;
102     AutoPtr<IInput> input = Factory::getInput(filePath);
103     Storage storage;
104     Statistics stats;
105     Outcome outcome = input->load(filePath, storage, stats);
106     if (!outcome) {
107         std::cout << "Cannot load particle data, " << outcome.error() << std::endl;
108         return 0;
109     }
110 
111     Post::HistogramParams params;
112     Array<Post::HistPoint> sfd =
113         Post::getCumulativeHistogram(storage, Post::HistogramId::EQUIVALENT_MASS_RADII, source, params);
114     FileLogger logSfd(sfdPath, EMPTY_FLAGS);
115     for (Post::HistPoint& p : sfd) {
116         logSfd.write(p.value, "  ", p.count);
117     }
118     return 0;
119 }
120 
ssfToOmega(const Path & filePath,const Path & omegaPath,const Path & omegaDPath,const Path & omegaDirPath)121 int ssfToOmega(const Path& filePath,
122     const Path& omegaPath,
123     const Path& omegaDPath,
124     const Path& omegaDirPath) {
125     std::cout << "Processing SPH file ... " << std::endl;
126     BinaryInput input;
127     Storage storage;
128     Statistics stats;
129     Outcome outcome = input.load(filePath, storage, stats);
130     if (!outcome) {
131         std::cout << "Cannot load particle data, " << outcome.error() << std::endl;
132         return 0;
133     }
134 
135     Post::HistogramParams params;
136     params.range = Interval(0._f, 13._f);
137     params.binCnt = 12;
138 
139     ArrayView<const Vector> w = storage.getValue<Vector>(QuantityId::ANGULAR_FREQUENCY);
140     ArrayView<const Float> m = storage.getValue<Float>(QuantityId::MASS);
141     // const Float massCutoff = 1._f / 300000._f;
142     const Float m_total = std::accumulate(m.begin(), m.end(), 0._f);
143     params.validator = [w](const Size i) { //
144         return getSqrLength(w[i]) > 0._f;  //= m_total * massCutoff;
145     };
146 
147     params.centerBins = false;
148 
149     Array<Post::HistPoint> sfd = Post::getDifferentialHistogram(
150         storage, Post::HistogramId::ROTATIONAL_FREQUENCY, Post::HistogramSource::PARTICLES, params);
151 
152     FileLogger logOmega(omegaPath, EMPTY_FLAGS);
153     for (Post::HistPoint& p : sfd) {
154         logOmega.write(p.value, "  ", p.count); // / sum);
155     }
156 
157     params.range = Interval();
158     Array<Post::HistPoint> dirs = Post::getDifferentialHistogram(
159         storage, Post::HistogramId::ROTATIONAL_AXIS, Post::HistogramSource::PARTICLES, params);
160 
161     FileLogger logOmegaDir(omegaDirPath, EMPTY_FLAGS);
162     for (Post::HistPoint& p : dirs) {
163         logOmegaDir.write(p.value, "  ", p.count);
164     }
165 
166     // print omega-D relation
167     Array<Float> h(storage.getParticleCnt());
168     /// ArrayView<const Float> rho = storage.getValue<Float>(QuantityId::DENSITY);
169     ArrayView<const Vector> r = storage.getValue<Vector>(QuantityId::POSITION);
170     for (Size i = 0; i < m.size(); ++i) {
171         h[i] = r[i][H]; // root<3>(3.f * m[i] / (rho[i] * 4.f * PI));
172     }
173 
174 
175     FileLogger logOmegaD(omegaDPath, EMPTY_FLAGS);
176     for (Size i = 0; i < m.size(); ++i) {
177         if (m[i] > 3._f * params.massCutoff * m_total) {
178             // in m vs. rev/day
179             logOmegaD.write(2._f * h[i], "  ", getLength(w[i]) * 3600._f * 24._f / (2._f * PI));
180         }
181     }
182 
183     return 0;
184 }
185 
186 
ssfToVelocity(const Path & filePath,const Path & outPath)187 int ssfToVelocity(const Path& filePath, const Path& outPath) {
188     std::cout << "Processing SPH file ... " << std::endl;
189     AutoPtr<IInput> input = Factory::getInput(filePath);
190     Storage storage;
191     Statistics stats;
192     Outcome outcome = input->load(filePath, storage, stats);
193     if (!outcome) {
194         std::cout << "Cannot load particle data, " << outcome.error() << std::endl;
195         return -1;
196     }
197 
198     // convert to system with center at LR
199     Array<Size> idxs = Post::findLargestComponent(storage, 2._f, EMPTY_FLAGS);
200     ArrayView<const Float> m = storage.getValue<Float>(QuantityId::MASS);
201     ArrayView<Vector> r, v, dv;
202     tie(r, v, dv) = storage.getAll<Vector>(QuantityId::POSITION);
203     Vector r0(0._f);
204     Vector v0(0._f);
205     Float m0 = 0._f;
206     for (Size i : idxs) {
207         m0 += m[i];
208         r0 += m[i] * r[i];
209         v0 += m[i] * v[i];
210     }
211     r0 /= m0;
212     v0 /= m0;
213 
214     for (Size i = 0; i < r.size(); ++i) {
215         r[i] -= r0;
216         v[i] -= v0;
217     }
218 
219     Post::HistogramParams params;
220     params.binCnt = 2000;
221     Array<Post::HistPoint> hist = Post::getDifferentialHistogram(
222         storage, Post::HistogramId::VELOCITIES, Post::HistogramSource::COMPONENTS, params);
223 
224     FileLogger logSfd(outPath, EMPTY_FLAGS);
225     for (Post::HistPoint& p : hist) {
226         logSfd.write(p.value, "  ", p.count);
227     }
228 
229     return 0;
230 }
231 
ssfToVelDir(const Path & filePath,const Path & outPath)232 void ssfToVelDir(const Path& filePath, const Path& outPath) {
233     std::cout << "Processing SPH file ... " << std::endl;
234     BinaryInput input;
235     Storage storage;
236     Statistics stats;
237     Outcome outcome = input.load(filePath, storage, stats);
238     if (!outcome) {
239         std::cout << "Cannot load particle data, " << outcome.error() << std::endl;
240         return;
241     }
242 
243     // convert to system with center at LR
244     Array<Size> idxs = Post::findLargestComponent(storage, 2._f, EMPTY_FLAGS);
245     ArrayView<const Float> m = storage.getValue<Float>(QuantityId::MASS);
246     ArrayView<Vector> r, v, dv;
247     tie(r, v, dv) = storage.getAll<Vector>(QuantityId::POSITION);
248     Vector r0(0._f);
249     Vector v0(0._f);
250     Float m0 = 0._f;
251     for (Size i : idxs) {
252         m0 += m[i];
253         r0 += m[i] * r[i];
254         v0 += m[i] * v[i];
255     }
256     r0 /= m0;
257     v0 /= m0;
258 
259     for (Size i = 0; i < r.size(); ++i) {
260         r[i] -= r0;
261         v[i] -= v0;
262     }
263 
264     // compute velocity directions (in x-y plane)
265     Array<Float> dirs;
266     for (Size i = 0; i < v.size(); ++i) {
267         Float dir = Sph::atan2(v[i][Y], v[i][X]);
268         if (dir < 0._f) {
269             dir += 2._f * PI;
270         }
271         dirs.push(dir * RAD_TO_DEG);
272     }
273 
274     Post::HistogramParams params;
275     params.range = Interval(0._f, 360._f);
276     params.binCnt = 72; // 5 deg bins
277     Array<Post::HistPoint> hist = Post::getDifferentialHistogram(dirs, params);
278     FileLogger logSfd(outPath, EMPTY_FLAGS);
279     for (Post::HistPoint& p : hist) {
280         logSfd.write(p.value, "  ", p.count);
281     }
282 }
283 
284 struct HarrisAsteroid {
285     Optional<Size> number;
286     String name;
287     Optional<Float> radius;
288     Optional<Float> period;
289 };
290 
loadHarris(std::wifstream & ifs)291 static Array<HarrisAsteroid> loadHarris(std::wifstream& ifs) {
292     Array<HarrisAsteroid> harris;
293     while (ifs) {
294         String dummy;
295         String number;
296         ifs >> number >> dummy;
297 
298         String name;
299         ifs >> name;
300         for (int i = 0; i < 6; ++i) {
301             ifs >> dummy;
302         }
303 
304         String radius;
305         ifs >> radius;
306         for (int i = 0; i < 5; ++i) {
307             ifs >> dummy;
308         }
309 
310         String period;
311         ifs >> period;
312         for (int i = 0; i < 10; ++i) {
313             ifs >> dummy;
314         }
315 
316         harris.push(HarrisAsteroid{
317             fromString<Size>(number),
318             name,
319             fromString<Float>(radius),
320             fromString<Float>(period),
321         });
322     }
323     return harris;
324 }
325 
326 struct FamilyAsteroid {
327     Optional<Size> number;
328     Optional<String> name;
329 };
330 
loadFamilies(std::wifstream & ifs)331 static Array<FamilyAsteroid> loadFamilies(std::wifstream& ifs) {
332     SPH_ASSERT(ifs);
333     Array<FamilyAsteroid> asteroids;
334     std::wstring line;
335     bool firstLine = true;
336     int format = 1;
337     while (std::getline(ifs, line)) {
338         if (line.empty() || line[0] == '#') {
339             // comment line
340             if (firstLine) {
341                 // this is the other format of the file, with asteroid names, etc.
342                 format = 2;
343             }
344             firstLine = false;
345             continue;
346         }
347         firstLine = false;
348         std::wstringstream ss(line);
349         // both formats start with asteroid number
350         String number;
351         ss >> number;
352         if (format == 2) {
353             String name;
354             ss >> name;
355             asteroids.push(FamilyAsteroid{ fromString<Size>(number), name });
356         } else {
357             asteroids.push(FamilyAsteroid{ fromString<Size>(number), NOTHING });
358         }
359         // check that we have at least one information
360         SPH_ASSERT(asteroids.back().name || asteroids.back().number);
361     }
362     return asteroids;
363 }
364 
findInHarris(const FamilyAsteroid & ast1,ArrayView<const HarrisAsteroid> catalog)365 static Optional<HarrisAsteroid> findInHarris(const FamilyAsteroid& ast1,
366     ArrayView<const HarrisAsteroid> catalog) {
367     auto iter = std::find_if(catalog.begin(), catalog.end(), [&ast1](const HarrisAsteroid& ast2) { //
368         // search primarily by number
369         if (ast1.number && ast2.number && ast1.number.value() == ast2.number.value()) {
370             return true;
371         }
372         // if we don't have the number, search by name
373         if (ast1.name && ast1.name.value() == ast2.name) {
374             return true;
375         }
376         // either don't match or we don't have the information
377         return false;
378     });
379     if (iter == catalog.end()) {
380         return NOTHING;
381     } else {
382         if (iter->period && iter->radius) {
383             return *iter;
384         } else {
385             return NOTHING;
386         }
387     }
388 }
389 
printDvsOmega(const Path & familyData,const Path & outputPath,ArrayView<const HarrisAsteroid> catalog,Array<PlotPoint> & outPoints)390 static Optional<Post::KsResult> printDvsOmega(const Path& familyData,
391     const Path& outputPath,
392     ArrayView<const HarrisAsteroid> catalog,
393     Array<PlotPoint>& outPoints) {
394 
395     std::wifstream ifs(familyData.native());
396     SPH_ASSERT(ifs);
397     Array<FamilyAsteroid> family = loadFamilies(ifs);
398     Array<HarrisAsteroid> found;
399     Interval rangePeriod;
400     Interval rangeR;
401     for (FamilyAsteroid& famAst : family) {
402         if (auto harAst = findInHarris(famAst, catalog)) {
403             found.push(harAst.value());
404             rangePeriod.extend(harAst->period.value());
405             rangeR.extend(harAst->radius.value());
406         }
407     }
408     if (found.size() < 10) {
409         // too few data to do any statistics
410         return NOTHING;
411     }
412 
413     FileSystem::createDirectory(outputPath.parentPath());
414     std::wofstream ofs(outputPath.native());
415     SPH_ASSERT(ofs);
416 
417     auto compare = [](HarrisAsteroid& ast1, HarrisAsteroid& ast2) {
418         return ast1.radius.value() < ast2.radius.value();
419     };
420     Iterator<HarrisAsteroid> largestRemnant = std::max_element(found.begin(), found.end(), compare);
421 
422     auto periodToOmega = [](const Float P) { return 1._f / (P / 24._f); };
423 
424     Array<PlotPoint> points;
425     for (HarrisAsteroid& ast : found) {
426         const Float omega = periodToOmega(ast.period.value());
427         if (&ast != &*largestRemnant) {
428             String printedName = ast.name;
429             if (ast.number) {
430                 printedName = "(" + toString(ast.number.value()) + ") " + printedName;
431             }
432             ofs << ast.radius.value() << "  " << omega << "   " << printedName << std::endl;
433         }
434         points.push(PlotPoint(ast.radius.value(), omega));
435     }
436     Interval rangeOmega;
437     rangeOmega.extend(periodToOmega(rangePeriod.lower()));
438     rangeOmega.extend(periodToOmega(rangePeriod.upper()));
439 
440     Post::KsFunction uniformCdf = Post::getUniformKsFunction(rangeR, rangeOmega);
441     Post::KsResult result = Post::kolmogorovSmirnovTest(points, uniformCdf);
442 
443 
444     if (points.size() > 36) {
445         const Path histPath = Path("histogram") / outputPath.fileName();
446         FileSystem::createDirectory(histPath.parentPath());
447         std::wofstream histofs(histPath.native());
448         Array<Float> values;
449         for (PlotPoint& p : points) {
450             values.push(p.y);
451         }
452         Post::HistogramParams params;
453         params.range = Interval(0._f, 13._f);
454         Array<Post::HistPoint> histogram = Post::getDifferentialHistogram(values, params);
455         for (Post::HistPoint p : histogram) {
456             histofs << p.value << "  " << p.count << std::endl;
457         }
458     }
459 
460     outPoints = std::move(points);
461     return result;
462 }
463 
elliptize(const String & s,const Size maxSize)464 static String elliptize(const String& s, const Size maxSize) {
465     SPH_ASSERT(maxSize >= 5);
466     if (s.size() < maxSize) {
467         return s;
468     }
469     const Size cutSize = (maxSize - 3) / 2;
470     return s.substr(0, cutSize) + "..." + s.substr(s.size() - cutSize);
471 }
472 
processHarrisFile()473 void processHarrisFile() {
474     Path harrisPath("/home/pavel/projects/astro/asteroids/grant3/harris.out");
475     std::wifstream ifs(harrisPath.native());
476     Array<HarrisAsteroid> harris = loadHarris(ifs);
477     ifs.close();
478 
479     Size below3 = 0, below7 = 0, below12 = 0, total = 0;
480     Float avgPeriod = 0._f;
481     Size count = 0;
482     for (auto& h : harris) {
483         if (!h.period) {
484             continue;
485         }
486 
487         if (h.period.value() < 3) {
488             below3++;
489         }
490         if (h.period.value() < 7) {
491             below7++;
492         }
493         if (h.period.value() < 12) {
494             below12++;
495         }
496 
497         if (h.radius.valueOr(0._f) > 100._f) {
498             avgPeriod += h.period.value();
499             count++;
500         }
501         total++;
502     }
503     std::cout << "Below 3h: " << (100.f * below3) / total << "%" << std::endl;
504     std::cout << "Below 7h: " << (100.f * below7) / total << "%" << std::endl;
505     std::cout << "Below 12h: " << (100.f * below12) / total << "%" << std::endl;
506 
507     if (count > 0) {
508         std::cout << "Average period for R>100km: " << avgPeriod / count << std::endl;
509     } else {
510         SPH_ASSERT(false);
511     }
512 
513     Array<PlotPoint> points;
514     printDvsOmega(
515         Path("/home/pavel/projects/astro/asteroids/families.txt"), Path("LR_D_omega.txt"), harris, points);
516 
517     const Path parentPath("/home/pavel/projects/astro/asteroids/families");
518     Array<Path> paths = FileSystem::getFilesInDirectory(parentPath);
519     std::mutex printMutex;
520     std::mutex plotMutex;
521     UniquePathManager uniquePaths;
522     std::ofstream kss("KS.txt");
523     kss << "# name                             D_ks           probability" << std::endl;
524 
525     std::ofstream alls("D_omega_all.txt");
526 
527     ThreadPool& pool = *ThreadPool::getGlobalInstance();
528     parallelFor(pool, 0, paths.size(), [&](const Size index) {
529         {
530             std::unique_lock<std::mutex> lock(printMutex);
531             std::wcout << paths[index].string() << std::endl;
532         }
533         const Path name = paths[index].fileName();
534         const Path targetPath = Path("D_omega") / Path(name.string() + ".txt");
535         const Path ksPath = Path("KS") / Path(name.string() + ".txt");
536         Array<PlotPoint> points;
537         Optional<Post::KsResult> ks = printDvsOmega(parentPath / paths[index], targetPath, harris, points);
538 
539         std::unique_lock<std::mutex> lock(plotMutex);
540         if (!FileSystem::pathExists(targetPath) || FileSystem::fileSize(targetPath) == 0) {
541             return;
542         }
543         if (ks) {
544             kss << std::left << std::setw(35) << elliptize(Path(name).removeExtension().string(), 30)
545                 << std::setw(15) << ks->D << std::setw(15) << ks->prob << std::endl;
546         }
547         for (PlotPoint p : points) {
548             alls << p.x << "  " << p.y << "  " << index << std::endl;
549         }
550 
551         FileSystem::copyFile(targetPath, Path("family.txt"));
552         // make plot
553         Process gnuplot(Path("/bin/gnuplot"), { "doplot.plt" });
554         gnuplot.wait();
555         SPH_ASSERT(FileSystem::pathExists(Path("plot.png")));
556         FileSystem::copyFile(Path("plot.png"), uniquePaths.getPath(Path(targetPath).replaceExtension("png")));
557 
558         if (points.size() > 25) {
559             const Path histPath = Path("histogram") / targetPath.fileName();
560             FileSystem::copyFile(histPath, Path("hist.txt"));
561             Process gnuplot2(Path("/bin/gnuplot"), { "dohistogram.plt" });
562             gnuplot2.wait();
563             SPH_ASSERT(FileSystem::pathExists(Path("hist.png")));
564             FileSystem::copyFile(
565                 Path("hist.png"), uniquePaths.getPath(Path(histPath).replaceExtension("png")));
566         }
567     });
568 
569     FileSystem::copyFile(Path("D_omega_all.txt"), Path("family.txt"));
570     Process gnuplot(Path("/bin/gnuplot"), { "doplot_all.plt" });
571     gnuplot.wait();
572 }
573 
maxwellBoltzmann(const Float x,const Float a)574 static Float maxwellBoltzmann(const Float x, const Float a) {
575     return sqrt(2._f / PI) * sqr(x) * exp(-sqr(x) / (2._f * sqr(a))) / pow<3>(a);
576 }
577 
sampleMaxwellBoltzmann(UniformRng & rng,const Float a)578 static Float sampleMaxwellBoltzmann(UniformRng& rng, const Float a) {
579     while (true) {
580         const Float x = rng() * a * 10._f;
581         const Float y = rng() / a;
582 
583         if (maxwellBoltzmann(x, a) > y) {
584             return x;
585         }
586     }
587 }
588 
makeSwift(const Path & filePath)589 void makeSwift(const Path& filePath) {
590     // for Hygiea
591     /*const Float a = 3.14178_f;
592     const Float e = 0.135631_f;
593     const Float I = asin(0.0889622);
594     const Float W = 64.621768_f * DEG_TO_RAD;
595     const Float w = 128.543611_f * DEG_TO_RAD;
596     const Float u = 0._f;
597 
598     const Float X = (cos(W) * cos(w) - sin(W) * cos(I) * sin(w)) * a * (cos(u) - e) -
599                     (cos(W) * sin(w) + sin(W) * cos(I) * cos(w)) * a * sqrt(1 - sqr(e)) * sin(u);
600     const Float Y = (sin(W) * cos(w) + cos(W) * cos(I) * sin(w)) * a * (cos(u) - e) +
601                     (-sin(W) * sin(w) + cos(W) * cos(I) * cos(w)) * a * sqrt(1 - sqr(e)) * sin(u);
602     const Float Z = sin(I) * sin(w) * a * (cos(u) - e) + sin(I) * cos(w) * a * sqrt(1 - sqr(e)) * sin(u);
603 
604     const Vector r = Vector(X, Y, Z);
605 
606     const Float npart = 1500;
607 
608     BinaryInput input;
609     Storage storage;
610     Statistics stats;
611     if (!input.load(filePath, storage, stats)) {
612         std::cout << "Cannot parse ssf file" << std::endl;
613     }
614 
615     ArrayView<const Vector> v = storage.getDt<Vector>(QuantityId::POSITION);
616 
617     FileLogger logger(Path("tp.in"));
618     UniformRng rng;
619     logger.write(npart);
620     for (Size i = 0; i < npart; ++i) {
621         logger.write(r / Constants::au);
622         const Size idx = clamp<Size>(Size(rng() * v.size()), 0, storage.getParticleCnt() - 1);
623         logger.write(v[idx] / Constants::au * Constants::year);
624         logger.write("0");
625         logger.write("0.0");
626     }*/
627     std::wifstream ifs(filePath.native());
628     std::wstring line;
629 
630     Array<Float> rs;
631     while (std::getline(ifs, line)) {
632         const Float d = std::stof(line);
633         rs.push(d / 2 * 1000);
634     }
635 
636     std::ofstream yarko("yarko.in");
637     yarko << rs.size() << std::endl;
638     for (Float r : rs) {
639         yarko << r << " 2860.0 1500.0 0.0010 680.0 0.10 0.90" << std::endl;
640     }
641 
642     std::ofstream spin("spin.in");
643     spin << rs.size() << "\n-1\n1\n";
644     UniformRng rng;
645     for (Size i = 0; i < rs.size(); ++i) {
646         const Float phi = rng() * 2._f * PI;
647         const Float cosTheta = rng() * 2._f - 1._f;
648         const Float theta = Sph::acos(cosTheta);
649         spin << sphericalToCartesian(1._f, theta, phi) << "  " << sampleMaxwellBoltzmann(rng, 0.0001_f)
650              << std::endl;
651     }
652 
653     std::ofstream yorp("yorp.in");
654     for (Size i = 1; i <= rs.size(); ++i) {
655         yorp << i << "  " << clamp(int(rng() * 200), 0, 199) << std::endl;
656     }
657 }
658 
origComponents(const Path & lastDumpPath,const Path & firstDumpPath,const Path & colorizedDumpPath)659 void origComponents(const Path& lastDumpPath, const Path& firstDumpPath, const Path& colorizedDumpPath) {
660     BinaryInput input;
661     Storage lastDump, firstDump;
662     Statistics stats;
663     Outcome res1 = input.load(lastDumpPath, lastDump, stats);
664     Outcome res2 = input.load(firstDumpPath, firstDump, stats);
665     if (!res1 || !res2) {
666         throw IoError((res1 || res2).error());
667     }
668 
669     // use last dump to find components
670     Array<Size> components;
671     Post::findComponents(
672         lastDump, 2._f, Post::ComponentFlag::ESCAPE_VELOCITY | Post::ComponentFlag::SORT_BY_MASS, components);
673 
674     // "colorize" the flag quantity using the components
675     SPH_ASSERT(firstDump.getParticleCnt() == components.size());
676     firstDump.getValue<Size>(QuantityId::FLAG) = components.clone();
677 
678     // save as new file
679     BinaryOutput output(colorizedDumpPath);
680     output.dump(firstDump, stats);
681 }
682 
extractLr(const Path & inputPath,const Path & outputPath)683 void extractLr(const Path& inputPath, const Path& outputPath) {
684     BinaryInput input;
685     Storage storage;
686     Statistics stats;
687     Outcome res = input.load(inputPath, storage, stats);
688     if (!res) {
689         throw IoError(res.error());
690     }
691 
692     // allow using this for storage without masses --> add ad hoc mass if it's missing
693     if (!storage.has(QuantityId::MASS)) {
694         storage.insert<Float>(QuantityId::MASS, OrderEnum::ZERO, 1._f);
695     }
696 
697     Array<Size> components;
698     const Size componentCnt =
699         Post::findComponents(storage, 1.5_f, Post::ComponentFlag::SORT_BY_MASS, components);
700     std::cout << "Component cnt = " << componentCnt << std::endl;
701 
702     Array<Size> toRemove;
703     for (Size i = 0; i < components.size(); ++i) {
704         if (components[i] != 0) {
705             // not LR
706             toRemove.push(i);
707         }
708     }
709     storage.remove(toRemove, Storage::IndicesFlag::INDICES_SORTED);
710 
711     moveToCenterOfMassFrame(storage);
712 
713     BinaryOutput output(outputPath);
714     output.dump(storage, stats);
715 
716     if (storage.has(QuantityId::DENSITY)) {
717         ArrayView<const Float> m = storage.getValue<Float>(QuantityId::MASS);
718         ArrayView<const Float> rho = storage.getValue<Float>(QuantityId::DENSITY);
719 
720         Float volume = 0._f;
721         for (Size i = 0; i < m.size(); ++i) {
722             volume += m[i] / rho[i];
723         }
724 
725         std::cout << "eq. diameter = " << Sph::cbrt(3._f * volume / (4._f * PI)) * 2._f / 1000._f << "km"
726                   << std::endl;
727     }
728 
729     ArrayView<const Float> m = storage.getValue<Float>(QuantityId::MASS);
730     ArrayView<Vector> r = storage.getValue<Vector>(QuantityId::POSITION);
731     ArrayView<Vector> v = storage.getDt<Vector>(QuantityId::POSITION);
732     const Float omega = getLength(Post::getAngularFrequency(m, r, v));
733     std::cout << "period = " << 2._f * PI / omega / 3600._f << "h" << std::endl;
734 
735     SymmetricTensor I = Post::getInertiaTensor(m, r);
736     SPH_ASSERT(isReal(I), I);
737     Eigen e = eigenDecomposition(I);
738     /*std::cout << "I = " << I << std::endl;
739     std::cout << "matrix = " << e.vectors << std::endl;
740     std::cout << "values = " << e.values << std::endl;*/
741     const Float A = e.values[2];
742     const Float B = e.values[1];
743     const Float C = e.values[0];
744     const Float a = sqrt(B + C - A);
745     const Float b = sqrt(A + C - B);
746     const Float c = sqrt(A + B - C);
747     SPH_ASSERT(a > 0._f && b > 0._f && c > 0._f, a, b, c);
748     std::cout << "a/b = " << a / b << std::endl;
749     std::cout << "b/c = " << b / c << std::endl;
750 }
751 
printHelp()752 void printHelp() {
753     std::cout << "Expected usage: post mode [parameters]" << std::endl
754               << " where 'mode' is one of:" << std::endl
755               << " - pkdgravToSfd   - computes the cumulative SFD from pkdgrav output file" << std::endl
756               << " - pkdgravToOmega - computes the spin rate distribution from pkdgrav output file"
757               << std::endl
758               << " - pkdgravToMoons - finds satellites of the largest remnant (fragment) from pkdgrav "
759                  "output file"
760               << std::endl
761               << "- ssfToSfd - computes the cumulative SFD from SPH output file" << std::endl
762               << "- ssfToVelocity - computes the velocity distribution from SPH output file" << std::endl
763               << "- harris - TODO" << std::endl
764               << "- stats - prints ejected mass and the period of the largest remnant" << std::endl
765               << "- swift - makes yarko.in, yorp.in and spin.in input file for swift" << std::endl;
766 }
767 
main(int argc,char ** argv)768 int main(int argc, char** argv) {
769     if (argc < 2) {
770         printHelp();
771         return 0;
772     }
773     try {
774 
775         String mode = String::fromAscii(argv[1]);
776         if (mode == "pkdgravToSfd") {
777             if (argc < 4) {
778                 std::cout << "Expected parameters: post pkdgravToSfd ss.50000.bt sfd.txt";
779                 return 0;
780             }
781             return pkdgravToSfd(Path(String::fromAscii(argv[2])), Path(String::fromAscii(argv[3])));
782         } else if (mode == "pkdgravToOmega") {
783             if (argc < 4) {
784                 std::cout << "Expected parameters: post pkdgravToOmega ss.50000.bt omega.txt";
785                 return 0;
786             }
787             return pkdgravToOmega(Path(String::fromAscii(argv[2])), Path(String::fromAscii(argv[3])));
788         } else if (mode == "pkdgravToMoons") {
789             if (argc < 4) {
790                 std::cout << "Expected parameters: post pkdgravToMoons ss.50000.bt 0.1";
791                 return 0;
792             }
793             const Float limit = std::atof(argv[3]);
794             return pkdgravToMoons(Path(String::fromAscii(argv[2])), limit);
795         } else if (mode == "ssfToSfd") {
796             if (argc < 4) {
797                 std::cout << "Expected parameters: post ssfToSfd [--components] output.ssf sfd.txt";
798                 return 0;
799             }
800             if (String(String::fromAscii(argv[2])) == "--components") {
801                 return ssfToSfd(Post::HistogramSource::COMPONENTS,
802                     Path(String::fromAscii(argv[3])),
803                     Path(String::fromAscii(argv[4])));
804             } else {
805                 return ssfToSfd(Post::HistogramSource::PARTICLES,
806                     Path(String::fromAscii(argv[2])),
807                     Path(String::fromAscii(argv[3])));
808             }
809         } else if (mode == "ssfToVelocity") {
810             return ssfToVelocity(Path(String::fromAscii(argv[2])), Path(String::fromAscii(argv[3])));
811         } else if (mode == "ssfToOmega") {
812             if (argc < 6) {
813                 std::cout << "Expected parameters: post ssfToOmega output.ssf omega.txt omega_D.txt "
814                              "omega_dir.txt";
815                 return 0;
816             }
817             return ssfToOmega(Path(String::fromAscii(argv[2])),
818                 Path(String::fromAscii(argv[3])),
819                 Path(String::fromAscii(argv[4])),
820                 Path(String::fromAscii(argv[5])));
821         } else if (mode == "ssfToVelDir") {
822             if (argc < 4) {
823                 std::cout << "Expected parameters: post ssfToVelDir output.ssf veldir.txt" << std::endl;
824                 return 0;
825             }
826             ssfToVelDir(Path(String::fromAscii(argv[2])), Path(String::fromAscii(argv[3])));
827         } else if (mode == "harris") {
828             processHarrisFile();
829         } else if (mode == "swift") {
830             if (argc < 3) {
831                 std::cout << "Expected parameters: post maketp D.dat";
832                 return 0;
833             }
834             makeSwift(Path(String::fromAscii(argv[2])));
835         } else if (mode == "origComponents") {
836             if (argc < 5) {
837                 std::cout << "Expected parameters: post origComponents lastDump.ssf firstDump.ssf "
838                              "colorizedDump.ssf"
839                           << std::endl;
840             }
841             origComponents(Path(String::fromAscii(argv[2])),
842                 Path(String::fromAscii(argv[3])),
843                 Path(String::fromAscii(argv[4])));
844         } else if (mode == "extractLr") {
845             if (argc < 4) {
846                 std::cout << "Expected parameters: post extractLr input.ssf lr.ssf" << std::endl;
847             }
848             extractLr(Path(String::fromAscii(argv[2])), Path(String::fromAscii(argv[3])));
849         } else {
850             printHelp();
851             return 0;
852         }
853 
854     } catch (const std::exception& e) {
855         std::cout << "ERROR: " << e.what() << std::endl;
856         return -1;
857     }
858 }
859