GCC Code Coverage Report


Directory: ./
File: src/output.cpp
Date: 2024-04-18 12:22:13
Exec Total Coverage
Lines: 48 346 13.9%
Functions: 8 26 30.8%
Branches: 28 102 27.5%

Line Branch Exec Source
1 #include <H5Cpp.h>
2 #include <ctime>
3 #include <exception>
4 #include <filesystem>
5 #include <fstream>
6 #include <iostream>
7 #include <limits>
8 #include <string>
9
10 #include "integrator.hpp"
11 #include "multiindex.hpp"
12 #include "observables.hpp"
13 #include "output.hpp"
14 #include "particle.hpp"
15 #include "simulation.hpp"
16 #include "system.hpp"
17 #include "timeseries.hpp"
18 #include "trajectories.hpp"
19 #include "version.hpp"
20
21 1 Teebuf::Teebuf(std::streambuf *sbuf1, std::streambuf *sbuf2) : sbuf1(sbuf1), sbuf2(sbuf2) {}
22
23 798 int Teebuf::overflow(int c) {
24
1/2
✓ Branch 0 taken 798 times.
✗ Branch 1 not taken.
798 if (c != std::char_traits<char>::eof()) {
25 798 this->sbuf1->sputc(c);
26 798 this->sbuf2->sputc(c);
27 }
28 798 return std::char_traits<char>::not_eof(c);
29 }
30
31 3 int Teebuf::sync() {
32 3 const int r1 = sbuf1->pubsync();
33 3 const int r2 = sbuf2->pubsync();
34 3 return r1 == 0 && r2 == 0 ? 0 : 1;
35 }
36
37 1 Output::Output(const std::filesystem::path &outputPath, bool dryrun)
38
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 : outputPath(outputPath), dryrun(dryrun) {
39
2/5
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 9 not taken.
1 if (std::filesystem::exists(this->outputPath) && !std::filesystem::is_empty(this->outputPath)) {
40 throw std::invalid_argument("Output path " + outputPath.string() + " already exists.");
41 }
42 1 time_t walltimeBegin = time(0);
43 1 char timestamp[20];
44 1 strftime(timestamp, sizeof(timestamp), "%Y-%m-%d_%H-%M-%S", localtime(&walltimeBegin));
45
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (this->outputPath.empty()) {
46 this->outputPath = this->dryrun ? std::filesystem::temp_directory_path() : "results";
47 this->outputPath /= std::string(timestamp);
48 }
49
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 std::filesystem::create_directories(this->outputPath);
50
3/6
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
2 this->logFile = std::ofstream(this->outputPath / "log.txt");
51
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 this->tee = new Teebuf(this->logFile.rdbuf(), std::clog.rdbuf());
52
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 this->clogbuf = std::clog.rdbuf(tee);
53 1 std::cout << std::boolalpha;
54 1 std::clog << std::boolalpha;
55 // print version info after clog is tee'd to also have this info in log.txt
56
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 std::clog << programInfo();
57
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 std::clog << "\nBegin of run: " << timestamp;
58
3/6
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
1 std::clog << "\nOutput path: " << this->outputPath.string();
59 1 try {
60
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 std::filesystem::remove("latest");
61
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 std::filesystem::create_directory_symlink(this->outputPath, "latest");
62 } catch (const std::filesystem::filesystem_error &e) {
63 std::clog << "\nWARNING: Could not create 'latest' symlink";
64 }
65 1 }
66
67 2 Output::~Output() {
68
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
2 if (dryrun) {
69 2 std::clog
70 2 << "\nWARNING: This was a dry-run. If no errors occured, you may run the actual simulation."
71 2 << std::endl;
72 2 std::filesystem::remove_all(outputPath);
73 }
74 2 std::clog.flush();
75 2 std::clog.rdbuf(clogbuf);
76
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
2 delete this->tee;
77 }
78
79 1 void Output::emitConfig(const YAML::Node &config) {
80
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 std::ofstream out(outputPath / "config.yaml");
81
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 out << config;
82 1 }
83
84 void Output::writeParticles(const std::vector<Particle> &particles, const std::string &filename) {
85 std::ofstream stream(outputPath / filename);
86 stream << std::fixed << std::setprecision(10);
87 for (auto &p : particles) {
88 for (int i = 0; i < p.r.size(); ++i) {
89 stream << p.r[i] << " ";
90 }
91 for (int i = 0; i < p.v.size(); ++i) {
92 stream << p.v[i] << " ";
93 }
94 stream << p.sigma << " ";
95 stream << p.m << " ";
96 stream << p.gamma << " ";
97 stream << p.species << "\n";
98 }
99 }
100
101 void Output::writeStats(Simulation *simulation, const std::string &filename) {
102 std::ofstream statsStream(outputPath / filename);
103 int col = 15;
104 statsStream << std::setw(5) << "stage" << std::setw(col) << "stepsMade" << std::setw(col)
105 << "tBefore" << std::setw(col) << "tAfter" << std::setw(col) << "walltime"
106 << "\n";
107 for (size_t i = 0; i < simulation->stageStats.size(); ++i) {
108 auto &s = simulation->stageStats[i];
109 int walltime = s.walltimeAfter ? difftime(s.walltimeAfter, s.walltimeBefore) : 0;
110 statsStream << std::setw(5) << i << std::setw(col) << s.stepsMade << std::setw(col) << s.tBefore
111 << std::setw(col) << s.tAfter << std::setw(col) << std::to_string(walltime) << "\n";
112 }
113 }
114
115 void Output::write(Simulation *simulation) {
116 unsigned int stage = simulation->stageCounter;
117 for (auto &o : simulation->observables) {
118 writeObservables(o, stage);
119 }
120 if (simulation->timeseries) {
121 writeTimeseries(simulation->timeseries, stage);
122 }
123 writeParticles(simulation->system->particles, "particlesLast.dat");
124 writeStats(simulation, "stats.dat");
125 }
126
127 void Output::writeTrajectoryFrame(Trajectories *trajectories, System *system, int stage) {
128 throw std::invalid_argument("Not implemented. Use output format H5MD for particle trajectories.");
129 }
130
131 1 H5MDOutput::H5MDOutput(const std::filesystem::path &outputPath, bool dryrun)
132
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 : Output(outputPath, dryrun) {}
133
134 4 H5MDOutput::~H5MDOutput() {
135
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
2 if (trajectoryWriter.joinable()) {
136 trajectoryWriter.join();
137 }
138 4 }
139
140 static H5::Group createOrOpen(H5::Group &parent, const std::string &name) {
141 return parent.nameExists(name) ? parent.openGroup(name) : parent.createGroup(name);
142 }
143
144 static H5::DataSet createOrOpen(H5::Group &group, const std::string &name, const H5::DataType &type,
145 const H5::DataSpace &dataspace) {
146 return group.nameExists(name) ? group.openDataSet(name)
147 : group.createDataSet(name, type, dataspace);
148 }
149
150 void H5MDOutput::write(Simulation *simulation) {
151 initFile(outputPath, std::to_string(simulation->stageCounter) + ".h5md");
152 Output::write(simulation);
153 file.flush(H5F_SCOPE_GLOBAL);
154 }
155
156 void H5MDOutput::writeMeta() {
157 H5::Group group = createOrOpen(file, "h5md");
158 // version
159 H5::Attribute version = group.createAttribute("version", H5::PredType::NATIVE_INT,
160 H5::DataSpace(1, std::vector<hsize_t>{2}.data()));
161 version.write(version.getDataType(), std::vector<int>{1, 0}.data());
162 // author
163 H5::Group author = createOrOpen(group, "author");
164 std::string authorNameData = "Florian Sammueller";
165 H5::Attribute authorName =
166 author.createAttribute("name", H5::StrType(H5::PredType::C_S1, H5T_VARIABLE),
167 H5::DataSpace(1, std::vector<hsize_t>{1}.data()));
168 authorName.write(authorName.getDataType(), authorNameData);
169 std::string emailData = "florian.sammueller@uni-bayreuth.de";
170 H5::Attribute email = author.createAttribute(
171 "email", H5::StrType(H5::PredType::C_S1, H5T_VARIABLE), H5::DataSpace());
172 email.write(email.getDataType(), emailData);
173 // creator
174 H5::Group creator = createOrOpen(group, "creator");
175 std::string creatorNameData = "MBD";
176 H5::Attribute creatorName = creator.createAttribute(
177 "name", H5::StrType(H5::PredType::C_S1, H5T_VARIABLE), H5::DataSpace());
178 creatorName.write(creatorName.getDataType(), creatorNameData);
179 std::string creatorVersionData = programInfo();
180 H5::Attribute creatorVersion = creator.createAttribute(
181 "version", H5::StrType(H5::PredType::C_S1, H5T_VARIABLE), H5::DataSpace());
182 creatorVersion.write(creatorVersion.getDataType(), creatorVersionData);
183 }
184
185 void H5MDOutput::initFile(const std::filesystem::path &path, const std::string &filename) {
186 if (trajectoryWriter.joinable()) {
187 trajectoryWriter.join();
188 }
189 if (!std::filesystem::exists(path / filename)) {
190 file.close(); // file could still point to an old one
191 file = H5::H5File(path / filename, H5F_ACC_TRUNC | H5F_ACC_SWMR_WRITE);
192 writeMeta(); // this is static info, so only write once
193 }
194 }
195
196 void H5MDOutput::writeObservables(Observables *observables, int stage) {
197 H5::Group observablesGroup = createOrOpen(file, "observables");
198 H5::Group group = createOrOpen(observablesGroup, observables->type());
199 std::vector<int> shapeTmp = observables->shape();
200 std::vector<hsize_t> shape(shapeTmp.begin(), shapeTmp.end());
201 std::vector<std::string> axisLabels = observables->axisLabels();
202 H5::Group coordinatesGroup;
203 if (shape.size()) {
204 coordinatesGroup = createOrOpen(group, "coordinates");
205 }
206 for (size_t axis = 0; axis < shape.size(); ++axis) {
207 std::vector<double> coordinates = observables->coordinates(axis);
208 H5::DataSet dataset =
209 createOrOpen(coordinatesGroup, std::to_string(axis), H5::PredType::NATIVE_DOUBLE,
210 H5::DataSpace(1, std::vector<hsize_t>{coordinates.size()}.data()));
211 dataset.write(coordinates.data(), dataset.getDataType());
212 if (!dataset.attrExists("label")) {
213 H5::Attribute axisLabel = dataset.createAttribute(
214 "label", H5::StrType(H5::PredType::C_S1, H5T_VARIABLE), H5::DataSpace());
215 axisLabel.write(axisLabel.getDataType(), axisLabels[axis]);
216 }
217 }
218 Multiindex<Eigen::Dynamic> speciesMultiindex(Eigen::Array<int, Eigen::Dynamic, 1>::Constant(
219 observables->numSpeciesIndices, observables->numSpecies));
220 for (const auto &key : observables->keys) {
221 for (auto species : speciesMultiindex) {
222 H5::Group speciesGroup = group;
223 for (int s = 0; s < species.size(); ++s) {
224 speciesGroup = createOrOpen(speciesGroup, std::to_string(species[s]));
225 }
226 H5::DataSet dataset = createOrOpen(speciesGroup, key, H5::PredType::NATIVE_DOUBLE,
227 H5::DataSpace(shape.size(), shape.data()));
228 dataset.write(observables->data(key, species).data(), dataset.getDataType());
229 }
230 }
231 }
232
233 void H5MDOutput::writeTimeseries(Timeseries *timeseries, int stage) {
234 // According to H5MD spec, timeseries data shall also be in the group "observables"
235 H5::Group observablesGroup = createOrOpen(file, "observables");
236 std::vector<double> &time = timeseries->time;
237 if (!observablesGroup.nameExists("timeseries")) { // init extendable dataspace
238 H5::Group timeseriesGroup = observablesGroup.createGroup("timeseries");
239 hsize_t dimsfChunk[1] = {1000}; // Might matter for performance
240 H5::DSetCreatPropList cparms;
241 cparms.setChunk(1, dimsfChunk);
242 double fillVal = std::numeric_limits<double>::quiet_NaN();
243 cparms.setFillValue(H5::PredType::NATIVE_DOUBLE, &fillVal);
244 hsize_t dimsfInitial[1] = {0};
245 hsize_t maxdimsf[1] = {H5S_UNLIMITED};
246 timeseriesGroup.createDataSet("time", H5::PredType::NATIVE_DOUBLE,
247 H5::DataSpace(1, dimsfInitial, maxdimsf), cparms);
248 for (const auto &[key, _] : timeseries->data) {
249 H5::Group group = timeseriesGroup.createGroup(key);
250 group.link(H5L_TYPE_HARD, "/observables/timeseries/time", "time");
251 group.createDataSet("value", H5::PredType::NATIVE_DOUBLE,
252 H5::DataSpace(1, dimsfInitial, maxdimsf), cparms);
253 }
254 }
255 H5::Group timeseriesGroup = observablesGroup.openGroup("timeseries");
256 H5::DataSet datasetTime = timeseriesGroup.openDataSet("time");
257 hsize_t dimsf[1] = {time.size()};
258 hsize_t offset[1];
259 datasetTime.getSpace().getSimpleExtentDims(offset);
260 hsize_t dimsfExtend[1] = {dimsf[0] - offset[0]};
261 datasetTime.extend(dimsf);
262 H5::DataSpace mExtend(1, dimsfExtend);
263 H5::DataSpace fExtendTime = datasetTime.getSpace();
264 fExtendTime.selectHyperslab(H5S_SELECT_SET, dimsfExtend, offset);
265 datasetTime.write(time.data() + offset[0], datasetTime.getDataType(), mExtend, fExtendTime);
266 for (const auto &[key, values] : timeseries->data) {
267 H5::Group group = timeseriesGroup.openGroup(key);
268 H5::DataSet datasetValue = group.openDataSet("value");
269 datasetValue.extend(dimsf);
270 H5::DataSpace fExtendValue = datasetValue.getSpace();
271 fExtendValue.selectHyperslab(H5S_SELECT_SET, dimsfExtend, offset);
272 datasetValue.write(values.data() + offset[0], datasetValue.getDataType(), mExtend,
273 fExtendValue);
274 }
275 }
276
277 void H5MDOutput::initParticlesGroup(Trajectories *trajectories, System *system) {
278 hsize_t dim = DIM;
279 hsize_t n = system->particles.size();
280 H5::Group particlesGroup = file.createGroup("particles");
281 // box
282 H5::Group boxGroup = particlesGroup.createGroup("box");
283 H5::Attribute dimension =
284 boxGroup.createAttribute("dimension", H5::PredType::NATIVE_INT, H5::DataSpace());
285 dimension.write(dimension.getDataType(), &dim);
286 std::vector<const char *> boundaryStrings = {"periodic", "periodic", "periodic"};
287 H5::Attribute boundary = boxGroup.createAttribute(
288 "boundary", H5::StrType(H5::PredType::C_S1, H5T_VARIABLE), H5::DataSpace(1, &dim));
289 boundary.write(boundary.getDataType(), boundaryStrings.data());
290 H5::Attribute edges =
291 boxGroup.createAttribute("edges", H5::PredType::NATIVE_DOUBLE, H5::DataSpace(1, &dim));
292 edges.write(edges.getDataType(), system->L.data());
293 // init trajectory props
294 // note that data pointer must be set before each use, see below in writeParticles()
295 trajectoryQuantities = {
296 // {name, {hdf5 datatype, dimension}}
297 {"position", {H5::PredType::NATIVE_DOUBLE, DIM}},
298 {"image", {H5::PredType::NATIVE_INT, DIM}},
299 {"velocity", {H5::PredType::NATIVE_DOUBLE, DIM}},
300 {"force", {H5::PredType::NATIVE_DOUBLE, DIM}},
301 {"Fext", {H5::PredType::NATIVE_DOUBLE, DIM}},
302 {"Fint", {H5::PredType::NATIVE_DOUBLE, DIM}},
303 {"rRand", {H5::PredType::NATIVE_DOUBLE, DIM}},
304 {"mass", {H5::PredType::NATIVE_DOUBLE, 1}},
305 {"species", {H5::PredType::NATIVE_INT, 1}},
306 };
307 // particle datasets
308 hsize_t dimsfChunk[1] = {1};
309 H5::DSetCreatPropList cparms;
310 cparms.setChunk(1, dimsfChunk);
311 hsize_t dimsfInitial[1] = {0};
312 hsize_t maxdimsf[1] = {H5S_UNLIMITED};
313 H5::DataSet datasetTime = particlesGroup.createDataSet(
314 "time", H5::PredType::NATIVE_DOUBLE, H5::DataSpace(1, dimsfInitial, maxdimsf), cparms);
315 for (const auto &qkey : trajectories->quantities) {
316 auto &props = trajectoryQuantities.at(qkey);
317 H5::Group group = particlesGroup.createGroup(qkey);
318 group.link(H5L_TYPE_HARD, "/particles/time", "time");
319 hsize_t qdimsfChunk[3] = {dimsfChunk[0], n, props.dim};
320 H5::DSetCreatPropList qcparms;
321 qcparms.setChunk(3, qdimsfChunk);
322 hsize_t qdimsfInitial[3] = {dimsfInitial[0], n, props.dim};
323 hsize_t qmaxdimsf[3] = {H5S_UNLIMITED, H5S_UNLIMITED, props.dim};
324 props.dataset = group.createDataSet("value", props.type,
325 H5::DataSpace(3, qdimsfInitial, qmaxdimsf), qcparms);
326 };
327 }
328
329 void H5MDOutput::writeTrajectoryFrame(Trajectories *trajectories, System *system, int stage) {
330 hsize_t n = system->particles.size();
331 if (trajectories->count == 0) {
332 initFile(outputPath, std::to_string(stage) + ".h5md");
333 initParticlesGroup(trajectories, system);
334 }
335 if (trajectoryWriter.joinable()) {
336 trajectoryWriter.join();
337 }
338 // copy trajectory quantities into contiguous buffers
339 trajectoryBuffers.r.reserve(n);
340 trajectoryBuffers.box.reserve(n);
341 trajectoryBuffers.v.reserve(n);
342 trajectoryBuffers.F.reserve(n);
343 trajectoryBuffers.Fext.reserve(n);
344 trajectoryBuffers.Fint.reserve(n);
345 trajectoryBuffers.rRand.reserve(n);
346 trajectoryBuffers.m.reserve(n);
347 trajectoryBuffers.species.reserve(n);
348 for (size_t i = 0; i < n; ++i) {
349 Particle &p = system->particles[i];
350 trajectoryBuffers.r[i] = p.r;
351 trajectoryBuffers.box[i] = p.box;
352 trajectoryBuffers.v[i] = p.v;
353 trajectoryBuffers.F[i] = p.F();
354 trajectoryBuffers.Fext[i] = p.Fext;
355 trajectoryBuffers.Fint[i] = p.Fint;
356 trajectoryBuffers.rRand[i] = p.rRand;
357 trajectoryBuffers.m[i] = p.m;
358 trajectoryBuffers.species[i] = p.species;
359 }
360 double t = system->t;
361 // this may invalidate the pointers, so set them again
362 trajectoryQuantities.at("position").data = trajectoryBuffers.r.data();
363 trajectoryQuantities.at("image").data = trajectoryBuffers.box.data();
364 trajectoryQuantities.at("velocity").data = trajectoryBuffers.v.data();
365 trajectoryQuantities.at("force").data = trajectoryBuffers.F.data();
366 trajectoryQuantities.at("Fext").data = trajectoryBuffers.Fext.data();
367 trajectoryQuantities.at("Fint").data = trajectoryBuffers.Fint.data();
368 trajectoryQuantities.at("rRand").data = trajectoryBuffers.rRand.data();
369 trajectoryQuantities.at("mass").data = trajectoryBuffers.m.data();
370 trajectoryQuantities.at("species").data = trajectoryBuffers.species.data();
371 // write to file
372 trajectoryWriter = std::thread([this, trajectories, n, t]() {
373 H5::Group particlesGroup = file.openGroup("particles");
374 hsize_t dimsf[1] = {(hsize_t)trajectories->count};
375 hsize_t offset[1] = {dimsf[0] - 1};
376 hsize_t dimsfExtend[1] = {1};
377 H5::DataSpace mExtend(1, dimsfExtend);
378 H5::DataSet datasetTime = particlesGroup.openDataSet("time");
379 datasetTime.extend(dimsf);
380 H5::DataSpace fExtendTime = datasetTime.getSpace();
381 fExtendTime.selectHyperslab(H5S_SELECT_SET, dimsfExtend, offset);
382 datasetTime.write(&t, datasetTime.getDataType(), mExtend, fExtendTime);
383 for (const auto &qkey : trajectories->quantities) {
384 const auto &props = trajectoryQuantities.at(qkey);
385 hsize_t qdimsf[3] = {dimsf[0], n, props.dim};
386 hsize_t qoffset[3] = {offset[0], 0, 0};
387 hsize_t qdimsfExtend[3] = {1, n, props.dim};
388 H5::DataSpace qmExtend(3, qdimsfExtend);
389 props.dataset.extend(qdimsf);
390 H5::DataSpace fExtendValue = props.dataset.getSpace();
391 fExtendValue.selectHyperslab(H5S_SELECT_SET, qdimsfExtend, qoffset);
392 props.dataset.write(props.data, props.dataset.getDataType(), qmExtend, fExtendValue);
393 }
394 });
395 }
396
397 ASCIIOutput::ASCIIOutput(const std::filesystem::path &outputPath, bool dryrun)
398 : Output(outputPath, dryrun) {}
399
400 void ASCIIOutput::writeObservables(Observables *observables, int stage) {
401 std::string filename = std::to_string(stage) + "_" + observables->type() + ".dat";
402 std::vector<int> shape = observables->shape();
403 std::vector<std::string> axisLabels = observables->axisLabels();
404 std::ofstream coordFile;
405 if (shape.size()) {
406 coordFile = std::ofstream(outputPath / (filename + ".coord"));
407 }
408 for (size_t d = 0; d < shape.size(); ++d) {
409 std::vector<double> coords = observables->coordinates(d);
410 coordFile << axisLabels[d] << ": ";
411 for (const double r : coords) {
412 coordFile << r << " ";
413 }
414 coordFile << "\n";
415 }
416 if (shape.size()) {
417 coordFile.close();
418 }
419 std::ofstream file(outputPath / filename, std::ios::trunc);
420 Eigen::Array<int, Eigen::Dynamic, 1> speciesIndexShape(observables->numSpeciesIndices);
421 speciesIndexShape.setConstant(observables->numSpecies);
422 Multiindex<Eigen::Dynamic> speciesMultiindex(speciesIndexShape);
423 for (const auto &key : observables->keys) {
424 for (int linSpecies = 0; linSpecies < speciesMultiindex.linsize; ++linSpecies) {
425 Eigen::Array<int, Eigen::Dynamic, 1> species = speciesMultiindex.lin2multi(linSpecies);
426 file << key;
427 for (int s = 0; s < species.size(); ++s) {
428 file << " " << species[s];
429 }
430 file << ":";
431 std::vector<double> &data = observables->data(key, species);
432 int datasize = data.size();
433 for (int i = 0; i < datasize; ++i) {
434 file << " " << data[i];
435 }
436 file << "\n";
437 }
438 }
439 file.close();
440 }
441
442 static size_t countLines(const std::string &filename) {
443 size_t offset = 0;
444 std::ifstream infile(filename);
445 std::string dump;
446 while (std::getline(infile, dump)) {
447 ++offset;
448 }
449 return offset;
450 }
451
452 void ASCIIOutput::writeTimeseries(Timeseries *timeseries, int stage) {
453 std::string filename = std::to_string(stage) + "_timeseries.dat";
454 size_t offset = 0;
455 std::ofstream file;
456 if (std::filesystem::exists(filename)) {
457 offset = countLines(outputPath / filename);
458 file.open(outputPath / filename, std::ios::app);
459 } else {
460 file.open(outputPath / filename, std::ios::out);
461 file << "time ";
462 for (const auto &[key, _] : timeseries->data) {
463 file << key << " ";
464 }
465 file << "\n";
466 }
467 for (size_t i = offset; i < timeseries->time.size(); ++i) {
468 file << timeseries->time[i] << " ";
469 for (const auto &[_, data] : timeseries->data) {
470 file << data[i] << " ";
471 }
472 file << "\n";
473 }
474 file.close();
475 }
476