From 4517692539b3e7ec60d54c585279e31f12fcd55c Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Wed, 29 Apr 2026 18:31:19 +0200 Subject: [PATCH 01/61] update release drafter (#2805) --- .github/workflows/release-management.yml | 55 ++++++++++++++---------- TestCases/serial_regression.py | 2 + 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/.github/workflows/release-management.yml b/.github/workflows/release-management.yml index 8acd87d4b2dd..7a560b1a94fb 100644 --- a/.github/workflows/release-management.yml +++ b/.github/workflows/release-management.yml @@ -2,17 +2,27 @@ name: Release Management on: push: - # branches to consider in the event; optional, defaults to all branches: - develop +permissions: + contents: write + jobs: build_and_upload: - name: Build SU2 + name: Build SU2 (${{ matrix.os_bin }}) + runs-on: ubuntu-latest + strategy: fail-fast: false matrix: - os_bin: [macos64, macos64-mpi, linux64-omp, linux64-mpi, win64-omp, win64-mpi] + os_bin: + - macos64 + - macos64-mpi + - linux64-omp + - linux64-mpi + - win64-omp + - win64-mpi include: - os_bin: win64-omp flags: '-Dcpu-arch=haswell -Dwith-omp=true -Dwith-mpi=disabled --cross-file=/hostfiles/hostfile_windows' @@ -26,42 +36,43 @@ jobs: flags: '-Dcpu-arch=haswell -Dwith-omp=true -Dwith-mpi=disabled -Dstatic-cgns-deps=true --cross-file=/hostfiles/hostfile_linux' - os_bin: linux64-mpi flags: '-Dcpu-arch=haswell -Dcustom-mpi=true --cross-file=/hostfiles/hostfile_linux_mpi' - runs-on: ubuntu-latest + steps: - name: Cache Object Files - uses: actions/cache@v5 + uses: actions/cache@v4 with: path: ccache key: ${{ matrix.os_bin }}-${{ github.sha }} restore-keys: ${{ matrix.os_bin }} + - name: Build uses: docker://ghcr.io/su2code/su2/build-su2-cross:260405-0054 with: - args: -b ${{ github.sha }} -f "${{matrix.flags}}" + args: -b ${{ github.sha }} -f "${{ matrix.flags }}" + - name: Create Archive run: | cd install - zip -r ../${{matrix.os_bin}}.zip bin/* - # Uploads binaries as artifacts (just as a backup) - - name: Upload Binaries - uses: actions/upload-artifact@v7 + zip -r ../${{ matrix.os_bin }}.zip bin/* + + - name: Upload Workflow Artifact + uses: actions/upload-artifact@v5 with: - name: ${{matrix.os_bin}} - path: ${{matrix.os_bin}}.zip - # Update the release notes of latest draft release - - uses: talbring/jenkins-release-drafter@v5.2.0-jenkins-11 - name: Update Release - id: update_release + name: ${{ matrix.os_bin }} + path: ${{ matrix.os_bin }}.zip + + - name: Update Release Draft + id: release_drafter + uses: release-drafter/release-drafter@v6 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Upload binaries as assets to draft release + - name: Upload Release Asset - id: upload-release-asset - uses: actions/upload-release-asset@v1.0.2 + uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - upload_url: ${{ steps.update_release.outputs.uploadurl }} - asset_path: ${{matrix.os_bin}}.zip - asset_name: SU2-${{ steps.update_release.outputs.tagname }}-${{matrix.os_bin}}.zip + upload_url: ${{ steps.release_drafter.outputs.upload_url }} + asset_path: ${{ matrix.os_bin }}.zip + asset_name: SU2-${{ steps.release_drafter.outputs.tag_name }}-${{ matrix.os_bin }}.zip asset_content_type: application/zip diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index 279923612d6b..aa8583f818c9 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -892,6 +892,7 @@ def main(): Aachen_3D_restart.cfg_file = "aachen_3D_MP_restart.cfg" Aachen_3D_restart.test_iter = 5 Aachen_3D_restart.test_vals = [-7.701448, -8.512353, -6.014939, -6.468417, -5.801739, -4.607173, -5.550692, -5.300771, -3.804187, -5.256008, -5.765048, -3.609601, -2.229277, -2.883894, -0.563470] + Aachen_3D_restart.enabled_with_asan = False test_list.append(Aachen_3D_restart) # Jones APU Turbocharger restart @@ -972,6 +973,7 @@ def main(): channel_3D.test_vals_aarch64 = [1.000000, 0.000000, 0.611996, 0.798988, 0.702357] channel_3D.unsteady = True channel_3D.multizone = True + channel_3D.enabled_with_asan = False test_list.append(channel_3D) # Pipe From 0fcf5bb7912b3021ce369cb6c75862db0b1a3c53 Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Thu, 7 May 2026 10:46:23 +0100 Subject: [PATCH 02/61] Allow FSI+CHT (aerothermoelasticity) (#2807) * allow fsi+cht * Apply suggestions from code review Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> * set regression values * back to develop --- Common/src/CConfig.cpp | 4 +- SU2_CFD/include/interfaces/CInterface.hpp | 12 +- SU2_CFD/src/drivers/CDriver.cpp | 72 +++++---- SU2_CFD/src/drivers/CMultizoneDriver.cpp | 66 +++++--- SU2_CFD/src/interfaces/CInterface.cpp | 2 + .../interfaces/fsi/CFlowTractionInterface.cpp | 4 +- SU2_CFD/src/solvers/CHeatSolver.cpp | 1 + TestCases/fea_fsi/stat_fsi/config.cfg | 34 ++-- TestCases/fea_fsi/stat_fsi/configFEA.cfg | 115 ++++++------- TestCases/fea_fsi/stat_fsi/configFlow.cfg | 152 +++++++----------- TestCases/fea_fsi/stat_fsi/config_restart.cfg | 35 ++-- TestCases/hybrid_regression.py | 17 +- TestCases/parallel_regression.py | 18 --- TestCases/serial_regression.py | 27 ++-- 14 files changed, 264 insertions(+), 295 deletions(-) diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index 0f7a5c6821fa..e6374b314ad1 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -1311,9 +1311,9 @@ void CConfig::SetConfig_Options() { /* DESCRIPTION: Definition of the turbulent thermal conductivity model (CONSTANT_PRANDTL_TURB (default), NONE). */ addEnumOption("TURBULENT_CONDUCTIVITY_MODEL", Kind_ConductivityModel_Turb, TurbConductivityModel_Map, CONDUCTIVITYMODEL_TURB::CONSTANT_PRANDTL); - /*--- Options related to Constant Thermal Conductivity Model ---*/ + /*--- Options related to Constant Thermal Conductivity Model ---*/ - /* DESCRIPTION: default value for AIR */ + /* DESCRIPTION: default value for AIR */ addDoubleListOption("THERMAL_CONDUCTIVITY_CONSTANT", nThermal_Conductivity_Constant , Thermal_Conductivity_Constant); /*--- Options related to temperature polynomial coefficients for fluid models. ---*/ diff --git a/SU2_CFD/include/interfaces/CInterface.hpp b/SU2_CFD/include/interfaces/CInterface.hpp index 7480f970ce4a..23a5621c4929 100644 --- a/SU2_CFD/include/interfaces/CInterface.hpp +++ b/SU2_CFD/include/interfaces/CInterface.hpp @@ -29,6 +29,7 @@ #pragma once #include "../../../Common/include/parallelization/mpi_structure.hpp" +#include "../../../Common/include/option_structure.hpp" #include #include @@ -77,7 +78,7 @@ class CInterface { /*! * \brief Constructor of the class. */ - CInterface(void); + CInterface(); /*! * \overload @@ -89,7 +90,7 @@ class CInterface { /*! * \brief Destructor of the class. */ - virtual ~CInterface(void); + virtual ~CInterface(); /*! * \brief Interpolate data and broadcast it into all processors, for nonmatching meshes. @@ -224,4 +225,11 @@ class CInterface { * \param[in] val_contact_resistance - Contact resistance value in m^2/W */ inline virtual void SetContactResistance(su2double val_contact_resistance) {}; + + /*! + * \brief These can be used to chain interfaces between the same zones but for other variables, + * without having to mix physics in the interface classes. Currently this is used for FSI+CHT. + */ + ENUM_TRANSFER NextInterfaceType = ENUM_TRANSFER::NO_TRANSFER; + CInterface* NextInterface = nullptr; }; diff --git a/SU2_CFD/src/drivers/CDriver.cpp b/SU2_CFD/src/drivers/CDriver.cpp index 5bdeb9ce1af7..daf5037a538e 100644 --- a/SU2_CFD/src/drivers/CDriver.cpp +++ b/SU2_CFD/src/drivers/CDriver.cpp @@ -2443,6 +2443,31 @@ void CDriver::InitializeInterface(CConfig **config, CSolver***** solver, CGeomet interpolation[donor][target] = unique_ptr(CInterpolatorFactory::CreateInterpolator( geometry, config, interpolation[target][donor].get(), donor, target)); + /*--- Helpers with logic to create CHT interfaces. ---*/ + + auto GetChtInterfaceType = [donor, target, config](bool heat_donor, bool heat_target) { + if (heat_donor && heat_target) return CONJUGATE_HEAT_SS; + + const auto fluidZone = heat_target ? donor : target; + if (config[fluidZone]->GetEnergy_Equation() || + config[fluidZone]->GetKind_Regime() == ENUM_REGIME::COMPRESSIBLE || + config[fluidZone]->GetKind_FluidModel() == ENUM_FLUIDMODEL::FLUID_FLAMELET) { + return heat_target ? CONJUGATE_HEAT_FS : CONJUGATE_HEAT_SF; + } else if (config[fluidZone]->GetWeakly_Coupled_Heat()) { + return heat_target ? CONJUGATE_HEAT_WEAKLY_FS : CONJUGATE_HEAT_WEAKLY_SF; + } + return NO_TRANSFER; + }; + + auto MakeChtInterface = [&](const auto type) { + if (type != NO_TRANSFER) { + if (rank == MASTER_NODE) cout << " Conjugate heat variables." << endl; + return new CConjugateHeatInterface(4, 0); + } + if (rank == MASTER_NODE) cout << " NO heat variables." << endl; + return static_cast(nullptr); + }; + /*--- The type of variables transferred depends on the donor/target physics. ---*/ const bool heat_target = config[target]->GetHeatProblem(); @@ -2467,6 +2492,11 @@ void CDriver::InitializeInterface(CConfig **config, CSolver***** solver, CGeomet interface[donor][target] = new CDiscAdjFlowTractionInterface(nDim, nConst, config[donor], conservative); } if (rank == MASTER_NODE) cout << "fluid " << (conservative? "forces." : "tractions.") << endl; + + if (config[target]->GetWeakly_Coupled_Heat()) { + interface[donor][target]->NextInterfaceType = GetChtInterfaceType(false, true); + interface[donor][target]->NextInterface = MakeChtInterface(interface[donor][target]->NextInterfaceType); + } } else if (structural_donor && (fluid_target || heat_target)) { if (solver_container[target][INST_0][MESH_0][MESH_SOL] == nullptr) { @@ -2474,9 +2504,14 @@ void CDriver::InitializeInterface(CConfig **config, CSolver***** solver, CGeomet "Use DEFORM_MESH=YES, and setup MARKER_DEFORM_MESH=(...)", CURRENT_FUNCTION); } interface_type = BOUNDARY_DISPLACEMENTS; - if (!config[donor]->GetTime_Domain()) interface[donor][target] = new CDisplacementsInterface(nDim, 0); - else interface[donor][target] = new CDisplacementsInterface(2*nDim, 0); + const auto nVar = config[donor]->GetTime_Domain() ? 2 * nDim : nDim; + interface[donor][target] = new CDisplacementsInterface(nVar, 0); if (rank == MASTER_NODE) cout << "boundary displacements from the structural solver." << endl; + + if (fluid_target && config[donor]->GetWeakly_Coupled_Heat()) { + interface[donor][target]->NextInterfaceType = GetChtInterfaceType(true, false); + interface[donor][target]->NextInterface = MakeChtInterface(interface[donor][target]->NextInterfaceType); + } } else if (fluid_donor && fluid_target) { /*--- Interface handling for turbomachinery applications. ---*/ @@ -2487,14 +2522,14 @@ void CDriver::InitializeInterface(CConfig **config, CSolver***** solver, CGeomet interface_type = MIXING_PLANE; auto nVar = solver[donor][INST_0][MESH_0][FLOW_SOL]->GetnVar(); interface[donor][target] = new CMixingPlaneInterface(nVar, 0); - if (rank == MASTER_NODE) cout << "using a mixing-plane interface from donor zone " << donor << " to target zone " << target << "." << endl; + if (rank == MASTER_NODE) cout << " Using a mixing-plane interface from donor zone " << donor << " to target zone " << target << "." << endl; break; } case TURBO_INTERFACE_KIND::FROZEN_ROTOR: { auto nVar = solver[donor][INST_0][MESH_0][FLOW_SOL]->GetnPrimVar(); interface_type = SLIDING_INTERFACE; interface[donor][target] = new CSlidingInterface(nVar, 0); - if (rank == MASTER_NODE) cout << "using a fluid interface interface from donor zone " << donor << " to target zone " << target << "." << endl; + if (rank == MASTER_NODE) cout << " Using a fluid interface interface from donor zone " << donor << " to target zone " << target << "." << endl; } } } @@ -2502,33 +2537,12 @@ void CDriver::InitializeInterface(CConfig **config, CSolver***** solver, CGeomet auto nVar = solver[donor][INST_0][MESH_0][FLOW_SOL]->GetnPrimVar(); interface_type = SLIDING_INTERFACE; interface[donor][target] = new CSlidingInterface(nVar, 0); - if (rank == MASTER_NODE) cout << "sliding interface." << endl; + if (rank == MASTER_NODE) cout << " Sliding interface." << endl; } } else if (heat_donor || heat_target) { - if (heat_donor && heat_target){ - interface_type = CONJUGATE_HEAT_SS; - - } else { - - const auto fluidZone = heat_target? donor : target; - if (config[fluidZone]->GetEnergy_Equation() || (config[fluidZone]->GetKind_Regime() == ENUM_REGIME::COMPRESSIBLE) - || (config[fluidZone]->GetKind_FluidModel() == ENUM_FLUIDMODEL::FLUID_FLAMELET)) - interface_type = heat_target? CONJUGATE_HEAT_FS : CONJUGATE_HEAT_SF; - else if (config[fluidZone]->GetWeakly_Coupled_Heat()) - interface_type = heat_target? CONJUGATE_HEAT_WEAKLY_FS : CONJUGATE_HEAT_WEAKLY_SF; - else - interface_type = NO_TRANSFER; - } - - if (interface_type != NO_TRANSFER) { - auto nVar = 4; - interface[donor][target] = new CConjugateHeatInterface(nVar, 0); - if (rank == MASTER_NODE) cout << "conjugate heat variables." << endl; - } - else { - if (rank == MASTER_NODE) cout << "NO heat variables." << endl; - } + interface_type = GetChtInterfaceType(heat_donor, heat_target); + interface[donor][target] = MakeChtInterface(interface_type); } else { if (solver[donor][INST_0][MESH_0][FLOW_SOL] == nullptr) @@ -2537,7 +2551,7 @@ void CDriver::InitializeInterface(CConfig **config, CSolver***** solver, CGeomet auto nVar = solver[donor][INST_0][MESH_0][FLOW_SOL]->GetnVar(); interface_type = CONSERVATIVE_VARIABLES; interface[donor][target] = new CConservativeVarsInterface(nVar, 0); - if (rank == MASTER_NODE) cout << "generic conservative variables." << endl; + if (rank == MASTER_NODE) cout << " Generic conservative variables." << endl; } } diff --git a/SU2_CFD/src/drivers/CMultizoneDriver.cpp b/SU2_CFD/src/drivers/CMultizoneDriver.cpp index d6f68a6682f6..00bdb364a449 100644 --- a/SU2_CFD/src/drivers/CMultizoneDriver.cpp +++ b/SU2_CFD/src/drivers/CMultizoneDriver.cpp @@ -549,19 +549,19 @@ bool CMultizoneDriver::TransferData(unsigned short donorZone, unsigned short tar /*--- Select the transfer method according to the magnitudes being transferred ---*/ - auto BroadcastData = [&](int donorSol, int targetSol) { - interface_container[donorZone][targetZone]->BroadcastData( - *interpolator_container[donorZone][targetZone].get(), - solver_container[donorZone][INST_0][MESH_0][donorSol], - solver_container[targetZone][INST_0][MESH_0][targetSol], - geometry_container[donorZone][INST_0][MESH_0], - geometry_container[targetZone][INST_0][MESH_0], - config_container[donorZone], - config_container[targetZone]); - }; - - switch (interface_types[donorZone][targetZone]) { - + auto HandleInterfaceType = [&] (const auto interface_type, auto* interface) { + auto BroadcastData = [&](int donorSol, int targetSol) { + interface->BroadcastData( + *interpolator_container[donorZone][targetZone], + solver_container[donorZone][INST_0][MESH_0][donorSol], + solver_container[targetZone][INST_0][MESH_0][targetSol], + geometry_container[donorZone][INST_0][MESH_0], + geometry_container[targetZone][INST_0][MESH_0], + config_container[donorZone], + config_container[targetZone]); + }; + + switch (interface_type) { case SLIDING_INTERFACE: BroadcastData(FLOW_SOL, FLOW_SOL); @@ -598,35 +598,49 @@ bool CMultizoneDriver::TransferData(unsigned short donorZone, unsigned short tar case FLOW_TRACTION: BroadcastData(FLOW_SOL, FEA_SOL); break; - case MIXING_PLANE: - { + case MIXING_PLANE: { const auto nMarkerInt = config_container[donorZone]->GetnMarker_MixingPlaneInterface() / 2; - /*--- Transfer the average value from the donorZone to the targetZone ---*/ - /*--- Loops over the mixing planes defined in the config file to find the correct mixing plane for the donor-target combination ---*/ + /*--- Transfer the average value from the donorZone to the targetZone + * Loops over the mixing planes defined in the config file to find the + * correct mixing plane for the donor-target combination ---*/ for (auto iMarkerInt = 1; iMarkerInt <= nMarkerInt; iMarkerInt++) { - interface_container[donorZone][targetZone]->AllgatherAverage(solver_container[donorZone][INST_0][MESH_0][FLOW_SOL],solver_container[targetZone][INST_0][MESH_0][FLOW_SOL], - geometry_container[donorZone][INST_0][MESH_0],geometry_container[targetZone][INST_0][MESH_0], - config_container[donorZone], config_container[targetZone], iMarkerInt ); + interface->AllgatherAverage( + solver_container[donorZone][INST_0][MESH_0][FLOW_SOL], + solver_container[targetZone][INST_0][MESH_0][FLOW_SOL], + geometry_container[donorZone][INST_0][MESH_0], + geometry_container[targetZone][INST_0][MESH_0], + config_container[donorZone], config_container[targetZone], iMarkerInt); } /*--- Set average value donorZone->targetZone ---*/ - interface_container[donorZone][targetZone]->SetAverageValues(solver_container[donorZone][INST_0][MESH_0][FLOW_SOL],solver_container[targetZone][INST_0][MESH_0][FLOW_SOL], donorZone); + interface->SetAverageValues(solver_container[donorZone][INST_0][MESH_0][FLOW_SOL], + solver_container[targetZone][INST_0][MESH_0][FLOW_SOL], donorZone); /*--- Set average geometrical properties FROM donorZone IN targetZone ---*/ - geometry_container[targetZone][INST_0][MESH_0]->SetAvgTurboGeoValues(config_container[iZone],geometry_container[iZone][INST_0][MESH_0], iZone); - - break; - } + geometry_container[targetZone][INST_0][MESH_0]->SetAvgTurboGeoValues( + config_container[iZone], geometry_container[iZone][INST_0][MESH_0], iZone); + } break; case NO_TRANSFER: case ZONES_ARE_EQUAL: case NO_COMMON_INTERFACE: break; default: - if(rank == MASTER_NODE) + if (rank == MASTER_NODE) { cout << "WARNING: One of the intended interface transfer routines is not " << "known to the chosen driver and has not been executed." << endl; + } break; + } + }; + + auto type = interface_types[donorZone][targetZone]; + auto* interface = interface_container[donorZone][targetZone]; + + while (interface != nullptr) { + HandleInterfaceType(type, interface); + type = interface->NextInterfaceType; + interface = interface->NextInterface; } return UpdateMesh; diff --git a/SU2_CFD/src/interfaces/CInterface.cpp b/SU2_CFD/src/interfaces/CInterface.cpp index 293141079c49..55537c325279 100644 --- a/SU2_CFD/src/interfaces/CInterface.cpp +++ b/SU2_CFD/src/interfaces/CInterface.cpp @@ -57,6 +57,8 @@ CInterface::~CInterface() { delete[] SpanValueCoeffTarget; delete[] SpanLevelDonor; + + delete NextInterface; } void CInterface::BroadcastData(const CInterpolator& interpolator, diff --git a/SU2_CFD/src/interfaces/fsi/CFlowTractionInterface.cpp b/SU2_CFD/src/interfaces/fsi/CFlowTractionInterface.cpp index e42aabe73358..79e65a2b9614 100644 --- a/SU2_CFD/src/interfaces/fsi/CFlowTractionInterface.cpp +++ b/SU2_CFD/src/interfaces/fsi/CFlowTractionInterface.cpp @@ -158,9 +158,9 @@ void CFlowTractionInterface::GetPhysical_Constants(CSolver *flow_solution, CSolv Physical_Constants[1] = ModAmpl; /*--- For static FSI, we cannot apply the ramp like this ---*/ - if ((!flow_config->GetTime_Domain())){ + if (!flow_config->GetTime_Domain()) { Physical_Constants[1] = 1.0; - if (Ramp_Load){ + if (Ramp_Load) { CurrentTime = static_cast(struct_config->GetOuterIter()); Ramp_Time = static_cast(struct_config->GetnIterFSI_Ramp() - 1); diff --git a/SU2_CFD/src/solvers/CHeatSolver.cpp b/SU2_CFD/src/solvers/CHeatSolver.cpp index 5e9b0b437c27..52b591770ca2 100644 --- a/SU2_CFD/src/solvers/CHeatSolver.cpp +++ b/SU2_CFD/src/solvers/CHeatSolver.cpp @@ -257,6 +257,7 @@ void CHeatSolver::LoadRestart(CGeometry **geometry, CSolver ***solver, CConfig * solver[MESH_0][HEAT_SOL]->InitiateComms(geometry[MESH_0], config, MPI_QUANTITIES::SOLUTION); solver[MESH_0][HEAT_SOL]->CompleteComms(geometry[MESH_0], config, MPI_QUANTITIES::SOLUTION); + SU2_OMP_SAFE_GLOBAL_ACCESS(config->SetGlobalParam(MAIN_SOLVER::HEAT_EQUATION, RUNTIME_HEAT_SYS);) solver[MESH_0][HEAT_SOL]->Preprocessing(geometry[MESH_0], solver[MESH_0], config, MESH_0, NO_RK_ITER, RUNTIME_HEAT_SYS, false); /*--- Interpolate the solution down to the coarse multigrid levels ---*/ diff --git a/TestCases/fea_fsi/stat_fsi/config.cfg b/TestCases/fea_fsi/stat_fsi/config.cfg index 029d94ef928c..5faa20853f84 100755 --- a/TestCases/fea_fsi/stat_fsi/config.cfg +++ b/TestCases/fea_fsi/stat_fsi/config.cfg @@ -1,24 +1,32 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% % % SU2 configuration file % -% Case description: Fluid structure interaction - Beam in channel - 2D - FEM % -% Author: R.Sanchez % -% Institution: Imperial College London % -% % +% Case description: Aero-thermo-elasticity % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% FSI settings SOLVER= MULTIPHYSICS +MULTIZONE_SOLVER= BLOCK_GAUSS_SEIDEL +RAMP_LOADING= YES +RAMP_FSI_ITER= 5 +BGS_RELAXATION= FIXED_PARAMETER +STAT_RELAX_PARAMETER= 0.9 -CONFIG_LIST = (configFlow.cfg, configFEA.cfg) +CONFIG_LIST= ( configFlow.cfg, configFEA.cfg ) -MULTIZONE_SOLVER = BLOCK_GAUSS_SEIDEL +MARKER_ZONE_INTERFACE= ( wallF, wallS ) -MARKER_ZONE_INTERFACE = (wallF, wallS) +SCREEN_OUTPUT= \ + OUTER_ITER, INNER_ITER[0], AVG_BGS_RES[0], AVG_BGS_RES[1],\ + RMS_DENSITY[0], RMS_UTOL[1], RMS_TEMPERATURE[1], VMS[1],\ + TOTAL_HEATFLUX[1], DEFORM_MIN_VOLUME[0], DEFORM_ITER[0] -MULTIZONE_MESH = NO -SCREEN_OUTPUT=(OUTER_ITER, BGS_DENSITY[0], AVG_BGS_RES[1], DEFORM_MIN_VOLUME[0], DEFORM_ITER[0]) RESTART_SOL= NO -RESTART_ITER = 0 +RESTART_ITER= 0 + +OUTER_ITER= 30 +CONV_RESIDUAL_MINVAL= -5 + +MULTIZONE_MESH= NO +WRT_ZONE_CONV= NO +OUTPUT_WRT_FREQ= 100 -TIME_DOMAIN = NO -OUTER_ITER = 8 diff --git a/TestCases/fea_fsi/stat_fsi/configFEA.cfg b/TestCases/fea_fsi/stat_fsi/configFEA.cfg index 07c2a748f956..a0430a07e717 100755 --- a/TestCases/fea_fsi/stat_fsi/configFEA.cfg +++ b/TestCases/fea_fsi/stat_fsi/configFEA.cfg @@ -1,82 +1,63 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% % % SU2 configuration file % -% Case description: Fluid structure interaction - Beam in channel - 2D - FEM % -% Author: R.Sanchez % -% Institution: TU Kaiserslautern % -% % +% Case description: Aero-thermo-elasticity % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% -% +% Nonlinear thermoelasticity SOLVER= ELASTICITY -MATH_PROBLEM= DIRECT - -% ------------------------ PARAMETERS FEM SOLVER ------------------------------% - GEOMETRIC_CONDITIONS= LARGE_DEFORMATIONS MATERIAL_MODEL= NEO_HOOKEAN MATERIAL_COMPRESSIBILITY= COMPRESSIBLE -NONLINEAR_FEM_SOLUTION_METHOD = NEWTON_RAPHSON -INNER_ITER = 10 - -INCREMENTAL_LOAD = NO - -% --------------------------- TIME STEP ISSUES --------------------------------% - -TIME_ITER= 1 -OUTPUT_WRT_FREQ= 1 - -% --------------------------- MESH REFINEMENT ---------------------------------% +FORMULATION_ELASTICITY_2D= PLANE_STRAIN +WEAKLY_COUPLED_HEAT_EQUATION= YES +INC_NONDIM= DIMENSIONAL + +% Material properties +ELASTICITY_MODULUS= 21000 +MATERIAL_DENSITY= 0.8 +POISSON_RATIO= 0.4 + +SPECIFIC_HEAT_CP= 1 +THERMAL_CONDUCTIVITY_CONSTANT= 4 +MATERIAL_REFERENCE_TEMPERATURE= 0.1 +MATERIAL_THERMAL_EXPANSION_COEFF= 1 + +% Boundary conditions +MARKER_CLAMPED= ( clamped ) +MARKER_FLUID_LOAD= ( wallS ) +MARKER_CHT_INTERFACE= ( wallS ) +MARKER_CREATE_COPY= ( clamped, isothermal ) +MARKER_ISOTHERMAL= ( isothermal, 0.1 ) + +MARKER_PLOTTING= ( wallS ) +MARKER_MONITORING= ( wallS ) + +% Initial conditions +FREESTREAM_TEMPERATURE= 0.1 + +% Solver settings +NUM_METHOD_GRAD= GREEN_GAUSS + +TIME_DISCRE_HEAT= EULER_IMPLICIT +CFL_NUMBER= 1e8 +LINEAR_SOLVER= CONJUGATE_GRADIENT +LINEAR_SOLVER_PREC= ILU +LINEAR_SOLVER_ERROR= 1e-4 +LINEAR_SOLVER_ITER= 500 + +% Convergence settings +INNER_ITER= 5 +CONV_FIELD= RMS_UTOL +CONV_RESIDUAL_MINVAL= -10 + +% In/Out +SCREEN_OUTPUT= INNER_ITER, RMS_RES, VMS, TOTAL_HEATFLUX, LINSOL +MESH_FORMAT= SU2 MESH_FILENAME= meshFEA.su2 - -% --------------------------- FSI CONDITIONS ----------------------------------% - -RAMP_LOADING=NO - -STAT_RELAX_PARAMETER= 1.0 -BGS_RELAXATION = FIXED_PARAMETER -PREDICTOR_ORDER = 0 - -CONV_FIELD=RMS_UTOL -CONV_RESIDUAL_MINVAL=-10 - -% ----------------------------- INPUT/OUTPUT ----------------------------------% +TABULAR_FORMAT= CSV VOLUME_FILENAME= result_beam - -BREAKDOWN_FILENAME= forces_breakdown.dat - SOLUTION_FILENAME= solution_beam RESTART_FILENAME= restart_beam -% ------------------------ STRUCTURAL PARAMETERS ------------------------------% - -ELASTICITY_MODULUS=21000 -MATERIAL_DENSITY=0.8 -FORMULATION_ELASTICITY_2D = PLANE_STRAIN -POISSON_RATIO=0.4 - -% -------------------------- DYNAMIC SIMULATION -------------------------------% -TIME_DOMAIN= NO -TIME_DISCRE_FEA= NEWMARK_IMPLICIT - -% -------------------------- STRUCTURAL SOLVER --------------------------------% - -LINEAR_SOLVER = CONJUGATE_GRADIENT -LINEAR_SOLVER_PREC = JACOBI -LINEAR_SOLVER_ERROR = 1E-3 -LINEAR_SOLVER_ITER = 2000 - -% -------------------- BOUNDARY CONDITION DEFINITION --------------------------% - -MARKER_CLAMPED = ( clamped ) -MARKER_PRESSURE= ( wallS, 0.0) - -MARKER_FLUID_LOAD = (wallS) - -% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% - -MESH_FORMAT= SU2 -TABULAR_FORMAT= CSV diff --git a/TestCases/fea_fsi/stat_fsi/configFlow.cfg b/TestCases/fea_fsi/stat_fsi/configFlow.cfg index 2b54ef4d3866..60cc8882a49d 100755 --- a/TestCases/fea_fsi/stat_fsi/configFlow.cfg +++ b/TestCases/fea_fsi/stat_fsi/configFlow.cfg @@ -1,121 +1,81 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% % % SU2 configuration file % -% Case description: Fluid structure interaction - Beam in channel - 2D - FEM % -% Author: R.Sanchez % -% Institution: TU Kaiserslautern % -% % +% Case description: Aero-thermo-elasticity % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% -% +% Laminar flow SOLVER= NAVIER_STOKES - KIND_TURB_MODEL= NONE -MATH_PROBLEM= DIRECT - -% --------------------------- MESH DEFORMATION ---------------------------------% - -MESH_FILENAME= meshFlow.su2 - -DEFORM_MESH = YES -MARKER_DEFORM_MESH = (wallF) - -DEFORM_STIFFNESS_TYPE = INVERSE_VOLUME -DEFORM_LINEAR_SOLVER = CONJUGATE_GRADIENT -DEFORM_LINEAR_SOLVER_PREC = LU_SGS -DEFORM_LINEAR_SOLVER_ERROR = 1E-5 -DEFORM_NONLINEAR_ITER= 1 -DEFORM_LINEAR_SOLVER_ITER = 5000 -DEFORM_CONSOLE_OUTPUT = NO - -% -----------------------------------------------------------------------------% - -OUTPUT_WRT_FREQ= 10 +% Material properties +FLUID_MODEL= STANDARD_AIR +VISCOSITY_MODEL= CONSTANT_VISCOSITY +MU_CONSTANT= 1.82E-4 +CONDUCTIVITY_MODEL= CONSTANT_PRANDTL +PRANDTL_LAM= 0.72 -INNER_ITER= 200 - -% ----------------------------- INPUT/OUTPUT ----------------------------------% - -VOLUME_FILENAME= result_flow -CONV_FILENAME= history - -BREAKDOWN_FILENAME= forces_breakdown.dat - -SOLUTION_FILENAME= solution_flow -RESTART_FILENAME= restart_flow - -% -------------------- BOUNDARY CONDITION DEFINITION --------------------------% - -MARKER_HEATFLUX= ( wallF, 0.0) +% Boundary conditions MARKER_INLET= ( inlet, 0.0578070909, 20.1346756866, 1.0, 0.0, 0.0 ) -MARKER_OUTLET= ( outlet, 19.5809057203) +MARKER_OUTLET= ( outlet, 19.5809057203 ) MARKER_EULER= ( upper, lower ) +MARKER_CHT_INTERFACE= ( wallF ) MARKER_PLOTTING= ( wallF ) MARKER_MONITORING= ( wallF ) -% -------------------------- FLUID SIMULATION ---------------------------------% - -TIME_DOMAIN=NO -MAX_TIME= 4.01 - -% ----------- COMPRESSIBLE AND INCOMPRESSIBLE FREE-STREAM DEFINITION ----------% - -MACH_NUMBER= 0.2 -MACH_MOTION= 0.2 -AoA= 0.0 -SIDESLIP_ANGLE= 0.0 - -INIT_OPTION = TD_CONDITIONS -FREESTREAM_OPTION = DENSITY_FS -FREESTREAM_DENSITY = 1.18 -FREESTREAM_PRESSURE = 19.5809057203 -FREESTREAM_TEMPERATURE = 0.0578070909 -VISCOSITY_MODEL = CONSTANT_VISCOSITY -MU_CONSTANT = 1.82E-3 -REYNOLDS_NUMBER= 10 -CFL_NUMBER = 100 - -% ---------------------- REFERENCE VALUE DEFINITION ---------------------------% - -REF_ORIGIN_MOMENT_X = 0.00 -REF_ORIGIN_MOMENT_Y = 0.00 -REF_ORIGIN_MOMENT_Z = 0.00 - -REF_AREA = 0.016 -REYNOLDS_LENGTH = 0.016 - -% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% - -NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES -RK_ALPHA_COEFF= ( 0.66667, 0.66667, 1.000000 ) - -% ------------------------ LINEAR SOLVER DEFINITION ---------------------------% - -LINEAR_SOLVER= FGMRES -LINEAR_SOLVER_PREC= ILU -LINEAR_SOLVER_ERROR= 1E-6 -LINEAR_SOLVER_ITER= 2 - -% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% - +% Initial conditions +MACH_NUMBER= 0.03 +INIT_OPTION= TD_CONDITIONS +FREESTREAM_OPTION= TEMPERATURE_FS +FREESTREAM_PRESSURE= 19.5809057203 +FREESTREAM_TEMPERATURE= 0.0578070909 + +% Mesh deformation +DEFORM_MESH= YES +MARKER_DEFORM_MESH= ( wallF ) +MARKER_DEFORM_MESH_SYM_PLANE= ( upper ) +DEFORM_NONLINEAR_ITER= 1 +DEFORM_STIFFNESS_TYPE= INVERSE_VOLUME +DEFORM_LINEAR_SOLVER= CONJUGATE_GRADIENT +DEFORM_LINEAR_SOLVER_PREC= ILU +DEFORM_LINEAR_SOLVER_ITER= 500 +DEFORM_LINEAR_SOLVER_ERROR= 1E-8 +DEFORM_CONSOLE_OUTPUT= NO + +% Solver settings CONV_NUM_METHOD_FLOW= ROE MUSCL_FLOW= YES +NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES SLOPE_LIMITER_FLOW= VENKATAKRISHNAN VENKAT_LIMITER_COEFF= 1.0 -JST_SENSOR_COEFF=( 0.5, 0.02 ) + TIME_DISCRE_FLOW= EULER_IMPLICIT +CFL_NUMBER= 500 +LINEAR_SOLVER= FGMRES +LINEAR_SOLVER_PREC= ILU +LINEAR_SOLVER_ERROR= 0.1 +LINEAR_SOLVER_ITER= 10 -% --------------------------- CONVERGENCE PARAMETERS --------------------------% +% Convergence settings +INNER_ITER= 100 +CONV_FIELD= RMS_DENSITY +CONV_RESIDUAL_MINVAL= -9 -CONV_RESIDUAL_MINVAL= -10 -CONV_STARTITER= 10 -CONV_CAUCHY_ELEMS= 100 -CONV_CAUCHY_EPS= 1E-5 +% Reference values +REF_ORIGIN_MOMENT_X= 0.0 +REF_ORIGIN_MOMENT_Y= 0.0 +REF_ORIGIN_MOMENT_Z= 0.0 +REF_AREA= 0.016 +REYNOLDS_LENGTH= 0.016 -% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% +% In/Out +SCREEN_OUTPUT= INNER_ITER, RMS_RES, TOTAL_HEATFLUX, LINSOL MESH_FORMAT= SU2 +MESH_FILENAME= meshFlow.su2 TABULAR_FORMAT= CSV + +VOLUME_FILENAME= result_flow +SOLUTION_FILENAME= solution_flow +RESTART_FILENAME= restart_flow + diff --git a/TestCases/fea_fsi/stat_fsi/config_restart.cfg b/TestCases/fea_fsi/stat_fsi/config_restart.cfg index fa395744db71..b88f64ca143f 100755 --- a/TestCases/fea_fsi/stat_fsi/config_restart.cfg +++ b/TestCases/fea_fsi/stat_fsi/config_restart.cfg @@ -1,23 +1,32 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% % % SU2 configuration file % -% Case description: Fluid structure interaction - Beam in channel - 2D - FEM % -% Author: R.Sanchez % -% Institution: Imperial College London % -% % +% Case description: Aero-thermo-elasticity % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% FSI settings SOLVER= MULTIPHYSICS +MULTIZONE_SOLVER= BLOCK_GAUSS_SEIDEL +RAMP_LOADING= NO +RAMP_FSI_ITER= 5 +BGS_RELAXATION= FIXED_PARAMETER +STAT_RELAX_PARAMETER= 0.9 -CONFIG_LIST = (configFlow.cfg, configFEA.cfg) +CONFIG_LIST= ( configFlow.cfg, configFEA.cfg ) -MULTIZONE_SOLVER = BLOCK_GAUSS_SEIDEL +MARKER_ZONE_INTERFACE= ( wallF, wallS ) -MARKER_ZONE_INTERFACE = (wallF, wallS) +SCREEN_OUTPUT= \ + OUTER_ITER, INNER_ITER[0], AVG_BGS_RES[0], AVG_BGS_RES[1],\ + RMS_DENSITY[0], RMS_UTOL[1], RMS_TEMPERATURE[1], VMS[1],\ + TOTAL_HEATFLUX[1], DEFORM_MIN_VOLUME[0], DEFORM_ITER[0] -MULTIZONE_MESH = NO -SCREEN_OUTPUT=(OUTER_ITER, BGS_DENSITY[0], AVG_BGS_RES[1], DEFORM_MIN_VOLUME[0], DEFORM_ITER[0]) RESTART_SOL= YES -RESTART_ITER = 0 -TIME_DOMAIN = NO -OUTER_ITER = 2 +RESTART_ITER= 0 + +OUTER_ITER= 1 +CONV_RESIDUAL_MINVAL= -5 + +MULTIZONE_MESH= NO +WRT_ZONE_CONV= NO +OUTPUT_WRT_FREQ= 100 + diff --git a/TestCases/hybrid_regression.py b/TestCases/hybrid_regression.py index 57074e8ea998..22059c14f673 100644 --- a/TestCases/hybrid_regression.py +++ b/TestCases/hybrid_regression.py @@ -711,15 +711,14 @@ def main(): dyn_fsi.unsteady = True test_list.append(dyn_fsi) - # FSI, Static, 2D, new mesh solver, restart - stat_fsi_restart = TestCase('stat_fsi_restart') - stat_fsi_restart.cfg_dir = "fea_fsi/stat_fsi" - stat_fsi_restart.cfg_file = "config_restart.cfg" - stat_fsi_restart.test_iter = 1 - stat_fsi_restart.test_vals = [-3.549586, -4.460650, 0.000000, 35.000000] - stat_fsi_restart.test_vals_aarch64 = [-3.549586, -4.460713, 0.000000, 35.000000] - stat_fsi_restart.multizone = True - test_list.append(stat_fsi_restart) + # FSI+CHT, Static, 2D, new mesh solver, restart + fsi_cht_restart = TestCase('fsi_cht_restart') + fsi_cht_restart.cfg_dir = "fea_fsi/stat_fsi" + fsi_cht_restart.cfg_file = "config_restart.cfg" + fsi_cht_restart.test_iter = 0 + fsi_cht_restart.test_vals = [5, 0.006352, -1.960362, -9.327033, -9.580521, -9.317956, 6.0838e+02, -1.2974e-02, 5.7607e-08, 20] + fsi_cht_restart.multizone = True + test_list.append(fsi_cht_restart) ############################################## ### Method of Manufactured Solutions (MMS) ### diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index 7f10753db6e3..97bb4a2d90ab 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -1357,15 +1357,6 @@ def main(): fsi2d.unsteady = True test_list.append(fsi2d) - # FSI, Static, 2D, new mesh solver - stat_fsi = TestCase('stat_fsi') - stat_fsi.cfg_dir = "fea_fsi/stat_fsi" - stat_fsi.cfg_file = "config.cfg" - stat_fsi.test_iter = 7 - stat_fsi.test_vals = [-3.301938, -4.971986, 0.000000, 11.000000] - stat_fsi.multizone = True - test_list.append(stat_fsi) - # FSI, Dynamic, 2D, new mesh solver dyn_fsi = TestCase('dyn_fsi') dyn_fsi.cfg_dir = "fea_fsi/dyn_fsi" @@ -1376,15 +1367,6 @@ def main(): dyn_fsi.unsteady = True test_list.append(dyn_fsi) - # FSI, Static, 2D, new mesh solver, restart - stat_fsi_restart = TestCase('stat_fsi_restart') - stat_fsi_restart.cfg_dir = "fea_fsi/stat_fsi" - stat_fsi_restart.cfg_file = "config_restart.cfg" - stat_fsi_restart.test_iter = 1 - stat_fsi_restart.test_vals = [-3.486655, -4.425104, 0.000000, 27.000000] - stat_fsi_restart.multizone = True - test_list.append(stat_fsi_restart) - # ############################### # ### Radiative Heat Transfer ### # ############################### diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index aa8583f818c9..37651f2b4f68 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -1074,7 +1074,7 @@ def main(): dynbeam2d.test_vals = [-3.240012, 2.895060, -0.353140, 76220] test_list.append(dynbeam2d) - # # FSI, 2d + # FSI, 2d fsi2d = TestCase('fsi2d') fsi2d.cfg_dir = "fea_fsi/WallChannel_2d" fsi2d.cfg_file = "configFSI.cfg" @@ -1084,23 +1084,14 @@ def main(): fsi2d.unsteady = True test_list.append(fsi2d) - # FSI, Static, 2D, new mesh solver - stat_fsi = TestCase('stat_fsi') - stat_fsi.cfg_dir = "fea_fsi/stat_fsi" - stat_fsi.cfg_file = "config.cfg" - stat_fsi.test_iter = 7 - stat_fsi.test_vals = [-3.336320, -4.991964, 0.000000, 7.000000] - stat_fsi.multizone = True - test_list.append(stat_fsi) - - # FSI, Static, 2D, new mesh solver, restart - stat_fsi_restart = TestCase('stat_fsi_restart') - stat_fsi_restart.cfg_dir = "fea_fsi/stat_fsi" - stat_fsi_restart.cfg_file = "config_restart.cfg" - stat_fsi_restart.test_iter = 1 - stat_fsi_restart.test_vals = [-3.401553, -4.672932, 0.000000, 26.000000] - stat_fsi_restart.multizone = True - test_list.append(stat_fsi_restart) + # FSI+CHT, Static, 2D, new mesh solver + fsi_cht = TestCase('fsi_cht') + fsi_cht.cfg_dir = "fea_fsi/stat_fsi" + fsi_cht.cfg_file = "config.cfg" + fsi_cht.test_iter = 20 + fsi_cht.test_vals = [5, -5.077012, -5.379779, -9.247804, -9.277819, -9.183796, 6.0835e+02, -1.2973e-02, 5.7607e-08, 29] + fsi_cht.multizone = True + test_list.append(fsi_cht) # FSI, Dynamic, 2D, new mesh solver dyn_fsi = TestCase('dyn_fsi') From 6d6db15a9737ed526d9444fe4fd298305a3754dd Mon Sep 17 00:00:00 2001 From: Pedro Gomes Date: Fri, 8 May 2026 08:32:35 -0700 Subject: [PATCH 03/61] tweak release management more --- .github/workflows/release-management.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release-management.yml b/.github/workflows/release-management.yml index 7a560b1a94fb..1c471fee0534 100644 --- a/.github/workflows/release-management.yml +++ b/.github/workflows/release-management.yml @@ -43,7 +43,8 @@ jobs: with: path: ccache key: ${{ matrix.os_bin }}-${{ github.sha }} - restore-keys: ${{ matrix.os_bin }} + restore-keys: | + ${{ matrix.os_bin }}- - name: Build uses: docker://ghcr.io/su2code/su2/build-su2-cross:260405-0054 @@ -68,11 +69,10 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload Release Asset - uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.release_drafter.outputs.upload_url }} - asset_path: ${{ matrix.os_bin }}.zip - asset_name: SU2-${{ steps.release_drafter.outputs.tag_name }}-${{ matrix.os_bin }}.zip - asset_content_type: application/zip + TAG_NAME: ${{ steps.release_drafter.outputs.tag_name }} + run: | + gh release upload "$TAG_NAME" \ + "${{ matrix.os_bin }}.zip#SU2-${TAG_NAME}-${{ matrix.os_bin }}.zip" \ + --clobber From 120a1626c3802a5679035628178f3107b2091974 Mon Sep 17 00:00:00 2001 From: Pedro Gomes Date: Fri, 8 May 2026 09:10:22 -0700 Subject: [PATCH 04/61] fix release management --- .github/release-drafter.yml | 14 +++++++++++--- .github/workflows/release-management.yml | 3 +++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index c5f256b220e9..76429a377db2 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,20 +1,28 @@ name-template: 'v$NEXT_PATCH_VERSION' tag-template: 'v$NEXT_PATCH_VERSION' + +branches: + - develop + categories: - title: ':rocket: Experimental Features' labels: - 'changelog:feature' + - title: ':pill: Bug Fixes' labels: - 'changelog:fix' + - title: ':wrench: Maintenance' labels: - 'changelog:chore' -change-template: '- $TITLE @$AUTHOR (#$NUMBER)' -branches: - - develop + exclude-labels: - 'changelog:none' + +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' + template: | ## Changes + $CHANGES diff --git a/.github/workflows/release-management.yml b/.github/workflows/release-management.yml index 1c471fee0534..99ba73a0a34a 100644 --- a/.github/workflows/release-management.yml +++ b/.github/workflows/release-management.yml @@ -38,6 +38,9 @@ jobs: flags: '-Dcpu-arch=haswell -Dcustom-mpi=true --cross-file=/hostfiles/hostfile_linux_mpi' steps: + - name: Checkout Repository + uses: actions/checkout@v4 + - name: Cache Object Files uses: actions/cache@v4 with: From ee4de5c5e1739fcdcb67381410fa19c7056c7e86 Mon Sep 17 00:00:00 2001 From: Pedro Gomes Date: Fri, 8 May 2026 09:30:26 -0700 Subject: [PATCH 05/61] try to fix duplicate binaries in release draft --- .github/workflows/release-management.yml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-management.yml b/.github/workflows/release-management.yml index 99ba73a0a34a..10776935d484 100644 --- a/.github/workflows/release-management.yml +++ b/.github/workflows/release-management.yml @@ -71,11 +71,26 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Upload Release Asset + - name: Replace Release Asset env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG_NAME: ${{ steps.release_drafter.outputs.tag_name }} + ASSET_NAME: SU2-${{ steps.release_drafter.outputs.tag_name }}-${{ matrix.os_bin }}.zip run: | + set -e + + # Delete existing asset if present + EXISTING_ASSET_ID=$(gh release view "$TAG_NAME" \ + --json assets \ + --jq ".assets[] | select(.name == \"$ASSET_NAME\") | .id") + + if [ -n "$EXISTING_ASSET_ID" ]; then + echo "Deleting existing asset: $ASSET_NAME" + gh api \ + -X DELETE \ + repos/${{ github.repository }}/releases/assets/$EXISTING_ASSET_ID + fi + + # Upload new asset gh release upload "$TAG_NAME" \ - "${{ matrix.os_bin }}.zip#SU2-${TAG_NAME}-${{ matrix.os_bin }}.zip" \ - --clobber + "${{ matrix.os_bin }}.zip#$ASSET_NAME" From 7fa55179ea0ec1ca0eb1bd951fc1a62e8fec3901 Mon Sep 17 00:00:00 2001 From: LwhJesse <256257451+LwhJesse@users.noreply.github.com> Date: Sat, 9 May 2026 06:00:28 +0800 Subject: [PATCH 06/61] Reduce redundant CUDA Jacobian uploads during a linear solve (#2806) * Reduce redundant CUDA Jacobian uploads * Move CUDA Jacobian upload into CSysMatrixVectorProduct * Defer CUDA matrix upload to first matvec use --- Common/include/linear_algebra/CMatrixVectorProduct.hpp | 5 +++++ Common/src/linear_algebra/CSysMatrixGPU.cu | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Common/include/linear_algebra/CMatrixVectorProduct.hpp b/Common/include/linear_algebra/CMatrixVectorProduct.hpp index 878bb132b984..a0cecaa63d76 100644 --- a/Common/include/linear_algebra/CMatrixVectorProduct.hpp +++ b/Common/include/linear_algebra/CMatrixVectorProduct.hpp @@ -72,6 +72,7 @@ class CSysMatrixVectorProduct final : public CMatrixVectorProduct { const CSysMatrix& matrix; /*!< \brief pointer to matrix that defines the product. */ CGeometry* geometry; /*!< \brief geometry associated with the matrix. */ const CConfig* config; /*!< \brief config of the problem. */ + mutable bool matrix_uploaded = false; /*!< \brief Upload the matrix lazily on the first actual GPU matvec. */ public: /*! @@ -97,6 +98,10 @@ class CSysMatrixVectorProduct final : public CMatrixVectorProduct { inline void operator()(const CSysVector& u, CSysVector& v) const override { if (config->GetCUDA()) { #ifdef HAVE_CUDA + if (!matrix_uploaded) { + matrix.HtDTransfer(); + matrix_uploaded = true; + } matrix.GPUMatrixVectorProduct(u, v, geometry, config); #else SU2_MPI::Error( diff --git a/Common/src/linear_algebra/CSysMatrixGPU.cu b/Common/src/linear_algebra/CSysMatrixGPU.cu index 90389264ed31..7e0c81ca54fd 100644 --- a/Common/src/linear_algebra/CSysMatrixGPU.cu +++ b/Common/src/linear_algebra/CSysMatrixGPU.cu @@ -70,7 +70,6 @@ void CSysMatrix::GPUMatrixVectorProduct(const CSysVector ScalarType* d_vec = vec.GetDevicePointer(); ScalarType* d_prod = prod.GetDevicePointer(); - HtDTransfer(); vec.HtDTransfer(); prod.GPUSetVal(0.0); From ea87245d78baa9f54b027d2ff13cdca46a09362a Mon Sep 17 00:00:00 2001 From: Pedro Gomes Date: Fri, 8 May 2026 15:02:51 -0700 Subject: [PATCH 07/61] Revert "try to fix duplicate binaries in release draft" This reverts commit ee4de5c5e1739fcdcb67381410fa19c7056c7e86. --- .github/workflows/release-management.yml | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release-management.yml b/.github/workflows/release-management.yml index 10776935d484..99ba73a0a34a 100644 --- a/.github/workflows/release-management.yml +++ b/.github/workflows/release-management.yml @@ -71,26 +71,11 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Replace Release Asset + - name: Upload Release Asset env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG_NAME: ${{ steps.release_drafter.outputs.tag_name }} - ASSET_NAME: SU2-${{ steps.release_drafter.outputs.tag_name }}-${{ matrix.os_bin }}.zip run: | - set -e - - # Delete existing asset if present - EXISTING_ASSET_ID=$(gh release view "$TAG_NAME" \ - --json assets \ - --jq ".assets[] | select(.name == \"$ASSET_NAME\") | .id") - - if [ -n "$EXISTING_ASSET_ID" ]; then - echo "Deleting existing asset: $ASSET_NAME" - gh api \ - -X DELETE \ - repos/${{ github.repository }}/releases/assets/$EXISTING_ASSET_ID - fi - - # Upload new asset gh release upload "$TAG_NAME" \ - "${{ matrix.os_bin }}.zip#$ASSET_NAME" + "${{ matrix.os_bin }}.zip#SU2-${TAG_NAME}-${{ matrix.os_bin }}.zip" \ + --clobber From f4ceaab1610417f07160d19493f590f36b2fedba Mon Sep 17 00:00:00 2001 From: Pedro Gomes Date: Fri, 8 May 2026 15:08:30 -0700 Subject: [PATCH 08/61] avoid race conditions when updating the release draft --- .github/workflows/release-management.yml | 50 +++++++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release-management.yml b/.github/workflows/release-management.yml index 99ba73a0a34a..e8c44d8ac399 100644 --- a/.github/workflows/release-management.yml +++ b/.github/workflows/release-management.yml @@ -8,8 +8,12 @@ on: permissions: contents: write +concurrency: + group: release-management-${{ github.ref }} + cancel-in-progress: true + jobs: - build_and_upload: + build: name: Build SU2 (${{ matrix.os_bin }}) runs-on: ubuntu-latest @@ -59,7 +63,7 @@ jobs: cd install zip -r ../${{ matrix.os_bin }}.zip bin/* - - name: Upload Workflow Artifact + - name: Upload Build Artifact uses: actions/upload-artifact@v5 with: name: ${{ matrix.os_bin }} @@ -71,11 +75,45 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Upload Release Asset + release: + name: Update Draft Release + runs-on: ubuntu-latest + needs: build + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Download All Build Artifacts + uses: actions/download-artifact@v5 + with: + path: release-assets + + - name: Flatten Artifact Directory + run: | + find release-assets -name "*.zip" -exec mv {} . \; + + - name: Update Release Draft + id: release_drafter + uses: release-drafter/release-drafter@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Release Assets env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG_NAME: ${{ steps.release_drafter.outputs.tag_name }} run: | - gh release upload "$TAG_NAME" \ - "${{ matrix.os_bin }}.zip#SU2-${TAG_NAME}-${{ matrix.os_bin }}.zip" \ - --clobber + set -e + + for file in *.zip; do + + asset_name="SU2-${TAG_NAME}-${file}" + + echo "Uploading $asset_name" + + gh release upload "$TAG_NAME" \ + "$file#$asset_name" \ + --clobber + + done From 154d0852c3da15959785852d623625e60212cc13 Mon Sep 17 00:00:00 2001 From: Nijso Date: Fri, 22 May 2026 08:37:03 +0200 Subject: [PATCH 09/61] Flexible setup of flamelet boundary conditions (#2814) * flexible BC for flamelet equations * add jacobian of the split source terms, rename FLAMELET->FLAME * add python wrapper to flamelet enthalpy --------- Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> --- Common/include/CConfig.hpp | 8 + Common/include/option_structure.hpp | 18 ++ Common/src/CConfig.cpp | 4 + SU2_CFD/flamelet_python_bc.md | 225 ++++++++++++++++++ .../solvers/CSpeciesFlameletSolver.hpp | 15 ++ .../variables/CSpeciesFlameletVariable.hpp | 15 ++ SU2_CFD/src/solvers/CIncEulerSolver.cpp | 16 +- .../src/solvers/CSpeciesFlameletSolver.cpp | 93 +++++++- .../variables/CSpeciesFlameletVariable.cpp | 1 + TestCases/parallel_regression.py | 10 +- TestCases/parallel_regression_AD.py | 4 +- TestCases/tutorials.py | 2 +- config_template.cfg | 7 +- 13 files changed, 398 insertions(+), 20 deletions(-) create mode 100644 SU2_CFD/flamelet_python_bc.md diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index 71f780bbed07..e44ad6a10a73 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -10265,4 +10265,12 @@ class CConfig { */ const FluidFlamelet_ParsedOptions& GetFlameletParsedOptions() const { return flamelet_ParsedOptions; } + /*! + * \brief Get the enthalpy BC mode for the flamelet solver. + * FLOW_MARKERS: derive enthalpy BCs from MARKER_ISOTHERMAL/MARKER_HEATFLUX/MARKER_INLET (temperature-based). + * SPECIES_MARKERS: take enthalpy BCs directly from MARKER_WALL_SPECIES/MARKER_INLET_SPECIES. + * \return FLAMELET_ENTHALPY_BC enum value. + */ + FLAMELET_ENTHALPY_BC GetFlamelet_Enthalpy_BC() const { return flamelet_ParsedOptions.enthalpy_bc; } + }; diff --git a/Common/include/option_structure.hpp b/Common/include/option_structure.hpp index 73266c7dacdb..4c521c292392 100644 --- a/Common/include/option_structure.hpp +++ b/Common/include/option_structure.hpp @@ -1441,12 +1441,30 @@ static const MapType Flamelet_Init_Map = { MakePair("SPARK", FLAMELET_INIT_TYPE::SPARK) }; +/*! + * \brief Selects the source of wall/inlet enthalpy boundary conditions for the flamelet solver. + * SPECIES_MARKERS (default): inlet H is taken directly from MARKER_INLET_SPECIES; wall enthalpy BC + * is obtained from MARKER_WALL_SPECIES. + * FLOW_MARKERS: inlet H is derived from the MARKER_INLET temperature via a Newton iteration on the + * LUT (reverse lookup using Z,T) from MARKER_ISOTHERMAL or MARKER_HEATFLUX. + */ +enum class FLAMELET_ENTHALPY_BC { + FLOW_MARKERS, /*!< \brief Derive inlet H from MARKER_INLET T (LUT Newton); walls from MARKER_ISOTHERMAL/MARKER_HEATFLUX. */ + SPECIES_MARKERS, /*!< \brief Take inlet H directly from MARKER_INLET_SPECIES; walls from MARKER_WALL_SPECIES (default). */ +}; + +static const MapType Flamelet_Enthalpy_BC_Map = { + MakePair("FLOW_MARKERS", FLAMELET_ENTHALPY_BC::FLOW_MARKERS) + MakePair("SPECIES_MARKERS", FLAMELET_ENTHALPY_BC::SPECIES_MARKERS) +}; + /*! * \brief Structure containing parsed options for flamelet fluid model. */ struct FluidFlamelet_ParsedOptions { ///TODO: Add python wrapper initialization option FLAMELET_INIT_TYPE ignition_method = FLAMELET_INIT_TYPE::NONE; /*!< \brief Method for solution ignition for flamelet problems. */ + FLAMELET_ENTHALPY_BC enthalpy_bc = FLAMELET_ENTHALPY_BC::SPECIES_MARKERS; /*!< \brief Source of enthalpy BCs: species markers (default, backward-compatible) or flow markers. */ unsigned short n_scalars = 0; /*!< \brief Number of transported scalars for flamelet LUT approach. */ unsigned short n_lookups = 0; /*!< \brief Number of lookup variables, for visualization only. */ unsigned short n_table_sources = 0; /*!< \brief Number of transported scalar source terms for LUT. */ diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index e6374b314ad1..c168ced08302 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -1410,6 +1410,10 @@ void CConfig::SetConfig_Options() { /*!\brief FLAME_INIT_METHOD \n DESCRIPTION: Ignition method for flamelet solver \n DEFAULT: no ignition; cold flow only. */ addEnumOption("FLAME_INIT_METHOD", flamelet_ParsedOptions.ignition_method, Flamelet_Init_Map, FLAMELET_INIT_TYPE::NONE); + /*!\brief FLAME_ENTHALPY_BC \n DESCRIPTION: enthalpy BC for thermal walls. FLOW_MARKERS (default): enthalpy derived + from MARKER_ISOTHERMAL temperature or MARKER_HEATFLUX via GetEnthFromTemp. SPECIES_MARKERS: enthalpy and all other + scalars taken directly from MARKER_WALL_SPECIES or the Python wrapper (SetMarkerCustomScalar). \n DEFAULT: FLOW_MARKERS \ingroup Config */ + addEnumOption("FLAME_ENTHALPY_BC", flamelet_ParsedOptions.enthalpy_bc, Flamelet_Enthalpy_BC_Map, FLAMELET_ENTHALPY_BC::FLOW_MARKERS); /*!\brief FLAME_INIT \n DESCRIPTION: flame front initialization using the flamelet model \ingroup Config*/ addDoubleArrayOption("FLAME_INIT", flamelet_ParsedOptions.flame_init.size(), false, flamelet_ParsedOptions.flame_init.begin()); diff --git a/SU2_CFD/flamelet_python_bc.md b/SU2_CFD/flamelet_python_bc.md new file mode 100644 index 000000000000..3aa393cee60b --- /dev/null +++ b/SU2_CFD/flamelet_python_bc.md @@ -0,0 +1,225 @@ +# Flamelet Solver — Python Wrapper Wall Boundary Conditions + +This document explains how to use the Python wrapper (`pysu2`) to set +custom per-vertex boundary conditions for the flamelet (FGM) scalar transport +solver, and how the setup differs between the two `FLAMELET_ENTHALPY_BC` modes. + +--- + +## Background + +The flamelet solver transports `nVar` scalar variables. Their order is: + +| Index | Variable | +|-------|----------| +| 0 | `PROGVAR` (progress variable) | +| 1 | `ENTH` (total enthalpy) | +| 2 … n\_CV−1 | additional control variables (e.g. `MIXFRAC`) | +| n\_CV … nVar−1 | auxiliary / user-defined species | + +Two modes control how the enthalpy boundary condition is derived at thermal +walls (`MARKER_ISOTHERMAL`, `MARKER_HEATFLUX`): + +- **`FLOW_MARKERS`** — enthalpy is derived from the flow thermal field (wall + temperature via `MARKER_ISOTHERMAL`, or wall heat flux via `MARKER_HEATFLUX`). +- **`SPECIES_MARKERS`** (default) — enthalpy and all other scalars are taken + directly from `MARKER_WALL_SPECIES`, or from the Python wrapper when + `MARKER_PYTHON_CUSTOM` is active. + +--- + +## Mode 1: `FLAMELET_ENTHALPY_BC = FLOW_MARKERS` + +### How it works + +The C++ function `BC_HeatFlux_Wall` reads the wall heat flux and applies it as +a Neumann condition on `I_ENTH`. When the marker is also listed in +`MARKER_PYTHON_CUSTOM`, it instead reads a **per-vertex heat flux** set by the +Python wrapper. + +``` +driver.SetMarkerCustomNormalHeatFlux(iMarker, iVertex, q_wall) + → geometry->CustomBoundaryHeatFlux[iMarker][iVertex] + → BC_HeatFlux_Wall reads geometry->GetCustomBoundaryHeatFlux(...) +``` + +> `BC_Isothermal_Wall` in `FLOW_MARKERS` mode always converts the wall +> temperature (from `MARKER_ISOTHERMAL`) to enthalpy via `GetEnthFromTemp`. +> Per-vertex customisation of the enthalpy Dirichlet value is not supported in +> this mode — use `SPECIES_MARKERS` instead. + +### Limitations in this mode + +- Only `I_ENTH` is reachable via the Python wrapper. +- Non-enthalpy scalars (`PROGVAR`, auxiliary species) receive implicit zero + Neumann at the wall and cannot be set via Python. + +### config.cfg + +```cfg +FLAMELET_ENTHALPY_BC = FLOW_MARKERS + +% Flow solver wall BC — sets KindBC and provides the fallback heat flux value +% used when MARKER_PYTHON_CUSTOM is not active. +MARKER_HEATFLUX = ( burner_wall, 0.0 ) + +% Enable the per-vertex Python override path. +MARKER_PYTHON_CUSTOM = ( burner_wall ) +``` + +### run.py + +```python +import pysu2 + +driver = pysu2.CSinglezoneDriver("config.cfg", 1, False) + +marker_name = "burner_wall" +iMarker = list(driver.GetMarkerTags()).index(marker_name) +n_vertex = driver.GetNumberMarkerNodes(iMarker) + +def compute_heat_flux(coord): + """Heat flux [W/m²], positive = into the domain.""" + import math + r = math.sqrt(coord[0]**2 + coord[1]**2) + return -5000.0 * math.exp(-r**2 / 0.01) # Gaussian profile, heat out + +for iteration in range(driver.GetNumberIter()): + + for iVertex in range(n_vertex): + node_id = driver.GetMarkerNode(iMarker, iVertex) + coord = driver.GetInitialMeshCoord(node_id) # [x, y(, z)] + q_wall = compute_heat_flux(coord) + driver.SetMarkerCustomNormalHeatFlux(iMarker, iVertex, q_wall) + + driver.Preprocess(iteration) + driver.Run() + driver.Postprocess() + driver.Monitor(iteration) + driver.Output(iteration) + +driver.Finalize() +``` + +--- + +## Mode 2: `FLAMELET_ENTHALPY_BC = SPECIES_MARKERS` (default) + +### How it works + +Both `BC_HeatFlux_Wall` and `BC_Isothermal_Wall` delegate immediately to +`CSpeciesSolver::BC_Wall_Generic`, which processes **all `nVar` scalars** +independently. When `MARKER_PYTHON_CUSTOM` is active, the value for each +scalar is taken from the array set by `driver.SetMarkerCustomScalar()`. + +``` +driver.SetMarkerCustomScalar(iMarker, iVertex, [val_0, val_1, ..., val_nVar-1]) + → CustomBoundaryScalar[iMarker](iVertex, iVar) + → BC_Wall_Generic reads CustomBoundaryScalar for every iVar +``` + +The **type** of BC for each scalar (Dirichlet `VALUE` or Neumann `FLUX`) is +read from `MARKER_WALL_SPECIES` in the config and cannot be changed from +Python. The Python wrapper only overrides the **magnitude** per vertex. + +### config.cfg + +The example below uses `nVar = 3` (PROGVAR, ENTH, one auxiliary species). +Adjust the number of `BC_TYPE, value` pairs to match the actual number of +scalar variables in your setup. + +```cfg +FLAMELET_ENTHALPY_BC = SPECIES_MARKERS + +% Flow solver wall BC — still required to set KindBC. +% The choice between MARKER_ISOTHERMAL and MARKER_HEATFLUX only affects +% the flow solver; the species solver uses BC_Wall_Generic in both cases. +MARKER_ISOTHERMAL = ( burner_wall, 300.0 ) + +% Per-variable BC types for the species solver. +% Format: (marker_name, BC_TYPE, fallback_value, BC_TYPE, fallback_value, ...) +% One BC_TYPE+value pair per scalar variable, in index order. +% BC_TYPE: FLUX → Neumann (value is normal flux density [unit/m²]) +% VALUE → Dirichlet (value is the scalar value at the wall) +% +% When MARKER_PYTHON_CUSTOM is active, the fallback_value is ignored and +% the Python-supplied value is used instead. The BC_TYPE is always from config. +% +% idx 0 PROGVAR : zero Neumann (no progress variable source at the wall) +% idx 1 ENTH : Dirichlet (enthalpy value set per-vertex by Python) +% idx 2 aux_1 : zero Neumann (no auxiliary species flux at the wall) +MARKER_WALL_SPECIES = ( burner_wall, FLUX, 0.0, VALUE, 0.0, FLUX, 0.0 ) + +% Enable the per-vertex Python override path. +MARKER_PYTHON_CUSTOM = ( burner_wall ) +``` + +### run.py + +```python +import pysu2 + +driver = pysu2.CSinglezoneDriver("config.cfg", 1, False) + +marker_name = "burner_wall" +iMarker = list(driver.GetMarkerTags()).index(marker_name) +n_vertex = driver.GetNumberMarkerNodes(iMarker) + +def compute_wall_enthalpy(coord): + """Return wall enthalpy [J/kg] at this vertex.""" + # Example: uniform value corresponding to T=300 K from your LUT. + return 3.5e5 + +for iteration in range(driver.GetNumberIter()): + + for iVertex in range(n_vertex): + node_id = driver.GetMarkerNode(iMarker, iVertex) + coord = driver.GetInitialMeshCoord(node_id) + + enth_wall = compute_wall_enthalpy(coord) + + # Pass one value per scalar variable (length = nVar). + # PROGVAR (idx 0): 0.0 → applied as FLUX → zero Neumann (BC_TYPE from config) + # ENTH (idx 1): enth_wall → applied as VALUE → Dirichlet + # aux_1 (idx 2): 0.0 → applied as FLUX → zero Neumann + driver.SetMarkerCustomScalar(iMarker, iVertex, [0.0, enth_wall, 0.0]) + + driver.Preprocess(iteration) + driver.Run() + driver.Postprocess() + driver.Monitor(iteration) + driver.Output(iteration) + +driver.Finalize() +``` + +--- + +## Comparison + +| | `FLOW_MARKERS` | `SPECIES_MARKERS` | +|---|---|---| +| **Python API** | `SetMarkerCustomNormalHeatFlux` | `SetMarkerCustomScalar` | +| **Storage** | `geometry->CustomBoundaryHeatFlux` | `CustomBoundaryScalar[iMarker](iVertex, iVar)` | +| **What you set** | Per-vertex heat flux (W/m²) for `I_ENTH` only | Per-vertex values for **all** `nVar` scalars | +| **BC type control** | Hard-coded Neumann in C++ | Per-variable via `MARKER_WALL_SPECIES` in config | +| **`MARKER_ISOTHERMAL` walls** | Enthalpy computed from T via `GetEnthFromTemp`; not overridable by Python | Enthalpy set as Dirichlet `VALUE` from Python | +| **Non-enthalpy scalars** | Not reachable via Python | All controlled via the same `SetMarkerCustomScalar` call | +| **Required config keys** | `MARKER_HEATFLUX`, `MARKER_PYTHON_CUSTOM` | `MARKER_ISOTHERMAL` or `MARKER_HEATFLUX`, `MARKER_WALL_SPECIES`, `MARKER_PYTHON_CUSTOM` | + +--- + +## Notes + +- `MARKER_PYTHON_CUSTOM` is a purely additive flag. A marker can appear in + both `MARKER_HEATFLUX` (or `MARKER_ISOTHERMAL`) and `MARKER_PYTHON_CUSTOM` + simultaneously. +- The fallback values in `MARKER_WALL_SPECIES` are used when `py_custom` is + false (e.g., during a plain SU2\_CFD run without the Python driver). They + allow the same config to be used both ways. +- In `SPECIES_MARKERS` mode, `SetMarkerCustomScalar` must supply a vector of + exactly `nVar` values. Indices that correspond to `FLUX`-type variables + should receive `0.0` unless you intentionally want a non-zero flux there. +- The `CHT` (conjugate heat transfer) path (`BC_ConjugateHeat_Interface`) + always uses `BC_Isothermal_Wall_Generic` and is unaffected by + `FLAMELET_ENTHALPY_BC`. Python custom BCs are not applicable to CHT markers. diff --git a/SU2_CFD/include/solvers/CSpeciesFlameletSolver.hpp b/SU2_CFD/include/solvers/CSpeciesFlameletSolver.hpp index 10ccc65cfd36..6b9c45172985 100644 --- a/SU2_CFD/include/solvers/CSpeciesFlameletSolver.hpp +++ b/SU2_CFD/include/solvers/CSpeciesFlameletSolver.hpp @@ -162,6 +162,21 @@ class CSpeciesFlameletSolver final : public CSpeciesSolver { void BC_Isothermal_Wall(CGeometry* geometry, CSolver** solver_container, CNumerics* conv_numerics, CNumerics* visc_numerics, CConfig* config, unsigned short val_marker) override; + /*! + * \brief Impose the heat-flux wall boundary condition on the flamelet enthalpy scalar. + * When FLAMELET_ENTHALPY_BC= FLOW_MARKERS (default), the heat flux value from MARKER_HEATFLUX + * is applied as a Neumann boundary condition on the enthalpy scalar H. + * When FLAMELET_ENTHALPY_BC= SPECIES_MARKERS, use flux/value from MARKER_WALL_SPECIES. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container vector with all the solutions. + * \param[in] conv_numerics - Description of the numerical method. + * \param[in] visc_numerics - Description of the numerical method. + * \param[in] config - Definition of the particular problem. + * \param[in] val_marker - Surface marker where the boundary condition is applied. + */ + void BC_HeatFlux_Wall(CGeometry* geometry, CSolver** solver_container, CNumerics* conv_numerics, + CNumerics* visc_numerics, CConfig* config, unsigned short val_marker) override; + /*! * \brief Impose the inlet boundary condition. * \param[in] geometry - Geometrical definition of the problem. diff --git a/SU2_CFD/include/variables/CSpeciesFlameletVariable.hpp b/SU2_CFD/include/variables/CSpeciesFlameletVariable.hpp index 63aae6709b1e..fda8056fcd55 100644 --- a/SU2_CFD/include/variables/CSpeciesFlameletVariable.hpp +++ b/SU2_CFD/include/variables/CSpeciesFlameletVariable.hpp @@ -38,6 +38,7 @@ class CSpeciesFlameletVariable final : public CSpeciesVariable { MatrixType source_scalar; /*!< \brief Vector of the source terms from the lookup table for each scalar equation */ MatrixType lookup_scalar; /*!< \brief Vector of the source terms from the lookup table for each scalar equation */ su2vector table_misses; /*!< \brief Vector of lookup table misses. */ + MatrixType source_cons_jac; /*!< \brief Consumption-rate Jacobian dS_aux_i/dY_aux_i = source_cons_i, one column per user scalar. */ public: /*! @@ -87,4 +88,18 @@ class CSpeciesFlameletVariable final : public CSpeciesVariable { inline void SetTableMisses(unsigned long iPoint, unsigned short misses) override { table_misses[iPoint] = misses; } inline unsigned short GetTableMisses(unsigned long iPoint) const override { return table_misses[iPoint]; } + + /*! + * \brief Store the consumption-rate Jacobian dS_aux_i/dY_aux_i = source_cons_i for user scalar i_aux. + */ + inline void SetAuxSourceCons(unsigned long iPoint, unsigned long i_aux, su2double val) { + source_cons_jac(iPoint, i_aux) = val; + } + + /*! + * \brief Get the consumption-rate Jacobian entry for user scalar i_aux at iPoint. + */ + inline su2double GetAuxSourceCons(unsigned long iPoint, unsigned long i_aux) const { + return source_cons_jac(iPoint, i_aux); + } }; diff --git a/SU2_CFD/src/solvers/CIncEulerSolver.cpp b/SU2_CFD/src/solvers/CIncEulerSolver.cpp index 1cb4a1951bdb..04757eddef6a 100644 --- a/SU2_CFD/src/solvers/CIncEulerSolver.cpp +++ b/SU2_CFD/src/solvers/CIncEulerSolver.cpp @@ -1183,7 +1183,7 @@ void CIncEulerSolver::Centered_Residual(CGeometry *geometry, CSolver **solver_co if (LD2_Scheme) { numerics->SetPrimVarGradient(nodes->GetGradient_Primitive(iPoint), nodes->GetGradient_Primitive(jPoint)); - if (!geometry->nodes->GetPeriodicBoundary(iPoint) || (geometry->nodes->GetPeriodicBoundary(iPoint) + if (!geometry->nodes->GetPeriodicBoundary(iPoint) || (geometry->nodes->GetPeriodicBoundary(iPoint) && !geometry->nodes->GetPeriodicBoundary(jPoint))) { numerics->SetCoord(geometry->nodes->GetCoord(iPoint), geometry->nodes->GetCoord(jPoint)); } else { @@ -2535,11 +2535,19 @@ void CIncEulerSolver::BC_Inlet(CGeometry *geometry, CSolver **solver_container, if (species_model) scalar_inlet = config->GetInlet_SpeciesVal(config->GetMarker_All_TagBound(val_marker)); CFluidModel* auxFluidModel = solver_container[FLOW_SOL]->GetFluidModel(); auxFluidModel->SetTDState_T(V_inlet[prim_idx.Temperature()], scalar_inlet); - V_inlet[prim_idx.Enthalpy()] = auxFluidModel->GetEnthalpy(); + + /*--- For the flamelet model with FLOW_MARKERS enthalpy BC, we obtain the inlet enthalpy + from the flamelet species solver With SPECIES_MARKERS, the enthalpy in MARKER_INLET_SPECIES + is used directly. ---*/ + if (config->GetKind_Species_Model() == SPECIES_MODEL::FLAMELET && + config->GetFlamelet_Enthalpy_BC() == FLAMELET_ENTHALPY_BC::FLOW_MARKERS) + V_inlet[prim_idx.Enthalpy()] = nodes->GetEnthalpy(iPoint); + else + V_inlet[prim_idx.Enthalpy()] = auxFluidModel->GetEnthalpy(); /*--- Access density at the node. This is either constant by - construction, or will be set fixed implicitly by the temperature - and equation of state. ---*/ + construction, or will be set fixed implicitly by the temperature + and equation of state. ---*/ V_inlet[prim_idx.Density()] = nodes->GetDensity(iPoint); diff --git a/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp b/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp index 10fb3c49ddb4..a6d2930a9048 100644 --- a/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp +++ b/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp @@ -395,11 +395,28 @@ void CSpeciesFlameletSolver::Source_Residual(CGeometry* geometry, CSolver** solv CNumerics** numerics_container, CConfig* config, unsigned short iMesh) { SU2_ZONE_SCOPED + const bool implicit = (config->GetKind_TimeIntScheme() == EULER_IMPLICIT); + const auto n_CV = flamelet_config_options.n_control_vars; + const auto n_aux = flamelet_config_options.n_user_scalars; + const auto* fn = static_cast(nodes); + SU2_OMP_FOR_STAT(omp_chunk_size) for (auto i_point = 0u; i_point < nPointDomain; i_point++) { + const su2double volume = geometry->nodes->GetVolume(i_point); + /*--- Add source terms from the lookup table directly to the residual. ---*/ for (auto i_var = 0; i_var < nVar; i_var++) { - LinSysRes(i_point, i_var) -= nodes->GetScalarSources(i_point)[i_var] * geometry->nodes->GetVolume(i_point); + LinSysRes(i_point, i_var) -= nodes->GetScalarSources(i_point)[i_var] * volume; + } + + /*--- Implicit: analytic Jacobian for auxiliary species from the split source form. + * S_aux_i = source_prod_i + source_cons_i * Y_aux_i + * dS_aux_i/dY_aux_i = source_cons_i + * J_ii += -source_cons_i * V ---*/ + if (implicit) { + for (auto i_aux = 0u; i_aux < n_aux; i_aux++) { + Jacobian.AddVal2Diag(i_point, n_CV + i_aux, -fn->GetAuxSourceCons(i_point, i_aux) * volume); + } } } END_SU2_OMP_FOR @@ -409,17 +426,66 @@ void CSpeciesFlameletSolver::Source_Residual(CGeometry* geometry, CSolver** solv } +void CSpeciesFlameletSolver::BC_HeatFlux_Wall(CGeometry* geometry, CSolver** solver_container, + CNumerics* conv_numerics, CNumerics* visc_numerics, + CConfig* config, unsigned short val_marker) { + SU2_ZONE_SCOPED + + /*--- In FLOW_MARKERS mode: read MARKER_HEATFLUX. + In SPECIES_MARKERS mode: read flux/value from MARKER_WALL_SPECIES. ---*/ + + if (config->GetFlamelet_Enthalpy_BC() != FLAMELET_ENTHALPY_BC::FLOW_MARKERS) { + CSpeciesSolver::BC_HeatFlux_Wall(geometry, solver_container, conv_numerics, visc_numerics, config, val_marker); + return; + } + + const string Marker_Tag = config->GetMarker_All_TagBound(val_marker); + const bool py_custom = config->GetMarker_All_PyCustom(val_marker); + + su2double Wall_HeatFlux = config->GetWall_HeatFlux(Marker_Tag); + /*--- Integrated heat flux requires area normalization only when using the config value. + When py_custom is active the per-vertex flux density is set directly by the Python wrapper. ---*/ + if (config->GetIntegrated_HeatFlux() && !py_custom) + Wall_HeatFlux /= geometry->GetSurfaceArea(config, val_marker); + + SU2_OMP_FOR_DYN(OMP_MIN_SIZE) + for (auto iVertex = 0ul; iVertex < geometry->nVertex[val_marker]; iVertex++) { + const auto iPoint = geometry->vertex[val_marker][iVertex]->GetNode(); + if (!geometry->nodes->GetDomain(iPoint)) continue; + + const auto Normal = geometry->vertex[val_marker][iVertex]->GetNormal(); + const su2double Area = GeometryToolbox::Norm(nDim, Normal); + + /*--- Override with the per-vertex value set by driver.SetMarkerCustomNormalHeatFlux(). ---*/ + if (py_custom) + Wall_HeatFlux = geometry->GetCustomBoundaryHeatFlux(val_marker, iVertex); + + /*--- Neumann condition: q_wall is the prescribed heat flux (W/m^2, positive into domain). + This adds a source term dH/dn * lambda = q_wall to the enthalpy residual. ---*/ + LinSysRes(iPoint, I_ENTH) -= Wall_HeatFlux * Area; + } + END_SU2_OMP_FOR +} + void CSpeciesFlameletSolver::BC_Inlet(CGeometry* geometry, CSolver** solver_container, CNumerics* conv_numerics, CNumerics* visc_numerics, CConfig* config, unsigned short val_marker) { SU2_ZONE_SCOPED string Marker_Tag = config->GetMarker_All_TagBound(val_marker); - su2double temp_inlet = config->GetInletTtotal(Marker_Tag); - - /*--- We compute inlet enthalpy from the temperature and progress variable. ---*/ su2double enth_inlet; - GetEnthFromTemp(solver_container[FLOW_SOL]->GetFluidModel(), temp_inlet, config->GetInlet_SpeciesVal(Marker_Tag), - &enth_inlet); + if (config->GetFlamelet_Enthalpy_BC() == FLAMELET_ENTHALPY_BC::FLOW_MARKERS) { + /*--- Derive inlet enthalpy from MARKER_INLET temperature via Newton iteration on the LUT. + This ensures the enthalpy is thermodynamically consistent with the prescribed temperature, + regardless of the value given in MARKER_INLET_SPECIES. ---*/ + su2double temp_inlet = config->GetInletTtotal(Marker_Tag); + GetEnthFromTemp(solver_container[FLOW_SOL]->GetFluidModel(), temp_inlet, + config->GetInlet_SpeciesVal(Marker_Tag), &enth_inlet); + } else { + /*--- Use the enthalpy value directly from MARKER_INLET_SPECIES (default). + The user is responsible for providing a thermodynamically consistent value. ---*/ + enth_inlet = config->GetInlet_SpeciesVal(Marker_Tag)[I_ENTH]; + } + SU2_OMP_FOR_STAT(OMP_MIN_SIZE) for (auto iVertex = 0u; iVertex < geometry->nVertex[val_marker]; iVertex++) { Inlet_SpeciesVars[val_marker][iVertex][I_ENTH] = enth_inlet; @@ -519,6 +585,17 @@ void CSpeciesFlameletSolver::BC_Isothermal_Wall(CGeometry* geometry, CSolver** s CNumerics* conv_numerics, CNumerics* visc_numerics, CConfig* config, unsigned short val_marker) { SU2_ZONE_SCOPED + + /*--- In FLOW_MARKERS mode: temperature comes from MARKER_ISOTHERMAL and is + converted to enthalpy via GetEnthFromTemp (thermodynamically consistent). + In SPECIES_MARKERS mode: enthalpy is taken directly from MARKER_WALL_SPECIES, + handled by the base class BC_Wall_Generic. ---*/ + + if (config->GetFlamelet_Enthalpy_BC() != FLAMELET_ENTHALPY_BC::FLOW_MARKERS) { + CSpeciesSolver::BC_Isothermal_Wall(geometry, solver_container, conv_numerics, visc_numerics, config, val_marker); + return; + } + BC_Isothermal_Wall_Generic(geometry, solver_container, conv_numerics, visc_numerics, config, val_marker); } @@ -539,7 +616,7 @@ unsigned long CSpeciesFlameletSolver::SetScalarSources(const CConfig* config, CF table_sources[I_PROGVAR] = fmax(0, table_sources[I_PROGVAR]); nodes->SetTableMisses(iPoint, misses); - /*--- The source term for progress variable is always positive, we clip from below to makes sure. --- */ + /*--- The source term for progress variable is always positive, we clip from below to make sure. --- */ vector source_scalar(flamelet_config_options.n_scalars); for (auto iCV = 0u; iCV < flamelet_config_options.n_control_vars; iCV++) source_scalar[iCV] = table_sources[iCV]; @@ -552,6 +629,8 @@ unsigned long CSpeciesFlameletSolver::SetScalarSources(const CConfig* config, CF su2double source_prod = table_sources[flamelet_config_options.n_control_vars + 2 * i_aux]; su2double source_cons = table_sources[flamelet_config_options.n_control_vars + 2 * i_aux + 1]; source_scalar[flamelet_config_options.n_control_vars + i_aux] = source_prod + source_cons * y_aux; + /*--- Store the analytic Jacobian dS_aux/dY_aux = source_cons for implicit treatment. ---*/ + static_cast(nodes)->SetAuxSourceCons(iPoint, i_aux, source_cons); } for (auto i_scalar = 0u; i_scalar < nVar; i_scalar++) nodes->SetScalarSource(iPoint, i_scalar, source_scalar[i_scalar]); diff --git a/SU2_CFD/src/variables/CSpeciesFlameletVariable.cpp b/SU2_CFD/src/variables/CSpeciesFlameletVariable.cpp index d5d2f50419bd..a981655b39af 100644 --- a/SU2_CFD/src/variables/CSpeciesFlameletVariable.cpp +++ b/SU2_CFD/src/variables/CSpeciesFlameletVariable.cpp @@ -53,6 +53,7 @@ CSpeciesFlameletVariable::CSpeciesFlameletVariable(const su2double* species_inf, source_scalar.resize(nPoint, flamelet_config_options.n_scalars) = su2double(0.0); lookup_scalar.resize(nPoint, flamelet_config_options.n_lookups) = su2double(0.0); table_misses.resize(nPoint) = 0; + source_cons_jac.resize(nPoint, flamelet_config_options.n_user_scalars) = su2double(0.0); if (flamelet_config_options.preferential_diffusion) { AuxVar.resize(nPoint, FLAMELET_PREF_DIFF_SCALARS::N_BETA_TERMS) = su2double(0.0); diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index 97bb4a2d90ab..abbc28f1ed6a 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -47,7 +47,7 @@ def main(): cfd_flamelet_ch4.cfg_dir = "flamelet/01_laminar_premixed_ch4_flame_cfd" cfd_flamelet_ch4.cfg_file = "lam_prem_ch4_cfd.cfg" cfd_flamelet_ch4.test_iter = 10 - cfd_flamelet_ch4.test_vals = [-14.429718, -15.860038, -8.226686, -16.506667, -19.493283, 87.326000] + cfd_flamelet_ch4.test_vals = [-10.539588, -12.650786, -5.649832, -13.470491, -17.249262, 94.854000] cfd_flamelet_ch4.new_output = True test_list.append(cfd_flamelet_ch4) @@ -56,7 +56,7 @@ def main(): cfd_flamelet_ch4_axi.cfg_dir = "flamelet/05_laminar_premixed_ch4_flame_cfd_axi" cfd_flamelet_ch4_axi.cfg_file = "lam_prem_ch4_cfd_axi.cfg" cfd_flamelet_ch4_axi.test_iter = 10 - cfd_flamelet_ch4_axi.test_vals = [-11.259626, -10.020810, -11.852795, -4.909152, 95.000000] + cfd_flamelet_ch4_axi.test_vals = [-11.255837, -10.017962, -11.850828, -4.916261, 72.669000] cfd_flamelet_ch4_axi.new_output = True test_list.append(cfd_flamelet_ch4_axi) @@ -65,7 +65,7 @@ def main(): cfd_flamelet_ch4_partial_premix.cfg_dir = "flamelet/06_laminar_partial_premixed_ch4_flame_cfd" cfd_flamelet_ch4_partial_premix.cfg_file = "lam_partial_prem_ch4_cfd.cfg" cfd_flamelet_ch4_partial_premix.test_iter = 10 - cfd_flamelet_ch4_partial_premix.test_vals = [-10.482722, -3.621055, -12.938151, -10.154391, 10.000000] + cfd_flamelet_ch4_partial_premix.test_vals = [-10.482722, -3.621055, -12.932224, -10.154391, 10.000000] cfd_flamelet_ch4_partial_premix.new_output = True test_list.append(cfd_flamelet_ch4_partial_premix) @@ -74,7 +74,7 @@ def main(): cfd_flamelet_h2.cfg_dir = "flamelet/07_laminar_premixed_h2_flame_cfd" cfd_flamelet_h2.cfg_file = "laminar_premixed_h2_flame_cfd.cfg" cfd_flamelet_h2.test_iter = 5 - cfd_flamelet_h2.test_vals = [-8.036958, -8.372668, -1.842800, -9.388446] + cfd_flamelet_h2.test_vals = [-8.036794, -8.372668, -1.842800, -9.388446] test_list.append(cfd_flamelet_h2) # Flame ignition methods @@ -1786,7 +1786,7 @@ def main(): cfd_flamelet_ch4_cht.cfg_dir = "flamelet/03_laminar_premixed_ch4_flame_cht_cfd" cfd_flamelet_ch4_cht.cfg_file = "lam_prem_ch4_cht_cfd_master.cfg" cfd_flamelet_ch4_cht.test_iter = 5 - cfd_flamelet_ch4_cht.test_vals = [-9.824525, -8.913124, -9.862973, -11.271228, -3.238372, -10.916614, -12.615134, -6.614396] + cfd_flamelet_ch4_cht.test_vals = [-7.223149, -6.338839, -8.334392, -10.410156, -3.171365, -10.884464, -12.615132, -6.614396] cfd_flamelet_ch4_cht.timeout = 1600 cfd_flamelet_ch4_cht.multizone = True test_list.append(cfd_flamelet_ch4_cht) diff --git a/TestCases/parallel_regression_AD.py b/TestCases/parallel_regression_AD.py index 812c8aeb9dab..af164925d953 100644 --- a/TestCases/parallel_regression_AD.py +++ b/TestCases/parallel_regression_AD.py @@ -342,7 +342,7 @@ def main(): discadj_flamelet_ch4_hx.cfg_file = "lam_prem_ch4_hx_ad.cfg" discadj_flamelet_ch4_hx.multizone = False discadj_flamelet_ch4_hx.test_iter = 10 - discadj_flamelet_ch4_hx.test_vals = [-12.782056, -13.049168, -13.441039, -11.710302, -18.821992, -8.887596, -18.882447] + discadj_flamelet_ch4_hx.test_vals = [-9.078706, -9.025745, -9.516205, -8.434002, -15.386905, -8.887596, -18.881167] test_list.append(discadj_flamelet_ch4_hx) # 2D planar laminar premixed flame on isothermal burner with conjugate heat transfer (restart) @@ -351,7 +351,7 @@ def main(): discadj_flamelet_ch4_cht.cfg_file = "lam_prem_ch4_cht_ad_master.cfg" discadj_flamelet_ch4_cht.multizone = True discadj_flamelet_ch4_cht.test_iter = 10 - discadj_flamelet_ch4_cht.test_vals = [-1.543982, 0.628666, -6.533516, -18.651097, -18.648552, -3.794724, -6.561913, 11.000000] + discadj_flamelet_ch4_cht.test_vals = [-1.545058, 0.628666, -6.533534, -18.651078, -18.648552, -3.794724, -6.561913, 11.000000] test_list.append(discadj_flamelet_ch4_cht) ###################################### diff --git a/TestCases/tutorials.py b/TestCases/tutorials.py index 5d7a226aa005..4ab5df41ed10 100644 --- a/TestCases/tutorials.py +++ b/TestCases/tutorials.py @@ -165,7 +165,7 @@ def main(): premixed_hydrogen.cfg_dir = "../Tutorials/incompressible_flow/Inc_Combustion/1__premixed_hydrogen" premixed_hydrogen.cfg_file = "H2_burner.cfg" premixed_hydrogen.test_iter = 10 - premixed_hydrogen.test_vals = [-9.647741, -10.286349, -11.353961, -4.380211, -12.831110] + premixed_hydrogen.test_vals = [-8.772073, -9.721083, -11.354959, -4.381554, -12.832787] test_list.append(premixed_hydrogen) ### Compressible Flow diff --git a/config_template.cfg b/config_template.cfg index b801ef95fd32..6f6d5ee9bcc1 100644 --- a/config_template.cfg +++ b/config_template.cfg @@ -1,6 +1,6 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % -% SU2 configuration file % +% SU2 configuration file config_template.cfg % % Case description: _________________________________________________________ % % Author: ___________________________________________________________________ % % Institution: ______________________________________________________________ % @@ -943,7 +943,12 @@ SPECIES_CLIPPING_MIN= 0.0 % for definition of the flamelet manifold. Either LUT or MLP can be used as options. % If the terms "Beta_ProgVar", "Beta_Enth_Thermal", "Beta_Enth", and "Beta_Mixfrac" are % found in the variables list of the manifold, preferential diffusion is assumed. +% The flamelet method uses the same boundary conditions as species transport. +% FLOW_MARKERS: flamelet method uses temperature BC from MARKER_INLET and MARKER_HEATFLUX/MARKER_ISOTHERMAL. +% It then does a reverse lookup for the enthalpy. +% SPECIES_MARKERS: flamelet method uses enthalpy from MARKER_INLET_SPECIES and MARKER_WALL_SPECIES. +FLAME_ENTHALPY_BC= SPECIES_MARKERS % % Names of the user defined (auxiliary) transport equations. USER_SCALAR_NAMES= (Y-CO) From f83bde42bac4356b2d77b6d02f6c0a4b40014c0d Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Sat, 23 May 2026 19:12:04 -0700 Subject: [PATCH 10/61] Efficiency improvements for optimization of linear elasticity problems (#2815) * allow adjoin-only, fix residual and objective reporting in single and multizone solvers * save main recording for functions that don't need it * update ref --- Common/src/linear_algebra/CPastixWrapper.cpp | 12 ++++------ .../drivers/CDiscAdjSinglezoneDriver.hpp | 8 +++++++ .../src/drivers/CDiscAdjMultizoneDriver.cpp | 11 ++++++--- .../src/drivers/CDiscAdjSinglezoneDriver.cpp | 24 ++++++++++++++++++- SU2_CFD/src/drivers/CDriver.cpp | 2 -- SU2_CFD/src/output/CElasticityOutput.cpp | 6 ++--- SU2_CFD/src/solvers/CDiscAdjFEASolver.cpp | 23 ++++++++++++++---- SU2_CFD/src/solvers/CFEASolver.cpp | 15 ++++++++---- TestCases/fea_topology/grad_ref_node.dat.ref | 2 +- 9 files changed, 76 insertions(+), 27 deletions(-) diff --git a/Common/src/linear_algebra/CPastixWrapper.cpp b/Common/src/linear_algebra/CPastixWrapper.cpp index 1633a88bbde9..bcdf38ed9ccc 100644 --- a/Common/src/linear_algebra/CPastixWrapper.cpp +++ b/Common/src/linear_algebra/CPastixWrapper.cpp @@ -204,8 +204,7 @@ void CPastixWrapper::Initialize(CGeometry* geometry, const CConfig* SU2_MPI::Error("Error analyzing matrix: " + std::to_string(rc), CURRENT_FUNCTION); } - if (mpi_rank == MASTER_NODE && verb > 0) - cout << " +--------------------------------------------------------------------+" << endl; + if (mpi_rank == MASTER_NODE && verb > 0) cout << "+-------------------------------------------------+" << endl; isinitialized = true; } @@ -250,10 +249,8 @@ void CPastixWrapper::Factorize(CGeometry* geometry, const CConfig* c /*--- Yes ---*/ if (mpi_rank == MASTER_NODE && verb > 0) { - cout << endl; - cout << " +--------------------------------------------------------------------+" << endl; - cout << " + PaStiX : Parallel Sparse matriX package +" << endl; - cout << " +--------------------------------------------------------------------+" << endl; + cout << "\n+-------------------------------------------------+"; + cout << "\n+ PaStiX : Parallel Sparse matriX package +" << endl; } const unsigned long szBlk = matrix.nVar * matrix.nVar, nNonZero = values.size(); @@ -297,8 +294,7 @@ void CPastixWrapper::Factorize(CGeometry* geometry, const CConfig* c SU2_MPI::Error("Error factorizing matrix: " + std::to_string(rc), CURRENT_FUNCTION); } - if (mpi_rank == MASTER_NODE && verb > 0) - cout << " +--------------------------------------------------------------------+" << endl << endl; + if (mpi_rank == MASTER_NODE && verb > 0) cout << "+-------------------------------------------------+\n" << endl; isfactorized = true; } diff --git a/SU2_CFD/include/drivers/CDiscAdjSinglezoneDriver.hpp b/SU2_CFD/include/drivers/CDiscAdjSinglezoneDriver.hpp index 137c961f1482..6a2f00932ef1 100644 --- a/SU2_CFD/include/drivers/CDiscAdjSinglezoneDriver.hpp +++ b/SU2_CFD/include/drivers/CDiscAdjSinglezoneDriver.hpp @@ -55,6 +55,14 @@ class CDiscAdjSinglezoneDriver : public CSinglezoneDriver { COutput *direct_output; CNumerics ***numerics; /*!< \brief Container vector with all the numerics. */ + /*! + * \brief Returns true if the objective function does not depend on the main variables. In which case, + * the adjoint variables are 0 and the sensitivities can be computed just with the secondary recording. + */ + bool TrivialFunction() const { + return config_container[ZONE_0]->GetnObj() == 1 && config_container[ZONE_0]->GetKind_ObjFunc() == VOLUME_FRACTION; + } + /*! * \brief Record one iteration of a flow iteration in within multiple zones. * \param[in] kind_recording - Type of recording (full list in ENUM_RECORDING, option_structure.hpp) diff --git a/SU2_CFD/src/drivers/CDiscAdjMultizoneDriver.cpp b/SU2_CFD/src/drivers/CDiscAdjMultizoneDriver.cpp index 61673bdad1b3..c9529050da06 100644 --- a/SU2_CFD/src/drivers/CDiscAdjMultizoneDriver.cpp +++ b/SU2_CFD/src/drivers/CDiscAdjMultizoneDriver.cpp @@ -719,7 +719,9 @@ void CDiscAdjMultizoneDriver::SetRecording(RECORDING kind_recording, Kind_Tape t switch(kind_recording) { case RECORDING::CLEAR_INDICES: cout << "Clearing the computational graph." << endl; break; case RECORDING::MESH_COORDS: cout << "Storing computational graph wrt MESH COORDINATES." << endl; break; - case RECORDING::SOLUTION_VARIABLES: cout << "Storing computational graph wrt CONSERVATIVE VARIABLES." << endl; break; + case RECORDING::SOLUTION_VARIABLES: + cout << "Storing computational graph wrt CONSERVATIVE VARIABLES.\n"; + cout << "Computing residuals to check the convergence of the direct problem." << endl; break; case RECORDING::TAG_INIT_SOLVER_VARIABLES: cout << "Simulating recording with tag 1 on conservative variables." << endl; AD::SetTag(1); break; case RECORDING::TAG_CHECK_SOLVER_VARIABLES: cout << "Checking first recording with tag 2 on conservative variables." << endl; AD::SetTag(2); break; case RECORDING::TAG_INIT_SOLVER_AND_MESH: cout << "Simulating recording with tag 1 on conservative variables and mesh coordinates." << endl; AD::SetTag(1); break; @@ -850,7 +852,7 @@ void CDiscAdjMultizoneDriver::SetObjFunction(RECORDING kind_recording) { solvers[FLOW_SOL]->Momentum_Forces(geometry, config); solvers[FLOW_SOL]->Friction_Forces(geometry, config); - if(config->GetWeakly_Coupled_Heat()) { + if (config->GetWeakly_Coupled_Heat()) { solvers[HEAT_SOL]->Heat_Fluxes(geometry, solvers, config); } @@ -865,6 +867,9 @@ void CDiscAdjMultizoneDriver::SetObjFunction(RECORDING kind_recording) { break; case MAIN_SOLVER::DISC_ADJ_FEM: + if (config->GetWeakly_Coupled_Heat()) { + solvers[HEAT_SOL]->Heat_Fluxes(geometry, solvers, config); + } solvers[FEA_SOL]->Postprocessing(geometry, config, numerics_container[iZone][INST_0][MESH_0][FEA_SOL], true); direct_output[iZone]->SetHistoryOutput(geometry, solvers, config); ObjFunc += solvers[FEA_SOL]->GetTotal_ComboObj(); @@ -883,7 +888,7 @@ void CDiscAdjMultizoneDriver::SetObjFunction(RECORDING kind_recording) { kind_recording == RECORDING::TAG_CHECK_SOLVER_VARIABLES || kind_recording == RECORDING::TAG_INIT_SOLVER_AND_MESH || kind_recording == RECORDING::TAG_CHECK_SOLVER_AND_MESH) { - cout << " Objective function : " << ObjFunc << endl; + cout << "Objective function value: " << std::setprecision(driver_config->GetOutput_Precision()) << ObjFunc << endl; } } } diff --git a/SU2_CFD/src/drivers/CDiscAdjSinglezoneDriver.cpp b/SU2_CFD/src/drivers/CDiscAdjSinglezoneDriver.cpp index ce6f7d93b10f..5fcd203b84d0 100644 --- a/SU2_CFD/src/drivers/CDiscAdjSinglezoneDriver.cpp +++ b/SU2_CFD/src/drivers/CDiscAdjSinglezoneDriver.cpp @@ -156,6 +156,12 @@ void CDiscAdjSinglezoneDriver::Preprocess(unsigned long TimeIter) { void CDiscAdjSinglezoneDriver::Run() { SU2_ZONE_SCOPED + /*--- No need to solve anything, the tape for the main recording is empty. ---*/ + if (TrivialFunction()) { + SetAllSolutions(ZONE_0, true, [](auto, auto) { return 0; }); + return; + } + CQuasiNewtonInvLeastSquares fixPtCorrector; if (config->GetnQuasiNewtonSamples() > 1) { fixPtCorrector.resize(config->GetnQuasiNewtonSamples(), @@ -273,7 +279,7 @@ void CDiscAdjSinglezoneDriver::SetRecording(RECORDING kind_recording){ case RECORDING::CLEAR_INDICES: cout << "Clearing the computational graph." << endl; break; case RECORDING::MESH_COORDS: cout << "Storing computational graph wrt MESH COORDINATES." << endl; break; case RECORDING::SOLUTION_VARIABLES: - cout << "Direct iteration to store the primal computational graph." << endl; + cout << "Direct iteration to store the primal computational graph.\n"; cout << "Computing residuals to check the convergence of the direct problem." << endl; break; default: break; } @@ -309,6 +315,12 @@ void CDiscAdjSinglezoneDriver::SetRecording(RECORDING kind_recording){ SetObjFunction(); + if (rank == MASTER_NODE && + (kind_recording == RECORDING::SOLUTION_VARIABLES || (TrivialFunction() && kind_recording == RECORDING::MESH_COORDS))) { + cout << "\nObjective function value: " << std::setprecision(config_container[ZONE_0]->GetOutput_Precision()) << ObjFunc; + cout << "\n-------------------------------------------------------------------------\n" << endl; + } + if (kind_recording != RECORDING::CLEAR_INDICES && config_container[ZONE_0]->GetWrt_AD_Statistics()) { AD::PrintStatistics(SU2_MPI::GetComm(), rank == MASTER_NODE); } @@ -410,6 +422,16 @@ void CDiscAdjSinglezoneDriver::DirectRun(RECORDING kind_recording){ void CDiscAdjSinglezoneDriver::MainRecording(){ SU2_ZONE_SCOPED + + /*--- We know this function only depends on the secondary variables, hence skip the main recording. ---*/ + + if (TrivialFunction()) { + if (rank == MASTER_NODE) { + cout << "Trivial objective function, skipping the solution of the adjoint equations." << endl; + } + return; + } + /*--- SetRecording stores the computational graph on one iteration of the direct problem. Calling it with * RECORDING::CLEAR_INDICES as argument ensures that all information from a previous recording is removed. ---*/ diff --git a/SU2_CFD/src/drivers/CDriver.cpp b/SU2_CFD/src/drivers/CDriver.cpp index daf5037a538e..8689f42ab9f2 100644 --- a/SU2_CFD/src/drivers/CDriver.cpp +++ b/SU2_CFD/src/drivers/CDriver.cpp @@ -2886,8 +2886,6 @@ void CDriver::PrintDirectResidual(RECORDING kind_recording) { } // for addVals - cout << "\n-------------------------------------------------------------------------\n" << endl; - } diff --git a/SU2_CFD/src/output/CElasticityOutput.cpp b/SU2_CFD/src/output/CElasticityOutput.cpp index 2694b034674d..1a78dbd25e60 100644 --- a/SU2_CFD/src/output/CElasticityOutput.cpp +++ b/SU2_CFD/src/output/CElasticityOutput.cpp @@ -107,10 +107,10 @@ void CElasticityOutput::LoadHistoryData(CConfig *config, CGeometry *geometry, CS /*--- Nonlinear analysis: UTOL, RTOL and DTOL (defined in the Postprocessing function) ---*/ if (linear_analysis){ - SetHistoryOutputValue("RMS_DISP_X", log10(fea_solver->GetRes_RMS(0))); - SetHistoryOutputValue("RMS_DISP_Y", log10(fea_solver->GetRes_RMS(1))); + SetHistoryOutputValue("RMS_DISP_X", log10(fea_solver->GetRes_FEM(0))); + SetHistoryOutputValue("RMS_DISP_Y", log10(fea_solver->GetRes_FEM(1))); if (nDim == 3){ - SetHistoryOutputValue("RMS_DISP_Z", log10(fea_solver->GetRes_RMS(2))); + SetHistoryOutputValue("RMS_DISP_Z", log10(fea_solver->GetRes_FEM(2))); } } else if (nonlinear_analysis){ SetHistoryOutputValue("RMS_UTOL", log10(fea_solver->GetRes_FEM(0))); diff --git a/SU2_CFD/src/solvers/CDiscAdjFEASolver.cpp b/SU2_CFD/src/solvers/CDiscAdjFEASolver.cpp index 8e1a6499db50..f8e2844d3fd7 100644 --- a/SU2_CFD/src/solvers/CDiscAdjFEASolver.cpp +++ b/SU2_CFD/src/solvers/CDiscAdjFEASolver.cpp @@ -126,16 +126,31 @@ CDiscAdjFEASolver::~CDiscAdjFEASolver() { delete nodes; } void CDiscAdjFEASolver::SetRecording(CGeometry* geometry, CConfig *config){ SU2_ZONE_SCOPED - /*--- Reset the solution to the initial (converged) solution ---*/ + /*--- Under some conditions, linear elasticity problems converge in one iteration. + * This means that the "clear indices" step of the discrete adjoint solver produces a + * converged solution regardless of the primal solution given to the adjoint solver. + * We can take advantage of this for optimization to skip the primal solver. ---*/ + + const bool linear = config->GetGeometricConditions() == STRUCT_DEFORMATION::SMALL; + const bool heat = config->GetWeakly_Coupled_Heat(); + const bool time_domain = config->GetTime_Domain(); + const bool keep_solution = linear && !heat && !time_domain && !std::is_same_v; + + /*--- Restore the solution to the initial (converged) solution or reset AD indices. ---*/ for (auto iPoint = 0ul; iPoint < nPoint; iPoint++) { - for (auto iVar = 0u; iVar < nVar; iVar++) - direct_solver->GetNodes()->SetSolution(iPoint, iVar, nodes->GetSolution_Direct(iPoint)[iVar]); + if (keep_solution) { + for (auto iVar = 0u; iVar < nVar; iVar++) + AD::ResetInput(direct_solver->GetNodes()->GetSolution(iPoint)[iVar]); + } else { + for (auto iVar = 0u; iVar < nVar; iVar++) + direct_solver->GetNodes()->SetSolution(iPoint, iVar, nodes->GetSolution_Direct(iPoint)[iVar]); + } } /*--- Reset the input for time n ---*/ - if (config->GetTime_Domain()) { + if (time_domain) { for (auto iPoint = 0ul; iPoint < nPoint; iPoint++) for (auto iVar = 0u; iVar < nVar; iVar++) AD::ResetInput(direct_solver->GetNodes()->GetSolution_time_n(iPoint)[iVar]); diff --git a/SU2_CFD/src/solvers/CFEASolver.cpp b/SU2_CFD/src/solvers/CFEASolver.cpp index e12f92140a4a..31cb41c52d93 100644 --- a/SU2_CFD/src/solvers/CFEASolver.cpp +++ b/SU2_CFD/src/solvers/CFEASolver.cpp @@ -1833,14 +1833,13 @@ void CFEASolver::Postprocessing(CGeometry *geometry, CConfig *config, CNumerics /*--- RTOL = norm(Residual(k): ABSOLUTE, norm of the residual (T-F) ---*/ /*--- ETOL = Delta_U(k) * Residual(k): ABSOLUTE, energy norm ---*/ - SU2_OMP_PARALLEL - { + SU2_OMP_PARALLEL { + su2double utol = LinSysSol.norm(); su2double rtol = LinSysRes.norm(); su2double etol = fabs(LinSysSol.dot(LinSysRes)); - SU2_OMP_MASTER - { + SU2_OMP_MASTER { Conv_Check[0] = utol; Conv_Check[1] = rtol; Conv_Check[2] = etol; @@ -1872,8 +1871,14 @@ void CFEASolver::Postprocessing(CGeometry *geometry, CConfig *config, CNumerics END_SU2_OMP_FOR /*--- "Add" residuals from all threads to global residual variables. ---*/ - ResidualReductions_FromAllThreads(geometry, config, resRMS,resMax,idxMax); + ResidualReductions_FromAllThreads(geometry, config, resRMS, resMax, idxMax); + SU2_OMP_MASTER { + Conv_Check[0] = Residual_RMS[0]; + Conv_Check[1] = Residual_RMS[1]; + Conv_Check[2] = nDim == 3 ? Residual_RMS[2] : 0; + } + END_SU2_OMP_MASTER } END_SU2_OMP_PARALLEL diff --git a/TestCases/fea_topology/grad_ref_node.dat.ref b/TestCases/fea_topology/grad_ref_node.dat.ref index 6f3a4d4a6854..15afff813c19 100644 --- a/TestCases/fea_topology/grad_ref_node.dat.ref +++ b/TestCases/fea_topology/grad_ref_node.dat.ref @@ -6157,7 +6157,7 @@ -9.57383e-11 -2.60763e-10 -2.54078e-10 --1.57598e-10 +-1.57599e-10 -8.68931e-12 -4.26927e-13 0 From 17e38a8c9d96cd0daca72ca108f95d8b4ac122bf Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Sun, 24 May 2026 09:59:09 -0700 Subject: [PATCH 11/61] Option to compile everything in single precision + explicit vectorization for ARM (#2819) * add option * define single precision type, mpi, avoid repeated instantiations in mixed precision * use correct tecplot function * option 1 to handle double precision literals: replace min/max with fmin/fmax * Revert "option 1 to handle double precision literals: replace min/max with fmin/fmax" This reverts commit 4e4d9e12037709aa345d18b90cd9973abb93fc2f. * option 2: overload std::min/max for float/double mix, handle blas and lapack functions, fix some FPEs * fix some FPEs * vectorization for floats * arm vectorization * arm fix and use cpu arch for arm, fix some pass by value warnings * remove commented code * remove debug stuff * update regressions --- .github/workflows/regression.yml | 2 +- Common/include/code_config.hpp | 32 ++- .../include/linear_algebra/CPastixWrapper.hpp | 4 +- Common/include/linear_algebra/CSysSolve.hpp | 4 +- .../include/parallelization/mpi_structure.cpp | 2 +- .../include/parallelization/mpi_structure.hpp | 39 +-- .../include/parallelization/vectorization.hpp | 228 +++++++++++++++++- Common/src/fem/fem_geometry_structure.cpp | 15 +- Common/src/geometry/CMultiGridGeometry.cpp | 2 +- Common/src/geometry/CPhysicalGeometry.cpp | 6 +- Common/src/grid_movement/CSurfaceMovement.cpp | 2 +- .../CRadialBasisFunction.cpp | 16 +- Common/src/linear_algebra/CPastixWrapper.cpp | 2 +- Common/src/linear_algebra/CSysMatrix.cpp | 22 +- Common/src/linear_algebra/CSysSolve.cpp | 2 +- Common/src/linear_algebra/CSysVector.cpp | 2 +- Common/src/linear_algebra/blas_structure.cpp | 20 +- Common/src/toolboxes/CSquareMatrixCM.cpp | 27 ++- Common/src/toolboxes/CSymmetricMatrix.cpp | 41 ++-- Common/src/wall_model.cpp | 11 +- .../flow/convection/centered.hpp | 48 ++-- .../numerics_simd/flow/convection/common.hpp | 76 +++--- .../numerics_simd/flow/convection/upwind.hpp | 22 +- .../numerics_simd/flow/diffusion/common.hpp | 10 +- .../flow/diffusion/viscous_fluxes.hpp | 36 +-- .../include/numerics_simd/flow/variables.hpp | 2 +- .../filewriter/CTecplotBinaryFileWriter.cpp | 16 +- SU2_CFD/src/solvers/CEulerSolver.cpp | 4 +- SU2_CFD/src/solvers/CFEM_DG_EulerSolver.cpp | 4 +- SU2_CFD/src/solvers/CIncEulerSolver.cpp | 4 +- SU2_CFD/src/solvers/CNEMOEulerSolver.cpp | 4 +- SU2_CFD/src/solvers/CSolver.cpp | 2 +- SU2_CFD/src/solvers/CTurbSASolver.cpp | 64 +++-- TestCases/hybrid_regression.py | 16 +- TestCases/parallel_regression.py | 12 +- TestCases/parallel_regression_AD.py | 2 +- TestCases/serial_regression.py | 12 +- TestCases/vandv.py | 8 +- meson.build | 5 +- meson_options.txt | 1 + 40 files changed, 571 insertions(+), 256 deletions(-) diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml index 02c94dd1198f..c4e3b652b069 100644 --- a/.github/workflows/regression.yml +++ b/.github/workflows/regression.yml @@ -30,7 +30,7 @@ jobs: - id: compute run: | if [[ "${{ inputs.runner || 'ubuntu-latest' }}" == *arm* ]]; then - echo "flags=" >> $GITHUB_OUTPUT + echo "flags=-Dcpu-arch=armv9-a+simd" >> $GITHUB_OUTPUT echo "werror=" >> $GITHUB_OUTPUT else echo "flags=-Dcpu-arch=skylake" >> $GITHUB_OUTPUT diff --git a/Common/include/code_config.hpp b/Common/include/code_config.hpp index 491777a01f3d..bc02c2830aab 100644 --- a/Common/include/code_config.hpp +++ b/Common/include/code_config.hpp @@ -27,6 +27,7 @@ #pragma once #include +#include #if defined(_MSC_VER) #define PRAGMIZE(X) __pragma(X) @@ -94,6 +95,31 @@ FORCEINLINE Out su2staticcast_p(In ptr) { #define HAVE_OMP #endif +/*--- No full single precision for AD builds. ---*/ +#if (defined(CODI_REVERSE_TYPE) || defined(CODI_FORWARD_TYPE)) && defined(USE_SINGLE_PRECISION) +#undef USE_SINGLE_PRECISION +#endif + +/*--- This type can be used for (rare) compatibility cases or for + * computations that are intended to be (always) passive. ---*/ +#ifdef USE_SINGLE_PRECISION +using passivedouble = float; +#else +using passivedouble = double; +#endif + +/*--- std::min/max do not compile if the arguments have inconsistent types, which + * happens in single precision due to floating point literals (double by default). + * These overloads delegate to fmin/fmax which do not have that problem. ---*/ +#ifdef USE_SINGLE_PRECISION +namespace std { +FORCEINLINE float min(const float& a, const double& b) { return fmin(a, static_cast(b)); } +FORCEINLINE float min(const double& b, const float& a) { return fmin(a, static_cast(b)); } +FORCEINLINE float max(const float& a, const double& b) { return fmax(a, static_cast(b)); } +FORCEINLINE float max(const double& b, const float& a) { return fmax(a, static_cast(b)); } +} // namespace std +#endif + /*--- Depending on the datatype defined during the configuration, * include the correct definition, and create the main typedef. ---*/ @@ -131,13 +157,9 @@ using su2double = codi::RealReverseTag; #include "codi.hpp" using su2double = codi::RealForward; #else // primal / direct / no AD -using su2double = double; +using su2double = passivedouble; #endif -/*--- This type can be used for (rare) compatibility cases or for - * computations that are intended to be (always) passive. ---*/ -using passivedouble = double; - /*--- Define a type for potentially lower precision operations. ---*/ #ifndef CODI_FORWARD_TYPE #ifdef USE_MIXED_PRECISION diff --git a/Common/include/linear_algebra/CPastixWrapper.hpp b/Common/include/linear_algebra/CPastixWrapper.hpp index 7ee570010f0b..dcc320debe1c 100644 --- a/Common/include/linear_algebra/CPastixWrapper.hpp +++ b/Common/include/linear_algebra/CPastixWrapper.hpp @@ -62,8 +62,8 @@ class CPastixWrapper { vector perm; /*!< \brief Ordering computed by PaStiX. */ vector workvec; /*!< \brief RHS vector which then becomes the solution. */ - pastix_int_t iparm[IPARM_SIZE]; /*!< \brief Integer parameters for PaStiX. */ - passivedouble dparm[DPARM_SIZE]; /*!< \brief Floating point parameters for PaStiX. */ + pastix_int_t iparm[IPARM_SIZE]; /*!< \brief Integer parameters for PaStiX. */ + double dparm[DPARM_SIZE]; /*!< \brief Floating point parameters for PaStiX. */ struct { unsigned long nVar = 0; diff --git a/Common/include/linear_algebra/CSysSolve.hpp b/Common/include/linear_algebra/CSysSolve.hpp index 91e9b11f4e26..2ea3cbf7df30 100644 --- a/Common/include/linear_algebra/CSysSolve.hpp +++ b/Common/include/linear_algebra/CSysSolve.hpp @@ -442,13 +442,13 @@ class CSysSolve { * \brief Get the number of iterations. * \return The number of iterations done by Solve or Solve_b */ - inline unsigned long GetIterations(void) const { return Iterations; } + inline unsigned long GetIterations() const { return Iterations; } /*! * \brief Get the final residual. * \return The residual at the end of Solve or Solve_b */ - inline ScalarType GetResidual(void) const { return Residual; } + inline ScalarType GetResidual() const { return Residual; } /*! * \brief Set the type of the tolerance for stoping the linear solvers (RELATIVE or ABSOLUTE). diff --git a/Common/include/parallelization/mpi_structure.cpp b/Common/include/parallelization/mpi_structure.cpp index 03152e709e45..e04759b9f952 100644 --- a/Common/include/parallelization/mpi_structure.cpp +++ b/Common/include/parallelization/mpi_structure.cpp @@ -190,7 +190,7 @@ template class CBaseMPIWrapper; #if defined CODI_REVERSE_TYPE template class CBaseMPIWrapper; #endif -#if defined USE_MIXED_PRECISION +#if defined(USE_MIXED_PRECISION) && !defined(USE_SINGLE_PRECISION) template class CBaseMPIWrapper; #endif diff --git a/Common/include/parallelization/mpi_structure.hpp b/Common/include/parallelization/mpi_structure.hpp index afe0c666dcbf..242aa00d7b0f 100644 --- a/Common/include/parallelization/mpi_structure.hpp +++ b/Common/include/parallelization/mpi_structure.hpp @@ -61,6 +61,11 @@ #ifdef HAVE_MPI +#ifdef USE_SINGLE_PRECISION +#undef MPI_DOUBLE +#define MPI_DOUBLE MPI_FLOAT +#endif + /*--- Depending on the datatype used, the correct MPI wrapper class is defined. * For the default (double type) case this results in using the normal MPI routines. ---*/ #if defined CODI_REVERSE_TYPE || defined CODI_FORWARD_TYPE @@ -71,10 +76,10 @@ using namespace medi; #include class CMediMPIWrapper; -typedef CMediMPIWrapper SU2_MPI; +using SU2_MPI = CMediMPIWrapper; -typedef codi::CoDiMpiTypes MediTypes; -typedef MediTypes::Tool MediTool; +using MediTypes = codi::CoDiMpiTypes; +using MediTool = MediTypes::Tool; extern MediTypes* mediTypes; #define AMPI_ADOUBLE ((medi::MpiTypeInterface*)mediTypes->MPI_TYPE) @@ -91,12 +96,12 @@ using SU2_MPI = CBaseMPIWrapper; */ class CBaseMPIWrapper { public: - typedef MPI_Request Request; - typedef MPI_Status Status; - typedef MPI_Datatype Datatype; - typedef MPI_Op Op; - typedef MPI_Comm Comm; - typedef MPI_Win Win; + using Request = MPI_Request; + using Status = MPI_Status; + using Datatype = MPI_Datatype; + using Op = MPI_Op; + using Comm = MPI_Comm; + using Win = MPI_Win; protected: static int Rank, Size, MinRankError; @@ -256,7 +261,7 @@ class CBaseMPIWrapper { static inline passivedouble Wtime(void) { return MPI_Wtime(); } }; -typedef MPI_Comm SU2_Comm; +using SU2_Comm = MPI_Comm; #if defined CODI_REVERSE_TYPE || defined CODI_FORWARD_TYPE @@ -267,8 +272,8 @@ typedef MPI_Comm SU2_Comm; class CMediMPIWrapper : public CBaseMPIWrapper { public: - typedef AMPI_Request Request; - typedef AMPI_Status Status; + using Request = AMPI_Request; + using Status = AMPI_Status; static inline void Init(int* argc, char*** argv) { AMPI_Init(argc, argv); @@ -492,10 +497,10 @@ class CMediMPIWrapper : public CBaseMPIWrapper { template class CBaseMPIWrapper { public: - typedef int Comm; - typedef int Datatype; - typedef int Request; - typedef int Op; + using Comm = int; + using Datatype = int; + using Request = int; + using Op = int; struct Status { int MPI_TAG; @@ -632,7 +637,7 @@ struct SelectMPIWrapper { #endif /*--- Specialize for the low precision type. ---*/ -#if defined(USE_MIXED_PRECISION) +#if defined(USE_MIXED_PRECISION) && !defined(USE_SINGLE_PRECISION) template <> struct SelectMPIWrapper { #if defined HAVE_MPI diff --git a/Common/include/parallelization/vectorization.hpp b/Common/include/parallelization/vectorization.hpp index 34747913843a..b11d19780af9 100644 --- a/Common/include/parallelization/vectorization.hpp +++ b/Common/include/parallelization/vectorization.hpp @@ -35,6 +35,9 @@ #ifdef __SSE2__ #include "x86intrin.h" #endif +#if defined(__ARM_NEON) || defined(__ARM_NEON__) +#include +#endif namespace simd { /// \addtogroup SIMD @@ -47,7 +50,7 @@ using namespace VecExpr; constexpr size_t PREFERRED_SIZE = 64; #elif defined(__AVX__) constexpr size_t PREFERRED_SIZE = 32; -#elif defined(__SSE2__) +#elif defined(__SSE2__) || defined(__ARM_NEON) || defined(__ARM_NEON__) constexpr size_t PREFERRED_SIZE = 16; #else constexpr size_t PREFERRED_SIZE = 8; @@ -200,9 +203,11 @@ struct SIXTEEN {}; /*--- Constants for bitwise implementations. ---*/ /*--- abs forces the sign bit to 0 ("x" & 0b0111...). ---*/ -constexpr auto abs_mask_d = 0x7FFFFFFFFFFFFFFFL; +constexpr uint64_t abs_mask_d = 0x7FFFFFFFFFFFFFFFL; +constexpr uint32_t abs_mask_s = 0x7FFFFFFFU; /*--- negation flips the sign bit ("x" ^ 0b1000...). ---*/ -constexpr auto sign_mask_d = 0x8000000000000000L; +constexpr uint64_t sign_mask_d = 0x8000000000000000L; +constexpr uint32_t sign_mask_s = 0x80000000U; #ifdef __SSE2__ /*! @@ -248,6 +253,46 @@ FORCEINLINE __m128d sign_p(__m128d x) { return _mm_or_pd(ones_2d, _mm_and_pd(x, #include "special_vectorization.hpp" +/*! + * Create specialization for array of 4 floats (this should be always available). + */ +#define ARRAY_T Array +#define SCALAR_T float +#define REGISTER_T __m128 +#define SIZE_TAG SizeTag::FOUR() + +static const __m128 abs_mask_4s = _mm_castsi128_ps(_mm_set1_epi32(abs_mask_s)); +static const __m128 sign_mask_4s = _mm_castsi128_ps(_mm_set1_epi32(sign_mask_s)); +static const __m128 ones_4s = _mm_set1_ps(1); + +FORCEINLINE __m128 set1_p(SizeTag::FOUR, float p) { return _mm_set1_ps(p); } +FORCEINLINE __m128 load_p(SizeTag::FOUR, const float* p) { return _mm_load_ps(p); } +FORCEINLINE __m128 loadu_p(SizeTag::FOUR, const float* p) { return _mm_loadu_ps(p); } +FORCEINLINE void store_p(float* p, __m128 x) { _mm_store_ps(p, x); } +FORCEINLINE void storeu_p(float* p, __m128 x) { _mm_storeu_ps(p, x); } +FORCEINLINE void stream_p(float* p, __m128 x) { _mm_stream_ps(p, x); } + +FORCEINLINE __m128 add_p(__m128 a, __m128 b) { return _mm_add_ps(a, b); } +FORCEINLINE __m128 sub_p(__m128 a, __m128 b) { return _mm_sub_ps(a, b); } +FORCEINLINE __m128 mul_p(__m128 a, __m128 b) { return _mm_mul_ps(a, b); } +FORCEINLINE __m128 div_p(__m128 a, __m128 b) { return _mm_div_ps(a, b); } +FORCEINLINE __m128 max_p(__m128 a, __m128 b) { return _mm_max_ps(a, b); } +FORCEINLINE __m128 min_p(__m128 a, __m128 b) { return _mm_min_ps(a, b); } + +FORCEINLINE __m128 eq_p(__m128 a, __m128 b) { return _mm_and_ps(ones_4s, _mm_cmpeq_ps(a, b)); } +FORCEINLINE __m128 lt_p(__m128 a, __m128 b) { return _mm_and_ps(ones_4s, _mm_cmplt_ps(a, b)); } +FORCEINLINE __m128 le_p(__m128 a, __m128 b) { return _mm_and_ps(ones_4s, _mm_cmple_ps(a, b)); } +FORCEINLINE __m128 ne_p(__m128 a, __m128 b) { return _mm_and_ps(ones_4s, _mm_cmpneq_ps(a, b)); } +FORCEINLINE __m128 ge_p(__m128 a, __m128 b) { return _mm_and_ps(ones_4s, _mm_cmpge_ps(a, b)); } +FORCEINLINE __m128 gt_p(__m128 a, __m128 b) { return _mm_and_ps(ones_4s, _mm_cmpgt_ps(a, b)); } + +FORCEINLINE __m128 sqrt_p(__m128 x) { return _mm_sqrt_ps(x); } +FORCEINLINE __m128 abs_p(__m128 x) { return _mm_and_ps(x, abs_mask_4s); } +FORCEINLINE __m128 neg_p(__m128 x) { return _mm_xor_ps(x, sign_mask_4s); } +FORCEINLINE __m128 sign_p(__m128 x) { return _mm_or_ps(ones_4s, _mm_and_ps(x, sign_mask_4s)); } + +#include "special_vectorization.hpp" + #endif // __SSE2__ #ifdef __AVX__ @@ -291,6 +336,46 @@ FORCEINLINE __m256d sign_p(__m256d x) { return _mm256_or_pd(ones_4d, _mm256_and_ #include "special_vectorization.hpp" +/*! + * Create specialization for array of 8 floats. + */ +#define ARRAY_T Array +#define SCALAR_T float +#define REGISTER_T __m256 +#define SIZE_TAG SizeTag::EIGHT() + +static const __m256 abs_mask_8s = _mm256_castsi256_ps(_mm256_set1_epi32(abs_mask_s)); +static const __m256 sign_mask_8s = _mm256_castsi256_ps(_mm256_set1_epi32(sign_mask_s)); +static const __m256 ones_8s = _mm256_set1_ps(1); + +FORCEINLINE __m256 set1_p(SizeTag::EIGHT, float p) { return _mm256_set1_ps(p); } +FORCEINLINE __m256 load_p(SizeTag::EIGHT, const float* p) { return _mm256_load_ps(p); } +FORCEINLINE __m256 loadu_p(SizeTag::EIGHT, const float* p) { return _mm256_loadu_ps(p); } +FORCEINLINE void store_p(float* p, __m256 x) { _mm256_store_ps(p, x); } +FORCEINLINE void storeu_p(float* p, __m256 x) { _mm256_storeu_ps(p, x); } +FORCEINLINE void stream_p(float* p, __m256 x) { _mm256_stream_ps(p, x); } + +FORCEINLINE __m256 add_p(__m256 a, __m256 b) { return _mm256_add_ps(a, b); } +FORCEINLINE __m256 sub_p(__m256 a, __m256 b) { return _mm256_sub_ps(a, b); } +FORCEINLINE __m256 mul_p(__m256 a, __m256 b) { return _mm256_mul_ps(a, b); } +FORCEINLINE __m256 div_p(__m256 a, __m256 b) { return _mm256_div_ps(a, b); } +FORCEINLINE __m256 max_p(__m256 a, __m256 b) { return _mm256_max_ps(a, b); } +FORCEINLINE __m256 min_p(__m256 a, __m256 b) { return _mm256_min_ps(a, b); } + +FORCEINLINE __m256 eq_p(__m256 a, __m256 b) { return _mm256_and_ps(ones_8s, _mm256_cmp_ps(a, b, 0)); } +FORCEINLINE __m256 lt_p(__m256 a, __m256 b) { return _mm256_and_ps(ones_8s, _mm256_cmp_ps(a, b, 1)); } +FORCEINLINE __m256 le_p(__m256 a, __m256 b) { return _mm256_and_ps(ones_8s, _mm256_cmp_ps(a, b, 2)); } +FORCEINLINE __m256 ne_p(__m256 a, __m256 b) { return _mm256_and_ps(ones_8s, _mm256_cmp_ps(a, b, 4)); } +FORCEINLINE __m256 ge_p(__m256 a, __m256 b) { return _mm256_and_ps(ones_8s, _mm256_cmp_ps(a, b, 13)); } +FORCEINLINE __m256 gt_p(__m256 a, __m256 b) { return _mm256_and_ps(ones_8s, _mm256_cmp_ps(a, b, 14)); } + +FORCEINLINE __m256 sqrt_p(__m256 x) { return _mm256_sqrt_ps(x); } +FORCEINLINE __m256 abs_p(__m256 x) { return _mm256_and_ps(x, abs_mask_8s); } +FORCEINLINE __m256 neg_p(__m256 x) { return _mm256_xor_ps(x, sign_mask_8s); } +FORCEINLINE __m256 sign_p(__m256 x) { return _mm256_or_ps(ones_8s, _mm256_and_ps(x, sign_mask_8s)); } + +#include "special_vectorization.hpp" + #endif // __AVX__ #ifdef __AVX512F__ @@ -338,8 +423,145 @@ FORCEINLINE __m512d sign_p(__m512d x) { return _mm512_or_pd(ones_8d, _mm512_and_ #include "special_vectorization.hpp" +/*! + * Create specialization for array of 16 floats. + */ +#define ARRAY_T Array +#define SCALAR_T float +#define REGISTER_T __m512 +#define SIZE_TAG SizeTag::SIXTEEN() + +static const __m512 abs_mask_16s = _mm512_castsi512_ps(_mm512_set1_epi32(abs_mask_s)); +static const __m512 sign_mask_16s = _mm512_castsi512_ps(_mm512_set1_epi32(sign_mask_s)); +static const __m512 ones_16s = _mm512_set1_ps(1); + +FORCEINLINE __m512 set1_p(SizeTag::SIXTEEN, float p) { return _mm512_set1_ps(p); } +FORCEINLINE __m512 load_p(SizeTag::SIXTEEN, const float* p) { return _mm512_load_ps(p); } +FORCEINLINE __m512 loadu_p(SizeTag::SIXTEEN, const float* p) { return _mm512_loadu_ps(p); } +FORCEINLINE void store_p(float* p, __m512 x) { _mm512_store_ps(p, x); } +FORCEINLINE void storeu_p(float* p, __m512 x) { _mm512_storeu_ps(p, x); } +FORCEINLINE void stream_p(float* p, __m512 x) { _mm512_stream_ps(p, x); } + +FORCEINLINE __m512 add_p(__m512 a, __m512 b) { return _mm512_add_ps(a, b); } +FORCEINLINE __m512 sub_p(__m512 a, __m512 b) { return _mm512_sub_ps(a, b); } +FORCEINLINE __m512 mul_p(__m512 a, __m512 b) { return _mm512_mul_ps(a, b); } +FORCEINLINE __m512 div_p(__m512 a, __m512 b) { return _mm512_div_ps(a, b); } +FORCEINLINE __m512 max_p(__m512 a, __m512 b) { return _mm512_max_ps(a, b); } +FORCEINLINE __m512 min_p(__m512 a, __m512 b) { return _mm512_min_ps(a, b); } + +template +FORCEINLINE __m512 cmp_p(__m512 a, __m512 b) { + return _mm512_mask_blend_ps(_mm512_cmp_ps_mask(a, b, opCode), _mm512_setzero_ps(), ones_16s); +} +FORCEINLINE __m512 eq_p(__m512 a, __m512 b) { return cmp_p<0>(a, b); } +FORCEINLINE __m512 lt_p(__m512 a, __m512 b) { return cmp_p<1>(a, b); } +FORCEINLINE __m512 le_p(__m512 a, __m512 b) { return cmp_p<2>(a, b); } +FORCEINLINE __m512 ne_p(__m512 a, __m512 b) { return cmp_p<4>(a, b); } +FORCEINLINE __m512 ge_p(__m512 a, __m512 b) { return cmp_p<13>(a, b); } +FORCEINLINE __m512 gt_p(__m512 a, __m512 b) { return cmp_p<14>(a, b); } + +FORCEINLINE __m512 sqrt_p(__m512 x) { return _mm512_sqrt_ps(x); } +FORCEINLINE __m512 abs_p(__m512 x) { return _mm512_and_ps(x, abs_mask_16s); } +FORCEINLINE __m512 neg_p(__m512 x) { return _mm512_xor_ps(x, sign_mask_16s); } +FORCEINLINE __m512 sign_p(__m512 x) { return _mm512_or_ps(ones_16s, _mm512_and_ps(x, sign_mask_16s)); } + +#include "special_vectorization.hpp" + #endif // __AVX512F__ +#if defined(__ARM_NEON) || defined(__ARM_NEON__) +/*! + * Create specialization for array of 2 doubles. + */ +#define ARRAY_T Array +#define SCALAR_T double +#define REGISTER_T float64x2_t +#define SIZE_TAG SizeTag::TWO() + +static const uint64x2_t abs_mask_2d_u = vdupq_n_u64(abs_mask_d); +static const uint64x2_t sign_mask_2d_u = vdupq_n_u64(sign_mask_d); +static const uint64x2_t ones_2d_u = vreinterpretq_u64_f64(vdupq_n_f64(1.0)); +static const uint64x2_t ones_2u = vdupq_n_u64(~0ULL); + +FORCEINLINE float64x2_t set1_p(SizeTag::TWO, double p) { return vdupq_n_f64(p); } +FORCEINLINE float64x2_t load_p(SizeTag::TWO, const double* p) { return vld1q_f64(p); } +FORCEINLINE float64x2_t loadu_p(SizeTag::TWO, const double* p) { return vld1q_f64(p); } +FORCEINLINE void store_p(double* p, float64x2_t x) { vst1q_f64(p, x); } +FORCEINLINE void storeu_p(double* p, float64x2_t x) { vst1q_f64(p, x); } +/*--- No direct NEON equivalent to streaming stores. ---*/ +FORCEINLINE void stream_p(double* p, float64x2_t x) { vst1q_f64(p, x); } + +FORCEINLINE float64x2_t add_p(float64x2_t a, float64x2_t b) { return vaddq_f64(a, b); } +FORCEINLINE float64x2_t sub_p(float64x2_t a, float64x2_t b) { return vsubq_f64(a, b); } +FORCEINLINE float64x2_t mul_p(float64x2_t a, float64x2_t b) { return vmulq_f64(a, b); } +FORCEINLINE float64x2_t div_p(float64x2_t a, float64x2_t b) { return vdivq_f64(a, b); } +FORCEINLINE float64x2_t max_p(float64x2_t a, float64x2_t b) { return vmaxq_f64(a, b); } +FORCEINLINE float64x2_t min_p(float64x2_t a, float64x2_t b) { return vminq_f64(a, b); } + +/*--- Comparisons return uint64x2_t masks. Convert to 0.0 / 1.0. ---*/ +FORCEINLINE float64x2_t int2float(uint64x2_t a) { return vreinterpretq_f64_u64(a); } +FORCEINLINE float64x2_t cmp2float(uint64x2_t cmp) { return int2float(vandq_u64(ones_2d_u, cmp)); } +FORCEINLINE float64x2_t eq_p(float64x2_t a, float64x2_t b) { return cmp2float(vceqq_f64(a, b)); } +FORCEINLINE float64x2_t lt_p(float64x2_t a, float64x2_t b) { return cmp2float(vcltq_f64(a, b)); } +FORCEINLINE float64x2_t le_p(float64x2_t a, float64x2_t b) { return cmp2float(vcleq_f64(a, b)); } +FORCEINLINE float64x2_t ne_p(float64x2_t a, float64x2_t b) { return cmp2float(veorq_u64(ones_2u, vceqq_f64(a, b))); } +FORCEINLINE float64x2_t ge_p(float64x2_t a, float64x2_t b) { return cmp2float(vcgeq_f64(a, b)); } +FORCEINLINE float64x2_t gt_p(float64x2_t a, float64x2_t b) { return cmp2float(vcgtq_f64(a, b)); } + +FORCEINLINE float64x2_t sqrt_p(float64x2_t x) { return vsqrtq_f64(x); } +FORCEINLINE float64x2_t abs_p(float64x2_t x) { return int2float(vandq_u64(vreinterpretq_u64_f64(x), abs_mask_2d_u)); } +FORCEINLINE float64x2_t neg_p(float64x2_t x) { return int2float(veorq_u64(vreinterpretq_u64_f64(x), sign_mask_2d_u)); } +FORCEINLINE float64x2_t sign_p(float64x2_t x) { + return int2float(vorrq_u64(ones_2d_u, vandq_u64(vreinterpretq_u64_f64(x), sign_mask_2d_u))); +} + +#include "special_vectorization.hpp" + +/*! + * Create specialization for array of 4 floats. + */ +#define ARRAY_T Array +#define SCALAR_T float +#define REGISTER_T float32x4_t +#define SIZE_TAG SizeTag::FOUR() + +static const uint32x4_t abs_mask_4s_u = vdupq_n_u32(abs_mask_s); +static const uint32x4_t sign_mask_4s_u = vdupq_n_u32(sign_mask_s); +static const uint32x4_t ones_4s_u = vreinterpretq_u32_f32(vdupq_n_f32(1.0f)); + +FORCEINLINE float32x4_t set1_p(SizeTag::FOUR, float p) { return vdupq_n_f32(p); } +FORCEINLINE float32x4_t load_p(SizeTag::FOUR, const float* p) { return vld1q_f32(p); } +FORCEINLINE float32x4_t loadu_p(SizeTag::FOUR, const float* p) { return vld1q_f32(p); } +FORCEINLINE void store_p(float* p, float32x4_t x) { vst1q_f32(p, x); } +FORCEINLINE void storeu_p(float* p, float32x4_t x) { vst1q_f32(p, x); } +FORCEINLINE void stream_p(float* p, float32x4_t x) { vst1q_f32(p, x); } +FORCEINLINE float32x4_t add_p(float32x4_t a, float32x4_t b) { return vaddq_f32(a, b); } +FORCEINLINE float32x4_t sub_p(float32x4_t a, float32x4_t b) { return vsubq_f32(a, b); } +FORCEINLINE float32x4_t mul_p(float32x4_t a, float32x4_t b) { return vmulq_f32(a, b); } +FORCEINLINE float32x4_t div_p(float32x4_t a, float32x4_t b) { return vdivq_f32(a, b); } +FORCEINLINE float32x4_t max_p(float32x4_t a, float32x4_t b) { return vmaxq_f32(a, b); } +FORCEINLINE float32x4_t min_p(float32x4_t a, float32x4_t b) { return vminq_f32(a, b); } + +FORCEINLINE float32x4_t int2float(uint32x4_t a) { return vreinterpretq_f32_u32(a); } +FORCEINLINE float32x4_t cmp2float(uint32x4_t cmp) { return int2float(vandq_u32(ones_4s_u, cmp)); } +FORCEINLINE float32x4_t eq_p(float32x4_t a, float32x4_t b) { return cmp2float(vceqq_f32(a, b)); } +FORCEINLINE float32x4_t lt_p(float32x4_t a, float32x4_t b) { return cmp2float(vcltq_f32(a, b)); } +FORCEINLINE float32x4_t le_p(float32x4_t a, float32x4_t b) { return cmp2float(vcleq_f32(a, b)); } +FORCEINLINE float32x4_t ne_p(float32x4_t a, float32x4_t b) { return cmp2float(vmvnq_u32(vceqq_f32(a, b))); } +FORCEINLINE float32x4_t ge_p(float32x4_t a, float32x4_t b) { return cmp2float(vcgeq_f32(a, b)); } +FORCEINLINE float32x4_t gt_p(float32x4_t a, float32x4_t b) { return cmp2float(vcgtq_f32(a, b)); } + +FORCEINLINE float32x4_t sqrt_p(float32x4_t x) { return vsqrtq_f32(x); } +FORCEINLINE float32x4_t abs_p(float32x4_t x) { return int2float(vandq_u32(vreinterpretq_u32_f32(x), abs_mask_4s_u)); } +FORCEINLINE float32x4_t neg_p(float32x4_t x) { return int2float(veorq_u32(vreinterpretq_u32_f32(x), sign_mask_4s_u)); } +FORCEINLINE float32x4_t sign_p(float32x4_t x) { + return int2float(vorrq_u32(ones_4s_u, vandq_u32(vreinterpretq_u32_f32(x), sign_mask_4s_u))); +} + +#include "special_vectorization.hpp" + +#endif // __ARM_NEON__ + #undef ARRAY_BOILERPLATE /// @} diff --git a/Common/src/fem/fem_geometry_structure.cpp b/Common/src/fem/fem_geometry_structure.cpp index 4a98ca15cd45..ae71919060a2 100644 --- a/Common/src/fem/fem_geometry_structure.cpp +++ b/Common/src/fem/fem_geometry_structure.cpp @@ -37,8 +37,15 @@ /* Prototypes for Lapack functions, if MKL or LAPACK is used. */ #if defined(HAVE_MKL) || defined(HAVE_LAPACK) -extern "C" void dpotrf_(char*, int*, passivedouble*, int*, int*); -extern "C" void dpotri_(char*, int*, passivedouble*, int*, int*); +#ifdef USE_SINGLE_PRECISION +#define POTRF_IMPL spotrf_ +#define POTRI_IMPL spotri_ +#else +#define POTRF_IMPL dpotrf_ +#define POTRI_IMPL dpotri_ +#endif +extern "C" void POTRF_IMPL(char*, int*, passivedouble*, int*, int*); +extern "C" void POTRI_IMPL(char*, int*, passivedouble*, int*, int*); #endif bool CPointFEM::operator<(const CPointFEM& other) const { @@ -5361,7 +5368,7 @@ void CMeshFEM_DG::MetricTermsVolumeElements(CConfig* config) { than a standard inverse. */ char uplo = 'L'; int NN = nDOFs, errorCode; - dpotrf_(&uplo, &NN, massMat.data(), &NN, &errorCode); + POTRF_IMPL(&uplo, &NN, massMat.data(), &NN, &errorCode); if (errorCode != 0) { ostringstream message; if (errorCode < 0) { @@ -5376,7 +5383,7 @@ void CMeshFEM_DG::MetricTermsVolumeElements(CConfig* config) { SU2_MPI::Error(message.str(), CURRENT_FUNCTION); } - dpotri_(&uplo, &NN, massMat.data(), &NN, &errorCode); + POTRI_IMPL(&uplo, &NN, massMat.data(), &NN, &errorCode); if (errorCode != 0) { ostringstream message; if (errorCode < 0) { diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index a55c3972cfa3..d41d992d4004 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -1005,7 +1005,7 @@ void CMultiGridGeometry::SetControlVolume(const CGeometry* fine_grid, unsigned s auto iFinePoint = nodes->GetChildren_CV(iCoarsePoint, iChildren); Coarse_Volume += fine_grid->nodes->GetVolume(iFinePoint); } - nodes->SetVolume(iCoarsePoint, Coarse_Volume); + nodes->SetVolume(iCoarsePoint, max(Coarse_Volume, EPS)); } /*--- Update or not the values of faces at the edge ---*/ diff --git a/Common/src/geometry/CPhysicalGeometry.cpp b/Common/src/geometry/CPhysicalGeometry.cpp index 53720985fe26..79421eded3e0 100644 --- a/Common/src/geometry/CPhysicalGeometry.cpp +++ b/Common/src/geometry/CPhysicalGeometry.cpp @@ -7456,10 +7456,8 @@ void CPhysicalGeometry::ComputeMeshQualityStatistics(const CConfig* config) { /*--- Compute the angle between the unit normal associated with the edge and the unit vector pointing from iPoint to jPoint. ---*/ - su2double dotProduct = 0.0; - for (unsigned short iDim = 0; iDim < nDim; iDim++) { - dotProduct += (Normal[iDim] / area) * (edgeVector[iDim] / distance); - } + su2double dotProduct = GeometryToolbox::DotProduct(nDim, Normal, edgeVector.data()); + dotProduct = min(max(-1.0, dotProduct / (area * distance)), 1.0); /*--- The definition of orthogonality is an area-weighted average of 90 degrees minus the angle between the face area unit normal and diff --git a/Common/src/grid_movement/CSurfaceMovement.cpp b/Common/src/grid_movement/CSurfaceMovement.cpp index 77f5110540e9..f43e7f89c593 100644 --- a/Common/src/grid_movement/CSurfaceMovement.cpp +++ b/Common/src/grid_movement/CSurfaceMovement.cpp @@ -3902,7 +3902,7 @@ void CSurfaceMovement::SetAirfoil(CGeometry* boundary, CConfig* config) { su2double *VarCoord, *Coord, NewYCoord, NewXCoord, *Coord_i, *Coord_ip1, yp1, ypn, Airfoil_Coord[2] = {0.0, 0.0}, factor, coeff = 10000, Upper, Lower, Arch = 0.0, TotalArch = 0.0, x_i, x_ip1, y_i, y_ip1; - passivedouble AirfoilScale; + double AirfoilScale; vector Svalue, Xcoord, Ycoord, Xcoord2, Ycoord2, Xcoord_Aux, Ycoord_Aux; bool AddBegin = true, AddEnd = true; char AirfoilFile[256], AirfoilFormat[15], MeshOrientation[15], AirfoilClose[15]; diff --git a/Common/src/interface_interpolation/CRadialBasisFunction.cpp b/Common/src/interface_interpolation/CRadialBasisFunction.cpp index c1d932588f69..3b14674a7866 100644 --- a/Common/src/interface_interpolation/CRadialBasisFunction.cpp +++ b/Common/src/interface_interpolation/CRadialBasisFunction.cpp @@ -37,11 +37,15 @@ #define HAVE_LAPACK #endif #elif defined(HAVE_LAPACK) +#ifdef USE_SINGLE_PRECISION +#define GEMM_IMPL sgemm_ +#else +#define GEMM_IMPL dgemm_ +#endif // dgemm(opA, opB, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc) -extern "C" void dgemm_(const char*, const char*, const int*, const int*, const int*, const passivedouble*, - const passivedouble*, const int*, const passivedouble*, const int*, const passivedouble*, - passivedouble*, const int*); -#define DGEMM dgemm_ +extern "C" void GEMM_IMPL(const char*, const char*, const int*, const int*, const int*, const passivedouble*, + const passivedouble*, const int*, const passivedouble*, const int*, const passivedouble*, + passivedouble*, const int*); #endif CRadialBasisFunction::CRadialBasisFunction(CGeometry**** geometry_container, const CConfig* const* config, @@ -334,7 +338,7 @@ void CRadialBasisFunction::SetTransferCoeff(const CConfig* const* config) { const int M = interpMat.cols(), N = slabSize, K = funcMat.cols(); // lda = C_inv_trunc.cols() = M; ldb = funcMat.cols() = K; ldc = interpMat.cols() = M; const passivedouble alpha = 1.0, beta = 0.0; - DGEMM(&op, &op, &M, &N, &K, &alpha, C_inv_trunc[0], &M, funcMat[0], &K, &beta, interpMat[0], &M); + GEMM_IMPL(&op, &op, &M, &N, &K, &alpha, C_inv_trunc[0], &M, funcMat[0], &K, &beta, interpMat[0], &M); #else /*--- Naive product, loop order considers short-wide * nature of funcMat and interpMat. ---*/ @@ -480,7 +484,7 @@ void CRadialBasisFunction::ComputeGeneratorMatrix(RADIAL_BASIS type, bool usePol const int M = nVertexDonor, N = nVertexDonor, K = nPolynomial + 1; // lda = C_inv_top.cols() = M; ldb = Q.cols() = M; ldc = C_inv_bot.cols() = M; const passivedouble alpha = -1.0, beta = 1.0; - DGEMM(&opa, &opb, &M, &N, &K, &alpha, C_inv_top[0], &M, Q[0], &M, &beta, C_inv_bot[0], &M); + GEMM_IMPL(&opa, &opb, &M, &N, &K, &alpha, C_inv_top[0], &M, Q[0], &M, &beta, C_inv_bot[0], &M); #else // naive product for (int i = 0; i < nVertexDonor; ++i) for (int j = 0; j < nVertexDonor; ++j) diff --git a/Common/src/linear_algebra/CPastixWrapper.cpp b/Common/src/linear_algebra/CPastixWrapper.cpp index bcdf38ed9ccc..f4db581a879f 100644 --- a/Common/src/linear_algebra/CPastixWrapper.cpp +++ b/Common/src/linear_algebra/CPastixWrapper.cpp @@ -303,7 +303,7 @@ void CPastixWrapper::Factorize(CGeometry* geometry, const CConfig* c template class CPastixWrapper; #else template class CPastixWrapper; -#ifdef USE_MIXED_PRECISION +#if defined(USE_MIXED_PRECISION) && !defined(USE_SINGLE_PRECISION) template class CPastixWrapper; #endif #endif diff --git a/Common/src/linear_algebra/CSysMatrix.cpp b/Common/src/linear_algebra/CSysMatrix.cpp index f9b5d5427321..00ef51c2023a 100644 --- a/Common/src/linear_algebra/CSysMatrix.cpp +++ b/Common/src/linear_algebra/CSysMatrix.cpp @@ -525,8 +525,14 @@ void CSysMatrix::Gauss_Elimination(ScalarType* matrix, ScalarType* v #ifdef USE_MKL_LAPACK // With MKL_DIRECT_CALL enabled, this is significantly faster than native code on Intel Architectures. lapack_int ipiv[MAXNVAR]; - LAPACKE_dgetrf(LAPACK_ROW_MAJOR, nVar, nVar, matrix, nVar, ipiv); - LAPACKE_dgetrs(LAPACK_ROW_MAJOR, 'N', nVar, 1, matrix, nVar, ipiv, vec, 1); + if constexpr (std::is_same_v) { + LAPACKE_dgetrf(LAPACK_ROW_MAJOR, nVar, nVar, matrix, nVar, ipiv); + LAPACKE_dgetrs(LAPACK_ROW_MAJOR, 'N', nVar, 1, matrix, nVar, ipiv, vec, 1); + } else { + static_assert(std::is_same_v, "ScalarType not handled"); + LAPACKE_sgetrf(LAPACK_ROW_MAJOR, nVar, nVar, matrix, nVar, ipiv); + LAPACKE_sgetrs(LAPACK_ROW_MAJOR, 'N', nVar, 1, matrix, nVar, ipiv, vec, 1); + } #else #define A(I, J) matrix[(I)*nVar + (J)] @@ -577,8 +583,14 @@ void CSysMatrix::MatrixInverse(ScalarType* matrix, ScalarType* inver #ifdef USE_MKL_LAPACK // With MKL_DIRECT_CALL enabled, this is significantly faster than native code on Intel Architectures. lapack_int ipiv[MAXNVAR]; - LAPACKE_dgetrf(LAPACK_ROW_MAJOR, nVar, nVar, matrix, nVar, ipiv); - LAPACKE_dgetrs(LAPACK_ROW_MAJOR, 'N', nVar, nVar, matrix, nVar, ipiv, inverse, nVar); + if constexpr (std::is_same_v) { + LAPACKE_dgetrf(LAPACK_ROW_MAJOR, nVar, nVar, matrix, nVar, ipiv); + LAPACKE_dgetrs(LAPACK_ROW_MAJOR, 'N', nVar, nVar, matrix, nVar, ipiv, inverse, nVar); + } else { + static_assert(std::is_same_v, "ScalarType not handled"); + LAPACKE_sgetrf(LAPACK_ROW_MAJOR, nVar, nVar, matrix, nVar, ipiv); + LAPACKE_sgetrs(LAPACK_ROW_MAJOR, 'N', nVar, nVar, matrix, nVar, ipiv, inverse, nVar); + } #else #define A(I, J) matrix[(I)*nVar + (J)] @@ -1364,7 +1376,7 @@ void CSysMatrix::ComputePastixPreconditioner(const CSysVector::Solve_b(CSysMatrix& Jacobian, c /*--- Explicit instantiations ---*/ template class CSysSolve; -#ifdef USE_MIXED_PRECISION +#if defined(USE_MIXED_PRECISION) && !defined(USE_SINGLE_PRECISION) template class CSysSolve; #endif diff --git a/Common/src/linear_algebra/CSysVector.cpp b/Common/src/linear_algebra/CSysVector.cpp index 61fc3b92a4fc..f7df34633a0e 100644 --- a/Common/src/linear_algebra/CSysVector.cpp +++ b/Common/src/linear_algebra/CSysVector.cpp @@ -144,7 +144,7 @@ CSysVector::~CSysVector() { /*--- Explicit instantiations ---*/ template class CSysVector; -#ifdef USE_MIXED_PRECISION +#if defined(USE_MIXED_PRECISION) && !defined(USE_SINGLE_PRECISION) template class CSysVector; #endif #ifdef CODI_REVERSE_TYPE diff --git a/Common/src/linear_algebra/blas_structure.cpp b/Common/src/linear_algebra/blas_structure.cpp index 2bd87cb378f9..bb85ed9610ad 100644 --- a/Common/src/linear_algebra/blas_structure.cpp +++ b/Common/src/linear_algebra/blas_structure.cpp @@ -34,11 +34,19 @@ #if (defined(HAVE_MKL) || defined(HAVE_BLAS)) && !(defined(CODI_REVERSE_TYPE) || defined(CODI_FORWARD_TYPE)) /* Function prototypes for the BLAS routines used. */ -extern "C" void dgemm_(char*, char*, const int*, const int*, const int*, const passivedouble*, const passivedouble*, - const int*, const passivedouble*, const int*, const passivedouble*, passivedouble*, const int*); +#ifdef USE_SINGLE_PRECISION +#define GEMM_IMPL sgemm_ +#define GEMV_IMPL sgemv_ +#else +#define GEMM_IMPL dgemm_ +#define GEMV_IMPL dgemv_ +#endif +extern "C" void GEMM_IMPL(char*, char*, const int*, const int*, const int*, const passivedouble*, const passivedouble*, + const int*, const passivedouble*, const int*, const passivedouble*, passivedouble*, + const int*); -extern "C" void dgemv_(char*, const int*, const int*, const passivedouble*, const passivedouble*, const int*, - const passivedouble*, const int*, const passivedouble*, passivedouble*, const int*); +extern "C" void GEMV_IMPL(char*, const int*, const int*, const passivedouble*, const passivedouble*, const int*, + const passivedouble*, const int*, const passivedouble*, passivedouble*, const int*); #endif /* Constructor. Initialize the const member variables, if needed. */ @@ -77,7 +85,7 @@ void CBlasStructure::gemm(const int M, const int N, const int K, const su2double su2double beta = 0.0; char trans = 'N'; - dgemm_(&trans, &trans, &N, &M, &K, &alpha, B, &N, A, &K, &beta, C, &N); + GEMM_IMPL(&trans, &trans, &N, &M, &K, &alpha, B, &N, A, &K, &beta, C, &N); #endif @@ -100,7 +108,7 @@ void CBlasStructure::gemv(const int M, const int N, const su2double* A, const su int inc = 1; char trans = 'T'; - dgemv_(&trans, &N, &M, &alpha, A, &N, x, &inc, &beta, y, &inc); + GEMV_IMPL(&trans, &N, &M, &alpha, A, &N, x, &inc, &beta, y, &inc); #else diff --git a/Common/src/toolboxes/CSquareMatrixCM.cpp b/Common/src/toolboxes/CSquareMatrixCM.cpp index 9cbc407cac63..87f6eb356df9 100644 --- a/Common/src/toolboxes/CSquareMatrixCM.cpp +++ b/Common/src/toolboxes/CSquareMatrixCM.cpp @@ -38,11 +38,20 @@ using namespace std; #endif #elif defined(HAVE_LAPACK) /*--- Lapack / Blas routines used in CSquareMatrixCM. ---*/ -extern "C" void dgetrf_(const int*, const int*, passivedouble*, const int*, int*, int*); -extern "C" void dgetri_(const int*, passivedouble*, const int*, int*, passivedouble*, const int*, int*); -extern "C" void dgemm_(char*, char*, const int*, const int*, const int*, const passivedouble*, const passivedouble*, - const int*, const passivedouble*, const int*, const passivedouble*, passivedouble*, const int*); -#define DGEMM dgemm_ +#ifdef USE_SINGLE_PRECISION +#define GEMM_IMPL sgemm_ +#define GETRF_IMPL sgetrf_ +#define GETRI_IMPL sgetri_ +#else +#define GEMM_IMPL dgemm_ +#define GETRF_IMPL dgetrf_ +#define GETRI_IMPL dgetri_ +#endif +extern "C" void GETRF_IMPL(const int*, const int*, passivedouble*, const int*, int*, int*); +extern "C" void GETRI_IMPL(const int*, passivedouble*, const int*, int*, passivedouble*, const int*, int*); +extern "C" void GEMM_IMPL(char*, char*, const int*, const int*, const int*, const passivedouble*, const passivedouble*, + const int*, const passivedouble*, const int*, const passivedouble*, passivedouble*, + const int*); #endif void CSquareMatrixCM::Transpose() { @@ -59,10 +68,10 @@ void CSquareMatrixCM::Invert() { vector ipiv(sz); vector work(sz); - dgetrf_(&sz, &sz, mat.data(), &sz, ipiv.data(), &info); + GETRF_IMPL(&sz, &sz, mat.data(), &sz, ipiv.data(), &info); if (info != 0) SU2_MPI::Error(string("Matrix is singular"), CURRENT_FUNCTION); - dgetri_(&sz, mat.data(), &sz, ipiv.data(), work.data(), &sz, &info); + GETRI_IMPL(&sz, mat.data(), &sz, ipiv.data(), work.data(), &sz, &info); if (info != 0) SU2_MPI::Error(string("Matrix inversion failed"), CURRENT_FUNCTION); #else @@ -88,7 +97,7 @@ void CSquareMatrixCM::MatMatMult(const char side, const ColMajorMatrix(tmp); vector work(query); /*--- Factorize and invert. ---*/ - dsytrf_(&uplo, &sz, mat.data(), &sz, ipiv.data(), work.data(), &query, &info); + SYTRF_IMPL(&uplo, &sz, mat.data(), &sz, ipiv.data(), work.data(), &query, &info); if (info != 0) SU2_MPI::Error("LDLT factorization failed.", CURRENT_FUNCTION); - dsytri_(&uplo, &sz, mat.data(), &sz, ipiv.data(), work.data(), &info); + SYTRI_IMPL(&uplo, &sz, mat.data(), &sz, ipiv.data(), work.data(), &info); if (info != 0) SU2_MPI::Error("Inversion with LDLT factorization failed.", CURRENT_FUNCTION); #endif } @@ -137,9 +150,9 @@ void CSymmetricMatrix::CalcInv_potri() { const int sz = Size(); int info; - dpotrf_(&uplo, &sz, mat.data(), &sz, &info); + POTRF_IMPL(&uplo, &sz, mat.data(), &sz, &info); if (info != 0) SU2_MPI::Error("LLT factorization failed.", CURRENT_FUNCTION); - dpotri_(&uplo, &sz, mat.data(), &sz, &info); + POTRI_IMPL(&uplo, &sz, mat.data(), &sz, &info); if (info != 0) SU2_MPI::Error("Inversion with LLT factorization failed.", CURRENT_FUNCTION); #endif } @@ -167,7 +180,7 @@ void CSymmetricMatrix::MatMatMult(const char side, const su2passivematrix& mat_i /*--- Right and lower because matrices are in row major order. ---*/ const char side = 'R', uplo = 'L'; const passivedouble alpha = 1.0, beta = 0.0; - DSYMM(&side, &uplo, &N, &M, &alpha, mat.data(), &M, mat_in.data(), &N, &beta, mat_out.data(), &N); + SYMM_IMPL(&side, &uplo, &N, &M, &alpha, mat.data(), &M, mat_in.data(), &N, &beta, mat_out.data(), &N); #else // Naive product for (int i = 0; i < M; ++i) for (int j = 0; j < N; ++j) { @@ -187,7 +200,7 @@ void CSymmetricMatrix::MatMatMult(const char side, const su2passivematrix& mat_i /*--- Left and lower because matrices are in row major order. ---*/ const char side = 'L', uplo = 'L'; const passivedouble alpha = 1.0, beta = 0.0; - DSYMM(&side, &uplo, &N, &M, &alpha, mat.data(), &N, mat_in.data(), &N, &beta, mat_out.data(), &N); + SYMM_IMPL(&side, &uplo, &N, &M, &alpha, mat.data(), &N, mat_in.data(), &N, &beta, mat_out.data(), &N); #else // Naive product for (int i = 0; i < M; ++i) for (int j = 0; j < N; ++j) { diff --git a/Common/src/wall_model.cpp b/Common/src/wall_model.cpp index 60c975c43ed5..5c8ea30603cb 100644 --- a/Common/src/wall_model.cpp +++ b/Common/src/wall_model.cpp @@ -31,7 +31,12 @@ /* Prototypes for Lapack functions, if MKL or LAPACK is used. */ #if defined(HAVE_MKL) || defined(HAVE_LAPACK) -extern "C" void dgtsv_(int*, int*, passivedouble*, passivedouble*, passivedouble*, passivedouble*, int*, int*); +#ifdef USE_SINGLE_PRECISION +#define GTSV_IMPL sgtsv_ +#else +#define GTSV_IMPL dgtsv_ +#endif +extern "C" void GTSV_IMPL(int*, int*, passivedouble*, passivedouble*, passivedouble*, passivedouble*, int*, int*); #endif CWallModel::CWallModel(CConfig* config) { @@ -193,7 +198,7 @@ void CWallModel1DEQ::WallShearStressAndHeatFlux(const su2double tExchange, const #if (defined(HAVE_MKL) || defined(HAVE_LAPACK)) && !(defined(CODI_REVERSE_TYPE) || defined(CODI_FORWARD_TYPE)) int info, nrhs = 1; - dgtsv_(&numPoints, &nrhs, lower.data(), diagonal.data(), upper.data(), rhs.data(), &numPoints, &info); + GTSV_IMPL(&numPoints, &nrhs, lower.data(), diagonal.data(), upper.data(), rhs.data(), &numPoints, &info); if (info != 0) SU2_MPI::Error("Unsuccessful call to dgtsv_", CURRENT_FUNCTION); #else SU2_MPI::Error("Not compiled with MKL or LAPACK support", CURRENT_FUNCTION); @@ -273,7 +278,7 @@ void CWallModel1DEQ::WallShearStressAndHeatFlux(const su2double tExchange, const /* Solve the matrix problem to get the Enthalpy field */ #if (defined(HAVE_MKL) || defined(HAVE_LAPACK)) && !(defined(CODI_REVERSE_TYPE) || defined(CODI_FORWARD_TYPE)) - dgtsv_(&numPoints, &nrhs, lower.data(), diagonal.data(), upper.data(), rhs.data(), &numPoints, &info); + GTSV_IMPL(&numPoints, &nrhs, lower.data(), diagonal.data(), upper.data(), rhs.data(), &numPoints, &info); if (info != 0) SU2_MPI::Error("Unsuccessful call to dgtsv_", CURRENT_FUNCTION); #else SU2_MPI::Error("Not compiled with MKL or LAPACK support", CURRENT_FUNCTION); diff --git a/SU2_CFD/include/numerics_simd/flow/convection/centered.hpp b/SU2_CFD/include/numerics_simd/flow/convection/centered.hpp index 6647e46825f9..dd010650a47f 100644 --- a/SU2_CFD/include/numerics_simd/flow/convection/centered.hpp +++ b/SU2_CFD/include/numerics_simd/flow/convection/centered.hpp @@ -67,7 +67,7 @@ class CCenteredBase : public Base { * \brief Special treatment needed to fetch integer data. */ template - FORCEINLINE static Double numNeighbor(simd::Array idx, const CGeometry& geometry) { + FORCEINLINE static Double numNeighbor(const simd::Array& idx, const CGeometry& geometry) { Double n; for (size_t k=0; kGetnNeighbor(idx[k]); return n; @@ -80,12 +80,12 @@ class CCenteredBase : public Base { /*! * \brief Implementation of the base centered flux. */ - void ComputeFlux(Int iEdge, + void ComputeFlux(const Int iEdge, const CConfig& config, const CGeometry& geometry, const CVariable& solution_, - UpdateType updateType, - Double updateMask, + const UpdateType updateType, + const Double updateMask, CSysVector& vector, SparseMatrixType& matrix) const final { @@ -220,14 +220,14 @@ class CJSTScheme : public CCenteredBase,Decorator> { FORCEINLINE void finalizeFlux(VectorDbl& flux, MatrixDbl& jac_i, MatrixDbl& jac_j, - bool implicit, - Double area, - Double projVel, + const bool implicit, + const Double& area, + const Double& projVel, const PrimVarType& avgV, const CPair& V, const VectorDbl& diffU, - Int iPoint, - Int jPoint, + const Int& iPoint, + const Int& jPoint, const CGeometry& geometry, const CEulerVariable& solution, Ts&...) const { @@ -301,14 +301,14 @@ class CJSTmatScheme : public CCenteredBase,Decorator> { FORCEINLINE void finalizeFlux(VectorDbl& flux, MatrixDbl& jac_i, MatrixDbl& jac_j, - bool implicit, - Double area, - Double projVel, + const bool implicit, + const Double& area, + const Double& projVel, const PrimVarType& avgV, const CPair& V, const VectorDbl& diffU, - Int iPoint, - Int jPoint, + const Int& iPoint, + const Int& jPoint, const CGeometry& geometry, const CEulerVariable& solution, const VectorDbl& unitNormal, @@ -425,14 +425,14 @@ class CJSTkeScheme : public CCenteredBase,Decorator> { FORCEINLINE void finalizeFlux(VectorDbl& flux, MatrixDbl& jac_i, MatrixDbl& jac_j, - bool implicit, - Double area, - Double projVel, + const bool implicit, + const Double& area, + const Double& projVel, const PrimVarType& avgV, const CPair& V, const VectorDbl& diffU, - Int iPoint, - Int jPoint, + const Int& iPoint, + const Int& jPoint, const CGeometry& geometry, const CEulerVariable& solution, Ts&...) const { @@ -496,14 +496,14 @@ class CLaxScheme : public CCenteredBase,Decorator> { FORCEINLINE void finalizeFlux(VectorDbl& flux, MatrixDbl& jac_i, MatrixDbl& jac_j, - bool implicit, - Double area, - Double projVel, + const bool implicit, + const Double& area, + const Double& projVel, const PrimVarType& avgV, const CPair& V, const VectorDbl& diffU, - Int iPoint, - Int jPoint, + const Int& iPoint, + const Int& jPoint, const CGeometry& geometry, const CEulerVariable& solution, Ts&...) const { diff --git a/SU2_CFD/include/numerics_simd/flow/convection/common.hpp b/SU2_CFD/include/numerics_simd/flow/convection/common.hpp index c2203e1f9841..4796f80ce031 100644 --- a/SU2_CFD/include/numerics_simd/flow/convection/common.hpp +++ b/SU2_CFD/include/numerics_simd/flow/convection/common.hpp @@ -39,9 +39,9 @@ * \param[in] kappa - Blending parameter. * \return Blended difference for reconstruction from point i. */ -FORCEINLINE Double umusclProjection(Double gradProj, - Double delta, - Double kappa) { +FORCEINLINE Double umusclProjection(const Double& gradProj, + const Double& delta, + const Double& kappa) { /*-------------------------------------------------------------------*/ /*--- The MUSCL kappa-scheme reconstruction is typically written: ---*/ /*--- V_L = V_i + 0.25 * dV_ij^kap, where ---*/ @@ -68,10 +68,10 @@ FORCEINLINE Double umusclProjection(Double gradProj, template FORCEINLINE Double musclReconstruction(const GradType& grad, const VectorDbl& vector_ij, - const Double delta, - size_t iVar, - Double kappa, - Double umusclRamp) { + const Double& delta, + const size_t iVar, + const Double& kappa, + const Double& umusclRamp) { const Double proj = dot(grad[iVar], vector_ij); return umusclRamp * umusclProjection(proj, delta, kappa); } @@ -80,13 +80,13 @@ FORCEINLINE Double musclReconstruction(const GradType& grad, * \brief Unlimited reconstruction. */ template -FORCEINLINE void musclUnlimited(Int iPoint, - Int jPoint, +FORCEINLINE void musclUnlimited(const Int& iPoint, + const Int& jPoint, const VectorDbl& vector_ij, const Gradient_t& gradient, CPair& V, - Double kappa, - Double umusclRamp) { + const Double& kappa, + const Double& umusclRamp) { constexpr auto nVarGrad = nVarGrad_ > 0 ? nVarGrad_ : VarType::nVar; auto grad_i = gatherVariables(iPoint, gradient); @@ -110,14 +110,14 @@ FORCEINLINE void musclUnlimited(Int iPoint, * \brief Limited reconstruction with point-based limiter. */ template -FORCEINLINE void musclPointLimited(Int iPoint, - Int jPoint, +FORCEINLINE void musclPointLimited(const Int& iPoint, + const Int& jPoint, const VectorDbl& vector_ij, const Limiter_t& limiter, const Gradient_t& gradient, CPair& V, - Double kappa, - Double umusclRamp) { + const Double& kappa, + const Double& umusclRamp) { constexpr auto nVarGrad = nVarGrad_ > 0 ? nVarGrad_ : VarType::nVar; auto lim_i = gatherVariables(iPoint, limiter); @@ -144,13 +144,13 @@ FORCEINLINE void musclPointLimited(Int iPoint, * \brief Limited reconstruction with edge-based limiter. */ template -FORCEINLINE void musclEdgeLimited(Int iPoint, - Int jPoint, +FORCEINLINE void musclEdgeLimited(const Int& iPoint, + const Int& jPoint, const VectorDbl& vector_ij, const Gradient_t& gradient, CPair& V, - Double kappa, - Double umusclRamp) { + const Double& kappa, + const Double& umusclRamp) { constexpr auto nVarGrad = nVarGrad_ > 0 ? nVarGrad_ : VarType::nVar; auto grad_i = gatherVariables(iPoint, gradient); @@ -191,13 +191,14 @@ FORCEINLINE void musclEdgeLimited(Int iPoint, * \return Pair of primitive variables. */ template -FORCEINLINE CPair reconstructPrimitives(Int iEdge, Int iPoint, Int jPoint, +FORCEINLINE CPair reconstructPrimitives(const Int& iEdge, + const Int& iPoint, const Int& jPoint, const su2double& gamma, const su2double& gasConst, - bool muscl, + const bool muscl, const su2double& kappa, const su2double& umusclRamp, - LIMITER limiterType, + const LIMITER limiterType, const CPair& V1st, const VectorDbl& vector_ij, const VariableType& solution) { @@ -278,8 +279,8 @@ FORCEINLINE CPair reconstructPrimitives(Int iEdge, Int iPoint, Int * \brief Compute and return the P tensor (compressible flow, ideal gas). */ template -FORCEINLINE MatrixDbl pMatrix(Double gamma, Double density, const RandomAccessIterator& velocity, - Double projVel, Double speedSound, const VectorDbl& normal) { +FORCEINLINE MatrixDbl pMatrix(const Double& gamma, const Double& density, const RandomAccessIterator& velocity, + const Double& projVel, const Double& speedSound, const VectorDbl& normal) { MatrixDbl pMat; const Double vel2 = 0.5*squaredNorm(velocity); @@ -340,8 +341,9 @@ FORCEINLINE MatrixDbl pMatrix(Double gamma, Double density, const Random * \brief Compute and return the inverse P tensor (compressible flow, ideal gas). */ template -FORCEINLINE MatrixDbl pMatrixInv(Double gamma, Double density, const RandomAccessIterator& velocity, - Double projVel, Double speedSound, const VectorDbl& normal) { +FORCEINLINE MatrixDbl pMatrixInv(const Double& gamma, const Double& density, + const RandomAccessIterator& velocity, const Double& projVel, + const Double& speedSound, const VectorDbl& normal) { MatrixDbl pMatInv; const Double c2 = pow(speedSound,2); @@ -421,9 +423,9 @@ FORCEINLINE VectorDbl inviscidProjFlux(const PrimVarType& V, * \brief Jacobian of the convective flux (compressible flow, ideal gas). */ template -FORCEINLINE MatrixDbl inviscidProjJac(Double gamma, RandomAccessIterator velocity, - Double energy, const VectorDbl& normal, - Double scale) { +FORCEINLINE MatrixDbl inviscidProjJac(const Double& gamma, RandomAccessIterator velocity, + const Double& energy, const VectorDbl& normal, + const Double& scale) { MatrixDbl jac; Double projVel = dot(velocity, normal); @@ -459,9 +461,9 @@ FORCEINLINE MatrixDbl inviscidProjJac(Double gamma, RandomAccessIterator * \brief (Low) Dissipation coefficient for Roe schemes. */ template -FORCEINLINE Double roeDissipation(Int iPoint, - Int jPoint, - ENUM_ROELOWDISS type, +FORCEINLINE Double roeDissipation(const Int& iPoint, + const Int& jPoint, + const ENUM_ROELOWDISS type, const VariableType& solution) { if (type == NO_ROELOWDISS) { return 1.0; @@ -511,10 +513,10 @@ FORCEINLINE Double roeDissipation(Int iPoint, * \brief Correct spectral radius (avgLambda) for stretching. */ template -FORCEINLINE Double correctedSpectralRadius(Int iPoint, - Int jPoint, - Double avgLambda, - T stretchParam, +FORCEINLINE Double correctedSpectralRadius(const Int& iPoint, + const Int& jPoint, + const Double& avgLambda, + const T& stretchParam, const VariableType& solution) { const auto lambda_i = gatherVariables(iPoint, solution.GetLambda()); @@ -531,7 +533,7 @@ FORCEINLINE Double correctedSpectralRadius(Int iPoint, */ template FORCEINLINE void scalarDissipationJacobian(const VariableType& V, - Double gamma, + const Double& gamma, Double dissipConst, MatrixDbl& jac) { /*--- Diagonal entries. ---*/ diff --git a/SU2_CFD/include/numerics_simd/flow/convection/upwind.hpp b/SU2_CFD/include/numerics_simd/flow/convection/upwind.hpp index 36036d7dc5dc..ad958a7e2f02 100644 --- a/SU2_CFD/include/numerics_simd/flow/convection/upwind.hpp +++ b/SU2_CFD/include/numerics_simd/flow/convection/upwind.hpp @@ -80,12 +80,12 @@ class CUpwindBase : public Base { /*! * \brief Implementation of the base Roe flux. */ - void ComputeFlux(Int iEdge, + void ComputeFlux(const Int iEdge, const CConfig& config, const CGeometry& geometry, const CVariable& solution_, - UpdateType updateType, - Double updateMask, + const UpdateType updateType, + const Double updateMask, CSysVector& vector, SparseMatrixType& matrix) const final { @@ -191,14 +191,14 @@ class CRoeScheme : public CUpwindBase, Decorator> { FORCEINLINE void finalizeFlux(VectorDbl& flux, MatrixDbl& jac_i, MatrixDbl& jac_j, - bool implicit, - Double area, + const bool implicit, + const Double& area, const VectorDbl& unitNormal, const VectorDbl& normal, const CPair& V, const CPair& U, - Int iPoint, - Int jPoint, + const Int& iPoint, + const Int& jPoint, const CEulerVariable& solution, const CGeometry& geometry, Ts&...) const { @@ -340,14 +340,14 @@ class CMSWScheme : public CUpwindBase, Decorator> { FORCEINLINE void finalizeFlux(VectorDbl& flux, MatrixDbl& jac_i, MatrixDbl& jac_j, - bool implicit, - Double area, + const bool implicit, + const Double& area, const VectorDbl& unitNormal, const VectorDbl& normal, const CPair& V, const CPair& U, - Int iPoint, - Int jPoint, + const Int& iPoint, + const Int& jPoint, const CEulerVariable& solution, const CGeometry& geometry, Ts&...) const { diff --git a/SU2_CFD/include/numerics_simd/flow/diffusion/common.hpp b/SU2_CFD/include/numerics_simd/flow/diffusion/common.hpp index 43793d215aaa..0f4fac0d03fd 100644 --- a/SU2_CFD/include/numerics_simd/flow/diffusion/common.hpp +++ b/SU2_CFD/include/numerics_simd/flow/diffusion/common.hpp @@ -36,7 +36,7 @@ * \brief Average gradients at i/j points. */ template -FORCEINLINE MatrixDbl averageGradient(Int iPoint, Int jPoint, +FORCEINLINE MatrixDbl averageGradient(const Int& iPoint, const Int& jPoint, const GradientType& gradient) { auto avgGrad = gatherVariables(iPoint, gradient); auto grad_j = gatherVariables(jPoint, gradient); @@ -55,7 +55,7 @@ FORCEINLINE MatrixDbl averageGradient(Int iPoint, Int jPoint, template FORCEINLINE void correctGradient(const PrimitiveType& V, const VectorDbl& vector_ij, - Double dist2_ij, + const Double& dist2_ij, MatrixDbl& avgGrad) { for (size_t iVar = 0; iVar < nVar; ++iVar) { Double corr = (dot(avgGrad[iVar],vector_ij) - V.j.all(iVar) + V.i.all(iVar)) / dist2_ij; @@ -70,7 +70,7 @@ FORCEINLINE void correctGradient(const PrimitiveType& V, * \note Second viscosity term ignored. */ template -FORCEINLINE MatrixDbl stressTensor(Double viscosity, +FORCEINLINE MatrixDbl stressTensor(const Double& viscosity, const MatrixDbl& grad) { /*--- Hydrostatic term. ---*/ Double velDiv = 0.0; @@ -154,7 +154,7 @@ FORCEINLINE void addQCR(const MatrixType& grad, MatrixDbl& tau) { * wall function) magnitude in the tangential direction. */ template -FORCEINLINE void addTauWall(Int iPoint, Int jPoint, +FORCEINLINE void addTauWall(const Int& iPoint, const Int& jPoint, const Container& tauWall, const VectorDbl& unitNormal, MatrixDbl& tau) { @@ -185,7 +185,7 @@ FORCEINLINE void addTauWall(Int iPoint, Int jPoint, template FORCEINLINE MatrixDbl stressTensorJacobian(const PrimitiveType& V, const VectorDbl& normal, - Double dist_ij) { + const Double& dist_ij) { Double viscosity = V.laminarVisc() + V.eddyVisc(); Double xi = viscosity / (V.density() * dist_ij); MatrixDbl jac; diff --git a/SU2_CFD/include/numerics_simd/flow/diffusion/viscous_fluxes.hpp b/SU2_CFD/include/numerics_simd/flow/diffusion/viscous_fluxes.hpp index 22114093435f..7c3a50052be3 100644 --- a/SU2_CFD/include/numerics_simd/flow/diffusion/viscous_fluxes.hpp +++ b/SU2_CFD/include/numerics_simd/flow/diffusion/viscous_fluxes.hpp @@ -111,16 +111,16 @@ class CCompressibleViscousFluxBase : public CNumericsSIMD { * \brief Add viscous contributions to flux and jacobians. */ template - FORCEINLINE void viscousTerms(Int iEdge, - Int iPoint, - Int jPoint, + FORCEINLINE void viscousTerms(const Int& iEdge, + const Int& iPoint, + const Int& jPoint, const PrimVarType& avgV, const CPair& V, const CVariable& solution_, const VectorDbl& vector_ij, const CGeometry& geometry, const CConfig& config, - Double area, + const Double& area, const VectorDbl& unitNormal, bool implicit, VectorDbl& flux, @@ -210,9 +210,9 @@ class CCompressibleViscousFluxBase : public CNumericsSIMD { * \overload Average primitives if not provided yet. */ template - FORCEINLINE void viscousTerms(Int iEdge, - Int iPoint, - Int jPoint, + FORCEINLINE void viscousTerms(const Int& iEdge, + const Int& iPoint, + const Int& jPoint, const CPair& V, Ts&... args) const { PrimVarType avgV; @@ -228,9 +228,9 @@ class CCompressibleViscousFluxBase : public CNumericsSIMD { * \overload Compute the i-j vector if not provided yet. */ template - FORCEINLINE void viscousTerms(Int iEdge, - Int iPoint, - Int jPoint, + FORCEINLINE void viscousTerms(const Int& iEdge, + const Int& iPoint, + const Int& jPoint, const PrimVarType& avgV, const CPair& V, const CVariable& solution_, @@ -278,9 +278,9 @@ class CCompressibleViscousFlux : public CCompressibleViscousFluxBase FORCEINLINE VectorDbl energyJacobian(const PrimitiveType& V, const MatrixDbl& dtau, - Double thermalCond, - Double area, - Double dist_ij, + const Double& thermalCond, + const Double& area, + const Double& dist_ij, Ts&... args) const { Double vel2 = 0.5 * squaredNorm(V.velocity()); Double phi = (gamma-1) / V.density(); @@ -334,11 +334,11 @@ class CGeneralCompressibleViscousFlux : public CCompressibleViscousFluxBase FORCEINLINE VectorDbl energyJacobian(const PrimitiveType& V, const MatrixDbl& dtau, - Double thermalCond, - Double area, - Double dist_ij, - Int iPoint, - Int jPoint, + const Double& thermalCond, + const Double& area, + const Double& dist_ij, + const Int& iPoint, + const Int& jPoint, const VariableType& solution) const { Double vel2 = squaredNorm(V.velocity()); Double contraction = 0.0; diff --git a/SU2_CFD/include/numerics_simd/flow/variables.hpp b/SU2_CFD/include/numerics_simd/flow/variables.hpp index fa39c7337f2e..4406a06abf7c 100644 --- a/SU2_CFD/include/numerics_simd/flow/variables.hpp +++ b/SU2_CFD/include/numerics_simd/flow/variables.hpp @@ -113,7 +113,7 @@ struct CRoeVariables { * \brief Compute Roe-averaged variables from pair of primitive variables. */ template -FORCEINLINE CRoeVariables roeAveragedVariables(Double gamma, +FORCEINLINE CRoeVariables roeAveragedVariables(const Double& gamma, const CPair& V, const VectorDbl& normal) { CRoeVariables roeAvg; diff --git a/SU2_CFD/src/output/filewriter/CTecplotBinaryFileWriter.cpp b/SU2_CFD/src/output/filewriter/CTecplotBinaryFileWriter.cpp index c933df314ab0..fc34b90d2fd0 100644 --- a/SU2_CFD/src/output/filewriter/CTecplotBinaryFileWriter.cpp +++ b/SU2_CFD/src/output/filewriter/CTecplotBinaryFileWriter.cpp @@ -31,6 +31,12 @@ #endif #include +#ifdef USE_SINGLE_PRECISION +#define TEC_ZONE_WRITE_VALUES tecZoneVarWriteFloatValues +#else +#define TEC_ZONE_WRITE_VALUES tecZoneVarWriteDoubleValues +#endif + const string CTecplotBinaryFileWriter::fileExt = ".szplt"; CTecplotBinaryFileWriter::CTecplotBinaryFileWriter(CParallelDataSorter *valDataSorter, @@ -279,12 +285,12 @@ void CTecplotBinaryFileWriter::WriteData(string val_filename){ for (iVar = 0; err == 0 && iVar < fieldNames.size(); iVar++) { for(unsigned long i = 0; i < dataSorter->GetnPoints(); ++i) values_to_write[i] = dataSorter->GetData(iVar, i); - err = tecZoneVarWriteDoubleValues(file_handle, zone, iVar + 1, rank + 1, dataSorter->GetnPoints(), values_to_write.data()); + err = TEC_ZONE_WRITE_VALUES(file_handle, zone, iVar + 1, rank + 1, dataSorter->GetnPoints(), values_to_write.data()); if (err) cout << rank << ": Error outputting Tecplot variable values." << endl; for (int iRank = 0; err == 0 && iRank < size; ++iRank) { if (num_nodes_to_receive[iRank] > 0) { int var_data_offset = values_to_receive_displacements[iRank] + num_nodes_to_receive[iRank] * iVar; - err = tecZoneVarWriteDoubleValues(file_handle, zone, iVar + 1, rank + 1, static_cast(num_nodes_to_receive[iRank]), &halo_var_data[var_data_offset]); + err = TEC_ZONE_WRITE_VALUES(file_handle, zone, iVar + 1, rank + 1, static_cast(num_nodes_to_receive[iRank]), &halo_var_data[var_data_offset]); if (err) cout << rank << ": Error outputting Tecplot halo values." << endl; } } @@ -306,7 +312,7 @@ void CTecplotBinaryFileWriter::WriteData(string val_filename){ values_to_write.resize(rank_num_points); for(unsigned long i = 0; i < (unsigned long)rank_num_points; ++i) values_to_write[i] = dataSorter->GetData(iVar,i); - err = tecZoneVarWriteDoubleValues(file_handle, zone, iVar + 1, 0, rank_num_points, values_to_write.data()); + err = TEC_ZONE_WRITE_VALUES(file_handle, zone, iVar + 1, 0, rank_num_points, values_to_write.data()); if (err) cout << rank << ": Error outputting Tecplot variable values." << endl; } } @@ -314,7 +320,7 @@ void CTecplotBinaryFileWriter::WriteData(string val_filename){ var_data.resize(max((int64_t)1, (int64_t)fieldNames.size() * rank_num_points)); CBaseMPIWrapper::Recv(var_data.data(), fieldNames.size() * rank_num_points, MPI_DOUBLE, iRank, iRank, SU2_MPI::GetComm(), MPI_STATUS_IGNORE); for (iVar = 0; err == 0 && iVar < fieldNames.size(); iVar++) { - err = tecZoneVarWriteDoubleValues(file_handle, zone, iVar + 1, 0, rank_num_points, &var_data[iVar * rank_num_points]); + err = TEC_ZONE_WRITE_VALUES(file_handle, zone, iVar + 1, 0, rank_num_points, &var_data[iVar * rank_num_points]); if (err) cout << rank << ": Error outputting Tecplot surface variable values." << endl; } } @@ -350,7 +356,7 @@ void CTecplotBinaryFileWriter::WriteData(string val_filename){ for (iVar = 0; err == 0 && iVar < fieldNames.size(); iVar++) { for(unsigned long i = 0; i < dataSorter->GetnPoints(); ++i) var_data.push_back(dataSorter->GetData(iVar,i)); - err = tecZoneVarWriteDoubleValues(file_handle, zone, iVar + 1, 0, dataSorter->GetnPoints(), &var_data[iVar * dataSorter->GetnPoints()]); + err = TEC_ZONE_WRITE_VALUES(file_handle, zone, iVar + 1, 0, dataSorter->GetnPoints(), &var_data[iVar * dataSorter->GetnPoints()]); if (err) cout << rank << ": Error outputting Tecplot variable value." << endl; } diff --git a/SU2_CFD/src/solvers/CEulerSolver.cpp b/SU2_CFD/src/solvers/CEulerSolver.cpp index c58f96c3e4ef..e5737ceebb10 100644 --- a/SU2_CFD/src/solvers/CEulerSolver.cpp +++ b/SU2_CFD/src/solvers/CEulerSolver.cpp @@ -1073,10 +1073,10 @@ void CEulerSolver::SetNondimensionalization(CConfig *config, unsigned short iMes Tke_FreeStreamND = 3.0/2.0*(ModVel_FreeStreamND*ModVel_FreeStreamND*config->GetTurbulenceIntensity_FreeStream()*config->GetTurbulenceIntensity_FreeStream()); config->SetTke_FreeStreamND(Tke_FreeStreamND); - Omega_FreeStream = Density_FreeStream*Tke_FreeStream/(Viscosity_FreeStream*config->GetTurb2LamViscRatio_FreeStream()); + Omega_FreeStream = Density_FreeStream*Tke_FreeStream/max(Viscosity_FreeStream*config->GetTurb2LamViscRatio_FreeStream(), EPS); config->SetOmega_FreeStream(Omega_FreeStream); - Omega_FreeStreamND = Density_FreeStreamND*Tke_FreeStreamND/(Viscosity_FreeStreamND*config->GetTurb2LamViscRatio_FreeStream()); + Omega_FreeStreamND = Density_FreeStreamND*Tke_FreeStreamND/max(Viscosity_FreeStreamND*config->GetTurb2LamViscRatio_FreeStream(), EPS); config->SetOmega_FreeStreamND(Omega_FreeStreamND); if (config->GetTurbulenceIntensity_FreeStream() *100 <= 1.3) { diff --git a/SU2_CFD/src/solvers/CFEM_DG_EulerSolver.cpp b/SU2_CFD/src/solvers/CFEM_DG_EulerSolver.cpp index b7870f2e64d6..f5177b953534 100644 --- a/SU2_CFD/src/solvers/CFEM_DG_EulerSolver.cpp +++ b/SU2_CFD/src/solvers/CFEM_DG_EulerSolver.cpp @@ -1081,10 +1081,10 @@ void CFEM_DG_EulerSolver::SetNondimensionalization(CConfig *config, Tke_FreeStreamND = 3.0/2.0*(ModVel_FreeStreamND*ModVel_FreeStreamND*config->GetTurbulenceIntensity_FreeStream()*config->GetTurbulenceIntensity_FreeStream()); config->SetTke_FreeStreamND(Tke_FreeStreamND); - Omega_FreeStream = Density_FreeStream*Tke_FreeStream/max((Viscosity_FreeStream*config->GetTurb2LamViscRatio_FreeStream()), 1.e-25); + Omega_FreeStream = Density_FreeStream*Tke_FreeStream/max(Viscosity_FreeStream*config->GetTurb2LamViscRatio_FreeStream(), EPS); config->SetOmega_FreeStream(Omega_FreeStream); - Omega_FreeStreamND = Density_FreeStreamND*Tke_FreeStreamND/max((Viscosity_FreeStreamND*config->GetTurb2LamViscRatio_FreeStream()), 1.e-25); + Omega_FreeStreamND = Density_FreeStreamND*Tke_FreeStreamND/max(Viscosity_FreeStreamND*config->GetTurb2LamViscRatio_FreeStream(), EPS); config->SetOmega_FreeStreamND(Omega_FreeStreamND); /*--- Initialize the dimensionless Fluid Model that will be used to solve the dimensionless problem ---*/ diff --git a/SU2_CFD/src/solvers/CIncEulerSolver.cpp b/SU2_CFD/src/solvers/CIncEulerSolver.cpp index 04757eddef6a..f138422f7ea5 100644 --- a/SU2_CFD/src/solvers/CIncEulerSolver.cpp +++ b/SU2_CFD/src/solvers/CIncEulerSolver.cpp @@ -463,10 +463,10 @@ void CIncEulerSolver::SetNondimensionalization(CConfig *config, unsigned short i Tke_FreeStreamND = 3.0/2.0*(ModVel_FreeStreamND*ModVel_FreeStreamND*config->GetTurbulenceIntensity_FreeStream()*config->GetTurbulenceIntensity_FreeStream()); config->SetTke_FreeStreamND(Tke_FreeStreamND); - Omega_FreeStream = Density_FreeStream*Tke_FreeStream/(Viscosity_FreeStream*config->GetTurb2LamViscRatio_FreeStream()); + Omega_FreeStream = Density_FreeStream*Tke_FreeStream/max(Viscosity_FreeStream*config->GetTurb2LamViscRatio_FreeStream(), EPS); config->SetOmega_FreeStream(Omega_FreeStream); - Omega_FreeStreamND = Density_FreeStreamND*Tke_FreeStreamND/(Viscosity_FreeStreamND*config->GetTurb2LamViscRatio_FreeStream()); + Omega_FreeStreamND = Density_FreeStreamND*Tke_FreeStreamND/max(Viscosity_FreeStreamND*config->GetTurb2LamViscRatio_FreeStream(), EPS); config->SetOmega_FreeStreamND(Omega_FreeStreamND); const su2double MassDiffusivityND = config->GetDiffusivity_Constant() / (Velocity_Ref * Length_Ref); diff --git a/SU2_CFD/src/solvers/CNEMOEulerSolver.cpp b/SU2_CFD/src/solvers/CNEMOEulerSolver.cpp index b86a31b3ba7a..1f3090a67cce 100644 --- a/SU2_CFD/src/solvers/CNEMOEulerSolver.cpp +++ b/SU2_CFD/src/solvers/CNEMOEulerSolver.cpp @@ -1208,10 +1208,10 @@ void CNEMOEulerSolver::SetNondimensionalization(CConfig *config, unsigned short Tke_FreeStreamND = 3.0/2.0*(ModVel_FreeStreamND*ModVel_FreeStreamND*config->GetTurbulenceIntensity_FreeStream()*config->GetTurbulenceIntensity_FreeStream()); config->SetTke_FreeStreamND(Tke_FreeStreamND); - Omega_FreeStream = Density_FreeStream*Tke_FreeStream/(Viscosity_FreeStream*config->GetTurb2LamViscRatio_FreeStream()); + Omega_FreeStream = Density_FreeStream*Tke_FreeStream/max(Viscosity_FreeStream*config->GetTurb2LamViscRatio_FreeStream(), EPS); config->SetOmega_FreeStream(Omega_FreeStream); - Omega_FreeStreamND = Density_FreeStreamND*Tke_FreeStreamND/(Viscosity_FreeStreamND*config->GetTurb2LamViscRatio_FreeStream()); + Omega_FreeStreamND = Density_FreeStreamND*Tke_FreeStreamND/max(Viscosity_FreeStreamND*config->GetTurb2LamViscRatio_FreeStream(), EPS); config->SetOmega_FreeStreamND(Omega_FreeStreamND); /*--- Initialize the dimensionless Fluid Model that will be used to solve the dimensionless problem ---*/ diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index aedacdb90593..0efafa2b8800 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -89,7 +89,7 @@ CSolver::CSolver(LINEAR_SOLVER_MODE linear_solver_mode) : System(linear_solver_m Jacobian_jj = nullptr; base_nodes = nullptr; nOutputVariables = 0; - ResLinSolver = 0.0; + ResLinSolver = EPS; /*--- Variable initialization to avoid valgrid warnings when not used. ---*/ diff --git a/SU2_CFD/src/solvers/CTurbSASolver.cpp b/SU2_CFD/src/solvers/CTurbSASolver.cpp index fb8d2bf5d484..7bc05787be17 100644 --- a/SU2_CFD/src/solvers/CTurbSASolver.cpp +++ b/SU2_CFD/src/solvers/CTurbSASolver.cpp @@ -506,12 +506,11 @@ void CTurbSASolver::BC_HeatFlux_Wall(CGeometry *geometry, CSolver **solver_conta } const bool implicit = (config->GetKind_TimeIntScheme() == EULER_IMPLICIT); - bool rough_wall = false; string Marker_Tag = config->GetMarker_All_TagBound(val_marker); WALL_TYPE WallType; su2double Roughness_Height; tie(WallType, Roughness_Height) = config->GetWallRoughnessProperties(Marker_Tag); - if (WallType == WALL_TYPE::ROUGH) rough_wall = true; + Roughness_Height = max(Roughness_Height, EPS); /*--- The dirichlet condition is used only without wall function, otherwise the convergence is compromised as we are providing nu tilde values for the @@ -524,47 +523,46 @@ void CTurbSASolver::BC_HeatFlux_Wall(CGeometry *geometry, CSolver **solver_conta /*--- Check if the node belongs to the domain (i.e, not a halo node) ---*/ - if (geometry->nodes->GetDomain(iPoint)) { - if (!rough_wall) { - for (auto iVar = 0u; iVar < nVar; iVar++) - nodes->SetSolution_Old(iPoint,iVar,0.0); + if (!geometry->nodes->GetDomain(iPoint)) continue; - LinSysRes.SetBlock_Zero(iPoint); + if (WallType == WALL_TYPE::SMOOTH) { + for (auto iVar = 0u; iVar < nVar; iVar++) + nodes->SetSolution_Old(iPoint,iVar,0.0); - /*--- Includes 1 in the diagonal ---*/ + LinSysRes.SetBlock_Zero(iPoint); - if (implicit) Jacobian.DeleteValsRowi(iPoint, 0); - } else { - /*--- For rough walls, the boundary condition is given by - * (\frac{\partial \nu}{\partial n})_wall = \frac{\nu}{0.03*k_s} - * where \nu is the solution variable, $n$ is the wall normal direction - * and k_s is the equivalent sand grain roughness specified. ---*/ + /*--- Includes 1 in the diagonal ---*/ - /*--- Compute dual-grid area and boundary normal ---*/ - su2double Normal[MAXNDIM] = {0.0}; - for (auto iDim = 0u; iDim < nDim; iDim++) - Normal[iDim] = -geometry->vertex[val_marker][iVertex]->GetNormal(iDim); + if (implicit) Jacobian.DeleteValsRowi(iPoint, 0); + continue; + } - su2double Area = GeometryToolbox::Norm(nDim, Normal); + /*--- For rough walls, the boundary condition is given by + * (\frac{\partial \nu}{\partial n})_wall = \frac{\nu}{0.03*k_s} + * where \nu is the solution variable, $n$ is the wall normal direction + * and k_s is the equivalent sand grain roughness specified. ---*/ + + /*--- Compute dual-grid area and boundary normal ---*/ + su2double Normal[MAXNDIM] = {0.0}; + for (auto iDim = 0u; iDim < nDim; iDim++) + Normal[iDim] = -geometry->vertex[val_marker][iVertex]->GetNormal(iDim); - /*--- Get laminar_viscosity and density ---*/ - su2double sigma = 2.0/3.0; - su2double laminar_viscosity = solver_container[FLOW_SOL]->GetNodes()->GetLaminarViscosity(iPoint); - su2double density = solver_container[FLOW_SOL]->GetNodes()->GetDensity(iPoint); + su2double Area = GeometryToolbox::Norm(nDim, Normal); - su2double nu_total = (laminar_viscosity/density + nodes->GetSolution(iPoint,0)); + /*--- Get laminar_viscosity and density ---*/ + su2double sigma = 2.0/3.0; + su2double laminar_viscosity = solver_container[FLOW_SOL]->GetNodes()->GetLaminarViscosity(iPoint); + su2double density = solver_container[FLOW_SOL]->GetNodes()->GetDensity(iPoint); + su2double nu_lam = laminar_viscosity / density; - su2double coeff = (nu_total/sigma); - su2double RoughWallBC = nodes->GetSolution(iPoint,0)/(0.03*Roughness_Height); + su2double denom = sigma * 0.03 * Roughness_Height; + su2double coeff = (nu_lam + nodes->GetSolution(iPoint,0)) / denom; - su2double Res_Wall = coeff*RoughWallBC*Area; - LinSysRes(iPoint, 0) -= Res_Wall; + su2double Res_Wall = Area * coeff * nodes->GetSolution(iPoint,0); + LinSysRes(iPoint, 0) -= Res_Wall; - Jacobian_i[0][0] = (laminar_viscosity /density *Area)/(0.03*Roughness_Height*sigma); - Jacobian_i[0][0] += 2.0*RoughWallBC*Area/sigma; - if (implicit) Jacobian.AddVal2Diag(iPoint, 0, -Jacobian_i[0][0]); - } - } + su2double Jac_Wall = Area * (coeff + nodes->GetSolution(iPoint,0) / denom); + if (implicit) Jacobian.AddVal2Diag(iPoint, 0, -Jac_Wall); } END_SU2_OMP_FOR } diff --git a/TestCases/hybrid_regression.py b/TestCases/hybrid_regression.py index 22059c14f673..e54011937eca 100644 --- a/TestCases/hybrid_regression.py +++ b/TestCases/hybrid_regression.py @@ -136,7 +136,7 @@ def main(): poiseuille_profile.cfg_dir = "navierstokes/poiseuille" poiseuille_profile.cfg_file = "profile_poiseuille.cfg" poiseuille_profile.test_iter = 10 - poiseuille_profile.test_vals = [-12.005211, -7.539073, -0.000000, 2.089953] + poiseuille_profile.test_vals = [-12.005205, -7.539393, -0.000000, 2.089953] poiseuille_profile.test_vals_aarch64 = [-12.009012, -7.262530, -0.000000, 2.089953] test_list.append(poiseuille_profile) @@ -197,7 +197,7 @@ def main(): turb_naca0012_sa.cfg_dir = "rans/naca0012" turb_naca0012_sa.cfg_file = "turb_NACA0012_sa.cfg" turb_naca0012_sa.test_iter = 5 - turb_naca0012_sa.test_vals = [-12.038018, -16.332088, 1.080346, 0.018385, 20, -2.873140, 0, -14.250270, 0] + turb_naca0012_sa.test_vals = [-12.038028, -16.332088, 1.080346, 0.018385, 20, -2.873477, 0, -14.250270, 0] turb_naca0012_sa.test_vals_aarch64 = [-12.038091, -16.332090, 1.080346, 0.018385, 20.000000, -2.873236, 0.000000, -14.250271, 0.000000] test_list.append(turb_naca0012_sa) @@ -206,7 +206,7 @@ def main(): turb_naca0012_sst.cfg_dir = "rans/naca0012" turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" turb_naca0012_sst.test_iter = 10 - turb_naca0012_sst.test_vals = [-12.093939, -15.251079, -5.906323, 1.070413, 0.015775, -2.854890, 0] + turb_naca0012_sst.test_vals = [-12.093871, -15.251077, -5.906324, 1.070413, 0.015775, -2.855457, 0] turb_naca0012_sst.test_vals_aarch64 = [-12.075928, -15.246732, -5.861249, 1.070036, 0.015841, -2.835263, 0] test_list.append(turb_naca0012_sst) @@ -215,7 +215,7 @@ def main(): turb_naca0012_sst_sust.cfg_dir = "rans/naca0012" turb_naca0012_sst_sust.cfg_file = "turb_NACA0012_sst_sust.cfg" turb_naca0012_sst_sust.test_iter = 10 - turb_naca0012_sst_sust.test_vals = [-12.080826, -14.837176, -5.732918, 1.000893, 0.019109, -2.120233] + turb_naca0012_sst_sust.test_vals = [-12.080828, -14.837176, -5.732906, 1.000893, 0.019109, -2.120116] turb_naca0012_sst_sust.test_vals_aarch64 = [-12.073210, -14.836724, -5.732627, 1.000050, 0.019144, -2.629689] test_list.append(turb_naca0012_sst_sust) @@ -232,7 +232,7 @@ def main(): turb_naca0012_sst_expliciteuler.cfg_dir = "rans/naca0012" turb_naca0012_sst_expliciteuler.cfg_file = "turb_NACA0012_sst_expliciteuler.cfg" turb_naca0012_sst_expliciteuler.test_iter = 10 - turb_naca0012_sst_expliciteuler.test_vals = [-3.532365, -3.157224, 3.743381, 1.124798, 0.501715, -float("inf")] + turb_naca0012_sst_expliciteuler.test_vals = [-3.532365, -3.157224, 3.743381, 1.124798, 0.501715, -16.000000] test_list.append(turb_naca0012_sst_expliciteuler) # PROPELLER @@ -252,7 +252,7 @@ def main(): axi_rans_air_nozzle_restart.cfg_dir = "axisymmetric_rans/air_nozzle" axi_rans_air_nozzle_restart.cfg_file = "air_nozzle_restart.cfg" axi_rans_air_nozzle_restart.test_iter = 10 - axi_rans_air_nozzle_restart.test_vals = [-12.066224, -7.425871, -8.816101, -3.732807, 0] + axi_rans_air_nozzle_restart.test_vals = [-12.066228, -7.425901, -8.815839, -3.732622, 0] axi_rans_air_nozzle_restart.test_vals_aarch64 = [-14.140441, -9.154674, -10.886121, -5.806594, 0.000000] test_list.append(axi_rans_air_nozzle_restart) @@ -471,7 +471,7 @@ def main(): cosine_gust.cfg_dir = "gust" cosine_gust.cfg_file = "cosine_gust_zdir.cfg" cosine_gust.test_iter = 79 - cosine_gust.test_vals = [-2.418805, 0.001521, -0.001239, 0.000394, -0.000593] + cosine_gust.test_vals = [-2.418805, 0.001775, -0.001245, 0.000411, -0.000593] cosine_gust.unsteady = True cosine_gust.enabled_with_tsan = False test_list.append(cosine_gust) @@ -716,7 +716,7 @@ def main(): fsi_cht_restart.cfg_dir = "fea_fsi/stat_fsi" fsi_cht_restart.cfg_file = "config_restart.cfg" fsi_cht_restart.test_iter = 0 - fsi_cht_restart.test_vals = [5, 0.006352, -1.960362, -9.327033, -9.580521, -9.317956, 6.0838e+02, -1.2974e-02, 5.7607e-08, 20] + fsi_cht_restart.test_vals = [5, 0.006352, -1.960362, -9.327033, -9.599649, -9.318478, 608.38, -0.012974, 0, 20] fsi_cht_restart.multizone = True test_list.append(fsi_cht_restart) diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index abbc28f1ed6a..a138b9c374a7 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -491,7 +491,7 @@ def main(): turb_naca0012_sa.cfg_dir = "rans/naca0012" turb_naca0012_sa.cfg_file = "turb_NACA0012_sa.cfg" turb_naca0012_sa.test_iter = 5 - turb_naca0012_sa.test_vals = [-12.037518, -16.376951, 1.080346, 0.018385, 20, -1.564108, 20, -4.180957, 0] + turb_naca0012_sa.test_vals = [-12.037537, -16.376951, 1.080346, 0.018385, 20, -1.564109, 20, -4.180956, 0] turb_naca0012_sa.test_vals_aarch64 = [-12.037489, -16.376949, 1.080346, 0.018385, 20.000000, -1.564143, 20.000000, -4.180945, 0.000000] turb_naca0012_sa.timeout = 3200 test_list.append(turb_naca0012_sa) @@ -501,7 +501,7 @@ def main(): turb_naca0012_sst.cfg_dir = "rans/naca0012" turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" turb_naca0012_sst.test_iter = 10 - turb_naca0012_sst.test_vals = [-12.094708, -15.251093, -5.906365, 1.070413, 0.015775, -2.376072, 0] + turb_naca0012_sst.test_vals = [-12.094646, -15.251093, -5.906365, 1.070413, 0.015775, -2.376189, 0] turb_naca0012_sst.test_vals_aarch64 = [-12.075620, -15.246688, -5.861276, 1.070036, 0.015841, -1.991001, 0.000000] turb_naca0012_sst.timeout = 3200 test_list.append(turb_naca0012_sst) @@ -511,7 +511,7 @@ def main(): turb_naca0012_sst_sust.cfg_dir = "rans/naca0012" turb_naca0012_sst_sust.cfg_file = "turb_NACA0012_sst_sust.cfg" turb_naca0012_sst_sust.test_iter = 10 - turb_naca0012_sst_sust.test_vals = [-12.082012, -14.837177, -5.733436, 1.000893, 0.019109, -2.241076] + turb_naca0012_sst_sust.test_vals = [-12.082080, -14.837177, -5.733436, 1.000893, 0.019109, -2.241006] turb_naca0012_sst_sust.test_vals_aarch64 = [-12.073964, -14.836726, -5.732390, 1.000050, 0.019144, -2.229074] turb_naca0012_sst_sust.timeout = 3200 test_list.append(turb_naca0012_sst_sust) @@ -548,7 +548,7 @@ def main(): turb_naca0012_sst_expliciteuler.cfg_dir = "rans/naca0012" turb_naca0012_sst_expliciteuler.cfg_file = "turb_NACA0012_sst_expliciteuler.cfg" turb_naca0012_sst_expliciteuler.test_iter = 10 - turb_naca0012_sst_expliciteuler.test_vals = [-3.532365, -3.157224, 3.743381, 1.124798, 0.501715, -float("inf")] + turb_naca0012_sst_expliciteuler.test_vals = [-3.532365, -3.157224, 3.743381, 1.124798, 0.501715, -16.000000] turb_naca0012_sst_expliciteuler.timeout = 3200 test_list.append(turb_naca0012_sst_expliciteuler) @@ -580,7 +580,7 @@ def main(): axi_rans_air_nozzle_restart.cfg_dir = "axisymmetric_rans/air_nozzle" axi_rans_air_nozzle_restart.cfg_file = "air_nozzle_restart.cfg" axi_rans_air_nozzle_restart.test_iter = 10 - axi_rans_air_nozzle_restart.test_vals = [-12.069351, -7.507867, -8.813423, -3.732860, 0] + axi_rans_air_nozzle_restart.test_vals = [-12.069346, -7.508216, -8.813393, -3.732843, 0] axi_rans_air_nozzle_restart.test_vals_aarch64 = [-14.143310, -9.163287, -10.858232, -5.787715, 0.000000] axi_rans_air_nozzle_restart.tol = 0.0001 test_list.append(axi_rans_air_nozzle_restart) @@ -1452,7 +1452,7 @@ def main(): pywrapper_turb_naca0012_sst.cfg_dir = "rans/naca0012" pywrapper_turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" pywrapper_turb_naca0012_sst.test_iter = 10 - pywrapper_turb_naca0012_sst.test_vals = [-12.094708, -15.251093, -5.906365, 1.070413, 0.015775, -2.376072, 0] + pywrapper_turb_naca0012_sst.test_vals = [-12.094646, -15.251093, -5.906365, 1.070413, 0.015775, -2.376189, 0] pywrapper_turb_naca0012_sst.test_vals_aarch64 = [-12.075620, -15.246688, -5.861276, 1.070036, 0.015841, -1.991001, 0.000000] pywrapper_turb_naca0012_sst.command = TestCase.Command("mpirun -np 2", "SU2_CFD.py", "--parallel -f") pywrapper_turb_naca0012_sst.timeout = 3200 diff --git a/TestCases/parallel_regression_AD.py b/TestCases/parallel_regression_AD.py index af164925d953..f4fa88dc75db 100644 --- a/TestCases/parallel_regression_AD.py +++ b/TestCases/parallel_regression_AD.py @@ -231,7 +231,7 @@ def main(): discadj_trans_stator.cfg_dir = "disc_adj_turbomachinery/transonic_stator_2D" discadj_trans_stator.cfg_file = "transonic_stator.cfg" discadj_trans_stator.test_iter = 79 - discadj_trans_stator.test_vals = [79.000000, 2.599441, 2.336307, 2.140979, 0.786891] + discadj_trans_stator.test_vals = [79.000000, 2.599442, 2.336454, 2.140576, 0.786869] discadj_trans_stator.test_vals_aarch64 = [79.000000, 0.696755, 0.485950, 0.569475, -0.990065] test_list.append(discadj_trans_stator) diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index 37651f2b4f68..017aaca5bf03 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -198,7 +198,7 @@ def main(): poiseuille_profile.cfg_dir = "navierstokes/poiseuille" poiseuille_profile.cfg_file = "profile_poiseuille.cfg" poiseuille_profile.test_iter = 10 - poiseuille_profile.test_vals = [-12.004416, -7.543897, -0.000000, 2.089953] + poiseuille_profile.test_vals = [-12.004417, -7.544595, -0.000000, 2.089953] poiseuille_profile.test_vals_aarch64 = [-12.009012, -7.262299, -0.000000, 2.089953] #last 4 columns test_list.append(poiseuille_profile) @@ -283,7 +283,7 @@ def main(): turb_naca0012_sa.cfg_dir = "rans/naca0012" turb_naca0012_sa.cfg_file = "turb_NACA0012_sa.cfg" turb_naca0012_sa.test_iter = 5 - turb_naca0012_sa.test_vals = [-12.037342, -16.384159, 1.080346, 0.018385, 20, -3.455793, 20, -4.641247, 0] + turb_naca0012_sa.test_vals = [-12.037341, -16.384159, 1.080346, 0.018385, 20, -3.455927, 20, -4.641252, 0] turb_naca0012_sa.test_vals_aarch64 = [-12.037297, -16.384158, 1.080346, 0.018385, 20.000000, -3.455886, 20.000000, -4.641247, 0.000000] turb_naca0012_sa.timeout = 3200 test_list.append(turb_naca0012_sa) @@ -293,7 +293,7 @@ def main(): turb_naca0012_sst.cfg_dir = "rans/naca0012" turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" turb_naca0012_sst.test_iter = 10 - turb_naca0012_sst.test_vals = [-12.094382, -15.251082, -5.906366, 1.070413, 0.015775, -3.178797, 0] + turb_naca0012_sst.test_vals = [-12.094371, -15.251083, -5.906366, 1.070413, 0.015775, -3.178593, 0] turb_naca0012_sst.test_vals_aarch64 = [-12.076068, -15.246740, -5.861280, 1.070036, 0.015841, -3.297854, 0.000000] turb_naca0012_sst.timeout = 3200 test_list.append(turb_naca0012_sst) @@ -312,7 +312,7 @@ def main(): turb_naca0012_sst_sust_restart.cfg_dir = "rans/naca0012" turb_naca0012_sst_sust_restart.cfg_file = "turb_NACA0012_sst_sust.cfg" turb_naca0012_sst_sust_restart.test_iter = 10 - turb_naca0012_sst_sust_restart.test_vals = [-12.080556, -14.837169, -5.733461, 1.000893, 0.019109, -2.633984] + turb_naca0012_sst_sust_restart.test_vals = [-12.080495, -14.837169, -5.733461, 1.000893, 0.019109, -2.634055] turb_naca0012_sst_sust_restart.test_vals_aarch64 = [-12.074189, -14.836725, -5.732398, 1.000050, 0.019144, -3.315560] turb_naca0012_sst_sust_restart.timeout = 3200 test_list.append(turb_naca0012_sst_sust_restart) @@ -1098,7 +1098,7 @@ def main(): dyn_fsi.cfg_dir = "fea_fsi/dyn_fsi" dyn_fsi.cfg_file = "config.cfg" dyn_fsi.test_iter = 4 - dyn_fsi.test_vals = [-4.330728, -4.152820, 0, 86] + dyn_fsi.test_vals = [-4.330728, -4.152820, 5.3831e-08, 85] dyn_fsi.multizone = True dyn_fsi.unsteady = True test_list.append(dyn_fsi) @@ -1558,7 +1558,7 @@ def main(): pywrapper_turb_naca0012_sst.cfg_dir = "rans/naca0012" pywrapper_turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" pywrapper_turb_naca0012_sst.test_iter = 10 - pywrapper_turb_naca0012_sst.test_vals = [-12.094382, -15.251082, -5.906366, 1.070413, 0.015775, -3.178797, 0] + pywrapper_turb_naca0012_sst.test_vals = [-12.094371, -15.251083, -5.906366, 1.070413, 0.015775, -3.178593, 0] pywrapper_turb_naca0012_sst.test_vals_aarch64 = [-12.076068, -15.246740, -5.861280, 1.070036, 0.015841, -3.297854, 0.000000] pywrapper_turb_naca0012_sst.command = TestCase.Command(exec = "SU2_CFD.py", param = "-f") pywrapper_turb_naca0012_sst.timeout = 3200 diff --git a/TestCases/vandv.py b/TestCases/vandv.py index 424b877b216e..b78df50eeb30 100644 --- a/TestCases/vandv.py +++ b/TestCases/vandv.py @@ -63,7 +63,7 @@ def main(): flatplate_sst1994m.cfg_dir = "vandv/rans/flatplate" flatplate_sst1994m.cfg_file = "turb_flatplate_sst.cfg" flatplate_sst1994m.test_iter = 5 - flatplate_sst1994m.test_vals = [-13.040446, -10.136636, -10.945108, -7.983081, -10.323877, -4.733046, 0.002801] + flatplate_sst1994m.test_vals = [-13.040526, -10.136914, -10.942003, -7.980425, -10.323871, -4.732398, 0.002801] flatplate_sst1994m.test_vals_aarch64 = [-13.021715, -9.534786, -10.401912, -7.501836, -9.750800, -4.850665, 0.002807] test_list.append(flatplate_sst1994m) @@ -72,7 +72,7 @@ def main(): bump_sst1994m.cfg_dir = "vandv/rans/bump_in_channel" bump_sst1994m.cfg_file = "turb_bump_sst.cfg" bump_sst1994m.test_iter = 5 - bump_sst1994m.test_vals = [-11.928190, -10.095673, -9.512453, -6.445471, -11.773665, -6.998333, 0.004931] + bump_sst1994m.test_vals = [-11.927868, -10.095409, -9.512544, -6.445154, -11.773530, -6.993606, 0.004931] bump_sst1994m.test_vals_aarch64 = [-13.042689, -10.812982, -10.604523, -7.655547, -10.816257, -5.308083, 0.004911] test_list.append(bump_sst1994m) @@ -99,7 +99,7 @@ def main(): dsma661_sa.cfg_dir = "vandv/rans/dsma661" dsma661_sa.cfg_file = "dsma661_sa_config.cfg" dsma661_sa.test_iter = 5 - dsma661_sa.test_vals = [-11.266227, -8.243175, -9.037538, -5.941643, -10.737679, 0.155687, 0.024232] + dsma661_sa.test_vals = [-11.247169, -8.242321, -9.020952, -5.903807, -10.737679, 0.155687, 0.024232] dsma661_sa.test_vals_aarch64 = [-11.293183, -8.241775, -9.083761, -6.011398, -10.737680, 0.155687, 0.024232] test_list.append(dsma661_sa) @@ -108,7 +108,7 @@ def main(): dsma661_sst.cfg_dir = "vandv/rans/dsma661" dsma661_sst.cfg_file = "dsma661_sst_config.cfg" dsma661_sst.test_iter = 5 - dsma661_sst.test_vals = [-11.020377, -8.157152, -8.998182, -5.917729, -10.650155, -7.856100, 0.155882, 0.023344] + dsma661_sst.test_vals = [-11.023206, -8.157128, -8.995930, -5.936029, -10.650466, -7.864550, 0.155882, 0.023344] dsma661_sst.test_vals_aarch64 = [-10.977195, -8.403731, -8.747068, -5.808899, -10.522786, -7.369851, 0.155875, 0.023353] test_list.append(dsma661_sst) diff --git a/meson.build b/meson.build index 0620578a1636..e76354a6e863 100644 --- a/meson.build +++ b/meson.build @@ -153,9 +153,12 @@ if get_option('enable-cgns') endif # check for mixed precision floating point arithmetic -if get_option('enable-mixedprec') +if get_option('enable-mixedprec') or get_option('enable-singleprec') su2_cpp_args += '-DUSE_MIXED_PRECISION' endif +if get_option('enable-singleprec') + su2_cpp_args += '-DUSE_SINGLE_PRECISION' +endif # check if MPI dependencies are found and add them if mpi diff --git a/meson_options.txt b/meson_options.txt index 4eba410a0013..6adad19386b7 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -16,6 +16,7 @@ option('enable-pastix', type : 'boolean', value : false, description: 'enable Pa option('custom-mpi', type : 'boolean', value : false, description: 'enable MPI assuming the compiler and/or env vars give the correct include dirs and linker args.') option('enable-tests', type : 'boolean', value : false, description: 'compile Unit Tests') option('enable-mixedprec', type : 'boolean', value : false, description: 'use single precision floating point arithmetic for sparse algebra') +option('enable-singleprec', type : 'boolean', value : false, description: 'use single precision floating point arithmetic for everything') option('extra-deps', type : 'string', value : '', description: 'comma-separated list of extra (custom) dependencies to add for compilation') option('enable-mpp', type : 'boolean', value : false, description: 'enable Mutation++ support') option('install-mpp', type : 'boolean', value : false, description: 'install Mutation++ in the directory defined with --prefix') From 732eaefab1634120c7d85d15f696aaaecb1220ac Mon Sep 17 00:00:00 2001 From: Jesse Li <256257451+LwhJesse@users.noreply.github.com> Date: Wed, 27 May 2026 11:51:15 +0800 Subject: [PATCH 12/61] Use cuSPARSE BSR SpMV for CUDA matrix-vector products (#2816) Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> --- Common/src/linear_algebra/CSysMatrixGPU.cu | 128 +++++++++++++++------ meson.build | 5 +- 2 files changed, 95 insertions(+), 38 deletions(-) diff --git a/Common/src/linear_algebra/CSysMatrixGPU.cu b/Common/src/linear_algebra/CSysMatrixGPU.cu index 7e0c81ca54fd..a3f1a77ca404 100644 --- a/Common/src/linear_algebra/CSysMatrixGPU.cu +++ b/Common/src/linear_algebra/CSysMatrixGPU.cu @@ -28,32 +28,43 @@ #include "../../include/linear_algebra/CSysMatrix.hpp" #include "../../include/linear_algebra/GPUComms.cuh" -template -__global__ void GPUMatrixVectorProductAdd(matrixType* matrix, vectorType* vec, vectorType* prod, const unsigned long* d_row_ptr, const unsigned long* d_col_ind, unsigned long nPointDomain, unsigned long nVar, unsigned long nEqn) -{ - int row = (blockIdx.x * blockDim.x + threadIdx.x)/32; - int threadNo = threadIdx.x%32; - int activeThreads = nVar * (32/nVar); - - int blockRow = (threadNo/nVar)%nVar; - - if(row +#include +#include + +inline void cusparseAssert(cusparseStatus_t code, const char* file, int line, bool abort = true) { + if (code != CUSPARSE_STATUS_SUCCESS) { + fprintf(stderr, "cuSPARSEassert: %s %s %d\n", cusparseGetErrorString(code), file, line); + if (abort) exit(static_cast(code)); + } +} - for(int index = d_row_ptr[row] * nVar * nEqn + threadNo; index < d_row_ptr[row+1] * nVar * nEqn; index+=activeThreads) - { - int blockCol = index%nEqn; - int blockNo = index/(nVar * nEqn); - res += matrix[index] * vec[(d_col_ind[blockNo])*nVar + blockCol]; - } +#define cusparseErrChk(ans) \ + { \ + cusparseAssert((ans), __FILE__, __LINE__); \ + } + +inline cusparseIndexType_t GetCusparseIndexType() { + if constexpr (sizeof(unsigned long) == 4) { + return CUSPARSE_INDEX_32I; + } else if constexpr (sizeof(unsigned long) == 8) { + return CUSPARSE_INDEX_64I; + } else { + static_assert(sizeof(unsigned long) == 4 || sizeof(unsigned long) == 8, + "cuSPARSE BSR SpMV only supports 32-bit or 64-bit index arrays in this path."); + } +} - atomicAdd(&prod[row * nVar + blockRow], res); - } +template +constexpr cudaDataType GetCudaDataType() { + if constexpr (std::is_same::value) { + return CUDA_R_32F; + } else if constexpr (std::is_same::value) { + return CUDA_R_64F; + } else { + static_assert(std::is_same::value || std::is_same::value, + "cuSPARSE BSR SpMV only supports float and double in this path."); + } } template @@ -62,26 +73,69 @@ void CSysMatrix::HtDTransfer(bool trigger) const if(trigger) gpuErrChk(cudaMemcpy((void*)(d_matrix), (void*)&matrix[0], (sizeof(ScalarType)*nnz*nVar*nEqn), cudaMemcpyHostToDevice)); } -template +template void CSysMatrix::GPUMatrixVectorProduct(const CSysVector& vec, CSysVector& prod, - CGeometry* geometry, const CConfig* config) const - { + CGeometry* geometry, const CConfig* config) const { + if (nVar != nEqn) { + SU2_MPI::Error("CUDA CSysMatrix matvec with cuSPARSE BSR requires square blocks.", CURRENT_FUNCTION); + } - ScalarType* d_vec = vec.GetDevicePointer(); - ScalarType* d_prod = prod.GetDevicePointer(); + ScalarType* d_vec = vec.GetDevicePointer(); + ScalarType* d_prod = prod.GetDevicePointer(); - vec.HtDTransfer(); - prod.GPUSetVal(0.0); + vec.HtDTransfer(); - dim3 blockDim(KernelParameters::MVP_BLOCK_SIZE,1,1); - int gridx = KernelParameters::round_up_division(KernelParameters::MVP_WARP_SIZE, nPointDomain); - dim3 gridDim(gridx, 1, 1); + const auto indexType = GetCusparseIndexType(); + const auto valueType = GetCudaDataType(); - GPUMatrixVectorProductAdd<<>>(d_matrix, d_vec, d_prod, d_row_ptr, d_col_ind, nPointDomain, nVar, nEqn); - gpuErrChk( cudaPeekAtLastError() ); + const std::int64_t blockSize = static_cast(nVar); - prod.DtHTransfer(); + const std::int64_t brows = static_cast(nPointDomain); + const std::int64_t bcols = static_cast(nPoint); + const std::int64_t bnnz = static_cast(nnz); + + const std::int64_t xSize = static_cast(nPoint) * blockSize; + const std::int64_t ySize = static_cast(nPointDomain) * blockSize; + + const ScalarType alpha = 1.0; + const ScalarType beta = 0.0; + cusparseHandle_t handle = nullptr; + cusparseConstSpMatDescr_t matA = nullptr; + cusparseDnVecDescr_t vecX = nullptr; + cusparseDnVecDescr_t vecY = nullptr; + + cusparseErrChk(cusparseCreate(&handle)); + + cusparseErrChk(cusparseCreateConstBsr(&matA, brows, bcols, bnnz, blockSize, blockSize, d_row_ptr, d_col_ind, d_matrix, + indexType, indexType, CUSPARSE_INDEX_BASE_ZERO, valueType, CUSPARSE_ORDER_ROW)); + + cusparseErrChk(cusparseCreateDnVec(&vecX, xSize, d_vec, valueType)); + cusparseErrChk(cusparseCreateDnVec(&vecY, ySize, d_prod, valueType)); + + size_t bufferSize = 0; + void* dBuffer = nullptr; + + cusparseErrChk(cusparseSpMV_bufferSize(handle, CUSPARSE_OPERATION_NON_TRANSPOSE, &alpha, matA, vecX, &beta, vecY, + valueType, CUSPARSE_SPMV_BSR_ALG1, &bufferSize)); + + if (bufferSize > 0) { + gpuErrChk(cudaMalloc(&dBuffer, bufferSize)); + } + + cusparseErrChk(cusparseSpMV(handle, CUSPARSE_OPERATION_NON_TRANSPOSE, &alpha, matA, vecX, &beta, vecY, valueType, + CUSPARSE_SPMV_BSR_ALG1, dBuffer)); + + if (dBuffer != nullptr) { + gpuErrChk(cudaFree(dBuffer)); + } + + cusparseErrChk(cusparseDestroyDnVec(vecY)); + cusparseErrChk(cusparseDestroyDnVec(vecX)); + cusparseErrChk(cusparseDestroySpMat(matA)); + cusparseErrChk(cusparseDestroy(handle)); + + prod.DtHTransfer(); } template class CSysMatrix; //This is a temporary fix for invalid instantiations due to separating the member function from the header file the class is defined in. Will try to rectify it in coming commits. diff --git a/meson.build b/meson.build index e76354a6e863..d43e2b031d88 100644 --- a/meson.build +++ b/meson.build @@ -20,10 +20,13 @@ python = pymod.find_installation() if get_option('enable-cuda') add_languages('cuda') add_global_arguments('-arch=sm_86', language : 'cuda') + cuda_deps = [meson.get_compiler('cuda').find_library('cusparse', required : true)] +else + cuda_deps = [] endif su2_cpp_args = [] -su2_deps = [declare_dependency(include_directories: 'externals/CLI11')] +su2_deps = [declare_dependency(include_directories: 'externals/CLI11')] + cuda_deps default_warning_flags = [] if build_machine.system() != 'windows' From 54ccbeb5ef8111b9afcc534054d7c702ceb2472e Mon Sep 17 00:00:00 2001 From: Nijso Date: Sun, 21 Jun 2026 10:31:21 +0200 Subject: [PATCH 13/61] Fix multigrid early exit functionality (#2831) Base early exit on proper residual fix some implicit line agglomeration add more debugging info --------- Co-authored-by: Pedro Gomes Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- Common/include/CConfig.hpp | 2 + Common/include/option_structure.hpp | 3 + Common/src/CConfig.cpp | 24 +- Common/src/geometry/CMultiGridGeometry.cpp | 20 +- .../integration/CMultiGridIntegration.hpp | 106 ++- .../integration/ComputeLinSysResRMS.hpp | 63 -- SU2_CFD/src/drivers/CDriver.cpp | 21 +- .../src/integration/CMultiGridIntegration.cpp | 656 +++++++----------- SU2_CFD/src/iteration/CFluidIteration.cpp | 16 +- SU2_CFD/src/solvers/CSolver.cpp | 2 +- .../src/solvers/CSpeciesFlameletSolver.cpp | 3 +- .../naca0012/of_grad_cd_disc.dat.ref | 76 +- .../naca0012/of_grad_directdiff.dat.ref | 6 +- .../wedge/of_grad_combo.dat.ref | 10 +- TestCases/hybrid_regression.py | 52 +- TestCases/hybrid_regression_AD.py | 4 +- .../multiple_ffd/naca0012/of_grad_cd.dat.ref | 4 +- .../naca0012/of_grad_directdiff.dat.ref | 4 +- TestCases/parallel_regression.py | 97 +-- TestCases/parallel_regression_AD.py | 6 +- .../translating_NACA0012/forces_0.csv.ref | 398 +++++------ .../updated_moving_frame_NACA12/config.cfg | 2 +- .../forces_0.csv.ref | 398 +++++------ TestCases/serial_regression.py | 80 +-- TestCases/serial_regression_AD.py | 6 +- TestCases/tutorials.py | 8 +- config_template.cfg | 10 +- 27 files changed, 953 insertions(+), 1124 deletions(-) delete mode 100644 SU2_CFD/include/integration/ComputeLinSysResRMS.hpp diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index e44ad6a10a73..7d85083eafbd 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -1147,6 +1147,8 @@ class CConfig { /*--- Multigrid options ---*/ unsigned short nMG_PreSmooth_p{0}, nMG_PostSmooth_p{0}, nMG_CorrecSmooth_p{0}; unsigned short *MG_PreSmooth_p{nullptr}, *MG_PostSmooth_p{nullptr}, *MG_CorrecSmooth_p{nullptr}; + unsigned short nMG_CflScaling_p{0}; + su2double *MG_CflScaling_p{nullptr}; ENUM_STREAMWISE_PERIODIC Kind_Streamwise_Periodic; /*!< \brief Kind of Streamwise periodic flow (pressure drop or massflow) */ bool Streamwise_Periodic_Temperature; /*!< \brief Use real periodicity for Energy equation or otherwise outlet source term. */ diff --git a/Common/include/option_structure.hpp b/Common/include/option_structure.hpp index 4c521c292392..0cc823bd8792 100644 --- a/Common/include/option_structure.hpp +++ b/Common/include/option_structure.hpp @@ -1117,9 +1117,12 @@ struct CMGOptions { std::vector MG_PreSmooth; /*!< \brief Multigrid pre-smoothing iterations per level. */ std::vector MG_PostSmooth; /*!< \brief Multigrid post-smoothing iterations per level. */ std::vector MG_CorrecSmooth; /*!< \brief Multigrid Jacobi correction-smoothing per level. */ + std::vector MG_CflScaling; /*!< \brief Per-level CFL scaling factors relative to the previous (finer) level. Entry [i] scales level i+1 from level i. Size = nMGLevels. */ bool MG_Smooth_EarlyExit{false}; /*!< \brief Enable early exit for MG smoothing iterations. */ bool MG_Smooth_Output{false}; /*!< \brief Output compact per-cycle smoothing summary. */ + su2double MG_Smooth_StagnationTol{0.0}; /*!< \brief Stagnation early exit: stop if current_rms >= prev_rms * tol. 0 = disabled. */ bool MG_Implicit_Lines{false}; /*!< \brief Enable implicit-lines agglomeration from walls. */ + unsigned long MG_Implicit_Lines_MaxLength{20}; /*!< \brief Maximum nodes on a wall-normal implicit line (including wall seed). */ }; /*! diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index c168ced08302..cf137709ea0c 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -1985,16 +1985,22 @@ void CConfig::SetConfig_Options() { addDoubleOption("MG_DAMP_PROLONGATION", Damp_Correc_Prolong, 0.5); /*!\brief MG_SMOOTH_EARLY_EXIT\n DESCRIPTION: Enable early exit for MG smoothing when RMS drops below threshold. DEFAULT: NO \ingroup Config*/ addBoolOption("MG_SMOOTH_EARLY_EXIT", MGOptions.MG_Smooth_EarlyExit, true); - /*!\brief MG_SMOOTH_RES_THRESHOLD\n DESCRIPTION: Smoothing stops when current_rms < threshold * initial_rms. DEFAULT: 0.1 \ingroup Config*/ - addDoubleOption("MG_SMOOTH_RES_THRESHOLD", MGOptions.MG_Smooth_Res_Threshold, 0.5); + /*!\brief MG_SMOOTH_RES_THRESHOLD\n DESCRIPTION: Early exit smoothing when current_rms drops below threshold * initial_rms. DEFAULT: 0.9 \ingroup Config*/ + addDoubleOption("MG_SMOOTH_RES_THRESHOLD", MGOptions.MG_Smooth_Res_Threshold, 0.9); /*!\brief MG_SMOOTH_OUTPUT\n DESCRIPTION: Print compact per-cycle smoothing iteration summary. DEFAULT: NO \ingroup Config*/ addBoolOption("MG_SMOOTH_OUTPUT", MGOptions.MG_Smooth_Output, false); + /*!\brief MG_SMOOTH_STAGNATION_TOL\n DESCRIPTION: Stop smoothing if current_rms >= previous_rms * this value. Values < 1.0 enable early exit on stagnation, 1.0 only exits on defect growth. DEFAULT: 0.99 \ingroup Config*/ + addDoubleOption("MG_SMOOTH_STAGNATION_TOL", MGOptions.MG_Smooth_StagnationTol, 0.99); /*!\brief MG_SMOOTH_COEFF\n DESCRIPTION: Smoothing coefficient for the correction prolongation Jacobi smoother. DEFAULT: 1.25 \ingroup Config*/ addDoubleOption("MG_SMOOTH_COEFF", MGOptions.MG_Smooth_Coeff, 1.25); /*!\brief MG_MIN_MESHSIZE\n DESCRIPTION: Minimum number of CVs on the coarsest multigrid level. Levels that would produce fewer CVs are not created. DEFAULT: 50 \ingroup Config*/ addUnsignedLongOption("MG_MIN_MESHSIZE", MGOptions.MG_Min_MeshSize, 500); /*!\brief MG_IMPLICIT_LINES\n DESCRIPTION: Enable agglomeration along implicit lines from wall seeds. DEFAULT: NO \ingroup Config*/ addBoolOption("MG_IMPLICIT_LINES", MGOptions.MG_Implicit_Lines, false); + /*!\brief MG_IMPLICIT_LINES_MAX_LENGTH\n DESCRIPTION: Maximum number of nodes on a wall-normal implicit agglomeration line (including the wall seed node). DEFAULT: 20 \ingroup Config*/ + addUnsignedLongOption("MG_IMPLICIT_LINES_MAX_LENGTH", MGOptions.MG_Implicit_Lines_MaxLength, 20); + /*!\brief MG_CFL_SCALING\n DESCRIPTION: Per-level CFL scaling factors for coarse MG levels. Entry i is the ratio CFL(i+1)/CFL(i). If fewer values than nMGLevels are given, the last value is repeated. DEFAULT: 0.25 (i.e., 1/4 per level) \ingroup Config*/ + addDoubleListOption("MG_CFL_SCALING", nMG_CflScaling_p, MG_CflScaling_p); /*!\par CONFIG_CATEGORY: Spatial Discretization \ingroup Config*/ /*--- Options related to the spatial discretization ---*/ @@ -4789,6 +4795,16 @@ void CConfig::SetPostprocessing(SU2_COMPONENT val_software, unsigned short val_i [](unsigned short ) { return (unsigned short)0; }); fillSmooth(nMG_CorrecSmooth_p, MG_CorrecSmooth_p, MGOptions.MG_CorrecSmooth, [](unsigned short ) { return (unsigned short)0; }); + + /*--- Fill MG_CflScaling to size nMGLevels (one entry per coarse level transition). ---*/ + MGOptions.MG_CflScaling.resize(nMGLevels); + if (nMG_CflScaling_p != 0) { + for (unsigned short i = 0; i < nMGLevels; ++i) + MGOptions.MG_CflScaling[i] = (i < nMG_CflScaling_p) ? MG_CflScaling_p[i] : MG_CflScaling_p[nMG_CflScaling_p - 1]; + } else { + for (unsigned short i = 0; i < nMGLevels; ++i) + MGOptions.MG_CflScaling[i] = 0.25; + } } /*--- Override MG Smooth parameters ---*/ @@ -7534,10 +7550,12 @@ void CConfig::SetOutput(SU2_COMPONENT val_software, unsigned short val_izone) { MGTable.AddColumn("Presmooth", 10); MGTable.AddColumn("PostSmooth", 10); MGTable.AddColumn("CorrectSmooth", 10); + MGTable.AddColumn("CFL Scaling", 12); MGTable.SetAlign(PrintingToolbox::CTablePrinter::RIGHT); MGTable.PrintHeader(); for (unsigned short iLevel = 0; iLevel < nMGLevels+1; iLevel++) { - MGTable << iLevel << MGOptions.MG_PreSmooth[iLevel] << MGOptions.MG_PostSmooth[iLevel] << MGOptions.MG_CorrecSmooth[iLevel]; + const string cflStr = (iLevel == 0) ? "-" : std::to_string(MGOptions.MG_CflScaling[iLevel-1]).substr(0,6); + MGTable << iLevel << MGOptions.MG_PreSmooth[iLevel] << MGOptions.MG_PostSmooth[iLevel] << MGOptions.MG_CorrecSmooth[iLevel] << cflStr; } MGTable.PrintFooter(); } diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index d41d992d4004..347a633e2a7b 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -1280,24 +1280,28 @@ su2double CMultiGridGeometry::ComputeLocalCurvature(const CGeometry* fine_grid, void CMultiGridGeometry::AgglomerateImplicitLines(unsigned long& Index_CoarseCV, const CGeometry* fine_grid, const CConfig* config, CMultiGridQueue& MGQueue_InnerCV) { /*--- Parameters ---*/ - const su2double ANGLE_THRESHOLD_DEG = 20.0; /*!< Stop line if direction deviates more than this. */ - constexpr unsigned long MAX_LINE_LENGTH = 20; /*!< Max nodes on implicit line (including wall). */ + const su2double ANGLE_THRESHOLD_DEG = 20.0; /*!< Stop line if direction deviates more than this. */ + const unsigned long MAX_LINE_LENGTH = config->GetMGOptions().MG_Implicit_Lines_MaxLength; const su2double cos_threshold = cos(ANGLE_THRESHOLD_DEG * PI_NUMBER / 180.0); const unsigned long nPointFine = fine_grid->GetnPoint(); - /*--- Collect implicit lines starting at wall vertices. + /*--- Collect implicit lines starting at viscous (no-slip) wall vertices only. + * Seeding from non-wall boundaries (farfield, inlet, outlet, symmetry) would + * claim interior BL cells before the wall lines can reach them, leaving + * wall-seeded lines with length < 3 (discarded). Restricting to viscous walls + * ensures the boundary-layer cells are agglomerated wall-first. * Each line: [wall_node, interior_1, interior_2, ...]. * The wall node (index 0) is already agglomerated by boundary agglomeration; * only interior nodes (index >= 1) are paired into coarse CVs. ---*/ vector> lines; for (auto iMarker = 0u; iMarker < fine_grid->GetnMarker(); iMarker++) { - /*--- Skip non-physical markers ---*/ - if (config->GetMarker_All_KindBC(iMarker) == SEND_RECEIVE || - config->GetMarker_All_KindBC(iMarker) == INTERNAL_BOUNDARY || - config->GetMarker_All_KindBC(iMarker) == NEARFIELD_BOUNDARY) - continue; + /*--- Only seed lines from viscous (no-slip) wall markers. + * Non-wall boundaries (farfield, inlet, outlet, symmetry) must NOT seed + * lines because they would prematurely claim boundary-layer interior nodes. ---*/ + const auto bc = config->GetMarker_All_KindBC(iMarker); + if (bc != HEAT_FLUX && bc != ISOTHERMAL && bc != CHT_WALL_INTERFACE && bc != SMOLUCHOWSKI_MAXWELL) continue; for (auto iVertex = 0ul; iVertex < fine_grid->GetnVertex(iMarker); iVertex++) { const auto iPoint = fine_grid->vertex[iMarker][iVertex]->GetNode(); diff --git a/SU2_CFD/include/integration/CMultiGridIntegration.hpp b/SU2_CFD/include/integration/CMultiGridIntegration.hpp index 5f17525cabf5..0672d1ae5ff9 100644 --- a/SU2_CFD/include/integration/CMultiGridIntegration.hpp +++ b/SU2_CFD/include/integration/CMultiGridIntegration.hpp @@ -217,90 +217,64 @@ class CMultiGridIntegration final : public CIntegration { unsigned short RunTime_EqSystem, unsigned long Iteration, unsigned short iZone); /*! - * \brief Compute adaptive CFL for multigrid coarse levels. - * \param[in] config - Problem configuration. - * \param[in] solver_coarse - Coarse grid solver. - * \param[in] geometry_coarse - Coarse grid geometry. - * \param[in] iMesh - Current multigrid level. - * \param[in] CFL_fine - Fine grid CFL value (passive). - * \param[in] CFL_coarse_current - Current coarse grid CFL value (passive). - * \param[in] rms_res_coarse - Coarse-grid RMS residual (already MPI-reduced, from lastPreSmoothRMS). - * \return New CFL value for the coarse grid. - */ - passivedouble computeMultigridCFL(CConfig* config, unsigned short iMesh, - passivedouble CFL_fine, passivedouble CFL_coarse_current, - passivedouble rms_res_coarse); - - /*! - * \brief Adapt the residual restriction damping factor. + * \brief Adapt both restriction and prolongation damping factors from the global-trend signal. * - * Uses \c lastPreSmoothIters[] (filled by the previous multigrid cycle) to assess - * whether the pre-smoother is converging fast or slow on coarse levels, then adjusts - * \c Damp_Res_Restric in \p config accordingly. + * Uses the cross-cycle EMA ratio (crossCycleRatio = fine_d0 / EMA(fine_d0)) to detect + * long-term convergence or divergence, then adjusts both \c Damp_Res_Restric and + * \c Damp_Correc_Prolong with a single shared signal. The EMA filters per-cycle noise; + * no per-level aggregation or floor counter is needed. * - * Signal logic: - * - any coarse level ran its full configured iterations: reduce damping - * - all coarse levels exited early: increase damping - * - mixed (some full, some partial): no change - * - * \param[in,out] config - Problem configuration. + * \param[in,out] config - Problem configuration. + * \param[in] crossCycleRatio - Current fine_d0 divided by the EMA of fine_d0. */ - void adaptRestrictionDamping(CConfig* config); + void adaptDampingFactors(CConfig* config, passivedouble crossCycleRatio); /*! - * \brief Adapt the correction prolongation damping factor. - * - * Uses \c lastCorrecSmoothIters[] (filled by the previous multigrid cycle) to assess - * whether the correction smoother is struggling or converging fast, - * then adjusts \c Damp_Correc_Prolong in \p config accordingly. - * - * Signal logic: - * - any level ran its full correction-smooth iterations: reduce damping - * - all levels exited early: increase damping - * - mixed: no change - * - * \param[in,out] config - Problem configuration; \c SetDamp_Correc_Prolong is called to persist the result. + * \brief Helper function for early-exit logic during pre/post-smoothing. + * \param[in] iSmooth - Current smoothing iteration index. + * \param[in] iMesh - Index of the mesh in multigrid computations. + * \param[in] defect - Current RMS defect value. + * \param[in] mgOpts - Reference to multigrid options. + * \param[in] stag_tol - Stagnation tolerance value. + * \param[in] early_exit - Whether early exit is enabled. + * \param[out] lastRMS - Array to store RMS values [start, end]. + * \param[out] exitReason - Character for early exit reason ('T', 'S', 'A', or ' '). + * \param[out] worstStepRatio - Worst step-to-step ratio seen. + * \param[out] worstStep - Iteration number of worst step. */ - void adaptProlongationDamping(CConfig* config); + void prePostEarlyExit(unsigned short iSmooth, unsigned short iMesh, + passivedouble defect, const CMGOptions& mgOpts, + passivedouble stag_tol, bool early_exit, + passivedouble lastRMS[2], char& exitReason, + passivedouble& worstStepRatio, unsigned short& worstStep); - /*--- CFL adaptation state variables. - * These must be passivedouble: AD::Reset() clears the tape between adjoint recordings, - * but class members survive. If these were su2double their stale AD indices would - * reference the cleared tape, causing invalid memory access during the backward pass. ---*/ static constexpr int MAX_MG_LEVELS = 10; - passivedouble current_avg[MAX_MG_LEVELS] = {}; - passivedouble prev_avg[MAX_MG_LEVELS] = {}; - passivedouble last_res[MAX_MG_LEVELS] = {}; - bool last_was_increase[MAX_MG_LEVELS] = {}; - int oscillation_count[MAX_MG_LEVELS] = {}; - unsigned long last_check_iter[MAX_MG_LEVELS] = {}; - unsigned long last_update_iter[MAX_MG_LEVELS] = {}; - unsigned long last_reset_iter = std::numeric_limits::max(); /*--- Early-exit smoothing state (shared across OMP threads via master write + barrier). ---*/ - bool mg_early_exit_flag = false; /*!< \brief Shared flag for early exit across OMP threads. */ - passivedouble mg_initial_smooth_rms = 0.0; /*!< \brief Initial RMS before current smoothing phase. */ - passivedouble mg_last_smooth_rms = 0.0; /*!< \brief Last computed RMS; cached to avoid redundant Allreduce. */ + bool mg_early_exit_flag = false; /*!< \brief Shared flag for early exit across OMP threads. */ + passivedouble mg_initial_smooth_rms = 0.0; /*!< \brief Initial RMS residual before current smoothing phase (FAS). */ + passivedouble mg_prev_smooth_rms = 0.0; /*!< \brief RMS residual from previous smoothing step; used for stagnation detection. */ + passivedouble mg_fine_rms_ema = 0.0; /*!< \brief EMA of fine-grid pre-smooth RMS across cycles; cross-cycle trend signal. */ + passivedouble last_crossCycleRatio = 1.0; /*!< \brief crossCycleRatio from the most recent cycle; stored for display only. */ /*--- Actual iteration counts per MG level, filled each cycle for the compact output summary. ---*/ unsigned short lastPreSmoothIters[MAX_MG_LEVELS+1] = {}; unsigned short lastPostSmoothIters[MAX_MG_LEVELS+1] = {}; unsigned short lastCorrecSmoothIters[MAX_MG_LEVELS+1] = {}; + /*--- Early-exit reason per level: 'T'=threshold, 'S'=stagnation, ' '=ran to completion. ---*/ + char lastPreSmoothExitReason[MAX_MG_LEVELS+1] = {}; + char lastPostSmoothExitReason[MAX_MG_LEVELS+1] = {}; - /*--- Per-level residual progress flags: true if the final RMS after that phase was lower - * than the initial RMS. Used by the adaptive damping routines to distinguish - * "hit max iters but still converging" from "hit max iters and stagnated". ---*/ - bool lastPreSmoothProgress[MAX_MG_LEVELS+1] = {}; - bool lastPostSmoothProgress[MAX_MG_LEVELS+1] = {}; - bool lastCorrecSmoothProgress[MAX_MG_LEVELS+1] = {}; - - /*--- Per-level start/end RMS for the compact output summary. - * [0] = initial RMS before smoothing, [1] = final RMS after smoothing. - * Filled unconditionally (early-exit path and exhaustion path). - * Must be passivedouble: class members survive tape resets; su2double would - * carry stale AD indices referencing a cleared tape. ---*/ + /*--- Per-level start/end RMS residual for adaptive damping. ---*/ passivedouble lastPreSmoothRMS[MAX_MG_LEVELS+1][2] = {}; passivedouble lastPostSmoothRMS[MAX_MG_LEVELS+1][2] = {}; passivedouble lastCorrecSmoothRMS[MAX_MG_LEVELS+1][2] = {}; + /*--- Per-level worst step-to-step amplification seen inside a smoothing phase. + * step==0 means no intra-smoother ratio was available (fewer than 2 sweeps). ---*/ + passivedouble lastPreSmoothWorstStepRatio[MAX_MG_LEVELS+1] = {}; + passivedouble lastPostSmoothWorstStepRatio[MAX_MG_LEVELS+1] = {}; + unsigned short lastPreSmoothWorstStep[MAX_MG_LEVELS+1] = {}; + unsigned short lastPostSmoothWorstStep[MAX_MG_LEVELS+1] = {}; + }; diff --git a/SU2_CFD/include/integration/ComputeLinSysResRMS.hpp b/SU2_CFD/include/integration/ComputeLinSysResRMS.hpp deleted file mode 100644 index c773f3fe683d..000000000000 --- a/SU2_CFD/include/integration/ComputeLinSysResRMS.hpp +++ /dev/null @@ -1,63 +0,0 @@ -/*! - * \file ComputeLinSysResRMS.hpp - * \brief Helper to compute the global RMS of LinSysRes across all variables and domain points. - * \author Nijso Beishuizen - * \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 . - */ - -#pragma once -#include "../../include/solvers/CSolver.hpp" -#include "../../../Common/include/parallelization/omp_structure.hpp" -#include - -/*! - * \brief Compute the global (MPI-reduced) RMS of LinSysRes over all variables and domain points. - * - * \note Thread-safety: This function MUST be called by ALL threads in the current - * OpenMP parallel region, because squaredNorm() uses parallel for + barriers - * internally. Do NOT call from inside BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS. - * The return value is correct only on the master thread (thread 0). - * - * \param[in] solver - Solver whose LinSysRes is evaluated. - * \return Global RMS value (valid on master thread; other threads return 0). - */ -inline passivedouble ComputeLinSysResRMS(const CSolver* solver) { - - /*--- squaredNorm() -> dot() uses OMP parallel for + barriers internally, - * so all threads must participate. ---*/ - const su2double sqNorm = solver->LinSysRes.squaredNorm(); - - /*--- The MPI reduction for nElmDomain must be single-threaded. ---*/ - passivedouble result = 0.0; - BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS - { - unsigned long nElmDomain = solver->LinSysRes.GetNElmDomain(); - unsigned long globalNElmDomain = 0; - SU2_MPI::Allreduce(&nElmDomain, &globalNElmDomain, 1, MPI_UNSIGNED_LONG, MPI_SUM, SU2_MPI::GetComm()); - if (globalNElmDomain > 0) - result = std::sqrt(SU2_TYPE::GetValue(sqNorm) / static_cast(globalNElmDomain)); - } - END_SU2_OMP_SAFE_GLOBAL_ACCESS - - return result; -} diff --git a/SU2_CFD/src/drivers/CDriver.cpp b/SU2_CFD/src/drivers/CDriver.cpp index 8689f42ab9f2..63d485900987 100644 --- a/SU2_CFD/src/drivers/CDriver.cpp +++ b/SU2_CFD/src/drivers/CDriver.cpp @@ -813,6 +813,16 @@ void CDriver::InitializeGeometryFVM(CConfig *config, CGeometry **&geometry) { geometry[iMGlevel] = new CMultiGridGeometry(geometry[iMGlevel-1], config, iMGlevel); + /*--- Protect against the situation that we were not able to complete + the agglomeration for this level, i.e., there weren't enough points. + Check immediately so the expensive post-construction steps are skipped. ---*/ + + if (config->GetnMGLevels() != requestedMGlevels) { + delete geometry[iMGlevel]; + geometry[iMGlevel] = nullptr; + break; + } + /*--- Compute points surrounding points. ---*/ geometry[iMGlevel]->SetPoint_Connectivity(geometry[iMGlevel-1]); @@ -836,17 +846,6 @@ void CDriver::InitializeGeometryFVM(CConfig *config, CGeometry **&geometry) { geometry[iMGlevel]->SetMGLevel(iMGlevel); - /*--- Protect against the situation that we were not able to complete - the agglomeration for this level, i.e., there weren't enough points. - We need to check if we changed the total number of levels and delete - the incomplete CMultiGridGeometry object. ---*/ - - if (config->GetnMGLevels() != requestedMGlevels) { - delete geometry[iMGlevel]; - geometry[iMGlevel] = nullptr; - break; - } - } if (config->GetWrt_MultiGrid()) geometry[MESH_0]->ColorMGLevels(config->GetnMGLevels(), geometry); diff --git a/SU2_CFD/src/integration/CMultiGridIntegration.cpp b/SU2_CFD/src/integration/CMultiGridIntegration.cpp index cf1bcd3b1ca5..1c0b35ad53d4 100644 --- a/SU2_CFD/src/integration/CMultiGridIntegration.cpp +++ b/SU2_CFD/src/integration/CMultiGridIntegration.cpp @@ -25,214 +25,54 @@ * License along with SU2. If not, see . */ -#include "../../include/integration/ComputeLinSysResRMS.hpp" #include "../../include/integration/CMultiGridIntegration.hpp" #include "../../../Common/include/parallelization/omp_structure.hpp" #include "../../../Common/include/toolboxes/printing_toolbox.hpp" -/*!\cond PRIVATE Helper: shared logic for adapting a single MG damping factor. - * Inputs: - * performed[] - actual iteration counts per level from this cycle - * progress[] - whether residuals decreased per level - * getConfigured - returns the per-level configured maximum - * levelStart/End - level range to inspect - * getCurrent - returns the current damping factor from config - * setPersist - persists the updated factor back to config +namespace { + +/*!\cond PRIVATE + * Global-trend damping update. Uses the cross-cycle EMA ratio to detect + * long-term divergence or convergence and adjusts both damping factors + * from a single, smooth signal. + * + * crossCycleRatio < LO : SCALE_UP (residual below EMA trend) + * crossCycleRatio >= HI : SCALE_DOWN (residual above EMA trend) + * [LO, HI) : no change (neutral zone) \endcond */ -template -static void adaptMGDampingFactor(const unsigned short* performed, - const bool* progress, - GetCfg getConfigured, - unsigned short levelStart, unsigned short levelEnd, - GetCur getCurrent, SetPersist setPersist) { - int local_any_stagnant = 0; /*--- hit max iters AND residuals did not decrease: scale down. ---*/ - int local_all_early = 1; /*--- all levels exited before max iters: scale up. ---*/ - int local_inspected = 0; - - for (unsigned short lvl = levelStart; lvl <= levelEnd; ++lvl) { - const unsigned short configured = getConfigured(lvl); - if (configured == 0) continue; - ++local_inspected; - const bool hit_max = (performed[lvl] >= configured); - /*--- Scale-down signal: hit the cap AND residuals did not improve. - * Hitting the cap while still converging is not stagnation; no reduction needed. ---*/ - if (hit_max && !progress[lvl]) local_any_stagnant = 1; - /*--- Scale-up signal: requires early exit on every level. - * Making progress at max iters is good, but the damping is already doing useful work; - * do not increase it further until the smoother actually exits early. ---*/ - if (hit_max) local_all_early = 0; - } - if (local_inspected == 0) return; - - /*--- performed[] and progress[] are derived from MPI-reduced ComputeLinSysResRMS values, - * so local_any_stagnant and local_all_early are already identical on every rank. ---*/ - const su2double SCALE_DOWN = 0.99; - const su2double SCALE_UP = 1.01; - const su2double CLAMP_MIN = 0.1; - const su2double CLAMP_MAX = 0.95; - - su2double factor = getCurrent(); - if (local_any_stagnant) factor *= SCALE_DOWN; - else if (local_all_early) factor *= SCALE_UP; - /*--- else: hit max iters but still converging, or mixed — hold factor. ---*/ - factor = max(CLAMP_MIN, min(CLAMP_MAX, factor)); - setPersist(factor); +static su2double applyGlobalTrend(su2double factor, passivedouble crossCycleRatio) { + constexpr passivedouble SCALE_DOWN = 0.92; + constexpr passivedouble SCALE_UP = 1.02; + constexpr passivedouble CLAMP_MIN = 0.10; + constexpr passivedouble CLAMP_MAX = 0.90; + constexpr passivedouble LO = 0.95; // ratio below this: converging, increase damping + constexpr passivedouble HI = 1.05; // ratio above this: diverging, decrease damping + + if (crossCycleRatio >= HI) factor *= SCALE_DOWN; + else if (crossCycleRatio < LO) factor *= SCALE_UP; + return max(su2double{CLAMP_MIN}, min(su2double{CLAMP_MAX}, factor)); } -void CMultiGridIntegration::adaptRestrictionDamping(CConfig* config) { - SU2_ZONE_SCOPED - const auto& mgOpts = config->GetMGOptions(); - const unsigned short nMGLevels = config->GetnMGLevels(); - adaptMGDampingFactor( - lastPreSmoothIters, - lastPreSmoothProgress, - [&mgOpts](unsigned short lvl){ return mgOpts.MG_PreSmooth[lvl]; }, - /*levelStart=*/1, nMGLevels, - [config](){ return config->GetDamp_Res_Restric(); }, - [config](su2double v){ config->SetDamp_Res_Restric(v); }); +inline passivedouble ComputeLinSysResRMS(const CSolver* solver) { + passivedouble result = 0; + for (unsigned short iVar = 0; iVar < solver->GetnVar(); ++iVar) { + result += pow(SU2_TYPE::GetValue(solver->GetRes_RMS(iVar)), 2); + } + return sqrt(result); } -void CMultiGridIntegration::adaptProlongationDamping(CConfig* config) { - SU2_ZONE_SCOPED - /*--- Post-smoothing directly measures whether the corrected fine-grid solution - * is well-behaved after prolongation. If it exits early, increase damping. - * If it stagnates at max iters, decrease damping. ---*/ - const auto& mgOpts = config->GetMGOptions(); - const unsigned short nMGLevels = config->GetnMGLevels(); - if (nMGLevels == 0) return; - adaptMGDampingFactor( - lastPostSmoothIters, - lastPostSmoothProgress, - [&mgOpts](unsigned short lvl){ return mgOpts.MG_PostSmooth[lvl]; }, - /*levelStart=*/0, static_cast(nMGLevels - 1), - [config](){ return config->GetDamp_Correc_Prolong(); }, - [config](su2double v){ config->SetDamp_Correc_Prolong(v); }); -} +} // anonymous namespace -passivedouble CMultiGridIntegration::computeMultigridCFL(CConfig* config, unsigned short iMesh, - passivedouble CFL_fine, passivedouble CFL_coarse_current, - passivedouble rms_res_coarse) { +void CMultiGridIntegration::adaptDampingFactors(CConfig* config, passivedouble crossCycleRatio) { SU2_ZONE_SCOPED - - const bool wasActive = AD::BeginPassive(); - - passivedouble current_coeff = CFL_coarse_current / CFL_fine; - - /*--- Adaptive CFL using Exponential Moving Average (EMA) ---*/ - constexpr int AVG_WINDOW = 5; - - passivedouble CFL_coarse_new = CFL_coarse_current; // Default: keep current value - - /*--- Get global iteration count first ---*/ - unsigned long current_iter; - if (config->GetTime_Domain()) - current_iter = config->GetTimeIter(); - else - current_iter = config->GetInnerIter(); - - /*--- Reset state at the beginning of a new solve (iter 0 or 1) ---*/ - /*--- This ensures deterministic behavior across multiple runs ---*/ - if (current_iter <= 1 && last_reset_iter != current_iter) { - for (int i = 0; i < MAX_MG_LEVELS; i++) { - current_avg[i] = 0.0; - prev_avg[i] = 0.0; - last_res[i] = 0.0; - last_was_increase[i] = false; - oscillation_count[i] = 0; - last_check_iter[i] = 0; - last_update_iter[i] = 0; - } - last_reset_iter = current_iter; - } - - unsigned short lvl = min(iMesh, (unsigned short)(MAX_MG_LEVELS - 1)); - unsigned long iter = current_iter; - - /*--- rms_res_coarse is passed in (from lastPreSmoothRMS, already MPI-reduced). ---*/ - - /*--- Flip-flop detection: detect oscillating residuals (once per outer iteration) ---*/ - bool oscillation_detected = false; - if (iter != last_check_iter[lvl]) { - last_check_iter[lvl] = iter; - - if (last_res[lvl] > EPS) { - bool current_is_increase = (rms_res_coarse > last_res[lvl]); - if (current_is_increase != last_was_increase[lvl]) { - /*--- Direction changed, increment oscillation counter ---*/ - oscillation_count[lvl]++; - if (oscillation_count[lvl] >= 4) { - /*--- Detected 4 consecutive direction changes = oscillation ---*/ - oscillation_detected = true; - oscillation_count[lvl] = 0; // Reset counter after detecting - } - } else { - /*--- Same direction, reset counter ---*/ - oscillation_count[lvl] = 0; - } - last_was_increase[lvl] = current_is_increase; - } - last_res[lvl] = rms_res_coarse; - } - - /*--- Update exponential moving average ---*/ - if (current_avg[lvl] < EPS) { - current_avg[lvl] = rms_res_coarse; // Initialize with first value - } else { - current_avg[lvl] = (current_avg[lvl] * (AVG_WINDOW - 1) + rms_res_coarse) / AVG_WINDOW; - } - - /*--- Check if we should compare and adapt CFL ---*/ - passivedouble new_coeff = current_coeff; - const passivedouble MIN_REDUCTION_FACTOR = 0.98; // Require at least 2% reduction - const int UPDATE_INTERVAL = 5; // Update reference every N iterations - - /*--- Initialize prev_avg on first use ---*/ - if (prev_avg[lvl] < EPS) { - prev_avg[lvl] = current_avg[lvl]; - } - - /*--- Periodically update prev_avg to allow ratio to reflect accumulated decrease ---*/ - bool should_update = (iter - last_update_iter[lvl] >= UPDATE_INTERVAL); - - /*--- Asymmetric adaptation for robustness ---*/ - if (prev_avg[lvl] > EPS) { - passivedouble ratio = current_avg[lvl] / prev_avg[lvl]; - bool sufficient_decrease = (ratio < MIN_REDUCTION_FACTOR); - bool increasing_trend = (ratio >= 1.0); - - if (increasing_trend) { - /*--- Residual increasing: reduce CFL immediately for robustness ---*/ - new_coeff = current_coeff * 0.90; - /*--- Update reference since we're reacting immediately ---*/ - prev_avg[lvl] = current_avg[lvl]; - last_update_iter[lvl] = iter; - } else if (sufficient_decrease && should_update) { - /*--- Residual decreasing sufficiently: increase CFL ---*/ - new_coeff = current_coeff * 1.05; - /*--- Update reference only when we actually increase CFL ---*/ - prev_avg[lvl] = current_avg[lvl]; - last_update_iter[lvl] = iter; - } - } - - /*--- CFL reduction for oscillation detection ---*/ - if (oscillation_detected) { - new_coeff = current_coeff * 0.75; - /*--- Update reference after oscillation response ---*/ - prev_avg[lvl] = current_avg[lvl]; - last_update_iter[lvl] = iter; - } - - /*--- Clamp coefficient between 0.5 and 1.0 ---*/ - new_coeff = max(0.5, min(1.0, new_coeff)); - - /*--- Update coarse grid CFL ---*/ - CFL_coarse_new = max(0.5 * CFL_fine, min(CFL_fine, CFL_fine * new_coeff)); - - config->SetCFL(iMesh+1, CFL_coarse_new); - - AD::EndPassive(wasActive); - return CFL_coarse_new; + if (config->GetnMGLevels() == 0) return; + + /*--- Both factors share the same global-trend signal. The EMA already filters + * per-cycle noise; no per-level aggregation or floor counter needed. ---*/ + config->SetDamp_Res_Restric( + applyGlobalTrend(config->GetDamp_Res_Restric(), crossCycleRatio)); + config->SetDamp_Correc_Prolong( + applyGlobalTrend(config->GetDamp_Correc_Prolong(), crossCycleRatio)); } CMultiGridIntegration::CMultiGridIntegration() : CIntegration() { } @@ -279,7 +119,7 @@ void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, su2double monitor = 1.0; bool FullMG = false; - unsigned short RecursiveParam = static_cast(config[iZone]->GetMGCycle()); + auto RecursiveParam = static_cast(config[iZone]->GetMGCycle()); if (config[iZone]->GetMGCycle() == MG_CYCLE::FULL) { RecursiveParam = static_cast(MG_CYCLE::V); @@ -290,22 +130,24 @@ void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, unsigned short FinestMesh = config[iZone]->GetFinestMesh(); - /*--- Initialize per-level smoothing iteration counters to the configured maximum. - * If early exit never fires, the output will show actual == max. ---*/ + /*--- Initialize per-level smoothing diagnostics for the current cycle. ---*/ BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { const unsigned short nMGLevels = config[iZone]->GetnMGLevels(); const auto& mgOpts = config[iZone]->GetMGOptions(); for (unsigned short i = 0; i <= nMGLevels; ++i) { - lastPreSmoothIters[i] = mgOpts.MG_PreSmooth[i]; - lastPostSmoothIters[i] = mgOpts.MG_PostSmooth[i]; + lastPreSmoothIters[i] = 0; + lastPostSmoothIters[i] = 0; lastCorrecSmoothIters[i] = mgOpts.MG_CorrecSmooth[i]; - lastPreSmoothProgress[i] = false; - lastPostSmoothProgress[i] = false; - lastCorrecSmoothProgress[i] = false; + lastCorrecSmoothRMS[i][0] = lastCorrecSmoothRMS[i][1] = 0.0; lastPreSmoothRMS[i][0] = lastPreSmoothRMS[i][1] = 0.0; lastPostSmoothRMS[i][0] = lastPostSmoothRMS[i][1] = 0.0; - lastCorrecSmoothRMS[i][0] = lastCorrecSmoothRMS[i][1] = 0.0; + lastPreSmoothWorstStepRatio[i] = 0.0; + lastPostSmoothWorstStepRatio[i] = 0.0; + lastPreSmoothWorstStep[i] = 0; + lastPostSmoothWorstStep[i] = 0; + lastPreSmoothExitReason[i] = ' '; + lastPostSmoothExitReason[i] = ' '; } } END_SU2_OMP_SAFE_GLOBAL_ACCESS @@ -338,16 +180,30 @@ void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, MultiGrid_Cycle(geometry, solver_container, numerics_container, config, FinestMesh, RecursiveParam, RunTime_EqSystem, iZone, iInst); - /*--- Adapt coarse-grid CFL once per cycle using smoothing residuals gathered during the cycle. - * lastPreSmoothRMS[iMesh+1][1] is the final RMS after pre-smoothing at the coarse level ---*/ + /*--- Adapt coarse-grid CFL once per cycle using smoothing residuals gathered during the cycle. ---*/ const unsigned short nMGLevels = config[iZone]->GetnMGLevels(); BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { + /*--- Use the current finest-grid CFL as the base for deterministic + * coarse-level scaling. Fall back to config scalar when local CFL + * adaptation is disabled. ---*/ + passivedouble cfl_base = SU2_TYPE::GetValue( + solver_container[iZone][iInst][FinestMesh][Solver_Position]->GetAvg_CFL_Local()); + if (cfl_base < EPS) + cfl_base = SU2_TYPE::GetValue(config[iZone]->GetCFL(FinestMesh)); + + const auto& cflScaling = config[iZone]->GetMGOptions().MG_CflScaling; + + passivedouble CFL_local = cfl_base; for (unsigned short iMesh = FinestMesh; iMesh < nMGLevels; ++iMesh) { - const passivedouble CFL_fine_p = SU2_TYPE::GetValue(config[iZone]->GetCFL(iMesh)); - const passivedouble CFL_coarse_p = SU2_TYPE::GetValue(config[iZone]->GetCFL(iMesh+1)); - computeMultigridCFL(config[iZone], iMesh, CFL_fine_p, CFL_coarse_p, - lastPreSmoothRMS[iMesh+1][1]); + const unsigned short lvl = iMesh + 1; + /*--- Use per-level scaling factor; clamp to (0,1] to prevent coarse CFL from + * exceeding the fine CFL. Index into cflScaling is iMesh (0-based transition). ---*/ + const passivedouble scale = (iMesh < cflScaling.size()) + ? max(passivedouble{1e-6}, min(passivedouble{1.0}, SU2_TYPE::GetValue(cflScaling[iMesh]))) + : passivedouble{0.25}; + CFL_local *= scale; + config[iZone]->SetCFL(lvl, CFL_local); } } END_SU2_OMP_SAFE_GLOBAL_ACCESS @@ -381,10 +237,20 @@ void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, * and the signal would always point to "scale down"). ---*/ const auto& mgOptsZone = config[iZone]->GetMGOptions(); if (mgOptsZone.MG_Smooth_EarlyExit) { - BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS - { - adaptRestrictionDamping(config[iZone]); - adaptProlongationDamping(config[iZone]); + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { + constexpr passivedouble EMA_ALPHA = 0.02; + const passivedouble fine_d0 = lastPreSmoothRMS[FinestMesh][0]; + const bool ema_ready = (mg_fine_rms_ema >= EPS); + if (!ema_ready) + mg_fine_rms_ema = fine_d0; + else + mg_fine_rms_ema = (1.0 - EMA_ALPHA) * mg_fine_rms_ema + EMA_ALPHA * fine_d0; + const passivedouble crossCycleRatio = (mg_fine_rms_ema > EPS) ? fine_d0 / mg_fine_rms_ema : 1.0; + + /*--- Adapt both damping factors from the same global-trend signal. + * Skip on the first cycle while the EMA is still being seeded. ---*/ + if (ema_ready) adaptDampingFactors(config[iZone], crossCycleRatio); + last_crossCycleRatio = crossCycleRatio; } END_SU2_OMP_SAFE_GLOBAL_ACCESS } @@ -392,52 +258,81 @@ void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, /*--- Print compact smoothing summary when MG_SMOOTH_OUTPUT= YES. ---*/ if (mgOptsZone.MG_Smooth_Output) { BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS - { - if (SU2_MPI::GetRank() == MASTER_NODE) { - - /*--- Helper: format one cell as "act/max [init->final]". ---*/ - auto cellStr = [](unsigned short act, unsigned short mx, - su2double rms0, su2double rms1) -> std::string { - std::ostringstream ss; - ss << act << "/" << mx << " [" - << std::scientific << std::setprecision(2) - << rms0 << "->" << rms1 << "]"; - return ss.str(); - }; - - PrintingToolbox::CTablePrinter table(&std::cout); - table.AddColumn("Smoother", 13); - for (unsigned short i = 0; i <= nMGLevels; ++i) - table.AddColumn("Level " + std::to_string(i), 26); - table.PrintHeader(); - - /*--- Pre-smooth: defined on all levels 0..nMGLevels. ---*/ - table << "Pre-smooth"; - for (unsigned short i = 0; i <= nMGLevels; ++i) - table << cellStr(lastPreSmoothIters[i], mgOptsZone.MG_PreSmooth[i], - lastPreSmoothRMS[i][0], lastPreSmoothRMS[i][1]); - - /*--- Post-smooth: defined on levels 0..nMGLevels-1; coarsest has none. ---*/ - table << "Post-smooth"; - for (unsigned short i = 0; i < nMGLevels; ++i) - table << cellStr(lastPostSmoothIters[i], mgOptsZone.MG_PostSmooth[i], - lastPostSmoothRMS[i][0], lastPostSmoothRMS[i][1]); - table << "-"; - - /*--- Corr.-smooth: defined on levels 0..nMGLevels-1; coarsest has none. ---*/ - table << "Corr-smooth"; - for (unsigned short i = 0; i < nMGLevels; ++i) - table << cellStr(lastCorrecSmoothIters[i], mgOptsZone.MG_CorrecSmooth[i], - lastCorrecSmoothRMS[i][0], lastCorrecSmoothRMS[i][1]); - table << "-"; - - table.PrintFooter(); - - cout << std::fixed << std::setprecision(4) - << "Damping [restrict | prolong] : " << config[iZone]->GetDamp_Res_Restric() - << " | " << config[iZone]->GetDamp_Correc_Prolong() << "\n" - << std::defaultfloat << std::setprecision(6); + if (SU2_MPI::GetRank() == MASTER_NODE) { + + /*--- Helper: format one cell as "act/max [init->final]". ---*/ + auto cellStr = [](unsigned short act, unsigned short mx, char reason, + su2double d0, su2double d1, + passivedouble worstStepRatio, + unsigned short worstStep) -> std::string { + /*--- Show: steps taken / max + exit reason + initial defect scale + d1/d0 ratio. + * r=d1/d0 < 1 means smoother reduced the defect (good). + * r > 1 means smoother grew the defect. + * Exit reason: T=threshold, S=clean stagnation, A=amplifying stagnation, + * ' '=ran to completion. ---*/ + std::ostringstream ss; + ss << act << "/" << mx; + if (act < mx) ss << reason; /*--- only tag early exits ---*/ + ss << " [" << std::scientific << std::setprecision(2) << d0 << "]"; + if (d0 > 0.0) { + ss << std::fixed << std::setprecision(3) << " r=" << d1 / d0; + } + if (worstStep > 0) { + ss << " rw" << worstStep << "=" << std::fixed << std::setprecision(3) << worstStepRatio; + } + return ss.str(); + }; + + PrintingToolbox::CTablePrinter table(&std::cout); + table.AddColumn("Smoother", 13); + for (unsigned short i = 0; i <= nMGLevels; ++i) + table.AddColumn("Level " + std::to_string(i), 38); + table.PrintHeader(); + + /*--- Pre-smooth: defect [d0->d1] — what early exit and damping adaptation read. ---*/ + table << "Pre-smooth"; + for (unsigned short i = 0; i <= nMGLevels; ++i) + table << cellStr(lastPreSmoothIters[i], mgOptsZone.MG_PreSmooth[i], + lastPreSmoothExitReason[i], + lastPreSmoothRMS[i][0], lastPreSmoothRMS[i][1], + lastPreSmoothWorstStepRatio[i], lastPreSmoothWorstStep[i]); + + /*--- Post-smooth: defect [d0->d1] — what early exit and prolongation-damping read. ---*/ + table << "Post-smooth"; + for (unsigned short i = 0; i < nMGLevels; ++i) + table << cellStr(lastPostSmoothIters[i], mgOptsZone.MG_PostSmooth[i], + lastPostSmoothExitReason[i], + lastPostSmoothRMS[i][0], lastPostSmoothRMS[i][1], + lastPostSmoothWorstStepRatio[i], lastPostSmoothWorstStep[i]); + table << "-"; + + /*--- Corr.-smooth: defined on levels 0..nMGLevels-1; coarsest has none. ---*/ + table << "Corr-smooth"; + for (unsigned short i = 0; i < nMGLevels; ++i) + table << cellStr(lastCorrecSmoothIters[i], mgOptsZone.MG_CorrecSmooth[i], + ' ', lastCorrecSmoothRMS[i][0], lastCorrecSmoothRMS[i][1], + 0.0, 0); + table << "-"; + + table << "CFL"; + for (unsigned short i = 0; i <= nMGLevels; ++i) { + std::ostringstream ss; + ss << std::fixed << std::setprecision(4); + if (i == MESH_0) { + ss << SU2_TYPE::GetValue(solver_container[iZone][iInst][MESH_0][Solver_Position]->GetAvg_CFL_Local()); + } else { + ss << SU2_TYPE::GetValue(config[iZone]->GetCFL(i)); + } + table << ss.str(); } + + table.PrintFooter(); + + cout << std::fixed << std::setprecision(4) + << "Damping [restrict | prolong] : " << config[iZone]->GetDamp_Res_Restric() + << " | " << config[iZone]->GetDamp_Correc_Prolong() + << " cross-cycle: " << std::setprecision(3) << last_crossCycleRatio << "\n" + << std::defaultfloat << std::setprecision(6); } END_SU2_OMP_SAFE_GLOBAL_ACCESS } @@ -502,6 +397,12 @@ void CMultiGridIntegration::MultiGrid_Cycle(CGeometry ****geometry, Space_Integration(geometry_fine, solver_container_fine, numerics_fine, config, iMesh, NO_RK_ITER, RunTime_EqSystem); + /*--- LinSysRes = R(u_N) here, before tau is added by SetResidual_Term. ---*/ + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { + lastPreSmoothRMS[iMesh][1] = ComputeLinSysResRMS(solver_fine); + } + END_SU2_OMP_SAFE_GLOBAL_ACCESS + SetResidual_Term(geometry_fine, solver_fine); /*--- Compute $r_(k+1) = F_(k+1)(I^(k+1)_k u_k)$ ---*/ @@ -553,6 +454,38 @@ void CMultiGridIntegration::MultiGrid_Cycle(CGeometry ****geometry, } +void CMultiGridIntegration::prePostEarlyExit(unsigned short iSmooth, unsigned short iMesh, + passivedouble defect, const CMGOptions& mgOpts, + passivedouble stag_tol, bool early_exit, + passivedouble lastRMS[2], char& exitReason, + passivedouble& worstStepRatio, unsigned short& worstStep) { + if (iSmooth == 0) { + mg_initial_smooth_rms = defect; + lastRMS[0] = defect; + } else { + if (mg_prev_smooth_rms > EPS) { + const passivedouble step_ratio = defect / mg_prev_smooth_rms; + if (step_ratio > worstStepRatio) { + worstStepRatio = step_ratio; + worstStep = iSmooth + 1; + } + } + + if (early_exit) { + if (defect < mgOpts.MG_Smooth_Res_Threshold * mg_initial_smooth_rms) { + exitReason = 'T'; + mg_early_exit_flag = true; + } else if (defect >= mg_prev_smooth_rms * stag_tol) { + /*--- 'A' = amplifying-stagnation (defect grew vs previous step). + * 'S' = clean stagnation (defect is not improving but also not growing). ---*/ + exitReason = (defect > mg_prev_smooth_rms) ? 'A' : 'S'; + mg_early_exit_flag = true; + } + } + } + mg_prev_smooth_rms = defect; +} + void CMultiGridIntegration::PreSmoothing(unsigned short RunTime_EqSystem, CGeometry**** geometry, CSolver***** solver_container, @@ -572,6 +505,11 @@ void CMultiGridIntegration::PreSmoothing(unsigned short RunTime_EqSystem, const unsigned short nPreSmooth = mgOpts.MG_PreSmooth[iMesh]; const unsigned long timeIter = config->GetTimeIter(); const bool early_exit = mgOpts.MG_Smooth_EarlyExit && (nPreSmooth > 1); + const bool need_per_step_rms = early_exit || mgOpts.MG_Smooth_Output; + /*--- Also capture initial RMS at MESH_0 for the cross-cycle EMA controller, even with nPreSmooth==1. ---*/ + const bool need_initial_rms = need_per_step_rms || (iMesh == MESH_0 && mgOpts.MG_Smooth_EarlyExit); + const passivedouble stag_tol = (mgOpts.MG_Smooth_StagnationTol > 0.0) + ? SU2_TYPE::GetValue(mgOpts.MG_Smooth_StagnationTol) : passivedouble(1.0); /*--- Reset the shared early-exit flag (master only). ---*/ SU2_OMP_SAFE_GLOBAL_ACCESS(mg_early_exit_flag = false;) @@ -596,59 +534,39 @@ void CMultiGridIntegration::PreSmoothing(unsigned short RunTime_EqSystem, /*--- Space integration ---*/ Space_Integration(geometry_fine, solver_container_fine, numerics_fine, config, iMesh, iRKStep, RunTime_EqSystem); - /*--- Capture initial RMS after the very first residual evaluation. - * This is the earliest point where LinSysRes = R(u_current) (not stale). - * ComputeLinSysResRMS must be called by all threads (uses parallel dot). ---*/ - if (iPreSmooth == 0 && iRKStep == 0) { - const passivedouble initial_rms = ComputeLinSysResRMS(solver_fine); - BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS - { - lastPreSmoothRMS[iMesh][0] = initial_rms; - if (early_exit) mg_initial_smooth_rms = initial_rms; + /*--- Time integration, update solution using the old solution plus the solution increment ---*/ + Time_Integration(geometry_fine, solver_container_fine, config, iRKStep, RunTime_EqSystem); + + /*--- At iRKStep==0 LinSysRes = R(u_k). ---*/ + if (iRKStep == 0 && need_initial_rms) { + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { + const passivedouble defect = ComputeLinSysResRMS(solver_fine); + prePostEarlyExit(iPreSmooth, iMesh, defect, mgOpts, stag_tol, early_exit, + lastPreSmoothRMS[iMesh], lastPreSmoothExitReason[iMesh], + lastPreSmoothWorstStepRatio[iMesh], lastPreSmoothWorstStep[iMesh]); } END_SU2_OMP_SAFE_GLOBAL_ACCESS + if (mg_early_exit_flag) break; } - /*--- Time integration, update solution using the old solution plus the solution increment ---*/ - Time_Integration(geometry_fine, solver_container_fine, config, iRKStep, RunTime_EqSystem); /*--- Send-Receive boundary conditions, and postprocessing ---*/ solver_fine->Postprocessing(geometry_fine, solver_container_fine, config, iMesh); } - - /*--- Early exit: check if RMS has dropped sufficiently. - * ComputeLinSysResRMS must be called by all threads. - * only master uses the result inside the safe block. ---*/ - if (early_exit) { - const passivedouble current_rms = ComputeLinSysResRMS(solver_fine); - BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS - { - mg_last_smooth_rms = current_rms; - if (mg_last_smooth_rms < mgOpts.MG_Smooth_Res_Threshold * mg_initial_smooth_rms) { - lastPreSmoothIters[iMesh] = iPreSmooth + 1; - mg_early_exit_flag = true; - } - } - END_SU2_OMP_SAFE_GLOBAL_ACCESS - if (mg_early_exit_flag) break; - } + SU2_OMP_SAFE_GLOBAL_ACCESS(lastPreSmoothIters[iMesh] = iPreSmooth + 1;) + if (mg_early_exit_flag) break; } - /*--- Record final RMS and progress flag. - * In the early-exit path mg_last_smooth_rms already holds the current value; - * in the normal path we compute it once here. - * The condition is the same for all threads so they all agree on whether to call. ---*/ - passivedouble final_pre_rms = mg_last_smooth_rms; - if (!(early_exit && mg_early_exit_flag)) - final_pre_rms = ComputeLinSysResRMS(solver_fine); - BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS - { - mg_last_smooth_rms = final_pre_rms; - lastPreSmoothRMS[iMesh][1] = final_pre_rms; - lastPreSmoothProgress[iMesh] = mg_early_exit_flag || - (final_pre_rms < lastPreSmoothRMS[iMesh][0]); + /*--- Record d_{N-1} as the final pre-smooth defect (the last value captured inside the loop). + * For non-coarsest levels MultiGrid_Cycle overwrites this with the exact d_N at zero + * additional cost in the restriction block (Space_Integration already runs there). + * Skip when nPreSmooth==0: lastPreSmoothDefect[iMesh] stays {0,0} (initialized). ---*/ + if (nPreSmooth > 0) { + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { + lastPreSmoothRMS[iMesh][1] = mg_prev_smooth_rms; + } + END_SU2_OMP_SAFE_GLOBAL_ACCESS } - END_SU2_OMP_SAFE_GLOBAL_ACCESS } @@ -667,6 +585,9 @@ void CMultiGridIntegration::PostSmoothing(unsigned short RunTime_EqSystem, const unsigned short nPostSmooth = mgOpts.MG_PostSmooth[iMesh]; const unsigned long timeIter = config->GetTimeIter(); const bool early_exit = mgOpts.MG_Smooth_EarlyExit && (nPostSmooth > 1); + const bool need_per_step_rms = early_exit || mgOpts.MG_Smooth_Output; + const passivedouble stag_tol = (mgOpts.MG_Smooth_StagnationTol > 0.0) + ? SU2_TYPE::GetValue(mgOpts.MG_Smooth_StagnationTol) : passivedouble(1.0); /*--- Reset the shared early-exit flag (master only). ---*/ SU2_OMP_SAFE_GLOBAL_ACCESS(mg_early_exit_flag = false;) @@ -688,77 +609,54 @@ void CMultiGridIntegration::PostSmoothing(unsigned short RunTime_EqSystem, /*--- Space integration ---*/ Space_Integration(geometry_fine, solver_container_fine, numerics_fine, config, iMesh, iRKStep, RunTime_EqSystem); - /*--- Capture initial RMS after the very first residual evaluation. - * Before this point, LinSysRes held the smoothed correction (from SmoothProlongated_Correction), - * NOT the spatial residual R(u). This is the first valid R(u) after applying the correction. - * ComputeLinSysResRMS must be called by all threads (uses parallel dot). ---*/ - if (iPostSmooth == 0 && iRKStep == 0) { - const passivedouble initial_rms = ComputeLinSysResRMS(solver_fine); - BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS - { - lastPostSmoothRMS[iMesh][0] = initial_rms; - if (early_exit) mg_initial_smooth_rms = initial_rms; + /*--- Time integration, update solution using the old solution plus the solution increment ---*/ + Time_Integration(geometry_fine, solver_container_fine, config, iRKStep, RunTime_EqSystem); + + /*--- At iRKStep==0 LinSysRes = R(u_k) ---*/ + if (iRKStep == 0 && need_per_step_rms) { + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { + const passivedouble defect = ComputeLinSysResRMS(solver_fine); + prePostEarlyExit(iPostSmooth, iMesh, defect, mgOpts, stag_tol, early_exit, + lastPostSmoothRMS[iMesh], lastPostSmoothExitReason[iMesh], + lastPostSmoothWorstStepRatio[iMesh], lastPostSmoothWorstStep[iMesh]); } END_SU2_OMP_SAFE_GLOBAL_ACCESS + if (mg_early_exit_flag) break; } - /*--- Time integration, update solution using the old solution plus the solution increment ---*/ - Time_Integration(geometry_fine, solver_container_fine, config, iRKStep, RunTime_EqSystem); /*--- Send-Receive boundary conditions, and postprocessing ---*/ solver_fine->Postprocessing(geometry_fine, solver_container_fine, config, iMesh); } - - /*--- Early exit: check if RMS has dropped sufficiently. - * ComputeLinSysResRMS must be called by all threads (uses parallel dot). ---*/ - if (early_exit) { - const passivedouble current_rms = ComputeLinSysResRMS(solver_fine); - BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS - { - mg_last_smooth_rms = current_rms; - if (mg_last_smooth_rms < mgOpts.MG_Smooth_Res_Threshold * mg_initial_smooth_rms) { - lastPostSmoothIters[iMesh] = iPostSmooth + 1; - mg_early_exit_flag = true; - } - } - END_SU2_OMP_SAFE_GLOBAL_ACCESS - if (mg_early_exit_flag) break; - } + SU2_OMP_SAFE_GLOBAL_ACCESS(lastPostSmoothIters[iMesh] = iPostSmooth + 1;) + if (mg_early_exit_flag) break; } - /*--- Record final RMS after post-smoothing. - * In the early-exit path mg_last_smooth_rms is already current; otherwise compute once. - * The condition is the same for all threads so they all agree on whether to call. ---*/ - passivedouble final_post_rms = mg_last_smooth_rms; - if (!(early_exit && mg_early_exit_flag)) - final_post_rms = ComputeLinSysResRMS(solver_fine); - BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS - { - mg_last_smooth_rms = final_post_rms; - lastPostSmoothRMS[iMesh][1] = final_post_rms; - lastPostSmoothProgress[iMesh] = mg_early_exit_flag || - (final_post_rms < lastPostSmoothRMS[iMesh][0]); + /*--- Record d_{N-1} as the final post-smooth defect (display only). + * Skip when nPostSmooth==0: lastPostSmoothRMS[iMesh] stays {0,0} (initialized). ---*/ + if (nPostSmooth > 0) { + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { + lastPostSmoothRMS[iMesh][1] = mg_prev_smooth_rms; + } + END_SU2_OMP_SAFE_GLOBAL_ACCESS } - END_SU2_OMP_SAFE_GLOBAL_ACCESS } void CMultiGridIntegration::GetProlongated_Correction(unsigned short RunTime_EqSystem, CSolver *sol_fine, CSolver *sol_coarse, CGeometry *geo_fine, CGeometry *geo_coarse, CConfig *config) { SU2_ZONE_SCOPED - const su2double *Solution_Fine = nullptr, *Solution_Coarse = nullptr; const unsigned short nVar = sol_coarse->GetnVar(); - - auto *Solution = new su2double[nVar]; + su2activevector Solution(nVar); SU2_OMP_FOR_STAT(roundUpDiv(geo_coarse->GetnPointDomain(), omp_get_num_threads())) for (auto Point_Coarse = 0ul; Point_Coarse < geo_coarse->GetnPointDomain(); Point_Coarse++) { su2double Area_Parent = geo_coarse->nodes->GetVolume(Point_Coarse); - for (auto iVar = 0u; iVar < nVar; iVar++) Solution[iVar] = 0.0; + Solution = su2double(0); /*--- Accumulate children contributions with stable ordering ---*/ /*--- Process all children in sequential order to ensure deterministic FP summation ---*/ @@ -766,24 +664,22 @@ void CMultiGridIntegration::GetProlongated_Correction(unsigned short RunTime_EqS for (auto iChildren = 0u; iChildren < nChildren; iChildren++) { auto Point_Fine = geo_coarse->nodes->GetChildren_CV(Point_Coarse, iChildren); su2double Area_Children = geo_fine->nodes->GetVolume(Point_Fine); - Solution_Fine = sol_fine->GetNodes()->GetSolution(Point_Fine); + const auto* Solution_Fine = sol_fine->GetNodes()->GetSolution(Point_Fine); su2double weight = Area_Children / Area_Parent; for (auto iVar = 0u; iVar < nVar; iVar++) Solution[iVar] -= Solution_Fine[iVar] * weight; } - Solution_Coarse = sol_coarse->GetNodes()->GetSolution(Point_Coarse); + const auto* Solution_Coarse = sol_coarse->GetNodes()->GetSolution(Point_Coarse); for (auto iVar = 0u; iVar < nVar; iVar++) Solution[iVar] += Solution_Coarse[iVar]; for (auto iVar = 0u; iVar < nVar; iVar++) - sol_coarse->GetNodes()->SetSolution_Old(Point_Coarse,Solution); + sol_coarse->GetNodes()->SetSolution_Old(Point_Coarse, Solution.data()); } END_SU2_OMP_FOR - delete [] Solution; - /*--- Enforce Euler wall BC on corrections by projecting to tangent plane ---*/ sol_coarse->MultigridProjectEulerWall(geo_coarse, config, true); @@ -831,24 +727,15 @@ void CMultiGridIntegration::SmoothProlongated_Correction(unsigned short RunTime_ /*--- Check if there is work to do. ---*/ if (val_nSmooth == 0) return; - const su2double *Residual_Old, *Residual_Sum, *Residual_j; - const unsigned short nVar = solver->GetnVar(); SU2_OMP_FOR_STAT(roundUpDiv(geometry->GetnPoint(), omp_get_num_threads())) for (auto iPoint = 0ul; iPoint < geometry->GetnPoint(); iPoint++) { - Residual_Old = solver->LinSysRes.GetBlock(iPoint); - solver->GetNodes()->SetResidual_Old(iPoint,Residual_Old); + const auto* Residual_Old = solver->LinSysRes.GetBlock(iPoint); + solver->GetNodes()->SetResidual_Old(iPoint, Residual_Old); } END_SU2_OMP_FOR - /*--- Record initial correction norm for debugging output. - * ComputeLinSysResRMS must be called by all threads (uses parallel dot). ---*/ - { - const passivedouble initial_corr_rms = ComputeLinSysResRMS(solver); - SU2_OMP_SAFE_GLOBAL_ACCESS(lastCorrecSmoothRMS[iMesh][0] = initial_corr_rms;) - } - /*--- Jacobi iterations (no early exit — Jacobi targets high-frequency modes, * so the global RMS is not a meaningful convergence indicator). ---*/ @@ -863,7 +750,7 @@ void CMultiGridIntegration::SmoothProlongated_Correction(unsigned short RunTime_ for (auto iNeigh = 0u; iNeigh < geometry->nodes->GetnPoint(iPoint); ++iNeigh) { auto jPoint = geometry->nodes->GetPoint(iPoint, iNeigh); - Residual_j = solver->LinSysRes.GetBlock(jPoint); + const auto* Residual_j = solver->LinSysRes.GetBlock(jPoint); solver->GetNodes()->AddResidual_Sum(iPoint, Residual_j); } @@ -877,8 +764,8 @@ void CMultiGridIntegration::SmoothProlongated_Correction(unsigned short RunTime_ su2double factor = 1.0/(1.0+val_smooth_coeff*su2double(geometry->nodes->GetnPoint(iPoint))); - Residual_Sum = solver->GetNodes()->GetResidual_Sum(iPoint); - Residual_Old = solver->GetNodes()->GetResidual_Old(iPoint); + const auto* Residual_Sum = solver->GetNodes()->GetResidual_Sum(iPoint); + const auto* Residual_Old = solver->GetNodes()->GetResidual_Old(iPoint); for (auto iVar = 0u; iVar < nVar; iVar++) solver->LinSysRes(iPoint,iVar) = (Residual_Old[iVar] + val_smooth_coeff*Residual_Sum[iVar])*factor; @@ -895,7 +782,7 @@ void CMultiGridIntegration::SmoothProlongated_Correction(unsigned short RunTime_ SU2_OMP_FOR_STAT(32) for (auto iVertex = 0ul; iVertex < geometry->GetnVertex(iMarker); iVertex++) { auto iPoint = geometry->vertex[iMarker][iVertex]->GetNode(); - Residual_Old = solver->GetNodes()->GetResidual_Old(iPoint); + const auto* Residual_Old = solver->GetNodes()->GetResidual_Old(iPoint); solver->LinSysRes.SetBlock(iPoint, Residual_Old); } END_SU2_OMP_FOR @@ -904,33 +791,26 @@ void CMultiGridIntegration::SmoothProlongated_Correction(unsigned short RunTime_ } - /*--- Record final correction norm for debugging output. - * ComputeLinSysResRMS must be called by all threads (uses parallel dot). ---*/ - const passivedouble final_corr_rms = ComputeLinSysResRMS(solver); - SU2_OMP_SAFE_GLOBAL_ACCESS(lastCorrecSmoothRMS[iMesh][1] = final_corr_rms;) - + /*--- Record final correction norm for debugging output. ---*/ + if (config->GetMGOptions().MG_Smooth_Output) { + const su2double res = sqrt(solver->LinSysRes.squaredNorm() / (nVar * geometry->GetGlobal_nPointDomain())); + SU2_OMP_SAFE_GLOBAL_ACCESS(lastCorrecSmoothRMS[iMesh][1] = SU2_TYPE::GetValue(res);) + } } void CMultiGridIntegration::SetProlongated_Correction(CSolver *sol_fine, CGeometry *geo_fine, CConfig *config, unsigned short iMesh) { SU2_ZONE_SCOPED - su2double *Solution_Fine, *Residual_Fine; const unsigned short nVar = sol_fine->GetnVar(); - /*--- Level-dependent damping: coarser prolongations produce noisier corrections - * due to larger cell-size jumps, so we reduce the factor progressively. - * iMesh=0: factor = base_damp * 1.0 (finest grid, full correction) - * iMesh=1: factor = base_damp * 0.75 - * iMesh=2: factor = base_damp * 0.5625, etc. ---*/ - const su2double base_damp = config->GetDamp_Correc_Prolong(); - const su2double level_factor = pow(0.75, static_cast(iMesh)); - const su2double factor = base_damp * level_factor; + /*--- Use the adaptive damping factor uniformly across all prolongation levels. ---*/ + const su2double factor = config->GetDamp_Correc_Prolong(); SU2_OMP_FOR_STAT(roundUpDiv(geo_fine->GetnPointDomain(), omp_get_num_threads())) for (auto Point_Fine = 0ul; Point_Fine < geo_fine->GetnPointDomain(); Point_Fine++) { - Residual_Fine = sol_fine->LinSysRes.GetBlock(Point_Fine); - Solution_Fine = sol_fine->GetNodes()->GetSolution(Point_Fine); + auto* Residual_Fine = sol_fine->LinSysRes.GetBlock(Point_Fine); + auto* Solution_Fine = sol_fine->GetNodes()->GetSolution(Point_Fine); for (auto iVar = 0u; iVar < nVar; iVar++) { /*--- Prevent a fine grid divergence due to a coarse grid divergence ---*/ if (Residual_Fine[iVar] != Residual_Fine[iVar]) @@ -972,26 +852,24 @@ void CMultiGridIntegration::SetForcing_Term(CSolver *sol_fine, CSolver *sol_coar const unsigned short nVar = sol_coarse->GetnVar(); const su2double factor = config->GetDamp_Res_Restric(); - auto *Residual = new su2double[nVar]; + su2activevector Residual(nVar); SU2_OMP_FOR_STAT(roundUpDiv(geo_coarse->GetnPointDomain(), omp_get_num_threads())) for (auto Point_Coarse = 0ul; Point_Coarse < geo_coarse->GetnPointDomain(); Point_Coarse++) { sol_coarse->GetNodes()->SetRes_TruncErrorZero(Point_Coarse); - for (auto iVar = 0u; iVar < nVar; iVar++) Residual[iVar] = 0.0; + Residual = su2double(0); for (auto iChildren = 0u; iChildren < geo_coarse->nodes->GetnChildren_CV(Point_Coarse); iChildren++) { auto Point_Fine = geo_coarse->nodes->GetChildren_CV(Point_Coarse, iChildren); Residual_Fine = sol_fine->LinSysRes.GetBlock(Point_Fine); for (auto iVar = 0u; iVar < nVar; iVar++) Residual[iVar] += factor * Residual_Fine[iVar]; } - sol_coarse->GetNodes()->AddRes_TruncError(Point_Coarse, Residual); + sol_coarse->GetNodes()->AddRes_TruncError(Point_Coarse, Residual.data()); } END_SU2_OMP_FOR - delete [] Residual; - for (auto iMarker = 0u; iMarker < config->GetnMarker_All(); iMarker++) { if (config->GetViscous_Wall(iMarker)) { SU2_OMP_FOR_STAT(32) diff --git a/SU2_CFD/src/iteration/CFluidIteration.cpp b/SU2_CFD/src/iteration/CFluidIteration.cpp index 35e2905a7c5e..55aba398ae85 100644 --- a/SU2_CFD/src/iteration/CFluidIteration.cpp +++ b/SU2_CFD/src/iteration/CFluidIteration.cpp @@ -130,9 +130,11 @@ void CFluidIteration::Iterate(COutput* output, CIntegration**** integration, CGe RUNTIME_RADIATION_SYS, val_iZone, val_iInst); } - /*--- Adapt the CFL number using an exponential progression with under-relaxation approach. ---*/ - - if ((config[val_iZone]->GetCFL_Adapt() == YES) && (!disc_adj)) { + /*--- Adapt the CFL number using an exponential progression with under-relaxation approach. + During Full-MG warmup (FinestMesh > MESH_0), skip adaptation entirely until the finest + mesh is active. ---*/ + if ((config[val_iZone]->GetCFL_Adapt() == YES) && (!disc_adj) && + (config[val_iZone]->GetFinestMesh() == MESH_0)) { SU2_OMP_PARALLEL solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->AdaptCFLNumber(geometry[val_iZone][val_iInst], solver[val_iZone][val_iInst], config[val_iZone]); @@ -245,12 +247,18 @@ bool CFluidIteration::Monitor(COutput* output, CIntegration**** integration, CGe if (config[val_iZone]->GetMUSCLRamp()) UpdateRamp(geometry, config, config[val_iZone]->GetInnerIter(), val_iZone, RAMP_TYPE::MUSCL); - output->SetHistoryOutput(geometry[val_iZone][val_iInst][MESH_0], solver[val_iZone][val_iInst][MESH_0], + /*--- During Full-MG startup FinestMesh > 0: read residuals from the active (coarse) level. ---*/ + const unsigned short finestMesh = config[val_iZone]->GetFinestMesh(); + output->SetHistoryOutput(geometry[val_iZone][val_iInst][finestMesh], solver[val_iZone][val_iInst][finestMesh], config[val_iZone], config[val_iZone]->GetTimeIter(), config[val_iZone]->GetOuterIter(), config[val_iZone]->GetInnerIter()); auto StopCalc = output->GetConvergence(); + /*--- During Full-MG warmup the convergence criterion is evaluated against coarse-mesh residuals. + * Never stop before the fine mesh is active. ---*/ + if (finestMesh != MESH_0) StopCalc = false; + /* --- Checking convergence of Fixed CL mode to target CL, and perform finite differencing if needed --*/ if (config[val_iZone]->GetFixed_CL_Mode()) { diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index 0efafa2b8800..36a7d2283f0f 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1986,7 +1986,7 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, void CSolver::SetResidual_RMS(const CGeometry *geometry, const CConfig *config) { SU2_ZONE_SCOPED - if (geometry->GetMGLevel() != MESH_0) return; + if (geometry->GetMGLevel() != MESH_0 && !config->GetMGOptions().MG_Smooth_EarlyExit) return; BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { diff --git a/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp b/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp index a6d2930a9048..8f518e2d9490 100644 --- a/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp +++ b/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp @@ -231,8 +231,9 @@ void CSpeciesFlameletSolver::SetInitialCondition(CGeometry** geometry, CSolver** if (flame_front_ignition) prog_burnt = GetBurntProgressVariable(fluid_model_local, scalar_init, flamelet_config_options.Flame_T_ignition); prog_unburnt = config->GetSpecies_Init()[I_PROGVAR]; + const auto nPoint_iMesh = geometry[i_mesh]->GetnPoint(); SU2_OMP_FOR_STAT(omp_chunk_size) - for (unsigned long i_point = 0; i_point < nPoint; i_point++) { + for (unsigned long i_point = 0; i_point < nPoint_iMesh; i_point++) { auto coords = geometry[i_mesh]->nodes->GetCoord(i_point); if (flame_front_ignition) { diff --git a/TestCases/cont_adj_euler/naca0012/of_grad_cd_disc.dat.ref b/TestCases/cont_adj_euler/naca0012/of_grad_cd_disc.dat.ref index f7e509e5e3cb..6f26c9c89c31 100644 --- a/TestCases/cont_adj_euler/naca0012/of_grad_cd_disc.dat.ref +++ b/TestCases/cont_adj_euler/naca0012/of_grad_cd_disc.dat.ref @@ -1,39 +1,39 @@ VARIABLES="VARIABLE" , "GRADIENT" , "FINDIFF_STEP" - 0 , -7747.74 , 0.001 - 1 , -10948.1 , 0.001 - 2 , -8280.46 , 0.001 - 3 , -3802.21 , 0.001 - 4 , 658.085 , 0.001 - 5 , 4392.72 , 0.001 - 6 , 7268.92 , 0.001 - 7 , 9422.0 , 0.001 - 8 , 11077.6 , 0.001 - 9 , 12447.2 , 0.001 - 10 , 13660.8 , 0.001 - 11 , 14713.0 , 0.001 - 12 , 15397.1 , 0.001 - 13 , 15179.4 , 0.001 - 14 , 12887.9 , 0.001 - 15 , 5922.05 , 0.001 - 16 , -11571.8 , 0.001 - 17 , -51702.7 , 0.001 - 18 , -126889.0 , 0.001 - 19 , -21493.4 , 0.001 - 20 , -22571.2 , 0.001 - 21 , -16492.2 , 0.001 - 22 , -10533.8 , 0.001 - 23 , -6901.19 , 0.001 - 24 , -5763.06 , 0.001 - 25 , -6449.91 , 0.001 - 26 , -8061.2 , 0.001 - 27 , -9790.18 , 0.001 - 28 , -11077.2 , 0.001 - 29 , -11650.8 , 0.001 - 30 , -11500.7 , 0.001 - 31 , -10793.0 , 0.001 - 32 , -9652.1 , 0.001 - 33 , -7555.2 , 0.001 - 34 , -2031.11 , 0.001 - 35 , 12735.8 , 0.001 - 36 , 43905.7 , 0.001 - 37 , 85428.3 , 0.001 + 0 , -9031.62 , 0.001 + 1 , -12012.1 , 0.001 + 2 , -9069.4 , 0.001 + 3 , -4384.1 , 0.001 + 4 , 226.313 , 0.001 + 5 , 4094.64 , 0.001 + 6 , 7135.57 , 0.001 + 7 , 9534.04 , 0.001 + 8 , 11567.7 , 0.001 + 9 , 13502.9 , 0.001 + 10 , 15525.3 , 0.001 + 11 , 17677.6 , 0.001 + 12 , 19774.0 , 0.001 + 13 , 21231.7 , 0.001 + 14 , 20667.9 , 0.001 + 15 , 14910.9 , 0.001 + 16 , -3204.93 , 0.001 + 17 , -47800.8 , 0.001 + 18 , -127342.0 , 0.001 + 19 , -22401.2 , 0.001 + 20 , -22469.9 , 0.001 + 21 , -15796.2 , 0.001 + 22 , -9601.62 , 0.001 + 23 , -5984.92 , 0.001 + 24 , -5040.23 , 0.001 + 25 , -6061.17 , 0.001 + 26 , -8142.27 , 0.001 + 27 , -10495.3 , 0.001 + 28 , -12591.9 , 0.001 + 29 , -14189.4 , 0.001 + 30 , -15280.9 , 0.001 + 31 , -15978.6 , 0.001 + 32 , -16253.9 , 0.001 + 33 , -15289.0 , 0.001 + 34 , -10118.9 , 0.001 + 35 , 5881.72 , 0.001 + 36 , 40595.3 , 0.001 + 37 , 83547.0 , 0.001 diff --git a/TestCases/cont_adj_euler/naca0012/of_grad_directdiff.dat.ref b/TestCases/cont_adj_euler/naca0012/of_grad_directdiff.dat.ref index 0218e98d3169..54b93ba1a831 100644 --- a/TestCases/cont_adj_euler/naca0012/of_grad_directdiff.dat.ref +++ b/TestCases/cont_adj_euler/naca0012/of_grad_directdiff.dat.ref @@ -1,4 +1,4 @@ VARIABLES="VARIABLE" , "DRAG" , "EFFICIENCY" , "FORCE_X" , "FORCE_Y" , "FORCE_Z" , "LIFT" , "MOMENT_X" , "MOMENT_Y" , "MOMENT_Z" , "SIDEFORCE" - 0 , 0.05240962543 , -27.33945591 , 0.08647516198 , -1.560629715 , 0.0 , -1.562144773 , 0.0 , 0.0 , 0.8858124377 , 0.0 - 1 , 0.2008795877 , -41.40890891 , 0.2463424389 , -2.081341632 , 0.0 , -2.086220261 , 0.0 , 0.0 , 0.737429107 , 0.0 - 2 , 0.371975273 , -55.04424867 , 0.4271103196 , -2.522745633 , 0.0 , -2.531462651 , 0.0 , 0.0 , 0.4069664819 , 0.0 + 0 , 0.3283027921 , -23.00124324 , 0.3601048051 , -1.453884254 , 0.0 , -1.461393914 , 0.0 , 0.0 , 0.3837075086 , 0.0 + 1 , 0.5529850442 , -38.57543344 , 0.6062515142 , -2.435135388 , 0.0 , -2.447781199 , 0.0 , 0.0 , 0.1364160306 , 0.0 + 2 , 0.7825703473 , -46.16887385 , 0.8428457563 , -2.753846031 , 0.0 , -2.771577273 , 0.0 , 0.0 , -0.1507632792 , 0.0 diff --git a/TestCases/cont_adj_euler/wedge/of_grad_combo.dat.ref b/TestCases/cont_adj_euler/wedge/of_grad_combo.dat.ref index 88d7794ce033..17f951cf4f58 100644 --- a/TestCases/cont_adj_euler/wedge/of_grad_combo.dat.ref +++ b/TestCases/cont_adj_euler/wedge/of_grad_combo.dat.ref @@ -1,5 +1,5 @@ -VARIABLES="VARIABLE" , "GRADIENT" , "FINDIFF_STEP" - 0 , 0.00701137 , 0.0001 - 1 , 0.00485473 , 0.0001 - 2 , 0.00244467 , 0.0001 - 3 , 0.000872557 , 0.0001 +VARIABLES="VARIABLE" , "GRADIENT" , "FINDIFF_STEP" + 0 , 0.00701908 , 0.0001 + 1 , 0.00485645 , 0.0001 + 2 , 0.002445 , 0.0001 + 3 , 0.000872591 , 0.0001 diff --git a/TestCases/hybrid_regression.py b/TestCases/hybrid_regression.py index e54011937eca..5f02581eeef1 100644 --- a/TestCases/hybrid_regression.py +++ b/TestCases/hybrid_regression.py @@ -51,7 +51,7 @@ def main(): channel.cfg_dir = "euler/channel" channel.cfg_file = "inv_channel_RK.cfg" channel.test_iter = 20 - channel.test_vals = [-2.318252, 3.226111, 0.133426, 0.150359] + channel.test_vals = [-2.000215, 3.536936, 0.034212, 0.193725] test_list.append(channel) # NACA0012 @@ -59,7 +59,7 @@ def main(): naca0012.cfg_dir = "euler/naca0012" naca0012.cfg_file = "inv_NACA0012_Roe.cfg" naca0012.test_iter = 20 - naca0012.test_vals = [-4.252415, -3.789971, 0.306821, 0.024573] + naca0012.test_vals = [-4.492584, -3.930725, 0.297160, 0.025487] test_list.append(naca0012) # Supersonic wedge @@ -67,7 +67,7 @@ def main(): wedge.cfg_dir = "euler/wedge" wedge.cfg_file = "inv_wedge_HLLC.cfg" wedge.test_iter = 20 - wedge.test_vals = [-3.587150, 2.136260, -0.249533, 0.043953] + wedge.test_vals = [-3.689935, 2.034291, -0.249531, 0.043953] test_list.append(wedge) # ONERA M6 Wing @@ -83,7 +83,7 @@ def main(): fixedCL_naca0012.cfg_dir = "fixed_cl/naca0012" fixedCL_naca0012.cfg_file = "inv_NACA0012.cfg" fixedCL_naca0012.test_iter = 10 - fixedCL_naca0012.test_vals = [-3.936884, 1.574653, 0.300864, 0.019462] + fixedCL_naca0012.test_vals = [-4.017883, 1.513039, 0.300961, 0.019472] test_list.append(fixedCL_naca0012) # HYPERSONIC FLOW PAST BLUNT BODY @@ -103,7 +103,7 @@ def main(): flatplate.cfg_dir = "navierstokes/flatplate" flatplate.cfg_file = "lam_flatplate.cfg" flatplate.test_iter = 100 - flatplate.test_vals = [-6.381351, -0.904240, 0.001313, 0.025285, 2.361500, -2.336300, 0, 0] + flatplate.test_vals = [-6.537258, -1.059050, 0.001198, 0.029303, 2.361500, -2.332200, 0.000000, 0.000000] test_list.append(flatplate) # Laminar cylinder (steady) @@ -111,7 +111,7 @@ def main(): cylinder.cfg_dir = "navierstokes/cylinder" cylinder.cfg_file = "lam_cylinder.cfg" cylinder.test_iter = 25 - cylinder.test_vals = [-8.460419, -2.972300, 0.271000, 1.701198, 0.000000] + cylinder.test_vals = [-8.463699, -2.981890, 0.045839, 1.659615, 0.000000] test_list.append(cylinder) # Laminar cylinder (low Mach correction) @@ -119,7 +119,7 @@ def main(): cylinder_lowmach.cfg_dir = "navierstokes/cylinder" cylinder_lowmach.cfg_file = "cylinder_lowmach.cfg" cylinder_lowmach.test_iter = 25 - cylinder_lowmach.test_vals = [-6.629113, -1.167336, -0.016421, -73.160843, 0.000000] + cylinder_lowmach.test_vals = [-6.473668, -1.011938, 0.275173, 71.130747, 0.000000] cylinder_lowmach.test_vals_aarch64 = [-6.830996, -1.368850, -0.143956, 73.963354, 0] test_list.append(cylinder_lowmach) @@ -136,7 +136,7 @@ def main(): poiseuille_profile.cfg_dir = "navierstokes/poiseuille" poiseuille_profile.cfg_file = "profile_poiseuille.cfg" poiseuille_profile.test_iter = 10 - poiseuille_profile.test_vals = [-12.005205, -7.539393, -0.000000, 2.089953] + poiseuille_profile.test_vals = [-12.005129, -7.581821, -0.000000, 2.089953] poiseuille_profile.test_vals_aarch64 = [-12.009012, -7.262530, -0.000000, 2.089953] test_list.append(poiseuille_profile) @@ -157,7 +157,7 @@ def main(): rae2822_sa.cfg_dir = "rans/rae2822" rae2822_sa.cfg_file = "turb_SA_RAE2822.cfg" rae2822_sa.test_iter = 20 - rae2822_sa.test_vals = [-2.169314, -5.450577, 0.293838, 0.104752, 0.000000] + rae2822_sa.test_vals = [-2.193049, -5.312606, 0.388492, 0.077204, 0.000000] test_list.append(rae2822_sa) # RAE2822 SST @@ -165,7 +165,7 @@ def main(): rae2822_sst.cfg_dir = "rans/rae2822" rae2822_sst.cfg_file = "turb_SST_RAE2822.cfg" rae2822_sst.test_iter = 20 - rae2822_sst.test_vals = [-1.028401, 5.875380, 0.279522, 0.093893, 0.000000] + rae2822_sst.test_vals = [-1.028279, 5.869352, 0.357127, 0.074559, 0.000000] test_list.append(rae2822_sst) # RAE2822 SST_SUST @@ -173,7 +173,7 @@ def main(): rae2822_sst_sust.cfg_dir = "rans/rae2822" rae2822_sst_sust.cfg_file = "turb_SST_SUST_RAE2822.cfg" rae2822_sst_sust.test_iter = 20 - rae2822_sst_sust.test_vals = [-2.540329, 5.875369, 0.279522, 0.093893] + rae2822_sst_sust.test_vals = [-2.479273, 5.869341, 0.357127, 0.074559] test_list.append(rae2822_sst_sust) # Flat plate @@ -181,7 +181,7 @@ def main(): turb_flatplate.cfg_dir = "rans/flatplate" turb_flatplate.cfg_file = "turb_SA_flatplate.cfg" turb_flatplate.test_iter = 20 - turb_flatplate.test_vals = [-4.938980, -7.469368, -0.187651, 0.015894] + turb_flatplate.test_vals = [-4.958138, -7.438030, -0.187473, 0.015060] test_list.append(turb_flatplate) # ONERA M6 Wing @@ -266,7 +266,7 @@ def main(): turb_naca0012_sst_restart_mg.cfg_file = "turb_NACA0012_sst_multigrid_restart.cfg" turb_naca0012_sst_restart_mg.test_iter = 20 turb_naca0012_sst_restart_mg.ntest_vals = 5 - turb_naca0012_sst_restart_mg.test_vals = [-6.538814, -5.057150, 0.830240, -0.008813, 0.078177] + turb_naca0012_sst_restart_mg.test_vals = [-6.589114, -5.057151, 0.830239, -0.008808, 0.078150] test_list.append(turb_naca0012_sst_restart_mg) ############################# @@ -347,7 +347,7 @@ def main(): inc_euler_naca0012.cfg_dir = "incomp_euler/naca0012" inc_euler_naca0012.cfg_file = "incomp_NACA0012.cfg" inc_euler_naca0012.test_iter = 20 - inc_euler_naca0012.test_vals = [-5.737092, -4.868827, 0.512333, 0.009264] + inc_euler_naca0012.test_vals = [-5.846118, -4.941076, 0.519913, 0.008955] test_list.append(inc_euler_naca0012) # C-D nozzle with pressure inlet and mass flow outlet @@ -355,7 +355,7 @@ def main(): inc_nozzle.cfg_dir = "incomp_euler/nozzle" inc_nozzle.cfg_file = "inv_nozzle.cfg" inc_nozzle.test_iter = 20 - inc_nozzle.test_vals = [-5.362338, -4.748247, -0.017498, 0.115865] + inc_nozzle.test_vals = [-6.171980, -5.419034, 0.009267, 0.127577] test_list.append(inc_nozzle) ############################# @@ -367,7 +367,7 @@ def main(): inc_lam_cylinder.cfg_dir = "incomp_navierstokes/cylinder" inc_lam_cylinder.cfg_file = "incomp_cylinder.cfg" inc_lam_cylinder.test_iter = 10 - inc_lam_cylinder.test_vals = [-4.168263, -3.611146, 0.013547, 4.538946] + inc_lam_cylinder.test_vals = [-4.159236, -3.569077, 0.017653, 4.934964] test_list.append(inc_lam_cylinder) # Buoyancy-driven cavity @@ -432,7 +432,7 @@ def main(): cavity.cfg_dir = "moving_wall/cavity" cavity.cfg_file = "lam_cavity.cfg" cavity.test_iter = 25 - cavity.test_vals = [-7.325788, -1.861524, 1.097830, 1.028368] + cavity.test_vals = [-7.938907, -2.490199, 0.013042, 0.004995] test_list.append(cavity) # Spinning cylinder @@ -440,7 +440,7 @@ def main(): spinning_cylinder.cfg_dir = "moving_wall/spinning_cylinder" spinning_cylinder.cfg_file = "spinning_cylinder.cfg" spinning_cylinder.test_iter = 25 - spinning_cylinder.test_vals = [-7.881383, -2.419298, 1.535192, 1.450905] + spinning_cylinder.test_vals = [-7.547526, -2.080576, 1.888705, 1.812327] spinning_cylinder.test_vals_aarch64 = [-8.008023, -2.611064, 1.497308, 1.487483] test_list.append(spinning_cylinder) @@ -462,7 +462,7 @@ def main(): sine_gust.cfg_dir = "gust" sine_gust.cfg_file = "inv_gust_NACA0012.cfg" sine_gust.test_iter = 5 - sine_gust.test_vals = [-1.977498, 3.481817, -0.009878, -0.005086] + sine_gust.test_vals = [-1.977498, 3.481817, -0.009944, -0.004266] sine_gust.unsteady = True test_list.append(sine_gust) @@ -471,7 +471,7 @@ def main(): cosine_gust.cfg_dir = "gust" cosine_gust.cfg_file = "cosine_gust_zdir.cfg" cosine_gust.test_iter = 79 - cosine_gust.test_vals = [-2.418805, 0.001775, -0.001245, 0.000411, -0.000593] + cosine_gust.test_vals = [-2.418805, 0.002211, -0.001258, 0.000438, -0.000592] cosine_gust.unsteady = True cosine_gust.enabled_with_tsan = False test_list.append(cosine_gust) @@ -491,7 +491,7 @@ def main(): aeroelastic.cfg_dir = "aeroelastic" aeroelastic.cfg_file = "aeroelastic_NACA64A010.cfg" aeroelastic.test_iter = 2 - aeroelastic.test_vals = [-1.876630, 4.021077, 0.078661, 0.027698, -0.001639, -0.000129, -1.056256] + aeroelastic.test_vals = [-1.876626, 4.021083, 0.081436, 0.027726, -0.001638, -0.000130, -1.056269] aeroelastic.unsteady = True aeroelastic.enabled_on_cpu_arch = ["x86_64"] # Requires AVX-capable architecture test_list.append(aeroelastic) @@ -519,7 +519,7 @@ def main(): unst_deforming_naca0012.cfg_dir = "disc_adj_euler/naca0012_pitching_def" unst_deforming_naca0012.cfg_file = "inv_NACA0012_pitching_deform.cfg" unst_deforming_naca0012.test_iter = 5 - unst_deforming_naca0012.test_vals = [-3.665243, -3.794042, -3.716822, -3.148495] + unst_deforming_naca0012.test_vals = [-3.665284, -3.794189, -3.716987, -3.148573] unst_deforming_naca0012.unsteady = True unst_deforming_naca0012.enabled_with_tsan = False test_list.append(unst_deforming_naca0012) @@ -532,8 +532,8 @@ def main(): edge_VW = TestCase('edge_VW') edge_VW.cfg_dir = "nicf/edge" edge_VW.cfg_file = "edge_VW.cfg" - edge_VW.test_iter = 40 - edge_VW.test_vals = [-8.895442, -2.694280, -0.000009, 0.000000] + edge_VW.test_iter = 30 + edge_VW.test_vals = [-7.053408, -0.851910, -0.000009, 0.000000] test_list.append(edge_VW) # Rarefaction shock wave edge_PPR @@ -541,7 +541,7 @@ def main(): edge_PPR.cfg_dir = "nicf/edge" edge_PPR.cfg_file = "edge_PPR.cfg" edge_PPR.test_iter = 20 - edge_PPR.test_vals = [-10.594993, -4.431993, -0.000034, 0.000000] + edge_PPR.test_vals = [-12.455039, -6.258168, -0.000034, 0.000000] edge_PPR.test_vals_aarch64 = [ -7.139211, -0.980821, -0.000034, 0.000000] test_list.append(edge_PPR) @@ -654,7 +654,7 @@ def main(): bars_SST_2D.cfg_dir = "sliding_interface/bars_SST_2D" bars_SST_2D.cfg_file = "bars.cfg" bars_SST_2D.test_iter = 13 - bars_SST_2D.test_vals = [13.000000, -0.473801, -1.563681] + bars_SST_2D.test_vals = [13.000000, -0.396746, -1.461254] bars_SST_2D.multizone = True test_list.append(bars_SST_2D) diff --git a/TestCases/hybrid_regression_AD.py b/TestCases/hybrid_regression_AD.py index 4e1aa84e22c2..df801edb10fe 100644 --- a/TestCases/hybrid_regression_AD.py +++ b/TestCases/hybrid_regression_AD.py @@ -99,7 +99,7 @@ def main(): discadj_incomp_NACA0012.cfg_dir = "disc_adj_incomp_euler/naca0012" discadj_incomp_NACA0012.cfg_file = "incomp_NACA0012_disc.cfg" discadj_incomp_NACA0012.test_iter = 20 - discadj_incomp_NACA0012.test_vals = [20.000000, -3.338267, -2.490046, 0.000000] + discadj_incomp_NACA0012.test_vals = [20.000000, -3.122291, -2.287328, 0.000000] test_list.append(discadj_incomp_NACA0012) ##################################### @@ -187,7 +187,7 @@ def main(): discadj_pitchingNACA0012.cfg_dir = "disc_adj_euler/naca0012_pitching" discadj_pitchingNACA0012.cfg_file = "inv_NACA0012_pitching.cfg" discadj_pitchingNACA0012.test_iter = 4 - discadj_pitchingNACA0012.test_vals = [-1.124927, -1.586220, -0.006032, 0.000009] + discadj_pitchingNACA0012.test_vals = [-1.041883, -1.512874, -0.006211, 0.000012] discadj_pitchingNACA0012.tol = 0.01 discadj_pitchingNACA0012.unsteady = True discadj_pitchingNACA0012.enabled_with_tsan = False diff --git a/TestCases/multiple_ffd/naca0012/of_grad_cd.dat.ref b/TestCases/multiple_ffd/naca0012/of_grad_cd.dat.ref index 8ab0b5b0fe70..45622cb6bd5e 100644 --- a/TestCases/multiple_ffd/naca0012/of_grad_cd.dat.ref +++ b/TestCases/multiple_ffd/naca0012/of_grad_cd.dat.ref @@ -1,3 +1,3 @@ VARIABLES="VARIABLE" , "GRADIENT" , "FINDIFF_STEP" - 0 , 0.0456482 , 0.001 - 1 , -0.0881187 , 0.001 + 0 , 0.0525397 , 0.001 + 1 , -0.099502 , 0.001 diff --git a/TestCases/multiple_ffd/naca0012/of_grad_directdiff.dat.ref b/TestCases/multiple_ffd/naca0012/of_grad_directdiff.dat.ref index 9365cd9cd74a..174226e78499 100644 --- a/TestCases/multiple_ffd/naca0012/of_grad_directdiff.dat.ref +++ b/TestCases/multiple_ffd/naca0012/of_grad_directdiff.dat.ref @@ -1,3 +1,3 @@ VARIABLES="VARIABLE" , "DRAG" , "EFFICIENCY" , "FORCE_X" , "FORCE_Y" , "FORCE_Z" , "LIFT" , "MOMENT_X" , "MOMENT_Y" , "MOMENT_Z" , "SIDEFORCE" - 0 , 0.04734305575 , -0.4824482295 , 0.04752721691 , -0.00792353334 , 0.0 , -0.008958448527 , 0.0 , 0.0 , 0.02636591979 , 0.0 - 1 , -0.09189465405 , 0.6527341405 , -0.09165316206 , -0.01206987592 , 0.0 , -0.01006760043 , 0.0 , 0.0 , 0.06097366677 , 0.0 + 0 , 0.05592643623 , -0.4243829941 , 0.05586792439 , 0.003291646374 , 0.0 , 0.002072110703 , 0.0 , 0.0 , 0.02964415677 , 0.0 + 1 , -0.1052916742 , 0.9115770745 , -0.1054405784 , 0.005675584425 , 0.0 , 0.007974407886 , 0.0 , 0.0 , 0.06727760621 , 0.0 diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index a138b9c374a7..a6e777342244 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -228,7 +228,7 @@ def main(): channel.cfg_dir = "euler/channel" channel.cfg_file = "inv_channel_RK.cfg" channel.test_iter = 20 - channel.test_vals = [-2.305051, 3.237256, 0.125006, 0.154077] + channel.test_vals = [-1.988782, 3.546674, 0.032065, 0.194399] test_list.append(channel) # NACA0012 @@ -236,7 +236,7 @@ def main(): naca0012.cfg_dir = "euler/naca0012" naca0012.cfg_file = "inv_NACA0012_Roe.cfg" naca0012.test_iter = 20 - naca0012.test_vals = [-4.314738, -3.845428, 0.307738, 0.024670] + naca0012.test_vals = [-4.442633, -3.913184, 0.295834, 0.024405] test_list.append(naca0012) # Supersonic wedge @@ -244,7 +244,7 @@ def main(): wedge.cfg_dir = "euler/wedge" wedge.cfg_file = "inv_wedge_HLLC.cfg" wedge.test_iter = 20 - wedge.test_vals = [-3.573085, 2.150321, -0.249533, 0.043953] + wedge.test_vals = [-3.681969, 2.042532, -0.249531, 0.043953] test_list.append(wedge) # ONERA M6 Wing @@ -252,7 +252,7 @@ def main(): oneram6.cfg_dir = "euler/oneram6" oneram6.cfg_file = "inv_ONERAM6.cfg" oneram6.test_iter = 10 - oneram6.test_vals = [-11.525271, -10.995902, 0.280800, 0.008623] + oneram6.test_vals = [-11.530230, -11.006685, 0.280800, 0.008623] oneram6.timeout = 3200 test_list.append(oneram6) @@ -261,7 +261,7 @@ def main(): fixedCL_naca0012.cfg_dir = "fixed_cl/naca0012" fixedCL_naca0012.cfg_file = "inv_NACA0012.cfg" fixedCL_naca0012.test_iter = 10 - fixedCL_naca0012.test_vals = [-3.919916, 1.592064, 0.300852, 0.019461] + fixedCL_naca0012.test_vals = [-4.006904, 1.523677, 0.300969, 0.019473] test_list.append(fixedCL_naca0012) # Polar sweep of the inviscid NACA0012 @@ -270,7 +270,7 @@ def main(): polar_naca0012.cfg_file = "inv_NACA0012.cfg" polar_naca0012.polar = True polar_naca0012.test_iter = 10 - polar_naca0012.test_vals = [-1.206973, 4.234604, 0.013410, 0.073285] + polar_naca0012.test_vals = [-1.285568, 4.161371, 0.003627, 0.083095] polar_naca0012.test_vals_aarch64 = [-1.083394, 4.386134, 0.001588, 0.033513] polar_naca0012.command = TestCase.Command(exec = "compute_polar.py", param = "-i 11") # flaky test on arm64 @@ -327,7 +327,7 @@ def main(): flatplate.cfg_dir = "navierstokes/flatplate" flatplate.cfg_file = "lam_flatplate.cfg" flatplate.test_iter = 100 - flatplate.test_vals = [-6.397042, -0.919335, 0.001321, 0.025064, 2.361500, -2.336500, 0.000000, 0.000000] + flatplate.test_vals = [-6.499563, -1.020731, 0.001223, 0.028373, 2.361500, -2.333200, 0.000000, 0.000000] test_list.append(flatplate) # Custom objective function @@ -352,7 +352,7 @@ def main(): cylinder.cfg_dir = "navierstokes/cylinder" cylinder.cfg_file = "lam_cylinder.cfg" cylinder.test_iter = 25 - cylinder.test_vals = [-8.529918, -3.059339, 0.189560, 1.746170, 0.000000] + cylinder.test_vals = [-8.411856, -2.937460, -0.002360, 1.643164, 0.000000] test_list.append(cylinder) # Laminar cylinder (low Mach correction) @@ -360,7 +360,8 @@ def main(): cylinder_lowmach.cfg_dir = "navierstokes/cylinder" cylinder_lowmach.cfg_file = "cylinder_lowmach.cfg" cylinder_lowmach.test_iter = 25 - cylinder_lowmach.test_vals = [-6.603373, -1.141590, -0.715787, -63.545748, 0.000000] + cylinder_lowmach.test_vals = [-6.469910, -1.008163, -0.381561, 78.514909, 0.000000] + test_list.append(cylinder_lowmach) # 2D Poiseuille flow (body force driven with periodic inlet / outlet) @@ -368,7 +369,7 @@ def main(): poiseuille.cfg_dir = "navierstokes/poiseuille" poiseuille.cfg_file = "lam_poiseuille.cfg" poiseuille.test_iter = 10 - poiseuille.test_vals = [-5.050889, 0.648196, 0.000199, 13.639173, 0.000000] + poiseuille.test_vals = [0.648196, 0.000199, 13.639173, 0.000000] poiseuille.tol = 0.001 test_list.append(poiseuille) @@ -377,7 +378,7 @@ def main(): poiseuille_profile.cfg_dir = "navierstokes/poiseuille" poiseuille_profile.cfg_file = "profile_poiseuille.cfg" poiseuille_profile.test_iter = 10 - poiseuille_profile.test_vals = [-12.004334, -7.534404, -0.000000, 2.089953] + poiseuille_profile.test_vals = [-12.004278, -7.578183, -0.000000, 2.089953] poiseuille_profile.test_vals_aarch64 = [-12.007498, -7.226926, -0.000000, 2.089953] poiseuille_profile.tol = [0.001, 0.001, 1e-5, 1e-5, 1e-5] test_list.append(poiseuille_profile) @@ -391,7 +392,7 @@ def main(): rae2822_sa.cfg_dir = "rans/rae2822" rae2822_sa.cfg_file = "turb_SA_RAE2822.cfg" rae2822_sa.test_iter = 20 - rae2822_sa.test_vals = [-2.172705, -5.465982, 0.292927, 0.103369, 0.000000] + rae2822_sa.test_vals = [-2.190821, -5.317349, 0.391922, 0.075544, 0.000000] test_list.append(rae2822_sa) # RAE2822 SST @@ -399,7 +400,7 @@ def main(): rae2822_sst.cfg_dir = "rans/rae2822" rae2822_sst.cfg_file = "turb_SST_RAE2822.cfg" rae2822_sst.test_iter = 20 - rae2822_sst.test_vals = [-1.035662, 5.868036, 0.283073, 0.094756, 0.000000] + rae2822_sst.test_vals = [-1.035574, 5.863601, 0.358893, 0.074724, 0.000000] test_list.append(rae2822_sst) # RAE2822 SST_SUST @@ -407,7 +408,7 @@ def main(): rae2822_sst_sust.cfg_dir = "rans/rae2822" rae2822_sst_sust.cfg_file = "turb_SST_SUST_RAE2822.cfg" rae2822_sst_sust.test_iter = 20 - rae2822_sst_sust.test_vals = [-2.542992, 5.868021, 0.283073, 0.094756] + rae2822_sst_sust.test_vals = [-2.492136, 5.863586, 0.358893, 0.074724] test_list.append(rae2822_sst_sust) # Flat plate @@ -415,7 +416,7 @@ def main(): turb_flatplate.cfg_dir = "rans/flatplate" turb_flatplate.cfg_file = "turb_SA_flatplate.cfg" turb_flatplate.test_iter = 20 - turb_flatplate.test_vals = [-4.879863, -7.464877, -0.187760, 0.016260] + turb_flatplate.test_vals = [-4.926218, -7.441083, -0.187477, 0.015330] test_list.append(turb_flatplate) # Flat plate (compressible) with species inlet @@ -423,7 +424,7 @@ def main(): turb_flatplate_species.cfg_dir = "rans/flatplate" turb_flatplate_species.cfg_file = "turb_SA_flatplate_species.cfg" turb_flatplate_species.test_iter = 20 - turb_flatplate_species.test_vals = [-4.548766, -1.476908, -1.980237, 0.937064, -3.573976, 3.000000, -0.475230, 2.000000, -1.511528, 3.000000, -0.699313, 0.999900, 0.999900] + turb_flatplate_species.test_vals = [-4.728172, -1.517257, -2.296200, 0.801649, -3.574284, 3.000000, -0.964853, 2.000000, -1.529003, 3.000000, -0.705161, 0.999933, 0.999933] test_list.append(turb_flatplate_species) # Flat plate SST compressibility correction Wilcox @@ -521,7 +522,7 @@ def main(): turb_naca0012_sst_2003_Vm.cfg_dir = "rans/naca0012" turb_naca0012_sst_2003_Vm.cfg_file = "turb_NACA0012_sst_2003-Vm.cfg" turb_naca0012_sst_2003_Vm.test_iter = 10 - turb_naca0012_sst_2003_Vm.test_vals = [-7.134118, -10.168739, -3.668709, 1.060563, 0.019147, -2.282800] + turb_naca0012_sst_2003_Vm.test_vals = [-10.168739, -3.668709, 1.060563, 0.019147, -2.282800] turb_naca0012_sst_2003_Vm.timeout = 3200 test_list.append(turb_naca0012_sst_2003_Vm) @@ -530,7 +531,7 @@ def main(): turb_naca0012_sst_1994_KLm.cfg_dir = "rans/naca0012" turb_naca0012_sst_1994_KLm.cfg_file = "turb_NACA0012_sst_1994-KLm.cfg" turb_naca0012_sst_1994_KLm.test_iter = 10 - turb_naca0012_sst_1994_KLm.test_vals = [-7.142696, -10.390871, -3.701584, 1.062164, 0.019076, -2.426123] + turb_naca0012_sst_1994_KLm.test_vals = [-10.390871, -3.701584, 1.062164, 0.019076, -2.426123] turb_naca0012_sst_1994_KLm.timeout = 3200 test_list.append(turb_naca0012_sst_1994_KLm) @@ -539,7 +540,7 @@ def main(): turb_naca0012_sst_fixedvalues.cfg_dir = "rans/naca0012" turb_naca0012_sst_fixedvalues.cfg_file = "turb_NACA0012_sst_fixedvalues.cfg" turb_naca0012_sst_fixedvalues.test_iter = 10 - turb_naca0012_sst_fixedvalues.test_vals = [-5.216551, -10.440018, 0.774146, 1.022363, 0.040546, -3.736444] + turb_naca0012_sst_fixedvalues.test_vals = [-10.440018, 0.774146, 1.022363, 0.040546, -3.736444] turb_naca0012_sst_fixedvalues.timeout = 3200 test_list.append(turb_naca0012_sst_fixedvalues) @@ -595,7 +596,7 @@ def main(): turb_naca0012_sst_restart_mg.cfg_file = "turb_NACA0012_sst_multigrid_restart.cfg" turb_naca0012_sst_restart_mg.test_iter = 20 turb_naca0012_sst_restart_mg.ntest_vals = 5 - turb_naca0012_sst_restart_mg.test_vals = [-6.538846, -5.057149, 0.830238, -0.008740, 0.078171] + turb_naca0012_sst_restart_mg.test_vals = [-6.566570, -5.057149, 0.830238, -0.008695, 0.078156] turb_naca0012_sst_restart_mg.timeout = 3200 turb_naca0012_sst_restart_mg.tol = 0.000001 test_list.append(turb_naca0012_sst_restart_mg) @@ -609,7 +610,7 @@ def main(): inc_euler_naca0012.cfg_dir = "incomp_euler/naca0012" inc_euler_naca0012.cfg_file = "incomp_NACA0012.cfg" inc_euler_naca0012.test_iter = 20 - inc_euler_naca0012.test_vals = [-5.984413, -5.104039, 0.523625, 0.008885] + inc_euler_naca0012.test_vals = [-6.058441, -5.126560, 0.525681, 0.008778] test_list.append(inc_euler_naca0012) # C-D nozzle with pressure inlet and mass flow outlet @@ -617,7 +618,7 @@ def main(): inc_nozzle.cfg_dir = "incomp_euler/nozzle" inc_nozzle.cfg_file = "inv_nozzle.cfg" inc_nozzle.test_iter = 20 - inc_nozzle.test_vals = [-5.594509, -4.878247, 0.005147, 0.139611] + inc_nozzle.test_vals = [-6.058643, -5.286249, 0.002391, 0.124107] test_list.append(inc_nozzle) # Laminar wall mounted cylinder, Euler walls, cylinder wall diagonally split @@ -637,7 +638,7 @@ def main(): inc_lam_cylinder.cfg_dir = "incomp_navierstokes/cylinder" inc_lam_cylinder.cfg_file = "incomp_cylinder.cfg" inc_lam_cylinder.test_iter = 10 - inc_lam_cylinder.test_vals = [-4.157264, -3.589125, -0.018681, 4.659302] + inc_lam_cylinder.test_vals = [-4.156152, -3.554127, -0.017923, 5.101126] test_list.append(inc_lam_cylinder) # Laminar sphere, Re=1. Last column: Cd=24/Re @@ -645,7 +646,7 @@ def main(): inc_lam_sphere.cfg_dir = "incomp_navierstokes/sphere" inc_lam_sphere.cfg_file = "sphere.cfg" inc_lam_sphere.test_iter = 5 - inc_lam_sphere.test_vals = [-8.166398, -8.947226, 0.121003, 25.782690] + inc_lam_sphere.test_vals = [-8.165744, -8.968003, 0.121003, 25.782691] test_list.append(inc_lam_sphere) # Buoyancy-driven cavity @@ -661,7 +662,7 @@ def main(): inc_poly_cylinder.cfg_dir = "incomp_navierstokes/cylinder" inc_poly_cylinder.cfg_file = "poly_cylinder.cfg" inc_poly_cylinder.test_iter = 20 - inc_poly_cylinder.test_vals = [-8.030976, -2.362995, 0.006829, 1.923532, -172.590000] + inc_poly_cylinder.test_vals = [-2.362995, 0.006829, 1.923532, -172.590000] test_list.append(inc_poly_cylinder) # X-coarse laminar bend as a mixed element CGNS test @@ -783,7 +784,7 @@ def main(): turbmod_sa_bsl_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_bsl_rae2822.cfg_file = "turb_SA_BSL_RAE2822.cfg" turbmod_sa_bsl_rae2822.test_iter = 20 - turbmod_sa_bsl_rae2822.test_vals = [-2.610446, 0.335724, -0.118510, -5.463071, 0.765741, 0.028251] + turbmod_sa_bsl_rae2822.test_vals = [-2.797759, 0.171338, -0.274795, -5.261510, 0.792586, 0.025577] test_list.append(turbmod_sa_bsl_rae2822) # SA Negative @@ -791,7 +792,7 @@ def main(): turbmod_sa_neg_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_neg_rae2822.cfg_file = "turb_SA_NEG_RAE2822.cfg" turbmod_sa_neg_rae2822.test_iter = 10 - turbmod_sa_neg_rae2822.test_vals = [-1.345556, 1.448390, 1.208561, -0.846814, 1.273854, 0.498380, 0.000000] + turbmod_sa_neg_rae2822.test_vals = [1.448390, 1.208561, -0.846814, 1.273854, 0.498380, 0.000000] turbmod_sa_neg_rae2822.test_vals_aarch64 = [-1.345593, 1.448310, 1.208721, -0.846597, 1.248410, 0.489117, 0.000000] test_list.append(turbmod_sa_neg_rae2822) @@ -800,7 +801,7 @@ def main(): turbmod_sa_comp_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_comp_rae2822.cfg_file = "turb_SA_COMP_RAE2822.cfg" turbmod_sa_comp_rae2822.test_iter = 20 - turbmod_sa_comp_rae2822.test_vals = [-2.610449, 0.335720, -0.118515, -5.468848, 0.765768, 0.028257] + turbmod_sa_comp_rae2822.test_vals = [-2.797713, 0.171400, -0.274750, -5.270451, 0.792619, 0.025579] test_list.append(turbmod_sa_comp_rae2822) # SA Edwards @@ -808,7 +809,7 @@ def main(): turbmod_sa_edw_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_edw_rae2822.cfg_file = "turb_SA_EDW_RAE2822.cfg" turbmod_sa_edw_rae2822.test_iter = 20 - turbmod_sa_edw_rae2822.test_vals = [-2.609968, 0.336105, -0.118179, -6.022707, 0.765928, 0.028192] + turbmod_sa_edw_rae2822.test_vals = [-2.798216, 0.171419, -0.274673, -5.950380, 0.793286, 0.025430] test_list.append(turbmod_sa_edw_rae2822) # SA Compressibility and Edwards @@ -816,7 +817,7 @@ def main(): turbmod_sa_comp_edw_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_comp_edw_rae2822.cfg_file = "turb_SA_COMP_EDW_RAE2822.cfg" turbmod_sa_comp_edw_rae2822.test_iter = 20 - turbmod_sa_comp_edw_rae2822.test_vals = [-2.610041, 0.336057, -0.118225, -6.023847, 0.765944, 0.028196] + turbmod_sa_comp_edw_rae2822.test_vals = [-2.804013, 0.164355, -0.281124, -5.949769, 0.793596, 0.025415] test_list.append(turbmod_sa_comp_edw_rae2822) # SA QCR @@ -824,7 +825,7 @@ def main(): turbmod_sa_qcr_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_qcr_rae2822.cfg_file = "turb_SA_QCR_RAE2822.cfg" turbmod_sa_qcr_rae2822.test_iter = 20 - turbmod_sa_qcr_rae2822.test_vals = [-2.320855, 0.512787, 0.108026, -5.449178, 0.770698, 0.026693] + turbmod_sa_qcr_rae2822.test_vals = [-2.818005, 0.149744, -0.284903, -5.229322, 0.793829, 0.025455] test_list.append(turbmod_sa_qcr_rae2822) ############################ @@ -848,7 +849,7 @@ def main(): contadj_naca0012.cfg_dir = "cont_adj_euler/naca0012" contadj_naca0012.cfg_file = "inv_NACA0012.cfg" contadj_naca0012.test_iter = 5 - contadj_naca0012.test_vals = [-9.520784, -15.101278, -0.726250, 0.020280] + contadj_naca0012.test_vals = [-9.526111, -15.089167, -0.726250, 0.020280] contadj_naca0012.test_vals_aarch64 = [-9.662546, -14.998818, -0.726250, 0.020280] test_list.append(contadj_naca0012) @@ -857,7 +858,7 @@ def main(): contadj_oneram6.cfg_dir = "cont_adj_euler/oneram6" contadj_oneram6.cfg_file = "inv_ONERAM6.cfg" contadj_oneram6.test_iter = 10 - contadj_oneram6.test_vals = [-12.069374, -12.632463, -1.086100, 0.007556] + contadj_oneram6.test_vals = [-12.086022, -12.648069, -1.086100, 0.007556] test_list.append(contadj_oneram6) # Inviscid WEDGE: tests averaged outflow total pressure adjoint @@ -873,7 +874,7 @@ def main(): contadj_fixed_CL_naca0012.cfg_dir = "fixed_cl/naca0012" contadj_fixed_CL_naca0012.cfg_file = "inv_NACA0012_ContAdj.cfg" contadj_fixed_CL_naca0012.test_iter = 100 - contadj_fixed_CL_naca0012.test_vals = [1.154889, -4.342907, -0.075177, -0.007496] + contadj_fixed_CL_naca0012.test_vals = [1.382576, -4.042295, -0.008696, 0.003238] test_list.append(contadj_fixed_CL_naca0012) ################################### @@ -885,7 +886,7 @@ def main(): contadj_ns_cylinder.cfg_dir = "cont_adj_navierstokes/cylinder" contadj_ns_cylinder.cfg_file = "lam_cylinder.cfg" contadj_ns_cylinder.test_iter = 20 - contadj_ns_cylinder.test_vals = [-3.589714, -9.027143, 2.056700, -0.000000] + contadj_ns_cylinder.test_vals = [-3.632833, -9.087692, 2.056700, -0.000000] test_list.append(contadj_ns_cylinder) # Adjoint laminar naca0012 subsonic @@ -929,7 +930,7 @@ def main(): contadj_rans_rae2822.cfg_dir = "cont_adj_rans/rae2822" contadj_rans_rae2822.cfg_file = "turb_SA_RAE2822.cfg" contadj_rans_rae2822.test_iter = 20 - contadj_rans_rae2822.test_vals = [-5.399633, -10.904666, -0.212470, 0.005448] + contadj_rans_rae2822.test_vals = [-5.399744, -10.904916, -0.212470, 0.005448] test_list.append(contadj_rans_rae2822) ############################# @@ -1009,7 +1010,7 @@ def main(): rot_naca0012.cfg_dir = "rotating/naca0012" rot_naca0012.cfg_file = "rot_NACA0012.cfg" rot_naca0012.test_iter = 25 - rot_naca0012.test_vals = [-1.302487, 4.220598, -0.002152, 0.124241] + rot_naca0012.test_vals = [-1.289907, 4.246244, -0.000518, 0.112723] test_list.append(rot_naca0012) # Lid-driven cavity @@ -1017,7 +1018,7 @@ def main(): cavity.cfg_dir = "moving_wall/cavity" cavity.cfg_file = "lam_cavity.cfg" cavity.test_iter = 25 - cavity.test_vals = [-8.194578, -2.735580, 0.005612, -0.019805] + cavity.test_vals = [-7.828480, -2.367075, 0.008928, 0.007370] test_list.append(cavity) # Spinning cylinder @@ -1025,7 +1026,7 @@ def main(): spinning_cylinder.cfg_dir = "moving_wall/spinning_cylinder" spinning_cylinder.cfg_file = "spinning_cylinder.cfg" spinning_cylinder.test_iter = 25 - spinning_cylinder.test_vals = [-7.716789, -2.257294, 2.054821, 1.660858] + spinning_cylinder.test_vals = [-7.383987, -1.919413, 2.601945, 1.976038] test_list.append(spinning_cylinder) ###################################### @@ -1046,7 +1047,7 @@ def main(): sine_gust.cfg_dir = "gust" sine_gust.cfg_file = "inv_gust_NACA0012.cfg" sine_gust.test_iter = 5 - sine_gust.test_vals = [-1.977498, 3.481817, -0.010372, -0.005217] + sine_gust.test_vals = [-1.977498, 3.481817, -0.010337, -0.004460] sine_gust.unsteady = True test_list.append(sine_gust) @@ -1084,7 +1085,7 @@ def main(): flatplate_unsteady.cfg_dir = "navierstokes/flatplate" flatplate_unsteady.cfg_file = "lam_flatplate_unst.cfg" flatplate_unsteady.test_iter = 3 - flatplate_unsteady.test_vals = [-8.875128, -8.250204, -6.305788, -5.469452, -3.398230, 0.002075, -0.325535] + flatplate_unsteady.test_vals = [0.000008, -8.875128, -8.250204, -6.305788, -5.469452, -3.398230, 0.002075, -0.325535] flatplate_unsteady.unsteady = True test_list.append(flatplate_unsteady) @@ -1097,15 +1098,15 @@ def main(): edge_VW.cfg_dir = "nicf/edge" edge_VW.cfg_file = "edge_VW.cfg" edge_VW.test_iter = 25 - edge_VW.test_vals = [-3.178712, 3.023048, -0.000009, 0.000000] + edge_VW.test_vals = [-3.145553, 3.055761, -0.000009, 0.000000] test_list.append(edge_VW) # Rarefaction shock wave edge_PPR edge_PPR = TestCase('edge_PPR') edge_PPR.cfg_dir = "nicf/edge" edge_PPR.cfg_file = "edge_PPR.cfg" - edge_PPR.test_iter = 25 - edge_PPR.test_vals = [-9.020461, -2.870274, -0.000034, 0.000000] + edge_PPR.test_iter = 20 + edge_PPR.test_vals = [-10.311364, -4.158193, -0.000034, 0.000000] test_list.append(edge_PPR) # Rarefaction Q1D nozzle, include CoolProp fluid model @@ -1252,7 +1253,7 @@ def main(): bars_SST_2D.cfg_dir = "sliding_interface/bars_SST_2D" bars_SST_2D.cfg_file = "bars.cfg" bars_SST_2D.test_iter = 13 - bars_SST_2D.test_vals = [13.000000, -0.472766, -1.559919] + bars_SST_2D.test_vals = [13.000000, -0.397743, -1.458724] bars_SST_2D.multizone = True test_list.append(bars_SST_2D) @@ -1443,7 +1444,7 @@ def main(): pywrapper_naca0012.cfg_dir = "euler/naca0012" pywrapper_naca0012.cfg_file = "inv_NACA0012_Roe.cfg" pywrapper_naca0012.test_iter = 80 - pywrapper_naca0012.test_vals = [-7.755848, -7.082517, 0.335769, 0.023275] + pywrapper_naca0012.test_vals = [-6.754892, -6.158544, 0.335712, 0.023273] pywrapper_naca0012.command = TestCase.Command("mpirun -np 2", "SU2_CFD.py", "--parallel -f") test_list.append(pywrapper_naca0012) @@ -1473,7 +1474,7 @@ def main(): pywrapper_aeroelastic.cfg_dir = "aeroelastic" pywrapper_aeroelastic.cfg_file = "aeroelastic_NACA64A010.cfg" pywrapper_aeroelastic.test_iter = 2 - pywrapper_aeroelastic.test_vals = [-1.876634, 4.021068, 0.081002, 0.027610, -0.001642, -0.000127, -0.966944] + pywrapper_aeroelastic.test_vals = [-1.876633, 4.021069, 0.082654, 0.027642, -0.001643, -0.000126, -0.966946] pywrapper_aeroelastic.command = TestCase.Command("mpirun -np 2", "SU2_CFD.py", "--parallel -f") pywrapper_aeroelastic.unsteady = True test_list.append(pywrapper_aeroelastic) @@ -1514,7 +1515,7 @@ def main(): pywrapper_unsteadyCHT.cfg_dir = "py_wrapper/flatPlate_unsteady_CHT" pywrapper_unsteadyCHT.cfg_file = "unsteady_CHT_FlatPlate_Conf.cfg" pywrapper_unsteadyCHT.test_iter = 5 - pywrapper_unsteadyCHT.test_vals = [-1.614168, 2.259984, -0.005601, 0.129785] + pywrapper_unsteadyCHT.test_vals = [-1.614168, 2.259968, -0.010622, 0.171621] pywrapper_unsteadyCHT.command = TestCase.Command("mpirun -np 2", "python", "launch_unsteady_CHT_FlatPlate.py --parallel -f") pywrapper_unsteadyCHT.unsteady = True test_list.append(pywrapper_unsteadyCHT) diff --git a/TestCases/parallel_regression_AD.py b/TestCases/parallel_regression_AD.py index f4fa88dc75db..cc4b94931bf7 100644 --- a/TestCases/parallel_regression_AD.py +++ b/TestCases/parallel_regression_AD.py @@ -104,7 +104,7 @@ def main(): discadj_incomp_NACA0012.cfg_dir = "disc_adj_incomp_euler/naca0012" discadj_incomp_NACA0012.cfg_file = "incomp_NACA0012_disc.cfg" discadj_incomp_NACA0012.test_iter = 20 - discadj_incomp_NACA0012.test_vals = [20.000000, -3.319959, -2.489110, 0.000000] + discadj_incomp_NACA0012.test_vals = [20.000000, -3.095094, -2.322528, 0.000000] test_list.append(discadj_incomp_NACA0012) ##################################### @@ -217,7 +217,7 @@ def main(): discadj_pitchingNACA0012.cfg_dir = "disc_adj_euler/naca0012_pitching" discadj_pitchingNACA0012.cfg_file = "inv_NACA0012_pitching.cfg" discadj_pitchingNACA0012.test_iter = 4 - discadj_pitchingNACA0012.test_vals = [-1.126077, -1.582787, -0.005904, 0.000009] + discadj_pitchingNACA0012.test_vals = [-1.040215, -1.508864, -0.006076, 0.000012] discadj_pitchingNACA0012.tol = 0.01 discadj_pitchingNACA0012.unsteady = True test_list.append(discadj_pitchingNACA0012) @@ -231,7 +231,7 @@ def main(): discadj_trans_stator.cfg_dir = "disc_adj_turbomachinery/transonic_stator_2D" discadj_trans_stator.cfg_file = "transonic_stator.cfg" discadj_trans_stator.test_iter = 79 - discadj_trans_stator.test_vals = [79.000000, 2.599442, 2.336454, 2.140576, 0.786869] + discadj_trans_stator.test_vals = [79.000000, 2.555965, 2.327109, 2.115689, 0.745501] discadj_trans_stator.test_vals_aarch64 = [79.000000, 0.696755, 0.485950, 0.569475, -0.990065] test_list.append(discadj_trans_stator) diff --git a/TestCases/py_wrapper/translating_NACA0012/forces_0.csv.ref b/TestCases/py_wrapper/translating_NACA0012/forces_0.csv.ref index 696f6078c386..5d4e8e3e99e7 100644 --- a/TestCases/py_wrapper/translating_NACA0012/forces_0.csv.ref +++ b/TestCases/py_wrapper/translating_NACA0012/forces_0.csv.ref @@ -1,200 +1,200 @@ -199, -0.97, -0.00, 0.00 +199, -0.96, -0.00, 0.00 0, -2.88, 19.83, 0.00 -1, -4.23, 29.14, 0.00 -2, -5.32, 36.73, 0.00 -3, -6.40, 44.31, 0.00 -4, -7.29, 50.60, 0.00 -5, -8.10, 56.42, 0.00 -6, -8.75, 61.21, 0.00 -7, -9.32, 65.49, 0.00 -8, -9.74, 68.79, 0.00 -9, -10.10, 71.74, 0.00 -10, -10.31, 73.69, 0.00 -11, -10.46, 75.35, 0.00 -12, -10.47, 76.00, 0.00 -13, -10.44, 76.40, 0.00 -14, -10.26, 75.71, 0.00 -15, -10.11, 75.29, 0.00 -16, -9.80, 73.70, 0.00 -17, -9.64, 73.29, 0.00 -18, -9.24, 70.95, 0.00 -19, -8.85, 68.73, 0.00 -20, -8.25, 64.81, 0.00 -21, -7.82, 62.19, 0.00 -22, -7.14, 57.56, 0.00 -23, -6.71, 54.74, 0.00 -24, -5.95, 49.24, 0.00 -25, -5.71, 47.88, 0.00 -26, -4.78, 40.72, 0.00 -27, -4.44, 38.38, 0.00 -28, -3.40, 29.82, 0.00 -29, -2.77, 24.72, 0.00 -30, -1.56, 14.15, 0.00 -31, -0.95, 8.74, 0.00 -32, 0.27, -2.57, 0.00 -33, 1.50, -14.37, 0.00 -34, 2.95, -28.80, 0.00 -35, 4.95, -49.22, 0.00 -36, 6.34, -64.46, 0.00 -37, 8.93, -92.82, 0.00 -38, 10.11, -107.58, 0.00 -39, 12.72, -138.81, 0.00 -40, 13.61, -152.47, 0.00 -41, 17.42, -200.74, 0.00 -42, 18.00, -213.73, 0.00 -43, 20.51, -251.51, 0.00 -44, 20.54, -260.85, 0.00 -45, 22.50, -296.71, 0.00 -46, 22.34, -306.87, 0.00 -47, 23.59, -338.75, 0.00 -48, 23.03, -347.38, 0.00 -49, 23.93, -380.83, 0.00 -50, 22.45, -379.32, 0.00 -51, 20.75, -374.79, 0.00 -52, 18.42, -358.59, 0.00 -53, 15.34, -325.29, 0.00 -54, 13.36, -312.48, 0.00 -55, 10.43, -273.29, 0.00 -56, 9.04, -271.47, 0.00 -57, 6.53, -231.40, 0.00 -58, 6.09, -266.47, 0.00 -59, 5.99, -346.96, 0.00 -60, 4.63, -406.68, 0.00 -61, 2.25, -429.96, 0.00 -62, -0.52, -423.85, 0.00 -63, -3.28, -409.29, 0.00 -64, -6.04, -398.86, 0.00 -65, -8.60, -380.50, 0.00 -66, -11.15, -366.37, 0.00 -67, -13.55, -350.18, 0.00 -68, -15.78, -333.45, 0.00 -69, -17.95, -317.94, 0.00 -70, -19.76, -299.23, 0.00 -71, -21.49, -282.21, 0.00 -72, -22.61, -260.41, 0.00 -73, -24.40, -248.66, 0.00 -74, -25.13, -228.22, 0.00 -75, -25.20, -205.09, 0.00 -76, -24.79, -181.60, 0.00 -77, -24.32, -160.97, 0.00 -78, -23.96, -143.67, 0.00 -79, -21.95, -119.46, 0.00 -80, -20.65, -102.16, 0.00 -81, -20.28, -91.26, 0.00 -82, -19.07, -78.04, 0.00 -83, -17.20, -63.96, 0.00 -84, -13.15, -44.35, 0.00 -85, -6.18, -18.88, 0.00 -86, 1.48, 4.08, 0.00 -87, 4.97, 12.30, 0.00 -88, 13.95, 30.78, 0.00 -89, 13.05, 25.52, 0.00 -90, 39.01, 66.93, 0.00 -91, 34.95, 51.96, 0.00 -92, 10.01, 12.67, 0.00 -93, 12.23, 12.90, 0.00 -94, 14.63, 12.43, 0.00 -95, 3.85, 2.51, 0.00 -96, -58.89, -26.99, 0.00 -97, -58.75, -15.90, 0.00 -98, -114.76, -15.40, 0.00 -99, 44.44, -0.00, 0.00 -100, -6.17, 0.83, 0.00 -101, 92.06, -24.91, 0.00 -102, 144.41, -66.19, 0.00 -103, 183.90, -119.78, 0.00 -104, 81.69, -69.39, 0.00 -105, 58.16, -61.33, 0.00 -106, 54.39, -68.88, 0.00 -107, 20.73, -30.81, 0.00 -108, 15.21, -26.10, 0.00 -109, -5.66, 11.08, 0.00 -110, -10.96, 24.19, 0.00 -111, -22.60, 55.88, 0.00 -112, -24.49, 67.42, 0.00 -113, -35.46, 108.24, 0.00 -114, -39.42, 132.94, 0.00 -115, -44.81, 166.57, 0.00 -116, -45.54, 186.33, 0.00 -117, -47.93, 215.64, 0.00 -118, -48.48, 239.82, 0.00 -119, -49.35, 268.61, 0.00 -120, -49.01, 293.88, 0.00 -121, -48.31, 319.72, 0.00 -122, -46.79, 342.80, 0.00 -123, -45.53, 370.47, 0.00 -124, -43.31, 393.24, 0.00 -125, -41.11, 418.88, 0.00 -126, -38.44, 442.69, 0.00 -127, -35.61, 467.64, 0.00 -128, -32.31, 489.28, 0.00 -129, -28.84, 510.98, 0.00 -130, -25.18, 532.04, 0.00 -131, -21.39, 553.10, 0.00 -132, -17.41, 571.76, 0.00 -133, -13.39, 592.26, 0.00 -134, -9.24, 610.35, 0.00 -135, -5.05, 629.44, 0.00 -136, -0.80, 646.65, 0.00 -137, 3.47, 663.79, 0.00 -138, 7.73, 678.32, 0.00 -139, 11.97, 693.15, 0.00 -140, 16.16, 706.43, 0.00 -141, 20.25, 717.98, 0.00 -142, 24.27, 728.83, 0.00 -143, 28.17, 738.33, 0.00 -144, 31.95, 747.06, 0.00 -145, 35.57, 754.00, 0.00 -146, 39.07, 760.46, 0.00 -147, 42.33, 764.49, 0.00 -148, 45.53, 769.28, 0.00 -149, 48.66, 774.52, 0.00 -150, 51.68, 779.40, 0.00 -151, 55.24, 793.46, 0.00 -152, 57.48, 789.62, 0.00 -153, 55.23, 728.32, 0.00 -154, 58.05, 737.21, 0.00 -155, 70.62, 866.19, 0.00 -156, 62.28, 739.59, 0.00 -157, 53.43, 615.64, 0.00 -158, 50.31, 563.60, 0.00 -159, 35.26, 384.74, 0.00 -160, 38.05, 404.94, 0.00 -161, 10.14, 105.39, 0.00 -162, 5.42, 55.09, 0.00 -163, -5.93, -59.00, 0.00 -164, -8.98, -87.52, 0.00 -165, -4.77, -45.59, 0.00 -166, -4.28, -40.14, 0.00 -167, -4.62, -42.60, 0.00 -168, -5.24, -47.52, 0.00 -169, -5.86, -52.28, 0.00 -170, -6.59, -57.83, 0.00 -171, -6.84, -59.11, 0.00 -172, -7.47, -63.60, 0.00 -173, -7.82, -65.59, 0.00 -174, -8.57, -70.89, 0.00 -175, -9.11, -74.38, 0.00 -176, -9.74, -78.44, 0.00 -177, -9.79, -77.88, 0.00 -178, -10.24, -80.44, 0.00 -179, -10.40, -80.83, 0.00 -180, -10.78, -82.78, 0.00 -181, -10.97, -83.35, 0.00 -182, -11.18, -84.12, 0.00 -183, -11.31, -84.24, 0.00 -184, -11.41, -84.21, 0.00 -185, -11.47, -83.91, 0.00 -186, -11.29, -81.91, 0.00 -187, -11.22, -80.81, 0.00 -188, -11.03, -78.90, 0.00 -189, -10.50, -74.56, 0.00 -190, -10.05, -70.94, 0.00 -191, -9.49, -66.66, 0.00 -192, -8.86, -61.95, 0.00 -193, -8.10, -56.41, 0.00 -194, -7.28, -50.51, 0.00 -195, -6.28, -43.47, 0.00 -196, -5.22, -36.04, 0.00 -197, -3.99, -27.53, 0.00 -198, -2.69, -18.50, 0.00 +1, -4.18, 28.79, 0.00 +2, -5.26, 36.27, 0.00 +3, -6.22, 43.06, 0.00 +4, -7.06, 48.99, 0.00 +5, -7.74, 53.91, 0.00 +6, -8.33, 58.23, 0.00 +7, -8.78, 61.68, 0.00 +8, -9.13, 64.48, 0.00 +9, -9.36, 66.46, 0.00 +10, -9.49, 67.87, 0.00 +11, -9.52, 68.60, 0.00 +12, -9.47, 68.72, 0.00 +13, -9.30, 68.08, 0.00 +14, -9.07, 66.93, 0.00 +15, -8.74, 65.11, 0.00 +16, -8.36, 62.92, 0.00 +17, -7.90, 60.05, 0.00 +18, -7.40, 56.86, 0.00 +19, -6.82, 53.00, 0.00 +20, -6.21, 48.82, 0.00 +21, -5.55, 44.16, 0.00 +22, -4.86, 39.18, 0.00 +23, -4.12, 33.67, 0.00 +24, -3.37, 27.85, 0.00 +25, -2.57, 21.54, 0.00 +26, -1.75, 14.91, 0.00 +27, -0.89, 7.67, 0.00 +28, -0.03, 0.29, 0.00 +29, 0.85, -7.59, 0.00 +30, 1.69, -15.34, 0.00 +31, 2.54, -23.41, 0.00 +32, 3.37, -31.59, 0.00 +33, 4.19, -40.08, 0.00 +34, 5.02, -48.92, 0.00 +35, 5.83, -57.97, 0.00 +36, 6.62, -67.30, 0.00 +37, 7.38, -76.73, 0.00 +38, 8.13, -86.51, 0.00 +39, 8.83, -96.39, 0.00 +40, 9.51, -106.58, 0.00 +41, 10.14, -116.85, 0.00 +42, 10.76, -127.75, 0.00 +43, 11.32, -138.90, 0.00 +44, 11.81, -149.97, 0.00 +45, 12.22, -161.13, 0.00 +46, 12.50, -171.78, 0.00 +47, 12.68, -182.09, 0.00 +48, 12.78, -192.81, 0.00 +49, 12.76, -203.07, 0.00 +50, 12.57, -212.38, 0.00 +51, 12.05, -217.67, 0.00 +52, 11.60, -225.88, 0.00 +53, 11.01, -233.43, 0.00 +54, 10.51, -245.81, 0.00 +55, 9.41, -246.68, 0.00 +56, 7.63, -229.29, 0.00 +57, 6.26, -221.94, 0.00 +58, 6.99, -305.64, 0.00 +59, 6.92, -400.58, 0.00 +60, 4.75, -416.91, 0.00 +61, 2.15, -411.19, 0.00 +62, -0.50, -401.81, 0.00 +63, -3.15, -392.75, 0.00 +64, -5.77, -381.55, 0.00 +65, -8.34, -369.11, 0.00 +66, -10.81, -355.18, 0.00 +67, -13.15, -340.07, 0.00 +68, -15.34, -324.16, 0.00 +69, -17.37, -307.70, 0.00 +70, -19.20, -290.73, 0.00 +71, -20.74, -272.38, 0.00 +72, -22.00, -253.37, 0.00 +73, -22.70, -231.35, 0.00 +74, -23.29, -211.45, 0.00 +75, -23.37, -190.20, 0.00 +76, -23.19, -169.88, 0.00 +77, -22.18, -146.81, 0.00 +78, -20.95, -125.64, 0.00 +79, -19.03, -103.59, 0.00 +80, -16.76, -82.89, 0.00 +81, -13.60, -61.19, 0.00 +82, -9.50, -38.86, 0.00 +83, -4.20, -15.60, 0.00 +84, 2.57, 8.67, 0.00 +85, 10.17, 31.06, 0.00 +86, 18.87, 51.96, 0.00 +87, 28.98, 71.65, 0.00 +88, 41.74, 92.13, 0.00 +89, 54.07, 105.75, 0.00 +90, 70.75, 121.39, 0.00 +91, 96.09, 142.85, 0.00 +92, 93.51, 118.43, 0.00 +93, 120.64, 127.20, 0.00 +94, 136.03, 115.56, 0.00 +95, 169.16, 110.18, 0.00 +96, 186.54, 85.50, 0.00 +97, 143.56, 38.86, 0.00 +98, 66.96, 8.98, 0.00 +99, 44.43, -0.00, 0.00 +100, 87.14, -11.69, 0.00 +101, 145.23, -39.31, 0.00 +102, 142.83, -65.47, 0.00 +103, 105.19, -68.51, 0.00 +104, 88.44, -75.13, 0.00 +105, 63.28, -66.72, 0.00 +106, 48.19, -61.02, 0.00 +107, 25.79, -38.34, 0.00 +108, 13.76, -23.61, 0.00 +109, -0.24, 0.48, 0.00 +110, -9.29, 20.51, 0.00 +111, -20.12, 49.76, 0.00 +112, -27.25, 75.02, 0.00 +113, -34.63, 105.71, 0.00 +114, -39.49, 133.20, 0.00 +115, -42.08, 156.42, 0.00 +116, -43.53, 178.10, 0.00 +117, -45.53, 204.86, 0.00 +118, -46.18, 228.44, 0.00 +119, -46.94, 255.49, 0.00 +120, -46.62, 279.57, 0.00 +121, -46.43, 307.28, 0.00 +122, -45.37, 332.37, 0.00 +123, -44.27, 360.19, 0.00 +124, -42.30, 384.11, 0.00 +125, -40.27, 410.38, 0.00 +126, -37.67, 433.86, 0.00 +127, -34.88, 458.06, 0.00 +128, -31.68, 479.72, 0.00 +129, -28.36, 502.38, 0.00 +130, -24.77, 523.30, 0.00 +131, -21.07, 544.83, 0.00 +132, -17.18, 564.29, 0.00 +133, -13.22, 584.83, 0.00 +134, -9.13, 602.95, 0.00 +135, -4.98, 621.40, 0.00 +136, -0.79, 638.19, 0.00 +137, 3.43, 654.80, 0.00 +138, 7.63, 669.17, 0.00 +139, 11.81, 684.02, 0.00 +140, 15.94, 697.09, 0.00 +141, 20.00, 708.97, 0.00 +142, 23.96, 719.61, 0.00 +143, 27.84, 729.78, 0.00 +144, 31.59, 738.57, 0.00 +145, 35.21, 746.32, 0.00 +146, 38.69, 753.11, 0.00 +147, 42.03, 758.96, 0.00 +148, 45.19, 763.53, 0.00 +149, 48.25, 767.89, 0.00 +150, 51.12, 770.97, 0.00 +151, 53.90, 774.14, 0.00 +152, 56.45, 775.45, 0.00 +153, 58.60, 772.82, 0.00 +154, 60.49, 768.26, 0.00 +155, 61.32, 752.19, 0.00 +156, 66.19, 786.01, 0.00 +157, 51.41, 592.36, 0.00 +158, 2.74, 30.66, 0.00 +159, -1.91, -20.81, 0.00 +160, -1.38, -14.70, 0.00 +161, -2.29, -23.78, 0.00 +162, -2.80, -28.43, 0.00 +163, -2.29, -22.77, 0.00 +164, -2.55, -24.89, 0.00 +165, -2.37, -22.65, 0.00 +166, -2.68, -25.18, 0.00 +167, -2.76, -25.43, 0.00 +168, -3.15, -28.54, 0.00 +169, -3.54, -31.56, 0.00 +170, -4.04, -35.47, 0.00 +171, -4.42, -38.18, 0.00 +172, -4.93, -41.95, 0.00 +173, -5.44, -45.65, 0.00 +174, -6.02, -49.76, 0.00 +175, -6.54, -53.35, 0.00 +176, -7.07, -56.96, 0.00 +177, -7.54, -59.96, 0.00 +178, -8.01, -62.95, 0.00 +179, -8.43, -65.45, 0.00 +180, -8.82, -67.79, 0.00 +181, -9.18, -69.80, 0.00 +182, -9.48, -71.32, 0.00 +183, -9.72, -72.39, 0.00 +184, -9.90, -73.09, 0.00 +185, -10.04, -73.44, 0.00 +186, -10.00, -72.60, 0.00 +187, -10.00, -71.99, 0.00 +188, -9.88, -70.65, 0.00 +189, -9.65, -68.59, 0.00 +190, -9.33, -65.89, 0.00 +191, -8.91, -62.56, 0.00 +192, -8.37, -58.52, 0.00 +193, -7.74, -53.89, 0.00 +194, -7.00, -48.56, 0.00 +195, -6.14, -42.46, 0.00 +196, -5.13, -35.44, 0.00 +197, -4.03, -27.78, 0.00 +198, -2.72, -18.69, 0.00 diff --git a/TestCases/py_wrapper/updated_moving_frame_NACA12/config.cfg b/TestCases/py_wrapper/updated_moving_frame_NACA12/config.cfg index 053828f96eb3..31d503068672 100644 --- a/TestCases/py_wrapper/updated_moving_frame_NACA12/config.cfg +++ b/TestCases/py_wrapper/updated_moving_frame_NACA12/config.cfg @@ -50,7 +50,7 @@ VENKAT_LIMITER_COEFF= 0.1 % SOLUTION ACCELERATION % -CFL_NUMBER= 50 +CFL_NUMBER= 10 CFL_ADAPT= NO % MGLEVEL= 3 diff --git a/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref b/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref index 46d649f5175e..e47d7e1ff730 100644 --- a/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref +++ b/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref @@ -1,200 +1,200 @@ 199, -0.97, -0.00, 0.00 -0, -2.85, 19.62, 0.00 -1, -4.08, 28.11, 0.00 -2, -5.17, 35.71, 0.00 -3, -6.11, 42.24, 0.00 -4, -6.94, 48.17, 0.00 -5, -7.57, 52.73, 0.00 -6, -8.15, 56.97, 0.00 -7, -8.53, 59.95, 0.00 -8, -8.87, 62.63, 0.00 -9, -8.99, 63.89, 0.00 -10, -9.11, 65.14, 0.00 -11, -9.01, 64.90, 0.00 -12, -8.93, 64.84, 0.00 -13, -8.60, 62.93, 0.00 -14, -8.34, 61.54, 0.00 -15, -7.81, 58.21, 0.00 -16, -7.40, 55.70, 0.00 -17, -6.70, 50.94, 0.00 -18, -6.14, 47.20, 0.00 -19, -5.32, 41.30, 0.00 -20, -4.67, 36.73, 0.00 -21, -3.69, 29.39, 0.00 -22, -2.98, 24.01, 0.00 -23, -1.95, 15.91, 0.00 -24, -1.21, 9.98, 0.00 -25, -0.16, 1.35, 0.00 -26, 0.63, -5.38, 0.00 -27, 1.68, -14.54, 0.00 -28, 2.49, -21.87, 0.00 -29, 3.57, -31.82, 0.00 -30, 4.36, -39.47, 0.00 -31, 5.42, -49.99, 0.00 -32, 6.16, -57.79, 0.00 -33, 7.10, -67.94, 0.00 -34, 7.75, -75.60, 0.00 -35, 8.60, -85.55, 0.00 -36, 9.16, -93.11, 0.00 -37, 9.88, -102.70, 0.00 -38, 10.30, -109.57, 0.00 -39, 10.67, -116.45, 0.00 -40, 10.97, -122.94, 0.00 -41, 10.85, -125.01, 0.00 -42, 10.97, -130.23, 0.00 -43, 10.59, -129.89, 0.00 -44, 10.72, -136.19, 0.00 -45, 10.25, -135.16, 0.00 -46, 10.25, -140.77, 0.00 -47, 9.09, -130.53, 0.00 -48, 8.67, -130.76, 0.00 -49, 6.09, -96.90, 0.00 -50, 5.92, -100.03, 0.00 -51, 5.90, -106.60, 0.00 -52, 6.79, -132.22, 0.00 -53, 20.46, -433.78, 0.00 -54, 27.67, -647.05, 0.00 -55, 21.99, -576.50, 0.00 -56, 18.31, -549.97, 0.00 -57, 15.75, -558.27, 0.00 -58, 12.65, -553.28, 0.00 -59, 9.40, -544.31, 0.00 -60, 6.08, -533.83, 0.00 -61, 2.74, -524.40, 0.00 -62, -0.63, -511.87, 0.00 -63, -4.01, -500.50, 0.00 -64, -7.36, -486.02, 0.00 -65, -10.72, -474.33, 0.00 -66, -13.96, -458.63, 0.00 -67, -17.17, -443.82, 0.00 -68, -20.18, -426.27, 0.00 -69, -23.14, -410.05, 0.00 -70, -25.82, -391.06, 0.00 -71, -28.45, -373.57, 0.00 -72, -30.70, -353.59, 0.00 -73, -33.29, -339.20, 0.00 -74, -35.07, -318.40, 0.00 -75, -36.82, -299.64, 0.00 -76, -37.96, -278.06, 0.00 -77, -39.56, -261.85, 0.00 -78, -40.14, -240.69, 0.00 -79, -41.29, -224.76, 0.00 -80, -41.06, -203.15, 0.00 -81, -42.40, -190.79, 0.00 -82, -41.79, -171.00, 0.00 -83, -42.95, -159.67, 0.00 -84, -42.40, -142.99, 0.00 -85, -45.26, -138.16, 0.00 -86, -45.31, -124.76, 0.00 -87, -48.24, -119.27, 0.00 -88, -50.85, -112.24, 0.00 -89, -59.21, -115.79, 0.00 -90, -58.88, -101.02, 0.00 -91, -81.20, -120.72, 0.00 -92, -82.96, -105.06, 0.00 -93, -107.96, -113.84, 0.00 -94, -139.30, -118.34, 0.00 -95, -41.52, -27.05, 0.00 -96, -37.01, -16.96, 0.00 -97, 39.35, 10.65, 0.00 -98, 37.44, 5.02, 0.00 -99, -65.44, 0.00, 0.00 -100, -86.45, 11.60, 0.00 -101, -161.88, 43.81, 0.00 -102, -143.44, 65.75, 0.00 -103, -113.80, 74.12, 0.00 -104, -114.52, 97.29, 0.00 -105, -107.02, 112.85, 0.00 -106, -135.53, 171.63, 0.00 -107, -84.72, 125.94, 0.00 -108, -68.44, 117.42, 0.00 -109, -43.03, 84.15, 0.00 -110, -50.65, 111.79, 0.00 -111, -47.20, 116.70, 0.00 -112, -49.20, 135.47, 0.00 -113, -45.81, 139.84, 0.00 -114, -49.98, 168.57, 0.00 -115, -49.99, 185.86, 0.00 -116, -51.63, 211.24, 0.00 -117, -50.46, 227.04, 0.00 -118, -51.05, 252.53, 0.00 -119, -49.65, 270.24, 0.00 -120, -49.56, 297.19, 0.00 -121, -47.48, 314.21, 0.00 -122, -46.22, 338.56, 0.00 -123, -44.23, 359.88, 0.00 -124, -42.11, 382.33, 0.00 -125, -39.68, 404.32, 0.00 -126, -37.10, 427.26, 0.00 -127, -34.16, 448.55, 0.00 -128, -31.03, 469.99, 0.00 -129, -27.69, 490.62, 0.00 -130, -24.20, 511.21, 0.00 -131, -20.49, 529.60, 0.00 -132, -16.71, 548.88, 0.00 -133, -12.79, 565.66, 0.00 -134, -8.83, 583.11, 0.00 -135, -4.79, 598.02, 0.00 -136, -0.76, 614.45, 0.00 -137, 3.29, 627.98, 0.00 -138, 7.32, 642.04, 0.00 -139, 11.29, 653.62, 0.00 -140, 15.24, 666.44, 0.00 -141, 19.08, 676.34, 0.00 -142, 22.88, 687.03, 0.00 -143, 26.54, 695.78, 0.00 -144, 30.12, 704.26, 0.00 -145, 33.47, 709.58, 0.00 -146, 36.77, 715.69, 0.00 -147, 39.75, 717.81, 0.00 -148, 42.73, 721.93, 0.00 -149, 45.49, 724.01, 0.00 -150, 48.31, 728.62, 0.00 -151, 51.12, 734.17, 0.00 -152, 55.50, 762.40, 0.00 -153, 39.08, 515.34, 0.00 -154, -2.39, -30.35, 0.00 -155, -1.02, -12.47, 0.00 -156, 3.53, 41.96, 0.00 -157, 4.67, 53.82, 0.00 -158, 4.83, 54.15, 0.00 -159, 5.42, 59.10, 0.00 -160, 5.72, 60.85, 0.00 -161, 5.71, 59.30, 0.00 -162, 5.61, 56.99, 0.00 -163, 5.18, 51.51, 0.00 -164, 4.87, 47.48, 0.00 -165, 4.24, 40.54, 0.00 -166, 3.76, 35.32, 0.00 -167, 3.00, 27.68, 0.00 -168, 2.43, 22.03, 0.00 -169, 1.62, 14.42, 0.00 -170, 0.98, 8.57, 0.00 -171, -0.06, -0.53, 0.00 -172, -0.78, -6.63, 0.00 -173, -1.76, -14.77, 0.00 -174, -2.46, -20.33, 0.00 -175, -3.34, -27.23, 0.00 -176, -4.02, -32.36, 0.00 -177, -4.96, -39.42, 0.00 -178, -5.61, -44.10, 0.00 -179, -6.39, -49.60, 0.00 -180, -6.94, -53.34, 0.00 -181, -7.60, -57.73, 0.00 -182, -8.02, -60.36, 0.00 -183, -8.51, -63.36, 0.00 -184, -8.81, -65.00, 0.00 -185, -9.15, -66.98, 0.00 -186, -9.27, -67.25, 0.00 -187, -9.42, -67.84, 0.00 -188, -9.34, -66.82, 0.00 -189, -9.25, -65.70, 0.00 -190, -8.98, -63.45, 0.00 -191, -8.67, -60.90, 0.00 -192, -8.19, -57.26, 0.00 -193, -7.63, -53.13, 0.00 -194, -6.92, -48.04, 0.00 -195, -6.09, -42.15, 0.00 -196, -5.12, -35.34, 0.00 -197, -4.03, -27.79, 0.00 -198, -2.78, -19.17, 0.00 +0, -2.88, 19.85, 0.00 +1, -4.05, 27.93, 0.00 +2, -5.07, 35.03, 0.00 +3, -5.96, 41.24, 0.00 +4, -6.74, 46.78, 0.00 +5, -7.39, 51.48, 0.00 +6, -7.92, 55.40, 0.00 +7, -8.36, 58.76, 0.00 +8, -8.64, 61.03, 0.00 +9, -8.84, 62.79, 0.00 +10, -8.89, 63.57, 0.00 +11, -8.86, 63.80, 0.00 +12, -8.73, 63.35, 0.00 +13, -8.52, 62.33, 0.00 +14, -8.24, 60.81, 0.00 +15, -7.91, 58.90, 0.00 +16, -7.49, 56.33, 0.00 +17, -7.04, 53.47, 0.00 +18, -6.48, 49.79, 0.00 +19, -5.90, 45.83, 0.00 +20, -5.25, 41.27, 0.00 +21, -4.60, 36.56, 0.00 +22, -3.89, 31.30, 0.00 +23, -3.18, 25.96, 0.00 +24, -2.42, 19.98, 0.00 +25, -1.68, 14.07, 0.00 +26, -0.87, 7.38, 0.00 +27, -0.09, 0.81, 0.00 +28, 0.74, -6.50, 0.00 +29, 1.54, -13.72, 0.00 +30, 2.37, -21.44, 0.00 +31, 3.13, -28.87, 0.00 +32, 3.94, -37.02, 0.00 +33, 4.69, -44.82, 0.00 +34, 5.49, -53.48, 0.00 +35, 6.20, -61.69, 0.00 +36, 6.97, -70.86, 0.00 +37, 7.67, -79.73, 0.00 +38, 8.38, -89.17, 0.00 +39, 9.03, -98.47, 0.00 +40, 9.61, -107.66, 0.00 +41, 10.12, -116.57, 0.00 +42, 10.52, -124.95, 0.00 +43, 10.83, -132.84, 0.00 +44, 11.03, -140.07, 0.00 +45, 11.07, -146.01, 0.00 +46, 10.87, -149.29, 0.00 +47, 10.24, -147.08, 0.00 +48, 9.85, -148.58, 0.00 +49, 9.23, -146.95, 0.00 +50, 8.03, -135.66, 0.00 +51, 5.02, -90.65, 0.00 +52, 8.44, -164.34, 0.00 +53, 24.61, -521.74, 0.00 +54, 25.24, -590.05, 0.00 +55, 21.35, -559.54, 0.00 +56, 18.43, -553.39, 0.00 +57, 15.47, -548.45, 0.00 +58, 12.34, -539.47, 0.00 +59, 9.16, -530.51, 0.00 +60, 5.93, -520.02, 0.00 +61, 2.66, -509.08, 0.00 +62, -0.61, -496.20, 0.00 +63, -3.87, -482.86, 0.00 +64, -7.09, -468.17, 0.00 +65, -10.23, -452.82, 0.00 +66, -13.28, -436.19, 0.00 +67, -16.22, -419.41, 0.00 +68, -19.00, -401.35, 0.00 +69, -21.63, -383.22, 0.00 +70, -24.05, -364.23, 0.00 +71, -26.30, -345.36, 0.00 +72, -28.26, -325.47, 0.00 +73, -30.06, -306.30, 0.00 +74, -31.47, -285.73, 0.00 +75, -32.68, -265.95, 0.00 +76, -33.54, -245.70, 0.00 +77, -34.32, -227.17, 0.00 +78, -34.48, -206.77, 0.00 +79, -34.55, -188.05, 0.00 +80, -33.88, -167.61, 0.00 +81, -33.34, -150.01, 0.00 +82, -31.79, -130.08, 0.00 +83, -30.47, -113.26, 0.00 +84, -27.83, -93.86, 0.00 +85, -26.36, -80.46, 0.00 +86, -22.80, -62.77, 0.00 +87, -20.38, -50.40, 0.00 +88, -16.48, -36.38, 0.00 +89, -13.50, -26.40, 0.00 +90, -8.89, -15.26, 0.00 +91, 2.07, 3.08, 0.00 +92, 9.08, 11.50, 0.00 +93, 13.77, 14.52, 0.00 +94, 20.38, 17.31, 0.00 +95, 38.32, 24.96, 0.00 +96, 51.52, 23.61, 0.00 +97, -11.06, -2.99, 0.00 +98, 9.27, 1.24, 0.00 +99, 56.99, -0.00, 0.00 +100, 121.78, -16.34, 0.00 +101, 216.03, -58.47, 0.00 +102, 204.05, -93.53, 0.00 +103, 153.71, -100.12, 0.00 +104, 131.85, -112.01, 0.00 +105, 83.63, -88.18, 0.00 +106, 65.02, -82.34, 0.00 +107, 36.90, -54.86, 0.00 +108, 24.12, -41.38, 0.00 +109, 3.60, -7.04, 0.00 +110, -5.15, 11.37, 0.00 +111, -17.47, 43.19, 0.00 +112, -23.69, 65.22, 0.00 +113, -32.00, 97.66, 0.00 +114, -36.22, 122.16, 0.00 +115, -40.69, 151.27, 0.00 +116, -42.86, 175.35, 0.00 +117, -45.56, 205.00, 0.00 +118, -46.38, 229.45, 0.00 +119, -47.11, 256.41, 0.00 +120, -46.83, 280.77, 0.00 +121, -46.29, 306.36, 0.00 +122, -45.08, 330.26, 0.00 +123, -43.57, 354.56, 0.00 +124, -41.55, 377.28, 0.00 +125, -39.29, 400.30, 0.00 +126, -36.74, 423.08, 0.00 +127, -33.87, 444.73, 0.00 +128, -30.79, 466.24, 0.00 +129, -27.45, 486.37, 0.00 +130, -23.98, 506.62, 0.00 +131, -20.34, 525.93, 0.00 +132, -16.59, 545.08, 0.00 +133, -12.73, 563.34, 0.00 +134, -8.79, 580.87, 0.00 +135, -4.79, 597.60, 0.00 +136, -0.76, 613.87, 0.00 +137, 3.29, 629.26, 0.00 +138, 7.33, 643.22, 0.00 +139, 11.34, 656.71, 0.00 +140, 15.31, 669.24, 0.00 +141, 19.20, 680.50, 0.00 +142, 23.00, 690.79, 0.00 +143, 26.72, 700.41, 0.00 +144, 30.30, 708.54, 0.00 +145, 33.76, 715.60, 0.00 +146, 37.07, 721.53, 0.00 +147, 40.24, 726.68, 0.00 +148, 43.22, 730.29, 0.00 +149, 46.10, 733.75, 0.00 +150, 48.72, 734.79, 0.00 +151, 51.06, 733.35, 0.00 +152, 54.83, 753.18, 0.00 +153, 51.89, 684.30, 0.00 +154, 8.03, 102.02, 0.00 +155, -1.05, -12.88, 0.00 +156, 3.20, 38.01, 0.00 +157, 3.99, 45.94, 0.00 +158, 4.27, 47.80, 0.00 +159, 4.38, 47.80, 0.00 +160, 4.41, 46.91, 0.00 +161, 4.21, 43.76, 0.00 +162, 3.97, 40.34, 0.00 +163, 3.55, 35.36, 0.00 +164, 3.12, 30.39, 0.00 +165, 2.57, 24.58, 0.00 +166, 2.00, 18.81, 0.00 +167, 1.36, 12.58, 0.00 +168, 0.72, 6.55, 0.00 +169, 0.01, 0.07, 0.00 +170, -0.70, -6.10, 0.00 +171, -1.46, -12.60, 0.00 +172, -2.18, -18.54, 0.00 +173, -2.95, -24.73, 0.00 +174, -3.66, -30.30, 0.00 +175, -4.40, -35.94, 0.00 +176, -5.09, -40.98, 0.00 +177, -5.78, -45.96, 0.00 +178, -6.39, -50.22, 0.00 +179, -7.01, -54.42, 0.00 +180, -7.54, -57.90, 0.00 +181, -8.04, -61.14, 0.00 +182, -8.47, -63.70, 0.00 +183, -8.82, -65.68, 0.00 +184, -9.10, -67.17, 0.00 +185, -9.28, -67.93, 0.00 +186, -9.39, -68.16, 0.00 +187, -9.41, -67.78, 0.00 +188, -9.32, -66.65, 0.00 +189, -9.21, -65.43, 0.00 +190, -8.95, -63.24, 0.00 +191, -8.66, -60.83, 0.00 +192, -8.20, -57.34, 0.00 +193, -7.65, -53.27, 0.00 +194, -6.97, -48.35, 0.00 +195, -6.12, -42.37, 0.00 +196, -5.16, -35.65, 0.00 +197, -4.06, -27.97, 0.00 +198, -2.82, -19.43, 0.00 diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index 017aaca5bf03..d9968f91b917 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -93,7 +93,7 @@ def main(): channel.cfg_dir = "euler/channel" channel.cfg_file = "inv_channel_RK.cfg" channel.test_iter = 10 - channel.test_vals = [-2.215160, 3.327978, 0.064048, 0.163516] + channel.test_vals = [-1.969337, 3.565024, 0.000246, 0.160624] test_list.append(channel) # NACA0012 @@ -101,7 +101,7 @@ def main(): naca0012.cfg_dir = "euler/naca0012" naca0012.cfg_file = "inv_NACA0012_Roe.cfg" naca0012.test_iter = 20 - naca0012.test_vals = [-4.256408, -3.795038, 0.306510, 0.024657] + naca0012.test_vals = [-4.507695, -3.938772, 0.297705, 0.025422] test_list.append(naca0012) # Supersonic wedge @@ -109,7 +109,7 @@ def main(): wedge.cfg_dir = "euler/wedge" wedge.cfg_file = "inv_wedge_HLLC.cfg" wedge.test_iter = 20 - wedge.test_vals = [-3.609826, 2.113544, -0.249533, 0.043953] + wedge.test_vals = [-3.703839, 2.020469, -0.249531, 0.043953] test_list.append(wedge) # ONERA M6 Wing @@ -117,7 +117,7 @@ def main(): oneram6.cfg_dir = "euler/oneram6" oneram6.cfg_file = "inv_ONERAM6.cfg" oneram6.test_iter = 10 - oneram6.test_vals = [-11.512881, -10.980703, 0.280800, 0.008623] + oneram6.test_vals = [-11.513859, -10.984761, 0.280800, 0.008623] oneram6.timeout = 9600 test_list.append(oneram6) @@ -126,7 +126,7 @@ def main(): fixedCL_naca0012.cfg_dir = "fixed_cl/naca0012" fixedCL_naca0012.cfg_file = "inv_NACA0012.cfg" fixedCL_naca0012.test_iter = 10 - fixedCL_naca0012.test_vals = [-3.920501, 1.594999, 0.300907, 0.019465] + fixedCL_naca0012.test_vals = [-3.962510, 1.570618, 0.301016, 0.019477] test_list.append(fixedCL_naca0012) # Polar sweep of the inviscid NACA0012 @@ -135,7 +135,7 @@ def main(): polar_naca0012.cfg_file = "inv_NACA0012.cfg" polar_naca0012.polar = True polar_naca0012.test_iter = 10 - polar_naca0012.test_vals = [-1.196985, 4.245790, 0.010492, 0.070124] + polar_naca0012.test_vals = [-1.278858, 4.165254, 0.004410, 0.083785] polar_naca0012.test_vals_aarch64 = [-1.063447, 4.401847, 0.000291, 0.031696] polar_naca0012.command = TestCase.Command(exec = "compute_polar.py", param = "-n 1 -i 11") # flaky test on arm64 @@ -166,7 +166,7 @@ def main(): flatplate.cfg_dir = "navierstokes/flatplate" flatplate.cfg_file = "lam_flatplate.cfg" flatplate.test_iter = 20 - flatplate.test_vals = [-5.448267, 0.027571, 0.002633, 0.015619, 2.361200, -2.345600, 0.000000, 0.000000] + flatplate.test_vals = [-5.492595, -0.012547, 0.002524, 0.011875, 2.361500, -2.349600, 0.000000, 0.000000] test_list.append(flatplate) # Laminar cylinder (steady) @@ -174,7 +174,7 @@ def main(): cylinder.cfg_dir = "navierstokes/cylinder" cylinder.cfg_file = "lam_cylinder.cfg" cylinder.test_iter = 25 - cylinder.test_vals = [-8.503813, -3.012051, 0.260768, 1.697376, 0.000000] + cylinder.test_vals = [-8.480930, -3.006218, -0.028387, 1.634020, 0.000000] test_list.append(cylinder) # Laminar cylinder (low Mach correction) @@ -182,7 +182,7 @@ def main(): cylinder_lowmach.cfg_dir = "navierstokes/cylinder" cylinder_lowmach.cfg_file = "cylinder_lowmach.cfg" cylinder_lowmach.test_iter = 25 - cylinder_lowmach.test_vals = [-6.604626, -1.142842, -0.080684, -80.137778, 0.000000] + cylinder_lowmach.test_vals = [-6.447834, -0.986069, 0.825992, 65.209800, 0.000000] test_list.append(cylinder_lowmach) # 2D Poiseuille flow (body force driven with periodic inlet / outlet) @@ -198,7 +198,7 @@ def main(): poiseuille_profile.cfg_dir = "navierstokes/poiseuille" poiseuille_profile.cfg_file = "profile_poiseuille.cfg" poiseuille_profile.test_iter = 10 - poiseuille_profile.test_vals = [-12.004417, -7.544595, -0.000000, 2.089953] + poiseuille_profile.test_vals = [-12.003743, -7.573505, -0.000000, 2.089953] poiseuille_profile.test_vals_aarch64 = [-12.009012, -7.262299, -0.000000, 2.089953] #last 4 columns test_list.append(poiseuille_profile) @@ -218,7 +218,7 @@ def main(): rae2822_sa.cfg_dir = "rans/rae2822" rae2822_sa.cfg_file = "turb_SA_RAE2822.cfg" rae2822_sa.test_iter = 20 - rae2822_sa.test_vals = [-2.168944, -5.451519, 0.287676, 0.104861, 0.000000] + rae2822_sa.test_vals = [-2.187684, -5.308480, 0.389353, 0.077079, 0.000000] test_list.append(rae2822_sa) # RAE2822 SST @@ -226,7 +226,7 @@ def main(): rae2822_sst.cfg_dir = "rans/rae2822" rae2822_sst.cfg_file = "turb_SST_RAE2822.cfg" rae2822_sst.test_iter = 20 - rae2822_sst.test_vals = [-1.028398, 5.868068, 0.277703, 0.093381, 0.000000] + rae2822_sst.test_vals = [-1.028239, 5.864396, 0.357218, 0.074732, 0.000000] test_list.append(rae2822_sst) # RAE2822 SST_SUST @@ -234,7 +234,7 @@ def main(): rae2822_sst_sust.cfg_dir = "rans/rae2822" rae2822_sst_sust.cfg_file = "turb_SST_SUST_RAE2822.cfg" rae2822_sst_sust.test_iter = 20 - rae2822_sst_sust.test_vals = [-2.536320, 5.868058, 0.277703, 0.093381] + rae2822_sst_sust.test_vals = [-2.487553, 5.864385, 0.357218, 0.074732] test_list.append(rae2822_sst_sust) # Flat plate @@ -242,7 +242,7 @@ def main(): turb_flatplate.cfg_dir = "rans/flatplate" turb_flatplate.cfg_file = "turb_SA_flatplate.cfg" turb_flatplate.test_iter = 20 - turb_flatplate.test_vals = [-4.939159, -7.469363, -0.187651, 0.015894] + turb_flatplate.test_vals = [-4.958238, -7.438031, -0.187473, 0.015059] test_list.append(turb_flatplate) # FLAT PLATE, WALL FUNCTIONS, COMPRESSIBLE SST @@ -359,7 +359,7 @@ def main(): turb_naca0012_sst_restart_mg.cfg_file = "turb_NACA0012_sst_multigrid_restart.cfg" turb_naca0012_sst_restart_mg.test_iter = 50 turb_naca0012_sst_restart_mg.ntest_vals = 5 - turb_naca0012_sst_restart_mg.test_vals = [-6.575448, -5.081421, 0.810883, -0.008836, 0.077960] + turb_naca0012_sst_restart_mg.test_vals = [-6.610290, -5.081422, 0.810881, -0.008844, 0.077940] turb_naca0012_sst_restart_mg.timeout = 3200 turb_naca0012_sst_restart_mg.tol = 0.000001 test_list.append(turb_naca0012_sst_restart_mg) @@ -380,7 +380,7 @@ def main(): inc_euler_naca0012.cfg_dir = "incomp_euler/naca0012" inc_euler_naca0012.cfg_file = "incomp_NACA0012.cfg" inc_euler_naca0012.test_iter = 20 - inc_euler_naca0012.test_vals = [-5.930469, -4.994452, 0.519589, 0.008977] + inc_euler_naca0012.test_vals = [-5.968755, -5.003709, 0.522550, 0.008867] test_list.append(inc_euler_naca0012) # C-D nozzle with pressure inlet and mass flow outlet @@ -388,7 +388,7 @@ def main(): inc_nozzle.cfg_dir = "incomp_euler/nozzle" inc_nozzle.cfg_file = "inv_nozzle.cfg" inc_nozzle.test_iter = 20 - inc_nozzle.test_vals = [-5.550954, -4.854401, -0.027964, 0.120734] + inc_nozzle.test_vals = [-6.247282, -5.460332, -0.019426, 0.126862] test_list.append(inc_nozzle) ############################# @@ -407,7 +407,7 @@ def main(): inc_lam_cylinder.cfg_dir = "incomp_navierstokes/cylinder" inc_lam_cylinder.cfg_file = "incomp_cylinder.cfg" inc_lam_cylinder.test_iter = 10 - inc_lam_cylinder.test_vals = [-4.168180, -3.611108, 0.007850, 4.539924] + inc_lam_cylinder.test_vals = [-4.159153, -3.569142, 0.011445, 4.936802] test_list.append(inc_lam_cylinder) # Buoyancy-driven cavity @@ -586,7 +586,7 @@ def main(): contadj_naca0012.cfg_dir = "cont_adj_euler/naca0012" contadj_naca0012.cfg_file = "inv_NACA0012.cfg" contadj_naca0012.test_iter = 5 - contadj_naca0012.test_vals = [-9.525042, -15.101716, -0.726250, 0.020280] + contadj_naca0012.test_vals = [-9.531049, -15.087710, -0.726250, 0.020280] contadj_naca0012.tol = 0.001 test_list.append(contadj_naca0012) @@ -595,7 +595,7 @@ def main(): contadj_oneram6.cfg_dir = "cont_adj_euler/oneram6" contadj_oneram6.cfg_file = "inv_ONERAM6.cfg" contadj_oneram6.test_iter = 10 - contadj_oneram6.test_vals = [-12.062243, -12.621811, -1.086100, 0.007556] + contadj_oneram6.test_vals = [-12.080232, -12.641294, -1.086100, 0.007556] test_list.append(contadj_oneram6) # Inviscid WEDGE: tests averaged outflow total pressure adjoint @@ -611,7 +611,7 @@ def main(): contadj_fixedCL_naca0012.cfg_dir = "fixed_cl/naca0012" contadj_fixedCL_naca0012.cfg_file = "inv_NACA0012_ContAdj.cfg" contadj_fixedCL_naca0012.test_iter = 100 - contadj_fixedCL_naca0012.test_vals = [1.165313, -4.338177, -0.068991, -0.007568] + contadj_fixedCL_naca0012.test_vals = [1.381080, -4.043374, -0.033478, 0.003350] test_list.append(contadj_fixedCL_naca0012) ################################### @@ -630,7 +630,7 @@ def main(): contadj_ns_cylinder.cfg_dir = "cont_adj_navierstokes/cylinder" contadj_ns_cylinder.cfg_file = "lam_cylinder.cfg" contadj_ns_cylinder.test_iter = 20 - contadj_ns_cylinder.test_vals = [-3.606048, -9.050787, 2.056700, -0.000000] + contadj_ns_cylinder.test_vals = [-3.628790, -9.082444, 2.056700, -0.000000] test_list.append(contadj_ns_cylinder) # Adjoint laminar naca0012 subsonic @@ -674,7 +674,7 @@ def main(): contadj_rans_rae2822.cfg_dir = "cont_adj_rans/rae2822" contadj_rans_rae2822.cfg_file = "turb_SA_RAE2822.cfg" contadj_rans_rae2822.test_iter = 20 - contadj_rans_rae2822.test_vals = [-5.399739, -10.904778, -0.212470, 0.005448] + contadj_rans_rae2822.test_vals = [-5.399819, -10.904997, -0.212470, 0.005448] test_list.append(contadj_rans_rae2822) ############################# @@ -752,7 +752,7 @@ def main(): rot_naca0012.cfg_dir = "rotating/naca0012" rot_naca0012.cfg_file = "rot_NACA0012.cfg" rot_naca0012.test_iter = 25 - rot_naca0012.test_vals = [-1.313205, 4.208222, -0.001635, 0.121312] + rot_naca0012.test_vals = [-1.290464, 4.245388, -0.000518, 0.112880] test_list.append(rot_naca0012) # Lid-driven cavity @@ -760,7 +760,7 @@ def main(): cavity.cfg_dir = "moving_wall/cavity" cavity.cfg_file = "lam_cavity.cfg" cavity.test_iter = 25 - cavity.test_vals = [-8.327173, -2.944600, 0.017813, 0.009045] + cavity.test_vals = [-8.144051, -2.746565, 0.014954, 0.007011] test_list.append(cavity) # Spinning cylinder @@ -768,7 +768,7 @@ def main(): spinning_cylinder.cfg_dir = "moving_wall/spinning_cylinder" spinning_cylinder.cfg_file = "spinning_cylinder.cfg" spinning_cylinder.test_iter = 25 - spinning_cylinder.test_vals = [-7.824549, -2.360413, 1.581109, 1.528894] + spinning_cylinder.test_vals = [-7.543538, -2.078580, 1.956630, 1.859963] test_list.append(spinning_cylinder) ###################################### @@ -789,7 +789,7 @@ def main(): sine_gust.cfg_dir = "gust" sine_gust.cfg_file = "inv_gust_NACA0012.cfg" sine_gust.test_iter = 5 - sine_gust.test_vals = [-1.977498, 3.481817, -0.009957, -0.005021] + sine_gust.test_vals = [-1.977498, 3.481817, -0.010134, -0.004283] sine_gust.unsteady = True test_list.append(sine_gust) @@ -798,7 +798,7 @@ def main(): aeroelastic.cfg_dir = "aeroelastic" aeroelastic.cfg_file = "aeroelastic_NACA64A010.cfg" aeroelastic.test_iter = 2 - aeroelastic.test_vals = [-1.876629, 4.021078, 0.079049, 0.027650, -0.001639, -0.000129, -1.140500] + aeroelastic.test_vals = [-1.876626, 4.021083, 0.081596, 0.027684, -0.001638, -0.000130, -1.140510] aeroelastic.unsteady = True test_list.append(aeroelastic) @@ -824,7 +824,7 @@ def main(): unst_pitching_naca64a010_rans.cfg_dir = "unsteady/pitching_naca64a010_rans" unst_pitching_naca64a010_rans.cfg_file = "turb_NACA64A010.cfg" unst_pitching_naca64a010_rans.test_iter = 2 - unst_pitching_naca64a010_rans.test_vals = [-1.299045, -3.951413, 0.012821, 0.008239] + unst_pitching_naca64a010_rans.test_vals = [-1.299045, -3.951366, 0.010128, 0.008245] unst_pitching_naca64a010_rans.unsteady = True test_list.append(unst_pitching_naca64a010_rans) # unsteady pitching NACA64A010, Euler @@ -832,7 +832,7 @@ def main(): unst_pitching_naca64a010_euler.cfg_dir = "unsteady/pitching_naca64a010_euler" unst_pitching_naca64a010_euler.cfg_file = "pitching_NACA64A010.cfg" unst_pitching_naca64a010_euler.test_iter = 2 - unst_pitching_naca64a010_euler.test_vals = [-1.186839, 4.280301, -0.043557, 0.000935] + unst_pitching_naca64a010_euler.test_vals = [-1.186839, 4.280301, -0.039488, 0.000918] unst_pitching_naca64a010_euler.unsteady = True test_list.append(unst_pitching_naca64a010_euler) # unsteady plunging NACA0012, Laminar NS @@ -840,7 +840,7 @@ def main(): unst_plunging_naca0012.cfg_dir = "unsteady/plunging_naca0012" unst_plunging_naca0012.cfg_file = "plunging_NACA0012.cfg" unst_plunging_naca0012.test_iter = 2 - unst_plunging_naca0012.test_vals = [-4.083462, 1.366757, -6.820746, -0.081557] + unst_plunging_naca0012.test_vals = [-4.083462, 1.366757, -6.470859, -0.078768] unst_plunging_naca0012.unsteady = True test_list.append(unst_plunging_naca0012) @@ -849,7 +849,7 @@ def main(): unst_deforming_naca0012.cfg_dir = "disc_adj_euler/naca0012_pitching_def" unst_deforming_naca0012.cfg_file = "inv_NACA0012_pitching_deform.cfg" unst_deforming_naca0012.test_iter = 5 - unst_deforming_naca0012.test_vals = [-3.665254, -3.794011, -3.716829, -3.148509] + unst_deforming_naca0012.test_vals = [-3.665263, -3.794184, -3.716978, -3.148551] unst_deforming_naca0012.unsteady = True test_list.append(unst_deforming_naca0012) @@ -862,7 +862,7 @@ def main(): ls89_sa.cfg_dir = "nicf/LS89" ls89_sa.cfg_file = "turb_SA_PR.cfg" ls89_sa.test_iter = 20 - ls89_sa.test_vals = [-5.241488, -13.580472, 0.188420, 0.414010] + ls89_sa.test_vals = [-5.069399, -13.403603, 0.180485, 0.429457] test_list.append(ls89_sa) # Rarefaction shock wave edge_VW @@ -870,7 +870,7 @@ def main(): edge_VW.cfg_dir = "nicf/edge" edge_VW.cfg_file = "edge_VW.cfg" edge_VW.test_iter = 20 - edge_VW.test_vals = [-2.656646, 3.544572, -0.000020, 0.000000] + edge_VW.test_vals = [-2.851854, 3.349325, -0.000009, 0.000000] test_list.append(edge_VW) # Rarefaction shock wave edge_PPR @@ -878,7 +878,7 @@ def main(): edge_PPR.cfg_dir = "nicf/edge" edge_PPR.cfg_file = "edge_PPR.cfg" edge_PPR.test_iter = 20 - edge_PPR.test_vals = [-12.155248, -5.996433, -0.000034, 0] + edge_PPR.test_vals = [-12.476580, -6.287745, -0.000034, 0.000000] test_list.append(edge_PPR) @@ -1011,7 +1011,7 @@ def main(): bars_SST_2D.cfg_dir = "sliding_interface/bars_SST_2D" bars_SST_2D.cfg_file = "bars.cfg" bars_SST_2D.test_iter = 13 - bars_SST_2D.test_vals = [13.000000, -0.473009, -1.562179] + bars_SST_2D.test_vals = [13.000000, -0.397243, -1.461873] bars_SST_2D.multizone = True test_list.append(bars_SST_2D) @@ -1108,7 +1108,7 @@ def main(): airfoilRBF.cfg_dir = "fea_fsi/Airfoil_RBF" airfoilRBF.cfg_file = "config.cfg" airfoilRBF.test_iter = 1 - airfoilRBF.test_vals = [1.000000, 0.184123, -3.375352] + airfoilRBF.test_vals = [1.000000, 0.028165, -3.530075] airfoilRBF.tol = 0.0001 airfoilRBF.multizone = True test_list.append(airfoilRBF) @@ -1515,7 +1515,7 @@ def main(): opt_multiobj1surf_py.cfg_dir = "optimization_euler/multiobjective_wedge" opt_multiobj1surf_py.cfg_file = "inv_wedge_ROE_multiobj_1surf.cfg" opt_multiobj1surf_py.test_iter = 1 - opt_multiobj1surf_py.test_vals = [1.000000, 1.000000, 36.699910, 5.780304] + opt_multiobj1surf_py.test_vals = [1.000000, 1.000000, 36.670510, 5.750360] opt_multiobj1surf_py.command = TestCase.Command(exec = "shape_optimization.py", param = "-g CONTINUOUS_ADJOINT -f") opt_multiobj1surf_py.timeout = 1600 opt_multiobj1surf_py.tol = 0.00001 @@ -1545,7 +1545,7 @@ def main(): pywrapper_naca0012.cfg_dir = "euler/naca0012" pywrapper_naca0012.cfg_file = "inv_NACA0012_Roe.cfg" pywrapper_naca0012.test_iter = 20 - pywrapper_naca0012.test_vals = [-4.256408, -3.795038, 0.306510, 0.024657] + pywrapper_naca0012.test_vals = [-4.507695, -3.938772, 0.297705, 0.025422] pywrapper_naca0012.command = TestCase.Command(exec = "SU2_CFD.py", param = "-f") pywrapper_naca0012.timeout = 1600 pywrapper_naca0012.tol = 0.00001 @@ -1601,7 +1601,7 @@ def main(): pywrapper_unsteadyCHT.cfg_dir = "py_wrapper/flatPlate_unsteady_CHT" pywrapper_unsteadyCHT.cfg_file = "unsteady_CHT_FlatPlate_Conf.cfg" pywrapper_unsteadyCHT.test_iter = 5 - pywrapper_unsteadyCHT.test_vals = [-1.614168, 2.259972, 0.000757, 0.113614] + pywrapper_unsteadyCHT.test_vals = [-1.614168, 2.260078, -0.020704, 0.172871] pywrapper_unsteadyCHT.command = TestCase.Command(exec = "python", param = "launch_unsteady_CHT_FlatPlate.py -f") pywrapper_unsteadyCHT.timeout = 1600 pywrapper_unsteadyCHT.tol = 0.00001 diff --git a/TestCases/serial_regression_AD.py b/TestCases/serial_regression_AD.py index 6d0d424251ad..baa6e114700f 100644 --- a/TestCases/serial_regression_AD.py +++ b/TestCases/serial_regression_AD.py @@ -108,7 +108,7 @@ def main(): discadj_incomp_NACA0012.cfg_dir = "disc_adj_incomp_euler/naca0012" discadj_incomp_NACA0012.cfg_file = "incomp_NACA0012_disc.cfg" discadj_incomp_NACA0012.test_iter = 20 - discadj_incomp_NACA0012.test_vals = [20.000000, -3.338269, -2.490047, 0.000000] + discadj_incomp_NACA0012.test_vals = [20.000000, -3.122293, -2.287328, 0.000000] test_list.append(discadj_incomp_NACA0012) ##################################### @@ -158,7 +158,7 @@ def main(): discadj_pitchingNACA0012.cfg_dir = "disc_adj_euler/naca0012_pitching" discadj_pitchingNACA0012.cfg_file = "inv_NACA0012_pitching.cfg" discadj_pitchingNACA0012.test_iter = 4 - discadj_pitchingNACA0012.test_vals = [-1.130532, -1.575494, -0.005113, 0.000007] + discadj_pitchingNACA0012.test_vals = [-1.049761, -1.501951, -0.004853, 0.000013] discadj_pitchingNACA0012.unsteady = True test_list.append(discadj_pitchingNACA0012) @@ -167,7 +167,7 @@ def main(): unst_deforming_naca0012.cfg_dir = "disc_adj_euler/naca0012_pitching_def" unst_deforming_naca0012.cfg_file = "inv_NACA0012_pitching_deform_ad.cfg" unst_deforming_naca0012.test_iter = 4 - unst_deforming_naca0012.test_vals = [-1.885032, -1.775564, 3994.600000, 0.000002] + unst_deforming_naca0012.test_vals = [-1.885816, -1.781193, 3920.300000, 0.000003] unst_deforming_naca0012.unsteady = True test_list.append(unst_deforming_naca0012) diff --git a/TestCases/tutorials.py b/TestCases/tutorials.py index 4ab5df41ed10..60680004487d 100644 --- a/TestCases/tutorials.py +++ b/TestCases/tutorials.py @@ -175,7 +175,7 @@ def main(): tutorial_inv_bump.cfg_dir = "../Tutorials/compressible_flow/Inviscid_Bump" tutorial_inv_bump.cfg_file = "inv_channel.cfg" tutorial_inv_bump.test_iter = 0 - tutorial_inv_bump.test_vals = [-1.437425, 4.075857, 0.043691, 0.034241] + tutorial_inv_bump.test_vals = [-1.437425, 4.075857, 0.080374, 0.012019] test_list.append(tutorial_inv_bump) # Inviscid Wedge @@ -192,7 +192,7 @@ def main(): tutorial_inv_onera.cfg_dir = "../Tutorials/compressible_flow/Inviscid_ONERAM6" tutorial_inv_onera.cfg_file = "inv_ONERAM6.cfg" tutorial_inv_onera.test_iter = 0 - tutorial_inv_onera.test_vals = [-5.204928, -4.597762, 0.255680, 0.087977] + tutorial_inv_onera.test_vals = [-5.204928, -4.597762, 0.261167, 0.084096] tutorial_inv_onera.no_restart = True test_list.append(tutorial_inv_onera) @@ -201,7 +201,7 @@ def main(): tutorial_lam_cylinder.cfg_dir = "../Tutorials/compressible_flow/Laminar_Cylinder" tutorial_lam_cylinder.cfg_file = "lam_cylinder.cfg" tutorial_lam_cylinder.test_iter = 0 - tutorial_lam_cylinder.test_vals = [-6.162141, -0.699617, -0.047729, 43.442977] + tutorial_lam_cylinder.test_vals = [-6.162141, -0.699617, -0.124663, 31.721714] tutorial_lam_cylinder.no_restart = True test_list.append(tutorial_lam_cylinder) @@ -323,7 +323,7 @@ def main(): tutorial_design_inv_naca0012.cfg_dir = "../Tutorials/design/Inviscid_2D_Unconstrained_NACA0012" tutorial_design_inv_naca0012.cfg_file = "inv_NACA0012_basic.cfg" tutorial_design_inv_naca0012.test_iter = 0 - tutorial_design_inv_naca0012.test_vals = [-3.585391, -2.989014, 0.164248, 0.228824] + tutorial_design_inv_naca0012.test_vals = [-3.585391, -2.989014, 0.169747, 0.235619] tutorial_design_inv_naca0012.no_restart = True test_list.append(tutorial_design_inv_naca0012) diff --git a/config_template.cfg b/config_template.cfg index 6f6d5ee9bcc1..889abb9f6b1f 100644 --- a/config_template.cfg +++ b/config_template.cfg @@ -1682,10 +1682,10 @@ MG_POST_SMOOTH= ( 4, 4, 4, 4 ) MG_CORRECTION_SMOOTH= ( 1, 1, 1, 1 ) % % Damping factor for the residual restriction -MG_DAMP_RESTRICTION= 0.75 +MG_DAMP_RESTRICTION= 0.5 % % Damping factor for the correction prolongation -MG_DAMP_PROLONGATION= 0.75 +MG_DAMP_PROLONGATION= 0.5 % % Enable early exit from multigrid smoothing when the residual RMS drops % below MG_SMOOTH_RES_THRESHOLD * initial_rms (NO, YES) @@ -1693,7 +1693,7 @@ MG_SMOOTH_EARLY_EXIT= YES % % Relative RMS reduction threshold for the multigrid smoothing early exit. % Smoothing stops when current_rms < threshold * initial_rms (default 0.5) -MG_SMOOTH_RES_THRESHOLD= 0.5 +MG_SMOOTH_RES_THRESHOLD= 0.9 % % Smoothing coefficient for the Jacobi correction-prolongation smoother (default 1.25) MG_SMOOTH_COEFF= 1.25 @@ -1707,6 +1707,10 @@ MG_MIN_MESHSIZE= 500 % % Enable agglomeration along implicit lines seeded from viscous walls (NO, YES) MG_IMPLICIT_LINES= NO +% +% Maximum nodes on a wall-normal implicit agglomeration line, including the wall seed. +% Increase to extend the line deeper into the boundary layer (default 20). +MG_IMPLICIT_LINES_MAX_LENGTH= 20 % -------------------------- MESH SMOOTHING -----------------------------% % From 37c50b9a532f7f78923e141599c84e441c5e2580 Mon Sep 17 00:00:00 2001 From: Evert Bunschoten <38651601+EvertBunschoten@users.noreply.github.com> Date: Sat, 27 Jun 2026 03:41:34 +0200 Subject: [PATCH 14/61] Discrete adjoint multi-zone for python wrapper (#2787) * Change nested classes to friend classes * Include discrete adjoint multizone driver in python wrapper * Re-define AdjointProduct and Identity as template classes * address comments --------- Co-authored-by: Pedro Gomes --- .../drivers/CDiscAdjMultizoneDriver.hpp | 40 ++++--------------- SU2_CFD/include/drivers/CDriver.hpp | 6 +-- .../src/drivers/CDiscAdjMultizoneDriver.cpp | 33 +++++++++++++++ SU2_PY/pySU2/pySU2ad.i | 2 + 4 files changed, 46 insertions(+), 35 deletions(-) diff --git a/SU2_CFD/include/drivers/CDiscAdjMultizoneDriver.hpp b/SU2_CFD/include/drivers/CDiscAdjMultizoneDriver.hpp index 60d040532ca0..5fe657d48f5f 100644 --- a/SU2_CFD/include/drivers/CDiscAdjMultizoneDriver.hpp +++ b/SU2_CFD/include/drivers/CDiscAdjMultizoneDriver.hpp @@ -28,45 +28,21 @@ #pragma once #include "CMultizoneDriver.hpp" #include "../../../Common/include/toolboxes/CQuasiNewtonInvLeastSquares.hpp" -#include "../../../Common/include/linear_algebra/CPreconditioner.hpp" -#include "../../../Common/include/linear_algebra/CMatrixVectorProduct.hpp" #include "../../../Common/include/linear_algebra/CSysSolve.hpp" /*! * \brief Block Gauss-Seidel driver for multizone / multiphysics discrete adjoint problems. * \ingroup DiscAdj */ + class CDiscAdjMultizoneDriver : public CMultizoneDriver { protected: -#ifdef CODI_FORWARD_TYPE - using Scalar = su2double; -#else - using Scalar = passivedouble; -#endif - - class AdjointProduct : public CMatrixVectorProduct { - public: - CDiscAdjMultizoneDriver* const driver; - const unsigned short iZone = 0; - mutable unsigned long iInnerIter = 0; - - AdjointProduct(CDiscAdjMultizoneDriver* d, unsigned short i) : driver(d), iZone(i) {} - - inline void operator()(const CSysVector & u, CSysVector & v) const override { - driver->SetAllSolutions(iZone, true, u); - driver->Iterate(iZone, iInnerIter, true); - driver->GetAllSolutions(iZone, true, v); - v -= u; - ++iInnerIter; - } - }; - - class Identity : public CPreconditioner { - public: - inline bool IsIdentity() const override { return true; } - inline void operator()(const CSysVector & u, CSysVector & v) const override { v = u; } - }; + #ifdef CODI_FORWARD_TYPE + using Scalar = su2double; + #else + using Scalar = passivedouble; + #endif /*! * \brief Kinds of recordings. @@ -161,14 +137,14 @@ class CDiscAdjMultizoneDriver : public CMultizoneDriver { */ void Run() override; -protected: - /*! * \brief Run one inner iteration for a given zone. * \return The result of "monitor". */ bool Iterate(unsigned short iZone, unsigned long iInnerIter, bool KrylovMode = false); +protected: + /*! * \brief Run inner iterations using a Krylov method (GMRES atm). */ diff --git a/SU2_CFD/include/drivers/CDriver.hpp b/SU2_CFD/include/drivers/CDriver.hpp index 0dc9dd2f604d..0328c0b436a2 100644 --- a/SU2_CFD/include/drivers/CDriver.hpp +++ b/SU2_CFD/include/drivers/CDriver.hpp @@ -352,6 +352,7 @@ class CDriver : public CDriverBase { */ void PrintDirectResidual(RECORDING kind_recording); + public: /*! * \brief Set the solution of all solvers (adjoint or primal) in a zone. * \param[in] iZone - Index of the zone. @@ -364,7 +365,7 @@ class CDriver : public CDriverBase { const auto nPoint = geometry_container[iZone][INST_0][MESH_0]->GetnPoint(); for (auto iSol = 0u, offset = 0u; iSol < MAX_SOLS; ++iSol) { auto solver = solver_container[iZone][INST_0][MESH_0][iSol]; - if (!(solver && (solver->GetAdjoint() == adjoint))) continue; + if (!solver || solver->GetAdjoint() != adjoint) continue; for (auto iPoint = 0ul; iPoint < nPoint; ++iPoint) for (auto iVar = 0ul; iVar < solver->GetnVar(); ++iVar) if (!Old) { @@ -395,7 +396,7 @@ class CDriver : public CDriverBase { const auto nPoint = geometry_container[iZone][INST_0][MESH_0]->GetnPoint(); for (auto iSol = 0u, offset = 0u; iSol < MAX_SOLS; ++iSol) { auto solver = solver_container[iZone][INST_0][MESH_0][iSol]; - if (!(solver && (solver->GetAdjoint() == adjoint))) continue; + if (!solver || solver->GetAdjoint() != adjoint) continue; const auto& sol = solver->GetNodes()->GetSolution(); for (auto iPoint = 0ul; iPoint < nPoint; ++iPoint) for (auto iVar = 0ul; iVar < solver->GetnVar(); ++iVar) @@ -419,7 +420,6 @@ class CDriver : public CDriverBase { return nVar; } - public: /*! * \brief Launch the computation for all zones and all physics. */ diff --git a/SU2_CFD/src/drivers/CDiscAdjMultizoneDriver.cpp b/SU2_CFD/src/drivers/CDiscAdjMultizoneDriver.cpp index c9529050da06..14e9d04faa95 100644 --- a/SU2_CFD/src/drivers/CDiscAdjMultizoneDriver.cpp +++ b/SU2_CFD/src/drivers/CDiscAdjMultizoneDriver.cpp @@ -30,6 +30,39 @@ #include "../../include/output/COutputFactory.hpp" #include "../../include/output/COutput.hpp" #include "../../include/iteration/CIterationFactory.hpp" +#include "../../../Common/include/linear_algebra/CPreconditioner.hpp" +#include "../../../Common/include/linear_algebra/CMatrixVectorProduct.hpp" + +namespace { +#ifdef CODI_FORWARD_TYPE + using Scalar = su2double; +#else + using Scalar = passivedouble; +#endif + +class AdjointProduct : public CMatrixVectorProduct { +public: + CDiscAdjMultizoneDriver* const driver; + const unsigned short iZone = 0; + mutable unsigned long iInnerIter = 0; + + AdjointProduct(CDiscAdjMultizoneDriver* d, unsigned short i) : driver(d), iZone(i) {} + + inline void operator()(const CSysVector& u, CSysVector& v) const override { + driver->SetAllSolutions(iZone, true, u); + driver->Iterate(iZone, iInnerIter, true); + driver->GetAllSolutions(iZone, true, v); + v -= u; + ++iInnerIter; + } +}; + +class Identity : public CPreconditioner { +public: + inline bool IsIdentity() const override { return true; } + inline void operator()(const CSysVector& u, CSysVector& v) const override { v = u; } +}; +} // namespace CDiscAdjMultizoneDriver::CDiscAdjMultizoneDriver(char* confFile, unsigned short val_nZone, diff --git a/SU2_PY/pySU2/pySU2ad.i b/SU2_PY/pySU2/pySU2ad.i index 7959e06231d8..e3394aa5a0e8 100644 --- a/SU2_PY/pySU2/pySU2ad.i +++ b/SU2_PY/pySU2/pySU2ad.i @@ -39,6 +39,7 @@ threads="1" %{ #include "../../Common/include/containers/CPyWrapperMatrixView.hpp" #include "../../SU2_CFD/include/drivers/CDiscAdjSinglezoneDriver.hpp" +#include "../../SU2_CFD/include/drivers/CDiscAdjMultizoneDriver.hpp" #include "../../SU2_CFD/include/drivers/CDriver.hpp" #include "../../SU2_CFD/include/drivers/CDriverBase.hpp" #include "../../SU2_CFD/include/drivers/CMultizoneDriver.hpp" @@ -98,4 +99,5 @@ const unsigned int ZONE_1 = 1; /*!< \brief Definition of the first grid domain. %include "../../SU2_CFD/include/drivers/CSinglezoneDriver.hpp" %include "../../SU2_CFD/include/drivers/CMultizoneDriver.hpp" %include "../../SU2_CFD/include/drivers/CDiscAdjSinglezoneDriver.hpp" +%include "../../SU2_CFD/include/drivers/CDiscAdjMultizoneDriver.hpp" %include "../../SU2_DEF/include/drivers/CDiscAdjDeformationDriver.hpp" From 9103b51f00e45cd681440dba25ef388702cbcfd5 Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Sun, 28 Jun 2026 13:41:55 -0700 Subject: [PATCH 15/61] Do not compute vorticity when it's not necessary (#2835) * do not compute vorticity when it's not necessary * fix * fix restarts --- SU2_CFD/include/solvers/CFVMFlowSolverBase.inl | 4 ++-- SU2_CFD/src/iteration/CDiscAdjFluidIteration.cpp | 2 +- SU2_CFD/src/solvers/CIncNSSolver.cpp | 5 ++--- SU2_CFD/src/solvers/CNEMONSSolver.cpp | 2 +- SU2_CFD/src/solvers/CNSSolver.cpp | 6 +++--- SU2_CFD/src/solvers/CRadSolver.cpp | 2 +- SU2_CFD/src/solvers/CSolverFactory.cpp | 10 +++++----- SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp | 4 ++-- SU2_CFD/src/solvers/CSpeciesSolver.cpp | 6 +++--- SU2_CFD/src/solvers/CTransLMSolver.cpp | 5 ++--- SU2_CFD/src/solvers/CTurbSASolver.cpp | 1 - SU2_CFD/src/solvers/CTurbSSTSolver.cpp | 1 - SU2_CFD/src/solvers/CTurbSolver.cpp | 4 ++-- 13 files changed, 24 insertions(+), 28 deletions(-) diff --git a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl index 05ec25a0e56e..8339bb465a7f 100644 --- a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl +++ b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl @@ -1022,7 +1022,7 @@ void CFVMFlowSolverBase::LoadRestart_impl(CGeometry **geometry, CSolver ** * species solver does all the Pre-/Postprocessing. ---*/ if (config->GetKind_Turb_Model() == TURB_MODEL::NONE && config->GetKind_Species_Model() == SPECIES_MODEL::NONE) { - solver[MESH_0][FLOW_SOL]->Preprocessing(geometry[MESH_0], solver[MESH_0], config, MESH_0, NO_RK_ITER, RUNTIME_FLOW_SYS, false); + solver[MESH_0][FLOW_SOL]->Preprocessing(geometry[MESH_0], solver[MESH_0], config, MESH_0, NO_RK_ITER, RUNTIME_FLOW_SYS, true); } /*--- Interpolate the solution down to the coarse multigrid levels ---*/ @@ -1035,7 +1035,7 @@ void CFVMFlowSolverBase::LoadRestart_impl(CGeometry **geometry, CSolver ** if (config->GetKind_Turb_Model() == TURB_MODEL::NONE && config->GetKind_Species_Model() == SPECIES_MODEL::NONE) { - solver[iMesh][FLOW_SOL]->Preprocessing(geometry[iMesh], solver[iMesh], config, iMesh, NO_RK_ITER, RUNTIME_FLOW_SYS, false); + solver[iMesh][FLOW_SOL]->Preprocessing(geometry[iMesh], solver[iMesh], config, iMesh, NO_RK_ITER, RUNTIME_FLOW_SYS, true); } } diff --git a/SU2_CFD/src/iteration/CDiscAdjFluidIteration.cpp b/SU2_CFD/src/iteration/CDiscAdjFluidIteration.cpp index 2482c0b4a476..a7f0dc0a8211 100644 --- a/SU2_CFD/src/iteration/CDiscAdjFluidIteration.cpp +++ b/SU2_CFD/src/iteration/CDiscAdjFluidIteration.cpp @@ -307,7 +307,7 @@ void CDiscAdjFluidIteration::LoadUnsteady_Solution(CGeometry**** geometry, CSolv for (auto iMesh = 0u; iMesh <= config[iZone]->GetnMGLevels(); iMesh++) { solvers[iMesh][FLOW_SOL]->SetFreeStream_Solution(config[iZone]); solvers[iMesh][FLOW_SOL]->Preprocessing(geometries[iMesh], solvers[iMesh], config[iZone], iMesh, - DirectIter, RUNTIME_FLOW_SYS, false); + DirectIter, RUNTIME_FLOW_SYS, true); if (turbulent) { solvers[iMesh][TURB_SOL]->SetFreeStream_Solution(config[iZone]); solvers[iMesh][TURB_SOL]->Postprocessing(geometries[iMesh], solvers[iMesh], config[iZone], iMesh); diff --git a/SU2_CFD/src/solvers/CIncNSSolver.cpp b/SU2_CFD/src/solvers/CIncNSSolver.cpp index 3c22e6404f07..2d6dd26786dd 100644 --- a/SU2_CFD/src/solvers/CIncNSSolver.cpp +++ b/SU2_CFD/src/solvers/CIncNSSolver.cpp @@ -104,7 +104,7 @@ void CIncNSSolver::Preprocessing(CGeometry *geometry, CSolver **solver_container SetPrimitive_Limiter(geometry, config); } - ComputeVorticityAndStrainMag(*config, geometry, iMesh); + if (Output) ComputeVorticityAndStrainMag(*config, geometry, iMesh); /*--- Compute the TauWall from the wall functions ---*/ @@ -277,7 +277,6 @@ void CIncNSSolver::Compute_Streamwise_Periodic_Recovered_Values(CConfig *config, void CIncNSSolver::Viscous_Residual(unsigned long iEdge, CGeometry *geometry, CSolver **solver_container, CNumerics *numerics, CConfig *config) { - SU2_ZONE_SCOPED const bool energy_multicomponent = config->GetKind_FluidModel() == FLUID_MIXTURE && config->GetEnergy_Equation(); /*--- Contribution to heat flux due to enthalpy diffusion for multicomponent and reacting flows ---*/ @@ -385,7 +384,7 @@ unsigned long CIncNSSolver::SetPrimitive_Variables(CSolver **solver_container, c if (config->GetKind_HybridRANSLES() != NO_HYBRIDRANSLES){ DES_LengthScale = solver_container[TURB_SOL]->GetNodes()->GetDES_LengthScale(iPoint); - LES_Mode = solver_container[TURB_SOL]->GetNodes()->GetLES_Mode(iPoint); + LES_Mode = solver_container[TURB_SOL]->GetNodes()->GetLES_Mode(iPoint); } } diff --git a/SU2_CFD/src/solvers/CNEMONSSolver.cpp b/SU2_CFD/src/solvers/CNEMONSSolver.cpp index 4ad43f4c0902..b3578a0d554d 100644 --- a/SU2_CFD/src/solvers/CNEMONSSolver.cpp +++ b/SU2_CFD/src/solvers/CNEMONSSolver.cpp @@ -100,7 +100,7 @@ void CNEMONSSolver::Preprocessing(CGeometry *geometry, CSolver **solver_containe /*--- Compute vorticity and strain mag. ---*/ - ComputeVorticityAndStrainMag(*config, geometry, iMesh); + if (Output) ComputeVorticityAndStrainMag(*config, geometry, iMesh); /*--- Compute the TauWall from the wall functions ---*/ diff --git a/SU2_CFD/src/solvers/CNSSolver.cpp b/SU2_CFD/src/solvers/CNSSolver.cpp index 36baaea473b7..367d7db04bbc 100644 --- a/SU2_CFD/src/solvers/CNSSolver.cpp +++ b/SU2_CFD/src/solvers/CNSSolver.cpp @@ -117,7 +117,9 @@ void CNSSolver::Preprocessing(CGeometry *geometry, CSolver **solver_container, C SetPrimitive_Limiter(geometry, config); } - ComputeVorticityAndStrainMag(*config, geometry, iMesh); + if (Output || config->GetVorticityConfinement()) { + ComputeVorticityAndStrainMag(*config, geometry, iMesh); + } /*--- Compute the TauWall from the wall functions ---*/ @@ -178,8 +180,6 @@ unsigned long CNSSolver::SetPrimitive_Variables(CSolver **solver_container, cons void CNSSolver::Viscous_Residual(unsigned long iEdge, CGeometry *geometry, CSolver **solver_container, CNumerics *numerics, CConfig *config) { - SU2_ZONE_SCOPED - Viscous_Residual_impl(iEdge, geometry, solver_container, numerics, config); } diff --git a/SU2_CFD/src/solvers/CRadSolver.cpp b/SU2_CFD/src/solvers/CRadSolver.cpp index 12ef7f20739c..abe8b901f96d 100644 --- a/SU2_CFD/src/solvers/CRadSolver.cpp +++ b/SU2_CFD/src/solvers/CRadSolver.cpp @@ -159,7 +159,7 @@ void CRadSolver::LoadRestart(CGeometry **geometry, CSolver ***solver, CConfig *c solver[MESH_0][RAD_SOL]->CompleteComms(geometry[MESH_0], config, MPI_QUANTITIES::SOLUTION); /*--- Preprocess the fluid solver to compute the primitive variables ---*/ - solver[MESH_0][FLOW_SOL]->Preprocessing(geometry[MESH_0], solver[MESH_0], config, MESH_0, NO_RK_ITER, RUNTIME_FLOW_SYS, false); + solver[MESH_0][FLOW_SOL]->Preprocessing(geometry[MESH_0], solver[MESH_0], config, MESH_0, NO_RK_ITER, RUNTIME_FLOW_SYS, true); /*--- Postprocess the radiation solver to compute the source term that goes into the fluid equations ---*/ solver[MESH_0][RAD_SOL]->Postprocessing(geometry[MESH_0], solver[MESH_0], config, MESH_0); diff --git a/SU2_CFD/src/solvers/CSolverFactory.cpp b/SU2_CFD/src/solvers/CSolverFactory.cpp index a01590c6ab27..ee798c1384b4 100644 --- a/SU2_CFD/src/solvers/CSolverFactory.cpp +++ b/SU2_CFD/src/solvers/CSolverFactory.cpp @@ -351,14 +351,14 @@ CSolver* CSolverFactory::CreateTurbSolver(TURB_MODEL kindTurbModel, CSolver **so switch (TurbModelFamily(kindTurbModel)) { case TURB_FAMILY::SA: turbSolver = new CTurbSASolver(geometry, config, iMGLevel, solver[FLOW_SOL]->GetFluidModel()); - solver[FLOW_SOL]->Preprocessing(geometry, solver, config, iMGLevel, NO_RK_ITER, RUNTIME_FLOW_SYS, false); + solver[FLOW_SOL]->Preprocessing(geometry, solver, config, iMGLevel, NO_RK_ITER, RUNTIME_FLOW_SYS, true); turbSolver->Postprocessing(geometry, solver, config, iMGLevel); break; case TURB_FAMILY::KW: turbSolver = new CTurbSSTSolver(geometry, config, iMGLevel); - solver[FLOW_SOL]->Preprocessing(geometry, solver, config, iMGLevel, NO_RK_ITER, RUNTIME_FLOW_SYS, false); + solver[FLOW_SOL]->Preprocessing(geometry, solver, config, iMGLevel, NO_RK_ITER, RUNTIME_FLOW_SYS, true); turbSolver->Postprocessing(geometry, solver, config, iMGLevel); - solver[FLOW_SOL]->Preprocessing(geometry, solver, config, iMGLevel, NO_RK_ITER, RUNTIME_FLOW_SYS, false); + solver[FLOW_SOL]->Preprocessing(geometry, solver, config, iMGLevel, NO_RK_ITER, RUNTIME_FLOW_SYS, true); break; case TURB_FAMILY::NONE: SU2_MPI::Error("Trying to create TurbSolver container but TURB_MODEL=NONE.", CURRENT_FUNCTION); @@ -386,9 +386,9 @@ CSolver* CSolverFactory::CreateTransSolver(TURB_TRANS_MODEL kindTransModel, CSol switch (kindTransModel) { case TURB_TRANS_MODEL::LM : transSolver = new CTransLMSolver(geometry, config, iMGLevel); - solver[FLOW_SOL]->Preprocessing(geometry, solver, config, iMGLevel, NO_RK_ITER, RUNTIME_FLOW_SYS, false); + solver[FLOW_SOL]->Preprocessing(geometry, solver, config, iMGLevel, NO_RK_ITER, RUNTIME_FLOW_SYS, true); transSolver->Postprocessing(geometry, solver, config, iMGLevel); - solver[FLOW_SOL]->Preprocessing(geometry, solver, config, iMGLevel, NO_RK_ITER, RUNTIME_FLOW_SYS, false); + solver[FLOW_SOL]->Preprocessing(geometry, solver, config, iMGLevel, NO_RK_ITER, RUNTIME_FLOW_SYS, true); break; case TURB_TRANS_MODEL::NONE: break; diff --git a/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp b/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp index 8f518e2d9490..82bf04a0125e 100644 --- a/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp +++ b/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp @@ -291,7 +291,7 @@ void CSpeciesFlameletSolver::SetInitialCondition(CGeometry** geometry, CSolver** solver_container[i_mesh][FLOW_SOL]->CompleteComms(geometry[i_mesh], config, MPI_QUANTITIES::SOLUTION); solver_container[i_mesh][FLOW_SOL]->Preprocessing(geometry[i_mesh], solver_container[i_mesh], config, i_mesh, - NO_RK_ITER, RUNTIME_FLOW_SYS, false); + NO_RK_ITER, RUNTIME_FLOW_SYS, true); } /* --- Sum up some global counters over processes. --- */ @@ -673,7 +673,7 @@ unsigned long CSpeciesFlameletSolver::SetPreferentialDiffusionScalars(CFluidMode void CSpeciesFlameletSolver::Viscous_Residual(const unsigned long iEdge, const CGeometry* geometry, CSolver** solver_container, CNumerics* numerics, const CConfig* config) { - SU2_ZONE_SCOPED + /*--- Overloaded viscous residual method which accounts for preferential diffusion. ---*/ const bool implicit = (config->GetKind_TimeIntScheme() == EULER_IMPLICIT), PreferentialDiffusion = flamelet_config_options.preferential_diffusion; diff --git a/SU2_CFD/src/solvers/CSpeciesSolver.cpp b/SU2_CFD/src/solvers/CSpeciesSolver.cpp index 0d1286671275..70245555eb64 100644 --- a/SU2_CFD/src/solvers/CSpeciesSolver.cpp +++ b/SU2_CFD/src/solvers/CSpeciesSolver.cpp @@ -269,7 +269,7 @@ void CSpeciesSolver::LoadRestart(CGeometry** geometry, CSolver*** solver, CConfi // Flow-Pre computes/sets mixture properties solver[MESH_0][FLOW_SOL]->Preprocessing(geometry[MESH_0], solver[MESH_0], config, MESH_0, NO_RK_ITER, - RUNTIME_FLOW_SYS, false); + RUNTIME_FLOW_SYS, true); // Update eddy-visc which needs correct mixture density and mixture lam-visc. Note that after this, another Flow-Pre // at the start of the Iteration sets the updated eddy-visc into the Flow-Solvers Primitives. if (config->GetKind_Turb_Model() != TURB_MODEL::NONE) @@ -287,7 +287,7 @@ void CSpeciesSolver::LoadRestart(CGeometry** geometry, CSolver*** solver, CConfi solver[iMesh][SPECIES_SOL]->CompleteComms(geometry[iMesh], config, MPI_QUANTITIES::SOLUTION); solver[iMesh][FLOW_SOL]->Preprocessing(geometry[iMesh], solver[iMesh], config, iMesh, NO_RK_ITER, RUNTIME_FLOW_SYS, - false); + true); if (config->GetKind_Turb_Model() != TURB_MODEL::NONE) solver[iMesh][TURB_SOL]->Postprocessing(geometry[MESH_0], solver[MESH_0], config, MESH_0); @@ -334,7 +334,7 @@ void CSpeciesSolver::Preprocessing(CGeometry* geometry, CSolver** solver_contain void CSpeciesSolver::Viscous_Residual(const unsigned long iEdge, const CGeometry* geometry, CSolver** solver_container, CNumerics* numerics, const CConfig* config) { - SU2_ZONE_SCOPED + /*--- Define an object to set solver specific numerics contribution. ---*/ auto SolverSpecificNumerics = [&](unsigned long iPoint, unsigned long jPoint) { /*--- Mass diffusivity coefficients. ---*/ diff --git a/SU2_CFD/src/solvers/CTransLMSolver.cpp b/SU2_CFD/src/solvers/CTransLMSolver.cpp index 87ac5c58db38..bb8a53cda125 100644 --- a/SU2_CFD/src/solvers/CTransLMSolver.cpp +++ b/SU2_CFD/src/solvers/CTransLMSolver.cpp @@ -268,7 +268,6 @@ void CTransLMSolver::Postprocessing(CGeometry *geometry, CSolver **solver_contai void CTransLMSolver::Viscous_Residual(const unsigned long iEdge, const CGeometry* geometry, CSolver** solver_container, CNumerics* numerics, const CConfig* config) { - SU2_ZONE_SCOPED /*--- Define an object to set solver specific numerics contribution. ---*/ @@ -584,7 +583,7 @@ void CTransLMSolver::LoadRestart(CGeometry** geometry, CSolver*** solver, CConfi /*--- For turbulent+species simulations the solver Pre-/Postprocessing is done by the species solver. ---*/ if (config->GetKind_Species_Model() == SPECIES_MODEL::NONE) { solver[MESH_0][FLOW_SOL]->Preprocessing(geometry[MESH_0], solver[MESH_0], config, MESH_0, NO_RK_ITER, - RUNTIME_FLOW_SYS, false); + RUNTIME_FLOW_SYS, true); solver[MESH_0][TURB_SOL]->Postprocessing(geometry[MESH_0], solver[MESH_0], config, MESH_0); solver[MESH_0][TRANS_SOL]->Postprocessing(geometry[MESH_0], solver[MESH_0], config, MESH_0); } @@ -600,7 +599,7 @@ void CTransLMSolver::LoadRestart(CGeometry** geometry, CSolver*** solver, CConfi if (config->GetKind_Species_Model() == SPECIES_MODEL::NONE) { solver[iMesh][FLOW_SOL]->Preprocessing(geometry[iMesh], solver[iMesh], config, iMesh, NO_RK_ITER, RUNTIME_FLOW_SYS, - false); + true); solver[iMesh][TRANS_SOL]->Postprocessing(geometry[iMesh], solver[iMesh], config, iMesh); } } diff --git a/SU2_CFD/src/solvers/CTurbSASolver.cpp b/SU2_CFD/src/solvers/CTurbSASolver.cpp index 7bc05787be17..407756c2b212 100644 --- a/SU2_CFD/src/solvers/CTurbSASolver.cpp +++ b/SU2_CFD/src/solvers/CTurbSASolver.cpp @@ -336,7 +336,6 @@ void CTurbSASolver::Postprocessing(CGeometry *geometry, CSolver **solver_contain void CTurbSASolver::Viscous_Residual(const unsigned long iEdge, const CGeometry* geometry, CSolver** solver_container, CNumerics* numerics, const CConfig* config) { - SU2_ZONE_SCOPED /*--- Define an object to set solver specific numerics contribution. ---*/ auto SolverSpecificNumerics = [&](unsigned long iPoint, unsigned long jPoint) { diff --git a/SU2_CFD/src/solvers/CTurbSSTSolver.cpp b/SU2_CFD/src/solvers/CTurbSSTSolver.cpp index 86f8bc06ad93..579b5f3cf72e 100644 --- a/SU2_CFD/src/solvers/CTurbSSTSolver.cpp +++ b/SU2_CFD/src/solvers/CTurbSSTSolver.cpp @@ -292,7 +292,6 @@ void CTurbSSTSolver::Postprocessing(CGeometry *geometry, CSolver **solver_contai void CTurbSSTSolver::Viscous_Residual(const unsigned long iEdge, const CGeometry* geometry, CSolver** solver_container, CNumerics* numerics, const CConfig* config) { - SU2_ZONE_SCOPED /*--- Define an object to set solver specific numerics contribution. ---*/ auto SolverSpecificNumerics = [&](unsigned long iPoint, unsigned long jPoint) { diff --git a/SU2_CFD/src/solvers/CTurbSolver.cpp b/SU2_CFD/src/solvers/CTurbSolver.cpp index a05a21edf5f1..394c9a0eba42 100644 --- a/SU2_CFD/src/solvers/CTurbSolver.cpp +++ b/SU2_CFD/src/solvers/CTurbSolver.cpp @@ -186,7 +186,7 @@ void CTurbSolver::LoadRestart(CGeometry** geometry, CSolver*** solver, CConfig* /*--- For turbulent+species simulations the solver Pre-/Postprocessing is done by the species solver. ---*/ if (config->GetKind_Species_Model() == SPECIES_MODEL::NONE && config->GetKind_Trans_Model() == TURB_TRANS_MODEL::NONE) { solver[MESH_0][FLOW_SOL]->Preprocessing(geometry[MESH_0], solver[MESH_0], config, MESH_0, NO_RK_ITER, - RUNTIME_FLOW_SYS, false); + RUNTIME_FLOW_SYS, true); solver[MESH_0][TURB_SOL]->Postprocessing(geometry[MESH_0], solver[MESH_0], config, MESH_0); } @@ -200,7 +200,7 @@ void CTurbSolver::LoadRestart(CGeometry** geometry, CSolver*** solver, CConfig* if (config->GetKind_Species_Model() == SPECIES_MODEL::NONE) { solver[iMesh][FLOW_SOL]->Preprocessing(geometry[iMesh], solver[iMesh], config, iMesh, NO_RK_ITER, RUNTIME_FLOW_SYS, - false); + true); solver[iMesh][TURB_SOL]->Postprocessing(geometry[iMesh], solver[iMesh], config, iMesh); } From b5cf039a1b20c79bedbf8b0bd3dfefb1ef9496a1 Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Sat, 4 Jul 2026 08:24:23 -0700 Subject: [PATCH 16/61] Detect non-physical solutions in a statistic way instead of just P or T <= 0 (#2830) * detect temperature outliers * non simd too * fix * fix AD OMP build * avoid zero-ing the whole matrix * update regressions * file diffs --- Common/include/CConfig.hpp | 6 + Common/include/linear_algebra/CSysMatrix.hpp | 15 +- Common/src/CConfig.cpp | 7 +- Common/src/linear_algebra/CSysMatrix.cpp | 26 +- .../numerics_simd/flow/convection/common.hpp | 17 +- .../numerics_simd/flow/convection/upwind.hpp | 17 +- SU2_CFD/include/solvers/CEulerSolver.hpp | 53 ++- .../include/solvers/CFVMFlowSolverBase.hpp | 44 +- .../include/solvers/CFVMFlowSolverBase.inl | 45 +- SU2_CFD/include/solvers/CIncEulerSolver.hpp | 7 - SU2_CFD/include/solvers/CNEMOEulerSolver.hpp | 7 - SU2_CFD/include/solvers/CSolver.hpp | 8 + SU2_CFD/include/variables/CEulerVariable.hpp | 10 + SU2_CFD/src/iteration/CFluidIteration.cpp | 8 +- SU2_CFD/src/solvers/CEulerSolver.cpp | 167 +++++++- SU2_CFD/src/solvers/CIncEulerSolver.cpp | 6 - SU2_CFD/src/solvers/CNEMOEulerSolver.cpp | 6 - SU2_CFD/src/solvers/CSolver.cpp | 7 +- SU2_CFD/src/solvers/CTurbSASolver.cpp | 4 + SU2_CFD/src/variables/CEulerVariable.cpp | 2 + .../naca0012/of_grad_directdiff.dat.ref | 8 +- TestCases/hybrid_regression.py | 92 ++-- TestCases/hybrid_regression_AD.py | 18 +- .../multiple_ffd/naca0012/of_grad_cd.dat.ref | 6 +- .../naca0012/of_grad_directdiff.dat.ref | 6 +- TestCases/parallel_regression.py | 102 ++--- TestCases/parallel_regression_AD.py | 12 +- .../translating_NACA0012/forces_0.csv.ref | 400 +++++++++--------- .../forces_0.csv.ref | 400 +++++++++--------- TestCases/serial_regression.py | 78 ++-- TestCases/serial_regression_AD.py | 2 +- TestCases/tutorials.py | 30 +- TestCases/vandv.py | 14 +- config_template.cfg | 8 + 34 files changed, 904 insertions(+), 734 deletions(-) diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index 7d85083eafbd..15432aadc7f4 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -815,6 +815,7 @@ class CConfig { unsigned short ActDisk_Jump; /*!< \brief Format of the output files. */ unsigned long StartWindowIteration; /*!< \brief Starting Iteration for long time Windowing apporach . */ unsigned short nCFL_AdaptParam; /*!< \brief Number of CFL parameters provided in config. */ + unsigned long outlierMitigationParam[4]; /*!< \brief Parameters of outlier mitigation strategy. */ bool CFL_Adapt; /*!< \brief Use adaptive CFL number. */ bool HB_Precondition; /*!< \brief Flag to turn on harmonic balance source term preconditioning */ su2double RefArea, /*!< \brief Reference area for coefficient computation. */ @@ -1714,6 +1715,11 @@ class CConfig { */ bool GetCFL_Adapt(void) const { return CFL_Adapt; } + /*! + * \brief Get the outlier mitigation parameters. + */ + const unsigned long* GetOutlierMitigationParam() const { return outlierMitigationParam; } + /*! * \brief Get the value of the limits for the sections. * \return Value of the limits for the sections. diff --git a/Common/include/linear_algebra/CSysMatrix.hpp b/Common/include/linear_algebra/CSysMatrix.hpp index ecb26959a06b..a71d31c1c085 100644 --- a/Common/include/linear_algebra/CSysMatrix.hpp +++ b/Common/include/linear_algebra/CSysMatrix.hpp @@ -590,7 +590,7 @@ class CSysMatrix { * \param[in] block_j - Adds to ij, subs from jj. * \param[in] scale - Scale blocks during update (axpy type op). */ - template + template inline void UpdateBlocks(unsigned long iEdge, unsigned long iPoint, unsigned long jPoint, const MatrixType& block_i, const MatrixType& block_j, OtherType scale = 1) { ScalarType *bii, *bij, *bji, *bjj; @@ -601,9 +601,14 @@ class CSysMatrix { for (iVar = 0; iVar < nVar; iVar++) { for (jVar = 0; jVar < nEqn; jVar++) { bii[offset] += PassiveAssign(block_i[iVar][jVar] * scale); - bij[offset] += PassiveAssign(block_j[iVar][jVar] * scale); - bji[offset] -= PassiveAssign(block_i[iVar][jVar] * scale); bjj[offset] -= PassiveAssign(block_j[iVar][jVar] * scale); + if constexpr (OverwriteOffDiag) { + bij[offset] = PassiveAssign(block_j[iVar][jVar] * scale); + bji[offset] = -PassiveAssign(block_i[iVar][jVar] * scale); + } else { + bij[offset] += PassiveAssign(block_j[iVar][jVar] * scale); + bji[offset] -= PassiveAssign(block_i[iVar][jVar] * scale); + } ++offset; } } @@ -615,14 +620,14 @@ class CSysMatrix { template inline void UpdateBlocksSub(unsigned long iEdge, unsigned long iPoint, unsigned long jPoint, const MatrixType& block_i, const MatrixType& block_j) { - UpdateBlocks(iEdge, iPoint, jPoint, block_i, block_j, -1); + UpdateBlocks(iEdge, iPoint, jPoint, block_i, block_j, -1); } /*! * \brief SIMD version, does the update for multiple edges and points. * \note Nothing is updated if the mask is 0. */ - template + template FORCEINLINE void UpdateBlocks(simd::Array iEdge, simd::Array iPoint, simd::Array jPoint, const MatTypeSIMD& block_i, const MatTypeSIMD& block_j, simd::Array mask = 1) { static_assert(MatTypeSIMD::StaticSize, "This method requires static size blocks."); diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index cf137709ea0c..2ae45cf340f8 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -1819,6 +1819,11 @@ void CConfig::SetConfig_Options() { addDoubleOption("CFL_NUMBER", CFLFineGrid, 1.25); /* DESCRIPTION: Max time step in local time stepping simulations */ addDoubleOption("MAX_DELTA_TIME", Max_DeltaTime, 1000000); + /* !\brief OUTLIER_MITIGATION_PARAM + * DESCRIPTION: Parameters of the outlier mitigation strategy: start iteration, update frequency, print frequency, + * and number of standard deviations (N * sigma) to identify outliers statistically. \ingroup Config*/ + outlierMitigationParam[0] = 999999; outlierMitigationParam[1] = 5; outlierMitigationParam[2] = 2; outlierMitigationParam[3] = 5; + addULongArrayOption("OUTLIER_MITIGATION_PARAM", 4, true, outlierMitigationParam); /* DESCRIPTION: Activate The adaptive CFL number. */ addBoolOption("CFL_ADAPT", CFL_Adapt, false); /* !\brief CFL_ADAPT_PARAM @@ -2035,7 +2040,7 @@ void CConfig::SetConfig_Options() { addDoubleOption("MUSCL_KAPPA_FLOW", MUSCL_Kappa_Flow, 0.0); /*!\brief RAMP_MUSCL \n DESCRIPTION: Enable ramping of the MUSCL scheme from 1st to 2nd order using specified method*/ addBoolOption("RAMP_MUSCL", RampMUSCL, false); - /*! brief RAMP_OUTLET_COEFF \n DESCRIPTION: the 1st coeff is the ramp start iteration, + /*! brief RAMP_MUSCL_COEFF \n DESCRIPTION: the 1st coeff is the ramp start iteration, * the 2nd coeff is the iteration update frequenct, 3rd coeff is the total number of iterations */ RampMUSCLParam.rampMUSCLCoeff[0] = 0.0; RampMUSCLParam.rampMUSCLCoeff[1] = 1.0; RampMUSCLParam.rampMUSCLCoeff[2] = 500.0; addULongArrayOption("RAMP_MUSCL_COEFF", 3, false, RampMUSCLParam.rampMUSCLCoeff); diff --git a/Common/src/linear_algebra/CSysMatrix.cpp b/Common/src/linear_algebra/CSysMatrix.cpp index 00ef51c2023a..e90f9e1c7045 100644 --- a/Common/src/linear_algebra/CSysMatrix.cpp +++ b/Common/src/linear_algebra/CSysMatrix.cpp @@ -32,6 +32,20 @@ #include +namespace { +/*--- Helper function to regularize small pivots ---*/ +template +FORCEINLINE void RegularizePivot(ScalarType& pivot, unsigned long row, unsigned long col, const char* context) { + const float eps = 1e-12; + if (std::abs(pivot) < eps) { + pivot = std::copysign(eps, SU2_TYPE::GetValue(pivot)); +#ifndef NDEBUG + std::cout << context << ": Regularized small pivot A(" << row << "," << col << ") to " << pivot << std::endl; +#endif + } +} +} // namespace + template CSysMatrix::CSysMatrix() : rank(SU2_MPI::GetRank()), size(SU2_MPI::GetSize()) { SU2_ZONE_SCOPED @@ -508,18 +522,6 @@ void CSysMatrix::SetValDiagonalZero() { END_SU2_OMP_FOR } -/*--- Helper function to regularize small pivots ---*/ -template -inline void RegularizePivot(ScalarType& pivot, unsigned long row, unsigned long col, const char* context) { - const float eps = 1e-12; - if (std::abs(pivot) < eps) { - pivot = std::copysign(eps, SU2_TYPE::GetValue(pivot)); -#ifndef NDEBUG - std::cout << context << ": Regularized small pivot A(" << row << "," << col << ") to " << pivot << std::endl; -#endif - } -} - template void CSysMatrix::Gauss_Elimination(ScalarType* matrix, ScalarType* vec) const { #ifdef USE_MKL_LAPACK diff --git a/SU2_CFD/include/numerics_simd/flow/convection/common.hpp b/SU2_CFD/include/numerics_simd/flow/convection/common.hpp index 4796f80ce031..f3669d454233 100644 --- a/SU2_CFD/include/numerics_simd/flow/convection/common.hpp +++ b/SU2_CFD/include/numerics_simd/flow/convection/common.hpp @@ -188,6 +188,7 @@ FORCEINLINE void musclEdgeLimited(const Int& iPoint, * \param[in] V1st - Pair of compressible flow primitives for nodes i,j. * \param[in] vector_ij - Distance vector from i to j. * \param[in] solution - Entire solution container (a derived CVariable). + * \param[out] nonPhysical - Signals that the edge is treated as non-physical. * \return Pair of primitive variables. */ template @@ -201,7 +202,8 @@ FORCEINLINE CPair reconstructPrimitives(const Int& iEdge, const LIMITER limiterType, const CPair& V1st, const VectorDbl& vector_ij, - const VariableType& solution) { + const VariableType& solution, + Double& nonPhysical) { static_assert(ReconVarType::nVar <= PrimVarType::nVar); const auto& gradients = solution.GetGradient_Reconstruction(); @@ -262,15 +264,20 @@ FORCEINLINE CPair reconstructPrimitives(const Int& iEdge, const Double neg_sound_speed = enthalpy * (R+1) < 0.5 * v_squared; /*--- Revert to first order if the state is non-physical. ---*/ - Double bad_recon = fmax(neg_p_or_rho, neg_sound_speed); + nonPhysical = fmax(neg_p_or_rho, neg_sound_speed); /*--- Handle SIMD dimensions 1 by 1. ---*/ for (size_t k = 0; k < Double::Size; ++k) { - bad_recon[k] = solution.UpdateNonPhysicalEdgeCounter(iEdge[k], bad_recon[k]); + nonPhysical[k] = solution.UpdateNonPhysicalEdgeCounter(iEdge[k], nonPhysical[k]); + nonPhysical[k] = fmax(nonPhysical[k], + fmax(solution.OutlierMitigation(iPoint[k]), + solution.OutlierMitigation(jPoint[k])) / VariableType::MAX_OUTLIER_MITIGATION); } for (size_t iVar = 0; iVar < ReconVarType::nVar; ++iVar) { - V.i.all(iVar) = bad_recon * V1st.i.all(iVar) + (1-bad_recon) * V.i.all(iVar); - V.j.all(iVar) = bad_recon * V1st.j.all(iVar) + (1-bad_recon) * V.j.all(iVar); + V.i.all(iVar) = nonPhysical * V1st.i.all(iVar) + (1-nonPhysical) * V.i.all(iVar); + V.j.all(iVar) = nonPhysical * V1st.j.all(iVar) + (1-nonPhysical) * V.j.all(iVar); } + } else { + nonPhysical = 0; } return V; } diff --git a/SU2_CFD/include/numerics_simd/flow/convection/upwind.hpp b/SU2_CFD/include/numerics_simd/flow/convection/upwind.hpp index ad958a7e2f02..090fb111332a 100644 --- a/SU2_CFD/include/numerics_simd/flow/convection/upwind.hpp +++ b/SU2_CFD/include/numerics_simd/flow/convection/upwind.hpp @@ -118,8 +118,10 @@ class CUpwindBase : public Base { V1st.j.all = gatherVariables(jPoint, solution.GetPrimitive()); /*--- Recompute density and enthalpy instead of reconstructing. ---*/ + Double nonPhysical; auto V = reconstructPrimitives >( - iEdge, iPoint, jPoint, gamma, gasConst, muscl, umusclKappa, umusclRamp, typeLimiter, V1st, vector_ij, solution); + iEdge, iPoint, jPoint, gamma, gasConst, muscl, umusclKappa, umusclRamp, + typeLimiter, V1st, vector_ij, solution, nonPhysical); /*--- Compute conservative variables. ---*/ @@ -132,8 +134,8 @@ class CUpwindBase : public Base { const auto derived = static_cast(this); VectorDbl flux; MatrixDbl jac_i, jac_j; - derived->finalizeFlux(flux, jac_i, jac_j, implicit, area, unitNormal, - normal, V, U, iPoint, jPoint, solution, geometry); + derived->finalizeFlux(flux, jac_i, jac_j, implicit, area, unitNormal, normal, + V, U, iPoint, jPoint, nonPhysical, solution, geometry); /*--- Add the contributions from the base class (static decorator). ---*/ @@ -199,6 +201,7 @@ class CRoeScheme : public CUpwindBase, Decorator> { const CPair& U, const Int& iPoint, const Int& jPoint, + const Double& nonPhysical, const CEulerVariable& solution, const CGeometry& geometry, Ts&...) const { @@ -227,10 +230,9 @@ class CRoeScheme : public CUpwindBase, Decorator> { /*--- Apply Mavriplis' entropy correction to eigenvalues. ---*/ - Double maxLambda = abs(projVel) + roeAvg.speedSound; - + Double lambdaMin = fmax(entropyFix, nonPhysical) * (abs(projVel) + roeAvg.speedSound); for (size_t iVar = 0; iVar < nVar; ++iVar) { - lambda(iVar) = fmax(abs(lambda(iVar)), entropyFix*maxLambda); + lambda(iVar) = fmax(abs(lambda(iVar)), lambdaMin); } /*--- Inviscid fluxes and Jacobians. ---*/ @@ -348,6 +350,7 @@ class CMSWScheme : public CUpwindBase, Decorator> { const CPair& U, const Int& iPoint, const Int& jPoint, + const Double& nonPhysical, const CEulerVariable& solution, const CGeometry& geometry, Ts&...) const { @@ -358,7 +361,7 @@ class CMSWScheme : public CUpwindBase, Decorator> { const auto sj = gatherVariables(jPoint, solution.GetSensor()); const Double dp = fmax(si, sj) - alpha * 0.06; - const Double w = 0.25 * (1 - sign(dp)) * (1 - exp(-100 * abs(dp))); + const Double w = 0.25 * (1 - sign(dp) * (1 - exp(-100 * abs(dp)))) * (1 - nonPhysical); const Double onemw = 1 - w; CPair> Vweighted; diff --git a/SU2_CFD/include/solvers/CEulerSolver.hpp b/SU2_CFD/include/solvers/CEulerSolver.hpp index 46eed006b049..dc932095bc47 100644 --- a/SU2_CFD/include/solvers/CEulerSolver.hpp +++ b/SU2_CFD/include/solvers/CEulerSolver.hpp @@ -116,6 +116,9 @@ class CEulerSolver : public CFVMFlowSolverBase FluidModel; /*!< \brief fluid model used in the solver. */ + /*!< \brief Variables for outlier detection. */ + su2double MeanTemperature, StdDevTemperature; + /*--- Turbomachinery Solver Variables ---*/ vector AverageFlux; @@ -782,11 +785,17 @@ class CEulerSolver : public CFVMFlowSolverBase - void CompleteImplicitIteration_impl(CGeometry *geometry, CConfig *config) { - - if (compute_ur) ComputeUnderRelaxationFactor(config); - - /*--- Update solution with under-relaxation and communicate it. ---*/ - - if (!config->GetContinuous_Adjoint()) { - SU2_OMP_FOR_STAT(omp_chunk_size) - for (unsigned long iPoint = 0; iPoint < nPointDomain; iPoint++) { - for (unsigned short iVar = 0; iVar < nVar; iVar++) { - nodes->AddSolution(iPoint, iVar, nodes->GetUnderRelaxation(iPoint)*LinSysSol[iPoint*nVar+iVar]); - } - } - END_SU2_OMP_FOR - } - - for (unsigned short iPeriodic = 1; iPeriodic <= config->GetnMarker_Periodic()/2; iPeriodic++) { - InitiatePeriodicComms(geometry, config, iPeriodic, PERIODIC_IMPLICIT); - CompletePeriodicComms(geometry, config, iPeriodic, PERIODIC_IMPLICIT); - } - - InitiateComms(geometry, config, MPI_QUANTITIES::SOLUTION); - CompleteComms(geometry, config, MPI_QUANTITIES::SOLUTION); - - /*--- For verification cases, compute the global error metrics. ---*/ - ComputeVerificationError(geometry, config); - } - /*! * \brief Evaluate the vorticity and strain rate magnitude. */ @@ -1075,6 +1044,13 @@ class CFVMFlowSolverBase : public CSolver { */ void ImplicitEuler_Iteration(CGeometry *geometry, CSolver **solver_container, CConfig *config) final; + /*! + * \brief Complete an implicit iteration. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] config - Definition of the particular problem. + */ + void CompleteImplicitIteration(CGeometry *geometry, CSolver**, CConfig *config) final; + /*! * \brief Set the total residual adding the term that comes from the Dual Time Strategy. * \param[in] geometry - Geometrical definition of the problem. diff --git a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl index 8339bb465a7f..28ca74d8149c 100644 --- a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl +++ b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl @@ -596,42 +596,33 @@ void CFVMFlowSolverBase::ComputeVerificationError(CGeometry* geometry, CCo } template -void CFVMFlowSolverBase::ComputeUnderRelaxationFactor(const CConfig* config) { +void CFVMFlowSolverBase::CompleteImplicitIteration(CGeometry *geometry, CSolver**, CConfig *config) { SU2_ZONE_SCOPED - /* Loop over the solution update given by relaxing the linear - system for this nonlinear iteration. */ + if constexpr (R == ENUM_REGIME::COMPRESSIBLE) ComputeUnderRelaxationFactor(config); - const su2double allowableRatio = config->GetMaxUpdateFractionFlow(); + /*--- Update solution with under-relaxation and communicate it. ---*/ - SU2_OMP_FOR_STAT(omp_chunk_size) - for (unsigned long iPoint = 0; iPoint < nPointDomain; iPoint++) { - su2double localUnderRelaxation = 1.0; - - for (unsigned short iVar = 0; iVar < nVar; iVar++) { - /* We impose a limit on the maximum percentage that the - density and energy can change over a nonlinear iteration. */ - - if ((iVar == 0) || (iVar == nVar - 1)) { - const unsigned long index = iPoint * nVar + iVar; - su2double ratio = fabs(LinSysSol[index]) / (fabs(nodes->GetSolution(iPoint, iVar)) + EPS); - if (ratio > allowableRatio) { - localUnderRelaxation = min(allowableRatio / ratio, localUnderRelaxation); - } + if (!config->GetContinuous_Adjoint()) { + SU2_OMP_FOR_STAT(omp_chunk_size) + for (unsigned long iPoint = 0; iPoint < nPointDomain; iPoint++) { + for (unsigned short iVar = 0; iVar < nVar; iVar++) { + nodes->AddSolution(iPoint, iVar, nodes->GetUnderRelaxation(iPoint) * LinSysSol(iPoint, iVar)); } } + END_SU2_OMP_FOR + } - /* Threshold the relaxation factor in the event that there is - a very small value. This helps avoid catastrophic crashes due - to non-realizable states by canceling the update. */ - - if (localUnderRelaxation < 1e-10) localUnderRelaxation = 0.0; + for (unsigned short iPeriodic = 1; iPeriodic <= config->GetnMarker_Periodic()/2; iPeriodic++) { + InitiatePeriodicComms(geometry, config, iPeriodic, PERIODIC_IMPLICIT); + CompletePeriodicComms(geometry, config, iPeriodic, PERIODIC_IMPLICIT); + } - /* Store the under-relaxation factor for this point. */ + InitiateComms(geometry, config, MPI_QUANTITIES::SOLUTION); + CompleteComms(geometry, config, MPI_QUANTITIES::SOLUTION); - nodes->SetUnderRelaxation(iPoint, localUnderRelaxation); - } - END_SU2_OMP_FOR + /*--- For verification cases, compute the global error metrics. ---*/ + ComputeVerificationError(geometry, config); } template diff --git a/SU2_CFD/include/solvers/CIncEulerSolver.hpp b/SU2_CFD/include/solvers/CIncEulerSolver.hpp index 98c3d21a8f2a..9c18691cf560 100644 --- a/SU2_CFD/include/solvers/CIncEulerSolver.hpp +++ b/SU2_CFD/include/solvers/CIncEulerSolver.hpp @@ -365,13 +365,6 @@ class CIncEulerSolver : public CFVMFlowSolverBase::max(); + + /*! + * \brief Marks outliers (0 ok, MAX_OUTLIER_MITIGATION maximum mitigation). + */ + su2vector OutlierMitigation; + }; diff --git a/SU2_CFD/src/iteration/CFluidIteration.cpp b/SU2_CFD/src/iteration/CFluidIteration.cpp index 55aba398ae85..a79a9f1f8f4a 100644 --- a/SU2_CFD/src/iteration/CFluidIteration.cpp +++ b/SU2_CFD/src/iteration/CFluidIteration.cpp @@ -133,13 +133,13 @@ void CFluidIteration::Iterate(COutput* output, CIntegration**** integration, CGe /*--- Adapt the CFL number using an exponential progression with under-relaxation approach. During Full-MG warmup (FinestMesh > MESH_0), skip adaptation entirely until the finest mesh is active. ---*/ - if ((config[val_iZone]->GetCFL_Adapt() == YES) && (!disc_adj) && - (config[val_iZone]->GetFinestMesh() == MESH_0)) { - SU2_OMP_PARALLEL + SU2_OMP_PARALLEL + if (!disc_adj && config[val_iZone]->GetFinestMesh() == MESH_0) { solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->AdaptCFLNumber(geometry[val_iZone][val_iInst], solver[val_iZone][val_iInst], config[val_iZone]); - END_SU2_OMP_PARALLEL + solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->IdentifySolutionOutliers(config[val_iZone], InnerIter); } + END_SU2_OMP_PARALLEL /*--- Call Dynamic mesh update if AEROELASTIC motion was specified ---*/ diff --git a/SU2_CFD/src/solvers/CEulerSolver.cpp b/SU2_CFD/src/solvers/CEulerSolver.cpp index e5737ceebb10..e89f022920c6 100644 --- a/SU2_CFD/src/solvers/CEulerSolver.cpp +++ b/SU2_CFD/src/solvers/CEulerSolver.cpp @@ -1669,7 +1669,7 @@ void CEulerSolver::CommonPreprocessing(CGeometry *geometry, CSolver **solver_con if (!ReducerStrategy && !Output) { LinSysRes.SetValZero(); - if (implicit) Jacobian.SetValZero(); + if (implicit) Jacobian.SetValDiagonalZero(); else {SU2_OMP_BARRIER} // because of "nowait" in LinSysRes } @@ -1916,11 +1916,12 @@ void CEulerSolver::Upwind_Residual(CGeometry *geometry, CSolver **solver_contain if (van_albada) { lim_i = LimiterHelpers<>::vanAlbadaFunction(Project_Grad_i, V_ij, EPS); lim_j = LimiterHelpers<>::vanAlbadaFunction(Project_Grad_j, V_ij, EPS); - } - else if (limiter) { + } else if (limiter) { lim_i = nodes->GetLimiter_Primitive(iPoint, iVar); lim_j = nodes->GetLimiter_Primitive(jPoint, iVar); } + lim_i *= (1 - static_cast(nodes->OutlierMitigation(iPoint)) / CEulerVariable::MAX_OUTLIER_MITIGATION); + lim_j *= (1 - static_cast(nodes->OutlierMitigation(jPoint)) / CEulerVariable::MAX_OUTLIER_MITIGATION); Primitive_i[iVar] = V_i[iVar] + 0.5 * lim_i * Project_Grad_i; Primitive_j[iVar] = V_j[iVar] - 0.5 * lim_j * Project_Grad_j; @@ -2004,7 +2005,7 @@ void CEulerSolver::Upwind_Residual(CGeometry *geometry, CSolver **solver_contain /*--- Set implicit computation ---*/ if (implicit) - Jacobian.UpdateBlocks(iEdge, iPoint, jPoint, residual.jacobian_i, residual.jacobian_j); + Jacobian.UpdateBlocks(iEdge, iPoint, jPoint, residual.jacobian_i, residual.jacobian_j); } /*--- Viscous contribution. ---*/ @@ -2520,10 +2521,164 @@ void CEulerSolver::PrepareImplicitIteration(CGeometry *geometry, CSolver**, CCon PrepareImplicitIteration_impl(precond, geometry, config); } -void CEulerSolver::CompleteImplicitIteration(CGeometry *geometry, CSolver**, CConfig *config) { +void CEulerSolver::ComputeUnderRelaxationFactor(const CConfig* config) { SU2_ZONE_SCOPED - CompleteImplicitIteration_impl(geometry, config); + /*--- Loop over the solution update given by relaxing the linear system for this + * nonlinear iteration and impose a limit on the maximum percentage that the + * density and static energy can change. */ + + const su2double allowableRatio = config->GetMaxUpdateFractionFlow(); + + SU2_OMP_FOR_STAT(omp_chunk_size) + for (unsigned long iPoint = 0; iPoint < nPointDomain; iPoint++) { + su2double ratio = fabs(LinSysSol(iPoint, 0)) / max(nodes->GetSolution(iPoint, 0), EPS); + su2double e_old = nodes->GetSolution(iPoint, nVar - 1); + su2double e_new = e_old + LinSysSol(iPoint, nVar - 1); + for (unsigned short jVar = 1; jVar <= nDim; jVar++) { + e_old -= 0.5 * pow(nodes->GetSolution(iPoint, jVar), 2); + e_new -= 0.5 * pow(nodes->GetSolution(iPoint, jVar) + LinSysSol(iPoint, jVar), 2); + } + ratio = fmax(ratio, fabs(e_new - e_old) / max(e_old, EPS)); + + su2double localUnderRelaxation = fmin(allowableRatio / fmax(ratio, EPS), 1); + + /* Threshold the relaxation factor in the event that there is + a very small value. This helps avoid catastrophic crashes due + to non-realizable states by canceling the update. */ + + if (localUnderRelaxation < 1e-10) localUnderRelaxation = 0.0; + + nodes->SetUnderRelaxation(iPoint, localUnderRelaxation); + } + END_SU2_OMP_FOR +} + +void CEulerSolver::IdentifySolutionOutliers(const CConfig *config, unsigned long iter) { + SU2_ZONE_SCOPED + + const unsigned long startIteration = config->GetOutlierMitigationParam()[0]; + const unsigned long updateFrequency = config->GetOutlierMitigationParam()[1]; + const unsigned long printFrequency = config->GetOutlierMitigationParam()[2]; + const int nSigma = config->GetOutlierMitigationParam()[3]; + + if (iter < startIteration) return; + + auto DetermineBinAndUpdatePoint = [&](const unsigned long iPoint) { + const su2double t = nodes->GetTemperature(iPoint); + const auto i = nSigma + min(max(-nSigma, SU2_TYPE::Int((t - MeanTemperature) / max(StdDevTemperature, EPS))), nSigma); + if (i == 0 || i == 2 * nSigma) { + if (nodes->OutlierMitigation(iPoint) == 0) { + /*--- Start mitigating with maximum strength if the point was just identified as outlier. ---*/ + nodes->OutlierMitigation(iPoint) = CEulerVariable::MAX_OUTLIER_MITIGATION; + } else if (nodes->OutlierMitigation(iPoint) < CEulerVariable::MAX_OUTLIER_MITIGATION) { + /*--- The point became an outlier again after reducing mitigations (below), increase them slowly. ---*/ + ++nodes->OutlierMitigation(iPoint); + } + } else if (nodes->OutlierMitigation(iPoint) > 0) { + /*--- Not an outlier anymore, try to slowly reduce the mitigations. ---*/ + --nodes->OutlierMitigation(iPoint); + } + return i; + }; + + /*--- Recompute mean and std deviation of temperature or use the stored values. ---*/ + if (iter == startIteration || iter % updateFrequency == 0) { + su2double localSum = 0; + static unsigned long nPointGlobal; + SU2_OMP_MASTER + MeanTemperature = 0; + END_SU2_OMP_MASTER + + SU2_OMP_FOR_STAT(omp_chunk_size) + for (unsigned long iPoint = 0; iPoint < nPointDomain; ++iPoint) { + localSum += nodes->GetTemperature(iPoint); + } + END_SU2_OMP_FOR + + atomicAdd(localSum, MeanTemperature); + + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { + su2double tmp[2] = {MeanTemperature, static_cast(nPointDomain)}, global[2]; + SU2_MPI::Allreduce(tmp, global, 2, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); + nPointGlobal = static_cast(SU2_TYPE::GetValue(global[1])); + MeanTemperature = global[0] / global[1]; + } + END_SU2_OMP_SAFE_GLOBAL_ACCESS + + localSum = 0; + SU2_OMP_MASTER + StdDevTemperature = 0; + END_SU2_OMP_MASTER + + SU2_OMP_FOR_STAT(omp_chunk_size) + for (unsigned long iPoint = 0; iPoint < nPointDomain; iPoint ++) { + localSum += pow(MeanTemperature - nodes->GetTemperature(iPoint), 2); + } + END_SU2_OMP_FOR + + atomicAdd(localSum, StdDevTemperature); + + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { + SU2_MPI::Allreduce(&StdDevTemperature, &localSum, 1, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); + StdDevTemperature = sqrt(localSum / nPointGlobal); + } + END_SU2_OMP_SAFE_GLOBAL_ACCESS + + const auto nBins = 2 * nSigma + 2; + std::vector bins(nBins, 0); + unsigned long nPointLocal = 0; + SU2_OMP_MASTER + nPointGlobal = 0; + END_SU2_OMP_MASTER + + SU2_OMP_FOR_STAT(omp_chunk_size) + for (unsigned long iPoint = 0; iPoint < nPoint; ++iPoint) { + const auto i = DetermineBinAndUpdatePoint(iPoint); + if (iPoint < nPointDomain) { + nPointLocal += static_cast(nodes->OutlierMitigation(iPoint) > 0); + + SU2_OMP_ATOMIC + ++bins[i]; + } + } + END_SU2_OMP_FOR + + atomicAdd(nPointLocal, nPointGlobal); + + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS + if (iter == 0 || iter % (updateFrequency * printFrequency) == 0) { + bins.back() = nPointGlobal; + std::vector global(nBins); + SU2_MPI::Reduce(bins.data(), global.data(), nBins, MPI_UNSIGNED_LONG, MPI_SUM, MASTER_NODE, SU2_MPI::GetComm()); + if (rank == MASTER_NODE) { + PrintingToolbox::CTablePrinter outlierTable(&std::cout); + outlierTable.AddColumn("Mean T", 12); + outlierTable.AddColumn("StdDev", 12); + outlierTable.AddColumn("< -" + std::to_string(nSigma), 12); + for (int i = -nSigma; i < nSigma; ++i) { + const int jump = (i == -1); + outlierTable.AddColumn(std::to_string(i) + " to " + std::to_string(i + 1 + jump), 12); + i += jump; + } + outlierTable.AddColumn("> " + std::to_string(nSigma), 12); + outlierTable.AddColumn("#Mitig.", 12); + outlierTable.SetAlign(PrintingToolbox::CTablePrinter::RIGHT); + outlierTable.PrintHeader(); + outlierTable << MeanTemperature << StdDevTemperature; + for (const auto n : global) outlierTable << n; + outlierTable.PrintFooter(); + } + } + END_SU2_OMP_SAFE_GLOBAL_ACCESS + + } else { + SU2_OMP_FOR_STAT(omp_chunk_size) + for (unsigned long iPoint = 0; iPoint < nPoint; ++iPoint) { + (void)DetermineBinAndUpdatePoint(iPoint); + } + END_SU2_OMP_FOR + } } void CEulerSolver::SetPreconditioner(const CConfig *config, unsigned long iPoint, diff --git a/SU2_CFD/src/solvers/CIncEulerSolver.cpp b/SU2_CFD/src/solvers/CIncEulerSolver.cpp index f138422f7ea5..2c4c9b05e1c6 100644 --- a/SU2_CFD/src/solvers/CIncEulerSolver.cpp +++ b/SU2_CFD/src/solvers/CIncEulerSolver.cpp @@ -2014,12 +2014,6 @@ void CIncEulerSolver::PrepareImplicitIteration(CGeometry *geometry, CSolver**, C PrepareImplicitIteration_impl(precond, geometry, config); } -void CIncEulerSolver::CompleteImplicitIteration(CGeometry *geometry, CSolver**, CConfig *config) { - SU2_ZONE_SCOPED - - CompleteImplicitIteration_impl(geometry, config); -} - void CIncEulerSolver::SetBeta_Parameter(CGeometry *geometry, CSolver **solver_container, CConfig *config, unsigned short iMesh) { SU2_ZONE_SCOPED diff --git a/SU2_CFD/src/solvers/CNEMOEulerSolver.cpp b/SU2_CFD/src/solvers/CNEMOEulerSolver.cpp index 1f3090a67cce..37de9a832074 100644 --- a/SU2_CFD/src/solvers/CNEMOEulerSolver.cpp +++ b/SU2_CFD/src/solvers/CNEMOEulerSolver.cpp @@ -936,12 +936,6 @@ void CNEMOEulerSolver::PrepareImplicitIteration(CGeometry *geometry, CSolver**, PrepareImplicitIteration_impl(precond, geometry, config); } -void CNEMOEulerSolver::CompleteImplicitIteration(CGeometry *geometry, CSolver**, CConfig *config) { - SU2_ZONE_SCOPED - - CompleteImplicitIteration_impl(geometry, config); -} - void CNEMOEulerSolver::ComputeUnderRelaxationFactor(const CConfig *config) { SU2_ZONE_SCOPED diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index 36a7d2283f0f..72685eb9bc56 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1747,6 +1747,8 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, CConfig *config) { SU2_ZONE_SCOPED + if (config->GetCFL_Adapt() != YES) return; + /* Adapt the CFL number on all multigrid levels using an exponential progression with under-relaxation approach. */ @@ -1811,7 +1813,10 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, canIncrease = (linRes < linTol) && (iter >= startingIter); - if ((iMesh == MESH_0) && (Res_Count > 0)) { + /* Do not use the residual flip-flop criteria when we are mitigating outliers + * because the former was never very reliable for large cases where monotonic + * residual reduction is impossible to achieve. */ + if (!config->OptionIsSet("OUTLIER_MITIGATION_PARAM") && iMesh == MESH_0 && Res_Count > 0) { Old_Func = New_Func; if (NonLinRes_Series.empty()) NonLinRes_Series.resize(Res_Count,0.0); diff --git a/SU2_CFD/src/solvers/CTurbSASolver.cpp b/SU2_CFD/src/solvers/CTurbSASolver.cpp index 407756c2b212..b58afe4c9b36 100644 --- a/SU2_CFD/src/solvers/CTurbSASolver.cpp +++ b/SU2_CFD/src/solvers/CTurbSASolver.cpp @@ -143,6 +143,10 @@ CTurbSASolver::CTurbSASolver(CGeometry *geometry, CConfig *config, unsigned shor fv1 = Ji_3/(Ji_3+cv1_3); muT_Inf = Density_Inf*fv1*nu_tilde_Inf; + if (config->GetSAParsedOptions().version != SA_OPTIONS::NEG) { + lowerlimit[0] = EPS; + } + /*--- Initialize the solution to the far-field state everywhere. ---*/ nodes = new CTurbSAVariable(nu_tilde_Inf, muT_Inf, nPoint, nDim, nVar, config); diff --git a/SU2_CFD/src/variables/CEulerVariable.cpp b/SU2_CFD/src/variables/CEulerVariable.cpp index 78671f780dc8..f45c60060b81 100644 --- a/SU2_CFD/src/variables/CEulerVariable.cpp +++ b/SU2_CFD/src/variables/CEulerVariable.cpp @@ -105,6 +105,8 @@ CEulerVariable::CEulerVariable(su2double density, const su2double *velocity, su2 NIterNewtonsolver.resize(nPoint) = 0; FluidEntropy.resize(nPoint) = su2double(0.0); } + + OutlierMitigation.resize(nPoint) = 0; } bool CEulerVariable::SetPrimVar(unsigned long iPoint, CFluidModel *FluidModel) { diff --git a/TestCases/cont_adj_euler/naca0012/of_grad_directdiff.dat.ref b/TestCases/cont_adj_euler/naca0012/of_grad_directdiff.dat.ref index 54b93ba1a831..7b5deefbf4d4 100644 --- a/TestCases/cont_adj_euler/naca0012/of_grad_directdiff.dat.ref +++ b/TestCases/cont_adj_euler/naca0012/of_grad_directdiff.dat.ref @@ -1,4 +1,4 @@ -VARIABLES="VARIABLE" , "DRAG" , "EFFICIENCY" , "FORCE_X" , "FORCE_Y" , "FORCE_Z" , "LIFT" , "MOMENT_X" , "MOMENT_Y" , "MOMENT_Z" , "SIDEFORCE" - 0 , 0.3283027921 , -23.00124324 , 0.3601048051 , -1.453884254 , 0.0 , -1.461393914 , 0.0 , 0.0 , 0.3837075086 , 0.0 - 1 , 0.5529850442 , -38.57543344 , 0.6062515142 , -2.435135388 , 0.0 , -2.447781199 , 0.0 , 0.0 , 0.1364160306 , 0.0 - 2 , 0.7825703473 , -46.16887385 , 0.8428457563 , -2.753846031 , 0.0 , -2.771577273 , 0.0 , 0.0 , -0.1507632792 , 0.0 +VARIABLES="VARIABLE" , "DRAG" , "EFFICIENCY" , "FORCE_X" , "FORCE_Y" , "FORCE_Z" , "LIFT" , "MOMENT_X" , "MOMENT_Y" , "MOMENT_Z" , "SIDEFORCE" + 0 , 0.3284091436 , -23.11888342 , 0.3603561695 , -1.460528943 , 0.0 , -1.468042505 , 0.0 , 0.0 , 0.3792955711 , 0.0 + 1 , 0.5523746333 , -38.65233166 , 0.6056912087 , -2.437438346 , 0.0 , -2.450071385 , 0.0 , 0.0 , 0.1342428158 , 0.0 + 2 , 0.7817003562 , -46.2043006 , 0.8419313277 , -2.751818981 , 0.0 , -2.769530757 , 0.0 , 0.0 , -0.1514885655 , 0.0 diff --git a/TestCases/hybrid_regression.py b/TestCases/hybrid_regression.py index 5f02581eeef1..612afb6c50a5 100644 --- a/TestCases/hybrid_regression.py +++ b/TestCases/hybrid_regression.py @@ -59,7 +59,7 @@ def main(): naca0012.cfg_dir = "euler/naca0012" naca0012.cfg_file = "inv_NACA0012_Roe.cfg" naca0012.test_iter = 20 - naca0012.test_vals = [-4.492584, -3.930725, 0.297160, 0.025487] + naca0012.test_vals = [-4.491302, -3.929519, 0.297210, 0.025485] test_list.append(naca0012) # Supersonic wedge @@ -67,7 +67,7 @@ def main(): wedge.cfg_dir = "euler/wedge" wedge.cfg_file = "inv_wedge_HLLC.cfg" wedge.test_iter = 20 - wedge.test_vals = [-3.689935, 2.034291, -0.249531, 0.043953] + wedge.test_vals = [-3.691631, 2.032783, -0.249531, 0.043953] test_list.append(wedge) # ONERA M6 Wing @@ -91,7 +91,7 @@ def main(): bluntbody.cfg_dir = "euler/bluntbody" bluntbody.cfg_file = "blunt.cfg" bluntbody.test_iter = 20 - bluntbody.test_vals = [0.475463, 6.835018, 0.000226, 1.784354] + bluntbody.test_vals = [0.666182, 7.055173, -0.000631, 3.917770] test_list.append(bluntbody) ########################## @@ -103,7 +103,7 @@ def main(): flatplate.cfg_dir = "navierstokes/flatplate" flatplate.cfg_file = "lam_flatplate.cfg" flatplate.test_iter = 100 - flatplate.test_vals = [-6.537258, -1.059050, 0.001198, 0.029303, 2.361500, -2.332200, 0.000000, 0.000000] + flatplate.test_vals = [-6.537258, -1.059049, 0.001198, 0.029303, 2.361500, -2.332200, 0.000000, 0.000000] test_list.append(flatplate) # Laminar cylinder (steady) @@ -136,7 +136,7 @@ def main(): poiseuille_profile.cfg_dir = "navierstokes/poiseuille" poiseuille_profile.cfg_file = "profile_poiseuille.cfg" poiseuille_profile.test_iter = 10 - poiseuille_profile.test_vals = [-12.005129, -7.581821, -0.000000, 2.089953] + poiseuille_profile.test_vals = [-12.005136, -7.582074, -0.000000, 2.089953] poiseuille_profile.test_vals_aarch64 = [-12.009012, -7.262530, -0.000000, 2.089953] test_list.append(poiseuille_profile) @@ -145,7 +145,7 @@ def main(): periodic2d.cfg_dir = "navierstokes/periodic2D" periodic2d.cfg_file = "config.cfg" periodic2d.test_iter = 1400 - periodic2d.test_vals = [-10.817611, -8.363544, -8.287461, -5.334104, -1.088411, -2945.200000] + periodic2d.test_vals = [-10.817616, -8.363550, -8.287466, -5.334109, -1.088411, -2945.200000] test_list.append(periodic2d) ########################## @@ -157,7 +157,7 @@ def main(): rae2822_sa.cfg_dir = "rans/rae2822" rae2822_sa.cfg_file = "turb_SA_RAE2822.cfg" rae2822_sa.test_iter = 20 - rae2822_sa.test_vals = [-2.193049, -5.312606, 0.388492, 0.077204, 0.000000] + rae2822_sa.test_vals = [-2.192911, -5.312614, 0.388496, 0.077215, 0.000000] test_list.append(rae2822_sa) # RAE2822 SST @@ -165,7 +165,7 @@ def main(): rae2822_sst.cfg_dir = "rans/rae2822" rae2822_sst.cfg_file = "turb_SST_RAE2822.cfg" rae2822_sst.test_iter = 20 - rae2822_sst.test_vals = [-1.028279, 5.869352, 0.357127, 0.074559, 0.000000] + rae2822_sst.test_vals = [-1.028279, 5.869352, 0.357142, 0.074560, 0.000000] test_list.append(rae2822_sst) # RAE2822 SST_SUST @@ -173,7 +173,7 @@ def main(): rae2822_sst_sust.cfg_dir = "rans/rae2822" rae2822_sst_sust.cfg_file = "turb_SST_SUST_RAE2822.cfg" rae2822_sst_sust.test_iter = 20 - rae2822_sst_sust.test_vals = [-2.479273, 5.869341, 0.357127, 0.074559] + rae2822_sst_sust.test_vals = [-2.479281, 5.869341, 0.357142, 0.074560] test_list.append(rae2822_sst_sust) # Flat plate @@ -189,7 +189,7 @@ def main(): turb_oneram6.cfg_dir = "rans/oneram6" turb_oneram6.cfg_file = "turb_ONERAM6.cfg" turb_oneram6.test_iter = 10 - turb_oneram6.test_vals = [-2.408655, -6.628338, 0.238580, 0.158951, 0.000000] + turb_oneram6.test_vals = [-2.418702, -6.631573, 0.238585, 0.159599, 0.000000] test_list.append(turb_oneram6) # NACA0012 (SA, FUN3D finest grid results: CL=1.0983, CD=0.01242) @@ -197,7 +197,7 @@ def main(): turb_naca0012_sa.cfg_dir = "rans/naca0012" turb_naca0012_sa.cfg_file = "turb_NACA0012_sa.cfg" turb_naca0012_sa.test_iter = 5 - turb_naca0012_sa.test_vals = [-12.038028, -16.332088, 1.080346, 0.018385, 20, -2.873477, 0, -14.250270, 0] + turb_naca0012_sa.test_vals = [-12.038045, -16.332088, 1.080346, 0.018385, 20.000000, -2.873565, 0.000000, -14.250270, 0.000000] turb_naca0012_sa.test_vals_aarch64 = [-12.038091, -16.332090, 1.080346, 0.018385, 20.000000, -2.873236, 0.000000, -14.250271, 0.000000] test_list.append(turb_naca0012_sa) @@ -206,7 +206,7 @@ def main(): turb_naca0012_sst.cfg_dir = "rans/naca0012" turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" turb_naca0012_sst.test_iter = 10 - turb_naca0012_sst.test_vals = [-12.093871, -15.251077, -5.906324, 1.070413, 0.015775, -2.855457, 0] + turb_naca0012_sst.test_vals = [-12.093924, -15.251077, -5.906324, 1.070413, 0.015775, -2.855555, 0.000000] turb_naca0012_sst.test_vals_aarch64 = [-12.075928, -15.246732, -5.861249, 1.070036, 0.015841, -2.835263, 0] test_list.append(turb_naca0012_sst) @@ -215,7 +215,7 @@ def main(): turb_naca0012_sst_sust.cfg_dir = "rans/naca0012" turb_naca0012_sst_sust.cfg_file = "turb_NACA0012_sst_sust.cfg" turb_naca0012_sst_sust.test_iter = 10 - turb_naca0012_sst_sust.test_vals = [-12.080828, -14.837176, -5.732906, 1.000893, 0.019109, -2.120116] + turb_naca0012_sst_sust.test_vals = [-12.080806, -14.837176, -5.732905, 1.000893, 0.019109, -2.119816] turb_naca0012_sst_sust.test_vals_aarch64 = [-12.073210, -14.836724, -5.732627, 1.000050, 0.019144, -2.629689] test_list.append(turb_naca0012_sst_sust) @@ -224,7 +224,7 @@ def main(): turb_naca0012_sst_fixedvalues.cfg_dir = "rans/naca0012" turb_naca0012_sst_fixedvalues.cfg_file = "turb_NACA0012_sst_fixedvalues.cfg" turb_naca0012_sst_fixedvalues.test_iter = 10 - turb_naca0012_sst_fixedvalues.test_vals = [-5.192389, -10.448080, 0.773965, 1.022534, 0.040529, -2.383403] + turb_naca0012_sst_fixedvalues.test_vals = [-5.192391, -10.448080, 0.773965, 1.022535, 0.040529, -2.383421] test_list.append(turb_naca0012_sst_fixedvalues) # NACA0012 (SST, explicit Euler for flow and turbulence equations) @@ -252,7 +252,7 @@ def main(): axi_rans_air_nozzle_restart.cfg_dir = "axisymmetric_rans/air_nozzle" axi_rans_air_nozzle_restart.cfg_file = "air_nozzle_restart.cfg" axi_rans_air_nozzle_restart.test_iter = 10 - axi_rans_air_nozzle_restart.test_vals = [-12.066228, -7.425901, -8.815839, -3.732622, 0] + axi_rans_air_nozzle_restart.test_vals = [-11.083069, -5.374686, -8.880083, -4.073484, 0.000000] axi_rans_air_nozzle_restart.test_vals_aarch64 = [-14.140441, -9.154674, -10.886121, -5.806594, 0.000000] test_list.append(axi_rans_air_nozzle_restart) @@ -278,7 +278,7 @@ def main(): turb_naca0012_1c.cfg_dir = "rans_uq/naca0012" turb_naca0012_1c.cfg_file = "turb_NACA0012_uq_1c.cfg" turb_naca0012_1c.test_iter = 10 - turb_naca0012_1c.test_vals = [-4.980125, 1.343354, 0.443788, -0.029257] + turb_naca0012_1c.test_vals = [-4.980126, 1.343353, 0.443722, -0.029245] turb_naca0012_1c.test_vals_aarch64 = [-4.976620, 1.345983, 0.433171, -0.033685] test_list.append(turb_naca0012_1c) @@ -287,7 +287,7 @@ def main(): turb_naca0012_2c.cfg_dir = "rans_uq/naca0012" turb_naca0012_2c.cfg_file = "turb_NACA0012_uq_2c.cfg" turb_naca0012_2c.test_iter = 10 - turb_naca0012_2c.test_vals = [-5.482849, 1.260868, 0.404588, -0.040284] + turb_naca0012_2c.test_vals = [-5.482849, 1.260866, 0.404517, -0.040307] turb_naca0012_2c.test_vals_aarch64 = [-5.485484, 1.263406, 0.411442, -0.040859] test_list.append(turb_naca0012_2c) @@ -296,7 +296,7 @@ def main(): turb_naca0012_3c.cfg_dir = "rans_uq/naca0012" turb_naca0012_3c.cfg_file = "turb_NACA0012_uq_3c.cfg" turb_naca0012_3c.test_iter = 10 - turb_naca0012_3c.test_vals = [-5.583738, 1.228730, 0.381824, -0.046280] + turb_naca0012_3c.test_vals = [-5.583738, 1.228727, 0.381732, -0.046307] turb_naca0012_3c.test_vals_aarch64 = [-5.583737, 1.232005, 0.390258, -0.046305] test_list.append(turb_naca0012_3c) @@ -305,7 +305,7 @@ def main(): turb_naca0012_p1c1.cfg_dir = "rans_uq/naca0012" turb_naca0012_p1c1.cfg_file = "turb_NACA0012_uq_p1c1.cfg" turb_naca0012_p1c1.test_iter = 10 - turb_naca0012_p1c1.test_vals = [-5.134031, 1.283495, 0.548237, 0.010736] + turb_naca0012_p1c1.test_vals = [-5.134040, 1.283488, 0.548247, 0.010741] turb_naca0012_p1c1.test_vals_aarch64 = [-5.114189, 1.285037, 0.406851, -0.043003] test_list.append(turb_naca0012_p1c1) @@ -314,7 +314,7 @@ def main(): turb_naca0012_p1c2.cfg_dir = "rans_uq/naca0012" turb_naca0012_p1c2.cfg_file = "turb_NACA0012_uq_p1c2.cfg" turb_naca0012_p1c2.test_iter = 10 - turb_naca0012_p1c2.test_vals = [-5.553917, 1.234038, 0.424217, -0.033478] + turb_naca0012_p1c2.test_vals = [-5.553917, 1.234037, 0.424160, -0.033497] turb_naca0012_p1c2.test_vals_aarch64 = [-5.548245, 1.236384, 0.381821, -0.050337] test_list.append(turb_naca0012_p1c2) @@ -335,7 +335,7 @@ def main(): hb_rans_preconditioning.cfg_dir = "harmonic_balance/hb_rans_preconditioning" hb_rans_preconditioning.cfg_file = "davis.cfg" hb_rans_preconditioning.test_iter = 25 - hb_rans_preconditioning.test_vals = [-1.902098, 0.484244, 0.601482, 3.609005, -5.943887] + hb_rans_preconditioning.test_vals = [-1.905219, 0.481910, 0.598991, 3.605350, -5.945851] test_list.append(hb_rans_preconditioning) ############################# @@ -383,7 +383,7 @@ def main(): inc_poly_cylinder.cfg_dir = "incomp_navierstokes/cylinder" inc_poly_cylinder.cfg_file = "poly_cylinder.cfg" inc_poly_cylinder.test_iter = 20 - inc_poly_cylinder.test_vals = [-8.241953, -2.424330, 0.027284, 1.909617, -173.010000] + inc_poly_cylinder.test_vals = [-8.241956, -2.424341, 0.027285, 1.909614, -173.010000] inc_poly_cylinder.test_vals_aarch64 = [-8.260165, -2.445453, 0.027209, 1.915447, -171.620000] test_list.append(inc_poly_cylinder) @@ -392,7 +392,7 @@ def main(): inc_lam_bend.cfg_dir = "incomp_navierstokes/bend" inc_lam_bend.cfg_file = "lam_bend.cfg" inc_lam_bend.test_iter = 10 - inc_lam_bend.test_vals = [-3.560185, -3.051988, -0.013972, 1.102842] + inc_lam_bend.test_vals = [-3.560185, -3.051989, -0.013972, 1.102841] test_list.append(inc_lam_bend) ############################ @@ -404,7 +404,7 @@ def main(): inc_turb_naca0012.cfg_dir = "incomp_rans/naca0012" inc_turb_naca0012.cfg_file = "naca0012.cfg" inc_turb_naca0012.test_iter = 20 - inc_turb_naca0012.test_vals = [-4.758063, -10.974497, -0.000004, -0.028654, 4, -5.404919, 2, -5.032662] + inc_turb_naca0012.test_vals = [-4.758063, -10.974497, -0.000004, -0.028654, 4.000000, -5.404348, 2.000000, -5.032687] test_list.append(inc_turb_naca0012) # NACA0012, SST_SUST @@ -420,7 +420,7 @@ def main(): inc_weakly_coupled.cfg_dir = "disc_adj_heat" inc_weakly_coupled.cfg_file = "primal.cfg" inc_weakly_coupled.test_iter = 10 - inc_weakly_coupled.test_vals = [-18.121422, -16.304159, -16.482315, -15.007167, -17.858118, -14.024885, 5.609100] + inc_weakly_coupled.test_vals = [-18.121444, -16.304190, -16.482326, -15.007166, -17.858047, -14.024909, 5.609100] test_list.append(inc_weakly_coupled) ###################################### @@ -481,7 +481,7 @@ def main(): gust_mesh_defo.cfg_dir = "gust" gust_mesh_defo.cfg_file = "gust_with_mesh_deformation.cfg" gust_mesh_defo.test_iter = 6 - gust_mesh_defo.test_vals = [-1.844761, 0.001077, -0.000263] + gust_mesh_defo.test_vals = [-1.844761, 0.001173, -0.000287] gust_mesh_defo.unsteady = True gust_mesh_defo.enabled_with_tsan = False test_list.append(gust_mesh_defo) @@ -501,7 +501,7 @@ def main(): ddes_flatplate.cfg_dir = "ddes/flatplate" ddes_flatplate.cfg_file = "ddes_flatplate.cfg" ddes_flatplate.test_iter = 10 - ddes_flatplate.test_vals = [-2.714713, -5.763293, -0.214960, 0.023758, 0.000000] + ddes_flatplate.test_vals = [-2.714713, -5.763298, -0.214960, 0.023758, 0.000000] ddes_flatplate.unsteady = True test_list.append(ddes_flatplate) @@ -510,7 +510,7 @@ def main(): unst_inc_turb_naca0015_sa.cfg_dir = "unsteady/pitching_naca0015_rans_inc" unst_inc_turb_naca0015_sa.cfg_file = "config_incomp_turb_sa.cfg" unst_inc_turb_naca0015_sa.test_iter = 1 - unst_inc_turb_naca0015_sa.test_vals = [-3.008629, -6.889003, 1.435193, 0.433537] + unst_inc_turb_naca0015_sa.test_vals = [-3.008630, -6.889005, 1.435192, 0.433540] unst_inc_turb_naca0015_sa.unsteady = True test_list.append(unst_inc_turb_naca0015_sa) @@ -519,7 +519,7 @@ def main(): unst_deforming_naca0012.cfg_dir = "disc_adj_euler/naca0012_pitching_def" unst_deforming_naca0012.cfg_file = "inv_NACA0012_pitching_deform.cfg" unst_deforming_naca0012.test_iter = 5 - unst_deforming_naca0012.test_vals = [-3.665284, -3.794189, -3.716987, -3.148573] + unst_deforming_naca0012.test_vals = [-3.665284, -3.794188, -3.716988, -3.148573] unst_deforming_naca0012.unsteady = True unst_deforming_naca0012.enabled_with_tsan = False test_list.append(unst_deforming_naca0012) @@ -533,7 +533,7 @@ def main(): edge_VW.cfg_dir = "nicf/edge" edge_VW.cfg_file = "edge_VW.cfg" edge_VW.test_iter = 30 - edge_VW.test_vals = [-7.053408, -0.851910, -0.000009, 0.000000] + edge_VW.test_vals = [-7.124331, -0.922828, -0.000009, 0.000000] test_list.append(edge_VW) # Rarefaction shock wave edge_PPR @@ -541,7 +541,7 @@ def main(): edge_PPR.cfg_dir = "nicf/edge" edge_PPR.cfg_file = "edge_PPR.cfg" edge_PPR.test_iter = 20 - edge_PPR.test_vals = [-12.455039, -6.258168, -0.000034, 0.000000] + edge_PPR.test_vals = [-12.029862, -5.864551, -0.000034, 0.000000] edge_PPR.test_vals_aarch64 = [ -7.139211, -0.980821, -0.000034, 0.000000] test_list.append(edge_PPR) @@ -554,7 +554,7 @@ def main(): Jones_tc_restart.cfg_dir = "turbomachinery/APU_turbocharger" Jones_tc_restart.cfg_file = "Jones_restart.cfg" Jones_tc_restart.test_iter = 5 - Jones_tc_restart.test_vals = [-7.645873, -5.849737, -15.337009, -9.825759, -13.216109, -7.752293, 73286.000000, 73286.000000, 0.020055, 82.286000] + Jones_tc_restart.test_vals = [-7.645864, -5.849737, -15.337009, -9.825759, -13.216109, -7.752293, 73286.000000, 73286.000000, 0.020055, 82.286000] test_list.append(Jones_tc_restart) # 2D axial stage @@ -562,7 +562,7 @@ def main(): axial_stage2D.cfg_dir = "turbomachinery/axial_stage_2D" axial_stage2D.cfg_file = "Axial_stage2D.cfg" axial_stage2D.test_iter = 20 - axial_stage2D.test_vals = [1.167176, 1.598838, -2.928275, 2.573906, -2.526637, 3.017140, 106370.000000, 106370.000000, 5.726800, 64.383000] + axial_stage2D.test_vals = [1.167181, 1.598494, -2.928576, 2.573645, -2.527390, 3.016171, 106370.000000, 106370.000000, 5.726800, 64.383000] test_list.append(axial_stage2D) # 2D transonic stator restart @@ -570,7 +570,7 @@ def main(): transonic_stator_restart.cfg_dir = "turbomachinery/transonic_stator_2D" transonic_stator_restart.cfg_file = "transonic_stator_restart.cfg" transonic_stator_restart.test_iter = 20 - transonic_stator_restart.test_vals = [-4.367184, -2.487640, -2.079069, 1.728134, -1.464968, 3.224889, -471620.000000, 94.839000, -0.051073] + transonic_stator_restart.test_vals = [-4.367185, -2.487642, -2.079071, 1.728133, -1.464968, 3.224887, -471620.000000, 94.839000, -0.051074] transonic_stator_restart.test_vals_aarch64 = [-4.442510, -2.561369, -2.165778, 1.652750, -1.355494, 3.172712, -471620.000000, 94.843000, -0.043825] test_list.append(transonic_stator_restart) @@ -592,7 +592,7 @@ def main(): uniform_flow.cfg_dir = "sliding_interface/uniform_flow" uniform_flow.cfg_file = "uniform_NN.cfg" uniform_flow.test_iter = 5 - uniform_flow.test_vals = [5.000000, 0.000000, -0.195002, -10.624451] + uniform_flow.test_vals = [5.000000, 0.000000, -0.195002, -10.624440] uniform_flow.unsteady = True uniform_flow.multizone = True test_list.append(uniform_flow) @@ -602,7 +602,7 @@ def main(): channel_2D.cfg_dir = "sliding_interface/channel_2D" channel_2D.cfg_file = "channel_2D_WA.cfg" channel_2D.test_iter = 2 - channel_2D.test_vals = [2.000000, 0.000000, 0.464906, 0.348048, 0.397471] + channel_2D.test_vals = [2.000000, 0.000000, 0.466188, 0.350089, 0.398977] channel_2D.unsteady = True channel_2D.multizone = True test_list.append(channel_2D) @@ -612,7 +612,7 @@ def main(): channel_3D.cfg_dir = "sliding_interface/channel_3D" channel_3D.cfg_file = "channel_3D_WA.cfg" channel_3D.test_iter = 2 - channel_3D.test_vals = [2.000000, 0.000000, 0.629091, 0.524932, 0.422527] + channel_3D.test_vals = [2.000000, 0.000000, 0.632254, 0.534189, 0.431979] channel_3D.test_vals_aarch64 = [2.000000, 0.000000, 0.629112, 0.524948, 0.422396] channel_3D.unsteady = True channel_3D.multizone = True @@ -624,7 +624,7 @@ def main(): pipe.cfg_dir = "sliding_interface/pipe" pipe.cfg_file = "pipe_NN.cfg" pipe.test_iter = 2 - pipe.test_vals = [0.080826, 0.547321, 0.655098, 0.968229, 1.049129] + pipe.test_vals = [0.092415, 0.568970, 0.692864, 0.989451, 1.048246] pipe.unsteady = True pipe.multizone = True test_list.append(pipe) @@ -634,7 +634,7 @@ def main(): rotating_cylinders.cfg_dir = "sliding_interface/rotating_cylinders" rotating_cylinders.cfg_file = "rot_cylinders_WA.cfg" rotating_cylinders.test_iter = 3 - rotating_cylinders.test_vals = [3.000000, 0.000000, 0.717065, 1.119817, 1.160326] + rotating_cylinders.test_vals = [3.000000, 0.000000, 0.664817, 1.125803, 1.117608] rotating_cylinders.unsteady = True rotating_cylinders.multizone = True test_list.append(rotating_cylinders) @@ -644,7 +644,7 @@ def main(): supersonic_vortex_shedding.cfg_dir = "sliding_interface/supersonic_vortex_shedding" supersonic_vortex_shedding.cfg_file = "sup_vor_shed_WA.cfg" supersonic_vortex_shedding.test_iter = 5 - supersonic_vortex_shedding.test_vals = [5.000000, 0.000000, 1.207118, 1.065254] + supersonic_vortex_shedding.test_vals = [5.000000, 0.000000, 0.899640, 1.076218] supersonic_vortex_shedding.unsteady = True supersonic_vortex_shedding.multizone = True test_list.append(supersonic_vortex_shedding) @@ -663,7 +663,7 @@ def main(): slinc_steady.cfg_dir = "sliding_interface/incompressible_steady" slinc_steady.cfg_file = "config.cfg" slinc_steady.test_iter = 19 - slinc_steady.test_vals = [19.000000, -1.154874, -1.378120] + slinc_steady.test_vals = [19.000000, -1.154874, -1.378127] slinc_steady.test_vals_aarch64 = [19.000000, -1.154874, -1.378120] slinc_steady.multizone = True test_list.append(slinc_steady) @@ -695,7 +695,7 @@ def main(): fsi2d.cfg_dir = "fea_fsi/WallChannel_2d" fsi2d.cfg_file = "configFSI.cfg" fsi2d.test_iter = 4 - fsi2d.test_vals = [4, 0, -3.726029, -4.277531] + fsi2d.test_vals = [4.000000, 0.000000, -3.726029, -4.277530] fsi2d.multizone= True fsi2d.unsteady = True fsi2d.enabled_with_tsan = False @@ -716,7 +716,7 @@ def main(): fsi_cht_restart.cfg_dir = "fea_fsi/stat_fsi" fsi_cht_restart.cfg_file = "config_restart.cfg" fsi_cht_restart.test_iter = 0 - fsi_cht_restart.test_vals = [5, 0.006352, -1.960362, -9.327033, -9.599649, -9.318478, 608.38, -0.012974, 0, 20] + fsi_cht_restart.test_vals = [5.000000, 0.006352, -1.960362, -9.327033, -9.580521, -9.317956, 608.380000, -0.012974, 0.000000, 20.000000] fsi_cht_restart.multizone = True test_list.append(fsi_cht_restart) @@ -729,7 +729,7 @@ def main(): mms_fvm_ns.cfg_dir = "mms/fvm_navierstokes" mms_fvm_ns.cfg_file = "lam_mms_roe.cfg" mms_fvm_ns.test_iter = 20 - mms_fvm_ns.test_vals = [-2.808514, 2.152654, 0.000000, 0.000000] + mms_fvm_ns.test_vals = [-2.808514, 2.152655, 0.000000, 0.000000] test_list.append(mms_fvm_ns) # FVM, incompressible, euler @@ -737,7 +737,7 @@ def main(): mms_fvm_inc_euler.cfg_dir = "mms/fvm_incomp_euler" mms_fvm_inc_euler.cfg_file = "inv_mms_jst.cfg" mms_fvm_inc_euler.test_iter = 20 - mms_fvm_inc_euler.test_vals = [-9.128033, -9.441406, 0.000000, 0.000000] + mms_fvm_inc_euler.test_vals = [-9.128035, -9.441406, 0.000000, 0.000000] mms_fvm_inc_euler.test_vals_aarch64 = [-9.128034, -9.441406, 0.000000, 0.000000] test_list.append(mms_fvm_inc_euler) @@ -746,7 +746,7 @@ def main(): mms_fvm_inc_ns.cfg_dir = "mms/fvm_incomp_navierstokes" mms_fvm_inc_ns.cfg_file = "lam_mms_fds.cfg" mms_fvm_inc_ns.test_iter = 20 - mms_fvm_inc_ns.test_vals = [-7.414944, -7.631546, 0.000000, 0.000000] + mms_fvm_inc_ns.test_vals = [-7.414945, -7.631547, 0.000000, 0.000000] test_list.append(mms_fvm_inc_ns) ########################## diff --git a/TestCases/hybrid_regression_AD.py b/TestCases/hybrid_regression_AD.py index df801edb10fe..b3c9e3ad7fdc 100644 --- a/TestCases/hybrid_regression_AD.py +++ b/TestCases/hybrid_regression_AD.py @@ -50,7 +50,7 @@ def main(): discadj_naca0012.cfg_dir = "cont_adj_euler/naca0012" discadj_naca0012.cfg_file = "inv_NACA0012_discadj.cfg" discadj_naca0012.test_iter = 100 - discadj_naca0012.test_vals = [-3.562611, -8.932638, -0.000000, 0.005608] + discadj_naca0012.test_vals = [-3.562611, -8.932639, -0.000000, 0.005608] test_list.append(discadj_naca0012) # Inviscid Cylinder 3D (multiple markers) @@ -58,7 +58,7 @@ def main(): discadj_cylinder3D.cfg_dir = "disc_adj_euler/cylinder3D" discadj_cylinder3D.cfg_file = "inv_cylinder3D.cfg" discadj_cylinder3D.test_iter = 5 - discadj_cylinder3D.test_vals = [-3.689810, -3.883743, -0.000000, 0.000000] + discadj_cylinder3D.test_vals = [-3.689811, -3.883747, -0.000000, 0.000000] test_list.append(discadj_cylinder3D) # Arina nozzle 2D @@ -78,7 +78,7 @@ def main(): discadj_rans_naca0012_sa.cfg_dir = "disc_adj_rans/naca0012" discadj_rans_naca0012_sa.cfg_file = "turb_NACA0012_sa.cfg" discadj_rans_naca0012_sa.test_iter = 10 - discadj_rans_naca0012_sa.test_vals = [-2.987151, 0.533082, 0.000004, -0.000000, 5.000000, -2.939636, 5.000000, -7.913743] + discadj_rans_naca0012_sa.test_vals = [-2.987150, 0.533080, 0.000004, -0.000000, 5.000000, -2.939634, 5.000000, -7.913741] test_list.append(discadj_rans_naca0012_sa) # Adjoint turbulent NACA0012 SST @@ -111,7 +111,7 @@ def main(): discadj_incomp_cylinder.cfg_dir = "disc_adj_incomp_navierstokes/cylinder" discadj_incomp_cylinder.cfg_file = "heated_cylinder.cfg" discadj_incomp_cylinder.test_iter = 20 - discadj_incomp_cylinder.test_vals = [20.000000, -1.671920, -6.254841, 0.000000] + discadj_incomp_cylinder.test_vals = [20.000000, -1.671928, -6.254786, 0.000000] discadj_incomp_cylinder.test_vals_aarch64 = [20.000000, -1.671920, -6.254841, 0.000000] discadj_incomp_cylinder.tol_aarch64 = 2e-1 test_list.append(discadj_incomp_cylinder) @@ -125,7 +125,7 @@ def main(): discadj_incomp_turb_NACA0012_sa.cfg_dir = "disc_adj_incomp_rans/naca0012" discadj_incomp_turb_NACA0012_sa.cfg_file = "turb_naca0012_sa.cfg" discadj_incomp_turb_NACA0012_sa.test_iter = 10 - discadj_incomp_turb_NACA0012_sa.test_vals = [10.000000, -3.845995, -1.023534, 0.000000] + discadj_incomp_turb_NACA0012_sa.test_vals = [10.000000, -3.845989, -1.023527, 0.000000] test_list.append(discadj_incomp_turb_NACA0012_sa) # Adjoint Incompressible Turbulent NACA 0012 SST @@ -145,7 +145,7 @@ def main(): discadj_cylinder.cfg_dir = "disc_adj_rans/cylinder" discadj_cylinder.cfg_file = "cylinder.cfg" discadj_cylinder.test_iter = 9 - discadj_cylinder.test_vals = [1.639345, -2.834279, -0.009538, 0.000020] + discadj_cylinder.test_vals = [1.639344, -2.834279, -0.009538, 0.000020] discadj_cylinder.unsteady = True discadj_cylinder.enabled_with_tsan = False test_list.append(discadj_cylinder) @@ -159,7 +159,7 @@ def main(): discadj_cylinder.cfg_dir = "disc_adj_rans/cylinder" discadj_cylinder.cfg_file = "cylinder_Windowing_AD.cfg" discadj_cylinder.test_iter = 9 - discadj_cylinder.test_vals = [2.183376] + discadj_cylinder.test_vals = [2.183375] discadj_cylinder.unsteady = True discadj_cylinder.enabled_with_tsan = False test_list.append(discadj_cylinder) @@ -228,7 +228,7 @@ def main(): pywrapper_FEA_AD_FlowLoad.cfg_dir = "py_wrapper/disc_adj_fea/flow_load_sens" pywrapper_FEA_AD_FlowLoad.cfg_file = "configAD_fem.cfg" pywrapper_FEA_AD_FlowLoad.test_iter = 100 - pywrapper_FEA_AD_FlowLoad.test_vals = [-0.132861, -0.558149, -0.000364, -0.003101] + pywrapper_FEA_AD_FlowLoad.test_vals = [-0.132010, -0.554418, -0.000364, -0.003101] pywrapper_FEA_AD_FlowLoad.test_vals_aarch64 = [-0.131745, -0.553214, -0.000364, -0.003101] pywrapper_FEA_AD_FlowLoad.command = TestCase.Command(exec = "python", param = "run_adjoint.py --parallel -f") pywrapper_FEA_AD_FlowLoad.timeout = 1600 @@ -243,7 +243,7 @@ def main(): pywrapper_CFD_AD_MeshDisp.cfg_dir = "py_wrapper/disc_adj_flow/mesh_disp_sens" pywrapper_CFD_AD_MeshDisp.cfg_file = "configAD_flow.cfg" pywrapper_CFD_AD_MeshDisp.test_iter = 1000 - pywrapper_CFD_AD_MeshDisp.test_vals = [30.000000, -2.496268, 1.441667, 0.000000] + pywrapper_CFD_AD_MeshDisp.test_vals = [30.000000, -2.496241, 1.441657, 0.000000] pywrapper_CFD_AD_MeshDisp.test_vals_aarch64 = [30.000000, -2.499079, 1.440068, 0.000000] pywrapper_CFD_AD_MeshDisp.command = TestCase.Command(exec = "python", param = "run_adjoint.py --parallel -f") pywrapper_CFD_AD_MeshDisp.timeout = 1600 diff --git a/TestCases/multiple_ffd/naca0012/of_grad_cd.dat.ref b/TestCases/multiple_ffd/naca0012/of_grad_cd.dat.ref index 45622cb6bd5e..7308d3ce095e 100644 --- a/TestCases/multiple_ffd/naca0012/of_grad_cd.dat.ref +++ b/TestCases/multiple_ffd/naca0012/of_grad_cd.dat.ref @@ -1,3 +1,3 @@ -VARIABLES="VARIABLE" , "GRADIENT" , "FINDIFF_STEP" - 0 , 0.0525397 , 0.001 - 1 , -0.099502 , 0.001 +VARIABLES="VARIABLE" , "GRADIENT" , "FINDIFF_STEP" + 0 , 0.0527036 , 0.001 + 1 , -0.0997374 , 0.001 diff --git a/TestCases/multiple_ffd/naca0012/of_grad_directdiff.dat.ref b/TestCases/multiple_ffd/naca0012/of_grad_directdiff.dat.ref index 174226e78499..1bf779ee0a17 100644 --- a/TestCases/multiple_ffd/naca0012/of_grad_directdiff.dat.ref +++ b/TestCases/multiple_ffd/naca0012/of_grad_directdiff.dat.ref @@ -1,3 +1,3 @@ -VARIABLES="VARIABLE" , "DRAG" , "EFFICIENCY" , "FORCE_X" , "FORCE_Y" , "FORCE_Z" , "LIFT" , "MOMENT_X" , "MOMENT_Y" , "MOMENT_Z" , "SIDEFORCE" - 0 , 0.05592643623 , -0.4243829941 , 0.05586792439 , 0.003291646374 , 0.0 , 0.002072110703 , 0.0 , 0.0 , 0.02964415677 , 0.0 - 1 , -0.1052916742 , 0.9115770745 , -0.1054405784 , 0.005675584425 , 0.0 , 0.007974407886 , 0.0 , 0.0 , 0.06727760621 , 0.0 +VARIABLES="VARIABLE" , "DRAG" , "EFFICIENCY" , "FORCE_X" , "FORCE_Y" , "FORCE_Z" , "LIFT" , "MOMENT_X" , "MOMENT_Y" , "MOMENT_Z" , "SIDEFORCE" + 0 , 0.05618256097 , -0.4264366682 , 0.05612587304 , 0.00321085185 , 0.0 , 0.001985708286 , 0.0 , 0.0 , 0.02965021664 , 0.0 + 1 , -0.1059117364 , 0.8978068975 , -0.1060212267 , 0.003862504658 , 0.0 , 0.006174426359 , 0.0 , 0.0 , 0.0675190381 , 0.0 diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index a6e777342244..faeca39742f6 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -236,7 +236,7 @@ def main(): naca0012.cfg_dir = "euler/naca0012" naca0012.cfg_file = "inv_NACA0012_Roe.cfg" naca0012.test_iter = 20 - naca0012.test_vals = [-4.442633, -3.913184, 0.295834, 0.024405] + naca0012.test_vals = [-4.441831, -3.912398, 0.295812, 0.024400] test_list.append(naca0012) # Supersonic wedge @@ -244,7 +244,7 @@ def main(): wedge.cfg_dir = "euler/wedge" wedge.cfg_file = "inv_wedge_HLLC.cfg" wedge.test_iter = 20 - wedge.test_vals = [-3.681969, 2.042532, -0.249531, 0.043953] + wedge.test_vals = [-3.681700, 2.042776, -0.249531, 0.043953] test_list.append(wedge) # ONERA M6 Wing @@ -270,7 +270,7 @@ def main(): polar_naca0012.cfg_file = "inv_NACA0012.cfg" polar_naca0012.polar = True polar_naca0012.test_iter = 10 - polar_naca0012.test_vals = [-1.285568, 4.161371, 0.003627, 0.083095] + polar_naca0012.test_vals = [-1.284049, 4.163288, 0.003791, 0.082785] polar_naca0012.test_vals_aarch64 = [-1.083394, 4.386134, 0.001588, 0.033513] polar_naca0012.command = TestCase.Command(exec = "compute_polar.py", param = "-i 11") # flaky test on arm64 @@ -282,7 +282,7 @@ def main(): bluntbody.cfg_dir = "euler/bluntbody" bluntbody.cfg_file = "blunt.cfg" bluntbody.test_iter = 20 - bluntbody.test_vals = [0.475144, 6.834602, -0.000007, 1.783980] + bluntbody.test_vals = [0.666133, 7.054751, -0.000051, 3.912894] test_list.append(bluntbody) # Equivalent area NACA64-206 @@ -290,7 +290,7 @@ def main(): ea_naca64206.cfg_dir = "optimization_euler/equivalentarea_naca64206" ea_naca64206.cfg_file = "NACA64206.cfg" ea_naca64206.test_iter = 10 - ea_naca64206.test_vals = [-1.125893, -0.474056, -0.002414, 67775.000000] + ea_naca64206.test_vals = [-1.125734, -0.473873, -0.002416, 67775.000000] test_list.append(ea_naca64206) # SUPERSONIC FLOW PAST A RAMP IN A CHANNEL @@ -298,7 +298,7 @@ def main(): ramp.cfg_dir = "euler/ramp" ramp.cfg_file = "inv_ramp.cfg" ramp.test_iter = 10 - ramp.test_vals = [-13.646937, -8.006398, -0.076277, 0.054839] + ramp.test_vals = [-13.647281, -8.010114, -0.076277, 0.054839] ramp.test_vals_aarch64 = [-13.648406, -8.014579, -0.076277, 0.054839] test_list.append(ramp) @@ -306,7 +306,7 @@ def main(): ramp_msw.cfg_dir = "euler/ramp" ramp_msw.cfg_file = "inv_ramp_msw.cfg" ramp_msw.test_iter = 100 - ramp_msw.test_vals = [-7.35, -1.6, -0.077520, 0.054427] + ramp_msw.test_vals = [-6.996547, -1.226863, -0.077507, 0.054419] ramp_msw.tol = [0.2, 0.2, 0.00001, 0.00001] test_list.append(ramp_msw) @@ -378,7 +378,7 @@ def main(): poiseuille_profile.cfg_dir = "navierstokes/poiseuille" poiseuille_profile.cfg_file = "profile_poiseuille.cfg" poiseuille_profile.test_iter = 10 - poiseuille_profile.test_vals = [-12.004278, -7.578183, -0.000000, 2.089953] + poiseuille_profile.test_vals = [-12.004290, -7.578539, -0.000000, 2.089953] poiseuille_profile.test_vals_aarch64 = [-12.007498, -7.226926, -0.000000, 2.089953] poiseuille_profile.tol = [0.001, 0.001, 1e-5, 1e-5, 1e-5] test_list.append(poiseuille_profile) @@ -392,7 +392,7 @@ def main(): rae2822_sa.cfg_dir = "rans/rae2822" rae2822_sa.cfg_file = "turb_SA_RAE2822.cfg" rae2822_sa.test_iter = 20 - rae2822_sa.test_vals = [-2.190821, -5.317349, 0.391922, 0.075544, 0.000000] + rae2822_sa.test_vals = [-2.190718, -5.317366, 0.391927, 0.075561, 0.000000] test_list.append(rae2822_sa) # RAE2822 SST @@ -400,7 +400,7 @@ def main(): rae2822_sst.cfg_dir = "rans/rae2822" rae2822_sst.cfg_file = "turb_SST_RAE2822.cfg" rae2822_sst.test_iter = 20 - rae2822_sst.test_vals = [-1.035574, 5.863601, 0.358893, 0.074724, 0.000000] + rae2822_sst.test_vals = [-1.035575, 5.863601, 0.358898, 0.074725, 0.000000] test_list.append(rae2822_sst) # RAE2822 SST_SUST @@ -408,7 +408,7 @@ def main(): rae2822_sst_sust.cfg_dir = "rans/rae2822" rae2822_sst_sust.cfg_file = "turb_SST_SUST_RAE2822.cfg" rae2822_sst_sust.test_iter = 20 - rae2822_sst_sust.test_vals = [-2.492136, 5.863586, 0.358893, 0.074724] + rae2822_sst_sust.test_vals = [-2.492141, 5.863586, 0.358898, 0.074725] test_list.append(rae2822_sst_sust) # Flat plate @@ -464,7 +464,7 @@ def main(): turb_oneram6.cfg_dir = "rans/oneram6" turb_oneram6.cfg_file = "turb_ONERAM6.cfg" turb_oneram6.test_iter = 10 - turb_oneram6.test_vals = [-2.408664, -6.628340, 0.238581, 0.158952, 0.000000] + turb_oneram6.test_vals = [-2.418711, -6.631576, 0.238587, 0.159599, 0.000000] turb_oneram6.timeout = 3200 test_list.append(turb_oneram6) @@ -473,7 +473,7 @@ def main(): turb_oneram6_vc.cfg_dir = "rans/oneram6" turb_oneram6_vc.cfg_file = "turb_ONERAM6_vc.cfg" turb_oneram6_vc.test_iter = 15 - turb_oneram6_vc.test_vals = [-2.282278, -6.568458, 0.234350, 0.142989, 0.000000] + turb_oneram6_vc.test_vals = [-2.294311, -6.572307, 0.234416, 0.144830, 0.000000] turb_oneram6_vc.timeout = 3200 test_list.append(turb_oneram6_vc) @@ -482,7 +482,7 @@ def main(): turb_oneram6_nk.cfg_dir = "rans/oneram6" turb_oneram6_nk.cfg_file = "turb_ONERAM6_nk.cfg" turb_oneram6_nk.test_iter = 20 - turb_oneram6_nk.test_vals = [-4.850719, -4.452661, -11.427627, 0.221809, 0.048349, 2, -0.881645, 10] + turb_oneram6_nk.test_vals = [-4.848809, -4.451213, -11.426556, 0.221554, 0.049197, 2.000000, -0.880072, 10.000000] turb_oneram6_nk.timeout = 600 turb_oneram6_nk.tol = 0.0001 test_list.append(turb_oneram6_nk) @@ -492,7 +492,7 @@ def main(): turb_naca0012_sa.cfg_dir = "rans/naca0012" turb_naca0012_sa.cfg_file = "turb_NACA0012_sa.cfg" turb_naca0012_sa.test_iter = 5 - turb_naca0012_sa.test_vals = [-12.037537, -16.376951, 1.080346, 0.018385, 20, -1.564109, 20, -4.180956, 0] + turb_naca0012_sa.test_vals = [-12.037515, -16.376951, 1.080346, 0.018385, 20.000000, -1.564135, 20.000000, -4.180955, 0.000000] turb_naca0012_sa.test_vals_aarch64 = [-12.037489, -16.376949, 1.080346, 0.018385, 20.000000, -1.564143, 20.000000, -4.180945, 0.000000] turb_naca0012_sa.timeout = 3200 test_list.append(turb_naca0012_sa) @@ -502,7 +502,7 @@ def main(): turb_naca0012_sst.cfg_dir = "rans/naca0012" turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" turb_naca0012_sst.test_iter = 10 - turb_naca0012_sst.test_vals = [-12.094646, -15.251093, -5.906365, 1.070413, 0.015775, -2.376189, 0] + turb_naca0012_sst.test_vals = [-12.094739, -15.251094, -5.906365, 1.070413, 0.015775, -2.376137, 0.000000] turb_naca0012_sst.test_vals_aarch64 = [-12.075620, -15.246688, -5.861276, 1.070036, 0.015841, -1.991001, 0.000000] turb_naca0012_sst.timeout = 3200 test_list.append(turb_naca0012_sst) @@ -512,7 +512,7 @@ def main(): turb_naca0012_sst_sust.cfg_dir = "rans/naca0012" turb_naca0012_sst_sust.cfg_file = "turb_NACA0012_sst_sust.cfg" turb_naca0012_sst_sust.test_iter = 10 - turb_naca0012_sst_sust.test_vals = [-12.082080, -14.837177, -5.733436, 1.000893, 0.019109, -2.241006] + turb_naca0012_sst_sust.test_vals = [-12.082081, -14.837177, -5.733435, 1.000893, 0.019109, -2.240976] turb_naca0012_sst_sust.test_vals_aarch64 = [-12.073964, -14.836726, -5.732390, 1.000050, 0.019144, -2.229074] turb_naca0012_sst_sust.timeout = 3200 test_list.append(turb_naca0012_sst_sust) @@ -540,7 +540,7 @@ def main(): turb_naca0012_sst_fixedvalues.cfg_dir = "rans/naca0012" turb_naca0012_sst_fixedvalues.cfg_file = "turb_NACA0012_sst_fixedvalues.cfg" turb_naca0012_sst_fixedvalues.test_iter = 10 - turb_naca0012_sst_fixedvalues.test_vals = [-10.440018, 0.774146, 1.022363, 0.040546, -3.736444] + turb_naca0012_sst_fixedvalues.test_vals = [-10.440016, 0.774146, 1.022363, 0.040546, -3.736437] turb_naca0012_sst_fixedvalues.timeout = 3200 test_list.append(turb_naca0012_sst_fixedvalues) @@ -567,7 +567,7 @@ def main(): actuatordisk_bem.cfg_dir = "rans/actuatordisk_bem" actuatordisk_bem.cfg_file = "actuatordisk_bem.cfg" actuatordisk_bem.test_iter = 15 - actuatordisk_bem.test_vals = [-5.388943, -10.318621, 0.001362, -0.376520] + actuatordisk_bem.test_vals = [-5.389236, -10.319123, 0.001362, -0.376528] actuatordisk_bem.timeout = 3200 actuatordisk_bem.tol = 0.001 test_list.append(actuatordisk_bem) @@ -581,7 +581,7 @@ def main(): axi_rans_air_nozzle_restart.cfg_dir = "axisymmetric_rans/air_nozzle" axi_rans_air_nozzle_restart.cfg_file = "air_nozzle_restart.cfg" axi_rans_air_nozzle_restart.test_iter = 10 - axi_rans_air_nozzle_restart.test_vals = [-12.069346, -7.508216, -8.813393, -3.732843, 0] + axi_rans_air_nozzle_restart.test_vals = [-11.056238, -5.334119, -8.842316, -4.067921, 0.000000] axi_rans_air_nozzle_restart.test_vals_aarch64 = [-14.143310, -9.163287, -10.858232, -5.787715, 0.000000] axi_rans_air_nozzle_restart.tol = 0.0001 test_list.append(axi_rans_air_nozzle_restart) @@ -784,7 +784,7 @@ def main(): turbmod_sa_bsl_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_bsl_rae2822.cfg_file = "turb_SA_BSL_RAE2822.cfg" turbmod_sa_bsl_rae2822.test_iter = 20 - turbmod_sa_bsl_rae2822.test_vals = [-2.797759, 0.171338, -0.274795, -5.261510, 0.792586, 0.025577] + turbmod_sa_bsl_rae2822.test_vals = [-2.811354, 0.159658, -0.279654, -5.239595, 0.792937, 0.025461] test_list.append(turbmod_sa_bsl_rae2822) # SA Negative @@ -792,7 +792,7 @@ def main(): turbmod_sa_neg_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_neg_rae2822.cfg_file = "turb_SA_NEG_RAE2822.cfg" turbmod_sa_neg_rae2822.test_iter = 10 - turbmod_sa_neg_rae2822.test_vals = [1.448390, 1.208561, -0.846814, 1.273854, 0.498380, 0.000000] + turbmod_sa_neg_rae2822.test_vals = [1.345830, 1.122324, -1.210207, 1.175663, 0.388687, 0.000000] turbmod_sa_neg_rae2822.test_vals_aarch64 = [-1.345593, 1.448310, 1.208721, -0.846597, 1.248410, 0.489117, 0.000000] test_list.append(turbmod_sa_neg_rae2822) @@ -801,7 +801,7 @@ def main(): turbmod_sa_comp_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_comp_rae2822.cfg_file = "turb_SA_COMP_RAE2822.cfg" turbmod_sa_comp_rae2822.test_iter = 20 - turbmod_sa_comp_rae2822.test_vals = [-2.797713, 0.171400, -0.274750, -5.270451, 0.792619, 0.025579] + turbmod_sa_comp_rae2822.test_vals = [-2.811319, 0.159702, -0.279630, -5.248778, 0.792974, 0.025463] test_list.append(turbmod_sa_comp_rae2822) # SA Edwards @@ -809,7 +809,7 @@ def main(): turbmod_sa_edw_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_edw_rae2822.cfg_file = "turb_SA_EDW_RAE2822.cfg" turbmod_sa_edw_rae2822.test_iter = 20 - turbmod_sa_edw_rae2822.test_vals = [-2.798216, 0.171419, -0.274673, -5.950380, 0.793286, 0.025430] + turbmod_sa_edw_rae2822.test_vals = [-2.811465, 0.159870, -0.279550, -5.932283, 0.793521, 0.025309] test_list.append(turbmod_sa_edw_rae2822) # SA Compressibility and Edwards @@ -817,7 +817,7 @@ def main(): turbmod_sa_comp_edw_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_comp_edw_rae2822.cfg_file = "turb_SA_COMP_EDW_RAE2822.cfg" turbmod_sa_comp_edw_rae2822.test_iter = 20 - turbmod_sa_comp_edw_rae2822.test_vals = [-2.804013, 0.164355, -0.281124, -5.949769, 0.793596, 0.025415] + turbmod_sa_comp_edw_rae2822.test_vals = [-2.811414, 0.159941, -0.279512, -5.935417, 0.793532, 0.025314] test_list.append(turbmod_sa_comp_edw_rae2822) # SA QCR @@ -825,7 +825,7 @@ def main(): turbmod_sa_qcr_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_qcr_rae2822.cfg_file = "turb_SA_QCR_RAE2822.cfg" turbmod_sa_qcr_rae2822.test_iter = 20 - turbmod_sa_qcr_rae2822.test_vals = [-2.818005, 0.149744, -0.284903, -5.229322, 0.793829, 0.025455] + turbmod_sa_qcr_rae2822.test_vals = [-2.804469, 0.164849, -0.274960, -5.243908, 0.792741, 0.025535] test_list.append(turbmod_sa_qcr_rae2822) ############################ @@ -866,7 +866,7 @@ def main(): contadj_wedge.cfg_dir = "cont_adj_euler/wedge" contadj_wedge.cfg_file = "inv_wedge_ROE.cfg" contadj_wedge.test_iter = 10 - contadj_wedge.test_vals = [2.872064, -2.756210, 1010800.000000, 0.000000] + contadj_wedge.test_vals = [2.872065, -2.756214, 1010800.000000, -0.000000] test_list.append(contadj_wedge) # Inviscid fixed CL NACA0012 @@ -942,7 +942,7 @@ def main(): turb_naca0012_1c.cfg_dir = "rans_uq/naca0012" turb_naca0012_1c.cfg_file = "turb_NACA0012_uq_1c.cfg" turb_naca0012_1c.test_iter = 10 - turb_naca0012_1c.test_vals = [-4.983993, 1.343552, 0.663881, 0.009383] + turb_naca0012_1c.test_vals = [-4.983993, 1.343551, 0.663876, 0.009417] turb_naca0012_1c.test_vals_aarch64 = [-4.981036, 1.345868, 0.673232, 0.010091] test_list.append(turb_naca0012_1c) @@ -951,7 +951,7 @@ def main(): turb_naca0012_2c.cfg_dir = "rans_uq/naca0012" turb_naca0012_2c.cfg_file = "turb_NACA0012_uq_2c.cfg" turb_naca0012_2c.test_iter = 10 - turb_naca0012_2c.test_vals = [-5.482691, 1.262640, 0.496012, -0.032672] + turb_naca0012_2c.test_vals = [-5.482691, 1.262639, 0.496036, -0.032657] turb_naca0012_2c.test_vals_aarch64 = [-5.484365, 1.264701, 0.501741, -0.033109] test_list.append(turb_naca0012_2c) @@ -960,7 +960,7 @@ def main(): turb_naca0012_3c.cfg_dir = "rans_uq/naca0012" turb_naca0012_3c.cfg_file = "turb_NACA0012_uq_3c.cfg" turb_naca0012_3c.test_iter = 10 - turb_naca0012_3c.test_vals = [-5.583617, 1.229594, 0.463394, -0.035393] + turb_naca0012_3c.test_vals = [-5.583617, 1.229594, 0.463384, -0.035444] test_list.append(turb_naca0012_3c) # NACA0012 p1c1 @@ -968,7 +968,7 @@ def main(): turb_naca0012_p1c1.cfg_dir = "rans_uq/naca0012" turb_naca0012_p1c1.cfg_file = "turb_NACA0012_uq_p1c1.cfg" turb_naca0012_p1c1.test_iter = 10 - turb_naca0012_p1c1.test_vals = [-5.129493, 1.283950, 0.807032, 0.047516] + turb_naca0012_p1c1.test_vals = [-5.129493, 1.283950, 0.807031, 0.047479] turb_naca0012_p1c1.test_vals_aarch64 = [-5.122100, 1.284478, 0.608744, -0.008593] test_list.append(turb_naca0012_p1c1) @@ -977,7 +977,7 @@ def main(): turb_naca0012_p1c2.cfg_dir = "rans_uq/naca0012" turb_naca0012_p1c2.cfg_file = "turb_NACA0012_uq_p1c2.cfg" turb_naca0012_p1c2.test_iter = 10 - turb_naca0012_p1c2.test_vals = [-5.553947, 1.234508, 0.604076, -0.008513] + turb_naca0012_p1c2.test_vals = [-5.553947, 1.234508, 0.604072, -0.008556] test_list.append(turb_naca0012_p1c2) ###################################### @@ -998,7 +998,7 @@ def main(): hb_rans_preconditioning.cfg_file = "davis.cfg" hb_rans_preconditioning.test_iter = 25 hb_rans_preconditioning.tol = 0.00001 - hb_rans_preconditioning.test_vals = [-1.902085, 0.484234, 0.601494, 3.609016, -5.943874] + hb_rans_preconditioning.test_vals = [-1.905206, 0.481900, 0.599004, 3.605361, -5.945837] test_list.append(hb_rans_preconditioning) ###################################### @@ -1010,7 +1010,7 @@ def main(): rot_naca0012.cfg_dir = "rotating/naca0012" rot_naca0012.cfg_file = "rot_NACA0012.cfg" rot_naca0012.test_iter = 25 - rot_naca0012.test_vals = [-1.289907, 4.246244, -0.000518, 0.112723] + rot_naca0012.test_vals = [-1.289931, 4.246409, -0.000484, 0.112457] test_list.append(rot_naca0012) # Lid-driven cavity @@ -1098,7 +1098,7 @@ def main(): edge_VW.cfg_dir = "nicf/edge" edge_VW.cfg_file = "edge_VW.cfg" edge_VW.test_iter = 25 - edge_VW.test_vals = [-3.145553, 3.055761, -0.000009, 0.000000] + edge_VW.test_vals = [-3.116423, 3.084890, -0.000009, 0.000000] test_list.append(edge_VW) # Rarefaction shock wave edge_PPR @@ -1106,7 +1106,7 @@ def main(): edge_PPR.cfg_dir = "nicf/edge" edge_PPR.cfg_file = "edge_PPR.cfg" edge_PPR.test_iter = 20 - edge_PPR.test_vals = [-10.311364, -4.158193, -0.000034, 0.000000] + edge_PPR.test_vals = [-9.963405, -3.810039, -0.000034, 0.000000] test_list.append(edge_PPR) # Rarefaction Q1D nozzle, include CoolProp fluid model @@ -1132,7 +1132,7 @@ def main(): datadriven_fluidModel.cfg_dir = "nicf/datadriven" datadriven_fluidModel.cfg_file = "datadriven_nozzle.cfg" datadriven_fluidModel.test_iter = 50 - datadriven_fluidModel.test_vals = [-6.283594, -3.253961, -4.172935, -0.949415, -2.046039, 1.546933] + datadriven_fluidModel.test_vals = [-4.879421, -2.353319, -2.344004, 0.390613, -1.559800, 1.322761] test_list.append(datadriven_fluidModel) ###################################### @@ -1161,7 +1161,7 @@ def main(): axial_stage2D.cfg_dir = "turbomachinery/axial_stage_2D" axial_stage2D.cfg_file = "Axial_stage2D.cfg" axial_stage2D.test_iter = 20 - axial_stage2D.test_vals = [1.167155, 1.598851, -2.928273, 2.573908, -2.526641, 3.017138, 106370.000000, 106370.000000, 5.726800, 64.383000] + axial_stage2D.test_vals = [1.167160, 1.598506, -2.928574, 2.573647, -2.527393, 3.016169, 106370.000000, 106370.000000, 5.726800, 64.383000] test_list.append(axial_stage2D) # 2D transonic stator restart @@ -1201,7 +1201,7 @@ def main(): channel_2D.cfg_dir = "sliding_interface/channel_2D" channel_2D.cfg_file = "channel_2D_WA.cfg" channel_2D.test_iter = 2 - channel_2D.test_vals = [2.000000, 0.000000, 0.464931, 0.348057, 0.397535] + channel_2D.test_vals = [2.000000, 0.000000, 0.466210, 0.350093, 0.398997] channel_2D.timeout = 100 channel_2D.unsteady = True channel_2D.multizone = True @@ -1212,7 +1212,7 @@ def main(): channel_3D.cfg_dir = "sliding_interface/channel_3D" channel_3D.cfg_file = "channel_3D_WA.cfg" channel_3D.test_iter = 2 - channel_3D.test_vals = [2.000000, 0.000000, 0.629098, 0.524941, 0.422526] + channel_3D.test_vals = [2.000000, 0.000000, 0.632259, 0.534230, 0.432007] channel_3D.test_vals_aarch64 = [2.000000, 0.000000, 0.629119, 0.524959, 0.422390] channel_3D.unsteady = True channel_3D.multizone = True @@ -1223,7 +1223,7 @@ def main(): pipe.cfg_dir = "sliding_interface/pipe" pipe.cfg_file = "pipe_NN.cfg" pipe.test_iter = 2 - pipe.test_vals = [0.080827, 0.547324, 0.655095, 0.968235, 1.049121] + pipe.test_vals = [0.092415, 0.568973, 0.692859, 0.989459, 1.048237] pipe.unsteady = True pipe.multizone = True test_list.append(pipe) @@ -1233,7 +1233,7 @@ def main(): rotating_cylinders.cfg_dir = "sliding_interface/rotating_cylinders" rotating_cylinders.cfg_file = "rot_cylinders_WA.cfg" rotating_cylinders.test_iter = 3 - rotating_cylinders.test_vals = [3.000000, 0.000000, 0.717065, 1.119815, 1.160330] + rotating_cylinders.test_vals = [3.000000, 0.000000, 0.664820, 1.125808, 1.117610] rotating_cylinders.unsteady = True rotating_cylinders.multizone = True test_list.append(rotating_cylinders) @@ -1243,7 +1243,7 @@ def main(): supersonic_vortex_shedding.cfg_dir = "sliding_interface/supersonic_vortex_shedding" supersonic_vortex_shedding.cfg_file = "sup_vor_shed_WA.cfg" supersonic_vortex_shedding.test_iter = 5 - supersonic_vortex_shedding.test_vals = [5.000000, 0.000000, 1.207118, 1.065260] + supersonic_vortex_shedding.test_vals = [5.000000, 0.000000, 0.899639, 1.076224] supersonic_vortex_shedding.unsteady = True supersonic_vortex_shedding.multizone = True test_list.append(supersonic_vortex_shedding) @@ -1305,7 +1305,7 @@ def main(): thermal_beam_nl_3d.cfg_dir = "fea_fsi/ThermalBeam_3d" thermal_beam_nl_3d.cfg_file = "configBeamNonlinear_3d.cfg" thermal_beam_nl_3d.test_iter = 8 - thermal_beam_nl_3d.test_vals = [-7.564309, -2.992893, -12.242503, -14.068322, 57, -4.017665, 24, -4.204804, 138710, 75.233] + thermal_beam_nl_3d.test_vals = [-7.564308, -2.992893, -12.242503, -14.068322, 57.000000, -4.017675, 24.000000, -4.204804, 138710.000000, 75.233000] test_list.append(thermal_beam_nl_3d) # Rotating cylinder, 3d @@ -1316,7 +1316,7 @@ def main(): # For a thin disk with the inner and outer radius of this geometry, from # "Formulas for Stress, Strain, and Structural Matrices", 2nd Edition, figure 19-4, # the maximum stress is 165.6MPa, we get a von Mises stress very close to that. - rotating_cylinder_fea.test_vals = [-6.886145, -6.917148, -6.959634, 23, -8.369804, 1.6502e+08] + rotating_cylinder_fea.test_vals = [-6.886142, -6.917150, -6.959635, 23.000000, -8.369804, 165020000.000000] rotating_cylinder_fea.test_vals_aarch64 = [-6.861939, -6.835539, -6.895498, 22, -8.313847, 1.6502e+08] test_list.append(rotating_cylinder_fea) @@ -1403,7 +1403,7 @@ def main(): cht_incompressible.cfg_dir = "coupled_cht/incomp_2d" cht_incompressible.cfg_file = "cht_2d_3cylinders.cfg" cht_incompressible.test_iter = 10 - cht_incompressible.test_vals = [-1.376348, -0.591208, -0.591208, -0.591208] + cht_incompressible.test_vals = [-1.376344, -0.591211, -0.591211, -0.591211] cht_incompressible.multizone = True test_list.append(cht_incompressible) @@ -1444,7 +1444,7 @@ def main(): pywrapper_naca0012.cfg_dir = "euler/naca0012" pywrapper_naca0012.cfg_file = "inv_NACA0012_Roe.cfg" pywrapper_naca0012.test_iter = 80 - pywrapper_naca0012.test_vals = [-6.754892, -6.158544, 0.335712, 0.023273] + pywrapper_naca0012.test_vals = [-6.753161, -6.156190, 0.335712, 0.023273] pywrapper_naca0012.command = TestCase.Command("mpirun -np 2", "SU2_CFD.py", "--parallel -f") test_list.append(pywrapper_naca0012) @@ -1453,7 +1453,7 @@ def main(): pywrapper_turb_naca0012_sst.cfg_dir = "rans/naca0012" pywrapper_turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" pywrapper_turb_naca0012_sst.test_iter = 10 - pywrapper_turb_naca0012_sst.test_vals = [-12.094646, -15.251093, -5.906365, 1.070413, 0.015775, -2.376189, 0] + pywrapper_turb_naca0012_sst.test_vals = [-12.094739, -15.251094, -5.906365, 1.070413, 0.015775, -2.376137, 0.000000] pywrapper_turb_naca0012_sst.test_vals_aarch64 = [-12.075620, -15.246688, -5.861276, 1.070036, 0.015841, -1.991001, 0.000000] pywrapper_turb_naca0012_sst.command = TestCase.Command("mpirun -np 2", "SU2_CFD.py", "--parallel -f") pywrapper_turb_naca0012_sst.timeout = 3200 @@ -1535,7 +1535,7 @@ def main(): pywrapper_deformingBump.cfg_dir = "py_wrapper/deforming_bump_in_channel" pywrapper_deformingBump.cfg_file = "config.cfg" pywrapper_deformingBump.test_iter = 1 - pywrapper_deformingBump.test_vals = [0.500000, 0.000000, -2.556309, -1.270839, -2.350590, 2.606851, 8.002480, -0.300272] + pywrapper_deformingBump.test_vals = [0.500000, 0.000000, -2.556309, -1.270839, -2.350591, 2.606851, 8.002480, -0.300272] pywrapper_deformingBump.command = TestCase.Command("mpirun -np 2", "python", "run.py") pywrapper_deformingBump.unsteady = True test_list.append(pywrapper_deformingBump) @@ -1545,7 +1545,7 @@ def main(): pywrapper_buoyancy.cfg_dir = "py_wrapper/custom_source_buoyancy" pywrapper_buoyancy.cfg_file = "lam_buoyancy_cavity.cfg" pywrapper_buoyancy.test_iter = 0 - pywrapper_buoyancy.test_vals = [-13.227985, -13.678478, -13.050758, -7.777151] + pywrapper_buoyancy.test_vals = [-13.227985, -13.678479, -13.050758, -7.777151] pywrapper_buoyancy.test_vals_aarch64 = [-13.227985, -13.678478, -13.050758, -7.777151] pywrapper_buoyancy.command = TestCase.Command("mpirun -np 2", "python", "run.py") test_list.append(pywrapper_buoyancy) @@ -1654,7 +1654,7 @@ def main(): species2_primitiveVenturi_mixingmodel_boundedscalar.cfg_dir = "species_transport/venturi_primitive_3species" species2_primitiveVenturi_mixingmodel_boundedscalar.cfg_file = "species2_primitiveVenturi_mixingmodel_boundedscalar.cfg" species2_primitiveVenturi_mixingmodel_boundedscalar.test_iter = 50 - species2_primitiveVenturi_mixingmodel_boundedscalar.test_vals = [-5.689670, -4.511504, -4.615493, -5.795204, -0.113336, -5.704986, 5.000000, -1.433752, 5.000000, -4.921373, 5.000000, -1.771016, 0.000318, 0.000318, 0.000000, 0.000000] + species2_primitiveVenturi_mixingmodel_boundedscalar.test_vals = [-5.689670, -4.511504, -4.615493, -5.795205, -0.113336, -5.704986, 5.000000, -1.433752, 5.000000, -4.921374, 5.000000, -1.771015, 0.000318, 0.000318, 0.000000, 0.000000] test_list.append(species2_primitiveVenturi_mixingmodel_boundedscalar) # 2 species (1 eq) primitive venturi mixing using mixing model including viscosity, thermal conductivity and inlet markers for SA turbulence model diff --git a/TestCases/parallel_regression_AD.py b/TestCases/parallel_regression_AD.py index cc4b94931bf7..f03615fba02a 100644 --- a/TestCases/parallel_regression_AD.py +++ b/TestCases/parallel_regression_AD.py @@ -150,7 +150,7 @@ def main(): discadj_axisymmetric_rans_nozzle.cfg_dir = "axisymmetric_rans/air_nozzle" discadj_axisymmetric_rans_nozzle.cfg_file = "air_nozzle_restart.cfg" discadj_axisymmetric_rans_nozzle.test_iter = 10 - discadj_axisymmetric_rans_nozzle.test_vals = [9.737045, 5.142730, 7.107566, 2.491197] + discadj_axisymmetric_rans_nozzle.test_vals = [9.909657, 5.078045, 7.129068, 2.490955] discadj_axisymmetric_rans_nozzle.no_restart = True test_list.append(discadj_axisymmetric_rans_nozzle) @@ -266,7 +266,7 @@ def main(): discadj_heat.cfg_dir = "disc_adj_heat" discadj_heat.cfg_file = "disc_adj_heat.cfg" discadj_heat.test_iter = 10 - discadj_heat.test_vals = [-1.880390, 0.759804, 0.000000, -4.486700] + discadj_heat.test_vals = [-1.880390, 0.759800, 0.000000, -4.486700] test_list.append(discadj_heat) ################################### @@ -278,7 +278,7 @@ def main(): discadj_fsi.cfg_dir = "disc_adj_fsi" discadj_fsi.cfg_file = "config.cfg" discadj_fsi.test_iter = 6 - discadj_fsi.test_vals = [6.000000, -7.017370, -7.872620, 0.000000, -0.000024] + discadj_fsi.test_vals = [6.000000, -7.017369, -7.872618, 0.000000, -0.000024] test_list.append(discadj_fsi) # Multi physics framework @@ -308,7 +308,7 @@ def main(): da_sp_pinArray_cht_2d_dp_hf.cfg_dir = "incomp_navierstokes/streamwise_periodic/chtPinArray_2d" da_sp_pinArray_cht_2d_dp_hf.cfg_file = "DA_configMaster.cfg" da_sp_pinArray_cht_2d_dp_hf.test_iter = 100 - da_sp_pinArray_cht_2d_dp_hf.test_vals = [-3.011140, -3.635369, -3.161207] + da_sp_pinArray_cht_2d_dp_hf.test_vals = [-2.805458, -3.441841, -2.767871] da_sp_pinArray_cht_2d_dp_hf.multizone = True test_list.append(da_sp_pinArray_cht_2d_dp_hf) @@ -326,7 +326,7 @@ def main(): da_unsteadyCHT_cylinder.cfg_dir = "coupled_cht/disc_adj_unsteadyCHT_cylinder" da_unsteadyCHT_cylinder.cfg_file = "chtMaster.cfg" da_unsteadyCHT_cylinder.test_iter = 2 - da_unsteadyCHT_cylinder.test_vals = [-8.479629, -9.239920, -9.234868, -15.934511, -13.661991, 0.000000, 10.627000, 0.295190] + da_unsteadyCHT_cylinder.test_vals = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662013, 0.000000, 10.627000, 0.295190] da_unsteadyCHT_cylinder.test_vals_aarch64 = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662012, 0.000000, 89.932000, 0.295190] da_unsteadyCHT_cylinder.unsteady = True da_unsteadyCHT_cylinder.multizone = True @@ -521,7 +521,7 @@ def main(): pywrapper_wavy_wall_steady.cfg_dir = "py_wrapper/wavy_wall" pywrapper_wavy_wall_steady.cfg_file = "run_steady.py" pywrapper_wavy_wall_steady.test_iter = 100 - pywrapper_wavy_wall_steady.test_vals = [-1.353007, 2.581051, -2.900574] + pywrapper_wavy_wall_steady.test_vals = [-1.353007, 2.581052, -2.900575] pywrapper_wavy_wall_steady.command = TestCase.Command("mpirun -n 2", "python", "run_steady.py") pywrapper_wavy_wall_steady.timeout = 1600 pywrapper_wavy_wall_steady.tol = 0.00001 diff --git a/TestCases/py_wrapper/translating_NACA0012/forces_0.csv.ref b/TestCases/py_wrapper/translating_NACA0012/forces_0.csv.ref index 5d4e8e3e99e7..df2f7651b449 100644 --- a/TestCases/py_wrapper/translating_NACA0012/forces_0.csv.ref +++ b/TestCases/py_wrapper/translating_NACA0012/forces_0.csv.ref @@ -1,200 +1,200 @@ -199, -0.96, -0.00, 0.00 -0, -2.88, 19.83, 0.00 -1, -4.18, 28.79, 0.00 -2, -5.26, 36.27, 0.00 -3, -6.22, 43.06, 0.00 -4, -7.06, 48.99, 0.00 -5, -7.74, 53.91, 0.00 -6, -8.33, 58.23, 0.00 -7, -8.78, 61.68, 0.00 -8, -9.13, 64.48, 0.00 -9, -9.36, 66.46, 0.00 -10, -9.49, 67.87, 0.00 -11, -9.52, 68.60, 0.00 -12, -9.47, 68.72, 0.00 -13, -9.30, 68.08, 0.00 -14, -9.07, 66.93, 0.00 -15, -8.74, 65.11, 0.00 -16, -8.36, 62.92, 0.00 -17, -7.90, 60.05, 0.00 -18, -7.40, 56.86, 0.00 -19, -6.82, 53.00, 0.00 -20, -6.21, 48.82, 0.00 -21, -5.55, 44.16, 0.00 -22, -4.86, 39.18, 0.00 -23, -4.12, 33.67, 0.00 -24, -3.37, 27.85, 0.00 -25, -2.57, 21.54, 0.00 -26, -1.75, 14.91, 0.00 -27, -0.89, 7.67, 0.00 -28, -0.03, 0.29, 0.00 -29, 0.85, -7.59, 0.00 -30, 1.69, -15.34, 0.00 -31, 2.54, -23.41, 0.00 -32, 3.37, -31.59, 0.00 -33, 4.19, -40.08, 0.00 -34, 5.02, -48.92, 0.00 -35, 5.83, -57.97, 0.00 -36, 6.62, -67.30, 0.00 -37, 7.38, -76.73, 0.00 -38, 8.13, -86.51, 0.00 -39, 8.83, -96.39, 0.00 -40, 9.51, -106.58, 0.00 -41, 10.14, -116.85, 0.00 -42, 10.76, -127.75, 0.00 -43, 11.32, -138.90, 0.00 -44, 11.81, -149.97, 0.00 -45, 12.22, -161.13, 0.00 -46, 12.50, -171.78, 0.00 -47, 12.68, -182.09, 0.00 -48, 12.78, -192.81, 0.00 -49, 12.76, -203.07, 0.00 -50, 12.57, -212.38, 0.00 -51, 12.05, -217.67, 0.00 -52, 11.60, -225.88, 0.00 -53, 11.01, -233.43, 0.00 -54, 10.51, -245.81, 0.00 -55, 9.41, -246.68, 0.00 -56, 7.63, -229.29, 0.00 -57, 6.26, -221.94, 0.00 -58, 6.99, -305.64, 0.00 -59, 6.92, -400.58, 0.00 -60, 4.75, -416.91, 0.00 -61, 2.15, -411.19, 0.00 -62, -0.50, -401.81, 0.00 -63, -3.15, -392.75, 0.00 -64, -5.77, -381.55, 0.00 -65, -8.34, -369.11, 0.00 -66, -10.81, -355.18, 0.00 -67, -13.15, -340.07, 0.00 -68, -15.34, -324.16, 0.00 -69, -17.37, -307.70, 0.00 -70, -19.20, -290.73, 0.00 -71, -20.74, -272.38, 0.00 -72, -22.00, -253.37, 0.00 -73, -22.70, -231.35, 0.00 -74, -23.29, -211.45, 0.00 -75, -23.37, -190.20, 0.00 -76, -23.19, -169.88, 0.00 -77, -22.18, -146.81, 0.00 -78, -20.95, -125.64, 0.00 -79, -19.03, -103.59, 0.00 -80, -16.76, -82.89, 0.00 -81, -13.60, -61.19, 0.00 -82, -9.50, -38.86, 0.00 -83, -4.20, -15.60, 0.00 -84, 2.57, 8.67, 0.00 -85, 10.17, 31.06, 0.00 -86, 18.87, 51.96, 0.00 -87, 28.98, 71.65, 0.00 -88, 41.74, 92.13, 0.00 -89, 54.07, 105.75, 0.00 -90, 70.75, 121.39, 0.00 -91, 96.09, 142.85, 0.00 -92, 93.51, 118.43, 0.00 -93, 120.64, 127.20, 0.00 -94, 136.03, 115.56, 0.00 -95, 169.16, 110.18, 0.00 -96, 186.54, 85.50, 0.00 -97, 143.56, 38.86, 0.00 -98, 66.96, 8.98, 0.00 -99, 44.43, -0.00, 0.00 -100, 87.14, -11.69, 0.00 -101, 145.23, -39.31, 0.00 -102, 142.83, -65.47, 0.00 -103, 105.19, -68.51, 0.00 -104, 88.44, -75.13, 0.00 -105, 63.28, -66.72, 0.00 -106, 48.19, -61.02, 0.00 -107, 25.79, -38.34, 0.00 -108, 13.76, -23.61, 0.00 -109, -0.24, 0.48, 0.00 -110, -9.29, 20.51, 0.00 -111, -20.12, 49.76, 0.00 -112, -27.25, 75.02, 0.00 -113, -34.63, 105.71, 0.00 -114, -39.49, 133.20, 0.00 -115, -42.08, 156.42, 0.00 -116, -43.53, 178.10, 0.00 -117, -45.53, 204.86, 0.00 -118, -46.18, 228.44, 0.00 -119, -46.94, 255.49, 0.00 -120, -46.62, 279.57, 0.00 -121, -46.43, 307.28, 0.00 -122, -45.37, 332.37, 0.00 -123, -44.27, 360.19, 0.00 -124, -42.30, 384.11, 0.00 -125, -40.27, 410.38, 0.00 -126, -37.67, 433.86, 0.00 -127, -34.88, 458.06, 0.00 -128, -31.68, 479.72, 0.00 -129, -28.36, 502.38, 0.00 -130, -24.77, 523.30, 0.00 -131, -21.07, 544.83, 0.00 -132, -17.18, 564.29, 0.00 -133, -13.22, 584.83, 0.00 -134, -9.13, 602.95, 0.00 -135, -4.98, 621.40, 0.00 -136, -0.79, 638.19, 0.00 -137, 3.43, 654.80, 0.00 -138, 7.63, 669.17, 0.00 -139, 11.81, 684.02, 0.00 -140, 15.94, 697.09, 0.00 -141, 20.00, 708.97, 0.00 -142, 23.96, 719.61, 0.00 -143, 27.84, 729.78, 0.00 -144, 31.59, 738.57, 0.00 -145, 35.21, 746.32, 0.00 -146, 38.69, 753.11, 0.00 -147, 42.03, 758.96, 0.00 -148, 45.19, 763.53, 0.00 -149, 48.25, 767.89, 0.00 -150, 51.12, 770.97, 0.00 -151, 53.90, 774.14, 0.00 -152, 56.45, 775.45, 0.00 -153, 58.60, 772.82, 0.00 -154, 60.49, 768.26, 0.00 -155, 61.32, 752.19, 0.00 -156, 66.19, 786.01, 0.00 -157, 51.41, 592.36, 0.00 -158, 2.74, 30.66, 0.00 -159, -1.91, -20.81, 0.00 -160, -1.38, -14.70, 0.00 -161, -2.29, -23.78, 0.00 -162, -2.80, -28.43, 0.00 -163, -2.29, -22.77, 0.00 -164, -2.55, -24.89, 0.00 -165, -2.37, -22.65, 0.00 -166, -2.68, -25.18, 0.00 -167, -2.76, -25.43, 0.00 -168, -3.15, -28.54, 0.00 -169, -3.54, -31.56, 0.00 -170, -4.04, -35.47, 0.00 -171, -4.42, -38.18, 0.00 -172, -4.93, -41.95, 0.00 -173, -5.44, -45.65, 0.00 -174, -6.02, -49.76, 0.00 -175, -6.54, -53.35, 0.00 -176, -7.07, -56.96, 0.00 -177, -7.54, -59.96, 0.00 -178, -8.01, -62.95, 0.00 -179, -8.43, -65.45, 0.00 -180, -8.82, -67.79, 0.00 -181, -9.18, -69.80, 0.00 -182, -9.48, -71.32, 0.00 -183, -9.72, -72.39, 0.00 -184, -9.90, -73.09, 0.00 -185, -10.04, -73.44, 0.00 -186, -10.00, -72.60, 0.00 -187, -10.00, -71.99, 0.00 -188, -9.88, -70.65, 0.00 -189, -9.65, -68.59, 0.00 -190, -9.33, -65.89, 0.00 -191, -8.91, -62.56, 0.00 -192, -8.37, -58.52, 0.00 -193, -7.74, -53.89, 0.00 -194, -7.00, -48.56, 0.00 -195, -6.14, -42.46, 0.00 -196, -5.13, -35.44, 0.00 -197, -4.03, -27.78, 0.00 -198, -2.72, -18.69, 0.00 +199, -0.92, -0.00, 0.00 +0, -2.81, 19.31, 0.00 +1, -3.96, 27.31, 0.00 +2, -4.94, 34.10, 0.00 +3, -5.78, 40.00, 0.00 +4, -6.52, 45.26, 0.00 +5, -7.12, 49.55, 0.00 +6, -7.61, 53.23, 0.00 +7, -7.99, 56.10, 0.00 +8, -8.25, 58.25, 0.00 +9, -8.40, 59.65, 0.00 +10, -8.44, 60.37, 0.00 +11, -8.40, 60.49, 0.00 +12, -8.26, 59.94, 0.00 +13, -8.07, 59.06, 0.00 +14, -7.76, 57.31, 0.00 +15, -7.41, 55.21, 0.00 +16, -6.96, 52.34, 0.00 +17, -6.45, 49.03, 0.00 +18, -5.88, 45.20, 0.00 +19, -5.32, 41.32, 0.00 +20, -4.65, 36.55, 0.00 +21, -3.98, 31.65, 0.00 +22, -3.24, 26.08, 0.00 +23, -2.50, 20.41, 0.00 +24, -1.70, 14.08, 0.00 +25, -0.94, 7.92, 0.00 +26, -0.11, 0.91, 0.00 +27, 0.76, -6.55, 0.00 +28, 1.61, -14.10, 0.00 +29, 2.34, -20.84, 0.00 +30, 3.22, -29.22, 0.00 +31, 4.11, -37.87, 0.00 +32, 4.97, -46.68, 0.00 +33, 5.78, -55.30, 0.00 +34, 6.60, -64.38, 0.00 +35, 7.41, -73.74, 0.00 +36, 8.19, -83.27, 0.00 +37, 8.99, -93.47, 0.00 +38, 9.72, -103.41, 0.00 +39, 10.47, -114.27, 0.00 +40, 11.12, -124.57, 0.00 +41, 11.78, -135.78, 0.00 +42, 12.30, -146.05, 0.00 +43, 12.85, -157.56, 0.00 +44, 13.23, -168.05, 0.00 +45, 13.58, -179.05, 0.00 +46, 13.80, -189.61, 0.00 +47, 13.87, -199.23, 0.00 +48, 13.88, -209.29, 0.00 +49, 13.76, -218.94, 0.00 +50, 13.55, -228.91, 0.00 +51, 12.94, -233.69, 0.00 +52, 12.72, -247.56, 0.00 +53, 12.33, -261.38, 0.00 +54, 11.74, -274.42, 0.00 +55, 10.71, -280.64, 0.00 +56, 9.07, -272.52, 0.00 +57, 7.59, -269.15, 0.00 +58, 6.93, -303.08, 0.00 +59, 6.80, -393.44, 0.00 +60, 5.02, -440.64, 0.00 +61, 2.26, -431.72, 0.00 +62, -0.52, -421.20, 0.00 +63, -3.29, -410.57, 0.00 +64, -6.05, -400.01, 0.00 +65, -8.76, -387.67, 0.00 +66, -11.41, -374.93, 0.00 +67, -13.94, -360.27, 0.00 +68, -16.37, -345.88, 0.00 +69, -18.55, -328.61, 0.00 +70, -20.66, -312.89, 0.00 +71, -22.66, -297.53, 0.00 +72, -24.25, -279.29, 0.00 +73, -25.65, -261.40, 0.00 +74, -26.69, -242.37, 0.00 +75, -28.31, -230.36, 0.00 +76, -28.63, -209.71, 0.00 +77, -29.03, -192.15, 0.00 +78, -28.80, -172.72, 0.00 +79, -29.70, -161.67, 0.00 +80, -28.77, -142.34, 0.00 +81, -28.04, -126.17, 0.00 +82, -25.54, -104.51, 0.00 +83, -26.31, -97.80, 0.00 +84, -22.38, -75.48, 0.00 +85, -27.40, -83.64, 0.00 +86, -22.35, -61.54, 0.00 +87, -14.41, -35.62, 0.00 +88, -9.00, -19.86, 0.00 +89, -14.66, -28.68, 0.00 +90, -11.68, -20.05, 0.00 +91, -38.05, -56.57, 0.00 +92, -40.77, -51.64, 0.00 +93, -49.35, -52.03, 0.00 +94, -28.55, -24.25, 0.00 +95, 30.46, 19.84, 0.00 +96, 14.41, 6.61, 0.00 +97, 124.60, 33.72, 0.00 +98, 88.18, 11.83, 0.00 +99, 50.99, -0.00, 0.00 +100, 139.64, -18.74, 0.00 +101, 167.24, -45.26, 0.00 +102, 115.14, -52.78, 0.00 +103, 86.56, -56.38, 0.00 +104, 75.50, -64.13, 0.00 +105, 8.40, -8.85, 0.00 +106, 5.63, -7.13, 0.00 +107, -24.68, 36.68, 0.00 +108, -33.56, 57.59, 0.00 +109, -40.80, 79.80, 0.00 +110, -48.06, 106.07, 0.00 +111, -47.07, 116.38, 0.00 +112, -51.99, 143.14, 0.00 +113, -50.64, 154.57, 0.00 +114, -53.25, 179.59, 0.00 +115, -55.77, 207.35, 0.00 +116, -56.99, 233.15, 0.00 +117, -56.78, 255.45, 0.00 +118, -56.53, 279.68, 0.00 +119, -55.76, 303.48, 0.00 +120, -54.71, 328.03, 0.00 +121, -53.03, 350.95, 0.00 +122, -51.15, 374.74, 0.00 +123, -48.72, 396.47, 0.00 +124, -46.17, 419.21, 0.00 +125, -43.26, 440.80, 0.00 +126, -40.26, 463.61, 0.00 +127, -36.79, 483.15, 0.00 +128, -33.38, 505.47, 0.00 +129, -29.71, 526.37, 0.00 +130, -25.91, 547.32, 0.00 +131, -21.93, 567.07, 0.00 +132, -17.86, 586.63, 0.00 +133, -13.70, 606.12, 0.00 +134, -9.45, 624.31, 0.00 +135, -5.14, 641.70, 0.00 +136, -0.81, 658.71, 0.00 +137, 3.53, 674.61, 0.00 +138, 7.86, 689.50, 0.00 +139, 12.16, 704.17, 0.00 +140, 16.41, 717.33, 0.00 +141, 20.57, 729.12, 0.00 +142, 24.65, 740.15, 0.00 +143, 28.65, 750.98, 0.00 +144, 32.50, 759.96, 0.00 +145, 36.21, 767.66, 0.00 +146, 39.79, 774.52, 0.00 +147, 43.22, 780.53, 0.00 +148, 46.47, 785.06, 0.00 +149, 49.59, 789.35, 0.00 +150, 52.48, 791.55, 0.00 +151, 55.08, 791.14, 0.00 +152, 57.66, 792.15, 0.00 +153, 60.78, 801.51, 0.00 +154, 63.32, 804.14, 0.00 +155, 64.99, 797.13, 0.00 +156, 66.49, 789.57, 0.00 +157, 59.04, 680.31, 0.00 +158, 13.95, 156.29, 0.00 +159, -6.84, -74.66, 0.00 +160, -3.13, -33.36, 0.00 +161, 3.28, 34.11, 0.00 +162, 4.40, 44.73, 0.00 +163, 2.73, 27.19, 0.00 +164, 2.91, 28.39, 0.00 +165, 1.42, 13.56, 0.00 +166, 1.29, 12.11, 0.00 +167, 0.35, 3.21, 0.00 +168, -0.01, -0.09, 0.00 +169, -0.71, -6.32, 0.00 +170, -1.16, -10.21, 0.00 +171, -2.03, -17.54, 0.00 +172, -2.58, -21.94, 0.00 +173, -3.22, -27.00, 0.00 +174, -3.78, -31.27, 0.00 +175, -4.35, -35.53, 0.00 +176, -4.92, -39.61, 0.00 +177, -5.48, -43.56, 0.00 +178, -5.99, -47.08, 0.00 +179, -6.52, -50.65, 0.00 +180, -6.98, -53.62, 0.00 +181, -7.37, -56.02, 0.00 +182, -7.75, -58.33, 0.00 +183, -8.07, -60.15, 0.00 +184, -8.35, -61.64, 0.00 +185, -8.58, -62.75, 0.00 +186, -8.72, -63.28, 0.00 +187, -8.75, -63.04, 0.00 +188, -8.72, -62.37, 0.00 +189, -8.59, -61.03, 0.00 +190, -8.38, -59.15, 0.00 +191, -8.07, -56.67, 0.00 +192, -7.66, -53.53, 0.00 +193, -7.10, -49.40, 0.00 +194, -6.47, -44.88, 0.00 +195, -5.69, -39.34, 0.00 +196, -4.81, -33.19, 0.00 +197, -3.80, -26.16, 0.00 +198, -2.63, -18.08, 0.00 diff --git a/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref b/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref index e47d7e1ff730..7fd783fdbb70 100644 --- a/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref +++ b/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref @@ -1,200 +1,200 @@ -199, -0.97, -0.00, 0.00 -0, -2.88, 19.85, 0.00 -1, -4.05, 27.93, 0.00 -2, -5.07, 35.03, 0.00 -3, -5.96, 41.24, 0.00 -4, -6.74, 46.78, 0.00 -5, -7.39, 51.48, 0.00 -6, -7.92, 55.40, 0.00 -7, -8.36, 58.76, 0.00 -8, -8.64, 61.03, 0.00 -9, -8.84, 62.79, 0.00 -10, -8.89, 63.57, 0.00 -11, -8.86, 63.80, 0.00 -12, -8.73, 63.35, 0.00 -13, -8.52, 62.33, 0.00 -14, -8.24, 60.81, 0.00 -15, -7.91, 58.90, 0.00 -16, -7.49, 56.33, 0.00 -17, -7.04, 53.47, 0.00 -18, -6.48, 49.79, 0.00 -19, -5.90, 45.83, 0.00 -20, -5.25, 41.27, 0.00 -21, -4.60, 36.56, 0.00 -22, -3.89, 31.30, 0.00 -23, -3.18, 25.96, 0.00 -24, -2.42, 19.98, 0.00 -25, -1.68, 14.07, 0.00 -26, -0.87, 7.38, 0.00 -27, -0.09, 0.81, 0.00 -28, 0.74, -6.50, 0.00 -29, 1.54, -13.72, 0.00 -30, 2.37, -21.44, 0.00 -31, 3.13, -28.87, 0.00 -32, 3.94, -37.02, 0.00 -33, 4.69, -44.82, 0.00 -34, 5.49, -53.48, 0.00 -35, 6.20, -61.69, 0.00 -36, 6.97, -70.86, 0.00 -37, 7.67, -79.73, 0.00 -38, 8.38, -89.17, 0.00 -39, 9.03, -98.47, 0.00 -40, 9.61, -107.66, 0.00 -41, 10.12, -116.57, 0.00 -42, 10.52, -124.95, 0.00 -43, 10.83, -132.84, 0.00 -44, 11.03, -140.07, 0.00 -45, 11.07, -146.01, 0.00 -46, 10.87, -149.29, 0.00 -47, 10.24, -147.08, 0.00 -48, 9.85, -148.58, 0.00 -49, 9.23, -146.95, 0.00 -50, 8.03, -135.66, 0.00 -51, 5.02, -90.65, 0.00 -52, 8.44, -164.34, 0.00 -53, 24.61, -521.74, 0.00 -54, 25.24, -590.05, 0.00 -55, 21.35, -559.54, 0.00 -56, 18.43, -553.39, 0.00 -57, 15.47, -548.45, 0.00 -58, 12.34, -539.47, 0.00 -59, 9.16, -530.51, 0.00 -60, 5.93, -520.02, 0.00 -61, 2.66, -509.08, 0.00 -62, -0.61, -496.20, 0.00 -63, -3.87, -482.86, 0.00 -64, -7.09, -468.17, 0.00 -65, -10.23, -452.82, 0.00 -66, -13.28, -436.19, 0.00 -67, -16.22, -419.41, 0.00 -68, -19.00, -401.35, 0.00 -69, -21.63, -383.22, 0.00 -70, -24.05, -364.23, 0.00 -71, -26.30, -345.36, 0.00 -72, -28.26, -325.47, 0.00 -73, -30.06, -306.30, 0.00 -74, -31.47, -285.73, 0.00 -75, -32.68, -265.95, 0.00 -76, -33.54, -245.70, 0.00 -77, -34.32, -227.17, 0.00 -78, -34.48, -206.77, 0.00 -79, -34.55, -188.05, 0.00 -80, -33.88, -167.61, 0.00 -81, -33.34, -150.01, 0.00 -82, -31.79, -130.08, 0.00 -83, -30.47, -113.26, 0.00 -84, -27.83, -93.86, 0.00 -85, -26.36, -80.46, 0.00 -86, -22.80, -62.77, 0.00 -87, -20.38, -50.40, 0.00 -88, -16.48, -36.38, 0.00 -89, -13.50, -26.40, 0.00 -90, -8.89, -15.26, 0.00 -91, 2.07, 3.08, 0.00 -92, 9.08, 11.50, 0.00 -93, 13.77, 14.52, 0.00 -94, 20.38, 17.31, 0.00 -95, 38.32, 24.96, 0.00 -96, 51.52, 23.61, 0.00 -97, -11.06, -2.99, 0.00 -98, 9.27, 1.24, 0.00 -99, 56.99, -0.00, 0.00 -100, 121.78, -16.34, 0.00 -101, 216.03, -58.47, 0.00 -102, 204.05, -93.53, 0.00 -103, 153.71, -100.12, 0.00 -104, 131.85, -112.01, 0.00 -105, 83.63, -88.18, 0.00 -106, 65.02, -82.34, 0.00 -107, 36.90, -54.86, 0.00 -108, 24.12, -41.38, 0.00 -109, 3.60, -7.04, 0.00 -110, -5.15, 11.37, 0.00 -111, -17.47, 43.19, 0.00 -112, -23.69, 65.22, 0.00 -113, -32.00, 97.66, 0.00 -114, -36.22, 122.16, 0.00 -115, -40.69, 151.27, 0.00 -116, -42.86, 175.35, 0.00 -117, -45.56, 205.00, 0.00 -118, -46.38, 229.45, 0.00 -119, -47.11, 256.41, 0.00 -120, -46.83, 280.77, 0.00 -121, -46.29, 306.36, 0.00 -122, -45.08, 330.26, 0.00 -123, -43.57, 354.56, 0.00 -124, -41.55, 377.28, 0.00 -125, -39.29, 400.30, 0.00 -126, -36.74, 423.08, 0.00 -127, -33.87, 444.73, 0.00 -128, -30.79, 466.24, 0.00 -129, -27.45, 486.37, 0.00 -130, -23.98, 506.62, 0.00 -131, -20.34, 525.93, 0.00 -132, -16.59, 545.08, 0.00 -133, -12.73, 563.34, 0.00 -134, -8.79, 580.87, 0.00 -135, -4.79, 597.60, 0.00 -136, -0.76, 613.87, 0.00 -137, 3.29, 629.26, 0.00 -138, 7.33, 643.22, 0.00 -139, 11.34, 656.71, 0.00 -140, 15.31, 669.24, 0.00 -141, 19.20, 680.50, 0.00 -142, 23.00, 690.79, 0.00 -143, 26.72, 700.41, 0.00 -144, 30.30, 708.54, 0.00 -145, 33.76, 715.60, 0.00 -146, 37.07, 721.53, 0.00 -147, 40.24, 726.68, 0.00 -148, 43.22, 730.29, 0.00 -149, 46.10, 733.75, 0.00 -150, 48.72, 734.79, 0.00 -151, 51.06, 733.35, 0.00 -152, 54.83, 753.18, 0.00 -153, 51.89, 684.30, 0.00 -154, 8.03, 102.02, 0.00 -155, -1.05, -12.88, 0.00 -156, 3.20, 38.01, 0.00 -157, 3.99, 45.94, 0.00 -158, 4.27, 47.80, 0.00 -159, 4.38, 47.80, 0.00 -160, 4.41, 46.91, 0.00 -161, 4.21, 43.76, 0.00 -162, 3.97, 40.34, 0.00 -163, 3.55, 35.36, 0.00 -164, 3.12, 30.39, 0.00 -165, 2.57, 24.58, 0.00 -166, 2.00, 18.81, 0.00 -167, 1.36, 12.58, 0.00 -168, 0.72, 6.55, 0.00 -169, 0.01, 0.07, 0.00 -170, -0.70, -6.10, 0.00 -171, -1.46, -12.60, 0.00 -172, -2.18, -18.54, 0.00 -173, -2.95, -24.73, 0.00 -174, -3.66, -30.30, 0.00 -175, -4.40, -35.94, 0.00 -176, -5.09, -40.98, 0.00 -177, -5.78, -45.96, 0.00 -178, -6.39, -50.22, 0.00 -179, -7.01, -54.42, 0.00 -180, -7.54, -57.90, 0.00 -181, -8.04, -61.14, 0.00 -182, -8.47, -63.70, 0.00 -183, -8.82, -65.68, 0.00 -184, -9.10, -67.17, 0.00 -185, -9.28, -67.93, 0.00 -186, -9.39, -68.16, 0.00 -187, -9.41, -67.78, 0.00 -188, -9.32, -66.65, 0.00 -189, -9.21, -65.43, 0.00 -190, -8.95, -63.24, 0.00 -191, -8.66, -60.83, 0.00 -192, -8.20, -57.34, 0.00 -193, -7.65, -53.27, 0.00 -194, -6.97, -48.35, 0.00 -195, -6.12, -42.37, 0.00 -196, -5.16, -35.65, 0.00 -197, -4.06, -27.97, 0.00 -198, -2.82, -19.43, 0.00 +199, -0.96, -0.00, 0.00 +0, -2.91, 20.04, 0.00 +1, -4.11, 28.35, 0.00 +2, -5.18, 35.78, 0.00 +3, -6.13, 42.38, 0.00 +4, -6.94, 48.17, 0.00 +5, -7.61, 52.99, 0.00 +6, -8.17, 57.11, 0.00 +7, -8.59, 60.35, 0.00 +8, -8.90, 62.89, 0.00 +9, -9.09, 64.57, 0.00 +10, -9.19, 65.68, 0.00 +11, -9.17, 66.06, 0.00 +12, -9.08, 65.87, 0.00 +13, -8.87, 64.88, 0.00 +14, -8.59, 63.44, 0.00 +15, -8.22, 61.21, 0.00 +16, -7.81, 58.75, 0.00 +17, -7.31, 55.54, 0.00 +18, -6.77, 52.03, 0.00 +19, -6.18, 48.05, 0.00 +20, -5.54, 43.54, 0.00 +21, -4.85, 38.56, 0.00 +22, -4.12, 33.16, 0.00 +23, -3.34, 27.27, 0.00 +24, -2.55, 21.11, 0.00 +25, -1.73, 14.49, 0.00 +26, -0.89, 7.57, 0.00 +27, -0.02, 0.21, 0.00 +28, 0.84, -7.36, 0.00 +29, 1.70, -15.16, 0.00 +30, 2.57, -23.26, 0.00 +31, 3.43, -31.62, 0.00 +32, 4.29, -40.23, 0.00 +33, 5.12, -48.93, 0.00 +34, 5.95, -57.97, 0.00 +35, 6.74, -67.11, 0.00 +36, 7.54, -76.64, 0.00 +37, 8.29, -86.18, 0.00 +38, 9.02, -96.03, 0.00 +39, 9.71, -105.89, 0.00 +40, 10.32, -115.64, 0.00 +41, 10.88, -125.42, 0.00 +42, 11.33, -134.52, 0.00 +43, 11.67, -143.10, 0.00 +44, 11.89, -151.03, 0.00 +45, 11.97, -157.81, 0.00 +46, 11.83, -162.44, 0.00 +47, 11.29, -162.21, 0.00 +48, 10.84, -163.47, 0.00 +49, 10.25, -163.16, 0.00 +50, 9.08, -153.46, 0.00 +51, 6.10, -110.12, 0.00 +52, 8.72, -169.72, 0.00 +53, 24.65, -522.48, 0.00 +54, 25.91, -605.84, 0.00 +55, 21.86, -573.09, 0.00 +56, 18.83, -565.58, 0.00 +57, 15.79, -559.72, 0.00 +58, 12.61, -551.18, 0.00 +59, 9.36, -541.85, 0.00 +60, 6.06, -531.49, 0.00 +61, 2.72, -519.62, 0.00 +62, -0.63, -506.79, 0.00 +63, -3.95, -492.32, 0.00 +64, -7.23, -477.72, 0.00 +65, -10.43, -461.55, 0.00 +66, -13.55, -444.97, 0.00 +67, -16.52, -426.99, 0.00 +68, -19.36, -408.93, 0.00 +69, -21.98, -389.46, 0.00 +70, -24.46, -370.41, 0.00 +71, -26.62, -349.56, 0.00 +72, -28.59, -329.24, 0.00 +73, -30.11, -306.85, 0.00 +74, -31.49, -285.97, 0.00 +75, -32.34, -263.19, 0.00 +76, -32.98, -241.59, 0.00 +77, -33.01, -218.48, 0.00 +78, -32.78, -196.56, 0.00 +79, -31.73, -172.68, 0.00 +80, -30.58, -151.31, 0.00 +81, -28.51, -128.29, 0.00 +82, -26.22, -107.28, 0.00 +83, -23.00, -85.52, 0.00 +84, -19.42, -65.49, 0.00 +85, -14.71, -44.89, 0.00 +86, -9.40, -25.89, 0.00 +87, -3.54, -8.76, 0.00 +88, 4.27, 9.41, 0.00 +89, 13.63, 26.65, 0.00 +90, 23.53, 40.38, 0.00 +91, 30.76, 45.73, 0.00 +92, 36.09, 45.71, 0.00 +93, 43.48, 45.84, 0.00 +94, 61.05, 51.86, 0.00 +95, 49.16, 32.02, 0.00 +96, 55.00, 25.21, 0.00 +97, 57.99, 15.70, 0.00 +98, 59.74, 8.02, 0.00 +99, 58.62, -0.00, 0.00 +100, 103.03, -13.83, 0.00 +101, 190.93, -51.68, 0.00 +102, 170.14, -77.99, 0.00 +103, 143.57, -93.51, 0.00 +104, 125.13, -106.30, 0.00 +105, 80.00, -84.35, 0.00 +106, 62.71, -79.42, 0.00 +107, 32.87, -48.86, 0.00 +108, 21.18, -36.34, 0.00 +109, -0.06, 0.13, 0.00 +110, -8.43, 18.60, 0.00 +111, -21.17, 52.35, 0.00 +112, -27.02, 74.41, 0.00 +113, -35.55, 108.52, 0.00 +114, -39.68, 133.84, 0.00 +115, -43.82, 162.90, 0.00 +116, -45.71, 187.00, 0.00 +117, -47.84, 215.27, 0.00 +118, -48.41, 239.50, 0.00 +119, -48.73, 265.23, 0.00 +120, -48.26, 289.37, 0.00 +121, -47.44, 313.96, 0.00 +122, -46.10, 337.69, 0.00 +123, -44.41, 361.33, 0.00 +124, -42.27, 383.81, 0.00 +125, -39.86, 406.13, 0.00 +126, -37.22, 428.63, 0.00 +127, -34.23, 449.48, 0.00 +128, -31.09, 470.82, 0.00 +129, -27.67, 490.26, 0.00 +130, -24.15, 510.22, 0.00 +131, -20.47, 529.16, 0.00 +132, -16.69, 548.12, 0.00 +133, -12.79, 565.92, 0.00 +134, -8.83, 583.35, 0.00 +135, -4.81, 599.76, 0.00 +136, -0.76, 615.87, 0.00 +137, 3.30, 631.04, 0.00 +138, 7.35, 644.84, 0.00 +139, 11.36, 657.96, 0.00 +140, 15.33, 670.28, 0.00 +141, 19.21, 681.06, 0.00 +142, 23.02, 691.20, 0.00 +143, 26.71, 700.31, 0.00 +144, 30.30, 708.38, 0.00 +145, 33.71, 714.71, 0.00 +146, 37.02, 720.63, 0.00 +147, 40.16, 725.20, 0.00 +148, 43.14, 728.78, 0.00 +149, 45.94, 731.11, 0.00 +150, 48.55, 732.27, 0.00 +151, 50.85, 730.39, 0.00 +152, 54.67, 750.98, 0.00 +153, 51.36, 677.32, 0.00 +154, 7.16, 90.97, 0.00 +155, -1.26, -15.46, 0.00 +156, 2.80, 33.26, 0.00 +157, 3.27, 37.72, 0.00 +158, 3.54, 39.66, 0.00 +159, 3.81, 41.53, 0.00 +160, 3.88, 41.25, 0.00 +161, 3.69, 38.38, 0.00 +162, 3.47, 35.31, 0.00 +163, 3.07, 30.54, 0.00 +164, 2.67, 26.04, 0.00 +165, 2.11, 20.15, 0.00 +166, 1.57, 14.73, 0.00 +167, 0.90, 8.32, 0.00 +168, 0.29, 2.59, 0.00 +169, -0.41, -3.70, 0.00 +170, -1.08, -9.45, 0.00 +171, -1.85, -16.01, 0.00 +172, -2.55, -21.66, 0.00 +173, -3.32, -27.82, 0.00 +174, -4.00, -33.09, 0.00 +175, -4.72, -38.54, 0.00 +176, -5.37, -43.26, 0.00 +177, -6.02, -47.89, 0.00 +178, -6.59, -51.77, 0.00 +179, -7.15, -55.52, 0.00 +180, -7.62, -58.52, 0.00 +181, -8.12, -61.69, 0.00 +182, -8.50, -63.91, 0.00 +183, -8.88, -66.15, 0.00 +184, -9.15, -67.52, 0.00 +185, -9.40, -68.80, 0.00 +186, -9.53, -69.15, 0.00 +187, -9.60, -69.17, 0.00 +188, -9.54, -68.24, 0.00 +189, -9.36, -66.47, 0.00 +190, -9.10, -64.24, 0.00 +191, -8.71, -61.18, 0.00 +192, -8.23, -57.52, 0.00 +193, -7.62, -53.07, 0.00 +194, -6.93, -48.05, 0.00 +195, -6.09, -42.14, 0.00 +196, -5.14, -35.49, 0.00 +197, -4.05, -27.93, 0.00 +198, -2.82, -19.39, 0.00 diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index d9968f91b917..830ce48e4a56 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -101,7 +101,7 @@ def main(): naca0012.cfg_dir = "euler/naca0012" naca0012.cfg_file = "inv_NACA0012_Roe.cfg" naca0012.test_iter = 20 - naca0012.test_vals = [-4.507695, -3.938772, 0.297705, 0.025422] + naca0012.test_vals = [-4.506451, -3.937611, 0.297774, 0.025416] test_list.append(naca0012) # Supersonic wedge @@ -109,7 +109,7 @@ def main(): wedge.cfg_dir = "euler/wedge" wedge.cfg_file = "inv_wedge_HLLC.cfg" wedge.test_iter = 20 - wedge.test_vals = [-3.703839, 2.020469, -0.249531, 0.043953] + wedge.test_vals = [-3.701409, 2.023011, -0.249531, 0.043953] test_list.append(wedge) # ONERA M6 Wing @@ -135,7 +135,7 @@ def main(): polar_naca0012.cfg_file = "inv_NACA0012.cfg" polar_naca0012.polar = True polar_naca0012.test_iter = 10 - polar_naca0012.test_vals = [-1.278858, 4.165254, 0.004410, 0.083785] + polar_naca0012.test_vals = [-1.278626, 4.165535, 0.004509, 0.083680] polar_naca0012.test_vals_aarch64 = [-1.063447, 4.401847, 0.000291, 0.031696] polar_naca0012.command = TestCase.Command(exec = "compute_polar.py", param = "-n 1 -i 11") # flaky test on arm64 @@ -147,7 +147,7 @@ def main(): bluntbody.cfg_dir = "euler/bluntbody" bluntbody.cfg_file = "blunt.cfg" bluntbody.test_iter = 20 - bluntbody.test_vals = [0.475378, 6.834898, 0.000000, 1.783956] + bluntbody.test_vals = [0.666162, 7.055023, -0.000093, 3.916825] test_list.append(bluntbody) ########################## @@ -198,7 +198,7 @@ def main(): poiseuille_profile.cfg_dir = "navierstokes/poiseuille" poiseuille_profile.cfg_file = "profile_poiseuille.cfg" poiseuille_profile.test_iter = 10 - poiseuille_profile.test_vals = [-12.003743, -7.573505, -0.000000, 2.089953] + poiseuille_profile.test_vals = [-12.003748, -7.573681, -0.000000, 2.089953] poiseuille_profile.test_vals_aarch64 = [-12.009012, -7.262299, -0.000000, 2.089953] #last 4 columns test_list.append(poiseuille_profile) @@ -218,7 +218,7 @@ def main(): rae2822_sa.cfg_dir = "rans/rae2822" rae2822_sa.cfg_file = "turb_SA_RAE2822.cfg" rae2822_sa.test_iter = 20 - rae2822_sa.test_vals = [-2.187684, -5.308480, 0.389353, 0.077079, 0.000000] + rae2822_sa.test_vals = [-2.187576, -5.308498, 0.389360, 0.077088, 0.000000] test_list.append(rae2822_sa) # RAE2822 SST @@ -226,7 +226,7 @@ def main(): rae2822_sst.cfg_dir = "rans/rae2822" rae2822_sst.cfg_file = "turb_SST_RAE2822.cfg" rae2822_sst.test_iter = 20 - rae2822_sst.test_vals = [-1.028239, 5.864396, 0.357218, 0.074732, 0.000000] + rae2822_sst.test_vals = [-1.028239, 5.864395, 0.357230, 0.074731, 0.000000] test_list.append(rae2822_sst) # RAE2822 SST_SUST @@ -234,7 +234,7 @@ def main(): rae2822_sst_sust.cfg_dir = "rans/rae2822" rae2822_sst_sust.cfg_file = "turb_SST_SUST_RAE2822.cfg" rae2822_sst_sust.test_iter = 20 - rae2822_sst_sust.test_vals = [-2.487553, 5.864385, 0.357218, 0.074732] + rae2822_sst_sust.test_vals = [-2.487567, 5.864384, 0.357230, 0.074731] test_list.append(rae2822_sst_sust) # Flat plate @@ -250,7 +250,7 @@ def main(): turb_wallfunction_flatplate_sst.cfg_dir = "wallfunctions/flatplate/compressible_SST" turb_wallfunction_flatplate_sst.cfg_file = "turb_SST_flatplate.cfg" turb_wallfunction_flatplate_sst.test_iter = 10 - turb_wallfunction_flatplate_sst.test_vals = [-4.036299, -1.916263, -1.821788, 1.443878, -1.579554, 1.521534, 10.000000, -2.351461, 0.030011, 0.002409] + turb_wallfunction_flatplate_sst.test_vals = [-4.036300, -1.916264, -1.821790, 1.443878, -1.579554, 1.521535, 10.000000, -2.351460, 0.030011, 0.002409] test_list.append(turb_wallfunction_flatplate_sst) # FLAT PLATE, ROUGHNESS BC WILCOX2006 SST @@ -274,7 +274,7 @@ def main(): turb_oneram6.cfg_dir = "rans/oneram6" turb_oneram6.cfg_file = "turb_ONERAM6.cfg" turb_oneram6.test_iter = 10 - turb_oneram6.test_vals = [-2.408665, -6.628342, 0.238581, 0.158951, 0.000000] + turb_oneram6.test_vals = [-2.418713, -6.631577, 0.238587, 0.159599, 0.000000] turb_oneram6.timeout = 3200 test_list.append(turb_oneram6) @@ -283,7 +283,7 @@ def main(): turb_naca0012_sa.cfg_dir = "rans/naca0012" turb_naca0012_sa.cfg_file = "turb_NACA0012_sa.cfg" turb_naca0012_sa.test_iter = 5 - turb_naca0012_sa.test_vals = [-12.037341, -16.384159, 1.080346, 0.018385, 20, -3.455927, 20, -4.641252, 0] + turb_naca0012_sa.test_vals = [-12.037366, -16.384159, 1.080346, 0.018385, 20.000000, -3.456447, 20.000000, -4.641258, 0.000000] turb_naca0012_sa.test_vals_aarch64 = [-12.037297, -16.384158, 1.080346, 0.018385, 20.000000, -3.455886, 20.000000, -4.641247, 0.000000] turb_naca0012_sa.timeout = 3200 test_list.append(turb_naca0012_sa) @@ -293,7 +293,7 @@ def main(): turb_naca0012_sst.cfg_dir = "rans/naca0012" turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" turb_naca0012_sst.test_iter = 10 - turb_naca0012_sst.test_vals = [-12.094371, -15.251083, -5.906366, 1.070413, 0.015775, -3.178593, 0] + turb_naca0012_sst.test_vals = [-12.094425, -15.251083, -5.906366, 1.070413, 0.015775, -3.178933, 0.000000] turb_naca0012_sst.test_vals_aarch64 = [-12.076068, -15.246740, -5.861280, 1.070036, 0.015841, -3.297854, 0.000000] turb_naca0012_sst.timeout = 3200 test_list.append(turb_naca0012_sst) @@ -312,7 +312,7 @@ def main(): turb_naca0012_sst_sust_restart.cfg_dir = "rans/naca0012" turb_naca0012_sst_sust_restart.cfg_file = "turb_NACA0012_sst_sust.cfg" turb_naca0012_sst_sust_restart.test_iter = 10 - turb_naca0012_sst_sust_restart.test_vals = [-12.080495, -14.837169, -5.733461, 1.000893, 0.019109, -2.634055] + turb_naca0012_sst_sust_restart.test_vals = [-12.080423, -14.837169, -5.733461, 1.000893, 0.019109, -2.634145] turb_naca0012_sst_sust_restart.test_vals_aarch64 = [-12.074189, -14.836725, -5.732398, 1.000050, 0.019144, -3.315560] turb_naca0012_sst_sust_restart.timeout = 3200 test_list.append(turb_naca0012_sst_sust_restart) @@ -322,7 +322,7 @@ def main(): turb_naca0012_sst_fixedvalues.cfg_dir = "rans/naca0012" turb_naca0012_sst_fixedvalues.cfg_file = "turb_NACA0012_sst_fixedvalues.cfg" turb_naca0012_sst_fixedvalues.test_iter = 10 - turb_naca0012_sst_fixedvalues.test_vals = [-5.206619, -10.436764, 0.774095, 1.021995, 0.040553, -3.477596] + turb_naca0012_sst_fixedvalues.test_vals = [-5.206618, -10.436758, 0.774095, 1.021995, 0.040553, -3.477597] turb_naca0012_sst_fixedvalues.timeout = 3200 test_list.append(turb_naca0012_sst_fixedvalues) @@ -344,7 +344,7 @@ def main(): axi_rans_air_nozzle_restart.cfg_dir = "axisymmetric_rans/air_nozzle" axi_rans_air_nozzle_restart.cfg_file = "air_nozzle_restart.cfg" axi_rans_air_nozzle_restart.test_iter = 10 - axi_rans_air_nozzle_restart.test_vals = [-12.067609, -7.525629, -8.817136, -3.735731, 0] + axi_rans_air_nozzle_restart.test_vals = [-11.053602, -5.329000, -8.835107, -4.056879, 0.000000] axi_rans_air_nozzle_restart.test_vals_aarch64 = [-14.143715, -9.170705, -10.848554, -5.776746, 0.000000] axi_rans_air_nozzle_restart.tol = 0.0001 test_list.append(axi_rans_air_nozzle_restart) @@ -603,7 +603,7 @@ def main(): contadj_wedge.cfg_dir = "cont_adj_euler/wedge" contadj_wedge.cfg_file = "inv_wedge_ROE.cfg" contadj_wedge.test_iter = 10 - contadj_wedge.test_vals = [2.872064, -2.756210, 1010800.000000, 0.000000] + contadj_wedge.test_vals = [2.872066, -2.756215, 1010800.000000, -0.000000] test_list.append(contadj_wedge) # Inviscid fixed CL NACA0012 @@ -686,7 +686,7 @@ def main(): turb_naca0012_1c.cfg_dir = "rans_uq/naca0012" turb_naca0012_1c.cfg_file = "turb_NACA0012_uq_1c.cfg" turb_naca0012_1c.test_iter = 10 - turb_naca0012_1c.test_vals = [-4.981677, 1.343534, 0.597773, 0.017412] + turb_naca0012_1c.test_vals = [-4.981677, 1.343533, 0.597773, 0.017451] turb_naca0012_1c.test_vals_aarch64 = [-4.992791, 1.342873, 0.557941, 0.003269] test_list.append(turb_naca0012_1c) @@ -695,7 +695,7 @@ def main(): turb_naca0012_2c.cfg_dir = "rans_uq/naca0012" turb_naca0012_2c.cfg_file = "turb_NACA0012_uq_2c.cfg" turb_naca0012_2c.test_iter = 10 - turb_naca0012_2c.test_vals = [-5.482890, 1.261920, 0.441122, -0.029557] + turb_naca0012_2c.test_vals = [-5.482890, 1.261920, 0.441111, -0.029535] test_list.append(turb_naca0012_2c) # NACA0012 3c @@ -703,7 +703,7 @@ def main(): turb_naca0012_3c.cfg_dir = "rans_uq/naca0012" turb_naca0012_3c.cfg_file = "turb_NACA0012_uq_3c.cfg" turb_naca0012_3c.test_iter = 10 - turb_naca0012_3c.test_vals = [-5.583768, 1.229824, 0.426251, -0.033154] + turb_naca0012_3c.test_vals = [-5.583768, 1.229824, 0.426258, -0.033150] test_list.append(turb_naca0012_3c) # NACA0012 p1c1 @@ -711,7 +711,7 @@ def main(): turb_naca0012_p1c1.cfg_dir = "rans_uq/naca0012" turb_naca0012_p1c1.cfg_file = "turb_NACA0012_uq_p1c1.cfg" turb_naca0012_p1c1.test_iter = 10 - turb_naca0012_p1c1.test_vals = [-5.127673, 1.284889, 0.779305, 0.086111] + turb_naca0012_p1c1.test_vals = [-5.127673, 1.284889, 0.779311, 0.086115] turb_naca0012_p1c1.test_vals_aarch64 = [-5.119942, 1.283920, 0.486264, -0.021518] test_list.append(turb_naca0012_p1c1) @@ -720,7 +720,7 @@ def main(): turb_naca0012_p1c2.cfg_dir = "rans_uq/naca0012" turb_naca0012_p1c2.cfg_file = "turb_NACA0012_uq_p1c2.cfg" turb_naca0012_p1c2.test_iter = 10 - turb_naca0012_p1c2.test_vals = [-5.553903, 1.235087, 0.521251, -0.004471] + turb_naca0012_p1c2.test_vals = [-5.553904, 1.235086, 0.521256, -0.004467] test_list.append(turb_naca0012_p1c2) ###################################### @@ -740,7 +740,7 @@ def main(): hb_rans_preconditioning.cfg_dir = "harmonic_balance/hb_rans_preconditioning" hb_rans_preconditioning.cfg_file = "davis.cfg" hb_rans_preconditioning.test_iter = 25 - hb_rans_preconditioning.test_vals = [-1.902084, 0.484233, 0.601496, 3.609018, -5.943873] + hb_rans_preconditioning.test_vals = [-1.905206, 0.481900, 0.599004, 3.605363, -5.945837] test_list.append(hb_rans_preconditioning) ###################################### @@ -752,7 +752,7 @@ def main(): rot_naca0012.cfg_dir = "rotating/naca0012" rot_naca0012.cfg_file = "rot_NACA0012.cfg" rot_naca0012.test_iter = 25 - rot_naca0012.test_vals = [-1.290464, 4.245388, -0.000518, 0.112880] + rot_naca0012.test_vals = [-1.291080, 4.244879, -0.000543, 0.112765] test_list.append(rot_naca0012) # Lid-driven cavity @@ -862,7 +862,7 @@ def main(): ls89_sa.cfg_dir = "nicf/LS89" ls89_sa.cfg_file = "turb_SA_PR.cfg" ls89_sa.test_iter = 20 - ls89_sa.test_vals = [-5.069399, -13.403603, 0.180485, 0.429457] + ls89_sa.test_vals = [-5.069400, -13.403604, 0.180485, 0.429458] test_list.append(ls89_sa) # Rarefaction shock wave edge_VW @@ -870,7 +870,7 @@ def main(): edge_VW.cfg_dir = "nicf/edge" edge_VW.cfg_file = "edge_VW.cfg" edge_VW.test_iter = 20 - edge_VW.test_vals = [-2.851854, 3.349325, -0.000009, 0.000000] + edge_VW.test_vals = [-2.807248, 3.393885, -0.000010, 0.000000] test_list.append(edge_VW) # Rarefaction shock wave edge_PPR @@ -878,7 +878,7 @@ def main(): edge_PPR.cfg_dir = "nicf/edge" edge_PPR.cfg_file = "edge_PPR.cfg" edge_PPR.test_iter = 20 - edge_PPR.test_vals = [-12.476580, -6.287745, -0.000034, 0.000000] + edge_PPR.test_vals = [-12.344691, -6.171790, -0.000034, 0.000000] test_list.append(edge_PPR) @@ -900,7 +900,7 @@ def main(): Jones_tc_restart.cfg_dir = "turbomachinery/APU_turbocharger" Jones_tc_restart.cfg_file = "Jones_restart.cfg" Jones_tc_restart.test_iter = 5 - Jones_tc_restart.test_vals = [-7.645867, -5.849734, -15.337011, -9.825761, -13.216108, -7.752293, 73286.000000, 73286.000000, 0.020055, 82.286000] + Jones_tc_restart.test_vals = [-7.645867, -5.849734, -15.337011, -9.825760, -13.216108, -7.752293, 73286.000000, 73286.000000, 0.020055, 82.286000] test_list.append(Jones_tc_restart) # 2D axial stage @@ -908,7 +908,7 @@ def main(): axial_stage2D.cfg_dir = "turbomachinery/axial_stage_2D" axial_stage2D.cfg_file = "Axial_stage2D.cfg" axial_stage2D.test_iter = 20 - axial_stage2D.test_vals = [1.167176, 1.598840, -2.928275, 2.573906, -2.526639, 3.017139, 106370.000000, 106370.000000, 5.726800, 64.383000] + axial_stage2D.test_vals = [1.167182, 1.598495, -2.928576, 2.573645, -2.527392, 3.016170, 106370.000000, 106370.000000, 5.726800, 64.383000] test_list.append(axial_stage2D) # 2D transonic stator restart @@ -958,7 +958,7 @@ def main(): channel_2D.cfg_dir = "sliding_interface/channel_2D" channel_2D.cfg_file = "channel_2D_WA.cfg" channel_2D.test_iter = 2 - channel_2D.test_vals = [2.000000, 0.000000, 0.464920, 0.348054, 0.397553] + channel_2D.test_vals = [2.000000, 0.000000, 0.466199, 0.350095, 0.399015] channel_2D.timeout = 100 channel_2D.unsteady = True channel_2D.multizone = True @@ -969,7 +969,7 @@ def main(): channel_3D.cfg_dir = "sliding_interface/channel_3D" channel_3D.cfg_file = "channel_3D_WA.cfg" channel_3D.test_iter = 1 - channel_3D.test_vals = [1.000000, 0.000000, 0.611998, 0.798899, 0.702676] + channel_3D.test_vals = [1.000000, 0.000000, 0.617233, 0.798811, 0.692599] channel_3D.test_vals_aarch64 = [1.000000, 0.000000, 0.611996, 0.798988, 0.702357] channel_3D.unsteady = True channel_3D.multizone = True @@ -981,7 +981,7 @@ def main(): pipe.cfg_dir = "sliding_interface/pipe" pipe.cfg_file = "pipe_NN.cfg" pipe.test_iter = 2 - pipe.test_vals = [0.547322, 0.655095, 0.968232, 1.049121] + pipe.test_vals = [0.568973, 0.692858, 0.989456, 1.048237] pipe.unsteady = True pipe.multizone = True test_list.append(pipe) @@ -991,7 +991,7 @@ def main(): rotating_cylinders.cfg_dir = "sliding_interface/rotating_cylinders" rotating_cylinders.cfg_file = "rot_cylinders_WA.cfg" rotating_cylinders.test_iter = 3 - rotating_cylinders.test_vals = [3.000000, 0.000000, 0.717067, 1.119816, 1.160327] + rotating_cylinders.test_vals = [3.000000, 0.000000, 0.664822, 1.125810, 1.117607] rotating_cylinders.unsteady = True rotating_cylinders.multizone = True test_list.append(rotating_cylinders) @@ -1001,7 +1001,7 @@ def main(): supersonic_vortex_shedding.cfg_dir = "sliding_interface/supersonic_vortex_shedding" supersonic_vortex_shedding.cfg_file = "sup_vor_shed_WA.cfg" supersonic_vortex_shedding.test_iter = 5 - supersonic_vortex_shedding.test_vals = [5.000000, 0.000000, 1.207119, 1.065262] + supersonic_vortex_shedding.test_vals = [5.000000, 0.000000, 0.899637, 1.076225] supersonic_vortex_shedding.unsteady = True supersonic_vortex_shedding.multizone = True test_list.append(supersonic_vortex_shedding) @@ -1122,7 +1122,7 @@ def main(): p1rad.cfg_dir = "radiation/p1model" p1rad.cfg_file = "configp1.cfg" p1rad.test_iter = 50 - p1rad.test_vals = [-8.284674, -8.008659, -2.424204, 0.389030, -56.560000] + p1rad.test_vals = [-8.284673, -8.008659, -2.424204, 0.389030, -56.560000] test_list.append(p1rad) # ############################### @@ -1141,7 +1141,7 @@ def main(): cht_incompressible.cfg_dir = "coupled_cht/incomp_2d" cht_incompressible.cfg_file = "cht_2d_3cylinders.cfg" cht_incompressible.test_iter = 10 - cht_incompressible.test_vals = [-1.376347, -0.591210, -0.591210, -0.591210] + cht_incompressible.test_vals = [-1.376345, -0.591212, -0.591212, -0.591212] cht_incompressible.multizone = True test_list.append(cht_incompressible) @@ -1463,7 +1463,7 @@ def main(): shape_opt_euler_py.cfg_dir = "optimization_euler/steady_naca0012" shape_opt_euler_py.cfg_file = "inv_NACA0012_adv.cfg" shape_opt_euler_py.test_iter = 1 - shape_opt_euler_py.test_vals = [1.000000, 1.000000, 0.000021, 0.003643] + shape_opt_euler_py.test_vals = [1.000000, 1.000000, 0.000021, 0.003642] shape_opt_euler_py.command = TestCase.Command(exec = "shape_optimization.py", param = "-g CONTINUOUS_ADJOINT -f") shape_opt_euler_py.timeout = 1600 shape_opt_euler_py.tol = 0.00001 @@ -1528,7 +1528,7 @@ def main(): opt_2surf1obj_py.cfg_dir = "optimization_euler/multiobjective_wedge" opt_2surf1obj_py.cfg_file = "inv_wedge_ROE_2surf_1obj.cfg" opt_2surf1obj_py.test_iter = 1 - opt_2surf1obj_py.test_vals = [1.000000, 1.000000, 2.005030, 0.000476] + opt_2surf1obj_py.test_vals = [1.000000, 1.000000, 2.005032, 0.000474] opt_2surf1obj_py.command = TestCase.Command(exec = "shape_optimization.py", param = "-g CONTINUOUS_ADJOINT -f") opt_2surf1obj_py.timeout = 1600 opt_2surf1obj_py.tol = 0.00001 @@ -1545,7 +1545,7 @@ def main(): pywrapper_naca0012.cfg_dir = "euler/naca0012" pywrapper_naca0012.cfg_file = "inv_NACA0012_Roe.cfg" pywrapper_naca0012.test_iter = 20 - pywrapper_naca0012.test_vals = [-4.507695, -3.938772, 0.297705, 0.025422] + pywrapper_naca0012.test_vals = [-4.506451, -3.937611, 0.297774, 0.025416] pywrapper_naca0012.command = TestCase.Command(exec = "SU2_CFD.py", param = "-f") pywrapper_naca0012.timeout = 1600 pywrapper_naca0012.tol = 0.00001 @@ -1558,7 +1558,7 @@ def main(): pywrapper_turb_naca0012_sst.cfg_dir = "rans/naca0012" pywrapper_turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" pywrapper_turb_naca0012_sst.test_iter = 10 - pywrapper_turb_naca0012_sst.test_vals = [-12.094371, -15.251083, -5.906366, 1.070413, 0.015775, -3.178593, 0] + pywrapper_turb_naca0012_sst.test_vals = [-12.094425, -15.251083, -5.906366, 1.070413, 0.015775, -3.178933, 0.000000] pywrapper_turb_naca0012_sst.test_vals_aarch64 = [-12.076068, -15.246740, -5.861280, 1.070036, 0.015841, -3.297854, 0.000000] pywrapper_turb_naca0012_sst.command = TestCase.Command(exec = "SU2_CFD.py", param = "-f") pywrapper_turb_naca0012_sst.timeout = 3200 diff --git a/TestCases/serial_regression_AD.py b/TestCases/serial_regression_AD.py index baa6e114700f..6e11587b8cf6 100644 --- a/TestCases/serial_regression_AD.py +++ b/TestCases/serial_regression_AD.py @@ -194,7 +194,7 @@ def main(): discadj_heat.cfg_dir = "disc_adj_heat" discadj_heat.cfg_file = "disc_adj_heat.cfg" discadj_heat.test_iter = 10 - discadj_heat.test_vals = [-2.677870, 0.674825, 0.000000, -9.215500] + discadj_heat.test_vals = [-2.677870, 0.674827, 0.000000, -9.215500] test_list.append(discadj_heat) ################################### diff --git a/TestCases/tutorials.py b/TestCases/tutorials.py index 60680004487d..305ab9fb2347 100644 --- a/TestCases/tutorials.py +++ b/TestCases/tutorials.py @@ -59,7 +59,7 @@ def main(): cht_incompressible.cfg_dir = "../Tutorials/multiphysics/steady_cht" cht_incompressible.cfg_file = "cht_2d_3cylinders.cfg" cht_incompressible.test_iter = 10 - cht_incompressible.test_vals = [-1.376347, -0.591210, -0.591210, -0.591210] #last 4 columns + cht_incompressible.test_vals = [-1.376345, -0.591212, -0.591212, -0.591212] cht_incompressible.command = TestCase.Command(exec = "SU2_CFD") cht_incompressible.multizone = True test_list.append(cht_incompressible) @@ -175,7 +175,7 @@ def main(): tutorial_inv_bump.cfg_dir = "../Tutorials/compressible_flow/Inviscid_Bump" tutorial_inv_bump.cfg_file = "inv_channel.cfg" tutorial_inv_bump.test_iter = 0 - tutorial_inv_bump.test_vals = [-1.437425, 4.075857, 0.080374, 0.012019] + tutorial_inv_bump.test_vals = [-1.437425, 4.075857, 0.071414, 0.017198] test_list.append(tutorial_inv_bump) # Inviscid Wedge @@ -183,7 +183,7 @@ def main(): tutorial_inv_wedge.cfg_dir = "../Tutorials/compressible_flow/Inviscid_Wedge" tutorial_inv_wedge.cfg_file = "inv_wedge_HLLC.cfg" tutorial_inv_wedge.test_iter = 0 - tutorial_inv_wedge.test_vals = [-0.481460, 5.253008, -0.253048, 0.044501] + tutorial_inv_wedge.test_vals = [-0.481460, 5.253008, -0.260109, 0.045753] tutorial_inv_wedge.no_restart = True test_list.append(tutorial_inv_wedge) @@ -192,7 +192,7 @@ def main(): tutorial_inv_onera.cfg_dir = "../Tutorials/compressible_flow/Inviscid_ONERAM6" tutorial_inv_onera.cfg_file = "inv_ONERAM6.cfg" tutorial_inv_onera.test_iter = 0 - tutorial_inv_onera.test_vals = [-5.204928, -4.597762, 0.261167, 0.084096] + tutorial_inv_onera.test_vals = [-5.204928, -4.597762, 0.272218, 0.091180] tutorial_inv_onera.no_restart = True test_list.append(tutorial_inv_onera) @@ -237,7 +237,7 @@ def main(): tutorial_trans_flatplate_T3A.cfg_dir = "../Tutorials/compressible_flow/Transitional_Flat_Plate/Langtry_and_Menter/T3A" tutorial_trans_flatplate_T3A.cfg_file = "transitional_LM_model_ConfigFile.cfg" tutorial_trans_flatplate_T3A.test_iter = 20 - tutorial_trans_flatplate_T3A.test_vals = [-5.790137, -2.054834, -3.894661, -0.255074, -1.747074, 5.119341, -3.493237, 0.393262] + tutorial_trans_flatplate_T3A.test_vals = [-5.790137, -2.054834, -3.894660, -0.255074, -1.747074, 5.119341, -3.493237, 0.393262] tutorial_trans_flatplate_T3A.test_vals_aarch64 = [-5.808996, -2.070606, -3.969765, -0.277943, -1.953289, 1.708472, -3.514943, 0.357411] tutorial_trans_flatplate_T3A.no_restart = True test_list.append(tutorial_trans_flatplate_T3A) @@ -247,7 +247,7 @@ def main(): tutorial_trans_flatplate_T3Am.cfg_dir = "../Tutorials/compressible_flow/Transitional_Flat_Plate/Langtry_and_Menter/T3A-" tutorial_trans_flatplate_T3Am.cfg_file = "transitional_LM_model_ConfigFile.cfg" tutorial_trans_flatplate_T3Am.test_iter = 20 - tutorial_trans_flatplate_T3Am.test_vals = [-5.590061, -1.700867, -3.098455, -0.105190, -3.750523, 3.287643, -2.394576, 1.119623] + tutorial_trans_flatplate_T3Am.test_vals = [-5.590222, -1.700867, -3.098733, -0.105332, -3.750523, 3.287643, -2.394576, 1.119623] tutorial_trans_flatplate_T3Am.test_vals_aarch64 = [-5.540938, -1.681627, -2.878831, -0.058224, -3.695533, 3.413628, -2.385345, 1.103633] tutorial_trans_flatplate_T3Am.no_restart = True test_list.append(tutorial_trans_flatplate_T3Am) @@ -257,7 +257,7 @@ def main(): tutorial_trans_e387_sa.cfg_dir = "../Tutorials/compressible_flow/Transitional_Airfoil/Langtry_and_Menter/E387" tutorial_trans_e387_sa.cfg_file = "transitional_SA_LM_model_ConfigFile.cfg" tutorial_trans_e387_sa.test_iter = 20 - tutorial_trans_e387_sa.test_vals = [-6.527027, -5.082051, -0.795021, 1.022607, 0.150175, 2.000000, -9.581277] + tutorial_trans_e387_sa.test_vals = [-6.527027, -5.082048, -0.795021, 1.022607, 0.150183, 2.000000, -9.581276] tutorial_trans_e387_sa.no_restart = True test_list.append(tutorial_trans_e387_sa) @@ -266,7 +266,7 @@ def main(): tutorial_trans_e387_sst.cfg_dir = "../Tutorials/compressible_flow/Transitional_Airfoil/Langtry_and_Menter/E387" tutorial_trans_e387_sst.cfg_file = "transitional_SST_LM_model_ConfigFile.cfg" tutorial_trans_e387_sst.test_iter = 20 - tutorial_trans_e387_sst.test_vals = [-6.532415, -2.932984, 0.401485, 1.078294, 0.188167, 2.000000, -10.005784] + tutorial_trans_e387_sst.test_vals = [-6.532415, -2.932984, 0.401484, 1.078294, 0.188167, 2.000000, -10.005786] tutorial_trans_e387_sst.no_restart = True test_list.append(tutorial_trans_e387_sst) @@ -275,7 +275,7 @@ def main(): tutorial_turb_oneram6.cfg_dir = "../Tutorials/compressible_flow/Turbulent_ONERAM6" tutorial_turb_oneram6.cfg_file = "turb_ONERAM6.cfg" tutorial_turb_oneram6.test_iter = 0 - tutorial_turb_oneram6.test_vals = [-4.564441, -11.533952, 0.330625, 0.097701] + tutorial_turb_oneram6.test_vals = [-4.564441, -11.537596, 0.293674, 0.087628] test_list.append(tutorial_turb_oneram6) # NICFD Nozzle @@ -283,7 +283,7 @@ def main(): tutorial_nicfd_nozzle.cfg_dir = "../Tutorials/compressible_flow/NICFD_nozzle" tutorial_nicfd_nozzle.cfg_file = "NICFD_nozzle.cfg" tutorial_nicfd_nozzle.test_iter = 20 - tutorial_nicfd_nozzle.test_vals = [-1.799850, -6.002739, 4.635400, 0.000000, 0.000000] + tutorial_nicfd_nozzle.test_vals = [-1.800072, -6.002691, 4.635204, 0.000000, 0.000000] tutorial_nicfd_nozzle.no_restart = True test_list.append(tutorial_nicfd_nozzle) @@ -302,7 +302,7 @@ def main(): tutorial_unst_naca0012.cfg_dir = "../Tutorials/compressible_flow/Unsteady_NACA0012" tutorial_unst_naca0012.cfg_file = "unsteady_naca0012.cfg" tutorial_unst_naca0012.test_iter = 520 - tutorial_unst_naca0012.test_vals = [520, 0, -5.294874, 0, 0.307437, 0.796981, 0.000921, 0.006756] + tutorial_unst_naca0012.test_vals = [520.000000, 0.000000, -5.294133, 0.000000, 0.314591, 0.778367, 0.000929, 0.007489] tutorial_unst_naca0012.test_vals_aarch64 = [520, 0, -5.292359, 0, 0.284720, 0.766329, 0.000954, 0.007565] tutorial_unst_naca0012.unsteady = True test_list.append(tutorial_unst_naca0012) @@ -312,7 +312,7 @@ def main(): propeller_var_load.cfg_dir = "../Tutorials/compressible_flow/ActuatorDisk_VariableLoad" propeller_var_load.cfg_file = "propeller_variable_load.cfg" propeller_var_load.test_iter = 20 - propeller_var_load.test_vals = [-1.830257, -4.534990, -0.000323, 0.171646] + propeller_var_load.test_vals = [-1.831126, -4.534989, -0.000323, 0.171579] propeller_var_load.timeout = 3200 test_list.append(propeller_var_load) @@ -323,7 +323,7 @@ def main(): tutorial_design_inv_naca0012.cfg_dir = "../Tutorials/design/Inviscid_2D_Unconstrained_NACA0012" tutorial_design_inv_naca0012.cfg_file = "inv_NACA0012_basic.cfg" tutorial_design_inv_naca0012.test_iter = 0 - tutorial_design_inv_naca0012.test_vals = [-3.585391, -2.989014, 0.169747, 0.235619] + tutorial_design_inv_naca0012.test_vals = [-3.585391, -2.989014, 0.172229, 0.240113] tutorial_design_inv_naca0012.no_restart = True test_list.append(tutorial_design_inv_naca0012) @@ -332,7 +332,7 @@ def main(): tutorial_design_turb_rae2822.cfg_dir = "../Tutorials/design/Turbulent_2D_Constrained_RAE2822" tutorial_design_turb_rae2822.cfg_file = "turb_SA_RAE2822.cfg" tutorial_design_turb_rae2822.test_iter = 0 - tutorial_design_turb_rae2822.test_vals = [-1.700114, -4.941834, 0.218348, 0.190357] + tutorial_design_turb_rae2822.test_vals = [-1.700114, -4.941829, 0.205309, 0.184990] tutorial_design_turb_rae2822.no_restart = True test_list.append(tutorial_design_turb_rae2822) @@ -341,7 +341,7 @@ def main(): tutorial_design_multiobj.cfg_dir = "../Tutorials/design/Multi_Objective_Shape_Design" tutorial_design_multiobj.cfg_file = "inv_wedge_ROE_multiobj_combo.cfg" tutorial_design_multiobj.test_iter = 0 - tutorial_design_multiobj.test_vals = [2.657333, -3.020635, 370220.000000, 0.000000] + tutorial_design_multiobj.test_vals = [2.657333, -3.020635, 370220.000000, -0.000000] tutorial_design_multiobj.no_restart = True test_list.append(tutorial_design_multiobj) diff --git a/TestCases/vandv.py b/TestCases/vandv.py index b78df50eeb30..d14f7507264e 100644 --- a/TestCases/vandv.py +++ b/TestCases/vandv.py @@ -45,7 +45,7 @@ def main(): p30n30.cfg_dir = "vandv/rans/30p30n" p30n30.cfg_file = "config.cfg" p30n30.test_iter = 5 - p30n30.test_vals = [-11.267106, -11.168215, -11.182822, -10.949673, -14.233489, 0.052235, 2.830394, 1.318894, -1.210648, 1, 1.2763e+01] + p30n30.test_vals = [-11.267106, -11.168215, -11.182822, -10.949673, -14.233489, 0.052235, 2.830394, 1.318894, -1.210645, 1.000000, 12.763000] test_list.append(p30n30) # This is not part of the V&V cases yet, its tested in this script because it is a relatively long test (~1 min). @@ -63,7 +63,7 @@ def main(): flatplate_sst1994m.cfg_dir = "vandv/rans/flatplate" flatplate_sst1994m.cfg_file = "turb_flatplate_sst.cfg" flatplate_sst1994m.test_iter = 5 - flatplate_sst1994m.test_vals = [-13.040526, -10.136914, -10.942003, -7.980425, -10.323871, -4.732398, 0.002801] + flatplate_sst1994m.test_vals = [-13.040636, -10.136914, -10.942005, -7.981146, -10.323869, -4.732398, 0.002801] flatplate_sst1994m.test_vals_aarch64 = [-13.021715, -9.534786, -10.401912, -7.501836, -9.750800, -4.850665, 0.002807] test_list.append(flatplate_sst1994m) @@ -72,7 +72,7 @@ def main(): bump_sst1994m.cfg_dir = "vandv/rans/bump_in_channel" bump_sst1994m.cfg_file = "turb_bump_sst.cfg" bump_sst1994m.test_iter = 5 - bump_sst1994m.test_vals = [-11.927868, -10.095409, -9.512544, -6.445154, -11.773530, -6.993606, 0.004931] + bump_sst1994m.test_vals = [-11.928332, -10.095849, -9.512485, -6.445671, -11.773518, -6.998128, 0.004931] bump_sst1994m.test_vals_aarch64 = [-13.042689, -10.812982, -10.604523, -7.655547, -10.816257, -5.308083, 0.004911] test_list.append(bump_sst1994m) @@ -81,7 +81,7 @@ def main(): swbli_sa.cfg_dir = "vandv/rans/swbli" swbli_sa.cfg_file = "config_sa.cfg" swbli_sa.test_iter = 5 - swbli_sa.test_vals = [-11.504424, -10.941741, -12.049925, -10.586263, -16.090385, 0.002242, -1.614365, 1.340100] + swbli_sa.test_vals = [-11.502718, -10.939184, -12.034284, -10.581169, -16.088844, 0.002242, -1.664946, 1.257900] swbli_sa.test_vals_aarch64 = [-11.504424, -10.941741, -12.049925, -10.586263, -16.090385, 0.002242, -1.614365, 1.340100] test_list.append(swbli_sa) @@ -91,7 +91,7 @@ def main(): swbli_sst.cfg_dir = "vandv/rans/swbli" swbli_sst.cfg_file = "config_sst.cfg" swbli_sst.test_iter = 5 - swbli_sst.test_vals = [-11.569218, -10.909086, -11.607984, -10.431163, -11.407588, -2.637660, 0.001816, -1.305818, -3.514590, 13.399000] + swbli_sst.test_vals = [-11.319578, -10.641523, -11.224600, -10.150215, -11.407538, -2.637660, 0.001816, -1.839484, -3.514593, 11.136000] test_list.append(swbli_sst) # DSMA661 - SA @@ -99,7 +99,7 @@ def main(): dsma661_sa.cfg_dir = "vandv/rans/dsma661" dsma661_sa.cfg_file = "dsma661_sa_config.cfg" dsma661_sa.test_iter = 5 - dsma661_sa.test_vals = [-11.247169, -8.242321, -9.020952, -5.903807, -10.737679, 0.155687, 0.024232] + dsma661_sa.test_vals = [-11.255214, -8.242489, -8.996097, -5.916501, -10.737676, 0.155687, 0.024232] dsma661_sa.test_vals_aarch64 = [-11.293183, -8.241775, -9.083761, -6.011398, -10.737680, 0.155687, 0.024232] test_list.append(dsma661_sa) @@ -108,7 +108,7 @@ def main(): dsma661_sst.cfg_dir = "vandv/rans/dsma661" dsma661_sst.cfg_file = "dsma661_sst_config.cfg" dsma661_sst.test_iter = 5 - dsma661_sst.test_vals = [-11.023206, -8.157128, -8.995930, -5.936029, -10.650466, -7.864550, 0.155882, 0.023344] + dsma661_sst.test_vals = [-11.027162, -8.156487, -9.036775, -5.963509, -10.650691, -7.872447, 0.155882, 0.023344] dsma661_sst.test_vals_aarch64 = [-10.977195, -8.403731, -8.747068, -5.808899, -10.522786, -7.369851, 0.155875, 0.023353] test_list.append(dsma661_sst) diff --git a/config_template.cfg b/config_template.cfg index 889abb9f6b1f..998360550b3d 100644 --- a/config_template.cfg +++ b/config_template.cfg @@ -1497,6 +1497,14 @@ CFL_ADAPT= NO % It is reset back to min when linear solvers diverge, or if nonlinear residuals increase too much. CFL_ADAPT_PARAM= ( 0.1, 2.0, 10.0, 1e10, 0.001, 0) % +% For the compressible solver only (EULER, NS, RANS). Detect non-physical solutions statistically instead of +% relying just on zero or negative pressure or temperature, treat all points where temperature is further than N +% standard deviations from the mean as non-physical (MUSCL off and potentially higher dissipation). +% Strategy starts at inner iteration I, mean and sigma update every F, and distribution is printed every P updates. +% OUTLIER_MITIGATION_PARAM= (I, F, P, N). With the default start iteration this is essentially off. +% When starting from freestream, use RAMP_MUSCL and set the start iteration to approximately the end of the MUSCL ramp. +OUTLIER_MITIGATION_PARAM= (999999, 5, 2, 5) +% % Maximum Delta Time in local time stepping simulations MAX_DELTA_TIME= 1E6 % From c3d75633c448ffe1857ec8042426a3edf2852ff2 Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Sat, 4 Jul 2026 21:14:18 -0700 Subject: [PATCH 17/61] Store matrices in LDU format to improve performance of LU-SGS and ILU preconditioners (#2838) * ldu * orientation not necessary * sort to simplify edge map * apply to ILU, cleanup * cleanup * cleanup * cleanup pastix wrapper * avoid two copies in pastix wrapper * missing ZONE_SCOPED * fix UB * update regressions * fix tests * Apply suggestion from @pcarruscag * last update --- Common/include/geometry/CGeometry.hpp | 44 +- .../include/linear_algebra/CPastixWrapper.hpp | 64 ++- Common/include/linear_algebra/CSysMatrix.hpp | 135 +++--- Common/include/linear_algebra/CSysMatrix.inl | 50 ++- Common/include/toolboxes/graph_toolbox.hpp | 94 +++- Common/src/geometry/CGeometry.cpp | 52 +-- Common/src/geometry/CMultiGridGeometry.cpp | 3 + Common/src/geometry/CPhysicalGeometry.cpp | 5 + Common/src/linear_algebra/CPastixWrapper.cpp | 44 +- Common/src/linear_algebra/CSysMatrix.cpp | 408 +++++++++--------- Common/src/linear_algebra/CSysMatrixGPU.cu | 134 ++---- Common/src/linear_algebra/CSysSolve.cpp | 14 +- .../naca0012/of_grad_cd_disc.dat.ref | 78 ++-- .../naca0012/of_grad_directdiff.dat.ref | 6 +- TestCases/disc_adj_fsi/config.cfg | 2 +- TestCases/disc_adj_fsi/configFEA.cfg | 12 +- TestCases/hybrid_regression.py | 132 +++--- TestCases/hybrid_regression_AD.py | 24 +- .../multiple_ffd/naca0012/of_grad_cd.dat.ref | 4 +- .../naca0012/of_grad_directdiff.dat.ref | 4 +- TestCases/parallel_regression.py | 184 ++++---- TestCases/parallel_regression_AD.py | 42 +- .../translating_NACA0012/forces_0.csv.ref | 400 ++++++++--------- .../forces_0.csv.ref | 398 ++++++++--------- .../radiation/p1adjoint/of_grad_cd.csv.ref | 102 ++--- TestCases/serial_regression.py | 120 +++--- TestCases/serial_regression_AD.py | 25 +- TestCases/tutorials.py | 22 +- TestCases/vandv.py | 12 +- UnitTests/Common/geometry/CGeometry_test.cpp | 4 +- meson.build | 5 +- 31 files changed, 1364 insertions(+), 1259 deletions(-) diff --git a/Common/include/geometry/CGeometry.hpp b/Common/include/geometry/CGeometry.hpp index 7958110fb23b..5d1e7e5a83a9 100644 --- a/Common/include/geometry/CGeometry.hpp +++ b/Common/include/geometry/CGeometry.hpp @@ -73,6 +73,19 @@ using namespace std; * \author F. Palacios */ class CGeometry { + public: + /*! + * \brief Aggregates the full symmetric CSR and its LDU split (L strictly-lower, U strictly-upper). + * Built together lazily via GetSparsePattern; all three are always valid once non-empty. + */ + struct LDUSparsePattern { + CCompressedSparsePatternUL csr; /*!< Full symmetric pattern (with diagonal pointer). */ + CCompressedSparsePatternUL l; /*!< Strictly-lower part. */ + CCompressedSparsePatternUL u; /*!< Strictly-upper part. */ + + bool empty() const { return csr.empty(); } + }; + protected: enum : size_t { OMP_MIN_SIZE = 32 }; /*!< \brief Chunk size for small loops. */ enum : size_t { MAXNDIM = 3 }; @@ -187,12 +200,15 @@ class CGeometry { /*--- Sparsity patterns associated with the geometry. ---*/ - CCompressedSparsePatternUL finiteVolumeCSRFill0, /*!< \brief 0-fill FVM sparsity. */ - finiteVolumeCSRFillN, /*!< \brief N-fill FVM sparsity (e.g. for ILUn preconditioner). */ - finiteElementCSRFill0, /*!< \brief 0-fill FEM sparsity. */ - finiteElementCSRFillN; /*!< \brief N-fill FEM sparsity (e.g. for ILUn preconditioner). */ + LDUSparsePattern finiteVolumePatternFill0; /*!< \brief FVM sparsity with 0-fill (structural pattern). */ + LDUSparsePattern finiteVolumePatternFillN; /*!< \brief FVM sparsity with N-fill (e.g. for ILU-N). */ + LDUSparsePattern finiteElementPatternFill0; /*!< \brief FEM sparsity with 0-fill (structural pattern). */ + LDUSparsePattern finiteElementPatternFillN; /*!< \brief FEM sparsity with N-fill (e.g. for ILU-N). */ - CEdgeToNonZeroMapUL edgeToCSRMap; /*!< \brief Map edges to CSR entries referenced by them (i,j) and (j,i). */ + su2vector finiteVolumeLToUTranspMap; /*!< \brief FVM L-entry -> U-entry of its transpose. */ + su2vector finiteVolumeUToLTranspMap; /*!< \brief FVM U-entry -> L-entry of its transpose. */ + su2vector finiteElementLToUTranspMap; /*!< \brief FEM L-entry -> U-entry of its transpose. */ + su2vector finiteElementUToLTranspMap; /*!< \brief FEM U-entry -> L-entry of its transpose. */ /*--- Edge and element colorings. ---*/ @@ -1868,21 +1884,23 @@ class CGeometry { * \param[in] fillLvl - Level of fill of the pattern. * \return Reference to the sparse pattern. */ - const CCompressedSparsePatternUL& GetSparsePattern(ConnectivityType type, unsigned long fillLvl = 0); + const LDUSparsePattern& GetSparsePattern(ConnectivityType type, unsigned long fillLvl = 0); /*! - * \brief Get the edge to sparse pattern map. - * \note This method builds the map and required pattern (0-fill FVM) if that has not been done yet. - * \return Reference to the map. + * \brief Get the bijective map from L-entry indices to U-entry indices of their transposes. + * \note Requires symmetric pattern. Builds both LU transpose maps if not already built. + * \param[in] type - Finite volume or finite element. + * \return Reference to the l_to_u map. */ - const CEdgeToNonZeroMapUL& GetEdgeToSparsePatternMap(); + const su2vector& GetLToUTransposeSparsePatternMap(ConnectivityType type); /*! - * \brief Get the transpose of the (main, i.e 0 fill) sparse pattern (e.g. CSR becomes CSC). + * \brief Get the bijective map from U-entry indices to L-entry indices of their transposes. + * \note Requires symmetric pattern. Builds both LU transpose maps if not already built. * \param[in] type - Finite volume or finite element. - * \return Reference to the map. + * \return Reference to the u_to_l map. */ - const su2vector& GetTransposeSparsePatternMap(ConnectivityType type); + const su2vector& GetUToLTransposeSparsePatternMap(ConnectivityType type); /*! * \brief Get the edge coloring. diff --git a/Common/include/linear_algebra/CPastixWrapper.hpp b/Common/include/linear_algebra/CPastixWrapper.hpp index dcc320debe1c..a471a60eaffe 100644 --- a/Common/include/linear_algebra/CPastixWrapper.hpp +++ b/Common/include/linear_algebra/CPastixWrapper.hpp @@ -62,6 +62,9 @@ class CPastixWrapper { vector perm; /*!< \brief Ordering computed by PaStiX. */ vector workvec; /*!< \brief RHS vector which then becomes the solution. */ + vector csr_row_ptr; /*!< \brief Owned CSR row pointers (built from LDU). */ + vector csr_col_ind; /*!< \brief Owned CSR column indices (built from LDU). */ + pastix_int_t iparm[IPARM_SIZE]; /*!< \brief Integer parameters for PaStiX. */ double dparm[DPARM_SIZE]; /*!< \brief Floating point parameters for PaStiX. */ @@ -69,14 +72,18 @@ class CPastixWrapper { unsigned long nVar = 0; unsigned long nPoint = 0; unsigned long nPointDomain = 0; - const unsigned long* rowptr = nullptr; - const unsigned long* colidx = nullptr; - const ScalarType* values = nullptr; + unsigned long blkSz = 0; /*!< \brief Block size (nVar * nVar) for value assembly. */ + + const unsigned long* row_ptr_l = nullptr; /*!< \brief LDU lower row pointers (geometry-owned). */ + const unsigned long* row_ptr_u = nullptr; /*!< \brief LDU upper row pointers (geometry-owned). */ + const ScalarType* d = nullptr; /*!< \brief Diagonal blocks (matrix-owned). */ + const ScalarType* l = nullptr; /*!< \brief Lower blocks (matrix-owned). */ + const ScalarType* u = nullptr; /*!< \brief Upper blocks (matrix-owned). */ unsigned long size_rhs() const { return nPointDomain * nVar; } - } matrix; /*!< \brief Pointers and sizes of the input matrix. */ + } matrix; /*!< \brief Dimensions and LDU pointers captured from the owning CSysMatrix. */ - bool issetup{}; /*!< \brief Signals that the matrix data has been provided. */ + bool issetup{}; /*!< \brief Signals that the structure has been provided. */ bool isinitialized{}; /*!< \brief Signals that the sparsity pattern has been set. */ bool isfactorized{}; /*!< \brief Signals that a factorization has been computed. */ bool transpose{}; /*!< \brief Solve A^T x = b instead of A x = b. */ @@ -110,6 +117,11 @@ class CPastixWrapper { */ void Initialize(CGeometry* geometry, const CConfig* config); + /*! + * \brief Assemble CSR values from the stored LDU pointers directly into the values buffer. + */ + void AssembleValues(); + public: CPastixWrapper() = default; @@ -125,23 +137,43 @@ class CPastixWrapper { ~CPastixWrapper() { Clean(); } /*! - * \brief Set matrix data, only once. - * \param[in] nVar - DOF per point. + * \brief Returns true once SetLDU has been called. + */ + bool IsSetup() const { return issetup; } + + /*! + * \brief Set LDU structure and value pointers; builds and owns assembled CSR (called once). + * \param[in] nVar - DOF per point (square blocks: nVar x nVar). * \param[in] nPoint - Total number of points including halos. - * \param[in] nPointDomain - Number of internal points. - * \param[in] rowptr - Array, where column index data starts for each matrix row. - * \param[in] colidx - Non zeros column indices. - * \param[in] values - Matrix coefficients. + * \param[in] nPointDomain - Number of internal points (domain rows). + * \param[in] row_ptr_l/u - LDU lower/upper row pointers (geometry-owned, must outlive wrapper). + * \param[in] col_ind_l/u - LDU lower/upper column indices (geometry-owned). + * \param[in] d/l/u - LDU value blocks (matrix-owned, must outlive wrapper). */ - void SetMatrix(unsigned long nVar, unsigned long nPoint, unsigned long nPointDomain, const unsigned long* rowptr, - const unsigned long* colidx, const ScalarType* values) { + void SetLDU(unsigned long nVar, unsigned long nPoint, unsigned long nPointDomain, const unsigned long* row_ptr_l, + const unsigned long* col_ind_l, const unsigned long* row_ptr_u, const unsigned long* col_ind_u, + const ScalarType* d, const ScalarType* l, const ScalarType* u) { if (issetup) return; matrix.nVar = nVar; matrix.nPoint = nPoint; matrix.nPointDomain = nPointDomain; - matrix.rowptr = rowptr; - matrix.colidx = colidx; - matrix.values = values; + matrix.row_ptr_l = row_ptr_l; + matrix.row_ptr_u = row_ptr_u; + matrix.d = d; + matrix.l = l; + matrix.u = u; + matrix.blkSz = nVar * nVar; + + const unsigned long nnz_domain = row_ptr_l[nPointDomain] + nPointDomain + row_ptr_u[nPointDomain]; + csr_row_ptr.resize(nPointDomain + 1); + csr_col_ind.reserve(nnz_domain); + for (auto i = 0ul; i < nPointDomain; ++i) { + csr_row_ptr[i] = static_cast(csr_col_ind.size()); + for (auto k = row_ptr_l[i]; k < row_ptr_l[i + 1]; ++k) csr_col_ind.push_back(col_ind_l[k]); + csr_col_ind.push_back(i); + for (auto k = row_ptr_u[i]; k < row_ptr_u[i + 1]; ++k) csr_col_ind.push_back(col_ind_u[k]); + } + csr_row_ptr[nPointDomain] = static_cast(csr_col_ind.size()); issetup = true; } diff --git a/Common/include/linear_algebra/CSysMatrix.hpp b/Common/include/linear_algebra/CSysMatrix.hpp index a71d31c1c085..11baf6edf8e8 100644 --- a/Common/include/linear_algebra/CSysMatrix.hpp +++ b/Common/include/linear_algebra/CSysMatrix.hpp @@ -138,25 +138,40 @@ class CSysMatrix { unsigned long nVar; /*!< \brief Number of variables (and rows of the blocks). */ unsigned long nEqn; /*!< \brief Number of equations (and columns of the blocks). */ - ScalarType* matrix; /*!< \brief Entries of the sparse matrix. */ - unsigned long nnz; /*!< \brief Number of possible nonzero entries in the matrix. */ - const unsigned long* row_ptr; /*!< \brief Pointers to the first element in each row. */ - const unsigned long* dia_ptr; /*!< \brief Pointers to the diagonal element in each row. */ - const unsigned long* col_ind; /*!< \brief Column index for each of the elements in val(). */ - const unsigned long* col_ptr; /*!< \brief The transpose of col_ind, pointer to blocks with the same column index. */ - - ScalarType* d_matrix; /*!< \brief Device Pointer to store the matrix values on the GPU. */ - const unsigned long* d_row_ptr; /*!< \brief Device Pointers to the first element in each row. */ - const unsigned long* d_col_ind; /*!< \brief Device Column index for each of the elements in val(). */ - bool useCuda = false; /*!< \brief Boolean that indicates whether user has enabled CUDA or not. - Mainly used to conditionally free GPU memory in the class destructor. */ - - ScalarType* ILU_matrix; /*!< \brief Entries of the ILU sparse matrix. */ - unsigned long nnz_ilu; /*!< \brief Number of possible nonzero entries in the matrix (ILU). */ - const unsigned long* row_ptr_ilu; /*!< \brief Pointers to the first element in each row (ILU). */ - const unsigned long* dia_ptr_ilu; /*!< \brief Pointers to the diagonal element in each row (ILU). */ - const unsigned long* col_ind_ilu; /*!< \brief Column index for each of the elements in val() (ILU). */ - unsigned short ilu_fill_in; /*!< \brief Fill in level for the ILU preconditioner. */ + /*! + * \brief Aggregates value arrays and sparse-structure pointers for an LDU-partitioned matrix. + * Each CSysMatrix holds three LDU instances: the host matrix (mat), its device copy (gpu), + * and the ILU factorization (ilu). Ownership of the value arrays (d/l/u) and whether + * the pointers address host or device memory is managed by CSysMatrix. + */ + struct LDU { + ScalarType* d = nullptr; /*!< \brief Diagonal block values. */ + ScalarType* l = nullptr; /*!< \brief Strictly-lower block values. */ + ScalarType* u = nullptr; /*!< \brief Strictly-upper block values. */ + const unsigned long* row_ptr_l = nullptr; /*!< \brief Row pointers for L (geometry-owned or GPU copy). */ + const unsigned long* col_ind_l = nullptr; /*!< \brief Column indices for L. */ + const unsigned long* row_ptr_u = nullptr; /*!< \brief Row pointers for U. */ + const unsigned long* col_ind_u = nullptr; /*!< \brief Column indices for U. */ + unsigned long nnz_l = 0; /*!< \brief Number of L nonzeros. */ + unsigned long nnz_u = 0; /*!< \brief Number of U nonzeros. */ + }; + + LDU mat; /*!< \brief Host matrix (values owned via aligned_alloc; pattern from geometry). */ + LDU gpu; /*!< \brief Device matrix (all pointers to GPU memory). */ + LDU ilu; /*!< \brief ILU factorization, host (values owned; pattern from geometry). */ + + bool useCuda = false; /*!< \brief Whether CUDA is enabled. */ + const unsigned long* l_to_u_transp; /*!< \brief L-entry index -> U-entry index of its transpose. */ + const unsigned long* u_to_l_transp; /*!< \brief U-entry index -> L-entry index of its transpose. */ + + /*! + * \brief Lookup table from edges to the L-index in the LDU split. + * U-index == edge index by construction (edges are ordered 1:1 with the U pattern). + * Therefore, edge_ptr_l == u_to_l_transp, but we keep a separate member for clarity. + */ + const unsigned long* edge_ptr_l; + + unsigned short ilu_fill_in; /*!< \brief Fill level for the ILU preconditioner. */ /*!< \brief Level structure for alternative shared memory parallelization of ILU. */ CCompressedSparsePatternUL levels_ilu; @@ -188,21 +203,6 @@ class CSysMatrix { mutable CPastixWrapper pastix_wrapper; #endif - /*! - * \brief Auxilary object to wrap the edge map pointer used in fast block updates, i.e. without linear searches. - */ - struct { - const unsigned long* ptr = nullptr; - unsigned long nEdge = 0; - - operator bool() { return nEdge != 0; } - - inline unsigned long operator()(unsigned long edge, unsigned long node) const { return ptr[2 * edge + node]; } - inline unsigned long ij(unsigned long edge) const { return ptr[2 * edge]; } - inline unsigned long ji(unsigned long edge) const { return ptr[2 * edge + 1]; } - - } edge_ptr; - /*! * \brief Handle type conversion for when we Set, Add, etc. blocks, preserving derivative information (if supported by * types). @@ -322,20 +322,11 @@ class CSysMatrix { inline const ScalarType* InvertDiagonalBlockILUMatrix(unsigned long block_i); /*! - * \brief Copies the block (i, j) of the matrix-by-blocks structure in the internal variable *block. - * \param[in] block_i - Indexes of the block in the matrix-by-blocks structure. - * \param[in] block_j - Indexes of the block in the matrix-by-blocks structure. + * \brief Returns the start of the ILU block or nullptr if (i,j) is not a nonzero. + * \param[in] block_i/j - Indexes of the block in the matrix-by-blocks structure. */ inline ScalarType* GetBlock_ILUMatrix(unsigned long block_i, unsigned long block_j); - /*! - * \brief Set the value of a block in the sparse matrix. - * \param[in] block_i - Indexes of the block in the matrix-by-blocks structure. - * \param[in] block_j - Indexes of the block in the matrix-by-blocks structure. - * \param[in] **val_block - Block to set to A(i, j). - */ - inline void SetBlock_ILUMatrix(unsigned long block_i, unsigned long block_j, ScalarType* val_block); - /*! * \brief Performs the product of i-th row of the upper part of a sparse matrix by a vector. * \param[in] vec - Vector to be multiplied by the upper part of the sparse matrix A. @@ -392,7 +383,7 @@ class CSysMatrix { * \param[in] neqn - Number of equations (and columns of the blocks). * \param[in] geometry - Geometrical definition of the problem. * \param[in] config - Definition of the particular problem. - * \param[in] needTranspPtr - If "col_ptr" should be created, used for "SetDiagonalAsColumnSum". + * \param[in] needTranspPtr - If the L/U transpose maps should be built, used for "SetDiagonalAsColumnSum". */ void Initialize(unsigned long npoint, unsigned long npointdomain, unsigned short nvar, unsigned short neqn, bool EdgeConnect, CGeometry* geometry, const CConfig* config, bool needTranspPtr = false, @@ -421,10 +412,14 @@ class CSysMatrix { * \return Pointer to location in memory where the block starts. */ FORCEINLINE const ScalarType* GetBlock(unsigned long block_i, unsigned long block_j) const { - /*--- The position of the diagonal block is known which allows halving the search space. ---*/ - const auto end = (block_j < block_i) ? dia_ptr[block_i] : row_ptr[block_i + 1]; - for (auto index = (block_j < block_i) ? row_ptr[block_i] : dia_ptr[block_i]; index < end; ++index) - if (col_ind[index] == block_j) return &matrix[index * nVar * nEqn]; + if (block_i == block_j) return &mat.d[block_i * nVar * nEqn]; + if (block_j < block_i) { + for (auto index = mat.row_ptr_l[block_i]; index < mat.row_ptr_l[block_i + 1]; ++index) + if (mat.col_ind_l[index] == block_j) return &mat.l[index * nVar * nEqn]; + return nullptr; + } + for (auto index = mat.row_ptr_u[block_i]; index < mat.row_ptr_u[block_i + 1]; ++index) + if (mat.col_ind_u[index] == block_j) return &mat.u[index * nVar * nEqn]; return nullptr; } @@ -574,10 +569,11 @@ class CSysMatrix { */ inline void GetBlocks(unsigned long iEdge, unsigned long iPoint, unsigned long jPoint, ScalarType*& bii, ScalarType*& bij, ScalarType*& bji, ScalarType*& bjj) { - bii = &matrix[dia_ptr[iPoint] * nVar * nEqn]; - bjj = &matrix[dia_ptr[jPoint] * nVar * nEqn]; - bij = &matrix[edge_ptr(iEdge, 0) * nVar * nEqn]; - bji = &matrix[edge_ptr(iEdge, 1) * nVar * nEqn]; + const auto blkSz = nVar * nEqn; + bii = &mat.d[iPoint * blkSz]; + bjj = &mat.d[jPoint * blkSz]; + bij = &mat.u[iEdge * blkSz]; + bji = &mat.l[edge_ptr_l[iEdge] * blkSz]; } /*! @@ -652,10 +648,10 @@ class CSysMatrix { if (mask[k] == 0) continue; /*--- Fetch the blocks. ---*/ - auto bii = &matrix[dia_ptr[iPoint[k]] * blkSz]; - auto bjj = &matrix[dia_ptr[jPoint[k]] * blkSz]; - auto bij = &matrix[edge_ptr(iEdge[k], 0) * blkSz]; - auto bji = &matrix[edge_ptr(iEdge[k], 1) * blkSz]; + auto bii = &mat.d[iPoint[k] * blkSz]; + auto bjj = &mat.d[jPoint[k] * blkSz]; + auto bij = &mat.u[iEdge[k] * blkSz]; + auto bji = &mat.l[edge_ptr_l[iEdge[k]] * blkSz]; /*--- Update, block i was negated during transpose in the * hope the assignments below become non-temporal stores. ---*/ @@ -682,8 +678,9 @@ class CSysMatrix { template inline void SetBlocks(unsigned long iEdge, const MatrixType& block_i, const MatrixType& block_j, OtherType scale = 1) { - ScalarType* bij = &matrix[edge_ptr(iEdge, 0) * nVar * nEqn]; - ScalarType* bji = &matrix[edge_ptr(iEdge, 1) * nVar * nEqn]; + const auto blkSz = nVar * nEqn; + ScalarType* bij = &mat.u[iEdge * blkSz]; + ScalarType* bji = &mat.l[edge_ptr_l[iEdge] * blkSz]; unsigned long iVar, jVar, offset = 0; @@ -742,8 +739,8 @@ class CSysMatrix { if (mask[k] == 0) continue; /*--- Fetch the blocks. ---*/ - auto bij = &matrix[edge_ptr(iEdge[k], 0) * blkSz]; - auto bji = &matrix[edge_ptr(iEdge[k], 1) * blkSz]; + ScalarType* bij = &mat.u[iEdge[k] * blkSz]; + ScalarType* bji = &mat.l[edge_ptr_l[iEdge[k]] * blkSz]; /*--- Update, block i was negated during transpose in the * hope the assignments below become non-temporal stores. ---*/ @@ -765,7 +762,7 @@ class CSysMatrix { */ template inline void SetBlock2Diag(unsigned long block_i, const OtherType& val_block, T alpha = 1.0) { - auto mat_ii = &matrix[dia_ptr[block_i] * nVar * nEqn]; + auto mat_ii = &mat.d[block_i * nVar * nEqn]; for (auto iVar = 0ul; iVar < nVar; iVar++) for (auto jVar = 0ul; jVar < nEqn; jVar++) { @@ -798,8 +795,8 @@ class CSysMatrix { */ template inline void AddVal2Diag(unsigned long block_i, OtherType val_matrix) { - for (auto iVar = 0ul; iVar < nVar; iVar++) - matrix[dia_ptr[block_i] * nVar * nVar + iVar * (nVar + 1)] += PassiveAssign(val_matrix); + auto d = &mat.d[block_i * nVar * nVar]; + for (auto iVar = 0ul; iVar < nVar; iVar++) d[iVar * (nVar + 1)] += PassiveAssign(val_matrix); } /*! @@ -811,7 +808,7 @@ class CSysMatrix { */ template inline void AddVal2Diag(unsigned long block_i, unsigned long iVar, OtherType val) { - matrix[dia_ptr[block_i] * nVar * nVar + iVar * (nVar + 1)] += PassiveAssign(val); + mat.d[block_i * nVar * nVar + iVar * (nVar + 1)] += PassiveAssign(val); } /*! @@ -822,13 +819,11 @@ class CSysMatrix { */ template inline void SetVal2Diag(unsigned long block_i, OtherType val_matrix) { - unsigned long iVar, index = dia_ptr[block_i] * nVar * nVar; - /*--- Clear entire block before setting its diagonal. ---*/ SU2_OMP_SIMD - for (iVar = 0; iVar < nVar * nVar; iVar++) matrix[index + iVar] = 0.0; + for (auto iVar = 0ul; iVar < nVar * nVar; iVar++) mat.d[block_i * nVar * nVar + iVar] = 0.0; - for (iVar = 0; iVar < nVar; iVar++) matrix[index + iVar * (nVar + 1)] = PassiveAssign(val_matrix); + AddVal2Diag(block_i, val_matrix); } /*! diff --git a/Common/include/linear_algebra/CSysMatrix.inl b/Common/include/linear_algebra/CSysMatrix.inl index 163e6fb08da0..d40d68b3a4fc 100644 --- a/Common/include/linear_algebra/CSysMatrix.inl +++ b/Common/include/linear_algebra/CSysMatrix.inl @@ -34,21 +34,16 @@ template FORCEINLINE ScalarType* CSysMatrix::GetBlock_ILUMatrix(unsigned long block_i, unsigned long block_j) { - /*--- The position of the diagonal block is known which allows halving the search space. ---*/ - const auto end = (block_j < block_i) ? dia_ptr_ilu[block_i] : row_ptr_ilu[block_i + 1]; - for (auto index = (block_j < block_i) ? row_ptr_ilu[block_i] : dia_ptr_ilu[block_i]; index < end; ++index) - if (col_ind_ilu[index] == block_j) return &ILU_matrix[index * nVar * nVar]; + if (block_i == block_j) return &ilu.d[block_i * nVar * nVar]; + const auto* __restrict row_ptr = block_j < block_i ? ilu.row_ptr_l : ilu.row_ptr_u; + const auto* __restrict col_ind = block_j < block_i ? ilu.col_ind_l : ilu.col_ind_u; + auto* __restrict vals = block_j < block_i ? ilu.l : ilu.u; + for (auto k = row_ptr[block_i]; k < row_ptr[block_i + 1]; ++k) { + if (col_ind[k] == block_j) return vals + k * nVar * nVar; + } return nullptr; } -template -FORCEINLINE void CSysMatrix::SetBlock_ILUMatrix(unsigned long block_i, unsigned long block_j, - ScalarType* val_block) { - auto ilu_ij = GetBlock_ILUMatrix(block_i, block_j); - if (!ilu_ij) return; - MatrixCopy(val_block, ilu_ij); -} - namespace { template @@ -145,7 +140,7 @@ template FORCEINLINE void CSysMatrix::Gauss_Elimination(unsigned long block_i, ScalarType* rhs) const { /*--- Copy block, as the algorithm modifies the matrix ---*/ ScalarType block[MAXNVAR * MAXNVAR]; - MatrixCopy(&matrix[dia_ptr[block_i] * nVar * nVar], block); + MatrixCopy(&mat.d[block_i * nVar * nVar], block); Gauss_Elimination(block, rhs); } @@ -154,7 +149,7 @@ template FORCEINLINE void CSysMatrix::InverseDiagonalBlock(unsigned long block_i, ScalarType* invBlock) const { /*--- Copy block, as the algorithm modifies the matrix ---*/ ScalarType block[MAXNVAR * MAXNVAR]; - MatrixCopy(&matrix[dia_ptr[block_i] * nVar * nVar], block); + MatrixCopy(&mat.d[block_i * nVar * nVar], block); MatrixInverse(block, invBlock); } @@ -162,7 +157,7 @@ FORCEINLINE void CSysMatrix::InverseDiagonalBlock(unsigned long bloc template FORCEINLINE const ScalarType* CSysMatrix::InvertDiagonalBlockILUMatrix(unsigned long block_i) { /*--- Copy block, as the algorithm modifies the matrix ---*/ - auto* Uii = &ILU_matrix[dia_ptr_ilu[block_i] * nVar * nVar]; + auto* Uii = &ilu.d[block_i * nVar * nVar]; ScalarType block[MAXNVAR * MAXNVAR]; MatrixCopy(Uii, block); MatrixInverse(block, Uii); @@ -174,10 +169,13 @@ FORCEINLINE void CSysMatrix::RowProduct(const CSysVector ScalarType* prod) const { for (auto iVar = 0ul; iVar < nVar; iVar++) prod[iVar] = 0.0; - for (auto index = row_ptr[row_i]; index < row_ptr[row_i + 1]; index++) { - auto col_j = col_ind[index]; - MatrixVectorProductAdd(&matrix[index * nVar * nEqn], &vec[col_j * nEqn], prod); - } + for (auto index = mat.row_ptr_l[row_i]; index < mat.row_ptr_l[row_i + 1]; index++) + MatrixVectorProductAdd(&mat.l[index * nVar * nEqn], &vec[mat.col_ind_l[index] * nEqn], prod); + + MatrixVectorProductAdd(&mat.d[row_i * nVar * nEqn], &vec[row_i * nEqn], prod); + + for (auto index = mat.row_ptr_u[row_i]; index < mat.row_ptr_u[row_i + 1]; index++) + MatrixVectorProductAdd(&mat.u[index * nVar * nEqn], &vec[mat.col_ind_u[index] * nEqn], prod); } template @@ -185,11 +183,11 @@ FORCEINLINE void CSysMatrix::UpperProduct(const CSysVector= nPointDomain) - MatrixVectorProductAdd(&matrix[index * nVar * nEqn], &vec[col_j * nEqn], prod); + MatrixVectorProductAdd(&mat.u[index * nVar * nEqn], &vec[col_j * nEqn], prod); } } @@ -198,14 +196,14 @@ FORCEINLINE void CSysMatrix::LowerProduct(const CSysVector= col_lb) MatrixVectorProductAdd(&matrix[index * nVar * nEqn], &vec[col_j * nEqn], prod); + for (auto index = mat.row_ptr_l[row_i]; index < mat.row_ptr_l[row_i + 1]; index++) { + auto col_j = mat.col_ind_l[index]; + if (col_j >= col_lb) MatrixVectorProductAdd(&mat.l[index * nVar * nEqn], &vec[col_j * nEqn], prod); } } template FORCEINLINE void CSysMatrix::DiagonalProduct(const CSysVector& vec, unsigned long row_i, ScalarType* prod) const { - MatrixVectorProduct(&matrix[dia_ptr[row_i] * nVar * nEqn], &vec[row_i * nEqn], prod); + MatrixVectorProduct(&mat.d[row_i * nVar * nEqn], &vec[row_i * nEqn], prod); } diff --git a/Common/include/toolboxes/graph_toolbox.hpp b/Common/include/toolboxes/graph_toolbox.hpp index 344e08ccf7ce..170b973ea1f8 100644 --- a/Common/include/toolboxes/graph_toolbox.hpp +++ b/Common/include/toolboxes/graph_toolbox.hpp @@ -417,30 +417,86 @@ CCompressedSparsePattern buildCSRPattern(Geometry_t& geometry, Connecti } /*! - * \brief Build a lookup table of the absolute positions of the non zero entries - * of a compressed sparse pattern, accessed when visiting the FVM edges - * of a grid. The table can then be used for fast access (avoids searches) - * to the non zero entries of a sparse matrix associated with the pattern. - * \param[in] geometry - Definition of the grid. - * \param[in] pattern - Sparse pattern. - * \return nEdge by 2 matrix. + * \brief Extract the strictly-lower part of a symmetric compressed sparse pattern. + * For each row i, the lower entries are those at positions [outerPtr[i], diagPtr[i]). + * \param[in] csr - Full symmetric pattern with diagonal pointer already built. + * \return Strictly-lower CSR pattern. */ -template -CEdgeToNonZeroMap mapEdgesToSparsePattern(Geometry_t& geometry, - const CCompressedSparsePattern& pattern) { - assert(!pattern.empty()); +template +CCompressedSparsePattern buildLowerPattern(const CCompressedSparsePattern& csr) { + assert(!csr.empty()); + const auto nPoint = csr.getOuterSize(); + const auto* outerPtr = csr.outerPtr(); + const auto* innerIdx = csr.innerIdx(); + const auto* diagPtr = csr.diagPtr(); + + su2vector outerPtrL(nPoint + 1); + outerPtrL(0) = 0; + for (auto i = 0ul; i < nPoint; ++i) outerPtrL(i + 1) = outerPtrL(i) + static_cast(diagPtr[i] - outerPtr[i]); + + su2vector innerIdxL(outerPtrL(nPoint)); + Index_t k = 0; + for (auto i = 0ul; i < nPoint; ++i) + for (auto p = outerPtr[i]; p < diagPtr[i]; ++p) innerIdxL(k++) = innerIdx[p]; - CEdgeToNonZeroMap edgeMap(geometry.GetnEdge(), 2); + return CCompressedSparsePattern(std::move(outerPtrL), std::move(innerIdxL)); +} + +/*! + * \brief Extract the strictly-upper part of a symmetric compressed sparse pattern. + * For each row i, the upper entries are those at positions (diagPtr[i], outerPtr[i+1]). + * \param[in] csr - Full symmetric pattern with diagonal pointer already built. + * \return Strictly-upper CSR pattern. + */ +template +CCompressedSparsePattern buildUpperPattern(const CCompressedSparsePattern& csr) { + assert(!csr.empty()); + const auto nPoint = csr.getOuterSize(); + const auto* outerPtr = csr.outerPtr(); + const auto* innerIdx = csr.innerIdx(); + const auto* diagPtr = csr.diagPtr(); + + su2vector outerPtrU(nPoint + 1); + outerPtrU(0) = 0; + for (auto i = 0ul; i < nPoint; ++i) + outerPtrU(i + 1) = outerPtrU(i) + static_cast(outerPtr[i + 1] - diagPtr[i] - 1); + + su2vector innerIdxU(outerPtrU(nPoint)); + Index_t k = 0; + for (auto i = 0ul; i < nPoint; ++i) + for (auto p = diagPtr[i] + 1; p < outerPtr[i + 1]; ++p) innerIdxU(k++) = innerIdx[p]; - for (Index_t iEdge = 0; iEdge < geometry.GetnEdge(); ++iEdge) { - Index_t iPoint = geometry.edges->GetNode(iEdge, 0); - Index_t jPoint = geometry.edges->GetNode(iEdge, 1); + return CCompressedSparsePattern(std::move(outerPtrU), std::move(innerIdxU)); +} - edgeMap(iEdge, 0) = pattern.quickFindInnerIdx(iPoint, jPoint); - edgeMap(iEdge, 1) = pattern.quickFindInnerIdx(jPoint, iPoint); +/*! + * \brief Build bijective maps between strictly-lower (L) and strictly-upper (U) non-zero entries + * that are each other's transposes. Requires a symmetric pattern. + * l_to_u[k_l] = k_u such that U-entry k_u is the transpose of L-entry k_l, and vice-versa. + * \param[in] pattern_l - Strictly-lower CSR pattern. + * \param[in] pattern_u - Strictly-upper CSR pattern. + * \param[out] l_to_u - For each L-entry index, the U-entry index of its transpose. + * \param[out] u_to_l - For each U-entry index, the L-entry index of its transpose. + */ +template +void buildLUTransposeMaps(const CCompressedSparsePattern& pattern_l, + const CCompressedSparsePattern& pattern_u, su2vector& l_to_u, + su2vector& u_to_l) { + const auto nnz_l = pattern_l.getNumNonZeros(); + const auto nnz_u = pattern_u.getNumNonZeros(); + assert(nnz_l == nnz_u && "L and U must have the same NNZ (symmetric pattern)."); + + l_to_u.resize(nnz_l); + u_to_l.resize(nnz_u); + + for (Index_t i = 0; i < pattern_l.getOuterSize(); ++i) { + for (Index_t k_l = pattern_l.outerPtr()[i]; k_l < pattern_l.outerPtr()[i + 1]; ++k_l) { + const Index_t j = pattern_l.innerIdx()[k_l]; // j < i (strictly lower) + const Index_t k_u = pattern_u.quickFindInnerIdx(j, i); // (j,i) is in U since jempty()) { - *pattern = buildCSRPattern(*this, type, fillLvl); - pattern->buildDiagPtr(); + auto& grp = fillLvl == 0 ? (fvm ? finiteVolumePatternFill0 : finiteElementPatternFill0) + : (fvm ? finiteVolumePatternFillN : finiteElementPatternFillN); + if (grp.empty()) { + grp.csr = buildCSRPattern(*this, type, fillLvl); + grp.csr.buildDiagPtr(); + grp.l = buildLowerPattern(grp.csr); + grp.u = buildUpperPattern(grp.csr); } - - return *pattern; + return grp; } -const CEdgeToNonZeroMapUL& CGeometry::GetEdgeToSparsePatternMap() { - if (edgeToCSRMap.empty()) { - if (finiteVolumeCSRFill0.empty()) { - finiteVolumeCSRFill0 = buildCSRPattern(*this, ConnectivityType::FiniteVolume, 0ul); - } - edgeToCSRMap = mapEdgesToSparsePattern(*this, finiteVolumeCSRFill0); +const su2vector& CGeometry::GetLToUTransposeSparsePatternMap(ConnectivityType type) { + bool fvm = (type == ConnectivityType::FiniteVolume); + auto& l_to_u = fvm ? finiteVolumeLToUTranspMap : finiteElementLToUTranspMap; + if (l_to_u.empty()) { + auto& u_to_l = fvm ? finiteVolumeUToLTranspMap : finiteElementUToLTranspMap; + const auto& pat = GetSparsePattern(type); + buildLUTransposeMaps(pat.l, pat.u, l_to_u, u_to_l); } - return edgeToCSRMap; + return l_to_u; } -const su2vector& CGeometry::GetTransposeSparsePatternMap(ConnectivityType type) { - /*--- Yes the const cast is weird but it is still better than repeating code. ---*/ - auto& pattern = const_cast(GetSparsePattern(type)); - pattern.buildTransposePtr(); - return pattern.transposePtr(); +const su2vector& CGeometry::GetUToLTransposeSparsePatternMap(ConnectivityType type) { + bool fvm = (type == ConnectivityType::FiniteVolume); + auto& u_to_l = fvm ? finiteVolumeUToLTranspMap : finiteElementUToLTranspMap; + if (u_to_l.empty()) { + auto& l_to_u = fvm ? finiteVolumeLToUTranspMap : finiteElementLToUTranspMap; + const auto& pat = GetSparsePattern(type); + buildLUTransposeMaps(pat.l, pat.u, l_to_u, u_to_l); + } + return u_to_l; } const CCompressedSparsePatternUL& CGeometry::GetEdgeColoring(su2double* efficiency, bool maximizeEdgeColorGroupSize) { diff --git a/Common/src/geometry/CMultiGridGeometry.cpp b/Common/src/geometry/CMultiGridGeometry.cpp index 347a633e2a7b..684da742b130 100644 --- a/Common/src/geometry/CMultiGridGeometry.cpp +++ b/Common/src/geometry/CMultiGridGeometry.cpp @@ -896,6 +896,9 @@ void CMultiGridGeometry::SetPoint_Connectivity(const CGeometry* fine_grid) { } } + /*--- See CPhysicalGeometry::SetPoint_Connectivity for why we sort. ---*/ + sort(points[iCoarsePoint].begin(), points[iCoarsePoint].end()); + /*--- Set the number of neighbors variable, this is important for JST and multigrid in parallel ---*/ nodes->SetnNeighbor(iCoarsePoint, points[iCoarsePoint].size()); diff --git a/Common/src/geometry/CPhysicalGeometry.cpp b/Common/src/geometry/CPhysicalGeometry.cpp index 79421eded3e0..a965b248b672 100644 --- a/Common/src/geometry/CPhysicalGeometry.cpp +++ b/Common/src/geometry/CPhysicalGeometry.cpp @@ -4463,6 +4463,11 @@ void CPhysicalGeometry::SetPoint_Connectivity() { } } + /*--- Sort the neighbors in ascending order so that the edge numbering done in + * SetEdges matches the upper-CSR ordering of the sparse pattern. This makes + * the edge->upper-block map the identity for the CSysMatrix LDU storage. ---*/ + sort(points[iPoint].begin(), points[iPoint].end()); + /*--- Set the number of neighbors variable, this is important for JST and multigrid in parallel. ---*/ nodes->SetnNeighbor(iPoint, points[iPoint].size()); } diff --git a/Common/src/linear_algebra/CPastixWrapper.cpp b/Common/src/linear_algebra/CPastixWrapper.cpp index f4db581a879f..f213939eaf61 100644 --- a/Common/src/linear_algebra/CPastixWrapper.cpp +++ b/Common/src/linear_algebra/CPastixWrapper.cpp @@ -41,7 +41,7 @@ void CPastixWrapper::Initialize(CGeometry* geometry, const CConfig* if (isinitialized) return; // only need to do this once const unsigned long nVar = matrix.nVar, nPoint = matrix.nPoint, nPointDomain = matrix.nPointDomain; - const unsigned long *row_ptr = matrix.rowptr, *col_ind = matrix.colidx; + const unsigned long *row_ptr = csr_row_ptr.data(), *col_ind = csr_col_ind.data(); const unsigned long nNonZero = row_ptr[nPointDomain]; /*--- Allocate ---*/ @@ -209,6 +209,22 @@ void CPastixWrapper::Initialize(CGeometry* geometry, const CConfig* isinitialized = true; } +template +void CPastixWrapper::AssembleValues() { + const auto nDomain = matrix.nPointDomain; + const auto blkSz = matrix.blkSz; + const auto *d = matrix.d, *l = matrix.l, *u = matrix.u; + for (auto iPoint = 0ul; iPoint < nDomain; ++iPoint) { + auto* dst = values.data() + csr_row_ptr[iPoint] * blkSz; + for (auto k = matrix.row_ptr_l[iPoint]; k < matrix.row_ptr_l[iPoint + 1]; ++k, dst += blkSz) + for (auto b = 0ul; b < blkSz; ++b) dst[b] = SU2_TYPE::GetValue(l[k * blkSz + b]); + for (auto b = 0ul; b < blkSz; ++b) dst[b] = SU2_TYPE::GetValue(d[iPoint * blkSz + b]); + dst += blkSz; + for (auto k = matrix.row_ptr_u[iPoint]; k < matrix.row_ptr_u[iPoint + 1]; ++k, dst += blkSz) + for (auto b = 0ul; b < blkSz; ++b) dst[b] = SU2_TYPE::GetValue(u[k * blkSz + b]); + } +} + template void CPastixWrapper::Factorize(CGeometry* geometry, const CConfig* config, unsigned short kind_fact) { /*--- Detect a possible change of settings between direct and adjoint that requires a reset ---*/ @@ -246,28 +262,30 @@ void CPastixWrapper::Factorize(CGeometry* geometry, const CConfig* c if (isfactorized && !factorize) return; // No - /*--- Yes ---*/ + /*--- Yes: assemble LDU blocks into the flat CSR buffer ---*/ + AssembleValues(); if (mpi_rank == MASTER_NODE && verb > 0) { cout << "\n+-------------------------------------------------+"; cout << "\n+ PaStiX : Parallel Sparse matriX package +" << endl; } - const unsigned long szBlk = matrix.nVar * matrix.nVar, nNonZero = values.size(); + const auto blkSz = matrix.blkSz; - /*--- Copy matrix values and swap blocks as required ---*/ - - for (auto i = 0ul; i < nNonZero; ++i) values[i] = SU2_TYPE::GetValue(matrix.values[i]); + /*--- Permute blocks for rows with halo columns into global sorted order. + AssembleValues wrote them in LDU order; copy to tmp then write back sorted. ---*/ + vector tmp; for (auto i = 0ul; i < sort_rows.size(); ++i) { const auto iRow = sort_rows[i]; - const auto begin = matrix.rowptr[iRow]; - - for (auto j = 0ul; j < sort_order[i].size(); ++j) { - const auto target = (begin + j) * szBlk; - const auto source = sort_order[i][j] * szBlk; - - for (auto k = 0ul; k < szBlk; ++k) values[target + k] = SU2_TYPE::GetValue(matrix.values[source + k]); + /*--- colptr is 1-based Fortran numbering: row start = colptr[iRow] - 1. ---*/ + const auto begin = static_cast(colptr[iRow] - 1); + const auto nnz_row = sort_order[i].size(); + + tmp.assign(values.begin() + begin * blkSz, values.begin() + (begin + nnz_row) * blkSz); + for (auto j = 0ul; j < nnz_row; ++j) { + const auto src_pos = sort_order[i][j] - begin; + for (auto k = 0ul; k < blkSz; ++k) values[(begin + j) * blkSz + k] = tmp[src_pos * blkSz + k]; } } diff --git a/Common/src/linear_algebra/CSysMatrix.cpp b/Common/src/linear_algebra/CSysMatrix.cpp index e90f9e1c7045..f02d908a059e 100644 --- a/Common/src/linear_algebra/CSysMatrix.cpp +++ b/Common/src/linear_algebra/CSysMatrix.cpp @@ -31,6 +31,7 @@ #include "../../include/toolboxes/allocation_toolbox.hpp" #include +#include namespace { /*--- Helper function to regularize small pivots ---*/ @@ -51,21 +52,36 @@ CSysMatrix::CSysMatrix() : rank(SU2_MPI::GetRank()), size(SU2_MPI::G SU2_ZONE_SCOPED nPoint = nPointDomain = nVar = nEqn = 0; - nnz = nnz_ilu = 0; + mat.nnz_l = mat.nnz_u = 0; + gpu.nnz_l = gpu.nnz_u = 0; + ilu.nnz_l = ilu.nnz_u = 0; ilu_fill_in = 0; omp_partitions = nullptr; - matrix = nullptr; - row_ptr = nullptr; - dia_ptr = nullptr; - col_ind = nullptr; - col_ptr = nullptr; - - ILU_matrix = nullptr; - row_ptr_ilu = nullptr; - dia_ptr_ilu = nullptr; - col_ind_ilu = nullptr; + mat.row_ptr_l = nullptr; + mat.col_ind_l = nullptr; + mat.row_ptr_u = nullptr; + mat.col_ind_u = nullptr; + l_to_u_transp = nullptr; + u_to_l_transp = nullptr; + edge_ptr_l = nullptr; + + mat.d = nullptr; + mat.l = nullptr; + mat.u = nullptr; + + gpu.d = nullptr; + gpu.l = nullptr; + gpu.u = nullptr; + gpu.row_ptr_l = nullptr; + gpu.col_ind_l = nullptr; + gpu.row_ptr_u = nullptr; + gpu.col_ind_u = nullptr; + + ilu.l = nullptr; + ilu.d = nullptr; + ilu.u = nullptr; invM = nullptr; @@ -82,14 +98,22 @@ CSysMatrix::~CSysMatrix() { SU2_ZONE_SCOPED delete[] omp_partitions; - MemoryAllocation::aligned_free(ILU_matrix); - MemoryAllocation::aligned_free(matrix); + MemoryAllocation::aligned_free(ilu.l); + MemoryAllocation::aligned_free(ilu.d); + MemoryAllocation::aligned_free(ilu.u); + MemoryAllocation::aligned_free(mat.d); + MemoryAllocation::aligned_free(mat.l); + MemoryAllocation::aligned_free(mat.u); MemoryAllocation::aligned_free(invM); if (useCuda) { - GPUMemoryAllocation::gpu_free(d_matrix); - GPUMemoryAllocation::gpu_free(d_row_ptr); - GPUMemoryAllocation::gpu_free(d_col_ind); + GPUMemoryAllocation::gpu_free(gpu.d); + GPUMemoryAllocation::gpu_free(gpu.l); + GPUMemoryAllocation::gpu_free(gpu.u); + GPUMemoryAllocation::gpu_free(gpu.row_ptr_l); + GPUMemoryAllocation::gpu_free(gpu.col_ind_l); + GPUMemoryAllocation::gpu_free(gpu.row_ptr_u); + GPUMemoryAllocation::gpu_free(gpu.col_ind_u); } #ifdef USE_MKL @@ -109,7 +133,7 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi if (npoint == 0) return; - if (matrix != nullptr) { + if (mat.d != nullptr) { SU2_MPI::Error("CSysMatrix can only be initialized once.", CURRENT_FUNCTION); } @@ -146,45 +170,49 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi nPoint = npoint; nPointDomain = npointdomain; - /*--- Get sparse structure pointers from geometry, - * the data is managed by CGeometry to allow re-use. ---*/ - - const auto& csr = geometry->GetSparsePattern(type, 0); - - nnz = csr.getNumNonZeros(); - row_ptr = csr.outerPtr(); - col_ind = csr.innerIdx(); - dia_ptr = csr.diagPtr(); - - /*--- Allocate data. ---*/ + /*--- Allocate host data. ---*/ auto allocAndInit = [](ScalarType*& ptr, unsigned long num) { ptr = MemoryAllocation::aligned_alloc(64, num * sizeof(ScalarType)); }; - allocAndInit(matrix, nnz * nVar * nEqn); - useCuda = config->GetCUDA(); + /*--- L/D/U index structures and value arrays. ---*/ + { + const auto& pat = geometry->GetSparsePattern(type, 0); + mat.row_ptr_l = pat.l.outerPtr(); + mat.col_ind_l = pat.l.innerIdx(); + mat.nnz_l = pat.l.getNumNonZeros(); + mat.row_ptr_u = pat.u.outerPtr(); + mat.col_ind_u = pat.u.innerIdx(); + mat.nnz_u = pat.u.getNumNonZeros(); + } + allocAndInit(mat.d, nPoint * nVar * nEqn); + allocAndInit(mat.l, mat.nnz_l * nVar * nEqn); + allocAndInit(mat.u, mat.nnz_u * nVar * nEqn); + if (useCuda) { - /*--- Allocate GPU data. ---*/ auto GPUAllocAndInit = [](ScalarType*& ptr, unsigned long num) { ptr = GPUMemoryAllocation::gpu_alloc(num * sizeof(ScalarType)); }; - - auto GPUAllocAndCopy = [](const unsigned long*& ptr, const unsigned long*& src_ptr, unsigned long num) { - ptr = GPUMemoryAllocation::gpu_alloc_cpy(src_ptr, num * sizeof(const unsigned long)); + auto GPUAllocAndCopy = [](const unsigned long*& ptr, const unsigned long* src_ptr, unsigned long num) { + ptr = GPUMemoryAllocation::gpu_alloc_cpy(src_ptr, num * sizeof(unsigned long)); }; - - GPUAllocAndInit(d_matrix, nnz * nVar * nEqn); - GPUAllocAndCopy(d_row_ptr, row_ptr, (nPointDomain + 1)); - GPUAllocAndCopy(d_col_ind, col_ind, nnz); + GPUAllocAndInit(gpu.d, nPoint * nVar * nEqn); + GPUAllocAndInit(gpu.l, mat.nnz_l * nVar * nEqn); + GPUAllocAndInit(gpu.u, mat.nnz_u * nVar * nEqn); + GPUAllocAndCopy(gpu.row_ptr_l, mat.row_ptr_l, nPointDomain + 1); + GPUAllocAndCopy(gpu.col_ind_l, mat.col_ind_l, mat.nnz_l); + GPUAllocAndCopy(gpu.row_ptr_u, mat.row_ptr_u, nPointDomain + 1); + GPUAllocAndCopy(gpu.col_ind_u, mat.col_ind_u, mat.nnz_u); } - if (needTranspPtr) col_ptr = geometry->GetTransposeSparsePatternMap(type).data(); - if (type == ConnectivityType::FiniteVolume) { - edge_ptr.ptr = geometry->GetEdgeToSparsePatternMap().data(); - edge_ptr.nEdge = geometry->GetnEdge(); + edge_ptr_l = geometry->GetUToLTransposeSparsePatternMap(type).data(); + } + if (needTranspPtr) { + l_to_u_transp = geometry->GetLToUTransposeSparsePatternMap(type).data(); + u_to_l_transp = geometry->GetUToLTransposeSparsePatternMap(type).data(); } /*--- Get ILU sparse pattern, if fill is 0 no new data is allocated. --*/ @@ -192,21 +220,26 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi if (ilu_needed) { ilu_fill_in = config->GetLinear_Solver_ILU_n(); - const auto& csr_ilu = geometry->GetSparsePattern(type, ilu_fill_in); - - row_ptr_ilu = csr_ilu.outerPtr(); - col_ind_ilu = csr_ilu.innerIdx(); - dia_ptr_ilu = csr_ilu.diagPtr(); - nnz_ilu = csr_ilu.getNumNonZeros(); + const auto& pat_ilu = geometry->GetSparsePattern(type, ilu_fill_in); + ilu.row_ptr_l = pat_ilu.l.outerPtr(); + ilu.col_ind_l = pat_ilu.l.innerIdx(); + ilu.nnz_l = pat_ilu.l.getNumNonZeros(); + ilu.row_ptr_u = pat_ilu.u.outerPtr(); + ilu.col_ind_u = pat_ilu.u.innerIdx(); + ilu.nnz_u = pat_ilu.u.getNumNonZeros(); if (omp_get_max_threads() > 1 && config->GetLinear_Solver_ILU_levels()) { - levels_ilu = computeLevels(csr_ilu); + levels_ilu = computeLevels(pat_ilu.l); } } /*--- Preconditioners. ---*/ - if (ilu_needed) allocAndInit(ILU_matrix, nnz_ilu * nVar * nEqn); + if (ilu_needed) { + allocAndInit(ilu.l, ilu.nnz_l * nVar * nEqn); + allocAndInit(ilu.d, nPointDomain * nVar * nEqn); + allocAndInit(ilu.u, ilu.nnz_u * nVar * nEqn); + } if (diag_needed) allocAndInit(invM, nPointDomain * nVar * nEqn); @@ -216,7 +249,7 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi /*--- Set suitable chunk sizes for light static for loops, and heavy dynamic ones, such that threads are approximately evenly loaded. ---*/ - omp_light_size = computeStaticChunkSize(nnz * nVar * nEqn, num_threads, OMP_MAX_SIZE_L); + omp_light_size = computeStaticChunkSize((mat.nnz_l + mat.nnz_u + nPoint) * nVar * nEqn, num_threads, OMP_MAX_SIZE_L); omp_heavy_size = computeStaticChunkSize(nPointDomain, num_threads, OMP_MAX_SIZE_H); omp_num_parts = config->GetLinear_Solver_Prec_Threads(); @@ -228,13 +261,16 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi /*--- Work estimate based on non-zeros to produce balanced partitions. ---*/ - const auto row_ptr_prec = ilu_needed ? row_ptr_ilu : row_ptr; - const auto nnz_prec = row_ptr_prec[nPointDomain]; - + /*--- Cumulative nnz up to row iPoint for the preconditioner's LDU pattern. ---*/ + auto nnz_up_to = [&](unsigned long iPoint) -> unsigned long { + if (ilu_needed) return ilu.row_ptr_l[iPoint] + iPoint + ilu.row_ptr_u[iPoint]; + return mat.row_ptr_l[iPoint] + iPoint + mat.row_ptr_u[iPoint]; + }; + const auto nnz_prec = nnz_up_to(nPointDomain); const auto nnz_per_part = roundUpDiv(nnz_prec, omp_num_parts); for (auto iPoint = 0ul, part = 0ul; iPoint < nPointDomain; ++iPoint) { - if (row_ptr_prec[iPoint] >= part * nnz_per_part) omp_partitions[part++] = iPoint; + if (nnz_up_to(iPoint) >= part * nnz_per_part) omp_partitions[part++] = iPoint; } for (unsigned long thread = 0; thread < omp_num_parts; ++thread) { @@ -505,11 +541,18 @@ void CSysMatrixComms::Complete(CSysVector& x, CGeometry* geometry, const CCon template void CSysMatrix::SetValZero() { SU2_ZONE_SCOPED - const auto size = nnz * nVar * nEqn; - const auto chunk = roundUpDiv(size, omp_get_num_threads()); - const auto begin = chunk * omp_get_thread_num(); - const auto mySize = min(chunk, size - begin) * sizeof(ScalarType); - memset(&matrix[begin], 0, mySize); + const auto nThreads = static_cast(omp_get_num_threads()); + const auto iThread = static_cast(omp_get_thread_num()); + auto zeroChunk = [&](ScalarType* arr, unsigned long n) { + if (n == 0) return; + const auto chunk = roundUpDiv(n, nThreads); + const auto begin = min(chunk * iThread, n); + const auto mySize = min(chunk, n - begin) * sizeof(ScalarType); + if (mySize) memset(&arr[begin], 0, mySize); + }; + zeroChunk(mat.d, nPoint * nVar * nEqn); + zeroChunk(mat.l, mat.nnz_l * nVar * nEqn); + zeroChunk(mat.u, mat.nnz_u * nVar * nEqn); SU2_OMP_BARRIER } @@ -517,8 +560,7 @@ template void CSysMatrix::SetValDiagonalZero() { SU2_ZONE_SCOPED SU2_OMP_FOR_STAT(omp_heavy_size) - for (auto iPoint = 0ul; iPoint < nPointDomain; ++iPoint) - for (auto index = 0ul; index < nVar * nEqn; ++index) matrix[dia_ptr[iPoint] * nVar * nEqn + index] = 0.0; + for (auto iVar = 0ul; iVar < nPointDomain * nVar * nEqn; ++iVar) mat.d[iVar] = 0; END_SU2_OMP_FOR } @@ -631,12 +673,13 @@ void CSysMatrix::MatrixInverse(ScalarType* matrix, ScalarType* inver template void CSysMatrix::DeleteValsRowi(unsigned long block_i, unsigned long row) { SU2_ZONE_SCOPED - for (auto index = row_ptr[block_i]; index < row_ptr[block_i + 1]; index++) { - for (auto iVar = 0u; iVar < nVar; iVar++) - matrix[index * nVar * nVar + row * nVar + iVar] = 0.0; // Delete row values in the block - if (col_ind[index] == block_i) - matrix[index * nVar * nVar + row * nVar + row] = 1.0; // Set 1 to the diagonal element - } + for (auto k = mat.row_ptr_l[block_i]; k < mat.row_ptr_l[block_i + 1]; ++k) + for (auto iVar = 0u; iVar < nVar; iVar++) mat.l[k * nVar * nEqn + row * nEqn + iVar] = 0.0; + auto* d = &mat.d[block_i * nVar * nEqn]; + for (auto iVar = 0u; iVar < nVar; iVar++) d[row * nEqn + iVar] = 0.0; + d[row * nEqn + row] = 1.0; + for (auto k = mat.row_ptr_u[block_i]; k < mat.row_ptr_u[block_i + 1]; ++k) + for (auto iVar = 0u; iVar < nVar; iVar++) mat.u[k * nVar * nEqn + row * nEqn + iVar] = 0.0; } template @@ -700,49 +743,53 @@ void CSysMatrix::ComputeJacobiPreconditioner(const CSysVector void CSysMatrix::BuildILUPreconditioner() { + SU2_ZONE_SCOPED const auto blockSize = nVar * nVar; ScalarType Lij[MAXNVAR * MAXNVAR], Lij_Ujk[MAXNVAR * MAXNVAR]; /*--- Helper to copy block matrix to compute factorization in-place. ---*/ auto InitIluRow = [&](const auto iPoint) { + MatrixCopy(&mat.d[iPoint * blockSize], &ilu.d[iPoint * blockSize]); + if (ilu_fill_in == 0) { - /*--- ILU0, direct copy to initialize. ---*/ - const auto begin = row_ptr_ilu[iPoint] * blockSize; - const auto end = row_ptr_ilu[iPoint + 1] * blockSize; - SU2_OMP_SIMD - for (unsigned long k = begin; k < end; ++k) ILU_matrix[k] = matrix[k]; + /*--- ILU0: Same sparse pattern, copy L and U blocks directly. ---*/ + auto copy = [&](const unsigned long* row_ptr, const ScalarType* mat, ScalarType* ilu) { + const unsigned long begin = row_ptr[iPoint] * blockSize; + const unsigned long end = row_ptr[iPoint + 1] * blockSize; + SU2_OMP_SIMD + for (auto k = begin; k < end; ++k) ilu[k] = mat[k]; + }; + copy(ilu.row_ptr_l, mat.l, ilu.l); + copy(ilu.row_ptr_u, mat.u, ilu.u); return; } - /*--- ILUn, clear or copy the entries of the matrix. ---*/ - auto indexMat = row_ptr[iPoint]; - const auto endMat = row_ptr[iPoint + 1]; - for (auto index = row_ptr_ilu[iPoint]; index < row_ptr_ilu[iPoint + 1];) { - const auto jPoint = col_ind_ilu[index]; - const auto jPointMat = col_ind[indexMat]; - if (jPoint < jPointMat || indexMat == endMat) { - /*--- ILU column has not caught up with matrix column or all matrix columns were used. ---*/ - ZeroMatrix(&ILU_matrix[index * blockSize]); - ++index; - } else { - /*--- Columns match, copy the matrix block. ---*/ - if (jPoint == jPointMat) { - MatrixCopy(&matrix[indexMat * blockSize], &ILU_matrix[index * blockSize]); - ++index; + /*--- ILUn: Merge-scan L and U via shared lambda. ---*/ + auto scatterPart = [&](const unsigned long* mat_row_ptr, const unsigned long* mat_col_ind, + const ScalarType* mat_vals, const unsigned long* ilu_row_ptr, + const unsigned long* ilu_col_ind, ScalarType* ilu_vals) { + auto km = mat_row_ptr[iPoint], km_end = mat_row_ptr[iPoint + 1]; + for (auto k = ilu_row_ptr[iPoint]; k < ilu_row_ptr[iPoint + 1]; ++k) { + const auto jPoint = ilu_col_ind[k]; + while (km < km_end && mat_col_ind[km] < jPoint) ++km; + if (km < km_end && mat_col_ind[km] == jPoint) { + MatrixCopy(&mat_vals[km * blockSize], &ilu_vals[k * blockSize]); + } else { + ZeroMatrix(&ilu_vals[k * blockSize]); } - /*--- We've either copied the matrix column or it has not caught up with the ILU column. ---*/ - ++indexMat; } - } + }; + scatterPart(mat.row_ptr_l, mat.col_ind_l, mat.l, ilu.row_ptr_l, ilu.col_ind_l, ilu.l); + scatterPart(mat.row_ptr_u, mat.col_ind_u, mat.u, ilu.row_ptr_u, ilu.col_ind_u, ilu.u); }; /*--- Update one row of the LU matrix. ---*/ auto BuildIluRow = [&](const auto iPoint, const auto begin, const auto end) { /*--- For this row (unknown), loop over its lower diagonal entries. ---*/ - for (auto index = row_ptr_ilu[iPoint]; index < dia_ptr_ilu[iPoint]; ++index) { + for (auto kl = ilu.row_ptr_l[iPoint]; kl < ilu.row_ptr_l[iPoint + 1]; ++kl) { /*--- jPoint is the column index (jPoint < iPoint). ---*/ - const auto jPoint = col_ind_ilu[index]; + const auto jPoint = ilu.col_ind_l[kl]; /*--- We only care about the sub matrix within "begin" and "end-1". ---*/ @@ -750,16 +797,16 @@ void CSysMatrix::BuildILUPreconditioner() { /*--- Multiply the block by the inverse of the corresponding diagonal block. ---*/ - auto* Block_ij = &ILU_matrix[index * blockSize]; - const auto* invUjj = &ILU_matrix[dia_ptr_ilu[jPoint] * blockSize]; + auto* Block_ij = &ilu.l[kl * blockSize]; + const auto* invUjj = &ilu.d[jPoint * blockSize]; MatrixMatrixProduct(Block_ij, invUjj, Lij); /*--- Lij holds Aij*inv(Ujj). Jump to the upper part of the jPoint row. ---*/ - for (auto index_ = dia_ptr_ilu[jPoint] + 1; index_ < row_ptr_ilu[jPoint + 1]; ++index_) { + for (auto ku = ilu.row_ptr_u[jPoint]; ku < ilu.row_ptr_u[jPoint + 1]; ++ku) { /*--- Get the column index (kPoint > jPoint). ---*/ - const auto kPoint = col_ind_ilu[index_]; + const auto kPoint = ilu.col_ind_u[ku]; if (kPoint >= end) break; /*--- If Aik exists, update it: Aik -= Lij * Ujk ---*/ @@ -767,7 +814,7 @@ void CSysMatrix::BuildILUPreconditioner() { auto* Block_ik = GetBlock_ILUMatrix(iPoint, kPoint); if (Block_ik == nullptr) continue; - const auto* Ujk = &ILU_matrix[index_ * blockSize]; + const auto* Ujk = &ilu.u[ku * blockSize]; MatrixMatrixProduct(Lij, Ujk, Lij_Ujk); MatrixSubtraction(Block_ik, Lij_Ujk, Block_ik); } @@ -827,6 +874,7 @@ void CSysMatrix::BuildILUPreconditioner() { template void CSysMatrix::ComputeILUPreconditioner(const CSysVector& vec, CSysVector& prod, CGeometry* geometry, const CConfig* config) const { + SU2_ZONE_SCOPED /*--- Coherent view of vectors. ---*/ SU2_OMP_BARRIER @@ -839,11 +887,10 @@ void CSysMatrix::ComputeILUPreconditioner(const CSysVector::ComputeILUPreconditioner(const CSysVector= end) break; - const auto* Block_ij = &ILU_matrix[index * blockSize]; + const auto* Block_ij = &ilu.u[ku * blockSize]; MatrixVectorProductSub(Block_ij, &prod[jPoint * nVar], aux_vec); } @@ -1045,14 +1092,14 @@ void CSysMatrix::ComputeLineletPreconditioner(const CSysVector::EnforceSolutionAtNode(const unsigned long node_i, c * symmetric the entire column may not be eliminated, the result (matrix and vector) is still correct. * The vector is updated with the product of column i by the known (enforced) solution at node i. ---*/ - for (auto index = row_ptr[node_i]; index < row_ptr[node_i + 1]; ++index) { - auto node_j = col_ind[index]; - - /*--- The diagonal block is handled outside the loop. ---*/ - if (node_j == node_i) continue; - - /*--- Delete block j on row i (bij) and ATTEMPT to delete block i on row j (bji). ---*/ - auto bij = &matrix[index * nVar * nVar]; + /*--- Visit off-diagonal columns (L then U; diagonal is handled by SetVal2Diag outside). ---*/ + auto processOffDiag = [&](unsigned long node_j) { + auto bij = GetBlock(node_i, node_j); auto bji = GetBlock(node_j, node_i); - - /*--- The "attempt" part. ---*/ if (bji == nullptr) { node_j = node_i; bji = bij; } - for (auto iVar = 0ul; iVar < nVar; ++iVar) { for (auto jVar = 0ul; jVar < nVar; ++jVar) { - /*--- Column product. ---*/ b[node_j * nVar + iVar] -= bji[iVar * nVar + jVar] * x_i[jVar]; - /*--- Delete blocks. ---*/ bij[iVar * nVar + jVar] = bji[iVar * nVar + jVar] = 0.0; } } - } + }; + for (auto k = mat.row_ptr_l[node_i]; k < mat.row_ptr_l[node_i + 1]; ++k) processOffDiag(mat.col_ind_l[k]); + for (auto k = mat.row_ptr_u[node_i]; k < mat.row_ptr_u[node_i + 1]; ++k) processOffDiag(mat.col_ind_u[k]); /*--- Set the diagonal block to the identity. ---*/ SetVal2Diag(node_i, 1.0); @@ -1169,54 +1208,33 @@ template void CSysMatrix::EnforceZeroProjection(unsigned long node_i, const OtherType* n, CSysVector& b) { SU2_ZONE_SCOPED - for (auto index = row_ptr[node_i]; index < row_ptr[node_i + 1]; ++index) { - const auto node_j = col_ind[index]; - - /*--- Remove product components of block j on row i (bij) and ATTEMPT - * to remove solution components of block i on row j (bji). - * This is identical to symmetry correction applied to gradients - * but extended to the entire matrix. ---*/ - - auto bij = &matrix[index * nVar * nVar]; + /*--- Visit all columns (L, diagonal, U) of row node_i. ---*/ + auto processCol = [&](unsigned long node_j, bool isDiag) { + auto bij = GetBlock(node_i, node_j); auto bji = GetBlock(node_j, node_i); - - /*--- Attempt to remove solution components. ---*/ ScalarType nbn{}; if (bji != nullptr) { for (auto iVar = 0ul; iVar < nVar; ++iVar) { ScalarType proj{}; - for (auto jVar = 0ul; jVar < nVar; ++jVar) { - proj += bji[iVar * nVar + jVar] * PassiveAssign(n[jVar]); - } - for (auto jVar = 0ul; jVar < nVar; ++jVar) { - bji[iVar * nVar + jVar] -= proj * PassiveAssign(n[jVar]); - } + for (auto jVar = 0ul; jVar < nVar; ++jVar) proj += bji[iVar * nVar + jVar] * PassiveAssign(n[jVar]); + for (auto jVar = 0ul; jVar < nVar; ++jVar) bji[iVar * nVar + jVar] -= proj * PassiveAssign(n[jVar]); nbn += proj * PassiveAssign(n[iVar]); } } - - /*--- Product components. ---*/ for (auto jVar = 0ul; jVar < nVar; ++jVar) { ScalarType proj{}; - for (auto iVar = 0ul; iVar < nVar; ++iVar) { - proj += bij[iVar * nVar + jVar] * PassiveAssign(n[iVar]); - } - for (auto iVar = 0ul; iVar < nVar; ++iVar) { - bij[iVar * nVar + jVar] -= proj * PassiveAssign(n[iVar]); - } + for (auto iVar = 0ul; iVar < nVar; ++iVar) proj += bij[iVar * nVar + jVar] * PassiveAssign(n[iVar]); + for (auto iVar = 0ul; iVar < nVar; ++iVar) bij[iVar * nVar + jVar] -= proj * PassiveAssign(n[iVar]); } - - /*--- This part doesn't have the "*2" factor because the product components - * were removed from the result of removing the solution components - * instead of from the original block (bji == bij). ---*/ - if (node_i == node_j) { - for (auto iVar = 0ul; iVar < nVar; ++iVar) { - for (auto jVar = 0ul; jVar < nVar; ++jVar) { + if (isDiag) { + for (auto iVar = 0ul; iVar < nVar; ++iVar) + for (auto jVar = 0ul; jVar < nVar; ++jVar) bij[iVar * nVar + jVar] += PassiveAssign(n[iVar]) * nbn * PassiveAssign(n[jVar]); - } - } } - } + }; + for (auto k = mat.row_ptr_l[node_i]; k < mat.row_ptr_l[node_i + 1]; ++k) processCol(mat.col_ind_l[k], false); + processCol(node_i, true); + for (auto k = mat.row_ptr_u[node_i]; k < mat.row_ptr_u[node_i + 1]; ++k) processCol(mat.col_ind_u[k], false); OtherType proj{}; for (auto iVar = 0ul; iVar < nVar; ++iVar) proj += b(node_i, iVar) * n[iVar]; @@ -1229,14 +1247,16 @@ void CSysMatrix::SetDiagonalAsColumnSum() { SU2_OMP_FOR_DYN(omp_heavy_size) for (auto iPoint = 0ul; iPoint < nPoint; ++iPoint) { - auto block_ii = &matrix[dia_ptr[iPoint] * nVar * nEqn]; + auto* d_i = &mat.d[iPoint * nVar * nEqn]; + for (auto k = 0ul; k < nVar * nEqn; ++k) d_i[k] = 0.0; - for (auto k = 0ul; k < nVar * nEqn; ++k) block_ii[k] = 0.0; + /*--- For each L entry (iPoint, j): subtract its U-transpose (j, iPoint). ---*/ + for (auto k_l = mat.row_ptr_l[iPoint]; k_l < mat.row_ptr_l[iPoint + 1]; ++k_l) + MatrixSubtraction(d_i, &mat.u[l_to_u_transp[k_l] * nVar * nEqn], d_i); - for (auto k = row_ptr[iPoint]; k < row_ptr[iPoint + 1]; ++k) { - auto block_ji = &matrix[col_ptr[k] * nVar * nEqn]; - if (block_ji != block_ii) MatrixSubtraction(block_ii, block_ji, block_ii); - } + /*--- For each U entry (iPoint, j): subtract its L-transpose (j, iPoint). ---*/ + for (auto k_u = mat.row_ptr_u[iPoint]; k_u < mat.row_ptr_u[iPoint + 1]; ++k_u) + MatrixSubtraction(d_i, &mat.l[u_to_l_transp[k_u] * nVar * nEqn], d_i); } END_SU2_OMP_FOR } @@ -1262,38 +1282,34 @@ void CSysMatrix::TransposeInPlace() { /*--- Swap ij with ji and transpose them. ---*/ - if (edge_ptr) { - /*--- The FV way. ---*/ + if (edge_ptr_l) { + /*--- FV path: each edge maps to one U and one L block. ---*/ SU2_OMP_FOR_DYN(omp_heavy_size * 2) - for (auto iEdge = 0ul; iEdge < edge_ptr.nEdge; ++iEdge) { - auto bij = &matrix[edge_ptr(iEdge, 0) * nVar * nVar]; - auto bji = &matrix[edge_ptr(iEdge, 1) * nVar * nVar]; - - swapAndTransp(nVar, bij, bji); + for (auto iEdge = 0ul; iEdge < mat.nnz_l; ++iEdge) { + auto* bij_u = &mat.u[iEdge * nVar * nVar]; + auto* bji_l = &mat.l[edge_ptr_l[iEdge] * nVar * nVar]; + swapAndTransp(nVar, bij_u, bji_l); } END_SU2_OMP_FOR - } else if (col_ptr) { - /*--- If the column pointer was built. ---*/ + } else if (l_to_u_transp) { + /*--- FEM/general path: use the L→U transpose map (one L entry per pair). ---*/ SU2_OMP_FOR_DYN(omp_heavy_size) for (auto iPoint = 0ul; iPoint < nPoint; ++iPoint) { - for (auto k = row_ptr[iPoint]; k < dia_ptr[iPoint]; ++k) { - auto bij = &matrix[k * nVar * nVar]; - auto bji = &matrix[col_ptr[k] * nVar * nVar]; - - swapAndTransp(nVar, bij, bji); + for (auto k_l = mat.row_ptr_l[iPoint]; k_l < mat.row_ptr_l[iPoint + 1]; ++k_l) { + const auto k_u = l_to_u_transp[k_l]; + swapAndTransp(nVar, &mat.u[k_u * nVar * nVar], &mat.l[k_l * nVar * nVar]); } } END_SU2_OMP_FOR } else { - /*--- Slow fallback, needs to search for ji. ---*/ + /*--- Slow fallback: search for each U entry's L partner via GetBlock. ---*/ SU2_OMP_FOR_DYN(omp_heavy_size) for (auto iPoint = 0ul; iPoint < nPoint; ++iPoint) { - for (auto k = dia_ptr[iPoint] + 1ul; k < row_ptr[iPoint + 1]; ++k) { - const auto jPoint = col_ind[k]; - auto bij = &matrix[k * nVar * nVar]; - auto bji = GetBlock(jPoint, iPoint); + for (auto k_u = mat.row_ptr_u[iPoint]; k_u < mat.row_ptr_u[iPoint + 1]; ++k_u) { + const auto jPoint = mat.col_ind_u[k_u]; + auto* bij = &mat.u[k_u * nVar * nVar]; + auto* bji = GetBlock(jPoint, iPoint); assert(bji && "Pattern is not symmetric."); - swapAndTransp(nVar, bij, bji); } } @@ -1304,7 +1320,7 @@ void CSysMatrix::TransposeInPlace() { SU2_OMP_FOR_STAT(omp_heavy_size) for (auto iPoint = 0ul; iPoint < nPoint; ++iPoint) { - auto bii = &matrix[dia_ptr[iPoint] * nVar * nVar]; + auto bii = &mat.d[iPoint * nVar * nVar]; for (auto i = 0ul; i < nVar; ++i) for (auto j = 0ul; j < i; ++j) std::swap(bii[i * nVar + j], bii[j * nVar + i]); } @@ -1320,16 +1336,20 @@ void CSysMatrix::TransposeInPlace() { template void CSysMatrix::MatrixMatrixAddition(ScalarType alpha, const CSysMatrix& B) { SU2_ZONE_SCOPED - /*--- Check that the sparse structure is shared between the two matrices, - * comparing pointers is ok as they are obtained from CGeometry. ---*/ - bool ok = (row_ptr == B.row_ptr) && (col_ind == B.col_ind) && (nVar == B.nVar) && (nEqn == B.nEqn) && (nnz == B.nnz); - - if (!ok) { - SU2_MPI::Error("Matrices do not have compatible sparsity.", CURRENT_FUNCTION); - } + /*--- Check that the LDU structure is shared (pointer equality since both come from CGeometry). ---*/ + const bool ok = (mat.row_ptr_l == B.mat.row_ptr_l) && (mat.col_ind_l == B.mat.col_ind_l) && + (mat.row_ptr_u == B.mat.row_ptr_u) && (mat.col_ind_u == B.mat.col_ind_u) && (nVar == B.nVar) && + (nEqn == B.nEqn) && (nPoint == B.nPoint) && (mat.nnz_l == B.mat.nnz_l) && (mat.nnz_u == B.mat.nnz_u); + if (!ok) SU2_MPI::Error("Matrices do not have compatible sparsity.", CURRENT_FUNCTION); SU2_OMP_FOR_STAT(omp_light_size) - for (auto i = 0ul; i < nnz * nVar * nEqn; ++i) matrix[i] += alpha * B.matrix[i]; + for (auto i = 0ul; i < nPoint * nVar * nEqn; ++i) mat.d[i] += alpha * B.mat.d[i]; + END_SU2_OMP_FOR + SU2_OMP_FOR_STAT(omp_light_size) + for (auto i = 0ul; i < mat.nnz_l * nVar * nEqn; ++i) mat.l[i] += alpha * B.mat.l[i]; + END_SU2_OMP_FOR + SU2_OMP_FOR_STAT(omp_light_size) + for (auto i = 0ul; i < mat.nnz_u * nVar * nEqn; ++i) mat.u[i] += alpha * B.mat.u[i]; END_SU2_OMP_FOR } @@ -1338,9 +1358,9 @@ void CSysMatrix::BuildPastixPreconditioner(CGeometry* geometry, cons unsigned short kind_fact) { SU2_ZONE_SCOPED #ifdef HAVE_PASTIX - /*--- Pastix will launch nested threads. ---*/ BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { - pastix_wrapper.SetMatrix(nVar, nPoint, nPointDomain, row_ptr, col_ind, matrix); + pastix_wrapper.SetLDU(nVar, nPoint, nPointDomain, mat.row_ptr_l, mat.col_ind_l, mat.row_ptr_u, mat.col_ind_u, mat.d, + mat.l, mat.u); pastix_wrapper.Factorize(geometry, config, kind_fact); } END_SU2_OMP_SAFE_GLOBAL_ACCESS diff --git a/Common/src/linear_algebra/CSysMatrixGPU.cu b/Common/src/linear_algebra/CSysMatrixGPU.cu index a3f1a77ca404..1ebba30097c2 100644 --- a/Common/src/linear_algebra/CSysMatrixGPU.cu +++ b/Common/src/linear_algebra/CSysMatrixGPU.cu @@ -28,112 +28,70 @@ #include "../../include/linear_algebra/CSysMatrix.hpp" #include "../../include/linear_algebra/GPUComms.cuh" -#include -#include -#include - -inline void cusparseAssert(cusparseStatus_t code, const char* file, int line, bool abort = true) { - if (code != CUSPARSE_STATUS_SUCCESS) { - fprintf(stderr, "cuSPARSEassert: %s %s %d\n", cusparseGetErrorString(code), file, line); - if (abort) exit(static_cast(code)); +/*! + * \brief Block-LDU SpMV kernel: y[iRow] = (L + D + U) * x per block-row. + * One CUDA block per block-row; threadIdx.x indexes output variable (0..nVar-1). + */ +template +__global__ void BlockLDU_SpMV_kernel(unsigned long nRows, unsigned long nVar, + const unsigned long* __restrict__ row_ptr_l, + const unsigned long* __restrict__ col_ind_l, + const ScalarType* __restrict__ mat_l, + const ScalarType* __restrict__ mat_d, + const unsigned long* __restrict__ row_ptr_u, + const unsigned long* __restrict__ col_ind_u, + const ScalarType* __restrict__ mat_u, + const ScalarType* __restrict__ x, ScalarType* __restrict__ y) { + const unsigned long iRow = blockIdx.x; + const unsigned long iVar = threadIdx.x; + if (iRow >= nRows || iVar >= nVar) return; + + ScalarType sum = 0; + /* Lower */ + for (auto k = row_ptr_l[iRow]; k < row_ptr_l[iRow + 1]; ++k) { + const auto col = col_ind_l[k]; + const ScalarType* blk = mat_l + k * nVar * nVar + iVar * nVar; + for (unsigned long jVar = 0; jVar < nVar; ++jVar) sum += blk[jVar] * x[col * nVar + jVar]; } -} - -#define cusparseErrChk(ans) \ - { \ - cusparseAssert((ans), __FILE__, __LINE__); \ + /* Diagonal */ + { + const ScalarType* blk = mat_d + iRow * nVar * nVar + iVar * nVar; + for (unsigned long jVar = 0; jVar < nVar; ++jVar) sum += blk[jVar] * x[iRow * nVar + jVar]; } - -inline cusparseIndexType_t GetCusparseIndexType() { - if constexpr (sizeof(unsigned long) == 4) { - return CUSPARSE_INDEX_32I; - } else if constexpr (sizeof(unsigned long) == 8) { - return CUSPARSE_INDEX_64I; - } else { - static_assert(sizeof(unsigned long) == 4 || sizeof(unsigned long) == 8, - "cuSPARSE BSR SpMV only supports 32-bit or 64-bit index arrays in this path."); + /* Upper */ + for (auto k = row_ptr_u[iRow]; k < row_ptr_u[iRow + 1]; ++k) { + const auto col = col_ind_u[k]; + const ScalarType* blk = mat_u + k * nVar * nVar + iVar * nVar; + for (unsigned long jVar = 0; jVar < nVar; ++jVar) sum += blk[jVar] * x[col * nVar + jVar]; } + y[iRow * nVar + iVar] = sum; } template -constexpr cudaDataType GetCudaDataType() { - if constexpr (std::is_same::value) { - return CUDA_R_32F; - } else if constexpr (std::is_same::value) { - return CUDA_R_64F; - } else { - static_assert(std::is_same::value || std::is_same::value, - "cuSPARSE BSR SpMV only supports float and double in this path."); - } -} - -template -void CSysMatrix::HtDTransfer(bool trigger) const -{ - if(trigger) gpuErrChk(cudaMemcpy((void*)(d_matrix), (void*)&matrix[0], (sizeof(ScalarType)*nnz*nVar*nEqn), cudaMemcpyHostToDevice)); +void CSysMatrix::HtDTransfer(bool trigger) const { + if (!trigger) return; + gpuErrChk(cudaMemcpy(gpu.d, mat.d, sizeof(ScalarType) * nPoint * nVar * nEqn, cudaMemcpyHostToDevice)); + gpuErrChk(cudaMemcpy(gpu.l, mat.l, sizeof(ScalarType) * mat.nnz_l * nVar * nEqn, cudaMemcpyHostToDevice)); + gpuErrChk(cudaMemcpy(gpu.u, mat.u, sizeof(ScalarType) * mat.nnz_u * nVar * nEqn, cudaMemcpyHostToDevice)); } template void CSysMatrix::GPUMatrixVectorProduct(const CSysVector& vec, CSysVector& prod, CGeometry* geometry, const CConfig* config) const { if (nVar != nEqn) { - SU2_MPI::Error("CUDA CSysMatrix matvec with cuSPARSE BSR requires square blocks.", CURRENT_FUNCTION); + SU2_MPI::Error("CUDA CSysMatrix block-LDU SpMV requires square blocks.", CURRENT_FUNCTION); } ScalarType* d_vec = vec.GetDevicePointer(); ScalarType* d_prod = prod.GetDevicePointer(); - vec.HtDTransfer(); - const auto indexType = GetCusparseIndexType(); - const auto valueType = GetCudaDataType(); - - const std::int64_t blockSize = static_cast(nVar); - - const std::int64_t brows = static_cast(nPointDomain); - const std::int64_t bcols = static_cast(nPoint); - const std::int64_t bnnz = static_cast(nnz); - - const std::int64_t xSize = static_cast(nPoint) * blockSize; - const std::int64_t ySize = static_cast(nPointDomain) * blockSize; - - const ScalarType alpha = 1.0; - const ScalarType beta = 0.0; - - cusparseHandle_t handle = nullptr; - cusparseConstSpMatDescr_t matA = nullptr; - cusparseDnVecDescr_t vecX = nullptr; - cusparseDnVecDescr_t vecY = nullptr; - - cusparseErrChk(cusparseCreate(&handle)); - - cusparseErrChk(cusparseCreateConstBsr(&matA, brows, bcols, bnnz, blockSize, blockSize, d_row_ptr, d_col_ind, d_matrix, - indexType, indexType, CUSPARSE_INDEX_BASE_ZERO, valueType, CUSPARSE_ORDER_ROW)); - - cusparseErrChk(cusparseCreateDnVec(&vecX, xSize, d_vec, valueType)); - cusparseErrChk(cusparseCreateDnVec(&vecY, ySize, d_prod, valueType)); - - size_t bufferSize = 0; - void* dBuffer = nullptr; - - cusparseErrChk(cusparseSpMV_bufferSize(handle, CUSPARSE_OPERATION_NON_TRANSPOSE, &alpha, matA, vecX, &beta, vecY, - valueType, CUSPARSE_SPMV_BSR_ALG1, &bufferSize)); - - if (bufferSize > 0) { - gpuErrChk(cudaMalloc(&dBuffer, bufferSize)); - } - - cusparseErrChk(cusparseSpMV(handle, CUSPARSE_OPERATION_NON_TRANSPOSE, &alpha, matA, vecX, &beta, vecY, valueType, - CUSPARSE_SPMV_BSR_ALG1, dBuffer)); - - if (dBuffer != nullptr) { - gpuErrChk(cudaFree(dBuffer)); - } - - cusparseErrChk(cusparseDestroyDnVec(vecY)); - cusparseErrChk(cusparseDestroyDnVec(vecX)); - cusparseErrChk(cusparseDestroySpMat(matA)); - cusparseErrChk(cusparseDestroy(handle)); + dim3 blockDim(static_cast(nVar), 1, 1); + dim3 gridDim(static_cast(nPointDomain), 1, 1); + BlockLDU_SpMV_kernel<<>>( + nPointDomain, nVar, gpu.row_ptr_l, gpu.col_ind_l, gpu.l, gpu.d, + gpu.row_ptr_u, gpu.col_ind_u, gpu.u, d_vec, d_prod); + gpuErrChk(cudaGetLastError()); prod.DtHTransfer(); } diff --git a/Common/src/linear_algebra/CSysSolve.cpp b/Common/src/linear_algebra/CSysSolve.cpp index cd100f311ab0..aae5d9ce7075 100644 --- a/Common/src/linear_algebra/CSysSolve.cpp +++ b/Common/src/linear_algebra/CSysSolve.cpp @@ -211,12 +211,14 @@ bool CSysSolve::ModGramSchmidt(bool shared_hsbg, int i, su2matrix::multiDot(w, i + 1, 1, w, i + 1); LinearCombination( shared_hsbg, i + 1, w, [&h_i](int k) { return -h_i(0, k); }, w[i + 1], true); - - const auto& dh_i = CSysVector::multiDot(w, i + 1, 1, w, i + 1); - LinearCombination( - shared_hsbg, i + 1, w, [&dh_i](int k) { return -dh_i(0, k); }, w[i + 1], true); - - for (int k = 0; k < i + 1; k++) SetHsbg(k, i, h_i(0, k) + dh_i(0, k)); + if (i < 5) { + for (int k = 0; k < i + 1; k++) SetHsbg(k, i, h_i(0, k)); + } else { + const auto& dh_i = CSysVector::multiDot(w, i + 1, 1, w, i + 1); + LinearCombination( + shared_hsbg, i + 1, w, [&dh_i](int k) { return -dh_i(0, k); }, w[i + 1], true); + for (int k = 0; k < i + 1; k++) SetHsbg(k, i, h_i(0, k) + dh_i(0, k)); + } /*--- The norm of w[i+1] is 0 or NaN: the input vector from mat_vec is * zero or contains NaN. Cannot proceed with orthogonalization. ---*/ diff --git a/TestCases/cont_adj_euler/naca0012/of_grad_cd_disc.dat.ref b/TestCases/cont_adj_euler/naca0012/of_grad_cd_disc.dat.ref index 6f26c9c89c31..a18ea246d5bf 100644 --- a/TestCases/cont_adj_euler/naca0012/of_grad_cd_disc.dat.ref +++ b/TestCases/cont_adj_euler/naca0012/of_grad_cd_disc.dat.ref @@ -1,39 +1,39 @@ -VARIABLES="VARIABLE" , "GRADIENT" , "FINDIFF_STEP" - 0 , -9031.62 , 0.001 - 1 , -12012.1 , 0.001 - 2 , -9069.4 , 0.001 - 3 , -4384.1 , 0.001 - 4 , 226.313 , 0.001 - 5 , 4094.64 , 0.001 - 6 , 7135.57 , 0.001 - 7 , 9534.04 , 0.001 - 8 , 11567.7 , 0.001 - 9 , 13502.9 , 0.001 - 10 , 15525.3 , 0.001 - 11 , 17677.6 , 0.001 - 12 , 19774.0 , 0.001 - 13 , 21231.7 , 0.001 - 14 , 20667.9 , 0.001 - 15 , 14910.9 , 0.001 - 16 , -3204.93 , 0.001 - 17 , -47800.8 , 0.001 - 18 , -127342.0 , 0.001 - 19 , -22401.2 , 0.001 - 20 , -22469.9 , 0.001 - 21 , -15796.2 , 0.001 - 22 , -9601.62 , 0.001 - 23 , -5984.92 , 0.001 - 24 , -5040.23 , 0.001 - 25 , -6061.17 , 0.001 - 26 , -8142.27 , 0.001 - 27 , -10495.3 , 0.001 - 28 , -12591.9 , 0.001 - 29 , -14189.4 , 0.001 - 30 , -15280.9 , 0.001 - 31 , -15978.6 , 0.001 - 32 , -16253.9 , 0.001 - 33 , -15289.0 , 0.001 - 34 , -10118.9 , 0.001 - 35 , 5881.72 , 0.001 - 36 , 40595.3 , 0.001 - 37 , 83547.0 , 0.001 +VARIABLES="VARIABLE" , "GRADIENT" , "FINDIFF_STEP" + 0 , -9252.48 , 0.001 + 1 , -12069.7 , 0.001 + 2 , -9121.57 , 0.001 + 3 , -4501.43 , 0.001 + 4 , 17.2477 , 0.001 + 5 , 3791.15 , 0.001 + 6 , 6746.92 , 0.001 + 7 , 9074.82 , 0.001 + 8 , 11055.2 , 0.001 + 9 , 12958.1 , 0.001 + 10 , 14976.4 , 0.001 + 11 , 17163.0 , 0.001 + 12 , 19338.7 , 0.001 + 13 , 20908.5 , 0.001 + 14 , 20442.6 , 0.001 + 15 , 14730.6 , 0.001 + 16 , -3146.36 , 0.001 + 17 , -46143.3 , 0.001 + 18 , -121515.0 , 0.001 + 19 , -23126.9 , 0.001 + 20 , -22603.3 , 0.001 + 21 , -15691.7 , 0.001 + 22 , -9459.14 , 0.001 + 23 , -5880.71 , 0.001 + 24 , -4985.65 , 0.001 + 25 , -6039.68 , 0.001 + 26 , -8128.46 , 0.001 + 27 , -10463.5 , 0.001 + 28 , -12519.2 , 0.001 + 29 , -14057.4 , 0.001 + 30 , -15078.1 , 0.001 + 31 , -15707.7 , 0.001 + 32 , -15951.5 , 0.001 + 33 , -15062.1 , 0.001 + 34 , -10170.7 , 0.001 + 35 , 5416.98 , 0.001 + 36 , 40795.0 , 0.001 + 37 , 88620.5 , 0.001 diff --git a/TestCases/cont_adj_euler/naca0012/of_grad_directdiff.dat.ref b/TestCases/cont_adj_euler/naca0012/of_grad_directdiff.dat.ref index 7b5deefbf4d4..7db911616f73 100644 --- a/TestCases/cont_adj_euler/naca0012/of_grad_directdiff.dat.ref +++ b/TestCases/cont_adj_euler/naca0012/of_grad_directdiff.dat.ref @@ -1,4 +1,4 @@ VARIABLES="VARIABLE" , "DRAG" , "EFFICIENCY" , "FORCE_X" , "FORCE_Y" , "FORCE_Z" , "LIFT" , "MOMENT_X" , "MOMENT_Y" , "MOMENT_Z" , "SIDEFORCE" - 0 , 0.3284091436 , -23.11888342 , 0.3603561695 , -1.460528943 , 0.0 , -1.468042505 , 0.0 , 0.0 , 0.3792955711 , 0.0 - 1 , 0.5523746333 , -38.65233166 , 0.6056912087 , -2.437438346 , 0.0 , -2.450071385 , 0.0 , 0.0 , 0.1342428158 , 0.0 - 2 , 0.7817003562 , -46.2043006 , 0.8419313277 , -2.751818981 , 0.0 , -2.769530757 , 0.0 , 0.0 , -0.1514885655 , 0.0 + 0 , 0.2688089461 , -21.61711857 , 0.3003183821 , -1.441124643 , 0.0 , -1.447333105 , 0.0 , 0.0 , 0.3977552572 , 0.0 + 1 , 0.5278545669 , -37.13677946 , 0.580192762 , -2.392867291 , 0.0 , -2.404954692 , 0.0 , 0.0 , 0.1526764135 , 0.0 + 2 , 0.7714722803 , -44.76622728 , 0.8308940915 , -2.714847262 , 0.0 , -2.732327061 , 0.0 , 0.0 , -0.1348424792 , 0.0 diff --git a/TestCases/disc_adj_fsi/config.cfg b/TestCases/disc_adj_fsi/config.cfg index f9729cb5adb4..a415882b3dc7 100644 --- a/TestCases/disc_adj_fsi/config.cfg +++ b/TestCases/disc_adj_fsi/config.cfg @@ -4,7 +4,7 @@ CONFIG_LIST=(configFlow.cfg, configFEA.cfg) MARKER_ZONE_INTERFACE = (UpperWall, UpperWallS, LowerWall, LowerWallS) -OUTER_ITER= 7 +OUTER_ITER= 10 MESH_FILENAME= mesh.su2 OBJECTIVE_FUNCTION = REFERENCE_GEOMETRY diff --git a/TestCases/disc_adj_fsi/configFEA.cfg b/TestCases/disc_adj_fsi/configFEA.cfg index 636d58b93ef9..b99b4f172742 100644 --- a/TestCases/disc_adj_fsi/configFEA.cfg +++ b/TestCases/disc_adj_fsi/configFEA.cfg @@ -43,12 +43,14 @@ MARKER_CLAMPED = ( Clamped_Right, Clamped_Left ) MARKER_FLUID_LOAD= ( LowerWallS, UpperWallS) -LINEAR_SOLVER= FGMRES +LINEAR_SOLVER= FGCRODR LINEAR_SOLVER_PREC= ILU -LINEAR_SOLVER_ERROR= 1E-9 -LINEAR_SOLVER_ITER= 100 +LINEAR_SOLVER_ERROR= 1E-5 +LINEAR_SOLVER_ITER= 200 +LINEAR_SOLVER_RESTART_FREQUENCY= 100 +LINEAR_SOLVER_RESTART_DEFLATION= 20 -DISCADJ_LIN_SOLVER = FGMRES +DISCADJ_LIN_SOLVER = FGCRODR DISCADJ_LIN_PREC = ILU CONV_RESIDUAL_MINVAL= -10 @@ -60,6 +62,8 @@ VOLUME_FILENAME= results_beam CONV_FILENAME= history +SCREEN_OUTPUT= RMS_RES, LINSOL + BREAKDOWN_FILENAME= forces_breakdown.dat SOLUTION_FILENAME= solution_beam RESTART_FILENAME= restart_beam diff --git a/TestCases/hybrid_regression.py b/TestCases/hybrid_regression.py index 612afb6c50a5..e32cd1b071f0 100644 --- a/TestCases/hybrid_regression.py +++ b/TestCases/hybrid_regression.py @@ -51,7 +51,7 @@ def main(): channel.cfg_dir = "euler/channel" channel.cfg_file = "inv_channel_RK.cfg" channel.test_iter = 20 - channel.test_vals = [-2.000215, 3.536936, 0.034212, 0.193725] + channel.test_vals = [-2.000214, 3.536937, 0.034200, 0.193725] test_list.append(channel) # NACA0012 @@ -59,7 +59,7 @@ def main(): naca0012.cfg_dir = "euler/naca0012" naca0012.cfg_file = "inv_NACA0012_Roe.cfg" naca0012.test_iter = 20 - naca0012.test_vals = [-4.491302, -3.929519, 0.297210, 0.025485] + naca0012.test_vals = [-4.387423, -3.845336, 0.296291, 0.025144] test_list.append(naca0012) # Supersonic wedge @@ -67,7 +67,7 @@ def main(): wedge.cfg_dir = "euler/wedge" wedge.cfg_file = "inv_wedge_HLLC.cfg" wedge.test_iter = 20 - wedge.test_vals = [-3.691631, 2.032783, -0.249531, 0.043953] + wedge.test_vals = [-3.690258, 2.034169, -0.249531, 0.043953] test_list.append(wedge) # ONERA M6 Wing @@ -75,7 +75,7 @@ def main(): oneram6.cfg_dir = "euler/oneram6" oneram6.cfg_file = "inv_ONERAM6.cfg" oneram6.test_iter = 10 - oneram6.test_vals = [0.280800, 0.008623] + oneram6.test_vals = [0.280803, 0.008625] test_list.append(oneram6) # Fixed CL NACA0012 @@ -83,7 +83,7 @@ def main(): fixedCL_naca0012.cfg_dir = "fixed_cl/naca0012" fixedCL_naca0012.cfg_file = "inv_NACA0012.cfg" fixedCL_naca0012.test_iter = 10 - fixedCL_naca0012.test_vals = [-4.017883, 1.513039, 0.300961, 0.019472] + fixedCL_naca0012.test_vals = [-4.010006, 1.521019, 0.300952, 0.019472] test_list.append(fixedCL_naca0012) # HYPERSONIC FLOW PAST BLUNT BODY @@ -103,7 +103,7 @@ def main(): flatplate.cfg_dir = "navierstokes/flatplate" flatplate.cfg_file = "lam_flatplate.cfg" flatplate.test_iter = 100 - flatplate.test_vals = [-6.537258, -1.059049, 0.001198, 0.029303, 2.361500, -2.332200, 0.000000, 0.000000] + flatplate.test_vals = [-6.543288, -1.065157, 0.001196, 0.029390, 2.361500, -2.332100, 0.000000, 0.000000] test_list.append(flatplate) # Laminar cylinder (steady) @@ -111,7 +111,7 @@ def main(): cylinder.cfg_dir = "navierstokes/cylinder" cylinder.cfg_file = "lam_cylinder.cfg" cylinder.test_iter = 25 - cylinder.test_vals = [-8.463699, -2.981890, 0.045839, 1.659615, 0.000000] + cylinder.test_vals = [-8.500691, -3.024888, 0.037975, 1.664042, 0.000000] test_list.append(cylinder) # Laminar cylinder (low Mach correction) @@ -119,7 +119,7 @@ def main(): cylinder_lowmach.cfg_dir = "navierstokes/cylinder" cylinder_lowmach.cfg_file = "cylinder_lowmach.cfg" cylinder_lowmach.test_iter = 25 - cylinder_lowmach.test_vals = [-6.473668, -1.011938, 0.275173, 71.130747, 0.000000] + cylinder_lowmach.test_vals = [-6.476618, -1.014896, 0.594402, 70.856965, 0.000000] cylinder_lowmach.test_vals_aarch64 = [-6.830996, -1.368850, -0.143956, 73.963354, 0] test_list.append(cylinder_lowmach) @@ -128,7 +128,7 @@ def main(): poiseuille.cfg_dir = "navierstokes/poiseuille" poiseuille.cfg_file = "lam_poiseuille.cfg" poiseuille.test_iter = 10 - poiseuille.test_vals = [-5.046139, 0.652976, 0.008353, 13.735637, 0] + poiseuille.test_vals = [-5.046182, 0.652932, 0.008488, 13.734488, 0.000000] test_list.append(poiseuille) # 2D Poiseuille flow (inlet profile file) @@ -136,7 +136,7 @@ def main(): poiseuille_profile.cfg_dir = "navierstokes/poiseuille" poiseuille_profile.cfg_file = "profile_poiseuille.cfg" poiseuille_profile.test_iter = 10 - poiseuille_profile.test_vals = [-12.005136, -7.582074, -0.000000, 2.089953] + poiseuille_profile.test_vals = [-12.005131, -7.582325, -0.000000, 2.089953] poiseuille_profile.test_vals_aarch64 = [-12.009012, -7.262530, -0.000000, 2.089953] test_list.append(poiseuille_profile) @@ -145,7 +145,7 @@ def main(): periodic2d.cfg_dir = "navierstokes/periodic2D" periodic2d.cfg_file = "config.cfg" periodic2d.test_iter = 1400 - periodic2d.test_vals = [-10.817616, -8.363550, -8.287466, -5.334109, -1.088411, -2945.200000] + periodic2d.test_vals = [-10.817608, -8.363542, -8.287458, -5.334102, -1.088411, -2945.200000] test_list.append(periodic2d) ########################## @@ -157,7 +157,7 @@ def main(): rae2822_sa.cfg_dir = "rans/rae2822" rae2822_sa.cfg_file = "turb_SA_RAE2822.cfg" rae2822_sa.test_iter = 20 - rae2822_sa.test_vals = [-2.192911, -5.312614, 0.388496, 0.077215, 0.000000] + rae2822_sa.test_vals = [-2.190527, -5.318180, 0.383386, 0.077605, 0.000000] test_list.append(rae2822_sa) # RAE2822 SST @@ -165,7 +165,7 @@ def main(): rae2822_sst.cfg_dir = "rans/rae2822" rae2822_sst.cfg_file = "turb_SST_RAE2822.cfg" rae2822_sst.test_iter = 20 - rae2822_sst.test_vals = [-1.028279, 5.869352, 0.357142, 0.074560, 0.000000] + rae2822_sst.test_vals = [-1.028081, 5.870628, 0.370417, 0.074982, 0.000000] test_list.append(rae2822_sst) # RAE2822 SST_SUST @@ -173,7 +173,7 @@ def main(): rae2822_sst_sust.cfg_dir = "rans/rae2822" rae2822_sst_sust.cfg_file = "turb_SST_SUST_RAE2822.cfg" rae2822_sst_sust.test_iter = 20 - rae2822_sst_sust.test_vals = [-2.479281, 5.869341, 0.357142, 0.074560] + rae2822_sst_sust.test_vals = [-2.487683, 5.870614, 0.370417, 0.074982] test_list.append(rae2822_sst_sust) # Flat plate @@ -181,7 +181,7 @@ def main(): turb_flatplate.cfg_dir = "rans/flatplate" turb_flatplate.cfg_file = "turb_SA_flatplate.cfg" turb_flatplate.test_iter = 20 - turb_flatplate.test_vals = [-4.958138, -7.438030, -0.187473, 0.015060] + turb_flatplate.test_vals = [-4.958328, -7.438303, -0.187472, 0.015052] test_list.append(turb_flatplate) # ONERA M6 Wing @@ -189,7 +189,7 @@ def main(): turb_oneram6.cfg_dir = "rans/oneram6" turb_oneram6.cfg_file = "turb_ONERAM6.cfg" turb_oneram6.test_iter = 10 - turb_oneram6.test_vals = [-2.418702, -6.631573, 0.238585, 0.159599, 0.000000] + turb_oneram6.test_vals = [-2.418706, -6.631575, 0.238586, 0.159599, 0.000000] test_list.append(turb_oneram6) # NACA0012 (SA, FUN3D finest grid results: CL=1.0983, CD=0.01242) @@ -197,7 +197,7 @@ def main(): turb_naca0012_sa.cfg_dir = "rans/naca0012" turb_naca0012_sa.cfg_file = "turb_NACA0012_sa.cfg" turb_naca0012_sa.test_iter = 5 - turb_naca0012_sa.test_vals = [-12.038045, -16.332088, 1.080346, 0.018385, 20.000000, -2.873565, 0.000000, -14.250270, 0.000000] + turb_naca0012_sa.test_vals = [-12.038011, -16.332088, 1.080346, 0.018385, 20.000000, -2.873515, 0.000000, -14.250270, 0.000000] turb_naca0012_sa.test_vals_aarch64 = [-12.038091, -16.332090, 1.080346, 0.018385, 20.000000, -2.873236, 0.000000, -14.250271, 0.000000] test_list.append(turb_naca0012_sa) @@ -206,7 +206,7 @@ def main(): turb_naca0012_sst.cfg_dir = "rans/naca0012" turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" turb_naca0012_sst.test_iter = 10 - turb_naca0012_sst.test_vals = [-12.093924, -15.251077, -5.906324, 1.070413, 0.015775, -2.855555, 0.000000] + turb_naca0012_sst.test_vals = [-12.093908, -15.250756, -5.906323, 1.070413, 0.015775, -2.855199, 0.000000] turb_naca0012_sst.test_vals_aarch64 = [-12.075928, -15.246732, -5.861249, 1.070036, 0.015841, -2.835263, 0] test_list.append(turb_naca0012_sst) @@ -215,7 +215,7 @@ def main(): turb_naca0012_sst_sust.cfg_dir = "rans/naca0012" turb_naca0012_sst_sust.cfg_file = "turb_NACA0012_sst_sust.cfg" turb_naca0012_sst_sust.test_iter = 10 - turb_naca0012_sst_sust.test_vals = [-12.080806, -14.837176, -5.732905, 1.000893, 0.019109, -2.119816] + turb_naca0012_sst_sust.test_vals = [-12.080880, -14.837176, -5.732907, 1.000893, 0.019109, -2.119818] turb_naca0012_sst_sust.test_vals_aarch64 = [-12.073210, -14.836724, -5.732627, 1.000050, 0.019144, -2.629689] test_list.append(turb_naca0012_sst_sust) @@ -224,7 +224,7 @@ def main(): turb_naca0012_sst_fixedvalues.cfg_dir = "rans/naca0012" turb_naca0012_sst_fixedvalues.cfg_file = "turb_NACA0012_sst_fixedvalues.cfg" turb_naca0012_sst_fixedvalues.test_iter = 10 - turb_naca0012_sst_fixedvalues.test_vals = [-5.192391, -10.448080, 0.773965, 1.022535, 0.040529, -2.383421] + turb_naca0012_sst_fixedvalues.test_vals = [-5.192390, -10.448219, 0.773965, 1.022535, 0.040529, -2.383282] test_list.append(turb_naca0012_sst_fixedvalues) # NACA0012 (SST, explicit Euler for flow and turbulence equations) @@ -240,7 +240,7 @@ def main(): propeller.cfg_dir = "rans/propeller" propeller.cfg_file = "propeller.cfg" propeller.test_iter = 10 - propeller.test_vals = [-3.389724, -8.410479, 0.000048, 0.056344] + propeller.test_vals = [-3.389718, -8.410477, 0.000048, 0.056343] test_list.append(propeller) ####################################### @@ -252,7 +252,7 @@ def main(): axi_rans_air_nozzle_restart.cfg_dir = "axisymmetric_rans/air_nozzle" axi_rans_air_nozzle_restart.cfg_file = "air_nozzle_restart.cfg" axi_rans_air_nozzle_restart.test_iter = 10 - axi_rans_air_nozzle_restart.test_vals = [-11.083069, -5.374686, -8.880083, -4.073484, 0.000000] + axi_rans_air_nozzle_restart.test_vals = [-11.083072, -5.374686, -8.880093, -4.073514, 0.000000] axi_rans_air_nozzle_restart.test_vals_aarch64 = [-14.140441, -9.154674, -10.886121, -5.806594, 0.000000] test_list.append(axi_rans_air_nozzle_restart) @@ -266,7 +266,7 @@ def main(): turb_naca0012_sst_restart_mg.cfg_file = "turb_NACA0012_sst_multigrid_restart.cfg" turb_naca0012_sst_restart_mg.test_iter = 20 turb_naca0012_sst_restart_mg.ntest_vals = 5 - turb_naca0012_sst_restart_mg.test_vals = [-6.589114, -5.057151, 0.830239, -0.008808, 0.078150] + turb_naca0012_sst_restart_mg.test_vals = [-6.589092, -5.057151, 0.830239, -0.008809, 0.078148] test_list.append(turb_naca0012_sst_restart_mg) ############################# @@ -278,7 +278,7 @@ def main(): turb_naca0012_1c.cfg_dir = "rans_uq/naca0012" turb_naca0012_1c.cfg_file = "turb_NACA0012_uq_1c.cfg" turb_naca0012_1c.test_iter = 10 - turb_naca0012_1c.test_vals = [-4.980126, 1.343353, 0.443722, -0.029245] + turb_naca0012_1c.test_vals = [-4.978722, 1.343530, 0.443909, -0.029125] turb_naca0012_1c.test_vals_aarch64 = [-4.976620, 1.345983, 0.433171, -0.033685] test_list.append(turb_naca0012_1c) @@ -287,7 +287,7 @@ def main(): turb_naca0012_2c.cfg_dir = "rans_uq/naca0012" turb_naca0012_2c.cfg_file = "turb_NACA0012_uq_2c.cfg" turb_naca0012_2c.test_iter = 10 - turb_naca0012_2c.test_vals = [-5.482849, 1.260866, 0.404517, -0.040307] + turb_naca0012_2c.test_vals = [-5.482844, 1.260870, 0.404377, -0.040361] turb_naca0012_2c.test_vals_aarch64 = [-5.485484, 1.263406, 0.411442, -0.040859] test_list.append(turb_naca0012_2c) @@ -296,7 +296,7 @@ def main(): turb_naca0012_3c.cfg_dir = "rans_uq/naca0012" turb_naca0012_3c.cfg_file = "turb_NACA0012_uq_3c.cfg" turb_naca0012_3c.test_iter = 10 - turb_naca0012_3c.test_vals = [-5.583738, 1.228727, 0.381732, -0.046307] + turb_naca0012_3c.test_vals = [-5.583730, 1.228732, 0.381968, -0.046233] turb_naca0012_3c.test_vals_aarch64 = [-5.583737, 1.232005, 0.390258, -0.046305] test_list.append(turb_naca0012_3c) @@ -305,7 +305,7 @@ def main(): turb_naca0012_p1c1.cfg_dir = "rans_uq/naca0012" turb_naca0012_p1c1.cfg_file = "turb_NACA0012_uq_p1c1.cfg" turb_naca0012_p1c1.test_iter = 10 - turb_naca0012_p1c1.test_vals = [-5.134040, 1.283488, 0.548247, 0.010741] + turb_naca0012_p1c1.test_vals = [-5.134179, 1.283465, 0.553971, 0.011916] turb_naca0012_p1c1.test_vals_aarch64 = [-5.114189, 1.285037, 0.406851, -0.043003] test_list.append(turb_naca0012_p1c1) @@ -314,7 +314,7 @@ def main(): turb_naca0012_p1c2.cfg_dir = "rans_uq/naca0012" turb_naca0012_p1c2.cfg_file = "turb_NACA0012_uq_p1c2.cfg" turb_naca0012_p1c2.test_iter = 10 - turb_naca0012_p1c2.test_vals = [-5.553917, 1.234037, 0.424160, -0.033497] + turb_naca0012_p1c2.test_vals = [-5.553990, 1.234030, 0.424168, -0.033501] turb_naca0012_p1c2.test_vals_aarch64 = [-5.548245, 1.236384, 0.381821, -0.050337] test_list.append(turb_naca0012_p1c2) @@ -335,7 +335,7 @@ def main(): hb_rans_preconditioning.cfg_dir = "harmonic_balance/hb_rans_preconditioning" hb_rans_preconditioning.cfg_file = "davis.cfg" hb_rans_preconditioning.test_iter = 25 - hb_rans_preconditioning.test_vals = [-1.905219, 0.481910, 0.598991, 3.605350, -5.945851] + hb_rans_preconditioning.test_vals = [-1.905220, 0.481910, 0.598990, 3.605349, -5.945852] test_list.append(hb_rans_preconditioning) ############################# @@ -347,7 +347,7 @@ def main(): inc_euler_naca0012.cfg_dir = "incomp_euler/naca0012" inc_euler_naca0012.cfg_file = "incomp_NACA0012.cfg" inc_euler_naca0012.test_iter = 20 - inc_euler_naca0012.test_vals = [-5.846118, -4.941076, 0.519913, 0.008955] + inc_euler_naca0012.test_vals = [-5.858104, -4.937295, 0.519817, 0.008958] test_list.append(inc_euler_naca0012) # C-D nozzle with pressure inlet and mass flow outlet @@ -355,7 +355,7 @@ def main(): inc_nozzle.cfg_dir = "incomp_euler/nozzle" inc_nozzle.cfg_file = "inv_nozzle.cfg" inc_nozzle.test_iter = 20 - inc_nozzle.test_vals = [-6.171980, -5.419034, 0.009267, 0.127577] + inc_nozzle.test_vals = [-6.171116, -5.413059, 0.008862, 0.127559] test_list.append(inc_nozzle) ############################# @@ -367,7 +367,7 @@ def main(): inc_lam_cylinder.cfg_dir = "incomp_navierstokes/cylinder" inc_lam_cylinder.cfg_file = "incomp_cylinder.cfg" inc_lam_cylinder.test_iter = 10 - inc_lam_cylinder.test_vals = [-4.159236, -3.569077, 0.017653, 4.934964] + inc_lam_cylinder.test_vals = [-4.161196, -3.573053, 0.025533, 4.944647] test_list.append(inc_lam_cylinder) # Buoyancy-driven cavity @@ -383,7 +383,7 @@ def main(): inc_poly_cylinder.cfg_dir = "incomp_navierstokes/cylinder" inc_poly_cylinder.cfg_file = "poly_cylinder.cfg" inc_poly_cylinder.test_iter = 20 - inc_poly_cylinder.test_vals = [-8.241956, -2.424341, 0.027285, 1.909614, -173.010000] + inc_poly_cylinder.test_vals = [-8.230355, -2.426276, 0.018487, 1.902089, -173.000000] inc_poly_cylinder.test_vals_aarch64 = [-8.260165, -2.445453, 0.027209, 1.915447, -171.620000] test_list.append(inc_poly_cylinder) @@ -392,7 +392,7 @@ def main(): inc_lam_bend.cfg_dir = "incomp_navierstokes/bend" inc_lam_bend.cfg_file = "lam_bend.cfg" inc_lam_bend.test_iter = 10 - inc_lam_bend.test_vals = [-3.560185, -3.051989, -0.013972, 1.102841] + inc_lam_bend.test_vals = [-3.558795, -3.051040, -0.014118, 1.101254] test_list.append(inc_lam_bend) ############################ @@ -404,7 +404,7 @@ def main(): inc_turb_naca0012.cfg_dir = "incomp_rans/naca0012" inc_turb_naca0012.cfg_file = "naca0012.cfg" inc_turb_naca0012.test_iter = 20 - inc_turb_naca0012.test_vals = [-4.758063, -10.974497, -0.000004, -0.028654, 4.000000, -5.404348, 2.000000, -5.032687] + inc_turb_naca0012.test_vals = [-4.758114, -10.974548, -0.000004, -0.028637, 5.000000, -4.080548, 2.000000, -4.490129] test_list.append(inc_turb_naca0012) # NACA0012, SST_SUST @@ -412,7 +412,7 @@ def main(): inc_turb_naca0012_sst_sust.cfg_dir = "incomp_rans/naca0012" inc_turb_naca0012_sst_sust.cfg_file = "naca0012_SST_SUST.cfg" inc_turb_naca0012_sst_sust.test_iter = 20 - inc_turb_naca0012_sst_sust.test_vals = [-7.170017, 0.332641, 0.000002, 0.312113] + inc_turb_naca0012_sst_sust.test_vals = [-7.170018, 0.332638, 0.000002, 0.312117] test_list.append(inc_turb_naca0012_sst_sust) # Weakly coupled heat equation @@ -420,7 +420,7 @@ def main(): inc_weakly_coupled.cfg_dir = "disc_adj_heat" inc_weakly_coupled.cfg_file = "primal.cfg" inc_weakly_coupled.test_iter = 10 - inc_weakly_coupled.test_vals = [-18.121444, -16.304190, -16.482326, -15.007166, -17.858047, -14.024909, 5.609100] + inc_weakly_coupled.test_vals = [-18.106209, -16.303012, -16.484904, -15.006575, -17.858050, -14.024869, 5.609100] test_list.append(inc_weakly_coupled) ###################################### @@ -432,7 +432,7 @@ def main(): cavity.cfg_dir = "moving_wall/cavity" cavity.cfg_file = "lam_cavity.cfg" cavity.test_iter = 25 - cavity.test_vals = [-7.938907, -2.490199, 0.013042, 0.004995] + cavity.test_vals = [-7.927912, -2.479033, 0.012837, 0.005130] test_list.append(cavity) # Spinning cylinder @@ -440,7 +440,7 @@ def main(): spinning_cylinder.cfg_dir = "moving_wall/spinning_cylinder" spinning_cylinder.cfg_file = "spinning_cylinder.cfg" spinning_cylinder.test_iter = 25 - spinning_cylinder.test_vals = [-7.547526, -2.080576, 1.888705, 1.812327] + spinning_cylinder.test_vals = [-7.533969, -2.066689, 1.832252, 1.843016] spinning_cylinder.test_vals_aarch64 = [-8.008023, -2.611064, 1.497308, 1.487483] test_list.append(spinning_cylinder) @@ -453,7 +453,7 @@ def main(): square_cylinder.cfg_dir = "unsteady/square_cylinder" square_cylinder.cfg_file = "turb_square.cfg" square_cylinder.test_iter = 3 - square_cylinder.test_vals = [-2.560678, -1.175981, 0.062203, 1.399351, 2.219223, 1.399297, 2.217470, 0.000000] + square_cylinder.test_vals = [-2.560678, -1.175981, 0.062204, 1.399351, 2.219223, 1.399297, 2.217470, 0.000000] square_cylinder.unsteady = True test_list.append(square_cylinder) @@ -462,7 +462,7 @@ def main(): sine_gust.cfg_dir = "gust" sine_gust.cfg_file = "inv_gust_NACA0012.cfg" sine_gust.test_iter = 5 - sine_gust.test_vals = [-1.977498, 3.481817, -0.009944, -0.004266] + sine_gust.test_vals = [-1.977498, 3.481817, -0.010188, -0.004302] sine_gust.unsteady = True test_list.append(sine_gust) @@ -471,7 +471,7 @@ def main(): cosine_gust.cfg_dir = "gust" cosine_gust.cfg_file = "cosine_gust_zdir.cfg" cosine_gust.test_iter = 79 - cosine_gust.test_vals = [-2.418805, 0.002211, -0.001258, 0.000438, -0.000592] + cosine_gust.test_vals = [-2.418805, 0.002212, -0.001249, 0.000439, -0.000592] cosine_gust.unsteady = True cosine_gust.enabled_with_tsan = False test_list.append(cosine_gust) @@ -481,7 +481,7 @@ def main(): gust_mesh_defo.cfg_dir = "gust" gust_mesh_defo.cfg_file = "gust_with_mesh_deformation.cfg" gust_mesh_defo.test_iter = 6 - gust_mesh_defo.test_vals = [-1.844761, 0.001173, -0.000287] + gust_mesh_defo.test_vals = [-1.844761, 0.001100, -0.000310] gust_mesh_defo.unsteady = True gust_mesh_defo.enabled_with_tsan = False test_list.append(gust_mesh_defo) @@ -491,7 +491,7 @@ def main(): aeroelastic.cfg_dir = "aeroelastic" aeroelastic.cfg_file = "aeroelastic_NACA64A010.cfg" aeroelastic.test_iter = 2 - aeroelastic.test_vals = [-1.876626, 4.021083, 0.081436, 0.027726, -0.001638, -0.000130, -1.056269] + aeroelastic.test_vals = [-1.876631, 4.021073, 0.081270, 0.027580, -0.001642, -0.000127, -1.052233] aeroelastic.unsteady = True aeroelastic.enabled_on_cpu_arch = ["x86_64"] # Requires AVX-capable architecture test_list.append(aeroelastic) @@ -501,7 +501,7 @@ def main(): ddes_flatplate.cfg_dir = "ddes/flatplate" ddes_flatplate.cfg_file = "ddes_flatplate.cfg" ddes_flatplate.test_iter = 10 - ddes_flatplate.test_vals = [-2.714713, -5.763298, -0.214960, 0.023758, 0.000000] + ddes_flatplate.test_vals = [-2.714713, -5.763299, -0.214960, 0.023758, 0.000000] ddes_flatplate.unsteady = True test_list.append(ddes_flatplate) @@ -510,7 +510,7 @@ def main(): unst_inc_turb_naca0015_sa.cfg_dir = "unsteady/pitching_naca0015_rans_inc" unst_inc_turb_naca0015_sa.cfg_file = "config_incomp_turb_sa.cfg" unst_inc_turb_naca0015_sa.test_iter = 1 - unst_inc_turb_naca0015_sa.test_vals = [-3.008630, -6.889005, 1.435192, 0.433540] + unst_inc_turb_naca0015_sa.test_vals = [-3.008640, -6.889022, 1.435181, 0.433567] unst_inc_turb_naca0015_sa.unsteady = True test_list.append(unst_inc_turb_naca0015_sa) @@ -519,7 +519,7 @@ def main(): unst_deforming_naca0012.cfg_dir = "disc_adj_euler/naca0012_pitching_def" unst_deforming_naca0012.cfg_file = "inv_NACA0012_pitching_deform.cfg" unst_deforming_naca0012.test_iter = 5 - unst_deforming_naca0012.test_vals = [-3.665284, -3.794188, -3.716988, -3.148573] + unst_deforming_naca0012.test_vals = [-3.665299, -3.794240, -3.717018, -3.148592] unst_deforming_naca0012.unsteady = True unst_deforming_naca0012.enabled_with_tsan = False test_list.append(unst_deforming_naca0012) @@ -533,7 +533,7 @@ def main(): edge_VW.cfg_dir = "nicf/edge" edge_VW.cfg_file = "edge_VW.cfg" edge_VW.test_iter = 30 - edge_VW.test_vals = [-7.124331, -0.922828, -0.000009, 0.000000] + edge_VW.test_vals = [-7.124287, -0.922783, -0.000009, 0.000000] test_list.append(edge_VW) # Rarefaction shock wave edge_PPR @@ -541,7 +541,7 @@ def main(): edge_PPR.cfg_dir = "nicf/edge" edge_PPR.cfg_file = "edge_PPR.cfg" edge_PPR.test_iter = 20 - edge_PPR.test_vals = [-12.029862, -5.864551, -0.000034, 0.000000] + edge_PPR.test_vals = [-12.028835, -5.863587, -0.000034, 0.000000] edge_PPR.test_vals_aarch64 = [ -7.139211, -0.980821, -0.000034, 0.000000] test_list.append(edge_PPR) @@ -554,7 +554,7 @@ def main(): Jones_tc_restart.cfg_dir = "turbomachinery/APU_turbocharger" Jones_tc_restart.cfg_file = "Jones_restart.cfg" Jones_tc_restart.test_iter = 5 - Jones_tc_restart.test_vals = [-7.645864, -5.849737, -15.337009, -9.825759, -13.216109, -7.752293, 73286.000000, 73286.000000, 0.020055, 82.286000] + Jones_tc_restart.test_vals = [-7.645867, -5.849738, -15.337009, -9.825758, -13.216110, -7.752296, 73286.000000, 73286.000000, 0.020055, 82.286000] test_list.append(Jones_tc_restart) # 2D axial stage @@ -562,7 +562,7 @@ def main(): axial_stage2D.cfg_dir = "turbomachinery/axial_stage_2D" axial_stage2D.cfg_file = "Axial_stage2D.cfg" axial_stage2D.test_iter = 20 - axial_stage2D.test_vals = [1.167181, 1.598494, -2.928576, 2.573645, -2.527390, 3.016171, 106370.000000, 106370.000000, 5.726800, 64.383000] + axial_stage2D.test_vals = [1.167182, 1.598494, -2.928576, 2.573645, -2.527390, 3.016171, 106370.000000, 106370.000000, 5.726800, 64.383000] test_list.append(axial_stage2D) # 2D transonic stator restart @@ -570,7 +570,7 @@ def main(): transonic_stator_restart.cfg_dir = "turbomachinery/transonic_stator_2D" transonic_stator_restart.cfg_file = "transonic_stator_restart.cfg" transonic_stator_restart.test_iter = 20 - transonic_stator_restart.test_vals = [-4.367185, -2.487642, -2.079071, 1.728133, -1.464968, 3.224887, -471620.000000, 94.839000, -0.051074] + transonic_stator_restart.test_vals = [-4.367141, -2.487739, -2.079071, 1.728176, -1.464952, 3.225028, -471620.000000, 94.839000, -0.051126] transonic_stator_restart.test_vals_aarch64 = [-4.442510, -2.561369, -2.165778, 1.652750, -1.355494, 3.172712, -471620.000000, 94.843000, -0.043825] test_list.append(transonic_stator_restart) @@ -579,7 +579,7 @@ def main(): 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.632229, -8.894737, -9.348730] + multi_interface.test_vals = [-8.632242, -8.894741, -9.348730] multi_interface.test_vals_aarch64 = [-8.632229, -8.894737, -9.348730] test_list.append(multi_interface) @@ -592,7 +592,7 @@ def main(): uniform_flow.cfg_dir = "sliding_interface/uniform_flow" uniform_flow.cfg_file = "uniform_NN.cfg" uniform_flow.test_iter = 5 - uniform_flow.test_vals = [5.000000, 0.000000, -0.195002, -10.624440] + uniform_flow.test_vals = [5.000000, 0.000000, -0.195002, -10.624456] uniform_flow.unsteady = True uniform_flow.multizone = True test_list.append(uniform_flow) @@ -602,7 +602,7 @@ def main(): channel_2D.cfg_dir = "sliding_interface/channel_2D" channel_2D.cfg_file = "channel_2D_WA.cfg" channel_2D.test_iter = 2 - channel_2D.test_vals = [2.000000, 0.000000, 0.466188, 0.350089, 0.398977] + channel_2D.test_vals = [2.000000, 0.000000, 0.466194, 0.350086, 0.398979] channel_2D.unsteady = True channel_2D.multizone = True test_list.append(channel_2D) @@ -612,7 +612,7 @@ def main(): channel_3D.cfg_dir = "sliding_interface/channel_3D" channel_3D.cfg_file = "channel_3D_WA.cfg" channel_3D.test_iter = 2 - channel_3D.test_vals = [2.000000, 0.000000, 0.632254, 0.534189, 0.431979] + channel_3D.test_vals = [2.000000, 0.000000, 0.632251, 0.534200, 0.431953] channel_3D.test_vals_aarch64 = [2.000000, 0.000000, 0.629112, 0.524948, 0.422396] channel_3D.unsteady = True channel_3D.multizone = True @@ -624,7 +624,7 @@ def main(): pipe.cfg_dir = "sliding_interface/pipe" pipe.cfg_file = "pipe_NN.cfg" pipe.test_iter = 2 - pipe.test_vals = [0.092415, 0.568970, 0.692864, 0.989451, 1.048246] + pipe.test_vals = [0.092415, 0.568971, 0.692864, 0.989451, 1.048245] pipe.unsteady = True pipe.multizone = True test_list.append(pipe) @@ -644,7 +644,7 @@ def main(): supersonic_vortex_shedding.cfg_dir = "sliding_interface/supersonic_vortex_shedding" supersonic_vortex_shedding.cfg_file = "sup_vor_shed_WA.cfg" supersonic_vortex_shedding.test_iter = 5 - supersonic_vortex_shedding.test_vals = [5.000000, 0.000000, 0.899640, 1.076218] + supersonic_vortex_shedding.test_vals = [5.000000, 0.000000, 0.899645, 1.076181] supersonic_vortex_shedding.unsteady = True supersonic_vortex_shedding.multizone = True test_list.append(supersonic_vortex_shedding) @@ -654,7 +654,7 @@ def main(): bars_SST_2D.cfg_dir = "sliding_interface/bars_SST_2D" bars_SST_2D.cfg_file = "bars.cfg" bars_SST_2D.test_iter = 13 - bars_SST_2D.test_vals = [13.000000, -0.396746, -1.461254] + bars_SST_2D.test_vals = [13.000000, -0.391571, -1.460876] bars_SST_2D.multizone = True test_list.append(bars_SST_2D) @@ -663,7 +663,7 @@ def main(): slinc_steady.cfg_dir = "sliding_interface/incompressible_steady" slinc_steady.cfg_file = "config.cfg" slinc_steady.test_iter = 19 - slinc_steady.test_vals = [19.000000, -1.154874, -1.378127] + slinc_steady.test_vals = [19.000000, -1.144102, -1.424987] slinc_steady.test_vals_aarch64 = [19.000000, -1.154874, -1.378120] slinc_steady.multizone = True test_list.append(slinc_steady) @@ -695,7 +695,7 @@ def main(): fsi2d.cfg_dir = "fea_fsi/WallChannel_2d" fsi2d.cfg_file = "configFSI.cfg" fsi2d.test_iter = 4 - fsi2d.test_vals = [4.000000, 0.000000, -3.726029, -4.277530] + fsi2d.test_vals = [4.000000, 0.000000, -3.726029, -4.277532] fsi2d.multizone= True fsi2d.unsteady = True fsi2d.enabled_with_tsan = False @@ -706,7 +706,7 @@ def main(): dyn_fsi.cfg_dir = "fea_fsi/dyn_fsi" dyn_fsi.cfg_file = "config.cfg" dyn_fsi.test_iter = 4 - dyn_fsi.test_vals = [-4.330725, -4.152808, 0, 102] + dyn_fsi.test_vals = [-4.330727, -4.152808, 0.000000, 103.000000] dyn_fsi.multizone = True dyn_fsi.unsteady = True test_list.append(dyn_fsi) @@ -716,7 +716,7 @@ def main(): fsi_cht_restart.cfg_dir = "fea_fsi/stat_fsi" fsi_cht_restart.cfg_file = "config_restart.cfg" fsi_cht_restart.test_iter = 0 - fsi_cht_restart.test_vals = [5.000000, 0.006352, -1.960362, -9.327033, -9.580521, -9.317956, 608.380000, -0.012974, 0.000000, 20.000000] + fsi_cht_restart.test_vals = [5.000000, 0.006352, -1.960362, -9.327033, -9.627867, -9.318971, 608.380000, -0.012974, 0.000000, 20.000000] fsi_cht_restart.multizone = True test_list.append(fsi_cht_restart) @@ -729,7 +729,7 @@ def main(): mms_fvm_ns.cfg_dir = "mms/fvm_navierstokes" mms_fvm_ns.cfg_file = "lam_mms_roe.cfg" mms_fvm_ns.test_iter = 20 - mms_fvm_ns.test_vals = [-2.808514, 2.152655, 0.000000, 0.000000] + mms_fvm_ns.test_vals = [-2.808514, 2.152654, 0.000000, 0.000000] test_list.append(mms_fvm_ns) # FVM, incompressible, euler @@ -737,7 +737,7 @@ def main(): mms_fvm_inc_euler.cfg_dir = "mms/fvm_incomp_euler" mms_fvm_inc_euler.cfg_file = "inv_mms_jst.cfg" mms_fvm_inc_euler.test_iter = 20 - mms_fvm_inc_euler.test_vals = [-9.128035, -9.441406, 0.000000, 0.000000] + mms_fvm_inc_euler.test_vals = [-9.128034, -9.441406, 0.000000, 0.000000] mms_fvm_inc_euler.test_vals_aarch64 = [-9.128034, -9.441406, 0.000000, 0.000000] test_list.append(mms_fvm_inc_euler) @@ -746,7 +746,7 @@ def main(): mms_fvm_inc_ns.cfg_dir = "mms/fvm_incomp_navierstokes" mms_fvm_inc_ns.cfg_file = "lam_mms_fds.cfg" mms_fvm_inc_ns.test_iter = 20 - mms_fvm_inc_ns.test_vals = [-7.414945, -7.631547, 0.000000, 0.000000] + mms_fvm_inc_ns.test_vals = [-7.414945, -7.631546, 0.000000, 0.000000] test_list.append(mms_fvm_inc_ns) ########################## diff --git a/TestCases/hybrid_regression_AD.py b/TestCases/hybrid_regression_AD.py index b3c9e3ad7fdc..a6c8c03b01ba 100644 --- a/TestCases/hybrid_regression_AD.py +++ b/TestCases/hybrid_regression_AD.py @@ -58,7 +58,7 @@ def main(): discadj_cylinder3D.cfg_dir = "disc_adj_euler/cylinder3D" discadj_cylinder3D.cfg_file = "inv_cylinder3D.cfg" discadj_cylinder3D.test_iter = 5 - discadj_cylinder3D.test_vals = [-3.689811, -3.883747, -0.000000, 0.000000] + discadj_cylinder3D.test_vals = [-3.689705, -3.883604, -0.000000, 0.000000] test_list.append(discadj_cylinder3D) # Arina nozzle 2D @@ -78,7 +78,7 @@ def main(): discadj_rans_naca0012_sa.cfg_dir = "disc_adj_rans/naca0012" discadj_rans_naca0012_sa.cfg_file = "turb_NACA0012_sa.cfg" discadj_rans_naca0012_sa.test_iter = 10 - discadj_rans_naca0012_sa.test_vals = [-2.987150, 0.533080, 0.000004, -0.000000, 5.000000, -2.939634, 5.000000, -7.913741] + discadj_rans_naca0012_sa.test_vals = [-2.987158, 0.533077, 0.000004, -0.000000, 5.000000, -2.939652, 5.000000, -5.502411] test_list.append(discadj_rans_naca0012_sa) # Adjoint turbulent NACA0012 SST @@ -86,7 +86,7 @@ def main(): discadj_rans_naca0012_sst.cfg_dir = "disc_adj_rans/naca0012" discadj_rans_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" discadj_rans_naca0012_sst.test_iter = 10 - discadj_rans_naca0012_sst.test_vals = [-2.201517, -0.175212, 3.044200, -0.041842] + discadj_rans_naca0012_sst.test_vals = [-2.201555, -0.175211, 3.045200, -0.041846] discadj_rans_naca0012_sst.test_vals_aarch64 = [-2.201855, -0.172443, 3.043400, -0.041820] test_list.append(discadj_rans_naca0012_sst) @@ -99,7 +99,7 @@ def main(): discadj_incomp_NACA0012.cfg_dir = "disc_adj_incomp_euler/naca0012" discadj_incomp_NACA0012.cfg_file = "incomp_NACA0012_disc.cfg" discadj_incomp_NACA0012.test_iter = 20 - discadj_incomp_NACA0012.test_vals = [20.000000, -3.122291, -2.287328, 0.000000] + discadj_incomp_NACA0012.test_vals = [20.000000, -3.122052, -2.290797, 0.000000] test_list.append(discadj_incomp_NACA0012) ##################################### @@ -111,7 +111,7 @@ def main(): discadj_incomp_cylinder.cfg_dir = "disc_adj_incomp_navierstokes/cylinder" discadj_incomp_cylinder.cfg_file = "heated_cylinder.cfg" discadj_incomp_cylinder.test_iter = 20 - discadj_incomp_cylinder.test_vals = [20.000000, -1.671928, -6.254786, 0.000000] + discadj_incomp_cylinder.test_vals = [20.000000, -1.665653, -6.239112, 0.000000] discadj_incomp_cylinder.test_vals_aarch64 = [20.000000, -1.671920, -6.254841, 0.000000] discadj_incomp_cylinder.tol_aarch64 = 2e-1 test_list.append(discadj_incomp_cylinder) @@ -125,7 +125,7 @@ def main(): discadj_incomp_turb_NACA0012_sa.cfg_dir = "disc_adj_incomp_rans/naca0012" discadj_incomp_turb_NACA0012_sa.cfg_file = "turb_naca0012_sa.cfg" discadj_incomp_turb_NACA0012_sa.test_iter = 10 - discadj_incomp_turb_NACA0012_sa.test_vals = [10.000000, -3.845989, -1.023527, 0.000000] + discadj_incomp_turb_NACA0012_sa.test_vals = [10.000000, -3.845989, -1.023526, 0.000000] test_list.append(discadj_incomp_turb_NACA0012_sa) # Adjoint Incompressible Turbulent NACA 0012 SST @@ -133,7 +133,7 @@ def main(): discadj_incomp_turb_NACA0012_sst.cfg_dir = "disc_adj_incomp_rans/naca0012" discadj_incomp_turb_NACA0012_sst.cfg_file = "turb_naca0012_sst.cfg" discadj_incomp_turb_NACA0012_sst.test_iter = 10 - discadj_incomp_turb_NACA0012_sst.test_vals = [-3.775388, -3.089117, -7.143490, 0.000000, -0.896797] + discadj_incomp_turb_NACA0012_sst.test_vals = [-3.775278, -3.089107, -7.143663, 0.000000, -0.896760] test_list.append(discadj_incomp_turb_NACA0012_sst) ####################################################### @@ -145,7 +145,7 @@ def main(): discadj_cylinder.cfg_dir = "disc_adj_rans/cylinder" discadj_cylinder.cfg_file = "cylinder.cfg" discadj_cylinder.test_iter = 9 - discadj_cylinder.test_vals = [1.639344, -2.834279, -0.009538, 0.000020] + discadj_cylinder.test_vals = [1.639345, -2.834285, -0.009538, 0.000020] discadj_cylinder.unsteady = True discadj_cylinder.enabled_with_tsan = False test_list.append(discadj_cylinder) @@ -159,7 +159,7 @@ def main(): discadj_cylinder.cfg_dir = "disc_adj_rans/cylinder" discadj_cylinder.cfg_file = "cylinder_Windowing_AD.cfg" discadj_cylinder.test_iter = 9 - discadj_cylinder.test_vals = [2.183375] + discadj_cylinder.test_vals = [2.183380] discadj_cylinder.unsteady = True discadj_cylinder.enabled_with_tsan = False test_list.append(discadj_cylinder) @@ -173,7 +173,7 @@ def main(): discadj_DT_1ST_cylinder.cfg_dir = "disc_adj_rans/cylinder_DT_1ST" discadj_DT_1ST_cylinder.cfg_file = "cylinder.cfg" discadj_DT_1ST_cylinder.test_iter = 9 - discadj_DT_1ST_cylinder.test_vals = [1.196347, -3.339014, -0.006212, 0.000020] + discadj_DT_1ST_cylinder.test_vals = [1.196350, -3.339010, -0.006213, 0.000020] discadj_DT_1ST_cylinder.unsteady = True discadj_DT_1ST_cylinder.enabled_with_tsan = False test_list.append(discadj_DT_1ST_cylinder) @@ -187,7 +187,7 @@ def main(): discadj_pitchingNACA0012.cfg_dir = "disc_adj_euler/naca0012_pitching" discadj_pitchingNACA0012.cfg_file = "inv_NACA0012_pitching.cfg" discadj_pitchingNACA0012.test_iter = 4 - discadj_pitchingNACA0012.test_vals = [-1.041883, -1.512874, -0.006211, 0.000012] + discadj_pitchingNACA0012.test_vals = [-1.041975, -1.513011, -0.006202, 0.000012] discadj_pitchingNACA0012.tol = 0.01 discadj_pitchingNACA0012.unsteady = True discadj_pitchingNACA0012.enabled_with_tsan = False @@ -243,7 +243,7 @@ def main(): pywrapper_CFD_AD_MeshDisp.cfg_dir = "py_wrapper/disc_adj_flow/mesh_disp_sens" pywrapper_CFD_AD_MeshDisp.cfg_file = "configAD_flow.cfg" pywrapper_CFD_AD_MeshDisp.test_iter = 1000 - pywrapper_CFD_AD_MeshDisp.test_vals = [30.000000, -2.496241, 1.441657, 0.000000] + pywrapper_CFD_AD_MeshDisp.test_vals = [30.000000, -2.496079, 1.441818, 0.000000] pywrapper_CFD_AD_MeshDisp.test_vals_aarch64 = [30.000000, -2.499079, 1.440068, 0.000000] pywrapper_CFD_AD_MeshDisp.command = TestCase.Command(exec = "python", param = "run_adjoint.py --parallel -f") pywrapper_CFD_AD_MeshDisp.timeout = 1600 diff --git a/TestCases/multiple_ffd/naca0012/of_grad_cd.dat.ref b/TestCases/multiple_ffd/naca0012/of_grad_cd.dat.ref index 7308d3ce095e..9f66318faa55 100644 --- a/TestCases/multiple_ffd/naca0012/of_grad_cd.dat.ref +++ b/TestCases/multiple_ffd/naca0012/of_grad_cd.dat.ref @@ -1,3 +1,3 @@ VARIABLES="VARIABLE" , "GRADIENT" , "FINDIFF_STEP" - 0 , 0.0527036 , 0.001 - 1 , -0.0997374 , 0.001 + 0 , 0.0518141 , 0.001 + 1 , -0.100128 , 0.001 diff --git a/TestCases/multiple_ffd/naca0012/of_grad_directdiff.dat.ref b/TestCases/multiple_ffd/naca0012/of_grad_directdiff.dat.ref index 1bf779ee0a17..13b9e7b640d2 100644 --- a/TestCases/multiple_ffd/naca0012/of_grad_directdiff.dat.ref +++ b/TestCases/multiple_ffd/naca0012/of_grad_directdiff.dat.ref @@ -1,3 +1,3 @@ VARIABLES="VARIABLE" , "DRAG" , "EFFICIENCY" , "FORCE_X" , "FORCE_Y" , "FORCE_Z" , "LIFT" , "MOMENT_X" , "MOMENT_Y" , "MOMENT_Z" , "SIDEFORCE" - 0 , 0.05618256097 , -0.4264366682 , 0.05612587304 , 0.00321085185 , 0.0 , 0.001985708286 , 0.0 , 0.0 , 0.02965021664 , 0.0 - 1 , -0.1059117364 , 0.8978068975 , -0.1060212267 , 0.003862504658 , 0.0 , 0.006174426359 , 0.0 , 0.0 , 0.0675190381 , 0.0 + 0 , 0.05574220713 , -0.3793750988 , 0.05560191869 , 0.007037405532 , 0.0 , 0.005822781356 , 0.0 , 0.0 , 0.02947622997 , 0.0 + 1 , -0.106175883 , 0.8749922756 , -0.1062584399 , 0.002625280933 , 0.0 , 0.004942671837 , 0.0 , 0.0 , 0.06524923045 , 0.0 diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index faeca39742f6..a98256722293 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -74,7 +74,7 @@ def main(): cfd_flamelet_h2.cfg_dir = "flamelet/07_laminar_premixed_h2_flame_cfd" cfd_flamelet_h2.cfg_file = "laminar_premixed_h2_flame_cfd.cfg" cfd_flamelet_h2.test_iter = 5 - cfd_flamelet_h2.test_vals = [-8.036794, -8.372668, -1.842800, -9.388446] + cfd_flamelet_h2.test_vals = [-8.036794, -8.372668, -1.842800, -9.388447] test_list.append(cfd_flamelet_h2) # Flame ignition methods @@ -95,7 +95,7 @@ def main(): thermalbath.cfg_dir = "nonequilibrium/thermalbath/finitechemistry" thermalbath.cfg_file = "thermalbath.cfg" thermalbath.test_iter = 10 - thermalbath.test_vals = [0.945997, 0.945997, -11.860991, -11.857906, -32.000000, 10.013239] + thermalbath.test_vals = [0.945997, 0.945997, -11.860991, -11.890652, -32.000000, 10.013239] test_list.append(thermalbath) # Adiabatic thermal bath @@ -112,7 +112,7 @@ def main(): thermalbath_frozen.cfg_dir = "nonequilibrium/thermalbath/frozen" thermalbath_frozen.cfg_file = "thermalbath_frozen.cfg" thermalbath_frozen.test_iter = 10 - thermalbath_frozen.test_vals = [-32.000000, -32.000000, -11.818647, -11.857909, -32.000000, 10.013545] + thermalbath_frozen.test_vals = [-32.000000, -32.000000, -11.818647, -11.848553, -32.000000, 10.013545] test_list.append(thermalbath_frozen) # Inviscid single wedge, ausm, implicit @@ -120,7 +120,7 @@ def main(): invwedge_a.cfg_dir = "nonequilibrium/invwedge" invwedge_a.cfg_file = "invwedge_ausm.cfg" invwedge_a.test_iter = 10 - invwedge_a.test_vals = [-1.069669, -1.594432, -18.299010, -18.626399, -18.572410, 2.245727, 1.874088, 5.290291, 0.847735] + invwedge_a.test_vals = [-1.069665, -1.594428, -18.299923, -18.627315, -18.573325, 2.245732, 1.874096, 5.290295, 0.847739] invwedge_a.test_vals_aarch64 = [-1.069675, -1.594438, -18.299736, -18.627126, -18.573137, 2.245721, 1.874105, 5.290285, 0.847729] test_list.append(invwedge_a) @@ -138,7 +138,7 @@ def main(): invwedge_msw.cfg_dir = "nonequilibrium/invwedge" invwedge_msw.cfg_file = "invwedge_msw.cfg" invwedge_msw.test_iter = 10 - invwedge_msw.test_vals = [-1.212335, -1.737098, -18.299375, -18.626782, -18.572771, 2.106171, 1.651949, 5.143958, 0.704444] + invwedge_msw.test_vals = [-1.212335, -1.737098, -18.301768, -18.629149, -18.575169, 2.106171, 1.651949, 5.143958, 0.704444] invwedge_msw.test_vals_aarch64 = [-1.212335, -1.737098, -18.299279, -18.626656, -18.572683, 2.106171, 1.651949, 5.143958, 0.704444] test_list.append(invwedge_msw) @@ -147,7 +147,7 @@ def main(): invwedge_roe.cfg_dir = "nonequilibrium/invwedge" invwedge_roe.cfg_file = "invwedge_roe.cfg" invwedge_roe.test_iter = 10 - invwedge_roe.test_vals = [-1.054108, -1.578871, -17.782421, -18.111535, -18.055538, 2.264661, 1.841727, 5.302630, 0.897714] + invwedge_roe.test_vals = [-1.023216, -1.547979, -17.814656, -18.143616, -18.087775, 2.295078, 1.885054, 5.338487, 0.926120] invwedge_roe.test_vals_aarch64 = [-1.052398, -1.577160, -17.794015, -18.122997, -18.067131, 2.266042, 1.849686, 5.304700, 0.899584] test_list.append(invwedge_roe) @@ -174,7 +174,7 @@ def main(): invwedge_ss_inlet.cfg_dir = "nonequilibrium/invwedge" invwedge_ss_inlet.cfg_file = "invwedge_ss_inlet.cfg" invwedge_ss_inlet.test_iter = 10 - invwedge_ss_inlet.test_vals = [-1.068592, -1.593355, -18.250183, -18.579524, -18.523255, 2.246972, 1.874197, 5.291273, 0.848771] + invwedge_ss_inlet.test_vals = [-1.068634, -1.593397, -18.246265, -18.575529, -18.519338, 2.246925, 1.874200, 5.291234, 0.848731] invwedge_ss_inlet.test_vals_aarch64 = [-1.068592, -1.593355, -18.250183, -18.579524, -18.523255, 2.246972, 1.874197, 5.291273, 0.848771] test_list.append(invwedge_ss_inlet) @@ -183,7 +183,7 @@ def main(): visc_cone.cfg_dir = "nonequilibrium/visc_wedge" visc_cone.cfg_file = "axi_visccone.cfg" visc_cone.test_iter = 10 - visc_cone.test_vals = [-5.222269, -5.746523, -20.560216, -20.510119, -20.409089, 1.255762, -3.208384, -0.016011, 0.093461, 32619] + visc_cone.test_vals = [-5.215235, -5.739371, -20.559852, -20.509281, -20.408911, 1.262701, -3.205457, -0.015696, 0.093205, 32637.000000] visc_cone.test_vals_aarch64 = [-5.222270, -5.746525, -20.560286, -20.510152, -20.409101, 1.255758, -3.208382, -0.016014, 0.093462, 32619.000000] test_list.append(visc_cone) @@ -216,7 +216,7 @@ def main(): ion_gy.cfg_dir = "nonequilibrium/visc_cylinder" ion_gy.cfg_file = "cyl_ion_gy.cfg" ion_gy.test_iter = 10 - ion_gy.test_vals = [-11.629873, -4.165563, -4.702662, -4.950351, -5.146155, -4.993878, -6.893332, 5.990109, 5.990004, -0.014849, 0.000000, 90090.000000] + ion_gy.test_vals = [-11.629873, -4.165562, -4.702662, -4.950351, -5.146155, -4.993878, -6.893332, 5.990109, 5.990004, -0.014849, 0.000000, 90090.000000] test_list.append(ion_gy) ########################## @@ -228,7 +228,7 @@ def main(): channel.cfg_dir = "euler/channel" channel.cfg_file = "inv_channel_RK.cfg" channel.test_iter = 20 - channel.test_vals = [-1.988782, 3.546674, 0.032065, 0.194399] + channel.test_vals = [-1.990518, 3.545643, 0.031745, 0.194289] test_list.append(channel) # NACA0012 @@ -236,7 +236,7 @@ def main(): naca0012.cfg_dir = "euler/naca0012" naca0012.cfg_file = "inv_NACA0012_Roe.cfg" naca0012.test_iter = 20 - naca0012.test_vals = [-4.441831, -3.912398, 0.295812, 0.024400] + naca0012.test_vals = [-4.452603, -3.920573, 0.296003, 0.024298] test_list.append(naca0012) # Supersonic wedge @@ -244,7 +244,7 @@ def main(): wedge.cfg_dir = "euler/wedge" wedge.cfg_file = "inv_wedge_HLLC.cfg" wedge.test_iter = 20 - wedge.test_vals = [-3.681700, 2.042776, -0.249531, 0.043953] + wedge.test_vals = [-3.675516, 2.048742, -0.249531, 0.043953] test_list.append(wedge) # ONERA M6 Wing @@ -252,7 +252,7 @@ def main(): oneram6.cfg_dir = "euler/oneram6" oneram6.cfg_file = "inv_ONERAM6.cfg" oneram6.test_iter = 10 - oneram6.test_vals = [-11.530230, -11.006685, 0.280800, 0.008623] + oneram6.test_vals = [-11.534670, -11.009582, 0.280800, 0.008623] oneram6.timeout = 3200 test_list.append(oneram6) @@ -261,7 +261,7 @@ def main(): fixedCL_naca0012.cfg_dir = "fixed_cl/naca0012" fixedCL_naca0012.cfg_file = "inv_NACA0012.cfg" fixedCL_naca0012.test_iter = 10 - fixedCL_naca0012.test_vals = [-4.006904, 1.523677, 0.300969, 0.019473] + fixedCL_naca0012.test_vals = [-3.962426, 1.570772, 0.300985, 0.019475] test_list.append(fixedCL_naca0012) # Polar sweep of the inviscid NACA0012 @@ -270,7 +270,7 @@ def main(): polar_naca0012.cfg_file = "inv_NACA0012.cfg" polar_naca0012.polar = True polar_naca0012.test_iter = 10 - polar_naca0012.test_vals = [-1.284049, 4.163288, 0.003791, 0.082785] + polar_naca0012.test_vals = [-1.279651, 4.168190, 0.002681, 0.083781] polar_naca0012.test_vals_aarch64 = [-1.083394, 4.386134, 0.001588, 0.033513] polar_naca0012.command = TestCase.Command(exec = "compute_polar.py", param = "-i 11") # flaky test on arm64 @@ -298,7 +298,7 @@ def main(): ramp.cfg_dir = "euler/ramp" ramp.cfg_file = "inv_ramp.cfg" ramp.test_iter = 10 - ramp.test_vals = [-13.647281, -8.010114, -0.076277, 0.054839] + ramp.test_vals = [-13.648343, -8.010490, -0.076277, 0.054839] ramp.test_vals_aarch64 = [-13.648406, -8.014579, -0.076277, 0.054839] test_list.append(ramp) @@ -306,7 +306,7 @@ def main(): ramp_msw.cfg_dir = "euler/ramp" ramp_msw.cfg_file = "inv_ramp_msw.cfg" ramp_msw.test_iter = 100 - ramp_msw.test_vals = [-6.996547, -1.226863, -0.077507, 0.054419] + ramp_msw.test_vals = [-7.059306, -1.300966, -0.077507, 0.054419] ramp_msw.tol = [0.2, 0.2, 0.00001, 0.00001] test_list.append(ramp_msw) @@ -315,7 +315,7 @@ def main(): MFR_coupling.cfg_dir = "euler/turbofan_MFR_coupling" MFR_coupling.cfg_file = "MFR_coupling.cfg" MFR_coupling.test_iter = 100 - MFR_coupling.test_vals = [-2.1124e+02, 1.5003e+02, 2.0151e+01] + MFR_coupling.test_vals = [-211.240000, 150.030000, 20.151000] test_list.append(MFR_coupling) ########################## @@ -327,7 +327,7 @@ def main(): flatplate.cfg_dir = "navierstokes/flatplate" flatplate.cfg_file = "lam_flatplate.cfg" flatplate.test_iter = 100 - flatplate.test_vals = [-6.499563, -1.020731, 0.001223, 0.028373, 2.361500, -2.333200, 0.000000, 0.000000] + flatplate.test_vals = [-6.496808, -1.017942, 0.001224, 0.028377, 2.361500, -2.333200, 0.000000, 0.000000] test_list.append(flatplate) # Custom objective function @@ -352,7 +352,7 @@ def main(): cylinder.cfg_dir = "navierstokes/cylinder" cylinder.cfg_file = "lam_cylinder.cfg" cylinder.test_iter = 25 - cylinder.test_vals = [-8.411856, -2.937460, -0.002360, 1.643164, 0.000000] + cylinder.test_vals = [-8.427810, -2.952885, -0.007651, 1.646675, 0.000000] test_list.append(cylinder) # Laminar cylinder (low Mach correction) @@ -360,7 +360,7 @@ def main(): cylinder_lowmach.cfg_dir = "navierstokes/cylinder" cylinder_lowmach.cfg_file = "cylinder_lowmach.cfg" cylinder_lowmach.test_iter = 25 - cylinder_lowmach.test_vals = [-6.469910, -1.008163, -0.381561, 78.514909, 0.000000] + cylinder_lowmach.test_vals = [-6.469226, -1.007489, -0.631813, 78.583596, 0.000000] test_list.append(cylinder_lowmach) @@ -378,7 +378,7 @@ def main(): poiseuille_profile.cfg_dir = "navierstokes/poiseuille" poiseuille_profile.cfg_file = "profile_poiseuille.cfg" poiseuille_profile.test_iter = 10 - poiseuille_profile.test_vals = [-12.004290, -7.578539, -0.000000, 2.089953] + poiseuille_profile.test_vals = [-12.004284, -7.577596, -0.000000, 2.089953] poiseuille_profile.test_vals_aarch64 = [-12.007498, -7.226926, -0.000000, 2.089953] poiseuille_profile.tol = [0.001, 0.001, 1e-5, 1e-5, 1e-5] test_list.append(poiseuille_profile) @@ -392,7 +392,7 @@ def main(): rae2822_sa.cfg_dir = "rans/rae2822" rae2822_sa.cfg_file = "turb_SA_RAE2822.cfg" rae2822_sa.test_iter = 20 - rae2822_sa.test_vals = [-2.190718, -5.317366, 0.391927, 0.075561, 0.000000] + rae2822_sa.test_vals = [-2.187401, -5.312133, 0.393515, 0.075584, 0.000000] test_list.append(rae2822_sa) # RAE2822 SST @@ -400,7 +400,7 @@ def main(): rae2822_sst.cfg_dir = "rans/rae2822" rae2822_sst.cfg_file = "turb_SST_RAE2822.cfg" rae2822_sst.test_iter = 20 - rae2822_sst.test_vals = [-1.035575, 5.863601, 0.358898, 0.074725, 0.000000] + rae2822_sst.test_vals = [-1.037030, 5.861942, 0.362143, 0.073809, 0.000000] test_list.append(rae2822_sst) # RAE2822 SST_SUST @@ -408,7 +408,7 @@ def main(): rae2822_sst_sust.cfg_dir = "rans/rae2822" rae2822_sst_sust.cfg_file = "turb_SST_SUST_RAE2822.cfg" rae2822_sst_sust.test_iter = 20 - rae2822_sst_sust.test_vals = [-2.492141, 5.863586, 0.358898, 0.074725] + rae2822_sst_sust.test_vals = [-2.484221, 5.861926, 0.362143, 0.073809] test_list.append(rae2822_sst_sust) # Flat plate @@ -416,7 +416,7 @@ def main(): turb_flatplate.cfg_dir = "rans/flatplate" turb_flatplate.cfg_file = "turb_SA_flatplate.cfg" turb_flatplate.test_iter = 20 - turb_flatplate.test_vals = [-4.926218, -7.441083, -0.187477, 0.015330] + turb_flatplate.test_vals = [-4.949425, -7.443679, -0.187485, 0.015345] test_list.append(turb_flatplate) # Flat plate (compressible) with species inlet @@ -424,7 +424,7 @@ def main(): turb_flatplate_species.cfg_dir = "rans/flatplate" turb_flatplate_species.cfg_file = "turb_SA_flatplate_species.cfg" turb_flatplate_species.test_iter = 20 - turb_flatplate_species.test_vals = [-4.728172, -1.517257, -2.296200, 0.801649, -3.574284, 3.000000, -0.964853, 2.000000, -1.529003, 3.000000, -0.705161, 0.999933, 0.999933] + turb_flatplate_species.test_vals = [-4.795630, -1.532152, -2.318309, 0.717570, -3.574258, 3.000000, -0.931729, 2.000000, -1.526521, 3.000000, -0.705481, 0.999934, 0.999934] test_list.append(turb_flatplate_species) # Flat plate SST compressibility correction Wilcox @@ -492,7 +492,7 @@ def main(): turb_naca0012_sa.cfg_dir = "rans/naca0012" turb_naca0012_sa.cfg_file = "turb_NACA0012_sa.cfg" turb_naca0012_sa.test_iter = 5 - turb_naca0012_sa.test_vals = [-12.037515, -16.376951, 1.080346, 0.018385, 20.000000, -1.564135, 20.000000, -4.180955, 0.000000] + turb_naca0012_sa.test_vals = [-12.037535, -16.376949, 1.080346, 0.018385, 20.000000, -1.564088, 20.000000, -4.180928, 0.000000] turb_naca0012_sa.test_vals_aarch64 = [-12.037489, -16.376949, 1.080346, 0.018385, 20.000000, -1.564143, 20.000000, -4.180945, 0.000000] turb_naca0012_sa.timeout = 3200 test_list.append(turb_naca0012_sa) @@ -502,7 +502,7 @@ def main(): turb_naca0012_sst.cfg_dir = "rans/naca0012" turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" turb_naca0012_sst.test_iter = 10 - turb_naca0012_sst.test_vals = [-12.094739, -15.251094, -5.906365, 1.070413, 0.015775, -2.376137, 0.000000] + turb_naca0012_sst.test_vals = [-12.094759, -15.251093, -5.906365, 1.070413, 0.015775, -2.376032, 0.000000] turb_naca0012_sst.test_vals_aarch64 = [-12.075620, -15.246688, -5.861276, 1.070036, 0.015841, -1.991001, 0.000000] turb_naca0012_sst.timeout = 3200 test_list.append(turb_naca0012_sst) @@ -512,7 +512,7 @@ def main(): turb_naca0012_sst_sust.cfg_dir = "rans/naca0012" turb_naca0012_sst_sust.cfg_file = "turb_NACA0012_sst_sust.cfg" turb_naca0012_sst_sust.test_iter = 10 - turb_naca0012_sst_sust.test_vals = [-12.082081, -14.837177, -5.733435, 1.000893, 0.019109, -2.240976] + turb_naca0012_sst_sust.test_vals = [-12.082056, -14.837177, -5.733436, 1.000893, 0.019109, -2.240933] turb_naca0012_sst_sust.test_vals_aarch64 = [-12.073964, -14.836726, -5.732390, 1.000050, 0.019144, -2.229074] turb_naca0012_sst_sust.timeout = 3200 test_list.append(turb_naca0012_sst_sust) @@ -567,7 +567,7 @@ def main(): actuatordisk_bem.cfg_dir = "rans/actuatordisk_bem" actuatordisk_bem.cfg_file = "actuatordisk_bem.cfg" actuatordisk_bem.test_iter = 15 - actuatordisk_bem.test_vals = [-5.389236, -10.319123, 0.001362, -0.376528] + actuatordisk_bem.test_vals = [-5.389236, -10.319122, 0.001362, -0.376528] actuatordisk_bem.timeout = 3200 actuatordisk_bem.tol = 0.001 test_list.append(actuatordisk_bem) @@ -581,7 +581,7 @@ def main(): axi_rans_air_nozzle_restart.cfg_dir = "axisymmetric_rans/air_nozzle" axi_rans_air_nozzle_restart.cfg_file = "air_nozzle_restart.cfg" axi_rans_air_nozzle_restart.test_iter = 10 - axi_rans_air_nozzle_restart.test_vals = [-11.056238, -5.334119, -8.842316, -4.067921, 0.000000] + axi_rans_air_nozzle_restart.test_vals = [-11.056236, -5.334117, -8.842317, -4.067917, 0.000000] axi_rans_air_nozzle_restart.test_vals_aarch64 = [-14.143310, -9.163287, -10.858232, -5.787715, 0.000000] axi_rans_air_nozzle_restart.tol = 0.0001 test_list.append(axi_rans_air_nozzle_restart) @@ -596,7 +596,7 @@ def main(): turb_naca0012_sst_restart_mg.cfg_file = "turb_NACA0012_sst_multigrid_restart.cfg" turb_naca0012_sst_restart_mg.test_iter = 20 turb_naca0012_sst_restart_mg.ntest_vals = 5 - turb_naca0012_sst_restart_mg.test_vals = [-6.566570, -5.057149, 0.830238, -0.008695, 0.078156] + turb_naca0012_sst_restart_mg.test_vals = [-6.566317, -5.057149, 0.830238, -0.008702, 0.078154] turb_naca0012_sst_restart_mg.timeout = 3200 turb_naca0012_sst_restart_mg.tol = 0.000001 test_list.append(turb_naca0012_sst_restart_mg) @@ -610,7 +610,7 @@ def main(): inc_euler_naca0012.cfg_dir = "incomp_euler/naca0012" inc_euler_naca0012.cfg_file = "incomp_NACA0012.cfg" inc_euler_naca0012.test_iter = 20 - inc_euler_naca0012.test_vals = [-6.058441, -5.126560, 0.525681, 0.008778] + inc_euler_naca0012.test_vals = [-6.067964, -5.125607, 0.525745, 0.008772] test_list.append(inc_euler_naca0012) # C-D nozzle with pressure inlet and mass flow outlet @@ -618,7 +618,7 @@ def main(): inc_nozzle.cfg_dir = "incomp_euler/nozzle" inc_nozzle.cfg_file = "inv_nozzle.cfg" inc_nozzle.test_iter = 20 - inc_nozzle.test_vals = [-6.058643, -5.286249, 0.002391, 0.124107] + inc_nozzle.test_vals = [-6.053549, -5.296666, 0.001330, 0.124087] test_list.append(inc_nozzle) # Laminar wall mounted cylinder, Euler walls, cylinder wall diagonally split @@ -638,7 +638,7 @@ def main(): inc_lam_cylinder.cfg_dir = "incomp_navierstokes/cylinder" inc_lam_cylinder.cfg_file = "incomp_cylinder.cfg" inc_lam_cylinder.test_iter = 10 - inc_lam_cylinder.test_vals = [-4.156152, -3.554127, -0.017923, 5.101126] + inc_lam_cylinder.test_vals = [-4.156113, -3.553508, -0.024563, 5.105605] test_list.append(inc_lam_cylinder) # Laminar sphere, Re=1. Last column: Cd=24/Re @@ -646,7 +646,7 @@ def main(): inc_lam_sphere.cfg_dir = "incomp_navierstokes/sphere" inc_lam_sphere.cfg_file = "sphere.cfg" inc_lam_sphere.test_iter = 5 - inc_lam_sphere.test_vals = [-8.165744, -8.968003, 0.121003, 25.782691] + inc_lam_sphere.test_vals = [-8.190948, -8.992588, 0.121003, 25.782691] test_list.append(inc_lam_sphere) # Buoyancy-driven cavity @@ -662,7 +662,7 @@ def main(): inc_poly_cylinder.cfg_dir = "incomp_navierstokes/cylinder" inc_poly_cylinder.cfg_file = "poly_cylinder.cfg" inc_poly_cylinder.test_iter = 20 - inc_poly_cylinder.test_vals = [-2.362995, 0.006829, 1.923532, -172.590000] + inc_poly_cylinder.test_vals = [-2.363389, 0.005552, 1.922971, -172.380000] test_list.append(inc_poly_cylinder) # X-coarse laminar bend as a mixed element CGNS test @@ -670,7 +670,7 @@ def main(): inc_lam_bend.cfg_dir = "incomp_navierstokes/bend" inc_lam_bend.cfg_file = "lam_bend.cfg" inc_lam_bend.test_iter = 10 - inc_lam_bend.test_vals = [-3.588863, -3.101606, -0.022302, 1.062971] + inc_lam_bend.test_vals = [-3.585943, -3.096592, -0.022111, 1.064110] test_list.append(inc_lam_bend) # 3D laminar channnel with 1 cell in flow direction, streamwise periodic @@ -784,7 +784,7 @@ def main(): turbmod_sa_bsl_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_bsl_rae2822.cfg_file = "turb_SA_BSL_RAE2822.cfg" turbmod_sa_bsl_rae2822.test_iter = 20 - turbmod_sa_bsl_rae2822.test_vals = [-2.811354, 0.159658, -0.279654, -5.239595, 0.792937, 0.025461] + turbmod_sa_bsl_rae2822.test_vals = [-2.801007, 0.156786, -0.273213, -5.259038, 0.799641, 0.025816] test_list.append(turbmod_sa_bsl_rae2822) # SA Negative @@ -792,7 +792,7 @@ def main(): turbmod_sa_neg_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_neg_rae2822.cfg_file = "turb_SA_NEG_RAE2822.cfg" turbmod_sa_neg_rae2822.test_iter = 10 - turbmod_sa_neg_rae2822.test_vals = [1.345830, 1.122324, -1.210207, 1.175663, 0.388687, 0.000000] + turbmod_sa_neg_rae2822.test_vals = [1.527546, 1.303378, -1.699437, 1.523548, 0.601919, 0.000000] turbmod_sa_neg_rae2822.test_vals_aarch64 = [-1.345593, 1.448310, 1.208721, -0.846597, 1.248410, 0.489117, 0.000000] test_list.append(turbmod_sa_neg_rae2822) @@ -801,7 +801,7 @@ def main(): turbmod_sa_comp_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_comp_rae2822.cfg_file = "turb_SA_COMP_RAE2822.cfg" turbmod_sa_comp_rae2822.test_iter = 20 - turbmod_sa_comp_rae2822.test_vals = [-2.811319, 0.159702, -0.279630, -5.248778, 0.792974, 0.025463] + turbmod_sa_comp_rae2822.test_vals = [-2.802560, 0.155007, -0.274449, -5.266509, 0.799829, 0.025809] test_list.append(turbmod_sa_comp_rae2822) # SA Edwards @@ -809,7 +809,7 @@ def main(): turbmod_sa_edw_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_edw_rae2822.cfg_file = "turb_SA_EDW_RAE2822.cfg" turbmod_sa_edw_rae2822.test_iter = 20 - turbmod_sa_edw_rae2822.test_vals = [-2.811465, 0.159870, -0.279550, -5.932283, 0.793521, 0.025309] + turbmod_sa_edw_rae2822.test_vals = [-2.795889, 0.162853, -0.269375, -5.943329, 0.799775, 0.025699] test_list.append(turbmod_sa_edw_rae2822) # SA Compressibility and Edwards @@ -817,7 +817,7 @@ def main(): turbmod_sa_comp_edw_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_comp_edw_rae2822.cfg_file = "turb_SA_COMP_EDW_RAE2822.cfg" turbmod_sa_comp_edw_rae2822.test_iter = 20 - turbmod_sa_comp_edw_rae2822.test_vals = [-2.811414, 0.159941, -0.279512, -5.935417, 0.793532, 0.025314] + turbmod_sa_comp_edw_rae2822.test_vals = [-2.798258, 0.160264, -0.271408, -5.944887, 0.799936, 0.025689] test_list.append(turbmod_sa_comp_edw_rae2822) # SA QCR @@ -825,7 +825,7 @@ def main(): turbmod_sa_qcr_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_qcr_rae2822.cfg_file = "turb_SA_QCR_RAE2822.cfg" turbmod_sa_qcr_rae2822.test_iter = 20 - turbmod_sa_qcr_rae2822.test_vals = [-2.804469, 0.164849, -0.274960, -5.243908, 0.792741, 0.025535] + turbmod_sa_qcr_rae2822.test_vals = [-2.802573, 0.138895, -0.286064, -5.233541, 0.796617, 0.025734] test_list.append(turbmod_sa_qcr_rae2822) ############################ @@ -849,7 +849,7 @@ def main(): contadj_naca0012.cfg_dir = "cont_adj_euler/naca0012" contadj_naca0012.cfg_file = "inv_NACA0012.cfg" contadj_naca0012.test_iter = 5 - contadj_naca0012.test_vals = [-9.526111, -15.089167, -0.726250, 0.020280] + contadj_naca0012.test_vals = [-9.527707, -15.088310, -0.726250, 0.020280] contadj_naca0012.test_vals_aarch64 = [-9.662546, -14.998818, -0.726250, 0.020280] test_list.append(contadj_naca0012) @@ -858,7 +858,7 @@ def main(): contadj_oneram6.cfg_dir = "cont_adj_euler/oneram6" contadj_oneram6.cfg_file = "inv_ONERAM6.cfg" contadj_oneram6.test_iter = 10 - contadj_oneram6.test_vals = [-12.086022, -12.648069, -1.086100, 0.007556] + contadj_oneram6.test_vals = [-12.087421, -12.650762, -1.086100, 0.007556] test_list.append(contadj_oneram6) # Inviscid WEDGE: tests averaged outflow total pressure adjoint @@ -866,7 +866,7 @@ def main(): contadj_wedge.cfg_dir = "cont_adj_euler/wedge" contadj_wedge.cfg_file = "inv_wedge_ROE.cfg" contadj_wedge.test_iter = 10 - contadj_wedge.test_vals = [2.872065, -2.756214, 1010800.000000, -0.000000] + contadj_wedge.test_vals = [2.872065, -2.756214, 1010800.000000, 0.000000] test_list.append(contadj_wedge) # Inviscid fixed CL NACA0012 @@ -874,7 +874,7 @@ def main(): contadj_fixed_CL_naca0012.cfg_dir = "fixed_cl/naca0012" contadj_fixed_CL_naca0012.cfg_file = "inv_NACA0012_ContAdj.cfg" contadj_fixed_CL_naca0012.test_iter = 100 - contadj_fixed_CL_naca0012.test_vals = [1.382576, -4.042295, -0.008696, 0.003238] + contadj_fixed_CL_naca0012.test_vals = [1.377921, -4.048140, -0.008264, 0.003369] test_list.append(contadj_fixed_CL_naca0012) ################################### @@ -886,7 +886,7 @@ def main(): contadj_ns_cylinder.cfg_dir = "cont_adj_navierstokes/cylinder" contadj_ns_cylinder.cfg_file = "lam_cylinder.cfg" contadj_ns_cylinder.test_iter = 20 - contadj_ns_cylinder.test_vals = [-3.632833, -9.087692, 2.056700, -0.000000] + contadj_ns_cylinder.test_vals = [-3.634298, -9.089945, 2.056700, -0.000000] test_list.append(contadj_ns_cylinder) # Adjoint laminar naca0012 subsonic @@ -930,7 +930,7 @@ def main(): contadj_rans_rae2822.cfg_dir = "cont_adj_rans/rae2822" contadj_rans_rae2822.cfg_file = "turb_SA_RAE2822.cfg" contadj_rans_rae2822.test_iter = 20 - contadj_rans_rae2822.test_vals = [-5.399744, -10.904916, -0.212470, 0.005448] + contadj_rans_rae2822.test_vals = [-5.399567, -10.904741, -0.212470, 0.005448] test_list.append(contadj_rans_rae2822) ############################# @@ -942,7 +942,7 @@ def main(): turb_naca0012_1c.cfg_dir = "rans_uq/naca0012" turb_naca0012_1c.cfg_file = "turb_NACA0012_uq_1c.cfg" turb_naca0012_1c.test_iter = 10 - turb_naca0012_1c.test_vals = [-4.983993, 1.343551, 0.663876, 0.009417] + turb_naca0012_1c.test_vals = [-4.979554, 1.344291, 0.666805, 0.010247] turb_naca0012_1c.test_vals_aarch64 = [-4.981036, 1.345868, 0.673232, 0.010091] test_list.append(turb_naca0012_1c) @@ -951,7 +951,7 @@ def main(): turb_naca0012_2c.cfg_dir = "rans_uq/naca0012" turb_naca0012_2c.cfg_file = "turb_NACA0012_uq_2c.cfg" turb_naca0012_2c.test_iter = 10 - turb_naca0012_2c.test_vals = [-5.482691, 1.262639, 0.496036, -0.032657] + turb_naca0012_2c.test_vals = [-5.482691, 1.262918, 0.529450, -0.023537] turb_naca0012_2c.test_vals_aarch64 = [-5.484365, 1.264701, 0.501741, -0.033109] test_list.append(turb_naca0012_2c) @@ -968,7 +968,7 @@ def main(): turb_naca0012_p1c1.cfg_dir = "rans_uq/naca0012" turb_naca0012_p1c1.cfg_file = "turb_NACA0012_uq_p1c1.cfg" turb_naca0012_p1c1.test_iter = 10 - turb_naca0012_p1c1.test_vals = [-5.129493, 1.283950, 0.807031, 0.047479] + turb_naca0012_p1c1.test_vals = [-5.129555, 1.283892, 0.808074, 0.047784] turb_naca0012_p1c1.test_vals_aarch64 = [-5.122100, 1.284478, 0.608744, -0.008593] test_list.append(turb_naca0012_p1c1) @@ -977,7 +977,7 @@ def main(): turb_naca0012_p1c2.cfg_dir = "rans_uq/naca0012" turb_naca0012_p1c2.cfg_file = "turb_NACA0012_uq_p1c2.cfg" turb_naca0012_p1c2.test_iter = 10 - turb_naca0012_p1c2.test_vals = [-5.553947, 1.234508, 0.604072, -0.008556] + turb_naca0012_p1c2.test_vals = [-5.554045, 1.234446, 0.603731, -0.008717] test_list.append(turb_naca0012_p1c2) ###################################### @@ -1010,7 +1010,7 @@ def main(): rot_naca0012.cfg_dir = "rotating/naca0012" rot_naca0012.cfg_file = "rot_NACA0012.cfg" rot_naca0012.test_iter = 25 - rot_naca0012.test_vals = [-1.289931, 4.246409, -0.000484, 0.112457] + rot_naca0012.test_vals = [-1.284573, 4.252376, -0.001615, 0.112755] test_list.append(rot_naca0012) # Lid-driven cavity @@ -1018,7 +1018,7 @@ def main(): cavity.cfg_dir = "moving_wall/cavity" cavity.cfg_file = "lam_cavity.cfg" cavity.test_iter = 25 - cavity.test_vals = [-7.828480, -2.367075, 0.008928, 0.007370] + cavity.test_vals = [-7.838584, -2.378333, 0.009057, 0.006632] test_list.append(cavity) # Spinning cylinder @@ -1026,7 +1026,7 @@ def main(): spinning_cylinder.cfg_dir = "moving_wall/spinning_cylinder" spinning_cylinder.cfg_file = "spinning_cylinder.cfg" spinning_cylinder.test_iter = 25 - spinning_cylinder.test_vals = [-7.383987, -1.919413, 2.601945, 1.976038] + spinning_cylinder.test_vals = [-7.401660, -1.942949, 2.534412, 1.997391] test_list.append(spinning_cylinder) ###################################### @@ -1047,7 +1047,7 @@ def main(): sine_gust.cfg_dir = "gust" sine_gust.cfg_file = "inv_gust_NACA0012.cfg" sine_gust.test_iter = 5 - sine_gust.test_vals = [-1.977498, 3.481817, -0.010337, -0.004460] + sine_gust.test_vals = [-1.977498, 3.481817, -0.010210, -0.004556] sine_gust.unsteady = True test_list.append(sine_gust) @@ -1098,7 +1098,7 @@ def main(): edge_VW.cfg_dir = "nicf/edge" edge_VW.cfg_file = "edge_VW.cfg" edge_VW.test_iter = 25 - edge_VW.test_vals = [-3.116423, 3.084890, -0.000009, 0.000000] + edge_VW.test_vals = [-3.109008, 3.092306, -0.000009, 0.000000] test_list.append(edge_VW) # Rarefaction shock wave edge_PPR @@ -1106,7 +1106,7 @@ def main(): edge_PPR.cfg_dir = "nicf/edge" edge_PPR.cfg_file = "edge_PPR.cfg" edge_PPR.test_iter = 20 - edge_PPR.test_vals = [-9.963405, -3.810039, -0.000034, 0.000000] + edge_PPR.test_vals = [-10.058036, -3.902722, -0.000034, 0.000000] test_list.append(edge_PPR) # Rarefaction Q1D nozzle, include CoolProp fluid model @@ -1153,7 +1153,7 @@ def main(): Jones_tc_restart.cfg_dir = "turbomachinery/APU_turbocharger" Jones_tc_restart.cfg_file = "Jones_restart.cfg" Jones_tc_restart.test_iter = 5 - Jones_tc_restart.test_vals = [-7.645871, -5.849734, -15.337010, -9.825760, -13.216108, -7.752293, 73286.000000, 73286.000000, 0.020055, 82.286000] + Jones_tc_restart.test_vals = [-7.645871, -5.849734, -15.337010, -9.825759, -13.216108, -7.752293, 73286.000000, 73286.000000, 0.020055, 82.286000] test_list.append(Jones_tc_restart) # 2D axial stage @@ -1161,7 +1161,7 @@ def main(): axial_stage2D.cfg_dir = "turbomachinery/axial_stage_2D" axial_stage2D.cfg_file = "Axial_stage2D.cfg" axial_stage2D.test_iter = 20 - axial_stage2D.test_vals = [1.167160, 1.598506, -2.928574, 2.573647, -2.527393, 3.016169, 106370.000000, 106370.000000, 5.726800, 64.383000] + axial_stage2D.test_vals = [1.167161, 1.598507, -2.928575, 2.573646, -2.527392, 3.016170, 106370.000000, 106370.000000, 5.726800, 64.383000] test_list.append(axial_stage2D) # 2D transonic stator restart @@ -1169,7 +1169,7 @@ def main(): transonic_stator_restart.cfg_dir = "turbomachinery/transonic_stator_2D" transonic_stator_restart.cfg_file = "transonic_stator_restart.cfg" transonic_stator_restart.test_iter = 20 - transonic_stator_restart.test_vals = [-4.363703, -2.480017, -2.079311, 1.731709, -1.467573, 3.229965, -471620.000000, 94.838000, -0.046906] + transonic_stator_restart.test_vals = [-4.365633, -2.482254, -2.081248, 1.729767, -1.467304, 3.231076, -471620.000000, 94.837000, -0.046836] transonic_stator_restart.test_vals_aarch64 = [-4.437809, -2.553049, -2.164729, 1.657542, -1.356823, 3.178788, -471620.000000, 94.842000, -0.040365] test_list.append(transonic_stator_restart) @@ -1191,7 +1191,7 @@ def main(): uniform_flow.cfg_dir = "sliding_interface/uniform_flow" uniform_flow.cfg_file = "uniform_NN.cfg" uniform_flow.test_iter = 5 - uniform_flow.test_vals = [5.000000, 0.000000, -0.195002, -10.624448] + uniform_flow.test_vals = [5.000000, 0.000000, -0.195001, -10.624449] uniform_flow.unsteady = True uniform_flow.multizone = True test_list.append(uniform_flow) @@ -1201,7 +1201,7 @@ def main(): channel_2D.cfg_dir = "sliding_interface/channel_2D" channel_2D.cfg_file = "channel_2D_WA.cfg" channel_2D.test_iter = 2 - channel_2D.test_vals = [2.000000, 0.000000, 0.466210, 0.350093, 0.398997] + channel_2D.test_vals = [2.000000, 0.000000, 0.466215, 0.350091, 0.398996] channel_2D.timeout = 100 channel_2D.unsteady = True channel_2D.multizone = True @@ -1212,7 +1212,7 @@ def main(): channel_3D.cfg_dir = "sliding_interface/channel_3D" channel_3D.cfg_file = "channel_3D_WA.cfg" channel_3D.test_iter = 2 - channel_3D.test_vals = [2.000000, 0.000000, 0.632259, 0.534230, 0.432007] + channel_3D.test_vals = [2.000000, 0.000000, 0.632256, 0.534230, 0.431926] channel_3D.test_vals_aarch64 = [2.000000, 0.000000, 0.629119, 0.524959, 0.422390] channel_3D.unsteady = True channel_3D.multizone = True @@ -1233,7 +1233,7 @@ def main(): rotating_cylinders.cfg_dir = "sliding_interface/rotating_cylinders" rotating_cylinders.cfg_file = "rot_cylinders_WA.cfg" rotating_cylinders.test_iter = 3 - rotating_cylinders.test_vals = [3.000000, 0.000000, 0.664820, 1.125808, 1.117610] + rotating_cylinders.test_vals = [3.000000, 0.000000, 0.664821, 1.125807, 1.117610] rotating_cylinders.unsteady = True rotating_cylinders.multizone = True test_list.append(rotating_cylinders) @@ -1243,7 +1243,7 @@ def main(): supersonic_vortex_shedding.cfg_dir = "sliding_interface/supersonic_vortex_shedding" supersonic_vortex_shedding.cfg_file = "sup_vor_shed_WA.cfg" supersonic_vortex_shedding.test_iter = 5 - supersonic_vortex_shedding.test_vals = [5.000000, 0.000000, 0.899639, 1.076224] + supersonic_vortex_shedding.test_vals = [5.000000, 0.000000, 0.899643, 1.076189] supersonic_vortex_shedding.unsteady = True supersonic_vortex_shedding.multizone = True test_list.append(supersonic_vortex_shedding) @@ -1253,7 +1253,7 @@ def main(): bars_SST_2D.cfg_dir = "sliding_interface/bars_SST_2D" bars_SST_2D.cfg_file = "bars.cfg" bars_SST_2D.test_iter = 13 - bars_SST_2D.test_vals = [13.000000, -0.397743, -1.458724] + bars_SST_2D.test_vals = [13.000000, -0.395917, -1.480217] bars_SST_2D.multizone = True test_list.append(bars_SST_2D) @@ -1262,7 +1262,7 @@ def main(): slinc_steady.cfg_dir = "sliding_interface/incompressible_steady" slinc_steady.cfg_file = "config.cfg" slinc_steady.test_iter = 19 - slinc_steady.test_vals = [19.000000, -1.131557, -1.370471] + slinc_steady.test_vals = [19.000000, -1.129117, -1.399143] slinc_steady.timeout = 100 slinc_steady.tol = 0.00002 slinc_steady.multizone = True @@ -1297,7 +1297,7 @@ def main(): thermal_beam_3d.cfg_dir = "fea_fsi/ThermalBeam_3d" thermal_beam_3d.cfg_file = "configBeam_3d.cfg" thermal_beam_3d.test_iter = 4 - thermal_beam_3d.test_vals = [-8.147213, -7.841446, -7.904153, -13.978110, 217, -4.095071, 39, -4.072614, 136760, 75] + thermal_beam_3d.test_vals = [-8.070340, -7.802437, -7.856284, -13.978110, 217.000000, -4.047750, 39.000000, -4.072613, 136760.000000, 75.000000] test_list.append(thermal_beam_3d) # Static beam, 3d with coupled temperature, nonlinear elasticity @@ -1305,7 +1305,7 @@ def main(): thermal_beam_nl_3d.cfg_dir = "fea_fsi/ThermalBeam_3d" thermal_beam_nl_3d.cfg_file = "configBeamNonlinear_3d.cfg" thermal_beam_nl_3d.test_iter = 8 - thermal_beam_nl_3d.test_vals = [-7.564308, -2.992893, -12.242503, -14.068322, 57.000000, -4.017675, 24.000000, -4.204804, 138710.000000, 75.233000] + thermal_beam_nl_3d.test_vals = [-7.564308, -2.992893, -12.242503, -14.068322, 57.000000, -4.017672, 24.000000, -4.204804, 138710.000000, 75.233000] test_list.append(thermal_beam_nl_3d) # Rotating cylinder, 3d @@ -1316,7 +1316,7 @@ def main(): # For a thin disk with the inner and outer radius of this geometry, from # "Formulas for Stress, Strain, and Structural Matrices", 2nd Edition, figure 19-4, # the maximum stress is 165.6MPa, we get a von Mises stress very close to that. - rotating_cylinder_fea.test_vals = [-6.886142, -6.917150, -6.959635, 23.000000, -8.369804, 165020000.000000] + rotating_cylinder_fea.test_vals = [-6.760497, -6.689264, -6.739355, 37.000000, -8.178510, 165020000.000000] rotating_cylinder_fea.test_vals_aarch64 = [-6.861939, -6.835539, -6.895498, 22, -8.313847, 1.6502e+08] test_list.append(rotating_cylinder_fea) @@ -1325,7 +1325,7 @@ def main(): linear_plane_strain.cfg_dir = "fea_fsi/VonMissesVerif" linear_plane_strain.cfg_file = "linear_plane_strain_2d.cfg" linear_plane_strain.test_iter = 0 - linear_plane_strain.test_vals = [-6.036048, -6.001905, 0, 120140, 325, -8.025024] + linear_plane_strain.test_vals = [-6.406458, -5.995503, 0, 120140, 144, -8.122248] test_list.append(linear_plane_strain) # 2D beam in plain stress with thermal expansion. This tests fixes to the 2D von Mises stress calculation, @@ -1334,7 +1334,7 @@ def main(): nonlinear_plane_stress.cfg_dir = "fea_fsi/VonMissesVerif" nonlinear_plane_stress.cfg_file = "nonlinear_plane_stress_2d.cfg" nonlinear_plane_stress.test_iter = 16 - nonlinear_plane_stress.test_vals = [-7.131557, -2.945301, -13.165302, 1.6248e+05, 32, -4.129942] + nonlinear_plane_stress.test_vals = [-6.230217, -2.228380, -11.698370, 162480.000000, 30.000000, -4.169761] nonlinear_plane_stress.tol = [2e-4, 2e-4, 2e-4, 1e-5, 1e-5, 4e-4] test_list.append(nonlinear_plane_stress) @@ -1363,7 +1363,7 @@ def main(): dyn_fsi.cfg_dir = "fea_fsi/dyn_fsi" dyn_fsi.cfg_file = "config.cfg" dyn_fsi.test_iter = 4 - dyn_fsi.test_vals = [-4.330741, -4.152826, 0, 97] + dyn_fsi.test_vals = [-4.330741, -4.152826, 0, 75] dyn_fsi.multizone = True dyn_fsi.unsteady = True test_list.append(dyn_fsi) @@ -1390,7 +1390,7 @@ def main(): solid_periodic_pins.cfg_dir = "solid_heat_conduction/periodic_pins" solid_periodic_pins.cfg_file = "configSolid.cfg" solid_periodic_pins.test_iter = 750 - solid_periodic_pins.test_vals = [-15.878973, -14.569206, 300.900000, 425.320000, 5.000000, -1.672670] + solid_periodic_pins.test_vals = [-15.878957, -14.569206, 300.900000, 425.320000, 5.000000, -1.672645] solid_periodic_pins.test_vals_aarch64 = [-15.879016, -14.569206, 300.900000, 425.320000, 5.000000, -1.672666] test_list.append(solid_periodic_pins) @@ -1403,7 +1403,7 @@ def main(): cht_incompressible.cfg_dir = "coupled_cht/incomp_2d" cht_incompressible.cfg_file = "cht_2d_3cylinders.cfg" cht_incompressible.test_iter = 10 - cht_incompressible.test_vals = [-1.376344, -0.591211, -0.591211, -0.591211] + cht_incompressible.test_vals = [-1.376344, -0.591210, -0.591210, -0.591211] cht_incompressible.multizone = True test_list.append(cht_incompressible) @@ -1412,7 +1412,7 @@ def main(): cht_compressible.cfg_dir = "coupled_cht/comp_2d" cht_compressible.cfg_file = "cht_2d_3cylinders.cfg" cht_compressible.test_iter = 10 - cht_compressible.test_vals = [-4.256053, -0.532725, -0.532725, -0.532726] + cht_compressible.test_vals = [-4.256051, -0.532725, -0.532724, -0.532725] cht_compressible.multizone = True test_list.append(cht_compressible) @@ -1444,7 +1444,7 @@ def main(): pywrapper_naca0012.cfg_dir = "euler/naca0012" pywrapper_naca0012.cfg_file = "inv_NACA0012_Roe.cfg" pywrapper_naca0012.test_iter = 80 - pywrapper_naca0012.test_vals = [-6.753161, -6.156190, 0.335712, 0.023273] + pywrapper_naca0012.test_vals = [-6.853230, -6.303063, 0.335702, 0.023274] pywrapper_naca0012.command = TestCase.Command("mpirun -np 2", "SU2_CFD.py", "--parallel -f") test_list.append(pywrapper_naca0012) @@ -1453,7 +1453,7 @@ def main(): pywrapper_turb_naca0012_sst.cfg_dir = "rans/naca0012" pywrapper_turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" pywrapper_turb_naca0012_sst.test_iter = 10 - pywrapper_turb_naca0012_sst.test_vals = [-12.094739, -15.251094, -5.906365, 1.070413, 0.015775, -2.376137, 0.000000] + pywrapper_turb_naca0012_sst.test_vals = [-12.094759, -15.251093, -5.906365, 1.070413, 0.015775, -2.376032, 0.000000] pywrapper_turb_naca0012_sst.test_vals_aarch64 = [-12.075620, -15.246688, -5.861276, 1.070036, 0.015841, -1.991001, 0.000000] pywrapper_turb_naca0012_sst.command = TestCase.Command("mpirun -np 2", "SU2_CFD.py", "--parallel -f") pywrapper_turb_naca0012_sst.timeout = 3200 @@ -1464,7 +1464,7 @@ def main(): pywrapper_square_cylinder.cfg_dir = "unsteady/square_cylinder" pywrapper_square_cylinder.cfg_file = "turb_square.cfg" pywrapper_square_cylinder.test_iter = 10 - pywrapper_square_cylinder.test_vals = [-1.178522, -0.349773, 1.401402, 2.359089, 1.401728, 2.300969, 0.000000] + pywrapper_square_cylinder.test_vals = [-1.178521, -0.349772, 1.401402, 2.359105, 1.401729, 2.300974, 0.000000] pywrapper_square_cylinder.command = TestCase.Command("mpirun -np 2", "SU2_CFD.py", "--parallel -f") pywrapper_square_cylinder.unsteady = True test_list.append(pywrapper_square_cylinder) @@ -1474,7 +1474,7 @@ def main(): pywrapper_aeroelastic.cfg_dir = "aeroelastic" pywrapper_aeroelastic.cfg_file = "aeroelastic_NACA64A010.cfg" pywrapper_aeroelastic.test_iter = 2 - pywrapper_aeroelastic.test_vals = [-1.876633, 4.021069, 0.082654, 0.027642, -0.001643, -0.000126, -0.966946] + pywrapper_aeroelastic.test_vals = [-1.876630, 4.021073, 0.082632, 0.027609, -0.001642, -0.000127, -0.965661] pywrapper_aeroelastic.command = TestCase.Command("mpirun -np 2", "SU2_CFD.py", "--parallel -f") pywrapper_aeroelastic.unsteady = True test_list.append(pywrapper_aeroelastic) @@ -1484,7 +1484,7 @@ def main(): pywrapper_custom_fea_load.cfg_dir = "py_wrapper/custom_load_fea" pywrapper_custom_fea_load.cfg_file = "config.cfg" pywrapper_custom_fea_load.test_iter = 13 - pywrapper_custom_fea_load.test_vals = [-7.262040, -4.945686, -14.163208, 34, -6.029883, 362.230000] + pywrapper_custom_fea_load.test_vals = [-7.262040, -4.945686, -14.163208, 27.000000, -6.282188, 362.230000] pywrapper_custom_fea_load.command = TestCase.Command("mpirun -np 2", "python", "run.py") test_list.append(pywrapper_custom_fea_load) @@ -1504,7 +1504,7 @@ def main(): pywrapper_unsteadyFSI.cfg_dir = "py_wrapper/dyn_fsi" pywrapper_unsteadyFSI.cfg_file = "config.cfg" pywrapper_unsteadyFSI.test_iter = 4 - pywrapper_unsteadyFSI.test_vals = [0, 31, 5, 58, -1.756677, -2.828286, -7.638545, -6.863930, 0.000156] + pywrapper_unsteadyFSI.test_vals = [0, 31, 5, 47, -1.756677, -2.828286, -7.638545, -6.863930, 0.000156] pywrapper_unsteadyFSI.command = TestCase.Command("mpirun -np 2", "python", "run.py") pywrapper_unsteadyFSI.unsteady = True pywrapper_unsteadyFSI.multizone = True @@ -1515,7 +1515,7 @@ def main(): pywrapper_unsteadyCHT.cfg_dir = "py_wrapper/flatPlate_unsteady_CHT" pywrapper_unsteadyCHT.cfg_file = "unsteady_CHT_FlatPlate_Conf.cfg" pywrapper_unsteadyCHT.test_iter = 5 - pywrapper_unsteadyCHT.test_vals = [-1.614168, 2.259968, -0.010622, 0.171621] + pywrapper_unsteadyCHT.test_vals = [-1.614169, 2.260087, 0.018775, 0.168073] pywrapper_unsteadyCHT.command = TestCase.Command("mpirun -np 2", "python", "launch_unsteady_CHT_FlatPlate.py --parallel -f") pywrapper_unsteadyCHT.unsteady = True test_list.append(pywrapper_unsteadyCHT) @@ -1525,7 +1525,7 @@ def main(): pywrapper_rigidMotion.cfg_dir = "py_wrapper/flatPlate_rigidMotion" pywrapper_rigidMotion.cfg_file = "flatPlate_rigidMotion_Conf.cfg" pywrapper_rigidMotion.test_iter = 5 - pywrapper_rigidMotion.test_vals = [-1.614166, 2.255135, 0.350194, 0.089496] + pywrapper_rigidMotion.test_vals = [-1.614166, 2.255135, 0.350196, 0.089496] pywrapper_rigidMotion.command = TestCase.Command("mpirun -np 2", "python", "launch_flatPlate_rigidMotion.py --parallel -f") pywrapper_rigidMotion.unsteady = True test_list.append(pywrapper_rigidMotion) @@ -1535,7 +1535,7 @@ def main(): pywrapper_deformingBump.cfg_dir = "py_wrapper/deforming_bump_in_channel" pywrapper_deformingBump.cfg_file = "config.cfg" pywrapper_deformingBump.test_iter = 1 - pywrapper_deformingBump.test_vals = [0.500000, 0.000000, -2.556309, -1.270839, -2.350591, 2.606851, 8.002480, -0.300272] + pywrapper_deformingBump.test_vals = [0.500000, 0.000000, -2.556310, -1.270841, -2.350592, 2.606850, 8.015020, -0.301652] pywrapper_deformingBump.command = TestCase.Command("mpirun -np 2", "python", "run.py") pywrapper_deformingBump.unsteady = True test_list.append(pywrapper_deformingBump) @@ -1597,7 +1597,7 @@ def main(): mms_fvm_inc_euler.cfg_dir = "mms/fvm_incomp_euler" mms_fvm_inc_euler.cfg_file = "inv_mms_jst.cfg" mms_fvm_inc_euler.test_iter = 20 - mms_fvm_inc_euler.test_vals = [-9.128660, -9.441806, 0.000000, 0.000000] + mms_fvm_inc_euler.test_vals = [-9.128735, -9.441756, 0.000000, 0.000000] mms_fvm_inc_euler.tol = 0.0001 test_list.append(mms_fvm_inc_euler) @@ -1654,7 +1654,7 @@ def main(): species2_primitiveVenturi_mixingmodel_boundedscalar.cfg_dir = "species_transport/venturi_primitive_3species" species2_primitiveVenturi_mixingmodel_boundedscalar.cfg_file = "species2_primitiveVenturi_mixingmodel_boundedscalar.cfg" species2_primitiveVenturi_mixingmodel_boundedscalar.test_iter = 50 - species2_primitiveVenturi_mixingmodel_boundedscalar.test_vals = [-5.689670, -4.511504, -4.615493, -5.795205, -0.113336, -5.704986, 5.000000, -1.433752, 5.000000, -4.921374, 5.000000, -1.771015, 0.000318, 0.000318, 0.000000, 0.000000] + species2_primitiveVenturi_mixingmodel_boundedscalar.test_vals = [-5.689670, -4.511504, -4.615493, -5.795205, -0.113336, -5.704986, 5.000000, -1.433752, 5.000000, -4.921373, 5.000000, -1.771015, 0.000318, 0.000318, 0.000000, 0.000000] test_list.append(species2_primitiveVenturi_mixingmodel_boundedscalar) # 2 species (1 eq) primitive venturi mixing using mixing model including viscosity, thermal conductivity and inlet markers for SA turbulence model @@ -1743,7 +1743,7 @@ def main(): species_passive_val.cfg_dir = "species_transport/passive_transport_validation" species_passive_val.cfg_file = "passive_transport.cfg" species_passive_val.test_iter = 50 - species_passive_val.test_vals = [-16.576049, -16.349168, -16.916647, -4.257599, 10, -4.232357, 8, -5.193350, 0.186610, 0] + species_passive_val.test_vals = [-16.493002, -16.246291, -16.871574, -4.257599, 10.000000, -4.526973, 8.000000, -5.193350, 0.186610, 0.000000] species_passive_val.test_vals_aarch64 = [-16.517744, -16.282420, -16.871663, -4.257599, 10.000000, -4.278151, 8.000000, -5.193350, 0.186610, 0.000000] test_list.append(species_passive_val) diff --git a/TestCases/parallel_regression_AD.py b/TestCases/parallel_regression_AD.py index f03615fba02a..2b8283f49a4b 100644 --- a/TestCases/parallel_regression_AD.py +++ b/TestCases/parallel_regression_AD.py @@ -55,7 +55,7 @@ def main(): discadj_cylinder3D.cfg_dir = "disc_adj_euler/cylinder3D" discadj_cylinder3D.cfg_file = "inv_cylinder3D.cfg" discadj_cylinder3D.test_iter = 5 - discadj_cylinder3D.test_vals = [-3.693714, -3.889422, 0.000000, 0.000000] + discadj_cylinder3D.test_vals = [-3.693793, -3.889408, 0.000000, 0.000000] test_list.append(discadj_cylinder3D) # Arina nozzle 2D @@ -63,7 +63,7 @@ def main(): discadj_arina2k.cfg_dir = "disc_adj_euler/arina2k" discadj_arina2k.cfg_file = "Arina2KRS.cfg" discadj_arina2k.test_iter = 20 - discadj_arina2k.test_vals = [-2.931649, -3.356322, 0.073332, 0.000000] + discadj_arina2k.test_vals = [-2.933011, -3.357218, 0.073338, 0.000000] test_list.append(discadj_arina2k) # Equivalent area NACA64-206 @@ -104,7 +104,7 @@ def main(): discadj_incomp_NACA0012.cfg_dir = "disc_adj_incomp_euler/naca0012" discadj_incomp_NACA0012.cfg_file = "incomp_NACA0012_disc.cfg" discadj_incomp_NACA0012.test_iter = 20 - discadj_incomp_NACA0012.test_vals = [20.000000, -3.095094, -2.322528, 0.000000] + discadj_incomp_NACA0012.test_vals = [20.000000, -3.096582, -2.323911, 0.000000] test_list.append(discadj_incomp_NACA0012) ##################################### @@ -116,7 +116,7 @@ def main(): discadj_incomp_cylinder.cfg_dir = "disc_adj_incomp_navierstokes/cylinder" discadj_incomp_cylinder.cfg_file = "heated_cylinder.cfg" discadj_incomp_cylinder.test_iter = 20 - discadj_incomp_cylinder.test_vals = [20.000000, -1.652493, -6.202452, 0.000000] + discadj_incomp_cylinder.test_vals = [20.000000, -1.658160, -6.215433, 0.000000] test_list.append(discadj_incomp_cylinder) ###################################### @@ -163,7 +163,7 @@ def main(): discadj_cylinder.cfg_dir = "disc_adj_rans/cylinder" discadj_cylinder.cfg_file = "cylinder.cfg" discadj_cylinder.test_iter = 9 - discadj_cylinder.test_vals = [1.639372, -2.834295, -0.009538, 0.000020] #last 4 columns + discadj_cylinder.test_vals = [1.639372, -2.834293, -0.009538, 0.000020] discadj_cylinder.unsteady = True test_list.append(discadj_cylinder) @@ -176,7 +176,7 @@ def main(): discadj_cylinder.cfg_dir = "disc_adj_rans/cylinder" discadj_cylinder.cfg_file = "cylinder_Windowing_AD.cfg" discadj_cylinder.test_iter = 9 - discadj_cylinder.test_vals = [2.183366] #last column + discadj_cylinder.test_vals = [2.183361] discadj_cylinder.unsteady = True test_list.append(discadj_cylinder) @@ -204,7 +204,7 @@ def main(): discadj_DT_1ST_cylinder.cfg_dir = "disc_adj_rans/cylinder_DT_1ST" discadj_DT_1ST_cylinder.cfg_file = "cylinder.cfg" discadj_DT_1ST_cylinder.test_iter = 9 - discadj_DT_1ST_cylinder.test_vals = [1.196414, -3.339025, -0.006212, 0.000020] + discadj_DT_1ST_cylinder.test_vals = [1.196414, -3.339029, -0.006212, 0.000020] discadj_DT_1ST_cylinder.unsteady = True test_list.append(discadj_DT_1ST_cylinder) @@ -217,7 +217,7 @@ def main(): discadj_pitchingNACA0012.cfg_dir = "disc_adj_euler/naca0012_pitching" discadj_pitchingNACA0012.cfg_file = "inv_NACA0012_pitching.cfg" discadj_pitchingNACA0012.test_iter = 4 - discadj_pitchingNACA0012.test_vals = [-1.040215, -1.508864, -0.006076, 0.000012] + discadj_pitchingNACA0012.test_vals = [-1.040019, -1.508690, -0.006062, 0.000012] discadj_pitchingNACA0012.tol = 0.01 discadj_pitchingNACA0012.unsteady = True test_list.append(discadj_pitchingNACA0012) @@ -231,7 +231,7 @@ def main(): discadj_trans_stator.cfg_dir = "disc_adj_turbomachinery/transonic_stator_2D" discadj_trans_stator.cfg_file = "transonic_stator.cfg" discadj_trans_stator.test_iter = 79 - discadj_trans_stator.test_vals = [79.000000, 2.555965, 2.327109, 2.115689, 0.745501] + discadj_trans_stator.test_vals = [79.000000, 2.549036, 2.313067, 2.139713, 0.736742] discadj_trans_stator.test_vals_aarch64 = [79.000000, 0.696755, 0.485950, 0.569475, -0.990065] test_list.append(discadj_trans_stator) @@ -244,7 +244,7 @@ def main(): discadj_fea.cfg_dir = "disc_adj_fea" discadj_fea.cfg_file = "configAD_fem.cfg" discadj_fea.test_iter = 4 - discadj_fea.test_vals = [-2.849687, -3.238608, -0.000364, -8.708700] #last 4 columns + discadj_fea.test_vals = [-2.849947, -3.238801, -0.000364, -8.708700] discadj_fea.test_vals_aarch64 = [-2.849646, -3.238577, -0.000364, -8.708700] #last 4 columns test_list.append(discadj_fea) @@ -254,7 +254,7 @@ def main(): discadj_thermoelastic.cfg_dir = "fea_fsi/ThermalBeam_3d" discadj_thermoelastic.cfg_file = "configBeamNonlinear_3d_ad.cfg" discadj_thermoelastic.test_iter = 10 - discadj_thermoelastic.test_vals = [-5.355531, -5.293380, -6.164482, -6.433863, 43, -4.049760, 27, -4.164183, 0, 0.192640, 0] + discadj_thermoelastic.test_vals = [-5.355530, -5.293381, -6.164472, -6.433864, 43, -4.049760, 27, -4.164193, 0, 0.192640, 0] test_list.append(discadj_thermoelastic) ################################### @@ -266,7 +266,7 @@ def main(): discadj_heat.cfg_dir = "disc_adj_heat" discadj_heat.cfg_file = "disc_adj_heat.cfg" discadj_heat.test_iter = 10 - discadj_heat.test_vals = [-1.880390, 0.759800, 0.000000, -4.486700] + discadj_heat.test_vals = [-2.344276, 0.759978, 0.000000, -3.967600] test_list.append(discadj_heat) ################################### @@ -277,8 +277,8 @@ def main(): discadj_fsi = TestCase('discadj_fsi') discadj_fsi.cfg_dir = "disc_adj_fsi" discadj_fsi.cfg_file = "config.cfg" - discadj_fsi.test_iter = 6 - discadj_fsi.test_vals = [6.000000, -7.017369, -7.872618, 0.000000, -0.000024] + discadj_fsi.test_iter = 9 + discadj_fsi.test_vals = [-3.166421, -4.160938, 0.000439, -1.061900] test_list.append(discadj_fsi) # Multi physics framework @@ -286,7 +286,7 @@ def main(): discadj_fsi2.cfg_dir = "disc_adj_fsi/Airfoil_2d" discadj_fsi2.cfg_file = "config.cfg" discadj_fsi2.test_iter = 8 - discadj_fsi2.test_vals = [-3.824630, 1.979615, -3.863368, 0.295450, 3.839800] + discadj_fsi2.test_vals = [-3.824634, 1.979533, -3.863368, 0.295450, 3.839800] discadj_fsi2.test_vals_aarch64 = [-3.824870, 1.979160, -3.863368, 0.295450, 3.839800] discadj_fsi2.tol = 0.00001 test_list.append(discadj_fsi2) @@ -308,7 +308,7 @@ def main(): da_sp_pinArray_cht_2d_dp_hf.cfg_dir = "incomp_navierstokes/streamwise_periodic/chtPinArray_2d" da_sp_pinArray_cht_2d_dp_hf.cfg_file = "DA_configMaster.cfg" da_sp_pinArray_cht_2d_dp_hf.test_iter = 100 - da_sp_pinArray_cht_2d_dp_hf.test_vals = [-2.805458, -3.441841, -2.767871] + da_sp_pinArray_cht_2d_dp_hf.test_vals = [-2.717803, -3.199669, -2.499149] da_sp_pinArray_cht_2d_dp_hf.multizone = True test_list.append(da_sp_pinArray_cht_2d_dp_hf) @@ -326,7 +326,7 @@ def main(): da_unsteadyCHT_cylinder.cfg_dir = "coupled_cht/disc_adj_unsteadyCHT_cylinder" da_unsteadyCHT_cylinder.cfg_file = "chtMaster.cfg" da_unsteadyCHT_cylinder.test_iter = 2 - da_unsteadyCHT_cylinder.test_vals = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662013, 0.000000, 10.627000, 0.295190] + da_unsteadyCHT_cylinder.test_vals = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662004, 0.000000, 10.627000, 0.295190] da_unsteadyCHT_cylinder.test_vals_aarch64 = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662012, 0.000000, 89.932000, 0.295190] da_unsteadyCHT_cylinder.unsteady = True da_unsteadyCHT_cylinder.multizone = True @@ -342,7 +342,7 @@ def main(): discadj_flamelet_ch4_hx.cfg_file = "lam_prem_ch4_hx_ad.cfg" discadj_flamelet_ch4_hx.multizone = False discadj_flamelet_ch4_hx.test_iter = 10 - discadj_flamelet_ch4_hx.test_vals = [-9.078706, -9.025745, -9.516205, -8.434002, -15.386905, -8.887596, -18.881167] + discadj_flamelet_ch4_hx.test_vals = [-9.083502, -9.033520, -9.529644, -8.442388, -15.399568, -6.170002, -18.881156] test_list.append(discadj_flamelet_ch4_hx) # 2D planar laminar premixed flame on isothermal burner with conjugate heat transfer (restart) @@ -351,7 +351,7 @@ def main(): discadj_flamelet_ch4_cht.cfg_file = "lam_prem_ch4_cht_ad_master.cfg" discadj_flamelet_ch4_cht.multizone = True discadj_flamelet_ch4_cht.test_iter = 10 - discadj_flamelet_ch4_cht.test_vals = [-1.545058, 0.628666, -6.533534, -18.651078, -18.648552, -3.794724, -6.561913, 11.000000] + discadj_flamelet_ch4_cht.test_vals = [-1.545058, 0.628666, -6.533534, -18.651078, -18.648552, -3.794724, -6.572494, 11.000000] test_list.append(discadj_flamelet_ch4_cht) ###################################### @@ -508,7 +508,7 @@ def main(): pywrapper_CFD_AD_MeshDisp.cfg_dir = "py_wrapper/disc_adj_flow/mesh_disp_sens" pywrapper_CFD_AD_MeshDisp.cfg_file = "configAD_flow.cfg" pywrapper_CFD_AD_MeshDisp.test_iter = 1000 - pywrapper_CFD_AD_MeshDisp.test_vals = [30.000000, -2.496560, 1.440884, 0.000000] + pywrapper_CFD_AD_MeshDisp.test_vals = [30.000000, -2.496452, 1.441022, 0.000000] pywrapper_CFD_AD_MeshDisp.command = TestCase.Command("mpirun -n 2", "python", "run_adjoint.py --parallel -f") pywrapper_CFD_AD_MeshDisp.timeout = 1600 pywrapper_CFD_AD_MeshDisp.tol = 0.000001 @@ -521,7 +521,7 @@ def main(): pywrapper_wavy_wall_steady.cfg_dir = "py_wrapper/wavy_wall" pywrapper_wavy_wall_steady.cfg_file = "run_steady.py" pywrapper_wavy_wall_steady.test_iter = 100 - pywrapper_wavy_wall_steady.test_vals = [-1.353007, 2.581052, -2.900575] + pywrapper_wavy_wall_steady.test_vals = [-1.353007, 2.581052, -2.900574] pywrapper_wavy_wall_steady.command = TestCase.Command("mpirun -n 2", "python", "run_steady.py") pywrapper_wavy_wall_steady.timeout = 1600 pywrapper_wavy_wall_steady.tol = 0.00001 diff --git a/TestCases/py_wrapper/translating_NACA0012/forces_0.csv.ref b/TestCases/py_wrapper/translating_NACA0012/forces_0.csv.ref index df2f7651b449..7602bf98c369 100644 --- a/TestCases/py_wrapper/translating_NACA0012/forces_0.csv.ref +++ b/TestCases/py_wrapper/translating_NACA0012/forces_0.csv.ref @@ -1,200 +1,200 @@ -199, -0.92, -0.00, 0.00 -0, -2.81, 19.31, 0.00 -1, -3.96, 27.31, 0.00 -2, -4.94, 34.10, 0.00 -3, -5.78, 40.00, 0.00 -4, -6.52, 45.26, 0.00 -5, -7.12, 49.55, 0.00 -6, -7.61, 53.23, 0.00 -7, -7.99, 56.10, 0.00 -8, -8.25, 58.25, 0.00 -9, -8.40, 59.65, 0.00 -10, -8.44, 60.37, 0.00 -11, -8.40, 60.49, 0.00 -12, -8.26, 59.94, 0.00 -13, -8.07, 59.06, 0.00 -14, -7.76, 57.31, 0.00 -15, -7.41, 55.21, 0.00 -16, -6.96, 52.34, 0.00 -17, -6.45, 49.03, 0.00 -18, -5.88, 45.20, 0.00 -19, -5.32, 41.32, 0.00 -20, -4.65, 36.55, 0.00 -21, -3.98, 31.65, 0.00 -22, -3.24, 26.08, 0.00 -23, -2.50, 20.41, 0.00 -24, -1.70, 14.08, 0.00 -25, -0.94, 7.92, 0.00 -26, -0.11, 0.91, 0.00 -27, 0.76, -6.55, 0.00 -28, 1.61, -14.10, 0.00 -29, 2.34, -20.84, 0.00 -30, 3.22, -29.22, 0.00 -31, 4.11, -37.87, 0.00 -32, 4.97, -46.68, 0.00 -33, 5.78, -55.30, 0.00 -34, 6.60, -64.38, 0.00 -35, 7.41, -73.74, 0.00 -36, 8.19, -83.27, 0.00 -37, 8.99, -93.47, 0.00 -38, 9.72, -103.41, 0.00 -39, 10.47, -114.27, 0.00 -40, 11.12, -124.57, 0.00 -41, 11.78, -135.78, 0.00 -42, 12.30, -146.05, 0.00 -43, 12.85, -157.56, 0.00 -44, 13.23, -168.05, 0.00 -45, 13.58, -179.05, 0.00 -46, 13.80, -189.61, 0.00 -47, 13.87, -199.23, 0.00 -48, 13.88, -209.29, 0.00 -49, 13.76, -218.94, 0.00 -50, 13.55, -228.91, 0.00 -51, 12.94, -233.69, 0.00 -52, 12.72, -247.56, 0.00 -53, 12.33, -261.38, 0.00 -54, 11.74, -274.42, 0.00 -55, 10.71, -280.64, 0.00 -56, 9.07, -272.52, 0.00 -57, 7.59, -269.15, 0.00 -58, 6.93, -303.08, 0.00 -59, 6.80, -393.44, 0.00 -60, 5.02, -440.64, 0.00 -61, 2.26, -431.72, 0.00 -62, -0.52, -421.20, 0.00 -63, -3.29, -410.57, 0.00 -64, -6.05, -400.01, 0.00 -65, -8.76, -387.67, 0.00 -66, -11.41, -374.93, 0.00 -67, -13.94, -360.27, 0.00 -68, -16.37, -345.88, 0.00 -69, -18.55, -328.61, 0.00 -70, -20.66, -312.89, 0.00 -71, -22.66, -297.53, 0.00 -72, -24.25, -279.29, 0.00 -73, -25.65, -261.40, 0.00 -74, -26.69, -242.37, 0.00 -75, -28.31, -230.36, 0.00 -76, -28.63, -209.71, 0.00 -77, -29.03, -192.15, 0.00 -78, -28.80, -172.72, 0.00 -79, -29.70, -161.67, 0.00 -80, -28.77, -142.34, 0.00 -81, -28.04, -126.17, 0.00 -82, -25.54, -104.51, 0.00 -83, -26.31, -97.80, 0.00 -84, -22.38, -75.48, 0.00 -85, -27.40, -83.64, 0.00 -86, -22.35, -61.54, 0.00 -87, -14.41, -35.62, 0.00 -88, -9.00, -19.86, 0.00 -89, -14.66, -28.68, 0.00 -90, -11.68, -20.05, 0.00 -91, -38.05, -56.57, 0.00 -92, -40.77, -51.64, 0.00 -93, -49.35, -52.03, 0.00 -94, -28.55, -24.25, 0.00 -95, 30.46, 19.84, 0.00 -96, 14.41, 6.61, 0.00 -97, 124.60, 33.72, 0.00 -98, 88.18, 11.83, 0.00 -99, 50.99, -0.00, 0.00 -100, 139.64, -18.74, 0.00 -101, 167.24, -45.26, 0.00 -102, 115.14, -52.78, 0.00 -103, 86.56, -56.38, 0.00 -104, 75.50, -64.13, 0.00 -105, 8.40, -8.85, 0.00 -106, 5.63, -7.13, 0.00 -107, -24.68, 36.68, 0.00 -108, -33.56, 57.59, 0.00 -109, -40.80, 79.80, 0.00 -110, -48.06, 106.07, 0.00 -111, -47.07, 116.38, 0.00 -112, -51.99, 143.14, 0.00 -113, -50.64, 154.57, 0.00 -114, -53.25, 179.59, 0.00 -115, -55.77, 207.35, 0.00 -116, -56.99, 233.15, 0.00 -117, -56.78, 255.45, 0.00 -118, -56.53, 279.68, 0.00 -119, -55.76, 303.48, 0.00 -120, -54.71, 328.03, 0.00 -121, -53.03, 350.95, 0.00 -122, -51.15, 374.74, 0.00 -123, -48.72, 396.47, 0.00 -124, -46.17, 419.21, 0.00 -125, -43.26, 440.80, 0.00 -126, -40.26, 463.61, 0.00 -127, -36.79, 483.15, 0.00 -128, -33.38, 505.47, 0.00 -129, -29.71, 526.37, 0.00 -130, -25.91, 547.32, 0.00 -131, -21.93, 567.07, 0.00 -132, -17.86, 586.63, 0.00 -133, -13.70, 606.12, 0.00 -134, -9.45, 624.31, 0.00 -135, -5.14, 641.70, 0.00 -136, -0.81, 658.71, 0.00 -137, 3.53, 674.61, 0.00 -138, 7.86, 689.50, 0.00 -139, 12.16, 704.17, 0.00 -140, 16.41, 717.33, 0.00 -141, 20.57, 729.12, 0.00 -142, 24.65, 740.15, 0.00 -143, 28.65, 750.98, 0.00 -144, 32.50, 759.96, 0.00 -145, 36.21, 767.66, 0.00 -146, 39.79, 774.52, 0.00 -147, 43.22, 780.53, 0.00 -148, 46.47, 785.06, 0.00 -149, 49.59, 789.35, 0.00 -150, 52.48, 791.55, 0.00 -151, 55.08, 791.14, 0.00 -152, 57.66, 792.15, 0.00 -153, 60.78, 801.51, 0.00 -154, 63.32, 804.14, 0.00 -155, 64.99, 797.13, 0.00 -156, 66.49, 789.57, 0.00 -157, 59.04, 680.31, 0.00 -158, 13.95, 156.29, 0.00 -159, -6.84, -74.66, 0.00 -160, -3.13, -33.36, 0.00 -161, 3.28, 34.11, 0.00 -162, 4.40, 44.73, 0.00 -163, 2.73, 27.19, 0.00 -164, 2.91, 28.39, 0.00 -165, 1.42, 13.56, 0.00 -166, 1.29, 12.11, 0.00 -167, 0.35, 3.21, 0.00 -168, -0.01, -0.09, 0.00 -169, -0.71, -6.32, 0.00 -170, -1.16, -10.21, 0.00 -171, -2.03, -17.54, 0.00 -172, -2.58, -21.94, 0.00 -173, -3.22, -27.00, 0.00 -174, -3.78, -31.27, 0.00 -175, -4.35, -35.53, 0.00 -176, -4.92, -39.61, 0.00 -177, -5.48, -43.56, 0.00 -178, -5.99, -47.08, 0.00 -179, -6.52, -50.65, 0.00 -180, -6.98, -53.62, 0.00 -181, -7.37, -56.02, 0.00 -182, -7.75, -58.33, 0.00 -183, -8.07, -60.15, 0.00 -184, -8.35, -61.64, 0.00 -185, -8.58, -62.75, 0.00 -186, -8.72, -63.28, 0.00 -187, -8.75, -63.04, 0.00 -188, -8.72, -62.37, 0.00 -189, -8.59, -61.03, 0.00 -190, -8.38, -59.15, 0.00 -191, -8.07, -56.67, 0.00 -192, -7.66, -53.53, 0.00 -193, -7.10, -49.40, 0.00 -194, -6.47, -44.88, 0.00 -195, -5.69, -39.34, 0.00 -196, -4.81, -33.19, 0.00 -197, -3.80, -26.16, 0.00 -198, -2.63, -18.08, 0.00 +199, -0.91, -0.00, 0.00 +0, -2.81, 19.36, 0.00 +1, -4.04, 27.84, 0.00 +2, -5.06, 34.89, 0.00 +3, -5.96, 41.22, 0.00 +4, -6.74, 46.79, 0.00 +5, -7.38, 51.39, 0.00 +6, -7.93, 55.45, 0.00 +7, -8.34, 58.60, 0.00 +8, -8.66, 61.16, 0.00 +9, -8.85, 62.88, 0.00 +10, -8.96, 64.08, 0.00 +11, -8.96, 64.56, 0.00 +12, -8.89, 64.51, 0.00 +13, -8.70, 63.68, 0.00 +14, -8.46, 62.43, 0.00 +15, -8.12, 60.46, 0.00 +16, -7.73, 58.13, 0.00 +17, -7.26, 55.15, 0.00 +18, -6.74, 51.77, 0.00 +19, -6.15, 47.79, 0.00 +20, -5.53, 43.47, 0.00 +21, -4.87, 38.71, 0.00 +22, -4.16, 33.49, 0.00 +23, -3.41, 27.84, 0.00 +24, -2.65, 21.90, 0.00 +25, -1.87, 15.65, 0.00 +26, -1.06, 9.02, 0.00 +27, -0.25, 2.17, 0.00 +28, 0.59, -5.20, 0.00 +29, 1.47, -13.09, 0.00 +30, 2.32, -21.04, 0.00 +31, 3.18, -29.33, 0.00 +32, 4.02, -37.73, 0.00 +33, 4.83, -46.23, 0.00 +34, 5.65, -55.07, 0.00 +35, 6.43, -63.94, 0.00 +36, 7.21, -73.28, 0.00 +37, 7.96, -82.77, 0.00 +38, 8.71, -92.70, 0.00 +39, 9.45, -103.10, 0.00 +40, 10.12, -113.32, 0.00 +41, 10.74, -123.75, 0.00 +42, 11.28, -133.98, 0.00 +43, 11.76, -144.27, 0.00 +44, 12.16, -154.42, 0.00 +45, 12.43, -163.95, 0.00 +46, 12.66, -173.97, 0.00 +47, 12.73, -182.77, 0.00 +48, 12.74, -192.14, 0.00 +49, 12.38, -196.96, 0.00 +50, 12.15, -205.21, 0.00 +51, 11.33, -204.66, 0.00 +52, 11.18, -217.62, 0.00 +53, 10.64, -225.59, 0.00 +54, 10.41, -243.33, 0.00 +55, 9.98, -261.63, 0.00 +56, 9.05, -271.88, 0.00 +57, 8.38, -297.01, 0.00 +58, 7.37, -322.02, 0.00 +59, 6.73, -389.49, 0.00 +60, 4.98, -437.02, 0.00 +61, 2.24, -427.29, 0.00 +62, -0.51, -413.94, 0.00 +63, -3.21, -400.44, 0.00 +64, -5.89, -389.11, 0.00 +65, -8.49, -375.82, 0.00 +66, -11.02, -361.82, 0.00 +67, -13.42, -346.89, 0.00 +68, -15.69, -331.52, 0.00 +69, -17.69, -313.40, 0.00 +70, -19.62, -297.17, 0.00 +71, -21.05, -276.48, 0.00 +72, -22.44, -258.43, 0.00 +73, -23.19, -236.27, 0.00 +74, -24.06, -218.46, 0.00 +75, -24.21, -196.97, 0.00 +76, -24.27, -177.79, 0.00 +77, -23.30, -154.22, 0.00 +78, -22.88, -137.20, 0.00 +79, -21.44, -116.69, 0.00 +80, -19.90, -98.46, 0.00 +81, -16.57, -74.54, 0.00 +82, -13.73, -56.16, 0.00 +83, -7.93, -29.49, 0.00 +84, -4.05, -13.67, 0.00 +85, 3.26, 9.96, 0.00 +86, 9.23, 25.42, 0.00 +87, 20.02, 49.49, 0.00 +88, 27.36, 60.38, 0.00 +89, 40.28, 78.78, 0.00 +90, 48.10, 82.53, 0.00 +91, 63.94, 95.06, 0.00 +92, 74.19, 93.96, 0.00 +93, 79.91, 84.25, 0.00 +94, 91.23, 77.50, 0.00 +95, 118.76, 77.35, 0.00 +96, 121.73, 55.80, 0.00 +97, 8.09, 2.19, 0.00 +98, 47.54, 6.38, 0.00 +99, -0.51, 0.00, 0.00 +100, 64.46, -8.65, 0.00 +101, 176.83, -47.86, 0.00 +102, 126.10, -57.80, 0.00 +103, 100.61, -65.53, 0.00 +104, 75.11, -63.81, 0.00 +105, 45.32, -47.78, 0.00 +106, 30.04, -38.05, 0.00 +107, 10.85, -16.13, 0.00 +108, -0.49, 0.83, 0.00 +109, -11.86, 23.19, 0.00 +110, -19.44, 42.91, 0.00 +111, -29.85, 73.80, 0.00 +112, -34.62, 95.32, 0.00 +113, -41.75, 127.45, 0.00 +114, -45.17, 152.36, 0.00 +115, -49.69, 184.71, 0.00 +116, -51.82, 212.01, 0.00 +117, -52.68, 237.02, 0.00 +118, -52.19, 258.18, 0.00 +119, -52.00, 283.02, 0.00 +120, -51.05, 306.08, 0.00 +121, -50.11, 331.62, 0.00 +122, -48.48, 355.15, 0.00 +123, -46.74, 380.31, 0.00 +124, -44.35, 402.71, 0.00 +125, -41.93, 427.20, 0.00 +126, -38.98, 448.90, 0.00 +127, -36.03, 473.18, 0.00 +128, -32.62, 494.05, 0.00 +129, -29.21, 517.44, 0.00 +130, -25.44, 537.50, 0.00 +131, -21.63, 559.29, 0.00 +132, -17.61, 578.46, 0.00 +133, -13.52, 598.34, 0.00 +134, -9.33, 616.18, 0.00 +135, -5.09, 635.07, 0.00 +136, -0.80, 651.77, 0.00 +137, 3.49, 668.01, 0.00 +138, 7.77, 682.29, 0.00 +139, 12.02, 696.11, 0.00 +140, 16.21, 708.87, 0.00 +141, 20.32, 720.40, 0.00 +142, 24.34, 730.98, 0.00 +143, 28.29, 741.71, 0.00 +144, 32.10, 750.50, 0.00 +145, 35.82, 759.33, 0.00 +146, 39.35, 766.05, 0.00 +147, 42.78, 772.64, 0.00 +148, 45.99, 776.96, 0.00 +149, 49.11, 781.64, 0.00 +150, 51.99, 784.09, 0.00 +151, 54.79, 786.89, 0.00 +152, 57.33, 787.54, 0.00 +153, 59.66, 786.77, 0.00 +154, 61.73, 783.94, 0.00 +155, 63.04, 773.18, 0.00 +156, 66.19, 786.02, 0.00 +157, 64.98, 748.75, 0.00 +158, 10.80, 121.00, 0.00 +159, -0.83, -9.03, 0.00 +160, 3.07, 32.68, 0.00 +161, 0.90, 9.39, 0.00 +162, -0.01, -0.08, 0.00 +163, 0.07, 0.74, 0.00 +164, -0.07, -0.71, 0.00 +165, -0.36, -3.47, 0.00 +166, -0.79, -7.38, 0.00 +167, -1.28, -11.85, 0.00 +168, -1.77, -16.08, 0.00 +169, -2.26, -20.12, 0.00 +170, -2.79, -24.49, 0.00 +171, -3.30, -28.47, 0.00 +172, -3.86, -32.82, 0.00 +173, -4.38, -36.71, 0.00 +174, -4.94, -40.85, 0.00 +175, -5.46, -44.55, 0.00 +176, -6.01, -48.45, 0.00 +177, -6.51, -51.82, 0.00 +178, -7.02, -55.18, 0.00 +179, -7.47, -58.05, 0.00 +180, -7.91, -60.74, 0.00 +181, -8.28, -62.89, 0.00 +182, -8.62, -64.87, 0.00 +183, -8.90, -66.27, 0.00 +184, -9.11, -67.24, 0.00 +185, -9.24, -67.62, 0.00 +186, -9.33, -67.68, 0.00 +187, -9.30, -66.98, 0.00 +188, -9.21, -65.83, 0.00 +189, -9.04, -64.21, 0.00 +190, -8.77, -61.97, 0.00 +191, -8.40, -59.03, 0.00 +192, -7.93, -55.43, 0.00 +193, -7.33, -51.05, 0.00 +194, -6.65, -46.16, 0.00 +195, -5.84, -40.38, 0.00 +196, -4.91, -33.90, 0.00 +197, -3.87, -26.67, 0.00 +198, -2.64, -18.21, 0.00 diff --git a/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref b/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref index 7fd783fdbb70..10f18610446d 100644 --- a/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref +++ b/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref @@ -1,200 +1,200 @@ 199, -0.96, -0.00, 0.00 -0, -2.91, 20.04, 0.00 -1, -4.11, 28.35, 0.00 -2, -5.18, 35.78, 0.00 -3, -6.13, 42.38, 0.00 -4, -6.94, 48.17, 0.00 -5, -7.61, 52.99, 0.00 -6, -8.17, 57.11, 0.00 -7, -8.59, 60.35, 0.00 -8, -8.90, 62.89, 0.00 -9, -9.09, 64.57, 0.00 -10, -9.19, 65.68, 0.00 -11, -9.17, 66.06, 0.00 -12, -9.08, 65.87, 0.00 -13, -8.87, 64.88, 0.00 -14, -8.59, 63.44, 0.00 -15, -8.22, 61.21, 0.00 -16, -7.81, 58.75, 0.00 -17, -7.31, 55.54, 0.00 -18, -6.77, 52.03, 0.00 -19, -6.18, 48.05, 0.00 -20, -5.54, 43.54, 0.00 -21, -4.85, 38.56, 0.00 -22, -4.12, 33.16, 0.00 -23, -3.34, 27.27, 0.00 -24, -2.55, 21.11, 0.00 -25, -1.73, 14.49, 0.00 -26, -0.89, 7.57, 0.00 -27, -0.02, 0.21, 0.00 -28, 0.84, -7.36, 0.00 -29, 1.70, -15.16, 0.00 -30, 2.57, -23.26, 0.00 -31, 3.43, -31.62, 0.00 -32, 4.29, -40.23, 0.00 -33, 5.12, -48.93, 0.00 -34, 5.95, -57.97, 0.00 -35, 6.74, -67.11, 0.00 -36, 7.54, -76.64, 0.00 -37, 8.29, -86.18, 0.00 -38, 9.02, -96.03, 0.00 -39, 9.71, -105.89, 0.00 -40, 10.32, -115.64, 0.00 -41, 10.88, -125.42, 0.00 -42, 11.33, -134.52, 0.00 -43, 11.67, -143.10, 0.00 -44, 11.89, -151.03, 0.00 -45, 11.97, -157.81, 0.00 -46, 11.83, -162.44, 0.00 -47, 11.29, -162.21, 0.00 -48, 10.84, -163.47, 0.00 -49, 10.25, -163.16, 0.00 -50, 9.08, -153.46, 0.00 -51, 6.10, -110.12, 0.00 -52, 8.72, -169.72, 0.00 -53, 24.65, -522.48, 0.00 -54, 25.91, -605.84, 0.00 -55, 21.86, -573.09, 0.00 -56, 18.83, -565.58, 0.00 -57, 15.79, -559.72, 0.00 -58, 12.61, -551.18, 0.00 -59, 9.36, -541.85, 0.00 -60, 6.06, -531.49, 0.00 -61, 2.72, -519.62, 0.00 -62, -0.63, -506.79, 0.00 -63, -3.95, -492.32, 0.00 -64, -7.23, -477.72, 0.00 -65, -10.43, -461.55, 0.00 -66, -13.55, -444.97, 0.00 -67, -16.52, -426.99, 0.00 -68, -19.36, -408.93, 0.00 -69, -21.98, -389.46, 0.00 -70, -24.46, -370.41, 0.00 -71, -26.62, -349.56, 0.00 -72, -28.59, -329.24, 0.00 -73, -30.11, -306.85, 0.00 -74, -31.49, -285.97, 0.00 -75, -32.34, -263.19, 0.00 -76, -32.98, -241.59, 0.00 -77, -33.01, -218.48, 0.00 -78, -32.78, -196.56, 0.00 -79, -31.73, -172.68, 0.00 -80, -30.58, -151.31, 0.00 -81, -28.51, -128.29, 0.00 -82, -26.22, -107.28, 0.00 -83, -23.00, -85.52, 0.00 -84, -19.42, -65.49, 0.00 -85, -14.71, -44.89, 0.00 -86, -9.40, -25.89, 0.00 -87, -3.54, -8.76, 0.00 -88, 4.27, 9.41, 0.00 -89, 13.63, 26.65, 0.00 -90, 23.53, 40.38, 0.00 -91, 30.76, 45.73, 0.00 -92, 36.09, 45.71, 0.00 -93, 43.48, 45.84, 0.00 -94, 61.05, 51.86, 0.00 -95, 49.16, 32.02, 0.00 -96, 55.00, 25.21, 0.00 -97, 57.99, 15.70, 0.00 -98, 59.74, 8.02, 0.00 -99, 58.62, -0.00, 0.00 -100, 103.03, -13.83, 0.00 -101, 190.93, -51.68, 0.00 -102, 170.14, -77.99, 0.00 -103, 143.57, -93.51, 0.00 -104, 125.13, -106.30, 0.00 -105, 80.00, -84.35, 0.00 -106, 62.71, -79.42, 0.00 -107, 32.87, -48.86, 0.00 -108, 21.18, -36.34, 0.00 -109, -0.06, 0.13, 0.00 -110, -8.43, 18.60, 0.00 -111, -21.17, 52.35, 0.00 -112, -27.02, 74.41, 0.00 -113, -35.55, 108.52, 0.00 -114, -39.68, 133.84, 0.00 -115, -43.82, 162.90, 0.00 -116, -45.71, 187.00, 0.00 -117, -47.84, 215.27, 0.00 -118, -48.41, 239.50, 0.00 -119, -48.73, 265.23, 0.00 -120, -48.26, 289.37, 0.00 -121, -47.44, 313.96, 0.00 -122, -46.10, 337.69, 0.00 -123, -44.41, 361.33, 0.00 -124, -42.27, 383.81, 0.00 -125, -39.86, 406.13, 0.00 -126, -37.22, 428.63, 0.00 -127, -34.23, 449.48, 0.00 -128, -31.09, 470.82, 0.00 -129, -27.67, 490.26, 0.00 -130, -24.15, 510.22, 0.00 -131, -20.47, 529.16, 0.00 -132, -16.69, 548.12, 0.00 -133, -12.79, 565.92, 0.00 -134, -8.83, 583.35, 0.00 -135, -4.81, 599.76, 0.00 -136, -0.76, 615.87, 0.00 -137, 3.30, 631.04, 0.00 -138, 7.35, 644.84, 0.00 -139, 11.36, 657.96, 0.00 -140, 15.33, 670.28, 0.00 -141, 19.21, 681.06, 0.00 -142, 23.02, 691.20, 0.00 -143, 26.71, 700.31, 0.00 -144, 30.30, 708.38, 0.00 -145, 33.71, 714.71, 0.00 -146, 37.02, 720.63, 0.00 -147, 40.16, 725.20, 0.00 -148, 43.14, 728.78, 0.00 -149, 45.94, 731.11, 0.00 -150, 48.55, 732.27, 0.00 -151, 50.85, 730.39, 0.00 -152, 54.67, 750.98, 0.00 -153, 51.36, 677.32, 0.00 -154, 7.16, 90.97, 0.00 -155, -1.26, -15.46, 0.00 -156, 2.80, 33.26, 0.00 -157, 3.27, 37.72, 0.00 -158, 3.54, 39.66, 0.00 -159, 3.81, 41.53, 0.00 -160, 3.88, 41.25, 0.00 -161, 3.69, 38.38, 0.00 -162, 3.47, 35.31, 0.00 -163, 3.07, 30.54, 0.00 -164, 2.67, 26.04, 0.00 -165, 2.11, 20.15, 0.00 -166, 1.57, 14.73, 0.00 -167, 0.90, 8.32, 0.00 -168, 0.29, 2.59, 0.00 -169, -0.41, -3.70, 0.00 -170, -1.08, -9.45, 0.00 -171, -1.85, -16.01, 0.00 -172, -2.55, -21.66, 0.00 -173, -3.32, -27.82, 0.00 -174, -4.00, -33.09, 0.00 -175, -4.72, -38.54, 0.00 -176, -5.37, -43.26, 0.00 -177, -6.02, -47.89, 0.00 -178, -6.59, -51.77, 0.00 -179, -7.15, -55.52, 0.00 -180, -7.62, -58.52, 0.00 -181, -8.12, -61.69, 0.00 -182, -8.50, -63.91, 0.00 -183, -8.88, -66.15, 0.00 -184, -9.15, -67.52, 0.00 -185, -9.40, -68.80, 0.00 -186, -9.53, -69.15, 0.00 -187, -9.60, -69.17, 0.00 -188, -9.54, -68.24, 0.00 -189, -9.36, -66.47, 0.00 -190, -9.10, -64.24, 0.00 -191, -8.71, -61.18, 0.00 -192, -8.23, -57.52, 0.00 -193, -7.62, -53.07, 0.00 -194, -6.93, -48.05, 0.00 -195, -6.09, -42.14, 0.00 -196, -5.14, -35.49, 0.00 -197, -4.05, -27.93, 0.00 -198, -2.82, -19.39, 0.00 +0, -2.88, 19.80, 0.00 +1, -4.08, 28.11, 0.00 +2, -5.13, 35.44, 0.00 +3, -6.05, 41.88, 0.00 +4, -6.85, 47.52, 0.00 +5, -7.49, 52.16, 0.00 +6, -8.03, 56.17, 0.00 +7, -8.44, 59.32, 0.00 +8, -8.75, 61.79, 0.00 +9, -8.93, 63.46, 0.00 +10, -9.03, 64.59, 0.00 +11, -9.03, 65.07, 0.00 +12, -8.95, 64.97, 0.00 +13, -8.78, 64.26, 0.00 +14, -8.54, 63.01, 0.00 +15, -8.22, 61.22, 0.00 +16, -7.83, 58.91, 0.00 +17, -7.39, 56.13, 0.00 +18, -6.87, 52.74, 0.00 +19, -6.30, 48.95, 0.00 +20, -5.67, 44.59, 0.00 +21, -5.01, 39.84, 0.00 +22, -4.30, 34.65, 0.00 +23, -3.56, 29.06, 0.00 +24, -2.79, 23.05, 0.00 +25, -1.98, 16.57, 0.00 +26, -1.15, 9.76, 0.00 +27, -0.29, 2.47, 0.00 +28, 0.56, -4.91, 0.00 +29, 1.43, -12.74, 0.00 +30, 2.26, -20.48, 0.00 +31, 3.11, -28.64, 0.00 +32, 3.92, -36.81, 0.00 +33, 4.73, -45.24, 0.00 +34, 5.51, -53.68, 0.00 +35, 6.26, -62.26, 0.00 +36, 6.99, -70.99, 0.00 +37, 7.67, -79.69, 0.00 +38, 8.31, -88.48, 0.00 +39, 8.89, -97.03, 0.00 +40, 9.43, -105.63, 0.00 +41, 9.85, -113.54, 0.00 +42, 10.26, -121.88, 0.00 +43, 10.54, -129.31, 0.00 +44, 10.77, -136.82, 0.00 +45, 10.78, -142.12, 0.00 +46, 10.74, -147.53, 0.00 +47, 10.54, -151.33, 0.00 +48, 10.38, -156.60, 0.00 +49, 9.75, -155.13, 0.00 +50, 8.45, -142.76, 0.00 +51, 6.40, -115.54, 0.00 +52, 9.86, -191.93, 0.00 +53, 24.75, -524.72, 0.00 +54, 26.02, -608.43, 0.00 +55, 21.64, -567.32, 0.00 +56, 18.59, -558.24, 0.00 +57, 15.69, -556.22, 0.00 +58, 12.54, -548.32, 0.00 +59, 9.29, -538.17, 0.00 +60, 6.01, -527.70, 0.00 +61, 2.70, -516.54, 0.00 +62, -0.62, -503.90, 0.00 +63, -3.93, -489.84, 0.00 +64, -7.19, -475.37, 0.00 +65, -10.40, -460.31, 0.00 +66, -13.51, -443.91, 0.00 +67, -16.52, -427.17, 0.00 +68, -19.37, -409.22, 0.00 +69, -22.09, -391.43, 0.00 +70, -24.59, -372.46, 0.00 +71, -26.96, -354.04, 0.00 +72, -29.02, -334.24, 0.00 +73, -30.98, -315.64, 0.00 +74, -32.47, -294.84, 0.00 +75, -33.84, -275.37, 0.00 +76, -34.68, -254.05, 0.00 +77, -35.53, -235.15, 0.00 +78, -35.70, -214.07, 0.00 +79, -35.83, -195.02, 0.00 +80, -35.05, -173.40, 0.00 +81, -34.19, -153.83, 0.00 +82, -32.26, -131.97, 0.00 +83, -30.00, -111.54, 0.00 +84, -26.57, -89.60, 0.00 +85, -22.26, -67.94, 0.00 +86, -16.65, -45.84, 0.00 +87, -10.42, -25.77, 0.00 +88, -1.99, -4.39, 0.00 +89, 7.81, 15.28, 0.00 +90, 17.06, 29.26, 0.00 +91, 27.28, 40.55, 0.00 +92, 36.43, 46.13, 0.00 +93, 50.49, 53.24, 0.00 +94, 63.17, 53.66, 0.00 +95, 73.70, 48.01, 0.00 +96, 73.02, 33.47, 0.00 +97, 67.53, 18.28, 0.00 +98, 54.75, 7.35, 0.00 +99, 17.21, -0.00, 0.00 +100, 52.61, -7.06, 0.00 +101, 93.59, -25.33, 0.00 +102, 62.72, -28.75, 0.00 +103, 27.14, -17.68, 0.00 +104, 23.11, -19.64, 0.00 +105, 5.43, -5.72, 0.00 +106, -0.12, 0.15, 0.00 +107, -12.46, 18.52, 0.00 +108, -19.25, 33.03, 0.00 +109, -25.52, 49.90, 0.00 +110, -31.57, 69.68, 0.00 +111, -35.49, 87.76, 0.00 +112, -39.31, 108.23, 0.00 +113, -41.08, 125.40, 0.00 +114, -43.50, 146.70, 0.00 +115, -44.37, 164.95, 0.00 +116, -45.79, 187.35, 0.00 +117, -46.14, 207.61, 0.00 +118, -46.53, 230.18, 0.00 +119, -46.07, 250.78, 0.00 +120, -45.67, 273.82, 0.00 +121, -44.43, 294.06, 0.00 +122, -43.29, 317.15, 0.00 +123, -41.64, 338.83, 0.00 +124, -39.78, 361.20, 0.00 +125, -37.54, 382.56, 0.00 +126, -35.17, 404.98, 0.00 +127, -32.45, 426.14, 0.00 +128, -29.56, 447.62, 0.00 +129, -26.45, 468.60, 0.00 +130, -23.16, 489.20, 0.00 +131, -19.70, 509.17, 0.00 +132, -16.09, 528.37, 0.00 +133, -12.37, 547.12, 0.00 +134, -8.55, 564.91, 0.00 +135, -4.66, 581.83, 0.00 +136, -0.74, 598.49, 0.00 +137, 3.21, 613.80, 0.00 +138, 7.16, 628.25, 0.00 +139, 11.09, 642.08, 0.00 +140, 14.98, 654.91, 0.00 +141, 18.80, 666.53, 0.00 +142, 22.56, 677.39, 0.00 +143, 26.23, 687.57, 0.00 +144, 29.78, 696.30, 0.00 +145, 33.20, 703.83, 0.00 +146, 36.50, 710.42, 0.00 +147, 39.65, 715.98, 0.00 +148, 42.62, 720.14, 0.00 +149, 45.50, 724.11, 0.00 +150, 48.13, 725.97, 0.00 +151, 50.35, 723.19, 0.00 +152, 54.49, 748.60, 0.00 +153, 50.92, 671.51, 0.00 +154, 6.40, 81.25, 0.00 +155, -0.48, -5.87, 0.00 +156, 2.99, 35.56, 0.00 +157, 3.29, 37.89, 0.00 +158, 3.41, 38.23, 0.00 +159, 3.70, 40.39, 0.00 +160, 3.68, 39.19, 0.00 +161, 3.67, 38.17, 0.00 +162, 3.42, 34.77, 0.00 +163, 3.16, 31.44, 0.00 +164, 2.74, 26.72, 0.00 +165, 2.33, 22.31, 0.00 +166, 1.78, 16.69, 0.00 +167, 1.24, 11.40, 0.00 +168, 0.60, 5.42, 0.00 +169, -0.01, -0.10, 0.00 +170, -0.70, -6.14, 0.00 +171, -1.32, -11.41, 0.00 +172, -2.03, -17.30, 0.00 +173, -2.66, -22.31, 0.00 +174, -3.36, -27.82, 0.00 +175, -3.98, -32.46, 0.00 +176, -4.65, -37.48, 0.00 +177, -5.24, -41.66, 0.00 +178, -5.86, -46.06, 0.00 +179, -6.39, -49.65, 0.00 +180, -6.93, -53.25, 0.00 +181, -7.36, -55.95, 0.00 +182, -7.80, -58.69, 0.00 +183, -8.13, -60.57, 0.00 +184, -8.45, -62.36, 0.00 +185, -8.68, -63.48, 0.00 +186, -8.84, -64.16, 0.00 +187, -8.92, -64.26, 0.00 +188, -8.92, -63.79, 0.00 +189, -8.83, -62.72, 0.00 +190, -8.64, -61.01, 0.00 +191, -8.33, -58.54, 0.00 +192, -7.92, -55.37, 0.00 +193, -7.37, -51.28, 0.00 +194, -6.72, -46.61, 0.00 +195, -5.92, -40.93, 0.00 +196, -4.99, -34.48, 0.00 +197, -3.97, -27.33, 0.00 +198, -2.78, -19.11, 0.00 diff --git a/TestCases/radiation/p1adjoint/of_grad_cd.csv.ref b/TestCases/radiation/p1adjoint/of_grad_cd.csv.ref index 4ecddfa3c3a8..c34515958522 100644 --- a/TestCases/radiation/p1adjoint/of_grad_cd.csv.ref +++ b/TestCases/radiation/p1adjoint/of_grad_cd.csv.ref @@ -1,51 +1,51 @@ -"VARIABLE" , "GRADIENT" , "FINDIFF_STEP" -0 , -0.000958193 , 0.001 -1 , -0.00432732 , 0.001 -2 , -0.0111443 , 0.001 -3 , -0.0214515 , 0.001 -4 , -0.0343997 , 0.001 -5 , -0.0489968 , 0.001 -6 , -0.0643023 , 0.001 -7 , -0.0791818 , 0.001 -8 , -0.092416 , 0.001 -9 , -0.103017 , 0.001 -10 , -0.11024 , 0.001 -11 , -0.113392 , 0.001 -12 , -0.111913 , 0.001 -13 , -0.105766 , 0.001 -14 , -0.0957015 , 0.001 -15 , -0.0830517 , 0.001 -16 , -0.0692182 , 0.001 -17 , -0.0553101 , 0.001 -18 , -0.042129 , 0.001 -19 , -0.0303137 , 0.001 -20 , -0.0203813 , 0.001 -21 , -0.0126225 , 0.001 -22 , -0.0070044 , 0.001 -23 , -0.00322725 , 0.001 -24 , -0.000951896 , 0.001 -25 , 0.00128802 , 0.001 -26 , 0.00517739 , 0.001 -27 , 0.0125547 , 0.001 -28 , 0.0234674 , 0.001 -29 , 0.0370465 , 0.001 -30 , 0.0521957 , 0.001 -31 , 0.0678379 , 0.001 -32 , 0.0827257 , 0.001 -33 , 0.0955787 , 0.001 -34 , 0.105412 , 0.001 -35 , 0.111548 , 0.001 -36 , 0.113415 , 0.001 -37 , 0.110605 , 0.001 -38 , 0.103242 , 0.001 -39 , 0.0922244 , 0.001 -40 , 0.0789963 , 0.001 -41 , 0.0650234 , 0.001 -42 , 0.0514199 , 0.001 -43 , 0.0389312 , 0.001 -44 , 0.0280831 , 0.001 -45 , 0.0192316 , 0.001 -46 , 0.0124655 , 0.001 -47 , 0.00752232 , 0.001 -48 , 0.00390719 , 0.001 -49 , 0.00131195 , 0.001 +"VARIABLE" , "GRADIENT" , "FINDIFF_STEP" +0 , -0.000958189 , 0.001 +1 , -0.00432731 , 0.001 +2 , -0.0111443 , 0.001 +3 , -0.0214515 , 0.001 +4 , -0.0343997 , 0.001 +5 , -0.0489968 , 0.001 +6 , -0.0643023 , 0.001 +7 , -0.0791818 , 0.001 +8 , -0.092416 , 0.001 +9 , -0.103017 , 0.001 +10 , -0.11024 , 0.001 +11 , -0.113392 , 0.001 +12 , -0.111913 , 0.001 +13 , -0.105766 , 0.001 +14 , -0.0957015 , 0.001 +15 , -0.0830517 , 0.001 +16 , -0.0692182 , 0.001 +17 , -0.0553101 , 0.001 +18 , -0.042129 , 0.001 +19 , -0.0303137 , 0.001 +20 , -0.0203813 , 0.001 +21 , -0.0126226 , 0.001 +22 , -0.0070044 , 0.001 +23 , -0.00322725 , 0.001 +24 , -0.000951897 , 0.001 +25 , 0.00128802 , 0.001 +26 , 0.00517738 , 0.001 +27 , 0.0125547 , 0.001 +28 , 0.0234674 , 0.001 +29 , 0.0370465 , 0.001 +30 , 0.0521957 , 0.001 +31 , 0.0678379 , 0.001 +32 , 0.0827257 , 0.001 +33 , 0.0955787 , 0.001 +34 , 0.105412 , 0.001 +35 , 0.111548 , 0.001 +36 , 0.113415 , 0.001 +37 , 0.110605 , 0.001 +38 , 0.103242 , 0.001 +39 , 0.0922244 , 0.001 +40 , 0.0789963 , 0.001 +41 , 0.0650234 , 0.001 +42 , 0.0514199 , 0.001 +43 , 0.0389312 , 0.001 +44 , 0.0280831 , 0.001 +45 , 0.0192316 , 0.001 +46 , 0.0124655 , 0.001 +47 , 0.00752231 , 0.001 +48 , 0.00390719 , 0.001 +49 , 0.00131195 , 0.001 diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index 830ce48e4a56..88a3cfe2b925 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -64,7 +64,7 @@ def main(): invwedge.cfg_dir = "nonequilibrium/invwedge" invwedge.cfg_file = "invwedge_ausm.cfg" invwedge.test_iter = 10 - invwedge.test_vals = [-1.073693, -1.598456, -18.298997, -18.626405, -18.572419, 2.241766, 1.868557, 5.286079, 0.843747] + invwedge.test_vals = [-1.073689, -1.598452, -18.299910, -18.627322, -18.573334, 2.241771, 1.868566, 5.286082, 0.843751] invwedge.test_vals_aarch64 = [-1.073699, -1.598462, -18.299723, -18.627132, -18.573146, 2.241760, 1.868575, 5.286072, 0.843741] test_list.append(invwedge) @@ -93,7 +93,7 @@ def main(): channel.cfg_dir = "euler/channel" channel.cfg_file = "inv_channel_RK.cfg" channel.test_iter = 10 - channel.test_vals = [-1.969337, 3.565024, 0.000246, 0.160624] + channel.test_vals = [-1.969337, 3.565024, 0.000242, 0.160624] test_list.append(channel) # NACA0012 @@ -101,7 +101,7 @@ def main(): naca0012.cfg_dir = "euler/naca0012" naca0012.cfg_file = "inv_NACA0012_Roe.cfg" naca0012.test_iter = 20 - naca0012.test_vals = [-4.506451, -3.937611, 0.297774, 0.025416] + naca0012.test_vals = [-4.489721, -3.937702, 0.293347, 0.025228] test_list.append(naca0012) # Supersonic wedge @@ -117,7 +117,7 @@ def main(): oneram6.cfg_dir = "euler/oneram6" oneram6.cfg_file = "inv_ONERAM6.cfg" oneram6.test_iter = 10 - oneram6.test_vals = [-11.513859, -10.984761, 0.280800, 0.008623] + oneram6.test_vals = [-7.649980, -7.032700, 0.280803, 0.008625] oneram6.timeout = 9600 test_list.append(oneram6) @@ -126,7 +126,7 @@ def main(): fixedCL_naca0012.cfg_dir = "fixed_cl/naca0012" fixedCL_naca0012.cfg_file = "inv_NACA0012.cfg" fixedCL_naca0012.test_iter = 10 - fixedCL_naca0012.test_vals = [-3.962510, 1.570618, 0.301016, 0.019477] + fixedCL_naca0012.test_vals = [-3.949168, 1.584810, 0.301017, 0.019479] test_list.append(fixedCL_naca0012) # Polar sweep of the inviscid NACA0012 @@ -135,7 +135,7 @@ def main(): polar_naca0012.cfg_file = "inv_NACA0012.cfg" polar_naca0012.polar = True polar_naca0012.test_iter = 10 - polar_naca0012.test_vals = [-1.278626, 4.165535, 0.004509, 0.083680] + polar_naca0012.test_vals = [-1.273417, 4.171509, -0.002852, 0.084424] polar_naca0012.test_vals_aarch64 = [-1.063447, 4.401847, 0.000291, 0.031696] polar_naca0012.command = TestCase.Command(exec = "compute_polar.py", param = "-n 1 -i 11") # flaky test on arm64 @@ -166,7 +166,7 @@ def main(): flatplate.cfg_dir = "navierstokes/flatplate" flatplate.cfg_file = "lam_flatplate.cfg" flatplate.test_iter = 20 - flatplate.test_vals = [-5.492595, -0.012547, 0.002524, 0.011875, 2.361500, -2.349600, 0.000000, 0.000000] + flatplate.test_vals = [-5.499362, -0.019046, 0.002526, 0.011870, 2.361500, -2.349600, 0.000000, 0.000000] test_list.append(flatplate) # Laminar cylinder (steady) @@ -174,7 +174,7 @@ def main(): cylinder.cfg_dir = "navierstokes/cylinder" cylinder.cfg_file = "lam_cylinder.cfg" cylinder.test_iter = 25 - cylinder.test_vals = [-8.480930, -3.006218, -0.028387, 1.634020, 0.000000] + cylinder.test_vals = [-8.508933, -3.034987, -0.014846, 1.644215, 0.000000] test_list.append(cylinder) # Laminar cylinder (low Mach correction) @@ -182,7 +182,7 @@ def main(): cylinder_lowmach.cfg_dir = "navierstokes/cylinder" cylinder_lowmach.cfg_file = "cylinder_lowmach.cfg" cylinder_lowmach.test_iter = 25 - cylinder_lowmach.test_vals = [-6.447834, -0.986069, 0.825992, 65.209800, 0.000000] + cylinder_lowmach.test_vals = [-6.453096, -0.991328, 0.722165, 66.048089, 0.000000] test_list.append(cylinder_lowmach) # 2D Poiseuille flow (body force driven with periodic inlet / outlet) @@ -198,7 +198,7 @@ def main(): poiseuille_profile.cfg_dir = "navierstokes/poiseuille" poiseuille_profile.cfg_file = "profile_poiseuille.cfg" poiseuille_profile.test_iter = 10 - poiseuille_profile.test_vals = [-12.003748, -7.573681, -0.000000, 2.089953] + poiseuille_profile.test_vals = [-12.003743, -7.573444, -0.000000, 2.089953] poiseuille_profile.test_vals_aarch64 = [-12.009012, -7.262299, -0.000000, 2.089953] #last 4 columns test_list.append(poiseuille_profile) @@ -218,7 +218,7 @@ def main(): rae2822_sa.cfg_dir = "rans/rae2822" rae2822_sa.cfg_file = "turb_SA_RAE2822.cfg" rae2822_sa.test_iter = 20 - rae2822_sa.test_vals = [-2.187576, -5.308498, 0.389360, 0.077088, 0.000000] + rae2822_sa.test_vals = [-2.187051, -5.315525, 0.382556, 0.077937, 0.000000] test_list.append(rae2822_sa) # RAE2822 SST @@ -226,7 +226,7 @@ def main(): rae2822_sst.cfg_dir = "rans/rae2822" rae2822_sst.cfg_file = "turb_SST_RAE2822.cfg" rae2822_sst.test_iter = 20 - rae2822_sst.test_vals = [-1.028239, 5.864395, 0.357230, 0.074731, 0.000000] + rae2822_sst.test_vals = [-1.028038, 5.869011, 0.367576, 0.075890, 0.000000] test_list.append(rae2822_sst) # RAE2822 SST_SUST @@ -234,7 +234,7 @@ def main(): rae2822_sst_sust.cfg_dir = "rans/rae2822" rae2822_sst_sust.cfg_file = "turb_SST_SUST_RAE2822.cfg" rae2822_sst_sust.test_iter = 20 - rae2822_sst_sust.test_vals = [-2.487567, 5.864384, 0.357230, 0.074731] + rae2822_sst_sust.test_vals = [-2.486848, 5.868998, 0.367576, 0.075890] test_list.append(rae2822_sst_sust) # Flat plate @@ -242,7 +242,7 @@ def main(): turb_flatplate.cfg_dir = "rans/flatplate" turb_flatplate.cfg_file = "turb_SA_flatplate.cfg" turb_flatplate.test_iter = 20 - turb_flatplate.test_vals = [-4.958238, -7.438031, -0.187473, 0.015059] + turb_flatplate.test_vals = [-4.958115, -7.438257, -0.187473, 0.015056] test_list.append(turb_flatplate) # FLAT PLATE, WALL FUNCTIONS, COMPRESSIBLE SST @@ -283,7 +283,7 @@ def main(): turb_naca0012_sa.cfg_dir = "rans/naca0012" turb_naca0012_sa.cfg_file = "turb_NACA0012_sa.cfg" turb_naca0012_sa.test_iter = 5 - turb_naca0012_sa.test_vals = [-12.037366, -16.384159, 1.080346, 0.018385, 20.000000, -3.456447, 20.000000, -4.641258, 0.000000] + turb_naca0012_sa.test_vals = [-12.037309, -16.384159, 1.080346, 0.018385, 20.000000, -3.456846, 20.000000, -4.641251, 0.000000] turb_naca0012_sa.test_vals_aarch64 = [-12.037297, -16.384158, 1.080346, 0.018385, 20.000000, -3.455886, 20.000000, -4.641247, 0.000000] turb_naca0012_sa.timeout = 3200 test_list.append(turb_naca0012_sa) @@ -293,7 +293,7 @@ def main(): turb_naca0012_sst.cfg_dir = "rans/naca0012" turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" turb_naca0012_sst.test_iter = 10 - turb_naca0012_sst.test_vals = [-12.094425, -15.251083, -5.906366, 1.070413, 0.015775, -3.178933, 0.000000] + turb_naca0012_sst.test_vals = [-12.094445, -15.251083, -5.906366, 1.070413, 0.015775, -3.178548, 0.000000] turb_naca0012_sst.test_vals_aarch64 = [-12.076068, -15.246740, -5.861280, 1.070036, 0.015841, -3.297854, 0.000000] turb_naca0012_sst.timeout = 3200 test_list.append(turb_naca0012_sst) @@ -312,7 +312,7 @@ def main(): turb_naca0012_sst_sust_restart.cfg_dir = "rans/naca0012" turb_naca0012_sst_sust_restart.cfg_file = "turb_NACA0012_sst_sust.cfg" turb_naca0012_sst_sust_restart.test_iter = 10 - turb_naca0012_sst_sust_restart.test_vals = [-12.080423, -14.837169, -5.733461, 1.000893, 0.019109, -2.634145] + turb_naca0012_sst_sust_restart.test_vals = [-12.080455, -14.837169, -5.733461, 1.000893, 0.019109, -2.634140] turb_naca0012_sst_sust_restart.test_vals_aarch64 = [-12.074189, -14.836725, -5.732398, 1.000050, 0.019144, -3.315560] turb_naca0012_sst_sust_restart.timeout = 3200 test_list.append(turb_naca0012_sst_sust_restart) @@ -344,7 +344,7 @@ def main(): axi_rans_air_nozzle_restart.cfg_dir = "axisymmetric_rans/air_nozzle" axi_rans_air_nozzle_restart.cfg_file = "air_nozzle_restart.cfg" axi_rans_air_nozzle_restart.test_iter = 10 - axi_rans_air_nozzle_restart.test_vals = [-11.053602, -5.329000, -8.835107, -4.056879, 0.000000] + axi_rans_air_nozzle_restart.test_vals = [-11.054279, -5.328901, -8.835585, -4.056810, 0.000000] axi_rans_air_nozzle_restart.test_vals_aarch64 = [-14.143715, -9.170705, -10.848554, -5.776746, 0.000000] axi_rans_air_nozzle_restart.tol = 0.0001 test_list.append(axi_rans_air_nozzle_restart) @@ -359,7 +359,7 @@ def main(): turb_naca0012_sst_restart_mg.cfg_file = "turb_NACA0012_sst_multigrid_restart.cfg" turb_naca0012_sst_restart_mg.test_iter = 50 turb_naca0012_sst_restart_mg.ntest_vals = 5 - turb_naca0012_sst_restart_mg.test_vals = [-6.610290, -5.081422, 0.810881, -0.008844, 0.077940] + turb_naca0012_sst_restart_mg.test_vals = [-6.610405, -5.081422, 0.810881, -0.008846, 0.077934] turb_naca0012_sst_restart_mg.timeout = 3200 turb_naca0012_sst_restart_mg.tol = 0.000001 test_list.append(turb_naca0012_sst_restart_mg) @@ -380,7 +380,7 @@ def main(): inc_euler_naca0012.cfg_dir = "incomp_euler/naca0012" inc_euler_naca0012.cfg_file = "incomp_NACA0012.cfg" inc_euler_naca0012.test_iter = 20 - inc_euler_naca0012.test_vals = [-5.968755, -5.003709, 0.522550, 0.008867] + inc_euler_naca0012.test_vals = [-5.988713, -5.020635, 0.522968, 0.008854] test_list.append(inc_euler_naca0012) # C-D nozzle with pressure inlet and mass flow outlet @@ -407,7 +407,7 @@ def main(): inc_lam_cylinder.cfg_dir = "incomp_navierstokes/cylinder" inc_lam_cylinder.cfg_file = "incomp_cylinder.cfg" inc_lam_cylinder.test_iter = 10 - inc_lam_cylinder.test_vals = [-4.159153, -3.569142, 0.011445, 4.936802] + inc_lam_cylinder.test_vals = [-4.161215, -3.573002, 0.019888, 4.945923] test_list.append(inc_lam_cylinder) # Buoyancy-driven cavity @@ -423,7 +423,7 @@ def main(): inc_poly_cylinder.cfg_dir = "incomp_navierstokes/cylinder" inc_poly_cylinder.cfg_file = "poly_cylinder.cfg" inc_poly_cylinder.test_iter = 20 - inc_poly_cylinder.test_vals = [-8.232551, -2.412865, 0.010163, 1.895333, -172.760000] + inc_poly_cylinder.test_vals = [-8.081227, -2.399756, 0.009794, 1.900988, -172.970000] test_list.append(inc_poly_cylinder) # X-coarse laminar bend as a mixed element CGNS test @@ -431,7 +431,7 @@ def main(): inc_lam_bend.cfg_dir = "incomp_navierstokes/bend" inc_lam_bend.cfg_file = "lam_bend.cfg" inc_lam_bend.test_iter = 10 - inc_lam_bend.test_vals = [-3.647474, -3.230291, -0.016108, 1.085750] + inc_lam_bend.test_vals = [-3.639664, -3.218039, -0.016067, 1.090645] test_list.append(inc_lam_bend) ############################ @@ -586,7 +586,7 @@ def main(): contadj_naca0012.cfg_dir = "cont_adj_euler/naca0012" contadj_naca0012.cfg_file = "inv_NACA0012.cfg" contadj_naca0012.test_iter = 5 - contadj_naca0012.test_vals = [-9.531049, -15.087710, -0.726250, 0.020280] + contadj_naca0012.test_vals = [-9.531733, -15.088205, -0.726250, 0.020280] contadj_naca0012.tol = 0.001 test_list.append(contadj_naca0012) @@ -595,7 +595,7 @@ def main(): contadj_oneram6.cfg_dir = "cont_adj_euler/oneram6" contadj_oneram6.cfg_file = "inv_ONERAM6.cfg" contadj_oneram6.test_iter = 10 - contadj_oneram6.test_vals = [-12.080232, -12.641294, -1.086100, 0.007556] + contadj_oneram6.test_vals = [-12.083706, -12.645329, -1.086100, 0.007556] test_list.append(contadj_oneram6) # Inviscid WEDGE: tests averaged outflow total pressure adjoint @@ -611,7 +611,7 @@ def main(): contadj_fixedCL_naca0012.cfg_dir = "fixed_cl/naca0012" contadj_fixedCL_naca0012.cfg_file = "inv_NACA0012_ContAdj.cfg" contadj_fixedCL_naca0012.test_iter = 100 - contadj_fixedCL_naca0012.test_vals = [1.381080, -4.043374, -0.033478, 0.003350] + contadj_fixedCL_naca0012.test_vals = [1.378116, -4.047513, -0.030259, 0.003488] test_list.append(contadj_fixedCL_naca0012) ################################### @@ -630,7 +630,7 @@ def main(): contadj_ns_cylinder.cfg_dir = "cont_adj_navierstokes/cylinder" contadj_ns_cylinder.cfg_file = "lam_cylinder.cfg" contadj_ns_cylinder.test_iter = 20 - contadj_ns_cylinder.test_vals = [-3.628790, -9.082444, 2.056700, -0.000000] + contadj_ns_cylinder.test_vals = [-3.628460, -9.081344, 2.056700, -0.000000] test_list.append(contadj_ns_cylinder) # Adjoint laminar naca0012 subsonic @@ -674,7 +674,7 @@ def main(): contadj_rans_rae2822.cfg_dir = "cont_adj_rans/rae2822" contadj_rans_rae2822.cfg_file = "turb_SA_RAE2822.cfg" contadj_rans_rae2822.test_iter = 20 - contadj_rans_rae2822.test_vals = [-5.399819, -10.904997, -0.212470, 0.005448] + contadj_rans_rae2822.test_vals = [-5.399778, -10.904866, -0.212470, 0.005448] test_list.append(contadj_rans_rae2822) ############################# @@ -686,7 +686,7 @@ def main(): turb_naca0012_1c.cfg_dir = "rans_uq/naca0012" turb_naca0012_1c.cfg_file = "turb_NACA0012_uq_1c.cfg" turb_naca0012_1c.test_iter = 10 - turb_naca0012_1c.test_vals = [-4.981677, 1.343533, 0.597773, 0.017451] + turb_naca0012_1c.test_vals = [-4.981181, 1.343583, 0.597121, 0.017223] turb_naca0012_1c.test_vals_aarch64 = [-4.992791, 1.342873, 0.557941, 0.003269] test_list.append(turb_naca0012_1c) @@ -695,7 +695,7 @@ def main(): turb_naca0012_2c.cfg_dir = "rans_uq/naca0012" turb_naca0012_2c.cfg_file = "turb_NACA0012_uq_2c.cfg" turb_naca0012_2c.test_iter = 10 - turb_naca0012_2c.test_vals = [-5.482890, 1.261920, 0.441111, -0.029535] + turb_naca0012_2c.test_vals = [-5.482907, 1.262063, 0.459671, -0.026410] test_list.append(turb_naca0012_2c) # NACA0012 3c @@ -711,7 +711,7 @@ def main(): turb_naca0012_p1c1.cfg_dir = "rans_uq/naca0012" turb_naca0012_p1c1.cfg_file = "turb_NACA0012_uq_p1c1.cfg" turb_naca0012_p1c1.test_iter = 10 - turb_naca0012_p1c1.test_vals = [-5.127673, 1.284889, 0.779311, 0.086115] + turb_naca0012_p1c1.test_vals = [-5.127701, 1.284875, 0.779301, 0.086111] turb_naca0012_p1c1.test_vals_aarch64 = [-5.119942, 1.283920, 0.486264, -0.021518] test_list.append(turb_naca0012_p1c1) @@ -720,7 +720,7 @@ def main(): turb_naca0012_p1c2.cfg_dir = "rans_uq/naca0012" turb_naca0012_p1c2.cfg_file = "turb_NACA0012_uq_p1c2.cfg" turb_naca0012_p1c2.test_iter = 10 - turb_naca0012_p1c2.test_vals = [-5.553904, 1.235086, 0.521256, -0.004467] + turb_naca0012_p1c2.test_vals = [-5.553987, 1.235071, 0.521197, -0.004487] test_list.append(turb_naca0012_p1c2) ###################################### @@ -752,7 +752,7 @@ def main(): rot_naca0012.cfg_dir = "rotating/naca0012" rot_naca0012.cfg_file = "rot_NACA0012.cfg" rot_naca0012.test_iter = 25 - rot_naca0012.test_vals = [-1.291080, 4.244879, -0.000543, 0.112765] + rot_naca0012.test_vals = [-1.281672, 4.255371, -0.001082, 0.112631] test_list.append(rot_naca0012) # Lid-driven cavity @@ -768,7 +768,7 @@ def main(): spinning_cylinder.cfg_dir = "moving_wall/spinning_cylinder" spinning_cylinder.cfg_file = "spinning_cylinder.cfg" spinning_cylinder.test_iter = 25 - spinning_cylinder.test_vals = [-7.543538, -2.078580, 1.956630, 1.859963] + spinning_cylinder.test_vals = [-7.549394, -2.082578, 1.841595, 1.853229] test_list.append(spinning_cylinder) ###################################### @@ -789,7 +789,7 @@ def main(): sine_gust.cfg_dir = "gust" sine_gust.cfg_file = "inv_gust_NACA0012.cfg" sine_gust.test_iter = 5 - sine_gust.test_vals = [-1.977498, 3.481817, -0.010134, -0.004283] + sine_gust.test_vals = [-1.977498, 3.481817, -0.010301, -0.004334] sine_gust.unsteady = True test_list.append(sine_gust) @@ -798,7 +798,7 @@ def main(): aeroelastic.cfg_dir = "aeroelastic" aeroelastic.cfg_file = "aeroelastic_NACA64A010.cfg" aeroelastic.test_iter = 2 - aeroelastic.test_vals = [-1.876626, 4.021083, 0.081596, 0.027684, -0.001638, -0.000130, -1.140510] + aeroelastic.test_vals = [-1.876631, 4.021073, 0.081373, 0.027542, -0.001642, -0.000127, -1.133902] aeroelastic.unsteady = True test_list.append(aeroelastic) @@ -824,7 +824,7 @@ def main(): unst_pitching_naca64a010_rans.cfg_dir = "unsteady/pitching_naca64a010_rans" unst_pitching_naca64a010_rans.cfg_file = "turb_NACA64A010.cfg" unst_pitching_naca64a010_rans.test_iter = 2 - unst_pitching_naca64a010_rans.test_vals = [-1.299045, -3.951366, 0.010128, 0.008245] + unst_pitching_naca64a010_rans.test_vals = [-1.299045, -3.951363, 0.010176, 0.008237] unst_pitching_naca64a010_rans.unsteady = True test_list.append(unst_pitching_naca64a010_rans) # unsteady pitching NACA64A010, Euler @@ -832,7 +832,7 @@ def main(): unst_pitching_naca64a010_euler.cfg_dir = "unsteady/pitching_naca64a010_euler" unst_pitching_naca64a010_euler.cfg_file = "pitching_NACA64A010.cfg" unst_pitching_naca64a010_euler.test_iter = 2 - unst_pitching_naca64a010_euler.test_vals = [-1.186839, 4.280301, -0.039488, 0.000918] + unst_pitching_naca64a010_euler.test_vals = [-1.186839, 4.280301, -0.039479, 0.000910] unst_pitching_naca64a010_euler.unsteady = True test_list.append(unst_pitching_naca64a010_euler) # unsteady plunging NACA0012, Laminar NS @@ -840,7 +840,7 @@ def main(): unst_plunging_naca0012.cfg_dir = "unsteady/plunging_naca0012" unst_plunging_naca0012.cfg_file = "plunging_NACA0012.cfg" unst_plunging_naca0012.test_iter = 2 - unst_plunging_naca0012.test_vals = [-4.083462, 1.366757, -6.470859, -0.078768] + unst_plunging_naca0012.test_vals = [-4.083462, 1.366757, -6.456450, -0.082788] unst_plunging_naca0012.unsteady = True test_list.append(unst_plunging_naca0012) @@ -849,7 +849,7 @@ def main(): unst_deforming_naca0012.cfg_dir = "disc_adj_euler/naca0012_pitching_def" unst_deforming_naca0012.cfg_file = "inv_NACA0012_pitching_deform.cfg" unst_deforming_naca0012.test_iter = 5 - unst_deforming_naca0012.test_vals = [-3.665263, -3.794184, -3.716978, -3.148551] + unst_deforming_naca0012.test_vals = [-3.665270, -3.794211, -3.716998, -3.148563] unst_deforming_naca0012.unsteady = True test_list.append(unst_deforming_naca0012) @@ -862,7 +862,7 @@ def main(): ls89_sa.cfg_dir = "nicf/LS89" ls89_sa.cfg_file = "turb_SA_PR.cfg" ls89_sa.test_iter = 20 - ls89_sa.test_vals = [-5.069400, -13.403604, 0.180485, 0.429458] + ls89_sa.test_vals = [-5.072889, -13.410694, 0.181586, 0.432065] test_list.append(ls89_sa) # Rarefaction shock wave edge_VW @@ -878,7 +878,7 @@ def main(): edge_PPR.cfg_dir = "nicf/edge" edge_PPR.cfg_file = "edge_PPR.cfg" edge_PPR.test_iter = 20 - edge_PPR.test_vals = [-12.344691, -6.171790, -0.000034, 0.000000] + edge_PPR.test_vals = [-12.342162, -6.169311, -0.000034, 0.000000] test_list.append(edge_PPR) @@ -900,7 +900,7 @@ def main(): Jones_tc_restart.cfg_dir = "turbomachinery/APU_turbocharger" Jones_tc_restart.cfg_file = "Jones_restart.cfg" Jones_tc_restart.test_iter = 5 - Jones_tc_restart.test_vals = [-7.645867, -5.849734, -15.337011, -9.825760, -13.216108, -7.752293, 73286.000000, 73286.000000, 0.020055, 82.286000] + Jones_tc_restart.test_vals = [-7.645867, -5.849734, -15.337011, -9.825761, -13.216108, -7.752293, 73286.000000, 73286.000000, 0.020055, 82.286000] test_list.append(Jones_tc_restart) # 2D axial stage @@ -908,7 +908,7 @@ def main(): axial_stage2D.cfg_dir = "turbomachinery/axial_stage_2D" axial_stage2D.cfg_file = "Axial_stage2D.cfg" axial_stage2D.test_iter = 20 - axial_stage2D.test_vals = [1.167182, 1.598495, -2.928576, 2.573645, -2.527392, 3.016170, 106370.000000, 106370.000000, 5.726800, 64.383000] + axial_stage2D.test_vals = [1.167182, 1.598496, -2.928577, 2.573644, -2.527392, 3.016170, 106370.000000, 106370.000000, 5.726800, 64.383000] test_list.append(axial_stage2D) # 2D transonic stator restart @@ -916,7 +916,7 @@ def main(): transonic_stator_restart.cfg_dir = "turbomachinery/transonic_stator_2D" transonic_stator_restart.cfg_file = "transonic_stator_restart.cfg" transonic_stator_restart.test_iter = 20 - transonic_stator_restart.test_vals = [-4.367851, -2.492866, -2.082422, 1.727424, -1.466963, 3.224518, -471620.000000, 94.839000, -0.052025] + transonic_stator_restart.test_vals = [-4.367780, -2.492918, -2.082410, 1.727494, -1.466974, 3.224733, -471620.000000, 94.839000, -0.052084] transonic_stator_restart.test_vals_aarch64 = [-4.443401, -2.566759, -2.169302, 1.651815, -1.356398, 3.172527, -471620.000000, 94.843000, -0.044669] test_list.append(transonic_stator_restart) @@ -946,7 +946,7 @@ def main(): uniform_flow.cfg_dir = "sliding_interface/uniform_flow" uniform_flow.cfg_file = "uniform_NN.cfg" uniform_flow.test_iter = 2 - uniform_flow.test_vals = [2.000000, 0.000000, -0.230641, -13.251039] + uniform_flow.test_vals = [2.000000, 0.000000, -0.230639, -13.253272] uniform_flow.test_vals_aarch64 = [2.000000, 0.000000, -0.230641, -13.249000] uniform_flow.tol = 0.000001 uniform_flow.unsteady = True @@ -958,7 +958,7 @@ def main(): channel_2D.cfg_dir = "sliding_interface/channel_2D" channel_2D.cfg_file = "channel_2D_WA.cfg" channel_2D.test_iter = 2 - channel_2D.test_vals = [2.000000, 0.000000, 0.466199, 0.350095, 0.399015] + channel_2D.test_vals = [2.000000, 0.000000, 0.466202, 0.350093, 0.399014] channel_2D.timeout = 100 channel_2D.unsteady = True channel_2D.multizone = True @@ -969,7 +969,7 @@ def main(): channel_3D.cfg_dir = "sliding_interface/channel_3D" channel_3D.cfg_file = "channel_3D_WA.cfg" channel_3D.test_iter = 1 - channel_3D.test_vals = [1.000000, 0.000000, 0.617233, 0.798811, 0.692599] + channel_3D.test_vals = [1.000000, 0.000000, 0.617237, 0.798808, 0.692608] channel_3D.test_vals_aarch64 = [1.000000, 0.000000, 0.611996, 0.798988, 0.702357] channel_3D.unsteady = True channel_3D.multizone = True @@ -981,7 +981,7 @@ def main(): pipe.cfg_dir = "sliding_interface/pipe" pipe.cfg_file = "pipe_NN.cfg" pipe.test_iter = 2 - pipe.test_vals = [0.568973, 0.692858, 0.989456, 1.048237] + pipe.test_vals = [0.568972, 0.692858, 0.989456, 1.048237] pipe.unsteady = True pipe.multizone = True test_list.append(pipe) @@ -991,7 +991,7 @@ def main(): rotating_cylinders.cfg_dir = "sliding_interface/rotating_cylinders" rotating_cylinders.cfg_file = "rot_cylinders_WA.cfg" rotating_cylinders.test_iter = 3 - rotating_cylinders.test_vals = [3.000000, 0.000000, 0.664822, 1.125810, 1.117607] + rotating_cylinders.test_vals = [3.000000, 0.000000, 0.664822, 1.125812, 1.117605] rotating_cylinders.unsteady = True rotating_cylinders.multizone = True test_list.append(rotating_cylinders) @@ -1001,7 +1001,7 @@ def main(): supersonic_vortex_shedding.cfg_dir = "sliding_interface/supersonic_vortex_shedding" supersonic_vortex_shedding.cfg_file = "sup_vor_shed_WA.cfg" supersonic_vortex_shedding.test_iter = 5 - supersonic_vortex_shedding.test_vals = [5.000000, 0.000000, 0.899637, 1.076225] + supersonic_vortex_shedding.test_vals = [5.000000, 0.000000, 0.899641, 1.076189] supersonic_vortex_shedding.unsteady = True supersonic_vortex_shedding.multizone = True test_list.append(supersonic_vortex_shedding) @@ -1011,7 +1011,7 @@ def main(): bars_SST_2D.cfg_dir = "sliding_interface/bars_SST_2D" bars_SST_2D.cfg_file = "bars.cfg" bars_SST_2D.test_iter = 13 - bars_SST_2D.test_vals = [13.000000, -0.397243, -1.461873] + bars_SST_2D.test_vals = [13.000000, -0.393225, -1.462257] bars_SST_2D.multizone = True test_list.append(bars_SST_2D) @@ -1020,7 +1020,7 @@ def main(): slinc_steady.cfg_dir = "sliding_interface/incompressible_steady" slinc_steady.cfg_file = "config.cfg" slinc_steady.test_iter = 19 - slinc_steady.test_vals = [19.000000, -1.148249, -1.398402] + slinc_steady.test_vals = [19.000000, -1.141747, -1.450721] slinc_steady.timeout = 100 slinc_steady.multizone = True test_list.append(slinc_steady) @@ -1089,7 +1089,7 @@ def main(): fsi_cht.cfg_dir = "fea_fsi/stat_fsi" fsi_cht.cfg_file = "config.cfg" fsi_cht.test_iter = 20 - fsi_cht.test_vals = [5, -5.077012, -5.379779, -9.247804, -9.277819, -9.183796, 6.0835e+02, -1.2973e-02, 5.7607e-08, 29] + fsi_cht.test_vals = [5.000000, -5.077003, -5.379449, -9.247804, -9.319626, -9.184904, 608.350000, -0.012973, 0.000000, 30.000000] fsi_cht.multizone = True test_list.append(fsi_cht) @@ -1098,7 +1098,7 @@ def main(): dyn_fsi.cfg_dir = "fea_fsi/dyn_fsi" dyn_fsi.cfg_file = "config.cfg" dyn_fsi.test_iter = 4 - dyn_fsi.test_vals = [-4.330728, -4.152820, 5.3831e-08, 85] + dyn_fsi.test_vals = [-4.330728, -4.152820, 0.000000, 85.000000] dyn_fsi.multizone = True dyn_fsi.unsteady = True test_list.append(dyn_fsi) @@ -1108,7 +1108,7 @@ def main(): airfoilRBF.cfg_dir = "fea_fsi/Airfoil_RBF" airfoilRBF.cfg_file = "config.cfg" airfoilRBF.test_iter = 1 - airfoilRBF.test_vals = [1.000000, 0.028165, -3.530075] + airfoilRBF.test_vals = [1.000000, 0.026697, -3.532043] airfoilRBF.tol = 0.0001 airfoilRBF.multizone = True test_list.append(airfoilRBF) @@ -1150,7 +1150,7 @@ def main(): cht_incompressible.cfg_dir = "coupled_cht/comp_2d" cht_incompressible.cfg_file = "cht_2d_3cylinders.cfg" cht_incompressible.test_iter = 10 - cht_incompressible.test_vals = [-4.256032, -0.532728, -0.532729, -0.532728] + cht_incompressible.test_vals = [-4.256032, -0.532728, -0.532728, -0.532728] cht_incompressible.multizone = True test_list.append(cht_incompressible) @@ -1545,7 +1545,7 @@ def main(): pywrapper_naca0012.cfg_dir = "euler/naca0012" pywrapper_naca0012.cfg_file = "inv_NACA0012_Roe.cfg" pywrapper_naca0012.test_iter = 20 - pywrapper_naca0012.test_vals = [-4.506451, -3.937611, 0.297774, 0.025416] + pywrapper_naca0012.test_vals = [-4.489721, -3.937702, 0.293347, 0.025228] pywrapper_naca0012.command = TestCase.Command(exec = "SU2_CFD.py", param = "-f") pywrapper_naca0012.timeout = 1600 pywrapper_naca0012.tol = 0.00001 @@ -1558,7 +1558,7 @@ def main(): pywrapper_turb_naca0012_sst.cfg_dir = "rans/naca0012" pywrapper_turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" pywrapper_turb_naca0012_sst.test_iter = 10 - pywrapper_turb_naca0012_sst.test_vals = [-12.094425, -15.251083, -5.906366, 1.070413, 0.015775, -3.178933, 0.000000] + pywrapper_turb_naca0012_sst.test_vals = [-12.094445, -15.251083, -5.906366, 1.070413, 0.015775, -3.178548, 0.000000] pywrapper_turb_naca0012_sst.test_vals_aarch64 = [-12.076068, -15.246740, -5.861280, 1.070036, 0.015841, -3.297854, 0.000000] pywrapper_turb_naca0012_sst.command = TestCase.Command(exec = "SU2_CFD.py", param = "-f") pywrapper_turb_naca0012_sst.timeout = 3200 @@ -1601,7 +1601,7 @@ def main(): pywrapper_unsteadyCHT.cfg_dir = "py_wrapper/flatPlate_unsteady_CHT" pywrapper_unsteadyCHT.cfg_file = "unsteady_CHT_FlatPlate_Conf.cfg" pywrapper_unsteadyCHT.test_iter = 5 - pywrapper_unsteadyCHT.test_vals = [-1.614168, 2.260078, -0.020704, 0.172871] + pywrapper_unsteadyCHT.test_vals = [-1.614169, 2.260215, -0.019432, 0.203751] pywrapper_unsteadyCHT.command = TestCase.Command(exec = "python", param = "launch_unsteady_CHT_FlatPlate.py -f") pywrapper_unsteadyCHT.timeout = 1600 pywrapper_unsteadyCHT.tol = 0.00001 diff --git a/TestCases/serial_regression_AD.py b/TestCases/serial_regression_AD.py index 6e11587b8cf6..15598919e70e 100644 --- a/TestCases/serial_regression_AD.py +++ b/TestCases/serial_regression_AD.py @@ -58,7 +58,7 @@ def main(): discadj_naca0012_via_mz.cfg_dir = "cont_adj_euler/naca0012" discadj_naca0012_via_mz.cfg_file = "inv_NACA0012_discadj_multizone.cfg" discadj_naca0012_via_mz.test_iter = 100 - discadj_naca0012_via_mz.test_vals = [-3.563784, -5.975640, -6.326231, -8.929567] + discadj_naca0012_via_mz.test_vals = [-3.563784, -5.975641, -6.326231, -8.929567] discadj_naca0012_via_mz.enabled_with_tapetests = True discadj_naca0012_via_mz.tapetest_vals = [0] test_list.append(discadj_naca0012_via_mz) @@ -68,7 +68,7 @@ def main(): discadj_cylinder3D.cfg_dir = "disc_adj_euler/cylinder3D" discadj_cylinder3D.cfg_file = "inv_cylinder3D.cfg" discadj_cylinder3D.test_iter = 5 - discadj_cylinder3D.test_vals = [-3.702105, -3.895140, -0.000000, 0.000000] + discadj_cylinder3D.test_vals = [-3.702109, -3.895140, -0.000000, 0.000000] test_list.append(discadj_cylinder3D) # Arina nozzle 2D @@ -108,7 +108,7 @@ def main(): discadj_incomp_NACA0012.cfg_dir = "disc_adj_incomp_euler/naca0012" discadj_incomp_NACA0012.cfg_file = "incomp_NACA0012_disc.cfg" discadj_incomp_NACA0012.test_iter = 20 - discadj_incomp_NACA0012.test_vals = [20.000000, -3.122293, -2.287328, 0.000000] + discadj_incomp_NACA0012.test_vals = [20.000000, -3.122056, -2.290799, 0.000000] test_list.append(discadj_incomp_NACA0012) ##################################### @@ -120,7 +120,7 @@ def main(): discadj_incomp_cylinder.cfg_dir = "disc_adj_incomp_navierstokes/cylinder" discadj_incomp_cylinder.cfg_file = "heated_cylinder.cfg" discadj_incomp_cylinder.test_iter = 20 - discadj_incomp_cylinder.test_vals = [20.000000, -1.672307, -6.247612, 0.000000] + discadj_incomp_cylinder.test_vals = [20.000000, -1.666057, -6.234535, 0.000000] test_list.append(discadj_incomp_cylinder) ####################################################### @@ -132,7 +132,7 @@ def main(): discadj_cylinder.cfg_dir = "disc_adj_rans/cylinder" discadj_cylinder.cfg_file = "cylinder.cfg" discadj_cylinder.test_iter = 9 - discadj_cylinder.test_vals = [1.639372, -2.834285, -0.009537, 0.000020] + discadj_cylinder.test_vals = [1.639373, -2.834286, -0.009537, 0.000020] discadj_cylinder.unsteady = True test_list.append(discadj_cylinder) @@ -145,7 +145,7 @@ def main(): discadj_DT_1ST_cylinder.cfg_dir = "disc_adj_rans/cylinder_DT_1ST" discadj_DT_1ST_cylinder.cfg_file = "cylinder.cfg" discadj_DT_1ST_cylinder.test_iter = 9 - discadj_DT_1ST_cylinder.test_vals = [1.196421, -3.339043, -0.006211, 0.000020] + discadj_DT_1ST_cylinder.test_vals = [1.196421, -3.339044, -0.006211, 0.000020] discadj_DT_1ST_cylinder.unsteady = True test_list.append(discadj_DT_1ST_cylinder) @@ -158,7 +158,7 @@ def main(): discadj_pitchingNACA0012.cfg_dir = "disc_adj_euler/naca0012_pitching" discadj_pitchingNACA0012.cfg_file = "inv_NACA0012_pitching.cfg" discadj_pitchingNACA0012.test_iter = 4 - discadj_pitchingNACA0012.test_vals = [-1.049761, -1.501951, -0.004853, 0.000013] + discadj_pitchingNACA0012.test_vals = [-1.050725, -1.504630, -0.004855, 0.000013] discadj_pitchingNACA0012.unsteady = True test_list.append(discadj_pitchingNACA0012) @@ -167,7 +167,7 @@ def main(): unst_deforming_naca0012.cfg_dir = "disc_adj_euler/naca0012_pitching_def" unst_deforming_naca0012.cfg_file = "inv_NACA0012_pitching_deform_ad.cfg" unst_deforming_naca0012.test_iter = 4 - unst_deforming_naca0012.test_vals = [-1.885816, -1.781193, 3920.300000, 0.000003] + unst_deforming_naca0012.test_vals = [-1.886194, -1.780629, 3882.900000, 0.000003] unst_deforming_naca0012.unsteady = True test_list.append(unst_deforming_naca0012) @@ -194,7 +194,7 @@ def main(): discadj_heat.cfg_dir = "disc_adj_heat" discadj_heat.cfg_file = "disc_adj_heat.cfg" discadj_heat.test_iter = 10 - discadj_heat.test_vals = [-2.677870, 0.674827, 0.000000, -9.215500] + discadj_heat.test_vals = [-2.707965, 0.684019, 0.000000, -9.483400] test_list.append(discadj_heat) ################################### @@ -205,9 +205,8 @@ def main(): discadj_fsi = TestCase('discadj_fsi') discadj_fsi.cfg_dir = "disc_adj_fsi" discadj_fsi.cfg_file = "config.cfg" - discadj_fsi.test_iter = 6 - discadj_fsi.test_vals = [6, -8.931715, -10.103841, 3.0937e-11, -1.7573e-06] - discadj_fsi.test_vals_aarch64 = [6, -8.928820, -10.067497, 3.0979e-11, -1.7585e-06] + discadj_fsi.test_iter = 9 + discadj_fsi.test_vals = [-3.167614, -4.164629, 4.3943e-04, -1.0619] test_list.append(discadj_fsi) ################################### @@ -349,7 +348,7 @@ def main(): pywrapper_CFD_AD_MeshDisp.cfg_dir = "py_wrapper/disc_adj_flow/mesh_disp_sens" pywrapper_CFD_AD_MeshDisp.cfg_file = "configAD_flow.cfg" pywrapper_CFD_AD_MeshDisp.test_iter = 1000 - pywrapper_CFD_AD_MeshDisp.test_vals = [30.000000, -2.496380, 1.441373, 0.000000] + pywrapper_CFD_AD_MeshDisp.test_vals = [30.000000, -2.496258, 1.441670, 0.000000] pywrapper_CFD_AD_MeshDisp.command = TestCase.Command(exec = "python", param = "run_adjoint.py -f") pywrapper_CFD_AD_MeshDisp.timeout = 1600 pywrapper_CFD_AD_MeshDisp.tol = 0.000001 diff --git a/TestCases/tutorials.py b/TestCases/tutorials.py index 305ab9fb2347..4f73d7fb1fa2 100644 --- a/TestCases/tutorials.py +++ b/TestCases/tutorials.py @@ -49,7 +49,7 @@ def main(): cht_incompressible_unsteady.cfg_dir = "../Tutorials/multiphysics/unsteady_cht/" cht_incompressible_unsteady.cfg_file = "cht_2d_3cylinders.cfg" cht_incompressible_unsteady.test_iter = 2 - cht_incompressible_unsteady.test_vals = [-3.075372, -0.080399, -0.080399, -0.080399, -11.163219, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 238.240000] + cht_incompressible_unsteady.test_vals = [-3.204738, -0.080399, -0.080399, -0.080399, -11.531676, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 238.240000] cht_incompressible_unsteady.multizone = True cht_incompressible_unsteady.unsteady = True test_list.append(cht_incompressible_unsteady) @@ -165,7 +165,7 @@ def main(): premixed_hydrogen.cfg_dir = "../Tutorials/incompressible_flow/Inc_Combustion/1__premixed_hydrogen" premixed_hydrogen.cfg_file = "H2_burner.cfg" premixed_hydrogen.test_iter = 10 - premixed_hydrogen.test_vals = [-8.772073, -9.721083, -11.354959, -4.381554, -12.832787] + premixed_hydrogen.test_vals = [-8.771332, -9.721253, -11.354950, -4.381656, -12.832815] test_list.append(premixed_hydrogen) ### Compressible Flow @@ -175,7 +175,7 @@ def main(): tutorial_inv_bump.cfg_dir = "../Tutorials/compressible_flow/Inviscid_Bump" tutorial_inv_bump.cfg_file = "inv_channel.cfg" tutorial_inv_bump.test_iter = 0 - tutorial_inv_bump.test_vals = [-1.437425, 4.075857, 0.071414, 0.017198] + tutorial_inv_bump.test_vals = [-1.437425, 4.075857, 0.074137, 0.015325] test_list.append(tutorial_inv_bump) # Inviscid Wedge @@ -192,7 +192,7 @@ def main(): tutorial_inv_onera.cfg_dir = "../Tutorials/compressible_flow/Inviscid_ONERAM6" tutorial_inv_onera.cfg_file = "inv_ONERAM6.cfg" tutorial_inv_onera.test_iter = 0 - tutorial_inv_onera.test_vals = [-5.204928, -4.597762, 0.272218, 0.091180] + tutorial_inv_onera.test_vals = [-5.204928, -4.597762, 0.271120, 0.091313] tutorial_inv_onera.no_restart = True test_list.append(tutorial_inv_onera) @@ -201,7 +201,7 @@ def main(): tutorial_lam_cylinder.cfg_dir = "../Tutorials/compressible_flow/Laminar_Cylinder" tutorial_lam_cylinder.cfg_file = "lam_cylinder.cfg" tutorial_lam_cylinder.test_iter = 0 - tutorial_lam_cylinder.test_vals = [-6.162141, -0.699617, -0.124663, 31.721714] + tutorial_lam_cylinder.test_vals = [-6.162141, -0.699617, -0.085099, 31.790369] tutorial_lam_cylinder.no_restart = True test_list.append(tutorial_lam_cylinder) @@ -228,7 +228,7 @@ def main(): tutorial_trans_flatplate.cfg_dir = "../Tutorials/compressible_flow/Transitional_Flat_Plate" tutorial_trans_flatplate.cfg_file = "transitional_BC_model_ConfigFile.cfg" tutorial_trans_flatplate.test_iter = 0 - tutorial_trans_flatplate.test_vals = [-22.021786, -15.330766, 0.000000, 0.023944] + tutorial_trans_flatplate.test_vals = [-22.025101, -15.330766, 0.000000, 0.023944] tutorial_trans_flatplate.no_restart = True test_list.append(tutorial_trans_flatplate) @@ -237,7 +237,7 @@ def main(): tutorial_trans_flatplate_T3A.cfg_dir = "../Tutorials/compressible_flow/Transitional_Flat_Plate/Langtry_and_Menter/T3A" tutorial_trans_flatplate_T3A.cfg_file = "transitional_LM_model_ConfigFile.cfg" tutorial_trans_flatplate_T3A.test_iter = 20 - tutorial_trans_flatplate_T3A.test_vals = [-5.790137, -2.054834, -3.894660, -0.255074, -1.747074, 5.119341, -3.493237, 0.393262] + tutorial_trans_flatplate_T3A.test_vals = [-5.790137, -2.054834, -3.894659, -0.255074, -1.747098, 5.119341, -3.493237, 0.393262] tutorial_trans_flatplate_T3A.test_vals_aarch64 = [-5.808996, -2.070606, -3.969765, -0.277943, -1.953289, 1.708472, -3.514943, 0.357411] tutorial_trans_flatplate_T3A.no_restart = True test_list.append(tutorial_trans_flatplate_T3A) @@ -247,7 +247,7 @@ def main(): tutorial_trans_flatplate_T3Am.cfg_dir = "../Tutorials/compressible_flow/Transitional_Flat_Plate/Langtry_and_Menter/T3A-" tutorial_trans_flatplate_T3Am.cfg_file = "transitional_LM_model_ConfigFile.cfg" tutorial_trans_flatplate_T3Am.test_iter = 20 - tutorial_trans_flatplate_T3Am.test_vals = [-5.590222, -1.700867, -3.098733, -0.105332, -3.750523, 3.287643, -2.394576, 1.119623] + tutorial_trans_flatplate_T3Am.test_vals = [-5.587332, -1.700868, -3.093872, -0.102783, -3.750523, 3.287643, -2.394575, 1.119623] tutorial_trans_flatplate_T3Am.test_vals_aarch64 = [-5.540938, -1.681627, -2.878831, -0.058224, -3.695533, 3.413628, -2.385345, 1.103633] tutorial_trans_flatplate_T3Am.no_restart = True test_list.append(tutorial_trans_flatplate_T3Am) @@ -292,7 +292,7 @@ def main(): tutorial_nicfd_nozzle_pinn.cfg_dir = "../Tutorials/compressible_flow/NICFD_nozzle/PhysicsInformed" tutorial_nicfd_nozzle_pinn.cfg_file = "config_NICFD_PINN.cfg" tutorial_nicfd_nozzle_pinn.test_iter = 20 - tutorial_nicfd_nozzle_pinn.test_vals = [-2.728179, -0.849337, -1.224542, 2.898995, -11.420290] + tutorial_nicfd_nozzle_pinn.test_vals = [-2.728179, -0.849337, -1.224543, 2.898995, -11.420290] tutorial_nicfd_nozzle_pinn.no_restart = True test_list.append(tutorial_nicfd_nozzle_pinn) @@ -302,7 +302,7 @@ def main(): tutorial_unst_naca0012.cfg_dir = "../Tutorials/compressible_flow/Unsteady_NACA0012" tutorial_unst_naca0012.cfg_file = "unsteady_naca0012.cfg" tutorial_unst_naca0012.test_iter = 520 - tutorial_unst_naca0012.test_vals = [520.000000, 0.000000, -5.294133, 0.000000, 0.314591, 0.778367, 0.000929, 0.007489] + tutorial_unst_naca0012.test_vals = [520.000000, 0.000000, -5.301440, 0.000000, 0.313631, 0.803638, 0.002198, 0.014969] tutorial_unst_naca0012.test_vals_aarch64 = [520, 0, -5.292359, 0, 0.284720, 0.766329, 0.000954, 0.007565] tutorial_unst_naca0012.unsteady = True test_list.append(tutorial_unst_naca0012) @@ -323,7 +323,7 @@ def main(): tutorial_design_inv_naca0012.cfg_dir = "../Tutorials/design/Inviscid_2D_Unconstrained_NACA0012" tutorial_design_inv_naca0012.cfg_file = "inv_NACA0012_basic.cfg" tutorial_design_inv_naca0012.test_iter = 0 - tutorial_design_inv_naca0012.test_vals = [-3.585391, -2.989014, 0.172229, 0.240113] + tutorial_design_inv_naca0012.test_vals = [-3.585391, -2.989014, 0.169476, 0.243426] tutorial_design_inv_naca0012.no_restart = True test_list.append(tutorial_design_inv_naca0012) diff --git a/TestCases/vandv.py b/TestCases/vandv.py index d14f7507264e..01bace8011e8 100644 --- a/TestCases/vandv.py +++ b/TestCases/vandv.py @@ -45,7 +45,7 @@ def main(): p30n30.cfg_dir = "vandv/rans/30p30n" p30n30.cfg_file = "config.cfg" p30n30.test_iter = 5 - p30n30.test_vals = [-11.267106, -11.168215, -11.182822, -10.949673, -14.233489, 0.052235, 2.830394, 1.318894, -1.210645, 1.000000, 12.763000] + p30n30.test_vals = [-11.267106, -11.168215, -11.182822, -10.949673, -14.233489, 0.052235, 2.830394, 1.318894, -1.210646, 1.000000, 12.763000] test_list.append(p30n30) # This is not part of the V&V cases yet, its tested in this script because it is a relatively long test (~1 min). @@ -63,7 +63,7 @@ def main(): flatplate_sst1994m.cfg_dir = "vandv/rans/flatplate" flatplate_sst1994m.cfg_file = "turb_flatplate_sst.cfg" flatplate_sst1994m.test_iter = 5 - flatplate_sst1994m.test_vals = [-13.040636, -10.136914, -10.942005, -7.981146, -10.323869, -4.732398, 0.002801] + flatplate_sst1994m.test_vals = [-13.040081, -10.136488, -10.941822, -7.977797, -10.323859, -4.732970, 0.002801] flatplate_sst1994m.test_vals_aarch64 = [-13.021715, -9.534786, -10.401912, -7.501836, -9.750800, -4.850665, 0.002807] test_list.append(flatplate_sst1994m) @@ -72,7 +72,7 @@ def main(): bump_sst1994m.cfg_dir = "vandv/rans/bump_in_channel" bump_sst1994m.cfg_file = "turb_bump_sst.cfg" bump_sst1994m.test_iter = 5 - bump_sst1994m.test_vals = [-11.928332, -10.095849, -9.512485, -6.445671, -11.773518, -6.998128, 0.004931] + bump_sst1994m.test_vals = [-11.928608, -10.096068, -9.512742, -6.445912, -11.774007, -6.978081, 0.004931] bump_sst1994m.test_vals_aarch64 = [-13.042689, -10.812982, -10.604523, -7.655547, -10.816257, -5.308083, 0.004911] test_list.append(bump_sst1994m) @@ -81,7 +81,7 @@ def main(): swbli_sa.cfg_dir = "vandv/rans/swbli" swbli_sa.cfg_file = "config_sa.cfg" swbli_sa.test_iter = 5 - swbli_sa.test_vals = [-11.502718, -10.939184, -12.034284, -10.581169, -16.088844, 0.002242, -1.664946, 1.257900] + swbli_sa.test_vals = [-11.502718, -10.939184, -12.034284, -10.581169, -16.088844, 0.002242, -1.664946, 1.258100] swbli_sa.test_vals_aarch64 = [-11.504424, -10.941741, -12.049925, -10.586263, -16.090385, 0.002242, -1.614365, 1.340100] test_list.append(swbli_sa) @@ -99,7 +99,7 @@ def main(): dsma661_sa.cfg_dir = "vandv/rans/dsma661" dsma661_sa.cfg_file = "dsma661_sa_config.cfg" dsma661_sa.test_iter = 5 - dsma661_sa.test_vals = [-11.255214, -8.242489, -8.996097, -5.916501, -10.737676, 0.155687, 0.024232] + dsma661_sa.test_vals = [-11.240967, -8.243826, -8.958188, -5.895885, -10.737669, 0.155687, 0.024232] dsma661_sa.test_vals_aarch64 = [-11.293183, -8.241775, -9.083761, -6.011398, -10.737680, 0.155687, 0.024232] test_list.append(dsma661_sa) @@ -108,7 +108,7 @@ def main(): dsma661_sst.cfg_dir = "vandv/rans/dsma661" dsma661_sst.cfg_file = "dsma661_sst_config.cfg" dsma661_sst.test_iter = 5 - dsma661_sst.test_vals = [-11.027162, -8.156487, -9.036775, -5.963509, -10.650691, -7.872447, 0.155882, 0.023344] + dsma661_sst.test_vals = [-11.023511, -8.156964, -9.060365, -5.934828, -10.651368, -7.898758, 0.155882, 0.023344] dsma661_sst.test_vals_aarch64 = [-10.977195, -8.403731, -8.747068, -5.808899, -10.522786, -7.369851, 0.155875, 0.023353] test_list.append(dsma661_sst) diff --git a/UnitTests/Common/geometry/CGeometry_test.cpp b/UnitTests/Common/geometry/CGeometry_test.cpp index cad2157a8040..22556b16055d 100644 --- a/UnitTests/Common/geometry/CGeometry_test.cpp +++ b/UnitTests/Common/geometry/CGeometry_test.cpp @@ -122,9 +122,9 @@ TEST_CASE("Set control volume", "[Geometry]") { CHECK(TestCase->geometry->nodes->GetVolume(42) == Approx(0.015625)); - CHECK(TestCase->geometry->edges->GetNormal(32)[0] == 0.03125); + CHECK(TestCase->geometry->edges->GetNormal(31)[0] == 0.03125); CHECK(TestCase->geometry->edges->GetNormal(5)[1] == 0.0); - CHECK(TestCase->geometry->edges->GetNormal(10)[2] == 0.03125); + CHECK(TestCase->geometry->edges->GetNormal(11)[2] == 0.03125); CHECK(TestCase->config->GetDomainVolume() == Approx(1.0)); } diff --git a/meson.build b/meson.build index d43e2b031d88..e76354a6e863 100644 --- a/meson.build +++ b/meson.build @@ -20,13 +20,10 @@ python = pymod.find_installation() if get_option('enable-cuda') add_languages('cuda') add_global_arguments('-arch=sm_86', language : 'cuda') - cuda_deps = [meson.get_compiler('cuda').find_library('cusparse', required : true)] -else - cuda_deps = [] endif su2_cpp_args = [] -su2_deps = [declare_dependency(include_directories: 'externals/CLI11')] + cuda_deps +su2_deps = [declare_dependency(include_directories: 'externals/CLI11')] default_warning_flags = [] if build_machine.system() != 'windows' From 3529807c3ba455bfc6073fd2b5ea2063bb4cbfe8 Mon Sep 17 00:00:00 2001 From: Edwin van der Weide Date: Tue, 14 Jul 2026 07:04:16 +0200 Subject: [PATCH 18/61] SU2 Binary grid format (#2535) * Implemented the reading of a binary SU2 format * Ran pre-commit * Addressing issues * Forgot to run pre-commit * Addressing comments * Set up the infrastructure for the writing of a binary grid file * Made sure to save these changes * review * writer and test * Added the number of DOFs per element when writing the file of the Jacobian matrix * fix --------- Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Co-authored-by: Pedro Gomes --- Common/include/CConfig.hpp | 56 +- .../meshreader/CSU2ASCIIMeshReaderBase.hpp | 37 +- .../meshreader/CSU2BinaryMeshReaderBase.hpp | 127 +++++ .../meshreader/CSU2BinaryMeshReaderFEM.hpp | 66 +++ .../meshreader/CSU2BinaryMeshReaderFVM.hpp | 55 ++ .../meshreader/CSU2MeshReaderBase.hpp | 84 +++ Common/include/option_structure.hpp | 36 +- Common/include/toolboxes/SwapBytes.hpp | 39 ++ Common/src/CConfig.cpp | 97 +++- Common/src/geometry/CPhysicalGeometry.cpp | 9 + .../meshreader/CSU2ASCIIMeshReaderBase.cpp | 5 +- .../meshreader/CSU2BinaryMeshReaderBase.cpp | 495 ++++++++++++++++++ .../meshreader/CSU2BinaryMeshReaderFEM.cpp | 73 +++ .../meshreader/CSU2BinaryMeshReaderFVM.cpp | 66 +++ .../meshreader/CSU2MeshReaderBase.cpp | 37 ++ Common/src/geometry/meshreader/meson.build | 6 +- Common/src/grid_movement/CSurfaceMovement.cpp | 2 +- Common/src/toolboxes/SwapBytes.cpp | 53 ++ Common/src/toolboxes/meson.build | 3 +- .../filewriter/CParaviewBinaryFileWriter.hpp | 11 - .../filewriter/CSU2MeshBinaryFileWriter.hpp | 59 +++ SU2_CFD/src/meson.build | 1 + SU2_CFD/src/output/COutput.cpp | 20 + .../filewriter/CParaviewBinaryFileWriter.cpp | 35 +- .../filewriter/CSU2MeshBinaryFileWriter.cpp | 306 +++++++++++ SU2_CFD/src/solvers/CFEM_DG_EulerSolver.cpp | 8 + SU2_DEF/src/drivers/CDeformationDriver.cpp | 19 +- TestCases/.gitignore | 2 + .../backward_step/backwardStep.cfg | 20 +- .../euler/naca0012/mesh_su2_to_su2bin.cfg | 34 ++ TestCases/parallel_regression.py | 24 +- .../turb_NACA0012_sst_multigrid_restart.cfg | 2 +- .../rans/naca0012/turb_NACA0012_sst_sust.cfg | 2 +- config_template.cfg | 5 +- 34 files changed, 1755 insertions(+), 139 deletions(-) create mode 100644 Common/include/geometry/meshreader/CSU2BinaryMeshReaderBase.hpp create mode 100644 Common/include/geometry/meshreader/CSU2BinaryMeshReaderFEM.hpp create mode 100644 Common/include/geometry/meshreader/CSU2BinaryMeshReaderFVM.hpp create mode 100644 Common/include/geometry/meshreader/CSU2MeshReaderBase.hpp create mode 100644 Common/include/toolboxes/SwapBytes.hpp create mode 100644 Common/src/geometry/meshreader/CSU2BinaryMeshReaderBase.cpp create mode 100644 Common/src/geometry/meshreader/CSU2BinaryMeshReaderFEM.cpp create mode 100644 Common/src/geometry/meshreader/CSU2BinaryMeshReaderFVM.cpp create mode 100644 Common/src/geometry/meshreader/CSU2MeshReaderBase.cpp create mode 100644 Common/src/toolboxes/SwapBytes.cpp create mode 100644 SU2_CFD/include/output/filewriter/CSU2MeshBinaryFileWriter.hpp create mode 100644 SU2_CFD/src/output/filewriter/CSU2MeshBinaryFileWriter.cpp create mode 100644 TestCases/euler/naca0012/mesh_su2_to_su2bin.cfg diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index 15432aadc7f4..1b2a45dc3ffe 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -810,6 +810,7 @@ class CConfig { su2double *nBlades; /*!< \brief number of blades for turbomachinery computation. */ unsigned short Geo_Description; /*!< \brief Description of the geometry. */ unsigned short Mesh_FileFormat; /*!< \brief Mesh input format. */ + unsigned short Mesh_Out_FileFormat; /*!< \brief Mesh output format. */ TAB_OUTPUT Tab_FileFormat; /*!< \brief Format of the output files. */ unsigned short output_precision; /*!< \brief .precision(value) for SU2_DOT and HISTORY output */ unsigned short ActDisk_Jump; /*!< \brief Format of the output files. */ @@ -1530,6 +1531,14 @@ class CConfig { */ void SetMPICommunicator(SU2_MPI::Comm Communicator); + /*! + * \brief Helper function, which checks and opens a binary SU2 file. + * \param[in] val_mesh_filename - Name of the file with the grid information. + * \param[in] readnDim = Whether or not nDim must be read. If false nZone is read. + * \return Number of dimensions or number of zones in the grid. + */ + static unsigned short CheckOpenSU2BinFile(const string& val_mesh_filename, bool readnDim); + /*! * \brief Gets the number of zones in the mesh file. * \param[in] val_mesh_filename - Name of the file with the grid information. @@ -5711,17 +5720,21 @@ class CConfig { /*--- we keep the original Mesh_FileName ---*/ string meshFilename = Mesh_FileName; - /*--- strip the extension, only if it is .su2 or .cgns ---*/ + /*--- strip the extension, only if it is .su2, .su2b or .cgns ---*/ PrintingToolbox::TrimExtension(".su2",meshFilename); + PrintingToolbox::TrimExtension(".su2b",meshFilename); PrintingToolbox::TrimExtension(".cgns",meshFilename); switch (GetMesh_FileFormat()) { - case SU2: - case RECTANGLE: - case BOX: + case ENUM_GRID::SU2: + case ENUM_GRID::RECTANGLE: + case ENUM_GRID::BOX: meshFilename += ".su2"; break; - case CGNS_GRID: + case ENUM_GRID::SU2_BIN: + meshFilename += ".su2b"; + break; + case ENUM_GRID::CGNS_GRID: meshFilename += ".cgns"; break; default: @@ -5735,6 +5748,9 @@ class CConfig { /*! * \brief Get name of the output grid, this parameter is important for grid * adaptation and deformation. + * \note The returned name does not include the extension, it is the + * responsibility of the caller (usually a CFileWriter) to append it, + * consistent with GetMesh_Out_FileExtension(). * \return File name of the output grid. */ string GetMesh_Out_FileName(void) const { @@ -5742,13 +5758,31 @@ class CConfig { /*--- we keep the original Mesh_Out_FileName ---*/ string meshFilename = Mesh_Out_FileName; - /*--- strip the extension, only if it is .su2 or .cgns ---*/ + /*--- strip the extension, only if it is .su2, .su2b or .cgns ---*/ PrintingToolbox::TrimExtension(".su2",meshFilename); + PrintingToolbox::TrimExtension(".su2b",meshFilename); PrintingToolbox::TrimExtension(".cgns",meshFilename); return meshFilename; } + /*! + * \brief Get the extension (including the leading dot) associated with the + * current mesh output format. + * \return Extension of the output grid file. + */ + string GetMesh_Out_FileExtension(void) const { + switch (GetMesh_Out_FileFormat()) { + case ENUM_GRID::SU2: + return ".su2"; + case ENUM_GRID::SU2_BIN: + return ".su2b"; + default: + SU2_MPI::Error("Unrecognized mesh_out format specified!", CURRENT_FUNCTION); + return ""; + } + } + /*! * \brief Get the name of the file with the solution of the flow problem. * \return Name of the file with the solution of the flow problem. @@ -5784,11 +5818,17 @@ class CConfig { } /*! - * \brief Get the format of the input/output grid. - * \return Format of the input/output grid. + * \brief Get the format of the input grid. + * \return Format of the input grid. */ unsigned short GetMesh_FileFormat(void) const { return Mesh_FileFormat; } + /*! + * \brief Get the format of the output grid. + * \return Format of the output grid. + */ + unsigned short GetMesh_Out_FileFormat(void) const { return Mesh_Out_FileFormat; } + /*! * \brief Get the format of the output solution. * \return Format of the output solution. diff --git a/Common/include/geometry/meshreader/CSU2ASCIIMeshReaderBase.hpp b/Common/include/geometry/meshreader/CSU2ASCIIMeshReaderBase.hpp index f1c35b2d9392..8d4cbb4dd385 100644 --- a/Common/include/geometry/meshreader/CSU2ASCIIMeshReaderBase.hpp +++ b/Common/include/geometry/meshreader/CSU2ASCIIMeshReaderBase.hpp @@ -30,50 +30,19 @@ #include -#include "CMeshReaderBase.hpp" +#include "CSU2MeshReaderBase.hpp" /*! * \class CSU2ASCIIMeshReaderBase * \brief Base class for the reading of a native SU2 ASCII grid. * \author T. Economon */ -class CSU2ASCIIMeshReaderBase : public CMeshReaderBase { +class CSU2ASCIIMeshReaderBase : public CSU2MeshReaderBase { protected: enum class FileSection { POINTS, ELEMENTS, MARKERS }; /*!< \brief Different sections of the file. */ std::array SectionOrder{}; /*!< \brief Order of the sections in the file. */ - const unsigned short myZone; /*!< \brief Current SU2 zone index. */ - const unsigned short nZones; /*!< \brief Total number of zones in the SU2 file. */ - - const string meshFilename; /*!< \brief Name of the SU2 ASCII mesh file being read. */ - ifstream mesh_file; /*!< \brief File object for the SU2 ASCII mesh file. */ - - bool actuator_disk; /*!< \brief Boolean for whether we have an actuator disk to split. */ - - unsigned long ActDiskNewPoints = - 0; /*!< \brief Total number of new grid points to add due to actuator disk splitting. */ - - su2double Xloc = 0.0; /*!< \brief X-coordinate of the CG of the actuator disk surface. */ - su2double Yloc = 0.0; /*!< \brief X-coordinate of the CG of the actuator disk surface. */ - su2double Zloc = 0.0; /*!< \brief X-coordinate of the CG of the actuator disk surface. */ - - vector ActDisk_Bool; /*!< \brief Flag to identify the grid points on the actuator disk. */ - - vector ActDiskPoint_Back; /*!< \brief Vector containing the global index for the new grid points added - to the back of the actuator disk. */ - vector VolumePoint_Inv; /*!< \brief Vector containing the inverse mapping from the global index to the - added point index for the actuator disk. */ - - vector CoordXActDisk; /*!< \brief X-coordinates of the new grid points added by splitting the actuator disk - (size = ActDiskNewPoints). */ - vector CoordYActDisk; /*!< \brief Y-coordinates of the new grid points added by splitting the actuator disk - (size = ActDiskNewPoints). */ - vector CoordZActDisk; /*!< \brief Z-coordinates of the new grid points added by splitting the actuator disk - (size = ActDiskNewPoints). */ - - vector CoordXVolumePoint; /*!< \brief X-coordinates of the volume elements touching the actuator disk. */ - vector CoordYVolumePoint; /*!< \brief Y-coordinates of the volume elements touching the actuator disk. */ - vector CoordZVolumePoint; /*!< \brief Z-coordinates of the volume elements touching the actuator disk. */ + ifstream mesh_file; /*!< \brief File object for the SU2 ASCII mesh file. */ /*! * \brief Reads all SU2 ASCII mesh metadata and checks for errors. diff --git a/Common/include/geometry/meshreader/CSU2BinaryMeshReaderBase.hpp b/Common/include/geometry/meshreader/CSU2BinaryMeshReaderBase.hpp new file mode 100644 index 000000000000..b6041d8cf624 --- /dev/null +++ b/Common/include/geometry/meshreader/CSU2BinaryMeshReaderBase.hpp @@ -0,0 +1,127 @@ +/*! + * \file CSU2BinaryMeshReaderBase.hpp + * \brief Header file for the class CSU2BinaryMeshReaderBase. + * The implementations are in the CSU2BinaryMeshReaderBase.cpp file. + * \author T. Economon, E. van der Weide + * \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-2025, 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 . + */ + +#pragma once + +#include "CSU2MeshReaderBase.hpp" +#include "../../../include/toolboxes/SwapBytes.hpp" + +/*! + * \class CSU2BinaryMeshReaderBase + * \brief Base class for the reading of a native SU2 binary grid. + * \author T. Economon, E. van der Weide + */ +class CSU2BinaryMeshReaderBase : public CSU2MeshReaderBase { + protected: + constexpr static int SU2_STRING_SIZE = + SU2_BINARY_STRING_SIZE; /*!< \brief Size of the strings in the SU2 binary mesh file. */ + + FILE* mesh_file; /*!< \brief File object for the SU2 binary mesh file. */ + bool swap_bytes; /*!< \brief Whether or not byte swapping must be used. */ + int size_conn_type; /*!< \brief Size, in bytes of the connectivity type. */ + + /*! + * \brief Reads the connectivity type used in the binary file and check if + * byte swapping must be applied. + */ + void ReadConnectivityType(); + + /*! + * \brief Reads all SU2 binary mesh metadata and checks for errors. + * \param[in] config - Problem configuration for the current zone. + */ + void ReadMetadata(CConfig* config); + + /*! + * \brief Reads the grid points from an SU2 zone into linear partitions across all ranks. + */ + virtual void ReadPointCoordinates(); + + /*! + * \brief Reads the interior volume elements from one section of an SU2 zone into linear partitions across all ranks. + */ + virtual void ReadVolumeElementConnectivity(); + + /*! + * \brief Reads the surface (boundary) elements from the SU2 zone. + */ + virtual void ReadSurfaceElementConnectivity(); + + /*! + * \brief Helper function to find the current zone in an SU2 binary mesh object. + */ + void FastForwardToMyZone(); + + /*! + * \brief Portable, 64-bit safe replacement for fseek. Needed because binary + * SU2 grid files can exceed 2 GiB even on platforms where "long" is + * only 32 bits wide (e.g. Windows). + */ + static int FileSeek64(FILE* file, int64_t offset, int whence); + + /*! + * \brief Portable, 64-bit safe replacement for ftell. + */ + static int64_t FileTell64(FILE* file); + + /*! + * \brief Function to read one entity of the connectivity type from the binary file. + * \return uint64_t version of the the data. + */ + uint64_t ReadBinaryNEntities(); + + /*! + * \brief Template function to read data from the binary file. + */ + template + void ReadBinaryData(T* data, const size_t nItems) { + /*--- Read the actual data. ---*/ + auto ret = fread(data, sizeof(T), nItems, mesh_file); + if (ret != nItems) SU2_MPI::Error(string("Error while reading the file ") + meshFilename, CURRENT_FUNCTION); + + /*--- Apply byte swapping, if needed. ---*/ + if (swap_bytes) SwapBytes((char*)data, sizeof(T), nItems); + } + + private: + /*! + * \brief Read the meta data for a zone. + */ + void ReadMetadataZone(); + + public: + /*! + * \brief Constructor of the CSU2BinaryMeshReaderBase class. + */ + CSU2BinaryMeshReaderBase(CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone); + + /*! + * \brief Destructor of the CSU2BinaryMeshReaderBase class. + */ + ~CSU2BinaryMeshReaderBase(void) override; +}; diff --git a/Common/include/geometry/meshreader/CSU2BinaryMeshReaderFEM.hpp b/Common/include/geometry/meshreader/CSU2BinaryMeshReaderFEM.hpp new file mode 100644 index 000000000000..bec54f75fcb7 --- /dev/null +++ b/Common/include/geometry/meshreader/CSU2BinaryMeshReaderFEM.hpp @@ -0,0 +1,66 @@ +/*! + * \file CSU2BinaryMeshReaderFEM.hpp + * \brief Header file for the class CSU2BinaryMeshReaderFEM. + * The implementations are in the CSU2BinaryMeshReaderFEM.cpp file. + * \author T. Economon, E. van der Weide + * \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-2025, 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 . + */ + +#pragma once + +#include "CSU2BinaryMeshReaderBase.hpp" + +/*! + * \class CSU2BinaryMeshReaderFEM + * \brief Reads a native SU2 binary grid into linear partitions for the finite element solver (FEM). + * \author T. Economon, E. van der Weide + */ +class CSU2BinaryMeshReaderFEM : public CSU2BinaryMeshReaderBase { + private: + /*! + * \brief Reads the grid points from an SU2 zone into linear partitions across all ranks. + */ + void ReadPointCoordinates(); + + /*! + * \brief Reads the interior volume elements from one section of an SU2 zone into linear partitions across all ranks. + */ + void ReadVolumeElementConnectivity(); + + /*! + * \brief Reads the surface (boundary) elements from one section of an SU2 zone into linear partitions across all + * ranks. + */ + void ReadSurfaceElementConnectivity(); + + public: + /*! + * \brief Constructor of the CSU2BinaryMeshReaderFEM class. + */ + CSU2BinaryMeshReaderFEM(CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone); + + /*! + * \brief Destructor of the CSU2BinaryMeshReaderFEM class. + */ + ~CSU2BinaryMeshReaderFEM(void) override; +}; diff --git a/Common/include/geometry/meshreader/CSU2BinaryMeshReaderFVM.hpp b/Common/include/geometry/meshreader/CSU2BinaryMeshReaderFVM.hpp new file mode 100644 index 000000000000..03ba796289bb --- /dev/null +++ b/Common/include/geometry/meshreader/CSU2BinaryMeshReaderFVM.hpp @@ -0,0 +1,55 @@ +/*! + * \file CSU2BinaryMeshReaderFVM.hpp + * \brief Header file for the class CSU2BinaryMeshReaderFVM. + * The implementations are in the CSU2BinaryMeshReaderFVM.cpp file. + * \author T. Economon, E. van der Weide + * \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-2025, 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 . + */ + +#pragma once + +#include "CSU2BinaryMeshReaderBase.hpp" + +/*! + * \class CSU2BinaryMeshReaderFVM + * \brief Reads a native SU2 binary grid into linear partitions for the finite volume solver (FVM). + * \author T. Economon, E. van der Weide + */ +class CSU2BinaryMeshReaderFVM : public CSU2BinaryMeshReaderBase { + private: + /*! + * \brief Splits a single surface actuator disk boundary into two separate markers (repeated points). + */ + void SplitActuatorDiskSurface(); + + public: + /*! + * \brief Constructor of the CSU2BinaryMeshReaderFVM class. + */ + CSU2BinaryMeshReaderFVM(CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone); + + /*! + * \brief Destructor of the CSU2BinaryMeshReaderFVM class. + */ + ~CSU2BinaryMeshReaderFVM(void) override; +}; diff --git a/Common/include/geometry/meshreader/CSU2MeshReaderBase.hpp b/Common/include/geometry/meshreader/CSU2MeshReaderBase.hpp new file mode 100644 index 000000000000..9e5217f5c2b3 --- /dev/null +++ b/Common/include/geometry/meshreader/CSU2MeshReaderBase.hpp @@ -0,0 +1,84 @@ +/*! + * \file CSU2MeshReaderBase.hpp + * \brief Header file for the class CSU2MeshReaderBase. + * The implementations are in the CSU2MeshReaderBase.cpp file. + * \author T. Economon + * \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-2025, 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 . + */ + +#pragma once + +#include + +#include "CMeshReaderBase.hpp" + +/*! + * \class CSU2MeshReaderBase + * \brief Base class for the reading of a native SU2 grid. + * \author T. Economon + */ +class CSU2MeshReaderBase : public CMeshReaderBase { + protected: + const unsigned short myZone; /*!< \brief Current SU2 zone index. */ + const unsigned short nZones; /*!< \brief Total number of zones in the SU2 file. */ + + const string meshFilename; /*!< \brief Name of the SU2 ASCII mesh file being read. */ + + bool actuator_disk; /*!< \brief Boolean for whether we have an actuator disk to split. */ + + unsigned long ActDiskNewPoints = + 0; /*!< \brief Total number of new grid points to add due to actuator disk splitting. */ + + su2double Xloc = 0.0; /*!< \brief X-coordinate of the CG of the actuator disk surface. */ + su2double Yloc = 0.0; /*!< \brief X-coordinate of the CG of the actuator disk surface. */ + su2double Zloc = 0.0; /*!< \brief X-coordinate of the CG of the actuator disk surface. */ + + vector ActDisk_Bool; /*!< \brief Flag to identify the grid points on the actuator disk. */ + + vector ActDiskPoint_Back; /*!< \brief Vector containing the global index for the new grid points added + to the back of the actuator disk. */ + vector VolumePoint_Inv; /*!< \brief Vector containing the inverse mapping from the global index to the + added point index for the actuator disk. */ + + vector CoordXActDisk; /*!< \brief X-coordinates of the new grid points added by splitting the actuator disk + (size = ActDiskNewPoints). */ + vector CoordYActDisk; /*!< \brief Y-coordinates of the new grid points added by splitting the actuator disk + (size = ActDiskNewPoints). */ + vector CoordZActDisk; /*!< \brief Z-coordinates of the new grid points added by splitting the actuator disk + (size = ActDiskNewPoints). */ + + vector CoordXVolumePoint; /*!< \brief X-coordinates of the volume elements touching the actuator disk. */ + vector CoordYVolumePoint; /*!< \brief Y-coordinates of the volume elements touching the actuator disk. */ + vector CoordZVolumePoint; /*!< \brief Z-coordinates of the volume elements touching the actuator disk. */ + + public: + /*! + * \brief Constructor of the CSU2MeshReaderBase class. + */ + CSU2MeshReaderBase(CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone); + + /*! + * \brief Destructor of the CSU2MeshReaderBase class. + */ + ~CSU2MeshReaderBase(void) override; +}; diff --git a/Common/include/option_structure.hpp b/Common/include/option_structure.hpp index 0cc823bd8792..1ba1bab61fe7 100644 --- a/Common/include/option_structure.hpp +++ b/Common/include/option_structure.hpp @@ -195,6 +195,9 @@ inline unsigned short nPointsOfElementType(unsigned short elementType) { } const int CGNS_STRING_SIZE = 33; /*!< \brief Length of strings used in the CGNS format. */ +const int SU2_BINARY_STRING_SIZE = 65; /*!< \brief Length of strings (e.g. marker names) used in the native + SU2 binary mesh format. Shared by CSU2BinaryMeshReaderBase + and CSU2MeshBinaryFileWriter so they cannot drift apart. */ const int SU2_CONN_SIZE = 10; /*!< \brief Size of the connectivity array that is allocated for each element that we read from a mesh file in the format [[globalID vtkType n0 n1 n2 n3 n4 n5 n6 n7 n8]. */ const int SU2_CONN_SKIP = 2; /*!< \brief Offset to skip the globalID and VTK type at the start of the element connectivity list for each CGNS element. */ @@ -2189,21 +2192,26 @@ static const MapType Objective_Map = { }; /*! - * \brief Types of input file formats + * \brief Types of grid file formats */ -enum ENUM_INPUT { - SU2 = 1, /*!< \brief SU2 input format. */ - CGNS_GRID = 2, /*!< \brief CGNS input format for the computational grid. */ - RECTANGLE = 3, /*!< \brief 2D rectangular mesh with N x M points of size Lx x Ly. */ - BOX = 4 /*!< \brief 3D box mesh with N x M x L points of size Lx x Ly x Lz. */ +enum ENUM_GRID { + SU2 = 1, /*!< \brief SU2 ascii format. */ + SU2_BIN = 2, /*!< \brief SU2 binary format. */ + CGNS_GRID = 3, /*!< \brief CGNS format for the computational grid. */ + RECTANGLE = 4, /*!< \brief 2D rectangular mesh with N x M points of size Lx x Ly. */ + BOX = 5 /*!< \brief 3D box mesh with N x M x L points of size Lx x Ly x Lz. */ }; -static const MapType Input_Map = { - MakePair("SU2", SU2) - MakePair("CGNS", CGNS_GRID) - MakePair("RECTANGLE", RECTANGLE) - MakePair("BOX", BOX) +static const MapType Input_Map = { + MakePair("SU2", ENUM_GRID::SU2) + MakePair("SU2B", ENUM_GRID::SU2_BIN) + MakePair("CGNS", ENUM_GRID::CGNS_GRID) + MakePair("RECTANGLE", ENUM_GRID::RECTANGLE) + MakePair("BOX", ENUM_GRID::BOX) +}; +static const MapType OutputMesh_Map = { + MakePair("SU2", ENUM_GRID::SU2) + MakePair("SU2B", ENUM_GRID::SU2_BIN) }; - /*! * \brief Type of solution output file formats @@ -2219,7 +2227,8 @@ enum class OUTPUT_TYPE { PARAVIEW_LEGACY_BINARY, /*!< \brief Paraview binary format for the solution output. */ SURFACE_PARAVIEW_ASCII, /*!< \brief Paraview ASCII format for the solution output. */ SURFACE_PARAVIEW_LEGACY_BINARY, /*!< \brief Paraview binary format for the solution output. */ - MESH, /*!< \brief SU2 mesh format. */ + MESH, /*!< \brief SU2 ASCII mesh format. */ + MESH_BINARY, /*!< \brief SU2 binary mesh format. */ RESTART_BINARY, /*!< \brief SU2 binary restart format. */ RESTART_ASCII, /*!< \brief SU2 ASCII restart format. */ PARAVIEW_XML, /*!< \brief Paraview XML with binary data format */ @@ -2245,6 +2254,7 @@ static const MapType Output_Map = { MakePair("SURFACE_PARAVIEW", OUTPUT_TYPE::SURFACE_PARAVIEW_XML) MakePair("PARAVIEW_MULTIBLOCK", OUTPUT_TYPE::PARAVIEW_MULTIBLOCK) MakePair("MESH", OUTPUT_TYPE::MESH) + MakePair("MESH_BINARY", OUTPUT_TYPE::MESH_BINARY) MakePair("RESTART_ASCII", OUTPUT_TYPE::RESTART_ASCII) MakePair("RESTART", OUTPUT_TYPE::RESTART_BINARY) MakePair("CGNS", OUTPUT_TYPE::CGNS) diff --git a/Common/include/toolboxes/SwapBytes.hpp b/Common/include/toolboxes/SwapBytes.hpp new file mode 100644 index 000000000000..033dc49dd8dd --- /dev/null +++ b/Common/include/toolboxes/SwapBytes.hpp @@ -0,0 +1,39 @@ +/*! + * \file SwapBytes.hpp + * \brief Function to swap bytes of primitive data types. + * \author P. Gomes + * \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-2025, 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 . + */ + +#pragma once + +#include +#include + +/*! + * \brief Change storage of buffer to/from big endian from/to little endian + * \param buffer - Pointer to the beginning of the buffer + * \param nBytes - The size in bytes of an data entry + * \param nVar - The number of entries + */ +void SwapBytes(char* buffer, size_t nBytes, unsigned long nVar); diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index 2ae45cf340f8..3e1105848b17 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -37,6 +37,7 @@ #include "../include/basic_types/ad_structure.hpp" #include "../include/toolboxes/printing_toolbox.hpp" +#include "../include/toolboxes/SwapBytes.hpp" using namespace PrintingToolbox; @@ -598,12 +599,60 @@ void CConfig::addPythonOption(const string& name) { option_map.insert(pair(name, val)); } +unsigned short CConfig::CheckOpenSU2BinFile(const string& val_mesh_filename, bool readnDim) { + /*--- Check if the mesh file can be opened for binary reading. ---*/ + FILE *mesh_file = fopen(val_mesh_filename.c_str(), "rb"); + if ( !mesh_file ) + SU2_MPI::Error("There is no geometry file called " + val_mesh_filename, + CURRENT_FUNCTION); + + /*--- Read the size of the connectivity type and determine whether + or not byte swapping must be applied. The size of the connectivity + type must be either 4 or 8. ---*/ + int size_conn_type; + auto ret = fread(&size_conn_type, sizeof(int), 1, mesh_file); + if (ret != 1) + SU2_MPI::Error("Error while reading the file " + val_mesh_filename, + CURRENT_FUNCTION); + + bool swap_bytes = false; + if ((size_conn_type != 4) && (size_conn_type != 8)) { + SwapBytes((char *) &size_conn_type, sizeof(int), 1); + swap_bytes = true; + } + + if ((size_conn_type != 4) && (size_conn_type != 8)) + SU2_MPI::Error("The file " + val_mesh_filename + + " is not a valid SU2 binary file", CURRENT_FUNCTION); + + /*--- Skip the number of zones and the zone ID, + if the number of dimensions must be read. ---*/ + if( readnDim ) { + if( fseek(mesh_file, 2*sizeof(int), SEEK_CUR) ) + SU2_MPI::Error("Failed to jump forward in the file" + val_mesh_filename, + CURRENT_FUNCTION); + } + + /*--- Read the information to be returned. ---*/ + int info; + ret = fread(&info, sizeof(int), 1, mesh_file); + if (ret != 1) + SU2_MPI::Error("Error while reading the file " + val_mesh_filename, + CURRENT_FUNCTION); + if ( swap_bytes) + SwapBytes((char *) &info, sizeof(int), 1); + + fclose(mesh_file); + + return (unsigned short) info; +} + unsigned short CConfig::GetnZone(const string& val_mesh_filename, unsigned short val_format) { int nZone = 1; /* Default value if nothing is specified. */ switch (val_format) { - case SU2: { + case ENUM_GRID::SU2: { /*--- Local variables for reading the SU2 file. ---*/ string text_line; @@ -612,8 +661,8 @@ unsigned short CConfig::GetnZone(const string& val_mesh_filename, unsigned short /*--- Check if the mesh file can be opened for reading. ---*/ mesh_file.open(val_mesh_filename.c_str(), ios::in); if (mesh_file.fail()) - SU2_MPI::Error(string("There is no geometry file called ") + val_mesh_filename, - CURRENT_FUNCTION); + SU2_MPI::Error("There is no geometry file called " + val_mesh_filename, + CURRENT_FUNCTION); /*--- Read the SU2 mesh file until the zone data is reached or when it can be decided that it is not present. ---*/ @@ -639,7 +688,15 @@ unsigned short CConfig::GetnZone(const string& val_mesh_filename, unsigned short } - case CGNS_GRID: { + case ENUM_GRID::SU2_BIN: { + + /*--- Open and check the grid file and read the number of zones + at the correct location. */ + nZone = CheckOpenSU2BinFile(val_mesh_filename, false); + break; + } + + case ENUM_GRID::CGNS_GRID: { #ifdef HAVE_CGNS @@ -706,11 +763,11 @@ unsigned short CConfig::GetnZone(const string& val_mesh_filename, unsigned short break; } - case RECTANGLE: { + case ENUM_GRID::RECTANGLE: { nZone = 1; break; } - case BOX: { + case ENUM_GRID::BOX: { nZone = 1; break; } @@ -722,10 +779,10 @@ unsigned short CConfig::GetnZone(const string& val_mesh_filename, unsigned short unsigned short CConfig::GetnDim(const string& val_mesh_filename, unsigned short val_format) { - short nDim = -1; + int nDim = -1; switch (val_format) { - case SU2: { + case ENUM_GRID::SU2: { /*--- Local variables for reading the SU2 file. ---*/ string text_line; @@ -734,7 +791,7 @@ unsigned short CConfig::GetnDim(const string& val_mesh_filename, unsigned short /*--- Open grid file ---*/ mesh_file.open(val_mesh_filename.c_str(), ios::in); if (mesh_file.fail()) { - SU2_MPI::Error(string("The SU2 mesh file named ") + val_mesh_filename + string(" was not found."), CURRENT_FUNCTION); + SU2_MPI::Error("The SU2 mesh file named " + val_mesh_filename + " was not found.", CURRENT_FUNCTION); } /*--- Read the SU2 mesh file until the dimension data is reached @@ -760,14 +817,22 @@ unsigned short CConfig::GetnDim(const string& val_mesh_filename, unsigned short /*--- Throw an error if the dimension was not found. ---*/ if (nDim == -1) { - SU2_MPI::Error(val_mesh_filename + string(" is not an SU2 mesh file or has the wrong format \n ('NDIME=' not found). Please check."), + SU2_MPI::Error(val_mesh_filename + " is not an SU2 mesh file or has the wrong format \n ('NDIME=' not found). Please check.", CURRENT_FUNCTION); } break; } - case CGNS_GRID: { + case ENUM_GRID::SU2_BIN: { + + /*--- Open and check the grid file and read the number of dimensions + at the correct location. */ + nDim = CheckOpenSU2BinFile(val_mesh_filename, true); + break; + } + + case ENUM_GRID::CGNS_GRID: { #ifdef HAVE_CGNS @@ -816,11 +881,11 @@ unsigned short CConfig::GetnDim(const string& val_mesh_filename, unsigned short break; } - case RECTANGLE: { + case ENUM_GRID::RECTANGLE: { nDim = 2; break; } - case BOX: { + case ENUM_GRID::BOX: { nDim = 3; break; } @@ -2227,9 +2292,11 @@ void CConfig::SetConfig_Options() { /*!\brief ACTDISK_JUMP \n DESCRIPTION: The jump is given by the difference in values or a ratio */ addEnumOption("ACTDISK_JUMP", ActDisk_Jump, Jump_Map, DIFFERENCE); /*!\brief MESH_FORMAT \n DESCRIPTION: Mesh input file format \n OPTIONS: see \link Input_Map \endlink \n DEFAULT: SU2 \ingroup Config*/ - addEnumOption("MESH_FORMAT", Mesh_FileFormat, Input_Map, SU2); + addEnumOption("MESH_FORMAT", Mesh_FileFormat, Input_Map, ENUM_GRID::SU2); /* DESCRIPTION: Mesh input file */ addStringOption("MESH_FILENAME", Mesh_FileName, string("mesh")); + /*!\brief MESH_OUT_FORMAT \n DESCRIPTION: Mesh output file format \n OPTIONS: see \link OutputMesh_Map \endlink \n DEFAULT: SU2 \ingroup Config*/ + addEnumOption("MESH_OUT_FORMAT", Mesh_Out_FileFormat, OutputMesh_Map, ENUM_GRID::SU2); /*!\brief MESH_OUT_FILENAME \n DESCRIPTION: Mesh output file name. Used when converting, scaling, or deforming a mesh. \n DEFAULT: mesh_out \ingroup Config*/ addStringOption("MESH_OUT_FILENAME", Mesh_Out_FileName, string("mesh_out")); @@ -7682,7 +7749,7 @@ void CConfig::SetOutput(SU2_COMPONENT val_software, unsigned short val_izone) { } if (val_software == SU2_COMPONENT::SU2_DEF) { - cout << "Output mesh file name: " << GetMesh_Out_FileName() << ".su2. " << endl; + cout << "Output mesh file name: " << GetMesh_Out_FileName() << GetMesh_Out_FileExtension() << ". " << endl; switch (GetDeform_Stiffness_Type()) { case INVERSE_VOLUME: cout << "Cell stiffness scaled by inverse of the cell volume." << endl; diff --git a/Common/src/geometry/CPhysicalGeometry.cpp b/Common/src/geometry/CPhysicalGeometry.cpp index a965b248b672..5d4c77b8b2ae 100644 --- a/Common/src/geometry/CPhysicalGeometry.cpp +++ b/Common/src/geometry/CPhysicalGeometry.cpp @@ -33,6 +33,8 @@ #include "../../include/toolboxes/geometry_toolbox.hpp" #include "../../include/geometry/meshreader/CSU2ASCIIMeshReaderFEM.hpp" #include "../../include/geometry/meshreader/CSU2ASCIIMeshReaderFVM.hpp" +#include "../../include/geometry/meshreader/CSU2BinaryMeshReaderFEM.hpp" +#include "../../include/geometry/meshreader/CSU2BinaryMeshReaderFVM.hpp" #include "../../include/geometry/meshreader/CCGNSMeshReaderFVM.hpp" #include "../../include/geometry/meshreader/CCGNSMeshReaderFEM.hpp" #include "../../include/geometry/meshreader/CRectangularMeshReaderFEM.hpp" @@ -80,6 +82,7 @@ CPhysicalGeometry::CPhysicalGeometry(CConfig* config, unsigned short val_iZone, switch (val_format) { case SU2: + case SU2_BIN: case CGNS_GRID: case RECTANGLE: case BOX: @@ -3460,6 +3463,12 @@ void CPhysicalGeometry::Read_Mesh(CConfig* config, const string& val_mesh_filena else Mesh = new CSU2ASCIIMeshReaderFVM(config, val_iZone, val_nZone); break; + case SU2_BIN: + if (fem_solver) + Mesh = new CSU2BinaryMeshReaderFEM(config, val_iZone, val_nZone); + else + Mesh = new CSU2BinaryMeshReaderFVM(config, val_iZone, val_nZone); + break; case CGNS_GRID: if (fem_solver) Mesh = new CCGNSMeshReaderFEM(config, val_iZone, val_nZone); diff --git a/Common/src/geometry/meshreader/CSU2ASCIIMeshReaderBase.cpp b/Common/src/geometry/meshreader/CSU2ASCIIMeshReaderBase.cpp index 5a9a6da1fb46..a0eb84174560 100644 --- a/Common/src/geometry/meshreader/CSU2ASCIIMeshReaderBase.cpp +++ b/Common/src/geometry/meshreader/CSU2ASCIIMeshReaderBase.cpp @@ -30,10 +30,7 @@ CSU2ASCIIMeshReaderBase::CSU2ASCIIMeshReaderBase(CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone) - : CMeshReaderBase(val_config, val_iZone, val_nZone), - myZone(val_iZone), - nZones(val_nZone), - meshFilename(config->GetMesh_FileName()) {} + : CSU2MeshReaderBase(val_config, val_iZone, val_nZone) {} CSU2ASCIIMeshReaderBase::~CSU2ASCIIMeshReaderBase(void) = default; diff --git a/Common/src/geometry/meshreader/CSU2BinaryMeshReaderBase.cpp b/Common/src/geometry/meshreader/CSU2BinaryMeshReaderBase.cpp new file mode 100644 index 000000000000..5831efe0c544 --- /dev/null +++ b/Common/src/geometry/meshreader/CSU2BinaryMeshReaderBase.cpp @@ -0,0 +1,495 @@ +/*! + * \file CSU2BinaryMeshReaderBase.cpp + * \brief Helper class for the reading of a native SU2 binary grid file. + * \author T. Economon, E. van der Weide + * \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-2025, 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 "../../../include/toolboxes/CLinearPartitioner.hpp" +#include "../../../include/geometry/meshreader/CSU2BinaryMeshReaderBase.hpp" + +#include +#include + +CSU2BinaryMeshReaderBase::CSU2BinaryMeshReaderBase(CConfig* val_config, unsigned short val_iZone, + unsigned short val_nZone) + : CSU2MeshReaderBase(val_config, val_iZone, val_nZone) {} + +CSU2BinaryMeshReaderBase::~CSU2BinaryMeshReaderBase(void) = default; + +int CSU2BinaryMeshReaderBase::FileSeek64(FILE* file, int64_t offset, int whence) { +#if defined(_WIN32) + return _fseeki64(file, offset, whence); +#else + return fseeko(file, static_cast(offset), whence); +#endif +} + +int64_t CSU2BinaryMeshReaderBase::FileTell64(FILE* file) { +#if defined(_WIN32) + return _ftelli64(file); +#else + return static_cast(ftello(file)); +#endif +} + +void CSU2BinaryMeshReaderBase::ReadConnectivityType() { + /*--- Initialize the byte swapping to false and + read the size of the connectivity type. ---*/ + swap_bytes = false; + ReadBinaryData(&size_conn_type, 1); + + /*--- Check if byte swapping must be applied. ---*/ + if ((size_conn_type != 4) && (size_conn_type != 8)) { + SwapBytes((char*)&size_conn_type, sizeof(int), 1); + swap_bytes = true; + } + + /*--- The size of the connectivity type must be either 4 or 8. ---*/ + if ((size_conn_type != 4) && (size_conn_type != 8)) + SU2_MPI::Error(string("The file ") + meshFilename + string(" is not a valid SU2 binary file"), CURRENT_FUNCTION); +} + +void CSU2BinaryMeshReaderBase::ReadMetadata(CConfig* config) { + const bool harmonic_balance = config->GetTime_Marching() == TIME_MARCHING::HARMONIC_BALANCE; + const bool multizone_file = config->GetMultizone_Mesh(); + + /*--- Open the grid file and check if it went OK. ---*/ + mesh_file = fopen(meshFilename.c_str(), "rb"); + if (!mesh_file) + SU2_MPI::Error( + string("Error opening SU2 binary grid file ") + meshFilename + string(". Check that the file exists"), + CURRENT_FUNCTION); + + /*--- Read the size of the connectivity type and check if byte swapping + must be applied. ---*/ + ReadConnectivityType(); + + /*--- Check for a harmonic balance simulation. If So, the data of the + first zone can be read. Otherwise jump to the location of the + current zone. ---*/ + if (harmonic_balance) { + if (rank == MASTER_NODE) cout << "Reading time instance " << config->GetiInst() + 1 << "." << endl; + FileSeek64(mesh_file, 2 * sizeof(int), SEEK_SET); + } else { + FastForwardToMyZone(); + if (nZones > 1 && multizone_file) { + if (rank == MASTER_NODE) cout << "Reading zone " << myZone << " from native SU2 binary mesh." << endl; + } + } + + /*--- Read the meta data from the current position. ---*/ + ReadMetadataZone(); + + /*--- Close the grid file again. ---*/ + fclose(mesh_file); +} + +void CSU2BinaryMeshReaderBase::ReadPointCoordinates() { + /* No support yet for actuator disks */ + if (actuator_disk) SU2_MPI::Error("No support for actuator disks yet", CURRENT_FUNCTION); + + /* Jump over the number of points, because it is already known, and + determine the position in the file where the point section ends. */ + FileSeek64(mesh_file, size_conn_type, SEEK_CUR); + auto pos_end_point = FileTell64(mesh_file) + numberOfGlobalPoints * (dimension * sizeof(double) + size_conn_type); + + /* Define a linear partitioner for the points. */ + CLinearPartitioner pointPartitioner(numberOfGlobalPoints, 0); + + /* Jump to the position in the file where the points are stored + that this rank must read. */ + const auto firstIndex = pointPartitioner.GetFirstIndexOnRank(rank); + FileSeek64(mesh_file, firstIndex * (dimension * sizeof(double) + size_conn_type), SEEK_CUR); + + /* Determine the number of local points and prepare the local + data structure to store the point coordinates. */ + numberOfLocalPoints = pointPartitioner.GetSizeOnRank(rank); + localPointCoordinates.resize(dimension); + for (int k = 0; k < dimension; k++) localPointCoordinates[k].resize(numberOfLocalPoints); + + /*--- Read the point coordinates into our data structure. ---*/ + for (unsigned long i = 0; i < numberOfLocalPoints; ++i) { + double Coords[3]; + ReadBinaryData(Coords, dimension); + FileSeek64(mesh_file, size_conn_type, SEEK_CUR); + + for (unsigned short iDim = 0; iDim < dimension; iDim++) { + localPointCoordinates[iDim][i] = Coords[iDim]; + } + } + + /* Jump to the end of the coordinate section. */ + FileSeek64(mesh_file, pos_end_point, SEEK_SET); +} + +void CSU2BinaryMeshReaderBase::ReadVolumeElementConnectivity() { + /* Jump over the zone ID, number of dimensions and number of elements, + because this information is already known. */ + FileSeek64(mesh_file, size_conn_type + 2 * sizeof(int), SEEK_CUR); + + /* Get a linear partitioner of the elements. */ + CLinearPartitioner elemPartitioner(numberOfGlobalElements, 0); + + /* Determine the position at the end of the offset array, where + the total size of the connectivity is stored. */ + const auto first_index = elemPartitioner.GetFirstIndexOnRank(rank); + const auto pos_size_global_conn = FileTell64(mesh_file) + numberOfGlobalElements * size_conn_type; + + /* Jump to position in the file where the offset data is stored for + the element range this rank will read. Allocate the memory for + this offset array. */ + FileSeek64(mesh_file, first_index * size_conn_type, SEEK_CUR); + numberOfLocalElements = elemPartitioner.GetSizeOnRank(rank); + vector offset(numberOfLocalElements + 1); + + /* Read the offset array from the file. It will be stored as uint64_t, + but it may be stored differently in the file. */ + if (size_conn_type == 4) { + vector tmp(offset.size()); + ReadBinaryData(tmp.data(), tmp.size()); + for (size_t i = 0; i < tmp.size(); ++i) offset[i] = static_cast(tmp[i]); + } else { + ReadBinaryData(offset.data(), offset.size()); + } + + /* Jump to the location where the total size of the connectivity array + is stored and read the size. Determine the location of the end of + the connectivity array. */ + FileSeek64(mesh_file, pos_size_global_conn, SEEK_SET); + const auto size_global_conn = ReadBinaryNEntities(); + const auto pos_end_conn = FileTell64(mesh_file) + size_global_conn * size_conn_type; + + /* Read the connectivity data of the elements this rank should read. + Store the data in uint64_t. */ + const auto size_conn = offset.back() - offset[0]; + vector conn_buff(size_conn); + FileSeek64(mesh_file, offset[0] * size_conn_type, SEEK_CUR); + + if (size_conn_type == 4) { + vector tmp(conn_buff.size()); + ReadBinaryData(tmp.data(), tmp.size()); + for (size_t i = 0; i < tmp.size(); ++i) conn_buff[i] = static_cast(tmp[i]); + } else { + ReadBinaryData(conn_buff.data(), conn_buff.size()); + } + + /* Jump to the end of the connectivity data. */ + FileSeek64(mesh_file, pos_end_conn, SEEK_SET); + +#ifdef HAVE_MPI + + /* Update the offset, such that it corresponds to the data in the + local connectivity buffer. */ + for (size_t i = 1; i < offset.size(); ++i) offset[i] -= offset[0]; + offset[0] = 0; + + /* Get a linear partitioner of the points. */ + CLinearPartitioner pointPartitioner(numberOfGlobalPoints, 0); + + /*--- Determine the ranks on which the elements must actually be stored. + Note that an element can be stored on multiple ranks, as the points + must be surrounded by all its elements. ---*/ + std::vector ranks_elements; + ranks_elements.reserve(numberOfLocalElements); + std::vector number_of_ranks_elements(numberOfLocalElements + 1); + number_of_ranks_elements[0] = 0; + + for (unsigned long i = 0; i < numberOfLocalElements; ++i) { + /* Determine the ranks where this element must be stored by looping + over its nodes. */ + set ranks_this_elem; + for (uint64_t j = offset[i] + 1; j < (offset[i + 1] - 1); ++j) { + auto rank_node = pointPartitioner.GetRankContainingIndex(conn_buff[j]); + ranks_this_elem.insert(static_cast(rank_node)); + } + + /* Store the data. */ + number_of_ranks_elements[i + 1] = number_of_ranks_elements[i] + ranks_this_elem.size(); + for (auto rank_elem : ranks_this_elem) ranks_elements.push_back(rank_elem); + } + + /* Create the send buffers. Both the size of each connectivity + information and the connectivity information itself is stored.*/ + std::vector> send_buf; + send_buf.resize(size); + + for (unsigned long i = 0; i < numberOfLocalElements; ++i) { + for (uint64_t j = number_of_ranks_elements[i]; j < number_of_ranks_elements[i + 1]; ++j) { + const int ii = ranks_elements[j]; + + auto size_this_conn = offset[i + 1] - offset[i]; + send_buf[ii].push_back(size_this_conn); + for (uint64_t k = offset[i]; k < offset[i + 1]; ++k) send_buf[ii].push_back(conn_buff[k]); + } + } + + /* Determine the number of ranks from which this rank will receive data. + Allow for self communication. */ + int nRankRecv; + vector sendToRank(size, 0), sizeSend(size, 1); + for (auto rank_elem : ranks_elements) sendToRank[rank_elem] = 1; + SU2_MPI::Reduce_scatter(sendToRank.data(), &nRankRecv, sizeSend.data(), MPI_INT, MPI_SUM, SU2_MPI::GetComm()); + + /* Explicitly delete the memory that is not needed anymore. */ + vector().swap(offset); + vector().swap(conn_buff); + vector().swap(sizeSend); + + /* Determine the number of ranks to which this rank will send data + and allocate the memory for the send requests. */ + int nRankSend = 0; + for (int i = 0; i < size; ++i) { + if (sendToRank[i]) ++nRankSend; + } + + vector sendReqs(nRankSend); + + /* Send the data using non-blocking sends. */ + nRankSend = 0; + for (int i = 0; i < size; ++i) { + if (sendToRank[i]) { + SU2_MPI::Isend(send_buf[i].data(), send_buf[i].size(), MPI_UNSIGNED_LONG, i, i, SU2_MPI::GetComm(), + &sendReqs[nRankSend]); + ++nRankSend; + } + } + + /* Define the receive buffers and receive the messages. */ + std::vector> recv_buf; + recv_buf.resize(size); + + for (int i = 0; i < nRankRecv; ++i) { + SU2_MPI::Status status; + SU2_MPI::Probe(MPI_ANY_SOURCE, rank, SU2_MPI::GetComm(), &status); + int rankRecv = status.MPI_SOURCE; + + int sizeMess; + SU2_MPI::Get_count(&status, MPI_UNSIGNED_LONG, &sizeMess); + recv_buf[rankRecv].resize(sizeMess); + SU2_MPI::Recv(recv_buf[rankRecv].data(), sizeMess, MPI_UNSIGNED_LONG, rankRecv, rank, SU2_MPI::GetComm(), &status); + } + + /* Complete the non-blocking sends and release the memory of the send buffers. */ + SU2_MPI::Waitall(nRankSend, sendReqs.data(), MPI_STATUSES_IGNORE); + for (int i = 0; i < size; ++i) { + if (sendToRank[i]) vector().swap(send_buf[i]); + } + + /* Synchronize the MPI ranks, because wild cards have been used. */ + SU2_MPI::Barrier(SU2_MPI::GetComm()); + + /*--- Store the information in the receive buffers in the offset and conn_buff + vectors, such that it is consistent with the information without MPI. + Release the memory of the receive buffers afterwards. ---*/ + offset.push_back(0); + for (int i = 0; i < size; ++i) { + if (recv_buf[i].size() > 0) { + size_t ind = 0; + while (ind < recv_buf[i].size()) { + auto n_items = recv_buf[i][ind++]; + offset.push_back(offset.back() + n_items); + for (unsigned long j = 0; j < n_items; ++j, ++ind) conn_buff.push_back(recv_buf[i][ind]); + } + vector().swap(recv_buf[i]); + } + } + +#endif + + /*--- Extract the connectivity data from conn_buf and store the data + in the appropriate member variables. ---*/ + numberOfLocalElements = offset.size() - 1; + array connectivity{}; + + for (unsigned long i = 0; i < numberOfLocalElements; ++i) { + auto ind = offset[i]; + auto size_this_elem = static_cast(offset[i + 1] - offset[i]); + auto VTK_Type = conn_buff[ind++]; + const auto nPointsElem = nPointsOfElementType(static_cast(VTK_Type)); + if (size_this_elem != (nPointsElem + 2)) + SU2_MPI::Error("Wrong number of items in volume connectivity", CURRENT_FUNCTION); + + for (unsigned short j = 0; j < nPointsElem; ++j, ++ind) connectivity[j] = conn_buff[ind]; + auto GlobalIndex = conn_buff[ind]; + + localVolumeElementConnectivity.push_back(GlobalIndex); + localVolumeElementConnectivity.push_back(VTK_Type); + /// TODO: Use a compressed format. + for (unsigned short j = 0; j < N_POINTS_HEXAHEDRON; ++j) { + localVolumeElementConnectivity.push_back(connectivity[j]); + } + } +} + +void CSU2BinaryMeshReaderBase::ReadSurfaceElementConnectivity() { + /* The number of surface markers is already known, so jump over it. */ + FileSeek64(mesh_file, sizeof(int), SEEK_CUR); + + /* Allocate the memory for the first index of the connectivity of the + surface elements and the marker names. Note that all ranks store + the entire surface connectivity. */ + surfaceElementConnectivity.resize(numberOfMarkers); + markerNames.resize(numberOfMarkers); + + array connectivity{}; + + /* Loop over the number of markers. */ + for (unsigned long iMarker = 0; iMarker < numberOfMarkers; ++iMarker) { + /* Read the name of the surface marker. */ + char charStr[SU2_STRING_SIZE]; + ReadBinaryData(charStr, SU2_STRING_SIZE); + charStr[SU2_STRING_SIZE - 1] = '\0'; + markerNames[iMarker] = string(charStr); + + /*--- Throw an error if we find deprecated references to SEND_RECEIVE + boundaries in the mesh. ---*/ + if (markerNames[iMarker] == "SEND_RECEIVE") + SU2_MPI::Error( + "Mesh file contains deprecated SEND_RECEIVE marker!\n" + "Please remove any SEND_RECEIVE markers from the SU2 binary mesh.", + CURRENT_FUNCTION); + + /* Read the number of elements for this boundary marker. */ + const auto nElem_Bound = ReadBinaryNEntities(); + + /*--- Read the offset array from the file. It will be stored as + uint64_t, but it may be stored differently in the file. ---*/ + vector offset(nElem_Bound + 1); + if (size_conn_type == 4) { + vector tmp(offset.size()); + ReadBinaryData(tmp.data(), tmp.size()); + for (size_t i = 0; i < tmp.size(); ++i) offset[i] = static_cast(tmp[i]); + } else { + ReadBinaryData(offset.data(), offset.size()); + } + + /*--- Read the connectivity and store it in a buffer. + Always use uint64_t for this internally. ---*/ + vector conn_buff(offset.back()); + if (size_conn_type == 4) { + vector tmp(conn_buff.size()); + ReadBinaryData(tmp.data(), tmp.size()); + for (size_t i = 0; i < tmp.size(); ++i) conn_buff[i] = static_cast(tmp[i]); + } else { + ReadBinaryData(conn_buff.data(), conn_buff.size()); + } + + /*--- Loop over the surface elements to store the connectivity + in the required data structures. ---*/ + for (unsigned long i = 0; i < nElem_Bound; ++i) { + auto ind = offset[i]; + auto size_this_elem = static_cast(offset[i + 1] - offset[i]); + auto VTK_Type = conn_buff[ind++]; + const auto nPointsElem = nPointsOfElementType(static_cast(VTK_Type)); + if (size_this_elem != (nPointsElem + 1)) + SU2_MPI::Error("Wrong number of items in surface connectivity", CURRENT_FUNCTION); + + if (dimension == 3 && VTK_Type == LINE) { + SU2_MPI::Error( + "Line boundary conditions are not possible for 3D calculations.\n" + "Please check the SU2 binary file.", + CURRENT_FUNCTION); + } + + for (unsigned short j = 0; j < nPointsElem; ++j, ++ind) connectivity[j] = conn_buff[ind]; + + surfaceElementConnectivity[iMarker].push_back(0); + surfaceElementConnectivity[iMarker].push_back(VTK_Type); + for (unsigned short j = 0; j < N_POINTS_HEXAHEDRON; ++j) { + surfaceElementConnectivity[iMarker].push_back(connectivity[j]); + } + } + } +} + +void CSU2BinaryMeshReaderBase::FastForwardToMyZone() { + /*--- Jump to the position where the data starts for the first zone. ---*/ + FileSeek64(mesh_file, 2 * sizeof(int), SEEK_SET); + + /*--- If there is only a single zone, or if this file holds a mesh that is + shared by all problem zones (MULTIZONE_MESH= NO), there is nothing to + skip: every zone reads the same (first) block of data, exactly as the + ASCII reader does. ---*/ + if (nZones == 1 || !config->GetMultizone_Mesh()) return; + + /*--- Loop over the lower numbered zones and read their meta data. ---*/ + for (int zone = 0; zone < myZone; ++zone) ReadMetadataZone(); +} + +uint64_t CSU2BinaryMeshReaderBase::ReadBinaryNEntities() { + /*--- Define the return value as an uint64_t. ---*/ + uint64_t nEntities; + + /*--- Read the actual data, depending on the connectivity type. ---*/ + if (size_conn_type == 4) { + uint32_t dummy; + ReadBinaryData(&dummy, 1); + nEntities = static_cast(dummy); + } else { + ReadBinaryData(&nEntities, 1); + } + + return nEntities; +} + +void CSU2BinaryMeshReaderBase::ReadMetadataZone() { + /*--- Skip the zone ID and read the number of dimensions. ---*/ + int nDim; + FileSeek64(mesh_file, sizeof(int), SEEK_CUR); + ReadBinaryData(&nDim, 1); + dimension = static_cast(nDim); + + /*--- Read the number of elements. ---*/ + const auto nElem = ReadBinaryNEntities(); + numberOfGlobalElements = static_cast(nElem); + + /*--- Jump to the end of the offset section, read the size of + the connectivity and jump over it. ---*/ + FileSeek64(mesh_file, nElem * size_conn_type, SEEK_CUR); + const auto size_conn = ReadBinaryNEntities(); + FileSeek64(mesh_file, size_conn * size_conn_type, SEEK_CUR); + + /*--- Read the number of points and jump over the coordinate section. ---*/ + const auto nPoints = ReadBinaryNEntities(); + numberOfGlobalPoints = static_cast(nPoints); + FileSeek64(mesh_file, nPoints * (nDim * sizeof(double) + size_conn_type), SEEK_CUR); + + /*--- Read the number of markers and loop over them. ---*/ + int nMark; + ReadBinaryData(&nMark, 1); + numberOfMarkers = static_cast(nMark); + + for (int mark = 0; mark < nMark; ++mark) { + /*--- Jump over the name of the marker and read + the number of surface elements. ---*/ + FileSeek64(mesh_file, SU2_STRING_SIZE * sizeof(char), SEEK_CUR); + const auto nElemMark = ReadBinaryNEntities(); + + /*--- Jump to the end of the offset section of this marker, read + the size of the connectivity and jump over it. ---*/ + FileSeek64(mesh_file, nElemMark * size_conn_type, SEEK_CUR); + const auto size_conn_mark = ReadBinaryNEntities(); + FileSeek64(mesh_file, size_conn_mark * size_conn_type, SEEK_CUR); + } +} diff --git a/Common/src/geometry/meshreader/CSU2BinaryMeshReaderFEM.cpp b/Common/src/geometry/meshreader/CSU2BinaryMeshReaderFEM.cpp new file mode 100644 index 000000000000..e3d4ceed2e6c --- /dev/null +++ b/Common/src/geometry/meshreader/CSU2BinaryMeshReaderFEM.cpp @@ -0,0 +1,73 @@ +/*! + * \file CSU2BinaryMeshReaderFEM.cpp + * \brief Reads a native SU2 binary grid into linear partitions for the + * finite element solver (FEM). + * \author T. Economon, E. van der Weide + * \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-2025, 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 "../../../include/toolboxes/CLinearPartitioner.hpp" +#include "../../../include/geometry/meshreader/CSU2BinaryMeshReaderFEM.hpp" +#include "../../../include/fem/fem_standard_element.hpp" + +CSU2BinaryMeshReaderFEM::CSU2BinaryMeshReaderFEM(CConfig* val_config, unsigned short val_iZone, + unsigned short val_nZone) + : CSU2BinaryMeshReaderBase(val_config, val_iZone, val_nZone) { + /* Read the basic metadata and perform some basic error checks. */ + ReadMetadata(val_config); + + /*--- Open the file with the mesh and go to the place where the data + of the current zone is stored. ---*/ + mesh_file = fopen(meshFilename.c_str(), "rb"); + if (!mesh_file) + SU2_MPI::Error( + string("Error opening SU2 binary grid file ") + meshFilename + string(". Check that the file exists"), + CURRENT_FUNCTION); + FastForwardToMyZone(); + + /*--- Read the volume connectivity and distribute it + linearly over the MPI ranks. ---*/ + ReadVolumeElementConnectivity(); + + /*--- Read the coordinates of the points that are needed + on this MPI rank. ---*/ + ReadPointCoordinates(); + + /*--- Read the surface connectivity and store the surface elements whose + corresponding volume element is stored on this MPI rank. ---*/ + ReadSurfaceElementConnectivity(); + + fclose(mesh_file); +} + +CSU2BinaryMeshReaderFEM::~CSU2BinaryMeshReaderFEM() = default; + +void CSU2BinaryMeshReaderFEM::ReadPointCoordinates() { SU2_MPI::Error("Not implemented yet", CURRENT_FUNCTION); } + +void CSU2BinaryMeshReaderFEM::ReadVolumeElementConnectivity() { + SU2_MPI::Error("Not implemented yet", CURRENT_FUNCTION); +} + +void CSU2BinaryMeshReaderFEM::ReadSurfaceElementConnectivity() { + SU2_MPI::Error("Not implemented yet", CURRENT_FUNCTION); +} diff --git a/Common/src/geometry/meshreader/CSU2BinaryMeshReaderFVM.cpp b/Common/src/geometry/meshreader/CSU2BinaryMeshReaderFVM.cpp new file mode 100644 index 000000000000..96905c0512a2 --- /dev/null +++ b/Common/src/geometry/meshreader/CSU2BinaryMeshReaderFVM.cpp @@ -0,0 +1,66 @@ +/*! + * \file CSU2BinaryMeshReaderFVM.cpp + * \brief Reads a native SU2 binary grid into linear partitions for the + * finite volume solver (FVM). + * \author T. Economon + * \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-2025, 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 "../../../include/geometry/meshreader/CSU2BinaryMeshReaderFVM.hpp" + +CSU2BinaryMeshReaderFVM::CSU2BinaryMeshReaderFVM(CConfig* val_config, unsigned short val_iZone, + unsigned short val_nZone) + : CSU2BinaryMeshReaderBase(val_config, val_iZone, val_nZone) { + actuator_disk = (((config->GetnMarker_ActDiskInlet() != 0) || (config->GetnMarker_ActDiskOutlet() != 0)) && + ((config->GetKind_SU2() == SU2_COMPONENT::SU2_CFD) || + ((config->GetKind_SU2() == SU2_COMPONENT::SU2_DEF) && (config->GetActDisk_SU2_DEF())))); + if (config->GetActDisk_DoubleSurface()) actuator_disk = false; + + /* Read the basic metadata and perform some basic error checks. */ + ReadMetadata(val_config); + + /* If the mesh contains an actuator disk as a single surface, + we need to first split the surface into repeated points and update + the connectivity for each element touching the surface. */ + if (actuator_disk) SplitActuatorDiskSurface(); + + /* Read and store the points, interior elements, and surface elements. + We store only the points and interior elements on our rank's linear + partition, but the master stores the entire set of surface connectivity. */ + mesh_file = fopen(meshFilename.c_str(), "rb"); + if (!mesh_file) + SU2_MPI::Error( + string("Error opening SU2 binary grid file ") + meshFilename + string(". Check that the file exists"), + CURRENT_FUNCTION); + + FastForwardToMyZone(); + ReadVolumeElementConnectivity(); + ReadPointCoordinates(); + ReadSurfaceElementConnectivity(); + + fclose(mesh_file); +} + +CSU2BinaryMeshReaderFVM::~CSU2BinaryMeshReaderFVM() = default; + +void CSU2BinaryMeshReaderFVM::SplitActuatorDiskSurface() { SU2_MPI::Error("Not implemented yet", CURRENT_FUNCTION); } diff --git a/Common/src/geometry/meshreader/CSU2MeshReaderBase.cpp b/Common/src/geometry/meshreader/CSU2MeshReaderBase.cpp new file mode 100644 index 000000000000..ef919ef724b1 --- /dev/null +++ b/Common/src/geometry/meshreader/CSU2MeshReaderBase.cpp @@ -0,0 +1,37 @@ +/*! + * \file CSU2MeshReaderBase.cpp + * \brief Helper class for the reading of a native SU2 grid file. + * \author T. Economon + * \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-2025, 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 "../../../include/toolboxes/CLinearPartitioner.hpp" +#include "../../../include/geometry/meshreader/CSU2MeshReaderBase.hpp" + +CSU2MeshReaderBase::CSU2MeshReaderBase(CConfig* val_config, unsigned short val_iZone, unsigned short val_nZone) + : CMeshReaderBase(val_config, val_iZone, val_nZone), + myZone(val_iZone), + nZones(val_nZone), + meshFilename(config->GetMesh_FileName()) {} + +CSU2MeshReaderBase::~CSU2MeshReaderBase(void) = default; diff --git a/Common/src/geometry/meshreader/meson.build b/Common/src/geometry/meshreader/meson.build index 543bdfcf97a7..d6c8342960c9 100644 --- a/Common/src/geometry/meshreader/meson.build +++ b/Common/src/geometry/meshreader/meson.build @@ -9,4 +9,8 @@ common_src += files(['CBoxMeshReaderFEM.cpp', 'CRectangularMeshReaderFVM.cpp', 'CSU2ASCIIMeshReaderBase.cpp', 'CSU2ASCIIMeshReaderFEM.cpp', - 'CSU2ASCIIMeshReaderFVM.cpp']) + 'CSU2ASCIIMeshReaderFVM.cpp', + 'CSU2BinaryMeshReaderBase.cpp', + 'CSU2BinaryMeshReaderFEM.cpp', + 'CSU2BinaryMeshReaderFVM.cpp', + 'CSU2MeshReaderBase.cpp']) diff --git a/Common/src/grid_movement/CSurfaceMovement.cpp b/Common/src/grid_movement/CSurfaceMovement.cpp index f43e7f89c593..6eb1b09ab409 100644 --- a/Common/src/grid_movement/CSurfaceMovement.cpp +++ b/Common/src/grid_movement/CSurfaceMovement.cpp @@ -5046,7 +5046,7 @@ void CSurfaceMovement::WriteFFDInfo(CSurfaceMovement** surface_movement, CGeomet if (rank == MASTER_NODE) { /*--- Read the name of the output file ---*/ - auto str = config[ZONE_0]->GetMesh_Out_FileName() + ".su2"; + auto str = config[ZONE_0]->GetMesh_Out_FileName() + config[ZONE_0]->GetMesh_Out_FileExtension(); output_file.precision(15); output_file.open(str, ios::out | ios::app); diff --git a/Common/src/toolboxes/SwapBytes.cpp b/Common/src/toolboxes/SwapBytes.cpp new file mode 100644 index 000000000000..6f4d43504570 --- /dev/null +++ b/Common/src/toolboxes/SwapBytes.cpp @@ -0,0 +1,53 @@ +/*! + * \file SwapBytes.cpp + * \brief Function to swap bytes of primitive data types + * \author P. Gomes + * \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-2025, 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 "../../include/toolboxes/SwapBytes.hpp" + +/*--- Function to swap bytes, in case we need to convert between + big and little endian storage. ---*/ +void SwapBytes(char* buffer, size_t nBytes, unsigned long nVar) { + /*--- Store half the number of bytes in kk. ---*/ + const int kk = (int)nBytes / 2; + + /*--- Loop over the number of variables in the buffer. ---*/ + for (unsigned long j = 0; j < nVar; j++) { + /*--- Initialize ii and jj, which are used to store the + indices of the bytes to be swapped. ---*/ + unsigned long ii = j * nBytes; + unsigned long jj = ii + nBytes - 1; + + /*--- Swap the bytes. ---*/ + for (int i = 0; i < kk; i++) { + char tmp = buffer[jj]; + buffer[jj] = buffer[ii]; + buffer[ii] = tmp; + + ii++; + jj--; + } + } +} diff --git a/Common/src/toolboxes/meson.build b/Common/src/toolboxes/meson.build index fa5e3b234c34..3240bac986d5 100644 --- a/Common/src/toolboxes/meson.build +++ b/Common/src/toolboxes/meson.build @@ -2,7 +2,8 @@ common_src += files(['CLinearPartitioner.cpp', 'printing_toolbox.cpp', 'C1DInterpolation.cpp', 'CSquareMatrixCM.cpp', - 'CSymmetricMatrix.cpp']) + 'CSymmetricMatrix.cpp', + 'SwapBytes.cpp']) subdir('MMS') subdir('fem') diff --git a/SU2_CFD/include/output/filewriter/CParaviewBinaryFileWriter.hpp b/SU2_CFD/include/output/filewriter/CParaviewBinaryFileWriter.hpp index 53617a633d42..507741a0e58f 100644 --- a/SU2_CFD/include/output/filewriter/CParaviewBinaryFileWriter.hpp +++ b/SU2_CFD/include/output/filewriter/CParaviewBinaryFileWriter.hpp @@ -60,16 +60,5 @@ class CParaviewBinaryFileWriter final: public CFileWriter{ * \param[in] val_filename - The name of the file */ void WriteData(string val_filename) override ; - -private: - - /*! - * \brief Change storage of buffer from big endian to little endian - * \param buffer - Pointer to the beginning of the buffer - * \param nBytes - The size in bytes of an data entry - * \param nVar - The number of entries - */ - void SwapBytes(char *buffer, size_t nBytes, unsigned long nVar); - }; diff --git a/SU2_CFD/include/output/filewriter/CSU2MeshBinaryFileWriter.hpp b/SU2_CFD/include/output/filewriter/CSU2MeshBinaryFileWriter.hpp new file mode 100644 index 000000000000..de3c681c713a --- /dev/null +++ b/SU2_CFD/include/output/filewriter/CSU2MeshBinaryFileWriter.hpp @@ -0,0 +1,59 @@ +/*! + * \file CSU2MeshBinaryFileWriter.hpp + * \brief Headers for the SU2 binary mesh file writer class. + * \author E. van der Weide + * \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 . + */ +#pragma once +#include "CFileWriter.hpp" + +class CSU2MeshBinaryFileWriter final: public CFileWriter{ + +private: + unsigned short iZone, //!< Index of the current zone + nZone; //!< Number of zones + +public: + + /*! + * \brief File extension + */ + const static string fileExt; + + /*! + * \brief Construct a file writer using field names, dimension. + * \param[in] valDataSorter - The parallel sorted data to write + * \param[in] valiZone - The index of the current zone + * \param[in] valnZone - The total number of zones + */ + CSU2MeshBinaryFileWriter(CParallelDataSorter* valDataSorter, + unsigned short valiZone, unsigned short valnZone); + + /*! + * \brief Write sorted data to file in SU2 mesh file format + * \param[in] val_filename - The name of the file + */ + void WriteData(string val_filename) override ; + +}; + diff --git a/SU2_CFD/src/meson.build b/SU2_CFD/src/meson.build index 3b40822a34f4..d4db53843e9b 100644 --- a/SU2_CFD/src/meson.build +++ b/SU2_CFD/src/meson.build @@ -51,6 +51,7 @@ su2_cfd_src += files(['output/COutputFactory.cpp', 'output/filewriter/CParaviewXMLFileWriter.cpp', 'output/filewriter/CParaviewVTMFileWriter.cpp', 'output/filewriter/CSU2MeshFileWriter.cpp', + 'output/filewriter/CSU2MeshBinaryFileWriter.cpp', 'output/filewriter/CCGNSFileWriter.cpp', 'output/tools/CWindowingTools.cpp']) diff --git a/SU2_CFD/src/output/COutput.cpp b/SU2_CFD/src/output/COutput.cpp index 5cc9a1fdd208..e210fbb59c4f 100644 --- a/SU2_CFD/src/output/COutput.cpp +++ b/SU2_CFD/src/output/COutput.cpp @@ -49,6 +49,7 @@ #include "../../include/output/filewriter/CSU2FileWriter.hpp" #include "../../include/output/filewriter/CSU2BinaryFileWriter.hpp" #include "../../include/output/filewriter/CSU2MeshFileWriter.hpp" +#include "../../include/output/filewriter/CSU2MeshBinaryFileWriter.hpp" namespace { volatile sig_atomic_t STOP; @@ -475,6 +476,25 @@ void COutput::WriteToFile(CConfig *config, CGeometry *geometry, OUTPUT_TYPE form break; + case OUTPUT_TYPE::MESH_BINARY: + + extension = CSU2MeshBinaryFileWriter::fileExt; + + if (fileName.empty()) + fileName = config->GetFilename(volumeFilename, "", curTimeIter); + + if (!config->GetWrt_Volume_Overwrite()) + filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); + + /*--- Load and sort the output data and connectivity. ---*/ + + volumeDataSorter->SortConnectivity(config, geometry, true); + + LogOutputFiles("SU2 binary mesh"); + fileWriter = new CSU2MeshBinaryFileWriter(volumeDataSorter, config->GetiZone(), config->GetnZone()); + + break; + case OUTPUT_TYPE::TECPLOT_BINARY: extension = CTecplotBinaryFileWriter::fileExt; diff --git a/SU2_CFD/src/output/filewriter/CParaviewBinaryFileWriter.cpp b/SU2_CFD/src/output/filewriter/CParaviewBinaryFileWriter.cpp index 7a9f3378a4ae..bde27d26bc65 100644 --- a/SU2_CFD/src/output/filewriter/CParaviewBinaryFileWriter.cpp +++ b/SU2_CFD/src/output/filewriter/CParaviewBinaryFileWriter.cpp @@ -26,6 +26,7 @@ */ #include "../../../include/output/filewriter/CParaviewBinaryFileWriter.hpp" +#include "../../../../Common/include/toolboxes/SwapBytes.hpp" const string CParaviewBinaryFileWriter::fileExt = ".vtk"; @@ -306,37 +307,3 @@ void CParaviewBinaryFileWriter::WriteData(string val_filename){ CloseMPIFile(); } - - -/*--- Subroutine to swap bytes, in case we need to convert to - big endian, which is expected for ParaView binary legacy format. ---*/ - -void CParaviewBinaryFileWriter::SwapBytes(char *buffer, size_t nBytes, unsigned long nVar) { - - /*--- Store half the number of bytes in kk. ---*/ - - const int kk = (int)nBytes/2; - - /*--- Loop over the number of variables in the buffer. ---*/ - - for (int j = 0; j < (int)nVar; j++) { - - /*--- Initialize ii and jj, which are used to store the - indices of the bytes to be swapped. ---*/ - - int ii = j*(int)nBytes; - int jj = ii + (int)nBytes - 1; - - /*--- Swap the bytes. ---*/ - - for (int i = 0; i < kk; i++) { - char tmp = buffer[jj]; - buffer[jj] = buffer[ii]; - buffer[ii] = tmp; - - ii++; - jj--; - - } - } -} diff --git a/SU2_CFD/src/output/filewriter/CSU2MeshBinaryFileWriter.cpp b/SU2_CFD/src/output/filewriter/CSU2MeshBinaryFileWriter.cpp new file mode 100644 index 000000000000..3349adcecf53 --- /dev/null +++ b/SU2_CFD/src/output/filewriter/CSU2MeshBinaryFileWriter.cpp @@ -0,0 +1,306 @@ +/*! + * \file CSU2MeshBinaryFileWriter.cpp + * \brief Filewriter class SU2 binary mesh format. + * \author E. van der Weide + * \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 "../../../include/output/filewriter/CSU2MeshBinaryFileWriter.hpp" +#include "../../../../Common/include/toolboxes/printing_toolbox.hpp" + +#include +#include +#include +#include +#include + +const string CSU2MeshBinaryFileWriter::fileExt = ".su2b"; + +CSU2MeshBinaryFileWriter::CSU2MeshBinaryFileWriter(CParallelDataSorter *valDataSorter, + unsigned short valiZone, unsigned short valnZone) : + CFileWriter(valDataSorter, fileExt), iZone(valiZone), nZone(valnZone) {} + +namespace { + +/*--- The writer always emits 8-byte (uint64_t) connectivity entries. This keeps + the implementation simple (no branching on element/point counts) and is + unconditionally readable by CSU2BinaryMeshReaderBase, which auto-detects + the connectivity width from the file header. ---*/ +using conn_t = uint64_t; +constexpr int32_t SU2B_CONN_TYPE_SIZE = static_cast(sizeof(conn_t)); + +/*--- Elements are always visited in this fixed order, matching CSU2MeshFileWriter + (the ASCII writer) so that the two formats produce numerically identical + meshes for the same input. ---*/ +constexpr std::array ElemTypes = {TRIANGLE, QUADRILATERAL, TETRAHEDRON, + HEXAHEDRON, PRISM, PYRAMID}; + +/*--- fopen() creates new files with permissive default permissions (typically + 0666 before umask, i.e. potentially world-writable). Explicitly restrict + to owner read/write, group/other read-only, matching the permissions of + the ASCII mesh files written elsewhere via ofstream. A no-op on Windows, + which does not use POSIX permission bits. ---*/ +void RestrictPermissions(const string& filename) { +#if !defined(_WIN32) + chmod(filename.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); +#endif +} + +FILE* OpenAppend(const string& filename) { + FILE* f = fopen(filename.c_str(), "ab"); + if (!f) SU2_MPI::Error(string("Unable to open file ") + filename, CURRENT_FUNCTION); + RestrictPermissions(filename); + return f; +} + +} // namespace + +void CSU2MeshBinaryFileWriter::WriteData(string val_filename) { + + val_filename.append(fileExt); + + /*--- Write the file-level header (only once, before the first zone) followed + by the per-zone header (zone_id, n_dim, n_elem). Only rank 0 touches the + file for this; the implicit synchronization at the first Allreduce below + (also present in CSU2MeshFileWriter) keeps the other ranks from writing + before this is done. ---*/ + + if (rank == 0) { + FILE* f = fopen(val_filename.c_str(), (iZone == 0) ? "wb" : "ab"); + if (!f) SU2_MPI::Error(string("Unable to open file ") + val_filename, CURRENT_FUNCTION); + RestrictPermissions(val_filename); + + if (iZone == 0) { + int32_t size_conn_type = SU2B_CONN_TYPE_SIZE; + int32_t n_zone = nZone; + fwrite(&size_conn_type, sizeof(size_conn_type), 1, f); + fwrite(&n_zone, sizeof(n_zone), 1, f); + } + + int32_t zone_id = iZone; + int32_t n_dim = dataSorter->GetnDim(); + conn_t n_elem = dataSorter->GetnElemGlobal(); + fwrite(&zone_id, sizeof(zone_id), 1, f); + fwrite(&n_dim, sizeof(n_dim), 1, f); + fwrite(&n_elem, sizeof(n_elem), 1, f); + + fclose(f); + } + + /*--- Section 1: element offsets. Every rank streams, in turn, the starting + connectivity-array position of each of its local elements (visited in + the fixed type order above), starting from the cumulative total left + behind by the previous ranks. Each rank's accumulator holds only its + own local contribution (like CSU2MeshFileWriter's nElem/myPoint), so + summing it across ranks via Allreduce yields the new cumulative total. + Once all ranks are done, the final total is appended once more as the + closing sentinel offset[n_elem]. ---*/ + + unsigned long connOffset = 0, localConnOffset = 0; + + for (int iProcessor = 0; iProcessor < size; iProcessor++) { + if (rank == iProcessor) { + FILE* f = OpenAppend(val_filename); + unsigned long running = connOffset; + for (auto type : ElemTypes) { + const conn_t nPointsElem = nPointsOfElementType(type); + for (auto iElem = 0ul; iElem < dataSorter->GetnElem(type); iElem++) { + conn_t value = running; + fwrite(&value, sizeof(value), 1, f); + running += nPointsElem + 2; + } + } + fclose(f); + localConnOffset = running - connOffset; + } + SU2_MPI::Allreduce(&localConnOffset, &connOffset, 1, MPI_UNSIGNED_LONG, MPI_SUM, SU2_MPI::GetComm()); + } + + if (rank == 0) { + FILE* f = OpenAppend(val_filename); + conn_t sentinel = connOffset; + fwrite(&sentinel, sizeof(sentinel), 1, f); + fclose(f); + } + + /*--- Section 2: element connectivity, [VTK_Type, node_0..node_n-1, GlobalIndex] + per element, in the same fixed type order and the same rank-by-rank + streaming pattern as CSU2MeshFileWriter uses for the ASCII format + (including the same "-1" to convert 1-based dataSorter indices to the + 0-based indices used throughout the SU2 mesh formats). ---*/ + + unsigned long elemIndexOffset = 0, localElemCount = 0; + + for (int iProcessor = 0; iProcessor < size; iProcessor++) { + if (rank == iProcessor) { + FILE* f = OpenAppend(val_filename); + conn_t globalIndex = elemIndexOffset; + for (auto type : ElemTypes) { + const auto nPointsElem = nPointsOfElementType(type); + for (auto iElem = 0ul; iElem < dataSorter->GetnElem(type); iElem++) { + conn_t vtkType = type; + fwrite(&vtkType, sizeof(vtkType), 1, f); + for (auto iNode = 0u; iNode < nPointsElem; iNode++) { + conn_t node = dataSorter->GetElemConnectivity(type, iElem, iNode) - 1; + fwrite(&node, sizeof(node), 1, f); + } + fwrite(&globalIndex, sizeof(globalIndex), 1, f); + globalIndex++; + } + } + fclose(f); + localElemCount = static_cast(globalIndex - elemIndexOffset); + } + SU2_MPI::Allreduce(&localElemCount, &elemIndexOffset, 1, MPI_UNSIGNED_LONG, MPI_SUM, SU2_MPI::GetComm()); + } + + /*--- Section 3: point coordinates and IDs, interleaved. Same rank-by-rank + streaming pattern as CSU2MeshFileWriter's point section. ---*/ + + if (rank == 0) { + FILE* f = OpenAppend(val_filename); + conn_t nPointsGlobal = dataSorter->GetnPointsGlobal(); + fwrite(&nPointsGlobal, sizeof(nPointsGlobal), 1, f); + fclose(f); + } + + unsigned long myPoint = 0, pointOffset = 0; + + for (int iProcessor = 0; iProcessor < size; iProcessor++) { + if (rank == iProcessor) { + FILE* f = OpenAppend(val_filename); + for (auto iPoint = 0ul; iPoint < dataSorter->GetnPoints(); iPoint++) { + for (auto iDim = 0u; iDim < dataSorter->GetnDim(); iDim++) { + double coord = dataSorter->GetData(iDim, iPoint); + fwrite(&coord, sizeof(coord), 1, f); + } + conn_t pointID = iPoint + pointOffset; + fwrite(&pointID, sizeof(pointID), 1, f); + } + fclose(f); + myPoint = dataSorter->GetnPoints(); + } + SU2_MPI::Allreduce(&myPoint, &pointOffset, 1, MPI_UNSIGNED_LONG, MPI_SUM, SU2_MPI::GetComm()); + } + + /*--- Section 4: markers. Mirrors CSU2MeshFileWriter: the marker connectivity + is not available from the data sorter, so it is read back from the + "boundary[_iZone].dat" file that CPhysicalGeometry writes (for + SU2_COMPONENT::SU2_DEF) right after reading the original mesh. Only the + master rank does this work, exactly as for the ASCII format. ---*/ + + if (rank == MASTER_NODE) { + FILE* f = OpenAppend(val_filename); + + string str = "boundary"; + if (nZone > 1) str += "_" + PrintingToolbox::to_string(iZone); + str += ".dat"; + + ifstream input_file(str); + if (!input_file.is_open()) SU2_MPI::Error(string("Cannot find ") + str, CURRENT_FUNCTION); + + string text_line; + while (getline(input_file, text_line)) { + + auto position = text_line.find("NMARK=", 0); + if (position == string::npos) continue; + + text_line.erase(0, 6); + const int32_t nMarker_ = atoi(text_line.c_str()); + fwrite(&nMarker_, sizeof(nMarker_), 1, f); + + for (int iMarker = 0; iMarker < nMarker_; iMarker++) { + + getline(input_file, text_line); + text_line.erase(0, 11); + for (int iChar = 0; iChar < 20; iChar++) { + position = text_line.find(' ', 0); + if (position != string::npos) text_line.erase(position, 1); + position = text_line.find('\r', 0); + if (position != string::npos) text_line.erase(position, 1); + position = text_line.find('\n', 0); + if (position != string::npos) text_line.erase(position, 1); + } + string Marker_Tag = text_line; + + /*--- Write the null-padded marker name. Uses SU2_BINARY_STRING_SIZE + (shared with CSU2BinaryMeshReaderBase::SU2_STRING_SIZE) rather + than CGNS_STRING_SIZE, since the two formats' name field widths + are independent and must not silently drift apart. ---*/ + char name_buf[SU2_BINARY_STRING_SIZE] = {}; + strncpy(name_buf, Marker_Tag.c_str(), SU2_BINARY_STRING_SIZE - 1); + fwrite(name_buf, sizeof(char), SU2_BINARY_STRING_SIZE, f); + + getline(input_file, text_line); + text_line.erase(0, 13); + const unsigned long nElem_Bound_ = atoi(text_line.c_str()); + + /*--- Consume (but do not use) the SEND_TO= line: periodic/send-receive + markers are not supported by the binary format yet, matching + CSU2BinaryMeshReaderBase, which errors out on SEND_RECEIVE. ---*/ + getline(input_file, text_line); + + /*--- Parse this marker's boundary elements into memory, then emit the + offset array followed by the connectivity array, matching what + CSU2BinaryMeshReaderBase::ReadSurfaceElementConnectivity expects. ---*/ + vector vtkTypes(nElem_Bound_); + vector> nodes(nElem_Bound_); + for (unsigned long iElem_Bound = 0; iElem_Bound < nElem_Bound_; iElem_Bound++) { + getline(input_file, text_line); + istringstream bound_line(text_line); + unsigned short VTK_Type; + bound_line >> VTK_Type; + vtkTypes[iElem_Bound] = VTK_Type; + const auto nPointsElem = nPointsOfElementType(VTK_Type); + for (unsigned short iNode = 0; iNode < nPointsElem; iNode++) bound_line >> nodes[iElem_Bound][iNode]; + } + + auto nElemBoundConn = static_cast(nElem_Bound_); + fwrite(&nElemBoundConn, sizeof(nElemBoundConn), 1, f); + + unsigned long running = 0; + for (unsigned long iElem_Bound = 0; iElem_Bound < nElem_Bound_; iElem_Bound++) { + conn_t value = running; + fwrite(&value, sizeof(value), 1, f); + running += nPointsOfElementType(vtkTypes[iElem_Bound]) + 1; + } + conn_t sentinel = running; + fwrite(&sentinel, sizeof(sentinel), 1, f); + + for (unsigned long iElem_Bound = 0; iElem_Bound < nElem_Bound_; iElem_Bound++) { + conn_t vtkType = vtkTypes[iElem_Bound]; + fwrite(&vtkType, sizeof(vtkType), 1, f); + const auto nPointsElem = nPointsOfElementType(vtkTypes[iElem_Bound]); + for (unsigned short iNode = 0; iNode < nPointsElem; iNode++) { + conn_t node = nodes[iElem_Bound][iNode]; + fwrite(&node, sizeof(node), 1, f); + } + } + } + } + + input_file.close(); + fclose(f); + } + + SU2_MPI::Barrier(SU2_MPI::GetComm()); +} diff --git a/SU2_CFD/src/solvers/CFEM_DG_EulerSolver.cpp b/SU2_CFD/src/solvers/CFEM_DG_EulerSolver.cpp index f5177b953534..0f5db8c0f8a7 100644 --- a/SU2_CFD/src/solvers/CFEM_DG_EulerSolver.cpp +++ b/SU2_CFD/src/solvers/CFEM_DG_EulerSolver.cpp @@ -3666,6 +3666,14 @@ void CFEM_DG_EulerSolver::ComputeSpatialJacobian(CGeometry *geometry, CSolver * /* Write the actual matrix elements. */ fwrite(Jacobian.data(), Jacobian.size(), sizeof(passivedouble), fJac); + /* Write the number of elements. */ + fwrite(&nVolElemOwned, 1, sizeof(unsigned long), fJac); + + /* Write the number of DOFs per element. */ + std::vector nDOFsElem(nVolElemOwned); + for(unsigned long i=0; iComputeMeshQualityStatistics(config_container[iZone]); } - /*--- Load the data. --- */ + /*--- Load the data and write the mesh. The format is either + ASCII or binary su2 format. --- */ output_container[iZone]->LoadData(geometry_container[iZone][INST_0][MESH_0], config_container[iZone], nullptr); + OUTPUT_TYPE output_type; + switch (config_container[iZone]->GetMesh_Out_FileFormat()) { + case ENUM_GRID::SU2: + output_type = OUTPUT_TYPE::MESH; + break; + case ENUM_GRID::SU2_BIN: + output_type = OUTPUT_TYPE::MESH_BINARY; + break; + default: + SU2_MPI::Error("Unrecognized mesh_out format specified!", CURRENT_FUNCTION); + output_type = OUTPUT_TYPE::MESH; + break; + } + output_container[iZone]->WriteToFile(config_container[iZone], geometry_container[iZone][INST_0][MESH_0], - OUTPUT_TYPE::MESH, driver_config->GetMesh_Out_FileName()); + output_type, driver_config->GetMesh_Out_FileName()); /*--- Set the file names for the visualization files. ---*/ diff --git a/TestCases/.gitignore b/TestCases/.gitignore index a63f2d78d230..3aca4cd7f368 100644 --- a/TestCases/.gitignore +++ b/TestCases/.gitignore @@ -11,6 +11,7 @@ # Things appearing in TestCases/ repo to ignore: # mesh files *.su2 +*.su2b *.cgns *.pw @@ -21,6 +22,7 @@ # auto-generated files by regression tests *.autotest config_*.cfg +euler/naca0012/inv_NACA0012_Roe_su2bin.cfg # flip the pickle *.pkl diff --git a/TestCases/backscatter/backward_step/backwardStep.cfg b/TestCases/backscatter/backward_step/backwardStep.cfg index 72a4304a467e..7a0cb8944313 100644 --- a/TestCases/backscatter/backward_step/backwardStep.cfg +++ b/TestCases/backscatter/backward_step/backwardStep.cfg @@ -1,12 +1,12 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % SU2 configuration file % -% Case description: Backward-Facing Step % +% Case description: Backward-Facing Step % % Author: Angelo Passariello % % Institution: Italian Aerospace Research Centre (CIRA) % % University of Naples Federico II % % Date: 2026.03.16 % -% File Version 8.3.0 "Harrier" % +% File Version 8.5.0 "Harrier" % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -15,7 +15,7 @@ SOLVER= INC_RANS KIND_TURB_MODEL= SA MATH_PROBLEM= DIRECT -RESTART_SOL= NO +RESTART_SOL= NO % ----------- HYBRID RANS-LES SIMULATION --------------------------------------% @@ -24,8 +24,8 @@ DES_CONST= 0.65 % ------------ STOCHASTIC BACKSCATTER MODEL DEFINITION ------------------------% -STOCHASTIC_BACKSCATTER= YES -SBS_LENGTHSCALE_COEFF= 0.02 +STOCHASTIC_BACKSCATTER= YES +SBS_LENGTHSCALE_COEFF= 0.02 SBS_MAX_ITER_SMOOTH= 100 SBS_TIMESCALE_COEFF= 0.05 SBS_INTENSITY_COEFF= 1.0 @@ -43,7 +43,7 @@ INC_DENSITY_INIT= 1.183 INC_VELOCITY_INIT= ( 44.316, 0.0, 0.0 ) INC_TEMPERATURE_INIT= 298.333 INC_ENERGY_EQUATION= NO -INC_NONDIM= DIMENSIONAL +INC_NONDIM= DIMENSIONAL REYNOLDS_LENGTH= 0.0125 VISCOSITY_MODEL= CONSTANT_VISCOSITY MU_CONSTANT= 1.820E-5 @@ -55,7 +55,7 @@ REF_AREA= 1.0 % ------------------------- UNSTEADY SIMULATION -------------------------------% -TIME_DOMAIN= YES +TIME_DOMAIN= YES TIME_MARCHING= DUAL_TIME_STEPPING-2ND_ORDER TIME_STEP= 2.82E-6 INNER_ITER= 20 @@ -82,13 +82,13 @@ RK_ALPHA_COEFF= ( 0.66667, 0.66667, 1.000000 ) % ----------------------- SLOPE LIMITER DEFINITION ----------------------------% -MUSCL_FLOW= NO -MUSCL_TURB= NO +MUSCL_FLOW= NO +MUSCL_TURB= NO % -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% % % -CONV_NUM_METHOD_FLOW= LD2 +CONV_NUM_METHOD_FLOW= LD2 JST_SENSOR_COEFF= ( 0.0, 0.002 ) TIME_DISCRE_FLOW= EULER_IMPLICIT diff --git a/TestCases/euler/naca0012/mesh_su2_to_su2bin.cfg b/TestCases/euler/naca0012/mesh_su2_to_su2bin.cfg new file mode 100644 index 000000000000..9427ecbc4e8e --- /dev/null +++ b/TestCases/euler/naca0012/mesh_su2_to_su2bin.cfg @@ -0,0 +1,34 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% SU2 configuration file % +% Case description: Convert mesh_NACA0012_inv.su2 to the native SU2 binary % +% mesh format (.su2b), for the naca0012_su2bin regression test. % +% A zero-magnitude grid translation is used. % +% File Version 8.5.0 "Harrier" % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +SOLVER= EULER +MARKER_EULER= ( airfoil ) +MARKER_FAR= ( farfield ) + +% ----------------------- DESIGN VARIABLE PARAMETERS --------------------------% +% +DV_KIND= TRANSLATE_GRID +DV_MARKER= ( airfoil ) +DV_PARAM= ( 1.0, 0.0, 0.0 ) +DV_VALUE= 0.0 + +% ---------------------- GRID DEFORMATION PARAMETERS ---------------------------% +% +DEFORM_LINEAR_SOLVER= FGMRES +DEFORM_LINEAR_SOLVER_PREC= ILU +DEFORM_LINEAR_SOLVER_ERROR= 1E-14 +DEFORM_LINEAR_SOLVER_ITER= 500 +DEFORM_NONLINEAR_ITER= 1 +DEFORM_STIFFNESS_TYPE= INVERSE_VOLUME + +% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% +% +MESH_FILENAME= mesh_NACA0012_inv.su2 +MESH_FORMAT= SU2 +MESH_OUT_FILENAME= mesh_NACA0012_inv_su2bin +MESH_OUT_FORMAT= SU2B diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index a98256722293..f23a9f14cf2a 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -231,10 +231,30 @@ def main(): channel.test_vals = [-1.990518, 3.545643, 0.031745, 0.194289] test_list.append(channel) - # NACA0012 + # NACA0012, native SU2 binary mesh format (.su2b) + # First, SU2_DEF converts mesh_NACA0012_inv.su2 into the native SU2 binary + # mesh format. The regression case below then loads that freshly generated + # .su2b file, using a copy of inv_NACA0012_Roe.cfg with MESH_FILENAME and + # MESH_FORMAT swapped to point at it. + naca0012_su2bin_convert = TestCase('naca0012_su2bin_convert') + naca0012_su2bin_convert.cfg_dir = "euler/naca0012" + naca0012_su2bin_convert.cfg_file = "mesh_su2_to_su2bin.cfg" + naca0012_su2bin_convert.command = TestCase.Command("mpirun -n 2", "SU2_DEF") + test_list.append(naca0012_su2bin_convert) + + naca0012_su2bin_cfg_path = "euler/naca0012/inv_NACA0012_Roe_su2bin.cfg" + with open("euler/naca0012/inv_NACA0012_Roe.cfg", 'r') as f: + naca0012_su2bin_cfg = f.read() + naca0012_su2bin_cfg = naca0012_su2bin_cfg.replace( + "MESH_FILENAME= mesh_NACA0012_inv.su2", "MESH_FILENAME= mesh_NACA0012_inv_su2bin") + naca0012_su2bin_cfg = naca0012_su2bin_cfg.replace( + "MESH_FORMAT= SU2\n", "MESH_FORMAT= SU2B\n") + with open(naca0012_su2bin_cfg_path, 'w') as f: + f.write(naca0012_su2bin_cfg) + naca0012 = TestCase('naca0012') naca0012.cfg_dir = "euler/naca0012" - naca0012.cfg_file = "inv_NACA0012_Roe.cfg" + naca0012.cfg_file = "inv_NACA0012_Roe_su2bin.cfg" naca0012.test_iter = 20 naca0012.test_vals = [-4.452603, -3.920573, 0.296003, 0.024298] test_list.append(naca0012) diff --git a/TestCases/rans/naca0012/turb_NACA0012_sst_multigrid_restart.cfg b/TestCases/rans/naca0012/turb_NACA0012_sst_multigrid_restart.cfg index cc0b26363819..b718fa52ebf1 100644 --- a/TestCases/rans/naca0012/turb_NACA0012_sst_multigrid_restart.cfg +++ b/TestCases/rans/naca0012/turb_NACA0012_sst_multigrid_restart.cfg @@ -6,7 +6,7 @@ % Author: David E. Manosalvas % % Institution: Stanford University % % Date: 02.14.2017 % -% File Version 8.2.0 "Harrier" % +% File Version 8.5.0 "Harrier" % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/TestCases/rans/naca0012/turb_NACA0012_sst_sust.cfg b/TestCases/rans/naca0012/turb_NACA0012_sst_sust.cfg index 383453e7a4fa..855f1225d97f 100644 --- a/TestCases/rans/naca0012/turb_NACA0012_sst_sust.cfg +++ b/TestCases/rans/naca0012/turb_NACA0012_sst_sust.cfg @@ -6,7 +6,7 @@ % Author: Thomas D. Economon % % Institution: Stanford University % % Date: Feb 18th, 2013 % -% File Version 8.2.0 "Harrier" % +% File Version 8.5.0 "Harrier" % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/config_template.cfg b/config_template.cfg index 998360550b3d..a7d357240e0e 100644 --- a/config_template.cfg +++ b/config_template.cfg @@ -2524,7 +2524,7 @@ EXTRA_HEAT_ZONE_OUTPUT= -1 % Mesh input file MESH_FILENAME= mesh_NACA0012_inv % -% Mesh input file format (SU2, CGNS) +% Mesh input file format (SU2, SU2B, CGNS, RECTANGLE, BOX) MESH_FORMAT= SU2 % % List of the number of grid points in the RECTANGLE or BOX grid in the x,y,z directions. (default: (33,33,33) ). @@ -2539,6 +2539,9 @@ MESH_BOX_OFFSET= (0.0, 0.0, 0.0) % Mesh output file MESH_OUT_FILENAME= mesh_out % +% Mesh output file format (SU2, SU2B) +MESH_OUT_FORMAT= SU2 +% % Restart flow input file SOLUTION_FILENAME= solution_flow % From 8e7426faa86125d821241b837b6f68b937d23256 Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Tue, 14 Jul 2026 22:32:48 -0700 Subject: [PATCH 19/61] BOUNDED_SCALAR convective scheme compatible with compressible flow (#2844) * bounded scalar for compressible * tests * fix windows build, update tests --- Common/src/CConfig.cpp | 4 --- Common/src/linear_algebra/CSysMatrix.cpp | 4 +-- .../include/numerics_simd/CNumericsSIMD.hpp | 6 +++- .../flow/convection/centered.hpp | 7 ++++- .../numerics_simd/flow/convection/upwind.hpp | 7 ++++- SU2_CFD/include/numerics_simd/util.hpp | 12 ++++++++ .../include/solvers/CFVMFlowSolverBase.inl | 6 ++-- SU2_CFD/src/solvers/CEulerSolver.cpp | 9 ++++++ TestCases/hybrid_regression.py | 20 ++++++------- TestCases/hybrid_regression_AD.py | 2 +- TestCases/parallel_regression.py | 4 +-- .../forces_0.csv.ref | 28 +++++++++---------- TestCases/rans/rae2822/turb_SA_RAE2822.cfg | 2 +- TestCases/serial_regression.py | 4 +-- 14 files changed, 74 insertions(+), 41 deletions(-) diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index 3e1105848b17..2c0dce07e683 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -5864,10 +5864,6 @@ void CConfig::SetPostprocessing(SU2_COMPONENT val_software, unsigned short val_i } } - if (Kind_Regime == ENUM_REGIME::COMPRESSIBLE && GetBounded_Scalar()) { - SU2_MPI::Error("BOUNDED_SCALAR discretization can only be used for incompressible problems.", CURRENT_FUNCTION); - } - } void CConfig::SetMarkers(SU2_COMPONENT val_software) { diff --git a/Common/src/linear_algebra/CSysMatrix.cpp b/Common/src/linear_algebra/CSysMatrix.cpp index f02d908a059e..ebe98d4311cc 100644 --- a/Common/src/linear_algebra/CSysMatrix.cpp +++ b/Common/src/linear_algebra/CSysMatrix.cpp @@ -546,8 +546,8 @@ void CSysMatrix::SetValZero() { auto zeroChunk = [&](ScalarType* arr, unsigned long n) { if (n == 0) return; const auto chunk = roundUpDiv(n, nThreads); - const auto begin = min(chunk * iThread, n); - const auto mySize = min(chunk, n - begin) * sizeof(ScalarType); + const auto begin = min(chunk * iThread, n); + const auto mySize = min(chunk, n - begin) * sizeof(ScalarType); if (mySize) memset(&arr[begin], 0, mySize); }; zeroChunk(mat.d, nPoint * nVar * nEqn); diff --git a/SU2_CFD/include/numerics_simd/CNumericsSIMD.hpp b/SU2_CFD/include/numerics_simd/CNumericsSIMD.hpp index 3379a8ac920e..a7726a6436a0 100644 --- a/SU2_CFD/include/numerics_simd/CNumericsSIMD.hpp +++ b/SU2_CFD/include/numerics_simd/CNumericsSIMD.hpp @@ -28,6 +28,7 @@ #pragma once #include "../../../Common/include/parallelization/vectorization.hpp" +#include "../../../Common/include/containers/C2DContainer.hpp" /*! * \enum UpdateType @@ -74,6 +75,8 @@ class CNumericsSIMD { * \param[in] updateMask - SIMD array of 1's and 0's, the latter prevent the update. * \param[in,out] vector - Target for the fluxes. * \param[in,out] matrix - Target for the flux Jacobians. + * \param[out] edgeMassFluxes - Optional, per-edge mass flux (density-equation flux), + * used by "bounded scalar" discretization of transported scalars (turbulence, species). * \note The update mask is used to handle "remainder" edges (nEdge mod simdSize). */ virtual void ComputeFlux(Int iEdge, @@ -83,7 +86,8 @@ class CNumericsSIMD { UpdateType updateType, Double updateMask, CSysVector& vector, - SparseMatrixType& matrix) const = 0; + SparseMatrixType& matrix, + su2activevector* edgeMassFluxes) const = 0; /*! \brief Destructor of the class. */ virtual ~CNumericsSIMD(void) = default; diff --git a/SU2_CFD/include/numerics_simd/flow/convection/centered.hpp b/SU2_CFD/include/numerics_simd/flow/convection/centered.hpp index dd010650a47f..faaf56912351 100644 --- a/SU2_CFD/include/numerics_simd/flow/convection/centered.hpp +++ b/SU2_CFD/include/numerics_simd/flow/convection/centered.hpp @@ -87,7 +87,8 @@ class CCenteredBase : public Base { const UpdateType updateType, const Double updateMask, CSysVector& vector, - SparseMatrixType& matrix) const final { + SparseMatrixType& matrix, + su2activevector* edgeMassFluxes) const final { /*--- Start preaccumulation, inputs are registered * automatically in "gatherVariables". ---*/ @@ -182,6 +183,10 @@ class CCenteredBase : public Base { updateLinearSystem(iEdge, iPoint, jPoint, implicit, updateType, updateMask, flux, jac_i, jac_j, vector, matrix); + + /*--- Store the mass flux (density-equation flux) for bounded-scalar transport. ---*/ + + updateEdgeMassFlux(iEdge, flux(0), edgeMassFluxes); } }; diff --git a/SU2_CFD/include/numerics_simd/flow/convection/upwind.hpp b/SU2_CFD/include/numerics_simd/flow/convection/upwind.hpp index 090fb111332a..38ec02c902b5 100644 --- a/SU2_CFD/include/numerics_simd/flow/convection/upwind.hpp +++ b/SU2_CFD/include/numerics_simd/flow/convection/upwind.hpp @@ -87,7 +87,8 @@ class CUpwindBase : public Base { const UpdateType updateType, const Double updateMask, CSysVector& vector, - SparseMatrixType& matrix) const final { + SparseMatrixType& matrix, + su2activevector* edgeMassFluxes) const final { /*--- Start preaccumulation, inputs are registered * automatically in "gatherVariables". ---*/ @@ -150,6 +151,10 @@ class CUpwindBase : public Base { updateLinearSystem(iEdge, iPoint, jPoint, implicit, updateType, updateMask, flux, jac_i, jac_j, vector, matrix); + + /*--- Store the mass flux (density-equation flux) for bounded-scalar transport. ---*/ + + updateEdgeMassFlux(iEdge, flux(0), edgeMassFluxes); } }; diff --git a/SU2_CFD/include/numerics_simd/util.hpp b/SU2_CFD/include/numerics_simd/util.hpp index 6e84209ee6db..e891155c0909 100644 --- a/SU2_CFD/include/numerics_simd/util.hpp +++ b/SU2_CFD/include/numerics_simd/util.hpp @@ -261,3 +261,15 @@ FORCEINLINE void updateLinearSystem(Int iEdge, } } } + +/*! + * \brief Store the (scalar) mass flux of an edge, e.g. for "bounded scalar" transport equations. + * \note No-op if "target" is null. As with CEdge's Nodes/Normal, edges within a SIMD group are + * contiguous (coloring groups are multiples of the SIMD size), so this is a plain vectorized store + * starting at iEdge[0], relying on "target" being padded to a multiple of the SIMD size. + */ +FORCEINLINE void updateEdgeMassFlux(Int iEdge, + const Double& massFlux, + su2activevector* target) { + if (target) massFlux.store(&(*target)[iEdge[0]]); +} diff --git a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl index 28ca74d8149c..699d48401d98 100644 --- a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl +++ b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl @@ -1595,6 +1595,8 @@ void CFVMFlowSolverBase::EdgeFluxResidual(const CGeometry *geometry, ErrorCounter = 0; END_SU2_OMP_MASTER + su2activevector* massFluxes = config->GetBounded_Scalar() ? &EdgeMassFluxes : nullptr; + /*--- For hybrid parallel AD, pause preaccumulation if there is shared reading of * variables, otherwise switch to the faster adjoint evaluation mode. ---*/ bool pausePreacc = false; @@ -1615,9 +1617,9 @@ void CFVMFlowSolverBase::EdgeFluxResidual(const CGeometry *geometry, } if (ReducerStrategy) { - edgeNumerics->ComputeFlux(iEdge, *config, *geometry, *nodes, UpdateType::REDUCTION, mask, EdgeFluxes, Jacobian); + edgeNumerics->ComputeFlux(iEdge, *config, *geometry, *nodes, UpdateType::REDUCTION, mask, EdgeFluxes, Jacobian, massFluxes); } else { - edgeNumerics->ComputeFlux(iEdge, *config, *geometry, *nodes, UpdateType::COLORING, mask, LinSysRes, Jacobian); + edgeNumerics->ComputeFlux(iEdge, *config, *geometry, *nodes, UpdateType::COLORING, mask, LinSysRes, Jacobian, massFluxes); } if (MGLevel == MESH_0) { for (auto j = 0ul; j < Double::Size; ++j) diff --git a/SU2_CFD/src/solvers/CEulerSolver.cpp b/SU2_CFD/src/solvers/CEulerSolver.cpp index e89f022920c6..81529583d07e 100644 --- a/SU2_CFD/src/solvers/CEulerSolver.cpp +++ b/SU2_CFD/src/solvers/CEulerSolver.cpp @@ -348,6 +348,12 @@ CEulerSolver::CEulerSolver(CGeometry *geometry, CConfig *config, CommunicateInitialState(geometry, config); + /*--- Sizing edge mass flux array, padded to a multiple of the SIMD size since the vectorized + * numerics write to it with contiguous (unmasked) SIMD stores, same as CEdge's Normal. ---*/ + if (config->GetBounded_Scalar()) { + EdgeMassFluxes.resize(nextMultiple(geometry->GetnEdge(), simd::preferredLen())) = su2double(0.0); + } + /*--- Add the solver name.. ---*/ SolverName = "C.FLOW"; @@ -1817,6 +1823,7 @@ void CEulerSolver::Upwind_Residual(CGeometry *geometry, CSolver **solver_contain } const bool implicit = (config->GetKind_TimeIntScheme() == EULER_IMPLICIT); + const bool bounded_scalar = config->GetBounded_Scalar(); const bool msw = (config->GetKind_Upwind_Flow() == UPWIND::MSW); const bool roe_turkel = (config->GetKind_Upwind_Flow() == UPWIND::TURKEL); @@ -1985,6 +1992,8 @@ void CEulerSolver::Upwind_Residual(CGeometry *geometry, CSolver **solver_contain auto residual = numerics->ComputeResidual(config); + if (bounded_scalar) EdgeMassFluxes[iEdge] = residual[0]; + /*--- Set the final value of the Roe dissipation coefficient ---*/ if ((kind_dissipation != NO_ROELOWDISS) && (MGLevel != MESH_0)) { diff --git a/TestCases/hybrid_regression.py b/TestCases/hybrid_regression.py index e32cd1b071f0..52b5c2755be5 100644 --- a/TestCases/hybrid_regression.py +++ b/TestCases/hybrid_regression.py @@ -103,7 +103,7 @@ def main(): flatplate.cfg_dir = "navierstokes/flatplate" flatplate.cfg_file = "lam_flatplate.cfg" flatplate.test_iter = 100 - flatplate.test_vals = [-6.543288, -1.065157, 0.001196, 0.029390, 2.361500, -2.332100, 0.000000, 0.000000] + flatplate.test_vals = [-6.543300, -1.065167, 0.001196, 0.029390, 2.361500, -2.332100, 0.000000, 0.000000] test_list.append(flatplate) # Laminar cylinder (steady) @@ -136,7 +136,7 @@ def main(): poiseuille_profile.cfg_dir = "navierstokes/poiseuille" poiseuille_profile.cfg_file = "profile_poiseuille.cfg" poiseuille_profile.test_iter = 10 - poiseuille_profile.test_vals = [-12.005131, -7.582325, -0.000000, 2.089953] + poiseuille_profile.test_vals = [-12.005129, -7.582314, -0.000000, 2.089953] poiseuille_profile.test_vals_aarch64 = [-12.009012, -7.262530, -0.000000, 2.089953] test_list.append(poiseuille_profile) @@ -145,7 +145,7 @@ def main(): periodic2d.cfg_dir = "navierstokes/periodic2D" periodic2d.cfg_file = "config.cfg" periodic2d.test_iter = 1400 - periodic2d.test_vals = [-10.817608, -8.363542, -8.287458, -5.334102, -1.088411, -2945.200000] + periodic2d.test_vals = [-10.817608, -8.363541, -8.287458, -5.334101, -1.088412, -2945.200000] test_list.append(periodic2d) ########################## @@ -157,7 +157,7 @@ def main(): rae2822_sa.cfg_dir = "rans/rae2822" rae2822_sa.cfg_file = "turb_SA_RAE2822.cfg" rae2822_sa.test_iter = 20 - rae2822_sa.test_vals = [-2.190527, -5.318180, 0.383386, 0.077605, 0.000000] + rae2822_sa.test_vals = [-2.190528, -5.335496, 0.383385, 0.077606, 0.000000] test_list.append(rae2822_sa) # RAE2822 SST @@ -197,7 +197,7 @@ def main(): turb_naca0012_sa.cfg_dir = "rans/naca0012" turb_naca0012_sa.cfg_file = "turb_NACA0012_sa.cfg" turb_naca0012_sa.test_iter = 5 - turb_naca0012_sa.test_vals = [-12.038011, -16.332088, 1.080346, 0.018385, 20.000000, -2.873515, 0.000000, -14.250270, 0.000000] + turb_naca0012_sa.test_vals = [-12.038060, -16.332088, 1.080346, 0.018385, 20.000000, -2.873410, 0.000000, -14.250270, 0.000000] turb_naca0012_sa.test_vals_aarch64 = [-12.038091, -16.332090, 1.080346, 0.018385, 20.000000, -2.873236, 0.000000, -14.250271, 0.000000] test_list.append(turb_naca0012_sa) @@ -206,7 +206,7 @@ def main(): turb_naca0012_sst.cfg_dir = "rans/naca0012" turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" turb_naca0012_sst.test_iter = 10 - turb_naca0012_sst.test_vals = [-12.093908, -15.250756, -5.906323, 1.070413, 0.015775, -2.855199, 0.000000] + turb_naca0012_sst.test_vals = [-12.093924, -15.250755, -5.906323, 1.070413, 0.015775, -2.855101, 0.000000] turb_naca0012_sst.test_vals_aarch64 = [-12.075928, -15.246732, -5.861249, 1.070036, 0.015841, -2.835263, 0] test_list.append(turb_naca0012_sst) @@ -215,7 +215,7 @@ def main(): turb_naca0012_sst_sust.cfg_dir = "rans/naca0012" turb_naca0012_sst_sust.cfg_file = "turb_NACA0012_sst_sust.cfg" turb_naca0012_sst_sust.test_iter = 10 - turb_naca0012_sst_sust.test_vals = [-12.080880, -14.837176, -5.732907, 1.000893, 0.019109, -2.119818] + turb_naca0012_sst_sust.test_vals = [-12.080792, -14.837174, -5.732908, 1.000893, 0.019109, -2.120172] turb_naca0012_sst_sust.test_vals_aarch64 = [-12.073210, -14.836724, -5.732627, 1.000050, 0.019144, -2.629689] test_list.append(turb_naca0012_sst_sust) @@ -252,7 +252,7 @@ def main(): axi_rans_air_nozzle_restart.cfg_dir = "axisymmetric_rans/air_nozzle" axi_rans_air_nozzle_restart.cfg_file = "air_nozzle_restart.cfg" axi_rans_air_nozzle_restart.test_iter = 10 - axi_rans_air_nozzle_restart.test_vals = [-11.083072, -5.374686, -8.880093, -4.073514, 0.000000] + axi_rans_air_nozzle_restart.test_vals = [-11.083069, -5.374689, -8.880092, -4.073524, 0.000000] axi_rans_air_nozzle_restart.test_vals_aarch64 = [-14.140441, -9.154674, -10.886121, -5.806594, 0.000000] test_list.append(axi_rans_air_nozzle_restart) @@ -440,7 +440,7 @@ def main(): spinning_cylinder.cfg_dir = "moving_wall/spinning_cylinder" spinning_cylinder.cfg_file = "spinning_cylinder.cfg" spinning_cylinder.test_iter = 25 - spinning_cylinder.test_vals = [-7.533969, -2.066689, 1.832252, 1.843016] + spinning_cylinder.test_vals = [-7.533969, -2.066690, 1.832252, 1.843016] spinning_cylinder.test_vals_aarch64 = [-8.008023, -2.611064, 1.497308, 1.487483] test_list.append(spinning_cylinder) @@ -592,7 +592,7 @@ def main(): uniform_flow.cfg_dir = "sliding_interface/uniform_flow" uniform_flow.cfg_file = "uniform_NN.cfg" uniform_flow.test_iter = 5 - uniform_flow.test_vals = [5.000000, 0.000000, -0.195002, -10.624456] + uniform_flow.test_vals = [5.000000, 0.000000, -0.195002, -10.624450] uniform_flow.unsteady = True uniform_flow.multizone = True test_list.append(uniform_flow) diff --git a/TestCases/hybrid_regression_AD.py b/TestCases/hybrid_regression_AD.py index a6c8c03b01ba..e0077078778c 100644 --- a/TestCases/hybrid_regression_AD.py +++ b/TestCases/hybrid_regression_AD.py @@ -159,7 +159,7 @@ def main(): discadj_cylinder.cfg_dir = "disc_adj_rans/cylinder" discadj_cylinder.cfg_file = "cylinder_Windowing_AD.cfg" discadj_cylinder.test_iter = 9 - discadj_cylinder.test_vals = [2.183380] + discadj_cylinder.test_vals = [2.183381] discadj_cylinder.unsteady = True discadj_cylinder.enabled_with_tsan = False test_list.append(discadj_cylinder) diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index f23a9f14cf2a..a74586507e67 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -412,7 +412,7 @@ def main(): rae2822_sa.cfg_dir = "rans/rae2822" rae2822_sa.cfg_file = "turb_SA_RAE2822.cfg" rae2822_sa.test_iter = 20 - rae2822_sa.test_vals = [-2.187401, -5.312133, 0.393515, 0.075584, 0.000000] + rae2822_sa.test_vals = [-2.187402, -5.330154, 0.393514, 0.075585, 0.000000] test_list.append(rae2822_sa) # RAE2822 SST @@ -1211,7 +1211,7 @@ def main(): uniform_flow.cfg_dir = "sliding_interface/uniform_flow" uniform_flow.cfg_file = "uniform_NN.cfg" uniform_flow.test_iter = 5 - uniform_flow.test_vals = [5.000000, 0.000000, -0.195001, -10.624449] + uniform_flow.test_vals = [5.000000, 0.000000, -0.195001, -10.624453] uniform_flow.unsteady = True uniform_flow.multizone = True test_list.append(uniform_flow) diff --git a/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref b/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref index 10f18610446d..512fc7311277 100644 --- a/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref +++ b/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref @@ -18,7 +18,7 @@ 16, -7.83, 58.91, 0.00 17, -7.39, 56.13, 0.00 18, -6.87, 52.74, 0.00 -19, -6.30, 48.95, 0.00 +19, -6.30, 48.94, 0.00 20, -5.67, 44.59, 0.00 21, -5.01, 39.84, 0.00 22, -4.30, 34.65, 0.00 @@ -83,35 +83,35 @@ 81, -34.19, -153.83, 0.00 82, -32.26, -131.97, 0.00 83, -30.00, -111.54, 0.00 -84, -26.57, -89.60, 0.00 +84, -26.56, -89.60, 0.00 85, -22.26, -67.94, 0.00 86, -16.65, -45.84, 0.00 87, -10.42, -25.77, 0.00 88, -1.99, -4.39, 0.00 -89, 7.81, 15.28, 0.00 -90, 17.06, 29.26, 0.00 +89, 7.81, 15.27, 0.00 +90, 17.05, 29.26, 0.00 91, 27.28, 40.55, 0.00 92, 36.43, 46.13, 0.00 93, 50.49, 53.24, 0.00 94, 63.17, 53.66, 0.00 95, 73.70, 48.01, 0.00 -96, 73.02, 33.47, 0.00 -97, 67.53, 18.28, 0.00 +96, 73.03, 33.47, 0.00 +97, 67.54, 18.28, 0.00 98, 54.75, 7.35, 0.00 -99, 17.21, -0.00, 0.00 -100, 52.61, -7.06, 0.00 -101, 93.59, -25.33, 0.00 -102, 62.72, -28.75, 0.00 +99, 17.22, -0.00, 0.00 +100, 52.62, -7.06, 0.00 +101, 93.60, -25.33, 0.00 +102, 62.71, -28.74, 0.00 103, 27.14, -17.68, 0.00 -104, 23.11, -19.64, 0.00 +104, 23.12, -19.64, 0.00 105, 5.43, -5.72, 0.00 -106, -0.12, 0.15, 0.00 +106, -0.11, 0.14, 0.00 107, -12.46, 18.52, 0.00 108, -19.25, 33.03, 0.00 109, -25.52, 49.90, 0.00 110, -31.57, 69.68, 0.00 111, -35.49, 87.76, 0.00 -112, -39.31, 108.23, 0.00 +112, -39.31, 108.24, 0.00 113, -41.08, 125.40, 0.00 114, -43.50, 146.70, 0.00 115, -44.37, 164.95, 0.00 @@ -153,7 +153,7 @@ 151, 50.35, 723.19, 0.00 152, 54.49, 748.60, 0.00 153, 50.92, 671.51, 0.00 -154, 6.40, 81.25, 0.00 +154, 6.40, 81.24, 0.00 155, -0.48, -5.87, 0.00 156, 2.99, 35.56, 0.00 157, 3.29, 37.89, 0.00 diff --git a/TestCases/rans/rae2822/turb_SA_RAE2822.cfg b/TestCases/rans/rae2822/turb_SA_RAE2822.cfg index b1a67bde3aa2..2157904ca34d 100644 --- a/TestCases/rans/rae2822/turb_SA_RAE2822.cfg +++ b/TestCases/rans/rae2822/turb_SA_RAE2822.cfg @@ -68,7 +68,7 @@ TIME_DISCRE_FLOW= EULER_IMPLICIT % -------------------- TURBULENT NUMERICAL METHOD DEFINITION ------------------% % -CONV_NUM_METHOD_TURB= SCALAR_UPWIND +CONV_NUM_METHOD_TURB= BOUNDED_SCALAR MUSCL_TURB= NO TIME_DISCRE_TURB= EULER_IMPLICIT diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index 88a3cfe2b925..2a00185ea860 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -218,7 +218,7 @@ def main(): rae2822_sa.cfg_dir = "rans/rae2822" rae2822_sa.cfg_file = "turb_SA_RAE2822.cfg" rae2822_sa.test_iter = 20 - rae2822_sa.test_vals = [-2.187051, -5.315525, 0.382556, 0.077937, 0.000000] + rae2822_sa.test_vals = [-2.187052, -5.333164, 0.382555, 0.077938, 0.000000] test_list.append(rae2822_sa) # RAE2822 SST @@ -946,7 +946,7 @@ def main(): uniform_flow.cfg_dir = "sliding_interface/uniform_flow" uniform_flow.cfg_file = "uniform_NN.cfg" uniform_flow.test_iter = 2 - uniform_flow.test_vals = [2.000000, 0.000000, -0.230639, -13.253272] + uniform_flow.test_vals = [2.000000, 0.000000, -0.230639, -13.251161] uniform_flow.test_vals_aarch64 = [2.000000, 0.000000, -0.230641, -13.249000] uniform_flow.tol = 0.000001 uniform_flow.unsteady = True From 7505806ff4ad743fc77bf10833847f2afdaaedc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lie=20Grebe?= Date: Sat, 18 Jul 2026 03:48:24 -0400 Subject: [PATCH 20/61] Add support for Riemann boundary condition to all scalar solvers (#2846) * Move BC_Riemann definition from CTurbSolver to CScalarSolver This allows BC_Riemann to be used with all scalar solvers, particularily the species solver. * Add Riemann boundary check to CSpeciesSolver::SetUniformInlet * Add regression test for species transport with Riemann BC --- SU2_CFD/include/solvers/CScalarSolver.hpp | 16 +++ SU2_CFD/include/solvers/CScalarSolver.inl | 19 +++- SU2_CFD/include/solvers/CTurbSolver.hpp | 14 --- SU2_CFD/src/solvers/CSpeciesSolver.cpp | 13 ++- SU2_CFD/src/solvers/CTurbSolver.cpp | 15 --- .../air_nozzle/air_nozzle_species.cfg | 107 ++++++++++++++++++ TestCases/serial_regression.py | 9 ++ 7 files changed, 161 insertions(+), 32 deletions(-) create mode 100644 TestCases/axisymmetric_rans/air_nozzle/air_nozzle_species.cfg diff --git a/SU2_CFD/include/solvers/CScalarSolver.hpp b/SU2_CFD/include/solvers/CScalarSolver.hpp index 1ef6d597a94b..ce391414b0bf 100644 --- a/SU2_CFD/include/solvers/CScalarSolver.hpp +++ b/SU2_CFD/include/solvers/CScalarSolver.hpp @@ -493,6 +493,22 @@ class CScalarSolver : public CSolver { /*--- Convective fluxes across euler wall are equal to zero. ---*/ } + /*! + * \brief Impose the boundary condition using characteristic recostruction. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container vector with all the solutions. + * \param[in] numerics - Description of the numerical method. + * \param[in] config - Definition of the particular problem. + * \param[in] val_marker - Surface marker where the boundary condition is applied. + */ + void BC_Riemann(CGeometry *geometry, + CSolver **solver_container, + CNumerics *conv_numerics, + CNumerics *visc_numerics, + CConfig *config, + unsigned short val_marker) final; + + /*! * \brief Impose the supersonic inlet boundary condition (same as inlet, see BC_Inlet). */ diff --git a/SU2_CFD/include/solvers/CScalarSolver.inl b/SU2_CFD/include/solvers/CScalarSolver.inl index 7d6871f8ca4e..fd6848c7a470 100644 --- a/SU2_CFD/include/solvers/CScalarSolver.inl +++ b/SU2_CFD/include/solvers/CScalarSolver.inl @@ -371,6 +371,23 @@ void CScalarSolver::SumEdgeFluxes(CGeometry* geometry) { END_SU2_OMP_FOR } +template +void CScalarSolver::BC_Riemann(CGeometry *geometry, CSolver **solver_container, CNumerics *conv_numerics, CNumerics *visc_numerics, CConfig *config, unsigned short val_marker) { + SU2_ZONE_SCOPED + + string Marker_Tag = config->GetMarker_All_TagBound(val_marker); + + switch(config->GetKind_Data_Riemann(Marker_Tag)) + { + case TOTAL_CONDITIONS_PT: case STATIC_SUPERSONIC_INFLOW_PT: case STATIC_SUPERSONIC_INFLOW_PD: case DENSITY_VELOCITY: + BC_Inlet(geometry, solver_container, conv_numerics, visc_numerics, config, val_marker); + break; + case STATIC_PRESSURE: + BC_Outlet(geometry, solver_container, conv_numerics, visc_numerics, config, val_marker); + break; + } +} + template void CScalarSolver::BC_Periodic(CGeometry* geometry, CSolver** solver_container, CNumerics* numerics, CConfig* config) { @@ -884,4 +901,4 @@ void CScalarSolver::PushSolutionBackInTime(unsigned long TimeIter, nodes->Set_Solution_time_n(); } } -} \ No newline at end of file +} diff --git a/SU2_CFD/include/solvers/CTurbSolver.hpp b/SU2_CFD/include/solvers/CTurbSolver.hpp index f2d1c789f7de..47636253a9d7 100644 --- a/SU2_CFD/include/solvers/CTurbSolver.hpp +++ b/SU2_CFD/include/solvers/CTurbSolver.hpp @@ -55,20 +55,6 @@ class CTurbSolver : public CScalarSolver { */ CTurbSolver(CGeometry* geometry, CConfig *config, bool conservative); - /*! - * \brief Impose via the residual the Euler wall boundary condition. - * \param[in] geometry - Geometrical definition of the problem. - * \param[in] solver_container - Container vector with all the solutions. - * \param[in] numerics - Description of the numerical method. - * \param[in] config - Definition of the particular problem. - * \param[in] val_marker - Surface marker where the boundary condition is applied. - */ - void BC_Riemann(CGeometry *geometry, - CSolver **solver_container, - CNumerics *conv_numerics, - CNumerics *visc_numerics, - CConfig *config, - unsigned short val_marker) final; /*! * \brief Impose via the residual the Euler wall boundary condition. diff --git a/SU2_CFD/src/solvers/CSpeciesSolver.cpp b/SU2_CFD/src/solvers/CSpeciesSolver.cpp index 70245555eb64..dc9f278e776d 100644 --- a/SU2_CFD/src/solvers/CSpeciesSolver.cpp +++ b/SU2_CFD/src/solvers/CSpeciesSolver.cpp @@ -517,9 +517,18 @@ su2double CSpeciesSolver::GetInletAtVertex(unsigned short iMarker, unsigned long void CSpeciesSolver::SetUniformInlet(const CConfig* config, unsigned short iMarker) { SU2_ZONE_SCOPED + bool riemann_inlet = false; + + const string Marker_Tag = config->GetMarker_All_TagBound(iMarker); + if (config->GetMarker_All_KindBC(iMarker) == RIEMANN_BOUNDARY) { + switch (config->GetKind_Data_Riemann(Marker_Tag)) { + case TOTAL_CONDITIONS_PT: case STATIC_SUPERSONIC_INFLOW_PT: case STATIC_SUPERSONIC_INFLOW_PD: case DENSITY_VELOCITY: + riemann_inlet = true; + break; + } + } /*--- Find BC string to the numeric-identifier. ---*/ - if (config->GetMarker_All_KindBC(iMarker) == INLET_FLOW || config->GetMarker_All_KindBC(iMarker) == SUPERSONIC_INLET) { - const string Marker_Tag = config->GetMarker_All_TagBound(iMarker); + if (config->GetMarker_All_KindBC(iMarker) == INLET_FLOW || config->GetMarker_All_KindBC(iMarker) == SUPERSONIC_INLET || riemann_inlet) { for (unsigned long iVertex = 0; iVertex < nVertex[iMarker]; iVertex++) { for (unsigned short iVar = 0; iVar < nVar; iVar++) { Inlet_SpeciesVars[iMarker][iVertex][iVar] = config->GetInlet_SpeciesVal(Marker_Tag)[iVar]; diff --git a/SU2_CFD/src/solvers/CTurbSolver.cpp b/SU2_CFD/src/solvers/CTurbSolver.cpp index 394c9a0eba42..0ac07ab1642c 100644 --- a/SU2_CFD/src/solvers/CTurbSolver.cpp +++ b/SU2_CFD/src/solvers/CTurbSolver.cpp @@ -47,21 +47,6 @@ CTurbSolver::~CTurbSolver() { } } -void CTurbSolver::BC_Riemann(CGeometry *geometry, CSolver **solver_container, CNumerics *conv_numerics, CNumerics *visc_numerics, CConfig *config, unsigned short val_marker) { - SU2_ZONE_SCOPED - - string Marker_Tag = config->GetMarker_All_TagBound(val_marker); - - switch(config->GetKind_Data_Riemann(Marker_Tag)) - { - case TOTAL_CONDITIONS_PT: case STATIC_SUPERSONIC_INFLOW_PT: case STATIC_SUPERSONIC_INFLOW_PD: case DENSITY_VELOCITY: - BC_Inlet(geometry, solver_container, conv_numerics, visc_numerics, config, val_marker); - break; - case STATIC_PRESSURE: - BC_Outlet(geometry, solver_container, conv_numerics, visc_numerics, config, val_marker); - break; - } -} void CTurbSolver::BC_TurboRiemann(CGeometry *geometry, CSolver **solver_container, CNumerics *conv_numerics, CNumerics *visc_numerics, CConfig *config, unsigned short val_marker) { SU2_ZONE_SCOPED diff --git a/TestCases/axisymmetric_rans/air_nozzle/air_nozzle_species.cfg b/TestCases/axisymmetric_rans/air_nozzle/air_nozzle_species.cfg new file mode 100644 index 000000000000..27ad7d6fa35e --- /dev/null +++ b/TestCases/axisymmetric_rans/air_nozzle/air_nozzle_species.cfg @@ -0,0 +1,107 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: Axisymmetric supersonic converging-diverging air nozzle % +% Author: Florian Dittmann % +% Date: 2021.12.02 % +% File Version 8.5.0 "Harrier" % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% +% +SOLVER= RANS +KIND_TURB_MODEL= SST +RESTART_SOL= NO +AXISYMMETRIC= YES + +% -------------------- COMPRESSIBLE FREE-STREAM DEFINITION --------------------% +% +MACH_NUMBER= 1E-9 +INIT_OPTION= TD_CONDITIONS +FREESTREAM_OPTION= TEMPERATURE_FS +FREESTREAM_PRESSURE= 1400000 +FREESTREAM_TEMPERATURE= 373.15 +REF_DIMENSIONALIZATION= DIMENSIONAL + +% ---- IDEAL GAS, POLYTROPIC, VAN DER WAALS AND PENG ROBINSON CONSTANTS -------% +% +FLUID_MODEL= STANDARD_AIR + +% --------------------------- VISCOSITY MODEL ---------------------------------% +% +VISCOSITY_MODEL= CONSTANT_VISCOSITY +MU_CONSTANT= 1.716E-5 + +% --------------------------- THERMAL CONDUCTIVITY MODEL ----------------------% +% +CONDUCTIVITY_MODEL= CONSTANT_PRANDTL +PRANDTL_LAM= 0.72 +PRANDTL_TURB= 0.90 + +% -------------------- BOUNDARY CONDITION DEFINITION --------------------------% +% +MARKER_HEATFLUX= ( WALL, 0.0 ) +MARKER_SYM= ( SYMMETRY ) +MARKER_RIEMANN= ( INFLOW, TOTAL_CONDITIONS_PT, 1400000.0, 373.15, 1.0, 0.0, 0.0, \ + OUTFLOW, STATIC_PRESSURE, 100000.0, 0.0, 0.0, 0.0, 0.0 ) +MARKER_MONITORING = (WALL) + + +% --------------------- SPECIES TRANSPORT SIMULATION --------------------------% +% +KIND_SCALAR_MODEL= SPECIES_TRANSPORT +DIFFUSIVITY_MODEL= CONSTANT_DIFFUSIVITY +DIFFUSIVITY_CONSTANT= 0.001 +MARKER_INLET_SPECIES= ( INFLOW, 0.5 ) +SPECIES_INIT= 0.25 +SPECIES_CLIPPING= YES +SPECIES_CLIPPING_MAX= 1.0 +SPECIES_CLIPPING_MIN= 0.0 + +% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% +% +NUM_METHOD_GRAD= GREEN_GAUSS +CFL_NUMBER= 1000.0 +CFL_ADAPT= NO +MAX_DELTA_TIME= 1E6 +OBJECTIVE_FUNCTION= DRAG + +% ----------- SLOPE LIMITER AND DISSIPATION SENSOR DEFINITION -----------------% +% +MUSCL_FLOW= YES +SLOPE_LIMITER_FLOW= NONE + +% ------------------------ LINEAR SOLVER DEFINITION ---------------------------% +% +LINEAR_SOLVER= FGMRES +LINEAR_SOLVER_PREC= ILU +LINEAR_SOLVER_ILU_FILL_IN= 0 +LINEAR_SOLVER_ERROR= 0.01 +LINEAR_SOLVER_ITER= 10 + +% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% +% +CONV_NUM_METHOD_FLOW= ROE +ENTROPY_FIX_COEFF= 0.1 +TIME_DISCRE_FLOW= EULER_IMPLICIT + +% -------------------- TURBULENT NUMERICAL METHOD DEFINITION ------------------% +% +CONV_NUM_METHOD_TURB= SCALAR_UPWIND +TIME_DISCRE_TURB= EULER_IMPLICIT +CFL_REDUCTION_TURB= 1.0 + +% --------------------------- CONVERGENCE PARAMETERS --------------------------% +% +ITER= 15 +CONV_RESIDUAL_MINVAL= -12 +CONV_STARTITER= 10 + +% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% +% +MESH_FILENAME= nozzle.su2 +RESTART_FILENAME= restart_flow +OUTPUT_WRT_FREQ= 1000 +SCREEN_OUTPUT= (INNER_ITER, RMS_DENSITY, RMS_ENERGY, RMS_TKE, RMS_DISSIPATION, RMS_SPECIES_0, TOTAL_HEATFLUX, \ + RMS_ADJ_DENSITY, RMS_ADJ_ENERGY, RMS_ADJ_TKE, RMS_ADJ_DISSIPATION) diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index 2a00185ea860..34b395713611 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -349,6 +349,15 @@ def main(): axi_rans_air_nozzle_restart.tol = 0.0001 test_list.append(axi_rans_air_nozzle_restart) + # Axisymmetric air nozzle species + axi_rans_air_nozzle_species = TestCase('axi_rans_air_nozzle_species') + axi_rans_air_nozzle_species.cfg_dir = "axisymmetric_rans/air_nozzle" + axi_rans_air_nozzle_species.cfg_file = "air_nozzle_species.cfg" + axi_rans_air_nozzle_species.test_iter = 10 + axi_rans_air_nozzle_species.test_vals = [-1.840714, 3.726195, -2.009323, 5.649002, -2.494388, 0.0000] + axi_rans_air_nozzle_species.tol = 0.0001 + test_list.append(axi_rans_air_nozzle_species) + ################################# ## Compressible RANS Restart ### ################################# From 6ea93be78c8bee7ccf55894c3ef442333766a253 Mon Sep 17 00:00:00 2001 From: Elon <87638938+yiluntam@users.noreply.github.com> Date: Sun, 19 Jul 2026 00:41:36 +0800 Subject: [PATCH 21/61] Enable viscous flux contribution at supersonic outlet boundary condition (#2829) * Enable viscous flux contribution at supersonic outlet boundary condition The viscous flux contribution in BC_Supersonic_Outlet was previously commented out due to reported convergence issues. However, for viscous NS simulations this omission is physically incorrect and causes severe convergence problems at outlet boundary cells, which also prevents the use of second-order MUSCL reconstruction. For supersonic flows, downstream disturbances cannot propagate upstream, so the outlet state can be safely extrapolated from the domain interior. Using SetPrimitive(V_domain, V_domain) instead of (V_domain, V_outlet) resolves the convergence problems that motivated the original commenting. * Fix: declare Point_Normal in BC_Supersonic_Outlet viscous section * Add laminar flat plate test case config (Ma=4.0, Re=1.2e4) Co-Authored-By: Claude Opus 4.7 * Remove test case files from PR Co-Authored-By: Claude Opus 4.7 * Add supersonic flat plate test case configuration (Ma=4.0, Re=1.2e4) Co-Authored-By: Claude Opus 4.7 * Add flatplate_supersonic to parallel regression tests Co-Authored-By: Claude Opus 4.7 * Update TestCases/navierstokes/flatplate/flatplate_supersonic.cfg Co-authored-by: Nijso * Update TestCases/navierstokes/flatplate/flatplate_supersonic.cfg Co-authored-by: Nijso * Update TestCases/navierstokes/flatplate/flatplate_supersonic.cfg Co-authored-by: Nijso * Update TestCases/navierstokes/flatplate/flatplate_supersonic.cfg Co-authored-by: Nijso * Update supersonic flat plate config * Apply suggestion from @bigfooted * Apply suggestion from @bigfooted * Add Yilun Tan to AUTHORS.md --------- Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Co-authored-by: Nijso Co-authored-by: Claude Opus 4.7 Co-authored-by: Nijso --- AUTHORS.md | 1 + SU2_CFD/src/solvers/CEulerSolver.cpp | 78 ++++++++++--------- .../flatplate/flatplate_supersonic.cfg | 72 +++++++++++++++++ TestCases/parallel_regression.py | 8 ++ 4 files changed, 121 insertions(+), 38 deletions(-) create mode 100644 TestCases/navierstokes/flatplate/flatplate_supersonic.cfg diff --git a/AUTHORS.md b/AUTHORS.md index 554d925a8e97..eaea9531b712 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -172,4 +172,5 @@ sravya91 srcopela tobadavid vfrancesmolla +Yilun Tan ``` diff --git a/SU2_CFD/src/solvers/CEulerSolver.cpp b/SU2_CFD/src/solvers/CEulerSolver.cpp index 81529583d07e..4532fc2e6c1c 100644 --- a/SU2_CFD/src/solvers/CEulerSolver.cpp +++ b/SU2_CFD/src/solvers/CEulerSolver.cpp @@ -7678,6 +7678,7 @@ void CEulerSolver::BC_Supersonic_Outlet(CGeometry *geometry, CSolver **solver_co su2double *V_outlet, *V_domain; bool implicit = (config->GetKind_TimeIntScheme() == EULER_IMPLICIT); + bool viscous = config->GetViscous(); string Marker_Tag = config->GetMarker_All_TagBound(val_marker); auto *Normal = new su2double[nDim]; @@ -7742,44 +7743,45 @@ void CEulerSolver::BC_Supersonic_Outlet(CGeometry *geometry, CSolver **solver_co if (implicit) Jacobian.AddBlock2Diag(iPoint, residual.jacobian_i); -// /*--- Viscous contribution, commented out because serious convergence problems ---*/ -// -// if (viscous) { -// -// /*--- Set laminar and eddy viscosity at the infinity ---*/ -// -// V_outlet[nDim+5] = nodes->GetLaminarViscosity(iPoint); -// V_outlet[nDim+6] = nodes->GetEddyViscosity(iPoint); -// -// /*--- Set the normal vector and the coordinates ---*/ -// -// visc_numerics->SetNormal(Normal); -// su2double Coord_Reflected[MAXNDIM]; -// GeometryToolbox::PointPointReflect(nDim, geometry->nodes->GetCoord(Point_Normal), -// geometry->nodes->GetCoord(iPoint), Coord_Reflected); -// visc_numerics->SetCoord(geometry->nodes->GetCoord(iPoint), Coord_Reflected); -// -// /*--- Primitive variables, and gradient ---*/ -// -// visc_numerics->SetPrimitive(V_domain, V_outlet); -// visc_numerics->SetPrimVarGradient(nodes->GetGradient_Primitive(iPoint), nodes->GetGradient_Primitive(iPoint)); -// -// /*--- Turbulent kinetic energy ---*/ -// -// if (config->GetKind_Turb_Model() == TURB_MODEL::SST) -// visc_numerics->SetTurbKineticEnergy(solver_container[TURB_SOL]->GetNodes()->GetSolution(iPoint,0), -// solver_container[TURB_SOL]->GetNodes()->GetSolution(iPoint,0)); -// -// /*--- Compute and update residual ---*/ -// -// auto residual = visc_numerics->ComputeResidual(config); -// LinSysRes.SubtractBlock(iPoint, residual); -// -// /*--- Jacobian contribution for implicit integration ---*/ -// -// if (implicit) -// Jacobian.SubtractBlock2Diag(iPoint, residual.jacobian_i); -// } + /*--- Viscous contribution, commented out because serious convergence problems ---*/ + + if (viscous) { + + /*--- Set laminar and eddy viscosity at the infinity ---*/ + + V_outlet[nDim+5] = nodes->GetLaminarViscosity(iPoint); + V_outlet[nDim+6] = nodes->GetEddyViscosity(iPoint); + + /*--- Set the normal vector and the coordinates ---*/ + + visc_numerics->SetNormal(Normal); + su2double Coord_Reflected[MAXNDIM]; + unsigned long Point_Normal = geometry->vertex[val_marker][iVertex]->GetNormal_Neighbor(); + GeometryToolbox::PointPointReflect(nDim, geometry->nodes->GetCoord(Point_Normal), + geometry->nodes->GetCoord(iPoint), Coord_Reflected); + visc_numerics->SetCoord(geometry->nodes->GetCoord(iPoint), Coord_Reflected); + + /*--- Primitive variables, and gradient ---*/ + + visc_numerics->SetPrimitive(V_domain, V_domain); + visc_numerics->SetPrimVarGradient(nodes->GetGradient_Primitive(iPoint), nodes->GetGradient_Primitive(iPoint)); + + /*--- Turbulent kinetic energy ---*/ + + if (config->GetKind_Turb_Model() == TURB_MODEL::SST) + visc_numerics->SetTurbKineticEnergy(solver_container[TURB_SOL]->GetNodes()->GetSolution(iPoint,0), + solver_container[TURB_SOL]->GetNodes()->GetSolution(iPoint,0)); + + /*--- Compute and update residual ---*/ + + auto residual = visc_numerics->ComputeResidual(config); + LinSysRes.SubtractBlock(iPoint, residual); + + /*--- Jacobian contribution for implicit integration ---*/ + + if (implicit) + Jacobian.SubtractBlock2Diag(iPoint, residual.jacobian_i); + } } } diff --git a/TestCases/navierstokes/flatplate/flatplate_supersonic.cfg b/TestCases/navierstokes/flatplate/flatplate_supersonic.cfg new file mode 100644 index 000000000000..f7798c39f785 --- /dev/null +++ b/TestCases/navierstokes/flatplate/flatplate_supersonic.cfg @@ -0,0 +1,72 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: Supersonic flat plate flow % +% Author: Tan Yilun % +% Institution: University of Chinese Academy of Sciences % +% Date: 2026.05.22 % +% File Version 8.5.1 % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +SOLVER= NAVIER_STOKES +KIND_TURB_MODEL= NONE +MATH_PROBLEM= DIRECT +RESTART_SOL= YES +SOLUTION_FILENAME= restart_flow.dat + +MESH_FILENAME= flatplate_supersonic.cgns +MESH_FORMAT= CGNS + +TABULAR_FORMAT= TECPLOT +CONV_FILENAME= history +RESTART_FILENAME= restart_flow +VOLUME_FILENAME= volume_flow +SURFACE_FILENAME= surface_flow +OUTPUT_WRT_FREQ= 500 +WRT_RESTART_OVERWRITE= YES +WRT_SURFACE_OVERWRITE= NO +WRT_VOLUME_OVERWRITE= NO +OUTPUT_FILES= (RESTART, TECPLOT, SURFACE_CSV) +SCREEN_OUTPUT=(INNER_ITER, RMS_DENSITY, RMS_MOMENTUM-X, RMS_MOMENTUM-Y, RMS_ENERGY, DRAG) + +MACH_NUMBER= 4.0 +AOA= 0.0 +SIDESLIP_ANGLE= 0.0 +INIT_OPTION= TD_CONDITIONS +FREESTREAM_TEMPERATURE= 300.0 +REYNOLDS_NUMBER= 12314 +FREESTREAM_PRESSURE= 70.47 +REYNOLDS_LENGTH= 0.2 + +MARKER_ISOTHERMAL= ( WALL, 300.0 ) +MARKER_SYM= ( SYMMETRY ) +MARKER_FAR= ( INLET ) +MARKER_SUPERSONIC_OUTLET= ( OUTLET ) +MARKER_PLOTTING= ( WALL ) +MARKER_MONITORING= ( WALL ) + +NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES + +LINEAR_SOLVER= BCGSTAB +LINEAR_SOLVER_PREC= LU_SGS +LINEAR_SOLVER_ERROR= 1E-10 +LINEAR_SOLVER_ITER= 10 + +MGLEVEL= 0 + +CONV_NUM_METHOD_FLOW= AUSM +MUSCL_FLOW= YES +SLOPE_LIMITER_FLOW= VENKATAKRISHNAN_WANG +VENKAT_LIMITER_COEFF= 0.05 + +CFL_NUMBER= 0.1 +CFL_ADAPT= YES +CFL_ADAPT_PARAM= ( 0.3, 1.05, 0.01, 5) +ITER= 1000 +TIME_DISCRE_FLOW= EULER_IMPLICIT + +CONV_FIELD= RMS_ENERGY +CONV_RESIDUAL_MINVAL= -5 +CONV_STARTITER= 10 + diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index a74586507e67..ebbf99274368 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -350,6 +350,14 @@ def main(): flatplate.test_vals = [-6.496808, -1.017942, 0.001224, 0.028377, 2.361500, -2.333200, 0.000000, 0.000000] test_list.append(flatplate) + # Supersonic laminar flat plate + flatplate_supersonic = TestCase('flatplate_supersonic') + flatplate_supersonic.cfg_dir = "navierstokes/flatplate" + flatplate_supersonic.cfg_file = "flatplate_supersonic.cfg" + flatplate_supersonic.test_iter = 100 + flatplate_supersonic.test_vals = [100.000000, -2.940787, -0.677195, -0.570647, 2.495441, 0.001678] + test_list.append(flatplate_supersonic) + # Custom objective function flatplate_udobj = TestCase('flatplate_udobj') flatplate_udobj.cfg_dir = "user_defined_functions" From 4564e131159520fd20c2ead9e981c80d617a7e5d Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Sat, 18 Jul 2026 19:25:26 -0700 Subject: [PATCH 22/61] Quantization of the Jacobian matrix to speed up linear algebra operations (#2836) * int8 quantization * [skip ci] * [skip ci] * full crazy * micro optimize * compress more [skip ci] * consistency [skip ci] * productize quantization as a new preconditioner * cleanup * cleanup overloads * reduce potential incompatibility with quantization * typedef int type * address more compatibility * final fixes for non-simd numerics * heat solver fix * need to check da_sp_pinArray_cht_2d_dp_hf * add comp and incomp tests * unit test * unit tests + updates * updates * multizone bin test * fix binary reader * fix * fix * again --- .../basic_types/datatype_structure.hpp | 4 + Common/include/code_config.hpp | 5 + Common/include/geometry/CGeometry.hpp | 12 +- .../meshreader/CSU2BinaryMeshReaderBase.hpp | 10 +- .../include/linear_algebra/CPastixWrapper.hpp | 18 +- .../linear_algebra/CPreconditioner.hpp | 34 ++ Common/include/linear_algebra/CSysMatrix.hpp | 446 ++++++++++++------ Common/include/linear_algebra/CSysMatrix.inl | 96 +++- Common/include/option_structure.hpp | 2 + Common/include/toolboxes/graph_toolbox.hpp | 11 +- Common/src/CConfig.cpp | 10 +- Common/src/geometry/CGeometry.cpp | 6 +- .../meshreader/CSU2BinaryMeshReaderBase.cpp | 24 +- Common/src/linear_algebra/CSysMatrix.cpp | 225 ++++++--- Common/src/linear_algebra/CSysSolve.cpp | 3 +- SU2_CFD/include/numerics_simd/util.hpp | 2 +- .../include/solvers/CFVMFlowSolverBase.hpp | 50 +- .../include/solvers/CFVMFlowSolverBase.inl | 24 +- SU2_CFD/include/solvers/CIncNSSolver.hpp | 5 +- SU2_CFD/include/solvers/CNSSolver.hpp | 5 +- SU2_CFD/include/solvers/CScalarSolver.inl | 6 +- SU2_CFD/include/solvers/CSolver.hpp | 2 +- .../filewriter/CSU2MeshBinaryFileWriter.cpp | 4 +- SU2_CFD/src/solvers/CAdjEulerSolver.cpp | 2 +- SU2_CFD/src/solvers/CEulerSolver.cpp | 27 +- SU2_CFD/src/solvers/CFEASolver.cpp | 2 +- SU2_CFD/src/solvers/CHeatSolver.cpp | 12 +- SU2_CFD/src/solvers/CIncEulerSolver.cpp | 53 +-- SU2_CFD/src/solvers/CIncNSSolver.cpp | 7 +- SU2_CFD/src/solvers/CNEMOEulerSolver.cpp | 8 +- SU2_CFD/src/solvers/CNEMONSSolver.cpp | 2 +- SU2_CFD/src/solvers/CNSSolver.cpp | 7 +- SU2_CFD/src/solvers/CSolver.cpp | 60 +-- TestCases/hybrid_regression.py | 51 +- TestCases/hybrid_regression_AD.py | 8 +- .../incomp_navierstokes/sphere/sphere.cfg | 6 +- .../poiseuille/lam_poiseuille.cfg | 4 +- TestCases/parallel_regression.py | 18 +- TestCases/parallel_regression_AD.py | 4 +- .../forces_0.csv.ref | 6 +- TestCases/serial_regression.py | 15 +- .../channel_2D/channel_2D_WA.cfg | 4 +- .../channel_2D/mesh_su2_to_su2bin.cfg | 40 ++ TestCases/tutorials.py | 4 +- .../linear_algebra/quantization_tests.cpp | 113 +++++ UnitTests/meson.build | 3 +- config_template.cfg | 6 +- 47 files changed, 1051 insertions(+), 415 deletions(-) create mode 100644 TestCases/sliding_interface/channel_2D/mesh_su2_to_su2bin.cfg create mode 100644 UnitTests/Common/linear_algebra/quantization_tests.cpp diff --git a/Common/include/basic_types/datatype_structure.hpp b/Common/include/basic_types/datatype_structure.hpp index 623c410e5510..d9b6bb0b58fb 100644 --- a/Common/include/basic_types/datatype_structure.hpp +++ b/Common/include/basic_types/datatype_structure.hpp @@ -135,6 +135,10 @@ template <> struct Passive { FORCEINLINE static passivedouble Value(const su2double& val) { return GetValue(val); } }; +template +FORCEINLINE auto PassiveValue(const T& val) { + return Passive::Value(val); +} /*! * \brief Casts the primitive value to int (uses GetValue, already implemented for each type). diff --git a/Common/include/code_config.hpp b/Common/include/code_config.hpp index bc02c2830aab..ae30c779e7af 100644 --- a/Common/include/code_config.hpp +++ b/Common/include/code_config.hpp @@ -26,6 +26,7 @@ */ #pragma once +#include #include #include @@ -100,6 +101,10 @@ FORCEINLINE Out su2staticcast_p(In ptr) { #undef USE_SINGLE_PRECISION #endif +/*--- Default integer types. Currently used for rank-local sparse patterns. ---*/ +using su2uint = uint32_t; +using su2int = int32_t; + /*--- This type can be used for (rare) compatibility cases or for * computations that are intended to be (always) passive. ---*/ #ifdef USE_SINGLE_PRECISION diff --git a/Common/include/geometry/CGeometry.hpp b/Common/include/geometry/CGeometry.hpp index 5d1e7e5a83a9..97d3f8455376 100644 --- a/Common/include/geometry/CGeometry.hpp +++ b/Common/include/geometry/CGeometry.hpp @@ -205,10 +205,10 @@ class CGeometry { LDUSparsePattern finiteElementPatternFill0; /*!< \brief FEM sparsity with 0-fill (structural pattern). */ LDUSparsePattern finiteElementPatternFillN; /*!< \brief FEM sparsity with N-fill (e.g. for ILU-N). */ - su2vector finiteVolumeLToUTranspMap; /*!< \brief FVM L-entry -> U-entry of its transpose. */ - su2vector finiteVolumeUToLTranspMap; /*!< \brief FVM U-entry -> L-entry of its transpose. */ - su2vector finiteElementLToUTranspMap; /*!< \brief FEM L-entry -> U-entry of its transpose. */ - su2vector finiteElementUToLTranspMap; /*!< \brief FEM U-entry -> L-entry of its transpose. */ + su2vector finiteVolumeLToUTranspMap; /*!< \brief FVM L-entry -> U-entry of its transpose. */ + su2vector finiteVolumeUToLTranspMap; /*!< \brief FVM U-entry -> L-entry of its transpose. */ + su2vector finiteElementLToUTranspMap; /*!< \brief FEM L-entry -> U-entry of its transpose. */ + su2vector finiteElementUToLTranspMap; /*!< \brief FEM U-entry -> L-entry of its transpose. */ /*--- Edge and element colorings. ---*/ @@ -1892,7 +1892,7 @@ class CGeometry { * \param[in] type - Finite volume or finite element. * \return Reference to the l_to_u map. */ - const su2vector& GetLToUTransposeSparsePatternMap(ConnectivityType type); + const su2vector& GetLToUTransposeSparsePatternMap(ConnectivityType type); /*! * \brief Get the bijective map from U-entry indices to L-entry indices of their transposes. @@ -1900,7 +1900,7 @@ class CGeometry { * \param[in] type - Finite volume or finite element. * \return Reference to the u_to_l map. */ - const su2vector& GetUToLTransposeSparsePatternMap(ConnectivityType type); + const su2vector& GetUToLTransposeSparsePatternMap(ConnectivityType type); /*! * \brief Get the edge coloring. diff --git a/Common/include/geometry/meshreader/CSU2BinaryMeshReaderBase.hpp b/Common/include/geometry/meshreader/CSU2BinaryMeshReaderBase.hpp index b6041d8cf624..f4b71d94e64e 100644 --- a/Common/include/geometry/meshreader/CSU2BinaryMeshReaderBase.hpp +++ b/Common/include/geometry/meshreader/CSU2BinaryMeshReaderBase.hpp @@ -110,9 +110,15 @@ class CSU2BinaryMeshReaderBase : public CSU2MeshReaderBase { private: /*! - * \brief Read the meta data for a zone. + * \brief Read the meta data for a zone, advancing the file position past it. + * \param[in] storeMetadata - Whether to store the read values in the + * dimension, numberOfGlobalElements, numberOfGlobalPoints and + * numberOfMarkers members. Must be false when only skipping + * past a lower-numbered zone (from + * FastForwardToMyZone), so that doing so does not clobber the + * metadata already read for the current (target) zone. */ - void ReadMetadataZone(); + void ReadMetadataZone(bool storeMetadata); public: /*! diff --git a/Common/include/linear_algebra/CPastixWrapper.hpp b/Common/include/linear_algebra/CPastixWrapper.hpp index a471a60eaffe..44fb9d503db3 100644 --- a/Common/include/linear_algebra/CPastixWrapper.hpp +++ b/Common/include/linear_algebra/CPastixWrapper.hpp @@ -28,6 +28,8 @@ #pragma once +#include "../code_config.hpp" + #ifdef HAVE_PASTIX #ifdef CODI_FORWARD_TYPE @@ -74,11 +76,11 @@ class CPastixWrapper { unsigned long nPointDomain = 0; unsigned long blkSz = 0; /*!< \brief Block size (nVar * nVar) for value assembly. */ - const unsigned long* row_ptr_l = nullptr; /*!< \brief LDU lower row pointers (geometry-owned). */ - const unsigned long* row_ptr_u = nullptr; /*!< \brief LDU upper row pointers (geometry-owned). */ - const ScalarType* d = nullptr; /*!< \brief Diagonal blocks (matrix-owned). */ - const ScalarType* l = nullptr; /*!< \brief Lower blocks (matrix-owned). */ - const ScalarType* u = nullptr; /*!< \brief Upper blocks (matrix-owned). */ + const su2uint* row_ptr_l = nullptr; /*!< \brief LDU lower row pointers (geometry-owned). */ + const su2uint* row_ptr_u = nullptr; /*!< \brief LDU upper row pointers (geometry-owned). */ + const ScalarType* d = nullptr; /*!< \brief Diagonal blocks (matrix-owned). */ + const ScalarType* l = nullptr; /*!< \brief Lower blocks (matrix-owned). */ + const ScalarType* u = nullptr; /*!< \brief Upper blocks (matrix-owned). */ unsigned long size_rhs() const { return nPointDomain * nVar; } } matrix; /*!< \brief Dimensions and LDU pointers captured from the owning CSysMatrix. */ @@ -150,9 +152,9 @@ class CPastixWrapper { * \param[in] col_ind_l/u - LDU lower/upper column indices (geometry-owned). * \param[in] d/l/u - LDU value blocks (matrix-owned, must outlive wrapper). */ - void SetLDU(unsigned long nVar, unsigned long nPoint, unsigned long nPointDomain, const unsigned long* row_ptr_l, - const unsigned long* col_ind_l, const unsigned long* row_ptr_u, const unsigned long* col_ind_u, - const ScalarType* d, const ScalarType* l, const ScalarType* u) { + void SetLDU(unsigned long nVar, unsigned long nPoint, unsigned long nPointDomain, const su2uint* row_ptr_l, + const su2uint* col_ind_l, const su2uint* row_ptr_u, const su2uint* col_ind_u, const ScalarType* d, + const ScalarType* l, const ScalarType* u) { if (issetup) return; matrix.nVar = nVar; matrix.nPoint = nPoint; diff --git a/Common/include/linear_algebra/CPreconditioner.hpp b/Common/include/linear_algebra/CPreconditioner.hpp index e4fc7cf159fa..532e0b538388 100644 --- a/Common/include/linear_algebra/CPreconditioner.hpp +++ b/Common/include/linear_algebra/CPreconditioner.hpp @@ -210,6 +210,37 @@ class CLU_SGSPreconditioner final : public CPreconditioner { } }; +/*! + * \class CQuantizedLUSGSPreconditioner + * \brief Specialization of preconditioner that uses CSysMatrix class. + */ +template +class CQuantizedLUSGSPreconditioner final : public CPreconditioner { + private: + CSysMatrix& sparse_matrix; + CGeometry* geometry; + const CConfig* config; + + public: + inline CQuantizedLUSGSPreconditioner(CSysMatrix& matrix_ref, CGeometry* geometry_ref, + const CConfig* config_ref) + : sparse_matrix(matrix_ref) { + if ((geometry_ref == nullptr) || (config_ref == nullptr)) + SU2_MPI::Error("Preconditioner needs to be built with valid references.", CURRENT_FUNCTION); + geometry = geometry_ref; + config = config_ref; + } + + CQuantizedLUSGSPreconditioner() = delete; + + inline void operator()(const CSysVector& u, CSysVector& v) const override { + sparse_matrix.ComputeLU_SGSPreconditioner(u, v, geometry, config); + } + + /*! \brief Quantize the diagonal blocks (off diagonals are quantized on the fly). */ + inline void Build() override { sparse_matrix.QuantizeDiagonalBlocks(); } +}; + /*! * \class CLineletPreconditioner * \brief Specialization of preconditioner that uses CSysMatrix class. @@ -341,6 +372,9 @@ CPreconditioner* CPreconditioner::Create(ENUM_LINEAR_SOL case LU_SGS: prec = new CLU_SGSPreconditioner(jacobian, geometry, config); break; + case Q_LU_SGS: + prec = new CQuantizedLUSGSPreconditioner(jacobian, geometry, config); + break; case ILU: prec = new CILUPreconditioner(jacobian, geometry, config); break; diff --git a/Common/include/linear_algebra/CSysMatrix.hpp b/Common/include/linear_algebra/CSysMatrix.hpp index 11baf6edf8e8..d31c48fb8a09 100644 --- a/Common/include/linear_algebra/CSysMatrix.hpp +++ b/Common/include/linear_algebra/CSysMatrix.hpp @@ -33,6 +33,7 @@ #include "CPastixWrapper.hpp" #include "../toolboxes/graph_toolbox.hpp" +#include #include #include #include @@ -108,6 +109,96 @@ struct CSysMatrixComms { MPI_QUANTITIES commType = MPI_QUANTITIES::SOLUTION_MATRIX); }; +/*! + * \brief Reconstruct the float row-scale from a stored int8 binary exponent. + * The exponent \p e was packed as (e + 127) into the IEEE 754 biased-exponent field + * with a zero mantissa, giving an exact power of two: 2^e. + * This is the inverse of the encoding in EncodeQuantBlock. + */ +FORCEINLINE float DecodeQuantScale(int8_t e) noexcept { + const uint32_t bits = static_cast(std::max(0, static_cast(e) + 127)) << 23; + float scale; + memcpy(&scale, &bits, sizeof(bits)); + return scale; +} + +/*! + * \brief Encode one nVar×nVar block into per-row int8 quantized storage. + * \p f(r,c) is called twice per entry (max-abs scan then encoding); it should be cheap. + * Stores a per-row scale exponent in \p qs and clamped int8 values in \p qv. + */ +template +FORCEINLINE void EncodeQuantBlock(const F& f, int8_t* __restrict qs, int8_t* __restrict qv, + unsigned long nVar) noexcept { + for (auto r = 0ul; r < nVar; ++r) { + constexpr uint32_t eps_bits = 0x34000000u; + uint32_t max_abs_bits = eps_bits; + for (auto c = 0ul; c < nVar; ++c) { + const float fv = SU2_TYPE::PassiveValue(f(r, c)); + uint32_t fb; + memcpy(&fb, &fv, sizeof(fb)); + max_abs_bits = std::max(max_abs_bits, fb & 0x7FFFFFFFu); + } + const int e = std::min(127, std::max(-128, static_cast(max_abs_bits >> 23) - 133)); + qs[r] = static_cast(e); + const uint32_t inv_bits = static_cast(127 - e) << 23; + float inv_rscale; + memcpy(&inv_rscale, &inv_bits, sizeof(inv_rscale)); + for (auto c = 0ul; c < nVar; ++c) { + qv[r * nVar + c] = + static_cast(std::max(-128.f, std::min(127.f, roundf(SU2_TYPE::PassiveValue(f(r, c)) * inv_rscale)))); + } + } +} + +/*! + * \brief View of one matrix block, const-correct via the ScalarType template parameter. + * \c CBlockView is read-only; \c CBlockView is mutable + * and exposes \c apply(f) for writing with on-the-fly quantized encoding. + * Evaluates to \c false if the block is absent from the sparsity pattern. + */ +template +struct CBlockView { + using QuantType = std::conditional_t, const int8_t, int8_t>; + + ScalarType* ptr = nullptr; ///< Full-precision block; non-null iff not quantized. + QuantType* qs = nullptr; ///< Per-row binary exponent; non-null iff quantized. + QuantType* qv = nullptr; ///< Quantized values (row-major); non-null iff quantized. + unsigned long nVar = 0; + + /*! \brief False when the block is not present in the sparsity pattern. */ + explicit operator bool() const { return ptr || qs; } + + /*! \brief Return entry (row \p i, col \p j), decoding quantization if necessary. */ + std::remove_const_t operator()(unsigned long i, unsigned long j) const { + using T = std::remove_const_t; + if (ptr) return ptr[i * nVar + j]; + return static_cast(qv[i * nVar + j] * DecodeQuantScale(qs[i])); + } + + /*! + * \brief Write the block from callable \p f(i,j). + * \p Overwrite=true overwrites (or quantizes for Q_LU_SGS off-diagonal blocks); + * \p Overwrite=false accumulates into non-quantized storage only — accumulating into + * quantized storage would require decode-accumulate-encode and is a silent no-op. + * Only enabled for mutable (non-const ScalarType) views. + */ + template > = 0> + void apply(const F& f) const { + if (ptr) { + for (auto i = 0ul; i < nVar; ++i) + for (auto j = 0ul; j < nVar; ++j) { + if constexpr (Overwrite) + ptr[i * nVar + j] = f(i, j); + else + ptr[i * nVar + j] += f(i, j); + } + } else if constexpr (Overwrite) { + if (qs) EncodeQuantBlock(f, qs, qv, nVar); + } + } +}; + /*! * \class CSysMatrix * \ingroup SpLinSys @@ -145,31 +236,50 @@ class CSysMatrix { * the pointers address host or device memory is managed by CSysMatrix. */ struct LDU { - ScalarType* d = nullptr; /*!< \brief Diagonal block values. */ - ScalarType* l = nullptr; /*!< \brief Strictly-lower block values. */ - ScalarType* u = nullptr; /*!< \brief Strictly-upper block values. */ - const unsigned long* row_ptr_l = nullptr; /*!< \brief Row pointers for L (geometry-owned or GPU copy). */ - const unsigned long* col_ind_l = nullptr; /*!< \brief Column indices for L. */ - const unsigned long* row_ptr_u = nullptr; /*!< \brief Row pointers for U. */ - const unsigned long* col_ind_u = nullptr; /*!< \brief Column indices for U. */ - unsigned long nnz_l = 0; /*!< \brief Number of L nonzeros. */ - unsigned long nnz_u = 0; /*!< \brief Number of U nonzeros. */ + ScalarType* d = nullptr; /*!< \brief Diagonal block values. */ + ScalarType* l = nullptr; /*!< \brief Strictly-lower block values. */ + ScalarType* u = nullptr; /*!< \brief Strictly-upper block values. */ + const su2uint* row_ptr_l = nullptr; /*!< \brief Row pointers for L (geometry-owned or GPU copy). */ + const su2uint* col_ind_l = nullptr; /*!< \brief Column indices for L. */ + const su2uint* row_ptr_u = nullptr; /*!< \brief Row pointers for U. */ + const su2uint* col_ind_u = nullptr; /*!< \brief Column indices for U. */ + unsigned long nnz_l = 0; /*!< \brief Number of L nonzeros. */ + unsigned long nnz_u = 0; /*!< \brief Number of U nonzeros. */ }; LDU mat; /*!< \brief Host matrix (values owned via aligned_alloc; pattern from geometry). */ LDU gpu; /*!< \brief Device matrix (all pointers to GPU memory). */ LDU ilu; /*!< \brief ILU factorization, host (values owned; pattern from geometry). */ - bool useCuda = false; /*!< \brief Whether CUDA is enabled. */ - const unsigned long* l_to_u_transp; /*!< \brief L-entry index -> U-entry index of its transpose. */ - const unsigned long* u_to_l_transp; /*!< \brief U-entry index -> L-entry index of its transpose. */ + /*--- Quantized off-diagonal storage (used when quantized_mode == true). ---*/ + using QuantType = int8_t; + + /*! \brief Set by Initialize() when preconditioner == Q_LU_SGS. + * mat.l and mat.u are NOT allocated; off-diagonal blocks live in the + * q_* arrays below. */ +#ifndef CODI_REVERSE_TYPE + bool quantized_mode = false; +#else + static constexpr bool quantized_mode = false; +#endif + QuantType* q_scale_l; /*!< \brief Per-row exponent for L blocks, [nnz_l * nVar]. */ + QuantType* q_blocks_l; /*!< \brief Quantized L block entries, [nnz_l * nVar * nEqn]. */ + QuantType* q_scale_u; /*!< \brief Same as q_scale_l for the upper entries. */ + QuantType* q_blocks_u; /*!< \brief Same as q_blocks_l for the upper entries. */ + QuantType* q_scale_d; /*!< \brief Same as q_scale_l for the diagonal entries, [nPoint * nVar]. + * Populated by QuantizeDiagonalBlocks(). */ + QuantType* q_blocks_d; /*!< \brief Same as q_blocks_l for the diagonal entries. */ + + bool useCuda = false; /*!< \brief Whether CUDA is enabled. */ + const su2uint* l_to_u_transp; /*!< \brief L-entry index -> U-entry index of its transpose. */ + const su2uint* u_to_l_transp; /*!< \brief U-entry index -> L-entry index of its transpose. */ /*! * \brief Lookup table from edges to the L-index in the LDU split. * U-index == edge index by construction (edges are ordered 1:1 with the U pattern). * Therefore, edge_ptr_l == u_to_l_transp, but we keep a separate member for clarity. */ - const unsigned long* edge_ptr_l; + const su2uint* edge_ptr_l; unsigned short ilu_fill_in; /*!< \brief Fill level for the ILU preconditioner. */ @@ -179,11 +289,11 @@ class CSysMatrix { ScalarType* invM; /*!< \brief Inverse of (Jacobi) preconditioner. */ /*--- Temporary (hence mutable) working memory used in the Linelet preconditioner, outer vector is for threads ---*/ - mutable vector > + mutable vector> LineletUpper; /*!< \brief Pointers to the upper blocks of the tri-diag system (working memory). */ - mutable vector > + mutable vector> LineletInvDiag; /*!< \brief Inverse of the diagonal blocks of the tri-diag system (working memory). */ - mutable vector > + mutable vector> LineletVector; /*!< \brief Solution and RHS of the tri-diag system (working memory). */ #ifdef USE_MKL @@ -290,7 +400,7 @@ class CSysMatrix { * \param[in,out] matrix - On entry the system matrix, on exit the factorized matrix. * \param[in,out] vec - On entry the rhs, on exit the solution. */ - void Gauss_Elimination(ScalarType* matrix, ScalarType* vec) const; + void GaussElimination(ScalarType* matrix, ScalarType* vec) const; /*! * \brief Invert a small dense matrix. @@ -305,7 +415,7 @@ class CSysMatrix { * \param[in] rhs - Right-hand-side of the linear system. * \return Solution of the linear system (overwritten on rhs). */ - inline void Gauss_Elimination(unsigned long block_i, ScalarType* rhs) const; + inline void GaussElimination(unsigned long block_i, ScalarType* rhs) const; /*! * \brief Inverse diagonal block. @@ -363,6 +473,38 @@ class CSysMatrix { */ void RowProduct(const CSysVector& vec, unsigned long row_i, ScalarType* prod) const; + /*! + * \brief Computes product += A_k * vec using the quantized representation of block k. + * \note Only valid after QuantizeDiagonalBlocks() has been called. + * \param[in] k - Block index in the CSR flat storage. + * \param[in] vec - Input vector (nEqn entries). + * \param[in,out] prod - Accumulation output (nVar entries). + */ + inline void QuantizedMatVecAdd(const QuantType* qs, const QuantType* qv, const ScalarType* vec, + ScalarType* prod) const; + + /*! \brief Quantize one nVar×nVar block (row-major) into the int8 scale+value arrays. + * Called on the hot assembly path (SetBlocks/UpdateBlocks in Q_LU_SGS mode). */ + void QuantizeBlock(const ScalarType* blk, QuantType* qs, QuantType* qv) const; + + /*! \brief Full-row product using quantized L/D/U (Q_LU_SGS SpMV path). */ + inline void QuantizedRowProduct(const CSysVector& vec, unsigned long row_i, ScalarType* prod) const; + + /*! \brief Upper-triangle product using quantized U (Q_LU_SGS backward sweep). */ + inline void QuantizedUpperProduct(const CSysVector& vec, unsigned long row_i, unsigned long col_ub, + ScalarType* prod) const; + + /*! \brief Lower-triangle product using quantized L (Q_LU_SGS forward sweep). */ + inline void QuantizedLowerProduct(const CSysVector& vec, unsigned long row_i, unsigned long col_lb, + ScalarType* prod) const; + + /*! \brief Diagonal product using quantized D (Q_LU_SGS backward sweep). */ + inline void QuantizedDiagonalProduct(const CSysVector& vec, unsigned long row_i, ScalarType* prod) const; + + /*! \brief Gauss elimination on the quantized diagonal block: decodes q_blocks_d into a local + * ScalarType buffer and delegates to the scalar GaussElimination overload. */ + inline void QuantizedGaussElimination(unsigned long block_i, ScalarType* rhs) const; + public: /*! * \brief Constructor of the class. @@ -384,10 +526,20 @@ class CSysMatrix { * \param[in] geometry - Geometrical definition of the problem. * \param[in] config - Definition of the particular problem. * \param[in] needTranspPtr - If the L/U transpose maps should be built, used for "SetDiagonalAsColumnSum". + * \param[in] grad_mode - Gradient smoothing mode, only used to detect the right preconditioner type. + * \param[in] allow_quant - Quantization is only possible with solvers that "set and forget" the off-diagonal + * blocks of the matrix. Solvers that perform multiple updates would lose too much information, so + * that pattern is not supported with quantization (the code will hit null pointers). It is up to + * the solver to declare whether it will "set and forget". */ void Initialize(unsigned long npoint, unsigned long npointdomain, unsigned short nvar, unsigned short neqn, bool EdgeConnect, CGeometry* geometry, const CConfig* config, bool needTranspPtr = false, - bool grad_mode = false); + bool grad_mode = false, bool allow_quant = false); + + /*! + * \brief Compresses off-diagonal blocks into quantized form for use with USE_QUANTIZATION. + */ + void QuantizeDiagonalBlocks(); /*! * \brief Sets to zero all the entries of the sparse matrix. @@ -424,7 +576,7 @@ class CSysMatrix { } /*! - * \brief Get a pointer to the start of block "ij", non-const version + * \brief Get a pointer to the start of block "ij", non-const version. */ FORCEINLINE ScalarType* GetBlock(unsigned long block_i, unsigned long block_j) { const CSysMatrix& const_this = *this; @@ -432,131 +584,98 @@ class CSysMatrix { } /*! - * \brief Gets the value of a particular entry in block "ij". - * \param[in] block_i - Row index. - * \param[in] block_j - Column index. - * \param[in] iVar - Row of the block. - * \param[in] jVar - Column of the block. - * \return Value of the block entry. - */ - FORCEINLINE ScalarType GetBlock(unsigned long block_i, unsigned long block_j, unsigned short iVar, - unsigned short jVar) const { - auto mat_ij = GetBlock(block_i, block_j); - if (!mat_ij) return 0.0; - return mat_ij[iVar * nEqn + jVar]; + * \brief Read-only view of block (block_i, block_j). In Q_LU_SGS mode values are decoded + * on access inside CBlockView::operator()(i,j); no temporary copy is made. + * \return A CBlockView that evaluates to false if the block is absent. + */ + FORCEINLINE CBlockView GetBlockView(unsigned long block_i, unsigned long block_j) const { +#define GET_BLOCK_VIEW_IMPL \ + if (!quantized_mode || block_i == block_j) { \ + return {GetBlock(block_i, block_j), nullptr, nullptr, nVar}; \ + } \ + if (block_j < block_i) { \ + for (auto k = mat.row_ptr_l[block_i]; k < mat.row_ptr_l[block_i + 1]; ++k) \ + if (mat.col_ind_l[k] == block_j) return {nullptr, &q_scale_l[k * nVar], &q_blocks_l[k * nVar * nVar], nVar}; \ + } else { \ + for (auto k = mat.row_ptr_u[block_i]; k < mat.row_ptr_u[block_i + 1]; ++k) \ + if (mat.col_ind_u[k] == block_j) return {nullptr, &q_scale_u[k * nVar], &q_blocks_u[k * nVar * nVar], nVar}; \ + } \ + return {} + GET_BLOCK_VIEW_IMPL; } /*! - * \brief Set the value of a block (in flat format) in the sparse matrix with scaling. - * \note If the template param Overwrite is false we add to the block (bij += alpha*b). - * \param[in] block_i - Row index. - * \param[in] block_j - Column index. - * \param[in] val_block - Block to set to A(i, j). - * \param[in] alpha - Scale factor. - */ - template ::value> = 0> - inline void SetBlock(unsigned long block_i, unsigned long block_j, const OtherType* val_block, - OtherType alpha = 1.0) { - auto mat_ij = GetBlock(block_i, block_j); - if (!mat_ij) return; - SU2_OMP_SIMD - for (auto iVar = 0ul; iVar < nVar * nEqn; ++iVar) { - mat_ij[iVar] = (Overwrite ? ScalarType(0) : mat_ij[iVar]) + PassiveAssign(alpha * val_block[iVar]); - } - } - - /*! - * \brief Add a scaled block (in flat format) to the sparse matrix (see SetBlock). - * \param[in] block_i - Row index. - * \param[in] block_j - Column index. - * \param[in] val_block - Block to set to A(i, j). - * \param[in] alpha - Scale factor. + * \overload Non const version of GetBlockView. */ - template ::value> = 0> - inline void AddBlock(unsigned long block_i, unsigned long block_j, const OtherType* val_block, - OtherType alpha = 1.0) { - SetBlock(block_i, block_j, val_block, alpha); + FORCEINLINE CBlockView GetBlockView(unsigned long block_i, unsigned long block_j) { + GET_BLOCK_VIEW_IMPL; +#undef GET_BLOCK_VIEW_IMPL } /*! * \brief Set the value of a scaled block in the sparse matrix. - * \note If the template param Overwrite is false we add to the block (bij += alpha*b). + * \note This is an templated overload for C2Dcontainer specialization su2matrix. + * It assumes that MatrixType supports a member type Scalar and access operator(i, j). + * If the template param Overwrite is false we add to the block (bij += alpha*b). * \param[in] block_i - Row index. * \param[in] block_j - Column index. * \param[in] val_block - Block to set to A(i, j). * \param[in] alpha - Scale factor. */ - template - inline void SetBlock(unsigned long block_i, unsigned long block_j, const OtherType* const* val_block, - OtherType alpha = 1.0) { - auto mat_ij = GetBlock(block_i, block_j); - if (!mat_ij) return; - for (auto iVar = 0ul; iVar < nVar; ++iVar) { - for (auto jVar = 0ul; jVar < nEqn; ++jVar) { - *mat_ij = (Overwrite ? ScalarType(0) : *mat_ij) + PassiveAssign(alpha * val_block[iVar][jVar]); - ++mat_ij; - } - } + template + inline void SetBlock(unsigned long block_i, unsigned long block_j, MatrixType& val_block, + std::decay_t alpha = 1.0) { + auto view = GetBlockView(block_i, block_j); + if (!view) return; + view.template apply( + [&](unsigned long i, unsigned long j) { return PassiveAssign(alpha * val_block(i, j)); }); } /*! - * \brief Adds a scaled block to the sparse matrix (see SetBlock). - * \param[in] block_i - Row index. - * \param[in] block_j - Column index. - * \param[in] val_block - Block to add to A(i, j). - * \param[in] alpha - Scale factor. + * \overload val_block is a pointer instead of a matrix type. */ - template - inline void AddBlock(unsigned long block_i, unsigned long block_j, const OtherType* const* val_block, - OtherType alpha = 1.0) { - SetBlock(block_i, block_j, val_block, alpha); + template ::value> = 0> + inline void SetBlock(unsigned long block_i, unsigned long block_j, const OtherType* val_block, + std::decay_t alpha = 1.0) { + auto view = GetBlockView(block_i, block_j); + if (!view) return; + view.template apply( + [&](unsigned long i, unsigned long j) { return PassiveAssign(alpha * val_block[i * nEqn + j]); }); } /*! - * \brief Subtracts the specified block to the sparse matrix (see AddBlock). - * \param[in] block_i - Row index. - * \param[in] block_j - Column index. - * \param[in] val_block - Block to subtract to A(i, j). + * \overload val_block is a double pointer instead of matrix type. */ - template - inline void SubtractBlock(unsigned long block_i, unsigned long block_j, const OtherType* const* val_block) { - AddBlock(block_i, block_j, val_block, OtherType(-1)); + template + inline void SetBlock(unsigned long block_i, unsigned long block_j, const OtherType* const* val_block, + std::decay_t alpha = 1.0) { + auto view = GetBlockView(block_i, block_j); + if (!view) return; + view.template apply( + [&](unsigned long i, unsigned long j) { return PassiveAssign(alpha * val_block[i][j]); }); } /*! - * \brief Set the value of a scaled block in the sparse matrix. - * \note This is an templated overload for C2Dcontainer specialization su2matrix. - * It assumes that MatrixType supports a member type Scalar and access operator[][]. - * If the template param Overwrite is false we add to the block (bij += alpha*b). + * \brief Add a scaled block (in flat format) to the sparse matrix (see SetBlock). * \param[in] block_i - Row index. * \param[in] block_j - Column index. * \param[in] val_block - Block to set to A(i, j). * \param[in] alpha - Scale factor. */ - template - inline void SetBlock(unsigned long block_i, unsigned long block_j, MatrixType& val_block, - typename MatrixType::Scalar alpha = 1.0) { - auto mat_ij = GetBlock(block_i, block_j); - if (!mat_ij) return; - for (auto iVar = 0ul; iVar < nVar; ++iVar) { - for (auto jVar = 0ul; jVar < nEqn; ++jVar) { - *mat_ij = (Overwrite ? ScalarType(0) : *mat_ij) + PassiveAssign(alpha * val_block(iVar, jVar)); - ++mat_ij; - } - } + template + inline void AddBlock(unsigned long block_i, unsigned long block_j, const T& val_block, OtherType alpha = 1.0) { + SetBlock(block_i, block_j, val_block, alpha); } /*! - * \brief Adds a scaled block to the sparse matrix (see SetBlock). + * \brief Subtracts the specified block to the sparse matrix (see AddBlock). * \param[in] block_i - Row index. * \param[in] block_j - Column index. - * \param[in] val_block - Block to add to A(i, j). - * \param[in] alpha - Scale factor. + * \param[in] val_block - Block to subtract to A(i, j). */ - template - inline void AddBlock(unsigned long block_i, unsigned long block_j, MatrixType& val_block, - typename MatrixType::Scalar alpha = 1.0) { - SetBlock(block_i, block_j, val_block, alpha); + template + inline void SubtractBlock(unsigned long block_i, unsigned long block_j, const T& val_block) { + AddBlock(block_i, block_j, val_block, -1); } /*! @@ -589,11 +708,31 @@ class CSysMatrix { template inline void UpdateBlocks(unsigned long iEdge, unsigned long iPoint, unsigned long jPoint, const MatrixType& block_i, const MatrixType& block_j, OtherType scale = 1) { - ScalarType *bii, *bij, *bji, *bjj; - GetBlocks(iEdge, iPoint, jPoint, bii, bij, bji, bjj); + const auto blkSz = nVar * nEqn; + auto* bii = &mat.d[iPoint * blkSz]; + auto* bjj = &mat.d[jPoint * blkSz]; unsigned long iVar, jVar, offset = 0; + if (quantized_mode) { + assert(OverwriteOffDiag); + /*--- Diagonal: full-precision accumulation. Off-diagonal: quantize on the fly. ---*/ + ScalarType bij_buf[MAXNVAR * MAXNVAR], bji_buf[MAXNVAR * MAXNVAR]; + for (iVar = 0; iVar < nVar; iVar++) + for (jVar = 0; jVar < nEqn; jVar++, ++offset) { + bii[offset] += PassiveAssign(block_i[iVar][jVar] * scale); + bjj[offset] -= PassiveAssign(block_j[iVar][jVar] * scale); + bij_buf[offset] = PassiveAssign(block_j[iVar][jVar] * scale); + bji_buf[offset] = -PassiveAssign(block_i[iVar][jVar] * scale); + } + QuantizeBlock(bij_buf, &q_scale_u[iEdge * nVar], &q_blocks_u[iEdge * blkSz]); + const auto k_l = edge_ptr_l[iEdge]; + QuantizeBlock(bji_buf, &q_scale_l[k_l * nVar], &q_blocks_l[k_l * blkSz]); + return; + } + + auto* bij = &mat.u[iEdge * blkSz]; + auto* bji = &mat.l[edge_ptr_l[iEdge] * blkSz]; for (iVar = 0; iVar < nVar; iVar++) { for (jVar = 0; jVar < nEqn; jVar++) { bii[offset] += PassiveAssign(block_i[iVar][jVar] * scale); @@ -623,9 +762,9 @@ class CSysMatrix { * \brief SIMD version, does the update for multiple edges and points. * \note Nothing is updated if the mask is 0. */ - template - FORCEINLINE void UpdateBlocks(simd::Array iEdge, simd::Array iPoint, simd::Array jPoint, - const MatTypeSIMD& block_i, const MatTypeSIMD& block_j, simd::Array mask = 1) { + template + FORCEINLINE void SetBlocks(simd::Array iEdge, simd::Array iPoint, simd::Array jPoint, + const MatTypeSIMD& block_i, const MatTypeSIMD& block_j, simd::Array mask = 1) { static_assert(MatTypeSIMD::StaticSize, "This method requires static size blocks."); static_assert(MatTypeSIMD::IsRowMajor, "Block storage is not compatible with matrix."); constexpr size_t blkSz = MatTypeSIMD::StaticSize; @@ -647,20 +786,30 @@ class CSysMatrix { for (size_t k = 0; k < N; ++k) { if (mask[k] == 0) continue; - /*--- Fetch the blocks. ---*/ auto bii = &mat.d[iPoint[k] * blkSz]; auto bjj = &mat.d[jPoint[k] * blkSz]; - auto bij = &mat.u[iEdge[k] * blkSz]; - auto bji = &mat.l[edge_ptr_l[iEdge[k]] * blkSz]; - - /*--- Update, block i was negated during transpose in the - * hope the assignments below become non-temporal stores. ---*/ - SU2_OMP_SIMD - for (size_t i = 0; i < blkSz; ++i) { - bii[i] -= blk_i[k][i]; - bjj[i] -= blk_j[k][i]; - bij[i] = blk_j[k][i]; - bji[i] = blk_i[k][i]; + + if (quantized_mode) { + SU2_OMP_SIMD + for (size_t i = 0; i < blkSz; ++i) { + bii[i] -= blk_i[k][i]; + bjj[i] -= blk_j[k][i]; + } + QuantizeBlock(blk_j[k], &q_scale_u[iEdge[k] * nVar], &q_blocks_u[iEdge[k] * blkSz]); + const auto k_l = edge_ptr_l[iEdge[k]]; + QuantizeBlock(blk_i[k], &q_scale_l[k_l * nVar], &q_blocks_l[k_l * blkSz]); + } else { + auto bij = &mat.u[iEdge[k] * blkSz]; + auto bji = &mat.l[edge_ptr_l[iEdge[k]] * blkSz]; + /*--- Update, block i was negated during transpose in the + * hope the assignments below become non-temporal stores. ---*/ + SU2_OMP_SIMD + for (size_t i = 0; i < blkSz; ++i) { + bii[i] -= blk_i[k][i]; + bjj[i] -= blk_j[k][i]; + bij[i] = blk_j[k][i]; + bji[i] = blk_i[k][i]; + } } } } @@ -679,11 +828,24 @@ class CSysMatrix { inline void SetBlocks(unsigned long iEdge, const MatrixType& block_i, const MatrixType& block_j, OtherType scale = 1) { const auto blkSz = nVar * nEqn; - ScalarType* bij = &mat.u[iEdge * blkSz]; - ScalarType* bji = &mat.l[edge_ptr_l[iEdge] * blkSz]; - unsigned long iVar, jVar, offset = 0; + if (quantized_mode) { + assert(Overwrite); + ScalarType bij_buf[MAXNVAR * MAXNVAR], bji_buf[MAXNVAR * MAXNVAR]; + for (iVar = 0; iVar < nVar; iVar++) + for (jVar = 0; jVar < nEqn; jVar++, ++offset) { + bij_buf[offset] = PassiveAssign(block_j[iVar][jVar] * scale); + bji_buf[offset] = -PassiveAssign(block_i[iVar][jVar] * scale); + } + QuantizeBlock(bij_buf, &q_scale_u[iEdge * nVar], &q_blocks_u[iEdge * blkSz]); + const auto k_l = edge_ptr_l[iEdge]; + QuantizeBlock(bji_buf, &q_scale_l[k_l * nVar], &q_blocks_l[k_l * blkSz]); + return; + } + + ScalarType* bij = &mat.u[iEdge * blkSz]; + ScalarType* bji = &mat.l[edge_ptr_l[iEdge] * blkSz]; for (iVar = 0; iVar < nVar; iVar++) { for (jVar = 0; jVar < nEqn; jVar++) { bij[offset] = (Overwrite ? ScalarType(0) : bij[offset]) + PassiveAssign(block_j[iVar][jVar] * scale); @@ -738,16 +900,20 @@ class CSysMatrix { for (size_t k = 0; k < N; ++k) { if (mask[k] == 0) continue; - /*--- Fetch the blocks. ---*/ - ScalarType* bij = &mat.u[iEdge[k] * blkSz]; - ScalarType* bji = &mat.l[edge_ptr_l[iEdge[k]] * blkSz]; - - /*--- Update, block i was negated during transpose in the - * hope the assignments below become non-temporal stores. ---*/ - SU2_OMP_SIMD - for (size_t i = 0; i < blkSz; ++i) { - bij[i] = blk_j[k][i]; - bji[i] = blk_i[k][i]; + if (quantized_mode) { + QuantizeBlock(blk_j[k], &q_scale_u[iEdge[k] * nVar], &q_blocks_u[iEdge[k] * blkSz]); + const auto k_l = edge_ptr_l[iEdge[k]]; + QuantizeBlock(blk_i[k], &q_scale_l[k_l * nVar], &q_blocks_l[k_l * blkSz]); + } else { + ScalarType* bij = &mat.u[iEdge[k] * blkSz]; + ScalarType* bji = &mat.l[edge_ptr_l[iEdge[k]] * blkSz]; + /*--- Update, block i was negated during transpose in the + * hope the assignments below become non-temporal stores. ---*/ + SU2_OMP_SIMD + for (size_t i = 0; i < blkSz; ++i) { + bij[i] = blk_j[k][i]; + bji[i] = blk_i[k][i]; + } } } } diff --git a/Common/include/linear_algebra/CSysMatrix.inl b/Common/include/linear_algebra/CSysMatrix.inl index d40d68b3a4fc..b5411016af67 100644 --- a/Common/include/linear_algebra/CSysMatrix.inl +++ b/Common/include/linear_algebra/CSysMatrix.inl @@ -137,12 +137,23 @@ FORCEINLINE void CSysMatrix::MatrixMatrixProduct(const ScalarType* m #undef __MATVECPROD_SIGNATURE__ template -FORCEINLINE void CSysMatrix::Gauss_Elimination(unsigned long block_i, ScalarType* rhs) const { +FORCEINLINE void CSysMatrix::GaussElimination(unsigned long block_i, ScalarType* rhs) const { /*--- Copy block, as the algorithm modifies the matrix ---*/ ScalarType block[MAXNVAR * MAXNVAR]; MatrixCopy(&mat.d[block_i * nVar * nVar], block); + GaussElimination(block, rhs); +} - Gauss_Elimination(block, rhs); +template +FORCEINLINE void CSysMatrix::QuantizedGaussElimination(unsigned long block_i, ScalarType* rhs) const { + ScalarType block[MAXNVAR * MAXNVAR]; + const QuantType* __restrict qs = &q_scale_d[block_i * nVar]; + const QuantType* __restrict qv = &q_blocks_d[block_i * nVar * nVar]; + for (auto r = 0ul; r < nVar; ++r) { + const float row_scale = DecodeQuantScale(qs[r]); + for (auto c = 0ul; c < nVar; ++c) block[r * nVar + c] = static_cast(qv[r * nVar + c] * row_scale); + } + GaussElimination(block, rhs); } template @@ -164,18 +175,32 @@ FORCEINLINE const ScalarType* CSysMatrix::InvertDiagonalBlockILUMatr return Uii; } +template +FORCEINLINE void CSysMatrix::QuantizedMatVecAdd(const QuantType* __restrict qs, + const QuantType* __restrict qv, + const ScalarType* __restrict vec, + ScalarType* __restrict prod) const { + for (auto r = 0ul; r < nVar; ++r) { + const float row_scale = DecodeQuantScale(qs[r]); + auto sum = ScalarType(0); + for (auto c = 0ul; c < nVar; ++c) sum += qv[r * nVar + c] * vec[c]; + prod[r] += row_scale * sum; + } +} + template FORCEINLINE void CSysMatrix::RowProduct(const CSysVector& vec, unsigned long row_i, ScalarType* prod) const { for (auto iVar = 0ul; iVar < nVar; iVar++) prod[iVar] = 0.0; - for (auto index = mat.row_ptr_l[row_i]; index < mat.row_ptr_l[row_i + 1]; index++) - MatrixVectorProductAdd(&mat.l[index * nVar * nEqn], &vec[mat.col_ind_l[index] * nEqn], prod); - + for (auto k = mat.row_ptr_l[row_i]; k < mat.row_ptr_l[row_i + 1]; k++) { + MatrixVectorProductAdd(&mat.l[k * nVar * nEqn], &vec[mat.col_ind_l[k] * nEqn], prod); + } MatrixVectorProductAdd(&mat.d[row_i * nVar * nEqn], &vec[row_i * nEqn], prod); - for (auto index = mat.row_ptr_u[row_i]; index < mat.row_ptr_u[row_i + 1]; index++) - MatrixVectorProductAdd(&mat.u[index * nVar * nEqn], &vec[mat.col_ind_u[index] * nEqn], prod); + for (auto k = mat.row_ptr_u[row_i]; k < mat.row_ptr_u[row_i + 1]; k++) { + MatrixVectorProductAdd(&mat.u[k * nVar * nEqn], &vec[mat.col_ind_u[k] * nEqn], prod); + } } template @@ -185,9 +210,10 @@ FORCEINLINE void CSysMatrix::UpperProduct(const CSysVector= nPointDomain) + + if (col_j < col_ub || col_j >= nPointDomain) { MatrixVectorProductAdd(&mat.u[index * nVar * nEqn], &vec[col_j * nEqn], prod); + } } } @@ -198,7 +224,9 @@ FORCEINLINE void CSysMatrix::LowerProduct(const CSysVector= col_lb) MatrixVectorProductAdd(&mat.l[index * nVar * nEqn], &vec[col_j * nEqn], prod); + if (col_j >= col_lb) { + MatrixVectorProductAdd(&mat.l[index * nVar * nEqn], &vec[col_j * nEqn], prod); + } } } @@ -207,3 +235,51 @@ FORCEINLINE void CSysMatrix::DiagonalProduct(const CSysVector +FORCEINLINE void CSysMatrix::QuantizedRowProduct(const CSysVector& vec, unsigned long row_i, + ScalarType* prod) const { + for (auto iVar = 0ul; iVar < nVar; iVar++) prod[iVar] = 0.0; + + for (auto k = mat.row_ptr_l[row_i]; k < mat.row_ptr_l[row_i + 1]; k++) { + QuantizedMatVecAdd(&q_scale_l[k * nVar], &q_blocks_l[k * nVar * nEqn], &vec[mat.col_ind_l[k] * nEqn], prod); + } + QuantizedMatVecAdd(&q_scale_d[row_i * nVar], &q_blocks_d[row_i * nVar * nEqn], &vec[row_i * nEqn], prod); + + for (auto k = mat.row_ptr_u[row_i]; k < mat.row_ptr_u[row_i + 1]; k++) { + QuantizedMatVecAdd(&q_scale_u[k * nVar], &q_blocks_u[k * nVar * nEqn], &vec[mat.col_ind_u[k] * nEqn], prod); + } +} + +template +FORCEINLINE void CSysMatrix::QuantizedUpperProduct(const CSysVector& vec, unsigned long row_i, + unsigned long col_ub, ScalarType* prod) const { + for (auto iVar = 0ul; iVar < nVar; iVar++) prod[iVar] = 0.0; + + for (auto index = mat.row_ptr_u[row_i]; index < mat.row_ptr_u[row_i + 1]; index++) { + auto col_j = mat.col_ind_u[index]; + if (col_j < col_ub || col_j >= nPointDomain) { + QuantizedMatVecAdd(&q_scale_u[index * nVar], &q_blocks_u[index * nVar * nEqn], &vec[col_j * nEqn], prod); + } + } +} + +template +FORCEINLINE void CSysMatrix::QuantizedLowerProduct(const CSysVector& vec, unsigned long row_i, + unsigned long col_lb, ScalarType* prod) const { + for (auto iVar = 0ul; iVar < nVar; iVar++) prod[iVar] = 0.0; + + for (auto index = mat.row_ptr_l[row_i]; index < mat.row_ptr_l[row_i + 1]; index++) { + auto col_j = mat.col_ind_l[index]; + if (col_j >= col_lb) { + QuantizedMatVecAdd(&q_scale_l[index * nVar], &q_blocks_l[index * nVar * nEqn], &vec[col_j * nEqn], prod); + } + } +} + +template +FORCEINLINE void CSysMatrix::QuantizedDiagonalProduct(const CSysVector& vec, + unsigned long row_i, ScalarType* prod) const { + for (auto iVar = 0ul; iVar < nVar; iVar++) prod[iVar] = 0.0; + QuantizedMatVecAdd(&q_scale_d[row_i * nVar], &q_blocks_d[row_i * nVar * nEqn], &vec[row_i * nEqn], prod); +} diff --git a/Common/include/option_structure.hpp b/Common/include/option_structure.hpp index 1ba1bab61fe7..2bc09b47adad 100644 --- a/Common/include/option_structure.hpp +++ b/Common/include/option_structure.hpp @@ -2527,6 +2527,7 @@ enum ENUM_LINEAR_SOLVER_PREC { LU_SGS, /*!< \brief LU SGS preconditioner. */ LINELET, /*!< \brief Line implicit preconditioner. */ ILU, /*!< \brief ILU(k) preconditioner. */ + Q_LU_SGS, /*!< \brief LU-SGS with quantized (int8) off-diagonal storage; L/U are never allocated as ScalarType. */ PASTIX_ILU=10, /*!< \brief PaStiX ILU(k) preconditioner. */ PASTIX_LU_P, /*!< \brief PaStiX LU as preconditioner. */ PASTIX_LDLT_P, /*!< \brief PaStiX LDLT as preconditioner. */ @@ -2536,6 +2537,7 @@ static const MapType Linear_Solver_Prec_Ma MakePair("LU_SGS", LU_SGS) MakePair("LINELET", LINELET) MakePair("ILU", ILU) + MakePair("Q_LU_SGS", Q_LU_SGS) MakePair("PASTIX_ILU", PASTIX_ILU) MakePair("PASTIX_LU", PASTIX_LU_P) MakePair("PASTIX_LDLT", PASTIX_LDLT_P) diff --git a/Common/include/toolboxes/graph_toolbox.hpp b/Common/include/toolboxes/graph_toolbox.hpp index 170b973ea1f8..f1c36969ada7 100644 --- a/Common/include/toolboxes/graph_toolbox.hpp +++ b/Common/include/toolboxes/graph_toolbox.hpp @@ -27,6 +27,7 @@ #pragma once +#include "../code_config.hpp" #include "../containers/C2DContainer.hpp" #include "../parallelization/omp_structure.hpp" @@ -333,9 +334,9 @@ class CCompressedSparsePattern { template using CEdgeToNonZeroMap = C2DContainer; -using CCompressedSparsePatternUL = CCompressedSparsePattern; -using CCompressedSparsePatternL = CCompressedSparsePattern; -using CEdgeToNonZeroMapUL = CEdgeToNonZeroMap; +using CCompressedSparsePatternUL = CCompressedSparsePattern; +using CCompressedSparsePatternL = CCompressedSparsePattern; +using CEdgeToNonZeroMapUL = CEdgeToNonZeroMap; /*! * \brief Build a sparse pattern from geometry information, of type FVM or FEM, @@ -661,7 +662,7 @@ T colorSparsePattern(const T& pattern, size_t groupSize = 1, bool includeOuterId /*! * \brief A way to represent one grid color that allows range-for syntax. */ -template +template struct GridColor { static_assert(std::is_integral::value); @@ -679,7 +680,7 @@ struct GridColor { * \brief A way to represent natural coloring {0,1,2,...,size-1} with zero * overhead (behaves like looping with an integer index, after optimization...). */ -template +template struct DummyGridColor { static_assert(std::is_integral::value); diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index 2c0dce07e683..a9ca92ab1454 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -7458,10 +7458,11 @@ void CConfig::SetOutput(SU2_COMPONENT val_software, unsigned short val_izone) { } } switch (Kind_Linear_Solver_Prec) { - case ILU: cout << "Using a ILU("<< Linear_Solver_ILU_n <<") preconditioning."<< endl; break; - case LINELET: cout << "Using a linelet preconditioning."<< endl; break; - case LU_SGS: cout << "Using a LU-SGS preconditioning."<< endl; break; - case JACOBI: cout << "Using a Jacobi preconditioning."<< endl; break; + case ILU: cout << "Using ILU("<< Linear_Solver_ILU_n <<") preconditioning."<< endl; break; + case LINELET: cout << "Using linelet preconditioning."<< endl; break; + case LU_SGS: cout << "Using LU-SGS preconditioning."<< endl; break; + case Q_LU_SGS: cout << "Using LU-SGS preconditioning with matrix quantization."<< endl; break; + case JACOBI: cout << "Using Jacobi preconditioning."<< endl; break; } break; case SMOOTHER: @@ -7469,6 +7470,7 @@ void CConfig::SetOutput(SU2_COMPONENT val_software, unsigned short val_izone) { case ILU: cout << "A ILU(" << Linear_Solver_ILU_n << ")"; break; case LINELET: cout << "A Linelet"; break; case LU_SGS: cout << "A LU-SGS"; break; + case Q_LU_SGS: cout << "A quantized LU-SGS"; break; case JACOBI: cout << "A Jacobi"; break; } cout << " method is used for smoothing the linear system." << endl; diff --git a/Common/src/geometry/CGeometry.cpp b/Common/src/geometry/CGeometry.cpp index f6f423761e6c..45153456086a 100644 --- a/Common/src/geometry/CGeometry.cpp +++ b/Common/src/geometry/CGeometry.cpp @@ -4128,7 +4128,7 @@ const CGeometry::LDUSparsePattern& CGeometry::GetSparsePattern(ConnectivityType auto& grp = fillLvl == 0 ? (fvm ? finiteVolumePatternFill0 : finiteElementPatternFill0) : (fvm ? finiteVolumePatternFillN : finiteElementPatternFillN); if (grp.empty()) { - grp.csr = buildCSRPattern(*this, type, fillLvl); + grp.csr = buildCSRPattern(*this, type, static_cast(fillLvl)); grp.csr.buildDiagPtr(); grp.l = buildLowerPattern(grp.csr); grp.u = buildUpperPattern(grp.csr); @@ -4136,7 +4136,7 @@ const CGeometry::LDUSparsePattern& CGeometry::GetSparsePattern(ConnectivityType return grp; } -const su2vector& CGeometry::GetLToUTransposeSparsePatternMap(ConnectivityType type) { +const su2vector& CGeometry::GetLToUTransposeSparsePatternMap(ConnectivityType type) { bool fvm = (type == ConnectivityType::FiniteVolume); auto& l_to_u = fvm ? finiteVolumeLToUTranspMap : finiteElementLToUTranspMap; if (l_to_u.empty()) { @@ -4147,7 +4147,7 @@ const su2vector& CGeometry::GetLToUTransposeSparsePatternMap(Conn return l_to_u; } -const su2vector& CGeometry::GetUToLTransposeSparsePatternMap(ConnectivityType type) { +const su2vector& CGeometry::GetUToLTransposeSparsePatternMap(ConnectivityType type) { bool fvm = (type == ConnectivityType::FiniteVolume); auto& u_to_l = fvm ? finiteVolumeUToLTranspMap : finiteElementUToLTranspMap; if (u_to_l.empty()) { diff --git a/Common/src/geometry/meshreader/CSU2BinaryMeshReaderBase.cpp b/Common/src/geometry/meshreader/CSU2BinaryMeshReaderBase.cpp index 5831efe0c544..ce7d03cb3e3c 100644 --- a/Common/src/geometry/meshreader/CSU2BinaryMeshReaderBase.cpp +++ b/Common/src/geometry/meshreader/CSU2BinaryMeshReaderBase.cpp @@ -99,7 +99,7 @@ void CSU2BinaryMeshReaderBase::ReadMetadata(CConfig* config) { } /*--- Read the meta data from the current position. ---*/ - ReadMetadataZone(); + ReadMetadataZone(true); /*--- Close the grid file again. ---*/ fclose(mesh_file); @@ -433,8 +433,10 @@ void CSU2BinaryMeshReaderBase::FastForwardToMyZone() { ASCII reader does. ---*/ if (nZones == 1 || !config->GetMultizone_Mesh()) return; - /*--- Loop over the lower numbered zones and read their meta data. ---*/ - for (int zone = 0; zone < myZone; ++zone) ReadMetadataZone(); + /*--- Loop over the lower numbered zones, skipping their metadata (without + overwriting the current zone's, which is read separately afterwards + by ReadMetadata()). ---*/ + for (int zone = 0; zone < myZone; ++zone) ReadMetadataZone(false); } uint64_t CSU2BinaryMeshReaderBase::ReadBinaryNEntities() { @@ -453,16 +455,20 @@ uint64_t CSU2BinaryMeshReaderBase::ReadBinaryNEntities() { return nEntities; } -void CSU2BinaryMeshReaderBase::ReadMetadataZone() { - /*--- Skip the zone ID and read the number of dimensions. ---*/ +void CSU2BinaryMeshReaderBase::ReadMetadataZone(bool storeMetadata) { + /*--- Skip the zone ID and read the number of dimensions. storeMetadata is + false when this call is only being used to skip over a lower-numbered + zone's data (from FastForwardToMyZone): the file-position arithmetic + below still has to run, but its results must not overwrite the + members already holding the current (target) zone's metadata. ---*/ int nDim; FileSeek64(mesh_file, sizeof(int), SEEK_CUR); ReadBinaryData(&nDim, 1); - dimension = static_cast(nDim); + if (storeMetadata) dimension = static_cast(nDim); /*--- Read the number of elements. ---*/ const auto nElem = ReadBinaryNEntities(); - numberOfGlobalElements = static_cast(nElem); + if (storeMetadata) numberOfGlobalElements = static_cast(nElem); /*--- Jump to the end of the offset section, read the size of the connectivity and jump over it. ---*/ @@ -472,13 +478,13 @@ void CSU2BinaryMeshReaderBase::ReadMetadataZone() { /*--- Read the number of points and jump over the coordinate section. ---*/ const auto nPoints = ReadBinaryNEntities(); - numberOfGlobalPoints = static_cast(nPoints); + if (storeMetadata) numberOfGlobalPoints = static_cast(nPoints); FileSeek64(mesh_file, nPoints * (nDim * sizeof(double) + size_conn_type), SEEK_CUR); /*--- Read the number of markers and loop over them. ---*/ int nMark; ReadBinaryData(&nMark, 1); - numberOfMarkers = static_cast(nMark); + if (storeMetadata) numberOfMarkers = static_cast(nMark); for (int mark = 0; mark < nMark; ++mark) { /*--- Jump over the name of the marker and read diff --git a/Common/src/linear_algebra/CSysMatrix.cpp b/Common/src/linear_algebra/CSysMatrix.cpp index ebe98d4311cc..fa47816f60a0 100644 --- a/Common/src/linear_algebra/CSysMatrix.cpp +++ b/Common/src/linear_algebra/CSysMatrix.cpp @@ -31,6 +31,8 @@ #include "../../include/toolboxes/allocation_toolbox.hpp" #include +#include +#include #include namespace { @@ -83,6 +85,13 @@ CSysMatrix::CSysMatrix() : rank(SU2_MPI::GetRank()), size(SU2_MPI::G ilu.d = nullptr; ilu.u = nullptr; + q_scale_l = nullptr; + q_blocks_l = nullptr; + q_scale_u = nullptr; + q_blocks_u = nullptr; + q_scale_d = nullptr; + q_blocks_d = nullptr; + invM = nullptr; #ifdef USE_MKL @@ -105,6 +114,12 @@ CSysMatrix::~CSysMatrix() { MemoryAllocation::aligned_free(mat.l); MemoryAllocation::aligned_free(mat.u); MemoryAllocation::aligned_free(invM); + MemoryAllocation::aligned_free(q_scale_l); + MemoryAllocation::aligned_free(q_blocks_l); + MemoryAllocation::aligned_free(q_scale_u); + MemoryAllocation::aligned_free(q_blocks_u); + MemoryAllocation::aligned_free(q_scale_d); + MemoryAllocation::aligned_free(q_blocks_d); if (useCuda) { GPUMemoryAllocation::gpu_free(gpu.d); @@ -127,7 +142,7 @@ CSysMatrix::~CSysMatrix() { template void CSysMatrix::Initialize(unsigned long npoint, unsigned long npointdomain, unsigned short nvar, unsigned short neqn, bool EdgeConnect, CGeometry* geometry, - const CConfig* config, bool needTranspPtr, bool grad_mode) { + const CConfig* config, bool needTranspPtr, bool grad_mode, bool allow_quant) { SU2_ZONE_SCOPED assert(omp_get_thread_num() == 0 && "Only the master thread is allowed to initialize the matrix."); @@ -161,8 +176,16 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi prec = config->GetKind_Grad_Linear_Solver_Prec(); } + useCuda = config->GetCUDA(); + const bool ilu_needed = (prec == ILU); const bool diag_needed = (prec == JACOBI) || (prec == LINELET); +#ifndef CODI_REVERSE_TYPE + const bool q_lus_needed = allow_quant && !useCuda && (prec == Q_LU_SGS); +#else + /*--- No quantization in adjoint mode for now because TransposeInPlace would get complicated. ---*/ + const bool q_lus_needed = false; +#endif /*--- Basic dimensions. ---*/ nVar = nvar; @@ -175,8 +198,6 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi ptr = MemoryAllocation::aligned_alloc(64, num * sizeof(ScalarType)); }; - useCuda = config->GetCUDA(); - /*--- L/D/U index structures and value arrays. ---*/ { const auto& pat = geometry->GetSparsePattern(type, 0); @@ -188,15 +209,33 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi mat.nnz_u = pat.u.getNumNonZeros(); } allocAndInit(mat.d, nPoint * nVar * nEqn); - allocAndInit(mat.l, mat.nnz_l * nVar * nEqn); - allocAndInit(mat.u, mat.nnz_u * nVar * nEqn); + + if (q_lus_needed) { + /*--- Q_LU_SGS: no full-precision L/U; off-diagonal blocks live in quantized storage. + * L/U are quantized on-the-fly during assembly; diagonal is quantized in Build step. ---*/ +#ifndef CODI_REVERSE_TYPE + quantized_mode = true; +#endif + auto allocQ = [](QuantType*& ptr, unsigned long n) { + ptr = MemoryAllocation::aligned_alloc(64, n * sizeof(QuantType)); + }; + allocQ(q_scale_l, mat.nnz_l * nVar); + allocQ(q_blocks_l, mat.nnz_l * nVar * nEqn); + allocQ(q_scale_u, mat.nnz_u * nVar); + allocQ(q_blocks_u, mat.nnz_u * nVar * nEqn); + allocQ(q_scale_d, nPoint * nVar); + allocQ(q_blocks_d, nPoint * nVar * nEqn); + } else { + allocAndInit(mat.l, mat.nnz_l * nVar * nEqn); + allocAndInit(mat.u, mat.nnz_u * nVar * nEqn); + } if (useCuda) { auto GPUAllocAndInit = [](ScalarType*& ptr, unsigned long num) { ptr = GPUMemoryAllocation::gpu_alloc(num * sizeof(ScalarType)); }; - auto GPUAllocAndCopy = [](const unsigned long*& ptr, const unsigned long* src_ptr, unsigned long num) { - ptr = GPUMemoryAllocation::gpu_alloc_cpy(src_ptr, num * sizeof(unsigned long)); + auto GPUAllocAndCopy = [](const su2uint*& ptr, const su2uint* src_ptr, unsigned long num) { + ptr = GPUMemoryAllocation::gpu_alloc_cpy(src_ptr, num * sizeof(su2uint)); }; GPUAllocAndInit(gpu.d, nPoint * nVar * nEqn); GPUAllocAndInit(gpu.l, mat.nnz_l * nVar * nEqn); @@ -249,7 +288,7 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi /*--- Set suitable chunk sizes for light static for loops, and heavy dynamic ones, such that threads are approximately evenly loaded. ---*/ - omp_light_size = computeStaticChunkSize((mat.nnz_l + mat.nnz_u + nPoint) * nVar * nEqn, num_threads, OMP_MAX_SIZE_L); + omp_light_size = computeStaticChunkSize(nPoint * nVar * nEqn, num_threads, OMP_MAX_SIZE_L); omp_heavy_size = computeStaticChunkSize(nPointDomain, num_threads, OMP_MAX_SIZE_H); omp_num_parts = config->GetLinear_Solver_Prec_Threads(); @@ -538,21 +577,47 @@ void CSysMatrixComms::Complete(CSysVector& x, CGeometry* geometry, const CCon #endif } +template +void CSysMatrix::QuantizeBlock(const ScalarType* blk, QuantType* qs, QuantType* qv) const { + EncodeQuantBlock([&](unsigned long r, unsigned long c) { return blk[r * nVar + c]; }, qs, qv, nVar); +} + +template +void CSysMatrix::QuantizeDiagonalBlocks() { + SU2_ZONE_SCOPED + + if (quantized_mode) { + /*--- Q_LU_SGS: L/U were quantized during assembly; only the diagonal needs quantization now. ---*/ + SU2_OMP_FOR_DYN(omp_heavy_size) + for (auto i = 0ul; i < nPointDomain; ++i) + QuantizeBlock(&mat.d[i * nVar * nVar], &q_scale_d[i * nVar], &q_blocks_d[i * nVar * nVar]); + END_SU2_OMP_FOR + } +} + template void CSysMatrix::SetValZero() { SU2_ZONE_SCOPED const auto nThreads = static_cast(omp_get_num_threads()); const auto iThread = static_cast(omp_get_thread_num()); - auto zeroChunk = [&](ScalarType* arr, unsigned long n) { + + auto zeroChunk = [&](auto* arr, unsigned long n) { if (n == 0) return; const auto chunk = roundUpDiv(n, nThreads); const auto begin = min(chunk * iThread, n); - const auto mySize = min(chunk, n - begin) * sizeof(ScalarType); + const auto mySize = min(chunk, n - begin) * sizeof(std::remove_pointer_t); if (mySize) memset(&arr[begin], 0, mySize); }; zeroChunk(mat.d, nPoint * nVar * nEqn); - zeroChunk(mat.l, mat.nnz_l * nVar * nEqn); - zeroChunk(mat.u, mat.nnz_u * nVar * nEqn); + if (!quantized_mode) { + zeroChunk(mat.l, mat.nnz_l * nVar * nEqn); + zeroChunk(mat.u, mat.nnz_u * nVar * nEqn); + } else { + zeroChunk(q_scale_l, mat.nnz_l * nVar); + zeroChunk(q_scale_u, mat.nnz_l * nVar); + zeroChunk(q_blocks_l, mat.nnz_l * nVar * nEqn); + zeroChunk(q_blocks_u, mat.nnz_u * nVar * nEqn); + } SU2_OMP_BARRIER } @@ -565,7 +630,7 @@ void CSysMatrix::SetValDiagonalZero() { } template -void CSysMatrix::Gauss_Elimination(ScalarType* matrix, ScalarType* vec) const { +void CSysMatrix::GaussElimination(ScalarType* matrix, ScalarType* vec) const { #ifdef USE_MKL_LAPACK // With MKL_DIRECT_CALL enabled, this is significantly faster than native code on Intel Architectures. lapack_int ipiv[MAXNVAR]; @@ -585,7 +650,7 @@ void CSysMatrix::Gauss_Elimination(ScalarType* matrix, ScalarType* v for (auto iVar = 1ul; iVar < nVar; iVar++) { for (auto jVar = 0ul; jVar < iVar; jVar++) { /*--- Regularize pivot if too small to prevent divide-by-zero ---*/ - RegularizePivot(A(jVar, jVar), jVar, jVar, "DEBUG Gauss_Elimination"); + RegularizePivot(A(jVar, jVar), jVar, jVar, "DEBUG GaussElimination"); ScalarType weight = A(iVar, jVar) / A(jVar, jVar); @@ -600,7 +665,7 @@ void CSysMatrix::Gauss_Elimination(ScalarType* matrix, ScalarType* v for (auto jVar = iVar + 1; jVar < nVar; jVar++) vec[iVar] -= A(iVar, jVar) * vec[jVar]; /*--- Regularize diagonal if too small ---*/ - RegularizePivot(A(iVar, iVar), iVar, iVar, "DEBUG Gauss_Elimination backsubst"); + RegularizePivot(A(iVar, iVar), iVar, iVar, "DEBUG GaussElimination backsubst"); vec[iVar] /= A(iVar, iVar); } @@ -611,7 +676,7 @@ void CSysMatrix::Gauss_Elimination(ScalarType* matrix, ScalarType* v template void CSysMatrix::MatrixInverse(ScalarType* matrix, ScalarType* inverse) const { /*--- This is a generalization of Gaussian elimination for multiple rhs' (the basis vectors). - We could call "Gauss_Elimination" multiple times or fully generalize it for multiple rhs, + We could call "GaussElimination" multiple times or fully generalize it for multiple rhs, the performance of both routines would suffer in both cases without the use of exotic templating. And so it feels reasonable to have some duplication here. ---*/ @@ -673,13 +738,27 @@ void CSysMatrix::MatrixInverse(ScalarType* matrix, ScalarType* inver template void CSysMatrix::DeleteValsRowi(unsigned long block_i, unsigned long row) { SU2_ZONE_SCOPED - for (auto k = mat.row_ptr_l[block_i]; k < mat.row_ptr_l[block_i + 1]; ++k) - for (auto iVar = 0u; iVar < nVar; iVar++) mat.l[k * nVar * nEqn + row * nEqn + iVar] = 0.0; - auto* d = &mat.d[block_i * nVar * nEqn]; - for (auto iVar = 0u; iVar < nVar; iVar++) d[row * nEqn + iVar] = 0.0; + const auto blkSz = nVar * nEqn; + + auto* d = &mat.d[block_i * blkSz]; + for (auto iVar = 0u; iVar < nEqn; iVar++) d[row * nEqn + iVar] = 0.0; d[row * nEqn + row] = 1.0; - for (auto k = mat.row_ptr_u[block_i]; k < mat.row_ptr_u[block_i + 1]; ++k) - for (auto iVar = 0u; iVar < nVar; iVar++) mat.u[k * nVar * nEqn + row * nEqn + iVar] = 0.0; + + if (quantized_mode) { + for (auto k = mat.row_ptr_l[block_i]; k < mat.row_ptr_l[block_i + 1]; ++k) { + for (auto iVar = 0u; iVar < nEqn; iVar++) q_blocks_l[k * blkSz + row * nEqn + iVar] = 0; + } + for (auto k = mat.row_ptr_u[block_i]; k < mat.row_ptr_u[block_i + 1]; ++k) { + for (auto iVar = 0u; iVar < nEqn; iVar++) q_blocks_u[k * blkSz + row * nEqn + iVar] = 0; + } + } else { + for (auto k = mat.row_ptr_l[block_i]; k < mat.row_ptr_l[block_i + 1]; ++k) { + for (auto iVar = 0u; iVar < nEqn; iVar++) mat.l[k * blkSz + row * nEqn + iVar] = 0; + } + for (auto k = mat.row_ptr_u[block_i]; k < mat.row_ptr_u[block_i + 1]; ++k) { + for (auto iVar = 0u; iVar < nEqn; iVar++) mat.u[k * blkSz + row * nEqn + iVar] = 0; + } + } } template @@ -702,11 +781,19 @@ void CSysMatrix::MatrixVectorProduct(const CSysVector& v SU2_OMP_BARRIER - SU2_OMP_FOR_DYN(omp_heavy_size) - for (auto row_i = 0ul; row_i < nPointDomain; row_i++) { - RowProduct(vec, row_i, &prod[row_i * nVar]); + if (quantized_mode) { + SU2_OMP_FOR_DYN(omp_heavy_size) + for (auto row_i = 0ul; row_i < nPointDomain; row_i++) { + QuantizedRowProduct(vec, row_i, &prod[row_i * nVar]); + } + END_SU2_OMP_FOR + } else { + SU2_OMP_FOR_DYN(omp_heavy_size) + for (auto row_i = 0ul; row_i < nPointDomain; row_i++) { + RowProduct(vec, row_i, &prod[row_i * nVar]); + } + END_SU2_OMP_FOR } - END_SU2_OMP_FOR /*--- MPI Parallelization. ---*/ @@ -753,7 +840,7 @@ void CSysMatrix::BuildILUPreconditioner() { if (ilu_fill_in == 0) { /*--- ILU0: Same sparse pattern, copy L and U blocks directly. ---*/ - auto copy = [&](const unsigned long* row_ptr, const ScalarType* mat, ScalarType* ilu) { + auto copy = [&](const su2uint* row_ptr, const ScalarType* mat, ScalarType* ilu) { const unsigned long begin = row_ptr[iPoint] * blockSize; const unsigned long end = row_ptr[iPoint + 1] * blockSize; SU2_OMP_SIMD @@ -764,9 +851,8 @@ void CSysMatrix::BuildILUPreconditioner() { return; } /*--- ILUn: Merge-scan L and U via shared lambda. ---*/ - auto scatterPart = [&](const unsigned long* mat_row_ptr, const unsigned long* mat_col_ind, - const ScalarType* mat_vals, const unsigned long* ilu_row_ptr, - const unsigned long* ilu_col_ind, ScalarType* ilu_vals) { + auto scatterPart = [&](const su2uint* mat_row_ptr, const su2uint* mat_col_ind, const ScalarType* mat_vals, + const su2uint* ilu_row_ptr, const su2uint* ilu_col_ind, ScalarType* ilu_vals) { auto km = mat_row_ptr[iPoint], km_end = mat_row_ptr[iPoint + 1]; for (auto k = ilu_row_ptr[iPoint]; k < ilu_row_ptr[iPoint + 1]; ++k) { const auto jPoint = ilu_col_ind[k]; @@ -976,11 +1062,20 @@ void CSysMatrix::ComputeLU_SGSPreconditioner(const CSysVector::ComputeLU_SGSPreconditioner(const CSysVector begin;) { - iPoint--; // because of unsigned type - auto idx = iPoint * nVar; - DiagonalProduct(prod, iPoint, dia_prod); // Compute D.x* - UpperProduct(prod, iPoint, row_end, up_prod); // Compute U.x_(n+1) - VectorSubtraction(dia_prod, up_prod, &prod[idx]); // Compute y = D.x*-U.x_(n+1) - Gauss_Elimination(iPoint, &prod[idx]); // Solve D.x* = y + if (quantized_mode) { + for (auto iPoint = row_end; iPoint > begin;) { + iPoint--; + auto idx = iPoint * nVar; + QuantizedDiagonalProduct(prod, iPoint, dia_prod); + QuantizedUpperProduct(prod, iPoint, row_end, up_prod); + VectorSubtraction(dia_prod, up_prod, &prod[idx]); + QuantizedGaussElimination(iPoint, &prod[idx]); + } + } else { + for (auto iPoint = row_end; iPoint > begin;) { + iPoint--; // because of unsigned type + auto idx = iPoint * nVar; + DiagonalProduct(prod, iPoint, dia_prod); // Compute D.x* + UpperProduct(prod, iPoint, row_end, up_prod); // Compute U.x_(n+1) + VectorSubtraction(dia_prod, up_prod, &prod[idx]); // Compute y = D.x*-U.x_(n+1) + GaussElimination(iPoint, &prod[idx]); // Solve D.x* = y + } } } END_SU2_OMP_FOR @@ -1129,7 +1235,7 @@ void CSysMatrix::ComputeLineletPreconditioner(const CSysVector 0; --iElem) { @@ -1244,19 +1350,32 @@ void CSysMatrix::EnforceZeroProjection(unsigned long node_i, const O template void CSysMatrix::SetDiagonalAsColumnSum() { SU2_ZONE_SCOPED + const auto blkSz = nVar * nEqn; SU2_OMP_FOR_DYN(omp_heavy_size) for (auto iPoint = 0ul; iPoint < nPoint; ++iPoint) { - auto* d_i = &mat.d[iPoint * nVar * nEqn]; - for (auto k = 0ul; k < nVar * nEqn; ++k) d_i[k] = 0.0; - - /*--- For each L entry (iPoint, j): subtract its U-transpose (j, iPoint). ---*/ - for (auto k_l = mat.row_ptr_l[iPoint]; k_l < mat.row_ptr_l[iPoint + 1]; ++k_l) - MatrixSubtraction(d_i, &mat.u[l_to_u_transp[k_l] * nVar * nEqn], d_i); - - /*--- For each U entry (iPoint, j): subtract its L-transpose (j, iPoint). ---*/ - for (auto k_u = mat.row_ptr_u[iPoint]; k_u < mat.row_ptr_u[iPoint + 1]; ++k_u) - MatrixSubtraction(d_i, &mat.l[u_to_l_transp[k_u] * nVar * nEqn], d_i); + auto* d_i = &mat.d[iPoint * blkSz]; + for (auto k = 0ul; k < blkSz; ++k) d_i[k] = 0.0; + + if (!quantized_mode) { + /*--- For each L entry (iPoint, j): subtract its U-transpose (j, iPoint). ---*/ + for (auto k_l = mat.row_ptr_l[iPoint]; k_l < mat.row_ptr_l[iPoint + 1]; ++k_l) + MatrixSubtraction(d_i, &mat.u[l_to_u_transp[k_l] * blkSz], d_i); + + /*--- For each U entry (iPoint, j): subtract its L-transpose (j, iPoint). ---*/ + for (auto k_u = mat.row_ptr_u[iPoint]; k_u < mat.row_ptr_u[iPoint + 1]; ++k_u) + MatrixSubtraction(d_i, &mat.l[u_to_l_transp[k_u] * blkSz], d_i); + } else { + auto subtractTransp = [&](su2uint k_transp, const QuantType* qs, const QuantType* qv) { + const CBlockView view{nullptr, &qs[k_transp * nVar], &qv[k_transp * blkSz], nVar}; + for (auto i = 0ul; i < nVar; ++i) + for (auto j = 0ul; j < nEqn; ++j) d_i[i * nEqn + j] -= view(i, j); + }; + for (auto k_l = mat.row_ptr_l[iPoint]; k_l < mat.row_ptr_l[iPoint + 1]; ++k_l) + subtractTransp(l_to_u_transp[k_l], q_scale_u, q_blocks_u); + for (auto k_u = mat.row_ptr_u[iPoint]; k_u < mat.row_ptr_u[iPoint + 1]; ++k_u) + subtractTransp(u_to_l_transp[k_u], q_scale_l, q_blocks_l); + } } END_SU2_OMP_FOR } diff --git a/Common/src/linear_algebra/CSysSolve.cpp b/Common/src/linear_algebra/CSysSolve.cpp index aae5d9ce7075..a50ed0596257 100644 --- a/Common/src/linear_algebra/CSysSolve.cpp +++ b/Common/src/linear_algebra/CSysSolve.cpp @@ -1569,7 +1569,8 @@ unsigned long CSysSolve::Solve(CSysMatrix& Jacobian, con if (RequiresTranspose) Jacobian.BuildJacobiPreconditioner(); break; case LU_SGS: - /*--- Nothing to build. ---*/ + case Q_LU_SGS: + /*--- Nothing to build (transpose path not supported for Q_LU_SGS, see CSysMatrix::Initialize). ---*/ break; case PASTIX_ILU: case PASTIX_LU_P: diff --git a/SU2_CFD/include/numerics_simd/util.hpp b/SU2_CFD/include/numerics_simd/util.hpp index e891155c0909..79594268be8f 100644 --- a/SU2_CFD/include/numerics_simd/util.hpp +++ b/SU2_CFD/include/numerics_simd/util.hpp @@ -248,7 +248,7 @@ FORCEINLINE void updateLinearSystem(Int iEdge, vector.UpdateBlocks(iPoint, jPoint, flux, updateMask); if(implicit) { auto wasActive = AD::BeginPassive(); - matrix.UpdateBlocks(iEdge, iPoint, jPoint, jac_i, jac_j, updateMask); + matrix.SetBlocks(iEdge, iPoint, jPoint, jac_i, jac_j, updateMask); AD::EndPassive(wasActive); } } diff --git a/SU2_CFD/include/solvers/CFVMFlowSolverBase.hpp b/SU2_CFD/include/solvers/CFVMFlowSolverBase.hpp index 93529597cdcd..b2dd6441318d 100644 --- a/SU2_CFD/include/solvers/CFVMFlowSolverBase.hpp +++ b/SU2_CFD/include/solvers/CFVMFlowSolverBase.hpp @@ -327,20 +327,58 @@ class CFVMFlowSolverBase : public CSolver { /*! * \brief Compute the viscous contribution for a particular edge. - * \note The convective residual methods include a call to this for each edge, - * this allows convective and viscous loops to be "fused". + * \note The convective residual methods include a call to this for each edge, this allows convective and + * viscous loops to be "fused". Only the residual is applied here, the Jacobians are returned so that + * the caller can update the system matrix in a single operation together with the convective part + * (a requirement of quantized matrix storage). * \param[in] iEdge - Edge for which the flux and Jacobians are to be computed. * \param[in] geometry - Geometrical definition of the problem. * \param[in] solver_container - Container vector with all the solutions. * \param[in] numerics - Description of the numerical method. * \param[in] config - Definition of the particular problem. + * \return The viscous Jacobians (null for inviscid solvers). */ - inline virtual void Viscous_Residual(unsigned long iEdge, CGeometry *geometry, CSolver **solver_container, - CNumerics *numerics, CConfig *config) { } - void Viscous_Residual_impl(unsigned long iEdge, CGeometry *geometry, CSolver **solver_container, - CNumerics *numerics, CConfig *config); + inline virtual CNumerics::ResidualType<> Viscous_Residual(unsigned long iEdge, CGeometry *geometry, + CSolver **solver_container, CNumerics *numerics, + CConfig *config) { + return CNumerics::ResidualType<>(nullptr, nullptr, nullptr); + } + CNumerics::ResidualType<> Viscous_Residual_impl(unsigned long iEdge, CGeometry *geometry, + CSolver **solver_container, CNumerics *numerics, CConfig *config); using CSolver::Viscous_Residual; /*--- Silence warning ---*/ + /*! + * \brief Update the Jacobian for one edge with the fused convective and viscous contributions. + * \note Both contributions must be applied at once because in quantized mode the + * off-diagonal blocks of the matrix can only be overwritten, not accumulated. + * \param[in] iEdge - Edge index for the off-diagonal blocks. + * \param[in] iPoint, jPoint - Points connected by the edge (diagonal blocks). + * \param[in] conv - Convective residual/Jacobians (added to i, subtracted from j). + * \param[in] visc - Viscous residual/Jacobians (subtracted from i, added to j), may hold null Jacobians. + */ + inline void UpdateJacobian(unsigned long iEdge, unsigned long iPoint, unsigned long jPoint, + const CNumerics::ResidualType<>& conv, const CNumerics::ResidualType<>& visc) { + /*--- Lazy element-wise difference, presented with the [i][j] access the matrix expects. ---*/ + struct CJacobianDifference { + const su2double* const* conv; + const su2double* const* visc; + struct Row { + const su2double *c, *v; + su2double operator[](unsigned long j) const { return c[j] - v[j]; } + }; + Row operator[](unsigned long i) const { return {conv[i], visc[i]}; } + }; + if (visc.jacobian_i != nullptr) { + const CJacobianDifference jac_i{conv.jacobian_i, visc.jacobian_i}; + const CJacobianDifference jac_j{conv.jacobian_j, visc.jacobian_j}; + if (ReducerStrategy) Jacobian.SetBlocks(iEdge, jac_i, jac_j); + else Jacobian.UpdateBlocks(iEdge, iPoint, jPoint, jac_i, jac_j); + } else { + if (ReducerStrategy) Jacobian.SetBlocks(iEdge, conv.jacobian_i, conv.jacobian_j); + else Jacobian.UpdateBlocks(iEdge, iPoint, jPoint, conv.jacobian_i, conv.jacobian_j); + } + } + /*! * \brief Compute a suitable under-relaxation parameter to limit the change in the solution variables over a nonlinear * iteration for stability. diff --git a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl index 699d48401d98..1c0d3c512eba 100644 --- a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl +++ b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl @@ -317,7 +317,7 @@ void CFVMFlowSolverBase::HybridParallelInitialization(const CConfig& confi if (!coloring.empty()) { /*--- If the reducer strategy is used we are not constrained by group * size as we have no other edge loops in the Euler/NS solvers. ---*/ - auto groupSize = ReducerStrategy ? 1ul : geometry.GetEdgeColorGroupSize(); + auto groupSize = static_cast(ReducerStrategy ? 1ul : geometry.GetEdgeColorGroupSize()); auto nColor = coloring.getOuterSize(); EdgeColoring.reserve(nColor); @@ -445,11 +445,11 @@ void CFVMFlowSolverBase::SetPrimitive_Limiter(CGeometry* geometry, const C } template -void CFVMFlowSolverBase::Viscous_Residual_impl(unsigned long iEdge, CGeometry *geometry, CSolver **solver_container, - CNumerics *numerics, CConfig *config) { +CNumerics::ResidualType<> CFVMFlowSolverBase::Viscous_Residual_impl(unsigned long iEdge, CGeometry *geometry, + CSolver **solver_container, + CNumerics *numerics, CConfig *config) { SU2_ZONE_SCOPED - const bool implicit = (config->GetKind_TimeIntScheme() == EULER_IMPLICIT); const bool tkeNeeded = (config->GetKind_Turb_Model() == TURB_MODEL::SST); const bool backscatter = config->GetSBSParam().StochasticBackscatter; const bool ideal_gas = (config->GetKind_FluidModel() == STANDARD_AIR) || @@ -518,17 +518,14 @@ void CFVMFlowSolverBase::Viscous_Residual_impl(unsigned long iEdge, CGeome if (ReducerStrategy) { EdgeFluxes.SubtractBlock(iEdge, residual); - if (implicit) - Jacobian.UpdateBlocksSub(iEdge, residual.jacobian_i, residual.jacobian_j); } else { LinSysRes.SubtractBlock(iPoint, residual); LinSysRes.AddBlock(jPoint, residual); - - if (implicit) - Jacobian.UpdateBlocksSub(iEdge, iPoint, jPoint, residual.jacobian_i, residual.jacobian_j); } + /*--- The Jacobians are applied by the caller, fused with the convective contribution. ---*/ + return residual; } template @@ -1296,13 +1293,14 @@ void CFVMFlowSolverBase::BC_Sym_Plane(CGeometry* geometry, CSolve auto ModifyJacobian = [&](const unsigned long jPoint) { su2double jac[MAXNVAR * MAXNVAR], newJac[MAXNVAR * MAXNVAR]; - auto* block = Jacobian.GetBlock(iPoint, jPoint); - for (auto iVar = 0u; iVar < nVar * nVar; iVar++) jac[iVar] = block[iVar]; + const auto view = Jacobian.GetBlockView(iPoint, jPoint); + if (!view) return; + for (auto iVar = 0u; iVar < nVar; iVar++) + for (auto jVar = 0u; jVar < nVar; jVar++) jac[iVar * nVar + jVar] = view(iVar, jVar); CBlasStructure().gemm(nVar, nVar, nVar, mat, jac, newJac, config); - for (auto iVar = 0u; iVar < nVar * nVar; iVar++) - block[iVar] = SU2_TYPE::GetValue(newJac[iVar]); + Jacobian.SetBlock(iPoint, jPoint, newJac); }; ModifyJacobian(iPoint); for (size_t iNeigh = 0; iNeigh < geometry->nodes->GetnPoint(iPoint); ++iNeigh) { diff --git a/SU2_CFD/include/solvers/CIncNSSolver.hpp b/SU2_CFD/include/solvers/CIncNSSolver.hpp index dc2d80e86e6f..712855d0a205 100644 --- a/SU2_CFD/include/solvers/CIncNSSolver.hpp +++ b/SU2_CFD/include/solvers/CIncNSSolver.hpp @@ -61,9 +61,10 @@ class CIncNSSolver final : public CIncEulerSolver { * \param[in] solver_container - Container vector with all the solutions. * \param[in] numerics - Description of the numerical method. * \param[in] config - Definition of the particular problem. + * \return The viscous Jacobians, to be applied by the caller together with the convective part. */ - void Viscous_Residual(unsigned long iEdge, CGeometry *geometry, CSolver **solver_container, - CNumerics *numerics, CConfig *config) override; + CNumerics::ResidualType<> Viscous_Residual(unsigned long iEdge, CGeometry *geometry, CSolver **solver_container, + CNumerics *numerics, CConfig *config) override; /*! * \brief Computes the wall shear stress (Tau_Wall) on the surface using a wall function. diff --git a/SU2_CFD/include/solvers/CNSSolver.hpp b/SU2_CFD/include/solvers/CNSSolver.hpp index 4ac0ee4ae7da..405ff6de9a72 100644 --- a/SU2_CFD/include/solvers/CNSSolver.hpp +++ b/SU2_CFD/include/solvers/CNSSolver.hpp @@ -111,9 +111,10 @@ class CNSSolver final : public CEulerSolver { * \param[in] solver_container - Container vector with all the solutions. * \param[in] numerics - Description of the numerical method. * \param[in] config - Definition of the particular problem. + * \return The viscous Jacobians, to be applied by the caller together with the convective part. */ - void Viscous_Residual(unsigned long iEdge, CGeometry *geometry, CSolver **solver_container, - CNumerics *numerics, CConfig *config) override; + CNumerics::ResidualType<> Viscous_Residual(unsigned long iEdge, CGeometry *geometry, CSolver **solver_container, + CNumerics *numerics, CConfig *config) override; /*! * \brief Computes the wall shear stress (Tau_Wall) on the surface using a wall function. diff --git a/SU2_CFD/include/solvers/CScalarSolver.inl b/SU2_CFD/include/solvers/CScalarSolver.inl index fd6848c7a470..d6e05191b228 100644 --- a/SU2_CFD/include/solvers/CScalarSolver.inl +++ b/SU2_CFD/include/solvers/CScalarSolver.inl @@ -63,7 +63,7 @@ CScalarSolver::CScalarSolver(CGeometry* geometry, CConfig* config, if (ReducerStrategy && (coloring.getOuterSize() > 1)) geometry->SetNaturalEdgeColoring(); if (!coloring.empty()) { - auto groupSize = ReducerStrategy ? 1ul : geometry->GetEdgeColorGroupSize(); + auto groupSize = static_cast(ReducerStrategy ? 1ul : geometry->GetEdgeColorGroupSize()); auto nColor = coloring.getOuterSize(); EdgeColoring.reserve(nColor); @@ -105,7 +105,7 @@ void CScalarSolver::CommonPreprocessing(CGeometry *geometry, const if (!ReducerStrategy && !Output) { LinSysRes.SetValZero(); if (implicit) { - Jacobian.SetValZero(); + Jacobian.SetValDiagonalZero(); } else { SU2_OMP_BARRIER } @@ -291,7 +291,7 @@ void CScalarSolver::Upwind_Residual(CGeometry* geometry, CSolver** } else { LinSysRes.AddBlock(iPoint, residual); LinSysRes.SubtractBlock(jPoint, residual); - if (implicit) Jacobian.UpdateBlocks(iEdge, iPoint, jPoint, residual.jacobian_i, residual.jacobian_j); + if (implicit) Jacobian.UpdateBlocks(iEdge, iPoint, jPoint, residual.jacobian_i, residual.jacobian_j); } /*--- Apply convective flux correction to negate the effects of flow divergence in case of incompressible flow. diff --git a/SU2_CFD/include/solvers/CSolver.hpp b/SU2_CFD/include/solvers/CSolver.hpp index 3fc3c2a7bc98..942e2e258779 100644 --- a/SU2_CFD/include/solvers/CSolver.hpp +++ b/SU2_CFD/include/solvers/CSolver.hpp @@ -3492,7 +3492,7 @@ class CSolver { * \param[in] rhs - Right hand side. * \param[in] nVar - Number of variables. */ - void Gauss_Elimination(su2double** A, + void GaussElimination(su2double** A, su2double* rhs, unsigned short nVar); diff --git a/SU2_CFD/src/output/filewriter/CSU2MeshBinaryFileWriter.cpp b/SU2_CFD/src/output/filewriter/CSU2MeshBinaryFileWriter.cpp index 3349adcecf53..525eb1e23c9e 100644 --- a/SU2_CFD/src/output/filewriter/CSU2MeshBinaryFileWriter.cpp +++ b/SU2_CFD/src/output/filewriter/CSU2MeshBinaryFileWriter.cpp @@ -96,7 +96,9 @@ void CSU2MeshBinaryFileWriter::WriteData(string val_filename) { fwrite(&n_zone, sizeof(n_zone), 1, f); } - int32_t zone_id = iZone; + /*--- Zone IDs are 1-based, matching the "IZONE=" convention of the ASCII + format (CSU2MeshFileWriter writes iZone+1 as well). ---*/ + int32_t zone_id = iZone + 1; int32_t n_dim = dataSorter->GetnDim(); conn_t n_elem = dataSorter->GetnElemGlobal(); fwrite(&zone_id, sizeof(zone_id), 1, f); diff --git a/SU2_CFD/src/solvers/CAdjEulerSolver.cpp b/SU2_CFD/src/solvers/CAdjEulerSolver.cpp index 361df6d4c6b1..88ee3214a633 100644 --- a/SU2_CFD/src/solvers/CAdjEulerSolver.cpp +++ b/SU2_CFD/src/solvers/CAdjEulerSolver.cpp @@ -2094,7 +2094,7 @@ void CAdjEulerSolver::Smooth_Sensitivity(CGeometry *geometry, CSolver **solver_c A[iVertex][iVertex+1] = 0.0; A[iVertex][iVertex-1] = 0.0; - Gauss_Elimination(A, b, (unsigned short)nVertex); + GaussElimination(A, b, (unsigned short)nVertex); /*--- Set the new value of the sensitiviy ---*/ diff --git a/SU2_CFD/src/solvers/CEulerSolver.cpp b/SU2_CFD/src/solvers/CEulerSolver.cpp index 4532fc2e6c1c..d5577a2320f5 100644 --- a/SU2_CFD/src/solvers/CEulerSolver.cpp +++ b/SU2_CFD/src/solvers/CEulerSolver.cpp @@ -164,7 +164,7 @@ CEulerSolver::CEulerSolver(CGeometry *geometry, CConfig *config, if (rank == MASTER_NODE) cout << "Initialize Jacobian structure (" << description << "). MG level: " << iMesh <<"." << endl; - Jacobian.Initialize(nPoint, nPointDomain, nVar, nVar, true, geometry, config, ReducerStrategy); + Jacobian.Initialize(nPoint, nPointDomain, nVar, nVar, true, geometry, config, ReducerStrategy, false, true); } else { if (rank == MASTER_NODE) @@ -1990,9 +1990,9 @@ void CEulerSolver::Upwind_Residual(CGeometry *geometry, CSolver **solver_contain /*--- Compute the residual ---*/ - auto residual = numerics->ComputeResidual(config); + auto conv_residual = numerics->ComputeResidual(config); - if (bounded_scalar) EdgeMassFluxes[iEdge] = residual[0]; + if (bounded_scalar) EdgeMassFluxes[iEdge] = conv_residual[0]; /*--- Set the final value of the Roe dissipation coefficient ---*/ @@ -2004,23 +2004,18 @@ void CEulerSolver::Upwind_Residual(CGeometry *geometry, CSolver **solver_contain /*--- Update residual value ---*/ if (ReducerStrategy) { - EdgeFluxes.SetBlock(iEdge, residual); - if (implicit) - Jacobian.SetBlocks(iEdge, residual.jacobian_i, residual.jacobian_j); + EdgeFluxes.SetBlock(iEdge, conv_residual); + } else { + LinSysRes.AddBlock(iPoint, conv_residual); + LinSysRes.SubtractBlock(jPoint, conv_residual); } - else { - LinSysRes.AddBlock(iPoint, residual); - LinSysRes.SubtractBlock(jPoint, residual); - /*--- Set implicit computation ---*/ - if (implicit) - Jacobian.UpdateBlocks(iEdge, iPoint, jPoint, residual.jacobian_i, residual.jacobian_j); - } + /*--- Viscous contribution, returns its Jacobians so that the matrix is updated once. ---*/ - /*--- Viscous contribution. ---*/ + const auto visc_residual = Viscous_Residual( + iEdge, geometry, solver_container, numerics_container[VISC_TERM + omp_get_thread_num()*MAX_TERMS], config); - Viscous_Residual(iEdge, geometry, solver_container, - numerics_container[VISC_TERM + omp_get_thread_num()*MAX_TERMS], config); + if (implicit) UpdateJacobian(iEdge, iPoint, jPoint, conv_residual, visc_residual); } END_SU2_OMP_FOR } // end color loop diff --git a/SU2_CFD/src/solvers/CFEASolver.cpp b/SU2_CFD/src/solvers/CFEASolver.cpp index 31cb41c52d93..27ad9c8fdffa 100644 --- a/SU2_CFD/src/solvers/CFEASolver.cpp +++ b/SU2_CFD/src/solvers/CFEASolver.cpp @@ -258,7 +258,7 @@ void CFEASolver::HybridParallelInitialization(CGeometry* geometry) { if (!coloring.empty()) { /*--- We are not constrained by the color group size when using locks. ---*/ - auto groupSize = LockStrategy? 1ul : geometry->GetElementColorGroupSize(); + auto groupSize = static_cast(LockStrategy ? 1ul : geometry->GetElementColorGroupSize()); auto nColor = coloring.getOuterSize(); ElemColoring.reserve(nColor); diff --git a/SU2_CFD/src/solvers/CHeatSolver.cpp b/SU2_CFD/src/solvers/CHeatSolver.cpp index 52b591770ca2..2a9e4790760b 100644 --- a/SU2_CFD/src/solvers/CHeatSolver.cpp +++ b/SU2_CFD/src/solvers/CHeatSolver.cpp @@ -180,13 +180,11 @@ void CHeatSolver::Preprocessing(CGeometry *geometry, CSolver **solver_container, /*--- Need to clear EdgeFluxes and Jacobian when only the viscous part is called for solid heat transfer, * for the weakly coupled energy equation the convection part does this by setting instead of incrementing. ---*/ - if (!Output && !flow && ReducerStrategy) { - EdgeFluxes.SetValZero(); - if (config->GetKind_TimeIntScheme() == EULER_IMPLICIT) { - Jacobian.SetValZero(); - } else { - SU2_OMP_BARRIER - } + if (!Output && !flow) { + if (ReducerStrategy) EdgeFluxes.SetValZero(); + if (config->GetKind_TimeIntScheme() == EULER_IMPLICIT) Jacobian.SetValZero(); + + SU2_OMP_BARRIER } } diff --git a/SU2_CFD/src/solvers/CIncEulerSolver.cpp b/SU2_CFD/src/solvers/CIncEulerSolver.cpp index 2c4c9b05e1c6..4951551851f0 100644 --- a/SU2_CFD/src/solvers/CIncEulerSolver.cpp +++ b/SU2_CFD/src/solvers/CIncEulerSolver.cpp @@ -155,7 +155,7 @@ CIncEulerSolver::CIncEulerSolver(CGeometry *geometry, CConfig *config, unsigned if (rank == MASTER_NODE) cout << "Initialize Jacobian structure (" << description << "). MG level: " << iMesh <<"." << endl; - Jacobian.Initialize(nPoint, nPointDomain, nVar, nVar, true, geometry, config, ReducerStrategy); + Jacobian.Initialize(nPoint, nPointDomain, nVar, nVar, true, geometry, config, ReducerStrategy, false, true); } else { if (rank == MASTER_NODE) @@ -1013,7 +1013,7 @@ void CIncEulerSolver::CommonPreprocessing(CGeometry *geometry, CSolver **solver_ if(!ReducerStrategy && !Output) { LinSysRes.SetValZero(); - if (implicit) Jacobian.SetValZero(); + if (implicit) Jacobian.SetValDiagonalZero(); else {SU2_OMP_BARRIER} // because of "nowait" in LinSysRes } } @@ -1199,30 +1199,25 @@ void CIncEulerSolver::Centered_Residual(CGeometry *geometry, CSolver **solver_co /*--- Compute residuals, and Jacobians ---*/ - auto residual = numerics->ComputeResidual(config); + auto conv_residual = numerics->ComputeResidual(config); - if (bounded_scalar) EdgeMassFluxes[iEdge] = residual[0]; + if (bounded_scalar) EdgeMassFluxes[iEdge] = conv_residual[0]; /*--- Update residual value ---*/ if (ReducerStrategy) { - EdgeFluxes.SetBlock(iEdge, residual); - if (implicit) - Jacobian.SetBlocks(iEdge, residual.jacobian_i, residual.jacobian_j); + EdgeFluxes.SetBlock(iEdge, conv_residual); + } else { + LinSysRes.AddBlock(iPoint, conv_residual); + LinSysRes.SubtractBlock(jPoint, conv_residual); } - else { - LinSysRes.AddBlock(iPoint, residual); - LinSysRes.SubtractBlock(jPoint, residual); - /*--- Set implicit computation ---*/ - if (implicit) - Jacobian.UpdateBlocks(iEdge, iPoint, jPoint, residual.jacobian_i, residual.jacobian_j); - } + /*--- Viscous contribution, returns its Jacobians so that the matrix is updated once. ---*/ - /*--- Viscous contribution. ---*/ + const auto visc_residual = Viscous_Residual( + iEdge, geometry, solver_container, numerics_container[VISC_TERM + omp_get_thread_num()*MAX_TERMS], config); - Viscous_Residual(iEdge, geometry, solver_container, - numerics_container[VISC_TERM + omp_get_thread_num()*MAX_TERMS], config); + if (implicit) UpdateJacobian(iEdge, iPoint, jPoint, conv_residual, visc_residual); } END_SU2_OMP_FOR } // end color loop @@ -1377,30 +1372,26 @@ void CIncEulerSolver::Upwind_Residual(CGeometry *geometry, CSolver **solver_cont /*--- Compute the residual ---*/ - auto residual = numerics->ComputeResidual(config); + auto conv_residual = numerics->ComputeResidual(config); - if (bounded_scalar) EdgeMassFluxes[iEdge] = residual[0]; + if (bounded_scalar) EdgeMassFluxes[iEdge] = conv_residual[0]; /*--- Update residual value ---*/ if (ReducerStrategy) { - EdgeFluxes.SetBlock(iEdge, residual); - if (implicit) - Jacobian.SetBlocks(iEdge, residual.jacobian_i, residual.jacobian_j); + EdgeFluxes.SetBlock(iEdge, conv_residual); } else { - LinSysRes.AddBlock(iPoint, residual); - LinSysRes.SubtractBlock(jPoint, residual); - - /*--- Set implicit computation ---*/ - if (implicit) - Jacobian.UpdateBlocks(iEdge, iPoint, jPoint, residual.jacobian_i, residual.jacobian_j); + LinSysRes.AddBlock(iPoint, conv_residual); + LinSysRes.SubtractBlock(jPoint, conv_residual); } - /*--- Viscous contribution. ---*/ + /*--- Viscous contribution, returns its Jacobians so that the matrix is updated once. ---*/ + + const auto visc_residual = Viscous_Residual( + iEdge, geometry, solver_container, numerics_container[VISC_TERM + omp_get_thread_num()*MAX_TERMS], config); - Viscous_Residual(iEdge, geometry, solver_container, - numerics_container[VISC_TERM + omp_get_thread_num()*MAX_TERMS], config); + if (implicit) UpdateJacobian(iEdge, iPoint, jPoint, conv_residual, visc_residual); } END_SU2_OMP_FOR diff --git a/SU2_CFD/src/solvers/CIncNSSolver.cpp b/SU2_CFD/src/solvers/CIncNSSolver.cpp index 2d6dd26786dd..2532215cf8d8 100644 --- a/SU2_CFD/src/solvers/CIncNSSolver.cpp +++ b/SU2_CFD/src/solvers/CIncNSSolver.cpp @@ -275,8 +275,9 @@ void CIncNSSolver::Compute_Streamwise_Periodic_Recovered_Values(CConfig *config, SU2_OMP_SAFE_GLOBAL_ACCESS(GetStreamwise_Periodic_Properties(geometry, config, iMesh);) } -void CIncNSSolver::Viscous_Residual(unsigned long iEdge, CGeometry *geometry, CSolver **solver_container, - CNumerics *numerics, CConfig *config) { +CNumerics::ResidualType<> CIncNSSolver::Viscous_Residual(unsigned long iEdge, CGeometry *geometry, + CSolver **solver_container, CNumerics *numerics, + CConfig *config) { const bool energy_multicomponent = config->GetKind_FluidModel() == FLUID_MIXTURE && config->GetEnergy_Equation(); /*--- Contribution to heat flux due to enthalpy diffusion for multicomponent and reacting flows ---*/ @@ -286,7 +287,7 @@ void CIncNSSolver::Viscous_Residual(unsigned long iEdge, CGeometry *geometry, CS Compute_Enthalpy_Diffusion(iEdge, geometry, solver_container, numerics, n_species, implicit); } - Viscous_Residual_impl(iEdge, geometry, solver_container, numerics, config); + return Viscous_Residual_impl(iEdge, geometry, solver_container, numerics, config); } void CIncNSSolver::Compute_Enthalpy_Diffusion(unsigned long iEdge, CGeometry* geometry, CSolver** solver_container, diff --git a/SU2_CFD/src/solvers/CNEMOEulerSolver.cpp b/SU2_CFD/src/solvers/CNEMOEulerSolver.cpp index 37de9a832074..93bc7037e1f6 100644 --- a/SU2_CFD/src/solvers/CNEMOEulerSolver.cpp +++ b/SU2_CFD/src/solvers/CNEMOEulerSolver.cpp @@ -282,7 +282,7 @@ void CNEMOEulerSolver::CommonPreprocessing(CGeometry *geometry, CSolver **solver if(!ReducerStrategy && !Output) { LinSysRes.SetValZero(); - if (implicit) Jacobian.SetValZero(); + if (implicit) Jacobian.SetValDiagonalZero(); } } @@ -460,7 +460,7 @@ void CNEMOEulerSolver::Centered_Residual(CGeometry *geometry, CSolver **solver_c LinSysRes.AddBlock(iPoint, residual); LinSysRes.SubtractBlock(jPoint, residual); if (implicit) { - Jacobian.UpdateBlocks(iEdge, iPoint, jPoint, residual.jacobian_i, residual.jacobian_j); + Jacobian.UpdateBlocks(iEdge, iPoint, jPoint, residual.jacobian_i, residual.jacobian_j); } } } @@ -625,7 +625,7 @@ void CNEMOEulerSolver::Upwind_Residual(CGeometry *geometry, CSolver **solver_con LinSysRes.AddBlock(iPoint, residual); LinSysRes.SubtractBlock(jPoint, residual); if (implicit) { - Jacobian.UpdateBlocks(iEdge, iPoint, jPoint, residual.jacobian_i, residual.jacobian_j); + Jacobian.UpdateBlocks(iEdge, iPoint, jPoint, residual.jacobian_i, residual.jacobian_j); } } } @@ -1894,7 +1894,7 @@ void CNEMOEulerSolver::BC_Inlet(CGeometry *geometry, CSolver **solver_container, // /*--- Jacobian contribution for implicit integration ---*/ // if (implicit) -// Jacobian.SubtractBlock(iPoint, iPoint, Jacobian_i); +// Jacobian.SubtractBlock2Diag(iPoint, Jacobian_i); // } } } diff --git a/SU2_CFD/src/solvers/CNEMONSSolver.cpp b/SU2_CFD/src/solvers/CNEMONSSolver.cpp index b3578a0d554d..bae5591d9460 100644 --- a/SU2_CFD/src/solvers/CNEMONSSolver.cpp +++ b/SU2_CFD/src/solvers/CNEMONSSolver.cpp @@ -798,7 +798,7 @@ void CNEMONSSolver::BC_IsothermalCatalytic_Wall(CGeometry *geometry, Jacobian_i[iVar][jVar] += Jacobian_j[iVar][kVar]*dVdU[kVar][jVar]*Area; /*--- Apply to the linear system ---*/ - Jacobian.SubtractBlock(iPoint, iPoint, Jacobian_i); + Jacobian.SubtractBlock2Diag(iPoint, Jacobian_i); } } else { diff --git a/SU2_CFD/src/solvers/CNSSolver.cpp b/SU2_CFD/src/solvers/CNSSolver.cpp index 367d7db04bbc..643cb9a4f6c9 100644 --- a/SU2_CFD/src/solvers/CNSSolver.cpp +++ b/SU2_CFD/src/solvers/CNSSolver.cpp @@ -178,9 +178,10 @@ unsigned long CNSSolver::SetPrimitive_Variables(CSolver **solver_container, cons return nonPhysicalPoints; } -void CNSSolver::Viscous_Residual(unsigned long iEdge, CGeometry *geometry, CSolver **solver_container, - CNumerics *numerics, CConfig *config) { - Viscous_Residual_impl(iEdge, geometry, solver_container, numerics, config); +CNumerics::ResidualType<> CNSSolver::Viscous_Residual(unsigned long iEdge, CGeometry *geometry, + CSolver **solver_container, CNumerics *numerics, + CConfig *config) { + return Viscous_Residual_impl(iEdge, geometry, solver_container, numerics, config); } void CNSSolver::Buffet_Monitoring(const CGeometry *geometry, const CConfig *config) { diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index 72685eb9bc56..e014c794a52d 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -27,6 +27,9 @@ #include "../../include/solvers/CSolver.hpp" + +#include + #include "../../include/gradients/computeGradientsGreenGauss.hpp" #include "../../include/gradients/computeGradientsLeastSquares.hpp" #include "../../include/limiters/computeLimiters.hpp" @@ -542,9 +545,11 @@ void CSolver::InitiatePeriodicComms(CGeometry *geometry, if (implicit_periodic) { + const auto block = Jacobian.GetBlockView(iPoint, iPoint); + for (iVar = 0; iVar < nVar; iVar++) { for (jVar = 0; jVar < nVar; jVar++) { - jacBlock[iVar][jVar] = Jacobian.GetBlock(iPoint, iPoint, iVar, jVar); + jacBlock[iVar][jVar] = block(iVar, jVar); } } @@ -553,21 +558,15 @@ void CSolver::InitiatePeriodicComms(CGeometry *geometry, if (rotate_periodic) { for (iVar = 0; iVar < nVar; iVar++) { if (nDim == 2) { - jacBlock[1][iVar] = (rotMatrix2D[0][0]*Jacobian.GetBlock(iPoint, iPoint, 1, iVar) + - rotMatrix2D[0][1]*Jacobian.GetBlock(iPoint, iPoint, 2, iVar)); - jacBlock[2][iVar] = (rotMatrix2D[1][0]*Jacobian.GetBlock(iPoint, iPoint, 1, iVar) + - rotMatrix2D[1][1]*Jacobian.GetBlock(iPoint, iPoint, 2, iVar)); + jacBlock[1][iVar] = rotMatrix2D[0][0]*block(1, iVar) + rotMatrix2D[0][1]*block(2, iVar); + jacBlock[2][iVar] = rotMatrix2D[1][0]*block(1, iVar) + rotMatrix2D[1][1]*block(2, iVar); } else { - - jacBlock[1][iVar] = (rotMatrix3D[0][0]*Jacobian.GetBlock(iPoint, iPoint, 1, iVar) + - rotMatrix3D[0][1]*Jacobian.GetBlock(iPoint, iPoint, 2, iVar) + - rotMatrix3D[0][2]*Jacobian.GetBlock(iPoint, iPoint, 3, iVar)); - jacBlock[2][iVar] = (rotMatrix3D[1][0]*Jacobian.GetBlock(iPoint, iPoint, 1, iVar) + - rotMatrix3D[1][1]*Jacobian.GetBlock(iPoint, iPoint, 2, iVar) + - rotMatrix3D[1][2]*Jacobian.GetBlock(iPoint, iPoint, 3, iVar)); - jacBlock[3][iVar] = (rotMatrix3D[2][0]*Jacobian.GetBlock(iPoint, iPoint, 1, iVar) + - rotMatrix3D[2][1]*Jacobian.GetBlock(iPoint, iPoint, 2, iVar) + - rotMatrix3D[2][2]*Jacobian.GetBlock(iPoint, iPoint, 3, iVar)); + jacBlock[1][iVar] = rotMatrix3D[0][0]*block(1, iVar) + rotMatrix3D[0][1]*block(2, iVar) + + rotMatrix3D[0][2]*block(3, iVar); + jacBlock[2][iVar] = rotMatrix3D[1][0]*block(1, iVar) + rotMatrix3D[1][1]*block(2, iVar) + + rotMatrix3D[1][2]*block(3, iVar); + jacBlock[3][iVar] = rotMatrix3D[2][0]*block(1, iVar) + rotMatrix3D[2][1]*block(2, iVar) + + rotMatrix3D[2][2]*block(3, iVar); } } } @@ -1997,7 +1996,7 @@ void CSolver::SetResidual_RMS(const CGeometry *geometry, const CConfig *config) /*--- Set the L2 Norm residual in all the processors. ---*/ - vector rbuf_res(nVar); + vector rbuf_res(nVar * nDim); unsigned long Global_nPointDomain = 0; if (config->GetComm_Level() == COMM_FULL) { @@ -2028,21 +2027,22 @@ void CSolver::SetResidual_RMS(const CGeometry *geometry, const CConfig *config) /*--- Set the Maximum residual in all the processors. ---*/ if (config->GetComm_Level() == COMM_FULL) { - - const unsigned long nProcessor = size; - - su2activematrix rbuf_residual(nProcessor,nVar); - su2matrix rbuf_point(nProcessor,nVar); - su2activematrix rbuf_coord(nProcessor*nVar, nDim); - - SU2_MPI::Allgather(Residual_Max.data(), nVar, MPI_DOUBLE, rbuf_residual.data(), nVar, MPI_DOUBLE, SU2_MPI::GetComm()); - SU2_MPI::Allgather(Point_Max.data(), nVar, MPI_UNSIGNED_LONG, rbuf_point.data(), nVar, MPI_UNSIGNED_LONG, SU2_MPI::GetComm()); - SU2_MPI::Allgather(Point_Max_Coord.data(), nVar*nDim, MPI_DOUBLE, rbuf_coord.data(), nVar*nDim, MPI_DOUBLE, SU2_MPI::GetComm()); - + SU2_MPI::Allreduce(Residual_Max.data(), rbuf_res.data(), nVar, MPI_DOUBLE, MPI_MAX, SU2_MPI::GetComm()); for (unsigned short iVar = 0; iVar < nVar; iVar++) { - for (auto iProcessor = 0ul; iProcessor < nProcessor; iProcessor++) { - AddRes_Max(iVar, rbuf_residual(iProcessor,iVar), rbuf_point(iProcessor,iVar), rbuf_coord[iProcessor*nVar+iVar]); + if (Residual_Max[iVar] < rbuf_res[iVar]) { + Point_Max[iVar] = 0; + for (unsigned short iDim = 0; iDim < nDim; iDim++) { + Point_Max_Coord(iVar, iDim) = std::numeric_limits::lowest(); + } } + Residual_Max[iVar] = rbuf_res[iVar]; + } + vector rbuf_point(nVar); + SU2_MPI::Allreduce(Point_Max.data(), rbuf_point.data(), nVar, MPI_UNSIGNED_LONG, MPI_MAX, SU2_MPI::GetComm()); + SU2_MPI::Allreduce(Point_Max_Coord.data(), rbuf_res.data(), nVar*nDim, MPI_DOUBLE, MPI_MAX, SU2_MPI::GetComm()); + Point_Max = std::move(rbuf_point); + for (unsigned short iVar = 0; iVar < nVar * nDim; iVar++) { + Point_Max_Coord.data()[iVar] = rbuf_res[iVar]; } } @@ -2331,7 +2331,7 @@ void CSolver::SetSolution_Limiter(CGeometry *geometry, const CConfig *config) { *geometry, *config, 0, nVar, umusclKappa, solution, gradient, solMin, solMax, limiter); } -void CSolver::Gauss_Elimination(su2double** A, su2double* rhs, unsigned short nVar) { +void CSolver::GaussElimination(su2double** A, su2double* rhs, unsigned short nVar) { SU2_ZONE_SCOPED short iVar, jVar, kVar; diff --git a/TestCases/hybrid_regression.py b/TestCases/hybrid_regression.py index 52b5c2755be5..443d367a10a8 100644 --- a/TestCases/hybrid_regression.py +++ b/TestCases/hybrid_regression.py @@ -103,7 +103,7 @@ def main(): flatplate.cfg_dir = "navierstokes/flatplate" flatplate.cfg_file = "lam_flatplate.cfg" flatplate.test_iter = 100 - flatplate.test_vals = [-6.543300, -1.065167, 0.001196, 0.029390, 2.361500, -2.332100, 0.000000, 0.000000] + flatplate.test_vals = [-6.543281, -1.065152, 0.001196, 0.029390, 2.361500, -2.332100, 0.000000, 0.000000] test_list.append(flatplate) # Laminar cylinder (steady) @@ -119,7 +119,7 @@ def main(): cylinder_lowmach.cfg_dir = "navierstokes/cylinder" cylinder_lowmach.cfg_file = "cylinder_lowmach.cfg" cylinder_lowmach.test_iter = 25 - cylinder_lowmach.test_vals = [-6.476618, -1.014896, 0.594402, 70.856965, 0.000000] + cylinder_lowmach.test_vals = [-6.476618, -1.014896, 0.594402, 70.856953, 0.000000] cylinder_lowmach.test_vals_aarch64 = [-6.830996, -1.368850, -0.143956, 73.963354, 0] test_list.append(cylinder_lowmach) @@ -128,7 +128,7 @@ def main(): poiseuille.cfg_dir = "navierstokes/poiseuille" poiseuille.cfg_file = "lam_poiseuille.cfg" poiseuille.test_iter = 10 - poiseuille.test_vals = [-5.046182, 0.652932, 0.008488, 13.734488, 0.000000] + poiseuille.test_vals = [-7.595535, -6.993332, 0.007670, 13.591507, 0] test_list.append(poiseuille) # 2D Poiseuille flow (inlet profile file) @@ -136,7 +136,7 @@ def main(): poiseuille_profile.cfg_dir = "navierstokes/poiseuille" poiseuille_profile.cfg_file = "profile_poiseuille.cfg" poiseuille_profile.test_iter = 10 - poiseuille_profile.test_vals = [-12.005129, -7.582314, -0.000000, 2.089953] + poiseuille_profile.test_vals = [-12.005126, -7.582352, -0.000000, 2.089953] poiseuille_profile.test_vals_aarch64 = [-12.009012, -7.262530, -0.000000, 2.089953] test_list.append(poiseuille_profile) @@ -145,7 +145,7 @@ def main(): periodic2d.cfg_dir = "navierstokes/periodic2D" periodic2d.cfg_file = "config.cfg" periodic2d.test_iter = 1400 - periodic2d.test_vals = [-10.817608, -8.363541, -8.287458, -5.334101, -1.088412, -2945.200000] + periodic2d.test_vals = [-10.817608, -8.363541, -8.287458, -5.334101, -1.088411, -2945.200000] test_list.append(periodic2d) ########################## @@ -197,7 +197,7 @@ def main(): turb_naca0012_sa.cfg_dir = "rans/naca0012" turb_naca0012_sa.cfg_file = "turb_NACA0012_sa.cfg" turb_naca0012_sa.test_iter = 5 - turb_naca0012_sa.test_vals = [-12.038060, -16.332088, 1.080346, 0.018385, 20.000000, -2.873410, 0.000000, -14.250270, 0.000000] + turb_naca0012_sa.test_vals = [-12.038042, -16.332088, 1.080346, 0.018385, 20.000000, -2.873258, 0.000000, -14.250270, 0.000000] turb_naca0012_sa.test_vals_aarch64 = [-12.038091, -16.332090, 1.080346, 0.018385, 20.000000, -2.873236, 0.000000, -14.250271, 0.000000] test_list.append(turb_naca0012_sa) @@ -206,7 +206,7 @@ def main(): turb_naca0012_sst.cfg_dir = "rans/naca0012" turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" turb_naca0012_sst.test_iter = 10 - turb_naca0012_sst.test_vals = [-12.093924, -15.250755, -5.906323, 1.070413, 0.015775, -2.855101, 0.000000] + turb_naca0012_sst.test_vals = [-12.093984, -15.250705, -5.906323, 1.070413, 0.015775, -2.855331, 0.000000] turb_naca0012_sst.test_vals_aarch64 = [-12.075928, -15.246732, -5.861249, 1.070036, 0.015841, -2.835263, 0] test_list.append(turb_naca0012_sst) @@ -215,7 +215,7 @@ def main(): turb_naca0012_sst_sust.cfg_dir = "rans/naca0012" turb_naca0012_sst_sust.cfg_file = "turb_NACA0012_sst_sust.cfg" turb_naca0012_sst_sust.test_iter = 10 - turb_naca0012_sst_sust.test_vals = [-12.080792, -14.837174, -5.732908, 1.000893, 0.019109, -2.120172] + turb_naca0012_sst_sust.test_vals = [-12.080774, -14.837176, -5.732907, 1.000893, 0.019109, -2.120168] turb_naca0012_sst_sust.test_vals_aarch64 = [-12.073210, -14.836724, -5.732627, 1.000050, 0.019144, -2.629689] test_list.append(turb_naca0012_sst_sust) @@ -252,7 +252,7 @@ def main(): axi_rans_air_nozzle_restart.cfg_dir = "axisymmetric_rans/air_nozzle" axi_rans_air_nozzle_restart.cfg_file = "air_nozzle_restart.cfg" axi_rans_air_nozzle_restart.test_iter = 10 - axi_rans_air_nozzle_restart.test_vals = [-11.083069, -5.374689, -8.880092, -4.073524, 0.000000] + axi_rans_air_nozzle_restart.test_vals = [-11.083070, -5.374688, -8.880089, -4.073519, 0.000000] axi_rans_air_nozzle_restart.test_vals_aarch64 = [-14.140441, -9.154674, -10.886121, -5.806594, 0.000000] test_list.append(axi_rans_air_nozzle_restart) @@ -367,7 +367,7 @@ def main(): inc_lam_cylinder.cfg_dir = "incomp_navierstokes/cylinder" inc_lam_cylinder.cfg_file = "incomp_cylinder.cfg" inc_lam_cylinder.test_iter = 10 - inc_lam_cylinder.test_vals = [-4.161196, -3.573053, 0.025533, 4.944647] + inc_lam_cylinder.test_vals = [-4.161196, -3.573053, 0.025533, 4.944646] test_list.append(inc_lam_cylinder) # Buoyancy-driven cavity @@ -404,7 +404,7 @@ def main(): inc_turb_naca0012.cfg_dir = "incomp_rans/naca0012" inc_turb_naca0012.cfg_file = "naca0012.cfg" inc_turb_naca0012.test_iter = 20 - inc_turb_naca0012.test_vals = [-4.758114, -10.974548, -0.000004, -0.028637, 5.000000, -4.080548, 2.000000, -4.490129] + inc_turb_naca0012.test_vals = [-4.758114, -10.974548, -0.000004, -0.028637, 5.000000, -4.080155, 2.000000, -4.491724] test_list.append(inc_turb_naca0012) # NACA0012, SST_SUST @@ -420,7 +420,7 @@ def main(): inc_weakly_coupled.cfg_dir = "disc_adj_heat" inc_weakly_coupled.cfg_file = "primal.cfg" inc_weakly_coupled.test_iter = 10 - inc_weakly_coupled.test_vals = [-18.106209, -16.303012, -16.484904, -15.006575, -17.858050, -14.024869, 5.609100] + inc_weakly_coupled.test_vals = [-18.106234, -16.302995, -16.484896, -15.006575, -17.858050, -14.024855, 5.609100] test_list.append(inc_weakly_coupled) ###################################### @@ -440,7 +440,7 @@ def main(): spinning_cylinder.cfg_dir = "moving_wall/spinning_cylinder" spinning_cylinder.cfg_file = "spinning_cylinder.cfg" spinning_cylinder.test_iter = 25 - spinning_cylinder.test_vals = [-7.533969, -2.066690, 1.832252, 1.843016] + spinning_cylinder.test_vals = [-7.533969, -2.066689, 1.832252, 1.843016] spinning_cylinder.test_vals_aarch64 = [-8.008023, -2.611064, 1.497308, 1.487483] test_list.append(spinning_cylinder) @@ -570,7 +570,7 @@ def main(): transonic_stator_restart.cfg_dir = "turbomachinery/transonic_stator_2D" transonic_stator_restart.cfg_file = "transonic_stator_restart.cfg" transonic_stator_restart.test_iter = 20 - transonic_stator_restart.test_vals = [-4.367141, -2.487739, -2.079071, 1.728176, -1.464952, 3.225028, -471620.000000, 94.839000, -0.051126] + transonic_stator_restart.test_vals = [-4.367141, -2.487739, -2.079071, 1.728176, -1.464951, 3.225028, -471620.000000, 94.839000, -0.051125] transonic_stator_restart.test_vals_aarch64 = [-4.442510, -2.561369, -2.165778, 1.652750, -1.355494, 3.172712, -471620.000000, 94.843000, -0.043825] test_list.append(transonic_stator_restart) @@ -592,12 +592,21 @@ def main(): uniform_flow.cfg_dir = "sliding_interface/uniform_flow" uniform_flow.cfg_file = "uniform_NN.cfg" uniform_flow.test_iter = 5 - uniform_flow.test_vals = [5.000000, 0.000000, -0.195002, -10.624450] + uniform_flow.test_vals = [5.000000, 0.000000, -0.195002, -10.624446] uniform_flow.unsteady = True uniform_flow.multizone = True test_list.append(uniform_flow) - # Channel_2D + # Channel_2D, native SU2 binary mesh format (.su2b) + # channel_2D_WA.cfg loads channel_2D_su2bin.su2b directly, so SU2_DEF must + # first convert channel_2D.su2 (3 zones) into that binary mesh. + channel_2D_su2bin_convert = TestCase('channel_2D_su2bin_convert') + channel_2D_su2bin_convert.cfg_dir = "sliding_interface/channel_2D" + channel_2D_su2bin_convert.cfg_file = "mesh_su2_to_su2bin.cfg" + channel_2D_su2bin_convert.command = TestCase.Command(exec = "SU2_DEF") + channel_2D_su2bin_convert.timeout = 600 + test_list.append(channel_2D_su2bin_convert) + channel_2D = TestCase('channel_2D') channel_2D.cfg_dir = "sliding_interface/channel_2D" channel_2D.cfg_file = "channel_2D_WA.cfg" @@ -663,7 +672,7 @@ def main(): slinc_steady.cfg_dir = "sliding_interface/incompressible_steady" slinc_steady.cfg_file = "config.cfg" slinc_steady.test_iter = 19 - slinc_steady.test_vals = [19.000000, -1.144102, -1.424987] + slinc_steady.test_vals = [19.000000, -1.144102, -1.424986] slinc_steady.test_vals_aarch64 = [19.000000, -1.154874, -1.378120] slinc_steady.multizone = True test_list.append(slinc_steady) @@ -746,7 +755,7 @@ def main(): mms_fvm_inc_ns.cfg_dir = "mms/fvm_incomp_navierstokes" mms_fvm_inc_ns.cfg_file = "lam_mms_fds.cfg" mms_fvm_inc_ns.test_iter = 20 - mms_fvm_inc_ns.test_vals = [-7.414945, -7.631546, 0.000000, 0.000000] + mms_fvm_inc_ns.test_vals = [-7.414945, -7.631547, 0.000000, 0.000000] test_list.append(mms_fvm_inc_ns) ########################## @@ -786,8 +795,10 @@ def main(): ###################################### for test in test_list: - test.command = TestCase.Command(exec = "SU2_CFD", param = "-t 2") - test.timeout = 600 + if test.command.empty(): + test.command = TestCase.Command(exec = "SU2_CFD", param = "-t 2") + if test.timeout == 0: + test.timeout = 600 test.tol = 1e-4 #end diff --git a/TestCases/hybrid_regression_AD.py b/TestCases/hybrid_regression_AD.py index e0077078778c..4db360930312 100644 --- a/TestCases/hybrid_regression_AD.py +++ b/TestCases/hybrid_regression_AD.py @@ -111,7 +111,7 @@ def main(): discadj_incomp_cylinder.cfg_dir = "disc_adj_incomp_navierstokes/cylinder" discadj_incomp_cylinder.cfg_file = "heated_cylinder.cfg" discadj_incomp_cylinder.test_iter = 20 - discadj_incomp_cylinder.test_vals = [20.000000, -1.665653, -6.239112, 0.000000] + discadj_incomp_cylinder.test_vals = [20.000000, -1.665652, -6.239091, 0.000000] discadj_incomp_cylinder.test_vals_aarch64 = [20.000000, -1.671920, -6.254841, 0.000000] discadj_incomp_cylinder.tol_aarch64 = 2e-1 test_list.append(discadj_incomp_cylinder) @@ -125,7 +125,7 @@ def main(): discadj_incomp_turb_NACA0012_sa.cfg_dir = "disc_adj_incomp_rans/naca0012" discadj_incomp_turb_NACA0012_sa.cfg_file = "turb_naca0012_sa.cfg" discadj_incomp_turb_NACA0012_sa.test_iter = 10 - discadj_incomp_turb_NACA0012_sa.test_vals = [10.000000, -3.845989, -1.023526, 0.000000] + discadj_incomp_turb_NACA0012_sa.test_vals = [10.000000, -3.845989, -1.023525, 0.000000] test_list.append(discadj_incomp_turb_NACA0012_sa) # Adjoint Incompressible Turbulent NACA 0012 SST @@ -133,7 +133,7 @@ def main(): discadj_incomp_turb_NACA0012_sst.cfg_dir = "disc_adj_incomp_rans/naca0012" discadj_incomp_turb_NACA0012_sst.cfg_file = "turb_naca0012_sst.cfg" discadj_incomp_turb_NACA0012_sst.test_iter = 10 - discadj_incomp_turb_NACA0012_sst.test_vals = [-3.775278, -3.089107, -7.143663, 0.000000, -0.896760] + discadj_incomp_turb_NACA0012_sst.test_vals = [-3.775275, -3.089116, -7.143654, 0.000000, -0.896764] test_list.append(discadj_incomp_turb_NACA0012_sst) ####################################################### @@ -243,7 +243,7 @@ def main(): pywrapper_CFD_AD_MeshDisp.cfg_dir = "py_wrapper/disc_adj_flow/mesh_disp_sens" pywrapper_CFD_AD_MeshDisp.cfg_file = "configAD_flow.cfg" pywrapper_CFD_AD_MeshDisp.test_iter = 1000 - pywrapper_CFD_AD_MeshDisp.test_vals = [30.000000, -2.496079, 1.441818, 0.000000] + pywrapper_CFD_AD_MeshDisp.test_vals = [30.000000, -2.496158, 1.441842, 0.000000] pywrapper_CFD_AD_MeshDisp.test_vals_aarch64 = [30.000000, -2.499079, 1.440068, 0.000000] pywrapper_CFD_AD_MeshDisp.command = TestCase.Command(exec = "python", param = "run_adjoint.py --parallel -f") pywrapper_CFD_AD_MeshDisp.timeout = 1600 diff --git a/TestCases/incomp_navierstokes/sphere/sphere.cfg b/TestCases/incomp_navierstokes/sphere/sphere.cfg index 0150ea5c0ba3..2f9eef68ff8d 100644 --- a/TestCases/incomp_navierstokes/sphere/sphere.cfg +++ b/TestCases/incomp_navierstokes/sphere/sphere.cfg @@ -51,7 +51,7 @@ MARKER_MONITORING= ( wall ) % ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% % NUM_METHOD_GRAD= GREEN_GAUSS -CFL_NUMBER= 100.0 +CFL_NUMBER= 25.0 CFL_ADAPT= NO ITER=50000 VENKAT_LIMITER_COEFF= 0.01 @@ -59,7 +59,7 @@ VENKAT_LIMITER_COEFF= 0.01 % ------------------------ LINEAR SOLVER DEFINITION ---------------------------% % LINEAR_SOLVER= FGMRES -LINEAR_SOLVER_PREC= ILU +LINEAR_SOLVER_PREC= Q_LU_SGS LINEAR_SOLVER_ERROR= 1E-01 LINEAR_SOLVER_ITER= 3 @@ -100,5 +100,5 @@ VOLUME_FILENAME= flow SURFACE_FILENAME= surface_flow OUTPUT_WRT_FREQ= 100 WRT_VOLUME_OVERWRITE= YES -SCREEN_OUTPUT= ( INNER_ITER, RMS_PRESSURE, RMS_VELOCITY-X, RMS_VELOCITY-Y, RMS_VELOCITY-Z, LIFT, DRAG) +SCREEN_OUTPUT= ( INNER_ITER, RMS_PRESSURE, RMS_VELOCITY-X, RMS_VELOCITY-Y, RMS_VELOCITY-Z, LIFT, DRAG, LINSOL_RESIDUAL) VOLUME_OUTPUT= ( SOLUTION,PRIMITIVE,RESIDUAL,MULTIGRID, RANK ) diff --git a/TestCases/navierstokes/poiseuille/lam_poiseuille.cfg b/TestCases/navierstokes/poiseuille/lam_poiseuille.cfg index fbdf927f053d..553e71b0ce7b 100644 --- a/TestCases/navierstokes/poiseuille/lam_poiseuille.cfg +++ b/TestCases/navierstokes/poiseuille/lam_poiseuille.cfg @@ -35,7 +35,7 @@ REF_ORIGIN_MOMENT_Y = 0.00 REF_ORIGIN_MOMENT_Z = 0.00 REF_LENGTH= 1.0 REF_AREA= 1.0 -REF_DIMENSIONALIZATION= DIMENSIONAL +REF_DIMENSIONALIZATION= FREESTREAM_VEL_EQ_MACH % ---- IDEAL GAS, POLYTROPIC, VAN DER WAALS AND PENG ROBINSON CONSTANTS -------% % @@ -77,7 +77,7 @@ ITER= 99999 % ------------------------ LINEAR SOLVER DEFINITION ---------------------------% % LINEAR_SOLVER= FGMRES -LINEAR_SOLVER_PREC= LU_SGS +LINEAR_SOLVER_PREC= Q_LU_SGS LINEAR_SOLVER_ERROR= 1E-4 LINEAR_SOLVER_ITER= 5 diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index ebbf99274368..bea1653798d4 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -397,7 +397,7 @@ def main(): poiseuille.cfg_dir = "navierstokes/poiseuille" poiseuille.cfg_file = "lam_poiseuille.cfg" poiseuille.test_iter = 10 - poiseuille.test_vals = [0.648196, 0.000199, 13.639173, 0.000000] + poiseuille.test_vals = [-6.994786, 0.000197, 13.596249, 0] poiseuille.tol = 0.001 test_list.append(poiseuille) @@ -674,7 +674,7 @@ def main(): inc_lam_sphere.cfg_dir = "incomp_navierstokes/sphere" inc_lam_sphere.cfg_file = "sphere.cfg" inc_lam_sphere.test_iter = 5 - inc_lam_sphere.test_vals = [-8.190948, -8.992588, 0.121003, 25.782691] + inc_lam_sphere.test_vals = [-7.600533, -8.244915, -8.361301, -9.325293, 0.121003, 25.782687, -1.881890] test_list.append(inc_lam_sphere) # Buoyancy-driven cavity @@ -1219,12 +1219,20 @@ def main(): uniform_flow.cfg_dir = "sliding_interface/uniform_flow" uniform_flow.cfg_file = "uniform_NN.cfg" uniform_flow.test_iter = 5 - uniform_flow.test_vals = [5.000000, 0.000000, -0.195001, -10.624453] + uniform_flow.test_vals = [5.000000, 0.000000, -0.195001, -10.624458] uniform_flow.unsteady = True uniform_flow.multizone = True test_list.append(uniform_flow) - # Channel_2D + # Channel_2D, native SU2 binary mesh format (.su2b) + # channel_2D_WA.cfg loads channel_2D_su2bin.su2b directly, so SU2_DEF must + # first convert channel_2D.su2 (3 zones) into that binary mesh. + channel_2D_su2bin_convert = TestCase('channel_2D_su2bin_convert') + channel_2D_su2bin_convert.cfg_dir = "sliding_interface/channel_2D" + channel_2D_su2bin_convert.cfg_file = "mesh_su2_to_su2bin.cfg" + channel_2D_su2bin_convert.command = TestCase.Command("mpirun -n 2", "SU2_DEF") + test_list.append(channel_2D_su2bin_convert) + channel_2D = TestCase('channel_2D') channel_2D.cfg_dir = "sliding_interface/channel_2D" channel_2D.cfg_file = "channel_2D_WA.cfg" @@ -1391,7 +1399,7 @@ def main(): dyn_fsi.cfg_dir = "fea_fsi/dyn_fsi" dyn_fsi.cfg_file = "config.cfg" dyn_fsi.test_iter = 4 - dyn_fsi.test_vals = [-4.330741, -4.152826, 0, 75] + dyn_fsi.test_vals = [-4.330741, -4.152826, 0.000000, 75.000000] dyn_fsi.multizone = True dyn_fsi.unsteady = True test_list.append(dyn_fsi) diff --git a/TestCases/parallel_regression_AD.py b/TestCases/parallel_regression_AD.py index 2b8283f49a4b..8e66a9716b10 100644 --- a/TestCases/parallel_regression_AD.py +++ b/TestCases/parallel_regression_AD.py @@ -308,7 +308,7 @@ def main(): da_sp_pinArray_cht_2d_dp_hf.cfg_dir = "incomp_navierstokes/streamwise_periodic/chtPinArray_2d" da_sp_pinArray_cht_2d_dp_hf.cfg_file = "DA_configMaster.cfg" da_sp_pinArray_cht_2d_dp_hf.test_iter = 100 - da_sp_pinArray_cht_2d_dp_hf.test_vals = [-2.717803, -3.199669, -2.499149] + da_sp_pinArray_cht_2d_dp_hf.test_vals = [-11.620815, -6.488915, -13.316507] da_sp_pinArray_cht_2d_dp_hf.multizone = True test_list.append(da_sp_pinArray_cht_2d_dp_hf) @@ -326,7 +326,7 @@ def main(): da_unsteadyCHT_cylinder.cfg_dir = "coupled_cht/disc_adj_unsteadyCHT_cylinder" da_unsteadyCHT_cylinder.cfg_file = "chtMaster.cfg" da_unsteadyCHT_cylinder.test_iter = 2 - da_unsteadyCHT_cylinder.test_vals = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662004, 0.000000, 10.627000, 0.295190] + da_unsteadyCHT_cylinder.test_vals = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662007, 0.000000, 10.627000, 0.295190] da_unsteadyCHT_cylinder.test_vals_aarch64 = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662012, 0.000000, 89.932000, 0.295190] da_unsteadyCHT_cylinder.unsteady = True da_unsteadyCHT_cylinder.multizone = True diff --git a/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref b/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref index 512fc7311277..0a0e8b9b8a41 100644 --- a/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref +++ b/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref @@ -98,14 +98,14 @@ 96, 73.03, 33.47, 0.00 97, 67.54, 18.28, 0.00 98, 54.75, 7.35, 0.00 -99, 17.22, -0.00, 0.00 -100, 52.62, -7.06, 0.00 +99, 17.21, -0.00, 0.00 +100, 52.61, -7.06, 0.00 101, 93.60, -25.33, 0.00 102, 62.71, -28.74, 0.00 103, 27.14, -17.68, 0.00 104, 23.12, -19.64, 0.00 105, 5.43, -5.72, 0.00 -106, -0.11, 0.14, 0.00 +106, -0.11, 0.15, 0.00 107, -12.46, 18.52, 0.00 108, -19.25, 33.03, 0.00 109, -25.52, 49.90, 0.00 diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index 34b395713611..8adb14300f37 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -190,7 +190,7 @@ def main(): poiseuille.cfg_dir = "navierstokes/poiseuille" poiseuille.cfg_file = "lam_poiseuille.cfg" poiseuille.test_iter = 10 - poiseuille.test_vals = [-5.050753, 0.648333, 0.012273, 13.643141, 0.000000] + poiseuille.test_vals = [-7.597002, -6.994814, 0.012154, 13.596296, 0] test_list.append(poiseuille) # 2D Poiseuille flow (inlet profile file) @@ -955,14 +955,23 @@ def main(): uniform_flow.cfg_dir = "sliding_interface/uniform_flow" uniform_flow.cfg_file = "uniform_NN.cfg" uniform_flow.test_iter = 2 - uniform_flow.test_vals = [2.000000, 0.000000, -0.230639, -13.251161] + uniform_flow.test_vals = [2.000000, 0.000000, -0.230639, -13.253604] uniform_flow.test_vals_aarch64 = [2.000000, 0.000000, -0.230641, -13.249000] uniform_flow.tol = 0.000001 uniform_flow.unsteady = True uniform_flow.multizone = True test_list.append(uniform_flow) - # Channel_2D + # Channel_2D, native SU2 binary mesh format (.su2b) + # channel_2D_WA.cfg loads channel_2D_su2bin.su2b directly, so SU2_DEF must + # first convert channel_2D.su2 (3 zones) into that binary mesh. + channel_2D_su2bin_convert = TestCase('channel_2D_su2bin_convert') + channel_2D_su2bin_convert.cfg_dir = "sliding_interface/channel_2D" + channel_2D_su2bin_convert.cfg_file = "mesh_su2_to_su2bin.cfg" + channel_2D_su2bin_convert.command = TestCase.Command(exec = "SU2_DEF") + test_list.append(channel_2D_su2bin_convert) + + # Channel_2D channel_2D = TestCase('channel_2D') channel_2D.cfg_dir = "sliding_interface/channel_2D" channel_2D.cfg_file = "channel_2D_WA.cfg" diff --git a/TestCases/sliding_interface/channel_2D/channel_2D_WA.cfg b/TestCases/sliding_interface/channel_2D/channel_2D_WA.cfg index c1f9bce93081..0249a5b226c8 100644 --- a/TestCases/sliding_interface/channel_2D/channel_2D_WA.cfg +++ b/TestCases/sliding_interface/channel_2D/channel_2D_WA.cfg @@ -84,8 +84,8 @@ CONV_CAUCHY_EPS= 1E-6 % % ------------------------- INPUT/OUTPUT INFORMATION --------------------------% % -MESH_FILENAME= channel_2D.su2 -MESH_FORMAT= SU2 +MESH_FILENAME= channel_2D_su2bin +MESH_FORMAT= SU2B MESH_OUT_FILENAME= mesh_out SOLUTION_FILENAME= restart_flow TABULAR_FORMAT= CSV diff --git a/TestCases/sliding_interface/channel_2D/mesh_su2_to_su2bin.cfg b/TestCases/sliding_interface/channel_2D/mesh_su2_to_su2bin.cfg new file mode 100644 index 000000000000..33834607d350 --- /dev/null +++ b/TestCases/sliding_interface/channel_2D/mesh_su2_to_su2bin.cfg @@ -0,0 +1,40 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% SU2 configuration file % +% Case description: Convert channel_2D.su2 (3 zones) to the native SU2 binary % +% mesh format (.su2b), for the channel_2D regression test. % +% A zero-magnitude grid translation is used. % +% File Version 8.5.0 "Harrier" % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +MULTIZONE = YES +CONFIG_LIST= (zone_1.cfg, zone_2.cfg, zone_3.cfg) + +SOLVER= EULER +MARKER_EULER= ( wall ) +MARKER_RIEMANN= (up_inlet, STATIC_SUPERSONIC_INFLOW_PT, 95750, 288.15, 3.0, 0.0, 0.0, down_inlet, STATIC_SUPERSONIC_INFLOW_PT, 95750, 288.15, 1.5, 0.0, 0.0,outlet, STATIC_PRESSURE, 95750.0, 0.0, 0.0, 0.0, 0.0) +MARKER_ZONE_INTERFACE= ( internal_interface, inner_interface, domain_interface, external_interface ) +MARKER_FLUID_INTERFACE= ( internal_interface, inner_interface, domain_interface, external_interface ) +KIND_INTERPOLATION= WEIGHTED_AVERAGE + +% ----------------------- DESIGN VARIABLE PARAMETERS --------------------------% +% +DV_KIND= TRANSLATE_GRID +DV_MARKER= ( wall ) +DV_PARAM= ( 1.0, 0.0, 0.0 ) +DV_VALUE= 0.0 + +% ---------------------- GRID DEFORMATION PARAMETERS ---------------------------% +% +DEFORM_LINEAR_SOLVER= FGMRES +DEFORM_LINEAR_SOLVER_PREC= ILU +DEFORM_LINEAR_SOLVER_ERROR= 1E-14 +DEFORM_LINEAR_SOLVER_ITER= 500 +DEFORM_NONLINEAR_ITER= 1 +DEFORM_STIFFNESS_TYPE= INVERSE_VOLUME + +% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% +% +MESH_FILENAME= channel_2D.su2 +MESH_FORMAT= SU2 +MESH_OUT_FILENAME= channel_2D_su2bin +MESH_OUT_FORMAT= SU2B diff --git a/TestCases/tutorials.py b/TestCases/tutorials.py index 4f73d7fb1fa2..4e039eea09b9 100644 --- a/TestCases/tutorials.py +++ b/TestCases/tutorials.py @@ -237,7 +237,7 @@ def main(): tutorial_trans_flatplate_T3A.cfg_dir = "../Tutorials/compressible_flow/Transitional_Flat_Plate/Langtry_and_Menter/T3A" tutorial_trans_flatplate_T3A.cfg_file = "transitional_LM_model_ConfigFile.cfg" tutorial_trans_flatplate_T3A.test_iter = 20 - tutorial_trans_flatplate_T3A.test_vals = [-5.790137, -2.054834, -3.894659, -0.255074, -1.747098, 5.119341, -3.493237, 0.393262] + tutorial_trans_flatplate_T3A.test_vals = [-5.790137, -2.054834, -3.894659, -0.255074, -1.747087, 5.119341, -3.493237, 0.393262] tutorial_trans_flatplate_T3A.test_vals_aarch64 = [-5.808996, -2.070606, -3.969765, -0.277943, -1.953289, 1.708472, -3.514943, 0.357411] tutorial_trans_flatplate_T3A.no_restart = True test_list.append(tutorial_trans_flatplate_T3A) @@ -247,7 +247,7 @@ def main(): tutorial_trans_flatplate_T3Am.cfg_dir = "../Tutorials/compressible_flow/Transitional_Flat_Plate/Langtry_and_Menter/T3A-" tutorial_trans_flatplate_T3Am.cfg_file = "transitional_LM_model_ConfigFile.cfg" tutorial_trans_flatplate_T3Am.test_iter = 20 - tutorial_trans_flatplate_T3Am.test_vals = [-5.587332, -1.700868, -3.093872, -0.102783, -3.750523, 3.287643, -2.394575, 1.119623] + tutorial_trans_flatplate_T3Am.test_vals = [-5.587389, -1.700868, -3.093936, -0.102834, -3.750523, 3.287643, -2.394575, 1.119623] tutorial_trans_flatplate_T3Am.test_vals_aarch64 = [-5.540938, -1.681627, -2.878831, -0.058224, -3.695533, 3.413628, -2.385345, 1.103633] tutorial_trans_flatplate_T3Am.no_restart = True test_list.append(tutorial_trans_flatplate_T3Am) diff --git a/UnitTests/Common/linear_algebra/quantization_tests.cpp b/UnitTests/Common/linear_algebra/quantization_tests.cpp new file mode 100644 index 000000000000..d074a6f16e2c --- /dev/null +++ b/UnitTests/Common/linear_algebra/quantization_tests.cpp @@ -0,0 +1,113 @@ +/*! + * \file quantization_tests.cpp + * \brief Unit tests for the int8 row-scaled block quantization used by Q_LU_SGS. + * \author P. Gomes + * \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 "../../../Common/include/linear_algebra/CSysMatrix.hpp" + +/*--- Row-major access into a flat quantized block, matching CBlockView's decode. ---*/ +static su2double Decode(const int8_t* qs, const int8_t* qv, unsigned long nVar, unsigned long r, unsigned long c) { + return static_cast(qv[r * nVar + c]) * DecodeQuantScale(qs[r]); +} + +TEST_CASE("Quantization round-trip is stable for well-behaved values", "[LinearAlgebra]") { + /*--- EncodeQuantBlock always encodes a full nVar x nVar block (one scale per row), + * so every row is set to the same values here and only row 0 is inspected. Values + * are moderate: none close enough to the row's own max to saturate, and none land + * exactly on the int8 min (-128), so the derived per-row scale does not shift + * between successive encode/decode passes. ---*/ + constexpr unsigned long nVar = 4; + const su2double row[nVar] = {3.0, -2.0, 1.5, 0.75}; + auto f = [&](unsigned long, unsigned long c) { return row[c]; }; + + int8_t qs1[nVar], qv1[nVar * nVar]; + EncodeQuantBlock(f, qs1, qv1, nVar); + + auto decoded = [&](unsigned long, unsigned long c) { return Decode(qs1, qv1, nVar, 0, c); }; + + int8_t qs2[nVar], qv2[nVar * nVar]; + EncodeQuantBlock(decoded, qs2, qv2, nVar); + + CHECK(qs2[0] == qs1[0]); + for (unsigned long c = 0; c < nVar; ++c) CHECK(qv2[c] == qv1[c]); + + /*--- A second decode/encode cycle from the now-stable representation must + * reproduce the exact same codes again. ---*/ + auto decoded2 = [&](unsigned long, unsigned long c) { return Decode(qs2, qv2, nVar, 0, c); }; + int8_t qs3[nVar], qv3[nVar * nVar]; + EncodeQuantBlock(decoded2, qs3, qv3, nVar); + + CHECK(qs3[0] == qs2[0]); + for (unsigned long c = 0; c < nVar; ++c) CHECK(qv3[c] == qv2[c]); +} + +TEST_CASE("Quantization saturates and truncates with a known, bounded error", "[LinearAlgebra]") { + /*--- One row engineered so that, at the scale it forces (2^0 = 1 here, since the + * largest magnitude in the row is in [64, 128)): + * col 0: 127.9 -> rounds to 128, clamped to 127 (int8 max), error ~0.9 (close to + * the theoretical worst case: clamping can only ever push a value that + * rounds to +128 down to +127, an error that approaches but never reaches + * 1 full scale unit). + * col 1: -127.9 -> rounds to -128 (int8 min), no clamping needed since -128 is a + * representable int8 value; error ~0.1 (near-exact). Clamping can only + * ever trigger on the positive side because int8 is asymmetric + * ([-128, 127]) while the encoding is symmetric around the row's max abs. + * col 2: 0.2 -> rounds to 0: small values are truncated away entirely. + * col 3: 60.0 -> reconstructs exactly, since the row's scale (2^0 = 1) is an + * exact power of two and 60 fits in int8. ---*/ + constexpr unsigned long nVar = 4; + const su2double row[nVar] = {127.9, -127.9, 0.2, 60.0}; + auto f = [&](unsigned long, unsigned long c) { return row[c]; }; + + int8_t qs[nVar], qv[nVar * nVar]; + EncodeQuantBlock(f, qs, qv, nVar); + + CHECK(qs[0] == 0); + CHECK(static_cast(qv[0]) == 127); // Saturated at the int8 maximum. + CHECK(static_cast(qv[1]) == -128); // Hits the int8 minimum exactly. + CHECK(static_cast(qv[2]) == 0); // Truncated to zero. + CHECK(static_cast(qv[3]) == 60); // Exact, mid-range value. + + const su2double scale = DecodeQuantScale(qs[0]); + CHECK(scale == Approx(1.0)); + + const su2double error0 = std::abs(row[0] - Decode(qs, qv, nVar, 0, 0)); + const su2double error1 = std::abs(row[1] - Decode(qs, qv, nVar, 0, 1)); + const su2double error2 = std::abs(row[2] - Decode(qs, qv, nVar, 0, 2)); + const su2double error3 = std::abs(row[3] - Decode(qs, qv, nVar, 0, 3)); + + /*--- The quantization error for any entry is strictly bounded by one scale unit: + * 0.5 from rounding to the nearest representable level, plus (only for values that + * saturate on the positive side) less than another 0.5 from clamping 128 down to 127. ---*/ + CHECK(error0 < scale); + CHECK(error1 < scale); + CHECK(error2 < scale); + CHECK(error3 == Approx(0.0).margin(1e-12)); + + CHECK(error0 == Approx(0.9).margin(1e-5)); + CHECK(error1 == Approx(0.1).margin(1e-5)); + CHECK(error2 == Approx(0.2).margin(1e-5)); +} diff --git a/UnitTests/meson.build b/UnitTests/meson.build index 76eecd1dc57c..f8c22511b5f8 100644 --- a/UnitTests/meson.build +++ b/UnitTests/meson.build @@ -17,7 +17,8 @@ su2_cfd_tests = files(['Common/geometry/primal_grid/CPrimalGrid_tests.cpp', 'SU2_CFD/fluid/CFluidModel_tests.cpp', 'SU2_CFD/gradients.cpp', 'SU2_CFD/windowing.cpp', - 'Common/toolboxes/random_toolbox_tests.cpp']) + 'Common/toolboxes/random_toolbox_tests.cpp', + 'Common/linear_algebra/quantization_tests.cpp']) # Reverse-mode (algorithmic differentiation) tests: su2_cfd_tests_ad = files(['Common/simple_ad_test.cpp', diff --git a/config_template.cfg b/config_template.cfg index a7d357240e0e..bc6be98103ae 100644 --- a/config_template.cfg +++ b/config_template.cfg @@ -1648,7 +1648,11 @@ DISCADJ_LIN_SOLVER= FGMRES % Use CUDA GPU Acceleration for FGMRES Linear Solver Only ENABLE_CUDA=NO % -% Preconditioner of the Krylov linear solver or type of smoother (ILU, LU_SGS, LINELET, JACOBI) +% Preconditioner of the Krylov linear solver or type of smoother (ILU, LU_SGS, Q_LU_SGS, LINELET, JACOBI) +% Q_LU_SGS triggers the use of quantization to reduce the size of the sparse matrix (for compressible flow +% the matrix becomes 3x smaller relative to mixed-precision mode). This is only used by the compressible +% and incompressible solvers, others fallback silently to LU_SGS. A suitable nondimensionalization mode +% MUST be used otherwise the solver is very likely to diverge. LINEAR_SOLVER_PREC= ILU % % Same for discrete adjoint (JACOBI or ILU), replaces LINEAR_SOLVER_PREC in SU2_*_AD codes. From de8c50153262ba4985173d560f1e95f39f30ee3d Mon Sep 17 00:00:00 2001 From: Pedro Gomes Date: Sat, 18 Jul 2026 20:18:29 -0700 Subject: [PATCH 23/61] trigger actions From 44bf0836958e8c9807427bc60cf7c27709bc2ed0 Mon Sep 17 00:00:00 2001 From: Jesse Li <256257451+LwhJesse@users.noreply.github.com> Date: Sun, 2 Aug 2026 09:56:39 +0800 Subject: [PATCH 24/61] Add initial end-to-end CUDA FGMRES solver path (#2825) ## Proposed Changes This PR adds an end-to-end CUDA linear solve path: the Krylov solvers keep their host control flow, and `CSysVector` operations, the SpMV and the Jacobi preconditioner are dispatched to CUDA kernels when `ENABLE_CUDA=YES`. Transfers are explicit and owned by the object responsible for the data, with no coherency or dirty-flag tracking in `CSysVector` / `CSysMatrix`: - `CSysMatrixVectorProduct` uploads the matrix on construction - the preconditioner uploads its data in `Build()` - `CSysSolve` uploads `b` and `x` and downloads `x` in `HandleTemporariesIn/Out`, which is also where device evaluation is switched on and off - preconditioners without a device implementation (ILU, LU-SGS, Linelet, PaStiX) download the input, apply on the host, and upload the result A solve is therefore fully device resident for Identity and Jacobi preconditioners: **2 uploads and 1 download per linear system**, independent of the Krylov subspace size. Implementation notes: - `cuBLAS` for `dot` / `norm`, custom kernels for the block-LDU SpMV, the Jacobi apply, and `CSysVector` expression assignment - `CSysVector` operands are captured in expressions by value, so an arbitrary expression tree is trivially copyable into the assignment kernel; the required expression shapes are explicitly instantiated in `CSysVectorGPU.cu`, consistent with how `CSysMatrix` is instantiated - device work is issued by a single thread with the OpenMP team synchronized around it, so the GPU path is usable from inside the existing parallel regions; this is internal to the linear algebra layer - the CUDA translation units are only linked into the primal libraries, since they cannot be compiled with the CoDiPack defines; device dispatch is compiled out of the AD builds - adds `LINEAR_SOLVER_PREC= NONE` (identity) ## Related Work Follows the review direction in #2822 (show a working end-to-end GPU linear solve before splitting out infrastructure) and the implementation preferences in #2816. ## Validation - CPU vs GPU on inviscid NACA0012, 25 iterations, RTX 4070 Ti SUPER (sm_89), for FGMRES + `JACOBI`, FGMRES + `NONE`, FGMRES + `ILU` (host preconditioner path) and BCGSTAB. Results agree to the printed precision in double, and to ~6 significant figures in mixed precision. BCGSTAB agrees exactly once the linear system is converged (`LINEAR_SOLVER_ERROR=1e-10`); at loose tolerances the two paths diverge through BCGSTAB's own sensitivity, not a difference in the algebra. - Mixed, normal and single precision builds all compile and run; device dispatch confirmed live in each (kernel launch counts track the subspace size). - `OMP_NUM_THREADS=1` and `4` give bit-identical results on the GPU path. - Builds verified: primal, primal + AD + directdiff, `enable-cuda` + `with-omp`, mixed / normal / single precision. - Transfer counts measured per solve: 2 H2D + 1 D2H + 1 matrix upload for Jacobi and `NONE`, plus one download/upload pair per preconditioner application for ILU. Earlier validation of the original design (6 representative cases, `nsys` / `ncu` profiling) predates the rework of the transfer and dispatch model and should be repeated. ## PR Checklist - [x] I am submitting my contribution to the develop branch. - [x] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [x] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [x] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [ ] I have added a test case that demonstrates my contribution, if necessary. - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --------- Co-authored-by: Pedro Gomes --- AUTHORS.md | 17 +- Common/include/code_config.hpp | 10 + .../linear_algebra/CMatrixVectorProduct.hpp | 30 ++- .../linear_algebra/CPreconditioner.hpp | 52 +++- Common/include/linear_algebra/CSysMatrix.hpp | 15 +- Common/include/linear_algebra/CSysSolve.hpp | 49 +++- Common/include/linear_algebra/CSysVector.hpp | 252 ++++++++++++++++-- Common/include/linear_algebra/GPUComms.cuh | 7 + .../linear_algebra/vector_expressions.hpp | 47 ++-- Common/include/option_structure.hpp | 2 + .../include/parallelization/omp_structure.hpp | 7 +- Common/src/linear_algebra/CSysMatrix.cpp | 41 +++ Common/src/linear_algebra/CSysMatrixGPU.cu | 22 +- .../linear_algebra/CSysPreconditionerGPU.cu | 84 ++++++ Common/src/linear_algebra/CSysSolve.cpp | 8 +- Common/src/linear_algebra/CSysVector.cpp | 20 ++ Common/src/linear_algebra/CSysVectorGPU.cu | 195 +++++++++++++- Common/src/linear_algebra/meson.build | 6 +- Common/src/meson.build | 24 +- meson.build | 20 +- su2omp.syntax.json | 1 + 21 files changed, 808 insertions(+), 101 deletions(-) create mode 100644 Common/src/linear_algebra/CSysPreconditionerGPU.cu diff --git a/AUTHORS.md b/AUTHORS.md index eaea9531b712..fea157beba2f 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -10,18 +10,18 @@ Thomas D. Economon (formerly Stanford University) Juan J. Alonso (Stanford University) ``` -## Current Maintainer ## +## Current Maintainers ## The SU2 project is maintained by members of the SU2 Foundation (https://su2foundation.org) ``` -Thomas D. Economon - Executive Director - tom@su2foundation.org -Tim Albring - Director - tim@su2foundation.org -Juan J. Alonso - Director - juan@su2foundation.org -Eran Arad - Director - eran@su2foundation.org -Piero Colonna - Director - piero@su2foundation.org -Pedro Gomes - Director - pedro@su2foundation.org -Daniel Mayer - Director - daniel@su2foundation.org +Thomas D. Economon - Chairperson +Matteo Pini - Vice Chairperson +Nijso Beishuizen - Treasurer +Pedro Gomes - Development Officer +Giulio Gori - Secretary +Nitish Anand - Editorial Officer +Edwin van der Weide - Events Officer ``` in collaboration with the following main contributors and research teams: @@ -93,6 +93,7 @@ Jairo Paes Cavalcante Filho Jason Howison Jayant Mukhopadhaya Jeffrey van Oostrom +Jesse Li Jessie Lauzon João Loureiro Johannes Blühdorn diff --git a/Common/include/code_config.hpp b/Common/include/code_config.hpp index ae30c779e7af..41d3c747cf86 100644 --- a/Common/include/code_config.hpp +++ b/Common/include/code_config.hpp @@ -96,6 +96,16 @@ FORCEINLINE Out su2staticcast_p(In ptr) { #define HAVE_OMP #endif +/*--- Detect whether the CUDA kernels are part of this build. The .cu translation units + * cannot be compiled with the CoDiPack defines (nvcc's device pass cannot parse the tape + * machinery), and an object compiled with a different definition of su2double must not be + * linked into an AD library. They are therefore only built into the primal libraries, and + * all device dispatch has to be compiled out of the AD builds, which HAVE_CUDA alone does + * not do because su2mixedfloat is a passive type there as well. ---*/ +#if defined(HAVE_CUDA) && !defined(CODI_REVERSE_TYPE) && !defined(CODI_FORWARD_TYPE) +#define SU2_ENABLE_CUDA_KERNELS +#endif + /*--- No full single precision for AD builds. ---*/ #if (defined(CODI_REVERSE_TYPE) || defined(CODI_FORWARD_TYPE)) && defined(USE_SINGLE_PRECISION) #undef USE_SINGLE_PRECISION diff --git a/Common/include/linear_algebra/CMatrixVectorProduct.hpp b/Common/include/linear_algebra/CMatrixVectorProduct.hpp index a0cecaa63d76..4069ff2fd006 100644 --- a/Common/include/linear_algebra/CMatrixVectorProduct.hpp +++ b/Common/include/linear_algebra/CMatrixVectorProduct.hpp @@ -72,7 +72,6 @@ class CSysMatrixVectorProduct final : public CMatrixVectorProduct { const CSysMatrix& matrix; /*!< \brief pointer to matrix that defines the product. */ CGeometry* geometry; /*!< \brief geometry associated with the matrix. */ const CConfig* config; /*!< \brief config of the problem. */ - mutable bool matrix_uploaded = false; /*!< \brief Upload the matrix lazily on the first actual GPU matvec. */ public: /*! @@ -83,7 +82,17 @@ class CSysMatrixVectorProduct final : public CMatrixVectorProduct { */ inline CSysMatrixVectorProduct(const CSysMatrix& matrix_ref, CGeometry* geometry_ref, const CConfig* config_ref) - : matrix(matrix_ref), geometry(geometry_ref), config(config_ref) {} + : matrix(matrix_ref), geometry(geometry_ref), config(config_ref) { + /*--- The matrix does not change while this object lives, so it crosses the bus once, + * here. The vectors are uploaded by CSysSolve, see HandleTemporariesIn. ---*/ +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + if (config->GetCUDA()) { + SU2_DEVICE_REGION(matrix.HtDTransfer();) + } + } +#endif + } /*! * \note This class cannot be default constructed as that would leave us with invalid pointers. @@ -97,12 +106,19 @@ class CSysMatrixVectorProduct final : public CMatrixVectorProduct { */ inline void operator()(const CSysVector& u, CSysVector& v) const override { if (config->GetCUDA()) { -#ifdef HAVE_CUDA - if (!matrix_uploaded) { - matrix.HtDTransfer(); - matrix_uploaded = true; +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + BEGIN_SU2_DEVICE_REGION + matrix.GPUMatrixVectorProduct(u, v, geometry, config); + END_SU2_DEVICE_REGION + } else { + SU2_MPI::Error("GPU acceleration is not supported for AD scalar types.", CURRENT_FUNCTION); } - matrix.GPUMatrixVectorProduct(u, v, geometry, config); +#elif defined(HAVE_CUDA) + SU2_MPI::Error( + "\nError in launching Matrix-Vector Product Function\nENABLE_CUDA is set to YES\nThe GPU kernels are not " + "part of the AD libraries, use the primal build for GPU acceleration", + CURRENT_FUNCTION); #else SU2_MPI::Error( "\nError in launching Matrix-Vector Product Function\nENABLE_CUDA is set to YES\nPlease compile with CUDA " diff --git a/Common/include/linear_algebra/CPreconditioner.hpp b/Common/include/linear_algebra/CPreconditioner.hpp index 532e0b538388..d28c729c4f0f 100644 --- a/Common/include/linear_algebra/CPreconditioner.hpp +++ b/Common/include/linear_algebra/CPreconditioner.hpp @@ -37,6 +37,33 @@ /// \addtogroup SpLinSys /// @{ +/*! + * \brief Applies a preconditioner that only has a host implementation to vectors that live + * on the device: bring the input down, apply, put the result back. + * \note This is what keeps ILU, LU-SGS, Linelet and PaStiX usable on the GPU path. The + * transfers are issued by one thread with the team synchronized around them, the apply + * itself is the normal OpenMP parallel host code. + */ +template +inline void ApplyPreconditionerOnHost(const CSysVector& u, CSysVector& v, Apply&& apply) { +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + if (VecExpr::UseDeviceExpressions()) { + /*--- The host code must not see the device pointers of any expression it builds, so + * the switch is flipped for the duration of the apply. It is written inside the + * regions, by one thread, and published to the team by the trailing barrier. ---*/ + SU2_DEVICE_REGION(u.DtHTransfer(); VecExpr::SetUseDeviceExpressions(false);) + + apply(); + + SU2_DEVICE_REGION(VecExpr::SetUseDeviceExpressions(true); v.HtDTransfer();) + return; + } + } +#endif + apply(); +} + /*! * \class CPreconditioner * \brief Abstract base class for defining a preconditioning operation. @@ -77,6 +104,18 @@ class CPreconditioner { template CPreconditioner::~CPreconditioner() {} +/*! + * \class CIdentityPreconditioner + * \brief No-op preconditioner used when Krylov solvers run without preconditioning. + */ +template +class CIdentityPreconditioner final : public CPreconditioner { + public: + inline void operator()(const CSysVector& u, CSysVector& v) const override { v = u; } + + inline bool IsIdentity() const override { return true; } +}; + /*! * \class CJacobiPreconditioner * \brief Specialization of preconditioner that uses CSysMatrix class. @@ -160,7 +199,7 @@ class CILUPreconditioner final : public CPreconditioner { * \param[out] v - CSysVector that is the result of the preconditioning. */ inline void operator()(const CSysVector& u, CSysVector& v) const override { - sparse_matrix.ComputeILUPreconditioner(u, v, geometry, config); + ApplyPreconditionerOnHost(u, v, [&] { sparse_matrix.ComputeILUPreconditioner(u, v, geometry, config); }); } /*! @@ -206,7 +245,7 @@ class CLU_SGSPreconditioner final : public CPreconditioner { * \param[out] v - CSysVector that is the result of the preconditioning. */ inline void operator()(const CSysVector& u, CSysVector& v) const override { - sparse_matrix.ComputeLU_SGSPreconditioner(u, v, geometry, config); + ApplyPreconditionerOnHost(u, v, [&] { sparse_matrix.ComputeLU_SGSPreconditioner(u, v, geometry, config); }); } }; @@ -234,7 +273,7 @@ class CQuantizedLUSGSPreconditioner final : public CPreconditioner { CQuantizedLUSGSPreconditioner() = delete; inline void operator()(const CSysVector& u, CSysVector& v) const override { - sparse_matrix.ComputeLU_SGSPreconditioner(u, v, geometry, config); + ApplyPreconditionerOnHost(u, v, [&] { sparse_matrix.ComputeLU_SGSPreconditioner(u, v, geometry, config); }); } /*! \brief Quantize the diagonal blocks (off diagonals are quantized on the fly). */ @@ -278,7 +317,7 @@ class CLineletPreconditioner final : public CPreconditioner { * \param[out] v - CSysVector that is the result of the preconditioning. */ inline void operator()(const CSysVector& u, CSysVector& v) const override { - sparse_matrix.ComputeLineletPreconditioner(u, v, geometry, config); + ApplyPreconditionerOnHost(u, v, [&] { sparse_matrix.ComputeLineletPreconditioner(u, v, geometry, config); }); } /*! @@ -328,7 +367,7 @@ class CPastixPreconditioner final : public CPreconditioner { * \param[out] v - CSysVector that is the result of the preconditioning. */ inline void operator()(const CSysVector& u, CSysVector& v) const override { - sparse_matrix.ComputePastixPreconditioner(u, v, geometry, config); + ApplyPreconditionerOnHost(u, v, [&] { sparse_matrix.ComputePastixPreconditioner(u, v, geometry, config); }); } /*! @@ -363,6 +402,9 @@ CPreconditioner* CPreconditioner::Create(ENUM_LINEAR_SOL CPreconditioner* prec = nullptr; switch (kind) { + case IDENTITY: + prec = new CIdentityPreconditioner(); + break; case JACOBI: prec = new CJacobiPreconditioner(jacobian, geometry, config); break; diff --git a/Common/include/linear_algebra/CSysMatrix.hpp b/Common/include/linear_algebra/CSysMatrix.hpp index d31c48fb8a09..3eddf892a7bf 100644 --- a/Common/include/linear_algebra/CSysMatrix.hpp +++ b/Common/include/linear_algebra/CSysMatrix.hpp @@ -247,9 +247,10 @@ class CSysMatrix { unsigned long nnz_u = 0; /*!< \brief Number of U nonzeros. */ }; - LDU mat; /*!< \brief Host matrix (values owned via aligned_alloc; pattern from geometry). */ - LDU gpu; /*!< \brief Device matrix (all pointers to GPU memory). */ - LDU ilu; /*!< \brief ILU factorization, host (values owned; pattern from geometry). */ + LDU mat; /*!< \brief Host matrix (values owned via aligned_alloc; pattern from geometry). */ + LDU gpu; /*!< \brief Device matrix (all pointers to GPU memory). */ + LDU ilu; /*!< \brief ILU factorization, host (values owned; pattern from geometry). */ + ScalarType* d_invM = nullptr; /*!< \brief Device inverse diagonal blocks for the Jacobi preconditioner. */ /*--- Quantized off-diagonal storage (used when quantized_mode == true). ---*/ using QuantType = int8_t; @@ -1100,6 +1101,14 @@ class CSysMatrix { void ComputeJacobiPreconditioner(const CSysVector& vec, CSysVector& prod, CGeometry* geometry, const CConfig* config) const; + /*! + * \brief Apply the Jacobi preconditioner on the GPU/device side. + * \note This helper is intended as the implementation hook for GPU-resident Krylov solvers. + * The actual implementation belongs in CSysMatrixGPU.cu. + */ + void ComputeJacobiPreconditionerGPU(const CSysVector& vec, CSysVector& prod, + CGeometry* geometry, const CConfig* config) const; + /*! * \brief Build the ILU preconditioner. */ diff --git a/Common/include/linear_algebra/CSysSolve.hpp b/Common/include/linear_algebra/CSysSolve.hpp index 2ea3cbf7df30..1f9bc851b92e 100644 --- a/Common/include/linear_algebra/CSysSolve.hpp +++ b/Common/include/linear_algebra/CSysSolve.hpp @@ -240,13 +240,50 @@ class CSysSolve { */ void WriteWarning(ScalarType res_calc, ScalarType res_true, ScalarType tol) const; + /*! + * \brief Moves the linear system to the device, if the GPU path is in use. + * \note This and DownloadSolution are the only places where b and x cross the bus. The + * work vectors of the solvers never do, they are allocated on the device and read back + * only through the reductions. + */ + void UploadSystem(bool useCuda) const { +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + if (!useCuda) return; + BEGIN_SU2_DEVICE_REGION { + LinSysRes_ptr->HtDTransfer(); + LinSysSol_ptr->HtDTransfer(); + VecExpr::SetUseDeviceExpressions(true); + } + END_SU2_DEVICE_REGION + } +#endif + } + + /*! + * \brief Brings the solution back from the device and returns to host evaluation. + */ + void DownloadSolution(bool useCuda) const { +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + if (!useCuda) return; + BEGIN_SU2_DEVICE_REGION { + LinSysSol_ptr->DtHTransfer(); + VecExpr::SetUseDeviceExpressions(false); + } + END_SU2_DEVICE_REGION + } +#endif + } + /*! * \brief Used by Solve for compatibility between passive and active CSysVector. * \param[in] LinSysRes - Linear system residual * \param[in,out] LinSysSol - Linear system solution + * \param[in] useCuda - Whether to move the system to the device for the solve. */ template - void HandleTemporariesIn(const CSysVector& LinSysRes, CSysVector& LinSysSol) { + void HandleTemporariesIn(const CSysVector& LinSysRes, CSysVector& LinSysSol, bool useCuda) { SU2_ZONE_SCOPED if constexpr (std::is_same_v) { /*--- Same type specialization, temporary variables are not required. ---*/ @@ -255,6 +292,7 @@ class CSysSolve { LinSysSol_ptr = &LinSysSol; } END_SU2_OMP_SAFE_GLOBAL_ACCESS + UploadSystem(useCuda); } else { /*--- Copy data, the solution is also copied as it serves as initial condition. ---*/ LinSysRes_tmp.PassiveCopy(LinSysRes); @@ -266,16 +304,19 @@ class CSysSolve { LinSysSol_ptr = &LinSysSol_tmp; } END_SU2_OMP_SAFE_GLOBAL_ACCESS + UploadSystem(useCuda); } } /*! * \brief Used by Solve for compatibility between passive and active CSysVector. * \param[out] LinSysSol - Linear system solution + * \param[in] useCuda - Whether the system was solved on the device. */ template - void HandleTemporariesOut(CSysVector& LinSysSol) { + void HandleTemporariesOut(CSysVector& LinSysSol, bool useCuda) { SU2_ZONE_SCOPED + DownloadSolution(useCuda); if constexpr (std::is_same_v) { /*--- Same type specialization, temporary variables are not required. ---*/ BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { @@ -432,9 +473,9 @@ class CSysSolve { template > = 0> unsigned long Solve_b(MatrixType& Jacobian, const CSysVector& LinSysRes, CSysVector& LinSysSol, CGeometry* geometry, const CConfig* config, bool directCall = true) { - HandleTemporariesIn(LinSysRes, LinSysSol); + HandleTemporariesIn(LinSysRes, LinSysSol, false); auto iter = Solve_b(Jacobian, *LinSysRes_ptr, *LinSysSol_ptr, geometry, config, directCall); - HandleTemporariesOut(LinSysSol); + HandleTemporariesOut(LinSysSol, false); return iter; } diff --git a/Common/include/linear_algebra/CSysVector.hpp b/Common/include/linear_algebra/CSysVector.hpp index 1498b549bbb8..2ffbb9a79672 100644 --- a/Common/include/linear_algebra/CSysVector.hpp +++ b/Common/include/linear_algebra/CSysVector.hpp @@ -37,6 +37,35 @@ #include "vector_expressions.hpp" #include "../../include/CConfig.hpp" +#ifdef __CUDACC__ +#include "GPUComms.cuh" +#define SU2_CUDA_HOST_DEVICE __host__ __device__ +#else +#define SU2_CUDA_HOST_DEVICE +#endif + +template +class CSysVector; + +/*! + * \brief True for the plain floating-point scalar types the GPU vector kernels + * (CSysVectorGPU.cu) are instantiated for, in builds where those kernels + * exist at all. AD active types are never dispatched to the device: the + * tape/expression machinery those types pull in is not compatible with + * nvcc's device-code compilation, and device-resident autodiff is not + * supported by this GPU path. + * \note In an AD build this is false even for su2mixedfloat, because the .cu + * translation units are not linked into the AD libraries (see + * SU2_ENABLE_CUDA_KERNELS in code_config.hpp). + */ +#ifdef SU2_ENABLE_CUDA_KERNELS +template +inline constexpr bool su2_gpu_capable_v = std::is_floating_point_v; +#else +template +inline constexpr bool su2_gpu_capable_v = false; +#endif + /*! * \brief OpenMP worksharing construct used in CSysVector for loops. * \note The loop will only run in parallel if methods are called from a @@ -59,6 +88,106 @@ #define END_CSYSVEC_PARFOR #endif +/*! + * \brief Brackets device work so that it is issued by a single thread with the whole team + * synchronized before and after. + * \note The GPU is one shared resource and the device path does not use OpenMP worksharing. + * Issuing from one thread keeps kernel launches ordered on the default stream and, above + * all, stops part of a team from entering a worksharing construct that the rest skipped. + * Correctness relies on all threads reaching the same vector operations in the same order, + * which is the assumption the "nowait" clause on CSYSVEC_PARFOR already makes. These + * regions must not be nested; they are used by the operations of this class and by the + * matrix-vector product and preconditioner wrappers, and by nothing above those. + */ +#define SU2_DEVICE_REGION(...) SU2_OMP_SAFE_GLOBAL_ACCESS(__VA_ARGS__) +#define BEGIN_SU2_DEVICE_REGION BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS +#define END_SU2_DEVICE_REGION END_SU2_OMP_SAFE_GLOBAL_ACCESS + +namespace VecExpr { + +enum class DeviceAssignOp { Assign, Add, Subtract, Multiply, Divide }; + +/*! + * \brief Whether vector expressions are currently evaluated on the device. + * \note Defined in CSysVectorGPU.cu. This is a plain global, not per thread: every thread + * of a team has to agree on it or they would split over the worksharing constructs below. + * It is switched by CSysSolve at the same boundary that uploads and downloads the vectors + * (HandleTemporariesIn/Out), and nowhere else, so it does not change while a solve runs. + */ +#ifdef SU2_ENABLE_CUDA_KERNELS +bool UseDeviceExpressions(); +void SetUseDeviceExpressions(bool use); +#else +inline bool UseDeviceExpressions() { return false; } +inline void SetUseDeviceExpressions(bool) {} +#endif + +/*! + * \brief How a CSysVector is captured inside an expression: a bare pointer to whichever + * storage the expression is going to be evaluated from. + * \note Capturing by value (rather than a reference to the vector) is what makes an + * arbitrary expression tree trivially copyable, and therefore passable by value to the + * assignment kernel. The choice of storage is fixed when the expression is built, which is + * sound because there is no fallback: while UseDeviceExpressions() holds, every expression + * is evaluated by a kernel. + */ +template +class CVectorView : public CVecExpr, Scalar> { + private: + const Scalar* data = nullptr; + + public: + static constexpr bool StoreAsRef = false; + + CVectorView(const CSysVector& vector); + + SU2_CUDA_HOST_DEVICE FORCEINLINE const Scalar& operator[](size_t i) const { return data[i]; } +}; + +template +struct store_type> { + using type = CVectorView; +}; + +template +struct store_type> { + using type = CVectorView; +}; + +template +void AssignDeviceExpression(Scalar* data, unsigned long size, const CVecExpr& expr); + +#ifdef __CUDACC__ +template +__global__ void DeviceAssignKernel(Scalar* data, unsigned long size, T expr) { + const unsigned long i = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; + if (i >= size) return; + + if constexpr (Op == DeviceAssignOp::Assign) { + data[i] = expr[i]; + } else if constexpr (Op == DeviceAssignOp::Add) { + data[i] += expr[i]; + } else if constexpr (Op == DeviceAssignOp::Subtract) { + data[i] -= expr[i]; + } else if constexpr (Op == DeviceAssignOp::Multiply) { + data[i] *= expr[i]; + } else { + data[i] /= expr[i]; + } +} + +template +inline void AssignDeviceExpression(Scalar* data, unsigned long size, const CVecExpr& expr) { + if (size == 0) return; + constexpr unsigned block_size = 256; + const auto grid_size = static_cast((size + block_size - 1) / block_size); + DeviceAssignKernel<<>>(data, size, expr.derived()); + gpuErrChk(cudaPeekAtLastError()); +} +#endif + +} // namespace VecExpr + /*! * \class CSysVector * \ingroup SpLinSys @@ -110,6 +239,37 @@ class CSysVector : public VecExpr::CVecExpr, ScalarType> } } + /*! + * \brief Evaluates an expression into the device storage of this vector. + * \note The kernel has to be instantiated for the expression type in CSysVectorGPU.cu, + * a shape that is not in that list is an undefined symbol at link time. + */ + template + CSysVector& AssignDevice(const VecExpr::CVecExpr& expr) { +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + BEGIN_SU2_DEVICE_REGION { + VecExpr::store_t stored_expr(expr.derived()); + VecExpr::AssignDeviceExpression(d_vec_val, nElm, stored_expr); + } + END_SU2_DEVICE_REGION + } +#endif + return *this; + } + + template + CSysVector& AssignDevice(ScalarType val) { +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + BEGIN_SU2_DEVICE_REGION + VecExpr::AssignDeviceExpression(d_vec_val, nElm, VecExpr::Bcast(val)); + END_SU2_DEVICE_REGION + } +#endif + return *this; + } + public: static constexpr bool StoreAsRef = true; /*! \brief Required by CVecExpr. */ @@ -235,16 +395,31 @@ class CSysVector : public VecExpr::CVecExpr, ScalarType> void DtHTransfer(bool trigger = true) const; /*! - * \brief Sets all the elements of the GPU vector to a certain value - * \param[in] trigger - boolean value that decides whether to conduct the transfer or not. True by default. + * \brief Dot product between this vector and another vector on the device. + * \note Explicit GPU helper for solver-side reductions. + * \param[in] other - Input vector. + * \return Dot product result. + */ + ScalarType GPUDot(const CSysVector& other) const; + + /*! + * \brief L2 norm of this vector on the device. + * \note Explicit GPU helper for solver-side reductions. + * \return L2 norm result. */ - void GPUSetVal(ScalarType val, bool trigger = true) const; + ScalarType GPUNorm() const; /*! * \brief return device pointer that points to the CSysVector values in GPU memory */ inline ScalarType* GetDevicePointer() const { return d_vec_val; } + /*! + * \brief return host pointer that points to the CSysVector values, counterpart of + * GetDevicePointer + */ + inline const ScalarType* GetHostPointer() const { return vec_val; } + /*! * \brief return the number of local elements in the CSysVector */ @@ -301,6 +476,11 @@ class CSysVector : public VecExpr::CVecExpr, ScalarType> * \param[in] other - Another vector. */ CSysVector& operator=(const CSysVector& other) { +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + if (VecExpr::UseDeviceExpressions()) return AssignDevice(other); + } +#endif CSYSVEC_PARFOR for (auto i = 0ul; i < nElm; ++i) vec_val[i] = other.vec_val[i]; END_CSYSVEC_PARFOR @@ -311,25 +491,31 @@ class CSysVector : public VecExpr::CVecExpr, ScalarType> * \brief Compound assignement operations with scalars and expressions. * \param[in] val/expr - Scalar value or expression. */ -#define MAKE_COMPOUND(OP) \ - CSysVector& operator OP(ScalarType val) { \ - CSYSVEC_PARFOR \ - for (auto i = 0ul; i < nElm; ++i) vec_val[i] OP val; \ - END_CSYSVEC_PARFOR \ - return *this; \ - } \ - template \ - CSysVector& operator OP(const VecExpr::CVecExpr& expr) { \ - CSYSVEC_PARFOR \ - for (auto i = 0ul; i < nElm; ++i) vec_val[i] OP expr.derived()[i]; \ - END_CSYSVEC_PARFOR \ - return *this; \ +#define MAKE_COMPOUND(OP, ASSIGN_OP) \ + CSysVector& operator OP(ScalarType val) { \ + if constexpr (su2_gpu_capable_v) { \ + if (VecExpr::UseDeviceExpressions()) return AssignDevice(val); \ + } \ + CSYSVEC_PARFOR \ + for (auto i = 0ul; i < nElm; ++i) vec_val[i] OP val; \ + END_CSYSVEC_PARFOR \ + return *this; \ + } \ + template \ + CSysVector& operator OP(const VecExpr::CVecExpr& expr) { \ + if constexpr (su2_gpu_capable_v) { \ + if (VecExpr::UseDeviceExpressions()) return AssignDevice(expr); \ + } \ + CSYSVEC_PARFOR \ + for (auto i = 0ul; i < nElm; ++i) vec_val[i] OP expr.derived()[i]; \ + END_CSYSVEC_PARFOR \ + return *this; \ } - MAKE_COMPOUND(=) - MAKE_COMPOUND(+=) - MAKE_COMPOUND(-=) - MAKE_COMPOUND(*=) - MAKE_COMPOUND(/=) + MAKE_COMPOUND(=, VecExpr::DeviceAssignOp::Assign) + MAKE_COMPOUND(+=, VecExpr::DeviceAssignOp::Add) + MAKE_COMPOUND(-=, VecExpr::DeviceAssignOp::Subtract) + MAKE_COMPOUND(*=, VecExpr::DeviceAssignOp::Multiply) + MAKE_COMPOUND(/=, VecExpr::DeviceAssignOp::Divide) #undef MAKE_COMPOUND /*! @@ -344,6 +530,21 @@ class CSysVector : public VecExpr::CVecExpr, ScalarType> */ template ScalarType dot(const VecExpr::CVecExpr& expr) const { +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + using DeviceExpr = std::remove_cv_t>; + static_assert(std::is_same_v, + "On the device the dot product is a cuBLAS call, so it only takes vectors. " + "Assign the expression to a vector first."); + if (VecExpr::UseDeviceExpressions()) { + /*--- GPUDot reduces over MPI, which has to happen once for the team, so the result + * is published through the same scratch slot the host reduction below uses. ---*/ + SU2_DEVICE_REGION(dot_scratch[0] = GPUDot(expr.derived());) + return dot_scratch[0]; + } + } +#endif + /*--- All threads get the same "view" of the vectors. ---*/ SU2_OMP_BARRIER @@ -502,5 +703,14 @@ class CSysVector : public VecExpr::CVecExpr, ScalarType> } }; +namespace VecExpr { + +template +CVectorView::CVectorView(const CSysVector& vector) + : data(UseDeviceExpressions() ? vector.GetDevicePointer() : vector.GetHostPointer()) {} + +} // namespace VecExpr + #undef CSYSVEC_PARFOR #undef END_CSYSVEC_PARFOR +#undef SU2_CUDA_HOST_DEVICE diff --git a/Common/include/linear_algebra/GPUComms.cuh b/Common/include/linear_algebra/GPUComms.cuh index 138543818771..eb727477f214 100644 --- a/Common/include/linear_algebra/GPUComms.cuh +++ b/Common/include/linear_algebra/GPUComms.cuh @@ -25,6 +25,11 @@ * License along with SU2. If not, see . */ +#pragma once + +#ifndef SU2_COMMON_LINEAR_ALGEBRA_GPUCOMMS_CUH +#define SU2_COMMON_LINEAR_ALGEBRA_GPUCOMMS_CUH + #include #include @@ -51,3 +56,5 @@ inline void gpuAssert(cudaError_t code, const char *file, int line, bool abort=t } #define gpuErrChk(ans) { gpuAssert((ans), __FILE__, __LINE__); } + +#endif // SU2_COMMON_LINEAR_ALGEBRA_GPUCOMMS_CUH diff --git a/Common/include/linear_algebra/vector_expressions.hpp b/Common/include/linear_algebra/vector_expressions.hpp index a0d0ce289013..f9b941a0bdb2 100644 --- a/Common/include/linear_algebra/vector_expressions.hpp +++ b/Common/include/linear_algebra/vector_expressions.hpp @@ -39,6 +39,12 @@ namespace VecExpr { /// \addtogroup VecExpr /// @{ +#ifdef __CUDACC__ +#define SU2_CUDA_HOST_DEVICE __host__ __device__ +#else +#define SU2_CUDA_HOST_DEVICE +#endif + /*! * \brief Base vector expression class. * \ingroup BLAS @@ -59,7 +65,7 @@ class CVecExpr { /*! * \brief Cast the expression to Derived, usually to allow evaluation via operator[]. */ - FORCEINLINE const Derived& derived() const { return static_cast(*this); } + SU2_CUDA_HOST_DEVICE FORCEINLINE const Derived& derived() const { return static_cast(*this); } // Allowed from C++14, allows nested expression propagation without // manually calling derived() on the expression being evaluated. @@ -76,8 +82,8 @@ class Bcast : public CVecExpr, Scalar> { public: static constexpr bool StoreAsRef = false; - FORCEINLINE Bcast(const Scalar& x_) : x(x_) {} - FORCEINLINE const Scalar& operator[](size_t) const { return x; } + SU2_CUDA_HOST_DEVICE FORCEINLINE Bcast(const Scalar& x_) : x(x_) {} + SU2_CUDA_HOST_DEVICE FORCEINLINE const Scalar& operator[](size_t) const { return x; } }; /*! @@ -101,7 +107,11 @@ struct add_lref_if { using type = remove_reference_t&; }; template -using store_t = typename add_lref_if::type; +struct store_type { + using type = typename add_lref_if::type; +}; +template +using store_t = typename store_type::type; /*--- Namespace from which the math function implementations come. ---*/ @@ -120,19 +130,19 @@ namespace math = ::std; /*--- Macro to create expression classes (EXPR) and overloads (FUN) for unary * functions, based on their coefficient-wise implementation (IMPL). ---*/ -#define MAKE_UNARY_FUN(FUN, EXPR, IMPL) \ - /*!--- Expression class. ---*/ \ - template \ - class EXPR : public CVecExpr, Scalar> { \ - store_t u; \ - \ - public: \ - static constexpr bool StoreAsRef = false; \ - FORCEINLINE EXPR(const U& u_) : u(u_) {} \ - FORCEINLINE auto operator[](size_t i) const RETURNS(IMPL(u[i])) \ - }; \ - /*!--- Function overload, returns an expression object. ---*/ \ - template \ +#define MAKE_UNARY_FUN(FUN, EXPR, IMPL) \ + /*!--- Expression class. ---*/ \ + template \ + class EXPR : public CVecExpr, Scalar> { \ + store_t u; \ + \ + public: \ + static constexpr bool StoreAsRef = false; \ + FORCEINLINE EXPR(const U& u_) : u(u_) {} \ + SU2_CUDA_HOST_DEVICE FORCEINLINE auto operator[](size_t i) const RETURNS(IMPL(u[i])) \ + }; \ + /*!--- Function overload, returns an expression object. ---*/ \ + template \ FORCEINLINE auto FUN(const CVecExpr& u) RETURNS(EXPR(u.derived())) #define sign_impl(x) Scalar(1 - 2 * (x < 0)) @@ -158,7 +168,7 @@ MAKE_UNARY_FUN(sign, sign_, sign_impl) public: \ static constexpr bool StoreAsRef = false; \ FORCEINLINE EXPR(const U& u_, const V& v_) : u(u_), v(v_) {} \ - FORCEINLINE auto operator[](size_t i) const RETURNS(IMPL(u[i], v[i])) \ + SU2_CUDA_HOST_DEVICE FORCEINLINE auto operator[](size_t i) const RETURNS(IMPL(u[i], v[i])) \ }; \ /*!--- Vector with vector function overload. ---*/ \ template \ @@ -241,4 +251,5 @@ MAKE_BINARY_FUN(operator>, gt_, gt_impl) #undef MAKE_BINARY_FUN /// @} +#undef SU2_CUDA_HOST_DEVICE } // namespace VecExpr diff --git a/Common/include/option_structure.hpp b/Common/include/option_structure.hpp index 2bc09b47adad..2dcd241f9ded 100644 --- a/Common/include/option_structure.hpp +++ b/Common/include/option_structure.hpp @@ -2523,6 +2523,7 @@ static const MapType Sens_Smoothing_Map = { * \brief Types of preconditioners for the linear solver */ enum ENUM_LINEAR_SOLVER_PREC { + IDENTITY, /*!< \brief No preconditioner. */ JACOBI, /*!< \brief Jacobi preconditioner. */ LU_SGS, /*!< \brief LU SGS preconditioner. */ LINELET, /*!< \brief Line implicit preconditioner. */ @@ -2533,6 +2534,7 @@ enum ENUM_LINEAR_SOLVER_PREC { PASTIX_LDLT_P, /*!< \brief PaStiX LDLT as preconditioner. */ }; static const MapType Linear_Solver_Prec_Map = { + MakePair("NONE", IDENTITY) MakePair("JACOBI", JACOBI) MakePair("LU_SGS", LU_SGS) MakePair("LINELET", LINELET) diff --git a/Common/include/parallelization/omp_structure.hpp b/Common/include/parallelization/omp_structure.hpp index af581bd1d76c..10bf9f7bca02 100644 --- a/Common/include/parallelization/omp_structure.hpp +++ b/Common/include/parallelization/omp_structure.hpp @@ -283,7 +283,10 @@ inline void atomicAdd(T rhs, T& lhs) { #define ATOMIC_COMPARE_FALLBACK /*--- Atomic max, shared = max(shared, local). ---*/ -#ifdef _OPENMP +/*--- nvcc's host pass drops the clause from "#pragma omp atomic compare", which the host + * compiler then rejects. The .cu sources do not use these functions, so they simply get + * the critical section fallback below. ---*/ +#if defined(_OPENMP) && !defined(__CUDACC__) #if _OPENMP >= ATOMIC_COMPARE_SINCE /*--- Atomic min/max are supported for arithmetic types. ---*/ template ::value> = 0> @@ -305,7 +308,7 @@ inline void atomicMax(const T& local, T& shared) { } /*--- Atomic min, shared = min(shared, local). ---*/ -#ifdef _OPENMP +#if defined(_OPENMP) && !defined(__CUDACC__) #if _OPENMP >= ATOMIC_COMPARE_SINCE template ::value> = 0> inline void atomicMin(const T& local, T& shared) { diff --git a/Common/src/linear_algebra/CSysMatrix.cpp b/Common/src/linear_algebra/CSysMatrix.cpp index fa47816f60a0..a0013e243002 100644 --- a/Common/src/linear_algebra/CSysMatrix.cpp +++ b/Common/src/linear_algebra/CSysMatrix.cpp @@ -93,6 +93,7 @@ CSysMatrix::CSysMatrix() : rank(SU2_MPI::GetRank()), size(SU2_MPI::G q_blocks_d = nullptr; invM = nullptr; + d_invM = nullptr; #ifdef USE_MKL MatrixMatrixProductJitter = nullptr; @@ -129,6 +130,7 @@ CSysMatrix::~CSysMatrix() { GPUMemoryAllocation::gpu_free(gpu.col_ind_l); GPUMemoryAllocation::gpu_free(gpu.row_ptr_u); GPUMemoryAllocation::gpu_free(gpu.col_ind_u); + GPUMemoryAllocation::gpu_free(d_invM); } #ifdef USE_MKL @@ -282,6 +284,10 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi if (diag_needed) allocAndInit(invM, nPointDomain * nVar * nEqn); + if (useCuda && diag_needed) { + d_invM = GPUMemoryAllocation::gpu_alloc(nPointDomain * nVar * nEqn * sizeof(ScalarType)); + } + /*--- Thread parallel initialization. ---*/ int num_threads = omp_get_max_threads(); @@ -804,11 +810,29 @@ void CSysMatrix::MatrixVectorProduct(const CSysVector& v template void CSysMatrix::BuildJacobiPreconditioner() { SU2_ZONE_SCOPED + /*--- Build Jacobi preconditioner (M = D), compute and store the inverses of the diagonal blocks. ---*/ SU2_OMP_FOR_DYN(omp_heavy_size) for (unsigned long iPoint = 0; iPoint < nPointDomain; iPoint++) InverseDiagonalBlock(iPoint, &(invM[iPoint * nVar * nVar])); END_SU2_OMP_FOR + + if (useCuda) { +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + BEGIN_SU2_DEVICE_REGION + gpuErrChk(cudaMemcpy(d_invM, invM, nPointDomain * nVar * nVar * sizeof(ScalarType), cudaMemcpyHostToDevice)); + END_SU2_DEVICE_REGION + } else { + SU2_MPI::Error("GPU acceleration is not supported for AD scalar types.", CURRENT_FUNCTION); + } +#else + SU2_MPI::Error( + "\nError in building Jacobi preconditioner\nENABLE_CUDA is set to YES\nPlease compile with CUDA options " + "enabled in Meson to access GPU Functions", + CURRENT_FUNCTION); +#endif + } } template @@ -816,6 +840,23 @@ void CSysMatrix::ComputeJacobiPreconditioner(const CSysVector& prod, CGeometry* geometry, const CConfig* config) const { SU2_ZONE_SCOPED + + if (useCuda) { +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + SU2_DEVICE_REGION(ComputeJacobiPreconditionerGPU(vec, prod, geometry, config);) + return; + } else { + SU2_MPI::Error("GPU acceleration is not supported for AD scalar types.", CURRENT_FUNCTION); + } +#else + SU2_MPI::Error( + "\nError in applying Jacobi preconditioner\nENABLE_CUDA is set to YES\nPlease compile with CUDA options " + "enabled in Meson to access GPU Functions", + CURRENT_FUNCTION); +#endif + } + /*--- Apply Jacobi preconditioner, y = D^{-1} * x, the inverse of the diagonal is already known. ---*/ SU2_OMP_BARRIER SU2_OMP_FOR_DYN(omp_heavy_size) diff --git a/Common/src/linear_algebra/CSysMatrixGPU.cu b/Common/src/linear_algebra/CSysMatrixGPU.cu index 1ebba30097c2..381379a0d812 100644 --- a/Common/src/linear_algebra/CSysMatrixGPU.cu +++ b/Common/src/linear_algebra/CSysMatrixGPU.cu @@ -34,12 +34,12 @@ */ template __global__ void BlockLDU_SpMV_kernel(unsigned long nRows, unsigned long nVar, - const unsigned long* __restrict__ row_ptr_l, - const unsigned long* __restrict__ col_ind_l, + const su2uint* __restrict__ row_ptr_l, + const su2uint* __restrict__ col_ind_l, const ScalarType* __restrict__ mat_l, const ScalarType* __restrict__ mat_d, - const unsigned long* __restrict__ row_ptr_u, - const unsigned long* __restrict__ col_ind_u, + const su2uint* __restrict__ row_ptr_u, + const su2uint* __restrict__ col_ind_u, const ScalarType* __restrict__ mat_u, const ScalarType* __restrict__ x, ScalarType* __restrict__ y) { const unsigned long iRow = blockIdx.x; @@ -84,7 +84,6 @@ void CSysMatrix::GPUMatrixVectorProduct(const CSysVector ScalarType* d_vec = vec.GetDevicePointer(); ScalarType* d_prod = prod.GetDevicePointer(); - vec.HtDTransfer(); dim3 blockDim(static_cast(nVar), 1, 1); dim3 gridDim(static_cast(nPointDomain), 1, 1); @@ -92,8 +91,15 @@ void CSysMatrix::GPUMatrixVectorProduct(const CSysVector nPointDomain, nVar, gpu.row_ptr_l, gpu.col_ind_l, gpu.l, gpu.d, gpu.row_ptr_u, gpu.col_ind_u, gpu.u, d_vec, d_prod); gpuErrChk(cudaGetLastError()); - - prod.DtHTransfer(); } +template void CSysMatrix::HtDTransfer(bool trigger) const; +template void CSysMatrix::GPUMatrixVectorProduct(const CSysVector& vec, + CSysVector& prod, CGeometry* geometry, + const CConfig* config) const; -template class CSysMatrix; //This is a temporary fix for invalid instantiations due to separating the member function from the header file the class is defined in. Will try to rectify it in coming commits. +#if defined(USE_MIXED_PRECISION) && !defined(USE_SINGLE_PRECISION) +template void CSysMatrix::HtDTransfer(bool trigger) const; +template void CSysMatrix::GPUMatrixVectorProduct(const CSysVector& vec, + CSysVector& prod, CGeometry* geometry, + const CConfig* config) const; +#endif diff --git a/Common/src/linear_algebra/CSysPreconditionerGPU.cu b/Common/src/linear_algebra/CSysPreconditionerGPU.cu new file mode 100644 index 000000000000..794726fbce35 --- /dev/null +++ b/Common/src/linear_algebra/CSysPreconditionerGPU.cu @@ -0,0 +1,84 @@ +/*! + * \file CSysPreconditionerGPU.cu + * \brief CUDA/GPU skeleton implementations for matrix-based preconditioners. + * \author Jesse Li + * \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 "../../include/linear_algebra/CSysMatrix.inl" +#include "../../include/linear_algebra/GPUComms.cuh" + +namespace { + +template +__global__ void ApplyJacobiPreconditionerKernel(const ScalarType* invM, const ScalarType* vec, ScalarType* prod, + unsigned long nPointDomain, unsigned long nVar) { + const auto iPoint = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; + if (iPoint >= nPointDomain) return; + + const auto block = &invM[iPoint * nVar * nVar]; + const auto rhs = &vec[iPoint * nVar]; + auto out = &prod[iPoint * nVar]; + + for (auto iVar = 0ul; iVar < nVar; ++iVar) { + auto sum = ScalarType(0); + for (auto jVar = 0ul; jVar < nVar; ++jVar) { + sum += block[iVar * nVar + jVar] * rhs[jVar]; + } + out[iVar] = sum; + } +} + +} // namespace + +template +void CSysMatrix::ComputeJacobiPreconditionerGPU(const CSysVector& vec, + CSysVector& prod, CGeometry* geometry, + const CConfig* config) const { + (void)geometry; + (void)config; + + SU2_ZONE_SCOPED + + if (d_invM == nullptr) { + SU2_MPI::Error("CUDA Jacobi preconditioner used before BuildJacobiPreconditionerGPU.", CURRENT_FUNCTION); + } + + constexpr unsigned threadsPerBlock = 128; + const auto blocks = static_cast((nPointDomain + threadsPerBlock - 1) / threadsPerBlock); + ApplyJacobiPreconditionerKernel<<>>(d_invM, vec.GetDevicePointer(), prod.GetDevicePointer(), + nPointDomain, nVar); + gpuErrChk(cudaPeekAtLastError()); +} + +template void CSysMatrix::ComputeJacobiPreconditionerGPU(const CSysVector& vec, + CSysVector& prod, + CGeometry* geometry, + const CConfig* config) const; + +#if defined(USE_MIXED_PRECISION) && !defined(USE_SINGLE_PRECISION) +template void CSysMatrix::ComputeJacobiPreconditionerGPU(const CSysVector& vec, + CSysVector& prod, + CGeometry* geometry, + const CConfig* config) const; +#endif diff --git a/Common/src/linear_algebra/CSysSolve.cpp b/Common/src/linear_algebra/CSysSolve.cpp index a50ed0596257..dcd88c452c5f 100644 --- a/Common/src/linear_algebra/CSysSolve.cpp +++ b/Common/src/linear_algebra/CSysSolve.cpp @@ -427,7 +427,7 @@ unsigned long CSysSolve::FGMRES_LinSolver(const CSysVector 1; + const bool nestedParallel = !omp_in_parallel() && omp_get_max_threads() > 1 && !VecExpr::UseDeviceExpressions(); /*--- Check the subspace size ---*/ @@ -664,7 +664,7 @@ unsigned long CSysSolve::FGCRODR_LinSolverImpl(const CSysVector 1; + const bool nestedParallel = !omp_in_parallel() && omp_get_max_threads() > 1 && !VecExpr::UseDeviceExpressions(); /*--- Check the subspace size. ---*/ @@ -1464,7 +1464,7 @@ unsigned long CSysSolve::Solve(CSysMatrix& Jacobian, con auto externalFunction = [&]() { /*--- Create matrix-vector product, preconditioner, and solve the linear system ---*/ - HandleTemporariesIn(LinSysRes, LinSysSol); + HandleTemporariesIn(LinSysRes, LinSysSol, config->GetCUDA()); auto mat_vec = CSysMatrixVectorProduct(Jacobian, geometry, config); @@ -1539,7 +1539,7 @@ unsigned long CSysSolve::Solve(CSysMatrix& Jacobian, con } END_SU2_OMP_MASTER - HandleTemporariesOut(LinSysSol); + HandleTemporariesOut(LinSysSol, config->GetCUDA()); delete normal_prec; delete nested_prec; diff --git a/Common/src/linear_algebra/CSysVector.cpp b/Common/src/linear_algebra/CSysVector.cpp index f7df34633a0e..54e47157d55e 100644 --- a/Common/src/linear_algebra/CSysVector.cpp +++ b/Common/src/linear_algebra/CSysVector.cpp @@ -52,6 +52,9 @@ void CSysVector::Initialize(unsigned long numBlk, unsigned long numB if (vec_val == nullptr) vec_val = MemoryAllocation::aligned_alloc(64, nElm * sizeof(ScalarType)); + /*--- Device storage mirrors the host allocation; free first so that re-initializing a + * vector does not leak it. ---*/ + GPUMemoryAllocation::gpu_free(d_vec_val); d_vec_val = GPUMemoryAllocation::gpu_alloc(nElm * sizeof(ScalarType)); #ifdef HAVE_OMP @@ -78,6 +81,23 @@ const su2matrix& CSysVector::multiDot(const std::vector< if (n == 0 || m == 0) return shared; +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + if (VecExpr::UseDeviceExpressions()) { + BEGIN_SU2_DEVICE_REGION { + shared.resize(n, m); + for (size_t i = 0; i < n; ++i) { + for (size_t j = 0; j < m; ++j) { + shared(i, j) = V[i0 + i].GPUDot(W[j]); + } + } + } + END_SU2_DEVICE_REGION + return shared; + } + } +#endif + SU2_OMP_BARRIER const size_t size = V[0].nElmDomain; diff --git a/Common/src/linear_algebra/CSysVectorGPU.cu b/Common/src/linear_algebra/CSysVectorGPU.cu index 94ec17bb88fa..2be1215a7bd0 100644 --- a/Common/src/linear_algebra/CSysVectorGPU.cu +++ b/Common/src/linear_algebra/CSysVectorGPU.cu @@ -27,23 +27,192 @@ #include "../../include/linear_algebra/CSysVector.hpp" #include "../../include/linear_algebra/GPUComms.cuh" +#include +#include +#include +#include -template -void CSysVector::HtDTransfer(bool trigger) const -{ - if(trigger) gpuErrChk(cudaMemcpy((void*)(d_vec_val), (void*)&vec_val[0], (sizeof(ScalarType)*nElm), cudaMemcpyHostToDevice)); +namespace { + +/*--- cuBLAS handle for the reductions. Created on first use and kept for the lifetime of + * the program, matching the fact that CUDA is either on or off for the whole run. ---*/ +cublasHandle_t solver_blas_handle = nullptr; + +cublasHandle_t GetBlasHandle() { + if (solver_blas_handle == nullptr) { + if (cublasCreate(&solver_blas_handle) != CUBLAS_STATUS_SUCCESS) { + SU2_MPI::Error("cuBLAS handle creation failed for the GPU linear algebra.", CURRENT_FUNCTION); + } + } + return solver_blas_handle; } -template -void CSysVector::DtHTransfer(bool trigger) const -{ - if(trigger) gpuErrChk(cudaMemcpy((void*)(&vec_val[0]), (void*)d_vec_val, (sizeof(ScalarType)*nElm), cudaMemcpyDeviceToHost)); +} // namespace + +namespace VecExpr { + +namespace { +/*--- Deliberately not thread local: every thread of an OpenMP team has to agree on this, + * otherwise the team splits over the worksharing constructs in CSysVector. It is only + * written by CSysSolve, outside any parallel region over the linear system. ---*/ +bool use_device_expressions = false; +} // namespace + +bool UseDeviceExpressions() { return use_device_expressions; } + +void SetUseDeviceExpressions(bool use) { use_device_expressions = use; } + +} // namespace VecExpr + +template +void CSysVector::HtDTransfer(bool trigger) const { + if (trigger) + gpuErrChk(cudaMemcpy((void*)(d_vec_val), (void*)&vec_val[0], (sizeof(ScalarType) * nElm), cudaMemcpyHostToDevice)); +} + +template +void CSysVector::DtHTransfer(bool trigger) const { + if (trigger) + gpuErrChk(cudaMemcpy((void*)(&vec_val[0]), (void*)d_vec_val, (sizeof(ScalarType) * nElm), cudaMemcpyDeviceToHost)); +} + +template +ScalarType CSysVector::GPUDot(const CSysVector& other) const { + /*--- Both operands are already on the device, the caller owns the transfers. This + * reduces over MPI, so it must be called by a single thread (see SU2_DEVICE_REGION). ---*/ + cublasHandle_t handle = GetBlasHandle(); + cublasStatus_t status = CUBLAS_STATUS_SUCCESS; + + ScalarType local_dot = ScalarType(0); + + if constexpr (std::is_same_v) { + status = cublasSdot(handle, static_cast(nElmDomain), GetDevicePointer(), 1, other.GetDevicePointer(), 1, + &local_dot); + } else if constexpr (std::is_same_v) { + status = cublasDdot(handle, static_cast(nElmDomain), GetDevicePointer(), 1, other.GetDevicePointer(), 1, + &local_dot); + } else { + SU2_MPI::Error("Unsupported ScalarType in CSysVector::GPUDot.", CURRENT_FUNCTION); + return ScalarType(0); + } + + if (status != CUBLAS_STATUS_SUCCESS) { + SU2_MPI::Error("cuBLAS dot failed in CSysVector::GPUDot.", CURRENT_FUNCTION); + return ScalarType(0); + } + + ScalarType global_dot = ScalarType(0); + const auto mpi_type = (sizeof(ScalarType) < sizeof(double)) ? MPI_FLOAT : MPI_DOUBLE; + SelectMPIWrapper::W::Allreduce(&local_dot, &global_dot, 1, mpi_type, MPI_SUM, SU2_MPI::GetComm()); + + return global_dot; } -template -void CSysVector::GPUSetVal(ScalarType val, bool trigger) const -{ - if(trigger) gpuErrChk(cudaMemset((void*)(d_vec_val), val, (sizeof(ScalarType)*nElm))); +template +ScalarType CSysVector::GPUNorm() const { + return sqrt(GPUDot(*this)); } -template class CSysVector; //This is a temporary fix for invalid instantiations due to separating the member function from the header file the class is defined in. Will try to rectify it in coming commits. +/*--- Every expression the solvers assign to a CSysVector needs its assignment kernel + * instantiated here; the host compiler cannot emit one. A shape that is missing shows up + * as an undefined reference to VecExpr::AssignDeviceExpression at link time, and is fixed + * by adding a line to DEVICE_EXPRESSION_SHAPES below. The aliases use CSysVector (not + * CVectorView) because that is how the operator overloads name their operands; store_t + * turns it into a view when the node is built. ---*/ +namespace { + +template +using Vec = CSysVector; +template +using Sca = VecExpr::Bcast; + +/*--- Leaves and the shapes of the FGMRES/GMRES basis updates. ---*/ +template +using DeviceBcast = Sca; +template +using DeviceView = VecExpr::CVectorView; +template +using DeviceNeg = VecExpr::minus_, S>; + +/*--- vector * scalar and scalar * vector are distinct types, both are used. ---*/ +template +using DeviceScale = VecExpr::mul_, Sca, S>; +template +using DeviceLScale = VecExpr::mul_, Vec, S>; +template +using DeviceDivScale = VecExpr::div_, Sca, S>; + +/*--- Linear combinations, CSysSolve unrolls them up to four terms. ---*/ +template +using DeviceScale2 = VecExpr::add_, DeviceScale, S>; +template +using DeviceScale3 = VecExpr::add_, DeviceScale, S>; +template +using DeviceScale4 = VecExpr::add_, DeviceScale, S>; + +/*--- r = b - A_x, in CG, BCGSTAB, Smoother and FGCRODR. ---*/ +template +using DeviceSub = VecExpr::sub_, Vec, S>; + +/*--- p = beta * p + z, in CG. ---*/ +template +using DeviceLScalePlus = VecExpr::add_, Vec, S>; + +/*--- p = beta * (p - omega * v) + r, in BCGSTAB. ---*/ +template +using DeviceSubLScale = VecExpr::sub_, DeviceLScale, S>; +template +using DeviceLScaleSub = VecExpr::mul_, DeviceSubLScale, S>; +template +using DeviceBcgsDir = VecExpr::add_, Vec, S>; + +} // namespace + +#define DEVICE_EXPRESSION_SHAPES(SCALAR) \ + INSTANTIATE_DEVICE_ASSIGN_EXPR(SCALAR, DeviceBcast); \ + INSTANTIATE_DEVICE_ASSIGN_EXPR(SCALAR, DeviceView); \ + INSTANTIATE_DEVICE_ASSIGN_EXPR(SCALAR, DeviceNeg); \ + INSTANTIATE_DEVICE_ASSIGN_EXPR(SCALAR, DeviceScale); \ + INSTANTIATE_DEVICE_ASSIGN_EXPR(SCALAR, DeviceLScale); \ + INSTANTIATE_DEVICE_ASSIGN_EXPR(SCALAR, DeviceDivScale); \ + INSTANTIATE_DEVICE_ASSIGN_EXPR(SCALAR, DeviceScale2); \ + INSTANTIATE_DEVICE_ASSIGN_EXPR(SCALAR, DeviceScale3); \ + INSTANTIATE_DEVICE_ASSIGN_EXPR(SCALAR, DeviceScale4); \ + INSTANTIATE_DEVICE_ASSIGN_EXPR(SCALAR, DeviceSub); \ + INSTANTIATE_DEVICE_ASSIGN_EXPR(SCALAR, DeviceLScalePlus); \ + INSTANTIATE_DEVICE_ASSIGN_EXPR(SCALAR, DeviceSubLScale); \ + INSTANTIATE_DEVICE_ASSIGN_EXPR(SCALAR, DeviceLScaleSub); \ + INSTANTIATE_DEVICE_ASSIGN_EXPR(SCALAR, DeviceBcgsDir) + +#define INSTANTIATE_DEVICE_ASSIGN(SCALAR, OP, EXPR) \ + template void VecExpr::AssignDeviceExpression>( \ + SCALAR*, unsigned long, const VecExpr::CVecExpr, SCALAR>&) + +#define INSTANTIATE_DEVICE_ASSIGN_EXPR(SCALAR, EXPR) \ + INSTANTIATE_DEVICE_ASSIGN(SCALAR, Assign, EXPR); \ + INSTANTIATE_DEVICE_ASSIGN(SCALAR, Add, EXPR); \ + INSTANTIATE_DEVICE_ASSIGN(SCALAR, Subtract, EXPR); \ + INSTANTIATE_DEVICE_ASSIGN(SCALAR, Multiply, EXPR); \ + INSTANTIATE_DEVICE_ASSIGN(SCALAR, Divide, EXPR) + +DEVICE_EXPRESSION_SHAPES(su2mixedfloat); + +#if defined(USE_MIXED_PRECISION) && !defined(USE_SINGLE_PRECISION) +DEVICE_EXPRESSION_SHAPES(passivedouble); +#endif + +#undef DEVICE_EXPRESSION_SHAPES +#undef INSTANTIATE_DEVICE_ASSIGN_EXPR +#undef INSTANTIATE_DEVICE_ASSIGN + +template void CSysVector::HtDTransfer(bool trigger) const; +template void CSysVector::DtHTransfer(bool trigger) const; +template su2mixedfloat CSysVector::GPUDot(const CSysVector& other) const; +template su2mixedfloat CSysVector::GPUNorm() const; + +#if defined(USE_MIXED_PRECISION) && !defined(USE_SINGLE_PRECISION) +template void CSysVector::HtDTransfer(bool trigger) const; +template void CSysVector::DtHTransfer(bool trigger) const; +template passivedouble CSysVector::GPUDot(const CSysVector& other) const; +template passivedouble CSysVector::GPUNorm() const; +#endif diff --git a/Common/src/linear_algebra/meson.build b/Common/src/linear_algebra/meson.build index 7b880b29c1e3..48ef65cb8db2 100644 --- a/Common/src/linear_algebra/meson.build +++ b/Common/src/linear_algebra/meson.build @@ -5,6 +5,8 @@ common_src += files(['CSysSolve_b.cpp', 'CPastixWrapper.cpp', 'blas_structure.cpp']) - if get_option('enable-cuda') - common_src += files(['CSysMatrixGPU.cu', 'CSysVectorGPU.cu',]) +if get_option('enable-cuda') + # Kept apart from common_src: these are compiled without the CoDiPack defines and so + # must only go into the primal library, see common_cuda_src in Common/src/meson.build. + common_cuda_src += files(['CSysMatrixGPU.cu', 'CSysVectorGPU.cu', 'CSysPreconditionerGPU.cu']) endif diff --git a/Common/src/meson.build b/Common/src/meson.build index f385c4a32edd..c34b014ab425 100644 --- a/Common/src/meson.build +++ b/Common/src/meson.build @@ -6,6 +6,25 @@ common_src =files(['graph_coloring_structure.cpp', '../include/parallelization/mpi_structure.cpp', '../include/parallelization/omp_structure.cpp']) +# nvcc cannot parse CoDiPack's tape machinery in the device pass, so the CUDA sources are +# compiled without the CODI_REVERSE_TYPE/CODI_FORWARD_TYPE defines. That gives them a +# different su2double (and hence a different layout for CConfig, CGeometry, ...) than the +# AD libraries, so they are collected separately and only linked into the primal library. +# The AD builds compile the device dispatch out entirely, see SU2_ENABLE_CUDA_KERNELS. +common_cuda_src = [] +common_cuda_cpp_args = [] +foreach arg : su2_cpp_args + if arg.startswith('-D') or arg.startswith('-U') + common_cuda_cpp_args += arg + endif +endforeach + +# Note that the OpenMP flags do reach nvcc (through omp_dep), so the .cu objects agree +# with the rest of the library about HAVE_OMP. They must still not instantiate anything +# containing OpenMP directives, because nvcc's host pass rewrites some of them; the +# device path is synchronized in the headers (see SU2_DEVICE_REGION in CSysVector.hpp), +# which only the .cpp sources instantiate. + subdir('linear_algebra') subdir('toolboxes') subdir('geometry') @@ -21,10 +40,11 @@ subdir('adt') if get_option('enable-normal') common = static_library('SU2Common', - common_src, + common_src, common_cuda_src, install : false, dependencies : su2_deps, - cpp_args: [default_warning_flags, su2_cpp_args]) + cpp_args: [default_warning_flags, su2_cpp_args], + cuda_args: common_cuda_cpp_args) common_dep = declare_dependency(link_with: common, include_directories : common_include) diff --git a/meson.build b/meson.build index e76354a6e863..a852cf7f1137 100644 --- a/meson.build +++ b/meson.build @@ -19,11 +19,24 @@ python = pymod.find_installation() if get_option('enable-cuda') add_languages('cuda') - add_global_arguments('-arch=sm_86', language : 'cuda') + add_global_arguments('-arch=sm_89', language : 'cuda') + # nvcc's frontend does not recognize the AMX-tile builtins pulled in by + # newer glibc/gcc ; SU2 does not use AMX, so skip the header. + add_global_arguments('-D_AMXTILEINTRIN_H_INCLUDED', language : 'cuda') + # Any target linking CUDA object code is linked via nvcc instead of the C++ + # linker, which otherwise defaults to a plain host gcc and silently drops + # the MPI link flags that -Dcustom-mpi=true relies on mpicxx to provide. + add_global_arguments('-ccbin=' + meson.get_compiler('cpp').cmd_array()[0], language : 'cuda') + add_global_link_arguments('-ccbin=' + meson.get_compiler('cpp').cmd_array()[0], language : 'cuda') + cuda_deps = [ + meson.get_compiler('cuda').find_library('cublas', required : true), + ] +else + cuda_deps = [] endif su2_cpp_args = [] -su2_deps = [declare_dependency(include_directories: 'externals/CLI11')] +su2_deps = [declare_dependency(include_directories: 'externals/CLI11')] + cuda_deps default_warning_flags = [] if build_machine.system() != 'windows' @@ -231,8 +244,7 @@ endif # CUDA dependencies if get_option('enable-cuda') su2_cpp_args += '-DHAVE_CUDA' - gpu_dep = dependency('cuda', version : '>=10', modules : ['cudart']) - su2_deps += gpu_dep + su2_deps += meson.get_compiler('cuda').find_library('cudart', required : true) endif # blas-type dependencies diff --git a/su2omp.syntax.json b/su2omp.syntax.json index eb3f79653e24..382986c03d2f 100644 --- a/su2omp.syntax.json +++ b/su2omp.syntax.json @@ -39,6 +39,7 @@ "CSYSVEC_PARFOR": "END_CSYSVEC_PARFOR", "CNEWTON_PARFOR": "END_CNEWTON_PARFOR", "BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS": "END_SU2_OMP_SAFE_GLOBAL_ACCESS", + "BEGIN_SU2_DEVICE_REGION": "END_SU2_DEVICE_REGION", "CPHYSGEO_PARFOR": "END_CPHYSGEO_PARFOR" } } From 27cab0630fffd42082ef07fed92bba37feeef0a1 Mon Sep 17 00:00:00 2001 From: bigboateng Date: Sun, 2 Aug 2026 02:58:41 +0100 Subject: [PATCH 25/61] Apply the QCR2000 correction to the turbulent stress only (fix #2738) (#2855) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Proposed Changes The QCR2000 quadratic constitutive relation (Spalart, *Int. J. Heat and Fluid Flow*, 2000) modifies the turbulent (Boussinesq) stresses, but `CNumerics::AddQCR` was applied to the total stress tensor at all call sites: laminar + eddy in the viscous fluxes (scalar and SIMD paths), and the **purely laminar** tensor in `Friction_Forces`, where the eddy viscosity is zero and the correction should vanish identically. For a pure-shear wall state this injects spurious normal stresses of ±0.6·τ_wall into the wall traction, which redistributes drag between the pressure and friction components while leaving total drag nearly unchanged — the exact signature reported in #2738. This PR scales the correction by the turbulent fraction of the viscosity used to build the tensor, `mu_t / (mu_l + mu_t)`, which recovers the turbulent-only correction exactly because the stress tensor is linear in the viscosity (SA carries no turbulent-kinetic-energy term). The wall-force site passes a zero fraction on smooth walls. A unit test covers both properties (zero correction at `mu_t = 0`; scaled-total ≡ separate-turbulent-tensor). **Validation** (Ubuntu 24.04, source build at `de8c5015`, the #2738 reporter's Joukowski R2 grid, 12000 iters, rms[Rho] ≈ −11): | run | CDp | CDv | CD | |---|---|---|---| | SA | 0.003180 | 0.004735 | 0.007915 | | SA-QCR2000, before | 0.003482 | 0.004432 | 0.007914 | | SA-QCR2000, after | 0.003183 | 0.004716 | 0.007898 | | SA on patched build | 0.003179 | 0.004735 | 0.007915 | Before the change, QCR2000 shifts CDp +9.5% / CDv −6.4%; after it, the split returns to within 0.1% of SA, consistent with CFL3D/FUN3D/Fluent behavior for this attached flow ([HiFi CFD verification workshop references](https://highfidelitycfdverificationworkshop.github.io/papers/rans.pdf)). With QCR disabled the patched build is behaviorally unchanged. Note: the `turb_SA_QCR_RAE2822` reference values in `parallel_regression.py` shift by 1e-4…4e-3 (verified on a same-platform before/after pair); I will update them from this PR's CI output, since reference values are runner-specific. Opening as a draft until then. ## Related Work Fixes #2738. ## PR Checklist - [x] I am submitting my contribution to the develop branch. - [x] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [x] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [x] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [x] I have added a test case that demonstrates my contribution, if necessary. - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --------- Co-authored-by: Boateng Opoku-Yeboah --- SU2_CFD/include/numerics/CNumerics.hpp | 14 +++++++-- .../numerics_simd/flow/diffusion/common.hpp | 4 +-- .../flow/diffusion/viscous_fluxes.hpp | 5 +-- .../include/solvers/CFVMFlowSolverBase.inl | 8 +++-- SU2_CFD/src/numerics/flow/flow_diffusion.cpp | 18 +++++++++-- TestCases/parallel_regression.py | 2 +- .../SU2_CFD/numerics/CNumerics_tests.cpp | 31 +++++++++++++++++++ 7 files changed, 69 insertions(+), 13 deletions(-) diff --git a/SU2_CFD/include/numerics/CNumerics.hpp b/SU2_CFD/include/numerics/CNumerics.hpp index 535e40cba96d..ec511a3fa1db 100644 --- a/SU2_CFD/include/numerics/CNumerics.hpp +++ b/SU2_CFD/include/numerics/CNumerics.hpp @@ -519,12 +519,20 @@ class CNumerics { * See: Spalart, P. R., "Strategies for Turbulence Modelling and Simulation", * International Journal of Heat and Fluid Flow, Vol. 21, 2000, pp. 252-263 * + * The QCR correction applies to the turbulent (Boussinesq) stresses only. + * When tau is the total (laminar + turbulent) stress tensor, which is + * proportional to the total viscosity, the turbulent part is recovered by + * scaling the correction with turb_fraction = mu_t / (mu_l + mu_t); at a + * no-slip wall (mu_t = 0) the correction vanishes. Pass 1 only if tau is + * already the turbulent stress tensor. + * * \param[in] nDim: 2D or 3D. * \param[in] gradvel: Velocity gradients. * \param[in,out] tau: Shear stress tensor. + * \param[in] turb_fraction: Turbulent share of the viscosity in tau. */ - template - FORCEINLINE static void AddQCR(size_t nDim, const Mat1& gradvel, Mat2& tau) { + template + FORCEINLINE static void AddQCR(size_t nDim, const Mat1& gradvel, Mat2& tau, Scalar2 turb_fraction) { using Scalar = typename std::decay::type; const Scalar c_cr1 = 0.3; @@ -553,7 +561,7 @@ class CNumerics { for (size_t iDim = 0; iDim < nDim; iDim++) for (size_t jDim = 0; jDim < nDim; jDim++) - tau[iDim][jDim] -= c_cr1 * tauQCR[iDim][jDim]; + tau[iDim][jDim] -= turb_fraction * c_cr1 * tauQCR[iDim][jDim]; } /*! diff --git a/SU2_CFD/include/numerics_simd/flow/diffusion/common.hpp b/SU2_CFD/include/numerics_simd/flow/diffusion/common.hpp index 0f4fac0d03fd..78b391d8a2fe 100644 --- a/SU2_CFD/include/numerics_simd/flow/diffusion/common.hpp +++ b/SU2_CFD/include/numerics_simd/flow/diffusion/common.hpp @@ -121,7 +121,7 @@ NEVERINLINE void addPerturbedRSM(const PrimitiveType& V, * \brief SA-QCR2000 modification of the stress tensor. */ template -FORCEINLINE void addQCR(const MatrixType& grad, MatrixDbl& tau) { +FORCEINLINE void addQCR(const MatrixType& grad, MatrixDbl& tau, Double turb_fraction) { constexpr passivedouble c_cr1 = 0.3; /*--- Denominator, antisymmetric normalized rotation tensor. ---*/ @@ -146,7 +146,7 @@ FORCEINLINE void addQCR(const MatrixType& grad, MatrixDbl& tau) { } for (size_t iDim = 0; iDim < nDim; ++iDim) for (size_t jDim = 0; jDim < nDim; ++jDim) - tau(iDim,jDim) -= c_cr1 * qcr(iDim,jDim); + tau(iDim,jDim) -= turb_fraction * c_cr1 * qcr(iDim,jDim); } /*! diff --git a/SU2_CFD/include/numerics_simd/flow/diffusion/viscous_fluxes.hpp b/SU2_CFD/include/numerics_simd/flow/diffusion/viscous_fluxes.hpp index 7c3a50052be3..1a8745b10bc2 100644 --- a/SU2_CFD/include/numerics_simd/flow/diffusion/viscous_fluxes.hpp +++ b/SU2_CFD/include/numerics_simd/flow/diffusion/viscous_fluxes.hpp @@ -149,8 +149,9 @@ class CCompressibleViscousFluxBase : public CNumericsSIMD { /*--- Stress and heat flux tensors. ---*/ - auto tau = stressTensor(avgV.laminarVisc() + (uq? Double(0.0) : avgV.eddyVisc()), avgGrad); - if(useSA_QCR) addQCR(avgGrad, tau); + const Double eddyVisc = uq? Double(0.0) : avgV.eddyVisc(); + auto tau = stressTensor(avgV.laminarVisc() + eddyVisc, avgGrad); + if(useSA_QCR) addQCR(avgGrad, tau, eddyVisc / (avgV.laminarVisc() + eddyVisc)); if(uq) { Double turb_ke = 0.5*(gatherVariables(iPoint, turbVars->GetSolution()) + gatherVariables(jPoint, turbVars->GetSolution())); diff --git a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl index 1c0d3c512eba..30dc89f0a437 100644 --- a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl +++ b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl @@ -2535,11 +2535,15 @@ void CFVMFlowSolverBase::Friction_Forces(const CGeometry* geometr } Viscosity = nodes->GetLaminarViscosity(iPoint); + su2double EddyViscosity = 0.0; if (roughwall) { WALL_TYPE WallType; su2double Roughness_Height; tie(WallType, Roughness_Height) = config->GetWallRoughnessProperties(Marker_Tag); - if (WallType == WALL_TYPE::ROUGH) Viscosity += nodes->GetEddyViscosity(iPoint); + if (WallType == WALL_TYPE::ROUGH) { + EddyViscosity = nodes->GetEddyViscosity(iPoint); + Viscosity += EddyViscosity; + } } Density = nodes->GetDensity(iPoint); @@ -2553,7 +2557,7 @@ void CFVMFlowSolverBase::Friction_Forces(const CGeometry* geometr /*--- If necessary evaluate the QCR contribution to Tau ---*/ - if (QCR) CNumerics::AddQCR(nDim, Grad_Vel, Tau); + if (QCR) CNumerics::AddQCR(nDim, Grad_Vel, Tau, EddyViscosity / Viscosity); /*--- Project Tau in each surface element ---*/ diff --git a/SU2_CFD/src/numerics/flow/flow_diffusion.cpp b/SU2_CFD/src/numerics/flow/flow_diffusion.cpp index 1acce0660399..ba2ba516c1c0 100644 --- a/SU2_CFD/src/numerics/flow/flow_diffusion.cpp +++ b/SU2_CFD/src/numerics/flow/flow_diffusion.cpp @@ -478,7 +478,11 @@ CNumerics::ResidualType<> CAvgGrad_Flow::ComputeResidual(const CConfig* config) SetStressTensor(Mean_PrimVar, Mean_GradPrimVar, Mean_turb_ke, Mean_Laminar_Viscosity, Mean_Eddy_Viscosity, config); - if (config->GetSAParsedOptions().qcr2000) AddQCR(nDim, &Mean_GradPrimVar[1], tau); + if (config->GetSAParsedOptions().qcr2000) { + const su2double total_viscosity = Mean_Laminar_Viscosity + Mean_Eddy_Viscosity; + const su2double turb_fraction = Mean_Eddy_Viscosity / fmax(total_viscosity, EPS); + AddQCR(nDim, &Mean_GradPrimVar[1], tau, turb_fraction); + } if (Mean_TauWall > 0) AddTauWall(UnitNormal, Mean_TauWall); SetHeatFluxVector(Mean_GradPrimVar, Mean_Eddy_Viscosity, Mean_Thermal_Conductivity, Mean_Cp); @@ -656,7 +660,11 @@ CNumerics::ResidualType<> CAvgGradInc_Flow::ComputeResidual(const CConfig* confi SetStressTensor(Mean_PrimVar, Mean_GradPrimVar, Mean_turb_ke, Mean_Laminar_Viscosity, Mean_Eddy_Viscosity, config); - if (config->GetSAParsedOptions().qcr2000) AddQCR(nDim, &Mean_GradPrimVar[1], tau); + if (config->GetSAParsedOptions().qcr2000) { + const su2double total_viscosity = Mean_Laminar_Viscosity + Mean_Eddy_Viscosity; + const su2double turb_fraction = Mean_Eddy_Viscosity / fmax(total_viscosity, EPS); + AddQCR(nDim, &Mean_GradPrimVar[1], tau, turb_fraction); + } if (Mean_TauWall > 0) AddTauWall(UnitNormal, Mean_TauWall); GetViscousIncProjFlux(Mean_GradPrimVar, Normal, Mean_Thermal_Conductivity); @@ -986,7 +994,11 @@ CNumerics::ResidualType<> CGeneralAvgGrad_Flow::ComputeResidual(const CConfig* c SetStressTensor(Mean_PrimVar, Mean_GradPrimVar, Mean_turb_ke, Mean_Laminar_Viscosity, Mean_Eddy_Viscosity, config); - if (config->GetSAParsedOptions().qcr2000) AddQCR(nDim, &Mean_GradPrimVar[1], tau); + if (config->GetSAParsedOptions().qcr2000) { + const su2double total_viscosity = Mean_Laminar_Viscosity + Mean_Eddy_Viscosity; + const su2double turb_fraction = Mean_Eddy_Viscosity / fmax(total_viscosity, EPS); + AddQCR(nDim, &Mean_GradPrimVar[1], tau, turb_fraction); + } if (Mean_TauWall > 0) AddTauWall(UnitNormal, Mean_TauWall); SetHeatFluxVector(Mean_GradPrimVar, Mean_Eddy_Viscosity, Mean_Thermal_Conductivity, Mean_Cp); diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index bea1653798d4..9c08ad013e5e 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -853,7 +853,7 @@ def main(): turbmod_sa_qcr_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_qcr_rae2822.cfg_file = "turb_SA_QCR_RAE2822.cfg" turbmod_sa_qcr_rae2822.test_iter = 20 - turbmod_sa_qcr_rae2822.test_vals = [-2.802573, 0.138895, -0.286064, -5.233541, 0.796617, 0.025734] + turbmod_sa_qcr_rae2822.test_vals = [-2.784977, 0.174833, -0.258852, -5.275520, 0.799156, 0.025897] test_list.append(turbmod_sa_qcr_rae2822) ############################ diff --git a/UnitTests/SU2_CFD/numerics/CNumerics_tests.cpp b/UnitTests/SU2_CFD/numerics/CNumerics_tests.cpp index 40ae1cfb9dfd..a4cee41e8d20 100644 --- a/UnitTests/SU2_CFD/numerics/CNumerics_tests.cpp +++ b/UnitTests/SU2_CFD/numerics/CNumerics_tests.cpp @@ -58,3 +58,34 @@ TEST_CASE("NTS blending has a minimum of 0.05", "[Upwind/central blending]") { delete config; } + +TEST_CASE("QCR2000 corrects only the turbulent stress", "[QCR]") { + constexpr size_t nDim = 3; + su2double gradvel[3][3] = {{0.0}}; + gradvel[0][1] = 100.0; + const su2double mu_lam = 2.0; + const su2double mu_turb = 3.0; + + /*--- At a no-slip wall the eddy viscosity is zero, so the correction + * must leave the (purely laminar) stress tensor unchanged. ---*/ + + su2double tau_wall[3][3] = {{0.0}}, tau_wall_ref[3][3] = {{0.0}}; + CNumerics::ComputeStressTensor(nDim, tau_wall, gradvel, mu_lam); + CNumerics::ComputeStressTensor(nDim, tau_wall_ref, gradvel, mu_lam); + CNumerics::AddQCR(nDim, gradvel, tau_wall, 0.0); + for (size_t iDim = 0; iDim < nDim; iDim++) + for (size_t jDim = 0; jDim < nDim; jDim++) REQUIRE(tau_wall[iDim][jDim] == tau_wall_ref[iDim][jDim]); + + /*--- Scaling the correction of the total stress by mu_t / (mu_l + mu_t) + * is equivalent to correcting the turbulent stress tensor alone. ---*/ + + su2double tau_total[3][3] = {{0.0}}, tau_turb[3][3] = {{0.0}}, tau_lam[3][3] = {{0.0}}; + CNumerics::ComputeStressTensor(nDim, tau_total, gradvel, mu_lam + mu_turb); + CNumerics::ComputeStressTensor(nDim, tau_turb, gradvel, mu_turb); + CNumerics::ComputeStressTensor(nDim, tau_lam, gradvel, mu_lam); + CNumerics::AddQCR(nDim, gradvel, tau_total, mu_turb / (mu_lam + mu_turb)); + CNumerics::AddQCR(nDim, gradvel, tau_turb, 1.0); + for (size_t iDim = 0; iDim < nDim; iDim++) + for (size_t jDim = 0; jDim < nDim; jDim++) + REQUIRE(tau_total[iDim][jDim] == Approx(tau_lam[iDim][jDim] + tau_turb[iDim][jDim]).margin(1e-12)); +} From fa28671a3f98e7065338317ed63db34b4d6f95c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lie=20Grebe?= Date: Sun, 9 Aug 2026 06:01:49 -0400 Subject: [PATCH 26/61] Fix MG species restart crash (#2849) ## Proposed Changes Currently, the species solver crashes when restarting a multi-grid simulation due to mismatched grid levels in two calls in `CSpeciesSolver::LoadRestart`. This PR fixes that, and modifies a test to exercise this code path. It'd be great if someone could also double-check whether this code makes sense. Given this bug, I suspect these lines have never been run since they were committed 5 years ago. ## Related Work I'll send an accompanying PR for the new restart file needed for the test case. ## PR Checklist - [x] I am submitting my contribution to the develop branch. - [x] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [x] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [x] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [x] I have added a test case that demonstrates my contribution, if necessary. - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --------- Co-authored-by: Nijso --- SU2_CFD/src/solvers/CSpeciesSolver.cpp | 4 ++-- SU2_CFD/src/solvers/CTurbSolver.cpp | 1 + .../axisymmetric_rans/air_nozzle/air_nozzle_species.cfg | 8 ++++++-- TestCases/serial_regression.py | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/SU2_CFD/src/solvers/CSpeciesSolver.cpp b/SU2_CFD/src/solvers/CSpeciesSolver.cpp index dc9f278e776d..28d09dac0506 100644 --- a/SU2_CFD/src/solvers/CSpeciesSolver.cpp +++ b/SU2_CFD/src/solvers/CSpeciesSolver.cpp @@ -290,9 +290,9 @@ void CSpeciesSolver::LoadRestart(CGeometry** geometry, CSolver*** solver, CConfi true); if (config->GetKind_Turb_Model() != TURB_MODEL::NONE) - solver[iMesh][TURB_SOL]->Postprocessing(geometry[MESH_0], solver[MESH_0], config, MESH_0); + solver[iMesh][TURB_SOL]->Postprocessing(geometry[iMesh], solver[iMesh], config, iMesh); - solver[iMesh][SPECIES_SOL]->Preprocessing(geometry[MESH_0], solver[MESH_0], config, MESH_0, NO_RK_ITER, + solver[iMesh][SPECIES_SOL]->Preprocessing(geometry[iMesh], solver[iMesh], config, iMesh, NO_RK_ITER, RUNTIME_SPECIES_SYS, false); } diff --git a/SU2_CFD/src/solvers/CTurbSolver.cpp b/SU2_CFD/src/solvers/CTurbSolver.cpp index 0ac07ab1642c..51562382311d 100644 --- a/SU2_CFD/src/solvers/CTurbSolver.cpp +++ b/SU2_CFD/src/solvers/CTurbSolver.cpp @@ -184,6 +184,7 @@ void CTurbSolver::LoadRestart(CGeometry** geometry, CSolver*** solver, CConfig* solver[iMesh][TURB_SOL]->CompleteComms(geometry[iMesh], config, MPI_QUANTITIES::SOLUTION); if (config->GetKind_Species_Model() == SPECIES_MODEL::NONE) { + /*--- The following is done by the species solver if active ---*/ solver[iMesh][FLOW_SOL]->Preprocessing(geometry[iMesh], solver[iMesh], config, iMesh, NO_RK_ITER, RUNTIME_FLOW_SYS, true); solver[iMesh][TURB_SOL]->Postprocessing(geometry[iMesh], solver[iMesh], config, iMesh); diff --git a/TestCases/axisymmetric_rans/air_nozzle/air_nozzle_species.cfg b/TestCases/axisymmetric_rans/air_nozzle/air_nozzle_species.cfg index 27ad7d6fa35e..0a54c2956730 100644 --- a/TestCases/axisymmetric_rans/air_nozzle/air_nozzle_species.cfg +++ b/TestCases/axisymmetric_rans/air_nozzle/air_nozzle_species.cfg @@ -12,7 +12,7 @@ % SOLVER= RANS KIND_TURB_MODEL= SST -RESTART_SOL= NO +RESTART_SOL= YES AXISYMMETRIC= YES % -------------------- COMPRESSIBLE FREE-STREAM DEFINITION --------------------% @@ -92,6 +92,9 @@ CONV_NUM_METHOD_TURB= SCALAR_UPWIND TIME_DISCRE_TURB= EULER_IMPLICIT CFL_REDUCTION_TURB= 1.0 +% -------------------------- MULTIGRID PARAMETERS -----------------------------% +MGLEVEL= 1 + % --------------------------- CONVERGENCE PARAMETERS --------------------------% % ITER= 15 @@ -101,7 +104,8 @@ CONV_STARTITER= 10 % ------------------------- INPUT/OUTPUT INFORMATION --------------------------% % MESH_FILENAME= nozzle.su2 -RESTART_FILENAME= restart_flow +RESTART_FILENAME= restart_flow_species +SOLUTION_FILENAME= solution_flow_species OUTPUT_WRT_FREQ= 1000 SCREEN_OUTPUT= (INNER_ITER, RMS_DENSITY, RMS_ENERGY, RMS_TKE, RMS_DISSIPATION, RMS_SPECIES_0, TOTAL_HEATFLUX, \ RMS_ADJ_DENSITY, RMS_ADJ_ENERGY, RMS_ADJ_TKE, RMS_ADJ_DISSIPATION) diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index 8adb14300f37..79773b6d986f 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -354,7 +354,7 @@ def main(): axi_rans_air_nozzle_species.cfg_dir = "axisymmetric_rans/air_nozzle" axi_rans_air_nozzle_species.cfg_file = "air_nozzle_species.cfg" axi_rans_air_nozzle_species.test_iter = 10 - axi_rans_air_nozzle_species.test_vals = [-1.840714, 3.726195, -2.009323, 5.649002, -2.494388, 0.0000] + axi_rans_air_nozzle_species.test_vals = [-1.690923, 3.877716, -2.932258, 5.754594, -3.130820, 0.0] axi_rans_air_nozzle_species.tol = 0.0001 test_list.append(axi_rans_air_nozzle_species) From 9405bb55abfdbe8cab42d44dd2fdf59d6dfb954b Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Sun, 9 Aug 2026 21:10:00 -0700 Subject: [PATCH 27/61] Port ILU to GPU (#2858) ## PR Checklist - [X] I am submitting my contribution to the develop branch. - [X] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [X] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [X] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [ ] I have added a test case that demonstrates my contribution, if necessary. - [X] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --------- Co-authored-by: Claude Sonnet 5 --- Common/include/CConfig.hpp | 28 +- .../meshreader/CSU2BinaryMeshReaderBase.hpp | 2 +- .../meshreader/CSU2BinaryMeshReaderFEM.hpp | 2 +- .../meshreader/CSU2BinaryMeshReaderFVM.hpp | 2 +- .../meshreader/CSU2MeshReaderBase.hpp | 2 +- .../CVolumetricMovementFactory.hpp | 2 +- .../include/linear_algebra/CMatrixInverse.hpp | 99 ++++ .../linear_algebra/CMatrixVectorProduct.hpp | 24 +- .../linear_algebra/CPreconditioner.hpp | 5 +- Common/include/linear_algebra/CSysMatrix.hpp | 126 +++-- Common/include/linear_algebra/GPUComms.cuh | 2 +- Common/include/toolboxes/SwapBytes.hpp | 2 +- Common/include/toolboxes/random_toolbox.hpp | 2 +- Common/src/CConfig.cpp | 20 +- Common/src/geometry/CPhysicalGeometry.cpp | 59 +- .../meshreader/CSU2BinaryMeshReaderBase.cpp | 2 +- .../meshreader/CSU2BinaryMeshReaderFEM.cpp | 2 +- .../meshreader/CSU2BinaryMeshReaderFVM.cpp | 2 +- .../meshreader/CSU2MeshReaderBase.cpp | 2 +- Common/src/linear_algebra/CSysMatrix.cpp | 302 ++++++++--- Common/src/linear_algebra/CSysMatrixGPU.cu | 503 +++++++++++++++++- .../linear_algebra/CSysPreconditionerGPU.cu | 84 --- Common/src/linear_algebra/CSysVectorGPU.cu | 6 +- Common/src/linear_algebra/meson.build | 2 +- Common/src/toolboxes/SwapBytes.cpp | 2 +- TestCases/hybrid_regression.py | 26 +- TestCases/hybrid_regression_AD.py | 20 +- TestCases/parallel_regression.py | 26 +- TestCases/parallel_regression_AD.py | 12 +- .../lam_buoyancy_cavity.cfg | 2 +- .../py_wrapper/custom_source_buoyancy/run.py | 4 +- .../py_wrapper/turbulent_premixed_psi/run.py | 6 +- .../forces_0.csv.ref | 38 +- TestCases/serial_regression.py | 4 +- TestCases/serial_regression_AD.py | 2 +- TestCases/tutorials.py | 2 +- config_template.cfg | 14 + 37 files changed, 1080 insertions(+), 360 deletions(-) create mode 100644 Common/include/linear_algebra/CMatrixInverse.hpp delete mode 100644 Common/src/linear_algebra/CSysPreconditionerGPU.cu diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index 1b2a45dc3ffe..3284f9527366 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -644,8 +644,14 @@ class CConfig { unsigned long Linear_Solver_Restart_Frequency; /*!< \brief Restart frequency of the linear solver for the implicit formulation. */ unsigned long Linear_Solver_Restart_Deflation; /*!< \brief Number of vectors used for deflated restarts. */ unsigned long Linear_Solver_Prec_Threads; /*!< \brief Number of threads per rank for ILU and LU_SGS preconditioners. */ - unsigned short Linear_Solver_ILU_n; /*!< \brief ILU fill=in level. */ - bool Linear_Solver_ILU_levels; /*!< \brief Use level scheduling for OMP parallelization of ILU. */ + + struct CIluOptions { + unsigned short FillIn = 0; /*!< \brief ILU fill-in level. */ + bool LevelScheduling = false; /*!< \brief Use level scheduling for OMP parallelization of ILU. */ + /*!< \brief Number of colored Gauss-Seidel sweeps used to build the GPU ILU factorization; + * the triangular solves are level-scheduled and exact (no sweep count). */ + unsigned short GPUSweeps = 2; + } IluOptions; su2double SemiSpan; /*!< \brief Wing Semi span. */ su2double MSW_Alpha; /*!< \brief Coefficient for blending states in the MSW scheme. */ su2double Roe_Kappa; /*!< \brief Relaxation of the Roe scheme. */ @@ -1271,6 +1277,8 @@ class CConfig { unsigned long edgeColorGroupSize; /*!< \brief Size of the edge groups colored for OpenMP parallelization of edge loops. */ bool edgeColoringRelaxDiscAdj; /*!< \brief Allow fallback to smaller edge color group sizes and use more colors for the discrete adjoint. */ + unsigned short rcmNumSeeds; /*!< \brief Number of concurrent BFS fronts used to build the RCM reordering. */ + INLET_SPANWISE_INTERP Kind_InletInterpolationFunction; /*!brief type of spanwise interpolation function to use for the inlet face. */ INLET_INTERP_TYPE Kind_Inlet_InterpolationType; /*!brief type of spanwise interpolation data to use for the inlet face. */ bool PrintInlet_InterpolatedData; /*!brief option for printing the interpolated data file. */ @@ -4390,15 +4398,10 @@ class CConfig { unsigned long GetDeform_Linear_Solver_Iter(void) const { return Deform_Linear_Solver_Iter; } /*! - * \brief Get the ILU fill-in level for the linear solver. - * \return Fill in level of the ILU preconditioner for the linear solver. - */ - unsigned short GetLinear_Solver_ILU_n(void) const { return Linear_Solver_ILU_n; } - - /*! - * \brief Get whether to use level scheduling for OMP parallelization of ILU. + * \brief Get the ILU preconditioner options (fill-in level, OMP level scheduling, GPU build + * sweeps), see CIluOptions. */ - bool GetLinear_Solver_ILU_levels(void) const { return Linear_Solver_ILU_levels; } + const CIluOptions& GetIluOptions(void) const { return IluOptions; } /*! * \brief Get restart frequency of the linear solver for the implicit formulation. @@ -10154,6 +10157,11 @@ class CConfig { */ bool GetEdgeColoringRelaxDiscAdj() const { return edgeColoringRelaxDiscAdj; } + /*! + * \brief Get the number of concurrent BFS fronts used to build the RCM reordering, see SetRCM_Ordering. + */ + unsigned short GetRCM_NumSeeds(void) const { return rcmNumSeeds; } + /*! * \brief Get the ParMETIS load balancing tolerance. */ diff --git a/Common/include/geometry/meshreader/CSU2BinaryMeshReaderBase.hpp b/Common/include/geometry/meshreader/CSU2BinaryMeshReaderBase.hpp index f4b71d94e64e..ca66e6aa80c8 100644 --- a/Common/include/geometry/meshreader/CSU2BinaryMeshReaderBase.hpp +++ b/Common/include/geometry/meshreader/CSU2BinaryMeshReaderBase.hpp @@ -10,7 +10,7 @@ * The SU2 Project is maintained by the SU2 Foundation * (http://su2foundation.org) * - * Copyright 2012-2025, SU2 Contributors (cf. AUTHORS.md) + * 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 diff --git a/Common/include/geometry/meshreader/CSU2BinaryMeshReaderFEM.hpp b/Common/include/geometry/meshreader/CSU2BinaryMeshReaderFEM.hpp index bec54f75fcb7..d4112d09e589 100644 --- a/Common/include/geometry/meshreader/CSU2BinaryMeshReaderFEM.hpp +++ b/Common/include/geometry/meshreader/CSU2BinaryMeshReaderFEM.hpp @@ -10,7 +10,7 @@ * The SU2 Project is maintained by the SU2 Foundation * (http://su2foundation.org) * - * Copyright 2012-2025, SU2 Contributors (cf. AUTHORS.md) + * 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 diff --git a/Common/include/geometry/meshreader/CSU2BinaryMeshReaderFVM.hpp b/Common/include/geometry/meshreader/CSU2BinaryMeshReaderFVM.hpp index 03ba796289bb..894bd41486d8 100644 --- a/Common/include/geometry/meshreader/CSU2BinaryMeshReaderFVM.hpp +++ b/Common/include/geometry/meshreader/CSU2BinaryMeshReaderFVM.hpp @@ -10,7 +10,7 @@ * The SU2 Project is maintained by the SU2 Foundation * (http://su2foundation.org) * - * Copyright 2012-2025, SU2 Contributors (cf. AUTHORS.md) + * 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 diff --git a/Common/include/geometry/meshreader/CSU2MeshReaderBase.hpp b/Common/include/geometry/meshreader/CSU2MeshReaderBase.hpp index 9e5217f5c2b3..0f80b73b23d9 100644 --- a/Common/include/geometry/meshreader/CSU2MeshReaderBase.hpp +++ b/Common/include/geometry/meshreader/CSU2MeshReaderBase.hpp @@ -10,7 +10,7 @@ * The SU2 Project is maintained by the SU2 Foundation * (http://su2foundation.org) * - * Copyright 2012-2025, SU2 Contributors (cf. AUTHORS.md) + * 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 diff --git a/Common/include/grid_movement/CVolumetricMovementFactory.hpp b/Common/include/grid_movement/CVolumetricMovementFactory.hpp index 702decb25e97..970d780676f8 100644 --- a/Common/include/grid_movement/CVolumetricMovementFactory.hpp +++ b/Common/include/grid_movement/CVolumetricMovementFactory.hpp @@ -8,7 +8,7 @@ * The SU2 Project is maintained by the SU2 Foundation * (http://su2foundation.org) * - * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) + * 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 diff --git a/Common/include/linear_algebra/CMatrixInverse.hpp b/Common/include/linear_algebra/CMatrixInverse.hpp new file mode 100644 index 000000000000..f3d64630349d --- /dev/null +++ b/Common/include/linear_algebra/CMatrixInverse.hpp @@ -0,0 +1,99 @@ +/*! + * \file CMatrixInverse.hpp + * \brief Dense small-matrix inversion via Gauss-Jordan elimination, shared between the host + * (CSysMatrix::MatrixInverse) and device (CSysPreconditionerGPU.cu) implementations. + * \author F. Palacios, A. Bueno, T. Economon, P. Gomes + * \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 . + */ + +#pragma once + +#include + +#ifdef __CUDACC__ +#define SU2_CUDA_HOST_DEVICE __host__ __device__ +#else +#define SU2_CUDA_HOST_DEVICE +#endif + +namespace SU2_LinAlg { + +/*! + * \brief Regularize a pivot that is too small to prevent divide-by-zero, on host and device + * this needs to clamp to the same value so that the two produce the same factors. + */ +template +SU2_CUDA_HOST_DEVICE inline void RegularizePivot(ScalarType& pivot) { + const float eps = 1e-12; +#ifdef __CUDA_ARCH__ + if (fabs(pivot) < eps) pivot = copysign(ScalarType(eps), pivot); +#else + if (std::abs(pivot) < eps) pivot = std::copysign(ScalarType(eps), pivot); +#endif +} + +/*! + * \brief Invert the \p nVar by \p nVar dense matrix \p matrix into \p inverse via Gauss-Jordan + * elimination with partial pivoting on the diagonal. + * \note \p matrix is used as scratch space and destroyed, \p inverse must not alias it. + */ +template +SU2_CUDA_HOST_DEVICE inline void MatrixInverse(unsigned long nVar, ScalarType* matrix, ScalarType* inverse) { +#define A(I, J) matrix[(I)*nVar + (J)] +#define M(I, J) inverse[(I)*nVar + (J)] + + /*--- Initialize the inverse with the identity. ---*/ + for (auto iVar = 0ul; iVar < nVar; iVar++) + for (auto jVar = 0ul; jVar < nVar; jVar++) M(iVar, jVar) = ScalarType(iVar == jVar); + + /*--- Transform system in Upper Matrix. ---*/ + for (auto iVar = 1ul; iVar < nVar; iVar++) { + for (auto jVar = 0ul; jVar < iVar; jVar++) { + RegularizePivot(A(jVar, jVar)); + + const ScalarType weight = A(iVar, jVar) / A(jVar, jVar); + for (auto kVar = jVar; kVar < nVar; kVar++) A(iVar, kVar) -= weight * A(jVar, kVar); + + /*--- At this stage M is lower triangular so not all cols need updating. ---*/ + for (auto kVar = 0ul; kVar <= jVar; kVar++) M(iVar, kVar) -= weight * M(jVar, kVar); + } + } + + /*--- Backwards substitution. ---*/ + for (auto iVar = nVar; iVar > 0ul;) { + iVar--; // unsigned type + for (auto jVar = iVar + 1; jVar < nVar; jVar++) + for (auto kVar = 0ul; kVar < nVar; kVar++) M(iVar, kVar) -= A(iVar, jVar) * M(jVar, kVar); + + RegularizePivot(A(iVar, iVar)); + + for (auto kVar = 0ul; kVar < nVar; kVar++) M(iVar, kVar) /= A(iVar, iVar); + } + +#undef A +#undef M +} + +} // namespace SU2_LinAlg + +#undef SU2_CUDA_HOST_DEVICE diff --git a/Common/include/linear_algebra/CMatrixVectorProduct.hpp b/Common/include/linear_algebra/CMatrixVectorProduct.hpp index 4069ff2fd006..52614a45770b 100644 --- a/Common/include/linear_algebra/CMatrixVectorProduct.hpp +++ b/Common/include/linear_algebra/CMatrixVectorProduct.hpp @@ -105,28 +105,6 @@ class CSysMatrixVectorProduct final : public CMatrixVectorProduct { * \param[out] v - CSysVector that is the result of the product */ inline void operator()(const CSysVector& u, CSysVector& v) const override { - if (config->GetCUDA()) { -#ifdef SU2_ENABLE_CUDA_KERNELS - if constexpr (su2_gpu_capable_v) { - BEGIN_SU2_DEVICE_REGION - matrix.GPUMatrixVectorProduct(u, v, geometry, config); - END_SU2_DEVICE_REGION - } else { - SU2_MPI::Error("GPU acceleration is not supported for AD scalar types.", CURRENT_FUNCTION); - } -#elif defined(HAVE_CUDA) - SU2_MPI::Error( - "\nError in launching Matrix-Vector Product Function\nENABLE_CUDA is set to YES\nThe GPU kernels are not " - "part of the AD libraries, use the primal build for GPU acceleration", - CURRENT_FUNCTION); -#else - SU2_MPI::Error( - "\nError in launching Matrix-Vector Product Function\nENABLE_CUDA is set to YES\nPlease compile with CUDA " - "options enabled in Meson to access GPU Functions", - CURRENT_FUNCTION); -#endif - } else { - matrix.MatrixVectorProduct(u, v, geometry, config); - } + matrix.MatrixVectorProduct(u, v, geometry, config); } }; diff --git a/Common/include/linear_algebra/CPreconditioner.hpp b/Common/include/linear_algebra/CPreconditioner.hpp index d28c729c4f0f..e23f5de381f2 100644 --- a/Common/include/linear_algebra/CPreconditioner.hpp +++ b/Common/include/linear_algebra/CPreconditioner.hpp @@ -40,7 +40,7 @@ /*! * \brief Applies a preconditioner that only has a host implementation to vectors that live * on the device: bring the input down, apply, put the result back. - * \note This is what keeps ILU, LU-SGS, Linelet and PaStiX usable on the GPU path. The + * \note This is what keeps LU-SGS, Linelet and PaStiX usable on the GPU path. The * transfers are issued by one thread with the team synchronized around them, the apply * itself is the normal OpenMP parallel host code. */ @@ -199,7 +199,8 @@ class CILUPreconditioner final : public CPreconditioner { * \param[out] v - CSysVector that is the result of the preconditioning. */ inline void operator()(const CSysVector& u, CSysVector& v) const override { - ApplyPreconditionerOnHost(u, v, [&] { sparse_matrix.ComputeILUPreconditioner(u, v, geometry, config); }); + /*--- No host bracket, ILU has a device implementation and ComputeILUPreconditioner dispatches to it. ---*/ + sparse_matrix.ComputeILUPreconditioner(u, v, geometry, config); } /*! diff --git a/Common/include/linear_algebra/CSysMatrix.hpp b/Common/include/linear_algebra/CSysMatrix.hpp index 3eddf892a7bf..38f3c9022145 100644 --- a/Common/include/linear_algebra/CSysMatrix.hpp +++ b/Common/include/linear_algebra/CSysMatrix.hpp @@ -250,6 +250,7 @@ class CSysMatrix { LDU mat; /*!< \brief Host matrix (values owned via aligned_alloc; pattern from geometry). */ LDU gpu; /*!< \brief Device matrix (all pointers to GPU memory). */ LDU ilu; /*!< \brief ILU factorization, host (values owned; pattern from geometry). */ + LDU gpu_ilu; /*!< \brief ILU factorization, device (values and pattern in GPU memory). */ ScalarType* d_invM = nullptr; /*!< \brief Device inverse diagonal blocks for the Jacobi preconditioner. */ /*--- Quantized off-diagonal storage (used when quantized_mode == true). ---*/ @@ -271,7 +272,12 @@ class CSysMatrix { * Populated by QuantizeDiagonalBlocks(). */ QuantType* q_blocks_d; /*!< \brief Same as q_blocks_l for the diagonal entries. */ - bool useCuda = false; /*!< \brief Whether CUDA is enabled. */ + bool useCuda = false; /*!< \brief Whether CUDA is enabled. */ + + /*!< \brief Whether the inverse diagonal blocks are only needed on the device. False for the + * Linelet preconditioner, which builds the Jacobi one but reads invM on the host. */ + bool jacobi_on_device = false; + const su2uint* l_to_u_transp; /*!< \brief L-entry index -> U-entry index of its transpose. */ const su2uint* u_to_l_transp; /*!< \brief U-entry index -> L-entry index of its transpose. */ @@ -284,9 +290,43 @@ class CSysMatrix { unsigned short ilu_fill_in; /*!< \brief Fill level for the ILU preconditioner. */ - /*!< \brief Level structure for alternative shared memory parallelization of ILU. */ + /*!< \brief Level structure of the ILU dependency graph: rows within a level are independent, + * rows in level k only depend on rows in levels < k. The same table drives the forward + * (increasing level) and backward (decreasing level) substitution, because the U pattern is + * the transpose of the L pattern. Used directly by the host/OMP substitution, and flattened + * into ilu_level_ptr / d_ilu_level_idx below for the GPU triangular solves. */ CCompressedSparsePatternUL levels_ilu; + /*!< \brief Coloring of the (domain-only) ILU dependency graph, used only by the GPU iterative + * factorization (see IluFactorColorKernel and ilu_color_ptr / d_ilu_color_idx below). The + * host/OMP path and the GPU triangular solves use levels_ilu instead. */ + CCompressedSparsePatternUL color_ilu; + + /*!< \brief Number of colored Gauss-Seidel sweeps used to build the ILU factorization on the + * device, see IluFactorColorKernel. Fixed (not adaptive) so the result is reproducible; set + * from config in Initialize(). The triangular solves have no equivalent sweep count: they are + * exact, one pass per level (see IluForwardKernel / IluBackwardKernel). */ + unsigned short ilu_gpu_sweeps = 1; + + vector ilu_color_ptr; /*!< \brief Start of each color in d_ilu_color_idx, size nColors+1. */ + su2uint* d_ilu_color_idx = nullptr; /*!< \brief Row indices, grouped by color. */ + + vector ilu_level_ptr; /*!< \brief Start of each level in d_ilu_level_idx, size nLevels+1. */ + su2uint* d_ilu_level_idx = nullptr; /*!< \brief Row indices, grouped by level. */ + + /*--- The per-color (factorization) and per-level (triangular solves) kernel launch sequences + * are identical on every call: same grid/block sizes, same device pointers (all fixed members, + * allocated once). Each is captured once into a CUDA graph and replayed to remove + * host-side launch overhead without changing the parallelization. ---*/ + mutable struct CUgraphExec_st* ilu_build_graph_exec = nullptr; + mutable struct CUgraphExec_st* ilu_apply_graph_exec = nullptr; + mutable const ScalarType* ilu_apply_graph_vec = nullptr; /*!< \brief Pointers the apply graph + * was captured with, to detect when + * it must be recaptured. */ + mutable ScalarType* ilu_apply_graph_prod = nullptr; + /*--- The legacy default stream cannot be captured into a graph. ---*/ + mutable struct CUstream_st* ilu_stream = nullptr; + ScalarType* invM; /*!< \brief Inverse of (Jacobi) preconditioner. */ /*--- Temporary (hence mutable) working memory used in the Linelet preconditioner, outer vector is for threads ---*/ @@ -506,6 +546,37 @@ class CSysMatrix { * ScalarType buffer and delegates to the scalar GaussElimination overload. */ inline void QuantizedGaussElimination(unsigned long block_i, ScalarType* rhs) const; + /*--- Hooks for GPU versions (implemented is in CSysMatrixGPU.cu). ---*/ + + /*! + * \brief Performs the product of a sparse matrix by a CSysVector on the device. + */ + void MatrixVectorProductGPU(const CSysVector& vec, CSysVector& prod, CGeometry* geometry, + const CConfig* config) const; + + /*! + * \brief Build the Jacobi preconditioner on the device, from the device copy of the matrix. + * \note Requires the device matrix to be up to date, see HtDTransfer. + */ + void BuildJacobiPreconditionerGPU(); + + /*! + * \brief Apply the Jacobi preconditioner on the GPU/device side. + */ + void ComputeJacobiPreconditionerGPU(const CSysVector& vec, CSysVector& prod, + CGeometry* geometry, const CConfig* config) const; + + /*! + * \brief Build the ILU preconditioner on the device, from the device copy of the matrix. + * \note Requires the device matrix to be up to date, see HtDTransfer. + */ + void BuildILUPreconditionerGPU(); + + /*! + * \brief Apply the ILU preconditioner on the device. + */ + void ComputeILUPreconditionerGPU(const CSysVector& vec, CSysVector& prod) const; + public: /*! * \brief Constructor of the class. @@ -1043,49 +1114,6 @@ class CSysMatrix { void MatrixVectorProduct(const CSysVector& vec, CSysVector& prod, CGeometry* geometry, const CConfig* config) const; - /*! - * \brief Performs the product of a sparse matrix by a CSysVector. - * \param[in] vec - CSysVector to be multiplied by the sparse matrix A. - * \param[in] geometry - Geometrical definition of the problem. - * \param[in] config - Definition of the particular problem. - * \param[out] prod - Result of the product. - */ - void GPUMatrixVectorProduct(const CSysVector& vec, CSysVector& prod, CGeometry* geometry, - const CConfig* config) const; - - /*! - * \brief Performs first step of the LU_SGS Preconditioner building - * \param[in] vec - CSysVector to be multiplied by the sparse matrix A. - * \param[in] geometry - Geometrical definition of the problem. - * \param[in] config - Definition of the particular problem. - * \param[out] prod - Result of the product. - */ - void GPUFirstSymmetricIteration(ScalarType& vec, ScalarType& prod, CGeometry* geometry, const CConfig* config) const; - - /*! - * \brief Performs second step of the LU_SGS Preconditioner building - * \param[in] geometry - Geometrical definition of the problem. - * \param[in] config - Definition of the particular problem. - * \param[out] prod - Result of the product. - */ - void GPUSecondSymmetricIteration(ScalarType& prod, CGeometry* geometry, const CConfig* config) const; - - /*! - * \brief Performs Gaussian Elimination between diagional blocks of the matrix and the prod vector - * \param[in] geometry - Geometrical definition of the problem. - * \param[in] config - Definition of the particular problem. - * \param[out] prod - Result of the product. - */ - void GPUGaussElimination(ScalarType& prod, CGeometry* geometry, const CConfig* config) const; - - /*! - * \brief Multiply CSysVector by the preconditioner all of which are stored on the device - * \param[in] vec - CSysVector to be multiplied by the preconditioner. - * \param[out] prod - Result of the product A*vec. - */ - void GPUComputeLU_SGSPreconditioner(ScalarType& vec, ScalarType& prod, CGeometry* geometry, - const CConfig* config) const; - /*! * \brief Build the Jacobi preconditioner. */ @@ -1101,14 +1129,6 @@ class CSysMatrix { void ComputeJacobiPreconditioner(const CSysVector& vec, CSysVector& prod, CGeometry* geometry, const CConfig* config) const; - /*! - * \brief Apply the Jacobi preconditioner on the GPU/device side. - * \note This helper is intended as the implementation hook for GPU-resident Krylov solvers. - * The actual implementation belongs in CSysMatrixGPU.cu. - */ - void ComputeJacobiPreconditionerGPU(const CSysVector& vec, CSysVector& prod, - CGeometry* geometry, const CConfig* config) const; - /*! * \brief Build the ILU preconditioner. */ diff --git a/Common/include/linear_algebra/GPUComms.cuh b/Common/include/linear_algebra/GPUComms.cuh index eb727477f214..39268c10e5a3 100644 --- a/Common/include/linear_algebra/GPUComms.cuh +++ b/Common/include/linear_algebra/GPUComms.cuh @@ -9,7 +9,7 @@ * The SU2 Project is maintained by the SU2 Foundation * (http://su2foundation.org) * -* Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) +* 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 diff --git a/Common/include/toolboxes/SwapBytes.hpp b/Common/include/toolboxes/SwapBytes.hpp index 033dc49dd8dd..c09a91e8b12a 100644 --- a/Common/include/toolboxes/SwapBytes.hpp +++ b/Common/include/toolboxes/SwapBytes.hpp @@ -9,7 +9,7 @@ * The SU2 Project is maintained by the SU2 Foundation * (http://su2foundation.org) * - * Copyright 2012-2025, SU2 Contributors (cf. AUTHORS.md) + * 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 diff --git a/Common/include/toolboxes/random_toolbox.hpp b/Common/include/toolboxes/random_toolbox.hpp index 6ad052d9e52d..f2ad39a7aa2d 100644 --- a/Common/include/toolboxes/random_toolbox.hpp +++ b/Common/include/toolboxes/random_toolbox.hpp @@ -8,7 +8,7 @@ * The SU2 Project is maintained by the SU2 Foundation * (http://su2foundation.org) * - * Copyright 2012-2025, SU2 Contributors (cf. AUTHORS.md) + * 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 diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index a9ca92ab1454..e075375001b6 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -1969,9 +1969,11 @@ void CConfig::SetConfig_Options() { /* DESCRIPTION: Maximum number of iterations of the linear solver for the implicit formulation */ addUnsignedLongOption("LINEAR_SOLVER_ITER", Linear_Solver_Iter, 10); /* DESCRIPTION: Fill in level for the ILU preconditioner */ - addUnsignedShortOption("LINEAR_SOLVER_ILU_FILL_IN", Linear_Solver_ILU_n, 0); + addUnsignedShortOption("LINEAR_SOLVER_ILU_FILL_IN", IluOptions.FillIn, 0); /* DESCRIPTION: Use level scheduling for OMP parallelization of the ILU preconditioner */ - addBoolOption("LINEAR_SOLVER_ILU_LEVEL_SCHEDULING", Linear_Solver_ILU_levels, false); + addBoolOption("LINEAR_SOLVER_ILU_LEVEL_SCHEDULING", IluOptions.LevelScheduling, false); + /* DESCRIPTION: Number of colored Gauss-Seidel sweeps used to build the GPU ILU factorization */ + addUnsignedShortOption("LINEAR_SOLVER_ILU_GPU_SWEEPS", IluOptions.GPUSweeps, 2); /* DESCRIPTION: Maximum number of iterations of the linear solver for the implicit formulation */ addUnsignedLongOption("LINEAR_SOLVER_RESTART_FREQUENCY", Linear_Solver_Restart_Frequency, 10); /* DESCRIPTION: Number of vectors used for deflated restarts */ @@ -3184,6 +3186,9 @@ void CConfig::SetConfig_Options() { /* DESCRIPTION: Allow fallback to smaller edge color group sizes for the discrete adjoint and allow more colors. */ addBoolOption("EDGE_COLORING_RELAX_DISC_ADJ", edgeColoringRelaxDiscAdj, true); + /* DESCRIPTION: Number of concurrent BFS fronts used to build the RCM reordering (1 is standard single-seed RCM). */ + addUnsignedShortOption("RCM_NUM_SEEDS", rcmNumSeeds, 1); + /*--- options that are used for libROM ---*/ /*!\par CONFIG_CATEGORY:libROM options \ingroup Config*/ @@ -4135,10 +4140,15 @@ void CConfig::SetPostprocessing(SU2_COMPONENT val_software, unsigned short val_i nMGLevels = 0; if (!OptionIsSet("LINEAR_SOLVER_ILU_LEVEL_SCHEDULING")) { /*--- Different default behavior for this solver type. ---*/ - Linear_Solver_ILU_levels = true; + IluOptions.LevelScheduling = true; } } + if (IluOptions.GPUSweeps == 0) { + SU2_MPI::Error("LINEAR_SOLVER_ILU_GPU_SWEEPS must be at least 1; 0 sweeps never factorizes the preconditioner.", + CURRENT_FUNCTION); + } + Radiation = (Kind_Radiation != RADIATION_MODEL::NONE); /*--- Check for unsupported features. ---*/ @@ -7458,7 +7468,7 @@ void CConfig::SetOutput(SU2_COMPONENT val_software, unsigned short val_izone) { } } switch (Kind_Linear_Solver_Prec) { - case ILU: cout << "Using ILU("<< Linear_Solver_ILU_n <<") preconditioning."<< endl; break; + case ILU: cout << "Using ILU("<< IluOptions.FillIn <<") preconditioning."<< endl; break; case LINELET: cout << "Using linelet preconditioning."<< endl; break; case LU_SGS: cout << "Using LU-SGS preconditioning."<< endl; break; case Q_LU_SGS: cout << "Using LU-SGS preconditioning with matrix quantization."<< endl; break; @@ -7467,7 +7477,7 @@ void CConfig::SetOutput(SU2_COMPONENT val_software, unsigned short val_izone) { break; case SMOOTHER: switch (Kind_Linear_Solver_Prec) { - case ILU: cout << "A ILU(" << Linear_Solver_ILU_n << ")"; break; + case ILU: cout << "A ILU(" << IluOptions.FillIn << ")"; break; case LINELET: cout << "A Linelet"; break; case LU_SGS: cout << "A LU-SGS"; break; case Q_LU_SGS: cout << "A quantized LU-SGS"; break; diff --git a/Common/src/geometry/CPhysicalGeometry.cpp b/Common/src/geometry/CPhysicalGeometry.cpp index 5d4c77b8b2ae..199c8cec03f6 100644 --- a/Common/src/geometry/CPhysicalGeometry.cpp +++ b/Common/src/geometry/CPhysicalGeometry.cpp @@ -4505,9 +4505,15 @@ void CPhysicalGeometry::SetRCM_Ordering(CConfig* config) { InQueue[iPoint] = true; } + const auto numSeeds = std::max(1, config->GetRCM_NumSeeds()); + constexpr auto unreached = std::numeric_limits::max(); + vector dist; + if (numSeeds > 1) dist.assign(nPoint, unreached); + vector component, bfsQueue; + /*--- Repeat as many times as necessary to handle disconnected graphs. ---*/ while (Result.size() < nPointDomain) { - /*--- Select the node with the lowest degree in the grid. ---*/ + /*--- Select the node with the lowest degree in the grid as the first seed. ---*/ auto AddPoint = nPoint; auto MinDegree = std::numeric_limits::max(); for (auto iPoint = 0ul; iPoint < nPointDomain; iPoint++) { @@ -4521,11 +4527,54 @@ void CPhysicalGeometry::SetRCM_Ordering(CConfig* config) { SU2_MPI::Error("RCM ordering failed", CURRENT_FUNCTION); } - /*--- Seed the queue with the minimum degree node. ---*/ - Result.push_back(AddPoint); - InQueue[AddPoint] = true; + /*--- Farthest-point sampling: grow the seed set with up to numSeeds-1 more points, each + * the node with the largest BFS distance (within this connected component) from every + * seed picked so far. Starting the RCM growth from several spread-out fronts instead of + * one bounds the number of levels by the covering radius of the seed set rather than the + * full component diameter, while keeping the RCM ordering local (and hence bandwidth and + * ILU quality) around each front. + * The distance from each point to its nearest seed is maintained incrementally: the first + * seed does a full BFS of the component, each later seed only relaxes the points it is + * strictly closer to than all previous seeds, keeping the total work close to a single + * BFS instead of one BFS per seed. ---*/ + vector Seeds(1, AddPoint); + if (numSeeds > 1) { + auto relaxFrom = [&](unsigned long seed) { + dist[seed] = 0; + bfsQueue.clear(); + bfsQueue.push_back(seed); + for (auto iBfs = 0ul; iBfs < bfsQueue.size(); ++iBfs) { + const auto iPoint = bfsQueue[iBfs]; + for (auto iNode = 0u; iNode < nodes->GetnPoint(iPoint); iNode++) { + const auto jPoint = nodes->GetPoint(iPoint, iNode); + if (!InQueue[jPoint] && dist[iPoint] + 1 < dist[jPoint]) { + dist[jPoint] = dist[iPoint] + 1; + bfsQueue.push_back(jPoint); + } + } + } + }; + relaxFrom(AddPoint); + /*--- The first BFS reaches exactly the connected component of the seed. ---*/ + component = bfsQueue; + for (auto iSeed = 1u; iSeed < numSeeds; ++iSeed) { + auto farthest = AddPoint; + for (const auto iPoint : component) + if (dist[iPoint] > dist[farthest]) farthest = iPoint; + /*--- The component is already fully covered by the existing seeds. ---*/ + if (dist[farthest] == 0) break; + Seeds.push_back(farthest); + relaxFrom(farthest); + } + } + + /*--- Seed the queue with all selected fronts. ---*/ + for (auto seed : Seeds) { + Result.push_back(seed); + InQueue[seed] = true; + } - /*--- Loop until reorganizing all nodes connected to AddPoint. This will + /*--- Loop until reorganizing all nodes connected to the seeds. This will * also terminate early once the ordering + queue include all points. ---*/ while (QueueStart < Result.size() && Result.size() < nPointDomain) { /*--- Move the start of the queue, equivalent to taking from the front of diff --git a/Common/src/geometry/meshreader/CSU2BinaryMeshReaderBase.cpp b/Common/src/geometry/meshreader/CSU2BinaryMeshReaderBase.cpp index ce7d03cb3e3c..9e0e9c02a65f 100644 --- a/Common/src/geometry/meshreader/CSU2BinaryMeshReaderBase.cpp +++ b/Common/src/geometry/meshreader/CSU2BinaryMeshReaderBase.cpp @@ -9,7 +9,7 @@ * The SU2 Project is maintained by the SU2 Foundation * (http://su2foundation.org) * - * Copyright 2012-2025, SU2 Contributors (cf. AUTHORS.md) + * 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 diff --git a/Common/src/geometry/meshreader/CSU2BinaryMeshReaderFEM.cpp b/Common/src/geometry/meshreader/CSU2BinaryMeshReaderFEM.cpp index e3d4ceed2e6c..cbbc4826e9d1 100644 --- a/Common/src/geometry/meshreader/CSU2BinaryMeshReaderFEM.cpp +++ b/Common/src/geometry/meshreader/CSU2BinaryMeshReaderFEM.cpp @@ -10,7 +10,7 @@ * The SU2 Project is maintained by the SU2 Foundation * (http://su2foundation.org) * - * Copyright 2012-2025, SU2 Contributors (cf. AUTHORS.md) + * 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 diff --git a/Common/src/geometry/meshreader/CSU2BinaryMeshReaderFVM.cpp b/Common/src/geometry/meshreader/CSU2BinaryMeshReaderFVM.cpp index 96905c0512a2..4c235416ebdc 100644 --- a/Common/src/geometry/meshreader/CSU2BinaryMeshReaderFVM.cpp +++ b/Common/src/geometry/meshreader/CSU2BinaryMeshReaderFVM.cpp @@ -10,7 +10,7 @@ * The SU2 Project is maintained by the SU2 Foundation * (http://su2foundation.org) * - * Copyright 2012-2025, SU2 Contributors (cf. AUTHORS.md) + * 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 diff --git a/Common/src/geometry/meshreader/CSU2MeshReaderBase.cpp b/Common/src/geometry/meshreader/CSU2MeshReaderBase.cpp index ef919ef724b1..752d9cbdfd0d 100644 --- a/Common/src/geometry/meshreader/CSU2MeshReaderBase.cpp +++ b/Common/src/geometry/meshreader/CSU2MeshReaderBase.cpp @@ -9,7 +9,7 @@ * The SU2 Project is maintained by the SU2 Foundation * (http://su2foundation.org) * - * Copyright 2012-2025, SU2 Contributors (cf. AUTHORS.md) + * 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 diff --git a/Common/src/linear_algebra/CSysMatrix.cpp b/Common/src/linear_algebra/CSysMatrix.cpp index a0013e243002..c9668aa0e88e 100644 --- a/Common/src/linear_algebra/CSysMatrix.cpp +++ b/Common/src/linear_algebra/CSysMatrix.cpp @@ -28,6 +28,7 @@ #include "../../include/linear_algebra/CSysMatrix.inl" #include "../../include/geometry/CGeometry.hpp" +#include "../../include/linear_algebra/CMatrixInverse.hpp" #include "../../include/toolboxes/allocation_toolbox.hpp" #include @@ -47,6 +48,19 @@ FORCEINLINE void RegularizePivot(ScalarType& pivot, unsigned long row, unsigned #endif } } + +/*--- Common failure path for a device dispatch that is not available in this build/scalar type + * combination, called with CURRENT_FUNCTION so the error names the right caller. ---*/ +void GPUNotAvailable(const char* caller) { +#ifdef SU2_ENABLE_CUDA_KERNELS + SU2_MPI::Error("GPU acceleration is not supported for AD scalar types.", caller); +#else + SU2_MPI::Error( + "ENABLE_CUDA is set to YES but SU2 was not compiled with CUDA support; " + "recompile with CUDA enabled in Meson to use GPU functions.", + caller); +#endif +} } // namespace template @@ -108,12 +122,13 @@ CSysMatrix::~CSysMatrix() { SU2_ZONE_SCOPED delete[] omp_partitions; - MemoryAllocation::aligned_free(ilu.l); - MemoryAllocation::aligned_free(ilu.d); - MemoryAllocation::aligned_free(ilu.u); - MemoryAllocation::aligned_free(mat.d); - MemoryAllocation::aligned_free(mat.l); - MemoryAllocation::aligned_free(mat.u); + auto freeHostLDU = [](LDU& m) { + MemoryAllocation::aligned_free(m.d); + MemoryAllocation::aligned_free(m.l); + MemoryAllocation::aligned_free(m.u); + }; + freeHostLDU(mat); + freeHostLDU(ilu); MemoryAllocation::aligned_free(invM); MemoryAllocation::aligned_free(q_scale_l); MemoryAllocation::aligned_free(q_blocks_l); @@ -123,14 +138,25 @@ CSysMatrix::~CSysMatrix() { MemoryAllocation::aligned_free(q_blocks_d); if (useCuda) { - GPUMemoryAllocation::gpu_free(gpu.d); - GPUMemoryAllocation::gpu_free(gpu.l); - GPUMemoryAllocation::gpu_free(gpu.u); - GPUMemoryAllocation::gpu_free(gpu.row_ptr_l); - GPUMemoryAllocation::gpu_free(gpu.col_ind_l); - GPUMemoryAllocation::gpu_free(gpu.row_ptr_u); - GPUMemoryAllocation::gpu_free(gpu.col_ind_u); + auto freeLDU = [](LDU& m) { + GPUMemoryAllocation::gpu_free(m.d); + GPUMemoryAllocation::gpu_free(m.l); + GPUMemoryAllocation::gpu_free(m.u); + GPUMemoryAllocation::gpu_free(m.row_ptr_l); + GPUMemoryAllocation::gpu_free(m.col_ind_l); + GPUMemoryAllocation::gpu_free(m.row_ptr_u); + GPUMemoryAllocation::gpu_free(m.col_ind_u); + }; + freeLDU(gpu); + freeLDU(gpu_ilu); GPUMemoryAllocation::gpu_free(d_invM); + GPUMemoryAllocation::gpu_free(d_ilu_color_idx); + GPUMemoryAllocation::gpu_free(d_ilu_level_idx); +#ifdef SU2_ENABLE_CUDA_KERNELS + if (ilu_build_graph_exec != nullptr) cudaGraphExecDestroy(ilu_build_graph_exec); + if (ilu_apply_graph_exec != nullptr) cudaGraphExecDestroy(ilu_apply_graph_exec); + if (ilu_stream != nullptr) cudaStreamDestroy(ilu_stream); +#endif } #ifdef USE_MKL @@ -182,6 +208,10 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi const bool ilu_needed = (prec == ILU); const bool diag_needed = (prec == JACOBI) || (prec == LINELET); + + /*--- Linelet also builds the Jacobi preconditioner but reads the inverse diagonal blocks on + * the host, so only plain Jacobi can keep them exclusively on the device. ---*/ + jacobi_on_device = useCuda && (prec == JACOBI); #ifndef CODI_REVERSE_TYPE const bool q_lus_needed = allow_quant && !useCuda && (prec == Q_LU_SGS); #else @@ -232,13 +262,17 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi allocAndInit(mat.u, mat.nnz_u * nVar * nEqn); } + auto GPUAllocAndInit = [](ScalarType*& ptr, unsigned long num) { + ptr = GPUMemoryAllocation::gpu_alloc(num * sizeof(ScalarType)); + }; + auto GPUAllocAndCopy = [](const su2uint*& ptr, const su2uint* src_ptr, unsigned long num) { + ptr = GPUMemoryAllocation::gpu_alloc_cpy(src_ptr, num * sizeof(su2uint)); + }; + if (useCuda) { - auto GPUAllocAndInit = [](ScalarType*& ptr, unsigned long num) { - ptr = GPUMemoryAllocation::gpu_alloc(num * sizeof(ScalarType)); - }; - auto GPUAllocAndCopy = [](const su2uint*& ptr, const su2uint* src_ptr, unsigned long num) { - ptr = GPUMemoryAllocation::gpu_alloc_cpy(src_ptr, num * sizeof(su2uint)); - }; + if (nVar != nEqn) { + SU2_MPI::Error("CUDA CSysMatrix block-LDU SpMV requires square blocks.", CURRENT_FUNCTION); + } GPUAllocAndInit(gpu.d, nPoint * nVar * nEqn); GPUAllocAndInit(gpu.l, mat.nnz_l * nVar * nEqn); GPUAllocAndInit(gpu.u, mat.nnz_u * nVar * nEqn); @@ -259,7 +293,8 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi /*--- Get ILU sparse pattern, if fill is 0 no new data is allocated. --*/ if (ilu_needed) { - ilu_fill_in = config->GetLinear_Solver_ILU_n(); + ilu_fill_in = config->GetIluOptions().FillIn; + ilu_gpu_sweeps = config->GetIluOptions().GPUSweeps; const auto& pat_ilu = geometry->GetSparsePattern(type, ilu_fill_in); ilu.row_ptr_l = pat_ilu.l.outerPtr(); @@ -269,9 +304,59 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi ilu.col_ind_u = pat_ilu.u.innerIdx(); ilu.nnz_u = pat_ilu.u.getNumNonZeros(); - if (omp_get_max_threads() > 1 && config->GetLinear_Solver_ILU_levels()) { + /*--- The GPU triangular solves are level-scheduled (exact, one pass per level), so they need + * levels_ilu unconditionally; the host/OMP path only needs it when both multi-threaded and + * requested via config. ---*/ + if (useCuda || (omp_get_max_threads() > 1 && config->GetIluOptions().LevelScheduling)) { levels_ilu = computeLevels(pat_ilu.l); } + + /*--- Coloring for the GPU iterative factorization, see IluFactorColorKernel. Colors are + * true independent sets of the (domain-only, symmetric) dependency graph, computed the same + * way SU2 already colors edges/elements for OMP loops, just applied to the ILU pattern + * instead. This does not change the elimination order/pattern (nothing here affects L/U + * membership), only how the build is scheduled on the device. ---*/ + if (useCuda) { + std::vector adjPtr(nPointDomain + 1, 0); + std::vector adjIdx; + adjIdx.reserve(ilu.nnz_l + ilu.nnz_u); + for (auto i = 0ul; i < nPointDomain; ++i) { + adjPtr[i] = static_cast(adjIdx.size()); + for (auto k = ilu.row_ptr_l[i]; k < ilu.row_ptr_l[i + 1]; ++k) adjIdx.push_back(ilu.col_ind_l[k]); + for (auto k = ilu.row_ptr_u[i]; k < ilu.row_ptr_u[i + 1]; ++k) { + const auto j = ilu.col_ind_u[k]; + if (j < nPointDomain) adjIdx.push_back(static_cast(j)); + } + } + adjPtr[nPointDomain] = static_cast(adjIdx.size()); + color_ilu = colorSparsePattern(CCompressedSparsePatternUL(adjPtr, adjIdx), 1, true, false); + + /*--- Report, across ranks, how many colors/levels the GPU ILU ends up scheduled over and how + * wide those groups are on average. Few, wide colors/levels use the GPU efficiently; many + * narrow ones (small average size) serialize into many small kernel launches instead. ---*/ + const auto nColorsLocal = static_cast(color_ilu.getOuterSize()); + const auto nLevelsLocal = static_cast(levels_ilu.getOuterSize()); + unsigned long nColorsMax = 0, nLevelsMax = 0; + SU2_MPI::Reduce(&nColorsLocal, &nColorsMax, 1, MPI_UNSIGNED_LONG, MPI_MAX, MASTER_NODE, SU2_MPI::GetComm()); + SU2_MPI::Reduce(&nLevelsLocal, &nLevelsMax, 1, MPI_UNSIGNED_LONG, MPI_MAX, MASTER_NODE, SU2_MPI::GetComm()); + + const auto avgColorSizeLocal = static_cast(std::lround(double(nPointDomain) / nColorsLocal)); + const auto avgLevelSizeLocal = static_cast(std::lround(double(nPointDomain) / nLevelsLocal)); + unsigned long minAvgColorSize = 0, minAvgLevelSize = 0; + SU2_MPI::Reduce(&avgColorSizeLocal, &minAvgColorSize, 1, MPI_UNSIGNED_LONG, MPI_MIN, MASTER_NODE, + SU2_MPI::GetComm()); + SU2_MPI::Reduce(&avgLevelSizeLocal, &minAvgLevelSize, 1, MPI_UNSIGNED_LONG, MPI_MIN, MASTER_NODE, + SU2_MPI::GetComm()); + + static bool printed = false; + if (rank == MASTER_NODE && !printed) { + cout << "GPU ILU scheduling (worst rank): " << nColorsMax << " colors for the factorization (~" + << minAvgColorSize << " points/color on average),\n" + << " " << nLevelsMax << " levels for the triangular solves (~" << minAvgLevelSize + << " points/level on average)." << endl; + printed = true; + } + } } /*--- Preconditioners. ---*/ @@ -284,10 +369,64 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi if (diag_needed) allocAndInit(invM, nPointDomain * nVar * nEqn); - if (useCuda && diag_needed) { + if (jacobi_on_device) { + if (nVar != nEqn) { + SU2_MPI::Error("CUDA Jacobi preconditioner requires square blocks.", CURRENT_FUNCTION); + } + if (nVar * nVar > 1024) { + SU2_MPI::Error("CUDA Jacobi preconditioner uses one thread per block entry, nVar is too large.", + CURRENT_FUNCTION); + } d_invM = GPUMemoryAllocation::gpu_alloc(nPointDomain * nVar * nEqn * sizeof(ScalarType)); } + if (useCuda && ilu_needed) { + if (nVar != nEqn) { + SU2_MPI::Error("CUDA ILU factorization requires square blocks.", CURRENT_FUNCTION); + } + if (nVar * nVar > 1024) { + SU2_MPI::Error("CUDA ILU factorization uses one thread per block entry, nVar is too large.", CURRENT_FUNCTION); + } + /*--- The factors are built and used on the device, only the pattern and the level table + * are uploaded (once, here) because they do not change. ---*/ + gpu_ilu.nnz_l = ilu.nnz_l; + gpu_ilu.nnz_u = ilu.nnz_u; + GPUAllocAndInit(gpu_ilu.d, nPointDomain * nVar * nEqn); + GPUAllocAndInit(gpu_ilu.l, ilu.nnz_l * nVar * nEqn); + GPUAllocAndInit(gpu_ilu.u, ilu.nnz_u * nVar * nEqn); + GPUAllocAndCopy(gpu_ilu.row_ptr_l, ilu.row_ptr_l, nPointDomain + 1); + GPUAllocAndCopy(gpu_ilu.col_ind_l, ilu.col_ind_l, ilu.nnz_l); + GPUAllocAndCopy(gpu_ilu.row_ptr_u, ilu.row_ptr_u, nPointDomain + 1); + GPUAllocAndCopy(gpu_ilu.col_ind_u, ilu.col_ind_u, ilu.nnz_u); + + /*--- Flatten the coloring, the index type differs from the one of the pattern. It drives + * the factorization on the device. ---*/ + std::vector color_idx; + color_idx.reserve(nPointDomain); + ilu_color_ptr.clear(); + ilu_color_ptr.push_back(0); + for (auto color = 0ul; color < color_ilu.getOuterSize(); ++color) { + for (auto k = 0ul; k < color_ilu.getNumNonZeros(color); ++k) { + color_idx.push_back(static_cast(color_ilu.getInnerIdx(color, k))); + } + ilu_color_ptr.push_back(static_cast(color_idx.size())); + } + d_ilu_color_idx = GPUMemoryAllocation::gpu_alloc_cpy(color_idx.data(), color_idx.size() * sizeof(su2uint)); + + /*--- Flatten levels_ilu the same way. It drives both triangular solves on the device. ---*/ + std::vector level_idx; + level_idx.reserve(nPointDomain); + ilu_level_ptr.clear(); + ilu_level_ptr.push_back(0); + for (auto level = 0ul; level < levels_ilu.getOuterSize(); ++level) { + for (auto k = 0ul; k < levels_ilu.getNumNonZeros(level); ++k) { + level_idx.push_back(static_cast(levels_ilu.getInnerIdx(level, k))); + } + ilu_level_ptr.push_back(static_cast(level_idx.size())); + } + d_ilu_level_idx = GPUMemoryAllocation::gpu_alloc_cpy(level_idx.data(), level_idx.size() * sizeof(su2uint)); + } + /*--- Thread parallel initialization. ---*/ int num_threads = omp_get_max_threads(); @@ -688,15 +827,16 @@ void CSysMatrix::MatrixInverse(ScalarType* matrix, ScalarType* inver assert((matrix != inverse) && "Output cannot be the same as the input."); + /*--- Inversion ---*/ +#ifdef USE_MKL_LAPACK + // With MKL_DIRECT_CALL enabled, this is significantly faster than native code on Intel Architectures. #define M(I, J) inverse[(I)*nVar + (J)] - /*--- Initialize the inverse with the identity. ---*/ + /*--- Initialize the inverse with the identity, LAPACKE_?getrs solves for it as the rhs. ---*/ for (auto iVar = 0ul; iVar < nVar; iVar++) for (auto jVar = 0ul; jVar < nVar; jVar++) M(iVar, jVar) = ScalarType(iVar == jVar); +#undef M - /*--- Inversion ---*/ -#ifdef USE_MKL_LAPACK - // With MKL_DIRECT_CALL enabled, this is significantly faster than native code on Intel Architectures. lapack_int ipiv[MAXNVAR]; if constexpr (std::is_same_v) { LAPACKE_dgetrf(LAPACK_ROW_MAJOR, nVar, nVar, matrix, nVar, ipiv); @@ -707,38 +847,9 @@ void CSysMatrix::MatrixInverse(ScalarType* matrix, ScalarType* inver LAPACKE_sgetrs(LAPACK_ROW_MAJOR, 'N', nVar, nVar, matrix, nVar, ipiv, inverse, nVar); } #else -#define A(I, J) matrix[(I)*nVar + (J)] - - /*--- Transform system in Upper Matrix ---*/ - for (auto iVar = 1ul; iVar < nVar; iVar++) { - for (auto jVar = 0ul; jVar < iVar; jVar++) { - /*--- Regularize pivot if too small to prevent divide-by-zero ---*/ - RegularizePivot(A(jVar, jVar), jVar, jVar, "MatrixInverse"); - - ScalarType weight = A(iVar, jVar) / A(jVar, jVar); - for (auto kVar = jVar; kVar < nVar; kVar++) A(iVar, kVar) -= weight * A(jVar, kVar); - - /*--- at this stage M is lower triangular so not all cols need updating ---*/ - for (auto kVar = 0ul; kVar <= jVar; kVar++) M(iVar, kVar) -= weight * M(jVar, kVar); - } - } - - /*--- Backwards substitution ---*/ - for (auto iVar = nVar; iVar > 0ul;) { - iVar--; // unsigned type - for (auto jVar = iVar + 1; jVar < nVar; jVar++) - for (auto kVar = 0ul; kVar < nVar; kVar++) M(iVar, kVar) -= A(iVar, jVar) * M(jVar, kVar); - - /*--- Regularize diagonal if too small ---*/ - RegularizePivot(A(iVar, iVar), iVar, iVar, "DEBUG MatrixInverse backsubst"); - - for (auto kVar = 0ul; kVar < nVar; kVar++) { - M(iVar, kVar) /= A(iVar, iVar); - } - } -#undef A + /*--- Shared with the device implementation, see CMatrixInverse.hpp. ---*/ + SU2_LinAlg::MatrixInverse(nVar, matrix, inverse); #endif -#undef M } template @@ -771,6 +882,22 @@ template void CSysMatrix::MatrixVectorProduct(const CSysVector& vec, CSysVector& prod, CGeometry* geometry, const CConfig* config) const { SU2_ZONE_SCOPED + + if (useCuda) { +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + BEGIN_SU2_DEVICE_REGION + MatrixVectorProductGPU(vec, prod, geometry, config); + END_SU2_DEVICE_REGION + return; + } else { + GPUNotAvailable(CURRENT_FUNCTION); + } +#else + GPUNotAvailable(CURRENT_FUNCTION); +#endif + } + /*--- Some checks for consistency between CSysMatrix and the CSysVectors ---*/ #ifndef NDEBUG if ((nEqn != vec.GetNVar()) || (nVar != prod.GetNVar())) { @@ -811,28 +938,24 @@ template void CSysMatrix::BuildJacobiPreconditioner() { SU2_ZONE_SCOPED - /*--- Build Jacobi preconditioner (M = D), compute and store the inverses of the diagonal blocks. ---*/ - SU2_OMP_FOR_DYN(omp_heavy_size) - for (unsigned long iPoint = 0; iPoint < nPointDomain; iPoint++) - InverseDiagonalBlock(iPoint, &(invM[iPoint * nVar * nVar])); - END_SU2_OMP_FOR - - if (useCuda) { + if (jacobi_on_device) { #ifdef SU2_ENABLE_CUDA_KERNELS if constexpr (su2_gpu_capable_v) { - BEGIN_SU2_DEVICE_REGION - gpuErrChk(cudaMemcpy(d_invM, invM, nPointDomain * nVar * nVar * sizeof(ScalarType), cudaMemcpyHostToDevice)); - END_SU2_DEVICE_REGION + SU2_DEVICE_REGION(BuildJacobiPreconditionerGPU();) + return; } else { - SU2_MPI::Error("GPU acceleration is not supported for AD scalar types.", CURRENT_FUNCTION); + GPUNotAvailable(CURRENT_FUNCTION); } #else - SU2_MPI::Error( - "\nError in building Jacobi preconditioner\nENABLE_CUDA is set to YES\nPlease compile with CUDA options " - "enabled in Meson to access GPU Functions", - CURRENT_FUNCTION); + GPUNotAvailable(CURRENT_FUNCTION); #endif } + + /*--- Build Jacobi preconditioner (M = D), compute and store the inverses of the diagonal blocks. ---*/ + SU2_OMP_FOR_DYN(omp_heavy_size) + for (unsigned long iPoint = 0; iPoint < nPointDomain; iPoint++) + InverseDiagonalBlock(iPoint, &(invM[iPoint * nVar * nVar])); + END_SU2_OMP_FOR } template @@ -847,13 +970,10 @@ void CSysMatrix::ComputeJacobiPreconditioner(const CSysVector::ComputeJacobiPreconditioner(const CSysVector void CSysMatrix::BuildILUPreconditioner() { SU2_ZONE_SCOPED + + if (useCuda) { +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + SU2_DEVICE_REGION(BuildILUPreconditionerGPU();) + return; + } else { + GPUNotAvailable(CURRENT_FUNCTION); + } +#else + GPUNotAvailable(CURRENT_FUNCTION); +#endif + } + const auto blockSize = nVar * nVar; ScalarType Lij[MAXNVAR * MAXNVAR], Lij_Ujk[MAXNVAR * MAXNVAR]; @@ -1002,6 +1136,20 @@ template void CSysMatrix::ComputeILUPreconditioner(const CSysVector& vec, CSysVector& prod, CGeometry* geometry, const CConfig* config) const { SU2_ZONE_SCOPED + + if (useCuda) { +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + SU2_DEVICE_REGION(ComputeILUPreconditionerGPU(vec, prod);) + return; + } else { + GPUNotAvailable(CURRENT_FUNCTION); + } +#else + GPUNotAvailable(CURRENT_FUNCTION); +#endif + } + /*--- Coherent view of vectors. ---*/ SU2_OMP_BARRIER diff --git a/Common/src/linear_algebra/CSysMatrixGPU.cu b/Common/src/linear_algebra/CSysMatrixGPU.cu index 381379a0d812..1cd5de51d5ce 100644 --- a/Common/src/linear_algebra/CSysMatrixGPU.cu +++ b/Common/src/linear_algebra/CSysMatrixGPU.cu @@ -1,7 +1,7 @@ /*! * \file CSysMatrixGPU.cu * \brief Implementations of Kernels and Functions for Matrix Operations on the GPU - * \author A. Raj + * \author A. Raj, Jesse Li, P. Gomes * \version 8.5.0 "Harrier" * * SU2 Project Website: https://su2code.github.io @@ -9,7 +9,7 @@ * The SU2 Project is maintained by the SU2 Foundation * (http://su2foundation.org) * - * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) + * 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 @@ -25,9 +25,295 @@ * License along with SU2. If not, see . */ -#include "../../include/linear_algebra/CSysMatrix.hpp" +#include + +#include "../../include/linear_algebra/CMatrixInverse.hpp" +#include "../../include/linear_algebra/CSysMatrix.inl" #include "../../include/linear_algebra/GPUComms.cuh" +namespace { + +template +__global__ void ApplyJacobiPreconditionerKernel(const ScalarType* invM, const ScalarType* vec, ScalarType* prod, + unsigned long nPointDomain, unsigned long nVar) { + const auto iPoint = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; + if (iPoint >= nPointDomain) return; + + const auto block = &invM[iPoint * nVar * nVar]; + const auto rhs = &vec[iPoint * nVar]; + auto out = &prod[iPoint * nVar]; + + for (auto iVar = 0ul; iVar < nVar; ++iVar) { + auto sum = ScalarType(0); + for (auto jVar = 0ul; jVar < nVar; ++jVar) { + sum += block[iVar * nVar + jVar] * rhs[jVar]; + } + out[iVar] = sum; + } +} + +/*--- ILU. The factorization is scheduled by coloring: colors are true independent sets of the + * ILU dependency graph (no two same-colored rows depend on each other in either direction), so a + * color's rows can be processed with zero races in one kernel launch, but since a color is wider + * and less ordered than a level, one pass over all colors is only an approximation, not an exact + * result — several sweeps (repeated passes) are needed to converge. The triangular solves are + * scheduled by level instead: a level's rows only depend on earlier levels (already finalized), + * so one pass over the levels, in order, is exact — no sweeping needed there. Both the color and + * level rows are scattered through the matrix, hence the indirection through their tables. + * Throughout, one CUDA block works on one row. ---*/ + +/*! + * \brief The pointers of an LDU-partitioned matrix, all in device memory. This mirrors the + * private CSysMatrix::LDU, which the kernels cannot name. + */ +template +struct DeviceLDU { + ScalarType* d; + ScalarType* l; + ScalarType* u; + const su2uint* row_ptr_l; + const su2uint* col_ind_l; + const su2uint* row_ptr_u; + const su2uint* col_ind_u; +}; + +/*! + * \brief Start of block (i,j), or nullptr if it is not a nonzero of the pattern. + */ +template +__device__ FORCEINLINE ScalarType* GetBlockILU(const DeviceLDU& M, unsigned long nVar, + unsigned long block_i, unsigned long block_j) { + const auto blockSize = nVar * nVar; + if (block_i == block_j) return M.d + block_i * blockSize; + + const bool lower = block_j < block_i; + const auto* row_ptr = lower ? M.row_ptr_l : M.row_ptr_u; + const auto* col_ind = lower ? M.col_ind_l : M.col_ind_u; + auto* vals = lower ? M.l : M.u; + + for (auto k = row_ptr[block_i]; k < row_ptr[block_i + 1]; ++k) { + if (col_ind[k] == block_j) return vals + k * blockSize; + } + return nullptr; +} + +/*! + * \brief Invert the diagonal blocks of the matrix, device version of InverseDiagonalBlock. + * \note Grid: one block per row, blockDim.x == nVar*nVar (one thread per block entry, they + * stage the input in shared memory). Dynamic shared memory: nVar*nVar scalars. + */ +template +__global__ void InvertDiagonalBlocksKernel(unsigned long nRows, unsigned long nVar, + const ScalarType* __restrict__ mat_d, ScalarType* __restrict__ invM) { + const unsigned long iRow = blockIdx.x; + if (iRow >= nRows) return; + + const auto blockSize = nVar * nVar; + const unsigned long tid = threadIdx.x; + + /*--- The inversion destroys its input, so it cannot work on the matrix itself. ---*/ + extern __shared__ __align__(sizeof(double)) char smem[]; + auto* work = reinterpret_cast(smem); + + work[tid] = mat_d[iRow * blockSize + tid]; + __syncthreads(); + + if (tid == 0) SU2_LinAlg::MatrixInverse(nVar, work, invM + iRow * blockSize); +} + +/*! + * \brief Factorize the rows of one color, one sweep of an iterative (colored Gauss-Seidel) + * ILU factorization: same order/pattern as the exact level-scheduled algorithm (this + * does not change L/U membership, so it converges to the exact same fixed point), but + * colors are true independent sets (zero dependency between same-colored rows in either + * direction), so a color can be processed with zero races in far fewer, wider launches + * than the number of levels — at the cost of needing several sweeps (repeated passes + * over all colors) instead of one exact pass, because for a fixed order the level count + * is already the minimum number of race-free single-pass groups (Mirsky's theorem). + * \note Every visit of a row (there is one per sweep) resets it from the original matrix first + * (folding in the device version of the InitIluRow helper of BuildILUPreconditioner), + * because the elimination below is a re-evaluation of the row's defining equation using + * the current (possibly stale) values of other rows, not an incremental accumulation. + * Grid: one block per row of the color, blockDim.x == nVar*nVar (one thread per block + * entry, so that the small matrix products are one dot product per thread). Dynamic + * shared memory: 2*nVar*nVar scalars. + */ +template +__global__ void IluFactorColorKernel(const su2uint* __restrict__ color_idx, unsigned long color_begin, + unsigned long color_size, unsigned long nRows, unsigned long nVar, + DeviceLDU A, DeviceLDU M) { + if (blockIdx.x >= color_size) return; + + const unsigned long iRow = color_idx[color_begin + blockIdx.x]; + const auto blockSize = nVar * nVar; + const unsigned long tid = threadIdx.x; + const auto iVar = tid / nVar, jVar = tid % nVar; + + extern __shared__ __align__(sizeof(double)) char smem[]; + auto* Lij = reinterpret_cast(smem); + auto* work = Lij + blockSize; + + /*--- Reset this row to the raw matrix entries (device version of InitIluRow, but for one + * row instead of the whole matrix, since here it runs once per row per sweep). ---*/ + M.d[iRow * blockSize + tid] = A.d[iRow * blockSize + tid]; + auto scatter = [&](const su2uint* a_row_ptr, const su2uint* a_col_ind, const ScalarType* a_vals, + const su2uint* m_row_ptr, const su2uint* m_col_ind, ScalarType* m_vals) { + auto ka = a_row_ptr[iRow]; + const auto ka_end = a_row_ptr[iRow + 1]; + for (auto k = m_row_ptr[iRow]; k < m_row_ptr[iRow + 1]; ++k) { + const auto jPoint = m_col_ind[k]; + while (ka < ka_end && a_col_ind[ka] < jPoint) ++ka; + if (ka < ka_end && a_col_ind[ka] == jPoint) { + m_vals[k * blockSize + tid] = a_vals[ka * blockSize + tid]; + } else { + m_vals[k * blockSize + tid] = ScalarType(0); + } + } + }; + scatter(A.row_ptr_l, A.col_ind_l, A.l, M.row_ptr_l, M.col_ind_l, M.l); + scatter(A.row_ptr_u, A.col_ind_u, A.u, M.row_ptr_u, M.col_ind_u, M.u); + __syncthreads(); + + /*--- For this row (unknown), loop over its lower diagonal entries. ---*/ + for (auto kl = M.row_ptr_l[iRow]; kl < M.row_ptr_l[iRow + 1]; ++kl) { + /*--- All threads must be done with the previous entry: Lij is about to be overwritten, + * and the blocks of this row updated below are read here across threads. ---*/ + __syncthreads(); + + /*--- jPoint is the column index (jPoint < iRow). ---*/ + const unsigned long jPoint = M.col_ind_l[kl]; + + /*--- Multiply the block by the inverse of the corresponding diagonal block. ---*/ + auto* Block_ij = M.l + kl * blockSize; + const auto* invUjj = M.d + jPoint * blockSize; + + ScalarType sum = 0; + for (auto k = 0ul; k < nVar; ++k) sum += Block_ij[iVar * nVar + k] * invUjj[k * nVar + jVar]; + Lij[tid] = sum; + __syncthreads(); + + /*--- Lij holds Aij*inv(Ujj). Jump to the upper part of the jPoint row. ---*/ + for (auto ku = M.row_ptr_u[jPoint]; ku < M.row_ptr_u[jPoint + 1]; ++ku) { + /*--- Get the column index (kPoint > jPoint), halo columns are not factorized. ---*/ + const unsigned long kPoint = M.col_ind_u[ku]; + if (kPoint >= nRows) break; + + /*--- If Aik exists, update it: Aik -= Lij * Ujk ---*/ + auto* Block_ik = GetBlockILU(M, nVar, iRow, kPoint); + if (Block_ik == nullptr) continue; + + /*--- Block_ik cannot alias Block_ij because kPoint > jPoint. ---*/ + const auto* Ujk = M.u + ku * blockSize; + ScalarType prod = 0; + for (auto k = 0ul; k < nVar; ++k) prod += Lij[iVar * nVar + k] * Ujk[k * nVar + jVar]; + Block_ik[tid] -= prod; + } + + /*--- Store Lij in the lower triangular part, each thread only writes its own entry. ---*/ + Block_ij[tid] = Lij[tid]; + } + + /*--- Invert the diagonal entry, Uii, for the rows that depend on it. The loop above may have + * updated it (when kPoint == iRow), so the whole block has to be done first. ---*/ + __syncthreads(); + work[tid] = M.d[iRow * blockSize + tid]; + __syncthreads(); + if (tid == 0) SU2_LinAlg::MatrixInverse(nVar, work, M.d + iRow * blockSize); +} + +/*! + * \brief Exact forward substitution for the rows of one level, (L+I).prod = vec. + * \note Every row in a level only depends on rows in earlier levels, which are already + * finalized (see CSysMatrix::levels_ilu), so one pass over the levels in increasing order + * gives the exact result, unlike the colored factorization above. One thread per block + * entry, so the inner dot product over a neighbor block is spread across nVar threads + * instead of done serially by one; each thread accumulates its own (iVar,jVar) partial + * product across every neighbor with no synchronization at all, and only the final + * nVar-way reduction (summing over jVar for each iVar) needs one __syncthreads(). Grid: + * one block per row of the level, blockDim.x == nVar*nVar. Dynamic shared memory: + * nVar*nVar scalars. + */ +template +__global__ void IluForwardKernel(const su2uint* __restrict__ level_idx, unsigned long level_begin, + unsigned long level_size, unsigned long nVar, DeviceLDU M, + const ScalarType* __restrict__ vec, ScalarType* __restrict__ prod) { + if (blockIdx.x >= level_size) return; + + const unsigned long iRow = level_idx[level_begin + blockIdx.x]; + const unsigned long tid = threadIdx.x; + const auto iVar = tid / nVar, jVar = tid % nVar; + + extern __shared__ __align__(sizeof(double)) char smem[]; + auto* partial = reinterpret_cast(smem); + + ScalarType acc = 0; + for (auto kl = M.row_ptr_l[iRow]; kl < M.row_ptr_l[iRow + 1]; ++kl) { + const unsigned long jPoint = M.col_ind_l[kl]; + const auto* blk = M.l + kl * nVar * nVar; + acc += blk[iVar * nVar + jVar] * prod[jPoint * nVar + jVar]; + } + partial[tid] = acc; + __syncthreads(); + + if (jVar == 0) { + ScalarType sum = vec[iRow * nVar + iVar]; + for (auto j = 0ul; j < nVar; ++j) sum -= partial[iVar * nVar + j]; + prod[iRow * nVar + iVar] = sum; + } +} + +/*! + * \brief Exact backward substitution for the rows of one level, U.prod = prod. + * \note The right-hand side is read directly from \p prod: a level is visited exactly once, so + * prod[iRow] is still the untouched forward-solve result when its row is processed (unlike + * a colored sweep, which revisits every row and would need a separate fixed buffer to tell + * the right-hand side apart from a solution estimate). Levels are processed in decreasing + * order so every U-neighbor (a higher row index) is already finalized. Same thread layout + * as IluForwardKernel, plus one extra __syncthreads() before the diagonal multiply (which + * needs every iVar's reduced sum). Grid: one block per row of the level, + * blockDim.x == nVar*nVar. Dynamic shared memory: nVar*nVar + nVar scalars. + */ +template +__global__ void IluBackwardKernel(const su2uint* __restrict__ level_idx, unsigned long level_begin, + unsigned long level_size, unsigned long nRows, unsigned long nVar, + DeviceLDU M, ScalarType* __restrict__ prod) { + if (blockIdx.x >= level_size) return; + + const unsigned long iRow = level_idx[level_begin + blockIdx.x]; + const auto blockSize = nVar * nVar; + const unsigned long tid = threadIdx.x; + const auto iVar = tid / nVar, jVar = tid % nVar; + + extern __shared__ __align__(sizeof(double)) char smem[]; + auto* partial = reinterpret_cast(smem); + auto* aux = partial + blockSize; + + ScalarType acc = 0; + for (auto ku = M.row_ptr_u[iRow]; ku < M.row_ptr_u[iRow + 1]; ++ku) { + const unsigned long jPoint = M.col_ind_u[ku]; + if (jPoint >= nRows) break; + const auto* blk = M.u + ku * blockSize; + acc += blk[iVar * nVar + jVar] * prod[jPoint * nVar + jVar]; + } + partial[tid] = acc; + __syncthreads(); + + if (jVar == 0) { + ScalarType sum = prod[iRow * nVar + iVar]; + for (auto j = 0ul; j < nVar; ++j) sum -= partial[iVar * nVar + j]; + aux[iVar] = sum; + } + __syncthreads(); + + if (jVar == 0) { + /*--- The diagonal blocks are stored inverted by the factorization. ---*/ + const auto* invUii = M.d + iRow * blockSize; + ScalarType out = 0; + for (auto k = 0ul; k < nVar; ++k) out += invUii[iVar * nVar + k] * aux[k]; + prod[iRow * nVar + iVar] = out; + } +} + /*! * \brief Block-LDU SpMV kernel: y[iRow] = (L + D + U) * x per block-row. * One CUDA block per block-row; threadIdx.x indexes output variable (0..nVar-1). @@ -67,8 +353,186 @@ __global__ void BlockLDU_SpMV_kernel(unsigned long nRows, unsigned long nVar, y[iRow * nVar + iVar] = sum; } +} // namespace + +template +void CSysMatrix::ComputeJacobiPreconditionerGPU(const CSysVector& vec, + CSysVector& prod, CGeometry* geometry, + const CConfig* config) const { + (void)geometry; + (void)config; + + SU2_ZONE_SCOPED + + if (d_invM == nullptr) { + SU2_MPI::Error("CUDA Jacobi preconditioner used before BuildJacobiPreconditionerGPU.", CURRENT_FUNCTION); + } + + constexpr unsigned threadsPerBlock = 128; + const auto blocks = static_cast((nPointDomain + threadsPerBlock - 1) / threadsPerBlock); + ApplyJacobiPreconditionerKernel<<>>(d_invM, vec.GetDevicePointer(), prod.GetDevicePointer(), + nPointDomain, nVar); + /*--- Sync so the zone above actually times the kernel, not just the (async) launch call. ---*/ + gpuErrChk(cudaStreamSynchronize(nullptr)); + gpuErrChk(cudaGetLastError()); +} + +template +void CSysMatrix::BuildJacobiPreconditionerGPU() { + SU2_ZONE_SCOPED + + if (d_invM == nullptr) { + SU2_MPI::Error("CUDA Jacobi preconditioner used without device storage.", CURRENT_FUNCTION); + } + if (nPointDomain == 0) return; + + /*--- The matrix is expected to be on the device already, it is uploaded once per solve by + * CSysMatrixVectorProduct, which is created before the preconditioner is built. ---*/ + const auto blockSize = static_cast(nVar * nVar); + InvertDiagonalBlocksKernel + <<(nPointDomain), blockSize, blockSize * sizeof(ScalarType)>>>(nPointDomain, nVar, gpu.d, + d_invM); + /*--- Sync so the zone above actually times the kernel, not just the (async) launch call. ---*/ + gpuErrChk(cudaStreamSynchronize(nullptr)); + gpuErrChk(cudaGetLastError()); +} + +template +void CSysMatrix::BuildILUPreconditionerGPU() { + SU2_ZONE_SCOPED + + if (gpu_ilu.d == nullptr) { + SU2_MPI::Error("CUDA ILU preconditioner used without device storage.", CURRENT_FUNCTION); + } + if (nPointDomain == 0) return; + + /*--- The matrix is expected to be on the device already, it is uploaded once per solve by + * CSysMatrixVectorProduct, which is created before the preconditioner is built. ---*/ + const DeviceLDU A{gpu.d, gpu.l, gpu.u, gpu.row_ptr_l, + gpu.col_ind_l, gpu.row_ptr_u, gpu.col_ind_u}; + const DeviceLDU M{gpu_ilu.d, gpu_ilu.l, gpu_ilu.u, gpu_ilu.row_ptr_l, + gpu_ilu.col_ind_l, gpu_ilu.row_ptr_u, gpu_ilu.col_ind_u}; + + const auto blockSize = static_cast(nVar * nVar); + const auto shared = 2 * blockSize * sizeof(ScalarType); + + /*--- The legacy default stream cannot be captured, so the graph lives on its own stream, + * created once. Every launch below is followed by a sync back to the host, so this does not + * change execution order relative to the rest of the (single-stream) solver. ---*/ + if (ilu_stream == nullptr) gpuErrChk(cudaStreamCreate(&ilu_stream)); + + /*--- The launch sequence (ilu_gpu_sweeps passes over all colors) is identical on every call: + * the grid and block sizes only depend on the (fixed) sparsity pattern/coloring and the device + * pointers are fixed members, allocated once. Capture it into a CUDA graph the first time and + * replay that from then on, which removes the per-launch host-side overhead without touching + * the parallelization of any individual kernel (unlike a persistent cooperative-groups kernel, + * this does not cap per-color parallelism to an occupancy-resident block count). See + * IluFactorColorKernel for why several sweeps over the colors are needed. Note that factors + * are not reset between calls to BuildILUPreconditionerGPU, so with LINEAR_SOLVER_ILU_GPU_SWEEPS + * set low (even 1), each call refines the previous one's result rather than reconverging from + * scratch, relying on the matrix changing little between outer/pseudo-time iterations. ---*/ + if (ilu_build_graph_exec == nullptr) { + cudaGraph_t graph; + gpuErrChk(cudaStreamBeginCapture(ilu_stream, cudaStreamCaptureModeThreadLocal)); + + for (unsigned short sweep = 0; sweep < ilu_gpu_sweeps; ++sweep) { + for (auto color = 0ul; color + 1 < ilu_color_ptr.size(); ++color) { + const auto begin = ilu_color_ptr[color]; + const auto size = ilu_color_ptr[color + 1] - begin; + if (size == 0) continue; + IluFactorColorKernel + <<>>(d_ilu_color_idx, begin, size, nPointDomain, nVar, A, M); + } + } + + gpuErrChk(cudaStreamEndCapture(ilu_stream, &graph)); + gpuErrChk(cudaGraphInstantiate(&ilu_build_graph_exec, graph, nullptr, nullptr, 0)); + gpuErrChk(cudaGraphDestroy(graph)); + } + + gpuErrChk(cudaGraphLaunch(ilu_build_graph_exec, ilu_stream)); + gpuErrChk(cudaStreamSynchronize(ilu_stream)); + gpuErrChk(cudaGetLastError()); +} + +template +void CSysMatrix::ComputeILUPreconditionerGPU(const CSysVector& vec, + CSysVector& prod) const { + SU2_ZONE_SCOPED + + if (gpu_ilu.d == nullptr) { + SU2_MPI::Error("CUDA ILU preconditioner used before BuildILUPreconditionerGPU.", CURRENT_FUNCTION); + } + if (nPointDomain == 0) return; + + const DeviceLDU M{gpu_ilu.d, gpu_ilu.l, gpu_ilu.u, gpu_ilu.row_ptr_l, + gpu_ilu.col_ind_l, gpu_ilu.row_ptr_u, gpu_ilu.col_ind_u}; + + auto* d_vec = vec.GetDevicePointer(); + auto* d_prod = prod.GetDevicePointer(); + + const auto nLevels = ilu_level_ptr.size() - 1; + + /*--- One thread per block entry, like the factorization kernel: spreads each row's neighbor + * dot products over nVar*nVar threads instead of doing them serially in nVar threads, without + * changing the number of blocks (still one per row), so this does not trade away SM coverage + * the way batching several rows into a block did. ---*/ + const auto threads = static_cast(nVar * nVar); + const auto sharedForward = threads * sizeof(ScalarType); + const auto sharedBackward = (threads + nVar) * sizeof(ScalarType); + + if (ilu_stream == nullptr) gpuErrChk(cudaStreamCreate(&ilu_stream)); + + /*--- Same idea as BuildILUPreconditionerGPU: the launch sequence only depends on the (fixed) + * level structure, plus the vec/prod device pointers. Those normally are the same temporary + * buffers on every call (owned by CSysSolve / CSysVector, allocated once), so the graph is + * captured once and replayed; if the pointers ever do change the graph is recaptured, which is + * no worse than the un-graphed loop, just not free. ---*/ + if (ilu_apply_graph_exec == nullptr || ilu_apply_graph_vec != d_vec || ilu_apply_graph_prod != d_prod) { + if (ilu_apply_graph_exec != nullptr) { + gpuErrChk(cudaGraphExecDestroy(ilu_apply_graph_exec)); + ilu_apply_graph_exec = nullptr; + } + + cudaGraph_t graph; + gpuErrChk(cudaStreamBeginCapture(ilu_stream, cudaStreamCaptureModeThreadLocal)); + + /*--- Forward substitution: one exact pass over the levels in increasing order, + * (L+I).prod = vec, see IluForwardKernel. ---*/ + for (auto level = 0ul; level < nLevels; ++level) { + const auto begin = ilu_level_ptr[level]; + const auto size = ilu_level_ptr[level + 1] - begin; + if (size == 0) continue; + IluForwardKernel + <<>>(d_ilu_level_idx, begin, size, nVar, M, d_vec, d_prod); + } + + /*--- Backward substitution: one exact pass over the levels in decreasing order, + * U.prod = prod, see IluBackwardKernel. ---*/ + for (auto level = nLevels; level > 0;) { + --level; + const auto begin = ilu_level_ptr[level]; + const auto size = ilu_level_ptr[level + 1] - begin; + if (size == 0) continue; + IluBackwardKernel + <<>>(d_ilu_level_idx, begin, size, nPointDomain, nVar, M, d_prod); + } + + gpuErrChk(cudaStreamEndCapture(ilu_stream, &graph)); + gpuErrChk(cudaGraphInstantiate(&ilu_apply_graph_exec, graph, nullptr, nullptr, 0)); + gpuErrChk(cudaGraphDestroy(graph)); + ilu_apply_graph_vec = d_vec; + ilu_apply_graph_prod = d_prod; + } + + gpuErrChk(cudaGraphLaunch(ilu_apply_graph_exec, ilu_stream)); + gpuErrChk(cudaStreamSynchronize(ilu_stream)); + gpuErrChk(cudaGetLastError()); +} + template void CSysMatrix::HtDTransfer(bool trigger) const { + SU2_ZONE_SCOPED if (!trigger) return; gpuErrChk(cudaMemcpy(gpu.d, mat.d, sizeof(ScalarType) * nPoint * nVar * nEqn, cudaMemcpyHostToDevice)); gpuErrChk(cudaMemcpy(gpu.l, mat.l, sizeof(ScalarType) * mat.nnz_l * nVar * nEqn, cudaMemcpyHostToDevice)); @@ -76,11 +540,9 @@ void CSysMatrix::HtDTransfer(bool trigger) const { } template -void CSysMatrix::GPUMatrixVectorProduct(const CSysVector& vec, CSysVector& prod, +void CSysMatrix::MatrixVectorProductGPU(const CSysVector& vec, CSysVector& prod, CGeometry* geometry, const CConfig* config) const { - if (nVar != nEqn) { - SU2_MPI::Error("CUDA CSysMatrix block-LDU SpMV requires square blocks.", CURRENT_FUNCTION); - } + SU2_ZONE_SCOPED ScalarType* d_vec = vec.GetDevicePointer(); ScalarType* d_prod = prod.GetDevicePointer(); @@ -90,16 +552,27 @@ void CSysMatrix::GPUMatrixVectorProduct(const CSysVector BlockLDU_SpMV_kernel<<>>( nPointDomain, nVar, gpu.row_ptr_l, gpu.col_ind_l, gpu.l, gpu.d, gpu.row_ptr_u, gpu.col_ind_u, gpu.u, d_vec, d_prod); + /*--- Sync so the zone above actually times the kernel, not just the (async) launch call. ---*/ + gpuErrChk(cudaStreamSynchronize(nullptr)); gpuErrChk(cudaGetLastError()); } -template void CSysMatrix::HtDTransfer(bool trigger) const; -template void CSysMatrix::GPUMatrixVectorProduct(const CSysVector& vec, - CSysVector& prod, CGeometry* geometry, - const CConfig* config) const; + +#define INSTANTIATE_MATRIX(TYPE) \ +template void CSysMatrix::HtDTransfer(bool trigger) const; \ +template void CSysMatrix::MatrixVectorProductGPU(const CSysVector& vec, \ + CSysVector& prod, \ + CGeometry* geometry, \ + const CConfig* config) const; \ +template void CSysMatrix::BuildJacobiPreconditionerGPU(); \ +template void CSysMatrix::BuildILUPreconditionerGPU(); \ +template void CSysMatrix::ComputeILUPreconditionerGPU(const CSysVector& vec, \ + CSysVector& prod) const; \ +template void CSysMatrix::ComputeJacobiPreconditionerGPU(const CSysVector& vec, \ + CSysVector& prod, \ + CGeometry* geometry, \ + const CConfig* config) const; +INSTANTIATE_MATRIX(su2mixedfloat) #if defined(USE_MIXED_PRECISION) && !defined(USE_SINGLE_PRECISION) -template void CSysMatrix::HtDTransfer(bool trigger) const; -template void CSysMatrix::GPUMatrixVectorProduct(const CSysVector& vec, - CSysVector& prod, CGeometry* geometry, - const CConfig* config) const; +INSTANTIATE_MATRIX(passivedouble) #endif diff --git a/Common/src/linear_algebra/CSysPreconditionerGPU.cu b/Common/src/linear_algebra/CSysPreconditionerGPU.cu deleted file mode 100644 index 794726fbce35..000000000000 --- a/Common/src/linear_algebra/CSysPreconditionerGPU.cu +++ /dev/null @@ -1,84 +0,0 @@ -/*! - * \file CSysPreconditionerGPU.cu - * \brief CUDA/GPU skeleton implementations for matrix-based preconditioners. - * \author Jesse Li - * \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 "../../include/linear_algebra/CSysMatrix.inl" -#include "../../include/linear_algebra/GPUComms.cuh" - -namespace { - -template -__global__ void ApplyJacobiPreconditionerKernel(const ScalarType* invM, const ScalarType* vec, ScalarType* prod, - unsigned long nPointDomain, unsigned long nVar) { - const auto iPoint = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; - if (iPoint >= nPointDomain) return; - - const auto block = &invM[iPoint * nVar * nVar]; - const auto rhs = &vec[iPoint * nVar]; - auto out = &prod[iPoint * nVar]; - - for (auto iVar = 0ul; iVar < nVar; ++iVar) { - auto sum = ScalarType(0); - for (auto jVar = 0ul; jVar < nVar; ++jVar) { - sum += block[iVar * nVar + jVar] * rhs[jVar]; - } - out[iVar] = sum; - } -} - -} // namespace - -template -void CSysMatrix::ComputeJacobiPreconditionerGPU(const CSysVector& vec, - CSysVector& prod, CGeometry* geometry, - const CConfig* config) const { - (void)geometry; - (void)config; - - SU2_ZONE_SCOPED - - if (d_invM == nullptr) { - SU2_MPI::Error("CUDA Jacobi preconditioner used before BuildJacobiPreconditionerGPU.", CURRENT_FUNCTION); - } - - constexpr unsigned threadsPerBlock = 128; - const auto blocks = static_cast((nPointDomain + threadsPerBlock - 1) / threadsPerBlock); - ApplyJacobiPreconditionerKernel<<>>(d_invM, vec.GetDevicePointer(), prod.GetDevicePointer(), - nPointDomain, nVar); - gpuErrChk(cudaPeekAtLastError()); -} - -template void CSysMatrix::ComputeJacobiPreconditionerGPU(const CSysVector& vec, - CSysVector& prod, - CGeometry* geometry, - const CConfig* config) const; - -#if defined(USE_MIXED_PRECISION) && !defined(USE_SINGLE_PRECISION) -template void CSysMatrix::ComputeJacobiPreconditionerGPU(const CSysVector& vec, - CSysVector& prod, - CGeometry* geometry, - const CConfig* config) const; -#endif diff --git a/Common/src/linear_algebra/CSysVectorGPU.cu b/Common/src/linear_algebra/CSysVectorGPU.cu index 2be1215a7bd0..01b146baf762 100644 --- a/Common/src/linear_algebra/CSysVectorGPU.cu +++ b/Common/src/linear_algebra/CSysVectorGPU.cu @@ -9,7 +9,7 @@ * The SU2 Project is maintained by the SU2 Foundation * (http://su2foundation.org) * - * Copyright 2012-2024, SU2 Contributors (cf. AUTHORS.md) + * 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 @@ -66,18 +66,21 @@ void SetUseDeviceExpressions(bool use) { use_device_expressions = use; } template void CSysVector::HtDTransfer(bool trigger) const { + SU2_ZONE_SCOPED if (trigger) gpuErrChk(cudaMemcpy((void*)(d_vec_val), (void*)&vec_val[0], (sizeof(ScalarType) * nElm), cudaMemcpyHostToDevice)); } template void CSysVector::DtHTransfer(bool trigger) const { + SU2_ZONE_SCOPED if (trigger) gpuErrChk(cudaMemcpy((void*)(&vec_val[0]), (void*)d_vec_val, (sizeof(ScalarType) * nElm), cudaMemcpyDeviceToHost)); } template ScalarType CSysVector::GPUDot(const CSysVector& other) const { + SU2_ZONE_SCOPED /*--- Both operands are already on the device, the caller owns the transfers. This * reduces over MPI, so it must be called by a single thread (see SU2_DEVICE_REGION). ---*/ cublasHandle_t handle = GetBlasHandle(); @@ -110,6 +113,7 @@ ScalarType CSysVector::GPUDot(const CSysVector& other) const { template ScalarType CSysVector::GPUNorm() const { + SU2_ZONE_SCOPED return sqrt(GPUDot(*this)); } diff --git a/Common/src/linear_algebra/meson.build b/Common/src/linear_algebra/meson.build index 48ef65cb8db2..3b84b2373a55 100644 --- a/Common/src/linear_algebra/meson.build +++ b/Common/src/linear_algebra/meson.build @@ -8,5 +8,5 @@ common_src += files(['CSysSolve_b.cpp', if get_option('enable-cuda') # Kept apart from common_src: these are compiled without the CoDiPack defines and so # must only go into the primal library, see common_cuda_src in Common/src/meson.build. - common_cuda_src += files(['CSysMatrixGPU.cu', 'CSysVectorGPU.cu', 'CSysPreconditionerGPU.cu']) + common_cuda_src += files(['CSysMatrixGPU.cu', 'CSysVectorGPU.cu']) endif diff --git a/Common/src/toolboxes/SwapBytes.cpp b/Common/src/toolboxes/SwapBytes.cpp index 6f4d43504570..d60567e09ed0 100644 --- a/Common/src/toolboxes/SwapBytes.cpp +++ b/Common/src/toolboxes/SwapBytes.cpp @@ -9,7 +9,7 @@ * The SU2 Project is maintained by the SU2 Foundation * (http://su2foundation.org) * - * Copyright 2012-2025, SU2 Contributors (cf. AUTHORS.md) + * 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 diff --git a/TestCases/hybrid_regression.py b/TestCases/hybrid_regression.py index 443d367a10a8..e2c375f831a8 100644 --- a/TestCases/hybrid_regression.py +++ b/TestCases/hybrid_regression.py @@ -59,7 +59,7 @@ def main(): naca0012.cfg_dir = "euler/naca0012" naca0012.cfg_file = "inv_NACA0012_Roe.cfg" naca0012.test_iter = 20 - naca0012.test_vals = [-4.387423, -3.845336, 0.296291, 0.025144] + naca0012.test_vals = [-4.387446, -3.845358, 0.296291, 0.025143] test_list.append(naca0012) # Supersonic wedge @@ -157,7 +157,7 @@ def main(): rae2822_sa.cfg_dir = "rans/rae2822" rae2822_sa.cfg_file = "turb_SA_RAE2822.cfg" rae2822_sa.test_iter = 20 - rae2822_sa.test_vals = [-2.190528, -5.335496, 0.383385, 0.077606, 0.000000] + rae2822_sa.test_vals = [-2.190528, -5.335496, 0.383385, 0.077605, 0.000000] test_list.append(rae2822_sa) # RAE2822 SST @@ -197,7 +197,7 @@ def main(): turb_naca0012_sa.cfg_dir = "rans/naca0012" turb_naca0012_sa.cfg_file = "turb_NACA0012_sa.cfg" turb_naca0012_sa.test_iter = 5 - turb_naca0012_sa.test_vals = [-12.038042, -16.332088, 1.080346, 0.018385, 20.000000, -2.873258, 0.000000, -14.250270, 0.000000] + turb_naca0012_sa.test_vals = [-12.038067, -16.332088, 1.080346, 0.018385, 20.000000, -2.873679, 0.000000, -14.250269, 0.000000] turb_naca0012_sa.test_vals_aarch64 = [-12.038091, -16.332090, 1.080346, 0.018385, 20.000000, -2.873236, 0.000000, -14.250271, 0.000000] test_list.append(turb_naca0012_sa) @@ -206,7 +206,7 @@ def main(): turb_naca0012_sst.cfg_dir = "rans/naca0012" turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" turb_naca0012_sst.test_iter = 10 - turb_naca0012_sst.test_vals = [-12.093984, -15.250705, -5.906323, 1.070413, 0.015775, -2.855331, 0.000000] + turb_naca0012_sst.test_vals = [-12.093958, -15.250719, -5.906323, 1.070413, 0.015775, -2.855776, 0.000000] turb_naca0012_sst.test_vals_aarch64 = [-12.075928, -15.246732, -5.861249, 1.070036, 0.015841, -2.835263, 0] test_list.append(turb_naca0012_sst) @@ -215,7 +215,7 @@ def main(): turb_naca0012_sst_sust.cfg_dir = "rans/naca0012" turb_naca0012_sst_sust.cfg_file = "turb_NACA0012_sst_sust.cfg" turb_naca0012_sst_sust.test_iter = 10 - turb_naca0012_sst_sust.test_vals = [-12.080774, -14.837176, -5.732907, 1.000893, 0.019109, -2.120168] + turb_naca0012_sst_sust.test_vals = [-12.080818, -14.837175, -5.732906, 1.000893, 0.019109, -2.119717] turb_naca0012_sst_sust.test_vals_aarch64 = [-12.073210, -14.836724, -5.732627, 1.000050, 0.019144, -2.629689] test_list.append(turb_naca0012_sst_sust) @@ -224,7 +224,7 @@ def main(): turb_naca0012_sst_fixedvalues.cfg_dir = "rans/naca0012" turb_naca0012_sst_fixedvalues.cfg_file = "turb_NACA0012_sst_fixedvalues.cfg" turb_naca0012_sst_fixedvalues.test_iter = 10 - turb_naca0012_sst_fixedvalues.test_vals = [-5.192390, -10.448219, 0.773965, 1.022535, 0.040529, -2.383282] + turb_naca0012_sst_fixedvalues.test_vals = [-5.192390, -10.448218, 0.773965, 1.022535, 0.040529, -2.383435] test_list.append(turb_naca0012_sst_fixedvalues) # NACA0012 (SST, explicit Euler for flow and turbulence equations) @@ -252,7 +252,7 @@ def main(): axi_rans_air_nozzle_restart.cfg_dir = "axisymmetric_rans/air_nozzle" axi_rans_air_nozzle_restart.cfg_file = "air_nozzle_restart.cfg" axi_rans_air_nozzle_restart.test_iter = 10 - axi_rans_air_nozzle_restart.test_vals = [-11.083070, -5.374688, -8.880089, -4.073519, 0.000000] + axi_rans_air_nozzle_restart.test_vals = [-11.083070, -5.374685, -8.880088, -4.073510, 0.000000] axi_rans_air_nozzle_restart.test_vals_aarch64 = [-14.140441, -9.154674, -10.886121, -5.806594, 0.000000] test_list.append(axi_rans_air_nozzle_restart) @@ -420,7 +420,7 @@ def main(): inc_weakly_coupled.cfg_dir = "disc_adj_heat" inc_weakly_coupled.cfg_file = "primal.cfg" inc_weakly_coupled.test_iter = 10 - inc_weakly_coupled.test_vals = [-18.106234, -16.302995, -16.484896, -15.006575, -17.858050, -14.024855, 5.609100] + inc_weakly_coupled.test_vals = [-18.106270, -16.302974, -16.484882, -15.006575, -17.858050, -14.024836, 5.609100] test_list.append(inc_weakly_coupled) ###################################### @@ -501,7 +501,7 @@ def main(): ddes_flatplate.cfg_dir = "ddes/flatplate" ddes_flatplate.cfg_file = "ddes_flatplate.cfg" ddes_flatplate.test_iter = 10 - ddes_flatplate.test_vals = [-2.714713, -5.763299, -0.214960, 0.023758, 0.000000] + ddes_flatplate.test_vals = [-2.714713, -5.763300, -0.214960, 0.023758, 0.000000] ddes_flatplate.unsteady = True test_list.append(ddes_flatplate) @@ -686,7 +686,7 @@ def main(): statbeam3d.cfg_dir = "fea_fsi/StatBeam_3d" statbeam3d.cfg_file = "configBeam_3d.cfg" statbeam3d.test_iter = 0 - statbeam3d.test_vals = [-2.830455, -1.746659, -2.366087, 110350] + statbeam3d.test_vals = [-2.862081, -1.795143, -2.380136, 110350.000000] statbeam3d.test_vals_aarch64 = [-2.777602, -1.710976, -2.445072, 110350] test_list.append(statbeam3d) @@ -715,7 +715,7 @@ def main(): dyn_fsi.cfg_dir = "fea_fsi/dyn_fsi" dyn_fsi.cfg_file = "config.cfg" dyn_fsi.test_iter = 4 - dyn_fsi.test_vals = [-4.330727, -4.152808, 0.000000, 103.000000] + dyn_fsi.test_vals = [-4.330727, -4.152808, 0.000000, 102.000000] dyn_fsi.multizone = True dyn_fsi.unsteady = True test_list.append(dyn_fsi) @@ -725,7 +725,7 @@ def main(): fsi_cht_restart.cfg_dir = "fea_fsi/stat_fsi" fsi_cht_restart.cfg_file = "config_restart.cfg" fsi_cht_restart.test_iter = 0 - fsi_cht_restart.test_vals = [5.000000, 0.006352, -1.960362, -9.327033, -9.627867, -9.318971, 608.380000, -0.012974, 0.000000, 20.000000] + fsi_cht_restart.test_vals = [5.000000, 0.006352, -1.960362, -9.327033, -9.643180, -9.319159, 608.380000, -0.012974, 0.000000, 20.000000] fsi_cht_restart.multizone = True test_list.append(fsi_cht_restart) @@ -755,7 +755,7 @@ def main(): mms_fvm_inc_ns.cfg_dir = "mms/fvm_incomp_navierstokes" mms_fvm_inc_ns.cfg_file = "lam_mms_fds.cfg" mms_fvm_inc_ns.test_iter = 20 - mms_fvm_inc_ns.test_vals = [-7.414945, -7.631547, 0.000000, 0.000000] + mms_fvm_inc_ns.test_vals = [-7.414944, -7.631546, 0.000000, 0.000000] test_list.append(mms_fvm_inc_ns) ########################## diff --git a/TestCases/hybrid_regression_AD.py b/TestCases/hybrid_regression_AD.py index 4db360930312..9440e408ab15 100644 --- a/TestCases/hybrid_regression_AD.py +++ b/TestCases/hybrid_regression_AD.py @@ -78,7 +78,7 @@ def main(): discadj_rans_naca0012_sa.cfg_dir = "disc_adj_rans/naca0012" discadj_rans_naca0012_sa.cfg_file = "turb_NACA0012_sa.cfg" discadj_rans_naca0012_sa.test_iter = 10 - discadj_rans_naca0012_sa.test_vals = [-2.987158, 0.533077, 0.000004, -0.000000, 5.000000, -2.939652, 5.000000, -5.502411] + discadj_rans_naca0012_sa.test_vals = [-2.987158, 0.533077, 0.000004, -0.000000, 5.000000, -2.939652, 5.000000, -5.503137] test_list.append(discadj_rans_naca0012_sa) # Adjoint turbulent NACA0012 SST @@ -86,7 +86,7 @@ def main(): discadj_rans_naca0012_sst.cfg_dir = "disc_adj_rans/naca0012" discadj_rans_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" discadj_rans_naca0012_sst.test_iter = 10 - discadj_rans_naca0012_sst.test_vals = [-2.201555, -0.175211, 3.045200, -0.041846] + discadj_rans_naca0012_sst.test_vals = [-2.201551, -0.175213, 3.045300, -0.041846] discadj_rans_naca0012_sst.test_vals_aarch64 = [-2.201855, -0.172443, 3.043400, -0.041820] test_list.append(discadj_rans_naca0012_sst) @@ -111,7 +111,7 @@ def main(): discadj_incomp_cylinder.cfg_dir = "disc_adj_incomp_navierstokes/cylinder" discadj_incomp_cylinder.cfg_file = "heated_cylinder.cfg" discadj_incomp_cylinder.test_iter = 20 - discadj_incomp_cylinder.test_vals = [20.000000, -1.665652, -6.239091, 0.000000] + discadj_incomp_cylinder.test_vals = [20.000000, -1.665652, -6.239103, 0.000000] discadj_incomp_cylinder.test_vals_aarch64 = [20.000000, -1.671920, -6.254841, 0.000000] discadj_incomp_cylinder.tol_aarch64 = 2e-1 test_list.append(discadj_incomp_cylinder) @@ -125,7 +125,7 @@ def main(): discadj_incomp_turb_NACA0012_sa.cfg_dir = "disc_adj_incomp_rans/naca0012" discadj_incomp_turb_NACA0012_sa.cfg_file = "turb_naca0012_sa.cfg" discadj_incomp_turb_NACA0012_sa.test_iter = 10 - discadj_incomp_turb_NACA0012_sa.test_vals = [10.000000, -3.845989, -1.023525, 0.000000] + discadj_incomp_turb_NACA0012_sa.test_vals = [10.000000, -3.845989, -1.023526, 0.000000] test_list.append(discadj_incomp_turb_NACA0012_sa) # Adjoint Incompressible Turbulent NACA 0012 SST @@ -133,7 +133,7 @@ def main(): discadj_incomp_turb_NACA0012_sst.cfg_dir = "disc_adj_incomp_rans/naca0012" discadj_incomp_turb_NACA0012_sst.cfg_file = "turb_naca0012_sst.cfg" discadj_incomp_turb_NACA0012_sst.test_iter = 10 - discadj_incomp_turb_NACA0012_sst.test_vals = [-3.775275, -3.089116, -7.143654, 0.000000, -0.896764] + discadj_incomp_turb_NACA0012_sst.test_vals = [-3.775320, -3.089107, -7.143663, 0.000000, -0.896754] test_list.append(discadj_incomp_turb_NACA0012_sst) ####################################################### @@ -159,7 +159,7 @@ def main(): discadj_cylinder.cfg_dir = "disc_adj_rans/cylinder" discadj_cylinder.cfg_file = "cylinder_Windowing_AD.cfg" discadj_cylinder.test_iter = 9 - discadj_cylinder.test_vals = [2.183381] + discadj_cylinder.test_vals = [2.183380] discadj_cylinder.unsteady = True discadj_cylinder.enabled_with_tsan = False test_list.append(discadj_cylinder) @@ -173,7 +173,7 @@ def main(): discadj_DT_1ST_cylinder.cfg_dir = "disc_adj_rans/cylinder_DT_1ST" discadj_DT_1ST_cylinder.cfg_file = "cylinder.cfg" discadj_DT_1ST_cylinder.test_iter = 9 - discadj_DT_1ST_cylinder.test_vals = [1.196350, -3.339010, -0.006213, 0.000020] + discadj_DT_1ST_cylinder.test_vals = [1.196351, -3.339009, -0.006213, 0.000020] discadj_DT_1ST_cylinder.unsteady = True discadj_DT_1ST_cylinder.enabled_with_tsan = False test_list.append(discadj_DT_1ST_cylinder) @@ -202,7 +202,7 @@ def main(): discadj_fea.cfg_dir = "disc_adj_fea" discadj_fea.cfg_file = "configAD_fem.cfg" discadj_fea.test_iter = 4 - discadj_fea.test_vals = [2.149620, 2.014985, -0.000364, -8.767900] + discadj_fea.test_vals = [2.180053, 2.075836, -0.000367, -8.730500] discadj_fea.test_vals_aarch64 = [1.794371, 2.005865, -0.000365, -8.718100] test_list.append(discadj_fea) @@ -228,7 +228,7 @@ def main(): pywrapper_FEA_AD_FlowLoad.cfg_dir = "py_wrapper/disc_adj_fea/flow_load_sens" pywrapper_FEA_AD_FlowLoad.cfg_file = "configAD_fem.cfg" pywrapper_FEA_AD_FlowLoad.test_iter = 100 - pywrapper_FEA_AD_FlowLoad.test_vals = [-0.132010, -0.554418, -0.000364, -0.003101] + pywrapper_FEA_AD_FlowLoad.test_vals = [-0.131741, -0.553305, -0.000364, -0.003101] pywrapper_FEA_AD_FlowLoad.test_vals_aarch64 = [-0.131745, -0.553214, -0.000364, -0.003101] pywrapper_FEA_AD_FlowLoad.command = TestCase.Command(exec = "python", param = "run_adjoint.py --parallel -f") pywrapper_FEA_AD_FlowLoad.timeout = 1600 @@ -243,7 +243,7 @@ def main(): pywrapper_CFD_AD_MeshDisp.cfg_dir = "py_wrapper/disc_adj_flow/mesh_disp_sens" pywrapper_CFD_AD_MeshDisp.cfg_file = "configAD_flow.cfg" pywrapper_CFD_AD_MeshDisp.test_iter = 1000 - pywrapper_CFD_AD_MeshDisp.test_vals = [30.000000, -2.496158, 1.441842, 0.000000] + pywrapper_CFD_AD_MeshDisp.test_vals = [30.000000, -2.496139, 1.441872, 0.000000] pywrapper_CFD_AD_MeshDisp.test_vals_aarch64 = [30.000000, -2.499079, 1.440068, 0.000000] pywrapper_CFD_AD_MeshDisp.command = TestCase.Command(exec = "python", param = "run_adjoint.py --parallel -f") pywrapper_CFD_AD_MeshDisp.timeout = 1600 diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index 9c08ad013e5e..e6242b093b1c 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -138,7 +138,7 @@ def main(): invwedge_msw.cfg_dir = "nonequilibrium/invwedge" invwedge_msw.cfg_file = "invwedge_msw.cfg" invwedge_msw.test_iter = 10 - invwedge_msw.test_vals = [-1.212335, -1.737098, -18.301768, -18.629149, -18.575169, 2.106171, 1.651949, 5.143958, 0.704444] + invwedge_msw.test_vals = [-1.212335, -1.737098, -18.301825, -18.629206, -18.575226, 2.106171, 1.651949, 5.143958, 0.704444] invwedge_msw.test_vals_aarch64 = [-1.212335, -1.737098, -18.299279, -18.626656, -18.572683, 2.106171, 1.651949, 5.143958, 0.704444] test_list.append(invwedge_msw) @@ -147,7 +147,7 @@ def main(): invwedge_roe.cfg_dir = "nonequilibrium/invwedge" invwedge_roe.cfg_file = "invwedge_roe.cfg" invwedge_roe.test_iter = 10 - invwedge_roe.test_vals = [-1.023216, -1.547979, -17.814656, -18.143616, -18.087775, 2.295078, 1.885054, 5.338487, 0.926120] + invwedge_roe.test_vals = [-1.023283, -1.548046, -17.814403, -18.143369, -18.087522, 2.295025, 1.884804, 5.338440, 0.926068] invwedge_roe.test_vals_aarch64 = [-1.052398, -1.577160, -17.794015, -18.122997, -18.067131, 2.266042, 1.849686, 5.304700, 0.899584] test_list.append(invwedge_roe) @@ -183,7 +183,7 @@ def main(): visc_cone.cfg_dir = "nonequilibrium/visc_wedge" visc_cone.cfg_file = "axi_visccone.cfg" visc_cone.test_iter = 10 - visc_cone.test_vals = [-5.215235, -5.739371, -20.559852, -20.509281, -20.408911, 1.262701, -3.205457, -0.015696, 0.093205, 32637.000000] + visc_cone.test_vals = [-5.215234, -5.739371, -20.559852, -20.509281, -20.408911, 1.262701, -3.205457, -0.015696, 0.093205, 32637.000000] visc_cone.test_vals_aarch64 = [-5.222270, -5.746525, -20.560286, -20.510152, -20.409101, 1.255758, -3.208382, -0.016014, 0.093462, 32619.000000] test_list.append(visc_cone) @@ -326,7 +326,7 @@ def main(): ramp_msw.cfg_dir = "euler/ramp" ramp_msw.cfg_file = "inv_ramp_msw.cfg" ramp_msw.test_iter = 100 - ramp_msw.test_vals = [-7.059306, -1.300966, -0.077507, 0.054419] + ramp_msw.test_vals = [-7.219257, -1.444776, -0.077507, 0.054419] ramp_msw.tol = [0.2, 0.2, 0.00001, 0.00001] test_list.append(ramp_msw) @@ -820,7 +820,7 @@ def main(): turbmod_sa_neg_rae2822.cfg_dir = "turbulence_models/sa/rae2822" turbmod_sa_neg_rae2822.cfg_file = "turb_SA_NEG_RAE2822.cfg" turbmod_sa_neg_rae2822.test_iter = 10 - turbmod_sa_neg_rae2822.test_vals = [1.527546, 1.303378, -1.699437, 1.523548, 0.601919, 0.000000] + turbmod_sa_neg_rae2822.test_vals = [1.611007, 1.331579, -1.264006, 1.360053, 0.508605, 0.000000] turbmod_sa_neg_rae2822.test_vals_aarch64 = [-1.345593, 1.448310, 1.208721, -0.846597, 1.248410, 0.489117, 0.000000] test_list.append(turbmod_sa_neg_rae2822) @@ -1324,7 +1324,7 @@ def main(): statbeam3d.cfg_dir = "fea_fsi/StatBeam_3d" statbeam3d.cfg_file = "configBeam_3d.cfg" statbeam3d.test_iter = 0 - statbeam3d.test_vals = [-6.047457, -5.730708, -5.926093, 110190] + statbeam3d.test_vals = [-6.020105, -5.749794, -5.931330, 110190.000000] statbeam3d.test_vals_aarch64 = [-6.062693, -5.769132, -5.891190, 110190] test_list.append(statbeam3d) @@ -1333,7 +1333,7 @@ def main(): thermal_beam_3d.cfg_dir = "fea_fsi/ThermalBeam_3d" thermal_beam_3d.cfg_file = "configBeam_3d.cfg" thermal_beam_3d.test_iter = 4 - thermal_beam_3d.test_vals = [-8.070340, -7.802437, -7.856284, -13.978110, 217.000000, -4.047750, 39.000000, -4.072613, 136760.000000, 75.000000] + thermal_beam_3d.test_vals = [-8.141292, -7.848655, -7.909552, -13.978110, 217.000000, -4.099949, 39.000000, -4.072613, 136760.000000, 75.000000] test_list.append(thermal_beam_3d) # Static beam, 3d with coupled temperature, nonlinear elasticity @@ -1341,7 +1341,7 @@ def main(): thermal_beam_nl_3d.cfg_dir = "fea_fsi/ThermalBeam_3d" thermal_beam_nl_3d.cfg_file = "configBeamNonlinear_3d.cfg" thermal_beam_nl_3d.test_iter = 8 - thermal_beam_nl_3d.test_vals = [-7.564308, -2.992893, -12.242503, -14.068322, 57.000000, -4.017672, 24.000000, -4.204804, 138710.000000, 75.233000] + thermal_beam_nl_3d.test_vals = [-7.564308, -2.992893, -12.242503, -14.068322, 57.000000, -4.017665, 24.000000, -4.204804, 138710.000000, 75.233000] test_list.append(thermal_beam_nl_3d) # Rotating cylinder, 3d @@ -1352,7 +1352,7 @@ def main(): # For a thin disk with the inner and outer radius of this geometry, from # "Formulas for Stress, Strain, and Structural Matrices", 2nd Edition, figure 19-4, # the maximum stress is 165.6MPa, we get a von Mises stress very close to that. - rotating_cylinder_fea.test_vals = [-6.760497, -6.689264, -6.739355, 37.000000, -8.178510, 165020000.000000] + rotating_cylinder_fea.test_vals = [-6.760497, -6.689266, -6.739355, 37.000000, -8.178510, 165020000.000000] rotating_cylinder_fea.test_vals_aarch64 = [-6.861939, -6.835539, -6.895498, 22, -8.313847, 1.6502e+08] test_list.append(rotating_cylinder_fea) @@ -1361,7 +1361,7 @@ def main(): linear_plane_strain.cfg_dir = "fea_fsi/VonMissesVerif" linear_plane_strain.cfg_file = "linear_plane_strain_2d.cfg" linear_plane_strain.test_iter = 0 - linear_plane_strain.test_vals = [-6.406458, -5.995503, 0, 120140, 144, -8.122248] + linear_plane_strain.test_vals = [-6.246772, -6.060575, 0.000000, 120140.000000, 148.000000, -8.140997] test_list.append(linear_plane_strain) # 2D beam in plain stress with thermal expansion. This tests fixes to the 2D von Mises stress calculation, @@ -1370,7 +1370,7 @@ def main(): nonlinear_plane_stress.cfg_dir = "fea_fsi/VonMissesVerif" nonlinear_plane_stress.cfg_file = "nonlinear_plane_stress_2d.cfg" nonlinear_plane_stress.test_iter = 16 - nonlinear_plane_stress.test_vals = [-6.230217, -2.228380, -11.698370, 162480.000000, 30.000000, -4.169761] + nonlinear_plane_stress.test_vals = [-6.235980, -2.229690, -11.701484, 162480.000000, 30.000000, -4.152654] nonlinear_plane_stress.tol = [2e-4, 2e-4, 2e-4, 1e-5, 1e-5, 4e-4] test_list.append(nonlinear_plane_stress) @@ -1520,7 +1520,7 @@ def main(): pywrapper_custom_fea_load.cfg_dir = "py_wrapper/custom_load_fea" pywrapper_custom_fea_load.cfg_file = "config.cfg" pywrapper_custom_fea_load.test_iter = 13 - pywrapper_custom_fea_load.test_vals = [-7.262040, -4.945686, -14.163208, 27.000000, -6.282188, 362.230000] + pywrapper_custom_fea_load.test_vals = [-7.262040, -4.945686, -14.163208, 27.000000, -6.285429, 362.230000] pywrapper_custom_fea_load.command = TestCase.Command("mpirun -np 2", "python", "run.py") test_list.append(pywrapper_custom_fea_load) @@ -1633,7 +1633,7 @@ def main(): mms_fvm_inc_euler.cfg_dir = "mms/fvm_incomp_euler" mms_fvm_inc_euler.cfg_file = "inv_mms_jst.cfg" mms_fvm_inc_euler.test_iter = 20 - mms_fvm_inc_euler.test_vals = [-9.128735, -9.441756, 0.000000, 0.000000] + mms_fvm_inc_euler.test_vals = [-9.128735, -9.441757, 0.000000, 0.000000] mms_fvm_inc_euler.tol = 0.0001 test_list.append(mms_fvm_inc_euler) diff --git a/TestCases/parallel_regression_AD.py b/TestCases/parallel_regression_AD.py index 8e66a9716b10..e68c3f27648e 100644 --- a/TestCases/parallel_regression_AD.py +++ b/TestCases/parallel_regression_AD.py @@ -231,7 +231,7 @@ def main(): discadj_trans_stator.cfg_dir = "disc_adj_turbomachinery/transonic_stator_2D" discadj_trans_stator.cfg_file = "transonic_stator.cfg" discadj_trans_stator.test_iter = 79 - discadj_trans_stator.test_vals = [79.000000, 2.549036, 2.313067, 2.139713, 0.736742] + discadj_trans_stator.test_vals = [79.000000, 2.549037, 2.313067, 2.139716, 0.736741] discadj_trans_stator.test_vals_aarch64 = [79.000000, 0.696755, 0.485950, 0.569475, -0.990065] test_list.append(discadj_trans_stator) @@ -244,7 +244,7 @@ def main(): discadj_fea.cfg_dir = "disc_adj_fea" discadj_fea.cfg_file = "configAD_fem.cfg" discadj_fea.test_iter = 4 - discadj_fea.test_vals = [-2.849947, -3.238801, -0.000364, -8.708700] + discadj_fea.test_vals = [-2.849664, -3.238571, -0.000364, -8.708700] discadj_fea.test_vals_aarch64 = [-2.849646, -3.238577, -0.000364, -8.708700] #last 4 columns test_list.append(discadj_fea) @@ -286,7 +286,7 @@ def main(): discadj_fsi2.cfg_dir = "disc_adj_fsi/Airfoil_2d" discadj_fsi2.cfg_file = "config.cfg" discadj_fsi2.test_iter = 8 - discadj_fsi2.test_vals = [-3.824634, 1.979533, -3.863368, 0.295450, 3.839800] + discadj_fsi2.test_vals = [-3.824641, 1.979547, -3.863368, 0.295450, 3.839800] discadj_fsi2.test_vals_aarch64 = [-3.824870, 1.979160, -3.863368, 0.295450, 3.839800] discadj_fsi2.tol = 0.00001 test_list.append(discadj_fsi2) @@ -308,7 +308,7 @@ def main(): da_sp_pinArray_cht_2d_dp_hf.cfg_dir = "incomp_navierstokes/streamwise_periodic/chtPinArray_2d" da_sp_pinArray_cht_2d_dp_hf.cfg_file = "DA_configMaster.cfg" da_sp_pinArray_cht_2d_dp_hf.test_iter = 100 - da_sp_pinArray_cht_2d_dp_hf.test_vals = [-11.620815, -6.488915, -13.316507] + da_sp_pinArray_cht_2d_dp_hf.test_vals = [-11.620815, -6.488915, -13.316675] da_sp_pinArray_cht_2d_dp_hf.multizone = True test_list.append(da_sp_pinArray_cht_2d_dp_hf) @@ -326,7 +326,7 @@ def main(): da_unsteadyCHT_cylinder.cfg_dir = "coupled_cht/disc_adj_unsteadyCHT_cylinder" da_unsteadyCHT_cylinder.cfg_file = "chtMaster.cfg" da_unsteadyCHT_cylinder.test_iter = 2 - da_unsteadyCHT_cylinder.test_vals = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662007, 0.000000, 10.627000, 0.295190] + da_unsteadyCHT_cylinder.test_vals = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662005, 0.000000, 10.627000, 0.295190] da_unsteadyCHT_cylinder.test_vals_aarch64 = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662012, 0.000000, 89.932000, 0.295190] da_unsteadyCHT_cylinder.unsteady = True da_unsteadyCHT_cylinder.multizone = True @@ -521,7 +521,7 @@ def main(): pywrapper_wavy_wall_steady.cfg_dir = "py_wrapper/wavy_wall" pywrapper_wavy_wall_steady.cfg_file = "run_steady.py" pywrapper_wavy_wall_steady.test_iter = 100 - pywrapper_wavy_wall_steady.test_vals = [-1.353007, 2.581052, -2.900574] + pywrapper_wavy_wall_steady.test_vals = [-1.353007, 2.581051, -2.900574] pywrapper_wavy_wall_steady.command = TestCase.Command("mpirun -n 2", "python", "run_steady.py") pywrapper_wavy_wall_steady.timeout = 1600 pywrapper_wavy_wall_steady.tol = 0.00001 diff --git a/TestCases/py_wrapper/custom_source_buoyancy/lam_buoyancy_cavity.cfg b/TestCases/py_wrapper/custom_source_buoyancy/lam_buoyancy_cavity.cfg index 6329eaf739c5..6f33ec59f608 100644 --- a/TestCases/py_wrapper/custom_source_buoyancy/lam_buoyancy_cavity.cfg +++ b/TestCases/py_wrapper/custom_source_buoyancy/lam_buoyancy_cavity.cfg @@ -4,7 +4,7 @@ % Case description: Buoyancy-driven flow inside a cavity % % Author: Thomas D. Economon % % Date: 2018.06.10 % -% File Version 8.1.0 "Harrier" % +% File Version 8.5.0 "Harrier" % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/TestCases/py_wrapper/custom_source_buoyancy/run.py b/TestCases/py_wrapper/custom_source_buoyancy/run.py index f07dfdc3570c..190c2db49133 100644 --- a/TestCases/py_wrapper/custom_source_buoyancy/run.py +++ b/TestCases/py_wrapper/custom_source_buoyancy/run.py @@ -2,14 +2,14 @@ ## \file run.py # \brief Buoyancy force using user defines source term -# \version 8.1.0 "Harrier" +# \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-2024, SU2 Contributors (cf. AUTHORS.md) +# 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 diff --git a/TestCases/py_wrapper/turbulent_premixed_psi/run.py b/TestCases/py_wrapper/turbulent_premixed_psi/run.py index 8a157ef0ae97..27021528fdf6 100644 --- a/TestCases/py_wrapper/turbulent_premixed_psi/run.py +++ b/TestCases/py_wrapper/turbulent_premixed_psi/run.py @@ -3,14 +3,14 @@ ## \file run.py # \brief turbulent premixed dump combustor simulation (PSI flame) # phi=0.5, methane-air, U=40 m/s -# \version 8.1.0 "Harrier" +# \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-2024, SU2 Contributors (cf. AUTHORS.md) +# 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 @@ -95,7 +95,7 @@ def update_temperature(SU2Driver, iPoint): iFLOWSOLVER = SU2Driver.GetSolverIndices()['INC.FLOW'] iENTH = 3 - #h = + #h = SU2Driver.Solution(iFLOWSOLVER).Set(iPoint,iENTH, cp_u*(T-Tref)) diff --git a/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref b/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref index 0a0e8b9b8a41..0f39252ba9ae 100644 --- a/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref +++ b/TestCases/py_wrapper/updated_moving_frame_NACA12/forces_0.csv.ref @@ -18,7 +18,7 @@ 16, -7.83, 58.91, 0.00 17, -7.39, 56.13, 0.00 18, -6.87, 52.74, 0.00 -19, -6.30, 48.94, 0.00 +19, -6.30, 48.95, 0.00 20, -5.67, 44.59, 0.00 21, -5.01, 39.84, 0.00 22, -4.30, 34.65, 0.00 @@ -52,7 +52,7 @@ 50, 8.45, -142.76, 0.00 51, 6.40, -115.54, 0.00 52, 9.86, -191.93, 0.00 -53, 24.75, -524.72, 0.00 +53, 24.75, -524.71, 0.00 54, 26.02, -608.43, 0.00 55, 21.64, -567.32, 0.00 56, 18.59, -558.24, 0.00 @@ -83,35 +83,35 @@ 81, -34.19, -153.83, 0.00 82, -32.26, -131.97, 0.00 83, -30.00, -111.54, 0.00 -84, -26.56, -89.60, 0.00 +84, -26.57, -89.60, 0.00 85, -22.26, -67.94, 0.00 86, -16.65, -45.84, 0.00 -87, -10.42, -25.77, 0.00 +87, -10.42, -25.78, 0.00 88, -1.99, -4.39, 0.00 -89, 7.81, 15.27, 0.00 -90, 17.05, 29.26, 0.00 +89, 7.81, 15.28, 0.00 +90, 17.06, 29.26, 0.00 91, 27.28, 40.55, 0.00 -92, 36.43, 46.13, 0.00 +92, 36.43, 46.14, 0.00 93, 50.49, 53.24, 0.00 94, 63.17, 53.66, 0.00 -95, 73.70, 48.01, 0.00 -96, 73.03, 33.47, 0.00 -97, 67.54, 18.28, 0.00 +95, 73.70, 48.00, 0.00 +96, 73.02, 33.47, 0.00 +97, 67.53, 18.28, 0.00 98, 54.75, 7.35, 0.00 -99, 17.21, -0.00, 0.00 -100, 52.61, -7.06, 0.00 -101, 93.60, -25.33, 0.00 -102, 62.71, -28.74, 0.00 +99, 17.20, -0.00, 0.00 +100, 52.60, -7.06, 0.00 +101, 93.59, -25.33, 0.00 +102, 62.72, -28.75, 0.00 103, 27.14, -17.68, 0.00 -104, 23.12, -19.64, 0.00 +104, 23.11, -19.63, 0.00 105, 5.43, -5.72, 0.00 -106, -0.11, 0.15, 0.00 -107, -12.46, 18.52, 0.00 +106, -0.12, 0.15, 0.00 +107, -12.46, 18.53, 0.00 108, -19.25, 33.03, 0.00 109, -25.52, 49.90, 0.00 110, -31.57, 69.68, 0.00 111, -35.49, 87.76, 0.00 -112, -39.31, 108.24, 0.00 +112, -39.31, 108.23, 0.00 113, -41.08, 125.40, 0.00 114, -43.50, 146.70, 0.00 115, -44.37, 164.95, 0.00 @@ -153,7 +153,7 @@ 151, 50.35, 723.19, 0.00 152, 54.49, 748.60, 0.00 153, 50.92, 671.51, 0.00 -154, 6.40, 81.24, 0.00 +154, 6.40, 81.25, 0.00 155, -0.48, -5.87, 0.00 156, 2.99, 35.56, 0.00 157, 3.29, 37.89, 0.00 diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index 79773b6d986f..b5eeedb38aed 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -1069,7 +1069,7 @@ def main(): statbeam3d.cfg_dir = "fea_fsi/StatBeam_3d" statbeam3d.cfg_file = "configBeam_3d.cfg" statbeam3d.test_iter = 0 - statbeam3d.test_vals = [-6.175086, -5.939313, -6.084188, 110190] + statbeam3d.test_vals = [-6.192310, -5.950395, -6.079363, 110190.000000] statbeam3d.test_vals_aarch64 = [-6.166287, -5.938291, -6.069768, 110190] #last 4 columns test_list.append(statbeam3d) @@ -1107,7 +1107,7 @@ def main(): fsi_cht.cfg_dir = "fea_fsi/stat_fsi" fsi_cht.cfg_file = "config.cfg" fsi_cht.test_iter = 20 - fsi_cht.test_vals = [5.000000, -5.077003, -5.379449, -9.247804, -9.319626, -9.184904, 608.350000, -0.012973, 0.000000, 30.000000] + fsi_cht.test_vals = [5.000000, -5.076991, -5.379442, -9.247793, -9.319193, -9.184753, 608.350000, -0.012973, 0.000000, 30.000000] fsi_cht.multizone = True test_list.append(fsi_cht) diff --git a/TestCases/serial_regression_AD.py b/TestCases/serial_regression_AD.py index 15598919e70e..2c51de9f9dd3 100644 --- a/TestCases/serial_regression_AD.py +++ b/TestCases/serial_regression_AD.py @@ -180,7 +180,7 @@ def main(): discadj_fea.cfg_dir = "disc_adj_fea" discadj_fea.cfg_file = "configAD_fem.cfg" discadj_fea.test_iter = 4 - discadj_fea.test_vals = [-2.849719, -3.238637, -0.000364, -8.708700] + discadj_fea.test_vals = [-2.849715, -3.238627, -0.000364, -8.708700] discadj_fea.test_vals_aarch64 = [-2.849588, -3.238523, -0.000364, -8.708700] discadj_fea.tol = 0.00007 test_list.append(discadj_fea) diff --git a/TestCases/tutorials.py b/TestCases/tutorials.py index 4e039eea09b9..aefbc66f815d 100644 --- a/TestCases/tutorials.py +++ b/TestCases/tutorials.py @@ -302,7 +302,7 @@ def main(): tutorial_unst_naca0012.cfg_dir = "../Tutorials/compressible_flow/Unsteady_NACA0012" tutorial_unst_naca0012.cfg_file = "unsteady_naca0012.cfg" tutorial_unst_naca0012.test_iter = 520 - tutorial_unst_naca0012.test_vals = [520.000000, 0.000000, -5.301440, 0.000000, 0.313631, 0.803638, 0.002198, 0.014969] + tutorial_unst_naca0012.test_vals = [520.000000, 0.000000, -5.293170, 0.000000, 0.301553, 0.773822, 0.001267, 0.007555] tutorial_unst_naca0012.test_vals_aarch64 = [520, 0, -5.292359, 0, 0.284720, 0.766329, 0.000954, 0.007565] tutorial_unst_naca0012.unsteady = True test_list.append(tutorial_unst_naca0012) diff --git a/config_template.cfg b/config_template.cfg index bc6be98103ae..922636bc6b40 100644 --- a/config_template.cfg +++ b/config_template.cfg @@ -1661,6 +1661,20 @@ DISCADJ_LIN_PREC= ILU % Linear solver ILU preconditioner fill-in level (0 by default) LINEAR_SOLVER_ILU_FILL_IN= 0 % +% Number of colored Gauss-Seidel sweeps used to build the GPU ILU factorization (default 2). It +% is not reset between calls, so with the matrix changing little between outer/pseudo-time +% iterations, each call refines the previous one's result instead of reconverging from scratch; +% for cases that do few outer iterations (e.g. elasticity problems) it may be useful to increase +% it up to 5. +LINEAR_SOLVER_ILU_GPU_SWEEPS= 2 +% +% Number of concurrent BFS fronts used to build the RCM point reordering (default 1, standard +% single-seed RCM). The GPU ILU triangular solves are scheduled by level, and the number of +% levels scales with the RCM ordering's BFS depth; increasing this spreads the ordering over +% several seeds picked by farthest-point sampling, which reduces that depth (and hence the +% number of level-scheduled kernel launches) at the cost of a somewhat wider matrix bandwidth. +RCM_NUM_SEEDS= 1 +% % Minimum error of the linear solver for implicit formulations LINEAR_SOLVER_ERROR= 1E-6 % From 787ae806009b55ae0c6e02b3adcbe3c74e532c83 Mon Sep 17 00:00:00 2001 From: tkiymaz <79564236+tkiymaz@users.noreply.github.com> Date: Wed, 12 Aug 2026 13:49:41 +0200 Subject: [PATCH 28/61] Implementing variable density for unsteady incompressible flow (#2641) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Proposed Changes Implements variable density treatment for unsteady incompressible flow simulations. Currently, SU2 uses constant density in transient simulations, which is inaccurate for combustion cases where density varies significantly due to heat release and species composition changes. This contribution enables proper density updates during time-stepping for flamelet-based combustion modeling. For now, only 1st order time marching is implemented ## Related Work Related to incompressible flow solver and flamelet combustion modeling. No specific issue linked yet. ## PR Checklist - [X] I am submitting my contribution to the develop branch. - [X] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [X] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [x] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [x] I have added a test case that demonstrates my contribution, if necessary. - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --------- Co-authored-by: Tahsin Berk Kiymaz Co-authored-by: Tahsin Berk Kiymaz Co-authored-by: Nijso Co-authored-by: Cristopher Morales <98025159+Cristopher-Morales@users.noreply.github.com> Co-authored-by: bigfooted Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Nijso Beishuizen Co-authored-by: Tahsin Berk Kiymaz Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Co-authored-by: Berk Kıymaz Co-authored-by: Berk Kıymaz Co-authored-by: Berk Kıymaz --- Common/src/CConfig.cpp | 8 + SU2_CFD/include/solvers/CIncEulerSolver.hpp | 12 ++ SU2_CFD/include/solvers/CScalarSolver.hpp | 3 +- SU2_CFD/include/solvers/CScalarSolver.inl | 96 ++++++----- SU2_CFD/include/solvers/CTurbSolver.hpp | 2 +- SU2_CFD/include/variables/CEulerVariable.hpp | 14 ++ SU2_CFD/include/variables/CFlowVariable.hpp | 14 ++ .../include/variables/CIncEulerVariable.hpp | 34 ++++ .../include/variables/CNEMOEulerVariable.hpp | 14 ++ SU2_CFD/src/solvers/CDiscAdjSolver.cpp | 6 +- SU2_CFD/src/solvers/CHeatSolver.cpp | 2 +- SU2_CFD/src/solvers/CIncEulerSolver.cpp | 77 +++++++-- .../src/solvers/CSpeciesFlameletSolver.cpp | 7 +- SU2_CFD/src/solvers/CSpeciesSolver.cpp | 2 +- SU2_CFD/src/solvers/CTurbSolver.cpp | 2 +- SU2_CFD/src/variables/CIncEulerVariable.cpp | 7 + .../lam_prem_ch4_unsteady.cfg | 154 ++++++++++++++++++ TestCases/parallel_regression.py | 11 +- TestCases/parallel_regression_AD.py | 4 +- TestCases/serial_regression.py | 2 +- 20 files changed, 395 insertions(+), 76 deletions(-) create mode 100644 TestCases/flamelet/09_laminar_premixed_ch4_flame_unsteady/lam_prem_ch4_unsteady.cfg diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index e075375001b6..3fe4c98bbe98 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -5764,6 +5764,14 @@ void CConfig::SetPostprocessing(SU2_COMPONENT val_software, unsigned short val_i Kind_Solver != MAIN_SOLVER::MULTIPHYSICS) SU2_MPI::Error("Species transport currently only available for compressible and incompressible flow.", CURRENT_FUNCTION); + /*--- The dual-time density history is recomputed via the fluid model, which needs the species + solution; the species solver only exists on the finest grid. ---*/ + if ((Kind_Regime == ENUM_REGIME::INCOMPRESSIBLE) && (Kind_DensityModel != INC_DENSITYMODEL::CONSTANT) && + (TimeMarching == TIME_MARCHING::DT_STEPPING_1ST || TimeMarching == TIME_MARCHING::DT_STEPPING_2ND) && + (nMGLevels > 0)) + SU2_MPI::Error("Dual-time stepping with species-dependent variable density does not support MGLEVEL > 0.", + CURRENT_FUNCTION); + /*--- Species specific OF currently can only handle one entry in Marker_Analyze. ---*/ for (unsigned short iObj = 0; iObj < nObj; iObj++) { if ((Kind_ObjFunc[iObj] == SURFACE_SPECIES_0 || diff --git a/SU2_CFD/include/solvers/CIncEulerSolver.hpp b/SU2_CFD/include/solvers/CIncEulerSolver.hpp index 9c18691cf560..9c94d4195e92 100644 --- a/SU2_CFD/include/solvers/CIncEulerSolver.hpp +++ b/SU2_CFD/include/solvers/CIncEulerSolver.hpp @@ -86,6 +86,18 @@ class CIncEulerSolver : public CFVMFlowSolverBase prim_idx; /*!< \brief Indices of the primitive flow variables. */ @@ -440,7 +441,7 @@ class CScalarSolver : public CSolver { * \param[in] geometry - Geometrical definition of the problem. * \param[in] config - Definition of the particular problem. */ - CScalarSolver(CGeometry* geometry, CConfig* config, bool conservative); + CScalarSolver(CGeometry* geometry, CConfig* config, bool conservative, bool bounded_scalar); /*! * \brief Compute the spatial integration using a upwind scheme. diff --git a/SU2_CFD/include/solvers/CScalarSolver.inl b/SU2_CFD/include/solvers/CScalarSolver.inl index d6e05191b228..2a20484449bb 100644 --- a/SU2_CFD/include/solvers/CScalarSolver.inl +++ b/SU2_CFD/include/solvers/CScalarSolver.inl @@ -30,8 +30,8 @@ #include "../../include/variables/CFlowVariable.hpp" template -CScalarSolver::CScalarSolver(CGeometry* geometry, CConfig* config, bool conservative) - : CSolver(), Conservative(conservative), +CScalarSolver::CScalarSolver(CGeometry* geometry, CConfig* config, bool conservative, bool bounded_scalar) + : CSolver(), Conservative(conservative), BoundedScalar(bounded_scalar), prim_idx(config->GetKind_Regime() == ENUM_REGIME::INCOMPRESSIBLE, config->GetNEMOProblem(), geometry->GetnDim(), config->GetnSpecies()) { SU2_ZONE_SCOPED @@ -639,11 +639,11 @@ void CScalarSolver::SetResidual_DualTime(CGeometry* geometry, CSol const bool implicit = (config->GetKind_TimeIntScheme() == EULER_IMPLICIT); const bool first_order = (config->GetTime_Marching() == TIME_MARCHING::DT_STEPPING_1ST); const bool second_order = (config->GetTime_Marching() == TIME_MARCHING::DT_STEPPING_2ND); - const bool incompressible = (config->GetKind_Regime() == ENUM_REGIME::INCOMPRESSIBLE); - /*--- Flow solution, needed to get density. ---*/ + const bool bounded_scalar = BoundedScalar; - CVariable* flowNodes = solver_container[FLOW_SOL]->GetNodes(); + /*--- Flow solution, needed to get density. ---*/ + auto* flowNodes = su2staticcast_p(solver_container[FLOW_SOL]->GetNodes()); /*--- Store the physical time step ---*/ @@ -671,19 +671,9 @@ void CScalarSolver::SetResidual_DualTime(CGeometry* geometry, CSol SU2_OMP_FOR_STAT(omp_chunk_size) for (iPoint = 0; iPoint < nPointDomain; iPoint++) { if (Conservative) { - if (incompressible) { - /*--- This is temporary and only valid for constant-density problems: - density could also be temperature dependent, but as it is not a part - of the solution vector it's neither stored for previous time steps - nor updated with the solution at the end of each iteration. */ - Density_nM1 = flowNodes->GetDensity(iPoint); - Density_n = flowNodes->GetDensity(iPoint); - Density_nP1 = flowNodes->GetDensity(iPoint); - } else { - Density_nM1 = flowNodes->GetSolution_time_n1(iPoint)[0]; - Density_n = flowNodes->GetSolution_time_n(iPoint, 0); - Density_nP1 = flowNodes->GetSolution(iPoint, 0); - } + Density_nM1 = flowNodes->GetDensity_time_n1(iPoint); + Density_n = flowNodes->GetDensity_time_n(iPoint); + Density_nP1 = flowNodes->GetDensity(iPoint); } /*--- Retrieve the solution at time levels n-1, n, and n+1. Note that @@ -703,13 +693,20 @@ void CScalarSolver::SetResidual_DualTime(CGeometry* geometry, CSol time discretization scheme (1st- or 2nd-order).---*/ for (iVar = 0; iVar < nVar; iVar++) { + su2double unsteady_term = 0.0; if (first_order) - LinSysRes(iPoint, iVar) += - (Density_nP1 * U_time_nP1[iVar] - Density_n * U_time_n[iVar]) * Volume_nP1 / TimeStep; + unsteady_term = (Density_nP1 * U_time_nP1[iVar] - Density_n * U_time_n[iVar]) * Volume_nP1 / TimeStep; if (second_order) - LinSysRes(iPoint, iVar) += (3.0 * Density_nP1 * U_time_nP1[iVar] - 4.0 * Density_n * U_time_n[iVar] + + unsteady_term = (3.0 * Density_nP1 * U_time_nP1[iVar] - 4.0 * Density_n * U_time_n[iVar] + 1.0 * Density_nM1 * U_time_nM1[iVar]) * Volume_nP1 / (2.0 * TimeStep); + + if (bounded_scalar) { + if (first_order) unsteady_term -= U_time_nP1[iVar] * (Density_nP1 - Density_n) * Volume_nP1 / TimeStep; + if (second_order) unsteady_term -= U_time_nP1[iVar] * (3.0 * Density_nP1 - 4.0 * Density_n + 1.0 * Density_nM1) * Volume_nP1 / (2.0 * TimeStep); + } + + LinSysRes(iPoint, iVar) += unsteady_term; } /*--- Compute the Jacobian contribution due to the dual time source term. ---*/ @@ -737,10 +734,7 @@ void CScalarSolver::SetResidual_DualTime(CGeometry* geometry, CSol U_time_n = nodes->GetSolution_time_n(iPoint); if (Conservative) { - if (incompressible) - Density_n = flowNodes->GetDensity(iPoint); // Temporary fix - else - Density_n = flowNodes->GetSolution_time_n(iPoint, 0); + Density_n = flowNodes->GetDensity_time_n(iPoint); } for (iNeigh = 0; iNeigh < geometry->nodes->GetnPoint(iPoint); iNeigh++) { @@ -793,10 +787,7 @@ void CScalarSolver::SetResidual_DualTime(CGeometry* geometry, CSol /*--- Multiply by density at node i for the SST model ---*/ if (Conservative) { - if (incompressible) - Density_n = flowNodes->GetDensity(iPoint); // Temporary fix - else - Density_n = flowNodes->GetSolution_time_n(iPoint, 0); + Density_n = flowNodes->GetDensity_time_n(iPoint); } for (iVar = 0; iVar < nVar; iVar++) LinSysRes(iPoint, iVar) += Density_n * U_time_n[iVar] * Residual_GCL; @@ -832,37 +823,42 @@ void CScalarSolver::SetResidual_DualTime(CGeometry* geometry, CSol due to the time discretization has a new form.---*/ if (Conservative) { - /*--- If this is the SST model, we need to multiply by the density - in order to get the conservative variables ---*/ - if (incompressible) { - /*--- This is temporary and only valid for constant-density problems: - density could also be temperature dependent, but as it is not a part - of the solution vector it's neither stored for previous time steps - nor updated with the solution at the end of each iteration. */ - Density_nM1 = flowNodes->GetDensity(iPoint); - Density_n = flowNodes->GetDensity(iPoint); - Density_nP1 = flowNodes->GetDensity(iPoint); - } else { - Density_nM1 = flowNodes->GetSolution_time_n1(iPoint)[0]; - Density_n = flowNodes->GetSolution_time_n(iPoint, 0); - Density_nP1 = flowNodes->GetSolution(iPoint, 0); - } + /*--- Get density at different time levels via virtual methods ---*/ + Density_nM1 = flowNodes->GetDensity_time_n1(iPoint); + Density_n = flowNodes->GetDensity_time_n(iPoint); + Density_nP1 = flowNodes->GetDensity(iPoint); } for (iVar = 0; iVar < nVar; iVar++) { + su2double unsteady_term = 0.0; if (first_order) - LinSysRes(iPoint, iVar) += - (Density_nP1 * U_time_nP1[iVar] - Density_n * U_time_n[iVar]) * (Volume_nP1 / TimeStep); + unsteady_term = (Density_nP1 * U_time_nP1[iVar] - Density_n * U_time_n[iVar]) * (Volume_nP1 / TimeStep); if (second_order) - LinSysRes(iPoint, iVar) += - (Density_nP1 * U_time_nP1[iVar] - Density_n * U_time_n[iVar]) * (3.0 * Volume_nP1 / (2.0 * TimeStep)) + + unsteady_term = (Density_nP1 * U_time_nP1[iVar] - Density_n * U_time_n[iVar]) * (3.0 * Volume_nP1 / (2.0 * TimeStep)) + (Density_nM1 * U_time_nM1[iVar] - Density_n * U_time_n[iVar]) * (Volume_nM1 / (2.0 * TimeStep)); + + if (bounded_scalar) { + if (first_order) unsteady_term -= U_time_nP1[iVar] * (Density_nP1 - Density_n) * (Volume_nP1 / TimeStep); + if (second_order) unsteady_term -= U_time_nP1[iVar] * ((Density_nP1 - Density_n) * (3.0 * Volume_nP1 / (2.0 * TimeStep)) + + (Density_nM1 - Density_n) * (Volume_nM1 / (2.0 * TimeStep))); + } + + LinSysRes(iPoint, iVar) += unsteady_term; } /*--- Compute the Jacobian contribution due to the dual time source term. ---*/ if (implicit) { - if (first_order) Jacobian.AddVal2Diag(iPoint, Volume_nP1 / TimeStep); - if (second_order) Jacobian.AddVal2Diag(iPoint, (Volume_nP1 * 3.0) / (2.0 * TimeStep)); + su2double diag_factor = 1.0; + if (Conservative) { + if (bounded_scalar) { + if (first_order) diag_factor = Density_n; + if (second_order) diag_factor = (4.0 * Density_n - Density_nM1) / 3.0; + } else { + diag_factor = Density_nP1; + } + } + if (first_order) Jacobian.AddVal2Diag(iPoint, diag_factor * Volume_nP1 / TimeStep); + if (second_order) Jacobian.AddVal2Diag(iPoint, diag_factor * 3.0 * Volume_nP1 / (2.0 * TimeStep)); } } END_SU2_OMP_FOR diff --git a/SU2_CFD/include/solvers/CTurbSolver.hpp b/SU2_CFD/include/solvers/CTurbSolver.hpp index 47636253a9d7..c5fce372e45a 100644 --- a/SU2_CFD/include/solvers/CTurbSolver.hpp +++ b/SU2_CFD/include/solvers/CTurbSolver.hpp @@ -133,7 +133,7 @@ class CTurbSolver : public CScalarSolver { * \returns The number of extra variables. */ unsigned long RegisterSolutionExtra(bool input, const CConfig* config) final; - + /*! * \brief Compute a suitable under-relaxation parameter to limit the change in the solution variables over * a nonlinear iteration for stability. diff --git a/SU2_CFD/include/variables/CEulerVariable.hpp b/SU2_CFD/include/variables/CEulerVariable.hpp index 12308f29c6cf..abd63a3afd27 100644 --- a/SU2_CFD/include/variables/CEulerVariable.hpp +++ b/SU2_CFD/include/variables/CEulerVariable.hpp @@ -97,6 +97,20 @@ class CEulerVariable : public CFlowVariable { CEulerVariable(su2double density, const su2double *velocity, su2double energy, unsigned long npoint, unsigned long ndim, unsigned long nvar, const CConfig *config); + /*! + * \brief Get the density at time level n for dual-time stepping. + * \param[in] iPoint - Point index. + * \return Density at time level n. + */ + inline su2double GetDensity_time_n(unsigned long iPoint) const final { return GetSolution_time_n(iPoint, 0); } + + /*! + * \brief Get the density at time level n-1 for dual-time stepping. + * \param[in] iPoint - Point index. + * \return Density at time level n-1. + */ + inline su2double GetDensity_time_n1(unsigned long iPoint) const final { return GetSolution_time_n1(iPoint, 0); } + /*! * \brief A virtual member. */ diff --git a/SU2_CFD/include/variables/CFlowVariable.hpp b/SU2_CFD/include/variables/CFlowVariable.hpp index 61e793cb42c1..92115519b6b3 100644 --- a/SU2_CFD/include/variables/CFlowVariable.hpp +++ b/SU2_CFD/include/variables/CFlowVariable.hpp @@ -270,4 +270,18 @@ class CFlowVariable : public CVariable { * \return Vector of magnitudes. */ inline su2activevector& GetStrainMag() { return StrainMag; } + + /*! + * \brief Get the density at time level n for dual-time stepping. + * \param[in] iPoint - Point index. + * \return Density at time level n. + */ + virtual su2double GetDensity_time_n(unsigned long iPoint) const = 0; + + /*! + * \brief Get the density at time level n-1 for dual-time stepping. + * \param[in] iPoint - Point index. + * \return Density at time level n-1. + */ + virtual su2double GetDensity_time_n1(unsigned long iPoint) const = 0; }; diff --git a/SU2_CFD/include/variables/CIncEulerVariable.hpp b/SU2_CFD/include/variables/CIncEulerVariable.hpp index 46be6d3dc2b6..d76990b71eb8 100644 --- a/SU2_CFD/include/variables/CIncEulerVariable.hpp +++ b/SU2_CFD/include/variables/CIncEulerVariable.hpp @@ -71,6 +71,8 @@ class CIncEulerVariable : public CFlowVariable { VectorType Streamwise_Periodic_RecoveredPressure, /*!< \brief Recovered/Physical pressure [Pa] for streamwise periodic flow. */ Streamwise_Periodic_RecoveredTemperature; /*!< \brief Recovered/Physical temperature [K] for streamwise periodic flow. */ + VectorType Density_time_n, /*!< \brief Density at time n for dual-time stepping. */ + Density_time_n1; /*!< \brief Density at time n-1 for dual-time stepping. */ su2double TemperatureLimits[2]; /*!< \brief Temperature limits [K]. */ public: /*! @@ -291,4 +293,36 @@ class CIncEulerVariable : public CFlowVariable { for (unsigned long iDim = 0; iDim < nDim; iDim++) Solution(iPoint, iDim+1) = val_vector[iDim]; } + /*! + * \brief Get the density at time level n for dual-time stepping. + * \param[in] iPoint - Point index. + * \return Density at time level n. + */ + inline su2double GetDensity_time_n(unsigned long iPoint) const final { + return Density_time_n.size() > 0 ? Density_time_n(iPoint) : GetDensity(iPoint); + } + + /*! + * \brief Get the density at time level n-1 for dual-time stepping. + * \param[in] iPoint - Point index. + * \return Density at time level n-1. + */ + inline su2double GetDensity_time_n1(unsigned long iPoint) const final { + return Density_time_n1.size() > 0 ? Density_time_n1(iPoint) : GetDensity(iPoint); + } + + /*! + * \brief Set the density at time level n for dual-time stepping. + * \param[in] iPoint - Point index. + * \param[in] val_density - Density value. + */ + inline void SetDensity_time_n(unsigned long iPoint, su2double val_density) { Density_time_n(iPoint) = val_density; } + + /*! + * \brief Set the density at time level n-1 for dual-time stepping. + * \param[in] iPoint - Point index. + * \param[in] val_density - Density value. + */ + inline void SetDensity_time_n1(unsigned long iPoint, su2double val_density) { Density_time_n1(iPoint) = val_density; } + }; diff --git a/SU2_CFD/include/variables/CNEMOEulerVariable.hpp b/SU2_CFD/include/variables/CNEMOEulerVariable.hpp index 408dcc7c144e..e0d4199144c4 100644 --- a/SU2_CFD/include/variables/CNEMOEulerVariable.hpp +++ b/SU2_CFD/include/variables/CNEMOEulerVariable.hpp @@ -122,6 +122,20 @@ class CNEMOEulerVariable : public CFlowVariable { unsigned long nvar, unsigned long nvalprim, unsigned long nvarprimgrad, const CConfig *config, CNEMOGas *fluidmodel); + /*! + * \brief Get the density at time level n for dual-time stepping. + * \param[in] iPoint - Point index. + * \return Density at time level n. + */ + inline su2double GetDensity_time_n(unsigned long iPoint) const final { return GetSolution_time_n(iPoint, 0); } + + /*! + * \brief Get the density at time level n-1 for dual-time stepping. + * \param[in] iPoint - Point index. + * \return Density at time level n-1. + */ + inline su2double GetDensity_time_n1(unsigned long iPoint) const final { return GetSolution_time_n1(iPoint, 0); } + /*---------------------------------------*/ /*--- U,V,S Routines ---*/ /*---------------------------------------*/ diff --git a/SU2_CFD/src/solvers/CDiscAdjSolver.cpp b/SU2_CFD/src/solvers/CDiscAdjSolver.cpp index 3194e28b6d37..0f396a2ebd04 100644 --- a/SU2_CFD/src/solvers/CDiscAdjSolver.cpp +++ b/SU2_CFD/src/solvers/CDiscAdjSolver.cpp @@ -168,11 +168,13 @@ void CDiscAdjSolver::RegisterSolution(CGeometry *geometry, CConfig *config) { /*--- Register quantities that are no solver variables but further inputs/outputs of the (outer) iteration. ---*/ direct_solver->RegisterSolutionExtra(true, config); - if (time_n_needed) + if (time_n_needed) { direct_solver->GetNodes()->RegisterSolution_time_n(); + } - if (time_n1_needed) + if (time_n1_needed) { direct_solver->GetNodes()->RegisterSolution_time_n1(); + } } void CDiscAdjSolver::RegisterVariables(CGeometry *geometry, CConfig *config, bool reset) { diff --git a/SU2_CFD/src/solvers/CHeatSolver.cpp b/SU2_CFD/src/solvers/CHeatSolver.cpp index 2a9e4790760b..d944255a694c 100644 --- a/SU2_CFD/src/solvers/CHeatSolver.cpp +++ b/SU2_CFD/src/solvers/CHeatSolver.cpp @@ -34,7 +34,7 @@ template class CScalarSolver; CHeatSolver::CHeatSolver(CGeometry *geometry, CConfig *config, unsigned short iMesh) - : CScalarSolver(geometry, config, false), + : CScalarSolver(geometry, config, false, false), flow(config->GetFluidProblem()) { SU2_ZONE_SCOPED diff --git a/SU2_CFD/src/solvers/CIncEulerSolver.cpp b/SU2_CFD/src/solvers/CIncEulerSolver.cpp index 4951551851f0..41580c3062af 100644 --- a/SU2_CFD/src/solvers/CIncEulerSolver.cpp +++ b/SU2_CFD/src/solvers/CIncEulerSolver.cpp @@ -966,6 +966,8 @@ void CIncEulerSolver::CommonPreprocessing(CGeometry *geometry, CSolver **solver_ const bool center = (config->GetKind_ConvNumScheme_Flow() == SPACE_CENTERED); const bool center_jst = (config->GetKind_Centered_Flow() == CENTERED::JST || config->GetKind_Centered_Flow() == CENTERED::LD2) && (iMesh == MESH_0); const bool outlet = (config->GetnMarker_Outlet() != 0); + const bool dual_time = (config->GetTime_Marching() == TIME_MARCHING::DT_STEPPING_1ST) || + (config->GetTime_Marching() == TIME_MARCHING::DT_STEPPING_2ND); /*--- Set the primitive variables ---*/ @@ -974,6 +976,11 @@ void CIncEulerSolver::CommonPreprocessing(CGeometry *geometry, CSolver **solver_ SU2_OMP_ATOMIC ErrorCounter += SetPrimitive_Variables(solver_container, config); + /*--- InnerIter is not reset while recording the discrete adjoint tape. ---*/ + if (dual_time && (config->GetInnerIter() == 0 || AD::TapeActive())) { + RecomputeDensity_time_n(solver_container, config); + } + if ((iMesh == MESH_0) && (config->GetComm_Level() == COMM_FULL)) { BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { @@ -1077,6 +1084,42 @@ unsigned long CIncEulerSolver::SetPrimitive_Variables(CSolver **solver_container return nonPhysicalPoints; } +void CIncEulerSolver::RecomputeDensity_time_n(CSolver **solver_container, const CConfig *config) { + SU2_ZONE_SCOPED + + /*--- Only variable-density (non-constant) cases allocate the density history. ---*/ + if (config->GetKind_DensityModel() == INC_DENSITYMODEL::CONSTANT) return; + + const bool second_order = (config->GetTime_Marching() == TIME_MARCHING::DT_STEPPING_2ND); + + CVariable* speciesNodes = (solver_container[SPECIES_SOL] != nullptr) + ? solver_container[SPECIES_SOL]->GetNodes() : nullptr; + + /*--- The species solver only exists on the fine grid; MG with scalar-dependent density is rejected in CConfig. ---*/ + const bool needs_scalars = (config->GetKind_Species_Model() != SPECIES_MODEL::NONE); + if (needs_scalars && speciesNodes == nullptr) return; + + SU2_OMP_FOR_STAT(omp_chunk_size) + for (unsigned long iPoint = 0; iPoint < nPoint; iPoint++) { + + /*--- Per-thread fluid model, mirroring the recipe in SetPrimitive_Variables. ---*/ + CFluidModel* fluidModel = GetFluidModel(); + + const su2double* scalar_n = speciesNodes ? speciesNodes->GetSolution_time_n(iPoint) : nullptr; + const su2double Enthalpy_n = nodes->GetSolution_time_n(iPoint, nDim + 1); + fluidModel->SetTDState_h(Enthalpy_n, scalar_n); + nodes->SetDensity_time_n(iPoint, fluidModel->GetDensity()); + + if (second_order) { + const su2double* scalar_n1 = speciesNodes ? speciesNodes->GetSolution_time_n1(iPoint) : nullptr; + const su2double Enthalpy_n1 = nodes->GetSolution_time_n1(iPoint, nDim + 1); + fluidModel->SetTDState_h(Enthalpy_n1, scalar_n1); + nodes->SetDensity_time_n1(iPoint, fluidModel->GetDensity()); + } + } + END_SU2_OMP_FOR +} + void CIncEulerSolver::SetTime_Step(CGeometry *geometry, CSolver **solver_container, CConfig *config, unsigned short iMesh, unsigned long Iteration) { SU2_ZONE_SCOPED @@ -2867,14 +2910,17 @@ void CIncEulerSolver::SetResidual_DualTime(CGeometry *geometry, CSolver **solver V_time_n = nodes->GetSolution_time_n(iPoint); V_time_nP1 = nodes->GetSolution(iPoint); - /*--- Access the density at this node (constant for now). ---*/ + /*--- Access the density at different time levels for non-constant density. ---*/ - Density = nodes->GetDensity(iPoint); + su2double Density_nM1 = nodes->GetDensity_time_n1(iPoint); + su2double Density_n = nodes->GetDensity_time_n(iPoint); + Density = nodes->GetDensity(iPoint); // Density at n+1 - /*--- Compute the conservative variable vector for all time levels. ---*/ + /*--- Compute the conservative variable vector for all time levels. + Use the density from the corresponding time level. ---*/ - V2U(Density, V_time_nM1, U_time_nM1); - V2U(Density, V_time_n, U_time_n); + V2U(Density_nM1, V_time_nM1, U_time_nM1); + V2U(Density_n, V_time_n, U_time_n); V2U(Density, V_time_nP1, U_time_nP1); /*--- CV volume at time n+1. As we are on a static mesh, the volume @@ -2922,8 +2968,8 @@ void CIncEulerSolver::SetResidual_DualTime(CGeometry *geometry, CSolver **solver /*--- Compute the conservative variables. ---*/ V_time_n = nodes->GetSolution_time_n(iPoint); - Density = nodes->GetDensity(iPoint); - V2U(Density, V_time_n, U_time_n); + su2double Density_n = nodes->GetDensity_time_n(iPoint); + V2U(Density_n, V_time_n, U_time_n); GridVel_i = geometry->nodes->GetGridVel(iPoint); @@ -2977,8 +3023,8 @@ void CIncEulerSolver::SetResidual_DualTime(CGeometry *geometry, CSolver **solver /*--- Compute the GCL component of the source term for node i ---*/ V_time_n = nodes->GetSolution_time_n(iPoint); - Density = nodes->GetDensity(iPoint); - V2U(Density, V_time_n, U_time_n); + su2double Density_n = nodes->GetDensity_time_n(iPoint); + V2U(Density_n, V_time_n, U_time_n); for (iVar = 0; iVar < nVar-!energy; iVar++) LinSysRes(iPoint,iVar) += U_time_n[iVar]*Residual_GCL; @@ -3004,14 +3050,17 @@ void CIncEulerSolver::SetResidual_DualTime(CGeometry *geometry, CSolver **solver V_time_n = nodes->GetSolution_time_n(iPoint); V_time_nP1 = nodes->GetSolution(iPoint); - /*--- Access the density at this node (constant for now). ---*/ + /*--- Access the density at different time levels for non-constant density. ---*/ - Density = nodes->GetDensity(iPoint); + su2double Density_nM1 = nodes->GetDensity_time_n1(iPoint); + su2double Density_n = nodes->GetDensity_time_n(iPoint); + Density = nodes->GetDensity(iPoint); // Density at n+1 - /*--- Compute the conservative variable vector for all time levels. ---*/ + /*--- Compute the conservative variable vector for all time levels. + Use the density from the corresponding time level. ---*/ - V2U(Density, V_time_nM1, U_time_nM1); - V2U(Density, V_time_n, U_time_n); + V2U(Density_nM1, V_time_nM1, U_time_nM1); + V2U(Density_n, V_time_n, U_time_n); V2U(Density, V_time_nP1, U_time_nP1); /*--- CV volume at time n-1 and n+1. In the case of dynamically deforming diff --git a/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp b/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp index 82bf04a0125e..fee5a0702e2b 100644 --- a/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp +++ b/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp @@ -83,7 +83,12 @@ void CSpeciesFlameletSolver::Preprocessing(CGeometry* geometry, CSolver** solver auto spark_init = flamelet_config_options.spark_init; spark_iter_start = ceil(spark_init[4]); spark_duration = ceil(spark_init[5]); - unsigned long iter = config->GetMultizone_Problem() ? config->GetOuterIter() : config->GetInnerIter(); + unsigned long iter; + if (config->GetTime_Domain()) { + iter = config->GetTimeIter(); // Use time step counter for unsteady problems + } else { + iter = config->GetMultizone_Problem() ? config->GetOuterIter() : config->GetInnerIter(); + } ignition = ((iter >= spark_iter_start) && (iter <= (spark_iter_start + spark_duration))); } diff --git a/SU2_CFD/src/solvers/CSpeciesSolver.cpp b/SU2_CFD/src/solvers/CSpeciesSolver.cpp index 28d09dac0506..38ad833f65ef 100644 --- a/SU2_CFD/src/solvers/CSpeciesSolver.cpp +++ b/SU2_CFD/src/solvers/CSpeciesSolver.cpp @@ -36,7 +36,7 @@ template class CScalarSolver; CSpeciesSolver::CSpeciesSolver(CGeometry* geometry, CConfig* config, unsigned short iMesh) - : CScalarSolver(geometry, config, true) { + : CScalarSolver(geometry, config, true, config->GetBounded_Species()) { SU2_ZONE_SCOPED /*--- Dimension of the problem. ---*/ diff --git a/SU2_CFD/src/solvers/CTurbSolver.cpp b/SU2_CFD/src/solvers/CTurbSolver.cpp index 51562382311d..b00ff47dd9f3 100644 --- a/SU2_CFD/src/solvers/CTurbSolver.cpp +++ b/SU2_CFD/src/solvers/CTurbSolver.cpp @@ -34,7 +34,7 @@ template class CScalarSolver; CTurbSolver::CTurbSolver(CGeometry* geometry, CConfig *config, bool conservative) - : CScalarSolver(geometry, config, conservative) { + : CScalarSolver(geometry, config, conservative, config->GetBounded_Turb()) { SU2_ZONE_SCOPED /*--- Store if an implicit scheme is used, for use during periodic boundary conditions. ---*/ SetImplicitPeriodic(config->GetKind_TimeIntScheme_Turb() == EULER_IMPLICIT); diff --git a/SU2_CFD/src/variables/CIncEulerVariable.cpp b/SU2_CFD/src/variables/CIncEulerVariable.cpp index 355a15874884..5274df766447 100644 --- a/SU2_CFD/src/variables/CIncEulerVariable.cpp +++ b/SU2_CFD/src/variables/CIncEulerVariable.cpp @@ -27,6 +27,7 @@ #include "../../include/variables/CIncEulerVariable.hpp" #include "../../include/fluid/CFluidModel.hpp" +#include "../../../Common/include/parallelization/omp_structure.hpp" CIncEulerVariable::CIncEulerVariable(su2double pressure, const su2double *velocity, su2double enthalpy, unsigned long npoint, unsigned long ndim, unsigned long nvar, const CConfig *config) @@ -58,6 +59,11 @@ CIncEulerVariable::CIncEulerVariable(su2double pressure, const su2double *veloci if (dual_time) { Solution_time_n = Solution; Solution_time_n1 = Solution; + + if (config->GetKind_DensityModel() != INC_DENSITYMODEL::CONSTANT) { + Density_time_n.resize(nPoint) = su2double(0.0); + Density_time_n1.resize(nPoint) = su2double(0.0); + } } if (config->GetKind_Streamwise_Periodic() != ENUM_STREAMWISE_PERIODIC::NONE) { @@ -127,3 +133,4 @@ bool CIncEulerVariable::SetPrimVar(unsigned long iPoint, CFluidModel *FluidModel return physical; } + diff --git a/TestCases/flamelet/09_laminar_premixed_ch4_flame_unsteady/lam_prem_ch4_unsteady.cfg b/TestCases/flamelet/09_laminar_premixed_ch4_flame_unsteady/lam_prem_ch4_unsteady.cfg new file mode 100644 index 000000000000..062586341780 --- /dev/null +++ b/TestCases/flamelet/09_laminar_premixed_ch4_flame_unsteady/lam_prem_ch4_unsteady.cfg @@ -0,0 +1,154 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% SU2 configuration file % +% Case description: Laminar premixed transient flame propagating in a channel % +% Author: Nijso Beishuizen % +% Institution: TU Eindhoven % +% Date: 30/04/2026 % +% File Version 8.5.0 "Harrier" % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% +% +SOLVER= INC_NAVIER_STOKES +KIND_TURB_MODEL= NONE +MATH_PROBLEM= DIRECT +RESTART_SOL= YES +% +% ---------------- INCOMPRESSIBLE FLOW CONDITION DEFINITION -------------------% +% +%INC_DENSITY_MODEL= FLAMELET +INC_DENSITY_MODEL= VARIABLE +INC_DENSITY_INIT= 1.00 +INC_VELOCITY_INIT= (0.5, 0.0, 0.0 ) +INC_TEMPERATURE_INIT= 300.0 +INC_NONDIM= DIMENSIONAL +% +% -------------------- FLUID MODEL --------------------------------------- % +% +FLUID_MODEL= FLUID_FLAMELET +PREFERENTIAL_DIFFUSION= NO +FILENAMES_INTERPOLATOR= (fgm_ch4_phi075.drg) +CONTROLLING_VARIABLE_NAMES= (ProgressVariable, EnthalpyTot) +CONTROLLING_VARIABLE_SOURCE_NAMES= (ProdRateTot_PV, NULL) +% +% -------------------- SCALAR TRANSPORT ---------------------------------------% +% +KIND_SCALAR_MODEL= FLAMELET +DIFFUSIVITY_MODEL= FLAMELET +VISCOSITY_MODEL= FLAMELET +CONDUCTIVITY_MODEL= FLAMELET +%FLAME_INIT_METHOD= FLAME_FRONT +FLAME_INIT_METHOD= NONE + +FLAME_INIT= (0.009, 0.00, 0.00, 1.0, 0.0, 0.0, 5.0e-4, 0.1) +% # progvar, enthalpy +SPECIES_INIT= (0.0, -193150) +CONV_NUM_METHOD_SPECIES= BOUNDED_SCALAR +MUSCL_SPECIES= YES +SLOPE_LIMITER_SPECIES= NONE +TIME_DISCRE_SPECIES= EULER_IMPLICIT +% SCALAR CLIPPING +SPECIES_CLIPPING= YES +SPECIES_CLIPPING_MIN= 0.000 -1e6 +SPECIES_CLIPPING_MAX= 0.300 +1e5 +% +MARKER_INLET_SPECIES= (inlet, 0, -193154.0) +CFL_REDUCTION_SPECIES= 1.0 +MARKER_SPECIES_STRONG_BC= (inlet, outlet) +LOOKUP_NAMES= (MolarWeightMix, Conductivity, Cp) +% +% ---------------------- REFERENCE VALUE DEFINITION ---------------------------% +% +REF_ORIGIN_MOMENT_X= 0.25 +REF_ORIGIN_MOMENT_Y= 0.00 +REF_ORIGIN_MOMENT_Z= 0.00 +REF_LENGTH= 1.0 +REF_AREA= 1.0 +% +% -------------------- BOUNDARY CONDITION DEFINITION --------------------------% +% +MARKER_SYM= (symmetry_bottom) +MARKER_EULER= wall +INC_INLET_TYPE= VELOCITY_INLET + +% switch from 'steady' flame to 'propagating with 0.1 m/s' +MARKER_INLET= (inlet, 300.0, 0.105, 1.0, 0.0, 0.0) +%MARKER_INLET= (inlet, 300.0, 0.127, 1.0, 0.0, 0.0) + +INC_OUTLET_TYPE= PRESSURE_OUTLET +INC_INLET_DAMPING= 0.1 +INC_OUTLET_DAMPING= 0.1 +MARKER_OUTLET= (outlet, 0.0) +MARKER_PLOTTING= ( inlet ) +MARKER_MONITORING= ( inlet ) +MARKER_ANALYZE= ( inlet,outlet ) +MARKER_ANALYZE_AVERAGE= AREA +% +% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% +% +NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES +%CFL_NUMBER= 10000 +CFL_NUMBER= 1000 +CFL_ADAPT= NO +OUTPUT_WRT_FREQ= 1,1 +% +% ------------------------- UNSTEADY SIMULATION -------------------------------% +% +TIME_DOMAIN= YES +% +TIME_MARCHING= DUAL_TIME_STEPPING-1ST_ORDER +%TIME_MARCHING= DUAL_TIME_STEPPING-2ND_ORDER +% +RESTART_ITER=1 +TIME_ITER= 25 +TIME_STEP= 5e-4 +% time= 0.1 s +MAX_TIME= 0.1 +% +UNST_CFL_NUMBER= 0.0 +INNER_ITER= 100 +LINEAR_SOLVER= FGMRES +LINEAR_SOLVER_PREC= ILU +% no impact on flame speed +LINEAR_SOLVER_ERROR= 0.1 +LINEAR_SOLVER_ITER= 5 +% +% -------------------------- MULTIGRID PARAMETERS -----------------------------% +% +MGLEVEL= 0 +% +% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% +% +CONV_NUM_METHOD_FLOW= FDS +MUSCL_FLOW= YES +SLOPE_LIMITER_FLOW= NONE +TIME_DISCRE_FLOW= EULER_IMPLICIT +% +% --------------------------- CONVERGENCE PARAMETERS --------------------------% +% +CONV_RESIDUAL_MINVAL= -10.0 +CONV_FIELD= RMS_ProgressVariable, RMS_VELOCITY-X, RMS_MixtureFraction +CONV_STARTITER= 10 +CONV_CAUCHY_ELEMS= 100 +CONV_CAUCHY_EPS= 1E-6 +SCREEN_OUTPUT= TIME_ITER INNER_ITER RMS_VELOCITY-X RMS_PRESSURE RMS_ProgressVariable RMS_EnthalpyTot RMS_MixtureFraction +HISTORY_OUTPUT= RMS_RES AERO_COEFF FLOW_COEFF FLOW_COEFF_SURF +VOLUME_OUTPUT= SOLUTION PRIMITIVE SOURCE RESIDUAL SENSITIVITY LOOKUP TIMESTEP +% +% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% +% +MESH_FORMAT= SU2 +MESH_FILENAME= 1d_chamber.su2 +MESH_OUT_FILENAME= mesh_out +SOLUTION_FILENAME= solution +RESTART_FILENAME= restart +OUTPUT_FILES= (RESTART,PARAVIEW) +%OUTPUT_FILES= (RESTART) +TABULAR_FORMAT= CSV +CONV_FILENAME= history +VOLUME_FILENAME= ch4_flame_cfd +SURFACE_FILENAME= surface_flow +WRT_PERFORMANCE= YES +SCREEN_WRT_FREQ_INNER= 1 +SCREEN_WRT_FREQ_OUTER= 1 diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index e6242b093b1c..fe7bfe81c60f 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -86,6 +86,15 @@ def main(): flame_init_methods.new_output = True test_list.append(flame_init_methods) + # 2D laminar premixed ch4-air flame, transient flame propagation + cfd_flamelet_ch4_unsteady = TestCase('cfd_flamelet_ch4_unsteady') + cfd_flamelet_ch4_unsteady.cfg_dir = "flamelet/09_laminar_premixed_ch4_flame_unsteady" + cfd_flamelet_ch4_unsteady.cfg_file = "lam_prem_ch4_unsteady.cfg" + cfd_flamelet_ch4_unsteady.test_iter = 5 + cfd_flamelet_ch4_unsteady.test_vals = [-8.856420, -8.095249, -9.153744, -9.321679] + cfd_flamelet_ch4_unsteady.test_vals_aarch64 = [-8.855500, -8.095195, -9.153704, -9.321686] + test_list.append(cfd_flamelet_ch4_unsteady) + ######################### ## NEMO solver ### ######################### @@ -1561,7 +1570,7 @@ def main(): pywrapper_rigidMotion.cfg_dir = "py_wrapper/flatPlate_rigidMotion" pywrapper_rigidMotion.cfg_file = "flatPlate_rigidMotion_Conf.cfg" pywrapper_rigidMotion.test_iter = 5 - pywrapper_rigidMotion.test_vals = [-1.614166, 2.255135, 0.350196, 0.089496] + pywrapper_rigidMotion.test_vals = [-1.607009, 2.260791, 0.350196, 0.089496] pywrapper_rigidMotion.command = TestCase.Command("mpirun -np 2", "python", "launch_flatPlate_rigidMotion.py --parallel -f") pywrapper_rigidMotion.unsteady = True test_list.append(pywrapper_rigidMotion) diff --git a/TestCases/parallel_regression_AD.py b/TestCases/parallel_regression_AD.py index e68c3f27648e..ee45cf41a79f 100644 --- a/TestCases/parallel_regression_AD.py +++ b/TestCases/parallel_regression_AD.py @@ -308,7 +308,7 @@ def main(): da_sp_pinArray_cht_2d_dp_hf.cfg_dir = "incomp_navierstokes/streamwise_periodic/chtPinArray_2d" da_sp_pinArray_cht_2d_dp_hf.cfg_file = "DA_configMaster.cfg" da_sp_pinArray_cht_2d_dp_hf.test_iter = 100 - da_sp_pinArray_cht_2d_dp_hf.test_vals = [-11.620815, -6.488915, -13.316675] + da_sp_pinArray_cht_2d_dp_hf.test_vals = [-11.620815, -6.488915, -13.316362] da_sp_pinArray_cht_2d_dp_hf.multizone = True test_list.append(da_sp_pinArray_cht_2d_dp_hf) @@ -326,7 +326,7 @@ def main(): da_unsteadyCHT_cylinder.cfg_dir = "coupled_cht/disc_adj_unsteadyCHT_cylinder" da_unsteadyCHT_cylinder.cfg_file = "chtMaster.cfg" da_unsteadyCHT_cylinder.test_iter = 2 - da_unsteadyCHT_cylinder.test_vals = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662005, 0.000000, 10.627000, 0.295190] + da_unsteadyCHT_cylinder.test_vals = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662021, 0.000000, 10.627000, 0.295190] da_unsteadyCHT_cylinder.test_vals_aarch64 = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662012, 0.000000, 89.932000, 0.295190] da_unsteadyCHT_cylinder.unsteady = True da_unsteadyCHT_cylinder.multizone = True diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index b5eeedb38aed..0d8e58e79d98 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -1633,7 +1633,7 @@ def main(): pywrapper_rigidMotion.cfg_dir = "py_wrapper/flatPlate_rigidMotion" pywrapper_rigidMotion.cfg_file = "flatPlate_rigidMotion_Conf.cfg" pywrapper_rigidMotion.test_iter = 5 - pywrapper_rigidMotion.test_vals = [-1.614166, 2.255135, 0.350208, 0.089496] + pywrapper_rigidMotion.test_vals = [-1.607008, 2.260791, 0.350208, 0.089496] pywrapper_rigidMotion.command = TestCase.Command(exec = "python", param = "launch_flatPlate_rigidMotion.py -f") pywrapper_rigidMotion.timeout = 1600 pywrapper_rigidMotion.tol = 0.00001 From d6ac7b6908d4f471aa09e4364afd7ce499725bb9 Mon Sep 17 00:00:00 2001 From: Davide <58471586+ddg93@users.noreply.github.com> Date: Sat, 15 Aug 2026 05:26:57 +0200 Subject: [PATCH 29/61] [GSoC] Feature: MultiDot with cublasgemm (Originally: Jacobi preconditioner on GPU and CUDA Unified preconditiong matrix) (#2843) ## Proposed Changes This PR introduces improves the GPU implementation of the multiDot product between Krylov vectors used in the FGMRES solver. It moves from a looped instantiation of pairwise dot products to a batched one-time kernel launch. A custom CUDA kernel and the batched gemm using cublasgemm are benchmarked, the latter being eventually selected. ## Original proposal for the GSoC This PR introduces CUDA Unified memory and Managed memory allocation, and memory management for the CSysVector class and the preconditioning matrix inside the Jacobi preconditioner. This allows for a benchmark between the two memory strategies within the FGMRES solver. Also, this draft PR extends the section of GPU execution inside the FGMRES solver Custom CUDA kernels are implemented for the preconditioning matrix, the multi dot product and the linear combination (inside the Modified Gram-Schmidt orthogonalization), and the vector norm calculation. Unary vector-scalar operations based on templates are also offloaded to GPU through a generic kernel. Moreover, abstract Syntax Tree are deployed to offload vector-vector binary operations to the GPU through a generic kernel based on a runtime evaluation of the tree. The solver logic is not modified, and the GPU path is hidden inside the specific methods. ### Unified memory approach https://github.com/su2code/SU2/pull/2843/commits/5fe25b814ff1c9b256a00a568f4c5e41be5c96ee A custom data() method recovers the CSysVector Unified pointer inside the CUDA logic, cleaning the logic from the double host/device pointers. All memory explicit memory copies are also removed from the CUDA logic, but explicit device synchronizations are introduced around MPI calls and at the end of the CUDA section. ### Managed memory approach https://github.com/su2code/SU2/pull/2843/commits/4b43fa0d365475d5e500ff50a3fa1c73c0d13976 Operators accessing the CSysVector on Host after GPU operations demand synchronization, which is introduced explicitly in each. ## Performance evaluation (on-going) For the considered test-case (rae2822), the CPU execution expresses an **Avg. s/iter: 0.198681**. The GPU execution with Unified memory expresses an **Avg. s/iter: 0.238953**. The GPU execution with Managed memory expresses an **Avg. s/iter: 0.372046**. The CPU is a Intel(R) Xeon(R) E-2276M CPU @ 2.80GHz with 12 cores. The GPU is a Quadro P620. Tests on more advanced hardware are ongoing. Profiling results comparing the Unified Memory (left column) against the Managed Memory (right column) are available in the attached pdf: [SU2_ra2822_GPU_MA_vs_UM_nsys_prof.pdf](https://github.com/user-attachments/files/30520807/SU2_ra2822_GPU_MA_vs_UM_nsys_prof.pdf) ## Asynchronous pre-fetching: The Jacobi preconditioner calculations are performed on GPU through a new custom CUDA kernel under the preconditioner abstraction. The preconditioning matrix is selected to test CUDA Unified Memory asynchronous prefetching to the GPU. For simplicity, the double CPU/GPU pointer is still maintained in the current logic, although the device pointer reduces to an alias for the Unified Memory pointer when this kind of allocation is adopted. **This strategy introduces a simple context to test the CUDA Unified Memory usage and study the possibility of overlapping memory transfers and calculations without the need to introduce CUDA streams.** Concretely, this PR: - introduces new CUDA Unified Memory allocation methods and asynchronous prefetching; - introduces the GPU logic for the Jacobi preconditioner; - introduces the GPU logic for the multi dot product and the linear combination (Modified Gram-Schmidt orthogonalization); - introduces the GPU logic for the vector norm operation; - introduces the GPU logic for generic scalar-vector unary operations through templates and generic vector/scalar-vector binary operations through Abstract Syntax Tree evaluated at runtime; - finally falls back to CUDA Managed memory as highlighted in the following discussion. This introduces the need for explicity synchronization in all the custom setter/getter methods of the CSysVector. This work is part of my ongoing contribution during the Google Summer of Code 2026 program. ## Validation Validated locally with: - serial CUDA build compilation - serial CPU build compilation - CPU/GPU numerical comparison on 1 representative case (rae2822) tested with LINEAR_SOLVER_PREC=JACOBI with both CUDA Unified and Managed memory approaches. Nsys profiling was performed to confirm the asynchronous prefetching of the CUDA Unified Memory preconditioning matrix on my local GPU. Partial prefetching is observed, although page faults were reported during the preconditioner CUDA kernel, indicating that the calculations were slowed down by the prefetching matrix still being transferred to the GPU. This overlap is expected to largely improve on more modern hardware; tests are ongoing in the cloud. ## Related Work The Jacobi preconditioner kernels come from the PR #2825. ## Observed Issues Some issues were observed during this first period of GSoC: - PR #2825 compiles with CUDA 13.3 but CUSPARSE calls raise an unknown operation at the first matrix-vector product. - the build.meson file has a hard-coded CUDA arch. - the HAVE_MPI flag is not being passed to the nvcc compiler in the develop branch. This raises a linking error if MPI operations are included within the *.cu files. That is not the case in the master branch. ## Next steps I propose to continue working on the following steps: - [X] extend the CUDA Unified Memory allocation to the CSysVector class - [X] evaluate if it might be of interest to extend the CUDA Unified Memory allocation to the CSysMatrix class; Update: discussion with mantainer indicates preference for Managed Memory approach; - [X] benchmark the multiDot approached: looped vs custom kernel batched vs cublasgemm batched: batched approaches are convenient due to just one kernel launch overhead, cublas implementation shows moderate speed-up (0.01s) against the custom CUDA kernel on local P620 but ensures less code complexity, thus it is selected. ## PR Checklist - [X] I am submitting my contribution to the develop branch. - [X] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [X] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [X] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [ ] I have added a test case that demonstrates my contribution, if necessary. - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --------- Co-authored-by: Pedro Gomes --- Common/include/linear_algebra/CSysVector.hpp | 30 ++--- Common/src/linear_algebra/CSysVector.cpp | 91 +++++++------- Common/src/linear_algebra/CSysVectorGPU.cu | 122 +++++++++++++++++-- 3 files changed, 171 insertions(+), 72 deletions(-) diff --git a/Common/include/linear_algebra/CSysVector.hpp b/Common/include/linear_algebra/CSysVector.hpp index 2ffbb9a79672..1b57a6ffe945 100644 --- a/Common/include/linear_algebra/CSysVector.hpp +++ b/Common/include/linear_algebra/CSysVector.hpp @@ -270,6 +270,17 @@ class CSysVector : public VecExpr::CVecExpr, ScalarType> return *this; } + /*! + * \brief GPU helper for `dot`. + */ + ScalarType dotGPU(const CSysVector& other) const; + + /*! + * \brief GPU helper for multiDot. + */ + static su2matrix multiDotGPU(const std::vector>& V, size_t i0, size_t n, + const std::vector>& W, size_t m); + public: static constexpr bool StoreAsRef = true; /*! \brief Required by CVecExpr. */ @@ -394,21 +405,6 @@ class CSysVector : public VecExpr::CVecExpr, ScalarType> */ void DtHTransfer(bool trigger = true) const; - /*! - * \brief Dot product between this vector and another vector on the device. - * \note Explicit GPU helper for solver-side reductions. - * \param[in] other - Input vector. - * \return Dot product result. - */ - ScalarType GPUDot(const CSysVector& other) const; - - /*! - * \brief L2 norm of this vector on the device. - * \note Explicit GPU helper for solver-side reductions. - * \return L2 norm result. - */ - ScalarType GPUNorm() const; - /*! * \brief return device pointer that points to the CSysVector values in GPU memory */ @@ -537,9 +533,9 @@ class CSysVector : public VecExpr::CVecExpr, ScalarType> "On the device the dot product is a cuBLAS call, so it only takes vectors. " "Assign the expression to a vector first."); if (VecExpr::UseDeviceExpressions()) { - /*--- GPUDot reduces over MPI, which has to happen once for the team, so the result + /*--- dotGPU reduces over MPI, which has to happen once for the team, so the result * is published through the same scratch slot the host reduction below uses. ---*/ - SU2_DEVICE_REGION(dot_scratch[0] = GPUDot(expr.derived());) + SU2_DEVICE_REGION(dot_scratch[0] = dotGPU(expr.derived());) return dot_scratch[0]; } } diff --git a/Common/src/linear_algebra/CSysVector.cpp b/Common/src/linear_algebra/CSysVector.cpp index 54e47157d55e..22449c3759d9 100644 --- a/Common/src/linear_algebra/CSysVector.cpp +++ b/Common/src/linear_algebra/CSysVector.cpp @@ -76,66 +76,69 @@ const su2matrix& CSysVector::multiDot(const std::vector< const std::vector>& W, const size_t m) { SU2_ZONE_SCOPED - static constexpr size_t BLOCK_SIZE = 1024; + static su2matrix shared; if (n == 0 || m == 0) return shared; + su2matrix local; + + if (VecExpr::UseDeviceExpressions()) { #ifdef SU2_ENABLE_CUDA_KERNELS - if constexpr (su2_gpu_capable_v) { - if (VecExpr::UseDeviceExpressions()) { - BEGIN_SU2_DEVICE_REGION { - shared.resize(n, m); - for (size_t i = 0; i < n; ++i) { - for (size_t j = 0; j < m; ++j) { - shared(i, j) = V[i0 + i].GPUDot(W[j]); + if constexpr (su2_gpu_capable_v) { + BEGIN_SU2_DEVICE_REGION + local = multiDotGPU(V, i0, n, W, m); + END_SU2_DEVICE_REGION + } else { + SU2_MPI::Error("GPU acceleration is not supported for AD scalar types.", CURRENT_FUNCTION); + } +#else + SU2_MPI::Error( + "\nError in multiDot\nENABLE_CUDA is set to YES\nPlease compile with CUDA options " + "enabled in Meson to access GPU Functions", + CURRENT_FUNCTION); +#endif + } else { + static constexpr size_t BLOCK_SIZE = 1024; + + SU2_OMP_BARRIER + const size_t size = V[0].nElmDomain; + + local.resize(n, m); + local.setConstant(0); + + SU2_OMP_FOR_(schedule(static) SU2_NOWAIT) + for (size_t offset = 0; offset < size; offset += BLOCK_SIZE) { + const auto limit = std::min(offset + BLOCK_SIZE, size); + for (size_t i = 0; i < n; ++i) { + const auto& vi = V[i0 + i]; + for (size_t j = 0; j < m; ++j) { + const auto& wj = W[j]; + ScalarType sum = 0.0; + SU2_OMP_SIMD + for (auto k = offset; k < limit; ++k) { + sum += vi[k] * wj[k]; } + local(i, j) += sum; } } - END_SU2_DEVICE_REGION - return shared; } - } -#endif - - SU2_OMP_BARRIER - const size_t size = V[0].nElmDomain; - - su2matrix local(n, m); - local.setConstant(0); + END_SU2_OMP_FOR - SU2_OMP_FOR_(schedule(static) SU2_NOWAIT) - for (size_t offset = 0; offset < size; offset += BLOCK_SIZE) { - const auto limit = std::min(offset + BLOCK_SIZE, size); + /*--- Reduce over all threads in an ordered way to ensure a deterministic result. ---*/ for (size_t i = 0; i < n; ++i) { - const auto& vi = V[i0 + i]; for (size_t j = 0; j < m; ++j) { - const auto& wj = W[j]; - ScalarType sum = 0.0; - SU2_OMP_SIMD - for (auto k = offset; k < limit; ++k) { - sum += vi[k] * wj[k]; - } - local(i, j) += sum; + W[j].dot_scratch[omp_get_thread_num()] = local(i, j); } - } - } - END_SU2_OMP_FOR - - /*--- Reduce over all threads in an ordered way to ensure a deterministic result. ---*/ - for (size_t i = 0; i < n; ++i) { - for (size_t j = 0; j < m; ++j) { - W[j].dot_scratch[omp_get_thread_num()] = local(i, j); - } - BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS - for (size_t j = 0; j < m; ++j) { - for (int t = 1; t < omp_get_num_threads(); ++t) { - local(i, j) += W[j].dot_scratch[t]; + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS + for (size_t j = 0; j < m; ++j) { + for (int t = 1; t < omp_get_num_threads(); ++t) { + local(i, j) += W[j].dot_scratch[t]; + } } + END_SU2_OMP_SAFE_GLOBAL_ACCESS } - END_SU2_OMP_SAFE_GLOBAL_ACCESS } - /*--- Single AllReduce of the result, only the master thread communicates. ---*/ SU2_OMP_MASTER { shared.resize(n, m); diff --git a/Common/src/linear_algebra/CSysVectorGPU.cu b/Common/src/linear_algebra/CSysVectorGPU.cu index 01b146baf762..ba53b0cbc299 100644 --- a/Common/src/linear_algebra/CSysVectorGPU.cu +++ b/Common/src/linear_algebra/CSysVectorGPU.cu @@ -1,7 +1,7 @@ /*! * \file CSysVectorGPU.cu * \brief Implementations of Kernels and Functions for Vector Operations on the GPU - * \author A. Raj + * \author A. Raj, D. Di giusto * \version 8.5.0 "Harrier" * * SU2 Project Website: https://su2code.github.io @@ -79,7 +79,7 @@ void CSysVector::DtHTransfer(bool trigger) const { } template -ScalarType CSysVector::GPUDot(const CSysVector& other) const { +ScalarType CSysVector::dotGPU(const CSysVector& other) const { SU2_ZONE_SCOPED /*--- Both operands are already on the device, the caller owns the transfers. This * reduces over MPI, so it must be called by a single thread (see SU2_DEVICE_REGION). ---*/ @@ -95,12 +95,12 @@ ScalarType CSysVector::GPUDot(const CSysVector& other) const { status = cublasDdot(handle, static_cast(nElmDomain), GetDevicePointer(), 1, other.GetDevicePointer(), 1, &local_dot); } else { - SU2_MPI::Error("Unsupported ScalarType in CSysVector::GPUDot.", CURRENT_FUNCTION); + SU2_MPI::Error("Unsupported ScalarType in CSysVector::dotGPU.", CURRENT_FUNCTION); return ScalarType(0); } if (status != CUBLAS_STATUS_SUCCESS) { - SU2_MPI::Error("cuBLAS dot failed in CSysVector::GPUDot.", CURRENT_FUNCTION); + SU2_MPI::Error("cuBLAS dot failed in CSysVector::dotGPU.", CURRENT_FUNCTION); return ScalarType(0); } @@ -111,10 +111,105 @@ ScalarType CSysVector::GPUDot(const CSysVector& other) const { return global_dot; } +/*! + * \brief multi vector product with cublasgemmBatched + */ template -ScalarType CSysVector::GPUNorm() const { - SU2_ZONE_SCOPED - return sqrt(GPUDot(*this)); +su2matrix CSysVector::multiDotGPU(const std::vector>& V, const size_t i0, + const size_t n, const std::vector>& W, + const size_t m) { + /*--- The multiDot product between n V[size] and m W[size] vectors is performed as + * a General Matrix Multiplication between two tall-skinny matrices: + * C = \alpha * A^T * B + \beta * C + * being A = V[ size * n ] and B = W[ size * m ] the batched vectors ---*/ + cublasHandle_t handle = GetBlasHandle(); + cublasStatus_t status = CUBLAS_STATUS_SUCCESS; + + const size_t size = V[0].nElmDomain; + const size_t batch = n * m; + + /*--- Persistent device workspace, cached across calls and freed automatically + * when the program exits (static local destruction), instead of leaking. ---*/ + struct Workspace { + ScalarType* d_local = nullptr; + const ScalarType** d_A = nullptr; + const ScalarType** d_B = nullptr; + ScalarType** d_C = nullptr; + size_t capacity = 0; + + void EnsureCapacity(size_t batch) { + if (batch <= capacity) return; + cudaFree(d_local); + cudaFree(d_A); + cudaFree(d_B); + cudaFree(d_C); + gpuErrChk(cudaMalloc(&d_local, batch * sizeof(ScalarType))); + gpuErrChk(cudaMalloc(&d_A, batch * sizeof(ScalarType*))); + gpuErrChk(cudaMalloc(&d_B, batch * sizeof(ScalarType*))); + gpuErrChk(cudaMalloc(&d_C, batch * sizeof(ScalarType*))); + capacity = batch; + } + + ~Workspace() { + cudaFree(d_local); + cudaFree(d_A); + cudaFree(d_B); + cudaFree(d_C); + } + }; + static Workspace ws; + + // allocate persistent result buffer local on host and device, is resized if needed + su2matrix local; + local.resize(n, m); + ws.EnsureCapacity(batch); + + // zero out the result buffer + gpuErrChk(cudaMemset(ws.d_local, 0, batch * sizeof(ScalarType))); + + // prepare the arrays A,B,C on host + static std::vector h_A, h_B; + static std::vector h_C; + h_A.resize(batch); h_B.resize(batch); h_C.resize(batch); + + for (size_t i = 0; i < n; ++i) { + for (size_t j =0; j < m; ++j) { + const size_t idx = i * m + j; + h_A[idx] = V[i0 + i].GetDevicePointer(); + h_B[idx] = W[j].GetDevicePointer(); + h_C[idx] = ws.d_local + idx; // C maps to d_local to store the coefficients in the 2D array + } + } + + // copy pointers to device + gpuErrChk(cudaMemcpy(ws.d_A, h_A.data(), batch * sizeof(ScalarType*), cudaMemcpyHostToDevice)); + gpuErrChk(cudaMemcpy(ws.d_B, h_B.data(), batch * sizeof(ScalarType*), cudaMemcpyHostToDevice)); + gpuErrChk(cudaMemcpy(ws.d_C, h_C.data(), batch * sizeof(ScalarType*), cudaMemcpyHostToDevice)); + + // define alpha = 1.0 and beta = 0.0 + const auto alpha = ScalarType(1.0); + const auto beta = ScalarType(0.0); + + if constexpr (std::is_same_v) { + status = cublasSgemmBatched(handle, CUBLAS_OP_T, CUBLAS_OP_N, 1, 1, size, &alpha, ws.d_A, static_cast(size), + ws.d_B, static_cast(size), &beta, ws.d_C, 1, static_cast(batch)); + } else if constexpr (std::is_same_v) { + status = cublasDgemmBatched(handle, CUBLAS_OP_T, CUBLAS_OP_N, 1, 1, size, &alpha, ws.d_A, static_cast(size), + ws.d_B, static_cast(size), &beta, ws.d_C, 1, static_cast(batch)); + } else { + SU2_MPI::Error("Unsupported ScalarType in CSysVector::multiDotGPU.", CURRENT_FUNCTION); + return local; + } + + if (status != CUBLAS_STATUS_SUCCESS) { + SU2_MPI::Error("cuBLAS cublasgemmBatched failed in CSysVector::multiDotGPU.", CURRENT_FUNCTION); + return local; + } + + // copy result to host for MPI reduce + gpuErrChk(cudaMemcpy(local.data(), ws.d_local, batch * sizeof(ScalarType), cudaMemcpyDeviceToHost)); + + return local; } /*--- Every expression the solvers assign to a CSysVector needs its assignment kernel @@ -209,14 +304,19 @@ DEVICE_EXPRESSION_SHAPES(passivedouble); #undef INSTANTIATE_DEVICE_ASSIGN_EXPR #undef INSTANTIATE_DEVICE_ASSIGN + template void CSysVector::HtDTransfer(bool trigger) const; template void CSysVector::DtHTransfer(bool trigger) const; -template su2mixedfloat CSysVector::GPUDot(const CSysVector& other) const; -template su2mixedfloat CSysVector::GPUNorm() const; +template su2mixedfloat CSysVector::dotGPU(const CSysVector& other) const; +template su2matrix CSysVector::multiDotGPU( + const std::vector>& V, size_t i0, size_t n, + const std::vector>& W, size_t m); #if defined(USE_MIXED_PRECISION) && !defined(USE_SINGLE_PRECISION) template void CSysVector::HtDTransfer(bool trigger) const; template void CSysVector::DtHTransfer(bool trigger) const; -template passivedouble CSysVector::GPUDot(const CSysVector& other) const; -template passivedouble CSysVector::GPUNorm() const; +template passivedouble CSysVector::dotGPU(const CSysVector& other) const; +template su2matrix CSysVector::multiDotGPU( + const std::vector>& V, size_t i0, size_t n, + const std::vector>& W, size_t m); #endif From 790073a7dd9e0853b3215609b07ae03593688811 Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Sun, 16 Aug 2026 11:38:23 -0700 Subject: [PATCH 30/61] Quantized versions of Jacobi and Identity preconditioners (#2869) ## Proposed Changes Also compatible with GPU ## PR Checklist - [X] I am submitting my contribution to the develop branch. - [X] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [X] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [X] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [ ] I have added a test case that demonstrates my contribution, if necessary. - [X] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --------- Co-authored-by: Claude Sonnet 5 --- Common/include/code_config.hpp | 7 + .../include/linear_algebra/CMatrixInverse.hpp | 8 +- .../linear_algebra/CPreconditioner.hpp | 56 ++- Common/include/linear_algebra/CSysMatrix.hpp | 198 ++++++++--- Common/include/linear_algebra/CSysMatrix.inl | 16 +- Common/include/linear_algebra/CSysVector.hpp | 8 +- .../linear_algebra/vector_expressions.hpp | 8 +- Common/include/option_structure.hpp | 7 + .../include/toolboxes/allocation_toolbox.hpp | 35 ++ Common/src/CConfig.cpp | 2 + Common/src/linear_algebra/CSysMatrix.cpp | 153 +++++--- Common/src/linear_algebra/CSysMatrixGPU.cu | 299 +++++++++++++--- Common/src/linear_algebra/CSysSolve.cpp | 2 + Common/src/linear_algebra/CSysVectorGPU.cu | 326 ++++++++++++------ .../include/solvers/CFVMFlowSolverBase.inl | 6 +- config_template.cfg | 12 +- meson.build | 7 +- 17 files changed, 817 insertions(+), 333 deletions(-) diff --git a/Common/include/code_config.hpp b/Common/include/code_config.hpp index 41d3c747cf86..a28c346b651b 100644 --- a/Common/include/code_config.hpp +++ b/Common/include/code_config.hpp @@ -50,6 +50,13 @@ #define NEVERINLINE inline #endif +/*--- Marks a function callable from both host and device code, a no-op outside nvcc. ---*/ +#ifdef __CUDACC__ +#define SU2_CUDA_HOST_DEVICE __host__ __device__ +#else +#define SU2_CUDA_HOST_DEVICE +#endif + #if defined(__INTEL_COMPILER) /*--- Disable warnings related to inline attributes. ---*/ #pragma warning disable 2196 diff --git a/Common/include/linear_algebra/CMatrixInverse.hpp b/Common/include/linear_algebra/CMatrixInverse.hpp index f3d64630349d..6d6dfa739595 100644 --- a/Common/include/linear_algebra/CMatrixInverse.hpp +++ b/Common/include/linear_algebra/CMatrixInverse.hpp @@ -30,11 +30,7 @@ #include -#ifdef __CUDACC__ -#define SU2_CUDA_HOST_DEVICE __host__ __device__ -#else -#define SU2_CUDA_HOST_DEVICE -#endif +#include "../code_config.hpp" namespace SU2_LinAlg { @@ -95,5 +91,3 @@ SU2_CUDA_HOST_DEVICE inline void MatrixInverse(unsigned long nVar, ScalarType* m } } // namespace SU2_LinAlg - -#undef SU2_CUDA_HOST_DEVICE diff --git a/Common/include/linear_algebra/CPreconditioner.hpp b/Common/include/linear_algebra/CPreconditioner.hpp index e23f5de381f2..2b6c67701ff6 100644 --- a/Common/include/linear_algebra/CPreconditioner.hpp +++ b/Common/include/linear_algebra/CPreconditioner.hpp @@ -107,13 +107,25 @@ CPreconditioner::~CPreconditioner() {} /*! * \class CIdentityPreconditioner * \brief No-op preconditioner used when Krylov solvers run without preconditioning. + * \note Also serves Q_IDENTITY: Build() requests quantization of the diagonal blocks, needed by + * the matrix-vector product shared with the Krylov solver even though this preconditioner's own + * operation is a no-op. CSysMatrix::QuantizeDiagonalBlocks() when quantization is off. */ template class CIdentityPreconditioner final : public CPreconditioner { + private: + CSysMatrix& sparse_matrix; + public: + inline explicit CIdentityPreconditioner(CSysMatrix& matrix_ref) : sparse_matrix(matrix_ref) {} + + CIdentityPreconditioner() = delete; + inline void operator()(const CSysVector& u, CSysVector& v) const override { v = u; } inline bool IsIdentity() const override { return true; } + + inline void Build() override { sparse_matrix.QuantizeDiagonalBlocks(); } }; /*! @@ -157,7 +169,9 @@ class CJacobiPreconditioner final : public CPreconditioner { } /*! - * \note Request the associated matrix to build the preconditioner. + * \note Request the associated matrix to build the preconditioner. Also serves Q_JACOBI: + * BuildJacobiPreconditioner() quantizes the diagonal blocks itself when the matrix was + * set up for it. */ inline void Build() override { sparse_matrix.BuildJacobiPreconditioner(); } }; @@ -248,36 +262,10 @@ class CLU_SGSPreconditioner final : public CPreconditioner { inline void operator()(const CSysVector& u, CSysVector& v) const override { ApplyPreconditionerOnHost(u, v, [&] { sparse_matrix.ComputeLU_SGSPreconditioner(u, v, geometry, config); }); } -}; - -/*! - * \class CQuantizedLUSGSPreconditioner - * \brief Specialization of preconditioner that uses CSysMatrix class. - */ -template -class CQuantizedLUSGSPreconditioner final : public CPreconditioner { - private: - CSysMatrix& sparse_matrix; - CGeometry* geometry; - const CConfig* config; - - public: - inline CQuantizedLUSGSPreconditioner(CSysMatrix& matrix_ref, CGeometry* geometry_ref, - const CConfig* config_ref) - : sparse_matrix(matrix_ref) { - if ((geometry_ref == nullptr) || (config_ref == nullptr)) - SU2_MPI::Error("Preconditioner needs to be built with valid references.", CURRENT_FUNCTION); - geometry = geometry_ref; - config = config_ref; - } - - CQuantizedLUSGSPreconditioner() = delete; - - inline void operator()(const CSysVector& u, CSysVector& v) const override { - ApplyPreconditionerOnHost(u, v, [&] { sparse_matrix.ComputeLU_SGSPreconditioner(u, v, geometry, config); }); - } - /*! \brief Quantize the diagonal blocks (off diagonals are quantized on the fly). */ + /*! + * \note Also serves Q_LU_SGS: quantizes the diagonal blocks, no-op for plain LU_SGS. + */ inline void Build() override { sparse_matrix.QuantizeDiagonalBlocks(); } }; @@ -404,19 +392,19 @@ CPreconditioner* CPreconditioner::Create(ENUM_LINEAR_SOL switch (kind) { case IDENTITY: - prec = new CIdentityPreconditioner(); + case Q_IDENTITY: + prec = new CIdentityPreconditioner(jacobian); break; case JACOBI: + case Q_JACOBI: prec = new CJacobiPreconditioner(jacobian, geometry, config); break; case LINELET: prec = new CLineletPreconditioner(jacobian, geometry, config); break; case LU_SGS: - prec = new CLU_SGSPreconditioner(jacobian, geometry, config); - break; case Q_LU_SGS: - prec = new CQuantizedLUSGSPreconditioner(jacobian, geometry, config); + prec = new CLU_SGSPreconditioner(jacobian, geometry, config); break; case ILU: prec = new CILUPreconditioner(jacobian, geometry, config); diff --git a/Common/include/linear_algebra/CSysMatrix.hpp b/Common/include/linear_algebra/CSysMatrix.hpp index 38f3c9022145..ccd9cae78779 100644 --- a/Common/include/linear_algebra/CSysMatrix.hpp +++ b/Common/include/linear_algebra/CSysMatrix.hpp @@ -29,6 +29,7 @@ #pragma once #include "../CConfig.hpp" +#include "../code_config.hpp" #include "CSysVector.hpp" #include "CPastixWrapper.hpp" #include "../toolboxes/graph_toolbox.hpp" @@ -109,48 +110,103 @@ struct CSysMatrixComms { MPI_QUANTITIES commType = MPI_QUANTITIES::SOLUTION_MATRIX); }; +/*! + * \brief std::max/std::min, usable from both host and device code. Calling std::max/std::min + * directly from a SU2_CUDA_HOST_DEVICE function compiles without error but is not + * actually valid without --expt-relaxed-constexpr which is not used in this build. + */ +template +SU2_CUDA_HOST_DEVICE inline T QuantMax(T a, T b) noexcept { +#ifdef __CUDA_ARCH__ + return max(a, b); +#else + return std::max(a, b); +#endif +} +template +SU2_CUDA_HOST_DEVICE inline T QuantMin(T a, T b) noexcept { +#ifdef __CUDA_ARCH__ + return min(a, b); +#else + return std::min(a, b); +#endif +} + /*! * \brief Reconstruct the float row-scale from a stored int8 binary exponent. * The exponent \p e was packed as (e + 127) into the IEEE 754 biased-exponent field * with a zero mantissa, giving an exact power of two: 2^e. * This is the inverse of the encoding in EncodeQuantBlock. + * \note Branches on __CUDA_ARCH__, plain memcpy compiles for the device but does not work! */ -FORCEINLINE float DecodeQuantScale(int8_t e) noexcept { - const uint32_t bits = static_cast(std::max(0, static_cast(e) + 127)) << 23; +SU2_CUDA_HOST_DEVICE inline float DecodeQuantScale(int8_t e) noexcept { + const uint32_t bits = static_cast(QuantMax(0, static_cast(e) + 127)) << 23; +#ifdef __CUDA_ARCH__ + return __uint_as_float(bits); +#else float scale; memcpy(&scale, &bits, sizeof(bits)); return scale; +#endif } /*! - * \brief Encode one nVar×nVar block into per-row int8 quantized storage. + * \brief Encode one row of an nVar×nVar block into int8 quantized storage: \p qs receives the + * row's scale exponent, \p qv (nVar entries) the clamped int8 values for row \p r. * \p f(r,c) is called twice per entry (max-abs scan then encoding); it should be cheap. - * Stores a per-row scale exponent in \p qs and clamped int8 values in \p qv. + * \note Shared with the device and thus same __CUDA_ARCH__ branches as DecodeQuantScale. \p f's + * return type is cast to float directly on device (only ever instantiated there for plain + * ScalarType, never AD-active); on host it goes through SU2_TYPE::PassiveValue first, since + * ScalarType can be AD-active there (quantized_mode is only compiled out for reverse-mode + * AD, not forward-mode, see quantized_offdiag_needed in CSysMatrix.cpp) and PassiveValue is + * host-only (not SU2_CUDA_HOST_DEVICE). */ template -FORCEINLINE void EncodeQuantBlock(const F& f, int8_t* __restrict qs, int8_t* __restrict qv, - unsigned long nVar) noexcept { - for (auto r = 0ul; r < nVar; ++r) { - constexpr uint32_t eps_bits = 0x34000000u; - uint32_t max_abs_bits = eps_bits; - for (auto c = 0ul; c < nVar; ++c) { - const float fv = SU2_TYPE::PassiveValue(f(r, c)); - uint32_t fb; - memcpy(&fb, &fv, sizeof(fb)); - max_abs_bits = std::max(max_abs_bits, fb & 0x7FFFFFFFu); - } - const int e = std::min(127, std::max(-128, static_cast(max_abs_bits >> 23) - 133)); - qs[r] = static_cast(e); - const uint32_t inv_bits = static_cast(127 - e) << 23; - float inv_rscale; - memcpy(&inv_rscale, &inv_bits, sizeof(inv_rscale)); - for (auto c = 0ul; c < nVar; ++c) { - qv[r * nVar + c] = - static_cast(std::max(-128.f, std::min(127.f, roundf(SU2_TYPE::PassiveValue(f(r, c)) * inv_rscale)))); - } +SU2_CUDA_HOST_DEVICE inline void EncodeQuantRow(const F& f, int8_t& qs, int8_t* __restrict qv, unsigned long nVar, + unsigned long r) noexcept { +#ifdef __CUDA_ARCH__ + auto passive = [&](unsigned long row, unsigned long col) { return f(row, col); }; +#else + auto passive = [&](unsigned long row, unsigned long col) { return SU2_TYPE::PassiveValue(f(row, col)); }; +#endif + constexpr uint32_t eps_bits = 0x34000000u; + uint32_t max_abs_bits = eps_bits; + for (auto c = 0ul; c < nVar; ++c) { + const float fv = static_cast(passive(r, c)); +#ifdef __CUDA_ARCH__ + const uint32_t fb = __float_as_uint(fv); +#else + uint32_t fb; + memcpy(&fb, &fv, sizeof(fb)); +#endif + max_abs_bits = QuantMax(max_abs_bits, fb & 0x7FFFFFFFu); + } + const int e = QuantMin(127, QuantMax(-128, static_cast(max_abs_bits >> 23) - 133)); + qs = static_cast(e); + const uint32_t inv_bits = static_cast(127 - e) << 23; +#ifdef __CUDA_ARCH__ + const float inv_rscale = __uint_as_float(inv_bits); +#else + float inv_rscale; + memcpy(&inv_rscale, &inv_bits, sizeof(inv_rscale)); +#endif + for (auto c = 0ul; c < nVar; ++c) { + qv[c] = + static_cast(QuantMax(-128.f, QuantMin(127.f, roundf(static_cast(passive(r, c)) * inv_rscale)))); } } +/*! + * \brief Encode one nVar×nVar block into per-row int8 quantized storage, see EncodeQuantRow (each + * row's scale/quantization is independent, this just loops over all of them serially for + * the host path). + */ +template +SU2_CUDA_HOST_DEVICE inline void EncodeQuantBlock(const F& f, int8_t* __restrict qs, int8_t* __restrict qv, + unsigned long nVar) noexcept { + for (auto r = 0ul; r < nVar; ++r) EncodeQuantRow(f, qs[r], qv + r * nVar, nVar, r); +} + /*! * \brief View of one matrix block, const-correct via the ScalarType template parameter. * \c CBlockView is read-only; \c CBlockView is mutable @@ -231,14 +287,19 @@ class CSysMatrix { /*! * \brief Aggregates value arrays and sparse-structure pointers for an LDU-partitioned matrix. - * Each CSysMatrix holds three LDU instances: the host matrix (mat), its device copy (gpu), - * and the ILU factorization (ilu). Ownership of the value arrays (d/l/u) and whether - * the pointers address host or device memory is managed by CSysMatrix. + * Each CSysMatrix holds three LDU instances: the host matrix (mat), its + * device copy (gpu), and the ILU factorization (ilu). Ownership of the value arrays + * (d/l/u) and whether the pointers address host or device memory is managed by + * CSysMatrix. Also reused with T = QuantType to group the quantized scale/blocks + * storage (q_scale, q_blocks, d_q_scale, d_q_blocks) the same way; for those the pattern + * fields (row_ptr_l, col_ind_l, row_ptr_u, col_ind_u, nnz_l, nnz_u) are simply left + * unused, since the sparsity pattern is already available from mat/gpu. */ + template struct LDU { - ScalarType* d = nullptr; /*!< \brief Diagonal block values. */ - ScalarType* l = nullptr; /*!< \brief Strictly-lower block values. */ - ScalarType* u = nullptr; /*!< \brief Strictly-upper block values. */ + T* d = nullptr; /*!< \brief Diagonal block values. */ + T* l = nullptr; /*!< \brief Strictly-lower block values. */ + T* u = nullptr; /*!< \brief Strictly-upper block values. */ const su2uint* row_ptr_l = nullptr; /*!< \brief Row pointers for L (geometry-owned or GPU copy). */ const su2uint* col_ind_l = nullptr; /*!< \brief Column indices for L. */ const su2uint* row_ptr_u = nullptr; /*!< \brief Row pointers for U. */ @@ -247,30 +308,37 @@ class CSysMatrix { unsigned long nnz_u = 0; /*!< \brief Number of U nonzeros. */ }; - LDU mat; /*!< \brief Host matrix (values owned via aligned_alloc; pattern from geometry). */ - LDU gpu; /*!< \brief Device matrix (all pointers to GPU memory). */ - LDU ilu; /*!< \brief ILU factorization, host (values owned; pattern from geometry). */ - LDU gpu_ilu; /*!< \brief ILU factorization, device (values and pattern in GPU memory). */ + LDU mat; /*!< \brief Host matrix (values owned via aligned_alloc; pattern from geometry). */ + LDU gpu; /*!< \brief Device matrix (all pointers to GPU memory). */ + LDU ilu; /*!< \brief ILU factorization, host (values owned; pattern from geometry). */ + LDU gpu_ilu; /*!< \brief ILU factorization, device (values and pattern in GPU memory). */ ScalarType* d_invM = nullptr; /*!< \brief Device inverse diagonal blocks for the Jacobi preconditioner. */ /*--- Quantized off-diagonal storage (used when quantized_mode == true). ---*/ using QuantType = int8_t; - /*! \brief Set by Initialize() when preconditioner == Q_LU_SGS. - * mat.l and mat.u are NOT allocated; off-diagonal blocks live in the - * q_* arrays below. */ + /*! \brief Set by Initialize() when preconditioner == Q_LU_SGS, Q_JACOBI or Q_IDENTITY. + * mat.l and mat.u are NOT allocated; off-diagonal blocks live in q_scale/q_blocks + * below. Only the matrix-vector product (used by the Krylov solver and, for Q_LU_SGS, + * by the sweeps) reads the quantized blocks; the Jacobi preconditioner never touches + * them since it only applies the (full precision) inverse diagonal, and the identity + * preconditioner does not touch the matrix at all. */ #ifndef CODI_REVERSE_TYPE bool quantized_mode = false; #else static constexpr bool quantized_mode = false; #endif - QuantType* q_scale_l; /*!< \brief Per-row exponent for L blocks, [nnz_l * nVar]. */ - QuantType* q_blocks_l; /*!< \brief Quantized L block entries, [nnz_l * nVar * nEqn]. */ - QuantType* q_scale_u; /*!< \brief Same as q_scale_l for the upper entries. */ - QuantType* q_blocks_u; /*!< \brief Same as q_blocks_l for the upper entries. */ - QuantType* q_scale_d; /*!< \brief Same as q_scale_l for the diagonal entries, [nPoint * nVar]. - * Populated by QuantizeDiagonalBlocks(). */ - QuantType* q_blocks_d; /*!< \brief Same as q_blocks_l for the diagonal entries. */ + /*!< \brief Per-row exponents; .l/.u sized [nnz_l/u * nVar], .d [nPoint * nVar]. .l/.u are + * populated during assembly (quantized on the fly); .d is populated by + * QuantizeDiagonalBlocks(). .l/.u are pinned (cudaMallocHost) rather than + * aligned_alloc when useCuda, so HtDTransfer()'s async uploads them. */ + LDU q_scale; + /*!< \brief Quantized block entries; .l/.u sized [nnz_l/u * nVar * nEqn], .d [nPoint * nVar * nEqn]. */ + LDU q_blocks; + + /*!< \brief Device mirrors of the quantized storage, only allocated when quantized_mode. */ + LDU d_q_scale; + LDU d_q_blocks; bool useCuda = false; /*!< \brief Whether CUDA is enabled. */ @@ -324,8 +392,18 @@ class CSysMatrix { * was captured with, to detect when * it must be recaptured. */ mutable ScalarType* ilu_apply_graph_prod = nullptr; - /*--- The legacy default stream cannot be captured into a graph. ---*/ - mutable struct CUstream_st* ilu_stream = nullptr; + /*--- Non-default stream, needed for two mutually exclusive uses that never overlap on a given + * matrix (quantized_mode and ILU are alternative preconditioner choices, decided once in + * Initialize()): (1) the ILU build/apply CUDA graphs below, since the legacy default stream + * cannot be captured into a graph; (2) HtDTransfer's async H2D transfer of the quantized L/U + * blocks, so that transfer can run concurrently (copy engine) with kernels issued on the + * default stream (e.g. QuantizeDiagonalBlocksGPU, on the SM) instead of queueing behind them on + * the same stream. Because the two uses are mutually exclusive, sharing one stream (rather than + * a dedicated one per use) needs no extra synchronization between them. htd_event marks the end + * of the H2D transfer specifically, so the default-stream kernel that first reads the result + * (the quantized SpMV) can wait on it without a host-side block. ---*/ + mutable struct CUstream_st* aux_stream = nullptr; + mutable struct CUevent_st* htd_event = nullptr; ScalarType* invM; /*!< \brief Inverse of (Jacobi) preconditioner. */ @@ -542,7 +620,7 @@ class CSysMatrix { /*! \brief Diagonal product using quantized D (Q_LU_SGS backward sweep). */ inline void QuantizedDiagonalProduct(const CSysVector& vec, unsigned long row_i, ScalarType* prod) const; - /*! \brief Gauss elimination on the quantized diagonal block: decodes q_blocks_d into a local + /*! \brief Gauss elimination on the quantized diagonal block: decodes q_blocks.d into a local * ScalarType buffer and delegates to the scalar GaussElimination overload. */ inline void QuantizedGaussElimination(unsigned long block_i, ScalarType* rhs) const; @@ -554,6 +632,12 @@ class CSysMatrix { void MatrixVectorProductGPU(const CSysVector& vec, CSysVector& prod, CGeometry* geometry, const CConfig* config) const; + /*! + * \brief Quantize the diagonal blocks directly on the device. + * \note Requires the device matrix to be up to date, see HtDTransfer. + */ + void QuantizeDiagonalBlocksGPU(); + /*! * \brief Build the Jacobi preconditioner on the device, from the device copy of the matrix. * \note Requires the device matrix to be up to date, see HtDTransfer. @@ -667,10 +751,10 @@ class CSysMatrix { } \ if (block_j < block_i) { \ for (auto k = mat.row_ptr_l[block_i]; k < mat.row_ptr_l[block_i + 1]; ++k) \ - if (mat.col_ind_l[k] == block_j) return {nullptr, &q_scale_l[k * nVar], &q_blocks_l[k * nVar * nVar], nVar}; \ + if (mat.col_ind_l[k] == block_j) return {nullptr, &q_scale.l[k * nVar], &q_blocks.l[k * nVar * nVar], nVar}; \ } else { \ for (auto k = mat.row_ptr_u[block_i]; k < mat.row_ptr_u[block_i + 1]; ++k) \ - if (mat.col_ind_u[k] == block_j) return {nullptr, &q_scale_u[k * nVar], &q_blocks_u[k * nVar * nVar], nVar}; \ + if (mat.col_ind_u[k] == block_j) return {nullptr, &q_scale.u[k * nVar], &q_blocks.u[k * nVar * nVar], nVar}; \ } \ return {} GET_BLOCK_VIEW_IMPL; @@ -797,9 +881,9 @@ class CSysMatrix { bij_buf[offset] = PassiveAssign(block_j[iVar][jVar] * scale); bji_buf[offset] = -PassiveAssign(block_i[iVar][jVar] * scale); } - QuantizeBlock(bij_buf, &q_scale_u[iEdge * nVar], &q_blocks_u[iEdge * blkSz]); + QuantizeBlock(bij_buf, &q_scale.u[iEdge * nVar], &q_blocks.u[iEdge * blkSz]); const auto k_l = edge_ptr_l[iEdge]; - QuantizeBlock(bji_buf, &q_scale_l[k_l * nVar], &q_blocks_l[k_l * blkSz]); + QuantizeBlock(bji_buf, &q_scale.l[k_l * nVar], &q_blocks.l[k_l * blkSz]); return; } @@ -867,9 +951,9 @@ class CSysMatrix { bii[i] -= blk_i[k][i]; bjj[i] -= blk_j[k][i]; } - QuantizeBlock(blk_j[k], &q_scale_u[iEdge[k] * nVar], &q_blocks_u[iEdge[k] * blkSz]); + QuantizeBlock(blk_j[k], &q_scale.u[iEdge[k] * nVar], &q_blocks.u[iEdge[k] * blkSz]); const auto k_l = edge_ptr_l[iEdge[k]]; - QuantizeBlock(blk_i[k], &q_scale_l[k_l * nVar], &q_blocks_l[k_l * blkSz]); + QuantizeBlock(blk_i[k], &q_scale.l[k_l * nVar], &q_blocks.l[k_l * blkSz]); } else { auto bij = &mat.u[iEdge[k] * blkSz]; auto bji = &mat.l[edge_ptr_l[iEdge[k]] * blkSz]; @@ -910,9 +994,9 @@ class CSysMatrix { bij_buf[offset] = PassiveAssign(block_j[iVar][jVar] * scale); bji_buf[offset] = -PassiveAssign(block_i[iVar][jVar] * scale); } - QuantizeBlock(bij_buf, &q_scale_u[iEdge * nVar], &q_blocks_u[iEdge * blkSz]); + QuantizeBlock(bij_buf, &q_scale.u[iEdge * nVar], &q_blocks.u[iEdge * blkSz]); const auto k_l = edge_ptr_l[iEdge]; - QuantizeBlock(bji_buf, &q_scale_l[k_l * nVar], &q_blocks_l[k_l * blkSz]); + QuantizeBlock(bji_buf, &q_scale.l[k_l * nVar], &q_blocks.l[k_l * blkSz]); return; } @@ -973,9 +1057,9 @@ class CSysMatrix { if (mask[k] == 0) continue; if (quantized_mode) { - QuantizeBlock(blk_j[k], &q_scale_u[iEdge[k] * nVar], &q_blocks_u[iEdge[k] * blkSz]); + QuantizeBlock(blk_j[k], &q_scale.u[iEdge[k] * nVar], &q_blocks.u[iEdge[k] * blkSz]); const auto k_l = edge_ptr_l[iEdge[k]]; - QuantizeBlock(blk_i[k], &q_scale_l[k_l * nVar], &q_blocks_l[k_l * blkSz]); + QuantizeBlock(blk_i[k], &q_scale.l[k_l * nVar], &q_blocks.l[k_l * blkSz]); } else { ScalarType* bij = &mat.u[iEdge[k] * blkSz]; ScalarType* bji = &mat.l[edge_ptr_l[iEdge[k]] * blkSz]; diff --git a/Common/include/linear_algebra/CSysMatrix.inl b/Common/include/linear_algebra/CSysMatrix.inl index b5411016af67..c0ac196c9610 100644 --- a/Common/include/linear_algebra/CSysMatrix.inl +++ b/Common/include/linear_algebra/CSysMatrix.inl @@ -147,8 +147,8 @@ FORCEINLINE void CSysMatrix::GaussElimination(unsigned long block_i, template FORCEINLINE void CSysMatrix::QuantizedGaussElimination(unsigned long block_i, ScalarType* rhs) const { ScalarType block[MAXNVAR * MAXNVAR]; - const QuantType* __restrict qs = &q_scale_d[block_i * nVar]; - const QuantType* __restrict qv = &q_blocks_d[block_i * nVar * nVar]; + const QuantType* __restrict qs = &q_scale.d[block_i * nVar]; + const QuantType* __restrict qv = &q_blocks.d[block_i * nVar * nVar]; for (auto r = 0ul; r < nVar; ++r) { const float row_scale = DecodeQuantScale(qs[r]); for (auto c = 0ul; c < nVar; ++c) block[r * nVar + c] = static_cast(qv[r * nVar + c] * row_scale); @@ -242,12 +242,12 @@ FORCEINLINE void CSysMatrix::QuantizedRowProduct(const CSysVector::QuantizedUpperProduct(const CSysVector< for (auto index = mat.row_ptr_u[row_i]; index < mat.row_ptr_u[row_i + 1]; index++) { auto col_j = mat.col_ind_u[index]; if (col_j < col_ub || col_j >= nPointDomain) { - QuantizedMatVecAdd(&q_scale_u[index * nVar], &q_blocks_u[index * nVar * nEqn], &vec[col_j * nEqn], prod); + QuantizedMatVecAdd(&q_scale.u[index * nVar], &q_blocks.u[index * nVar * nEqn], &vec[col_j * nEqn], prod); } } } @@ -272,7 +272,7 @@ FORCEINLINE void CSysMatrix::QuantizedLowerProduct(const CSysVector< for (auto index = mat.row_ptr_l[row_i]; index < mat.row_ptr_l[row_i + 1]; index++) { auto col_j = mat.col_ind_l[index]; if (col_j >= col_lb) { - QuantizedMatVecAdd(&q_scale_l[index * nVar], &q_blocks_l[index * nVar * nEqn], &vec[col_j * nEqn], prod); + QuantizedMatVecAdd(&q_scale.l[index * nVar], &q_blocks.l[index * nVar * nEqn], &vec[col_j * nEqn], prod); } } } @@ -281,5 +281,5 @@ template FORCEINLINE void CSysMatrix::QuantizedDiagonalProduct(const CSysVector& vec, unsigned long row_i, ScalarType* prod) const { for (auto iVar = 0ul; iVar < nVar; iVar++) prod[iVar] = 0.0; - QuantizedMatVecAdd(&q_scale_d[row_i * nVar], &q_blocks_d[row_i * nVar * nEqn], &vec[row_i * nEqn], prod); + QuantizedMatVecAdd(&q_scale.d[row_i * nVar], &q_blocks.d[row_i * nVar * nEqn], &vec[row_i * nEqn], prod); } diff --git a/Common/include/linear_algebra/CSysVector.hpp b/Common/include/linear_algebra/CSysVector.hpp index 1b57a6ffe945..28c750049580 100644 --- a/Common/include/linear_algebra/CSysVector.hpp +++ b/Common/include/linear_algebra/CSysVector.hpp @@ -31,6 +31,7 @@ #include #include +#include "../code_config.hpp" #include "../parallelization/mpi_structure.hpp" #include "../parallelization/omp_structure.hpp" #include "../parallelization/vectorization.hpp" @@ -39,9 +40,6 @@ #ifdef __CUDACC__ #include "GPUComms.cuh" -#define SU2_CUDA_HOST_DEVICE __host__ __device__ -#else -#define SU2_CUDA_HOST_DEVICE #endif template @@ -530,7 +528,8 @@ class CSysVector : public VecExpr::CVecExpr, ScalarType> if constexpr (su2_gpu_capable_v) { using DeviceExpr = std::remove_cv_t>; static_assert(std::is_same_v, - "On the device the dot product is a cuBLAS call, so it only takes vectors. " + "On the device the dot product needs a real device pointer (dotGPU takes a " + "materialized vector, not an expression template), so it only takes vectors. " "Assign the expression to a vector first."); if (VecExpr::UseDeviceExpressions()) { /*--- dotGPU reduces over MPI, which has to happen once for the team, so the result @@ -709,4 +708,3 @@ CVectorView::CVectorView(const CSysVector& vector) #undef CSYSVEC_PARFOR #undef END_CSYSVEC_PARFOR -#undef SU2_CUDA_HOST_DEVICE diff --git a/Common/include/linear_algebra/vector_expressions.hpp b/Common/include/linear_algebra/vector_expressions.hpp index f9b941a0bdb2..d59dfdf51ae8 100644 --- a/Common/include/linear_algebra/vector_expressions.hpp +++ b/Common/include/linear_algebra/vector_expressions.hpp @@ -28,6 +28,7 @@ #pragma once #include "../basic_types/datatype_structure.hpp" +#include "../code_config.hpp" #include #include #include @@ -39,12 +40,6 @@ namespace VecExpr { /// \addtogroup VecExpr /// @{ -#ifdef __CUDACC__ -#define SU2_CUDA_HOST_DEVICE __host__ __device__ -#else -#define SU2_CUDA_HOST_DEVICE -#endif - /*! * \brief Base vector expression class. * \ingroup BLAS @@ -251,5 +246,4 @@ MAKE_BINARY_FUN(operator>, gt_, gt_impl) #undef MAKE_BINARY_FUN /// @} -#undef SU2_CUDA_HOST_DEVICE } // namespace VecExpr diff --git a/Common/include/option_structure.hpp b/Common/include/option_structure.hpp index 2dcd241f9ded..c6bddc15aa6e 100644 --- a/Common/include/option_structure.hpp +++ b/Common/include/option_structure.hpp @@ -2529,17 +2529,24 @@ enum ENUM_LINEAR_SOLVER_PREC { LINELET, /*!< \brief Line implicit preconditioner. */ ILU, /*!< \brief ILU(k) preconditioner. */ Q_LU_SGS, /*!< \brief LU-SGS with quantized (int8) off-diagonal storage; L/U are never allocated as ScalarType. */ + Q_JACOBI, /*!< \brief Jacobi with quantized (int8) off-diagonal storage; same matvec quantization as Q_LU_SGS, + the diagonal inverse is still computed and applied at full precision. */ + Q_IDENTITY, /*!< \brief No preconditioner, but the matrix-vector product still uses quantized (int8) + off-diagonal storage, same matvec quantization as Q_LU_SGS/Q_JACOBI. */ PASTIX_ILU=10, /*!< \brief PaStiX ILU(k) preconditioner. */ PASTIX_LU_P, /*!< \brief PaStiX LU as preconditioner. */ PASTIX_LDLT_P, /*!< \brief PaStiX LDLT as preconditioner. */ }; static const MapType Linear_Solver_Prec_Map = { MakePair("NONE", IDENTITY) + MakePair("IDENTITY", IDENTITY) MakePair("JACOBI", JACOBI) MakePair("LU_SGS", LU_SGS) MakePair("LINELET", LINELET) MakePair("ILU", ILU) MakePair("Q_LU_SGS", Q_LU_SGS) + MakePair("Q_JACOBI", Q_JACOBI) + MakePair("Q_IDENTITY", Q_IDENTITY) MakePair("PASTIX_ILU", PASTIX_ILU) MakePair("PASTIX_LU", PASTIX_LU_P) MakePair("PASTIX_LDLT", PASTIX_LDLT_P) diff --git a/Common/include/toolboxes/allocation_toolbox.hpp b/Common/include/toolboxes/allocation_toolbox.hpp index 9c357405b757..66db0a04035a 100644 --- a/Common/include/toolboxes/allocation_toolbox.hpp +++ b/Common/include/toolboxes/allocation_toolbox.hpp @@ -144,4 +144,39 @@ inline T* gpu_alloc_cpy(const T* src_ptr, size_t size) noexcept { return static_cast(ptr); } + +/*! + * \brief Page-locked ("pinned") host memory allocation. + * \note Unlike regular (pageable) host memory, cudaMemcpyAsync from/to a pinned buffer is + * actually asynchronous with respect to the host thread; from pageable memory the driver + * silently falls back to a synchronous staged copy. Only worth it for host buffers that + * are the source/destination of an async transfer meant to overlap with other host work. + * \param[in] size in bytes. + * \tparam ZeroInit, initialize memory to 0. + * \return Pointer to memory, always use pinned_free to deallocate. + */ +template +inline T* pinned_alloc(size_t size) noexcept { + void* ptr = nullptr; + +#if defined(HAVE_CUDA) + gpuErrChk(cudaMallocHost((void**)(&ptr), size)); + if (ZeroInit) memset(ptr, 0, size); +#else + return 0; +#endif + + return static_cast(ptr); +} + +/*! + * \brief Free memory allocated with pinned_alloc. + * \param[in] ptr, pointer to memory we want to release. + */ +template +inline void pinned_free(T* ptr) noexcept { +#ifdef HAVE_CUDA + gpuErrChk(cudaFreeHost((void*)ptr)); +#endif +} } // namespace GPUMemoryAllocation diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index 3fe4c98bbe98..c2633f84ec4b 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -7481,6 +7481,7 @@ void CConfig::SetOutput(SU2_COMPONENT val_software, unsigned short val_izone) { case LU_SGS: cout << "Using LU-SGS preconditioning."<< endl; break; case Q_LU_SGS: cout << "Using LU-SGS preconditioning with matrix quantization."<< endl; break; case JACOBI: cout << "Using Jacobi preconditioning."<< endl; break; + case Q_JACOBI: cout << "Using Jacobi preconditioning with matrix quantization."<< endl; break; } break; case SMOOTHER: @@ -7490,6 +7491,7 @@ void CConfig::SetOutput(SU2_COMPONENT val_software, unsigned short val_izone) { case LU_SGS: cout << "A LU-SGS"; break; case Q_LU_SGS: cout << "A quantized LU-SGS"; break; case JACOBI: cout << "A Jacobi"; break; + case Q_JACOBI: cout << "A quantized Jacobi"; break; } cout << " method is used for smoothing the linear system." << endl; break; diff --git a/Common/src/linear_algebra/CSysMatrix.cpp b/Common/src/linear_algebra/CSysMatrix.cpp index c9668aa0e88e..7bee9a093470 100644 --- a/Common/src/linear_algebra/CSysMatrix.cpp +++ b/Common/src/linear_algebra/CSysMatrix.cpp @@ -99,12 +99,8 @@ CSysMatrix::CSysMatrix() : rank(SU2_MPI::GetRank()), size(SU2_MPI::G ilu.d = nullptr; ilu.u = nullptr; - q_scale_l = nullptr; - q_blocks_l = nullptr; - q_scale_u = nullptr; - q_blocks_u = nullptr; - q_scale_d = nullptr; - q_blocks_d = nullptr; + q_scale = {}; + q_blocks = {}; invM = nullptr; d_invM = nullptr; @@ -122,7 +118,7 @@ CSysMatrix::~CSysMatrix() { SU2_ZONE_SCOPED delete[] omp_partitions; - auto freeHostLDU = [](LDU& m) { + auto freeHostLDU = [](auto& m) { MemoryAllocation::aligned_free(m.d); MemoryAllocation::aligned_free(m.l); MemoryAllocation::aligned_free(m.u); @@ -130,15 +126,25 @@ CSysMatrix::~CSysMatrix() { freeHostLDU(mat); freeHostLDU(ilu); MemoryAllocation::aligned_free(invM); - MemoryAllocation::aligned_free(q_scale_l); - MemoryAllocation::aligned_free(q_blocks_l); - MemoryAllocation::aligned_free(q_scale_u); - MemoryAllocation::aligned_free(q_blocks_u); - MemoryAllocation::aligned_free(q_scale_d); - MemoryAllocation::aligned_free(q_blocks_d); + + /*--- q_scale/q_blocks' .l/.u are pinned (cudaMallocHost) rather than aligned_alloc when + * useCuda, .d never is; see the comment in Initialize(). Free each with its matching + * deallocator. ---*/ + auto freeQuantLDU = [this](auto& m) { + MemoryAllocation::aligned_free(m.d); + if (useCuda) { + GPUMemoryAllocation::pinned_free(m.l); + GPUMemoryAllocation::pinned_free(m.u); + } else { + MemoryAllocation::aligned_free(m.l); + MemoryAllocation::aligned_free(m.u); + } + }; + freeQuantLDU(q_scale); + freeQuantLDU(q_blocks); if (useCuda) { - auto freeLDU = [](LDU& m) { + auto freeLDU = [](auto& m) { GPUMemoryAllocation::gpu_free(m.d); GPUMemoryAllocation::gpu_free(m.l); GPUMemoryAllocation::gpu_free(m.u); @@ -149,13 +155,16 @@ CSysMatrix::~CSysMatrix() { }; freeLDU(gpu); freeLDU(gpu_ilu); + freeLDU(d_q_scale); + freeLDU(d_q_blocks); GPUMemoryAllocation::gpu_free(d_invM); GPUMemoryAllocation::gpu_free(d_ilu_color_idx); GPUMemoryAllocation::gpu_free(d_ilu_level_idx); #ifdef SU2_ENABLE_CUDA_KERNELS if (ilu_build_graph_exec != nullptr) cudaGraphExecDestroy(ilu_build_graph_exec); if (ilu_apply_graph_exec != nullptr) cudaGraphExecDestroy(ilu_apply_graph_exec); - if (ilu_stream != nullptr) cudaStreamDestroy(ilu_stream); + if (aux_stream != nullptr) cudaStreamDestroy(aux_stream); + if (htd_event != nullptr) cudaEventDestroy(htd_event); #endif } @@ -207,16 +216,18 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi useCuda = config->GetCUDA(); const bool ilu_needed = (prec == ILU); - const bool diag_needed = (prec == JACOBI) || (prec == LINELET); + const bool diag_needed = (prec == JACOBI) || (prec == Q_JACOBI) || (prec == LINELET); /*--- Linelet also builds the Jacobi preconditioner but reads the inverse diagonal blocks on - * the host, so only plain Jacobi can keep them exclusively on the device. ---*/ - jacobi_on_device = useCuda && (prec == JACOBI); + * the host, so only plain (or quantized) Jacobi can keep them exclusively on the device. ---*/ + jacobi_on_device = useCuda && (prec == JACOBI || prec == Q_JACOBI); #ifndef CODI_REVERSE_TYPE - const bool q_lus_needed = allow_quant && !useCuda && (prec == Q_LU_SGS); + /*--- Q_LU_SGS is still host-only. ---*/ + const bool quantized_offdiag_needed = + allow_quant && (prec == Q_JACOBI || prec == Q_IDENTITY || (prec == Q_LU_SGS && !useCuda)); #else /*--- No quantization in adjoint mode for now because TransposeInPlace would get complicated. ---*/ - const bool q_lus_needed = false; + const bool quantized_offdiag_needed = false; #endif /*--- Basic dimensions. ---*/ @@ -242,21 +253,30 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi } allocAndInit(mat.d, nPoint * nVar * nEqn); - if (q_lus_needed) { - /*--- Q_LU_SGS: no full-precision L/U; off-diagonal blocks live in quantized storage. - * L/U are quantized on-the-fly during assembly; diagonal is quantized in Build step. ---*/ + if (quantized_offdiag_needed) { + /*--- Q_LU_SGS / Q_JACOBI / Q_IDENTITY: no full-precision L/U; off-diagonal blocks live in quantized storage. + * L/U are quantized on-the-fly during assembly; diagonal is quantized in the Build step. ---*/ #ifndef CODI_REVERSE_TYPE quantized_mode = true; #endif + /*--- .l/.u are pinned (page-locked) when useCuda because HtDTransfer() uploads them with + * cudaMemcpyAsync, which is only genuinely asynchronous from pinned host memory. ---*/ auto allocQ = [](QuantType*& ptr, unsigned long n) { ptr = MemoryAllocation::aligned_alloc(64, n * sizeof(QuantType)); }; - allocQ(q_scale_l, mat.nnz_l * nVar); - allocQ(q_blocks_l, mat.nnz_l * nVar * nEqn); - allocQ(q_scale_u, mat.nnz_u * nVar); - allocQ(q_blocks_u, mat.nnz_u * nVar * nEqn); - allocQ(q_scale_d, nPoint * nVar); - allocQ(q_blocks_d, nPoint * nVar * nEqn); + auto allocPinnedIfCuda = [useCuda = this->useCuda](QuantType*& ptr, unsigned long n) { + if (useCuda) { + ptr = GPUMemoryAllocation::pinned_alloc(n * sizeof(QuantType)); + } else { + ptr = MemoryAllocation::aligned_alloc(64, n * sizeof(QuantType)); + } + }; + allocPinnedIfCuda(q_scale.l, mat.nnz_l * nVar); + allocPinnedIfCuda(q_blocks.l, mat.nnz_l * nVar * nEqn); + allocPinnedIfCuda(q_scale.u, mat.nnz_u * nVar); + allocPinnedIfCuda(q_blocks.u, mat.nnz_u * nVar * nEqn); + allocQ(q_scale.d, nPoint * nVar); + allocQ(q_blocks.d, nPoint * nVar * nEqn); } else { allocAndInit(mat.l, mat.nnz_l * nVar * nEqn); allocAndInit(mat.u, mat.nnz_u * nVar * nEqn); @@ -274,12 +294,28 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi SU2_MPI::Error("CUDA CSysMatrix block-LDU SpMV requires square blocks.", CURRENT_FUNCTION); } GPUAllocAndInit(gpu.d, nPoint * nVar * nEqn); - GPUAllocAndInit(gpu.l, mat.nnz_l * nVar * nEqn); - GPUAllocAndInit(gpu.u, mat.nnz_u * nVar * nEqn); GPUAllocAndCopy(gpu.row_ptr_l, mat.row_ptr_l, nPointDomain + 1); GPUAllocAndCopy(gpu.col_ind_l, mat.col_ind_l, mat.nnz_l); GPUAllocAndCopy(gpu.row_ptr_u, mat.row_ptr_u, nPointDomain + 1); GPUAllocAndCopy(gpu.col_ind_u, mat.col_ind_u, mat.nnz_u); + + if (quantized_mode) { + /*--- Device mirrors of the host quantized storage; gpu.l/gpu.u are not allocated (nothing + * would ever read them). d_q_scale.d/d_q_blocks.d are uploaded from the host result once + * QuantizeDiagonalBlocks() has computed it, see the comment on those members. ---*/ + auto GPUAllocQ = [](QuantType*& ptr, unsigned long n) { + ptr = GPUMemoryAllocation::gpu_alloc(n * sizeof(QuantType)); + }; + GPUAllocQ(d_q_scale.l, mat.nnz_l * nVar); + GPUAllocQ(d_q_blocks.l, mat.nnz_l * nVar * nEqn); + GPUAllocQ(d_q_scale.u, mat.nnz_u * nVar); + GPUAllocQ(d_q_blocks.u, mat.nnz_u * nVar * nEqn); + GPUAllocQ(d_q_scale.d, nPoint * nVar); + GPUAllocQ(d_q_blocks.d, nPoint * nVar * nEqn); + } else { + GPUAllocAndInit(gpu.l, mat.nnz_l * nVar * nEqn); + GPUAllocAndInit(gpu.u, mat.nnz_u * nVar * nEqn); + } } if (type == ConnectivityType::FiniteVolume) { @@ -350,9 +386,9 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi static bool printed = false; if (rank == MASTER_NODE && !printed) { - cout << "GPU ILU scheduling (worst rank): " << nColorsMax << " colors for the factorization (~" + cout << "GPU ILU scheduling (worst rank):\n " << nColorsMax << " colors for the factorization (~" << minAvgColorSize << " points/color on average),\n" - << " " << nLevelsMax << " levels for the triangular solves (~" << minAvgLevelSize + << " " << nLevelsMax << " levels for the triangular solves (~" << minAvgLevelSize << " points/level on average)." << endl; printed = true; } @@ -731,13 +767,31 @@ template void CSysMatrix::QuantizeDiagonalBlocks() { SU2_ZONE_SCOPED - if (quantized_mode) { - /*--- Q_LU_SGS: L/U were quantized during assembly; only the diagonal needs quantization now. ---*/ - SU2_OMP_FOR_DYN(omp_heavy_size) - for (auto i = 0ul; i < nPointDomain; ++i) - QuantizeBlock(&mat.d[i * nVar * nVar], &q_scale_d[i * nVar], &q_blocks_d[i * nVar * nVar]); - END_SU2_OMP_FOR + if (!quantized_mode) return; + + if (useCuda) { +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + /*--- gpu.d is already on the device - HtDTransfer() uploads it unconditionally, since + * Jacobi's own build needs the full precision diagonal regardless of quantization - so + * quantize straight from it here instead of quantizing on the host and uploading the + * result. ---*/ + SU2_DEVICE_REGION(QuantizeDiagonalBlocksGPU();) + return; + } else { + GPUNotAvailable(CURRENT_FUNCTION); + } +#else + GPUNotAvailable(CURRENT_FUNCTION); +#endif } + + /*--- Q_LU_SGS / Q_JACOBI / Q_IDENTITY: L/U were quantized during assembly; only the diagonal needs quantization + * now. ---*/ + SU2_OMP_FOR_DYN(omp_heavy_size) + for (auto i = 0ul; i < nPointDomain; ++i) + QuantizeBlock(&mat.d[i * nVar * nVar], &q_scale.d[i * nVar], &q_blocks.d[i * nVar * nVar]); + END_SU2_OMP_FOR } template @@ -758,10 +812,10 @@ void CSysMatrix::SetValZero() { zeroChunk(mat.l, mat.nnz_l * nVar * nEqn); zeroChunk(mat.u, mat.nnz_u * nVar * nEqn); } else { - zeroChunk(q_scale_l, mat.nnz_l * nVar); - zeroChunk(q_scale_u, mat.nnz_l * nVar); - zeroChunk(q_blocks_l, mat.nnz_l * nVar * nEqn); - zeroChunk(q_blocks_u, mat.nnz_u * nVar * nEqn); + zeroChunk(q_scale.l, mat.nnz_l * nVar); + zeroChunk(q_scale.u, mat.nnz_l * nVar); + zeroChunk(q_blocks.l, mat.nnz_l * nVar * nEqn); + zeroChunk(q_blocks.u, mat.nnz_u * nVar * nEqn); } SU2_OMP_BARRIER } @@ -863,10 +917,10 @@ void CSysMatrix::DeleteValsRowi(unsigned long block_i, unsigned long if (quantized_mode) { for (auto k = mat.row_ptr_l[block_i]; k < mat.row_ptr_l[block_i + 1]; ++k) { - for (auto iVar = 0u; iVar < nEqn; iVar++) q_blocks_l[k * blkSz + row * nEqn + iVar] = 0; + for (auto iVar = 0u; iVar < nEqn; iVar++) q_blocks.l[k * blkSz + row * nEqn + iVar] = 0; } for (auto k = mat.row_ptr_u[block_i]; k < mat.row_ptr_u[block_i + 1]; ++k) { - for (auto iVar = 0u; iVar < nEqn; iVar++) q_blocks_u[k * blkSz + row * nEqn + iVar] = 0; + for (auto iVar = 0u; iVar < nEqn; iVar++) q_blocks.u[k * blkSz + row * nEqn + iVar] = 0; } } else { for (auto k = mat.row_ptr_l[block_i]; k < mat.row_ptr_l[block_i + 1]; ++k) { @@ -938,6 +992,11 @@ template void CSysMatrix::BuildJacobiPreconditioner() { SU2_ZONE_SCOPED + /*--- Independent of invM (reads/quantizes mat.d, a no-op unless quantized_mode); done first, + * unconditionally, so it runs whichever branch below builds invM (in particular the + * jacobi_on_device one, which returns early). ---*/ + QuantizeDiagonalBlocks(); + if (jacobi_on_device) { #ifdef SU2_ENABLE_CUDA_KERNELS if constexpr (su2_gpu_capable_v) { @@ -1561,9 +1620,9 @@ void CSysMatrix::SetDiagonalAsColumnSum() { for (auto j = 0ul; j < nEqn; ++j) d_i[i * nEqn + j] -= view(i, j); }; for (auto k_l = mat.row_ptr_l[iPoint]; k_l < mat.row_ptr_l[iPoint + 1]; ++k_l) - subtractTransp(l_to_u_transp[k_l], q_scale_u, q_blocks_u); + subtractTransp(l_to_u_transp[k_l], q_scale.u, q_blocks.u); for (auto k_u = mat.row_ptr_u[iPoint]; k_u < mat.row_ptr_u[iPoint + 1]; ++k_u) - subtractTransp(u_to_l_transp[k_u], q_scale_l, q_blocks_l); + subtractTransp(u_to_l_transp[k_u], q_scale.l, q_blocks.l); } } END_SU2_OMP_FOR diff --git a/Common/src/linear_algebra/CSysMatrixGPU.cu b/Common/src/linear_algebra/CSysMatrixGPU.cu index 1cd5de51d5ce..470b7a22bcf4 100644 --- a/Common/src/linear_algebra/CSysMatrixGPU.cu +++ b/Common/src/linear_algebra/CSysMatrixGPU.cu @@ -26,6 +26,7 @@ */ #include +#include #include "../../include/linear_algebra/CMatrixInverse.hpp" #include "../../include/linear_algebra/CSysMatrix.inl" @@ -33,23 +34,32 @@ namespace { +/*! + * \brief Apply the Jacobi preconditioner: prod = invM * vec (block-diagonal). Points are batched + * into blocks of ~128 threads (see ComputeJacobiPreconditionerGPU), threadIdx.x mapping + * to (point-within-block, output variable) via divmod by nVar - the same layout as + * BlockLDU_SpMV_kernel. Occupancy was already fine here (one thread per point, full + * warps), but consecutive threads used to land nVar^2 elements apart in invM (one point's + * whole dense block per thread); with this mapping they land nVar elements apart instead, + * a real (if partial, since it is still not stride-1) coalescing win that needs no shared + * memory or synchronization, unlike a fully-coalesced one-thread-per-block-entry version + * would. + */ template -__global__ void ApplyJacobiPreconditionerKernel(const ScalarType* invM, const ScalarType* vec, ScalarType* prod, +__global__ void ApplyJacobiPreconditionerKernel(const ScalarType* __restrict__ invM, + const ScalarType* __restrict__ vec, ScalarType* __restrict__ prod, unsigned long nPointDomain, unsigned long nVar) { - const auto iPoint = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; + const unsigned long rowsPerBlock = blockDim.x / nVar; + const unsigned long iVar = threadIdx.x % nVar; + const unsigned long iPoint = static_cast(blockIdx.x) * rowsPerBlock + threadIdx.x / nVar; if (iPoint >= nPointDomain) return; - const auto block = &invM[iPoint * nVar * nVar]; - const auto rhs = &vec[iPoint * nVar]; - auto out = &prod[iPoint * nVar]; + const auto* block = &invM[iPoint * nVar * nVar + iVar * nVar]; + const auto* rhs = &vec[iPoint * nVar]; - for (auto iVar = 0ul; iVar < nVar; ++iVar) { - auto sum = ScalarType(0); - for (auto jVar = 0ul; jVar < nVar; ++jVar) { - sum += block[iVar * nVar + jVar] * rhs[jVar]; - } - out[iVar] = sum; - } + auto sum = ScalarType(0); + for (unsigned long jVar = 0; jVar < nVar; ++jVar) sum += block[jVar] * rhs[jVar]; + prod[iPoint * nVar + iVar] = sum; } /*--- ILU. The factorization is scheduled by coloring: colors are true independent sets of the @@ -98,9 +108,54 @@ __device__ FORCEINLINE ScalarType* GetBlockILU(const DeviceLDU& M, u } /*! - * \brief Invert the diagonal blocks of the matrix, device version of InverseDiagonalBlock. - * \note Grid: one block per row, blockDim.x == nVar*nVar (one thread per block entry, they - * stage the input in shared memory). Dynamic shared memory: nVar*nVar scalars. + * \brief Parallel Gauss-Jordan matrix inversion, shared by InvertDiagonalBlocksKernel and + * IluFactorColorKernel's diagonal-inversion step below - both already have nVar*nVar + * threads and two blockSize-sized shared buffers on hand at the point they need a diagonal + * block inverted. SU2_LinAlg::MatrixInverse would have to run on a single thread. + * \param i,j Row/column of the block entry this thread owns, in 0..nVar-1 (i.e. threadIdx.x's + * divmod by nVar, same mapping the caller already uses for everything else). + * \param A Destroyed. \param Inv Must be pre-loaded with the identity, must not alias \p A; + * holds the inverse on return. + * \note __syncthreads() (not __syncwarp()) is used so this stays correct even when nVar*nVar + * exceeds one warp (nVar > ~5), at the cost of a full block-wide barrier even for the + * common case where the whole block already fits in one warp. Every thread of the block + * must call this (no divergent early return before it), since every __syncthreads() here + * is a whole-block barrier. + */ +template +__device__ FORCEINLINE void ParallelMatrixInverse(unsigned long nVar, unsigned long i, unsigned long j, + ScalarType* __restrict__ A, ScalarType* __restrict__ Inv) { + for (auto k = 0ul; k < nVar; ++k) { + /*--- Regularize the pivot (shared with the host path, same clamp value). ---*/ + if (i == k && j == k) SU2_LinAlg::RegularizePivot(A[k * nVar + k]); + __syncthreads(); + + /*--- Normalize the pivot row. ---*/ + const ScalarType pivot = A[k * nVar + k]; + if (i == k) { + A[i * nVar + j] /= pivot; + Inv[i * nVar + j] /= pivot; + } + __syncthreads(); + + /*--- Eliminate column k from every other row; A(k,*) and Inv(k,*) are already finalized for + * this step (previous barrier), and each thread only ever writes its own (i,j), so this + * needs no further synchronization until the next pivot's regularization reads A(k+1,k+1). ---*/ + if (i != k) { + const ScalarType factor = A[i * nVar + k]; + A[i * nVar + j] -= factor * A[k * nVar + j]; + Inv[i * nVar + j] -= factor * Inv[k * nVar + j]; + } + __syncthreads(); + } +} + +/*! + * \brief Invert the diagonal blocks of the matrix, device version of InverseDiagonalBlock, via + * ParallelMatrixInverse (see its comment for why this beats a single serial thread). + * \note Grid: one block per row, blockDim.x == nVar*nVar. Dynamic shared memory: 2*nVar*nVar + * scalars (the working copy of the block, and the inverse being accumulated in place of + * the old identity-then-eliminate scheme). */ template __global__ void InvertDiagonalBlocksKernel(unsigned long nRows, unsigned long nVar, @@ -110,15 +165,41 @@ __global__ void InvertDiagonalBlocksKernel(unsigned long nRows, unsigned long nV const auto blockSize = nVar * nVar; const unsigned long tid = threadIdx.x; + const unsigned long i = tid / nVar; + const unsigned long j = tid % nVar; /*--- The inversion destroys its input, so it cannot work on the matrix itself. ---*/ extern __shared__ __align__(sizeof(double)) char smem[]; - auto* work = reinterpret_cast(smem); + auto* A = reinterpret_cast(smem); + auto* Inv = A + blockSize; - work[tid] = mat_d[iRow * blockSize + tid]; + A[tid] = mat_d[iRow * blockSize + tid]; + Inv[tid] = ScalarType(i == j); __syncthreads(); - if (tid == 0) SU2_LinAlg::MatrixInverse(nVar, work, invM + iRow * blockSize); + ParallelMatrixInverse(nVar, i, j, A, Inv); + + invM[iRow * blockSize + tid] = Inv[tid]; +} + +/*! + * \brief Quantize the diagonal blocks straight from the device diagonal (gpu.d), device + * counterpart of CSysMatrix::QuantizeBlock (CSysMatrix.cpp). Calls the exact same + * EncodeQuantRow encoding function. One thread per (point, row) - each block-row's + * scale and quantization are independent of every other row. + */ +template +__global__ void QuantizeDiagonalBlocksKernel(unsigned long nRows, unsigned long nVar, + const ScalarType* __restrict__ mat_d, int8_t* __restrict__ q_scale_d, + int8_t* __restrict__ q_blocks_d) { + const unsigned long rowsPerBlock = blockDim.x / nVar; + const unsigned long iVar = threadIdx.x % nVar; + const unsigned long iPoint = static_cast(blockIdx.x) * rowsPerBlock + threadIdx.x / nVar; + if (iPoint >= nRows) return; + + const auto* blk = mat_d + iPoint * nVar * nVar; + EncodeQuantRow([&](unsigned long r, unsigned long c) { return blk[r * nVar + c]; }, q_scale_d[iPoint * nVar + iVar], + q_blocks_d + iPoint * nVar * nVar + iVar * nVar, nVar, iVar); } /*! @@ -214,11 +295,17 @@ __global__ void IluFactorColorKernel(const su2uint* __restrict__ color_idx, unsi } /*--- Invert the diagonal entry, Uii, for the rows that depend on it. The loop above may have - * updated it (when kPoint == iRow), so the whole block has to be done first. ---*/ + * updated it (when kPoint == iRow), so the whole block has to be done first. Lij is free again + * here (its last use, storing it into Block_ij, is done) - reuse it as the identity/inverse + * buffer ParallelMatrixInverse needs, instead of a separate shared allocation. ---*/ __syncthreads(); work[tid] = M.d[iRow * blockSize + tid]; + Lij[tid] = ScalarType(iVar == jVar); __syncthreads(); - if (tid == 0) SU2_LinAlg::MatrixInverse(nVar, work, M.d + iRow * blockSize); + + ParallelMatrixInverse(nVar, iVar, jVar, work, Lij); + + M.d[iRow * blockSize + tid] = Lij[tid]; } /*! @@ -315,8 +402,12 @@ __global__ void IluBackwardKernel(const su2uint* __restrict__ level_idx, unsigne } /*! - * \brief Block-LDU SpMV kernel: y[iRow] = (L + D + U) * x per block-row. - * One CUDA block per block-row; threadIdx.x indexes output variable (0..nVar-1). + * \brief Block-LDU SpMV kernel: y[iRow] = (L + D + U) * x per block-row. Several rows are + * batched into one CUDA block (blockDim.x / nVar of them, see MatrixVectorProductGPU) + * instead of one row per block: nVar is typically ~4-6, so one-row-per-block leaves most + * of a warp's lanes permanently idle and caps occupancy at a few resident (mostly-empty) + * warps per SM, well before DRAM bandwidth is the limit. threadIdx.x indexes + * (row-within-block, output variable) as (threadIdx.x / nVar, threadIdx.x % nVar). */ template __global__ void BlockLDU_SpMV_kernel(unsigned long nRows, unsigned long nVar, @@ -328,9 +419,10 @@ __global__ void BlockLDU_SpMV_kernel(unsigned long nRows, unsigned long nVar, const su2uint* __restrict__ col_ind_u, const ScalarType* __restrict__ mat_u, const ScalarType* __restrict__ x, ScalarType* __restrict__ y) { - const unsigned long iRow = blockIdx.x; - const unsigned long iVar = threadIdx.x; - if (iRow >= nRows || iVar >= nVar) return; + const unsigned long rowsPerBlock = blockDim.x / nVar; + const unsigned long iVar = threadIdx.x % nVar; + const unsigned long iRow = static_cast(blockIdx.x) * rowsPerBlock + threadIdx.x / nVar; + if (iRow >= nRows) return; ScalarType sum = 0; /* Lower */ @@ -353,6 +445,58 @@ __global__ void BlockLDU_SpMV_kernel(unsigned long nRows, unsigned long nVar, y[iRow * nVar + iVar] = sum; } +/*! + * \brief Device version of QuantizedRowProduct/QuantizedMatVecAdd (CSysMatrix.inl). Rows are + * batched per block the same way as BlockLDU_SpMV_kernel. + */ +template +__global__ void QuantizedBlockLDU_SpMV_kernel( + unsigned long nRows, unsigned long nVar, const su2uint* __restrict__ row_ptr_l, + const su2uint* __restrict__ col_ind_l, const int8_t* __restrict__ q_scale_l, + const int8_t* __restrict__ q_blocks_l, const int8_t* __restrict__ q_scale_d, + const int8_t* __restrict__ q_blocks_d, const su2uint* __restrict__ row_ptr_u, + const su2uint* __restrict__ col_ind_u, const int8_t* __restrict__ q_scale_u, + const int8_t* __restrict__ q_blocks_u, const ScalarType* __restrict__ x, ScalarType* __restrict__ y) { + const unsigned long rowsPerBlock = blockDim.x / nVar; + const unsigned long iVar = threadIdx.x % nVar; + const unsigned long iRow = static_cast(blockIdx.x) * rowsPerBlock + threadIdx.x / nVar; + if (iRow >= nRows) return; + + auto addBlock = [&](const int8_t* __restrict__ qs, const int8_t* __restrict__ qv, const ScalarType* __restrict__ xk) { + const float row_scale = DecodeQuantScale(qs[iVar]); + const int8_t* __restrict__ row = qv + iVar * nVar; + ScalarType partial = 0; + unsigned long jVar = 0; + for (; jVar + 4 <= nVar; jVar += 4) { + /*--- Row bytes are not generally 4-byte aligned (nVar*nVar need not be a multiple of 4), + * so this must go through memcpy rather than a reinterpret_cast deref. ---*/ + uint32_t packed; + memcpy(&packed, row + jVar, sizeof(packed)); + partial += static_cast(packed) * xk[jVar]; + partial += static_cast(packed >> 8) * xk[jVar + 1]; + partial += static_cast(packed >> 16) * xk[jVar + 2]; + partial += static_cast(packed >> 24) * xk[jVar + 3]; + } + for (; jVar < nVar; ++jVar) partial += row[jVar] * xk[jVar]; + return static_cast(row_scale) * partial; + }; + + ScalarType sum = 0; + /* Lower */ + for (auto k = row_ptr_l[iRow]; k < row_ptr_l[iRow + 1]; ++k) { + const auto col = col_ind_l[k]; + sum += addBlock(q_scale_l + k * nVar, q_blocks_l + k * nVar * nVar, x + col * nVar); + } + /* Diagonal */ + sum += addBlock(q_scale_d + iRow * nVar, q_blocks_d + iRow * nVar * nVar, x + iRow * nVar); + /* Upper */ + for (auto k = row_ptr_u[iRow]; k < row_ptr_u[iRow + 1]; ++k) { + const auto col = col_ind_u[k]; + sum += addBlock(q_scale_u + k * nVar, q_blocks_u + k * nVar * nVar, x + col * nVar); + } + y[iRow * nVar + iVar] = sum; +} + } // namespace template @@ -368,8 +512,10 @@ void CSysMatrix::ComputeJacobiPreconditionerGPU(const CSysVector((nPointDomain + threadsPerBlock - 1) / threadsPerBlock); + constexpr unsigned long targetThreadsPerBlock = 128; + const auto rowsPerBlock = std::max(1, targetThreadsPerBlock / nVar); + const auto threadsPerBlock = static_cast(rowsPerBlock * nVar); + const auto blocks = static_cast((nPointDomain + rowsPerBlock - 1) / rowsPerBlock); ApplyJacobiPreconditionerKernel<<>>(d_invM, vec.GetDevicePointer(), prod.GetDevicePointer(), nPointDomain, nVar); /*--- Sync so the zone above actually times the kernel, not just the (async) launch call. ---*/ @@ -377,6 +523,25 @@ void CSysMatrix::ComputeJacobiPreconditionerGPU(const CSysVector +void CSysMatrix::QuantizeDiagonalBlocksGPU() { + SU2_ZONE_SCOPED + + if (nPointDomain == 0) return; + + /*--- The matrix is expected to be on the device already, it is uploaded once per solve by + * CSysMatrixVectorProduct, which is created before the preconditioner is built. ---*/ + constexpr unsigned long targetThreadsPerBlock = 128; + const auto rowsPerBlock = std::max(1, targetThreadsPerBlock / nVar); + const auto threadsPerBlock = static_cast(rowsPerBlock * nVar); + const auto blocks = static_cast((nPointDomain + rowsPerBlock - 1) / rowsPerBlock); + QuantizeDiagonalBlocksKernel + <<>>(nPointDomain, nVar, gpu.d, d_q_scale.d, d_q_blocks.d); + /*--- Sync so the zone above actually times the kernel, not just the (async) launch call. ---*/ + gpuErrChk(cudaStreamSynchronize(nullptr)); + gpuErrChk(cudaGetLastError()); +} + template void CSysMatrix::BuildJacobiPreconditionerGPU() { SU2_ZONE_SCOPED @@ -389,9 +554,8 @@ void CSysMatrix::BuildJacobiPreconditionerGPU() { /*--- The matrix is expected to be on the device already, it is uploaded once per solve by * CSysMatrixVectorProduct, which is created before the preconditioner is built. ---*/ const auto blockSize = static_cast(nVar * nVar); - InvertDiagonalBlocksKernel - <<(nPointDomain), blockSize, blockSize * sizeof(ScalarType)>>>(nPointDomain, nVar, gpu.d, - d_invM); + InvertDiagonalBlocksKernel<<(nPointDomain), blockSize, + 2 * blockSize * sizeof(ScalarType)>>>(nPointDomain, nVar, gpu.d, d_invM); /*--- Sync so the zone above actually times the kernel, not just the (async) launch call. ---*/ gpuErrChk(cudaStreamSynchronize(nullptr)); gpuErrChk(cudaGetLastError()); @@ -419,7 +583,7 @@ void CSysMatrix::BuildILUPreconditionerGPU() { /*--- The legacy default stream cannot be captured, so the graph lives on its own stream, * created once. Every launch below is followed by a sync back to the host, so this does not * change execution order relative to the rest of the (single-stream) solver. ---*/ - if (ilu_stream == nullptr) gpuErrChk(cudaStreamCreate(&ilu_stream)); + if (aux_stream == nullptr) gpuErrChk(cudaStreamCreate(&aux_stream)); /*--- The launch sequence (ilu_gpu_sweeps passes over all colors) is identical on every call: * the grid and block sizes only depend on the (fixed) sparsity pattern/coloring and the device @@ -433,7 +597,7 @@ void CSysMatrix::BuildILUPreconditionerGPU() { * scratch, relying on the matrix changing little between outer/pseudo-time iterations. ---*/ if (ilu_build_graph_exec == nullptr) { cudaGraph_t graph; - gpuErrChk(cudaStreamBeginCapture(ilu_stream, cudaStreamCaptureModeThreadLocal)); + gpuErrChk(cudaStreamBeginCapture(aux_stream, cudaStreamCaptureModeThreadLocal)); for (unsigned short sweep = 0; sweep < ilu_gpu_sweeps; ++sweep) { for (auto color = 0ul; color + 1 < ilu_color_ptr.size(); ++color) { @@ -441,17 +605,17 @@ void CSysMatrix::BuildILUPreconditionerGPU() { const auto size = ilu_color_ptr[color + 1] - begin; if (size == 0) continue; IluFactorColorKernel - <<>>(d_ilu_color_idx, begin, size, nPointDomain, nVar, A, M); + <<>>(d_ilu_color_idx, begin, size, nPointDomain, nVar, A, M); } } - gpuErrChk(cudaStreamEndCapture(ilu_stream, &graph)); + gpuErrChk(cudaStreamEndCapture(aux_stream, &graph)); gpuErrChk(cudaGraphInstantiate(&ilu_build_graph_exec, graph, nullptr, nullptr, 0)); gpuErrChk(cudaGraphDestroy(graph)); } - gpuErrChk(cudaGraphLaunch(ilu_build_graph_exec, ilu_stream)); - gpuErrChk(cudaStreamSynchronize(ilu_stream)); + gpuErrChk(cudaGraphLaunch(ilu_build_graph_exec, aux_stream)); + gpuErrChk(cudaStreamSynchronize(aux_stream)); gpuErrChk(cudaGetLastError()); } @@ -481,7 +645,7 @@ void CSysMatrix::ComputeILUPreconditionerGPU(const CSysVector::ComputeILUPreconditionerGPU(const CSysVector::ComputeILUPreconditionerGPU(const CSysVector - <<>>(d_ilu_level_idx, begin, size, nVar, M, d_vec, d_prod); + <<>>(d_ilu_level_idx, begin, size, nVar, M, d_vec, d_prod); } /*--- Backward substitution: one exact pass over the levels in decreasing order, @@ -515,18 +679,18 @@ void CSysMatrix::ComputeILUPreconditionerGPU(const CSysVector - <<>>(d_ilu_level_idx, begin, size, nPointDomain, nVar, M, d_prod); + <<>>(d_ilu_level_idx, begin, size, nPointDomain, nVar, M, d_prod); } - gpuErrChk(cudaStreamEndCapture(ilu_stream, &graph)); + gpuErrChk(cudaStreamEndCapture(aux_stream, &graph)); gpuErrChk(cudaGraphInstantiate(&ilu_apply_graph_exec, graph, nullptr, nullptr, 0)); gpuErrChk(cudaGraphDestroy(graph)); ilu_apply_graph_vec = d_vec; ilu_apply_graph_prod = d_prod; } - gpuErrChk(cudaGraphLaunch(ilu_apply_graph_exec, ilu_stream)); - gpuErrChk(cudaStreamSynchronize(ilu_stream)); + gpuErrChk(cudaGraphLaunch(ilu_apply_graph_exec, aux_stream)); + gpuErrChk(cudaStreamSynchronize(aux_stream)); gpuErrChk(cudaGetLastError()); } @@ -535,8 +699,28 @@ void CSysMatrix::HtDTransfer(bool trigger) const { SU2_ZONE_SCOPED if (!trigger) return; gpuErrChk(cudaMemcpy(gpu.d, mat.d, sizeof(ScalarType) * nPoint * nVar * nEqn, cudaMemcpyHostToDevice)); - gpuErrChk(cudaMemcpy(gpu.l, mat.l, sizeof(ScalarType) * mat.nnz_l * nVar * nEqn, cudaMemcpyHostToDevice)); - gpuErrChk(cudaMemcpy(gpu.u, mat.u, sizeof(ScalarType) * mat.nnz_u * nVar * nEqn, cudaMemcpyHostToDevice)); + if (quantized_mode) { + /*--- No gpu.l/gpu.u to transfer (never allocated); mirror the host quantized off-diagonal + * storage instead (the diagonal mirrors, d_q_scale.d/d_q_blocks.d, are not touched here at + * all - QuantizeDiagonalBlocksGPU() populates them straight from gpu.d, just uploaded above, + * with no host round trip). Issued as async copies on a dedicated stream so this transfer can + * run concurrently with whatever the preconditioner's Build() launches next on the default + * stream. ---*/ + if (aux_stream == nullptr) gpuErrChk(cudaStreamCreate(&aux_stream)); + if (htd_event == nullptr) gpuErrChk(cudaEventCreateWithFlags(&htd_event, cudaEventDisableTiming)); + gpuErrChk(cudaMemcpyAsync(d_q_scale.l, q_scale.l, sizeof(QuantType) * mat.nnz_l * nVar, cudaMemcpyHostToDevice, + aux_stream)); + gpuErrChk(cudaMemcpyAsync(d_q_blocks.l, q_blocks.l, sizeof(QuantType) * mat.nnz_l * nVar * nEqn, + cudaMemcpyHostToDevice, aux_stream)); + gpuErrChk(cudaMemcpyAsync(d_q_scale.u, q_scale.u, sizeof(QuantType) * mat.nnz_u * nVar, cudaMemcpyHostToDevice, + aux_stream)); + gpuErrChk(cudaMemcpyAsync(d_q_blocks.u, q_blocks.u, sizeof(QuantType) * mat.nnz_u * nVar * nEqn, + cudaMemcpyHostToDevice, aux_stream)); + gpuErrChk(cudaEventRecord(htd_event, aux_stream)); + } else { + gpuErrChk(cudaMemcpy(gpu.l, mat.l, sizeof(ScalarType) * mat.nnz_l * nVar * nEqn, cudaMemcpyHostToDevice)); + gpuErrChk(cudaMemcpy(gpu.u, mat.u, sizeof(ScalarType) * mat.nnz_u * nVar * nEqn, cudaMemcpyHostToDevice)); + } } template @@ -547,11 +731,25 @@ void CSysMatrix::MatrixVectorProductGPU(const CSysVector ScalarType* d_vec = vec.GetDevicePointer(); ScalarType* d_prod = prod.GetDevicePointer(); - dim3 blockDim(static_cast(nVar), 1, 1); - dim3 gridDim(static_cast(nPointDomain), 1, 1); - BlockLDU_SpMV_kernel<<>>( - nPointDomain, nVar, gpu.row_ptr_l, gpu.col_ind_l, gpu.l, gpu.d, - gpu.row_ptr_u, gpu.col_ind_u, gpu.u, d_vec, d_prod); + /*--- Batch several rows per block (see BlockLDU_SpMV_kernel's comment): nVar is small + * (typically ~4-6), so one row per block would leave most of a warp idle. Aim for ~128 + * threads/block, the largest whole number of rows that fits. ---*/ + constexpr unsigned long targetThreadsPerBlock = 128; + const auto rowsPerBlock = std::max(1, targetThreadsPerBlock / nVar); + dim3 blockDim(static_cast(rowsPerBlock * nVar), 1, 1); + dim3 gridDim(static_cast((nPointDomain + rowsPerBlock - 1) / rowsPerBlock), 1, 1); + if (quantized_mode) { + /*--- Wait (on the device, no host block) for HtDTransfer's async L/U copy on its own stream + * to finish before this default-stream kernel reads d_q_scale.l/.u/d_q_blocks.l/.u. ---*/ + gpuErrChk(cudaStreamWaitEvent(nullptr, htd_event, 0)); + QuantizedBlockLDU_SpMV_kernel<<>>( + nPointDomain, nVar, gpu.row_ptr_l, gpu.col_ind_l, d_q_scale.l, d_q_blocks.l, d_q_scale.d, d_q_blocks.d, + gpu.row_ptr_u, gpu.col_ind_u, d_q_scale.u, d_q_blocks.u, d_vec, d_prod); + } else { + BlockLDU_SpMV_kernel<<>>( + nPointDomain, nVar, gpu.row_ptr_l, gpu.col_ind_l, gpu.l, gpu.d, + gpu.row_ptr_u, gpu.col_ind_u, gpu.u, d_vec, d_prod); + } /*--- Sync so the zone above actually times the kernel, not just the (async) launch call. ---*/ gpuErrChk(cudaStreamSynchronize(nullptr)); gpuErrChk(cudaGetLastError()); @@ -563,6 +761,7 @@ template void CSysMatrix::MatrixVectorProductGPU(const CSysVector& v CSysVector& prod, \ CGeometry* geometry, \ const CConfig* config) const; \ +template void CSysMatrix::QuantizeDiagonalBlocksGPU(); \ template void CSysMatrix::BuildJacobiPreconditionerGPU(); \ template void CSysMatrix::BuildILUPreconditionerGPU(); \ template void CSysMatrix::ComputeILUPreconditionerGPU(const CSysVector& vec, \ diff --git a/Common/src/linear_algebra/CSysSolve.cpp b/Common/src/linear_algebra/CSysSolve.cpp index dcd88c452c5f..3c9176d87160 100644 --- a/Common/src/linear_algebra/CSysSolve.cpp +++ b/Common/src/linear_algebra/CSysSolve.cpp @@ -1566,6 +1566,8 @@ unsigned long CSysSolve::Solve(CSysMatrix& Jacobian, con break; case JACOBI: case LINELET: + case Q_JACOBI: + /*--- BuildJacobiPreconditioner() quantizes the diagonal itself when needed. ---*/ if (RequiresTranspose) Jacobian.BuildJacobiPreconditioner(); break; case LU_SGS: diff --git a/Common/src/linear_algebra/CSysVectorGPU.cu b/Common/src/linear_algebra/CSysVectorGPU.cu index ba53b0cbc299..81e094cda94c 100644 --- a/Common/src/linear_algebra/CSysVectorGPU.cu +++ b/Common/src/linear_algebra/CSysVectorGPU.cu @@ -27,24 +27,146 @@ #include "../../include/linear_algebra/CSysVector.hpp" #include "../../include/linear_algebra/GPUComms.cuh" -#include #include -#include #include namespace { -/*--- cuBLAS handle for the reductions. Created on first use and kept for the lifetime of - * the program, matching the fact that CUDA is either on or off for the whole run. ---*/ -cublasHandle_t solver_blas_handle = nullptr; +/*! + * \brief Fixed launch shape for DotKernel, so the number of warps per block (and thus the static + * shared-memory reduction buffer's size) is a compile-time constant - no dynamic shared + * memory needed for a single running sum, unlike MultiDotKernel's per-(i,j) buffer. + */ +constexpr unsigned int DOT_THREADS_PER_BLOCK = 256; +constexpr unsigned int DOT_WARPS_PER_BLOCK = DOT_THREADS_PER_BLOCK / 32u; + +/*! + * \brief Single dot product result[0] = , same single-pass grid-stride + warp-shuffle + + * block-combine + atomicAdd reduction as MultiDotKernel (see its comment), specialized for + * the one-running-sum case: no per-thread array, no dynamic shared memory, just a plain + * register accumulator and a small static warpSums buffer. + */ +template +__global__ void DotKernel(const ScalarType* __restrict__ x, const ScalarType* __restrict__ y, unsigned long size, + ScalarType* __restrict__ result) { + ScalarType sum = 0; + const auto stride = static_cast(blockDim.x) * gridDim.x; + for (auto k = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; k < size; k += stride) { + sum += x[k] * y[k]; + } + + /*--- Warp-level reduction: after this, lane 0 of every warp holds that warp's true sum. ---*/ + for (int offset = 16; offset > 0; offset >>= 1) sum += __shfl_down_sync(0xFFFFFFFFu, sum, offset); + + /*--- Block-level combine: warp leaders stage their partials in static shared memory, thread 0 + * sums them and issues the one atomicAdd this block contributes to the global result + * (pre-zeroed by the caller). ---*/ + __shared__ ScalarType warpSums[DOT_WARPS_PER_BLOCK]; + const unsigned int lane = threadIdx.x % 32u; + const unsigned int warpId = threadIdx.x / 32u; + + if (lane == 0) warpSums[warpId] = sum; + __syncthreads(); + + if (threadIdx.x == 0) { + ScalarType blockSum = 0; + for (unsigned int w = 0; w < DOT_WARPS_PER_BLOCK; ++w) blockSum += warpSums[w]; + atomicAdd(result, blockSum); + } +} -cublasHandle_t GetBlasHandle() { - if (solver_blas_handle == nullptr) { - if (cublasCreate(&solver_blas_handle) != CUBLAS_STATUS_SUCCESS) { - SU2_MPI::Error("cuBLAS handle creation failed for the GPU linear algebra.", CURRENT_FUNCTION); +/*! + * \brief Cap on n*m (the number of dot products one MultiDotKernel launch computes), sizing the + * per-thread accumulator array below and the block's shared-memory reduction buffer - + * both compile-time sized, since CUDA has no runtime-sized register/local arrays. + * FGCRODR's Ritz-value path (CSysSolve.cpp) can reach n = m+1 with m = + * LINEAR_SOLVER_RESTART_DEFLATION (user-configurable, default 4 but not uncommon to raise + * into the tens), so n*m grows quadratically with that setting - e.g. m=10 already needs + * 110. Sized generously above realistic usage; multiDotGPU raises a clear SU2_MPI::Error + * rather than silently truncating if a caller ever needs more. + */ +constexpr unsigned int MULTIDOT_MAX_NM = 1024; + +/*! + * \brief Caps on the individual vector counts n and m, bounding the fixed-size pointer arrays + * passed into MultiDotKernel by value as ordinary launch parameters. + * Asymmetric because the two call sites in this codebase produce genuinely asymmetric + * shapes. multiDotGPU always puts whichever of n, m is larger into the "large" kernel + * argument (transposing the result back if needed). + */ +constexpr unsigned int MULTIDOT_MAX_VEC_LARGE = 256; +constexpr unsigned int MULTIDOT_MAX_VEC_SMALL = 64; + +/*! + * \brief Fixed-size array of device pointers, passed to MultiDotKernel by value (see + * MULTIDOT_MAX_VEC_LARGE/SMALL for why). + */ +template +struct MultiDotPointers { + const ScalarType* ptr[MaxCount]; +}; + +/*! + * \brief Compute the aCount*bCount matrix of dot products D(a,b) = in a single pass + * over the data: every thread reads each of the aCount+bCount vectors once per k and + * forms all aCount*bCount products from that same read, so the vectors are only ever read + * once total (aCount+bCount reads of length size, not aCount*bCount), and the reduction + * stays memory-bound. + * \note Each thread accumulates its own private running aCount*bCount sums while striding over k + * (kept in thread-local storage, capped at MULTIDOT_MAX_NM - small enough that it should + * stay resident in registers or L1 for realistic shapes, cheap either way next to the + * K-length main loop's DRAM traffic). Only after that loop do threads combine: a + * warp-shuffle reduction, then one shared-memory combine and one atomicAdd per (a,b) per + * block, so total atomics are O(aCount*bCount * numBlocks), not O(aCount*bCount * size). + * \note A is the "large" argument (up to MULTIDOT_MAX_VEC_LARGE), B the "small" one (up to + * MULTIDOT_MAX_VEC_SMALL) - the caller (multiDotGPU) is responsible for putting the larger + * of its two vector counts into A, and transposing the result back if it had to swap. + */ +template +__global__ void MultiDotKernel(MultiDotPointers A, unsigned int aCount, + MultiDotPointers B, unsigned int bCount, + unsigned long size, ScalarType* __restrict__ D) { + const unsigned int nm = aCount * bCount; + + ScalarType local[MULTIDOT_MAX_NM]; + for (unsigned int t = 0; t < nm; ++t) local[t] = ScalarType(0); + + const auto stride = static_cast(blockDim.x) * gridDim.x; + for (auto k = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; k < size; k += stride) { + for (unsigned int a = 0; a < aCount; ++a) { + const ScalarType v = A.ptr[a][k]; + for (unsigned int b = 0; b < bCount; ++b) local[a * bCount + b] += v * B.ptr[b][k]; + } + } + + /*--- Warp-level reduction: after this, lane 0 of every warp holds that warp's true sum. ---*/ + for (unsigned int t = 0; t < nm; ++t) { + ScalarType val = local[t]; + for (int offset = 16; offset > 0; offset >>= 1) val += __shfl_down_sync(0xFFFFFFFFu, val, offset); + local[t] = val; + } + + /*--- Block-level combine: warp leaders stage their partials in shared memory, thread 0 sums + * them and issues the one atomicAdd per (i,j) this block contributes to the global result + * (pre-zeroed by the caller). ---*/ + extern __shared__ __align__(sizeof(double)) char smem[]; + auto* warpPartials = reinterpret_cast(smem); + const unsigned int lane = threadIdx.x % 32u; + const unsigned int warpId = threadIdx.x / 32u; + const unsigned int warpsPerBlock = blockDim.x / 32u; + + if (lane == 0) { + for (unsigned int t = 0; t < nm; ++t) warpPartials[warpId * nm + t] = local[t]; + } + __syncthreads(); + + if (threadIdx.x == 0) { + for (unsigned int t = 0; t < nm; ++t) { + ScalarType sum = 0; + for (unsigned int w = 0; w < warpsPerBlock; ++w) sum += warpPartials[w * nm + t]; + atomicAdd(&D[t], sum); } } - return solver_blas_handle; } } // namespace @@ -81,28 +203,22 @@ void CSysVector::DtHTransfer(bool trigger) const { template ScalarType CSysVector::dotGPU(const CSysVector& other) const { SU2_ZONE_SCOPED - /*--- Both operands are already on the device, the caller owns the transfers. This - * reduces over MPI, so it must be called by a single thread (see SU2_DEVICE_REGION). ---*/ - cublasHandle_t handle = GetBlasHandle(); - cublasStatus_t status = CUBLAS_STATUS_SUCCESS; + /*--- Both operands are already on the device, the caller owns the transfers. This reduces over + * MPI, so it must be called by a single thread (see SU2_DEVICE_REGION). ---*/ + static ScalarType* d_result = nullptr; + if (d_result == nullptr) gpuErrChk(cudaMalloc(&d_result, sizeof(ScalarType))); - ScalarType local_dot = ScalarType(0); + gpuErrChk(cudaMemsetAsync(d_result, 0, sizeof(ScalarType))); - if constexpr (std::is_same_v) { - status = cublasSdot(handle, static_cast(nElmDomain), GetDevicePointer(), 1, other.GetDevicePointer(), 1, - &local_dot); - } else if constexpr (std::is_same_v) { - status = cublasDdot(handle, static_cast(nElmDomain), GetDevicePointer(), 1, other.GetDevicePointer(), 1, - &local_dot); - } else { - SU2_MPI::Error("Unsupported ScalarType in CSysVector::dotGPU.", CURRENT_FUNCTION); - return ScalarType(0); - } + const auto blocks = static_cast( + std::min((nElmDomain + DOT_THREADS_PER_BLOCK - 1) / DOT_THREADS_PER_BLOCK, 1024)); + DotKernel<<>>(GetDevicePointer(), other.GetDevicePointer(), nElmDomain, + d_result); - if (status != CUBLAS_STATUS_SUCCESS) { - SU2_MPI::Error("cuBLAS dot failed in CSysVector::dotGPU.", CURRENT_FUNCTION); - return ScalarType(0); - } + ScalarType local_dot = ScalarType(0); + gpuErrChk(cudaMemcpyAsync(&local_dot, d_result, sizeof(ScalarType), cudaMemcpyDeviceToHost)); + gpuErrChk(cudaStreamSynchronize(nullptr)); + gpuErrChk(cudaGetLastError()); ScalarType global_dot = ScalarType(0); const auto mpi_type = (sizeof(ScalarType) < sizeof(double)) ? MPI_FLOAT : MPI_DOUBLE; @@ -112,102 +228,104 @@ ScalarType CSysVector::dotGPU(const CSysVector& other) const { } /*! - * \brief multi vector product with cublasgemmBatched + * \brief Multi vector dot product, local(i,j) = , via MultiDotKernel. + * \note Whichever of n, m is larger is passed as MultiDotKernel's "A" (large-capacity) argument + * and the other as "B" (small-capacity) - see MULTIDOT_MAX_VEC_LARGE/SMALL for why - so if + * m > n, V and W (and their counts) are swapped for the call, and the resulting m*n matrix + * is transposed back into the n*m shape the caller expects (cheap: this matrix is at most + * MULTIDOT_MAX_NM elements, nowhere near the size of the reduction itself). */ template su2matrix CSysVector::multiDotGPU(const std::vector>& V, const size_t i0, const size_t n, const std::vector>& W, const size_t m) { - /*--- The multiDot product between n V[size] and m W[size] vectors is performed as - * a General Matrix Multiplication between two tall-skinny matrices: - * C = \alpha * A^T * B + \beta * C - * being A = V[ size * n ] and B = W[ size * m ] the batched vectors ---*/ - cublasHandle_t handle = GetBlasHandle(); - cublasStatus_t status = CUBLAS_STATUS_SUCCESS; + const size_t nm = n * m; + if (nm > MULTIDOT_MAX_NM) { + SU2_MPI::Error("CSysVector::multiDotGPU: n*m exceeds MULTIDOT_MAX_NM, raise that constant in " + "CSysVectorGPU.cu.", + CURRENT_FUNCTION); + } + + su2matrix local; + local.resize(n, m); + if (nm == 0) return local; + + const bool swap = m > n; + const size_t aCount = swap ? m : n; + const size_t bCount = swap ? n : m; + if (aCount > MULTIDOT_MAX_VEC_LARGE || bCount > MULTIDOT_MAX_VEC_SMALL) { + SU2_MPI::Error("CSysVector::multiDotGPU: n or m exceeds MULTIDOT_MAX_VEC_LARGE/SMALL, raise " + "those constants in CSysVectorGPU.cu.", + CURRENT_FUNCTION); + } const size_t size = V[0].nElmDomain; - const size_t batch = n * m; - /*--- Persistent device workspace, cached across calls and freed automatically - * when the program exits (static local destruction), instead of leaking. ---*/ + /*--- Persistent device workspace for the output only, cached across calls and freed + * automatically when the program exits (static local destruction), instead of leaking. The + * V/W pointers themselves need no device buffer at all: they go to MultiDotKernel as ordinary + * by-value launch parameters (see MultiDotPointers/MULTIDOT_MAX_VEC_LARGE/SMALL). ---*/ struct Workspace { - ScalarType* d_local = nullptr; - const ScalarType** d_A = nullptr; - const ScalarType** d_B = nullptr; - ScalarType** d_C = nullptr; + ScalarType* d_D = nullptr; size_t capacity = 0; - void EnsureCapacity(size_t batch) { - if (batch <= capacity) return; - cudaFree(d_local); - cudaFree(d_A); - cudaFree(d_B); - cudaFree(d_C); - gpuErrChk(cudaMalloc(&d_local, batch * sizeof(ScalarType))); - gpuErrChk(cudaMalloc(&d_A, batch * sizeof(ScalarType*))); - gpuErrChk(cudaMalloc(&d_B, batch * sizeof(ScalarType*))); - gpuErrChk(cudaMalloc(&d_C, batch * sizeof(ScalarType*))); - capacity = batch; + void EnsureCapacity(size_t nm) { + if (nm > capacity) { + cudaFree(d_D); + gpuErrChk(cudaMalloc(&d_D, nm * sizeof(ScalarType))); + capacity = nm; + } } - ~Workspace() { - cudaFree(d_local); - cudaFree(d_A); - cudaFree(d_B); - cudaFree(d_C); - } + ~Workspace() { cudaFree(d_D); } }; static Workspace ws; + ws.EnsureCapacity(nm); - // allocate persistent result buffer local on host and device, is resized if needed - su2matrix local; - local.resize(n, m); - ws.EnsureCapacity(batch); - - // zero out the result buffer - gpuErrChk(cudaMemset(ws.d_local, 0, batch * sizeof(ScalarType))); - - // prepare the arrays A,B,C on host - static std::vector h_A, h_B; - static std::vector h_C; - h_A.resize(batch); h_B.resize(batch); h_C.resize(batch); - - for (size_t i = 0; i < n; ++i) { - for (size_t j =0; j < m; ++j) { - const size_t idx = i * m + j; - h_A[idx] = V[i0 + i].GetDevicePointer(); - h_B[idx] = W[j].GetDevicePointer(); - h_C[idx] = ws.d_local + idx; // C maps to d_local to store the coefficients in the 2D array - } - } - - // copy pointers to device - gpuErrChk(cudaMemcpy(ws.d_A, h_A.data(), batch * sizeof(ScalarType*), cudaMemcpyHostToDevice)); - gpuErrChk(cudaMemcpy(ws.d_B, h_B.data(), batch * sizeof(ScalarType*), cudaMemcpyHostToDevice)); - gpuErrChk(cudaMemcpy(ws.d_C, h_C.data(), batch * sizeof(ScalarType*), cudaMemcpyHostToDevice)); - - // define alpha = 1.0 and beta = 0.0 - const auto alpha = ScalarType(1.0); - const auto beta = ScalarType(0.0); - - if constexpr (std::is_same_v) { - status = cublasSgemmBatched(handle, CUBLAS_OP_T, CUBLAS_OP_N, 1, 1, size, &alpha, ws.d_A, static_cast(size), - ws.d_B, static_cast(size), &beta, ws.d_C, 1, static_cast(batch)); - } else if constexpr (std::is_same_v) { - status = cublasDgemmBatched(handle, CUBLAS_OP_T, CUBLAS_OP_N, 1, 1, size, &alpha, ws.d_A, static_cast(size), - ws.d_B, static_cast(size), &beta, ws.d_C, 1, static_cast(batch)); + MultiDotPointers aPtrs{}; + MultiDotPointers bPtrs{}; + if (!swap) { + for (size_t i = 0; i < n; ++i) aPtrs.ptr[i] = V[i0 + i].GetDevicePointer(); + for (size_t j = 0; j < m; ++j) bPtrs.ptr[j] = W[j].GetDevicePointer(); } else { - SU2_MPI::Error("Unsupported ScalarType in CSysVector::multiDotGPU.", CURRENT_FUNCTION); - return local; + for (size_t i = 0; i < m; ++i) aPtrs.ptr[i] = W[i].GetDevicePointer(); + for (size_t j = 0; j < n; ++j) bPtrs.ptr[j] = V[i0 + j].GetDevicePointer(); } - if (status != CUBLAS_STATUS_SUCCESS) { - SU2_MPI::Error("cuBLAS cublasgemmBatched failed in CSysVector::multiDotGPU.", CURRENT_FUNCTION); - return local; + gpuErrChk(cudaMemsetAsync(ws.d_D, 0, nm * sizeof(ScalarType))); + + constexpr unsigned int threadsPerBlock = 256; + const auto blocks = static_cast(std::min((size + threadsPerBlock - 1) / threadsPerBlock, 1024)); + const auto sharedBytes = static_cast(threadsPerBlock / 32u) * nm * sizeof(ScalarType); + + /*--- sharedBytes can exceed the default 48KB static shared-memory limit for large nm (the + * FGCRODR deflation matrix in particular, see MULTIDOT_MAX_NM); opt in to the device's larger + * "dynamic" limit once, the first time it is actually needed, rather than always paying for + * the query. ---*/ + static size_t optedInSharedBytes = 0; + if (sharedBytes > optedInSharedBytes) { + gpuErrChk(cudaFuncSetAttribute(MultiDotKernel, cudaFuncAttributeMaxDynamicSharedMemorySize, + static_cast(sharedBytes))); + optedInSharedBytes = sharedBytes; } - // copy result to host for MPI reduce - gpuErrChk(cudaMemcpy(local.data(), ws.d_local, batch * sizeof(ScalarType), cudaMemcpyDeviceToHost)); + MultiDotKernel<<>>( + aPtrs, static_cast(aCount), bPtrs, static_cast(bCount), size, ws.d_D); + + if (!swap) { + gpuErrChk(cudaMemcpyAsync(local.data(), ws.d_D, nm * sizeof(ScalarType), cudaMemcpyDeviceToHost)); + gpuErrChk(cudaStreamSynchronize(nullptr)); + } else { + /*--- D is aCount*bCount = m*n row-major (D(a,b) = ); local is n*m with + * local(i,j) = = D(j,i), i.e. local is D transposed. ---*/ + static std::vector D; + D.resize(nm); + gpuErrChk(cudaMemcpyAsync(D.data(), ws.d_D, nm * sizeof(ScalarType), cudaMemcpyDeviceToHost)); + gpuErrChk(cudaStreamSynchronize(nullptr)); + for (size_t i = 0; i < n; ++i) + for (size_t j = 0; j < m; ++j) local(i, j) = D[j * n + i]; + } + gpuErrChk(cudaGetLastError()); return local; } diff --git a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl index 30dc89f0a437..5b1c49b75d93 100644 --- a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl +++ b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl @@ -362,9 +362,9 @@ void CFVMFlowSolverBase::HybridParallelInitialization(const CConfig& confi if (SU2_MPI::GetRank() == MASTER_NODE && numRanksUsingReducer != SU2_MPI::GetSize()) { cout << "Among the ranks that use edge coloring,\n" - << " the minimum efficiency is " << minColoredParallelEff << ",\n" - << " the maximum number of colors is " << maxColoredNumColors << ",\n" - << " the minimum edge color group size is " << minColoredEdgeColorGroupSize << "." << endl; + << " the minimum efficiency is " << minColoredParallelEff << ",\n" + << " the maximum number of colors is " << maxColoredNumColors << ",\n" + << " the minimum edge color group size is " << minColoredEdgeColorGroupSize << "." << endl; } } diff --git a/config_template.cfg b/config_template.cfg index 922636bc6b40..ff33a439456c 100644 --- a/config_template.cfg +++ b/config_template.cfg @@ -1648,11 +1648,13 @@ DISCADJ_LIN_SOLVER= FGMRES % Use CUDA GPU Acceleration for FGMRES Linear Solver Only ENABLE_CUDA=NO % -% Preconditioner of the Krylov linear solver or type of smoother (ILU, LU_SGS, Q_LU_SGS, LINELET, JACOBI) -% Q_LU_SGS triggers the use of quantization to reduce the size of the sparse matrix (for compressible flow -% the matrix becomes 3x smaller relative to mixed-precision mode). This is only used by the compressible -% and incompressible solvers, others fallback silently to LU_SGS. A suitable nondimensionalization mode -% MUST be used otherwise the solver is very likely to diverge. +% Preconditioner of the Krylov linear solver or type of smoother (ILU, LU_SGS, Q_LU_SGS, LINELET, JACOBI, Q_JACOBI) +% Q_LU_SGS and Q_JACOBI trigger the use of quantization to reduce the size of the sparse matrix (for +% compressible flow the matrix becomes 3x smaller relative to mixed-precision mode). Q_IDENTITY requests the +% same quantized matrix-vector product without any actual preconditioning (equivalent to NONE otherwise). +% This is only used by the compressible and incompressible solvers, others fallback silently to +% LU_SGS/JACOBI/NONE. A suitable nondimensionalization mode MUST be used otherwise the solver is very +% likely to diverge. LINEAR_SOLVER_PREC= ILU % % Same for discrete adjoint (JACOBI or ILU), replaces LINEAR_SOLVER_PREC in SU2_*_AD codes. diff --git a/meson.build b/meson.build index a852cf7f1137..78379a455075 100644 --- a/meson.build +++ b/meson.build @@ -28,15 +28,10 @@ if get_option('enable-cuda') # the MPI link flags that -Dcustom-mpi=true relies on mpicxx to provide. add_global_arguments('-ccbin=' + meson.get_compiler('cpp').cmd_array()[0], language : 'cuda') add_global_link_arguments('-ccbin=' + meson.get_compiler('cpp').cmd_array()[0], language : 'cuda') - cuda_deps = [ - meson.get_compiler('cuda').find_library('cublas', required : true), - ] -else - cuda_deps = [] endif su2_cpp_args = [] -su2_deps = [declare_dependency(include_directories: 'externals/CLI11')] + cuda_deps +su2_deps = [declare_dependency(include_directories: 'externals/CLI11')] default_warning_flags = [] if build_machine.system() != 'windows' From b12aa0cdd48b0c891916fe439f9210f373873094 Mon Sep 17 00:00:00 2001 From: Nijso Date: Sun, 16 Aug 2026 20:38:39 +0200 Subject: [PATCH 31/61] Fix segfault parsing marker options whose names start with a digit (#2868) ## Proposed Changes boundary condition names cannot start with values, which is pretty common for CGNS meshes. ## PR Checklist *Put an X by all that apply. You can fill this out after submitting the PR. If you have any questions, don't hesitate to ask! We want to help. These are a guide for you to know what the reviewers will be looking for in your contribution.* - [x] I am submitting my contribution to the develop branch. - [x] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [x] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [x] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [ ] I have added a test case that demonstrates my contribution, if necessary. - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. Co-authored-by: Claude Opus 5 --- Common/include/option_structure.inl | 34 +++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/Common/include/option_structure.inl b/Common/include/option_structure.inl index cf85994db070..fe1b2df385ee 100644 --- a/Common/include/option_structure.inl +++ b/Common/include/option_structure.inl @@ -1101,6 +1101,21 @@ struct CStringValuesListHelper { }; // Class where the option is represented by (string, N * "some type", string, N * "some type", ...) +/*! + * \brief Whether a config token is a numeric value rather than a name. + * + * Options that interleave marker names with numbers have to tell the two apart. Testing the first + * character for a letter is not enough: mesh formats such as CGNS routinely produce boundary names + * that begin with a digit (4000_QUAD_4_Bdy6), which such a test reads as a value. Requiring the + * whole token to parse as a number is unambiguous for every name that is not purely numeric. + */ +inline bool IsNumericToken(const std::string& token) { + if (token.empty()) return false; + char* end = nullptr; + std::strtod(token.c_str(), &end); + return (end != token.c_str()) && (*end == '\0'); +} + template class COptionStringValuesList final : public COptionBase { const string name; // identifier for the option @@ -1143,15 +1158,20 @@ class COptionStringValuesList final : public COptionBase { return ""; } - /*--- Determine the number of strings: A new string is found if the first char in the option is a letter. - * This will fail in if a string starts with a number! Additionally, determine the number of values that - * are prescribed per string. ---*/ + /*--- Determine the number of strings: a field that does not parse as a number starts a new string, + * anything that does is one of its values. Testing only the first character for a letter would + * misread the digit-leading marker names that CGNS meshes produce. Additionally, determine the + * number of values that are prescribed per string. ---*/ vector num_vals_per_string; /*--- Loop through the fields of the option. ---*/ for (const auto& val : option_value) { - if (isalpha(val[0])) { + if (!IsNumericToken(val)) { num_vals_per_string.push_back(0); } else { + if (num_vals_per_string.empty()) + SU2_MPI::Error(name + string(" must begin with a marker name, but starts with the value \"") + val + + string("\". A marker whose name is purely numeric cannot be told apart from a value."), + CURRENT_FUNCTION); num_vals_per_string.back()++; } } @@ -1360,15 +1380,15 @@ class COptionWallSpecies : public COptionBase { /*--- Determine the number of markers and species per marker. * Format: marker1, TYPE1, value1, TYPE2, value2, ..., marker2, TYPE1, value1, ... - * Each marker name starts with a letter, each TYPE is an enum string (starts with letter), - * and each value is numeric. Pattern: marker, (TYPE, value) x N ---*/ + * Marker names and TYPE keywords are non-numeric fields, values are numeric. + * Pattern: marker, (TYPE, value) x N ---*/ vector marker_indices; // Indices where markers start vector species_counts; // Number of species per marker // Find all marker positions (strings starting with a letter that are not TYPE keywords) for (unsigned short i = 0; i < totalVals; i++) { - if (isalpha(option_value[i][0])) { + if (!IsNumericToken(option_value[i])) { // Check if this could be a TYPE keyword (i.e., is it in the enum map?) if (this->m.find(option_value[i]) != m.end()) { continue; // This is a TYPE keyword, not a marker From 37707bf20385b3fe12a05f6b04330c823ae03bb7 Mon Sep 17 00:00:00 2001 From: Vincent Gao Date: Thu, 20 Aug 2026 05:36:58 +0200 Subject: [PATCH 32/61] Fix parsing of Nastran small field reals in the modal structural solver (#2859) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Proposed Changes `SU2_PY/SU2_Nastran/pysu2_nastran.py` reads the GRID and CORD2R entries of a Nastran sorted bulk data echo by slicing eight character fields and then interpreting each slice *positionally*. Two helpers do that interpretation, and both are only correct when the value happens to be left flush and to fill all eight columns: ```python def nastran_float(s): if s.find("E") == -1: s = s.replace("-", "e-") s = s.replace("+", "e+") if s[0] == "e": # only removes the spurious "e" if it lands on index 0 s = s[1:] return float(s) def __checkBlankField(self, string): if string == " " * 8: # only recognises a blank field exactly eight characters wide return int(0) return int(string) ``` Neither assumption is guaranteed. The Quick Reference Guide, *Format of Bulk Data Entries -> Small Field Format*, says: > Fields 1 and 10 must be left justified. > Fields 2 through 9 do not need to be either right or left justified, although aligning the data fields is good practice. and on the same page: > Real numbers may be entered in a variety of ways. For example, the following are all acceptable versions of the real number seven: > `7.0 .7E1 0.7+1 .70+1 7.E+0 70.-1` So three families of legal input abort the FSI run before it starts: 1. **Any negative real that is not left flush.** `nastran_float(" -5.2")` builds `" e-5.2"` and raises `ValueError`. This does not need the E-less exponent form at all; a plain right justified negative coordinate is enough. pyNastran, for one, writes every small field with `'%8s'`/`'%8d'`/`'%8.Nf'`, i.e. right justified. 2. **Lower case `e` and `D` exponents.** `"700.e-2"` becomes `"700.ee-2"` and `".7D1"` is never converted, in *any* justification. Both are accepted real formats (`.1d-5` is in the OptiStruct bulk data guidelines, and the QRG notes that "a double precision specification requires a 'D' type exponent"). 3. **A GRID entry without the optional CD field.** Nastran trims the trailing blanks of each echo line, so the entry ends after X3 and `line[48:56]` is `''` -> `int('')` raises. In the tutorial mesh (`Tutorials/multiphysics/unsteady_fsi_python/Ma01/modal.f06`) CD is written explicitly, which is why this has not shown up; one column shorter and it is an empty slice. The three symptoms are one root cause, so the fix is at the two helpers rather than at the twelve GRID/CORD2R call sites, which are unchanged. `nastran_float` normalises the field first and then inserts the omitted exponent letter at the first sign after the mantissa sign, which is what the established readers do; `__checkBlankField` treats any whitespace-only or absent field as blank. `nastran_float` moved to module level so it can be tested — it was a closure inside `__readNastranMesh`. ## Verification I enumerated the QRG spellings of a real crossed with sign and with left/right/centre justification in an eight column field: **108 fields, 49 of them rejected or miscomputed before, 0 after**. The failures group as: | family | left | right | centre | |---|---|---|---| | QRG spellings of seven | 0 | 8 | 8 | | lower case `e` / `D` exponent | 9 | 13 | 11 | `SU2_PY/SU2_Nastran/test_pysu2_nastran.py` is new: it drives that table through `nastran_float`, and then builds the same little model (a CORD2R plus three GRIDs with negative coordinates, an E-less exponent, a blank CP and an omitted CD) in each justification and reads it through `__readNastranMesh`, asserting the parsed geometry does not depend on how the fields were written. 10 tests, all 10 fail on `develop` and pass here. There is no Python test harness in the repository at the moment, so this is plain `unittest` with no new dependency, run with: ``` python -m unittest discover -s SU2_PY/SU2_Nastran ``` Happy to drop the file if you would rather not start a Python test directory here. As a no-regression check I read the real tutorial `modal.f06` (124 grid points, 41 negative coordinate values) with `develop` and with this branch: the parsed coordinates, IDs, CP, CD and marker sets are bit-for-bit identical. `pre-commit run` is clean (black 22.6.0). I did not build the C++ side; nothing outside this Python module is touched. ## Related Work No related PR that I can find. #2313 is a different problem in the same file (page headers interrupting a SET1 continuation in the echo) and is not addressed here. ## PR Checklist - [x] I am submitting my contribution to the develop branch. - [x] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). Nothing compiled changes. - [x] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [x] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [x] I have added a test case that demonstrates my contribution, if necessary. - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --- SU2_PY/SU2_Nastran/pysu2_nastran.py | 35 +++-- SU2_PY/SU2_Nastran/test_pysu2_nastran.py | 184 +++++++++++++++++++++++ SU2_PY/meson.build | 3 +- TestCases/serial_regression.py | 22 +++ 4 files changed, 234 insertions(+), 10 deletions(-) create mode 100644 SU2_PY/SU2_Nastran/test_pysu2_nastran.py diff --git a/SU2_PY/SU2_Nastran/pysu2_nastran.py b/SU2_PY/SU2_Nastran/pysu2_nastran.py index 9d1b993b0547..8a65a0f296d0 100644 --- a/SU2_PY/SU2_Nastran/pysu2_nastran.py +++ b/SU2_PY/SU2_Nastran/pysu2_nastran.py @@ -33,6 +33,30 @@ import scipy.linalg as linalg from math import * +# ---------------------------------------------------------------------- +# Bulk data field parsing +# ---------------------------------------------------------------------- + + +def nastran_float(s): + """ + This method converts one small field of a bulk data entry into a float. + + Fields 2 through 9 do not need to be either right or left justified, and + the exponent may be written with an "E", with a "D", or with no letter at + all: "7.0", ".7E1", "0.7+1", ".70+1", "7.E+0" and "70.-1" all mean seven. + """ + + s = s.strip().upper().replace("D", "E") + if "E" not in s: + # The exponent letter is omitted, so the exponent starts at the first + # sign which is not the sign of the mantissa. + signs = [i for i in (s.find("+", 1), s.find("-", 1)) if i > 0] + if signs: + s = s[: min(signs)] + "E" + s[min(signs) :] + return float(s) + + # ---------------------------------------------------------------------- # Config class # ---------------------------------------------------------------------- @@ -426,14 +450,6 @@ def __readNastranMesh(self): This method reads the nastran 3D mesh. """ - def nastran_float(s): - if s.find("E") == -1: - s = s.replace("-", "e-") - s = s.replace("+", "e+") - if s[0] == "e": - s = s[1:] - return float(s) - self.nMarker = 0 self.nPoint = 0 self.nRefSys = 0 @@ -573,7 +589,8 @@ def __checkBlankField(self, string): This method considers that Nastran apply 0 when the reference system is not specified """ - if string == " " * 8: + string = string.strip() + if not string: return int(0) return int(string) diff --git a/SU2_PY/SU2_Nastran/test_pysu2_nastran.py b/SU2_PY/SU2_Nastran/test_pysu2_nastran.py new file mode 100644 index 000000000000..dd4cb0ab4ba8 --- /dev/null +++ b/SU2_PY/SU2_Nastran/test_pysu2_nastran.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python + +## \file test_pysu2_nastran.py +# \brief Tests for the bulk data parsing of the Nastran structural solver. +# \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 . + +import os +import sys +import tempfile +import unittest + +import numpy as np + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from pysu2_nastran import Solver, nastran_float + +# All the spellings of the real number seven listed in the Quick Reference +# Guide, "Format of Bulk Data Entries", plus the "D" exponent and the lower +# case forms accepted by the bulk data readers. +SEVEN = [ + "7.0", + ".7E1", + "0.7+1", + ".70+1", + "7.E+0", + "70.-1", + ".7e1", + "700.e-2", + "7.0D0", + ".7d1", + "70.-01", + "7000.-3", +] + + +def justify(value, style): + """Places a value in an eight character field, as Nastran allows.""" + + if style == "left": + return value.ljust(8) + if style == "right": + return value.rjust(8) + return value.center(8) + + +def card(fields, style, prefix=30): + """Builds one line of a small field bulk data echo.""" + + line = fields[0].ljust(8) + for field in fields[1:]: + line += justify(field, style) + return " " * prefix + line.rstrip() + "\n" + + +class TestNastranFloat(unittest.TestCase): + def test_spellings_of_seven(self): + for value in SEVEN: + for style in ("left", "right", "centre"): + field = justify(value, style) + self.assertAlmostEqual(nastran_float(field), 7.0, msg=repr(field)) + + def test_negative_spellings_of_seven(self): + for value in SEVEN: + for style in ("left", "right", "centre"): + field = justify("-" + value, style) + self.assertAlmostEqual(nastran_float(field), -7.0, msg=repr(field)) + + def test_leading_plus_is_not_an_exponent(self): + for style in ("left", "right", "centre"): + self.assertAlmostEqual(nastran_float(justify("+7.0", style)), 7.0) + + def test_omitted_exponent_letter(self): + self.assertAlmostEqual(nastran_float("1.23-5"), 1.23e-5) + self.assertAlmostEqual(nastran_float(" -1.23-5"), -1.23e-5) + self.assertAlmostEqual(nastran_float("-1.23+5"), -1.23e5) + self.assertAlmostEqual(nastran_float(" 1+5"), 1.0e5) + + def test_invalid_field_is_rejected(self): + for field in ("", " ", "abc", "1.2.3"): + with self.assertRaises(ValueError): + nastran_float(field) + + +class TestReadNastranMesh(unittest.TestCase): + """ + Reads the same model written with different, equally valid, spellings. + The parsed geometry must not depend on how the fields were written. + """ + + def mesh(self, style, omit_optional_fields=False): + cd = [] if omit_optional_fields else ["0"] + lines = [ + card( + ["CORD2R", "1", "0", "-1.5", "-2.5", "-3.5", "-1.5", "-2.5", "-0.5"], + style, + ), + card(["+", "0.5", "-2.5", "-3.5"], style), + card(["GRID", "1", "0", "-1.25", "-2.5", "3.75"] + cd, style), + # A blank CP field means the basic coordinate system. + card(["GRID", "2", "", "-1.5-2", "2.5-2", "-3.5+1"] + cd, style), + card(["GRID", "3", "1", "1.0", "2.0", "-3.0"] + cd, style), + card(["SET1", "1", "1", "2", "3"], style), + ] + handle, path = tempfile.mkstemp(suffix=".f06") + with os.fdopen(handle, "w") as mesh_file: + mesh_file.writelines(lines) + self.addCleanup(os.remove, path) + return path + + def read(self, style, omit_optional_fields=False): + solver = Solver.__new__(Solver) + solver.Mesh_file = self.mesh(style, omit_optional_fields) + solver.FSI_marker = "1" + solver.node = [] + solver.markers = {} + solver.refsystems = [] + solver._Solver__readNastranMesh() + return solver + + def coordinates(self, solver): + return np.array([point.GetCoord0().ravel() for point in solver.node]) + + def test_justification_does_not_change_the_model(self): + reference = self.coordinates(self.read("left")) + self.assertEqual(reference.shape, (3, 3)) + for style in ("right", "centre"): + np.testing.assert_allclose(self.coordinates(self.read(style)), reference) + + def test_coordinates_are_read_correctly(self): + for style in ("left", "right", "centre"): + coordinates = self.coordinates(self.read(style)) + # Point 1 is given in the basic system. + np.testing.assert_allclose(coordinates[0], [-1.25, -2.5, 3.75]) + # Point 2 uses the omitted exponent letter. + np.testing.assert_allclose(coordinates[1], [-0.015, 0.025, -35.0]) + # Point 3 is given in the reference system defined by the CORD2R, + # whose origin is (-1.5, -2.5, -3.5) and whose x axis points to + # (0.5, -2.5, -3.5), i.e. the basic x axis. + np.testing.assert_allclose(coordinates[2], [-0.5, -0.5, -6.5]) + + def test_blank_reference_system_field_is_the_basic_system(self): + for style in ("left", "right", "centre"): + solver = self.read(style) + self.assertEqual([point.GetCP() for point in solver.node], [0, 0, 1]) + + def test_optional_trailing_field_may_be_missing(self): + for style in ("left", "right", "centre"): + reference = self.coordinates(self.read(style)) + without_cd = self.coordinates(self.read(style, omit_optional_fields=True)) + np.testing.assert_allclose(without_cd, reference) + + def test_reference_system_is_read_correctly(self): + for style in ("left", "right", "centre"): + solver = self.read(style) + self.assertEqual(len(solver.refsystems), 1) + system = solver.refsystems[0] + self.assertEqual(system.GetCID(), 1) + np.testing.assert_allclose(system.GetOrigin().ravel(), [-1.5, -2.5, -3.5]) + np.testing.assert_allclose(system.GetRotMatrix(), np.eye(3), atol=1e-12) + + +if __name__ == "__main__": + unittest.main() diff --git a/SU2_PY/meson.build b/SU2_PY/meson.build index ced812903468..7aadaf162c8d 100644 --- a/SU2_PY/meson.build +++ b/SU2_PY/meson.build @@ -68,7 +68,8 @@ install_data(['FSI_tools/__init__.py', install_dir: join_paths(get_option('bindir'), 'FSI_tools')) install_data(['SU2_Nastran/__init__.py', - 'SU2_Nastran/pysu2_nastran.py'], + 'SU2_Nastran/pysu2_nastran.py', + 'SU2_Nastran/test_pysu2_nastran.py'], install_dir: join_paths(get_option('bindir'), 'SU2_Nastran')) install_subdir(['../externals/FADO'], install_dir: get_option('bindir')) diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index 0d8e58e79d98..f49e342a519a 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -26,6 +26,8 @@ # License along with SU2. If not, see . from __future__ import print_function, division, absolute_import +import os +import subprocess import sys from TestCase import TestCase from TestCase import parse_args @@ -1257,6 +1259,26 @@ def main(): pass_list = [ test.run_test(args.tsan, args.asan) for test in test_list ] + # Nastran bulk data parser unit tests + nastran_parser = TestCase('pysu2_nastran') + # The CI container runs this script from a copied tests/TestCases tree, so + # the repo-relative path does not exist there; use the installed copy that + # SU2_RUN points to and fall back to the source tree for local runs. + nastran_test = os.path.join( + os.environ.get('SU2_RUN', ''), + 'SU2_Nastran', + 'test_pysu2_nastran.py', + ) + if not os.path.isfile(nastran_test): + nastran_test = os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + 'SU2_PY', + 'SU2_Nastran', + 'test_pysu2_nastran.py', + ) + pass_list.append(subprocess.call([sys.executable, nastran_test]) == 0) + test_list.append(nastran_parser) + ###################################### ### RUN SU2_GEO TESTS ### From 81ce6a68f9cf05259d61bad6d46abcd2e5fb3594 Mon Sep 17 00:00:00 2001 From: Josh Kelly <81244680+joshkellyjak@users.noreply.github.com> Date: Fri, 21 Aug 2026 11:02:46 +0200 Subject: [PATCH 33/61] Multizone adjoints for turbomachinery (#2446) ## Proposed Changes This is a cleaned up PR of the fixes needed for multizone adjoints for turbomachinery from the previous PR of @oleburghardt and I's work. This PR refactors the mixing plane implementation and some of the turbo specific features to make them tape consistent. ## Related Work Now closed PR #2317 ## PR Checklist *Put an X by all that apply. You can fill this out after submitting the PR. If you have any questions, don't hesitate to ask! We want to help. These are a guide for you to know what the reviewers will be looking for in your contribution.* - [ X ] I am submitting my contribution to the develop branch. - [x] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [x] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [x] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [x] I have added a test case that demonstrates my contribution, if necessary. - [x] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --------- Co-authored-by: Joshua Kelly Co-authored-by: Josh Kelly Co-authored-by: Ole Burghardt Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> --- Common/include/CConfig.hpp | 19 +- Common/include/geometry/CGeometry.hpp | 5 +- .../interface_interpolation/CInterpolator.hpp | 15 +- .../CInterpolatorFactory.hpp | 2 +- .../CIsoparametric.hpp | 2 +- .../interface_interpolation/CMirror.hpp | 2 +- .../interface_interpolation/CMixingPlane.hpp | 101 ++++ .../CNearestNeighbor.hpp | 2 +- .../CRadialBasisFunction.hpp | 2 +- .../interface_interpolation/CSlidingMesh.hpp | 2 +- Common/include/option_structure.hpp | 8 +- Common/src/CConfig.cpp | 22 + Common/src/geometry/CGeometry.cpp | 8 +- Common/src/geometry/CPhysicalGeometry.cpp | 7 +- .../CInterpolatorFactory.cpp | 60 +-- .../CIsoparametric.cpp | 4 +- .../src/interface_interpolation/CMirror.cpp | 4 +- .../interface_interpolation/CMixingPlane.cpp | 239 ++++++++++ .../CNearestNeighbor.cpp | 4 +- .../CRadialBasisFunction.cpp | 4 +- .../interface_interpolation/CSlidingMesh.cpp | 4 +- .../src/interface_interpolation/meson.build | 3 +- SU2_CFD/include/drivers/CDriver.hpp | 16 +- SU2_CFD/include/drivers/CDriverBase.hpp | 3 +- SU2_CFD/include/drivers/CMultizoneDriver.hpp | 9 - SU2_CFD/include/interfaces/CInterface.hpp | 67 +-- .../interfaces/cfd/CMixingPlaneInterface.hpp | 36 +- SU2_CFD/include/iteration/CFluidIteration.hpp | 7 +- SU2_CFD/include/iteration/CIteration.hpp | 20 +- SU2_CFD/include/output/CFlowCompOutput.hpp | 12 +- SU2_CFD/include/output/CFlowOutput.hpp | 2 +- SU2_CFD/include/output/COutput.hpp | 10 +- SU2_CFD/include/output/CTurboOutput.hpp | 27 +- SU2_CFD/include/solvers/CDiscAdjSolver.hpp | 2 +- SU2_CFD/include/solvers/CEulerSolver.hpp | 159 +++---- SU2_CFD/include/solvers/CSolver.hpp | 109 ++--- .../src/drivers/CDiscAdjMultizoneDriver.cpp | 32 +- .../src/drivers/CDiscAdjSinglezoneDriver.cpp | 1 + SU2_CFD/src/drivers/CDriver.cpp | 277 ++++++----- SU2_CFD/src/drivers/CMultizoneDriver.cpp | 56 +-- SU2_CFD/src/integration/CIntegration.cpp | 10 - .../src/integration/CMultiGridIntegration.cpp | 15 - SU2_CFD/src/interfaces/CInterface.cpp | 442 +----------------- .../cfd/CConservativeVarsInterface.cpp | 1 + .../interfaces/cfd/CMixingPlaneInterface.cpp | 189 +++++--- .../src/interfaces/cfd/CSlidingInterface.cpp | 1 + .../fsi/CDiscAdjFlowTractionInterface.cpp | 1 + .../fsi/CDisplacementsInterface.cpp | 1 + .../interfaces/fsi/CFlowTractionInterface.cpp | 1 + .../src/iteration/CDiscAdjFluidIteration.cpp | 34 +- SU2_CFD/src/iteration/CFluidIteration.cpp | 60 +-- SU2_CFD/src/iteration/CIteration.cpp | 20 + SU2_CFD/src/iteration/CTurboIteration.cpp | 46 +- SU2_CFD/src/numerics/flow/convection/roe.cpp | 2 + SU2_CFD/src/numerics/flow/flow_diffusion.cpp | 2 + SU2_CFD/src/output/CFlowCompOutput.cpp | 36 +- SU2_CFD/src/output/CFlowOutput.cpp | 2 + SU2_CFD/src/output/COutput.cpp | 10 +- SU2_CFD/src/output/CTurboOutput.cpp | 97 ++-- SU2_CFD/src/solvers/CDiscAdjSolver.cpp | 87 ++-- SU2_CFD/src/solvers/CEulerSolver.cpp | 291 +++++++++--- SU2_CFD/src/solvers/CTurbSASolver.cpp | 5 +- SU2_CFD/src/solvers/CTurbSSTSolver.cpp | 4 +- .../inv_NACA0012_discadj_multizone.cfg | 1 - .../disc_adj_ffi/mixing_plane/circles.cfg | 213 +++++++++ .../disc_adj_ffi/mixing_plane/zone_1.cfg | 16 + .../disc_adj_ffi/mixing_plane/zone_2.cfg | 16 + .../sliding_interface/circles.cfg | 200 ++++++++ .../disc_adj_fsi/dyn_fsi/grad_dv.opt.ref | 16 +- .../dyn_fsi/grad_dv_aarch64.opt.ref | 16 +- .../axial_stage_2D/Axial_stage2D.cfg | 188 ++++++++ .../axial_stage_2D/zone_1.cfg | 15 + .../axial_stage_2D/zone_2.cfg | 16 + .../transonic_stator_2D/transonic_stator.cfg | 73 ++- TestCases/hybrid_regression.py | 4 +- TestCases/parallel_regression.py | 12 +- TestCases/parallel_regression_AD.py | 41 +- TestCases/serial_regression.py | 14 +- TestCases/serial_regression_AD.py | 51 +- 79 files changed, 2214 insertions(+), 1399 deletions(-) create mode 100644 Common/include/interface_interpolation/CMixingPlane.hpp create mode 100644 Common/src/interface_interpolation/CMixingPlane.cpp create mode 100755 TestCases/disc_adj_ffi/mixing_plane/circles.cfg create mode 100644 TestCases/disc_adj_ffi/mixing_plane/zone_1.cfg create mode 100644 TestCases/disc_adj_ffi/mixing_plane/zone_2.cfg create mode 100755 TestCases/disc_adj_ffi/sliding_interface/circles.cfg create mode 100755 TestCases/disc_adj_turbomachinery/axial_stage_2D/Axial_stage2D.cfg create mode 100644 TestCases/disc_adj_turbomachinery/axial_stage_2D/zone_1.cfg create mode 100644 TestCases/disc_adj_turbomachinery/axial_stage_2D/zone_2.cfg diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index 3284f9527366..8e0cc89cb379 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -2013,12 +2013,6 @@ class CConfig { */ su2double GetPressure_FreeStreamND(void) const { return Pressure_FreeStreamND; } - /*! - * \brief Get a reference to the non-dimensionalized freestream pressure (used for AD tracking). - * \return Reference to non-dimensionalized freestream pressure. - */ - su2double& GetPressure_FreeStreamND(void) { return Pressure_FreeStreamND; } - /*! * \brief Get the value of the thermodynamic pressure. * \return Thermodynamic pressure. @@ -2044,12 +2038,6 @@ class CConfig { */ su2double GetTemperature_FreeStreamND(void) const { return Temperature_FreeStreamND; } - /*! - * \brief Get a reference to the non-dimensionalized freestream temperature (used for AD tracking). - * \return Reference to non-dimensionalized freestream temperature. - */ - su2double& GetTemperature_FreeStreamND(void) { return Temperature_FreeStreamND; } - /*! * \brief Get the value of the non-dimensionalized vibrational-electronic freestream temperature. * \return Non-dimensionalized vibrational-electronic freestream temperature. @@ -10184,6 +10172,13 @@ class CConfig { */ short FindInterfaceMarker(unsigned short iInterface) const; + /*! + * \brief Find the marker index (if any) that is part of a mixing plane interface pair. + * \param[in] nMarker - Number of the marker in a zone being tested, starting at 0. + * \return value > 1 if (on this mpi rank) the zone defined by config is part of the mixing plane. + */ + short FindMixingPlaneInterfaceMarker(unsigned short nMarker, unsigned short iMarkerInt) const; + /*! * \brief Get whether or not to save solution data to libROM. * \return True if specified in config file. diff --git a/Common/include/geometry/CGeometry.hpp b/Common/include/geometry/CGeometry.hpp index 97d3f8455376..9d3e574b4328 100644 --- a/Common/include/geometry/CGeometry.hpp +++ b/Common/include/geometry/CGeometry.hpp @@ -894,7 +894,7 @@ class CGeometry { inline virtual void GatherInOutAverageValues(CConfig* config, bool allocate) {} /*! - * \brief Store all the turboperformance in the solver in ZONE_0. + * \brief Store all the turboperformance in the solver in final zone. * \param[in] donor_geometry - Solution from the donor mesh. * \param[in] target_geometry - Solution from the target mesh. * \param[in] donorZone - counter of the donor solution @@ -1978,7 +1978,8 @@ class CGeometry { * \param[in] config_container - Definition of the particular problem. * \param[in] geometry_container - Geometrical definition of the problem. */ - static void ComputeWallDistance(const CConfig* const* config_container, CGeometry**** geometry_container); + static void ComputeWallDistance(const CConfig* const* config_container, CGeometry**** geometry_container, + const int record_zone = -1); /*! * \brief Set the amount of nonconvex elements in the mesh. diff --git a/Common/include/interface_interpolation/CInterpolator.hpp b/Common/include/interface_interpolation/CInterpolator.hpp index 49d6841abe61..045e28c8d3fd 100644 --- a/Common/include/interface_interpolation/CInterpolator.hpp +++ b/Common/include/interface_interpolation/CInterpolator.hpp @@ -99,7 +99,13 @@ class CInterpolator { coefficient.resize(nDonor); } }; - vector > targetVertices; /*! \brief Donor information per marker per vertex of the target. */ + vector> targetVertices; /*! \brief Donor information per marker per vertex of the target. */ + + struct CSpanDonorInfo { + size_t donorSpan; // Refers to donor span + su2double coefficient; // Refers to coefficient + }; + vector> targetSpans; // > /*! * \brief Constructor of the class. @@ -125,13 +131,18 @@ class CInterpolator { * \note Main method that derived classes must implement. * \param[in] config - Definition of the particular problem. */ - virtual void SetTransferCoeff(const CConfig* const* config) = 0; + virtual void SetTransferCoeff(CGeometry**** geometry, const CConfig* const* config) = 0; /*! * \brief Print information about the interpolation. */ virtual void PrintStatistics(void) const {} + /*! + * \brief Write mixing plane interpolation details to file + */ + inline virtual void WriteInterpolationDetails(const string& filename, const CConfig* const* config){}; + /*! * \brief Check whether an interface should be processed or not, i.e. if it is part of the zones. * \param[in] val_markDonor - Marker tag from donor zone. diff --git a/Common/include/interface_interpolation/CInterpolatorFactory.hpp b/Common/include/interface_interpolation/CInterpolatorFactory.hpp index fdc76ceccf47..e3c86cff5a32 100644 --- a/Common/include/interface_interpolation/CInterpolatorFactory.hpp +++ b/Common/include/interface_interpolation/CInterpolatorFactory.hpp @@ -43,5 +43,5 @@ namespace CInterpolatorFactory { */ CInterpolator* CreateInterpolator(CGeometry**** geometry_container, const CConfig* const* config, const CInterpolator* transpInterpolator, unsigned iZone, unsigned jZone, - bool verbose = true); + bool mixing_plane, bool verbose = true); } // namespace CInterpolatorFactory diff --git a/Common/include/interface_interpolation/CIsoparametric.hpp b/Common/include/interface_interpolation/CIsoparametric.hpp index 0b540f4e66c0..6b05dc851be0 100644 --- a/Common/include/interface_interpolation/CIsoparametric.hpp +++ b/Common/include/interface_interpolation/CIsoparametric.hpp @@ -64,7 +64,7 @@ class CIsoparametric final : public CInterpolator { * \brief Set up transfer matrix defining relation between two meshes * \param[in] config - Definition of the particular problem. */ - void SetTransferCoeff(const CConfig* const* config) override; + void SetTransferCoeff(CGeometry**** geometry, const CConfig* const* config) override; /*! * \brief Print information about the interpolation. diff --git a/Common/include/interface_interpolation/CMirror.hpp b/Common/include/interface_interpolation/CMirror.hpp index 1b569483a802..0f0bb2af4f80 100644 --- a/Common/include/interface_interpolation/CMirror.hpp +++ b/Common/include/interface_interpolation/CMirror.hpp @@ -54,5 +54,5 @@ class CMirror final : public CInterpolator { * \brief Set up transfer matrix defining relation between two meshes * \param[in] config - Definition of the particular problem. */ - void SetTransferCoeff(const CConfig* const* config) override; + void SetTransferCoeff(CGeometry**** geometry, const CConfig* const* config) override; }; diff --git a/Common/include/interface_interpolation/CMixingPlane.hpp b/Common/include/interface_interpolation/CMixingPlane.hpp new file mode 100644 index 000000000000..4b0b256bb738 --- /dev/null +++ b/Common/include/interface_interpolation/CMixingPlane.hpp @@ -0,0 +1,101 @@ +/*! + * \file CMixingPlane.hpp + * \brief Header of mixing plane interpolation methods. + * \author J. Kelly + * \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-2025, 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 . + */ + +#pragma once +#include "CInterpolator.hpp" +#include "../option_structure.hpp" + +/*! + * \brief Mixing plane interpolation. + * \note This contains several interpolation methods used in the mixing plane interpolation + * and enables the mixing state class structure for proper recording in AD mode + * \ingroup Interfaces + */ +class CMixingPlane final : public CInterpolator { + public: + CMixingPlane(CGeometry**** geometry_container, const CConfig* const* config, unsigned int iZone, unsigned int jZone); + + void SetTransferCoeff(CGeometry**** geometry, const CConfig* const* config) override; + + inline CSpanDonorInfo MapMatchingSpan(unsigned short iSpanTarget) { return {iSpanTarget, 0.0}; } + + inline CSpanDonorInfo MapNearestSpan(const su2double iSpanTargetValue, const su2double* spanValuesDonor, + unsigned long nSpanDonor) { + unsigned short tSpan = 0; // Nearest donor span index + auto minDist = std::numeric_limits::max(); + + for (auto iSpanDonor = 0u; iSpanDonor < nSpanDonor - 1; iSpanDonor++) { + const auto dist = abs(iSpanTargetValue - spanValuesDonor[iSpanDonor]); + if (dist < minDist) { + minDist = dist; + tSpan = iSpanDonor; + } + } + return {tSpan, 0.0}; + }; + + inline CSpanDonorInfo MapLinearInterpolationSpan(const su2double iSpanTargetValue, const su2double* spanValuesDonor, + unsigned long nSpanDonor, int rank) { + unsigned short kSpan = 0; // Lower bound donor span for interpolation + auto minDist = std::numeric_limits::max(); + su2double coeff = 0.0; // Interpolation coefficient + + if (iSpanTargetValue <= spanValuesDonor[0]) { + PrintClampingWarning(rank, true); + return {0, 0.0}; + } + + if (iSpanTargetValue >= spanValuesDonor[nSpanDonor - 1]) { + PrintClampingWarning(rank, false); + return {nSpanDonor - 1, 0.0}; + } + + for (auto iSpanDonor = 0u; iSpanDonor < nSpanDonor - 1; iSpanDonor++) { + const auto dist = abs(iSpanTargetValue - spanValuesDonor[iSpanDonor]); + if (dist < minDist && iSpanTargetValue >= spanValuesDonor[iSpanDonor]) { + kSpan = iSpanDonor; + minDist = dist; + } + } + coeff = (iSpanTargetValue - spanValuesDonor[kSpan]) / (spanValuesDonor[kSpan + 1] - spanValuesDonor[kSpan]); + return {kSpan, coeff}; + }; + + inline void PrintClampingWarning(int rank, bool atHub) { + if (rank != MASTER_NODE) return; + cout << "Warning! Target spans exist outside the bounds of donor spans! Clamping interpolator..." << endl; + cout << (atHub ? "This is an issue at the hub." : "This is an issue at the shroud.") << endl; + cout << "Setting coeff = 0.0 and transferring endwall value!" << endl; + }; + + /*! + * \brief Write interpolation details to file. + * \param[in] filename - Name of output file. + * \param[in] config - Configuration for all zones. + */ + void WriteInterpolationDetails(const string& filename, const CConfig* const* config) override; +}; diff --git a/Common/include/interface_interpolation/CNearestNeighbor.hpp b/Common/include/interface_interpolation/CNearestNeighbor.hpp index 12e5cafd73bd..f75507a6f4d9 100644 --- a/Common/include/interface_interpolation/CNearestNeighbor.hpp +++ b/Common/include/interface_interpolation/CNearestNeighbor.hpp @@ -63,7 +63,7 @@ class CNearestNeighbor final : public CInterpolator { * \brief Set up transfer matrix defining relation between two meshes. * \param[in] config - Definition of the particular problem. */ - void SetTransferCoeff(const CConfig* const* config) override; + void SetTransferCoeff(CGeometry**** geometry, const CConfig* const* config) override; /*! * \brief Print interpolation statistics. diff --git a/Common/include/interface_interpolation/CRadialBasisFunction.hpp b/Common/include/interface_interpolation/CRadialBasisFunction.hpp index 97a3e8524548..b783b10d882d 100644 --- a/Common/include/interface_interpolation/CRadialBasisFunction.hpp +++ b/Common/include/interface_interpolation/CRadialBasisFunction.hpp @@ -56,7 +56,7 @@ class CRadialBasisFunction final : public CInterpolator { * \brief Set up transfer matrix defining relation between two meshes * \param[in] config - Definition of the particular problem. */ - void SetTransferCoeff(const CConfig* const* config) override; + void SetTransferCoeff(CGeometry**** geometry, const CConfig* const* config) override; /*! * \brief Print information about the interpolation. diff --git a/Common/include/interface_interpolation/CSlidingMesh.hpp b/Common/include/interface_interpolation/CSlidingMesh.hpp index 328052bd167f..34474bad910a 100644 --- a/Common/include/interface_interpolation/CSlidingMesh.hpp +++ b/Common/include/interface_interpolation/CSlidingMesh.hpp @@ -49,7 +49,7 @@ class CSlidingMesh final : public CInterpolator { * \brief Set up transfer matrix defining relation between two meshes * \param[in] config - Definition of the particular problem. */ - void SetTransferCoeff(const CConfig* const* config) override; + void SetTransferCoeff(CGeometry**** geometry, const CConfig* const* config) override; private: /*! diff --git a/Common/include/option_structure.hpp b/Common/include/option_structure.hpp index c6bddc15aa6e..6d97299d09d0 100644 --- a/Common/include/option_structure.hpp +++ b/Common/include/option_structure.hpp @@ -2147,6 +2147,9 @@ enum ENUM_OBJECTIVE { TOPOL_DISCRETENESS = 63, /*!< \brief Measure of the discreteness of the current topology. */ TOPOL_COMPLIANCE = 64, /*!< \brief Measure of the discreteness of the current topology. */ STRESS_PENALTY = 65, /*!< \brief Penalty function of VM stresses above a maximum value. */ + ENTROPY_GENERATION = 80, /*!< \brief Entropy generation turbomachinery objective function. */ + TOTAL_PRESSURE_LOSS = 81, /*!< \brief Total pressure loss turbomachinery objective function. */ + KINETIC_ENERGY_LOSS = 82 /*!< \breif Kinetic energy loss coefficient turbomachinery objective function. */ }; static const MapType Objective_Map = { MakePair("DRAG", DRAG_COEFFICIENT) @@ -2189,6 +2192,9 @@ static const MapType Objective_Map = { MakePair("TOPOL_DISCRETENESS", TOPOL_DISCRETENESS) MakePair("TOPOL_COMPLIANCE", TOPOL_COMPLIANCE) MakePair("STRESS_PENALTY", STRESS_PENALTY) + MakePair("ENTROPY_GENERATION", ENTROPY_GENERATION) + MakePair("TOTAL_PRESSURE_LOSS", TOTAL_PRESSURE_LOSS) + MakePair("KINETIC_ENERGY_LOSS", KINETIC_ENERGY_LOSS) }; /*! @@ -2681,7 +2687,7 @@ enum class CHECK_TAPE_VARIABLES { }; static const MapType CheckTapeVariables_Map = { MakePair("SOLVER_VARIABLES", CHECK_TAPE_VARIABLES::SOLVER_VARIABLES) - MakePair("SOLVER_VARIABLES_AND_MESH_COORDINATES", CHECK_TAPE_VARIABLES::MESH_COORDINATES) + MakePair("MESH_COORDINATES", CHECK_TAPE_VARIABLES::MESH_COORDINATES) }; enum class RECORDING { diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index c2633f84ec4b..f272a84ab6fb 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -7140,6 +7140,7 @@ void CConfig::SetOutput(SU2_COMPONENT val_software, unsigned short val_izone) { case TOPOL_DISCRETENESS: cout << "Topology discreteness objective function." << endl; break; case TOPOL_COMPLIANCE: cout << "Topology compliance objective function." << endl; break; case STRESS_PENALTY: cout << "Stress penalty objective function." << endl; break; + case ENTROPY_GENERATION: cout << "Entropy generation objective function." << endl; break; } } else { @@ -8748,6 +8749,7 @@ CConfig::~CConfig() { delete [] nBlades; delete [] FreeStreamTurboNormal; + } /*--- Input is the filename base, output is the completed filename. ---*/ @@ -8891,6 +8893,9 @@ string CConfig::GetObjFunc_Extension(string val_filename) const { case TOPOL_DISCRETENESS: AdjExt = "_topdisc"; break; case TOPOL_COMPLIANCE: AdjExt = "_topcomp"; break; case STRESS_PENALTY: AdjExt = "_stress"; break; + case ENTROPY_GENERATION: AdjExt = "_entg"; break; + case TOTAL_PRESSURE_LOSS: AdjExt = "_tot_press_loss"; break; + case KINETIC_ENERGY_LOSS: AdjExt = "_kin_en_loss"; break; } } else{ @@ -10191,6 +10196,23 @@ short CConfig::FindInterfaceMarker(unsigned short iInterface) const { return -1; } +short CConfig::FindMixingPlaneInterfaceMarker(unsigned short nMarker, unsigned short iMarkerInt) const { + short mark; + for (auto iMarker = 0; iMarker < nMarker; iMarker++){ + /*--- If the tag GetMarker_All_MixingPlaneInterface equals the index we are looping at ---*/ + if (GetMarker_All_MixingPlaneInterface(iMarker) == iMarkerInt){ + /*--- We have identified the local index of the marker ---*/ + /*--- Store the identifier for the marker ---*/ + mark = iMarker; + /*--- Exit the for loop: we have found the local index for Mixing-Plane interface ---*/ + return mark; + } + /*--- If the tag hasn't matched any tag within the donor markers ---*/ + mark = -1; + } + return mark; +} + void CConfig::GEMM_Tick(double *val_start_time) const { #ifdef PROFILE diff --git a/Common/src/geometry/CGeometry.cpp b/Common/src/geometry/CGeometry.cpp index 45153456086a..360da1aaaa1e 100644 --- a/Common/src/geometry/CGeometry.cpp +++ b/Common/src/geometry/CGeometry.cpp @@ -4540,7 +4540,8 @@ su2double NearestNeighborDistance(CGeometry* geometry, const CConfig* config, co } } // namespace -void CGeometry::ComputeWallDistance(const CConfig* const* config_container, CGeometry**** geometry_container) { +void CGeometry::ComputeWallDistance(const CConfig* const* config_container, CGeometry**** geometry_container, + const int record_zone) { int nZone = config_container[ZONE_0]->GetnZone(); bool allEmpty = true; vector wallDistanceNeeded(nZone, false); @@ -4608,6 +4609,11 @@ void CGeometry::ComputeWallDistance(const CConfig* const* config_container, CGeo } for (int iZone = 0; iZone < nZone; iZone++) { + /*--- When recording for a specific zone, only compute nearest-neighbor distances (which read vertex + * normals) for that zone. Reading normals from other zones at this tape position would create + * cross-zone AD dependencies before those zones have had their geometry updated. ---*/ + if (record_zone >= 0 && iZone != record_zone) continue; + /*--- For the FEM solver, we use a different mesh structure ---*/ MAIN_SOLVER kindSolver = config_container[iZone]->GetKind_Solver(); if (!wallDistanceNeeded[iZone] || kindSolver == MAIN_SOLVER::FEM_LES || kindSolver == MAIN_SOLVER::FEM_RANS) { diff --git a/Common/src/geometry/CPhysicalGeometry.cpp b/Common/src/geometry/CPhysicalGeometry.cpp index 199c8cec03f6..f7d2d36f198c 100644 --- a/Common/src/geometry/CPhysicalGeometry.cpp +++ b/Common/src/geometry/CPhysicalGeometry.cpp @@ -52,6 +52,8 @@ #include "../../include/geometry/primal_grid/CPrism.hpp" #include "../../include/geometry/primal_grid/CVertexMPI.hpp" +#include "../../../Common/include/tracy_structure.hpp" + #include #include #include @@ -5745,9 +5747,9 @@ void CPhysicalGeometry::SetTurboVertex(CConfig* config, unsigned short val_iZone } } if (marker_flag == INFLOW) { - multizone_filename = "TURBOMACHINERY/spanwise_division_inflow.dat"; + multizone_filename = "TURBOMACHINERY/spanwise_division_inflow"; } else { - multizone_filename = "TURBOMACHINERY/spanwise_division_outflow.dat"; + multizone_filename = "TURBOMACHINERY/spanwise_division_outflow"; } char buffer[50]; @@ -10222,7 +10224,6 @@ void CPhysicalGeometry::SetWallDistance(CADTElemClass* WallADT, const CConfig* c if (!WallADT->IsEmpty()) { /*--- Solid wall boundary nodes are present. Compute the wall distance for all nodes. ---*/ - SU2_OMP_PARALLEL { CPHYSGEO_PARFOR for (unsigned long iPoint = 0; iPoint < GetnPoint(); ++iPoint) { diff --git a/Common/src/interface_interpolation/CInterpolatorFactory.cpp b/Common/src/interface_interpolation/CInterpolatorFactory.cpp index f17d2d3af941..4364b44e16bc 100644 --- a/Common/src/interface_interpolation/CInterpolatorFactory.cpp +++ b/Common/src/interface_interpolation/CInterpolatorFactory.cpp @@ -32,11 +32,12 @@ #include "../../include/interface_interpolation/CNearestNeighbor.hpp" #include "../../include/interface_interpolation/CRadialBasisFunction.hpp" #include "../../include/interface_interpolation/CSlidingMesh.hpp" +#include "../../include/interface_interpolation/CMixingPlane.hpp" namespace CInterpolatorFactory { CInterpolator* CreateInterpolator(CGeometry**** geometry_container, const CConfig* const* config, const CInterpolator* transpInterpolator, unsigned iZone, unsigned jZone, - bool verbose) { + bool mixing_plane, bool verbose) { CInterpolator* interpolator = nullptr; /*--- Only print information on master node. ---*/ @@ -47,36 +48,41 @@ CInterpolator* CreateInterpolator(CGeometry**** geometry_container, const CConfi if (verbose) cout << " Setting coupling "; - /*--- Conservative interpolation is not applicable to the sliding - * mesh approach so that case is handled first. Then we either - * return a CMirror if the target requires conservative inter- - * polation, or the type of interpolator defined by "type". ---*/ + if (mixing_plane) { + if (verbose) cout << "using a mixing plane interpolation." << endl; + interpolator = new CMixingPlane(geometry_container, config, iZone, jZone); + } else { // Really awful thing to do + /*--- Conservative interpolation is not applicable to the sliding + * mesh approach so that case is handled first. Then we either + * return a CMirror if the target requires conservative inter- + * polation, or the type of interpolator defined by "type". ---*/ - if (type == INTERFACE_INTERPOLATOR::WEIGHTED_AVERAGE) { - if (verbose) cout << "using a sliding mesh approach." << endl; - interpolator = new CSlidingMesh(geometry_container, config, iZone, jZone); - } else if (config[jZone]->GetConservativeInterpolation()) { - if (verbose) cout << "using the mirror approach, \"transposing\" coefficients from opposite mesh." << endl; - interpolator = new CMirror(geometry_container, config, transpInterpolator, iZone, jZone); - } else { - switch (type) { - case INTERFACE_INTERPOLATOR::ISOPARAMETRIC: - if (verbose) cout << "using the isoparametric approach." << endl; - interpolator = new CIsoparametric(geometry_container, config, iZone, jZone); - break; + if (type == INTERFACE_INTERPOLATOR::WEIGHTED_AVERAGE) { + if (verbose) cout << "using a sliding mesh approach." << endl; + interpolator = new CSlidingMesh(geometry_container, config, iZone, jZone); + } else if (config[jZone]->GetConservativeInterpolation()) { + if (verbose) cout << "using the mirror approach, \"transposing\" coefficients from opposite mesh." << endl; + interpolator = new CMirror(geometry_container, config, transpInterpolator, iZone, jZone); + } else { + switch (type) { + case INTERFACE_INTERPOLATOR::ISOPARAMETRIC: + if (verbose) cout << "using the isoparametric approach." << endl; + interpolator = new CIsoparametric(geometry_container, config, iZone, jZone); + break; - case INTERFACE_INTERPOLATOR::NEAREST_NEIGHBOR: - if (verbose) cout << "using a nearest neighbor approach." << endl; - interpolator = new CNearestNeighbor(geometry_container, config, iZone, jZone); - break; + case INTERFACE_INTERPOLATOR::NEAREST_NEIGHBOR: + if (verbose) cout << "using a nearest neighbor approach." << endl; + interpolator = new CNearestNeighbor(geometry_container, config, iZone, jZone); + break; - case INTERFACE_INTERPOLATOR::RADIAL_BASIS_FUNCTION: - if (verbose) cout << "using a radial basis function approach." << endl; - interpolator = new CRadialBasisFunction(geometry_container, config, iZone, jZone); - break; + case INTERFACE_INTERPOLATOR::RADIAL_BASIS_FUNCTION: + if (verbose) cout << "using a radial basis function approach." << endl; + interpolator = new CRadialBasisFunction(geometry_container, config, iZone, jZone); + break; - default: - SU2_MPI::Error("Unknown type of interpolation.", CURRENT_FUNCTION); + default: + SU2_MPI::Error("Unknown type of interpolation.", CURRENT_FUNCTION); + } } } diff --git a/Common/src/interface_interpolation/CIsoparametric.cpp b/Common/src/interface_interpolation/CIsoparametric.cpp index 83e737e7a72a..797cbd0a9c7a 100644 --- a/Common/src/interface_interpolation/CIsoparametric.cpp +++ b/Common/src/interface_interpolation/CIsoparametric.cpp @@ -37,7 +37,7 @@ using namespace GeometryToolbox; CIsoparametric::CIsoparametric(CGeometry**** geometry_container, const CConfig* const* config, unsigned int iZone, unsigned int jZone) : CInterpolator(geometry_container, config, iZone, jZone) { - SetTransferCoeff(config); + SetTransferCoeff(geometry_container, config); } void CIsoparametric::PrintStatistics() const { @@ -46,7 +46,7 @@ void CIsoparametric::PrintStatistics() const { << " Interpolation clipped for " << ErrorCounter << " (" << ErrorRate << "%) target vertices." << endl; } -void CIsoparametric::SetTransferCoeff(const CConfig* const* config) { +void CIsoparametric::SetTransferCoeff(CGeometry**** geometry, const CConfig* const* config) { const su2double matchingVertexTol = 1e-12; // 1um^2 const int nProcessor = size; diff --git a/Common/src/interface_interpolation/CMirror.cpp b/Common/src/interface_interpolation/CMirror.cpp index ed4236ce6a02..36f4b3ed64ae 100644 --- a/Common/src/interface_interpolation/CMirror.cpp +++ b/Common/src/interface_interpolation/CMirror.cpp @@ -40,10 +40,10 @@ CMirror::CMirror(CGeometry**** geometry_container, const CConfig* const* config, to_string(iZone) + string(" and ") + to_string(jZone) + string("."), CURRENT_FUNCTION); } - SetTransferCoeff(config); + SetTransferCoeff(geometry_container, config); } -void CMirror::SetTransferCoeff(const CConfig* const* config) { +void CMirror::SetTransferCoeff(CGeometry**** geometry, const CConfig* const* config) { const int nProcessor = size; vector allNumVertexTarget(nProcessor); diff --git a/Common/src/interface_interpolation/CMixingPlane.cpp b/Common/src/interface_interpolation/CMixingPlane.cpp new file mode 100644 index 000000000000..b7f06b51e62d --- /dev/null +++ b/Common/src/interface_interpolation/CMixingPlane.cpp @@ -0,0 +1,239 @@ +/*! + * \file CMixingPlane.cpp + * \brief Implementation of mixing plane interpolation methods. + * \author J. Kelly + * \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-2025, 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 "../../include/interface_interpolation/CMixingPlane.hpp" +#include "../../include/CConfig.hpp" +#include "../../include/geometry/CGeometry.hpp" +#include "../../include/toolboxes/geometry_toolbox.hpp" + +CMixingPlane::CMixingPlane(CGeometry**** geometry_container, const CConfig* const* config, unsigned int iZone, + unsigned int jZone) + : CInterpolator(geometry_container, config, iZone, jZone) { + SetTransferCoeff(geometry_container, config); +} + +void CMixingPlane::SetTransferCoeff(CGeometry**** geometry, const CConfig* const* config) { + const auto nMarkerInt = config[donorZone]->GetnMarker_MixingPlaneInterface() / 2; + const auto nDim = donor_geometry->GetnDim(); + + const auto donor_config = config[donorZone]; + const auto target_config = config[targetZone]; + + const auto donor_geometry = geometry[donorZone][INST_0][MESH_0]; + const auto target_geometry = geometry[targetZone][INST_0][MESH_0]; + + // TODO turbo this approach only works if all the turboamchinery marker + // of all zones have the same amount of span wise sections. + // TODO turbo initialization needed for the MPI routine should be place somewhere else. + auto nSpanDonor = donor_config->GetnSpanWiseSections(); + auto nSpanTarget = target_config->GetnSpanWiseSections(); + + targetSpans.resize(config[donorZone]->GetnMarker_MixingPlaneInterface()); + + /*--- On the donor side ---*/ + for (auto iMarkerInt = 1; iMarkerInt < nMarkerInt + 1; iMarkerInt++) { + int markDonor = -1, markTarget = -1; + short donorFlag = 0, targetFlag = 0; + + markDonor = donor_config->FindMixingPlaneInterfaceMarker(donor_geometry->GetnMarker(), iMarkerInt); + donorFlag = (markDonor != -1) ? donor_config->GetMarker_All_MixingPlaneInterface(markDonor) : -1; + + markTarget = target_config->FindMixingPlaneInterfaceMarker(target_geometry->GetnMarker(), iMarkerInt); + targetFlag = (markTarget != -1) ? target_config->GetMarker_All_MixingPlaneInterface(markTarget) : -1; + +#ifdef HAVE_MPI + auto buffMarkerDonor = new int[size]; + auto buffDonorFlag = new int[size]; + auto buffMarkerTarget = new int[size]; + auto buffTargetFlag = new int[size]; + for (int iSize = 0; iSize < size; iSize++) { + buffMarkerDonor[iSize] = -1; + buffDonorFlag[iSize] = -1; + buffMarkerTarget[iSize] = -1; + buffTargetFlag[iSize] = -1; + } + + SU2_MPI::Allgather(&markDonor, 1, MPI_INT, buffMarkerDonor, 1, MPI_INT, SU2_MPI::GetComm()); + SU2_MPI::Allgather(&donorFlag, 1, MPI_INT, buffDonorFlag, 1, MPI_INT, SU2_MPI::GetComm()); + SU2_MPI::Allgather(&markTarget, 1, MPI_INT, buffMarkerTarget, 1, MPI_INT, SU2_MPI::GetComm()); + SU2_MPI::Allgather(&targetFlag, 1, MPI_INT, buffTargetFlag, 1, MPI_INT, SU2_MPI::GetComm()); + + markDonor = -1; + donorFlag = -1; + markTarget = -1; + targetFlag = -1; + + for (int iSize = 0; iSize < size; iSize++) { + if (buffMarkerDonor[iSize] != -1) { + markDonor = buffMarkerDonor[iSize]; + donorFlag = buffDonorFlag[iSize]; + break; + } + } + + for (int iSize = 0; iSize < size; iSize++) { + if (buffMarkerTarget[iSize] != -1) { + markTarget = buffMarkerTarget[iSize]; + targetFlag = buffTargetFlag[iSize]; + break; + } + } + delete[] buffMarkerDonor; + delete[] buffDonorFlag; + delete[] buffMarkerTarget; + delete[] buffTargetFlag; +#endif + if (markTarget == -1 || markDonor == -1) continue; + + nSpanDonor = donor_config->GetnSpanWiseSections(); + nSpanTarget = target_config->GetnSpanWiseSections(); + + targetSpans[iMarkerInt].resize(nSpanTarget + 1); + + const auto spanValuesDonor = donor_geometry->GetSpanWiseValue(donorFlag); + const auto spanValuesTarget = target_geometry->GetSpanWiseValue(targetFlag); + + /*--- Interpolation at hub, shroud & 1D values ---*/ + targetSpans[iMarkerInt][0].donorSpan = 0; + targetSpans[iMarkerInt][0].coefficient = 0.0; + if (nDim > 2) { + targetSpans[iMarkerInt][nSpanTarget - 1].donorSpan = nSpanDonor - 1; + targetSpans[iMarkerInt][nSpanTarget - 1].coefficient = 0.0; + } + targetSpans[iMarkerInt][nSpanTarget].donorSpan = nSpanDonor; + targetSpans[iMarkerInt][nSpanTarget].coefficient = 0.0; + + for (auto iSpanTarget = 1; iSpanTarget < nSpanTarget - 1; iSpanTarget++) { + auto& targetSpan = targetSpans[iMarkerInt][iSpanTarget]; + + switch (donor_config->GetKind_MixingPlaneInterface()) { + case MATCHING: + targetSpan = MapMatchingSpan(iSpanTarget); + break; + + case NEAREST_SPAN: + targetSpan = MapNearestSpan(spanValuesTarget[iSpanTarget], spanValuesDonor, nSpanDonor); + break; + + case LINEAR_INTERPOLATION: { + targetSpan = MapLinearInterpolationSpan(spanValuesTarget[iSpanTarget], spanValuesDonor, nSpanDonor, rank); + break; + } + default: + SU2_MPI::Error("MixingPlane interface option not implemented yet", CURRENT_FUNCTION); + break; + } + } + } +} + +void CMixingPlane::WriteInterpolationDetails(const std::string& filename, const CConfig* const* config) { + // Only write from master process in MPI + if (rank != MASTER_NODE) return; + + std::ofstream outFile(filename); + + if (!outFile.is_open()) { + cout << "Error: Could not open file " << filename << ". Abandoning interpolator writing..." << endl; + return; + } + + const auto donor_config = config[donorZone]; + const auto nMarkerInt = config[donorZone]->GetnMarker_MixingPlaneInterface() / 2; + + outFile << "Mixing-Plane Interpolator Details. Donor Zone = " << donorZone << " Target Zone = " << targetZone + << ". Interpolation Method = "; + switch (donor_config->GetKind_MixingPlaneInterface()) { + case MATCHING: + outFile << "MATCHING\n"; + break; + case NEAREST_SPAN: + outFile << "NEAREST_SPAN\n"; + break; + case LINEAR_INTERPOLATION: + outFile << "LINEAR_INTERPOLATION\n"; + break; + default: + outFile << "UNKNOWN\n"; + } + outFile << "\n"; + outFile << "===============================================================" << endl; + + // Loop through each marker interface + for (auto iMarkerInt = 0; iMarkerInt < nMarkerInt + 1; iMarkerInt++) { + if (targetSpans[iMarkerInt].empty()) continue; + + outFile << "Marker Interface " << iMarkerInt << "\n"; + outFile << "---------------------\n"; + outFile << "Target Span, Donor Span, Interpolation Coefficient\n"; + + for (size_t iSpanTarget = 0; iSpanTarget < targetSpans[iMarkerInt].size(); iSpanTarget++) { + const auto& targetSpan = targetSpans[iMarkerInt][iSpanTarget]; + outFile << iSpanTarget << ", " << targetSpan.donorSpan << ", " << targetSpan.coefficient << "\n"; + } + outFile << "\n"; + } + + // Optional: Write grouped by donor span + outFile << "\n\nGrouped by Donor Span\n"; + outFile << "=====================\n\n"; + + for (auto iMarkerInt = 0; iMarkerInt < nMarkerInt + 1; iMarkerInt++) { + if (targetSpans[iMarkerInt].empty()) continue; + + outFile << "Marker Interface " << iMarkerInt << "\n"; + outFile << "---------------------\n"; + + // Find max donor span + unsigned long maxDonorSpan = 0; + for (const auto& ts : targetSpans[iMarkerInt]) { + maxDonorSpan = std::max(maxDonorSpan, ts.donorSpan); + } + + // Group by donor span + for (unsigned long iDonor = 0; iDonor <= maxDonorSpan; iDonor++) { + bool hasTargets = false; + std::ostringstream targets; + + for (size_t iSpanTarget = 0; iSpanTarget < targetSpans[iMarkerInt].size(); iSpanTarget++) { + if (targetSpans[iMarkerInt][iSpanTarget].donorSpan == iDonor) { + if (hasTargets) targets << ", "; + targets << "Target " << iSpanTarget << " (coeff=" << targetSpans[iMarkerInt][iSpanTarget].coefficient << ")"; + hasTargets = true; + } + } + + if (hasTargets) { + outFile << "Donor Span " << iDonor << ": " << targets.str() << "\n"; + } + } + outFile << "\n"; + } + + outFile.close(); + cout << "Interpolation details written to " << filename << endl; +} diff --git a/Common/src/interface_interpolation/CNearestNeighbor.cpp b/Common/src/interface_interpolation/CNearestNeighbor.cpp index 2c245e9fc554..33a99994ccdd 100644 --- a/Common/src/interface_interpolation/CNearestNeighbor.cpp +++ b/Common/src/interface_interpolation/CNearestNeighbor.cpp @@ -33,7 +33,7 @@ CNearestNeighbor::CNearestNeighbor(CGeometry**** geometry_container, const CConfig* const* config, unsigned int iZone, unsigned int jZone) : CInterpolator(geometry_container, config, iZone, jZone) { - SetTransferCoeff(config); + SetTransferCoeff(geometry_container, config); } void CNearestNeighbor::PrintStatistics() const { @@ -41,7 +41,7 @@ void CNearestNeighbor::PrintStatistics() const { cout << " Avg/max distance to closest donor point: " << AvgDistance << "/" << MaxDistance << endl; } -void CNearestNeighbor::SetTransferCoeff(const CConfig* const* config) { +void CNearestNeighbor::SetTransferCoeff(CGeometry**** geometry, const CConfig* const* config) { /*--- Desired number of donor points. ---*/ const auto nDonor = max(config[donorZone]->GetNumNearestNeighbors(), 1); diff --git a/Common/src/interface_interpolation/CRadialBasisFunction.cpp b/Common/src/interface_interpolation/CRadialBasisFunction.cpp index 3b14674a7866..f5040f925b0c 100644 --- a/Common/src/interface_interpolation/CRadialBasisFunction.cpp +++ b/Common/src/interface_interpolation/CRadialBasisFunction.cpp @@ -51,7 +51,7 @@ extern "C" void GEMM_IMPL(const char*, const char*, const int*, const int*, cons CRadialBasisFunction::CRadialBasisFunction(CGeometry**** geometry_container, const CConfig* const* config, unsigned int iZone, unsigned int jZone) : CInterpolator(geometry_container, config, iZone, jZone) { - SetTransferCoeff(config); + SetTransferCoeff(geometry_container, config); } void CRadialBasisFunction::PrintStatistics() const { @@ -102,7 +102,7 @@ su2double CRadialBasisFunction::Get_RadialBasisValue(RADIAL_BASIS type, const su return rbf; } -void CRadialBasisFunction::SetTransferCoeff(const CConfig* const* config) { +void CRadialBasisFunction::SetTransferCoeff(CGeometry**** geometry, const CConfig* const* config) { /*--- RBF options. ---*/ const auto kindRBF = config[donorZone]->GetKindRadialBasisFunction(); const bool usePolynomial = config[donorZone]->GetRadialBasisFunctionPolynomialOption(); diff --git a/Common/src/interface_interpolation/CSlidingMesh.cpp b/Common/src/interface_interpolation/CSlidingMesh.cpp index a81ff98f31fb..7e287c12f998 100644 --- a/Common/src/interface_interpolation/CSlidingMesh.cpp +++ b/Common/src/interface_interpolation/CSlidingMesh.cpp @@ -33,10 +33,10 @@ CSlidingMesh::CSlidingMesh(CGeometry**** geometry_container, const CConfig* const* config, unsigned int iZone, unsigned int jZone) : CInterpolator(geometry_container, config, iZone, jZone) { - SetTransferCoeff(config); + SetTransferCoeff(geometry_container, config); } -void CSlidingMesh::SetTransferCoeff(const CConfig* const* config) { +void CSlidingMesh::SetTransferCoeff(CGeometry**** geometry, const CConfig* const* config) { /* 0 - Variable declaration */ /* --- General variables --- */ diff --git a/Common/src/interface_interpolation/meson.build b/Common/src/interface_interpolation/meson.build index 8624b3ad4fa8..6cd185ef873c 100644 --- a/Common/src/interface_interpolation/meson.build +++ b/Common/src/interface_interpolation/meson.build @@ -4,4 +4,5 @@ common_src += files(['CInterpolatorFactory.cpp', 'CSlidingMesh.cpp', 'CIsoparametric.cpp', 'CNearestNeighbor.cpp', - 'CRadialBasisFunction.cpp']) + 'CRadialBasisFunction.cpp', + 'CMixingPlane.cpp']) diff --git a/SU2_CFD/include/drivers/CDriver.hpp b/SU2_CFD/include/drivers/CDriver.hpp index 0328c0b436a2..916661ecfb06 100644 --- a/SU2_CFD/include/drivers/CDriver.hpp +++ b/SU2_CFD/include/drivers/CDriver.hpp @@ -77,6 +77,7 @@ class CDriver : public CDriverBase { interpolator_container; /*!< \brief Definition of the interpolation method between non-matching discretizations of the interface. */ CInterface*** interface_container; /*!< \brief Definition of the interface of information and physics. */ + unsigned short** interface_types; /*!< \brief Type of coupling between the distinct (physical) zones. */ bool dry_run; /*!< \brief Flag if SU2_CFD was started as dry-run via "SU2_CFD -d .cfg" */ public: @@ -203,8 +204,8 @@ class CDriver : public CDriverBase { * \param[in] interpolation - Object defining the interpolation. */ void InitializeInterface(CConfig** config, CSolver***** solver, CGeometry**** geometry, - unsigned short** interface_types, CInterface*** interface, - vector>>& interpolation); + unsigned short** interface_types, CInterface*** interface, + vector>>& interpolation); /*! * \brief Definition and allocation of all solver classes. @@ -290,17 +291,14 @@ class CDriver : public CDriverBase { * \param[in] geometry - Geometrical definition of the problem. * \param[in] solver - Container vector with all the solutions. * \param[in] interface - Class defining the physical transfer of information. + * \param[in] iteration - Class defining the iteration strcuture. * \param[in] dummy - Definition of dummy driver */ void PreprocessTurbomachinery(CConfig** config, CGeometry**** geometry, CSolver***** solver, - CInterface*** interface, bool dummy); + CInterface*** interface, CIteration*** iteration, bool dummy); - /*! - * \brief Ramp some simulation settings for turbomachinery problems. - * \param[in] iter - Iteration for the ramp (can be outer or time depending on type of simulation). - * \note TODO This is not compatible with inner iterations because they are delegated to the iteration class. - */ - void RampTurbomachineryValues(unsigned long iter); + void PreprocessTurboVertex(CConfig** config, CGeometry**** geometry, CSolver***** solver, + CInterface*** interface, CIteration*** iteration, bool dummy); /*! * \brief A virtual member. diff --git a/SU2_CFD/include/drivers/CDriverBase.hpp b/SU2_CFD/include/drivers/CDriverBase.hpp index ba3af37349fd..b9d9c74cb1f7 100644 --- a/SU2_CFD/include/drivers/CDriverBase.hpp +++ b/SU2_CFD/include/drivers/CDriverBase.hpp @@ -60,8 +60,7 @@ class CDriverBase { nZone, /*!< \brief Total number of zones in the problem. */ nDim, /*!< \brief Number of dimensions. */ iInst, /*!< \brief Iterator on instance levels. */ - *nInst, /*!< \brief Total number of instances in the problem (per zone). */ - **interface_types; /*!< \brief Type of coupling between the distinct (physical) zones. */ + *nInst; /*!< \brief Total number of instances in the problem (per zone). */ CConfig* driver_config = nullptr; /*!< \brief Definition of the driver configuration. */ COutput* driver_output = nullptr; /*!< \brief Definition of the driver output. */ diff --git a/SU2_CFD/include/drivers/CMultizoneDriver.hpp b/SU2_CFD/include/drivers/CMultizoneDriver.hpp index 8d7fcd7e8838..d0b1ab976e36 100644 --- a/SU2_CFD/include/drivers/CMultizoneDriver.hpp +++ b/SU2_CFD/include/drivers/CMultizoneDriver.hpp @@ -83,15 +83,6 @@ class CMultizoneDriver : public CDriver { */ bool TransferData(unsigned short donorZone, unsigned short targetZone); - - /*! - * \brief Transfer the local turboperfomance quantities (for each blade row) from all the donorZones to the - * targetZone (ZONE_0). - * \note IMPORTANT: This approach of multi-zone performances rely upon the fact that turbomachinery markers follow - * the natural (stator-rotor) development of the real machine. - */ - void SetTurboPerformance(); - /*! * \brief Check the convergence at the outer level. */ diff --git a/SU2_CFD/include/interfaces/CInterface.hpp b/SU2_CFD/include/interfaces/CInterface.hpp index 23a5621c4929..05498f1abff2 100644 --- a/SU2_CFD/include/interfaces/CInterface.hpp +++ b/SU2_CFD/include/interfaces/CInterface.hpp @@ -29,6 +29,7 @@ #pragma once #include "../../../Common/include/parallelization/mpi_structure.hpp" +#include "../../../Common/include/containers/C2DContainer.hpp" #include "../../../Common/include/option_structure.hpp" #include @@ -37,6 +38,7 @@ #include #include #include +#include #include #include @@ -66,6 +68,8 @@ class CInterface { su2double *Target_Variable = nullptr; bool valAggregated = false; + unsigned short InterfaceType; /*!< \brief The type of interface. */ + /*--- Mixing Plane interface variable ---*/ su2double *SpanValueCoeffTarget = nullptr; unsigned short *SpanLevelDonor = nullptr; @@ -147,6 +151,21 @@ class CInterface { for (auto iVar = 0u; iVar < nVar; iVar++) Target_Variable[iVar] += donorCoeff * bcastVariable[iVar]; } + /*! + * \brief Recovers the target variable at the endwall from the buffer of su2doubles that was broadcast for mixing plane interfaces. + * \param[in] bcastVariable - Broadcast variable. + * \param[in] idx - Index of the target point. + */ + inline virtual void RecoverTarget_SpanEndwall(const su2activevector &bcastVariable, unsigned long idx) { } + + /*! + * \brief Recovers the target variable at the span from the buffer of su2doubles that was broadcast for mixing plane interfaces. + * \param[in] bcastVariable - Broadcast variable. + * \param[in] idx - Index of the target point. + * \param[in] donorCoeff - value of the donor coefficient. + */ + inline virtual void RecoverTarget_Span(const su2activevector &bcastVariable, unsigned long idx, su2double donorCoeff) { } + /*! * \brief A virtual member. * \param[in] target_solution - Solution from the target mesh. @@ -178,53 +197,37 @@ class CInterface { inline virtual void SetSpanWiseLevels(const CConfig *donor_config, const CConfig *target_config) { } /*! - * \brief A virtual member. - * \param[in] target_solution - Solution from the target mesh. + * \brief Interpolate data and broadcast it into all processors, for nonmatching meshes. + * \param[in] interpolator - Object defining the interpolation. + * \param[in] donor_solution - Solution from the donor mesh. * \param[in] target_solution - Solution from the target mesh. - * \param[in] donor_zone - Index of the donorZone. - */ - inline virtual void SetAverageValues(CSolver *donor_solution, CSolver *target_solution, - unsigned short donorZone) { } - - /*! - * \brief Transfer pre-processing for the mixing plane inteface. * \param[in] donor_geometry - Geometry of the donor mesh. * \param[in] target_geometry - Geometry of the target mesh. * \param[in] donor_config - Definition of the problem at the donor mesh. * \param[in] target_config - Definition of the problem at the target mesh. */ - void PreprocessAverage(CGeometry *donor_geometry, CGeometry *target_geometry, - const CConfig *donor_config, const CConfig *target_config, unsigned short iMarkerInt); + inline virtual void BroadcastData_MixingPlane(const CInterpolator& interpolator, + CSolver *donor_solution, CSolver *target_solution, + CGeometry *donor_geometry, CGeometry *target_geometry, + const CConfig *donor_config, const CConfig *target_config) { }; + /*! - * \brief Interpolate data and scatter it into different processors, for matching meshes. - * \param[in] donor_solution - Solution from the donor mesh. - * \param[in] target_solution - Solution from the target mesh. - * \param[in] donor_geometry - Geometry of the donor mesh. - * \param[in] target_geometry - Geometry of the target mesh. - * \param[in] donor_config - Definition of the problem at the donor mesh. - * \param[in] target_config - Definition of the problem at the target mesh. + * \brief Set the contact resistance value for the solid-to-solid heat transfer interface. + * \param[in] val_contact_resistance - Contact resistance value in m^2/W */ - void AllgatherAverage(CSolver *donor_solution, CSolver *target_solution, - CGeometry *donor_geometry, CGeometry *target_geometry, - const CConfig *donor_config, const CConfig *target_config, unsigned short iMarkerInt); + inline virtual void SetContactResistance(su2double val_contact_resistance) {}; /*! - * \brief Interpolate data and scatter it into different processors, for matching meshes. - * \param[in] donor_solution - Solution from the donor mesh. - * \param[in] target_solution - Solution from the target mesh. - * \param[in] donor_geometry - Geometry of the donor mesh. - * \param[in] target_geometry - Geometry of the target mesh. - * \param[in] donor_config - Definition of the problem at the donor mesh. - * \param[in] target_config - Definition of the problem at the target mesh. + * \brief Set the type of an interface + * \param[in] interface_type - The type of interface */ - void GatherAverageValues(CSolver *donor_solution, CSolver *target_solution, unsigned short donorZone); + void SetInterfaceType(unsigned short interface_type) { InterfaceType = interface_type; } /*! - * \brief Set the contact resistance value for the solid-to-solid heat transfer interface. - * \param[in] val_contact_resistance - Contact resistance value in m^2/W + * \brief Get the type of an interface */ - inline virtual void SetContactResistance(su2double val_contact_resistance) {}; + unsigned short GetInterfaceType(void) const { return InterfaceType; } /*! * \brief These can be used to chain interfaces between the same zones but for other variables, diff --git a/SU2_CFD/include/interfaces/cfd/CMixingPlaneInterface.hpp b/SU2_CFD/include/interfaces/cfd/CMixingPlaneInterface.hpp index e57d7c61764c..fad7aa7a4c27 100644 --- a/SU2_CFD/include/interfaces/cfd/CMixingPlaneInterface.hpp +++ b/SU2_CFD/include/interfaces/cfd/CMixingPlaneInterface.hpp @@ -29,6 +29,7 @@ #pragma once #include "../CInterface.hpp" +#include "../../../Common/include/containers/C2DContainer.hpp" /*! * \brief Mixing plane interface for turbomachinery. @@ -36,6 +37,7 @@ */ class CMixingPlaneInterface : public CInterface { public: + unsigned short nMixingVars; /*! * \overload * \param[in] val_nVar - Number of variables that need to be transferred. @@ -49,6 +51,21 @@ class CMixingPlaneInterface : public CInterface { */ void SetSpanWiseLevels(const CConfig *donor_config, const CConfig *target_config) override; + /*! + * \brief Interpolate data and broadcast it into all processors, for nonmatching meshes. + * \param[in] interpolator - Object defining the interpolation. + * \param[in] donor_solution - Solution from the donor mesh. + * \param[in] target_solution - Solution from the target mesh. + * \param[in] donor_geometry - Geometry of the donor mesh. + * \param[in] target_geometry - Geometry of the target mesh. + * \param[in] donor_config - Definition of the problem at the donor mesh. + * \param[in] target_config - Definition of the problem at the target mesh. + */ + void BroadcastData_MixingPlane(const CInterpolator& interpolator, + CSolver *donor_solution, CSolver *target_solution, + CGeometry *donor_geometry, CGeometry *target_geometry, + const CConfig *donor_config, const CConfig *target_config) override; + /*! * \brief Retrieve the variable that will be sent from donor mesh to target mesh. * \param[in] donor_solution - Solution from the donor mesh. @@ -73,14 +90,15 @@ class CMixingPlaneInterface : public CInterface { void SetTarget_Variable(CSolver *target_solution, CGeometry *target_geometry, const CConfig *target_config, unsigned long Marker_Target, unsigned long val_Span, unsigned long Point_Target) override; - /*! - * \brief Store all the turboperformance in the solver in ZONE_0. - * \param[in] donor_solution - Solution from the donor mesh. - * \param[in] target_solution - Solution from the target mesh. - * \param[in] donorZone - counter of the donor solution - */ - void SetAverageValues(CSolver *donor_solution, CSolver *target_solution, unsigned short donorZone) override; - - + inline void RecoverTarget_SpanEndwall(const su2activevector &bcastVariable, unsigned long iSpan) override { + for (auto iVar = 0u; iVar < nMixingVars; iVar++) { + Target_Variable[iVar] = bcastVariable[iSpan * nMixingVars + iVar]; + } + } + inline void RecoverTarget_Span(const su2activevector &bcastVariable, unsigned long iSpan, su2double donorCoeff) override { + for (auto iVar = 0u; iVar < nMixingVars; iVar++) { + Target_Variable[iVar] = (1 - donorCoeff)*bcastVariable[iSpan * nMixingVars + iVar] + donorCoeff * bcastVariable[(iSpan + 1) * nMixingVars + iVar]; + } + } }; diff --git a/SU2_CFD/include/iteration/CFluidIteration.hpp b/SU2_CFD/include/iteration/CFluidIteration.hpp index 2a53eba538cb..024f88e7d432 100644 --- a/SU2_CFD/include/iteration/CFluidIteration.hpp +++ b/SU2_CFD/include/iteration/CFluidIteration.hpp @@ -115,12 +115,7 @@ class CFluidIteration : public CIteration { * \param[in] iZone - The current zone * \param[in] ramp_flag - Flag indicating type of ramp (grid or boundary) */ - void UpdateRamp(CGeometry**** geometry_container, CConfig** config_container, unsigned long iter, unsigned short iZone, RAMP_TYPE ramp_flag); - - /*! - * \brief Computes turboperformance. - */ - void ComputeTurboPerformance(CSolver***** solver, CGeometry**** geometry_container, CConfig** config_container, unsigned long ExtIter); + void UpdateRamps(CGeometry**** geometry_container, CConfig** config_container, unsigned long iter, unsigned short iZone, RAMP_TYPE ramp_flag); /*! * \brief Postprocesses the fluid system before heading to another physics system or the next iteration. diff --git a/SU2_CFD/include/iteration/CIteration.hpp b/SU2_CFD/include/iteration/CIteration.hpp index b570d4b75ab4..06783c821f12 100644 --- a/SU2_CFD/include/iteration/CIteration.hpp +++ b/SU2_CFD/include/iteration/CIteration.hpp @@ -60,9 +60,7 @@ class CIteration { su2double StartTime{0.0}, /*!< \brief Tracking wall time. */ StopTime{0.0}, UsedTime{0.0}; - std::shared_ptr TurbomachineryPerformance; /*!< \brief turbo performance calculator. */ std::shared_ptr TurbomachineryStagePerformance; /*!< \brief turbo stage performance calculator. */ - public: /*! * \brief Constructor of the class. @@ -295,4 +293,22 @@ class CIteration { virtual void RegisterOutput(CSolver***** solver, CGeometry**** geometry, CConfig** config, unsigned short iZone, unsigned short iInst) {} + + /*! + * \brief Computes turboperformance. + */ + void ComputeTurboPerformance(CSolver***** solver, CGeometry**** geometry_container, CConfig** config_container); + + /*! + * \brief Initialises turboperformance classes. + */ + void InitTurboPerformance(CGeometry *geometry, CConfig** config, CFluidModel *fluid, unsigned short val_iZone); + + inline su2vector> GetBladesPerformanceVector(CSolver***** solver, unsigned short nBladeRow){ + su2vector> bladePerformances(nBladeRow); + for (auto iBladeRow = 0u; iBladeRow < nBladeRow; iBladeRow++) { + bladePerformances[iBladeRow] = solver[iBladeRow][INST_0][MESH_0][FLOW_SOL]->GetTurboBladePerformance(); + } + return bladePerformances; + } }; diff --git a/SU2_CFD/include/output/CFlowCompOutput.hpp b/SU2_CFD/include/output/CFlowCompOutput.hpp index ba1e289f8529..ad2dd0217317 100644 --- a/SU2_CFD/include/output/CFlowCompOutput.hpp +++ b/SU2_CFD/include/output/CFlowCompOutput.hpp @@ -53,6 +53,8 @@ class CFlowCompOutput final: public CFlowOutput { */ void LoadHistoryData(CConfig *config, CGeometry *geometry, CSolver **solver) override; + void LoadHistoryData(CConfig *config, CGeometry *geometry, CSolver **solver, unsigned short iZone); + /*! * \brief Set the available volume output fields * \param[in] config - Definition of the particular problem. @@ -74,6 +76,8 @@ class CFlowCompOutput final: public CFlowOutput { */ void SetHistoryOutputFields(CConfig *config) override; + void SetTurbomachineryObjectiveFunctions(CSolver *solver, CConfig *config); + /*! * \brief Check whether the base values for relative residuals should be initialized * \param[in] config - Definition of the particular problem. @@ -101,7 +105,7 @@ class CFlowCompOutput final: public CFlowOutput { * \param[in] OuterIter - Index of current outer iteration * \param[in] InnerIter - Index of current inner iteration */ - void SetTurboPerformance_Output(std::shared_ptr TurboPerf, CConfig *config, unsigned long TimeIter, unsigned long OuterIter, unsigned long InnerIter) override; + void SetTurboPerformance_Output(su2vector> TurboBladePerfs, CConfig *config, unsigned long TimeIter, unsigned long OuterIter, unsigned long InnerIter) override; /*! * \brief Sets the multizone turboperformacne screen output @@ -109,7 +113,7 @@ class CFlowCompOutput final: public CFlowOutput { * \param[in] TurboPerf - Turboperformance class * \param[in] config - Definition of the particular problem */ - void SetTurboMultiZonePerformance_Output(std::shared_ptr TurboStagePerf, std::shared_ptr TurboPerf, CConfig *config) override; + void SetTurboMultiZonePerformance_Output(std::shared_ptr TurboStagePerf, su2vector> TurboBladePerfs, CConfig *config) override; /*! * \brief Loads the turboperformacne history data @@ -117,7 +121,7 @@ class CFlowCompOutput final: public CFlowOutput { * \param[in] TurboPerf - Turboperformance class * \param[in] config - Definition of the particular problem */ - void LoadTurboHistoryData(std::shared_ptr TurboStagePerf, std::shared_ptr TurboPerf, CConfig *config) override; + void LoadTurboHistoryData(std::shared_ptr TurboStagePerf, su2vector> TurboBladePerfs, CConfig *config) override; /*! * \brief Write the kinematic and thermodynamic variables at each spanwise division @@ -126,6 +130,6 @@ class CFlowCompOutput final: public CFlowOutput { * \param[in] config - Descripiton of the particular problem * \param[in] val_iZone - Idientifier of current zone */ - void WriteTurboSpanwisePerformance(std::shared_ptr TurboPerf, CGeometry *geometry, CConfig **config, + void WriteTurboSpanwisePerformance(su2vector> TurboBladePerfs, CGeometry *geometry, CConfig **config, unsigned short val_iZone) override; }; diff --git a/SU2_CFD/include/output/CFlowOutput.hpp b/SU2_CFD/include/output/CFlowOutput.hpp index e79067c79d9b..cf4b2c9f6aa6 100644 --- a/SU2_CFD/include/output/CFlowOutput.hpp +++ b/SU2_CFD/include/output/CFlowOutput.hpp @@ -44,7 +44,7 @@ class CFlowOutput : public CFVMOutput{ */ CFlowOutput(const CConfig *config, unsigned short nDim, bool femOutput); - /* + /*! * \brief Add turboperformance outputs as history field * \param[in] nZone - Number of zones in problem */ diff --git a/SU2_CFD/include/output/COutput.hpp b/SU2_CFD/include/output/COutput.hpp index f5df6d6272b7..11234880c507 100644 --- a/SU2_CFD/include/output/COutput.hpp +++ b/SU2_CFD/include/output/COutput.hpp @@ -411,7 +411,7 @@ class COutput { */ void SetHistoryOutput(CGeometry ****geometry, CSolver *****solver_container, CConfig **config, std::shared_ptr TurboStagePerf, - std::shared_ptr TurboPerf, unsigned short val_iZone, + su2vector> TurboBladePerfs, unsigned short val_iZone, unsigned long TimeIter, unsigned long OuterIter, unsigned long InnerIter, unsigned short val_iInst); /*! @@ -982,7 +982,7 @@ class COutput { * \param[in] OuterIter - Index of current outer iteration * \param[in] InnerIter - Index of current inner iteration */ - inline virtual void SetTurboPerformance_Output(std::shared_ptr TurboPerf, CConfig *config, unsigned long TimeIter, unsigned long OuterIter, unsigned long InnerIter) {} + inline virtual void SetTurboPerformance_Output(su2vector> TurboBladePerfs, CConfig *config, unsigned long TimeIter, unsigned long OuterIter, unsigned long InnerIter) {} /*! * \brief Sets the multizone turboperformacne screen output @@ -990,7 +990,7 @@ class COutput { * \param[in] TurboPerf - Turboperformance class * \param[in] config - Definition of the particular problem */ - inline virtual void SetTurboMultiZonePerformance_Output(std::shared_ptr TurboStagePerf, std::shared_ptr TurboPerf, CConfig *config) {} + inline virtual void SetTurboMultiZonePerformance_Output(std::shared_ptr TurboStagePerf, su2vector> TurboPerf, CConfig *config) {} /*! * \brief Loads the turboperformacne history data @@ -998,7 +998,7 @@ class COutput { * \param[in] TurboPerf - Turboperformance class * \param[in] config - Definition of the particular problem */ - inline virtual void LoadTurboHistoryData(std::shared_ptr TurboStagePerf, std::shared_ptr TurboPerf, CConfig *config) {} + inline virtual void LoadTurboHistoryData(std::shared_ptr TurboStagePerf, su2vector> TurboPerf, CConfig *config) {} /*! * \brief Write the kinematic and thermodynamic variables at each spanwise division @@ -1007,7 +1007,7 @@ class COutput { * \param[in] config - Descripiton of the particular problem * \param[in] val_iZone - Idientifier of current zone */ - inline virtual void WriteTurboSpanwisePerformance(std::shared_ptr TurboPerf, CGeometry *geometry, CConfig **config, + inline virtual void WriteTurboSpanwisePerformance(su2vector> TurboBladePerfs, CGeometry *geometry, CConfig **config, unsigned short val_iZone) {}; /*! diff --git a/SU2_CFD/include/output/CTurboOutput.hpp b/SU2_CFD/include/output/CTurboOutput.hpp index 21c69659e8f6..430f239cd8e6 100644 --- a/SU2_CFD/include/output/CTurboOutput.hpp +++ b/SU2_CFD/include/output/CTurboOutput.hpp @@ -96,6 +96,11 @@ class CTurbomachineryState { CTurbomachineryState(unsigned short nDim, su2double area, su2double radius); + inline void SetZeroValues() { + Density = Pressure = Entropy = Enthalpy = Temperature = TotalTemperature = TotalPressure = TotalEnthalpy = 0.0; + AbsFlowAngle = FlowAngle = MassFlow = Rothalpy = TotalRelPressure = 0.0; + } + void ComputeState(CFluidModel& fluidModel, const CTurbomachineryPrimitiveState& primitiveState); const su2double& GetDensity() const { return Density; } @@ -247,16 +252,30 @@ class CTurbomachineryStagePerformance { */ class CTurboOutput { private: - vector>> BladesPerformances; + vector> BladesPerformances; static void ComputePerBlade(vector> const bladePerformances, vector const bladePrimitives); static void ComputePerSpan(shared_ptr const spanPerformances, const CTurbomachineryCombinedPrimitiveStates& spanPrimitives); public: - CTurboOutput(CConfig** config, const CGeometry& geometry, CFluidModel& fluidModel); + CTurboOutput(CConfig** config, const CGeometry& geometry, CFluidModel& fluidModel, unsigned short iBladeRow); + + const vector>& GetBladesPerformances() const { return BladesPerformances; } + + void ComputeTurbomachineryPerformance(vector const primitives, unsigned short iBladeRow); - const vector>>& GetBladesPerformances() const { return BladesPerformances; } + /*! + * \brief Returns true if the given objective function kind is a turbomachinery objective + * that can be evaluated via GetObjectiveValue. + * \param[in] kind - Objective function kind (ENUM_OBJECTIVE value). + */ + static bool IsTurboObjective(unsigned short kind); - void ComputeTurbomachineryPerformance(vector> const primitives); + /*! + * \brief Get the value of a turbomachinery objective function from the tip span performance. + * \param[in] kind - Objective function kind (ENUM_OBJECTIVE value). + * \return The objective function value, or 0.0 for unrecognised kinds. + */ + su2double GetObjectiveValue(unsigned short kind) const; }; \ No newline at end of file diff --git a/SU2_CFD/include/solvers/CDiscAdjSolver.hpp b/SU2_CFD/include/solvers/CDiscAdjSolver.hpp index 71758c0f43cf..81d325feaf5c 100644 --- a/SU2_CFD/include/solvers/CDiscAdjSolver.hpp +++ b/SU2_CFD/include/solvers/CDiscAdjSolver.hpp @@ -57,7 +57,7 @@ class CDiscAdjSolver final : public CSolver { su2double Total_Sens_BPress; /*!< \brief Total sensitivity to outlet pressure. */ su2double Total_Sens_Density; /*!< \brief Total sensitivity to initial density (incompressible). */ su2double Total_Sens_ModVel; /*!< \brief Total sensitivity to inlet velocity (incompressible). */ - su2double Mach, Alpha, Beta, Temperature, BPressure, ModVel; + su2double Mach, Alpha, Beta, Pressure, Temperature, BPressure, ModVel; su2double TemperatureRad, Total_Sens_Temp_Rad; CDiscAdjVariable* nodes = nullptr; /*!< \brief The highest level in the variable hierarchy this solver can safely use. */ diff --git a/SU2_CFD/include/solvers/CEulerSolver.hpp b/SU2_CFD/include/solvers/CEulerSolver.hpp index dc932095bc47..345be6d3d3a4 100644 --- a/SU2_CFD/include/solvers/CEulerSolver.hpp +++ b/SU2_CFD/include/solvers/CEulerSolver.hpp @@ -29,6 +29,7 @@ #include "CFVMFlowSolverBase.hpp" #include "../variables/CEulerVariable.hpp" +#include "../output/CTurboOutput.hpp" /*! * \class CEulerSolver @@ -126,21 +127,16 @@ class CEulerSolver : public CFVMFlowSolverBase AverageVelocity; vector AverageTurboVelocity; vector OldAverageTurboVelocity; - vector ExtAverageTurboVelocity; su2activematrix AveragePressure; su2activematrix OldAveragePressure; su2activematrix RadialEquilibriumPressure; - su2activematrix ExtAveragePressure; su2activematrix AverageDensity; su2activematrix OldAverageDensity; - su2activematrix ExtAverageDensity; su2activematrix AverageNu; su2activematrix AverageKine; su2activematrix AverageOmega; - su2activematrix ExtAverageNu; - su2activematrix ExtAverageKine; - su2activematrix ExtAverageOmega; su2activevector AverageMassFlowRate; + su2activematrix AverageRelTangVelocity; su2activematrix DensityIn; su2activematrix PressureIn; @@ -154,11 +150,57 @@ class CEulerSolver : public CFVMFlowSolverBase > > CkInflow, CkOutflow1, CkOutflow2; + static constexpr unsigned short nMixingStateVars = 8; /*!< \brief Number of averaged variables transferred across mixing plane interfaces. */ + vector MixingState; /* GetTurboBladePerformance() const final { return TurbomachineryPerformance; } + /*! * \brief it take a velocity in the cartesian reference of framework and transform into the turbomachinery frame of reference. * \param[in] cartesianVelocity - cartesian components of velocity vector. @@ -1238,105 +1284,6 @@ class CEulerSolver : public CFVMFlowSolverBaseval_marker. - */ - inline su2double GetExtAverageNu(unsigned short valMarker, unsigned short valSpan) const final { - return ExtAverageNu[valMarker][valSpan]; - } - - /*! - * \brief Provide the average density at the boundary of interest. - * \param[in] val_marker - bound marker. - * \return Value of the Average turbulent Kine on the surface val_marker. - */ - inline su2double GetExtAverageKine(unsigned short valMarker, unsigned short valSpan) const final { - return ExtAverageKine[valMarker][valSpan]; - } - - /*! - * \brief Provide the average density at the boundary of interest. - * \param[in] val_marker - bound marker. - * \return Value of the Average turbulent Omega on the surface val_marker. - */ - inline su2double GetExtAverageOmega(unsigned short valMarker, unsigned short valSpan) const final { - return ExtAverageOmega[valMarker][valSpan]; - } - - /*! - * \brief Set the external average density at the boundary of interest. - * \param[in] val_marker - bound marker. - * \param[in] val_Span - value of the Span. - * \param[in] valDensity - value to set. - */ - inline void SetExtAverageDensity(unsigned short valMarker, - unsigned short valSpan, - su2double valDensity) final { - ExtAverageDensity[valMarker][valSpan] = valDensity; - } - - /*! - * \brief Set the external average density at the boundary of interest. - * \param[in] val_marker - bound marker. - * \param[in] val_Span - value of the Span. - * \param[in] valPressure - value to set. - */ - inline void SetExtAveragePressure(unsigned short valMarker, - unsigned short valSpan, - su2double valPressure) final { - ExtAveragePressure[valMarker][valSpan] = valPressure; - } - - /*! - * \brief Set the external the average turbo velocity average at the boundary of interest. - * \param[in] val_marker - bound marker. - * \return Value of the Average Total Pressure on the surface val_marker. - */ - inline void SetExtAverageTurboVelocity(unsigned short valMarker, - unsigned short valSpan, - unsigned short valIndex, - su2double valTurboVelocity) final { - ExtAverageTurboVelocity[valMarker][valSpan][valIndex] = valTurboVelocity; - } - - /*! - * \brief Set the external average turbulent Nu at the boundary of interest. - * \param[in] val_marker - bound marker. - * \param[in] val_Span - value of the Span. - * \param[in] valNu - value to set. - */ - inline void SetExtAverageNu(unsigned short valMarker, - unsigned short valSpan, - su2double valNu) final { - ExtAverageNu[valMarker][valSpan] = valNu; - } - - /*! - * \brief Set the external average turbulent Kine at the boundary of interest. - * \param[in] val_marker - bound marker. - * \param[in] val_Span - value of the Span. - * \param[in] valKine - value to set. - */ - inline void SetExtAverageKine(unsigned short valMarker, - unsigned short valSpan, - su2double valKine) final { - ExtAverageKine[valMarker][valSpan] = valKine; - } - - /*! - * \brief Set the external average turbulent Omega at the boundary of interest. - * \param[in] val_marker - bound marker. - * \param[in] val_Span - value of the Span. - * \param[in] valOmega - value to set. - */ - inline void SetExtAverageOmega(unsigned short valMarker, - unsigned short valSpan, - su2double valOmega) final { - ExtAverageOmega[valMarker][valSpan] = valOmega; - } - /*! * \brief Provide the inlet density to check convergence of conservative mixing-plane. * \param[in] inMarkerTP - bound marker. diff --git a/SU2_CFD/include/solvers/CSolver.hpp b/SU2_CFD/include/solvers/CSolver.hpp index 942e2e258779..9ac49e634203 100644 --- a/SU2_CFD/include/solvers/CSolver.hpp +++ b/SU2_CFD/include/solvers/CSolver.hpp @@ -59,6 +59,8 @@ #include "../limiters/CLimiterDetails.hpp" #include "../variables/CVariable.hpp" +#include "../output/CTurboOutput.hpp" + #ifdef HAVE_LIBROM #include "librom.h" #endif @@ -150,6 +152,8 @@ class CSolver { vector VertexTraction; /*- Temporary, this will be moved to a new postprocessing structure once in place -*/ vector VertexTractionAdjoint; /*- Also temporary -*/ + std::shared_ptr TurbomachineryPerformance; /*!< \brief turbo performance calculator. */ + string SolverName; /*!< \brief Store the name of the solver for output purposes. */ /*! @@ -1106,6 +1110,8 @@ class CSolver { CConfig *config, unsigned short val_marker) { } + inline virtual std::shared_ptr GetTurboBladePerformance() const { return std::shared_ptr(nullptr); } + /*! * \brief A virtual member. * \param[in] geometry - Geometrical definition of the problem. @@ -1287,6 +1293,29 @@ class CSolver { */ virtual void Impose_Fixed_Values(const CGeometry *geometry, const CConfig *config) { } + /*! + * \brief Get a component of the donor-side averaged state at a mixing plane interface + * \param[in] val_marker - marker index + * \param[in] val_span - span index + * \param[in] val_state - requested state component + */ + inline virtual su2double GetMixingState(unsigned short val_marker, + unsigned long val_span, + unsigned short val_state) const { return 0; } + + /*! + * \brief Set a component of the donor-side averaged state at a mixing plane interface nodes. + * \param[in] val_marker - marker index + * \param[in] val_span - span index + * \param[in] val_state - state component to set + * \param[in] component - value to set + */ + inline virtual void SetMixingState(unsigned short val_marker, + unsigned long val_span, + unsigned short val_state, + // unsigned long donor_span, // Do I care about where it comes from? + su2double component) { } + /*! * \brief Get the outer state for fluid interface nodes. * \param[in] val_marker - marker index @@ -3786,7 +3815,7 @@ class CSolver { * \param[in] geometry - Geometrical definition of the problem. * \param[in] config - Definition of the particular problem. */ - inline virtual void InitTurboContainers(CGeometry *geometry, CConfig *config) { } + inline virtual void InitTurboContainers(CGeometry *geometry, CConfig **config, unsigned short iZone) { } /*! * \brief Get Primal variables for turbo performance computation @@ -3826,6 +3855,8 @@ class CSolver { */ inline virtual void GatherInOutAverageValues(CConfig *config, CGeometry *geometry) { } + inline virtual void ComputeTurboBladePerformance(CGeometry* geometry, CConfig* config, unsigned short iBlade) { }; + /*! * \brief A virtual member. * \param[in] val_marker - bound marker. @@ -3875,82 +3906,6 @@ class CSolver { */ inline virtual su2double GetAverageOmega(unsigned short valMarker, unsigned short valSpan) const { return 0.0; } - /*! - * \brief A virtual member. - * \param[in] val_marker - bound marker. - * \return Value of the Average Nu on the surface val_marker. - */ - inline virtual su2double GetExtAverageNu(unsigned short valMarker, unsigned short valSpan) const { return 0.0; } - - /*! - * \brief A virtual member. - * \param[in] val_marker - bound marker. - * \return Value of the Average Kine on the surface val_marker. - */ - inline virtual su2double GetExtAverageKine(unsigned short valMarker, unsigned short valSpan) const { return 0.0; } - - /*! - * \brief A virtual member. - * \param[in] val_marker - bound marker. - * \return Value of the Average Omega on the surface val_marker. - */ - inline virtual su2double GetExtAverageOmega(unsigned short valMarker, unsigned short valSpan) const { return 0.0; } - - /*! - * \brief A virtual member. - * \param[in] val_marker - bound marker. - * \return Value of the Average Density on the surface val_marker. - */ - inline virtual void SetExtAverageDensity(unsigned short valMarker, - unsigned short valSpan, - su2double valDensity) { } - - /*! - * \brief A virtual member. - * \param[in] val_marker - bound marker. - * \return Value of the Average Pressure on the surface val_marker. - */ - inline virtual void SetExtAveragePressure(unsigned short valMarker, - unsigned short valSpan, - su2double valPressure) { } - - /*! - * \brief A virtual member. - * \param[in] val_marker - bound marker. - * \return Value of the Average Total Pressure on the surface val_marker. - */ - inline virtual void SetExtAverageTurboVelocity(unsigned short valMarker, - unsigned short valSpan, - unsigned short valIndex, - su2double valTurboVelocity) { } - - /*! - * \brief A virtual member. - * \param[in] val_marker - bound marker. - * \return Value of the Average Nu on the surface val_marker. - */ - inline virtual void SetExtAverageNu(unsigned short valMarker, - unsigned short valSpan, - su2double valNu) { } - - /*! - * \brief A virtual member. - * \param[in] val_marker - bound marker. - * \return Value of the Average Kine on the surface val_marker. - */ - inline virtual void SetExtAverageKine(unsigned short valMarker, - unsigned short valSpan, - su2double valKine) { } - - /*! - * \brief A virtual member. - * \param[in] val_marker - bound marker. - * \return Value of the Average Omega on the surface val_marker. - */ - inline virtual void SetExtAverageOmega(unsigned short valMarker, - unsigned short valSpan, - su2double valOmega) { } - /*! * \brief A virtual member. * \param[in] inMarkerTP - bound marker. diff --git a/SU2_CFD/src/drivers/CDiscAdjMultizoneDriver.cpp b/SU2_CFD/src/drivers/CDiscAdjMultizoneDriver.cpp index 14e9d04faa95..08c9fce72ba9 100644 --- a/SU2_CFD/src/drivers/CDiscAdjMultizoneDriver.cpp +++ b/SU2_CFD/src/drivers/CDiscAdjMultizoneDriver.cpp @@ -284,25 +284,26 @@ void CDiscAdjMultizoneDriver::TapeTest() { /*--- This recording will assign the initial (same) tag to each registered variable. * During the recording, each dependent variable will be assigned the same tag. ---*/ + AD::SetTag(1); if(driver_config->GetAD_CheckTapeType() == CHECK_TAPE_TYPE::OBJECTIVE_FUNCTION) { if(driver_config->GetAD_CheckTapeVariables() == CHECK_TAPE_VARIABLES::MESH_COORDINATES) { - if (rank == MASTER_NODE) cout << "\nChecking OBJECTIVE_FUNCTION_TAPE for SOLVER_VARIABLES_AND_MESH_COORDINATES." << endl; - SetRecording(RECORDING::TAG_INIT_SOLVER_AND_MESH, Kind_Tape::OBJECTIVE_FUNCTION_TAPE, ZONE_0); + if (rank == MASTER_NODE) cout << "\nChecking OBJECTIVE_FUNCTION_TAPE for MESH_COORDINATES." << endl; + SetRecording(RECORDING::MESH_COORDS, Kind_Tape::OBJECTIVE_FUNCTION_TAPE, ZONE_0); } else { - if (rank == MASTER_NODE) cout << "\nChecking OBJECTIVE_FUNCTION_TAPE for SOLVER_VARIABLES." << endl; - SetRecording(RECORDING::TAG_INIT_SOLVER_VARIABLES, Kind_Tape::OBJECTIVE_FUNCTION_TAPE, ZONE_0); + if (rank == MASTER_NODE) cout << "\nChecking OBJECTIVE_FUNCTION_TAPE for SOLUTION_VARIABLES." << endl; + SetRecording(RECORDING::SOLUTION_VARIABLES, Kind_Tape::OBJECTIVE_FUNCTION_TAPE, ZONE_0); } } else { if(driver_config->GetAD_CheckTapeVariables() == CHECK_TAPE_VARIABLES::MESH_COORDINATES) { - if (rank == MASTER_NODE) cout << "\nChecking FULL_SOLVER_TAPE for SOLVER_VARIABLES_AND_MESH_COORDINATES." << endl; - SetRecording(RECORDING::TAG_INIT_SOLVER_AND_MESH, Kind_Tape::FULL_SOLVER_TAPE, ZONE_0); + if (rank == MASTER_NODE) cout << "\nChecking FULL_SOLVER_TAPE for MESH_COORDINATES." << endl; + SetRecording(RECORDING::MESH_COORDS, Kind_Tape::FULL_SOLVER_TAPE, ZONE_0); } else { - if (rank == MASTER_NODE) cout << "\nChecking FULL_SOLVER_TAPE for SOLVER_VARIABLES." << endl; - SetRecording(RECORDING::TAG_INIT_SOLVER_VARIABLES, Kind_Tape::FULL_SOLVER_TAPE, ZONE_0); + if (rank == MASTER_NODE) cout << "\nChecking FULL_SOLVER_TAPE for SOLUTION_VARIABLES." << endl; + SetRecording(RECORDING::SOLUTION_VARIABLES, Kind_Tape::FULL_SOLVER_TAPE, ZONE_0); } } total_errors = TapeTestGatherErrors(error_report); @@ -315,18 +316,19 @@ void CDiscAdjMultizoneDriver::TapeTest() { * from the initial recording and a mismatch with the "check" recording tag will throw an error. * In such a case, a possible reason could be that such a variable is set by a post-processing routine while * for a mathematically correct recording this dependency must be included earlier. ---*/ + AD::SetTag(2); if(driver_config->GetAD_CheckTapeType() == CHECK_TAPE_TYPE::OBJECTIVE_FUNCTION) { if(driver_config->GetAD_CheckTapeVariables() == CHECK_TAPE_VARIABLES::MESH_COORDINATES) - SetRecording(RECORDING::TAG_CHECK_SOLVER_AND_MESH, Kind_Tape::OBJECTIVE_FUNCTION_TAPE, ZONE_0); + SetRecording(RECORDING::MESH_COORDS, Kind_Tape::OBJECTIVE_FUNCTION_TAPE, ZONE_0); else - SetRecording(RECORDING::TAG_CHECK_SOLVER_VARIABLES, Kind_Tape::OBJECTIVE_FUNCTION_TAPE, ZONE_0); + SetRecording(RECORDING::SOLUTION_VARIABLES, Kind_Tape::OBJECTIVE_FUNCTION_TAPE, ZONE_0); } else { if(driver_config->GetAD_CheckTapeVariables() == CHECK_TAPE_VARIABLES::MESH_COORDINATES) - SetRecording(RECORDING::TAG_CHECK_SOLVER_AND_MESH, Kind_Tape::FULL_SOLVER_TAPE, ZONE_0); + SetRecording(RECORDING::MESH_COORDS, Kind_Tape::FULL_SOLVER_TAPE, ZONE_0); else - SetRecording(RECORDING::TAG_CHECK_SOLVER_VARIABLES, Kind_Tape::FULL_SOLVER_TAPE, ZONE_0); + SetRecording(RECORDING::SOLUTION_VARIABLES, Kind_Tape::FULL_SOLVER_TAPE, ZONE_0); } total_errors += TapeTestGatherErrors(error_report); @@ -889,6 +891,12 @@ void CDiscAdjMultizoneDriver::SetObjFunction(RECORDING kind_recording) { solvers[HEAT_SOL]->Heat_Fluxes(geometry, solvers, config); } + if(config->GetBoolTurbomachinery()){ + /*--- Gather Inflow and Outflow quantities on the Master Node to compute performance ---*/ + solvers[FLOW_SOL]->GatherInOutAverageValues(config, geometry); + solvers[FLOW_SOL]->ComputeTurboBladePerformance(geometry, config, iZone); + } + direct_output[iZone]->SetHistoryOutput(geometry, solvers, config); ObjFunc += solvers[FLOW_SOL]->GetTotal_ComboObj(); break; diff --git a/SU2_CFD/src/drivers/CDiscAdjSinglezoneDriver.cpp b/SU2_CFD/src/drivers/CDiscAdjSinglezoneDriver.cpp index 5fcd203b84d0..195142357641 100644 --- a/SU2_CFD/src/drivers/CDiscAdjSinglezoneDriver.cpp +++ b/SU2_CFD/src/drivers/CDiscAdjSinglezoneDriver.cpp @@ -33,6 +33,7 @@ #include "../../include/iteration/CTurboIteration.hpp" #include "../../../Common/include/toolboxes/CQuasiNewtonInvLeastSquares.hpp" + CDiscAdjSinglezoneDriver::CDiscAdjSinglezoneDriver(char* confFile, unsigned short val_nZone, SU2_Comm MPICommunicator) : CSinglezoneDriver(confFile, diff --git a/SU2_CFD/src/drivers/CDriver.cpp b/SU2_CFD/src/drivers/CDriver.cpp index 63d485900987..5904ed9febb2 100644 --- a/SU2_CFD/src/drivers/CDriver.cpp +++ b/SU2_CFD/src/drivers/CDriver.cpp @@ -223,6 +223,13 @@ CDriverBase(confFile, val_nZone, MPICommunicator), StopCalc(false), fsi(false), CGeometry::ComputeWallDistance(config_container, geometry_container); } + if (config_container[ZONE_0]->GetBoolTurbomachinery()){ + if (rank == MASTER_NODE) + cout << endl <<"---------------------- Turbo-Vertex Preprocessing ---------------------" << endl; + + PreprocessTurboVertex(config_container, geometry_container, solver_container, interface_container, iteration_container, dummy_geo); + } + /*--- Definition of the interface and transfer conditions between different zones. ---*/ if (nZone > 1) { @@ -230,7 +237,7 @@ CDriverBase(confFile, val_nZone, MPICommunicator), StopCalc(false), fsi(false), cout << endl <<"------------------- Multizone Interface Preprocessing -------------------" << endl; InitializeInterface(config_container, solver_container, geometry_container, - interface_types, interface_container, interpolator_container); + interface_types, interface_container, interpolator_container); } if (fsi) { @@ -246,12 +253,9 @@ CDriverBase(confFile, val_nZone, MPICommunicator), StopCalc(false), fsi(false), if (rank == MASTER_NODE) cout << endl <<"---------------------- Turbomachinery Preprocessing ---------------------" << endl; - PreprocessTurbomachinery(config_container, geometry_container, solver_container, interface_container, dummy_geo); - } else { - mixingplane = false; + PreprocessTurbomachinery(config_container, geometry_container, solver_container, interface_container, iteration_container, dummy_geo); } - PreprocessPythonInterface(config_container, geometry_container, solver_container); @@ -312,7 +316,6 @@ void CDriver::InitializeContainers(){ grid_movement = nullptr; FFDBox = nullptr; interface_container = nullptr; - interface_types = nullptr; nInst = nullptr; /*--- Definition and of the containers for all possible zones. ---*/ @@ -338,7 +341,6 @@ void CDriver::InitializeContainers(){ interface_types[iZone] = new unsigned short[nZone]; nInst[iZone] = 1; } - } @@ -414,12 +416,12 @@ void CDriver::Finalize() { } delete [] interface_container; if (rank == MASTER_NODE) cout << "Deleted CInterface container." << endl; - } - if (interface_types != nullptr) { - for (iZone = 0; iZone < nZone; iZone++) - delete [] interface_types[iZone]; - delete [] interface_types; + if (interface_types != nullptr) { + for (iZone = 0; iZone < nZone; iZone++) + delete [] interface_types[iZone]; + delete [] interface_types; + } } for (iZone = 0; iZone < nZone; iZone++) { @@ -1283,14 +1285,14 @@ void CDriver::InstantiateTurbulentNumerics(unsigned short nVar_Turb, int offset, numerics[iMGlevel][TURB_SOL][conv_bound_term] = new CUpwSca_TurbSA(nDim, nVar_Turb, config); if (config->GetSAParsedOptions().version == SA_OPTIONS::NEG) { - numerics[iMGlevel][TURB_SOL][visc_bound_term] = new CAvgGrad_TurbSA_Neg(nDim, nVar_Turb, false, config); + numerics[iMGlevel][TURB_SOL][visc_bound_term] = new CAvgGrad_TurbSA_Neg(nDim, nVar_Turb, true, config); } else { - numerics[iMGlevel][TURB_SOL][visc_bound_term] = new CAvgGrad_TurbSA(nDim, nVar_Turb, false, config); + numerics[iMGlevel][TURB_SOL][visc_bound_term] = new CAvgGrad_TurbSA(nDim, nVar_Turb, true, config); } } else if (menter_sst) { numerics[iMGlevel][TURB_SOL][conv_bound_term] = new CUpwSca_TurbSST(nDim, nVar_Turb, config); - numerics[iMGlevel][TURB_SOL][visc_bound_term] = new CAvgGrad_TurbSST(nDim, nVar_Turb, constants, false, + numerics[iMGlevel][TURB_SOL][visc_bound_term] = new CAvgGrad_TurbSST(nDim, nVar_Turb, constants, true, config); } } @@ -2428,7 +2430,6 @@ void CDriver::InitializeInterface(CConfig **config, CSolver***** solver, CGeomet interface_type = NO_TRANSFER; /*--- If there is a common interface setup the interpolation and transfer. ---*/ - if (!CInterpolator::CheckZonesInterface(config[donor], config[target])) { interface_type = NO_COMMON_INTERFACE; } @@ -2439,8 +2440,11 @@ void CDriver::InitializeInterface(CConfig **config, CSolver***** solver, CGeomet /*--- Setup the interpolation. ---*/ - interpolation[donor][target] = unique_ptr(CInterpolatorFactory::CreateInterpolator( - geometry, config, interpolation[target][donor].get(), donor, target)); + if (!config[donor]->GetBoolTurbomachinery()) { + interpolation[donor][target] = unique_ptr(CInterpolatorFactory::CreateInterpolator( + geometry, config, interpolation[target][donor].get(), donor, target, false)); + if (rank == MASTER_NODE) cout << " Transferring "; + } /*--- Helpers with logic to create CHT interfaces. ---*/ @@ -2479,8 +2483,6 @@ void CDriver::InitializeInterface(CConfig **config, CSolver***** solver, CGeomet /*--- Initialize the appropriate transfer strategy. ---*/ - if (rank == MASTER_NODE) cout << " Transferring "; - if (fluid_donor && structural_target) { interface_type = FLOW_TRACTION; auto nConst = 2; @@ -2518,6 +2520,11 @@ void CDriver::InitializeInterface(CConfig **config, CSolver***** solver, CGeomet auto interfaceIndex = donor+target; // Here we assume that the interfaces at each side are the same kind switch (config[donor]->GetKind_TurboInterface(interfaceIndex)) { case TURBO_INTERFACE_KIND::MIXING_PLANE: { + interpolation[donor][target] = unique_ptr(CInterpolatorFactory::CreateInterpolator( + geometry, config, interpolation[target][donor].get(), donor, target, true)); + string fname = "TURBOMACHINERY/Mixing_Plane_Interpolator_Donor_" + to_string(donor) + "_Target_" + to_string(target) + ".dat"; + interpolation[donor][target]->WriteInterpolationDetails(fname, config); + if (rank == MASTER_NODE) cout << " Transferring "; interface_type = MIXING_PLANE; auto nVar = solver[donor][INST_0][MESH_0][FLOW_SOL]->GetnVar(); interface[donor][target] = new CMixingPlaneInterface(nVar, 0); @@ -2525,16 +2532,19 @@ void CDriver::InitializeInterface(CConfig **config, CSolver***** solver, CGeomet break; } case TURBO_INTERFACE_KIND::FROZEN_ROTOR: { - auto nVar = solver[donor][INST_0][MESH_0][FLOW_SOL]->GetnPrimVar(); + interpolation[donor][target] = unique_ptr(CInterpolatorFactory::CreateInterpolator( + geometry, config, interpolation[target][donor].get(), donor, target, false)); + if (rank == MASTER_NODE) cout << " Transferring "; interface_type = SLIDING_INTERFACE; + auto nVar = solver[donor][INST_0][MESH_0][FLOW_SOL]->GetnPrimVar(); interface[donor][target] = new CSlidingInterface(nVar, 0); if (rank == MASTER_NODE) cout << " Using a fluid interface interface from donor zone " << donor << " to target zone " << target << "." << endl; } } } else{ + interface_type = SLIDING_INTERFACE; auto nVar = solver[donor][INST_0][MESH_0][FLOW_SOL]->GetnPrimVar(); - interface_type = SLIDING_INTERFACE; interface[donor][target] = new CSlidingInterface(nVar, 0); if (rank == MASTER_NODE) cout << " Sliding interface." << endl; } @@ -2547,8 +2557,8 @@ void CDriver::InitializeInterface(CConfig **config, CSolver***** solver, CGeomet if (solver[donor][INST_0][MESH_0][FLOW_SOL] == nullptr) SU2_MPI::Error("Could not determine the number of variables for transfer.", CURRENT_FUNCTION); - auto nVar = solver[donor][INST_0][MESH_0][FLOW_SOL]->GetnVar(); interface_type = CONSERVATIVE_VARIABLES; + auto nVar = solver[donor][INST_0][MESH_0][FLOW_SOL]->GetnVar(); interface[donor][target] = new CConservativeVarsInterface(nVar, 0); if (rank == MASTER_NODE) cout << " Generic conservative variables." << endl; } @@ -2645,16 +2655,11 @@ void CDriver::PreprocessOutput(CConfig **config, CConfig *driver_config, COutput } +void CDriver::PreprocessTurboVertex(CConfig** config, CGeometry**** geometry, CSolver***** solver, + CInterface*** interface, CIteration*** iteration, bool dummy){ -void CDriver::PreprocessTurbomachinery(CConfig** config, CGeometry**** geometry, CSolver***** solver, - CInterface*** interface, bool dummy){ - - unsigned short donorZone,targetZone, nMarkerInt, iMarkerInt; unsigned short nSpanMax = 0; - bool restart = (config[ZONE_0]->GetRestart() || config[ZONE_0]->GetRestart_Flow()); mixingplane = config[ZONE_0]->GetBoolMixingPlaneInterface(); - bool discrete_adjoint = config[ZONE_0]->GetDiscrete_Adjoint(); - su2double areaIn, areaOut, nBlades, flowAngleIn, flowAngleOut; /*--- Create turbovertex structure ---*/ if (rank == MASTER_NODE) cout<InitTurboContainers(geometry[iZone][INST_0][MESH_0],config[iZone]); - } +void CDriver::PreprocessTurbomachinery(CConfig** config, CGeometry**** geometry, CSolver***** solver, + CInterface*** interface, CIteration*** iteration, bool dummy){ + unsigned short donorZone,targetZone; + bool restart = (config[ZONE_0]->GetRestart() || config[ZONE_0]->GetRestart_Flow()); + mixingplane = config[ZONE_0]->GetBoolMixingPlaneInterface(); + bool discrete_adjoint = config[ZONE_0]->GetDiscrete_Adjoint(); + su2double areaIn, areaOut, nBlades, flowAngleIn, flowAngleOut; // TODO(turbo): make it general for turbo HB if (rank == MASTER_NODE) cout<<"Compute inflow and outflow average geometric quantities." << endl; for (iZone = 0; iZone < nZone; iZone++) { geometry[iZone][INST_0][MESH_0]->SetAvgTurboValue(config[iZone], iZone, INFLOW, true); - geometry[iZone][INST_0][MESH_0]->SetAvgTurboValue(config[iZone],iZone, OUTFLOW, true); + geometry[iZone][INST_0][MESH_0]->SetAvgTurboValue(config[iZone], iZone, OUTFLOW, true); geometry[iZone][INST_0][MESH_0]->GatherInOutAverageValues(config[iZone], true); } + if (rank == MASTER_NODE) cout<<"Initialize solver containers for average quantities." << endl; + if (!dummy){ + for (iZone = 0; iZone < nZone; iZone++) { + solver[iZone][INST_0][MESH_0][FLOW_SOL]->InitTurboContainers(geometry[iZone][INST_0][MESH_0],config, iZone); + } + } if(mixingplane){ if (rank == MASTER_NODE) cout << "Set span-wise sections between zones on Mixing-Plane interface." << endl; @@ -2708,10 +2722,6 @@ void CDriver::PreprocessTurbomachinery(CConfig** config, CGeometry**** geometry, } } - for (iZone = 0; iZone < nZone-1; iZone++) { - geometry[nZone-1][INST_0][MESH_0]->SetAvgTurboGeoValues(config[iZone],geometry[iZone][INST_0][MESH_0], iZone); - } - /*--- Transfer number of blade to ZONE_0 to correctly compute turbo performance---*/ for (iZone = 1; iZone < nZone; iZone++) { nBlades = config[iZone]->GetnBlades(iZone); @@ -2729,23 +2739,6 @@ void CDriver::PreprocessTurbomachinery(CConfig** config, CGeometry**** geometry, } } - - if(mixingplane){ - if (rank == MASTER_NODE) cout<<"Preprocessing of the Mixing-Plane Interface." << endl; - for (donorZone = 0; donorZone < nZone; donorZone++) { - nMarkerInt = config_container[donorZone]->GetnMarker_MixingPlaneInterface()/2; - for (iMarkerInt = 1; iMarkerInt <= nMarkerInt; iMarkerInt++){ - for (targetZone = 0; targetZone < nZone; targetZone++) { - if (interface_types[donorZone][targetZone]==MIXING_PLANE){ - interface[donorZone][targetZone]->PreprocessAverage(geometry[donorZone][INST_0][MESH_0], geometry[targetZone][INST_0][MESH_0], - config[donorZone], config[targetZone], - iMarkerInt); - } - } - } - } - } - if(!restart && !discrete_adjoint){ if (rank == MASTER_NODE) cout<<"Initialize turbomachinery solution quantities." << endl; for(iZone = 0; iZone < nZone; iZone++) { @@ -2753,6 +2746,7 @@ void CDriver::PreprocessTurbomachinery(CConfig** config, CGeometry**** geometry, } } + if (dummy) return; // No need to go further for a dummy run if (rank == MASTER_NODE) cout<<"Initialize inflow and outflow average solution quantities." << endl; for(iZone = 0; iZone < nZone; iZone++) { solver[iZone][INST_0][MESH_0][FLOW_SOL]->PreprocessAverage(solver[iZone][INST_0][MESH_0], geometry[iZone][INST_0][MESH_0],config[iZone],INFLOW); @@ -2769,10 +2763,8 @@ void CDriver::PreprocessTurbomachinery(CConfig** config, CGeometry**** geometry, flowAngleOut /= solver[iZone][INST_0][MESH_0][FLOW_SOL]->GetTurboVelocityOut(iZone, config[iZone]->GetnSpanWiseSections())[0]; flowAngleOut = atan(flowAngleOut)*180.0/PI_NUMBER; cout << "Outlet flow angle for Row "<< iZone + 1<< ": "<< flowAngleOut <<"°." <GetMultizone_Problem(); + const unsigned short fieldWidth = 25; - /*--- Helper lambda func to return lenghty [iVar][iZone] string. ---*/ - auto iVar_iZone2string = [&](unsigned short ivar, unsigned short izone) { - if (multizone) { - return "[" + std::to_string(ivar) + "][" + std::to_string(izone) + "]"; - } - return "[" + std::to_string(ivar) + "]"; - }; + /*--- Table for Residual Values ---*/ + PrintingToolbox::CTablePrinter ResidualTable(&std::cout); + ResidualTable.SetPrecision(config_container[ZONE_0]->GetOutput_Precision()); + ResidualTable.SetAlign(PrintingToolbox::CTablePrinter::RIGHT); - /*--- Print residuals in the first iteration ---*/ + std::cout << "\n-- Direct Residual Summary:" << std::endl; - const unsigned short fieldWidth = 15; - PrintingToolbox::CTablePrinter RMSTable(&std::cout); - RMSTable.SetPrecision(config_container[ZONE_0]->GetOutput_Precision()); + /*--- Setup table columns ---*/ + ResidualTable.AddColumn("Residual", fieldWidth); + ResidualTable.AddColumn("log10(RMS)", fieldWidth); + ResidualTable.PrintHeader(); - /*--- The CTablePrinter requires two sweeps: - *--- 0. Add the colum names (addVals=0=false) plus CTablePrinter.PrintHeader() - *--- 1. Add the RMS-residual values (addVals=1=true) plus CTablePrinter.PrintFooter() ---*/ - for (int addVals = 0; addVals < 2; addVals++) { + /*--- Loop through each zone ---*/ + for (unsigned short iZone = 0; iZone < nZone; iZone++) { - for (unsigned short iZone = 0; iZone < nZone; iZone++) { + auto solvers = solver_container[iZone][INST_0][MESH_0]; + auto configs = config_container[iZone]; - auto solvers = solver_container[iZone][INST_0][MESH_0]; - auto configs = config_container[iZone]; + /*--- Print zone header ---*/ + if (multizone) { + ResidualTable << "ZONE " + std::to_string(iZone) << ""; + ResidualTable.PrintFooter(); + } - /*--- Note: the FEM-Flow solvers are availalbe for disc. adjoint runs only for SingleZone. ---*/ - if (configs->GetFluidProblem() || configs->GetFEMSolver()) { + /*--- Fluid or FEM-Flow Problems ---*/ + if (configs->GetFluidProblem() || configs->GetFEMSolver()) { - for (unsigned short iVar = 0; iVar < solvers[FLOW_SOL]->GetnVar(); iVar++) { - if (!addVals) - RMSTable.AddColumn("rms_Flow" + iVar_iZone2string(iVar, iZone), fieldWidth); - else - RMSTable << log10(solvers[FLOW_SOL]->GetRes_RMS(iVar)); - } + /*--- Flow residuals ---*/ + for (unsigned short iVar = 0; iVar < solvers[FLOW_SOL]->GetnVar(); iVar++) { + std::string varName = "rms_Flow[" + std::to_string(iVar) + "]"; + ResidualTable << varName << log10(solvers[FLOW_SOL]->GetRes_RMS(iVar)); + } - if (configs->GetKind_Turb_Model() != TURB_MODEL::NONE && !configs->GetFrozen_Visc_Disc()) { - for (unsigned short iVar = 0; iVar < solvers[TURB_SOL]->GetnVar(); iVar++) { - if (!addVals) - RMSTable.AddColumn("rms_Turb" + iVar_iZone2string(iVar, iZone), fieldWidth); - else - RMSTable << log10(solvers[TURB_SOL]->GetRes_RMS(iVar)); - } + /*--- Turbulence residuals ---*/ + if (configs->GetKind_Turb_Model() != TURB_MODEL::NONE && !configs->GetFrozen_Visc_Disc()) { + for (unsigned short iVar = 0; iVar < solvers[TURB_SOL]->GetnVar(); iVar++) { + std::string varName = "rms_Turb[" + std::to_string(iVar) + "]"; + ResidualTable << varName << log10(solvers[TURB_SOL]->GetRes_RMS(iVar)); } + } - if (configs->GetKind_Species_Model() != SPECIES_MODEL::NONE) { - for (unsigned short iVar = 0; iVar < solvers[SPECIES_SOL]->GetnVar(); iVar++) { - if (!addVals) - RMSTable.AddColumn("rms_Spec" + iVar_iZone2string(iVar, iZone), fieldWidth); - else - RMSTable << log10(solvers[SPECIES_SOL]->GetRes_RMS(iVar)); - } + /*--- Species residuals ---*/ + if (configs->GetKind_Species_Model() != SPECIES_MODEL::NONE) { + for (unsigned short iVar = 0; iVar < solvers[SPECIES_SOL]->GetnVar(); iVar++) { + std::string varName = "rms_Spec[" + std::to_string(iVar) + "]"; + ResidualTable << varName << log10(solvers[SPECIES_SOL]->GetRes_RMS(iVar)); } + } - if (!multizone && configs->GetWeakly_Coupled_Heat()){ - if (!addVals) RMSTable.AddColumn("rms_Heat" + iVar_iZone2string(0, iZone), fieldWidth); - else RMSTable << log10(solvers[HEAT_SOL]->GetRes_RMS(0)); - } + /*--- Heat residuals (weakly coupled) ---*/ + if (!multizone && configs->GetWeakly_Coupled_Heat()) { + ResidualTable << "rms_Heat[0]" << log10(solvers[HEAT_SOL]->GetRes_RMS(0)); + } - if (configs->AddRadiation()) { - if (!addVals) RMSTable.AddColumn("rms_Rad" + iVar_iZone2string(0, iZone), fieldWidth); - else RMSTable << log10(solvers[RAD_SOL]->GetRes_RMS(0)); - } - } else if (configs->GetStructuralProblem()) { - if (configs->GetGeometricConditions() == STRUCT_DEFORMATION::LARGE){ - if (!addVals) { - RMSTable.AddColumn("UTOL-A", fieldWidth); - RMSTable.AddColumn("RTOL-A", fieldWidth); - RMSTable.AddColumn("ETOL-A", fieldWidth); - } else { - RMSTable << log10(solvers[FEA_SOL]->GetRes_FEM(0)) - << log10(solvers[FEA_SOL]->GetRes_FEM(1)) - << log10(solvers[FEA_SOL]->GetRes_FEM(2)); - } - } else { - if (!addVals) { - RMSTable.AddColumn("log10[RMS Ux]", fieldWidth); - RMSTable.AddColumn("log10[RMS Uy]", fieldWidth); - if (nDim == 3) RMSTable.AddColumn("log10[RMS Uz]", fieldWidth); - } else { - RMSTable << log10(solvers[FEA_SOL]->GetRes_FEM(0)) - << log10(solvers[FEA_SOL]->GetRes_FEM(1)); - if (nDim == 3) RMSTable << log10(solvers[FEA_SOL]->GetRes_FEM(2)); - } - } - if (configs->GetWeakly_Coupled_Heat()){ - if (!addVals) RMSTable.AddColumn("rms_Heat", fieldWidth); - else RMSTable << log10(solvers[HEAT_SOL]->GetRes_RMS(0)); - } - } else if (configs->GetHeatProblem()) { + /*--- Radiation residuals ---*/ + if (configs->AddRadiation()) { + ResidualTable << "rms_Rad[0]" << log10(solvers[RAD_SOL]->GetRes_RMS(0)); + } - if (!addVals) RMSTable.AddColumn("rms_Heat" + iVar_iZone2string(0, iZone), fieldWidth); - else RMSTable << log10(solvers[HEAT_SOL]->GetRes_RMS(0)); - } else { - SU2_MPI::Error("Invalid KindSolver for CDiscAdj-MultiZone/SingleZone-Driver.", CURRENT_FUNCTION); + } + /*--- Structural Problems ---*/ + else if (configs->GetStructuralProblem()) { + + if (configs->GetGeometricConditions() == STRUCT_DEFORMATION::LARGE) { + ResidualTable << "UTOL-A" << log10(solvers[FEA_SOL]->GetRes_FEM(0)); + ResidualTable << "RTOL-A" << log10(solvers[FEA_SOL]->GetRes_FEM(1)); + ResidualTable << "ETOL-A" << log10(solvers[FEA_SOL]->GetRes_FEM(2)); } - } // loop iZone + else { + ResidualTable << "RMS Ux" << log10(solvers[FEA_SOL]->GetRes_FEM(0)); + ResidualTable << "RMS Uy" << log10(solvers[FEA_SOL]->GetRes_FEM(1)); + if (nDim == 3) { + ResidualTable << "RMS Uz" << log10(solvers[FEA_SOL]->GetRes_FEM(2)); + } + } + + } + /*--- Heat Problems ---*/ + else if (configs->GetHeatProblem()) { + + ResidualTable << "rms_Heat[0]" << log10(solvers[HEAT_SOL]->GetRes_RMS(0)); - if (!addVals) RMSTable.PrintHeader(); - else RMSTable.PrintFooter(); + } + else { + SU2_MPI::Error("Invalid KindSolver for CDiscAdj-MultiZone/SingleZone-Driver.", CURRENT_FUNCTION); + } - } // for addVals + /*--- Print zone footer ---*/ + if (multizone) { + ResidualTable.PrintFooter(); + } + + } + + /*--- Print final footer for single zone ---*/ + if (!multizone) { + ResidualTable.PrintFooter(); + } } @@ -3004,7 +2995,7 @@ void CFluidDriver::Run() { for (iZone = 0; iZone < nZone; iZone++) { for (jZone = 0; jZone < nZone; jZone++) if(jZone != iZone && interpolator_container[iZone][jZone] != nullptr) - interpolator_container[iZone][jZone]->SetTransferCoeff(config_container); + interpolator_container[iZone][jZone]->SetTransferCoeff(geometry_container, config_container); } } diff --git a/SU2_CFD/src/drivers/CMultizoneDriver.cpp b/SU2_CFD/src/drivers/CMultizoneDriver.cpp index 00bdb364a449..aa7422f56505 100644 --- a/SU2_CFD/src/drivers/CMultizoneDriver.cpp +++ b/SU2_CFD/src/drivers/CMultizoneDriver.cpp @@ -274,7 +274,7 @@ void CMultizoneDriver::Preprocess(unsigned long TimeIter) { for (iZone = 0; iZone < nZone; iZone++) { for (unsigned short jZone = 0; jZone < nZone; jZone++){ if(jZone != iZone && interpolator_container[iZone][jZone] != nullptr && prefixed_motion[iZone]) - interpolator_container[iZone][jZone]->SetTransferCoeff(config_container); + interpolator_container[iZone][jZone]->SetTransferCoeff(geometry_container, config_container); } } } @@ -548,6 +548,7 @@ bool CMultizoneDriver::TransferData(unsigned short donorZone, unsigned short tar bool UpdateMesh = false; /*--- Select the transfer method according to the magnitudes being transferred ---*/ + if(donorZone == targetZone || interface_container[donorZone][targetZone] == nullptr) return UpdateMesh; // Zones are equal or unconnected auto HandleInterfaceType = [&] (const auto interface_type, auto* interface) { auto BroadcastData = [&](int donorSol, int targetSol) { @@ -567,7 +568,10 @@ bool CMultizoneDriver::TransferData(unsigned short donorZone, unsigned short tar /*--- Additional transfer for turbulence variables. ---*/ if (config_container[targetZone]->GetKind_Solver() == MAIN_SOLVER::RANS || - config_container[targetZone]->GetKind_Solver() == MAIN_SOLVER::INC_RANS) { + config_container[targetZone]->GetKind_Solver() == MAIN_SOLVER::INC_RANS || + config_container[targetZone]->GetKind_Solver() == MAIN_SOLVER::DISC_ADJ_RANS || + config_container[targetZone]->GetKind_Solver() == MAIN_SOLVER::DISC_ADJ_INC_RANS + ) { BroadcastData(TURB_SOL, TURB_SOL); } @@ -598,33 +602,18 @@ bool CMultizoneDriver::TransferData(unsigned short donorZone, unsigned short tar case FLOW_TRACTION: BroadcastData(FLOW_SOL, FEA_SOL); break; - case MIXING_PLANE: { - const auto nMarkerInt = config_container[donorZone]->GetnMarker_MixingPlaneInterface() / 2; - - /*--- Transfer the average value from the donorZone to the targetZone - * Loops over the mixing planes defined in the config file to find the - * correct mixing plane for the donor-target combination ---*/ - for (auto iMarkerInt = 1; iMarkerInt <= nMarkerInt; iMarkerInt++) { - interface->AllgatherAverage( - solver_container[donorZone][INST_0][MESH_0][FLOW_SOL], - solver_container[targetZone][INST_0][MESH_0][FLOW_SOL], - geometry_container[donorZone][INST_0][MESH_0], - geometry_container[targetZone][INST_0][MESH_0], - config_container[donorZone], config_container[targetZone], iMarkerInt); - } - - /*--- Set average value donorZone->targetZone ---*/ - interface->SetAverageValues(solver_container[donorZone][INST_0][MESH_0][FLOW_SOL], - solver_container[targetZone][INST_0][MESH_0][FLOW_SOL], donorZone); - - /*--- Set average geometrical properties FROM donorZone IN targetZone ---*/ - geometry_container[targetZone][INST_0][MESH_0]->SetAvgTurboGeoValues( - config_container[iZone], geometry_container[iZone][INST_0][MESH_0], iZone); - } break; - case NO_TRANSFER: - case ZONES_ARE_EQUAL: - case NO_COMMON_INTERFACE: + case MIXING_PLANE: + { + interface_container[donorZone][targetZone]->BroadcastData_MixingPlane( + *interpolator_container[donorZone][targetZone].get(), + solver_container[donorZone][INST_0][MESH_0][FLOW_SOL], + solver_container[targetZone][INST_0][MESH_0][FLOW_SOL], + geometry_container[donorZone][INST_0][MESH_0], + geometry_container[targetZone][INST_0][MESH_0], + config_container[donorZone], + config_container[targetZone]); break; + } default: if (rank == MASTER_NODE) { cout << "WARNING: One of the intended interface transfer routines is not " @@ -646,17 +635,6 @@ bool CMultizoneDriver::TransferData(unsigned short donorZone, unsigned short tar return UpdateMesh; } - - -void CMultizoneDriver::SetTurboPerformance() { - SU2_ZONE_SCOPED - for (auto donorZone = 1u; donorZone < nZone; donorZone++) { - interface_container[donorZone][ZONE_0]->SetAverageValues(solver_container[donorZone][INST_0][MESH_0][FLOW_SOL], - solver_container[ZONE_0][INST_0][MESH_0][FLOW_SOL], - donorZone); - } -} - bool CMultizoneDriver::Monitor(unsigned long TimeIter) { SU2_ZONE_SCOPED diff --git a/SU2_CFD/src/integration/CIntegration.cpp b/SU2_CFD/src/integration/CIntegration.cpp index 21024a317945..1e24f9768c4a 100644 --- a/SU2_CFD/src/integration/CIntegration.cpp +++ b/SU2_CFD/src/integration/CIntegration.cpp @@ -89,19 +89,9 @@ void CIntegration::Space_Integration(CGeometry *geometry, if (iMesh == MESH_0 && config->GetBoolGiles() && config->GetSpatialFourier()){ solver_container[MainSolver]->PreprocessBC_Giles(geometry, config, conv_bound_numerics, INFLOW); - solver_container[MainSolver]->PreprocessBC_Giles(geometry, config, conv_bound_numerics, OUTFLOW); } - BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { - if (iMesh == MESH_0 && config->GetBoolTurbomachinery()){ - /*--- Average quantities at the inflow and outflow boundaries ---*/ - solver_container[MainSolver]->TurboAverageProcess(solver_container, geometry,config,INFLOW); - solver_container[MainSolver]->TurboAverageProcess(solver_container, geometry, config, OUTFLOW); - } - } - END_SU2_OMP_SAFE_GLOBAL_ACCESS - /*--- Weak boundary conditions ---*/ for (iMarker = 0; iMarker < config->GetnMarker_All(); iMarker++) { diff --git a/SU2_CFD/src/integration/CMultiGridIntegration.cpp b/SU2_CFD/src/integration/CMultiGridIntegration.cpp index 1c0b35ad53d4..f8ca90cd37fd 100644 --- a/SU2_CFD/src/integration/CMultiGridIntegration.cpp +++ b/SU2_CFD/src/integration/CMultiGridIntegration.cpp @@ -1012,21 +1012,6 @@ void CMultiGridIntegration::NonDimensional_Parameters(CGeometry **geometry, CSol solver_container[FinestMesh][FLOW_SOL]->Momentum_Forces(geometry[FinestMesh], config); solver_container[FinestMesh][FLOW_SOL]->Friction_Forces(geometry[FinestMesh], config); - /*--- Calculate the turbo performance (only on the fine grid; turbo - * geometry data is only available on MESH_0). ---*/ - if (config->GetBoolTurbomachinery() && FinestMesh == MESH_0){ - - /*--- Average quantities at the inflow and outflow boundaries ---*/ - - solver_container[FinestMesh][FLOW_SOL]->TurboAverageProcess(solver_container[FinestMesh], geometry[FinestMesh],config,INFLOW); - solver_container[FinestMesh][FLOW_SOL]->TurboAverageProcess(solver_container[FinestMesh], geometry[FinestMesh], config, OUTFLOW); - - /*--- Gather Inflow and Outflow quantities on the Master Node to compute performance ---*/ - - solver_container[FinestMesh][FLOW_SOL]->GatherInOutAverageValues(config, geometry[FinestMesh]); - - } - break; case RUNTIME_ADJFLOW_SYS: diff --git a/SU2_CFD/src/interfaces/CInterface.cpp b/SU2_CFD/src/interfaces/CInterface.cpp index 55537c325279..ce189fd15dc6 100644 --- a/SU2_CFD/src/interfaces/CInterface.cpp +++ b/SU2_CFD/src/interfaces/CInterface.cpp @@ -200,444 +200,4 @@ void CInterface::BroadcastData(const CInterpolator& interpolator, SetTarget_Variable(target_solution, target_geometry, target_config, markTarget, iVertex, iPoint); } } -} - -void CInterface::PreprocessAverage(CGeometry *donor_geometry, CGeometry *target_geometry, - const CConfig *donor_config, const CConfig *target_config, - unsigned short iMarkerInt){ - - unsigned short nMarkerDonor, nMarkerTarget; // Number of markers on the interface, donor and target side - unsigned short iMarkerDonor, iMarkerTarget; // Variables for iteration over markers - unsigned short iSpan,jSpan, tSpan = 0, kSpan = 0, nSpanDonor, nSpanTarget, Donor_Flag = 0, Target_Flag = 0; - int Marker_Donor = -1, Marker_Target = -1; - - const su2double *SpanValuesDonor, *SpanValuesTarget; - su2double dist, test, dist2, test2; - - nMarkerDonor = donor_geometry->GetnMarker(); - nMarkerTarget = target_geometry->GetnMarker(); - //TODO turbo this approach only works if all the turboamchinery marker - // of all zones have the same amount of span wise sections. - //TODO turbo initialization needed for the MPI routine should be place somewhere else. - nSpanDonor = donor_config->GetnSpanWiseSections(); - nSpanTarget = target_config->GetnSpanWiseSections(); - - /*--- On the donor side ---*/ - for (iMarkerDonor = 0; iMarkerDonor < nMarkerDonor; iMarkerDonor++){ - /*--- If the tag GetMarker_All_MixingPlaneInterface equals the index we are looping at ---*/ - if ( donor_config->GetMarker_All_MixingPlaneInterface(iMarkerDonor) == iMarkerInt ){ - /*--- We have identified the local index of the Donor marker ---*/ - /*--- Now we are going to store the average values that belong to Marker_Donor on each processor ---*/ - /*--- Store the identifier for the structural marker ---*/ - Marker_Donor = iMarkerDonor; - Donor_Flag = donor_config->GetMarker_All_TurbomachineryFlag(iMarkerDonor); - /*--- Exit the for loop: we have found the local index for Mixing-Plane interface ---*/ - break; - } - /*--- If the tag hasn't matched any tag within the donor markers ---*/ - Marker_Donor = -1; - Donor_Flag = -1; - - } - -#ifdef HAVE_MPI - auto BuffMarkerDonor = new int[size]; - auto BuffDonorFlag = new int[size]; - for (int iSize=0; iSize= 0.0){ - Marker_Donor = BuffMarkerDonor[iSize]; - Donor_Flag = BuffDonorFlag[iSize]; - break; - } - } - delete [] BuffMarkerDonor; - delete [] BuffDonorFlag; -#endif - - /*--- On the target side we have to identify the marker as well ---*/ - - for (iMarkerTarget = 0; iMarkerTarget < nMarkerTarget; iMarkerTarget++){ - /*--- If the tag GetMarker_All_MixingPlaneInterface(iMarkerTarget) equals the index we are looping at ---*/ - if ( target_config->GetMarker_All_MixingPlaneInterface(iMarkerTarget) == iMarkerInt ){ - /*--- Store the identifier for the fluid marker ---*/ - - // here i should then store it in the target zone - - Marker_Target = iMarkerTarget; - Target_Flag = target_config->GetMarker_All_TurbomachineryFlag(iMarkerTarget); - /*--- Exit the for loop: we have found the local index for iMarkerFSI on the FEA side ---*/ - break; - } - /*--- If the tag hasn't matched any tag within the Flow markers ---*/ - Marker_Target = -1; - Target_Flag = -1; - } - - if (Marker_Target != -1 && Marker_Donor != -1){ - - SpanValuesDonor = donor_geometry->GetSpanWiseValue(Donor_Flag); - SpanValuesTarget = target_geometry->GetSpanWiseValue(Target_Flag); - - - for(iSpan = 1; iSpan SpanValuesDonor[jSpan]){ - dist = test; - kSpan = jSpan; - } - if(test2 < dist2){ - dist2 = test2; - tSpan = jSpan; - } - - } - switch(donor_config->GetKind_MixingPlaneInterface()){ - case MATCHING: - SpanLevelDonor[iSpan] = iSpan; - SpanValueCoeffTarget[iSpan] = 0.0; - break; - case NEAREST_SPAN: - SpanLevelDonor[iSpan] = tSpan; - SpanValueCoeffTarget[iSpan] = 0.0; - break; - case LINEAR_INTERPOLATION: - SpanLevelDonor[iSpan] = kSpan; - SpanValueCoeffTarget[iSpan] = (SpanValuesTarget[iSpan] - SpanValuesDonor[kSpan]) - /(SpanValuesDonor[kSpan + 1] - SpanValuesDonor[kSpan]); - break; - default: - SU2_MPI::Error("MixingPlane interface option not implemented yet", CURRENT_FUNCTION); - break; - - } - } - } - -} - - -void CInterface::AllgatherAverage(CSolver *donor_solution, CSolver *target_solution, - CGeometry *donor_geometry, CGeometry *target_geometry, - const CConfig *donor_config, const CConfig *target_config, unsigned short iMarkerInt){ - - unsigned short nMarkerDonor, nMarkerTarget; // Number of markers on the interface, donor and target side - unsigned short iMarkerDonor, iMarkerTarget; // Variables for iteration over markers - unsigned short iSpan, nSpanDonor, nSpanTarget; - int Marker_Donor = -1, Marker_Target = -1; - su2double *avgPressureDonor = nullptr, *avgDensityDonor = nullptr, *avgNormalVelDonor = nullptr, - *avgTangVelDonor = nullptr, *avg3DVelDonor = nullptr, *avgNuDonor = nullptr, - *avgOmegaDonor = nullptr, *avgKineDonor = nullptr; - su2double *avgPressureTarget = nullptr, *avgDensityTarget = nullptr, *avgNormalVelTarget = nullptr, - *avg3DVelTarget = nullptr, *avgTangVelTarget = nullptr, *avgNuTarget = nullptr, - *avgOmegaTarget = nullptr, *avgKineTarget = nullptr; - -#ifdef HAVE_MPI - int iSize; - su2double *BuffAvgPressureDonor = nullptr, *BuffAvgDensityDonor = nullptr, *BuffAvgNormalVelDonor = nullptr, - *BuffAvg3DVelDonor = nullptr, *BuffAvgTangVelDonor = nullptr, *BuffAvgNuDonor = nullptr, - *BuffAvgKineDonor = nullptr, *BuffAvgOmegaDonor = nullptr; - int nSpanSize, *BuffMarkerDonor; -#endif - - - nMarkerTarget = target_geometry->GetnMarker(); - nMarkerDonor = donor_geometry->GetnMarker(); - nSpanDonor = donor_config->GetnSpanWiseSections() +1; - nSpanTarget = target_config->GetnSpanWiseSections() +1; - - - avgDensityDonor = new su2double[nSpanDonor]; - avgPressureDonor = new su2double[nSpanDonor]; - avgNormalVelDonor = new su2double[nSpanDonor]; - avgTangVelDonor = new su2double[nSpanDonor]; - avg3DVelDonor = new su2double[nSpanDonor]; - avgNuDonor = new su2double[nSpanDonor]; - avgKineDonor = new su2double[nSpanDonor]; - avgOmegaDonor = new su2double[nSpanDonor]; - - for (iSpan = 0; iSpan < nSpanDonor; iSpan++){ - avgDensityDonor[iSpan] = -1.0; - avgPressureDonor[iSpan] = -1.0; - avgNormalVelDonor[iSpan] = -1.0; - avgTangVelDonor[iSpan] = -1.0; - avg3DVelDonor[iSpan] = -1.0; - avgNuDonor[iSpan] = -1.0; - avgKineDonor[iSpan] = -1.0; - avgOmegaDonor[iSpan] = -1.0; - } - - avgDensityTarget = new su2double[nSpanTarget]; - avgPressureTarget = new su2double[nSpanTarget]; - avgNormalVelTarget = new su2double[nSpanTarget]; - avgTangVelTarget = new su2double[nSpanTarget]; - avg3DVelTarget = new su2double[nSpanTarget]; - avgNuTarget = new su2double[nSpanTarget]; - avgKineTarget = new su2double[nSpanTarget]; - avgOmegaTarget = new su2double[nSpanTarget]; - - - for (iSpan = 0; iSpan < nSpanTarget; iSpan++){ - avgDensityTarget[iSpan] = -1.0; - avgPressureTarget[iSpan] = -1.0; - avgNormalVelTarget[iSpan] = -1.0; - avgTangVelTarget[iSpan] = -1.0; - avg3DVelTarget[iSpan] = -1.0; - avgNuTarget[iSpan] = -1.0; - avgKineTarget[iSpan] = -1.0; - avgOmegaTarget[iSpan] = -1.0; - } - - /*--- Outer loop over the markers on the Mixing-Plane interface: compute one by one ---*/ - /*--- The tags are always an integer greater than 1: loop from 1 to nMarkerMixingPlane ---*/ - Marker_Donor = -1; - Marker_Target = -1; - - /*--- The donor and target markers are tagged with the same index. - *--- This is independent of the MPI domain decomposition. - *--- We need to loop over all markers on both sides ---*/ - - /*--- On the donor side ---*/ - - for (iMarkerDonor = 0; iMarkerDonor < nMarkerDonor; iMarkerDonor++){ - /*--- If the tag GetMarker_All_MixingPlaneInterface equals the index we are looping at ---*/ - if ( donor_config->GetMarker_All_MixingPlaneInterface(iMarkerDonor) == iMarkerInt ){ - /*--- We have identified the local index of the Donor marker ---*/ - /*--- Now we are going to store the average values that belong to Marker_Donor on each processor ---*/ - /*--- Store the identifier for the structural marker ---*/ - Marker_Donor = iMarkerDonor; - /*--- Exit the for loop: we have found the local index for Mixing-Plane interface ---*/ - break; - } - /*--- If the tag hasn't matched any tag within the donor markers ---*/ - Marker_Donor = -1; - - } - /*--- Here we want to make available the quantities for all the processors and collect them in a buffer - * for each span of the donor the span-wise height vector also so - * that then we can interpolate on the target side ---*/ - if (Marker_Donor != -1){ - for(iSpan = 0; iSpan < nSpanDonor; iSpan++){ - GetDonor_Variable(donor_solution, donor_geometry, donor_config, Marker_Donor, iSpan, rank); - avgDensityDonor[iSpan] = Donor_Variable[0]; - avgPressureDonor[iSpan] = Donor_Variable[1]; - avgNormalVelDonor[iSpan] = Donor_Variable[2]; - avgTangVelDonor[iSpan] = Donor_Variable[3]; - avg3DVelDonor[iSpan] = Donor_Variable[4]; - avgNuDonor[iSpan] = Donor_Variable[5]; - avgKineDonor[iSpan] = Donor_Variable[6]; - avgOmegaDonor[iSpan] = Donor_Variable[7]; - } - } - -#ifdef HAVE_MPI - nSpanSize = size*nSpanDonor; - BuffAvgDensityDonor = new su2double[nSpanSize]; - BuffAvgPressureDonor = new su2double[nSpanSize]; - BuffAvgNormalVelDonor = new su2double[nSpanSize]; - BuffAvgTangVelDonor = new su2double[nSpanSize]; - BuffAvg3DVelDonor = new su2double[nSpanSize]; - BuffAvgNuDonor = new su2double[nSpanSize]; - BuffAvgKineDonor = new su2double[nSpanSize]; - BuffAvgOmegaDonor = new su2double[nSpanSize]; - BuffMarkerDonor = new int[size]; - - for (iSpan=0;iSpan 0.0){ - for (iSpan = 0; iSpan < nSpanDonor; iSpan++){ - avgDensityDonor[iSpan] = BuffAvgDensityDonor[nSpanDonor*iSize + iSpan]; - avgPressureDonor[iSpan] = BuffAvgPressureDonor[nSpanDonor*iSize + iSpan]; - avgNormalVelDonor[iSpan] = BuffAvgNormalVelDonor[nSpanDonor*iSize + iSpan]; - avgTangVelDonor[iSpan] = BuffAvgTangVelDonor[nSpanDonor*iSize + iSpan]; - avg3DVelDonor[iSpan] = BuffAvg3DVelDonor[nSpanDonor*iSize + iSpan]; - avgNuDonor[iSpan] = BuffAvgNuDonor[nSpanDonor*iSize + iSpan]; - avgKineDonor[iSpan] = BuffAvgKineDonor[nSpanDonor*iSize + iSpan]; - avgOmegaDonor[iSpan] = BuffAvgOmegaDonor[nSpanDonor*iSize + iSpan]; - } - Marker_Donor = BuffMarkerDonor[iSize]; - break; - } - } - delete [] BuffAvgDensityDonor; - delete [] BuffAvgPressureDonor; - delete [] BuffAvgNormalVelDonor; - delete [] BuffAvgTangVelDonor; - delete [] BuffAvg3DVelDonor; - delete [] BuffAvgNuDonor; - delete [] BuffAvgKineDonor; - delete [] BuffAvgOmegaDonor; - delete [] BuffMarkerDonor; -#endif - - /*--- On the target side we have to identify the marker as well ---*/ - for (iMarkerTarget = 0; iMarkerTarget < nMarkerTarget; iMarkerTarget++){ - /*--- If the tag GetMarker_All_MixingPlaneInterface(iMarkerTarget) equals the index we are looping at ---*/ - if ( target_config->GetMarker_All_MixingPlaneInterface(iMarkerTarget) == iMarkerInt ){ - /*--- Store the identifier for the fluid marker ---*/ - Marker_Target = iMarkerTarget; - /*--- Exit the for loop: we have found the local index for iMarkerFSI on the FEA side ---*/ - break; - } - /*--- If the tag hasn't matched any tag within the Flow markers ---*/ - Marker_Target = -1; - - } - - - if (Marker_Target != -1 && Marker_Donor != -1){ - - /*--- linear interpolation of the average value of for the internal span-wise levels ---*/ - for(iSpan = 1; iSpan < nSpanTarget -2 ; iSpan++){ - avgDensityTarget[iSpan] = SpanValueCoeffTarget[iSpan]*(avgDensityDonor[SpanLevelDonor[iSpan] + 1] - - avgDensityDonor[SpanLevelDonor[iSpan]]); - avgDensityTarget[iSpan] += avgDensityDonor[SpanLevelDonor[iSpan]]; - avgPressureTarget[iSpan] = SpanValueCoeffTarget[iSpan]*(avgPressureDonor[SpanLevelDonor[iSpan] + 1] - - avgPressureDonor[SpanLevelDonor[iSpan]]); - avgPressureTarget[iSpan] += avgPressureDonor[SpanLevelDonor[iSpan]]; - avgNormalVelTarget[iSpan] = SpanValueCoeffTarget[iSpan]*(avgNormalVelDonor[SpanLevelDonor[iSpan] + 1] - - avgNormalVelDonor[SpanLevelDonor[iSpan]]); - avgNormalVelTarget[iSpan] += avgNormalVelDonor[SpanLevelDonor[iSpan]]; - avgTangVelTarget[iSpan] = SpanValueCoeffTarget[iSpan]*(avgTangVelDonor[SpanLevelDonor[iSpan] + 1] - - avgTangVelDonor[SpanLevelDonor[iSpan]]); - avgTangVelTarget[iSpan] += avgTangVelDonor[SpanLevelDonor[iSpan]]; - avg3DVelTarget[iSpan] = SpanValueCoeffTarget[iSpan]*(avg3DVelDonor[SpanLevelDonor[iSpan] + 1] - - avg3DVelDonor[SpanLevelDonor[iSpan]]); - avg3DVelTarget[iSpan] += avg3DVelDonor[SpanLevelDonor[iSpan]]; - avgNuTarget[iSpan] = SpanValueCoeffTarget[iSpan]*(avgNuDonor[SpanLevelDonor[iSpan] + 1] - - avgNuDonor[SpanLevelDonor[iSpan]]); - avgNuTarget[iSpan] += avgNuDonor[SpanLevelDonor[iSpan]]; - avgKineTarget[iSpan] = SpanValueCoeffTarget[iSpan]*(avgKineDonor[SpanLevelDonor[iSpan] + 1] - - avgKineDonor[SpanLevelDonor[iSpan]]); - avgKineTarget[iSpan] += avgKineDonor[SpanLevelDonor[iSpan]]; - avgOmegaTarget[iSpan] = SpanValueCoeffTarget[iSpan]*(avgOmegaDonor[SpanLevelDonor[iSpan] + 1] - - avgOmegaDonor[SpanLevelDonor[iSpan] ]); - avgOmegaTarget[iSpan] += avgOmegaDonor[SpanLevelDonor[iSpan]]; - } - - - /*--- transfer values at the hub ---*/ - avgDensityTarget[0] = avgDensityDonor[0]; - avgPressureTarget[0] = avgPressureDonor[0]; - avgNormalVelTarget[0] = avgNormalVelDonor[0]; - avgTangVelTarget[0] = avgTangVelDonor[0]; - avg3DVelTarget[0] = avg3DVelDonor[0]; - avgNuTarget[0] = avgNuDonor[0]; - avgKineTarget[0] = avgKineDonor[0]; - avgOmegaTarget[0] = avgOmegaDonor[0]; - - /*--- transfer values at the shroud ---*/ - avgDensityTarget[nSpanTarget - 2] = avgDensityDonor[nSpanDonor - 2]; - avgPressureTarget[nSpanTarget - 2] = avgPressureDonor[nSpanDonor - 2]; - avgNormalVelTarget[nSpanTarget - 2] = avgNormalVelDonor[nSpanDonor - 2]; - avgTangVelTarget[nSpanTarget - 2] = avgTangVelDonor[nSpanDonor - 2]; - avg3DVelTarget[nSpanTarget - 2] = avg3DVelDonor[nSpanDonor - 2]; - avgNuTarget[nSpanTarget - 2] = avgNuDonor[nSpanDonor - 2]; - avgKineTarget[nSpanTarget - 2] = avgKineDonor[nSpanDonor - 2]; - avgOmegaTarget[nSpanTarget - 2] = avgOmegaDonor[nSpanDonor - 2]; - - /*--- transfer 1D values ---*/ - avgDensityTarget[nSpanTarget - 1] = avgDensityDonor[nSpanDonor - 1]; - avgPressureTarget[nSpanTarget - 1] = avgPressureDonor[nSpanDonor - 1]; - avgNormalVelTarget[nSpanTarget - 1] = avgNormalVelDonor[nSpanDonor - 1]; - avgTangVelTarget[nSpanTarget - 1] = avgTangVelDonor[nSpanDonor - 1]; - avg3DVelTarget[nSpanTarget - 1] = avg3DVelDonor[nSpanDonor - 1]; - avgNuTarget[nSpanTarget - 1] = avgNuDonor[nSpanDonor - 1]; - avgKineTarget[nSpanTarget - 1] = avgKineDonor[nSpanDonor - 1]; - avgOmegaTarget[nSpanTarget - 1] = avgOmegaDonor[nSpanDonor - 1]; - - - /*---finally, the interpolated value is sent to the target zone ---*/ - for(iSpan = 0; iSpan < nSpanTarget ; iSpan++){ - Target_Variable[0] = avgDensityTarget[iSpan]; - Target_Variable[1] = avgPressureTarget[iSpan]; - Target_Variable[2] = avgNormalVelTarget[iSpan]; - Target_Variable[3] = avgTangVelTarget[iSpan]; - Target_Variable[4] = avg3DVelTarget[iSpan]; - Target_Variable[5] = avgNuTarget[iSpan]; - Target_Variable[6] = avgKineTarget[iSpan]; - Target_Variable[7] = avgOmegaTarget[iSpan]; - - - SetTarget_Variable(target_solution, target_geometry, target_config, Marker_Target, iSpan, rank); - } - } - - delete [] avgDensityDonor; - delete [] avgPressureDonor; - delete [] avgNormalVelDonor; - delete [] avgTangVelDonor; - delete [] avg3DVelDonor; - delete [] avgNuDonor; - delete [] avgKineDonor; - delete [] avgOmegaDonor; - - - delete [] avgDensityTarget; - delete [] avgPressureTarget; - delete [] avgNormalVelTarget; - delete [] avgTangVelTarget; - delete [] avg3DVelTarget; - delete [] avgNuTarget; - delete [] avgKineTarget; - delete [] avgOmegaTarget; -} +} \ No newline at end of file diff --git a/SU2_CFD/src/interfaces/cfd/CConservativeVarsInterface.cpp b/SU2_CFD/src/interfaces/cfd/CConservativeVarsInterface.cpp index f9c7a0c10cce..120921c16232 100644 --- a/SU2_CFD/src/interfaces/cfd/CConservativeVarsInterface.cpp +++ b/SU2_CFD/src/interfaces/cfd/CConservativeVarsInterface.cpp @@ -33,6 +33,7 @@ CConservativeVarsInterface::CConservativeVarsInterface(unsigned short val_nVar, unsigned short val_nConst) : CInterface(val_nVar, val_nConst) { + InterfaceType = ENUM_TRANSFER::CONSERVATIVE_VARIABLES; } void CConservativeVarsInterface::GetDonor_Variable(CSolver *donor_solution, CGeometry *donor_geometry, diff --git a/SU2_CFD/src/interfaces/cfd/CMixingPlaneInterface.cpp b/SU2_CFD/src/interfaces/cfd/CMixingPlaneInterface.cpp index 49755cf285e2..ad27ae841e80 100644 --- a/SU2_CFD/src/interfaces/cfd/CMixingPlaneInterface.cpp +++ b/SU2_CFD/src/interfaces/cfd/CMixingPlaneInterface.cpp @@ -1,9 +1,9 @@ /*! * \file CMixingPlaneInterface.cpp - * \brief Declaration and inlines of the class to transfer average variables + * \brief Declaration and inlines of the class to transfer average solver variables * needed for MixingPlane computation from a generic zone into another one. - * \author S. Vitale - * \version 8.5.0 "Harrier" + * \author J. Kelly, S. Vitale + * \version 8.4.0 "Harrier" * * SU2 Project Website: https://su2code.github.io * @@ -27,14 +27,17 @@ */ #include "../../../include/interfaces/cfd/CMixingPlaneInterface.hpp" +#include "../../../Common/include/interface_interpolation/CInterpolator.hpp" #include "../../../../Common/include/CConfig.hpp" #include "../../../../Common/include/geometry/CGeometry.hpp" #include "../../../include/solvers/CSolver.hpp" CMixingPlaneInterface::CMixingPlaneInterface(unsigned short val_nVar, unsigned short val_nConst){ - nVar = val_nVar; - Donor_Variable = new su2double[nVar + 5](); - Target_Variable = new su2double[nVar + 5](); + nVar = val_nVar; // Solver vars + nMixingVars = 8; // 8 solver vars in turbo MP + Donor_Variable = new su2double[nMixingVars](); + Target_Variable = new su2double[nMixingVars](); + InterfaceType = ENUM_TRANSFER::MIXING_PLANE; } void CMixingPlaneInterface::SetSpanWiseLevels(const CConfig *donor_config, const CConfig *target_config){ @@ -53,86 +56,132 @@ void CMixingPlaneInterface::SetSpanWiseLevels(const CConfig *donor_config, const } } -void CMixingPlaneInterface::GetDonor_Variable(CSolver *donor_solution, CGeometry *donor_geometry, - const CConfig *donor_config, unsigned long Marker_Donor, - unsigned long iSpan, unsigned long rank) { - - unsigned short nDim = nVar - 2; - bool turbulent = (donor_config->GetKind_Turb_Model() != TURB_MODEL::NONE); - +void CMixingPlaneInterface::BroadcastData_MixingPlane(const CInterpolator& interpolator, + CSolver *donor_solution, CSolver *target_solution, + CGeometry *donor_geometry, CGeometry *target_geometry, + const CConfig *donor_config, const CConfig *target_config) { + static_assert(su2activematrix::Storage == StorageType::RowMajor,""); + + /*--- Loop over interface markers. ---*/ + const auto nMarkerInt = donor_config->GetnMarker_MixingPlaneInterface() / 2; + for (auto iMarkerInt = 1; iMarkerInt < nMarkerInt + 1; iMarkerInt++) { + + /*--- Find the markers containing the interface ---*/ + short markDonor = donor_config->FindMixingPlaneInterfaceMarker(donor_geometry->GetnMarker(), iMarkerInt); + short markTarget= target_config->FindMixingPlaneInterfaceMarker(target_geometry->GetnMarker(), iMarkerInt); + + /*--- Check if this interface connects the two zones, if not continue. ---*/ + if(!CInterpolator::CheckInterfaceBoundary(markDonor, markTarget)) continue; + + // The number of spans is available on every rank + const auto nSpanDonor = donor_config->GetnSpanWiseSections(); + + /*--- Fill send buffers. ---*/ + + su2vector sendDonorMarker(nSpanDonor + 1); + sendDonorMarker.setConstant(-1); // Initialize to -1 to identify ranks that do not have the marker + su2activevector sendDonorVar(static_cast(nSpanDonor + 1) * nMixingVars); + + if (markDonor != -1) { + for (auto iSpan = 0; iSpan < nSpanDonor + 1; iSpan++) { + GetDonor_Variable(donor_solution, donor_geometry, donor_config, markDonor, iSpan, 0); + for (auto iVar = 0u; iVar < nMixingVars; iVar++) sendDonorVar[iSpan * nMixingVars + iVar] = Donor_Variable[iVar]; + sendDonorMarker[iSpan] = markDonor; + } + } +#ifdef HAVE_MPI + /*--- Gather data. ---*/ + const size_t nTotalDonors = static_cast(nSpanDonor + 1) * size; // Number of donor spans across all ranks + const size_t nSpanDonorVars = static_cast(nSpanDonor + 1) * nMixingVars; // Number of variables to be transferred on each rank + su2vector buffDonorMarker(nTotalDonors); + su2activevector buffDonorVar(static_cast(nTotalDonors) * nMixingVars); // Total number of variables to be transferred on all ranks + + SU2_MPI::Allgather(sendDonorMarker.data(), nSpanDonor + 1, MPI_SHORT, + buffDonorMarker.data(), nSpanDonor + 1, MPI_SHORT, + SU2_MPI::GetComm()); + + SU2_MPI::Allgather(sendDonorVar.data(), nSpanDonorVars, MPI_DOUBLE, + buffDonorVar.data(), nSpanDonorVars, MPI_DOUBLE, + SU2_MPI::GetComm()); + + for (auto iSize = 0; iSize < size; iSize++){ + if (buffDonorMarker[static_cast(iSize) * static_cast(nSpanDonor + 1)] != -1) { + for (auto iSpan = 0; iSpan < nSpanDonor + 1; iSpan++){ + const size_t spanOffset = static_cast(iSpan) * nMixingVars; + const size_t donorOffset = static_cast(iSize) * nSpanDonorVars; + for (size_t iVar = 0u; iVar < nMixingVars; iVar++) sendDonorVar[spanOffset + iVar] = buffDonorVar[donorOffset + spanOffset + iVar]; + } + markDonor = buffDonorMarker[static_cast(iSize) * static_cast(nSpanDonor + 1)]; + break; // Avoid overwriting + } + } +#endif + + /*--- This rank does not need to do more work. ---*/ + if (!(markTarget != -1 && markDonor != -1)) continue; + + /*--- Loop over target spans. ---*/ + unsigned long nTargetSpan = target_config->GetnSpanWiseSections() + 1; + + for (auto iTargetSpan = 0ul; iTargetSpan < nTargetSpan; iTargetSpan++) { + + auto& targetSpan = interpolator.targetSpans[iMarkerInt][iTargetSpan]; + + /*--- Get the global index of the donor span. ---*/ + const auto donorSpan = targetSpan.donorSpan; + + if ((iTargetSpan == 0) || (iTargetSpan == nTargetSpan) || (iTargetSpan == nTargetSpan - 1)) { + /*--- Transfer values at hub, shroud and 1D values ---*/ + RecoverTarget_SpanEndwall(sendDonorVar, donorSpan); + + SetTarget_Variable(target_solution, target_geometry, target_config, markTarget, iTargetSpan, 0); + } + else { + /*--- Get the global index of interpolation coefficient. ---*/ + const auto donorCoeff = targetSpan.coefficient; + + /*--- Recover the Target_Variable from the buffer of variables. ---*/ + RecoverTarget_Span(sendDonorVar, donorSpan, donorCoeff); + + SetTarget_Variable(target_solution, target_geometry, target_config, markTarget, iTargetSpan, 0); + } + } + } +} +void CMixingPlaneInterface::GetDonor_Variable(CSolver *donor_solution, CGeometry *donor_geometry, + const CConfig *donor_config, unsigned long Marker_Donor, + unsigned long Span_Donor, unsigned long Point_Donor) { - Donor_Variable[0] = donor_solution->GetAverageDensity(Marker_Donor, iSpan); - Donor_Variable[1] = donor_solution->GetAveragePressure(Marker_Donor, iSpan); - Donor_Variable[2] = donor_solution->GetAverageTurboVelocity(Marker_Donor, iSpan)[0]; - Donor_Variable[3] = donor_solution->GetAverageTurboVelocity(Marker_Donor, iSpan)[1]; + Donor_Variable[0] = donor_solution->GetAverageDensity(Marker_Donor, Span_Donor); + Donor_Variable[1] = donor_solution->GetAveragePressure(Marker_Donor, Span_Donor); + Donor_Variable[2] = donor_solution->GetAverageTurboVelocity(Marker_Donor, Span_Donor)[0]; + Donor_Variable[3] = donor_solution->GetAverageTurboVelocity(Marker_Donor, Span_Donor)[1]; - if(nDim == 3){ - Donor_Variable[4] = donor_solution->GetAverageTurboVelocity(Marker_Donor, iSpan)[2]; + if(donor_geometry->GetnDim() == 3){ + Donor_Variable[4] = donor_solution->GetAverageTurboVelocity(Marker_Donor, Span_Donor)[2]; } else{ Donor_Variable[4] = -1.0; } - if(turbulent){ - Donor_Variable[5] = donor_solution->GetAverageNu(Marker_Donor, iSpan); - Donor_Variable[6] = donor_solution->GetAverageKine(Marker_Donor, iSpan); - Donor_Variable[7] = donor_solution->GetAverageOmega(Marker_Donor, iSpan); + if(donor_config->GetKind_Turb_Model() != TURB_MODEL::NONE){ + Donor_Variable[5] = donor_solution->GetAverageNu(Marker_Donor, Span_Donor); + Donor_Variable[6] = donor_solution->GetAverageKine(Marker_Donor, Span_Donor); + Donor_Variable[7] = donor_solution->GetAverageOmega(Marker_Donor, Span_Donor); } else{ Donor_Variable[5] = -1.0; Donor_Variable[6] = -1.0; Donor_Variable[7] = -1.0; } - } - void CMixingPlaneInterface::SetTarget_Variable(CSolver *target_solution, CGeometry *target_geometry, - const CConfig *target_config, unsigned long Marker_Target, - unsigned long iSpan, unsigned long rank) { - - unsigned short nDim = nVar - 2; - bool turbulent = (target_config->GetKind_Turb_Model() != TURB_MODEL::NONE); - - - target_solution->SetExtAverageDensity(Marker_Target, iSpan, Target_Variable[0]); - target_solution->SetExtAveragePressure(Marker_Target, iSpan, Target_Variable[1]); - target_solution->SetExtAverageTurboVelocity(Marker_Target, iSpan, 0, Target_Variable[2]); - target_solution->SetExtAverageTurboVelocity(Marker_Target, iSpan, 1, Target_Variable[3]); - - if(nDim == 3){ - target_solution->SetExtAverageTurboVelocity(Marker_Target, iSpan, 2, Target_Variable[4]); - } - - if(turbulent){ - target_solution->SetExtAverageNu(Marker_Target, iSpan, Target_Variable[5]); - target_solution->SetExtAverageKine(Marker_Target, iSpan, Target_Variable[6]); - target_solution->SetExtAverageOmega(Marker_Target, iSpan, Target_Variable[7]); - } - -} - -void CMixingPlaneInterface::SetAverageValues(CSolver *donor_solution, CSolver *target_solution, - unsigned short donorZone){ - unsigned short iSpan; - - for(iSpan = 0; iSpanSetDensityIn(donor_solution->GetDensityIn(donorZone, iSpan), donorZone, iSpan); - target_solution->SetPressureIn(donor_solution->GetPressureIn(donorZone, iSpan), donorZone, iSpan); - target_solution->SetTurboVelocityIn(donor_solution->GetTurboVelocityIn(donorZone, iSpan), donorZone, iSpan); - target_solution->SetDensityOut(donor_solution->GetDensityOut(donorZone, iSpan), donorZone, iSpan); - target_solution->SetPressureOut(donor_solution->GetPressureOut(donorZone, iSpan), donorZone, iSpan); - target_solution->SetTurboVelocityOut(donor_solution->GetTurboVelocityOut(donorZone, iSpan), donorZone, iSpan); - - /*--- transfer turbulent quantities ---*/ - target_solution->SetKineIn(donor_solution->GetKineIn(donorZone, iSpan), donorZone, iSpan); - target_solution->SetOmegaIn(donor_solution->GetOmegaIn(donorZone, iSpan), donorZone, iSpan); - target_solution->SetNuIn(donor_solution->GetNuIn(donorZone, iSpan), donorZone, iSpan); - target_solution->SetKineOut(donor_solution->GetKineOut(donorZone, iSpan), donorZone, iSpan); - target_solution->SetOmegaOut(donor_solution->GetOmegaOut(donorZone, iSpan), donorZone, iSpan); - target_solution->SetNuOut(donor_solution->GetNuOut(donorZone, iSpan), donorZone, iSpan); - + const CConfig *target_config, unsigned long Marker_Target, + unsigned long Span_Target, unsigned long Point_Target) { + /*--- Set the mixing plane solution with the value of the Target Variable ---*/ + for (unsigned short iVar = 0; iVar < nMixingVars; iVar++) { + target_solution->SetMixingState(Marker_Target, Span_Target, iVar, Target_Variable[iVar]); } } diff --git a/SU2_CFD/src/interfaces/cfd/CSlidingInterface.cpp b/SU2_CFD/src/interfaces/cfd/CSlidingInterface.cpp index a11725fdcfd2..835fb8f79b01 100644 --- a/SU2_CFD/src/interfaces/cfd/CSlidingInterface.cpp +++ b/SU2_CFD/src/interfaces/cfd/CSlidingInterface.cpp @@ -40,6 +40,7 @@ CSlidingInterface::CSlidingInterface(unsigned short val_nVar, unsigned short val valAggregated = false; nVar = val_nVar; + InterfaceType = ENUM_TRANSFER::SLIDING_INTERFACE; } diff --git a/SU2_CFD/src/interfaces/fsi/CDiscAdjFlowTractionInterface.cpp b/SU2_CFD/src/interfaces/fsi/CDiscAdjFlowTractionInterface.cpp index 053ad71cc30b..a6484ac382ce 100644 --- a/SU2_CFD/src/interfaces/fsi/CDiscAdjFlowTractionInterface.cpp +++ b/SU2_CFD/src/interfaces/fsi/CDiscAdjFlowTractionInterface.cpp @@ -34,6 +34,7 @@ CDiscAdjFlowTractionInterface::CDiscAdjFlowTractionInterface(unsigned short val_nVar, unsigned short val_nConst, const CConfig *config, bool conservative_) : CFlowTractionInterface(val_nVar, val_nConst, config, conservative_) { + InterfaceType = ENUM_TRANSFER::FLOW_TRACTION; } void CDiscAdjFlowTractionInterface::GetPhysical_Constants(CSolver *flow_solution, CSolver *struct_solution, diff --git a/SU2_CFD/src/interfaces/fsi/CDisplacementsInterface.cpp b/SU2_CFD/src/interfaces/fsi/CDisplacementsInterface.cpp index 2ccaec971a00..52d9323c9efc 100644 --- a/SU2_CFD/src/interfaces/fsi/CDisplacementsInterface.cpp +++ b/SU2_CFD/src/interfaces/fsi/CDisplacementsInterface.cpp @@ -33,6 +33,7 @@ CDisplacementsInterface::CDisplacementsInterface(unsigned short val_nVar, unsigned short val_nConst) : CInterface(val_nVar, val_nConst) { + InterfaceType = ENUM_TRANSFER::BOUNDARY_DISPLACEMENTS; } void CDisplacementsInterface::GetDonor_Variable(CSolver *struct_solution, CGeometry *struct_geometry, diff --git a/SU2_CFD/src/interfaces/fsi/CFlowTractionInterface.cpp b/SU2_CFD/src/interfaces/fsi/CFlowTractionInterface.cpp index 79e65a2b9614..6a78e0d5713e 100644 --- a/SU2_CFD/src/interfaces/fsi/CFlowTractionInterface.cpp +++ b/SU2_CFD/src/interfaces/fsi/CFlowTractionInterface.cpp @@ -38,6 +38,7 @@ CFlowTractionInterface::CFlowTractionInterface(unsigned short val_nVar, unsigned const CConfig *config, bool conservative_) : CInterface(val_nVar, val_nConst), conservative(conservative_) { + InterfaceType = ENUM_TRANSFER::FLOW_TRACTION; } void CFlowTractionInterface::Preprocess(const CConfig *flow_config, const CConfig *struct_config, diff --git a/SU2_CFD/src/iteration/CDiscAdjFluidIteration.cpp b/SU2_CFD/src/iteration/CDiscAdjFluidIteration.cpp index a7f0dc0a8211..a3bd1d0616e0 100644 --- a/SU2_CFD/src/iteration/CDiscAdjFluidIteration.cpp +++ b/SU2_CFD/src/iteration/CDiscAdjFluidIteration.cpp @@ -409,12 +409,15 @@ void CDiscAdjFluidIteration::RegisterInput(CSolver***** solver, CGeometry**** ge SU2_OMP_PARALLEL_(if(solvers0[ADJFLOW_SOL]->GetHasHybridParallel())) { + bool AD_debug_mesh_coordinates = false; + if (config[iZone]->GetAD_CheckTapeVariables() == CHECK_TAPE_VARIABLES::MESH_COORDINATES) { + cout << "Register additional SOLUTION VARIABLES for tag debug mode (zone " << iZone << ")." << endl; + AD_debug_mesh_coordinates = true; + } + if (kind_recording == RECORDING::SOLUTION_VARIABLES || - kind_recording == RECORDING::TAG_INIT_SOLVER_VARIABLES || - kind_recording == RECORDING::TAG_CHECK_SOLVER_VARIABLES || - kind_recording == RECORDING::TAG_INIT_SOLVER_AND_MESH || - kind_recording == RECORDING::TAG_CHECK_SOLVER_AND_MESH || - kind_recording == RECORDING::SOLUTION_AND_MESH) { + kind_recording == RECORDING::SOLUTION_AND_MESH || + AD_debug_mesh_coordinates) { /*--- Register flow and turbulent variables as input ---*/ @@ -441,9 +444,7 @@ void CDiscAdjFluidIteration::RegisterInput(CSolver***** solver, CGeometry**** ge } if (kind_recording == RECORDING::MESH_COORDS || - kind_recording == RECORDING::SOLUTION_AND_MESH || - kind_recording == RECORDING::TAG_INIT_SOLVER_AND_MESH || - kind_recording == RECORDING::TAG_CHECK_SOLVER_AND_MESH) { + kind_recording == RECORDING::SOLUTION_AND_MESH) { /*--- Register node coordinates as input ---*/ geometry0->RegisterCoordinates(); @@ -476,7 +477,7 @@ void CDiscAdjFluidIteration::SetDependencies(CSolver***** solver, CGeometry**** CGeometry::UpdateGeometry(geometry[iZone][iInst], config[iZone]); END_SU2_OMP_PARALLEL - CGeometry::ComputeWallDistance(config, geometry); + CGeometry::ComputeWallDistance(config, geometry, iZone); } SU2_OMP_PARALLEL_(if(solvers0[ADJFLOW_SOL]->GetHasHybridParallel())) { @@ -486,16 +487,23 @@ void CDiscAdjFluidIteration::SetDependencies(CSolver***** solver, CGeometry**** solvers0[FLOW_SOL]->InitiateComms(geometry0, config[iZone], MPI_QUANTITIES::SOLUTION); solvers0[FLOW_SOL]->CompleteComms(geometry0, config[iZone], MPI_QUANTITIES::SOLUTION); - if (config[iZone]->GetBoolTurbomachinery()) { - solvers0[FLOW_SOL]->TurboAverageProcess(solvers0, geometry0, config[iZone], INFLOW); - solvers0[FLOW_SOL]->TurboAverageProcess(solvers0, geometry0, config[iZone], OUTFLOW); - } if (turbulent && !config[iZone]->GetFrozen_Visc_Disc()) { solvers0[TURB_SOL]->Postprocessing(geometry0, solvers0, config[iZone], MESH_0); solvers0[TURB_SOL]->InitiateComms(geometry0, config[iZone], MPI_QUANTITIES::SOLUTION); solvers0[TURB_SOL]->CompleteComms(geometry0, config[iZone], MPI_QUANTITIES::SOLUTION); } + if (config[iZone]->GetBoolTurbomachinery()) { + solvers0[FLOW_SOL]->PreprocessAverage(solvers0, geometry0, config[iZone], INFLOW); + solvers0[FLOW_SOL]->PreprocessAverage(solvers0, geometry0, config[iZone], OUTFLOW); + solvers0[FLOW_SOL]->TurboAverageProcess(solvers0, geometry0, config[iZone], INFLOW); + solvers0[FLOW_SOL]->TurboAverageProcess(solvers0, geometry0, config[iZone], OUTFLOW); + if (config[iZone]->GetBoolGiles() && config[iZone]->GetSpatialFourier()){ + auto conv_bound_numerics = numerics[iZone][iInst][MESH_0][FLOW_SOL][CONV_BOUND_TERM + omp_get_thread_num()*MAX_TERMS]; + solvers0[FLOW_SOL]->PreprocessBC_Giles(geometry0, config[iZone], conv_bound_numerics, INFLOW); + solvers0[FLOW_SOL]->PreprocessBC_Giles(geometry0, config[iZone], conv_bound_numerics, OUTFLOW); + } + } if (config[iZone]->GetKind_Species_Model() != SPECIES_MODEL::NONE) { solvers0[SPECIES_SOL]->Preprocessing(geometry0, solvers0, config[iZone], MESH_0, NO_RK_ITER, RUNTIME_FLOW_SYS, true); solvers0[SPECIES_SOL]->InitiateComms(geometry0, config[iZone], MPI_QUANTITIES::SOLUTION); diff --git a/SU2_CFD/src/iteration/CFluidIteration.cpp b/SU2_CFD/src/iteration/CFluidIteration.cpp index a79a9f1f8f4a..cb484bd6530b 100644 --- a/SU2_CFD/src/iteration/CFluidIteration.cpp +++ b/SU2_CFD/src/iteration/CFluidIteration.cpp @@ -229,23 +229,24 @@ bool CFluidIteration::Monitor(COutput* output, CIntegration**** integration, CGe /*--- Turbomachinery Specific Montior ---*/ if (config[ZONE_0]->GetBoolTurbomachinery()){ if (val_iZone == config[ZONE_0]->GetnZone()-1) { - ComputeTurboPerformance(solver, geometry, config, config[val_iZone]->GetnInner_Iter()); + ComputeTurboPerformance(solver, geometry, config); + auto TurbomachineryBladePerformances = GetBladesPerformanceVector(solver, config[val_iZone]->GetnZone()); - output->SetHistoryOutput(geometry, solver, - config, TurbomachineryStagePerformance, TurbomachineryPerformance, val_iZone, config[val_iZone]->GetTimeIter(), config[val_iZone]->GetOuterIter(), - config[val_iZone]->GetInnerIter(), val_iInst); + output->SetHistoryOutput(geometry, solver, config, TurbomachineryStagePerformance, TurbomachineryBladePerformances, + val_iZone, config[val_iZone]->GetTimeIter(), config[val_iZone]->GetOuterIter(), + config[val_iZone]->GetInnerIter(), val_iInst); } /*--- Update ramps, grid first then outlet boundary ---*/ if (config[val_iZone]->GetRampMotionFrame()) - UpdateRamp(geometry, config, config[val_iZone]->GetInnerIter(), val_iZone, RAMP_TYPE::GRID); + UpdateRamps(geometry, config, config[val_iZone]->GetInnerIter(), val_iZone, RAMP_TYPE::GRID); } // Outside turbo scope as Riemann boundaries can be ramped (pressure only) if (config[val_iZone]->GetRampOutflow()) - UpdateRamp(geometry, config, config[val_iZone]->GetInnerIter(), val_iZone, RAMP_TYPE::BOUNDARY); + UpdateRamps(geometry, config, config[val_iZone]->GetInnerIter(), val_iZone, RAMP_TYPE::BOUNDARY); if (config[val_iZone]->GetMUSCLRamp()) - UpdateRamp(geometry, config, config[val_iZone]->GetInnerIter(), val_iZone, RAMP_TYPE::MUSCL); + UpdateRamps(geometry, config, config[val_iZone]->GetInnerIter(), val_iZone, RAMP_TYPE::MUSCL); /*--- During Full-MG startup FinestMesh > 0: read residuals from the active (coarse) level. ---*/ const unsigned short finestMesh = config[val_iZone]->GetFinestMesh(); @@ -269,9 +270,8 @@ bool CFluidIteration::Monitor(COutput* output, CIntegration**** integration, CGe return StopCalc; } -void CFluidIteration::UpdateRamp(CGeometry**** geometry_container, CConfig** config_container, unsigned long iter, unsigned short iZone, RAMP_TYPE ramp_flag) { +void CFluidIteration::UpdateRamps(CGeometry**** geometry_container, CConfig** config_container, unsigned long iter, unsigned short iZone, RAMP_TYPE ramp_flag) { SU2_ZONE_SCOPED - /*--- Generic function for handling ramps ---*/ // Grid updates (i.e. rotation/translation) handled seperately to boundary (i.e. pressure/mass flow) updates auto* config = config_container[iZone]; @@ -304,10 +304,6 @@ void CFluidIteration::UpdateRamp(CGeometry**** geometry_container, CConfig** con geometry->SetAvgTurboValue(config, iZone, INFLOW, false); geometry->SetAvgTurboValue(config, iZone, OUTFLOW, false); geometry->GatherInOutAverageValues(config, false); - - if (iZone < nZone - 1) { - geometry_container[nZone-1][INST_0][MESH_0]->SetAvgTurboGeoValues(config ,geometry_container[iZone][INST_0][MESH_0], iZone); - } } } @@ -372,41 +368,6 @@ void CFluidIteration::UpdateRamp(CGeometry**** geometry_container, CConfig** con } } -void CFluidIteration::ComputeTurboPerformance(CSolver***** solver, CGeometry**** geometry_container, CConfig** config_container, unsigned long ExtIter) { - SU2_ZONE_SCOPED - - unsigned short nDim = geometry_container[ZONE_0][INST_0][MESH_0]->GetnDim(); - unsigned short nBladesRow = config_container[ZONE_0]->GetnMarker_Turbomachinery(); - unsigned short iBlade=0, iSpan; - vector TurboPrimitiveIn, TurboPrimitiveOut; - std::vector> bladesPrimitives; - - if (rank == MASTER_NODE) { - for (iBlade = 0; iBlade < nBladesRow; iBlade++){ - /* Blade Primitive initialized per blade */ - std::vector bladePrimitives; - auto nSpan = config_container[iBlade]->GetnSpanWiseSections(); - for (iSpan = 0; iSpan < nSpan + 1; iSpan++) { - TurboPrimitiveIn= solver[iBlade][INST_0][MESH_0][FLOW_SOL]->GetTurboPrimitive(iBlade, iSpan, true); - TurboPrimitiveOut= solver[iBlade][INST_0][MESH_0][FLOW_SOL]->GetTurboPrimitive(iBlade, iSpan, false); - auto spanInletPrimitive = CTurbomachineryPrimitiveState(TurboPrimitiveIn, nDim, geometry_container[iBlade][INST_0][MESH_0]->GetTangGridVelIn(iBlade, iSpan)); - auto spanOutletPrimitive = CTurbomachineryPrimitiveState(TurboPrimitiveOut, nDim, geometry_container[iBlade][INST_0][MESH_0]->GetTangGridVelOut(iBlade, iSpan)); - auto spanCombinedPrimitive = CTurbomachineryCombinedPrimitiveStates(spanInletPrimitive, spanOutletPrimitive); - bladePrimitives.push_back(spanCombinedPrimitive); - } - bladesPrimitives.push_back(bladePrimitives); - } - TurbomachineryPerformance->ComputeTurbomachineryPerformance(bladesPrimitives); - - auto nSpan = config_container[ZONE_0]->GetnSpanWiseSections(); - auto InState = TurbomachineryPerformance->GetBladesPerformances().at(ZONE_0).at(nSpan)->GetInletState(); - nSpan = config_container[nZone-1]->GetnSpanWiseSections(); - auto OutState = TurbomachineryPerformance->GetBladesPerformances().at(nZone-1).at(nSpan)->GetOutletState(); - - TurbomachineryStagePerformance->ComputePerformanceStage(InState, OutState, config_container[nZone-1]); - } -} - void CFluidIteration::Postprocess(COutput* output, CIntegration**** integration, CGeometry**** geometry, CSolver***** solver, CNumerics****** numerics, CConfig** config, CSurfaceMovement** surface_movement, CVolumetricMovement*** grid_movement, @@ -453,6 +414,9 @@ void CFluidIteration::Solve(COutput* output, CIntegration**** integration, CGeom Iterate(output, integration, geometry, solver, numerics, config, surface_movement, grid_movement, FFDBox, val_iZone, INST_0); + /*--- Postprocessing Step ---*/ + Postprocess(output, integration, geometry, solver, numerics, config, surface_movement, grid_movement, FFDBox, val_iZone, val_iInst); + /*--- Monitor the pseudo-time ---*/ StopCalc = Monitor(output, integration, geometry, solver, numerics, config, surface_movement, grid_movement, FFDBox, val_iZone, INST_0); diff --git a/SU2_CFD/src/iteration/CIteration.cpp b/SU2_CFD/src/iteration/CIteration.cpp index eab360cb34be..e0ede7e9b4cf 100644 --- a/SU2_CFD/src/iteration/CIteration.cpp +++ b/SU2_CFD/src/iteration/CIteration.cpp @@ -213,3 +213,23 @@ void CIteration::Output(COutput* output, CGeometry**** geometry, CSolver***** so output->SetResultFiles(geometry[val_iZone][INST_0][MESH_0], config[val_iZone], solver[val_iZone][INST_0][MESH_0], InnerIter); } + +void CIteration::InitTurboPerformance(CGeometry* geometry, CConfig** config, CFluidModel* fluid, unsigned short val_iZone) { + TurbomachineryStagePerformance = std::make_shared(*fluid); +} + +void CIteration::ComputeTurboPerformance(CSolver***** solver, CGeometry**** geometry_container, CConfig** config_container) { + // Computes the turboperformance per blade in zone iBlade + const auto nZone = config_container[ZONE_0]->GetnZone(); + + if (rank == MASTER_NODE) { + auto TurbomachineryBladePerformances = GetBladesPerformanceVector(solver, nZone); + + auto nSpan = config_container[ZONE_0]->GetnSpanWiseSections(); + auto InState = TurbomachineryBladePerformances[ZONE_0]->GetBladesPerformances().at(nSpan)->GetInletState(); + nSpan = config_container[nZone-1]->GetnSpanWiseSections(); + auto OutState = TurbomachineryBladePerformances[nZone-1]->GetBladesPerformances().at(nSpan)->GetOutletState(); + + TurbomachineryStagePerformance->ComputePerformanceStage(InState, OutState, config_container[nZone-1]); + } +} diff --git a/SU2_CFD/src/iteration/CTurboIteration.cpp b/SU2_CFD/src/iteration/CTurboIteration.cpp index 482e4c053957..90f83995927b 100644 --- a/SU2_CFD/src/iteration/CTurboIteration.cpp +++ b/SU2_CFD/src/iteration/CTurboIteration.cpp @@ -33,37 +33,37 @@ void CTurboIteration::Preprocess(COutput* output, CIntegration**** integration, CSolver***** solver, CNumerics****** numerics, CConfig** config, CSurfaceMovement** surface_movement, CVolumetricMovement*** grid_movement, CFreeFormDefBox*** FFDBox, unsigned short val_iZone, unsigned short val_iInst) { - SU2_ZONE_SCOPED - /*--- Average quantities at the inflow and outflow boundaries ---*/ - solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->TurboAverageProcess( - solver[val_iZone][val_iInst][MESH_0], geometry[val_iZone][val_iInst][MESH_0], config[val_iZone], INFLOW); - solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->TurboAverageProcess( - solver[val_iZone][val_iInst][MESH_0], geometry[val_iZone][val_iInst][MESH_0], config[val_iZone], OUTFLOW); - if (config[val_iZone]->GetBoolTurbomachinery()) { - InitTurboPerformance(geometry[val_iZone][INST_0][MESH_0], config, - solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->GetFluidModel()); - } + /*--- Average quantities at the inflow and outflow boundaries ---*/ + solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->TurboAverageProcess( + solver[val_iZone][val_iInst][MESH_0], geometry[val_iZone][val_iInst][MESH_0], config[val_iZone], INFLOW); + solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->TurboAverageProcess( + solver[val_iZone][val_iInst][MESH_0], geometry[val_iZone][val_iInst][MESH_0], config[val_iZone], OUTFLOW); + + InitTurboPerformance(geometry[val_iZone][val_iInst][MESH_0], config, solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->GetFluidModel()); + } void CTurboIteration::Postprocess(COutput* output, CIntegration**** integration, CGeometry**** geometry, CSolver***** solver, CNumerics****** numerics, CConfig** config, CSurfaceMovement** surface_movement, CVolumetricMovement*** grid_movement, CFreeFormDefBox*** FFDBox, unsigned short val_iZone, unsigned short val_iInst) { - SU2_ZONE_SCOPED - /*--- Average quantities at the inflow and outflow boundaries ---*/ - solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->TurboAverageProcess( - solver[val_iZone][val_iInst][MESH_0], geometry[val_iZone][val_iInst][MESH_0], config[val_iZone], INFLOW); - solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->TurboAverageProcess( - solver[val_iZone][val_iInst][MESH_0], geometry[val_iZone][val_iInst][MESH_0], config[val_iZone], OUTFLOW); + /*--- Average quantities at the inflow and outflow boundaries ---*/ + solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->TurboAverageProcess( + solver[val_iZone][val_iInst][MESH_0], geometry[val_iZone][val_iInst][MESH_0], config[val_iZone], INFLOW); + solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->TurboAverageProcess( + solver[val_iZone][val_iInst][MESH_0], geometry[val_iZone][val_iInst][MESH_0], config[val_iZone], OUTFLOW); + + /*--- Gather Inflow and Outflow quantities on the Master Node to compute performance ---*/ + + solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->GatherInOutAverageValues(config[val_iZone], geometry[val_iZone][val_iInst][MESH_0]); + + /*--- Compute the turboperformance ---*/ + + solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->ComputeTurboBladePerformance(geometry[val_iZone][val_iInst][MESH_0], config[val_iZone], val_iZone); - /*--- Gather Inflow and Outflow quantities on the Master Node to compute performance ---*/ - solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->GatherInOutAverageValues(config[val_iZone], - geometry[val_iZone][val_iInst][MESH_0]); } void CTurboIteration::InitTurboPerformance(CGeometry* geometry, CConfig** config, CFluidModel* fluid) { - SU2_ZONE_SCOPED - TurbomachineryPerformance = std::make_shared(config, *geometry, *fluid); - TurbomachineryStagePerformance = std::make_shared(*fluid); -} \ No newline at end of file + TurbomachineryStagePerformance = std::make_shared(*fluid); +} diff --git a/SU2_CFD/src/numerics/flow/convection/roe.cpp b/SU2_CFD/src/numerics/flow/convection/roe.cpp index d43b71ab4bcf..73114576d409 100644 --- a/SU2_CFD/src/numerics/flow/convection/roe.cpp +++ b/SU2_CFD/src/numerics/flow/convection/roe.cpp @@ -244,6 +244,8 @@ CNumerics::ResidualType<> CUpwRoeBase_Flow::ComputeResidual(const CConfig* confi } AD::SetPreaccOut(Flux, nVar); + AD::SetPreaccOut(Jacobian_i, nVar, nVar); + AD::SetPreaccOut(Jacobian_j, nVar, nVar); AD::EndPreacc(); return ResidualType<>(Flux, Jacobian_i, Jacobian_j); diff --git a/SU2_CFD/src/numerics/flow/flow_diffusion.cpp b/SU2_CFD/src/numerics/flow/flow_diffusion.cpp index ba2ba516c1c0..66945b2f700a 100644 --- a/SU2_CFD/src/numerics/flow/flow_diffusion.cpp +++ b/SU2_CFD/src/numerics/flow/flow_diffusion.cpp @@ -512,6 +512,8 @@ CNumerics::ResidualType<> CAvgGrad_Flow::ComputeResidual(const CConfig* config) } AD::SetPreaccOut(Proj_Flux_Tensor, nVar); + AD::SetPreaccOut(Jacobian_i, nVar, nVar); + AD::SetPreaccOut(Jacobian_j, nVar, nVar); AD::EndPreacc(); return ResidualType<>(Proj_Flux_Tensor, Jacobian_i, Jacobian_j); diff --git a/SU2_CFD/src/output/CFlowCompOutput.cpp b/SU2_CFD/src/output/CFlowCompOutput.cpp index 0895ed560fc1..fd9bd5356b03 100644 --- a/SU2_CFD/src/output/CFlowCompOutput.cpp +++ b/SU2_CFD/src/output/CFlowCompOutput.cpp @@ -513,7 +513,7 @@ bool CFlowCompOutput::WriteHistoryFileOutput(const CConfig *config) { return !config->GetFinite_Difference_Mode() && COutput::WriteHistoryFileOutput(config); } -void CFlowCompOutput::SetTurboPerformance_Output(std::shared_ptr TurboPerf, +void CFlowCompOutput::SetTurboPerformance_Output(su2vector> TurboBladePerfs, CConfig *config, unsigned long TimeIter, unsigned long OuterIter, @@ -525,8 +525,6 @@ void CFlowCompOutput::SetTurboPerformance_Output(std::shared_ptr T curInnerIter = InnerIter; stringstream TurboInOutTable, TurboPerfTable; - auto BladePerformance = TurboPerf->GetBladesPerformances(); - /*-- Table for Turbomachinery Performance Values --*/ PrintingToolbox::CTablePrinter TurboInOut(&TurboInOutTable); @@ -539,7 +537,7 @@ void CFlowCompOutput::SetTurboPerformance_Output(std::shared_ptr T for (unsigned short iZone = 0; iZone <= config->GetnZone()-1; iZone++) { auto nSpan = config->GetnSpan_iZones(iZone); - const auto& BladePerf = BladePerformance.at(iZone).at(nSpan); + const auto& BladePerf = TurboBladePerfs[iZone]->GetBladesPerformances().at(nSpan); TurboInOut<<" BLADE ROW INDEX "< T TurboInOut << "Mass Flow " << BladePerf->GetInletState().GetMassFlow() << BladePerf->GetOutletState().GetMassFlow(); TurboInOut << "Mach " << BladePerf->GetInletState().GetMachValue() << BladePerf->GetOutletState().GetMachValue(); TurboInOut << "Abs Flow Angle " << BladePerf->GetInletState().GetAbsFlowAngle()*180/PI_NUMBER << BladePerf->GetOutletState().GetAbsFlowAngle()*180/PI_NUMBER; + TurboInOut << "Rel Flow Angle " << BladePerf->GetInletState().GetFlowAngle()*180/PI_NUMBER << BladePerf->GetOutletState().GetFlowAngle()*180/PI_NUMBER; TurboInOut.PrintFooter(); } cout< TurboStagePerf, std::shared_ptr TurboPerf, CConfig *config) { +void CFlowCompOutput::SetTurboMultiZonePerformance_Output(std::shared_ptr TurboStagePerf, su2vector> TurboPerf, CConfig *config) { stringstream TurboMZPerf; @@ -589,11 +588,10 @@ void CFlowCompOutput::SetTurboMultiZonePerformance_Output(std::shared_ptr TurboStagePerf, std::shared_ptr TurboPerf, CConfig *config) { - auto BladePerformance = TurboPerf->GetBladesPerformances(); +void CFlowCompOutput::LoadTurboHistoryData(std::shared_ptr TurboStagePerf, su2vector> TurboBladePerfs, CConfig *config) { for (unsigned short iZone = 0; iZone <= config->GetnZone()-1; iZone++) { auto nSpan = config->GetnSpan_iZones(iZone); - const auto& BladePerf = BladePerformance.at(iZone).at(nSpan); + const auto& BladePerf = TurboBladePerfs[iZone]->GetBladesPerformances().at(nSpan); stringstream tag; tag << iZone + 1; @@ -622,6 +620,8 @@ void CFlowCompOutput::LoadTurboHistoryData(std::shared_ptrGetOutletState().GetMachValue()); SetHistoryOutputValue("AbsFlowAngleIn_" + tag.str(), BladePerf->GetInletState().GetAbsFlowAngle()*180/PI_NUMBER); SetHistoryOutputValue("AbsFlowAngleOut_" + tag.str(), BladePerf->GetOutletState().GetAbsFlowAngle()*180/PI_NUMBER); + SetHistoryOutputValue("RelFlowAngleIn_" + tag.str(), BladePerf->GetInletState().GetFlowAngle()*180/PI_NUMBER); + SetHistoryOutputValue("RelFlowAngleOut_" + tag.str(), BladePerf->GetOutletState().GetFlowAngle()*180/PI_NUMBER); SetHistoryOutputValue("KineticEnergyLoss_" + tag.str(), BladePerf->GetKineticEnergyLoss()); SetHistoryOutputValue("TotPressureLoss_" + tag.str(), BladePerf->GetTotalPressureLoss()); } @@ -635,7 +635,7 @@ void CFlowCompOutput::LoadTurboHistoryData(std::shared_ptrGetTotalPressureLoss()); } -void CFlowCompOutput::WriteTurboSpanwisePerformance(std::shared_ptr TurboPerf, CGeometry *geometry, CConfig **config, unsigned short val_iZone) { +void CFlowCompOutput::WriteTurboSpanwisePerformance(su2vector> TurboBladePerfs, CGeometry *geometry, CConfig **config, unsigned short val_iZone) { string inMarker_Tag, outMarker_Tag, inMarkerTag_Mix; unsigned short nZone = config[val_iZone]->GetnZone(); @@ -647,14 +647,12 @@ void CFlowCompOutput::WriteTurboSpanwisePerformance(std::shared_ptrGetBladesPerformances(); - /*--- Start of write file turboperformance spanwise ---*/ SpanWiseValuesIn = geometry->GetSpanWiseValue(INFLOW); SpanWiseValuesOut = geometry->GetSpanWiseValue(OUTFLOW); /*--- Writing Span wise inflow thermodynamic quantities. ---*/ - spanwise_performance_filename = "TURBOMACHINERY/inflow_spanwise_thermodynamic_values.dat"; + spanwise_performance_filename = "TURBOMACHINERY/inflow_spanwise_thermodynamic_values"; if (nZone > 1) { spanwise_performance_filename.append("_" + std::to_string(val_iZone) + ".dat"); } else { @@ -680,7 +678,7 @@ void CFlowCompOutput::WriteTurboSpanwisePerformance(std::shared_ptrGetnSpanWiseSections(); iSpan++){ - const auto& BladePerf = BladePerformance.at(val_iZone).at(iSpan); + const auto& BladePerf = TurboBladePerfs[val_iZone]->GetBladesPerformances().at(iSpan); file.width(30); file << SpanWiseValuesIn[iSpan]; file.width(15); file << iSpan; @@ -692,12 +690,13 @@ void CFlowCompOutput::WriteTurboSpanwisePerformance(std::shared_ptrGetInletState().GetTotalEnthalpy()*config[ZONE_0]->GetEnergy_Ref(); file.width(30); file << BladePerf->GetInletState().GetDensity()*config[ZONE_0]->GetDensity_Ref(); file.width(30); file << BladePerf->GetInletState().GetEntropy()*config[ZONE_0]->GetEnergy_Ref()/config[ZONE_0]->GetTemperature_Ref(); + file << endl; } file.close(); /*--- Writing Span wise outflow thermodynamic quantities. ---*/ - spanwise_performance_filename = "TURBOMACHINERY/outflow_spanwise_thermodynamic_values.dat"; + spanwise_performance_filename = "TURBOMACHINERY/outflow_spanwise_thermodynamic_values"; if (nZone > 1) { spanwise_performance_filename.append("_" + std::to_string(val_iZone) + ".dat"); } else { @@ -724,7 +723,7 @@ void CFlowCompOutput::WriteTurboSpanwisePerformance(std::shared_ptrGetnSpanWiseSections(); iSpan++){ - const auto& BladePerf = BladePerformance.at(val_iZone).at(iSpan); + const auto& BladePerf = TurboBladePerfs[val_iZone]->GetBladesPerformances().at(iSpan); file.width(30); file << SpanWiseValuesOut[iSpan]; file.width(15); file << iSpan; @@ -736,12 +735,13 @@ void CFlowCompOutput::WriteTurboSpanwisePerformance(std::shared_ptrGetOutletState().GetTotalEnthalpy()*config[ZONE_0]->GetEnergy_Ref(); file.width(30); file << BladePerf->GetOutletState().GetDensity()*config[ZONE_0]->GetDensity_Ref(); file.width(30); file << BladePerf->GetOutletState().GetEntropy()*config[ZONE_0]->GetEnergy_Ref()/config[ZONE_0]->GetTemperature_Ref(); + file << endl; } file.close(); /*--- Writing Span wise inflow kinematic quantities. ---*/ - spanwise_performance_filename = "TURBOMACHINERY/inflow_spanwise_kinematic_values.dat"; + spanwise_performance_filename = "TURBOMACHINERY/inflow_spanwise_kinematic_values"; if (nZone > 1) { spanwise_performance_filename.append("_" + std::to_string(val_iZone) + ".dat"); } else { @@ -774,7 +774,7 @@ void CFlowCompOutput::WriteTurboSpanwisePerformance(std::shared_ptrGetnSpanWiseSections(); iSpan++){ - const auto& BladePerf = BladePerformance.at(val_iZone).at(iSpan); + const auto& BladePerf = TurboBladePerfs[val_iZone]->GetBladesPerformances().at(iSpan); file.width(30); file << SpanWiseValuesIn[iSpan]; file.width(15); file << iSpan; @@ -838,7 +838,7 @@ void CFlowCompOutput::WriteTurboSpanwisePerformance(std::shared_ptrGetnSpanWiseSections(); iSpan++){ - const auto& BladePerf = BladePerformance.at(val_iZone).at(iSpan); + const auto& BladePerf = TurboBladePerfs[val_iZone]->GetBladesPerformances().at(iSpan); file.width(30); file << SpanWiseValuesOut[iSpan]; file.width(15); file << iSpan; diff --git a/SU2_CFD/src/output/CFlowOutput.cpp b/SU2_CFD/src/output/CFlowOutput.cpp index b0e16fedcc8d..06f0534e11af 100644 --- a/SU2_CFD/src/output/CFlowOutput.cpp +++ b/SU2_CFD/src/output/CFlowOutput.cpp @@ -4301,6 +4301,8 @@ void CFlowOutput::AddTurboOutput(unsigned short nZone){ AddHistoryOutput("MachOut_" + tag, "MachOut_" + tag, ScreenOutputFormat::SCIENTIFIC, "TURBO_PERF", "Total-to-Static efficiency " + tag, HistoryFieldType::DEFAULT); AddHistoryOutput("AbsFlowAngleIn_" + tag, "AbsFlowAngleIn_" + tag, ScreenOutputFormat::SCIENTIFIC, "TURBO_PERF", "Absolute flow angle in " + tag, HistoryFieldType::DEFAULT); AddHistoryOutput("AbsFlowAngleOut_" + tag, "AbsFlowAngleOut_" + tag, ScreenOutputFormat::SCIENTIFIC, "TURBO_PERF", "Absolute flow angle out " + tag, HistoryFieldType::DEFAULT); + AddHistoryOutput("RelFlowAngleIn_" + tag, "RelFlowAngleIn_" + tag, ScreenOutputFormat::SCIENTIFIC, "TURBO_PERF", "Relative flow angle in " + tag, HistoryFieldType::DEFAULT); + AddHistoryOutput("RelFlowAngleOut_" + tag, "RelFlowAngleOut_" + tag, ScreenOutputFormat::SCIENTIFIC, "TURBO_PERF", "Relative flow angle out " + tag, HistoryFieldType::DEFAULT); AddHistoryOutput("KineticEnergyLoss_" + tag, "KELC_" + tag, ScreenOutputFormat::SCIENTIFIC, "TURBO_PERF", "Blade Kinetic Energy Loss Coefficient", HistoryFieldType::DEFAULT); AddHistoryOutput("TotPressureLoss_" + tag, "TPLC_" + tag, ScreenOutputFormat::SCIENTIFIC, "TURBO_PERF", "Blade Pressure Loss Coefficient", HistoryFieldType::DEFAULT); } diff --git a/SU2_CFD/src/output/COutput.cpp b/SU2_CFD/src/output/COutput.cpp index e210fbb59c4f..c603ad771e88 100644 --- a/SU2_CFD/src/output/COutput.cpp +++ b/SU2_CFD/src/output/COutput.cpp @@ -231,7 +231,7 @@ void COutput::SetHistoryOutput(CGeometry *geometry, } -void COutput::SetHistoryOutput(CGeometry ****geometry, CSolver *****solver, CConfig **config, std::shared_ptr(TurboStagePerf), std::shared_ptr TurboPerf, unsigned short val_iZone, unsigned long TimeIter, unsigned long OuterIter, unsigned long InnerIter, unsigned short val_iInst){ +void COutput::SetHistoryOutput(CGeometry ****geometry, CSolver *****solver, CConfig **config, std::shared_ptr(TurboStagePerf), su2vector> TurboBladePerfs, unsigned short val_iZone, unsigned long TimeIter, unsigned long OuterIter, unsigned long InnerIter, unsigned short val_iInst){ unsigned long Iter= InnerIter; @@ -240,19 +240,19 @@ void COutput::SetHistoryOutput(CGeometry ****geometry, CSolver *****solver, CCon /*--- Turbomachinery Performance Screen summary output---*/ if (Iter%100 == 0 && rank == MASTER_NODE) { - SetTurboPerformance_Output(TurboPerf, config[val_iZone], TimeIter, OuterIter, InnerIter); - SetTurboMultiZonePerformance_Output(TurboStagePerf, TurboPerf, config[val_iZone]); + SetTurboPerformance_Output(TurboBladePerfs, config[val_iZone], TimeIter, OuterIter, InnerIter); //Blade-row index scree + SetTurboMultiZonePerformance_Output(TurboStagePerf, TurboBladePerfs, config[val_iZone]); //Stage performance screen } for (int iZone = 0; iZone < config[ZONE_0]->GetnZone(); iZone ++){ if (rank == MASTER_NODE) { - WriteTurboSpanwisePerformance(TurboPerf, geometry[iZone][val_iInst][MESH_0], config, iZone); + WriteTurboSpanwisePerformance(TurboBladePerfs, geometry[iZone][val_iInst][MESH_0], config, iZone); //Spanwise files } } /*--- Update turboperformance history file*/ if (rank == MASTER_NODE){ - LoadTurboHistoryData(TurboStagePerf, TurboPerf, config[val_iZone]); + LoadTurboHistoryData(TurboStagePerf, TurboBladePerfs, config[val_iZone]); //History files } } diff --git a/SU2_CFD/src/output/CTurboOutput.cpp b/SU2_CFD/src/output/CTurboOutput.cpp index 2d7e0138ff48..fbe760652f9c 100644 --- a/SU2_CFD/src/output/CTurboOutput.cpp +++ b/SU2_CFD/src/output/CTurboOutput.cpp @@ -39,8 +39,7 @@ CTurbomachineryCombinedPrimitiveStates::CTurbomachineryCombinedPrimitiveStates( : InletPrimitiveState(inletPrimitiveState), OutletPrimitiveState(outletPrimitiveState) {} CTurbomachineryState::CTurbomachineryState() { - Density = Pressure = Entropy = Enthalpy = Temperature = TotalTemperature = TotalPressure = TotalEnthalpy = 0.0; - AbsFlowAngle = FlowAngle = MassFlow = Rothalpy = TotalRelPressure = 0.0; + SetZeroValues(); Area = Radius = 0.0; } @@ -84,7 +83,7 @@ void CTurbomachineryState::ComputeState(CFluidModel& fluidModel, const CTurbomac su2double tangVel2 = TangVelocity * TangVelocity; RelVelocity.assign(Velocity.begin(), Velocity.end()); RelVelocity[1] -= TangVelocity; - su2double relVel2 = GetRelVelocityValue(); + su2double relVel2 = GetRelVelocityValue() * GetRelVelocityValue(); FlowAngle = atan(RelVelocity[1] / RelVelocity[0]); RelMach.assign(RelVelocity.begin(), RelVelocity.end()); std::for_each(RelMach.begin(), RelMach.end(), [&](su2double& el) { el /= soundSpeed; }); @@ -160,51 +159,46 @@ void CPropellorBladePerformance::ComputePerformance(const CTurbomachineryCombine // TODO: to be implemented } -CTurboOutput::CTurboOutput(CConfig** config, const CGeometry& geometry, CFluidModel& fluidModel) { - unsigned short nBladesRow = config[ZONE_0]->GetnMarker_Turbomachinery(); +CTurboOutput::CTurboOutput(CConfig** config, const CGeometry& geometry, CFluidModel& fluidModel, unsigned short iBladeRow) { unsigned short nDim = geometry.GetnDim(); - for (unsigned short iBladeRow = 0; iBladeRow < nBladesRow; iBladeRow++) { - vector> bladeSpanPerformances; - unsigned short nSpan = config[iBladeRow]->GetnSpanWiseSections(); - for (unsigned short iSpan = 0; iSpan < nSpan + 1; iSpan++) { - su2double areaIn = geometry.GetSpanAreaIn(iBladeRow, iSpan); - su2double areaOut = geometry.GetSpanAreaOut(iBladeRow, iSpan); - su2double radiusIn = geometry.GetTurboRadiusIn(iBladeRow, iSpan); - su2double radiusOut = geometry.GetTurboRadiusOut(iBladeRow, iSpan); - - /* Switch between the Turbomachinery Performance Kind */ - switch (config[ZONE_0]->GetKind_TurboPerf(iBladeRow)) { - case TURBO_PERF_KIND::TURBINE: - bladeSpanPerformances.push_back( - make_shared(fluidModel, nDim, areaIn, radiusIn, areaOut, radiusOut)); - break; - - case TURBO_PERF_KIND::COMPRESSOR: - bladeSpanPerformances.push_back( - make_shared(fluidModel, nDim, areaIn, radiusIn, areaOut, radiusOut)); - break; - - case TURBO_PERF_KIND::PROPELLOR: - bladeSpanPerformances.push_back( - make_shared(fluidModel, nDim, areaIn, radiusIn, areaOut, radiusOut)); - break; - - default: - bladeSpanPerformances.push_back( - make_shared(fluidModel, nDim, areaIn, radiusIn, areaOut, radiusOut)); - break; - } + vector> bladeSpanPerformances; + unsigned short nSpan = config[iBladeRow]->GetnSpanWiseSections(); + for (unsigned short iSpan = 0; iSpan < nSpan + 1; iSpan++) { + auto areaIn = geometry.GetSpanAreaIn(iBladeRow, iSpan); + auto areaOut = geometry.GetSpanAreaOut(iBladeRow, iSpan); + auto radiusIn = geometry.GetTurboRadiusIn(iBladeRow, iSpan); + auto radiusOut = geometry.GetTurboRadiusOut(iBladeRow, iSpan); + + /* Switch between the Turbomachinery Performance Kind */ + switch (config[ZONE_0]->GetKind_TurboPerf(iBladeRow)) { + case TURBO_PERF_KIND::TURBINE: + bladeSpanPerformances.push_back( + make_shared(fluidModel, nDim, areaIn, radiusIn, areaOut, radiusOut)); + break; + + case TURBO_PERF_KIND::COMPRESSOR: + bladeSpanPerformances.push_back( + make_shared(fluidModel, nDim, areaIn, radiusIn, areaOut, radiusOut)); + break; + + case TURBO_PERF_KIND::PROPELLOR: + bladeSpanPerformances.push_back( + make_shared(fluidModel, nDim, areaIn, radiusIn, areaOut, radiusOut)); + break; + + default: + bladeSpanPerformances.push_back( + make_shared(fluidModel, nDim, areaIn, radiusIn, areaOut, radiusOut)); + break; } - BladesPerformances.push_back(bladeSpanPerformances); } + BladesPerformances = bladeSpanPerformances; } void CTurboOutput::ComputeTurbomachineryPerformance( - vector> const bladesPrimitives) { - for (unsigned i = 0; i < BladesPerformances.size(); ++i) { - ComputePerBlade(BladesPerformances[i], bladesPrimitives[i]); - } + vector const bladePrimitives, unsigned short iBladeRow) { + ComputePerBlade(BladesPerformances, bladePrimitives); } void CTurboOutput::ComputePerBlade(vector> const bladePerformances, @@ -219,6 +213,22 @@ void CTurboOutput::ComputePerSpan(shared_ptr co spanPerformances->ComputePerformance(spanPrimitives); } +bool CTurboOutput::IsTurboObjective(unsigned short kind) { + return kind == ENTROPY_GENERATION || kind == TOTAL_PRESSURE_LOSS || kind == KINETIC_ENERGY_LOSS; +} + +su2double CTurboOutput::GetObjectiveValue(unsigned short kind) const { + /*--- Use the tip-span (last element) performance, consistent with how ComputeTurboBladePerformance + * selects the representative blade performance for objective function evaluation. ---*/ + const auto& perf = BladesPerformances.back(); + switch (kind) { + case ENTROPY_GENERATION: return perf->GetEntropyGen(); + case TOTAL_PRESSURE_LOSS: return perf->GetTotalPressureLoss(); + case KINETIC_ENERGY_LOSS: return perf->GetKineticEnergyLoss(); + default: return 0.0; + } +} + CTurbomachineryStagePerformance::CTurbomachineryStagePerformance(CFluidModel& fluid) : fluidModel(fluid) {} void CTurbomachineryStagePerformance::ComputePerformanceStage(const CTurbomachineryState& InState, @@ -265,6 +275,8 @@ void CTurbomachineryStagePerformance::ComputeCompressorStagePerformance(const CT fluidModel.SetTDState_Ps(OutState.GetPressure(), InState.GetEntropy()); su2double enthalpyOutIs = fluidModel.GetStaticEnergy() + OutState.GetPressure() / fluidModel.GetDensity(); su2double totEnthalpyOutIs = enthalpyOutIs + 0.5 * OutState.GetVelocityValue() * OutState.GetVelocityValue(); + su2double tangVel = OutState.GetTangVelocity(); + su2double relVelOutIs2 = 2 * (OutState.GetRothalpy() - enthalpyOutIs) + tangVel * tangVel; /*--- Compute compressor stage performance ---*/ NormEntropyGen = (OutState.GetEntropy() - InState.GetEntropy()) / InState.GetEntropy(); @@ -273,4 +285,7 @@ void CTurbomachineryStagePerformance::ComputeCompressorStagePerformance(const CT TotalTotalEfficiency = (totEnthalpyOutIs - InState.GetTotalEnthalpy()) / EulerianWork; TotalStaticPressureRatio = OutState.GetPressure() / InState.GetTotalPressure(); TotalTotalPressureRatio = OutState.GetTotalPressure() / InState.GetTotalPressure(); + TotalPressureLoss = (InState.GetTotalRelPressure() - OutState.GetTotalRelPressure()) / + (InState.GetTotalRelPressure() - InState.GetPressure()); + KineticEnergyLoss = 2 * (OutState.GetEnthalpy() - enthalpyOutIs) / relVelOutIs2; } \ No newline at end of file diff --git a/SU2_CFD/src/solvers/CDiscAdjSolver.cpp b/SU2_CFD/src/solvers/CDiscAdjSolver.cpp index 0f396a2ebd04..056c774d8cec 100644 --- a/SU2_CFD/src/solvers/CDiscAdjSolver.cpp +++ b/SU2_CFD/src/solvers/CDiscAdjSolver.cpp @@ -186,21 +186,30 @@ void CDiscAdjSolver::RegisterVariables(CGeometry *geometry, CConfig *config, boo if((config->GetKind_Regime() == ENUM_REGIME::COMPRESSIBLE) && (KindDirect_Solver == RUNTIME_FLOW_SYS && !config->GetBoolTurbomachinery())){ - su2double Velocity_Ref = config->GetVelocity_Ref(); - Alpha = config->GetAoA()*PI_NUMBER/180.0; - Beta = config->GetAoS()*PI_NUMBER/180.0; - Mach = config->GetMach(); - /*--- Pressure and Temperature can be registered directly via their config file value - * (no further treatment required here). ---*/ - su2double& Pressure = config->GetPressure_FreeStreamND(); - su2double& Temperature = config->GetTemperature_FreeStreamND(); - - su2double SoundSpeed = 0.0; - - // Treat Velocity_FreeStreamND config value as non-dependent (in debug mode) - AD::ClearTagOnVariable(config->GetVelocity_FreeStreamND()[0]); - if (nDim == 2) { SoundSpeed = config->GetVelocity_FreeStreamND()[0]*Velocity_Ref/(cos(Alpha)*Mach); } - if (nDim == 3) { SoundSpeed = config->GetVelocity_FreeStreamND()[0]*Velocity_Ref/(cos(Alpha)*cos(Beta)*Mach); } + static bool value_is_set = false; + static double Velocity_Ref = 0.0, Velocity_FreeStreamND = 0.0, AlphaValue = 0.0, BetaValue = 0.0, MachValue = 0.0, TemperatureValue = 0.0, PressureValue = 0.0; + if (!value_is_set) { + Velocity_Ref = SU2_TYPE::GetValue(config->GetVelocity_Ref()); + Velocity_FreeStreamND = SU2_TYPE::GetValue(config->GetVelocity_FreeStreamND()[0]); + AlphaValue = SU2_TYPE::GetValue(config->GetAoA())*PI_NUMBER/180.0; + BetaValue = SU2_TYPE::GetValue(config->GetAoS())*PI_NUMBER/180.0; + MachValue = SU2_TYPE::GetValue(config->GetMach()); + TemperatureValue = SU2_TYPE::GetValue(config->GetTemperature_FreeStreamND()); + PressureValue = SU2_TYPE::GetValue(config->GetPressure_FreeStreamND()); + value_is_set = true; + } + + Alpha = AlphaValue; + Beta = BetaValue; + Mach = MachValue; + Pressure = PressureValue; + Temperature = TemperatureValue; + + double SoundSpeed = 0.0; + if (nDim == 2) { SoundSpeed = Velocity_FreeStreamND*Velocity_Ref/(cos(AlphaValue)*MachValue); } + if (nDim == 3) { SoundSpeed = Velocity_FreeStreamND*Velocity_Ref/(cos(AlphaValue)*cos(BetaValue)*MachValue); } + + /*--- Register the variables for AD. ---*/ if (!reset) { AD::RegisterInput(Mach); @@ -209,7 +218,7 @@ void CDiscAdjSolver::RegisterVariables(CGeometry *geometry, CConfig *config, boo AD::RegisterInput(Pressure); } - /*--- Recompute the free stream velocity ---*/ + /*--- Set the free stream velocity (now using the registered values for Alpha, Beta and Mach). ---*/ if (nDim == 2) { config->GetVelocity_FreeStreamND()[0] = cos(Alpha)*Mach*SoundSpeed/Velocity_Ref; @@ -221,14 +230,26 @@ void CDiscAdjSolver::RegisterVariables(CGeometry *geometry, CConfig *config, boo config->GetVelocity_FreeStreamND()[2] = sin(Alpha)*cos(Beta)*Mach*SoundSpeed/Velocity_Ref; } + /*--- Set the freestream values in the direct solver (now using the registered values for Temperature and Pressure). ---*/ + direct_solver->SetTemperature_Inf(Temperature); direct_solver->SetPressure_Inf(Pressure); } if ((config->GetKind_Regime() == ENUM_REGIME::COMPRESSIBLE) && (KindDirect_Solver == RUNTIME_FLOW_SYS) && config->GetBoolTurbomachinery()){ - BPressure = config->GetPressureOut_BC(); - Temperature = config->GetTotalTemperatureIn_BC(); + static bool value_is_set = false; + static double BPressureValue = 0.0, TemperatureValue = 0.0; + if (!value_is_set) { + BPressureValue = SU2_TYPE::GetValue(config->GetPressureOut_BC()); + TemperatureValue = SU2_TYPE::GetValue(config->GetTotalTemperatureIn_BC()); + value_is_set = true; + } + + BPressure = BPressureValue; + Temperature = TemperatureValue; + + /*--- Register the variables for AD. ---*/ if (!reset){ AD::RegisterInput(BPressure); @@ -245,15 +266,25 @@ void CDiscAdjSolver::RegisterVariables(CGeometry *geometry, CConfig *config, boo ((KindDirect_Solver == RUNTIME_FLOW_SYS && (!config->GetBoolTurbomachinery())))) { - /*--- Access the velocity (or pressure) and temperature at the + static bool value_is_set = false; + static double ModVelValue = 0.0, BPressureValue = 0.0, TemperatureValue = 0.0; + + /*--- Access the velocity (or pressure) and temperature at the inlet BC and the back pressure at the outlet. Note that we are assuming that have internal flow, which will be true for the majority of cases. External flows with far-field BCs will report zero for these sensitivities. ---*/ - ModVel = config->GetIncInlet_BC(); - BPressure = config->GetIncPressureOut_BC(); - Temperature = config->GetIncTemperature_BC(); + if (!value_is_set) { + ModVelValue = SU2_TYPE::GetValue(config->GetIncInlet_BC()); + BPressureValue = SU2_TYPE::GetValue(config->GetIncPressureOut_BC()); + TemperatureValue = SU2_TYPE::GetValue(config->GetIncTemperature_BC()); + value_is_set = true; + } + + ModVel = ModVelValue; + BPressure = BPressureValue; + Temperature = TemperatureValue; /*--- Register the variables for AD. ---*/ @@ -279,7 +310,14 @@ void CDiscAdjSolver::RegisterVariables(CGeometry *geometry, CConfig *config, boo /*--- Access the nondimensional freestream temperature. ---*/ - TemperatureRad = config->GetTemperature_FreeStreamND(); + static bool value_is_set = false; + static double TemperatureRadValue = 0.0; + if (!value_is_set) { + TemperatureRadValue = SU2_TYPE::GetValue(config->GetTemperature_FreeStreamND()); + value_is_set = true; + } + TemperatureRad = TemperatureRadValue; + /*--- Register the variables for AD. ---*/ @@ -409,9 +447,6 @@ void CDiscAdjSolver::ExtractAdjoint_Variables(CGeometry *geometry, CConfig *conf if ((config->GetKind_Regime() == ENUM_REGIME::COMPRESSIBLE) && (KindDirect_Solver == RUNTIME_FLOW_SYS) && !config->GetBoolTurbomachinery()) { su2double Local_Sens_Press, Local_Sens_Temp, Local_Sens_AoA, Local_Sens_Mach; - su2double& Pressure = config->GetPressure_FreeStreamND(); - su2double& Temperature = config->GetTemperature_FreeStreamND(); - Local_Sens_Mach = SU2_TYPE::GetDerivative(Mach); Local_Sens_AoA = SU2_TYPE::GetDerivative(Alpha); Local_Sens_Temp = SU2_TYPE::GetDerivative(Temperature); diff --git a/SU2_CFD/src/solvers/CEulerSolver.cpp b/SU2_CFD/src/solvers/CEulerSolver.cpp index d5577a2320f5..acae4cbde050 100644 --- a/SU2_CFD/src/solvers/CEulerSolver.cpp +++ b/SU2_CFD/src/solvers/CEulerSolver.cpp @@ -35,9 +35,10 @@ #include "../../include/fluid/CDataDrivenFluid.hpp" #include "../../include/fluid/CCoolProp.hpp" #include "../../include/numerics_simd/CNumericsSIMD.hpp" +#include "../../include/limiters/CLimiterDetails.hpp" +#include "../../include/output/COutput.hpp" #include "../../include/output/CTurboOutput.hpp" - CEulerSolver::CEulerSolver(CGeometry *geometry, CConfig *config, unsigned short iMesh, const bool navier_stokes) : CFVMFlowSolverBase(*geometry, *config) { @@ -364,8 +365,6 @@ CEulerSolver::CEulerSolver(CGeometry *geometry, CConfig *config, } CEulerSolver::~CEulerSolver() { - SU2_ZONE_SCOPED - for(auto& model : FluidModel) delete model; } @@ -391,38 +390,32 @@ void CEulerSolver::InstantiateEdgeNumerics(const CSolver* const* solver_containe END_SU2_OMP_SAFE_GLOBAL_ACCESS } -void CEulerSolver::InitTurboContainers(CGeometry *geometry, CConfig *config){ - SU2_ZONE_SCOPED +void CEulerSolver::InitTurboContainers(CGeometry *geometry, CConfig **config_container, unsigned short iZone){ - /*--- Initialize quantities for the average process for internal flow ---*/ + auto config = config_container[iZone]; + /*--- Initialize quantities for the average process for internal flow ---*/ const auto nSpanWiseSections = config->GetnSpanWiseSections(); AverageVelocity.resize(nMarker); AverageTurboVelocity.resize(nMarker); OldAverageTurboVelocity.resize(nMarker); - ExtAverageTurboVelocity.resize(nMarker); AverageFlux.resize(nMarker); SpanTotalFlux.resize(nMarker); AveragePressure.resize(nMarker,nSpanWiseSections+1) = su2double(0.0); OldAveragePressure = AveragePressure; RadialEquilibriumPressure = AveragePressure; - ExtAveragePressure = AveragePressure; AverageDensity = AveragePressure; OldAverageDensity = AveragePressure; - ExtAverageDensity = AveragePressure; AverageNu = AveragePressure; AverageKine = AveragePressure; AverageOmega = AveragePressure; - ExtAverageNu = AveragePressure; - ExtAverageKine = AveragePressure; - ExtAverageOmega = AveragePressure; + AverageRelTangVelocity = AveragePressure; for (unsigned long iMarker = 0; iMarker < nMarker; iMarker++) { AverageVelocity[iMarker].resize(nSpanWiseSections+1,nDim) = su2double(0.0); AverageTurboVelocity[iMarker].resize(nSpanWiseSections+1,nDim) = su2double(0.0); OldAverageTurboVelocity[iMarker].resize(nSpanWiseSections+1,nDim) = su2double(0.0); - ExtAverageTurboVelocity[iMarker].resize(nSpanWiseSections+1,nDim) = su2double(0.0); AverageFlux[iMarker].resize(nSpanWiseSections+1,nVar) = su2double(0.0); SpanTotalFlux[iMarker].resize(nSpanWiseSections+1,nVar) = su2double(0.0); } @@ -444,6 +437,8 @@ void CEulerSolver::InitTurboContainers(CGeometry *geometry, CConfig *config){ KineOut = DensityIn; OmegaOut = DensityIn; NuOut = DensityIn; + RelTangVelocityIn = DensityIn; + RelTangVelocityOut = DensityIn; for (unsigned long iMarker = 0; iMarker < nMarkerTurboPerf; iMarker++) { TurboVelocityIn[iMarker].resize(nSpanMax+1,nDim) = su2double(0.0); @@ -464,6 +459,23 @@ void CEulerSolver::InitTurboContainers(CGeometry *geometry, CConfig *config){ CkOutflow2[iMarker] = CkOutflow1[iMarker]; } } + + /*--- Initialise donor quantities ---*/ + ExtAverageDensity = su2double(0.0); + ExtAveragePressure = su2double(0.0); + ExtAverageTurboVelocity.resize(nDim); + for (auto iDim = 0u; iDim < nDim; iDim++) ExtAverageTurboVelocity[iDim] = su2double(0.0); + + MixingState.resize(nMarker); + + for (auto iMarkerInt = 1; iMarkerInt < config->GetnMarker_MixingPlaneInterface()/2 + 1; iMarkerInt++) { + auto iMarkerMP = config->FindMixingPlaneInterfaceMarker(geometry->GetnMarker(), iMarkerInt); + if (iMarkerMP != -1) { + MixingState[iMarkerMP].resize(nSpanWiseSections+1, nMixingStateVars) = su2double(0.0); + } + } + + TurbomachineryPerformance = std::make_shared(config_container, *geometry, *GetFluidModel(), iZone); } void CEulerSolver::Set_MPI_ActDisk(CSolver **solver_container, CGeometry *geometry, CConfig *config) { @@ -4966,6 +4978,10 @@ void CEulerSolver::Evaluate_ObjFunc(const CConfig *config, CSolver**) { Weight_ObjFunc = config->GetWeight_ObjFunc(0); Kind_ObjFunc = config->GetKind_ObjFunc(0); + /*--- Turbomachinery specific objective functions ---*/ + if (TurbomachineryPerformance && CTurboOutput::IsTurboObjective(Kind_ObjFunc)) + Total_ComboObj += Weight_ObjFunc * TurbomachineryPerformance->GetObjectiveValue(Kind_ObjFunc); + switch(Kind_ObjFunc) { case NEARFIELD_PRESSURE: Total_ComboObj+=Weight_ObjFunc*Total_CNearFieldOF; @@ -5780,6 +5796,20 @@ void CEulerSolver::BC_TurboRiemann(CGeometry *geometry, CSolver **solver_contain for (iDim = 0; iDim < nDim; iDim++) ProjVelocity_i += Velocity_i[iDim]*UnitNormal[iDim]; + su2double donorAverages[5] = {0.0}; + switch (config->GetKind_Data_Giles(Marker_Tag)){ + case MIXING_IN: case MIXING_IN_1D: case MIXING_OUT: case MIXING_OUT_1D: + for (auto mixVar = 0u; mixVar < 5; mixVar++) donorAverages[mixVar] = GetMixingState(val_marker, iSpan, mixVar); + break; + default: + break; + } + ExtAverageDensity = donorAverages[0]; + ExtAveragePressure = donorAverages[1]; + ExtAverageTurboVelocity[0] = donorAverages[2]; + ExtAverageTurboVelocity[1] = donorAverages[3]; + if (nDim == 3) ExtAverageTurboVelocity[2] = donorAverages[4]; + /*--- Build the external state u_e from boundary data and internal node ---*/ switch(config->GetKind_Data_Riemann(Marker_Tag)) @@ -5820,15 +5850,15 @@ void CEulerSolver::BC_TurboRiemann(CGeometry *geometry, CSolver **solver_contain case MIXING_IN: /* --- compute total averaged quantities ---*/ - GetFluidModel()->SetTDState_Prho(ExtAveragePressure[val_marker][iSpan], ExtAverageDensity[val_marker][iSpan]); - AverageEnthalpy = GetFluidModel()->GetStaticEnergy() + ExtAveragePressure[val_marker][iSpan]/ExtAverageDensity[val_marker][iSpan]; + GetFluidModel()->SetTDState_Prho(ExtAveragePressure, ExtAverageDensity); + AverageEnthalpy = GetFluidModel()->GetStaticEnergy() + ExtAveragePressure/ExtAverageDensity; AverageEntropy = GetFluidModel()->GetEntropy(); FlowDirMixMag = 0; for (iDim = 0; iDim < nDim; iDim++) - FlowDirMixMag += ExtAverageTurboVelocity[val_marker][iSpan][iDim]*ExtAverageTurboVelocity[val_marker][iSpan][iDim]; + FlowDirMixMag += ExtAverageTurboVelocity[iDim]*ExtAverageTurboVelocity[iDim]; for (iDim = 0; iDim < nDim; iDim++){ - FlowDirMix[iDim] = ExtAverageTurboVelocity[val_marker][iSpan][iDim]/sqrt(FlowDirMixMag); + FlowDirMix[iDim] = ExtAverageTurboVelocity[iDim]/sqrt(FlowDirMixMag); } @@ -5856,7 +5886,7 @@ void CEulerSolver::BC_TurboRiemann(CGeometry *geometry, CSolver **solver_contain case MIXING_OUT: /*--- Retrieve the static pressure for this boundary. ---*/ - Pressure_e = ExtAveragePressure[val_marker][iSpan]; + Pressure_e = ExtAveragePressure; Density_e = Density_i; /* --- Compute the boundary state u_e --- */ @@ -6186,8 +6216,7 @@ void CEulerSolver::BC_TurboRiemann(CGeometry *geometry, CSolver **solver_contain } void CEulerSolver::PreprocessBC_Giles(CGeometry *geometry, CConfig *config, CNumerics *conv_numerics, unsigned short marker_flag) { - SU2_ZONE_SCOPED - /* Implementation of Fuorier Transformations for non-regfelcting BC will come soon */ + /* Implementation of Fourier Transformations for non-reflecting BC will come soon */ su2double cj_inf,cj_out1, cj_out2, Density_i, Pressure_i, *turboNormal, *turboVelocity, *Velocity_i, AverageSoundSpeed; su2double *deltaprim, *cj, TwoPiThetaFreq_Pitch, pitch, theta, deltaTheta; unsigned short iMarker, iSpan, iMarkerTP, iDim; @@ -6431,7 +6460,6 @@ void CEulerSolver::BC_Giles(CGeometry *geometry, CSolver **solver_container, CNu deltaSpan = SpanWiseValues[nSpanWiseSections-1]*spanPercent; coeffrelfacAvg = (relfacAvgCfg - extrarelfacAvg)/deltaSpan; } - for (iSpan= 0; iSpan < nSpanWiseSections ; iSpan++){ /*--- Compute under relaxation for the Hub and Shroud Avg and Fourier Coefficient---*/ if(nDim == 3){ @@ -6461,7 +6489,7 @@ void CEulerSolver::BC_Giles(CGeometry *geometry, CSolver **solver_container, CNu AverageTurboMach[1] = AverageTurboVelocity[val_marker][iSpan][1]/AverageSoundSpeed; if(dynamic_grid){ - AverageTurboMach[1] -= geometry->GetAverageTangGridVel(val_marker,iSpan)/AverageSoundSpeed; + AverageTurboMach[1] = AverageRelTangVelocity[val_marker][iSpan]/AverageSoundSpeed; } AvgMach = AverageTurboMach[0]*AverageTurboMach[0] + AverageTurboMach[1]*AverageTurboMach[1]; @@ -6470,6 +6498,22 @@ void CEulerSolver::BC_Giles(CGeometry *geometry, CSolver **solver_container, CNu kend_max = geometry->GetnFreqSpanMax(config->GetMarker_All_TurbomachineryFlag(val_marker)); conv_numerics->GetRMatrix(AverageSoundSpeed, AverageDensity[val_marker][iSpan], R_Matrix); + su2double donorAverages[5] = {0.0}; + switch (config->GetKind_Data_Giles(Marker_Tag)){ + case MIXING_IN: case MIXING_IN_1D: case MIXING_OUT: case MIXING_OUT_1D: + for (auto mixVar = 0u; mixVar < 5; mixVar++) donorAverages[mixVar] = GetMixingState(val_marker, iSpan, mixVar); + break; + default: + break; + } + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { + ExtAverageDensity = donorAverages[0]; + ExtAveragePressure = donorAverages[1]; + ExtAverageTurboVelocity[0] = donorAverages[2]; + ExtAverageTurboVelocity[1] = donorAverages[3]; + if (nDim == 3) ExtAverageTurboVelocity[2] = donorAverages[4]; + } END_SU2_OMP_SAFE_GLOBAL_ACCESS + switch(config->GetKind_Data_Giles(Marker_Tag)){ case TOTAL_CONDITIONS_PT: @@ -6587,16 +6631,16 @@ void CEulerSolver::BC_Giles(CGeometry *geometry, CSolver **solver_container, CNu case MIXING_IN: case MIXING_OUT: /* --- Compute average jump of primitive at the mixing-plane interface--- */ - deltaprim[0] = ExtAverageDensity[val_marker][iSpan] - AverageDensity[val_marker][iSpan]; - deltaprim[1] = ExtAverageTurboVelocity[val_marker][iSpan][0] - AverageTurboVelocity[val_marker][iSpan][0]; - deltaprim[2] = ExtAverageTurboVelocity[val_marker][iSpan][1] - AverageTurboVelocity[val_marker][iSpan][1]; + deltaprim[0] = ExtAverageDensity - AverageDensity[val_marker][iSpan]; + deltaprim[1] = ExtAverageTurboVelocity[0] - AverageTurboVelocity[val_marker][iSpan][0]; + deltaprim[2] = ExtAverageTurboVelocity[1] - AverageTurboVelocity[val_marker][iSpan][1]; if (nDim == 2){ - deltaprim[3] = ExtAveragePressure[val_marker][iSpan] - AveragePressure[val_marker][iSpan]; + deltaprim[3] = ExtAveragePressure - AveragePressure[val_marker][iSpan]; } else { - deltaprim[3] = ExtAverageTurboVelocity[val_marker][iSpan][2] - AverageTurboVelocity[val_marker][iSpan][2]; - deltaprim[4] = ExtAveragePressure[val_marker][iSpan] - AveragePressure[val_marker][iSpan]; + deltaprim[3] = ExtAverageTurboVelocity[2] - AverageTurboVelocity[val_marker][iSpan][2]; + deltaprim[4] = ExtAveragePressure - AveragePressure[val_marker][iSpan]; } @@ -6609,16 +6653,16 @@ void CEulerSolver::BC_Giles(CGeometry *geometry, CSolver **solver_container, CNu case MIXING_IN_1D: case MIXING_OUT_1D: /* --- Compute average jump of primitive at the mixing-plane interface--- */ - deltaprim[0] = ExtAverageDensity[val_marker][nSpanWiseSections] - AverageDensity[val_marker][nSpanWiseSections]; - deltaprim[1] = ExtAverageTurboVelocity[val_marker][nSpanWiseSections][0] - AverageTurboVelocity[val_marker][nSpanWiseSections][0]; - deltaprim[2] = ExtAverageTurboVelocity[val_marker][nSpanWiseSections][1] - AverageTurboVelocity[val_marker][nSpanWiseSections][1]; + deltaprim[0] = ExtAverageDensity - AverageDensity[val_marker][nSpanWiseSections]; + deltaprim[1] = ExtAverageTurboVelocity[0] - AverageTurboVelocity[val_marker][nSpanWiseSections][0]; + deltaprim[2] = ExtAverageTurboVelocity[1] - AverageTurboVelocity[val_marker][nSpanWiseSections][1]; if (nDim == 2){ - deltaprim[3] = ExtAveragePressure[val_marker][nSpanWiseSections] - AveragePressure[val_marker][nSpanWiseSections]; + deltaprim[3] = ExtAveragePressure - AveragePressure[val_marker][nSpanWiseSections]; } else { - deltaprim[3] = ExtAverageTurboVelocity[val_marker][nSpanWiseSections][2] - AverageTurboVelocity[val_marker][nSpanWiseSections][2]; - deltaprim[4] = ExtAveragePressure[val_marker][nSpanWiseSections] - AveragePressure[val_marker][nSpanWiseSections]; + deltaprim[3] = ExtAverageTurboVelocity[2] - AverageTurboVelocity[val_marker][nSpanWiseSections][2]; + deltaprim[4] = ExtAveragePressure - AveragePressure[val_marker][nSpanWiseSections]; } /* --- Compute average jump of charachteristic variable at the mixing-plane interface--- */ @@ -9044,11 +9088,16 @@ void CEulerSolver::PreprocessAverage(CSolver **solver, CGeometry *geometry, CCon const auto nSpanWiseSections = config->GetnSpanWiseSections(); const auto iZone = config->GetiZone(); + const bool spalart_allmaras = (config->GetKind_Turb_Model() == TURB_MODEL::SA); + const bool menter_sst = (config->GetKind_Turb_Model() == TURB_MODEL::SST); for (auto iSpan= 0u; iSpan < nSpanWiseSections; iSpan++){ su2double TotalAreaVelocity[MAXNDIM]={0.0}, TotalAreaPressure{0}, - TotalAreaDensity{0}; + TotalAreaDensity{0}, + TotalAreaNu{0}, + TotalAreaKine{0}, + TotalAreaOmega{0}; for (auto iMarker = 0u; iMarker < config->GetnMarker_All(); iMarker++){ for (auto iMarkerTP=1; iMarkerTP < config->GetnMarker_Turbomachinery()+1; iMarkerTP++){ if (config->GetMarker_All_Turbomachinery(iMarker) == iMarkerTP){ @@ -9065,6 +9114,16 @@ void CEulerSolver::PreprocessAverage(CSolver **solver, CGeometry *geometry, CCon auto Pressure = nodes->GetPressure(iPoint); auto Density = nodes->GetDensity(iPoint); + /*--- This is in Euler, however we also need to average the turbulent variables so we do it here too ---*/ + su2double Kine{0}, Omega{0}, Nu{0}; + if(menter_sst){ + Kine = solver[TURB_SOL]->GetNodes()->GetSolution(iPoint,0); + Omega = solver[TURB_SOL]->GetNodes()->GetSolution(iPoint,1); + } + if(spalart_allmaras){ + Nu = solver[TURB_SOL]->GetNodes()->GetSolution(iPoint,0); + } + su2double UnitNormal[MAXNDIM]={0}, TurboNormal[MAXNDIM]={0}, TurboVelocity[MAXNDIM], @@ -9085,6 +9144,9 @@ void CEulerSolver::PreprocessAverage(CSolver **solver, CGeometry *geometry, CCon TotalAreaDensity += Area*Density; for (auto iDim = 0u; iDim < nDim; iDim++) TotalAreaVelocity[iDim] += Area*Velocity[iDim]; + TotalAreaNu += Area*Nu; + TotalAreaKine += Area*Kine; + TotalAreaOmega += Area*Omega; } } } @@ -9094,29 +9156,34 @@ void CEulerSolver::PreprocessAverage(CSolver **solver, CGeometry *geometry, CCon #ifdef HAVE_MPI - /*--- Add information using all the nodes ---*/ - - su2double MyTotalAreaDensity = TotalAreaDensity; - su2double MyTotalAreaPressure = TotalAreaPressure; + auto Allreduce = [](su2double x) { + su2double tmp = x; x = 0.0; + SU2_MPI::Allreduce(&tmp, &x, 1, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); + return x; + }; - SU2_MPI::Allreduce(&MyTotalAreaDensity, &TotalAreaDensity, 1, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); - SU2_MPI::Allreduce(&MyTotalAreaPressure, &TotalAreaPressure, 1, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); + /*--- Add information using all the nodes ---*/ - auto* MyTotalAreaVelocity = new su2double[nDim]; + TotalAreaDensity = Allreduce(TotalAreaDensity); + TotalAreaPressure = Allreduce(TotalAreaPressure); + TotalAreaNu = Allreduce(TotalAreaNu); + TotalAreaKine = Allreduce(TotalAreaKine); + TotalAreaOmega = Allreduce(TotalAreaOmega); - for (auto iDim = 0u; iDim < nDim; iDim++) { - MyTotalAreaVelocity[iDim] = TotalAreaVelocity[iDim]; - } + auto* buffer = new su2double[nDim]; - SU2_MPI::Allreduce(MyTotalAreaVelocity, TotalAreaVelocity, nDim, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); + auto Allreduce_inplace = [buffer](int size, su2double* x) { + SU2_MPI::Allreduce(x, buffer, size, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); + for(int i=0; iGetnMarker_All(); iMarker++){ for (auto iMarkerTP=1; iMarkerTP < config->GetnMarker_Turbomachinery()+1; iMarkerTP++){ if (config->GetMarker_All_Turbomachinery(iMarker) == iMarkerTP){ @@ -9132,6 +9199,10 @@ void CEulerSolver::PreprocessAverage(CSolver **solver, CGeometry *geometry, CCon for (auto iDim = 0u; iDim < nDim; iDim++) AverageVelocity[iMarker][iSpan][iDim] = TotalAreaVelocity[iDim] / TotalArea; + AverageNu[iMarker][iSpan] = TotalAreaNu / TotalArea; + AverageKine[iMarker][iSpan] = TotalAreaKine / TotalArea; + AverageOmega[iMarker][iSpan] = TotalAreaOmega / TotalArea; + /* --- compute static averaged quantities ---*/ ComputeTurboVelocity(AverageVelocity[iMarker][iSpan], AverageTurboNormal , AverageTurboVelocity[iMarker][iSpan], marker_flag, config->GetKind_TurboMachinery(iZone)); @@ -9162,6 +9233,10 @@ void CEulerSolver::PreprocessAverage(CSolver **solver, CGeometry *geometry, CCon for (auto iDim = 0u; iDim < nDim; iDim++) AverageVelocity[iMarker][nSpanWiseSections][iDim] = AverageVelocity[iMarker][nSpanWiseSections/2][iDim]; + AverageNu[iMarker][nSpanWiseSections] = AverageNu[iMarker][nSpanWiseSections/2]; + AverageKine[iMarker][nSpanWiseSections] = AverageKine[iMarker][nSpanWiseSections/2]; + AverageOmega[iMarker][nSpanWiseSections] = AverageOmega[iMarker][nSpanWiseSections/2]; + /* --- compute static averaged quantities ---*/ ComputeTurboVelocity(AverageVelocity[iMarker][nSpanWiseSections], AverageTurboNormal , AverageTurboVelocity[iMarker][nSpanWiseSections], marker_flag, config->GetKind_TurboMachinery(iZone)); @@ -9191,7 +9266,9 @@ void CEulerSolver::TurboAverageProcess(CSolver **solver, CGeometry *geometry, CC for (auto iSpan= 0; iSpan < nSpanWiseSections + 1; iSpan++){ su2double TotalDensity{0}, TotalPressure{0}, TotalNu{0}, TotalOmega{0}, TotalKine{0}, TotalVelocity[MAXNDIM], TotalAreaDensity{0}, TotalAreaPressure{0}, TotalAreaNu{0}, TotalAreaOmega{0}, TotalAreaKine{0}, TotalAreaVelocity[MAXNDIM], - TotalMassDensity{0}, TotalMassPressure{0}, TotalMassNu{0}, TotalMassOmega{0}, TotalMassKine{0}, TotalMassVelocity[MAXNDIM]; + TotalMassDensity{0}, TotalMassPressure{0}, TotalMassNu{0}, TotalMassOmega{0}, TotalMassKine{0}, TotalMassVelocity[MAXNDIM], + TotalRelTangVel{0}, TotalTangFlux{0}, TotalAreaRelTangVel{0}, + TotalAbsTangVel{0}, TotalAreaAbsTangVel{0}, TotalMassAbsTangVel{0}; su2double TotalFluxes[MAXNVAR]; /*--- Forces initialization for contenitors ---*/ @@ -9213,7 +9290,7 @@ void CEulerSolver::TurboAverageProcess(CSolver **solver, CGeometry *geometry, CC const auto Density = nodes->GetDensity(iPoint); const auto Enthalpy = nodes->GetEnthalpy(iPoint); - su2double Velocity[MAXNDIM] = {0}, UnitNormal[MAXNDIM] = {0}, TurboNormal[MAXNDIM] = {0}, TurboVelocity[MAXNDIM] = {0}; + su2double Velocity[MAXNDIM] = {0}, UnitNormal[MAXNDIM] = {0}, TurboNormal[MAXNDIM] = {0}, TurboVelocity[MAXNDIM] = {0}, TurboGridVelocity[MAXNDIM] = {0}; geometry->turbovertex[iMarker][iSpan][iVertex]->GetNormal(UnitNormal); geometry->turbovertex[iMarker][iSpan][iVertex]->GetTurboNormal(TurboNormal); const auto Area = geometry->turbovertex[iMarker][iSpan][iVertex]->GetArea(); @@ -9244,6 +9321,20 @@ void CEulerSolver::TurboAverageProcess(CSolver **solver, CGeometry *geometry, CC TotalFluxes[iDim] += Area*(Density*TurboVelocity[0]*TurboVelocity[iDim -1]); TotalFluxes[nDim+1] += Area*(Density*TurboVelocity[0]*Enthalpy); + /*--- Compute relative tangential velocity ---*/ + if (dynamic_grid) { + auto GridVel = geometry->nodes->GetGridVel(iPoint); + ComputeTurboVelocity(GridVel, TurboNormal, TurboGridVelocity, marker_flag, config->GetKind_TurboMachinery(iZone)); + } + + TotalRelTangVel += (TurboVelocity[1] - TurboGridVelocity[1]); + TotalAreaRelTangVel += Area*(TurboVelocity[1] - TurboGridVelocity[1]); + TotalTangFlux += Area*Density*TurboVelocity[0]*(TurboVelocity[1] - TurboGridVelocity[1]); + + TotalAbsTangVel += TurboVelocity[1]; + TotalAreaAbsTangVel += Area*TurboVelocity[1]; + TotalMassAbsTangVel += Area*Density*TurboVelocity[0]*TurboVelocity[1]; + /*--- Compute turbulent integral quantities for the boundary of interest ---*/ if(turbulent){ @@ -9321,6 +9412,14 @@ void CEulerSolver::TurboAverageProcess(CSolver **solver, CGeometry *geometry, CC TotalMassKine = Allreduce(TotalMassKine); TotalMassOmega = Allreduce(TotalMassOmega); + TotalRelTangVel = Allreduce(TotalRelTangVel); + TotalAreaRelTangVel = Allreduce(TotalAreaRelTangVel); + TotalTangFlux = Allreduce(TotalTangFlux); + + TotalAbsTangVel = Allreduce(TotalAbsTangVel); + TotalAreaAbsTangVel = Allreduce(TotalAreaAbsTangVel); + TotalMassAbsTangVel = Allreduce(TotalMassAbsTangVel); + auto* buffer = new su2double[max(nVar,nDim)]; auto Allreduce_inplace = [buffer](int size, su2double* x) { @@ -9355,7 +9454,7 @@ void CEulerSolver::TurboAverageProcess(CSolver **solver, CGeometry *geometry, CC /*--- Compute the averaged value for the boundary of interest for the span of interest ---*/ const bool belowMachLimit = (abs(MachTest)< config->GetAverageMachLimit()); - su2double avgDensity{0}, avgPressure{0}, avgKine{0}, avgOmega{0}, avgNu{0}, avgVelocity[MAXNDIM] = {0}; + su2double avgDensity{0}, avgPressure{0}, avgKine{0}, avgOmega{0}, avgNu{0}, avgVelocity[MAXNDIM] = {0}, avgRelTangVel{0}, avgAbsTangVel{0}; for (auto iVar = 0u; iVarGetKind_TurboMachinery(iZone)); } + /*--- Override the tangential component with the direct per-vertex turbo-frame average + * so that TurboVel[1] - RelTangVelocity = avg(grid tang. vel.) ---*/ + TurboVel[1] = avgAbsTangVel; } } } // iMarkerTP @@ -9607,8 +9724,8 @@ void CEulerSolver::GatherInOutAverageValues(CConfig *config, CGeometry *geometry unsigned short iMarker, iMarkerTP; unsigned short iSpan; int markerTP; - su2double densityIn, pressureIn, normalVelocityIn, tangVelocityIn, radialVelocityIn; - su2double densityOut, pressureOut, normalVelocityOut, tangVelocityOut, radialVelocityOut; + su2double densityIn, pressureIn, normalVelocityIn, tangVelocityIn, radialVelocityIn, relTangVelocityIn; + su2double densityOut, pressureOut, normalVelocityOut, tangVelocityOut, radialVelocityOut, relTangVelocityOut; su2double kineIn, omegaIn, nuIn, kineOut, omegaOut, nuOut; //TODO (turbo) implement interpolation so that Inflow and Outflow spanwise section can be different @@ -9622,8 +9739,8 @@ void CEulerSolver::GatherInOutAverageValues(CConfig *config, CGeometry *geometry su2double *TotTurbPerfIn = nullptr,*TotTurbPerfOut = nullptr; int *TotMarkerTP = nullptr; - n1 = 8; - n2 = 8; + n1 = 9; + n2 = 9; n1t = n1*size; n2t = n2*size; TurbPerfIn = new su2double[n1]; @@ -9640,11 +9757,13 @@ void CEulerSolver::GatherInOutAverageValues(CConfig *config, CGeometry *geometry normalVelocityIn = -1.0; tangVelocityIn = -1.0; radialVelocityIn = -1.0; + relTangVelocityIn = -1.0; densityOut = -1.0; pressureOut = -1.0; normalVelocityOut = -1.0; tangVelocityOut = -1.0; radialVelocityOut = -1.0; + relTangVelocityOut = -1.0; kineIn = -1.0; omegaIn = -1.0; nuIn = -1.0; @@ -9669,6 +9788,7 @@ void CEulerSolver::GatherInOutAverageValues(CConfig *config, CGeometry *geometry kineIn = KineIn[iMarkerTP -1][iSpan]; omegaIn = OmegaIn[iMarkerTP -1][iSpan]; nuIn = NuIn[iMarkerTP -1][iSpan]; + relTangVelocityIn = RelTangVelocityIn[iMarkerTP -1][iSpan]; #ifdef HAVE_MPI TurbPerfIn[0] = densityIn; @@ -9679,6 +9799,7 @@ void CEulerSolver::GatherInOutAverageValues(CConfig *config, CGeometry *geometry TurbPerfIn[5] = kineIn; TurbPerfIn[6] = omegaIn; TurbPerfIn[7] = nuIn; + TurbPerfIn[8] = relTangVelocityIn; #endif } @@ -9694,6 +9815,7 @@ void CEulerSolver::GatherInOutAverageValues(CConfig *config, CGeometry *geometry kineOut = KineOut[iMarkerTP -1][iSpan]; omegaOut = OmegaOut[iMarkerTP -1][iSpan]; nuOut = NuOut[iMarkerTP -1][iSpan]; + relTangVelocityOut = RelTangVelocityOut[iMarkerTP -1][iSpan]; #ifdef HAVE_MPI TurbPerfOut[0] = densityOut; @@ -9704,6 +9826,7 @@ void CEulerSolver::GatherInOutAverageValues(CConfig *config, CGeometry *geometry TurbPerfOut[5] = kineOut; TurbPerfOut[6] = omegaOut; TurbPerfOut[7] = nuOut; + TurbPerfOut[8] = relTangVelocityOut; #endif } } @@ -9733,26 +9856,28 @@ void CEulerSolver::GatherInOutAverageValues(CConfig *config, CGeometry *geometry if (rank == MASTER_NODE){ for (int i=0;i 0.0){ - densityIn = TotTurbPerfIn[n1*i]; - pressureIn = TotTurbPerfIn[n1*i+1]; - normalVelocityIn = TotTurbPerfIn[n1*i+2]; - tangVelocityIn = TotTurbPerfIn[n1*i+3]; - radialVelocityIn = TotTurbPerfIn[n1*i+4]; - kineIn = TotTurbPerfIn[n1*i+5]; - omegaIn = TotTurbPerfIn[n1*i+6]; - nuIn = TotTurbPerfIn[n1*i+7]; - markerTP = TotMarkerTP[i]; + densityIn = TotTurbPerfIn[n1*i]; + pressureIn = TotTurbPerfIn[n1*i+1]; + normalVelocityIn = TotTurbPerfIn[n1*i+2]; + tangVelocityIn = TotTurbPerfIn[n1*i+3]; + radialVelocityIn = TotTurbPerfIn[n1*i+4]; + kineIn = TotTurbPerfIn[n1*i+5]; + omegaIn = TotTurbPerfIn[n1*i+6]; + nuIn = TotTurbPerfIn[n1*i+7]; + relTangVelocityIn = TotTurbPerfIn[n1*i+8]; + markerTP = TotMarkerTP[i]; } if(TotTurbPerfOut[n2*i] > 0.0){ - densityOut = TotTurbPerfOut[n1*i]; - pressureOut = TotTurbPerfOut[n1*i+1]; - normalVelocityOut = TotTurbPerfOut[n1*i+2]; - tangVelocityOut = TotTurbPerfOut[n1*i+3]; - radialVelocityOut = TotTurbPerfOut[n1*i+4]; - kineOut = TotTurbPerfOut[n1*i+5]; - omegaOut = TotTurbPerfOut[n1*i+6]; - nuOut = TotTurbPerfOut[n1*i+7]; + densityOut = TotTurbPerfOut[n2*i]; + pressureOut = TotTurbPerfOut[n2*i+1]; + normalVelocityOut = TotTurbPerfOut[n2*i+2]; + tangVelocityOut = TotTurbPerfOut[n2*i+3]; + radialVelocityOut = TotTurbPerfOut[n2*i+4]; + kineOut = TotTurbPerfOut[n2*i+5]; + omegaOut = TotTurbPerfOut[n2*i+6]; + nuOut = TotTurbPerfOut[n2*i+7]; + relTangVelocityOut = TotTurbPerfOut[n2*i+8]; } } @@ -9772,6 +9897,7 @@ void CEulerSolver::GatherInOutAverageValues(CConfig *config, CGeometry *geometry KineIn[markerTP -1][iSpan] = kineIn; OmegaIn[markerTP -1][iSpan] = omegaIn; NuIn[markerTP -1][iSpan] = nuIn; + RelTangVelocityIn[markerTP -1][iSpan] = relTangVelocityIn; DensityOut[markerTP -1][iSpan] = densityOut; PressureOut[markerTP -1][iSpan] = pressureOut; @@ -9782,6 +9908,25 @@ void CEulerSolver::GatherInOutAverageValues(CConfig *config, CGeometry *geometry KineOut[markerTP -1][iSpan] = kineOut; OmegaOut[markerTP -1][iSpan] = omegaOut; NuOut[markerTP -1][iSpan] = nuOut; + RelTangVelocityOut[markerTP -1][iSpan] = relTangVelocityOut; } } } + +void CEulerSolver::ComputeTurboBladePerformance(CGeometry* geometry, CConfig* config, unsigned short iBlade) { + // Computes the turboperformance per blade in zone iBlade and stores the results in TurbomachineryPerformance. + const auto nDim = geometry->GetnDim(); + vector TurboPrimitiveIn, TurboPrimitiveOut; + if (rank == MASTER_NODE) { + std::vector bladePrimitives; + auto nSpan = config->GetnSpanWiseSections(); + for (auto iSpan = 0; iSpan < nSpan + 1; iSpan++) { + TurboPrimitiveIn = GetTurboPrimitive(iBlade, iSpan, true); + TurboPrimitiveOut = GetTurboPrimitive(iBlade, iSpan, false); + auto spanInletPrimitive = CTurbomachineryPrimitiveState(TurboPrimitiveIn, nDim, GetTangGridVelIn(iBlade, iSpan)); + auto spanOutletPrimitive = CTurbomachineryPrimitiveState(TurboPrimitiveOut, nDim, GetTangGridVelOut(iBlade, iSpan)); + bladePrimitives.push_back(CTurbomachineryCombinedPrimitiveStates(spanInletPrimitive, spanOutletPrimitive)); + } + TurbomachineryPerformance->ComputeTurbomachineryPerformance(bladePrimitives, iBlade); + } +} \ No newline at end of file diff --git a/SU2_CFD/src/solvers/CTurbSASolver.cpp b/SU2_CFD/src/solvers/CTurbSASolver.cpp index b58afe4c9b36..8e2b7fb3c619 100644 --- a/SU2_CFD/src/solvers/CTurbSASolver.cpp +++ b/SU2_CFD/src/solvers/CTurbSASolver.cpp @@ -1136,10 +1136,9 @@ void CTurbSASolver::BC_Inlet_MixingPlane(CGeometry *geometry, CSolver **solver_c const auto nSpanWiseSections = config->GetnSpanWiseSections(); /*--- Loop over all the vertices on this boundary marker ---*/ - for (auto iSpan = 0u; iSpan < nSpanWiseSections ; iSpan++){ - + for (auto iSpan = 0u; iSpan < nSpanWiseSections; iSpan++){ su2double extAverageNu[MAXNVAR] = {0.0}; - extAverageNu[0] = solver_container[FLOW_SOL]->GetExtAverageNu(val_marker, iSpan); + extAverageNu[0] = solver_container[FLOW_SOL]->GetMixingState(val_marker, iSpan, 5); /*--- Loop over all the vertices on this boundary marker ---*/ diff --git a/SU2_CFD/src/solvers/CTurbSSTSolver.cpp b/SU2_CFD/src/solvers/CTurbSSTSolver.cpp index 579b5f3cf72e..6bc32c814f72 100644 --- a/SU2_CFD/src/solvers/CTurbSSTSolver.cpp +++ b/SU2_CFD/src/solvers/CTurbSSTSolver.cpp @@ -833,8 +833,8 @@ void CTurbSSTSolver::BC_Inlet_MixingPlane(CGeometry *geometry, CSolver **solver_ for (auto iSpan = 0u; iSpan < nSpanWiseSections ; iSpan++){ - su2double extAverageKine = solver_container[FLOW_SOL]->GetExtAverageKine(val_marker, iSpan); - su2double extAverageOmega = solver_container[FLOW_SOL]->GetExtAverageOmega(val_marker, iSpan); + const auto extAverageKine = solver_container[FLOW_SOL]->GetMixingState(val_marker, iSpan, 6); + const auto extAverageOmega = solver_container[FLOW_SOL]->GetMixingState(val_marker, iSpan, 7); su2double solution_j[] = {extAverageKine, extAverageOmega}; /*--- Loop over all the vertices on this boundary marker ---*/ diff --git a/TestCases/cont_adj_euler/naca0012/inv_NACA0012_discadj_multizone.cfg b/TestCases/cont_adj_euler/naca0012/inv_NACA0012_discadj_multizone.cfg index ef79eb900523..9aee12469fa9 100644 --- a/TestCases/cont_adj_euler/naca0012/inv_NACA0012_discadj_multizone.cfg +++ b/TestCases/cont_adj_euler/naca0012/inv_NACA0012_discadj_multizone.cfg @@ -101,7 +101,6 @@ VOLUME_ADJ_FILENAME= adjoint GRAD_OBJFUNC_FILENAME= of_grad.dat SURFACE_FILENAME= surface_flow SURFACE_ADJ_FILENAME= surface_adjoint -OUTPUT_WRT_FREQ= 250 SCREEN_OUTPUT= (OUTER_ITER, BGS_RES[0]) OUTPUT_FILES=(RESTART_ASCII) diff --git a/TestCases/disc_adj_ffi/mixing_plane/circles.cfg b/TestCases/disc_adj_ffi/mixing_plane/circles.cfg new file mode 100755 index 000000000000..dd3f17e02897 --- /dev/null +++ b/TestCases/disc_adj_ffi/mixing_plane/circles.cfg @@ -0,0 +1,213 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: 2D Cylinder Interface (Mixing Plane) % +% Author: J. Kelly % +% Institution: University of Liverpool % +% Date: Dec 1st, 2025 % +% File Version 8.5.0 "Harrier" % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +MULTIZONE= YES +CONFIG_LIST= (zone_1.cfg, zone_2.cfg) +% +% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% +% +SOLVER= RANS +KIND_TURB_MODEL= SA +READ_BINARY_RESTART= NO +MATH_PROBLEM= DISCRETE_ADJOINT +% +% -------------------- COMPRESSIBLE FREE-STREAM DEFINITION --------------------% +% +MACH_NUMBER= 0.05 +AOA= 0.0 +FREESTREAM_PRESSURE= 1E6 +FREESTREAM_TEMPERATURE= 300.0 +FREESTREAM_DENSITY= 1.7418 +FREESTREAM_OPTION= TEMPERATURE_FS +FREESTREAM_TURBULENCEINTENSITY = 0.03 +FREESTREAM_TURB2LAMVISCRATIO = 100.0 +INIT_OPTION= TD_CONDITIONS +% +% ---------------------- REFERENCE VALUE DEFINITION ---------------------------% +% +REF_ORIGIN_MOMENT_X = 0.00 +REF_ORIGIN_MOMENT_Y = 0.00 +REF_ORIGIN_MOMENT_Z = 0.00 +REF_LENGTH= 1.0 +REF_AREA= 1.0 +REF_DIMENSIONALIZATION= DIMENSIONAL +% +% ------------------------------ EQUATION OF STATE ----------------------------% +% +FLUID_MODEL= IDEAL_GAS +GAMMA_VALUE= 1.4 +GAS_CONSTANT= 287.058 +% +% --------------------------- VISCOSITY MODEL ---------------------------------% +% +VISCOSITY_MODEL= CONSTANT_VISCOSITY +MU_REF= 1.716E-5 +MU_T_REF= 273.15 +SUTHERLAND_CONSTANT= 110.4 +% +% --------------------------- THERMAL CONDUCTIVITY MODEL ----------------------% +% +CONDUCTIVITY_MODEL= CONSTANT_PRANDTL +% +% -------------------- BOUNDARY CONDITION DEFINITION --------------------------% +% +%MARKER_EULER = (INFLOW, INMIX, OUTMIX, OUTFLOW, CIRC1, CIRC2) +MARKER_HEATFLUX= ( CIRC1, 0.0, CIRC2, 0.0) +% +MARKER_PERIODIC= ( PER1, PER2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, PER3, PER4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0) +% +%-------- INFLOW/OUTFLOW BOUNDARY CONDITION SPECIFIC FOR TURBOMACHINERY --------% +% +% Inflow and Outflow markers must be specified, for each blade (zone), following the natural groth of the machine (i.e, from the first blade to the last) +MARKER_TURBOMACHINERY= (INFLOW, OUTMIX, INMIX, OUTFLOW) +% +MARKER_ZONE_INTERFACE= (OUTMIX, INMIX) +% Mixing-plane interface markers must be specified to activate the transfer of information between zones +MARKER_MIXINGPLANE_INTERFACE= (OUTMIX, INMIX) +% +MARKER_GILES= (INFLOW, TOTAL_CONDITIONS_PT, 1E6, 300, 1.0, 0.0, 0.0,1.0,1.0, OUTMIX, MIXING_OUT, 0.0, 0.0, 0.0, 0.0, 0.0,1.0,1.0, INMIX, MIXING_IN, 0.0, 0.0, 0.0, 0.0, 0.0,1.0, 1.0, OUTFLOW, STATIC_PRESSURE, 9E5, 0.0, 0.0, 0.0, 0.0,1.0,1.0) +SPATIAL_FOURIER= YES +% +%---------------------------- TURBOMACHINERY SIMULATION -----------------------------% +% +TURBOMACHINERY_KIND= AXIAL AXIAL +TURBO_PERF_KIND = TURBINE TURBINE +TURBULENT_MIXINGPLANE= YES +AVERAGE_PROCESS_KIND= MIXEDOUT +PERFORMANCE_AVERAGE_PROCESS_KIND= MIXEDOUT +MIXEDOUT_COEFF= (1.0, 1.0E-05, 15) +AVERAGE_MACH_LIMIT= 0.05 +% +% ------------------------ SURFACES IDENTIFICATION ----------------------------% +% +MARKER_PLOTTING= CIRC1, CIRC2 +MARKER_MONITORING= CIRC1, CIRC2 +MARKER_DESIGNING= CIRC1, CIRC2 +MARKER_ANALYZE= CIRC1, CIRC2 +% +% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% +% +NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES +CFL_NUMBER= 10.0 +CFL_ADAPT= NO +CFL_ADAPT_PARAM= ( 1.3, 1.2, 1.0, 10.0) +% +% ------------------------ LINEAR SOLVER DEFINITION ---------------------------% +% +LINEAR_SOLVER= FGMRES +LINEAR_SOLVER_PREC= LU_SGS +LINEAR_SOLVER_ERROR= 1E-4 +LINEAR_SOLVER_ITER= 20 +% +% ----------------------- SLOPE LIMITER DEFINITION ----------------------------% +% +VENKAT_LIMITER_COEFF= 0.05 +LIMITER_ITER= 999999 +% +% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% +% +CONV_NUM_METHOD_FLOW= ROE +MUSCL_FLOW= NO +SLOPE_LIMITER_FLOW= VAN_ALBADA_EDGE +ENTROPY_FIX_COEFF= 0.1 +JST_SENSOR_COEFF= ( 0.5, 0.02 ) +TIME_DISCRE_FLOW= EULER_IMPLICIT +% +% -------------------- TURBULENT NUMERICAL METHOD DEFINITION ------------------% +% +CONV_NUM_METHOD_TURB= SCALAR_UPWIND +MUSCL_TURB= NO +SLOPE_LIMITER_TURB= VENKATAKRISHNAN +TIME_DISCRE_TURB= EULER_IMPLICIT +CFL_REDUCTION_TURB= 1.0 +% +% ----------------------- DESIGN VARIABLE PARAMETERS --------------------------% +% +DV_KIND= FFD_CONTROL_POINT_2D, FFD_CONTROL_POINT_2D, FFD_CONTROL_POINT_2D, FFD_CONTROL_POINT_2D +DV_MARKER= ( CIRC1, CIRC2 ) +DV_PARAM=( CIRCLE1, 0, 0, 1.0, 0.0 );( CIRCLE2, 0, 0, 1.0, 0.0 );( CIRCLE1, 1, 1, 1.0, 0.0 );( CIRCLE2, 1, 1, 1.0, 0.0 ) +DV_VALUE=0.0,0.0,0.0,0.0 +% +% ------------------------ GRID DEFORMATION PARAMETERS ------------------------% +% +% Linear solver or smoother for implicit formulations (FGMRES, RESTARTED_FGMRES, BCGSTAB) +DEFORM_LINEAR_SOLVER= FGMRES +% +% Preconditioner of the Krylov linear solver (ILU, LU_SGS, JACOBI) +DEFORM_LINEAR_SOLVER_PREC= ILU +% +% Number of smoothing iterations for mesh deformation +DEFORM_LINEAR_SOLVER_ITER= 1000 +% +% Number of nonlinear deformation iterations (surface deformation increments) +DEFORM_NONLINEAR_ITER= 1 +% +% Minimum residual criteria for the linear solver convergence of grid deformation +DEFORM_LINEAR_SOLVER_ERROR= 1E-14 +% +% Print the residuals during mesh deformation to the console (YES, NO) +DEFORM_CONSOLE_OUTPUT= YES +% +% Type of element stiffness imposed for FEA mesh deformation (INVERSE_VOLUME, +% WALL_DISTANCE, CONSTANT_STIFFNESS) +DEFORM_STIFFNESS_TYPE= WALL_DISTANCE +% +% -------------------- FREE-FORM DEFORMATION PARAMETERS -----------------------% +% +FFD_TOLERANCE= 1E-10 +FFD_ITERATIONS= 500 +FFD_DEFINITION= (CIRCLE1, -2.0, -2.0, 0.0, 2.0, -2.0, 0.0, 2.0, 2.0, 0.0, -2.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); (CIRCLE2, 18.0, -2.0, 0.0, 22.0, -2.0, 0.0, 22.0, 2.0, 0.0, 18.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +FFD_DEGREE= ( 2 , 2 , 0 ); (2, 2, 0) +FFD_CONTINUITY= 2ND_DERIVATIVE +% +% --------------------- OPTIMAL SHAPE DESIGN DEFINITION -----------------------% +% +% FFD_CONTROL_POINT_2D +DEFINITION_DV= ( 19, 1.0 | CIRC1 | CIRCLE1, 0, 0, 1.0, 0.0 ); ( 19, 1.0 | CIRC2 | CIRCLE2, 0, 0, 1.0, 0.0 ); ( 19, 1.0 | CIRC1 | CIRCLE1, 1, 1, 1.0, 0.0 ); ( 19, 1.0 | CIRC2 | CIRCLE2, 1, 1, 1.0, 0.0 ); +OPT_OBJECTIVE= ENTROPY_GENERATION* 0.0001 +OPT_ITERATIONS= 19 +OPT_ACCURACY= 1E-10 +% +% --------------------- OBJECTIVE FUNCTION DEFINITION -----------------------% +% +OBJECTIVE_FUNCTION= ENTROPY_GENERATION +% +% ------------------------- CONVERGENCE PARAMETERS --------------------------% +% +OUTER_ITER= 21 +CONV_RESIDUAL_MINVAL= -16 +CONV_STARTITER= 10 +CONV_CAUCHY_ELEMS= 100 +CONV_CAUCHY_EPS= 1E-6 +% +% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% +% +SCREEN_OUTPUT= OUTER_ITER, AVG_BGS_RES[0], AVG_BGS_RES[1] +HISTORY_OUTPUT= ITER, RMS_RES +MESH_FILENAME= circ_out.su2 +MESH_FORMAT= SU2 +MESH_OUT_FILENAME= mesh_out.su2 +SOLUTION_FILENAME= solution_flow +SOLUTION_ADJ_FILENAME= solution_adj +TABULAR_FORMAT= CSV +OUTPUT_FILES= RESTART_ASCII, TECPLOT, SURFACE_TECPLOT +CONV_FILENAME= history +RESTART_FILENAME= restart_flow +RESTART_ADJ_FILENAME= restart_adj +VOLUME_FILENAME= flow +VOLUME_ADJ_FILENAME= adjoint +GRAD_OBJFUNC_FILENAME= of_grad.dat +SURFACE_FILENAME= surface_flow +SURFACE_ADJ_FILENAME= surface_adjoint +SURFACE_SENS_FILENAME= surface_sens +WRT_ZONE_CONV= NO +WRT_ZONE_HIST= YES +OUTPUT_PRECISION= 16 diff --git a/TestCases/disc_adj_ffi/mixing_plane/zone_1.cfg b/TestCases/disc_adj_ffi/mixing_plane/zone_1.cfg new file mode 100644 index 000000000000..88f3b9585247 --- /dev/null +++ b/TestCases/disc_adj_ffi/mixing_plane/zone_1.cfg @@ -0,0 +1,16 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: 2D Cylinder Interface (Mixing Plane) % +% Author: J. Kelly % +% Institution: University of Liverpool % +% Date: Dec 1st, 2025 % +% File Version 8.5.0 "Harrier" % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% ----------------------- DYNAMIC MESH DEFINITION -----------------------------% +% +GRID_MOVEMENT= STEADY_TRANSLATION +MACH_MOTION= 0.35 +TRANSLATION_RATE= 0.0 0.0 0.0 diff --git a/TestCases/disc_adj_ffi/mixing_plane/zone_2.cfg b/TestCases/disc_adj_ffi/mixing_plane/zone_2.cfg new file mode 100644 index 000000000000..88f3b9585247 --- /dev/null +++ b/TestCases/disc_adj_ffi/mixing_plane/zone_2.cfg @@ -0,0 +1,16 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: 2D Cylinder Interface (Mixing Plane) % +% Author: J. Kelly % +% Institution: University of Liverpool % +% Date: Dec 1st, 2025 % +% File Version 8.5.0 "Harrier" % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% ----------------------- DYNAMIC MESH DEFINITION -----------------------------% +% +GRID_MOVEMENT= STEADY_TRANSLATION +MACH_MOTION= 0.35 +TRANSLATION_RATE= 0.0 0.0 0.0 diff --git a/TestCases/disc_adj_ffi/sliding_interface/circles.cfg b/TestCases/disc_adj_ffi/sliding_interface/circles.cfg new file mode 100755 index 000000000000..d3557dca679f --- /dev/null +++ b/TestCases/disc_adj_ffi/sliding_interface/circles.cfg @@ -0,0 +1,200 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: 2D Cylinder Interface (Sliding Interface) % +% Author: J. Kelly % +% Institution: University of Liverpool % +% Date: Dec 1st, 2025 % +% File Version 8.5.0 "Harrier" % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +MULTIZONE= YES +% +% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% +% +SOLVER= RANS +KIND_TURB_MODEL= SA +READ_BINARY_RESTART=NO +MATH_PROBLEM= DISCRETE_ADJOINT +% +% -------------------- COMPRESSIBLE FREE-STREAM DEFINITION --------------------% +% +MACH_NUMBER= 0.05 +AOA= 0.0 +FREESTREAM_PRESSURE= 1E6 +FREESTREAM_TEMPERATURE= 300.0 +FREESTREAM_DENSITY= 1.7418 +FREESTREAM_OPTION= TEMPERATURE_FS +FREESTREAM_TURBULENCEINTENSITY = 0.03 +FREESTREAM_TURB2LAMVISCRATIO = 100.0 +INIT_OPTION= TD_CONDITIONS +% +% ---------------------- REFERENCE VALUE DEFINITION ---------------------------% +% +REF_ORIGIN_MOMENT_X = 0.00 +REF_ORIGIN_MOMENT_Y = 0.00 +REF_ORIGIN_MOMENT_Z = 0.00 +REF_LENGTH= 1.0 +REF_AREA= 1.0 +REF_DIMENSIONALIZATION= DIMENSIONAL +% +% ------------------------------ EQUATION OF STATE ----------------------------% +% +FLUID_MODEL= IDEAL_GAS +GAMMA_VALUE= 1.4 +GAS_CONSTANT= 287.058 +% +% --------------------------- VISCOSITY MODEL ---------------------------------% +% +VISCOSITY_MODEL= CONSTANT_VISCOSITY +MU_REF= 1.716E-5 +MU_T_REF= 273.15 +SUTHERLAND_CONSTANT= 110.4 +%FROZEN_VISC_DISC= YES +% +% --------------------------- THERMAL CONDUCTIVITY MODEL ----------------------% +% +CONDUCTIVITY_MODEL= CONSTANT_PRANDTL +% +% -------------------- BOUNDARY CONDITION DEFINITION --------------------------% +% +MARKER_HEATFLUX= ( CIRC1, 0.0, CIRC2, 0.0) +% +MARKER_PERIODIC= ( PER1, PER2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, PER3, PER4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0) +% +%-------- INFLOW/OUTFLOW BOUNDARY CONDITION SPECIFIC FOR TURBOMACHINERY --------% +% +MARKER_ZONE_INTERFACE= (OUTMIX, INMIX) +% Mixing-plane interface markers must be specified to activate the transfer of information between zones +MARKER_FLUID_INTERFACE= (OUTMIX, INMIX) +% +INLET_TYPE= TOTAL_CONDITIONS +MARKER_INLET= (INFLOW, 300.0, 1E6, 1.0, 0.0, 0.0) +MARKER_OUTLET= (OUTFLOW, 9E5) +% +% ------------------------ SURFACES IDENTIFICATION ----------------------------% +% +MARKER_PLOTTING= CIRC1, CIRC2 +MARKER_MONITORING= CIRC1, CIRC2 +MARKER_DESIGNING= CIRC1, CIRC2 +MARKER_ANALYZE= CIRC1, CIRC2 +% +% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% +% +NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES +CFL_NUMBER= 10.0 +CFL_ADAPT= NO +CFL_ADAPT_PARAM= ( 1.3, 1.2, 1.0, 10.0) +% +% ------------------------ LINEAR SOLVER DEFINITION ---------------------------% +% +LINEAR_SOLVER= FGMRES +LINEAR_SOLVER_PREC= LU_SGS +LINEAR_SOLVER_ERROR= 1E-4 +LINEAR_SOLVER_ITER= 20 +% +% ----------------------- SLOPE LIMITER DEFINITION ----------------------------% +% +VENKAT_LIMITER_COEFF= 0.05 +LIMITER_ITER= 999999 +% +% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% +% +CONV_NUM_METHOD_FLOW= ROE +MUSCL_FLOW= NO +SLOPE_LIMITER_FLOW= VAN_ALBADA_EDGE +ENTROPY_FIX_COEFF= 0.1 +JST_SENSOR_COEFF= ( 0.5, 0.02 ) +TIME_DISCRE_FLOW= EULER_IMPLICIT +% +% -------------------- TURBULENT NUMERICAL METHOD DEFINITION ------------------% +% +CONV_NUM_METHOD_TURB= SCALAR_UPWIND +MUSCL_TURB= NO +SLOPE_LIMITER_TURB= VENKATAKRISHNAN +TIME_DISCRE_TURB= EULER_IMPLICIT +CFL_REDUCTION_TURB= 1.0 +% +% ----------------------- DESIGN VARIABLE PARAMETERS --------------------------% +% +DV_KIND= FFD_CONTROL_POINT_2D, FFD_CONTROL_POINT_2D, FFD_CONTROL_POINT_2D, FFD_CONTROL_POINT_2D +DV_MARKER= ( CIRC1, CIRC2 ) +DV_PARAM=( CIRCLE1, 0, 0, 1.0, 0.0 );( CIRCLE2, 0, 0, 1.0, 0.0 );( CIRCLE1, 1, 1, 1.0, 0.0 );( CIRCLE2, 1, 1, 1.0, 0.0 ) +DV_VALUE=0.0,0.0,0.0,0.0 +% +% ------------------------ GRID DEFORMATION PARAMETERS ------------------------% +% +% Linear solver or smoother for implicit formulations (FGMRES, RESTARTED_FGMRES, BCGSTAB) +DEFORM_LINEAR_SOLVER= FGMRES +% +% Preconditioner of the Krylov linear solver (ILU, LU_SGS, JACOBI) +DEFORM_LINEAR_SOLVER_PREC= ILU +% +% Number of smoothing iterations for mesh deformation +DEFORM_LINEAR_SOLVER_ITER= 1000 +% +% Number of nonlinear deformation iterations (surface deformation increments) +DEFORM_NONLINEAR_ITER= 1 +% +% Minimum residual criteria for the linear solver convergence of grid deformation +DEFORM_LINEAR_SOLVER_ERROR= 1E-14 +% +% Print the residuals during mesh deformation to the console (YES, NO) +DEFORM_CONSOLE_OUTPUT= YES +% +% Type of element stiffness imposed for FEA mesh deformation (INVERSE_VOLUME, +% WALL_DISTANCE, CONSTANT_STIFFNESS) +DEFORM_STIFFNESS_TYPE= INVERSE_VOLUME +% +% -------------------- FREE-FORM DEFORMATION PARAMETERS -----------------------% +% +FFD_TOLERANCE= 1E-10 +FFD_ITERATIONS= 500 +FFD_DEFINITION= (CIRCLE1, -2.0, -2.0, 0.0, 2.0, -2.0, 0.0, 2.0, 2.0, 0.0, -2.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); (CIRCLE2, 18.0, -2.0, 0.0, 22.0, -2.0, 0.0, 22.0, 2.0, 0.0, 18.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +FFD_DEGREE= ( 2 , 2 , 0 ); (2, 2, 0) +FFD_CONTINUITY= 2ND_DERIVATIVE +% +% --------------------- OPTIMAL SHAPE DESIGN DEFINITION -----------------------% +% +% FFD_CONTROL_POINT_2D +DEFINITION_DV= ( 19, 1.0 | CIRC1 | CIRCLE1, 0, 0, 1.0, 0.0 ); ( 19, 1.0 | CIRC2 | CIRCLE2, 0, 0, 1.0, 0.0 ); ( 19, 1.0 | CIRC1 | CIRCLE1, 1, 1, 1.0, 0.0 ); ( 19, 1.0 | CIRC2 | CIRCLE2, 1, 1, 1.0, 0.0 ); +OPT_OBJECTIVE= ENTROPY_GENERATION* 0.0001 +OPT_ITERATIONS= 19 +OPT_ACCURACY= 1E-10 +% +% --------------------- OBJECTIVE FUNCTION DEFINITION -----------------------% +% +OBJECTIVE_FUNCTION= DRAG +% +% ------------------------- CONVERGENCE PARAMETERS --------------------------% +% +OUTER_ITER= 21 +CONV_RESIDUAL_MINVAL= -16 +CONV_STARTITER= 10 +CONV_CAUCHY_ELEMS= 100 +CONV_CAUCHY_EPS= 1E-6 +% +% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% +% +SCREEN_OUTPUT= OUTER_ITER, AVG_BGS_RES[0], AVG_BGS_RES[1] +HISTORY_OUTPUT= ITER, RMS_RES +MESH_FILENAME= circle_rans_ffd.su2 +MESH_FORMAT= SU2 +MESH_OUT_FILENAME= mesh_out.su2 +SOLUTION_FILENAME= solution_flow +SOLUTION_ADJ_FILENAME= solution_adj +TABULAR_FORMAT= CSV +OUTPUT_FILES= RESTART_ASCII, TECPLOT, SURFACE_TECPLOT +CONV_FILENAME= history +RESTART_FILENAME= restart_flow +RESTART_ADJ_FILENAME= restart_adj +VOLUME_FILENAME= flow +VOLUME_ADJ_FILENAME= adjoint +GRAD_OBJFUNC_FILENAME= of_grad.dat +SURFACE_FILENAME= surface_flow +SURFACE_ADJ_FILENAME= surface_adjoint +SURFACE_SENS_FILENAME= surface_sens +WRT_ZONE_CONV= NO +WRT_ZONE_HIST= YES +OUTPUT_PRECISION= 16 diff --git a/TestCases/disc_adj_fsi/dyn_fsi/grad_dv.opt.ref b/TestCases/disc_adj_fsi/dyn_fsi/grad_dv.opt.ref index aeefa8f67249..1f0241b9e2fa 100644 --- a/TestCases/disc_adj_fsi/dyn_fsi/grad_dv.opt.ref +++ b/TestCases/disc_adj_fsi/dyn_fsi/grad_dv.opt.ref @@ -1,9 +1,9 @@ INDEX GRAD -0 -4.570880237875418e-04 -1 -2.401474145411196e-04 -2 -9.134741927897237e-05 -3 -1.628103641919142e-05 -4 -1.741046901352254e-05 -5 -9.462481564764888e-05 -6 -2.452469723327466e-04 -7 -4.635498231799566e-04 +0 -2.998512907701271e-03 +1 -1.584866997023666e-03 +2 -6.647075933794050e-04 +3 -2.112197065714601e-04 +4 -2.145348429280959e-04 +5 -6.735292498556252e-04 +6 -1.595607953604883e-03 +7 -3.004453684429480e-03 diff --git a/TestCases/disc_adj_fsi/dyn_fsi/grad_dv_aarch64.opt.ref b/TestCases/disc_adj_fsi/dyn_fsi/grad_dv_aarch64.opt.ref index 352ddda64058..1f0241b9e2fa 100644 --- a/TestCases/disc_adj_fsi/dyn_fsi/grad_dv_aarch64.opt.ref +++ b/TestCases/disc_adj_fsi/dyn_fsi/grad_dv_aarch64.opt.ref @@ -1,9 +1,9 @@ INDEX GRAD -0 -4.570869211378251e-04 -1 -2.401466735452708e-04 -2 -9.134698210593547e-05 -3 -1.628086946841527e-05 -4 -1.741052543353507e-05 -5 -9.462490180375868e-05 -6 -2.452466338580702e-04 -7 -4.635483760290384e-04 +0 -2.998512907701271e-03 +1 -1.584866997023666e-03 +2 -6.647075933794050e-04 +3 -2.112197065714601e-04 +4 -2.145348429280959e-04 +5 -6.735292498556252e-04 +6 -1.595607953604883e-03 +7 -3.004453684429480e-03 diff --git a/TestCases/disc_adj_turbomachinery/axial_stage_2D/Axial_stage2D.cfg b/TestCases/disc_adj_turbomachinery/axial_stage_2D/Axial_stage2D.cfg new file mode 100755 index 000000000000..ecb25309f668 --- /dev/null +++ b/TestCases/disc_adj_turbomachinery/axial_stage_2D/Axial_stage2D.cfg @@ -0,0 +1,188 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: 2D Discrete Adjoint Axial stage % +% Author: J. Kelly % +% Institution: University of Liverpool. % +% Date: Oct 11th, 2025 % +% File Version 8.4.0 "Harrier" % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +MULTIZONE= YES +CONFIG_LIST= (zone_1.cfg, zone_2.cfg) +% +% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% +% +SOLVER= RANS +KIND_TURB_MODEL= SST +READ_BINARY_RESTART=NO +RESTART_SOL= YES +% +% -------------------- COMPRESSIBLE FREE-STREAM DEFINITION --------------------% +% +MACH_NUMBER= 0.05 +AOA= 0.0 +FREESTREAM_PRESSURE= 140000.0 +FREESTREAM_TEMPERATURE= 300.0 +FREESTREAM_DENSITY= 1.7418 +FREESTREAM_OPTION= TEMPERATURE_FS +FREESTREAM_TURBULENCEINTENSITY = 0.03 +FREESTREAM_TURB2LAMVISCRATIO = 100.0 +INIT_OPTION= TD_CONDITIONS +% +% ---------------------- REFERENCE VALUE DEFINITION ---------------------------% +% +REF_ORIGIN_MOMENT_X = 0.00 +REF_ORIGIN_MOMENT_Y = 0.00 +REF_ORIGIN_MOMENT_Z = 0.00 +REF_LENGTH= 1.0 +REF_AREA= 1.0 +REF_DIMENSIONALIZATION= DIMENSIONAL +% +% ------------------------------ EQUATION OF STATE ----------------------------% +% +FLUID_MODEL= IDEAL_GAS +GAMMA_VALUE= 1.4 +GAS_CONSTANT= 287.058 +% +% --------------------------- VISCOSITY MODEL ---------------------------------% +% +VISCOSITY_MODEL= CONSTANT_VISCOSITY +MU_REF= 1.716E-5 +MU_T_REF= 273.15 +SUTHERLAND_CONSTANT= 110.4 +% +% --------------------------- THERMAL CONDUCTIVITY MODEL ----------------------% +% +CONDUCTIVITY_MODEL= CONSTANT_PRANDTL +% +% -------------------- BOUNDARY CONDITION DEFINITION --------------------------% +% +MARKER_HEATFLUX= ( wall1, 0.0, wall2, 0.0) +MARKER_PERIODIC= ( periodic1, periodic2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.04463756775, 0.0, periodic3, periodic4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.04463756775, 0.0) +% +%-------- INFLOW/OUTFLOW BOUNDARY CONDITION SPECIFIC FOR TURBOMACHINERY --------% +% +MARKER_TURBOMACHINERY= (inflow, outmix, inmix, outflow) +MARKER_ZONE_INTERFACE= (outmix, inmix) +MARKER_MIXINGPLANE_INTERFACE= (outmix, inmix) +MARKER_GILES= (inflow, TOTAL_CONDITIONS_PT, 169623.33, 305.76, 1.0, 0.0, 0.0,1.0,1.0, outmix, MIXING_OUT, 0.0, 0.0, 0.0, 0.0, 0.0,1.0,1.0, inmix, MIXING_IN, 0.0, 0.0, 0.0, 0.0, 0.0,1.0, 1.0 outflow, STATIC_PRESSURE, 99741.00, 0.0, 0.0, 0.0, 0.0,1.0,1.0) +SPATIAL_FOURIER= YES +% +%---------------------------- TURBOMACHINERY SIMULATION -----------------------------% +% +TURBOMACHINERY_KIND= AXIAL AXIAL +TURBO_PERF_KIND = TURBINE TURBINE +TURBULENT_MIXINGPLANE= YES +RAMP_OUTLET= NO +RAMP_OUTLET_COEFF= (140000.0, 10.0, 2000) +AVERAGE_PROCESS_KIND= MIXEDOUT +PERFORMANCE_AVERAGE_PROCESS_KIND= MIXEDOUT +MIXEDOUT_COEFF= (1.0, 1.0E-05, 15) +AVERAGE_MACH_LIMIT= 0.05 +% +% ------------------------ SURFACES IDENTIFICATION ----------------------------% +% +MARKER_PLOTTING= wall1, wall2 +MARKER_MONITORING= wall1, wall2 +MARKER_DESIGNING= wall1, wall2 +MARKER_ANALYZE= wall1, wall2 +% +% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% +% +NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES +CFL_NUMBER= 10.0 +CFL_ADAPT= NO +CFL_ADAPT_PARAM= ( 1.3, 1.2, 1.0, 10.0) +% +% ------------------------ LINEAR SOLVER DEFINITION ---------------------------% +% +LINEAR_SOLVER= FGMRES +LINEAR_SOLVER_PREC= LU_SGS +LINEAR_SOLVER_ERROR= 1E-4 +LINEAR_SOLVER_ITER= 20 +% +% ----------------------- SLOPE LIMITER DEFINITION ----------------------------% +% +VENKAT_LIMITER_COEFF= 0.05 +LIMITER_ITER= 999999 +% +% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% +% +CONV_NUM_METHOD_FLOW= ROE +MUSCL_FLOW= YES +SLOPE_LIMITER_FLOW= VAN_ALBADA_EDGE +ENTROPY_FIX_COEFF= 0.1 +JST_SENSOR_COEFF= ( 0.5, 0.02 ) +TIME_DISCRE_FLOW= EULER_IMPLICIT +% +% -------------------- TURBULENT NUMERICAL METHOD DEFINITION ------------------% +% +CONV_NUM_METHOD_TURB= SCALAR_UPWIND +MUSCL_TURB= NO +SLOPE_LIMITER_TURB= VENKATAKRISHNAN +TIME_DISCRE_TURB= EULER_IMPLICIT +CFL_REDUCTION_TURB= 1.0 +% +% ----------------------- DESIGN VARIABLE PARAMETERS --------------------------% +% +DV_KIND= FFD_CONTROL_POINT_2D, FFD_CONTROL_POINT_2D +DV_MARKER= ( wall1, wall2 ) +DV_PARAM=( STATOR, 0, 0, 1.0, 0.0 ); ( ROTOR, 2, 2, 1.0, 0.0 ) +DV_VALUE=0.0,0.0 +DEFINITION_DV= ( 19, 1.0 | wall1 | STATOR, 0, 0, 1.0, 0.0 ); ( 19, 1.0 | wall2 | ROTOR, 2, 2, 1.0, 0.0 ); +% +% ------------------------ GRID DEFORMATION PARAMETERS ------------------------% +% +DEFORM_LINEAR_SOLVER= FGMRES +DEFORM_LINEAR_SOLVER_PREC= ILU +DEFORM_LINEAR_SOLVER_ITER= 1000 +DEFORM_NONLINEAR_ITER= 1 +DEFORM_LINEAR_SOLVER_ERROR= 1E-14 +DEFORM_CONSOLE_OUTPUT= YES +DEFORM_STIFFNESS_TYPE= INVERSE_VOLUME +% +% -------------------- FREE-FORM DEFORMATION PARAMETERS -----------------------% +% +FFD_TOLERANCE= 1E-10 +FFD_ITERATIONS= 500 +FFD_DEFINITION= (STATOR, 0.139, -0.0038, 0.0, 0.186, -0.0567, 0.0, 0.193, -0.031, 0.0, 0.156, 0.010, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); (ROTOR, 0.200, -0.040, 0.0, 0.259, -0.001, 0.0, 0.259, 0.032, 0.0, 0.200, -0.007, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) +FFD_DEGREE= ( 2 , 2 , 0 ); (2, 2, 0) +FFD_CONTINUITY= 2ND_DERIVATIVE +% +% --------------------- OBJECTIVE FUNCTION DEFINITION -----------------------% +% +OBJECTIVE_FUNCTION= ENTROPY_GENERATION +% +% ------------------------- CONVERGENCE PARAMETERS --------------------------% +% +OUTER_ITER= 101 +CONV_RESIDUAL_MINVAL= -16 +CONV_STARTITER= 10 +CONV_CAUCHY_ELEMS= 100 +CONV_CAUCHY_EPS= 1E-6 +% +% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% +% +SCREEN_OUTPUT= OUTER_ITER, AVG_BGS_RES[0], AVG_BGS_RES[1] +HISTORY_OUTPUT= COMBO +MESH_FILENAME= axial_stage_2D.su2 +MESH_FORMAT= SU2 +MESH_OUT_FILENAME= mesh_out.su2 +SOLUTION_FILENAME= solution_flow +SOLUTION_ADJ_FILENAME= solution_adj +TABULAR_FORMAT= CSV +OUTPUT_FILES= RESTART_ASCII, TECPLOT, SURFACE_TECPLOT +CONV_FILENAME= history +RESTART_FILENAME= restart_flow +RESTART_ADJ_FILENAME= restart_adj +VOLUME_FILENAME= flow +VOLUME_ADJ_FILENAME= adjoint +GRAD_OBJFUNC_FILENAME= of_grad.dat +SURFACE_FILENAME= surface_flow +SURFACE_ADJ_FILENAME= surface_adjoint +SURFACE_SENS_FILENAME= surface_sens +WRT_ZONE_CONV= NO +WRT_ZONE_HIST= YES +OUTPUT_PRECISION= 16 diff --git a/TestCases/disc_adj_turbomachinery/axial_stage_2D/zone_1.cfg b/TestCases/disc_adj_turbomachinery/axial_stage_2D/zone_1.cfg new file mode 100644 index 000000000000..c2ec5ed2cc90 --- /dev/null +++ b/TestCases/disc_adj_turbomachinery/axial_stage_2D/zone_1.cfg @@ -0,0 +1,15 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: 2D Axial stage % +% Author: S. Vitale % +% Institution: Delft University of Technology % +% Date: Feb 28th, 2017 % +% File Version 8.0.0 "Harrier" % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% ----------------------- DYNAMIC MESH DEFINITION -----------------------------% +% +GRID_MOVEMENT= NONE + diff --git a/TestCases/disc_adj_turbomachinery/axial_stage_2D/zone_2.cfg b/TestCases/disc_adj_turbomachinery/axial_stage_2D/zone_2.cfg new file mode 100644 index 000000000000..2fe06969028a --- /dev/null +++ b/TestCases/disc_adj_turbomachinery/axial_stage_2D/zone_2.cfg @@ -0,0 +1,16 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: 2D Axial stage % +% Author: S. Vitale % +% Institution: Delft University of Technology % +% Date: Feb 28th, 2017 % +% File Version 8.0.0 "Harrier" % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% ----------------------- DYNAMIC MESH DEFINITION -----------------------------% +% +GRID_MOVEMENT= STEADY_TRANSLATION +MACH_MOTION= 0.35 +TRANSLATION_RATE= 0.0 -150.0 0.0 diff --git a/TestCases/disc_adj_turbomachinery/transonic_stator_2D/transonic_stator.cfg b/TestCases/disc_adj_turbomachinery/transonic_stator_2D/transonic_stator.cfg index fe7c86b29425..f76665962fd3 100644 --- a/TestCases/disc_adj_turbomachinery/transonic_stator_2D/transonic_stator.cfg +++ b/TestCases/disc_adj_turbomachinery/transonic_stator_2D/transonic_stator.cfg @@ -8,16 +8,13 @@ % File Version 8.5.0 "Harrier" % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - % ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% % SOLVER= RANS KIND_TURB_MODEL= SST -SST_OPTIONS= V1994m -RESTART_SOL= NO +RESTART_SOL= YES NZONES= 1 - +% % -------------------- COMPRESSIBLE FREE-STREAM DEFINITION --------------------% % MACH_NUMBER= 0.1 @@ -37,8 +34,8 @@ REF_ORIGIN_MOMENT_Y = 0.00 REF_ORIGIN_MOMENT_Z = 0.00 REF_LENGTH= 1.0 REF_AREA= 1.0 -REF_DIMENSIONALIZATION= FREESTREAM_PRESS_EQ_ONE - +REF_DIMENSIONALIZATION= DIMENSIONAL +% % ------------------------------ EQUATION OF STATE ----------------------------% % FLUID_MODEL= PR_GAS @@ -47,7 +44,7 @@ GAS_CONSTANT= 35.23 CRITICAL_TEMPERATURE= 564.1 CRITICAL_PRESSURE= 1415000.0 ACENTRIC_FACTOR= 0.529 - +% % --------------------------- VISCOSITY MODEL ---------------------------------% % VISCOSITY_MODEL= CONSTANT_VISCOSITY @@ -55,23 +52,23 @@ MU_CONSTANT= 1.3764E-5 MU_REF= 1.716E-5 MU_T_REF= 273.15 SUTHERLAND_CONSTANT= 110.4 - +% % --------------------------- THERMAL CONDUCTIVITY MODEL ----------------------% % CONDUCTIVITY_MODEL= CONSTANT_CONDUCTIVITY THERMAL_CONDUCTIVITY_CONSTANT= 0.047280 - +% % -------------------- BOUNDARY CONDITION DEFINITION --------------------------% % MARKER_HEATFLUX= (airfoil, 0.0) MARKER_PERIODIC= ( periodic_1, periodic_2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.05749995, 0.0 ) - +% %-------- INFLOW/OUTFLOW BOUNDARY CONDITION SPECIFIC FOR TURBOMACHINERY --------% % MARKER_TURBOMACHINERY= (inflow, outflow) MARKER_GILES= (inflow, TOTAL_CONDITIONS_PT, 13.8686E+05, 592.295, 1.0, 0.0, 0.0, 1.0, 1.0, outflow, STATIC_PRESSURE, 9.00E+05, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0) SPATIAL_FOURIER= YES - +% %---------------------------- TURBOMACHINERY SIMULATION -----------------------------% % TURBOMACHINERY_KIND = AXIAL @@ -82,51 +79,41 @@ AVERAGE_PROCESS_KIND= MIXEDOUT PERFORMANCE_AVERAGE_PROCESS_KIND= MIXEDOUT MIXEDOUT_COEFF= (1.0, 1.0E-05, 15) AVERAGE_MACH_LIMIT= 0.03 - +% % ------------------------ SURFACES IDENTIFICATION ----------------------------% % MARKER_PLOTTING= (airfoil) MARKER_MONITORING= (airfoil) MARKER_ANALYZE= (outflow, inflow) - +% % ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% % NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES CFL_NUMBER= 50.0 CFL_ADAPT= NO CFL_ADAPT_PARAM= ( 1.3, 1.2, 1.0, 10.0) - +% % ------------------------ LINEAR SOLVER DEFINITION ---------------------------% % LINEAR_SOLVER= FGMRES LINEAR_SOLVER_PREC= LU_SGS -LINEAR_SOLVER_ERROR= 1E-1 -LINEAR_SOLVER_ITER= 3 - -% -------------------------- MULTIGRID PARAMETERS -----------------------------% -% -MGLEVEL= 1 -MGCYCLE= V_CYCLE -MG_PRE_SMOOTH= ( 4, 4, 4, 4 ) -MG_POST_SMOOTH= ( 4, 4, 4, 4 ) -MG_CORRECTION_SMOOTH= ( 1, 1, 1, 1 ) -MG_DAMP_RESTRICTION= 0.5 -MG_DAMP_PROLONGATION= 0.5 - +LINEAR_SOLVER_ERROR= 1E-4 +LINEAR_SOLVER_ITER= 5 +% % ----------------------- SLOPE LIMITER DEFINITION ----------------------------% % VENKAT_LIMITER_COEFF= 0.05 LIMITER_ITER= 999999 - +% % -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% % CONV_NUM_METHOD_FLOW= ROE -MUSCL_FLOW= NO -SLOPE_LIMITER_FLOW= VENKATAKRISHNAN +MUSCL_FLOW= YES +SLOPE_LIMITER_FLOW= VENKATAKRISHNAN_WANG ENTROPY_FIX_COEFF= 0.03 JST_SENSOR_COEFF= ( 0.5, 0.02 ) TIME_DISCRE_FLOW= EULER_IMPLICIT - +% % -------------------- TURBULENT NUMERICAL METHOD DEFINITION ------------------% % CONV_NUM_METHOD_TURB= SCALAR_UPWIND @@ -134,18 +121,18 @@ MUSCL_TURB= NO SLOPE_LIMITER_TURB= VENKATAKRISHNAN TIME_DISCRE_TURB= EULER_IMPLICIT CFL_REDUCTION_TURB= 1.0 - +% % ----------------------- DESIGN VARIABLE PARAMETERS --------------------------% % DV_KIND= FFD_SETTING DV_MARKER= ( airfoil ) DV_PARAM= ( MAIN_BOX, 3.0, 4.0, 0.0, 0.0) DV_VALUE= 0.01 - +% % ------------------------ GRID DEFORMATION PARAMETERS ------------------------% % DEFORM_LINEAR_SOLVER_ITER= 250 - +% % -------------------- FREE-FORM DEFORMATION PARAMETERS -----------------------% % FFD_TOLERANCE= 1E-10 @@ -153,7 +140,7 @@ FFD_ITERATIONS= 500 FFD_DEFINITION= (MAIN_BOX, -0.007280, 0.012690, 0.0, 0.024666, 0.02200, 0.0, 0.04785, -0.04985, 0.0, 0.01709, -0.05911, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) FFD_DEGREE= ( 4 , 4 , 0 ) FFD_CONTINUITY= 2ND_DERIVATIVE - +% % --------------------- OPTIMAL SHAPE DESIGN DEFINITION -----------------------% % DEFINITION_DV= ( 19, 1.0 | airfoil | MAIN_BOX, 0, 0, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 1, 0, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 2, 0, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 3, 0, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 4, 0, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 0, 1, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 1, 1, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 2, 1, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 3, 1, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 4, 1, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 0, 2, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 1, 2, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 2, 2, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 3, 2, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 4, 2, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 0, 3, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 1, 3, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 2, 3, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 3, 3, 0.0, 0.0 ); ( 19, 1.0 | airfoil | MAIN_BOX, 4, 3, 0.0, 0.0 ) @@ -161,19 +148,19 @@ OPT_CONSTRAINT= ( FLOW_ANGLE_OUT < -74.0 ) * 0.001 OPT_OBJECTIVE= SURFACE_PRESSURE_DROP* 0.0001 OPT_ITERATIONS= 19 OPT_ACCURACY= 1E-10 - +% % --------------------- OBJECTIVE FUNCTION DEFINITION -----------------------% % -OBJECTIVE_FUNCTION= SURFACE_PRESSURE_DROP - +OBJECTIVE_FUNCTION= TOTAL_PRESSURE_LOSS +% % --------------------------- CONVERGENCE PARAMETERS --------------------------% % -ITER= 2001 +ITER= 100 CONV_RESIDUAL_MINVAL= -16 CONV_STARTITER= 10 CONV_CAUCHY_ELEMS= 100 CONV_CAUCHY_EPS= 1E-6 - +% % ---------------- ADJOINT-FLOW NUMERICAL METHOD DEFINITION -------------------% % CONV_NUM_METHOD_ADJFLOW= ROE @@ -184,14 +171,14 @@ QUASI_NEWTON_NUM_SAMPLES= 20 CFL_REDUCTION_ADJFLOW= 0.4 FROZEN_VISC_DISC= YES INCONSISTENT_DISC= YES - +% % ---------------- ADJOINT-TURBULENT NUMERICAL METHOD DEFINITION --------------% % CONV_NUM_METHOD_ADJTURB= SCALAR_UPWIND TIME_DISCRE_ADJTURB= EULER_IMPLICIT CFL_REDUCTION_ADJTURB= 0.01 MUSCL_ADJTURB= NO - +% % ------------------------- INPUT/OUTPUT INFORMATION --------------------------% % MESH_FILENAME= mesh_stator_turb.su2 diff --git a/TestCases/hybrid_regression.py b/TestCases/hybrid_regression.py index e2c375f831a8..23519654b898 100644 --- a/TestCases/hybrid_regression.py +++ b/TestCases/hybrid_regression.py @@ -554,7 +554,7 @@ def main(): Jones_tc_restart.cfg_dir = "turbomachinery/APU_turbocharger" Jones_tc_restart.cfg_file = "Jones_restart.cfg" Jones_tc_restart.test_iter = 5 - Jones_tc_restart.test_vals = [-7.645867, -5.849738, -15.337009, -9.825758, -13.216110, -7.752296, 73286.000000, 73286.000000, 0.020055, 82.286000] + Jones_tc_restart.test_vals = [-11.907788, -12.215989, -19.151836, -13.452316, -19.083454, -13.445506, 73286.000000, 73286.000000, 0.020056, 82.286000] test_list.append(Jones_tc_restart) # 2D axial stage @@ -562,7 +562,7 @@ def main(): axial_stage2D.cfg_dir = "turbomachinery/axial_stage_2D" axial_stage2D.cfg_file = "Axial_stage2D.cfg" axial_stage2D.test_iter = 20 - axial_stage2D.test_vals = [1.167182, 1.598494, -2.928576, 2.573645, -2.527390, 3.016171, 106370.000000, 106370.000000, 5.726800, 64.383000] + axial_stage2D.test_vals = [1.167512, 1.598494, -2.928579, 2.573642, -2.527390, 3.016171, 106370.000000, 106370.000000, 5.726800, 64.383000] test_list.append(axial_stage2D) # 2D transonic stator restart diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index fe7bfe81c60f..2b10faa2c39e 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -1182,7 +1182,7 @@ def main(): Aachen_3D_restart.cfg_file = "aachen_3D_MP_restart.cfg" Aachen_3D_restart.test_iter = 5 Aachen_3D_restart.tol = 0.00001 - Aachen_3D_restart.test_vals = [-7.701448, -8.512355, -6.014939, -6.468419, -5.801738, -4.607179, -5.550692, -5.300771, -3.804188, -5.256009, -5.765048, -3.609605, -2.229276, -2.883895, -0.563469] + Aachen_3D_restart.test_vals = [-7.701420, -8.504728, -6.014939, -6.468223, -5.801124, -4.607179, -5.550665, -5.300778, -3.804188, -5.255983, -5.763060, -3.609605, -2.229249, -2.880453, -0.563469] test_list.append(Aachen_3D_restart) # Jones APU Turbocharger restart @@ -1190,7 +1190,7 @@ def main(): Jones_tc_restart.cfg_dir = "turbomachinery/APU_turbocharger" Jones_tc_restart.cfg_file = "Jones_restart.cfg" Jones_tc_restart.test_iter = 5 - Jones_tc_restart.test_vals = [-7.645871, -5.849734, -15.337010, -9.825759, -13.216108, -7.752293, 73286.000000, 73286.000000, 0.020055, 82.286000] + Jones_tc_restart.test_vals = [-11.942136, -12.212801, -19.255081, -13.545405, -19.091794, -13.455630, 73286.000000, 73286.000000, 0.020056, 82.286000] test_list.append(Jones_tc_restart) # 2D axial stage @@ -1198,7 +1198,7 @@ def main(): axial_stage2D.cfg_dir = "turbomachinery/axial_stage_2D" axial_stage2D.cfg_file = "Axial_stage2D.cfg" axial_stage2D.test_iter = 20 - axial_stage2D.test_vals = [1.167161, 1.598507, -2.928575, 2.573646, -2.527392, 3.016170, 106370.000000, 106370.000000, 5.726800, 64.383000] + axial_stage2D.test_vals = [1.167491, 1.598507, -2.928578, 2.573644, -2.527392, 3.016170, 106370.000000, 106370.000000, 5.726800, 64.383000] test_list.append(axial_stage2D) # 2D transonic stator restart @@ -1215,8 +1215,8 @@ def main(): 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.632229, -8.894737, -9.348730] - multi_interface.test_vals_aarch64 = [-8.632229, -8.894737, -9.348730] + multi_interface.test_vals = [-8.632227, -8.894736, -9.348706] + multi_interface.test_vals_aarch64 = [-8.632227, -8.894736, -9.348706] test_list.append(multi_interface) ###################################### @@ -1806,7 +1806,7 @@ def main(): species3_multizone_restart.cfg_dir = "species_transport/multizone" species3_multizone_restart.cfg_file = "configMaster.cfg" species3_multizone_restart.test_iter = 5 - species3_multizone_restart.test_vals = [-4.634484, -4.515504] + species3_multizone_restart.test_vals = [-4.634924, -4.516692] species3_multizone_restart.multizone = True test_list.append(species3_multizone_restart) diff --git a/TestCases/parallel_regression_AD.py b/TestCases/parallel_regression_AD.py index ee45cf41a79f..561591099140 100644 --- a/TestCases/parallel_regression_AD.py +++ b/TestCases/parallel_regression_AD.py @@ -71,7 +71,7 @@ def main(): ea_naca64206.cfg_dir = "optimization_euler/equivalentarea_naca64206" ea_naca64206.cfg_file = "NACA64206.cfg" ea_naca64206.test_iter = 10 - ea_naca64206.test_vals = [3.117653, 2.396440, -5467200.000000, 11.585000] + ea_naca64206.test_vals = [3.117653, 2.396440, 334.750000, 11.585000] test_list.append(ea_naca64206) #################################### @@ -222,6 +222,26 @@ def main(): discadj_pitchingNACA0012.unsteady = True test_list.append(discadj_pitchingNACA0012) + ####################################################### + ### Disc. adj. multizone interfaces ### + ####################################################### + + # Mixing plane interface + discadj_mixing_plane = TestCase('discadj_mixing_plane') + discadj_mixing_plane.cfg_dir = "disc_adj_ffi/mixing_plane" + discadj_mixing_plane.cfg_file = "circles.cfg" + discadj_mixing_plane.test_iter = 10 + discadj_mixing_plane.test_vals = [10.000000, -4.714607, -4.479606] + test_list.append(discadj_mixing_plane) + + # Sliding interface + discadj_sliding_interface = TestCase('discadj_sliding_interface') + discadj_sliding_interface.cfg_dir = "disc_adj_ffi/sliding_interface" + discadj_sliding_interface.cfg_file = "circles.cfg" + discadj_sliding_interface.test_iter = 10 + discadj_sliding_interface.test_vals = [10.000000, -4.395153, -4.419435] + test_list.append(discadj_sliding_interface) + ####################################################### ### Disc. adj. turbomachinery ### ####################################################### @@ -231,10 +251,19 @@ def main(): discadj_trans_stator.cfg_dir = "disc_adj_turbomachinery/transonic_stator_2D" discadj_trans_stator.cfg_file = "transonic_stator.cfg" discadj_trans_stator.test_iter = 79 - discadj_trans_stator.test_vals = [79.000000, 2.549037, 2.313067, 2.139716, 0.736741] - discadj_trans_stator.test_vals_aarch64 = [79.000000, 0.696755, 0.485950, 0.569475, -0.990065] + discadj_trans_stator.test_vals = [79.000000, -7.555647, -10.335486, -10.356919, -13.629543] + discadj_trans_stator.test_vals_aarch64 = [79.000000, -7.555647, -10.335486, -10.356919, -13.629543] test_list.append(discadj_trans_stator) + # Axial stage 2D + discadj_axial_stage = TestCase('axial_stage_2D') + discadj_axial_stage.cfg_dir = "disc_adj_turbomachinery/axial_stage_2D" + discadj_axial_stage.cfg_file = "Axial_stage2D.cfg" + discadj_axial_stage.test_iter = 79 + discadj_axial_stage.test_vals = [79.000000, -6.606356, -7.139726] + discadj_axial_stage.test_vals_aarch64 = [79.000000, -6.606356, -7.139726] + test_list.append(discadj_axial_stage) + ################################### ### Structural Adjoint ### ################################### @@ -286,7 +315,7 @@ def main(): discadj_fsi2.cfg_dir = "disc_adj_fsi/Airfoil_2d" discadj_fsi2.cfg_file = "config.cfg" discadj_fsi2.test_iter = 8 - discadj_fsi2.test_vals = [-3.824641, 1.979547, -3.863368, 0.295450, 3.839800] + discadj_fsi2.test_vals = [-3.824633, 1.979516, -3.863368, 0.295450, 3.839800] discadj_fsi2.test_vals_aarch64 = [-3.824870, 1.979160, -3.863368, 0.295450, 3.839800] discadj_fsi2.tol = 0.00001 test_list.append(discadj_fsi2) @@ -308,7 +337,7 @@ def main(): da_sp_pinArray_cht_2d_dp_hf.cfg_dir = "incomp_navierstokes/streamwise_periodic/chtPinArray_2d" da_sp_pinArray_cht_2d_dp_hf.cfg_file = "DA_configMaster.cfg" da_sp_pinArray_cht_2d_dp_hf.test_iter = 100 - da_sp_pinArray_cht_2d_dp_hf.test_vals = [-11.620815, -6.488915, -13.316362] + da_sp_pinArray_cht_2d_dp_hf.test_vals = [-11.620815, -6.488915, -13.316675] da_sp_pinArray_cht_2d_dp_hf.multizone = True test_list.append(da_sp_pinArray_cht_2d_dp_hf) @@ -326,7 +355,7 @@ def main(): da_unsteadyCHT_cylinder.cfg_dir = "coupled_cht/disc_adj_unsteadyCHT_cylinder" da_unsteadyCHT_cylinder.cfg_file = "chtMaster.cfg" da_unsteadyCHT_cylinder.test_iter = 2 - da_unsteadyCHT_cylinder.test_vals = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662021, 0.000000, 10.627000, 0.295190] + da_unsteadyCHT_cylinder.test_vals = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662005, 0.000000, 10.627000, 0.295190] da_unsteadyCHT_cylinder.test_vals_aarch64 = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662012, 0.000000, 89.932000, 0.295190] da_unsteadyCHT_cylinder.unsteady = True da_unsteadyCHT_cylinder.multizone = True diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index f49e342a519a..579c9071f1bf 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -902,7 +902,7 @@ def main(): Aachen_3D_restart.cfg_dir = "turbomachinery/Aachen_turbine" Aachen_3D_restart.cfg_file = "aachen_3D_MP_restart.cfg" Aachen_3D_restart.test_iter = 5 - Aachen_3D_restart.test_vals = [-7.701448, -8.512353, -6.014939, -6.468417, -5.801739, -4.607173, -5.550692, -5.300771, -3.804187, -5.256008, -5.765048, -3.609601, -2.229277, -2.883894, -0.563470] + Aachen_3D_restart.test_vals = [-7.701421, -8.504727, -6.014939, -6.468221, -5.801125, -4.607173, -5.550665, -5.300779, -3.804187, -5.255982, -5.763060, -3.609601, -2.229250, -2.880453, -0.563470] Aachen_3D_restart.enabled_with_asan = False test_list.append(Aachen_3D_restart) @@ -911,7 +911,7 @@ def main(): Jones_tc_restart.cfg_dir = "turbomachinery/APU_turbocharger" Jones_tc_restart.cfg_file = "Jones_restart.cfg" Jones_tc_restart.test_iter = 5 - Jones_tc_restart.test_vals = [-7.645867, -5.849734, -15.337011, -9.825761, -13.216108, -7.752293, 73286.000000, 73286.000000, 0.020055, 82.286000] + Jones_tc_restart.test_vals = [-11.945086, -12.212837, -19.261517, -13.548349, -19.083279, -13.446452, 73286.000000, 73286.000000, 0.020056, 82.286000] test_list.append(Jones_tc_restart) # 2D axial stage @@ -919,7 +919,7 @@ def main(): axial_stage2D.cfg_dir = "turbomachinery/axial_stage_2D" axial_stage2D.cfg_file = "Axial_stage2D.cfg" axial_stage2D.test_iter = 20 - axial_stage2D.test_vals = [1.167182, 1.598496, -2.928577, 2.573644, -2.527392, 3.016170, 106370.000000, 106370.000000, 5.726800, 64.383000] + axial_stage2D.test_vals = [1.167512, 1.598496, -2.928579, 2.573642, -2.527392, 3.016170, 106370.000000, 106370.000000, 5.726800, 64.383000] test_list.append(axial_stage2D) # 2D transonic stator restart @@ -927,8 +927,8 @@ def main(): transonic_stator_restart.cfg_dir = "turbomachinery/transonic_stator_2D" transonic_stator_restart.cfg_file = "transonic_stator_restart.cfg" transonic_stator_restart.test_iter = 20 - transonic_stator_restart.test_vals = [-4.367780, -2.492918, -2.082410, 1.727494, -1.466974, 3.224733, -471620.000000, 94.839000, -0.052084] - transonic_stator_restart.test_vals_aarch64 = [-4.443401, -2.566759, -2.169302, 1.651815, -1.356398, 3.172527, -471620.000000, 94.843000, -0.044669] + transonic_stator_restart.test_vals = [-4.367784, -2.492912, -2.082414, 1.727491, -1.466974, 3.224730, -471620.000000, 94.839000, -0.052082] + transonic_stator_restart.test_vals_aarch64 = [-4.367784, -2.492912, -2.082414, 1.727491, -1.466974, 3.224730, -471620.000000, 94.839000, -0.052082] test_list.append(transonic_stator_restart) # Multiple turbomachinery interface restart @@ -936,8 +936,8 @@ def main(): 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.632229, -8.894737, -9.348730] - multi_interface.test_vals_aarch64 = [-8.632229, -8.894737, -9.348730] + multi_interface.test_vals = [-8.632227, -8.894736, -9.348706] + multi_interface.test_vals_aarch64 = [-8.632227, -8.894736, -9.348706] test_list.append(multi_interface) diff --git a/TestCases/serial_regression_AD.py b/TestCases/serial_regression_AD.py index 2c51de9f9dd3..1f4d63fc5bb5 100644 --- a/TestCases/serial_regression_AD.py +++ b/TestCases/serial_regression_AD.py @@ -158,7 +158,7 @@ def main(): discadj_pitchingNACA0012.cfg_dir = "disc_adj_euler/naca0012_pitching" discadj_pitchingNACA0012.cfg_file = "inv_NACA0012_pitching.cfg" discadj_pitchingNACA0012.test_iter = 4 - discadj_pitchingNACA0012.test_vals = [-1.050725, -1.504630, -0.004855, 0.000013] + discadj_pitchingNACA0012.test_vals = [-1.050429, -1.504333, -0.004852, 0.000013] discadj_pitchingNACA0012.unsteady = True test_list.append(discadj_pitchingNACA0012) @@ -167,10 +167,52 @@ def main(): unst_deforming_naca0012.cfg_dir = "disc_adj_euler/naca0012_pitching_def" unst_deforming_naca0012.cfg_file = "inv_NACA0012_pitching_deform_ad.cfg" unst_deforming_naca0012.test_iter = 4 - unst_deforming_naca0012.test_vals = [-1.886194, -1.780629, 3882.900000, 0.000003] + unst_deforming_naca0012.test_vals = [-1.885968, -1.780392, 3890.100000, 0.000003] unst_deforming_naca0012.unsteady = True test_list.append(unst_deforming_naca0012) + ####################################################### + ### Disc. adj. multizone interfaces ### + ####################################################### + + # Mixing plane interface + discadj_mixing_plane = TestCase('discadj_mixing_plane') + discadj_mixing_plane.cfg_dir = "disc_adj_ffi/mixing_plane" + discadj_mixing_plane.cfg_file = "circles.cfg" + discadj_mixing_plane.test_iter = 10 + discadj_mixing_plane.test_vals = [10.000000, -4.714573, -4.479609] + test_list.append(discadj_mixing_plane) + + # Sliding interface + discadj_sliding_interface = TestCase('discadj_sliding_interface') + discadj_sliding_interface.cfg_dir = "disc_adj_ffi/sliding_interface" + discadj_sliding_interface.cfg_file = "circles.cfg" + discadj_sliding_interface.test_iter = 10 + discadj_sliding_interface.test_vals = [10.000000, -4.395143, -4.419431] + test_list.append(discadj_sliding_interface) + + ####################################################### + ### Disc. adj. turbomachinery ### + ####################################################### + + # Transonic Stator 2D + discadj_trans_stator = TestCase('transonic_stator') + discadj_trans_stator.cfg_dir = "disc_adj_turbomachinery/transonic_stator_2D" + discadj_trans_stator.cfg_file = "transonic_stator.cfg" + discadj_trans_stator.test_iter = 79 + discadj_trans_stator.test_vals = [79.000000, -7.308167, -9.891117, -10.038669, -13.368501] + discadj_trans_stator.test_vals_aarch64 = [79.000000, -7.308167, -9.891117, -10.038669, -13.368501] + test_list.append(discadj_trans_stator) + + # Axial stage 2D + discadj_axial_stage = TestCase('axial_stage_2D') + discadj_axial_stage.cfg_dir = "disc_adj_turbomachinery/axial_stage_2D" + discadj_axial_stage.cfg_file = "Axial_stage2D.cfg" + discadj_axial_stage.test_iter = 79 + discadj_axial_stage.test_vals = [79.000000, -6.605593, -7.138207] + discadj_axial_stage.test_vals_aarch64 = [79.000000, -6.605593, -7.138207] + test_list.append(discadj_axial_stage) + ################################### ### Structural Adjoint ### ################################### @@ -205,8 +247,9 @@ def main(): discadj_fsi = TestCase('discadj_fsi') discadj_fsi.cfg_dir = "disc_adj_fsi" discadj_fsi.cfg_file = "config.cfg" - discadj_fsi.test_iter = 9 - discadj_fsi.test_vals = [-3.167614, -4.164629, 4.3943e-04, -1.0619] + discadj_fsi.test_iter = 6 + discadj_fsi.test_vals = [6.000000, -1.956662, -3.071318, 0.000440, -1.062800] + discadj_fsi.test_vals_aarch64 = [6.000000, -1.956662, -3.071318, 0.000440, -1.062800] test_list.append(discadj_fsi) ################################### From 556c415aea954be116b202255121a4c39d96db74 Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Sat, 22 Aug 2026 21:19:22 -0700 Subject: [PATCH 34/61] Add OpenMP for force calculation. (#2870) ## Proposed Changes Add OpenMP for force calculation. ## PR Checklist - [X] I am submitting my contribution to the develop branch. - [X] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [X] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [X] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [ ] I have added a test case that demonstrates my contribution, if necessary. - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --------- Co-authored-by: Claude Sonnet 5 --- Common/include/containers/C2DContainer.hpp | 2 + Common/include/linear_algebra/CSysMatrix.hpp | 108 +- Common/include/linear_algebra/CSysMatrix.inl | 4 +- Common/src/linear_algebra/CSysMatrix.cpp | 24 +- Common/src/linear_algebra/CSysMatrixGPU.cu | 15 +- .../include/solvers/CFVMFlowSolverBase.hpp | 14 +- .../include/solvers/CFVMFlowSolverBase.inl | 1447 ++++++++--------- .../src/integration/CMultiGridIntegration.cpp | 39 +- TestCases/hybrid_regression.py | 20 +- TestCases/parallel_regression.py | 20 +- TestCases/parallel_regression_AD.py | 4 +- TestCases/serial_regression.py | 18 +- TestCases/serial_regression_AD.py | 2 +- TestCases/vandv.py | 8 +- .../linear_algebra/quantization_tests.cpp | 25 +- 15 files changed, 828 insertions(+), 922 deletions(-) diff --git a/Common/include/containers/C2DContainer.hpp b/Common/include/containers/C2DContainer.hpp index 85d6d463e115..380e43cfba7b 100644 --- a/Common/include/containers/C2DContainer.hpp +++ b/Common/include/containers/C2DContainer.hpp @@ -391,6 +391,8 @@ class C2DContainer static constexpr bool IsRowMajor = (Store == StorageType::RowMajor); static constexpr bool IsColumnMajor = (Store == StorageType::ColumnMajor); static constexpr size_t StaticSize = StaticRows * StaticCols; + static constexpr size_t StaticNRows = StaticRows; + static constexpr size_t StaticNCols = StaticCols; /*! * \brief Scalar iterator to the inner dimension of the container, read-only. diff --git a/Common/include/linear_algebra/CSysMatrix.hpp b/Common/include/linear_algebra/CSysMatrix.hpp index ccd9cae78779..abecc767e26c 100644 --- a/Common/include/linear_algebra/CSysMatrix.hpp +++ b/Common/include/linear_algebra/CSysMatrix.hpp @@ -111,36 +111,14 @@ struct CSysMatrixComms { }; /*! - * \brief std::max/std::min, usable from both host and device code. Calling std::max/std::min - * directly from a SU2_CUDA_HOST_DEVICE function compiles without error but is not - * actually valid without --expt-relaxed-constexpr which is not used in this build. - */ -template -SU2_CUDA_HOST_DEVICE inline T QuantMax(T a, T b) noexcept { -#ifdef __CUDA_ARCH__ - return max(a, b); -#else - return std::max(a, b); -#endif -} -template -SU2_CUDA_HOST_DEVICE inline T QuantMin(T a, T b) noexcept { -#ifdef __CUDA_ARCH__ - return min(a, b); -#else - return std::min(a, b); -#endif -} - -/*! - * \brief Reconstruct the float row-scale from a stored int8 binary exponent. - * The exponent \p e was packed as (e + 127) into the IEEE 754 biased-exponent field - * with a zero mantissa, giving an exact power of two: 2^e. + * \brief Reconstruct the float row-scale from a stored uint8 binary exponent. + * \p e is stored already biased, i.e. it is the IEEE 754 biased-exponent field itself, so + * shifting it into place with a zero mantissa gives the exact power of two 2^(e-127). * This is the inverse of the encoding in EncodeQuantBlock. * \note Branches on __CUDA_ARCH__, plain memcpy compiles for the device but does not work! */ -SU2_CUDA_HOST_DEVICE inline float DecodeQuantScale(int8_t e) noexcept { - const uint32_t bits = static_cast(QuantMax(0, static_cast(e) + 127)) << 23; +SU2_CUDA_HOST_DEVICE FORCEINLINE float DecodeQuantScale(uint8_t e) noexcept { + const uint32_t bits = static_cast(e) << 23; #ifdef __CUDA_ARCH__ return __uint_as_float(bits); #else @@ -162,28 +140,38 @@ SU2_CUDA_HOST_DEVICE inline float DecodeQuantScale(int8_t e) noexcept { * host-only (not SU2_CUDA_HOST_DEVICE). */ template -SU2_CUDA_HOST_DEVICE inline void EncodeQuantRow(const F& f, int8_t& qs, int8_t* __restrict qv, unsigned long nVar, - unsigned long r) noexcept { +SU2_CUDA_HOST_DEVICE FORCEINLINE void EncodeQuantRow(const F& f, uint8_t& qs, int8_t* __restrict qv, unsigned long nVar, + unsigned long r) noexcept { #ifdef __CUDA_ARCH__ - auto passive = [&](unsigned long row, unsigned long col) { return f(row, col); }; +#define EQR_PASSIVE(ROW, COL) f(ROW, COL) #else - auto passive = [&](unsigned long row, unsigned long col) { return SU2_TYPE::PassiveValue(f(row, col)); }; +#define EQR_PASSIVE(ROW, COL) SU2_TYPE::PassiveValue(f(ROW, COL)) + using std::max; + using std::min; #endif - constexpr uint32_t eps_bits = 0x34000000u; + constexpr uint32_t eps_bits = 0x34000000u; // ~1.2e-7 uint32_t max_abs_bits = eps_bits; for (auto c = 0ul; c < nVar; ++c) { - const float fv = static_cast(passive(r, c)); + const auto fv = static_cast(EQR_PASSIVE(r, c)); #ifdef __CUDA_ARCH__ const uint32_t fb = __float_as_uint(fv); #else uint32_t fb; memcpy(&fb, &fv, sizeof(fb)); #endif - max_abs_bits = QuantMax(max_abs_bits, fb & 0x7FFFFFFFu); + /*--- Masking the mantissa as well as the sign leaves the exponent alone in place, which is + * all the scale needs (the max of the exponents is the exponent of the max). ---*/ + max_abs_bits = max(max_abs_bits, fb & 0x7F800000u); } - const int e = QuantMin(127, QuantMax(-128, static_cast(max_abs_bits >> 23) - 133)); - qs = static_cast(e); - const uint32_t inv_bits = static_cast(127 - e) << 23; + /*--- Add 1 (round up the exponent) and subtract 7 (to divide by 128. which is the int8 range + * for "qv") = -6. The 127 float offset is NOT removed, so the stored value is the biased + * exponent of the scale and DecodeQuantScale can use it as exponent bits directly. The + * eps_bits floor puts the result in [98, 249], so it always fits in uint8 without clamping. ---*/ + qs = static_cast((max_abs_bits >> 23) - 6); + /*--- 1/scale = 2^-(qs-127), whose biased exponent is 254 - qs. Because max_abs_bits holds the + * exponent already shifted into place with a zero mantissa, that whole expression collapses to + * one subtraction: (254 - ((max_abs_bits >> 23) - 6)) << 23 == (260 << 23) - max_abs_bits. ---*/ + const uint32_t inv_bits = 0x82000000u /* 260 << 23 */ - max_abs_bits; #ifdef __CUDA_ARCH__ const float inv_rscale = __uint_as_float(inv_bits); #else @@ -191,9 +179,11 @@ SU2_CUDA_HOST_DEVICE inline void EncodeQuantRow(const F& f, int8_t& qs, int8_t* memcpy(&inv_rscale, &inv_bits, sizeof(inv_rscale)); #endif for (auto c = 0ul; c < nVar; ++c) { - qv[c] = - static_cast(QuantMax(-128.f, QuantMin(127.f, roundf(static_cast(passive(r, c)) * inv_rscale)))); + /*--- Truncate and add 0.5 away from 0, equivalent to roundf, but inline. ---*/ + const float t = max(-128.f, min(127.f, static_cast(EQR_PASSIVE(r, c) * inv_rscale))); + qv[c] = static_cast(t + copysignf(0.5f, t)); } +#undef EQR_PASSIVE } /*! @@ -202,8 +192,8 @@ SU2_CUDA_HOST_DEVICE inline void EncodeQuantRow(const F& f, int8_t& qs, int8_t* * the host path). */ template -SU2_CUDA_HOST_DEVICE inline void EncodeQuantBlock(const F& f, int8_t* __restrict qs, int8_t* __restrict qv, - unsigned long nVar) noexcept { +SU2_CUDA_HOST_DEVICE FORCEINLINE void EncodeQuantBlock(const F& f, uint8_t* __restrict qs, int8_t* __restrict qv, + unsigned long nVar) noexcept { for (auto r = 0ul; r < nVar; ++r) EncodeQuantRow(f, qs[r], qv + r * nVar, nVar, r); } @@ -216,10 +206,11 @@ SU2_CUDA_HOST_DEVICE inline void EncodeQuantBlock(const F& f, int8_t* __restrict template struct CBlockView { using QuantType = std::conditional_t, const int8_t, int8_t>; + using QuantScaleType = std::conditional_t, const uint8_t, uint8_t>; - ScalarType* ptr = nullptr; ///< Full-precision block; non-null iff not quantized. - QuantType* qs = nullptr; ///< Per-row binary exponent; non-null iff quantized. - QuantType* qv = nullptr; ///< Quantized values (row-major); non-null iff quantized. + ScalarType* ptr = nullptr; ///< Full-precision block; non-null iff not quantized. + QuantScaleType* qs = nullptr; ///< Per-row biased binary exponent; non-null iff quantized. + QuantType* qv = nullptr; ///< Quantized values (row-major); non-null iff quantized. unsigned long nVar = 0; /*! \brief False when the block is not present in the sparsity pattern. */ @@ -316,6 +307,9 @@ class CSysMatrix { /*--- Quantized off-diagonal storage (used when quantized_mode == true). ---*/ using QuantType = int8_t; + /*!< \brief Row scales are stored as the biased float exponent, hence unsigned, see + * DecodeQuantScale. */ + using QuantScaleType = uint8_t; /*! \brief Set by Initialize() when preconditioner == Q_LU_SGS, Q_JACOBI or Q_IDENTITY. * mat.l and mat.u are NOT allocated; off-diagonal blocks live in q_scale/q_blocks @@ -328,16 +322,16 @@ class CSysMatrix { #else static constexpr bool quantized_mode = false; #endif - /*!< \brief Per-row exponents; .l/.u sized [nnz_l/u * nVar], .d [nPoint * nVar]. .l/.u are + /*!< \brief Per-row biased exponents; .l/.u sized [nnz_l/u * nVar], .d [nPoint * nVar]. .l/.u are * populated during assembly (quantized on the fly); .d is populated by * QuantizeDiagonalBlocks(). .l/.u are pinned (cudaMallocHost) rather than * aligned_alloc when useCuda, so HtDTransfer()'s async uploads them. */ - LDU q_scale; + LDU q_scale; /*!< \brief Quantized block entries; .l/.u sized [nnz_l/u * nVar * nEqn], .d [nPoint * nVar * nEqn]. */ LDU q_blocks; /*!< \brief Device mirrors of the quantized storage, only allocated when quantized_mode. */ - LDU d_q_scale; + LDU d_q_scale; LDU d_q_blocks; bool useCuda = false; /*!< \brief Whether CUDA is enabled. */ @@ -599,12 +593,14 @@ class CSysMatrix { * \param[in] vec - Input vector (nEqn entries). * \param[in,out] prod - Accumulation output (nVar entries). */ - inline void QuantizedMatVecAdd(const QuantType* qs, const QuantType* qv, const ScalarType* vec, + inline void QuantizedMatVecAdd(const QuantScaleType* qs, const QuantType* qv, const ScalarType* vec, ScalarType* prod) const; /*! \brief Quantize one nVar×nVar block (row-major) into the int8 scale+value arrays. * Called on the hot assembly path (SetBlocks/UpdateBlocks in Q_LU_SGS mode). */ - void QuantizeBlock(const ScalarType* blk, QuantType* qs, QuantType* qv) const; + inline void QuantizeBlock(const ScalarType* blk, QuantScaleType* qs, QuantType* qv) const { + EncodeQuantBlock([&](unsigned long r, unsigned long c) { return blk[r * nVar + c]; }, qs, qv, nVar); + } /*! \brief Full-row product using quantized L/D/U (Q_LU_SGS SpMV path). */ inline void QuantizedRowProduct(const CSysVector& vec, unsigned long row_i, ScalarType* prod) const; @@ -925,6 +921,7 @@ class CSysMatrix { static_assert(MatTypeSIMD::IsRowMajor, "Block storage is not compatible with matrix."); constexpr size_t blkSz = MatTypeSIMD::StaticSize; assert(blkSz == nVar * nEqn); + constexpr size_t nVar = MatTypeSIMD::StaticNRows; /*--- "Transpose" the blocks, scale, and possibly convert types, * giving the compiler the chance to vectorize all of these. ---*/ @@ -951,9 +948,11 @@ class CSysMatrix { bii[i] -= blk_i[k][i]; bjj[i] -= blk_j[k][i]; } - QuantizeBlock(blk_j[k], &q_scale.u[iEdge[k] * nVar], &q_blocks.u[iEdge[k] * blkSz]); + EncodeQuantBlock([&, k](unsigned long r, unsigned long c) { return blk_j[k][r * nVar + c]; }, + &q_scale.u[iEdge[k] * nVar], &q_blocks.u[iEdge[k] * blkSz], nVar); const auto k_l = edge_ptr_l[iEdge[k]]; - QuantizeBlock(blk_i[k], &q_scale.l[k_l * nVar], &q_blocks.l[k_l * blkSz]); + EncodeQuantBlock([&, k](unsigned long r, unsigned long c) { return blk_i[k][r * nVar + c]; }, + &q_scale.l[k_l * nVar], &q_blocks.l[k_l * blkSz], nVar); } else { auto bij = &mat.u[iEdge[k] * blkSz]; auto bji = &mat.l[edge_ptr_l[iEdge[k]] * blkSz]; @@ -1039,6 +1038,7 @@ class CSysMatrix { static_assert(MatTypeSIMD::IsRowMajor, "Block storage is not compatible with matrix."); constexpr size_t blkSz = MatTypeSIMD::StaticSize; assert(blkSz == nVar * nEqn); + constexpr size_t nVar = MatTypeSIMD::StaticNRows; /*--- "Transpose" the blocks, scale, and possibly convert types, * giving the compiler the chance to vectorize all of these. ---*/ @@ -1057,9 +1057,11 @@ class CSysMatrix { if (mask[k] == 0) continue; if (quantized_mode) { - QuantizeBlock(blk_j[k], &q_scale.u[iEdge[k] * nVar], &q_blocks.u[iEdge[k] * blkSz]); + EncodeQuantBlock([&, k](unsigned long r, unsigned long c) { return blk_j[k][r * nVar + c]; }, + &q_scale.u[iEdge[k] * nVar], &q_blocks.u[iEdge[k] * blkSz], nVar); const auto k_l = edge_ptr_l[iEdge[k]]; - QuantizeBlock(blk_i[k], &q_scale.l[k_l * nVar], &q_blocks.l[k_l * blkSz]); + EncodeQuantBlock([&, k](unsigned long r, unsigned long c) { return blk_i[k][r * nVar + c]; }, + &q_scale.l[k_l * nVar], &q_blocks.l[k_l * blkSz], nVar); } else { ScalarType* bij = &mat.u[iEdge[k] * blkSz]; ScalarType* bji = &mat.l[edge_ptr_l[iEdge[k]] * blkSz]; diff --git a/Common/include/linear_algebra/CSysMatrix.inl b/Common/include/linear_algebra/CSysMatrix.inl index c0ac196c9610..fd6d6aeff119 100644 --- a/Common/include/linear_algebra/CSysMatrix.inl +++ b/Common/include/linear_algebra/CSysMatrix.inl @@ -147,7 +147,7 @@ FORCEINLINE void CSysMatrix::GaussElimination(unsigned long block_i, template FORCEINLINE void CSysMatrix::QuantizedGaussElimination(unsigned long block_i, ScalarType* rhs) const { ScalarType block[MAXNVAR * MAXNVAR]; - const QuantType* __restrict qs = &q_scale.d[block_i * nVar]; + const QuantScaleType* __restrict qs = &q_scale.d[block_i * nVar]; const QuantType* __restrict qv = &q_blocks.d[block_i * nVar * nVar]; for (auto r = 0ul; r < nVar; ++r) { const float row_scale = DecodeQuantScale(qs[r]); @@ -176,7 +176,7 @@ FORCEINLINE const ScalarType* CSysMatrix::InvertDiagonalBlockILUMatr } template -FORCEINLINE void CSysMatrix::QuantizedMatVecAdd(const QuantType* __restrict qs, +FORCEINLINE void CSysMatrix::QuantizedMatVecAdd(const QuantScaleType* __restrict qs, const QuantType* __restrict qv, const ScalarType* __restrict vec, ScalarType* __restrict prod) const { diff --git a/Common/src/linear_algebra/CSysMatrix.cpp b/Common/src/linear_algebra/CSysMatrix.cpp index 7bee9a093470..84f30a2aa3d2 100644 --- a/Common/src/linear_algebra/CSysMatrix.cpp +++ b/Common/src/linear_algebra/CSysMatrix.cpp @@ -261,14 +261,16 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi #endif /*--- .l/.u are pinned (page-locked) when useCuda because HtDTransfer() uploads them with * cudaMemcpyAsync, which is only genuinely asynchronous from pinned host memory. ---*/ - auto allocQ = [](QuantType*& ptr, unsigned long n) { - ptr = MemoryAllocation::aligned_alloc(64, n * sizeof(QuantType)); + auto allocQ = [](auto*& ptr, unsigned long n) { + using T = std::remove_reference_t; + ptr = MemoryAllocation::aligned_alloc(64, n * sizeof(T)); }; - auto allocPinnedIfCuda = [useCuda = this->useCuda](QuantType*& ptr, unsigned long n) { + auto allocPinnedIfCuda = [useCuda = this->useCuda](auto*& ptr, unsigned long n) { + using T = std::remove_reference_t; if (useCuda) { - ptr = GPUMemoryAllocation::pinned_alloc(n * sizeof(QuantType)); + ptr = GPUMemoryAllocation::pinned_alloc(n * sizeof(T)); } else { - ptr = MemoryAllocation::aligned_alloc(64, n * sizeof(QuantType)); + ptr = MemoryAllocation::aligned_alloc(64, n * sizeof(T)); } }; allocPinnedIfCuda(q_scale.l, mat.nnz_l * nVar); @@ -303,8 +305,9 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi /*--- Device mirrors of the host quantized storage; gpu.l/gpu.u are not allocated (nothing * would ever read them). d_q_scale.d/d_q_blocks.d are uploaded from the host result once * QuantizeDiagonalBlocks() has computed it, see the comment on those members. ---*/ - auto GPUAllocQ = [](QuantType*& ptr, unsigned long n) { - ptr = GPUMemoryAllocation::gpu_alloc(n * sizeof(QuantType)); + auto GPUAllocQ = [](auto*& ptr, unsigned long n) { + using T = std::remove_reference_t; + ptr = GPUMemoryAllocation::gpu_alloc(n * sizeof(T)); }; GPUAllocQ(d_q_scale.l, mat.nnz_l * nVar); GPUAllocQ(d_q_blocks.l, mat.nnz_l * nVar * nEqn); @@ -758,11 +761,6 @@ void CSysMatrixComms::Complete(CSysVector& x, CGeometry* geometry, const CCon #endif } -template -void CSysMatrix::QuantizeBlock(const ScalarType* blk, QuantType* qs, QuantType* qv) const { - EncodeQuantBlock([&](unsigned long r, unsigned long c) { return blk[r * nVar + c]; }, qs, qv, nVar); -} - template void CSysMatrix::QuantizeDiagonalBlocks() { SU2_ZONE_SCOPED @@ -1614,7 +1612,7 @@ void CSysMatrix::SetDiagonalAsColumnSum() { for (auto k_u = mat.row_ptr_u[iPoint]; k_u < mat.row_ptr_u[iPoint + 1]; ++k_u) MatrixSubtraction(d_i, &mat.l[u_to_l_transp[k_u] * blkSz], d_i); } else { - auto subtractTransp = [&](su2uint k_transp, const QuantType* qs, const QuantType* qv) { + auto subtractTransp = [&](su2uint k_transp, const QuantScaleType* qs, const QuantType* qv) { const CBlockView view{nullptr, &qs[k_transp * nVar], &qv[k_transp * blkSz], nVar}; for (auto i = 0ul; i < nVar; ++i) for (auto j = 0ul; j < nEqn; ++j) d_i[i * nEqn + j] -= view(i, j); diff --git a/Common/src/linear_algebra/CSysMatrixGPU.cu b/Common/src/linear_algebra/CSysMatrixGPU.cu index 470b7a22bcf4..56b8a1006319 100644 --- a/Common/src/linear_algebra/CSysMatrixGPU.cu +++ b/Common/src/linear_algebra/CSysMatrixGPU.cu @@ -190,7 +190,7 @@ __global__ void InvertDiagonalBlocksKernel(unsigned long nRows, unsigned long nV */ template __global__ void QuantizeDiagonalBlocksKernel(unsigned long nRows, unsigned long nVar, - const ScalarType* __restrict__ mat_d, int8_t* __restrict__ q_scale_d, + const ScalarType* __restrict__ mat_d, uint8_t* __restrict__ q_scale_d, int8_t* __restrict__ q_blocks_d) { const unsigned long rowsPerBlock = blockDim.x / nVar; const unsigned long iVar = threadIdx.x % nVar; @@ -452,17 +452,18 @@ __global__ void BlockLDU_SpMV_kernel(unsigned long nRows, unsigned long nVar, template __global__ void QuantizedBlockLDU_SpMV_kernel( unsigned long nRows, unsigned long nVar, const su2uint* __restrict__ row_ptr_l, - const su2uint* __restrict__ col_ind_l, const int8_t* __restrict__ q_scale_l, - const int8_t* __restrict__ q_blocks_l, const int8_t* __restrict__ q_scale_d, + const su2uint* __restrict__ col_ind_l, const uint8_t* __restrict__ q_scale_l, + const int8_t* __restrict__ q_blocks_l, const uint8_t* __restrict__ q_scale_d, const int8_t* __restrict__ q_blocks_d, const su2uint* __restrict__ row_ptr_u, - const su2uint* __restrict__ col_ind_u, const int8_t* __restrict__ q_scale_u, + const su2uint* __restrict__ col_ind_u, const uint8_t* __restrict__ q_scale_u, const int8_t* __restrict__ q_blocks_u, const ScalarType* __restrict__ x, ScalarType* __restrict__ y) { const unsigned long rowsPerBlock = blockDim.x / nVar; const unsigned long iVar = threadIdx.x % nVar; const unsigned long iRow = static_cast(blockIdx.x) * rowsPerBlock + threadIdx.x / nVar; if (iRow >= nRows) return; - auto addBlock = [&](const int8_t* __restrict__ qs, const int8_t* __restrict__ qv, const ScalarType* __restrict__ xk) { + auto addBlock = [&](const uint8_t* __restrict__ qs, const int8_t* __restrict__ qv, + const ScalarType* __restrict__ xk) { const float row_scale = DecodeQuantScale(qs[iVar]); const int8_t* __restrict__ row = qv + iVar * nVar; ScalarType partial = 0; @@ -708,11 +709,11 @@ void CSysMatrix::HtDTransfer(bool trigger) const { * stream. ---*/ if (aux_stream == nullptr) gpuErrChk(cudaStreamCreate(&aux_stream)); if (htd_event == nullptr) gpuErrChk(cudaEventCreateWithFlags(&htd_event, cudaEventDisableTiming)); - gpuErrChk(cudaMemcpyAsync(d_q_scale.l, q_scale.l, sizeof(QuantType) * mat.nnz_l * nVar, cudaMemcpyHostToDevice, + gpuErrChk(cudaMemcpyAsync(d_q_scale.l, q_scale.l, sizeof(QuantScaleType) * mat.nnz_l * nVar, cudaMemcpyHostToDevice, aux_stream)); gpuErrChk(cudaMemcpyAsync(d_q_blocks.l, q_blocks.l, sizeof(QuantType) * mat.nnz_l * nVar * nEqn, cudaMemcpyHostToDevice, aux_stream)); - gpuErrChk(cudaMemcpyAsync(d_q_scale.u, q_scale.u, sizeof(QuantType) * mat.nnz_u * nVar, cudaMemcpyHostToDevice, + gpuErrChk(cudaMemcpyAsync(d_q_scale.u, q_scale.u, sizeof(QuantScaleType) * mat.nnz_u * nVar, cudaMemcpyHostToDevice, aux_stream)); gpuErrChk(cudaMemcpyAsync(d_q_blocks.u, q_blocks.u, sizeof(QuantType) * mat.nnz_u * nVar * nEqn, cudaMemcpyHostToDevice, aux_stream)); diff --git a/SU2_CFD/include/solvers/CFVMFlowSolverBase.hpp b/SU2_CFD/include/solvers/CFVMFlowSolverBase.hpp index b2dd6441318d..ae07bef84552 100644 --- a/SU2_CFD/include/solvers/CFVMFlowSolverBase.hpp +++ b/SU2_CFD/include/solvers/CFVMFlowSolverBase.hpp @@ -109,9 +109,19 @@ class CFVMFlowSolverBase : public CSolver { void allocate(int size); /*!< \brief Allocates arrays. */ - void setZero(int i); /*!< \brief Sets all values to zero at a particular index. */ - void setZero() { /*!< \brief Sets all values to zero for all indices. */ + /*!< \brief Sets all values to zero at a particular index. */ + void setZero(int i) { + CD[i] = CL[i] = CSF[i] = CEff[i] = 0.0; + CFx[i] = CFy[i] = CFz[i] = CMx[i] = 0.0; + CMy[i] = CMz[i] = CoPx[i] = CoPy[i] = 0.0; + CoPz[i] = CT[i] = CQ[i] = CMerit[i] = 0.0; + } + + /*!< \brief Sets all values to zero for all indices. */ + void setZero() { + SU2_OMP_FOR_STAT(OMP_MIN_SIZE / 2) for (int i = 0; i < _size; ++i) setZero(i); + END_SU2_OMP_FOR } AeroCoeffsArray(int size = 0) : _size(size) { diff --git a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl index 5b1c49b75d93..aea0dc1d6a26 100644 --- a/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl +++ b/SU2_CFD/include/solvers/CFVMFlowSolverBase.inl @@ -74,14 +74,6 @@ CFVMFlowSolverBase::AeroCoeffsArray::~AeroCoeffsArray() { delete[] CMerit; } -template -void CFVMFlowSolverBase::AeroCoeffsArray::setZero(int i) { - CD[i] = CL[i] = CSF[i] = CEff[i] = 0.0; - CFx[i] = CFy[i] = CFz[i] = CMx[i] = 0.0; - CMy[i] = CMz[i] = CoPx[i] = CoPy[i] = 0.0; - CoPz[i] = CT[i] = CQ[i] = CMerit[i] = 0.0; -} - template void CFVMFlowSolverBase::Allocate(const CConfig& config) { SU2_ZONE_SCOPED @@ -1839,81 +1831,408 @@ void CFVMFlowSolverBase::SetResidual_DualTime(CGeometry *geometry } -template -void CFVMFlowSolverBase::Pressure_Forces(const CGeometry* geometry, const CConfig* config) { - unsigned long iVertex, iPoint; - unsigned short iDim, iMarker, Boundary, Monitoring, iMarker_Monitoring; - su2double Pressure = 0.0, NFPressOF, RefPressure; - const su2double *Normal = nullptr, *Coord = nullptr; - string Marker_Tag, Monitoring_Tag; - su2double AxiFactor; - - su2double Alpha = config->GetAoA() * PI_NUMBER / 180.0; - su2double Beta = config->GetAoS() * PI_NUMBER / 180.0; - su2double RefArea = config->GetRefArea(); - su2double RefLength = config->GetRefLength(); - auto Origin = config->GetRefOriginMoment(0); - bool axisymmetric = config->GetAxisymmetric(); +/*--- Helpers shared by Pressure_Forces, Momentum_Forces and Friction_Forces. Free function + * templates rather than members: none need instance state, and the AeroCoeffs/AeroCoeffsArray + * types are simply deduced from the caller's arguments. ---*/ +namespace { - SetReferenceValues(*config); +/*! + * \brief Whether a marker's boundary kind is one of the momentum (inlet/outlet/actuator-disk/ + * engine) surfaces handled by Momentum_Forces, used both to gate accumulation into + * MntCoeff and to gate the later CEff/CMerit derivation from it. + * \param[in] Boundary - Boundary kind of the marker (config->GetMarker_All_KindBC(iMarker)). + */ +inline bool IsMomentumBoundary(unsigned short Boundary) { + return (Boundary == INLET_FLOW) || (Boundary == OUTLET_FLOW) || (Boundary == ACTDISK_INLET) || + (Boundary == ACTDISK_OUTLET) || (Boundary == ENGINE_INFLOW) || (Boundary == ENGINE_EXHAUST); +} - const su2double factor = 1.0 / AeroCoeffForceRef; +/*! + * \brief Find iMarker's index within the monitoring markers, and its reference origin if found. + * \param[in] config - Problem definition. + * \param[in] iMarker - Marker to look up. + * \param[in] Monitoring - config->GetMarker_All_Monitoring(iMarker). + * \param[in,out] Origin - Set to iMarker's reference origin if found, left unchanged otherwise. + * \return Index within the monitoring markers, or -1 if iMarker is not monitored, or not found + * among the monitoring markers. + */ +inline int FindMonitoringIndex(const CConfig* config, unsigned long iMarker, unsigned short Monitoring, + std::array& Origin) { + if (Monitoring != YES) return -1; + const string Marker_Tag = config->GetMarker_All_TagBound(iMarker); + const auto nMarker_Monitoring = static_cast(config->GetnMarker_Monitoring()); + for (int iMarker_Monitoring = 0; iMarker_Monitoring < nMarker_Monitoring; iMarker_Monitoring++) { + if (Marker_Tag == config->GetMarker_Monitoring_TagBound(iMarker_Monitoring)) { + Origin = config->GetRefOriginMoment(iMarker_Monitoring); + return iMarker_Monitoring; + } + } + return -1; +} - /*--- Reference pressure is always the far-field value. ---*/ +/*! + * \brief Fold one thread's partial force/moment coefficient contribution (accumulated over + * its share of a marker's vertices) into the per-marker, AllBound and per-surface + * aerodynamic coefficient totals, in a single critical section. Does not touch + * CEff/CMerit (nonlinear ratios), those are derived once from the fully-reduced totals + * after all threads have merged in. + * \param[in] iMarker - Marker index the contribution belongs to. + * \param[in] iMarker_Monitoring - If iMarker is monitored, index within the monitoring markers, -1 otherwise. + * \param[in] partial - This thread's partial coefficients (CD, CL, ..., CQ only). + * \param[in,out] coeffArray - Per-marker totals to update at iMarker. + * \param[in,out] allBoundCoeff - Totals over all boundaries to update. + * \param[in,out] surfaceCoeff - Per-monitoring-surface totals to update. + */ +template +void AddCoeffContribution(unsigned long iMarker, int iMarker_Monitoring, const AeroCoeffsT& partial, + AeroCoeffsArrayT& coeffArray, AeroCoeffsT& allBoundCoeff, AeroCoeffsArrayT& surfaceCoeff) { + SU2_OMP_CRITICAL { + coeffArray.CD[iMarker] += partial.CD; + coeffArray.CL[iMarker] += partial.CL; + coeffArray.CSF[iMarker] += partial.CSF; + coeffArray.CFx[iMarker] += partial.CFx; + coeffArray.CFy[iMarker] += partial.CFy; + coeffArray.CFz[iMarker] += partial.CFz; + coeffArray.CMx[iMarker] += partial.CMx; + coeffArray.CMy[iMarker] += partial.CMy; + coeffArray.CMz[iMarker] += partial.CMz; + coeffArray.CoPx[iMarker] += partial.CoPx; + coeffArray.CoPy[iMarker] += partial.CoPy; + coeffArray.CoPz[iMarker] += partial.CoPz; + coeffArray.CT[iMarker] += partial.CT; + coeffArray.CQ[iMarker] += partial.CQ; + + allBoundCoeff.CD += partial.CD; + allBoundCoeff.CL += partial.CL; + allBoundCoeff.CSF += partial.CSF; + allBoundCoeff.CFx += partial.CFx; + allBoundCoeff.CFy += partial.CFy; + allBoundCoeff.CFz += partial.CFz; + allBoundCoeff.CMx += partial.CMx; + allBoundCoeff.CMy += partial.CMy; + allBoundCoeff.CMz += partial.CMz; + allBoundCoeff.CoPx += partial.CoPx; + allBoundCoeff.CoPy += partial.CoPy; + allBoundCoeff.CoPz += partial.CoPz; + allBoundCoeff.CT += partial.CT; + allBoundCoeff.CQ += partial.CQ; + + /*--- Compute the coefficients per surface ---*/ + + if (iMarker_Monitoring >= 0) { + surfaceCoeff.CL[iMarker_Monitoring] += partial.CL; + surfaceCoeff.CD[iMarker_Monitoring] += partial.CD; + surfaceCoeff.CSF[iMarker_Monitoring] += partial.CSF; + surfaceCoeff.CFx[iMarker_Monitoring] += partial.CFx; + surfaceCoeff.CFy[iMarker_Monitoring] += partial.CFy; + surfaceCoeff.CFz[iMarker_Monitoring] += partial.CFz; + surfaceCoeff.CMx[iMarker_Monitoring] += partial.CMx; + surfaceCoeff.CMy[iMarker_Monitoring] += partial.CMy; + surfaceCoeff.CMz[iMarker_Monitoring] += partial.CMz; + } + } + END_SU2_OMP_CRITICAL +} - RefPressure = Pressure_Inf; +/*! + * \brief Project summed force/moment components onto the wind axes (Alpha, Beta) to get the + * standard aerodynamic coefficients. Identical formulas are used by Pressure_Forces, + * Momentum_Forces and Friction_Forces, applied respectively to their inviscid, + * momentum and viscous force/moment sums. Does not set CEff/CMerit (nonlinear ratios, + * derived later from fully-reduced totals) nor CSF/CMx/CMy/CFz/CoPz in 2D (n/a). + * \param[in] nDim - Number of spatial dimensions (2 or 3). + * \param[in] CosAlpha, SinAlpha, CosBeta, SinBeta - sin/cos of the angle of attack and sideslip, + * precomputed once by the caller (this is called once per monitored marker, redundantly + * by every thread, so recomputing the trig functions here would not be free). + * \param[in] Force - Summed force components (size MAXNDIM). + * \param[in] Moment - Summed moment components about Origin (size MAXNDIM). + * \param[in] MomentX_Force, MomentY_Force, MomentZ_Force - Summed moment-of-force components + * about the coordinate axes, used for the center-of-pressure coordinates. + * \return The wind-axis aerodynamic coefficients (CD, CL, CSF, CFx..CFz, CMx..CMz, CoPx..CoPz, + * CT, CQ). AeroCoeffsT is explicit at the call site (it can't be deduced, since it is + * only the return type): ComputeAeroCoeffsFromForceMoment(...). + */ +template +AeroCoeffsT ComputeAeroCoeffsFromForceMoment(unsigned short nDim, su2double CosAlpha, su2double SinAlpha, + su2double CosBeta, su2double SinBeta, const su2double* Force, + const su2double* Moment, const su2double* MomentX_Force, + const su2double* MomentY_Force, const su2double* MomentZ_Force) { + AeroCoeffsT c; + + if (nDim == 2) { + c.CD = Force[0] * CosAlpha + Force[1] * SinAlpha; + c.CL = -Force[0] * SinAlpha + Force[1] * CosAlpha; + c.CMz = Moment[2]; + c.CoPx = MomentZ_Force[1]; + c.CoPy = -MomentZ_Force[0]; + c.CFx = Force[0]; + c.CFy = Force[1]; + c.CT = -c.CFx; + c.CQ = -c.CMz; + } + if (nDim == 3) { + c.CD = Force[0] * CosAlpha * CosBeta + Force[1] * SinBeta + Force[2] * SinAlpha * CosBeta; + c.CL = -Force[0] * SinAlpha + Force[2] * CosAlpha; + c.CSF = -Force[0] * SinBeta * CosAlpha + Force[1] * CosBeta - Force[2] * SinBeta * SinAlpha; + c.CMx = Moment[0]; + c.CMy = Moment[1]; + c.CMz = Moment[2]; + c.CoPx = -MomentY_Force[0]; + c.CoPz = MomentY_Force[2]; + c.CFx = Force[0]; + c.CFy = Force[1]; + c.CFz = Force[2]; + c.CT = -c.CFz; + c.CQ = -c.CMz; + } + + return c; +} - /*-- Variables initialization ---*/ +/*! + * \brief MPI-sum a single value across ranks (identity if not built with MPI). + * \param[in] x - Value to reduce. + * \return Sum of x over all ranks. + */ +inline su2double MPIReduceSum(su2double x) { +#ifdef HAVE_MPI + su2double tmp = x; + x = 0.0; + SU2_MPI::Allreduce(&tmp, &x, 1, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); +#endif + return x; +} - TotalCoeff.setZero(); +/*! + * \brief MPI-sum an array of per-monitoring-surface values across ranks, in place + * (no-op if not built with MPI). + * \param[in,out] x - Array of size n to reduce in place. + * \param[in] n - Number of entries in x. + */ +inline void MPIReduceSumInPlace(su2double* x, int n) { +#ifdef HAVE_MPI + if (SU2_MPI::GetSize() == SINGLE_NODE) return; + static vector buffer; + buffer.resize(n); + SU2_MPI::Allreduce(x, buffer.data(), n, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); + for (int i = 0; i < n; ++i) x[i] = buffer[i]; +#endif +} + +/*! + * \brief MPI-reduce an AllBound/Surface aerodynamic coefficient pair across ranks (no-op if + * not built with MPI, or if the comm level does not require it). + * \param[in] config - Definition of the particular problem. + * \param[in,out] allBoundCoeff - Totals over all boundaries to reduce. + * \param[in,out] surfaceCoeff - Per-monitoring-surface totals to reduce. + */ +template +void ReduceCoeffsMPI(const CConfig* config, AeroCoeffsT& allBoundCoeff, AeroCoeffsArrayT& surfaceCoeff) { +#ifdef HAVE_MPI + if (config->GetComm_Level() != COMM_FULL) return; - Total_CNearFieldOF = 0.0; - Total_Heat = 0.0; - Total_MaxHeat = 0.0; + /*--- Add AllBound information using all the nodes ---*/ + + allBoundCoeff.CD = MPIReduceSum(allBoundCoeff.CD); + allBoundCoeff.CL = MPIReduceSum(allBoundCoeff.CL); + allBoundCoeff.CSF = MPIReduceSum(allBoundCoeff.CSF); + allBoundCoeff.CEff = allBoundCoeff.CL / (allBoundCoeff.CD + EPS); + + allBoundCoeff.CMx = MPIReduceSum(allBoundCoeff.CMx); + allBoundCoeff.CMy = MPIReduceSum(allBoundCoeff.CMy); + allBoundCoeff.CMz = MPIReduceSum(allBoundCoeff.CMz); + + allBoundCoeff.CoPx = MPIReduceSum(allBoundCoeff.CoPx); + allBoundCoeff.CoPy = MPIReduceSum(allBoundCoeff.CoPy); + allBoundCoeff.CoPz = MPIReduceSum(allBoundCoeff.CoPz); + + allBoundCoeff.CFx = MPIReduceSum(allBoundCoeff.CFx); + allBoundCoeff.CFy = MPIReduceSum(allBoundCoeff.CFy); + allBoundCoeff.CFz = MPIReduceSum(allBoundCoeff.CFz); + + allBoundCoeff.CT = MPIReduceSum(allBoundCoeff.CT); + allBoundCoeff.CQ = MPIReduceSum(allBoundCoeff.CQ); + allBoundCoeff.CMerit = allBoundCoeff.CT / (allBoundCoeff.CQ + EPS); + + /*--- Add the forces on the surfaces using all the nodes ---*/ + + const int nMarkerMon = config->GetnMarker_Monitoring(); + + MPIReduceSumInPlace(surfaceCoeff.CL, nMarkerMon); + MPIReduceSumInPlace(surfaceCoeff.CD, nMarkerMon); + MPIReduceSumInPlace(surfaceCoeff.CSF, nMarkerMon); + + for (int iMarker_Monitoring = 0; iMarker_Monitoring < nMarkerMon; iMarker_Monitoring++) + surfaceCoeff.CEff[iMarker_Monitoring] = + surfaceCoeff.CL[iMarker_Monitoring] / (surfaceCoeff.CD[iMarker_Monitoring] + EPS); + + MPIReduceSumInPlace(surfaceCoeff.CFx, nMarkerMon); + MPIReduceSumInPlace(surfaceCoeff.CFy, nMarkerMon); + MPIReduceSumInPlace(surfaceCoeff.CFz, nMarkerMon); + + MPIReduceSumInPlace(surfaceCoeff.CMx, nMarkerMon); + MPIReduceSumInPlace(surfaceCoeff.CMy, nMarkerMon); + MPIReduceSumInPlace(surfaceCoeff.CMz, nMarkerMon); +#endif +} + +/*! + * \brief Merge an AllBound/Surface aerodynamic coefficient pair into the running total/ + * surfaceTotal grand totals. + * \param[in] config - Definition of the particular problem. + * \param[in] allBoundCoeff - Totals over all boundaries to merge in (already MPI-reduced). + * \param[in] surfaceCoeff - Per-monitoring-surface totals to merge in (already MPI-reduced). + * \param[in,out] total - Grand total to update (e.g. the solver's TotalCoeff). + * \param[in,out] surfaceTotal - Per-surface grand total to update (e.g. the solver's SurfaceCoeff). + * \param[in] overwrite - True to overwrite total/surfaceTotal (first contributor, i.e. + * Pressure_Forces, which also resets them to zero beforehand), false to add to them. + */ +template +void AccumulateTotalCoeffs(const CConfig* config, const AeroCoeffsT& allBoundCoeff, + const AeroCoeffsArrayT& surfaceCoeff, AeroCoeffsT& total, AeroCoeffsArrayT& surfaceTotal, + bool overwrite) { + auto Update = [overwrite](su2double& dst, su2double src) { + if (overwrite) dst = src; + else dst += src; + }; + + Update(total.CD, allBoundCoeff.CD); + Update(total.CL, allBoundCoeff.CL); + Update(total.CSF, allBoundCoeff.CSF); + total.CEff = total.CL / (total.CD + EPS); + Update(total.CFx, allBoundCoeff.CFx); + Update(total.CFy, allBoundCoeff.CFy); + Update(total.CFz, allBoundCoeff.CFz); + Update(total.CMx, allBoundCoeff.CMx); + Update(total.CMy, allBoundCoeff.CMy); + Update(total.CMz, allBoundCoeff.CMz); + Update(total.CoPx, allBoundCoeff.CoPx); + Update(total.CoPy, allBoundCoeff.CoPy); + Update(total.CoPz, allBoundCoeff.CoPz); + Update(total.CT, allBoundCoeff.CT); + Update(total.CQ, allBoundCoeff.CQ); + total.CMerit = total.CT / (total.CQ + EPS); + + /*--- Update the total coefficients per surface (note that all the nodes have the same value)---*/ + + for (unsigned short iMarker_Monitoring = 0; iMarker_Monitoring < config->GetnMarker_Monitoring(); + iMarker_Monitoring++) { + Update(surfaceTotal.CL[iMarker_Monitoring], surfaceCoeff.CL[iMarker_Monitoring]); + Update(surfaceTotal.CD[iMarker_Monitoring], surfaceCoeff.CD[iMarker_Monitoring]); + Update(surfaceTotal.CSF[iMarker_Monitoring], surfaceCoeff.CSF[iMarker_Monitoring]); + surfaceTotal.CEff[iMarker_Monitoring] = + surfaceTotal.CL[iMarker_Monitoring] / (surfaceTotal.CD[iMarker_Monitoring] + EPS); + Update(surfaceTotal.CFx[iMarker_Monitoring], surfaceCoeff.CFx[iMarker_Monitoring]); + Update(surfaceTotal.CFy[iMarker_Monitoring], surfaceCoeff.CFy[iMarker_Monitoring]); + Update(surfaceTotal.CFz[iMarker_Monitoring], surfaceCoeff.CFz[iMarker_Monitoring]); + Update(surfaceTotal.CMx[iMarker_Monitoring], surfaceCoeff.CMx[iMarker_Monitoring]); + Update(surfaceTotal.CMy[iMarker_Monitoring], surfaceCoeff.CMy[iMarker_Monitoring]); + Update(surfaceTotal.CMz[iMarker_Monitoring], surfaceCoeff.CMz[iMarker_Monitoring]); + } +} + +/*! + * \brief Fold one vertex's Force/MomentDist/Coord into the running moment sums. Identical + * formulas are used by Pressure_Forces, Momentum_Forces and Friction_Forces, applied + * respectively to their inviscid, momentum and viscous force/moment sums. + * \param[in] nDim - Number of spatial dimensions (2 or 3). + * \param[in] RefLength - Reference length used to non-dimensionalize the moments. + * \param[in] Force, MomentDist, Coord - This vertex's force, moment arm and position (size MAXNDIM). + * \param[in,out] Moment - Running moment sum about Origin. + * \param[in,out] MomentX_Force, MomentY_Force, MomentZ_Force - Running moment-of-force sums about + * the coordinate axes, used for the center-of-pressure coordinates. + */ +void AccumulateMoment(unsigned short nDim, su2double RefLength, const su2double* Force, + const su2double* MomentDist, const su2double* Coord, su2double* Moment, + su2double* MomentX_Force, su2double* MomentY_Force, su2double* MomentZ_Force) { + if (nDim == 3) { + Moment[0] += (Force[2] * MomentDist[1] - Force[1] * MomentDist[2]) / RefLength; + MomentX_Force[1] += (-Force[1] * Coord[2]); + MomentX_Force[2] += (Force[2] * Coord[1]); + + Moment[1] += (Force[0] * MomentDist[2] - Force[2] * MomentDist[0]) / RefLength; + MomentY_Force[2] += (-Force[2] * Coord[0]); + MomentY_Force[0] += (Force[0] * Coord[2]); + } + Moment[2] += (Force[1] * MomentDist[0] - Force[0] * MomentDist[1]) / RefLength; + MomentZ_Force[0] += (-Force[0] * Coord[1]); + MomentZ_Force[1] += (Force[1] * Coord[0]); +} + +} // namespace + +template +void CFVMFlowSolverBase::Pressure_Forces(const CGeometry* geometry, const CConfig* config) { + SU2_ZONE_SCOPED + + const su2double Alpha = config->GetAoA() * PI_NUMBER / 180.0; + const su2double Beta = config->GetAoS() * PI_NUMBER / 180.0; + /*--- Precomputed once here since ComputeAeroCoeffsFromForceMoment is called once per monitored + * marker, redundantly by every thread (see below). ---*/ + const su2double CosAlpha = cos(Alpha), SinAlpha = sin(Alpha), CosBeta = cos(Beta), SinBeta = sin(Beta); + const su2double RefArea = config->GetRefArea(); + const su2double RefLength = config->GetRefLength(); + auto Origin = config->GetRefOriginMoment(0); + const bool axisymmetric = config->GetAxisymmetric(); - AllBoundInvCoeff.setZero(); + /*--- Variables initialization, and other writes to shared (possibly AD-active) state, + * are confined to the master thread, synchronized with a barrier so that the + * subsequent parallel loop over markers sees consistent, zeroed accumulators. ---*/ - AllBound_CNearFieldOF_Inv = 0.0; + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { + SetReferenceValues(*config); + Total_CNearFieldOF = 0.0; + Total_Heat = 0.0; + Total_MaxHeat = 0.0; + AllBound_CNearFieldOF_Inv = 0.0; + /*--- AeroCoeffs::setZero is not parallel. ---*/ + AllBoundInvCoeff.setZero(); + TotalCoeff.setZero(); + } + END_SU2_OMP_SAFE_GLOBAL_ACCESS SurfaceInvCoeff.setZero(); SurfaceCoeff.setZero(); + InvCoeff.setZero(); - /*--- Loop over the Euler and Navier-Stokes markers ---*/ + SU2_OMP_FOR_STAT(OMP_MIN_SIZE) + for (unsigned long iMarker = 0; iMarker < nMarker; iMarker++) CNearFieldOF_Inv[iMarker] = 0.0; + END_SU2_OMP_FOR - for (iMarker = 0; iMarker < nMarker; iMarker++) { - Boundary = config->GetMarker_All_KindBC(iMarker); - Monitoring = config->GetMarker_All_Monitoring(iMarker); + const su2double factor = 1.0 / AeroCoeffForceRef; - /*--- Obtain the origin for the moment computation for a particular marker ---*/ + /*--- Reference pressure is always the far-field value. ---*/ - if (Monitoring == YES) { - for (iMarker_Monitoring = 0; iMarker_Monitoring < config->GetnMarker_Monitoring(); iMarker_Monitoring++) { - Monitoring_Tag = config->GetMarker_Monitoring_TagBound(iMarker_Monitoring); - Marker_Tag = config->GetMarker_All_TagBound(iMarker); - if (Marker_Tag == Monitoring_Tag) Origin = config->GetRefOriginMoment(iMarker_Monitoring); - } - } + const su2double RefPressure = Pressure_Inf; + + /*--- Loop over the Euler and Navier-Stokes markers. Every thread runs this marker loop + * redundantly; only the per-vertex loop nested inside it is work-shared (SU2_OMP_FOR_STAT). + * Each thread accumulates its own partial force/moment sums and folds them into the shared + * per-marker/AllBound/Surface totals via AddCoeffContribution. CEff/CMerit are nonlinear + * ratios, so they are derived once after the loop, from the fully-reduced totals. ---*/ - if (config->GetSolid_Wall(iMarker) || (Boundary == NEARFIELD_BOUNDARY) || (Boundary == INLET_FLOW) || - (Boundary == OUTLET_FLOW) || (Boundary == ACTDISK_INLET) || (Boundary == ACTDISK_OUTLET) || - (Boundary == ENGINE_INFLOW) || (Boundary == ENGINE_EXHAUST)) { - /*--- Forces initialization at each Marker ---*/ + for (unsigned long iMarker = 0; iMarker < nMarker; iMarker++) { + const auto Boundary = config->GetMarker_All_KindBC(iMarker); - InvCoeff.setZero(iMarker); + if (config->GetSolid_Wall(iMarker) || (Boundary == NEARFIELD_BOUNDARY) || IsMomentumBoundary(Boundary)) { - CNearFieldOF_Inv[iMarker] = 0.0; + /*--- Obtain the origin for the moment computation for a particular marker ---*/ + + const auto Monitoring = config->GetMarker_All_Monitoring(iMarker); + const int iMarker_Monitoring = FindMonitoringIndex(config, iMarker, Monitoring, Origin); su2double ForceInviscid[MAXNDIM] = {0.0}, MomentInviscid[MAXNDIM] = {0.0}; su2double MomentX_Force[MAXNDIM] = {0.0}, MomentY_Force[MAXNDIM] = {0.0}, MomentZ_Force[MAXNDIM] = {0.0}; - NFPressOF = 0.0; + su2double NFPressOF = 0.0; /*--- Loop over the vertices to compute the forces ---*/ - for (iVertex = 0; iVertex < geometry->GetnVertex(iMarker); iVertex++) { - iPoint = geometry->vertex[iMarker][iVertex]->GetNode(); + SU2_OMP_FOR_(schedule(static, OMP_MIN_SIZE) SU2_NOWAIT) + for (unsigned long iVertex = 0; iVertex < geometry->GetnVertex(iMarker); iVertex++) { + const auto iPoint = geometry->vertex[iMarker][iVertex]->GetNode(); - Pressure = nodes->GetPressure(iPoint); + const su2double Pressure = nodes->GetPressure(iPoint); CPressure[iMarker][iVertex] = (Pressure - RefPressure) * factor * RefArea; @@ -1921,8 +2240,8 @@ void CFVMFlowSolverBase::Pressure_Forces(const CGeometry* geometr halo cells (for visualization purposes), but not the forces ---*/ if ((geometry->nodes->GetDomain(iPoint)) && (Monitoring == YES)) { - Normal = geometry->vertex[iMarker][iVertex]->GetNormal(); - Coord = geometry->nodes->GetCoord(iPoint); + const su2double* Normal = geometry->vertex[iMarker][iVertex]->GetNormal(); + const su2double* Coord = geometry->nodes->GetCoord(iPoint); /*--- Quadratic objective function for the near-field. This uses the infinity pressure regardless of Mach number. ---*/ @@ -1930,533 +2249,220 @@ void CFVMFlowSolverBase::Pressure_Forces(const CGeometry* geometr NFPressOF += 0.5 * (Pressure - Pressure_Inf) * (Pressure - Pressure_Inf) * Normal[nDim - 1]; su2double MomentDist[MAXNDIM] = {0.0}; - for (iDim = 0; iDim < nDim; iDim++) { - MomentDist[iDim] = Coord[iDim] - Origin[iDim]; - } + GeometryToolbox::Distance(nDim, Coord, Origin.data(), MomentDist); /*--- Axisymmetric simulations ---*/ - if (axisymmetric) - AxiFactor = 2.0 * PI_NUMBER * geometry->nodes->GetCoord(iPoint, 1); - else - AxiFactor = 1.0; + const su2double AxiFactor = axisymmetric ? su2double(2.0 * PI_NUMBER * geometry->nodes->GetCoord(iPoint, 1)) : su2double(1.0); /*--- Force computation, note the minus sign due to the orientation of the normal (outward) ---*/ su2double Force[MAXNDIM] = {0.0}; - for (iDim = 0; iDim < nDim; iDim++) { + for (unsigned short iDim = 0; iDim < nDim; iDim++) { Force[iDim] = -(Pressure - Pressure_Inf) * Normal[iDim] * factor * AxiFactor; ForceInviscid[iDim] += Force[iDim]; } /*--- Moment with respect to the reference axis ---*/ - if (nDim == 3) { - MomentInviscid[0] += (Force[2] * MomentDist[1] - Force[1] * MomentDist[2]) / RefLength; - MomentX_Force[1] += (-Force[1] * Coord[2]); - MomentX_Force[2] += (Force[2] * Coord[1]); - - MomentInviscid[1] += (Force[0] * MomentDist[2] - Force[2] * MomentDist[0]) / RefLength; - MomentY_Force[2] += (-Force[2] * Coord[0]); - MomentY_Force[0] += (Force[0] * Coord[2]); - } - MomentInviscid[2] += (Force[1] * MomentDist[0] - Force[0] * MomentDist[1]) / RefLength; - MomentZ_Force[0] += (-Force[0] * Coord[1]); - MomentZ_Force[1] += (Force[1] * Coord[0]); + AccumulateMoment(nDim, RefLength, Force, MomentDist, Coord, MomentInviscid, MomentX_Force, MomentY_Force, + MomentZ_Force); } } - - /*--- Project forces and store the non-dimensional coefficients ---*/ + END_SU2_OMP_FOR if (Monitoring == YES) { if (Boundary != NEARFIELD_BOUNDARY) { - if (nDim == 2) { - InvCoeff.CD[iMarker] = ForceInviscid[0] * cos(Alpha) + ForceInviscid[1] * sin(Alpha); - InvCoeff.CL[iMarker] = -ForceInviscid[0] * sin(Alpha) + ForceInviscid[1] * cos(Alpha); - InvCoeff.CEff[iMarker] = InvCoeff.CL[iMarker] / (InvCoeff.CD[iMarker] + EPS); - InvCoeff.CMz[iMarker] = MomentInviscid[2]; - InvCoeff.CoPx[iMarker] = MomentZ_Force[1]; - InvCoeff.CoPy[iMarker] = -MomentZ_Force[0]; - InvCoeff.CFx[iMarker] = ForceInviscid[0]; - InvCoeff.CFy[iMarker] = ForceInviscid[1]; - InvCoeff.CT[iMarker] = -InvCoeff.CFx[iMarker]; - InvCoeff.CQ[iMarker] = -InvCoeff.CMz[iMarker]; - InvCoeff.CMerit[iMarker] = InvCoeff.CT[iMarker] / (InvCoeff.CQ[iMarker] + EPS); - } - if (nDim == 3) { - InvCoeff.CD[iMarker] = ForceInviscid[0] * cos(Alpha) * cos(Beta) + ForceInviscid[1] * sin(Beta) + - ForceInviscid[2] * sin(Alpha) * cos(Beta); - InvCoeff.CL[iMarker] = -ForceInviscid[0] * sin(Alpha) + ForceInviscid[2] * cos(Alpha); - InvCoeff.CSF[iMarker] = -ForceInviscid[0] * sin(Beta) * cos(Alpha) + ForceInviscid[1] * cos(Beta) - - ForceInviscid[2] * sin(Beta) * sin(Alpha); - InvCoeff.CEff[iMarker] = InvCoeff.CL[iMarker] / (InvCoeff.CD[iMarker] + EPS); - InvCoeff.CMx[iMarker] = MomentInviscid[0]; - InvCoeff.CMy[iMarker] = MomentInviscid[1]; - InvCoeff.CMz[iMarker] = MomentInviscid[2]; - InvCoeff.CoPx[iMarker] = -MomentY_Force[0]; - InvCoeff.CoPz[iMarker] = MomentY_Force[2]; - InvCoeff.CFx[iMarker] = ForceInviscid[0]; - InvCoeff.CFy[iMarker] = ForceInviscid[1]; - InvCoeff.CFz[iMarker] = ForceInviscid[2]; - InvCoeff.CT[iMarker] = -InvCoeff.CFz[iMarker]; - InvCoeff.CQ[iMarker] = -InvCoeff.CMz[iMarker]; - InvCoeff.CMerit[iMarker] = InvCoeff.CT[iMarker] / (InvCoeff.CQ[iMarker] + EPS); - } + const auto partial = ComputeAeroCoeffsFromForceMoment( + nDim, CosAlpha, SinAlpha, CosBeta, SinBeta, ForceInviscid, MomentInviscid, MomentX_Force, MomentY_Force, + MomentZ_Force); - AllBoundInvCoeff.CD += InvCoeff.CD[iMarker]; - AllBoundInvCoeff.CL += InvCoeff.CL[iMarker]; - AllBoundInvCoeff.CSF += InvCoeff.CSF[iMarker]; - AllBoundInvCoeff.CEff = AllBoundInvCoeff.CL / (AllBoundInvCoeff.CD + EPS); - AllBoundInvCoeff.CMx += InvCoeff.CMx[iMarker]; - AllBoundInvCoeff.CMy += InvCoeff.CMy[iMarker]; - AllBoundInvCoeff.CMz += InvCoeff.CMz[iMarker]; - AllBoundInvCoeff.CoPx += InvCoeff.CoPx[iMarker]; - AllBoundInvCoeff.CoPy += InvCoeff.CoPy[iMarker]; - AllBoundInvCoeff.CoPz += InvCoeff.CoPz[iMarker]; - AllBoundInvCoeff.CFx += InvCoeff.CFx[iMarker]; - AllBoundInvCoeff.CFy += InvCoeff.CFy[iMarker]; - AllBoundInvCoeff.CFz += InvCoeff.CFz[iMarker]; - AllBoundInvCoeff.CT += InvCoeff.CT[iMarker]; - AllBoundInvCoeff.CQ += InvCoeff.CQ[iMarker]; - AllBoundInvCoeff.CMerit = AllBoundInvCoeff.CT / (AllBoundInvCoeff.CQ + EPS); - - /*--- Compute the coefficients per surface ---*/ - - for (iMarker_Monitoring = 0; iMarker_Monitoring < config->GetnMarker_Monitoring(); iMarker_Monitoring++) { - Monitoring_Tag = config->GetMarker_Monitoring_TagBound(iMarker_Monitoring); - Marker_Tag = config->GetMarker_All_TagBound(iMarker); - if (Marker_Tag == Monitoring_Tag) { - SurfaceInvCoeff.CL[iMarker_Monitoring] += InvCoeff.CL[iMarker]; - SurfaceInvCoeff.CD[iMarker_Monitoring] += InvCoeff.CD[iMarker]; - SurfaceInvCoeff.CSF[iMarker_Monitoring] += InvCoeff.CSF[iMarker]; - SurfaceInvCoeff.CEff[iMarker_Monitoring] = SurfaceInvCoeff.CL[iMarker_Monitoring] / (SurfaceInvCoeff.CD[iMarker_Monitoring] + EPS); - SurfaceInvCoeff.CFx[iMarker_Monitoring] += InvCoeff.CFx[iMarker]; - SurfaceInvCoeff.CFy[iMarker_Monitoring] += InvCoeff.CFy[iMarker]; - SurfaceInvCoeff.CFz[iMarker_Monitoring] += InvCoeff.CFz[iMarker]; - SurfaceInvCoeff.CMx[iMarker_Monitoring] += InvCoeff.CMx[iMarker]; - SurfaceInvCoeff.CMy[iMarker_Monitoring] += InvCoeff.CMy[iMarker]; - SurfaceInvCoeff.CMz[iMarker_Monitoring] += InvCoeff.CMz[iMarker]; - } + AddCoeffContribution(iMarker, iMarker_Monitoring, partial, InvCoeff, AllBoundInvCoeff, SurfaceInvCoeff); + } else { + /*--- At the Nearfield SU2 only cares about the pressure coeffient ---*/ + SU2_OMP_CRITICAL { + CNearFieldOF_Inv[iMarker] += NFPressOF; + AllBound_CNearFieldOF_Inv += NFPressOF; } - - } - - /*--- At the Nearfield SU2 only cares about the pressure coeffient ---*/ - - else { - CNearFieldOF_Inv[iMarker] = NFPressOF; - AllBound_CNearFieldOF_Inv += CNearFieldOF_Inv[iMarker]; + END_SU2_OMP_CRITICAL } } } } + /*--- For the SU2_NOWAIT in the vertex loop. ---*/ + SU2_OMP_BARRIER -#ifdef HAVE_MPI - - /*--- Add AllBound information using all the nodes ---*/ - - if (config->GetComm_Level() == COMM_FULL) { - auto Allreduce = [](su2double x) { - su2double tmp = x; - x = 0.0; - SU2_MPI::Allreduce(&tmp, &x, 1, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); - return x; - }; - AllBoundInvCoeff.CD = Allreduce(AllBoundInvCoeff.CD); - AllBoundInvCoeff.CL = Allreduce(AllBoundInvCoeff.CL); - AllBoundInvCoeff.CSF = Allreduce(AllBoundInvCoeff.CSF); - AllBoundInvCoeff.CEff = AllBoundInvCoeff.CL / (AllBoundInvCoeff.CD + EPS); - - AllBoundInvCoeff.CMx = Allreduce(AllBoundInvCoeff.CMx); - AllBoundInvCoeff.CMy = Allreduce(AllBoundInvCoeff.CMy); - AllBoundInvCoeff.CMz = Allreduce(AllBoundInvCoeff.CMz); - - AllBoundInvCoeff.CoPx = Allreduce(AllBoundInvCoeff.CoPx); - AllBoundInvCoeff.CoPy = Allreduce(AllBoundInvCoeff.CoPy); - AllBoundInvCoeff.CoPz = Allreduce(AllBoundInvCoeff.CoPz); - - AllBoundInvCoeff.CFx = Allreduce(AllBoundInvCoeff.CFx); - AllBoundInvCoeff.CFy = Allreduce(AllBoundInvCoeff.CFy); - AllBoundInvCoeff.CFz = Allreduce(AllBoundInvCoeff.CFz); + /*--- Derive the (nonlinear) per-marker, AllBound and Surface ratio coefficients from the + * now fully-reduced totals. This must happen once, after every thread has finished + * folding its partial contributions above (guaranteed by the barrier at the start of + * the safe-global-access section). ---*/ - AllBoundInvCoeff.CT = Allreduce(AllBoundInvCoeff.CT); - AllBoundInvCoeff.CQ = Allreduce(AllBoundInvCoeff.CQ); - AllBoundInvCoeff.CMerit = AllBoundInvCoeff.CT / (AllBoundInvCoeff.CQ + EPS); - AllBound_CNearFieldOF_Inv = Allreduce(AllBound_CNearFieldOF_Inv); + SU2_OMP_FOR_(schedule(static, OMP_MIN_SIZE) SU2_NOWAIT) + for (unsigned long iMarker = 0; iMarker < nMarker; iMarker++) { + const auto Boundary = config->GetMarker_All_KindBC(iMarker); + const auto Monitoring = config->GetMarker_All_Monitoring(iMarker); + if (Monitoring == YES && Boundary != NEARFIELD_BOUNDARY) { + InvCoeff.CEff[iMarker] = InvCoeff.CL[iMarker] / (InvCoeff.CD[iMarker] + EPS); + InvCoeff.CMerit[iMarker] = InvCoeff.CT[iMarker] / (InvCoeff.CQ[iMarker] + EPS); + } } + END_SU2_OMP_FOR - /*--- Add the forces on the surfaces using all the nodes ---*/ - - if (config->GetComm_Level() == COMM_FULL) { - int nMarkerMon = config->GetnMarker_Monitoring(); - - /*--- Use the same buffer for all reductions. We could avoid the copy back into - * the original variable by swaping pointers, but it is safer this way... ---*/ - - su2double* buffer = new su2double[nMarkerMon]; - - auto Allreduce_inplace = [buffer](int size, su2double* x) { - SU2_MPI::Allreduce(x, buffer, size, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); - for (int i = 0; i < size; ++i) x[i] = buffer[i]; - }; - - Allreduce_inplace(nMarkerMon, SurfaceInvCoeff.CL); - Allreduce_inplace(nMarkerMon, SurfaceInvCoeff.CD); - Allreduce_inplace(nMarkerMon, SurfaceInvCoeff.CSF); - - for (iMarker_Monitoring = 0; iMarker_Monitoring < nMarkerMon; iMarker_Monitoring++) - SurfaceInvCoeff.CEff[iMarker_Monitoring] = - SurfaceInvCoeff.CL[iMarker_Monitoring] / (SurfaceInvCoeff.CD[iMarker_Monitoring] + EPS); - - Allreduce_inplace(nMarkerMon, SurfaceInvCoeff.CFx); - Allreduce_inplace(nMarkerMon, SurfaceInvCoeff.CFy); - Allreduce_inplace(nMarkerMon, SurfaceInvCoeff.CFz); - - Allreduce_inplace(nMarkerMon, SurfaceInvCoeff.CMx); - Allreduce_inplace(nMarkerMon, SurfaceInvCoeff.CMy); - Allreduce_inplace(nMarkerMon, SurfaceInvCoeff.CMz); - - delete[] buffer; + SU2_OMP_FOR_(schedule(static, OMP_MIN_SIZE) SU2_NOWAIT) + for (unsigned short iMarker_Monitoring = 0; iMarker_Monitoring < config->GetnMarker_Monitoring(); + iMarker_Monitoring++) { + SurfaceInvCoeff.CEff[iMarker_Monitoring] = + SurfaceInvCoeff.CL[iMarker_Monitoring] / (SurfaceInvCoeff.CD[iMarker_Monitoring] + EPS); } + END_SU2_OMP_FOR -#endif + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { + AllBoundInvCoeff.CEff = AllBoundInvCoeff.CL / (AllBoundInvCoeff.CD + EPS); + AllBoundInvCoeff.CMerit = AllBoundInvCoeff.CT / (AllBoundInvCoeff.CQ + EPS); - /*--- Update the total coefficients (note that all the nodes have the same value) ---*/ - - TotalCoeff.CD = AllBoundInvCoeff.CD; - TotalCoeff.CL = AllBoundInvCoeff.CL; - TotalCoeff.CSF = AllBoundInvCoeff.CSF; - TotalCoeff.CEff = TotalCoeff.CL / (TotalCoeff.CD + EPS); - TotalCoeff.CFx = AllBoundInvCoeff.CFx; - TotalCoeff.CFy = AllBoundInvCoeff.CFy; - TotalCoeff.CFz = AllBoundInvCoeff.CFz; - TotalCoeff.CMx = AllBoundInvCoeff.CMx; - TotalCoeff.CMy = AllBoundInvCoeff.CMy; - TotalCoeff.CMz = AllBoundInvCoeff.CMz; - TotalCoeff.CoPx = AllBoundInvCoeff.CoPx; - TotalCoeff.CoPy = AllBoundInvCoeff.CoPy; - TotalCoeff.CoPz = AllBoundInvCoeff.CoPz; - TotalCoeff.CT = AllBoundInvCoeff.CT; - TotalCoeff.CQ = AllBoundInvCoeff.CQ; - TotalCoeff.CMerit = TotalCoeff.CT / (TotalCoeff.CQ + EPS); - Total_CNearFieldOF = AllBound_CNearFieldOF_Inv; + ReduceCoeffsMPI(config, AllBoundInvCoeff, SurfaceInvCoeff); - /*--- Update the total coefficients per surface (note that all the nodes have the same value)---*/ + /*--- AllBound_CNearFieldOF_Inv, not covered by ReduceCoeffsMPI, is reduced separately. ---*/ + if (config->GetComm_Level() == COMM_FULL) { + AllBound_CNearFieldOF_Inv = MPIReduceSum(AllBound_CNearFieldOF_Inv); + } - for (iMarker_Monitoring = 0; iMarker_Monitoring < config->GetnMarker_Monitoring(); iMarker_Monitoring++) { - SurfaceCoeff.CL[iMarker_Monitoring] = SurfaceInvCoeff.CL[iMarker_Monitoring]; - SurfaceCoeff.CD[iMarker_Monitoring] = SurfaceInvCoeff.CD[iMarker_Monitoring]; - SurfaceCoeff.CSF[iMarker_Monitoring] = SurfaceInvCoeff.CSF[iMarker_Monitoring]; - SurfaceCoeff.CEff[iMarker_Monitoring] = - SurfaceCoeff.CL[iMarker_Monitoring] / (SurfaceCoeff.CD[iMarker_Monitoring] + EPS); - SurfaceCoeff.CFx[iMarker_Monitoring] = SurfaceInvCoeff.CFx[iMarker_Monitoring]; - SurfaceCoeff.CFy[iMarker_Monitoring] = SurfaceInvCoeff.CFy[iMarker_Monitoring]; - SurfaceCoeff.CFz[iMarker_Monitoring] = SurfaceInvCoeff.CFz[iMarker_Monitoring]; - SurfaceCoeff.CMx[iMarker_Monitoring] = SurfaceInvCoeff.CMx[iMarker_Monitoring]; - SurfaceCoeff.CMy[iMarker_Monitoring] = SurfaceInvCoeff.CMy[iMarker_Monitoring]; - SurfaceCoeff.CMz[iMarker_Monitoring] = SurfaceInvCoeff.CMz[iMarker_Monitoring]; + AccumulateTotalCoeffs(config, AllBoundInvCoeff, SurfaceInvCoeff, TotalCoeff, SurfaceCoeff, /*overwrite=*/true); + Total_CNearFieldOF = AllBound_CNearFieldOF_Inv; } + END_SU2_OMP_SAFE_GLOBAL_ACCESS } template void CFVMFlowSolverBase::Momentum_Forces(const CGeometry* geometry, const CConfig* config) { - unsigned long iVertex, iPoint; - unsigned short iDim, iMarker, Boundary, Monitoring, iMarker_Monitoring; - su2double MassFlow, Density; - const su2double *Normal = nullptr, *Coord = nullptr; - string Marker_Tag, Monitoring_Tag; - su2double AxiFactor; - - su2double Alpha = config->GetAoA() * PI_NUMBER / 180.0; - su2double Beta = config->GetAoS() * PI_NUMBER / 180.0; - su2double RefLength = config->GetRefLength(); + SU2_ZONE_SCOPED + + const su2double Alpha = config->GetAoA() * PI_NUMBER / 180.0; + const su2double Beta = config->GetAoS() * PI_NUMBER / 180.0; + const su2double CosAlpha = cos(Alpha), SinAlpha = sin(Alpha), CosBeta = cos(Beta), SinBeta = sin(Beta); + const su2double RefLength = config->GetRefLength(); auto Origin = config->GetRefOriginMoment(0); - bool axisymmetric = config->GetAxisymmetric(); + const bool axisymmetric = config->GetAxisymmetric(); const su2double factor = 1.0 / AeroCoeffForceRef; - /*-- Variables initialization ---*/ - - AllBoundMntCoeff.setZero(); + SU2_OMP_SAFE_GLOBAL_ACCESS(AllBoundMntCoeff.setZero();) SurfaceMntCoeff.setZero(); + MntCoeff.setZero(); - /*--- Loop over the Inlet -Outlet Markers ---*/ - - for (iMarker = 0; iMarker < nMarker; iMarker++) { - Boundary = config->GetMarker_All_KindBC(iMarker); - Monitoring = config->GetMarker_All_Monitoring(iMarker); - - /*--- Obtain the origin for the moment computation for a particular marker ---*/ - - if (Monitoring == YES) { - for (iMarker_Monitoring = 0; iMarker_Monitoring < config->GetnMarker_Monitoring(); iMarker_Monitoring++) { - Monitoring_Tag = config->GetMarker_Monitoring_TagBound(iMarker_Monitoring); - Marker_Tag = config->GetMarker_All_TagBound(iMarker); - if (Marker_Tag == Monitoring_Tag) Origin = config->GetRefOriginMoment(iMarker_Monitoring); - } - } + /*--- Loop over the Inlet-Outlet Markers (see Pressure_Forces for how the parallel + * reduction over threads is organized). ---*/ - if ((Boundary == INLET_FLOW) || (Boundary == OUTLET_FLOW) || (Boundary == ACTDISK_INLET) || - (Boundary == ACTDISK_OUTLET) || (Boundary == ENGINE_INFLOW) || (Boundary == ENGINE_EXHAUST)) { - /*--- Forces initialization at each Marker ---*/ + for (unsigned long iMarker = 0; iMarker < nMarker; iMarker++) { + const auto Boundary = config->GetMarker_All_KindBC(iMarker); + if (!IsMomentumBoundary(Boundary)) continue; - MntCoeff.setZero(iMarker); + const auto Monitoring = config->GetMarker_All_Monitoring(iMarker); - su2double ForceMomentum[MAXNDIM] = {0.0}, MomentMomentum[MAXNDIM] = {0.0}; - su2double MomentX_Force[3] = {0.0}, MomentY_Force[3] = {0.0}, MomentZ_Force[3] = {0.0}; + /*--- Obtain the origin for the moment computation for a particular marker ---*/ - /*--- Loop over the vertices to compute the forces ---*/ + const int iMarker_Monitoring = FindMonitoringIndex(config, iMarker, Monitoring, Origin); - for (iVertex = 0; iVertex < geometry->GetnVertex(iMarker); iVertex++) { - iPoint = geometry->vertex[iMarker][iVertex]->GetNode(); + su2double ForceMomentum[MAXNDIM] = {0.0}, MomentMomentum[MAXNDIM] = {0.0}; + su2double MomentX_Force[3] = {0.0}, MomentY_Force[3] = {0.0}, MomentZ_Force[3] = {0.0}; - /*--- Note that the pressure coefficient is computed at the - halo cells (for visualization purposes), but not the forces ---*/ + /*--- Loop over the vertices to compute the forces (work-shared across threads, see + * Pressure_Forces for why the chunk size is computed and the barrier skipped). ---*/ - if ((geometry->nodes->GetDomain(iPoint)) && (Monitoring == YES)) { - Normal = geometry->vertex[iMarker][iVertex]->GetNormal(); - Coord = geometry->nodes->GetCoord(iPoint); - Density = nodes->GetDensity(iPoint); - MassFlow = 0.0; - su2double Velocity[MAXNDIM] = {0.0}, MomentDist[MAXNDIM] = {0.0}; - for (iDim = 0; iDim < nDim; iDim++) { - Velocity[iDim] = nodes->GetVelocity(iPoint, iDim); - MomentDist[iDim] = Coord[iDim] - Origin[iDim]; - MassFlow -= Normal[iDim] * Velocity[iDim] * Density; - } - - /*--- Axisymmetric simulations ---*/ + SU2_OMP_FOR_(schedule(static, OMP_MIN_SIZE) SU2_NOWAIT) + for (unsigned long iVertex = 0; iVertex < geometry->GetnVertex(iMarker); iVertex++) { + const auto iPoint = geometry->vertex[iMarker][iVertex]->GetNode(); - if (axisymmetric) - AxiFactor = 2.0 * PI_NUMBER * geometry->nodes->GetCoord(iPoint, 1); - else - AxiFactor = 1.0; + /*--- Note that the pressure coefficient is computed at the + halo cells (for visualization purposes), but not the forces ---*/ - /*--- Force computation, note the minus sign due to the - orientation of the normal (outward) ---*/ + if ((geometry->nodes->GetDomain(iPoint)) && (Monitoring == YES)) { + const su2double* Normal = geometry->vertex[iMarker][iVertex]->GetNormal(); + const su2double* Coord = geometry->nodes->GetCoord(iPoint); + const su2double Density = nodes->GetDensity(iPoint); + su2double MassFlow = 0.0; + su2double Velocity[MAXNDIM] = {0.0}, MomentDist[MAXNDIM] = {0.0}; + for (unsigned short iDim = 0; iDim < nDim; iDim++) { + Velocity[iDim] = nodes->GetVelocity(iPoint, iDim); + MomentDist[iDim] = Coord[iDim] - Origin[iDim]; + MassFlow -= Normal[iDim] * Velocity[iDim] * Density; + } - su2double Force[MAXNDIM] = {0.0}; - for (iDim = 0; iDim < nDim; iDim++) { - Force[iDim] = MassFlow * Velocity[iDim] * factor * AxiFactor; - ForceMomentum[iDim] += Force[iDim]; - } + /*--- Axisymmetric simulations ---*/ - /*--- Moment with respect to the reference axis ---*/ + const su2double AxiFactor = axisymmetric ? su2double(2.0 * PI_NUMBER * geometry->nodes->GetCoord(iPoint, 1)) : su2double(1.0); - if (nDim == 3) { - MomentMomentum[0] += (Force[2] * MomentDist[1] - Force[1] * MomentDist[2]) / RefLength; - MomentX_Force[1] += (-Force[1] * Coord[2]); - MomentX_Force[2] += (Force[2] * Coord[1]); + /*--- Force computation, note the minus sign due to the + orientation of the normal (outward) ---*/ - MomentMomentum[1] += (Force[0] * MomentDist[2] - Force[2] * MomentDist[0]) / RefLength; - MomentY_Force[2] += (-Force[2] * Coord[0]); - MomentY_Force[0] += (Force[0] * Coord[2]); - } - MomentMomentum[2] += (Force[1] * MomentDist[0] - Force[0] * MomentDist[1]) / RefLength; - MomentZ_Force[0] += (-Force[0] * Coord[1]); - MomentZ_Force[1] += (Force[1] * Coord[0]); + su2double Force[MAXNDIM] = {0.0}; + for (unsigned short iDim = 0; iDim < nDim; iDim++) { + Force[iDim] = MassFlow * Velocity[iDim] * factor * AxiFactor; + ForceMomentum[iDim] += Force[iDim]; } - } - /*--- Project forces and store the non-dimensional coefficients ---*/ - - if (Monitoring == YES) { - if (nDim == 2) { - MntCoeff.CD[iMarker] = ForceMomentum[0] * cos(Alpha) + ForceMomentum[1] * sin(Alpha); - MntCoeff.CL[iMarker] = -ForceMomentum[0] * sin(Alpha) + ForceMomentum[1] * cos(Alpha); - MntCoeff.CEff[iMarker] = MntCoeff.CL[iMarker] / (MntCoeff.CD[iMarker] + EPS); - MntCoeff.CFx[iMarker] = ForceMomentum[0]; - MntCoeff.CFy[iMarker] = ForceMomentum[1]; - MntCoeff.CMz[iMarker] = MomentMomentum[2]; - MntCoeff.CoPx[iMarker] = MomentZ_Force[1]; - MntCoeff.CoPy[iMarker] = -MomentZ_Force[0]; - MntCoeff.CT[iMarker] = -MntCoeff.CFx[iMarker]; - MntCoeff.CQ[iMarker] = -MntCoeff.CMz[iMarker]; - MntCoeff.CMerit[iMarker] = MntCoeff.CT[iMarker] / (MntCoeff.CQ[iMarker] + EPS); - } - if (nDim == 3) { - MntCoeff.CD[iMarker] = ForceMomentum[0] * cos(Alpha) * cos(Beta) + ForceMomentum[1] * sin(Beta) + - ForceMomentum[2] * sin(Alpha) * cos(Beta); - MntCoeff.CL[iMarker] = -ForceMomentum[0] * sin(Alpha) + ForceMomentum[2] * cos(Alpha); - MntCoeff.CSF[iMarker] = -ForceMomentum[0] * sin(Beta) * cos(Alpha) + ForceMomentum[1] * cos(Beta) - - ForceMomentum[2] * sin(Beta) * sin(Alpha); - MntCoeff.CEff[iMarker] = MntCoeff.CL[iMarker] / (MntCoeff.CD[iMarker] + EPS); - MntCoeff.CFx[iMarker] = ForceMomentum[0]; - MntCoeff.CFy[iMarker] = ForceMomentum[1]; - MntCoeff.CFz[iMarker] = ForceMomentum[2]; - MntCoeff.CMx[iMarker] = MomentMomentum[0]; - MntCoeff.CMy[iMarker] = MomentMomentum[1]; - MntCoeff.CMz[iMarker] = MomentMomentum[2]; - MntCoeff.CoPx[iMarker] = -MomentY_Force[0]; - MntCoeff.CoPz[iMarker] = MomentY_Force[2]; - MntCoeff.CT[iMarker] = -MntCoeff.CFz[iMarker]; - MntCoeff.CQ[iMarker] = -MntCoeff.CMz[iMarker]; - MntCoeff.CMerit[iMarker] = MntCoeff.CT[iMarker] / (MntCoeff.CQ[iMarker] + EPS); - } + /*--- Moment with respect to the reference axis ---*/ - AllBoundMntCoeff.CD += MntCoeff.CD[iMarker]; - AllBoundMntCoeff.CL += MntCoeff.CL[iMarker]; - AllBoundMntCoeff.CSF += MntCoeff.CSF[iMarker]; - AllBoundMntCoeff.CEff = AllBoundMntCoeff.CL / (AllBoundMntCoeff.CD + EPS); - AllBoundMntCoeff.CFx += MntCoeff.CFx[iMarker]; - AllBoundMntCoeff.CFy += MntCoeff.CFy[iMarker]; - AllBoundMntCoeff.CFz += MntCoeff.CFz[iMarker]; - AllBoundMntCoeff.CMx += MntCoeff.CMx[iMarker]; - AllBoundMntCoeff.CMy += MntCoeff.CMy[iMarker]; - AllBoundMntCoeff.CMx += MntCoeff.CMz[iMarker]; - AllBoundMntCoeff.CoPx += MntCoeff.CoPx[iMarker]; - AllBoundMntCoeff.CoPy += MntCoeff.CoPy[iMarker]; - AllBoundMntCoeff.CoPz += MntCoeff.CoPz[iMarker]; - AllBoundMntCoeff.CT += MntCoeff.CT[iMarker]; - AllBoundMntCoeff.CQ += MntCoeff.CQ[iMarker]; - AllBoundMntCoeff.CMerit += AllBoundMntCoeff.CT / (AllBoundMntCoeff.CQ + EPS); - - /*--- Compute the coefficients per surface ---*/ - - for (iMarker_Monitoring = 0; iMarker_Monitoring < config->GetnMarker_Monitoring(); iMarker_Monitoring++) { - Monitoring_Tag = config->GetMarker_Monitoring_TagBound(iMarker_Monitoring); - Marker_Tag = config->GetMarker_All_TagBound(iMarker); - if (Marker_Tag == Monitoring_Tag) { - SurfaceMntCoeff.CL[iMarker_Monitoring] += MntCoeff.CL[iMarker]; - SurfaceMntCoeff.CD[iMarker_Monitoring] += MntCoeff.CD[iMarker]; - SurfaceMntCoeff.CSF[iMarker_Monitoring] += MntCoeff.CSF[iMarker]; - SurfaceMntCoeff.CEff[iMarker_Monitoring] = SurfaceMntCoeff.CL[iMarker_Monitoring] / (SurfaceMntCoeff.CD[iMarker_Monitoring] + EPS); - SurfaceMntCoeff.CFx[iMarker_Monitoring] += MntCoeff.CFx[iMarker]; - SurfaceMntCoeff.CFy[iMarker_Monitoring] += MntCoeff.CFy[iMarker]; - SurfaceMntCoeff.CFz[iMarker_Monitoring] += MntCoeff.CFz[iMarker]; - SurfaceMntCoeff.CMx[iMarker_Monitoring] += MntCoeff.CMx[iMarker]; - SurfaceMntCoeff.CMy[iMarker_Monitoring] += MntCoeff.CMy[iMarker]; - SurfaceMntCoeff.CMz[iMarker_Monitoring] += MntCoeff.CMz[iMarker]; - } - } + AccumulateMoment(nDim, RefLength, Force, MomentDist, Coord, MomentMomentum, MomentX_Force, MomentY_Force, + MomentZ_Force); } } - } - -#ifdef HAVE_MPI - - /*--- Add AllBound information using all the nodes ---*/ - - if (config->GetComm_Level() == COMM_FULL) { - auto Allreduce = [](su2double x) { - su2double tmp = x; - x = 0.0; - SU2_MPI::Allreduce(&tmp, &x, 1, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); - return x; - }; - - AllBoundMntCoeff.CD = Allreduce(AllBoundMntCoeff.CD); - AllBoundMntCoeff.CL = Allreduce(AllBoundMntCoeff.CL); - AllBoundMntCoeff.CSF = Allreduce(AllBoundMntCoeff.CSF); - AllBoundMntCoeff.CEff = AllBoundMntCoeff.CL / (AllBoundMntCoeff.CD + EPS); - - AllBoundMntCoeff.CFx = Allreduce(AllBoundMntCoeff.CFx); - AllBoundMntCoeff.CFy = Allreduce(AllBoundMntCoeff.CFy); - AllBoundMntCoeff.CFz = Allreduce(AllBoundMntCoeff.CFz); - - AllBoundMntCoeff.CMx = Allreduce(AllBoundMntCoeff.CMx); - AllBoundMntCoeff.CMy = Allreduce(AllBoundMntCoeff.CMy); - AllBoundMntCoeff.CMz = Allreduce(AllBoundMntCoeff.CMz); + END_SU2_OMP_FOR - AllBoundMntCoeff.CoPx = Allreduce(AllBoundMntCoeff.CoPx); - AllBoundMntCoeff.CoPy = Allreduce(AllBoundMntCoeff.CoPy); - AllBoundMntCoeff.CoPz = Allreduce(AllBoundMntCoeff.CoPz); + if (Monitoring == YES) { + const auto partial = ComputeAeroCoeffsFromForceMoment( + nDim, CosAlpha, SinAlpha, CosBeta, SinBeta, ForceMomentum, MomentMomentum, MomentX_Force, MomentY_Force, + MomentZ_Force); - AllBoundMntCoeff.CT = Allreduce(AllBoundMntCoeff.CT); - AllBoundMntCoeff.CQ = Allreduce(AllBoundMntCoeff.CQ); - AllBoundMntCoeff.CMerit = AllBoundMntCoeff.CT / (AllBoundMntCoeff.CQ + EPS); + AddCoeffContribution(iMarker, iMarker_Monitoring, partial, MntCoeff, AllBoundMntCoeff, SurfaceMntCoeff); + } } + /*--- For the SU2_NOWAIT in the vertex loop. ---*/ + SU2_OMP_BARRIER - /*--- Add the forces on the surfaces using all the nodes ---*/ - - if (config->GetComm_Level() == COMM_FULL) { - int nMarkerMon = config->GetnMarker_Monitoring(); - - /*--- Use the same buffer for all reductions. We could avoid the copy back into - * the original variable by swaping pointers, but it is safer this way... ---*/ - - su2double* buffer = new su2double[nMarkerMon]; + /*--- Derive the ratio coefficients from the fully-reduced totals, once. ---*/ - auto Allreduce_inplace = [buffer](int size, su2double* x) { - SU2_MPI::Allreduce(x, buffer, size, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); - for (int i = 0; i < size; ++i) x[i] = buffer[i]; - }; - - Allreduce_inplace(nMarkerMon, SurfaceMntCoeff.CL); - Allreduce_inplace(nMarkerMon, SurfaceMntCoeff.CD); - Allreduce_inplace(nMarkerMon, SurfaceMntCoeff.CSF); - - for (iMarker_Monitoring = 0; iMarker_Monitoring < nMarkerMon; iMarker_Monitoring++) - SurfaceMntCoeff.CEff[iMarker_Monitoring] = - SurfaceMntCoeff.CL[iMarker_Monitoring] / (SurfaceMntCoeff.CD[iMarker_Monitoring] + EPS); - - Allreduce_inplace(nMarkerMon, SurfaceMntCoeff.CFx); - Allreduce_inplace(nMarkerMon, SurfaceMntCoeff.CFy); - Allreduce_inplace(nMarkerMon, SurfaceMntCoeff.CFz); - - Allreduce_inplace(nMarkerMon, SurfaceMntCoeff.CMx); - Allreduce_inplace(nMarkerMon, SurfaceMntCoeff.CMy); - Allreduce_inplace(nMarkerMon, SurfaceMntCoeff.CMz); - - delete[] buffer; + SU2_OMP_FOR_(schedule(static, OMP_MIN_SIZE) SU2_NOWAIT) + for (unsigned long iMarker = 0; iMarker < nMarker; iMarker++) { + const auto Boundary = config->GetMarker_All_KindBC(iMarker); + const auto Monitoring = config->GetMarker_All_Monitoring(iMarker); + if (Monitoring == YES && IsMomentumBoundary(Boundary)) { + MntCoeff.CEff[iMarker] = MntCoeff.CL[iMarker] / (MntCoeff.CD[iMarker] + EPS); + MntCoeff.CMerit[iMarker] = MntCoeff.CT[iMarker] / (MntCoeff.CQ[iMarker] + EPS); + } } + END_SU2_OMP_FOR -#endif - - /*--- Update the total coefficients (note that all the nodes have the same value) ---*/ - - TotalCoeff.CD += AllBoundMntCoeff.CD; - TotalCoeff.CL += AllBoundMntCoeff.CL; - TotalCoeff.CSF += AllBoundMntCoeff.CSF; - TotalCoeff.CEff = TotalCoeff.CL / (TotalCoeff.CD + EPS); - TotalCoeff.CFx += AllBoundMntCoeff.CFx; - TotalCoeff.CFy += AllBoundMntCoeff.CFy; - TotalCoeff.CFz += AllBoundMntCoeff.CFz; - TotalCoeff.CMx += AllBoundMntCoeff.CMx; - TotalCoeff.CMy += AllBoundMntCoeff.CMy; - TotalCoeff.CMz += AllBoundMntCoeff.CMz; - TotalCoeff.CoPx += AllBoundMntCoeff.CoPx; - TotalCoeff.CoPy += AllBoundMntCoeff.CoPy; - TotalCoeff.CoPz += AllBoundMntCoeff.CoPz; - TotalCoeff.CT += AllBoundMntCoeff.CT; - TotalCoeff.CQ += AllBoundMntCoeff.CQ; - TotalCoeff.CMerit = TotalCoeff.CT / (TotalCoeff.CQ + EPS); + SU2_OMP_FOR_(schedule(static, OMP_MIN_SIZE) SU2_NOWAIT) + for (unsigned short iMarker_Monitoring = 0; iMarker_Monitoring < config->GetnMarker_Monitoring(); + iMarker_Monitoring++) { + SurfaceMntCoeff.CEff[iMarker_Monitoring] = + SurfaceMntCoeff.CL[iMarker_Monitoring] / (SurfaceMntCoeff.CD[iMarker_Monitoring] + EPS); + } + END_SU2_OMP_FOR - /*--- Update the total coefficients per surface (note that all the nodes have the same value)---*/ + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { + AllBoundMntCoeff.CEff = AllBoundMntCoeff.CL / (AllBoundMntCoeff.CD + EPS); + AllBoundMntCoeff.CMerit = AllBoundMntCoeff.CT / (AllBoundMntCoeff.CQ + EPS); - for (iMarker_Monitoring = 0; iMarker_Monitoring < config->GetnMarker_Monitoring(); iMarker_Monitoring++) { - SurfaceCoeff.CL[iMarker_Monitoring] += SurfaceMntCoeff.CL[iMarker_Monitoring]; - SurfaceCoeff.CD[iMarker_Monitoring] += SurfaceMntCoeff.CD[iMarker_Monitoring]; - SurfaceCoeff.CSF[iMarker_Monitoring] += SurfaceMntCoeff.CSF[iMarker_Monitoring]; - SurfaceCoeff.CEff[iMarker_Monitoring] = - SurfaceCoeff.CL[iMarker_Monitoring] / (SurfaceCoeff.CD[iMarker_Monitoring] + EPS); - SurfaceCoeff.CFx[iMarker_Monitoring] += SurfaceMntCoeff.CFx[iMarker_Monitoring]; - SurfaceCoeff.CFy[iMarker_Monitoring] += SurfaceMntCoeff.CFy[iMarker_Monitoring]; - SurfaceCoeff.CFz[iMarker_Monitoring] += SurfaceMntCoeff.CFz[iMarker_Monitoring]; - SurfaceCoeff.CMx[iMarker_Monitoring] += SurfaceMntCoeff.CMx[iMarker_Monitoring]; - SurfaceCoeff.CMy[iMarker_Monitoring] += SurfaceMntCoeff.CMy[iMarker_Monitoring]; - SurfaceCoeff.CMz[iMarker_Monitoring] += SurfaceMntCoeff.CMz[iMarker_Monitoring]; + ReduceCoeffsMPI(config, AllBoundMntCoeff, SurfaceMntCoeff); + AccumulateTotalCoeffs(config, AllBoundMntCoeff, SurfaceMntCoeff, TotalCoeff, SurfaceCoeff, /*overwrite=*/false); } + END_SU2_OMP_SAFE_GLOBAL_ACCESS } template void CFVMFlowSolverBase::Friction_Forces(const CGeometry* geometry, const CConfig* config) { - /// TODO: Major cleanup needed. - + SU2_ZONE_SCOPED if (!config->GetViscous()) return; - unsigned long iVertex, iPoint, iPointNormal; - unsigned short iMarker, iMarker_Monitoring, iDim, jDim; - su2double Viscosity = 0.0, Area, Density = 0.0, FrictionVel, - UnitNormal[3] = {0.0}, TauElem[3] = {0.0}, Tau[3][3] = {{0.0}}, - thermal_conductivity, MaxNorm = 8.0, Grad_Vel[3][3] = {{0.0}}, Grad_Temp[3] = {0.0}, - Grad_Temp_ve[3] = {0.0}, AxiFactor; - const su2double *Coord = nullptr, *Coord_Normal = nullptr, *Normal = nullptr; + constexpr int MaxNorm = 8; const su2double minYPlus = config->GetwallModel_MinYPlus(); const su2double Alpha = config->GetAoA() * PI_NUMBER / 180.0; const su2double Beta = config->GetAoS() * PI_NUMBER / 180.0; + const su2double CosAlpha = cos(Alpha), SinAlpha = sin(Alpha), CosBeta = cos(Beta), SinBeta = sin(Beta); const su2double RefLength = config->GetRefLength(); const su2double RefHeatFlux = config->GetHeat_Flux_Ref(); const su2double RefTemperature = config->GetTemperature_Ref(); @@ -2473,86 +2479,115 @@ void CFVMFlowSolverBase::Friction_Forces(const CGeometry* geometr /*--- Variables initialization ---*/ - AllBoundViscCoeff.setZero(); - SurfaceViscCoeff.setZero(); + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { + AllBound_HF_Visc = 0.0; + AllBound_MaxHF_Visc = 0.0; + AllBoundViscCoeff.setZero(); + } + END_SU2_OMP_SAFE_GLOBAL_ACCESS - AllBound_HF_Visc = 0.0; - AllBound_MaxHF_Visc = 0.0; + SurfaceViscCoeff.setZero(); - for (iMarker_Monitoring = 0; iMarker_Monitoring < config->GetnMarker_Monitoring(); iMarker_Monitoring++) { + SU2_OMP_FOR_(schedule(static, OMP_MIN_SIZE) SU2_NOWAIT) + for (unsigned short iMarker_Monitoring = 0; iMarker_Monitoring < config->GetnMarker_Monitoring(); + iMarker_Monitoring++) { Surface_HF_Visc[iMarker_Monitoring] = 0.0; Surface_MaxHF_Visc[iMarker_Monitoring] = 0.0; } + END_SU2_OMP_FOR - /*--- Loop over the Navier-Stokes markers ---*/ + SU2_OMP_FOR_STAT(OMP_MIN_SIZE) + for (unsigned long iMarker = 0; iMarker < nMarker; iMarker++) { + if (!config->GetViscous_Wall(iMarker)) continue; + ViscCoeff.setZero(iMarker); + HF_Visc[iMarker] = 0.0; + MaxHF_Visc[iMarker] = 0.0; + } + END_SU2_OMP_FOR + + /*--- Loop over the Navier-Stokes markers (see Pressure_Forces for how the parallel + * reduction over threads is organized). The per-vertex loop below is the expensive + * part (stress-tensor and heat-flux evaluations) and is work-shared across threads. ---*/ - for (iMarker = 0; iMarker < nMarker; iMarker++) { + for (unsigned long iMarker = 0; iMarker < nMarker; iMarker++) { if (!config->GetViscous_Wall(iMarker)) continue; const auto Marker_Tag = config->GetMarker_All_TagBound(iMarker); - + const auto Monitoring = config->GetMarker_All_Monitoring(iMarker); const bool py_custom = config->GetMarker_All_PyCustom(iMarker); /*--- Obtain the origin for the moment computation for a particular marker ---*/ - const auto Monitoring = config->GetMarker_All_Monitoring(iMarker); - if (Monitoring == YES) { - for (iMarker_Monitoring = 0; iMarker_Monitoring < config->GetnMarker_Monitoring(); iMarker_Monitoring++) { - const auto Monitoring_Tag = config->GetMarker_Monitoring_TagBound(iMarker_Monitoring); - if (Marker_Tag == Monitoring_Tag) Origin = config->GetRefOriginMoment(iMarker_Monitoring); - } - } - - /*--- Forces initialization at each Marker ---*/ - - ViscCoeff.setZero(iMarker); - - HF_Visc[iMarker] = 0.0; - MaxHF_Visc[iMarker] = 0.0; + const int iMarker_Monitoring = FindMonitoringIndex(config, iMarker, Monitoring, Origin); su2double ForceViscous[MAXNDIM] = {0.0}, MomentViscous[MAXNDIM] = {0.0}; su2double MomentX_Force[MAXNDIM] = {0.0}, MomentY_Force[MAXNDIM] = {0.0}, MomentZ_Force[MAXNDIM] = {0.0}; + su2double HF_Visc_Local = 0.0, MaxHF_Visc_Local = 0.0; /* --- check if wall functions are used --- */ const bool wallfunctions = (config->GetWallFunction_Treatment(Marker_Tag) != WALL_FUNCTIONS::NONE); - /*--- Loop over the vertices to compute the forces ---*/ + /*--- Marker-level lookups hoisted out of the per-vertex loop below: GetWallRoughnessProperties, + * GetWall_HeatFlux, GetIsothermal_Temperature and GetCatalytic_Wall all scan over marker + * lists (some by string comparison), so evaluating them once per vertex instead of once per + * marker was a real cost for markers with many vertices. ---*/ - for (iVertex = 0; iVertex < geometry->nVertex[iMarker]; iVertex++) { - iPoint = geometry->vertex[iMarker][iVertex]->GetNode(); + WALL_TYPE WallType = WALL_TYPE::SMOOTH; + if (roughwall) { + su2double Roughness_Height; + tie(WallType, Roughness_Height) = config->GetWallRoughnessProperties(Marker_Tag); + } - Coord = geometry->nodes->GetCoord(iPoint); + const auto KindBC = config->GetMarker_All_KindBC(iMarker); + su2double Wall_HeatFlux_Value = 0.0, Twall = 0.0; + if (!nemo && !py_custom) { + if (KindBC == BC_TYPE::HEAT_FLUX) { + Wall_HeatFlux_Value = -config->GetWall_HeatFlux(Marker_Tag); + if (config->GetIntegrated_HeatFlux()) Wall_HeatFlux_Value /= geometry->GetSurfaceArea(config, iMarker); + } else if (KindBC == BC_TYPE::ISOTHERMAL) { + Twall = config->GetIsothermal_Temperature(Marker_Tag) / RefTemperature; + } + } + const bool catalytic = nemo && config->GetCatalytic_Wall(iMarker); - Normal = geometry->vertex[iMarker][iVertex]->GetNormal(); + /*--- Loop over the vertices to compute the forces (work-shared across threads, see + * Pressure_Forces for why the chunk size is computed and the barrier skipped). ---*/ - for (iDim = 0; iDim < nDim; iDim++) { - for (jDim = 0; jDim < nDim; jDim++) { - Grad_Vel[iDim][jDim] = nodes->GetGradient_Primitive(iPoint, prim_idx.Velocity() + iDim, jDim); - } + SU2_OMP_FOR_(schedule(static, OMP_MIN_SIZE) SU2_NOWAIT) + for (unsigned long iVertex = 0; iVertex < geometry->nVertex[iMarker]; iVertex++) { + const auto iPoint = geometry->vertex[iMarker][iVertex]->GetNode(); + + const su2double* Coord = geometry->nodes->GetCoord(iPoint); + + const su2double* Normal = geometry->vertex[iMarker][iVertex]->GetNormal(); + + /*--- One view covering the whole velocity-gradient block, instead of nDim*nDim separate + * single-element lookups (ComputeStressTensor/AddQCR accept any [][]-indexable type). ---*/ + const auto Grad_Vel = nodes->GetVelocityGradient(iPoint); + + su2double Grad_Temp[3] = {0.0}, Grad_Temp_ve[3] = {0.0}; + for (unsigned short iDim = 0; iDim < nDim; iDim++) { Grad_Temp[iDim] = nodes->GetGradient_Primitive(iPoint, prim_idx.Temperature(), iDim); if (nemo) Grad_Temp_ve[iDim] = nodes->GetGradient_Primitive(iPoint, prim_idx.Temperature_ve(), iDim); } - Viscosity = nodes->GetLaminarViscosity(iPoint); + su2double Viscosity = nodes->GetLaminarViscosity(iPoint); su2double EddyViscosity = 0.0; - if (roughwall) { - WALL_TYPE WallType; - su2double Roughness_Height; - tie(WallType, Roughness_Height) = config->GetWallRoughnessProperties(Marker_Tag); - if (WallType == WALL_TYPE::ROUGH) { - EddyViscosity = nodes->GetEddyViscosity(iPoint); - Viscosity += EddyViscosity; - } + if (WallType == WALL_TYPE::ROUGH) { + EddyViscosity = nodes->GetEddyViscosity(iPoint); + Viscosity += EddyViscosity; } - Density = nodes->GetDensity(iPoint); + const su2double Density = nodes->GetDensity(iPoint); - Area = GeometryToolbox::Norm(nDim, Normal); - for (iDim = 0; iDim < nDim; iDim++) { + const su2double Area = GeometryToolbox::Norm(nDim, Normal); + su2double UnitNormal[3] = {0.0}; + for (unsigned short iDim = 0; iDim < nDim; iDim++) { UnitNormal[iDim] = Normal[iDim] / Area; } /*--- Evaluate Tau ---*/ + su2double Tau[3][3] = {{0.0}}; CNumerics::ComputeStressTensor(nDim, Tau, Grad_Vel, Viscosity); /*--- If necessary evaluate the QCR contribution to Tau ---*/ @@ -2561,9 +2596,9 @@ void CFVMFlowSolverBase::Friction_Forces(const CGeometry* geometr /*--- Project Tau in each surface element ---*/ - for (iDim = 0; iDim < nDim; iDim++) { - TauElem[iDim] = 0.0; - for (jDim = 0; jDim < nDim; jDim++) { + su2double TauElem[3] = {0.0}; + for (unsigned short iDim = 0; iDim < nDim; iDim++) { + for (unsigned short jDim = 0; jDim < nDim; jDim++) { TauElem[iDim] += Tau[iDim][jDim] * UnitNormal[jDim]; } } @@ -2577,11 +2612,10 @@ void CFVMFlowSolverBase::Friction_Forces(const CGeometry* geometr WallShearStress[iMarker][iVertex] = GeometryToolbox::Norm(int(MAXNDIM), TauTangent); /*--- For wall functions, the wall stresses need to be scaled by the wallfunction stress Tau_Wall---*/ - su2double Tau_Wall, scale; if (wallfunctions && (YPlus[iMarker][iVertex] > minYPlus)){ - Tau_Wall = nodes->GetTau_Wall(iPoint); - scale = Tau_Wall / WallShearStress[iMarker][iVertex]; - for (iDim = 0; iDim < nDim; iDim++) { + const su2double Tau_Wall = nodes->GetTau_Wall(iPoint); + const su2double scale = Tau_Wall / WallShearStress[iMarker][iVertex]; + for (unsigned short iDim = 0; iDim < nDim; iDim++) { TauTangent[iDim] *= scale; TauElem[iDim] *= scale; } @@ -2589,48 +2623,39 @@ void CFVMFlowSolverBase::Friction_Forces(const CGeometry* geometr WallShearStress[iMarker][iVertex] = Tau_Wall; } - for (iDim = 0; iDim < nDim; iDim++) { + for (unsigned short iDim = 0; iDim < nDim; iDim++) { CSkinFriction[iMarker](iVertex,iDim) = TauTangent[iDim] * factorFric; } /*--- Compute non-dimensional velocity and y+ ---*/ - FrictionVel = sqrt(fabs(WallShearStress[iMarker][iVertex]) / Density); + const su2double FrictionVel = sqrt(fabs(WallShearStress[iMarker][iVertex]) / Density); if (!wallfunctions && MGLevel == MESH_0 && geometry->nodes->GetDomain(iPoint)) { // for CMultiGridGeometry and halos, the nearest neighbor distance is not set const su2double WallDistMod = geometry->vertex[iMarker][iVertex]->GetNearestNeighborDistance(); - YPlus[iMarker][iVertex] = WallDistMod * FrictionVel / (Viscosity / Density); + YPlus[iMarker][iVertex] = WallDistMod * FrictionVel * Density / Viscosity; } /*--- Compute total and maximum heat flux on the wall ---*/ if (!nemo) { + su2double thermal_conductivity = 0.0; if ((FlowRegime == ENUM_REGIME::COMPRESSIBLE) || (FlowRegime == ENUM_REGIME::INCOMPRESSIBLE)) { thermal_conductivity = nodes->GetThermalConductivity(iPoint); } - if (config->GetMarker_All_KindBC(iMarker) == BC_TYPE::HEAT_FLUX) { - if (py_custom) { - HeatFlux[iMarker][iVertex] = -geometry->GetCustomBoundaryHeatFlux(iMarker, iVertex); - } else { - HeatFlux[iMarker][iVertex] = -config->GetWall_HeatFlux(Marker_Tag); - if (config->GetIntegrated_HeatFlux()) { - HeatFlux[iMarker][iVertex] /= geometry->GetSurfaceArea(config, iMarker); - } - } - } else if (config->GetMarker_All_KindBC(iMarker) == BC_TYPE::ISOTHERMAL) { - su2double Twall = 0.0; - if (py_custom) { - Twall = geometry->GetCustomBoundaryTemperature(iMarker, iVertex) / RefTemperature; - } else { - Twall = config->GetIsothermal_Temperature(Marker_Tag) / RefTemperature; - } - iPointNormal = geometry->vertex[iMarker][iVertex]->GetNormal_Neighbor(); - Coord_Normal = geometry->nodes->GetCoord(iPointNormal); + if (KindBC == BC_TYPE::HEAT_FLUX) { + HeatFlux[iMarker][iVertex] = + py_custom ? -geometry->GetCustomBoundaryHeatFlux(iMarker, iVertex) : Wall_HeatFlux_Value; + } else if (KindBC == BC_TYPE::ISOTHERMAL) { + const su2double Twall_local = + py_custom ? geometry->GetCustomBoundaryTemperature(iMarker, iVertex) / RefTemperature : Twall; + const auto iPointNormal = geometry->vertex[iMarker][iVertex]->GetNormal_Neighbor(); + const su2double* Coord_Normal = geometry->nodes->GetCoord(iPointNormal); const su2double dist_ij = GeometryToolbox::NormalDistance(nDim, UnitNormal, Coord, Coord_Normal); const su2double There = nodes->GetTemperature(iPointNormal); - HeatFlux[iMarker][iVertex] = thermal_conductivity * (There - Twall) / dist_ij * RefHeatFlux; + HeatFlux[iMarker][iVertex] = thermal_conductivity * (There - Twall_local) / dist_ij * RefHeatFlux; } else { su2double dTdn = GeometryToolbox::DotProduct(nDim, Grad_Temp, UnitNormal); if (FlowRegime == ENUM_REGIME::INCOMPRESSIBLE && !energy) dTdn = 0.0; @@ -2648,8 +2673,7 @@ void CFVMFlowSolverBase::Friction_Forces(const CGeometry* geometr HeatFlux[iMarker][iVertex] = -(thermal_conductivity_tr*dTdn + thermal_conductivity_ve*dTvedn); /*--- Compute enthalpy transport to surface due to mass diffusion ---*/ - bool catalytic = config->GetCatalytic_Wall(iMarker); - if (catalytic){ + if (catalytic) { const auto nSpecies = config->GetnSpecies(); const auto& Grad_PrimVar = nodes->GetGradient_Primitive(iPoint); @@ -2677,15 +2701,12 @@ void CFVMFlowSolverBase::Friction_Forces(const CGeometry* geometr if ((geometry->nodes->GetDomain(iPoint)) && (Monitoring == YES)) { /*--- Axisymmetric simulations ---*/ - if (axisymmetric) - AxiFactor = 2.0 * PI_NUMBER * geometry->nodes->GetCoord(iPoint, 1); - else - AxiFactor = 1.0; + const su2double AxiFactor = axisymmetric ? su2double(2.0 * PI_NUMBER * geometry->nodes->GetCoord(iPoint, 1)) : su2double(1.0); /*--- Force computation ---*/ su2double Force[MAXNDIM] = {0.0}, MomentDist[MAXNDIM] = {0.0}; - for (iDim = 0; iDim < nDim; iDim++) { + for (unsigned short iDim = 0; iDim < nDim; iDim++) { Force[iDim] = TauElem[iDim] * Area * factor * AxiFactor; ForceViscous[iDim] += Force[iDim]; MomentDist[iDim] = Coord[iDim] - Origin[iDim]; @@ -2693,227 +2714,97 @@ void CFVMFlowSolverBase::Friction_Forces(const CGeometry* geometr /*--- Moment with respect to the reference axis ---*/ - if (nDim == 3) { - MomentViscous[0] += (Force[2] * MomentDist[1] - Force[1] * MomentDist[2]) / RefLength; - MomentX_Force[1] += (-Force[1] * Coord[2]); - MomentX_Force[2] += (Force[2] * Coord[1]); - - MomentViscous[1] += (Force[0] * MomentDist[2] - Force[2] * MomentDist[0]) / RefLength; - MomentY_Force[2] += (-Force[2] * Coord[0]); - MomentY_Force[0] += (Force[0] * Coord[2]); - } - MomentViscous[2] += (Force[1] * MomentDist[0] - Force[0] * MomentDist[1]) / RefLength; - MomentZ_Force[0] += (-Force[0] * Coord[1]); - MomentZ_Force[1] += (Force[1] * Coord[0]); + AccumulateMoment(nDim, RefLength, Force, MomentDist, Coord, MomentViscous, MomentX_Force, MomentY_Force, + MomentZ_Force); - HF_Visc[iMarker] += HeatFlux[iMarker][iVertex] * Area; - MaxHF_Visc[iMarker] += pow(HeatFlux[iMarker][iVertex], MaxNorm); + HF_Visc_Local += HeatFlux[iMarker][iVertex] * Area; + MaxHF_Visc_Local += pow(HeatFlux[iMarker][iVertex], MaxNorm); } } + END_SU2_OMP_FOR - /*--- Project forces and store the non-dimensional coefficients ---*/ + /*--- MaxHF_Visc_Local (and the shared accumulators it feeds below) are left un-rooted, + * i.e. still a raw sum of HeatFlux^MaxNorm; pow(., 1/MaxNorm) is taken once at the end, + * which is equivalent since pow(x^(1/n), n) == x. ---*/ if (Monitoring == YES) { - if (nDim == 2) { - ViscCoeff.CD[iMarker] = ForceViscous[0] * cos(Alpha) + ForceViscous[1] * sin(Alpha); - ViscCoeff.CL[iMarker] = -ForceViscous[0] * sin(Alpha) + ForceViscous[1] * cos(Alpha); - ViscCoeff.CEff[iMarker] = ViscCoeff.CL[iMarker] / (ViscCoeff.CD[iMarker] + EPS); - ViscCoeff.CFx[iMarker] = ForceViscous[0]; - ViscCoeff.CFy[iMarker] = ForceViscous[1]; - ViscCoeff.CMz[iMarker] = MomentViscous[2]; - ViscCoeff.CoPx[iMarker] = MomentZ_Force[1]; - ViscCoeff.CoPy[iMarker] = -MomentZ_Force[0]; - ViscCoeff.CT[iMarker] = -ViscCoeff.CFx[iMarker]; - ViscCoeff.CQ[iMarker] = -ViscCoeff.CMz[iMarker]; - ViscCoeff.CMerit[iMarker] = ViscCoeff.CT[iMarker] / (ViscCoeff.CQ[iMarker] + EPS); - MaxHF_Visc[iMarker] = pow(MaxHF_Visc[iMarker], 1.0 / MaxNorm); - } - if (nDim == 3) { - ViscCoeff.CD[iMarker] = ForceViscous[0] * cos(Alpha) * cos(Beta) + ForceViscous[1] * sin(Beta) + - ForceViscous[2] * sin(Alpha) * cos(Beta); - ViscCoeff.CL[iMarker] = -ForceViscous[0] * sin(Alpha) + ForceViscous[2] * cos(Alpha); - ViscCoeff.CSF[iMarker] = -ForceViscous[0] * sin(Beta) * cos(Alpha) + ForceViscous[1] * cos(Beta) - - ForceViscous[2] * sin(Beta) * sin(Alpha); - ViscCoeff.CEff[iMarker] = ViscCoeff.CL[iMarker] / (ViscCoeff.CD[iMarker] + EPS); - ViscCoeff.CFx[iMarker] = ForceViscous[0]; - ViscCoeff.CFy[iMarker] = ForceViscous[1]; - ViscCoeff.CFz[iMarker] = ForceViscous[2]; - ViscCoeff.CMx[iMarker] = MomentViscous[0]; - ViscCoeff.CMy[iMarker] = MomentViscous[1]; - ViscCoeff.CMz[iMarker] = MomentViscous[2]; - ViscCoeff.CoPx[iMarker] = -MomentY_Force[0]; - ViscCoeff.CoPz[iMarker] = MomentY_Force[2]; - ViscCoeff.CT[iMarker] = -ViscCoeff.CFz[iMarker]; - ViscCoeff.CQ[iMarker] = -ViscCoeff.CMz[iMarker]; - ViscCoeff.CMerit[iMarker] = ViscCoeff.CT[iMarker] / (ViscCoeff.CQ[iMarker] + EPS); - MaxHF_Visc[iMarker] = pow(MaxHF_Visc[iMarker], 1.0 / MaxNorm); - } + const auto partial = ComputeAeroCoeffsFromForceMoment( + nDim, CosAlpha, SinAlpha, CosBeta, SinBeta, ForceViscous, MomentViscous, MomentX_Force, MomentY_Force, + MomentZ_Force); + + AddCoeffContribution(iMarker, iMarker_Monitoring, partial, ViscCoeff, AllBoundViscCoeff, SurfaceViscCoeff); + + /*--- Heat flux, not covered by AddCoeffContribution, is folded in its own critical section. ---*/ + + SU2_OMP_CRITICAL { + HF_Visc[iMarker] += HF_Visc_Local; + AllBound_HF_Visc += HF_Visc_Local; + MaxHF_Visc[iMarker] += MaxHF_Visc_Local; + AllBound_MaxHF_Visc += MaxHF_Visc_Local; - AllBoundViscCoeff.CD += ViscCoeff.CD[iMarker]; - AllBoundViscCoeff.CL += ViscCoeff.CL[iMarker]; - AllBoundViscCoeff.CSF += ViscCoeff.CSF[iMarker]; - AllBoundViscCoeff.CFx += ViscCoeff.CFx[iMarker]; - AllBoundViscCoeff.CFy += ViscCoeff.CFy[iMarker]; - AllBoundViscCoeff.CFz += ViscCoeff.CFz[iMarker]; - AllBoundViscCoeff.CMx += ViscCoeff.CMx[iMarker]; - AllBoundViscCoeff.CMy += ViscCoeff.CMy[iMarker]; - AllBoundViscCoeff.CMz += ViscCoeff.CMz[iMarker]; - AllBoundViscCoeff.CoPx += ViscCoeff.CoPx[iMarker]; - AllBoundViscCoeff.CoPy += ViscCoeff.CoPy[iMarker]; - AllBoundViscCoeff.CoPz += ViscCoeff.CoPz[iMarker]; - AllBoundViscCoeff.CT += ViscCoeff.CT[iMarker]; - AllBoundViscCoeff.CQ += ViscCoeff.CQ[iMarker]; - AllBound_HF_Visc += HF_Visc[iMarker]; - AllBound_MaxHF_Visc += pow(MaxHF_Visc[iMarker], MaxNorm); - - /*--- Compute the coefficients per surface ---*/ - - for (iMarker_Monitoring = 0; iMarker_Monitoring < config->GetnMarker_Monitoring(); iMarker_Monitoring++) { - const auto Monitoring_Tag = config->GetMarker_Monitoring_TagBound(iMarker_Monitoring); - if (Marker_Tag == Monitoring_Tag) { - SurfaceViscCoeff.CL[iMarker_Monitoring] += ViscCoeff.CL[iMarker]; - SurfaceViscCoeff.CD[iMarker_Monitoring] += ViscCoeff.CD[iMarker]; - SurfaceViscCoeff.CSF[iMarker_Monitoring] += ViscCoeff.CSF[iMarker]; - SurfaceViscCoeff.CEff[iMarker_Monitoring] = SurfaceViscCoeff.CL[iMarker_Monitoring] / (SurfaceViscCoeff.CD[iMarker_Monitoring] + EPS); - SurfaceViscCoeff.CFx[iMarker_Monitoring] += ViscCoeff.CFx[iMarker]; - SurfaceViscCoeff.CFy[iMarker_Monitoring] += ViscCoeff.CFy[iMarker]; - SurfaceViscCoeff.CFz[iMarker_Monitoring] += ViscCoeff.CFz[iMarker]; - SurfaceViscCoeff.CMx[iMarker_Monitoring] += ViscCoeff.CMx[iMarker]; - SurfaceViscCoeff.CMy[iMarker_Monitoring] += ViscCoeff.CMy[iMarker]; - SurfaceViscCoeff.CMz[iMarker_Monitoring] += ViscCoeff.CMz[iMarker]; - Surface_HF_Visc[iMarker_Monitoring] += HF_Visc[iMarker]; - Surface_MaxHF_Visc[iMarker_Monitoring] += pow(MaxHF_Visc[iMarker], MaxNorm); + if (iMarker_Monitoring >= 0) { + Surface_HF_Visc[iMarker_Monitoring] += HF_Visc_Local; + Surface_MaxHF_Visc[iMarker_Monitoring] += MaxHF_Visc_Local; } } + END_SU2_OMP_CRITICAL } } + /*--- For the SU2_NOWAIT in the vertex loop. ---*/ + SU2_OMP_BARRIER - /*--- Update some global coeffients ---*/ - - AllBoundViscCoeff.CEff = AllBoundViscCoeff.CL / (AllBoundViscCoeff.CD + EPS); - AllBoundViscCoeff.CMerit = AllBoundViscCoeff.CT / (AllBoundViscCoeff.CQ + EPS); - -#ifdef HAVE_MPI - - /*--- Add AllBound information using all the nodes ---*/ + /*--- Derive the ratio coefficients, and root the (still raw) per-marker maximum heat flux, + * from the fully-reduced totals, once. Surface_MaxHF_Visc and AllBound_MaxHF_Visc are + * rooted later below, after the MPI reduction. ---*/ - if (config->GetComm_Level() == COMM_FULL) { - auto Allreduce = [](su2double x) { - su2double tmp = x; - x = 0.0; - SU2_MPI::Allreduce(&tmp, &x, 1, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); - return x; - }; - AllBoundViscCoeff.CD = Allreduce(AllBoundViscCoeff.CD); - AllBoundViscCoeff.CL = Allreduce(AllBoundViscCoeff.CL); - AllBoundViscCoeff.CSF = Allreduce(AllBoundViscCoeff.CSF); - AllBoundViscCoeff.CEff = AllBoundViscCoeff.CL / (AllBoundViscCoeff.CD + EPS); - - AllBoundViscCoeff.CMx = Allreduce(AllBoundViscCoeff.CMx); - AllBoundViscCoeff.CMy = Allreduce(AllBoundViscCoeff.CMy); - AllBoundViscCoeff.CMz = Allreduce(AllBoundViscCoeff.CMz); - - AllBoundViscCoeff.CFx = Allreduce(AllBoundViscCoeff.CFx); - AllBoundViscCoeff.CFy = Allreduce(AllBoundViscCoeff.CFy); - AllBoundViscCoeff.CFz = Allreduce(AllBoundViscCoeff.CFz); - - AllBoundViscCoeff.CoPx = Allreduce(AllBoundViscCoeff.CoPx); - AllBoundViscCoeff.CoPy = Allreduce(AllBoundViscCoeff.CoPy); - AllBoundViscCoeff.CoPz = Allreduce(AllBoundViscCoeff.CoPz); - - AllBoundViscCoeff.CT = Allreduce(AllBoundViscCoeff.CT); - AllBoundViscCoeff.CQ = Allreduce(AllBoundViscCoeff.CQ); - AllBoundViscCoeff.CMerit = AllBoundViscCoeff.CT / (AllBoundViscCoeff.CQ + EPS); - - AllBound_HF_Visc = Allreduce(AllBound_HF_Visc); - AllBound_MaxHF_Visc = Allreduce(AllBound_MaxHF_Visc); + SU2_OMP_FOR_(schedule(static, OMP_MIN_SIZE) SU2_NOWAIT) + for (unsigned long iMarker = 0; iMarker < nMarker; iMarker++) { + if (!config->GetViscous_Wall(iMarker)) continue; + if (config->GetMarker_All_Monitoring(iMarker) == YES) { + ViscCoeff.CEff[iMarker] = ViscCoeff.CL[iMarker] / (ViscCoeff.CD[iMarker] + EPS); + ViscCoeff.CMerit[iMarker] = ViscCoeff.CT[iMarker] / (ViscCoeff.CQ[iMarker] + EPS); + MaxHF_Visc[iMarker] = pow(MaxHF_Visc[iMarker], 1.0 / MaxNorm); + } } + END_SU2_OMP_FOR - /*--- Add the forces on the surfaces using all the nodes ---*/ - - if (config->GetComm_Level() == COMM_FULL) { - int nMarkerMon = config->GetnMarker_Monitoring(); - - /*--- Use the same buffer for all reductions. We could avoid the copy back into - * the original variable by swaping pointers, but it is safer this way... ---*/ - - su2double* buffer = new su2double[nMarkerMon]; - - auto Allreduce_inplace = [buffer](int size, su2double* x) { - SU2_MPI::Allreduce(x, buffer, size, MPI_DOUBLE, MPI_SUM, SU2_MPI::GetComm()); - for (int i = 0; i < size; ++i) x[i] = buffer[i]; - }; - - Allreduce_inplace(nMarkerMon, SurfaceViscCoeff.CL); - Allreduce_inplace(nMarkerMon, SurfaceViscCoeff.CD); - Allreduce_inplace(nMarkerMon, SurfaceViscCoeff.CSF); - - for (iMarker_Monitoring = 0; iMarker_Monitoring < nMarkerMon; iMarker_Monitoring++) - SurfaceViscCoeff.CEff[iMarker_Monitoring] = - SurfaceViscCoeff.CL[iMarker_Monitoring] / (SurfaceViscCoeff.CD[iMarker_Monitoring] + EPS); + SU2_OMP_FOR_(schedule(static, OMP_MIN_SIZE) SU2_NOWAIT) + for (unsigned short iMarker_Monitoring = 0; iMarker_Monitoring < config->GetnMarker_Monitoring(); + iMarker_Monitoring++) { + SurfaceViscCoeff.CEff[iMarker_Monitoring] = + SurfaceViscCoeff.CL[iMarker_Monitoring] / (SurfaceViscCoeff.CD[iMarker_Monitoring] + EPS); + } + END_SU2_OMP_FOR - Allreduce_inplace(nMarkerMon, SurfaceViscCoeff.CFx); - Allreduce_inplace(nMarkerMon, SurfaceViscCoeff.CFy); - Allreduce_inplace(nMarkerMon, SurfaceViscCoeff.CFz); + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { + ReduceCoeffsMPI(config, AllBoundViscCoeff, SurfaceViscCoeff); - Allreduce_inplace(nMarkerMon, SurfaceViscCoeff.CMx); - Allreduce_inplace(nMarkerMon, SurfaceViscCoeff.CMy); - Allreduce_inplace(nMarkerMon, SurfaceViscCoeff.CMz); + /*--- HF_Visc/MaxHF_Visc, not covered by ReduceCoeffsMPI, are reduced separately. ---*/ + if (config->GetComm_Level() == COMM_FULL) { + AllBound_HF_Visc = MPIReduceSum(AllBound_HF_Visc); + AllBound_MaxHF_Visc = MPIReduceSum(AllBound_MaxHF_Visc); - Allreduce_inplace(nMarkerMon, Surface_HF_Visc.data()); - Allreduce_inplace(nMarkerMon, Surface_MaxHF_Visc.data()); + const int nMarkerMon = config->GetnMarker_Monitoring(); + MPIReduceSumInPlace(Surface_HF_Visc.data(), nMarkerMon); + MPIReduceSumInPlace(Surface_MaxHF_Visc.data(), nMarkerMon); + } - delete[] buffer; - } + /*--- Complete the calculation of maximum heat flux. ---*/ -#endif + for (auto& hf : Surface_MaxHF_Visc) { + hf = pow(hf, 1.0 / MaxNorm); + } + AllBound_MaxHF_Visc = pow(AllBound_MaxHF_Visc, 1.0 / MaxNorm); - /*--- Complete the calculation of maximum heat flux. ---*/ - - for (auto& hf : Surface_MaxHF_Visc) { - hf = pow(hf, 1.0 / MaxNorm); - } - AllBound_MaxHF_Visc = pow(AllBound_MaxHF_Visc, 1.0 / MaxNorm); - - /*--- Update the total coefficients (note that all the nodes have the same value)---*/ - - TotalCoeff.CD += AllBoundViscCoeff.CD; - TotalCoeff.CL += AllBoundViscCoeff.CL; - TotalCoeff.CSF += AllBoundViscCoeff.CSF; - TotalCoeff.CEff = TotalCoeff.CL / (TotalCoeff.CD + EPS); - TotalCoeff.CFx += AllBoundViscCoeff.CFx; - TotalCoeff.CFy += AllBoundViscCoeff.CFy; - TotalCoeff.CFz += AllBoundViscCoeff.CFz; - TotalCoeff.CMx += AllBoundViscCoeff.CMx; - TotalCoeff.CMy += AllBoundViscCoeff.CMy; - TotalCoeff.CMz += AllBoundViscCoeff.CMz; - TotalCoeff.CoPx += AllBoundViscCoeff.CoPx; - TotalCoeff.CoPy += AllBoundViscCoeff.CoPy; - TotalCoeff.CoPz += AllBoundViscCoeff.CoPz; - TotalCoeff.CT += AllBoundViscCoeff.CT; - TotalCoeff.CQ += AllBoundViscCoeff.CQ; - TotalCoeff.CMerit = AllBoundViscCoeff.CT / (AllBoundViscCoeff.CQ + EPS); - Total_Heat = AllBound_HF_Visc; - Total_MaxHeat = AllBound_MaxHF_Visc; + AccumulateTotalCoeffs(config, AllBoundViscCoeff, SurfaceViscCoeff, TotalCoeff, SurfaceCoeff, /*overwrite=*/false); + Total_Heat = AllBound_HF_Visc; + Total_MaxHeat = AllBound_MaxHF_Visc; - /*--- Update the total coefficients per surface (note that all the nodes have the same value)---*/ + /*--- Buffet_Monitoring is not thread-safe, hence confined to the master thread. ---*/ - for (iMarker_Monitoring = 0; iMarker_Monitoring < config->GetnMarker_Monitoring(); iMarker_Monitoring++) { - SurfaceCoeff.CL[iMarker_Monitoring] += SurfaceViscCoeff.CL[iMarker_Monitoring]; - SurfaceCoeff.CD[iMarker_Monitoring] += SurfaceViscCoeff.CD[iMarker_Monitoring]; - SurfaceCoeff.CSF[iMarker_Monitoring] += SurfaceViscCoeff.CSF[iMarker_Monitoring]; - SurfaceCoeff.CEff[iMarker_Monitoring] = - SurfaceCoeff.CL[iMarker_Monitoring] / (SurfaceCoeff.CD[iMarker_Monitoring] + EPS); - SurfaceCoeff.CFx[iMarker_Monitoring] += SurfaceViscCoeff.CFx[iMarker_Monitoring]; - SurfaceCoeff.CFy[iMarker_Monitoring] += SurfaceViscCoeff.CFy[iMarker_Monitoring]; - SurfaceCoeff.CFz[iMarker_Monitoring] += SurfaceViscCoeff.CFz[iMarker_Monitoring]; - SurfaceCoeff.CMx[iMarker_Monitoring] += SurfaceViscCoeff.CMx[iMarker_Monitoring]; - SurfaceCoeff.CMy[iMarker_Monitoring] += SurfaceViscCoeff.CMy[iMarker_Monitoring]; - SurfaceCoeff.CMz[iMarker_Monitoring] += SurfaceViscCoeff.CMz[iMarker_Monitoring]; + Buffet_Monitoring(geometry, config); } - - Buffet_Monitoring(geometry, config); - + END_SU2_OMP_SAFE_GLOBAL_ACCESS } template diff --git a/SU2_CFD/src/integration/CMultiGridIntegration.cpp b/SU2_CFD/src/integration/CMultiGridIntegration.cpp index f8ca90cd37fd..679a31cef6b3 100644 --- a/SU2_CFD/src/integration/CMultiGridIntegration.cpp +++ b/SU2_CFD/src/integration/CMultiGridIntegration.cpp @@ -1001,35 +1001,30 @@ void CMultiGridIntegration::NonDimensional_Parameters(CGeometry **geometry, CSol unsigned short FinestMesh, unsigned short RunTime_EqSystem, su2double *monitor) { SU2_ZONE_SCOPED - BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS - switch (RunTime_EqSystem) { - - case RUNTIME_FLOW_SYS: - - /*--- Calculate the inviscid and viscous forces ---*/ - - solver_container[FinestMesh][FLOW_SOL]->Pressure_Forces(geometry[FinestMesh], config); - solver_container[FinestMesh][FLOW_SOL]->Momentum_Forces(geometry[FinestMesh], config); - solver_container[FinestMesh][FLOW_SOL]->Friction_Forces(geometry[FinestMesh], config); - break; + if (RunTime_EqSystem == RUNTIME_FLOW_SYS) { + /*--- Calculate the inviscid and viscous forces ---*/ - case RUNTIME_ADJFLOW_SYS: + solver_container[FinestMesh][FLOW_SOL]->Pressure_Forces(geometry[FinestMesh], config); + solver_container[FinestMesh][FLOW_SOL]->Momentum_Forces(geometry[FinestMesh], config); + solver_container[FinestMesh][FLOW_SOL]->Friction_Forces(geometry[FinestMesh], config); + } - /*--- Calculate the inviscid and viscous sensitivities ---*/ + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS + if (RunTime_EqSystem == RUNTIME_ADJFLOW_SYS) { + /*--- Calculate the inviscid and viscous sensitivities ---*/ - solver_container[FinestMesh][ADJFLOW_SOL]->Inviscid_Sensitivity(geometry[FinestMesh], solver_container[FinestMesh], - numerics_container[FinestMesh][ADJFLOW_SOL][CONV_BOUND_TERM], config); + solver_container[FinestMesh][ADJFLOW_SOL]->Inviscid_Sensitivity(geometry[FinestMesh], solver_container[FinestMesh], + numerics_container[FinestMesh][ADJFLOW_SOL][CONV_BOUND_TERM], config); - solver_container[FinestMesh][ADJFLOW_SOL]->Viscous_Sensitivity(geometry[FinestMesh], solver_container[FinestMesh], - numerics_container[FinestMesh][ADJFLOW_SOL][CONV_BOUND_TERM], config); + solver_container[FinestMesh][ADJFLOW_SOL]->Viscous_Sensitivity(geometry[FinestMesh], solver_container[FinestMesh], + numerics_container[FinestMesh][ADJFLOW_SOL][CONV_BOUND_TERM], config); - /*--- Smooth the inviscid and viscous sensitivities ---*/ + /*--- Smooth the inviscid and viscous sensitivities ---*/ - if (config->GetKind_SensSmooth() != NONE) - solver_container[FinestMesh][ADJFLOW_SOL]->Smooth_Sensitivity(geometry[FinestMesh], solver_container[FinestMesh], - numerics_container[FinestMesh][ADJFLOW_SOL][CONV_BOUND_TERM], config); - break; + if (config->GetKind_SensSmooth() != NONE) + solver_container[FinestMesh][ADJFLOW_SOL]->Smooth_Sensitivity(geometry[FinestMesh], solver_container[FinestMesh], + numerics_container[FinestMesh][ADJFLOW_SOL][CONV_BOUND_TERM], config); } END_SU2_OMP_SAFE_GLOBAL_ACCESS } diff --git a/TestCases/hybrid_regression.py b/TestCases/hybrid_regression.py index 23519654b898..166a46a368a5 100644 --- a/TestCases/hybrid_regression.py +++ b/TestCases/hybrid_regression.py @@ -103,7 +103,7 @@ def main(): flatplate.cfg_dir = "navierstokes/flatplate" flatplate.cfg_file = "lam_flatplate.cfg" flatplate.test_iter = 100 - flatplate.test_vals = [-6.543281, -1.065152, 0.001196, 0.029390, 2.361500, -2.332100, 0.000000, 0.000000] + flatplate.test_vals = [-6.543284, -1.065154, 0.001196, 0.029390, 2.361500, -2.332100, 0.000000, 0.000000] test_list.append(flatplate) # Laminar cylinder (steady) @@ -136,7 +136,7 @@ def main(): poiseuille_profile.cfg_dir = "navierstokes/poiseuille" poiseuille_profile.cfg_file = "profile_poiseuille.cfg" poiseuille_profile.test_iter = 10 - poiseuille_profile.test_vals = [-12.005126, -7.582352, -0.000000, 2.089953] + poiseuille_profile.test_vals = [-12.005126, -7.582590, -0.000000, 2.089953] poiseuille_profile.test_vals_aarch64 = [-12.009012, -7.262530, -0.000000, 2.089953] test_list.append(poiseuille_profile) @@ -197,7 +197,7 @@ def main(): turb_naca0012_sa.cfg_dir = "rans/naca0012" turb_naca0012_sa.cfg_file = "turb_NACA0012_sa.cfg" turb_naca0012_sa.test_iter = 5 - turb_naca0012_sa.test_vals = [-12.038067, -16.332088, 1.080346, 0.018385, 20.000000, -2.873679, 0.000000, -14.250269, 0.000000] + turb_naca0012_sa.test_vals = [-12.038074, -16.332088, 1.080346, 0.018385, 20.000000, -2.873831, 0.000000, -14.250269, 0.000000] turb_naca0012_sa.test_vals_aarch64 = [-12.038091, -16.332090, 1.080346, 0.018385, 20.000000, -2.873236, 0.000000, -14.250271, 0.000000] test_list.append(turb_naca0012_sa) @@ -206,7 +206,7 @@ def main(): turb_naca0012_sst.cfg_dir = "rans/naca0012" turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" turb_naca0012_sst.test_iter = 10 - turb_naca0012_sst.test_vals = [-12.093958, -15.250719, -5.906323, 1.070413, 0.015775, -2.855776, 0.000000] + turb_naca0012_sst.test_vals = [-12.093938, -15.250736, -5.906323, 1.070413, 0.015775, -2.855438, 0.000000] turb_naca0012_sst.test_vals_aarch64 = [-12.075928, -15.246732, -5.861249, 1.070036, 0.015841, -2.835263, 0] test_list.append(turb_naca0012_sst) @@ -215,7 +215,7 @@ def main(): turb_naca0012_sst_sust.cfg_dir = "rans/naca0012" turb_naca0012_sst_sust.cfg_file = "turb_NACA0012_sst_sust.cfg" turb_naca0012_sst_sust.test_iter = 10 - turb_naca0012_sst_sust.test_vals = [-12.080818, -14.837175, -5.732906, 1.000893, 0.019109, -2.119717] + turb_naca0012_sst_sust.test_vals = [-12.080868, -14.837176, -5.732906, 1.000893, 0.019109, -2.119686] turb_naca0012_sst_sust.test_vals_aarch64 = [-12.073210, -14.836724, -5.732627, 1.000050, 0.019144, -2.629689] test_list.append(turb_naca0012_sst_sust) @@ -252,7 +252,7 @@ def main(): axi_rans_air_nozzle_restart.cfg_dir = "axisymmetric_rans/air_nozzle" axi_rans_air_nozzle_restart.cfg_file = "air_nozzle_restart.cfg" axi_rans_air_nozzle_restart.test_iter = 10 - axi_rans_air_nozzle_restart.test_vals = [-11.083070, -5.374685, -8.880088, -4.073510, 0.000000] + axi_rans_air_nozzle_restart.test_vals = [-11.083069, -5.374687, -8.880090, -4.073516, 0.000000] axi_rans_air_nozzle_restart.test_vals_aarch64 = [-14.140441, -9.154674, -10.886121, -5.806594, 0.000000] test_list.append(axi_rans_air_nozzle_restart) @@ -570,7 +570,7 @@ def main(): transonic_stator_restart.cfg_dir = "turbomachinery/transonic_stator_2D" transonic_stator_restart.cfg_file = "transonic_stator_restart.cfg" transonic_stator_restart.test_iter = 20 - transonic_stator_restart.test_vals = [-4.367141, -2.487739, -2.079071, 1.728176, -1.464951, 3.225028, -471620.000000, 94.839000, -0.051125] + transonic_stator_restart.test_vals = [-4.367144, -2.487733, -2.079075, 1.728173, -1.464953, 3.225025, -471620.000000, 94.839000, -0.051124] transonic_stator_restart.test_vals_aarch64 = [-4.442510, -2.561369, -2.165778, 1.652750, -1.355494, 3.172712, -471620.000000, 94.843000, -0.043825] test_list.append(transonic_stator_restart) @@ -579,7 +579,7 @@ def main(): 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.632242, -8.894741, -9.348730] + multi_interface.test_vals = [-8.632240, -8.894740, -9.348706] multi_interface.test_vals_aarch64 = [-8.632229, -8.894737, -9.348730] test_list.append(multi_interface) @@ -592,7 +592,7 @@ def main(): uniform_flow.cfg_dir = "sliding_interface/uniform_flow" uniform_flow.cfg_file = "uniform_NN.cfg" uniform_flow.test_iter = 5 - uniform_flow.test_vals = [5.000000, 0.000000, -0.195002, -10.624446] + uniform_flow.test_vals = [5.000000, 0.000000, -0.195002, -10.624450] uniform_flow.unsteady = True uniform_flow.multizone = True test_list.append(uniform_flow) @@ -663,7 +663,7 @@ def main(): bars_SST_2D.cfg_dir = "sliding_interface/bars_SST_2D" bars_SST_2D.cfg_file = "bars.cfg" bars_SST_2D.test_iter = 13 - bars_SST_2D.test_vals = [13.000000, -0.391571, -1.460876] + bars_SST_2D.test_vals = [13.000000, -0.391571, -1.460869] bars_SST_2D.multizone = True test_list.append(bars_SST_2D) diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index 2b10faa2c39e..ff9b8c667061 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -91,7 +91,7 @@ def main(): cfd_flamelet_ch4_unsteady.cfg_dir = "flamelet/09_laminar_premixed_ch4_flame_unsteady" cfd_flamelet_ch4_unsteady.cfg_file = "lam_prem_ch4_unsteady.cfg" cfd_flamelet_ch4_unsteady.test_iter = 5 - cfd_flamelet_ch4_unsteady.test_vals = [-8.856420, -8.095249, -9.153744, -9.321679] + cfd_flamelet_ch4_unsteady.test_vals = [-8.856419, -8.095249, -9.153744, -9.321679] cfd_flamelet_ch4_unsteady.test_vals_aarch64 = [-8.855500, -8.095195, -9.153704, -9.321686] test_list.append(cfd_flamelet_ch4_unsteady) @@ -415,7 +415,7 @@ def main(): poiseuille_profile.cfg_dir = "navierstokes/poiseuille" poiseuille_profile.cfg_file = "profile_poiseuille.cfg" poiseuille_profile.test_iter = 10 - poiseuille_profile.test_vals = [-12.004284, -7.577596, -0.000000, 2.089953] + poiseuille_profile.test_vals = [-12.004280, -7.578037, -0.000000, 2.089953] poiseuille_profile.test_vals_aarch64 = [-12.007498, -7.226926, -0.000000, 2.089953] poiseuille_profile.tol = [0.001, 0.001, 1e-5, 1e-5, 1e-5] test_list.append(poiseuille_profile) @@ -529,7 +529,7 @@ def main(): turb_naca0012_sa.cfg_dir = "rans/naca0012" turb_naca0012_sa.cfg_file = "turb_NACA0012_sa.cfg" turb_naca0012_sa.test_iter = 5 - turb_naca0012_sa.test_vals = [-12.037535, -16.376949, 1.080346, 0.018385, 20.000000, -1.564088, 20.000000, -4.180928, 0.000000] + turb_naca0012_sa.test_vals = [-12.037507, -16.376949, 1.080346, 0.018385, 20.000000, -1.564144, 20.000000, -4.180914, 0.000000] turb_naca0012_sa.test_vals_aarch64 = [-12.037489, -16.376949, 1.080346, 0.018385, 20.000000, -1.564143, 20.000000, -4.180945, 0.000000] turb_naca0012_sa.timeout = 3200 test_list.append(turb_naca0012_sa) @@ -539,7 +539,7 @@ def main(): turb_naca0012_sst.cfg_dir = "rans/naca0012" turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" turb_naca0012_sst.test_iter = 10 - turb_naca0012_sst.test_vals = [-12.094759, -15.251093, -5.906365, 1.070413, 0.015775, -2.376032, 0.000000] + turb_naca0012_sst.test_vals = [-12.094749, -15.251093, -5.906365, 1.070413, 0.015775, -2.376096, 0.000000] turb_naca0012_sst.test_vals_aarch64 = [-12.075620, -15.246688, -5.861276, 1.070036, 0.015841, -1.991001, 0.000000] turb_naca0012_sst.timeout = 3200 test_list.append(turb_naca0012_sst) @@ -549,7 +549,7 @@ def main(): turb_naca0012_sst_sust.cfg_dir = "rans/naca0012" turb_naca0012_sst_sust.cfg_file = "turb_NACA0012_sst_sust.cfg" turb_naca0012_sst_sust.test_iter = 10 - turb_naca0012_sst_sust.test_vals = [-12.082056, -14.837177, -5.733436, 1.000893, 0.019109, -2.240933] + turb_naca0012_sst_sust.test_vals = [-12.081999, -14.837177, -5.733436, 1.000893, 0.019109, -2.241048] turb_naca0012_sst_sust.test_vals_aarch64 = [-12.073964, -14.836726, -5.732390, 1.000050, 0.019144, -2.229074] turb_naca0012_sst_sust.timeout = 3200 test_list.append(turb_naca0012_sst_sust) @@ -618,7 +618,7 @@ def main(): axi_rans_air_nozzle_restart.cfg_dir = "axisymmetric_rans/air_nozzle" axi_rans_air_nozzle_restart.cfg_file = "air_nozzle_restart.cfg" axi_rans_air_nozzle_restart.test_iter = 10 - axi_rans_air_nozzle_restart.test_vals = [-11.056236, -5.334117, -8.842317, -4.067917, 0.000000] + axi_rans_air_nozzle_restart.test_vals = [-11.056235, -5.334118, -8.842315, -4.067894, 0.000000] axi_rans_air_nozzle_restart.test_vals_aarch64 = [-14.143310, -9.163287, -10.858232, -5.787715, 0.000000] axi_rans_air_nozzle_restart.tol = 0.0001 test_list.append(axi_rans_air_nozzle_restart) @@ -1206,7 +1206,7 @@ def main(): transonic_stator_restart.cfg_dir = "turbomachinery/transonic_stator_2D" transonic_stator_restart.cfg_file = "transonic_stator_restart.cfg" transonic_stator_restart.test_iter = 20 - transonic_stator_restart.test_vals = [-4.365633, -2.482254, -2.081248, 1.729767, -1.467304, 3.231076, -471620.000000, 94.837000, -0.046836] + transonic_stator_restart.test_vals = [-4.365636, -2.482248, -2.081252, 1.729763, -1.467305, 3.231073, -471620.000000, 94.837000, -0.046834] transonic_stator_restart.test_vals_aarch64 = [-4.437809, -2.553049, -2.164729, 1.657542, -1.356823, 3.178788, -471620.000000, 94.842000, -0.040365] test_list.append(transonic_stator_restart) @@ -1228,7 +1228,7 @@ def main(): uniform_flow.cfg_dir = "sliding_interface/uniform_flow" uniform_flow.cfg_file = "uniform_NN.cfg" uniform_flow.test_iter = 5 - uniform_flow.test_vals = [5.000000, 0.000000, -0.195001, -10.624458] + uniform_flow.test_vals = [5.000000, 0.000000, -0.195001, -10.624454] uniform_flow.unsteady = True uniform_flow.multizone = True test_list.append(uniform_flow) @@ -1298,7 +1298,7 @@ def main(): bars_SST_2D.cfg_dir = "sliding_interface/bars_SST_2D" bars_SST_2D.cfg_file = "bars.cfg" bars_SST_2D.test_iter = 13 - bars_SST_2D.test_vals = [13.000000, -0.395917, -1.480217] + bars_SST_2D.test_vals = [13.000000, -0.395917, -1.480210] bars_SST_2D.multizone = True test_list.append(bars_SST_2D) @@ -1498,7 +1498,7 @@ def main(): pywrapper_turb_naca0012_sst.cfg_dir = "rans/naca0012" pywrapper_turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" pywrapper_turb_naca0012_sst.test_iter = 10 - pywrapper_turb_naca0012_sst.test_vals = [-12.094759, -15.251093, -5.906365, 1.070413, 0.015775, -2.376032, 0.000000] + pywrapper_turb_naca0012_sst.test_vals = [-12.094749, -15.251093, -5.906365, 1.070413, 0.015775, -2.376096, 0.000000] pywrapper_turb_naca0012_sst.test_vals_aarch64 = [-12.075620, -15.246688, -5.861276, 1.070036, 0.015841, -1.991001, 0.000000] pywrapper_turb_naca0012_sst.command = TestCase.Command("mpirun -np 2", "SU2_CFD.py", "--parallel -f") pywrapper_turb_naca0012_sst.timeout = 3200 diff --git a/TestCases/parallel_regression_AD.py b/TestCases/parallel_regression_AD.py index 561591099140..5c06556d4fb9 100644 --- a/TestCases/parallel_regression_AD.py +++ b/TestCases/parallel_regression_AD.py @@ -217,7 +217,7 @@ def main(): discadj_pitchingNACA0012.cfg_dir = "disc_adj_euler/naca0012_pitching" discadj_pitchingNACA0012.cfg_file = "inv_NACA0012_pitching.cfg" discadj_pitchingNACA0012.test_iter = 4 - discadj_pitchingNACA0012.test_vals = [-1.040019, -1.508690, -0.006062, 0.000012] + discadj_pitchingNACA0012.test_vals = [-1.039711, -1.508402, -0.006059, 0.000012] discadj_pitchingNACA0012.tol = 0.01 discadj_pitchingNACA0012.unsteady = True test_list.append(discadj_pitchingNACA0012) @@ -251,7 +251,7 @@ def main(): discadj_trans_stator.cfg_dir = "disc_adj_turbomachinery/transonic_stator_2D" discadj_trans_stator.cfg_file = "transonic_stator.cfg" discadj_trans_stator.test_iter = 79 - discadj_trans_stator.test_vals = [79.000000, -7.555647, -10.335486, -10.356919, -13.629543] + discadj_trans_stator.test_vals = [79.000000, -7.555663, -10.335501, -10.356934, -13.629559] discadj_trans_stator.test_vals_aarch64 = [79.000000, -7.555647, -10.335486, -10.356919, -13.629543] test_list.append(discadj_trans_stator) diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index 579c9071f1bf..1d7cd05c5079 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -200,7 +200,7 @@ def main(): poiseuille_profile.cfg_dir = "navierstokes/poiseuille" poiseuille_profile.cfg_file = "profile_poiseuille.cfg" poiseuille_profile.test_iter = 10 - poiseuille_profile.test_vals = [-12.003743, -7.573444, -0.000000, 2.089953] + poiseuille_profile.test_vals = [-12.003747, -7.573848, -0.000000, 2.089953] poiseuille_profile.test_vals_aarch64 = [-12.009012, -7.262299, -0.000000, 2.089953] #last 4 columns test_list.append(poiseuille_profile) @@ -285,7 +285,7 @@ def main(): turb_naca0012_sa.cfg_dir = "rans/naca0012" turb_naca0012_sa.cfg_file = "turb_NACA0012_sa.cfg" turb_naca0012_sa.test_iter = 5 - turb_naca0012_sa.test_vals = [-12.037309, -16.384159, 1.080346, 0.018385, 20.000000, -3.456846, 20.000000, -4.641251, 0.000000] + turb_naca0012_sa.test_vals = [-12.037323, -16.384159, 1.080346, 0.018385, 20.000000, -3.457510, 20.000000, -4.641262, 0.000000] turb_naca0012_sa.test_vals_aarch64 = [-12.037297, -16.384158, 1.080346, 0.018385, 20.000000, -3.455886, 20.000000, -4.641247, 0.000000] turb_naca0012_sa.timeout = 3200 test_list.append(turb_naca0012_sa) @@ -295,7 +295,7 @@ def main(): turb_naca0012_sst.cfg_dir = "rans/naca0012" turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" turb_naca0012_sst.test_iter = 10 - turb_naca0012_sst.test_vals = [-12.094445, -15.251083, -5.906366, 1.070413, 0.015775, -3.178548, 0.000000] + turb_naca0012_sst.test_vals = [-12.094385, -15.251083, -5.906366, 1.070413, 0.015775, -3.178761, 0.000000] turb_naca0012_sst.test_vals_aarch64 = [-12.076068, -15.246740, -5.861280, 1.070036, 0.015841, -3.297854, 0.000000] turb_naca0012_sst.timeout = 3200 test_list.append(turb_naca0012_sst) @@ -314,7 +314,7 @@ def main(): turb_naca0012_sst_sust_restart.cfg_dir = "rans/naca0012" turb_naca0012_sst_sust_restart.cfg_file = "turb_NACA0012_sst_sust.cfg" turb_naca0012_sst_sust_restart.test_iter = 10 - turb_naca0012_sst_sust_restart.test_vals = [-12.080455, -14.837169, -5.733461, 1.000893, 0.019109, -2.634140] + turb_naca0012_sst_sust_restart.test_vals = [-12.080469, -14.837169, -5.733461, 1.000893, 0.019109, -2.634201] turb_naca0012_sst_sust_restart.test_vals_aarch64 = [-12.074189, -14.836725, -5.732398, 1.000050, 0.019144, -3.315560] turb_naca0012_sst_sust_restart.timeout = 3200 test_list.append(turb_naca0012_sst_sust_restart) @@ -346,7 +346,7 @@ def main(): axi_rans_air_nozzle_restart.cfg_dir = "axisymmetric_rans/air_nozzle" axi_rans_air_nozzle_restart.cfg_file = "air_nozzle_restart.cfg" axi_rans_air_nozzle_restart.test_iter = 10 - axi_rans_air_nozzle_restart.test_vals = [-11.054279, -5.328901, -8.835585, -4.056810, 0.000000] + axi_rans_air_nozzle_restart.test_vals = [-11.054280, -5.328907, -8.835561, -4.056831, 0.000000] axi_rans_air_nozzle_restart.test_vals_aarch64 = [-14.143715, -9.170705, -10.848554, -5.776746, 0.000000] axi_rans_air_nozzle_restart.tol = 0.0001 test_list.append(axi_rans_air_nozzle_restart) @@ -957,7 +957,7 @@ def main(): uniform_flow.cfg_dir = "sliding_interface/uniform_flow" uniform_flow.cfg_file = "uniform_NN.cfg" uniform_flow.test_iter = 2 - uniform_flow.test_vals = [2.000000, 0.000000, -0.230639, -13.253604] + uniform_flow.test_vals = [2.000000, 0.000000, -0.230639, -13.249870] uniform_flow.test_vals_aarch64 = [2.000000, 0.000000, -0.230641, -13.249000] uniform_flow.tol = 0.000001 uniform_flow.unsteady = True @@ -1031,7 +1031,7 @@ def main(): bars_SST_2D.cfg_dir = "sliding_interface/bars_SST_2D" bars_SST_2D.cfg_file = "bars.cfg" bars_SST_2D.test_iter = 13 - bars_SST_2D.test_vals = [13.000000, -0.393225, -1.462257] + bars_SST_2D.test_vals = [13.000000, -0.393225, -1.462251] bars_SST_2D.multizone = True test_list.append(bars_SST_2D) @@ -1109,7 +1109,7 @@ def main(): fsi_cht.cfg_dir = "fea_fsi/stat_fsi" fsi_cht.cfg_file = "config.cfg" fsi_cht.test_iter = 20 - fsi_cht.test_vals = [5.000000, -5.076991, -5.379442, -9.247793, -9.319193, -9.184753, 608.350000, -0.012973, 0.000000, 30.000000] + fsi_cht.test_vals = [5.000000, -5.077019, -5.379465, -9.247811, -9.319812, -9.185005, 608.350000, -0.012973, 0.000000, 29.000000] fsi_cht.multizone = True test_list.append(fsi_cht) @@ -1598,7 +1598,7 @@ def main(): pywrapper_turb_naca0012_sst.cfg_dir = "rans/naca0012" pywrapper_turb_naca0012_sst.cfg_file = "turb_NACA0012_sst.cfg" pywrapper_turb_naca0012_sst.test_iter = 10 - pywrapper_turb_naca0012_sst.test_vals = [-12.094445, -15.251083, -5.906366, 1.070413, 0.015775, -3.178548, 0.000000] + pywrapper_turb_naca0012_sst.test_vals = [-12.094385, -15.251083, -5.906366, 1.070413, 0.015775, -3.178761, 0.000000] pywrapper_turb_naca0012_sst.test_vals_aarch64 = [-12.076068, -15.246740, -5.861280, 1.070036, 0.015841, -3.297854, 0.000000] pywrapper_turb_naca0012_sst.command = TestCase.Command(exec = "SU2_CFD.py", param = "-f") pywrapper_turb_naca0012_sst.timeout = 3200 diff --git a/TestCases/serial_regression_AD.py b/TestCases/serial_regression_AD.py index 1f4d63fc5bb5..8c556a3eef14 100644 --- a/TestCases/serial_regression_AD.py +++ b/TestCases/serial_regression_AD.py @@ -200,7 +200,7 @@ def main(): discadj_trans_stator.cfg_dir = "disc_adj_turbomachinery/transonic_stator_2D" discadj_trans_stator.cfg_file = "transonic_stator.cfg" discadj_trans_stator.test_iter = 79 - discadj_trans_stator.test_vals = [79.000000, -7.308167, -9.891117, -10.038669, -13.368501] + discadj_trans_stator.test_vals = [79.000000, -7.308168, -9.891121, -10.038669, -13.368502] discadj_trans_stator.test_vals_aarch64 = [79.000000, -7.308167, -9.891117, -10.038669, -13.368501] test_list.append(discadj_trans_stator) diff --git a/TestCases/vandv.py b/TestCases/vandv.py index 01bace8011e8..5913b5753da6 100644 --- a/TestCases/vandv.py +++ b/TestCases/vandv.py @@ -63,7 +63,7 @@ def main(): flatplate_sst1994m.cfg_dir = "vandv/rans/flatplate" flatplate_sst1994m.cfg_file = "turb_flatplate_sst.cfg" flatplate_sst1994m.test_iter = 5 - flatplate_sst1994m.test_vals = [-13.040081, -10.136488, -10.941822, -7.977797, -10.323859, -4.732970, 0.002801] + flatplate_sst1994m.test_vals = [-13.041846, -10.136935, -10.935141, -7.992679, -10.323868, -4.732448, 0.002801] flatplate_sst1994m.test_vals_aarch64 = [-13.021715, -9.534786, -10.401912, -7.501836, -9.750800, -4.850665, 0.002807] test_list.append(flatplate_sst1994m) @@ -72,7 +72,7 @@ def main(): bump_sst1994m.cfg_dir = "vandv/rans/bump_in_channel" bump_sst1994m.cfg_file = "turb_bump_sst.cfg" bump_sst1994m.test_iter = 5 - bump_sst1994m.test_vals = [-11.928608, -10.096068, -9.512742, -6.445912, -11.774007, -6.978081, 0.004931] + bump_sst1994m.test_vals = [-11.928390, -10.096025, -9.513319, -6.445738, -11.773755, -6.978794, 0.004931] bump_sst1994m.test_vals_aarch64 = [-13.042689, -10.812982, -10.604523, -7.655547, -10.816257, -5.308083, 0.004911] test_list.append(bump_sst1994m) @@ -99,7 +99,7 @@ def main(): dsma661_sa.cfg_dir = "vandv/rans/dsma661" dsma661_sa.cfg_file = "dsma661_sa_config.cfg" dsma661_sa.test_iter = 5 - dsma661_sa.test_vals = [-11.240967, -8.243826, -8.958188, -5.895885, -10.737669, 0.155687, 0.024232] + dsma661_sa.test_vals = [-11.256607, -8.243150, -9.026822, -5.919168, -10.737670, 0.155687, 0.024232] dsma661_sa.test_vals_aarch64 = [-11.293183, -8.241775, -9.083761, -6.011398, -10.737680, 0.155687, 0.024232] test_list.append(dsma661_sa) @@ -108,7 +108,7 @@ def main(): dsma661_sst.cfg_dir = "vandv/rans/dsma661" dsma661_sst.cfg_file = "dsma661_sst_config.cfg" dsma661_sst.test_iter = 5 - dsma661_sst.test_vals = [-11.023511, -8.156964, -9.060365, -5.934828, -10.651368, -7.898758, 0.155882, 0.023344] + dsma661_sst.test_vals = [-11.024598, -8.157367, -9.084723, -5.944329, -10.650922, -7.880638, 0.155882, 0.023344] dsma661_sst.test_vals_aarch64 = [-10.977195, -8.403731, -8.747068, -5.808899, -10.522786, -7.369851, 0.155875, 0.023353] test_list.append(dsma661_sst) diff --git a/UnitTests/Common/linear_algebra/quantization_tests.cpp b/UnitTests/Common/linear_algebra/quantization_tests.cpp index d074a6f16e2c..e7c95ab815cc 100644 --- a/UnitTests/Common/linear_algebra/quantization_tests.cpp +++ b/UnitTests/Common/linear_algebra/quantization_tests.cpp @@ -1,6 +1,7 @@ /*! * \file quantization_tests.cpp - * \brief Unit tests for the int8 row-scaled block quantization used by Q_LU_SGS. + * \brief Unit tests for the row-scaled block quantization used by Q_LU_SGS (int8 values, with + * one uint8 per-row scale holding the biased float exponent). * \author P. Gomes * \version 8.5.0 "Harrier" * @@ -28,8 +29,10 @@ #include "catch.hpp" #include "../../../Common/include/linear_algebra/CSysMatrix.hpp" +#include + /*--- Row-major access into a flat quantized block, matching CBlockView's decode. ---*/ -static su2double Decode(const int8_t* qs, const int8_t* qv, unsigned long nVar, unsigned long r, unsigned long c) { +static su2double Decode(const uint8_t* qs, const int8_t* qv, unsigned long nVar, unsigned long r, unsigned long c) { return static_cast(qv[r * nVar + c]) * DecodeQuantScale(qs[r]); } @@ -43,12 +46,14 @@ TEST_CASE("Quantization round-trip is stable for well-behaved values", "[LinearA const su2double row[nVar] = {3.0, -2.0, 1.5, 0.75}; auto f = [&](unsigned long, unsigned long c) { return row[c]; }; - int8_t qs1[nVar], qv1[nVar * nVar]; + uint8_t qs1[nVar]; + int8_t qv1[nVar * nVar]; EncodeQuantBlock(f, qs1, qv1, nVar); auto decoded = [&](unsigned long, unsigned long c) { return Decode(qs1, qv1, nVar, 0, c); }; - int8_t qs2[nVar], qv2[nVar * nVar]; + uint8_t qs2[nVar]; + int8_t qv2[nVar * nVar]; EncodeQuantBlock(decoded, qs2, qv2, nVar); CHECK(qs2[0] == qs1[0]); @@ -57,7 +62,8 @@ TEST_CASE("Quantization round-trip is stable for well-behaved values", "[LinearA /*--- A second decode/encode cycle from the now-stable representation must * reproduce the exact same codes again. ---*/ auto decoded2 = [&](unsigned long, unsigned long c) { return Decode(qs2, qv2, nVar, 0, c); }; - int8_t qs3[nVar], qv3[nVar * nVar]; + uint8_t qs3[nVar]; + int8_t qv3[nVar * nVar]; EncodeQuantBlock(decoded2, qs3, qv3, nVar); CHECK(qs3[0] == qs2[0]); @@ -65,8 +71,8 @@ TEST_CASE("Quantization round-trip is stable for well-behaved values", "[LinearA } TEST_CASE("Quantization saturates and truncates with a known, bounded error", "[LinearAlgebra]") { - /*--- One row engineered so that, at the scale it forces (2^0 = 1 here, since the - * largest magnitude in the row is in [64, 128)): + /*--- One row engineered so that, at the scale it forces (2^0 = 1 here, stored as the biased + * exponent 127, since the largest magnitude in the row is in [64, 128)): * col 0: 127.9 -> rounds to 128, clamped to 127 (int8 max), error ~0.9 (close to * the theoretical worst case: clamping can only ever push a value that * rounds to +128 down to +127, an error that approaches but never reaches @@ -82,10 +88,11 @@ TEST_CASE("Quantization saturates and truncates with a known, bounded error", "[ const su2double row[nVar] = {127.9, -127.9, 0.2, 60.0}; auto f = [&](unsigned long, unsigned long c) { return row[c]; }; - int8_t qs[nVar], qv[nVar * nVar]; + uint8_t qs[nVar]; + int8_t qv[nVar * nVar]; EncodeQuantBlock(f, qs, qv, nVar); - CHECK(qs[0] == 0); + CHECK(static_cast(qs[0]) == 127); // Biased exponent of 2^0. CHECK(static_cast(qv[0]) == 127); // Saturated at the int8 maximum. CHECK(static_cast(qv[1]) == -128); // Hits the int8 minimum exactly. CHECK(static_cast(qv[2]) == 0); // Truncated to zero. From c46e5eb67243d453424b6a99f6c1f3066a14518f Mon Sep 17 00:00:00 2001 From: Josh Kelly <81244680+joshkellyjak@users.noreply.github.com> Date: Mon, 24 Aug 2026 19:48:17 +0200 Subject: [PATCH 35/61] Fix turbo windows type error (#2876) ## Proposed Changes Fix type comparison error introduced by turbo adjoints ## PR Checklist *Put an X by all that apply. You can fill this out after submitting the PR. If you have any questions, don't hesitate to ask! We want to help. These are a guide for you to know what the reviewers will be looking for in your contribution.* - [X] I am submitting my contribution to the develop branch. - [X] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [X] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [X] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [X] I have added a test case that demonstrates my contribution, if necessary. - [X] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Co-authored-by: Josh Kelly --- Common/src/interface_interpolation/CMixingPlane.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Common/src/interface_interpolation/CMixingPlane.cpp b/Common/src/interface_interpolation/CMixingPlane.cpp index b7f06b51e62d..7459498b27ec 100644 --- a/Common/src/interface_interpolation/CMixingPlane.cpp +++ b/Common/src/interface_interpolation/CMixingPlane.cpp @@ -209,13 +209,13 @@ void CMixingPlane::WriteInterpolationDetails(const std::string& filename, const outFile << "---------------------\n"; // Find max donor span - unsigned long maxDonorSpan = 0; + size_t maxDonorSpan = 0; for (const auto& ts : targetSpans[iMarkerInt]) { maxDonorSpan = std::max(maxDonorSpan, ts.donorSpan); } // Group by donor span - for (unsigned long iDonor = 0; iDonor <= maxDonorSpan; iDonor++) { + for (size_t iDonor = 0; iDonor <= maxDonorSpan; iDonor++) { bool hasTargets = false; std::ostringstream targets; From 25f2a79996c149b27f80bf9c19b7da7590eb402f Mon Sep 17 00:00:00 2001 From: tkiymaz <79564236+tkiymaz@users.noreply.github.com> Date: Wed, 26 Aug 2026 19:10:27 +0200 Subject: [PATCH 36/61] Improved Trapezoidal Mapping for FGM - Update (#2845) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Proposed Changes The original trapezoidal map implementation needs excessive memory for large lookup tables (LUT) (e.g., 300+ MB for tables with ~80k points). This PR introduces a new memory-efficient trapezoidal map implementation (LUT_FAST) for Flamelet-Generated Manifold (FGM) lookup tables, based on LUT algorithm of Pedro Gomes. The new implementation reduces memory usage while maintaining query performance. A new unit test is created and all tests are passed. ## Related Work This code is an implementation of https://github.com/pcarruscag/LUT to SU2. ## PR Checklist *Put an X by all that apply. You can fill this out after submitting the PR. If you have any questions, don't hesitate to ask! We want to help. These are a guide for you to know what the reviewers will be looking for in your contribution.* - [ x] I am submitting my contribution to the develop branch. - [ x] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [ ] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [ ] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [x ] I have added a test case that demonstrates my contribution, if necessary. - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --------- Co-authored-by: tkiymaz Co-authored-by: Nijso Co-authored-by: Berk Kıymaz Co-authored-by: Berk Kıymaz --- Common/include/containers/CLookUpTable.hpp | 23 - Common/include/containers/CTrapezoidalMap.hpp | 516 ++++++++++++++++-- Common/src/containers/CLookUpTable.cpp | 97 +--- Common/src/containers/CTrapezoidalMap.cpp | 283 ---------- Common/src/containers/meson.build | 3 +- SU2_CFD/src/fluid/CFluidFlamelet.cpp | 37 +- TestCases/parallel_regression_AD.py | 4 +- 7 files changed, 510 insertions(+), 453 deletions(-) delete mode 100644 Common/src/containers/CTrapezoidalMap.cpp diff --git a/Common/include/containers/CLookUpTable.hpp b/Common/include/containers/CLookUpTable.hpp index c41819521538..e6bf86d074d2 100644 --- a/Common/include/containers/CLookUpTable.hpp +++ b/Common/include/containers/CLookUpTable.hpp @@ -260,29 +260,6 @@ class CLookUpTable { void InterpolateToNearestNeighbors(const su2double val_CV1, const su2double val_CV2, const std::string& name_var, su2double* var_val, const unsigned long i_level = 0); - /*! - * \brief Determine if a point P(val_CV1,val_CV2) is inside the triangle val_id_triangle. - * \param[in] val_CV1 - First coordinate of point P(val_CV1,val_CV2) to check. - * \param[in] val_CV2 - Second coordinate of point P(val_CV1,val_CV2) to check. - * \param[in] val_id_triangle - ID of the triangle to check. - * \returns True if the point is in the triangle, false if it is outside. - */ - bool IsInTriangle(su2double val_CV1, su2double val_CV2, unsigned long val_id_triangle, unsigned long i_level = 0); - - /*! - * \brief Compute the area of a triangle given the 3 points of the triangle. - * \param[in] x1 - The coordinates of the points P1(x1,y1), P2(x2,y2) and P3(x3,y3). - * \param[in] y1 - The coordinates of the points P1(x1,y1), P2(x2,y2) and P3(x3,y3). - * \param[in] x2 - The coordinates of the points P1(x1,y1), P2(x2,y2) and P3(x3,y3). - * \param[in] y2 - The coordinates of the points P1(x1,y1), P2(x2,y2) and P3(x3,y3). - * \param[in] x3 - The coordinates of the points P1(x1,y1), P2(x2,y2) and P3(x3,y3). - * \param[in] y3 - The coordinates of the points P1(x1,y1), P2(x2,y2) and P3(x3,y3). - * \returns The absolute value of the area of the triangle. - */ - inline su2double TriArea(su2double x1, su2double y1, su2double x2, su2double y2, su2double x3, su2double y3) { - return abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) * 0.5); - } - /*! * \brief Compute the values of the first and second controlling variable based on normalized query coordinates * \param[in] inclusion_levels - Pair containing lower(first) and upper(second) table inclusion level indices. diff --git a/Common/include/containers/CTrapezoidalMap.hpp b/Common/include/containers/CTrapezoidalMap.hpp index a083b80d9feb..996eaa03eeb8 100644 --- a/Common/include/containers/CTrapezoidalMap.hpp +++ b/Common/include/containers/CTrapezoidalMap.hpp @@ -1,7 +1,8 @@ /*! * \file CTrapezoidalMap.hpp - * \brief Implementation of the trapezoidal map for tabulation and lookup of fluid properties - * \author D. Mayer, T. Economon + * \brief Memory-efficient trapezoidal map for 2D lookup table queries, + * based on the LUT implementation of P. Gomes (https://github.com/pcarruscag/LUT). + * \author T. Kiymaz, P. Gomes * \version 8.5.0 "Harrier" * * SU2 Project Website: https://su2code.github.io @@ -27,82 +28,485 @@ #pragma once -#include +#include +#include +#include +#include +#include +#include #include -#include "../../Common/include/linear_algebra/blas_structure.hpp" -#include "../../Common/include/toolboxes/CSquareMatrixCM.hpp" +#include "../basic_types/datatype_structure.hpp" +#include "C2DContainer.hpp" + +namespace su2_lut { + +using IntT = int32_t; +using RealT = su2double; + +/*--- Row-major integer matrices with a compile-time number of columns. ---*/ +template +using MatrixNi = C2DContainer; +using Matrix2i = MatrixNi<2>; +using Matrix3i = MatrixNi<3>; +using VectorInt = std::vector; +using VectorReal = std::vector; + +/*--- The map is defined by the limits of the bands in the x direction and a CSR of + * the edge IDs in each band, sorted by the edge y position at the band midpoint. ---*/ +struct TrapezoidalMap { + VectorInt offsets, edge_id; + VectorReal x_bands, edge_y; +}; /*! - * \class CTrapezoidalMap - * \ingroup LookUpInterp - * \brief Construction of trapezoidal map for tabulated lookup - * \author: D. Mayer, T. Economon - * \version 8.5.0 "Harrier" + * \brief Orders points by ascending x coordinates and updates triangle indices. */ -class CTrapezoidalMap { - protected: - /* The unique values of x which exist in the data */ - std::vector unique_bands_x; +inline void ReorderPoints(Matrix3i& triangles, VectorReal& x, VectorReal& y) { + const IntT n_pts = static_cast(x.size()); + + std::vector perm(n_pts); + std::iota(perm.begin(), perm.end(), 0); + std::sort(perm.begin(), perm.end(), + [&x, &y](const auto i, const auto j) { return x[i] != x[j] ? x[i] < x[j] : y[i] < y[j]; }); + + auto reorder = [n_pts, &perm](const auto& v) { + VectorReal tmp(n_pts); + for (IntT i = 0; i < n_pts; ++i) { + tmp[i] = v[perm[i]]; + } + return tmp; + }; + x = reorder(x); + y = reorder(y); + + std::vector inv_perm(n_pts); + for (IntT i = 0; i < n_pts; ++i) { + inv_perm[perm[i]] = i; + } + for (IntT i = 0; i < static_cast(triangles.rows()); ++i) { + for (IntT j = 0; j < 3; ++j) { + triangles(i, j) = inv_perm[triangles(i, j)]; + } + } +} + +/*! + * \brief Extracts unique edges from triangles. Edges are defined by two point IDs and + * up to two adjacent triangles (boundary edges have the second triangle ID < 0). + */ +inline void ExtractEdges(const Matrix3i& triangles, Matrix2i& edge_pts, Matrix2i& edge_faces) { + std::vector> edges; + edges.resize(3 * triangles.rows()); + + for (IntT i_tri = 0; i_tri < static_cast(triangles.rows()); ++i_tri) { + for (IntT i = 0; i < 3; ++i) { + const IntT j = (i + 1) % 3; + const IntT i_pt = std::min(triangles(i_tri, i), triangles(i_tri, j)); + const IntT j_pt = std::max(triangles(i_tri, i), triangles(i_tri, j)); + edges[3 * i_tri + i] = {i_pt, j_pt, i_tri}; + } + } + + /*--- Sort to identify duplicates. ---*/ + std::sort(edges.begin(), edges.end(), + [](const auto& a, const auto& b) { return a[0] != b[0] ? (a[0] < b[0]) : (a[1] < b[1]); }); + + auto is_equal = [](const auto& a, const auto& b) { return a[0] == b[0] && a[1] == b[1]; }; + + IntT n_edges = 1; + for (IntT i = 1; i < static_cast(edges.size()); ++i) { + n_edges += static_cast(!is_equal(edges[i], edges[i - 1])); + } + + edge_pts.resize(n_edges, 2); + edge_faces.resize(n_edges, 2); + IntT pos = 0; + + auto new_edge = [&](const auto& edge) { + edge_pts(pos, 0) = edge[0]; + edge_pts(pos, 1) = edge[1]; + edge_faces(pos, 0) = edge[2]; + edge_faces(pos, 1) = -1; + ++pos; + }; + + new_edge(edges[0]); + for (IntT i = 1; i < static_cast(edges.size()); ++i) { + if (is_equal(edges[i], edges[i - 1])) { + edge_faces(pos - 1, 1) = edges[i][2]; + } else { + new_edge(edges[i]); + } + } +} + +/*! + * \brief Detects the x bands of the map. One band per unique x coordinate is used unless + * that would exceed max_bands, in which case equal-width bands are used to limit memory. + * \return Tuple of (n_bands, x_bands). + */ +inline auto DetectBands(const VectorReal& x, IntT max_bands = 0) { + if (max_bands <= 0) { + max_bands = std::min(IntT{5000}, static_cast(4.0 * std::sqrt(static_cast(x.size())))); + } + + IntT n_unique = 1; + for (IntT i = 1; i < static_cast(x.size()); ++i) { + if (x[i] != x[i - 1]) n_unique++; + } + + if (n_unique <= max_bands) { + const IntT n_bands = n_unique - 1; + VectorReal x_bands(n_unique); + IntT pos = 0; + x_bands[pos] = x[0]; + for (IntT i = 1; i < static_cast(x.size()); ++i) { + if (x[i] != x_bands[pos]) { + x_bands[++pos] = x[i]; + } + } + return std::make_tuple(n_bands, std::move(x_bands)); + } + + const RealT x_min = x.front(); + const RealT x_max = x.back(); + const RealT band_width = (x_max - x_min) / max_bands; + + VectorReal x_bands(max_bands + 1); + for (IntT i = 0; i <= max_bands; ++i) { + x_bands[i] = x_min + i * band_width; + } + x_bands[max_bands] = x_max; + + return std::make_tuple(max_bands, std::move(x_bands)); +} + +/*! + * \brief Builds the trapezoidal map for a set of edges (points must be ordered by x). + */ +inline void BuildTrapezoidalMap(const Matrix2i& edge_pts, const VectorReal& x, const VectorReal& y, + TrapezoidalMap& map) { + auto& x_bands = map.x_bands; + auto& offsets = map.offsets; + auto& edge_id = map.edge_id; + auto& edge_y = map.edge_y; + + auto clear_map = [&]() { + x_bands.clear(); + offsets.clear(); + edge_id.clear(); + edge_y.clear(); + }; + + const auto [n_bands, bands] = DetectBands(x); + x_bands = std::move(bands); + + if (n_bands <= 0) { + clear_map(); + return; + } + + auto find_band = [&x_bands, n_bands = n_bands](RealT x_val) -> IntT { + auto it = std::lower_bound(x_bands.begin(), x_bands.end(), x_val); + const IntT idx = static_cast(it - x_bands.begin()); + return std::min(std::max(IntT{0}, idx - 1), n_bands - 1); + }; + + /*--- Count edges per band. Each edge is stored in every band between the bands of its + * two endpoints (inclusive), a superset of the bands it overlaps. ---*/ + auto& counts = offsets; + counts.clear(); + counts.resize(n_bands + 1, 0); + + for (IntT i = 0; i < static_cast(edge_pts.rows()); ++i) { + const IntT band_0 = find_band(x[edge_pts(i, 0)]); + const IntT band_1 = find_band(x[edge_pts(i, 1)]); + + for (IntT j = std::min(band_0, band_1); j <= std::max(band_0, band_1); ++j) { + ++counts[j + 1]; + } + } + + /*--- Convert counts to offsets (CSR format). ---*/ + for (IntT i = 2; i < static_cast(offsets.size()); ++i) { + offsets[i] += offsets[i - 1]; + } + + /*--- Give up (build failure) rather than allocating an excessive amount of memory. ---*/ + const size_t memory_mb = static_cast(offsets.back()) * (sizeof(IntT) + sizeof(RealT)) / (1024 * 1024); + if (memory_mb > 2048) { + clear_map(); + return; + } + + edge_id.resize(offsets.back()); + edge_y.resize(offsets.back()); + auto pos = offsets; + + for (IntT i_edge = 0; i_edge < static_cast(edge_pts.rows()); ++i_edge) { + const IntT pt_0 = edge_pts(i_edge, 0); + const IntT pt_1 = edge_pts(i_edge, 1); + const RealT x_0 = x[pt_0], y_0 = y[pt_0]; + const RealT x_1 = x[pt_1], y_1 = y[pt_1]; + + const IntT band_0 = find_band(x_0); + const IntT band_1 = find_band(x_1); + + const RealT dx = x_1 - x_0; + const bool vertical = std::abs(SU2_TYPE::GetValue(dx)) < 1e-30; + const RealT dy_dx = vertical ? RealT{0} : (y_1 - y_0) / dx; + + for (IntT j = std::min(band_0, band_1); j <= std::max(band_0, band_1); ++j) { + edge_id[pos[j]] = i_edge; + const RealT x_mid = (x_bands[j] + x_bands[j + 1]) / 2; + edge_y[pos[j]] = vertical ? RealT((y_0 + y_1) / 2) : RealT(y_0 + dy_dx * (x_mid - x_0)); + ++pos[j]; + } + } - su2activematrix edge_limits_x; - su2activematrix edge_limits_y; + /*--- Sort the edges in each band by y coordinate. ---*/ + std::vector> tmp; + for (IntT i = 0; i < n_bands; ++i) { + const IntT begin = offsets[i]; + const IntT end = offsets[i + 1]; + if (begin >= end) continue; - su2vector > edge_to_triangle; + tmp.resize(end - begin); + for (auto k = begin; k < end; ++k) { + tmp[k - begin] = {edge_id[k], edge_y[k]}; + } + std::sort(tmp.begin(), tmp.end(), [](const auto& a, const auto& b) { return a.second < b.second; }); + for (auto k = begin; k < end; ++k) { + edge_id[k] = tmp[k - begin].first; + edge_y[k] = tmp[k - begin].second; + } + } +} + +/*! + * \brief Returns the IDs of the edges directly below and above a query point + * (either ID can be -1 if the point is at a boundary). + */ +inline auto QueryTrapezoidalMap(const TrapezoidalMap& map, const Matrix2i& edge_pts, const VectorReal& x_coords, + const VectorReal& y_coords, const RealT& x, const RealT& y) { + if (map.x_bands.size() < 2 || map.offsets.empty()) { + return std::make_pair(IntT{-1}, IntT{-1}); + } + + const auto& x_bands = map.x_bands; + const IntT n_bands = static_cast(x_bands.size()) - 1; + auto it = std::lower_bound(x_bands.begin(), x_bands.end(), x); + const IntT d = static_cast(it - x_bands.begin()); + const IntT band_idx = std::min(std::max(IntT{0}, d - 1), n_bands - 1); + + RealT best_y_below = -1e300; + RealT best_y_above = 1e300; + IntT edge_below = -1; + IntT edge_above = -1; + + const IntT begin = map.offsets[band_idx]; + const IntT end = map.offsets[band_idx + 1]; - /* The value that each edge which intersects the band takes within that - * same band. Used to sort the edges */ - su2vector > > y_edge_at_band_mid; + for (IntT k = begin; k < end; ++k) { + const IntT e_id = map.edge_id[k]; - double memory_footprint = 0; + const IntT p0 = edge_pts(e_id, 0); + const IntT p1 = edge_pts(e_id, 1); + const RealT x0 = x_coords[p0], y0 = y_coords[p0]; + const RealT x1 = x_coords[p1], y1 = y_coords[p1]; + + if (x < std::min(x0, x1) - 1e-10 || x > std::max(x0, x1) + 1e-10) { + continue; + } + + /*--- y position of the edge at the query x. ---*/ + RealT edge_y_at_x; + const RealT dx = x1 - x0; + if (std::abs(SU2_TYPE::GetValue(dx)) < 1e-30) { + edge_y_at_x = (y0 + y1) / 2.0; + } else { + edge_y_at_x = y0 + (x - x0) / dx * (y1 - y0); + } + + if (edge_y_at_x <= y + 1e-10 && edge_y_at_x > best_y_below) { + best_y_below = edge_y_at_x; + edge_below = e_id; + } + if (edge_y_at_x >= y - 1e-10 && edge_y_at_x < best_y_above) { + best_y_above = edge_y_at_x; + edge_above = e_id; + } + } + + return std::make_pair(edge_below, edge_above); +} + +/*! + * \brief Returns the IDs of the triangles adjacent to two query edges (up to 3 triangles). + */ +inline auto AdjacentTriangles(const IntT edge_0, const IntT edge_1, const Matrix2i& edge_faces) { + std::array tris = {-1, -1, -1}; + IntT pos = 0; + + auto insert = [&tris, &pos](const IntT t) { + if (t < 0) return; + for (IntT i = 0; i < pos; ++i) { + if (t == tris[i]) return; + } + tris[pos++] = t; + }; + + auto get_tris = [&edge_faces](const IntT e) { + if (e < 0) return std::array{IntT{-1}, IntT{-1}}; + return std::array{edge_faces(e, 0), edge_faces(e, 1)}; + }; + + for (const auto e : {edge_0, edge_1}) { + for (const auto t : get_tris(e)) { + insert(t); + } + } + return tris; +} + +/*! + * \brief Computes the barycentric coordinates of point (x_q, y_q) in a triangle. + */ +inline auto TriangleCoords(const IntT i_tri, const Matrix3i& triangles, const VectorReal& x, const VectorReal& y, + const RealT x_q, const RealT y_q) { + const IntT p0 = triangles(i_tri, 0); + const IntT p1 = triangles(i_tri, 1); + const IntT p2 = triangles(i_tri, 2); + + const RealT x0 = x[p0], y0 = y[p0]; + const RealT x1 = x[p1], y1 = y[p1]; + const RealT x2 = x[p2], y2 = y[p2]; + + const RealT dx1 = x1 - x0, dy1 = y1 - y0; + const RealT dx2 = x2 - x0, dy2 = y2 - y0; + + auto cross = [](const RealT ux, const RealT uy, const RealT vx, const RealT vy) { return ux * vy - uy * vx; }; + + const RealT det = cross(dx1, dy1, dx2, dy2); + if (std::abs(SU2_TYPE::GetValue(det)) < 1e-30) { + return std::array{RealT{0}, RealT{0}, RealT{0}}; + } + + const RealT inv_det = 1.0 / det; + const RealT a = (cross(x_q, y_q, dx2, dy2) - cross(x0, y0, dx2, dy2)) * inv_det; + const RealT b = (cross(x0, y0, dx1, dy1) - cross(x_q, y_q, dx1, dy1)) * inv_det; + + return std::array{1 - a - b, a, b}; +} + +/*! + * \brief Checks if a point is inside a triangle based on its barycentric coordinates. + */ +inline bool InTriangle(const std::array& coords, const RealT tol = 0.0) { + return coords[0] >= -tol && coords[1] >= -tol && coords[2] >= -tol; +} + +/*! + * \brief Finds the triangle containing a point using the trapezoidal map. + */ +inline IntT FindTriangle(const TrapezoidalMap& map, const Matrix3i& triangles, const Matrix2i& edge_pts, + const Matrix2i& edge_faces, const VectorReal& x, const VectorReal& y, const RealT x_q, + const RealT y_q, std::array& bary_out) { + const auto [e_below, e_above] = QueryTrapezoidalMap(map, edge_pts, x, y, x_q, y_q); + const auto candidates = AdjacentTriangles(e_below, e_above, edge_faces); + + const RealT tol = 1e-12; + for (const auto t : candidates) { + if (t < 0) continue; + + const auto coords = TriangleCoords(t, triangles, x, y, x_q, y_q); + if (InTriangle(coords, tol)) { + bary_out = coords; + return t; + } + } + + bary_out = {0.0, 0.0, 0.0}; + return -1; +} + +} // namespace su2_lut + +/*! + * \class CTrapezoidalMap + * \ingroup LookUpInterp + * \brief Trapezoidal map for finding the triangle containing a query point in a 2D triangulation. + */ +class CTrapezoidalMap { + private: + su2_lut::Matrix3i triangles; + su2_lut::Matrix2i edge_pts, edge_faces; + su2_lut::VectorReal x_coords, y_coords; + su2_lut::TrapezoidalMap map; + + unsigned long n_points = 0; + unsigned long n_triangles = 0; public: CTrapezoidalMap() = default; - CTrapezoidalMap(const su2double* samples_x, const su2double* samples_y, const unsigned long size, - const std::vector >& edges, - const su2vector >& edge_to_triangle, bool display = false); - /*! - * \brief return the index to the triangle that contains the coordinates (val_x,val_y) - * \param[in] val_x - x-coordinate or first independent variable - * \param[in] val_y - y-coordinate or second independent variable - * \param[out] val_index - index to the triangle + * \brief Build the trapezoidal map from a triangulation. + * \return True on success. */ - unsigned long GetTriangle(const su2double val_x, const su2double val_y); + bool Build(unsigned long num_points, unsigned long num_triangles, const su2double* x, const su2double* y, + const unsigned long* connectivity) { + n_points = num_points; + n_triangles = num_triangles; - /*! - * \brief get the indices of the vertical coordinate band (xmin,xmax) in the 2D search space - * that contains the coordinate val_x - * \param[in] val_x - x-coordinate or first independent variable - * \param[out] val_band - a pair(i_low,i_up) , the lower index and upper index between which the value val_x - * can be found - */ - std::pair GetBand(const su2double val_x); + if (num_points == 0 || num_triangles == 0) return false; - /*! - * \brief for a given coordinate (val_x,value), known to be in the band (xmin,xmax) with band index (i_low,i_up), - * find the edges in the band (these edges come from the triangulation) that enclose the coordinate - * \param[in] val_band - pair i_low,i_up - * \param[in] val_x - x-coordinate or first independent variable - * \param[in] val_y - y-coordinate or first independent variable - * \param[out] pair (edge_low,edge_up) - lower edge and upper edge of a triangle that encloses the coordinate - */ - std::pair GetEdges(std::pair val_band, su2double val_x, - su2double val_y) const; + x_coords.assign(x, x + num_points); + y_coords.assign(y, y + num_points); + + triangles.resize(num_triangles, 3); + for (size_t i = 0; i < 3 * num_triangles; ++i) { + triangles.data()[i] = static_cast(connectivity[i]); + } + + su2_lut::ReorderPoints(triangles, x_coords, y_coords); + su2_lut::ExtractEdges(triangles, edge_pts, edge_faces); + su2_lut::BuildTrapezoidalMap(edge_pts, x_coords, y_coords, map); + + return !map.x_bands.empty() && !map.offsets.empty(); + } /*! - * \brief determine if the x-coordinate falls within the bounds xmin,xmax of the table - * \param[in] val_x - x-coordinate or first independent variable - * \param[out] bool - true if val_x is within (xmin,xmax) + * \brief Find the triangle containing a query point. + * \return True if the point is inside the triangulation. */ - inline bool IsInsideHullX(su2double val_x) { - return (val_x >= unique_bands_x.front()) && (val_x <= unique_bands_x.back()); + bool FindTriangle(su2double val_x, su2double val_y, unsigned long& triangle_id, + std::array& bary_coords) const { + if (n_triangles == 0 || n_points == 0 || map.x_bands.empty()) { + bary_coords = {0.0, 0.0, 0.0}; + return false; + } + + std::array bary; + const su2_lut::IntT tri_id = + su2_lut::FindTriangle(map, triangles, edge_pts, edge_faces, x_coords, y_coords, val_x, val_y, bary); + + if (tri_id < 0) return false; + + triangle_id = static_cast(tri_id); + bary_coords = {bary[0], bary[1], bary[2]}; + return true; } /*! - * \brief get memory footprint of trapezoidal map. - * \return - memory footprint in mega bytes. + * \brief Get the memory footprint of the map in MB. */ - double GetMemoryFootprint() const { return memory_footprint; } + double GetMemoryFootprint() const { + const size_t bytes = + (map.edge_id.size() + map.offsets.size() + edge_pts.size() + edge_faces.size() + triangles.size()) * + sizeof(su2_lut::IntT) + + (map.edge_y.size() + map.x_bands.size() + x_coords.size() + y_coords.size()) * sizeof(su2_lut::RealT); + return double(bytes) / (1024.0 * 1024.0); + } }; diff --git a/Common/src/containers/CLookUpTable.cpp b/Common/src/containers/CLookUpTable.cpp index a9cd03d96641..50b57924d8fb 100644 --- a/Common/src/containers/CLookUpTable.cpp +++ b/Common/src/containers/CLookUpTable.cpp @@ -46,10 +46,7 @@ CLookUpTable::CLookUpTable(const string& var_file_name_lut, string name_CV1_in, FindTableLimits(name_CV1, name_CV2); - if (rank == MASTER_NODE) - cout << "Detecting all unique edges and setting edge to triangle connectivity " - "..." - << endl; + if (rank == MASTER_NODE) cout << "Detecting all unique edges and setting edge to triangle connectivity ..." << endl; IdentifyUniqueEdges(); @@ -62,16 +59,10 @@ CLookUpTable::CLookUpTable(const string& var_file_name_lut, string name_CV1_in, if (rank == MASTER_NODE) switch (table_dim) { case 2: - cout << "Building a trapezoidal map for the (" + name_CV1 + ", " + name_CV2 + - ") " - "space ..." - << endl; + cout << "Building a trapezoidal map for the (" + name_CV1 + ", " + name_CV2 + ") space ..." << endl; break; case 3: - cout << "Building trapezoidal map stack for the (" + name_CV1 + ", " + name_CV2 + - ") " - "space ..." - << endl; + cout << "Building trapezoidal map stack for the (" + name_CV1 + ", " + name_CV2 + ") space ..." << endl; break; default: break; @@ -79,38 +70,36 @@ CLookUpTable::CLookUpTable(const string& var_file_name_lut, string name_CV1_in, trap_map_x_y.resize(n_table_levels); su2double startTime = SU2_MPI::Wtime(); - unsigned short barwidth = 65; - bool display_map_info = (n_table_levels < 2); double tmap_memory_footprint = 0; + for (auto i_level = 0ul; i_level < n_table_levels; i_level++) { - trap_map_x_y[i_level] = - CTrapezoidalMap(GetDataP(name_CV1, i_level), GetDataP(name_CV2, i_level), table_data[i_level].cols(), - edges[i_level], edge_to_triangle[i_level], display_map_info); - tmap_memory_footprint += trap_map_x_y[i_level].GetMemoryFootprint(); - /* Display a progress bar to monitor table generation process */ - if (rank == MASTER_NODE) { - su2double progress = su2double(i_level) / n_table_levels; - auto completed = floor(progress * barwidth); - auto to_do = barwidth - completed; - cout << "[" << setfill('=') << setw(completed); - cout << '>'; - cout << setfill(' ') << setw(to_do) << std::right << "] " << 100 * progress << "%\r"; - cout.flush(); + const auto n_pts = n_points[i_level]; + const auto n_tris = n_triangles[i_level]; + + std::vector x_coords(n_pts); + std::vector y_coords(n_pts); + for (auto i_point = 0ul; i_point < n_pts; ++i_point) { + x_coords[i_point] = table_data[i_level][idx_CV1][i_point]; + y_coords[i_point] = table_data[i_level][idx_CV2][i_point]; + } + + std::vector tri_conn(3 * n_tris); + for (auto i_tri = 0ul; i_tri < n_tris; ++i_tri) { + tri_conn[3 * i_tri + 0] = triangles[i_level][i_tri][0]; + tri_conn[3 * i_tri + 1] = triangles[i_level][i_tri][1]; + tri_conn[3 * i_tri + 2] = triangles[i_level][i_tri][2]; } + + if (!trap_map_x_y[i_level].Build(n_pts, n_tris, x_coords.data(), y_coords.data(), tri_conn.data())) + SU2_MPI::Error( + "Construction of trapezoidal map failed for level " + std::to_string(i_level) + " of table " + file_name_lut, + CURRENT_FUNCTION); + tmap_memory_footprint += trap_map_x_y[i_level].GetMemoryFootprint(); } su2double stopTime = SU2_MPI::Wtime(); if (rank == MASTER_NODE) { - switch (table_dim) { - case 2: - cout << "\nConstruction of trapezoidal map took " << stopTime - startTime << " seconds\n" << endl; - break; - case 3: - cout << "\nConstruction of trapezoidal map stack took " << stopTime - startTime << " seconds\n" << endl; - break; - default: - break; - } + cout << "Construction of trapezoidal map took " << stopTime - startTime << " seconds\n"; cout << "Trapezoidal map memory footprint: " << tmap_memory_footprint << " MB\n"; cout << "Table data memory footprint: " << memory_footprint_data << " MB\n" << endl; } @@ -617,18 +606,8 @@ bool CLookUpTable::LookUp_XY(const vector& idx_var, vector= *limits_table_x[iLevel].first && val_CV1 <= *limits_table_x[iLevel].second) && - (val_CV2 >= *limits_table_y[iLevel].first && val_CV2 <= *limits_table_y[iLevel].second)) { - /* if so, try to find the triangle that holds the (prog, enth) point */ - id_triangle = trap_map_x_y[iLevel].GetTriangle(val_CV1, val_CV2); - - /* check if point is inside a triangle (if table domain is non-rectangular, - * the previous range check might be true but the point could still be outside of the domain) */ - return IsInTriangle(val_CV1, val_CV2, id_triangle, iLevel); - } - return false; + std::array bary_coords; + return trap_map_x_y[iLevel].FindTriangle(val_CV1, val_CV2, id_triangle, bary_coords); } void CLookUpTable::GetInterpCoeffs(su2double val_CV1, su2double val_CV2, const su2activematrix& interp_mat_inv, @@ -783,26 +762,6 @@ void CLookUpTable::InterpolateToNearestNeighbors(const su2double val_CV1, const InterpolateToNearestNeighbors(val_CV1, val_CV2, names_var, val_names_var, i_level); } -bool CLookUpTable::IsInTriangle(su2double val_CV1, su2double val_CV2, unsigned long val_id_triangle, - unsigned long i_level) { - su2double tri_x_0 = table_data[i_level][idx_CV1][triangles[i_level][val_id_triangle][0]]; - su2double tri_y_0 = table_data[i_level][idx_CV2][triangles[i_level][val_id_triangle][0]]; - - su2double tri_x_1 = table_data[i_level][idx_CV1][triangles[i_level][val_id_triangle][1]]; - su2double tri_y_1 = table_data[i_level][idx_CV2][triangles[i_level][val_id_triangle][1]]; - - su2double tri_x_2 = table_data[i_level][idx_CV1][triangles[i_level][val_id_triangle][2]]; - su2double tri_y_2 = table_data[i_level][idx_CV2][triangles[i_level][val_id_triangle][2]]; - - su2double area_tri = TriArea(tri_x_0, tri_y_0, tri_x_1, tri_y_1, tri_x_2, tri_y_2); - - su2double area_0 = TriArea(val_CV1, val_CV2, tri_x_1, tri_y_1, tri_x_2, tri_y_2); - su2double area_1 = TriArea(tri_x_0, tri_y_0, val_CV1, val_CV2, tri_x_2, tri_y_2); - su2double area_2 = TriArea(tri_x_0, tri_y_0, tri_x_1, tri_y_1, val_CV1, val_CV2); - - return (abs(area_tri - (area_0 + area_1 + area_2)) < area_tri * 1e-10); -} - bool CLookUpTable::CheckForVariables(const std::vector& vars_to_check) const { for (const string& var_to_check : vars_to_check) { if (!std::any_of(names_var.begin(), names_var.end(), diff --git a/Common/src/containers/CTrapezoidalMap.cpp b/Common/src/containers/CTrapezoidalMap.cpp deleted file mode 100644 index 75721ee749af..000000000000 --- a/Common/src/containers/CTrapezoidalMap.cpp +++ /dev/null @@ -1,283 +0,0 @@ -/*! - * \file CTrapezoidalMap.cpp - * \brief Implementation of the trapezoidal map for tabulation and lookup of fluid properties - * \author D. Mayer, T. Economon, N. Beishuizen - * \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 -#include - -#include "../../Common/include/option_structure.hpp" -#include "../../Common/include/containers/CTrapezoidalMap.hpp" - -using namespace std; - -/* Trapezoidal map implementation. Reference: - * M. de Berg, O. Cheong M. van Kreveld, M. Overmars, - * Computational Geometry, Algorithms and Applications pp. 121-146 (2008) - * NOTE: the current implementation is actually the simpler 'slab' approach. - */ -CTrapezoidalMap::CTrapezoidalMap(const su2double* samples_x, const su2double* samples_y, const unsigned long size, - vector > const& edges, - su2vector > const& val_edge_to_triangle, bool display) { - int rank = SU2_MPI::GetRank(); - su2double startTime = SU2_MPI::Wtime(); - - edge_to_triangle = su2vector >(val_edge_to_triangle); - - unique_bands_x.assign(samples_x, samples_x + size); - - /* sort x_bands and make them unique */ - sort(unique_bands_x.begin(), unique_bands_x.end()); - - auto iter = unique(unique_bands_x.begin(), unique_bands_x.end()); - - unique_bands_x.resize(distance(unique_bands_x.begin(), iter)); - - edge_limits_x.resize(edges.size(), 2); - edge_limits_y.resize(edges.size(), 2); - - /* store x and y values of each edge in a vector for a slight speed up - * as it prevents some uncoalesced accesses */ - for (unsigned long j = 0; j < edges.size(); j++) { - edge_limits_x[j][0] = samples_x[edges[j][0]]; - edge_limits_x[j][1] = samples_x[edges[j][1]]; - edge_limits_y[j][0] = samples_y[edges[j][0]]; - edge_limits_y[j][1] = samples_y[edges[j][1]]; - } - - /* number of bands */ - unsigned long n_bands_x = unique_bands_x.size() - 1; - /* band index */ - unsigned long i_band = 0; - /* number of edges */ - unsigned long n_edges = edges.size(); - /* edge index */ - unsigned long i_edge = 0; - unsigned long j_edge = 0; - /* counter for edges intersects */ - unsigned long n_intersects = 0; - /* lower and upper x value of each band */ - su2double band_lower_x = 0; - su2double band_upper_x = 0; - - su2double x_0; - su2double y_0; - su2double dy_edge; - su2double dx_edge; - su2double x_band_mid; - - /* y values of all intersecting edges for every band */ - y_edge_at_band_mid.resize(unique_bands_x.size() - 1); - - /* loop over bands */ - while (i_band < n_bands_x) { - band_lower_x = unique_bands_x[i_band]; - band_upper_x = unique_bands_x[i_band + 1]; - i_edge = 0; - n_intersects = 0; - - /* loop over edges and determine which edges appear in current band */ - while (i_edge < n_edges) { - /* check if edge intersects the band - * (vertical edges are automatically discarded) */ - if (((edge_limits_x[i_edge][0] <= band_lower_x) and (edge_limits_x[i_edge][1] >= band_upper_x)) or - ((edge_limits_x[i_edge][1] <= band_lower_x) and (edge_limits_x[i_edge][0] >= band_upper_x))) { - y_edge_at_band_mid[i_band].emplace_back(0.0, 0); - - x_0 = edge_limits_x[i_edge][0]; - y_0 = edge_limits_y[i_edge][0]; - - dy_edge = edge_limits_y[i_edge][1] - edge_limits_y[i_edge][0]; - dx_edge = edge_limits_x[i_edge][1] - edge_limits_x[i_edge][0]; - x_band_mid = (band_lower_x + band_upper_x) / 2.0; - - y_edge_at_band_mid[i_band][n_intersects].first = y_0 + dy_edge / dx_edge * (x_band_mid - x_0); - - /* save edge index so it can later be recalled when searching */ - y_edge_at_band_mid[i_band][n_intersects].second = i_edge; - - n_intersects++; - } - i_edge++; - } - - /* sort edges by their y values. - * note that these y values are unique (i.e. edges cannot - * intersect in a band) */ - sort(y_edge_at_band_mid[i_band].begin(), y_edge_at_band_mid[i_band].end()); - - i_band++; - } - - su2double stopTime = SU2_MPI::Wtime(); - - /* calculate size of trapezoidal map components */ - double size_unique_bands = sizeof(su2double) * unique_bands_x.size() / 1e6; - double size_edge_limits_x = sizeof(su2double) * edge_limits_x.size() * 2 / 1e6; - double size_edge_limits_y = sizeof(su2double) * edge_limits_y.size() * 2 / 1e6; - - double size_edge_to_triangle = 0; - for (i_edge = 0; i_edge < edge_to_triangle.size(); i_edge++) - for (j_edge = 0; j_edge < edge_to_triangle[i_edge].size(); j_edge++) - size_edge_to_triangle += sizeof(unsigned long) / 1e6; - - double size_y_edge_at_band_mid = 0; - for (unsigned long i_y = 0; i_y < y_edge_at_band_mid.size(); i_y++) - for (unsigned long j_y = 0; j_y < y_edge_at_band_mid[i_y].size(); j_y++) - size_y_edge_at_band_mid += sizeof(su2double) / 1e6 + sizeof(unsigned long) / 1e6; - - memory_footprint = - size_unique_bands + size_edge_limits_x + size_edge_limits_y + size_edge_to_triangle + size_y_edge_at_band_mid; - - /* print size of trapezoidal map components to screen */ - if ((rank == MASTER_NODE) && display) { - cout << setfill(' '); - cout << "\n" << endl; - cout << "+------------------------------------------------------------------+\n"; - cout << "| Trapezoidal map info |\n"; - cout << "+------------------------------------------------------------------+" << endl; - - cout << "| Time to construct trapezoidal map: " << setw(22) << right << stopTime - startTime << " sec" - << " |" << endl; - cout << "| Size of unique_bands in memory: " << setw(22) << size_unique_bands << " MB " - << " |" << endl; - cout << "| Size of edge_limits_x in memory: " << setw(22) << size_edge_limits_x << " MB " - << " |" << endl; - cout << "| Size of edge_limits_y in memory: " << setw(22) << size_edge_limits_y << " MB " - << " |" << endl; - cout << "| Size of edge_to_triangle in memory: " << setw(22) << size_edge_to_triangle << " MB " - << " |" << endl; - cout << "| Size of y_edge_at_band_mid in memory: " << setw(22) << size_y_edge_at_band_mid << " MB " - << " |" << endl; - cout << "| Total: " << setw(22) << memory_footprint << " MB " - << " |" << endl; - cout << "+------------------------------------------------------------------+" << endl; - cout << "\n" << endl; - } -} - -unsigned long CTrapezoidalMap::GetTriangle(const su2double val_x, const su2double val_y) { - /* find x band in which val_x sits */ - pair band = GetBand(val_x); - - /* within that band, find edges which enclose the (val_x, val_y) point */ - pair edges = GetEdges(band, val_x, val_y); - - /* identify the adjacent triangles using the two edges */ - std::array triangles_edge_low; - for (unsigned long i = 0; i < edge_to_triangle[edges.first].size(); i++) - triangles_edge_low[i] = edge_to_triangle[edges.first][i]; - - std::array triangles_edge_up; - for (unsigned long i = 0; i < edge_to_triangle[edges.second].size(); i++) - triangles_edge_up[i] = edge_to_triangle[edges.second][i]; - - sort(triangles_edge_low.begin(), triangles_edge_low.end()); - sort(triangles_edge_up.begin(), triangles_edge_up.end()); - - /* The intersection of the faces to which upper or lower belongs is the face that both belong to. */ - vector triangle; - set_intersection(triangles_edge_up.begin(), triangles_edge_up.end(), triangles_edge_low.begin(), - triangles_edge_low.end(), std::back_inserter(triangle)); - - /*--- We failed to find an intersection, so take the lower triangle inside the band enclosing the point---*/ - if (triangle.size() < 1) { - triangle.resize(1, triangles_edge_low[0]); - } - - return triangle[0]; -} - -pair CTrapezoidalMap::GetBand(const su2double val_x) { - unsigned long i_low = 0; - unsigned long i_up = 0; - su2double val_x_sample = val_x; - /* check if val_x is in x-bounds of the table, if not then project val_x to either x-min or x-max */ - if (val_x_sample < unique_bands_x.front()) val_x_sample = unique_bands_x.front(); - if (val_x_sample > unique_bands_x.back()) val_x_sample = unique_bands_x.back(); - - std::pair::iterator, std::vector::iterator> bounds; - bounds = std::equal_range(unique_bands_x.begin(), unique_bands_x.end(), val_x_sample); - - /*--- if upper bound = 0, then use the range [0,1] ---*/ - i_up = max(1, bounds.first - unique_bands_x.begin()); - i_low = i_up - 1; - - return make_pair(i_low, i_up); -} - -pair CTrapezoidalMap::GetEdges(pair val_band, - su2double val_x, su2double val_y) const { - su2double next_y; - su2double y_edge_low; - su2double y_edge_up; - su2double x_edge_low; - su2double x_edge_up; - - unsigned long i_band_low = val_band.first; - - unsigned long next_edge; - - unsigned long j_low = 0; - unsigned long j_mid = 0; - unsigned long j_up = 0; - - j_up = y_edge_at_band_mid[i_band_low].size() - 1; - j_low = 0; - - while (j_up - j_low > 1) { - j_mid = (j_up + j_low) / 2; - - // Select the edge associated with the x band (i_band_low) - // Search for the RunEdge in the y direction (second value is index of - // edge) - next_edge = y_edge_at_band_mid[i_band_low][j_mid].second; - - y_edge_low = edge_limits_y[next_edge][0]; - y_edge_up = edge_limits_y[next_edge][1]; - x_edge_low = edge_limits_x[next_edge][0]; - x_edge_up = edge_limits_x[next_edge][1]; - - // The search variable in j should be interpolated in i as well - next_y = y_edge_low + (y_edge_up - y_edge_low) / (x_edge_up - x_edge_low) * (val_x - x_edge_low); - - if (next_y > val_y) { - j_up = j_mid; - - } else if (next_y < val_y) { - j_low = j_mid; - - } else if (next_y == val_y) { - j_low = j_mid; - j_up = j_low + 1; - break; - } - } - - unsigned long edge_low = y_edge_at_band_mid[i_band_low][j_low].second; - unsigned long edge_up = y_edge_at_band_mid[i_band_low][j_up].second; - - return make_pair(edge_low, edge_up); -} diff --git a/Common/src/containers/meson.build b/Common/src/containers/meson.build index 4c8d4fe618a4..0743fd6b60c0 100644 --- a/Common/src/containers/meson.build +++ b/Common/src/containers/meson.build @@ -1,3 +1,2 @@ -common_src += files(['CTrapezoidalMap.cpp', - 'CFileReaderLUT.cpp', +common_src += files(['CFileReaderLUT.cpp', 'CLookUpTable.cpp']) diff --git a/SU2_CFD/src/fluid/CFluidFlamelet.cpp b/SU2_CFD/src/fluid/CFluidFlamelet.cpp index f95a844a0f71..dc9ef2ba2b2f 100644 --- a/SU2_CFD/src/fluid/CFluidFlamelet.cpp +++ b/SU2_CFD/src/fluid/CFluidFlamelet.cpp @@ -55,7 +55,8 @@ CFluidFlamelet::CFluidFlamelet(CConfig* config, su2double value_pressure_operati scalars_vector.resize(n_scalars); table_scalar_names.resize(n_scalars); - for (auto iCV = 0u; iCV < n_control_vars; iCV++) table_scalar_names[iCV] = flamelet_options.controlling_variable_names[iCV]; + for (auto iCV = 0u; iCV < n_control_vars; iCV++) + table_scalar_names[iCV] = flamelet_options.controlling_variable_names[iCV]; /*--- auxiliary species transport equations---*/ for (auto i_aux = 0u; i_aux < n_user_scalars; i_aux++) { @@ -64,10 +65,11 @@ CFluidFlamelet::CFluidFlamelet(CConfig* config, su2double value_pressure_operati controlling_variable_names.resize(n_control_vars); for (auto iCV = 0u; iCV < n_control_vars; iCV++) - controlling_variable_names[iCV] =flamelet_options.controlling_variable_names[iCV]; + controlling_variable_names[iCV] = flamelet_options.controlling_variable_names[iCV]; passive_specie_names.resize(n_user_scalars); - for (auto i_aux = 0u; i_aux < n_user_scalars; i_aux++) passive_specie_names[i_aux] = flamelet_options.user_scalar_names[i_aux]; + for (auto i_aux = 0u; i_aux < n_user_scalars; i_aux++) + passive_specie_names[i_aux] = flamelet_options.user_scalar_names[i_aux]; switch (Kind_DataDriven_Method) { case ENUM_DATADRIVEN_METHOD::LUT: @@ -79,6 +81,7 @@ CFluidFlamelet::CFluidFlamelet(CConfig* config, su2double value_pressure_operati look_up_table = new CLookUpTable(datadriven_fluid_options.datadriven_filenames[0], table_scalar_names[I_PROGVAR], table_scalar_names[I_ENTH]); break; + default: if (rank == MASTER_NODE) { cout << "***********************************************" << endl; @@ -86,7 +89,8 @@ CFluidFlamelet::CFluidFlamelet(CConfig* config, su2double value_pressure_operati cout << "***********************************************" << endl; } #ifdef USE_MLPCPP - lookup_mlp = new MLPToolbox::CLookUp_ANN(datadriven_fluid_options.n_filenames, datadriven_fluid_options.datadriven_filenames); + lookup_mlp = new MLPToolbox::CLookUp_ANN(datadriven_fluid_options.n_filenames, + datadriven_fluid_options.datadriven_filenames); if ((rank == MASTER_NODE)) lookup_mlp->DisplayNetworkInfo(); #else SU2_MPI::Error("SU2 was not compiled with MLPCpp enabled (-Denable-mlpcpp=true).", CURRENT_FUNCTION); @@ -104,8 +108,7 @@ CFluidFlamelet::CFluidFlamelet(CConfig* config, su2double value_pressure_operati } CFluidFlamelet::~CFluidFlamelet() { - if (Kind_DataDriven_Method == ENUM_DATADRIVEN_METHOD::LUT) - delete look_up_table; + if (Kind_DataDriven_Method == ENUM_DATADRIVEN_METHOD::LUT) delete look_up_table; #ifdef USE_MLPCPP if (Kind_DataDriven_Method == ENUM_DATADRIVEN_METHOD::MLP) { delete iomap_TD; @@ -113,7 +116,7 @@ CFluidFlamelet::~CFluidFlamelet() { delete iomap_LookUp; delete lookup_mlp; if (preferential_diffusion) delete iomap_PD; - } + } #endif } @@ -184,8 +187,7 @@ void CFluidFlamelet::PreprocessLookUp(CConfig* config) { size_t n_sources = n_control_vars + 2 * n_user_scalars; varnames_Sources.resize(n_sources); val_vars_Sources.resize(n_sources); - for (auto iCV = 0u; iCV < n_control_vars; iCV++) - varnames_Sources[iCV] = flamelet_options.cv_source_names[iCV]; + for (auto iCV = 0u; iCV < n_control_vars; iCV++) varnames_Sources[iCV] = flamelet_options.cv_source_names[iCV]; /*--- No source term for enthalpy ---*/ /*--- For the auxiliary equations, we use a positive (production) and a negative (consumption) term: @@ -206,7 +208,8 @@ void CFluidFlamelet::PreprocessLookUp(CConfig* config) { } else { varnames_LookUp.resize(n_lookups); val_vars_LookUp.resize(n_lookups); - for (auto iLookup = 0u; iLookup < n_lookups; iLookup++) varnames_LookUp[iLookup] = flamelet_options.lookup_names[iLookup]; + for (auto iLookup = 0u; iLookup < n_lookups; iLookup++) + varnames_LookUp[iLookup] = flamelet_options.lookup_names[iLookup]; } /*--- Preferential diffusion scalars ---*/ @@ -243,10 +246,10 @@ void CFluidFlamelet::PreprocessLookUp(CConfig* config) { } #endif } else { - for (auto iVar=0u; iVar < varnames_TD.size(); iVar++) { + for (auto iVar = 0u; iVar < varnames_TD.size(); iVar++) { LUT_idx_TD.push_back(look_up_table->GetIndexOfVar(varnames_TD[iVar])); } - for (auto iVar=0u; iVar < varnames_Sources.size(); iVar++) { + for (auto iVar = 0u; iVar < varnames_Sources.size(); iVar++) { unsigned long LUT_idx; if (noSource(varnames_Sources[iVar])) { LUT_idx = look_up_table->GetNullIndex(); @@ -255,16 +258,16 @@ void CFluidFlamelet::PreprocessLookUp(CConfig* config) { } LUT_idx_Sources.push_back(LUT_idx); } - for (auto iVar=0u; iVar < varnames_LookUp.size(); iVar++) { + for (auto iVar = 0u; iVar < varnames_LookUp.size(); iVar++) { unsigned long LUT_idx; if (noSource(varnames_LookUp[iVar])) LUT_idx = look_up_table->GetNullIndex(); - else + else LUT_idx = look_up_table->GetIndexOfVar(varnames_LookUp[iVar]); LUT_idx_LookUp.push_back(LUT_idx); } if (preferential_diffusion) { - for (auto iVar=0u; iVar < varnames_PD.size(); iVar++) { + for (auto iVar = 0u; iVar < varnames_PD.size(); iVar++) { LUT_idx_PD.push_back(look_up_table->GetIndexOfVar(varnames_PD[iVar])); } } @@ -275,7 +278,7 @@ unsigned long CFluidFlamelet::EvaluateDataSet(const vector& input_sca vector& output_refs) { AD::StartPreacc(); for (auto iVar = 0u; iVar < input_scalar.size(); iVar++) AD::SetPreaccIn(input_scalar[iVar]); - + su2double val_enth = input_scalar[I_ENTH]; su2double val_prog = input_scalar[I_PROGVAR]; su2double val_mixfrac = include_mixture_fraction ? input_scalar[I_MIXFRAC] : 0.0; @@ -310,7 +313,6 @@ unsigned long CFluidFlamelet::EvaluateDataSet(const vector& input_sca default: break; } - /*--- Add all quantities and their names to the look up vectors. ---*/ bool inside{true}; @@ -323,7 +325,6 @@ unsigned long CFluidFlamelet::EvaluateDataSet(const vector& input_sca } else { inside = look_up_table->LookUp_XY(LUT_idx, output_refs, val_prog, val_enth); } - break; case ENUM_DATADRIVEN_METHOD::MLP: refs_vars.resize(output_refs.size()); diff --git a/TestCases/parallel_regression_AD.py b/TestCases/parallel_regression_AD.py index 5c06556d4fb9..245f060ae6e4 100644 --- a/TestCases/parallel_regression_AD.py +++ b/TestCases/parallel_regression_AD.py @@ -337,7 +337,7 @@ def main(): da_sp_pinArray_cht_2d_dp_hf.cfg_dir = "incomp_navierstokes/streamwise_periodic/chtPinArray_2d" da_sp_pinArray_cht_2d_dp_hf.cfg_file = "DA_configMaster.cfg" da_sp_pinArray_cht_2d_dp_hf.test_iter = 100 - da_sp_pinArray_cht_2d_dp_hf.test_vals = [-11.620815, -6.488915, -13.316675] + da_sp_pinArray_cht_2d_dp_hf.test_vals = [-11.620815, -6.488915, -13.316362] da_sp_pinArray_cht_2d_dp_hf.multizone = True test_list.append(da_sp_pinArray_cht_2d_dp_hf) @@ -355,7 +355,7 @@ def main(): da_unsteadyCHT_cylinder.cfg_dir = "coupled_cht/disc_adj_unsteadyCHT_cylinder" da_unsteadyCHT_cylinder.cfg_file = "chtMaster.cfg" da_unsteadyCHT_cylinder.test_iter = 2 - da_unsteadyCHT_cylinder.test_vals = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662005, 0.000000, 10.627000, 0.295190] + da_unsteadyCHT_cylinder.test_vals = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662021, 0.000000, 10.627000, 0.295190] da_unsteadyCHT_cylinder.test_vals_aarch64 = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662012, 0.000000, 89.932000, 0.295190] da_unsteadyCHT_cylinder.unsteady = True da_unsteadyCHT_cylinder.multizone = True From 0b697492c86ce9b8f46e8a4106dd3c007f991267 Mon Sep 17 00:00:00 2001 From: Nijso Date: Wed, 26 Aug 2026 21:34:51 +0200 Subject: [PATCH 37/61] Full Multi-Grid improvements (#2873) ## Proposed Changes This is a split from #2863 to clean up the different implementations. This PR does not really improve convergence, it merely implements some configuration options and makes it work robustly. An improvement comes from the CFL ramping: every level of the startup phase in FMG gets its own scaled CFL, then for this level CFL increases from the max CFL of the previous level to the new CFL of this level over MG_STARTUP_ITER iterations. This does mean some CFL jumps when you converge early. Also, when you exit the startup phase and enter the final v-cycle phase, you immediately jump to CFL_NUMBER, no ramping here. The real future improvement is probably stepping away from the piecewise constant interpolation. At the moment there is no improvement because the coarser levels do not result in a better starting point for the final v-cycle. Implementation details: MG_STARTUP_ITER= 100 %Full multigrid iterations per level for the warmup phase. MG_CFL_SCALING= 0.5, 0.5, 0.5 %Full multigrid now has CFL scaling for the coarser levels during warmup phase MG_STARTUP_CONVERGENCE= -2 %Full multigrid now exits the current level during the warmup phase when convergence dropped. MG_STARTUP_STAGNATION_ITER= 0.99 %Full multigrid now exits the current level during the warmup phase when the level residual stalls. MG_STARTUP_STAGNATION_ITER= 5 % 5 consecutive iteration of stagnation before exiting. ## Related Work #2863 ## PR Checklist *Put an X by all that apply. You can fill this out after submitting the PR. If you have any questions, don't hesitate to ask! We want to help. These are a guide for you to know what the reviewers will be looking for in your contribution.* - [x] I am submitting my contribution to the develop branch. - [x] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [x] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [x] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [x] I have added a test case that demonstrates my contribution, if necessary. - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --------- Co-authored-by: Claude Opus 5 Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> --- Common/include/CConfig.hpp | 3 +- Common/include/option_structure.hpp | 6 + Common/src/CConfig.cpp | 21 +- SU2_CFD/include/integration/CIntegration.hpp | 18 + .../integration/CMultiGridIntegration.hpp | 101 +++- SU2_CFD/include/output/COutput.hpp | 29 ++ SU2_CFD/include/solvers/CSolver.hpp | 16 +- .../src/integration/CMultiGridIntegration.cpp | 442 +++++++++++++++--- SU2_CFD/src/iteration/CFluidIteration.cpp | 28 +- SU2_CFD/src/output/COutput.cpp | 38 +- SU2_CFD/src/solvers/CSolver.cpp | 6 +- SU2_CFD/src/solvers/CTransLMSolver.cpp | 5 +- TestCases/euler/naca0012/inv_NACA0012.cfg | 18 +- TestCases/parallel_regression_AD.py | 6 +- TestCases/serial_regression.py | 8 + config_template.cfg | 25 + 16 files changed, 680 insertions(+), 90 deletions(-) diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index 8e0cc89cb379..3544f028267d 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -2936,7 +2936,8 @@ class CConfig { */ void SetMGLevels(unsigned short val_nMGLevels) { nMGLevels = val_nMGLevels; - if (Kind_MGCycle == MG_CYCLE::FULL) { + /*--- Clamp so FinestMesh can never point past the last level that still exists. ---*/ + if ((Kind_MGCycle == MG_CYCLE::FULL) || (FinestMesh > val_nMGLevels)) { SetFinestMesh(val_nMGLevels); } } diff --git a/Common/include/option_structure.hpp b/Common/include/option_structure.hpp index 6d97299d09d0..d4d0e7f0fa0a 100644 --- a/Common/include/option_structure.hpp +++ b/Common/include/option_structure.hpp @@ -1126,6 +1126,12 @@ struct CMGOptions { su2double MG_Smooth_StagnationTol{0.0}; /*!< \brief Stagnation early exit: stop if current_rms >= prev_rms * tol. 0 = disabled. */ bool MG_Implicit_Lines{false}; /*!< \brief Enable implicit-lines agglomeration from walls. */ unsigned long MG_Implicit_Lines_MaxLength{20}; /*!< \brief Maximum nodes on a wall-normal implicit line (including wall seed). */ + unsigned long MG_Startup_Iter{100}; /*!< \brief Iterations per mesh during FMG startup, and the length of each level's CFL ramp. 0 = no iteration budget. */ + su2double MG_Startup_Convergence{-2.0}; /*!< \brief FMG: orders of magnitude (log10) that CONV_FIELD must drop on the + active level before promoting to the next finer one. Negative is a + drop, as for CONV_RESIDUAL_MINVAL. */ + su2double MG_Startup_Stagnation{0.99}; /*!< \brief FMG: promote when the residual ratio between successive iterations exceeds this. 0 = disabled. */ + unsigned long MG_Startup_Stagnation_Iter{5}; /*!< \brief FMG: consecutive stalled iterations required before promoting. 0 = disabled. */ }; /*! diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index f272a84ab6fb..c21116c6f3e8 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -2071,6 +2071,20 @@ void CConfig::SetConfig_Options() { addBoolOption("MG_IMPLICIT_LINES", MGOptions.MG_Implicit_Lines, false); /*!\brief MG_IMPLICIT_LINES_MAX_LENGTH\n DESCRIPTION: Maximum number of nodes on a wall-normal implicit agglomeration line (including the wall seed node). DEFAULT: 20 \ingroup Config*/ addUnsignedLongOption("MG_IMPLICIT_LINES_MAX_LENGTH", MGOptions.MG_Implicit_Lines_MaxLength, 20); + /*!\brief MG_STARTUP_ITER\n DESCRIPTION: Max number of iterations spent on each mesh during the Full + * Multigrid (FMG) startup phase. DEFAULT: 100 \ingroup Config*/ + addUnsignedLongOption("MG_STARTUP_ITER", MGOptions.MG_Startup_Iter, 100); + /*!\brief MG_STARTUP_CONVERGENCE\n DESCRIPTION: During the startup phase of Full-MG, leave the current level once + * CONV_FIELD has dropped by this many orders of magnitude relative to its value when the level became active. + * DEFAULT: -2 \ingroup Config*/ + addDoubleOption("MG_STARTUP_CONVERGENCE", MGOptions.MG_Startup_Convergence, -2.0); + /*!\brief MG_STARTUP_STAGNATION\n DESCRIPTION: Full-MG promotion on stagnation. If the active level's residual ratio + * between successive iterations exceeds this value for MG_STARTUP_STAGNATION_ITER consecutive iterations, promote to + * the next finer level without waiting out MG_STARTUP_ITER. 0 disables it. DEFAULT: 0.99 \ingroup Config*/ + addDoubleOption("MG_STARTUP_STAGNATION", MGOptions.MG_Startup_Stagnation, 0.99); + /*!\brief MG_STARTUP_STAGNATION_ITER\n DESCRIPTION: Consecutive stalled iterations required before Full-MG promotes + * on stagnation. 0 disables it, as MG_STARTUP_STAGNATION= 0 does. DEFAULT: 5 \ingroup Config*/ + addUnsignedLongOption("MG_STARTUP_STAGNATION_ITER", MGOptions.MG_Startup_Stagnation_Iter, 5); /*!\brief MG_CFL_SCALING\n DESCRIPTION: Per-level CFL scaling factors for coarse MG levels. Entry i is the ratio CFL(i+1)/CFL(i). If fewer values than nMGLevels are given, the last value is repeated. DEFAULT: 0.25 (i.e., 1/4 per level) \ingroup Config*/ addDoubleListOption("MG_CFL_SCALING", nMG_CflScaling_p, MG_CflScaling_p); @@ -4835,6 +4849,11 @@ void CConfig::SetPostprocessing(SU2_COMPONENT val_software, unsigned short val_i } } + /*--- Only the direct problem promotes a Full-MG startup. Downgrade before FinestMesh is + * derived from the cycle, or it stays on the coarsest level for the entire run. ---*/ + + if (Restart || ((Kind_MGCycle == MG_CYCLE::FULL) && ContinuousAdjoint)) Kind_MGCycle = MG_CYCLE::V; + FinestMesh = MESH_0; if (Kind_MGCycle == MG_CYCLE::FULL) FinestMesh = nMGLevels; @@ -4896,8 +4915,6 @@ void CConfig::SetPostprocessing(SU2_COMPONENT val_software, unsigned short val_i MGOptions.MG_PostSmooth[nMGLevels] = 0; MGOptions.MG_CorrecSmooth[nMGLevels] = 0; - if (Restart) Kind_MGCycle = MG_CYCLE::V; - if (ContinuousAdjoint) { if (Kind_Solver == MAIN_SOLVER::EULER) Kind_Solver = MAIN_SOLVER::ADJ_EULER; if (Kind_Solver == MAIN_SOLVER::NAVIER_STOKES) Kind_Solver = MAIN_SOLVER::ADJ_NAVIER_STOKES; diff --git a/SU2_CFD/include/integration/CIntegration.hpp b/SU2_CFD/include/integration/CIntegration.hpp index b970a8099b01..51dacbc93b2a 100644 --- a/SU2_CFD/include/integration/CIntegration.hpp +++ b/SU2_CFD/include/integration/CIntegration.hpp @@ -113,6 +113,24 @@ class CIntegration { CNumerics ******numerics_container, CConfig **config, unsigned short RunTime_EqSystem, unsigned short iZone, unsigned short iInst) { }; + /*! + * \brief Report the convergence fields on the active mesh level to a Full-MG startup. + * \param[in] convFields - Name and log10 value of each monitored residual field. + * \param[in] config - Definition of the particular problem. + */ + virtual void MonitorFullMG_Startup(const vector >& convFields, + const CConfig *config) { }; + + /*! + * \brief Whether the Full-MG startup is still ramping the CFL of the finest grid. + */ + virtual bool GetFullMG_CFLRamp() const { return false; } + + /*! + * \brief InnerIter at which the currently active Full-MG level became active. + */ + virtual unsigned long GetLevelStartIter() const { return 0; } + /*! * \brief A virtual member. * \param[in] geometry - Geometrical definition of the problem. diff --git a/SU2_CFD/include/integration/CMultiGridIntegration.hpp b/SU2_CFD/include/integration/CMultiGridIntegration.hpp index 0672d1ae5ff9..eee8c7235fd7 100644 --- a/SU2_CFD/include/integration/CMultiGridIntegration.hpp +++ b/SU2_CFD/include/integration/CMultiGridIntegration.hpp @@ -53,6 +53,18 @@ class CMultiGridIntegration final : public CIntegration { CNumerics ******numerics_container, CConfig **config, unsigned short RunTime_EqSystem, unsigned short iZone, unsigned short iInst) override; + /*! + * \brief Record CONV_FIELD on the active Full-MG level and decide whether it is done. + * \param[in] convFields - Name and log10 value of each monitored residual field. + * \param[in] config - Definition of the particular problem. + */ + void MonitorFullMG_Startup(const vector >& convFields, + const CConfig *config) override; + + bool GetFullMG_CFLRamp() const override { return mg_ramp_mesh0_active; } + + unsigned long GetLevelStartIter() const override { return mg_ramp_level_start_iter; } + private: /*! * \brief Perform a Full-Approximation Storage (FAS) Multigrid. @@ -218,17 +230,76 @@ class CMultiGridIntegration final : public CIntegration { /*! * \brief Adapt both restriction and prolongation damping factors from the global-trend signal. - * - * Uses the cross-cycle EMA ratio (crossCycleRatio = fine_d0 / EMA(fine_d0)) to detect - * long-term convergence or divergence, then adjusts both \c Damp_Res_Restric and - * \c Damp_Correc_Prolong with a single shared signal. The EMA filters per-cycle noise; - * no per-level aggregation or floor counter is needed. - * * \param[in,out] config - Problem configuration. * \param[in] crossCycleRatio - Current fine_d0 divided by the EMA of fine_d0. */ void adaptDampingFactors(CConfig* config, passivedouble crossCycleRatio); + /*! + * \brief Set the CFL of every coarse multigrid level, ramping level i from the final CFL of + * level i+1 to its own target, which CFL_NUMBER and MG_CFL_SCALING determine. + * + * \param[in] geometry - Geometrical definition of the problem. + * \param[in,out] solver_container - Container vector with all the solutions. + * \param[in,out] config - Definition of the particular problem. + * \param[in] RunTime_EqSystem - System of equations which is going to be solved. + * \param[in] iZone - Zone index. + * \param[in] iInst - Instance index. + * \param[in] FinestMesh - Currently active finest mesh level. + * \param[in] FullMG - Whether the Full-MG cycle is active. + * \param[in] mesh0_ramp_window - Result of FullMG_Mesh0RampWindow, computed by the caller. + */ + void SetCoarseGridCFL(CGeometry ****geometry, CSolver *****solver_container, CConfig **config, + unsigned short RunTime_EqSystem, unsigned short iZone, unsigned short iInst, + unsigned short FinestMesh, bool FullMG, bool mesh0_ramp_window); + + /*! + * \brief Whether the finest grid has a Full-MG CFL ramp at all. + * \param[in] config - Definition of the particular problem. + * \param[in] FinestMesh - Currently active finest mesh level. + * \param[in] FullMG - Whether the Full-MG cycle is active. + */ + bool FullMG_Mesh0HasRamp(const CConfig* config, unsigned short FinestMesh, bool FullMG) const { + return FullMG && (FinestMesh == MESH_0) && (config->GetMGOptions().MG_Startup_Iter > 0) && + (mg_ramp_cfl_start > 0.0); + } + + /*! + * \brief Whether the level-0 CFL is still climbing towards the configured number. + */ + bool FullMG_Mesh0Ramping(const CConfig* config, unsigned short FinestMesh, bool FullMG) const { + return FullMG_Mesh0HasRamp(config, FinestMesh, FullMG) && + ((config->GetInnerIter() - mg_ramp_level_start_iter) < config->GetMGOptions().MG_Startup_Iter); + } + + /*! + * \brief The single iteration after the ramp, which writes the configured CFL back. + */ + bool FullMG_Mesh0RampRestore(const CConfig* config, unsigned short FinestMesh, bool FullMG) const { + return FullMG_Mesh0HasRamp(config, FinestMesh, FullMG) && + ((config->GetInnerIter() - mg_ramp_level_start_iter) == config->GetMGOptions().MG_Startup_Iter); + } + + /*! + * \brief Whether the startup owns the level-0 CFL this iteration. + */ + bool FullMG_Mesh0RampWindow(const CConfig* config, unsigned short FinestMesh, bool FullMG) const { + return FullMG_Mesh0Ramping(config, FinestMesh, FullMG) || + FullMG_Mesh0RampRestore(config, FinestMesh, FullMG); + } + + /*! + * \brief Interpolate a scalar solver's solution onto the newly activated Full-MG level. + * \param[in,out] sol_fine - Solver on the level being activated. + * \param[in] sol_coarse - Solver on the level handing over. + * \param[in] geo_fine - Geometry of the level being activated. + * \param[in] geo_coarse - Geometry of the level handing over. + * \param[in] config - Definition of the particular problem. + * \param[in] eddy_viscosity - Carry the eddy viscosity across as well. + */ + void SetProlongated_ScalarSolution(CSolver *sol_fine, CSolver *sol_coarse, CGeometry *geo_fine, + CGeometry *geo_coarse, CConfig *config, bool eddy_viscosity); + /*! * \brief Helper function for early-exit logic during pre/post-smoothing. * \param[in] iSmooth - Current smoothing iteration index. @@ -277,4 +348,22 @@ class CMultiGridIntegration final : public CIntegration { unsigned short lastPreSmoothWorstStep[MAX_MG_LEVELS+1] = {}; unsigned short lastPostSmoothWorstStep[MAX_MG_LEVELS+1] = {}; + /*! \brief FinestMesh observed on the previous call; 0 also serves as the sentinel forcing a + * reset on the first call, since a FULL cycle starts at FinestMesh == nMGLevels > 0. */ + unsigned short mg_ramp_last_FinestMesh = 0; + unsigned long mg_ramp_level_start_iter = 0; /*!< \brief InnerIter the active FMG level became active at. */ + passivedouble mg_ramp_cfl_start = 0.0; /*!< \brief CFL the level below handed over; 0 before a promotion. */ + bool mg_ramp_mesh0_active = false; /*!< \brief Whether the level-0 CFL ramp is still climbing. */ + + su2double mg_damp_restric_initial = -1.0; /*!< \brief MG_DAMP_RESTRICTION as configured; negative if not captured. */ + su2double mg_damp_prolong_initial = 0.0; /*!< \brief MG_DAMP_PROLONGATION as configured. */ + + /*! \brief Why the active level was last promoted, for the report message. */ + enum class MGStartupPromote { NONE, BUDGET, CONVERGENCE, STAGNATION }; + MGStartupPromote mg_startup_promote_reason = MGStartupPromote::NONE; + + vector mg_startup_conv_start; /*!< \brief Field values when the active level became active. */ + vector mg_startup_conv_prev; /*!< \brief Field values on the previous iteration. */ + unsigned long mg_startup_stall_count = 0; /*!< \brief Consecutive iterations without useful reduction. */ + }; diff --git a/SU2_CFD/include/output/COutput.hpp b/SU2_CFD/include/output/COutput.hpp index 11234880c507..a1fea3fdf46c 100644 --- a/SU2_CFD/include/output/COutput.hpp +++ b/SU2_CFD/include/output/COutput.hpp @@ -323,6 +323,7 @@ class COutput { bool convergence; /*!< \brief To indicate if the solver has converged or not. */ su2double initResidual; /*!< \brief Initial value of the residual to evaluate the convergence level. */ vector convFields; /*!< \brief Name of the field to be monitored for convergence. */ + unsigned long convergenceStartIter = 0; /*!< \brief Iteration the convergence history is counted from. */ /*----------------------------- Adaptive CFL ----------------------------*/ @@ -486,6 +487,28 @@ class COutput { return 0; } + /*! + * \brief Discard the convergence history and count it from the given iteration instead. + * \param[in] Iteration - Iteration the history restarts from. + */ + void ResetConvergenceMonitoring(unsigned long Iteration) { convergenceStartIter = Iteration; } + + /*! + * \brief Names and values of the fields convergence is monitored on that are residuals. + * \return Name and current value of every monitored residual field, in CONV_FIELD order. + */ + vector> GetResidualConvFields() const { + vector> fields; + for (const auto& name : convFields) { + const auto it = historyOutput_Map.find(name); + if (it == historyOutput_Map.end()) continue; + if ((it->second.fieldType != HistoryFieldType::RESIDUAL) && + (it->second.fieldType != HistoryFieldType::AUTO_RESIDUAL)) continue; + fields.emplace_back(name, SU2_TYPE::GetValue(it->second.value)); + } + return fields; + } + /*! * \brief Get the value of particular surface history output field * \param[in] field - Name of the field @@ -786,6 +809,12 @@ class COutput { */ void CheckHistoryOutput(unsigned short nZone); + /*! + * \brief Check that the Full-MG startup has a criterion left to promote the active level on. + * \param[in] config - Definition of the particular problem. + */ + void CheckFullMG_Startup(const CConfig *config) const; + /*! * \brief Open the history file and write the header. * \param[in] config - Definition of the particular problem. diff --git a/SU2_CFD/include/solvers/CSolver.hpp b/SU2_CFD/include/solvers/CSolver.hpp index 9ac49e634203..863eb77b49f9 100644 --- a/SU2_CFD/include/solvers/CSolver.hpp +++ b/SU2_CFD/include/solvers/CSolver.hpp @@ -318,9 +318,11 @@ class CSolver { /*! * \brief Set the value of the max residual and RMS residual. - * \param[in] val_iterlinsolver - Number of linear iterations. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] config - Definition of the particular problem. + * \param[in] force - Reduce on a coarse level too, where it is skipped by default. */ - void SetResidual_RMS(const CGeometry *geometry, const CConfig *config); + void SetResidual_RMS(const CGeometry *geometry, const CConfig *config, bool force = false); /*! * \brief Communicate the value of the max residual and RMS residual. @@ -393,6 +395,16 @@ class CSolver { */ inline su2double GetAvg_CFL_Local(void) const { return Avg_CFL_Local; } + /*! + * \brief Set min/max/avg local CFL summary statistics. + * \param[in] val_cfl - Uniform CFL value to report. + */ + inline void SetCFL_Local_Stats(su2double val_cfl) { + Min_CFL_Local = val_cfl; + Max_CFL_Local = val_cfl; + Avg_CFL_Local = val_cfl; + } + /*! * \brief Get the number of variables of the problem. */ diff --git a/SU2_CFD/src/integration/CMultiGridIntegration.cpp b/SU2_CFD/src/integration/CMultiGridIntegration.cpp index 679a31cef6b3..fecbf0491b73 100644 --- a/SU2_CFD/src/integration/CMultiGridIntegration.cpp +++ b/SU2_CFD/src/integration/CMultiGridIntegration.cpp @@ -28,6 +28,8 @@ #include "../../include/integration/CMultiGridIntegration.hpp" #include "../../../Common/include/parallelization/omp_structure.hpp" #include "../../../Common/include/toolboxes/printing_toolbox.hpp" +#include + namespace { @@ -77,6 +79,172 @@ void CMultiGridIntegration::adaptDampingFactors(CConfig* config, passivedouble c CMultiGridIntegration::CMultiGridIntegration() : CIntegration() { } +void CMultiGridIntegration::MonitorFullMG_Startup(const vector >& convFields, + const CConfig *config) { + + const auto& mgOpts = config->GetMGOptions(); + + /*--- No residual to promote on, MG_STARTUP_ITER and MG_STARTUP_STAGNATION are all that is left. ---*/ + + if (convFields.empty()) return; + + const auto nFields = convFields.size(); + + /*--- Fields are log10, so MG_STARTUP_CONVERGENCE is added to the level's starting value. + * All of them have to have dropped. ---*/ + + if (mg_startup_conv_start.empty()) { + for (const auto& field : convFields) mg_startup_conv_start.push_back(field.second); + } + else { + const passivedouble drop = SU2_TYPE::GetValue(mgOpts.MG_Startup_Convergence); + bool converged = true; + for (auto iField = 0ul; iField < nFields; iField++) + converged = converged && (convFields[iField].second <= mg_startup_conv_start[iField] + drop); + + if (converged && (mg_startup_promote_reason == MGStartupPromote::NONE)) + mg_startup_promote_reason = MGStartupPromote::CONVERGENCE; + } + + /*--- Ratio of successive residuals, so a difference in log10. One slow iteration + * is not stagnation, and a field still coming down means the level is not stalled. ---*/ + + const passivedouble stall_tol = SU2_TYPE::GetValue(mgOpts.MG_Startup_Stagnation); + if (stall_tol > 0.0 && !mg_startup_conv_prev.empty()) { + bool stalled = true; + for (auto iField = 0ul; iField < nFields; iField++) + stalled = stalled && (convFields[iField].second - mg_startup_conv_prev[iField] >= log10(stall_tol)); + + if (stalled) + mg_startup_stall_count++; + else + mg_startup_stall_count = 0; + + if ((mgOpts.MG_Startup_Stagnation_Iter > 0) && + (mg_startup_stall_count >= mgOpts.MG_Startup_Stagnation_Iter) && + (mg_startup_promote_reason == MGStartupPromote::NONE)) { + mg_startup_promote_reason = MGStartupPromote::STAGNATION; + } + } + + mg_startup_conv_prev.clear(); + for (const auto& field : convFields) mg_startup_conv_prev.push_back(field.second); +} + +void CMultiGridIntegration::SetCoarseGridCFL(CGeometry ****geometry, CSolver *****solver_container, CConfig **config, + unsigned short RunTime_EqSystem, unsigned short iZone, + unsigned short iInst, unsigned short FinestMesh, bool FullMG, + bool mesh0_ramp_window) { + SU2_ZONE_SCOPED + + const unsigned short Solver_Position = config[iZone]->GetContainerPosition(RunTime_EqSystem); + const unsigned short nMGLevels = config[iZone]->GetnMGLevels(); + const unsigned long startup_iter = config[iZone]->GetMGOptions().MG_Startup_Iter; + CSolver* sol_f = solver_container[iZone][iInst][MESH_0][Solver_Position]; + + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS + { + /*--- The active level is ramped, MESH_0 included, until it reaches its target. ---*/ + + const bool ramping = FullMG && (FinestMesh <= nMGLevels) && (startup_iter > 0) && + ((FinestMesh > MESH_0) || mesh0_ramp_window); + const unsigned long iter_in_level = config[iZone]->GetInnerIter() - mg_ramp_level_start_iter; + passivedouble ramp_progress = 1.0; + if (ramping) { + ramp_progress = min(passivedouble{1.0}, passivedouble(iter_in_level) / passivedouble(startup_iter)); + } + + mg_ramp_mesh0_active = FullMG_Mesh0Ramping(config[iZone], FinestMesh, FullMG); + + /*--- Coarse targets are derived from the level-0 CFL via MG_CFL_SCALING. ---*/ + passivedouble cfl_base = mesh0_ramp_window ? passivedouble{0.0} : SU2_TYPE::GetValue(sol_f->GetAvg_CFL_Local()); + if (cfl_base < EPS) + cfl_base = SU2_TYPE::GetValue(config[iZone]->GetCFL(MESH_0)); + + const auto& cflScaling = config[iZone]->GetMGOptions().MG_CflScaling; + + passivedouble CFL_target[MAX_MG_LEVELS+1]; + passivedouble CFL_scale[MAX_MG_LEVELS+1]; + CFL_target[0] = cfl_base; + CFL_scale[0] = 1.0; + for (unsigned short lvl = 1; lvl <= nMGLevels; ++lvl) { + /*--- Entry lvl-1 is the transition lvl-1 -> lvl, clamped so a coarse level never runs + * at a higher CFL than the grid above it. ---*/ + const unsigned short iScale = lvl - 1; + const passivedouble scale = + max(passivedouble{1e-6}, min(passivedouble{1.0}, SU2_TYPE::GetValue(cflScaling[iScale]))); + CFL_scale[lvl] = scale; + CFL_target[lvl] = CFL_target[lvl-1] * scale; + } + + /*--- Ramp from the CFL the level below handed over at, which it may never have reached its + * target. The coarsest level has none and starts from its own target scaled down once more. ---*/ + + const passivedouble CFL_handover = + (mg_ramp_cfl_start > 0.0) ? mg_ramp_cfl_start : CFL_target[nMGLevels] * CFL_scale[nMGLevels]; + + passivedouble CFL_mesh0 = CFL_target[MESH_0]; + + for (unsigned short lvl = 0; lvl <= nMGLevels; ++lvl) { + passivedouble CFL_local = CFL_target[lvl]; + if (ramping && (lvl == FinestMesh)) { + CFL_local = (passivedouble(1.0) - ramp_progress) * CFL_handover + ramp_progress * CFL_target[lvl]; + } + /*--- The configured level-0 CFL stays as the user wrote it, only the solver value is ramped. ---*/ + if (lvl > MESH_0) config[iZone]->SetCFL(lvl, CFL_local); + else CFL_mesh0 = CFL_local; + } + /*--- Stashed in the solver's CFL stats for the point loop below to read back. ---*/ + if (mesh0_ramp_window) sol_f->SetCFL_Local_Stats(CFL_mesh0); + } + END_SU2_OMP_SAFE_GLOBAL_ACCESS + + /*--- Propagate the ramped CFL to every point of the finest grid. ---*/ + if (mesh0_ramp_window) { + CGeometry* geo_f = geometry[iZone][iInst][MESH_0]; + const passivedouble cfl_mesh0 = SU2_TYPE::GetValue(sol_f->GetAvg_CFL_Local()); + SU2_OMP_FOR_STAT(roundUpDiv(geo_f->GetnPoint(), omp_get_num_threads())) + for (auto iPoint = 0ul; iPoint < geo_f->GetnPoint(); iPoint++) + sol_f->GetNodes()->SetLocalCFL(iPoint, cfl_mesh0); + END_SU2_OMP_FOR + } + + /*--- Propagate the updated CFL to every coarse-grid point. ---*/ + for (unsigned short iMesh = 1; iMesh <= nMGLevels; ++iMesh) { + const passivedouble CFL_coarse_new = SU2_TYPE::GetValue(config[iZone]->GetCFL(iMesh)); + CGeometry* geo_c = geometry[iZone][iInst][iMesh]; + CSolver* sol_c = solver_container[iZone][iInst][iMesh][Solver_Position]; + SU2_OMP_SAFE_GLOBAL_ACCESS(sol_c->SetCFL_Local_Stats(CFL_coarse_new);) + SU2_OMP_FOR_STAT(roundUpDiv(geo_c->GetnPoint(), omp_get_num_threads())) + for (auto iPoint = 0ul; iPoint < geo_c->GetnPoint(); iPoint++) + sol_c->GetNodes()->SetLocalCFL(iPoint, CFL_coarse_new); + END_SU2_OMP_FOR + } + + /*--- These solvers scale the flow's time step by CFL_scalar/CFL_flow, so keep the two in step + * on the active level for as long as the startup owns the flow's CFL. ---*/ + + if ((Solver_Position != FLOW_SOL) || !FullMG || ((FinestMesh == MESH_0) && !mesh0_ramp_window)) return; + + CGeometry* geo_a = geometry[iZone][iInst][FinestMesh]; + const passivedouble cfl_flow = + SU2_TYPE::GetValue(solver_container[iZone][iInst][FinestMesh][FLOW_SOL]->GetAvg_CFL_Local()); + + for (const auto Scalar_Position : {TURB_SOL, TRANS_SOL, SPECIES_SOL}) { + CSolver* sol_s = solver_container[iZone][iInst][FinestMesh][Scalar_Position]; + if (sol_s == nullptr) continue; + + const su2double cfl = cfl_flow * SU2_TYPE::GetValue((Scalar_Position == SPECIES_SOL) + ? config[iZone]->GetCFLRedCoeff_Species() + : config[iZone]->GetCFLRedCoeff_Turb()); + SU2_OMP_SAFE_GLOBAL_ACCESS(sol_s->SetCFL_Local_Stats(cfl);) + SU2_OMP_FOR_STAT(roundUpDiv(geo_a->GetnPoint(), omp_get_num_threads())) + for (auto iPoint = 0ul; iPoint < geo_a->GetnPoint(); iPoint++) + sol_s->GetNodes()->SetLocalCFL(iPoint, cfl); + END_SU2_OMP_FOR + } +} + void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, CSolver *****solver_container, CNumerics ******numerics_container, @@ -90,6 +258,12 @@ void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, switch (config[iZone]->GetKind_Solver()) { case MAIN_SOLVER::EULER: case MAIN_SOLVER::NAVIER_STOKES: + case MAIN_SOLVER::INC_EULER: + case MAIN_SOLVER::INC_NAVIER_STOKES: + case MAIN_SOLVER::INC_RANS: + case MAIN_SOLVER::DISC_ADJ_INC_EULER: + case MAIN_SOLVER::DISC_ADJ_INC_NAVIER_STOKES: + case MAIN_SOLVER::DISC_ADJ_INC_RANS: case MAIN_SOLVER::NEMO_EULER: case MAIN_SOLVER::NEMO_NAVIER_STOKES: case MAIN_SOLVER::RANS: @@ -149,17 +323,25 @@ void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, lastPreSmoothExitReason[i] = ' '; lastPostSmoothExitReason[i] = ' '; } + + /*--- InnerIter restarts at every time step, the active level does not; re-anchor so the + * difference below cannot wrap around. ---*/ + + if (config[iZone]->GetInnerIter() < mg_ramp_level_start_iter) + mg_ramp_level_start_iter = config[iZone]->GetInnerIter(); } END_SU2_OMP_SAFE_GLOBAL_ACCESS - /*--- Full MG: advance to the next finer grid after a fixed number of - * outer iterations on the current coarsest active level. - * We use 100 iterations per level (nMGLevels levels total) ---*/ - const bool Convergence_FullMG = - FullMG && (FinestMesh != MESH_0) && - (config[iZone]->GetInnerIter() % 100 == 99); + /*--- Promote to the next finer grid on MG_STARTUP_ITER, MG_STARTUP_CONVERGENCE or + * MG_STARTUP_STAGNATION, whichever comes first. ---*/ + const unsigned long startup_iter = config[iZone]->GetMGOptions().MG_Startup_Iter; + const unsigned long iters_on_level = config[iZone]->GetInnerIter() - mg_ramp_level_start_iter; + MGStartupPromote promote_reason = mg_startup_promote_reason; + if ((promote_reason == MGStartupPromote::NONE) && (startup_iter > 0) && (iters_on_level >= startup_iter)) + promote_reason = MGStartupPromote::BUDGET; + const bool Convergence_FullMG = FullMG && (FinestMesh != MESH_0) && (promote_reason != MGStartupPromote::NONE); - if (!config[iZone]->GetRestart() && FullMG && direct && ( Convergence_FullMG && (FinestMesh != MESH_0 ))) { + if (Convergence_FullMG && direct && (RunTime_EqSystem == RUNTIME_FLOW_SYS)) { SetProlongated_Solution(RunTime_EqSystem, solver_container[iZone][iInst][FinestMesh-1][Solver_Position], @@ -168,6 +350,44 @@ void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, geometry[iZone][iInst][FinestMesh], config[iZone]); + /*--- The scalar equations only restrict downward, so hand the new level their solution too, + * otherwise they restart from their initial condition at every promotion. ---*/ + + for (const auto Scalar_Position : {TURB_SOL, TRANS_SOL, SPECIES_SOL, HEAT_SOL, RAD_SOL}) { + + CSolver* scalar_fine = solver_container[iZone][iInst][FinestMesh-1][Scalar_Position]; + CSolver* scalar_coarse = solver_container[iZone][iInst][FinestMesh][Scalar_Position]; + if ((scalar_fine == nullptr) || (scalar_coarse == nullptr)) continue; + + SetProlongated_ScalarSolution(scalar_fine, scalar_coarse, + geometry[iZone][iInst][FinestMesh-1], + geometry[iZone][iInst][FinestMesh], + config[iZone], Scalar_Position == TURB_SOL); + } + + /*--- Report the promotion before the startup state is reset below. ---*/ + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS + if (rank == MASTER_NODE) { + cout << "Full-MG: mesh level " << FinestMesh << " -> " << FinestMesh - 1 << " after " + << iters_on_level << " iteration(s) ("; + switch (promote_reason) { + case MGStartupPromote::STAGNATION: + cout << "residual stalled for " << config[iZone]->GetMGOptions().MG_Startup_Stagnation_Iter + << " iteration(s)"; + break; + case MGStartupPromote::CONVERGENCE: + cout << "CONV_FIELD dropped " + << fabs(SU2_TYPE::GetValue(config[iZone]->GetMGOptions().MG_Startup_Convergence)) + << " order(s) of magnitude"; + break; + default: + cout << "MG_STARTUP_ITER= " << startup_iter << " reached"; + break; + } + cout << ")." << endl; + } + END_SU2_OMP_SAFE_GLOBAL_ACCESS + SU2_OMP_SAFE_GLOBAL_ACCESS(config[iZone]->SubtractFinestMesh();) } @@ -175,48 +395,65 @@ void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, FinestMesh = config[iZone]->GetFinestMesh(); - /*--- Perform the Full Approximation Scheme multigrid ---*/ - - MultiGrid_Cycle(geometry, solver_container, numerics_container, config, - FinestMesh, RecursiveParam, RunTime_EqSystem, iZone, iInst); - - /*--- Adapt coarse-grid CFL once per cycle using smoothing residuals gathered during the cycle. ---*/ + /*--- Rebuild the coarse-grid CFL before the cycle. ---*/ const unsigned short nMGLevels = config[iZone]->GetnMGLevels(); BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { - /*--- Use the current finest-grid CFL as the base for deterministic - * coarse-level scaling. Fall back to config scalar when local CFL - * adaptation is disabled. ---*/ - passivedouble cfl_base = SU2_TYPE::GetValue( - solver_container[iZone][iInst][FinestMesh][Solver_Position]->GetAvg_CFL_Local()); - if (cfl_base < EPS) - cfl_base = SU2_TYPE::GetValue(config[iZone]->GetCFL(FinestMesh)); + /*--- Capture the configured damping factors, before adaptation has modified them. ---*/ + if (mg_damp_restric_initial < 0.0) { + mg_damp_restric_initial = config[iZone]->GetDamp_Res_Restric(); + mg_damp_prolong_initial = config[iZone]->GetDamp_Correc_Prolong(); + } - const auto& cflScaling = config[iZone]->GetMGOptions().MG_CflScaling; + /*--- On a change of active level, restart the ramp window and the startup state. ---*/ + if (FinestMesh != mg_ramp_last_FinestMesh) { + /*--- Only a promotion has a level below to take the handover CFL from. ---*/ + if (mg_ramp_last_FinestMesh == FinestMesh + 1) + mg_ramp_cfl_start = SU2_TYPE::GetValue(config[iZone]->GetCFL(mg_ramp_last_FinestMesh)); + + mg_ramp_level_start_iter = config[iZone]->GetInnerIter(); + mg_ramp_last_FinestMesh = FinestMesh; - passivedouble CFL_local = cfl_base; - for (unsigned short iMesh = FinestMesh; iMesh < nMGLevels; ++iMesh) { - const unsigned short lvl = iMesh + 1; - /*--- Use per-level scaling factor; clamp to (0,1] to prevent coarse CFL from - * exceeding the fine CFL. Index into cflScaling is iMesh (0-based transition). ---*/ - const passivedouble scale = (iMesh < cflScaling.size()) - ? max(passivedouble{1e-6}, min(passivedouble{1.0}, SU2_TYPE::GetValue(cflScaling[iMesh]))) - : passivedouble{0.25}; - CFL_local *= scale; - config[iZone]->SetCFL(lvl, CFL_local); + /*--- The EMA is measured on a different grid after a promotion. ---*/ + mg_fine_rms_ema = 0.0; + config[iZone]->SetDamp_Res_Restric(mg_damp_restric_initial); + config[iZone]->SetDamp_Correc_Prolong(mg_damp_prolong_initial); + + mg_startup_conv_start.clear(); + mg_startup_conv_prev.clear(); + mg_startup_stall_count = 0; + mg_startup_promote_reason = MGStartupPromote::NONE; } + } END_SU2_OMP_SAFE_GLOBAL_ACCESS - /*--- Propagate the updated coarse-grid CFL to every coarse-grid point (all threads). ---*/ - for (unsigned short iMesh = FinestMesh; iMesh < nMGLevels; ++iMesh) { - const passivedouble CFL_coarse_new = SU2_TYPE::GetValue(config[iZone]->GetCFL(iMesh+1)); - CGeometry* geo_c = geometry[iZone][iInst][iMesh+1]; - CSolver* sol_c = solver_container[iZone][iInst][iMesh+1][Solver_Position]; - SU2_OMP_FOR_STAT(roundUpDiv(geo_c->GetnPoint(), omp_get_num_threads())) - for (auto iPoint = 0ul; iPoint < geo_c->GetnPoint(); iPoint++) - sol_c->GetNodes()->SetLocalCFL(iPoint, CFL_coarse_new); - END_SU2_OMP_FOR + + /*--- While the startup ramps a level, its CFL has to be written before the cycle, not after + * it as in ordinary operation. ---*/ + + const bool fmg_warmup = FullMG && (FinestMesh != MESH_0); + const bool fmg_mesh0_ramp = FullMG_Mesh0RampWindow(config[iZone], FinestMesh, FullMG); + + if (fmg_warmup || fmg_mesh0_ramp) + SetCoarseGridCFL(geometry, solver_container, config, RunTime_EqSystem, iZone, iInst, FinestMesh, FullMG, + fmg_mesh0_ramp); + + /*--- Perform the Full Approximation Scheme multigrid ---*/ + + MultiGrid_Cycle(geometry, solver_container, numerics_container, config, + FinestMesh, RecursiveParam, RunTime_EqSystem, iZone, iInst); + + if (!fmg_warmup && !fmg_mesh0_ramp) + SetCoarseGridCFL(geometry, solver_container, config, RunTime_EqSystem, iZone, iInst, FinestMesh, FullMG, + fmg_mesh0_ramp); + + /*--- Coarse-level residuals are per-rank partial sums; reduce once so the promotion criterion + * agrees on every rank. The smoothing early exit already reduces when it is on. ---*/ + + if (fmg_warmup && !config[iZone]->GetMGOptions().MG_Smooth_EarlyExit) { + solver_container[iZone][iInst][FinestMesh][Solver_Position]->SetResidual_RMS( + geometry[iZone][iInst][FinestMesh], config[iZone], true); } /*--- Computes primitive variables and gradients in the finest mesh (useful for the next solver (turbulence) and output ---*/ @@ -232,9 +469,7 @@ void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, numerics_container[iZone][iInst], config[iZone], FinestMesh, RunTime_EqSystem, &monitor); - /*--- Adapt restriction damping based on coarse-level pre-smoothing workload from this cycle. - * Only effective when MG_SMOOTH_EARLY_EXIT= YES (otherwise all levels always run to completion - * and the signal would always point to "scale down"). ---*/ + /*--- Adapt the damping factors, only meaningful when MG_SMOOTH_EARLY_EXIT= YES. ---*/ const auto& mgOptsZone = config[iZone]->GetMGOptions(); if (mgOptsZone.MG_Smooth_EarlyExit) { BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { @@ -247,8 +482,7 @@ void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, mg_fine_rms_ema = (1.0 - EMA_ALPHA) * mg_fine_rms_ema + EMA_ALPHA * fine_d0; const passivedouble crossCycleRatio = (mg_fine_rms_ema > EPS) ? fine_d0 / mg_fine_rms_ema : 1.0; - /*--- Adapt both damping factors from the same global-trend signal. - * Skip on the first cycle while the EMA is still being seeded. ---*/ + /*--- Skip on the first cycle while the EMA is still being seeded. ---*/ if (ema_ready) adaptDampingFactors(config[iZone], crossCycleRatio); last_crossCycleRatio = crossCycleRatio; } @@ -265,11 +499,7 @@ void CMultiGridIntegration::MultiGrid_Iteration(CGeometry ****geometry, su2double d0, su2double d1, passivedouble worstStepRatio, unsigned short worstStep) -> std::string { - /*--- Show: steps taken / max + exit reason + initial defect scale + d1/d0 ratio. - * r=d1/d0 < 1 means smoother reduced the defect (good). - * r > 1 means smoother grew the defect. - * Exit reason: T=threshold, S=clean stagnation, A=amplifying stagnation, - * ' '=ran to completion. ---*/ + /*--- Steps taken/max, exit reason, initial defect and the d1/d0 ratio. ---*/ std::ostringstream ss; ss << act << "/" << mx; if (act < mx) ss << reason; /*--- only tag early exits ---*/ @@ -437,7 +667,8 @@ void CMultiGridIntegration::MultiGrid_Cycle(CGeometry ****geometry, iMesh+1, nextRecurseParam, RunTime_EqSystem, iZone, iInst); } - /*--- Compute prolongated solution, and smooth the correction $u^(new)_k = u_k + Smooth(I^k_(k+1)(u_(k+1)-I^(k+1)_k u_k))$ ---*/ + /*--- Compute prolongated solution, and smooth the correction $u^(new)_k = u_k + + Smooth(I^k_(k+1)(u_(k+1)-I^(k+1)_k u_k))$ ---*/ GetProlongated_Correction(RunTime_EqSystem, solver_fine, solver_coarse, geometry_fine, geometry_coarse, config); @@ -557,10 +788,8 @@ void CMultiGridIntegration::PreSmoothing(unsigned short RunTime_EqSystem, if (mg_early_exit_flag) break; } - /*--- Record d_{N-1} as the final pre-smooth defect (the last value captured inside the loop). - * For non-coarsest levels MultiGrid_Cycle overwrites this with the exact d_N at zero - * additional cost in the restriction block (Space_Integration already runs there). - * Skip when nPreSmooth==0: lastPreSmoothDefect[iMesh] stays {0,0} (initialized). ---*/ + /*--- Record the final pre-smooth defect; MultiGrid_Cycle overwrites it with the exact + * d_N on non-coarsest levels. ---*/ if (nPreSmooth > 0) { BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { lastPreSmoothRMS[iMesh][1] = mg_prev_smooth_rms; @@ -633,8 +862,7 @@ void CMultiGridIntegration::PostSmoothing(unsigned short RunTime_EqSystem, if (mg_early_exit_flag) break; } - /*--- Record d_{N-1} as the final post-smooth defect (display only). - * Skip when nPostSmooth==0: lastPostSmoothRMS[iMesh] stays {0,0} (initialized). ---*/ + /*--- Record the final post-smooth defect, for display only. ---*/ if (nPostSmooth > 0) { BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { lastPostSmoothRMS[iMesh][1] = mg_prev_smooth_rms; @@ -817,6 +1045,7 @@ void CMultiGridIntegration::SetProlongated_Correction(CSolver *sol_fine, CGeomet Residual_Fine[iVar] = 0.0; su2double correction = factor * Residual_Fine[iVar]; + Solution_Fine[iVar] += correction; } } @@ -833,14 +1062,109 @@ void CMultiGridIntegration::SetProlongated_Solution(unsigned short RunTime_EqSys CGeometry *geo_fine, CGeometry *geo_coarse, CConfig *config) { SU2_ZONE_SCOPED + const unsigned short Solver_Position = config->GetContainerPosition(RunTime_EqSystem); + const bool grid_movement = config->GetGrid_Movement(); + + /*--- Constant injection: every fine child of a coarse CV takes its parent's value. ---*/ + SU2_OMP_FOR_STAT(roundUpDiv(geo_coarse->GetnPointDomain(), omp_get_num_threads())) for (auto Point_Coarse = 0ul; Point_Coarse < geo_coarse->GetnPointDomain(); Point_Coarse++) { + const auto* Solution_Coarse = sol_coarse->GetNodes()->GetSolution(Point_Coarse); for (auto iChildren = 0u; iChildren < geo_coarse->nodes->GetnChildren_CV(Point_Coarse); iChildren++) { - auto Point_Fine = geo_coarse->nodes->GetChildren_CV(Point_Coarse, iChildren); - sol_fine->GetNodes()->SetSolution(Point_Fine, sol_coarse->GetNodes()->GetSolution(Point_Coarse)); + const auto Point_Fine = geo_coarse->nodes->GetChildren_CV(Point_Coarse, iChildren); + sol_fine->GetNodes()->SetSolution(Point_Fine, Solution_Coarse); + } + } + END_SU2_OMP_FOR + + /*--- The injected values do not satisfy the fine-grid wall conditions on their own. ---*/ + + for (auto iMarker = 0u; iMarker < config->GetnMarker_All(); iMarker++) { + if (config->GetViscous_Wall(iMarker)) { + + SU2_OMP_FOR_STAT(32) + for (auto iVertex = 0ul; iVertex < geo_fine->nVertex[iMarker]; iVertex++) { + + const auto Point_Fine = geo_fine->vertex[iMarker][iVertex]->GetNode(); + + if (Solver_Position == FLOW_SOL) { + + /*--- At moving walls, set the solution based on the new density and wall velocity ---*/ + + if (grid_movement) { + const auto* Grid_Vel = geo_fine->nodes->GetGridVel(Point_Fine); + sol_fine->GetNodes()->SetVelSolutionVector(Point_Fine, Grid_Vel); + } + else { + /*--- For stationary no-slip walls, set the velocity to zero. ---*/ + su2double zero[3] = {0.0}; + sol_fine->GetNodes()->SetVelSolutionVector(Point_Fine, zero); + } + + } + + if (Solver_Position == ADJFLOW_SOL) { + sol_fine->GetNodes()->SetVelSolutionDVector(Point_Fine); + } + + } + END_SU2_OMP_FOR + } + } + + /*--- Project the velocity onto the fine-grid wall tangent plane. ---*/ + + sol_fine->MultigridProjectEulerWall(geo_fine, config, false); + + /*--- MPI the new interpolated solution. ---*/ + + sol_fine->InitiateComms(geo_fine, config, MPI_QUANTITIES::SOLUTION); + sol_fine->CompleteComms(geo_fine, config, MPI_QUANTITIES::SOLUTION); + +} + +void CMultiGridIntegration::SetProlongated_ScalarSolution(CSolver *sol_fine, CSolver *sol_coarse, + CGeometry *geo_fine, CGeometry *geo_coarse, + CConfig *config, bool eddy_viscosity) { + SU2_ZONE_SCOPED + + /*--- Constant injection: every fine child of a coarse CV takes its parent's value. ---*/ + + SU2_OMP_FOR_STAT(roundUpDiv(geo_coarse->GetnPointDomain(), omp_get_num_threads())) + for (auto Point_Coarse = 0ul; Point_Coarse < geo_coarse->GetnPointDomain(); Point_Coarse++) { + + const auto* Solution_Coarse = sol_coarse->GetNodes()->GetSolution(Point_Coarse); + const su2double muT_Coarse = eddy_viscosity ? sol_coarse->GetNodes()->GetmuT(Point_Coarse) : su2double(0.0); + + for (auto iChildren = 0u; iChildren < geo_coarse->nodes->GetnChildren_CV(Point_Coarse); iChildren++) { + const auto Point_Fine = geo_coarse->nodes->GetChildren_CV(Point_Coarse, iChildren); + sol_fine->GetNodes()->SetSolution(Point_Fine, Solution_Coarse); + if (eddy_viscosity) sol_fine->GetNodes()->SetmuT(Point_Fine, muT_Coarse); } } END_SU2_OMP_FOR + + /*--- The eddy viscosity is zero on a no-slip wall, the injected value is not. ---*/ + + if (eddy_viscosity) { + for (auto iMarker = 0u; iMarker < config->GetnMarker_All(); iMarker++) { + if (!config->GetViscous_Wall(iMarker)) continue; + + SU2_OMP_FOR_STAT(32) + for (auto iVertex = 0ul; iVertex < geo_fine->nVertex[iMarker]; iVertex++) { + const auto Point_Fine = geo_fine->vertex[iMarker][iVertex]->GetNode(); + sol_fine->GetNodes()->SetmuT(Point_Fine, 0.0); + } + END_SU2_OMP_FOR + } + } + + /*--- MPI the new interpolated solution, and the eddy viscosity with it. ---*/ + + const auto commType = eddy_viscosity ? MPI_QUANTITIES::SOLUTION_EDDY : MPI_QUANTITIES::SOLUTION; + sol_fine->InitiateComms(geo_fine, config, commType); + sol_fine->CompleteComms(geo_fine, config, commType); + } void CMultiGridIntegration::SetForcing_Term(CSolver *sol_fine, CSolver *sol_coarse, CGeometry *geo_fine, diff --git a/SU2_CFD/src/iteration/CFluidIteration.cpp b/SU2_CFD/src/iteration/CFluidIteration.cpp index cb484bd6530b..468e5cbb2312 100644 --- a/SU2_CFD/src/iteration/CFluidIteration.cpp +++ b/SU2_CFD/src/iteration/CFluidIteration.cpp @@ -82,6 +82,10 @@ void CFluidIteration::Iterate(COutput* output, CIntegration**** integration, CGe integration[val_iZone][val_iInst][FLOW_SOL]->MultiGrid_Iteration(geometry, solver, numerics, config, RUNTIME_FLOW_SYS, val_iZone, val_iInst); + /*--- Whether the Full-MG startup is still ramping the finest grid's CFL. ---*/ + + const bool fmg_cfl_ramp = integration[val_iZone][val_iInst][FLOW_SOL]->GetFullMG_CFLRamp(); + /*--- If the flow integration is not fully coupled, run the various single grid integrations. ---*/ if (config[val_iZone]->GetKind_Turb_Model() != TURB_MODEL::NONE && !frozen_visc) { @@ -131,10 +135,9 @@ void CFluidIteration::Iterate(COutput* output, CIntegration**** integration, CGe } /*--- Adapt the CFL number using an exponential progression with under-relaxation approach. - During Full-MG warmup (FinestMesh > MESH_0), skip adaptation entirely until the finest - mesh is active. ---*/ + The Full-MG startup owns the CFL while it ramps, so leave it alone until then. ---*/ SU2_OMP_PARALLEL - if (!disc_adj && config[val_iZone]->GetFinestMesh() == MESH_0) { + if (!disc_adj && config[val_iZone]->GetFinestMesh() == MESH_0 && !fmg_cfl_ramp) { solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->AdaptCFLNumber(geometry[val_iZone][val_iInst], solver[val_iZone][val_iInst], config[val_iZone]); solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->IdentifySolutionOutliers(config[val_iZone], InnerIter); @@ -250,16 +253,31 @@ bool CFluidIteration::Monitor(COutput* output, CIntegration**** integration, CGe /*--- During Full-MG startup FinestMesh > 0: read residuals from the active (coarse) level. ---*/ const unsigned short finestMesh = config[val_iZone]->GetFinestMesh(); + + /*--- The startup has just handed over to the finest grid, so restart the convergence history: + * everything in it up to now was measured on a coarser mesh. ---*/ + + if ((config[val_iZone]->GetMGCycle() == MG_CYCLE::FULL) && (finestMesh == MESH_0) && + (config[val_iZone]->GetInnerIter() == + integration[val_iZone][val_iInst][FLOW_SOL]->GetLevelStartIter())) { + output->ResetConvergenceMonitoring(config[val_iZone]->GetInnerIter()); + } + output->SetHistoryOutput(geometry[val_iZone][val_iInst][finestMesh], solver[val_iZone][val_iInst][finestMesh], config[val_iZone], config[val_iZone]->GetTimeIter(), config[val_iZone]->GetOuterIter(), config[val_iZone]->GetInnerIter()); auto StopCalc = output->GetConvergence(); - /*--- During Full-MG warmup the convergence criterion is evaluated against coarse-mesh residuals. - * Never stop before the fine mesh is active. ---*/ + /*--- Never stop before the fine mesh is active. ---*/ if (finestMesh != MESH_0) StopCalc = false; + /*--- The history fields were just written from the active level, feed them to the startup. ---*/ + if (config[val_iZone]->GetMGCycle() == MG_CYCLE::FULL && finestMesh != MESH_0) { + integration[val_iZone][val_iInst][FLOW_SOL]->MonitorFullMG_Startup(output->GetResidualConvFields(), + config[val_iZone]); + } + /* --- Checking convergence of Fixed CL mode to target CL, and perform finite differencing if needed --*/ if (config[val_iZone]->GetFixed_CL_Mode()) { diff --git a/SU2_CFD/src/output/COutput.cpp b/SU2_CFD/src/output/COutput.cpp index c603ad771e88..07c053d629ae 100644 --- a/SU2_CFD/src/output/COutput.cpp +++ b/SU2_CFD/src/output/COutput.cpp @@ -824,14 +824,14 @@ 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; @@ -842,7 +842,7 @@ bool COutput::GetCauchyCorrectedTimeConvergence(const CConfig *config){ maxTimeDelayActive = false; // Reset for next run } } - + return TimeConvergence; } @@ -935,6 +935,11 @@ bool COutput::ConvergenceMonitoring(CConfig *config, unsigned long Iteration) { convergence = true; + /*--- Count from wherever the history was last restarted. ---*/ + + if (Iteration >= convergenceStartIter) Iteration -= convergenceStartIter; + else Iteration = 0; + for (auto iField_Conv = 0ul; iField_Conv < convFields.size(); iField_Conv++) { const auto& convField = convFields[iField_Conv]; @@ -1247,6 +1252,8 @@ void COutput::PreprocessHistoryOutput(CConfig *config, bool wrt){ CheckHistoryOutput(config->GetnZone()); + CheckFullMG_Startup(config); + if (rank == MASTER_NODE && !noWriting){ /*--- Open history file and print the header ---*/ @@ -1314,6 +1321,31 @@ void COutput::PreprocessMultizoneHistoryOutput(COutput **output, CConfig **confi } +void COutput::CheckFullMG_Startup(const CConfig *config) const { + + if (config->GetMGCycle() != MG_CYCLE::FULL) return; + + /*--- With a residual to monitor the startup always has MG_STARTUP_CONVERGENCE to promote on. ---*/ + + if (!GetResidualConvFields().empty()) return; + + const auto& mgOpts = config->GetMGOptions(); + const bool stagnation_on = (mgOpts.MG_Startup_Stagnation > 0.0) && (mgOpts.MG_Startup_Stagnation_Iter > 0); + + if ((mgOpts.MG_Startup_Iter == 0) && !stagnation_on) { + SU2_MPI::Error("The Full-MG startup has no criterion left to promote on and would stay on the " + "coarsest grid: MG_STARTUP_ITER is 0, MG_STARTUP_STAGNATION is off, and " + "CONV_FIELD holds no residual field for MG_STARTUP_CONVERGENCE to use.", + CURRENT_FUNCTION); + } + + if (rank == MASTER_NODE) { + cout << "WARNING: no residual CONV_FIELD to monitor, the Full-MG startup advances on " + << (stagnation_on ? "MG_STARTUP_STAGNATION and MG_STARTUP_ITER" : "MG_STARTUP_ITER") + << " alone." << endl; + } +} + void COutput::PrepareHistoryFile(CConfig *config){ /*--- Open the history file ---*/ diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index e014c794a52d..5662aa46ed46 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1987,10 +1987,12 @@ void CSolver::AdaptCFLNumber(CGeometry **geometry, } -void CSolver::SetResidual_RMS(const CGeometry *geometry, const CConfig *config) { +void CSolver::SetResidual_RMS(const CGeometry *geometry, const CConfig *config, bool force) { SU2_ZONE_SCOPED - if (geometry->GetMGLevel() != MESH_0 && !config->GetMGOptions().MG_Smooth_EarlyExit) return; + /*--- On coarse levels the reduction is skipped for performance, unless MG_Smooth_EarlyExit + * needs it or the caller asks for it. ---*/ + if (!force && geometry->GetMGLevel() != MESH_0 && !config->GetMGOptions().MG_Smooth_EarlyExit) return; BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { diff --git a/SU2_CFD/src/solvers/CTransLMSolver.cpp b/SU2_CFD/src/solvers/CTransLMSolver.cpp index bb8a53cda125..646aa70250fc 100644 --- a/SU2_CFD/src/solvers/CTransLMSolver.cpp +++ b/SU2_CFD/src/solvers/CTransLMSolver.cpp @@ -66,9 +66,10 @@ CTransLMSolver::CTransLMSolver(CGeometry *geometry, CConfig *config, unsigned sh TransCorrelations.SetOptions(options); TurbFamily = TurbModelFamily(config->GetKind_Turb_Model()); - /*--- Single grid simulation ---*/ + /*--- Single grid simulation, and every level of a Full-MG startup, which solves the transition + * equations on whichever grid the flow solver is currently on. ---*/ - if (iMesh == MESH_0) { + if (iMesh == MESH_0 || config->GetMGCycle() == MG_CYCLE::FULL) { /*--- Define some auxiliary vector related with the residual ---*/ diff --git a/TestCases/euler/naca0012/inv_NACA0012.cfg b/TestCases/euler/naca0012/inv_NACA0012.cfg index ca235bb07a31..0c3304d7fd78 100644 --- a/TestCases/euler/naca0012/inv_NACA0012.cfg +++ b/TestCases/euler/naca0012/inv_NACA0012.cfg @@ -46,23 +46,29 @@ MARKER_DESIGNING = ( airfoil ) % NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES OBJECTIVE_FUNCTION= DRAG -CFL_NUMBER= 5.0 +CFL_NUMBER= 1000.0 CFL_ADAPT= NO CFL_ADAPT_PARAM= ( 1.5, 0.5, 1.0, 100.0 ) -ITER= 250 +ITER= 1000 % ------------------------ LINEAR SOLVER DEFINITION ---------------------------% % LINEAR_SOLVER= FGMRES LINEAR_SOLVER_PREC= LU_SGS -LINEAR_SOLVER_ERROR= 1E-6 +LINEAR_SOLVER_ERROR= 1E-3 LINEAR_SOLVER_ITER= 5 % -------------------------- MULTIGRID PARAMETERS -----------------------------% % MGLEVEL= 3 -MGCYCLE= W_CYCLE -MG_PRE_SMOOTH= ( 1, 2, 3, 3 ) +MGCYCLE= FULLMG_CYCLE + +% full multigrid options +MG_STARTUP_STAGNATION= 0.99 +MG_STARTUP_ITER= 10 +MG_STARTUP_CONVERGENCE= -2 + +MG_PRE_SMOOTH= ( 1, 2, 3, 4 ) MG_POST_SMOOTH= ( 0, 0, 0, 0 ) MG_CORRECTION_SMOOTH= ( 0, 0, 0, 0 ) MG_DAMP_RESTRICTION= 1.0 @@ -98,7 +104,7 @@ DEFORM_STIFFNESS_TYPE= INVERSE_VOLUME % --------------------------- CONVERGENCE PARAMETERS --------------------------% % -CONV_RESIDUAL_MINVAL= -8 +CONV_RESIDUAL_MINVAL= -12 CONV_STARTITER= 10 CONV_CAUCHY_ELEMS= 100 CONV_CAUCHY_EPS= 1E-6 diff --git a/TestCases/parallel_regression_AD.py b/TestCases/parallel_regression_AD.py index 245f060ae6e4..2c10e2579f08 100644 --- a/TestCases/parallel_regression_AD.py +++ b/TestCases/parallel_regression_AD.py @@ -337,7 +337,8 @@ def main(): da_sp_pinArray_cht_2d_dp_hf.cfg_dir = "incomp_navierstokes/streamwise_periodic/chtPinArray_2d" da_sp_pinArray_cht_2d_dp_hf.cfg_file = "DA_configMaster.cfg" da_sp_pinArray_cht_2d_dp_hf.test_iter = 100 - da_sp_pinArray_cht_2d_dp_hf.test_vals = [-11.620815, -6.488915, -13.316362] + da_sp_pinArray_cht_2d_dp_hf.tol = 5e-4 + da_sp_pinArray_cht_2d_dp_hf.test_vals = [-11.620815, -6.488915, -13.316675] da_sp_pinArray_cht_2d_dp_hf.multizone = True test_list.append(da_sp_pinArray_cht_2d_dp_hf) @@ -355,7 +356,8 @@ def main(): da_unsteadyCHT_cylinder.cfg_dir = "coupled_cht/disc_adj_unsteadyCHT_cylinder" da_unsteadyCHT_cylinder.cfg_file = "chtMaster.cfg" da_unsteadyCHT_cylinder.test_iter = 2 - da_unsteadyCHT_cylinder.test_vals = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662021, 0.000000, 10.627000, 0.295190] + da_unsteadyCHT_cylinder.tol = 2e-5 + da_unsteadyCHT_cylinder.test_vals = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662005, 0.000000, 10.627000, 0.295190] da_unsteadyCHT_cylinder.test_vals_aarch64 = [-8.479629, -9.239920, -9.234868, -15.934511, -13.662012, 0.000000, 89.932000, 0.295190] da_unsteadyCHT_cylinder.unsteady = True da_unsteadyCHT_cylinder.multizone = True diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index 1d7cd05c5079..aa22e30d8ae9 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -106,6 +106,14 @@ def main(): naca0012.test_vals = [-4.489721, -3.937702, 0.293347, 0.025228] test_list.append(naca0012) + # NACA0012 - FMG test + naca0012_FMG = TestCase('naca0012_FMG') + naca0012_FMG.cfg_dir = "euler/naca0012" + naca0012_FMG.cfg_file = "inv_NACA0012.cfg" + naca0012_FMG.test_iter = 20 + naca0012_FMG.test_vals = [-3.880921, -3.284668, 0.176713, 0.044753] + test_list.append(naca0012_FMG) + # Supersonic wedge wedge = TestCase('wedge') wedge.cfg_dir = "euler/wedge" diff --git a/config_template.cfg b/config_template.cfg index ff33a439456c..535ae564510f 100644 --- a/config_template.cfg +++ b/config_template.cfg @@ -1739,6 +1739,31 @@ MG_IMPLICIT_LINES= NO % Maximum nodes on a wall-normal implicit agglomeration line, including the wall seed. % Increase to extend the line deeper into the boundary layer (default 20). MG_IMPLICIT_LINES_MAX_LENGTH= 20 +% +% Number of iterations spent on each mesh during the Full Multigrid (FMG) startup phase. After +% this many iterations the solution is prolongated to the next finer mesh. It is also the length +% of the CFL ramp every level is brought up over, the finest grid included, which is what keeps +% the CFL from jumping when the startup hands the solution over. 0 removes the iteration budget, +% leaving MG_STARTUP_CONVERGENCE and MG_STARTUP_STAGNATION to decide, and disables the ramp +% (default 100). +MG_STARTUP_ITER= 100 +% +% Full-MG promotion on convergence: orders of magnitude (log10) that CONV_FIELD must drop +% on the active level, relative to its value when that level became active, before +% promoting to the next finer level early. Negative for a drop, on the same scale as +% CONV_RESIDUAL_MINVAL, so -2 asks for a fall to 1% of the starting residual. Only +% residual convergence fields are used. Make it more negative to demand a deeper drop; a +% large negative value effectively disables this criterion (default -2). +MG_STARTUP_CONVERGENCE= -2 +% +% Full-MG promotion on stagnation: if the active level's residual ratio between +% successive iterations exceeds this for MG_STARTUP_STAGNATION_ITER consecutive +% iterations, promote to the next finer level early. 0 disables (default 0.99). +MG_STARTUP_STAGNATION= 0.99 +% +% Consecutive stalled iterations required before Full-MG promotes on stagnation. +% 0 disables the criterion, as MG_STARTUP_STAGNATION= 0 does (default 5). +MG_STARTUP_STAGNATION_ITER= 5 % -------------------------- MESH SMOOTHING -----------------------------% % From 07aa46b1868655ec01f534bf3ac84ba5fbb6b822 Mon Sep 17 00:00:00 2001 From: Davide <58471586+ddg93@users.noreply.github.com> Date: Tue, 1 Sep 2026 05:10:53 +0200 Subject: [PATCH 38/61] [GSoC] Feature: LU-SGS preconditioner to GPU (#2872) This PR ports the LU-SGS preconditioner to GPU ## Proposed Changes This PR introduces a build phase for the LU-SGS preconditioner where the inverse of the diagonal D is calculated to facilitate the compute phase. Then, forward and backward sweeps are implemented through custom CUDA kernels matching the logic of the CPU section (except for the inverse of D, which is directly multiplied in the GPU logic). These kernels are managed by the compute method on GPU which maintains the MPI communications. ## Related Work This PR follows the discussion of #2843 . Kernel level dependencies are managed as for the ILU preconditioner proposed in #2858 . ## Validation CPU and GPU implementations of the LU-SGS preconditioner are compared for validation of this PR on the rae2822 test-case. After one iteration, the first, preconditioned Krylov vector is compared finding matching results down to machine precision. Residuals are compared after 100 iterations finding matching results. ## TO-DO - [X] complete with the quantized_mode option path; - [ ] run benchmarks on modern GPUs. Current evaluations on a P620 are inconclusive; - [ ] profiling of the custom kernels; ## PR Checklist *Put an X by all that apply. You can fill this out after submitting the PR. If you have any questions, don't hesitate to ask! We want to help. These are a guide for you to know what the reviewers will be looking for in your contribution.* - [X] I am submitting my contribution to the develop branch. - [X] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [X] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [X] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [ ] I have added a test case that demonstrates my contribution, if necessary. - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --------- Co-authored-by: Nijso --- AUTHORS.md | 1 + .../linear_algebra/CPreconditioner.hpp | 4 +- Common/include/linear_algebra/CSysMatrix.hpp | 51 ++- Common/src/linear_algebra/CSysMatrix.cpp | 192 +++++++--- Common/src/linear_algebra/CSysMatrixGPU.cu | 356 +++++++++++++++--- 5 files changed, 488 insertions(+), 116 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index fea157beba2f..67ecbf541e5a 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -69,6 +69,7 @@ Christian Bauer Clark Pederson Daumantas Kavolis Dave Taflin +Davide Di Giusto Eduardo Molina Edwin van der Weide Eitan Aberman diff --git a/Common/include/linear_algebra/CPreconditioner.hpp b/Common/include/linear_algebra/CPreconditioner.hpp index 2b6c67701ff6..8bd7d5157105 100644 --- a/Common/include/linear_algebra/CPreconditioner.hpp +++ b/Common/include/linear_algebra/CPreconditioner.hpp @@ -260,13 +260,13 @@ class CLU_SGSPreconditioner final : public CPreconditioner { * \param[out] v - CSysVector that is the result of the preconditioning. */ inline void operator()(const CSysVector& u, CSysVector& v) const override { - ApplyPreconditionerOnHost(u, v, [&] { sparse_matrix.ComputeLU_SGSPreconditioner(u, v, geometry, config); }); + sparse_matrix.ComputeLU_SGSPreconditioner(u, v, geometry, config); } /*! * \note Also serves Q_LU_SGS: quantizes the diagonal blocks, no-op for plain LU_SGS. */ - inline void Build() override { sparse_matrix.QuantizeDiagonalBlocks(); } + inline void Build() override { sparse_matrix.BuildLU_SGSPreconditioner(); } }; /*! diff --git a/Common/include/linear_algebra/CSysMatrix.hpp b/Common/include/linear_algebra/CSysMatrix.hpp index abecc767e26c..384b0bf6212f 100644 --- a/Common/include/linear_algebra/CSysMatrix.hpp +++ b/Common/include/linear_algebra/CSysMatrix.hpp @@ -303,7 +303,7 @@ class CSysMatrix { LDU gpu; /*!< \brief Device matrix (all pointers to GPU memory). */ LDU ilu; /*!< \brief ILU factorization, host (values owned; pattern from geometry). */ LDU gpu_ilu; /*!< \brief ILU factorization, device (values and pattern in GPU memory). */ - ScalarType* d_invM = nullptr; /*!< \brief Device inverse diagonal blocks for the Jacobi preconditioner. */ + ScalarType* d_invM = nullptr; /*!< \brief Device inverse diagonal blocks for the Jacobi or LU-SGS preconditioner. */ /*--- Quantized off-diagonal storage (used when quantized_mode == true). ---*/ using QuantType = int8_t; @@ -356,7 +356,7 @@ class CSysMatrix { * rows in level k only depend on rows in levels < k. The same table drives the forward * (increasing level) and backward (decreasing level) substitution, because the U pattern is * the transpose of the L pattern. Used directly by the host/OMP substitution, and flattened - * into ilu_level_ptr / d_ilu_level_idx below for the GPU triangular solves. */ + * into ilu_level_ptr / d_precond_level_idx below for the GPU triangular solves. */ CCompressedSparsePatternUL levels_ilu; /*!< \brief Coloring of the (domain-only) ILU dependency graph, used only by the GPU iterative @@ -373,19 +373,22 @@ class CSysMatrix { vector ilu_color_ptr; /*!< \brief Start of each color in d_ilu_color_idx, size nColors+1. */ su2uint* d_ilu_color_idx = nullptr; /*!< \brief Row indices, grouped by color. */ - vector ilu_level_ptr; /*!< \brief Start of each level in d_ilu_level_idx, size nLevels+1. */ - su2uint* d_ilu_level_idx = nullptr; /*!< \brief Row indices, grouped by level. */ + vector precond_level_ptr; /*!< \brief Start of each level in d_precond_level_idx, size nLevels+1. */ + su2uint* d_precond_level_idx = nullptr; /*!< \brief Row indices, grouped by level. */ /*--- The per-color (factorization) and per-level (triangular solves) kernel launch sequences * are identical on every call: same grid/block sizes, same device pointers (all fixed members, * allocated once). Each is captured once into a CUDA graph and replayed to remove * host-side launch overhead without changing the parallelization. ---*/ mutable struct CUgraphExec_st* ilu_build_graph_exec = nullptr; - mutable struct CUgraphExec_st* ilu_apply_graph_exec = nullptr; - mutable const ScalarType* ilu_apply_graph_vec = nullptr; /*!< \brief Pointers the apply graph - * was captured with, to detect when - * it must be recaptured. */ - mutable ScalarType* ilu_apply_graph_prod = nullptr; + mutable struct CUgraphExec_st* precond_fwd_graph_exec = nullptr; // ILU or LU-SGS forward only + mutable struct CUgraphExec_st* precond_bwd_graph_exec = nullptr; // LU-SGS backward only + mutable const ScalarType* precond_fwd_graph_vec = nullptr; /*!< \brief Pointers the apply graph + * was captured with, to detect when + * it must be recaptured. */ + mutable ScalarType* precond_fwd_graph_prod = nullptr; + mutable ScalarType* precond_bwd_graph_prod = nullptr; + /*--- Non-default stream, needed for two mutually exclusive uses that never overlap on a given * matrix (quantized_mode and ILU are alternative preconditioner choices, decided once in * Initialize()): (1) the ILU build/apply CUDA graphs below, since the legacy default stream @@ -657,6 +660,31 @@ class CSysMatrix { */ void ComputeILUPreconditionerGPU(const CSysVector& vec, CSysVector& prod) const; + /*! + * \brief Build the LU-SGS preconditioner on the device + */ + void BuildLU_SGSPreconditionerGPU(); + + /*! + * \brief Apply the LU-SGS preconditioner forward pass on the device + */ + void ComputeLU_SGSForwardGPU(const CSysVector& vec, CSysVector& prod) const; + + /*! + * \brief Apply the LU-SGS preconditioner backward pass on the device + */ + void ComputeLU_SGSBackwardGPU(CSysVector& prod) const; + + /*! + * \brief Apply the forward pass of the LU-SGS preconditioner + */ + void ComputeLU_SGSPreconditionerForward(const CSysVector& vec, CSysVector& prod) const; + + /*! + * \brief Apply the backward pass of the LU-SGS preconditioner + */ + void ComputeLU_SGSPreconditionerBackward(CSysVector& prod) const; + public: /*! * \brief Constructor of the class. @@ -1230,6 +1258,11 @@ class CSysMatrix { void ComputeILUPreconditioner(const CSysVector& vec, CSysVector& prod, CGeometry* geometry, const CConfig* config) const; + /*! + * \brief Build the LU-SGS preconditioner. + */ + void BuildLU_SGSPreconditioner(); + /*! * \brief Multiply CSysVector by the preconditioner * \param[in] vec - CSysVector to be multiplied by the preconditioner. diff --git a/Common/src/linear_algebra/CSysMatrix.cpp b/Common/src/linear_algebra/CSysMatrix.cpp index 84f30a2aa3d2..e6292b4c10c5 100644 --- a/Common/src/linear_algebra/CSysMatrix.cpp +++ b/Common/src/linear_algebra/CSysMatrix.cpp @@ -159,10 +159,11 @@ CSysMatrix::~CSysMatrix() { freeLDU(d_q_blocks); GPUMemoryAllocation::gpu_free(d_invM); GPUMemoryAllocation::gpu_free(d_ilu_color_idx); - GPUMemoryAllocation::gpu_free(d_ilu_level_idx); + GPUMemoryAllocation::gpu_free(d_precond_level_idx); #ifdef SU2_ENABLE_CUDA_KERNELS if (ilu_build_graph_exec != nullptr) cudaGraphExecDestroy(ilu_build_graph_exec); - if (ilu_apply_graph_exec != nullptr) cudaGraphExecDestroy(ilu_apply_graph_exec); + if (precond_fwd_graph_exec != nullptr) cudaGraphExecDestroy(precond_fwd_graph_exec); + if (precond_bwd_graph_exec != nullptr) cudaGraphExecDestroy(precond_bwd_graph_exec); if (aux_stream != nullptr) cudaStreamDestroy(aux_stream); if (htd_event != nullptr) cudaEventDestroy(htd_event); #endif @@ -217,6 +218,7 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi const bool ilu_needed = (prec == ILU); const bool diag_needed = (prec == JACOBI) || (prec == Q_JACOBI) || (prec == LINELET); + const bool lu_sgs_on_device = useCuda && (prec == LU_SGS || prec == Q_LU_SGS); /*--- Linelet also builds the Jacobi preconditioner but reads the inverse diagonal blocks on * the host, so only plain (or quantized) Jacobi can keep them exclusively on the device. ---*/ @@ -408,62 +410,72 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi if (diag_needed) allocAndInit(invM, nPointDomain * nVar * nEqn); - if (jacobi_on_device) { + const bool any_precond_on_device = useCuda && (jacobi_on_device || lu_sgs_on_device || ilu_needed); + + if (any_precond_on_device) { if (nVar != nEqn) { - SU2_MPI::Error("CUDA Jacobi preconditioner requires square blocks.", CURRENT_FUNCTION); + SU2_MPI::Error("CUDA preconditioners require square blocks.", CURRENT_FUNCTION); } if (nVar * nVar > 1024) { - SU2_MPI::Error("CUDA Jacobi preconditioner uses one thread per block entry, nVar is too large.", - CURRENT_FUNCTION); + SU2_MPI::Error("CUDA preconditioners use one thread per block entry, nVar is too large.", CURRENT_FUNCTION); } - d_invM = GPUMemoryAllocation::gpu_alloc(nPointDomain * nVar * nEqn * sizeof(ScalarType)); - } - if (useCuda && ilu_needed) { - if (nVar != nEqn) { - SU2_MPI::Error("CUDA ILU factorization requires square blocks.", CURRENT_FUNCTION); - } - if (nVar * nVar > 1024) { - SU2_MPI::Error("CUDA ILU factorization uses one thread per block entry, nVar is too large.", CURRENT_FUNCTION); + if (jacobi_on_device || lu_sgs_on_device) { + d_invM = GPUMemoryAllocation::gpu_alloc(nPointDomain * nVar * nEqn * sizeof(ScalarType)); } - /*--- The factors are built and used on the device, only the pattern and the level table - * are uploaded (once, here) because they do not change. ---*/ - gpu_ilu.nnz_l = ilu.nnz_l; - gpu_ilu.nnz_u = ilu.nnz_u; - GPUAllocAndInit(gpu_ilu.d, nPointDomain * nVar * nEqn); - GPUAllocAndInit(gpu_ilu.l, ilu.nnz_l * nVar * nEqn); - GPUAllocAndInit(gpu_ilu.u, ilu.nnz_u * nVar * nEqn); - GPUAllocAndCopy(gpu_ilu.row_ptr_l, ilu.row_ptr_l, nPointDomain + 1); - GPUAllocAndCopy(gpu_ilu.col_ind_l, ilu.col_ind_l, ilu.nnz_l); - GPUAllocAndCopy(gpu_ilu.row_ptr_u, ilu.row_ptr_u, nPointDomain + 1); - GPUAllocAndCopy(gpu_ilu.col_ind_u, ilu.col_ind_u, ilu.nnz_u); - - /*--- Flatten the coloring, the index type differs from the one of the pattern. It drives - * the factorization on the device. ---*/ - std::vector color_idx; - color_idx.reserve(nPointDomain); - ilu_color_ptr.clear(); - ilu_color_ptr.push_back(0); - for (auto color = 0ul; color < color_ilu.getOuterSize(); ++color) { - for (auto k = 0ul; k < color_ilu.getNumNonZeros(color); ++k) { - color_idx.push_back(static_cast(color_ilu.getInnerIdx(color, k))); + + /*--- Flattens a grouped sparse pattern (levels, colors) into a host ptr and device index arrays. + * Used in ILU levels and colors, and LU-SGS levels---*/ + auto FlattenGroupToDevice = [](const auto& grouped, std::vector& group_ptr, unsigned long reserveHint, + unsigned long bound = ~0ul) { + std::vector flat_idx; + flat_idx.reserve(reserveHint); + group_ptr.clear(); + group_ptr.push_back(0); + for (auto group = 0ul; group < grouped.getOuterSize(); ++group) { + for (auto k = 0ul; k < grouped.getNumNonZeros(group); ++k) { + auto idx = grouped.getInnerIdx(group, k); + if (static_cast(idx) >= bound) + continue; // prevent out of bounds in LU-SGS kernels if more than 1 mpi task + flat_idx.push_back(static_cast(idx)); + } + group_ptr.push_back(static_cast(flat_idx.size())); } - ilu_color_ptr.push_back(static_cast(color_idx.size())); + return GPUMemoryAllocation::gpu_alloc_cpy(flat_idx.data(), flat_idx.size() * sizeof(su2uint)); + }; + + if (lu_sgs_on_device) { + // get the zero-filled sparse pattern for the LU-SGS + const auto& pat_lusgs = geometry->GetSparsePattern(type, 0); + + /*--- Compute the levels using the lower pattern for the forward pass and + * reverse the levels for the backward pass. This works if L and U are symmetric, to be verified ---*/ + auto levels_lusgs = computeLevels(pat_lusgs.l); + + /*--- Flatten levels_lusgs. It drives both triangular solves on the device. ---*/ + d_precond_level_idx = FlattenGroupToDevice(levels_lusgs, precond_level_ptr, nPointDomain, nPointDomain); } - d_ilu_color_idx = GPUMemoryAllocation::gpu_alloc_cpy(color_idx.data(), color_idx.size() * sizeof(su2uint)); - /*--- Flatten levels_ilu the same way. It drives both triangular solves on the device. ---*/ - std::vector level_idx; - level_idx.reserve(nPointDomain); - ilu_level_ptr.clear(); - ilu_level_ptr.push_back(0); - for (auto level = 0ul; level < levels_ilu.getOuterSize(); ++level) { - for (auto k = 0ul; k < levels_ilu.getNumNonZeros(level); ++k) { - level_idx.push_back(static_cast(levels_ilu.getInnerIdx(level, k))); - } - ilu_level_ptr.push_back(static_cast(level_idx.size())); + if (ilu_needed) { + /*--- The factors are built and used on the device, only the pattern and the level table + * are uploaded (once, here) because they do not change. ---*/ + gpu_ilu.nnz_l = ilu.nnz_l; + gpu_ilu.nnz_u = ilu.nnz_u; + GPUAllocAndInit(gpu_ilu.d, nPointDomain * nVar * nEqn); + GPUAllocAndInit(gpu_ilu.l, ilu.nnz_l * nVar * nEqn); + GPUAllocAndInit(gpu_ilu.u, ilu.nnz_u * nVar * nEqn); + GPUAllocAndCopy(gpu_ilu.row_ptr_l, ilu.row_ptr_l, nPointDomain + 1); + GPUAllocAndCopy(gpu_ilu.col_ind_l, ilu.col_ind_l, ilu.nnz_l); + GPUAllocAndCopy(gpu_ilu.row_ptr_u, ilu.row_ptr_u, nPointDomain + 1); + GPUAllocAndCopy(gpu_ilu.col_ind_u, ilu.col_ind_u, ilu.nnz_u); + + /*--- Flatten the coloring, the index type differs from the one of the pattern. It drives + * the factorization on the device. ---*/ + d_ilu_color_idx = FlattenGroupToDevice(color_ilu, ilu_color_ptr, nPointDomain); + + /*--- Flatten levels_ilu the same way. It drives both triangular solves on the device. ---*/ + d_precond_level_idx = FlattenGroupToDevice(levels_ilu, precond_level_ptr, nPointDomain); } - d_ilu_level_idx = GPUMemoryAllocation::gpu_alloc_cpy(level_idx.data(), level_idx.size() * sizeof(su2uint)); } /*--- Thread parallel initialization. ---*/ @@ -1284,11 +1296,69 @@ void CSysMatrix::ComputeILUPreconditioner(const CSysVector +void CSysMatrix::BuildLU_SGSPreconditioner() { + SU2_ZONE_SCOPED + + /*--- Quantize diagonal blocks if mode is active ---*/ + QuantizeDiagonalBlocks(); + + /*--- if on GPU, precompute the inverse of the diagonal D. Otherwise, this is a no-op ---*/ + if (useCuda) { +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + SU2_DEVICE_REGION(BuildLU_SGSPreconditionerGPU();) + return; + } else { + GPUNotAvailable(CURRENT_FUNCTION); + } +#else + GPUNotAvailable(CURRENT_FUNCTION); +#endif + } +} + template void CSysMatrix::ComputeLU_SGSPreconditioner(const CSysVector& vec, CSysVector& prod, CGeometry* geometry, const CConfig* config) const { SU2_ZONE_SCOPED + + /*--- First part of the symmetric iteration: (D+L).x* = b ---*/ + ComputeLU_SGSPreconditionerForward(vec, prod); + + /*--- MPI Parallelization ---*/ + + CSysMatrixComms::Initiate(prod, geometry, config); + CSysMatrixComms::Complete(prod, geometry, config); + + /*--- Second part of the symmetric iteration: (D+U).x_(1) = D.x* ---*/ + ComputeLU_SGSPreconditionerBackward(prod); + + /*--- MPI Parallelization ---*/ + + CSysMatrixComms::Initiate(prod, geometry, config); + CSysMatrixComms::Complete(prod, geometry, config); +} + +template +void CSysMatrix::ComputeLU_SGSPreconditionerForward(const CSysVector& vec, + CSysVector& prod) const { + SU2_ZONE_SCOPED + + if (useCuda) { +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + SU2_DEVICE_REGION(ComputeLU_SGSForwardGPU(vec, prod);) + return; + } else { + GPUNotAvailable(CURRENT_FUNCTION); + } +#else + GPUNotAvailable(CURRENT_FUNCTION); +#endif + } + /*--- First part of the symmetric iteration: (D+L).x* = b ---*/ /*--- Coherent view of vectors. ---*/ @@ -1325,14 +1395,27 @@ void CSysMatrix::ComputeLU_SGSPreconditioner(const CSysVector +void CSysMatrix::ComputeLU_SGSPreconditionerBackward(CSysVector& prod) const { + SU2_ZONE_SCOPED /*--- Second part of the symmetric iteration: (D+U).x_(1) = D.x* ---*/ + if (useCuda) { +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + SU2_DEVICE_REGION(ComputeLU_SGSBackwardGPU(prod);) + return; + } else { + GPUNotAvailable(CURRENT_FUNCTION); + } +#else + GPUNotAvailable(CURRENT_FUNCTION); +#endif + } + /*--- OpenMP Parallelization ---*/ SU2_OMP_FOR_STAT(1) for (unsigned long thread = 0; thread < omp_num_parts; ++thread) { @@ -1363,11 +1446,6 @@ void CSysMatrix::ComputeLU_SGSPreconditioner(const CSysVector diff --git a/Common/src/linear_algebra/CSysMatrixGPU.cu b/Common/src/linear_algebra/CSysMatrixGPU.cu index 56b8a1006319..723c8fb82680 100644 --- a/Common/src/linear_algebra/CSysMatrixGPU.cu +++ b/Common/src/linear_algebra/CSysMatrixGPU.cu @@ -308,6 +308,75 @@ __global__ void IluFactorColorKernel(const su2uint* __restrict__ color_idx, unsi M.d[iRow * blockSize + tid] = Lij[tid]; } +/*! + * \brief Compute blk[iVar,jVar].x[col,jVar] and sum over neighbor rows on device + * \note used for L.x* and U.x* in ILU and LU-SGS preconditioners + */ +template +__device__ FORCEINLINE ScalarType DeviceSparseBlockMatVec(unsigned long iRow, unsigned long iVar, unsigned long jVar, unsigned long nVar, + const su2uint* __restrict__ row_ptr, const su2uint* __restrict__ col_ind, + const ScalarType* __restrict__ blk, const ScalarType* __restrict__ x, + unsigned long nRows= ~0ul) { + + const auto blockSize = nVar * nVar; + // compute blk[iVar,jVar].x[col,jVar] and sum over row + ScalarType acc = 0; + for (auto k = row_ptr[iRow]; k < row_ptr[iRow + 1]; ++k) { + const unsigned long jPoint = col_ind[k]; + if (jPoint >= nRows) break; //default is largest possible value thus skipped by default + acc += blk[k * blockSize + iVar * nVar + jVar] * x[jPoint * nVar + jVar]; + } + return acc; +} + +/*! + * \brief Compute Quantized blk[iVar,jVar].x[col,jVar] and sum over neighbor rows on device + */ +template +__device__ FORCEINLINE ScalarType QuantizedDeviceSparseBlockMatVec(unsigned long iRow, unsigned long iVar, unsigned long jVar, unsigned long nVar, + const su2uint* __restrict__ row_ptr, const su2uint* __restrict__ col_ind, + const QuantType* __restrict__ q_blk, const QuantScaleType* __restrict__ q_scale, + const ScalarType* __restrict__ x, unsigned long nRows= ~0ul) { + + const auto blockSize = nVar * nVar; + ScalarType acc = 0; + + for (auto k = row_ptr[iRow]; k < row_ptr[iRow + 1]; ++k) { + const unsigned long jPoint = col_ind[k]; + if (jPoint >= nRows) break; //default is largest possible value thus skipped by default + const float scale = DecodeQuantScale(q_scale[k * nVar + iVar]); + ScalarType q_val = static_cast(q_blk[k * blockSize + iVar * nVar + jVar]); // directly cast to ScalarType + acc += scale * q_val * x[jPoint * nVar + jVar]; + } + return acc; +} + + +/*! + * \brief Compute the partial sum across a row on device + * \note used after DeviceSparseBlockMatVec, it completes the dot product for a given iVar + */ +template +__device__ FORCEINLINE ScalarType DeviceReduceBlockRow(const ScalarType* __restrict__ x, unsigned long iVar, unsigned long nVar) { + ScalarType sum = 0; + for (auto j = 0ul; j < nVar; ++j) sum += x[iVar * nVar + j]; + return sum; +} + +/*! + * \brief Compute the block by vector multiplication + */ +template + __device__ FORCEINLINE ScalarType DeviceDenseBlockMatVec(const ScalarType* __restrict__ blk, const ScalarType* __restrict__ x, + ScalarType* __restrict__ partial, unsigned long tid, + unsigned long iVar, unsigned long jVar, unsigned long nVar) { + + // Compute blk.x + partial[tid] = blk[iVar * nVar + jVar] * x[jVar]; + __syncthreads(); + return DeviceReduceBlockRow(partial, iVar, nVar); +} + /*! * \brief Exact forward substitution for the rows of one level, (L+I).prod = vec. * \note Every row in a level only depends on rows in earlier levels, which are already @@ -333,20 +402,10 @@ __global__ void IluForwardKernel(const su2uint* __restrict__ level_idx, unsigned extern __shared__ __align__(sizeof(double)) char smem[]; auto* partial = reinterpret_cast(smem); - ScalarType acc = 0; - for (auto kl = M.row_ptr_l[iRow]; kl < M.row_ptr_l[iRow + 1]; ++kl) { - const unsigned long jPoint = M.col_ind_l[kl]; - const auto* blk = M.l + kl * nVar * nVar; - acc += blk[iVar * nVar + jVar] * prod[jPoint * nVar + jVar]; - } - partial[tid] = acc; + partial[tid] = DeviceSparseBlockMatVec(iRow, iVar, jVar, nVar, M.row_ptr_l, M.col_ind_l, M.l, prod); __syncthreads(); - if (jVar == 0) { - ScalarType sum = vec[iRow * nVar + iVar]; - for (auto j = 0ul; j < nVar; ++j) sum -= partial[iVar * nVar + j]; - prod[iRow * nVar + iVar] = sum; - } + if (jVar == 0) prod[iRow * nVar + iVar] = vec[iRow * nVar + iVar] - DeviceReduceBlockRow(partial, iVar, nVar); } /*! @@ -375,30 +434,14 @@ __global__ void IluBackwardKernel(const su2uint* __restrict__ level_idx, unsigne auto* partial = reinterpret_cast(smem); auto* aux = partial + blockSize; - ScalarType acc = 0; - for (auto ku = M.row_ptr_u[iRow]; ku < M.row_ptr_u[iRow + 1]; ++ku) { - const unsigned long jPoint = M.col_ind_u[ku]; - if (jPoint >= nRows) break; - const auto* blk = M.u + ku * blockSize; - acc += blk[iVar * nVar + jVar] * prod[jPoint * nVar + jVar]; - } - partial[tid] = acc; + partial[tid] = DeviceSparseBlockMatVec(iRow, iVar, jVar, nVar, M.row_ptr_u, M.col_ind_u, M.u, prod, nRows); __syncthreads(); - if (jVar == 0) { - ScalarType sum = prod[iRow * nVar + iVar]; - for (auto j = 0ul; j < nVar; ++j) sum -= partial[iVar * nVar + j]; - aux[iVar] = sum; - } + if (jVar == 0) aux[iVar] = prod[iRow * nVar + iVar] - DeviceReduceBlockRow(partial, iVar, nVar); __syncthreads(); - if (jVar == 0) { - /*--- The diagonal blocks are stored inverted by the factorization. ---*/ - const auto* invUii = M.d + iRow * blockSize; - ScalarType out = 0; - for (auto k = 0ul; k < nVar; ++k) out += invUii[iVar * nVar + k] * aux[k]; - prod[iRow * nVar + iVar] = out; - } + ScalarType out = DeviceDenseBlockMatVec(M.d + iRow * blockSize, aux, partial, tid, iVar, jVar, nVar); + if (jVar == 0) prod[iRow * nVar + iVar] = out; } /*! @@ -636,7 +679,7 @@ void CSysMatrix::ComputeILUPreconditionerGPU(const CSysVector::ComputeILUPreconditionerGPU(const CSysVector::ComputeILUPreconditionerGPU(const CSysVector - <<>>(d_ilu_level_idx, begin, size, nVar, M, d_vec, d_prod); + <<>>(d_precond_level_idx, begin, size, nVar, M, d_vec, d_prod); } /*--- Backward substitution: one exact pass over the levels in decreasing order, * U.prod = prod, see IluBackwardKernel. ---*/ for (auto level = nLevels; level > 0;) { --level; - const auto begin = ilu_level_ptr[level]; - const auto size = ilu_level_ptr[level + 1] - begin; + const auto begin = precond_level_ptr[level]; + const auto size = precond_level_ptr[level + 1] - begin; if (size == 0) continue; IluBackwardKernel - <<>>(d_ilu_level_idx, begin, size, nPointDomain, nVar, M, d_prod); + <<>>(d_precond_level_idx, begin, size, nPointDomain, nVar, M, d_prod); } gpuErrChk(cudaStreamEndCapture(aux_stream, &graph)); - gpuErrChk(cudaGraphInstantiate(&ilu_apply_graph_exec, graph, nullptr, nullptr, 0)); + gpuErrChk(cudaGraphInstantiate(&precond_fwd_graph_exec, graph, nullptr, nullptr, 0)); gpuErrChk(cudaGraphDestroy(graph)); - ilu_apply_graph_vec = d_vec; - ilu_apply_graph_prod = d_prod; + precond_fwd_graph_vec = d_vec; + precond_fwd_graph_prod = d_prod; } - gpuErrChk(cudaGraphLaunch(ilu_apply_graph_exec, aux_stream)); + gpuErrChk(cudaGraphLaunch(precond_fwd_graph_exec, aux_stream)); gpuErrChk(cudaStreamSynchronize(aux_stream)); gpuErrChk(cudaGetLastError()); } +/*! + * \brief Exact forward substitution for the rows of one level, x* = D^{-1}.(b-Lx*) + * \note See notes in IluForwardKernel for more details. + */ +template +__global__ void LU_SGS_ForwardKernel(const su2uint* __restrict__ level_idx, unsigned long level_begin, + unsigned long level_size, unsigned long nVar, DeviceLDU M, + const QuantType* __restrict__ q_l, const QuantScaleType* __restrict__ q_scale_l, + const ScalarType* __restrict__ invD, const ScalarType* __restrict__ vec, + ScalarType* __restrict__ prod, bool quantized_mode) { + if (blockIdx.x >= level_size) return; + + const unsigned long iRow = level_idx[level_begin + blockIdx.x]; + const auto blockSize = nVar * nVar; + const unsigned long tid = threadIdx.x; + const auto iVar = tid / nVar, jVar = tid % nVar; + + extern __shared__ __align__(sizeof(double)) char smem[]; + auto* partial = reinterpret_cast(smem); // serves nVar * nVar threads + auto* aux = partial + blockSize; // skip nVar * nVar threads, serves nVar threads + + // Compute L.x* + if (quantized_mode) { + partial[tid] = QuantizedDeviceSparseBlockMatVec(iRow, iVar, jVar, nVar, M.row_ptr_l, M.col_ind_l, q_l, q_scale_l, prod); + } else { + partial[tid] = DeviceSparseBlockMatVec(iRow, iVar, jVar, nVar, M.row_ptr_l, M.col_ind_l, M.l, prod); + } + __syncthreads(); + + // Compute y = b - L.x* + if (jVar == 0) aux[iVar] = vec[iRow * nVar + iVar] - DeviceReduceBlockRow(partial, iVar, nVar); + __syncthreads(); + + // Compute x* - D^{-1}.y + ScalarType out = DeviceDenseBlockMatVec(invD + iRow * blockSize, aux, partial, tid, iVar, jVar, nVar); + if (jVar == 0) prod[iRow * nVar + iVar] = out; +} + + +/*! + * \brief Exact backward substitution for the rows of one level, x* = D^{-1}.(D.x* - U.x) = x* - D^{-1}.U.x + * \note See notes in IluBackwardKernel for more details + */ +template +__global__ void LU_SGS_BackwardKernel(const su2uint* __restrict__ level_idx, unsigned long level_begin, + unsigned long level_size, unsigned long nRows, unsigned long nVar, + DeviceLDU M, const QuantType* __restrict__ q_u, + const QuantScaleType* __restrict__ q_scale_u, const ScalarType* __restrict__ invD, + ScalarType* __restrict__ prod, bool quantized_mode) { + if (blockIdx.x >= level_size) return; + + const unsigned long iRow = level_idx[level_begin + blockIdx.x]; + const auto blockSize = nVar * nVar; + const unsigned long tid = threadIdx.x; + const auto iVar = tid / nVar, jVar = tid % nVar; + + extern __shared__ __align__(sizeof(double)) char smem[]; + auto* partial = reinterpret_cast(smem); // serves nVar * nVar threads + auto* aux = partial + blockSize; // skip nVar * nVar threads, serves nVar threads + + // Compute U.x + if (quantized_mode) { + partial[tid] = QuantizedDeviceSparseBlockMatVec(iRow, iVar, jVar, nVar, M.row_ptr_u, M.col_ind_u, q_u, q_scale_u, prod, nRows); + } else { + partial[tid] = DeviceSparseBlockMatVec(iRow, iVar, jVar, nVar, M.row_ptr_u, M.col_ind_u, M.u, prod, nRows); + } + __syncthreads(); + + + if (jVar == 0) aux[iVar] = DeviceReduceBlockRow(partial, iVar, nVar); + __syncthreads(); + + // Compute x* - D^{-1}.(U.x) + ScalarType correction = DeviceDenseBlockMatVec(invD + iRow * blockSize, aux, partial, tid, iVar, jVar, nVar); + if (jVar == 0) prod[iRow * nVar + iVar] -= correction; + +} + +/*! + * \brief Pre-calculates the inverse of the diagonal matrix D, same as for the Jacobi preconditioner + */ +template +void CSysMatrix::BuildLU_SGSPreconditionerGPU() { + SU2_ZONE_SCOPED + if (d_invM == nullptr) { + SU2_MPI::Error("CUDA LU-SGS preconditioner used without device storage.", CURRENT_FUNCTION); + } + if (nPointDomain == 0) return; + + /*--- The matrix is expected to be on the device already, it is uploaded once per solve by + * CSysMatrixVectorProduct, which is created before the preconditioner is built. ---*/ + const auto blockSize = static_cast(nVar * nVar); + InvertDiagonalBlocksKernel<<(nPointDomain), blockSize, + 2 * blockSize * sizeof(ScalarType)>>>(nPointDomain, nVar, gpu.d, d_invM); + /*--- Sync so the zone above actually times the kernel, not just the (async) launch call. ---*/ + gpuErrChk(cudaStreamSynchronize(nullptr)); + gpuErrChk(cudaGetLastError()); +} + +/*! + * \brief Compute the LU-SGS preconditioner forward pass + */ +template +void CSysMatrix::ComputeLU_SGSForwardGPU(const CSysVector& vec, + CSysVector& prod) const { + SU2_ZONE_SCOPED + + if (d_invM == nullptr) { + SU2_MPI::Error("CUDA LU-SGS preconditioner used without device storage.", CURRENT_FUNCTION); + } + if (nPointDomain == 0) return; + + const DeviceLDU M{gpu.d, gpu.l, gpu.u, gpu.row_ptr_l, + gpu.col_ind_l, gpu.row_ptr_u, gpu.col_ind_u}; + + auto* d_vec = vec.GetDevicePointer(); + auto* d_prod = prod.GetDevicePointer(); + + /*--- One thread per block entry, as done in ILU preconditioner ---*/ + const auto threads = static_cast(nVar * nVar); + const auto sharedForward = (threads + nVar) * sizeof(ScalarType); + + if (aux_stream == nullptr) gpuErrChk(cudaStreamCreate(&aux_stream)); + + /*--- First part of the symmetric iteration: (D+L).x* = b ---*/ + if (precond_fwd_graph_exec == nullptr || precond_fwd_graph_vec != d_vec || precond_fwd_graph_prod != d_prod) { + if (precond_fwd_graph_exec != nullptr) { + gpuErrChk(cudaGraphExecDestroy(precond_fwd_graph_exec)); + precond_fwd_graph_exec = nullptr; + } + + cudaGraph_t graph; + gpuErrChk(cudaStreamBeginCapture(aux_stream, cudaStreamCaptureModeThreadLocal)); + + const auto nLevels = precond_level_ptr.size() - 1; + /*--- Forward substitution: compute x* = D^{-1}.(vec - L.x*) ---*/ + for (auto level = 0ul; level < nLevels; ++level) { + const auto begin = precond_level_ptr[level]; + const auto size = precond_level_ptr[level + 1] - begin; + if (size == 0) continue; + LU_SGS_ForwardKernel<<>>(d_precond_level_idx, begin, size, nVar, M, d_q_blocks.l, d_q_scale.l, d_invM, d_vec, d_prod, quantized_mode); + } + + gpuErrChk(cudaStreamEndCapture(aux_stream, &graph)); + gpuErrChk(cudaGraphInstantiate(&precond_fwd_graph_exec, graph, nullptr, nullptr, 0)); + gpuErrChk(cudaGraphDestroy(graph)); + precond_fwd_graph_vec = d_vec; + precond_fwd_graph_prod = d_prod; + + } + + gpuErrChk(cudaGraphLaunch(precond_fwd_graph_exec, aux_stream)); + gpuErrChk(cudaStreamSynchronize(aux_stream)); + gpuErrChk(cudaGetLastError()); + +} + +/*! + * \brief Compute the LU-SGS preconditioner forward pass + */ +template +void CSysMatrix::ComputeLU_SGSBackwardGPU(CSysVector& prod) const { + SU2_ZONE_SCOPED + + if (d_invM == nullptr) { + SU2_MPI::Error("CUDA LU-SGS preconditioner used without device storage.", CURRENT_FUNCTION); + } + if (nPointDomain == 0) return; + + const DeviceLDU M{gpu.d, gpu.l, gpu.u, gpu.row_ptr_l, + gpu.col_ind_l, gpu.row_ptr_u, gpu.col_ind_u}; + + auto* d_prod = prod.GetDevicePointer(); + + /*--- One thread per block entry, as done in ILU preconditioner ---*/ + const auto threads = static_cast(nVar * nVar); + const auto sharedBackward = (threads + nVar) * sizeof(ScalarType); + + if (aux_stream == nullptr) gpuErrChk(cudaStreamCreate(&aux_stream)); + + /*--- Second part of the symmetric iteration: (D+U).x_(1) = D.x* ---*/ + if (precond_bwd_graph_exec == nullptr || precond_bwd_graph_prod != d_prod) { + if (precond_bwd_graph_exec != nullptr) { + gpuErrChk(cudaGraphExecDestroy(precond_bwd_graph_exec)); + precond_bwd_graph_exec = nullptr; + } + + cudaGraph_t graph; + gpuErrChk(cudaStreamBeginCapture(aux_stream, cudaStreamCaptureModeThreadLocal)); + + const auto nLevels = precond_level_ptr.size() - 1; + /*--- Backward substitution: compute x* = D^{-1}.(D.x* - U.x) = x* - D^{-1}.U.x ---*/ + for (auto level = nLevels; level > 0;) { + --level; + const auto begin = precond_level_ptr[level]; + const auto size = precond_level_ptr[level + 1] - begin; + if (size == 0) continue; + LU_SGS_BackwardKernel<<>>(d_precond_level_idx, begin, size, nPointDomain, nVar, M, d_q_blocks.u, d_q_scale.u, d_invM, d_prod, quantized_mode); + } + + gpuErrChk(cudaStreamEndCapture(aux_stream, &graph)); + gpuErrChk(cudaGraphInstantiate(&precond_bwd_graph_exec, graph, nullptr, nullptr, 0)); + gpuErrChk(cudaGraphDestroy(graph)); + precond_bwd_graph_prod = d_prod; + + } + + gpuErrChk(cudaGraphLaunch(precond_bwd_graph_exec, aux_stream)); + gpuErrChk(cudaStreamSynchronize(aux_stream)); + gpuErrChk(cudaGetLastError()); + +} + template void CSysMatrix::HtDTransfer(bool trigger) const { SU2_ZONE_SCOPED @@ -765,12 +1021,16 @@ template void CSysMatrix::MatrixVectorProductGPU(const CSysVector& v template void CSysMatrix::QuantizeDiagonalBlocksGPU(); \ template void CSysMatrix::BuildJacobiPreconditionerGPU(); \ template void CSysMatrix::BuildILUPreconditionerGPU(); \ +template void CSysMatrix::BuildLU_SGSPreconditionerGPU(); \ template void CSysMatrix::ComputeILUPreconditionerGPU(const CSysVector& vec, \ CSysVector& prod) const; \ template void CSysMatrix::ComputeJacobiPreconditionerGPU(const CSysVector& vec, \ CSysVector& prod, \ CGeometry* geometry, \ - const CConfig* config) const; + const CConfig* config) const;\ +template void CSysMatrix::ComputeLU_SGSForwardGPU(const CSysVector& vec, \ + CSysVector& prod) const; \ +template void CSysMatrix::ComputeLU_SGSBackwardGPU(CSysVector& prod) const; INSTANTIATE_MATRIX(su2mixedfloat) #if defined(USE_MIXED_PRECISION) && !defined(USE_SINGLE_PRECISION) From befbdf6a0b09e06ac384807e261e42592819b64b Mon Sep 17 00:00:00 2001 From: Ole Burghardt Date: Sun, 6 Sep 2026 20:16:56 +0200 Subject: [PATCH 39/61] Improvements for tape recording debug mode (e.g. zone-specific tags) (#2721) ## Proposed Changes Continuation of #2442. ## Related Work - General improvements - Adds the zone number to tags so that more mismatches/errors can occur whenever a dependency is wrong or is not behaving as expected ## PR Checklist - [x] I am submitting my contribution to the develop branch. - [x] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [x] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [x] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [ ] I have added a test case that demonstrates my contribution, if necessary. - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --- Common/include/CConfig.hpp | 8 +- Common/include/basic_types/ad_structure.hpp | 204 +++++++++++++++--- Common/include/option_structure.hpp | 6 +- Common/src/basic_types/ad_structure.cpp | 4 + .../drivers/CDiscAdjMultizoneDriver.hpp | 21 +- SU2_CFD/include/output/CFlowOutput.hpp | 8 + SU2_CFD/include/output/COutput.hpp | 20 ++ .../src/drivers/CDiscAdjMultizoneDriver.cpp | 109 +++++++--- SU2_CFD/src/interfaces/CInterface.cpp | 15 +- .../src/iteration/CDiscAdjFluidIteration.cpp | 3 +- SU2_CFD/src/output/CFlowOutput.cpp | 4 + SU2_CFD/src/output/COutput.cpp | 4 + 12 files changed, 329 insertions(+), 77 deletions(-) diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index 3544f028267d..bbbc5b8a5163 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -5641,14 +5641,14 @@ class CConfig { unsigned short GetnVar(void); /*! - * \brief Provides the number of variables. - * \return Number of variables. + * \brief Provides the total number of zones. + * \return Total number of zones. */ unsigned short GetnZone(void) const { return nZone; } /*! - * \brief Provides the number of variables. - * \return Number of variables. + * \brief Provides the zone index the configuration belongs to. + * \return Zone index. */ unsigned short GetiZone(void) const { return iZone; } diff --git a/Common/include/basic_types/ad_structure.hpp b/Common/include/basic_types/ad_structure.hpp index 2945e4f9ef84..294af302dc4d 100644 --- a/Common/include/basic_types/ad_structure.hpp +++ b/Common/include/basic_types/ad_structure.hpp @@ -1,7 +1,7 @@ /*! * \file ad_structure.hpp * \brief Main routines for the algorithmic differentiation (AD) structure. - * \author T. Albring, J. Blühdorn + * \author T. Albring, J. Blühdorn, O. Burghardt * \version 8.5.0 "Harrier" * * SU2 Project Website: https://su2code.github.io @@ -36,7 +36,21 @@ * In case there is no reverse type configured, they have no effect at all, * and so the real versions of the routined are after #else. */ + namespace AD { + +enum class TAPE_DEBUG_OPTION { + ALLOW_PREACC, + ACTIVATE_PREACC, + ALLOW_ZONE, + ALLOW_ALL_ZONES, + ACTIVATE_ALL_ZONES, + ACTIVATE_ALL_ERRORS, + MULTIZONE_TAGS, + INIT_RUN, + CHECK_RUN +}; + #ifndef CODI_REVERSE_TYPE using Identifier = int; @@ -287,42 +301,73 @@ inline void SetIndex(Identifier& index, const su2double& data) {} */ inline void SetTag(int tag) {} +/*! + * \brief Gets the current tag. + * \param[in] tag - the number to which the tag is set. + */ +inline int GetTag() { return 0; } + +/*! + * \brief Compute the zone-specific tag. + * \param[in] iZone - Zone index from which the zone-specific tag is formed. + * \return The zone-specific tag. + */ +inline int ComputeTag(unsigned short iZone) { return 0; } + /*! * \brief Sets the tag of a variable to 0. * \param[in] v - the variable whose tag is cleared. */ inline void ClearTagOnVariable(su2double& v) {} +/*! + * \brief Sets the tag of a variable to a specified value. + * \param[in] v - the variable whose tag is set manually. + */ +inline void SetTagOnVariable(su2double& v, int zone_tag = 0, int run_tag = 0) {} + /*! * \brief Struct to store information about errors during a tag debug run. */ -struct ErrorReport {}; +struct DebugControl {}; /*! - * \brief Set a reference to the output file of an ErrorReport. - * \param[in] report - the ErrorReport whose output file is set. - * \param[in] output_file - pointer to the output file. + * \brief Set a pointer to the current DebugControl. + * \param[in] control - pointer to the current debug control. */ -inline void SetDebugReportFile(ErrorReport& report, std::ostream* output_file) {} +inline void SetDebugControl(DebugControl* control) {} /*! - * \brief Set the ErrorReport to which error information from a tag debug recording is written. - * \param[in] report - the ErrorReport to which error information is written. + * \brief Set options which kind of tag mismatches are considered errors and written to file. + * \param[in] option - specification which kind of tag mismatches are considered. + * \param[in] izone - the zone w.r.t. which a tag mismatch is allowed. */ -inline void SetTagErrorCallback(ErrorReport& report) {} +inline void SetTapeDebugOption(TAPE_DEBUG_OPTION option, unsigned short izone = 0) {} /*! - * \brief Reset the error counter in an ErrorReport. - * \param[in] report - the ErrorReport whose error counter is resetted. + * \brief Activate the error callback by letting the tape call tagErrorCallback whenever a tag mismatch arises. */ -inline void ResetErrorCounter(ErrorReport& report) {} +inline void ActivateTagErrorCallback() {} /*! - * \brief Get the error count of an ErrorReport. - * \param[in] report - the ErrorReport whose pointer to its error counter is returned. + * \brief Set a pointer to the output file of a DebugControl. + * \param[in] control - the DebugControl whose output file is set. + * \param[in] output_file - pointer to the output file. + */ +inline void SetDebugReportFile(DebugControl& control, std::ostream* output_file) {} + +/*! + * \brief Reset the error counter in a DebugControl. + * \param[in] control - the DebugControl whose error counter is resetted. + */ +inline void ResetErrorCounter(DebugControl& control) {} + +/*! + * \brief Get the error count of a DebugControl. + * \param[in] control - the DebugControl whose error count is reported. * \return Value of the error counter. */ -inline unsigned long GetErrorCount(const ErrorReport& report) { return 0; } +inline unsigned long GetErrorCount(const DebugControl& control) { return 0; } /*! * \brief Pushes back the current tape position to the tape position's vector. @@ -744,37 +789,142 @@ FORCEINLINE void ResumePreaccumulation(bool wasActive) { SU2_OMP_SAFE_GLOBAL_ACCESS(PreaccEnabled = true;) } -struct ErrorReport { +#ifdef CODI_TAG_TAPE + +struct DebugControl { + int current_tag; + bool multizone_tags = false; + bool init_run = false; + bool allow_preacc = false; + bool allow_zones = false; + unsigned short allow_izone = 0; unsigned long ErrorCounter = 0; std::ostream* out = &std::cout; }; -FORCEINLINE void ResetErrorCounter(ErrorReport& report) { report.ErrorCounter = 0; } +struct AdjustDebugControl { + TAPE_DEBUG_OPTION option; + void (*adjust)(DebugControl*, unsigned short); +}; -FORCEINLINE void SetDebugReportFile(ErrorReport& report, std::ostream* output_file) { report.out = output_file; } +static const AdjustDebugControl debug_control_adjustments[] = { + {TAPE_DEBUG_OPTION::ALLOW_PREACC, [](DebugControl* c, unsigned short) { c->allow_preacc = true; }}, + {TAPE_DEBUG_OPTION::ACTIVATE_PREACC, [](DebugControl* c, unsigned short) { c->allow_preacc = false; }}, + {TAPE_DEBUG_OPTION::ALLOW_ZONE, [](DebugControl* c, unsigned short zone) { c->allow_izone = zone + 1; }}, + {TAPE_DEBUG_OPTION::ALLOW_ALL_ZONES, [](DebugControl* c, unsigned short) { c->allow_zones = true; }}, + {TAPE_DEBUG_OPTION::ACTIVATE_ALL_ZONES, + [](DebugControl* c, unsigned short) { + c->allow_zones = false; + c->allow_izone = 0; + }}, + {TAPE_DEBUG_OPTION::ACTIVATE_ALL_ERRORS, + [](DebugControl* c, unsigned short) { + c->allow_preacc = false; + c->allow_zones = false; + c->allow_izone = 0; + }}, + {TAPE_DEBUG_OPTION::INIT_RUN, [](DebugControl* c, unsigned short) { c->init_run = true; }}, + {TAPE_DEBUG_OPTION::CHECK_RUN, [](DebugControl* c, unsigned short) { c->init_run = false; }}, + {TAPE_DEBUG_OPTION::MULTIZONE_TAGS, [](DebugControl* c, unsigned short) { c->multizone_tags = true; }}, +}; -FORCEINLINE unsigned long GetErrorCount(const ErrorReport& report) { return report.ErrorCounter; } +FORCEINLINE void ResetErrorCounter(DebugControl& control) { control.ErrorCounter = 0; } -#ifdef CODI_TAG_TAPE +FORCEINLINE void SetDebugReportFile(DebugControl& control, std::ostream* output_file) { control.out = output_file; } + +FORCEINLINE unsigned long GetErrorCount(const DebugControl& control) { return control.ErrorCounter; } + +extern DebugControl* current_control; + +FORCEINLINE void SetDebugControl(DebugControl* control) { current_control = control; } + +FORCEINLINE void SetTag(int tag) { + current_control->current_tag = tag; + AD::getTape().setCurTag(tag); +} + +FORCEINLINE int GetTag() { return current_control->current_tag; } + +FORCEINLINE int ComputeTag(unsigned short iZone) { + if (current_control->init_run) { + return (current_control->multizone_tags) ? ((int)iZone + 1) * 10 + 1 : 1; + } else { + return (current_control->multizone_tags) ? ((int)iZone + 1) * 10 + 2 : 2; + } +} -FORCEINLINE void SetTag(int tag) { AD::getTape().setCurTag(tag); } FORCEINLINE void ClearTagOnVariable(su2double& v) { AD::getTape().clearTagOnVariable(v); } +FORCEINLINE void SetTagOnVariable(su2double& v, int zone_tag = 0, int run_tag = 0) { + int tag = v.getIdentifier().tag; + int tens = (zone_tag > 0) ? zone_tag + 1 : tag / 10; + int ones = (run_tag > 0 && run_tag < 10) ? run_tag : tag % 10; + if (tag != 0) { + v.getIdentifier().tag = tens * 10 + ones; + } +} + static void tagErrorCallback(const int& correctTag, const int& wrongTag, void* userData) { - auto* report = static_cast(userData); + auto* status = static_cast(userData); + + bool throw_mismatch_error = true; - report->ErrorCounter += 1; - *(report->out) << "Use of variable with bad tag '" << wrongTag << "', should be '" << correctTag << "'." << std::endl; + /*--- The callback could be due to a preaccumulation tag mismatch that we maybe want to allow, ... ---*/ + if (status->allow_preacc) { + if (correctTag == 1337 || wrongTag == 1337) { + throw_mismatch_error = false; + } + } + + /*--- ... or to a mismatch in the zone part of the tag. ---*/ + if (status->allow_zones) { + throw_mismatch_error = false; + } else if (status->allow_izone > 0) { + if (wrongTag / 10 == status->allow_izone) { + throw_mismatch_error = false; + } + } + + if (throw_mismatch_error) { + status->ErrorCounter += 1; + *(status->out) << "Use of variable with bad tag '" << std::setw(2) << std::setfill('0') << wrongTag + << "', should be '" << std::setw(2) << std::setfill('0') << correctTag << "'." << std::endl; + } } -FORCEINLINE void SetTagErrorCallback(ErrorReport& report) { - AD::getTape().setTagErrorCallback(tagErrorCallback, &report); +FORCEINLINE void SetTapeDebugOption(TAPE_DEBUG_OPTION option, unsigned short izone = 0) { + if (current_control == nullptr) { + return; + } + for (const AdjustDebugControl& entry : debug_control_adjustments) { + if (entry.option == option) { + entry.adjust(current_control, izone); + break; + } + } +} + +FORCEINLINE void ActivateTagErrorCallback() { + if (current_control != NULL) { + AD::getTape().setTagErrorCallback(tagErrorCallback, current_control); + } else { + std::cout << "No tape debug control set!" << std::endl; + } } #else +struct DebugControl {}; +FORCEINLINE void ResetErrorCounter(DebugControl& control) {} +FORCEINLINE void SetDebugReportFile(DebugControl& control, std::ostream* output_file) {} +FORCEINLINE unsigned long GetErrorCount(const DebugControl& control) { return 0; } +FORCEINLINE void SetDebugControl(DebugControl* status) {} +FORCEINLINE int GetTag() { return 0; } FORCEINLINE void SetTag(int tag) {} +FORCEINLINE int ComputeTag(unsigned short iZone) { return 0; } FORCEINLINE void ClearTagOnVariable(su2double& v) {} -FORCEINLINE void SetTagErrorCallback(ErrorReport report) {} +FORCEINLINE void SetTagOnVariable(su2double& v, int zone_tag = 0, int run_tag = 0) {} +FORCEINLINE void SetTapeDebugOption(TAPE_DEBUG_OPTION option, unsigned short izone = 0) {} +FORCEINLINE void ActivateTagErrorCallback() {} #endif // CODI_TAG_TAPE diff --git a/Common/include/option_structure.hpp b/Common/include/option_structure.hpp index d4d0e7f0fa0a..3d5d57c17672 100644 --- a/Common/include/option_structure.hpp +++ b/Common/include/option_structure.hpp @@ -2701,11 +2701,7 @@ enum class RECORDING { SOLUTION_VARIABLES, MESH_COORDS, MESH_DEFORM, - SOLUTION_AND_MESH, - TAG_INIT_SOLVER_VARIABLES, - TAG_CHECK_SOLVER_VARIABLES, - TAG_INIT_SOLVER_AND_MESH, - TAG_CHECK_SOLVER_AND_MESH + SOLUTION_AND_MESH }; /*! diff --git a/Common/src/basic_types/ad_structure.cpp b/Common/src/basic_types/ad_structure.cpp index d22ea67f373b..2e1224847976 100644 --- a/Common/src/basic_types/ad_structure.cpp +++ b/Common/src/basic_types/ad_structure.cpp @@ -48,6 +48,10 @@ SU2_OMP(threadprivate(PreaccHelper)) ExtFuncHelper FuncHelper; +#ifdef CODI_TAG_TAPE +DebugControl* current_control = NULL; +#endif // CODI_TAG_TAPE + #endif void Initialize() { diff --git a/SU2_CFD/include/drivers/CDiscAdjMultizoneDriver.hpp b/SU2_CFD/include/drivers/CDiscAdjMultizoneDriver.hpp index 5fe657d48f5f..ac3bc15e713f 100644 --- a/SU2_CFD/include/drivers/CDiscAdjMultizoneDriver.hpp +++ b/SU2_CFD/include/drivers/CDiscAdjMultizoneDriver.hpp @@ -117,16 +117,6 @@ class CDiscAdjMultizoneDriver : public CMultizoneDriver { */ void StartSolver() override; - /*! - * \brief [Overload] Launch the tape test mode for the discrete adjoint multizone solver. - */ - void TapeTest (); - - /*! - * \brief [Overload] Get error numbers after a tape test run of the discrete adjoint multizone solver. - */ - int TapeTestGatherErrors(AD::ErrorReport& error_report) const; - /*! * \brief Preprocess the multizone iteration */ @@ -279,4 +269,15 @@ class CDiscAdjMultizoneDriver : public CMultizoneDriver { } } + /*! + * \brief Launch the tape test run of the discrete adjoint multizone solver. + */ + void TapeTest (); + + /*! + * \brief Get the total error count after a tape test run of the discrete adjoint multizone solver. + * \param[in] debug_control - DebugControl from which this rank's contribution to the total error count is read. + * \return The total error count across all ranks. + */ + int TapeTestGatherErrors(AD::DebugControl& debug_control) const; }; diff --git a/SU2_CFD/include/output/CFlowOutput.hpp b/SU2_CFD/include/output/CFlowOutput.hpp index cf4b2c9f6aa6..7e6264f18476 100644 --- a/SU2_CFD/include/output/CFlowOutput.hpp +++ b/SU2_CFD/include/output/CFlowOutput.hpp @@ -116,6 +116,14 @@ class CFlowOutput : public CFVMOutput{ */ void LoadHistoryDataScalar(const CConfig* config, const CSolver* const* solver); + /*! + * \brief Recompute history output field values that can be used as objective functions in the (multiphysics) discrete adjoint solver. + * \param[in] config - Definition of the particular problem. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver - The container holding all solution data. + */ + void LoadCustomAndComboObjectiveFunctions(CConfig *config, CGeometry *geometry, CSolver **solver) override; + /*! * \brief Add scalar (turbulence/species) volume solution fields for a point (FVMComp, FVMInc, FVMNEMO). * \note The order of fields in restart files is fixed. Therefore the split-up. diff --git a/SU2_CFD/include/output/COutput.hpp b/SU2_CFD/include/output/COutput.hpp index a1fea3fdf46c..ea47413ae8ac 100644 --- a/SU2_CFD/include/output/COutput.hpp +++ b/SU2_CFD/include/output/COutput.hpp @@ -427,6 +427,15 @@ class COutput { void SetMultizoneHistoryOutput(COutput** output, CConfig **config, CConfig *driver_config, unsigned long TimeIter, unsigned long OuterIter); + /*! + * \brief Evaluates objective functions in the (multiphysics) discrete adjoint solver. + * \note Uses the same subroutines for objective function evaluation as SetHistoryOutput, but omits unnecessary evaluations (e.g. residuals, convergence data) to avoid AD complications. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container vector with all the solutions. + * \param[in] config - Definition of the particular problem. + */ + void SetObjectiveFunctionValues(CGeometry *geometry, CSolver **solver_container, CConfig *config); + /*! * \brief Sets the volume output filename * \param[in] filename - the new filename @@ -996,6 +1005,17 @@ class COutput { */ inline virtual void LoadHistoryData(CConfig *config, CGeometry *geometry, CSolver **solver) {} + /*! + * \brief Recompute history output field values that can be used as objective functions in the (multiphysics) discrete adjoint solver. + * \param[in] config - Definition of the particular problem. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver - The container holding all solution data. + */ + inline virtual void LoadCustomAndComboObjectiveFunctions(CConfig *config, CGeometry *geometry, CSolver **solver) { + /*--- Unless LoadCustomAndComboObjectiveFunctions is implemented in a derived output class, we use LoadHistoryData (not ideal for AD). ---*/ + LoadHistoryData(config, geometry, solver); + } + /*! * \brief Load the multizone history output field values * \param[in] output - Container holding the output instances per zone. diff --git a/SU2_CFD/src/drivers/CDiscAdjMultizoneDriver.cpp b/SU2_CFD/src/drivers/CDiscAdjMultizoneDriver.cpp index 08c9fce72ba9..47ed928cb876 100644 --- a/SU2_CFD/src/drivers/CDiscAdjMultizoneDriver.cpp +++ b/SU2_CFD/src/drivers/CDiscAdjMultizoneDriver.cpp @@ -269,22 +269,48 @@ void CDiscAdjMultizoneDriver::StartSolver() { void CDiscAdjMultizoneDriver::TapeTest() { SU2_ZONE_SCOPED + if (nZone > 100) { + SU2_MPI::Error("The tape debug mode tag system is limited to a maximum zone number of 100.", CURRENT_FUNCTION); + } + if (rank == MASTER_NODE) { cout <<"\n---------------------------- Start Debug Run ----------------------------" << endl; } int total_errors = 0; - AD::ErrorReport error_report; - AD::SetTagErrorCallback(error_report); - std::ofstream out1("run1_process" + to_string(rank) + ".out"); - std::ofstream out2("run2_process" + to_string(rank) + ".out"); - AD::ResetErrorCounter(error_report); - AD::SetDebugReportFile(error_report, &out1); + /*--- Errors are reported to an instance of AD::DebugControl that holds an error counter, a pointer to + * an error log file and configurations determined by the TAPE_DEBUG_OPTION settings. ---*/ + AD::DebugControl debug_control; + + /*--- Set a pointer to the current status internally in the AD structure. ---*/ + AD::SetDebugControl(&debug_control); + + /*--- Set the callback function that handles the event of a tag mismatch on the tape. ---*/ + AD::ActivateTagErrorCallback(); + + /*--- For multizone cases (nZone > 1), we use zone-specific tags. ---*/ + if(nZone > 1) { AD::SetTapeDebugOption(AD::TAPE_DEBUG_OPTION::MULTIZONE_TAGS); } + + /*--- Set the default tag mismatch callback (consider every mismatch an error). ---*/ + AD::SetTapeDebugOption(AD::TAPE_DEBUG_OPTION::ACTIVATE_ALL_ERRORS); - /*--- This recording will assign the initial (same) tag to each registered variable. + // Make this a config option? + // AD::SetTapeDebugOption(AD::TAPE_DEBUG_OPTION::ALLOW_ALL_ZONES); + + /*--- Reset the error counter and set the error log file (each process writes its own). ---*/ + AD::ResetErrorCounter(debug_control); + std::ofstream out("debug_run_process" + to_string(rank) + ".out"); + AD::SetDebugReportFile(debug_control, &out); + + /*--- This recording will assign an initial, zone-specific tag to each registered variable. * During the recording, each dependent variable will be assigned the same tag. ---*/ - AD::SetTag(1); + + out << "-----------------------------------------------------------------------------------------" << std::endl; + out << "INITIAL recording." << std::endl; + out << "Errors appearing in this recording are most likely preaccumulation errors (preaccumulation tag: 1337).\n" << std::endl; + + AD::SetTapeDebugOption(AD::TAPE_DEBUG_OPTION::INIT_RUN); if(driver_config->GetAD_CheckTapeType() == CHECK_TAPE_TYPE::OBJECTIVE_FUNCTION) { if(driver_config->GetAD_CheckTapeVariables() == CHECK_TAPE_VARIABLES::MESH_COORDINATES) { @@ -292,7 +318,7 @@ void CDiscAdjMultizoneDriver::TapeTest() { SetRecording(RECORDING::MESH_COORDS, Kind_Tape::OBJECTIVE_FUNCTION_TAPE, ZONE_0); } else { - if (rank == MASTER_NODE) cout << "\nChecking OBJECTIVE_FUNCTION_TAPE for SOLUTION_VARIABLES." << endl; + if (rank == MASTER_NODE) cout << "\nChecking OBJECTIVE_FUNCTION_TAPE for SOLVER_VARIABLES." << endl; SetRecording(RECORDING::SOLUTION_VARIABLES, Kind_Tape::OBJECTIVE_FUNCTION_TAPE, ZONE_0); } } @@ -302,21 +328,29 @@ void CDiscAdjMultizoneDriver::TapeTest() { SetRecording(RECORDING::MESH_COORDS, Kind_Tape::FULL_SOLVER_TAPE, ZONE_0); } else { - if (rank == MASTER_NODE) cout << "\nChecking FULL_SOLVER_TAPE for SOLUTION_VARIABLES." << endl; + if (rank == MASTER_NODE) cout << "\nChecking FULL_SOLVER_TAPE for SOLVER_VARIABLES." << endl; SetRecording(RECORDING::SOLUTION_VARIABLES, Kind_Tape::FULL_SOLVER_TAPE, ZONE_0); } } - total_errors = TapeTestGatherErrors(error_report); - AD::ResetErrorCounter(error_report); - AD::SetDebugReportFile(error_report, &out2); + /*--- Gather errors from all ranks from the initial recording (e.g. preaccumulation errors). ---*/ + total_errors = TapeTestGatherErrors(debug_control); + AD::ResetErrorCounter(debug_control); /*--- This recording repeats the initial recording with a different tag. * If a variable was used before it became dependent on the inputs, this variable will still carry the tag * from the initial recording and a mismatch with the "check" recording tag will throw an error. * In such a case, a possible reason could be that such a variable is set by a post-processing routine while * for a mathematically correct recording this dependency must be included earlier. ---*/ - AD::SetTag(2); + + out << "-------------------------------------------------------------------------------------------------" << std::endl; + out << "IZONE = " << iZone << ", SECOND recording." << std::endl; + out << "Errors appearing hereafter are most likely mathematical errors (e.g. check for circular dependencies)." << std::endl; + + AD::SetTapeDebugOption(AD::TAPE_DEBUG_OPTION::CHECK_RUN); + + /*--- We ignore preaccumulation mismatches during the second recording as they have already been reported. ---*/ + AD::SetTapeDebugOption(AD::TAPE_DEBUG_OPTION::ALLOW_PREACC); if(driver_config->GetAD_CheckTapeType() == CHECK_TAPE_TYPE::OBJECTIVE_FUNCTION) { if(driver_config->GetAD_CheckTapeVariables() == CHECK_TAPE_VARIABLES::MESH_COORDINATES) @@ -330,7 +364,8 @@ void CDiscAdjMultizoneDriver::TapeTest() { else SetRecording(RECORDING::SOLUTION_VARIABLES, Kind_Tape::FULL_SOLVER_TAPE, ZONE_0); } - total_errors += TapeTestGatherErrors(error_report); + total_errors += TapeTestGatherErrors(debug_control); + if (rank == MASTER_NODE) { cout << "\n------------------------- Tape Test Run Summary -------------------------" << endl; @@ -339,10 +374,10 @@ void CDiscAdjMultizoneDriver::TapeTest() { } } -int CDiscAdjMultizoneDriver::TapeTestGatherErrors(AD::ErrorReport& error_report) const { +int CDiscAdjMultizoneDriver::TapeTestGatherErrors(AD::DebugControl& debug_control) const { SU2_ZONE_SCOPED - int num_errors = AD::GetErrorCount(error_report); + int num_errors = AD::GetErrorCount(debug_control); int total_errors = 0; std::vector process_error(size); SU2_MPI::Allreduce(&num_errors, &total_errors, 1, MPI_INT, MPI_SUM, SU2_MPI::GetComm()); @@ -757,10 +792,6 @@ void CDiscAdjMultizoneDriver::SetRecording(RECORDING kind_recording, Kind_Tape t case RECORDING::SOLUTION_VARIABLES: cout << "Storing computational graph wrt CONSERVATIVE VARIABLES.\n"; cout << "Computing residuals to check the convergence of the direct problem." << endl; break; - case RECORDING::TAG_INIT_SOLVER_VARIABLES: cout << "Simulating recording with tag 1 on conservative variables." << endl; AD::SetTag(1); break; - case RECORDING::TAG_CHECK_SOLVER_VARIABLES: cout << "Checking first recording with tag 2 on conservative variables." << endl; AD::SetTag(2); break; - case RECORDING::TAG_INIT_SOLVER_AND_MESH: cout << "Simulating recording with tag 1 on conservative variables and mesh coordinates." << endl; AD::SetTag(1); break; - case RECORDING::TAG_CHECK_SOLVER_AND_MESH: cout << "Checking first recording with tag 2 on conservative variables and mesh coordinates." << endl; AD::SetTag(2); break; default: break; } } @@ -785,6 +816,12 @@ void CDiscAdjMultizoneDriver::SetRecording(RECORDING kind_recording, Kind_Tape t type_recording = RECORDING::MESH_DEFORM; } + /*--- If we are in tape debug mode, set a global (but zone-specific) tag. + * Every variable that we register below will be initialized with this tag. ---*/ + int tag = AD::ComputeTag(iZone); + AD::SetTag(tag); + if(tag != 0) { cout << " - register input variables with tag " << AD::GetTag() << " on zone " << iZone << "." << endl; } + iteration_container[iZone][INST_0]->RegisterInput(solver_container, geometry_container, config_container, iZone, INST_0, type_recording); } @@ -793,6 +830,10 @@ void CDiscAdjMultizoneDriver::SetRecording(RECORDING kind_recording, Kind_Tape t AD::Push_TapePosition(); /// REGISTERED for (iZone = 0; iZone < nZone; iZone++) { + + /*--- If in tape debug mode, set the zone-specific tag for computations in this zone. ---*/ + AD::SetTag(AD::ComputeTag(iZone)); + iteration_container[iZone][INST_0]->SetDependencies(solver_container, geometry_container, numerics_container, config_container, iZone, INST_0, kind_recording); } @@ -806,6 +847,10 @@ void CDiscAdjMultizoneDriver::SetRecording(RECORDING kind_recording, Kind_Tape t if ((tape_type == Kind_Tape::OBJECTIVE_FUNCTION_TAPE) || (kind_recording == RECORDING::MESH_COORDS)) { HandleDataTransfer(); for (iZone = 0; iZone < nZone; iZone++) { + + /*--- If in tape debug mode, set the zone-specific test tag for computations in this zone. ---*/ + AD::SetTag(AD::ComputeTag(iZone)); + if (Has_Deformation(iZone)) { iteration_container[iZone][INST_0]->SetDependencies(solver_container, geometry_container, numerics_container, config_container, iZone, INST_0, kind_recording); @@ -832,6 +877,11 @@ void CDiscAdjMultizoneDriver::SetRecording(RECORDING kind_recording, Kind_Tape t AD::Push_TapePosition(); /// enter_zone + /*--- If in tape debug mode, set the zone-specific test tag for computations in this zone. ---*/ + int tag = AD::ComputeTag(iZone); + AD::SetTag(tag); + if(tag != 0) { cout << " - check solver of zone " << iZone << " against tag " << tag << endl; } + DirectIteration(iZone, kind_recording); iteration_container[iZone][INST_0]->RegisterOutput(solver_container, geometry_container, @@ -897,13 +947,13 @@ void CDiscAdjMultizoneDriver::SetObjFunction(RECORDING kind_recording) { solvers[FLOW_SOL]->ComputeTurboBladePerformance(geometry, config, iZone); } - direct_output[iZone]->SetHistoryOutput(geometry, solvers, config); + direct_output[iZone]->SetObjectiveFunctionValues(geometry, solvers, config); ObjFunc += solvers[FLOW_SOL]->GetTotal_ComboObj(); break; case MAIN_SOLVER::DISC_ADJ_HEAT: solvers[HEAT_SOL]->Heat_Fluxes(geometry, solvers, config); - direct_output[iZone]->SetHistoryOutput(geometry, solvers, config); + direct_output[iZone]->SetObjectiveFunctionValues(geometry, solvers, config); ObjFunc += solvers[HEAT_SOL]->GetTotal_ComboObj(); break; @@ -912,7 +962,7 @@ void CDiscAdjMultizoneDriver::SetObjFunction(RECORDING kind_recording) { solvers[HEAT_SOL]->Heat_Fluxes(geometry, solvers, config); } solvers[FEA_SOL]->Postprocessing(geometry, config, numerics_container[iZone][INST_0][MESH_0][FEA_SOL], true); - direct_output[iZone]->SetHistoryOutput(geometry, solvers, config); + direct_output[iZone]->SetObjectiveFunctionValues(geometry, solvers, config); ObjFunc += solvers[FEA_SOL]->GetTotal_ComboObj(); break; @@ -924,11 +974,7 @@ void CDiscAdjMultizoneDriver::SetObjFunction(RECORDING kind_recording) { if (rank == MASTER_NODE) { AD::RegisterOutput(ObjFunc); AD::SetIndex(ObjFunc_Index, ObjFunc); - if (kind_recording == RECORDING::SOLUTION_VARIABLES || - kind_recording == RECORDING::TAG_INIT_SOLVER_VARIABLES || - kind_recording == RECORDING::TAG_CHECK_SOLVER_VARIABLES || - kind_recording == RECORDING::TAG_INIT_SOLVER_AND_MESH || - kind_recording == RECORDING::TAG_CHECK_SOLVER_AND_MESH) { + if (kind_recording == RECORDING::SOLUTION_VARIABLES) { cout << "Objective function value: " << std::setprecision(driver_config->GetOutput_Precision()) << ObjFunc << endl; } } @@ -1035,10 +1081,15 @@ void CDiscAdjMultizoneDriver::HandleDataTransfer() { /*--- In principle, the mesh does not need to be updated ---*/ bool DeformMesh = false; + int tag = AD::ComputeTag(iZone); + AD::SetTag(tag); + if(tag != 0) { cout << " - check data transfer of variables into zone " << iZone << " against its (correct) tag " << tag << endl; } + /*--- Transfer from all the remaining zones ---*/ for (unsigned short jZone = 0; jZone < nZone; jZone++){ /*--- The target zone is iZone ---*/ if (jZone != iZone && interface_container[jZone][iZone] != nullptr) { + if(tag != 0) { cout << " - From zone " << jZone << " into zone " << iZone << endl;} DeformMesh |= TransferData(jZone, iZone); } } diff --git a/SU2_CFD/src/interfaces/CInterface.cpp b/SU2_CFD/src/interfaces/CInterface.cpp index ce189fd15dc6..6a2552d45f7a 100644 --- a/SU2_CFD/src/interfaces/CInterface.cpp +++ b/SU2_CFD/src/interfaces/CInterface.cpp @@ -121,8 +121,21 @@ void CInterface::BroadcastData(const CInterpolator& interpolator, /*--- If this processor owns the node. ---*/ if (donor_geometry->nodes->GetDomain(iPoint)) { + /*--- Read variables from donor solver. + * If in AD test recording mode, keep the current tag, but allow the donor tag while loading the donor variable into Donor_Variable. ---*/ + AD::SetTapeDebugOption(AD::TAPE_DEBUG_OPTION::ALLOW_ZONE, donor_config->GetiZone()); + GetDonor_Variable(donor_solution, donor_geometry, donor_config, markDonor, iVertex, iPoint); - for (auto iVar = 0u; iVar < nVar; iVar++) sendDonorVar(iSend, iVar) = Donor_Variable[iVar]; + + /*--- If in AD test recording mode, we manually adapt the tag to the target (this) zone and return to strict tag mismatch handling. ---*/ + for (auto iVar = 0u; iVar < nVar; iVar++) { + AD::SetTagOnVariable(Donor_Variable[iVar], target_config->GetiZone()); + } + AD::SetTapeDebugOption(AD::TAPE_DEBUG_OPTION::ACTIVATE_ALL_ZONES); + + for (auto iVar = 0u; iVar < nVar; iVar++) { + sendDonorVar(iSend, iVar) = Donor_Variable[iVar]; + } sendDonorIdx[iSend] = donor_geometry->nodes->GetGlobalIndex(iPoint); ++iSend; diff --git a/SU2_CFD/src/iteration/CDiscAdjFluidIteration.cpp b/SU2_CFD/src/iteration/CDiscAdjFluidIteration.cpp index a3bd1d0616e0..b1c1fff09371 100644 --- a/SU2_CFD/src/iteration/CDiscAdjFluidIteration.cpp +++ b/SU2_CFD/src/iteration/CDiscAdjFluidIteration.cpp @@ -410,7 +410,8 @@ void CDiscAdjFluidIteration::RegisterInput(CSolver***** solver, CGeometry**** ge SU2_OMP_PARALLEL_(if(solvers0[ADJFLOW_SOL]->GetHasHybridParallel())) { bool AD_debug_mesh_coordinates = false; - if (config[iZone]->GetAD_CheckTapeVariables() == CHECK_TAPE_VARIABLES::MESH_COORDINATES) { + if(config[iZone]->GetDiscrete_Adjoint_Debug() && + config[iZone]->GetAD_CheckTapeVariables() == CHECK_TAPE_VARIABLES::MESH_COORDINATES) { cout << "Register additional SOLUTION VARIABLES for tag debug mode (zone " << iZone << ")." << endl; AD_debug_mesh_coordinates = true; } diff --git a/SU2_CFD/src/output/CFlowOutput.cpp b/SU2_CFD/src/output/CFlowOutput.cpp index 06f0534e11af..6abf8c9b5a93 100644 --- a/SU2_CFD/src/output/CFlowOutput.cpp +++ b/SU2_CFD/src/output/CFlowOutput.cpp @@ -1405,6 +1405,10 @@ void CFlowOutput::SetVolumeOutputFieldsScalarSolution(const CConfig* config){ } } +void CFlowOutput::LoadCustomAndComboObjectiveFunctions(CConfig *config, CGeometry *geometry, CSolver **solver) { + LoadHistoryData(config, geometry, solver); +} + void CFlowOutput::SetVolumeOutputFieldsScalarResidual(const CConfig* config) { /*--- Only place outputs of the "RESIDUAL" group here. ---*/ diff --git a/SU2_CFD/src/output/COutput.cpp b/SU2_CFD/src/output/COutput.cpp index 07c053d629ae..06c4272f9caa 100644 --- a/SU2_CFD/src/output/COutput.cpp +++ b/SU2_CFD/src/output/COutput.cpp @@ -231,6 +231,10 @@ void COutput::SetHistoryOutput(CGeometry *geometry, } +void COutput::SetObjectiveFunctionValues(CGeometry *geometry, CSolver **solver_container, CConfig *config) { + LoadCustomAndComboObjectiveFunctions(config, geometry, solver_container); +} + void COutput::SetHistoryOutput(CGeometry ****geometry, CSolver *****solver, CConfig **config, std::shared_ptr(TurboStagePerf), su2vector> TurboBladePerfs, unsigned short val_iZone, unsigned long TimeIter, unsigned long OuterIter, unsigned long InnerIter, unsigned short val_iInst){ unsigned long Iter= InnerIter; From 5ae555fe467e5733a0a605071588c08a5dce5bf9 Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Sun, 6 Sep 2026 21:34:55 -0700 Subject: [PATCH 40/61] Fix MUSCL_TURB with convective schemes that do not store density gradients (#2886) ## Proposed Changes Fall back to no reconstruction for variables that are not stored. ## PR Checklist - [X] I am submitting my contribution to the develop branch. - [X] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [X] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [X] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [X] I have added a test case that demonstrates my contribution, if necessary. - [X] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --- SU2_CFD/include/solvers/CScalarSolver.inl | 12 ++++++++++-- TestCases/TestCase.py | 11 ++++++++++- TestCases/serial_regression.py | 13 +++++++++++++ TestCases/tutorials.py | 2 +- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/SU2_CFD/include/solvers/CScalarSolver.inl b/SU2_CFD/include/solvers/CScalarSolver.inl index 2a20484449bb..3e3a8461d7a1 100644 --- a/SU2_CFD/include/solvers/CScalarSolver.inl +++ b/SU2_CFD/include/solvers/CScalarSolver.inl @@ -226,6 +226,14 @@ void CScalarSolver::Upwind_Residual(CGeometry* geometry, CSolver** Limiter_j = flowNodes->GetLimiter_Primitive(jPoint); } + /*--- Some upwind schemes (see EulerNPrimVarGrad) size the flow's gradient/limiter + * columns smaller than the full primitive count, e.g. excluding density. Fall back + * to the cell-centered primitives for those. ---*/ + for (auto iVar = 0u; iVar < solver_container[FLOW_SOL]->GetnPrimVar(); iVar++) { + flowPrimVar_i[iVar] = V_i[iVar]; + flowPrimVar_j[iVar] = V_j[iVar]; + } + for (auto iVar = 0u; iVar < solver_container[FLOW_SOL]->GetnPrimVarGrad(); iVar++) { const su2double V_ij = V_j[iVar] - V_i[iVar]; @@ -237,8 +245,8 @@ void CScalarSolver::Upwind_Residual(CGeometry* geometry, CSolver** Project_Grad_j *= Limiter_j[iVar]; } - flowPrimVar_i[iVar] = V_i[iVar] + 0.5 * Project_Grad_i; - flowPrimVar_j[iVar] = V_j[iVar] - 0.5 * Project_Grad_j; + flowPrimVar_i[iVar] += 0.5 * Project_Grad_i; + flowPrimVar_j[iVar] -= 0.5 * Project_Grad_j; } numerics->SetPrimitive(flowPrimVar_i, flowPrimVar_j); diff --git a/TestCases/TestCase.py b/TestCases/TestCase.py index df517dff31dc..30b63a6909f8 100644 --- a/TestCases/TestCase.py +++ b/TestCases/TestCase.py @@ -121,6 +121,8 @@ def __init__(self,tag_in): self.enabled_on_cpu_arch = ["x86_64","amd64","aarch64","arm64"] self.enabled_with_tsan = True self.enabled_with_asan = True + self.enabled_with_regular = True # Set to False for a case that should only run under a sanitizer, + # e.g. one added purely to exercise a sanitizer-only finding. self.command = self.Command() self.timeout = 0 self.tol = 0.0 @@ -1035,6 +1037,10 @@ def is_enabled(self, with_tsan=False, with_asan=False, with_tapetests=False): tsan_compatible = not with_tsan or self.enabled_with_tsan asan_compatible = not with_asan or self.enabled_with_asan tapetests_compatible = not with_tapetests or self.enabled_with_tapetests + # A case marked enabled_with_regular = False only runs under a sanitizer/tapetests mode, + # e.g. one added purely to exercise a finding that only a sanitizer catches. + regular_run = not (with_tsan or with_asan or with_tapetests) + regular_compatible = not regular_run or self.enabled_with_regular if not tsan_compatible: print('Ignoring test "%s" because it is not enabled to run with the thread sanitizer.' % self.tag) @@ -1042,7 +1048,10 @@ def is_enabled(self, with_tsan=False, with_asan=False, with_tapetests=False): if not tapetests_compatible: print('Ignoring test "%s" because it is not enabled to run a test of the tape.' % self.tag) - return is_enabled_on_arch and tsan_compatible and asan_compatible and tapetests_compatible and tapetests_compatible + if not regular_compatible: + print('Ignoring test "%s" because it is only enabled to run under a sanitizer.' % self.tag) + + return is_enabled_on_arch and tsan_compatible and asan_compatible and tapetests_compatible and regular_compatible def adjust_test_data(self): diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index aa22e30d8ae9..331fe709f0ec 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -308,6 +308,19 @@ def main(): turb_naca0012_sst.timeout = 3200 test_list.append(turb_naca0012_sst) + # E387 transitional SST+LM tutorial config, re-run here as a sanitizer-only probe. + # Covers the density gradient not being available for MUSCL_TURB=YES with a flow scheme + # that does not store that gradient. + tutorial_trans_e387_sst_asan = TestCase('tutorial_trans_e387_sst_asan') + tutorial_trans_e387_sst_asan.cfg_dir = "../Tutorials/compressible_flow/Transitional_Airfoil/Langtry_and_Menter/E387" + tutorial_trans_e387_sst_asan.cfg_file = "transitional_SST_LM_model_ConfigFile.cfg" + tutorial_trans_e387_sst_asan.test_iter = 2 + tutorial_trans_e387_sst_asan.test_vals = [-6.418119, -4.827573, -2.220229, 3.029787, 3.123846, 5.000000, -5.610239] + tutorial_trans_e387_sst_asan.timeout = 1600 + tutorial_trans_e387_sst_asan.no_restart = True + tutorial_trans_e387_sst_asan.enabled_with_regular = False + test_list.append(tutorial_trans_e387_sst_asan) + # NACA0012 (SST V2003m, FUN3D results for finest grid: CL=1.0840, CD=0.01253) turb_naca0012_sst_2003m = TestCase('turb_naca0012_sst_2003m') turb_naca0012_sst_2003m.cfg_dir = "rans/naca0012" diff --git a/TestCases/tutorials.py b/TestCases/tutorials.py index aefbc66f815d..9ab5ef5a6a71 100644 --- a/TestCases/tutorials.py +++ b/TestCases/tutorials.py @@ -266,7 +266,7 @@ def main(): tutorial_trans_e387_sst.cfg_dir = "../Tutorials/compressible_flow/Transitional_Airfoil/Langtry_and_Menter/E387" tutorial_trans_e387_sst.cfg_file = "transitional_SST_LM_model_ConfigFile.cfg" tutorial_trans_e387_sst.test_iter = 20 - tutorial_trans_e387_sst.test_vals = [-6.532415, -2.932984, 0.401484, 1.078294, 0.188167, 2.000000, -10.005786] + tutorial_trans_e387_sst.test_vals = [-6.532415, -5.082018, -0.789469, 1.078293, 0.188166, 2.000000, -9.567997] tutorial_trans_e387_sst.no_restart = True test_list.append(tutorial_trans_e387_sst) From 1344619dee61631d0ff97e3bc5698eba9f9cfe3d Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Mon, 7 Sep 2026 09:01:09 -0700 Subject: [PATCH 41/61] Error on MPI+CUDA, compatibility with Newton-Krylov, fix OpenMP deadlock in Newton-Krylov (#2887) ## Proposed Changes All in the title. ## PR Checklist - [X] I am submitting my contribution to the develop branch. - [X] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [X] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [X] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [X] I have added a test case that demonstrates my contribution, if necessary. - [X] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --------- Co-authored-by: Claude Opus 5 --- .../linear_algebra/CPreconditioner.hpp | 27 ++++ Common/include/linear_algebra/CSysMatrix.hpp | 26 ++-- Common/src/CConfig.cpp | 25 ++++ Common/src/linear_algebra/CSysMatrix.cpp | 9 +- Common/src/linear_algebra/CSysMatrixGPU.cu | 119 ++++++++++++------ .../integration/CNewtonIntegration.hpp | 12 ++ .../src/integration/CNewtonIntegration.cpp | 22 ++-- meson.build | 33 ++++- meson_options.txt | 1 + 9 files changed, 213 insertions(+), 61 deletions(-) diff --git a/Common/include/linear_algebra/CPreconditioner.hpp b/Common/include/linear_algebra/CPreconditioner.hpp index 8bd7d5157105..d8cbfd9a806c 100644 --- a/Common/include/linear_algebra/CPreconditioner.hpp +++ b/Common/include/linear_algebra/CPreconditioner.hpp @@ -64,6 +64,33 @@ inline void ApplyPreconditionerOnHost(const CSysVector& u, CSysVecto apply(); } +/*! + * \brief Mirror of ApplyPreconditionerOnHost: applies a device preconditioner to host vectors. + * \note For callers that drive the Krylov solvers themselves and so never went through + * CSysSolve::Solve, which is what normally leaves the vectors on the device (Newton-Krylov). + * Device expressions are on for the duration so that a nested solve also uses the device copies. + * Only \p u is uploaded, \p v is always overwritten by the apply. + */ +template +inline void ApplyPreconditionerOnDevice(const CSysVector& u, CSysVector& v, bool useCuda, + Apply&& apply) { +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + if (useCuda && !VecExpr::UseDeviceExpressions()) { + SU2_DEVICE_REGION(u.HtDTransfer(); VecExpr::SetUseDeviceExpressions(true);) + + apply(); + + SU2_DEVICE_REGION(VecExpr::SetUseDeviceExpressions(false); v.DtHTransfer();) + return; + } + } +#else + (void)useCuda; +#endif + apply(); +} + /*! * \class CPreconditioner * \brief Abstract base class for defining a preconditioning operation. diff --git a/Common/include/linear_algebra/CSysMatrix.hpp b/Common/include/linear_algebra/CSysMatrix.hpp index 384b0bf6212f..f35efcef415c 100644 --- a/Common/include/linear_algebra/CSysMatrix.hpp +++ b/Common/include/linear_algebra/CSysMatrix.hpp @@ -385,20 +385,24 @@ class CSysMatrix { mutable struct CUgraphExec_st* precond_bwd_graph_exec = nullptr; // LU-SGS backward only mutable const ScalarType* precond_fwd_graph_vec = nullptr; /*!< \brief Pointers the apply graph * was captured with, to detect when - * it must be recaptured. */ + * it must be recaptured (the + * executable graph itself is then + * updated in place, not rebuilt, + * see InstantiateOrUpdateGraph). */ mutable ScalarType* precond_fwd_graph_prod = nullptr; mutable ScalarType* precond_bwd_graph_prod = nullptr; - /*--- Non-default stream, needed for two mutually exclusive uses that never overlap on a given - * matrix (quantized_mode and ILU are alternative preconditioner choices, decided once in - * Initialize()): (1) the ILU build/apply CUDA graphs below, since the legacy default stream - * cannot be captured into a graph; (2) HtDTransfer's async H2D transfer of the quantized L/U - * blocks, so that transfer can run concurrently (copy engine) with kernels issued on the - * default stream (e.g. QuantizeDiagonalBlocksGPU, on the SM) instead of queueing behind them on - * the same stream. Because the two uses are mutually exclusive, sharing one stream (rather than - * a dedicated one per use) needs no extra synchronization between them. htd_event marks the end - * of the H2D transfer specifically, so the default-stream kernel that first reads the result - * (the quantized SpMV) can wait on it without a host-side block. ---*/ + /*--- Non-default stream, needed for two uses: (1) the preconditioner build/apply CUDA graphs + * below, since the legacy default stream cannot be captured into a graph; (2) HtDTransfer's + * async H2D transfer of the quantized L/U blocks, so that transfer can run concurrently (copy + * engine) with kernels issued on the default stream (e.g. QuantizeDiagonalBlocksGPU, on the SM) + * instead of queueing behind them on the same stream. The two are mutually exclusive for ILU + * (never quantized) but not for Q_LU_SGS, which uses both; sharing one stream still needs no + * extra synchronization, and in fact gives the right answer for free: the apply graph is + * launched into aux_stream, hence ordered after the transfer of the quantized blocks its + * kernels read. htd_event marks the end of the H2D transfer specifically, so a *default*-stream + * kernel that reads the result (the quantized SpMV) can wait on it without a host-side + * block. ---*/ mutable struct CUstream_st* aux_stream = nullptr; mutable struct CUevent_st* htd_event = nullptr; diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index c21116c6f3e8..4a2f292bd614 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -3645,6 +3645,31 @@ void CConfig::SetPostprocessing(SU2_COMPONENT val_software, unsigned short val_i Multizone_Problem = YES; } + /*--- The solver vectors stay on the device but the halo exchange is host-side, so more than + * one rank would use stale halos. Use OpenMP for the host parts instead. ---*/ + if (Enable_Cuda && size > 1) { + SU2_MPI::Error("ENABLE_CUDA= YES is not supported with more than one MPI rank,\n" + " the halo exchange only happens on the host.\n" + " Use a single rank with OpenMP threads, e.g. 'SU2_CFD -t config.cfg'.", + CURRENT_FUNCTION); + } + + /*--- nvcc cannot compile the CoDiPack types, so the kernels are only built into the primal + * solver (see SU2_ENABLE_CUDA_KERNELS). Catch it here, not minutes into the run. ---*/ + if (Enable_Cuda) { +#ifndef SU2_ENABLE_CUDA_KERNELS +#ifdef HAVE_CUDA + SU2_MPI::Error("ENABLE_CUDA= YES is not available in the AD and direct differentiation solvers,\n" + " the CUDA kernels are only built into SU2_CFD.", + CURRENT_FUNCTION); +#else + SU2_MPI::Error("ENABLE_CUDA= YES but SU2 was not compiled with CUDA support,\n" + " reconfigure the build with -Denable-cuda=true.", + CURRENT_FUNCTION); +#endif +#endif + } + /*--- Set the default output files ---*/ if (!OptionIsSet("OUTPUT_FILES")){ nVolumeOutputFiles = 3; diff --git a/Common/src/linear_algebra/CSysMatrix.cpp b/Common/src/linear_algebra/CSysMatrix.cpp index e6292b4c10c5..a4d9fbb16127 100644 --- a/Common/src/linear_algebra/CSysMatrix.cpp +++ b/Common/src/linear_algebra/CSysMatrix.cpp @@ -52,8 +52,11 @@ FORCEINLINE void RegularizePivot(ScalarType& pivot, unsigned long row, unsigned /*--- Common failure path for a device dispatch that is not available in this build/scalar type * combination, called with CURRENT_FUNCTION so the error names the right caller. ---*/ void GPUNotAvailable(const char* caller) { -#ifdef SU2_ENABLE_CUDA_KERNELS +#if defined(SU2_ENABLE_CUDA_KERNELS) SU2_MPI::Error("GPU acceleration is not supported for AD scalar types.", caller); +#elif defined(HAVE_CUDA) + /*--- AD build, the kernels are compiled out; normally rejected by CConfig::SetPostprocessing. ---*/ + SU2_MPI::Error("GPU acceleration is not available in the AD and direct differentiation solvers.", caller); #else SU2_MPI::Error( "ENABLE_CUDA is set to YES but SU2 was not compiled with CUDA support; " @@ -224,9 +227,7 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi * the host, so only plain (or quantized) Jacobi can keep them exclusively on the device. ---*/ jacobi_on_device = useCuda && (prec == JACOBI || prec == Q_JACOBI); #ifndef CODI_REVERSE_TYPE - /*--- Q_LU_SGS is still host-only. ---*/ - const bool quantized_offdiag_needed = - allow_quant && (prec == Q_JACOBI || prec == Q_IDENTITY || (prec == Q_LU_SGS && !useCuda)); + const bool quantized_offdiag_needed = allow_quant && (prec == Q_JACOBI || prec == Q_IDENTITY || prec == Q_LU_SGS); #else /*--- No quantization in adjoint mode for now because TransposeInPlace would get complicated. ---*/ const bool quantized_offdiag_needed = false; diff --git a/Common/src/linear_algebra/CSysMatrixGPU.cu b/Common/src/linear_algebra/CSysMatrixGPU.cu index 723c8fb82680..7e6cf83d1b89 100644 --- a/Common/src/linear_algebra/CSysMatrixGPU.cu +++ b/Common/src/linear_algebra/CSysMatrixGPU.cu @@ -27,6 +27,7 @@ #include #include +#include #include "../../include/linear_algebra/CMatrixInverse.hpp" #include "../../include/linear_algebra/CSysMatrix.inl" @@ -541,6 +542,33 @@ __global__ void QuantizedBlockLDU_SpMV_kernel( y[iRow * nVar + iVar] = sum; } +/*! + * \brief Instantiate the freshly captured \p graph into \p exec, or, when \p exec already holds a + * graph with the same topology, push the new node parameters into it in place. + * \note Re-capturing the topology is cheap, instantiating it is not: cudaGraphInstantiate + * allocates and builds the executable graph, at a cost that grows with the node count (one + * node per level here), so doing it on every call would cost more than simply launching the + * kernels and would defeat the purpose of using graphs at all. cudaGraphExecUpdate keeps the + * executable graph and only rewrites the kernel arguments that changed, which is what makes + * the graphs worth having on the flexible-FGMRES path where the vectors change every call. + * The full instantiation stays as the fallback for the first call and for the (unexpected) + * case of the topology actually changing. + */ +inline void InstantiateOrUpdateGraph(cudaGraphExec_t& exec, cudaGraph_t graph, const char* what) { + SU2_ZONE_SCOPED_N("Graph instantiate or update") + if (exec != nullptr) { + cudaGraphExecUpdateResultInfo info{}; + if (cudaGraphExecUpdate(exec, graph, &info) == cudaSuccess) return; + + /*--- A failed update is recoverable (we just instantiate again), but the runtime holds on to + * the error, so consume it before the next gpuErrChk mistakes it for a real failure. ---*/ + cudaGetLastError(); + gpuErrChk(cudaGraphExecDestroy(exec)); + exec = nullptr; + } + gpuErrChk(cudaGraphInstantiate(&exec, graph, nullptr, nullptr, 0)); +} + } // namespace template @@ -692,15 +720,13 @@ void CSysMatrix::ComputeILUPreconditionerGPU(const CSysVector::ComputeILUPreconditionerGPU(const CSysVector::ComputeILUPreconditionerGPU(const CSysVector +template __global__ void LU_SGS_ForwardKernel(const su2uint* __restrict__ level_idx, unsigned long level_begin, unsigned long level_size, unsigned long nVar, DeviceLDU M, const QuantType* __restrict__ q_l, const QuantScaleType* __restrict__ q_scale_l, const ScalarType* __restrict__ invD, const ScalarType* __restrict__ vec, - ScalarType* __restrict__ prod, bool quantized_mode) { + ScalarType* __restrict__ prod) { if (blockIdx.x >= level_size) return; const unsigned long iRow = level_idx[level_begin + blockIdx.x]; @@ -760,7 +786,7 @@ __global__ void LU_SGS_ForwardKernel(const su2uint* __restrict__ level_idx, unsi auto* aux = partial + blockSize; // skip nVar * nVar threads, serves nVar threads // Compute L.x* - if (quantized_mode) { + if constexpr (Quantized) { partial[tid] = QuantizedDeviceSparseBlockMatVec(iRow, iVar, jVar, nVar, M.row_ptr_l, M.col_ind_l, q_l, q_scale_l, prod); } else { partial[tid] = DeviceSparseBlockMatVec(iRow, iVar, jVar, nVar, M.row_ptr_l, M.col_ind_l, M.l, prod); @@ -781,12 +807,12 @@ __global__ void LU_SGS_ForwardKernel(const su2uint* __restrict__ level_idx, unsi * \brief Exact backward substitution for the rows of one level, x* = D^{-1}.(D.x* - U.x) = x* - D^{-1}.U.x * \note See notes in IluBackwardKernel for more details */ -template +template __global__ void LU_SGS_BackwardKernel(const su2uint* __restrict__ level_idx, unsigned long level_begin, unsigned long level_size, unsigned long nRows, unsigned long nVar, DeviceLDU M, const QuantType* __restrict__ q_u, const QuantScaleType* __restrict__ q_scale_u, const ScalarType* __restrict__ invD, - ScalarType* __restrict__ prod, bool quantized_mode) { + ScalarType* __restrict__ prod) { if (blockIdx.x >= level_size) return; const unsigned long iRow = level_idx[level_begin + blockIdx.x]; @@ -799,7 +825,7 @@ __global__ void LU_SGS_BackwardKernel(const su2uint* __restrict__ level_idx, uns auto* aux = partial + blockSize; // skip nVar * nVar threads, serves nVar threads // Compute U.x - if (quantized_mode) { + if constexpr (Quantized) { partial[tid] = QuantizedDeviceSparseBlockMatVec(iRow, iVar, jVar, nVar, M.row_ptr_u, M.col_ind_u, q_u, q_scale_u, prod, nRows); } else { partial[tid] = DeviceSparseBlockMatVec(iRow, iVar, jVar, nVar, M.row_ptr_u, M.col_ind_u, M.u, prod, nRows); @@ -864,25 +890,35 @@ void CSysMatrix::ComputeLU_SGSForwardGPU(const CSysVector<<>>(d_precond_level_idx, begin, size, nVar, M, d_q_blocks.l, d_q_scale.l, d_invM, d_vec, d_prod, quantized_mode); + /*--- Forward substitution: compute x* = D^{-1}.(vec - L.x*). Whether the off-diagonal blocks + * are quantized is fixed for the lifetime of the matrix (Initialize decides it from the + * preconditioner type), so it selects the kernel instantiation here rather than being tested + * by every thread: inside the kernel it is a compile-time constant and the unused branch is + * not compiled at all. ---*/ + auto RecordSweep = [&](auto quantized) { + for (auto level = 0ul; level < nLevels; ++level) { + const auto begin = precond_level_ptr[level]; + const auto size = precond_level_ptr[level + 1] - begin; + if (size == 0) continue; + LU_SGS_ForwardKernel + <<>>(d_precond_level_idx, begin, size, nVar, M, d_q_blocks.l, + d_q_scale.l, d_invM, d_vec, d_prod); + } + }; + if (quantized_mode) { + RecordSweep(std::true_type{}); + } else { + RecordSweep(std::false_type{}); } gpuErrChk(cudaStreamEndCapture(aux_stream, &graph)); - gpuErrChk(cudaGraphInstantiate(&precond_fwd_graph_exec, graph, nullptr, nullptr, 0)); + InstantiateOrUpdateGraph(precond_fwd_graph_exec, graph, "LU-SGS forward"); gpuErrChk(cudaGraphDestroy(graph)); precond_fwd_graph_vec = d_vec; precond_fwd_graph_prod = d_prod; @@ -920,26 +956,33 @@ void CSysMatrix::ComputeLU_SGSBackwardGPU(CSysVector& pr /*--- Second part of the symmetric iteration: (D+U).x_(1) = D.x* ---*/ if (precond_bwd_graph_exec == nullptr || precond_bwd_graph_prod != d_prod) { - if (precond_bwd_graph_exec != nullptr) { - gpuErrChk(cudaGraphExecDestroy(precond_bwd_graph_exec)); - precond_bwd_graph_exec = nullptr; - } + SU2_ZONE_SCOPED_N("LU-SGS bwd graph recapture") cudaGraph_t graph; gpuErrChk(cudaStreamBeginCapture(aux_stream, cudaStreamCaptureModeThreadLocal)); const auto nLevels = precond_level_ptr.size() - 1; - /*--- Backward substitution: compute x* = D^{-1}.(D.x* - U.x) = x* - D^{-1}.U.x ---*/ - for (auto level = nLevels; level > 0;) { - --level; - const auto begin = precond_level_ptr[level]; - const auto size = precond_level_ptr[level + 1] - begin; - if (size == 0) continue; - LU_SGS_BackwardKernel<<>>(d_precond_level_idx, begin, size, nPointDomain, nVar, M, d_q_blocks.u, d_q_scale.u, d_invM, d_prod, quantized_mode); + /*--- Backward substitution: compute x* = D^{-1}.(D.x* - U.x) = x* - D^{-1}.U.x. Quantization + * selects the kernel instantiation, see the forward sweep. ---*/ + auto RecordSweep = [&](auto quantized) { + for (auto level = nLevels; level > 0;) { + --level; + const auto begin = precond_level_ptr[level]; + const auto size = precond_level_ptr[level + 1] - begin; + if (size == 0) continue; + LU_SGS_BackwardKernel + <<>>(d_precond_level_idx, begin, size, nPointDomain, nVar, M, + d_q_blocks.u, d_q_scale.u, d_invM, d_prod); + } + }; + if (quantized_mode) { + RecordSweep(std::true_type{}); + } else { + RecordSweep(std::false_type{}); } gpuErrChk(cudaStreamEndCapture(aux_stream, &graph)); - gpuErrChk(cudaGraphInstantiate(&precond_bwd_graph_exec, graph, nullptr, nullptr, 0)); + InstantiateOrUpdateGraph(precond_bwd_graph_exec, graph, "LU-SGS backward"); gpuErrChk(cudaGraphDestroy(graph)); precond_bwd_graph_prod = d_prod; diff --git a/SU2_CFD/include/integration/CNewtonIntegration.hpp b/SU2_CFD/include/integration/CNewtonIntegration.hpp index da8ff67930ad..25bb3139777e 100644 --- a/SU2_CFD/include/integration/CNewtonIntegration.hpp +++ b/SU2_CFD/include/integration/CNewtonIntegration.hpp @@ -152,6 +152,18 @@ class CNewtonIntegration final : public CIntegration { template::value> = 0> inline unsigned long Preconditioner_impl(const CSysVector& u, CSysVector& v, unsigned long iters, Scalar& eps) const { + /*--- Unlike the matrix-free outer product this is a CSysMatrix operation, it can run on the + * device. The outer Krylov vectors are host resident, hence the transfers. ---*/ + unsigned long nIters = 0; + ApplyPreconditionerOnDevice(u, v, config->GetCUDA(), [&] { nIters = PreconditionerApply(u, v, iters, eps); }); + return nIters; + } + + /*! + * \brief The preconditioner on its own, or a nested solve with the approximate Jacobian. + */ + inline unsigned long PreconditionerApply(const CSysVector& u, CSysVector& v, + unsigned long iters, Scalar& eps) const { const auto inner_solver = config->GetKind_Linear_Solver_Inner(); if (iters == 0 || (iters == 1 && inner_solver == LINEAR_SOLVER_INNER::SMOOTHER)) { diff --git a/SU2_CFD/src/integration/CNewtonIntegration.cpp b/SU2_CFD/src/integration/CNewtonIntegration.cpp index feb07924cafb..e745112fcb42 100644 --- a/SU2_CFD/src/integration/CNewtonIntegration.cpp +++ b/SU2_CFD/src/integration/CNewtonIntegration.cpp @@ -211,7 +211,18 @@ void CNewtonIntegration::MultiGrid_Iteration(CGeometry ****geometry_, CSolver ** solvers[FLOW_SOL]->PrepareImplicitIteration(geometry, solvers, config); - if (preconditioner) preconditioner->Build(); + if (preconditioner) { + /*--- The Jacobian is normally uploaded by CSysMatrixVectorProduct, but the outer product + * here is matrix free, so nothing else would upload it for Build(). ---*/ +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + if (config->GetCUDA()) { + SU2_DEVICE_REGION(solvers[FLOW_SOL]->Jacobian.HtDTransfer();) + } + } +#endif + preconditioner->Build(); + } auto CopyLinSysRes = [&](int sign, auto& dst) { SU2_OMP_FOR_STAT(omp_chunk_size) @@ -307,12 +318,9 @@ void CNewtonIntegration::MultiGrid_Iteration(CGeometry ****geometry_, CSolver ** solvers[FLOW_SOL]->Postprocessing(geometry, solvers, config, MESH_0); - SU2_OMP_MASTER { - solvers[FLOW_SOL]->Pressure_Forces(geometry, config); - solvers[FLOW_SOL]->Momentum_Forces(geometry, config); - solvers[FLOW_SOL]->Friction_Forces(geometry, config); - } - END_SU2_OMP_MASTER + solvers[FLOW_SOL]->Pressure_Forces(geometry, config); + solvers[FLOW_SOL]->Momentum_Forces(geometry, config); + solvers[FLOW_SOL]->Friction_Forces(geometry, config); /*--- At the end of the startup period the CFL is reset to the initial value. ---*/ diff --git a/meson.build b/meson.build index 78379a455075..e7efa33d1a68 100644 --- a/meson.build +++ b/meson.build @@ -19,7 +19,38 @@ python = pymod.find_installation() if get_option('enable-cuda') add_languages('cuda') - add_global_arguments('-arch=sm_89', language : 'cuda') + + # Compute capability to generate code for. Anything other than 'auto' is passed to nvcc as it + # is, so 'native', 'all-major', 'compute_XY', etc. all work. + cuda_arch = get_option('cuda-arch') + if cuda_arch == 'auto' + cuda_cc = '' + # Ships with the toolkit and prints e.g. "89"; it is what nvcc's own -arch=native uses. + cuda_query = find_program('__nvcc_device_query', required : false) + if cuda_query.found() + cuda_probe = run_command(cuda_query, check : false) + if cuda_probe.returncode() == 0 + cuda_cc = cuda_probe.stdout().strip().split('\n')[0].strip() + endif + endif + # Fall back to the driver, which prints e.g. "8.9". + if cuda_cc == '' + cuda_query = find_program('nvidia-smi', required : false) + if cuda_query.found() + cuda_probe = run_command(cuda_query, '--query-gpu=compute_cap', '--format=csv,noheader', check : false) + if cuda_probe.returncode() == 0 + cuda_cc = cuda_probe.stdout().strip().split('\n')[0].strip().replace('.', '') + endif + endif + endif + if cuda_cc == '' + error('Could not detect the CUDA compute capability of this machine (no GPU visible?), ' + + 'set it explicitly, for example -Dcuda-arch=sm_89.') + endif + cuda_arch = 'sm_' + cuda_cc + message('Detected CUDA compute capability, building for ' + cuda_arch) + endif + add_global_arguments('-arch=' + cuda_arch, language : 'cuda') # nvcc's frontend does not recognize the AMX-tile builtins pulled in by # newer glibc/gcc ; SU2 does not use AMX, so skip the header. add_global_arguments('-D_AMXTILEINTRIN_H_INCLUDED', language : 'cuda') diff --git a/meson_options.txt b/meson_options.txt index 6adad19386b7..db3bb47f5521 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -11,6 +11,7 @@ option('enable-mkl', type : 'boolean', value : false, description: 'enable Intel option('mkl_root', type : 'string', value : '/opt/intel/mkl', description: 'root of Intel-MKL installation (only for non-intel compilers)') option('enable-openblas', type : 'boolean', value : false, description: 'enable BLAS and LAPACK support via OpenBLAS') option('enable-cuda', type : 'boolean', value : false, description: 'enable GPU acceleration using CUDA') +option('cuda-arch', type : 'string', value : 'auto', description: 'CUDA compute capability to generate code for, "auto" detects the GPU in this machine, otherwise passed to nvcc verbatim (e.g. sm_89, native, all-major)') option('blas-name', type : 'string', value : 'openblas', description: 'name of the BLAS/LAPACK dependency') option('enable-pastix', type : 'boolean', value : false, description: 'enable PaStiX support') option('custom-mpi', type : 'boolean', value : false, description: 'enable MPI assuming the compiler and/or env vars give the correct include dirs and linker args.') From 9cd08dcdf7fc8aab2660497ac02c3f67285efbbe Mon Sep 17 00:00:00 2001 From: bellonarts Date: Mon, 7 Sep 2026 12:03:55 -0400 Subject: [PATCH 42/61] Compare NEMO freestream mass fractions against a roundoff tolerance (#2880) ## Proposed Changes `CSU2TCLib` validates the user-supplied freestream composition with `if (mf != 1.0)`, an exact floating-point equality on an accumulated sum, at four gas-model sites (ARGON, N2, AIR-5, AIR-7). A physically exact composition need not sum to bit-exact unity in double precision. For example ``` GAS_COMPOSITION= ( 0.999, 0.00025, 0.00025, 0.00025, 0.00025 ) ``` sums to `1 - 1.1e-16` and is rejected with *"Intial gas mass fractions do not sum to 1!"*, which reads to the user as a typo in a composition that visibly sums to one. Any case seeding trace species can hit this. This PR compares against a `1e-10` roundoff tolerance and reports the offending sum in the message. The tolerance sits about six orders above the roundoff in the example (`1.1e-16`) and four orders below the `1.e-6` already used for the same "is this roundoff?" question in `Common/src/fem/fem_geometry_structure.cpp`, so genuine input errors are still rejected. The constant is `constexpr passivedouble` per the convention in `option_structure.hpp`, and the message uses `SU2_TYPE::GetValue` so the change is safe under AD builds. The sum is printed with 16 significant digits through a small static helper shared by the four sites, because `to_string` would print `1.000000` for any sum that fails the tolerance by less than `5e-7`; a composition off by `3e-7` now reports `sum = 0.9999997` and a real typo reports `sum = 0.99`. The pre-existing "Intial" typo is corrected in the same lines. Verification with actual solver launches on the public viscous-cone mesh (`ITER= 0`, exit code as the verdict): the roundoff-unity AIR-5 and AIR-7 compositions are now accepted; exact compositions for ARGON, N2, AIR-5 and AIR-7 remain accepted; real typos such as `( 0.75, 0.24, 0, 0, 0 )` remain rejected for every gas model. The regression baselines are unchanged, serial and MPI, so no regression vectors are modified. The contributor entry is added to `AUTHORS.md`. A dedicated constructor unit test was removed in response to maintainer review; the acceptance/rejection matrix above exercises the behavior through actual solver launches. ## Related Work The confusing failure mode may account for some reports of NEMO setup difficulty; it surfaces whenever trace species are seeded rather than left at exact zero. No open issue or PR is known to cover it. ## PR Checklist *Put an X by all that apply. You can fill this out after submitting the PR. If you have any questions, don't hesitate to ask! We want to help. These a guide for you to know what the reviewers will be looking for in your contribution.* - [x] I am submitting my contribution to the develop branch. - [x] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [x] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [x] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [x] A dedicated unit test is not included at maintainer request; actual solver launches cover accepted roundoff and rejected invalid compositions. - [x] Documentation is not applicable; this corrects input validation and its diagnostic without changing a configuration keyword or public API. --------- Co-authored-by: Claude Fable 5.1 Co-authored-by: BlueChips RunPod Integration --- AUTHORS.md | 1 + SU2_CFD/src/fluid/CSU2TCLib.cpp | 36 +++++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 67ecbf541e5a..d82ccb599fe9 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -60,6 +60,7 @@ Arne Voß Ayush Kumar Beckett Y. Zhou Benjamin S. Kirk +BlueChips Brendan Tracey Brian Munguía Carsten Othmer diff --git a/SU2_CFD/src/fluid/CSU2TCLib.cpp b/SU2_CFD/src/fluid/CSU2TCLib.cpp index 4f81220fac04..fefbd074ef49 100644 --- a/SU2_CFD/src/fluid/CSU2TCLib.cpp +++ b/SU2_CFD/src/fluid/CSU2TCLib.cpp @@ -28,6 +28,26 @@ #include "../../include/fluid/CSU2TCLib.hpp" #include "../../../Common/include/option_structure.hpp" +#include +#include + +/*--- Freestream mass fractions are supplied by the user as decimal literals and + accumulated in double precision, so a physically exact composition need not + sum to bit-exact unity (e.g. 0.999 + 4*0.00025 sums to 1 - 1.1e-16). Compare + against a roundoff tolerance instead: tight enough that any real typo is + still rejected, loose enough that valid input never is. ---*/ +constexpr passivedouble MASSFRAC_SUM_TOL = 1.0E-10; + +/*--- Report the offending sum with enough digits to tell it apart from unity; + the default six decimals of to_string would print 1.000000 for a sum that + fails the tolerance by less than 5e-7. ---*/ +static string MassFracSumErrorMessage(su2double mf) { + ostringstream msg; + msg << "CONFIG ERROR: Initial gas mass fractions do not sum to 1 (sum = " << setprecision(16) + << SU2_TYPE::GetValue(mf) << ")"; + return msg.str(); +} + CSU2TCLib::CSU2TCLib(const CConfig* config, unsigned short val_nDim, bool viscous): CNEMOGas(config, val_nDim){ unsigned short maxEl = 0; @@ -66,8 +86,8 @@ CSU2TCLib::CSU2TCLib(const CConfig* config, unsigned short val_nDim, bool viscou mf = 0.0; for (iSpecies = 0; iSpecies < nSpecies; iSpecies++) mf += MassFrac_Freestream[iSpecies]; - if (mf != 1.0) { - SU2_MPI::Error("CONFIG ERROR: Intial gas mass fractions do not sum to 1!", CURRENT_FUNCTION); + if (fabs(mf - 1.0) > MASSFRAC_SUM_TOL) { + SU2_MPI::Error(MassFracSumErrorMessage(mf), CURRENT_FUNCTION); } /*--- Define parameters of the gas model ---*/ @@ -133,8 +153,8 @@ CSU2TCLib::CSU2TCLib(const CConfig* config, unsigned short val_nDim, bool viscou mf = 0.0; for (iSpecies = 0; iSpecies < nSpecies; iSpecies++) mf += MassFrac_Freestream[iSpecies]; - if (mf != 1.0) { - SU2_MPI::Error("CONFIG ERROR: Intial gas mass fractions do not sum to 1!", CURRENT_FUNCTION); + if (fabs(mf - 1.0) > MASSFRAC_SUM_TOL) { + SU2_MPI::Error(MassFracSumErrorMessage(mf), CURRENT_FUNCTION); } /*--- Define parameters of the gas model ---*/ @@ -296,8 +316,8 @@ CSU2TCLib::CSU2TCLib(const CConfig* config, unsigned short val_nDim, bool viscou mf = 0.0; for (iSpecies = 0; iSpecies < nSpecies; iSpecies++) mf += MassFrac_Freestream[iSpecies]; - if (mf != 1.0) { - SU2_MPI::Error("CONFIG ERROR: Intial gas mass fractions do not sum to 1!", CURRENT_FUNCTION); + if (fabs(mf - 1.0) > MASSFRAC_SUM_TOL) { + SU2_MPI::Error(MassFracSumErrorMessage(mf), CURRENT_FUNCTION); } /*--- Define parameters of the gas model ---*/ @@ -663,8 +683,8 @@ CSU2TCLib::CSU2TCLib(const CConfig* config, unsigned short val_nDim, bool viscou mf = 0.0; for (iSpecies = 0; iSpecies < nSpecies; iSpecies++) mf += MassFrac_Freestream[iSpecies]; - if (mf != 1.0) { - SU2_MPI::Error("CONFIG ERROR: Intial gas mass fractions do not sum to 1!", CURRENT_FUNCTION); + if (fabs(mf - 1.0) > MASSFRAC_SUM_TOL) { + SU2_MPI::Error(MassFracSumErrorMessage(mf), CURRENT_FUNCTION); } /*--- Define parameters of the gas model ---*/ From 710ce76dc7031e651ac4f082b1b98839c1c3594f Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:30:19 -0700 Subject: [PATCH 43/61] Make single and double precision restarts compatible with either version of the code (#2889) ## Proposed Changes The 4th int of the header was written as 0 and never read, it now holds the size in bytes of the floating point data, so single and double precision builds can read each other's restart files. Files written before this field existed have a 0 there and were always double. The MPI-IO read paths were hardcoded to MPI_DOUBLE, which is redefined to MPI_FLOAT in single precision builds and was therefore consistent within a build but not across builds. They now describe the payload as blocks of bytes of the size given by the header, and convert to the precision of the build when the two differ. ## PR Checklist - [X] I am submitting my contribution to the develop branch. - [X] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [X] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [X] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [X] I have added a test case that demonstrates my contribution, if necessary. - [X] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --------- Co-authored-by: Claude Opus 5 --- Common/include/option_structure.hpp | 65 +++++++++++ Common/src/geometry/CPhysicalGeometry.cpp | 108 ++++++++++++------ .../filewriter/CSU2BinaryFileWriter.cpp | 12 +- SU2_CFD/src/solvers/CBaselineSolver.cpp | 12 +- SU2_CFD/src/solvers/CBaselineSolver_FEM.cpp | 12 +- SU2_CFD/src/solvers/CSolver.cpp | 55 ++++++--- 6 files changed, 197 insertions(+), 67 deletions(-) diff --git a/Common/include/option_structure.hpp b/Common/include/option_structure.hpp index 3d5d57c17672..20ed826c85ea 100644 --- a/Common/include/option_structure.hpp +++ b/Common/include/option_structure.hpp @@ -195,6 +195,71 @@ inline unsigned short nPointsOfElementType(unsigned short elementType) { } const int CGNS_STRING_SIZE = 33; /*!< \brief Length of strings used in the CGNS format. */ + +/*--- Layout of the header of the native SU2 binary solution/restart format, shared by + CSU2BinaryFileWriter and the routines that read those files so they cannot drift + apart. The header is SU2_RESTART_HEADER_SIZE ints: a magic number, the number of + variables, the number of points, the size in bytes of the floating point data that + follows, and one spare. + + The 4th and 5th ints used to be the number of ints and of doubles of a metadata + trailer (1 and 5, later 1 and 8) that the writer appended after the data. That + trailer is no longer written (metadata goes to a separate ASCII file) and both + ints have been 0 since, but old files in circulation still have 1 and 5 or 8 + there, which is why 1 is accepted below as meaning double precision. ---*/ +const int SU2_RESTART_MAGIC_NUMBER = 535532; /*!< \brief Hex representation of "SU2". */ +const int SU2_RESTART_HEADER_SIZE = 5; /*!< \brief Number of ints in the header. */ +const int SU2_RESTART_PRECISION_IDX = 3; /*!< \brief Position of the precision field. */ +const int SU2_RESTART_METADATA_IDX = 4; /*!< \brief Position of the legacy metadata count. */ +const int SU2_RESTART_MAX_METADATA = 8; /*!< \brief Most metadata doubles a trailer ever had. */ + +/*! + * \brief Size in bytes of the floating point data of a native SU2 binary solution file. + * \param[in] precisionField - The SU2_RESTART_PRECISION_IDX entry of the file header. + * \return 8 for double precision, 4 for single precision. + * \note Files written before the field had this meaning have a 0 or a 1 there (see above) + * and were always double precision. + */ +inline int GetSU2BinaryScalarSize(int precisionField) { + if (precisionField == 0 || precisionField == 1) return static_cast(sizeof(double)); + if (precisionField != static_cast(sizeof(double)) && precisionField != static_cast(sizeof(float))) { + SU2_MPI::Error("Invalid floating point precision in the header of a binary SU2 solution file.", CURRENT_FUNCTION); + } + return precisionField; +} + +/*! + * \brief Number of metadata scalars in the trailer of a native SU2 binary solution file. + * \param[in] precisionField - The SU2_RESTART_PRECISION_IDX entry of the file header. + * \param[in] metadataField - The SU2_RESTART_METADATA_IDX entry of the file header. + * \return Number of scalars of the trailer, preceded by one int (the iteration number), + * or 0 for the files that do not have one. + * \note Only files that still use the two ints as trailer counts have a trailer, and in + * those the precision field is the number of trailer ints, which was always 1 (see above). + */ +inline int GetSU2BinaryMetadataSize(int precisionField, int metadataField) { + if (precisionField != 1) return 0; + return std::min(metadataField, SU2_RESTART_MAX_METADATA); +} + +/*! + * \brief Convert floating point data read from a native SU2 binary solution file, which + * may have been written by a build of different precision, to the precision of this build. + * \param[in] buffer - Raw data as read from the file, of size count*scalarSize bytes. + * \param[in] scalarSize - Size in bytes of the scalars in the file, see GetSU2BinaryScalarSize. + * \param[in] count - Number of scalars. + * \param[out] data - Converted data, must not overlap with buffer. + */ +inline void SU2BinaryDataToPassive(const void* buffer, int scalarSize, unsigned long count, passivedouble* data) { + if (scalarSize == static_cast(sizeof(float))) { + const auto* src = static_cast(buffer); + for (unsigned long i = 0; i < count; ++i) data[i] = src[i]; + } else { + const auto* src = static_cast(buffer); + for (unsigned long i = 0; i < count; ++i) data[i] = src[i]; + } +} + const int SU2_BINARY_STRING_SIZE = 65; /*!< \brief Length of strings (e.g. marker names) used in the native SU2 binary mesh format. Shared by CSU2BinaryMeshReaderBase and CSU2MeshBinaryFileWriter so they cannot drift apart. */ diff --git a/Common/src/geometry/CPhysicalGeometry.cpp b/Common/src/geometry/CPhysicalGeometry.cpp index f7d2d36f198c..0f5dbc59afb7 100644 --- a/Common/src/geometry/CPhysicalGeometry.cpp +++ b/Common/src/geometry/CPhysicalGeometry.cpp @@ -7938,8 +7938,8 @@ void CPhysicalGeometry::SetSensitivity(CConfig* config) { char str_buf[CGNS_STRING_SIZE], fname[100]; unsigned short iVar; strcpy(fname, filename.c_str()); - int nRestart_Vars = 5, nFields; - int* Restart_Vars = new int[5]; + int nRestart_Vars = SU2_RESTART_HEADER_SIZE, nFields; + int* Restart_Vars = new int[SU2_RESTART_HEADER_SIZE]; passivedouble* Restart_Data = nullptr; int Restart_Iter = 0; passivedouble Restart_Meta_Passive[8] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; @@ -7969,7 +7969,7 @@ void CPhysicalGeometry::SetSensitivity(CConfig* config) { /*--- Check that this is an SU2 binary file. SU2 binary files have the hex representation of "SU2" as the first int in the file. ---*/ - if (Restart_Vars[0] != 535532) { + if (Restart_Vars[0] != SU2_RESTART_MAGIC_NUMBER) { SU2_MPI::Error(string("File ") + string(fname) + string(" is not a binary SU2 restart file.\n") + string("SU2 reads/writes binary restart files by default.\n") + string("Note that backward compatibility for ASCII restart files is\n") + @@ -7977,9 +7977,11 @@ void CPhysicalGeometry::SetSensitivity(CConfig* config) { CURRENT_FUNCTION); } - /*--- Store the number of fields for simplicity. ---*/ + /*--- Store the number of fields for simplicity. The file may have been written by + a build of different precision, in which case the data needs to be converted. ---*/ nFields = Restart_Vars[1]; + const int scalarSize = GetSU2BinaryScalarSize(Restart_Vars[SU2_RESTART_PRECISION_IDX]); /*--- Read the variable names from the file. Note that we are adopting a fixed length of 33 for the string length to match with CGNS. This is @@ -8001,28 +8003,45 @@ void CPhysicalGeometry::SetSensitivity(CConfig* config) { /*--- Read in the data for the restart at all local points. ---*/ - ret = fread(Restart_Data, sizeof(passivedouble), nFields * GetnPointDomain(), fhw); - if (ret != static_cast(nFields) * GetnPointDomain()) { + const unsigned long nScalars = static_cast(nFields) * GetnPointDomain(); + + if (scalarSize == static_cast(sizeof(passivedouble))) { + ret = fread(Restart_Data, scalarSize, nScalars, fhw); + } else { + vector buffer(nScalars * scalarSize); + ret = fread(buffer.data(), scalarSize, nScalars, fhw); + SU2BinaryDataToPassive(buffer.data(), scalarSize, nScalars, Restart_Data); + } + if (ret != nScalars) { SU2_MPI::Error("Error reading restart file.", CURRENT_FUNCTION); } - /*--- Compute (negative) displacements and grab the metadata. ---*/ + /*--- Grab the metadata trailer, which only old files have. Without it the iteration + number and the metadata keep the zeros they were initialized with. ---*/ - ret = sizeof(int) + 8 * sizeof(passivedouble); - fseek(fhw, -ret, SEEK_END); + const int nMeta = + GetSU2BinaryMetadataSize(Restart_Vars[SU2_RESTART_PRECISION_IDX], Restart_Vars[SU2_RESTART_METADATA_IDX]); + if (nMeta > 0) { + /*--- Compute (negative) displacements and jump to the trailer. ---*/ - /*--- Read the external iteration. ---*/ + ret = sizeof(int) + nMeta * scalarSize; + fseek(fhw, -ret, SEEK_END); - ret = fread(&Restart_Iter, sizeof(int), 1, fhw); - if (ret != 1) { - SU2_MPI::Error("Error reading restart file.", CURRENT_FUNCTION); - } + /*--- Read the external iteration. ---*/ - /*--- Read the metadata. ---*/ + ret = fread(&Restart_Iter, sizeof(int), 1, fhw); + if (ret != 1) { + SU2_MPI::Error("Error reading restart file.", CURRENT_FUNCTION); + } - ret = fread(Restart_Meta_Passive, sizeof(passivedouble), 8, fhw); - if (ret != 8) { - SU2_MPI::Error("Error reading restart file.", CURRENT_FUNCTION); + /*--- Read the metadata. ---*/ + + double meta_buf[SU2_RESTART_MAX_METADATA]; /*--- Correctly aligned for either precision. ---*/ + ret = fread(meta_buf, scalarSize, nMeta, fhw); + if (ret != static_cast(nMeta)) { + SU2_MPI::Error("Error reading restart file.", CURRENT_FUNCTION); + } + SU2BinaryDataToPassive(meta_buf, scalarSize, nMeta, Restart_Meta_Passive); } /*--- Close the file. ---*/ @@ -8065,7 +8084,7 @@ void CPhysicalGeometry::SetSensitivity(CConfig* config) { /*--- Check that this is an SU2 binary file. SU2 binary files have the hex representation of "SU2" as the first int in the file. ---*/ - if (Restart_Vars[0] != 535532) { + if (Restart_Vars[0] != SU2_RESTART_MAGIC_NUMBER) { SU2_MPI::Error(string("File ") + string(fname) + string(" is not a binary SU2 restart file.\n") + string("SU2 reads/writes binary restart files by default.\n") + string("Note that backward compatibility for ASCII restart files is\n") + @@ -8073,9 +8092,11 @@ void CPhysicalGeometry::SetSensitivity(CConfig* config) { CURRENT_FUNCTION); } - /*--- Store the number of fields for simplicity. ---*/ + /*--- Store the number of fields for simplicity. The file may have been written by + a build of different precision, in which case the data needs to be converted. ---*/ nFields = Restart_Vars[1]; + const int scalarSize = GetSU2BinaryScalarSize(Restart_Vars[SU2_RESTART_PRECISION_IDX]); /*--- Read the variable names from the file. Note that we are adopting a fixed length of 33 for the string length to match with CGNS. This is @@ -8109,9 +8130,12 @@ void CPhysicalGeometry::SetSensitivity(CConfig* config) { delete[] mpi_str_buf; - /*--- We're writing only su2doubles in the data portion of the file. ---*/ + /*--- The data portion of the file holds scalars of the precision recorded in the + header, which is not necessarily that of this build. Describe them as opaque + blocks of bytes so that the file views do not depend on the build precision. ---*/ - etype = MPI_DOUBLE; + MPI_Type_contiguous(scalarSize, MPI_BYTE, &etype); + MPI_Type_commit(&etype); /*--- We need to ignore the 4 ints describing the nVar_Restart and nPoints, along with the string names of the variables. ---*/ @@ -8129,11 +8153,11 @@ void CPhysicalGeometry::SetSensitivity(CConfig* config) { for (iPoint_Global = 0; iPoint_Global < GetGlobal_nPointDomain(); iPoint_Global++) { if (GetGlobal_to_Local_Point(iPoint_Global) > -1) { blocklen[counter] = nFields; - displace[counter] = iPoint_Global * nFields * sizeof(passivedouble); + displace[counter] = iPoint_Global * nFields * scalarSize; counter++; } } - MPI_Type_create_hindexed(GetnPointDomain(), blocklen, displace, MPI_DOUBLE, &filetype); + MPI_Type_create_hindexed(GetnPointDomain(), blocklen, displace, etype, &filetype); MPI_Type_commit(&filetype); /*--- Set the view for the MPI file write, i.e., describe the location in @@ -8145,31 +8169,45 @@ void CPhysicalGeometry::SetSensitivity(CConfig* config) { Restart_Data = new passivedouble[nFields * GetnPointDomain()]; - /*--- Collective call for all ranks to read from their view simultaneously. ---*/ + /*--- Collective call for all ranks to read from their view simultaneously, + converting the data if the file precision does not match this build. ---*/ + + const unsigned long nScalars = static_cast(nFields) * GetnPointDomain(); - MPI_File_read_all(fhw, Restart_Data, nFields * GetnPointDomain(), MPI_DOUBLE, &status); + if (scalarSize == static_cast(sizeof(passivedouble))) { + MPI_File_read_all(fhw, Restart_Data, nScalars, etype, &status); + } else { + vector buffer(nScalars * scalarSize); + MPI_File_read_all(fhw, buffer.data(), nScalars, etype, &status); + SU2BinaryDataToPassive(buffer.data(), scalarSize, nScalars, Restart_Data); + } - /*--- Free the derived datatype. ---*/ + /*--- Free the derived datatypes. ---*/ MPI_Type_free(&filetype); + MPI_Type_free(&etype); /*--- Reset the file view before writing the metadata. ---*/ MPI_File_set_view(fhw, 0, MPI_BYTE, MPI_BYTE, (char*)"native", MPI_INFO_NULL); - /*--- Access the metadata. ---*/ + /*--- Access the metadata trailer, which only old files have. Without it the iteration + number and the metadata keep the zeros they were initialized with. ---*/ - if (rank == MASTER_NODE) { + const int nMeta = + GetSU2BinaryMetadataSize(Restart_Vars[SU2_RESTART_PRECISION_IDX], Restart_Vars[SU2_RESTART_METADATA_IDX]); + if (nMeta > 0 && rank == MASTER_NODE) { /*--- External iteration. ---*/ disp = (nRestart_Vars * sizeof(int) + nFields * CGNS_STRING_SIZE * sizeof(char) + - nFields * Restart_Vars[2] * sizeof(passivedouble)); + static_cast(nFields) * Restart_Vars[2] * scalarSize); MPI_File_read_at(fhw, disp, &Restart_Iter, 1, MPI_INT, MPI_STATUS_IGNORE); /*--- Additional doubles for AoA, AoS, etc. ---*/ - disp = (nRestart_Vars * sizeof(int) + nFields * CGNS_STRING_SIZE * sizeof(char) + - nFields * Restart_Vars[2] * sizeof(passivedouble) + 1 * sizeof(int)); - MPI_File_read_at(fhw, disp, Restart_Meta_Passive, 8, MPI_DOUBLE, MPI_STATUS_IGNORE); + disp += sizeof(int); + double meta_buf[SU2_RESTART_MAX_METADATA]; /*--- Correctly aligned for either precision. ---*/ + MPI_File_read_at(fhw, disp, meta_buf, nMeta * scalarSize, MPI_BYTE, MPI_STATUS_IGNORE); + SU2BinaryDataToPassive(meta_buf, scalarSize, nMeta, Restart_Meta_Passive); } /*--- Communicate metadata. ---*/ @@ -8277,7 +8315,7 @@ void CPhysicalGeometry::SetSensitivity(CConfig* config) { /*--- Check that this is an SU2 binary file. SU2 binary files have the hex representation of "SU2" as the first int in the file. ---*/ - if (magic_number == 535532) { + if (magic_number == SU2_RESTART_MAGIC_NUMBER) { SU2_MPI::Error(string("File ") + string(fname) + string(" is a binary SU2 restart file, expected ASCII.\n") + string("SU2 reads/writes binary restart files by default.\n") + string("Note that backward compatibility for ASCII restart files is\n") + @@ -8315,7 +8353,7 @@ void CPhysicalGeometry::SetSensitivity(CConfig* config) { /*--- Check that this is an SU2 binary file. SU2 binary files have the hex representation of "SU2" as the first int in the file. ---*/ - if (magic_number == 535532) { + if (magic_number == SU2_RESTART_MAGIC_NUMBER) { SU2_MPI::Error(string("File ") + string(fname) + string(" is a binary SU2 restart file, expected ASCII.\n") + string("SU2 reads/writes binary restart files by default.\n") + string("Note that backward compatibility for ASCII restart files is\n") + diff --git a/SU2_CFD/src/output/filewriter/CSU2BinaryFileWriter.cpp b/SU2_CFD/src/output/filewriter/CSU2BinaryFileWriter.cpp index 113fe6fde5cb..b9c7e57ed86b 100644 --- a/SU2_CFD/src/output/filewriter/CSU2BinaryFileWriter.cpp +++ b/SU2_CFD/src/output/filewriter/CSU2BinaryFileWriter.cpp @@ -51,10 +51,14 @@ void CSU2BinaryFileWriter::WriteData(string val_filename){ /*--- Prepare the first ints containing the counts. The first is a magic number that we can use to check for binary files (it is the hex representation for "SU2"). The second two values are number of variables - and number of points (DoFs). ---*/ - - int var_buf_size = 5; - int var_buf[5] = {535532, nVar, (int)nPoint_Global, 0, 0}; + and number of points (DoFs). The fourth is the size in bytes of the + floating point data, which lets builds of either precision read the + file (a 0 there, in files written before this field existed, means + double precision). ---*/ + + int var_buf_size = SU2_RESTART_HEADER_SIZE; + int var_buf[SU2_RESTART_HEADER_SIZE] = {SU2_RESTART_MAGIC_NUMBER, nVar, (int)nPoint_Global, + (int)sizeof(passivedouble), 0}; /*--- Open the file using MPI I/O ---*/ diff --git a/SU2_CFD/src/solvers/CBaselineSolver.cpp b/SU2_CFD/src/solvers/CBaselineSolver.cpp index 09828bf243c1..fb93d974d327 100644 --- a/SU2_CFD/src/solvers/CBaselineSolver.cpp +++ b/SU2_CFD/src/solvers/CBaselineSolver.cpp @@ -106,8 +106,8 @@ void CBaselineSolver::SetOutputVariables(CGeometry *geometry, CConfig *config) { char fname[100]; strcpy(fname, filename.c_str()); - int nVar_Buf = 5; - int var_buf[5]; + int nVar_Buf = SU2_RESTART_HEADER_SIZE; + int var_buf[SU2_RESTART_HEADER_SIZE]; #ifndef HAVE_MPI @@ -133,7 +133,7 @@ void CBaselineSolver::SetOutputVariables(CGeometry *geometry, CConfig *config) { /*--- Check that this is an SU2 binary file. SU2 binary files have the hex representation of "SU2" as the first int in the file. ---*/ - if (var_buf[0] != 535532) { + if (var_buf[0] != SU2_RESTART_MAGIC_NUMBER) { SU2_MPI::Error(string("File ") + string(fname) + string(" is not a binary SU2 restart file.\n") + string("SU2 reads/writes binary restart files by default.\n") + string("Note that backward compatibility for ASCII restart files is\n") + @@ -185,7 +185,7 @@ void CBaselineSolver::SetOutputVariables(CGeometry *geometry, CConfig *config) { /*--- Check that this is an SU2 binary file. SU2 binary files have the hex representation of "SU2" as the first int in the file. ---*/ - if (var_buf[0] != 535532) { + if (var_buf[0] != SU2_RESTART_MAGIC_NUMBER) { SU2_MPI::Error(string("File ") + string(fname) + string(" is not a binary SU2 restart file.\n") + string("SU2 reads/writes binary restart files by default.\n") + string("Note that backward compatibility for ASCII restart files is\n") + @@ -270,7 +270,7 @@ void CBaselineSolver::SetOutputVariables(CGeometry *geometry, CConfig *config) { /*--- Check that this is an SU2 binary file. SU2 binary files have the hex representation of "SU2" as the first int in the file. ---*/ - if (magic_number == 535532) { + if (magic_number == SU2_RESTART_MAGIC_NUMBER) { SU2_MPI::Error(string("File ") + string(fname) + string(" is a binary SU2 restart file, expected ASCII.\n") + string("SU2 reads/writes binary restart files by default.\n") + string("Note that backward compatibility for ASCII restart files is\n") + @@ -308,7 +308,7 @@ void CBaselineSolver::SetOutputVariables(CGeometry *geometry, CConfig *config) { /*--- Check that this is an SU2 binary file. SU2 binary files have the hex representation of "SU2" as the first int in the file. ---*/ - if (magic_number == 535532) { + if (magic_number == SU2_RESTART_MAGIC_NUMBER) { SU2_MPI::Error(string("File ") + string(fname) + string(" is a binary SU2 restart file, expected ASCII.\n") + string("SU2 reads/writes binary restart files by default.\n") + string("Note that backward compatibility for ASCII restart files is\n") + diff --git a/SU2_CFD/src/solvers/CBaselineSolver_FEM.cpp b/SU2_CFD/src/solvers/CBaselineSolver_FEM.cpp index d667a76649c4..df18c536a112 100644 --- a/SU2_CFD/src/solvers/CBaselineSolver_FEM.cpp +++ b/SU2_CFD/src/solvers/CBaselineSolver_FEM.cpp @@ -111,8 +111,8 @@ void CBaselineSolver_FEM::SetOutputVariables(CGeometry *geometry, CConfig *confi if (config->GetRead_Binary_Restart()) { - int nVar_Buf = 5; - int var_buf[5]; + int nVar_Buf = SU2_RESTART_HEADER_SIZE; + int var_buf[SU2_RESTART_HEADER_SIZE]; #ifndef HAVE_MPI @@ -138,7 +138,7 @@ void CBaselineSolver_FEM::SetOutputVariables(CGeometry *geometry, CConfig *confi /*--- Check that this is an SU2 binary file. SU2 binary files have the hex representation of "SU2" as the first int in the file. ---*/ - if (var_buf[0] != 535532) + if (var_buf[0] != SU2_RESTART_MAGIC_NUMBER) SU2_MPI::Error(string("File ") + filename + string(" is not a binary SU2 restart file.\n") + string("SU2 reads/writes binary restart files by default.\n") + string("Note that backward compatibility for ASCII restart files is\n") + @@ -181,7 +181,7 @@ void CBaselineSolver_FEM::SetOutputVariables(CGeometry *geometry, CConfig *confi /*--- Check that this is an SU2 binary file. SU2 binary files have the hex representation of "SU2" as the first int in the file. ---*/ - if (var_buf[0] != 535532) + if (var_buf[0] != SU2_RESTART_MAGIC_NUMBER) SU2_MPI::Error(string("File ") + filename + string(" is not a binary SU2 restart file.\n") + string("SU2 reads/writes binary restart files by default.\n") + string("Note that backward compatibility for ASCII restart files is\n") + @@ -228,7 +228,7 @@ void CBaselineSolver_FEM::SetOutputVariables(CGeometry *geometry, CConfig *confi /*--- Check that this is an SU2 binary file. SU2 binary files have the hex representation of "SU2" as the first int in the file. ---*/ - if (magic_number == 535532) + if (magic_number == SU2_RESTART_MAGIC_NUMBER) SU2_MPI::Error(string("File ") + filename + string(" is a binary SU2 restart file, expected ASCII.\n") + string("SU2 reads/writes binary restart files by default.\n") + string("Note that backward compatibility for ASCII restart files is\n") + @@ -266,7 +266,7 @@ void CBaselineSolver_FEM::SetOutputVariables(CGeometry *geometry, CConfig *confi /*--- Check that this is an SU2 binary file. SU2 binary files have the hex representation of "SU2" as the first int in the file. ---*/ - if (magic_number == 535532) + if (magic_number == SU2_RESTART_MAGIC_NUMBER) SU2_MPI::Error(string("File ") + filename + string(" is a binary SU2 restart file, expected ASCII.\n") + string("SU2 reads/writes binary restart files by default.\n") + string("Note that backward compatibility for ASCII restart files is\n") + diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index 5662aa46ed46..d1d323dd193d 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -2856,7 +2856,7 @@ void CSolver::Read_SU2_Restart_ASCII(CGeometry *geometry, const CConfig *config, /*--- Check that this is an SU2 binary file. SU2 binary files have the hex representation of "SU2" as the first int in the file. ---*/ - if (magic_number == 535532) { + if (magic_number == SU2_RESTART_MAGIC_NUMBER) { SU2_MPI::Error(string("File ") + string(fname) + string(" is a binary SU2 restart file, expected ASCII.\n") + string("SU2 reads/writes binary restart files by default.\n") + string("Note that backward compatibility for ASCII restart files is\n") + @@ -2895,7 +2895,7 @@ void CSolver::Read_SU2_Restart_ASCII(CGeometry *geometry, const CConfig *config, /*--- Check that this is an SU2 binary file. SU2 binary files have the hex representation of "SU2" as the first int in the file. ---*/ - if (magic_number == 535532) { + if (magic_number == SU2_RESTART_MAGIC_NUMBER) { SU2_MPI::Error(string("File ") + string(fname) + string(" is a binary SU2 restart file, expected ASCII.\n") + string("SU2 reads/writes binary restart files by default.\n") + string("Note that backward compatibility for ASCII restart files is\n") + @@ -2979,7 +2979,7 @@ void CSolver::Read_SU2_Restart_Binary(CGeometry *geometry, const CConfig *config char str_buf[CGNS_STRING_SIZE], fname[100]; strcpy(fname, val_filename.c_str()); - const int nRestart_Vars = 5; + const int nRestart_Vars = SU2_RESTART_HEADER_SIZE; Restart_Vars.resize(nRestart_Vars); fields.clear(); @@ -3007,17 +3007,20 @@ void CSolver::Read_SU2_Restart_Binary(CGeometry *geometry, const CConfig *config /*--- Check that this is an SU2 binary file. SU2 binary files have the hex representation of "SU2" as the first int in the file. ---*/ - if (Restart_Vars[0] != 535532) { + if (Restart_Vars[0] != SU2_RESTART_MAGIC_NUMBER) { SU2_MPI::Error(string("File ") + string(fname) + string(" is not a binary SU2 restart file.\n") + string("SU2 reads/writes binary restart files by default.\n") + string("Note that backward compatibility for ASCII restart files is\n") + string("possible with the READ_BINARY_RESTART option."), CURRENT_FUNCTION); } - /*--- Store the number of fields and points to be read for clarity. ---*/ + /*--- Store the number of fields and points to be read for clarity. The file may + have been written by a build of different precision, in which case the data needs + to be converted after reading it. ---*/ const unsigned long nFields = Restart_Vars[1]; const unsigned long nPointFile = Restart_Vars[2]; + const int scalarSize = GetSU2BinaryScalarSize(Restart_Vars[SU2_RESTART_PRECISION_IDX]); /*--- Read the variable names from the file. Note that we are adopting a fixed length of 33 for the string length to match with CGNS. This is @@ -3039,7 +3042,13 @@ void CSolver::Read_SU2_Restart_Binary(CGeometry *geometry, const CConfig *config /*--- Read in the data for the restart at all local points. ---*/ - ret = fread(Restart_Data.data(), sizeof(passivedouble), nFields*nPointFile, fhw); + if (scalarSize == static_cast(sizeof(passivedouble))) { + ret = fread(Restart_Data.data(), scalarSize, nFields*nPointFile, fhw); + } else { + vector buffer(nFields*nPointFile*scalarSize); + ret = fread(buffer.data(), scalarSize, nFields*nPointFile, fhw); + SU2BinaryDataToPassive(buffer.data(), scalarSize, nFields*nPointFile, Restart_Data.data()); + } if (ret != nFields*nPointFile) { SU2_MPI::Error("Error reading restart file.", CURRENT_FUNCTION); } @@ -3077,17 +3086,20 @@ void CSolver::Read_SU2_Restart_Binary(CGeometry *geometry, const CConfig *config /*--- Check that this is an SU2 binary file. SU2 binary files have the hex representation of "SU2" as the first int in the file. ---*/ - if (Restart_Vars[0] != 535532) { + if (Restart_Vars[0] != SU2_RESTART_MAGIC_NUMBER) { SU2_MPI::Error(string("File ") + string(fname) + string(" is not a binary SU2 restart file.\n") + string("SU2 reads/writes binary restart files by default.\n") + string("Note that backward compatibility for ASCII restart files is\n") + string("possible with the READ_BINARY_RESTART option."), CURRENT_FUNCTION); } - /*--- Store the number of fields and points to be read for clarity. ---*/ + /*--- Store the number of fields and points to be read for clarity. The file may + have been written by a build of different precision, in which case the data needs + to be converted after reading it. ---*/ const unsigned long nFields = Restart_Vars[1]; const unsigned long nPointFile = Restart_Vars[2]; + const int scalarSize = GetSU2BinaryScalarSize(Restart_Vars[SU2_RESTART_PRECISION_IDX]); /*--- Read the variable names from the file. Note that we are adopting a fixed length of 33 for the string length to match with CGNS. This is @@ -3124,9 +3136,12 @@ void CSolver::Read_SU2_Restart_Binary(CGeometry *geometry, const CConfig *config delete [] mpi_str_buf; - /*--- We're writing only su2doubles in the data portion of the file. ---*/ + /*--- The data portion of the file holds scalars of the precision recorded in the + header, which is not necessarily that of this build. Describe them as opaque + blocks of bytes so that the file views do not depend on the build precision. ---*/ - etype = MPI_DOUBLE; + MPI_Type_contiguous(scalarSize, MPI_BYTE, &etype); + MPI_Type_commit(&etype); /*--- We need to ignore the 4 ints describing the nVar_Restart and nPoints, along with the string names of the variables. ---*/ @@ -3152,7 +3167,7 @@ void CSolver::Read_SU2_Restart_Binary(CGeometry *geometry, const CConfig *config for (auto iPoint_Global = 0ul; iPoint_Global < geometry->GetGlobal_nPointDomain(); ++iPoint_Global) { if (geometry->GetGlobal_to_Local_Point(iPoint_Global) > -1) { blocklen[counter] = nFields; - displace[counter] = iPoint_Global*nFields*sizeof(passivedouble); + displace[counter] = iPoint_Global*nFields*scalarSize; counter++; } } @@ -3167,10 +3182,10 @@ void CSolver::Read_SU2_Restart_Binary(CGeometry *geometry, const CConfig *config const auto partitioner = CLinearPartitioner(nPointFile,0); blocklen[0] = nFields*partitioner.GetSizeOnRank(rank); - displace[0] = nFields*partitioner.GetFirstIndexOnRank(rank)*sizeof(passivedouble);; + displace[0] = nFields*partitioner.GetFirstIndexOnRank(rank)*scalarSize; } - MPI_Type_create_hindexed(nBlock, blocklen, displace, MPI_DOUBLE, &filetype); + MPI_Type_create_hindexed(nBlock, blocklen, displace, etype, &filetype); MPI_Type_commit(&filetype); /*--- Set the view for the MPI file write, i.e., describe the location in @@ -3183,17 +3198,25 @@ void CSolver::Read_SU2_Restart_Binary(CGeometry *geometry, const CConfig *config const int bufSize = nBlock*blocklen[0]; Restart_Data.resize(bufSize); - /*--- Collective call for all ranks to read from their view simultaneously. ---*/ + /*--- Collective call for all ranks to read from their view simultaneously, + converting the data if the file precision does not match this build. ---*/ - MPI_File_read_all(fhw, Restart_Data.data(), bufSize, MPI_DOUBLE, &status); + if (scalarSize == static_cast(sizeof(passivedouble))) { + MPI_File_read_all(fhw, Restart_Data.data(), bufSize, etype, &status); + } else { + vector buffer(static_cast(bufSize)*scalarSize); + MPI_File_read_all(fhw, buffer.data(), bufSize, etype, &status); + SU2BinaryDataToPassive(buffer.data(), scalarSize, bufSize, Restart_Data.data()); + } /*--- All ranks close the file after writing. ---*/ MPI_File_close(&fhw); - /*--- Free the derived datatype and release temp memory. ---*/ + /*--- Free the derived datatypes and release temp memory. ---*/ MPI_Type_free(&filetype); + MPI_Type_free(&etype); delete [] blocklen; delete [] displace; From ce79e9378c505fb6526698ed9b2425d2ed315f63 Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:30:36 -0700 Subject: [PATCH 44/61] Make CUDA acceleration compatible with discrete adjoint (#2888) ## Proposed Changes Bit of a hack to make it compile, but I think it's ok since no adjoint code is done on GPU, just the linear systems in plain types. Also fixes an issue with adjoint Krylov and OpenMP. ## PR Checklist - [X] I am submitting my contribution to the develop branch. - [X] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [X] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [X] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [X] I have added a test case that demonstrates my contribution, if necessary. - [X] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --- Common/include/basic_types/codi_host_only.hpp | 49 +++++++++++++++++++ Common/include/code_config.hpp | 13 +++-- .../interface_interpolation/CMixingPlane.hpp | 2 +- Common/include/linear_algebra/CSysMatrix.hpp | 9 ++++ .../include/parallelization/omp_structure.hpp | 1 + .../interface_interpolation/CMixingPlane.cpp | 2 +- Common/src/linear_algebra/CSysMatrix.cpp | 13 +++++ Common/src/linear_algebra/CSysMatrixGPU.cu | 18 +++++++ Common/src/linear_algebra/CSysSolve.cpp | 23 +++++++-- Common/src/linear_algebra/CSysVectorGPU.cu | 3 ++ Common/src/linear_algebra/meson.build | 4 +- Common/src/meson.build | 18 ++++--- TestCases/hybrid_regression_AD.py | 24 +++++++++ TestCases/vandv/rans/30p30n/config_ad.cfg | 4 ++ 14 files changed, 162 insertions(+), 21 deletions(-) create mode 100644 Common/include/basic_types/codi_host_only.hpp diff --git a/Common/include/basic_types/codi_host_only.hpp b/Common/include/basic_types/codi_host_only.hpp new file mode 100644 index 000000000000..e7b6768da44a --- /dev/null +++ b/Common/include/basic_types/codi_host_only.hpp @@ -0,0 +1,49 @@ +/*! + * \file codi_host_only.hpp + * \brief Keeps CoDiPack out of nvcc's device pass. + * \author P. Gomes + * \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 . + */ + +#pragma once + +/*--- Must be the first include of every .cu, before anything can pull in codi.hpp. + * + * When __CUDA_ARCH__ is defined, i.e. only in the device pass, CoDiPack stamps + * "__device__ __host__" on every one of its functions. nvcc then has to generate device + * code for the entire tape, which does not work: the tape uses function-scope statics with + * dynamic initializers (illegal in device code), takes the address of host statics, and + * calls into std::map and std::bitset. The kernels never touch an active type, so none of + * that is wanted in the first place. + * + * CODI_INLINE expands CODI_CUDAFunctionAttributes at each declaration, so emptying the + * macro before CoDiPack is parsed leaves it host-only. Including the defining header first + * makes the copy in config.h a no-op (it is "#pragma once"), so this definition is the one + * that survives. The host pass is unaffected either way, it never defines __CUDA_ARCH__ + * and so already sees the same host-only declarations as the .cpp translation units. ---*/ +#if defined(CODI_REVERSE_TYPE) || defined(CODI_FORWARD_TYPE) +#include "codi/tools/cuda/cudaFunctionAttributes.hpp" + +#undef CODI_CUDAFunctionAttributes +#define CODI_CUDAFunctionAttributes +#endif diff --git a/Common/include/code_config.hpp b/Common/include/code_config.hpp index a28c346b651b..61d143f40a7b 100644 --- a/Common/include/code_config.hpp +++ b/Common/include/code_config.hpp @@ -103,13 +103,12 @@ FORCEINLINE Out su2staticcast_p(In ptr) { #define HAVE_OMP #endif -/*--- Detect whether the CUDA kernels are part of this build. The .cu translation units - * cannot be compiled with the CoDiPack defines (nvcc's device pass cannot parse the tape - * machinery), and an object compiled with a different definition of su2double must not be - * linked into an AD library. They are therefore only built into the primal libraries, and - * all device dispatch has to be compiled out of the AD builds, which HAVE_CUDA alone does - * not do because su2mixedfloat is a passive type there as well. ---*/ -#if defined(HAVE_CUDA) && !defined(CODI_REVERSE_TYPE) && !defined(CODI_FORWARD_TYPE) +/*--- Detect whether the CUDA kernels are part of this build. They work on su2mixedfloat + * and passivedouble, so reverse AD is fine: su2_gpu_capable_v is false for the active type + * and CSysMatrix stays on the host. Forward AD is not, there su2mixedfloat is + * su2double (see below) and the active type would reach the kernels. The .cu sources are + * compiled with the CoDiPack defines like everything else, see codi_host_only.hpp. ---*/ +#if defined(HAVE_CUDA) && !defined(CODI_FORWARD_TYPE) #define SU2_ENABLE_CUDA_KERNELS #endif diff --git a/Common/include/interface_interpolation/CMixingPlane.hpp b/Common/include/interface_interpolation/CMixingPlane.hpp index 4b0b256bb738..a706bf2fe786 100644 --- a/Common/include/interface_interpolation/CMixingPlane.hpp +++ b/Common/include/interface_interpolation/CMixingPlane.hpp @@ -9,7 +9,7 @@ * The SU2 Project is maintained by the SU2 Foundation * (http://su2foundation.org) * - * Copyright 2012-2025, SU2 Contributors (cf. AUTHORS.md) + * 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 diff --git a/Common/include/linear_algebra/CSysMatrix.hpp b/Common/include/linear_algebra/CSysMatrix.hpp index f35efcef415c..465ba7b576dd 100644 --- a/Common/include/linear_algebra/CSysMatrix.hpp +++ b/Common/include/linear_algebra/CSysMatrix.hpp @@ -381,6 +381,15 @@ class CSysMatrix { * allocated once). Each is captured once into a CUDA graph and replayed to remove * host-side launch overhead without changing the parallelization. ---*/ mutable struct CUgraphExec_st* ilu_build_graph_exec = nullptr; + + /*!< \brief Whether a build may refine the factors already on the device instead of computing + * them exactly. TransposeInPlace() clears it for good: from then on this matrix is used in + * both orientations, and the factors of one are a bad starting point for the other, which the + * ilu_gpu_sweeps colored sweeps cannot recover from. It is not restored after a build because + * the orientation flips again on the next one (and the solver refills the matrix in between + * without going through TransposeInPlace). Only the discrete adjoint transposes, so the primal + * keeps refining as before. */ + mutable bool ilu_can_refine = true; mutable struct CUgraphExec_st* precond_fwd_graph_exec = nullptr; // ILU or LU-SGS forward only mutable struct CUgraphExec_st* precond_bwd_graph_exec = nullptr; // LU-SGS backward only mutable const ScalarType* precond_fwd_graph_vec = nullptr; /*!< \brief Pointers the apply graph diff --git a/Common/include/parallelization/omp_structure.hpp b/Common/include/parallelization/omp_structure.hpp index 10bf9f7bca02..1763169bef27 100644 --- a/Common/include/parallelization/omp_structure.hpp +++ b/Common/include/parallelization/omp_structure.hpp @@ -134,6 +134,7 @@ void omp_finalize(); /*--- Convenience macros (do not use excessive nesting). ---*/ #define SU2_OMP_ATOMIC SU2_OMP(atomic) +#define SU2_OMP_ATOMIC_WRITE SU2_OMP(atomic write) #ifndef HAVE_OPDI diff --git a/Common/src/interface_interpolation/CMixingPlane.cpp b/Common/src/interface_interpolation/CMixingPlane.cpp index 7459498b27ec..d44b598c4aee 100644 --- a/Common/src/interface_interpolation/CMixingPlane.cpp +++ b/Common/src/interface_interpolation/CMixingPlane.cpp @@ -9,7 +9,7 @@ * The SU2 Project is maintained by the SU2 Foundation * (http://su2foundation.org) * - * Copyright 2012-2025, SU2 Contributors (cf. AUTHORS.md) + * 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 diff --git a/Common/src/linear_algebra/CSysMatrix.cpp b/Common/src/linear_algebra/CSysMatrix.cpp index a4d9fbb16127..2d77cc361fa0 100644 --- a/Common/src/linear_algebra/CSysMatrix.cpp +++ b/Common/src/linear_algebra/CSysMatrix.cpp @@ -1775,6 +1775,19 @@ void CSysMatrix::TransposeInPlace() { pastix_wrapper.SetTransposedSolve(); END_SU2_OMP_MASTER #endif + +#ifdef SU2_ENABLE_CUDA_KERNELS + if constexpr (su2_gpu_capable_v) { + if (useCuda) { + BEGIN_SU2_DEVICE_REGION { + HtDTransfer(); + /*--- The factors of one orientation are not a starting point for the other. ---*/ + ilu_can_refine = false; + } + END_SU2_DEVICE_REGION + } + } +#endif } template diff --git a/Common/src/linear_algebra/CSysMatrixGPU.cu b/Common/src/linear_algebra/CSysMatrixGPU.cu index 7e6cf83d1b89..50e113bc00a0 100644 --- a/Common/src/linear_algebra/CSysMatrixGPU.cu +++ b/Common/src/linear_algebra/CSysMatrixGPU.cu @@ -25,6 +25,9 @@ * License along with SU2. If not, see . */ +/*--- Must come first, see the file for why. ---*/ +#include "../../include/basic_types/codi_host_only.hpp" + #include #include #include @@ -657,6 +660,21 @@ void CSysMatrix::BuildILUPreconditionerGPU() { * change execution order relative to the rest of the (single-stream) solver. ---*/ if (aux_stream == nullptr) gpuErrChk(cudaStreamCreate(&aux_stream)); + /*--- Once the matrix is transposed the factors cannot be refined, see ilu_can_refine, so + * launch by levels instead of colors to eliminate the dependence on previous factors. ---*/ + if (!ilu_can_refine) { + for (auto level = 0ul; level + 1 < precond_level_ptr.size(); ++level) { + const auto begin = precond_level_ptr[level]; + const auto size = precond_level_ptr[level + 1] - begin; + if (size == 0) continue; + IluFactorColorKernel + <<>>(d_precond_level_idx, begin, size, nPointDomain, nVar, A, M); + } + gpuErrChk(cudaStreamSynchronize(aux_stream)); + gpuErrChk(cudaGetLastError()); + return; + } + /*--- The launch sequence (ilu_gpu_sweeps passes over all colors) is identical on every call: * the grid and block sizes only depend on the (fixed) sparsity pattern/coloring and the device * pointers are fixed members, allocated once. Capture it into a CUDA graph the first time and diff --git a/Common/src/linear_algebra/CSysSolve.cpp b/Common/src/linear_algebra/CSysSolve.cpp index 3c9176d87160..3a350aaa13ea 100644 --- a/Common/src/linear_algebra/CSysSolve.cpp +++ b/Common/src/linear_algebra/CSysSolve.cpp @@ -208,15 +208,17 @@ bool CSysSolve::ModGramSchmidt(bool shared_hsbg, int i, su2matrix::multiDot(w, i + 1, 1, w, i + 1); LinearCombination( - shared_hsbg, i + 1, w, [&h_i](int k) { return -h_i(0, k); }, w[i + 1], true); + false, i + 1, w, [&h_i](int k) { return -h_i(0, k); }, w[i + 1], true); if (i < 5) { for (int k = 0; k < i + 1; k++) SetHsbg(k, i, h_i(0, k)); } else { const auto& dh_i = CSysVector::multiDot(w, i + 1, 1, w, i + 1); LinearCombination( - shared_hsbg, i + 1, w, [&dh_i](int k) { return -dh_i(0, k); }, w[i + 1], true); + false, i + 1, w, [&dh_i](int k) { return -dh_i(0, k); }, w[i + 1], true); for (int k = 0; k < i + 1; k++) SetHsbg(k, i, h_i(0, k) + dh_i(0, k)); } @@ -548,6 +550,8 @@ unsigned long CSysSolve::FGMRES_LinSolver(const CSysVector::FGCRODR_LinSolverImpl(const CSysVector::FGCRODR_LinSolverImpl(const CSysVector::multiDot(V, i0, n, W, k); END_SU2_OMP_PARALLEL } else { @@ -1572,7 +1580,10 @@ unsigned long CSysSolve::Solve(CSysMatrix& Jacobian, con break; case LU_SGS: case Q_LU_SGS: - /*--- Nothing to build (transpose path not supported for Q_LU_SGS, see CSysMatrix::Initialize). ---*/ + /*--- Nothing to build on the host, but the device keeps the inverted diagonal blocks + * and those have to follow the transpose (no-op without CUDA). Transpose path not + * supported for Q_LU_SGS, see CSysMatrix::Initialize. ---*/ + if (RequiresTranspose) Jacobian.BuildLU_SGSPreconditioner(); break; case PASTIX_ILU: case PASTIX_LU_P: @@ -1659,6 +1670,10 @@ unsigned long CSysSolve::Solve_b(CSysMatrix& Jacobian, c normal_prec->Build(); } + /*--- The vectors are already of the solver type here, but they still have to cross the + * bus: the matrix and preconditioner operations dispatch to the device on their own. ---*/ + HandleTemporariesIn(LinSysRes, LinSysSol, config->GetCUDA()); + CPreconditioner* nested_prec = nullptr; if (nested) { auto f = [&](const CSysVector& u, CSysVector& v) { @@ -1718,6 +1733,8 @@ unsigned long CSysSolve::Solve_b(CSysMatrix& Jacobian, c break; } + HandleTemporariesOut(LinSysSol, config->GetCUDA()); + delete normal_prec; delete nested_prec; diff --git a/Common/src/linear_algebra/CSysVectorGPU.cu b/Common/src/linear_algebra/CSysVectorGPU.cu index 81e094cda94c..ef699893423e 100644 --- a/Common/src/linear_algebra/CSysVectorGPU.cu +++ b/Common/src/linear_algebra/CSysVectorGPU.cu @@ -25,6 +25,9 @@ * License along with SU2. If not, see . */ +/*--- Must come first, see the file for why. ---*/ +#include "../../include/basic_types/codi_host_only.hpp" + #include "../../include/linear_algebra/CSysVector.hpp" #include "../../include/linear_algebra/GPUComms.cuh" #include diff --git a/Common/src/linear_algebra/meson.build b/Common/src/linear_algebra/meson.build index 3b84b2373a55..c86898903bdf 100644 --- a/Common/src/linear_algebra/meson.build +++ b/Common/src/linear_algebra/meson.build @@ -6,7 +6,7 @@ common_src += files(['CSysSolve_b.cpp', 'blas_structure.cpp']) if get_option('enable-cuda') - # Kept apart from common_src: these are compiled without the CoDiPack defines and so - # must only go into the primal library, see common_cuda_src in Common/src/meson.build. + # Kept apart from common_src because nvcc takes a different set of flags, see + # common_cuda_src in Common/src/meson.build. common_cuda_src += files(['CSysMatrixGPU.cu', 'CSysVectorGPU.cu']) endif diff --git a/Common/src/meson.build b/Common/src/meson.build index c34b014ab425..52b02348a95d 100644 --- a/Common/src/meson.build +++ b/Common/src/meson.build @@ -6,11 +6,11 @@ common_src =files(['graph_coloring_structure.cpp', '../include/parallelization/mpi_structure.cpp', '../include/parallelization/omp_structure.cpp']) -# nvcc cannot parse CoDiPack's tape machinery in the device pass, so the CUDA sources are -# compiled without the CODI_REVERSE_TYPE/CODI_FORWARD_TYPE defines. That gives them a -# different su2double (and hence a different layout for CConfig, CGeometry, ...) than the -# AD libraries, so they are collected separately and only linked into the primal library. -# The AD builds compile the device dispatch out entirely, see SU2_ENABLE_CUDA_KERNELS. +# The CUDA sources are kept in their own list because nvcc only accepts the defines out of +# su2_cpp_args, not the warning and tuning flags meant for the host compiler. They go into +# the primal and the reverse AD libraries with the same defines as the .cpp sources next to +# them, so su2double means the same thing everywhere; codi_host_only.hpp is what keeps +# CoDiPack out of the device pass. Forward AD gets no kernels, see SU2_ENABLE_CUDA_KERNELS. common_cuda_src = [] common_cuda_cpp_args = [] foreach arg : su2_cpp_args @@ -52,11 +52,15 @@ endif if get_option('enable-autodiff') + # codi_rev_args is only defined for AD builds, and it is all defines, which nvcc accepts. + common_cuda_rev_args = common_cuda_cpp_args + codi_rev_args + commonAD = static_library('SU2CommonAD', - common_src, + common_src, common_cuda_src, install : false, dependencies : [su2_deps, codi_dep], - cpp_args: [default_warning_flags, su2_cpp_args, codi_rev_args]) + cpp_args: [default_warning_flags, su2_cpp_args, codi_rev_args], + cuda_args: common_cuda_rev_args) commonAD_dep = declare_dependency(link_with: commonAD, include_directories : common_include) diff --git a/TestCases/hybrid_regression_AD.py b/TestCases/hybrid_regression_AD.py index 9440e408ab15..04c46c2392cb 100644 --- a/TestCases/hybrid_regression_AD.py +++ b/TestCases/hybrid_regression_AD.py @@ -28,6 +28,7 @@ # make print(*args) function available in PY2.6+, does'nt work on PY < 2.6 from __future__ import print_function +import shutil import sys from TestCase import TestCase from TestCase import parse_args @@ -219,6 +220,29 @@ def main(): pass_list = [ test.run_test(args.tsan) for test in test_list ] + ########################################## + ### Thread sanitizer only ### + ########################################## + + # Sanitizer coverage for the nested parallel logic in adjoint Krylov mode. + # The case is not run to convergence here because the hybrid binary uses + # mixed precision which does not work very well with the adjoint Krylov mode. + if args.tsan: + shutil.copy("vandv/rans/30p30n/solution.dat", "vandv/rans/30p30n/solution_0.dat") + + discadj_30p30n_krylov = TestCase('discadj_30p30n_krylov') + discadj_30p30n_krylov.cfg_dir = "vandv/rans/30p30n" + discadj_30p30n_krylov.cfg_file = "config_ad.cfg" + discadj_30p30n_krylov.multizone = True + discadj_30p30n_krylov.test_iter = 0 + discadj_30p30n_krylov.test_vals = [-2.101502, -1.334769, -1.144549, -1.970734, 0.150069, + -1.137743, -2.897633, 0.077908, 10.334000] + discadj_30p30n_krylov.command = TestCase.Command(exec = "SU2_CFD_AD", param = "-t 2") + discadj_30p30n_krylov.timeout = 1600 + discadj_30p30n_krylov.enabled_with_regular = False + test_list.append(discadj_30p30n_krylov) + pass_list.append(discadj_30p30n_krylov.run_test(args.tsan)) + ################################### ### Python Wrapper ### ################################### diff --git a/TestCases/vandv/rans/30p30n/config_ad.cfg b/TestCases/vandv/rans/30p30n/config_ad.cfg index 5892f8d5520a..a3b50783f83a 100644 --- a/TestCases/vandv/rans/30p30n/config_ad.cfg +++ b/TestCases/vandv/rans/30p30n/config_ad.cfg @@ -71,6 +71,10 @@ DISCADJ_LIN_PREC= ILU LINEAR_SOLVER_ERROR= 1e-30 LINEAR_SOLVER_ITER= 20 LINEAR_SOLVER_SMOOTHER_RELAXATION= 0.6 +% Helpful performance settings when using OpenMP or OpenMP + CUDA +% LINEAR_SOLVER_ILU_LEVEL_SCHEDULING= YES +% RCM_NUM_SEEDS= 32 +% ENABLE_CUDA= YES % MGLEVEL= 0 % From 2b3c6a73ac2081811342e13e85533ba0e85d9cff Mon Sep 17 00:00:00 2001 From: bellonarts Date: Wed, 9 Sep 2026 00:31:01 -0400 Subject: [PATCH 45/61] Fix misleading "All convergence criteria satisfied." message on interrupted runs (#2881) ## Proposed Changes Interrupt-driven termination is not limited to a user's Ctrl-C. SIGTERM is the normal way batch schedulers terminate a job at its wall-clock limit: SLURM sends SIGTERM when a job reaches its `TimeLimit` and on `scancel` (waiting `KillWait` before SIGKILL), and PBS/Torque and LSF behave equivalently. Today every SU2 job that exhausts its allocation on a cluster ends with a log stating "All convergence criteria satisfied." directly above a Solver Exit table whose every criterion reads "No". Any parameter sweep or optimization loop that classifies runs by scraping that banner silently admits timed-out, unconverged runs as converged, and a human skimming the log is misled the same way. That population is far larger than the manual-interrupt case. Mechanism: on SIGTERM the signal handler sets `STOP`, and `COutput::ConvergenceMonitoring` forces `convergence = true` so the run stops and saves. Both `CSinglezoneDriver::Monitor` and `CMultizoneDriver::Monitor` then read that flag as `InnerConvergence` and print the satisfied banner. Fix: a new `convergenceInterrupted` member of `COutput` records that the stop came from the interrupt path rather than from the criteria. It is propagated across ranks in the existing convergence Allreduce as a count-2 element-wise `MPI_MAX` (SU2's serial MPI stub does not define `MPI_BOR`, and the AD layer maps only SUM/MIN/MAX/PROD), exposed through `GetConvergenceInterrupted()`, and both drivers print ``` Interrupt signal received, exiting before the convergence criteria were satisfied. ``` on that path. Stop-and-save behavior, exit code 0, and the genuine-convergence and maximum-iteration messages are unchanged. Files: `SU2_CFD/include/output/COutput.hpp`, `SU2_CFD/src/output/COutput.cpp`, `SU2_CFD/src/drivers/CSinglezoneDriver.cpp`, `SU2_CFD/src/drivers/CMultizoneDriver.cpp` (+29/-8). Verification (QuickStart `inv_NACA0012.cfg`, serial release build of develop `07aa46b1`): - SIGTERM sent to the exact `SU2_CFD` process about 8 s into the run: the new interrupt message is printed, the satisfied banner does not appear, restart and solution files are written, exit code 0. - Run to genuine convergence and run to maximum iterations: output byte-identical to the unpatched build (checked when the change was first made on develop `81ce6a68f9`). - Touched files re-checked with `-Wall -Wextra`: no new warnings. ## Related Work The defect is present at v8.5.0 and at the current develop tip. I found no existing upstream issue or PR that addresses it. The change is independent of the other NEMO-related PRs from the same qualification campaign; it touches only the output and driver exit path. ## PR Checklist *Put an X by all that apply. You can fill this out after submitting the PR. If you have any questions, don't hesitate to ask! We want to help. These are a guide for you to know what the reviewers will be looking for in your contribution.* - [x] I am submitting my contribution to the develop branch. - [x] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [x] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [x] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [ ] I have added a test case that demonstrates my contribution, if necessary. - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. Co-authored-by: Claude Fable 5.1 --- SU2_CFD/include/output/COutput.hpp | 7 +++++++ SU2_CFD/src/drivers/CMultizoneDriver.cpp | 4 +++- SU2_CFD/src/drivers/CSinglezoneDriver.cpp | 4 +++- SU2_CFD/src/output/COutput.cpp | 22 ++++++++++++++++------ 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/SU2_CFD/include/output/COutput.hpp b/SU2_CFD/include/output/COutput.hpp index ea47413ae8ac..29032b003026 100644 --- a/SU2_CFD/include/output/COutput.hpp +++ b/SU2_CFD/include/output/COutput.hpp @@ -321,6 +321,7 @@ class COutput { vector oldFunc, /*!< \brief Old value of the coefficient. */ newFunc; /*!< \brief Current value of the coefficient. */ bool convergence; /*!< \brief To indicate if the solver has converged or not. */ + bool convergenceInterrupted; /*!< \brief To indicate that the exit was forced by an interrupt signal instead of the convergence criteria. */ su2double initResidual; /*!< \brief Initial value of the residual to evaluate the convergence level. */ vector convFields; /*!< \brief Name of the field to be monitored for convergence. */ unsigned long convergenceStartIter = 0; /*!< \brief Iteration the convergence history is counted from. */ @@ -597,6 +598,12 @@ class COutput { */ bool GetConvergence() const {return convergence;} + /*! + * \brief Get whether the exit was forced by an interrupt signal (e.g. SIGTERM) instead of the convergence criteria. + * \return Boolean indicating whether an interrupt signal forced the exit. + */ + bool GetConvergenceInterrupted() const {return convergenceInterrupted;} + /*! * \brief Set the value of the convergence flag. * \param[in] conv - New value of the convergence flag. diff --git a/SU2_CFD/src/drivers/CMultizoneDriver.cpp b/SU2_CFD/src/drivers/CMultizoneDriver.cpp index aa7422f56505..979efe706d56 100644 --- a/SU2_CFD/src/drivers/CMultizoneDriver.cpp +++ b/SU2_CFD/src/drivers/CMultizoneDriver.cpp @@ -650,7 +650,9 @@ bool CMultizoneDriver::Monitor(unsigned long TimeIter) { if ((MaxIterationsReached || InnerConvergence) && (rank == MASTER_NODE)) { cout << "\n----------------------------- Solver Exit -------------------------------" << endl; - if (InnerConvergence) cout << "All convergence criteria satisfied." << endl; + if (driver_output->GetConvergenceInterrupted()) + cout << "Interrupt signal received, exiting before the convergence criteria were satisfied." << endl; + else if (InnerConvergence) cout << "All convergence criteria satisfied." << endl; else cout << "\nMaximum number of iterations reached (OUTER_ITER = " << OuterIter+1 << ") before convergence." << endl; driver_output->PrintConvergenceSummary(); cout << "-------------------------------------------------------------------------" << endl; diff --git a/SU2_CFD/src/drivers/CSinglezoneDriver.cpp b/SU2_CFD/src/drivers/CSinglezoneDriver.cpp index fa6e5ac30060..b9ecf8cfef0b 100644 --- a/SU2_CFD/src/drivers/CSinglezoneDriver.cpp +++ b/SU2_CFD/src/drivers/CSinglezoneDriver.cpp @@ -278,7 +278,9 @@ bool CSinglezoneDriver::Monitor(unsigned long TimeIter){ if ((MaxIterationsReached || InnerConvergence) && (rank == MASTER_NODE)) { cout << "\n----------------------------- Solver Exit -------------------------------" << endl; - if (InnerConvergence) cout << "All convergence criteria satisfied." << endl; + if (output_container[ZONE_0]->GetConvergenceInterrupted()) + cout << "Interrupt signal received, exiting before the convergence criteria were satisfied." << endl; + else if (InnerConvergence) cout << "All convergence criteria satisfied." << endl; else cout << "\nMaximum number of iterations reached (ITER = " << nInnerIter << ") before convergence." << endl; output_container[ZONE_0]->PrintConvergenceSummary(); cout << "-------------------------------------------------------------------------" << endl; diff --git a/SU2_CFD/src/output/COutput.cpp b/SU2_CFD/src/output/COutput.cpp index 06c4272f9caa..c63857615b42 100644 --- a/SU2_CFD/src/output/COutput.cpp +++ b/SU2_CFD/src/output/COutput.cpp @@ -130,6 +130,7 @@ COutput::COutput(const CConfig *config, unsigned short ndim, bool fem_output): cauchySerie = vector>(convFields.size(), vector(nCauchy_Elems, 0.0)); cauchyValue = 0.0; convergence = false; + convergenceInterrupted = false; /*--- Initialize time convergence monitoring structure ---*/ @@ -1018,15 +1019,24 @@ bool COutput::ConvergenceMonitoring(CConfig *config, unsigned long Iteration) { if (convFields.empty() || Iteration < config->GetStartConv_Iter()) convergence = false; - /*--- If a SIGTERM signal is sent to one of the processes, we set convergence to true. ---*/ - if (STOP) convergence = true; + /*--- If a SIGTERM signal is sent to one of the processes, we set convergence to true so the + * solver stops and saves the solution, but remember that the exit was forced by the signal + * rather than by the convergence criteria so the exit message stays truthful. ---*/ + if (STOP) { + if (!convergence) convergenceInterrupted = true; + convergence = true; + } - /*--- Apply the same convergence criteria to all processors. ---*/ + /*--- Apply the same convergence criteria to all processors, and propagate an + * interrupt received on any rank. ---*/ - unsigned short local = convergence, global = 0; + unsigned short local[2] = {static_cast(convergence), + static_cast(convergenceInterrupted)}; + unsigned short global[2] = {0, 0}; - SU2_MPI::Allreduce(&local, &global, 1, MPI_UNSIGNED_SHORT, MPI_MAX, SU2_MPI::GetComm()); - convergence = global > 0; + SU2_MPI::Allreduce(local, global, 2, MPI_UNSIGNED_SHORT, MPI_MAX, SU2_MPI::GetComm()); + convergence = global[0] > 0; + convergenceInterrupted = global[1] > 0; return convergence; } From e2aa1807a67ffa08aeafda66cf70ded485f68333 Mon Sep 17 00:00:00 2001 From: Josh Kelly <81244680+joshkellyjak@users.noreply.github.com> Date: Thu, 10 Sep 2026 05:51:23 +0200 Subject: [PATCH 46/61] Fix ASCII compact restarts (#2891) ## Proposed Changes `CSU2FileWriter` takes its header from `dataSorter->GetRequiredFieldNames()` but reads data columns by positional index, so the sorter it's handed must be the one whose layout those names describe. The `RESTART_BINARY` branch directly below already does this correctly with `volumeDataSorterCompact` but for ASCII restarts where the user has specified `VOLUME_OUTPUT` fields outside the compact set, the columns become shifted and the outputted values are wrong. ## Related Work N/A ## PR Checklist *Put an X by all that apply. You can fill this out after submitting the PR. If you have any questions, don't hesitate to ask! We want to help. These are a guide for you to know what the reviewers will be looking for in your contribution.* - [X] I am submitting my contribution to the develop branch. - [X] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [X] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [X] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [X] I have added a test case that demonstrates my contribution, if necessary. - [X] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --------- Co-authored-by: Josh Kelly --- SU2_CFD/src/output/COutput.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/SU2_CFD/src/output/COutput.cpp b/SU2_CFD/src/output/COutput.cpp index c63857615b42..a376483cee59 100644 --- a/SU2_CFD/src/output/COutput.cpp +++ b/SU2_CFD/src/output/COutput.cpp @@ -433,12 +433,15 @@ void COutput::WriteToFile(CConfig *config, CGeometry *geometry, OUTPUT_TYPE form if (!config->GetWrt_Restart_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); - /*--- If we have compact restarts, we use only the required fields. ---*/ - if (config->GetWrt_Restart_Compact()) - volumeDataSorter->SetRequiredFieldNames(requiredVolumeFieldNames); - LogOutputFiles("SU2 ASCII restart"); - fileWriter = new CSU2FileWriter(volumeDataSorter); + + if (config->GetWrt_Restart_Compact()) { + /*--- If we have compact restarts, we use only the required fields. ---*/ + volumeDataSorterCompact->SetRequiredFieldNames(requiredVolumeFieldNames); + fileWriter = new CSU2FileWriter(volumeDataSorterCompact); + } else { + fileWriter = new CSU2FileWriter(volumeDataSorter); + } break; From 1a1b556b09af13ca56a442006f0c30f948960b79 Mon Sep 17 00:00:00 2001 From: Evert Bunschoten <38651601+EvertBunschoten@users.noreply.github.com> Date: Fri, 11 Sep 2026 09:18:58 +0200 Subject: [PATCH 47/61] Improved combustion solver robustness for coarse grids (#2733) Simulations of flames in large domains is difficult because volumetric source terms blow up in large cells, causing the species solver to diverge. I implemented a damping term which scales the species source terms and diffusion in cells with a length scale above a user-defined threshold. This allows for the flame to propagate through regions of the domain with large cells (such as in the far-field) without the solver diverging, while the source terms in refined regions remain unaffected. Co-authored-by: Nijso --- Common/include/option_structure.hpp | 1 + Common/src/CConfig.cpp | 3 + SU2_CFD/include/solvers/CSolver.hpp | 6 + .../solvers/CSpeciesFlameletSolver.hpp | 28 +++- SU2_CFD/src/output/CFlowOutput.cpp | 5 + .../src/solvers/CSpeciesFlameletSolver.cpp | 141 ++++++++++++++++-- config_template.cfg | 11 +- 7 files changed, 178 insertions(+), 17 deletions(-) diff --git a/Common/include/option_structure.hpp b/Common/include/option_structure.hpp index 20ed826c85ea..e74b1ac9fcac 100644 --- a/Common/include/option_structure.hpp +++ b/Common/include/option_structure.hpp @@ -1562,6 +1562,7 @@ struct FluidFlamelet_ParsedOptions { su2double* spark_reaction_rates; /*!< \brief Source terms for flamelet spark ignition option. */ unsigned short nspark; /*!< \brief Number of source terms for spark initialization. */ bool preferential_diffusion = false; /*!< \brief Preferential diffusion physics for flamelet solver.*/ + bool thickenedflame_correction{true}; /*!< \brief Thickened flame correction. */ su2double Flame_T_ignition = 5000; /*!< \brief Ignition temperature for the flame, used for initialization. */ }; diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index 4a2f292bd614..8ee96abb5bc9 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -1488,6 +1488,9 @@ void CConfig::SetConfig_Options() { /*!\brief SPARK_REACTION_RATES \n DESCRIPTION: Net source term values applied to species within spark area during spark ignition. \ingroup Config*/ addDoubleListOption("SPARK_REACTION_RATES", flamelet_ParsedOptions.nspark, flamelet_ParsedOptions.spark_reaction_rates); + /*!\brief THICKENED_FLAME_CORRECTION \n DESCRIPTION: Coarse grid correction for source terms and diffusive fluxes in reacting flows. \ingroup Config*/ + addBoolOption("THICKENED_FLAME_CORRECTION", flamelet_ParsedOptions.thickenedflame_correction, true); + /*!\brief FLAME_INIT_IGNITION \n DESCRIPTION: Ignition temperature for the flame initialization \ingroup Config*/ addDoubleOption("FLAME_INIT_IGNITION", flamelet_ParsedOptions.Flame_T_ignition, 5000.0); diff --git a/SU2_CFD/include/solvers/CSolver.hpp b/SU2_CFD/include/solvers/CSolver.hpp index 863eb77b49f9..11e6655c18cb 100644 --- a/SU2_CFD/include/solvers/CSolver.hpp +++ b/SU2_CFD/include/solvers/CSolver.hpp @@ -585,6 +585,12 @@ class CSolver { */ inline virtual void SetPrimitive_Limiter(CGeometry *geometry, const CConfig *config) { } + /*! + * \brief A virtual member. + * \return flame thickness value. + */ + virtual su2double GetFlameThickness() const {return 1.0;} + /*! * \brief Compute the projection of a variable for MUSCL reconstruction. * \note The result should be halved when added to i (or subtracted from j). diff --git a/SU2_CFD/include/solvers/CSpeciesFlameletSolver.hpp b/SU2_CFD/include/solvers/CSpeciesFlameletSolver.hpp index 6b9c45172985..b41cf336a29f 100644 --- a/SU2_CFD/include/solvers/CSpeciesFlameletSolver.hpp +++ b/SU2_CFD/include/solvers/CSpeciesFlameletSolver.hpp @@ -37,6 +37,9 @@ */ class CSpeciesFlameletSolver final : public CSpeciesSolver { private: + const su2double default_flame_thickness{1.0}; + su2double global_flame_thickness; + bool calc_flame_thickness{false}; FluidFlamelet_ParsedOptions flamelet_config_options; bool include_mixture_fraction = false; /*!< \brief include mixture fraction as a controlling variable. */ /*! @@ -82,10 +85,11 @@ class CSpeciesFlameletSolver final : public CSpeciesSolver { * \param[in] iPoint - node ID. * \param[in] scalars - local scalar solution. * \param[in] table_source_names - variable names of scalar source terms. + * \param[in] F - flame thickness correction factor. * \return - within manifold bounds (0) or outside manifold bounds (1). */ unsigned long SetScalarSources(const CConfig* config, CFluidModel* fluid_model_local, unsigned long iPoint, - const vector& scalars); + const vector& scalars, const su2double F=1.0); /*! * \brief Retrieve passive look-up data from manifold. @@ -106,6 +110,22 @@ class CSpeciesFlameletSolver final : public CSpeciesSolver { */ unsigned long SetPreferentialDiffusionScalars(CFluidModel* fluid_model_local, unsigned long iPoint, const vector& scalars); + + /*! + * \brief Calculate correction factor for flame propagation on coarse grids. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] iPoint - node ID. + * \return - flame thickness correction factor. + */ + su2double ThickenedFlameCorrection(const CGeometry* geometry, unsigned long iPoint) const; + + /*! + * \brief Approximate the minimum flame thickness value used for the thickened flame model. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container vector with all the solutions. + * \return - approximate flame thickness value. + */ + su2double GetOverallFlameThickness(CGeometry* geometry, CSolver** solver_container) const; public: /*! @@ -211,4 +231,10 @@ class CSpeciesFlameletSolver final : public CSpeciesSolver { */ void Viscous_Residual(const unsigned long iEdge, const CGeometry* geometry, CSolver** solver_container, CNumerics* numerics, const CConfig* config) final; + + /*! + * \brief Obtain the overall flame thickness value. + * \return flame thickness value. + */ + su2double GetFlameThickness() const override {return global_flame_thickness;} }; diff --git a/SU2_CFD/src/output/CFlowOutput.cpp b/SU2_CFD/src/output/CFlowOutput.cpp index 6abf8c9b5a93..2ac2693b9952 100644 --- a/SU2_CFD/src/output/CFlowOutput.cpp +++ b/SU2_CFD/src/output/CFlowOutput.cpp @@ -1075,6 +1075,8 @@ void CFlowOutput::AddHistoryOutputFields_ScalarRMS_RES(const CConfig* config) { const auto& CV_name = flamelet_config_options.controlling_variable_names[iCV]; AddHistoryOutput("RMS_"+CV_name, "rms["+CV_name+"]",ScreenOutputFormat::FIXED, "RMS_RES", "Root-mean squared residual of " + CV_name + " controlling variable equation.", HistoryFieldType::RESIDUAL); } + if (flamelet_config_options.thickenedflame_correction) + AddHistoryOutput("THICKNESS","flamethickness",ScreenOutputFormat::FIXED, "FLAME_THICKNESS", "Flame thickness used for thickened flame correction model.", HistoryFieldType::COEFFICIENT); /*--- auxiliary species transport ---*/ for (auto i_scalar = 0u; i_scalar < flamelet_config_options.n_user_scalars; i_scalar++){ @@ -1339,6 +1341,9 @@ void CFlowOutput::LoadHistoryDataScalar(const CConfig* config, const CSolver* co } } + if (flamelet_config_options.thickenedflame_correction) + SetHistoryOutputValue("THICKNESS", solver[SPECIES_SOL]->GetFlameThickness()); + SetHistoryOutputValue("LINSOL_ITER_FLAMELET", solver[SPECIES_SOL]->GetIterLinSolver()); SetHistoryOutputValue("LINSOL_RESIDUAL_FLAMELET", log10(solver[SPECIES_SOL]->GetResLinSolver())); } diff --git a/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp b/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp index fee5a0702e2b..25f187c3fd9e 100644 --- a/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp +++ b/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp @@ -40,6 +40,8 @@ CSpeciesFlameletSolver::CSpeciesFlameletSolver(CGeometry* geometry, CConfig* con /*--- Retrieve options from config. ---*/ flamelet_config_options = config->GetFlameletParsedOptions(); + global_flame_thickness = default_flame_thickness; + calc_flame_thickness = flamelet_config_options.thickenedflame_correction; /*--- Dimension of the problem. ---*/ nVar = flamelet_config_options.n_scalars; @@ -66,6 +68,10 @@ CSpeciesFlameletSolver::CSpeciesFlameletSolver(CGeometry* geometry, CConfig* con /*--- Add the solver name. ---*/ SolverName = "FLAMELET"; + + if (calc_flame_thickness && rank==MASTER_NODE) { + cout << "Applying thickened flame source and diffusion correction." << endl; + } } void CSpeciesFlameletSolver::Preprocessing(CGeometry* geometry, CSolver** solver_container, CConfig* config, @@ -74,34 +80,58 @@ void CSpeciesFlameletSolver::Preprocessing(CGeometry* geometry, CSolver** solver SU2_ZONE_SCOPED unsigned long n_not_in_domain_local = 0, n_not_in_domain_global = 0; vector scalars_vector(nVar); + unsigned long spark_iter_start, spark_duration; bool ignition = false; auto* flowNodes = su2staticcast_p(solver_container[FLOW_SOL]->GetNodes()); /*--- Retrieve spark ignition parameters for spark-type ignition. ---*/ + unsigned long iter; + if (config->GetMultizone_Problem()) { + iter = config->GetOuterIter(); + } else if (config->GetTime_Domain()) { + iter = config->GetTimeIter(); + } else { + iter = config->GetInnerIter(); + } if ((flamelet_config_options.ignition_method == FLAMELET_INIT_TYPE::SPARK)) { auto spark_init = flamelet_config_options.spark_init; spark_iter_start = ceil(spark_init[4]); spark_duration = ceil(spark_init[5]); - unsigned long iter; - if (config->GetTime_Domain()) { - iter = config->GetTimeIter(); // Use time step counter for unsteady problems + + ignition = ((iter >= spark_iter_start) && (iter <= (spark_iter_start + spark_duration))); + } + SU2_OMP_SAFE_GLOBAL_ACCESS(config->SetGlobalParam(config->GetKind_Solver(), RunTime_EqSystem);) + + /* Update global flame thickness value. */ + if (calc_flame_thickness) { + su2double calc_thickness = GetOverallFlameThickness(geometry, solver_container); + su2double test_thickness = min(default_flame_thickness, calc_thickness); + if (test_thickness < global_flame_thickness) { + global_flame_thickness = test_thickness; } else { - iter = config->GetMultizone_Problem() ? config->GetOuterIter() : config->GetInnerIter(); + global_flame_thickness = 0.95*global_flame_thickness + 0.05*test_thickness; } - ignition = ((iter >= spark_iter_start) && (iter <= (spark_iter_start + spark_duration))); } - SU2_OMP_SAFE_GLOBAL_ACCESS(config->SetGlobalParam(config->GetKind_Solver(), RunTime_EqSystem);) + /* Flame thickness correction factors */ + su2double F{1.0}, F_source{1.0}; SU2_OMP_FOR_STAT(omp_chunk_size) for (auto i_point = 0u; i_point < nPoint; i_point++) { CFluidModel* fluid_model_local = solver_container[FLOW_SOL]->GetFluidModel(); su2double* scalars = nodes->GetSolution(i_point); + + /*--- Calculate correction factor for flame propagation on coarse grids. ---*/ + if (calc_flame_thickness) { + F = ThickenedFlameCorrection(geometry, i_point); + F_source = 1.0 / F; + } + for (auto iVar = 0u; iVar < nVar; iVar++) scalars_vector[iVar] = scalars[iVar]; - /*--- Compute total source terms from the production and consumption. ---*/ - unsigned long misses = SetScalarSources(config, fluid_model_local, i_point, scalars_vector); + /*--- Only apply thickened flame correction factor to sources for steady problems. ---*/ + unsigned long misses = SetScalarSources(config, fluid_model_local, i_point, scalars_vector, F_source); if (ignition) { /*--- Apply source terms within spark radius. ---*/ @@ -113,7 +143,7 @@ void CSpeciesFlameletSolver::Preprocessing(CGeometry* geometry, CSolver** solver /*--- Add spark reaction rates to the sources that were just set by SetScalarSources ---*/ const su2double* current_sources = nodes->GetScalarSources(i_point); for (auto iVar = 0u; iVar < nVar; iVar++) { - nodes->SetScalarSource(i_point, iVar, current_sources[iVar] + flamelet_config_options.spark_reaction_rates[iVar]); + nodes->SetScalarSource(i_point, iVar, current_sources[iVar] + F_source * flamelet_config_options.spark_reaction_rates[iVar]); } } } @@ -126,9 +156,9 @@ void CSpeciesFlameletSolver::Preprocessing(CGeometry* geometry, CSolver** solver /*--- Set mass diffusivity based on thermodynamic state. ---*/ auto T = flowNodes->GetTemperature(i_point); fluid_model_local->SetTDState_T(T, scalars); - /*--- set the diffusivity in the fluid model to the diffusivity obtained from the lookup table ---*/ + /*--- set the diffusivity in the fluid model to the diffusivity obtained from the lookup table, multiplied by flame thickness correction factor ---*/ for (auto i_scalar = 0u; i_scalar < nVar; ++i_scalar) { - nodes->SetDiffusivity(i_point, fluid_model_local->GetMassDiffusivity(i_scalar), i_scalar); + nodes->SetDiffusivity(i_point, F * (fluid_model_local->GetMassDiffusivity(i_scalar)), i_scalar); } /*--- Obtain preferential diffusion scalar values. ---*/ @@ -226,7 +256,6 @@ void CSpeciesFlameletSolver::SetInitialCondition(CGeometry** geometry, CSolver** for (unsigned long i_mesh = 0; i_mesh <= config->GetnMGLevels(); i_mesh++) { fluid_model_local = solver_container[i_mesh][FLOW_SOL]->GetFluidModel(); - for (auto iVar = 0u; iVar < nVar; iVar++) scalar_init[iVar] = config->GetSpecies_Init()[iVar]; /*--- Set enthalpy based on initial temperature and scalars. ---*/ @@ -242,7 +271,6 @@ void CSpeciesFlameletSolver::SetInitialCondition(CGeometry** geometry, CSolver** auto coords = geometry[i_mesh]->nodes->GetCoord(i_point); if (flame_front_ignition) { - /*--- Determine if point is above or below the plane, assuming the normal is pointing towards the burned region. ---*/ point_loc = 0.0; @@ -613,7 +641,7 @@ void CSpeciesFlameletSolver::BC_ConjugateHeat_Interface(CGeometry* geometry, CSo } unsigned long CSpeciesFlameletSolver::SetScalarSources(const CConfig* config, CFluidModel* fluid_model_local, - unsigned long iPoint, const vector& scalars) { + unsigned long iPoint, const vector& scalars, const su2double F) { SU2_ZONE_SCOPED /*--- Compute total source terms from the production and consumption. ---*/ @@ -638,8 +666,9 @@ unsigned long CSpeciesFlameletSolver::SetScalarSources(const CConfig* config, CF /*--- Store the analytic Jacobian dS_aux/dY_aux = source_cons for implicit treatment. ---*/ static_cast(nodes)->SetAuxSourceCons(iPoint, i_aux, source_cons); } + /*--- Source term is divided by flame thickness correction factor to improve stability on coarse grids. ---*/ for (auto i_scalar = 0u; i_scalar < nVar; i_scalar++) - nodes->SetScalarSource(iPoint, i_scalar, source_scalar[i_scalar]); + nodes->SetScalarSource(iPoint, i_scalar, F*source_scalar[i_scalar]); return misses; } @@ -915,3 +944,85 @@ su2double CSpeciesFlameletSolver::GetBurntProgressVariable(CFluidModel* fluid_mo } return pv_burnt; } + + +su2double CSpeciesFlameletSolver::ThickenedFlameCorrection(const CGeometry* geometry, unsigned long iPoint) const { + su2double F{1.0}; + if (fabs(global_flame_thickness - default_flame_thickness) > EPS * max(1.0, fabs(default_flame_thickness))) { + su2double max_flame_vol = pow(global_flame_thickness, nDim); + F = max(1.0, geometry->nodes->GetVolume(iPoint) / max_flame_vol); + } + return F; +} + +su2double CSpeciesFlameletSolver::GetOverallFlameThickness(CGeometry* geometry, CSolver** solver_container) const { + SU2_ZONE_SCOPED + + const CFlowVariable* flowNodes = su2staticcast_p(solver_container[FLOW_SOL]->GetNodes()); + su2double pvmax_local{-1e3}, pvmin_local{1e3}, gradpv_local{0.0}, Tmax_local{-1e6}; + + static su2double pvmax_global,pvmin_global,gradpv_global,Tmax_global; + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { + pvmax_global = -1e3; + pvmin_global = 1e3; + gradpv_global = 0.0; + Tmax_global = 0.0; + } + END_SU2_OMP_SAFE_GLOBAL_ACCESS + + SU2_OMP_FOR_(schedule(static,omp_chunk_size) SU2_NOWAIT) + for (auto iPoint = 0u; iPoint < nPointDomain; iPoint++) { + su2double pv_local = nodes->GetSolution(iPoint, I_PROGVAR); + su2double T_local = solver_container[FLOW_SOL]->GetNodes()->GetTemperature(iPoint); + + /* Parallel projection of progress variable gradient against velocity */ + su2double proj_grad_pv_u[MAXNDIM]={0}; + for (auto iDim=0u; iDim < nDim; iDim++) { + su2double gradpv = nodes->GetGradient(iPoint, I_PROGVAR, iDim); + su2double val_u = flowNodes->GetVelocity(iPoint, iDim); + proj_grad_pv_u[iDim] = gradpv * val_u * val_u / (flowNodes->GetVelocity2(iPoint) + EPS); + } + + /* Parallel projection of temperature gradient against projected progress variable gradient */ + su2double gradT[MAXNDIM]={0}; + for (auto iDim=0u; iDim < nDim; iDim++) + gradT[iDim] = flowNodes->GetGradient_Primitive(iPoint, prim_idx.Temperature(), iDim); + + su2double proj_grad_T_u = GeometryToolbox::DotProduct(nDim, gradT, proj_grad_pv_u); + su2double mag_gradT = GeometryToolbox::Norm(nDim, gradT); + + proj_grad_T_u /= max(mag_gradT, EPS); + + /* Update minimum and maximum values. */ + gradpv_local = max(gradpv_local, proj_grad_T_u); + pvmax_local = max(pvmax_local, pv_local); + pvmin_local = min(pvmin_local, pv_local); + Tmax_local = max(Tmax_local, T_local); + } + END_SU2_OMP_FOR + + atomicMax(pvmax_local, pvmax_global); + atomicMax(Tmax_local, Tmax_global); + atomicMin(pvmin_local, pvmin_global); + + su2double MyFlameThickness[3]={}, TotalFlameThickness[3]={}; + MyFlameThickness[0] = gradpv_local; + MyFlameThickness[1] = pvmax_local; + MyFlameThickness[2] = Tmax_local; + + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { + SU2_MPI::Allreduce(MyFlameThickness, TotalFlameThickness, 3, MPI_DOUBLE, MPI_MAX, SU2_MPI::GetComm()); + SU2_MPI::Allreduce(&pvmin_local, &pvmin_global, 1, MPI_DOUBLE, MPI_MIN, SU2_MPI::GetComm()); + gradpv_global = TotalFlameThickness[0]; + pvmax_global = TotalFlameThickness[1]; + Tmax_global = TotalFlameThickness[2]; + } + END_SU2_OMP_SAFE_GLOBAL_ACCESS + + + /* Update flame thickness value. */ + su2double flame_thickness{default_flame_thickness}; + if (Tmax_global > flamelet_config_options.Flame_T_ignition) flame_thickness = (pvmax_global - pvmin_global) / (gradpv_global+EPS); + + return flame_thickness; +} \ No newline at end of file diff --git a/config_template.cfg b/config_template.cfg index 535ae564510f..34fc40cf1ab7 100644 --- a/config_template.cfg +++ b/config_template.cfg @@ -978,7 +978,7 @@ CONTROLLING_VARIABLE_SOURCE_NAMES= (ProdRateTot_PV, NULL) % Method used to ignite the solution: % FLAME_FRONT : the flame is initialized using a plane, defined by a point and a normal. On one side, the solution is initialized % using 'burnt' conditions and on the other side 'unburnt' conditions. The normal points in the direction of the 'burnt' -% condition. +% condition. The 'burnt' condition is defined as the value of the progress variable for which the temperature exceeds the IGNITION_TEMPERATURE. % SPARK : the solution is ignited through application of a set of source terms within a specified region for a set number % of solver iterations. This artificial spark can be applied after a certain number of iterations has passed, allowing for % the flow to evolve before igniting the mixture. @@ -1028,6 +1028,15 @@ SPARK_INIT= (0.004, 0.0, 0.0, 1e-4, 100, 10) % The number of terms should equate the total number of species in the flamelet problem. SPARK_REACTION_RATES= (1000, 0, 0) +% Thickened flame correction +% Approximate the thickness of the flame and suppress source terms and enhance diffusivity in cells with a lengthscale higher +% than the flame thickness. +THICKENED_FLAME_CORRECTION= YES + +% Flame ignition temperature +% Flame thickness calculations are performed when the maximum temperature in the flow solution exceeds this value. +IGNITION_TEMPERATURE= 1100.0 + % % --------------------- INVERSE DESIGN SIMULATION -----------------------------% % From 9a5ad2dbc9bec6b80f19fd59edfa416a5ff5d3be Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Sat, 12 Sep 2026 14:06:17 -0700 Subject: [PATCH 48/61] Fix periodic comm counts (#2894) ## Related Work #2893 ## PR Checklist - [X] I am submitting my contribution to the develop branch. - [X] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [X] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [X] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [X] I have added a test case that demonstrates my contribution, if necessary. - [X] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --- SU2_CFD/src/solvers/CSolver.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index d1d323dd193d..407c960dceb8 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -370,10 +370,10 @@ void CSolver::InitiatePeriodicComms(CGeometry *geometry, auto *Diff = new su2double[nVar]; auto *Und_Lapl = new su2double[nVar]; - auto *Sol_Min = new su2double[nPrimVarGrad]; - auto *Sol_Max = new su2double[nPrimVarGrad]; - auto *rotPrim_i = new su2double[nPrimVar]; - auto *rotPrim_j = new su2double[nPrimVar]; + auto *Sol_Min = new su2double[std::max(nVar, nPrimVarGrad)]; + auto *Sol_Max = new su2double[std::max(nVar, nPrimVarGrad)]; + auto *rotPrim_i = new su2double[std::max(nVar, nPrimVar)]; + auto *rotPrim_j = new su2double[std::max(nVar, nPrimVar)]; su2double Sensor_i = 0.0, Sensor_j = 0.0, Pressure_i, Pressure_j; const su2double *Coord_i, *Coord_j; From 2c34b9062fa0073ae57dfe87be3a7a451487e02c Mon Sep 17 00:00:00 2001 From: Nijso Date: Sun, 13 Sep 2026 14:07:39 +0200 Subject: [PATCH 49/61] Fix compact restarts for surface_csv (#2892) ## Proposed Changes *Give a brief overview of your contribution here in a few sentences.* continuation of #2891 SURFACE_CSV still has a compact restart issue. Since surface_csv is not a restart file, we do not apply compactness to it. ## Related Work *Resolve any issues (bug fix or feature request), note any related PRs, or mention interactions with the work of others, if any.* ## PR Checklist *Put an X by all that apply. You can fill this out after submitting the PR. If you have any questions, don't hesitate to ask! We want to help. These are a guide for you to know what the reviewers will be looking for in your contribution.* - [x] I am submitting my contribution to the develop branch. - [ ] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [ ] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [ ] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [ ] I have added a test case that demonstrates my contribution, if necessary. - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --- .../output/filewriter/CParallelDataSorter.hpp | 17 --- SU2_CFD/src/output/COutput.cpp | 10 +- .../output/filewriter/CParallelDataSorter.cpp | 3 +- .../filewriter/CSU2BinaryFileWriter.cpp | 2 +- .../src/output/filewriter/CSU2FileWriter.cpp | 2 +- TestCases/parallel_regression.py | 17 +++ .../venturi_primitive_3species/README.md | 6 + .../species2_primitiveVenturi.cfg | 10 +- ...tiveVenturi_compact_restart_read_ascii.cfg | 140 ++++++++++++++++++ ...iveVenturi_compact_restart_read_binary.cfg | 140 ++++++++++++++++++ 10 files changed, 316 insertions(+), 31 deletions(-) create mode 100644 TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi_compact_restart_read_ascii.cfg create mode 100644 TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi_compact_restart_read_binary.cfg diff --git a/SU2_CFD/include/output/filewriter/CParallelDataSorter.hpp b/SU2_CFD/include/output/filewriter/CParallelDataSorter.hpp index 9f2b0e52031f..b2e49b0d8c55 100644 --- a/SU2_CFD/include/output/filewriter/CParallelDataSorter.hpp +++ b/SU2_CFD/include/output/filewriter/CParallelDataSorter.hpp @@ -109,7 +109,6 @@ class CParallelDataSorter{ nRecvs; //!< Number of receives vector fieldNames; //!< Vector with names of all the output fields - vector requiredFieldNames; //!< Vector with names of the required output fields that we write to file unsigned short nDim; //!< Spatial dimension of the data @@ -341,22 +340,6 @@ class CParallelDataSorter{ return fieldNames; } - /*! - * \brief Get the vector containing the names of the required output fields - * \return Vector of strings containing the required field names - */ - const vector& GetRequiredFieldNames() const{ - return requiredFieldNames; - } - - /*! - * \brief Set the vector of required output fields. - * \return None. - */ - void SetRequiredFieldNames(const vector& req_field_names) { - requiredFieldNames = req_field_names; - } - /*! * \brief Get the spatial dimension * \return The spatial dimension diff --git a/SU2_CFD/src/output/COutput.cpp b/SU2_CFD/src/output/COutput.cpp index a376483cee59..4fcc0eaa2941 100644 --- a/SU2_CFD/src/output/COutput.cpp +++ b/SU2_CFD/src/output/COutput.cpp @@ -411,10 +411,6 @@ void COutput::WriteToFile(CConfig *config, CGeometry *geometry, OUTPUT_TYPE form if (!config->GetWrt_Surface_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); - /*--- If we have compact restarts, we use only the required fields. ---*/ - if (config->GetWrt_Restart_Compact()) - surfaceDataSorter->SetRequiredFieldNames(requiredVolumeFieldNames); - surfaceDataSorter->SortConnectivity(config, geometry); surfaceDataSorter->SortOutputData(); @@ -435,9 +431,8 @@ void COutput::WriteToFile(CConfig *config, CGeometry *geometry, OUTPUT_TYPE form LogOutputFiles("SU2 ASCII restart"); + /*--- If we have compact restarts, we use only the required fields. ---*/ if (config->GetWrt_Restart_Compact()) { - /*--- If we have compact restarts, we use only the required fields. ---*/ - volumeDataSorterCompact->SetRequiredFieldNames(requiredVolumeFieldNames); fileWriter = new CSU2FileWriter(volumeDataSorterCompact); } else { fileWriter = new CSU2FileWriter(volumeDataSorter); @@ -456,9 +451,8 @@ void COutput::WriteToFile(CConfig *config, CGeometry *geometry, OUTPUT_TYPE form filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); LogOutputFiles("SU2 binary restart"); + /*--- If we have compact restarts, we use only the required fields. ---*/ if (config->GetWrt_Restart_Compact()) { - /*--- If we have compact restarts, we use only the required fields. ---*/ - volumeDataSorterCompact->SetRequiredFieldNames(requiredVolumeFieldNames); fileWriter = new CSU2BinaryFileWriter(volumeDataSorterCompact); } else { fileWriter = new CSU2BinaryFileWriter(volumeDataSorter); diff --git a/SU2_CFD/src/output/filewriter/CParallelDataSorter.cpp b/SU2_CFD/src/output/filewriter/CParallelDataSorter.cpp index 915e33d5c33f..e008e86f07c8 100644 --- a/SU2_CFD/src/output/filewriter/CParallelDataSorter.cpp +++ b/SU2_CFD/src/output/filewriter/CParallelDataSorter.cpp @@ -32,8 +32,7 @@ CParallelDataSorter::CParallelDataSorter(CConfig *config, const vector &valFieldNames) : rank(SU2_MPI::GetRank()), size(SU2_MPI::GetSize()), - fieldNames(valFieldNames), - requiredFieldNames(valFieldNames) { + fieldNames(valFieldNames) { GlobalField_Counter = fieldNames.size(); diff --git a/SU2_CFD/src/output/filewriter/CSU2BinaryFileWriter.cpp b/SU2_CFD/src/output/filewriter/CSU2BinaryFileWriter.cpp index b9c7e57ed86b..1e63aeeaa6ba 100644 --- a/SU2_CFD/src/output/filewriter/CSU2BinaryFileWriter.cpp +++ b/SU2_CFD/src/output/filewriter/CSU2BinaryFileWriter.cpp @@ -41,7 +41,7 @@ void CSU2BinaryFileWriter::WriteData(string val_filename){ unsigned short iVar; - const vector& fieldNames = dataSorter->GetRequiredFieldNames(); + const vector& fieldNames = dataSorter->GetFieldNames(); unsigned short nVar = fieldNames.size(); unsigned long nParallel_Poin = dataSorter->GetnPoints(); unsigned long nPoint_Global = dataSorter->GetnPointsGlobal(); diff --git a/SU2_CFD/src/output/filewriter/CSU2FileWriter.cpp b/SU2_CFD/src/output/filewriter/CSU2FileWriter.cpp index e0e940b83284..331bb6384866 100644 --- a/SU2_CFD/src/output/filewriter/CSU2FileWriter.cpp +++ b/SU2_CFD/src/output/filewriter/CSU2FileWriter.cpp @@ -35,7 +35,7 @@ CSU2FileWriter::CSU2FileWriter(CParallelDataSorter *valDataSorter) : void CSU2FileWriter::WriteData(string val_filename){ ofstream restart_file; - const vector fieldNames = dataSorter->GetRequiredFieldNames(); + const vector& fieldNames = dataSorter->GetFieldNames(); /*--- We append the pre-defined suffix (extension) to the filename (prefix) ---*/ val_filename.append(fileExt); diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index ff9b8c667061..25fa0e315bcf 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -1750,6 +1750,23 @@ def main(): species2_primitiveVenturi.test_vals = [-5.470699, -4.435379, -4.486544, -5.327925, -0.866369, -5.623281, 5.000000, -0.557915, 5.000000, -2.599732, 5.000000, -0.536608, 0.000037, 0.000037, 0.000000, 0.000000] test_list.append(species2_primitiveVenturi) + # Compact restart check. The case above writes a compact ASCII and binary restart file at + # iteration 49, holding the solution that enters iteration 50. The first iteration of these + # restarted runs must therefore reproduce its iteration 50, so they share its test values. + species2_compact_restart_ascii = TestCase('species2_primitiveVenturi_compact_restart_read_ascii') + species2_compact_restart_ascii.cfg_dir = "species_transport/venturi_primitive_3species" + species2_compact_restart_ascii.cfg_file = "species2_primitiveVenturi_compact_restart_read_ascii.cfg" + species2_compact_restart_ascii.test_iter = 0 + species2_compact_restart_ascii.test_vals = species2_primitiveVenturi.test_vals + test_list.append(species2_compact_restart_ascii) + + species2_compact_restart_binary = TestCase('species2_primitiveVenturi_compact_restart_read_binary') + species2_compact_restart_binary.cfg_dir = "species_transport/venturi_primitive_3species" + species2_compact_restart_binary.cfg_file = "species2_primitiveVenturi_compact_restart_read_binary.cfg" + species2_compact_restart_binary.test_iter = 0 + species2_compact_restart_binary.test_vals = species2_primitiveVenturi.test_vals + test_list.append(species2_compact_restart_binary) + # 2 species (1 eq) primitive venturi mixing with bounded scalar transport species_primitiveVenturi_boundedscalar = TestCase('species2_primitiveVenturi_bounded_scalar') species_primitiveVenturi_boundedscalar.cfg_dir = "species_transport/venturi_primitive_3species" diff --git a/TestCases/species_transport/venturi_primitive_3species/README.md b/TestCases/species_transport/venturi_primitive_3species/README.md index e26fde2ae74f..897e52c69c39 100644 --- a/TestCases/species_transport/venturi_primitive_3species/README.md +++ b/TestCases/species_transport/venturi_primitive_3species/README.md @@ -21,6 +21,12 @@ t 4. Adjoint simulation with 1 timestep, using the primal restart file from simulation in 2nd step. The printed direct residuals are taken for comparison +- `species2_primitiveVenturi_compact_restart_read_ascii.cfg` and `species2_primitiveVenturi_compact_restart_read_binary.cfg` check that compact restart files (`WRT_RESTART_COMPACT= YES`) are written and read correctly. +They restart from the files that `species2_venturiPrimitive.cfg` writes at iteration 49 (`WRT_RESTART_OVERWRITE= NO` with `OUTPUT_WRT_FREQ= 49, 49, 1000`), which hold the solution that enters iteration 50. +Their first iteration must therefore reproduce the residuals of iteration 50 of that case, and the regression test uses its values for all three cases. +`VOLUME_OUTPUT` contains fields outside the compact set, which is the situation in which the compact writers can shift the restart columns. + + - `species3_venturiPrimitive_inletFile.cfg` With the `test_inlet_files.sh` a simple sanity check for inlet files is performed. SU2 writes an `example_inlet_file.dat` when the specified inlet file is not available, with the values of the specified `MARKER_INLET` content. Therefore comparing a simulation with this example inlet file and without inlet files should result in exactly the same results. diff --git a/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi.cfg b/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi.cfg index d23ef78e0f56..bf9c1a72cc0a 100644 --- a/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi.cfg +++ b/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi.cfg @@ -119,9 +119,15 @@ SCREEN_WRT_FREQ_INNER= 10 HISTORY_OUTPUT= RMS_RES FLOW_COEFF LINSOL SPECIES_COEFF SPECIES_COEFF_SURF MARKER_ANALYZE= outlet gas_inlet air_axial_inlet % -OUTPUT_FILES= RESTART_ASCII, PARAVIEW_MULTIBLOCK +% The restart files written at iteration 49 hold the solution that enters +% iteration 50 and are the starting point of the compact restart checks in +% species2_primitiveVenturi_compact_restart_read_ascii.cfg and +% species2_primitiveVenturi_compact_restart_read_binary.cfg. +OUTPUT_FILES= RESTART_ASCII, RESTART, PARAVIEW_MULTIBLOCK VOLUME_OUTPUT= RESIDUAL, PRIMITIVE -OUTPUT_WRT_FREQ= 1000 +WRT_RESTART_COMPACT= YES +WRT_RESTART_OVERWRITE= NO +OUTPUT_WRT_FREQ= 49, 49, 1000 % RESTART_SOL= NO SOLUTION_FILENAME= solution diff --git a/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi_compact_restart_read_ascii.cfg b/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi_compact_restart_read_ascii.cfg new file mode 100644 index 000000000000..5f0016102159 --- /dev/null +++ b/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi_compact_restart_read_ascii.cfg @@ -0,0 +1,140 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: Species mixing with 2 species, i.e. 1 transport equations % +% restarted from the compact ASCII restart file of % +% species2_primitiveVenturi.cfg % +% Author: N. Beishuizen % +% Institution: TU Eindhoven % +% Date: 13-09-2026 % +% File Version 8.5.0 "Harrier" % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% This run restarts from the solution that entered iteration 50 of +% species2_primitiveVenturi.cfg, so its first iteration must reproduce the +% residuals printed at iteration 50 of that case. VOLUME_OUTPUT there holds +% fields outside the compact set, which is what the compact writers can get +% wrong. + +% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% +% +SOLVER= INC_RANS +KIND_TURB_MODEL= SST +% +% ---------------- INCOMPRESSIBLE FLOW CONDITION DEFINITION -------------------% +% +INC_DENSITY_MODEL= CONSTANT +INC_DENSITY_INIT= 1.1766 +% +INC_VELOCITY_INIT= ( 1.00, 0.0, 0.0 ) +% +INC_ENERGY_EQUATION= YES +INC_TEMPERATURE_INIT= 300.0 +% +INC_NONDIM= DIMENSIONAL +% +% -------------------- FLUID PROPERTIES ------------------------------------- % +% +FLUID_MODEL= CONSTANT_DENSITY +% +CONDUCTIVITY_MODEL= CONSTANT_CONDUCTIVITY +THERMAL_CONDUCTIVITY_CONSTANT= 0.0357 +% +PRANDTL_LAM= 0.72 +TURBULENT_CONDUCTIVITY_MODEL= NONE +PRANDTL_TURB= 0.90 +% +VISCOSITY_MODEL= CONSTANT_VISCOSITY +MU_CONSTANT= 1.716E-5 +% +% -------------------- BOUNDARY CONDITION DEFINITION --------------------------% +% +MARKER_HEATFLUX= ( wall, 0.0 ) +MARKER_SYM= ( axis ) +% +INC_INLET_TYPE= VELOCITY_INLET VELOCITY_INLET +MARKER_INLET= ( gas_inlet, 300, 1.0, 1.0, 0.0, 0.0,\ + air_axial_inlet, 300, 1.0, 0.0, -1.0, 0.0 ) +MARKER_INLET_SPECIES= (gas_inlet, 1.0,\ + air_axial_inlet, 0.6 ) +% +INC_OUTLET_TYPE= PRESSURE_OUTLET +MARKER_OUTLET= ( outlet, 0.0) +% +% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% +% +NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES +% +CFL_NUMBER= 2000 +CFL_REDUCTION_SPECIES= 1.0 +CFL_REDUCTION_TURB= 1.0 +% +ITER= 1 +% +% ------------------------ LINEAR SOLVER DEFINITION ---------------------------% +% +LINEAR_SOLVER= FGMRES +LINEAR_SOLVER_PREC= ILU +LINEAR_SOLVER_ERROR= 1E-8 +LINEAR_SOLVER_ITER= 5 +% +% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% +% +CONV_NUM_METHOD_FLOW= FDS +MUSCL_FLOW= YES +SLOPE_LIMITER_FLOW = NONE +TIME_DISCRE_FLOW= EULER_IMPLICIT +% +% -------------------- SCALAR TRANSPORT ---------------------------------------% +% +KIND_SCALAR_MODEL= SPECIES_TRANSPORT +DIFFUSIVITY_MODEL= CONSTANT_DIFFUSIVITY +DIFFUSIVITY_CONSTANT= 0.001 +% +CONV_NUM_METHOD_SPECIES= SCALAR_UPWIND +MUSCL_SPECIES= NO +SLOPE_LIMITER_SPECIES = NONE +% +TIME_DISCRE_SPECIES= EULER_IMPLICIT +% +SPECIES_INIT= 1.0 +SPECIES_CLIPPING= YES +SPECIES_CLIPPING_MIN= 0.0 +SPECIES_CLIPPING_MAX= 1.0 +% +% -------------------- TURBULENT TRANSPORT ---------------------------------------% +% +CONV_NUM_METHOD_TURB= SCALAR_UPWIND +MUSCL_TURB= NO +% +% --------------------------- CONVERGENCE PARAMETERS --------------------------% +% +CONV_FIELD= RMS_PRESSURE, RMS_VELOCITY-X, RMS_VELOCITY-Y, RMS_TKE, RMS_SPECIES +CONV_RESIDUAL_MINVAL= -18 +CONV_STARTITER= 10 +% +% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% +% +MESH_FILENAME= primitiveVenturi.su2 +SCREEN_OUTPUT= INNER_ITER WALL_TIME \ + RMS_PRESSURE RMS_VELOCITY-X RMS_VELOCITY-Y RMS_TKE RMS_DISSIPATION RMS_SPECIES_0 \ + LINSOL_ITER LINSOL_RESIDUAL \ + LINSOL_ITER_TURB LINSOL_RESIDUAL_TURB \ + LINSOL_ITER_SPECIES LINSOL_RESIDUAL_SPECIES SURFACE_SPECIES_VARIANCE +SCREEN_WRT_FREQ_INNER= 10 +% +HISTORY_OUTPUT= RMS_RES FLOW_COEFF LINSOL SPECIES_COEFF SPECIES_COEFF_SURF +MARKER_ANALYZE= outlet gas_inlet air_axial_inlet +% +OUTPUT_FILES= RESTART_ASCII +VOLUME_OUTPUT= RESIDUAL, PRIMITIVE +WRT_RESTART_COMPACT= YES +OUTPUT_WRT_FREQ= 1000 +% +RESTART_SOL= YES +READ_BINARY_RESTART= NO +SOLUTION_FILENAME= restart_000049 +RESTART_FILENAME= restart_compact_read_ascii +% +WRT_PERFORMANCE= YES diff --git a/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi_compact_restart_read_binary.cfg b/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi_compact_restart_read_binary.cfg new file mode 100644 index 000000000000..da06ca86b73f --- /dev/null +++ b/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi_compact_restart_read_binary.cfg @@ -0,0 +1,140 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: Species mixing with 2 species, i.e. 1 transport equations % +% restarted from the compact binary restart file of % +% species2_primitiveVenturi.cfg % +% Author: N. Beishuizen % +% Institution: TU Eindhoven % +% Date: 13-09-2026 % +% File Version 8.5.0 "Harrier" % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% This run restarts from the solution that entered iteration 50 of +% species2_primitiveVenturi.cfg, so its first iteration must reproduce the +% residuals printed at iteration 50 of that case. VOLUME_OUTPUT there holds +% fields outside the compact set, which is what the compact writers can get +% wrong. + +% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% +% +SOLVER= INC_RANS +KIND_TURB_MODEL= SST +% +% ---------------- INCOMPRESSIBLE FLOW CONDITION DEFINITION -------------------% +% +INC_DENSITY_MODEL= CONSTANT +INC_DENSITY_INIT= 1.1766 +% +INC_VELOCITY_INIT= ( 1.00, 0.0, 0.0 ) +% +INC_ENERGY_EQUATION= YES +INC_TEMPERATURE_INIT= 300.0 +% +INC_NONDIM= DIMENSIONAL +% +% -------------------- FLUID PROPERTIES ------------------------------------- % +% +FLUID_MODEL= CONSTANT_DENSITY +% +CONDUCTIVITY_MODEL= CONSTANT_CONDUCTIVITY +THERMAL_CONDUCTIVITY_CONSTANT= 0.0357 +% +PRANDTL_LAM= 0.72 +TURBULENT_CONDUCTIVITY_MODEL= NONE +PRANDTL_TURB= 0.90 +% +VISCOSITY_MODEL= CONSTANT_VISCOSITY +MU_CONSTANT= 1.716E-5 +% +% -------------------- BOUNDARY CONDITION DEFINITION --------------------------% +% +MARKER_HEATFLUX= ( wall, 0.0 ) +MARKER_SYM= ( axis ) +% +INC_INLET_TYPE= VELOCITY_INLET VELOCITY_INLET +MARKER_INLET= ( gas_inlet, 300, 1.0, 1.0, 0.0, 0.0,\ + air_axial_inlet, 300, 1.0, 0.0, -1.0, 0.0 ) +MARKER_INLET_SPECIES= (gas_inlet, 1.0,\ + air_axial_inlet, 0.6 ) +% +INC_OUTLET_TYPE= PRESSURE_OUTLET +MARKER_OUTLET= ( outlet, 0.0) +% +% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% +% +NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES +% +CFL_NUMBER= 2000 +CFL_REDUCTION_SPECIES= 1.0 +CFL_REDUCTION_TURB= 1.0 +% +ITER= 1 +% +% ------------------------ LINEAR SOLVER DEFINITION ---------------------------% +% +LINEAR_SOLVER= FGMRES +LINEAR_SOLVER_PREC= ILU +LINEAR_SOLVER_ERROR= 1E-8 +LINEAR_SOLVER_ITER= 5 +% +% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% +% +CONV_NUM_METHOD_FLOW= FDS +MUSCL_FLOW= YES +SLOPE_LIMITER_FLOW = NONE +TIME_DISCRE_FLOW= EULER_IMPLICIT +% +% -------------------- SCALAR TRANSPORT ---------------------------------------% +% +KIND_SCALAR_MODEL= SPECIES_TRANSPORT +DIFFUSIVITY_MODEL= CONSTANT_DIFFUSIVITY +DIFFUSIVITY_CONSTANT= 0.001 +% +CONV_NUM_METHOD_SPECIES= SCALAR_UPWIND +MUSCL_SPECIES= NO +SLOPE_LIMITER_SPECIES = NONE +% +TIME_DISCRE_SPECIES= EULER_IMPLICIT +% +SPECIES_INIT= 1.0 +SPECIES_CLIPPING= YES +SPECIES_CLIPPING_MIN= 0.0 +SPECIES_CLIPPING_MAX= 1.0 +% +% -------------------- TURBULENT TRANSPORT ---------------------------------------% +% +CONV_NUM_METHOD_TURB= SCALAR_UPWIND +MUSCL_TURB= NO +% +% --------------------------- CONVERGENCE PARAMETERS --------------------------% +% +CONV_FIELD= RMS_PRESSURE, RMS_VELOCITY-X, RMS_VELOCITY-Y, RMS_TKE, RMS_SPECIES +CONV_RESIDUAL_MINVAL= -18 +CONV_STARTITER= 10 +% +% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% +% +MESH_FILENAME= primitiveVenturi.su2 +SCREEN_OUTPUT= INNER_ITER WALL_TIME \ + RMS_PRESSURE RMS_VELOCITY-X RMS_VELOCITY-Y RMS_TKE RMS_DISSIPATION RMS_SPECIES_0 \ + LINSOL_ITER LINSOL_RESIDUAL \ + LINSOL_ITER_TURB LINSOL_RESIDUAL_TURB \ + LINSOL_ITER_SPECIES LINSOL_RESIDUAL_SPECIES SURFACE_SPECIES_VARIANCE +SCREEN_WRT_FREQ_INNER= 10 +% +HISTORY_OUTPUT= RMS_RES FLOW_COEFF LINSOL SPECIES_COEFF SPECIES_COEFF_SURF +MARKER_ANALYZE= outlet gas_inlet air_axial_inlet +% +OUTPUT_FILES= RESTART +VOLUME_OUTPUT= RESIDUAL, PRIMITIVE +WRT_RESTART_COMPACT= YES +OUTPUT_WRT_FREQ= 1000 +% +RESTART_SOL= YES +READ_BINARY_RESTART= YES +SOLUTION_FILENAME= restart_000049 +RESTART_FILENAME= restart_compact_read_binary +% +WRT_PERFORMANCE= YES From 04cefe2242be059b890cdd88aefa5d24448a3aaf Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Sun, 13 Sep 2026 10:58:40 -0700 Subject: [PATCH 50/61] Revert "Fix compact restarts for surface_csv " (#2895) Reverts su2code/SU2#2892 --- .../output/filewriter/CParallelDataSorter.hpp | 17 +++ SU2_CFD/src/output/COutput.cpp | 10 +- .../output/filewriter/CParallelDataSorter.cpp | 3 +- .../filewriter/CSU2BinaryFileWriter.cpp | 2 +- .../src/output/filewriter/CSU2FileWriter.cpp | 2 +- TestCases/parallel_regression.py | 17 --- .../venturi_primitive_3species/README.md | 6 - .../species2_primitiveVenturi.cfg | 10 +- ...tiveVenturi_compact_restart_read_ascii.cfg | 140 ------------------ ...iveVenturi_compact_restart_read_binary.cfg | 140 ------------------ 10 files changed, 31 insertions(+), 316 deletions(-) delete mode 100644 TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi_compact_restart_read_ascii.cfg delete mode 100644 TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi_compact_restart_read_binary.cfg diff --git a/SU2_CFD/include/output/filewriter/CParallelDataSorter.hpp b/SU2_CFD/include/output/filewriter/CParallelDataSorter.hpp index b2e49b0d8c55..9f2b0e52031f 100644 --- a/SU2_CFD/include/output/filewriter/CParallelDataSorter.hpp +++ b/SU2_CFD/include/output/filewriter/CParallelDataSorter.hpp @@ -109,6 +109,7 @@ class CParallelDataSorter{ nRecvs; //!< Number of receives vector fieldNames; //!< Vector with names of all the output fields + vector requiredFieldNames; //!< Vector with names of the required output fields that we write to file unsigned short nDim; //!< Spatial dimension of the data @@ -340,6 +341,22 @@ class CParallelDataSorter{ return fieldNames; } + /*! + * \brief Get the vector containing the names of the required output fields + * \return Vector of strings containing the required field names + */ + const vector& GetRequiredFieldNames() const{ + return requiredFieldNames; + } + + /*! + * \brief Set the vector of required output fields. + * \return None. + */ + void SetRequiredFieldNames(const vector& req_field_names) { + requiredFieldNames = req_field_names; + } + /*! * \brief Get the spatial dimension * \return The spatial dimension diff --git a/SU2_CFD/src/output/COutput.cpp b/SU2_CFD/src/output/COutput.cpp index 4fcc0eaa2941..a376483cee59 100644 --- a/SU2_CFD/src/output/COutput.cpp +++ b/SU2_CFD/src/output/COutput.cpp @@ -411,6 +411,10 @@ void COutput::WriteToFile(CConfig *config, CGeometry *geometry, OUTPUT_TYPE form if (!config->GetWrt_Surface_Overwrite()) filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); + /*--- If we have compact restarts, we use only the required fields. ---*/ + if (config->GetWrt_Restart_Compact()) + surfaceDataSorter->SetRequiredFieldNames(requiredVolumeFieldNames); + surfaceDataSorter->SortConnectivity(config, geometry); surfaceDataSorter->SortOutputData(); @@ -431,8 +435,9 @@ void COutput::WriteToFile(CConfig *config, CGeometry *geometry, OUTPUT_TYPE form LogOutputFiles("SU2 ASCII restart"); - /*--- If we have compact restarts, we use only the required fields. ---*/ if (config->GetWrt_Restart_Compact()) { + /*--- If we have compact restarts, we use only the required fields. ---*/ + volumeDataSorterCompact->SetRequiredFieldNames(requiredVolumeFieldNames); fileWriter = new CSU2FileWriter(volumeDataSorterCompact); } else { fileWriter = new CSU2FileWriter(volumeDataSorter); @@ -451,8 +456,9 @@ void COutput::WriteToFile(CConfig *config, CGeometry *geometry, OUTPUT_TYPE form filename_iter = config->GetFilename_Iter(fileName, curInnerIter, curOuterIter); LogOutputFiles("SU2 binary restart"); - /*--- If we have compact restarts, we use only the required fields. ---*/ if (config->GetWrt_Restart_Compact()) { + /*--- If we have compact restarts, we use only the required fields. ---*/ + volumeDataSorterCompact->SetRequiredFieldNames(requiredVolumeFieldNames); fileWriter = new CSU2BinaryFileWriter(volumeDataSorterCompact); } else { fileWriter = new CSU2BinaryFileWriter(volumeDataSorter); diff --git a/SU2_CFD/src/output/filewriter/CParallelDataSorter.cpp b/SU2_CFD/src/output/filewriter/CParallelDataSorter.cpp index e008e86f07c8..915e33d5c33f 100644 --- a/SU2_CFD/src/output/filewriter/CParallelDataSorter.cpp +++ b/SU2_CFD/src/output/filewriter/CParallelDataSorter.cpp @@ -32,7 +32,8 @@ CParallelDataSorter::CParallelDataSorter(CConfig *config, const vector &valFieldNames) : rank(SU2_MPI::GetRank()), size(SU2_MPI::GetSize()), - fieldNames(valFieldNames) { + fieldNames(valFieldNames), + requiredFieldNames(valFieldNames) { GlobalField_Counter = fieldNames.size(); diff --git a/SU2_CFD/src/output/filewriter/CSU2BinaryFileWriter.cpp b/SU2_CFD/src/output/filewriter/CSU2BinaryFileWriter.cpp index 1e63aeeaa6ba..b9c7e57ed86b 100644 --- a/SU2_CFD/src/output/filewriter/CSU2BinaryFileWriter.cpp +++ b/SU2_CFD/src/output/filewriter/CSU2BinaryFileWriter.cpp @@ -41,7 +41,7 @@ void CSU2BinaryFileWriter::WriteData(string val_filename){ unsigned short iVar; - const vector& fieldNames = dataSorter->GetFieldNames(); + const vector& fieldNames = dataSorter->GetRequiredFieldNames(); unsigned short nVar = fieldNames.size(); unsigned long nParallel_Poin = dataSorter->GetnPoints(); unsigned long nPoint_Global = dataSorter->GetnPointsGlobal(); diff --git a/SU2_CFD/src/output/filewriter/CSU2FileWriter.cpp b/SU2_CFD/src/output/filewriter/CSU2FileWriter.cpp index 331bb6384866..e0e940b83284 100644 --- a/SU2_CFD/src/output/filewriter/CSU2FileWriter.cpp +++ b/SU2_CFD/src/output/filewriter/CSU2FileWriter.cpp @@ -35,7 +35,7 @@ CSU2FileWriter::CSU2FileWriter(CParallelDataSorter *valDataSorter) : void CSU2FileWriter::WriteData(string val_filename){ ofstream restart_file; - const vector& fieldNames = dataSorter->GetFieldNames(); + const vector fieldNames = dataSorter->GetRequiredFieldNames(); /*--- We append the pre-defined suffix (extension) to the filename (prefix) ---*/ val_filename.append(fileExt); diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index 25fa0e315bcf..ff9b8c667061 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -1750,23 +1750,6 @@ def main(): species2_primitiveVenturi.test_vals = [-5.470699, -4.435379, -4.486544, -5.327925, -0.866369, -5.623281, 5.000000, -0.557915, 5.000000, -2.599732, 5.000000, -0.536608, 0.000037, 0.000037, 0.000000, 0.000000] test_list.append(species2_primitiveVenturi) - # Compact restart check. The case above writes a compact ASCII and binary restart file at - # iteration 49, holding the solution that enters iteration 50. The first iteration of these - # restarted runs must therefore reproduce its iteration 50, so they share its test values. - species2_compact_restart_ascii = TestCase('species2_primitiveVenturi_compact_restart_read_ascii') - species2_compact_restart_ascii.cfg_dir = "species_transport/venturi_primitive_3species" - species2_compact_restart_ascii.cfg_file = "species2_primitiveVenturi_compact_restart_read_ascii.cfg" - species2_compact_restart_ascii.test_iter = 0 - species2_compact_restart_ascii.test_vals = species2_primitiveVenturi.test_vals - test_list.append(species2_compact_restart_ascii) - - species2_compact_restart_binary = TestCase('species2_primitiveVenturi_compact_restart_read_binary') - species2_compact_restart_binary.cfg_dir = "species_transport/venturi_primitive_3species" - species2_compact_restart_binary.cfg_file = "species2_primitiveVenturi_compact_restart_read_binary.cfg" - species2_compact_restart_binary.test_iter = 0 - species2_compact_restart_binary.test_vals = species2_primitiveVenturi.test_vals - test_list.append(species2_compact_restart_binary) - # 2 species (1 eq) primitive venturi mixing with bounded scalar transport species_primitiveVenturi_boundedscalar = TestCase('species2_primitiveVenturi_bounded_scalar') species_primitiveVenturi_boundedscalar.cfg_dir = "species_transport/venturi_primitive_3species" diff --git a/TestCases/species_transport/venturi_primitive_3species/README.md b/TestCases/species_transport/venturi_primitive_3species/README.md index 897e52c69c39..e26fde2ae74f 100644 --- a/TestCases/species_transport/venturi_primitive_3species/README.md +++ b/TestCases/species_transport/venturi_primitive_3species/README.md @@ -21,12 +21,6 @@ t 4. Adjoint simulation with 1 timestep, using the primal restart file from simulation in 2nd step. The printed direct residuals are taken for comparison -- `species2_primitiveVenturi_compact_restart_read_ascii.cfg` and `species2_primitiveVenturi_compact_restart_read_binary.cfg` check that compact restart files (`WRT_RESTART_COMPACT= YES`) are written and read correctly. -They restart from the files that `species2_venturiPrimitive.cfg` writes at iteration 49 (`WRT_RESTART_OVERWRITE= NO` with `OUTPUT_WRT_FREQ= 49, 49, 1000`), which hold the solution that enters iteration 50. -Their first iteration must therefore reproduce the residuals of iteration 50 of that case, and the regression test uses its values for all three cases. -`VOLUME_OUTPUT` contains fields outside the compact set, which is the situation in which the compact writers can shift the restart columns. - - - `species3_venturiPrimitive_inletFile.cfg` With the `test_inlet_files.sh` a simple sanity check for inlet files is performed. SU2 writes an `example_inlet_file.dat` when the specified inlet file is not available, with the values of the specified `MARKER_INLET` content. Therefore comparing a simulation with this example inlet file and without inlet files should result in exactly the same results. diff --git a/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi.cfg b/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi.cfg index bf9c1a72cc0a..d23ef78e0f56 100644 --- a/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi.cfg +++ b/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi.cfg @@ -119,15 +119,9 @@ SCREEN_WRT_FREQ_INNER= 10 HISTORY_OUTPUT= RMS_RES FLOW_COEFF LINSOL SPECIES_COEFF SPECIES_COEFF_SURF MARKER_ANALYZE= outlet gas_inlet air_axial_inlet % -% The restart files written at iteration 49 hold the solution that enters -% iteration 50 and are the starting point of the compact restart checks in -% species2_primitiveVenturi_compact_restart_read_ascii.cfg and -% species2_primitiveVenturi_compact_restart_read_binary.cfg. -OUTPUT_FILES= RESTART_ASCII, RESTART, PARAVIEW_MULTIBLOCK +OUTPUT_FILES= RESTART_ASCII, PARAVIEW_MULTIBLOCK VOLUME_OUTPUT= RESIDUAL, PRIMITIVE -WRT_RESTART_COMPACT= YES -WRT_RESTART_OVERWRITE= NO -OUTPUT_WRT_FREQ= 49, 49, 1000 +OUTPUT_WRT_FREQ= 1000 % RESTART_SOL= NO SOLUTION_FILENAME= solution diff --git a/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi_compact_restart_read_ascii.cfg b/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi_compact_restart_read_ascii.cfg deleted file mode 100644 index 5f0016102159..000000000000 --- a/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi_compact_restart_read_ascii.cfg +++ /dev/null @@ -1,140 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% % -% SU2 configuration file % -% Case description: Species mixing with 2 species, i.e. 1 transport equations % -% restarted from the compact ASCII restart file of % -% species2_primitiveVenturi.cfg % -% Author: N. Beishuizen % -% Institution: TU Eindhoven % -% Date: 13-09-2026 % -% File Version 8.5.0 "Harrier" % -% % -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -% This run restarts from the solution that entered iteration 50 of -% species2_primitiveVenturi.cfg, so its first iteration must reproduce the -% residuals printed at iteration 50 of that case. VOLUME_OUTPUT there holds -% fields outside the compact set, which is what the compact writers can get -% wrong. - -% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% -% -SOLVER= INC_RANS -KIND_TURB_MODEL= SST -% -% ---------------- INCOMPRESSIBLE FLOW CONDITION DEFINITION -------------------% -% -INC_DENSITY_MODEL= CONSTANT -INC_DENSITY_INIT= 1.1766 -% -INC_VELOCITY_INIT= ( 1.00, 0.0, 0.0 ) -% -INC_ENERGY_EQUATION= YES -INC_TEMPERATURE_INIT= 300.0 -% -INC_NONDIM= DIMENSIONAL -% -% -------------------- FLUID PROPERTIES ------------------------------------- % -% -FLUID_MODEL= CONSTANT_DENSITY -% -CONDUCTIVITY_MODEL= CONSTANT_CONDUCTIVITY -THERMAL_CONDUCTIVITY_CONSTANT= 0.0357 -% -PRANDTL_LAM= 0.72 -TURBULENT_CONDUCTIVITY_MODEL= NONE -PRANDTL_TURB= 0.90 -% -VISCOSITY_MODEL= CONSTANT_VISCOSITY -MU_CONSTANT= 1.716E-5 -% -% -------------------- BOUNDARY CONDITION DEFINITION --------------------------% -% -MARKER_HEATFLUX= ( wall, 0.0 ) -MARKER_SYM= ( axis ) -% -INC_INLET_TYPE= VELOCITY_INLET VELOCITY_INLET -MARKER_INLET= ( gas_inlet, 300, 1.0, 1.0, 0.0, 0.0,\ - air_axial_inlet, 300, 1.0, 0.0, -1.0, 0.0 ) -MARKER_INLET_SPECIES= (gas_inlet, 1.0,\ - air_axial_inlet, 0.6 ) -% -INC_OUTLET_TYPE= PRESSURE_OUTLET -MARKER_OUTLET= ( outlet, 0.0) -% -% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% -% -NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES -% -CFL_NUMBER= 2000 -CFL_REDUCTION_SPECIES= 1.0 -CFL_REDUCTION_TURB= 1.0 -% -ITER= 1 -% -% ------------------------ LINEAR SOLVER DEFINITION ---------------------------% -% -LINEAR_SOLVER= FGMRES -LINEAR_SOLVER_PREC= ILU -LINEAR_SOLVER_ERROR= 1E-8 -LINEAR_SOLVER_ITER= 5 -% -% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% -% -CONV_NUM_METHOD_FLOW= FDS -MUSCL_FLOW= YES -SLOPE_LIMITER_FLOW = NONE -TIME_DISCRE_FLOW= EULER_IMPLICIT -% -% -------------------- SCALAR TRANSPORT ---------------------------------------% -% -KIND_SCALAR_MODEL= SPECIES_TRANSPORT -DIFFUSIVITY_MODEL= CONSTANT_DIFFUSIVITY -DIFFUSIVITY_CONSTANT= 0.001 -% -CONV_NUM_METHOD_SPECIES= SCALAR_UPWIND -MUSCL_SPECIES= NO -SLOPE_LIMITER_SPECIES = NONE -% -TIME_DISCRE_SPECIES= EULER_IMPLICIT -% -SPECIES_INIT= 1.0 -SPECIES_CLIPPING= YES -SPECIES_CLIPPING_MIN= 0.0 -SPECIES_CLIPPING_MAX= 1.0 -% -% -------------------- TURBULENT TRANSPORT ---------------------------------------% -% -CONV_NUM_METHOD_TURB= SCALAR_UPWIND -MUSCL_TURB= NO -% -% --------------------------- CONVERGENCE PARAMETERS --------------------------% -% -CONV_FIELD= RMS_PRESSURE, RMS_VELOCITY-X, RMS_VELOCITY-Y, RMS_TKE, RMS_SPECIES -CONV_RESIDUAL_MINVAL= -18 -CONV_STARTITER= 10 -% -% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% -% -MESH_FILENAME= primitiveVenturi.su2 -SCREEN_OUTPUT= INNER_ITER WALL_TIME \ - RMS_PRESSURE RMS_VELOCITY-X RMS_VELOCITY-Y RMS_TKE RMS_DISSIPATION RMS_SPECIES_0 \ - LINSOL_ITER LINSOL_RESIDUAL \ - LINSOL_ITER_TURB LINSOL_RESIDUAL_TURB \ - LINSOL_ITER_SPECIES LINSOL_RESIDUAL_SPECIES SURFACE_SPECIES_VARIANCE -SCREEN_WRT_FREQ_INNER= 10 -% -HISTORY_OUTPUT= RMS_RES FLOW_COEFF LINSOL SPECIES_COEFF SPECIES_COEFF_SURF -MARKER_ANALYZE= outlet gas_inlet air_axial_inlet -% -OUTPUT_FILES= RESTART_ASCII -VOLUME_OUTPUT= RESIDUAL, PRIMITIVE -WRT_RESTART_COMPACT= YES -OUTPUT_WRT_FREQ= 1000 -% -RESTART_SOL= YES -READ_BINARY_RESTART= NO -SOLUTION_FILENAME= restart_000049 -RESTART_FILENAME= restart_compact_read_ascii -% -WRT_PERFORMANCE= YES diff --git a/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi_compact_restart_read_binary.cfg b/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi_compact_restart_read_binary.cfg deleted file mode 100644 index da06ca86b73f..000000000000 --- a/TestCases/species_transport/venturi_primitive_3species/species2_primitiveVenturi_compact_restart_read_binary.cfg +++ /dev/null @@ -1,140 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% % -% SU2 configuration file % -% Case description: Species mixing with 2 species, i.e. 1 transport equations % -% restarted from the compact binary restart file of % -% species2_primitiveVenturi.cfg % -% Author: N. Beishuizen % -% Institution: TU Eindhoven % -% Date: 13-09-2026 % -% File Version 8.5.0 "Harrier" % -% % -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -% This run restarts from the solution that entered iteration 50 of -% species2_primitiveVenturi.cfg, so its first iteration must reproduce the -% residuals printed at iteration 50 of that case. VOLUME_OUTPUT there holds -% fields outside the compact set, which is what the compact writers can get -% wrong. - -% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% -% -SOLVER= INC_RANS -KIND_TURB_MODEL= SST -% -% ---------------- INCOMPRESSIBLE FLOW CONDITION DEFINITION -------------------% -% -INC_DENSITY_MODEL= CONSTANT -INC_DENSITY_INIT= 1.1766 -% -INC_VELOCITY_INIT= ( 1.00, 0.0, 0.0 ) -% -INC_ENERGY_EQUATION= YES -INC_TEMPERATURE_INIT= 300.0 -% -INC_NONDIM= DIMENSIONAL -% -% -------------------- FLUID PROPERTIES ------------------------------------- % -% -FLUID_MODEL= CONSTANT_DENSITY -% -CONDUCTIVITY_MODEL= CONSTANT_CONDUCTIVITY -THERMAL_CONDUCTIVITY_CONSTANT= 0.0357 -% -PRANDTL_LAM= 0.72 -TURBULENT_CONDUCTIVITY_MODEL= NONE -PRANDTL_TURB= 0.90 -% -VISCOSITY_MODEL= CONSTANT_VISCOSITY -MU_CONSTANT= 1.716E-5 -% -% -------------------- BOUNDARY CONDITION DEFINITION --------------------------% -% -MARKER_HEATFLUX= ( wall, 0.0 ) -MARKER_SYM= ( axis ) -% -INC_INLET_TYPE= VELOCITY_INLET VELOCITY_INLET -MARKER_INLET= ( gas_inlet, 300, 1.0, 1.0, 0.0, 0.0,\ - air_axial_inlet, 300, 1.0, 0.0, -1.0, 0.0 ) -MARKER_INLET_SPECIES= (gas_inlet, 1.0,\ - air_axial_inlet, 0.6 ) -% -INC_OUTLET_TYPE= PRESSURE_OUTLET -MARKER_OUTLET= ( outlet, 0.0) -% -% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% -% -NUM_METHOD_GRAD= WEIGHTED_LEAST_SQUARES -% -CFL_NUMBER= 2000 -CFL_REDUCTION_SPECIES= 1.0 -CFL_REDUCTION_TURB= 1.0 -% -ITER= 1 -% -% ------------------------ LINEAR SOLVER DEFINITION ---------------------------% -% -LINEAR_SOLVER= FGMRES -LINEAR_SOLVER_PREC= ILU -LINEAR_SOLVER_ERROR= 1E-8 -LINEAR_SOLVER_ITER= 5 -% -% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% -% -CONV_NUM_METHOD_FLOW= FDS -MUSCL_FLOW= YES -SLOPE_LIMITER_FLOW = NONE -TIME_DISCRE_FLOW= EULER_IMPLICIT -% -% -------------------- SCALAR TRANSPORT ---------------------------------------% -% -KIND_SCALAR_MODEL= SPECIES_TRANSPORT -DIFFUSIVITY_MODEL= CONSTANT_DIFFUSIVITY -DIFFUSIVITY_CONSTANT= 0.001 -% -CONV_NUM_METHOD_SPECIES= SCALAR_UPWIND -MUSCL_SPECIES= NO -SLOPE_LIMITER_SPECIES = NONE -% -TIME_DISCRE_SPECIES= EULER_IMPLICIT -% -SPECIES_INIT= 1.0 -SPECIES_CLIPPING= YES -SPECIES_CLIPPING_MIN= 0.0 -SPECIES_CLIPPING_MAX= 1.0 -% -% -------------------- TURBULENT TRANSPORT ---------------------------------------% -% -CONV_NUM_METHOD_TURB= SCALAR_UPWIND -MUSCL_TURB= NO -% -% --------------------------- CONVERGENCE PARAMETERS --------------------------% -% -CONV_FIELD= RMS_PRESSURE, RMS_VELOCITY-X, RMS_VELOCITY-Y, RMS_TKE, RMS_SPECIES -CONV_RESIDUAL_MINVAL= -18 -CONV_STARTITER= 10 -% -% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% -% -MESH_FILENAME= primitiveVenturi.su2 -SCREEN_OUTPUT= INNER_ITER WALL_TIME \ - RMS_PRESSURE RMS_VELOCITY-X RMS_VELOCITY-Y RMS_TKE RMS_DISSIPATION RMS_SPECIES_0 \ - LINSOL_ITER LINSOL_RESIDUAL \ - LINSOL_ITER_TURB LINSOL_RESIDUAL_TURB \ - LINSOL_ITER_SPECIES LINSOL_RESIDUAL_SPECIES SURFACE_SPECIES_VARIANCE -SCREEN_WRT_FREQ_INNER= 10 -% -HISTORY_OUTPUT= RMS_RES FLOW_COEFF LINSOL SPECIES_COEFF SPECIES_COEFF_SURF -MARKER_ANALYZE= outlet gas_inlet air_axial_inlet -% -OUTPUT_FILES= RESTART -VOLUME_OUTPUT= RESIDUAL, PRIMITIVE -WRT_RESTART_COMPACT= YES -OUTPUT_WRT_FREQ= 1000 -% -RESTART_SOL= YES -READ_BINARY_RESTART= YES -SOLUTION_FILENAME= restart_000049 -RESTART_FILENAME= restart_compact_read_binary -% -WRT_PERFORMANCE= YES From 2c358160b4d7d3f291e23255b8c45c4fe9ca7ed3 Mon Sep 17 00:00:00 2001 From: FabianYan2010 <151528731+FabianYan2010@users.noreply.github.com> Date: Mon, 14 Sep 2026 03:48:34 +0800 Subject: [PATCH 51/61] Change for sliding plane when relative frame is used (#2311) ## Proposed Changes In some cases we use relative frame for rotor zone to perform URANS, e.g., in aeroelasticity analysis, we want the grid movement comes only from blade deformation. The problem is that the sliding plane in SU2 works only for absolute frame. In relative frame, the grid is not rotating, so the interpolation at sliding interface is not changed as time step is marching. The proposed changes rotate the sliding interface in accordance with physical time steps to perform interpolation. This function is activated only for relative frame. The grid itself is not rotating. Currently, this is only implemented in CNearestneighbor class, other interpolation method should be changed as well in the near future. ## Related Work *Resolve any issues (bug fix or feature request), note any related PRs, or mention interactions with the work of others, if any.* ## PR Checklist *Put an X by all that apply. You can fill this out after submitting the PR. If you have any questions, don't hesitate to ask! We want to help. These are a guide for you to know what the reviewers will be looking for in your contribution.* - [x] I am submitting my contribution to the develop branch. - [x] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [x] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [x] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [x] I have added a test case that demonstrates my contribution, if necessary. - [x] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --------- Co-authored-by: chuanxiang yan Co-authored-by: Nijso Co-authored-by: Pedro Gomes Co-authored-by: Claude Sonnet 5 --- Common/include/CConfig.hpp | 7 ++ Common/src/CConfig.cpp | 2 + .../CNearestNeighbor.cpp | 64 ++++++++++++++- SU2_CFD/include/interfaces/CInterface.hpp | 10 +++ .../interfaces/cfd/CSlidingInterface.hpp | 10 +++ SU2_CFD/src/drivers/CMultizoneDriver.cpp | 5 ++ SU2_CFD/src/interfaces/CInterface.cpp | 7 ++ .../src/interfaces/cfd/CSlidingInterface.cpp | 77 +++++++++++++++++++ 8 files changed, 180 insertions(+), 2 deletions(-) diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index bbbc5b8a5163..c5181dc9a7e0 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -1126,6 +1126,7 @@ class CConfig { unsigned short *nSpan_iZones; /*!< \brief number of span-wise sections for each zones */ bool turbMixingPlane; /*!< \brief option for turbulent mixingplane */ bool SpatialFourier; /*!< \brief option for computing the fourier transforms for subsonic non-reflecting BC. */ + bool RelFrame_SlidingPlane; /*!< \brief option for relative frame slidingplane */ bool RampMotionFrame; /*!< \brief option for ramping up or down the motion Frame values */ bool RampOutlet; /*!< \brief option for ramping up or down the outlet values */ bool RampMUSCL; @@ -5364,6 +5365,12 @@ class CConfig { */ bool GetBoolTurbomachinery(void) const { return (nMarker_Turbomachinery !=0);} + /*! + * \brief Verify if a sliding plane for relative frame is specified from config file. + * \return boolean. + */ + bool GetBoolRelFrame_SlidingPlane(void) const { return (RelFrame_SlidingPlane !=0);} + /*! * \brief number Turbomachinery blades computed using the pitch information. * \return nBlades. diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index 8ee96abb5bc9..0501181dbedf 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -1747,6 +1747,8 @@ void CConfig::SetConfig_Options() { addStringListOption("MARKER_MIXINGPLANE_INTERFACE", nMarker_MixingPlaneInterface, Marker_MixingPlaneInterface); /*!\brief TURBULENT_MIXINGPLANE \n DESCRIPTION: Activate mixing plane also for turbulent quantities \ingroup Config*/ addBoolOption("TURBULENT_MIXINGPLANE", turbMixingPlane, false); + /*!\brief RELATIVE_FRAME_SLIDINGPLANE \n DESCRIPTION: Activate sliding plane for relative frame \ingroup Config*/ + addBoolOption("RELATIVE_FRAME_SLIDINGPLANE", RelFrame_SlidingPlane, false); /*!\brief MARKER_TURBOMACHINERY \n DESCRIPTION: Identify the boundaries for which the turbomachinery settings are applied. \ingroup Config*/ addTurboPerfOption("MARKER_TURBOMACHINERY", nMarker_Turbomachinery, Marker_TurboBoundIn, Marker_TurboBoundOut, Marker_Turbomachinery); /*!\brief NUM_SPANWISE_SECTIONS \n DESCRIPTION: Integer number of spanwise sections to compute 3D turbo BC and Performance for turbomachinery */ diff --git a/Common/src/interface_interpolation/CNearestNeighbor.cpp b/Common/src/interface_interpolation/CNearestNeighbor.cpp index 33a99994ccdd..298b34011b30 100644 --- a/Common/src/interface_interpolation/CNearestNeighbor.cpp +++ b/Common/src/interface_interpolation/CNearestNeighbor.cpp @@ -111,15 +111,75 @@ void CNearestNeighbor::SetTransferCoeff(CGeometry**** geometry, const CConfig* c /*--- Coordinates of the target point. ---*/ const su2double* Coord_i = target_geometry->nodes->GetCoord(Point_Target); + /*--- If the relative-frame sliding plane is active, precompute (once per target vertex, not per + * donor candidate) the rotated target coordinate and the donor-zone rotation matrix: both are + * invariant across every donor candidate visited in the loop below. ---*/ + const bool relframe_sp = + config[targetZone]->GetBoolRelFrame_SlidingPlane() || config[donorZone]->GetBoolRelFrame_SlidingPlane(); + su2double rotCoord_i[3] = {0.0, 0.0, 0.0}; + su2double donorRotMatrix[3][3] = {{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}}; + bool rotate_donor = false; + const su2double zeros[3] = {0.0}; + + if (relframe_sp) { + for (unsigned short iDim = 0; iDim < 3; iDim++) rotCoord_i[iDim] = Coord_i[iDim]; + + if (config[targetZone]->GetRotating_Frame() == YES) { + su2double Omega_i[3] = {0.0, 0.0, 0.0}; + su2double dt = config[targetZone]->GetDelta_UnstTimeND(); + unsigned long TimeIter = config[targetZone]->GetTimeIter(); + for (unsigned short iDim = 0; iDim < 3; iDim++) { + Omega_i[iDim] = config[targetZone]->GetRotation_Rate(iDim) / config[targetZone]->GetOmega_Ref(); + } + + /*--- Compute the rotation matrix. Note that the implicit + ordering is rotation about the x-axis, y-axis, then z-axis. ---*/ + su2double Theta = Omega_i[0] * dt * TimeIter; + su2double Phi = Omega_i[1] * dt * TimeIter; + su2double Psi = Omega_i[2] * dt * TimeIter; + su2double rotMatrix[3][3] = {{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}}; + GeometryToolbox::RotationMatrix(Theta, Phi, Psi, rotMatrix); + + /*--- Compute transformed point coordinates. ---*/ + GeometryToolbox::Rotate(rotMatrix, zeros, Coord_i, rotCoord_i); + } + + if (config[donorZone]->GetRotating_Frame() == YES) { + rotate_donor = true; + su2double Omega_j[3] = {0.0, 0.0, 0.0}; + su2double dt = config[donorZone]->GetDelta_UnstTimeND(); + unsigned long TimeIter = config[donorZone]->GetTimeIter(); + for (unsigned short iDim = 0; iDim < 3; iDim++) { + Omega_j[iDim] = config[donorZone]->GetRotation_Rate(iDim) / config[donorZone]->GetOmega_Ref(); + } + + /*--- Compute the rotation matrix. Note that the implicit + ordering is rotation about the x-axis, y-axis, then z-axis. ---*/ + su2double Theta = Omega_j[0] * dt * TimeIter; + su2double Phi = Omega_j[1] * dt * TimeIter; + su2double Psi = Omega_j[2] * dt * TimeIter; + GeometryToolbox::RotationMatrix(Theta, Phi, Psi, donorRotMatrix); + } + } + /*--- Compute all distances. ---*/ for (int iProcessor = 0, iDonor = 0; iProcessor < nProcessor; ++iProcessor) { for (auto jVertex = 0ul; jVertex < Buffer_Receive_nVertex_Donor[iProcessor]; ++jVertex) { const auto idx = iProcessor * MaxLocalVertex_Donor + jVertex; const auto pGlobalPoint = Buffer_Receive_GlobalPoint[idx]; const su2double* Coord_j = Buffer_Receive_Coord[idx]; - const auto dist2 = GeometryToolbox::SquaredDistance(nDim, Coord_i, Coord_j); - donorInfo[iDonor++] = DonorInfo(dist2, pGlobalPoint, iProcessor); + /*--- Rotate the donor point before matching if sliding plane for relative frame is activated. ---*/ + if (relframe_sp) { + su2double rotCoord_j[3] = {Coord_j[0], Coord_j[1], Coord_j[2]}; + if (rotate_donor) GeometryToolbox::Rotate(donorRotMatrix, zeros, Coord_j, rotCoord_j); + + const auto dist2 = GeometryToolbox::SquaredDistance(nDim, rotCoord_i, rotCoord_j); + donorInfo[iDonor++] = DonorInfo(dist2, pGlobalPoint, iProcessor); + } else { + const auto dist2 = GeometryToolbox::SquaredDistance(nDim, Coord_i, Coord_j); + donorInfo[iDonor++] = DonorInfo(dist2, pGlobalPoint, iProcessor); + } } } diff --git a/SU2_CFD/include/interfaces/CInterface.hpp b/SU2_CFD/include/interfaces/CInterface.hpp index 05498f1abff2..ed85008e184e 100644 --- a/SU2_CFD/include/interfaces/CInterface.hpp +++ b/SU2_CFD/include/interfaces/CInterface.hpp @@ -130,6 +130,16 @@ class CInterface { const CConfig *donor_config, unsigned long Marker_Donor, unsigned long Vertex_Donor, unsigned long Point_Donor) = 0; + /*! + * \brief A virtual member. + * \param[in] donor_config - Definition of the problem at the donor mesh. + * \param[in] donor_geometry - Geometry of the donor mesh. + * \param[in] target_config - Definition of the problem at the target mesh. + * \param[in] target_geometry - Geometry of the target mesh. + */ + inline virtual void GetDonor_Velocity_RotatingFrame(const CConfig *donor_config, CGeometry *donor_geometry, + const CConfig *target_config, CGeometry *target_geometry) {} + /*! * \brief Initializes the target variable. * \param[in] target_solution - Solution from the target mesh. diff --git a/SU2_CFD/include/interfaces/cfd/CSlidingInterface.hpp b/SU2_CFD/include/interfaces/cfd/CSlidingInterface.hpp index 6a93ac1bdd70..1cae1287915d 100644 --- a/SU2_CFD/include/interfaces/cfd/CSlidingInterface.hpp +++ b/SU2_CFD/include/interfaces/cfd/CSlidingInterface.hpp @@ -54,6 +54,16 @@ class CSlidingInterface : public CInterface { void GetDonor_Variable(CSolver *donor_solution, CGeometry *donor_geometry, const CConfig *donor_config, unsigned long Marker_Donor, unsigned long Vertex_Donor, unsigned long Point_Donor) override; + /*! + * \brief Rotate the velocity if rotating frame is applied. + * \param[in] donor_config - Definition of the problem at the donor mesh. + * \param[in] donor_geometry - Geometry of the donor mesh. + * \param[in] target_config - Definition of the problem at the target mesh. + * \param[in] target_geometry - Geometry of the target mesh. + */ + void GetDonor_Velocity_RotatingFrame(const CConfig *donor_config, CGeometry *donor_geometry, + const CConfig *target_config, CGeometry *target_geometry) override; + /*! * \brief A virtual member, initializes the target variable for sliding mesh. * \param[in] target_solution - Solution from the target mesh. diff --git a/SU2_CFD/src/drivers/CMultizoneDriver.cpp b/SU2_CFD/src/drivers/CMultizoneDriver.cpp index 979efe706d56..c776d52cff85 100644 --- a/SU2_CFD/src/drivers/CMultizoneDriver.cpp +++ b/SU2_CFD/src/drivers/CMultizoneDriver.cpp @@ -104,6 +104,11 @@ CMultizoneDriver::CMultizoneDriver(char* confFile, unsigned short val_nZone, SU2 switch (config_container[iZone]->GetKind_GridMovement()){ case RIGID_MOTION: prefixed_motion[iZone] = true; break; + case ROTATING_FRAME: + /*--- A non-deforming rotating-frame zone only needs its interface transfer coefficients + * refreshed every time step when the relative-frame sliding-plane feature is active; + * otherwise its interpolation weights are constant like any other static zone. ---*/ + prefixed_motion[iZone] = config_container[iZone]->GetBoolRelFrame_SlidingPlane(); break; default: prefixed_motion[iZone] = false; break; } diff --git a/SU2_CFD/src/interfaces/CInterface.cpp b/SU2_CFD/src/interfaces/CInterface.cpp index 6a2552d45f7a..194d04132b77 100644 --- a/SU2_CFD/src/interfaces/CInterface.cpp +++ b/SU2_CFD/src/interfaces/CInterface.cpp @@ -127,6 +127,13 @@ void CInterface::BroadcastData(const CInterpolator& interpolator, GetDonor_Variable(donor_solution, donor_geometry, donor_config, markDonor, iVertex, iPoint); + /*--- Rotate the velocity if the relative-frame sliding plane is active on either side. ---*/ + if (donor_config->GetBoolRelFrame_SlidingPlane() || target_config->GetBoolRelFrame_SlidingPlane()) { + if (donor_solution->GetnPrimVar() > 2){ + GetDonor_Velocity_RotatingFrame(donor_config, donor_geometry, target_config, target_geometry); + } + } + /*--- If in AD test recording mode, we manually adapt the tag to the target (this) zone and return to strict tag mismatch handling. ---*/ for (auto iVar = 0u; iVar < nVar; iVar++) { AD::SetTagOnVariable(Donor_Variable[iVar], target_config->GetiZone()); diff --git a/SU2_CFD/src/interfaces/cfd/CSlidingInterface.cpp b/SU2_CFD/src/interfaces/cfd/CSlidingInterface.cpp index 835fb8f79b01..96e53af55ad3 100644 --- a/SU2_CFD/src/interfaces/cfd/CSlidingInterface.cpp +++ b/SU2_CFD/src/interfaces/cfd/CSlidingInterface.cpp @@ -30,6 +30,7 @@ #include "../../../../Common/include/CConfig.hpp" #include "../../../../Common/include/geometry/CGeometry.hpp" #include "../../../include/solvers/CSolver.hpp" +#include "../../../../Common/include/toolboxes/geometry_toolbox.hpp" CSlidingInterface::CSlidingInterface(unsigned short val_nVar, unsigned short val_nConst) : CInterface() { @@ -44,6 +45,82 @@ CSlidingInterface::CSlidingInterface(unsigned short val_nVar, unsigned short val } +void CSlidingInterface::GetDonor_Velocity_RotatingFrame(const CConfig *donor_config, CGeometry *donor_geometry, + const CConfig *target_config, CGeometry *target_geometry){ + + /*--- Rotate the velocity for rotating frame. ---*/ + if (donor_config->GetRotating_Frame()==YES){ + + unsigned short nDim = donor_geometry->GetnDim(); + + su2double Theta, Phi, Psi; + su2double oriVel[3] = {0.0, 0.0, 0.0}; + su2double rotVel[3] = {0.0, 0.0, 0.0}; + su2double rotMatrix[3][3] = {{1.0,0.0,0.0},{0.0,1.0,0.0},{0.0,0.0,1.0}}; + su2double Omega[3] = {0.0, 0.0, 0.0}; + const su2double zeros[3] = {0.0}; + su2double dt = donor_config->GetDelta_UnstTimeND(); + unsigned long TimeIter = donor_config->GetTimeIter(); + for (unsigned short iDim=0; iDim<3; iDim++){ + Omega[iDim] = donor_config->GetRotation_Rate(iDim)/donor_config->GetOmega_Ref(); + } + + /*--- Compute the rotation matrix. Note that the implicit + ordering is rotation about the x-axis, y-axis, then z-axis. ---*/ + Theta = Omega[0]*dt*TimeIter; Phi = Omega[1]*dt*TimeIter; Psi = Omega[2]*dt*TimeIter; + GeometryToolbox::RotationMatrix(Theta, Phi, Psi, rotMatrix); + + /*--- Velocities before rotating. ---*/ + for (unsigned short iDim = 0; iDim < nDim; ++iDim) + oriVel[iDim] = Donor_Variable[iDim+1]; + + /*--- Compute transformed velocities. ---*/ + GeometryToolbox::Rotate(rotMatrix, zeros, oriVel, rotVel); + + /*--- Set the rotated velocity. ---*/ + for (unsigned short iDim = 0; iDim < nDim; iDim++) { + Donor_Variable[iDim+1] = rotVel[iDim]; + } + + } + + if (target_config->GetRotating_Frame()==YES){ + + unsigned short nDim = target_geometry->GetnDim(); + + su2double Theta, Phi, Psi; + su2double oriVel[3] = {0.0, 0.0, 0.0}; + su2double rotVel[3] = {0.0, 0.0, 0.0}; + su2double rotMatrix[3][3] = {{1.0,0.0,0.0},{0.0,1.0,0.0},{0.0,0.0,1.0}}; + su2double Omega[3] = {0.0, 0.0, 0.0}; + const su2double zeros[3] = {0.0}; + su2double dt = target_config->GetDelta_UnstTimeND(); + unsigned long TimeIter = target_config->GetTimeIter(); + for (unsigned short iDim=0; iDim<3; iDim++){ + Omega[iDim] = -target_config->GetRotation_Rate(iDim)/target_config->GetOmega_Ref(); + } + + /*--- Compute the rotation matrix. Note that the implicit + ordering is rotation about the x-axis, y-axis, then z-axis. ---*/ + Theta = Omega[0]*dt*TimeIter; Phi = Omega[1]*dt*TimeIter; Psi = Omega[2]*dt*TimeIter; + GeometryToolbox::RotationMatrix(Theta, Phi, Psi, rotMatrix); + + /*--- Velocities before rotating. ---*/ + for (unsigned short iDim = 0; iDim < nDim; ++iDim) + oriVel[iDim] = Donor_Variable[iDim+1]; + + /*--- Compute transformed velocities. ---*/ + GeometryToolbox::Rotate(rotMatrix, zeros, oriVel, rotVel); + + /*--- Set the rotated velocity. ---*/ + for (unsigned short iDim = 0; iDim < nDim; iDim++) { + Donor_Variable[iDim+1] = rotVel[iDim]; + } + + } + +} + void CSlidingInterface::GetDonor_Variable(CSolver *donor_solution, CGeometry *donor_geometry, const CConfig *donor_config, unsigned long Marker_Donor, unsigned long Vertex_Donor, unsigned long Point_Donor) { From a6cfc3eea7456a53df7aa5032cc55dabef947379 Mon Sep 17 00:00:00 2001 From: Pragyaan Gaur Date: Mon, 14 Sep 2026 01:19:46 +0530 Subject: [PATCH 52/61] Fix broken append and var_names paths in SU2.io.data (#2867) ### Proposed Changes Fixes two defects in `SU2_PY/SU2/io/data.py`. Both make documented public API paths of `SU2.io` raise immediately on any input. **1. `save_data(..., append=True)` raises `NameError`.** The append branch calls `load(...)`, which does not exist in the module. The function is `load_data()`. The stale end-of-function markers (`#: def load()`, `#: def save()`) suggest the functions were renamed at some point and this internal call site was missed. `git log -S` dates it to the v2.0.2 import. Simply calling `load_data()` here is **not** a correct fix. `filelock` is not reentrant (it is an `O_CREAT | O_EXCL` lock on a `.lock` sidecar file), and `save_data` makes this call from inside its own `with filelock(file_name)` block. The naive change therefore swaps an instant `NameError` for a 10 second stall followed by `FileLockException`, which is strictly worse. Instead the lock-free read body is extracted into a private `_read_data()` helper. `load_data()` wraps it in the filelock, and `save_data()` calls it directly under the lock it already holds. This keeps the read-modify-write atomic, rather than hoisting the read outside the lock and introducing a TOCTOU race. **2. `load_data(var_names=...)` raises `RuntimeError`.** The filter deletes keys while iterating `input_data.keys()`. This was safe on Python 2, where `.keys()` returned a list copy, and has been broken since the Python 3 migration. It only triggers when at least one key is actually dropped, which is why a single-key call appears to work. Fixed by iterating a copy of the keys. Both fixes sit in the same pair of functions and share one root cause, namely leftovers from a rename and from the Python 2 to 3 migration, so I have kept them in a single PR. Happy to split them if reviewers prefer. Also removes a `scipy` import probe in `load_data()` that becomes dead once the read moves into the helper, and corrects the two stale marker comments. ## Related Work No open issue. No in-tree caller currently passes `append=True` or `var_names`, so there is no regression risk to the optimization drivers. These are broken paths in a documented public API rather than a live crash in the design loop. Out of scope, flagged for a possible follow-up: the matlab (`.mat`) path of `save_data` fails with `TypeError: 'method' object does not support item assignment` inside `mat_bunch`. I verified this behaves identically before and after this change, and did not touch it, in order to keep this PR to one thing. Could a maintainer please add the `changelog:fix` label.] ## Verification `SU2_PY` has no Python test harness (`UnitTests/` is C++/Catch2 only), so no test file is added. The following reproduces both failures on `develop` and passes on this branch: ```python import sys, os, tempfile sys.path.insert(0, "SU2_PY") os.environ.setdefault("SU2_RUN", tempfile.mkdtemp()) from SU2.io.data import save_data, load_data os.chdir(tempfile.mkdtemp()) save_data("z.pkl", {"DRAG": 0.1, "LIFT": 0.9, "MOMENT_Z": 0.02}) save_data("z.pkl", {"CD": 1.0}, append=True) # was NameError print(sorted(load_data("z.pkl"))) # ['CD', 'DRAG', 'LIFT', 'MOMENT_Z'] print(load_data("z.pkl", var_names="DRAG")) # was RuntimeError ``` Before and after on `develop`: | Call | Before | After | | --- | --- | --- | | `save_data(append=True)` | `NameError` | OK | | `load_data(var_names="DRAG")` | `RuntimeError` | OK | | `load_data(var_names=["DRAG", "LIFT"])` | `RuntimeError` | OK | | `load_data()` plain (regression check) | OK | OK | `pre-commit run --files SU2_PY/SU2/io/data.py` passes. ## PR Checklist - [x] I am submitting my contribution to the develop branch. - [ ] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). *(N/A, Python only)* - [x] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [x] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [ ] I have added a test case that demonstrates my contribution, if necessary. *(No Python test harness exists in the repo; a runnable reproduction is included above.)* - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. *(N/A, internal bug fix with no user-facing API change.)* Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> --- SU2_PY/SU2/io/data.py | 94 ++++++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 42 deletions(-) diff --git a/SU2_PY/SU2/io/data.py b/SU2_PY/SU2/io/data.py index a2265c119842..810e901786ee 100644 --- a/SU2_PY/SU2/io/data.py +++ b/SU2_PY/SU2/io/data.py @@ -67,13 +67,6 @@ def load_data(file_name, var_names=None, file_format="infer", core_name="python_ ('.mat','.pkl') """ - try: - import scipy.io - - scipy_loaded = True - except ImportError: - scipy_loaded = False - if not os.path.exists(file_name): raise Exception("File does not exist: %s" % file_name) @@ -87,31 +80,7 @@ def load_data(file_name, var_names=None, file_format="infer", core_name="python_ # get filelock with filelock(file_name): - - # LOAD MATLAB - if file_format == "matlab" and scipy_loaded: - input_data = scipy.io.loadmat( - file_name=file_name, - squeeze_me=False, - chars_as_strings=True, - struct_as_record=True, - ) - # pull core variable - assert core_name in input_data, "core data not found" - input_data = input_data[core_name] - - # convert recarray to dictionary - input_data = rec2dict(input_data) - - # LOAD PICKLE - elif file_format == "pickle": - input_data = load_pickle(file_name) - # pull core variable - assert core_name in input_data, "core data not found" - input_data = input_data[core_name] - - #: if file_format - + input_data = _read_data(file_name, file_format, core_name) #: with filelock # load specified varname into dictionary @@ -121,7 +90,8 @@ def load_data(file_name, var_names=None, file_format="infer", core_name="python_ var_names = [ var_names, ] - for key in input_data.keys(): + # iterate a copy of the keys, the dictionary is modified in the loop + for key in list(input_data.keys()): if not key in var_names: del input_data[key] #: for key @@ -130,14 +100,59 @@ def load_data(file_name, var_names=None, file_format="infer", core_name="python_ return input_data -#: def load() +#: def load_data() # ------------------------------------------------------------------- -# Save a Dictionary of Data +# Read a Dictionary of Data, without locking # ------------------------------------------------------------------- +def _read_data(file_name, file_format, core_name): + """data = _read_data( file_name, file_format, core_name ) + + Reads the data dictionary from file, assuming the caller already + holds the filelock for file_name. filelock is not reentrant, so + this must never acquire the lock itself. + """ + + try: + import scipy.io + + scipy_loaded = True + except ImportError: + scipy_loaded = False + + # LOAD MATLAB + if file_format == "matlab" and scipy_loaded: + input_data = scipy.io.loadmat( + file_name=file_name, + squeeze_me=False, + chars_as_strings=True, + struct_as_record=True, + ) + # pull core variable + assert core_name in input_data, "core data not found" + input_data = input_data[core_name] + + # convert recarray to dictionary + input_data = rec2dict(input_data) + + # LOAD PICKLE + elif file_format == "pickle": + input_data = load_pickle(file_name) + # pull core variable + assert core_name in input_data, "core data not found" + input_data = input_data[core_name] + + #: if file_format + + return input_data + + +#: def _read_data() + + def save_data( file_name, data_dict, append=False, file_format="infer", core_name="python_data" ): @@ -189,12 +204,7 @@ def save_data( if not os.path.exists(file_name): raise Exception("Cannot append, file does not exist: %s" % file_name) # load old data - data_dict_old = load( - file_name=file_name, - var_names=None, - file_format=file_format, - core_name=core_name, - ) + data_dict_old = _read_data(file_name, file_format, core_name) # check for keys not in new data for key, value in data_dict_old.items(): if not (key in data_dict): @@ -227,7 +237,7 @@ def save_data( return -#: def save() +#: def save_data() # ------------------------------------------------------------------- From 17e976e02977310d4473516d788ae26ef45de4ef Mon Sep 17 00:00:00 2001 From: thijsaalbers Date: Tue, 15 Sep 2026 04:05:22 +0200 Subject: [PATCH 53/61] Pressure Based Solver (#2812) ## Proposed Changes The current work (part of GSoC) provides a working version of a pressure-based algorithm for the incompressible flow solver as an alternative to the existing Density-based solver. Below, the reader may find the algorithm which has been implemented, as well as the current progress of the code and challenges. All the way at the bottom one can find performance comparisons between the DB and PB solvers for some test cases. ### Algorithm A lot of versions of pressure-based algorithms exist, and many different versions can be implemented. Here, we opt for versions of the original SIMPLE/PISO algorithm, it is briefly defined here for clarity. First, the momentum equations are solved, starting from the previous time step's velocity $\vec{u}^{(0)}$, pressure $p^{(0)}$, and face velocity $\vec{u}_f^{(0)}$. The resulting momentum is the predicted momentum, here its discretized form is shown, as its coefficients are used in the subsequent equations $A_p(\rho\vec{u})_p^{(1)}+\sum_n A_n (\rho\vec{u})_n^{(1)}=-V\nabla p^{(0)}+S_m$ The subsequent momentum is not necessarily incompressible, the pressure correction equation can be derived by rewriting it as follows, using a term often called H by A $(\rho\vec{u})_p^{(1)}=-\frac{\sum_n A_n (\rho\vec{u})_n^{(1)}}{A_p}-\frac{V}{A_p}\nabla p+S_m=\frac{H((\rho\vec{u})^{(1)})}{A}-\frac{V}{A_p}\nabla p^{(0)}+S_m$ $(\rho\vec{u})_p^{(2)}=\frac{H((\rho\vec{u})^{(1)})}{A}-\frac{V}{A_p}\nabla p^{(1)}+S_m$ Note how a simplification is used here where the HbyA term is neglected. Subtracting these two equations yields the first pressure correction equation for $p'$ as $\nabla \cdot \left( \frac{V}{A_p}\nabla p'\right)=\nabla \cdot \left( \rho \vec{u}^{(1)}\right)$ Make note that for the divergence here, we require the face mass fluxes, which are computed using Rhie-Chow interpolation to avoid odd-even decoupling. After the equation is solved, using the pressure correction $p'$, the pressure and momentum are corrected according to $p^{(1)} = p^{(0)} + p' ,\quad (\rho u)^{(2)} = (\rho u)^{(1)}+(\rho u)'.\quad (\rho u)'= -\frac{V}{A_p}\nabla p'$ So far, this is equal to a pseudo-transient version of the SIMPLE algorithm. This algorithm however suffers from a very tight stability condition on the time-step size. Therefore, multiple pressure corrections can be applied, which for two corrections is originally called the PISO algorithm. The second pressure correction does not neglect the HbyA term, which then results in the equation $\nabla \cdot \left( \frac{V}{A_p}\nabla p'\right)=\nabla \cdot \left( \rho \vec{u}^{(2)}\right)+\nabla\cdot\left(\frac{H((\rho u)')}{A}\right)$ And the new correction equations are defined as $p^{(2)} = p^{(1)} + p' ,\quad (\rho u)^{(3)} = (\rho u)^{(2)}+(\rho u)'.\quad (\rho u)'= \frac{H((\rho u)')}{A}-\frac{V}{A_p}\nabla p'$ Note that HbyA here uses the previous velocity correction and is thus the same quantity as the one used in the second pressure correction equation. Later pressure correction equations follow analogously. ### Progress: - Pressure-based solver added as alternative to density-based solver for the incompressible flow equations. - The pressure-based solver has only been tested for constant density cases for basic Navier-Stokes and Euler flow. - The pressure-based solver is implemented based on a pseudo time-stepping approach to remain consistent with the other solvers in SU2. The pressure-based solver is currently set to the SIMPLE algorithm by default, with options for SIMPLEC and PISO available. - The Poisson solver is a major bottleneck in the computation speed. To account for this, an option was added to use a different linear solver and preconditioner for the poisson solver. - For details on the implementation of the algorithm and the responsibility distribution please see the file CPBFluidIteration.cpp. - A tutorial showcasing the different options of the PB solver for the lid driven cavity flow problem has been added, please see su2code/Tutorials#86 and su2code/su2code.github.io#218 - Regression tests have been added. ### Future work: #### Performance: - The Poisson solver can sometimes struggle a lot due to high Reynolds numbers and fine meshes, and thus require a ridiculous number of iterations to converge reasonably. Possible fixes include adding multigrid support or a DIC preconditioner (far less efficient). Multigrid support is tricky as SU2 currently only considers multigrid for the main (flow) solver and not for auxiliary solvers. - Convergence issues with RANS (SA and SST) on fine meshes with high Reynolds numbers. Tests have shown that cases such as flow over a flat plate converges fine. However, external aerodynamic cases such as the naca0012 RANS test case do not converge well at all. The convergence does slightly improve when we switch out the mesh for a more uniform unstructured mesh without large aspect ratio cells in the wake of the airfoil, although this only slightly helps. The flat plate turbulence test case also uses large aspect ratio cells so this is not the sole issue. The RANS solver also often requires many iterations of the Poisson solver to converge reasonably, this is however not the reason for the lack of convergence. - Periodic boundary conditions have not been implemented/tested at all as of yet. - Any code related to adjoints has not been considered at all either. #### Code: - Parallelization with OMP gives wrong results due to a unknown issue in the Poisson solver, MPI however does work as expected. - The restart solution currently does not write the edge mass fluxes which are used by the pressure-based solver. Therefore restarted solutions have to estimate these mass fluxes based on the average of the nodal solutions. This results in the restarted solutions starting from slightly different residuals. A fix for this issue can be to let the edge mass fluxes be stored in the restart file. - Support for the energy equation is added trough both the enthalpy equation as well as trough the weakly coupled heat solver. This support has however not yet been verified trough actual test cases. - The numerics class for the convective residuals write the continuity parts of the flux and jacobians. This will allow for a coupled solver in the future, but as it stands now this code is _not_ used. ### TODO list - Fix issues mentioned above (left for future work) ## Related Work This work is based on earlier attempts by Nitish Anand (2024) and Akshay Koodly (2021), see feature branches feature_PBFlow_V8 and feature_Pressure_based respectively. Also see PR #2210 ## PR Checklist - [x] I am submitting my contribution to the develop branch. - [x] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [x] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [x] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [x] I have added a test case that demonstrates my contribution, if necessary. - [x] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. ## Result showcase ### Inviscid Hydrofoil *Convergence history of the inviscid flow around a hydrofoil at a 5 degree aoa.* *The pressure coefficient along the surface of the hydrofoil at a 5 degree aoa and the corresponding lift coefficients, X-FOIL predicts C_L=0.6.* ### Lid Driven Cavity *Convergence history of the lid driven cavity problem, note that CFL=60 is the highest stable CFL for the PB solver, whereas the DB solver does not have this CFL related stability issue.* ### Flatplate RANS *The skin friction coefficient for turbulent flow over a (rough) flat plate with SA.* --------- Co-authored-by: Pedro Gomes Co-authored-by: Claude Sonnet 5 Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> --- Common/include/CConfig.hpp | 59 +- Common/include/linear_algebra/CSysMatrix.hpp | 5 +- Common/include/option_structure.hpp | 42 +- Common/src/CConfig.cpp | 71 ++ Common/src/linear_algebra/CSysMatrix.cpp | 7 +- Common/src/linear_algebra/CSysSolve.cpp | 20 + SU2_CFD/include/iteration/CFluidIteration.hpp | 16 + .../include/iteration/CPBFluidIteration.hpp | 66 ++ .../flow/convection/pressure_based.hpp | 160 ++++ .../include/numerics_simd/CNumericsSIMD.cpp | 4 +- SU2_CFD/include/output/CFlowIncOutput.hpp | 1 + SU2_CFD/include/solvers/CIncEulerSolver.hpp | 38 + SU2_CFD/include/solvers/CPoissonSolver.hpp | 255 ++++++ SU2_CFD/include/solvers/CScalarSolver.hpp | 6 +- SU2_CFD/include/solvers/CScalarSolver.inl | 4 +- SU2_CFD/include/solvers/CSolver.hpp | 35 + SU2_CFD/include/solvers/CSolverFactory.hpp | 1 + .../include/variables/CIncEulerVariable.hpp | 23 + .../include/variables/CPoissonVariable.hpp | 99 +++ SU2_CFD/include/variables/CVariable.hpp | 18 + SU2_CFD/src/drivers/CDriver.cpp | 98 ++- SU2_CFD/src/iteration/CFluidIteration.cpp | 56 +- SU2_CFD/src/iteration/CIterationFactory.cpp | 8 +- SU2_CFD/src/iteration/CPBFluidIteration.cpp | 111 +++ SU2_CFD/src/meson.build | 4 + .../flow/convection/pressure_based.cpp | 201 +++++ SU2_CFD/src/output/CFlowIncOutput.cpp | 39 +- SU2_CFD/src/solvers/CEulerSolver.cpp | 2 +- .../src/solvers/CGradientSmoothingSolver.cpp | 8 +- SU2_CFD/src/solvers/CIncEulerSolver.cpp | 778 ++++++++++++++++-- SU2_CFD/src/solvers/CIncNSSolver.cpp | 7 +- SU2_CFD/src/solvers/CPoissonSolver.cpp | 592 +++++++++++++ SU2_CFD/src/solvers/CSolver.cpp | 34 + SU2_CFD/src/solvers/CSolverFactory.cpp | 13 + SU2_CFD/src/variables/CFlowVariable.cpp | 2 +- SU2_CFD/src/variables/CIncEulerVariable.cpp | 6 + SU2_CFD/src/variables/CPoissonVariable.cpp | 57 ++ TestCases/hybrid_regression.py | 35 + .../naca0012/incomp_pb_NACA0012.cfg | 109 +++ .../incomp_navierstokes/bend/pb_lam_bend.cfg | 115 +++ .../cylinder/incomp_pb_cylinder.cfg | 102 +++ .../cylinder/pb_poly_cylinder.cfg | 123 +++ .../incomp_navierstokes/sphere/pb_sphere.cfg | 108 +++ .../sphere/pb_sphere_urf.cfg | 109 +++ .../pb_rough_flatplate_incomp.cfg | 130 +++ TestCases/parallel_regression.py | 61 ++ TestCases/serial_regression.py | 26 + TestCases/tutorials.py | 12 + config_template.cfg | 35 + 49 files changed, 3743 insertions(+), 168 deletions(-) create mode 100644 SU2_CFD/include/iteration/CPBFluidIteration.hpp create mode 100644 SU2_CFD/include/numerics/flow/convection/pressure_based.hpp create mode 100644 SU2_CFD/include/solvers/CPoissonSolver.hpp create mode 100644 SU2_CFD/include/variables/CPoissonVariable.hpp create mode 100644 SU2_CFD/src/iteration/CPBFluidIteration.cpp create mode 100644 SU2_CFD/src/numerics/flow/convection/pressure_based.cpp create mode 100644 SU2_CFD/src/solvers/CPoissonSolver.cpp create mode 100644 SU2_CFD/src/variables/CPoissonVariable.cpp create mode 100644 TestCases/incomp_euler/naca0012/incomp_pb_NACA0012.cfg create mode 100644 TestCases/incomp_navierstokes/bend/pb_lam_bend.cfg create mode 100644 TestCases/incomp_navierstokes/cylinder/incomp_pb_cylinder.cfg create mode 100644 TestCases/incomp_navierstokes/cylinder/pb_poly_cylinder.cfg create mode 100644 TestCases/incomp_navierstokes/sphere/pb_sphere.cfg create mode 100644 TestCases/incomp_navierstokes/sphere/pb_sphere_urf.cfg create mode 100644 TestCases/incomp_rans/rough_flatplate/pb_rough_flatplate_incomp.cfg diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index c5181dc9a7e0..36e802e99f67 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -135,7 +135,8 @@ class CConfig { Hold_GridFixed, /*!< \brief Flag hold fixed some part of the mesh during the deformation. */ Axisymmetric, /*!< \brief Flag for axisymmetric calculations */ Enable_Cuda, /*!< \brief Flag for switching GPU computing*/ - Integrated_HeatFlux; /*!< \brief Flag for heat flux BC whether it deals with integrated values.*/ + Integrated_HeatFlux, /*!< \brief Flag for heat flux BC whether it deals with integrated values.*/ + Pressure_Based; /*!< \brief Flag to check if we are using a pressure-based system.*/ su2double Buffet_k; /*!< \brief Sharpness coefficient for buffet sensor.*/ su2double Buffet_lambda; /*!< \brief Offset parameter for buffet sensor.*/ su2double Damp_Engine_Inflow; /*!< \brief Damping factor for the engine inlet. */ @@ -525,6 +526,8 @@ class CConfig { Kind_Gradient_Method_Recon, /*!< \brief Numerical method for computation of spatial gradients used for upwind reconstruction. */ Kind_Deform_Linear_Solver, /*!< Numerical method to deform the grid */ Kind_Deform_Linear_Solver_Prec, /*!< \brief Preconditioner of the linear solver. */ + Kind_Poisson_Linear_Solver, /*!< \brief Numerical solver for the poisson equation. */ + Kind_Poisson_Linear_Solver_Prec, /*!< \brief Preconditioner of the linear solver of the poisson equation. */ Kind_Linear_Solver, /*!< \brief Numerical solver for the implicit scheme. */ Kind_Linear_Solver_Prec, /*!< \brief Preconditioner of the linear solver. */ Kind_DiscAdj_Linear_Solver, /*!< \brief Linear solver for the discrete adjoint system. */ @@ -587,6 +590,9 @@ class CConfig { Kind_Upwind_Heat, /*!< \brief Upwind scheme for the heat transfer model. */ Kind_Upwind_Template; /*!< \brief Upwind scheme for the template model. */ + PBITER Kind_PBIter; /*< \brief Kind of pressure-based algorithm that is used. */ + INCOMP_SYSTEM Kind_Incomp_System; /*< \brief Kind of incompressible solver. */ + bool MUSCL, /*!< \brief MUSCL scheme (for the runtime eq. system). */ MUSCL_Flow, /*!< \brief MUSCL scheme for the flow equations.*/ MUSCL_Turb, /*!< \brief MUSCL scheme for the turbulence equations.*/ @@ -638,8 +644,10 @@ class CConfig { bool InletUseNormal; /*!< \brief Flag for whether to use the local normal as the flow direction for a pressure inlet. */ su2double Linear_Solver_Error; /*!< \brief Min error of the linear solver for the implicit formulation. */ su2double Deform_Linear_Solver_Error; /*!< \brief Min error of the linear solver for the implicit formulation. */ + su2double Poisson_Linear_Solver_Error; /*!< \brief Min error of the linear solver for the poisson equation. */ su2double Linear_Solver_Smoother_Relaxation; /*!< \brief Relaxation factor for iterative linear smoothers. */ unsigned long Linear_Solver_Iter; /*!< \brief Max iterations of the linear solver for the implicit formulation. */ + unsigned long Poisson_Linear_Solver_Iter; /*!< \brief Max iterations of the linear solver for the poisson solver*/ unsigned long Deform_Linear_Solver_Iter; /*!< \brief Max iterations of the linear solver for the implicit formulation. */ unsigned long Linear_Solver_Restart_Frequency; /*!< \brief Restart frequency of the linear solver for the implicit formulation. */ unsigned long Linear_Solver_Restart_Deflation; /*!< \brief Number of vectors used for deflated restarts. */ @@ -655,6 +663,14 @@ class CConfig { su2double SemiSpan; /*!< \brief Wing Semi span. */ su2double MSW_Alpha; /*!< \brief Coefficient for blending states in the MSW scheme. */ su2double Roe_Kappa; /*!< \brief Relaxation of the Roe scheme. */ + + struct CSIMPLE_Options { + su2double Transient_Term_Removal_Factor; /*!< \brief Coefficient for removing the transient term from the momentum coefficient. */ + su2double Relaxation_Factor_Pressure; /*!< \brief Relaxation coefficient of the pressure corrections in the SIMPLE solver. */ + bool AutomaticRelaxationFactors; /*!< \brief option for automatically computing relaxation factors for flow corrections in SIMPLE. */ + unsigned short nCorrections_PISO; /*!< \brief Number of corrections used in PISO algorithm. */ + } SIMPLE_Options; + su2double Relaxation_Factor_Adjoint; /*!< \brief Relaxation coefficient for variable updates of adjoint solvers. */ su2double Relaxation_Factor_CHT; /*!< \brief Relaxation coefficient for the update of conjugate heat variables. */ su2double EntropyFix_Coeff; /*!< \brief Entropy fix coefficient. */ @@ -4007,6 +4023,18 @@ class CConfig { */ ENUM_REGIME GetKind_Regime(void) const { return Kind_Regime; } + /*! + * \brief Kind of incompressible solver formulation. + * \return Kind of incompressible solver. + */ + INCOMP_SYSTEM GetKind_Incomp_System(void) const { return Kind_Incomp_System; } + + /*! + * \brief Kind of iteration used for pressure based iterations. + * \return Kind of iteration used for pressure based iterations. + */ + PBITER GetKind_PBIter(void) const { return Kind_PBIter; } + /*! * \brief Governing equations of the flow (it can be different from the run time equation). * \param[in] val_zone - Zone where the soler is applied. @@ -4357,12 +4385,24 @@ class CConfig { */ unsigned short GetKind_Linear_Solver_Prec(void) const { return Kind_Linear_Solver_Prec; } + /*! + * \brief Get the kind of preconditioner for the linear solver of the poisson problem. + * \return Numerical preconditioner for poisson equation (solving the linear system). + */ + unsigned short GetKind_Poisson_Linear_Solver_Prec(void) const { return Kind_Poisson_Linear_Solver_Prec; } + /*! * \brief Get the kind of solver for the implicit solver. * \return Numerical solver for implicit formulation (solving the linear system). */ unsigned short GetKind_Deform_Linear_Solver(void) const { return Kind_Deform_Linear_Solver; } + /*! + * \brief Get the kind of solver for the poisson equation. + * \return Numerical solver for poisson equation (solving the linear system). + */ + unsigned short GetKind_Poisson_Linear_Solver(void) const { return Kind_Poisson_Linear_Solver; } + /*! * \brief Get min error of the linear solver for the implicit formulation. * \return Min error of the linear solver for the implicit formulation. @@ -4375,12 +4415,24 @@ class CConfig { */ su2double GetDeform_Linear_Solver_Error(void) const { return Deform_Linear_Solver_Error; } + /*! + * \brief Get min error of the linear solver for the poisson equation. + * \return Min error of the linear solver for the poisson equation. + */ + su2double GetPoisson_Linear_Solver_Error(void) const { return Poisson_Linear_Solver_Error; } + /*! * \brief Get max number of iterations of the linear solver for the implicit formulation. * \return Max number of iterations of the linear solver for the implicit formulation. */ unsigned long GetLinear_Solver_Iter(void) const { return Linear_Solver_Iter; } + /*! + * \brief Get max number of iterations of the linear solver for the poisson equation. + * \return Max number of iterations of the linear solver for the poisson equation. + */ + unsigned long GetPoisson_Linear_Solver_Iter(void) const { return Poisson_Linear_Solver_Iter; } + /*! * \brief Get max number of iterations of the linear solver for the implicit formulation. * \return Max number of iterations of the linear solver for the implicit formulation. @@ -4393,6 +4445,11 @@ class CConfig { */ const CIluOptions& GetIluOptions(void) const { return IluOptions; } + /*! + * \brief Get the SIMPLE (and PISO) algorithm options, see CSIMPLE_Options. + */ + const CSIMPLE_Options& GetSIMPLE_Options(void) const { return SIMPLE_Options; } + /*! * \brief Get restart frequency of the linear solver for the implicit formulation. * \return Restart frequency of the linear solver for the implicit formulation. diff --git a/Common/include/linear_algebra/CSysMatrix.hpp b/Common/include/linear_algebra/CSysMatrix.hpp index 465ba7b576dd..c835bacefde9 100644 --- a/Common/include/linear_algebra/CSysMatrix.hpp +++ b/Common/include/linear_algebra/CSysMatrix.hpp @@ -38,6 +38,7 @@ #include #include #include +#include /*--- In forward mode the matrix is not of a built-in type. ---*/ #if defined(HAVE_MKL) && !defined(CODI_FORWARD_TYPE) @@ -719,15 +720,15 @@ class CSysMatrix { * \param[in] geometry - Geometrical definition of the problem. * \param[in] config - Definition of the particular problem. * \param[in] needTranspPtr - If the L/U transpose maps should be built, used for "SetDiagonalAsColumnSum". - * \param[in] grad_mode - Gradient smoothing mode, only used to detect the right preconditioner type. * \param[in] allow_quant - Quantization is only possible with solvers that "set and forget" the off-diagonal * blocks of the matrix. Solvers that perform multiple updates would lose too much information, so * that pattern is not supported with quantization (the code will hit null pointers). It is up to * the solver to declare whether it will "set and forget". + * \param[in] override_prec - Decide if, and with what argument to override the preconditioner. */ void Initialize(unsigned long npoint, unsigned long npointdomain, unsigned short nvar, unsigned short neqn, bool EdgeConnect, CGeometry* geometry, const CConfig* config, bool needTranspPtr = false, - bool grad_mode = false, bool allow_quant = false); + bool allow_quant = false, std::optional override_prec = std::nullopt); /*! * \brief Compresses off-diagonal blocks into quantized form for use with USE_QUANTIZATION. diff --git a/Common/include/option_structure.hpp b/Common/include/option_structure.hpp index e74b1ac9fcac..03b00b23e3d8 100644 --- a/Common/include/option_structure.hpp +++ b/Common/include/option_structure.hpp @@ -77,7 +77,7 @@ const unsigned int MAX_PARAMETERS = 10; /*!< \brief Maximum number of para const unsigned int MAX_NUMBER_PERIODIC = 10; /*!< \brief Maximum number of periodic boundary conditions. */ const unsigned int MAX_STRING_SIZE = 400; /*!< \brief Maximum size of a generic string. */ const unsigned int MAX_NUMBER_FFD = 15; /*!< \brief Maximum number of FFDBoxes for the FFD. */ -enum: unsigned int{MAX_SOLS = 13}; /*!< \brief Maximum number of solutions at the same time (dimension of solution container array). */ +enum: unsigned int{MAX_SOLS = 14}; /*!< \brief Maximum number of solutions at the same time (dimension of solution container array). */ const unsigned int MAX_TERMS = 7; /*!< \brief Maximum number of terms in the numerical equations (dimension of solver container array). */ const unsigned int MAX_ZONES = 3; /*!< \brief Maximum number of zones. */ const unsigned int MAX_FE_KINDS = 7; /*!< \brief Maximum number of Finite Elements. */ @@ -329,6 +329,7 @@ enum class MAIN_SOLVER { FEM_RANS, /*!< \brief Definition of the finite element Reynolds-averaged Navier-Stokes' (RANS) solver. */ FEM_LES, /*!< \brief Definition of the finite element Large Eddy Simulation Navier-Stokes' (LES) solver. */ MULTIPHYSICS, + POISSON_EQUATION, /*!< \brief Definition of the Poisson equation solver. */ NEMO_EULER, /*!< \brief Definition of the NEMO Euler solver. */ NEMO_NAVIER_STOKES, /*!< \brief Definition of the NEMO NS solver. */ }; @@ -404,6 +405,31 @@ static const MapType MatComp_Map = { MakePair("NEARLY_INCOMPRESSIBLE", STRUCT_COMPRESS::NEARLY_INCOMP) }; +/*! + * \brief Type of incompressible solver + */ +enum class INCOMP_SYSTEM { + DENSITY_BASED, /*!< \brief Density-based. */ + PRESSURE_BASED, /*!< \brief Pressure-based. */ +}; +static const MapType Incomp_Map = { + MakePair("DENSITY_BASED", INCOMP_SYSTEM::DENSITY_BASED) + MakePair("PRESSURE_BASED", INCOMP_SYSTEM::PRESSURE_BASED) +}; + +/*! + * \brief Type of iteration + */ +enum class PBITER { + SIMPLE, /*!< \brief SIMPLE algorithm. */ + SIMPLEC, /*!< \brief SIMPLEC algorithm. */ +}; + +static const MapType PBIter_Map = { + MakePair("SIMPLE", PBITER::SIMPLE) + MakePair("SIMPLEC", PBITER::SIMPLEC) +}; + /*! * \brief Types of interpolators */ @@ -544,6 +570,7 @@ enum RUNTIME_TYPE { RUNTIME_ADJRAD_SYS = 24, /*!< \brief One-physics case, the code is solving the adjoint radiation model. */ RUNTIME_SPECIES_SYS = 25, /*!< \brief One-physics case, the code is solving the species model. */ RUNTIME_ADJSPECIES_SYS = 26,/*!< \brief One-physics case, the code is solving the adjoint species model. */ + RUNTIME_POISSON_SYS = 27, /*!< \brief One-physics case, the code is solving the poisson equation. */ }; enum SOLVER_TYPE : const int { @@ -562,6 +589,7 @@ enum RUNTIME_TYPE { ADJSPECIES_SOL=12, /*!< \brief Position of the adjoint of the species solver. */ FEA_SOL=0, /*!< \brief Position of the Finite Element flow solution in the solver container array. */ ADJFEA_SOL=1, /*!< \brief Position of the continuous adjoint Finite Element flow solution in the solver container array. */ + POISSON_SOL=13, /*!< \brief Position of the poisson solution in the solver container array */ TEMPLATE_SOL=0, /*!< \brief Position of the template solution. */ }; @@ -893,7 +921,8 @@ enum class CENTERED { LAX, /*!< \brief Lax-Friedrich centered numerical method. */ JST_MAT, /*!< \brief JST with matrix dissipation. */ JST_KE, /*!< \brief Kinetic Energy preserving Jameson-Smith-Turkel centered numerical method. */ - LD2 /*!< \brief Low-Dissipation Low-Dispersion (LD2) centered scheme. */ + LD2, /*!< \brief Low-Dissipation Low-Dispersion (LD2) centered scheme. */ + CDS /*!< \brief Central Difference Scheme used for pressure based solver. */ }; static const MapType Centered_Map = { MakePair("NONE", CENTERED::NONE) @@ -902,6 +931,7 @@ static const MapType Centered_Map = { MakePair("JST_MAT", CENTERED::JST_MAT) MakePair("LAX-FRIEDRICH", CENTERED::LAX) MakePair("LD2", CENTERED::LD2) + MakePair("CDS", CENTERED::CDS) }; @@ -928,7 +958,8 @@ enum class UPWIND { AUSMPLUSUP, /*!< \brief AUSM+ -up numerical method (All Speed) */ AUSMPLUSUP2, /*!< \brief AUSM+ -up2 numerical method (All Speed) */ AUSMPLUSM, /*!< \breif AUSM+M numerical method. (NEMO Only)*/ - BOUNDED_SCALAR /*!< \brief Scalar advection numerical method. */ + BOUNDED_SCALAR, /*!< \brief Scalar advection numerical method. */ + UDS /*!< \brief Upwind Difference Scheme used for pressure based solver. */ }; static const MapType Upwind_Map = { MakePair("NONE", UPWIND::NONE) @@ -950,6 +981,7 @@ static const MapType Upwind_Map = { MakePair("SLAU2", UPWIND::SLAU2) MakePair("FDS", UPWIND::FDS) MakePair("LAX-FRIEDRICH", UPWIND::LAX_FRIEDRICH) + MakePair("UDS", UPWIND::UDS) }; /*! @@ -2843,6 +2875,9 @@ enum class MPI_QUANTITIES { MESH_DISPLACEMENTS , /*!< \brief Mesh displacements at the interface. */ SOLUTION_TIME_N , /*!< \brief Solution at time n. */ SOLUTION_TIME_N1 , /*!< \brief Solution at time n-1. */ + MOM_COEFF , /*!< \brief Momentum coefficient for the Rhie-Chow scheme. */ + MOM_CORRECTION , /*!< \brief Momentum correction for the pressure-based poisson solver (used when computing HbyA). */ + HBYA_CORRECTION , /*!< \brief HbyA correction for the pressure-based poisson solver. */ }; /*! @@ -2970,6 +3005,7 @@ enum class LINEAR_SOLVER_MODE { STANDARD, /*!< \brief Operate in standard mode. */ MESH_DEFORM, /*!< \brief Operate in mesh deformation mode. */ GRADIENT_MODE, /*!< \brief Operate in gradient smoothing mode. */ + POISSON, /*!< \brief Operate in poisson solver mode. */ }; /*! diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index 0501181dbedf..503160942f79 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -1202,6 +1202,11 @@ void CConfig::SetConfig_Options() { /*!\brief SST_OPTIONS \n DESCRIPTION: Specify SA turbulence model options/corrections. \n Options: see \link SA_Options_Map \endlink \n DEFAULT: NONE \ingroup Config*/ addEnumListOption("SA_OPTIONS", nSA_Options, SA_Options, SA_Options_Map); + /*!\brief KIND_INCOMP_SYSTEM \n DESCRIPTION: Incomp type \n OPTIONS: see \link Incomp_Map \endlink DEFAULT: NONE \ingroup Config*/ + addEnumOption("KIND_INCOMP_SYSTEM", Kind_Incomp_System, Incomp_Map, INCOMP_SYSTEM::DENSITY_BASED); + /*!\brief KIND_PB_ITER \n DESCRIPTION: Kind_PBIter \n OPTIONS: see \link PBIter_Map \endlink \ingroup Config*/ + addEnumOption("KIND_PB_ITER", Kind_PBIter, PBIter_Map, PBITER::SIMPLE); + /*!\brief ROUGHSST_OPTIONS \n DESCRIPTION: Specify type of boundary condition for rough walls for SST turbulence model. \n Options: see \link ROUGHSST_Options_Map \endlink \n DEFAULT: wilcox1998 \ingroup Config*/ addEnumOption("KIND_ROUGHSST_MODEL", Kind_RoughSST_Model, RoughSST_Model_Map, ROUGHSST_MODEL::WILCOX1998); /*!\brief KIND_TRANS_MODEL \n DESCRIPTION: Specify transition model OPTIONS: see \link Trans_Model_Map \endlink \n DEFAULT: NONE \ingroup Config*/ @@ -1973,6 +1978,16 @@ void CConfig::SetConfig_Options() { addDoubleOption("LINEAR_SOLVER_ERROR", Linear_Solver_Error, 1E-6); /* DESCRIPTION: Maximum number of iterations of the linear solver for the implicit formulation */ addUnsignedLongOption("LINEAR_SOLVER_ITER", Linear_Solver_Iter, 10); + /*!\brief LINEAR_SOLVER + * \n DESCRIPTION: Linear solver for the poisson system \n OPTIONS: see \link Linear_Solver_Map \endlink \n DEFAULT: FGMRES \ingroup Config*/ + addEnumOption("POISSON_LINEAR_SOLVER", Kind_Poisson_Linear_Solver, Linear_Solver_Map, FGMRES); + /*!\brief LINEAR_SOLVER_PREC + * \n DESCRIPTION: Preconditioner for the Krylov linear solvers \n OPTIONS: see \link Linear_Solver_Prec_Map \endlink \n DEFAULT: LU_SGS \ingroup Config*/ + addEnumOption("POISSON_LINEAR_SOLVER_PREC", Kind_Poisson_Linear_Solver_Prec, Linear_Solver_Prec_Map, ILU); + /* DESCRIPTION: Minimum error threshold for the poisson linear solver */ + addDoubleOption("POISSON_LINEAR_SOLVER_ERROR", Poisson_Linear_Solver_Error, 1E-6); + /* DESCRIPTION: Maximum number of iterations of the poisson linear solver */ + addUnsignedLongOption("POISSON_LINEAR_SOLVER_ITER", Poisson_Linear_Solver_Iter, 10); /* DESCRIPTION: Fill in level for the ILU preconditioner */ addUnsignedShortOption("LINEAR_SOLVER_ILU_FILL_IN", IluOptions.FillIn, 0); /* DESCRIPTION: Use level scheduling for OMP parallelization of the ILU preconditioner */ @@ -1989,6 +2004,14 @@ void CConfig::SetConfig_Options() { addUnsignedLongOption("LINEAR_SOLVER_PREC_THREADS", Linear_Solver_Prec_Threads, 0); /* DESCRIPTION: Use an inner linear solver. */ addEnumOption("LINEAR_SOLVER_INNER", Kind_Linear_Solver_Inner, Inner_Linear_Solver_Map, LINEAR_SOLVER_INNER::NONE); + /* DESCRIPTION: Relaxation of the pressure corrections for the SIMPLE algorithm */ + addDoubleOption("RELAXATION_FACTOR_PRESSURE", SIMPLE_Options.Relaxation_Factor_Pressure, 1.0); + /* DESCRIPTION: Removal factor for the transient term in the momentum coefficients for the poisson solver. */ + addDoubleOption("TRANSIENT_TERM_REMOVAL_FACTOR", SIMPLE_Options.Transient_Term_Removal_Factor, 0.0); + /*!\DESCRIPTION: Automatically compute relaxation factors for flow corrections in the SIMPLE algorithm */ + addBoolOption("USE_AUTOMATIC_RELAXATION_FACTORS", SIMPLE_Options.AutomaticRelaxationFactors, false); + /* DESCRIPTION: Number of corrections in the PISO algorithm (pressure based). */ + addUnsignedShortOption("PISO_CORRECTIONS", SIMPLE_Options.nCorrections_PISO, 1); /* DESCRIPTION: Relaxation factor for updates of adjoint variables. */ addDoubleOption("RELAXATION_FACTOR_ADJOINT", Relaxation_Factor_Adjoint, 1.0); /* DESCRIPTION: Relaxation of the CHT coupling */ @@ -4201,6 +4224,46 @@ void CConfig::SetPostprocessing(SU2_COMPONENT val_software, unsigned short val_i SU2_MPI::Error("Harmonic Balance not yet implemented for the incompressible solver.", CURRENT_FUNCTION); } + /*--- The pressure-based solver's Poisson equation only runs on the finest grid, its Rhie-Chow + * mass flux has no pseudo-transient term, it has no adjoint, and its marker switches have no + * PERIODIC_BOUNDARY case. Fail here instead of silently ignoring the option or erroring deep + * inside the first iteration. Gated on the incompressible regime (not just the option's raw + * value) since KIND_INCOMP_SYSTEM is read regardless of solver family, and a compressible or + * SU2_DEF config that happens to carry a leftover PRESSURE_BASED line (e.g. copied from + * config_template.cfg before it defaulted to DENSITY_BASED) must not hard-error here. ---*/ + if (Kind_Regime == ENUM_REGIME::INCOMPRESSIBLE && Kind_Incomp_System == INCOMP_SYSTEM::PRESSURE_BASED) { + if (nMGLevels > 0) { + SU2_MPI::Error("KIND_INCOMP_SYSTEM= PRESSURE_BASED does not support MGLEVEL > 0,\n" + " the Poisson solver is single-grid only.", CURRENT_FUNCTION); + } + if (Time_Domain) { + SU2_MPI::Error("KIND_INCOMP_SYSTEM= PRESSURE_BASED does not support TIME_DOMAIN= YES,\n" + " it converges to a physically wrong solution instead of failing.", + CURRENT_FUNCTION); + } + if (DiscreteAdjoint || ContinuousAdjoint) { + SU2_MPI::Error("KIND_INCOMP_SYSTEM= PRESSURE_BASED has no adjoint formulation.", CURRENT_FUNCTION); + } + if (nMarker_PerBound > 0) { + SU2_MPI::Error("KIND_INCOMP_SYSTEM= PRESSURE_BASED does not support MARKER_PERIODIC.", CURRENT_FUNCTION); + } + if (Kind_Streamwise_Periodic != ENUM_STREAMWISE_PERIODIC::NONE) { + SU2_MPI::Error("KIND_INCOMP_SYSTEM= PRESSURE_BASED does not support streamwise periodicity.", + CURRENT_FUNCTION); + } + + /*--- A_p already carries Vol/dt when SIMPLEC's A_p-Sum_A_nb correction runs, so at the 0.0 + * default that correction collapses to roughly Vol/dt and the pressure correction becomes + * vanishingly weak at low CFL. ---*/ + if (Kind_PBIter == PBITER::SIMPLEC && !OptionIsSet("TRANSIENT_TERM_REMOVAL_FACTOR")) { + SIMPLE_Options.Transient_Term_Removal_Factor = 1.0; + if (rank == MASTER_NODE) { + cout << "WARNING: KIND_PB_ITER= SIMPLEC without TRANSIENT_TERM_REMOVAL_FACTOR set - " + << "defaulting it to 1.0, its intended companion value for SIMPLEC." << endl; + } + } + } + /*--- Check for Fluid model consistency ---*/ if (standard_air) { @@ -8969,6 +9032,7 @@ unsigned short CConfig::GetContainerPosition(unsigned short val_eqsystem) { case RUNTIME_ADJSPECIES_SYS:return ADJSPECIES_SOL; case RUNTIME_ADJFEA_SYS: return ADJFEA_SOL; case RUNTIME_RADIATION_SYS: return RAD_SOL; + case RUNTIME_POISSON_SYS: return POISSON_SOL; case RUNTIME_MULTIGRID_SYS: return 0; } return 0; @@ -9107,6 +9171,13 @@ void CConfig::SetGlobalParam(MAIN_SOLVER val_solver, } break; + case MAIN_SOLVER::POISSON_EQUATION: + if (val_system == RUNTIME_POISSON_SYS) { + SetKind_ConvNumScheme(NONE, CENTERED::NONE, UPWIND::NONE, LIMITER::NONE, NONE, 0.0, NONE); + SetKind_TimeIntScheme(EULER_IMPLICIT); + } + break; + case MAIN_SOLVER::FEM_ELASTICITY: case MAIN_SOLVER::DISC_ADJ_FEM: if (val_system == RUNTIME_FEA_SYS) { diff --git a/Common/src/linear_algebra/CSysMatrix.cpp b/Common/src/linear_algebra/CSysMatrix.cpp index 2d77cc361fa0..503e2ad4893c 100644 --- a/Common/src/linear_algebra/CSysMatrix.cpp +++ b/Common/src/linear_algebra/CSysMatrix.cpp @@ -183,7 +183,8 @@ CSysMatrix::~CSysMatrix() { template void CSysMatrix::Initialize(unsigned long npoint, unsigned long npointdomain, unsigned short nvar, unsigned short neqn, bool EdgeConnect, CGeometry* geometry, - const CConfig* config, bool needTranspPtr, bool grad_mode, bool allow_quant) { + const CConfig* config, bool needTranspPtr, bool allow_quant, + std::optional override_prec) { SU2_ZONE_SCOPED assert(omp_get_thread_num() == 0 && "Only the master thread is allowed to initialize the matrix."); @@ -213,8 +214,8 @@ void CSysMatrix::Initialize(unsigned long npoint, unsigned long npoi } /*--- No else if, but separate if case! ---*/ - if (config->GetSmoothGradient() && grad_mode) { - prec = config->GetKind_Grad_Linear_Solver_Prec(); + if (override_prec) { + prec = *override_prec; } useCuda = config->GetCUDA(); diff --git a/Common/src/linear_algebra/CSysSolve.cpp b/Common/src/linear_algebra/CSysSolve.cpp index 3a350aaa13ea..8ec1b19b23b3 100644 --- a/Common/src/linear_algebra/CSysSolve.cpp +++ b/Common/src/linear_algebra/CSysSolve.cpp @@ -1429,6 +1429,16 @@ unsigned long CSysSolve::Solve(CSysMatrix& Jacobian, con break; } + /*--- Poisson solver mode ---*/ + case LINEAR_SOLVER_MODE::POISSON: { + KindSolver = config->GetKind_Poisson_Linear_Solver(); + KindPrecond = config->GetKind_Poisson_Linear_Solver_Prec(); + MaxIter = config->GetPoisson_Linear_Solver_Iter(); + SolverTol = SU2_TYPE::GetValue(config->GetPoisson_Linear_Solver_Error()); + ScreenOutput = false; + break; + } + /*--- Normal mode assumes that 'lin_sol_mode==LINEAR_SOLVER_MODE::STANDARD', * but does not enforce it to avoid compiler warning. ---*/ default: { @@ -1643,6 +1653,16 @@ unsigned long CSysSolve::Solve_b(CSysMatrix& Jacobian, c break; } + /*--- Poisson solver mode ---*/ + case LINEAR_SOLVER_MODE::POISSON: { + KindSolver = config->GetKind_Poisson_Linear_Solver(); + KindPrecond = config->GetKind_Poisson_Linear_Solver_Prec(); + MaxIter = config->GetPoisson_Linear_Solver_Iter(); + SolverTol = SU2_TYPE::GetValue(config->GetPoisson_Linear_Solver_Error()); + ScreenOutput = false; + break; + } + /*--- Normal mode assumes that 'lin_sol_mode==LINEAR_SOLVER_MODE::STANDARD', * but does not enforce it to avoid compiler warning. ---*/ default: { diff --git a/SU2_CFD/include/iteration/CFluidIteration.hpp b/SU2_CFD/include/iteration/CFluidIteration.hpp index 024f88e7d432..4adacde2045f 100644 --- a/SU2_CFD/include/iteration/CFluidIteration.hpp +++ b/SU2_CFD/include/iteration/CFluidIteration.hpp @@ -72,6 +72,22 @@ class CFluidIteration : public CIteration { CVolumetricMovement*** grid_movement, CFreeFormDefBox*** FFDBox, unsigned short val_iZone, unsigned short val_iInst) override; + /*! + * \brief Perform auxiliary solvers iterations after the main flow solver. + * \param[in] integration - Container vector with all the integration methods. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver - Container vector with all the solutions. + * \param[in] numerics - Description of the numerical method (the way in which the equations are solved). + * \param[in] config - Definition of the particular problem. + * \param[in] val_iZone - Index of the zone. + * \param[in] val_iInst - Index of the instance layer. + * \param[in] main_solver - Main solver. + * \param[in] frozen_visc - Flag for frozen viscosity. + */ + void CommonAuxiliarySolvers(CIntegration**** integration, CGeometry**** geometry, CSolver***** solver, + CNumerics****** numerics, CConfig** config, unsigned short val_iZone, + unsigned short val_iInst, MAIN_SOLVER main_solver, bool frozen_visc); + /*! * \brief Iterate the fluid system for a number of Inner_Iter iterations. * \param[in] output - Pointer to the COutput class. diff --git a/SU2_CFD/include/iteration/CPBFluidIteration.hpp b/SU2_CFD/include/iteration/CPBFluidIteration.hpp new file mode 100644 index 000000000000..9efedea916e5 --- /dev/null +++ b/SU2_CFD/include/iteration/CPBFluidIteration.hpp @@ -0,0 +1,66 @@ +/*! + * \file CPBFluidIteration.hpp + * \brief Headers of the pressure based fluid iteration class. + * \author T. Aalbers + * \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 . + */ + +#pragma once + +#include "CFluidIteration.hpp" + +/*! + * \class CPBFluidIteration + * \ingroup Drivers + * \brief Class for driving a pressure-based iteration of the fluid system. + * \author T. Aalbers + */ +class CPBFluidIteration : public CFluidIteration { +public: + /*! + * \brief Constructor of the class. + * \param[in] config - Definition of the particular problem. + */ + explicit CPBFluidIteration(const CConfig* config) : CFluidIteration(config) {} + + /*! + * \brief Perform a single iteration of the fluid system. + * \param[in] output - Pointer to the COutput class. + * \param[in] integration - Container vector with all the integration methods. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver - Container vector with all the solutions. + * \param[in] numerics - Description of the numerical method (the way in which the equations are solved). + * \param[in] config - Definition of the particular problem. + * \param[in] surface_movement - Surface movement classes of the problem. + * \param[in] grid_movement - Volume grid movement classes of the problem. + * \param[in] FFDBox - FFD FFDBoxes of the problem. + * \param[in] val_iZone - Index of the zone. + * \param[in] val_iInst - Index of the instance layer. + */ + void Iterate(COutput* output, CIntegration**** integration, CGeometry**** geometry, CSolver***** solver, + CNumerics****** numerics, CConfig** config, CSurfaceMovement** surface_movement, + CVolumetricMovement*** grid_movement, CFreeFormDefBox*** FFDBox, unsigned short val_iZone, + unsigned short val_iInst) override; + + }; + diff --git a/SU2_CFD/include/numerics/flow/convection/pressure_based.hpp b/SU2_CFD/include/numerics/flow/convection/pressure_based.hpp new file mode 100644 index 000000000000..cc618dbb4516 --- /dev/null +++ b/SU2_CFD/include/numerics/flow/convection/pressure_based.hpp @@ -0,0 +1,160 @@ +/*! + * \file pressure_based.hpp + * \brief Declaration of numerics classes for convective schemes for + * the pressure based solver, the implementation is in pressure_based.cpp. + * \author T. Aalbers + * \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 . + */ + +#pragma once + +#include "../../CNumerics.hpp" + + +/*! + * \class CPBConvection_Base + * \brief Class for computing a linear centered scheme. + * \ingroup ConvDiscr + * \author T. Aalbers + */ +class CPBConvection_Base : public CNumerics { +protected: + + bool implicit, dynamic_grid, energy, variable_density; + + unsigned short iDim, jDim, iVar, jVar; + + su2double *AdvectedVelocity = nullptr, AdvectedEnthalpy; + su2double *Flux = nullptr; + su2double **Jacobian_i = nullptr; + su2double **Jacobian_j = nullptr; + + su2double MeanDensity; + su2double dRhodh_i, dRhodh_j, Temperature_i, Temperature_j; + + su2double weight_jacobian_i, weight_jacobian_j; + + /*! + * \brief Function which defines the advected quantities + */ + void virtual ComputeAdvectedQuantities(void) = 0; + + /*! + * \brief Function which defines jacobian weights + */ + void virtual ComputeJacobianWeights(void) = 0; + + /*! + * \brief Function which defines the Jacobian + */ + void ComputeJacobian(su2double val_density, const su2double *val_velocity, + su2double val_enthalpy, su2double val_dRhodh, + su2double val_scale, su2double **val_Proj_Jac_Tensor); + +public: + + /*! + * \brief Constructor of the class. + * \param[in] val_nDim - Number of dimension of the problem. + * \param[in] val_nVar - Number of variables of the problem. + * \param[in] config - Definition of the particular problem. + */ + CPBConvection_Base(unsigned short val_nDim, unsigned short val_nVar, CConfig *config); + + /*! + * \brief Destructor of the class. + */ + virtual ~CPBConvection_Base(void); + + /*! + * \brief Compute the flow residual. + * \param[out] val_resconv - Pointer to the convective residual. + * \param[out] val_Jacobian_i - Jacobian of the numerical method at node i (implicit computation). + * \param[out] val_Jacobian_j - Jacobian of the numerical method at node j (implicit computation). + * \param[in] config - Definition of the particular problem. + */ + ResidualType<> ComputeResidual(const CConfig* config) final; +}; + +/*! + * \class CPBConvection_Central + * \brief Class for computing a centered scheme. + * \ingroup ConvDiscr + * \author T. Aalbers + */ +class CPBConvection_Central : public CPBConvection_Base { + +public: + + /*! + * \brief Constructor of the class. + * \param[in] val_nDim - Number of dimension of the problem. + * \param[in] val_nVar - Number of variables of the problem. + * \param[in] config - Definition of the particular problem. + */ + CPBConvection_Central(unsigned short val_nDim, unsigned short val_nVar, CConfig *config) + : CPBConvection_Base(val_nDim, val_nVar, config) {} + + /*! + * \brief Function which defines the advected quantities + */ + void ComputeAdvectedQuantities(void) final; + + /*! + * \brief Function which defines jacobian weights + */ + void ComputeJacobianWeights(void) final; + +}; + + +/*! + * \class CPBConvection_Upwind + * \brief Class for computing an upwind scheme. + * \ingroup ConvDiscr + * \author T. Aalbers + */ +class CPBConvection_Upwind : public CPBConvection_Base { + +public: + + /*! + * \brief Constructor of the class. + * \param[in] val_nDim - Number of dimension of the problem. + * \param[in] val_nVar - Number of variables of the problem. + * \param[in] config - Definition of the particular problem. + */ + CPBConvection_Upwind(unsigned short val_nDim, unsigned short val_nVar, CConfig *config) + : CPBConvection_Base(val_nDim, val_nVar, config) {} + + /*! + * \brief Function which defines the advected quantities + */ + void ComputeAdvectedQuantities(void) final; + + /*! + * \brief Function which defines jacobian weights + */ + void ComputeJacobianWeights(void) final; + +}; diff --git a/SU2_CFD/include/numerics_simd/CNumericsSIMD.cpp b/SU2_CFD/include/numerics_simd/CNumericsSIMD.cpp index 52c1d6e743ea..f5ce83935a10 100644 --- a/SU2_CFD/include/numerics_simd/CNumericsSIMD.cpp +++ b/SU2_CFD/include/numerics_simd/CNumericsSIMD.cpp @@ -82,8 +82,8 @@ CNumericsSIMD* createCenteredNumerics(const CConfig& config, int iMesh, const CV case CENTERED::JST_MAT: obj = new CJSTmatScheme(config, iMesh, turbVars); break; - case CENTERED::LD2: - /*--- LD2 implemented only in the incompressible solver. ---*/ + case CENTERED::LD2: case CENTERED::CDS: + /*--- CDS and LD2 implemented only in the incompressible solver. ---*/ break; } return obj; diff --git a/SU2_CFD/include/output/CFlowIncOutput.hpp b/SU2_CFD/include/output/CFlowIncOutput.hpp index 0eb29f2911a1..80e22a80d3b1 100644 --- a/SU2_CFD/include/output/CFlowIncOutput.hpp +++ b/SU2_CFD/include/output/CFlowIncOutput.hpp @@ -42,6 +42,7 @@ class CFlowIncOutput final: public CFlowOutput { bool heat; /*!< \brief Boolean indicating whether have a heat problem*/ bool weakly_coupled_heat; /*!< \brief Boolean indicating whether have a weakly coupled heat equation*/ bool flamelet; /*!< \brief Boolean indicating whether we solve the flamelet equations */ + bool pressure_based; /*!< Boolean indicating whether running the pressure based version */ unsigned short streamwisePeriodic; /*!< \brief Boolean indicating whether it is a streamwise periodic simulation. */ bool streamwisePeriodic_temperature; /*!< \brief Boolean indicating streamwise periodic temperature is used. */ diff --git a/SU2_CFD/include/solvers/CIncEulerSolver.hpp b/SU2_CFD/include/solvers/CIncEulerSolver.hpp index 9c94d4195e92..fd5fbf986092 100644 --- a/SU2_CFD/include/solvers/CIncEulerSolver.hpp +++ b/SU2_CFD/include/solvers/CIncEulerSolver.hpp @@ -41,6 +41,12 @@ class CIncEulerSolver : public CFVMFlowSolverBase FluidModel; /*!< \brief fluid model used in the solver. */ StreamwisePeriodicValues SPvals, SPvalsUpdated; + bool pressure_based; + su2activevector alpha_p; + su2activevector pressureCorrection; + su2activematrix momentumCorrection; + su2activevector EdgeMassFluxCorrection; + /*! * \brief Preprocessing actions common to the Euler and NS solvers. * \param[in] geometry - Geometrical definition of the problem. @@ -148,6 +154,22 @@ class CIncEulerSolver : public CFVMFlowSolverBase. + */ + +#pragma once + +#include "CScalarSolver.hpp" +#include "../variables/CPoissonVariable.hpp" + +/*! + * \class CPoissonSolver + * \brief Main class for defining the finite-volume poisson equation solver. + * \author T. Aalbers + * \version 8.5.0 "Harrier" + */ +class CPoissonSolver final : public CScalarSolver { +protected: + static constexpr size_t MAXNDIM = 3; /*!< \brief Max number of space dimensions, used in some static arrays. */ + static constexpr size_t MAXNVAR = 1; /*!< \brief Max number of variables, for static arrays. */ + + /*! + * \brief Compute the viscous flux for the scalar equation at a particular edge. + * \param[in] iEdge - Edge for which we want to compute the flux + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container vector with all the solutions. + * \param[in] numerics - Description of the numerical method. + * \param[in] config - Definition of the particular problem. + * \note Calls a generic implementation after defining a SolverSpecificNumerics object. + */ + inline void Viscous_Residual(const unsigned long iEdge, const CGeometry* geometry, CSolver** solver_container, + CNumerics* numerics, const CConfig* config) override { + + su2double mom_coeff_i{}, mom_coeff_j{}; + + /*--- Sets the momentum coefficients to use in the viscous numerics. A point under a strong + * velocity BC has no momentum coefficient, so the edge uses that of its other node. ---*/ + auto compute_momentum_coeff = [&](unsigned long iPoint, unsigned long jPoint) { + const auto* flow_nodes = solver_container[FLOW_SOL]->GetNodes(); + + mom_coeff_i = nodes->GetMomCoeff(flow_nodes->GetStrongBC(iPoint) ? jPoint : iPoint); + mom_coeff_j = nodes->GetMomCoeff(flow_nodes->GetStrongBC(jPoint) ? iPoint : jPoint); + numerics->SetDiffusionCoeff(&mom_coeff_i, &mom_coeff_j); + }; + + /*--- Compute residual and Jacobians. ---*/ + Viscous_Residual_impl(compute_momentum_coeff, iEdge, geometry, solver_container, numerics, config); + } + +public: + + /* + * \overload + * \param[in] geometry - Geometrical definition of the problem + * \param[in] config - Definition of the particular problem + */ + CPoissonSolver(CGeometry *geometry, CConfig *config, unsigned short iMesh); + + /*! + * \brief Restart residual and compute gradients. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container vector with all the solutions. + * \param[in] config - Definition of the particular problem. + * \param[in] iMesh - Index of the mesh in multigrid computations. + * \param[in] iRKStep - Current step of the Runge-Kutta iteration. + * \param[in] RunTime_EqSystem - System of equations which is going to be solved. + * \param[in] Output - boolean to determine whether to print output. + */ + void Preprocessing(CGeometry *geometry, + CSolver **solver_container, + CConfig *config, + unsigned short iMesh, + unsigned short iRKStep, + unsigned short RunTime_EqSystem, + bool Output) override; + + /*! + * \brief Correct the pressure and velocities for the flow solution + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container vector with all the solutions. + * \param[in] config - Definition of the particular problem. + * \param[in] iMesh - Index of the mesh in multigrid computations. + */ + void Postprocessing(CGeometry *geometry, + CSolver **solver_container, + CConfig *config, + unsigned short iMesh) final; + + /*! + * \brief Compute the viscous residuals for the turbulent equation. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container vector with all the solutions. + * \param[in] numerics_container - Description of the numerical method. + * \param[in] config - Definition of the particular problem. + * \param[in] iMesh - Index of the mesh in multigrid computations. + * \param[in] iRKStep - Current step of the Runge-Kutta iteration. + */ + void Viscous_Residual(CGeometry *geometry, + CSolver **solver_container, + CNumerics **numerics_container, + CConfig *config, + unsigned short iMesh, + unsigned short iRKStep) override; + + /*! + * \brief Source term computation. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container vector with all the solutions. + * \param[in] numerics_container - Description of the numerical method. + * \param[in] config - Definition of the particular problem. + * \param[in] iMesh - Index of the mesh in multigrid computations. + */ + void Source_Residual(CGeometry *geometry, CSolver **solver_container, CNumerics **numerics_container, + CConfig *config, unsigned short iMesh) override; + + /*! + * \brief No upwind residual for poisson equation. + */ + void Upwind_Residual(CGeometry* geometry, CSolver** solver_container, CNumerics** numerics_container, + CConfig* config, unsigned short iMesh) override {} + + /*! + * \brief Update the solution using an implicit solver. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container vector with all the solutions. + * \param[in] config - Definition of the particular problem. + */ + void ImplicitEuler_Iteration(CGeometry *geometry, CSolver **solver_container, CConfig *config) override; + + /*! + * \brief No time step as it is a stationary problem + */ + void SetTime_Step(CGeometry *geometry, + CSolver **solver_container, + CConfig *config, + unsigned short iMesh, + unsigned long Iteration) override {} + + /*! + * \brief No dual time stepping as there is no time stepping at all. + */ + void SetResidual_DualTime(CGeometry* geometry, CSolver** solver_container, CConfig* config, unsigned short iRKStep, + unsigned short iMesh, unsigned short RunTime_EqSystem) override {} + + /*! + * \brief The pressure correction is reset to zero every iteration (see Preprocessing), so it + * carries no state that a restart file needs to provide. + */ + void LoadRestart(CGeometry** geometry, CSolver*** solver, CConfig* config, int val_iter, + bool val_update_geo) override {} + + /*! + * \brief Compute the coefficients for the pressure correction equation based + * on the residuals from the solution of the momentum equation. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container with all the solutions. + * \param[in] config - Definition of the particular problem. + * \param[in] periodic - Flag for periodic boundary conditions. + * \param[in] iMesh - Index of the mesh in multigrid computations. + */ + void SetMomCoeff(CGeometry *geometry, CSolver **solver_container, CConfig *config, bool periodic, unsigned short iMesh) final; + + + /*! + * \brief Compute the HbyA based on the momentum correction to be used in second PISO + * correction equation. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container with all the solutions. + * \param[in] config - Definition of the particular problem. + * \param[in] iMesh - Index of the mesh in multigrid computations. + */ + void ComputeHbyA(CGeometry *geometry, CSolver **solver_container, CConfig *config, unsigned short iMesh) final; + + /*! + * \brief Impose a constant heat-flux condition at the wall. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container vector with all the solutions. + * \param[in] conv_numerics - Description of the numerical method. + * \param[in] visc_numerics - Description of the numerical method. + * \param[in] config - Definition of the particular problem. + * \param[in] val_marker - Surface marker where the boundary condition is applied. + */ + void BC_HeatFlux_Wall(CGeometry *geometry, + CSolver **solver_container, + CNumerics *conv_numerics, + CNumerics *visc_numerics, + CConfig *config, + unsigned short val_marker) final; + + /*! + * \brief A virtual member. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container vector with all the solutions. + * \param[in] conv_numerics - Description of the numerical method. + * \param[in] visc_numerics - Description of the numerical method. + * \param[in] config - Definition of the particular problem. + * \param[in] val_marker - Surface marker where the boundary condition is applied. + */ + void BC_Far_Field(CGeometry *geometry, CSolver **solver_container, CNumerics *conv_numerics, CNumerics *visc_numerics, CConfig *config, + unsigned short val_marker) final; + + /*! + * \brief Impose the inlet boundary condition. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container vector with all the solutions. + * \param[in] conv_numerics - Description of the numerical method. + * \param[in] visc_numerics - Description of the numerical method. + * \param[in] config - Definition of the particular problem. + * \param[in] val_marker - Surface marker where the boundary condition is applied. + */ + void BC_Inlet(CGeometry *geometry, + CSolver **solver_container, + CNumerics *conv_numerics, + CNumerics *visc_numerics, + CConfig *config, + unsigned short val_marker) override; + /*! + * \brief Impose the outlet boundary condition. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container vector with all the solutions. + * \param[in] conv_numerics - Description of the numerical method. + * \param[in] visc_numerics - Description of the numerical method. + * \param[in] config - Definition of the particular problem. + * \param[in] val_marker - Surface marker where the boundary condition is applied. + */ + void BC_Outlet(CGeometry *geometry, + CSolver **solver_container, + CNumerics *conv_numerics, + CNumerics *visc_numerics, + CConfig *config, + unsigned short val_marker) override; + +}; diff --git a/SU2_CFD/include/solvers/CScalarSolver.hpp b/SU2_CFD/include/solvers/CScalarSolver.hpp index 500233645bf1..39ae101bf0ea 100644 --- a/SU2_CFD/include/solvers/CScalarSolver.hpp +++ b/SU2_CFD/include/solvers/CScalarSolver.hpp @@ -441,7 +441,7 @@ class CScalarSolver : public CSolver { * \param[in] geometry - Geometrical definition of the problem. * \param[in] config - Definition of the particular problem. */ - CScalarSolver(CGeometry* geometry, CConfig* config, bool conservative, bool bounded_scalar); + CScalarSolver(CGeometry* geometry, CConfig* config, bool conservative, bool bounded_scalar, LINEAR_SOLVER_MODE linear_solver_mode = LINEAR_SOLVER_MODE::STANDARD); /*! * \brief Compute the spatial integration using a upwind scheme. @@ -464,7 +464,7 @@ class CScalarSolver : public CSolver { * \param[in] val_marker - Surface marker where the boundary condition is applied. */ void BC_Far_Field(CGeometry *geometry, CSolver **solver_container, CNumerics *conv_numerics, - CNumerics *visc_numerics, CConfig *config, unsigned short val_marker) final; + CNumerics *visc_numerics, CConfig *config, unsigned short val_marker) override; /*! * \brief Impose the Symmetry Plane boundary condition. @@ -602,7 +602,7 @@ class CScalarSolver : public CSolver { * \param[in] solver_container - Container vector with all the solutions. * \param[in] config - Definition of the particular problem. */ - void ImplicitEuler_Iteration(CGeometry* geometry, CSolver** solver_container, CConfig* config) final; + void ImplicitEuler_Iteration(CGeometry* geometry, CSolver** solver_container, CConfig* config) override; /*! * \brief Set the total residual adding the term that comes from the Dual Time-Stepping Strategy. diff --git a/SU2_CFD/include/solvers/CScalarSolver.inl b/SU2_CFD/include/solvers/CScalarSolver.inl index 3e3a8461d7a1..761140905e6c 100644 --- a/SU2_CFD/include/solvers/CScalarSolver.inl +++ b/SU2_CFD/include/solvers/CScalarSolver.inl @@ -30,8 +30,8 @@ #include "../../include/variables/CFlowVariable.hpp" template -CScalarSolver::CScalarSolver(CGeometry* geometry, CConfig* config, bool conservative, bool bounded_scalar) - : CSolver(), Conservative(conservative), BoundedScalar(bounded_scalar), +CScalarSolver::CScalarSolver(CGeometry* geometry, CConfig* config, bool conservative, bool bounded_scalar, LINEAR_SOLVER_MODE linear_solver_mode) + : CSolver(linear_solver_mode), Conservative(conservative), BoundedScalar(bounded_scalar), prim_idx(config->GetKind_Regime() == ENUM_REGIME::INCOMPRESSIBLE, config->GetNEMOProblem(), geometry->GetnDim(), config->GetnSpecies()) { SU2_ZONE_SCOPED diff --git a/SU2_CFD/include/solvers/CSolver.hpp b/SU2_CFD/include/solvers/CSolver.hpp index 11e6655c18cb..7a7b6c726021 100644 --- a/SU2_CFD/include/solvers/CSolver.hpp +++ b/SU2_CFD/include/solvers/CSolver.hpp @@ -783,6 +783,22 @@ class CSolver { unsigned short RunTime_EqSystem, bool Output) { } + /*! + * \brief A virtual member. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container vector with all the solutions. + * \param[in] config - Definition of the particular problem. + */ + inline virtual void ComputeEdgeMassFluxesRhieChow(CGeometry *geometry, CSolver **solver_container, CConfig *config) { } + + /*! + * \brief A virtual member. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container vector with all the solutions. + * \param[in] config - Definition of the particular problem. + */ + inline virtual void ApplyPressureVelocityCorrection(CGeometry *geometry, CSolver **solver_container, CConfig *config) { } + /*! * \brief A virtual member. * \param[in] geometry - Geometrical definition of the problem. @@ -4304,6 +4320,25 @@ class CSolver { */ virtual StreamwisePeriodicValues GetStreamwisePeriodicValues() const { return StreamwisePeriodicValues(); } + /*! + * \brief A virtual member + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container with all the solutions. + * \param[in] config - Definition of the particular problem. + * \param[in] periodic - Flag for periodic boundary conditions. + * \param[in] iMesh - Index of the mesh in multigrid computations. + */ + inline virtual void SetMomCoeff(CGeometry *geometry, CSolver **solver_container, CConfig *config, bool periodic, unsigned short iMesh) { } + + /*! + * \brief A virtual member + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container with all the solutions. + * \param[in] config - Definition of the particular problem. + * \param[in] iMesh - Index of the mesh in multigrid computations. + */ + inline virtual void ComputeHbyA(CGeometry *geometry, CSolver **solver_container, CConfig *config, unsigned short iMesh) { } + /*! * \brief Save snapshot or POD data using libROM * \param[in] geometry - Geometrical definition of the problem. diff --git a/SU2_CFD/include/solvers/CSolverFactory.hpp b/SU2_CFD/include/solvers/CSolverFactory.hpp index e612f3fb1e3a..4e897cc26ff6 100644 --- a/SU2_CFD/include/solvers/CSolverFactory.hpp +++ b/SU2_CFD/include/solvers/CSolverFactory.hpp @@ -63,6 +63,7 @@ enum class SUB_SOLVER_TYPE { MESH, /*!< \brief Mesh solver */ RADIATION, /*!< \brief Radiation solver */ DISC_ADJ_RADIATION, /*!< \brief Discrete adjoint radiation solver */ + POISSON, /*!< \brief Poisson equation solver */ NONE }; diff --git a/SU2_CFD/include/variables/CIncEulerVariable.hpp b/SU2_CFD/include/variables/CIncEulerVariable.hpp index d76990b71eb8..aaa6c437b665 100644 --- a/SU2_CFD/include/variables/CIncEulerVariable.hpp +++ b/SU2_CFD/include/variables/CIncEulerVariable.hpp @@ -74,6 +74,10 @@ class CIncEulerVariable : public CFlowVariable { VectorType Density_time_n, /*!< \brief Density at time n for dual-time stepping. */ Density_time_n1; /*!< \brief Density at time n-1 for dual-time stepping. */ su2double TemperatureLimits[2]; /*!< \brief Temperature limits [K]. */ + + using BoolVectorType = C2DContainer; + BoolVectorType strongBC; /*!< \brief Flag for boundary conditions to indicate if a strong BC has been applied, currently only used to keep track of farfield. */ + public: /*! * \brief Constructor of the class. @@ -325,4 +329,23 @@ class CIncEulerVariable : public CFlowVariable { */ inline void SetDensity_time_n1(unsigned long iPoint, su2double val_density) { Density_time_n1(iPoint) = val_density; } + /*! + * \brief Set the BC flag to true of the point. + * \param[in] iPoint - Point index. + */ + inline void SetStrongBC(unsigned long iPoint) { strongBC(iPoint) = true; } + + /*! + * \brief Get the BC flag of the point + * \param[in] iPoint - Point index. + * \return The boolean flag of the strong boundary condition. + */ + inline bool GetStrongBC(unsigned long iPoint) const final { return strongBC(iPoint); } + + /*! + * \brief Set the BC flag to false of the point. + * \param[in] iPoint - Point index. + */ + inline void ResetStrongBC(unsigned long iPoint) { strongBC(iPoint) = false; } + }; diff --git a/SU2_CFD/include/variables/CPoissonVariable.hpp b/SU2_CFD/include/variables/CPoissonVariable.hpp new file mode 100644 index 000000000000..e0bbb0a52756 --- /dev/null +++ b/SU2_CFD/include/variables/CPoissonVariable.hpp @@ -0,0 +1,99 @@ +/*! + * \file CPoissonVariable.hpp + * \brief Class for defining the variables of the finite-volume poisson equation solver. + * \author T. Aalbers + * \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 . + */ + +#pragma once + +#include "CScalarVariable.hpp" + +/*! + * \class CPoissonVariable + * \brief Class for defining the variables of the finite-volume poisson equation solver. + * \author T. Aalbers + * \version 8.5.0 "Harrier" + */ +class CPoissonVariable final : public CScalarVariable { +protected: + VectorType MomCoeff; /*!< \brief Momentum coefficients vol/A_p used as the diffusion coefficients in the poisson solver. */ + MatrixType MomentumCorrection; /*!< \brief (rho*u)' in the context of: (rho*u)** = (rho*u)* + (rho*u)'. */ + MatrixType HbyACorrection; /*!< \brief H(rhou')/A = (sum_nb A_nb (rhou)'_nb) / A; used by the second pressure correction in the PISO algorithm. */ +public: + static constexpr size_t MAXNVAR = 1; /*!< \brief Max number of variables, for static arrays. */ + + /*! + * \brief Constructor of the class. + * \param[in] value - Values of the poisson solution (initialization value). + * \param[in] npoint - Number of points/nodes/vertices in the domain. + * \param[in] ndim - Number of dimensions of the problem. + * \param[in] nvar - Number of variables of the problem. + * \param[in] config - Definition of the particular problem. + */ + CPoissonVariable(su2double value, unsigned long npoint, unsigned long ndim, unsigned long nvar, CConfig *config); + + /*! + * \brief Get the momentum coefficient of the point. + * \return Value of the momentum coefficient of the point. + */ + inline su2double GetMomCoeff(unsigned long iPoint) final { return MomCoeff(iPoint);} + + /*! + * \brief Set the momentum coefficient of the point. + */ + inline void SetMomCoeff(unsigned long iPoint, su2double val_Mom_Coeff) final { MomCoeff(iPoint) = val_Mom_Coeff; } + + /*! + * \brief Set H(u')/A for the point + * \param[in] iPoint - Point index. + * \param[in] iDim - Dimension index. + * \param[in] val_HbyA - HbyA correction. + */ + inline void SetHbyACorrection(unsigned long iPoint, unsigned short iDim, su2double val_HbyA) final { HbyACorrection(iPoint, iDim) = val_HbyA; } + + /*! + * \brief Get H(u')/A for the point + * \param[in] iPoint - Point index. + * \param[in] iDim - Dimension index. + * \return The H(u')/A for the point. + */ + inline su2double GetHbyACorrection(unsigned long iPoint, unsigned short iDim) final { return HbyACorrection(iPoint, iDim); } + + /*! + * \brief Set (rho*u)' for the point + * \param[in] iPoint - Point index. + * \param[in] iDim - Dimension index. + * \param[in] val_mom - Momentum correction (rho*u)' value. + */ + inline void SetMomentumCorrection(unsigned long iPoint, unsigned short iDim, su2double val_mom) final { MomentumCorrection(iPoint, iDim) = val_mom; } + + /*! + * \brief Get (rho*u)' for the point + * \param[in] iPoint - Point index. + * \param[in] iDim - Dimension index. + * \return The (rho*u)' for the point. + */ + inline su2double GetMomentumCorrection(unsigned long iPoint, unsigned short iDim) final { return MomentumCorrection(iPoint, iDim); } + +}; diff --git a/SU2_CFD/include/variables/CVariable.hpp b/SU2_CFD/include/variables/CVariable.hpp index 528adc139dbb..8d8a8ac0b256 100644 --- a/SU2_CFD/include/variables/CVariable.hpp +++ b/SU2_CFD/include/variables/CVariable.hpp @@ -2416,4 +2416,22 @@ class CVariable { inline virtual const su2double *GetScalarSources(unsigned long iPoint) const { return nullptr; } inline virtual const su2double *GetScalarLookups(unsigned long iPoint) const { return nullptr; } + + inline virtual su2double GetMomCoeff(unsigned long iPoint) { return 0.0; } + + inline virtual void SetMomCoeff(unsigned long iPoint, su2double val_Mom_Coeff) { } + + /*! + * \brief Get whether a strong boundary condition was applied to the point, which for the + * pressure-based solver means its momentum row was deleted and carries no A_p. + */ + inline virtual bool GetStrongBC(unsigned long iPoint) const { return false; } + + inline virtual su2double GetMomentumCorrection(unsigned long iPoint, unsigned short iDim) { return 0.0; } + + inline virtual void SetMomentumCorrection(unsigned long iPoint, unsigned short iDim, su2double val_mom) { } + + inline virtual su2double GetHbyACorrection(unsigned long iPoint, unsigned short iDim) { return 0.0; } + + inline virtual void SetHbyACorrection(unsigned long iPoint, unsigned short iDim, su2double val_HbyAcorrection) { } }; diff --git a/SU2_CFD/src/drivers/CDriver.cpp b/SU2_CFD/src/drivers/CDriver.cpp index 5904ed9febb2..6bdc08a435bc 100644 --- a/SU2_CFD/src/drivers/CDriver.cpp +++ b/SU2_CFD/src/drivers/CDriver.cpp @@ -62,6 +62,7 @@ #include "../../include/numerics/flow/convection/hllc.hpp" #include "../../include/numerics/flow/convection/ausm_slau.hpp" #include "../../include/numerics/flow/convection/centered.hpp" +#include "../../include/numerics/flow/convection/pressure_based.hpp" #include "../../include/numerics/flow/flow_diffusion.hpp" #include "../../include/numerics/flow/flow_sources.hpp" #include "../../include/numerics/NEMO/convection/roe.hpp" @@ -1455,6 +1456,7 @@ void CDriver::InitializeNumerics(CConfig *config, CGeometry **geometry, CSolver bool compressible = false; bool incompressible = false; bool ideal_gas = (config->GetKind_FluidModel() == STANDARD_AIR) || (config->GetKind_FluidModel() == IDEAL_GAS); + bool pressure_based = (config->GetKind_Incomp_System() == INCOMP_SYSTEM::PRESSURE_BASED); bool roe_low_dissipation = (config->GetKind_RoeLowDiss() != NO_ROELOWDISS); /*--- Initialize some useful booleans ---*/ @@ -1567,7 +1569,7 @@ void CDriver::InitializeNumerics(CConfig *config, CGeometry **geometry, CSolver if (fem_ns) nVar_Flow = solver[MESH_0][FLOW_SOL]->GetnVar(); if (fem) nVar_FEM = solver[MESH_0][FEA_SOL]->GetnVar(); - + if (config->AddRadiation()) nVar_Rad = solver[MESH_0][RAD_SOL]->GetnVar(); /*--- Number of variables for adjoint problem ---*/ @@ -1655,22 +1657,36 @@ void CDriver::InitializeNumerics(CConfig *config, CGeometry **geometry, CSolver } if (incompressible) { - /*--- Incompressible flow, use preconditioning method ---*/ - switch (config->GetKind_Centered_Flow()) { - case CENTERED::LAX : numerics[MESH_0][FLOW_SOL][conv_term] = new CCentLaxInc_Flow(nDim, nVar_Flow, config); break; - case CENTERED::LD2 : - case CENTERED::JST : numerics[MESH_0][FLOW_SOL][conv_term] = new CCentJSTInc_Flow(nDim, nVar_Flow, config); break; - default: - SU2_MPI::Error("Invalid centered scheme or not implemented.\n Currently, only JST and LAX-FRIEDRICH are available for incompressible flows.", CURRENT_FUNCTION); - break; - } - for (iMGlevel = 1; iMGlevel <= config->GetnMGLevels(); iMGlevel++) - numerics[iMGlevel][FLOW_SOL][conv_term] = new CCentLaxInc_Flow(nDim, nVar_Flow, config); + if (!pressure_based) { + /*--- Incompressible flow, use preconditioning method ---*/ + switch (config->GetKind_Centered_Flow()) { + case CENTERED::LAX : numerics[MESH_0][FLOW_SOL][conv_term] = new CCentLaxInc_Flow(nDim, nVar_Flow, config); break; + case CENTERED::LD2 : + case CENTERED::JST : numerics[MESH_0][FLOW_SOL][conv_term] = new CCentJSTInc_Flow(nDim, nVar_Flow, config); break; + default: + SU2_MPI::Error("Invalid centered scheme or not implemented.\n Currently, only JST and LAX-FRIEDRICH are available for density based incompressible flows.", CURRENT_FUNCTION); + break; + } + for (iMGlevel = 1; iMGlevel <= config->GetnMGLevels(); iMGlevel++) + numerics[iMGlevel][FLOW_SOL][conv_term] = new CCentLaxInc_Flow(nDim, nVar_Flow, config); + /*--- Definition of the boundary condition method ---*/ + for (iMGlevel = 0; iMGlevel <= config->GetnMGLevels(); iMGlevel++) + numerics[iMGlevel][FLOW_SOL][conv_bound_term] = new CUpwFDSInc_Flow(nDim, nVar_Flow, config); - /*--- Definition of the boundary condition method ---*/ - for (iMGlevel = 0; iMGlevel <= config->GetnMGLevels(); iMGlevel++) - numerics[iMGlevel][FLOW_SOL][conv_bound_term] = new CUpwFDSInc_Flow(nDim, nVar_Flow, config); + } else { + /*--- Incompressible flow, use pressure-based method ---*/ + switch (config->GetKind_Centered_Flow()) { + case CENTERED::CDS : numerics[MESH_0][FLOW_SOL][conv_term] = new CPBConvection_Central(nDim, nVar_Flow, config); break; + default: + SU2_MPI::Error("Invalid centered scheme or not implemented.\n Currently, only CDS is available for pressure based incompressible flows.", CURRENT_FUNCTION); + } + for (iMGlevel = 1; iMGlevel <= config->GetnMGLevels(); iMGlevel++) + numerics[iMGlevel][FLOW_SOL][conv_term] = new CPBConvection_Central(nDim, nVar_Flow, config); + /*--- Definition of the boundary condition method ---*/ + for (iMGlevel = 0; iMGlevel <= config->GetnMGLevels(); iMGlevel++) + numerics[iMGlevel][FLOW_SOL][conv_bound_term] = new CPBConvection_Upwind(nDim, nVar_Flow, config); + } } break; case SPACE_UPWIND : @@ -1777,17 +1793,32 @@ void CDriver::InitializeNumerics(CConfig *config, CGeometry **geometry, CSolver } if (incompressible) { - /*--- Incompressible flow, use artificial compressibility method ---*/ - switch (config->GetKind_Upwind_Flow()) { - case UPWIND::FDS: - for (iMGlevel = 0; iMGlevel <= config->GetnMGLevels(); iMGlevel++) { - numerics[iMGlevel][FLOW_SOL][conv_term] = new CUpwFDSInc_Flow(nDim, nVar_Flow, config); - numerics[iMGlevel][FLOW_SOL][conv_bound_term] = new CUpwFDSInc_Flow(nDim, nVar_Flow, config); - } - break; - default: - SU2_MPI::Error("Invalid upwind scheme or not implemented.\n Currently, only FDS is available for incompressible flows.", CURRENT_FUNCTION); - break; + if (!pressure_based) { + /*--- Incompressible flow, use artificial compressibility method ---*/ + switch (config->GetKind_Upwind_Flow()) { + case UPWIND::FDS: + for (iMGlevel = 0; iMGlevel <= config->GetnMGLevels(); iMGlevel++) { + numerics[iMGlevel][FLOW_SOL][conv_term] = new CUpwFDSInc_Flow(nDim, nVar_Flow, config); + numerics[iMGlevel][FLOW_SOL][conv_bound_term] = new CUpwFDSInc_Flow(nDim, nVar_Flow, config); + } + break; + default: + SU2_MPI::Error("Invalid upwind scheme or not implemented.\n Currently, only FDS is available for density based incompressible flows.", CURRENT_FUNCTION); + break; + } + } else { + /*--- Incompressible flow, use pressure based method ---*/ + switch (config->GetKind_Upwind_Flow()) { + case UPWIND::UDS: + for (iMGlevel = 0; iMGlevel <= config->GetnMGLevels(); iMGlevel++) { + numerics[iMGlevel][FLOW_SOL][conv_term] = new CPBConvection_Upwind(nDim, nVar_Flow, config); + numerics[iMGlevel][FLOW_SOL][conv_bound_term] = new CPBConvection_Upwind(nDim, nVar_Flow, config); + } + break; + default: + SU2_MPI::Error("Invalid upwind scheme or not implemented.\n Currently, only UDS is available for pressure based incompressible flows.", CURRENT_FUNCTION); + break; + } } } break; @@ -2095,6 +2126,21 @@ void CDriver::InitializeNumerics(CConfig *config, CGeometry **geometry, CSolver } } + /*--- Solver definition for the poisson/pressure correction problem ---*/ + if (pressure_based) { + /*--- Pressure correction (Poisson) equation ---*/ + numerics[MESH_0][POISSON_SOL][visc_term] = new CAvgGrad_Heat(nDim, config, true); + + for (iMGlevel = 1; iMGlevel <= config->GetnMGLevels(); iMGlevel++) + numerics[iMGlevel][POISSON_SOL][visc_term] = new CAvgGrad_Heat(nDim, config, false); + + /*--- Assign the convective boundary term as well to account for flow BCs as well --*/ + for (iMGlevel = 0; iMGlevel <= config->GetnMGLevels(); iMGlevel++) { + numerics[iMGlevel][POISSON_SOL][visc_bound_term] = new CAvgGrad_Heat(nDim, config, false); + + } + } + /*--- Solver definition for the radiation model problem ---*/ if (config->AddRadiation()) { diff --git a/SU2_CFD/src/iteration/CFluidIteration.cpp b/SU2_CFD/src/iteration/CFluidIteration.cpp index 468e5cbb2312..f7d9350fe1a4 100644 --- a/SU2_CFD/src/iteration/CFluidIteration.cpp +++ b/SU2_CFD/src/iteration/CFluidIteration.cpp @@ -87,6 +87,37 @@ void CFluidIteration::Iterate(COutput* output, CIntegration**** integration, CGe const bool fmg_cfl_ramp = integration[val_iZone][val_iInst][FLOW_SOL]->GetFullMG_CFLRamp(); /*--- If the flow integration is not fully coupled, run the various single grid integrations. ---*/ + CommonAuxiliarySolvers(integration, geometry, solver, numerics, config, val_iZone, val_iInst, main_solver, frozen_visc); + + /*--- Adapt the CFL number using an exponential progression with under-relaxation approach. + The Full-MG startup owns the CFL while it ramps, so leave it alone until then. ---*/ + SU2_OMP_PARALLEL + if (!disc_adj && config[val_iZone]->GetFinestMesh() == MESH_0 && !fmg_cfl_ramp) { + solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->AdaptCFLNumber(geometry[val_iZone][val_iInst], + solver[val_iZone][val_iInst], config[val_iZone]); + solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->IdentifySolutionOutliers(config[val_iZone], InnerIter); + } + END_SU2_OMP_PARALLEL + + /*--- Call Dynamic mesh update if AEROELASTIC motion was specified ---*/ + + if ((config[val_iZone]->GetGrid_Movement()) && (config[val_iZone]->GetAeroelastic_Simulation()) && unsteady) { + SetGrid_Movement(geometry[val_iZone][val_iInst], surface_movement[val_iZone], grid_movement[val_iZone][val_iInst], + solver[val_iZone][val_iInst], config[val_iZone], InnerIter, TimeIter); + + /*--- Apply a Wind Gust ---*/ + + if (config[val_iZone]->GetWind_Gust()) { + if (InnerIter % config[val_iZone]->GetAeroelasticIter() == 0 && InnerIter != 0) + SetWind_GustField(config[val_iZone], geometry[val_iZone][val_iInst], solver[val_iZone][val_iInst]); + } + } +} + +void CFluidIteration::CommonAuxiliarySolvers(CIntegration**** integration, CGeometry**** geometry, + CSolver***** solver, CNumerics****** numerics, CConfig** config, + unsigned short val_iZone, unsigned short val_iInst, MAIN_SOLVER main_solver, bool frozen_visc) { + if (config[val_iZone]->GetKind_Turb_Model() != TURB_MODEL::NONE && !frozen_visc) { @@ -133,30 +164,7 @@ void CFluidIteration::Iterate(COutput* output, CIntegration**** integration, CGe integration[val_iZone][val_iInst][RAD_SOL]->SingleGrid_Iteration(geometry, solver, numerics, config, RUNTIME_RADIATION_SYS, val_iZone, val_iInst); } - - /*--- Adapt the CFL number using an exponential progression with under-relaxation approach. - The Full-MG startup owns the CFL while it ramps, so leave it alone until then. ---*/ - SU2_OMP_PARALLEL - if (!disc_adj && config[val_iZone]->GetFinestMesh() == MESH_0 && !fmg_cfl_ramp) { - solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->AdaptCFLNumber(geometry[val_iZone][val_iInst], - solver[val_iZone][val_iInst], config[val_iZone]); - solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->IdentifySolutionOutliers(config[val_iZone], InnerIter); - } - END_SU2_OMP_PARALLEL - - /*--- Call Dynamic mesh update if AEROELASTIC motion was specified ---*/ - - if ((config[val_iZone]->GetGrid_Movement()) && (config[val_iZone]->GetAeroelastic_Simulation()) && unsteady) { - SetGrid_Movement(geometry[val_iZone][val_iInst], surface_movement[val_iZone], grid_movement[val_iZone][val_iInst], - solver[val_iZone][val_iInst], config[val_iZone], InnerIter, TimeIter); - - /*--- Apply a Wind Gust ---*/ - - if (config[val_iZone]->GetWind_Gust()) { - if (InnerIter % config[val_iZone]->GetAeroelasticIter() == 0 && InnerIter != 0) - SetWind_GustField(config[val_iZone], geometry[val_iZone][val_iInst], solver[val_iZone][val_iInst]); - } - } + } void CFluidIteration::Update(COutput* output, CIntegration**** integration, CGeometry**** geometry, CSolver***** solver, diff --git a/SU2_CFD/src/iteration/CIterationFactory.cpp b/SU2_CFD/src/iteration/CIterationFactory.cpp index 761ceca98f81..6d738b62b284 100644 --- a/SU2_CFD/src/iteration/CIterationFactory.cpp +++ b/SU2_CFD/src/iteration/CIterationFactory.cpp @@ -32,6 +32,7 @@ #include "../../include/iteration/CDiscAdjFluidIteration.hpp" #include "../../include/iteration/CDiscAdjHeatIteration.hpp" #include "../../include/iteration/CFluidIteration.hpp" +#include "../../include/iteration/CPBFluidIteration.hpp" #include "../../include/iteration/CFEMFluidIteration.hpp" #include "../../include/iteration/CTurboIteration.hpp" #include "../../include/iteration/CHeatIteration.hpp" @@ -57,6 +58,11 @@ CIteration* CIterationFactory::CreateIteration(MAIN_SOLVER kindSolver, const CCo iteration = new CTurboIteration(config); } + else if (config->GetKind_Incomp_System() == INCOMP_SYSTEM::PRESSURE_BASED) { + if (rank == MASTER_NODE) + cout << "Pressure based Euler/Navier-Stokes/RANS fluid iteration." << endl; + iteration = new CPBFluidIteration(config); + } else{ if (rank == MASTER_NODE) cout << "Euler/Navier-Stokes/RANS fluid iteration." << endl; @@ -113,7 +119,7 @@ CIteration* CIterationFactory::CreateIteration(MAIN_SOLVER kindSolver, const CCo iteration = new CDiscAdjHeatIteration(config); break; - case MAIN_SOLVER::NONE: case MAIN_SOLVER::TEMPLATE_SOLVER: case MAIN_SOLVER::MULTIPHYSICS: + case MAIN_SOLVER::NONE: case MAIN_SOLVER::TEMPLATE_SOLVER: case MAIN_SOLVER::MULTIPHYSICS: case MAIN_SOLVER::POISSON_EQUATION: SU2_MPI::Error("No iteration found for specified solver.", CURRENT_FUNCTION); break; } diff --git a/SU2_CFD/src/iteration/CPBFluidIteration.cpp b/SU2_CFD/src/iteration/CPBFluidIteration.cpp new file mode 100644 index 000000000000..fc31ab976108 --- /dev/null +++ b/SU2_CFD/src/iteration/CPBFluidIteration.cpp @@ -0,0 +1,111 @@ +/*! + * \file CPBFluidIteration.cpp + * \brief Main subroutines used by SU2_CFD + * \author T. Aalbers + * \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 "../../include/iteration/CPBFluidIteration.hpp" +#include "../../include/output/COutput.hpp" +#include "../../include/integration/CIntegration.hpp" + +void CPBFluidIteration::Iterate(COutput* output, CIntegration**** integration, CGeometry**** geometry, + CSolver***** solver, CNumerics****** numerics, CConfig** config, + CSurfaceMovement** surface_movement, CVolumetricMovement*** grid_movement, + CFreeFormDefBox*** FFDBox, unsigned short val_iZone, unsigned short val_iInst) { + SU2_ZONE_SCOPED + + const bool frozen_visc = (config[val_iZone]->GetContinuous_Adjoint() && config[val_iZone]->GetFrozen_Visc_Cont()) || + (config[val_iZone]->GetDiscrete_Adjoint() && config[val_iZone]->GetFrozen_Visc_Disc()); + const bool disc_adj = (config[val_iZone]->GetDiscrete_Adjoint()); + const bool periodic = (config[val_iZone]->GetnMarker_Periodic() > 0); + + const unsigned short nCorrections = config[val_iZone]->GetSIMPLE_Options().nCorrections_PISO; + + /*--- Solve the Euler, Navier-Stokes, RANS equations. ---*/ + + const auto main_solver = config[val_iZone]->GetKind_Solver(); + config[val_iZone]->SetGlobalParam(main_solver, RUNTIME_FLOW_SYS); + + /*--- Solve the momentum equations (to find the predicted velocity u*). ---*/ + + integration[val_iZone][val_iInst][FLOW_SOL]->MultiGrid_Iteration(geometry, solver, numerics, config, RUNTIME_FLOW_SYS, + val_iZone, val_iInst); + + /*--- The momentum coefficients (resulting from the flow solution) are set only once + at the start of the corrections. These coefficients make up the entirety of the coefficient + matrix (Jacobian) used by the Poisson solver. Currently the matrix is redefined each correction + but as the coefficients are frozen this doesnt/shouldnt change the matrix at all. ---*/ + + /*--- The mass fluxes at the cell edges then follow from Rhie-Chow interpolation. ---*/ + + SU2_OMP_PARALLEL { + solver[val_iZone][val_iInst][MESH_0][POISSON_SOL]->SetMomCoeff(geometry[val_iZone][val_iInst][MESH_0], solver[val_iZone][val_iInst][MESH_0], config[val_iZone], periodic, MESH_0); + + solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->ComputeEdgeMassFluxesRhieChow(geometry[val_iZone][val_iInst][MESH_0], solver[val_iZone][val_iInst][MESH_0], config[val_iZone]); + } + END_SU2_OMP_PARALLEL + + /*--- Solve the pressure poisson (correction) equation ---*/ + + config[val_iZone]->SetGlobalParam(MAIN_SOLVER::POISSON_EQUATION, RUNTIME_POISSON_SYS); + + for (unsigned short iCorrection = 0; iCorrection < nCorrections; ++iCorrection) { + + /*--- For later corrections (PISO) the pressure equation has an additional div(H(u')/A_p) term on the right side, u' is computed in the last correction routine. ---*/ + + if (iCorrection > 0) { + SU2_OMP_PARALLEL + solver[val_iZone][val_iInst][MESH_0][POISSON_SOL]->ComputeHbyA(geometry[val_iZone][val_iInst][MESH_0], solver[val_iZone][val_iInst][MESH_0], config[val_iZone], MESH_0); + END_SU2_OMP_PARALLEL + } + + /*--- Solve the pressure Poisson equation to find p' i.e. div(V/A_p * grad(p')) = div rhou*}. This + call opens its own parallel region internally, so it must not be nested inside one of ours. ---*/ + + integration[val_iZone][val_iInst][POISSON_SOL]->SingleGrid_Iteration(geometry, solver, numerics, config, RUNTIME_POISSON_SYS, + val_iZone, val_iInst); + + /*--- The velocity and pressure are corrected based on the solution to the Poisson problem i.e. p* = p + p' and rhou** = rhou* - V/Ap * p' ---*/ + + SU2_OMP_PARALLEL + solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->ApplyPressureVelocityCorrection(geometry[val_iZone][val_iInst][MESH_0], solver[val_iZone][val_iInst][MESH_0], config[val_iZone]); + END_SU2_OMP_PARALLEL + + } + + /*--- Pressure-based algorithm finished, now run auxiliary solvers ---*/ + + /*--- If the flow integration is not fully coupled, run the various single grid integrations. ---*/ + CommonAuxiliarySolvers(integration, geometry, solver, numerics, config, val_iZone, val_iInst, main_solver, frozen_visc); + + /*--- Adapt the CFL number using an exponential progression with under-relaxation approach. ---*/ + + if ((config[val_iZone]->GetCFL_Adapt() == YES) && (!disc_adj)) { + SU2_OMP_PARALLEL + solver[val_iZone][val_iInst][MESH_0][FLOW_SOL]->AdaptCFLNumber(geometry[val_iZone][val_iInst], + solver[val_iZone][val_iInst], config[val_iZone]); + END_SU2_OMP_PARALLEL + } + +} diff --git a/SU2_CFD/src/meson.build b/SU2_CFD/src/meson.build index d4db53843e9b..4462b6b41112 100644 --- a/SU2_CFD/src/meson.build +++ b/SU2_CFD/src/meson.build @@ -82,6 +82,7 @@ su2_cfd_src += files(['variables/CIncNSVariable.cpp', 'variables/CAdjTurbVariable.cpp', 'variables/CFlowVariable.cpp', 'variables/CIncEulerVariable.cpp', + 'variables/CPoissonVariable.cpp', 'variables/CEulerVariable.cpp', 'variables/CNEMOEulerVariable.cpp', 'variables/CNEMONSVariable.cpp', @@ -105,6 +106,7 @@ su2_cfd_src += files(['solvers/CSolverFactory.cpp', 'solvers/CHeatSolver.cpp', 'solvers/CIncEulerSolver.cpp', 'solvers/CIncNSSolver.cpp', + 'solvers/CPoissonSolver.cpp', 'solvers/CMeshSolver.cpp', 'solvers/CNEMOEulerSolver.cpp', 'solvers/CNEMONSSolver.cpp', @@ -129,6 +131,7 @@ su2_cfd_src += files(['numerics/CNumerics.cpp', 'numerics/flow/convection/hllc.cpp', 'numerics/flow/convection/ausm_slau.cpp', 'numerics/flow/convection/centered.cpp', + 'numerics/flow/convection/pressure_based.cpp', 'numerics/flow/flow_diffusion.cpp', 'numerics/flow/flow_sources.cpp', 'numerics/NEMO/CNEMONumerics.cpp', @@ -185,6 +188,7 @@ su2_cfd_src += files(['iteration/CIteration.cpp', 'iteration/CFEAIteration.cpp', 'iteration/CFEMFluidIteration.cpp', 'iteration/CFluidIteration.cpp', + 'iteration/CPBFluidIteration.cpp', 'iteration/CHeatIteration.cpp', 'iteration/CTurboIteration.cpp']) diff --git a/SU2_CFD/src/numerics/flow/convection/pressure_based.cpp b/SU2_CFD/src/numerics/flow/convection/pressure_based.cpp new file mode 100644 index 000000000000..bd4522f92725 --- /dev/null +++ b/SU2_CFD/src/numerics/flow/convection/pressure_based.cpp @@ -0,0 +1,201 @@ +/*! + * \file pressure_based.cpp + * \brief Implementations of fluxes for pressure-based solvers. + * \author T. Aalbers + * \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 "../../../../include/numerics/flow/convection/pressure_based.hpp" + +CPBConvection_Base::CPBConvection_Base(unsigned short val_nDim, unsigned short val_nVar, CConfig *config) : CNumerics(val_nDim, val_nVar, config) { + + implicit = (config->GetKind_TimeIntScheme_Flow() == EULER_IMPLICIT); + dynamic_grid = config->GetDynamic_Grid(); + energy = config->GetEnergy_Equation(); + variable_density = (config->GetVariable_Density_Model()); + + AdvectedVelocity = new su2double [MAXNDIM]; + Flux = new su2double [nVar]; + Jacobian_i = new su2double* [nVar]; + Jacobian_j = new su2double* [nVar]; + + for (iVar = 0; iVar < nVar; iVar++) { + Jacobian_i[iVar] = new su2double [nVar]; + Jacobian_j[iVar] = new su2double [nVar]; + } +} + +CPBConvection_Base::~CPBConvection_Base(void) { + + delete [] AdvectedVelocity; + delete [] Flux; + + for (iVar = 0; iVar < nVar; iVar++) { + delete [] Jacobian_i[iVar]; + delete [] Jacobian_j[iVar]; + } + + delete [] Jacobian_i; + delete [] Jacobian_j; + +} + +CNumerics::ResidualType<> CPBConvection_Base::ComputeResidual(const CConfig *config) { + + /*--- Primitive variables at point i and j ---*/ + + Pressure_i = V_i[0]; Pressure_j = V_j[0]; + DensityInc_i = V_i[nDim+2]; DensityInc_j = V_j[nDim+2]; + Enthalpy_i = V_i[nDim+3]; Enthalpy_j = V_j[nDim+3]; + MeanDensity = 0.5 * (DensityInc_i + DensityInc_j); + + /*--- Find the velocity that is advected ---*/ + + ComputeAdvectedQuantities(); + + /*--- Set the flux vector. ---*/ + + Flux[0] = MassFlux; + for (iDim = 0; iDim < nDim; ++iDim) + Flux[1+iDim] = MassFlux * AdvectedVelocity[iDim]; + Flux[nDim+1] = MassFlux * AdvectedEnthalpy; + + /*--- Find Jacobian ---*/ + + if (implicit) { + + for (jVar = 0; jVar < nVar; jVar++) + for (iVar = 0; iVar < nVar; iVar++) { + Jacobian_i[iVar][jVar] = 0.0; + Jacobian_j[iVar][jVar] = 0.0; + } + + /*--- We need the derivative of the equation of state to build the + preconditioning matrix. For now, the only option is the ideal gas + law, but in the future, dRhodT should be in the fluid model. ---*/ + + dRhodh_i = 0.0; dRhodh_j = 0.0; + if (variable_density) { + Temperature_i = V_i[nDim+1]; Temperature_j = V_j[nDim+1]; + Cp_i = V_i[nDim+8]; Cp_j = V_j[nDim+8]; + + dRhodh_i = -DensityInc_i / (Temperature_i * Cp_i); + dRhodh_j = -DensityInc_j / (Temperature_j * Cp_j); + } + + ComputeJacobianWeights(); + ComputeJacobian(DensityInc_i, &V_i[1], Enthalpy_i, dRhodh_i, weight_jacobian_i, Jacobian_i); + ComputeJacobian(DensityInc_j, &V_j[1], Enthalpy_j, dRhodh_j, weight_jacobian_j, Jacobian_j); + + } + + /*--- Remove energy contributions if we aren't solving the energy equation. ---*/ + + if (!energy) { + Flux[nDim+1] = 0.0; + if (implicit) { + for (iVar = 0; iVar < nVar; iVar++) { + Jacobian_i[iVar][nDim+1] = 0.0; + Jacobian_j[iVar][nDim+1] = 0.0; + + Jacobian_i[nDim+1][iVar] = 0.0; + Jacobian_j[nDim+1][iVar] = 0.0; + } + } + } + + return ResidualType<>(Flux, Jacobian_i, Jacobian_j); +} + + +void CPBConvection_Base::ComputeJacobian(su2double val_density, const su2double *val_velocity, + su2double val_enthalpy, su2double val_dRhodh, + su2double val_scale, su2double **val_Proj_Jac_Tensor) { + + su2double proj_vel = MassFlux / MeanDensity; + + /*--- Continuity row is discarded by PrepareImplicitIteration, which deletes row 0 under + PRESSURE_BASED because continuity is solved by the Poisson correction instead. ---*/ + + val_Proj_Jac_Tensor[0][0] = 0.0; + for (iDim = 0; iDim < nDim; iDim++) { + val_Proj_Jac_Tensor[0][iDim+1] = val_scale*(Normal[iDim] * (val_density)); + } + + /*--- MassFlux is frozen here: it comes from the previous Rhie-Chow interpolation, not from the + state being solved for. The only derivative left is d(m_f*v_adv)/du, and val_scale carries the + advection scheme's weight since v_adv is what the scheme upwinds or averages. ---*/ + + for (jDim = 0; jDim < nDim; jDim++) { + for (iDim = 0; iDim < nDim; iDim++) { + val_Proj_Jac_Tensor[iDim+1][jDim+1] = val_density * val_scale * proj_vel * delta[iDim][jDim]; + } + } + + /*--- With m_f frozen, Flux[nDim+1] = m_f*h_adv depends only on enthalpy and Flux[1+iDim] = + m_f*v_adv only on velocity, so the velocity<->enthalpy cross terms are exactly zero. ---*/ + + val_Proj_Jac_Tensor[nDim+1][0] = 0.0; + val_Proj_Jac_Tensor[0][nDim+1] = val_scale * ((val_dRhodh) * proj_vel); + val_Proj_Jac_Tensor[nDim+1][nDim+1] = val_scale*(((val_enthalpy)*(val_dRhodh) + (val_density))*proj_vel); + for (iDim = 0; iDim < nDim; iDim++) { + val_Proj_Jac_Tensor[nDim+1][iDim+1] = 0.0; + val_Proj_Jac_Tensor[iDim+1][nDim+1] = 0.0; + } + +} + +void CPBConvection_Central::ComputeAdvectedQuantities() { + + for (iDim = 0; iDim < nDim; iDim++) + AdvectedVelocity[iDim] = 0.5 * (V_i[iDim+1] + V_j[iDim+1]); + + AdvectedEnthalpy = 0.5 * (Enthalpy_i + Enthalpy_j); + +} + +void CPBConvection_Central::ComputeJacobianWeights() { + + weight_jacobian_i = weight_jacobian_j = 0.5; + +} + +void CPBConvection_Upwind::ComputeAdvectedQuantities() { + + bool Upw_i = (MassFlux>0); + + for (iDim = 0; iDim < nDim; iDim++) + AdvectedVelocity[iDim] = Upw_i ? V_i[iDim+1] : V_j[iDim+1]; + + AdvectedEnthalpy = (Upw_i) ? Enthalpy_i : Enthalpy_j; + +} + +void CPBConvection_Upwind::ComputeJacobianWeights() { + + bool Upw_i = (MassFlux>0); + + weight_jacobian_i = static_cast(Upw_i); + weight_jacobian_j = static_cast(!Upw_i); + +} diff --git a/SU2_CFD/src/output/CFlowIncOutput.cpp b/SU2_CFD/src/output/CFlowIncOutput.cpp index 23e5f066f154..674df02946ba 100644 --- a/SU2_CFD/src/output/CFlowIncOutput.cpp +++ b/SU2_CFD/src/output/CFlowIncOutput.cpp @@ -40,6 +40,8 @@ CFlowIncOutput::CFlowIncOutput(CConfig *config, unsigned short nDim) : CFlowOutp weakly_coupled_heat = config->GetWeakly_Coupled_Heat(); flamelet = (config->GetKind_Species_Model() == SPECIES_MODEL::FLAMELET); + pressure_based = (config->GetKind_Incomp_System() == INCOMP_SYSTEM::PRESSURE_BASED); + streamwisePeriodic = (config->GetKind_Streamwise_Periodic() != ENUM_STREAMWISE_PERIODIC::NONE); streamwisePeriodic_temperature = config->GetStreamwise_Periodic_Temperature(); @@ -91,8 +93,14 @@ CFlowIncOutput::CFlowIncOutput(CConfig *config, unsigned short nDim) : CFlowOutp restartFilename = config->GetRestart_FileName(); /*--- Set the default convergence field --- */ + /*--- The default field for the pressure-based solver is velocity, as the pressure is only + solved as a correction and it is thus possible to run the pb solver without corrections. to + ensure the default residual is always defined we use velocity instead ---*/ - if (convFields.empty()) convFields.emplace_back("RMS_PRESSURE"); + if (convFields.empty()) { + if (pressure_based) convFields.emplace_back("RMS_VELOCITY-X"); + else convFields.emplace_back("RMS_PRESSURE"); + } } @@ -111,7 +119,7 @@ void CFlowIncOutput::SetHistoryOutputFields(CConfig *config){ if (weakly_coupled_heat) AddHistoryOutput("RMS_TEMPERATURE", "rms[T]", ScreenOutputFormat::FIXED, "RMS_RES", "Root-mean square residual of the temperature.", HistoryFieldType::RESIDUAL); /// DESCRIPTION: Root-mean square residual of the enthalpy. if (heat) AddHistoryOutput("RMS_ENTHALPY", "rms[h]", ScreenOutputFormat::FIXED, "RMS_RES", "Root-mean square residual of the enthalpy.", HistoryFieldType::RESIDUAL); - + AddHistoryOutputFields_ScalarRMS_RES(config); /// DESCRIPTION: Root-mean square residual of the radiative energy (P1 model). @@ -168,6 +176,10 @@ void CFlowIncOutput::SetHistoryOutputFields(CConfig *config){ /// DESCRIPTION: Linear solver iterations AddHistoryOutput("LINSOL_ITER", "LinSolIter", ScreenOutputFormat::INTEGER, "LINSOL", "Number of iterations of the linear solver."); AddHistoryOutput("LINSOL_RESIDUAL", "LinSolRes", ScreenOutputFormat::FIXED, "LINSOL", "Residual of the linear solver."); + if (pressure_based) { + AddHistoryOutput("LINSOL_POISSON_ITER", "PoissonSolIter", ScreenOutputFormat::INTEGER, "LINSOL", "Number of iterations of the poisson solver."); + AddHistoryOutput("LINSOL_POISSON_RESIDUAL", "PoissonSolRes", ScreenOutputFormat::FIXED, "LINSOL", "Residual of the poisson solver."); + } AddHistoryOutputFieldsScalarLinsol(config); AddHistoryOutput("MIN_DELTA_TIME", "Min DT", ScreenOutputFormat::SCIENTIFIC, "CFL_NUMBER", "Current minimum local time step"); @@ -209,8 +221,13 @@ void CFlowIncOutput::LoadHistoryData(CConfig *config, CGeometry *geometry, CSolv CSolver* heat_solver = solver[HEAT_SOL]; CSolver* rad_solver = solver[RAD_SOL]; CSolver* mesh_solver = solver[MESH_SOL]; + CSolver* poisson_solver = solver[POISSON_SOL]; - SetHistoryOutputValue("RMS_PRESSURE", log10(flow_solver->GetRes_RMS(0))); + if (pressure_based) { + SetHistoryOutputValue("RMS_PRESSURE", log10(poisson_solver->GetRes_RMS(0))); + } else { + SetHistoryOutputValue("RMS_PRESSURE", log10(flow_solver->GetRes_RMS(0))); + } SetHistoryOutputValue("RMS_VELOCITY-X", log10(flow_solver->GetRes_RMS(1))); SetHistoryOutputValue("RMS_VELOCITY-Y", log10(flow_solver->GetRes_RMS(2))); if (nDim == 3) SetHistoryOutputValue("RMS_VELOCITY-Z", log10(flow_solver->GetRes_RMS(3))); @@ -218,7 +235,11 @@ void CFlowIncOutput::LoadHistoryData(CConfig *config, CGeometry *geometry, CSolv if (config->AddRadiation()) SetHistoryOutputValue("RMS_RAD_ENERGY", log10(rad_solver->GetRes_RMS(0))); - SetHistoryOutputValue("MAX_PRESSURE", log10(flow_solver->GetRes_Max(0))); + if (pressure_based) { + SetHistoryOutputValue("MAX_PRESSURE", log10(poisson_solver->GetRes_Max(0))); + } else { + SetHistoryOutputValue("MAX_PRESSURE", log10(flow_solver->GetRes_Max(0))); + } SetHistoryOutputValue("MAX_VELOCITY-X", log10(flow_solver->GetRes_Max(1))); SetHistoryOutputValue("MAX_VELOCITY-Y", log10(flow_solver->GetRes_Max(2))); if (nDim == 3) SetHistoryOutputValue("MAX_VELOCITY-Z", log10(flow_solver->GetRes_Max(3))); @@ -251,6 +272,10 @@ void CFlowIncOutput::LoadHistoryData(CConfig *config, CGeometry *geometry, CSolv SetHistoryOutputValue("LINSOL_ITER", flow_solver->GetIterLinSolver()); SetHistoryOutputValue("LINSOL_RESIDUAL", log10(flow_solver->GetResLinSolver())); + if (pressure_based) { + SetHistoryOutputValue("LINSOL_POISSON_ITER",poisson_solver->GetIterLinSolver()); + SetHistoryOutputValue("LINSOL_POISSON_RESIDUAL",log10(poisson_solver->GetResLinSolver())); + } if (config->GetDeform_Mesh()){ SetHistoryOutputValue("DEFORM_MIN_VOLUME", mesh_solver->GetMinimum_Volume()); @@ -447,7 +472,11 @@ void CFlowIncOutput::LoadVolumeData(CConfig *config, CGeometry *geometry, CSolve SetVolumeOutputValue("TEMPERATURE", iPoint, Node_Flow->GetTemperature(iPoint)); } - SetVolumeOutputValue("RES_PRESSURE", iPoint, solver[FLOW_SOL]->LinSysRes(iPoint, 0)); + if (pressure_based) { + SetVolumeOutputValue("RES_PRESSURE", iPoint, solver[POISSON_SOL]->LinSysRes(iPoint, 0)); + } else { + SetVolumeOutputValue("RES_PRESSURE", iPoint, solver[FLOW_SOL]->LinSysRes(iPoint, 0)); + } SetVolumeOutputValue("RES_VELOCITY-X", iPoint, solver[FLOW_SOL]->LinSysRes(iPoint, 1)); SetVolumeOutputValue("RES_VELOCITY-Y", iPoint, solver[FLOW_SOL]->LinSysRes(iPoint, 2)); if (nDim == 3) diff --git a/SU2_CFD/src/solvers/CEulerSolver.cpp b/SU2_CFD/src/solvers/CEulerSolver.cpp index acae4cbde050..32c265e55656 100644 --- a/SU2_CFD/src/solvers/CEulerSolver.cpp +++ b/SU2_CFD/src/solvers/CEulerSolver.cpp @@ -165,7 +165,7 @@ CEulerSolver::CEulerSolver(CGeometry *geometry, CConfig *config, if (rank == MASTER_NODE) cout << "Initialize Jacobian structure (" << description << "). MG level: " << iMesh <<"." << endl; - Jacobian.Initialize(nPoint, nPointDomain, nVar, nVar, true, geometry, config, ReducerStrategy, false, true); + Jacobian.Initialize(nPoint, nPointDomain, nVar, nVar, true, geometry, config, ReducerStrategy, true); } else { if (rank == MASTER_NODE) diff --git a/SU2_CFD/src/solvers/CGradientSmoothingSolver.cpp b/SU2_CFD/src/solvers/CGradientSmoothingSolver.cpp index fb26eee6661f..a439f913c835 100644 --- a/SU2_CFD/src/solvers/CGradientSmoothingSolver.cpp +++ b/SU2_CFD/src/solvers/CGradientSmoothingSolver.cpp @@ -107,18 +107,20 @@ CGradientSmoothingSolver::CGradientSmoothingSolver(CGeometry *geometry, CConfig } /*--- initializations for linear equation systems ---*/ + std::optional override_prec = + config->GetSmoothGradient() ? std::optional{config->GetKind_Grad_Linear_Solver_Prec()} : std::nullopt; if ( !config->GetSmoothOnSurface() ) { nVar = config->GetSmoothSepDim() ? 1 : nDim; LinSysSol.Initialize(nPoint, nPointDomain, nVar, 0.0); LinSysRes.Initialize(nPoint, nPointDomain, nVar, 0.0); - Jacobian.Initialize(nPoint, nPointDomain, nVar, nVar, false, geometry, config, false, true); + Jacobian.Initialize(nPoint, nPointDomain, nVar, nVar, false, geometry, config, false, false, override_prec); } else { if (config->GetSobMode() == ENUM_SOBOLEV_MODUS::PARAM_LEVEL_COMPLETE) { - Jacobian.Initialize(nPoint, nPointDomain, nDim, nDim, false, geometry, config, false , true); + Jacobian.Initialize(nPoint, nPointDomain, nDim, nDim, false, geometry, config, false, false, override_prec); } else { LinSysSol.Initialize(nPoint, nPointDomain, 1, 0.0); LinSysRes.Initialize(nPoint, nPointDomain, 1, 0.0); - Jacobian.Initialize(nPoint, nPointDomain, 1, 1, false, geometry, config, false, true); + Jacobian.Initialize(nPoint, nPointDomain, 1, 1, false, geometry, config, false, false, override_prec); } visited.resize(geometry->GetnPoint(), false); } diff --git a/SU2_CFD/src/solvers/CIncEulerSolver.cpp b/SU2_CFD/src/solvers/CIncEulerSolver.cpp index 41580c3062af..36ec2da234b5 100644 --- a/SU2_CFD/src/solvers/CIncEulerSolver.cpp +++ b/SU2_CFD/src/solvers/CIncEulerSolver.cpp @@ -39,7 +39,8 @@ CIncEulerSolver::CIncEulerSolver(CGeometry *geometry, CConfig *config, unsigned short iMesh, const bool navier_stokes) : - CFVMFlowSolverBase(*geometry, *config) { + CFVMFlowSolverBase(*geometry, *config), + pressure_based(config->GetKind_Incomp_System() == INCOMP_SYSTEM::PRESSURE_BASED) { SU2_ZONE_SCOPED /*--- Based on the navier_stokes boolean, determine if this constructor is @@ -155,7 +156,7 @@ CIncEulerSolver::CIncEulerSolver(CGeometry *geometry, CConfig *config, unsigned if (rank == MASTER_NODE) cout << "Initialize Jacobian structure (" << description << "). MG level: " << iMesh <<"." << endl; - Jacobian.Initialize(nPoint, nPointDomain, nVar, nVar, true, geometry, config, ReducerStrategy, false, true); + Jacobian.Initialize(nPoint, nPointDomain, nVar, nVar, true, geometry, config, ReducerStrategy, true); } else { if (rank == MASTER_NODE) @@ -216,9 +217,31 @@ CIncEulerSolver::CIncEulerSolver(CGeometry *geometry, CConfig *config, unsigned CommunicateInitialState(geometry, config); /*--- Sizing edge mass flux array ---*/ - if (config->GetBounded_Scalar()) + if (config->GetBounded_Scalar() || pressure_based) EdgeMassFluxes.resize(geometry->GetnEdge()) = su2double(0.0); + /*--- Pressure based solver specific allocations ---*/ + if (pressure_based) { + + /*--- Initialize the edge mass flux array ---*/ + + for (unsigned long iEdge = 0; iEdge < geometry->GetnEdge(); iEdge++) { + + EdgeMassFluxes[iEdge] = 0.0; + for (unsigned short iDim = 0; iDim < nDim; iDim++) + EdgeMassFluxes[iEdge] += Density_Inf * Velocity_Inf[iDim] * geometry->edges->GetNormal(iEdge)[iDim]; + + } + + /*--- Allocate corrections and relaxation ---*/ + + pressureCorrection.resize(nPointDomain) = su2double(0.0); + momentumCorrection.resize(nPointDomain,nDim) = su2double(0.0); + EdgeMassFluxCorrection.resize(geometry->GetnEdge()) = su2double(0.0); + alpha_p.resize(nPointDomain) = su2double(1.0); + + } + /*--- Add the solver name. ---*/ SolverName = "INC.FLOW"; @@ -1003,7 +1026,7 @@ void CIncEulerSolver::CommonPreprocessing(CGeometry *geometry, CSolver **solver_ /*--- Update the beta value based on the maximum velocity. ---*/ - SetBeta_Parameter(geometry, solver_container, config, iMesh); + if (!pressure_based) SetBeta_Parameter(geometry, solver_container, config, iMesh); /*--- Update the pressure range in the domain for target outflow mass flow rate. ---*/ @@ -1015,6 +1038,14 @@ void CIncEulerSolver::CommonPreprocessing(CGeometry *geometry, CSolver **solver_ SU2_OMP_SAFE_GLOBAL_ACCESS(GetOutlet_Properties(geometry, config, iMesh, Output);) } + /*--- Reset flag for strong BCs. ---*/ + if (pressure_based) { + SU2_OMP_FOR_STAT(omp_chunk_size) + for (unsigned long iPoint = 0; iPoint < nPointDomain; iPoint++) + nodes->ResetStrongBC(iPoint); + END_SU2_OMP_FOR + } + /*--- Initialize the Jacobian matrix and residual, not needed for the reducer strategy * as we set blocks (including diagonal ones) and completely overwrite. ---*/ @@ -1038,6 +1069,21 @@ void CIncEulerSolver::Preprocessing(CGeometry *geometry, CSolver **solver_contai CommonPreprocessing(geometry, solver_container, config, iMesh, iRKStep, RunTime_EqSystem, Output); + /*--- Source_Residual needs the pressure gradient to build the pressure-based solver's + momentum source term. For a viscous run CIncNSSolver::Preprocessing computes it unconditionally, + but this inviscid path otherwise only computes Gradient_Reconstruction, and only when MUSCL is + on - which silently leaves the pressure gradient at zero for an inviscid PB run with + MUSCL_FLOW=NO, or with mismatched reconstruction/base gradient methods. ---*/ + + if (pressure_based) { + switch (config->GetKind_Gradient_Method()) { + case GREEN_GAUSS: SetPrimitive_Gradient_GG(geometry, config); break; + case LEAST_SQUARES: + case WEIGHTED_LEAST_SQUARES: SetPrimitive_Gradient_LS(geometry, config); break; + default: break; + } + } + /*--- Upwind second order reconstruction ---*/ if (!Output && muscl && !center) { @@ -1124,18 +1170,6 @@ void CIncEulerSolver::SetTime_Step(CGeometry *geometry, CSolver **solver_contain unsigned short iMesh, unsigned long Iteration) { SU2_ZONE_SCOPED - /*--- Define an object to compute the speed of sound. ---*/ - struct SoundSpeed { - FORCEINLINE su2double operator() (const CIncEulerVariable& nodes, unsigned long iPoint, unsigned long jPoint) const { - return sqrt(0.5 * (nodes.GetBetaInc2(iPoint) + nodes.GetBetaInc2(jPoint))); - } - - FORCEINLINE su2double operator() (const CIncEulerVariable& nodes, unsigned long iPoint) const { - return sqrt(nodes.GetBetaInc2(iPoint)); - } - - } soundSpeed; - /*--- Define an object to compute the viscous eigenvalue. ---*/ struct LambdaVisc { const bool energy; @@ -1169,9 +1203,38 @@ void CIncEulerSolver::SetTime_Step(CGeometry *geometry, CSolver **solver_contain } lambdaVisc(config->GetEnergy_Equation()); - /*--- Now instantiate the generic implementation with the two functors above. ---*/ + if (pressure_based) { + /* Define an object to compute the speed of sound, as the speed of sound is theoretically infinite, + this makes no sense. However to be able to reuse the time step routine we artificially define the speed of sound + to be zero such that a regular advective time step is computed */ + struct SoundSpeed { + FORCEINLINE su2double operator() (const CIncEulerVariable& nodes, unsigned long iPoint, unsigned long jPoint = 0) const { + return 0.0; + } - SetTime_Step_impl(soundSpeed, lambdaVisc, geometry, solver_container, config, iMesh, Iteration); + } soundSpeed; + + /*--- Now instantiate the generic implementation with the two functors above. ---*/ + + SetTime_Step_impl(soundSpeed, lambdaVisc, geometry, solver_container, config, iMesh, Iteration); + + } else { + /*--- Define an object to compute the speed of sound. ---*/ + struct SoundSpeed { + FORCEINLINE su2double operator() (const CIncEulerVariable& nodes, unsigned long iPoint, unsigned long jPoint) const { + return sqrt(0.5 * (nodes.GetBetaInc2(iPoint) + nodes.GetBetaInc2(jPoint))); + } + + FORCEINLINE su2double operator() (const CIncEulerVariable& nodes, unsigned long iPoint) const { + return sqrt(nodes.GetBetaInc2(iPoint)); + } + + } soundSpeed; + + /*--- Now instantiate the generic implementation with the two functors above. ---*/ + + SetTime_Step_impl(soundSpeed, lambdaVisc, geometry, solver_container, config, iMesh, Iteration); + } } @@ -1240,6 +1303,10 @@ void CIncEulerSolver::Centered_Residual(CGeometry *geometry, CSolver **solver_co numerics->SetGridVel(geometry->nodes->GetGridVel(iPoint), geometry->nodes->GetGridVel(jPoint)); } + /*--- Set the edge mass flux ---*/ + + if (pressure_based) numerics->SetMassFlux(EdgeMassFluxes[iEdge]); + /*--- Compute residuals, and Jacobians ---*/ auto conv_residual = numerics->ComputeResidual(config); @@ -1413,6 +1480,10 @@ void CIncEulerSolver::Upwind_Residual(CGeometry *geometry, CSolver **solver_cont } + /*--- Set the edge mass flux ---*/ + + if (pressure_based) numerics->SetMassFlux(EdgeMassFluxes[iEdge]); + /*--- Compute the residual ---*/ auto conv_residual = numerics->ComputeResidual(config); @@ -1477,6 +1548,20 @@ void CIncEulerSolver::Source_Residual(CGeometry *geometry, CSolver **solver_cont AD::StartNoSharedReading(); + if (pressure_based) { + + /*--- Add pressure source term (V * gradp) ---*/ + + SU2_OMP_FOR_STAT(omp_chunk_size) + for (auto iPoint = 0ul; iPoint < nPointDomain; iPoint++) { + + for (unsigned short iDim = 0; iDim < nDim; iDim++) + LinSysRes(iPoint, iDim + 1) += geometry->nodes->GetVolume(iPoint) * nodes->GetGradient_Primitive(iPoint,prim_idx.Pressure(),iDim); + + } + END_SU2_OMP_FOR + } + if (body_force) { /*--- Loop over all points ---*/ @@ -1984,6 +2069,12 @@ template FORCEINLINE void CIncEulerSolver::Explicit_Iteration(CGeometry *geometry, CSolver **solver_container, CConfig *config, unsigned short iRKStep) { SU2_ZONE_SCOPED + + if (pressure_based) { + CFVMFlowSolverBase::Explicit_Iteration(geometry, solver_container, config, iRKStep); + return; + } + struct Precond { const CIncEulerSolver* solver; su2activematrix matrix; @@ -2033,10 +2124,12 @@ void CIncEulerSolver::PrepareImplicitIteration(CGeometry *geometry, CSolver**, C struct IncPrec { const CIncEulerSolver* solver; - const bool active = true; + const bool active; su2activematrix matrix; - IncPrec(const CIncEulerSolver* s, unsigned short nVar) : solver(s) { matrix.resize(nVar,nVar); } + IncPrec(const CIncEulerSolver* s, unsigned short nVar) : solver(s), active(!s->pressure_based) { + matrix.resize(nVar,nVar); + } FORCEINLINE const su2activematrix& operator() (const CConfig* config, unsigned long iPoint, su2double delta) { solver->SetPreconditioner(config, iPoint, delta, matrix); @@ -2046,6 +2139,17 @@ void CIncEulerSolver::PrepareImplicitIteration(CGeometry *geometry, CSolver**, C } precond(this, nVar); PrepareImplicitIteration_impl(precond, geometry, config); + + /*--- Delete pressure rows for segregated solver type. ---*/ + if (pressure_based) { + SU2_OMP_FOR_(schedule(static,omp_chunk_size) SU2_NOWAIT) + for (unsigned long iPoint = 0; iPoint < nPoint; iPoint++) { + Jacobian.DeleteValsRowi(iPoint, 0); + LinSysRes(iPoint,0) = 0.0; + LinSysSol(iPoint,0) = 0.0; + } + END_SU2_OMP_FOR + } } void CIncEulerSolver::SetBeta_Parameter(CGeometry *geometry, CSolver **solver_container, @@ -2292,7 +2396,6 @@ void CIncEulerSolver::BC_Far_Field(CGeometry *geometry, CSolver **solver_contain V_infty[prim_idx.Pressure()] = GetPressure_Inf(); /*--- Dirichlet condition for temperature at far-field (if energy is active). ---*/ - V_infty[prim_idx.Temperature()] = GetTemperature_Inf(); /*-- Enthalpy at far-field. ---*/ @@ -2308,7 +2411,7 @@ void CIncEulerSolver::BC_Far_Field(CGeometry *geometry, CSolver **solver_contain /*--- Beta coefficient stored at the node ---*/ - V_infty[prim_idx.Beta()] = nodes->GetBetaInc2(iPoint); + if (!pressure_based) V_infty[prim_idx.Beta()] = nodes->GetBetaInc2(iPoint); /*--- Cp is needed for Temperature equation. ---*/ @@ -2322,22 +2425,75 @@ void CIncEulerSolver::BC_Far_Field(CGeometry *geometry, CSolver **solver_contain conv_numerics->SetGridVel(geometry->nodes->GetGridVel(iPoint), geometry->nodes->GetGridVel(iPoint)); - /*--- Compute the convective residual using an upwind scheme ---*/ + if (pressure_based) { - auto residual = conv_numerics->ComputeResidual(config); + /*--- Decide if the boundary should be an inlet or an outlet ---*/ - /*--- Update residual value ---*/ + su2double Face_Flux = 0.0; + if (dynamic_grid) + for (iDim = 0; iDim < nDim; iDim++) + Face_Flux += nodes->GetDensity(iPoint)*(V_domain[iDim+1]-geometry->nodes->GetGridVel(iPoint)[iDim])*Normal[iDim]; + else + for (iDim = 0; iDim < nDim; iDim++) + Face_Flux += nodes->GetDensity(iPoint)*V_domain[iDim+1]*Normal[iDim]; - LinSysRes.AddBlock(iPoint, residual); + bool inflow = false; + if ((Face_Flux < 0.0) && (fabs(Face_Flux) > EPS)) inflow = true; - /*--- Convective Jacobian contribution for implicit integration ---*/ + if (inflow) { - if (implicit) - Jacobian.AddBlock2Diag(iPoint, residual.jacobian_i); + /*--- Set this face as an inlet via a strong BC. ---*/ + + LinSysRes.SetBlock_Zero(iPoint); + + /*--- Mark as a strong BC which is important for deciding if a velocity correction should be applied ---*/ + + nodes->SetStrongBC(iPoint); + + if (implicit) + for (iDim = 0; iDim < nDim; iDim++) + Jacobian.DeleteValsRowi(iPoint, iDim+1); + + } else { + + /*--- Set the edge mass flux ---*/ + + conv_numerics->SetMassFlux(Face_Flux); + + /*--- Compute the residual using an upwind scheme ---*/ + + conv_numerics->SetPrimitive(V_domain, V_domain); + + auto residual = conv_numerics->ComputeResidual(config); + + LinSysRes.AddBlock(iPoint, residual); + nodes->SetSolution(iPoint, 0, GetPressure_Inf()); + nodes->SetPressure(iPoint); + + if (implicit) + Jacobian.AddBlock2Diag(iPoint, residual.jacobian_i); + + } + } else { + + /*--- Compute the convective residual using an upwind scheme ---*/ + + auto residual = conv_numerics->ComputeResidual(config); + + /*--- Update residual value ---*/ + + LinSysRes.AddBlock(iPoint, residual); + + /*--- Convective Jacobian contribution for implicit integration ---*/ + + if (implicit) + Jacobian.AddBlock2Diag(iPoint, residual.jacobian_i); + + } /*--- Viscous residual contribution ---*/ - if (!viscous || energy_multicomponent) continue; + if (!viscous || energy_multicomponent || pressure_based) continue; /*--- Set transport properties at infinity. ---*/ @@ -2481,6 +2637,9 @@ void CIncEulerSolver::BC_Inlet(CGeometry *geometry, CSolver **solver_container, case INLET_TYPE::PRESSURE_INLET: + if (pressure_based) + SU2_MPI::Error("Pressure Inlet is currently an unsupported INC_INLET_TYPE for pressure based solver.", CURRENT_FUNCTION); + /*--- Retrieve the specified total pressure for the inlet. ---*/ P_total = Inlet_Ptotal[val_marker][iVertex]/config->GetPressure_Ref(); @@ -2558,96 +2717,113 @@ void CIncEulerSolver::BC_Inlet(CGeometry *geometry, CSolver **solver_container, V_inlet[prim_idx.Pressure()] = nodes->GetPressure(iPoint); } - /*-- Enthalpy is needed for energy equation. ---*/ - const su2double* scalar_inlet = nullptr; - if (species_model) scalar_inlet = config->GetInlet_SpeciesVal(config->GetMarker_All_TagBound(val_marker)); - CFluidModel* auxFluidModel = solver_container[FLOW_SOL]->GetFluidModel(); - auxFluidModel->SetTDState_T(V_inlet[prim_idx.Temperature()], scalar_inlet); + if (pressure_based) { - /*--- For the flamelet model with FLOW_MARKERS enthalpy BC, we obtain the inlet enthalpy - from the flamelet species solver With SPECIES_MARKERS, the enthalpy in MARKER_INLET_SPECIES - is used directly. ---*/ - if (config->GetKind_Species_Model() == SPECIES_MODEL::FLAMELET && - config->GetFlamelet_Enthalpy_BC() == FLAMELET_ENTHALPY_BC::FLOW_MARKERS) - V_inlet[prim_idx.Enthalpy()] = nodes->GetEnthalpy(iPoint); - else - V_inlet[prim_idx.Enthalpy()] = auxFluidModel->GetEnthalpy(); + /*--- Directly overwrite the velocity at the boundary nodes as a dirichlet boundary condition ---*/ - /*--- Access density at the node. This is either constant by - construction, or will be set fixed implicitly by the temperature - and equation of state. ---*/ + nodes->SetVelocity_Old(iPoint,V_inlet+prim_idx.Velocity()); - V_inlet[prim_idx.Density()] = nodes->GetDensity(iPoint); + LinSysRes.SetBlock_Zero(iPoint); - /*--- Beta coefficient from the config file ---*/ + if (pressure_based) nodes->SetStrongBC(iPoint); - V_inlet[prim_idx.Beta()] = nodes->GetBetaInc2(iPoint); + if (implicit) + for (iDim = 0; iDim < nDim; iDim++) + Jacobian.DeleteValsRowi(iPoint, iDim+1); - /*--- Cp is needed for Temperature equation. ---*/ + } else { - V_inlet[prim_idx.CpTotal()] = nodes->GetSpecificHeatCp(iPoint); + /*-- Enthalpy is needed for energy equation. ---*/ + const su2double* scalar_inlet = nullptr; + if (species_model) scalar_inlet = config->GetInlet_SpeciesVal(config->GetMarker_All_TagBound(val_marker)); + CFluidModel* auxFluidModel = solver_container[FLOW_SOL]->GetFluidModel(); + auxFluidModel->SetTDState_T(V_inlet[prim_idx.Temperature()], scalar_inlet); + + /*--- For the flamelet model with FLOW_MARKERS enthalpy BC, we obtain the inlet enthalpy + from the flamelet species solver With SPECIES_MARKERS, the enthalpy in MARKER_INLET_SPECIES + is used directly. ---*/ + if (config->GetKind_Species_Model() == SPECIES_MODEL::FLAMELET && + config->GetFlamelet_Enthalpy_BC() == FLAMELET_ENTHALPY_BC::FLOW_MARKERS) + V_inlet[prim_idx.Enthalpy()] = nodes->GetEnthalpy(iPoint); + else + V_inlet[prim_idx.Enthalpy()] = auxFluidModel->GetEnthalpy(); + + /*--- Access density at the node. This is either constant by + construction, or will be set fixed implicitly by the temperature + and equation of state. ---*/ - /*--- Set various quantities in the solver class ---*/ + V_inlet[prim_idx.Density()] = nodes->GetDensity(iPoint); - conv_numerics->SetPrimitive(V_domain, V_inlet); + /*--- Beta coefficient from the config file ---*/ - if (dynamic_grid) - conv_numerics->SetGridVel(geometry->nodes->GetGridVel(iPoint), - geometry->nodes->GetGridVel(iPoint)); + V_inlet[prim_idx.Beta()] = nodes->GetBetaInc2(iPoint); - /*--- Compute the residual using an upwind scheme ---*/ + /*--- Cp is needed for Temperature equation. ---*/ - auto residual = conv_numerics->ComputeResidual(config); + V_inlet[prim_idx.CpTotal()] = nodes->GetSpecificHeatCp(iPoint); - /*--- Update residual value ---*/ + /*--- Set various quantities in the solver class ---*/ - LinSysRes.AddBlock(iPoint, residual); + conv_numerics->SetPrimitive(V_domain, V_inlet); - /*--- Jacobian contribution for implicit integration ---*/ + if (dynamic_grid) + conv_numerics->SetGridVel(geometry->nodes->GetGridVel(iPoint), + geometry->nodes->GetGridVel(iPoint)); - if (implicit) - Jacobian.AddBlock2Diag(iPoint, residual.jacobian_i); + /*--- Compute the residual using an upwind scheme ---*/ - /*--- Viscous contribution, commented out because serious convergence problems ---*/ + auto residual = conv_numerics->ComputeResidual(config); - if (!viscous || energy_multicomponent) continue; + /*--- Update residual value ---*/ + + LinSysRes.AddBlock(iPoint, residual); - /*--- Set transport properties at the inlet ---*/ + /*--- Jacobian contribution for implicit integration ---*/ - V_inlet[prim_idx.LaminarViscosity()] = nodes->GetLaminarViscosity(iPoint); - V_inlet[prim_idx.EddyViscosity()] = nodes->GetEddyViscosity(iPoint); - V_inlet[prim_idx.ThermalConductivity()] = nodes->GetThermalConductivity(iPoint); + if (implicit) + Jacobian.AddBlock2Diag(iPoint, residual.jacobian_i); - /*--- Set the normal vector and the coordinates ---*/ + /*--- Viscous contribution, commented out because serious convergence problems ---*/ - visc_numerics->SetNormal(Normal); - su2double Coord_Reflected[MAXNDIM]; - GeometryToolbox::PointPointReflect(nDim, geometry->nodes->GetCoord(Point_Normal), - geometry->nodes->GetCoord(iPoint), Coord_Reflected); - visc_numerics->SetCoord(geometry->nodes->GetCoord(iPoint), Coord_Reflected); + if (!viscous || energy_multicomponent) continue; - /*--- Primitive variables, and gradient ---*/ + /*--- Set transport properties at the inlet ---*/ - visc_numerics->SetPrimitive(V_domain, V_inlet); - visc_numerics->SetPrimVarGradient(nodes->GetGradient_Primitive(iPoint), - nodes->GetGradient_Primitive(iPoint)); + V_inlet[prim_idx.LaminarViscosity()] = nodes->GetLaminarViscosity(iPoint); + V_inlet[prim_idx.EddyViscosity()] = nodes->GetEddyViscosity(iPoint); + V_inlet[prim_idx.ThermalConductivity()] = nodes->GetThermalConductivity(iPoint); - /*--- Turbulent kinetic energy ---*/ + /*--- Set the normal vector and the coordinates ---*/ - if (config->GetKind_Turb_Model() == TURB_MODEL::SST) - visc_numerics->SetTurbKineticEnergy(solver_container[TURB_SOL]->GetNodes()->GetSolution(iPoint,0), - solver_container[TURB_SOL]->GetNodes()->GetSolution(iPoint,0)); + visc_numerics->SetNormal(Normal); + su2double Coord_Reflected[MAXNDIM]; + GeometryToolbox::PointPointReflect(nDim, geometry->nodes->GetCoord(Point_Normal), + geometry->nodes->GetCoord(iPoint), Coord_Reflected); + visc_numerics->SetCoord(geometry->nodes->GetCoord(iPoint), Coord_Reflected); - /*--- Compute and update residual ---*/ + /*--- Primitive variables, and gradient ---*/ - auto residual_v = visc_numerics->ComputeResidual(config); + visc_numerics->SetPrimitive(V_domain, V_inlet); + visc_numerics->SetPrimVarGradient(nodes->GetGradient_Primitive(iPoint), + nodes->GetGradient_Primitive(iPoint)); - LinSysRes.SubtractBlock(iPoint, residual_v); + /*--- Turbulent kinetic energy ---*/ - /*--- Jacobian contribution for implicit integration ---*/ + if (config->GetKind_Turb_Model() == TURB_MODEL::SST) + visc_numerics->SetTurbKineticEnergy(solver_container[TURB_SOL]->GetNodes()->GetSolution(iPoint,0), + solver_container[TURB_SOL]->GetNodes()->GetSolution(iPoint,0)); - if (implicit) - Jacobian.SubtractBlock2Diag(iPoint, residual_v.jacobian_i); + /*--- Compute and update residual ---*/ + + auto residual_v = visc_numerics->ComputeResidual(config); + + LinSysRes.SubtractBlock(iPoint, residual_v); + + /*--- Jacobian contribution for implicit integration ---*/ + + if (implicit) + Jacobian.SubtractBlock2Diag(iPoint, residual_v.jacobian_i); + } } END_SU2_OMP_FOR } @@ -2731,6 +2907,9 @@ void CIncEulerSolver::BC_Outlet(CGeometry *geometry, CSolver **solver_container, case INC_OUTLET_TYPE::MASS_FLOW_OUTLET: + if (pressure_based) + SU2_MPI::Error("Mass Flow Outlet is currently an unsupported INC_OUTLET_TYPE for pressure based solver.", CURRENT_FUNCTION); + /*--- Retrieve the specified target mass flow at the outlet. ---*/ mDot_Target = config->GetOutlet_Pressure(Marker_Tag)/(config->GetDensity_Ref() * config->GetVelocity_Ref()); @@ -2784,7 +2963,7 @@ void CIncEulerSolver::BC_Outlet(CGeometry *geometry, CSolver **solver_container, /*--- Beta coefficient from the config file ---*/ - V_outlet[prim_idx.Beta()] = nodes->GetBetaInc2(iPoint); + if (!pressure_based) V_outlet[prim_idx.Beta()] = nodes->GetBetaInc2(iPoint); /*--- Cp is needed for Temperature equation. ---*/ @@ -2801,6 +2980,21 @@ void CIncEulerSolver::BC_Outlet(CGeometry *geometry, CSolver **solver_container, conv_numerics->SetGridVel(geometry->nodes->GetGridVel(iPoint), geometry->nodes->GetGridVel(iPoint)); + /*--- Set the edge mass flux ---*/ + + if (pressure_based) { + su2double ProjVelocity = 0.0; + if (dynamic_grid) + for (iDim = 0; iDim < nDim; iDim++) + ProjVelocity += (V_domain[iDim+prim_idx.Velocity()] - geometry->nodes->GetGridVel(iPoint)[iDim]) * Normal[iDim]; + else + for (iDim = 0; iDim < nDim; iDim++) + ProjVelocity += V_domain[iDim+prim_idx.Velocity()] * Normal[iDim]; + su2double MeanDensity = 0.5 * (V_domain[prim_idx.Density()] + V_outlet[prim_idx.Density()]); + su2double MassFlux = MeanDensity * ProjVelocity; + conv_numerics->SetMassFlux(MassFlux); + } + /*--- Compute the residual using an upwind scheme ---*/ auto residual = conv_numerics->ComputeResidual(config); @@ -3369,6 +3563,31 @@ void CIncEulerSolver::LoadRestart(CGeometry **geometry, CSolver ***solver, CConf LoadRestart_impl(geometry, solver, config, val_iter, val_update_geo, Solution, nVar_Restart); + if (pressure_based) { + + /*--- Initialize the edge mass flux array ---*/ + + unsigned long iEdge, iPoint, jPoint; + su2double MeanVelocity[MAXNDIM], MeanDensity; + + for (iEdge = 0; iEdge < geometry[MESH_0]->GetnEdge(); iEdge++) { + + iPoint = geometry[MESH_0]->edges->GetNode(iEdge,0); jPoint = geometry[MESH_0]->edges->GetNode(iEdge,1); + + /*--- Compute average velocities and density between two nodes ---*/ + + for (unsigned short iDim = 0; iDim < nDim; iDim++) + MeanVelocity[iDim] = 0.5 * (nodes->GetVelocity(iPoint, iDim) + nodes->GetVelocity(jPoint, iDim)); + + MeanDensity = 0.5 * (nodes->GetDensity(iPoint) + nodes->GetDensity(jPoint)); + + /*--- Initialize the edge mass flux ---*/ + + EdgeMassFluxes[iEdge] = 0.0; + for (unsigned short iDim = 0; iDim < nDim; iDim++) + EdgeMassFluxes[iEdge] += MeanDensity * MeanVelocity[iDim] * geometry[MESH_0]->edges->GetNormal(iEdge)[iDim]; + } + } } void CIncEulerSolver::SetFreeStream_Solution(const CConfig *config){ @@ -3408,3 +3627,382 @@ void CIncEulerSolver::ExtractAdjoint_SolutionExtra(su2activevector& adj_sol, con adj_sol[0] = SU2_TYPE::GetDerivative(SPvals.Streamwise_Periodic_PressureDrop); } } + +void CIncEulerSolver::CorrectPressureGradient(su2double* corrected_grad_pressure, + const su2double* avg_grad_pressure, + const su2double val_pressure_i, + const su2double val_pressure_j, + const su2double* val_edge_vector, + const su2double val_dist_ij_2) { + + /*--- Eq 15.62 F Moukalled, L Mangani M. Darwish OpenFOAM and uFVM book. ---*/ + su2double Proj_Mean_Grad_Pressure_Edge = 0.0; + for (unsigned short iDim = 0; iDim < nDim; iDim++) { + Proj_Mean_Grad_Pressure_Edge += avg_grad_pressure[iDim]*val_edge_vector[iDim]; + } + for (unsigned short iDim = 0; iDim < nDim; iDim++) { + corrected_grad_pressure[iDim] = avg_grad_pressure[iDim] - (Proj_Mean_Grad_Pressure_Edge - + (val_pressure_j-val_pressure_i))*val_edge_vector[iDim] / val_dist_ij_2; + } +} + +void CIncEulerSolver::ComputeEdgeMassFluxesRhieChow(CGeometry *geometry, CSolver **solver_container, CConfig *config) { + SU2_ZONE_SCOPED + + /*--- Compute gradients to be used in Rhie Chow interpolation ---*/ + + if (config->GetKind_Gradient_Method() == GREEN_GAUSS) { + SetPrimitive_Gradient_GG(geometry, config); + } + if (config->GetKind_Gradient_Method() == LEAST_SQUARES || + config->GetKind_Gradient_Method() == WEIGHTED_LEAST_SQUARES) { + SetPrimitive_Gradient_LS(geometry, config); + } + + unsigned short iDim; + unsigned long iPoint, jPoint; + const su2double *Normal = nullptr, *Coord_i, *Coord_j, *GridVel_i,*GridVel_j; + su2double GradPressure_f[MAXNDIM], GradPressure_avg[MAXNDIM], Edge_Vector[MAXNDIM], dist_ij_2, Coeff_Mom; + + CSolver* poisson_solver = solver_container[POISSON_SOL]; + CVariable* poisson_nodes = poisson_solver->GetNodes(); + + /*--- Mass flux is computed over all edges. Each edge writes only its own slot of + EdgeMassFluxes, so no coloring is needed to avoid races between edges sharing a point. ---*/ + + SU2_OMP_FOR_STAT(omp_chunk_size) + for (unsigned long iEdge = 0; iEdge < geometry->GetnEdge(); iEdge++) { + + iPoint = geometry->edges->GetNode(iEdge,0); jPoint = geometry->edges->GetNode(iEdge,1); + + Normal = geometry->edges->GetNormal(iEdge); + + if (dynamic_grid) { + GridVel_i = geometry->nodes->GetGridVel(iPoint); + GridVel_j = geometry->nodes->GetGridVel(jPoint); + } + + /*--- Correct pressure gradient ---*/ + + Coord_i = geometry->nodes->GetCoord(iPoint); + Coord_j = geometry->nodes->GetCoord(jPoint); + dist_ij_2 = 0.0; + for (iDim = 0; iDim < nDim; iDim++) { + Edge_Vector[iDim] = Coord_j[iDim]-Coord_i[iDim]; + dist_ij_2 += Edge_Vector[iDim]*Edge_Vector[iDim]; + } + + /*--- 1. Interpolate the pressure gradient based on node values ---*/ + + for (iDim = 0; iDim < nDim; iDim++) + GradPressure_avg[iDim] = 0.5 * (nodes->GetGradient_Primitive(iPoint,prim_idx.Pressure(),iDim) + nodes->GetGradient_Primitive(jPoint,prim_idx.Pressure(),iDim)); + + /*--- 2. Compute pressure gradient at the face ---*/ + + CorrectPressureGradient(GradPressure_f, GradPressure_avg, nodes->GetPressure(iPoint), nodes->GetPressure(jPoint), Edge_Vector, dist_ij_2); + + /*--- Linearly interpolated coefficient. A point under a strong velocity BC has no momentum + coefficient, so the edge uses that of its other node. ---*/ + + Coeff_Mom = 0.5*(poisson_nodes->GetMomCoeff(nodes->GetStrongBC(iPoint) ? jPoint : iPoint) + + poisson_nodes->GetMomCoeff(nodes->GetStrongBC(jPoint) ? iPoint : jPoint)); + + /*--- Initialize mass flux ---*/ + + EdgeMassFluxes[iEdge] = 0.0; + + for (iDim = 0; iDim < nDim; iDim++) { + + /*--- Face average mass flux. ---*/ + + su2double meanMassFlux = 0.5 * (nodes->GetDensity(iPoint) * nodes->GetVelocity(iPoint,iDim) + + nodes->GetDensity(jPoint) * nodes->GetVelocity(jPoint,iDim)); + + if (dynamic_grid) { + meanMassFlux -= 0.5 * (nodes->GetDensity(iPoint) * GridVel_i[iDim] + nodes->GetDensity(jPoint) * GridVel_j[iDim]); + } + + /*--- Correction based on Rhie-Chow. ---*/ + + su2double RhieChowCorrection = Coeff_Mom * (GradPressure_f[iDim] - GradPressure_avg[iDim]); + + su2double CorrectedMassFlux = meanMassFlux - RhieChowCorrection; + + /*--- Update edge mass flux ---*/ + + EdgeMassFluxes[iEdge] += CorrectedMassFlux * Normal[iDim]; + + } + } + END_SU2_OMP_FOR +} + + +void CIncEulerSolver::ApplyPressureVelocityCorrection(CGeometry *geometry, CSolver **solver_container, CConfig *config) { + SU2_ZONE_SCOPED + + /*--- Start of computing the corrections ---*/ + unsigned long iPoint, jPoint, iMarker, iVertex; + unsigned short iDim, KindBC; + su2double Current_Pressure, factor, PCorr_Ref, Vol, delT; + string Marker_Tag; + const su2double *Normal = nullptr; + + bool AutomaticURF = config->GetSIMPLE_Options().AutomaticRelaxationFactors; + + CSolver* poisson_solver = solver_container[POISSON_SOL]; + CVariable* poisson_nodes = poisson_solver->GetNodes(); + + /*--- Combine all pressure corrections into a vector for easy access ---*/ + + SU2_OMP_FOR_STAT(omp_chunk_size) + for (iPoint = 0; iPoint < nPointDomain; iPoint++) { + pressureCorrection[iPoint] = poisson_nodes->GetSolution(iPoint,0); + } + END_SU2_OMP_FOR + + /*--- Define a reference pressure. Fixed at 0 for now: for a domain with at least one + Dirichlet pressure boundary (an outlet or a far-field with outflow) this reference is + unused (the boundary loop below overwrites pressureCorrection at those points instead), + but for a fully closed domain (walls only) the Poisson system is pure-Neumann and has no + pressure datum, so pinning a single point's correction to a real reference value would be + needed there instead of leaving it at 0. ---*/ + + PCorr_Ref = 0.0; + + /*--- Compute Velocity Corrections and under relaxation factor for the pressure. ---*/ + + SU2_OMP_FOR_STAT(omp_chunk_size) + for (iPoint = 0; iPoint < nPointDomain; iPoint++) { + for (iDim = 0; iDim < nDim; iDim++) { + momentumCorrection[iPoint][iDim] = - poisson_nodes->GetMomCoeff(iPoint) * poisson_nodes->GetGradient(iPoint,0,iDim); + } + + if (AutomaticURF) { + /*--- a_P = dR/d(rho u) has mass/time units, so it needs the same /density SetMomCoeff + * applies for the same reason. Block row 0 is the continuity/pressure row, not a velocity + * direction - starting the diagonal sum from iDim=0 mixed it into a_P, and summing every + * velocity direction's diagonal made alpha_p dimension-dependent. Use the x-momentum row + * alone, matching SetMomCoeff's own convention that this coefficient is the same in every + * direction. ---*/ + const auto view = Jacobian.GetBlockView(iPoint, iPoint); + factor = view(1, 1) / nodes->GetDensity(iPoint); + Vol = geometry->nodes->GetVolume(iPoint); + delT = nodes->GetDelta_Time(iPoint); + alpha_p[iPoint] = (Vol / delT) / (factor + (Vol / delT)); + } else { + alpha_p[iPoint] = config->GetSIMPLE_Options().Relaxation_Factor_Pressure; + } + + } + END_SU2_OMP_FOR + + // TODO: The HbyA correction is always zero during the first PISO correction, therefore this can be skipped. + SU2_OMP_FOR_STAT(omp_chunk_size) + for (iPoint = 0; iPoint < nPointDomain; iPoint++) { + for (iDim = 0; iDim < nDim; iDim++) { + momentumCorrection[iPoint][iDim] += poisson_nodes->GetHbyACorrection(iPoint, iDim); + } + } + END_SU2_OMP_FOR + + /*--- Compute the edge corrections based on the average of the momentum coefficients and the average of the p' gradient. ---*/ + + su2double* Coord_i,* Coord_j; + su2double GradPressure_f[MAXNDIM], GradPressure_avg[MAXNDIM], Edge_Vector[MAXNDIM], dist_ij_2; + SU2_OMP_FOR_STAT(omp_chunk_size) + for (unsigned long iEdge = 0; iEdge < geometry->GetnEdge(); iEdge++) { + + iPoint = geometry->edges->GetNode(iEdge,0); jPoint = geometry->edges->GetNode(iEdge,1); + + Normal = geometry->edges->GetNormal(iEdge); + + /*--- Correct pressure deviation (p') gradient ---*/ + + Coord_i = geometry->nodes->GetCoord(iPoint); + Coord_j = geometry->nodes->GetCoord(jPoint); + dist_ij_2 = 0.0; + for (iDim = 0; iDim < nDim; iDim++) { + Edge_Vector[iDim] = Coord_j[iDim]-Coord_i[iDim]; + dist_ij_2 += Edge_Vector[iDim]*Edge_Vector[iDim]; + } + + /*--- 1. Interpolate the p' gradient based on node values - deliberately zero: this is a + compact (orthogonal-only) mass-flux correction, not an oversight. Feeding the real, + node-averaged p' gradient here (available via GetGradient(), already used correctly a few + lines above for the interior velocity correction) was tried and measured to reintroduce + checkerboard-style pressure-velocity decoupling - the 3D sphere case diverges from the first + iteration regardless of CFL, with the same sign-flipping, magnitude-growing oscillation + signature as the unrelated broken-SIMPLEC finding. A node-averaged gradient is exactly the + kind of quantity Rhie-Chow interpolation exists to avoid using directly in a face mass flux. + Kept explicitly zero (rather than reachable only by accident through + GetGradient_Primitive's unrelated always-0.0 base-class stub) so a future refactor cannot + silently reintroduce this instability by "fixing" what looks like a missing override. ---*/ + + for (iDim = 0; iDim < nDim; iDim++) + GradPressure_avg[iDim] = 0.0; + + /*--- 2. Compute p' at the face ---*/ + + CorrectPressureGradient(GradPressure_f, GradPressure_avg, poisson_nodes->GetSolution(iPoint, 0), poisson_nodes->GetSolution(jPoint, 0), Edge_Vector, dist_ij_2); + + /*--- Initialize projected velocity and density ---*/ + + su2double ProjMassFluxCorrection = 0.0; + + for (iDim = 0; iDim < nDim; iDim++) { + + su2double MassFluxCorrection = + -0.5 * (poisson_nodes->GetMomCoeff(nodes->GetStrongBC(iPoint) ? jPoint : iPoint) + + poisson_nodes->GetMomCoeff(nodes->GetStrongBC(jPoint) ? iPoint : jPoint)) * GradPressure_f[iDim]; + + /*--- 2nd piso correction term (HbyA') --- (TODO: this is zero for the first correction and can thus also be skipped) ---*/ + + MassFluxCorrection += 0.5*(poisson_nodes->GetHbyACorrection(iPoint, iDim) + +poisson_nodes->GetHbyACorrection(jPoint, iDim)); + + /*--- Accumulate into the edge mass flux correction ---*/ + + ProjMassFluxCorrection += MassFluxCorrection * Normal[iDim]; + } + + /*--- Set the mass flux correction ---*/ + + EdgeMassFluxCorrection[iEdge] = ProjMassFluxCorrection; + } + END_SU2_OMP_FOR + + /*--- Reassign strong boundary conditions ---*/ + /*--- For now I only have velocity inlet and fully developed outlet. Will need to add other types of inlet/outlet conditions + * where different treatment of pressure might be needed. Symmetry and Euler wall are weak BCs. ---*/ + for (iMarker = 0; iMarker < geometry->GetnMarker(); iMarker++) { + KindBC = config->GetMarker_All_KindBC(iMarker); + Marker_Tag = config->GetMarker_All_TagBound(iMarker); + switch (KindBC) { + case EULER_WALL: case SYMMETRY_PLANE: + break; + + /*--- Nothing at MPI boundaries ---*/ + case SEND_RECEIVE: + break; + + /*--- Only a fully developed outlet is implemented. For pressure, a dirichlet + BC has to be applied and no correction is necessary. Velocity has a neumann BC. ---*/ + case OUTLET_FLOW:{ + auto Kind_Outlet = config->GetKind_Inc_Outlet(Marker_Tag); + switch (Kind_Outlet) { + case INC_OUTLET_TYPE::PRESSURE_OUTLET:{ + SU2_OMP_FOR_DYN(OMP_MIN_SIZE) + for (iVertex = 0; iVertex < geometry->GetnVertex(iMarker); iVertex++) { + iPoint = geometry->vertex[iMarker][iVertex]->GetNode(); + if (geometry->nodes->GetDomain(iPoint)) + pressureCorrection[iPoint] = PCorr_Ref; + } + END_SU2_OMP_FOR + break; + } + //TODO: other outlet types + default: + SU2_MPI::Error("The requested outflow boundary condition has not yet been implemented for the pressure based poisson solver", CURRENT_FUNCTION); + break; + } + break; + } + + /*--- Only a fixed velocity inlet is implemented now. Along with the wall boundaries, + * the velocity is known and thus no correction is necessary.---*/ + case ISOTHERMAL: case HEAT_FLUX: case INLET_FLOW: { + SU2_OMP_FOR_DYN(OMP_MIN_SIZE) + for (iVertex = 0; iVertex < geometry->GetnVertex(iMarker); iVertex++) { + iPoint = geometry->vertex[iMarker][iVertex]->GetNode(); + if (geometry->nodes->GetDomain(iPoint)) { + for (iDim = 0; iDim < nDim; iDim++) + momentumCorrection[iPoint][iDim] = 0.0; + alpha_p[iPoint] = 1.0; + } + } + END_SU2_OMP_FOR + break; + } + + /*--- Farfield is treated as a fully developed flow for pressure and a fixed pressure is + * used, thus no correction is necessary. The treatment for velocity depends on whether the + * flow is into the domain or out. If flow is in, a dirichlet bc is applied and no correction + * is made, otherwise a Neumann BC is used and velocity is adjusted. ---*/ + + case FAR_FIELD: + SU2_OMP_FOR_DYN(OMP_MIN_SIZE) + for (iVertex = 0; iVertex < geometry->GetnVertex(iMarker); iVertex++) { + iPoint = geometry->vertex[iMarker][iVertex]->GetNode(); + if (geometry->nodes->GetDomain(iPoint)) { + // Check if the boundary condition is an inlet or not + if (nodes->GetStrongBC(iPoint)) { + for (iDim = 0; iDim < nDim; iDim++) + momentumCorrection[iPoint][iDim] = 0.0; + } + pressureCorrection[iPoint] = PCorr_Ref; + } + } + END_SU2_OMP_FOR + break; + + default: + SU2_MPI::Error("The requested boundary condition has not yet been implemented for the pressure based poisson solver", CURRENT_FUNCTION); + break; + } + } + + /*--- Apply corrections to the nodal solution ---*/ + SU2_OMP_FOR_STAT(omp_chunk_size) + for (iPoint = 0; iPoint < nPointDomain; iPoint++) { + + /*--- Pressure corrections ---*/ + + Current_Pressure = nodes->GetPressure(iPoint); + Current_Pressure += alpha_p[iPoint] * (pressureCorrection[iPoint] - PCorr_Ref); + nodes->SetSolution(iPoint, 0, Current_Pressure); + + /*--- Velocity corrections ---*/ + + for (iDim = 0; iDim < nDim; ++iDim) { + nodes->SetSolution(iPoint, iDim + 1, nodes->GetSolution(iPoint,iDim + 1) + momentumCorrection[iPoint][iDim] / nodes->GetDensity(iPoint)); + poisson_nodes->SetMomentumCorrection(iPoint,iDim,momentumCorrection[iPoint][iDim]); + } + + /*--- Update primitive variables ---*/ + + nodes->SetPressure(iPoint); + nodes->SetVelocity(iPoint); + + } + END_SU2_OMP_FOR + + /*--- Add corrections to the edge velocities. Each edge accumulates only into its own slot, + so partitioning by edge index is race-free. ---*/ + + SU2_OMP_FOR_STAT(omp_chunk_size) + for (unsigned long iEdge = 0; iEdge < geometry->GetnEdge(); iEdge++) + EdgeMassFluxes[iEdge] += EdgeMassFluxCorrection[iEdge]; + END_SU2_OMP_FOR + + /*--- Reset HbyA for next iteration ---*/ + + SU2_OMP_FOR_STAT(omp_chunk_size) + for (iPoint = 0; iPoint < nPoint; iPoint++) { + for (iDim = 0; iDim < nDim; iDim++) + poisson_nodes->SetHbyACorrection(iPoint, iDim, 0.0); + } + END_SU2_OMP_FOR + + + /*--- periodic communication for both the momentum and the poisson equations as both are now updated ---*/ + for (unsigned short iPeriodic = 1; iPeriodic <= config->GetnMarker_Periodic()/2; iPeriodic++) { + InitiatePeriodicComms(geometry, config, iPeriodic, PERIODIC_IMPLICIT); + CompletePeriodicComms(geometry, config, iPeriodic, PERIODIC_IMPLICIT); + } + + /*--- Communicate updated velocities and pressure ---*/ + InitiateComms(geometry, config, MPI_QUANTITIES::SOLUTION); + CompleteComms(geometry, config, MPI_QUANTITIES::SOLUTION); + +} diff --git a/SU2_CFD/src/solvers/CIncNSSolver.cpp b/SU2_CFD/src/solvers/CIncNSSolver.cpp index 2532215cf8d8..ec1b78935e1f 100644 --- a/SU2_CFD/src/solvers/CIncNSSolver.cpp +++ b/SU2_CFD/src/solvers/CIncNSSolver.cpp @@ -94,7 +94,8 @@ void CIncNSSolver::Preprocessing(CGeometry *geometry, CSolver **solver_container if (config->GetKind_Gradient_Method() == GREEN_GAUSS) { SetPrimitive_Gradient_GG(geometry, config); } - else if (config->GetKind_Gradient_Method() == WEIGHTED_LEAST_SQUARES) { + else if (config->GetKind_Gradient_Method() == LEAST_SQUARES || + config->GetKind_Gradient_Method() == WEIGHTED_LEAST_SQUARES) { SetPrimitive_Gradient_LS(geometry, config); } @@ -498,6 +499,8 @@ void CIncNSSolver::BC_Wall_Generic(const CGeometry *geometry, const CConfig *con LinSysRes(iPoint, iDim+1) = 0.0; nodes->SetVel_ResTruncError_Zero(iPoint); + if (pressure_based) nodes->SetStrongBC(iPoint); + /*--- Enforce the no-slip boundary condition in a strong way by modifying the velocity-rows of the Jacobian (1 on the diagonal). ---*/ @@ -654,6 +657,8 @@ void CIncNSSolver::BC_ConjugateHeat_Interface(CGeometry *geometry, CSolver **sol LinSysRes(iPoint, iDim+1) = 0.0; nodes->SetVel_ResTruncError_Zero(iPoint); + if (pressure_based) nodes->SetStrongBC(iPoint); + /*--- Enforce the no-slip boundary condition in a strong way by modifying the velocity-rows of the Jacobian (1 on the diagonal). ---*/ diff --git a/SU2_CFD/src/solvers/CPoissonSolver.cpp b/SU2_CFD/src/solvers/CPoissonSolver.cpp new file mode 100644 index 000000000000..080c4572f1e4 --- /dev/null +++ b/SU2_CFD/src/solvers/CPoissonSolver.cpp @@ -0,0 +1,592 @@ +/*! + * \file CPoissonSolver.cpp + * \brief Main subroutines for solving the Poisson equation + * \author T. Aalbers + * \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 "../../include/solvers/CPoissonSolver.hpp" +#include +#include "../../../Common/include/toolboxes/geometry_toolbox.hpp" +#include "../../include/solvers/CScalarSolver.inl" + +/*--- Explicit instantiation of the parent class of CPoissonSolver. ---*/ +template class CScalarSolver; + +CPoissonSolver::CPoissonSolver(CGeometry *geometry, CConfig *config, unsigned short iMesh) + : CScalarSolver(geometry, config, false, false, LINEAR_SOLVER_MODE::POISSON) { + SU2_ZONE_SCOPED + + /*--- Dimension of the problem --> pressure deviation is the only conservative variable ---*/ + + nVar = 1; + nPrimVar = 1; + nPoint = geometry->GetnPoint(); + nPointDomain = geometry->GetnPointDomain(); + + /*--- Initialize nVarGrad for deallocation ---*/ + + nVarGrad = nVar; + + /*--- Define geometry constants in the solver structure ---*/ + + nDim = geometry->GetnDim(); + + /*--- Define some structures for locating max residuals ---*/ + + Residual_RMS.resize(nVar,0.0); + Residual_Max.resize(nVar,0.0); + Point_Max.resize(nVar,0); + Point_Max_Coord.resize(nVar,nDim) = su2double(0.0); + + + /*--- Initialization of the structure of the whole Jacobian ---*/ + + if (rank == MASTER_NODE) cout << "Initialize Jacobian structure (poisson equation) MG level: " << iMesh << "." << endl; + Jacobian.Initialize(nPoint, nPointDomain, nVar, nVar, true, geometry, config, ReducerStrategy, false, config->GetKind_Poisson_Linear_Solver_Prec()); + LinSysSol.Initialize(nPoint, nPointDomain, nVar, 0.0); + LinSysRes.Initialize(nPoint, nPointDomain, nVar, 0.0); + if (ReducerStrategy) EdgeFluxes.Initialize(geometry->GetnEdge(), geometry->GetnEdge(), nVar, nullptr); + + /*--- Initialize the nodes vector. ---*/ + + nodes = new CPoissonVariable(0.0, nPoint, nDim, nVar, config); + + SetBaseClassPointerToNodes(); + + /*--- Communicate and store volume and the number of neighbors for any dual CVs that lie on on periodic markers. ---*/ + for (unsigned short iPeriodic = 1; iPeriodic <= config->GetnMarker_Periodic() / 2; iPeriodic++) { + InitiatePeriodicComms(geometry, config, iPeriodic, PERIODIC_VOLUME); + CompletePeriodicComms(geometry, config, iPeriodic, PERIODIC_VOLUME); + InitiatePeriodicComms(geometry, config, iPeriodic, PERIODIC_NEIGHBORS); + CompletePeriodicComms(geometry, config, iPeriodic, PERIODIC_NEIGHBORS); + } + + /*--- MPI solution ---*/ + + InitiateComms(geometry, config, MPI_QUANTITIES::SOLUTION); + CompleteComms(geometry, config, MPI_QUANTITIES::SOLUTION); + + /*--- Add the solver name. ---*/ + + SolverName = "POISSON"; + +} + + +void CPoissonSolver::Preprocessing(CGeometry *geometry, CSolver **solver_container, CConfig *config, unsigned short iMesh, + unsigned short iRKStep, unsigned short RunTime_EqSystem, bool Output) { + SU2_ZONE_SCOPED + SU2_OMP_SAFE_GLOBAL_ACCESS(config->SetGlobalParam(config->GetKind_Solver(), RunTime_EqSystem);) + + /*--- Reset pressure corrections to zero for next iteration. ---*/ + SU2_OMP_FOR_STAT(omp_chunk_size) + for (unsigned long iPoint = 0; iPoint < nPointDomain; iPoint++) { + nodes->SetSolution(iPoint,0,0.0); + } + END_SU2_OMP_FOR + + /*--- Communicate updated Poisson solution (which should now be zero everywhere) ---*/ + solver_container[POISSON_SOL]->InitiateComms(geometry, config, MPI_QUANTITIES::SOLUTION); + solver_container[POISSON_SOL]->CompleteComms(geometry, config, MPI_QUANTITIES::SOLUTION); + + for (unsigned short iPeriodic = 1; iPeriodic <= config->GetnMarker_Periodic()/2; iPeriodic++) { + solver_container[POISSON_SOL]->InitiatePeriodicComms(geometry, config, iPeriodic, PERIODIC_IMPLICIT); + solver_container[POISSON_SOL]->CompletePeriodicComms(geometry, config, iPeriodic, PERIODIC_IMPLICIT); + } + + /*--- Compute the gradients only after the solution has been reset to zero ---*/ + CommonPreprocessing(geometry, config, Output); + + /*--- Need to clear EdgeFluxes and Jacobian. ---*/ + if (!Output) { + LinSysRes.SetValZero(); + if (ReducerStrategy) EdgeFluxes.SetValZero(); + Jacobian.SetValZero(); + } + + +} + +void CPoissonSolver::Postprocessing(CGeometry *geometry, + CSolver **solver_container, + CConfig *config, + unsigned short iMesh) { + SU2_ZONE_SCOPED + + /*--- Compute gradients of the pressure correction p' so we can use it to find the velocity corrections ---*/ + if (config->GetKind_Gradient_Method() == GREEN_GAUSS) + SetSolution_Gradient_GG(geometry, config,false); + + if (config->GetKind_Gradient_Method() == LEAST_SQUARES || + config->GetKind_Gradient_Method() == WEIGHTED_LEAST_SQUARES) + SetSolution_Gradient_LS(geometry, config,false); + +} + + +void CPoissonSolver::SetMomCoeff(CGeometry *geometry, CSolver **solver_container, CConfig *config, bool periodic, unsigned short iMesh) { + + bool simplec = (config->GetKind_PBIter() == PBITER::SIMPLEC); + bool implicit = (config->GetKind_TimeIntScheme_Flow() == EULER_IMPLICIT); + + const CSolver* flow_solution = solver_container[FLOW_SOL]; + const CVariable* flow_nodes = flow_solution->GetNodes(); + + if (implicit) { + + SU2_OMP_FOR_STAT(omp_chunk_size) + for (unsigned long iPoint = 0; iPoint < nPointDomain; iPoint++) { + + su2double Vol = geometry->nodes->GetVolume(iPoint); + + /*--- The momentum equation is not assembled at a strong velocity BC, DeleteValsRowi zeroes + * the row and writes 1.0 on the diagonal, so there is no A_p to read. Nothing consumes the + * value stored here: edges touching the point take the coefficient of their other node, the + * velocity correction is overwritten in the boundary loop, and HbyA scales it by a numerator + * that is identically zero. Store a finite placeholder and skip the corrections below, which + * divide by zero for a transient removal factor of 1. ---*/ + + if (flow_nodes->GetStrongBC(iPoint)) { + nodes->SetMomCoeff(iPoint, Vol * flow_nodes->GetDensity(iPoint)); + continue; + } + + /*--- Self coefficient A_p = dR/d(rhou), the x-momentum entry is used for all directions. ---*/ + + su2double A_p = flow_solution->Jacobian.GetBlockView(iPoint, iPoint)(1,1) / flow_nodes->GetDensity(iPoint); + + /*--- Optionally alter the coefficient using SIMPLEC ---*/ + + su2double Sum_A_nb = 0.0; + + if (simplec) { + for (unsigned long iNeigh = 0; iNeigh < geometry->nodes->GetnPoint(iPoint); iNeigh++) { + auto jPoint = geometry->nodes->GetPoint(iPoint,iNeigh); + Sum_A_nb += flow_solution->Jacobian.GetBlockView(iPoint, jPoint)(1,1) / flow_nodes->GetDensity(jPoint); + } + } + + /*--- Add simplec neighbour contributions and optional time dependent term. ---*/ + + su2double delT = flow_nodes->GetDelta_Time(iPoint); + + su2double CorrectedA_p = A_p - Sum_A_nb - config->GetSIMPLE_Options().Transient_Term_Removal_Factor * (Vol / delT); + + /*--- Invert the momentum coefficient to 1/a_p and scale by the volume and density so it can be used as diffusion coefficient in the poisson eq ---*/ + + nodes->SetMomCoeff(iPoint, Vol / CorrectedA_p); + + } + END_SU2_OMP_FOR + } + else { + + SU2_MPI::Error("The definition of the momentum coefficient for an explicit solution is currently only an approximation and is not yet tested.", CURRENT_FUNCTION); + + /* + SU2_OMP_FOR_STAT(omp_chunk_size) + for (unsigned long iPoint = 0; iPoint < nPointDomain; iPoint++) { + + su2double delT = flow_nodes->GetDelta_Time(iPoint); + + su2double Mom_Coeff = delT; + + nodes->SetMomCoeff(iPoint, Mom_Coeff); + } + END_SU2_OMP_FOR + */ + } + + /*--- Insert MPI call here. ---*/ + InitiateComms(geometry, config, MPI_QUANTITIES::MOM_COEFF); + CompleteComms(geometry, config, MPI_QUANTITIES::MOM_COEFF); +} + + +void CPoissonSolver::ComputeHbyA(CGeometry *geometry, CSolver **solver_container, CConfig *config, unsigned short iMesh) { + + unsigned short iDim; + unsigned long iPoint, jPoint, iNeigh; + su2double H, A_p, A_nb; + bool implicit = (config->GetKind_TimeIntScheme_Flow() == EULER_IMPLICIT); + + const CSolver* flow_solver = solver_container[FLOW_SOL]; + const CVariable* flow_nodes = flow_solver->GetNodes(); + + /*--- First exchange momentum correction which is required to compute H. ---*/ + InitiateComms(geometry, config, MPI_QUANTITIES::MOM_CORRECTION); + CompleteComms(geometry, config, MPI_QUANTITIES::MOM_CORRECTION); + + if (implicit) { + SU2_OMP_FOR_STAT(omp_chunk_size) + for (iPoint = 0; iPoint < nPointDomain; iPoint++) { + + /*--- Read A_p back from SetMomCoeff's own result (MomCoeff = Vol/A_p) instead of + * re-deriving it from the raw Jacobian diagonal: that raw diagonal is exactly what + * SetMomCoeff itself does not use any more, since it applies the strong-BC row-deletion + * reconstruction and the SIMPLEC/transient-removal corrections before storing MomCoeff. + * Re-reading the uncorrected diagonal here would use a different, inconsistent A_p in the + * PISO correction than the one the pressure equation was actually assembled against. ---*/ + A_p = geometry->nodes->GetVolume(iPoint) / nodes->GetMomCoeff(iPoint); + + for (iDim = 0; iDim < nDim; ++iDim) { + H = 0.0; + for (iNeigh = 0; iNeigh < geometry->nodes->GetnPoint(iPoint); iNeigh++) { + jPoint = geometry->nodes->GetPoint(iPoint,iNeigh); + A_nb = flow_solver->Jacobian.GetBlockView(iPoint, jPoint)(1,1) / flow_nodes->GetDensity(jPoint); + H -= A_nb * nodes->GetMomentumCorrection(jPoint, iDim); + } + nodes->SetHbyACorrection(iPoint, iDim, H/A_p); + } + } + END_SU2_OMP_FOR + } + else { + SU2_MPI::Error("HbyA is currently not supported for an explicit momentum solver.", CURRENT_FUNCTION); + } + + /*--- Exchange HbyA with MPI call. ---*/ + InitiateComms(geometry, config, MPI_QUANTITIES::HBYA_CORRECTION); + CompleteComms(geometry, config, MPI_QUANTITIES::HBYA_CORRECTION); +} + +void CPoissonSolver::Viscous_Residual(CGeometry *geometry, CSolver **solver_container, CNumerics **numerics_container, + CConfig *config, unsigned short iMesh, unsigned short iRKStep) { + SU2_ZONE_SCOPED + + CNumerics* numerics = numerics_container[VISC_TERM + omp_get_thread_num() * MAX_TERMS]; + + bool pausePreacc = false; + if (ReducerStrategy) + pausePreacc = AD::PausePreaccumulation(); + else + AD::StartNoSharedReading(); + + for (auto color : EdgeColoring) { + SU2_OMP_FOR_DYN(nextMultiple(OMP_MIN_SIZE, color.groupSize)) + for (auto k = 0ul; k < color.size; ++k) { + auto iEdge = color.indices[k]; + Viscous_Residual(iEdge, geometry, solver_container, numerics, config); + } + END_SU2_OMP_FOR + } + + /*--- Restore preaccumulation and adjoint evaluation state. ---*/ + AD::ResumePreaccumulation(pausePreacc); + if (!ReducerStrategy) AD::EndNoSharedReading(); + + if (ReducerStrategy) { + SumEdgeFluxes(geometry); + Jacobian.SetDiagonalAsColumnSum(); + } +} + +void CPoissonSolver::Source_Residual(CGeometry *geometry, CSolver **solver_container, CNumerics **numerics_container, + CConfig *config, unsigned short iMesh) { + SU2_ZONE_SCOPED + + su2double *GridVel_i; + + const CSolver* flow_solver = solver_container[FLOW_SOL]; + const CVariable* flow_nodes = flow_solver->GetNodes(); + + const auto& edgeMassFluxes = *(flow_solver->GetEdgeMassFluxes()); + + /*--- flux is computed over all edges ---*/ + + for (auto color : EdgeColoring) { + SU2_OMP_FOR_DYN(nextMultiple(OMP_MIN_SIZE, color.groupSize)) + for (auto k = 0ul; k < color.size; ++k) { + auto iEdge = color.indices[k]; + + auto iPoint = geometry->edges->GetNode(iEdge,0); auto jPoint = geometry->edges->GetNode(iEdge,1); + su2double Normal[MAXNDIM] = {0.0}; + geometry->edges->GetNormal(iEdge, Normal); + + /*--- Only for the second pressure correction in the case PISO is used, we need the additional HbyA(u') term ---*/ + // TODO: currently its just set to zero and does not contribute for the first piso correctin but would be nice if this entire block would be skipped otherwise. + su2double MeanHbyA = 0.0; + for (unsigned short iDim = 0; iDim < nDim; ++iDim) + MeanHbyA += 0.5 * (nodes->GetHbyACorrection(iPoint, iDim) + nodes->GetHbyACorrection(jPoint, iDim)) * Normal[iDim]; + + /*--- Add the mass flux and the HbyA correction to the source term for the poisson equation ---*/ + + su2double EdgeSource = edgeMassFluxes[iEdge] + MeanHbyA; + auto residual = CNumerics::ResidualType<>(&EdgeSource, nullptr, nullptr); + + if (geometry->nodes->GetDomain(iPoint)) LinSysRes.AddBlock(iPoint, residual); + if (geometry->nodes->GetDomain(jPoint)) LinSysRes.SubtractBlock(jPoint, residual); + + } + END_SU2_OMP_FOR + } + + /*--- Now add corrections to the previously computed mass fluxes for boundary conditions which alter the mass flux. + geometry->vertex[...]->GetNormal() returns the normal pointing into the domain, so accumulating with -= below + (rather than negating Normal first, as CIncEulerSolver does) yields the outward mass flux used as the RHS here. ---*/ + + unsigned short iDim, KindBC; + unsigned long iMarker, iVertex, iPoint; + string Marker_Tag; + su2double MassFlux_corr = 0.0, Normal[MAXNDIM]; + + /*--- Loop boundary edges ---*/ + for (iMarker = 0; iMarker < geometry->GetnMarker(); iMarker++) { + KindBC = config->GetMarker_All_KindBC(iMarker); + Marker_Tag = config->GetMarker_All_TagBound(iMarker); + + switch (KindBC) { + /*--- Wall boundaries have zero mass flux (irrespective of grid movement) ---*/ + case EULER_WALL: case ISOTHERMAL: case HEAT_FLUX: case SYMMETRY_PLANE: + break; + + /*--- Nothing has to happen at MPI boundaries*/ + case SEND_RECEIVE: + break; + + case INLET_FLOW: + SU2_OMP_FOR_DYN(OMP_MIN_SIZE) + for (iVertex = 0; iVertex < geometry->GetnVertex(iMarker); iVertex++) { + iPoint = geometry->vertex[iMarker][iVertex]->GetNode(); + + if (!geometry->nodes->GetDomain(iPoint)) continue; + + geometry->vertex[iMarker][iVertex]->GetNormal(Normal); + + MassFlux_corr = 0.0; + if (dynamic_grid) { + GridVel_i = geometry->nodes->GetGridVel(iPoint); + for (iDim = 0; iDim < nDim; iDim++) + MassFlux_corr -= flow_nodes->GetDensity(iPoint) * (flow_nodes->GetVelocity(iPoint, iDim) - GridVel_i[iDim]) * Normal[iDim]; + } + else + for (iDim = 0; iDim < nDim; iDim++) + MassFlux_corr -= flow_nodes->GetDensity(iPoint) * flow_nodes->GetVelocity(iPoint, iDim) * Normal[iDim]; + + auto residual = CNumerics::ResidualType<>(&MassFlux_corr, nullptr, nullptr); + + if (geometry->nodes->GetDomain(iPoint)) LinSysRes.AddBlock(iPoint, residual); + + } + END_SU2_OMP_FOR + break; + + case FAR_FIELD: + /*--- Treat the farfield as a fully developed outlet for pressure. ---*/ + SU2_OMP_FOR_DYN(OMP_MIN_SIZE) + for (iVertex = 0; iVertex < geometry->GetnVertex(iMarker); iVertex++) { + iPoint = geometry->vertex[iMarker][iVertex]->GetNode(); + + if (geometry->nodes->GetDomain(iPoint)) { + geometry->vertex[iMarker][iVertex]->GetNormal(Normal); + + if (dynamic_grid) + GridVel_i = geometry->nodes->GetGridVel(iPoint); + + MassFlux_corr = 0.0; + if (dynamic_grid) + for (iDim = 0; iDim < nDim; iDim++) + MassFlux_corr -= flow_nodes->GetDensity(iPoint) * (flow_nodes->GetVelocity(iPoint, iDim) - GridVel_i[iDim]) * Normal[iDim]; + else + for (iDim = 0; iDim < nDim; iDim++) + MassFlux_corr -= flow_nodes->GetDensity(iPoint) * flow_nodes->GetVelocity(iPoint, iDim) * Normal[iDim]; + + auto residual = CNumerics::ResidualType<>(&MassFlux_corr, nullptr, nullptr); + LinSysRes.AddBlock(iPoint, residual); + + } + } + END_SU2_OMP_FOR + break; + + + case OUTLET_FLOW:{ + /*--- Note I am assuming a fully developed outlet, thus the pressure value is prescribed + * -- and a dirichlet bc has to be applied along outlet faces. The Massflux, which forms the RHS + * -- of the equation, is set to zero to enforce the dirichlet bc. ---*/ + + auto Kind_Outlet = config->GetKind_Inc_Outlet(Marker_Tag); + + switch (Kind_Outlet) { + case INC_OUTLET_TYPE::PRESSURE_OUTLET: + SU2_OMP_FOR_DYN(OMP_MIN_SIZE) + for (iVertex = 0; iVertex < geometry->GetnVertex(iMarker); iVertex++) { + iPoint = geometry->vertex[iMarker][iVertex]->GetNode(); + + if (geometry->nodes->GetDomain(iPoint)) { + geometry->vertex[iMarker][iVertex]->GetNormal(Normal); + + if (dynamic_grid) + GridVel_i = geometry->nodes->GetGridVel(iPoint); + + MassFlux_corr = 0.0; + if (dynamic_grid) + for (iDim = 0; iDim < nDim; iDim++) + MassFlux_corr -= flow_nodes->GetDensity(iPoint) * (flow_nodes->GetVelocity(iPoint, iDim) - GridVel_i[iDim]) * Normal[iDim]; + else + for (iDim = 0; iDim < nDim; iDim++) + MassFlux_corr -= flow_nodes->GetDensity(iPoint) * (flow_nodes->GetVelocity(iPoint, iDim)) * Normal[iDim]; + + auto residual = CNumerics::ResidualType<>(&MassFlux_corr, nullptr, nullptr); + + if (geometry->nodes->GetDomain(iPoint)) LinSysRes.AddBlock(iPoint, residual); + } + } + END_SU2_OMP_FOR + break; + default: + SU2_MPI::Error("Requested type of outlet boundary condition not available", CURRENT_FUNCTION); + break; + } + break; + } + + default: + SU2_MPI::Error("Invalid boundary condition for flux correction", CURRENT_FUNCTION); + break; + + } + } + +} + +void CPoissonSolver::ImplicitEuler_Iteration(CGeometry *geometry, CSolver **solver_container, CConfig *config) { + SU2_ZONE_SCOPED + + /*--- No actual time integration is done here. The routine is used as a means to solve the linear equation + * resulting from the poisson equation. The linear system is solved using the jacobian matrix in a way + * consistent with the rest of the code. The time step is set to zero and no under-relaxation is applied to the + * jacobian matrix. ---*/ + + /*--- Local residual variables for current thread ---*/ + su2double resMax[MAXNVAR] = {0.0}, resRMS[MAXNVAR] = {0.0}; + unsigned long idxMax[MAXNVAR] = {0}; + + SetResToZero(); + + /*--- Right hand side of the system (-Residual) and initial guess (x = 0) ---*/ + SU2_OMP_FOR_(schedule(static,omp_chunk_size) SU2_NOWAIT) + for (unsigned long iPoint = 0; iPoint < nPointDomain; iPoint++) { + + /*--- Multigrid contribution to residual. ---*/ + su2double *local_Res_TruncError = nodes->GetResTruncError(iPoint); + + for (unsigned short iVar = 0; iVar < nVar; iVar++) { + LinSysRes(iPoint, iVar) = - (LinSysRes(iPoint, iVar) + local_Res_TruncError[iVar] ); + LinSysSol(iPoint, iVar) = 0.0; + + /*--- "Add" residual at (iPoint,iVar) to local residual variables. ---*/ + ResidualReductions_PerThread(iPoint, iVar, LinSysRes(iPoint, iVar), resRMS, resMax, idxMax); + } + } + END_SU2_OMP_FOR + + /*--- "Add" residuals from all threads to global residual variables. ---*/ + ResidualReductions_FromAllThreads(geometry, config, resRMS, resMax, idxMax); + + /*--- Solve or smooth the linear system. ---*/ + + SU2_OMP_FOR_(schedule(static,OMP_MIN_SIZE) SU2_NOWAIT) + for (unsigned long iPoint = nPointDomain; iPoint < nPoint; iPoint++) { + LinSysRes.SetBlock_Zero(iPoint); + LinSysSol.SetBlock_Zero(iPoint); + } + END_SU2_OMP_FOR + + auto iter = System.Solve(Jacobian, LinSysRes, LinSysSol, geometry, config); + + BEGIN_SU2_OMP_SAFE_GLOBAL_ACCESS { + SetIterLinSolver(iter); + SetResLinSolver(System.GetResidual()); + } + END_SU2_OMP_SAFE_GLOBAL_ACCESS + + SU2_OMP_FOR_STAT(omp_chunk_size) + for (unsigned long iPoint = 0; iPoint < nPointDomain; iPoint++) { + for (unsigned short iVar = 0; iVar < nVar; iVar++) { + nodes->AddSolution(iPoint, iVar, LinSysSol(iPoint,iVar)); + } + } + END_SU2_OMP_FOR + + InitiateComms(geometry, config, MPI_QUANTITIES::SOLUTION); + CompleteComms(geometry, config, MPI_QUANTITIES::SOLUTION); + +} + +void CPoissonSolver::BC_HeatFlux_Wall(CGeometry *geometry, CSolver **solver_container, CNumerics *conv_numerics, CNumerics *visc_numerics, CConfig *config, unsigned short val_marker) { + /*--- Zero flux (Neumann) BC on pressure ---*/ +} + +void CPoissonSolver::BC_Far_Field(CGeometry *geometry, CSolver **solver_container, CNumerics *conv_numerics, + CNumerics *visc_numerics, CConfig *config, unsigned short val_marker) { + + unsigned long iVertex, iPoint; + su2double pressureDeviation = 0.0; + + SU2_OMP_FOR_STAT(OMP_MIN_SIZE) + for (iVertex = 0; iVertex < geometry->nVertex[val_marker]; iVertex++) { + iPoint = geometry->vertex[val_marker][iVertex]->GetNode(); + + /*--- Check if the node belongs to the domain (i.e, not a halo node) ---*/ + if (!geometry->nodes->GetDomain(iPoint)) continue; + /*--- The farfield boundary is considered as an inlet-outlet boundary, where flow + * can either enter or leave. For pressure, it is treated as a fully developed flow + * and a dirichlet BC is applied. For velocity, based on the sign of massflux, either + * a dirichlet or a neumann BC is applied (in correction routine). ---*/ + + LinSysRes.SetBlock_Zero(iPoint); + + nodes->SetSolution(iPoint, &pressureDeviation); + nodes->SetSolution_Old(iPoint,&pressureDeviation); + Jacobian.DeleteValsRowi(iPoint, 0); + } + END_SU2_OMP_FOR +} + +void CPoissonSolver::BC_Inlet(CGeometry *geometry, CSolver **solver_container, CNumerics *conv_numerics, CNumerics *visc_numerics, CConfig *config, unsigned short val_marker) { + /*--- Zero flux (Neumann) BC on pressure ---*/ +} + +void CPoissonSolver::BC_Outlet(CGeometry *geometry, CSolver **solver_container, CNumerics *conv_numerics, CNumerics *visc_numerics, CConfig *config, unsigned short val_marker) { + unsigned long iVertex, iPoint; + su2double pressureDeviation = 0.0; + + SU2_OMP_FOR_STAT(OMP_MIN_SIZE) + for (iVertex = 0; iVertex < geometry->nVertex[val_marker]; iVertex++) { + iPoint = geometry->vertex[val_marker][iVertex]->GetNode(); + + /*--- Check if the node belongs to the domain (i.e, not a halo node) ---*/ + if (!geometry->nodes->GetDomain(iPoint)) continue; + + /*--- apply a dirichlet boundary condition as pressure is prescribed*/ + + LinSysRes.SetBlock_Zero(iPoint); + + nodes->SetSolution(iPoint, &pressureDeviation); + nodes->SetSolution_Old(iPoint,&pressureDeviation); + Jacobian.DeleteValsRowi(iPoint, 0); + } + END_SU2_OMP_FOR +} diff --git a/SU2_CFD/src/solvers/CSolver.cpp b/SU2_CFD/src/solvers/CSolver.cpp index 407c960dceb8..270293101081 100644 --- a/SU2_CFD/src/solvers/CSolver.cpp +++ b/SU2_CFD/src/solvers/CSolver.cpp @@ -1399,6 +1399,18 @@ void CSolver::GetCommCountAndType(const CConfig* config, COUNT_PER_POINT = nVar; MPI_TYPE = COMM_TYPE::DOUBLE; break; + case MPI_QUANTITIES::MOM_COEFF: + COUNT_PER_POINT = 1; + MPI_TYPE = COMM_TYPE::DOUBLE; + break; + case MPI_QUANTITIES::MOM_CORRECTION: + COUNT_PER_POINT = nDim; + MPI_TYPE = COMM_TYPE::DOUBLE; + break; + case MPI_QUANTITIES::HBYA_CORRECTION: + COUNT_PER_POINT = nDim; + MPI_TYPE = COMM_TYPE::DOUBLE; + break; default: SU2_MPI::Error("Unrecognized quantity for point-to-point MPI comms.", CURRENT_FUNCTION); @@ -1556,6 +1568,17 @@ void CSolver::InitiateComms(CGeometry *geometry, for (iVar = 0; iVar < nVar; iVar++) bufDSend[buf_offset+iVar] = base_nodes->GetSolution_time_n1(iPoint, iVar); break; + case MPI_QUANTITIES::MOM_COEFF: + bufDSend[buf_offset] = base_nodes->GetMomCoeff(iPoint); + break; + case MPI_QUANTITIES::MOM_CORRECTION: + for (iDim = 0; iDim < nDim; iDim++) + bufDSend[buf_offset+iDim] = base_nodes->GetMomentumCorrection(iPoint, iDim); + break; + case MPI_QUANTITIES::HBYA_CORRECTION: + for (iDim = 0; iDim < nDim; iDim++) + bufDSend[buf_offset+iDim] = base_nodes->GetHbyACorrection(iPoint, iDim); + break; default: SU2_MPI::Error("Unrecognized quantity for point-to-point MPI comms.", CURRENT_FUNCTION); @@ -1712,6 +1735,17 @@ void CSolver::CompleteComms(CGeometry *geometry, for (iVar = 0; iVar < nVar; iVar++) base_nodes->Set_Solution_time_n1(iPoint, iVar, bufDRecv[buf_offset+iVar]); break; + case MPI_QUANTITIES::MOM_COEFF: + base_nodes->SetMomCoeff(iPoint, bufDRecv[buf_offset]); + break; + case MPI_QUANTITIES::MOM_CORRECTION: + for (iDim = 0; iDim < nDim; iDim++) + base_nodes->SetMomentumCorrection(iPoint, iDim, bufDRecv[buf_offset+iDim]); + break; + case MPI_QUANTITIES::HBYA_CORRECTION: + for (iDim = 0; iDim < nDim; iDim++) + base_nodes->SetHbyACorrection(iPoint, iDim, bufDRecv[buf_offset+iDim]); + break; default: SU2_MPI::Error("Unrecognized quantity for point-to-point MPI comms.", CURRENT_FUNCTION); diff --git a/SU2_CFD/src/solvers/CSolverFactory.cpp b/SU2_CFD/src/solvers/CSolverFactory.cpp index ee798c1384b4..4a7977f6902a 100644 --- a/SU2_CFD/src/solvers/CSolverFactory.cpp +++ b/SU2_CFD/src/solvers/CSolverFactory.cpp @@ -31,6 +31,7 @@ #include "../../include/solvers/CIncEulerSolver.hpp" #include "../../include/solvers/CNSSolver.hpp" #include "../../include/solvers/CIncNSSolver.hpp" +#include "../../include/solvers/CPoissonSolver.hpp" #include "../../include/solvers/CNEMOEulerSolver.hpp" #include "../../include/solvers/CNEMONSSolver.hpp" #include "../../include/solvers/CTurbSASolver.hpp" @@ -69,6 +70,7 @@ CSolver** CSolverFactory::CreateSolverContainer(MAIN_SOLVER kindMainSolver, CCon case MAIN_SOLVER::INC_EULER: solver[FLOW_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::INC_EULER, solver, geometry, config, iMGLevel); solver[RAD_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::RADIATION, solver, geometry, config, iMGLevel); + solver[POISSON_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::POISSON, solver, geometry, config, iMGLevel); break; case MAIN_SOLVER::EULER: solver[FLOW_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::EULER, solver, geometry, config, iMGLevel); @@ -81,6 +83,7 @@ CSolver** CSolverFactory::CreateSolverContainer(MAIN_SOLVER kindMainSolver, CCon solver[HEAT_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::HEAT, solver, geometry, config, iMGLevel); solver[RAD_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::RADIATION, solver, geometry, config, iMGLevel); solver[SPECIES_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::SPECIES, solver, geometry, config, iMGLevel); + solver[POISSON_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::POISSON, solver, geometry, config, iMGLevel); break; case MAIN_SOLVER::NAVIER_STOKES: solver[FLOW_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::NAVIER_STOKES, solver, geometry, config, iMGLevel); @@ -103,6 +106,7 @@ CSolver** CSolverFactory::CreateSolverContainer(MAIN_SOLVER kindMainSolver, CCon solver[TURB_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::TURB, solver, geometry, config, iMGLevel); solver[TRANS_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::TRANSITION, solver, geometry, config, iMGLevel); solver[RAD_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::RADIATION, solver, geometry, config, iMGLevel); + solver[POISSON_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::POISSON, solver, geometry, config, iMGLevel); break; case MAIN_SOLVER::HEAT_EQUATION: solver[HEAT_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::HEAT, solver, geometry, config, iMGLevel); @@ -144,6 +148,7 @@ CSolver** CSolverFactory::CreateSolverContainer(MAIN_SOLVER kindMainSolver, CCon solver[ADJFLOW_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::DISC_ADJ_FLOW, solver, geometry, config, iMGLevel); solver[RAD_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::RADIATION, solver, geometry, config, iMGLevel); solver[ADJRAD_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::DISC_ADJ_RADIATION, solver, geometry, config, iMGLevel); + solver[POISSON_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::POISSON, solver, geometry, config, iMGLevel); break; case MAIN_SOLVER::DISC_ADJ_INC_NAVIER_STOKES: solver[FLOW_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::INC_NAVIER_STOKES, solver, geometry, config, iMGLevel); @@ -154,6 +159,7 @@ CSolver** CSolverFactory::CreateSolverContainer(MAIN_SOLVER kindMainSolver, CCon solver[ADJRAD_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::DISC_ADJ_RADIATION, solver, geometry, config, iMGLevel); solver[SPECIES_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::SPECIES, solver, geometry, config, iMGLevel); solver[ADJSPECIES_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::DISC_ADJ_SPECIES, solver, geometry, config, iMGLevel); + solver[POISSON_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::POISSON, solver, geometry, config, iMGLevel); break; case MAIN_SOLVER::DISC_ADJ_INC_RANS: solver[FLOW_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::INC_NAVIER_STOKES, solver, geometry, config, iMGLevel); @@ -166,6 +172,7 @@ CSolver** CSolverFactory::CreateSolverContainer(MAIN_SOLVER kindMainSolver, CCon solver[ADJTURB_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::DISC_ADJ_TURB, solver, geometry, config, iMGLevel); solver[RAD_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::RADIATION, solver, geometry, config, iMGLevel); solver[ADJRAD_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::DISC_ADJ_RADIATION, solver, geometry, config, iMGLevel); + solver[POISSON_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::POISSON, solver, geometry, config, iMGLevel); break; case MAIN_SOLVER::DISC_ADJ_HEAT: solver[HEAT_SOL] = CreateSubSolver(SUB_SOLVER_TYPE::HEAT, solver, geometry, config, iMGLevel); @@ -331,6 +338,12 @@ CSolver* CSolverFactory::CreateSubSolver(SUB_SOLVER_TYPE kindSolver, CSolver **s } metaData.integrationType = INTEGRATION_TYPE::DEFAULT; break; + case SUB_SOLVER_TYPE::POISSON: + if (config->GetKind_Incomp_System() == INCOMP_SYSTEM::PRESSURE_BASED) { + genericSolver = new CPoissonSolver(geometry, config, iMGLevel); + metaData.integrationType = INTEGRATION_TYPE::SINGLEGRID; + } + break; default: SU2_MPI::Error("No proper allocation found for requested sub solver", CURRENT_FUNCTION); break; diff --git a/SU2_CFD/src/variables/CFlowVariable.cpp b/SU2_CFD/src/variables/CFlowVariable.cpp index 7828b1afbb0a..c9f3524ba053 100644 --- a/SU2_CFD/src/variables/CFlowVariable.cpp +++ b/SU2_CFD/src/variables/CFlowVariable.cpp @@ -50,7 +50,7 @@ CFlowVariable::CFlowVariable(unsigned long npoint, unsigned long ndim, unsigned Primitive.resize(nPoint, nPrimVar) = su2double(0.0); - if (config->GetMUSCL_Flow() || config->GetViscous() || config->GetContinuous_Adjoint()) { + if (config->GetMUSCL_Flow() || config->GetViscous() || config->GetContinuous_Adjoint() || config->GetKind_Incomp_System() == INCOMP_SYSTEM::PRESSURE_BASED) { Gradient_Primitive.resize(nPoint, nPrimVarGrad, nDim, 0.0); } diff --git a/SU2_CFD/src/variables/CIncEulerVariable.cpp b/SU2_CFD/src/variables/CIncEulerVariable.cpp index 5274df766447..a2495980d5c0 100644 --- a/SU2_CFD/src/variables/CIncEulerVariable.cpp +++ b/SU2_CFD/src/variables/CIncEulerVariable.cpp @@ -71,6 +71,12 @@ CIncEulerVariable::CIncEulerVariable(su2double pressure, const su2double *veloci if (config->GetStreamwise_Periodic_Temperature()) Streamwise_Periodic_RecoveredTemperature.resize(nPoint) = su2double(0.0); } + + /*--- Allocate strong BC vector for pressure-based solver ---*/ + + if (config->GetKind_Incomp_System() == INCOMP_SYSTEM::PRESSURE_BASED) { + strongBC.resize(nPoint) = false; + } } bool CIncEulerVariable::SetPrimVar(unsigned long iPoint, CFluidModel *FluidModel) { diff --git a/SU2_CFD/src/variables/CPoissonVariable.cpp b/SU2_CFD/src/variables/CPoissonVariable.cpp new file mode 100644 index 000000000000..0ac42afb417b --- /dev/null +++ b/SU2_CFD/src/variables/CPoissonVariable.cpp @@ -0,0 +1,57 @@ +/*! + * \file CPoissonVariable.cpp + * \brief Definition of the variables for poisson equation problems. + * \author T. Aalbers + * \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 "../../include/variables/CPoissonVariable.hpp" + +CPoissonVariable::CPoissonVariable(su2double value, unsigned long npoint, unsigned long ndim, unsigned long nvar, CConfig *config) + : CScalarVariable(npoint, ndim, nvar, config) { + + /*--- Initialization ---*/ + + Solution = value; + Solution_Old = value; + + /*--- Allocate residual structures ---*/ + + Res_TruncError.resize(nPoint, nVar) = su2double(0.0); + + /*--- Only for residual smoothing (multigrid) ---*/ + + for (unsigned long iMesh = 0; iMesh <= config->GetnMGLevels(); iMesh++) { + if (config->GetMGOptions().MG_CorrecSmooth[iMesh] > 0) { + Residual_Sum.resize(nPoint, nVar); + Residual_Old.resize(nPoint, nVar); + break; + } + } + + /*--- Initialize momentum coefficient and HbyA ---*/ + MomCoeff.resize(nPoint) = su2double(0.0); + MomentumCorrection.resize(nPoint, nDim) = su2double(0.0); + HbyACorrection.resize(nPoint, nDim) = su2double(0.0); + +} diff --git a/TestCases/hybrid_regression.py b/TestCases/hybrid_regression.py index 166a46a368a5..0b1500802f39 100644 --- a/TestCases/hybrid_regression.py +++ b/TestCases/hybrid_regression.py @@ -350,6 +350,41 @@ def main(): inc_euler_naca0012.test_vals = [-5.858104, -4.937295, 0.519817, 0.008958] test_list.append(inc_euler_naca0012) + # NACA0012 Hydrofoil, pressure-based. Exercises the OpenMP path of the Poisson + # solver's boundary flux corrections (inlet, far-field, pressure outlet). + inc_euler_naca0012_pb = TestCase('inc_euler_naca0012_pb') + inc_euler_naca0012_pb.cfg_dir = "incomp_euler/naca0012" + inc_euler_naca0012_pb.cfg_file = "incomp_pb_NACA0012.cfg" + inc_euler_naca0012_pb.test_iter = 20 + inc_euler_naca0012_pb.test_vals = [-4.454818, -4.784246, 0.427448, 0.012083] + test_list.append(inc_euler_naca0012_pb) + + # Laminar cylinder, pressure-based. Viscous counterpart of the hydrofoil above, so the + # threaded momentum and Rhie-Chow stages run with wall markers present. + inc_lam_cylinder_pb = TestCase('inc_lam_cylinder_pb') + inc_lam_cylinder_pb.cfg_dir = "incomp_navierstokes/cylinder" + inc_lam_cylinder_pb.cfg_file = "incomp_pb_cylinder.cfg" + inc_lam_cylinder_pb.test_iter = 10 + inc_lam_cylinder_pb.test_vals = [-3.486075, -3.777632, 0.012229, 6.178719] + test_list.append(inc_lam_cylinder_pb) + + # Laminar sphere, pressure-based. The only 3D pressure-based case, and the one where the + # alpha_p reduction sums over a different set of Jacobian diagonals per thread. + inc_lam_sphere_pb = TestCase('inc_lam_sphere_pb') + inc_lam_sphere_pb.cfg_dir = "incomp_navierstokes/sphere" + inc_lam_sphere_pb.cfg_file = "pb_sphere.cfg" + inc_lam_sphere_pb.test_iter = 9 + inc_lam_sphere_pb.test_vals = [-6.092084, -2.305039, -2.479072, -2.548968, 0.191798, 170.793204, -6.134920] + test_list.append(inc_lam_sphere_pb) + + # Heated cylinder, pressure-based, coupled energy equation and variable density. + inc_poly_cylinder_pb = TestCase('inc_poly_cylinder_pb') + inc_poly_cylinder_pb.cfg_dir = "incomp_navierstokes/cylinder" + inc_poly_cylinder_pb.cfg_file = "pb_poly_cylinder.cfg" + inc_poly_cylinder_pb.test_iter = 20 + inc_poly_cylinder_pb.test_vals = [-13.483266, 0.541350, 0.005972, 17.020220, -8927.600000] + test_list.append(inc_poly_cylinder_pb) + # C-D nozzle with pressure inlet and mass flow outlet inc_nozzle = TestCase('inc_nozzle') inc_nozzle.cfg_dir = "incomp_euler/nozzle" diff --git a/TestCases/incomp_euler/naca0012/incomp_pb_NACA0012.cfg b/TestCases/incomp_euler/naca0012/incomp_pb_NACA0012.cfg new file mode 100644 index 000000000000..28a632767efc --- /dev/null +++ b/TestCases/incomp_euler/naca0012/incomp_pb_NACA0012.cfg @@ -0,0 +1,109 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: Incompressible flow hydrofoil 5 degrees % +% Author: Francisco Palacios % +% Institution: Stanford University % +% Date: 09/18/2011 % +% File Version 8.5.0 "Harrier" % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% +% +SOLVER= INC_EULER +KIND_INCOMP_SYSTEM= PRESSURE_BASED +MATH_PROBLEM= DIRECT +RESTART_SOL= NO + +% ---------------- INCOMPRESSIBLE FLOW CONDITION DEFINITION -------------------% +% +INC_DENSITY_INIT= 998.2 +INC_VELOCITY_INIT= ( 1.775, 0.0, 0.0 ) +INC_INLET_TYPE= VELOCITY_INLET +INC_INLET_DAMPING= 0.1 +INC_OUTLET_TYPE= PRESSURE_OUTLET +INC_OUTLET_DAMPING= 0.1 + +% ---------------------- REFERENCE VALUE DEFINITION ---------------------------% +% +REF_ORIGIN_MOMENT_X = 0.25 +REF_ORIGIN_MOMENT_Y = 0.00 +REF_ORIGIN_MOMENT_Z = 0.00 +REF_LENGTH= 1.0 +REF_AREA= 1.0 + +% ----------------------- BOUNDARY CONDITION DEFINITION -----------------------% +% +MARKER_EULER= ( airfoil, lower_wall, upper_wall ) +MARKER_INLET= ( inlet, 0.0, 1.775, 1.0, 0.0, 0.0 ) +MARKER_OUTLET= ( outlet, 0.0 ) +MARKER_PLOTTING= ( airfoil ) +MARKER_MONITORING= ( airfoil ) + +% ------------- COMMON PARAMETERS TO DEFINE THE NUMERICAL METHOD --------------% +% +NUM_METHOD_GRAD= GREEN_GAUSS +CFL_NUMBER= 10.0 +CFL_ADAPT= NO +CFL_ADAPT_PARAM= ( 1.5, 0.5, 1.0, 100.0 ) +RK_ALPHA_COEFF= ( 0.66667, 0.66667, 1.000000 ) +ITER= 9999 + +% ------------------------ LINEAR SOLVER DEFINITION ---------------------------% +% +LINEAR_SOLVER= FGMRES +LINEAR_SOLVER_PREC= ILU +LINEAR_SOLVER_ILU_FILL_IN= 0 +LINEAR_SOLVER_ERROR= 1E-6 +LINEAR_SOLVER_ITER= 25 + +% ----------------------- PRESSURE BASED PARAMETERS ---------------------------% +% +RELAXATION_FACTOR_PRESSURE= 1.0 +TRANSIENT_TERM_REMOVAL_FACTOR= 0.0 +KIND_PB_ITER= SIMPLE +PISO_CORRECTIONS = 2 +POISSON_LINEAR_SOLVER= FGMRES +POISSON_LINEAR_SOLVER_PREC= LU_SGS +POISSON_LINEAR_SOLVER_ERROR= 1E-6 +POISSON_LINEAR_SOLVER_ITER= 1000 + +% -------------------------- MULTIGRID PARAMETERS -----------------------------% +% +MGLEVEL= 0 +MGCYCLE= W_CYCLE +MG_PRE_SMOOTH= ( 4, 4, 4, 4 ) +MG_POST_SMOOTH= ( 4, 4, 4, 4 ) +MG_CORRECTION_SMOOTH= ( 1, 1, 1, 1 ) +MG_DAMP_RESTRICTION= 0.5 +MG_DAMP_PROLONGATION= 0.5 + +% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% +% +CONV_NUM_METHOD_FLOW= UDS +MUSCL_FLOW= YES +SLOPE_LIMITER_FLOW= NONE +VENKAT_LIMITER_COEFF= 0.0002 +JST_SENSOR_COEFF= ( 0.5, 0.04 ) +TIME_DISCRE_FLOW= EULER_IMPLICIT + +% --------------------------- CONVERGENCE PARAMETERS --------------------------% +% +CONV_RESIDUAL_MINVAL= -10 +CONV_STARTITER= 10 +CONV_CAUCHY_ELEMS= 50 +CONV_CAUCHY_EPS= 1E-6 + +% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% +% +MESH_FILENAME= mesh_NACA0012_5deg_6814.su2 +MESH_FORMAT= SU2 +SOLUTION_FILENAME= solution_flow +TABULAR_FORMAT= CSV +CONV_FILENAME= history_pb +RESTART_FILENAME= restart_flow +VOLUME_FILENAME= flow_pb +SURFACE_FILENAME= surface_flow_pb +OUTPUT_WRT_FREQ= 100 +SCREEN_OUTPUT= (INNER_ITER, RMS_PRESSURE, RMS_VELOCITY-X, RMS_VELOCITY-Y, LIFT, DRAG) diff --git a/TestCases/incomp_navierstokes/bend/pb_lam_bend.cfg b/TestCases/incomp_navierstokes/bend/pb_lam_bend.cfg new file mode 100644 index 000000000000..cdf71ade0fc7 --- /dev/null +++ b/TestCases/incomp_navierstokes/bend/pb_lam_bend.cfg @@ -0,0 +1,115 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: Ultra coarse GGNS grid with mixed sections, pressure-based % +% Author: Thomas D. Economon % +% Date: 2019.07.26 % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% +% +SOLVER= INC_NAVIER_STOKES +KIND_INCOMP_SYSTEM= PRESSURE_BASED +KIND_TURB_MODEL= NONE +% +% Mathematical problem (DIRECT, ADJOINT) +MATH_PROBLEM= DIRECT +RESTART_SOL= NO + +% ---------------- INCOMPRESSIBLE FLOW CONDITION DEFINITION -------------------% +% +INC_DENSITY_MODEL= CONSTANT +INC_ENERGY_EQUATION = NO +INC_DENSITY_INIT= 1.2886 +INC_VELOCITY_INIT= ( 0.1, 0.0, 0.0 ) +INC_TEMPERATURE_INIT= 288.15 +INC_NONDIM= INITIAL_VALUES +INC_DENSITY_REF= 1.0 +INC_VELOCITY_REF= 1.0 +INC_TEMPERATURE_REF = 1.0 +INC_INLET_TYPE= VELOCITY_INLET +INC_INLET_DAMPING= 0.1 +INC_OUTLET_TYPE= PRESSURE_OUTLET +INC_OUTLET_DAMPING= 0.1 + +% --------------------------- VISCOSITY MODEL ---------------------------------% +% +VISCOSITY_MODEL= CONSTANT_VISCOSITY +MU_CONSTANT= 1.716E-5 + +% ---------------------- REFERENCE VALUE DEFINITION ---------------------------% +% +REF_ORIGIN_MOMENT_X = 0.25 +REF_ORIGIN_MOMENT_Y = 0.00 +REF_ORIGIN_MOMENT_Z = 0.00 +REF_LENGTH= 1.0 +REF_AREA= 1.0 + +% -------------------- BOUNDARY CONDITION DEFINITION --------------------------% +% +MARKER_HEATFLUX= ( WALL1, 0.0, WALL2, 0.0 ) +MARKER_INLET= ( INLET, 288.15, 0.1, 1.0, 0.0, 0.0 ) +MARKER_OUTLET= ( OUTLET, 0.0 ) +MARKER_SYM= ( SYMMETRY ) +MARKER_PLOTTING = ( INLET, OUTLET, WALL1, WALL2, SYMMETRY ) +MARKER_MONITORING = ( INLET, OUTLET, WALL1, WALL2, SYMMETRY ) + +% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% +% +NUM_METHOD_GRAD= GREEN_GAUSS +CFL_NUMBER= 1.0 +RK_ALPHA_COEFF= ( 0.66667, 0.66667, 1.000000 ) + +% ---------------- PRESSURE-BASED INCOMPRESSIBLE SOLVER DEFINITION ------------% +% +RELAXATION_FACTOR_PRESSURE= 1.0 +TRANSIENT_TERM_REMOVAL_FACTOR= 0.0 +KIND_PB_ITER= SIMPLE +PISO_CORRECTIONS = 2 +POISSON_LINEAR_SOLVER= FGMRES +POISSON_LINEAR_SOLVER_PREC= ILU +POISSON_LINEAR_SOLVER_ERROR= 1E-6 +POISSON_LINEAR_SOLVER_ITER= 1000 + +% ------------------------ LINEAR SOLVER DEFINITION ---------------------------% +% +LINEAR_SOLVER= FGMRES +LINEAR_SOLVER_PREC= ILU +LINEAR_SOLVER_ERROR= 1E-15 +LINEAR_SOLVER_ITER= 5 + +% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% +% +CONV_NUM_METHOD_FLOW= UDS +MUSCL_FLOW= NO +SLOPE_LIMITER_FLOW= NONE +TIME_DISCRE_FLOW= EULER_IMPLICIT + +% --------------------------- CONVERGENCE PARAMETERS --------------------------% +% +ITER= 11 +CONV_RESIDUAL_MINVAL= -8 +CONV_STARTITER= 10 +CONV_CAUCHY_ELEMS= 100 +CONV_CAUCHY_EPS= 1E-10 + +% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% +% +MESH_FILENAME= mesh_bend_coarse.cgns +MESH_FORMAT= CGNS +MESH_OUT_FILENAME= mesh_out +SOLUTION_FILENAME= solution_flow +SOLUTION_ADJ_FILENAME= solution_adj +TABULAR_FORMAT= CSV +CONV_FILENAME= history_pb +RESTART_FILENAME= restart_flow +RESTART_ADJ_FILENAME= restart_adj +VOLUME_FILENAME= flow_pb +VOLUME_ADJ_FILENAME= adjoint +VALUE_OBJFUNC_FILENAME= of_eval +GRAD_OBJFUNC_FILENAME= of_grad +SURFACE_FILENAME= surface_flow_pb +SURFACE_ADJ_FILENAME= surface_adjoint +OUTPUT_WRT_FREQ= 1000 +SCREEN_OUTPUT= (INNER_ITER, RMS_PRESSURE, RMS_VELOCITY-X, LIFT, DRAG) diff --git a/TestCases/incomp_navierstokes/cylinder/incomp_pb_cylinder.cfg b/TestCases/incomp_navierstokes/cylinder/incomp_pb_cylinder.cfg new file mode 100644 index 000000000000..b06b23eb1edd --- /dev/null +++ b/TestCases/incomp_navierstokes/cylinder/incomp_pb_cylinder.cfg @@ -0,0 +1,102 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: Steady incompressible laminar flow around a cylinder % +% Author: Francisco Palacios % +% Institution: Stanford University % +% Date: 2012.03.14 % +% File Version 8.5.0 "Harrier" % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% +% +SOLVER= INC_NAVIER_STOKES +KIND_INCOMP_SYSTEM= PRESSURE_BASED +KIND_TURB_MODEL= NONE +MATH_PROBLEM= DIRECT +RESTART_SOL= NO + +% ---------------- INCOMPRESSIBLE FLOW CONDITION DEFINITION -------------------% +% +INC_DENSITY_INIT= 998.2 +INC_VELOCITY_INIT= ( 0.000008, 0.0, 0.0 ) + +% --------------------------- VISCOSITY MODEL ---------------------------------% +% +VISCOSITY_MODEL= CONSTANT_VISCOSITY +MU_CONSTANT= 0.798E-3 + +% ---------------------- REFERENCE VALUE DEFINITION ---------------------------% +% +REF_ORIGIN_MOMENT_X = 0.25 +REF_ORIGIN_MOMENT_Y = 0.00 +REF_ORIGIN_MOMENT_Z = 0.00 +REF_LENGTH= 1.0 +REF_AREA= 1.0 + +% -------------------- BOUNDARY CONDITION DEFINITION --------------------------% +% +MARKER_HEATFLUX= ( cylinder, 0.0 ) +MARKER_FAR= ( farfield ) +MARKER_PLOTTING= ( cylinder ) +MARKER_MONITORING= ( cylinder ) + +% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% +% +NUM_METHOD_GRAD= GREEN_GAUSS +CFL_NUMBER= 50.0 +CFL_ADAPT= NO +CFL_ADAPT_PARAM= ( 1.5, 0.5, 1.0, 100.0 ) +RK_ALPHA_COEFF= ( 0.66667, 0.66667, 1.000000 ) +ITER= 5000 +VENKAT_LIMITER_COEFF= 0.01 + +% ----------------------- PRESSURE BASED PARAMETERS ---------------------------% +% +RELAXATION_FACTOR_PRESSURE= 1.0 +TRANSIENT_TERM_REMOVAL_FACTOR= 0.0 +KIND_PB_ITER= SIMPLE +PISO_CORRECTIONS = 4 +POISSON_LINEAR_SOLVER= FGMRES +POISSON_LINEAR_SOLVER_PREC= LU_SGS +POISSON_LINEAR_SOLVER_ERROR= 1E-6 +POISSON_LINEAR_SOLVER_ITER= 1000 + +% -------------------------- MULTIGRID PARAMETERS -----------------------------% +% +MGLEVEL= 0 +MGCYCLE= V_CYCLE +MG_PRE_SMOOTH= ( 4, 4, 4, 4 ) +MG_POST_SMOOTH= ( 4, 4, 4, 4 ) +MG_CORRECTION_SMOOTH= ( 1, 1, 1, 1 ) +MG_DAMP_RESTRICTION= 0.5 +MG_DAMP_PROLONGATION= 0.5 + +% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% +% +CONV_NUM_METHOD_FLOW= UDS +MUSCL_FLOW= NO +SLOPE_LIMITER_FLOW= NONE +JST_SENSOR_COEFF= ( 0.5, 0.04 ) +TIME_DISCRE_FLOW= EULER_IMPLICIT + +% --------------------------- CONVERGENCE PARAMETERS --------------------------% +% +CONV_RESIDUAL_MINVAL= -10 +CONV_STARTITER= 10 +CONV_CAUCHY_ELEMS= 100 +CONV_CAUCHY_EPS= 1E-6 + +% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% +% +MESH_FILENAME= mesh_cylinder_lam.su2 +MESH_FORMAT= SU2 +SOLUTION_FILENAME= solution_flow +TABULAR_FORMAT= CSV +CONV_FILENAME= history_pb +RESTART_FILENAME= restart_flow +VOLUME_FILENAME= flow_pb +SURFACE_FILENAME= surface_flow +OUTPUT_WRT_FREQ= 10 +SCREEN_OUTPUT= (INNER_ITER, RMS_PRESSURE, RMS_VELOCITY-X, RMS_VELOCITY-Y, LIFT, DRAG) diff --git a/TestCases/incomp_navierstokes/cylinder/pb_poly_cylinder.cfg b/TestCases/incomp_navierstokes/cylinder/pb_poly_cylinder.cfg new file mode 100644 index 000000000000..fa61649158d2 --- /dev/null +++ b/TestCases/incomp_navierstokes/cylinder/pb_poly_cylinder.cfg @@ -0,0 +1,123 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: Steady incompressible laminar flow around a heated % +% cylinder with a polynomial fluid model, pressure-based, % +% coupled energy equation and variable density. % +% File Version 8.5.0 "Harrier" % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% +% +SOLVER= INC_NAVIER_STOKES +KIND_INCOMP_SYSTEM= PRESSURE_BASED +KIND_TURB_MODEL= NONE +MATH_PROBLEM= DIRECT +RESTART_SOL= NO + +% ---------------- INCOMPRESSIBLE FLOW CONDITION DEFINITION -------------------% +% +INC_DENSITY_MODEL= VARIABLE +INC_ENERGY_EQUATION = YES +INC_DENSITY_INIT= 0.000210322 +INC_VELOCITY_INIT= ( 3.40297, 0.0, 0.0 ) +INC_TEMPERATURE_INIT= 288.15 +INC_NONDIM= DIMENSIONAL + +% ---- IDEAL GAS, POLYTROPIC, VAN DER WAALS AND PENG ROBINSON CONSTANTS -------% +% +FLUID_MODEL= INC_IDEAL_GAS_POLY +SPECIFIC_HEAT_CP= 1004.703 +MOLECULAR_WEIGHT= 28.96 +CP_POLYCOEFFS= ( 1004.703, 0.1, 0.0, 0.0, 0.0) + +% --------------------------- VISCOSITY MODEL ---------------------------------% +% +VISCOSITY_MODEL= POLYNOMIAL_VISCOSITY +MU_CONSTANT= 1.7893e-05 +MU_POLYCOEFFS= (1.7893e-05, 1e-8, 0.0, 0.0, 0.0) + +% --------------------------- THERMAL CONDUCTIVITY MODEL ----------------------% +% +CONDUCTIVITY_MODEL= POLYNOMIAL_CONDUCTIVITY +THERMAL_CONDUCTIVITY_CONSTANT= 0.0257 +PRANDTL_LAM= 0.72 +KT_POLYCOEFFS= (0.0257, 1e-5, 0.0, 0.0, 0.0) +TURBULENT_CONDUCTIVITY_MODEL= CONSTANT_PRANDTL_TURB +PRANDTL_TURB= 0.90 + +% ---------------------- REFERENCE VALUE DEFINITION ---------------------------% +% +REF_ORIGIN_MOMENT_X = 0.25 +REF_ORIGIN_MOMENT_Y = 0.00 +REF_ORIGIN_MOMENT_Z = 0.00 +REF_LENGTH= 1.0 +REF_AREA= 1.0 + +% -------------------- BOUNDARY CONDITION DEFINITION --------------------------% +% +MARKER_ISOTHERMAL= ( cylinder, 1000.0 ) +MARKER_FAR= ( farfield ) +MARKER_PLOTTING= ( cylinder ) +MARKER_MONITORING= ( cylinder ) + +% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% +% +NUM_METHOD_GRAD= GREEN_GAUSS +CFL_NUMBER= 1000.0 +CFL_ADAPT= NO +CFL_ADAPT_PARAM= ( 1.5, 0.5, 10.0, 10000.0 ) +ITER= 20 +VENKAT_LIMITER_COEFF= 0.05 + +% ----------------------- PRESSURE BASED PARAMETERS ---------------------------% +% +RELAXATION_FACTOR_PRESSURE= 1.0 +TRANSIENT_TERM_REMOVAL_FACTOR= 0.0 +KIND_PB_ITER= SIMPLE +PISO_CORRECTIONS = 4 +POISSON_LINEAR_SOLVER= FGMRES +POISSON_LINEAR_SOLVER_PREC= LU_SGS +POISSON_LINEAR_SOLVER_ERROR= 1E-6 +POISSON_LINEAR_SOLVER_ITER= 200 + +% -------------------------- MULTIGRID PARAMETERS -----------------------------% +% +MGLEVEL= 0 +MGCYCLE= V_CYCLE + +% ------------------------ LINEAR SOLVER DEFINITION ---------------------------% +% +LINEAR_SOLVER= FGMRES +LINEAR_SOLVER_PREC= ILU +LINEAR_SOLVER_ILU_FILL_IN= 0 +LINEAR_SOLVER_ERROR= 1E-15 +LINEAR_SOLVER_ITER= 10 + +% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% +% +CONV_NUM_METHOD_FLOW= UDS +MUSCL_FLOW= YES +SLOPE_LIMITER_FLOW= NONE +TIME_DISCRE_FLOW= EULER_IMPLICIT + +% --------------------------- CONVERGENCE PARAMETERS --------------------------% +% +CONV_RESIDUAL_MINVAL= -14 +CONV_STARTITER= 10 +CONV_CAUCHY_ELEMS= 100 +CONV_CAUCHY_EPS= 1E-6 + +% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% +% +MESH_FILENAME= mesh_cylinder_lam.su2 +MESH_FORMAT= SU2 +SOLUTION_FILENAME= solution_flow +TABULAR_FORMAT= CSV +CONV_FILENAME= history_pb +RESTART_FILENAME= restart_flow +VOLUME_FILENAME= flow_pb +SURFACE_FILENAME= surface_flow +OUTPUT_WRT_FREQ= 10 +SCREEN_OUTPUT= (INNER_ITER, RMS_PRESSURE, RMS_ENTHALPY, LIFT, DRAG, TOTAL_HEATFLUX) diff --git a/TestCases/incomp_navierstokes/sphere/pb_sphere.cfg b/TestCases/incomp_navierstokes/sphere/pb_sphere.cfg new file mode 100644 index 000000000000..37ac1d0b5181 --- /dev/null +++ b/TestCases/incomp_navierstokes/sphere/pb_sphere.cfg @@ -0,0 +1,108 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: laminar flow around a sphere, pressure-based % +% Author: Nijso Beishuizen % +% Institution: Technische Universiteit Eindhoven % +% Date: 2024.05.05 % +% File Version 8.5.0 "Harrier" % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% +% +SOLVER= INC_NAVIER_STOKES +KIND_INCOMP_SYSTEM= PRESSURE_BASED +KIND_TURB_MODEL= NONE +RESTART_SOL= NO +INC_NONDIM= DIMENSIONAL + +% ---------------- INCOMPRESSIBLE FLOW CONDITION DEFINITION -------------------% +% Re = rho*V*D/mu = 1*1*2.0/2.0 = 1.0 +INC_DENSITY_MODEL= CONSTANT +INC_ENERGY_EQUATION= NO +INC_DENSITY_INIT= 1.0 + +INC_VELOCITY_INIT= (1.0, 0.0, 0.0 ) +INC_VELOCITY_REF = 1 +INC_DENSITY_REF = 1.0 + +% --------------------------- VISCOSITY MODEL ---------------------------------% +% +VISCOSITY_MODEL= CONSTANT_VISCOSITY +MU_CONSTANT= 2.0 +% ---------------------- REFERENCE VALUE DEFINITION ---------------------------% +% +REF_ORIGIN_MOMENT_X = 0.25 +REF_ORIGIN_MOMENT_Y = 0.00 +REF_ORIGIN_MOMENT_Z = 0.00 +% sphere is 1/40 of a complete sphere +% area = pi*r^2 = 3.14159 +% 1/40 slice = 0.07854 +REF_AREA= 0.07854 + +% -------------------- BOUNDARY CONDITION DEFINITION --------------------------% +% +MARKER_HEATFLUX= ( wall, 0.0 ) +MARKER_SYM= ( symmetry_left, symmetry_right ) +MARKER_FAR= ( farfield ) +MARKER_PLOTTING= ( wall ) +MARKER_MONITORING= ( wall ) + +% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% +% +NUM_METHOD_GRAD= GREEN_GAUSS +CFL_NUMBER= 10.0 +CFL_ADAPT= NO +ITER= 10 + +% ---------------- PRESSURE-BASED INCOMPRESSIBLE SOLVER DEFINITION ------------% +% +RELAXATION_FACTOR_PRESSURE= 1.0 +TRANSIENT_TERM_REMOVAL_FACTOR= 0.0 +KIND_PB_ITER= SIMPLE +PISO_CORRECTIONS = 2 +POISSON_LINEAR_SOLVER= FGMRES +POISSON_LINEAR_SOLVER_PREC= ILU +POISSON_LINEAR_SOLVER_ERROR= 1E-6 +POISSON_LINEAR_SOLVER_ITER= 1000 + +% ------------------------ LINEAR SOLVER DEFINITION ---------------------------% +% +LINEAR_SOLVER= FGMRES +LINEAR_SOLVER_PREC= ILU +LINEAR_SOLVER_ERROR= 1E-6 +LINEAR_SOLVER_ITER= 25 + +% -------------------------- MULTIGRID PARAMETERS -----------------------------% +% +MGLEVEL= 0 + +% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% +% +CONV_NUM_METHOD_FLOW= UDS +MUSCL_FLOW= NO +SLOPE_LIMITER_FLOW= NONE +TIME_DISCRE_FLOW= EULER_IMPLICIT + +% --------------------------- CONVERGENCE PARAMETERS --------------------------% +% +CONV_RESIDUAL_MINVAL= -10 +CONV_STARTITER= 10 +CONV_CAUCHY_ELEMS= 100 +CONV_CAUCHY_EPS= 1E-6 + +% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% +% +MESH_FILENAME= slice_2syms.su2 +MESH_FORMAT= SU2 +SOLUTION_FILENAME= solution_flow +TABULAR_FORMAT= CSV +CONV_FILENAME= history_pb +RESTART_FILENAME= restart_flow +VOLUME_FILENAME= flow_pb +SURFACE_FILENAME= surface_flow_pb +OUTPUT_WRT_FREQ= 100 +WRT_VOLUME_OVERWRITE= YES +SCREEN_OUTPUT= ( INNER_ITER, RMS_PRESSURE, RMS_VELOCITY-X, RMS_VELOCITY-Y, RMS_VELOCITY-Z, LIFT, DRAG, LINSOL_RESIDUAL) +VOLUME_OUTPUT= ( SOLUTION,PRIMITIVE,RESIDUAL,MULTIGRID, RANK ) diff --git a/TestCases/incomp_navierstokes/sphere/pb_sphere_urf.cfg b/TestCases/incomp_navierstokes/sphere/pb_sphere_urf.cfg new file mode 100644 index 000000000000..1fd49ab44b05 --- /dev/null +++ b/TestCases/incomp_navierstokes/sphere/pb_sphere_urf.cfg @@ -0,0 +1,109 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: laminar flow around a sphere, pressure-based, % +% automatic relaxation factors (3D coverage for F2) % +% Author: Nijso Beishuizen % +% Institution: Technische Universiteit Eindhoven % +% Date: 2024.05.05 % +% File Version 8.5.0 "Harrier" % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% +% +SOLVER= INC_NAVIER_STOKES +KIND_INCOMP_SYSTEM= PRESSURE_BASED +KIND_TURB_MODEL= NONE +RESTART_SOL= NO +INC_NONDIM= DIMENSIONAL + +% ---------------- INCOMPRESSIBLE FLOW CONDITION DEFINITION -------------------% +% Re = rho*V*D/mu = 1*1*2.0/2.0 = 1.0 +INC_DENSITY_MODEL= CONSTANT +INC_ENERGY_EQUATION= NO +INC_DENSITY_INIT= 1.0 + +INC_VELOCITY_INIT= (1.0, 0.0, 0.0 ) +INC_VELOCITY_REF = 1 +INC_DENSITY_REF = 1.0 + +% --------------------------- VISCOSITY MODEL ---------------------------------% +% +VISCOSITY_MODEL= CONSTANT_VISCOSITY +MU_CONSTANT= 2.0 +% ---------------------- REFERENCE VALUE DEFINITION ---------------------------% +% +REF_ORIGIN_MOMENT_X = 0.25 +REF_ORIGIN_MOMENT_Y = 0.00 +REF_ORIGIN_MOMENT_Z = 0.00 +% sphere is 1/40 of a complete sphere +% area = pi*r^2 = 3.14159 +% 1/40 slice = 0.07854 +REF_AREA= 0.07854 + +% -------------------- BOUNDARY CONDITION DEFINITION --------------------------% +% +MARKER_HEATFLUX= ( wall, 0.0 ) +MARKER_SYM= ( symmetry_left, symmetry_right ) +MARKER_FAR= ( farfield ) +MARKER_PLOTTING= ( wall ) +MARKER_MONITORING= ( wall ) + +% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% +% +NUM_METHOD_GRAD= GREEN_GAUSS +CFL_NUMBER= 10.0 +CFL_ADAPT= NO +ITER= 10 + +% ---------------- PRESSURE-BASED INCOMPRESSIBLE SOLVER DEFINITION ------------% +% +USE_AUTOMATIC_RELAXATION_FACTORS= YES +TRANSIENT_TERM_REMOVAL_FACTOR= 0.0 +KIND_PB_ITER= SIMPLE +PISO_CORRECTIONS = 2 +POISSON_LINEAR_SOLVER= FGMRES +POISSON_LINEAR_SOLVER_PREC= ILU +POISSON_LINEAR_SOLVER_ERROR= 1E-6 +POISSON_LINEAR_SOLVER_ITER= 1000 + +% ------------------------ LINEAR SOLVER DEFINITION ---------------------------% +% +LINEAR_SOLVER= FGMRES +LINEAR_SOLVER_PREC= ILU +LINEAR_SOLVER_ERROR= 1E-6 +LINEAR_SOLVER_ITER= 25 + +% -------------------------- MULTIGRID PARAMETERS -----------------------------% +% +MGLEVEL= 0 + +% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% +% +CONV_NUM_METHOD_FLOW= UDS +MUSCL_FLOW= NO +SLOPE_LIMITER_FLOW= NONE +TIME_DISCRE_FLOW= EULER_IMPLICIT + +% --------------------------- CONVERGENCE PARAMETERS --------------------------% +% +CONV_RESIDUAL_MINVAL= -10 +CONV_STARTITER= 10 +CONV_CAUCHY_ELEMS= 100 +CONV_CAUCHY_EPS= 1E-6 + +% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% +% +MESH_FILENAME= slice_2syms.su2 +MESH_FORMAT= SU2 +SOLUTION_FILENAME= solution_flow +TABULAR_FORMAT= CSV +CONV_FILENAME= history_pb +RESTART_FILENAME= restart_flow +VOLUME_FILENAME= flow_pb +SURFACE_FILENAME= surface_flow_pb +OUTPUT_WRT_FREQ= 100 +WRT_VOLUME_OVERWRITE= YES +SCREEN_OUTPUT= ( INNER_ITER, RMS_PRESSURE, RMS_VELOCITY-X, RMS_VELOCITY-Y, RMS_VELOCITY-Z, LIFT, DRAG, LINSOL_RESIDUAL) +VOLUME_OUTPUT= ( SOLUTION,PRIMITIVE,RESIDUAL,MULTIGRID, RANK ) diff --git a/TestCases/incomp_rans/rough_flatplate/pb_rough_flatplate_incomp.cfg b/TestCases/incomp_rans/rough_flatplate/pb_rough_flatplate_incomp.cfg new file mode 100644 index 000000000000..5ebe84e3d53e --- /dev/null +++ b/TestCases/incomp_rans/rough_flatplate/pb_rough_flatplate_incomp.cfg @@ -0,0 +1,130 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: Turbulent flow over rough flat plate with zero % +% pressure gradient % +% Author: Akshay Koodly % +% Date: 2020.07.07 % +% File Version 8.5.0 "Harrier" % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% ------------- DIRECT, ADJOINT, AND LINEARIZED PROBLEM DEFINITION ------------% +% +SOLVER= INC_RANS +KIND_INCOMP_SYSTEM= PRESSURE_BASED +KIND_TURB_MODEL= SA +MATH_PROBLEM= DIRECT +RESTART_SOL= NO + +% ---------------- INCOMPRESSIBLE FLOW CONDITION DEFINITION -------------------% +% +INC_DENSITY_MODEL= CONSTANT +INC_ENERGY_EQUATION = NO +INC_DENSITY_INIT= 1.32905 +INC_VELOCITY_INIT= ( 69.4448, 0.0, 0.0 ) +INC_TEMPERATURE_INIT= 300.0 +INC_NONDIM= INITIAL_VALUES +INC_DENSITY_REF= 1.0 +INC_VELOCITY_REF= 1.0 +INC_TEMPERATURE_REF = 1.0 +INC_INLET_TYPE= VELOCITY_INLET + +% --------------------------- VISCOSITY MODEL ---------------------------------% +% +VISCOSITY_MODEL= CONSTANT_VISCOSITY +MU_CONSTANT= 1.84592e-05 +MU_REF= 1.716E-5 +MU_T_REF= 273.15 +SUTHERLAND_CONSTANT= 110.4 + +% ---------------------- REFERENCE VALUE DEFINITION ---------------------------% +% +REF_ORIGIN_MOMENT_X = 0.25 +REF_ORIGIN_MOMENT_Y = 0.00 +REF_ORIGIN_MOMENT_Z = 0.00 +REF_LENGTH= 1.0 +REF_AREA= 2.0 + +% -------------------- BOUNDARY CONDITION DEFINITION --------------------------% +% +MARKER_HEATFLUX= ( wall, 0.0 ) +WALL_ROUGHNESS = (wall, 0.000246) +MARKER_INLET= ( inlet, 300.0, 69.4448, 1.0, 0.0, 0.0 ) +MARKER_OUTLET= ( outlet, 0.0, farfield, 0.0 ) +INC_OUTLET_TYPE= PRESSURE_OUTLET,PRESSURE_OUTLET +MARKER_SYM= ( symmetry ) +MARKER_PLOTTING= ( wall ) +MARKER_MONITORING= ( wall ) + +% ------------- COMMON PARAMETERS DEFINING THE NUMERICAL METHOD ---------------% +% +NUM_METHOD_GRAD= GREEN_GAUSS +CFL_NUMBER= 10.0 +CFL_ADAPT= NO +CFL_ADAPT_PARAM= ( 1.5, 0.5, 1.1, 100.0 ) +RK_ALPHA_COEFF= ( 0.66667, 0.66667, 1.000000 ) +ITER= 1000 + +% ----------------------- PRESSURE BASED PARAMETERS ---------------------------% +% +RELAXATION_FACTOR_PRESSURE= 1.0 +TRANSIENT_TERM_REMOVAL_FACTOR= 0.0 +KIND_PB_ITER= SIMPLE +PISO_CORRECTIONS = 2 +POISSON_LINEAR_SOLVER= FGMRES +POISSON_LINEAR_SOLVER_PREC= ILU +POISSON_LINEAR_SOLVER_ERROR= 1E-6 +POISSON_LINEAR_SOLVER_ITER= 1000 + +% ------------------------ LINEAR SOLVER DEFINITION ---------------------------% +% +LINEAR_SOLVER= FGMRES +LINEAR_SOLVER_PREC= ILU +LINEAR_SOLVER_ILU_FILL_IN= 0 +LINEAR_SOLVER_ERROR= 1E-12 +LINEAR_SOLVER_ITER= 20 + +% ----------------------- SLOPE LIMITER DEFINITION ----------------------------% +% +VENKAT_LIMITER_COEFF= 0.1 +ADJ_SHARP_LIMITER_COEFF= 3.0 +REF_SHARP_EDGES= 3.0 +SENS_REMOVE_SHARP= NO + +% -------------------- FLOW NUMERICAL METHOD DEFINITION -----------------------% +% +CONV_NUM_METHOD_FLOW= UDS +MUSCL_FLOW= YES +SLOPE_LIMITER_FLOW= NONE +JST_SENSOR_COEFF= ( 0.5, 0.02 ) +TIME_DISCRE_FLOW= EULER_IMPLICIT + +% -------------------- TURBULENT NUMERICAL METHOD DEFINITION ------------------% +% +CONV_NUM_METHOD_TURB= SCALAR_UPWIND +MUSCL_TURB= NO +SLOPE_LIMITER_TURB= VENKATAKRISHNAN +TIME_DISCRE_TURB= EULER_IMPLICIT + +% --------------------------- CONVERGENCE PARAMETERS --------------------------% +% +CONV_FIELD= RMS_VELOCITY-X +CONV_RESIDUAL_MINVAL= -14 +CONV_STARTITER= 10 + +% ------------------------- INPUT/OUTPUT INFORMATION --------------------------% +% +MESH_FILENAME= mesh_flatplate_turb_137x97.su2 +MESH_FORMAT= SU2 +MESH_OUT_FILENAME= mesh_out +SOLUTION_FILENAME= restart_flow_pb +TABULAR_FORMAT= CSV +OUTPUT_FILES= RESTART, PARAVIEW, SURFACE_PARAVIEW +CONV_FILENAME= history +RESTART_FILENAME= restart_flow_pb +VOLUME_FILENAME= flow_pb +SURFACE_FILENAME= surface_flow_pb +OUTPUT_WRT_FREQ= 100, 50, 50 +SCREEN_OUTPUT= (INNER_ITER, RMS_VELOCITY-X, RMS_NU_TILDE, LIFT,DRAG) +WRT_FORCES_BREAKDOWN= YES diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index ff9b8c667061..44815db4a8b2 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -650,6 +650,14 @@ def main(): inc_euler_naca0012.test_vals = [-6.067964, -5.125607, 0.525745, 0.008772] test_list.append(inc_euler_naca0012) + # NACA0012 Hydrofoil + inc_euler_naca0012_pb = TestCase('inc_euler_naca0012_pb') + inc_euler_naca0012_pb.cfg_dir = "incomp_euler/naca0012" + inc_euler_naca0012_pb.cfg_file = "incomp_pb_NACA0012.cfg" + inc_euler_naca0012_pb.test_iter = 20 + inc_euler_naca0012_pb.test_vals = [-4.454818, -4.784247, 0.427448, 0.012083] + test_list.append(inc_euler_naca0012_pb) + # C-D nozzle with pressure inlet and mass flow outlet inc_nozzle = TestCase('inc_nozzle') inc_nozzle.cfg_dir = "incomp_euler/nozzle" @@ -678,6 +686,25 @@ def main(): inc_lam_cylinder.test_vals = [-4.156113, -3.553508, -0.024563, 5.105605] test_list.append(inc_lam_cylinder) + # Laminar cylinder, pressure-based + inc_lam_cylinder_pb = TestCase('inc_lam_cylinder_pb') + inc_lam_cylinder_pb.cfg_dir = "incomp_navierstokes/cylinder" + inc_lam_cylinder_pb.cfg_file = "incomp_pb_cylinder.cfg" + inc_lam_cylinder_pb.test_iter = 10 + inc_lam_cylinder_pb.test_vals = [-3.486113, -3.777688, 0.012054, 6.178573] + test_list.append(inc_lam_cylinder_pb) + + # Laminar heated cylinder with polynomial fluid model, pressure-based, coupled energy + # equation and variable density. Convergence is genuine but slow (needs ~40k iterations + # for rms[h] to reach a low residual); this only checks a short trajectory guard, matching + # the pattern used for other hard-to-converge cases in this suite. + inc_poly_cylinder_pb = TestCase('inc_poly_cylinder_pb') + inc_poly_cylinder_pb.cfg_dir = "incomp_navierstokes/cylinder" + inc_poly_cylinder_pb.cfg_file = "pb_poly_cylinder.cfg" + inc_poly_cylinder_pb.test_iter = 20 + inc_poly_cylinder_pb.test_vals = [-13.483272, 0.541350, 0.005972, 17.020220, -8927.600000] + test_list.append(inc_poly_cylinder_pb) + # Laminar sphere, Re=1. Last column: Cd=24/Re inc_lam_sphere = TestCase('inc_lam_sphere') inc_lam_sphere.cfg_dir = "incomp_navierstokes/sphere" @@ -686,6 +713,24 @@ def main(): inc_lam_sphere.test_vals = [-7.600533, -8.244915, -8.361301, -9.325293, 0.121003, 25.782687, -1.881890] test_list.append(inc_lam_sphere) + # Laminar sphere, Re=1, pressure-based. Only 3D pressure-based case in the regression suite. + inc_lam_sphere_pb = TestCase('inc_lam_sphere_pb') + inc_lam_sphere_pb.cfg_dir = "incomp_navierstokes/sphere" + inc_lam_sphere_pb.cfg_file = "pb_sphere.cfg" + inc_lam_sphere_pb.test_iter = 9 + inc_lam_sphere_pb.test_vals = [-6.092084, -2.305040, -2.479072, -2.548968, 0.191798, 170.793203, -6.000632] + test_list.append(inc_lam_sphere_pb) + + # Laminar sphere, Re=1, pressure-based, automatic relaxation factors. The only case + # in the suite that exercises USE_AUTOMATIC_RELAXATION_FACTORS, and 3D since the alpha_p + # bug this guards against is invisible in 2D (it sums the wrong set of Jacobian diagonals). + inc_lam_sphere_pb_urf = TestCase('inc_lam_sphere_pb_urf') + inc_lam_sphere_pb_urf.cfg_dir = "incomp_navierstokes/sphere" + inc_lam_sphere_pb_urf.cfg_file = "pb_sphere_urf.cfg" + inc_lam_sphere_pb_urf.test_iter = 9 + inc_lam_sphere_pb_urf.test_vals = [-4.747391, -2.239844, -2.380839, -1.685462, 0.237593, 206.027273, -6.331275] + test_list.append(inc_lam_sphere_pb_urf) + # Buoyancy-driven cavity inc_buoyancy = TestCase('inc_buoyancy') inc_buoyancy.cfg_dir = "incomp_navierstokes/buoyancy_cavity" @@ -710,6 +755,14 @@ def main(): inc_lam_bend.test_vals = [-3.585943, -3.096592, -0.022111, 1.064110] test_list.append(inc_lam_bend) + # X-coarse laminar bend as a mixed element CGNS test, pressure-based + inc_lam_bend_pb = TestCase('inc_lam_bend_pb') + inc_lam_bend_pb.cfg_dir = "incomp_navierstokes/bend" + inc_lam_bend_pb.cfg_file = "pb_lam_bend.cfg" + inc_lam_bend_pb.test_iter = 10 + inc_lam_bend_pb.test_vals = [-3.824468, -3.345335, -0.012351, 1.685090] + test_list.append(inc_lam_bend_pb) + # 3D laminar channnel with 1 cell in flow direction, streamwise periodic sp_pipeSlice_3d_dp_hf_tp = TestCase('sp_pipeSlice_3d_dp_hf_tp') sp_pipeSlice_3d_dp_hf_tp.cfg_dir = "incomp_navierstokes/streamwise_periodic/pipeSlice_3d" @@ -746,6 +799,14 @@ def main(): inc_turb_naca0012_sst_sust.test_vals = [-7.169837, 0.332730, -0.000001, 0.312131] test_list.append(inc_turb_naca0012_sst_sust) + # Flat plate, pressure-based + inc_flatplate_pb = TestCase('inc_flatplate_pb') + inc_flatplate_pb.cfg_dir = "incomp_rans/rough_flatplate" + inc_flatplate_pb.cfg_file = "pb_rough_flatplate_incomp.cfg" + inc_flatplate_pb.test_iter = 10 + inc_flatplate_pb.test_vals = [-4.063342, -9.884401, 0.000010, 0.228472] + test_list.append(inc_flatplate_pb) + #################### ### DG-FEM Euler ### #################### diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index 331fe709f0ec..5bcbf01c5cf0 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -415,6 +415,14 @@ def main(): inc_euler_naca0012.test_vals = [-5.988713, -5.020635, 0.522968, 0.008854] test_list.append(inc_euler_naca0012) + # NACA0012 Hydrofoil, pressure-based + inc_euler_naca0012_pb = TestCase('inc_euler_naca0012_pb') + inc_euler_naca0012_pb.cfg_dir = "incomp_euler/naca0012" + inc_euler_naca0012_pb.cfg_file = "incomp_pb_NACA0012.cfg" + inc_euler_naca0012_pb.test_iter = 20 + inc_euler_naca0012_pb.test_vals = [-4.454818, -4.784246, 0.427448, 0.012083] + test_list.append(inc_euler_naca0012_pb) + # C-D nozzle with pressure inlet and mass flow outlet inc_nozzle = TestCase('inc_nozzle') inc_nozzle.cfg_dir = "incomp_euler/nozzle" @@ -442,6 +450,15 @@ def main(): inc_lam_cylinder.test_vals = [-4.161215, -3.573002, 0.019888, 4.945923] test_list.append(inc_lam_cylinder) + # Laminar cylinder, pressure-based + inc_lam_cylinder_pb = TestCase('inc_lam_cylinder_pb') + inc_lam_cylinder_pb.cfg_dir = "incomp_navierstokes/cylinder" + inc_lam_cylinder_pb.cfg_file = "incomp_pb_cylinder.cfg" + inc_lam_cylinder_pb.test_iter = 10 + inc_lam_cylinder_pb.test_vals = [-3.486100, -3.777681, 0.012003, 6.178586] + test_list.append(inc_lam_cylinder_pb) + + # Buoyancy-driven cavity inc_buoyancy = TestCase('inc_buoyancy') inc_buoyancy.cfg_dir = "incomp_navierstokes/buoyancy_cavity" @@ -466,6 +483,7 @@ def main(): inc_lam_bend.test_vals = [-3.639664, -3.218039, -0.016067, 1.090645] test_list.append(inc_lam_bend) + ############################ ### Incompressible RANS ### ############################ @@ -493,6 +511,14 @@ def main(): inc_turb_naca0012_sst_sust.test_vals = [-7.169704, 0.332779, 0.000021, 0.312114] test_list.append(inc_turb_naca0012_sst_sust) + # Flat plate, pressure-based + inc_flatplate_pb = TestCase('inc_flatplate_pb') + inc_flatplate_pb.cfg_dir = "incomp_rans/rough_flatplate" + inc_flatplate_pb.cfg_file = "pb_rough_flatplate_incomp.cfg" + inc_flatplate_pb.test_iter = 10 + inc_flatplate_pb.test_vals = [-4.063342, -9.884401, 0.000010, 0.228472] + test_list.append(inc_flatplate_pb) + # FLAT PLATE, WALL FUNCTIONS, INCOMPRESSIBLE SST inc_turb_wallfunction_flatplate_sst = TestCase('inc_turb_sst_wallfunction_flatplate') inc_turb_wallfunction_flatplate_sst.cfg_dir = "wallfunctions/flatplate/incompressible_SST" diff --git a/TestCases/tutorials.py b/TestCases/tutorials.py index 9ab5ef5a6a71..64cb681163b8 100644 --- a/TestCases/tutorials.py +++ b/TestCases/tutorials.py @@ -128,6 +128,18 @@ def main(): von_karman_cylinder.test_vals = [-7.845765, -7.681042, -8.736704, -0.002581, 1.423652] test_list.append(von_karman_cylinder) + # Lid Driven Cavity Flow (Re=400), pressure-based. Disabled: the config and mesh live in + # su2code/Tutorials#86, which had not landed as of this commit - ../Tutorials/incompressible_flow/ + # Inc_Lid_Driven_Cavity/incomp_pb_liddrivencavity.cfg does not exist yet, and enabling this + # entry would fail CI outright rather than fail the regression check. Re-enable once that PR + # merges (test_vals recorded below were measured against the config in that PR). + # lid_driven_cavity = TestCase('lid_driven_cavity') + # lid_driven_cavity.cfg_dir = "../Tutorials/incompressible_flow/Inc_Lid_Driven_Cavity" + # lid_driven_cavity.cfg_file = "incomp_pb_liddrivencavity.cfg" + # lid_driven_cavity.test_iter = 2 + # lid_driven_cavity.test_vals = [-5.798956, -4.393539, -5.070494] + # test_list.append(lid_driven_cavity) + ### Species Transport diff --git a/config_template.cfg b/config_template.cfg index 34fc40cf1ab7..f2aa561f9619 100644 --- a/config_template.cfg +++ b/config_template.cfg @@ -347,7 +347,42 @@ INC_OUTLET_DAMPING= 0.1 BULK_MODULUS= 1.42E5 % Epsilon^2 multipier in Beta calculation for incompressible preconditioner. BETA_FACTOR= 4.1 + +% -------------- PRESSURE-BASED INCOMPRESSIBLE SOLVER DEFINITION ---------------% +% +% Incompressible solver. Options are: DENSITY_BASED (default), PRESSURE_BASED +KIND_INCOMP_SYSTEM= DENSITY_BASED +% +% Pressure based method. Options are: SIMPLE (default), SIMPLEC +KIND_PB_ITER= SIMPLE +% +% The relaxation factor for the pressure correction (default = 1.0) +RELAXATION_FACTOR_PRESSURE= 1.0 +% +% Decide if automatic relaxation factors for pressure and momentum should be used +% or not. If set to NO, the value from RELAXATION_FACTOR_PRESSURE is used. default NO +USE_AUTOMATIC_RELAXATION_FACTORS= NO +% +% Heuristic parameter influencing the effect of the pseudo time derivative in the +% momentum coefficients for the pressure correction equation (float [0,1], default=0.0) +TRANSIENT_TERM_REMOVAL_FACTOR= 0.0 % +% Number of pressure corrections in the SIMPLE algorithm. >1 leads to the PISO +% algorithm. (default = 1 = SIMPLE) +PISO_CORRECTIONS= 1 +% +% Linear solver for poisson equation (same options as LINEAR_SOLVER) +POISSON_LINEAR_SOLVER= FGMRES +% +% Preconditioner of the poisson linear solver (same options as LINEAR_SOLVER_PREC) +POISSON_LINEAR_SOLVER_PREC= ILU +% +% Minimum error of the linear solver for poisson eq. +POISSON_LINEAR_SOLVER_ERROR= 1E-6 +% +% Max. Number of iterations for the poisson linear solver +POISSON_LINEAR_SOLVER_ITER= 10 + % ----------------------------- SOLID ZONE HEAT VARIABLES-----------------------% % % Thermal conductivity used for heat equation From 56df7491c9027fd28c83d8b09617c2852ccd078e Mon Sep 17 00:00:00 2001 From: bellonarts Date: Mon, 14 Sep 2026 22:15:43 -0400 Subject: [PATCH 54/61] Consistent NEMO viscous Jacobian: distinct left block and subtractive edge assembly (#2885) ## Proposed Changes The gradient-corrected NEMO viscous routine returns the right Jacobian block twice, and the solver assembles viscous Jacobians with additive signs despite subtracting the flux at node i and adding it at node j. This patch returns the distinct left and right blocks and uses `UpdateBlocksSub` for the matching assembly signs. The residual equations are unchanged. For the controlled constant density and viscosity momentum test, Newtonian stress gives raw flux derivatives of -8/15 at i and +8/15 at j. The opposite signs inside numerics come from the velocity difference; they do not include the residual assembly sign. The assembled blocks must therefore be `(-Ji, -Jj, +Ji, +Jj)`. The general viscous Jacobian remains a TSL approximation. ## Tests - The numerics unit test checks the independently derived values and central differences of the returned momentum flux at both endpoints, using two perturbation sizes. The unused gradient reconstruction helper is removed. - The solver assembly test checks the signs and placement of the supplied blocks, with explicit finite checks. It is an integration test, not an independent proof of the full Jacobian. - The existing `ion_gy` regression now runs through iteration 99 using `test_iter`, the existing mesh and the same stored restart. Its residual threshold is lowered to prevent the previous stop at iteration 10. The duplicate case and config are removed, and the output restart has a separate filename so the input seed stays unchanged. - The installed unit-test container now gets the Mutation++ library path required by the new NEMO coverage. In the earlier captured two-rank comparison, the final log10 energy residual was 8.78 with the original code and 4.54 with the patch. The patched trajectory stayed bounded but still reported nonphysical states. This is regression stability evidence, not convergence or physical validation. The existing 0.01 tolerance is retained. In the [focused Linux run](https://github.com/babybluechips/SU2/actions/runs/34157605477), both MPI and NoMPI builds passed the full normal unit suite: 36 cases and 73,848 assertions each, including MLPCpp. `super_cat` passed, and both fresh two-rank `ion_gy` repeats passed at iteration 99 with identical printed rows and unchanged input seeds. Native checks also confirmed that restoring either original bug makes its relevant test fail. The cone references were refreshed from identical results in the fork and [upstream runs](https://github.com/su2code/SU2/actions/runs/34156557441), keeping the existing 1e-5 tolerance. The original regression comparisons failed only those stored cone references. The focused solver runs used `cd8a39d`; the final commit `50e3343` changes only the two reference arrays and the unit container's library path. Compiled source, unit tests and solver configs are identical. [Final upstream CI](https://github.com/su2code/SU2/actions/runs/34159628295) now passes the installed normal unit suite (36 cases, 73,848 assertions), AD/DD unit suites, and all 157 serial regression tests. The parallel cone, `super_cat`, and `ion_gy` at iteration 99 also pass; the rest of the parallel suite is still running. Both cone comparisons have zero printed difference. The overlapping `visc_cone`, `super_cat`, and `ion_gy` expectations must be refreshed if another NEMO PR merges first. ## Related Work Possibly relevant to #2717. The inviscid case in #2026 is outside this patch's scope. Earlier matched Mach 7.95 cone runs showed the original code aborting at iteration 1,406 and this patch completing 3,000 bounded iterations; those runs did not establish convergence. ## PR Checklist - [x] I am submitting my contribution to the develop branch. - [x] My contribution is commented and consistent with SU2 style. - [x] I have added a test case that demonstrates my contribution. - [x] Documentation is not applicable; no user-facing configuration option or API is added. - [x] Pre-commit formatting checks pass. - [x] The focused Linux checks and final upstream results listed above are verified. --------- Co-authored-by: Claude Fable 5.1 --- .github/workflows/regression.yml | 1 + SU2_CFD/src/numerics/NEMO/NEMO_diffusion.cpp | 2 +- SU2_CFD/src/solvers/CNEMONSSolver.cpp | 2 +- .../visc_cylinder/cyl_ion_gy.cfg | 4 +- TestCases/parallel_regression.py | 13 +- TestCases/serial_regression.py | 4 +- UnitTests/SU2_CFD/nemo_viscous_assembly.cpp | 254 ++++++++++++++++++ .../SU2_CFD/numerics/CNumerics_tests.cpp | 154 +++++++++++ UnitTests/meson.build | 1 + 9 files changed, 423 insertions(+), 12 deletions(-) create mode 100644 UnitTests/SU2_CFD/nemo_viscous_assembly.cpp diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml index c4e3b652b069..76eb60ebc8f8 100644 --- a/.github/workflows/regression.yml +++ b/.github/workflows/regression.yml @@ -462,6 +462,7 @@ jobs: uses: docker://ghcr.io/su2code/su2/test-su2:260405-0054 env: OMPI_MCA_osc: pt2pt + LD_LIBRARY_PATH: /github/workspace/install/lib with: entrypoint: install/bin/${{matrix.testdriver}} - name: Post Cleanup diff --git a/SU2_CFD/src/numerics/NEMO/NEMO_diffusion.cpp b/SU2_CFD/src/numerics/NEMO/NEMO_diffusion.cpp index 2553fa2915d3..705c1b253adb 100644 --- a/SU2_CFD/src/numerics/NEMO/NEMO_diffusion.cpp +++ b/SU2_CFD/src/numerics/NEMO/NEMO_diffusion.cpp @@ -359,6 +359,6 @@ CNumerics::ResidualType<> CAvgGradCorrected_NEMO::ComputeResidual(const CConfig } - return ResidualType<>(Flux, Jacobian_j, Jacobian_j); + return ResidualType<>(Flux, Jacobian_i, Jacobian_j); } diff --git a/SU2_CFD/src/solvers/CNEMONSSolver.cpp b/SU2_CFD/src/solvers/CNEMONSSolver.cpp index bae5591d9460..e29934e88032 100644 --- a/SU2_CFD/src/solvers/CNEMONSSolver.cpp +++ b/SU2_CFD/src/solvers/CNEMONSSolver.cpp @@ -238,7 +238,7 @@ void CNEMONSSolver::Viscous_Residual(CGeometry *geometry, LinSysRes.SubtractBlock(iPoint, residual); LinSysRes.AddBlock(jPoint, residual); if (implicit) { - Jacobian.UpdateBlocks(iEdge, iPoint, jPoint, residual.jacobian_i, residual.jacobian_j); + Jacobian.UpdateBlocksSub(iEdge, iPoint, jPoint, residual.jacobian_i, residual.jacobian_j); } } } //iEdge diff --git a/TestCases/nonequilibrium/visc_cylinder/cyl_ion_gy.cfg b/TestCases/nonequilibrium/visc_cylinder/cyl_ion_gy.cfg index 2f51ad7f73fc..f7ae13236606 100644 --- a/TestCases/nonequilibrium/visc_cylinder/cyl_ion_gy.cfg +++ b/TestCases/nonequilibrium/visc_cylinder/cyl_ion_gy.cfg @@ -57,7 +57,7 @@ TIME_DISCRE_FLOW= EULER_IMPLICIT % --------------------------- CONVERGENCE PARAMETERS --------------------------% % -CONV_RESIDUAL_MINVAL= -10 +CONV_RESIDUAL_MINVAL= -30 CONV_STARTITER= 10 % ------------------------- INPUT/OUTPUT INFORMATION --------------------------% @@ -65,7 +65,7 @@ CONV_STARTITER= 10 MESH_FILENAME= visc_cyl.su2 MESH_FORMAT= SU2 SOLUTION_FILENAME= restart_flow_gy -RESTART_FILENAME= restart_flow_gy +RESTART_FILENAME= restart_flow_gy_out TABULAR_FORMAT= TECPLOT CONV_FILENAME= convergence VOLUME_FILENAME= soln_volume diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index 44815db4a8b2..68a7a54d35a3 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -192,8 +192,8 @@ def main(): visc_cone.cfg_dir = "nonequilibrium/visc_wedge" visc_cone.cfg_file = "axi_visccone.cfg" visc_cone.test_iter = 10 - visc_cone.test_vals = [-5.215234, -5.739371, -20.559852, -20.509281, -20.408911, 1.262701, -3.205457, -0.015696, 0.093205, 32637.000000] - visc_cone.test_vals_aarch64 = [-5.222270, -5.746525, -20.560286, -20.510152, -20.409101, 1.255758, -3.208382, -0.016014, 0.093462, 32619.000000] + visc_cone.test_vals = [-5.298530, -5.823015, -20.404788, -20.318330, -20.378801, 1.067489, -3.250987, -0.015488, 0.095136, 24939.000000] + visc_cone.test_vals_aarch64 = [-5.298530, -5.823015, -20.404788, -20.318329, -20.378801, 1.067489, -3.250987, -0.015488, 0.095136, 24939.000000] test_list.append(visc_cone) # Viscous single wedge with Mutation++ @@ -209,7 +209,7 @@ def main(): super_cat.cfg_dir = "nonequilibrium/visc_wedge" super_cat.cfg_file = "super_cat.cfg" super_cat.test_iter = 10 - super_cat.test_vals = [-5.232595, -5.757889, -20.641415, -20.640623, -20.541670, 1.246866, -3.205258, -0.028372, 0.250647, 32440.000000] + super_cat.test_vals = [-5.309257, -5.834048, -21.098287, -21.157699, -21.180688, 1.056908, -3.252349, -0.028039, 0.252019, 24878.000000] test_list.append(super_cat) # Viscous single wedge - partially catalytic walls @@ -220,12 +220,13 @@ def main(): partial_cat.test_vals = [-5.210302, -5.735065, -20.880448, -20.825971, -23.475263, 1.806201, -2.813952, -0.078400, 0.495606, 29020.000000] test_list.append(partial_cat) - # Viscous cylinder, ionization, Gupta-Yos + # Viscous cylinder, ionization, Gupta-Yos, marched 100 iterations from its restart. ion_gy = TestCase('ion_gy') ion_gy.cfg_dir = "nonequilibrium/visc_cylinder" ion_gy.cfg_file = "cyl_ion_gy.cfg" - ion_gy.test_iter = 10 - ion_gy.test_vals = [-11.629873, -4.165562, -4.702662, -4.950351, -5.146155, -4.993878, -6.893332, 5.990109, 5.990004, -0.014849, 0.000000, 90090.000000] + ion_gy.test_iter = 99 + ion_gy.test_vals = [-11.662039, -4.203178, -4.868257, -5.462497, -5.232052, -4.960881, -6.951391, 4.541901, 4.552855, -0.014861, 0.000001, 90357.000000] + ion_gy.tol = 0.01 test_list.append(ion_gy) ########################## diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index 5bcbf01c5cf0..fedb4302209c 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -75,8 +75,8 @@ def main(): visc_cone.cfg_dir = "nonequilibrium/visc_wedge" visc_cone.cfg_file = "axi_visccone.cfg" visc_cone.test_iter = 10 - visc_cone.test_vals = [-5.215230, -5.739367, -20.560781, -20.516922, -20.406516, 1.262782, -3.205476, -0.015696, 0.093206, 32641] - visc_cone.test_vals_aarch64 = [-5.215250, -5.739384, -20.560917, -20.517096, -20.406630, 1.262772, -3.205492, -0.015695, 0.093205, 32641.000000] + visc_cone.test_vals = [-5.298550, -5.823031, -20.330692, -20.368713, -20.310533, 1.067466, -3.251001, -0.015489, 0.095136, 24939.000000] + visc_cone.test_vals_aarch64 = [-5.298550, -5.823031, -20.330692, -20.368713, -20.310533, 1.067466, -3.251001, -0.015489, 0.095136, 24939.000000] test_list.append(visc_cone) ######################### diff --git a/UnitTests/SU2_CFD/nemo_viscous_assembly.cpp b/UnitTests/SU2_CFD/nemo_viscous_assembly.cpp new file mode 100644 index 000000000000..93da083f6357 --- /dev/null +++ b/UnitTests/SU2_CFD/nemo_viscous_assembly.cpp @@ -0,0 +1,254 @@ +/*! + * \file nemo_viscous_assembly.cpp + * \brief Unit test for the edge assembly of the NEMO viscous residual and Jacobian. + * \author J. Bellon + * \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 +#include +#include +#include +#include +#include "../../Common/include/geometry/CPhysicalGeometry.hpp" +#include "../../SU2_CFD/include/numerics/NEMO/NEMO_diffusion.hpp" +#include "../../SU2_CFD/include/solvers/CNEMONSSolver.hpp" +#include "../../SU2_CFD/include/variables/CNEMONSVariable.hpp" + +namespace { + +/*! + * \brief A NEMO Navier-Stokes solver on a small box mesh, built the same way + * the gradient tests build their geometry, with a non-uniform velocity field + * so that the viscous residual is not trivially zero. + */ +struct NEMOViscousAssemblyCase { + const std::string configOptions = + "SOLVER= NEMO_NAVIER_STOKES\n" + "GAS_MODEL= AIR-5\n" + "GAS_COMPOSITION= (0.77, 0.23, 0.0, 0.0, 0.0)\n" + "FLUID_MODEL= SU2_NONEQ\n" + "MESH_FORMAT= BOX\n" + "MESH_BOX_SIZE= 4,4,4\n" + "MESH_BOX_LENGTH= 1,1,1\n" + "MESH_BOX_OFFSET= 0,0,0\n" + "INIT_OPTION= TD_CONDITIONS\n" + "MACH_NUMBER= 0.3\n" + "FREESTREAM_PRESSURE= 1000.0\n" + "FREESTREAM_TEMPERATURE= 300.0\n" + "FREESTREAM_TEMPERATURE_VE= 300.0\n" + "REYNOLDS_NUMBER= 1000\n" + "KIND_TURB_MODEL= NONE\n" + "MARKER_FAR= (x_minus, x_plus, y_minus, y_plus, z_minus, z_plus)\n" + "NUM_METHOD_GRAD= GREEN_GAUSS\n" + "CONV_NUM_METHOD_FLOW= AUSM\n" + "MUSCL_FLOW= NO\n" + "TIME_DISCRE_FLOW= EULER_IMPLICIT\n"; + + std::unique_ptr config; + std::unique_ptr geometry; + CNEMONSSolver* solver{nullptr}; + + NEMOViscousAssemblyCase() { + auto origBuf = cout.rdbuf(); + cout.rdbuf(nullptr); + + stringstream ss(configOptions); + config = std::unique_ptr(new CConfig(ss, SU2_COMPONENT::SU2_CFD, false)); + + { + auto aux_geometry = std::unique_ptr(new CPhysicalGeometry(config.get(), 0, 1)); + geometry = std::unique_ptr(new CPhysicalGeometry(aux_geometry.get(), config.get())); + } + geometry->SetSendReceive(config.get()); + geometry->SetBoundaries(config.get()); + geometry->SetPoint_Connectivity(); + geometry->SetElement_Connectivity(); + geometry->SetBoundVolume(); + geometry->Check_IntElem_Orientation(config.get()); + geometry->Check_BoundElem_Orientation(config.get()); + geometry->SetEdges(); + geometry->SetVertex(config.get()); + geometry->SetControlVolume(config.get(), ALLOCATE); + geometry->SetBoundControlVolume(config.get(), ALLOCATE); + geometry->FindNormal_Neighbor(config.get()); + geometry->SetGlobal_to_Local_Point(); + geometry->PreprocessP2PComms(geometry.get(), config.get()); + + solver = new CNEMONSSolver(geometry.get(), config.get(), MESH_0); + + /*--- Scale the momentum with position so that the velocity gradients, + * and with them the viscous fluxes, are non-zero. ---*/ + const auto nDim = geometry->GetnDim(); + const auto nSpecies = config->GetnSpecies(); + auto* nodes = solver->GetNodes(); + for (auto iPoint = 0ul; iPoint < geometry->GetnPoint(); ++iPoint) { + const auto* coord = geometry->nodes->GetCoord(iPoint); + const su2double scale = 1.0 + 0.2 * coord[0] + 0.1 * coord[1]; + for (unsigned short iDim = 0; iDim < nDim; ++iDim) { + const auto iVar = nSpecies + iDim; + nodes->SetSolution(iPoint, iVar, scale * nodes->GetSolution(iPoint, iVar)); + } + } + + cout.rdbuf(origBuf); + } + + ~NEMOViscousAssemblyCase() { delete solver; } +}; + +} // namespace + +TEST_CASE("NEMO viscous solver assembles edge fluxes and Jacobian blocks with the correct signs and placement", + "[NEMO][viscous][Jacobian]") { + NEMOViscousAssemblyCase testCase; + auto* config = testCase.config.get(); + auto* geometry = testCase.geometry.get(); + auto* solver = testCase.solver; + + const auto nDim = geometry->GetnDim(); + const auto nVar = solver->GetnVar(); + const auto nPrimVar = solver->GetnPrimVar(); + const auto nPrimVarGrad = solver->GetnPrimVarGrad(); + const auto nPoint = geometry->GetnPoint(); + + /*--- Primitive variables, transport properties and gradients. ---*/ + CSolver* solver_container[MAX_SOLS] = {nullptr}; + solver_container[FLOW_SOL] = solver; + { + auto origBuf = cout.rdbuf(); + cout.rdbuf(nullptr); + solver->Preprocessing(geometry, solver_container, config, MESH_0, 0, RUNTIME_FLOW_SYS, false); + cout.rdbuf(origBuf); + } + solver->LinSysRes.SetValZero(); + solver->Jacobian.SetValZero(); + + /*--- Assemble the viscous residual and Jacobian with the solver. The + * solver's override is private, so dispatch through the public base + * interface, exactly as the integration classes do. ---*/ + CAvgGradCorrected_NEMO numerics(nDim, nVar, nPrimVar, nPrimVarGrad, config); + CNumerics* numerics_container[MAX_TERMS] = {nullptr}; + numerics_container[VISC_TERM] = &numerics; + CSolver& base = *solver; + base.Viscous_Residual(geometry, solver_container, numerics_container, config, MESH_0, 0); + + /*--- Replay every edge with a separate instance of the same numerics. + * This checks solver integration: each flux is subtracted at i and added + * at j, and the supplied i/j Jacobian blocks must have matching signs and + * positions. The reference reuses production numerics; it is not an + * independent derivative check of the full viscous Jacobian. ---*/ + CAvgGradCorrected_NEMO replay(nDim, nVar, nPrimVar, nPrimVarGrad, config); + auto* nodes = dynamic_cast(solver->GetNodes()); + REQUIRE(nodes != nullptr); + + std::vector residual_ref(nPoint * nVar, 0.0); + std::vector diagonal_ref(nPoint * nVar * nVar, 0.0); + su2double offdiag_error = 0.0, jacobian_scale = 0.0; + + for (auto iEdge = 0ul; iEdge < geometry->GetnEdge(); ++iEdge) { + const auto iPoint = geometry->edges->GetNode(iEdge, 0); + const auto jPoint = geometry->edges->GetNode(iEdge, 1); + + replay.SetCoord(geometry->nodes->GetCoord(iPoint), geometry->nodes->GetCoord(jPoint)); + replay.SetNormal(geometry->edges->GetNormal(iEdge)); + replay.SetConservative(nodes->GetSolution(iPoint), nodes->GetSolution(jPoint)); + replay.SetPrimitive(nodes->GetPrimitive(iPoint), nodes->GetPrimitive(jPoint)); + replay.SetPrimVarGradient(nodes->GetGradient_Primitive(iPoint), nodes->GetGradient_Primitive(jPoint)); + replay.SetdPdU(nodes->GetdPdU(iPoint), nodes->GetdPdU(jPoint)); + replay.SetdTdU(nodes->GetdTdU(iPoint), nodes->GetdTdU(jPoint)); + replay.SetdTvedU(nodes->GetdTvedU(iPoint), nodes->GetdTvedU(jPoint)); + replay.SetEve(nodes->GetEve(iPoint), nodes->GetEve(jPoint)); + replay.SetCvve(nodes->GetCvve(iPoint), nodes->GetCvve(jPoint)); + replay.SetDiffusionCoeff(nodes->GetDiffusionCoeff(iPoint), nodes->GetDiffusionCoeff(jPoint)); + replay.SetLaminarViscosity(nodes->GetLaminarViscosity(iPoint), nodes->GetLaminarViscosity(jPoint)); + replay.SetEddyViscosity(nodes->GetEddyViscosity(iPoint), nodes->GetEddyViscosity(jPoint)); + replay.SetThermalConductivity(nodes->GetThermalConductivity(iPoint), nodes->GetThermalConductivity(jPoint)); + replay.SetThermalConductivity_ve(nodes->GetThermalConductivity_ve(iPoint), + nodes->GetThermalConductivity_ve(jPoint)); + + const auto edge = replay.ComputeResidual(config); + + const auto* block_ij = solver->Jacobian.GetBlock(iPoint, jPoint); + const auto* block_ji = solver->Jacobian.GetBlock(jPoint, iPoint); + REQUIRE(block_ij != nullptr); + REQUIRE(block_ji != nullptr); + + for (unsigned short iVar = 0; iVar < nVar; ++iVar) { + REQUIRE(std::isfinite(SU2_TYPE::GetValue(edge.residual[iVar]))); + residual_ref[iPoint * nVar + iVar] -= edge.residual[iVar]; + residual_ref[jPoint * nVar + iVar] += edge.residual[iVar]; + + for (unsigned short jVar = 0; jVar < nVar; ++jVar) { + const su2double dFdUi = edge.jacobian_i[iVar][jVar]; + const su2double dFdUj = edge.jacobian_j[iVar][jVar]; + REQUIRE(std::isfinite(SU2_TYPE::GetValue(dFdUi))); + REQUIRE(std::isfinite(SU2_TYPE::GetValue(dFdUj))); + jacobian_scale = std::max(jacobian_scale, std::max(std::fabs(dFdUi), std::fabs(dFdUj))); + + /*--- Diagonal blocks collect every edge of a point. ---*/ + diagonal_ref[(iPoint * nVar + iVar) * nVar + jVar] -= dFdUi; + diagonal_ref[(jPoint * nVar + iVar) * nVar + jVar] += dFdUj; + + /*--- Off-diagonal blocks belong to this edge alone. ---*/ + const su2double assembled_ij = block_ij[iVar * nVar + jVar]; + const su2double assembled_ji = block_ji[iVar * nVar + jVar]; + REQUIRE(std::isfinite(SU2_TYPE::GetValue(assembled_ij))); + REQUIRE(std::isfinite(SU2_TYPE::GetValue(assembled_ji))); + offdiag_error = std::max(offdiag_error, std::fabs(assembled_ij - (-dFdUj))); + offdiag_error = std::max(offdiag_error, std::fabs(assembled_ji - (+dFdUi))); + } + } + } + + su2double diagonal_error = 0.0, residual_error = 0.0, residual_scale = 0.0; + for (auto iPoint = 0ul; iPoint < nPoint; ++iPoint) { + const auto* block_ii = solver->Jacobian.GetBlock(iPoint, iPoint); + const auto* assembled_residual = solver->LinSysRes.GetBlock(iPoint); + REQUIRE(block_ii != nullptr); + for (unsigned short iVar = 0; iVar < nVar; ++iVar) { + REQUIRE(std::isfinite(SU2_TYPE::GetValue(residual_ref[iPoint * nVar + iVar]))); + REQUIRE(std::isfinite(SU2_TYPE::GetValue(assembled_residual[iVar]))); + residual_scale = std::max(residual_scale, std::fabs(residual_ref[iPoint * nVar + iVar])); + residual_error = + std::max(residual_error, std::fabs(assembled_residual[iVar] - residual_ref[iPoint * nVar + iVar])); + for (unsigned short jVar = 0; jVar < nVar; ++jVar) { + const su2double assembled = block_ii[iVar * nVar + jVar]; + REQUIRE(std::isfinite(SU2_TYPE::GetValue(assembled))); + REQUIRE(std::isfinite(SU2_TYPE::GetValue(diagonal_ref[(iPoint * nVar + iVar) * nVar + jVar]))); + diagonal_error = + std::max(diagonal_error, std::fabs(assembled - diagonal_ref[(iPoint * nVar + iVar) * nVar + jVar])); + } + } + } + + /*--- The reference must be non-trivial, otherwise the sign is untested. ---*/ + REQUIRE(jacobian_scale > 0.0); + REQUIRE(residual_scale > 0.0); + + const su2double tolerance = 1.0e-10; + CHECK(residual_error <= tolerance * residual_scale); + CHECK(offdiag_error <= tolerance * jacobian_scale); + CHECK(diagonal_error <= tolerance * jacobian_scale); +} diff --git a/UnitTests/SU2_CFD/numerics/CNumerics_tests.cpp b/UnitTests/SU2_CFD/numerics/CNumerics_tests.cpp index a4cee41e8d20..bea3da327e01 100644 --- a/UnitTests/SU2_CFD/numerics/CNumerics_tests.cpp +++ b/UnitTests/SU2_CFD/numerics/CNumerics_tests.cpp @@ -26,8 +26,12 @@ */ #include "catch.hpp" +#include +#include #include +#include #include "../../../SU2_CFD/include/numerics/CNumerics.hpp" +#include "../../../SU2_CFD/include/numerics/NEMO/NEMO_diffusion.hpp" TEST_CASE("NTS blending has a minimum of 0.05", "[Upwind/central blending]") { std::stringstream config_options; @@ -89,3 +93,153 @@ TEST_CASE("QCR2000 corrects only the turbulent stress", "[QCR]") { for (size_t jDim = 0; jDim < nDim; jDim++) REQUIRE(tau_total[iDim][jDim] == Approx(tau_lam[iDim][jDim] + tau_turb[iDim][jDim]).margin(1e-12)); } + +namespace { + +struct NEMOViscousFixture { + static constexpr unsigned short nDim = 2; + static constexpr unsigned short nSpecies = 5; + static constexpr unsigned short nVar = nSpecies + nDim + 2; + static constexpr unsigned short nPrimVar = nSpecies + nDim + 10; + static constexpr unsigned short nPrimVarGrad = nSpecies + nDim + 8; + static constexpr unsigned short T_INDEX = nSpecies; + static constexpr unsigned short TVE_INDEX = nSpecies + 1; + static constexpr unsigned short VEL_INDEX = nSpecies + 2; + static constexpr unsigned short P_INDEX = nSpecies + nDim + 2; + static constexpr unsigned short RHO_INDEX = nSpecies + nDim + 3; + static constexpr unsigned short H_INDEX = nSpecies + nDim + 4; + static constexpr unsigned short A_INDEX = nSpecies + nDim + 5; + static constexpr unsigned short RHOCVTR_INDEX = nSpecies + nDim + 6; + static constexpr unsigned short RHOCVVE_INDEX = nSpecies + nDim + 7; + + std::stringstream options; + CConfig* config = nullptr; + std::vector primitive_i = std::vector(nPrimVar, 0.0); + std::vector primitive_j = std::vector(nPrimVar, 0.0); + su2activematrix gradient_i = su2activematrix(nPrimVarGrad, nDim); + su2activematrix gradient_j = su2activematrix(nPrimVarGrad, nDim); + std::array diffusion_i{}; + std::array diffusion_j{}; + std::array eve_i{}; + std::array eve_j{}; + std::array cvve_i{}; + std::array cvve_j{}; + std::array dT_i{}; + std::array dT_j{}; + std::array dTve_i{}; + std::array dTve_j{}; + const std::array coord_i{0.0, 0.0}; + const std::array coord_j{3.0, 4.0}; + const std::array normal{0.6, 0.8}; + + NEMOViscousFixture() { + options << "SOLVER= NEMO_NAVIER_STOKES\n" + << "GAS_MODEL= AIR-5\n" + << "GAS_COMPOSITION= (0.77, 0.23, 0.0, 0.0, 0.0)\n" + << "FLUID_MODEL= SU2_NONEQ\n" + << "FROZEN_MIXTURE= YES\n" + << "TIME_DISCRE_FLOW= EULER_IMPLICIT\n" + << "CONV_NUM_METHOD_FLOW= AUSM\n"; + config = new CConfig(options, SU2_COMPONENT::SU2_CFD, false); + + primitive_i[0] = primitive_j[0] = 0.77; + primitive_i[1] = primitive_j[1] = 0.23; + primitive_i[T_INDEX] = primitive_j[T_INDEX] = 300.0; + primitive_i[TVE_INDEX] = primitive_j[TVE_INDEX] = 300.0; + primitive_i[P_INDEX] = primitive_j[P_INDEX] = 101325.0; + primitive_i[RHO_INDEX] = primitive_j[RHO_INDEX] = 1.0; + primitive_i[H_INDEX] = primitive_j[H_INDEX] = 3.0e5; + primitive_i[A_INDEX] = primitive_j[A_INDEX] = 340.0; + primitive_i[RHOCVTR_INDEX] = primitive_j[RHOCVTR_INDEX] = 700.0; + primitive_i[RHOCVVE_INDEX] = primitive_j[RHOCVVE_INDEX] = 1.0; + gradient_i = su2double(0.0); + gradient_j = su2double(0.0); + } + + ~NEMOViscousFixture() { delete config; } + + void set_common(CNumerics& numerics) { + numerics.SetCoord(coord_i.data(), coord_j.data()); + numerics.SetNormal(normal.data()); + numerics.SetPrimitive(primitive_i.data(), primitive_j.data()); + numerics.SetPrimVarGradient(CMatrixView(gradient_i), CMatrixView(gradient_j)); + numerics.SetDiffusionCoeff(diffusion_i.data(), diffusion_j.data()); + numerics.SetLaminarViscosity(2.0, 2.0); + numerics.SetEddyViscosity(0.0, 0.0); + numerics.SetThermalConductivity(0.0, 0.0); + numerics.SetThermalConductivity_ve(0.0, 0.0); + numerics.SetEve(eve_i.data(), eve_j.data()); + numerics.SetCvve(cvve_i.data(), cvve_j.data()); + numerics.SetdTdU(dT_i.data(), dT_j.data()); + numerics.SetdTvedU(dTve_i.data(), dTve_j.data()); + } + + template + su2double directional_flux(Numerics& numerics) { + set_common(numerics); + const auto residual = numerics.ComputeResidual(config); + su2double projected = 0.0; + for (unsigned short component = 0; component < nDim; ++component) + projected += normal[component] * residual.residual[nSpecies + component]; + return projected; + } + + static su2double directional_jacobian(const su2double* const* matrix, const std::array& direction) { + su2double value = 0.0; + for (unsigned short row = 0; row < nDim; ++row) + for (unsigned short column = 0; column < nDim; ++column) + value += direction[row] * matrix[nSpecies + row][nSpecies + column] * direction[column]; + return value; + } +}; + +} // namespace + +TEST_CASE("NEMO corrected viscous residual returns distinct i and j Jacobians", "[NEMO][viscous][Jacobian]") { + NEMOViscousFixture fixture; + CAvgGradCorrected_NEMO numerics(fixture.nDim, fixture.nVar, fixture.nPrimVar, fixture.nPrimVarGrad, fixture.config); + fixture.set_common(numerics); + const auto base = numerics.ComputeResidual(fixture.config); + /*--- Copy these values: later residual evaluations reuse the numerics' + * Jacobian storage. ---*/ + const su2double analytic_i = fixture.directional_jacobian(base.jacobian_i, fixture.normal); + const su2double analytic_j = fixture.directional_jacobian(base.jacobian_j, fixture.normal); + + /*--- Independent Newtonian-stress result for this longitudinal mode: + * tau_nn = (4/3) mu du_n/dn. The unit face normal is parallel to the + * length-5 edge, with mu = 2 and rho = 1, hence the momentum derivatives + * are -(4/3) mu/(rho d) = -8/15 at i and +8/15 at j. ---*/ + REQUIRE(analytic_i == Approx(-8.0 / 15.0).epsilon(1.0e-12)); + REQUIRE(analytic_j == Approx(8.0 / 15.0).epsilon(1.0e-12)); + REQUIRE(analytic_i != Approx(analytic_j)); + + /*--- Differentiate only the returned momentum flux, without using its + * Jacobians to construct the reference. Perturb each endpoint's momentum + * along the normal; density, thermodynamic inputs, transport and supplied + * zero gradients stay fixed. The corrected numerics reconstructs the + * edge gradient itself. This tests the controlled momentum mode, not the + * full thermochemical derivative of the approximate viscous Jacobian. ---*/ + for (unsigned short endpoint = 0; endpoint < 2; ++endpoint) { + auto& primitive = endpoint == 0 ? fixture.primitive_i : fixture.primitive_j; + const auto original = primitive; + for (const auto step : {1.0e-4, 1.0e-6}) { + INFO("endpoint = " << endpoint << ", momentum step = " << step); + for (unsigned short component = 0; component < fixture.nDim; ++component) + primitive[fixture.VEL_INDEX + component] = + original[fixture.VEL_INDEX + component] + step * fixture.normal[component] / original[fixture.RHO_INDEX]; + const su2double flux_plus = fixture.directional_flux(numerics); + + for (unsigned short component = 0; component < fixture.nDim; ++component) + primitive[fixture.VEL_INDEX + component] = + original[fixture.VEL_INDEX + component] - step * fixture.normal[component] / original[fixture.RHO_INDEX]; + const su2double flux_minus = fixture.directional_flux(numerics); + primitive = original; + + REQUIRE(std::isfinite(SU2_TYPE::GetValue(flux_plus))); + REQUIRE(std::isfinite(SU2_TYPE::GetValue(flux_minus))); + const su2double finite_difference = (flux_plus - flux_minus) / (2.0 * step); + const su2double analytic = endpoint == 0 ? analytic_i : analytic_j; + CHECK(finite_difference == Approx(analytic).epsilon(1.0e-8).margin(1.0e-10)); + } + } +} diff --git a/UnitTests/meson.build b/UnitTests/meson.build index f8c22511b5f8..66917b9782be 100644 --- a/UnitTests/meson.build +++ b/UnitTests/meson.build @@ -16,6 +16,7 @@ su2_cfd_tests = files(['Common/geometry/primal_grid/CPrimalGrid_tests.cpp', 'SU2_CFD/numerics/CNumerics_tests.cpp', 'SU2_CFD/fluid/CFluidModel_tests.cpp', 'SU2_CFD/gradients.cpp', + 'SU2_CFD/nemo_viscous_assembly.cpp', 'SU2_CFD/windowing.cpp', 'Common/toolboxes/random_toolbox_tests.cpp', 'Common/linear_algebra/quantization_tests.cpp']) From 8118ee0f507b5fb0fd70b819e6f1351c1b3c6d28 Mon Sep 17 00:00:00 2001 From: bellonarts Date: Tue, 15 Sep 2026 04:40:13 -0400 Subject: [PATCH 55/61] Pass the edge length, not its square, to the coarse NEMO viscous Jacobian (#2883) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use `GeometryToolbox::Norm` instead of `SquaredNorm` when passing edge distance to the NEMO approximate viscous Jacobian. This corrects one production line; the physical flux calculation is unchanged. The duplicated fixture remains removed. ## Historical convergence comparison The published comparison tested head `b8504d8de172a20a0493391d3f3a3fd572b0cfbd` against base `07aa46b1868655ec01f534bf3ac84ba5fbb6b822`. Each Mach 2 and 2.5 pair used the same 37,192 point sphere mesh, identical settings and cold starts: AIR5, 6 Pa, T=Tve=220 K, first order AUSM, implicit Euler, an adiabatic noncatalytic wall, CFL1, MG1 with the default coarse CFL ratio of 0.25, and two MPI ranks per arm. | Condition | Base | Historical PR head | |---|---|---| | Mach 2 | Guard stopped at 34 rows; maximum 4 nonphysical points | Natural convergence in 1,341 rows; zero nonphysical points | | Mach 2.5 | Guard stopped at 23 rows; maximum 9 nonphysical points | Natural convergence in 1,243 rows; zero nonphysical points | Counts include row zero. Both patched runs met all ten log10 RMS residual limits ≤−6 and all three absolute CD/CL/HF Cauchy limits <1e−6 over a window of 100 samples, with clean natural exits before the 10,000 row cap. Each saved final field also passed a separate MG0 residual audit on the same mesh. Independent verification passed. The controls were stopped by the guard for nonphysical points. These two operating conditions on one geometry demonstrate improved robustness under the stated settings; they do not establish a speedup or improved physical accuracy. [Evidence ZIP with configs, mesh, logs, results, plots and reproduction instructions](https://github.com/user-attachments/files/31923042/PR2883-supersonic-reproduction-v1.zip). [Reply to Pedro](https://github.com/su2code/SU2/pull/2883#issuecomment-5574011280). ## Validation after merging develop Source revision `a3a60bfa6d25f60066f2a892ea85841df986862d` includes develop `56df7491c9027fd28c83d8b09617c2852ccd078e` and #2885. The historical convergence cases above were not rerun on this revision. [Supplemental Linux run](https://github.com/babybluechips/SU2/actions/runs/34925451837) tested that exact source on x86_64 and arm64, with separate serial and MPI builds. Full normal unit suites passed, including MLPCpp and Mutation++. Each selected regression ran twice from clean directories. The autotest configs change only `ITER` to `test_iter + 1`; all other settings and protected restart inputs are preserved. All 52 solver runs completed cleanly, repeated rows were identical, and the refreshed references accept every captured row under the original tolerances. Archive digests and all 680 sealed files for this PR were independently verified. Reference commit `c0519aa5f310f65f453da24bb7d955f65d6ee158` updates ten arrays from that evidence. The subsequent E221 cleanup changes only assignment spacing on 37 lines; the Python syntax trees and all numerical arrays are unchanged. **Head after the whitespace cleanup:** `dc81add7a6ba9a42dc4b18d2e41bda94451db9ee`. **Full upstream CI at that head:** [pending](https://github.com/su2code/SU2/actions/runs/34929371327). --------- Co-authored-by: Claude Fable 5.1 --- SU2_CFD/src/numerics/NEMO/NEMO_diffusion.cpp | 2 +- TestCases/parallel_regression.py | 56 ++++++++++---------- TestCases/serial_regression.py | 38 ++++++------- 3 files changed, 48 insertions(+), 48 deletions(-) diff --git a/SU2_CFD/src/numerics/NEMO/NEMO_diffusion.cpp b/SU2_CFD/src/numerics/NEMO/NEMO_diffusion.cpp index 705c1b253adb..fa1c80ef77b8 100644 --- a/SU2_CFD/src/numerics/NEMO/NEMO_diffusion.cpp +++ b/SU2_CFD/src/numerics/NEMO/NEMO_diffusion.cpp @@ -169,7 +169,7 @@ CNumerics::ResidualType<> CAvgGrad_NEMO::ComputeResidual(const CConfig *config) su2double dist_ij_2[MAXNDIM] = {0.0}; GeometryToolbox::Distance(nDim, Coord_j, Coord_i, dist_ij_2); - dist_ij = GeometryToolbox::SquaredNorm(nDim, dist_ij_2); + dist_ij = GeometryToolbox::Norm(nDim, dist_ij_2); for (auto iVar = 0ul; iVar < nVar; iVar++) { for (auto jVar = 0ul; jVar < nVar; jVar++) { diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index 68a7a54d35a3..9df477c1d4f2 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -130,7 +130,7 @@ def main(): invwedge_a.cfg_file = "invwedge_ausm.cfg" invwedge_a.test_iter = 10 invwedge_a.test_vals = [-1.069665, -1.594428, -18.299923, -18.627315, -18.573325, 2.245732, 1.874096, 5.290295, 0.847739] - invwedge_a.test_vals_aarch64 = [-1.069675, -1.594438, -18.299736, -18.627126, -18.573137, 2.245721, 1.874105, 5.290285, 0.847729] + invwedge_a.test_vals_aarch64 = [-1.069665, -1.594428, -18.299923, -18.627315, -18.573325, 2.245732, 1.874096, 5.290295, 0.847739] test_list.append(invwedge_a) # Inviscid single wedge, ausm+-up2, implicit @@ -148,7 +148,7 @@ def main(): invwedge_msw.cfg_file = "invwedge_msw.cfg" invwedge_msw.test_iter = 10 invwedge_msw.test_vals = [-1.212335, -1.737098, -18.301825, -18.629206, -18.575226, 2.106171, 1.651949, 5.143958, 0.704444] - invwedge_msw.test_vals_aarch64 = [-1.212335, -1.737098, -18.299279, -18.626656, -18.572683, 2.106171, 1.651949, 5.143958, 0.704444] + invwedge_msw.test_vals_aarch64 = [-1.212335, -1.737098, -18.299771, -18.627181, -18.573171, 2.106171, 1.651949, 5.143958, 0.704444] test_list.append(invwedge_msw) # Inviscid single wedge, roe, implicit @@ -157,7 +157,7 @@ def main(): invwedge_roe.cfg_file = "invwedge_roe.cfg" invwedge_roe.test_iter = 10 invwedge_roe.test_vals = [-1.023283, -1.548046, -17.814403, -18.143369, -18.087522, 2.295025, 1.884804, 5.338440, 0.926068] - invwedge_roe.test_vals_aarch64 = [-1.052398, -1.577160, -17.794015, -18.122997, -18.067131, 2.266042, 1.849686, 5.304700, 0.899584] + invwedge_roe.test_vals_aarch64 = [-1.022187, -1.546949, -17.810073, -18.139026, -18.083190, 2.296242, 1.883666, 5.339443, 0.925366] test_list.append(invwedge_roe) # Inviscid single wedge, lax, implicit @@ -184,7 +184,7 @@ def main(): invwedge_ss_inlet.cfg_file = "invwedge_ss_inlet.cfg" invwedge_ss_inlet.test_iter = 10 invwedge_ss_inlet.test_vals = [-1.068634, -1.593397, -18.246265, -18.575529, -18.519338, 2.246925, 1.874200, 5.291234, 0.848731] - invwedge_ss_inlet.test_vals_aarch64 = [-1.068592, -1.593355, -18.250183, -18.579524, -18.523255, 2.246972, 1.874197, 5.291273, 0.848771] + invwedge_ss_inlet.test_vals_aarch64 = [-1.068634, -1.593397, -18.246267, -18.575555, -18.519340, 2.246925, 1.874200, 5.291234, 0.848731] test_list.append(invwedge_ss_inlet) # Viscous single cone - axisymmetric @@ -192,8 +192,8 @@ def main(): visc_cone.cfg_dir = "nonequilibrium/visc_wedge" visc_cone.cfg_file = "axi_visccone.cfg" visc_cone.test_iter = 10 - visc_cone.test_vals = [-5.298530, -5.823015, -20.404788, -20.318330, -20.378801, 1.067489, -3.250987, -0.015488, 0.095136, 24939.000000] - visc_cone.test_vals_aarch64 = [-5.298530, -5.823015, -20.404788, -20.318329, -20.378801, 1.067489, -3.250987, -0.015488, 0.095136, 24939.000000] + visc_cone.test_vals = [-5.298530, -5.823015, -20.290704, -20.436259, -20.904377, 1.067489, -3.250987, -0.015488, 0.095136, 24939.000000] + visc_cone.test_vals_aarch64 = [-5.298530, -5.823015, -20.290704, -20.436259, -20.904377, 1.067489, -3.250987, -0.015488, 0.095136, 24939.000000] test_list.append(visc_cone) # Viscous single wedge with Mutation++ @@ -225,7 +225,7 @@ def main(): ion_gy.cfg_dir = "nonequilibrium/visc_cylinder" ion_gy.cfg_file = "cyl_ion_gy.cfg" ion_gy.test_iter = 99 - ion_gy.test_vals = [-11.662039, -4.203178, -4.868257, -5.462497, -5.232052, -4.960881, -6.951391, 4.541901, 4.552855, -0.014861, 0.000001, 90357.000000] + ion_gy.test_vals = [-11.675651, -4.207534, -4.868346, -5.467964, -5.238572, -4.965372, -6.964212, 4.529202, 4.541233, -0.014861, 0.000001, 90357.000000] ion_gy.tol = 0.01 test_list.append(ion_gy) @@ -652,9 +652,9 @@ def main(): test_list.append(inc_euler_naca0012) # NACA0012 Hydrofoil - inc_euler_naca0012_pb = TestCase('inc_euler_naca0012_pb') - inc_euler_naca0012_pb.cfg_dir = "incomp_euler/naca0012" - inc_euler_naca0012_pb.cfg_file = "incomp_pb_NACA0012.cfg" + inc_euler_naca0012_pb = TestCase('inc_euler_naca0012_pb') + inc_euler_naca0012_pb.cfg_dir = "incomp_euler/naca0012" + inc_euler_naca0012_pb.cfg_file = "incomp_pb_NACA0012.cfg" inc_euler_naca0012_pb.test_iter = 20 inc_euler_naca0012_pb.test_vals = [-4.454818, -4.784247, 0.427448, 0.012083] test_list.append(inc_euler_naca0012_pb) @@ -688,9 +688,9 @@ def main(): test_list.append(inc_lam_cylinder) # Laminar cylinder, pressure-based - inc_lam_cylinder_pb = TestCase('inc_lam_cylinder_pb') - inc_lam_cylinder_pb.cfg_dir = "incomp_navierstokes/cylinder" - inc_lam_cylinder_pb.cfg_file = "incomp_pb_cylinder.cfg" + inc_lam_cylinder_pb = TestCase('inc_lam_cylinder_pb') + inc_lam_cylinder_pb.cfg_dir = "incomp_navierstokes/cylinder" + inc_lam_cylinder_pb.cfg_file = "incomp_pb_cylinder.cfg" inc_lam_cylinder_pb.test_iter = 10 inc_lam_cylinder_pb.test_vals = [-3.486113, -3.777688, 0.012054, 6.178573] test_list.append(inc_lam_cylinder_pb) @@ -699,9 +699,9 @@ def main(): # equation and variable density. Convergence is genuine but slow (needs ~40k iterations # for rms[h] to reach a low residual); this only checks a short trajectory guard, matching # the pattern used for other hard-to-converge cases in this suite. - inc_poly_cylinder_pb = TestCase('inc_poly_cylinder_pb') - inc_poly_cylinder_pb.cfg_dir = "incomp_navierstokes/cylinder" - inc_poly_cylinder_pb.cfg_file = "pb_poly_cylinder.cfg" + inc_poly_cylinder_pb = TestCase('inc_poly_cylinder_pb') + inc_poly_cylinder_pb.cfg_dir = "incomp_navierstokes/cylinder" + inc_poly_cylinder_pb.cfg_file = "pb_poly_cylinder.cfg" inc_poly_cylinder_pb.test_iter = 20 inc_poly_cylinder_pb.test_vals = [-13.483272, 0.541350, 0.005972, 17.020220, -8927.600000] test_list.append(inc_poly_cylinder_pb) @@ -715,9 +715,9 @@ def main(): test_list.append(inc_lam_sphere) # Laminar sphere, Re=1, pressure-based. Only 3D pressure-based case in the regression suite. - inc_lam_sphere_pb = TestCase('inc_lam_sphere_pb') - inc_lam_sphere_pb.cfg_dir = "incomp_navierstokes/sphere" - inc_lam_sphere_pb.cfg_file = "pb_sphere.cfg" + inc_lam_sphere_pb = TestCase('inc_lam_sphere_pb') + inc_lam_sphere_pb.cfg_dir = "incomp_navierstokes/sphere" + inc_lam_sphere_pb.cfg_file = "pb_sphere.cfg" inc_lam_sphere_pb.test_iter = 9 inc_lam_sphere_pb.test_vals = [-6.092084, -2.305040, -2.479072, -2.548968, 0.191798, 170.793203, -6.000632] test_list.append(inc_lam_sphere_pb) @@ -725,9 +725,9 @@ def main(): # Laminar sphere, Re=1, pressure-based, automatic relaxation factors. The only case # in the suite that exercises USE_AUTOMATIC_RELAXATION_FACTORS, and 3D since the alpha_p # bug this guards against is invisible in 2D (it sums the wrong set of Jacobian diagonals). - inc_lam_sphere_pb_urf = TestCase('inc_lam_sphere_pb_urf') - inc_lam_sphere_pb_urf.cfg_dir = "incomp_navierstokes/sphere" - inc_lam_sphere_pb_urf.cfg_file = "pb_sphere_urf.cfg" + inc_lam_sphere_pb_urf = TestCase('inc_lam_sphere_pb_urf') + inc_lam_sphere_pb_urf.cfg_dir = "incomp_navierstokes/sphere" + inc_lam_sphere_pb_urf.cfg_file = "pb_sphere_urf.cfg" inc_lam_sphere_pb_urf.test_iter = 9 inc_lam_sphere_pb_urf.test_vals = [-4.747391, -2.239844, -2.380839, -1.685462, 0.237593, 206.027273, -6.331275] test_list.append(inc_lam_sphere_pb_urf) @@ -757,9 +757,9 @@ def main(): test_list.append(inc_lam_bend) # X-coarse laminar bend as a mixed element CGNS test, pressure-based - inc_lam_bend_pb = TestCase('inc_lam_bend_pb') - inc_lam_bend_pb.cfg_dir = "incomp_navierstokes/bend" - inc_lam_bend_pb.cfg_file = "pb_lam_bend.cfg" + inc_lam_bend_pb = TestCase('inc_lam_bend_pb') + inc_lam_bend_pb.cfg_dir = "incomp_navierstokes/bend" + inc_lam_bend_pb.cfg_file = "pb_lam_bend.cfg" inc_lam_bend_pb.test_iter = 10 inc_lam_bend_pb.test_vals = [-3.824468, -3.345335, -0.012351, 1.685090] test_list.append(inc_lam_bend_pb) @@ -801,9 +801,9 @@ def main(): test_list.append(inc_turb_naca0012_sst_sust) # Flat plate, pressure-based - inc_flatplate_pb = TestCase('inc_flatplate_pb') - inc_flatplate_pb.cfg_dir = "incomp_rans/rough_flatplate" - inc_flatplate_pb.cfg_file = "pb_rough_flatplate_incomp.cfg" + inc_flatplate_pb = TestCase('inc_flatplate_pb') + inc_flatplate_pb.cfg_dir = "incomp_rans/rough_flatplate" + inc_flatplate_pb.cfg_file = "pb_rough_flatplate_incomp.cfg" inc_flatplate_pb.test_iter = 10 inc_flatplate_pb.test_vals = [-4.063342, -9.884401, 0.000010, 0.228472] test_list.append(inc_flatplate_pb) diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index fedb4302209c..9ed255afd6ac 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -67,7 +67,7 @@ def main(): invwedge.cfg_file = "invwedge_ausm.cfg" invwedge.test_iter = 10 invwedge.test_vals = [-1.073689, -1.598452, -18.299910, -18.627322, -18.573334, 2.241771, 1.868566, 5.286082, 0.843751] - invwedge.test_vals_aarch64 = [-1.073699, -1.598462, -18.299723, -18.627132, -18.573146, 2.241760, 1.868575, 5.286072, 0.843741] + invwedge.test_vals_aarch64 = [-1.073689, -1.598452, -18.299910, -18.627322, -18.573334, 2.241771, 1.868566, 5.286082, 0.843751] test_list.append(invwedge) # Viscous single cone - axisymmetric @@ -75,8 +75,8 @@ def main(): visc_cone.cfg_dir = "nonequilibrium/visc_wedge" visc_cone.cfg_file = "axi_visccone.cfg" visc_cone.test_iter = 10 - visc_cone.test_vals = [-5.298550, -5.823031, -20.330692, -20.368713, -20.310533, 1.067466, -3.251001, -0.015489, 0.095136, 24939.000000] - visc_cone.test_vals_aarch64 = [-5.298550, -5.823031, -20.330692, -20.368713, -20.310533, 1.067466, -3.251001, -0.015489, 0.095136, 24939.000000] + visc_cone.test_vals = [-5.298550, -5.823031, -20.180369, -20.157135, -20.112771, 1.067466, -3.251001, -0.015489, 0.095136, 24939.000000] + visc_cone.test_vals_aarch64 = [-5.298550, -5.823031, -20.180369, -20.157135, -20.112771, 1.067466, -3.251001, -0.015489, 0.095136, 24939.000000] test_list.append(visc_cone) ######################### @@ -311,13 +311,13 @@ def main(): # E387 transitional SST+LM tutorial config, re-run here as a sanitizer-only probe. # Covers the density gradient not being available for MUSCL_TURB=YES with a flow scheme # that does not store that gradient. - tutorial_trans_e387_sst_asan = TestCase('tutorial_trans_e387_sst_asan') - tutorial_trans_e387_sst_asan.cfg_dir = "../Tutorials/compressible_flow/Transitional_Airfoil/Langtry_and_Menter/E387" - tutorial_trans_e387_sst_asan.cfg_file = "transitional_SST_LM_model_ConfigFile.cfg" - tutorial_trans_e387_sst_asan.test_iter = 2 - tutorial_trans_e387_sst_asan.test_vals = [-6.418119, -4.827573, -2.220229, 3.029787, 3.123846, 5.000000, -5.610239] - tutorial_trans_e387_sst_asan.timeout = 1600 - tutorial_trans_e387_sst_asan.no_restart = True + tutorial_trans_e387_sst_asan = TestCase('tutorial_trans_e387_sst_asan') + tutorial_trans_e387_sst_asan.cfg_dir = "../Tutorials/compressible_flow/Transitional_Airfoil/Langtry_and_Menter/E387" + tutorial_trans_e387_sst_asan.cfg_file = "transitional_SST_LM_model_ConfigFile.cfg" + tutorial_trans_e387_sst_asan.test_iter = 2 + tutorial_trans_e387_sst_asan.test_vals = [-6.418119, -4.827573, -2.220229, 3.029787, 3.123846, 5.000000, -5.610239] + tutorial_trans_e387_sst_asan.timeout = 1600 + tutorial_trans_e387_sst_asan.no_restart = True tutorial_trans_e387_sst_asan.enabled_with_regular = False test_list.append(tutorial_trans_e387_sst_asan) @@ -416,9 +416,9 @@ def main(): test_list.append(inc_euler_naca0012) # NACA0012 Hydrofoil, pressure-based - inc_euler_naca0012_pb = TestCase('inc_euler_naca0012_pb') - inc_euler_naca0012_pb.cfg_dir = "incomp_euler/naca0012" - inc_euler_naca0012_pb.cfg_file = "incomp_pb_NACA0012.cfg" + inc_euler_naca0012_pb = TestCase('inc_euler_naca0012_pb') + inc_euler_naca0012_pb.cfg_dir = "incomp_euler/naca0012" + inc_euler_naca0012_pb.cfg_file = "incomp_pb_NACA0012.cfg" inc_euler_naca0012_pb.test_iter = 20 inc_euler_naca0012_pb.test_vals = [-4.454818, -4.784246, 0.427448, 0.012083] test_list.append(inc_euler_naca0012_pb) @@ -451,9 +451,9 @@ def main(): test_list.append(inc_lam_cylinder) # Laminar cylinder, pressure-based - inc_lam_cylinder_pb = TestCase('inc_lam_cylinder_pb') - inc_lam_cylinder_pb.cfg_dir = "incomp_navierstokes/cylinder" - inc_lam_cylinder_pb.cfg_file = "incomp_pb_cylinder.cfg" + inc_lam_cylinder_pb = TestCase('inc_lam_cylinder_pb') + inc_lam_cylinder_pb.cfg_dir = "incomp_navierstokes/cylinder" + inc_lam_cylinder_pb.cfg_file = "incomp_pb_cylinder.cfg" inc_lam_cylinder_pb.test_iter = 10 inc_lam_cylinder_pb.test_vals = [-3.486100, -3.777681, 0.012003, 6.178586] test_list.append(inc_lam_cylinder_pb) @@ -512,9 +512,9 @@ def main(): test_list.append(inc_turb_naca0012_sst_sust) # Flat plate, pressure-based - inc_flatplate_pb = TestCase('inc_flatplate_pb') - inc_flatplate_pb.cfg_dir = "incomp_rans/rough_flatplate" - inc_flatplate_pb.cfg_file = "pb_rough_flatplate_incomp.cfg" + inc_flatplate_pb = TestCase('inc_flatplate_pb') + inc_flatplate_pb.cfg_dir = "incomp_rans/rough_flatplate" + inc_flatplate_pb.cfg_file = "pb_rough_flatplate_incomp.cfg" inc_flatplate_pb.test_iter = 10 inc_flatplate_pb.test_vals = [-4.063342, -9.884401, 0.000010, 0.228472] test_list.append(inc_flatplate_pb) From 93da64190c6be4e8d384ec00bb78d6a8de8c27f0 Mon Sep 17 00:00:00 2001 From: bellonarts Date: Wed, 16 Sep 2026 02:27:09 -0400 Subject: [PATCH 56/61] Make the NEMO total-energy under-relaxation check reachable (#2884) The total energy check was nested inside the species block, so it could never execute. This makes the energy check independent and applies the tighter species or total energy limit. The loop logic is now the small `ComputeUnderRelaxationFactor` helper requested in review. Its direct test with manufactured data covers small and excessive energy updates, opposing species updates, which limit wins, and cancellation for a tiny factor. ## Validation after the latest develop merge Updated with develop `8118ee0f507b5fb0fd70b819e6f1351c1b3c6d28` after #2883 merged. The approved helper and its direct tests are unchanged. The [combined source validation](https://github.com/babybluechips/SU2/actions/runs/35018055822) tested merge commit `7e89cb6be1a869a61f9425bd069dc1ac6aa3d699` on Linux x86 and ARM with separate serial and MPI builds. All 52 solver runs completed, and every repeated row agreed within its architecture and build. All four full normal unit suites passed. Both MPI builds included Mutation++ support. That validation run failed its comparisons against the old cone references. Commit `53b8b75affdf3b1c29ff56691fc2333dd32cbd88` updates four cone arrays from the repeated results, with three residual entries changed in each. All 52 recorded rows pass the updated references under the original tolerances. Other references, configurations and restart inputs are unchanged. Full upstream CI on `53b8b75affdf3b1c29ff56691fc2333dd32cbd88` passed: [all 31 regression workflow jobs](https://github.com/su2code/SU2/actions/runs/35022817008), including unit tests and both sanitizer suites. Code Style, CodeQL, labels and CodeFactor also passed. ## PR Checklist - [x] I am submitting my contribution to the develop branch. - [x] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [x] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [x] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [x] I have added a test case that demonstrates my contribution, if necessary. - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --------- Co-authored-by: Claude Fable 5.1 --- SU2_CFD/include/solvers/CNEMOEulerSolver.hpp | 13 +++ SU2_CFD/src/solvers/CNEMOEulerSolver.cpp | 79 +++++++++---------- TestCases/parallel_regression.py | 28 +++---- TestCases/serial_regression.py | 8 +- .../solvers/CNEMOEulerSolver_tests.cpp | 59 ++++++++++++++ UnitTests/meson.build | 1 + 6 files changed, 128 insertions(+), 60 deletions(-) create mode 100644 UnitTests/SU2_CFD/solvers/CNEMOEulerSolver_tests.cpp diff --git a/SU2_CFD/include/solvers/CNEMOEulerSolver.hpp b/SU2_CFD/include/solvers/CNEMOEulerSolver.hpp index e5d3c2d3c89e..f1955c46a1aa 100644 --- a/SU2_CFD/include/solvers/CNEMOEulerSolver.hpp +++ b/SU2_CFD/include/solvers/CNEMOEulerSolver.hpp @@ -199,6 +199,19 @@ class CNEMOEulerSolver : public CFVMFlowSolverBaseGetMaxUpdateFractionFlow(); - - SU2_OMP_FOR_STAT(omp_chunk_size) - for (auto iPoint = 0ul; iPoint < nPointDomain; iPoint++) { - su2double localUnderRelaxation = 1.0; - - su2double num = 0.0; - su2double denom = 0.0; - - for (auto iVar = 0; iVar < nVar; iVar++) { - /* We impose a limit on the maximum percentage that the - density (sum of all species) and energy can change over a nonlinear iteration. */ - - const unsigned long index = iPoint * nVar + iVar; - if (iVar < config->GetnSpecies()) { - num += fabs(LinSysSol[index]); - denom += fabs(nodes->GetSolution(iPoint, iVar)); - - /*--- If final density/species, compute Under-relaxation ---*/ - if (iVar == (config ->GetnSpecies()-1)){ - su2double ratio = (num/(denom+EPS)); - if (ratio > allowableRatio) { - localUnderRelaxation = min(allowableRatio / ratio, localUnderRelaxation); - } +su2double CNEMOEulerSolver::ComputeUnderRelaxationFactor(unsigned short nSpecies, unsigned short nVar, + const su2double* solution, const su2double* update, + su2double allowableRatio) { + su2double localUnderRelaxation = 1.0; + su2double num = 0.0; + su2double denom = 0.0; + + for (auto iVar = 0; iVar < nVar; iVar++) { + /*--- Limit the sum of the species updates relative to the mixture density. ---*/ + if (iVar < nSpecies) { + num += fabs(update[iVar]); + denom += fabs(solution[iVar]); + + if (iVar == nSpecies - 1) { + su2double ratio = num / (denom + EPS); + if (ratio > allowableRatio) { + localUnderRelaxation = min(allowableRatio / ratio, localUnderRelaxation); } + } + } - /*--- Energy ---*/ - if (iVar == (nVar-2)){ - su2double ratio = fabs(LinSysSol[index]) / (fabs(nodes->GetSolution(iPoint, iVar)) + EPS); - if (ratio > allowableRatio) { - localUnderRelaxation = min(allowableRatio / ratio, localUnderRelaxation); - } - } + /*--- Total energy must be checked independently of the species block. ---*/ + if (iVar == nVar - 2) { + su2double ratio = fabs(update[iVar]) / (fabs(solution[iVar]) + EPS); + if (ratio > allowableRatio) { + localUnderRelaxation = min(allowableRatio / ratio, localUnderRelaxation); } } + } - /* Threshold the relaxation factor in the event that there is - a very small value. This helps avoid catastrophic crashes due - to non-realizable states by canceling the update. */ + /*--- Cancel very small updates to avoid non-realizable states. ---*/ + if (localUnderRelaxation < 1e-10) localUnderRelaxation = 0.0; + return localUnderRelaxation; +} - if (localUnderRelaxation < 1e-10) localUnderRelaxation = 0.0; +void CNEMOEulerSolver::ComputeUnderRelaxationFactor(const CConfig *config) { + SU2_ZONE_SCOPED - /* Store the under-relaxation factor for this point. */ + const su2double allowableRatio = config->GetMaxUpdateFractionFlow(); + const unsigned short nSpecies = config->GetnSpecies(); + SU2_OMP_FOR_STAT(omp_chunk_size) + for (auto iPoint = 0ul; iPoint < nPointDomain; iPoint++) { + const su2double localUnderRelaxation = ComputeUnderRelaxationFactor( + nSpecies, nVar, nodes->GetSolution(iPoint), LinSysSol.GetBlock(iPoint), allowableRatio); nodes->SetUnderRelaxation(iPoint, localUnderRelaxation); } END_SU2_OMP_FOR diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index 9df477c1d4f2..d3ab22ea9174 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -129,8 +129,8 @@ def main(): invwedge_a.cfg_dir = "nonequilibrium/invwedge" invwedge_a.cfg_file = "invwedge_ausm.cfg" invwedge_a.test_iter = 10 - invwedge_a.test_vals = [-1.069665, -1.594428, -18.299923, -18.627315, -18.573325, 2.245732, 1.874096, 5.290295, 0.847739] - invwedge_a.test_vals_aarch64 = [-1.069665, -1.594428, -18.299923, -18.627315, -18.573325, 2.245732, 1.874096, 5.290295, 0.847739] + invwedge_a.test_vals = [-1.081764, -1.606527, -18.299923, -18.627313, -18.573325, 2.234595, 1.854912, 5.278328, 0.837083] + invwedge_a.test_vals_aarch64 = [-1.081764, -1.606527, -18.299923, -18.627313, -18.573325, 2.234595, 1.854912, 5.278328, 0.837083] test_list.append(invwedge_a) # Inviscid single wedge, ausm+-up2, implicit @@ -147,8 +147,8 @@ def main(): invwedge_msw.cfg_dir = "nonequilibrium/invwedge" invwedge_msw.cfg_file = "invwedge_msw.cfg" invwedge_msw.test_iter = 10 - invwedge_msw.test_vals = [-1.212335, -1.737098, -18.301825, -18.629206, -18.575226, 2.106171, 1.651949, 5.143958, 0.704444] - invwedge_msw.test_vals_aarch64 = [-1.212335, -1.737098, -18.299771, -18.627181, -18.573171, 2.106171, 1.651949, 5.143958, 0.704444] + invwedge_msw.test_vals = [-1.206414, -1.731177, -18.301825, -18.629206, -18.575226, 2.111923, 1.660408, 5.150501, 0.710186] + invwedge_msw.test_vals_aarch64 = [-1.206414, -1.731177, -18.299771, -18.627181, -18.573171, 2.111923, 1.660408, 5.150501, 0.710186] test_list.append(invwedge_msw) # Inviscid single wedge, roe, implicit @@ -156,8 +156,8 @@ def main(): invwedge_roe.cfg_dir = "nonequilibrium/invwedge" invwedge_roe.cfg_file = "invwedge_roe.cfg" invwedge_roe.test_iter = 10 - invwedge_roe.test_vals = [-1.023283, -1.548046, -17.814403, -18.143369, -18.087522, 2.295025, 1.884804, 5.338440, 0.926068] - invwedge_roe.test_vals_aarch64 = [-1.022187, -1.546949, -17.810073, -18.139026, -18.083190, 2.296242, 1.883666, 5.339443, 0.925366] + invwedge_roe.test_vals = [-1.158437, -1.683200, -17.734417, -18.064156, -18.007526, 2.157771, 1.789407, 5.196450, 0.767270] + invwedge_roe.test_vals_aarch64 = [-1.156325, -1.681088, -17.726749, -18.056513, -17.999860, 2.159488, 1.793498, 5.198638, 0.769831] test_list.append(invwedge_roe) # Inviscid single wedge, lax, implicit @@ -165,8 +165,8 @@ def main(): invwedge_lax.cfg_dir = "nonequilibrium/invwedge" invwedge_lax.cfg_file = "invwedge_lax.cfg" invwedge_lax.test_iter = 10 - invwedge_lax.test_vals = [-0.877280, -1.402043, -32.000000, -32.000000, -24.952631, 2.451869, 1.857084, 5.486158, 1.051580] - invwedge_lax.test_vals_aarch64 = [-0.877280, -1.402043, -32.000000, -32.000000, -24.952631, 2.451869, 1.857084, 5.486158, 1.051580] + invwedge_lax.test_vals = [-0.882120, -1.406883, -32.000000, -32.000000, -24.953606, 2.447071, 1.853423, 5.480164, 1.047015] + invwedge_lax.test_vals_aarch64 = [-0.882120, -1.406883, -32.000000, -32.000000, -24.953606, 2.447071, 1.853423, 5.480164, 1.047015] test_list.append(invwedge_lax) # Inviscid single wedge, implicit, AUSM+M scheme @@ -183,8 +183,8 @@ def main(): invwedge_ss_inlet.cfg_dir = "nonequilibrium/invwedge" invwedge_ss_inlet.cfg_file = "invwedge_ss_inlet.cfg" invwedge_ss_inlet.test_iter = 10 - invwedge_ss_inlet.test_vals = [-1.068634, -1.593397, -18.246265, -18.575529, -18.519338, 2.246925, 1.874200, 5.291234, 0.848731] - invwedge_ss_inlet.test_vals_aarch64 = [-1.068634, -1.593397, -18.246267, -18.575555, -18.519340, 2.246925, 1.874200, 5.291234, 0.848731] + invwedge_ss_inlet.test_vals = [-1.081061, -1.605824, -18.246258, -18.575522, -18.519330, 2.235398, 1.855022, 5.278994, 0.837755] + invwedge_ss_inlet.test_vals_aarch64 = [-1.081061, -1.605824, -18.246260, -18.575547, -18.519333, 2.235398, 1.855022, 5.278994, 0.837755] test_list.append(invwedge_ss_inlet) # Viscous single cone - axisymmetric @@ -192,8 +192,8 @@ def main(): visc_cone.cfg_dir = "nonequilibrium/visc_wedge" visc_cone.cfg_file = "axi_visccone.cfg" visc_cone.test_iter = 10 - visc_cone.test_vals = [-5.298530, -5.823015, -20.290704, -20.436259, -20.904377, 1.067489, -3.250987, -0.015488, 0.095136, 24939.000000] - visc_cone.test_vals_aarch64 = [-5.298530, -5.823015, -20.290704, -20.436259, -20.904377, 1.067489, -3.250987, -0.015488, 0.095136, 24939.000000] + visc_cone.test_vals = [-5.275015, -5.799160, -20.292303, -20.436143, -20.888373, 1.156776, -3.217423, -0.013712, 0.092252, 27878.000000] + visc_cone.test_vals_aarch64 = [-5.275015, -5.799159, -20.292303, -20.436143, -20.888374, 1.156775, -3.217422, -0.013713, 0.092252, 27878.000000] test_list.append(visc_cone) # Viscous single wedge with Mutation++ @@ -209,7 +209,7 @@ def main(): super_cat.cfg_dir = "nonequilibrium/visc_wedge" super_cat.cfg_file = "super_cat.cfg" super_cat.test_iter = 10 - super_cat.test_vals = [-5.309257, -5.834048, -21.098287, -21.157699, -21.180688, 1.056908, -3.252349, -0.028039, 0.252019, 24878.000000] + super_cat.test_vals = [-5.296256, -5.820629, -21.102797, -21.154470, -21.132197, 1.137393, -3.220148, -0.025962, 0.247096, 27884.000000] test_list.append(super_cat) # Viscous single wedge - partially catalytic walls @@ -225,7 +225,7 @@ def main(): ion_gy.cfg_dir = "nonequilibrium/visc_cylinder" ion_gy.cfg_file = "cyl_ion_gy.cfg" ion_gy.test_iter = 99 - ion_gy.test_vals = [-11.675651, -4.207534, -4.868346, -5.467964, -5.238572, -4.965372, -6.964212, 4.529202, 4.541233, -0.014861, 0.000001, 90357.000000] + ion_gy.test_vals = [-12.191649, -4.245292, -4.904190, -5.585803, -5.472455, -5.057619, -7.442352, 3.429183, 3.433581, -0.014861, 0.000001, 90357.000000] ion_gy.tol = 0.01 test_list.append(ion_gy) diff --git a/TestCases/serial_regression.py b/TestCases/serial_regression.py index 9ed255afd6ac..8815d612b271 100755 --- a/TestCases/serial_regression.py +++ b/TestCases/serial_regression.py @@ -66,8 +66,8 @@ def main(): invwedge.cfg_dir = "nonequilibrium/invwedge" invwedge.cfg_file = "invwedge_ausm.cfg" invwedge.test_iter = 10 - invwedge.test_vals = [-1.073689, -1.598452, -18.299910, -18.627322, -18.573334, 2.241771, 1.868566, 5.286082, 0.843751] - invwedge.test_vals_aarch64 = [-1.073689, -1.598452, -18.299910, -18.627322, -18.573334, 2.241771, 1.868566, 5.286082, 0.843751] + invwedge.test_vals = [-1.085516, -1.610279, -18.299901, -18.627313, -18.573325, 2.230829, 1.850866, 5.274272, 0.833227] + invwedge.test_vals_aarch64 = [-1.085516, -1.610279, -18.299901, -18.627313, -18.573325, 2.230829, 1.850866, 5.274272, 0.833227] test_list.append(invwedge) # Viscous single cone - axisymmetric @@ -75,8 +75,8 @@ def main(): visc_cone.cfg_dir = "nonequilibrium/visc_wedge" visc_cone.cfg_file = "axi_visccone.cfg" visc_cone.test_iter = 10 - visc_cone.test_vals = [-5.298550, -5.823031, -20.180369, -20.157135, -20.112771, 1.067466, -3.251001, -0.015489, 0.095136, 24939.000000] - visc_cone.test_vals_aarch64 = [-5.298550, -5.823031, -20.180369, -20.157135, -20.112771, 1.067466, -3.251001, -0.015489, 0.095136, 24939.000000] + visc_cone.test_vals = [-5.275039, -5.799179, -20.180464, -20.157030, -20.112497, 1.156763, -3.217449, -0.013710, 0.092250, 27878.000000] + visc_cone.test_vals_aarch64 = [-5.275039, -5.799179, -20.180464, -20.157030, -20.112496, 1.156764, -3.217451, -0.013710, 0.092250, 27878.000000] test_list.append(visc_cone) ######################### diff --git a/UnitTests/SU2_CFD/solvers/CNEMOEulerSolver_tests.cpp b/UnitTests/SU2_CFD/solvers/CNEMOEulerSolver_tests.cpp new file mode 100644 index 000000000000..bbc4268f4c9e --- /dev/null +++ b/UnitTests/SU2_CFD/solvers/CNEMOEulerSolver_tests.cpp @@ -0,0 +1,59 @@ +/*! + * \file CNEMOEulerSolver_tests.cpp + * \brief Unit tests for the NEMO Euler solver. + * \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 "../../../SU2_CFD/include/solvers/CNEMOEulerSolver.hpp" + +TEST_CASE("NEMO under-relaxation limits species and total-energy updates", "[NEMO][Solver]") { + const unsigned short nSpecies = 2; + const unsigned short nVar = 6; + /*--- Two species, two momentum components, total energy, and vibrational energy. ---*/ + const su2double solution[nVar] = {0.75, 0.25, 1.0, 1.0, 10.0, 2.0}; + const su2double allowableRatio = 0.2; + + const struct { + const char* name; + su2double update[nVar]; + su2double expected; + } cases[] = { + {"zero update", {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, 1.0}, + {"small energy update", {0.0, 0.0, 0.0, 0.0, 1.0, 0.0}, 1.0}, + {"positive energy excess", {0.0, 0.0, 0.0, 0.0, 8.0, 0.0}, 0.25}, + {"negative energy excess", {0.0, 0.0, 0.0, 0.0, -8.0, 0.0}, 0.25}, + {"opposing species updates", {0.4, -0.4, 0.0, 0.0, 0.0, 0.0}, 0.25}, + {"species sets the tighter limit", {0.8, -0.8, 0.0, 0.0, 8.0, 0.0}, 0.125}, + {"energy sets the tighter limit", {0.2, -0.2, 0.0, 0.0, 8.0, 0.0}, 0.25}, + {"momentum and vibrational energy are not limited", {0.0, 0.0, 100.0, -100.0, 0.0, 100.0}, 1.0}, + {"tiny factor cancels the update", {0.0, 0.0, 0.0, 0.0, 1e12, 0.0}, 0.0}, + }; + + for (const auto& test : cases) { + CAPTURE(test.name); + const su2double factor = + CNEMOEulerSolver::ComputeUnderRelaxationFactor(nSpecies, nVar, solution, test.update, allowableRatio); + CHECK(factor == Approx(test.expected).margin(1e-12)); + } +} diff --git a/UnitTests/meson.build b/UnitTests/meson.build index 66917b9782be..b5784d381b39 100644 --- a/UnitTests/meson.build +++ b/UnitTests/meson.build @@ -16,6 +16,7 @@ su2_cfd_tests = files(['Common/geometry/primal_grid/CPrimalGrid_tests.cpp', 'SU2_CFD/numerics/CNumerics_tests.cpp', 'SU2_CFD/fluid/CFluidModel_tests.cpp', 'SU2_CFD/gradients.cpp', + 'SU2_CFD/solvers/CNEMOEulerSolver_tests.cpp', 'SU2_CFD/nemo_viscous_assembly.cpp', 'SU2_CFD/windowing.cpp', 'Common/toolboxes/random_toolbox_tests.cpp', From 872abb1a5acd19b790131b10989e67c0b5c42de9 Mon Sep 17 00:00:00 2001 From: ManasBagul23 <147498197+ManasBagul23@users.noreply.github.com> Date: Thu, 17 Sep 2026 09:16:04 +0530 Subject: [PATCH 57/61] Fix MSVC build error from std::array::begin() passed as raw pointer (#2900) addDoubleArrayOption expects a su2double*, but std::array::begin() returns a checked iterator type under the MSVC STL instead of decaying to a pointer as it effectively does with libstdc++. This broke native Windows builds with MSVC. Use .data() instead, which returns a raw pointer on every implementation. ## Proposed Changes *Give a brief overview of your contribution here in a few sentences.* ## Related Work *Resolve any issues (bug fix or feature request), note any related PRs, or mention interactions with the work of others, if any.* ## PR Checklist *Put an X by all that apply. You can fill this out after submitting the PR. If you have any questions, don't hesitate to ask! We want to help. These are a guide for you to know what the reviewers will be looking for in your contribution.* - [ ] I am submitting my contribution to the develop branch. - [ ] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [ ] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [ ] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [ ] I have added a test case that demonstrates my contribution, if necessary. - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --- Common/src/CConfig.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index 503160942f79..30d0de166045 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -1485,10 +1485,10 @@ void CConfig::SetConfig_Options() { scalars taken directly from MARKER_WALL_SPECIES or the Python wrapper (SetMarkerCustomScalar). \n DEFAULT: FLOW_MARKERS \ingroup Config */ addEnumOption("FLAME_ENTHALPY_BC", flamelet_ParsedOptions.enthalpy_bc, Flamelet_Enthalpy_BC_Map, FLAMELET_ENTHALPY_BC::FLOW_MARKERS); /*!\brief FLAME_INIT \n DESCRIPTION: flame front initialization using the flamelet model \ingroup Config*/ - addDoubleArrayOption("FLAME_INIT", flamelet_ParsedOptions.flame_init.size(), false, flamelet_ParsedOptions.flame_init.begin()); + addDoubleArrayOption("FLAME_INIT", flamelet_ParsedOptions.flame_init.size(), false, flamelet_ParsedOptions.flame_init.data()); /*!\brief SPARK_INIT \n DESCRIPTION: spark initialization using the flamelet model \ingroup Config*/ - addDoubleArrayOption("SPARK_INIT", flamelet_ParsedOptions.spark_init.size(), false, flamelet_ParsedOptions.spark_init.begin()); + addDoubleArrayOption("SPARK_INIT", flamelet_ParsedOptions.spark_init.size(), false, flamelet_ParsedOptions.spark_init.data()); /*!\brief SPARK_REACTION_RATES \n DESCRIPTION: Net source term values applied to species within spark area during spark ignition. \ingroup Config*/ addDoubleListOption("SPARK_REACTION_RATES", flamelet_ParsedOptions.nspark, flamelet_ParsedOptions.spark_reaction_rates); From 426f307457449016508329d17e26badb37e1d818 Mon Sep 17 00:00:00 2001 From: ManasBagul23 <147498197+ManasBagul23@users.noreply.github.com> Date: Thu, 17 Sep 2026 09:16:58 +0530 Subject: [PATCH 58/61] Fix two output bugs in pysu2_nastran (#2901) [skip ci] ## Proposed Changes In `SU2_PY/SU2_Nastran/pysu2_nastran.py`, the mesh summary prints `"Number of points in the moving marker".format(len(...))`. The string has no `{}` placeholder, so the count is computed but never shown. This adds the missing placeholder. Before: `Number of points in the moving marker` After: `Number of points in the moving marker: 123` Found with pyflakes (`'...'.format(...) has unused arguments at position(s): 0`). The exception for an unknown imposed motion type referenced `self.tipo`, which does not exist, so it raised `AttributeError: 'ImposedMotionClass' object has no attribute 'tipo'` instead of the intended message. It now uses `self.typeOfMotion`. ## Related Work None. This does not touch the SET1 parsing discussed in #2313. ## PR Checklist - [x] I am submitting my contribution to the develop branch. - [x] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [x] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [x] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [ ] I have added a test case that demonstrates my contribution, if necessary. - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary --- SU2_PY/SU2_Nastran/pysu2_nastran.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SU2_PY/SU2_Nastran/pysu2_nastran.py b/SU2_PY/SU2_Nastran/pysu2_nastran.py index 8a65a0f296d0..d045550e016e 100644 --- a/SU2_PY/SU2_Nastran/pysu2_nastran.py +++ b/SU2_PY/SU2_Nastran/pysu2_nastran.py @@ -90,7 +90,7 @@ def __init__(self, time0, typeOfMotion, parameters, mode): else: raise Exception( "Imposed function {} not found, please implement it in pysu2_nastran.py".format( - self.tipo + self.typeOfMotion ) ) @@ -579,7 +579,7 @@ def __readNastranMesh(self): print("Number of reference systems: {}".format(self.nRefSys)) print("Moving marker: {}".format(self.FSI_marker)) print( - "Number of points in the moving marker".format( + "Number of points in the moving marker: {}".format( len(self.markers[self.FSI_marker]) ) ) From 4ea31da26c0087567f08c03afa6ad0101c9c9b1a Mon Sep 17 00:00:00 2001 From: ManasBagul23 <147498197+ManasBagul23@users.noreply.github.com> Date: Thu, 17 Sep 2026 09:31:16 +0530 Subject: [PATCH 59/61] Add missing adjoint suffixes to get_adjointSuffix (#2909) [skip ci] ## Proposed Changes Eleven objectives that `CConfig::GetObjFunc_Extension` gives an adjoint file suffix were missing from `get_adjointSuffix` in `SU2_PY/SU2/io/tools.py`, so the Python scripts stop with `Unrecognized adjoint function name` when one of them is used: `INVERSE_DESIGN_HEATFLUX`, `AVG_TEMPERATURE`, `SURFACE_STATIC_TEMPERATURE`, `SURFACE_SPECIES_0`, `SURFACE_SPECIES_VARIANCE`, `REFERENCE_GEOMETRY`, `REFERENCE_NODE`, `VOLUME_FRACTION`, `TOPOL_DISCRETENESS`, `TOPOL_COMPLIANCE`, `STRESS_PENALTY` `INVERSE_DESIGN_HEATFLUX` was listed as `INVERSE_DESIGN_HEAT`, which is not an objective name in SU2, so that entry is renamed. The suffixes are the ones from `GetObjFunc_Extension`. After this change every objective with a suffix in CConfig is found by `get_adjointSuffix`. ## Related Work Complements #2907, which corrects the suffixes of `TOTAL_PRESSURE_LOSS` and `KINETIC_ENERGY_LOSS`. The two PRs do not conflict. ## PR Checklist - [x] I am submitting my contribution to the develop branch. - [x] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [x] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [x] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [ ] I have added a test case that demonstrates my contribution, if necessary. - [ ] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --- SU2_PY/SU2/io/tools.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/SU2_PY/SU2/io/tools.py b/SU2_PY/SU2/io/tools.py index 10983e6852b5..aaeccf9433fc 100755 --- a/SU2_PY/SU2/io/tools.py +++ b/SU2_PY/SU2/io/tools.py @@ -486,7 +486,7 @@ def get_adjointSuffix(objective_function=None): "FORCE_Z": "cfz", "EFFICIENCY": "eff", "INVERSE_DESIGN_PRESSURE": "invpress", - "INVERSE_DESIGN_HEAT": "invheat", + "INVERSE_DESIGN_HEATFLUX": "invheat", "MAXIMUM_HEATFLUX": "maxheat", "TOTAL_HEATFLUX": "totheat", "EQUIVALENT_AREA": "ea", @@ -499,6 +499,10 @@ def get_adjointSuffix(objective_function=None): "SURFACE_STATIC_PRESSURE": "pe", "SURFACE_MASSFLOW": "mfr", "SURFACE_MACH": "mach", + "SURFACE_STATIC_TEMPERATURE": "T", + "AVG_TEMPERATURE": "avtp", + "SURFACE_SPECIES_0": "avgspec0", + "SURFACE_SPECIES_VARIANCE": "specvar", "SURFACE_UNIFORMITY": "uniform", "SURFACE_SECONDARY": "second", "SURFACE_MOM_DISTORTION": "distort", @@ -515,6 +519,12 @@ def get_adjointSuffix(objective_function=None): "MASS_FLOW_IN": "mfi", "TOTAL_EFFICIENCY": "teff", "TOTAL_STATIC_EFFICIENCY": "tseff", + "REFERENCE_GEOMETRY": "refgeom", + "REFERENCE_NODE": "refnode", + "VOLUME_FRACTION": "volfrac", + "TOPOL_DISCRETENESS": "topdisc", + "TOPOL_COMPLIANCE": "topcomp", + "STRESS_PENALTY": "stress", "COMBO": "combo", } From c6aa31cd0103eada32c786d6220697bc654c3a3b Mon Sep 17 00:00:00 2001 From: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com> Date: Thu, 17 Sep 2026 21:04:20 -0700 Subject: [PATCH 60/61] Modernize scalar numerics (#2878) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Proposed Changes Apply the design of the SIMD numerics to scalars (but without vectorization because it does not pay off for scalars). Performance is still about 2 times better. Every transported-scalar equation (SA, SST, LM, species, flamelet, heat, pressure correction) now gets its convective and diffusive flux from a **single statically-sized inline kernel**, used by both the interior edge loop and the boundary conditions. The old `CNumerics`-based scalar convection/diffusion classes are deleted, along with their `CDriver` registration. Deleted: `scalar_convection.hpp`, `scalar_diffusion.hpp`, `turb_convection.hpp`, `turb_diffusion.hpp`, `trans_convection.hpp`, `trans_diffusion.hpp`, `species_convection.hpp`, `species_diffusion.hpp`, `heat.hpp`. Added: `numerics/util.hpp`, `numerics/scalar/scalar_edge_flux.hpp` and one `*_edge_flux.hpp` per model. ### Key points of the architecture **One class per model, a three-layer CRTP chain.** Convection and diffusion are not separate objects; they accumulate into one residual under one preaccumulation region. | Layer | Written | Holds | | --- | --- | --- | | `CUpwScalarBase` | once | upwinding weights, MUSCL reconstruction, preaccumulation region, both `ComputeFlux` overloads | | `CUpwScalarFlux` / `CAvgGradScalarBase` | once | the generic convective expression; the edge geometry, projected gradient, contraction with the coefficient matrix and the TSL Jacobians | | Model (`CScalarFlux_SA`, `_SST`, `_TransLM`, `_Species`, `_Flamelet`, `_Heat`, `_Poisson`) | per model | `Conservative`, `DiagonalDiffusion`, and one `coefficients()` function | **Runtime flags, not separate instantiations.** `ScalarFluxOptions` carries `convective`, `viscous`, `oneSided`, `muscl`, `implicit`, `boundedScalar`, `dynamicGrid`, `correctGradient` and `accurateJacobians`. They are loop-invariant and unswitched by the compiler. They are built through named constructors (`Interior`, `BoundaryConvective`, `BoundaryFull`, `BoundaryDiffusive`) so that no flag is passed positionally. A boundary clears `viscous`; a solid zone (heat) and the pressure correction clear `convective`. **Boundaries use the same kernel through ghost containers.** `EdgeSide` locates one endpoint (scalar nodes, flow nodes, coordinates, grid velocity). The interior loop binds one `EdgeSide` as both endpoints; a boundary binds the solver's containers for `i` and per-marker ghost containers for `j` — a `CGhostFlowVariable` (same primitive layout as the flow solver) plus an instance of the solver's own `VariableType`, indexed by vertex. Each BC is a fill pass followed by the shared flux pass; there is no boundary special case inside any kernel. **Non-conservative diffusion is supported.** `EdgeResidual` carries two independent row contributions (`flux_i`, `flux_j`) and four Jacobian blocks (`ii`, `ij`, `ji`, `jj`). `CSysMatrix` gains a four-block `SetBlocks` and a `SetOffDiagBlocks`, both quantization-aware, covered by a new unit test (`UnitTests/Common/linear_algebra/edge_residual_blocks_tests.cpp`). **The value type is a template parameter.** `numerics_simd/util.hpp` was promoted to `numerics/util.hpp` with its value and index types parameterized (`CLaneTraits`, `CValueTraits`) and its alignment behind a trait; the MUSCL helpers moved with it and are now shared by the flow and scalar paths. The solvers bind `su2double` today; a SIMD binding would be the same code with `simd::Array`. **`nVar` is a template parameter, or `Dynamic`.** Species and flamelet pass `Dynamic`: the static arrays are backed by `MaxScalarVar`, the loops run to a count read from the solver, and reconstruction takes a runtime width. **Shared loops in `CScalarSolver`:** `EdgeFluxResidual` (both the coloring and the reducer strategy, one loop for both terms — the solvers no longer have a `Viscous_Residual`), `BoundaryFluxResidual`, and `FluidInterfaceFluxResidual`. ### Adding a new scalar model 1. **Write the flux class** in `numerics/.../_edge_flux.hpp`: ```cpp template class CScalarFlux_Foo final : public CUpwScalarBase, FlowIndices, nDim, nVar> { public: static constexpr bool Conservative = true; // density-weighted transport static constexpr bool DiagonalDiffusion = true; // vector of coefficients instead of a matrix using Base = CUpwScalarBase; using Base::Base; template FORCEINLINE CPair> coefficients(const FlowIndices& idx, Int iPoint, const EdgeSide& side_i, Int jPoint, const EdgeSide& side_j, const CPair& rho) const { ... } }; ``` That is the whole model in the common case. The optional hooks, each inherited empty or generic, are: - `finalizeFlux` — only if the convective term is not `a0*w_i*phi_i + a1*w_j*phi_j` (SA overrides it for the backscatter equations). - `coefficientJacobians` — extra Jacobian terms when the coefficients depend on the transported variables (SA, SST). - `extraDiffusionTerms` — extra gradients of synthesised states (flamelet preferential diffusion). - `DiffusionReadsDensity = true` — if the coefficients need the density and the model is not `Conservative`; otherwise the density is never gathered. 2. **Dispatch from the solver.** `CScalarSolver::DispatchScheme(config, f)` resolves the flow indices (compressible/incompressible), `nDim` and the equation count, and calls a generic lambda with a type tag. There is nothing else to touch: no `CDriver` registration and no `CNumerics` allocation. ```cpp const auto opt = ScalarFluxOptions::Interior(*config, /*bounded*/ false); DispatchScheme(config, [&](auto tag) { EdgeFluxResidual(geometry, solver_container, config, opt); }); ``` Use `Dynamic` in the `nVarList` for a runtime equation count, and list several counts (SA uses `1, 4`) to instantiate variants. 3. **Boundaries.** Write the fill pass (ghost solution, `SetGhostPrimitives`, `SetGhostGeometry`, `SetGhostDiffusionState` for the diffusion sites, `ghostSkip` for vertices that contribute nothing), then call `BoundaryFluxResidual` through the same `DispatchScheme`. Most boundaries of the `CScalarSolver` family are already generic and need no per-model code. The instantiation cost is `regime x nDim x |nVarList|` per model. ### Behavior changes and fixes - NEMO with a turbulence model is now rejected at configuration; the dead NEMO scalar-numerics branches and instantiations are gone. - SST and species had no `BC_Far_Field` of their own (crash on `MARKER_FAR`); it is now inherited from `CScalarSolver`. - `BC_Inlet_MixingPlane` / `BC_Inlet_Turbo` never applied the bounded-scalar mass-flux correction. - The heat solver gained far-field and fluid-interface boundaries, and its diffusion coefficients are now visible to AD. - SA-neg keeps its `fn`-corrected diffusion coefficient and frozen Jacobians; exact turbulent Jacobians stay behind `USE_ACCURATE_TURB_JACOBIANS`. - The unused `CAvgGrad_Species` `visc_bound_term` allocation is deleted. ### Validation Last-digit changes are expected: the two terms now accumulate in one `EdgeResidual` and each Jacobian block is cast to `su2mixedfloat` once. The reference values were refreshed from CI across the serial, parallel and hybrid suites, the AD suites, `tutorials.py` and `vandv.py`; a flamelet CFD case was added to `hybrid_regression.py`. ## PR Checklist - [X] I am submitting my contribution to the develop branch. - [X] My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson). - [X] My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/). - [X] I used the pre-commit hook to prevent dirty commits and used `pre-commit run --all` to format old commits. - [X] I have added a test case that demonstrates my contribution, if necessary. - [X] I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary. --------- Co-authored-by: Claude Sonnet 5 --- .../containers/container_decorators.hpp | 27 + Common/include/linear_algebra/CSysMatrix.hpp | 74 ++ Common/src/CConfig.cpp | 4 + SU2_CFD/include/numerics/heat.hpp | 107 --- SU2_CFD/include/numerics/heat_edge_flux.hpp | 89 ++ .../include/numerics/poisson_edge_flux.hpp | 78 ++ .../numerics/scalar/scalar_convection.hpp | 150 ---- .../numerics/scalar/scalar_diffusion.hpp | 155 ---- .../numerics/scalar/scalar_edge_flux.hpp | 504 ++++++++++++ .../numerics/species/flamelet_edge_flux.hpp | 172 ++++ .../numerics/species/species_convection.hpp | 85 -- .../numerics/species/species_diffusion.hpp | 110 --- .../numerics/species/species_edge_flux.hpp | 113 +++ .../turbulent/transition/trans_convection.hpp | 40 - .../turbulent/transition/trans_diffusion.hpp | 105 --- .../turbulent/transition/trans_edge_flux.hpp | 78 ++ .../numerics/turbulent/turb_convection.hpp | 141 ---- .../numerics/turbulent/turb_diffusion.hpp | 346 -------- .../numerics/turbulent/turb_sa_edge_flux.hpp | 183 +++++ .../numerics/turbulent/turb_sources.hpp | 2 +- .../numerics/turbulent/turb_sst_edge_flux.hpp | 147 ++++ SU2_CFD/include/numerics/util.hpp | 638 +++++++++++++++ .../include/numerics_simd/CNumericsSIMD.hpp | 18 +- .../numerics_simd/flow/convection/common.hpp | 155 +--- SU2_CFD/include/numerics_simd/util.hpp | 250 +----- SU2_CFD/include/solvers/CHeatSolver.hpp | 81 +- SU2_CFD/include/solvers/CPoissonSolver.hpp | 32 +- SU2_CFD/include/solvers/CScalarSolver.hpp | 344 ++++---- SU2_CFD/include/solvers/CScalarSolver.inl | 383 +++++---- .../solvers/CSpeciesFlameletSolver.hpp | 16 +- SU2_CFD/include/solvers/CSpeciesSolver.hpp | 57 +- SU2_CFD/include/solvers/CTransLMSolver.hpp | 52 +- SU2_CFD/include/solvers/CTurbSASolver.hpp | 49 +- SU2_CFD/include/solvers/CTurbSSTSolver.hpp | 60 +- SU2_CFD/include/solvers/CTurbSolver.hpp | 3 +- SU2_CFD/include/variables/CFlowVariable.hpp | 7 + .../include/variables/CGhostFlowVariable.hpp | 52 ++ .../include/variables/CPoissonVariable.hpp | 9 +- .../include/variables/CSpeciesVariable.hpp | 5 + .../include/variables/CTurbSSTVariable.hpp | 10 + SU2_CFD/include/variables/CTurbVariable.hpp | 17 + SU2_CFD/include/variables/CVariable.hpp | 7 + SU2_CFD/src/drivers/CDriver.cpp | 179 +--- SU2_CFD/src/meson.build | 3 + SU2_CFD/src/solvers/CHeatSolver.cpp | 207 +++-- SU2_CFD/src/solvers/CPoissonSolver.cpp | 78 +- SU2_CFD/src/solvers/CSolverFactory.cpp | 12 +- .../src/solvers/CSpeciesFlameletSolver.cpp | 223 +---- SU2_CFD/src/solvers/CSpeciesSolver.cpp | 253 +++--- SU2_CFD/src/solvers/CTransLMSolver.cpp | 198 +++-- SU2_CFD/src/solvers/CTurbSASolver.cpp | 770 +++++------------- SU2_CFD/src/solvers/CTurbSSTSolver.cpp | 504 ++++-------- SU2_CFD/src/solvers/CTurbSolver.cpp | 4 +- SU2_CFD/src/variables/CVariable.cpp | 20 + TestCases/hybrid_regression.py | 59 +- TestCases/hybrid_regression_AD.py | 8 +- TestCases/parallel_regression.py | 30 +- TestCases/parallel_regression_AD.py | 4 +- TestCases/serial_regression.py | 32 +- TestCases/tutorials.py | 8 +- TestCases/vandv.py | 8 +- .../edge_residual_blocks_tests.cpp | 167 ++++ UnitTests/meson.build | 6 +- 63 files changed, 3823 insertions(+), 3905 deletions(-) delete mode 100644 SU2_CFD/include/numerics/heat.hpp create mode 100644 SU2_CFD/include/numerics/heat_edge_flux.hpp create mode 100644 SU2_CFD/include/numerics/poisson_edge_flux.hpp delete mode 100644 SU2_CFD/include/numerics/scalar/scalar_convection.hpp delete mode 100644 SU2_CFD/include/numerics/scalar/scalar_diffusion.hpp create mode 100644 SU2_CFD/include/numerics/scalar/scalar_edge_flux.hpp create mode 100644 SU2_CFD/include/numerics/species/flamelet_edge_flux.hpp delete mode 100644 SU2_CFD/include/numerics/species/species_convection.hpp delete mode 100644 SU2_CFD/include/numerics/species/species_diffusion.hpp create mode 100644 SU2_CFD/include/numerics/species/species_edge_flux.hpp delete mode 100644 SU2_CFD/include/numerics/turbulent/transition/trans_convection.hpp delete mode 100644 SU2_CFD/include/numerics/turbulent/transition/trans_diffusion.hpp create mode 100644 SU2_CFD/include/numerics/turbulent/transition/trans_edge_flux.hpp delete mode 100644 SU2_CFD/include/numerics/turbulent/turb_convection.hpp delete mode 100644 SU2_CFD/include/numerics/turbulent/turb_diffusion.hpp create mode 100644 SU2_CFD/include/numerics/turbulent/turb_sa_edge_flux.hpp create mode 100644 SU2_CFD/include/numerics/turbulent/turb_sst_edge_flux.hpp create mode 100644 SU2_CFD/include/numerics/util.hpp create mode 100644 SU2_CFD/include/variables/CGhostFlowVariable.hpp create mode 100644 UnitTests/Common/linear_algebra/edge_residual_blocks_tests.cpp diff --git a/Common/include/containers/container_decorators.hpp b/Common/include/containers/container_decorators.hpp index a9e66857eb33..331e87b9a0f4 100644 --- a/Common/include/containers/container_decorators.hpp +++ b/Common/include/containers/container_decorators.hpp @@ -62,6 +62,33 @@ class CMatrixView { const Scalar* operator[](Index i) const noexcept { return &m_ptr[i * m_cols]; } const Scalar& operator()(Index i, Index j) const noexcept { return m_ptr[i * m_cols + j]; } + /*! + * \brief Return copy of data in a static size container (see C2DContainer::get). + * \param[in] i - Row of the view (e.g. point index, whole-mesh usage). + * \param[in] start - Starting column to copy the data (amount determined by container size). + */ + template + StaticContainer get(Index i, Index start = 0) const noexcept { + constexpr size_t Size = StaticContainer::StaticSize; + static_assert(Size, "This method requires a static output type."); + StaticContainer ret; + for (size_t k = 0; k < Size; ++k) ret.data()[k] = m_ptr[i * m_cols + start + k]; + return ret; + } + + /*! + * \brief SIMD gather version of get, one row per lane. + */ + template + StaticContainer get(simd::Array i, Index start = 0) const noexcept { + constexpr size_t Size = StaticContainer::StaticSize; + static_assert(Size, "This method requires a static output type."); + StaticContainer ret; + for (size_t lane = 0; lane < N; ++lane) + for (size_t k = 0; k < Size; ++k) ret.data()[k][lane] = m_ptr[i[lane] * m_cols + start + k]; + return ret; + } + template ::value> = 0> Scalar* operator[](Index i) noexcept { return &m_ptr[i * m_cols]; diff --git a/Common/include/linear_algebra/CSysMatrix.hpp b/Common/include/linear_algebra/CSysMatrix.hpp index c835bacefde9..70dd943690ec 100644 --- a/Common/include/linear_algebra/CSysMatrix.hpp +++ b/Common/include/linear_algebra/CSysMatrix.hpp @@ -1069,6 +1069,80 @@ class CSysMatrix { SetBlocks(iEdge, block_i, block_j, -1); } + /*! + * \brief Set the four blocks of an edge, for fluxes whose i and j contributions are independent. + * \note The diagonal blocks are accumulated, the off-diagonal blocks are set. + */ + template + inline void SetBlocks(unsigned long iEdge, unsigned long iPoint, unsigned long jPoint, const MatrixType& jac_ii, + const MatrixType& jac_ij, const MatrixType& jac_ji, const MatrixType& jac_jj, + OtherType mask = 1) { + const auto blkSz = nVar * nEqn; + auto* bii = &mat.d[iPoint * blkSz]; + auto* bjj = &mat.d[jPoint * blkSz]; + unsigned long iVar, jVar, offset = 0; + + if (quantized_mode) { + ScalarType bij_buf[MAXNVAR * MAXNVAR], bji_buf[MAXNVAR * MAXNVAR]; + for (iVar = 0; iVar < nVar; iVar++) + for (jVar = 0; jVar < nEqn; jVar++, ++offset) { + bii[offset] += PassiveAssign(jac_ii[iVar][jVar] * mask); + bjj[offset] += PassiveAssign(jac_jj[iVar][jVar] * mask); + bij_buf[offset] = PassiveAssign(jac_ij[iVar][jVar] * mask); + bji_buf[offset] = PassiveAssign(jac_ji[iVar][jVar] * mask); + } + QuantizeBlock(bij_buf, &q_scale.u[iEdge * nVar], &q_blocks.u[iEdge * blkSz]); + const auto k_l = edge_ptr_l[iEdge]; + QuantizeBlock(bji_buf, &q_scale.l[k_l * nVar], &q_blocks.l[k_l * blkSz]); + return; + } + + auto* bij = &mat.u[iEdge * blkSz]; + auto* bji = &mat.l[edge_ptr_l[iEdge] * blkSz]; + for (iVar = 0; iVar < nVar; iVar++) { + for (jVar = 0; jVar < nEqn; jVar++) { + bii[offset] += PassiveAssign(jac_ii[iVar][jVar] * mask); + bjj[offset] += PassiveAssign(jac_jj[iVar][jVar] * mask); + bij[offset] = PassiveAssign(jac_ij[iVar][jVar] * mask); + bji[offset] = PassiveAssign(jac_ji[iVar][jVar] * mask); + ++offset; + } + } + } + + /*! + * \brief Set the off-diagonal blocks of an edge, the diagonal being assembled elsewhere. + */ + template + inline void SetOffDiagBlocks(unsigned long iEdge, const MatrixType& jac_ij, const MatrixType& jac_ji, + OtherType mask = 1) { + const auto blkSz = nVar * nEqn; + unsigned long iVar, jVar, offset = 0; + + if (quantized_mode) { + ScalarType bij_buf[MAXNVAR * MAXNVAR], bji_buf[MAXNVAR * MAXNVAR]; + for (iVar = 0; iVar < nVar; iVar++) + for (jVar = 0; jVar < nEqn; jVar++, ++offset) { + bij_buf[offset] = PassiveAssign(jac_ij[iVar][jVar] * mask); + bji_buf[offset] = PassiveAssign(jac_ji[iVar][jVar] * mask); + } + QuantizeBlock(bij_buf, &q_scale.u[iEdge * nVar], &q_blocks.u[iEdge * blkSz]); + const auto k_l = edge_ptr_l[iEdge]; + QuantizeBlock(bji_buf, &q_scale.l[k_l * nVar], &q_blocks.l[k_l * blkSz]); + return; + } + + auto* bij = &mat.u[iEdge * blkSz]; + auto* bji = &mat.l[edge_ptr_l[iEdge] * blkSz]; + for (iVar = 0; iVar < nVar; iVar++) { + for (jVar = 0; jVar < nEqn; jVar++) { + bij[offset] = PassiveAssign(jac_ij[iVar][jVar] * mask); + bji[offset] = PassiveAssign(jac_ji[iVar][jVar] * mask); + ++offset; + } + } + } + /*! * \brief SIMD version, does the update for multiple edges. * \note Nothing is updated if the mask is 0. diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index 30d0de166045..d5c403d9ab11 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -4445,6 +4445,10 @@ void CConfig::SetPostprocessing(SU2_COMPONENT val_software, unsigned short val_i } /* --- Check for NEMO compatibility issues ---*/ + if (nemo && Kind_Turb_Model != TURB_MODEL::NONE) { + SU2_MPI::Error("A turbulence model is not yet available for the NEMO solver.", CURRENT_FUNCTION); + } + if (Kind_FluidModel == SU2_NONEQ && (Kind_TransCoeffModel != TRANSCOEFFMODEL::WILKE && Kind_TransCoeffModel != TRANSCOEFFMODEL::SUTHERLAND && Kind_TransCoeffModel != TRANSCOEFFMODEL::GUPTAYOS) ) { SU2_MPI::Error("Transport model not available for NEMO solver using SU2TCLIB. Please use the WILKE, SUTHERLAND or GUPTAYOS transport model instead.", CURRENT_FUNCTION); } diff --git a/SU2_CFD/include/numerics/heat.hpp b/SU2_CFD/include/numerics/heat.hpp deleted file mode 100644 index 1e9cbf815052..000000000000 --- a/SU2_CFD/include/numerics/heat.hpp +++ /dev/null @@ -1,107 +0,0 @@ -/*! - * \file heat.hpp - * \brief Declarations of numerics classes for heat transfer problems. - * \author F. Palacios, T. Economon - * \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 . - */ - -#pragma once - -#include "scalar/scalar_diffusion.hpp" -#include "scalar/scalar_convection.hpp" -#include "../variables/CIncEulerVariable.hpp" - -/*! - * \class CUpwSca_Heat - * \brief Class for doing a scalar upwind solver for the heat convection equation. - * \ingroup ConvDiscr - * \author O. Burghardt. - * \version 8.5.0 "Harrier" - */ -class CUpwSca_Heat final : public CUpwScalar> { - public: - /*! - * \brief Constructor of the class. - * \param[in] val_nDim - Number of dimensions of the problem. - * \param[in] config - Definition of the particular problem. - */ - CUpwSca_Heat(unsigned short val_nDim, const CConfig *config) - : CUpwScalar>(val_nDim, 1, config) {} - - private: - /*! - * \brief Adds extra variables to AD - */ - void ExtraADPreaccIn(void) override {} - - /*! - * \brief Heat-specific specific steps in the ComputeResidual method - * \param[in] config - Definition of the particular problem. - */ - void FinishResidualCalc(const CConfig* config) override { - Flux[0] = a0 * ScalarVar_i[0] + a1 * ScalarVar_j[0]; - Jacobian_i[0][0] = a0; - Jacobian_j[0][0] = a1; - } -}; - -/*! - * \class CAvgGrad_Heat - * \brief Class for computing viscous term using average of gradients without correction (heat equation). - * \ingroup ViscDiscr - * \author O. Burghardt. - * \version 8.5.0 "Harrier" - */ -class CAvgGrad_Heat final : public CAvgGrad_Scalar { - public: - /*! - * \brief Constructor of the class. - * \param[in] val_nDim - Number of dimensions of the problem. - * \param[in] config - Definition of the particular problem. - * \param[in] correct - Whether to correct the gradient. - */ - CAvgGrad_Heat(unsigned short val_nDim, const CConfig *config, bool correct) - : CAvgGrad_Scalar(val_nDim, 1, correct, config) {} - - private: - /*! - * \brief Adds extra variables to AD - */ - void ExtraADPreaccIn(void) override { - AD::SetPreaccIn(*Diffusion_Coeff_i, *Diffusion_Coeff_j); - } - - /*! - * \brief Heat-specific specific steps in the ComputeResidual method - * \param[in] config - Definition of the particular problem. - */ - void FinishResidualCalc(const CConfig* config) override { - const su2double Thermal_Diffusivity_Mean = 0.5 * (*Diffusion_Coeff_i + *Diffusion_Coeff_j); - - Flux[0] = Thermal_Diffusivity_Mean * Proj_Mean_GradScalarVar[0]; - - /*--- Use TSL for Jacobians. ---*/ - Jacobian_i[0][0] = -Thermal_Diffusivity_Mean * proj_vector_ij; - Jacobian_j[0][0] = Thermal_Diffusivity_Mean * proj_vector_ij; - } -}; diff --git a/SU2_CFD/include/numerics/heat_edge_flux.hpp b/SU2_CFD/include/numerics/heat_edge_flux.hpp new file mode 100644 index 000000000000..213d168d97f2 --- /dev/null +++ b/SU2_CFD/include/numerics/heat_edge_flux.hpp @@ -0,0 +1,89 @@ +/*! + * \file heat_edge_flux.hpp + * \brief Heat transport as a third-layer scalar flux, see numerics/scalar/scalar_edge_flux.hpp. + * \author P. Gomes + * \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 . + */ + +#pragma once + +#include "scalar/scalar_edge_flux.hpp" + +/*! + * \class CScalarFlux_Heat + * \ingroup ViscDiscr + * \brief Convection and diffusion of temperature, non-conservative, with a diagonal + * (single-equation) diffusion coefficient. + * \note The temperature has no notion of density weighting, so Conservative is false and the + * inherited CUpwScalarFlux::finalizeFlux, flux(0) = a0*phi_i(0) + a1*phi_j(0), is exactly + * the model's whole convective term; no override is needed. + * \note The solver runs in two modes, weakly-coupled energy equation on a fluid zone or standalone + * conduction on a solid one (CHeatSolver::flow); the diffusion coefficient is the flow's + * thermal conductivity over specific heat, plus a turbulent contribution, in the former, and + * the configured constant thermal diffusivity in the latter. EdgeSide::flowNodes is null in + * the solid case, so this is the only place that may read it, and only when flow is set. + */ +template +class CScalarFlux_Heat final + : public CUpwScalarBase, FlowIndices, nDim, nVar> { + public: + static constexpr bool Conservative = false; + static constexpr bool DiagonalDiffusion = true; + + using Base = CUpwScalarBase; + using Int = typename Base::Int; + + explicit CScalarFlux_Heat(const CConfig& config) + : Base(config), + flow(config.GetFluidProblem()), + prandtlTurb(config.GetPrandtl_Turb()), + constDiffusivity(config.GetThermalDiffusivity()) {} + + /*! + * \brief Thermal diffusivity, an i/j average, identical for both edge sides (TSL Jacobian). + */ + template + FORCEINLINE CPair> coefficients(const FlowIndices& idx, Int iPoint, + const EdgeSide& side_i, Int jPoint, + const EdgeSide& side_j, + const CPair&) const { + Vector D; + if (flow) { + const Double k_i = gatherVariables(iPoint, side_i.flowNodes->GetPrimitive(), idx.ThermalConductivity()); + const Double cp_i = gatherVariables(iPoint, side_i.flowNodes->GetPrimitive(), idx.CpTotal()); + const Double muT_i = gatherVariables(iPoint, side_i.flowNodes->GetPrimitive(), idx.EddyViscosity()); + const Double k_j = gatherVariables(jPoint, side_j.flowNodes->GetPrimitive(), idx.ThermalConductivity()); + const Double cp_j = gatherVariables(jPoint, side_j.flowNodes->GetPrimitive(), idx.CpTotal()); + const Double muT_j = gatherVariables(jPoint, side_j.flowNodes->GetPrimitive(), idx.EddyViscosity()); + D(0) = 0.5 * (k_i / cp_i + muT_i / prandtlTurb + k_j / cp_j + muT_j / prandtlTurb); + } else { + D(0) = constDiffusivity; + } + return {D, D}; + } + + private: + const bool flow; + const su2double prandtlTurb; + const su2double constDiffusivity; +}; diff --git a/SU2_CFD/include/numerics/poisson_edge_flux.hpp b/SU2_CFD/include/numerics/poisson_edge_flux.hpp new file mode 100644 index 000000000000..69aca80d5a68 --- /dev/null +++ b/SU2_CFD/include/numerics/poisson_edge_flux.hpp @@ -0,0 +1,78 @@ +/*! + * \file poisson_edge_flux.hpp + * \brief Pressure correction (Poisson) equation as a third-layer scalar flux, + * see numerics/scalar/scalar_edge_flux.hpp. + * \author T. Aalbers, P. Gomes + * \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 . + */ + +#pragma once + +#include "scalar/scalar_edge_flux.hpp" + +/*! + * \class CScalarFlux_Poisson + * \ingroup ViscDiscr + * \brief Diffusion of the pressure correction, with a diagonal (single-equation) coefficient. + * \note The equation has no convective term at all, so the solver runs this kernel with + * ScalarFluxOptions::convective cleared and the inherited CUpwScalarFlux::finalizeFlux + * is never reached; see CPoissonSolver::Viscous_Residual. + * \note The diffusion coefficient is the momentum coefficient vol/A_p carried by the solver's + * own variables, not a flow primitive, so nothing here reads the flow's primitive row; + * the flow variables are only asked which points carry a strong velocity BC. + */ +template +class CScalarFlux_Poisson final + : public CUpwScalarBase, FlowIndices, nDim, nVar> { + public: + static constexpr bool Conservative = false; + static constexpr bool DiagonalDiffusion = true; + + using Base = CUpwScalarBase; + using Int = typename Base::Int; + + using Base::Base; + + /*! + * \brief Momentum coefficient, an i/j average, identical for both edge sides (TSL Jacobian). + * \note A point under a strong velocity BC has no momentum equation, and so no momentum + * coefficient of its own; the edge uses that of its other node instead. + */ + template + FORCEINLINE CPair> coefficients(const FlowIndices&, Int iPoint, + const EdgeSide& side_i, Int jPoint, + const EdgeSide& side_j, + const CPair&) const { + const bool strong_i = side_i.flowNodes->GetStrongBC(iPoint); + const bool strong_j = side_j.flowNodes->GetStrongBC(jPoint); + + const Double coeff_i = strong_i ? gatherVariables(jPoint, side_j.scalarNodes.GetMomCoeff()) + : gatherVariables(iPoint, side_i.scalarNodes.GetMomCoeff()); + const Double coeff_j = strong_j ? gatherVariables(iPoint, side_i.scalarNodes.GetMomCoeff()) + : gatherVariables(jPoint, side_j.scalarNodes.GetMomCoeff()); + + Vector D; + D(0) = 0.5 * (coeff_i + coeff_j); + return {D, D}; + } +}; diff --git a/SU2_CFD/include/numerics/scalar/scalar_convection.hpp b/SU2_CFD/include/numerics/scalar/scalar_convection.hpp deleted file mode 100644 index e40749a5c1d3..000000000000 --- a/SU2_CFD/include/numerics/scalar/scalar_convection.hpp +++ /dev/null @@ -1,150 +0,0 @@ -/*! - * \file scalar_convection.hpp - * \brief Declarations of numerics classes for discretization of - * convective fluxes in scalar problems. - * \author F. Palacios, T. Economon - * \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 . - */ - -#pragma once - -#include "../CNumerics.hpp" - -/*! - * \class CUpwScalar - * \brief Template class for scalar upwind fluxes between nodes i and j. - * \details This class serves as a template for the scalar upwinding residual - * classes. The general structure of a scalar upwinding calculation is the - * same for many different models, which leads to a lot of repeated code. - * By using the template design pattern, these sections of repeated code are - * moved to this shared base class, and the specifics of each model - * are implemented by derived classes. In order to add a new residual - * calculation for a convection residual, extend this class and implement - * the pure virtual functions with model-specific behavior. - * \ingroup ConvDiscr - * \author C. Pederson, A. Bueno., and A. Campos. - */ -template -class CUpwScalar : public CNumerics { - protected: - enum : unsigned short {MAXNVAR = 8}; - - const FlowIndices idx; /*!< \brief Object to manage the access to the flow primitives. */ - su2double a0 = 0.0; /*!< \brief The maximum of the face-normal velocity and 0. */ - su2double a1 = 0.0; /*!< \brief The minimum of the face-normal velocity and 0. */ - su2double Flux[MAXNVAR]; /*!< \brief Final result, diffusive flux/residual. */ - su2double* Jacobian_i[MAXNVAR]; /*!< \brief Flux Jacobian w.r.t. node i. */ - su2double* Jacobian_j[MAXNVAR]; /*!< \brief Flux Jacobian w.r.t. node j. */ - su2double JacobianBuffer[2*MAXNVAR*MAXNVAR]; /*!< \brief Static storage for the two Jacobians. */ - - const bool incompressible = false, dynamic_grid = false; - - /*! - * \brief A pure virtual function. Derived classes must use it to register the additional - * variables they use as preaccumulation inputs, e.g. the density for SST. - */ - virtual void ExtraADPreaccIn() = 0; - - /*! - * \brief Model-specific steps in the ComputeResidual method, derived classes - * compute the Flux and its Jacobians via this method. - * \param[in] config - Definition of the particular problem. - */ - virtual void FinishResidualCalc(const CConfig* config) = 0; - - public: - /*! - * \brief Constructor of the class. - * \param[in] ndim - Number of dimensions of the problem. - * \param[in] nvar - Number of variables of the problem. - * \param[in] config - Definition of the particular problem. - */ - CUpwScalar(unsigned short ndim, unsigned short nvar, const CConfig* config) - : CNumerics(ndim, nvar, config), - idx(ndim, config->GetnSpecies()), - incompressible(config->GetKind_Regime() == ENUM_REGIME::INCOMPRESSIBLE), - dynamic_grid(config->GetDynamic_Grid()) { - if (nVar > MAXNVAR) { - SU2_MPI::Error("Static arrays are too small.", CURRENT_FUNCTION); - } - for (unsigned short iVar = 0; iVar < nVar; iVar++) { - Jacobian_i[iVar] = &JacobianBuffer[iVar * nVar]; - Jacobian_j[iVar] = &JacobianBuffer[iVar * nVar + MAXNVAR * MAXNVAR]; - } - - /*--- Initialize the JacobianBuffer to zero. ---*/ - for (unsigned short iVar = 0; iVar < 2*MAXNVAR*MAXNVAR; iVar++) { - JacobianBuffer[iVar] = 0.0; - } - } - - /*! - * \brief Compute the scalar upwind flux between two nodes i and j. - * \param[in] config - Definition of the particular problem. - * \return A lightweight const-view (read-only) of the residual/flux and Jacobians. - */ - CNumerics::ResidualType<> ComputeResidual(const CConfig* config) final { - AD::StartPreacc(); - AD::SetPreaccIn(Normal, nDim); - AD::SetPreaccIn(ScalarVar_i, nVar); - AD::SetPreaccIn(ScalarVar_j, nVar); - if (dynamic_grid) { - AD::SetPreaccIn(GridVel_i, nDim); - AD::SetPreaccIn(GridVel_j, nDim); - } - AD::SetPreaccIn(&V_i[idx.Velocity()], nDim); - AD::SetPreaccIn(&V_j[idx.Velocity()], nDim); - AD::SetPreaccIn(V_i[idx.Density()]); - AD::SetPreaccIn(V_j[idx.Density()]); - AD::SetPreaccIn(MassFlux); - - ExtraADPreaccIn(); - - if (bounded_scalar) { - a0 = fmax(0.0, MassFlux) / V_i[idx.Density()]; - a1 = fmin(0.0, MassFlux) / V_j[idx.Density()]; - } else { - su2double q_ij = 0.0; - if (dynamic_grid) { - for (unsigned short iDim = 0; iDim < nDim; iDim++) { - su2double Velocity_i = V_i[iDim + idx.Velocity()] - GridVel_i[iDim]; - su2double Velocity_j = V_j[iDim + idx.Velocity()] - GridVel_j[iDim]; - q_ij += 0.5 * (Velocity_i + Velocity_j) * Normal[iDim]; - } - } else { - for (unsigned short iDim = 0; iDim < nDim; iDim++) { - q_ij += 0.5 * (V_i[iDim + idx.Velocity()] + V_j[iDim + idx.Velocity()]) * Normal[iDim]; - } - } - a0 = fmax(0.0, q_ij); - a1 = fmin(0.0, q_ij); - } - - FinishResidualCalc(config); - - AD::SetPreaccOut(Flux, nVar); - AD::EndPreacc(); - - return ResidualType<>(Flux, Jacobian_i, Jacobian_j); - } -}; diff --git a/SU2_CFD/include/numerics/scalar/scalar_diffusion.hpp b/SU2_CFD/include/numerics/scalar/scalar_diffusion.hpp deleted file mode 100644 index 580c0bedcefa..000000000000 --- a/SU2_CFD/include/numerics/scalar/scalar_diffusion.hpp +++ /dev/null @@ -1,155 +0,0 @@ -/*! - * \file scalar_diffusion.hpp - * \brief Declarations of numerics classes for discretization of - * viscous fluxes in scalar problems. - * \author F. Palacios, T. Economon - * \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 . - */ - -#pragma once - -#include "../CNumerics.hpp" - -/*! - * \class CNoFlowIndices - * \brief Dummy flow indices class to use CAvgGrad_Scalar when flow variables are not available. - * For example, solid heat transfer problems. - */ -struct CNoFlowIndices { - CNoFlowIndices(int, int) {} - inline int Density() const { return 0; } - inline int LaminarViscosity() const { return 0; } - inline int EddyViscosity() const { return 0; } -}; - -/*! - * \class CAvgGrad_Scalar - * \brief Template class for computing viscous residual of scalar values - * \details This class serves as a template for the scalar viscous residual - * classes. The general structure of a viscous residual calculation is the - * same for many different models, which leads to a lot of repeated code. - * By using the template design pattern, these sections of repeated code are - * moved to a shared base class, and the specifics of each model - * are implemented by derived classes. In order to add a new residual - * calculation for a viscous residual, extend this class and implement - * the pure virtual functions with model-specific behavior. - * \ingroup ViscDiscr - * \author C. Pederson, A. Bueno, and F. Palacios - */ -template -class CAvgGrad_Scalar : public CNumerics { - protected: - enum : unsigned short {MAXNVAR = 8}; - - const FlowIndices idx; /*!< \brief Object to manage the access to the flow primitives. */ - su2double Proj_Mean_GradScalarVar[MAXNVAR]; /*!< \brief Mean_gradScalarVar DOT normal, corrected if required. */ - su2double proj_vector_ij = 0.0; /*!< \brief (Edge_Vector DOT normal)/|Edge_Vector|^2 */ - su2double Flux[MAXNVAR] = {0.0}; /*!< \brief Final result, diffusive flux/residual. */ - su2double* Jacobian_i[MAXNVAR]; /*!< \brief Flux Jacobian w.r.t. node i. */ - su2double* Jacobian_j[MAXNVAR]; /*!< \brief Flux Jacobian w.r.t. node j. */ - su2double JacobianBuffer[2*MAXNVAR*MAXNVAR];/*!< \brief Static storage for the two Jacobians. */ - - const bool correct_gradient = false, incompressible = false; - - /*! - * \brief A pure virtual function; Adds any extra variables to AD - */ - virtual void ExtraADPreaccIn() = 0; - - /*! - * \brief Model-specific steps in the ComputeResidual method, derived classes - * should compute the Flux and Jacobians (i/j) inside this method. - * \param[in] config - Definition of the particular problem. - */ - virtual void FinishResidualCalc(const CConfig* config) = 0; - - public: - /*! - * \brief Constructor of the class. - * \param[in] val_nDim - Number of dimensions of the problem. - * \param[in] val_nVar - Number of variables of the problem. - * \param[in] correct_gradient - Whether to correct gradient for skewness. - * \param[in] config - Definition of the particular problem. - */ - CAvgGrad_Scalar(unsigned short val_nDim, unsigned short val_nVar, bool correct_grad, - const CConfig* config) - : CNumerics(val_nDim, val_nVar, config), - idx(val_nDim, config->GetnSpecies()), - correct_gradient(correct_grad), - incompressible(config->GetKind_Regime() == ENUM_REGIME::INCOMPRESSIBLE) { - if (nVar > MAXNVAR) { - SU2_MPI::Error("Static arrays are too small.", CURRENT_FUNCTION); - } - for (unsigned short iVar = 0; iVar < nVar; iVar++) { - Jacobian_i[iVar] = &JacobianBuffer[iVar * nVar]; - Jacobian_j[iVar] = &JacobianBuffer[iVar * nVar + MAXNVAR * MAXNVAR]; - } - - /*--- Initialize the JacobianBuffer to zero. ---*/ - for (unsigned short iVar = 0; iVar < 2*MAXNVAR*MAXNVAR; iVar++) { - JacobianBuffer[iVar] = 0.0; - } - } - - /*! - * \brief Compute the viscous residual using an average of gradients without correction. - * \param[in] config - Definition of the particular problem. - * \return A lightweight const-view (read-only) of the residual/flux and Jacobians. - */ - ResidualType<> ComputeResidual(const CConfig* config) final { - AD::StartPreacc(); - AD::SetPreaccIn(Coord_i, nDim); - AD::SetPreaccIn(Coord_j, nDim); - AD::SetPreaccIn(Normal, nDim); - AD::SetPreaccIn(ScalarVar_Grad_i, nVar, nDim); - AD::SetPreaccIn(ScalarVar_Grad_j, nVar, nDim); - if (correct_gradient) { - AD::SetPreaccIn(ScalarVar_i, nVar); - AD::SetPreaccIn(ScalarVar_j, nVar); - } - if (!std::is_same::value) { - AD::SetPreaccIn(V_i[idx.Density()], V_i[idx.LaminarViscosity()], V_i[idx.EddyViscosity()]); - AD::SetPreaccIn(V_j[idx.Density()], V_j[idx.LaminarViscosity()], V_j[idx.EddyViscosity()]); - - Density_i = V_i[idx.Density()]; - Density_j = V_j[idx.Density()]; - Laminar_Viscosity_i = V_i[idx.LaminarViscosity()]; - Laminar_Viscosity_j = V_j[idx.LaminarViscosity()]; - Eddy_Viscosity_i = V_i[idx.EddyViscosity()]; - Eddy_Viscosity_j = V_j[idx.EddyViscosity()]; - } - - ExtraADPreaccIn(); - - su2double ProjGradScalarVarNoCorr[MAXNVAR]; - proj_vector_ij = ComputeProjectedGradient(nDim, nVar, Normal, Coord_i, Coord_j, ScalarVar_Grad_i, ScalarVar_Grad_j, - correct_gradient, ScalarVar_i, ScalarVar_j, ProjGradScalarVarNoCorr, - Proj_Mean_GradScalarVar); - FinishResidualCalc(config); - - AD::SetPreaccOut(Flux, nVar); - AD::EndPreacc(); - - return ResidualType<>(Flux, Jacobian_i, Jacobian_j); - } -}; diff --git a/SU2_CFD/include/numerics/scalar/scalar_edge_flux.hpp b/SU2_CFD/include/numerics/scalar/scalar_edge_flux.hpp new file mode 100644 index 000000000000..4d2e79608bdf --- /dev/null +++ b/SU2_CFD/include/numerics/scalar/scalar_edge_flux.hpp @@ -0,0 +1,504 @@ +/*! + * \file scalar_edge_flux.hpp + * \brief Model-agnostic convection and diffusion of a transported scalar, shared by every + * scalar solver (turbulence, transition, species, flamelet, heat). + * \author P. Gomes + * \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 . + */ + +#pragma once + +#include "../../../../Common/include/CConfig.hpp" +#include "../../../../Common/include/containers/container_decorators.hpp" +#include "../util.hpp" +#include "../../variables/CFlowVariable.hpp" + +/*! + * \brief Locates the data of one endpoint of an edge. + * \note The j side of a boundary flux indexes per-marker ghost containers, which have the + * same types as the solver's own, so the kernels read both through one code path. + */ +template +struct EdgeSide { + const VariableType& scalarNodes; /*!< \brief Scalar solver variables. */ + const CFlowVariable* flowNodes; /*!< \brief Flow variables, null for solid heat transfer. */ + CMatrixView coord; /*!< \brief Point coordinates. */ + CMatrixView gridVel; /*!< \brief Empty when the grid is static. */ +}; + +/*! + * \brief Loop invariant flags for a scalar edge flux, built once outside the edge loop so the + * compiler can unswitch the branches they guard. + * \note Built through the named constructors below: the flags are too many and too alike to be + * given positionally, where one transposed pair would change the discretization silently. + */ +struct ScalarFluxOptions { + bool dynamicGrid = false; + bool boundedScalar = false; + bool correctGradient = false; + bool accurateJacobians = false; + bool implicit = false; /*!< \brief Whether the Jacobians are assembled, and so computed. */ + bool convective = true; /*!< \brief Whether the convective scheme contributes. */ + bool viscous = false; /*!< \brief Whether the diffusion term contributes. */ + bool oneSided = false; /*!< \brief Whether only the row of i is assembled. */ + bool muscl = false; /*!< \brief Whether the convective scheme reconstructs. */ + + /*! + * \brief Options of the interior edge loop: both terms, both rows, and reconstruction and + * gradient correction as the configuration asks for them. + */ + static ScalarFluxOptions Interior(const CConfig& config, bool bounded, bool accurateJacobians = false) { + auto opt = common(config); + opt.boundedScalar = bounded; + opt.accurateJacobians = accurateJacobians; + opt.correctGradient = true; + opt.viscous = true; + opt.muscl = config.GetMUSCL(); + return opt; + } + + /*! + * \brief Options of a boundary that imposes a convective flux alone, which is most of them: + * the diffusive term at an inlet or an outlet causes serious convergence problems. + */ + static ScalarFluxOptions BoundaryConvective(const CConfig& config, bool bounded) { + auto opt = common(config); + opt.boundedScalar = bounded; + opt.oneSided = true; + return opt; + } + + /*! + * \brief Options of a boundary that imposes both terms, which is the turbomachinery sites. + * \note They impose no mass flux, so the bounded scheme contributes nothing here whatever the + * configuration says. + */ + static ScalarFluxOptions BoundaryFull(const CConfig& config) { + auto opt = common(config); + opt.correctGradient = true; + opt.viscous = true; + opt.oneSided = true; + return opt; + } + + /*! + * \brief Options of the diffusive pass of a fluid interface, which follows a convective pass + * over the donor vertices. + * \param[in] correctGrad - Whether the projected gradient is corrected for skewness, which the + * models do not agree on at this boundary. + */ + static ScalarFluxOptions BoundaryDiffusive(const CConfig& config, bool correctGrad) { + auto opt = common(config); + opt.correctGradient = correctGrad; + opt.convective = false; + opt.viscous = true; + opt.oneSided = true; + return opt; + } + + private: + static ScalarFluxOptions common(const CConfig& config) { + ScalarFluxOptions opt; + opt.dynamicGrid = config.GetDynamic_Grid(); + opt.implicit = config.GetKind_TimeIntScheme() == EULER_IMPLICIT; + return opt; + } +}; + +/*! + * \brief Thin wrapper giving a Vector the all(iVar) accessor the MUSCL reconstruction helpers + * expect (see CCompressiblePrimitives in numerics_simd/flow/variables.hpp). + */ +template +struct CScalarValues { + static constexpr size_t nVar = Size; /*!< \brief Only used as VarType::nVar when reconstruct's own nVarGrad_ default + applies; every call site here passes an explicit nVarGrad_ instead. */ + Vector all; +}; + +/*! + * \brief Diffusion of a transported scalar, driven by model-supplied coefficients. + * \note The derived class returns the coefficients of both orientations of the edge, as one + * object, so that whatever the two share is computed once. A model whose coefficients + * are the same in both orientations returns the same value twice. A model whose matrix + * is diagonal declares DiagonalDiffusion and returns a vector instead of a matrix. + */ +template +class CAvgGradScalarBase { + protected: + using Int = typename CLaneTraits::Int; + + /*! + * \param[in] rho - Density of both endpoints, read once by ComputeFlux. + */ + template + FORCEINLINE void diffusionTerms(const FlowIndices& idx, const ScalarFluxOptions& opt, Int iPoint, + const EdgeSide& side_i, Int jPoint, + const EdgeSide& side_j, const CPair& rho, + const Vector& normal, const Vector& vector_ij, + EdgeResidual& res) const { + if (!opt.viscous) return; + + constexpr size_t Size = EdgeResidual::Size; + + const Double dist2_ij = fmax(squaredNorm(vector_ij), EPS); + const Double proj_vector_ij = dot(vector_ij, normal) / dist2_ij; + + /*--- Average gradient, corrected for skewness when asked. + * \note Gathered one variable at a time, bounded by res.nVar rather than Size: a static + * model with nVar 1 has Size 2 (the Matrix degeneracy floor), and a + * dynamic one has Size MaxScalarVar, so a single Size-wide read would run past the + * actual width of the gradient container in either case. ---*/ + Matrix avgGrad; + for (size_t iVar = 0; iVar < res.nVar; ++iVar) { + const auto grad_i = gatherVariables(iPoint, side_i.scalarNodes.GetGradient(), iVar); + const auto grad_j = gatherVariables(jPoint, side_j.scalarNodes.GetGradient(), iVar); + for (int iDim = 0; iDim < nDim; ++iDim) avgGrad(iVar, iDim) = 0.5 * (grad_i(iDim) + grad_j(iDim)); + } + + if (opt.correctGradient) { + for (size_t iVar = 0; iVar < res.nVar; ++iVar) { + const Double phi_i = gatherVariables(iPoint, side_i.scalarNodes.GetSolution(), iVar); + const Double phi_j = gatherVariables(jPoint, side_j.scalarNodes.GetSolution(), iVar); + const Double corr = (dot(avgGrad[iVar], vector_ij) - phi_j + phi_i) / dist2_ij; + for (int iDim = 0; iDim < nDim; ++iDim) avgGrad(iVar, iDim) -= corr * vector_ij(iDim); + } + } + + Vector projGrad; + for (size_t iVar = 0; iVar < res.nVar; ++iVar) projGrad(iVar) = dot(avgGrad[iVar], normal); + + /*--- The Jacobians of a conservative model are w.r.t. the conserved (density-weighted) + * variable, which divides the geometric projection by the density of the row being written. ---*/ + Double proj_on_w_i = proj_vector_ij, proj_on_w_j = proj_vector_ij; + if constexpr (Derived::Conservative) { + proj_on_w_i = proj_vector_ij / rho.i; + proj_on_w_j = proj_vector_ij / rho.j; + } + + const auto* self = static_cast(this); + const auto D = self->coefficients(idx, iPoint, side_i, jPoint, side_j, rho); + + for (size_t iVar = 0; iVar < res.nVar; ++iVar) { + if constexpr (Derived::DiagonalDiffusion) { + res.flux_i(iVar) -= D.i(iVar) * projGrad(iVar); + if (opt.implicit) { + res.jac_ii(iVar, iVar) += D.i(iVar) * proj_on_w_i; + if (!opt.oneSided) res.jac_ij(iVar, iVar) -= D.i(iVar) * proj_on_w_j; + } + if (!opt.oneSided) { + res.flux_j(iVar) += D.j(iVar) * projGrad(iVar); + if (opt.implicit) { + res.jac_ji(iVar, iVar) -= D.j(iVar) * proj_on_w_i; + res.jac_jj(iVar, iVar) += D.j(iVar) * proj_on_w_j; + } + } + } else { + for (size_t jVar = 0; jVar < res.nVar; ++jVar) { + res.flux_i(iVar) -= D.i(iVar, jVar) * projGrad(jVar); + if (opt.implicit) { + res.jac_ii(iVar, jVar) += D.i(iVar, jVar) * proj_on_w_i; + if (!opt.oneSided) res.jac_ij(iVar, jVar) -= D.i(iVar, jVar) * proj_on_w_j; + } + if (!opt.oneSided) { + res.flux_j(iVar) += D.j(iVar, jVar) * projGrad(jVar); + if (opt.implicit) { + res.jac_ji(iVar, jVar) -= D.j(iVar, jVar) * proj_on_w_i; + res.jac_jj(iVar, jVar) += D.j(iVar, jVar) * proj_on_w_j; + } + } + } + } + } + + if (opt.implicit && opt.accurateJacobians) { + /*--- Coefficients that depend on the transported variables contribute here, from whatever + * the model chose to carry in the object it returned from coefficients. ---*/ + self->coefficientJacobians(opt, D, projGrad, res); + } + + self->extraDiffusionTerms(idx, opt, iPoint, side_i, jPoint, side_j, rho, normal, vector_ij, res); + } + + /*! + * \brief Contribution of the derivatives of the coefficients themselves. + */ + template + FORCEINLINE void coefficientJacobians(Ts&&...) const {} + + /*! + * \brief Diffusion of a model that transports more than one gradient, of states it + * synthesises from its own containers. + */ + template + FORCEINLINE void extraDiffusionTerms(Ts&&...) const {} +}; + +/*! + * \brief Convective flux shared by every model whose transport equation has the shape + * flux(iVar) = a0 * w_i * phi_i(iVar) + a1 * w_j * phi_j(iVar), which is every + * model except SA and stochastic backscatter (see CScalarFlux_SA). + * \note The weight w is 1 for a non-conservative model, the density for a conservative one. + */ +template +class CUpwScalarFlux : public CAvgGradScalarBase { + protected: + using Int = typename CLaneTraits::Int; + + explicit CUpwScalarFlux(const CConfig&) {} + + /*! + * \brief Upwind convection of the transported variable, weighted by the density for a + * conservative model. + * \param[in] phi - Transported variable of both endpoints, reconstructed if opt.muscl is set; + * read from here rather than side_i/side_j.scalarNodes directly so a model needs + * no reconstruction logic of its own. + * \param[in] rho - Density of both endpoints, reconstructed alongside the velocity when the + * convective scheme reconstructs, so it weights the flux as the velocity does. + * \note The flux is written in terms of the transported variable but the Jacobians are w.r.t. + * the conserved one, which for a conservative model is the density-weighted variable; + * the density therefore multiplies the flux and not the Jacobian. + */ + template + FORCEINLINE void finalizeFlux(const FlowIndices&, const ScalarFluxOptions& opt, Int, const EdgeSide&, + Int, const EdgeSide&, const Double& a0, const Double& a1, + const CPair& rho, const CPair>& phi, + EdgeResidual& res) const { + Double w0 = a0, w1 = a1; + if constexpr (Derived::Conservative) { + w0 *= rho.i; + w1 *= rho.j; + } + + for (size_t iVar = 0; iVar < res.nVar; ++iVar) { + const Double flux = w0 * phi.i.all(iVar) + w1 * phi.j.all(iVar); + + res.flux_i(iVar) += flux; + if (!opt.oneSided) res.flux_j(iVar) -= flux; + + if (opt.implicit) { + res.jac_ii(iVar, iVar) += a0; + if (!opt.oneSided) { + res.jac_ij(iVar, iVar) += a1; + res.jac_ji(iVar, iVar) -= a0; + res.jac_jj(iVar, iVar) -= a1; + } + } + } + } +}; + +/*! + * \brief Upwind convection and diffusion of a transported scalar, accumulated into one + * residual, each term contributing or not according to the options. + */ +template +class CUpwScalarBase : public CUpwScalarFlux { + public: + using Double = Double_; + using Int = typename CLaneTraits::Int; + static constexpr int nDim = nDim_; + static constexpr size_t nVar = nVar_; + + /*! + * \brief Backing size, for the model to size the containers its coefficients() returns; + * nVar itself is Dynamic for a runtime model and never usable as a container size. + */ + static constexpr size_t Size = EdgeResidual::Size; + + /*! + * \brief Whether the model's diffusion coefficients read the density, beyond the reading that + * Conservative already implies. A model that declares neither never gathers it. + */ + static constexpr bool DiffusionReadsDensity = false; + + protected: + using Base = CUpwScalarFlux; + + const FlowIndices idx; + const size_t nEqn; /*!< \brief Equations of the model, which a dynamic one gives to its base. */ + + /*! + * \brief MUSCL reconstruction parameters, read from CConfig once per construction (i.e. once + * per nonlinear iteration, see CScalarSolver::EdgeFluxResidual) instead of per edge; + * this is also where the scalar limiter's freezing (GetLimiterIter) is resolved, by + * collapsing its type to NONE once frozen. The flow limiter is not frozen this way: once + * the flow solver stops recomputing it, it keeps applying the last values it has. + */ + const su2double kappa, umusclRamp, kappaFlow; + const LIMITER limiterType, limiterTypeFlow; + const bool musclFlow; + + public: + /*! + * \brief Constructor, inherited by the model with `using Base::Base`. + * \note Public, not protected: a using-declaration that inherits a constructor keeps the + * base's own access, so the solver that builds the concrete model needs this public + * to build it at all. + */ + explicit CUpwScalarBase(const CConfig& config, size_t nEqn_ = nVar_) + : Base(config), + idx(nDim, config.GetnSpecies()), + nEqn(nEqn_), + kappa(config.GetMUSCL_Kappa()), + umusclRamp(config.GetMUSCLRampValue()), + kappaFlow(config.GetMUSCL_Kappa_Flow()), + limiterType(config.GetInnerIter() <= config.GetLimiterIter() ? config.GetKind_SlopeLimit() : LIMITER::NONE), + limiterTypeFlow(config.GetKind_SlopeLimit_Flow() != LIMITER::VAN_ALBADA_EDGE ? config.GetKind_SlopeLimit_Flow() + : LIMITER::NONE), + musclFlow(config.GetMUSCL_Flow() && config.GetKind_ConvNumScheme_Flow() == SPACE_UPWIND) { + if (nEqn > Size) { + SU2_MPI::Error("Static arrays are too small for the requested equation count.", CURRENT_FUNCTION); + } + } + + template + FORCEINLINE EdgeResidual ComputeFlux(const ScalarFluxOptions& opt, Int iPoint, + const EdgeSide& side_i, Int jPoint, + const EdgeSide& side_j, + const Vector& normal, const Double& massFlux) const { + /*--- Inputs are registered as they are read, by each of the two terms. ---*/ + AD::StartPreacc(); + AD::SetPreaccIn(normal, nDim); + + EdgeResidual res(nEqn); + + /*--- Read once by the reconstruction and by the diffusion. ---*/ + Vector vector_ij; + if (opt.muscl || opt.viscous) { + vector_ij = distanceVector(iPoint, side_i.coord, jPoint, side_j.coord); + } + + /*--- Density of both endpoints, gathered once: the conservative weighting of the convective + * term, the bounded scheme's division of the mass flux, and some models' diffusion + * coefficients all want it, and in reverse mode every gather is a preaccumulation input. ---*/ + CPair rho{Double(1.0), Double(1.0)}; + if (Derived::Conservative || Derived::DiffusionReadsDensity || opt.boundedScalar) { + rho.i = gatherVariables(iPoint, side_i.flowNodes->GetPrimitive(), idx.Density()); + rho.j = gatherVariables(jPoint, side_j.flowNodes->GetPrimitive(), idx.Density()); + } + + if (opt.convective) { + /*--- Upwinding weights of the face normal mass or volume flux, and the density that weights + * a conservative flux, which follows the velocity in being reconstructed or not. ---*/ + Double a0, a1; + CPair rhoConv = rho; + + if (opt.boundedScalar) { + AD::SetPreaccIn(massFlux); + a0 = fmax(0.0, massFlux) / rho.i; + a1 = fmin(0.0, massFlux) / rho.j; + } else { + CPair> u; + u.i.all = gatherVariables(iPoint, side_i.flowNodes->GetPrimitive(), idx.Velocity()); + u.j.all = gatherVariables(jPoint, side_j.flowNodes->GetPrimitive(), idx.Velocity()); + + if (opt.muscl && musclFlow) { + reconstruct(iPoint, jPoint, vector_ij, side_i.flowNodes->GetGradient_Reconstruction(), + side_i.flowNodes->GetLimiter_Primitive(), limiterTypeFlow, idx.Velocity(), u, kappaFlow, + umusclRamp); + + if constexpr (Derived::Conservative) { + /*--- Density is not adjacent to the velocity in the primitive row, so it is a second + * reconstruction of one variable rather than a wider slice of the first. Some upwind + * schemes (see EulerNPrimVarGrad) size the flow's gradient/limiter columns smaller + * than the full primitive count and leave density out of it entirely, so fall back to + * the cell-centered density instead. ---*/ + if (idx.Density() < side_i.flowNodes->GetnPrimVarGrad()) { + CPair> r; + r.i.all(0) = rho.i; + r.j.all(0) = rho.j; + reconstruct<1>(iPoint, jPoint, vector_ij, side_i.flowNodes->GetGradient_Reconstruction(), + side_i.flowNodes->GetLimiter_Primitive(), limiterTypeFlow, idx.Density(), r, kappaFlow, + umusclRamp); + rhoConv.i = r.i.all(0); + rhoConv.j = r.j.all(0); + } + } + } + + /*--- Face normal velocity of the mean of the two points, relative to the grid. ---*/ + Vector vel_ij; + for (int iDim = 0; iDim < nDim; ++iDim) vel_ij(iDim) = 0.5 * (u.i.all(iDim) + u.j.all(iDim)); + + if (opt.dynamicGrid) { + const auto ug_i = gatherVariables(iPoint, side_i.gridVel); + /*--- A boundary's ghost point has no grid velocity of its own: it is spatially + * coincident with i, so it moves with it. ---*/ + const auto ug_j = opt.oneSided ? ug_i : gatherVariables(jPoint, side_j.gridVel); + for (int iDim = 0; iDim < nDim; ++iDim) vel_ij(iDim) -= 0.5 * (ug_i(iDim) + ug_j(iDim)); + } + + const Double q_ij = dot(vel_ij, normal); + a0 = fmax(0.0, q_ij); + a1 = fmin(0.0, q_ij); + } + + /*--- Transported variable of both endpoints, reconstructed if opt.muscl is set. + * Gathered one variable at a time (like the diffusion gradients above) so a static model + * with nVar 1 never reads past the single column its solution container actually has. ---*/ + CPair> phi; + for (size_t iVar = 0; iVar < res.nVar; ++iVar) { + phi.i.all(iVar) = gatherVariables(iPoint, side_i.scalarNodes.GetSolution(), iVar); + phi.j.all(iVar) = gatherVariables(jPoint, side_j.scalarNodes.GetSolution(), iVar); + } + + if (opt.muscl) { + if constexpr (nVar != Dynamic) { + reconstruct(iPoint, jPoint, vector_ij, side_i.scalarNodes.GetGradient_Reconstruction(), + side_i.scalarNodes.GetLimiter(), limiterType, 0, phi, kappa, umusclRamp); + } else { + /*--- A dynamic model's equation count is only known at runtime, so the reconstructed + * width is passed as an argument instead of a template parameter. ---*/ + reconstruct(iPoint, jPoint, vector_ij, side_i.scalarNodes.GetGradient_Reconstruction(), + side_i.scalarNodes.GetLimiter(), limiterType, 0, phi, kappa, umusclRamp, res.nVar); + } + } + + static_cast(this)->finalizeFlux(idx, opt, iPoint, side_i, jPoint, side_j, a0, a1, rhoConv, phi, + res); + } + + Base::diffusionTerms(idx, opt, iPoint, side_i, jPoint, side_j, rho, normal, vector_ij, res); + + setPreaccOut(res.flux_i, res.nVar); + if (!opt.oneSided) setPreaccOut(res.flux_j, res.nVar); + AD::EndPreacc(); + + return res; + } + + /*! + * \brief Compute the flux of an edge and write it to the linear system. + */ + template + FORCEINLINE void ComputeFlux(const ScalarFluxOptions& opt, Int iEdge, Int iPoint, + const EdgeSide& side_i, Int jPoint, const EdgeSide& side_j, + const Vector& normal, const Double& massFlux, UpdateType updateType, + Double updateMask, CSysVector& vector, CSysVector& vectorDiff, + SparseMatrixType& matrix) const { + const auto res = ComputeFlux(opt, iPoint, side_i, jPoint, side_j, normal, massFlux); + + updateLinearSystem(iEdge, iPoint, jPoint, opt.implicit, updateType, updateMask, res, vector, vectorDiff, matrix); + } +}; diff --git a/SU2_CFD/include/numerics/species/flamelet_edge_flux.hpp b/SU2_CFD/include/numerics/species/flamelet_edge_flux.hpp new file mode 100644 index 000000000000..dc634a235ca1 --- /dev/null +++ b/SU2_CFD/include/numerics/species/flamelet_edge_flux.hpp @@ -0,0 +1,172 @@ +/*! + * \file flamelet_edge_flux.hpp + * \brief Flamelet transport as a third-layer scalar flux, see numerics/scalar/scalar_edge_flux.hpp. + * \author P. Gomes + * \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 . + */ + +#pragma once + +#include "species_edge_flux.hpp" + +/*! + * \class CScalarFlux_Flamelet + * \ingroup ViscDiscr + * \brief Convection and diffusion of the flamelet controlling variables and passive species, + * which is species transport plus two preferential diffusion terms. + * \note The preferential diffusion terms read the beta scalars and their gradients from the + * auxiliary variables of the solver's own containers, which the per-marker ghost containers + * of a boundary do not carry. They are an interior edge term: boundaries instantiate + * CScalarFlux_Species, as they did before this model existed. + */ +template +class CScalarFlux_Flamelet final + : public CScalarFluxSpeciesBase, FlowIndices, nDim, + nVar> { + public: + using Base = CScalarFluxSpeciesBase; + using Int = typename Base::Int; + + explicit CScalarFlux_Flamelet(const CConfig& config) + : Base(config), + preferentialDiffusion(config.GetFlameletParsedOptions().preferential_diffusion), + nControlVars(config.GetFlameletParsedOptions().n_control_vars) {} + + /*! + * \brief Preferential diffusion, two terms with the shape of the ordinary diffusion but of + * states the model synthesises: div(D grad(beta - phi)) for each controlling variable, + * and a thermal term div(beta_T D grad(T)) on the enthalpy equation. + * \note The thermal term has no implicit part, matching the treatment of the heat flux it + * models; the first term has the same thin shear layer Jacobian as the ordinary diffusion, + * because it is the same operator applied to a shifted state. + */ + template + FORCEINLINE void extraDiffusionTerms(const FlowIndices& idx, const ScalarFluxOptions& opt, Int iPoint, + const EdgeSide& side_i, Int jPoint, + const EdgeSide& side_j, const CPair& rho, + const Vector& normal, const Vector& vector_ij, + EdgeResidual& res) const { + if (!preferentialDiffusion) return; + + const Double dist2_ij = fmax(squaredNorm(vector_ij), EPS); + const Double proj_vector_ij = dot(vector_ij, normal) / dist2_ij; + const Double proj_on_rho_i = proj_vector_ij / rho.i; + const Double proj_on_rho_j = proj_vector_ij / rho.j; + + const Double diffTurb = Base::turbulentDiffusivity(idx, iPoint, side_i, jPoint, side_j); + + /*--- The gradient of a controlling variable is subtracted from that of its beta scalar, so + * that what is added here is the difference from the ordinary diffusion already applied. ---*/ + for (auto iScalar = 0u; iScalar < nControlVars; ++iScalar) { + const auto iBeta = betaIndex(iScalar); + + const Double phi_i = gatherVariables(iPoint, side_i.scalarNodes.GetAuxVar(), iBeta) - + gatherVariables(iPoint, side_i.scalarNodes.GetSolution(), iScalar); + const Double phi_j = gatherVariables(jPoint, side_j.scalarNodes.GetAuxVar(), iBeta) - + gatherVariables(jPoint, side_j.scalarNodes.GetSolution(), iScalar); + + auto grad_i = gatherVariables(iPoint, side_i.scalarNodes.GetAuxVarGradient(), iBeta); + auto grad_j = gatherVariables(jPoint, side_j.scalarNodes.GetAuxVarGradient(), iBeta); + const auto gradPhi_i = gatherVariables(iPoint, side_i.scalarNodes.GetGradient(), iScalar); + const auto gradPhi_j = gatherVariables(jPoint, side_j.scalarNodes.GetGradient(), iScalar); + for (int iDim = 0; iDim < nDim; ++iDim) { + grad_i(iDim) -= gradPhi_i(iDim); + grad_j(iDim) -= gradPhi_j(iDim); + } + + const Double D_i = gatherVariables(iPoint, side_i.scalarNodes.GetDiffusivity(), iScalar); + const Double D_j = gatherVariables(jPoint, side_j.scalarNodes.GetDiffusivity(), iScalar); + const Double D = 0.5 * (rho.i * D_i + rho.j * D_j) + diffTurb; + + const Double projGrad = projectedGradient(opt, grad_i, grad_j, phi_i, phi_j, normal, vector_ij, dist2_ij); + + res.flux_i(iScalar) -= D * projGrad; + if (!opt.oneSided) res.flux_j(iScalar) += D * projGrad; + + if (opt.implicit) { + res.jac_ii(iScalar, iScalar) += D * proj_on_rho_i; + if (!opt.oneSided) { + res.jac_ij(iScalar, iScalar) -= D * proj_on_rho_j; + res.jac_ji(iScalar, iScalar) -= D * proj_on_rho_i; + res.jac_jj(iScalar, iScalar) += D * proj_on_rho_j; + } + } + } + + /*--- Thermal term, on the enthalpy equation alone, driven by the temperature gradient. ---*/ + if (nControlVars <= I_ENTH) return; + + const Double T_i = gatherVariables(iPoint, side_i.flowNodes->GetPrimitive(), idx.Temperature()); + const Double T_j = gatherVariables(jPoint, side_j.flowNodes->GetPrimitive(), idx.Temperature()); + + const auto gradT_i = gatherVariables(iPoint, side_i.flowNodes->GetGradient_Primitive(), idx.Temperature()); + const auto gradT_j = gatherVariables(jPoint, side_j.flowNodes->GetGradient_Primitive(), idx.Temperature()); + + const Double Dth_i = gatherVariables(iPoint, side_i.scalarNodes.GetAuxVar(), I_BETA_ENTH_THERMAL) * + gatherVariables(iPoint, side_i.scalarNodes.GetDiffusivity(), I_ENTH); + const Double Dth_j = gatherVariables(jPoint, side_j.scalarNodes.GetAuxVar(), I_BETA_ENTH_THERMAL) * + gatherVariables(jPoint, side_j.scalarNodes.GetDiffusivity(), I_ENTH); + const Double Dth = 0.5 * (rho.i * Dth_i + rho.j * Dth_j) + diffTurb; + + const Double projGradT = projectedGradient(opt, gradT_i, gradT_j, T_i, T_j, normal, vector_ij, dist2_ij); + + res.flux_i(I_ENTH) -= Dth * projGradT; + if (!opt.oneSided) res.flux_j(I_ENTH) += Dth * projGradT; + } + + private: + const bool preferentialDiffusion; + const unsigned short nControlVars; + + /*! + * \brief Auxiliary variable holding the beta scalar of a controlling variable. + */ + static FORCEINLINE unsigned short betaIndex(unsigned short iScalar) { + switch (iScalar) { + case I_PROGVAR: + return I_BETA_PROGVAR; + case I_ENTH: + return I_BETA_ENTH; + default: + return I_BETA_MIXFRAC; + } + } + + /*! + * \brief Average gradient of one synthesised state projected on the normal, corrected for + * skewness when asked, which is what the ordinary diffusion does for a transported one. + */ + FORCEINLINE Double projectedGradient(const ScalarFluxOptions& opt, const Vector& grad_i, + const Vector& grad_j, const Double& phi_i, const Double& phi_j, + const Vector& normal, const Vector& vector_ij, + const Double& dist2_ij) const { + Vector avgGrad; + for (int iDim = 0; iDim < nDim; ++iDim) avgGrad(iDim) = 0.5 * (grad_i(iDim) + grad_j(iDim)); + + if (opt.correctGradient) { + const Double corr = (dot(avgGrad, vector_ij) - phi_j + phi_i) / dist2_ij; + for (int iDim = 0; iDim < nDim; ++iDim) avgGrad(iDim) -= corr * vector_ij(iDim); + } + return dot(avgGrad, normal); + } +}; diff --git a/SU2_CFD/include/numerics/species/species_convection.hpp b/SU2_CFD/include/numerics/species/species_convection.hpp deleted file mode 100644 index 114501cb5449..000000000000 --- a/SU2_CFD/include/numerics/species/species_convection.hpp +++ /dev/null @@ -1,85 +0,0 @@ -/*! - * \file species_convection.hpp - * \brief Declarations of numerics classes for discretization of - * convective fluxes in species problems. - * \author T. Kattmann - * \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 . - */ - -#pragma once - -#include "../scalar/scalar_convection.hpp" - -/*! - * \class CUpwSca_Species - * \brief Class for doing a scalar upwind solver for the species transport equations. - * \ingroup ConvDiscr - */ -template -class CUpwSca_Species final : public CUpwScalar { - private: - using Base = CUpwScalar; - using Base::nVar; - using Base::nDim; - using Base::V_i; - using Base::V_j; - using Base::a0; - using Base::a1; - using Base::Flux; - using Base::Jacobian_i; - using Base::Jacobian_j; - using Base::ScalarVar_i; - using Base::ScalarVar_j; - using Base::idx; - using Base::bounded_scalar; - - /*! - * \brief Adds any extra variables to AD - */ - void ExtraADPreaccIn() override {} - - /*! - * \brief Species transport specific steps in the ComputeResidual method - * \param[in] config - Definition of the particular problem. - */ - void FinishResidualCalc(const CConfig* config) override { - for (auto iVar = 0u; iVar < nVar; iVar++) { - Flux[iVar] = a0 * V_i[idx.Density()] * ScalarVar_i[iVar] + a1 * V_j[idx.Density()] * ScalarVar_j[iVar]; - - /*--- Jacobians are taken wrt rho*Y not Y alone in the species solver. ---*/ - /*--- Off-diagonal entries are zero. ---*/ - Jacobian_i[iVar][iVar] = a0; - Jacobian_j[iVar][iVar] = a1; - } // iVar - } - - public: - /*! - * \brief Constructor of the class. - * \param[in] val_nDim - Number of dimensions of the problem. - * \param[in] val_nVar - Number of variables of the problem. - * \param[in] config - Definition of the particular problem. - */ - CUpwSca_Species(unsigned short val_nDim, unsigned short val_nVar, const CConfig* config) - : CUpwScalar(val_nDim, val_nVar, config) { bounded_scalar = config->GetBounded_Species(); } -}; diff --git a/SU2_CFD/include/numerics/species/species_diffusion.hpp b/SU2_CFD/include/numerics/species/species_diffusion.hpp deleted file mode 100644 index 424c4a47b8ca..000000000000 --- a/SU2_CFD/include/numerics/species/species_diffusion.hpp +++ /dev/null @@ -1,110 +0,0 @@ -/*! - * \file species_diffusion.hpp - * \brief Declarations of numerics classes for discretization of - * viscous fluxes in species problems. - * \author T. Kattmann - * \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 . - */ - -#pragma once - -#include "../scalar/scalar_diffusion.hpp" - -/*! - * \class CAvgGrad_Species - * \brief Class for computing viscous term using average of gradients (species transport model). - * \ingroup ViscDiscr - */ -template -class CAvgGrad_Species final : public CAvgGrad_Scalar { - private: - using Base = CAvgGrad_Scalar; - using Base::nVar; - using Base::Eddy_Viscosity_i; - using Base::Eddy_Viscosity_j; - using Base::Diffusion_Coeff_i; - using Base::Diffusion_Coeff_j; - using Base::Density_i; - using Base::Density_j; - using Base::ScalarVar_i; - using Base::ScalarVar_j; - using Base::Proj_Mean_GradScalarVar; - using Base::proj_vector_ij; - using Base::Flux; - using Base::Jacobian_i; - using Base::Jacobian_j; - - const bool turbulence; - - /*! - * \brief Adds any extra variables to AD - */ - void ExtraADPreaccIn(void) override { - AD::SetPreaccIn(Diffusion_Coeff_i, nVar); - AD::SetPreaccIn(Diffusion_Coeff_j, nVar); - } - - /*! - * \brief Species transport specific steps in the ComputeResidual method - * \param[in] config - Definition of the particular problem. - */ - void FinishResidualCalc(const CConfig* config) override { - for (auto iVar = 0u; iVar < nVar; iVar++) { - - const su2double Diffusivity_Lam = 0.5 * (Density_i * Diffusion_Coeff_i[iVar] + Density_j * Diffusion_Coeff_j[iVar]); - - su2double Diffusivity_Turb = 0.0; - - if (turbulence) { - const su2double Sc_t = config->GetSchmidt_Number_Turbulent(); - Diffusivity_Turb = 0.5 * (Eddy_Viscosity_i / Sc_t + Eddy_Viscosity_j / Sc_t); - } - - const su2double Diffusivity = Diffusivity_Lam + Diffusivity_Turb; - - Flux[iVar] = Diffusivity * Proj_Mean_GradScalarVar[iVar]; - - /*--- Use TSL approx. to compute derivatives of the gradients. ---*/ - - /*--- Off-diagonal entries are all zero. ---*/ - const su2double proj_on_rhoi = proj_vector_ij / Density_i; - Jacobian_i[iVar][iVar] = -Diffusivity * proj_on_rhoi; - - const su2double proj_on_rhoj = proj_vector_ij / Density_j; - Jacobian_j[iVar][iVar] = Diffusivity * proj_on_rhoj; - - } // iVar - } - - public: - /*! - * \brief Constructor of the class. - * \param[in] val_nDim - Number of dimensions of the problem. - * \param[in] val_nVar - Number of variables of the problem. - * \param[in] correct_grad - Whether to correct gradient for skewness. - * \param[in] config - Definition of the particular problem. - */ - CAvgGrad_Species(unsigned short val_nDim, unsigned short val_nVar, bool correct_grad, const CConfig* config) - : CAvgGrad_Scalar(val_nDim, val_nVar, correct_grad, config), - turbulence(config->GetKind_Turb_Model() != TURB_MODEL::NONE) {} -}; diff --git a/SU2_CFD/include/numerics/species/species_edge_flux.hpp b/SU2_CFD/include/numerics/species/species_edge_flux.hpp new file mode 100644 index 000000000000..e744d8c96553 --- /dev/null +++ b/SU2_CFD/include/numerics/species/species_edge_flux.hpp @@ -0,0 +1,113 @@ +/*! + * \file species_edge_flux.hpp + * \brief Species transport as a third-layer scalar flux, see numerics/scalar/scalar_edge_flux.hpp. + * \author P. Gomes + * \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 . + */ + +#pragma once + +#include "../scalar/scalar_edge_flux.hpp" + +/*! + * \class CScalarFluxSpeciesBase + * \ingroup ViscDiscr + * \brief Convection and diffusion of a mass fraction, conservative with a diagonal, + * i/j-symmetric diffusion matrix. Unlike SA/SST/LM, the equation count is only known at + * runtime (one per transported species), so this is the framework's first Dynamic-nVar + * model: nEqn is passed to the base explicitly, and coefficients() loops to it rather than + * to a compile-time nVar. + * \note This carries no finalizeFlux of its own: the inherited CUpwScalarFlux one is exactly + * flux(iVar) = a0*rho_i*Y_i(iVar) + a1*rho_j*Y_j(iVar), Conservative weighting by density, + * which is the model's whole convective term. + * \note It takes the most derived class as a parameter so that the flamelet model, which adds a + * preferential diffusion term to the same coefficients, is a sibling rather than a copy. + */ +template +class CScalarFluxSpeciesBase : public CUpwScalarBase { + public: + static constexpr bool Conservative = true; + static constexpr bool DiagonalDiffusion = true; + + using Base = CUpwScalarBase; + using Int = typename Base::Int; + + explicit CScalarFluxSpeciesBase(const CConfig& config) + : Base(config, config.GetnSpecies()), + turbulence(config.GetKind_Turb_Model() != TURB_MODEL::NONE), + Sc_t(config.GetSchmidt_Number_Turbulent()) {} + + /*! + * \brief Diffusion coefficients, an i/j average of (rho * mass diffusivity) per species, plus a + * turbulent (mu_t/Sc_t) contribution shared by every species, when a turbulence model is + * active; identical for both edge sides. + */ + template + FORCEINLINE CPair> coefficients(const FlowIndices& idx, Int iPoint, + const EdgeSide& side_i, Int jPoint, + const EdgeSide& side_j, + const CPair& rho) const { + const Double diffTurb = turbulentDiffusivity(idx, iPoint, side_i, jPoint, side_j); + + Vector D; + for (size_t iVar = 0; iVar < this->nEqn; ++iVar) { + const Double D_lam_i = gatherVariables(iPoint, side_i.scalarNodes.GetDiffusivity(), iVar); + const Double D_lam_j = gatherVariables(jPoint, side_j.scalarNodes.GetDiffusivity(), iVar); + D(iVar) = 0.5 * (rho.i * D_lam_i + rho.j * D_lam_j) + diffTurb; + } + return {D, D}; + } + + protected: + /*! + * \brief Turbulent contribution to the diffusivity, shared by every species and, in the + * flamelet model, by the preferential diffusion terms. + */ + template + FORCEINLINE Double turbulentDiffusivity(const FlowIndices& idx, Int iPoint, const EdgeSide& side_i, + Int jPoint, const EdgeSide& side_j) const { + if (!turbulence) return Double(0.0); + + const Double muT_i = gatherVariables(iPoint, side_i.flowNodes->GetPrimitive(), idx.EddyViscosity()); + const Double muT_j = gatherVariables(jPoint, side_j.flowNodes->GetPrimitive(), idx.EddyViscosity()); + return 0.5 * (muT_i / Sc_t + muT_j / Sc_t); + } + + private: + const bool turbulence; + const su2double Sc_t; +}; + +/*! + * \class CScalarFlux_Species + * \ingroup ViscDiscr + * \brief Convection and diffusion of the species transport model. + */ +template +class CScalarFlux_Species final + : public CScalarFluxSpeciesBase, FlowIndices, nDim, + nVar> { + public: + using Base = CScalarFluxSpeciesBase; + using Base::Base; +}; diff --git a/SU2_CFD/include/numerics/turbulent/transition/trans_convection.hpp b/SU2_CFD/include/numerics/turbulent/transition/trans_convection.hpp deleted file mode 100644 index 2338c2ca837b..000000000000 --- a/SU2_CFD/include/numerics/turbulent/transition/trans_convection.hpp +++ /dev/null @@ -1,40 +0,0 @@ -/*! - * \file trans_convection.hpp - * \brief Delarations of numerics classes for discretization of - * convective fluxes in transition problems. - * \author S. Kang - * \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 . - */ - -#pragma once - -#include "../turb_convection.hpp" - -/*! - * \class CUpwSca_TransLM - * \brief Re-use the SST convective fluxes for the scalar upwind discretization of LM transition model equations. - * \ingroup ConvDiscr - */ -template -using CUpwSca_TransLM = CUpwSca_TurbSST; - diff --git a/SU2_CFD/include/numerics/turbulent/transition/trans_diffusion.hpp b/SU2_CFD/include/numerics/turbulent/transition/trans_diffusion.hpp deleted file mode 100644 index 0d4aee5a9472..000000000000 --- a/SU2_CFD/include/numerics/turbulent/transition/trans_diffusion.hpp +++ /dev/null @@ -1,105 +0,0 @@ -/*! - * \file trans_diffusion.hpp - * \brief Declarations of numerics classes for discretization of - * viscous fluxes in transition problems. - * \author S. Kang - * \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 . - */ -#pragma once - - -#include "../../scalar/scalar_diffusion.hpp" - -/*! - * \class CAvgGrad_TransLM - * \brief Class for computing viscous term using average of gradient with correction (LM transition model). - * \ingroup ViscDiscr - * \author S. Kang. - */ -template -class CAvgGrad_TransLM final : public CAvgGrad_Scalar { -private: - using Base = CAvgGrad_Scalar; - using Base::Laminar_Viscosity_i; - using Base::Laminar_Viscosity_j; - using Base::Eddy_Viscosity_i; - using Base::Eddy_Viscosity_j; - using Base::Density_i; - using Base::Density_j; - using Base::ScalarVar_i; - using Base::ScalarVar_j; - using Base::Proj_Mean_GradScalarVar; - using Base::proj_vector_ij; - using Base::Flux; - using Base::Jacobian_i; - using Base::Jacobian_j; - - /*! - * \brief Adds any extra variables to AD - */ - void ExtraADPreaccIn() override {} - - /*! - * \brief LM transition model specific steps in the ComputeResidual method - * \param[in] config - Definition of the particular problem. - */ - void FinishResidualCalc(const CConfig* config) override { - const bool implicit = config->GetKind_TimeIntScheme() == EULER_IMPLICIT; - - /*--- Compute mean effective dynamic viscosity ---*/ - const su2double diff_i_gamma = Laminar_Viscosity_i + Eddy_Viscosity_i; - const su2double diff_j_gamma = Laminar_Viscosity_j + Eddy_Viscosity_j; - const su2double diff_i_ReThetaT = 2.0*(Laminar_Viscosity_i + Eddy_Viscosity_i); - const su2double diff_j_ReThetaT = 2.0*(Laminar_Viscosity_j + Eddy_Viscosity_j); - - const su2double diff_gamma = 0.5*(diff_i_gamma + diff_j_gamma); - const su2double diff_ReThetaT = 0.5*(diff_i_ReThetaT + diff_j_ReThetaT); - - Flux[0] = diff_gamma*Proj_Mean_GradScalarVar[0]; - Flux[1] = diff_ReThetaT*Proj_Mean_GradScalarVar[1]; - - /*--- For Jacobians -> Use of TSL (Thin Shear Layer) approx. to compute derivatives of the gradients ---*/ - if (implicit) { - const su2double proj_on_rho_i = proj_vector_ij/Density_i; - Jacobian_i[0][0] = -diff_gamma*proj_on_rho_i; Jacobian_i[0][1] = 0.0; - Jacobian_i[1][0] = 0.0; Jacobian_i[1][1] = -diff_ReThetaT*proj_on_rho_i; - - const su2double proj_on_rho_j = proj_vector_ij/Density_j; - Jacobian_j[0][0] = diff_gamma*proj_on_rho_j; Jacobian_j[0][1] = 0.0; - Jacobian_j[1][0] = 0.0; Jacobian_j[1][1] = diff_ReThetaT*proj_on_rho_j; - } - } - -public: - /*! - * \brief Constructor of the class. - * \param[in] val_nDim - Number of dimensions of the problem. - * \param[in] val_nVar - Number of variables of the problem. - * \param[in] correct_grad - Whether to correct gradient for skewness. - * \param[in] config - Definition of the particular problem. - */ - CAvgGrad_TransLM(unsigned short val_nDim, unsigned short val_nVar, bool correct_grad, const CConfig* config) - : CAvgGrad_Scalar(val_nDim, val_nVar, correct_grad, config){ - } - -}; diff --git a/SU2_CFD/include/numerics/turbulent/transition/trans_edge_flux.hpp b/SU2_CFD/include/numerics/turbulent/transition/trans_edge_flux.hpp new file mode 100644 index 000000000000..927fcb6f5b02 --- /dev/null +++ b/SU2_CFD/include/numerics/turbulent/transition/trans_edge_flux.hpp @@ -0,0 +1,78 @@ +/*! + * \file trans_edge_flux.hpp + * \brief Langtry-Menter transition model as a third-layer scalar flux, see numerics/scalar/scalar_edge_flux.hpp. + * \author P. Gomes + * \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 . + */ + +#pragma once + +#include "../../scalar/scalar_edge_flux.hpp" + +/*! + * \class CScalarFlux_TransLM + * \ingroup ViscDiscr + * \brief Convection and diffusion of the Langtry-Menter transition model, conservative with a + * diagonal, i/j-symmetric diffusion matrix. The coefficients depend only on the flow's + * mu/mu_t, not on the transported gamma/Re_theta, so no coefficientJacobians override + * is needed. + * \note LM writes no finalizeFlux of its own: the inherited CUpwScalarFlux one is exactly + * flux(iVar) = a0*rho_i*phi_i(iVar) + a1*rho_j*phi_j(iVar), Conservative weighting by + * density, which is the model's whole convective term. + */ +template +class CScalarFlux_TransLM + : public CUpwScalarBase, FlowIndices, nDim, nVar> { + public: + static constexpr bool Conservative = true; + static constexpr bool DiagonalDiffusion = true; + + using Base = CUpwScalarBase; + using Int = typename Base::Int; + using Base::Base; + + /*! + * \brief Diffusion coefficients, an i/j average of (mu+mu_t) for intermittency and of + * 2*(mu+mu_t) for the momentum-thickness Reynolds number; identical for both edge sides. + */ + template + FORCEINLINE CPair> coefficients(const FlowIndices& idx, Int iPoint, + const EdgeSide& side_i, Int jPoint, + const EdgeSide& side_j, + const CPair&) const { + const Double mu_i = gatherVariables(iPoint, side_i.flowNodes->GetPrimitive(), idx.LaminarViscosity()); + const Double mu_j = gatherVariables(jPoint, side_j.flowNodes->GetPrimitive(), idx.LaminarViscosity()); + const Double muT_i = gatherVariables(iPoint, side_i.flowNodes->GetPrimitive(), idx.EddyViscosity()); + const Double muT_j = gatherVariables(jPoint, side_j.flowNodes->GetPrimitive(), idx.EddyViscosity()); + + const Double diff_i_gamma = mu_i + muT_i; + const Double diff_j_gamma = mu_j + muT_j; + const Double diff_i_ReThetaT = 2.0 * (mu_i + muT_i); + const Double diff_j_ReThetaT = 2.0 * (mu_j + muT_j); + + Vector D; + D(0) = 0.5 * (diff_i_gamma + diff_j_gamma); + D(1) = 0.5 * (diff_i_ReThetaT + diff_j_ReThetaT); + return {D, D}; + } +}; diff --git a/SU2_CFD/include/numerics/turbulent/turb_convection.hpp b/SU2_CFD/include/numerics/turbulent/turb_convection.hpp deleted file mode 100644 index 6c1641db87d3..000000000000 --- a/SU2_CFD/include/numerics/turbulent/turb_convection.hpp +++ /dev/null @@ -1,141 +0,0 @@ -/*! - * \file turb_convection.hpp - * \brief Declarations of numerics classes for discretization of - * convective fluxes in turbulence problems. - * \author F. Palacios, T. Economon - * \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 . - */ - -#pragma once - -#include "../scalar/scalar_convection.hpp" - -/*! - * \class CUpwSca_TurbSA - * \brief Class for doing a scalar upwind solver for the Spalar-Allmaras turbulence model equations. - * \ingroup ConvDiscr - * \author A. Bueno. - */ -template -class CUpwSca_TurbSA final : public CUpwScalar { -private: - using Base = CUpwScalar; - using Base::a0; - using Base::a1; - using Base::Flux; - using Base::Jacobian_i; - using Base::Jacobian_j; - using Base::ScalarVar_i; - using Base::ScalarVar_j; - using Base::bounded_scalar; - using Base::V_i; - using Base::V_j; - using Base::idx; - using Base::nVar; - - /*! - * \brief Adds any extra variables to AD. - */ - void ExtraADPreaccIn() override {} - - /*! - * \brief SA specific steps in the ComputeResidual method - * \param[in] config - Definition of the particular problem. - */ - void FinishResidualCalc(const CConfig* config) override { - if (config->GetSBSParam().StochasticBackscatter && config->GetSBSParam().SBS_Ctau > 0.0) { - for (unsigned short iVar = 1; iVar < nVar; iVar++) { - Flux[iVar] = (a0 + a1) * 0.5 * (ScalarVar_i[iVar] + ScalarVar_j[iVar]); - Jacobian_i[iVar][iVar] = 0.5 * (a0+a1); - Jacobian_j[iVar][iVar] = 0.5 * (a0+a1); - } - } - Flux[0] = a0*ScalarVar_i[0] + a1*ScalarVar_j[0]; - Jacobian_i[0][0] = a0; - Jacobian_j[0][0] = a1; - } - -public: - /*! - * \brief Constructor of the class. - * \param[in] val_nDim - Number of dimensions of the problem. - * \param[in] val_nVar - Number of variables of the problem. - * \param[in] config - Definition of the particular problem. - */ - CUpwSca_TurbSA(unsigned short val_nDim, unsigned short val_nVar, const CConfig* config) - : CUpwScalar(val_nDim, val_nVar, config) { bounded_scalar = config->GetBounded_Turb(); } -}; - -/*! - * \class CUpwSca_TurbSST - * \brief Class for doing a scalar upwind solver for the Menter SST turbulence model equations. - * \ingroup ConvDiscr - * \author A. Campos. - */ -template -class CUpwSca_TurbSST final : public CUpwScalar { -private: - using Base = CUpwScalar; - using Base::nDim; - using Base::V_i; - using Base::V_j; - using Base::a0; - using Base::a1; - using Base::Flux; - using Base::Jacobian_i; - using Base::Jacobian_j; - using Base::ScalarVar_i; - using Base::ScalarVar_j; - using Base::idx; - using Base::bounded_scalar; - - /*! - * \brief Adds any extra variables to AD - */ - void ExtraADPreaccIn() override {} - - /*! - * \brief SST specific steps in the ComputeResidual method - * \param[in] config - Definition of the particular problem. - */ - void FinishResidualCalc(const CConfig* config) override { - Flux[0] = a0*V_i[idx.Density()]*ScalarVar_i[0] + a1*V_j[idx.Density()]*ScalarVar_j[0]; - Flux[1] = a0*V_i[idx.Density()]*ScalarVar_i[1] + a1*V_j[idx.Density()]*ScalarVar_j[1]; - - Jacobian_i[0][0] = a0; Jacobian_i[0][1] = 0.0; - Jacobian_i[1][0] = 0.0; Jacobian_i[1][1] = a0; - - Jacobian_j[0][0] = a1; Jacobian_j[0][1] = 0.0; - Jacobian_j[1][0] = 0.0; Jacobian_j[1][1] = a1; - } - -public: - /*! - * \brief Constructor of the class. - * \param[in] val_nDim - Number of dimensions of the problem. - * \param[in] val_nVar - Number of variables of the problem. - * \param[in] config - Definition of the particular problem. - */ - CUpwSca_TurbSST(unsigned short val_nDim, unsigned short val_nVar, const CConfig* config) - : CUpwScalar(val_nDim, val_nVar, config) { bounded_scalar = config->GetBounded_Turb(); } -}; diff --git a/SU2_CFD/include/numerics/turbulent/turb_diffusion.hpp b/SU2_CFD/include/numerics/turbulent/turb_diffusion.hpp deleted file mode 100644 index a4b2bbe264b0..000000000000 --- a/SU2_CFD/include/numerics/turbulent/turb_diffusion.hpp +++ /dev/null @@ -1,346 +0,0 @@ -/*! - * \file turb_diffusion.hpp - * \brief Declarations of numerics classes for discretization of - * viscous fluxes in turbulence problems. - * \author F. Palacios, T. Economon - * \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 . - */ -#pragma once - -#include "../scalar/scalar_diffusion.hpp" - -/*! - * \class CAvgGrad_TurbSA - * \brief Class for computing viscous term using average of gradients (Spalart-Allmaras Turbulence model). - * \ingroup ViscDiscr - * \author A. Bueno. - */ -template -class CAvgGrad_TurbSA final : public CAvgGrad_Scalar { -private: - using Base = CAvgGrad_Scalar; - using Base::Laminar_Viscosity_i; - using Base::Laminar_Viscosity_j; - using Base::Density_i; - using Base::Density_j; - using Base::ScalarVar_i; - using Base::ScalarVar_j; - using Base::Proj_Mean_GradScalarVar; - using Base::proj_vector_ij; - using Base::Flux; - using Base::Jacobian_i; - using Base::Jacobian_j; - - const su2double sigma = 2.0/3.0; - const su2double cb2 = 0.622; - - const bool use_accurate_jacobians; - - /*! - * \brief Adds any extra variables to AD - */ - void ExtraADPreaccIn() override {} - - /*! - * \brief SA specific steps in the ComputeResidual method - * \param[in] config - Definition of the particular problem. - */ - void FinishResidualCalc(const CConfig* config) override { - const bool implicit = config->GetKind_TimeIntScheme() == EULER_IMPLICIT; - - /*--- Compute mean effective viscosity ---*/ - - /*--- First Term. Normal diffusion, and conservative part of the quadratic diffusion. - * ||grad nu_t||^2 = div(nu_t grad nu_t) - nu_t div grad nu_t ---*/ - const su2double nu_i = Laminar_Viscosity_i/Density_i; - const su2double nu_j = Laminar_Viscosity_j/Density_j; - const su2double nu_e = 0.5 * (nu_i + nu_j + (1 + cb2) * (ScalarVar_i[0] + ScalarVar_j[0])); - const su2double term_1 = nu_e; - - /* Second Term (quadratic diffusion, non conservative). */ - const su2double nu_tilde_i = ScalarVar_i[0]; - const su2double term_2 = cb2 * nu_tilde_i; - - const su2double diffusion_coefficient = term_1 - term_2; - Flux[0] = diffusion_coefficient * Proj_Mean_GradScalarVar[0] / sigma; - - if (implicit) { - /*--- For Jacobians -> Use of TSL approx. to compute derivatives of the gradients ---*/ - Jacobian_i[0][0] = -diffusion_coefficient * proj_vector_ij / sigma; - Jacobian_j[0][0] = diffusion_coefficient * proj_vector_ij / sigma; - - if (use_accurate_jacobians) { - /*--- The diffusion coefficient is also a function of nu_t. ---*/ - const su2double dTerm1_dnut_i = (1 + cb2) * 0.5; - const su2double dTerm1_dnut_j = (1 + cb2) * 0.5; - - const su2double dTerm2_dnut_i = cb2; - const su2double dTerm2_dnut_j = 0.0; - - const su2double dDC_dnut_i = dTerm1_dnut_i - dTerm2_dnut_i; - const su2double dDC_dnut_j = dTerm1_dnut_j - dTerm2_dnut_j; - - Jacobian_i[0][0] += dDC_dnut_i * Proj_Mean_GradScalarVar[0] / sigma; - Jacobian_j[0][0] += dDC_dnut_j * Proj_Mean_GradScalarVar[0] / sigma; - } - } - } - -public: - /*! - * \brief Constructor of the class. - * \param[in] val_nDim - Number of dimensions of the problem. - * \param[in] val_nVar - Number of variables of the problem. - * \param[in] correct_grad - Whether to correct gradient for skewness. - * \param[in] config - Definition of the particular problem. - */ - CAvgGrad_TurbSA(unsigned short val_nDim, unsigned short val_nVar, - bool correct_grad, const CConfig* config) - : CAvgGrad_Scalar(val_nDim, val_nVar, correct_grad, config), - use_accurate_jacobians(config->GetUse_Accurate_Turb_Jacobians()) {} -}; - -/*! - * \class CAvgGrad_TurbSA_Neg - * \brief Class for computing viscous term using average of gradients (Spalart-Allmaras Turbulence model). - * \ingroup ViscDiscr - * \author F. Palacios - */ -template -class CAvgGrad_TurbSA_Neg final : public CAvgGrad_Scalar { -private: - using Base = CAvgGrad_Scalar; - using Base::Laminar_Viscosity_i; - using Base::Laminar_Viscosity_j; - using Base::Density_i; - using Base::Density_j; - using Base::ScalarVar_i; - using Base::ScalarVar_j; - using Base::Proj_Mean_GradScalarVar; - using Base::proj_vector_ij; - using Base::Flux; - using Base::Jacobian_i; - using Base::Jacobian_j; - - const su2double sigma = 2.0/3.0; - const su2double cn1 = 16.0; - const su2double cb2 = 0.622; - - /*! - * \brief Adds any extra variables to AD - */ - void ExtraADPreaccIn() override {} - - /*! - * \brief SA-neg specific steps in the ComputeResidual method - * \param[in] config - Definition of the particular problem. - */ - void FinishResidualCalc(const CConfig* config) override { - const bool implicit = config->GetKind_TimeIntScheme() == EULER_IMPLICIT; - - /*--- Compute mean effective viscosity ---*/ - - const su2double nu_i = Laminar_Viscosity_i/Density_i; - const su2double nu_j = Laminar_Viscosity_j/Density_j; - - const su2double nu_ij = 0.5 * (nu_i + nu_j); - const su2double nu_tilde_i = ScalarVar_i[0]; - const su2double nu_tilde_j = ScalarVar_j[0]; - const su2double nu_tilde_ij = 0.5 * (nu_tilde_i + nu_tilde_j); - - /*--- Following Diskin's implementation from 10.2514/1.J064629, they propose a new fn function - * to be evaluated at the cell to maintain positivity in the diffusion coefficient, which is - * used in both terms. The new fn term averaged across the face reverts to the original fn - * function. ---*/ - - /*--- Second Term (LHS) ---*/ - const su2double zeta_i = ((1 + cb2) * nu_tilde_ij - cb2 * nu_tilde_i) / nu_ij; - su2double fn_i = 1.0; - if (zeta_i < 0.0) { - fn_i = (cn1 + pow(zeta_i,3)) / (cn1 - pow(zeta_i,3)); - } - - const su2double term_1 = (nu_ij + (1 + cb2) * nu_tilde_ij * fn_i); - const su2double term_2 = cb2 * nu_tilde_i * fn_i; - Flux[0] = (term_1 - term_2) * Proj_Mean_GradScalarVar[0] / sigma; - - /*--- For Jacobians -> Use of TSL approx. to compute derivatives of the gradients - * Exact Jacobians were tested on multiple cases but resulted in divergence of all - * simulations, hence only frozen diffusion coefficient (approximate) Jacobians are used. ---*/ - - if (implicit) { - const su2double diffusion_coefficient = (term_1 - term_2); - - const su2double dGrad_dnut_i = -proj_vector_ij; - const su2double dGrad_dnut_j = proj_vector_ij; - - Jacobian_i[0][0] = diffusion_coefficient * dGrad_dnut_i / sigma; - Jacobian_j[0][0] = diffusion_coefficient * dGrad_dnut_j / sigma; - } - } - -public: - /*! - * \brief Constructor of the class. - * \param[in] val_nDim - Number of dimensions of the problem. - * \param[in] val_nVar - Number of variables of the problem. - * \param[in] correct_grad - Whether to correct gradient for skewness. - * \param[in] config - Definition of the particular problem. - */ - CAvgGrad_TurbSA_Neg(unsigned short val_nDim, unsigned short val_nVar, - bool correct_grad, const CConfig* config) - : CAvgGrad_Scalar(val_nDim, val_nVar, correct_grad, config) {} -}; - -/*! - * \class CAvgGrad_TurbSST - * \brief Class for computing viscous term using average of gradient with correction (Menter SST turbulence model). - * \ingroup ViscDiscr - * \author A. Bueno. - */ -template -class CAvgGrad_TurbSST final : public CAvgGrad_Scalar { -private: - using Base = CAvgGrad_Scalar; - using Base::Laminar_Viscosity_i; - using Base::Laminar_Viscosity_j; - using Base::Eddy_Viscosity_i; - using Base::Eddy_Viscosity_j; - using Base::Density_i; - using Base::Density_j; - using Base::ScalarVar_i; - using Base::ScalarVar_j; - using Base::Proj_Mean_GradScalarVar; - using Base::proj_vector_ij; - using Base::Flux; - using Base::Jacobian_i; - using Base::Jacobian_j; - - const su2double sigma_k1; /*!< \brief Constants for the viscous terms, k-w (1), k-eps (2)*/ - const su2double sigma_k2; - const su2double sigma_om1; - const su2double sigma_om2; - const bool use_accurate_jacobians; - - su2double F1_i, F1_j; /*!< \brief Menter's first blending function */ - - /*! - * \brief Adds any extra variables to AD - */ - void ExtraADPreaccIn() override { - AD::SetPreaccIn(F1_i, F1_j); - } - - /*! - * \brief SST specific steps in the ComputeResidual method - * \param[in] config - Definition of the particular problem. - */ - void FinishResidualCalc(const CConfig* config) override { - const bool implicit = config->GetKind_TimeIntScheme() == EULER_IMPLICIT; - - /*--- Compute the blended constant for the viscous terms ---*/ - const su2double sigma_kine_i = F1_i*sigma_k1 + (1.0 - F1_i)*sigma_k2; - const su2double sigma_kine_j = F1_j*sigma_k1 + (1.0 - F1_j)*sigma_k2; - const su2double sigma_omega_i = F1_i*sigma_om1 + (1.0 - F1_i)*sigma_om2; - const su2double sigma_omega_j = F1_j*sigma_om1 + (1.0 - F1_j)*sigma_om2; - - /*--- Compute mean effective dynamic viscosity ---*/ - const su2double diff_i_kine = Laminar_Viscosity_i + sigma_kine_i*Eddy_Viscosity_i; - const su2double diff_j_kine = Laminar_Viscosity_j + sigma_kine_j*Eddy_Viscosity_j; - const su2double diff_i_omega = Laminar_Viscosity_i + sigma_omega_i*Eddy_Viscosity_i; - const su2double diff_j_omega = Laminar_Viscosity_j + sigma_omega_j*Eddy_Viscosity_j; - - const su2double diff_kine = 0.5*(diff_i_kine + diff_j_kine); - const su2double diff_omega_T1 = 0.5*(diff_i_omega + diff_j_omega); - - /*--- We aim to treat the cross-diffusion as a diffusion term rather than a source term. - * Re-writing the cross-diffusion contribution as λ/w ∇w ∇k, where λ = (2 (1- F1) ρ σ_ω2) - * and expanding using the product rule for divergence theorem gives: ∇(w λ/w ∇k) - w ∇(λ/w ∇k). - * Discretising using FVM, gives: (λ)_ij ∇k - w_c (λ/w)_ij ∇k. where w_c is the cell centre value ---*/ - - const su2double lambda_i = 2 * (1 - F1_i) * Density_i * sigma_omega_i; - const su2double lambda_j = 2 * (1 - F1_j) * Density_j * sigma_omega_j; - const su2double lambda_ij = 0.5 * (lambda_i + lambda_j); - const su2double w_ij = 0.5 * (ScalarVar_i[1] + ScalarVar_j[1]); - - const su2double diff_omega_T2 = lambda_ij; - - const su2double diff_omega_T3 = -ScalarVar_i[1] * lambda_ij/w_ij; - - Flux[0] = diff_kine*Proj_Mean_GradScalarVar[0]; - Flux[1] = diff_omega_T1*Proj_Mean_GradScalarVar[1] + (diff_omega_T2 + diff_omega_T3)*Proj_Mean_GradScalarVar[0]; - - /*--- For Jacobians -> Use of TSL (Thin Shear Layer) approx. to compute derivatives of the gradients ---*/ - if (implicit) { - const su2double proj_on_rho_i = proj_vector_ij/Density_i; - const su2double proj_on_rho_j = proj_vector_ij/Density_j; - Jacobian_i[0][0] = -diff_kine*proj_on_rho_i; - Jacobian_i[0][1] = 0.0; - Jacobian_i[1][0] = (diff_omega_T2+diff_omega_T3)*-proj_on_rho_i; - Jacobian_i[1][1] = -diff_omega_T1*proj_on_rho_i; - - Jacobian_j[0][0] = diff_kine*proj_on_rho_j; - Jacobian_j[0][1] = 0.0; - Jacobian_j[1][0] = (diff_omega_T2+diff_omega_T3)*proj_on_rho_j; - Jacobian_j[1][1] = diff_omega_T1*proj_on_rho_j; - - if (use_accurate_jacobians) { - Jacobian_i[0][0] = -diff_kine*proj_on_rho_i; - Jacobian_i[0][1] = 0.0; - Jacobian_i[1][0] = (diff_omega_T2 + diff_omega_T3)*-proj_on_rho_i; - Jacobian_i[1][1] = -proj_on_rho_i * diff_omega_T1 - 2*lambda_ij*ScalarVar_j[1]/pow(ScalarVar_i[1]+ScalarVar_j[1],2) * Proj_Mean_GradScalarVar[0]; - - Jacobian_j[0][0] = diff_kine*proj_on_rho_j; - Jacobian_j[0][1] = 0.0; - Jacobian_j[1][0] = (diff_omega_T2 + diff_omega_T3)*proj_on_rho_j; - Jacobian_j[1][1] = proj_on_rho_j * diff_omega_T1 + 2*lambda_ij*ScalarVar_i[1]/pow(ScalarVar_i[1]+ScalarVar_j[1],2) * Proj_Mean_GradScalarVar[0]; - } - } - } - -public: - /*! - * \brief Constructor of the class. - * \param[in] val_nDim - Number of dimensions of the problem. - * \param[in] val_nVar - Number of variables of the problem. - * \param[in] constants - Constants of the model. - * \param[in] correct_grad - Whether to correct gradient for skewness. - * \param[in] config - Definition of the particular problem. - */ - CAvgGrad_TurbSST(unsigned short val_nDim, unsigned short val_nVar, - const su2double* constants, bool correct_grad, const CConfig* config) - : CAvgGrad_Scalar(val_nDim, val_nVar, correct_grad, config), - sigma_k1(constants[0]), - sigma_k2(constants[1]), - sigma_om1(constants[2]), - sigma_om2(constants[3]), - use_accurate_jacobians(config->GetUse_Accurate_Turb_Jacobians()) { - } - - /*! - * \brief Sets value of first blending function. - */ - void SetF1blending(su2double val_F1_i, su2double val_F1_j) override { - F1_i = val_F1_i; F1_j = val_F1_j; - } -}; diff --git a/SU2_CFD/include/numerics/turbulent/turb_sa_edge_flux.hpp b/SU2_CFD/include/numerics/turbulent/turb_sa_edge_flux.hpp new file mode 100644 index 000000000000..978b0aaaf583 --- /dev/null +++ b/SU2_CFD/include/numerics/turbulent/turb_sa_edge_flux.hpp @@ -0,0 +1,183 @@ +/*! + * \file turb_sa_edge_flux.hpp + * \brief Spalart-Allmaras model as a third-layer scalar flux, see numerics/scalar/scalar_edge_flux.hpp. + * \author P. Gomes + * \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 . + */ + +#pragma once + +#include "../scalar/scalar_edge_flux.hpp" + +/*! + * \class CScalarFlux_SA + * \ingroup ConvDiscr + * \ingroup ViscDiscr + * \brief Convection and diffusion of the Spalart-Allmaras model, non-conservative and with a + * diagonal (but asymmetric) diffusion coefficient. + * \note SA writes its own convective term rather than using the inherited CUpwScalarFlux one, + * because with stochastic backscatter active (nVar 4) the three Langevin equations are + * advected with a centered flux, unlike the plain upwind SA equation itself. + */ +template +class CScalarFlux_SA + : public CUpwScalarBase, FlowIndices, nDim, nVar> { + public: + static constexpr bool Conservative = false; + static constexpr bool DiagonalDiffusion = true; + static constexpr bool DiffusionReadsDensity = true; /*!< \brief The kinematic viscosities below. */ + + using Base = CUpwScalarBase; + using Int = typename Base::Int; + + explicit CScalarFlux_SA(const CConfig& config) + : Base(config), + negativeSA(config.GetSAParsedOptions().version == SA_OPTIONS::NEG), + accurateJacobians(config.GetUse_Accurate_Turb_Jacobians()) {} + + private: + static constexpr passivedouble sigma = 2.0 / 3.0; /*!< \brief Constant of the diffusion term. */ + static constexpr passivedouble cb2 = 0.622; /*!< \brief Constant of the diffusion term. */ + static constexpr passivedouble cn1 = 16.0; /*!< \brief Constant of the SA-neg diffusion correction. */ + + /*!< \brief Whether nu_tilde may go negative (SA_OPTIONS= NEGATIVE), which needs the fn-corrected + * diffusion coefficient below to keep the diffusion term from turning anti-diffusive. */ + const bool negativeSA; + const bool accurateJacobians; + + public: + /*! + * \brief SA convection, plus the centered advection of the backscatter equations when nVar > 1. + */ + template + FORCEINLINE void finalizeFlux(const FlowIndices&, const ScalarFluxOptions& opt, Int, const EdgeSide&, + Int, const EdgeSide&, const Double& a0, const Double& a1, + const CPair&, const CPair>& phi, + EdgeResidual& res) const { + const Double flux = a0 * phi.i.all(0) + a1 * phi.j.all(0); + + res.flux_i(0) += flux; + if (!opt.oneSided) res.flux_j(0) -= flux; + + if (opt.implicit) { + res.jac_ii(0, 0) += a0; + if (!opt.oneSided) { + res.jac_ij(0, 0) += a1; + res.jac_ji(0, 0) -= a0; + res.jac_jj(0, 0) -= a1; + } + } + + /*--- Stochastic backscatter: three Langevin equations, advected with the mean of the two + * upwinding weights and with no diffusion. ---*/ + const Double avg = 0.5 * (a0 + a1); + for (size_t iVar = 1; iVar < res.nVar; ++iVar) { + const Double flux_bs = avg * (phi.i.all(iVar) + phi.j.all(iVar)); + + res.flux_i(iVar) += flux_bs; + if (!opt.oneSided) res.flux_j(iVar) -= flux_bs; + + if (opt.implicit) { + res.jac_ii(iVar, iVar) += avg; + if (!opt.oneSided) { + res.jac_ij(iVar, iVar) += avg; + res.jac_ji(iVar, iVar) -= avg; + res.jac_jj(iVar, iVar) -= avg; + } + } + } + } + + /*! + * \brief fn, the positivity-preserving correction to the SA-neg diffusion coefficient + * (Allmaras, Johnson & Spalart), 1 when nu_tilde is not negative enough to need it. + */ + FORCEINLINE Double fn(const Double& zeta) const { + if (!negativeSA || zeta >= 0.0) return 1.0; + const Double zeta3 = zeta * zeta * zeta; + return (cn1 + zeta3) / (cn1 - zeta3); + } + + /*! + * \brief Diffusion coefficients of both orientations of the edge. + * \note The coefficient is not symmetric: it uses the transported variable of the row it is + * going to be used for (the quadratic, non-conservative part of the diffusion term). + * Coefficients past index 0 are left at zero, the backscatter equations have no diffusion. + */ + template + FORCEINLINE CPair> coefficients(const FlowIndices& idx, Int iPoint, + const EdgeSide& side_i, Int jPoint, + const EdgeSide& side_j, + const CPair& rho) const { + const Double nu_i = gatherVariables(iPoint, side_i.flowNodes->GetPrimitive(), idx.LaminarViscosity()) / rho.i; + const Double nu_j = gatherVariables(jPoint, side_j.flowNodes->GetPrimitive(), idx.LaminarViscosity()) / rho.j; + + const Double nuTilde_i = gatherVariables(iPoint, side_i.scalarNodes.GetSolution(), 0); + const Double nuTilde_j = gatherVariables(jPoint, side_j.scalarNodes.GetSolution(), 0); + + const Double nu_ij = 0.5 * (nu_i + nu_j); + const Double nuTilde_ij = 0.5 * (nuTilde_i + nuTilde_j); + + /*--- fn is only ever != 1 under SA_OPTIONS= NEGATIVE, and then only where the row's own + * nu_tilde pulls the coefficient negative; without it (nu + nu_tilde going anti-diffusive) + * the equation diverges, see Allmaras, Johnson & Spalart's negative SA modification. ---*/ + const Double fn_i = fn(((1.0 + cb2) * nuTilde_ij - cb2 * nuTilde_i) / nu_ij); + const Double fn_j = fn(((1.0 + cb2) * nuTilde_ij - cb2 * nuTilde_j) / nu_ij); + + Vector D_i, D_j; + D_i(0) = (nu_ij + (1.0 + cb2) * nuTilde_ij * fn_i - cb2 * nuTilde_i * fn_i) / sigma; + D_j(0) = (nu_ij + (1.0 + cb2) * nuTilde_ij * fn_j - cb2 * nuTilde_j * fn_j) / sigma; + for (size_t iVar = 1; iVar < nVar; ++iVar) { + D_i(iVar) = 0.0; + D_j(iVar) = 0.0; + } + return {D_i, D_j}; + } + + /*! + * \brief Extra Jacobian terms from the dependence of the diffusion coefficient on nu_tilde. + * \note Skipped for SA-neg, whose fn-corrected coefficient was found (upstream, pre-migration) + * to diverge with exact Jacobians; frozen (TSL-only) Jacobians are used there instead. + * Skipped for standard SA too unless USE_ACCURATE_TURB_JACOBIANS is set, matching the + * pre-migration default of using frozen Jacobians there as well. + */ + template + FORCEINLINE void coefficientJacobians(const ScalarFluxOptions& opt, const Coefficients&, + const Vector& projGrad, EdgeResidual& res) const { + if (negativeSA || !accurateJacobians) return; + + /*--- d(diffusion coefficient of i)/d(nu_tilde_i), and its counterpart w.r.t. nu_tilde_j; + * the coefficient of j is the same expression with i and j swapped, so the same two + * derivatives apply to both orientations. Both are per-edge constants, so the coefficients + * themselves are not read here. ---*/ + const Double dDC_dNuTilde_i = ((1.0 + cb2) * 0.5 - cb2) / sigma; + const Double dDC_dNuTilde_j = (1.0 + cb2) * 0.5 / sigma; + + res.jac_ii(0, 0) -= dDC_dNuTilde_i * projGrad(0); + if (opt.oneSided) return; + + res.jac_ij(0, 0) -= dDC_dNuTilde_j * projGrad(0); + res.jac_ji(0, 0) += dDC_dNuTilde_j * projGrad(0); + res.jac_jj(0, 0) += dDC_dNuTilde_i * projGrad(0); + } +}; diff --git a/SU2_CFD/include/numerics/turbulent/turb_sources.hpp b/SU2_CFD/include/numerics/turbulent/turb_sources.hpp index 2c0cb51a8d21..9f72b6e37cc4 100644 --- a/SU2_CFD/include/numerics/turbulent/turb_sources.hpp +++ b/SU2_CFD/include/numerics/turbulent/turb_sources.hpp @@ -1014,7 +1014,7 @@ class CSourcePieceWise_TurbSST final : public CNumerics { Residual[0] -= dk * Volume; Residual[1] -= dw * Volume; - /*--- Cross diffusion is included in the viscous fluxes, discretisation in turb_diffusion.hpp ---*/ + /*--- Cross diffusion is included in the viscous fluxes, not this source term. ---*/ /*--- Contribution due to 2D axisymmetric formulation ---*/ diff --git a/SU2_CFD/include/numerics/turbulent/turb_sst_edge_flux.hpp b/SU2_CFD/include/numerics/turbulent/turb_sst_edge_flux.hpp new file mode 100644 index 000000000000..b891b6e14d5c --- /dev/null +++ b/SU2_CFD/include/numerics/turbulent/turb_sst_edge_flux.hpp @@ -0,0 +1,147 @@ +/*! + * \file turb_sst_edge_flux.hpp + * \brief Menter SST model as a third-layer scalar flux, see numerics/scalar/scalar_edge_flux.hpp. + * \author P. Gomes + * \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 . + */ + +#pragma once + +#include "../scalar/scalar_edge_flux.hpp" + +/*! + * \class CScalarFlux_SST + * \ingroup ViscDiscr + * \brief Convection and diffusion of the Menter SST model, conservative with a coupled (but + * neither symmetric nor diagonal) 2x2 diffusion matrix. + * \note SST writes no finalizeFlux of its own: the inherited CUpwScalarFlux one is exactly + * flux(iVar) = a0*rho_i*phi_i(iVar) + a1*rho_j*phi_j(iVar), Conservative weighting by + * density, which is the model's whole convective term. + */ +template +class CScalarFlux_SST + : public CUpwScalarBase, FlowIndices, nDim, nVar> { + public: + static constexpr bool Conservative = true; + static constexpr bool DiagonalDiffusion = false; + + using Base = CUpwScalarBase; + using Int = typename Base::Int; + using Base::Base; + + private: + /*--- Fixed regardless of SST_OPTIONS::version: only the production-limiter and source-term + * constants (alfa/gamma) differ by version, not these. ---*/ + static constexpr passivedouble sigma_k1 = 0.85; + static constexpr passivedouble sigma_k2 = 1.0; + static constexpr passivedouble sigma_om1 = 0.5; + static constexpr passivedouble sigma_om2 = 0.856; + + public: + /*! + * \brief Diffusion coefficients of both orientations of the edge, and the terms of the cross + * diffusion that the Jacobian correction below needs, so that neither the gathers nor + * the blending are repeated for it. + * \note The cross term reads the transported omega of whichever point its row is being written + * for, so it is not symmetric: i, read by i's row, uses omega at i; j, read by j's row, + * uses omega at j. Every other entry is an i/j average, so it is the same in both. + */ + struct CCoefficients { + Matrix i, j; + Double lambda_ij, omega_i, omega_j; + }; + + template + FORCEINLINE CCoefficients coefficients(const FlowIndices& idx, Int iPoint, const EdgeSide& side_i, + Int jPoint, const EdgeSide& side_j, + const CPair& rho) const { + const Double mu_i = gatherVariables(iPoint, side_i.flowNodes->GetPrimitive(), idx.LaminarViscosity()); + const Double mu_j = gatherVariables(jPoint, side_j.flowNodes->GetPrimitive(), idx.LaminarViscosity()); + const Double muT_i = gatherVariables(iPoint, side_i.flowNodes->GetPrimitive(), idx.EddyViscosity()); + const Double muT_j = gatherVariables(jPoint, side_j.flowNodes->GetPrimitive(), idx.EddyViscosity()); + + const Double F1_i = gatherVariables(iPoint, side_i.scalarNodes.GetF1blending()); + const Double F1_j = gatherVariables(jPoint, side_j.scalarNodes.GetF1blending()); + + CCoefficients D; + D.omega_i = gatherVariables(iPoint, side_i.scalarNodes.GetSolution(), 1); + D.omega_j = gatherVariables(jPoint, side_j.scalarNodes.GetSolution(), 1); + + const Double sigma_kine_i = F1_i * sigma_k1 + (1.0 - F1_i) * sigma_k2; + const Double sigma_kine_j = F1_j * sigma_k1 + (1.0 - F1_j) * sigma_k2; + const Double sigma_omega_i = F1_i * sigma_om1 + (1.0 - F1_i) * sigma_om2; + const Double sigma_omega_j = F1_j * sigma_om1 + (1.0 - F1_j) * sigma_om2; + + const Double diff_kine = 0.5 * ((mu_i + sigma_kine_i * muT_i) + (mu_j + sigma_kine_j * muT_j)); + const Double diff_omega = 0.5 * ((mu_i + sigma_omega_i * muT_i) + (mu_j + sigma_omega_j * muT_j)); + + const Double lambda_i = 2.0 * (1.0 - F1_i) * rho.i * sigma_omega_i; + const Double lambda_j = 2.0 * (1.0 - F1_j) * rho.j * sigma_omega_j; + D.lambda_ij = 0.5 * (lambda_i + lambda_j); + const Double w_ij = 0.5 * (D.omega_i + D.omega_j); + + /*--- Cross-diffusion coefficient: a divergence-theorem term (diff_omega_T2) plus a cell + * centre correction (diff_omega_T3) that reads the transported omega of the row's own point. ---*/ + const Double diff_omega_T2 = D.lambda_ij; + const Double diff_omega_T3_i = -D.omega_i * D.lambda_ij / w_ij; + const Double diff_omega_T3_j = -D.omega_j * D.lambda_ij / w_ij; + + /*--- D.i(0,1) and D.j(0,1) are left zero: there is no diffusive coupling from omega into + * the k row. ---*/ + D.i = Double(0.0); + D.j = Double(0.0); + D.i(0, 0) = diff_kine; + D.i(1, 1) = diff_omega; + D.i(1, 0) = diff_omega_T2 + diff_omega_T3_i; + + D.j(0, 0) = diff_kine; + D.j(1, 1) = diff_omega; + D.j(1, 0) = diff_omega_T2 + diff_omega_T3_j; + + return D; + } + + /*! + * \brief Extra Jacobian terms from the dependence of the cross-diffusion coefficient on omega. + * \note diff_omega_T3_i and diff_omega_T3_j both depend on omega_i and omega_j through w_ij, so + * each of the four blocks needs a correction beyond the one diffusionTerms already applies + * through projGrad. The correction only depends on which point's omega is being + * differentiated against, not on which row it lands in: differentiating against omega_i + * gives +E_j in both jac_ii and jac_ji, differentiating against omega_j gives -E_i in both + * jac_ij and jac_jj. + */ + template + FORCEINLINE void coefficientJacobians(const ScalarFluxOptions& opt, const CCoefficients& D, + const Vector& projGrad, EdgeResidual& res) const { + const Double denom = pow(D.omega_i + D.omega_j, 2.0); + const Double E_i = 2.0 * D.lambda_ij * D.omega_i / denom * projGrad(0); + const Double E_j = 2.0 * D.lambda_ij * D.omega_j / denom * projGrad(0); + + res.jac_ii(1, 1) += E_j; + if (opt.oneSided) return; + + res.jac_ij(1, 1) -= E_i; + res.jac_ji(1, 1) += E_j; + res.jac_jj(1, 1) -= E_i; + } +}; diff --git a/SU2_CFD/include/numerics/util.hpp b/SU2_CFD/include/numerics/util.hpp new file mode 100644 index 000000000000..92e5144b9c93 --- /dev/null +++ b/SU2_CFD/include/numerics/util.hpp @@ -0,0 +1,638 @@ +/*! + * \file util.hpp + * \brief Generic auxiliary functions. + * \author P. Gomes + * \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 . + */ + +#pragma once + +#include + +#include "../../../Common/include/option_structure.hpp" +#include "../../../Common/include/parallelization/vectorization.hpp" +#include "../../../Common/include/containers/C2DContainer.hpp" +#include "../../../Common/include/linear_algebra/CSysVector.hpp" +#include "../../../Common/include/linear_algebra/CSysMatrix.hpp" + +/*! + * \enum UpdateType + * \brief Ways to update vectors and system matrices. + * COLORING is the typical i/j update, whereas for REDUCTION + * the fluxes are stored and the matrix diagonal is not modified. + */ +enum class UpdateType { COLORING, REDUCTION }; + +#ifdef CODI_FORWARD_TYPE +using SparseMatrixType = CSysMatrix; +#else +using SparseMatrixType = CSysMatrix; +#endif + +/*! + * \brief Alignment of the static containers backing a flux value type. + * \note Yields the type's own alignment for a SIMD array, and the plain type's natural + * alignment for a scalar; C2DContainer's AlignSize also accepts 0 to mean its own + * default, but that reaches `alignas(0)` on the static specializations, which some + * compilers warn about even though it is a no-op, so a real value is passed instead. + */ +template +struct CAlignTraits { + enum : size_t { Align = alignof(Type) }; +}; + +template +struct CAlignTraits> { + enum : size_t { Align = simd::Array::Align }; +}; + +/*! + * \brief Static vector and matrix types. + * \note These should be used instead of C-style arrays. + */ +template +using Vector = C2DContainer::Align, Size, 1>; + +template +using Matrix = C2DContainer::Align, Rows, Cols>; + +/*! + * \brief The flux value type and lane count that go with an index type. + * \note There is exactly one floating type in play, su2double, active under AD; a plain + * integral index reads one of it, a lane-vector index reads a lane-vector of it. This + * is what lets every helper below deduce its value type from the index it is handed, + * instead of a caller naming it explicitly. + */ +template +struct CValueTraits; + +template <> +struct CValueTraits { + using Double = su2double; + static constexpr size_t Size = 1; +}; + +template +struct CValueTraits> { + using Double = simd::Array; + static constexpr size_t Size = N; +}; + +/*! + * \brief Index type and lane count that go with a flux value type, the converse of + * CValueTraits, used where the value type is already known (e.g. a class template + * parameter) and the index type is what needs deriving. + */ +template +struct CLaneTraits; + +template <> +struct CLaneTraits { + using Int = unsigned long; + static constexpr bool IsArray = false; +}; + +template +struct CLaneTraits> { + using Int = simd::Array; + static constexpr bool IsArray = true; +}; + +/*! + * \brief Constexpr version of max. + */ +inline constexpr size_t Max(size_t a, size_t b) { return a > b ? a : b; } + +/*! + * \brief Simple pair type for i/j variables. + */ +template +struct CPair { + T i, j; +}; + +/*! + * \brief Blocks a template parameter from participating in argument deduction. + * \note Deduction never applies a user conversion, so a parameter typed plain Double would + * force a caller passing a bare su2double constant (kappa, a limiter ramp) to have + * already broadcast it. Wrapping the parameter type here defers Double entirely to + * the other, genuinely deduced arguments, and the broadcast then happens as an + * ordinary implicit conversion at the call. + */ +template +struct CIdentity { + using type = T; +}; +template +using CNonDeduced = typename CIdentity::type; + +/*! + * \brief Equation count of a model whose value is only known at runtime. + */ +constexpr size_t Dynamic = size_t(-1); + +/*! + * \brief Backing size of the static arrays of a dynamic model. + * \note The scalar numerics cap the equation count at this value and error above it, so a + * configuration that fits them fits these kernels. + */ +constexpr size_t MaxScalarVar = 8; + +/*! + * \brief Residual of one edge, accumulated by the convective and the diffusive terms. + * \note flux_i and flux_j are the contributions to the rows of i and j. They are opposite + * for a conservative term and independent for a non-conservative one. The Jacobians + * map onto the ii, ij, ji and jj blocks of the edge. A dynamic model sizes the storage + * with the maximum and iterates to nVar, which the scheme sets from the solver. + */ +template +struct EdgeResidual { + /*!< \brief The Matrix a static nVar==1 model would otherwise need degenerates + * to vector-only indexing in C2DContainer (its RowMajor, one-row specialization), so the + * backing is never smaller than 2; the unused padding row/column is simply never visited, + * every loop here and in the model bounds itself to nVar, not Size. */ + static constexpr size_t Size = Max(2, (nVar_ == Dynamic) ? MaxScalarVar : nVar_); + + Vector flux_i, flux_j; + Matrix jac_ii, jac_ij, jac_ji, jac_jj; + const size_t nVar; + + /*! + * \brief Zero the terms of the equations in use, so that both terms can accumulate into them. + * \note A static model zeroes its whole storage with constant trip counts; a dynamic one + * zeroes the leading nVar rows and columns and leaves the rest of the backing untouched. + */ + FORCEINLINE explicit EdgeResidual(size_t nEqn) : nVar(nEqn) { + for (size_t iVar = 0; iVar < nVar; ++iVar) { + flux_i(iVar) = 0.0; + flux_j(iVar) = 0.0; + for (size_t jVar = 0; jVar < nVar; ++jVar) { + jac_ii(iVar, jVar) = 0.0; + jac_ij(iVar, jVar) = 0.0; + jac_ji(iVar, jVar) = 0.0; + jac_jj(iVar, jVar) = 0.0; + } + } + } +}; + +/*! + * \brief Dot product. + */ +template +FORCEINLINE auto dot(ForwardIterator iterator, const T* ptr) -> typename std::decay::type { + typename std::decay::type sum = 0.0; + for (size_t iDim = 0; iDim < nDim; ++iDim) { + sum += *(iterator++) * ptr[iDim]; + } + return sum; +} + +/*! + * \overload Dot product. + */ +template +FORCEINLINE Double dot(ForwardIterator iterator, const Vector& vector) { + return dot(iterator, vector.data()); +} + +/*! + * \overload Dot product. + */ +template +FORCEINLINE Double dot(const Vector& a, const Vector& b) { + return dot(a.data(), b.data()); +} + +/*! + * \brief Squared norm. + */ +template +FORCEINLINE auto squaredNorm(ForwardIterator iterator) -> typename std::decay::type { + typename std::decay::type sum = 0.0; + for (size_t iDim = 0; iDim < nDim; ++iDim) { + sum += pow(*(iterator++), 2); + } + return sum; +} + +/*! + * \overload Squared norm. + */ +template +FORCEINLINE Double squaredNorm(const Vector& vector) { + return squaredNorm(vector.data()); +} + +/*! + * \brief Tangential projection. + */ +template +FORCEINLINE Vector tangentProjection(const Matrix& tensor, + const Vector& unitVector) { + Vector proj; + for (size_t iDim = 0; iDim < nDim; ++iDim) proj(iDim) = dot(tensor[iDim], unitVector); + + Double normalProj = dot(proj, unitVector); + + for (size_t iDim = 0; iDim < nDim; ++iDim) proj(iDim) -= normalProj * unitVector(iDim); + + return proj; +} + +/*! + * \brief Vector norm. + */ +template +FORCEINLINE Double norm(const Vector& vector) { + return sqrt(squaredNorm(vector)); +} + +#ifndef CODI_REVERSE_TYPE +/*! + * \brief Gather a single variable, from column iVar (0 by default) of row iPoint of a + * 2D container, or from index iPoint of a 1D container. + */ +template ::Double> +FORCEINLINE Double gatherVariables(Int iPoint, const Container& vars, size_t iVar = 0) { + return vars.template get>(iPoint, iVar)(0); +} + +/*! + * \brief Gather nVar contiguous variables starting at column iVar (0 by default) of row + * iPoint of a 2D container. + */ +template ::Double> +FORCEINLINE Vector gatherVariables(Int iPoint, const Container& vars, size_t iVar = 0) { + return vars.template get>(iPoint, iVar); +} + +/*! + * \brief Gather an nRows x nCols block of a 3D container, from outer index iPoint and + * starting at middle index iRow. + */ +template ::Double> +FORCEINLINE Matrix gatherVariables(Int iPoint, const Container& vars, size_t iRow = 0) { + return vars.template get>(iPoint, iRow); +} +#else + +namespace { +/*--- Rank of a container, from the element accessors it offers: a 1D container is indexed by + * point alone, a 3D one by (point, row, column). A 3D container also answers a two-argument + * call, but with an offset sub-matrix view rather than a scalar, which is why the rank is + * detected up front instead of trying the access forms in turn. ---*/ +template +struct Is1D : std::false_type {}; +template +struct Is1D()(0ul))>> : std::true_type {}; + +template +struct Is3D : std::false_type {}; +template +struct Is3D()(0ul, 0ul, 0ul))>> : std::true_type {}; + +/*--- One lane of an index or of a gathered value, the whole thing when there are no lanes. ---*/ +FORCEINLINE unsigned long lane(unsigned long iPoint, size_t) { return iPoint; } +template +FORCEINLINE unsigned long lane(const simd::Array& iPoint, size_t k) { return iPoint[k]; } +FORCEINLINE su2double& lane(su2double& x, size_t) { return x; } +template +FORCEINLINE T& lane(simd::Array& x, size_t k) { return x[k]; } + +/*--- Register one source element as a preaccumulation input, passing it through for the copy. + * The registration has to happen here, on the reference into the container's own storage: a + * copy has a fresh identifier of its own, and registering that instead would sever the source + * from the statement EndPreacc() stores. ---*/ +FORCEINLINE const su2double& preaccIn(const su2double& value) { + AD::SetPreaccIn(value); + return value; +} +} // namespace + +template ::Double> +FORCEINLINE Double gatherVariables(Int iPoint, const Container& vars, size_t iVar = 0) { + Double x; + for (size_t k = 0; k < CValueTraits::Size; ++k) { + if constexpr (Is1D::value) + lane(x, k) = preaccIn(vars(lane(iPoint, k))); + else + lane(x, k) = preaccIn(vars(lane(iPoint, k), iVar)); + } + return x; +} + +template ::Double> +FORCEINLINE Vector gatherVariables(Int iPoint, const Container& vars, size_t iVar = 0) { + Vector x; + for (size_t i = 0; i < nVar; ++i) { + for (size_t k = 0; k < CValueTraits::Size; ++k) { + if constexpr (Is3D::value) + lane(x(i), k) = preaccIn(vars(lane(iPoint, k), iVar, i)); + else + lane(x(i), k) = preaccIn(vars(lane(iPoint, k), iVar + i)); + } + } + return x; +} + +template ::Double> +FORCEINLINE Matrix gatherVariables(Int iPoint, const Container& vars, size_t iRow = 0) { + Matrix x; + for (size_t i = 0; i < nRows; ++i) { + for (size_t j = 0; j < nCols; ++j) { + for (size_t k = 0; k < CValueTraits::Size; ++k) { + lane(x(i, j), k) = preaccIn(vars(lane(iPoint, k), iRow + i, j)); + } + } + } + return x; +} +#endif + +/*! + * \brief Register the leading nVar entries of a static vector as preaccumulation outputs. + * \note A lane vector is registered one lane at a time, a scalar in one call; a kernel therefore + * reaches this rather than AD::SetPreaccOut directly, and reads the same whichever value + * type it is bound to. + */ +template +FORCEINLINE void setPreaccOut(Vector& x, size_t nVar) { + if constexpr (!CLaneTraits::IsArray) { + AD::SetPreaccOut(x, static_cast(nVar)); + } else { + AD::SetPreaccOut(x, static_cast(nVar), Double::Size); + } +} + +/*! + * \brief Stop the AD preaccumulation. + */ +template +FORCEINLINE void stopPreacc(Vector& x) { + setPreaccOut(x, nVar); + AD::EndPreacc(); +} + +/*! + * \brief Distance vector, from point i to point j of one container. + */ +template ::Double> +FORCEINLINE Vector distanceVector(Int iPoint, Int jPoint, const Container& coords) { + return distanceVector(iPoint, coords, jPoint, coords); +} + +/*! + * \brief Distance vector, from point i of one container to point j of another. + * \note The two endpoints of a boundary flux read different containers, the solver's own + * and the marker's ghost one; the interior edge loop passes the same container twice. + */ +template ::Double> +FORCEINLINE Vector distanceVector(Int iPoint, const Container& coords_i, Int jPoint, + const Container& coords_j) { + auto coord_i = gatherVariables(iPoint, coords_i); + auto coord_j = gatherVariables(jPoint, coords_j); + Vector vector_ij; + for (size_t iDim = 0; iDim < nDim; ++iDim) { + vector_ij(iDim) = coord_j(iDim) - coord_i(iDim); + } + return vector_ij; +} + +/*! + * \brief Blended difference for U-MUSCL reconstruction. + * \param[in] gradProj - Gradient projection at point i: dot(grad_i, vector_ij). + * \param[in] delta - Centered difference: V_j - V_i. + * \param[in] kappa - Blending parameter. + * \return Blended difference for reconstruction from point i. + */ +template +FORCEINLINE Double umusclProjection(const Double& gradProj, const Double& delta, const CNonDeduced& kappa) { + /*-------------------------------------------------------------------*/ + /*--- The MUSCL kappa-scheme reconstruction is typically written: ---*/ + /*--- V_L = V_i + 0.25 * dV_ij^kap, where ---*/ + /*--- dV_ij^kap = (1-kappa) dV_ij^upw + (1+kappa) dV_ij^cen, ---*/ + /*--- dV_ij^cen = V_j - V_i, ---*/ + /*--- dV_ij^upw = 2 grad(Vi) dot vector_ij - dV_ij^cen. ---*/ + /*--- To maintain proper scaling for edge limiters, the result of ---*/ + /*--- this function is 0.5 * dV_ij^kap. ---*/ + /*-------------------------------------------------------------------*/ + return (1.0 - kappa) * gradProj + kappa * delta; +} + +/*! + * \brief Reads the gradient rows of one point as one block, gathered up front. + * \note This is what a kernel whose variable count is a compile-time constant above one wants: + * one gather of the whole nVarGrad x nDim block instead of nVarGrad of them. + */ +template +struct CGradientBlock { + using Int = typename CLaneTraits::Int; + Matrix rows; + + FORCEINLINE CGradientBlock(Int iPoint, const Gradient_t& gradient, size_t iRow) + : rows(gatherVariables(iPoint, gradient, iRow)) {} + + FORCEINLINE Double project(size_t iVar, const Vector& vector_ij) const { + return dot(rows[iVar], vector_ij); + } +}; + +/*! + * \brief Reads the gradient rows of one point one row at a time. + * \note This is what a runtime variable count forces, since the block shape would not be a + * compile-time constant, and what a single variable forces, since its block would be a + * Matrix: that shape satisfies IsVector and would silently degenerate into + * a lone scalar instead of failing to compile. + */ +template +struct CGradientRows { + using Int = typename CLaneTraits::Int; + const Int iPoint; + const Gradient_t& gradient; + const size_t iRow; + + FORCEINLINE Double project(size_t iVar, const Vector& vector_ij) const { + return dot(gatherVariables(iPoint, gradient, iRow + iVar), vector_ij); + } +}; + +/*! + * \brief Gradient reader of one point, blocked or row by row according to nVarGrad_. + */ +template +FORCEINLINE auto gradientReader(typename CLaneTraits::Int iPoint, const Gradient_t& gradient, size_t iRow) { + if constexpr (nVarGrad_ > 1) { + return CGradientBlock(iPoint, gradient, iRow); + } else { + return CGradientRows{iPoint, gradient, iRow}; + } +} + +/*! + * \brief How the reconstructed differences are limited. + */ +enum class MusclLimiter { NONE, EDGE, POINT }; + +/*! + * \brief U-MUSCL reconstruction of nVarGrad variables, from the gradient rows starting at iRow. + * \note The limiter kind is a template parameter so that the choice is made once, outside the + * loop, by the dispatching overload below. + * \param[in] iRow - Starting row of gradient (and column of limiter) to read, for reconstructing + * a slice of a larger set of gradients (e.g. only the velocity out of the primitives). + * \param[in] nVarGradRuntime - Variable count of a Dynamic model, known only at runtime; ignored + * (falling back to nVarGrad_ or VarType::nVar) when left at its default of 0. + */ +template +FORCEINLINE void muscl(typename CLaneTraits::Int iPoint, typename CLaneTraits::Int jPoint, + const Vector& vector_ij, const Gradient_t& gradient, const Limiter_t& limiter, + size_t iRow, CPair& V, const CNonDeduced& kappa, + const CNonDeduced& umusclRamp, size_t nVarGradRuntime) { + const size_t nVarGrad = nVarGrad_ > 0 ? nVarGrad_ : (nVarGradRuntime > 0 ? nVarGradRuntime : VarType::nVar); + + const auto grad_i = gradientReader(iPoint, gradient, iRow); + const auto grad_j = gradientReader(jPoint, gradient, iRow); + + for (size_t iVar = 0; iVar < nVarGrad; ++iVar) { + /*--- Centered difference, needed for the U-MUSCL projection and the edge limiter. ---*/ + const Double delta_ij = V.j.all(iVar) - V.i.all(iVar); + + /*--- U-MUSCL reconstructed differences, to be halved when applied. ---*/ + const Double proj_i = umusclRamp * umusclProjection(grad_i.project(iVar, vector_ij), delta_ij, kappa); + const Double proj_j = umusclRamp * umusclProjection(grad_j.project(iVar, vector_ij), delta_ij, kappa); + + Double lim_i = 1.0, lim_j = 1.0; + if constexpr (limiterKind == MusclLimiter::EDGE) { + const Double delta_ij_2 = pow(delta_ij, 2) + 1e-6; + lim_i = (delta_ij_2 + proj_i * delta_ij) / (pow(proj_i, 2) + delta_ij_2); + lim_j = (delta_ij_2 + proj_j * delta_ij) / (pow(proj_j, 2) + delta_ij_2); + } else if constexpr (limiterKind == MusclLimiter::POINT) { + lim_i = gatherVariables(iPoint, limiter, iRow + iVar); + lim_j = gatherVariables(jPoint, limiter, iRow + iVar); + } + + /*--- Apply reconstruction: V_L = V_i + 0.5 * lim * dV_ij^kap ---*/ + V.i.all(iVar) += 0.5 * lim_i * proj_i; + V.j.all(iVar) -= 0.5 * lim_j * proj_j; + } +} + +/*! + * \brief Reconstruct a slice of nVarGrad variables starting at column iRow, dispatching on the + * limiter type; shared by the flow and the scalar reconstructions so both call one body. + */ +template +FORCEINLINE void reconstruct(typename CLaneTraits::Int iPoint, typename CLaneTraits::Int jPoint, + const Vector& vector_ij, const Gradient_t& gradient, + const Limiter_t& limiter, LIMITER limiterType, size_t iRow, CPair& V, + const CNonDeduced& kappa, const CNonDeduced& umusclRamp, + size_t nVarGradRuntime = 0) { + switch (limiterType) { + case LIMITER::NONE: + muscl(iPoint, jPoint, vector_ij, gradient, limiter, iRow, V, kappa, umusclRamp, + nVarGradRuntime); + break; + case LIMITER::VAN_ALBADA_EDGE: + muscl(iPoint, jPoint, vector_ij, gradient, limiter, iRow, V, kappa, umusclRamp, + nVarGradRuntime); + break; + default: + muscl(iPoint, jPoint, vector_ij, gradient, limiter, iRow, V, kappa, umusclRamp, + nVarGradRuntime); + break; + } +} + +/*! + * \brief Update the matrix and right-hand-side of a linear system with one conservative flux. + */ +template ::Int> +FORCEINLINE void updateLinearSystem(Int iEdge, Int iPoint, Int jPoint, bool implicit, UpdateType updateType, + Double updateMask, const Vector& flux, + const Matrix& jac_i, const Matrix& jac_j, + CSysVector& vector, SparseMatrixType& matrix) { + if (updateType == UpdateType::COLORING) { + vector.UpdateBlocks(iPoint, jPoint, flux, updateMask); + if (implicit) { + auto wasActive = AD::BeginPassive(); + matrix.SetBlocks(iEdge, iPoint, jPoint, jac_i, jac_j, updateMask); + AD::EndPassive(wasActive); + } + } else { + vector.SetBlock(iEdge, flux, updateMask); + if (implicit) { + auto wasActive = AD::BeginPassive(); + matrix.SetBlocks(iEdge, jac_i, jac_j, updateMask); + AD::EndPassive(wasActive); + } + } +} + +/*! + * \brief Update the matrix and right-hand-side of a linear system with two independent row + * contributions and four independent Jacobian blocks. + * \note It carries a second CSysVector, the target of flux_j under UpdateType::REDUCTION and + * unused under COLORING, where both rows are written directly. + * \note The residual is the same under both update types, the Jacobian is not: REDUCTION writes + * the off-diagonal blocks only and CSysMatrix::SetDiagonalAsColumnSum then derives each + * diagonal block as minus the sum of its column, which equals the jac_ii and jac_jj computed + * here only where the flux is conservative (jac_ii == -jac_ji). A model whose diffusion + * coefficients differ between the two orientations of an edge, i.e. one that evaluates a + * non-conservative term at the point whose row it is writing, therefore converges along a + * slightly different path under the reducer, to the same solution. + */ +template ::Int> +FORCEINLINE void updateLinearSystem(Int iEdge, Int iPoint, Int jPoint, bool implicit, UpdateType updateType, + Double updateMask, const EdgeResidual& res, + CSysVector& vector, CSysVector& vectorDiff, + SparseMatrixType& matrix) { + if (updateType == UpdateType::COLORING) { + vector.AddBlock(iPoint, res.flux_i, updateMask); + vector.AddBlock(jPoint, res.flux_j, updateMask); + if (implicit) { + auto wasActive = AD::BeginPassive(); + matrix.SetBlocks(iEdge, iPoint, jPoint, res.jac_ii, res.jac_ij, res.jac_ji, res.jac_jj, updateMask); + AD::EndPassive(wasActive); + } + } else { + vector.SetBlock(iEdge, res.flux_i, updateMask); + vectorDiff.SetBlock(iEdge, res.flux_j, updateMask); + if (implicit) { + auto wasActive = AD::BeginPassive(); + matrix.SetOffDiagBlocks(iEdge, res.jac_ij, res.jac_ji, updateMask); + AD::EndPassive(wasActive); + } + } +} + +/*! + * \brief Store the (scalar) mass flux of an edge, e.g. for "bounded scalar" transport equations. + * \note No-op if "target" is null. As with CEdge's Nodes/Normal, edges within a SIMD group are + * contiguous (coloring groups are multiples of the SIMD size), so this is a plain vectorized store + * starting at iEdge[0], relying on "target" being padded to a multiple of the SIMD size. + */ +template ::Int> +FORCEINLINE void updateEdgeMassFlux(Int iEdge, const Double& massFlux, su2activevector* target) { + if (target) massFlux.store(&(*target)[iEdge[0]]); +} diff --git a/SU2_CFD/include/numerics_simd/CNumericsSIMD.hpp b/SU2_CFD/include/numerics_simd/CNumericsSIMD.hpp index a7726a6436a0..076c08517b40 100644 --- a/SU2_CFD/include/numerics_simd/CNumericsSIMD.hpp +++ b/SU2_CFD/include/numerics_simd/CNumericsSIMD.hpp @@ -28,15 +28,7 @@ #pragma once #include "../../../Common/include/parallelization/vectorization.hpp" -#include "../../../Common/include/containers/C2DContainer.hpp" - -/*! - * \enum UpdateType - * \brief Ways to update vectors and system matrices. - * COLORING is the typical i/j update, whereas for REDUCTION - * the fluxes are stored and the matrix diagonal is not modified. - */ -enum class UpdateType {COLORING, REDUCTION}; +#include "../numerics/util.hpp" /*! * \brief Define Double and Int SIMD types. @@ -45,18 +37,10 @@ using Double = simd::Array; using Int = simd::Array; /*--- Forward declare a few classes used in name only by the interface. ---*/ -template class CSysVector; -template class CSysMatrix; class CConfig; class CGeometry; class CVariable; -#ifdef CODI_FORWARD_TYPE -using SparseMatrixType = CSysMatrix; -#else -using SparseMatrixType = CSysMatrix; -#endif - /*! * \class CNumericsSIMD * \ingroup ConvDiscr diff --git a/SU2_CFD/include/numerics_simd/flow/convection/common.hpp b/SU2_CFD/include/numerics_simd/flow/convection/common.hpp index f3669d454233..800167d4b0f5 100644 --- a/SU2_CFD/include/numerics_simd/flow/convection/common.hpp +++ b/SU2_CFD/include/numerics_simd/flow/convection/common.hpp @@ -32,149 +32,6 @@ #include "../variables.hpp" #include "../../../variables/CNSVariable.hpp" -/*! - * \brief Blended difference for U-MUSCL reconstruction. - * \param[in] gradProj - Gradient projection at point i: dot(grad_i, vector_ij). - * \param[in] delta - Centered difference: V_j - V_i. - * \param[in] kappa - Blending parameter. - * \return Blended difference for reconstruction from point i. - */ -FORCEINLINE Double umusclProjection(const Double& gradProj, - const Double& delta, - const Double& kappa) { - /*-------------------------------------------------------------------*/ - /*--- The MUSCL kappa-scheme reconstruction is typically written: ---*/ - /*--- V_L = V_i + 0.25 * dV_ij^kap, where ---*/ - /*--- dV_ij^kap = (1-kappa) dV_ij^upw + (1+kappa) dV_ij^cen, ---*/ - /*--- dV_ij^cen = V_j - V_i, ---*/ - /*--- dV_ij^upw = 2 grad(Vi) dot vector_ij - dV_ij^cen. ---*/ - /*--- To maintain proper scaling for edge limiters, the result of ---*/ - /*--- this function is 0.5 * dV_ij^kap. ---*/ - /*-------------------------------------------------------------------*/ - return (1.0 - kappa) * gradProj + kappa * delta; -} - -/*! - * \brief MUSCL reconstruction of the specified variable. - * \note The result should be halved when added to i (or subtracted from j). - * \param[in] grad_i - Gradient vector at point i. - * \param[in] vector_ij - Distance vector from i to j. - * \param[in] delta - Centered difference: V_j - V_i. - * \param[in] iVar - Variable index. - * \param[in] kappa - Blending coefficient. - * \param[in] umusclRamp - MUSCL 1st-2nd order ramp times Newton-Krylov relaxation. - * \return Variable reconstructed from point i. - */ -template -FORCEINLINE Double musclReconstruction(const GradType& grad, - const VectorDbl& vector_ij, - const Double& delta, - const size_t iVar, - const Double& kappa, - const Double& umusclRamp) { - const Double proj = dot(grad[iVar], vector_ij); - return umusclRamp * umusclProjection(proj, delta, kappa); -} - -/*! - * \brief Unlimited reconstruction. - */ -template -FORCEINLINE void musclUnlimited(const Int& iPoint, - const Int& jPoint, - const VectorDbl& vector_ij, - const Gradient_t& gradient, - CPair& V, - const Double& kappa, - const Double& umusclRamp) { - constexpr auto nVarGrad = nVarGrad_ > 0 ? nVarGrad_ : VarType::nVar; - - auto grad_i = gatherVariables(iPoint, gradient); - auto grad_j = gatherVariables(jPoint, gradient); - - for (size_t iVar = 0; iVar < nVarGrad; ++iVar) { - /*--- Centered difference, needed for U-MUSCL projection ---*/ - const Double delta_ij = V.j.all(iVar) - V.i.all(iVar); - - /*--- U-MUSCL reconstructed variables ---*/ - const Double proj_i = musclReconstruction(grad_i, vector_ij, delta_ij, iVar, kappa, umusclRamp); - const Double proj_j = musclReconstruction(grad_j, vector_ij, delta_ij, iVar, kappa, umusclRamp); - - /*--- Apply reconstruction: V_L = V_i + 0.5 * dV_ij^kap ---*/ - V.i.all(iVar) += 0.5 * proj_i; - V.j.all(iVar) -= 0.5 * proj_j; - } -} - -/*! - * \brief Limited reconstruction with point-based limiter. - */ -template -FORCEINLINE void musclPointLimited(const Int& iPoint, - const Int& jPoint, - const VectorDbl& vector_ij, - const Limiter_t& limiter, - const Gradient_t& gradient, - CPair& V, - const Double& kappa, - const Double& umusclRamp) { - constexpr auto nVarGrad = nVarGrad_ > 0 ? nVarGrad_ : VarType::nVar; - - auto lim_i = gatherVariables(iPoint, limiter); - auto lim_j = gatherVariables(jPoint, limiter); - - auto grad_i = gatherVariables(iPoint, gradient); - auto grad_j = gatherVariables(jPoint, gradient); - - for (size_t iVar = 0; iVar < nVarGrad; ++iVar) { - /*--- Centered difference, needed for U-MUSCL projection ---*/ - const Double delta_ij = V.j.all(iVar) - V.i.all(iVar); - - /*--- U-MUSCL reconstructed variables ---*/ - const Double proj_i = musclReconstruction(grad_i, vector_ij, delta_ij, iVar, kappa, umusclRamp); - const Double proj_j = musclReconstruction(grad_j, vector_ij, delta_ij, iVar, kappa, umusclRamp); - - /*--- Apply reconstruction: V_L = V_i + 0.5 * lim * dV_ij^kap ---*/ - V.i.all(iVar) += 0.5 * lim_i(iVar) * proj_i; - V.j.all(iVar) -= 0.5 * lim_j(iVar) * proj_j; - } -} - -/*! - * \brief Limited reconstruction with edge-based limiter. - */ -template -FORCEINLINE void musclEdgeLimited(const Int& iPoint, - const Int& jPoint, - const VectorDbl& vector_ij, - const Gradient_t& gradient, - CPair& V, - const Double& kappa, - const Double& umusclRamp) { - constexpr auto nVarGrad = nVarGrad_ > 0 ? nVarGrad_ : VarType::nVar; - - auto grad_i = gatherVariables(iPoint, gradient); - auto grad_j = gatherVariables(jPoint, gradient); - - for (size_t iVar = 0; iVar < nVarGrad; ++iVar) { - /*--- Centered difference, needed for U-MUSCL projection and limiter ---*/ - const Double delta_ij = V.j.all(iVar) - V.i.all(iVar); - const Double delta_ij_2 = pow(delta_ij, 2) + 1e-6; - - /*--- U-MUSCL reconstructed variables ---*/ - const Double proj_i = musclReconstruction(grad_i, vector_ij, delta_ij, iVar, kappa, umusclRamp); - const Double proj_j = musclReconstruction(grad_j, vector_ij, delta_ij, iVar, kappa, umusclRamp); - - /// TODO: Customize the limiter function. - const Double lim_i = (delta_ij_2 + proj_i*delta_ij) / (pow(proj_i,2) + delta_ij_2); - const Double lim_j = (delta_ij_2 + proj_j*delta_ij) / (pow(proj_j,2) + delta_ij_2); - - /*--- Apply reconstruction: V_L = V_i + 0.5 * lim * dV_ij^kap ---*/ - V.i.all(iVar) += 0.5 * lim_i * proj_i; - V.j.all(iVar) -= 0.5 * lim_j * proj_j; - } -} - /*! * \brief Retrieve primitive variables for points i/j, reconstructing them if needed. * \note Density and enthalpy are recomputed from ideal gas EOS. @@ -219,17 +76,7 @@ FORCEINLINE CPair reconstructPrimitives(const Int& iEdge, if (muscl) { /*--- Reconstruct density and enthalpy without using their gradients. ---*/ constexpr auto nVarGrad = ReconVarType::nVar - 2; - switch (limiterType) { - case LIMITER::NONE: - musclUnlimited(iPoint, jPoint, vector_ij, gradients, V, kappa, umusclRamp); - break; - case LIMITER::VAN_ALBADA_EDGE: - musclEdgeLimited(iPoint, jPoint, vector_ij, gradients, V, kappa, umusclRamp); - break; - default: - musclPointLimited(iPoint, jPoint, vector_ij, limiters, gradients, V, kappa, umusclRamp); - break; - } + reconstruct(iPoint, jPoint, vector_ij, gradients, limiters, limiterType, 0, V, kappa, umusclRamp); /*--- Recompute density using the reconstructed pressure and temperature. ---*/ V.i.density() = V.i.pressure() / (gasConst * V.i.temperature()); V.j.density() = V.j.pressure() / (gasConst * V.j.temperature()); diff --git a/SU2_CFD/include/numerics_simd/util.hpp b/SU2_CFD/include/numerics_simd/util.hpp index 79594268be8f..9db7721803f4 100644 --- a/SU2_CFD/include/numerics_simd/util.hpp +++ b/SU2_CFD/include/numerics_simd/util.hpp @@ -1,6 +1,6 @@ /*! * \file util.hpp - * \brief Generic auxiliary functions. + * \brief Vector, matrix and index types bound to the SIMD Double/Int of CNumericsSIMD.hpp. * \author P. Gomes * \version 8.5.0 "Harrier" * @@ -28,248 +28,10 @@ #pragma once #include "CNumericsSIMD.hpp" -#include "../../../Common/include/containers/C2DContainer.hpp" -#include "../../../Common/include/linear_algebra/CSysVector.hpp" -#include "../../../Common/include/linear_algebra/CSysMatrix.hpp" +#include "../numerics/util.hpp" -/*! - * \brief Static vector and matrix types. - * \note These should be used instead of C-style arrays. - */ -template -using Vector = C2DContainer; - -template using VectorInt = Vector; -template using VectorDbl = Vector; - -template -using Matrix = C2DContainer; - -template using MatrixInt = Matrix; -template using MatrixDbl = Matrix; - -/*! - * \brief Constexpr version of max. - */ -inline constexpr size_t Max(size_t a, size_t b) { return a>b? a : b; } - -/*! - * \brief Simple pair type for i/j variables. - */ -template -struct CPair { - T i, j; -}; - -/*! - * \brief Dot product. - */ -template -FORCEINLINE Double dot(ForwardIterator iterator, const T* ptr) { - Double sum = 0.0; - for (size_t iDim = 0; iDim < nDim; ++iDim) { - sum += *(iterator++) * ptr[iDim]; - } - return sum; -} - -/*! - * \overload Dot product. - */ -template -FORCEINLINE Double dot(ForwardIterator iterator, const VectorDbl& vector) { - return dot(iterator, vector.data()); -} - -/*! - * \overload Dot product. - */ -template -FORCEINLINE Double dot(const VectorDbl& a, const VectorDbl& b) { - return dot(a.data(), b.data()); -} - -/*! - * \brief Squared norm. - */ -template -FORCEINLINE Double squaredNorm(ForwardIterator iterator) { - Double sum = 0.0; - for (size_t iDim = 0; iDim < nDim; ++iDim) { - sum += pow(*(iterator++),2); - } - return sum; -} - -/*! - * \overload Squared norm. - */ -template -FORCEINLINE Double squaredNorm(const VectorDbl& vector) { - return squaredNorm(vector.data()); -} - -/*! - * \brief Tangential projection. - */ -template -FORCEINLINE VectorDbl tangentProjection(const MatrixDbl& tensor, - const VectorDbl& unitVector) { - VectorDbl proj; - for (size_t iDim = 0; iDim < nDim; ++iDim) - proj(iDim) = dot(tensor[iDim], unitVector); - - Double normalProj = dot(proj, unitVector); - - for (size_t iDim = 0; iDim < nDim; ++iDim) - proj(iDim) -= normalProj * unitVector(iDim); +template using VectorInt = Vector; +template using VectorDbl = Vector; - return proj; -} - -/*! - * \brief Vector norm. - */ -template -FORCEINLINE Double norm(const VectorDbl& vector) { return sqrt(squaredNorm(vector)); } - -#ifndef CODI_REVERSE_TYPE -/*! - * \brief Gather a single variable from index iPoint of a 1D container. - */ -template -FORCEINLINE Double gatherVariables(Int iPoint, const Container& vars) { - return *vars.innerIter(iPoint); -} - -/*! - * \brief Gather a vector of variables (size nVar) from row iPoint of a 2D container. - */ -template -FORCEINLINE VectorDbl gatherVariables(Int iPoint, const Container& vars) { - return vars.template get >(iPoint); -} - -/*! - * \brief Gather a matrix of variables from outer index iPoint of a 3D container. - */ -template -FORCEINLINE MatrixDbl gatherVariables(Int iPoint, const Container& vars) { - return vars.template get >(iPoint); -} -#else - -namespace { - template = 0> - FORCEINLINE const su2double& get(const Container& vars, unsigned long iPoint) { return vars(iPoint); } - - /*--- When getting 1 variable from a matrix container, we assume it is the first. ---*/ - template = 0> - FORCEINLINE const su2double& get(const Container& vars, unsigned long iPoint) { return vars(iPoint,0); } -} - -template -FORCEINLINE Double gatherVariables(Int iPoint, const Container& vars) { - Double x; - for (size_t k=0; k -FORCEINLINE VectorDbl gatherVariables(Int iPoint, const Container& vars) { - VectorDbl x; - for (size_t i=0; i -FORCEINLINE MatrixDbl gatherVariables(Int iPoint, const Container& vars) { - MatrixDbl x; - for (size_t i=0; i -FORCEINLINE void stopPreacc(VectorDbl& x) { - AD::SetPreaccOut(x, nVar, Double::Size); - AD::EndPreacc(); -} - -/*! - * \brief Distance vector, from point i to point j. - */ -template -FORCEINLINE VectorDbl distanceVector(Int iPoint, Int jPoint, - const Container& coords) { - auto coord_i = gatherVariables(iPoint, coords); - auto coord_j = gatherVariables(jPoint, coords); - VectorDbl vector_ij; - for (size_t iDim = 0; iDim < nDim; ++iDim) { - vector_ij(iDim) = coord_j(iDim) - coord_i(iDim); - } - return vector_ij; -} - -/*! - * \brief Update the matrix and right-hand-side of a linear system. - */ -template -FORCEINLINE void updateLinearSystem(Int iEdge, - Int iPoint, - Int jPoint, - bool implicit, - UpdateType updateType, - Double updateMask, - const VectorDbl& flux, - const MatrixDbl& jac_i, - const MatrixDbl& jac_j, - CSysVector& vector, - SparseMatrixType& matrix) { - if (updateType == UpdateType::COLORING) { - vector.UpdateBlocks(iPoint, jPoint, flux, updateMask); - if(implicit) { - auto wasActive = AD::BeginPassive(); - matrix.SetBlocks(iEdge, iPoint, jPoint, jac_i, jac_j, updateMask); - AD::EndPassive(wasActive); - } - } - else { - vector.SetBlock(iEdge, flux, updateMask); - if(implicit) { - auto wasActive = AD::BeginPassive(); - matrix.SetBlocks(iEdge, jac_i, jac_j, updateMask); - AD::EndPassive(wasActive); - } - } -} - -/*! - * \brief Store the (scalar) mass flux of an edge, e.g. for "bounded scalar" transport equations. - * \note No-op if "target" is null. As with CEdge's Nodes/Normal, edges within a SIMD group are - * contiguous (coloring groups are multiples of the SIMD size), so this is a plain vectorized store - * starting at iEdge[0], relying on "target" being padded to a multiple of the SIMD size. - */ -FORCEINLINE void updateEdgeMassFlux(Int iEdge, - const Double& massFlux, - su2activevector* target) { - if (target) massFlux.store(&(*target)[iEdge[0]]); -} +template using MatrixInt = Matrix; +template using MatrixDbl = Matrix; diff --git a/SU2_CFD/include/solvers/CHeatSolver.hpp b/SU2_CFD/include/solvers/CHeatSolver.hpp index a0cce12c57ca..1097b2cd06f8 100644 --- a/SU2_CFD/include/solvers/CHeatSolver.hpp +++ b/SU2_CFD/include/solvers/CHeatSolver.hpp @@ -93,46 +93,12 @@ class CHeatSolver final : public CScalarSolver { } } - /*! - * \brief Compute the viscous flux for the scalar equation at a particular edge. - * \param[in] iEdge - Edge for which we want to compute the flux - * \param[in] geometry - Geometrical definition of the problem. - * \param[in] solver_container - Container vector with all the solutions. - * \param[in] numerics - Description of the numerical method. - * \param[in] config - Definition of the particular problem. - * \note Calls a generic implementation after defining a SolverSpecificNumerics object. - */ - inline void Viscous_Residual(const unsigned long iEdge, const CGeometry* geometry, CSolver** solver_container, - CNumerics* numerics, const CConfig* config) override { - const CVariable* flow_nodes = flow ? solver_container[FLOW_SOL]->GetNodes() : nullptr; - - const su2double const_diffusivity = config->GetThermalDiffusivity(); - const su2double pr_turb = config->GetPrandtl_Turb(); - - su2double thermal_diffusivity_i{}, thermal_diffusivity_j{}; - - /*--- Computes the thermal diffusivity to use in the viscous numerics. ---*/ - auto compute_thermal_diffusivity = [&](unsigned long iPoint, unsigned long jPoint) { - if (flow) { - thermal_diffusivity_i = flow_nodes->GetThermalConductivity(iPoint) / flow_nodes->GetSpecificHeatCp(iPoint) + - flow_nodes->GetEddyViscosity(iPoint) / pr_turb; - thermal_diffusivity_j = flow_nodes->GetThermalConductivity(jPoint) / flow_nodes->GetSpecificHeatCp(jPoint) + - flow_nodes->GetEddyViscosity(jPoint) / pr_turb; - numerics->SetDiffusionCoeff(&thermal_diffusivity_i, &thermal_diffusivity_j); - } else { - numerics->SetDiffusionCoeff(&const_diffusivity, &const_diffusivity); - } - }; - /*--- Compute residual and Jacobians. ---*/ - Viscous_Residual_impl(compute_thermal_diffusivity, iEdge, geometry, solver_container, numerics, config); - } - public: /*! * \brief Constructor of the class. */ - CHeatSolver(CGeometry *geometry, CConfig *config, unsigned short iMesh); + CHeatSolver(CGeometry *geometry, CConfig *config, const CSolver* flow_solver, unsigned short iMesh); /*! * \brief Restart residual and compute gradients. @@ -181,13 +147,16 @@ class CHeatSolver final : public CScalarSolver { unsigned short iMesh) override; /*! - * \brief Compute the viscous residuals for the turbulent equation. - * \param[in] geometry - Geometrical definition of the problem. - * \param[in] solver_container - Container vector with all the solutions. - * \param[in] numerics_container - Description of the numerical method. - * \param[in] config - Definition of the particular problem. - * \param[in] iMesh - Index of the mesh in multigrid computations. - * \param[in] iRKStep - Current step of the Runge-Kutta iteration. + * \brief Resolve the compile-time parameters of CScalarFlux_Heat and run one of this solver's + * boundaries through the shared boundary flux pass. + * \param[in] opt - Flags of the boundary, from one of ScalarFluxOptions' named constructors. + */ + void BoundaryFlux(CGeometry* geometry, CSolver** solver_container, CConfig* config, const ScalarFluxOptions& opt, + unsigned short val_marker); + + /*! + * \brief Diffusion for solid conduction, called unconditionally unlike Upwind_Residual. A no-op + * for a fluid zone, where diffusion was already computed together with convection. */ void Viscous_Residual(CGeometry *geometry, CSolver **solver_container, @@ -273,6 +242,34 @@ class CHeatSolver final : public CScalarSolver { CConfig *config, unsigned short val_marker) override; + /*! + * \brief Impose the far-field boundary condition. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container vector with all the solutions. + * \param[in] conv_numerics - Description of the numerical method. + * \param[in] visc_numerics - Description of the numerical method. + * \param[in] config - Definition of the particular problem. + * \param[in] val_marker - Surface marker where the boundary condition is applied. + */ + void BC_Far_Field(CGeometry *geometry, + CSolver **solver_container, + CNumerics *conv_numerics, + CNumerics *visc_numerics, + CConfig *config, + unsigned short val_marker) override; + + /*! + * \brief Impose the fluid interface (sliding mesh) boundary condition, via the + * CScalarFlux_Heat edge kernel. + * \param[in] geometry - Geometrical definition of the problem. + * \param[in] solver_container - Container vector with all the solutions. + * \param[in] conv_numerics - Unused, kept only for the boundary condition dispatch. + * \param[in] visc_numerics - Unused, kept only for the boundary condition dispatch. + * \param[in] config - Definition of the particular problem. + */ + void BC_Fluid_Interface(CGeometry *geometry, CSolver **solver_container, CNumerics *conv_numerics, + CNumerics *visc_numerics, CConfig *config) override; + /*! * \brief Impose the (received) conjugate heat variables. * \param[in] geometry - Geometrical definition of the problem. diff --git a/SU2_CFD/include/solvers/CPoissonSolver.hpp b/SU2_CFD/include/solvers/CPoissonSolver.hpp index b8e86bac8ce7..e00217e21e7a 100644 --- a/SU2_CFD/include/solvers/CPoissonSolver.hpp +++ b/SU2_CFD/include/solvers/CPoissonSolver.hpp @@ -41,34 +41,6 @@ class CPoissonSolver final : public CScalarSolver { static constexpr size_t MAXNDIM = 3; /*!< \brief Max number of space dimensions, used in some static arrays. */ static constexpr size_t MAXNVAR = 1; /*!< \brief Max number of variables, for static arrays. */ - /*! - * \brief Compute the viscous flux for the scalar equation at a particular edge. - * \param[in] iEdge - Edge for which we want to compute the flux - * \param[in] geometry - Geometrical definition of the problem. - * \param[in] solver_container - Container vector with all the solutions. - * \param[in] numerics - Description of the numerical method. - * \param[in] config - Definition of the particular problem. - * \note Calls a generic implementation after defining a SolverSpecificNumerics object. - */ - inline void Viscous_Residual(const unsigned long iEdge, const CGeometry* geometry, CSolver** solver_container, - CNumerics* numerics, const CConfig* config) override { - - su2double mom_coeff_i{}, mom_coeff_j{}; - - /*--- Sets the momentum coefficients to use in the viscous numerics. A point under a strong - * velocity BC has no momentum coefficient, so the edge uses that of its other node. ---*/ - auto compute_momentum_coeff = [&](unsigned long iPoint, unsigned long jPoint) { - const auto* flow_nodes = solver_container[FLOW_SOL]->GetNodes(); - - mom_coeff_i = nodes->GetMomCoeff(flow_nodes->GetStrongBC(iPoint) ? jPoint : iPoint); - mom_coeff_j = nodes->GetMomCoeff(flow_nodes->GetStrongBC(jPoint) ? iPoint : jPoint); - numerics->SetDiffusionCoeff(&mom_coeff_i, &mom_coeff_j); - }; - - /*--- Compute residual and Jacobians. ---*/ - Viscous_Residual_impl(compute_momentum_coeff, iEdge, geometry, solver_container, numerics, config); - } - public: /* @@ -109,10 +81,10 @@ class CPoissonSolver final : public CScalarSolver { unsigned short iMesh) final; /*! - * \brief Compute the viscous residuals for the turbulent equation. + * \brief Compute the diffusion of the pressure correction, through the CScalarFlux_Poisson + * edge kernel; the equation has no convective term, see Upwind_Residual. * \param[in] geometry - Geometrical definition of the problem. * \param[in] solver_container - Container vector with all the solutions. - * \param[in] numerics_container - Description of the numerical method. * \param[in] config - Definition of the particular problem. * \param[in] iMesh - Index of the mesh in multigrid computations. * \param[in] iRKStep - Current step of the Runge-Kutta iteration. diff --git a/SU2_CFD/include/solvers/CScalarSolver.hpp b/SU2_CFD/include/solvers/CScalarSolver.hpp index 39ae101bf0ea..50ecf8a42c7f 100644 --- a/SU2_CFD/include/solvers/CScalarSolver.hpp +++ b/SU2_CFD/include/solvers/CScalarSolver.hpp @@ -30,11 +30,26 @@ #include "../../../Common/include/parallelization/omp_structure.hpp" #include "../../../Common/include/toolboxes/geometry_toolbox.hpp" +#include "../numerics/scalar/scalar_edge_flux.hpp" #include "../variables/CScalarVariable.hpp" +#include "../variables/CEulerVariable.hpp" #include "../variables/CFlowVariable.hpp" +#include "../variables/CGhostFlowVariable.hpp" +#include "../variables/CIncEulerVariable.hpp" #include "../variables/CPrimitiveIndices.hpp" #include "CSolver.hpp" +/*! + * \brief Carries a type through a value, so a runtime branch can hand a compile-time type to a + * generic lambda (its parameter deduces as CTypeTag, and the lambda recovers T as + * decltype(tag)::type). Standing in for a C++20 template lambda, which this project's + * C++17 baseline does not have. + */ +template +struct CTypeTag { + using type = T; +}; + /*! * \brief Main class for defining a scalar solver. * \tparam VariableType - Class of variable used by the solver inheriting from this template. @@ -80,6 +95,17 @@ class CScalarSolver : public CSolver { CSysVector EdgeFluxes; /*!< \brief Flux across each edge. */ CSysVector EdgeFluxesDiff; /*!< \brief Flux difference between ij and ji for non-conservative discretisation. */ + /*--- Ghost states of the marker currently being processed by a boundary, indexed by vertex + * and sized to the largest marker; same container types as the interior ones, so the flux + * kernels read a boundary through the same accessors as an interior edge. Boundary loops run + * one marker at a time, parallel over its vertices, so the buffers are written and consumed + * before the next marker reaches them (see BoundaryFluxResidual). ---*/ + unique_ptr ghostNodes; /*!< \brief Allocated by the derived solver, whose VariableType constructor it alone knows how to call. */ + unique_ptr ghostFlowNodes; /*!< \brief Sized from the flow solver, see the constructor. */ + su2activematrix ghostNormal; /*!< \brief Outward normals, sign flipped from the vertex normals. */ + su2activematrix ghostCoord; /*!< \brief Reflected coordinates, read by the diffusion sites. */ + su2vector ghostSkip; /*!< \brief Whether a vertex contributes no flux, set by the fill pass. */ + /*! * \brief The highest level in the variable hierarchy this solver can safely use. */ @@ -90,159 +116,6 @@ class CScalarSolver : public CSolver { */ inline CVariable* GetBaseClassPointerToNodes() final { return nodes; } - /*! - * \brief Compute the viscous flux for the scalar equation at a particular edge. - * \tparam SolverSpecificNumericsFunc - lambda-function, that implements solver specific contributions to numerics. - * \note The functor has to implement (iPoint, jPoint) - * \param[in] iEdge - Edge for which we want to compute the flux - * \param[in] geometry - Geometrical definition of the problem. - * \param[in] solver_container - Container vector with all the solutions. - * \param[in] numerics - Description of the numerical method. - * \param[in] config - Definition of the particular problem. - */ - template - FORCEINLINE void Viscous_Residual_impl(const SolverSpecificNumericsFunc& SolverSpecificNumerics, const unsigned long iEdge, - const CGeometry* geometry, CSolver** solver_container, CNumerics* numerics, - const CConfig* config) { - const bool implicit = (config->GetKind_TimeIntScheme() == EULER_IMPLICIT); - CFlowVariable* flowNodes = solver_container[FLOW_SOL] ? - su2staticcast_p(solver_container[FLOW_SOL]->GetNodes()) : nullptr; - - /*--- Points in edge ---*/ - - auto iPoint = geometry->edges->GetNode(iEdge, 0); - auto jPoint = geometry->edges->GetNode(iEdge, 1); - - /*--- Points coordinates, and normal vector ---*/ - - numerics->SetCoord(geometry->nodes->GetCoord(iPoint), geometry->nodes->GetCoord(jPoint)); - numerics->SetNormal(geometry->edges->GetNormal(iEdge)); - - /*--- Conservative variables w/o reconstruction ---*/ - - if (flowNodes) { - numerics->SetPrimitive(flowNodes->GetPrimitive(iPoint), flowNodes->GetPrimitive(jPoint)); - } - - /*--- Turbulent variables w/o reconstruction, and its gradients ---*/ - - numerics->SetScalarVar(nodes->GetSolution(iPoint), nodes->GetSolution(jPoint)); - numerics->SetScalarVarGradient(nodes->GetGradient(iPoint), nodes->GetGradient(jPoint)); - - /*--- Call Numerics contribution which are Solver-Specifc. Implemented in the caller: Viscous_Residual. ---*/ - - SolverSpecificNumerics(iPoint, jPoint); - - /*--- Compute residual, and Jacobians ---*/ - - auto residual = numerics->ComputeResidual(config); - - if (ReducerStrategy) { - EdgeFluxes.SubtractBlock(iEdge, residual); - if (implicit) Jacobian.UpdateBlocksSub(iEdge, residual.jacobian_i, residual.jacobian_j); - } else { - LinSysRes.SubtractBlock(iPoint, residual); - LinSysRes.AddBlock(jPoint, residual); - if (implicit) Jacobian.UpdateBlocksSub(iEdge, iPoint, jPoint, residual.jacobian_i, residual.jacobian_j); - } - } - - /*! - * \brief Compute the viscous flux for the turbulence equations at a particular edge for a non-conservative discretisation. - * \tparam SolverSpecificNumericsTemp - lambda-function, to implement solver specific contributions to numerics. - * \note The functor has to implement (iPoint, jPoint) - * \param[in] iEdge - Edge for which we want to compute the flux - * \param[in] geometry - Geometrical definition of the problem. - * \param[in] solver_container - Container vector with all the solutions. - * \param[in] numerics - Description of the numerical method. - * \param[in] config - Definition of the particular problem. - */ - template - void Viscous_Residual_NonCons(const unsigned long iEdge, const CGeometry* geometry, CSolver** solver_container, - CNumerics* numerics, const CConfig* config, SolverSpecificNumericsFunc&& SolverSpecificNumerics) { - const bool implicit = (config->GetKind_TimeIntScheme() == EULER_IMPLICIT); - CFlowVariable* flowNodes = solver_container[FLOW_SOL] ? - su2staticcast_p(solver_container[FLOW_SOL]->GetNodes()) : nullptr; - - const auto iPoint = geometry->edges->GetNode(iEdge, 0); - const auto jPoint = geometry->edges->GetNode(iEdge, 1); - - /*--- Lambda function to compute the flux ---*/ - auto ComputeFlux = [&](unsigned long iPoint, unsigned long jPoint, const su2double* normal) { - numerics->SetCoord(geometry->nodes->GetCoord(iPoint),geometry->nodes->GetCoord(jPoint)); - numerics->SetNormal(normal); - - if (flowNodes) { - numerics->SetPrimitive(flowNodes->GetPrimitive(iPoint), flowNodes->GetPrimitive(jPoint)); - } - - /*--- Solver specific numerics contribution. ---*/ - SolverSpecificNumerics(iPoint, jPoint); - - numerics->SetScalarVar(nodes->GetSolution(iPoint), nodes->GetSolution(jPoint)); - numerics->SetScalarVarGradient(nodes->GetGradient(iPoint), nodes->GetGradient(jPoint)); - - return numerics->ComputeResidual(config); - }; - - /*--- Compute fluxes and jacobians i->j ---*/ - const su2double* normal = geometry->edges->GetNormal(iEdge); - auto residual_ij = ComputeFlux(iPoint, jPoint, normal); - - su2mixedfloat *Block_ii = nullptr, *Block_ij = nullptr, *Block_ji = nullptr, *Block_jj = nullptr; - if (implicit) { - Jacobian.GetBlocks(iEdge, iPoint, jPoint, Block_ii, Block_ij, Block_ji, Block_jj); - } - if (ReducerStrategy) { - EdgeFluxes.SubtractBlock(iEdge, residual_ij); - EdgeFluxesDiff.SetBlock(iEdge, residual_ij); - if (implicit) { - /*--- For the reducer strategy the Jacobians are averaged for simplicity. ---*/ - for (int iVar=0; iVari ---*/ - su2double flipped_normal[MAXNDIM]; - for (auto iDim = 0u; iDim < nDim; iDim++) flipped_normal[iDim] = -normal[iDim]; - - auto residual_ji = ComputeFlux(jPoint, iPoint, flipped_normal); - if (ReducerStrategy) { - EdgeFluxesDiff.AddBlock(iEdge, residual_ji); - if (implicit) { - for (int iVar=0; iVar. + * \param[in] opt - Loop invariant flags built by the caller from the current CConfig state. */ - inline virtual void Viscous_Residual(const unsigned long iEdge, const CGeometry* geometry, CSolver** solver_container, - CNumerics* numerics, const CConfig* config) { - /*--- Define an empty object for solver specific numerics contribution. In case there are none, this default - *--- implementation will be called ---*/ - auto SolverSpecificNumerics = [&](unsigned long iPoint, unsigned long jPoint) {}; + template + void EdgeFluxResidual(const CGeometry* geometry, CSolver** solver_container, const CConfig* config, + const ScalarFluxOptions& opt); - /*--- Now instantiate the generic implementation with the functor above. ---*/ + /*! + * \brief Write the four flow primitives the flux kernels read into one row of ghostFlowNodes. + * \param[in] iVertex - Vertex of the marker currently being processed. + * \param[in] V - Row of flow primitives to copy from (e.g. GetCharacPrimVar's or a sliding state's). + */ + inline void SetGhostPrimitives(unsigned long iVertex, const su2double* V) { + auto* ghostV = ghostFlowNodes->GetPrimitive(iVertex); + ghostV[prim_idx.Density()] = V[prim_idx.Density()]; + for (auto iDim = 0u; iDim < nDim; ++iDim) ghostV[prim_idx.Velocity() + iDim] = V[prim_idx.Velocity() + iDim]; + ghostV[prim_idx.LaminarViscosity()] = V[prim_idx.LaminarViscosity()]; + ghostV[prim_idx.EddyViscosity()] = V[prim_idx.EddyViscosity()]; + /*--- NEMO's primitive layout has no single thermal conductivity or specific heat, so its + * CIndices returns a sentinel for these two; only heat reads them, and NEMO rejects any + * scalar transport at configuration. ---*/ + if (prim_idx.ThermalConductivity() != std::numeric_limits::max()) { + ghostV[prim_idx.ThermalConductivity()] = V[prim_idx.ThermalConductivity()]; + ghostV[prim_idx.CpTotal()] = V[prim_idx.CpTotal()]; + } + } - Viscous_Residual_impl(SolverSpecificNumerics, iEdge, geometry, solver_container, numerics, config); + /*! + * \brief Generic boundary flux pass, run after a boundary's fill pass has written the ghost + * row, the outward normal and (for the diffusion sites) the ghost gradient of every + * vertex of the marker. The ghost point has no row, so only the contribution to the + * interior point is assembled. + * \tparam Scheme - Same model the interior loop uses, instantiated with muscl false. + */ + template + void BoundaryFluxResidual(const CGeometry* geometry, CSolver** solver_container, const CConfig* config, + const ScalarFluxOptions& opt, unsigned short val_marker); + + /*! + * \brief Generic fluid interface (sliding mesh) flux pass, shared by every model. The convective + * term is a per-donor weighted average, computed in the same pass that fills the ghost row + * of each donor; the diffusive term is computed once per vertex, after the donor loop, + * from the ghost state the last donor left behind. This does not fit the + * fill-pass-then-BoundaryFluxResidual shape the other boundaries use, so it drives the + * kernel directly. + * \tparam Scheme - Same model the interior loop uses, instantiated with muscl false. + * \param[in] fillGhostExtras - Functor (iVertex, iPoint) writing the auxiliary ghost fields the + * model's diffusion coefficients read, e.g. SST's blending function or the species + * mass diffusivities. Called once per vertex, before the diffusive flux. + */ + template + void FluidInterfaceFluxResidual(const CGeometry* geometry, CSolver** solver_container, const CConfig* config, + const ScalarFluxOptions& optConv, const ScalarFluxOptions& optVisc, + const GhostFunc& fillGhostExtras); + + /*! + * \brief Write the outward normal of one vertex into the ghost row and mark the vertex as + * contributing a flux. + * \note Vertex normals point into the domain, the flux convention needs them outward. + */ + inline void SetGhostGeometry(const CGeometry* geometry, unsigned short val_marker, unsigned long iVertex) { + for (auto iDim = 0u; iDim < nDim; ++iDim) + ghostNormal(iVertex, iDim) = -geometry->vertex[val_marker][iVertex]->GetNormal(iDim); + ghostSkip[iVertex] = false; + } + + /*! + * \brief Write what the diffusion term of a boundary reads beyond the ghost solution: the + * coordinate of the interior point reflected about the boundary, and the interior + * gradient mirrored into the ghost row. + * \param[in] iPoint - Interior point of the vertex. + * \param[in] jPoint - Point the interior one is reflected about, the vertex's normal neighbor. + */ + inline void SetGhostDiffusionState(const CGeometry* geometry, unsigned long iVertex, unsigned long iPoint, + unsigned long jPoint) { + su2double Coord_Reflected[MAXNDIM]; + GeometryToolbox::PointPointReflect(nDim, geometry->nodes->GetCoord(jPoint), geometry->nodes->GetCoord(iPoint), + Coord_Reflected); + for (auto iDim = 0u; iDim < nDim; ++iDim) ghostCoord(iVertex, iDim) = Coord_Reflected[iDim]; + + auto ghostGrad = ghostNodes->GetGradient(iVertex); + const auto interiorGrad = nodes->GetGradient(iPoint); + for (auto iVar = 0u; iVar < nVar; ++iVar) + for (auto iDim = 0u; iDim < nDim; ++iDim) ghostGrad(iVar, iDim) = interiorGrad(iVar, iDim); + } + + /*! + * \brief Resolve the compile-time parameters of a scalar flux kernel, the flow indices, the + * dimension and the equation count, from the runtime state, and call f with a CTypeTag of + * the resulting scheme type: f is a generic lambda, + * `[&](auto tag){ using Scheme = typename decltype(tag)::type; ... }`. + * \tparam Model - Model class template, e.g. CScalarFlux_SST, taking the four parameters of + * CUpwScalarBase: value type, flow indices, dimension and equation count. + * \tparam nVarList - Equation counts to instantiate. Dynamic matches any count, a static one is + * taken when it equals the solver's nVar; the counts are tried in the order given. + * \note NEMO is not one of the index branches: transported scalars are rejected for it at + * configuration. + */ + template