diff --git a/examples/all-options.xml b/examples/all-options.xml
index 94723411a4..ca98ff6018 100644
--- a/examples/all-options.xml
+++ b/examples/all-options.xml
@@ -337,6 +337,15 @@
default
+
+
+ 1000
+ results
+ 1000
+ 5
+
+
diff --git a/src/io/CsvWriter.cpp b/src/io/CsvWriter.cpp
new file mode 100644
index 0000000000..57bda4c548
--- /dev/null
+++ b/src/io/CsvWriter.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 High Performance Computing Center Stuttgart,
+ * University of Stuttgart. All rights reserved.
+ *
+ * This file is part of ls1-mardyn. Redistribution and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ * [Conditions]
+ *
+ * For full license details, see the LICENSE file in the root of this project.
+ *
+ */
+
+#include "io/CsvWriter.h"
+
+#include
+#include
+#include
+#include
+
+#include "Domain.h"
+#include "Simulation.h"
+#include "parallel/DomainDecompBase.h"
+#include "utils/Logger.h"
+#include "utils/mardyn_assert.h"
+
+void CsvWriter::readXML(XMLfileUnits &xmlconfig) {
+ xmlconfig.getNodeValue("writefrequency", _writeFrequency);
+ Log::global_log->info() << "[CSVWriter] Write frequency: " << _writeFrequency << std::endl;
+ if (_writeFrequency <= 0) {
+ std::string error_message =
+ "[CSVWriter] Write frequency must be a positive nonzero integer, but is " + std::to_string(_writeFrequency);
+ MARDYN_EXIT(error_message);
+ }
+
+ xmlconfig.getNodeValue("outputprefix", _outputPrefix);
+ Log::global_log->info() << "[CSVWriter] Output prefix: " << _outputPrefix << std::endl;
+ _filename = std::filesystem::path(_outputPrefix + ".csv");
+ Log::global_log->info() << "[CSVWriter] Output file: " << _filename << std::endl;
+
+ size_t acc_steps = 1000;
+ xmlconfig.getNodeValue("accumulation_steps", acc_steps);
+ Log::global_log->info() << "[CSVWriter] Accumulation steps: " << acc_steps << std::endl;
+ _U_pot_acc = std::make_unique>(acc_steps);
+ _U_kin_acc = std::make_unique>(acc_steps);
+ _p_acc = std::make_unique>(acc_steps);
+
+ xmlconfig.getNodeValue("writeprecision", _writePrecision);
+ Log::global_log->info() << "[CSVWriter] Write precision: " << _writePrecision << std::endl;
+}
+
+void CsvWriter::init(ParticleContainer * /*particleContainer*/, DomainDecompBase *domainDecomp, Domain * /*domain*/) {
+ if (domainDecomp->getRank() == 0) {
+ std::ofstream csvFile(_filename);
+ const auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+ tm unused{};
+ const auto nowStr = std::put_time(localtime_r(&now, &unused), "%FT%T%z"); // ISO 8601 time format
+ csvFile << "# ls1-mardyn simulation started at " << nowStr << std::endl;
+ csvFile << "#" << std::endl;
+ csvFile << "# Fields in this file are" << std::endl;
+ csvFile << "# simstep: current time step" << std::endl;
+ csvFile << "# N: number of particles in the system at current time step" << std::endl;
+ csvFile << "# time: current simulation time" << std::endl;
+ csvFile << "# U_pot: potential energy in the system at current time step" << std::endl;
+ csvFile << "# U_pot_avg: average potential energy in the system over the last " << _U_pot_acc->getWindowLength()
+ << " time steps" << std::endl;
+ csvFile << "# U_kin: kinetic energy in the system at current time step" << std::endl;
+ csvFile << "# U_kin_avg: kinetic energy in the system over the last " << _U_kin_acc->getWindowLength()
+ << " time steps" << std::endl;
+ csvFile << "# p: pressure in the system at current time step" << std::endl;
+ csvFile << "# p_avg: average pressure in the system over the last " << _p_acc->getWindowLength()
+ << " time steps" << std::endl;
+ csvFile << "#" << std::endl;
+ csvFile << "simstep,N,time,U_pot,U_pot_avg,U_kin,U_kin_avg,p,p_avg" << std::endl;
+ }
+}
+
+void CsvWriter::endStep(ParticleContainer *particleContainer, DomainDecompBase *domainDecomp, Domain *domain,
+ unsigned long simstep) {
+ const auto globalNumMolecules = domain->getglobalNumMolecules(true, particleContainer, domainDecomp);
+ const auto U_pot = domain->getGlobalUpot();
+ const auto U_kin = domain->getGlobalUkinTrans() + domain->getGlobalUkinRot();
+ const auto p = domain->getGlobalPressure();
+
+ _U_pot_acc->addEntry(U_pot);
+ _U_kin_acc->addEntry(U_kin);
+ _p_acc->addEntry(p);
+
+ if ((domainDecomp->getRank() == 0) && (simstep % _writeFrequency == 0)) {
+ std::ofstream csvFile(_filename, std::ios::app);
+
+ csvFile << simstep << ",";
+ csvFile << globalNumMolecules << ",";
+
+ csvFile << std::scientific << std::setprecision(_writePrecision);
+ csvFile << _simulation.getSimulationTime() << ",";
+ csvFile << U_pot << ",";
+ csvFile << _U_pot_acc->getAverage() << ",";
+ csvFile << U_kin << ",";
+ csvFile << _U_kin_acc->getAverage() << ",";
+ csvFile << p << ",";
+ csvFile << _p_acc->getAverage();
+ csvFile << std::endl;
+ }
+}
+
+void CsvWriter::finish(ParticleContainer * /*particleContainer*/, DomainDecompBase *domainDecomp, Domain * /*domain*/) {
+ if (domainDecomp->getRank() == 0) {
+ std::ofstream csvFile(_filename, std::ios::app);
+ const auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+ tm unused{};
+ const auto nowStr = std::put_time(localtime_r(&now, &unused), "%FT%T%z"); // ISO 8601 time format
+ csvFile << "# ls1-mardyn simulation finished at " << nowStr << std::endl;
+ }
+}
diff --git a/src/io/CsvWriter.h b/src/io/CsvWriter.h
new file mode 100644
index 0000000000..43f88f6d9f
--- /dev/null
+++ b/src/io/CsvWriter.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 High Performance Computing Center Stuttgart,
+ * University of Stuttgart. All rights reserved.
+ *
+ * This file is part of ls1-mardyn. Redistribution and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ * [Conditions]
+ *
+ * For full license details, see the LICENSE file in the root of this project.
+ *
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+#include "plugins/PluginBase.h"
+#include "utils/Accumulator.h"
+
+/** @brief Writes thermodynamic properties to a file in CSV format.
+ *
+ * Writes the current value and the average over a specified number of time
+ * steps of values to a CSV file including a column header line as well as
+ * additional information as comments in lines starting with '#'.
+ *
+ * The following fields are written to the file:
+ * - simstep: time step
+ * - N: number of particles
+ * - time: simulation time
+ * - U_pot: potential energy
+ * - U_pot_avg: average potential energy
+ * - U_kin: kinetic energy
+ * - U_kin_avg: average kinetic energy
+ * - p: pressure
+ * - p_avg: average pressure
+ */
+class CsvWriter : public PluginBase {
+
+public:
+ /// @brief Read in XML configuration for CsvWriter and all its included objects.
+ ///
+ /// The following xml object structure is handled by this method:
+ // clang-format off
+ /// \code{.xml}
+ ///
+ /// INTEGER
+ /// STRING
+ /// INTEGER
+ /// UINTEGER
+ ///
+ /// \endcode
+ ///
+ // clang-format on
+ virtual void readXML(XMLfileUnits &xmlconfig);
+
+ void init(ParticleContainer *particleContainer, DomainDecompBase *domainDecomp, Domain *domain);
+
+ void endStep(ParticleContainer *particleContainer, DomainDecompBase *domainDecomp, Domain *domain,
+ unsigned long simstep);
+
+ void finish(ParticleContainer *particleContainer, DomainDecompBase *domainDecomp, Domain *domain);
+
+ std::string getPluginName() { return std::string("CSVWriter"); }
+ static PluginBase *createInstance() { return new CsvWriter(); }
+
+private:
+ long _writeFrequency{1000L};
+ unsigned int _writePrecision{5};
+ std::string _outputPrefix{"results"};
+ std::filesystem::path _filename;
+ std::unique_ptr> _U_pot_acc;
+ std::unique_ptr> _U_kin_acc;
+ std::unique_ptr> _p_acc;
+};
diff --git a/src/plugins/PluginFactory.cpp b/src/plugins/PluginFactory.cpp
index 88c67caf83..4bc99c8762 100644
--- a/src/plugins/PluginFactory.cpp
+++ b/src/plugins/PluginFactory.cpp
@@ -17,6 +17,7 @@
#include "io/CavityWriter.h"
#include "io/CheckpointWriter.h"
#include "io/CommunicationPartnerWriter.h"
+#include "io/CsvWriter.h"
#include "io/DecompWriter.h"
#include "io/EnergyLogWriter.h"
#include "io/FlopRateWriter.h"
@@ -92,6 +93,7 @@ void PluginFactory::registerDefaultPlugins() {
REGISTER_PLUGIN(CavityWriter);
REGISTER_PLUGIN(CheckpointWriter);
REGISTER_PLUGIN(CommunicationPartnerWriter);
+ REGISTER_PLUGIN(CsvWriter);
REGISTER_PLUGIN(DecompWriter);
REGISTER_PLUGIN(DirectedPM);
REGISTER_PLUGIN(Dropaccelerator);