From b57a663144c6b74cafacb5073bfecea7b65275cf Mon Sep 17 00:00:00 2001 From: rois1995 Date: Wed, 7 Oct 2026 14:59:36 +0200 Subject: [PATCH 1/2] Reject periodic combinations without solver boundary support --- Common/src/CConfig.cpp | 10 +++++ UnitTests/Common/CConfig_tests.cpp | 30 +++++++++++++ UnitTests/Common/check_periodic_support.py | 50 ++++++++++++++++++++++ UnitTests/meson.build | 3 ++ 4 files changed, 93 insertions(+) create mode 100644 UnitTests/Common/check_periodic_support.py diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index fd4207b0c5fe..fb7ef35173fd 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -4226,6 +4226,16 @@ void CConfig::SetPostprocessing(SU2_COMPONENT val_software, unsigned short val_i /*--- Check for unsupported features. ---*/ + if (Kind_SU2 == SU2_COMPONENT::SU2_CFD && nMarker_PerBound > 0) { + if (ContinuousAdjoint) + SU2_MPI::Error("Continuous adjoints do not implement MARKER_PERIODIC. Use MATH_PROBLEM= DISCRETE_ADJOINT.", + CURRENT_FUNCTION); + if (Radiation) + SU2_MPI::Error("RADIATION_MODEL does not implement MARKER_PERIODIC.", CURRENT_FUNCTION); + if (Kind_Solver == MAIN_SOLVER::FEM_ELASTICITY) + SU2_MPI::Error("SOLVER= ELASTICITY does not implement MARKER_PERIODIC.", CURRENT_FUNCTION); + } + if ((Kind_Solver != MAIN_SOLVER::EULER && Kind_Solver != MAIN_SOLVER::NAVIER_STOKES && Kind_Solver != MAIN_SOLVER::RANS) && (TimeMarching == TIME_MARCHING::HARMONIC_BALANCE)){ SU2_MPI::Error("Harmonic Balance not yet implemented for the incompressible solver.", CURRENT_FUNCTION); } diff --git a/UnitTests/Common/CConfig_tests.cpp b/UnitTests/Common/CConfig_tests.cpp index 111918785545..fcd132a7dbe3 100644 --- a/UnitTests/Common/CConfig_tests.cpp +++ b/UnitTests/Common/CConfig_tests.cpp @@ -87,3 +87,33 @@ TEST_CASE("INIT_OPTION_INC defaults", "[Config]") { INIT_OPTION_INC::OPERATING_PRESSURE); CHECK(GetInitOptionInc(ideal_gas_options + "INIT_OPTION_INC= DENSITY_INIT\n") == INIT_OPTION_INC::DENSITY_INIT); } + +TEST_CASE("Periodic Continuous support", "[.PeriodicSupportContinuous]") { + auto base = base_options; + base.erase(0, base.find("MESH_FORMAT=")); + const std::string custom = "MARKER_CUSTOM= (x_minus, x_plus, z_plus, z_minus)"; + base.replace(base.find(custom), custom.size(), "MARKER_CUSTOM= (z_minus)\nMARKER_SYM= (z_plus)"); + std::stringstream options( + base + + "MARKER_PERIODIC= (x_minus,x_plus, 0,0,0, 0,0,0, 1,0,0)\nSOLVER= " + "NAVIER_STOKES\nMACH_NUMBER= 0.2\nREYNOLDS_NUMBER= 1000000\nMATH_PROBLEM= CONTINUOUS_ADJOINT\n"); + CConfig config(options, SU2_COMPONENT::SU2_CFD, false); +} + +TEST_CASE("Periodic Radiation support", "[.PeriodicSupportRadiation]") { + auto base = base_options; + const std::string custom = "MARKER_CUSTOM= (x_minus, x_plus, z_plus, z_minus)"; + base.replace(base.find(custom), custom.size(), "MARKER_CUSTOM= (z_minus)\nMARKER_SYM= (z_plus)"); + std::stringstream options( + base + "MARKER_PERIODIC= (x_minus,x_plus, 0,0,0, 0,0,0, 1,0,0)\nRADIATION_MODEL= P1\nINC_ENERGY_EQUATION= YES\n"); + CConfig config(options, SU2_COMPONENT::SU2_CFD, false); +} + +TEST_CASE("Periodic Structure support", "[.PeriodicSupportStructure]") { + auto base = base_options; + base.erase(0, base.find("MESH_FORMAT=")); + const std::string custom = "MARKER_CUSTOM= (x_minus, x_plus, z_plus, z_minus)"; + base.replace(base.find(custom), custom.size(), "MARKER_CUSTOM= (z_minus)\nMARKER_SYM= (z_plus)"); + std::stringstream options(base + "MARKER_PERIODIC= (x_minus,x_plus, 0,0,0, 0,0,0, 1,0,0)\nSOLVER= ELASTICITY\n"); + CConfig config(options, SU2_COMPONENT::SU2_CFD, false); +} diff --git a/UnitTests/Common/check_periodic_support.py b/UnitTests/Common/check_periodic_support.py new file mode 100644 index 000000000000..5aa6cdf02540 --- /dev/null +++ b/UnitTests/Common/check_periodic_support.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +"""Check periodic configuration errors in isolated processes (MPI errors terminate).""" +import argparse +import subprocess + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("test_driver") + parser.add_argument("--streamwise", action="store_true") + parser.add_argument("--launcher", nargs=argparse.REMAINDER, default=[]) + args = parser.parse_args() + cases = ( + ( + ("Rotation", "must be purely translational"), + ("Zero", "must have a nonzero translation"), + ( + "Convection", + "MARKER_HEATTRANSFER and MARKER_CHT_INTERFACE are unsupported", + ), + ("CHT", "MARKER_HEATTRANSFER and MARKER_CHT_INTERFACE are unsupported"), + ) + if args.streamwise + else ( + ("Continuous", "Continuous adjoints do not implement MARKER_PERIODIC"), + ("Radiation", "RADIATION_MODEL does not implement MARKER_PERIODIC"), + ("Structure", "SOLVER= ELASTICITY does not implement MARKER_PERIODIC"), + ) + ) + prefix = "StreamwisePeriodicSupport" if args.streamwise else "PeriodicSupport" + for tag, message in cases: + result = subprocess.run( + args.launcher + [args.test_driver, "[." + prefix + tag + "]"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + timeout=30, + check=False, + ) + if result.returncode == 0 or message not in result.stdout: + raise RuntimeError( + tag + + " did not report the expected configuration error:\n" + + result.stdout + ) + print("PASS: " + tag, flush=True) + + +if __name__ == "__main__": + main() diff --git a/UnitTests/meson.build b/UnitTests/meson.build index ba3c63afce92..71ddcd588c21 100644 --- a/UnitTests/meson.build +++ b/UnitTests/meson.build @@ -50,6 +50,9 @@ if get_option('enable-tests') cpp_args: ['-fPIC', default_warning_flags, su2_cpp_args] ) test('Catch2 test driver', test_driver) + test('Periodic solver support', python, + args: files('Common/check_periodic_support.py') + [test_driver.full_path()], + timeout: 120) test('NEMO convergence field migration', python, args: files('SU2_CFD/output/check_nemo_convergence_migration.py') + [test_driver.full_path()], timeout: 300) From 522998ab888d8fed22bcf97eb2f0796db5d19d4b Mon Sep 17 00:00:00 2001 From: rois1995 Date: Fri, 9 Oct 2026 14:00:22 +0200 Subject: [PATCH 2/2] Address periodic support review feedback --- Common/src/CConfig.cpp | 7 +-- UnitTests/Common/CConfig_tests.cpp | 30 ------------- UnitTests/Common/check_periodic_support.py | 50 ---------------------- UnitTests/meson.build | 3 -- 4 files changed, 4 insertions(+), 86 deletions(-) delete mode 100644 UnitTests/Common/check_periodic_support.py diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index fb7ef35173fd..a71d9b6d63a7 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -4228,12 +4228,13 @@ void CConfig::SetPostprocessing(SU2_COMPONENT val_software, unsigned short val_i if (Kind_SU2 == SU2_COMPONENT::SU2_CFD && nMarker_PerBound > 0) { if (ContinuousAdjoint) - SU2_MPI::Error("Continuous adjoints do not implement MARKER_PERIODIC. Use MATH_PROBLEM= DISCRETE_ADJOINT.", + SU2_MPI::Error("MARKER_PERIODIC is not currently supported in the continuous adjoint solver. " + "Use MATH_PROBLEM= DISCRETE_ADJOINT.", CURRENT_FUNCTION); if (Radiation) - SU2_MPI::Error("RADIATION_MODEL does not implement MARKER_PERIODIC.", CURRENT_FUNCTION); + SU2_MPI::Error("MARKER_PERIODIC is not currently supported with RADIATION_MODEL.", CURRENT_FUNCTION); if (Kind_Solver == MAIN_SOLVER::FEM_ELASTICITY) - SU2_MPI::Error("SOLVER= ELASTICITY does not implement MARKER_PERIODIC.", CURRENT_FUNCTION); + SU2_MPI::Error("MARKER_PERIODIC is not currently supported with SOLVER= ELASTICITY.", CURRENT_FUNCTION); } if ((Kind_Solver != MAIN_SOLVER::EULER && Kind_Solver != MAIN_SOLVER::NAVIER_STOKES && Kind_Solver != MAIN_SOLVER::RANS) && (TimeMarching == TIME_MARCHING::HARMONIC_BALANCE)){ diff --git a/UnitTests/Common/CConfig_tests.cpp b/UnitTests/Common/CConfig_tests.cpp index fcd132a7dbe3..111918785545 100644 --- a/UnitTests/Common/CConfig_tests.cpp +++ b/UnitTests/Common/CConfig_tests.cpp @@ -87,33 +87,3 @@ TEST_CASE("INIT_OPTION_INC defaults", "[Config]") { INIT_OPTION_INC::OPERATING_PRESSURE); CHECK(GetInitOptionInc(ideal_gas_options + "INIT_OPTION_INC= DENSITY_INIT\n") == INIT_OPTION_INC::DENSITY_INIT); } - -TEST_CASE("Periodic Continuous support", "[.PeriodicSupportContinuous]") { - auto base = base_options; - base.erase(0, base.find("MESH_FORMAT=")); - const std::string custom = "MARKER_CUSTOM= (x_minus, x_plus, z_plus, z_minus)"; - base.replace(base.find(custom), custom.size(), "MARKER_CUSTOM= (z_minus)\nMARKER_SYM= (z_plus)"); - std::stringstream options( - base + - "MARKER_PERIODIC= (x_minus,x_plus, 0,0,0, 0,0,0, 1,0,0)\nSOLVER= " - "NAVIER_STOKES\nMACH_NUMBER= 0.2\nREYNOLDS_NUMBER= 1000000\nMATH_PROBLEM= CONTINUOUS_ADJOINT\n"); - CConfig config(options, SU2_COMPONENT::SU2_CFD, false); -} - -TEST_CASE("Periodic Radiation support", "[.PeriodicSupportRadiation]") { - auto base = base_options; - const std::string custom = "MARKER_CUSTOM= (x_minus, x_plus, z_plus, z_minus)"; - base.replace(base.find(custom), custom.size(), "MARKER_CUSTOM= (z_minus)\nMARKER_SYM= (z_plus)"); - std::stringstream options( - base + "MARKER_PERIODIC= (x_minus,x_plus, 0,0,0, 0,0,0, 1,0,0)\nRADIATION_MODEL= P1\nINC_ENERGY_EQUATION= YES\n"); - CConfig config(options, SU2_COMPONENT::SU2_CFD, false); -} - -TEST_CASE("Periodic Structure support", "[.PeriodicSupportStructure]") { - auto base = base_options; - base.erase(0, base.find("MESH_FORMAT=")); - const std::string custom = "MARKER_CUSTOM= (x_minus, x_plus, z_plus, z_minus)"; - base.replace(base.find(custom), custom.size(), "MARKER_CUSTOM= (z_minus)\nMARKER_SYM= (z_plus)"); - std::stringstream options(base + "MARKER_PERIODIC= (x_minus,x_plus, 0,0,0, 0,0,0, 1,0,0)\nSOLVER= ELASTICITY\n"); - CConfig config(options, SU2_COMPONENT::SU2_CFD, false); -} diff --git a/UnitTests/Common/check_periodic_support.py b/UnitTests/Common/check_periodic_support.py deleted file mode 100644 index 5aa6cdf02540..000000000000 --- a/UnitTests/Common/check_periodic_support.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 -"""Check periodic configuration errors in isolated processes (MPI errors terminate).""" -import argparse -import subprocess - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("test_driver") - parser.add_argument("--streamwise", action="store_true") - parser.add_argument("--launcher", nargs=argparse.REMAINDER, default=[]) - args = parser.parse_args() - cases = ( - ( - ("Rotation", "must be purely translational"), - ("Zero", "must have a nonzero translation"), - ( - "Convection", - "MARKER_HEATTRANSFER and MARKER_CHT_INTERFACE are unsupported", - ), - ("CHT", "MARKER_HEATTRANSFER and MARKER_CHT_INTERFACE are unsupported"), - ) - if args.streamwise - else ( - ("Continuous", "Continuous adjoints do not implement MARKER_PERIODIC"), - ("Radiation", "RADIATION_MODEL does not implement MARKER_PERIODIC"), - ("Structure", "SOLVER= ELASTICITY does not implement MARKER_PERIODIC"), - ) - ) - prefix = "StreamwisePeriodicSupport" if args.streamwise else "PeriodicSupport" - for tag, message in cases: - result = subprocess.run( - args.launcher + [args.test_driver, "[." + prefix + tag + "]"], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - timeout=30, - check=False, - ) - if result.returncode == 0 or message not in result.stdout: - raise RuntimeError( - tag - + " did not report the expected configuration error:\n" - + result.stdout - ) - print("PASS: " + tag, flush=True) - - -if __name__ == "__main__": - main() diff --git a/UnitTests/meson.build b/UnitTests/meson.build index 71ddcd588c21..ba3c63afce92 100644 --- a/UnitTests/meson.build +++ b/UnitTests/meson.build @@ -50,9 +50,6 @@ if get_option('enable-tests') cpp_args: ['-fPIC', default_warning_flags, su2_cpp_args] ) test('Catch2 test driver', test_driver) - test('Periodic solver support', python, - args: files('Common/check_periodic_support.py') + [test_driver.full_path()], - timeout: 120) test('NEMO convergence field migration', python, args: files('SU2_CFD/output/check_nemo_convergence_migration.py') + [test_driver.full_path()], timeout: 300)