SPH
Output.cpp
Go to the documentation of this file.
1 #include "io/Output.h"
2 #include "io/Column.h"
3 #include "io/FileSystem.h"
4 #include "io/Logger.h"
5 #include "io/Serializer.h"
7 #include "post/TwoBody.h"
8 #include "quantities/IMaterial.h"
9 #include "system/Factory.h"
10 #include <fstream>
11 
12 #ifdef SPH_USE_HDF5
13 #include <hdf5.h>
14 #endif
15 
17 
18 // ----------------------------------------------------------------------------------------------------------
19 // OutputFile
20 // ----------------------------------------------------------------------------------------------------------
21 
22 OutputFile::OutputFile(const Path& pathMask, const Size firstDumpIdx)
23  : pathMask(pathMask) {
24  dumpNum = firstDumpIdx;
25  SPH_ASSERT(!pathMask.empty());
26 }
27 
29  SPH_ASSERT(!pathMask.empty());
30  std::string path = pathMask.native();
31  std::size_t n = path.find("%d");
32  if (n != std::string::npos) {
33  std::ostringstream ss;
34  ss << std::setw(4) << std::setfill('0') << dumpNum;
35  path.replace(n, 2, ss.str());
36  }
37  n = path.find("%t");
38  if (n != std::string::npos) {
39  std::ostringstream ss;
40  const Float t = stats.get<Float>(StatisticsId::RUN_TIME);
41  ss << std::fixed << t;
43  path.replace(n, 2, ss.str());
44  }
45  dumpNum++;
46  return Path(path);
47 }
48 
50  // look for 4 consecutive digits.
51  const std::string s = path.fileName().native();
52  for (int i = 0; i < int(s.size()) - 3; ++i) {
53  if (std::isdigit(s[i]) && std::isdigit(s[i + 1]) && std::isdigit(s[i + 2]) &&
54  std::isdigit(s[i + 3])) {
55  // next digit must NOT be a number
56  if (i + 4 < int(s.size()) && std::isdigit(s[i + 4])) {
57  // 4-digit sequence is not unique, report error
58  return NOTHING;
59  }
60  try {
61  Size index = std::stoul(s.substr(i, 4));
62  return index;
63  } catch (const std::exception& e) {
64  SPH_ASSERT(false, e.what());
65  return NOTHING;
66  }
67  }
68  }
69  return NOTHING;
70 }
71 
72 Optional<OutputFile> OutputFile::getMaskFromPath(const Path& path, const Size firstDumpIdx) {
74  const std::string s = path.fileName().native();
75  for (int i = 0; i < int(s.size()) - 3; ++i) {
76  if (std::isdigit(s[i]) && std::isdigit(s[i + 1]) && std::isdigit(s[i + 2]) &&
77  std::isdigit(s[i + 3])) {
78  if (i + 4 < int(s.size()) && std::isdigit(s[i + 4])) {
79  return NOTHING;
80  }
81  std::string mask = s.substr(0, i) + "%d" + s.substr(i + 4);
82  // prepend the original parent path
83  return OutputFile(path.parentPath() / Path(mask), firstDumpIdx);
84  }
85  }
86  return NOTHING;
87 }
88 
90  std::string path = pathMask.native();
91  return path.find("%d") != std::string::npos || path.find("%t") != std::string::npos;
92 }
93 
95  return pathMask;
96 }
97 
98 IOutput::IOutput(const OutputFile& fileMask)
99  : paths(fileMask) {
100  SPH_ASSERT(!fileMask.getMask().empty());
101 }
102 
103 // ----------------------------------------------------------------------------------------------------------
104 // TextOutput/Input
105 // ----------------------------------------------------------------------------------------------------------
106 
107 static void printHeader(std::ostream& ofs, const std::string& name, const ValueEnum type) {
108  switch (type) {
109  case ValueEnum::SCALAR:
110  case ValueEnum::INDEX:
111  ofs << std::setw(20) << name;
112  break;
113  case ValueEnum::VECTOR:
114  ofs << std::setw(20) << (name + " [x]") << std::setw(20) << (name + " [y]") << std::setw(20)
115  << (name + " [z]");
116  break;
118  ofs << std::setw(20) << (name + " [xx]") << std::setw(20) << (name + " [yy]") << std::setw(20)
119  << (name + " [zz]") << std::setw(20) << (name + " [xy]") << std::setw(20) << (name + " [xz]")
120  << std::setw(20) << (name + " [yz]");
121  break;
123  ofs << std::setw(20) << (name + " [xx]") << std::setw(20) << (name + " [yy]") << std::setw(20)
124  << (name + " [xy]") << std::setw(20) << (name + " [xz]") << std::setw(20) << (name + " [yz]");
125  break;
126  default:
128  }
129 }
130 
131 static void addColumns(const Flags<OutputQuantityFlag> quantities, Array<AutoPtr<ITextColumn>>& columns) {
132  if (quantities.has(OutputQuantityFlag::INDEX)) {
133  columns.push(makeAuto<ParticleNumberColumn>());
134  }
135  if (quantities.has(OutputQuantityFlag::POSITION)) {
137  }
138  if (quantities.has(OutputQuantityFlag::VELOCITY)) {
140  }
141  if (quantities.has(OutputQuantityFlag::ANGULAR_FREQUENCY)) {
143  }
144  if (quantities.has(OutputQuantityFlag::SMOOTHING_LENGTH)) {
145  columns.push(makeAuto<SmoothingLengthColumn>());
146  }
147  if (quantities.has(OutputQuantityFlag::MASS)) {
149  }
150  if (quantities.has(OutputQuantityFlag::PRESSURE)) {
152  }
153  if (quantities.has(OutputQuantityFlag::DENSITY)) {
155  }
156  if (quantities.has(OutputQuantityFlag::ENERGY)) {
158  }
159  if (quantities.has(OutputQuantityFlag::DEVIATORIC_STRESS)) {
161  }
162  if (quantities.has(OutputQuantityFlag::DAMAGE)) {
164  }
167  }
168  if (quantities.has(OutputQuantityFlag::MATERIAL_ID)) {
170  }
171 }
172 
174  template <typename TValue>
176  columns.push(makeAuto<ValueColumn<TValue>>(id));
177  }
178 };
179 
181  const std::string& runName,
182  const Flags<OutputQuantityFlag> quantities,
183  const Flags<Options> options)
184  : IOutput(fileMask)
185  , runName(runName)
186  , options(options) {
187  addColumns(quantities, columns);
188 }
189 
190 TextOutput::~TextOutput() = default;
191 
192 Expected<Path> TextOutput::dump(const Storage& storage, const Statistics& stats) {
193  if (options.has(Options::DUMP_ALL)) {
194  columns.clear();
195  // add some 'extraordinary' quantities and position (we want those to be one of the first, not after
196  // density, etc).
197  columns.push(makeAuto<ParticleNumberColumn>());
200  columns.push(makeAuto<SmoothingLengthColumn>());
201  for (ConstStorageElement e : storage.getQuantities()) {
202  if (e.id == QuantityId::POSITION) {
203  // already added
204  continue;
205  }
206  dispatch(e.quantity.getValueEnum(), DumpAllVisitor{}, e.id, columns);
207  }
208  }
209 
210  SPH_ASSERT(!columns.empty(), "No column added to TextOutput");
211  const Path fileName = paths.getNextPath(stats);
212 
213  Outcome dirResult = FileSystem::createDirectory(fileName.parentPath());
214  if (!dirResult) {
215  return makeUnexpected<Path>(
216  "Cannot create directory " + fileName.parentPath().native() + ": " + dirResult.error());
217  }
218 
219  try {
220  std::ofstream ofs(fileName.native());
221  // print description
222  ofs << "# Run: " << runName << std::endl;
223  if (stats.has(StatisticsId::RUN_TIME)) {
224  ofs << "# SPH dump, time = " << stats.get<Float>(StatisticsId::RUN_TIME) << std::endl;
225  }
226  ofs << "# ";
227  for (auto& column : columns) {
228  printHeader(ofs, column->getName(), column->getType());
229  }
230  ofs << std::endl;
231  // print data lines, starting with second-order quantities
232  for (Size i = 0; i < storage.getParticleCnt(); ++i) {
233  for (auto& column : columns) {
234  // write one extra space to be sure numbers won't merge
235  if (options.has(Options::SCIENTIFIC)) {
236  ofs << std::scientific << std::setprecision(PRECISION)
237  << column->evaluate(storage, stats, i);
238  } else {
239  ofs << std::setprecision(PRECISION) << column->evaluate(storage, stats, i);
240  }
241  }
242  ofs << std::endl;
243  }
244  ofs.close();
245  return fileName;
246  } catch (const std::exception& e) {
247  return makeUnexpected<Path>("Cannot save output file " + fileName.native() + ": " + e.what());
248  }
249 }
250 
252  columns.push(std::move(column));
253  return *this;
254 }
255 
257  addColumns(quantities, columns);
258 }
259 
260 Outcome TextInput::load(const Path& path, Storage& storage, Statistics& UNUSED(stats)) {
261  try {
262  std::ifstream ifs(path.native());
263  if (!ifs) {
264  return makeFailed("Failed to open the file");
265  }
266  std::string line;
267  storage.removeAll();
268  // storage currently requires at least one quantity for insertion by value
270 
271  Size particleCnt = 0;
272  while (std::getline(ifs, line)) {
273  if (line[0] == '#') { // comment
274  continue;
275  }
276  std::stringstream ss(line);
277  for (AutoPtr<ITextColumn>& column : columns) {
278  switch (column->getType()) {
280  case ValueEnum::INDEX: {
281  Size i;
282  ss >> i;
283  column->accumulate(storage, i, particleCnt);
284  break;
285  }
286  case ValueEnum::SCALAR: {
287  Float f;
288  ss >> f;
289  column->accumulate(storage, f, particleCnt);
290  break;
291  }
292  case ValueEnum::VECTOR: {
293  Vector v(0._f);
294  ss >> v[X] >> v[Y] >> v[Z];
295  column->accumulate(storage, v, particleCnt);
296  break;
297  }
299  Float xx, yy, xy, xz, yz;
300  ss >> xx >> yy >> xy >> xz >> yz;
301  TracelessTensor t(xx, yy, xy, xz, yz);
302  column->accumulate(storage, t, particleCnt);
303  break;
304  }
305  default:
307  }
308  }
309  particleCnt++;
310  }
311  ifs.close();
312 
313  // resize the flag quantity to make the storage consistent
314  Quantity& flags = storage.getQuantity(QuantityId::FLAG);
315  for (Array<Size>& buffer : flags.getAll<Size>()) {
316  buffer.resize(particleCnt);
317  }
318 
319  // sanity check
320  if (storage.getParticleCnt() != particleCnt || !storage.isValid()) {
321  return makeFailed("Loaded storage is not valid");
322  }
323 
324  } catch (const std::exception& e) {
325  return makeFailed(e.what());
326  }
327  return SUCCESS;
328 }
329 
331  columns.push(std::move(column));
332  return *this;
333 }
334 
335 // ----------------------------------------------------------------------------------------------------------
336 // GnuplotOutput
337 // ----------------------------------------------------------------------------------------------------------
338 
339 Expected<Path> GnuplotOutput::dump(const Storage& storage, const Statistics& stats) {
340  const Expected<Path> path = TextOutput::dump(storage, stats);
341  if (!path) {
342  return path;
343  }
344  const Path pathWithoutExt = Path(path.value()).removeExtension();
345  const Float time = stats.get<Float>(StatisticsId::RUN_TIME);
346  const std::string command = "gnuplot -e \"filename='" + pathWithoutExt.native() +
347  "'; time=" + std::to_string(time) + "\" " + scriptPath;
348  const int returned = system(command.c_str());
349  MARK_USED(returned);
350  return path;
351 }
352 
353 // ----------------------------------------------------------------------------------------------------------
354 // BinaryOutput/Input
355 // ----------------------------------------------------------------------------------------------------------
356 
357 namespace {
358 
360 template <bool Precise>
361 struct SerializerDispatcher {
362  Serializer<Precise>& serializer;
363 
364  template <typename T>
365  void operator()(const T& value) {
366  serializer.write(value);
367  }
368  void operator()(const Interval& value) {
369  serializer.write(value.lower(), value.upper());
370  }
371  void operator()(const Vector& value) {
372  serializer.write(value[X], value[Y], value[Z], value[H]);
373  }
374  void operator()(const SymmetricTensor& t) {
375  serializer.write(t(0, 0), t(1, 1), t(2, 2), t(0, 1), t(0, 2), t(1, 2));
376  }
377  void operator()(const TracelessTensor& t) {
378  serializer.write(t(0, 0), t(1, 1), t(0, 1), t(0, 2), t(1, 2));
379  }
380  void operator()(const Tensor& t) {
381  serializer.write(t(0, 0), t(0, 1), t(0, 2), t(1, 0), t(1, 1), t(1, 2), t(2, 0), t(2, 1), t(2, 2));
382  }
383  void operator()(const EnumWrapper& e) {
384  // Type hash can be different between invocations, so we cannot serialize it. Write 0 (for backward
385  // compatibility)
386  serializer.write(e.value, 0);
387  }
388 };
389 
390 template <bool Precise>
391 struct DeserializerDispatcher {
392  Deserializer<Precise>& deserializer;
393 
394  template <typename T>
395  void operator()(T& value) {
396  deserializer.read(value);
397  }
398  void operator()(Interval& value) {
399  Float lower, upper;
400  deserializer.read(lower, upper);
401  value = Interval(lower, upper);
402  }
403  void operator()(Vector& value) {
404  deserializer.read(value[X], value[Y], value[Z], value[H]);
405  }
406  void operator()(SymmetricTensor& t) {
407  deserializer.read(t(0, 0), t(1, 1), t(2, 2), t(0, 1), t(0, 2), t(1, 2));
408  }
409  void operator()(TracelessTensor& t) {
411  deserializer.read(a[0], a[1], a[2], a[3], a[4]);
412  t = TracelessTensor(a[0], a[1], a[2], a[3], a[4]);
413  }
414  void operator()(Tensor& t) {
415  deserializer.read(t(0, 0), t(0, 1), t(0, 2), t(1, 0), t(1, 1), t(1, 2), t(2, 0), t(2, 1), t(2, 2));
416  }
417  void operator()(EnumWrapper& e) {
418  int dummy;
419  deserializer.read(e.value, dummy);
420  }
421 };
422 
423 struct StoreBuffersVisitor {
424  template <typename TValue>
425  void visit(const Quantity& q, Serializer<true>& serializer, const IndexSequence& sequence) {
426  SerializerDispatcher<true> dispatcher{ serializer };
427  StaticArray<const Array<TValue>&, 3> buffers = q.getAll<TValue>();
428  for (Size i : sequence) {
429  dispatcher(buffers[0][i]);
430  }
431  switch (q.getOrderEnum()) {
432  case OrderEnum::ZERO:
433  break;
434  case OrderEnum::FIRST:
435  for (Size i : sequence) {
436  dispatcher(buffers[1][i]);
437  }
438  break;
439  case OrderEnum::SECOND:
440  for (Size i : sequence) {
441  dispatcher(buffers[1][i]);
442  }
443  for (Size i : sequence) {
444  dispatcher(buffers[2][i]);
445  }
446  break;
447  default:
448  STOP;
449  }
450  }
451 };
452 
453 struct LoadBuffersVisitor {
454  template <typename TValue>
455  void visit(Storage& storage,
456  Deserializer<true>& deserializer,
457  const IndexSequence& sequence,
458  const QuantityId id,
459  const OrderEnum order) {
460  DeserializerDispatcher<true> dispatcher{ deserializer };
461  Array<TValue> buffer(sequence.size());
462  for (Size i : sequence) {
463  dispatcher(buffer[i]);
464  }
465  storage.insert<TValue>(id, order, std::move(buffer));
466  switch (order) {
467  case OrderEnum::ZERO:
468  // already done
469  break;
470  case OrderEnum::FIRST: {
471  ArrayView<TValue> dv = storage.getDt<TValue>(id);
472  for (Size i : sequence) {
473  dispatcher(dv[i]);
474  }
475  break;
476  }
477  case OrderEnum::SECOND: {
478  ArrayView<TValue> dv = storage.getDt<TValue>(id);
479  ArrayView<TValue> d2v = storage.getD2t<TValue>(id);
480  for (Size i : sequence) {
481  dispatcher(dv[i]);
482  }
483  for (Size i : sequence) {
484  dispatcher(d2v[i]);
485  }
486  break;
487  }
488  default:
490  }
491  }
492 };
493 
494 void writeString(const std::string& s, Serializer<true>& serializer) {
495  char buffer[16];
496  for (Size i = 0; i < 16; ++i) {
497  if (i < s.size()) {
498  buffer[i] = s[i];
499  } else {
500  buffer[i] = '\0';
501  }
502  }
503  serializer.write(buffer);
504 }
505 
506 } // namespace
507 
508 BinaryOutput::BinaryOutput(const OutputFile& fileMask, const RunTypeEnum runTypeId)
509  : IOutput(fileMask)
510  , runTypeId(runTypeId) {}
511 
512 Expected<Path> BinaryOutput::dump(const Storage& storage, const Statistics& stats) {
514 
515  const Path fileName = paths.getNextPath(stats);
516  Outcome dirResult = FileSystem::createDirectory(fileName.parentPath());
517  if (!dirResult) {
518  return makeUnexpected<Path>(
519  "Cannot create directory " + fileName.parentPath().native() + ": " + dirResult.error());
520  }
521 
522  const Float runTime = stats.getOr<Float>(StatisticsId::RUN_TIME, 0._f);
523  const Size wallclockTime = stats.getOr<int>(StatisticsId::WALLCLOCK_TIME, 0._f);
524 
525  Serializer<true> serializer(fileName);
526  // file format identifier
527  const Size materialCnt = storage.getMaterialCnt();
528  const Size quantityCnt = storage.getQuantityCnt() - int(storage.has(QuantityId::MATERIAL_ID));
529  const Float timeStep = stats.getOr<Float>(StatisticsId::TIMESTEP_VALUE, 0.1_f);
530  serializer.write("SPH",
531  runTime,
532  storage.getParticleCnt(),
533  quantityCnt,
534  materialCnt,
535  timeStep,
537  // write run type
538  writeString(EnumMap::toString(runTypeId), serializer);
539  // write build date
540  writeString(__DATE__, serializer);
541  // write wallclock time for proper ETA of resumed simulation
542  serializer.write(wallclockTime);
543 
544  // zero bytes until 256 to allow extensions of the header
545  serializer.addPadding(PADDING_SIZE);
546 
547  // quantity information
548  Array<QuantityId> cachedIds;
549  for (auto i : storage.getQuantities()) {
550  // first 3 values: quantity ID, order (number of derivatives), type
551  const Quantity& q = i.quantity;
552  if (i.id != QuantityId::MATERIAL_ID) {
553  // no need to dump material IDs, they are always consecutive
554  cachedIds.push(i.id);
555  serializer.write(Size(i.id), Size(q.getOrderEnum()), Size(q.getValueEnum()));
556  }
557  }
558 
559  SerializerDispatcher<true> dispatcher{ serializer };
560  const bool hasMaterials = materialCnt > 0;
561  // dump quantities separated by materials
562  for (Size matIdx = 0; matIdx < max(materialCnt, Size(1)); ++matIdx) {
563  // storage can currently exist without materials, only write material params if we have a material
564  if (hasMaterials) {
565  serializer.write("MAT", matIdx);
566  MaterialView material = storage.getMaterial(matIdx);
567  serializer.write(material->getParams().size());
568  // dump body settings
569  for (auto param : material->getParams()) {
570  serializer.write(param.id);
571  serializer.write(param.value.getTypeIdx());
572  forValue(param.value, dispatcher);
573  }
574  // dump all ranges and minimal values for timestepping
575  for (QuantityId id : cachedIds) {
576  const Interval range = material->range(id);
577  const Float minimal = material->minimal(id);
578  serializer.write(id, range.lower(), range.upper(), minimal);
579  }
580  } else {
581  // write that we have no materials
582  serializer.write("NOMAT");
583  }
584 
585  // storage dump for given material
586  IndexSequence sequence = [&] {
587  if (hasMaterials) {
588  MaterialView material = storage.getMaterial(matIdx);
589  return material.sequence();
590  } else {
591  return IndexSequence(0, storage.getParticleCnt());
592  }
593  }();
594  serializer.write(*sequence.begin(), *sequence.end());
595 
596  for (auto i : storage.getQuantities()) {
597  if (i.id != QuantityId::MATERIAL_ID) {
598  const Quantity& q = i.quantity;
599  StoreBuffersVisitor visitor;
600  dispatch(q.getValueEnum(), visitor, q, serializer, sequence);
601  }
602  }
603  }
604 
605  return fileName;
606 }
607 
608 template <typename T>
609 static void setEnumIndex(const BodySettings& UNUSED(settings),
610  const BodySettingsId UNUSED(paramId),
611  T& UNUSED(entry)) {
612  // do nothing for other types
613 }
614 
615 template <>
616 void setEnumIndex(const BodySettings& settings, const BodySettingsId paramId, EnumWrapper& entry) {
617  EnumWrapper current = settings.get<EnumWrapper>(paramId);
618  entry.index = current.index;
619 }
620 
621 
622 static Expected<Storage> loadMaterial(const Size matIdx,
623  Deserializer<true>& deserializer,
625  const BinaryIoVersion version) {
626  Size matIdxCheck;
627  std::string identifier;
628  deserializer.read(identifier, matIdxCheck);
629  // some consistency checks
630  if (identifier != "MAT") {
631  return makeUnexpected<Storage>("Invalid material identifier, expected MAT, got " + identifier);
632  }
633  if (matIdxCheck != matIdx) {
634  return makeUnexpected<Storage>("Unexpected material index, expected " + std::to_string(matIdx) +
635  ", got " + std::to_string(matIdxCheck));
636  }
637 
638  Size matParamCnt;
639  deserializer.read(matParamCnt);
640  BodySettings body;
641  for (Size i = 0; i < matParamCnt; ++i) {
642  // read body settings
643  BodySettingsId paramId;
644  Size valueId;
645  deserializer.read(paramId, valueId);
646 
647  if (version == BinaryIoVersion::FIRST) {
648  if (valueId == 1 && body.hasType<EnumWrapper>(paramId)) {
649  // enums used to be stored as ints (index 1), now we store it as enum wrapper;
650  // convert the value to enum and save manually
651  EnumWrapper e = body.get<EnumWrapper>(paramId);
652  deserializer.read(e.value);
653  body.set(paramId, e);
654  continue;
655  }
656  }
657 
660  { CONSTRUCT_TYPE_IDX, valueId } };
661 
662  forValue(iteratorValue.value, [&deserializer, &body, paramId](auto& entry) {
663  DeserializerDispatcher<true>{ deserializer }(entry);
664  // little hack: EnumWrapper is loaded with no type index (as it cannot be serialized), so we have
665  // to set it to the correct value, otherwise it would trigger asserts in set function.
666  try {
667  setEnumIndex(body, paramId, entry);
668  body.set(paramId, entry);
669  } catch (const Exception& UNUSED(e)) {
670  // can be a parameter from newer version, silence the exception for backwards compatibility
672  }
673  });
674  }
675 
676  // create material based on settings
677  AutoPtr<IMaterial> material = Factory::getMaterial(body);
678  // read all ranges and minimal values for timestepping
679  for (Size i = 0; i < ids.size(); ++i) {
680  QuantityId id;
681  Float lower, upper, minimal;
682  deserializer.read(id, lower, upper, minimal);
683  if (id != ids[i]) {
684  return makeUnexpected<Storage>("Unexpected quantityId, expected " +
685  getMetadata(ids[i]).quantityName + ", got " +
686  getMetadata(id).quantityName);
687  }
688  Interval range;
689  if (lower < upper) {
690  range = Interval(lower, upper);
691  } else {
692  range = Interval::unbounded();
693  }
694  material->setRange(id, range, minimal);
695  }
696  // create storage for this material
697  return Storage(std::move(material));
698 }
699 
700 static Optional<RunTypeEnum> readRunType(char* buffer, const BinaryIoVersion version) {
701  std::string runTypeStr(buffer);
702  if (!runTypeStr.empty()) {
703  return EnumMap::fromString<RunTypeEnum>(runTypeStr).value();
704  } else {
706  return NOTHING;
707  }
708 }
709 
710 static Optional<std::string> readBuildDate(char* buffer, const BinaryIoVersion version) {
711  if (version >= BinaryIoVersion::V2021_03_20) {
712  return std::string(buffer);
713  } else {
714  return NOTHING;
715  }
716 }
717 
718 Outcome BinaryInput::load(const Path& path, Storage& storage, Statistics& stats) {
719  storage.removeAll();
720  Deserializer<true> deserializer(path);
721  std::string identifier;
722  Float time, timeStep;
723  Size wallclockTime;
724  Size particleCnt, quantityCnt, materialCnt;
725  BinaryIoVersion version;
726  try {
727  char runTypeBuffer[16];
728  char buildDateBuffer[16];
729  deserializer.read(identifier,
730  time,
731  particleCnt,
732  quantityCnt,
733  materialCnt,
734  timeStep,
735  version,
736  runTypeBuffer,
737  buildDateBuffer,
738  wallclockTime);
739  } catch (SerializerException&) {
740  return makeFailed("Invalid file format");
741  }
742  if (identifier != "SPH") {
743  return makeFailed("Invalid format specifier: expected SPH, got ", identifier);
744  }
745  stats.set(StatisticsId::RUN_TIME, time);
746  stats.set(StatisticsId::TIMESTEP_VALUE, timeStep);
747  if (version >= BinaryIoVersion::V2021_03_20) {
748  stats.set(StatisticsId::WALLCLOCK_TIME, int(wallclockTime));
749  }
750  try {
751  deserializer.skip(BinaryOutput::PADDING_SIZE);
752  } catch (SerializerException&) {
753  return makeFailed("Incorrect header size");
754  }
755  Array<QuantityId> quantityIds(quantityCnt);
756  Array<OrderEnum> orders(quantityCnt);
757  Array<ValueEnum> valueTypes(quantityCnt);
758  try {
759  for (Size i = 0; i < quantityCnt; ++i) {
760  deserializer.read(quantityIds[i], orders[i], valueTypes[i]);
761  }
762  } catch (SerializerException& e) {
763  return makeFailed(e.what());
764  }
765 
766  // Size loadedQuantities = 0;
767  const bool hasMaterials = materialCnt > 0;
768  for (Size matIdx = 0; matIdx < max(materialCnt, Size(1)); ++matIdx) {
769  Storage bodyStorage;
770  if (hasMaterials) {
771  try {
772  Expected<Storage> loadedStorage = loadMaterial(matIdx, deserializer, quantityIds, version);
773  if (!loadedStorage) {
774  return makeFailed(loadedStorage.error());
775  } else {
776  bodyStorage = std::move(loadedStorage.value());
777  }
778  } catch (SerializerException& e) {
779  return makeFailed(e.what());
780  }
781  } else {
782  try {
783  deserializer.read(identifier);
784  } catch (SerializerException& e) {
785  return makeFailed(e.what());
786  }
787  if (identifier != "NOMAT") {
788  return makeFailed("Unexpected missing material identifier, expected NOMAT, got ", identifier);
789  }
790  }
791 
792  try {
793  Size from, to;
794  deserializer.read(from, to);
795  LoadBuffersVisitor visitor;
796  for (Size i = 0; i < quantityCnt; ++i) {
797  dispatch(valueTypes[i],
798  visitor,
799  bodyStorage,
800  deserializer,
801  IndexSequence(0, to - from),
802  quantityIds[i],
803  orders[i]);
804  }
805  } catch (SerializerException& e) {
806  return makeFailed(e.what());
807  }
808  storage.merge(std::move(bodyStorage));
809  }
810 
811  return SUCCESS;
812 }
813 
815  Info info;
816  char runTypeBuffer[16];
817  char dateBuffer[16];
818  std::string identifier;
819  try {
820  Deserializer<true> deserializer(path);
821  deserializer.read(identifier,
822  info.runTime,
823  info.particleCnt,
824  info.quantityCnt,
825  info.materialCnt,
826  info.timeStep,
827  info.version,
828  runTypeBuffer,
829  dateBuffer,
830  info.wallclockTime);
831  } catch (SerializerException&) {
832  return makeUnexpected<Info>("Invalid file format");
833  }
834  if (identifier != "SPH") {
835  return makeUnexpected<Info>("Invalid format specifier: expected SPH, got " + identifier);
836  }
837  info.runType = readRunType(runTypeBuffer, info.version);
838  info.buildDate = readBuildDate(dateBuffer, info.version);
839  return Expected<Info>(std::move(info));
840 }
841 
842 // ----------------------------------------------------------------------------------------------------------
843 // CompressedOutput/Input
844 // ----------------------------------------------------------------------------------------------------------
845 
847  const CompressionEnum compression,
848  const RunTypeEnum runTypeId)
849  : IOutput(fileMask)
850  , compression(compression)
851  , runTypeId(runTypeId) {}
852 
853 const int MAGIC_NUMBER = 42;
854 
855 template <typename T>
856 static void compressQuantity(Serializer<false>& serializer,
857  const CompressionEnum compression,
858  const Array<T>& values) {
859  SerializerDispatcher<false> dispatcher{ serializer };
860  if (compression == CompressionEnum::RLE) {
861  dispatcher(MAGIC_NUMBER);
862 
863  // StaticArray<char, 256> lastBuffer;
864  T lastValue(NAN);
865  Size count = 0;
866  for (Size i = 0; i < values.size(); ++i) {
869  if (!almostEqual(values[i], lastValue, 1.e-4_f)) {
870  if (count > 0) {
871  // end of the run, write the count
872  dispatcher(count);
873  count = 0;
874  }
875  dispatcher(values[i]);
876  lastValue = values[i];
877  } else {
878  if (count == 0) {
879  // first repeated value, write again to mark the start of the run
880  dispatcher(lastValue);
881  }
882  ++count;
883  }
884  }
885  // close the last run
886  if (count > 0) {
887  dispatcher(count);
888  }
889  } else {
890  SPH_ASSERT(compression == CompressionEnum::NONE);
891  for (Size i = 0; i < values.size(); ++i) {
892  dispatcher(values[i]);
893  }
894  }
895 }
896 
897 template <typename T>
898 static void decompressQuantity(Deserializer<false>& deserializer,
899  const CompressionEnum compression,
900  Array<T>& values) {
901  DeserializerDispatcher<false> dispatcher{ deserializer };
902 
903  if (compression == CompressionEnum::RLE) {
904  int magic;
905  dispatcher(magic);
906  if (magic != MAGIC_NUMBER) {
907  throw SerializerException("Invalid compression", 0);
908  }
909 
910  T lastValue = T(NAN);
911  Size i = 0;
912  while (i < values.size()) {
913  dispatcher(values[i]);
914  if (values[i] != lastValue) {
915  lastValue = values[i];
916  ++i;
917  } else {
918  Size count;
919  dispatcher(count);
920  SPH_ASSERT(i + count <= values.size());
921  for (Size j = 0; j < count; ++j) {
922  values[i++] = lastValue;
923  }
924  }
925  }
926  } else {
927  for (Size i = 0; i < values.size(); ++i) {
928  dispatcher(values[i]);
929  }
930  }
931 }
932 
935 
936  const Path fileName = paths.getNextPath(stats);
937  Outcome dirResult = FileSystem::createDirectory(fileName.parentPath());
938  if (!dirResult) {
939  return makeUnexpected<Path>(
940  "Cannot create directory " + fileName.parentPath().native() + ": " + dirResult.error());
941  }
942 
943  const Float time = stats.getOr<Float>(StatisticsId::RUN_TIME, 0._f);
944 
945  Serializer<false> serializer(fileName);
946  serializer.write("CPRSPH", time, storage.getParticleCnt(), compression, CompressedIoVersion::FIRST);
947 
949  serializer.write(runTypeId);
950  serializer.addPadding(230);
951 
952  // mandatory, without prefix
953  compressQuantity(serializer, compression, storage.getValue<Vector>(QuantityId::POSITION));
954  compressQuantity(serializer, compression, storage.getDt<Vector>(QuantityId::POSITION));
955 
956  Array<QuantityId> expectedIds{
958  };
959  Array<QuantityId> ids;
960  Size count = 0;
961  for (QuantityId id : expectedIds) {
962  if (storage.has(id)) {
963  ++count;
964  ids.push(id);
965  }
966  }
967  serializer.write(count);
968 
969  for (QuantityId id : ids) {
970  serializer.write(id);
971  compressQuantity(serializer, compression, storage.getValue<Float>(id));
972  }
973 
974  return fileName;
975 }
976 
977 Outcome CompressedInput::load(const Path& path, Storage& storage, Statistics& stats) {
978  // create any material
980 
981  Deserializer<false> deserializer(path);
982  std::string identifier;
983  Float time;
984  Size particleCnt;
985  CompressedIoVersion version;
986  CompressionEnum compression;
987  RunTypeEnum runTypeId;
988  try {
989  deserializer.read(identifier, time, particleCnt, compression, version, runTypeId);
990  } catch (SerializerException&) {
991  return makeFailed("Invalid file format");
992  }
993  if (identifier != "CPRSPH") {
994  return makeFailed("Invalid format specifier: expected CPRSPH, got ", identifier);
995  }
996  stats.set(StatisticsId::RUN_TIME, time);
997  try {
998  deserializer.skip(230);
999  } catch (SerializerException&) {
1000  return makeFailed("Incorrect header size");
1001  }
1002 
1003  try {
1004  Array<Vector> positions(particleCnt);
1005  decompressQuantity(deserializer, compression, positions);
1006  storage.insert<Vector>(QuantityId::POSITION, OrderEnum::SECOND, std::move(positions));
1007 
1008  Array<Vector> velocities(particleCnt);
1009  decompressQuantity(deserializer, compression, velocities);
1010  storage.getDt<Vector>(QuantityId::POSITION) = std::move(velocities);
1011 
1012  Size count;
1013  deserializer.read(count);
1014  for (Size i = 0; i < count; ++i) {
1015  QuantityId id;
1016  deserializer.read(id);
1017  Array<Float> values(particleCnt);
1018  decompressQuantity(deserializer, compression, values);
1019  storage.insert<Float>(id, OrderEnum::ZERO, std::move(values));
1020  }
1021  } catch (SerializerException& e) {
1022  return makeFailed(e.what());
1023  }
1024 
1025  SPH_ASSERT(storage.isValid());
1026 
1027  return SUCCESS;
1028 }
1029 
1031  Deserializer<false> deserializer(path);
1032  std::string identifier;
1033  Float time;
1034  Size particleCnt;
1035  CompressedIoVersion version;
1036  CompressionEnum compression;
1037  RunTypeEnum runTypeId;
1038  try {
1039  deserializer.read(identifier, time, particleCnt, compression, version, runTypeId);
1040  } catch (SerializerException&) {
1041  return makeUnexpected<CompressedInput::Info>("Invalid file format");
1042  }
1043  if (identifier != "CPRSPH") {
1044  return makeUnexpected<CompressedInput::Info>(
1045  "Invalid format specifier: expected CPRSPH, got " + identifier);
1046  }
1047  CompressedInput::Info info;
1048  info.particleCnt = particleCnt;
1049  info.runTime = time;
1050  info.runType = runTypeId;
1051  info.version = version;
1052  return info;
1053 }
1054 
1055 // ----------------------------------------------------------------------------------------------------------
1056 // VtkOutput
1057 // ----------------------------------------------------------------------------------------------------------
1058 
1059 static void writeDataArray(std::ofstream& of,
1060  const Storage& storage,
1061  const Statistics& stats,
1062  const ITextColumn& column) {
1063  switch (column.getType()) {
1064  case ValueEnum::SCALAR:
1065  of << R"( <DataArray type="Float32" Name=")" << column.getName() << R"(" format="ascii">)";
1066  break;
1067  case ValueEnum::VECTOR:
1068  of << R"( <DataArray type="Float32" Name=")" << column.getName()
1069  << R"(" NumberOfComponents="3" format="ascii">)";
1070  break;
1071  case ValueEnum::INDEX:
1072  of << R"( <DataArray type="Int32" Name=")" << column.getName() << R"(" format="ascii">)";
1073  break;
1075  of << R"( <DataArray type="Float32" Name=")" << column.getName()
1076  << R"(" NumberOfComponents="6" format="ascii">)";
1077  break;
1079  of << R"( <DataArray type="Float32" Name=")" << column.getName()
1080  << R"(" NumberOfComponents="5" format="ascii">)";
1081  break;
1082  default:
1084  }
1085 
1086  of << "\n";
1087  for (Size i = 0; i < storage.getParticleCnt(); ++i) {
1088  of << column.evaluate(storage, stats, i) << "\n";
1089  }
1090 
1091  of << R"( </DataArray>)"
1092  << "\n";
1093 }
1094 
1096  : IOutput(fileMask)
1097  , flags(flags) {
1098  // Positions are stored in <Points> block, other quantities in <PointData>; remove the position flag to
1099  // avoid storing positions twice
1100  this->flags.unset(OutputQuantityFlag::POSITION);
1101 }
1102 
1103 Expected<Path> VtkOutput::dump(const Storage& storage, const Statistics& stats) {
1104  VERBOSE_LOG
1105 
1106  const Path fileName = paths.getNextPath(stats);
1107  Outcome dirResult = FileSystem::createDirectory(fileName.parentPath());
1108  if (!dirResult) {
1109  return makeUnexpected<Path>(
1110  "Cannot create directory " + fileName.parentPath().native() + ": " + dirResult.error());
1111  }
1112 
1113  try {
1114  std::ofstream of(fileName.native());
1115  of << R"(<VTKFile type="UnstructuredGrid" version="0.1" byte_order="LittleEndian">
1116  <UnstructuredGrid>
1117  <Piece NumberOfPoints=")"
1118  << storage.getParticleCnt() << R"(" NumberOfCells="0">
1119  <Points>
1120  <DataArray name="Position" type="Float32" NumberOfComponents="3" format="ascii">)"
1121  << "\n";
1123  for (Size i = 0; i < r.size(); ++i) {
1124  of << r[i] << "\n";
1125  }
1126  of << R"( </DataArray>
1127  </Points>
1128  <PointData Vectors="vector">)"
1129  << "\n";
1130 
1131  Array<AutoPtr<ITextColumn>> columns;
1132  addColumns(flags, columns);
1133 
1134  for (auto& column : columns) {
1135  writeDataArray(of, storage, stats, *column);
1136  }
1137 
1138  of << R"( </PointData>
1139  <Cells>
1140  <DataArray type="Int32" Name="connectivity" format="ascii">
1141  </DataArray>
1142  <DataArray type="Int32" Name="offsets" format="ascii">
1143  </DataArray>
1144  <DataArray type="UInt8" Name="types" format="ascii">
1145  </DataArray>
1146  </Cells>
1147  </Piece>
1148  </UnstructuredGrid>
1149 </VTKFile>)";
1150 
1151  return fileName;
1152  } catch (const std::exception& e) {
1153  return makeUnexpected<Path>("Cannot save file " + fileName.native() + ": " + e.what());
1154  }
1155 }
1156 
1157 // ----------------------------------------------------------------------------------------------------------
1158 // Hdf5Input
1159 // ----------------------------------------------------------------------------------------------------------
1160 
1161 #ifdef SPH_USE_HDF5
1162 
1163 template <typename T>
1164 Size typeDim;
1165 
1166 template <>
1167 Size typeDim<Float> = 1;
1168 template <>
1169 Size typeDim<Vector> = 3;
1170 
1171 template <typename T>
1172 T doubleToType(ArrayView<const double> data, const Size i);
1173 
1174 template <>
1175 Float doubleToType(ArrayView<const double> data, const Size i) {
1176  return Float(data[i]);
1177 }
1178 template <>
1179 Vector doubleToType(ArrayView<const double> data, const Size i) {
1180  return Vector(data[3 * i + 0], data[3 * i + 1], data[3 * i + 2]);
1181 }
1182 
1183 template <typename T>
1184 static void loadQuantity(const hid_t fileId,
1185  const std::string& label,
1186  const QuantityId id,
1187  const OrderEnum order,
1188  Storage& storage) {
1189  const hid_t hid = H5Dopen(fileId, label.c_str(), H5P_DEFAULT);
1190  if (hid < 0) {
1191  throw IoError("Cannot read " + getMetadata(id).quantityName + " data");
1192  }
1193  const Size particleCnt = storage.getParticleCnt();
1194  Array<double> data(typeDim<T> * particleCnt);
1195  H5Dread(hid, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, &data[0]);
1196  H5Dclose(hid);
1197 
1198  Array<T> values(particleCnt);
1199  for (Size i = 0; i < particleCnt; ++i) {
1200  values[i] = doubleToType<T>(data, i);
1201  }
1202  switch (order) {
1203  case OrderEnum::ZERO:
1204  storage.insert<T>(id, OrderEnum::ZERO, std::move(values));
1205  break;
1206  case OrderEnum::FIRST:
1207  storage.getDt<T>(id) = std::move(values);
1208  break;
1209  case OrderEnum::SECOND:
1210  storage.getD2t<T>(id) = std::move(values);
1211  break;
1212  default:
1214  }
1215 }
1216 
1217 Outcome Hdf5Input::load(const Path& path, Storage& storage, Statistics& stats) {
1218  const hid_t fileId = H5Fopen(path.native().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
1219  if (fileId < 0) {
1220  return makeFailed("Cannot open file '", path.native(), "'");
1221  }
1222 
1224 
1225  const hid_t posId = H5Dopen(fileId, "/x", H5P_DEFAULT);
1226  if (posId < 0) {
1227  return makeFailed("Cannot read position data from file '", path.native(), "'");
1228  }
1229  const hid_t dspace = H5Dget_space(posId);
1230  const Size ndims = H5Sget_simple_extent_ndims(dspace);
1231  Array<hsize_t> dims(ndims);
1232  H5Sget_simple_extent_dims(dspace, &dims[0], nullptr);
1233  const Size particleCnt = dims[0];
1234  H5Dclose(posId);
1236 
1237  const hid_t timeId = H5Dopen(fileId, "/time", H5P_DEFAULT);
1238  if (timeId < 0) {
1239  return makeFailed("Cannot read simulation time from file '", path.native(), "'");
1240  }
1241  double runTime;
1242  H5Dread(timeId, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, &runTime);
1243  H5Dclose(timeId);
1244  stats.set(StatisticsId::RUN_TIME, Float(runTime));
1245 
1246  try {
1247  loadQuantity<Vector>(fileId, "/x", QuantityId::POSITION, OrderEnum::ZERO, storage);
1248  loadQuantity<Vector>(fileId, "/v", QuantityId::POSITION, OrderEnum::FIRST, storage);
1249  loadQuantity<Float>(fileId, "/m", QuantityId::MASS, OrderEnum::ZERO, storage);
1250  loadQuantity<Float>(fileId, "/p", QuantityId::PRESSURE, OrderEnum::ZERO, storage);
1251  loadQuantity<Float>(fileId, "/rho", QuantityId::DENSITY, OrderEnum::ZERO, storage);
1252  loadQuantity<Float>(fileId, "/e", QuantityId::ENERGY, OrderEnum::ZERO, storage);
1253  loadQuantity<Float>(fileId, "/sml", QuantityId::SMOOTHING_LENGTH, OrderEnum::ZERO, storage);
1254  } catch (const IoError& e) {
1255  return makeFailed("Cannot read file '", path.native(), "'.\n", e.what());
1256  }
1257 
1258  // copy the smoothing lengths
1261  for (Size i = 0; i < particleCnt; ++i) {
1262  r[i][H] = h[i];
1263  }
1264  return SUCCESS;
1265 }
1266 
1267 
1268 #else
1269 
1271  return makeFailed("HDF5 support not enabled. Please rebuild the code with CONFIG+=use_hdf5.");
1272 }
1273 #endif
1274 
1275 // ----------------------------------------------------------------------------------------------------------
1276 // MpcorpInput
1277 // ----------------------------------------------------------------------------------------------------------
1278 
1279 static Float computeRadius(const Float H, const Float albedo) {
1280  // https://cneos.jpl.nasa.gov/tools/ast_size_est.html
1281  const Float d = exp10(3.1236 - 0.5 * log10(albedo) - 0.2 * H);
1282  return 0.5_f * d * 1.e3_f;
1283 }
1284 
1285 static void parseMpcorp(std::ifstream& ifs, Storage& storage, const Float rho, const Float albedo) {
1286  std::string line;
1287  // skip header
1288  while (std::getline(ifs, line)) {
1289  if (line.size() >= 5 && line.substr(0, 5) == "-----") {
1290  break;
1291  }
1292  }
1293 
1294  std::string dummy;
1295  Array<Vector> positions, velocities;
1296  Array<Float> masses;
1297  Array<Size> flags;
1298  while (std::getline(ifs, line)) {
1299  if (line.empty()) {
1300  continue;
1301  }
1302  std::stringstream ss(line);
1303  Float mag;
1304  ss >> dummy >> mag >> dummy >> dummy;
1305  if (!ss.good()) {
1306  continue;
1307  }
1308  Float M, omega, Omega, I, e, n, a;
1309  ss >> M >> omega >> Omega >> I >> e >> n >> a;
1310  M *= DEG_TO_RAD;
1311  omega *= DEG_TO_RAD;
1312  Omega *= DEG_TO_RAD;
1313  I *= DEG_TO_RAD;
1314  a *= Constants::au;
1315  n *= DEG_TO_RAD / Constants::day;
1316  std::string flag;
1317  ss >> dummy >> dummy >> dummy >> dummy >> dummy >> dummy >> dummy >> dummy >> dummy >> flag;
1318 
1319  const Float E = Kepler::solveKeplersEquation(M, e);
1320  const AffineMatrix R_Omega = AffineMatrix::rotateZ(Omega);
1321  const AffineMatrix R_I = AffineMatrix::rotateX(I);
1322  const AffineMatrix R_omega = AffineMatrix::rotateZ(omega);
1323  const AffineMatrix R = R_Omega * R_I * R_omega;
1324 
1325  Vector r = a * R * Vector(cos(E) - e, sqrt(1 - sqr(e)) * sin(E), 0);
1326  SPH_ASSERT(isReal(r), r);
1327  Vector v = a * R * n / (1 - e * cos(E)) * Vector(-sin(E), sqrt(1 - sqr(e)) * cos(E), 0);
1328  SPH_ASSERT(isReal(v), v);
1329  r[H] = computeRadius(mag, albedo);
1330  v[H] = 0._f;
1331  positions.push(r);
1332  velocities.push(v);
1334  const Float m = sphereVolume(r[H]) * rho;
1335  masses.push(m);
1336 
1337  if (std::isdigit(flag.back())) {
1338  flags.push(flag.back() - '0');
1339  } else {
1340  flags.push(0);
1341  }
1342  }
1343 
1344  storage.insert<Vector>(QuantityId::POSITION, OrderEnum::SECOND, std::move(positions));
1345  storage.getDt<Vector>(QuantityId::POSITION) = std::move(velocities);
1346  storage.insert<Float>(QuantityId::MASS, OrderEnum::ZERO, std::move(masses));
1347  storage.insert<Size>(QuantityId::FLAG, OrderEnum::ZERO, std::move(flags));
1348 }
1349 
1350 Outcome MpcorpInput::load(const Path& path, Storage& storage, Statistics& UNUSED(stats)) {
1351  try {
1352  std::ifstream ifs(path.native());
1353  if (!ifs) {
1354  return makeFailed("Failed to open file '", path.native(), "'");
1355  }
1356  parseMpcorp(ifs, storage, rho, albedo);
1357  return SUCCESS;
1358  } catch (const std::exception& e) {
1359  return makeFailed("Cannot load file '", path.native(), "'\n", e.what());
1360  }
1361 }
1362 
1363 
1364 // ----------------------------------------------------------------------------------------------------------
1365 // PkdgravOutput/Input
1366 // ----------------------------------------------------------------------------------------------------------
1367 
1368 PkdgravOutput::PkdgravOutput(const OutputFile& fileMask, PkdgravParams&& params)
1369  : IOutput(fileMask)
1370  , params(std::move(params)) {
1371  SPH_ASSERT(almostEqual(this->params.conversion.velocity, 2.97853e4_f, 1.e-4_f));
1372 }
1373 
1374 Expected<Path> PkdgravOutput::dump(const Storage& storage, const Statistics& stats) {
1375  const Path fileName = paths.getNextPath(stats);
1377 
1378  ArrayView<const Float> m, rho, u;
1380  ArrayView<const Vector> r, v, dv;
1381  tie(r, v, dv) = storage.getAll<Vector>(QuantityId::POSITION);
1383 
1384  std::ofstream ofs(fileName.native());
1385  ofs << std::setprecision(PRECISION) << std::scientific;
1386 
1387  Size idx = 0;
1388  for (Size i = 0; i < r.size(); ++i) {
1389  if (u[i] > params.vaporThreshold) {
1390  continue;
1391  }
1392  const Float radius = this->getRadius(r[idx][H], m[idx], rho[idx]);
1393  const Vector v_in = v[idx] + cross(params.omega, r[idx]);
1394  SPH_ASSERT(flags[idx] < params.colors.size(), flags[idx], params.colors.size());
1395  ofs << std::setw(25) << idx << //
1396  std::setw(25) << idx << //
1397  std::setw(25) << m[idx] / params.conversion.mass << //
1398  std::setw(25) << radius / params.conversion.distance << //
1399  std::setw(25) << r[idx] / params.conversion.distance << //
1400  std::setw(25) << v_in / params.conversion.velocity << //
1401  std::setw(25) << Vector(0._f) /* zero initial rotation */ << //
1402  std::setw(25) << params.colors[flags[idx]] << std::endl;
1403  idx++;
1404  }
1405  return fileName;
1406 }
1407 
1408 Outcome PkdgravInput::load(const Path& path, Storage& storage, Statistics& stats) {
1409  TextInput input(EMPTY_FLAGS);
1410 
1411  // 1) Particle index -- we don't really need that, just add dummy columnm
1412  class DummyColumn : public ITextColumn {
1413  private:
1414  ValueEnum type;
1415 
1416  public:
1417  DummyColumn(const ValueEnum type)
1418  : type(type) {}
1419 
1420  virtual Dynamic evaluate(const Storage&, const Statistics&, const Size) const override {
1422  }
1423 
1424  virtual void accumulate(Storage&, const Dynamic, const Size) const override {}
1425 
1426  virtual std::string getName() const override {
1427  return "dummy";
1428  }
1429 
1430  virtual ValueEnum getType() const override {
1431  return type;
1432  }
1433  };
1434  input.addColumn(makeAuto<DummyColumn>(ValueEnum::INDEX));
1435 
1436  // 2) Original index -- not really needed, skip
1437  input.addColumn(makeAuto<DummyColumn>(ValueEnum::INDEX));
1438 
1439  // 3) Particle mass
1440  input.addColumn(makeAuto<ValueColumn<Float>>(QuantityId::MASS));
1441 
1442  // 4) radius ? -- skip
1443  input.addColumn(makeAuto<ValueColumn<Float>>(QuantityId::DENSITY));
1444 
1445  // 5) Positions (3 components)
1447 
1448  // 6) Velocities (3 components)
1450 
1451  // 7) Angular velocities (3 components)
1453 
1454  // 8) Color index -- skip
1455  input.addColumn(makeAuto<DummyColumn>(ValueEnum::INDEX));
1456 
1457  Outcome outcome = input.load(path, storage, stats);
1458 
1459  if (!outcome) {
1460  return outcome;
1461  }
1462 
1463  // whole code assumes positions is a 2nd order quantity, so we have to add the acceleration
1466 
1467  // Convert units -- assuming default conversion values
1468  PkdgravParams::Conversion conversion;
1474 
1475  for (Size i = 0; i < r.size(); ++i) {
1476  r[i] *= conversion.distance;
1477  v[i] *= conversion.velocity;
1478  m[i] *= conversion.mass;
1479 
1480  // compute radius, using the density formula
1482  rho[i] *= conversion.distance;
1483  r[i][H] = root<3>(3._f * m[i] / (2700._f * 4._f * PI));
1484 
1485  // replace the radius with actual density
1487  rho[i] = m[i] / pow<3>(rho[i]);
1488 
1489  omega[i] *= conversion.velocity / conversion.distance;
1490  }
1492  // sort
1493  Order order(r.size());
1494  order.shuffle([&m](const Size i1, const Size i2) { return m[i1] > m[i2]; });
1495  r = order.apply(r);
1496  v = order.apply(v);
1497  m = order.apply(m);
1498  rho = order.apply(rho);
1499  omega = order.apply(omega);
1500 
1501  return SUCCESS;
1502 }
1503 
1504 // ----------------------------------------------------------------------------------------------------------
1505 // TabInput
1506 // ----------------------------------------------------------------------------------------------------------
1507 
1509  input = makeAuto<TextInput>(
1511 }
1512 
1513 TabInput::~TabInput() = default;
1514 
1515 Outcome TabInput::load(const Path& path, Storage& storage, Statistics& stats) {
1516  Outcome result = input->load(path, storage, stats);
1517  if (!result) {
1518  return result;
1519  }
1520 
1523  for (Size i = 0; i < r.size(); ++i) {
1524  r[i][H] = 1.e-5_f;
1525  }
1526 
1527  return SUCCESS;
1528 }
1529 
INLINE bool almostEqual(const AffineMatrix &m1, const AffineMatrix &m2, const Float eps=EPS)
Definition: AffineMatrix.h:278
INLINE bool isReal(const AntisymmetricTensor &t)
#define SPH_ASSERT(x,...)
Definition: Assert.h:94
#define STOP
Definition: Assert.h:106
#define NOT_IMPLEMENTED
Helper macro marking missing implementation.
Definition: Assert.h:100
INLINE AutoPtr< T > makeAuto(TArgs &&... args)
Definition: AutoPtr.h:124
NAMESPACE_SPH_BEGIN
Definition: BarnesHut.cpp:13
Object for printing quantity values into output.
const float radius
Definition: CurveDialog.cpp:18
const EmptyFlags EMPTY_FLAGS
Definition: Flags.h:16
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
constexpr int PRECISION
Number of valid digits of numbers on output.
Definition: Globals.h:25
Base class for all particle materials.
Logging routines of the run.
#define VERBOSE_LOG
Helper macro, creating.
Definition: Logger.h:242
constexpr INLINE T max(const T &f1, const T &f2)
Definition: MathBasic.h:20
constexpr INLINE T sqr(const T &f) noexcept
Return a squared value.
Definition: MathUtils.h:67
INLINE T sin(const T f)
Definition: MathUtils.h:296
INLINE T cos(const T f)
Definition: MathUtils.h:291
INLINE T sqrt(const T f)
Return a squared root of a value.
Definition: MathUtils.h:78
constexpr INLINE Float pow< 3 >(const Float v)
Definition: MathUtils.h:132
constexpr INLINE Float sphereVolume(const Float radius)
Computes a volume of a sphere given its radius.
Definition: MathUtils.h:375
constexpr Float DEG_TO_RAD
Definition: MathUtils.h:369
INLINE T log10(const T f)
Definition: MathUtils.h:331
INLINE Float root< 3 >(const Float f)
Definition: MathUtils.h:105
INLINE T exp10(const T f)
Definition: MathUtils.h:326
constexpr Float E
Definition: MathUtils.h:365
constexpr Float PI
Mathematical constants.
Definition: MathUtils.h:361
#define UNUSED(x)
Definition: Object.h:37
#define MARK_USED(x)
Silences the "unused variable" warning for given variable.
Definition: Object.h:42
#define NAMESPACE_SPH_END
Definition: Object.h:12
const NothingType NOTHING
Definition: Optional.h:16
Helper object defining permutation.
const SuccessTag SUCCESS
Global constant for successful outcome.
Definition: Outcome.h:141
INLINE Outcome makeFailed(TArgs &&... args)
Constructs failed object with error message.
Definition: Outcome.h:157
const int MAGIC_NUMBER
Definition: Output.cpp:853
void setEnumIndex(const BodySettings &settings, const BodySettingsId paramId, EnumWrapper &entry)
Definition: Output.cpp:616
Saving and loading particle data.
@ STRAIN_RATE_CORRECTION_TENSOR
Symmetric tensor correcting kernel gradient for linear consistency.
@ DEVIATORIC_STRESS
Deviatoric stress tensor, always a traceless tensor stored in components xx, yy, xy,...
@ PRESSURE
Pressure, reduced by yielding and fracture model (multiplied by 1-damage); always a scalar quantity.
@ VELOCITY
Current velocities of particles, always a vector quantity.
@ DAMAGE
Damage, reducing the pressure and deviatoric stress.
@ POSITION
Positions of particles, always a vector quantity.
@ ENERGY
Specific internal energy, always a scalar quantity.
@ SMOOTHING_LENGTH
Smoothing lenghts of particles.
@ ANGULAR_FREQUENCY
Angular frequency of particles, used in N-body simulations.
@ DENSITY
Density, always a scalar quantity.
@ INDEX
Index of particle.
@ MASS
Particle masses, always a scalar quantity.
@ MATERIAL_ID
ID of material, indexed from 0 to (#bodies - 1).
RunTypeEnum
Type of simulation, stored as metadata in the binary file.
Definition: Output.h:237
BinaryIoVersion
Definition: Output.h:258
@ V2021_03_20
added wallclock time and build date
@ V2018_10_24
reverted enum (storing zero instead of hash), storing type of simulation
CompressedIoVersion
Definition: Output.h:408
CompressionEnum
Definition: Output.h:412
ValueEnum
@ SYMMETRIC_TENSOR
@ TRACELESS_TENSOR
decltype(auto) dispatch(const ValueEnum value, TVisitor &&visitor, TArgs &&... args)
Selects type based on run-time ValueEnum value and runs visit<Type>() method of the visitor.
QuantityMetadata getMetadata(const QuantityId key)
Returns the quantity information using quantity ID.
Definition: QuantityIds.cpp:27
QuantityId
Unique IDs of basic quantities of SPH particles.
Definition: QuantityIds.h:19
@ FLAG
ID of original body, used to implement discontinuities between bodies in SPH.
@ STRAIN_RATE_CORRECTION_TENSOR
Correction tensor used to improve conservation of total angular momentum.
@ DEVIATORIC_STRESS
Deviatoric stress tensor, always a traceless tensor.
@ PRESSURE
Pressure, affected by yielding and fragmentation model, always a scalar quantity.
@ 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.
@ MATERIAL_ID
Index of material of the particle. Can be generally different than the flag value.
OrderEnum
Definition: Quantity.h:15
@ SECOND
Quantity with 1st and 2nd derivative.
@ FIRST
Quantity with 1st derivative.
@ ZERO
Quantity without derivatives, or "zero order" of quantity.
Data serialization and deserialization.
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
@ WALLCLOCK_TIME
Current wallclock duration of the simulation.
@ RUN_TIME
Current time of the simulation in code units. Does not necessarily have to be 0 when run starts.
@ TIMESTEP_VALUE
Current value of timestep.
decltype(auto) INLINE forValue(Variant< TArgs... > &variant, TFunctor &&functor)
Definition: Variant.h:428
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
@ Y
Definition: Vector.h:23
@ X
Definition: Vector.h:22
@ Z
Definition: Vector.h:24
static AffineMatrix rotateX(const Float angle)
Definition: AffineMatrix.h:141
static AffineMatrix rotateZ(const Float angle)
Definition: AffineMatrix.h:153
Object providing safe access to continuous memory of data.
Definition: ArrayView.h:17
INLINE TCounter size() const
Definition: ArrayView.h:101
Generic dynamically allocated resizable storage.
Definition: Array.h:43
INLINE void push(U &&u)
Adds new element to the end of the array, resizing the array if necessary.
Definition: Array.h:306
void clear()
Removes all elements from the array, but does NOT release the memory.
Definition: Array.h:434
INLINE TCounter size() const noexcept
Definition: Array.h:193
INLINE bool empty() const noexcept
Definition: Array.h:201
Wrapper of pointer that deletes the resource from destructor.
Definition: AutoPtr.h:15
INLINE const TError & error() const
Returns the error message.
Definition: Outcome.h:88
virtual Outcome load(const Path &path, Storage &storage, Statistics &stats) override
Loads data from the file into the storage.
Definition: Output.cpp:718
Expected< Info > getInfo(const Path &path) const
Opens the file and reads header info without reading the rest of the file.
Definition: Output.cpp:814
virtual Expected< Path > dump(const Storage &storage, const Statistics &stats) override
Saves data from particle storage into the file.
Definition: Output.cpp:512
BinaryOutput(const OutputFile &fileMask, const RunTypeEnum runTypeId=RunTypeEnum::SPH)
Definition: Output.cpp:508
virtual Outcome load(const Path &path, Storage &storage, Statistics &stats) override
Loads data from the file into the storage.
Definition: Output.cpp:977
Expected< Info > getInfo(const Path &path) const
Definition: Output.cpp:1030
CompressedOutput(const OutputFile &fileMask, const CompressionEnum compression, const RunTypeEnum runTypeId=RunTypeEnum::SPH)
Definition: Output.cpp:846
virtual Expected< Path > dump(const Storage &storage, const Statistics &stats) override
Saves data from particle storage into the file.
Definition: Output.cpp:933
Returns first derivatives of given quantity as stored in storage.
Definition: Column.h:115
Object for reading serialized primitives from input stream.
Definition: Serializer.h:124
void skip(const Size size)
Skip a number of bytes in the stream; used to skip unused parameters or padding bytes.
Definition: Serializer.h:149
void read(TArgs &... args)
Definition: Serializer.h:144
Convenient object for storing a single value of different types.
Definition: Dynamic.h:35
static std::string toString(const TEnum value)
Definition: EnumMap.h:67
Generic exception.
Definition: Exceptions.h:10
virtual const char * what() const noexcept
Definition: Exceptions.h:18
Wrapper of type that either contains a value of given type, or an error message.
Definition: Expected.h:25
const Error & error() const
Returns the error message.
Definition: Expected.h:94
Type & value()
Returns the reference to expected value.
Definition: Expected.h:69
INLINE void unset(const TEnum flag)
Removed a single flag.
Definition: Flags.h:102
constexpr INLINE bool has(const TEnum flag) const
Checks if the object has a given flag.
Definition: Flags.h:77
virtual Expected< Path > dump(const Storage &storage, const Statistics &stats) override
Saves data from particle storage into the file.
Definition: Output.cpp:339
virtual Outcome load(const Path &path, Storage &storage, Statistics &stats) override
Loads data from the file into the storage.
Definition: Output.cpp:1253
void setRange(const QuantityId id, const Interval &range, const Float minimal)
Sets the timestepping parameters of given quantity.
Definition: IMaterial.cpp:17
Interface for saving quantities of SPH particles to a file.
Definition: Output.h:76
OutputFile paths
Definition: Output.h:78
IOutput(const OutputFile &fileMask)
Constructs output given the file name of the output.
Definition: Output.cpp:98
Base class for conversion of quantities into the output data.
Definition: Column.h:30
virtual std::string getName() const =0
Returns a name of the column.
virtual ValueEnum getType() const =0
Returns the value type of the column.
virtual Dynamic evaluate(const Storage &storage, const Statistics &stats, const Size particleIdx) const =0
Returns the value of the output column for given particle.
INLINE IndexIterator begin() const
INLINE IndexIterator end() const
INLINE Size size() const
Object representing a 1D interval of real numbers.
Definition: Interval.h:17
INLINE Float lower() const
Returns lower bound of the interval.
Definition: Interval.h:74
INLINE Float upper() const
Returns upper bound of the interval.
Definition: Interval.h:79
static Interval unbounded()
Returns an unbounded (infinite) interval.
Definition: Interval.h:109
Exception thrown when file cannot be read, it has invalid format, etc.
Definition: Exceptions.h:31
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 Outcome load(const Path &path, Storage &storage, Statistics &stats) override
Loads data from the file into the storage.
Definition: Output.cpp:1333
Permutation, i.e. (discrete) invertible function int->int.
Definition: Order.h:18
Helper file generating file names for output files.
Definition: Output.h:21
static Optional< Size > getDumpIdx(const Path &path)
Extracts the dump index from given path generated by OutputFile.
Definition: Output.cpp:49
Path getNextPath(const Statistics &stats) const
Returns path to the next output file.
Definition: Output.cpp:28
Path getMask() const
Returns the file mask as given in constructor.
Definition: Output.cpp:94
static Optional< OutputFile > getMaskFromPath(const Path &path, const Size firstDumpIdx=0)
Attemps to get the OutputFile from one of the path generated from it.
Definition: Output.cpp:72
bool hasWildcard() const
Returns true if the file mask contains (at least one) wildcard.
Definition: Output.cpp:89
OutputFile()=default
Object representing a path on a filesystem.
Definition: Path.h:17
Path & removeExtension()
Removes the extension from the path.
Definition: Path.cpp:100
std::string native() const
Returns the native version of the path.
Definition: Path.cpp:71
bool empty() const
Checks if the path is empty.
Definition: Path.cpp:10
Path fileName() const
Returns the filename of the path.
Definition: Path.cpp:47
Path parentPath() const
Returns the parent directory. If the path is empty or root, return empty path.
Definition: Path.cpp:35
virtual Outcome load(const Path &path, Storage &storage, Statistics &stats) override
Loads data from the file into the storage.
Definition: Output.cpp:1391
virtual Expected< Path > dump(const Storage &storage, const Statistics &stats) override
Saves data from particle storage into the file.
Definition: Output.cpp:1357
PkdgravOutput(const OutputFile &fileMask, PkdgravParams &&params)
Definition: Output.cpp:1351
Generic container for storing scalar, vector or tensor quantity and its derivatives.
Definition: Quantity.h:200
void setOrder(const OrderEnum order)
Definition: Quantity.h:297
OrderEnum getOrderEnum() const
Returns the order of the quantity.
Definition: Quantity.h:241
StaticArray< Array< TValue > &, 3 > getAll()
Returns all buffers of given type stored in this quantity.
Definition: Quantity.h:338
ValueEnum getValueEnum() const
Returns the value order of the quantity.
Definition: Quantity.h:246
Exception thrown by Deserializer on failure.
Definition: Serializer.h:105
virtual const char * what() const noexcept override
Definition: Serializer.h:117
Object providing serialization of primitives into a stream.
Definition: Serializer.h:47
void write(const TArgs &... args)
Definition: Serializer.h:57
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
static const Settings & getDefaults()
\brief Returns a reference to object containing default values of all settings.
Settings & set(const TEnum idx, TValue &&value, std::enable_if_t<!std::is_enum< std::decay_t< TValue >>::value, int >=0)
Saves a value into the settings.
Definition: Settings.h:226
bool hasType(const TEnum idx) const
Checks if the given entry has specified type.
Definition: Settings.h:422
Array with fixed number of allocated elements.
Definition: StaticArray.h:19
Object holding various statistics about current run.
Definition: Statistics.h:22
TValue get(const StatisticsId idx) const
Returns value of a statistic.
Definition: Statistics.h:88
Statistics & set(const StatisticsId idx, TValue &&value)
Sets new values of a statistic.
Definition: Statistics.h:52
TValue getOr(const StatisticsId idx, const TValue &other) const
Returns value of a statistic, or a given value if the statistic is not stored.
Definition: Statistics.h:98
bool has(const StatisticsId idx) const
Checks if the object contains a statistic with given ID.
Definition: Statistics.h:44
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
Quantity & getQuantity(const QuantityId key)
Retrieves quantity with given key from the storage.
Definition: Storage.cpp:150
Outcome isValid(const Flags< ValidFlag > flags=ValidFlag::COMPLETE) const
Checks whether the storage is in valid state.
Definition: Storage.cpp:609
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
StorageSequence getQuantities()
Returns the sequence of quantities.
Definition: Storage.cpp:416
auto getValues(const QuantityId first, const QuantityId second, const TArgs... others)
Retrieves an array of quantities from the key.
Definition: Storage.h:359
Size getQuantityCnt() const
Returns the number of stored quantities.
Definition: Storage.cpp:441
Array< TValue > & getD2t(const QuantityId key)
Retrieves a quantity second derivative from the storage, given its key and value type.
Definition: Storage.cpp:243
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
Array< TValue > & getValue(const QuantityId key)
Retrieves a quantity values from the storage, given its key and value type.
Definition: Storage.cpp:191
Symmetric tensor of 2nd order.
TabInput()
Definition: Output.cpp:1491
virtual Outcome load(const Path &path, Storage &storage, Statistics &stats) override
Loads data from the file into the storage.
Definition: Output.cpp:1498
Generic 2nd-order tensor with 9 independent components.
Definition: Tensor.h:13
Input for the text file, generated by TextOutput or conforming to the same format.
Definition: Output.h:202
virtual Outcome load(const Path &path, Storage &storage, Statistics &stats) override
Loads data from the file into the storage.
Definition: Output.cpp:260
TextInput & addColumn(AutoPtr< ITextColumn > &&column)
Definition: Output.cpp:330
TextInput(Flags< OutputQuantityFlag > quantities)
Definition: Output.cpp:256
Output saving data to text (human readable) file.
Definition: Output.h:152
TextOutput(const OutputFile &fileMask, const std::string &runName, Flags< OutputQuantityFlag > quantities, Flags< Options > options=EMPTY_FLAGS)
Creates a new text file output.
Definition: Output.cpp:180
virtual Expected< Path > dump(const Storage &storage, const Statistics &stats) override
Saves data from particle storage into the file.
Definition: Output.cpp:192
@ DUMP_ALL
Dumps all quantity values from the storage; this overrides the list of selected particles.
@ SCIENTIFIC
Writes all numbers in scientific format.
TextOutput & addColumn(AutoPtr< ITextColumn > &&column)
Adds a new column to be saved into the file.
Definition: Output.cpp:251
Symmetric traceless 2nd order tensor.
Returns values of given quantity as stored in storage.
Definition: Column.h:73
VtkOutput(const OutputFile &fileMask, const Flags< OutputQuantityFlag > flags)
Definition: Output.cpp:1095
virtual Expected< Path > dump(const Storage &storage, const Statistics &stats) override
Saves data from particle storage into the file.
Definition: Output.cpp:1103
Creating code components based on values from settings.
BodySettingsId
Settings of a single body / gas phase / ...
Definition: Settings.h:1394
constexpr Float au
Astronomical unit (exactly)
Definition: Constants.h:38
constexpr Float day
Number of seconds in a day.
Definition: Constants.h:44
AutoPtr< IMaterial > getMaterial(const BodySettings &settings)
Definition: Factory.cpp:508
Outcome createDirectory(const Path &path, const Flags< CreateDirectoryFlag > flags=CreateDirectoryFlag::ALLOW_EXISTING)
Creates a directory with given path. Creates all parent directories as well.
Definition: FileSystem.cpp:119
Float solveKeplersEquation(const Float M, const Float e, const Size iterCnt=10)
Computes the eccentric anomaly by solving the Kepler's equation.
Definition: TwoBody.cpp:62
Overload of std::swap for Sph::Array.
Definition: Array.h:578
Optional< std::string > buildDate
Definition: Output.h:398
Size wallclockTime
Wallclock time of the snapshot (in seconds)
Definition: Output.h:385
Size particleCnt
Number of particles in the file.
Definition: Output.h:361
Float timeStep
Current timestep of the run.
Definition: Output.h:388
BinaryIoVersion version
Format version of the file.
Definition: Output.h:401
Optional< RunTypeEnum > runType
Definition: Output.h:393
Size materialCnt
Number of materials in the file.
Definition: Output.h:364
Float runTime
Run time of the snapshot.
Definition: Output.h:382
Size quantityCnt
Number of quantities in the file.
Definition: Output.h:358
Size particleCnt
Number of particles in the file.
Definition: Output.h:436
CompressedIoVersion version
Format version of the file.
Definition: Output.h:445
RunTypeEnum runType
Type of the simulation.
Definition: Output.h:442
Float runTime
Run time of the snapshot.
Definition: Output.h:439
void visit(QuantityId id, Array< AutoPtr< ITextColumn >> &columns)
Definition: Output.cpp:175
Wrapper of an enum.
Definition: Settings.h:37
int value
Definition: Settings.h:38
EnumIndex index
Definition: Settings.h:40
Conversion factors for pkdgrav.
Definition: Output.h:504
struct PkdgravParams::Conversion conversion
Float vaporThreshold
Definition: Output.h:520
Array< Size > colors
Definition: Output.h:524
Vector omega
Definition: Output.h:516