diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml index 5c2157eb2277..b14fb181b98f 100644 --- a/.github/workflows/regression.yml +++ b/.github/workflows/regression.yml @@ -474,6 +474,15 @@ jobs: with: entrypoint: python3 args: src/SU2/UnitTests/SU2_CFD/output/check_nemo_convergence_migration.py /github/workspace/install/bin/test_driver + - name: Check CConfig option errors + if: matrix.testdriver == 'test_driver' + uses: docker://ghcr.io/su2code/su2/test-su2:261011-1107 + env: + OMPI_MCA_osc: pt2pt + LD_LIBRARY_PATH: /github/workspace/install/lib + with: + entrypoint: python3 + args: src/SU2/UnitTests/Common/check_config_errors.py /github/workspace/install/bin/test_driver - name: Post Cleanup uses: docker://ghcr.io/su2code/su2/test-su2:261011-1107 with: diff --git a/Common/include/toolboxes/geometry_toolbox.hpp b/Common/include/toolboxes/geometry_toolbox.hpp index e621ea65b7b2..ecbd3c024403 100644 --- a/Common/include/toolboxes/geometry_toolbox.hpp +++ b/Common/include/toolboxes/geometry_toolbox.hpp @@ -250,5 +250,27 @@ inline void TangentProjection(Int nDim, const Mat& tensor, const Scalar* vector, for (Int iDim = 0; iDim < nDim; iDim++) proj[iDim] -= normalProj * vector[iDim]; } +/*! + * \brief Check if a point lies inside an ellipsoid (ellipse in 2D) rotated about the z axis. + * \note Intermediate values are stored with an explicit type, because with AD types "auto" would hold + * expression templates. + * \param[in] nDim - Number of dimensions, only the first nDim coordinates and semi-axes are used. + * \param[in] coord - Coordinates of the point. + * \param[in] center - Center of the ellipsoid. + * \param[in] axes - Semi-axes of the ellipsoid. + * \param[in] rotZ - Rotation of the ellipsoid about the z axis, in radians. + * \return True if the point is inside the ellipsoid or on its surface. + */ +template +inline bool PointInEllipsoid(Int nDim, const T* coord, const T* center, const T* axes, const T& rotZ) { + const T dx = coord[0] - center[0]; + const T dy = coord[1] - center[1]; + const T x = dx * cos(rotZ) + dy * sin(rotZ); + const T y = -dx * sin(rotZ) + dy * cos(rotZ); + T check = pow(x / axes[0], 2) + pow(y / axes[1], 2); + if (nDim == 3) check += pow((coord[2] - center[2]) / axes[2], 2); + return check <= 1; +} + /// @} } // namespace GeometryToolbox diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index b0c273939be0..79b7a4d83548 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -4231,6 +4231,12 @@ void CConfig::SetPostprocessing(SU2_COMPONENT val_software, unsigned short val_i Radiation = (Kind_Radiation != RADIATION_MODEL::NONE); + /*--- The ellipsoidal HEAT_SOURCE is part of the radiation model. Without one, no solver applies it. ---*/ + if (HeatSource && !Radiation) { + SU2_MPI::Error("HEAT_SOURCE= YES requires a RADIATION_MODEL. For other volumetric sources, use PYTHON_CUSTOM_SOURCE.", + CURRENT_FUNCTION); + } + /*--- Check for unsupported features. ---*/ 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/SU2_CFD/src/solvers/CRadSolver.cpp b/SU2_CFD/src/solvers/CRadSolver.cpp index abe8b901f96d..941eb393f87e 100644 --- a/SU2_CFD/src/solvers/CRadSolver.cpp +++ b/SU2_CFD/src/solvers/CRadSolver.cpp @@ -46,30 +46,16 @@ CRadSolver::CRadSolver(CGeometry* geometry, CConfig *config) : CSolver() { void CRadSolver::SetVolumetricHeatSource(CGeometry *geometry, CConfig *config) { SU2_ZONE_SCOPED - unsigned long iPoint; - unsigned short iDim; - - su2double CP[3]={0.0,0.0,0.0}; - su2double alpha = config->GetHeatSource_Rot_Z() * PI_NUMBER/180.0; - su2double OP_rot[3]={0.0,0.0,0.0}; - const su2double *OP; + const su2double alpha = config->GetHeatSource_Rot_Z() * PI_NUMBER/180.0; const su2double *OC = config->GetHeatSource_Center(); const su2double *Axes = config->GetHeatSource_Axes(); - su2double check; + // Reset the boolean for all points nodes->ResetVol_HeatSource(); // Loop over all points and determine whether they are inside - for (iPoint = 0; iPoint < nPoint; iPoint ++) { - check = 0; - OP = geometry->nodes->GetCoord(iPoint); - // Reference point with respect to center of the ellipse - for (iDim = 0; iDim < nDim; iDim++) CP[iDim] = OP[iDim]-OC[iDim]; - // Rotate point with respect to Z axis - OP_rot[0] = OC[0] + CP[0]*cos(alpha) + CP[1]*sin(alpha); - OP_rot[1] = OC[1] - CP[0]*sin(alpha) + CP[1]*cos(alpha); - // Check if rotated point is inside the ellipse - for (iDim = 0; iDim < nDim; iDim++) check += pow(OP_rot[iDim]-OC[iDim],2.0)/pow(Axes[iDim], 2.0); - if (check <=1) nodes->SetVol_HeatSource(iPoint); + for (unsigned long iPoint = 0; iPoint < nPoint; iPoint++) { + const auto *OP = geometry->nodes->GetCoord(iPoint); + if (GeometryToolbox::PointInEllipsoid(nDim, OP, OC, Axes, alpha)) nodes->SetVol_HeatSource(iPoint); } } diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index ddb1c8049b58..be5fd0811da2 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -403,12 +403,12 @@ def main(): test_list.append(cylinder_lowmach) # Laminar NACA0012 with linelet preconditioner (see #2877), 4 ranks so that some partitions have no linelets - lam_naca0012_linelet = TestCase('lam_naca0012_linelet') - lam_naca0012_linelet.cfg_dir = "navierstokes/naca0012" - lam_naca0012_linelet.cfg_file = "lam_NACA0012_LINELET.cfg" + lam_naca0012_linelet = TestCase('lam_naca0012_linelet') + lam_naca0012_linelet.cfg_dir = "navierstokes/naca0012" + lam_naca0012_linelet.cfg_file = "lam_NACA0012_LINELET.cfg" lam_naca0012_linelet.test_iter = 20 lam_naca0012_linelet.test_vals = [-5.335771, -2.936187, -2.943926, 0.102449] - lam_naca0012_linelet.command = TestCase.Command("mpirun --oversubscribe -n 4", "SU2_CFD") + lam_naca0012_linelet.command = TestCase.Command("mpirun --oversubscribe -n 4", "SU2_CFD") test_list.append(lam_naca0012_linelet) # 2D Poiseuille flow (body force driven with periodic inlet / outlet) @@ -1352,12 +1352,12 @@ def main(): test_list.append(transonic_stator_restart) # Multiple turbomachinery interface restart - multi_interface = TestCase('multi_interface') - multi_interface.cfg_dir = "turbomachinery/multi_interface" - multi_interface.cfg_file = "multi_interface_rst.cfg" - multi_interface.test_iter = 5 - multi_interface.test_vals = [-8.634558, -8.895554, -9.348754] - multi_interface.test_vals_aarch64 = [-8.632227, -8.894736, -9.348706] + multi_interface = TestCase('multi_interface') + multi_interface.cfg_dir = "turbomachinery/multi_interface" + multi_interface.cfg_file = "multi_interface_rst.cfg" + multi_interface.test_iter = 5 + multi_interface.test_vals = [-8.634558, -8.895554, -9.348754] + multi_interface.test_vals_aarch64 = [-8.632227, -8.894736, -9.348706] test_list.append(multi_interface) ###################################### @@ -1756,6 +1756,16 @@ def main(): pywrapper_Unst_Heat_Source.command = TestCase.Command("mpirun -n 2", "python", "run.py") test_list.append(pywrapper_Unst_Heat_Source) + # Heat solver steady with a volumetric source in an ellipsoid, analytic solution + pywrapper_Steady_Heat_Source = TestCase('pywrapper_Steady_Heat_Source') + pywrapper_Steady_Heat_Source.cfg_dir = "py_wrapper/custom_heat_source_steady" + pywrapper_Steady_Heat_Source.cfg_file = "run.py" + pywrapper_Steady_Heat_Source.test_iter = 15 + # Only the total heat flux is compared, the residual history of this small case depends on the partitioning. + pywrapper_Steady_Heat_Source.test_vals = [-64.000000] + pywrapper_Steady_Heat_Source.command = TestCase.Command("mpirun -n 2", "python", "run.py") + test_list.append(pywrapper_Steady_Heat_Source) + ############################################## ### Method of Manufactured Solutions (MMS) ### ############################################## diff --git a/TestCases/py_wrapper/custom_heat_source_steady/run.py b/TestCases/py_wrapper/custom_heat_source_steady/run.py new file mode 100644 index 000000000000..d29ee55b771e --- /dev/null +++ b/TestCases/py_wrapper/custom_heat_source_steady/run.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python + +## \file run.py +# \brief Steady solid heat conduction with a volumetric heat source in an ellipsoid. +# \version 8.5.0 "Harrier" +# +# SU2 Project Website: https://su2code.github.io +# +# The SU2 Project is maintained by the SU2 Foundation +# (http://su2foundation.org) +# +# Copyright 2012-2026, SU2 Contributors (cf. AUTHORS.md) +# +# SU2 is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# SU2 is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with SU2. If not, see . + +from mpi4py import MPI +import math +import sys +import pysu2 # imports the SU2 wrapped module + +# Slab of width L with both x faces at T_WALL and insulated y faces. +L = 0.016 +H = 0.004 +N_X = 17 +T_WALL = 300.0 +DENSITY = 5000.0 +CP = 500.0 +CONDUCTIVITY = 15.0 + +# Volumetric heat source (W/m^3) inside an ellipsoid that can be rotated about the z axis. +# The ellipsoid encloses the whole slab here, so the source is uniform. +SOURCE_VAL = 1e6 +SOURCE_CENTER = (0.5 * L, 0.5 * H, 0.0) +SOURCE_AXES = (0.02, 0.02, 1.0) +SOURCE_ROTATION_Z = 0.0 # degrees + +settings = f""" +SOLVER= HEAT_EQUATION +INC_NONDIM= DIMENSIONAL + +FREESTREAM_TEMPERATURE= {T_WALL} +MATERIAL_DENSITY= {DENSITY} +SPECIFIC_HEAT_CP= {CP} +THERMAL_CONDUCTIVITY_CONSTANT= {CONDUCTIVITY} + +MARKER_ISOTHERMAL= ( x_minus, {T_WALL}, x_plus, {T_WALL} ) +MARKER_HEATFLUX= ( y_minus, 0, y_plus, 0 ) +MARKER_MONITORING= ( x_minus, x_plus ) + +PYTHON_CUSTOM_SOURCE= YES + +NUM_METHOD_GRAD= GREEN_GAUSS +CFL_NUMBER= 1e8 +TIME_DISCRE_HEAT= EULER_IMPLICIT + +LINEAR_SOLVER= FGMRES +LINEAR_SOLVER_PREC= ILU +LINEAR_SOLVER_ERROR= 1E-15 +LINEAR_SOLVER_ITER= 10 + +MESH_FORMAT= RECTANGLE +MESH_BOX_SIZE= ( {N_X}, 5, 0 ) +MESH_BOX_LENGTH= ( {L}, {H}, 0 ) + +ITER= 20 +CONV_FIELD= RMS_TEMPERATURE +CONV_RESIDUAL_MINVAL= -20 + +SCREEN_OUTPUT= INNER_ITER, RMS_TEMPERATURE, LINSOL_ITER, TOTAL_HEATFLUX +HISTORY_OUTPUT= ITER, RMS_RES, HEAT, LINSOL +OUTPUT_FILES= RESTART, PARAVIEW +""" + + +def HeatSource(coord): + """Returns the source (W/m^3) at a point, SOURCE_VAL inside the ellipsoid and zero outside.""" + alpha = math.radians(SOURCE_ROTATION_Z) + dx = coord[0] - SOURCE_CENTER[0] + dy = coord[1] - SOURCE_CENTER[1] + x = dx * math.cos(alpha) + dy * math.sin(alpha) + y = -dx * math.sin(alpha) + dy * math.cos(alpha) + check = (x / SOURCE_AXES[0])**2 + (y / SOURCE_AXES[1])**2 + if len(coord) == 3: + check += ((coord[2] - SOURCE_CENTER[2]) / SOURCE_AXES[2])**2 + return SOURCE_VAL if check <= 1 else 0.0 + + +def main(): + comm = MPI.COMM_WORLD + rank = comm.Get_rank() + + if rank == 0: + with open('config.cfg', 'w') as f: + f.write(settings) + comm.Barrier() + + # Initialize the corresponding driver of SU2, this includes solver preprocessing. + try: + driver = pysu2.CSinglezoneDriver('config.cfg', 1, comm) + except TypeError as exception: + print('A TypeError occured in pysu2.CDriver : ', exception) + raise + + iHEATSOLVER = driver.GetSolverIndices()['HEAT'] + nNode = driver.GetNumberNodes() - driver.GetNumberHaloNodes() + Coords = driver.Coordinates() + + # The heat solver works with the temperature equation divided by rho*cp, + # so a source in W/m^3 has to be divided by rho*cp as well. + Source = driver.UserDefinedSource(iHEATSOLVER) + for iPoint in range(nNode): + Source.Set(iPoint, 0, HeatSource(Coords.Get(iPoint)) / (DENSITY * CP)) + + if rank == 0: + print("\n------------------------------ Begin Solver -----------------------------\n") + sys.stdout.flush() + + driver.StartSolver() + + # Compare with the exact solution T(x) = T_WALL + q*x*(L-x)/(2*k). + Solution = driver.Solution(iHEATSOLVER) + max_error = 0.0 + for iPoint in range(nNode): + x = Coords.Get(iPoint)[0] + exact = T_WALL + SOURCE_VAL * x * (L - x) / (2 * CONDUCTIVITY) + max_error = max(max_error, abs(Solution.Get(iPoint)[0] - exact)) + max_error = comm.allreduce(max_error, op=MPI.MAX) + + # All the heat added by the source has to leave through the isothermal walls. + heat_flux = driver.GetOutputValue('TOTAL_HEATFLUX') + + driver.Finalize() + + # The isothermal wall is imposed weakly, which shifts the solution by q*h^2/(2*k). + h = L / (N_X - 1) + tolerance = 1.01 * SOURCE_VAL * h**2 / (2 * CONDUCTIVITY) + if rank == 0: + print(f"Maximum temperature error: {max_error:.6f} K (tolerance {tolerance:.6f} K)") + print(f"Total heat flux: {heat_flux:.6f} W (exact {-SOURCE_VAL * L * H:.6f} W)") + + assert max_error < tolerance, f"Test FAILED, maximum temperature error = {max_error}" + assert abs(heat_flux / (-SOURCE_VAL * L * H) - 1) < 1e-6, f"Test FAILED, total heat flux = {heat_flux}" + + +if __name__ == '__main__': + main() diff --git a/UnitTests/Common/CConfig_tests.cpp b/UnitTests/Common/CConfig_tests.cpp index 111918785545..70ac2f0d22a1 100644 --- a/UnitTests/Common/CConfig_tests.cpp +++ b/UnitTests/Common/CConfig_tests.cpp @@ -72,8 +72,56 @@ const std::string mixture_options = "PRANDTL_TURB= 0.9, 0.9\n" "DIFFUSIVITY_CONSTANT= 1.0e-05\n"; +/*--- Solid heat conduction on a rectangle mesh, used by the HEAT_SOURCE checks. ---*/ +const std::string heat_equation_options = + "SOLVER= HEAT_EQUATION\n" + "INC_NONDIM= DIMENSIONAL\n" + "MESH_FORMAT= RECTANGLE\n" + "MESH_BOX_SIZE= 5,5,0\n" + "MESH_BOX_LENGTH= 1,1,0\n" + "MARKER_ISOTHERMAL= (x_minus, 300.0, x_plus, 300.0)\n" + "MARKER_HEATFLUX= (y_minus, 0.0, y_plus, 0.0)\n"; + +const std::string heat_source_options = + "HEAT_SOURCE= YES\n" + "HEAT_SOURCE_VAL= 1e6\n"; + +/*--- Builds a config quietly, so that the test output only shows the result. ---*/ +bool GetHeatSource(const std::string& options) { + std::stringstream ss(options); + auto* orig_buf = std::cout.rdbuf(nullptr); + CConfig config(ss, SU2_COMPONENT::SU2_CFD, false); + std::cout.rdbuf(orig_buf); + return config.GetHeatSource(); +} + +/*--- Builds a config with the output left on, so that the message of SU2_MPI::Error reaches the runner. ---*/ +void BuildConfig(const std::string& options) { + std::stringstream ss(options); + CConfig config(ss, SU2_COMPONENT::SU2_CFD, false); +} + } // namespace +TEST_CASE("HEAT_SOURCE is accepted with a radiation model", "[Config]") { + CHECK(GetHeatSource(base_options + ideal_gas_options + heat_source_options + "RADIATION_MODEL= P1\n")); + CHECK_FALSE(GetHeatSource(heat_equation_options)); +} + +/*--- Hidden tests, SU2_MPI::Error aborts the process. check_config_errors.py runs each one on its own and checks + * that it fails with the expected message. ---*/ + +TEST_CASE("HEAT_SOURCE is rejected for the heat equation", "[.HeatSourceHeatEquation]") { + BuildConfig(heat_equation_options + heat_source_options); + /*--- Returning is a successful exit, which check_config_errors.py treats as a failure. ---*/ + SUCCEED("HEAT_SOURCE without a radiation model was not rejected"); +} + +TEST_CASE("HEAT_SOURCE is rejected for the incompressible solver without radiation", "[.HeatSourceIncompressible]") { + BuildConfig(base_options + ideal_gas_options + heat_source_options); + SUCCEED("HEAT_SOURCE without a radiation model was not rejected"); +} + TEST_CASE("INIT_OPTION_INC defaults", "[Config]") { /*--- Without the option each fluid model keeps the density it used before INIT_OPTION_INC existed. ---*/ diff --git a/UnitTests/Common/check_config_errors.py b/UnitTests/Common/check_config_errors.py new file mode 100644 index 000000000000..8103670d4901 --- /dev/null +++ b/UnitTests/Common/check_config_errors.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +"""Run the hidden CConfig tests that must stop with an error (SU2_MPI::Error terminates the process).""" + +import argparse +import subprocess + +# Each hidden test tag, with the text its error message has to contain. +CASES = ( + ("HeatSourceHeatEquation", "HEAT_SOURCE= YES requires a RADIATION_MODEL"), + ("HeatSourceIncompressible", "HEAT_SOURCE= YES requires a RADIATION_MODEL"), +) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("test_driver") + parser.add_argument( + "--launcher", + nargs=argparse.REMAINDER, + default=[], + help="Optional MPI launcher and arguments, for example mpiexec -n 2", + ) + args = parser.parse_args() + for tag, expected in CASES: + command = args.launcher + [args.test_driver, "[." + tag + "]"] + result = subprocess.run( + command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + timeout=30, + check=False, + ) + if result.returncode == 0 or expected not in result.stdout: + raise RuntimeError( + "Config check " + + tag + + " did not stop with the expected error:\n" + + result.stdout + ) + print("PASS: " + tag + " stops with: " + expected, flush=True) + + +if __name__ == "__main__": + main() diff --git a/UnitTests/Common/toolboxes/geometry_toolbox_tests.cpp b/UnitTests/Common/toolboxes/geometry_toolbox_tests.cpp new file mode 100644 index 000000000000..ac2b08460dcd --- /dev/null +++ b/UnitTests/Common/toolboxes/geometry_toolbox_tests.cpp @@ -0,0 +1,55 @@ +/*! + * \file geometry_toolbox_tests.cpp + * \brief Unit tests for the geometry toolbox. + * \author P. Gaur + * \version 8.5.0 "Harrier" + * + * SU2 Project Website: https://su2code.github.io + * + * The SU2 Project is maintained by the SU2 Foundation + * (http://su2foundation.org) + * + * Copyright 2012-2026, SU2 Contributors + * + * SU2 is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * SU2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with SU2. If not, see . + */ + +#include "catch.hpp" +#include "../../Common/include/toolboxes/geometry_toolbox.hpp" + +TEST_CASE("PointInEllipsoid", "[Toolboxes]") { + const double center[3] = {1.0, 2.0, 3.0}; + const double axes[3] = {2.0, 1.0, 0.5}; + const double pi = 3.14159265358979323846; + + /*--- 2D, unrotated: the x semi-axis is 2 and the y semi-axis is 1. ---*/ + const double inX[3] = {2.9, 2.0, 0.0}; + const double outY[3] = {1.0, 3.1, 0.0}; + CHECK(GeometryToolbox::PointInEllipsoid(2, center, center, axes, 0.0)); + CHECK(GeometryToolbox::PointInEllipsoid(2, inX, center, axes, 0.0)); + CHECK_FALSE(GeometryToolbox::PointInEllipsoid(2, outY, center, axes, 0.0)); + + /*--- 2D, rotated by 90 degrees about z: the long axis now lies along y. ---*/ + CHECK_FALSE(GeometryToolbox::PointInEllipsoid(2, inX, center, axes, 0.5 * pi)); + const double inYrot[3] = {1.0, 3.9, 0.0}; + CHECK(GeometryToolbox::PointInEllipsoid(2, inYrot, center, axes, 0.5 * pi)); + + /*--- 3D: the z coordinate is measured from the center and scaled by the z semi-axis. ---*/ + const double inZ[3] = {1.0, 2.0, 3.4}; + const double outZ[3] = {1.0, 2.0, 3.6}; + const double atOrigin[3] = {1.0, 2.0, 0.0}; + CHECK(GeometryToolbox::PointInEllipsoid(3, inZ, center, axes, 0.0)); + CHECK_FALSE(GeometryToolbox::PointInEllipsoid(3, outZ, center, axes, 0.0)); + CHECK_FALSE(GeometryToolbox::PointInEllipsoid(3, atOrigin, center, axes, 0.0)); +} diff --git a/UnitTests/meson.build b/UnitTests/meson.build index 1d1a97f3b827..f01c2ff0f619 100644 --- a/UnitTests/meson.build +++ b/UnitTests/meson.build @@ -24,6 +24,7 @@ su2_cfd_tests = files(['Common/CConfig_tests.cpp', 'SU2_CFD/nemo_viscous_assembly.cpp', 'SU2_CFD/windowing.cpp', 'Common/toolboxes/random_toolbox_tests.cpp', + 'Common/toolboxes/geometry_toolbox_tests.cpp', 'Common/linear_algebra/quantization_tests.cpp', 'Common/linear_algebra/edge_residual_blocks_tests.cpp']) @@ -54,6 +55,9 @@ if get_option('enable-tests') test('NEMO convergence field migration', python, args: files('SU2_CFD/output/check_nemo_convergence_migration.py') + [test_driver.full_path()], timeout: 300) + test('CConfig option errors', python, + args: files('Common/check_config_errors.py') + [test_driver.full_path()], + timeout: 300) endif if get_option('enable-autodiff') diff --git a/config_template.cfg b/config_template.cfg index 5e611f22ed48..d8ef73cf07c3 100644 --- a/config_template.cfg +++ b/config_template.cfg @@ -898,10 +898,11 @@ ABSORPTION_COEFF = 1.0 % Scattering coefficient SCATTERING_COEFF = 0.0 % -% Apply a volumetric heat source as a source term (NO, YES) in the form of an ellipsoid (YES, NO) +% Apply a volumetric heat source as a source term (NO, YES) in the form of an ellipsoid (YES, NO). +% It requires a RADIATION_MODEL. For other solvers, use PYTHON_CUSTOM_SOURCE with the python wrapper. HEAT_SOURCE = NO % -% Value of the volumetric heat source +% Value of the volumetric heat source (W/m^3) HEAT_SOURCE_VAL = 1.0E6 % % Rotation of the volumetric heat source respect to Z axis (degrees)