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);