diff --git a/SU2_CFD/src/solvers/CEulerSolver.cpp b/SU2_CFD/src/solvers/CEulerSolver.cpp index 32c265e55656..2b040b298b3c 100644 --- a/SU2_CFD/src/solvers/CEulerSolver.cpp +++ b/SU2_CFD/src/solvers/CEulerSolver.cpp @@ -7766,6 +7766,8 @@ void CEulerSolver::BC_Supersonic_Outlet(CGeometry *geometry, CSolver **solver_co conv_numerics->SetNormal(Normal); conv_numerics->SetPrimitive(V_domain, V_outlet); + if (config->GetKind_FluidModel() != STANDARD_AIR && config->GetKind_FluidModel() != IDEAL_GAS) + conv_numerics->SetSecondary(nodes->GetSecondary(iPoint), nodes->GetSecondary(iPoint)); if (dynamic_grid) conv_numerics->SetGridVel(geometry->nodes->GetGridVel(iPoint), @@ -7803,6 +7805,8 @@ void CEulerSolver::BC_Supersonic_Outlet(CGeometry *geometry, CSolver **solver_co /*--- Primitive variables, and gradient ---*/ visc_numerics->SetPrimitive(V_domain, V_domain); + if (config->GetKind_FluidModel() != STANDARD_AIR && config->GetKind_FluidModel() != IDEAL_GAS) + visc_numerics->SetSecondary(nodes->GetSecondary(iPoint), nodes->GetSecondary(iPoint)); visc_numerics->SetPrimVarGradient(nodes->GetGradient_Primitive(iPoint), nodes->GetGradient_Primitive(iPoint)); /*--- Turbulent kinetic energy ---*/ diff --git a/UnitTests/SU2_CFD/solvers/supersonic_outlet_tests.cpp b/UnitTests/SU2_CFD/solvers/supersonic_outlet_tests.cpp new file mode 100644 index 000000000000..62d635a8507b --- /dev/null +++ b/UnitTests/SU2_CFD/solvers/supersonic_outlet_tests.cpp @@ -0,0 +1,123 @@ +/*! + * \file supersonic_outlet_tests.cpp + * \brief Regression for extrapolated supersonic outlet thermodynamic data. + * \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 . + */ + +#include "catch.hpp" +#include "../../UnitQuadTestCase.hpp" +#include "../../../SU2_CFD/include/variables/CNSVariable.hpp" +#include "../../../SU2_CFD/include/fluid/CPengRobinson.hpp" +#include "../../../SU2_CFD/include/numerics/flow/convection/hllc.hpp" +#include "../../../SU2_CFD/include/numerics/flow/flow_diffusion.hpp" + +TEST_CASE("Supersonic outlet supplies general-gas thermodynamic data", "[Supersonic-outlet]") { + for (const auto nDim : {2u, 3u}) { + for (const bool realGas : {true, false}) { + for (const bool moving : {false, true}) { + for (const bool viscous : {false, true}) { + UnitQuadTestCase test; + test.config_options = + std::string(viscous ? "SOLVER= NAVIER_STOKES\n" : "SOLVER= EULER\n") + + "MESH_FORMAT= " + std::string(nDim == 2 ? "RECTANGLE" : "BOX") + + "\nMESH_BOX_SIZE= 5,5,5\nREF_DIMENSIONALIZATION= DIMENSIONAL\nMACH_NUMBER= 0.1\n" + "FREESTREAM_OPTION= DENSITY_FS\nFREESTREAM_DENSITY= 230\nFREESTREAM_PRESSURE= 1500112\n" + "GAS_CONSTANT= 35.152\nCONV_NUM_METHOD_FLOW= HLLC\nTIME_DISCRE_FLOW= EULER_IMPLICIT\n" + "REYNOLDS_NUMBER= 1E6\nREYNOLDS_LENGTH= 1\n" + "VISCOSITY_MODEL= CONSTANT_VISCOSITY\nCONDUCTIVITY_MODEL= CONSTANT_CONDUCTIVITY\n" + "GRID_MOVEMENT= STEADY_TRANSLATION\nTRANSLATION_RATE= 0,0,0\n" + "MARKER_SUPERSONIC_OUTLET= (x_plus)\nMARKER_MONITORING= (x_plus)\nMARKER_EULER= " + + std::string(nDim == 2 ? "(x_minus,y_minus,y_plus)\n" : "(x_minus,y_minus,y_plus,z_minus,z_plus)\n") + + (realGas ? "FLUID_MODEL= PR_GAS\nGAMMA_VALUE= 1.0125\nCRITICAL_PRESSURE= 1415200\n" + "CRITICAL_TEMPERATURE= 564.1\nACENTRIC_FACTOR= 0.529\n" + : "FLUID_MODEL= IDEAL_GAS\nGAMMA_VALUE= 1.4\n"); + test.InitConfig(); + test.InitGeometry(); + test.InitSolver(); + std::unique_ptr gas = + realGas ? std::unique_ptr(new CPengRobinson(1.0125, 35.152, 1415200, 564.1, .529)) + : std::unique_ptr(new CIdealGas(1.4, 35.152)); + gas->SetLaminarViscosityModel(test.config.get()); + gas->SetThermalConductivityModel(test.config.get()); + gas->SetTDState_Prho(1500112, 230); + const auto nVar = nDim + 2; + const su2double grid[3] = {moving ? 20.0 : 0.0, 0, 0}; + const su2double velocity[3] = {2 * gas->GetSoundSpeed() + grid[0], 2, 0}; + const su2double density = gas->GetDensity(), pressure = gas->GetPressure(); + const auto energy = gas->GetStaticEnergy() + .5 * GeometryToolbox::SquaredNorm(nDim, velocity); + const su2double enthalpy = energy + pressure / density; + su2double state[5] = {density}; + for (auto i = 0u; i < nDim; ++i) state[i + 1] = density * velocity[i]; + state[nVar - 1] = density * energy; + auto* solver = test.solver[FLOW_SOL]; + auto* nodes = static_cast(solver->GetNodes()); + for (auto p = 0ul; p < test.geometry->GetnPoint(); ++p) { + nodes->SetSolution(p, state); + nodes->SetSolution_Old(p, state); + if (viscous) { + REQUIRE(static_cast(nodes)->SetPrimVar(p, 0, 0, gas.get())); + static_cast(nodes)->SetSecondaryVar(p, gas.get()); + } else { + REQUIRE(nodes->SetPrimVar(p, gas.get())); + nodes->SetSecondaryVar(p, gas.get()); + } + test.geometry->nodes->SetGridVel(p, grid); + for (auto i = 0u; i < nVar; ++i) solver->LinSysRes(p, i) = 0; + } + std::unique_ptr numerics = + realGas ? std::unique_ptr(new CUpwGeneralHLLC_Flow(nDim, nVar, test.config.get())) + : std::unique_ptr(new CUpwHLLC_Flow(nDim, nVar, test.config.get())); + std::unique_ptr diffusion; + if (viscous) { + if (realGas) + diffusion.reset(new CGeneralAvgGrad_Flow(nDim, nVar, true, test.config.get())); + else + diffusion.reset(new CAvgGrad_Flow(nDim, nVar, true, test.config.get())); + } + for (auto marker = 0u; marker < test.config->GetnMarker_All(); ++marker) { + if (test.config->GetMarker_All_TagBound(marker) != "x_plus") continue; + solver->BC_Supersonic_Outlet(test.geometry.get(), test.solver, numerics.get(), diffusion.get(), + test.config.get(), marker); + for (auto v = 0ul; v < test.geometry->nVertex[marker]; ++v) { + const auto p = test.geometry->vertex[marker][v]->GetNode(); + su2double normal[3], flux[5]; + test.geometry->vertex[marker][v]->GetNormal(normal); + for (auto i = 0u; i < nDim; ++i) normal[i] = -normal[i]; + numerics->GetInviscidProjFlux(&density, velocity, &pressure, &enthalpy, normal, flux); + numerics->SetNormal(normal); + numerics->SetPrimitive(nodes->GetPrimitive(p), nodes->GetPrimitive(p)); + if (realGas) numerics->SetSecondary(nodes->GetSecondary(p), nodes->GetSecondary(p)); + const auto reference = numerics->ComputeResidual(test.config.get()); + for (auto i = 0u; i < nVar; ++i) { + // The boundary must use the same extrapolated state as an interior equal-state flux. + CHECK(solver->LinSysRes(p, i) == Approx(SU2_TYPE::GetValue(reference.residual[i]))); + if (!moving) CHECK(solver->LinSysRes(p, i) == Approx(SU2_TYPE::GetValue(flux[i]))); + for (auto j = 0u; j < nVar; ++j) CHECK(std::isfinite(solver->Jacobian.GetBlock(p, p)[i * nVar + j])); + } + } + } + } + } + } + } +} diff --git a/UnitTests/meson.build b/UnitTests/meson.build index ba3c63afce92..9f5e19f99709 100644 --- a/UnitTests/meson.build +++ b/UnitTests/meson.build @@ -20,6 +20,7 @@ su2_cfd_tests = files(['Common/CConfig_tests.cpp', 'SU2_CFD/output/COutput_convergence_tests.cpp', 'SU2_CFD/gradients.cpp', 'SU2_CFD/solvers/CNEMOEulerSolver_tests.cpp', + 'SU2_CFD/solvers/supersonic_outlet_tests.cpp', 'SU2_CFD/nemo_viscous_assembly.cpp', 'SU2_CFD/windowing.cpp', 'Common/toolboxes/random_toolbox_tests.cpp',