From daef6c6a78e434953fb45c339bc6033c4c1b63d5 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 13 Feb 2026 13:04:55 +0530 Subject: [PATCH 1/4] Add functionality for unsteady restart handling in CSinglezoneDriver and CMultizoneDriver - Implemented WriteUnsteadyRestartPreviousStep method to manage writing of previous timestep data when stopping calculations. - Updated COutput to conditionally write only restart files. - Introduced SwapSolutionWithOld method in CVariable for swapping current and previous solutions. - Enhanced driver output methods to support second-order unsteady time stepping requirements. --- SU2_CFD/include/drivers/CDriver.hpp | 12 ++++++++++++ SU2_CFD/include/output/COutput.hpp | 3 ++- SU2_CFD/include/variables/CVariable.hpp | 5 +++++ SU2_CFD/src/drivers/CDriver.cpp | 20 ++++++++++++++++++++ SU2_CFD/src/drivers/CMultizoneDriver.cpp | 23 +++++++++++++++++++++++ SU2_CFD/src/drivers/CSinglezoneDriver.cpp | 21 +++++++++++++++++++++ SU2_CFD/src/output/COutput.cpp | 9 ++++++++- SU2_CFD/src/variables/CVariable.cpp | 6 ++++++ 8 files changed, 97 insertions(+), 2 deletions(-) diff --git a/SU2_CFD/include/drivers/CDriver.hpp b/SU2_CFD/include/drivers/CDriver.hpp index b01f560fa28..4916386bf41 100644 --- a/SU2_CFD/include/drivers/CDriver.hpp +++ b/SU2_CFD/include/drivers/CDriver.hpp @@ -166,6 +166,18 @@ class CDriver : public CDriverBase { */ void RestartSolver(CSolver*** solver, CGeometry** geometry, CConfig* config, bool update_geo); + /*! + * \brief Write restart files for the previous timestep (Solution_Old) when stopping at max_time + * or last iteration with second-order unsteady time stepping. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] config - Definition of the particular problem. + * \param[in] solver - Container vector with all the solutions. + * \param[in] output - Output instance for writing files. + * \param[in] TimeIter - Current time iteration (restart for TimeIter-1 is written). + */ + void WriteUnsteadyRestartPreviousStep(CGeometry* geometry, CConfig* config, CSolver** solver, + COutput* output, unsigned long TimeIter); + /*! * \brief Definition and allocation of all solution classes. * \param[in] solver - Container vector with all the solutions. diff --git a/SU2_CFD/include/output/COutput.hpp b/SU2_CFD/include/output/COutput.hpp index ce4e29ed2c1..9a5cd0e4d94 100644 --- a/SU2_CFD/include/output/COutput.hpp +++ b/SU2_CFD/include/output/COutput.hpp @@ -591,10 +591,11 @@ class COutput { * \param[in] solver_container - Container vector with all the solutions. * \param[in] iter - The current time, outer or inner iteration index. * \param[in] force_writing - If , writing of output files is forced without checking the output frequency. + * \param[in] write_restart_only - If , only restart (and CSV restart) volume files are written. * \return if output files have been written to disk. */ bool SetResultFiles(CGeometry *geometry, CConfig *config, CSolver** solver_container, - unsigned long iter, bool force_writing = false); + unsigned long iter, bool force_writing = false, bool write_restart_only = false); /*! * \brief Get convergence time convergence of the specified windowed-time-averaged ouput of the problem. diff --git a/SU2_CFD/include/variables/CVariable.hpp b/SU2_CFD/include/variables/CVariable.hpp index bc4bc55e57c..7c110e9670d 100644 --- a/SU2_CFD/include/variables/CVariable.hpp +++ b/SU2_CFD/include/variables/CVariable.hpp @@ -281,6 +281,11 @@ class CVariable { */ void Set_Solution(); + /*! + * \brief Swap Solution and Solution_Old (e.g. for writing unsteady restart at previous timestep). + */ + void SwapSolutionWithOld(); + /*! * \brief Set the variable solution at time n. */ diff --git a/SU2_CFD/src/drivers/CDriver.cpp b/SU2_CFD/src/drivers/CDriver.cpp index 961ce04832f..c30b2a26f1c 100644 --- a/SU2_CFD/src/drivers/CDriver.cpp +++ b/SU2_CFD/src/drivers/CDriver.cpp @@ -1155,6 +1155,26 @@ void CDriver::RestartSolver(CSolver ***solver, CGeometry **geometry, } +void CDriver::WriteUnsteadyRestartPreviousStep(CGeometry* geometry, CConfig* config, CSolver** solver, + COutput* output, unsigned long TimeIter) { + + /*--- Swap Solution and Solution_Old so output writes the previous timestep. ---*/ + for (auto iSol = 0u; iSol < MAX_SOLS; ++iSol) { + auto* sol = solver[iSol]; + if (sol && !sol->GetAdjoint() && sol->GetNodes()) sol->GetNodes()->SwapSolutionWithOld(); + } + + config->SetTimeIter(TimeIter - 1); + output->SetResultFiles(geometry, config, solver, TimeIter - 1, true, true); + config->SetTimeIter(TimeIter); + + /*--- Swap back to restore current solution. ---*/ + for (auto iSol = 0u; iSol < MAX_SOLS; ++iSol) { + auto* sol = solver[iSol]; + if (sol && !sol->GetAdjoint() && sol->GetNodes()) sol->GetNodes()->SwapSolutionWithOld(); + } +} + void CDriver::FinalizeSolver(CSolver ****solver, CGeometry **geometry, CConfig *config, unsigned short val_iInst) { diff --git a/SU2_CFD/src/drivers/CMultizoneDriver.cpp b/SU2_CFD/src/drivers/CMultizoneDriver.cpp index 3843fe32037..77cca5dbdf6 100644 --- a/SU2_CFD/src/drivers/CMultizoneDriver.cpp +++ b/SU2_CFD/src/drivers/CMultizoneDriver.cpp @@ -462,6 +462,29 @@ void CMultizoneDriver::Output(unsigned long TimeIter) { StartTime = SU2_MPI::Wtime(); + /*--- When stopping (max_time or last iteration), second-order unsteady restart needs both + * timestep N and N-1. Write the previous timestep (N-1) first if not already written. ---*/ + if (StopCalc && TimeIter > 0) { + for (iZone = 0; iZone < nZone; iZone++) { + auto* config = config_container[iZone]; + if (!config->GetTime_Domain() || config->GetTime_Marching() != TIME_MARCHING::DT_STEPPING_2ND) continue; + bool write_restart = false; + const auto* volFiles = config->GetVolumeOutputFiles(); + for (unsigned short iFile = 0; iFile < config->GetnVolumeOutputFiles(); iFile++) { + const auto fmt = volFiles[iFile]; + if (fmt == OUTPUT_TYPE::RESTART_ASCII || fmt == OUTPUT_TYPE::RESTART_BINARY || fmt == OUTPUT_TYPE::CSV) { + write_restart = true; + break; + } + } + if (write_restart) { + WriteUnsteadyRestartPreviousStep(geometry_container[iZone][INST_0][MESH_0], config, + solver_container[iZone][INST_0][MESH_0], + output_container[iZone], TimeIter); + } + } + } + bool wrote_files = false; for (iZone = 0; iZone < nZone; iZone++){ diff --git a/SU2_CFD/src/drivers/CSinglezoneDriver.cpp b/SU2_CFD/src/drivers/CSinglezoneDriver.cpp index 23d6c790db6..9c2b9eba4e1 100644 --- a/SU2_CFD/src/drivers/CSinglezoneDriver.cpp +++ b/SU2_CFD/src/drivers/CSinglezoneDriver.cpp @@ -195,6 +195,27 @@ void CSinglezoneDriver::Output(unsigned long TimeIter) { StartTime = SU2_MPI::Wtime(); + /*--- When stopping (max_time or last iteration), second-order unsteady restart needs both + * timestep N and N-1. Write the previous timestep (N-1) first if not already written. ---*/ + auto* config = config_container[ZONE_0]; + if (StopCalc && TimeIter > 0 && config->GetTime_Domain() && + config->GetTime_Marching() == TIME_MARCHING::DT_STEPPING_2ND) { + bool write_restart = false; + const auto* volFiles = config->GetVolumeOutputFiles(); + for (unsigned short iFile = 0; iFile < config->GetnVolumeOutputFiles(); iFile++) { + const auto fmt = volFiles[iFile]; + if (fmt == OUTPUT_TYPE::RESTART_ASCII || fmt == OUTPUT_TYPE::RESTART_BINARY || fmt == OUTPUT_TYPE::CSV) { + write_restart = true; + break; + } + } + if (write_restart) { + WriteUnsteadyRestartPreviousStep(geometry_container[ZONE_0][INST_0][MESH_0], config, + solver_container[ZONE_0][INST_0][MESH_0], + output_container[ZONE_0], TimeIter); + } + } + bool wrote_files = output_container[ZONE_0]->SetResultFiles(geometry_container[ZONE_0][INST_0][MESH_0], config_container[ZONE_0], solver_container[ZONE_0][INST_0][MESH_0], diff --git a/SU2_CFD/src/output/COutput.cpp b/SU2_CFD/src/output/COutput.cpp index 0be8bfb759e..fcb2c830e78 100644 --- a/SU2_CFD/src/output/COutput.cpp +++ b/SU2_CFD/src/output/COutput.cpp @@ -805,7 +805,7 @@ bool COutput::GetCauchyCorrectedTimeConvergence(const CConfig *config){ } bool COutput::SetResultFiles(CGeometry *geometry, CConfig *config, CSolver** solver_container, - unsigned long iter, bool force_writing) { + unsigned long iter, bool force_writing, bool write_restart_only) { bool isFileWrite = false, dataIsLoaded = false; const auto nVolumeFiles = config->GetnVolumeOutputFiles(); @@ -816,6 +816,13 @@ bool COutput::SetResultFiles(CGeometry *geometry, CConfig *config, CSolver** sol for (unsigned short iFile = 0; iFile < nVolumeFiles; iFile++) { + /*--- When writing only restart files, skip non-restart volume output types. ---*/ + if (write_restart_only) { + const auto fmt = VolumeFiles[iFile]; + if (fmt != OUTPUT_TYPE::RESTART_ASCII && fmt != OUTPUT_TYPE::RESTART_BINARY && fmt != OUTPUT_TYPE::CSV) + continue; + } + /*--- Collect the volume data from the solvers. * If time-domain is enabled, we also load the data although we don't output it, * since we might want to do time-averaging. ---*/ diff --git a/SU2_CFD/src/variables/CVariable.cpp b/SU2_CFD/src/variables/CVariable.cpp index 7de950de622..4db791bf575 100644 --- a/SU2_CFD/src/variables/CVariable.cpp +++ b/SU2_CFD/src/variables/CVariable.cpp @@ -27,6 +27,7 @@ #include "../../include/variables/CVariable.hpp" #include "../../../Common/include/parallelization/omp_structure.hpp" +#include CVariable::CVariable(unsigned long npoint, unsigned long nvar, const CConfig *config) { @@ -91,6 +92,11 @@ void CVariable::Set_Solution() { parallelCopy(Solution_Old.size(), Solution_Old.data(), Solution.data()); } +void CVariable::SwapSolutionWithOld() { + assert(Solution.size() == Solution_Old.size()); + std::swap(Solution, Solution_Old); +} + void CVariable::Set_Solution_time_n() { assert(Solution_time_n.size() == Solution.size()); parallelCopy(Solution.size(), Solution.data(), Solution_time_n.data()); From 19fd3ce8589ca0a03e3810e3a0f89edbfdb4c188 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 14 Feb 2026 00:17:56 +0530 Subject: [PATCH 2/4] Refactor unsteady restart logic to detect max_time explicitly - Changed condition from generic StopCalc flag to specific max_time detection - CSinglezoneDriver: Detects final_time_reached = (cur_time >= max_time) - CMultizoneDriver: Applies same pattern for all zones - Maintains original functionality with clearer, more maintainable code - Specific to time-domain simulations with second-order time stepping This addresses maintainer feedback to use explicit time checks instead of generic stopping conditions, ensuring the fix properly generalizes to all problem types and follows SU2 monitoring patterns. --- SU2_CFD/src/drivers/CMultizoneDriver.cpp | 13 ++++++-- SU2_CFD/src/drivers/CSinglezoneDriver.cpp | 39 +++++++++++++---------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/SU2_CFD/src/drivers/CMultizoneDriver.cpp b/SU2_CFD/src/drivers/CMultizoneDriver.cpp index 77cca5dbdf6..8a323f0380b 100644 --- a/SU2_CFD/src/drivers/CMultizoneDriver.cpp +++ b/SU2_CFD/src/drivers/CMultizoneDriver.cpp @@ -462,12 +462,19 @@ void CMultizoneDriver::Output(unsigned long TimeIter) { StartTime = SU2_MPI::Wtime(); - /*--- When stopping (max_time or last iteration), second-order unsteady restart needs both - * timestep N and N-1. Write the previous timestep (N-1) first if not already written. ---*/ - if (StopCalc && TimeIter > 0) { + /*--- When stopping due to max_time with second-order unsteady time stepping, we need to write + * both the previous timestep (N-1) and current timestep (N) for proper restart capability. ---*/ + if (TimeIter > 0) { for (iZone = 0; iZone < nZone; iZone++) { auto* config = config_container[iZone]; if (!config->GetTime_Domain() || config->GetTime_Marching() != TIME_MARCHING::DT_STEPPING_2ND) continue; + + const su2double cur_time = output_container[iZone]->GetHistoryFieldValue("CUR_TIME"); + const su2double max_time = config->GetMax_Time(); + const bool final_time_reached = cur_time >= max_time; + + if (!final_time_reached) continue; + bool write_restart = false; const auto* volFiles = config->GetVolumeOutputFiles(); for (unsigned short iFile = 0; iFile < config->GetnVolumeOutputFiles(); iFile++) { diff --git a/SU2_CFD/src/drivers/CSinglezoneDriver.cpp b/SU2_CFD/src/drivers/CSinglezoneDriver.cpp index 9c2b9eba4e1..dae966c2629 100644 --- a/SU2_CFD/src/drivers/CSinglezoneDriver.cpp +++ b/SU2_CFD/src/drivers/CSinglezoneDriver.cpp @@ -195,24 +195,31 @@ void CSinglezoneDriver::Output(unsigned long TimeIter) { StartTime = SU2_MPI::Wtime(); - /*--- When stopping (max_time or last iteration), second-order unsteady restart needs both - * timestep N and N-1. Write the previous timestep (N-1) first if not already written. ---*/ + /*--- When stopping due to max_time with second-order unsteady time stepping, we need to write + * both the previous timestep (N-1) and current timestep (N) for proper restart capability. ---*/ auto* config = config_container[ZONE_0]; - if (StopCalc && TimeIter > 0 && config->GetTime_Domain() && - config->GetTime_Marching() == TIME_MARCHING::DT_STEPPING_2ND) { - bool write_restart = false; - const auto* volFiles = config->GetVolumeOutputFiles(); - for (unsigned short iFile = 0; iFile < config->GetnVolumeOutputFiles(); iFile++) { - const auto fmt = volFiles[iFile]; - if (fmt == OUTPUT_TYPE::RESTART_ASCII || fmt == OUTPUT_TYPE::RESTART_BINARY || fmt == OUTPUT_TYPE::CSV) { - write_restart = true; - break; + const bool time_domain = config->GetTime_Domain(); + + if (time_domain && config->GetTime_Marching() == TIME_MARCHING::DT_STEPPING_2ND && TimeIter > 0) { + const su2double cur_time = output_container[ZONE_0]->GetHistoryFieldValue("CUR_TIME"); + const su2double max_time = config->GetMax_Time(); + const bool final_time_reached = cur_time >= max_time; + + if (final_time_reached) { + bool write_restart = false; + const auto* volFiles = config->GetVolumeOutputFiles(); + for (unsigned short iFile = 0; iFile < config->GetnVolumeOutputFiles(); iFile++) { + const auto fmt = volFiles[iFile]; + if (fmt == OUTPUT_TYPE::RESTART_ASCII || fmt == OUTPUT_TYPE::RESTART_BINARY || fmt == OUTPUT_TYPE::CSV) { + write_restart = true; + break; + } + } + if (write_restart) { + WriteUnsteadyRestartPreviousStep(geometry_container[ZONE_0][INST_0][MESH_0], config, + solver_container[ZONE_0][INST_0][MESH_0], + output_container[ZONE_0], TimeIter); } - } - if (write_restart) { - WriteUnsteadyRestartPreviousStep(geometry_container[ZONE_0][INST_0][MESH_0], config, - solver_container[ZONE_0][INST_0][MESH_0], - output_container[ZONE_0], TimeIter); } } From 50d25fadeb76eee00dcd4542e536892ceb8e6b57 Mon Sep 17 00:00:00 2001 From: Soumyadipta Banerjee Date: Wed, 18 Feb 2026 23:11:09 +0530 Subject: [PATCH 3/4] Refactor unsteady restart fix to use safer delay mechanism Implement max_time detection in GetCauchyCorrectedTimeConvergence() to delay convergence by one iteration, ensuring both current and previous timesteps are saved to unsteady restart files. This replaces the previous unsafe solution-swapping approach with a more robust pattern modeled after PR #1237. The delay mechanism leverages existing Cauchy convergence infrastructure and properly handles second-order time stepping. Fixes #2493 --- SU2_CFD/include/drivers/CDriver.hpp | 11 --------- SU2_CFD/include/output/COutput.hpp | 3 ++- SU2_CFD/include/variables/CVariable.hpp | 5 ---- SU2_CFD/src/drivers/CDriver.cpp | 20 --------------- SU2_CFD/src/drivers/CMultizoneDriver.cpp | 30 ----------------------- SU2_CFD/src/drivers/CSinglezoneDriver.cpp | 28 --------------------- SU2_CFD/src/output/COutput.cpp | 30 +++++++++++++++++------ SU2_CFD/src/variables/CVariable.cpp | 5 ---- 8 files changed, 24 insertions(+), 108 deletions(-) diff --git a/SU2_CFD/include/drivers/CDriver.hpp b/SU2_CFD/include/drivers/CDriver.hpp index 4916386bf41..f88e229349d 100644 --- a/SU2_CFD/include/drivers/CDriver.hpp +++ b/SU2_CFD/include/drivers/CDriver.hpp @@ -167,17 +167,6 @@ class CDriver : public CDriverBase { void RestartSolver(CSolver*** solver, CGeometry** geometry, CConfig* config, bool update_geo); /*! - * \brief Write restart files for the previous timestep (Solution_Old) when stopping at max_time - * or last iteration with second-order unsteady time stepping. - * \param[in] geometry - Geometrical definition of the problem. - * \param[in] config - Definition of the particular problem. - * \param[in] solver - Container vector with all the solutions. - * \param[in] output - Output instance for writing files. - * \param[in] TimeIter - Current time iteration (restart for TimeIter-1 is written). - */ - void WriteUnsteadyRestartPreviousStep(CGeometry* geometry, CConfig* config, CSolver** solver, - COutput* output, unsigned long TimeIter); - /*! * \brief Definition and allocation of all solution classes. * \param[in] solver - Container vector with all the solutions. diff --git a/SU2_CFD/include/output/COutput.hpp b/SU2_CFD/include/output/COutput.hpp index 9a5cd0e4d94..e33eb0d1baf 100644 --- a/SU2_CFD/include/output/COutput.hpp +++ b/SU2_CFD/include/output/COutput.hpp @@ -95,6 +95,7 @@ class COutput { ofstream histFile; /*!< \brief Output file stream for the history */ bool cauchyTimeConverged; /*! \brief: Flag indicating that solver is already converged. Needed for writing restart files. */ + bool maxTimeDelayActive; /*! \brief: Flag for delaying stop at max_time with 2nd order time stepping. */ /** \brief Enum to identify the screen output format. */ enum class ScreenOutputFormat { @@ -595,7 +596,7 @@ class COutput { * \return if output files have been written to disk. */ bool SetResultFiles(CGeometry *geometry, CConfig *config, CSolver** solver_container, - unsigned long iter, bool force_writing = false, bool write_restart_only = false); + unsigned long iter, bool force_writing = false); /*! * \brief Get convergence time convergence of the specified windowed-time-averaged ouput of the problem. diff --git a/SU2_CFD/include/variables/CVariable.hpp b/SU2_CFD/include/variables/CVariable.hpp index 7c110e9670d..bc4bc55e57c 100644 --- a/SU2_CFD/include/variables/CVariable.hpp +++ b/SU2_CFD/include/variables/CVariable.hpp @@ -281,11 +281,6 @@ class CVariable { */ void Set_Solution(); - /*! - * \brief Swap Solution and Solution_Old (e.g. for writing unsteady restart at previous timestep). - */ - void SwapSolutionWithOld(); - /*! * \brief Set the variable solution at time n. */ diff --git a/SU2_CFD/src/drivers/CDriver.cpp b/SU2_CFD/src/drivers/CDriver.cpp index c30b2a26f1c..961ce04832f 100644 --- a/SU2_CFD/src/drivers/CDriver.cpp +++ b/SU2_CFD/src/drivers/CDriver.cpp @@ -1155,26 +1155,6 @@ void CDriver::RestartSolver(CSolver ***solver, CGeometry **geometry, } -void CDriver::WriteUnsteadyRestartPreviousStep(CGeometry* geometry, CConfig* config, CSolver** solver, - COutput* output, unsigned long TimeIter) { - - /*--- Swap Solution and Solution_Old so output writes the previous timestep. ---*/ - for (auto iSol = 0u; iSol < MAX_SOLS; ++iSol) { - auto* sol = solver[iSol]; - if (sol && !sol->GetAdjoint() && sol->GetNodes()) sol->GetNodes()->SwapSolutionWithOld(); - } - - config->SetTimeIter(TimeIter - 1); - output->SetResultFiles(geometry, config, solver, TimeIter - 1, true, true); - config->SetTimeIter(TimeIter); - - /*--- Swap back to restore current solution. ---*/ - for (auto iSol = 0u; iSol < MAX_SOLS; ++iSol) { - auto* sol = solver[iSol]; - if (sol && !sol->GetAdjoint() && sol->GetNodes()) sol->GetNodes()->SwapSolutionWithOld(); - } -} - void CDriver::FinalizeSolver(CSolver ****solver, CGeometry **geometry, CConfig *config, unsigned short val_iInst) { diff --git a/SU2_CFD/src/drivers/CMultizoneDriver.cpp b/SU2_CFD/src/drivers/CMultizoneDriver.cpp index 8a323f0380b..3843fe32037 100644 --- a/SU2_CFD/src/drivers/CMultizoneDriver.cpp +++ b/SU2_CFD/src/drivers/CMultizoneDriver.cpp @@ -462,36 +462,6 @@ void CMultizoneDriver::Output(unsigned long TimeIter) { StartTime = SU2_MPI::Wtime(); - /*--- When stopping due to max_time with second-order unsteady time stepping, we need to write - * both the previous timestep (N-1) and current timestep (N) for proper restart capability. ---*/ - if (TimeIter > 0) { - for (iZone = 0; iZone < nZone; iZone++) { - auto* config = config_container[iZone]; - if (!config->GetTime_Domain() || config->GetTime_Marching() != TIME_MARCHING::DT_STEPPING_2ND) continue; - - const su2double cur_time = output_container[iZone]->GetHistoryFieldValue("CUR_TIME"); - const su2double max_time = config->GetMax_Time(); - const bool final_time_reached = cur_time >= max_time; - - if (!final_time_reached) continue; - - bool write_restart = false; - const auto* volFiles = config->GetVolumeOutputFiles(); - for (unsigned short iFile = 0; iFile < config->GetnVolumeOutputFiles(); iFile++) { - const auto fmt = volFiles[iFile]; - if (fmt == OUTPUT_TYPE::RESTART_ASCII || fmt == OUTPUT_TYPE::RESTART_BINARY || fmt == OUTPUT_TYPE::CSV) { - write_restart = true; - break; - } - } - if (write_restart) { - WriteUnsteadyRestartPreviousStep(geometry_container[iZone][INST_0][MESH_0], config, - solver_container[iZone][INST_0][MESH_0], - output_container[iZone], TimeIter); - } - } - } - bool wrote_files = false; for (iZone = 0; iZone < nZone; iZone++){ diff --git a/SU2_CFD/src/drivers/CSinglezoneDriver.cpp b/SU2_CFD/src/drivers/CSinglezoneDriver.cpp index dae966c2629..23d6c790db6 100644 --- a/SU2_CFD/src/drivers/CSinglezoneDriver.cpp +++ b/SU2_CFD/src/drivers/CSinglezoneDriver.cpp @@ -195,34 +195,6 @@ void CSinglezoneDriver::Output(unsigned long TimeIter) { StartTime = SU2_MPI::Wtime(); - /*--- When stopping due to max_time with second-order unsteady time stepping, we need to write - * both the previous timestep (N-1) and current timestep (N) for proper restart capability. ---*/ - auto* config = config_container[ZONE_0]; - const bool time_domain = config->GetTime_Domain(); - - if (time_domain && config->GetTime_Marching() == TIME_MARCHING::DT_STEPPING_2ND && TimeIter > 0) { - const su2double cur_time = output_container[ZONE_0]->GetHistoryFieldValue("CUR_TIME"); - const su2double max_time = config->GetMax_Time(); - const bool final_time_reached = cur_time >= max_time; - - if (final_time_reached) { - bool write_restart = false; - const auto* volFiles = config->GetVolumeOutputFiles(); - for (unsigned short iFile = 0; iFile < config->GetnVolumeOutputFiles(); iFile++) { - const auto fmt = volFiles[iFile]; - if (fmt == OUTPUT_TYPE::RESTART_ASCII || fmt == OUTPUT_TYPE::RESTART_BINARY || fmt == OUTPUT_TYPE::CSV) { - write_restart = true; - break; - } - } - if (write_restart) { - WriteUnsteadyRestartPreviousStep(geometry_container[ZONE_0][INST_0][MESH_0], config, - solver_container[ZONE_0][INST_0][MESH_0], - output_container[ZONE_0], TimeIter); - } - } - } - bool wrote_files = output_container[ZONE_0]->SetResultFiles(geometry_container[ZONE_0][INST_0][MESH_0], config_container[ZONE_0], solver_container[ZONE_0][INST_0][MESH_0], diff --git a/SU2_CFD/src/output/COutput.cpp b/SU2_CFD/src/output/COutput.cpp index fcb2c830e78..fa7a00d4a8c 100644 --- a/SU2_CFD/src/output/COutput.cpp +++ b/SU2_CFD/src/output/COutput.cpp @@ -70,6 +70,7 @@ COutput::COutput(const CConfig *config, unsigned short ndim, bool fem_output): us_units(config->GetSystemMeasurements() == US) { cauchyTimeConverged = false; + maxTimeDelayActive = false; convergenceTable = new PrintingToolbox::CTablePrinter(&std::cout); multiZoneHeaderTable = new PrintingToolbox::CTablePrinter(&std::cout); @@ -793,6 +794,7 @@ void COutput::WriteToFile(CConfig *config, CGeometry *geometry, OUTPUT_TYPE form } bool COutput::GetCauchyCorrectedTimeConvergence(const CConfig *config){ + // Handle Cauchy convergence delay for 2nd order time stepping if(!cauchyTimeConverged && TimeConvergence && config->GetTime_Marching() == TIME_MARCHING::DT_STEPPING_2ND){ // Change flags for 2nd order Time stepping: In case of convergence, this iter and next iter gets written out. then solver stops cauchyTimeConverged = TimeConvergence; @@ -801,11 +803,30 @@ bool COutput::GetCauchyCorrectedTimeConvergence(const CConfig *config){ else if(cauchyTimeConverged){ TimeConvergence = cauchyTimeConverged; } + + // Handle max time delay for 2nd order time stepping + // Delay stopping at max_time to ensure both timestep N and N-1 are written for proper restart + if(config->GetTime_Marching() == TIME_MARCHING::DT_STEPPING_2ND){ + const su2double cur_time = GetHistoryFieldValue("CUR_TIME"); + const su2double max_time = config->GetMax_Time(); + const bool final_time_reached = (cur_time >= max_time); + + // If max_time is reached on first detection, delay the stop + if(final_time_reached && !maxTimeDelayActive){ + maxTimeDelayActive = true; + TimeConvergence = false; // Delay stop to run one more iteration + } + else if(maxTimeDelayActive){ + TimeConvergence = true; // Now allow stop + maxTimeDelayActive = false; // Reset for next run + } + } + return TimeConvergence; } bool COutput::SetResultFiles(CGeometry *geometry, CConfig *config, CSolver** solver_container, - unsigned long iter, bool force_writing, bool write_restart_only) { + unsigned long iter, bool force_writing) { bool isFileWrite = false, dataIsLoaded = false; const auto nVolumeFiles = config->GetnVolumeOutputFiles(); @@ -816,13 +837,6 @@ bool COutput::SetResultFiles(CGeometry *geometry, CConfig *config, CSolver** sol for (unsigned short iFile = 0; iFile < nVolumeFiles; iFile++) { - /*--- When writing only restart files, skip non-restart volume output types. ---*/ - if (write_restart_only) { - const auto fmt = VolumeFiles[iFile]; - if (fmt != OUTPUT_TYPE::RESTART_ASCII && fmt != OUTPUT_TYPE::RESTART_BINARY && fmt != OUTPUT_TYPE::CSV) - continue; - } - /*--- Collect the volume data from the solvers. * If time-domain is enabled, we also load the data although we don't output it, * since we might want to do time-averaging. ---*/ diff --git a/SU2_CFD/src/variables/CVariable.cpp b/SU2_CFD/src/variables/CVariable.cpp index 4db791bf575..86128255f14 100644 --- a/SU2_CFD/src/variables/CVariable.cpp +++ b/SU2_CFD/src/variables/CVariable.cpp @@ -92,11 +92,6 @@ void CVariable::Set_Solution() { parallelCopy(Solution_Old.size(), Solution_Old.data(), Solution.data()); } -void CVariable::SwapSolutionWithOld() { - assert(Solution.size() == Solution_Old.size()); - std::swap(Solution, Solution_Old); -} - void CVariable::Set_Solution_time_n() { assert(Solution_time_n.size() == Solution.size()); parallelCopy(Solution.size(), Solution.data(), Solution_time_n.data()); From fd7ce021e4574751e646b5a60b3177fcfd989d61 Mon Sep 17 00:00:00 2001 From: Soumyadipta Banerjee Date: Fri, 20 Feb 2026 01:49:54 +0530 Subject: [PATCH 4/4] Fix syntax: remove duplicate Doxygen marker and drop unused include --- SU2_CFD/include/drivers/CDriver.hpp | 1 - SU2_CFD/src/variables/CVariable.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/SU2_CFD/include/drivers/CDriver.hpp b/SU2_CFD/include/drivers/CDriver.hpp index f88e229349d..b01f560fa28 100644 --- a/SU2_CFD/include/drivers/CDriver.hpp +++ b/SU2_CFD/include/drivers/CDriver.hpp @@ -166,7 +166,6 @@ class CDriver : public CDriverBase { */ void RestartSolver(CSolver*** solver, CGeometry** geometry, CConfig* config, bool update_geo); - /*! /*! * \brief Definition and allocation of all solution classes. * \param[in] solver - Container vector with all the solutions. diff --git a/SU2_CFD/src/variables/CVariable.cpp b/SU2_CFD/src/variables/CVariable.cpp index 86128255f14..7de950de622 100644 --- a/SU2_CFD/src/variables/CVariable.cpp +++ b/SU2_CFD/src/variables/CVariable.cpp @@ -27,7 +27,6 @@ #include "../../include/variables/CVariable.hpp" #include "../../../Common/include/parallelization/omp_structure.hpp" -#include CVariable::CVariable(unsigned long npoint, unsigned long nvar, const CConfig *config) {