diff --git a/changes/user/bugfix.temperature-ramp-updates.md b/changes/user/bugfix.temperature-ramp-updates.md new file mode 100644 index 000000000..0a57032a8 --- /dev/null +++ b/changes/user/bugfix.temperature-ramp-updates.md @@ -0,0 +1 @@ +- Temperature ramps validate their schedule and reach the requested target across partial final intervals. diff --git a/docs/sphinx/src/userGuide/inputFile.rst b/docs/sphinx/src/userGuide/inputFile.rst index 30e04b017..4a960b795 100644 --- a/docs/sphinx/src/userGuide/inputFile.rst +++ b/docs/sphinx/src/userGuide/inputFile.rst @@ -815,6 +815,8 @@ Temperature Ramp Frequency With the ``temp_ramp_frequency`` keyword the user can specify the frequency of the temperature ramping from the ``start_temp`` to the ``temp`` value. If no starting temperature is given the keyword will be ignored. If a starting temperature is given and this keyword is omitted the temperature ramping will be performed, so that each step the temperature is increased by the same value. +If the ramp length is not divisible by this frequency, the temperature increments are scaled by the number of scheduled updates so that the final update reaches the requested target temperature exactly. + .. centered:: *default value* = 1 step .. _thermostatKey: diff --git a/src/setup/thermostatSetup.cpp b/src/setup/thermostatSetup.cpp index b18eea196..b3584bb54 100644 --- a/src/setup/thermostatSetup.cpp +++ b/src/setup/thermostatSetup.cpp @@ -219,45 +219,54 @@ void ThermostatSetup::setupNoseHooverThermostat() void ThermostatSetup::setupTemperatureRamp() { /************************************************************************* - * If the start temperature is defined, the temperature ramp is enabled. - ** + * If the start temperature is defined, the temperature ramp is enabled. * *************************************************************************/ if (!ThermostatSettings::isStartTemperatureSet()) return; /************************************************************* - * resetting the target temperature to the start temperature * + * If steps is 0, set the steps to the total number of steps * *************************************************************/ - const auto startTemp = ThermostatSettings::getStartTemperature(); + auto steps = ThermostatSettings::getTemperatureRampSteps(); + const auto useFullSimulation = steps == 0; - _engine.getThermostat().setTargetTemperature(startTemp); - ThermostatSettings::setActualTargetTemperature(startTemp); + if (useFullSimulation) + steps = TimingsSettings::getNumberOfSteps(); - auto steps = ThermostatSettings::getTemperatureRampSteps(); + if (steps == 0) + throw InputFileException( + "Temperature ramp requires at least one simulation step" + ); + + const auto frequency = ThermostatSettings::getTemperatureRampFrequency(); + + if (frequency == 0) + throw InputFileException( + "Temperature ramp frequency must be greater than zero" + ); + + if (useFullSimulation) + ThermostatSettings::setTemperatureRampSteps(steps); /************************************************************* - * If steps is 0, set the steps to the total number of steps * + * resetting the target temperature to the start temperature * *************************************************************/ - if (steps == 0) - { - steps = TimingsSettings::getNumberOfSteps(); - ThermostatSettings::setTemperatureRampSteps(steps); - } + const auto startTemp = ThermostatSettings::getStartTemperature(); + _engine.getThermostat().setTargetTemperature(startTemp); + ThermostatSettings::setActualTargetTemperature(startTemp); _engine.getThermostat().setTemperatureRampingSteps(steps); + _engine.getThermostat().setTemperatureRampingFrequency(frequency); - const auto frequency = ThermostatSettings::getTemperatureRampFrequency(); - - const auto targetTemp = ThermostatSettings::getTargetTemperature(); - const auto tempDelta = targetTemp - startTemp; - const auto tempIncrease = - tempDelta / double(steps) * static_cast(frequency); + const auto targetTemp = ThermostatSettings::getTargetTemperature(); + const auto tempDelta = targetTemp - startTemp; + const auto updates = steps / frequency + (steps % frequency != 0); + const auto tempIncrease = tempDelta / double(updates); _engine.getThermostat().setTemperatureIncrease(tempIncrease); - _engine.getThermostat().setTemperatureRampingFrequency(frequency); } void ThermostatSetup::writeSetupInfo() const diff --git a/tests/src/setup/testThermostatSetup.cpp b/tests/src/setup/testThermostatSetup.cpp index 0455766d8..d681dd1fa 100644 --- a/tests/src/setup/testThermostatSetup.cpp +++ b/tests/src/setup/testThermostatSetup.cpp @@ -27,7 +27,8 @@ #include "berendsenThermostat.hpp" // for BerendsenThermostat #include "constants/conversionFactors.hpp" // for _FS_TO_S_, _KG_TO_GRAM_ #include "constants/natureConstants.hpp" // for _UNIVERSAL_GAS_CONSTANT_ -#include "gtest/gtest.h" // for Message, TestPartResult +#include "exceptions.hpp" // for InputFileException +#include "inputFileReader.hpp" // for InputFileReader #include "langevinThermostat.hpp" // for LangevinThermostat #include "noseHooverThermostat.hpp" // for NoseHooverThermostat #include "testSetup.hpp" // for TestSetup @@ -100,6 +101,67 @@ TEST_F(TestSetup, setupThermostat_temp_ramping) ); } +TEST_F(TestSetup, temperatureRampReachesEndWithPartialFinalInterval) +{ + ThermostatSetup thermostatSetup(*_mdEngine); + input::InputFileReader reader("input.in", *_mdEngine); + + reader.process({"nstep", "=", "10"}); + reader.process({"thermostat", "=", "berendsen"}); + reader.process({"temp", "=", "300"}); + reader.process({"start_temp", "=", "200"}); + reader.process({"temp_ramp_steps", "=", "10"}); + reader.process({"temp_ramp_frequency", "=", "3"}); + + thermostatSetup.setup(); + + EXPECT_DOUBLE_EQ( + thermostatSetup.getEngine().getThermostat().getTemperatureIncrease(), + 25.0 + ); + + for (size_t step = 0; step < 10; ++step) + thermostatSetup.getEngine().getThermostat().applyTemperatureRamping(); + + EXPECT_DOUBLE_EQ( + thermostatSetup.getEngine().getThermostat().getTargetTemperature(), + 300.0 + ); + + settings::ThermostatSettings::setTemperatureRampSteps(0); + settings::ThermostatSettings::setTemperatureRampFrequency(1); +} + +TEST_F(TestSetup, rejectsEmptyTemperatureRamp) +{ + ThermostatSetup thermostatSetup(*_mdEngine); + + settings::TimingsSettings::setNumberOfSteps(0); + settings::ThermostatSettings::setThermostatType("berendsen"); + settings::ThermostatSettings::setTargetTemperature(300); + settings::ThermostatSettings::setStartTemperature(200); + settings::ThermostatSettings::setTemperatureRampSteps(0); + + EXPECT_THROW(thermostatSetup.setup(), customException::InputFileException); +} + +TEST_F(TestSetup, rejectsZeroTemperatureRampFrequency) +{ + ThermostatSetup thermostatSetup(*_mdEngine); + + settings::TimingsSettings::setNumberOfSteps(10); + settings::ThermostatSettings::setThermostatType("berendsen"); + settings::ThermostatSettings::setTargetTemperature(300); + settings::ThermostatSettings::setStartTemperature(200); + settings::ThermostatSettings::setTemperatureRampSteps(10); + settings::ThermostatSettings::setTemperatureRampFrequency(0); + + EXPECT_THROW(thermostatSetup.setup(), customException::InputFileException); + + settings::ThermostatSettings::setTemperatureRampSteps(0); + settings::ThermostatSettings::setTemperatureRampFrequency(1); +} + TEST_F(TestSetup, setupThermostat_only_end_temp_defined) { ThermostatSetup thermostatSetup(*_mdEngine);