From de22c97d8f89a52096e103b8867ac2c9dec39ff4 Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Thu, 26 Feb 2026 09:30:28 +0100 Subject: [PATCH 01/19] Made data-driven fluid model classes compatible with newer version of MLPCpp --- SU2_CFD/include/fluid/CDataDrivenFluid.hpp | 14 +-- SU2_CFD/src/fluid/CDataDrivenFluid.cpp | 118 +++++++----------- SU2_CFD/src/fluid/CFluidFlamelet.cpp | 19 +-- .../laminar_premixed_h2_flame_cfd.cfg | 2 +- subprojects/MLPCpp | 2 +- 5 files changed, 51 insertions(+), 104 deletions(-) diff --git a/SU2_CFD/include/fluid/CDataDrivenFluid.hpp b/SU2_CFD/include/fluid/CDataDrivenFluid.hpp index 45cd6f430625..47092a51f260 100644 --- a/SU2_CFD/include/fluid/CDataDrivenFluid.hpp +++ b/SU2_CFD/include/fluid/CDataDrivenFluid.hpp @@ -107,13 +107,12 @@ class CDataDrivenFluid final : public CFluidModel { string varname_rho, /*!< \brief Controlling variable name for density. */ varname_e; /*!< \brief Controlling variable name for static energy. */ - - size_t idx_rho, /*!< \brief Interpolator index for density input. */ - idx_e; /*!< \brief Interpolator index for energy input. */ su2double Newton_Relaxation, /*!< \brief Relaxation factor for Newton solvers. */ rho_start, /*!< \brief Starting value for the density in Newton solver processes. */ e_start, /*!< \brief Starting value for the energy in Newton solver processes. */ + rho_query, + e_query, Newton_Tolerance, /*!< \brief Normalized tolerance for Newton solvers. */ rho_min, rho_max, /*!< \brief Minimum and maximum density values in data set. */ e_min, e_max; /*!< \brief Minimum and maximum energy values in data set. */ @@ -130,10 +129,7 @@ class CDataDrivenFluid final : public CFluidModel { dhdrho_e, /*!< \brief Enthalpy derivative w.r.t. density. */ dhde_rho; /*!< \brief Enthalpy derivative w.r.t. static energy. */ - vector input_names_rhoe, /*!< \brief Data-driven method input variable names of the independent variables - (density, energy). */ - output_names_rhoe; /*!< \brief Output variable names listed in the data-driven method input file name. */ - + vector output_names_rhoe; /*!< \brief Output variable names listed in the data-driven method input file name. */ vector outputs_rhoe; /*!< \brief Pointers to output variables. */ vector> dsdrhoe; /*!< \brief Entropy Jacobian terms. */ @@ -144,8 +140,8 @@ class CDataDrivenFluid final : public CFluidModel { bool display_Newton_process{false}; /*--- Class variables for the multi-layer perceptron method ---*/ #ifdef USE_MLPCPP - MLPToolbox::CLookUp_ANN* lookup_mlp; /*!< \brief Multi-layer perceptron collection. */ - MLPToolbox::CIOMap* iomap_rhoe; /*!< \brief Input-output map. */ + MLPToolbox::CLookUp_ANN *lookup_mlp; /*!< \brief Multi-layer perceptron collection. */ + MLPToolbox::CIOMap iomap_rhoe; /*!< \brief Input-output map. */ #endif vector MLP_inputs; /*!< \brief Inputs for the multi-layer perceptron look-up operation. */ diff --git a/SU2_CFD/src/fluid/CDataDrivenFluid.cpp b/SU2_CFD/src/fluid/CDataDrivenFluid.cpp index 64fa394f74f9..bc9e8333517e 100644 --- a/SU2_CFD/src/fluid/CDataDrivenFluid.cpp +++ b/SU2_CFD/src/fluid/CDataDrivenFluid.cpp @@ -79,7 +79,6 @@ CDataDrivenFluid::~CDataDrivenFluid() { switch (Kind_DataDriven_Method) { case ENUM_DATADRIVEN_METHOD::MLP: #ifdef USE_MLPCPP - delete iomap_rhoe; delete lookup_mlp; #endif break; @@ -92,82 +91,55 @@ CDataDrivenFluid::~CDataDrivenFluid() { } void CDataDrivenFluid::MapInputs_to_Outputs() { /*--- Inputs of the data-driven method are density and internal energy. ---*/ - input_names_rhoe.resize(2); - idx_rho = 0; - idx_e = 1; - input_names_rhoe[idx_rho] = varname_rho; - input_names_rhoe[idx_e] = varname_e; /*--- Required outputs for the interpolation method are entropy and its partial derivatives with respect to energy and * density. ---*/ - size_t n_outputs, idx_s,idx_dsde_rho = 1, idx_dsdrho_e = 2, idx_d2sde2 = 3, idx_d2sdedrho = 4, idx_d2sdrho2 = 5; - if (use_MLP_derivatives) { - n_outputs = 1; - idx_s = 0; - - outputs_rhoe.resize(n_outputs); - output_names_rhoe.resize(n_outputs); - output_names_rhoe[idx_s] = "s"; - outputs_rhoe[idx_s] = &Entropy; - - dsdrhoe.resize(n_outputs); - d2sdrhoe2.resize(n_outputs); - dsdrhoe[0].resize(2); - dsdrhoe[0][idx_rho] = &dsdrho_e; - dsdrhoe[0][idx_e] = &dsde_rho; - - d2sdrhoe2[0].resize(2); - d2sdrhoe2[0][idx_rho].resize(2); - d2sdrhoe2[0][idx_e].resize(2); - d2sdrhoe2[0][idx_rho][idx_rho] = &d2sdrho2; - d2sdrhoe2[0][idx_rho][idx_e] = &d2sdedrho; - d2sdrhoe2[0][idx_e][idx_rho] = &d2sdedrho; - d2sdrhoe2[0][idx_e][idx_e] = &d2sde2; - } else { - n_outputs = 6; - idx_s = 0; - idx_dsde_rho = 1, idx_dsdrho_e = 2, idx_d2sde2 = 3, idx_d2sdedrho = 4, idx_d2sdrho2 = 5; - - outputs_rhoe.resize(n_outputs); - output_names_rhoe.resize(n_outputs); - output_names_rhoe[idx_s] = "s"; - outputs_rhoe[idx_s] = &Entropy; - output_names_rhoe[idx_dsde_rho] = "dsde_rho"; - outputs_rhoe[idx_dsde_rho] = &dsde_rho; - output_names_rhoe[idx_dsdrho_e] = "dsdrho_e"; - outputs_rhoe[idx_dsdrho_e] = &dsdrho_e; - output_names_rhoe[idx_d2sde2] = "d2sde2"; - outputs_rhoe[idx_d2sde2] = &d2sde2; - output_names_rhoe[idx_d2sdedrho] = "d2sdedrho"; - outputs_rhoe[idx_d2sdedrho] = &d2sdedrho; - output_names_rhoe[idx_d2sdrho2] = "d2sdrho2"; - outputs_rhoe[idx_d2sdrho2] = &d2sdrho2; - } - + const string name_entropy{"s"}, name_dsdrho{"dsdrho_e"}, name_dsde{"dsde"}, name_d2sdrho2{"d2sdrho2"}, name_d2sdedrho{"d2sdedrho"}, name_d2sde2{"d2sde2"}; + - /*--- Further preprocessing of input and output variables. ---*/ if (Kind_DataDriven_Method == ENUM_DATADRIVEN_METHOD::MLP) { -/*--- Map MLP inputs to outputs. ---*/ -#ifdef USE_MLPCPP - iomap_rhoe = new MLPToolbox::CIOMap(input_names_rhoe, output_names_rhoe); - lookup_mlp->PairVariableswithMLPs(*iomap_rhoe); - MLP_inputs.resize(2); -#endif + #ifdef USE_MLPCPP + iomap_rhoe = MLPToolbox::CIOMap(); + iomap_rhoe.AddQueryInput(varname_rho, &rho_query); + iomap_rhoe.AddQueryInput(varname_e, &e_query); + iomap_rhoe.AddQueryOutput(name_entropy, &Entropy); + + if (use_MLP_derivatives) { + iomap_rhoe.AddQueryJacobian(name_entropy, varname_rho, &dsdrho_e); + iomap_rhoe.AddQueryJacobian(name_entropy, varname_e, &dsde_rho); + iomap_rhoe.AddQueryHessian(name_entropy, varname_rho, varname_rho, &d2sdrho2); + iomap_rhoe.AddQueryHessian(name_entropy, varname_e, varname_rho, &d2sdedrho); + iomap_rhoe.AddQueryHessian(name_entropy, varname_e, varname_e, &d2sde2); + } else { + iomap_rhoe.AddQueryOutput(name_dsdrho, &dsdrho_e); + iomap_rhoe.AddQueryOutput(name_dsde, &dsde_rho); + iomap_rhoe.AddQueryOutput(name_d2sdrho2, &d2sdrho2); + iomap_rhoe.AddQueryOutput(name_d2sde2, &d2sde2); + iomap_rhoe.AddQueryOutput(name_d2sdedrho, &d2sdedrho); + } + + lookup_mlp->PairVariableswithMLPs(iomap_rhoe); + #endif } else { - /*--- Retrieve column indices of LUT output variables ---*/ - LUT_idx_s = lookup_table->GetIndexOfVar(output_names_rhoe[idx_s]); - LUT_idx_dsdrho_e = lookup_table->GetIndexOfVar(output_names_rhoe[idx_dsdrho_e]); - LUT_idx_dsde_rho = lookup_table->GetIndexOfVar(output_names_rhoe[idx_dsde_rho]); - LUT_idx_d2sde2 = lookup_table->GetIndexOfVar(output_names_rhoe[idx_d2sde2]); - LUT_idx_d2sdedrho= lookup_table->GetIndexOfVar(output_names_rhoe[idx_d2sdedrho]); - LUT_idx_d2sdrho2 = lookup_table->GetIndexOfVar(output_names_rhoe[idx_d2sdrho2]); + LUT_idx_s = lookup_table->GetIndexOfVar(name_entropy); + LUT_idx_dsdrho_e = lookup_table->GetIndexOfVar(name_dsdrho); + LUT_idx_dsde_rho = lookup_table->GetIndexOfVar(name_dsde); + LUT_idx_d2sde2 = lookup_table->GetIndexOfVar(name_d2sde2); + LUT_idx_d2sdedrho= lookup_table->GetIndexOfVar(name_d2sdedrho); + LUT_idx_d2sdrho2 = lookup_table->GetIndexOfVar(name_d2sdrho2); LUT_lookup_indices.push_back(LUT_idx_s); + outputs_rhoe.push_back(&Entropy); LUT_lookup_indices.push_back(LUT_idx_dsde_rho); + outputs_rhoe.push_back(&dsde_rho); LUT_lookup_indices.push_back(LUT_idx_dsdrho_e); + outputs_rhoe.push_back(&dsdrho_e); LUT_lookup_indices.push_back(LUT_idx_d2sde2); + outputs_rhoe.push_back(&d2sde2); LUT_lookup_indices.push_back(LUT_idx_d2sdedrho); + outputs_rhoe.push_back(&d2sdedrho); LUT_lookup_indices.push_back(LUT_idx_d2sdrho2); + outputs_rhoe.push_back(&d2sdrho2); } } @@ -297,13 +269,9 @@ unsigned long CDataDrivenFluid::Predict_MLP(su2double rho, su2double e) { unsigned long exit_code = 0; /*--- Evaluate MLP collection for the given values for density and energy. ---*/ #ifdef USE_MLPCPP - MLP_inputs[idx_rho] = rho; - MLP_inputs[idx_e] = e; - if (use_MLP_derivatives){ - exit_code = lookup_mlp->PredictANN(iomap_rhoe, MLP_inputs, outputs_rhoe, &dsdrhoe, &d2sdrhoe2); - } else { - exit_code = lookup_mlp->PredictANN(iomap_rhoe, MLP_inputs, outputs_rhoe); - } + rho_query = rho; + e_query = e; + lookup_mlp->Predict(iomap_rhoe); #endif return exit_code; @@ -435,10 +403,10 @@ void CDataDrivenFluid::ComputeIdealGasQuantities() { break; case ENUM_DATADRIVEN_METHOD::MLP: #ifdef USE_MLPCPP - rho_min = lookup_mlp->GetInputNorm(iomap_rhoe, idx_rho).first; - e_min = lookup_mlp->GetInputNorm(iomap_rhoe, idx_e).first; - rho_max = lookup_mlp->GetInputNorm(iomap_rhoe, idx_rho).second; - e_max = lookup_mlp->GetInputNorm(iomap_rhoe, idx_e).second; + rho_min = iomap_rhoe.GetInputNorm(varname_rho).first; + e_min = iomap_rhoe.GetInputNorm(varname_e).first; + rho_max = iomap_rhoe.GetInputNorm(varname_rho).second; + e_max = iomap_rhoe.GetInputNorm(varname_e).second; #endif break; default: diff --git a/SU2_CFD/src/fluid/CFluidFlamelet.cpp b/SU2_CFD/src/fluid/CFluidFlamelet.cpp index e0d30ce4092e..e15e8eac94f4 100644 --- a/SU2_CFD/src/fluid/CFluidFlamelet.cpp +++ b/SU2_CFD/src/fluid/CFluidFlamelet.cpp @@ -224,23 +224,6 @@ void CFluidFlamelet::PreprocessLookUp(CConfig* config) { val_vars_PD[FLAMELET_PREF_DIFF_SCALARS::I_BETA_MIXFRAC] = beta_mixfrac; preferential_diffusion = flamelet_options.preferential_diffusion; - switch (Kind_DataDriven_Method) { - case ENUM_DATADRIVEN_METHOD::LUT: - preferential_diffusion = look_up_table->CheckForVariables(varnames_PD); - break; - case ENUM_DATADRIVEN_METHOD::MLP: -#ifdef USE_MLPCPP - n_betas = 0; - for (auto iMLP = 0u; iMLP < datadriven_fluid_options.n_filenames; iMLP++) { - auto outputMap = lookup_mlp->FindVariableIndices(iMLP, varnames_PD, false); - n_betas += outputMap.size(); - } - preferential_diffusion = (n_betas == varnames_PD.size()); -#endif - break; - default: - break; - } if (!preferential_diffusion && flamelet_options.preferential_diffusion) SU2_MPI::Error("Preferential diffusion scalars not included in flamelet manifold.", CURRENT_FUNCTION); @@ -346,7 +329,7 @@ unsigned long CFluidFlamelet::EvaluateDataSet(const vector& input_sca refs_vars.resize(output_refs.size()); for (auto iVar = 0u; iVar < output_refs.size(); iVar++) refs_vars[iVar] = &output_refs[iVar]; #ifdef USE_MLPCPP - extrapolation = lookup_mlp->PredictANN(iomap_Current, input_scalar, refs_vars); + lookup_mlp->Predict(*iomap_Current, input_scalar, refs_vars); #endif break; default: diff --git a/TestCases/flamelet/07_laminar_premixed_h2_flame_cfd/laminar_premixed_h2_flame_cfd.cfg b/TestCases/flamelet/07_laminar_premixed_h2_flame_cfd/laminar_premixed_h2_flame_cfd.cfg index 93328224dc0b..974505d809bf 100644 --- a/TestCases/flamelet/07_laminar_premixed_h2_flame_cfd/laminar_premixed_h2_flame_cfd.cfg +++ b/TestCases/flamelet/07_laminar_premixed_h2_flame_cfd/laminar_premixed_h2_flame_cfd.cfg @@ -31,7 +31,7 @@ CONDUCTIVITY_MODEL= FLAMELET DIFFUSIVITY_MODEL= FLAMELET KIND_SCALAR_MODEL= FLAMELET INTERPOLATION_METHOD= MLP -FILENAMES_INTERPOLATOR= (MLP_TD.mlp, MLP_PD.mlp, MLP_PPV.mlp, MLP_null.mlp) +FILENAMES_INTERPOLATOR= (MLP_TD.mlp, MLP_PD.mlp, MLP_PPV.mlp) PREFERENTIAL_DIFFUSION= YES % -------------------- SCALAR TRANSPORT ---------------------------------------% diff --git a/subprojects/MLPCpp b/subprojects/MLPCpp index e19ca0cafb28..350c8740abab 160000 --- a/subprojects/MLPCpp +++ b/subprojects/MLPCpp @@ -1 +1 @@ -Subproject commit e19ca0cafb28c4b7ba5b8cffef42883259b00dc0 +Subproject commit 350c8740abab519b9f8f68785fd06d5a027251ce From 982feab5189df0acb1809b7e63ab636d7de8b8f9 Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Mon, 2 Mar 2026 15:16:02 +0100 Subject: [PATCH 02/19] update MLPCpp submodule --- subprojects/MLPCpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/MLPCpp b/subprojects/MLPCpp index 350c8740abab..dc748c5a324d 160000 --- a/subprojects/MLPCpp +++ b/subprojects/MLPCpp @@ -1 +1 @@ -Subproject commit 350c8740abab519b9f8f68785fd06d5a027251ce +Subproject commit dc748c5a324db4d1616227f4393d9f5be09da4bb From 9797283bc1ae1c6eda41b200bc6a1d86869ca417 Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Fri, 6 Mar 2026 11:17:55 +0100 Subject: [PATCH 03/19] Burnt progress variable is only calculated when using flame front ignition --- SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp b/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp index 622a1a6b068f..e90480ff12b9 100644 --- a/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp +++ b/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp @@ -211,9 +211,10 @@ void CSpeciesFlameletSolver::SetInitialCondition(CGeometry** geometry, CSolver** n_points_burnt_local = 0, n_points_flame_local = 0, n_not_iterated_global, n_not_in_domain_global, n_points_burnt_global, n_points_flame_global, n_points_unburnt_global; + if (flame_front_ignition) prog_burnt = GetBurntProgressVariable(fluid_model_local, scalar_init); for (unsigned long i_mesh = 0; i_mesh <= config->GetnMGLevels(); i_mesh++) { fluid_model_local = solver_container[i_mesh][FLOW_SOL]->GetFluidModel(); - prog_burnt = GetBurntProgressVariable(fluid_model_local, scalar_init); + for (auto iVar = 0u; iVar < nVar; iVar++) scalar_init[iVar] = config->GetSpecies_Init()[iVar]; /*--- Set enthalpy based on initial temperature and scalars. ---*/ @@ -797,7 +798,7 @@ su2double CSpeciesFlameletSolver::GetBurntProgressVariable(CFluidModel* fluid_mo bool outside = false; while (!outside) { fluid_model->SetTDState_T(300, scalars); - if (fluid_model->GetExtrapolation() == 1) outside = true; + if (fluid_model->GetExtrapolation() == 1 || (fluid_model->GetTemperature()>1000.)) outside = true; scalars[I_PROGVAR] += delta; } su2double pv_burnt = scalars[I_PROGVAR] - delta; From c84d744743d6881e619d96863706617b458de539 Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Fri, 6 Mar 2026 11:18:28 +0100 Subject: [PATCH 04/19] SHA tag update for MLPCpp --- meson_scripts/init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson_scripts/init.py b/meson_scripts/init.py index 74e42b601776..5730f4ae1b94 100755 --- a/meson_scripts/init.py +++ b/meson_scripts/init.py @@ -74,7 +74,7 @@ def init_submodules( github_repo_mel = "https://github.com/pcarruscag/MEL" sha_version_fado = "ce7ee018e4e699af5028d69baa1939fea290e18a" github_repo_fado = "https://github.com/pcarruscag/FADO" - sha_version_mlpcpp = "ff57e0cf9e60127196d3f1be71e711d47ff646ef" + sha_version_mlpcpp = "dc748c5a324db4d1616227f4393d9f5be09da4bb" github_repo_mlpcpp = "https://github.com/EvertBunschoten/MLPCpp" sha_version_eigen = "d71c30c47858effcbd39967097a2d99ee48db464" github_repo_eigen = "https://gitlab.com/libeigen/eigen.git" From 48f5771846fb5b3424855bbb3967753d946df40a Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Fri, 6 Mar 2026 11:18:52 +0100 Subject: [PATCH 05/19] Include extrapolation checks --- SU2_CFD/src/fluid/CDataDrivenFluid.cpp | 2 +- SU2_CFD/src/fluid/CFluidFlamelet.cpp | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/SU2_CFD/src/fluid/CDataDrivenFluid.cpp b/SU2_CFD/src/fluid/CDataDrivenFluid.cpp index bc9e8333517e..8b4f2c20859f 100644 --- a/SU2_CFD/src/fluid/CDataDrivenFluid.cpp +++ b/SU2_CFD/src/fluid/CDataDrivenFluid.cpp @@ -271,7 +271,7 @@ unsigned long CDataDrivenFluid::Predict_MLP(su2double rho, su2double e) { #ifdef USE_MLPCPP rho_query = rho; e_query = e; - lookup_mlp->Predict(iomap_rhoe); + if(!lookup_mlp->Predict(iomap_rhoe)) exit_code=1; #endif return exit_code; diff --git a/SU2_CFD/src/fluid/CFluidFlamelet.cpp b/SU2_CFD/src/fluid/CFluidFlamelet.cpp index e15e8eac94f4..2c8ec530209f 100644 --- a/SU2_CFD/src/fluid/CFluidFlamelet.cpp +++ b/SU2_CFD/src/fluid/CFluidFlamelet.cpp @@ -322,19 +322,20 @@ unsigned long CFluidFlamelet::EvaluateDataSet(const vector& input_sca } else { inside = look_up_table->LookUp_XY(LUT_idx, output_refs, val_prog, val_enth); } - if (inside) extrapolation = 0; - else extrapolation = 1; + break; case ENUM_DATADRIVEN_METHOD::MLP: refs_vars.resize(output_refs.size()); for (auto iVar = 0u; iVar < output_refs.size(); iVar++) refs_vars[iVar] = &output_refs[iVar]; #ifdef USE_MLPCPP - lookup_mlp->Predict(*iomap_Current, input_scalar, refs_vars); + inside=lookup_mlp->Predict(*iomap_Current, input_scalar, refs_vars); #endif break; default: break; } + if (inside) extrapolation = 0; + else extrapolation = 1; for (auto iVar = 0u; iVar < output_refs.size(); iVar++) AD::SetPreaccOut(output_refs[iVar]); AD::EndPreacc(); return extrapolation; From 98f3cd005a52ac073ab08814849f36533d9f1b42 Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Fri, 6 Mar 2026 11:19:23 +0100 Subject: [PATCH 06/19] Added unit test for differentiability of MLP inference --- .../MLP_Jacobian_tests.cpp | 120 ++++++++++++++++++ UnitTests/meson.build | 2 +- 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 UnitTests/Common/toolboxes/multilayer_perceptron/MLP_Jacobian_tests.cpp diff --git a/UnitTests/Common/toolboxes/multilayer_perceptron/MLP_Jacobian_tests.cpp b/UnitTests/Common/toolboxes/multilayer_perceptron/MLP_Jacobian_tests.cpp new file mode 100644 index 000000000000..8b60ee0ebb7e --- /dev/null +++ b/UnitTests/Common/toolboxes/multilayer_perceptron/MLP_Jacobian_tests.cpp @@ -0,0 +1,120 @@ +/*! + * \file MLP_Jacobian_tests.cpp + * \brief Check if MLP is differentiable + * \author E.C.Bunschoten + * \version 8.4.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" +#if defined(HAVE_MLPCPP) +#define MLP_CUSTOM_TYPE su2double +#include "../../../../subprojects/MLPCpp/include/CLookUp_ANN.hpp" +#define USE_MLPCPP +#endif +#include + +#ifdef USE_MLPCPP +TEST_CASE("MLP Jacobian test", "[LookUpANN]") { + + /* Create network with random weights */ + std::vector NN = {3, 10, 10, 1}; + MLPToolbox::CNeuralNetwork mlp = MLPToolbox::CNeuralNetwork(NN); + mlp.SetActivationFunction("gelu"); + mlp.RandomWeights(); + + /* Enable Jacobian and Hessian computation */ + mlp.CalcJacobian(true); + mlp.CalcHessian(true); + + /* Network input values */ + su2double val_in_1,val_in_2, val_in_3; + val_in_1 = 0.2; + val_in_2 = 0.3; + val_in_3 = 0.9; + + /* Calculate Jacobians with AD */ + AD::Reset(); + AD::StartRecording(); + AD::RegisterInput(val_in_1); + AD::RegisterInput(val_in_2); + AD::RegisterInput(val_in_3); + + mlp.SetInput(0, val_in_1); + mlp.SetInput(1, val_in_2); + mlp.SetInput(2, val_in_3); + mlp.Predict(); + + su2double val_out = mlp.GetOutput(0); + + AD::RegisterOutput(val_out); + AD::StopRecording(); + SU2_TYPE::SetDerivative(val_out, 1.0); + AD::ComputeAdjoint(); + const su2double jacobian_AD_1 = SU2_TYPE::GetDerivative(val_in_1); + const su2double jacobian_AD_2 = SU2_TYPE::GetDerivative(val_in_2); + const su2double jacobian_AD_3 = SU2_TYPE::GetDerivative(val_in_3); + + /* Retrieve analytical Jacobians */ + const su2double jacobian_ref_1 = mlp.GetJacobian(0, 0); + const su2double jacobian_ref_2 = mlp.GetJacobian(0, 1); + const su2double jacobian_ref_3 = mlp.GetJacobian(0, 2); + + /* Unit tests for Jacobians */ + CHECK(SU2_TYPE::GetValue(jacobian_AD_1) == Approx(SU2_TYPE::GetValue(jacobian_ref_1))); + CHECK(SU2_TYPE::GetValue(jacobian_AD_2) == Approx(SU2_TYPE::GetValue(jacobian_ref_2))); + CHECK(SU2_TYPE::GetValue(jacobian_AD_3) == Approx(SU2_TYPE::GetValue(jacobian_ref_3))); + + + /* Calculate Hessians with AD */ + AD::Reset(); + AD::StartRecording(); + AD::RegisterInput(val_in_1); + AD::RegisterInput(val_in_2); + AD::RegisterInput(val_in_3); + + mlp.SetInput(0, val_in_1); + mlp.SetInput(1, val_in_2); + mlp.SetInput(2, val_in_3); + mlp.Predict(); + + su2double jacobian_analytical = mlp.GetJacobian(0, 0); + + AD::RegisterOutput(jacobian_analytical); + AD::StopRecording(); + SU2_TYPE::SetDerivative(jacobian_analytical, 1.0); + AD::ComputeAdjoint(); + const su2double hessian_AD_1 = SU2_TYPE::GetDerivative(val_in_1); + const su2double hessian_AD_2 = SU2_TYPE::GetDerivative(val_in_2); + const su2double hessian_AD_3 = SU2_TYPE::GetDerivative(val_in_3); + + /* Retrieve analytical Hessians */ + const su2double hessian_ref_1 = mlp.GetHessian(0, 0, 0); + const su2double hessian_ref_2 = mlp.GetHessian(0, 0, 1); + const su2double hessian_ref_3 = mlp.GetHessian(0, 0, 2); + + /* Unit tests on Hessians */ + CHECK(SU2_TYPE::GetValue(hessian_AD_1) == Approx(SU2_TYPE::GetValue(hessian_ref_1))); + CHECK(SU2_TYPE::GetValue(hessian_AD_2) == Approx(SU2_TYPE::GetValue(hessian_ref_2))); + CHECK(SU2_TYPE::GetValue(hessian_AD_3) == Approx(SU2_TYPE::GetValue(hessian_ref_3))); +} +#endif \ No newline at end of file diff --git a/UnitTests/meson.build b/UnitTests/meson.build index 3e64cfb9aa3d..b2f2ac81a695 100644 --- a/UnitTests/meson.build +++ b/UnitTests/meson.build @@ -21,7 +21,7 @@ su2_cfd_tests = files(['Common/geometry/primal_grid/CPrimalGrid_tests.cpp', # Reverse-mode (algorithmic differentiation) tests: su2_cfd_tests_ad = files(['Common/simple_ad_test.cpp']) if get_option('enable-mlpcpp') - su2_cfd_tests_ad = su2_cfd_tests_ad + files(['SU2_CFD/fluid/CFluidModel_tests_AD.cpp']) + su2_cfd_tests_ad = su2_cfd_tests_ad + files(['SU2_CFD/fluid/CFluidModel_tests_AD.cpp', 'Common/toolboxes/multilayer_perceptron/MLP_Jacobian_tests.cpp']) endif # Forward-mode (direct differentiation) tests: From 2ffe77959e545f0d21dc1826967eb8d2c7fa1a88 Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Fri, 6 Mar 2026 11:19:52 +0100 Subject: [PATCH 07/19] MLP unit test compatible with new version --- .../CLookUp_ANN_tests.cpp | 79 +++++++++++-------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/UnitTests/Common/toolboxes/multilayer_perceptron/CLookUp_ANN_tests.cpp b/UnitTests/Common/toolboxes/multilayer_perceptron/CLookUp_ANN_tests.cpp index 2d255f8ca5cb..d081a93afe99 100644 --- a/UnitTests/Common/toolboxes/multilayer_perceptron/CLookUp_ANN_tests.cpp +++ b/UnitTests/Common/toolboxes/multilayer_perceptron/CLookUp_ANN_tests.cpp @@ -28,6 +28,7 @@ #include "catch.hpp" #include "../../../../Common/include/CConfig.hpp" #if defined(HAVE_MLPCPP) +#define MLP_CUSTOM_TYPE su2double #include "../../../../subprojects/MLPCpp/include/CLookUp_ANN.hpp" #define USE_MLPCPP #endif @@ -35,43 +36,59 @@ #ifdef USE_MLPCPP TEST_CASE("LookUp ANN test", "[LookUpANN]") { - std::string MLP_input_files[] = {"src/SU2/UnitTests/Common/toolboxes/multilayer_perceptron/simple_mlp.mlp"}; - unsigned short n_MLPs = 1; - MLPToolbox::CLookUp_ANN ANN(n_MLPs, MLP_input_files); - std::vector MLP_input_names, MLP_output_names; - std::vector MLP_inputs; - std::vector MLP_outputs; - su2double x, y, z; + MLPToolbox::CLookUp_ANN ANN; + MLPToolbox::CNeuralNetwork mlp = MLPToolbox::CNeuralNetwork("src/SU2/UnitTests/Common/toolboxes/multilayer_perceptron/simple_mlp.mlp"); - /*--- Define MLP inputs and outputs ---*/ - MLP_input_names.resize(2); - MLP_input_names[0] = "x"; - MLP_input_names[1] = "y"; - MLP_inputs.resize(2); + ANN.AddNetwork(&mlp); + su2double x, y, z, z_alt; - MLP_outputs.resize(1); - MLP_output_names.resize(1); - MLP_output_names[0] = "z"; - MLP_outputs[0] = &z; + /*--- Create a query where the value of z is calculated from x and y ---*/ + MLPToolbox::CIOMap iomap_ref; + iomap_ref.AddQueryInput("x", &x); + iomap_ref.AddQueryInput("y", &y); + iomap_ref.AddQueryOutput("z", &z); - /*--- Generate input-output map ---*/ - MLPToolbox::CIOMap iomap(MLP_input_names, MLP_output_names); - ANN.PairVariableswithMLPs(iomap); - /*--- MLP evaluation on point in the middle of the training data range ---*/ + ANN.PairVariableswithMLPs(iomap_ref); + + MLPToolbox::CIOMap iomap_vec; + std::vector input_names = {"x", "y"}, output_names = {"z"}; + std::vector output_refs = {&z_alt}; + std::vector mlp_inputs; + + iomap_vec.SetQueryInput(input_names); + iomap_vec.SetQueryOutput(output_names); + ANN.PairVariableswithMLPs(iomap_vec); + + /*--- MLP evaluation on two points in the middle of the training data range ---*/ x = 1.0; y = -0.5; - MLP_inputs[0] = x; - MLP_inputs[1] = y; - ANN.PredictANN(&iomap, MLP_inputs, MLP_outputs); + bool inside = ANN.Predict(iomap_ref); CHECK(z == Approx(0.344829)); + CHECK(inside); + + mlp_inputs.resize(2); + mlp_inputs[0] = x; + mlp_inputs[1] = y; + ANN.Predict(iomap_vec, mlp_inputs, output_refs); + CHECK(z == z_alt); + + x = 0.5; + y = -0.23; + inside = ANN.Predict(iomap_ref); + + CHECK(z == Approx(0.224986)); + CHECK(inside); + + mlp_inputs[0] = x; + mlp_inputs[1] = y; + ANN.Predict(iomap_vec, mlp_inputs, output_refs); + + /*--- */ + x = 10.0; + y = -20.0; + inside = ANN.Predict(iomap_ref); + CHECK(!inside); - /*--- MLP evaluation on point outside the training data range ---*/ - x = 3.0; - y = -10; - MLP_inputs[0] = x; - MLP_inputs[1] = y; - ANN.PredictANN(&iomap, MLP_inputs, MLP_outputs); - CHECK(z == Approx(0.012737)); } -#endif +#endif \ No newline at end of file From fffa199d6075d0065638cc50d8ab481bef915261 Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Fri, 6 Mar 2026 11:33:27 +0100 Subject: [PATCH 08/19] update MLPCpp sha tag --- meson_scripts/init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson_scripts/init.py b/meson_scripts/init.py index 5730f4ae1b94..76d961d43483 100755 --- a/meson_scripts/init.py +++ b/meson_scripts/init.py @@ -74,7 +74,7 @@ def init_submodules( github_repo_mel = "https://github.com/pcarruscag/MEL" sha_version_fado = "ce7ee018e4e699af5028d69baa1939fea290e18a" github_repo_fado = "https://github.com/pcarruscag/FADO" - sha_version_mlpcpp = "dc748c5a324db4d1616227f4393d9f5be09da4bb" + sha_version_mlpcpp = "4c1696d92cfd7b731d8575b3dce5d89dc65a7910" github_repo_mlpcpp = "https://github.com/EvertBunschoten/MLPCpp" sha_version_eigen = "d71c30c47858effcbd39967097a2d99ee48db464" github_repo_eigen = "https://gitlab.com/libeigen/eigen.git" From 87f713ae328428798994a819b3ddef852b8b5192 Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Fri, 6 Mar 2026 11:36:24 +0100 Subject: [PATCH 09/19] Updated MLPCpp version --- subprojects/MLPCpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/MLPCpp b/subprojects/MLPCpp index dc748c5a324d..4c1696d92cfd 160000 --- a/subprojects/MLPCpp +++ b/subprojects/MLPCpp @@ -1 +1 @@ -Subproject commit dc748c5a324db4d1616227f4393d9f5be09da4bb +Subproject commit 4c1696d92cfd7b731d8575b3dce5d89dc65a7910 From 2036e6c4ad23b13fb1a815e717b579b7120d7b27 Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Fri, 6 Mar 2026 11:47:44 +0100 Subject: [PATCH 10/19] Include config to define su2double --- .../toolboxes/multilayer_perceptron/MLP_Jacobian_tests.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/UnitTests/Common/toolboxes/multilayer_perceptron/MLP_Jacobian_tests.cpp b/UnitTests/Common/toolboxes/multilayer_perceptron/MLP_Jacobian_tests.cpp index 8b60ee0ebb7e..cf12b6539ab6 100644 --- a/UnitTests/Common/toolboxes/multilayer_perceptron/MLP_Jacobian_tests.cpp +++ b/UnitTests/Common/toolboxes/multilayer_perceptron/MLP_Jacobian_tests.cpp @@ -26,6 +26,7 @@ */ #include "catch.hpp" +#include "../../../../Common/include/CConfig.hpp" #if defined(HAVE_MLPCPP) #define MLP_CUSTOM_TYPE su2double #include "../../../../subprojects/MLPCpp/include/CLookUp_ANN.hpp" From 278f323d53ff799b5f88c84cbcd453a53ec173b3 Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Fri, 6 Mar 2026 11:48:25 +0100 Subject: [PATCH 11/19] Fix warnings --- SU2_CFD/src/fluid/CFluidFlamelet.cpp | 2 +- SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/SU2_CFD/src/fluid/CFluidFlamelet.cpp b/SU2_CFD/src/fluid/CFluidFlamelet.cpp index 2c8ec530209f..e663c6564612 100644 --- a/SU2_CFD/src/fluid/CFluidFlamelet.cpp +++ b/SU2_CFD/src/fluid/CFluidFlamelet.cpp @@ -312,7 +312,7 @@ unsigned long CFluidFlamelet::EvaluateDataSet(const vector& input_sca /*--- Add all quantities and their names to the look up vectors. ---*/ - bool inside; + bool inside{true}; switch (Kind_DataDriven_Method) { case ENUM_DATADRIVEN_METHOD::LUT: if (output_refs.size() != LUT_idx.size()) diff --git a/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp b/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp index e90480ff12b9..7efdd28f4187 100644 --- a/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp +++ b/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp @@ -211,9 +211,10 @@ void CSpeciesFlameletSolver::SetInitialCondition(CGeometry** geometry, CSolver** n_points_burnt_local = 0, n_points_flame_local = 0, n_not_iterated_global, n_not_in_domain_global, n_points_burnt_global, n_points_flame_global, n_points_unburnt_global; - if (flame_front_ignition) prog_burnt = GetBurntProgressVariable(fluid_model_local, scalar_init); + for (unsigned long i_mesh = 0; i_mesh <= config->GetnMGLevels(); i_mesh++) { fluid_model_local = solver_container[i_mesh][FLOW_SOL]->GetFluidModel(); + if (flame_front_ignition) prog_burnt = GetBurntProgressVariable(fluid_model_local, scalar_init); for (auto iVar = 0u; iVar < nVar; iVar++) scalar_init[iVar] = config->GetSpecies_Init()[iVar]; From 54db191ea32ef3db44cf5fb6d283ef5e8a4fd68a Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Fri, 6 Mar 2026 11:48:37 +0100 Subject: [PATCH 12/19] Fix warnings --- SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp b/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp index 7efdd28f4187..afc8ed5e2e52 100644 --- a/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp +++ b/SU2_CFD/src/solvers/CSpeciesFlameletSolver.cpp @@ -211,7 +211,6 @@ void CSpeciesFlameletSolver::SetInitialCondition(CGeometry** geometry, CSolver** n_points_burnt_local = 0, n_points_flame_local = 0, n_not_iterated_global, n_not_in_domain_global, n_points_burnt_global, n_points_flame_global, n_points_unburnt_global; - for (unsigned long i_mesh = 0; i_mesh <= config->GetnMGLevels(); i_mesh++) { fluid_model_local = solver_container[i_mesh][FLOW_SOL]->GetFluidModel(); if (flame_front_ignition) prog_burnt = GetBurntProgressVariable(fluid_model_local, scalar_init); From bc300c7ec2f26cb7a56317a4d04ef100435e22b0 Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Fri, 6 Mar 2026 11:50:10 +0100 Subject: [PATCH 13/19] Update MLPCpp subproject --- subprojects/MLPCpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/MLPCpp b/subprojects/MLPCpp index 4c1696d92cfd..02f2cb9dde79 160000 --- a/subprojects/MLPCpp +++ b/subprojects/MLPCpp @@ -1 +1 @@ -Subproject commit 4c1696d92cfd7b731d8575b3dce5d89dc65a7910 +Subproject commit 02f2cb9dde791074858e11ac091f7c4df9c6af65 From cdecd43e2e3d90a2ba430a79347c5dcb514826c1 Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Fri, 6 Mar 2026 11:51:24 +0100 Subject: [PATCH 14/19] Update MLPCpp sha tag --- meson_scripts/init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson_scripts/init.py b/meson_scripts/init.py index 76d961d43483..8f54fd3b9594 100755 --- a/meson_scripts/init.py +++ b/meson_scripts/init.py @@ -74,7 +74,7 @@ def init_submodules( github_repo_mel = "https://github.com/pcarruscag/MEL" sha_version_fado = "ce7ee018e4e699af5028d69baa1939fea290e18a" github_repo_fado = "https://github.com/pcarruscag/FADO" - sha_version_mlpcpp = "4c1696d92cfd7b731d8575b3dce5d89dc65a7910" + sha_version_mlpcpp = "02f2cb9dde791074858e11ac091f7c4df9c6af65" github_repo_mlpcpp = "https://github.com/EvertBunschoten/MLPCpp" sha_version_eigen = "d71c30c47858effcbd39967097a2d99ee48db464" github_repo_eigen = "https://gitlab.com/libeigen/eigen.git" From d8c7ed0b07b01d67773e780a088c61e5709f4b9a Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Fri, 6 Mar 2026 12:59:22 +0100 Subject: [PATCH 15/19] Formatting fix --- .../CLookUp_ANN_tests.cpp | 6 +- .../MLP_Jacobian_tests.cpp | 162 +++++++++--------- 2 files changed, 83 insertions(+), 85 deletions(-) diff --git a/UnitTests/Common/toolboxes/multilayer_perceptron/CLookUp_ANN_tests.cpp b/UnitTests/Common/toolboxes/multilayer_perceptron/CLookUp_ANN_tests.cpp index d081a93afe99..7f6dc38ad614 100644 --- a/UnitTests/Common/toolboxes/multilayer_perceptron/CLookUp_ANN_tests.cpp +++ b/UnitTests/Common/toolboxes/multilayer_perceptron/CLookUp_ANN_tests.cpp @@ -37,7 +37,8 @@ #ifdef USE_MLPCPP TEST_CASE("LookUp ANN test", "[LookUpANN]") { MLPToolbox::CLookUp_ANN ANN; - MLPToolbox::CNeuralNetwork mlp = MLPToolbox::CNeuralNetwork("src/SU2/UnitTests/Common/toolboxes/multilayer_perceptron/simple_mlp.mlp"); + MLPToolbox::CNeuralNetwork mlp = + MLPToolbox::CNeuralNetwork("src/SU2/UnitTests/Common/toolboxes/multilayer_perceptron/simple_mlp.mlp"); ANN.AddNetwork(&mlp); su2double x, y, z, z_alt; @@ -89,6 +90,5 @@ TEST_CASE("LookUp ANN test", "[LookUpANN]") { y = -20.0; inside = ANN.Predict(iomap_ref); CHECK(!inside); - } -#endif \ No newline at end of file +#endif diff --git a/UnitTests/Common/toolboxes/multilayer_perceptron/MLP_Jacobian_tests.cpp b/UnitTests/Common/toolboxes/multilayer_perceptron/MLP_Jacobian_tests.cpp index cf12b6539ab6..b2277d7d1b5b 100644 --- a/UnitTests/Common/toolboxes/multilayer_perceptron/MLP_Jacobian_tests.cpp +++ b/UnitTests/Common/toolboxes/multilayer_perceptron/MLP_Jacobian_tests.cpp @@ -36,86 +36,84 @@ #ifdef USE_MLPCPP TEST_CASE("MLP Jacobian test", "[LookUpANN]") { - - /* Create network with random weights */ - std::vector NN = {3, 10, 10, 1}; - MLPToolbox::CNeuralNetwork mlp = MLPToolbox::CNeuralNetwork(NN); - mlp.SetActivationFunction("gelu"); - mlp.RandomWeights(); - - /* Enable Jacobian and Hessian computation */ - mlp.CalcJacobian(true); - mlp.CalcHessian(true); - - /* Network input values */ - su2double val_in_1,val_in_2, val_in_3; - val_in_1 = 0.2; - val_in_2 = 0.3; - val_in_3 = 0.9; - - /* Calculate Jacobians with AD */ - AD::Reset(); - AD::StartRecording(); - AD::RegisterInput(val_in_1); - AD::RegisterInput(val_in_2); - AD::RegisterInput(val_in_3); - - mlp.SetInput(0, val_in_1); - mlp.SetInput(1, val_in_2); - mlp.SetInput(2, val_in_3); - mlp.Predict(); - - su2double val_out = mlp.GetOutput(0); - - AD::RegisterOutput(val_out); - AD::StopRecording(); - SU2_TYPE::SetDerivative(val_out, 1.0); - AD::ComputeAdjoint(); - const su2double jacobian_AD_1 = SU2_TYPE::GetDerivative(val_in_1); - const su2double jacobian_AD_2 = SU2_TYPE::GetDerivative(val_in_2); - const su2double jacobian_AD_3 = SU2_TYPE::GetDerivative(val_in_3); - - /* Retrieve analytical Jacobians */ - const su2double jacobian_ref_1 = mlp.GetJacobian(0, 0); - const su2double jacobian_ref_2 = mlp.GetJacobian(0, 1); - const su2double jacobian_ref_3 = mlp.GetJacobian(0, 2); - - /* Unit tests for Jacobians */ - CHECK(SU2_TYPE::GetValue(jacobian_AD_1) == Approx(SU2_TYPE::GetValue(jacobian_ref_1))); - CHECK(SU2_TYPE::GetValue(jacobian_AD_2) == Approx(SU2_TYPE::GetValue(jacobian_ref_2))); - CHECK(SU2_TYPE::GetValue(jacobian_AD_3) == Approx(SU2_TYPE::GetValue(jacobian_ref_3))); - - - /* Calculate Hessians with AD */ - AD::Reset(); - AD::StartRecording(); - AD::RegisterInput(val_in_1); - AD::RegisterInput(val_in_2); - AD::RegisterInput(val_in_3); - - mlp.SetInput(0, val_in_1); - mlp.SetInput(1, val_in_2); - mlp.SetInput(2, val_in_3); - mlp.Predict(); - - su2double jacobian_analytical = mlp.GetJacobian(0, 0); - - AD::RegisterOutput(jacobian_analytical); - AD::StopRecording(); - SU2_TYPE::SetDerivative(jacobian_analytical, 1.0); - AD::ComputeAdjoint(); - const su2double hessian_AD_1 = SU2_TYPE::GetDerivative(val_in_1); - const su2double hessian_AD_2 = SU2_TYPE::GetDerivative(val_in_2); - const su2double hessian_AD_3 = SU2_TYPE::GetDerivative(val_in_3); - - /* Retrieve analytical Hessians */ - const su2double hessian_ref_1 = mlp.GetHessian(0, 0, 0); - const su2double hessian_ref_2 = mlp.GetHessian(0, 0, 1); - const su2double hessian_ref_3 = mlp.GetHessian(0, 0, 2); - - /* Unit tests on Hessians */ - CHECK(SU2_TYPE::GetValue(hessian_AD_1) == Approx(SU2_TYPE::GetValue(hessian_ref_1))); - CHECK(SU2_TYPE::GetValue(hessian_AD_2) == Approx(SU2_TYPE::GetValue(hessian_ref_2))); - CHECK(SU2_TYPE::GetValue(hessian_AD_3) == Approx(SU2_TYPE::GetValue(hessian_ref_3))); + /* Create network with random weights */ + std::vector NN = {3, 10, 10, 1}; + MLPToolbox::CNeuralNetwork mlp = MLPToolbox::CNeuralNetwork(NN); + mlp.SetActivationFunction("gelu"); + mlp.RandomWeights(); + + /* Enable Jacobian and Hessian computation */ + mlp.CalcJacobian(true); + mlp.CalcHessian(true); + + /* Network input values */ + su2double val_in_1, val_in_2, val_in_3; + val_in_1 = 0.2; + val_in_2 = 0.3; + val_in_3 = 0.9; + + /* Calculate Jacobians with AD */ + AD::Reset(); + AD::StartRecording(); + AD::RegisterInput(val_in_1); + AD::RegisterInput(val_in_2); + AD::RegisterInput(val_in_3); + + mlp.SetInput(0, val_in_1); + mlp.SetInput(1, val_in_2); + mlp.SetInput(2, val_in_3); + mlp.Predict(); + + su2double val_out = mlp.GetOutput(0); + + AD::RegisterOutput(val_out); + AD::StopRecording(); + SU2_TYPE::SetDerivative(val_out, 1.0); + AD::ComputeAdjoint(); + const su2double jacobian_AD_1 = SU2_TYPE::GetDerivative(val_in_1); + const su2double jacobian_AD_2 = SU2_TYPE::GetDerivative(val_in_2); + const su2double jacobian_AD_3 = SU2_TYPE::GetDerivative(val_in_3); + + /* Retrieve analytical Jacobians */ + const su2double jacobian_ref_1 = mlp.GetJacobian(0, 0); + const su2double jacobian_ref_2 = mlp.GetJacobian(0, 1); + const su2double jacobian_ref_3 = mlp.GetJacobian(0, 2); + + /* Unit tests for Jacobians */ + CHECK(SU2_TYPE::GetValue(jacobian_AD_1) == Approx(SU2_TYPE::GetValue(jacobian_ref_1))); + CHECK(SU2_TYPE::GetValue(jacobian_AD_2) == Approx(SU2_TYPE::GetValue(jacobian_ref_2))); + CHECK(SU2_TYPE::GetValue(jacobian_AD_3) == Approx(SU2_TYPE::GetValue(jacobian_ref_3))); + + /* Calculate Hessians with AD */ + AD::Reset(); + AD::StartRecording(); + AD::RegisterInput(val_in_1); + AD::RegisterInput(val_in_2); + AD::RegisterInput(val_in_3); + + mlp.SetInput(0, val_in_1); + mlp.SetInput(1, val_in_2); + mlp.SetInput(2, val_in_3); + mlp.Predict(); + + su2double jacobian_analytical = mlp.GetJacobian(0, 0); + + AD::RegisterOutput(jacobian_analytical); + AD::StopRecording(); + SU2_TYPE::SetDerivative(jacobian_analytical, 1.0); + AD::ComputeAdjoint(); + const su2double hessian_AD_1 = SU2_TYPE::GetDerivative(val_in_1); + const su2double hessian_AD_2 = SU2_TYPE::GetDerivative(val_in_2); + const su2double hessian_AD_3 = SU2_TYPE::GetDerivative(val_in_3); + + /* Retrieve analytical Hessians */ + const su2double hessian_ref_1 = mlp.GetHessian(0, 0, 0); + const su2double hessian_ref_2 = mlp.GetHessian(0, 0, 1); + const su2double hessian_ref_3 = mlp.GetHessian(0, 0, 2); + + /* Unit tests on Hessians */ + CHECK(SU2_TYPE::GetValue(hessian_AD_1) == Approx(SU2_TYPE::GetValue(hessian_ref_1))); + CHECK(SU2_TYPE::GetValue(hessian_AD_2) == Approx(SU2_TYPE::GetValue(hessian_ref_2))); + CHECK(SU2_TYPE::GetValue(hessian_AD_3) == Approx(SU2_TYPE::GetValue(hessian_ref_3))); } -#endif \ No newline at end of file +#endif From d1e07356837cd2bc30413a0a6a40ebb5cc2a8402 Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Fri, 6 Mar 2026 12:59:39 +0100 Subject: [PATCH 16/19] Include fluid model consistency tests and MLP jacobian tests in default AD unit tests --- UnitTests/meson.build | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/UnitTests/meson.build b/UnitTests/meson.build index b2f2ac81a695..f96d5d4adbd5 100644 --- a/UnitTests/meson.build +++ b/UnitTests/meson.build @@ -19,9 +19,10 @@ su2_cfd_tests = files(['Common/geometry/primal_grid/CPrimalGrid_tests.cpp', 'SU2_CFD/windowing.cpp']) # Reverse-mode (algorithmic differentiation) tests: -su2_cfd_tests_ad = files(['Common/simple_ad_test.cpp']) -if get_option('enable-mlpcpp') - su2_cfd_tests_ad = su2_cfd_tests_ad + files(['SU2_CFD/fluid/CFluidModel_tests_AD.cpp', 'Common/toolboxes/multilayer_perceptron/MLP_Jacobian_tests.cpp']) +su2_cfd_tests_ad = files(['Common/simple_ad_test.cpp', + 'SU2_CFD/fluid/CFluidModel_tests_AD.cpp', + 'Common/toolboxes/multilayer_perceptron/MLP_Jacobian_tests.cpp']) + endif # Forward-mode (direct differentiation) tests: From 60c0515cb0f485c3a31ee13faab7526b0c8f2102 Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Fri, 6 Mar 2026 13:03:09 +0100 Subject: [PATCH 17/19] removed typo --- UnitTests/meson.build | 2 -- 1 file changed, 2 deletions(-) diff --git a/UnitTests/meson.build b/UnitTests/meson.build index f96d5d4adbd5..40b54cf69717 100644 --- a/UnitTests/meson.build +++ b/UnitTests/meson.build @@ -23,8 +23,6 @@ su2_cfd_tests_ad = files(['Common/simple_ad_test.cpp', 'SU2_CFD/fluid/CFluidModel_tests_AD.cpp', 'Common/toolboxes/multilayer_perceptron/MLP_Jacobian_tests.cpp']) -endif - # Forward-mode (direct differentiation) tests: su2_cfd_tests_dd = files(['Common/simple_directdiff_test.cpp']) From 752ea22cb9e6807aba01dc705ded88f41e7cad46 Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Fri, 6 Mar 2026 13:25:21 +0100 Subject: [PATCH 18/19] Include MLPCpp submodule in AD build --- .github/workflows/regression.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml index c1bebd3928ff..cb7d6cca23ce 100644 --- a/.github/workflows/regression.yml +++ b/.github/workflows/regression.yml @@ -28,7 +28,7 @@ jobs: - config_set: BaseMPI flags: '-Denable-pywrapper=true -Denable-coolprop=true -Denable-mpp=true -Dinstall-mpp=true -Denable-mlpcpp=true -Denable-tests=true --warnlevel=2' - config_set: ReverseMPI - flags: '-Denable-autodiff=true -Denable-normal=false -Denable-pywrapper=true -Denable-tests=true --warnlevel=3 --werror' + flags: '-Denable-autodiff=true -Denable-normal=false -Denable-pywrapper=true -Denable-tests=true -Denable-mlpcpp=true --warnlevel=3 --werror' - config_set: ForwardMPI flags: '-Denable-directdiff=true -Denable-normal=false -Denable-tests=true -Denable-mlpcpp=true --warnlevel=3 --werror' - config_set: BaseNoMPI From 80c7c945e28bb9f1cae82266c2cd7b6ff5eeea31 Mon Sep 17 00:00:00 2001 From: EvertBunschoten Date: Fri, 6 Mar 2026 14:17:41 +0100 Subject: [PATCH 19/19] Fix: only configure query for passive look-ups if there are any --- SU2_CFD/src/fluid/CFluidFlamelet.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SU2_CFD/src/fluid/CFluidFlamelet.cpp b/SU2_CFD/src/fluid/CFluidFlamelet.cpp index e663c6564612..d84a4b83a745 100644 --- a/SU2_CFD/src/fluid/CFluidFlamelet.cpp +++ b/SU2_CFD/src/fluid/CFluidFlamelet.cpp @@ -235,7 +235,8 @@ void CFluidFlamelet::PreprocessLookUp(CConfig* config) { iomap_LookUp = new MLPToolbox::CIOMap(controlling_variable_names, varnames_LookUp); lookup_mlp->PairVariableswithMLPs(*iomap_TD); lookup_mlp->PairVariableswithMLPs(*iomap_Sources); - lookup_mlp->PairVariableswithMLPs(*iomap_LookUp); + if (n_lookups > 1) + lookup_mlp->PairVariableswithMLPs(*iomap_LookUp); if (preferential_diffusion) { iomap_PD = new MLPToolbox::CIOMap(controlling_variable_names, varnames_PD); lookup_mlp->PairVariableswithMLPs(*iomap_PD);