-
Configuration File
Mesh File
Restart File
Solver Setup
Physical Definition
Markers and Boundary Conditions
Convective Schemes
Slope Limiters and Shock Resolution
History and Solution Output
Linear Solvers and Preconditioners
Basics of Multi-Zone Computations
Execution
Aeroacoustic analysis of unsteady pressure
Flamelet Solver — Python Wrapper Wall Boundary Conditions
Output Fields Reference
Error Messages
-
Setup Guide: Steady RANS for External Aerodynamics
Setup Guide: Incompressible Flows
Setup Guide: Unsteady Flows and DES
Setup Guide: Laminar-Turbulent Transition
Setup Guide: Turbomachinery
Setup Guide: Heat Transfer and Conjugate Heat Transfer
Setup Guide: Species Transport and Combustion
Setup Guide: Shape Optimization with the Discrete Adjoint
Setup Guide: Non-Ideal Compressible Flows
-
Option Reference
Problem Definition
FluidModel
Data-driven fluid model parameters
Freestream Conditions
Reference Conditions
Boundary Markers
Time-marching
Linear solver definition
Convergence
Multi-grid
Spatial Discretization
Adjoint and Gradient
Sobolev Gradient Solver Parameters
Input/output files and formats
Dynamic mesh definition
Aeroelastic Simulation (Typical Section Model)
Wind Gust
Equivalent Area
Grid deformation
Deformable mesh
FEM flow solver definition
FEA solver
Multizone definition
Radiation solver
Heat solver
Visualize Control Volumes
Inverse design problem
Unsupported options
FFD point inversion
Automatic Differentation options
Python Options
Hybrid_RANSLES Options
Output Options
libROM options
Renamed and Removed Options
-
GIT-ting Started
Developing SU2 on GitHub (Internal Developers)
Running Regression Tests
Code Review
Code Structure
Style Guide
Advanced AD Techniques
Containers for Development
Writing Unit Tests
Integrating Tracy Profiler with SU2
Automatic Differentiation (AD)
Code Reference (Doxygen)
Debugging SU2
Documentation Improvements and Status
Glossary
Docs Check
Adding a Fluid Model
This page shows how to add a new thermodynamic model to the finite volume flow solvers of SU2. Such a model is an equation of state: the relations between pressure, density, temperature and energy of the fluid. The page also covers, more briefly, new models for viscosity, thermal conductivity and mass diffusivity.
You will learn:
- what the compressible and incompressible solvers ask from a fluid model;
- where the model is created;
- how the model works with the non-dimensionalization, that is, the scaling of all quantities by reference values.
For the overall layout of the code, see Code Structure. For options in general, see Adding a Config Option.
- Files you will touch
- The FLUID_MODEL option
- The CFluidModel class
- What the solvers call
- Existing models
- Add a compressible model
- Add an incompressible model
- Non-dimensionalization and free-stream values
- Transport property models
- NEMO
- Discrete adjoint
- Tests
- Checklist
Files you will touch
| File | What goes there |
|---|---|
Common/include/option_structure.hpp |
A value in enum ENUM_FLUIDMODEL and its keyword in FluidModel_Map |
Common/include/CConfig.hpp, Common/src/CConfig.cpp |
Options for the parameters of the model, checks in CConfig::SetPostprocessing |
config_template.cfg |
The keyword in the comment above FLUID_MODEL, and the new options |
SU2_CFD/include/fluid/CMyFluid.hpp, SU2_CFD/src/fluid/CMyFluid.cpp |
The model class (a header alone is fine for simple models) |
SU2_CFD/src/meson.build |
The new .cpp in the fluid/... list |
SU2_CFD/src/solvers/CEulerSolver.cpp (compressible) or SU2_CFD/src/solvers/CIncEulerSolver.cpp (incompressible) |
The new of the model in SetNondimensionalization (two places) |
SU2_CFD/src/solvers/CFEM_DG_EulerSolver.cpp |
Optional: the same for the DG solver |
SU2_CFD/src/output/CFlowOutput.cpp |
Optional: the description in CFlowOutput::WriteForcesBreakdown |
UnitTests/SU2_CFD/fluid/CFluidModel_tests.cpp, CFluidModel_tests_AD.cpp |
Consistency tests of the model |
TestCases/serial_regression.py, TestCases/parallel_regression.py |
A regression test |
The FLUID_MODEL option
The option is registered in CConfig::SetConfig_Options:
addEnumOption("FLUID_MODEL", Kind_FluidModel, FluidModel_Map, STANDARD_AIR);
The enum and the map are in Common/include/option_structure.hpp. ENUM_FLUIDMODEL is a plain enum, so the values have no prefix: write IDEAL_GAS, not ENUM_FLUIDMODEL::IDEAL_GAS.
enum ENUM_FLUIDMODEL {
STANDARD_AIR = 0, /*!< \brief Standard air gas model. */
IDEAL_GAS = 1, /*!< \brief Ideal gas model. */
VW_GAS = 2, /*!< \brief Van Der Waals gas model. */
PR_GAS = 3, /*!< \brief Perfect Real gas model. */
CONSTANT_DENSITY = 4, /*!< \brief Constant density gas model. */
INC_IDEAL_GAS = 5, /*!< \brief Incompressible ideal gas model. */
INC_IDEAL_GAS_POLY = 6, /*!< \brief Inc. ideal gas, polynomial gas model. */
MUTATIONPP = 7, /*!< \brief Mutation++ gas model for nonequilibrium flow. */
SU2_NONEQ = 8, /*!< \brief User defined gas model for nonequilibrium flow. */
FLUID_MIXTURE = 9, /*!< \brief Species mixture model. */
COOLPROP = 10, /*!< \brief Thermodynamics library. */
FLUID_FLAMELET = 11, /*!< \brief lookup table (LUT) method for premixed flamelets. */
DATADRIVEN_FLUID = 12, /*!< \brief multi-layer perceptron driven fluid model. */
};
To register the model:
- Add the new value at the end of the enum.
- Add a
MakePair("MY_FLUID", MY_FLUID)line toFluidModel_Map. - Add the keyword to the comment above
FLUID_MODELinconfig_template.cfg.
Read the value with config->GetKind_FluidModel().
The model parameters are normal options. The existing ones are next to FLUID_MODEL in CConfig::SetConfig_Options. Reuse them when they fit:
GAMMA_VALUE,GAS_CONSTANT,CRITICAL_TEMPERATURE,CRITICAL_PRESSURE,ACENTRIC_FACTOR;FLUID_NAME(CoolProp);INTERPOLATION_METHODandFILENAMES_INTERPOLATOR(data-driven);SPECIFIC_HEAT_CP,MOLECULAR_WEIGHT,THERMODYNAMIC_PRESSURE(incompressible);- …
The CFluidModel class
Your model derives from CFluidModel (SU2_CFD/include/fluid/CFluidModel.hpp, SU2_CFD/src/fluid/CFluidModel.cpp). This class holds the thermodynamic state of one point. A solver sets the state with one of the SetTDState_... functions, then reads the other quantities with getters.
The state is stored in protected members. A child class must fill them:
Density,Pressure,Temperature,StaticEnergy,Enthalpy,Entropy,SoundSpeed2,Cp,Cv;- the derivatives
dPdrho_e,dPde_rho,dTdrho_e,dTde_rho, used by the compressible solver; - the derivatives
dhdrho_P,dhdP_rho,dsdrho_P,dsdP_rho, used by the non-reflecting boundary conditions (NRBC); Mu,Kt,mass_diffusivityand their derivatives. The transport models fill these (see below).
The getters (GetPressure(), GetTemperature(), GetSoundSpeed(), GetSoundSpeed2(), GetdPdrho_e(), …) are not virtual. They return these members.
The virtual functions that set the state all do nothing in the base class. Override the ones that your target solver calls:
| Function | Inputs |
|---|---|
SetTDState_rhoe(su2double rho, su2double e) |
density, static energy |
SetTDState_PT(su2double P, su2double T) |
pressure, temperature |
SetTDState_Prho(su2double P, su2double rho) |
pressure, density |
SetEnergy_Prho(su2double P, su2double rho) |
pressure, density; only StaticEnergy is required |
SetTDState_hs(su2double h, su2double s) |
enthalpy, entropy |
SetTDState_rhoT(su2double rho, su2double T) |
density, temperature |
SetTDState_Ps(su2double P, su2double s) |
pressure, entropy |
ComputeDerivativeNRBC_Prho(su2double P, su2double rho) |
pressure, density; sets the state and dhdrho_P, dhdP_rho, dsdrho_P, dsdP_rho |
SetTDState_T(su2double val_Temperature, const su2double* val_scalars = nullptr) |
temperature (incompressible, with optional species or flamelet scalars) |
SetTDState_h(su2double val_enthalpy, const su2double* val_scalars = nullptr) |
enthalpy (incompressible) |
Special models use other virtual functions:
SetCpModel(CIncIdealGasPolynomial);EvaluateDataSet(CFluidFlamelet);GetNScalars(not overridden by any model);GetExtrapolation(CDataDrivenFluid,CFluidFlamelet);GetnIter_Newton(CDataDrivenFluid);GetEnthalpyDiffusivityandGetGradEnthalpyDiffusivity(CFluidScalar);- the transport getters
GetLaminarViscosity,GetThermalConductivity,GetMassDiffusivity.
A call that is not overridden does nothing and gives no error. So check the table of callers before you leave a function out.
What the solvers call
Your model must implement the functions that its solver calls.
Compressible solver (CEulerSolver, CNSSolver). The main call is in the variable classes. CEulerVariable::SetPrimVar (SU2_CFD/src/variables/CEulerVariable.cpp) runs for every point at every iteration:
bool CEulerVariable::SetPrimVar(unsigned long iPoint, CFluidModel *FluidModel) {
bool RightVol = true;
SetVelocity(iPoint); // Computes velocity and velocity^2
su2double density = GetDensity(iPoint);
su2double staticEnergy = GetEnergy(iPoint)-0.5*Velocity2(iPoint);
/*--- Check will be moved inside fluid model plus error description strings ---*/
FluidModel->SetTDState_rhoe(density, staticEnergy);
bool check_dens = SetDensity(iPoint);
bool check_press = SetPressure(iPoint, FluidModel->GetPressure());
bool check_sos = SetSoundSpeed(iPoint, FluidModel->GetSoundSpeed2());
bool check_temp = SetTemperature(iPoint, FluidModel->GetTemperature());
// ...
}
Other calls in the variable classes:
CNSVariable::SetPrimVardoes the same, with the turbulent kinetic energy removed from the energy. It then callsGetLaminarViscosity(),GetThermalConductivity()andGetCp().- The
SetSecondaryVarfunctions storeGetdPdrho_e()andGetdPde_rho()(Euler). For Navier-Stokes they also storeGetdTdrho_e(),GetdTde_rho(),Getdmudrho_T(),GetdmudT_rho(),Getdktdrho_T(),GetdktdT_rho(). - The solver reaches the model with
GetFluidModel(), which returns the object of the current OpenMP thread.
So in SetTDState_rhoe, a compressible model must set at least:
Density,StaticEnergy,Pressure,Temperature,SoundSpeed2;dPdrho_e,dPde_rho,dTdrho_e,dTde_rho;Entropy;Cp, for viscous flows.
The ideal gas sets Cp, Cv and the temperature derivatives only once, in its constructor.
The other functions are called by:
| Function | Callers (compressible) |
|---|---|
SetTDState_rhoe |
the variable classes (above), the CEulerSolver constructor, BC_Riemann, BC_TurboRiemann, BC_Giles |
SetTDState_PT |
CEulerSolver::SetNondimensionalization (free stream), BC_Riemann, BC_TurboRiemann, BC_Giles, BC_ActDisk |
SetTDState_Prho |
SetNondimensionalization, ComputeConsistentExtrapolation (MUSCL with non-ideal fluids), BC_Riemann, BC_TurboRiemann, BC_Giles, PreprocessBC_Giles, MixedOut_Average, TurboAverageProcess, SetFreeStream_TurboSolution, CFVMFlowSolverBase::BC_Fluid_Interface; also CTurbSASolver and CTurbSSTSolver |
SetEnergy_Prho |
SetNondimensionalization (non-dimensional free-stream energy), LowMachPrimitiveCorrection |
SetTDState_rhoT |
SetNondimensionalization (INIT_OPTION= REYNOLDS) |
SetTDState_hs |
BC_Riemann, BC_TurboRiemann |
SetTDState_Ps |
CTurboOutput (turbomachinery performance) |
ComputeDerivativeNRBC_Prho |
CEulerSolver::MixedOut_Average, CNumerics::ComputeResJacobianGiles |
Some boundary conditions do not use the fluid model. For example, CEulerSolver::BC_Inlet uses ideal-gas relations with Gamma and Gas_Constant. For non-ideal fluids, the Riemann and Giles boundary conditions go through the fluid model.
Incompressible solver (CIncEulerSolver, CIncNSSolver). The energy variable is the enthalpy.
CIncEulerVariable::SetPrimVarandCIncNSVariable::SetPrimVarcallSetTDState_h(Enthalpy, scalar). They then readGetTemperature(),GetDensity(),GetCp(),GetCv()andGetEnthalpy(). For viscous flows they also readGetLaminarViscosity()andGetThermalConductivity(), afterSetEddyViscosity.- Initialization and boundary conditions call
SetTDState_T. - The thermodynamic pressure is constant during the run. It is set in the constructor of the model. Only the setup changes it, with
SetPressure(see below).
Existing models
Start from an existing model close to yours. All are in SU2_CFD/include/fluid/ and SU2_CFD/src/fluid/.
| Keyword | Class | Solver | Notes |
|---|---|---|---|
STANDARD_AIR, IDEAL_GAS |
CIdealGas |
compressible | Closed-form inverses for every SetTDState_... |
VW_GAS |
CVanDerWaalsGas |
compressible | Child of CIdealGas; Newton iteration in SetTDState_PT |
PR_GAS |
CPengRobinson |
compressible | Child of CIdealGas |
COOLPROP |
CCoolProp |
compressible | Calls the CoolProp library (-Denable-coolprop=true); dimensional only |
DATADRIVEN_FLUID |
CDataDrivenFluid |
compressible | Multi-layer perceptron, a small neural network (-Denable-mlpcpp=true), or look-up table; Newton solver for the inverse functions |
CONSTANT_DENSITY |
CConstantDensity |
incompressible | Header only |
INC_IDEAL_GAS |
CIncIdealGas |
incompressible | Header only, density from the operating pressure |
INC_IDEAL_GAS_POLY |
CIncIdealGasPolynomial |
incompressible | Header only, Cp polynomial in temperature |
FLUID_MIXTURE |
CFluidScalar |
incompressible | Species mixture, one transport model per species |
FLUID_FLAMELET |
CFluidFlamelet |
incompressible | Look-up table of a flamelet manifold (precomputed combustion states) |
SU2_NONEQ, MUTATIONPP |
CSU2TCLib, CMutationTCLib (children of CNEMOGas) |
NEMO | See NEMO |
Good starting points:
CVanDerWaalsGasfor a cubic equation of state;CDataDrivenFluidfor a model that inverts its functions with a Newton solver;CIncIdealGasfor an incompressible model.
The state function of the ideal gas, from SU2_CFD/src/fluid/CIdealGas.cpp, shows how little a model needs:
void CIdealGas::SetTDState_rhoe(su2double rho, su2double e) {
Density = rho;
StaticEnergy = e;
dPdrho_e = Gamma_Minus_One * StaticEnergy;
dPde_rho = Gamma_Minus_One * Density;
Pressure = dPde_rho * StaticEnergy;
Temperature = dTde_rho * StaticEnergy;
SoundSpeed2 = Gamma * Gas_Constant * Temperature;
if (ComputeEntropy) Entropy = Cv * log(Temperature) - Gas_Constant * log(Density);
}
Add a compressible model
Follow these steps to add a model for CEulerSolver and CNSSolver.
- Option. Add the enum value and the keyword (above). Add options for new parameters: member, getter,
add...Option,config_template.cfg. See Adding a Config Option. - Class. Write
class CMyFluid final : public CFluidModelinSU2_CFD/include/fluid/CMyFluid.hpp. You can also derive fromCIdealGasto reuse its members, asCVanDerWaalsGasandCPengRobinsondo. Implement the functions of the table above that your cases need. The initialization alone needsSetTDState_rhoe,SetTDState_PT,SetTDState_PrhoandSetEnergy_Prho. It also needsSetTDState_rhoTwhen the free stream is set from the Reynolds number (INIT_OPTION= REYNOLDS). - Build. Add
fluid/CMyFluid.cppto thefluid/...list inSU2_CFD/src/meson.build. - Creation. In
CEulerSolver::SetNondimensionalization(SU2_CFD/src/solvers/CEulerSolver.cpp), add acaseto bothswitch (config->GetKind_FluidModel())blocks:- the first creates
auxFluidModelwith dimensional parameters, to compute the free-stream state; - the second, inside
SU2_OMP_PARALLEL, creates one model per thread with non-dimensional parameters, and stores it inFluidModel[thread].
This is the second block for the Van der Waals gas:
case VW_GAS: FluidModel[thread] = new CVanDerWaalsGas(Gamma, Gas_ConstantND, config->GetPressure_Critical() / config->GetPressure_Ref(), config->GetTemperature_Critical() / config->GetTemperature_Ref()); break;Include the header at the top of
CEulerSolver.cpp.CEulerSolver::~CEulerSolverdeletes the models. The firstswitchends withSU2_MPI::Error("Unknown fluid model.", ...), so an incompressible keyword cannot reach the compressible solver. - the first creates
- Optional. Add the name to the
ModelTableprinted at the end ofSetNondimensionalization, and toCFlowOutput::WriteForcesBreakdown. If the model must also work with the DG solver, repeat step 4 inCFEM_DG_EulerSolver::SetNondimensionalization.
When the model is not STANDARD_AIR or IDEAL_GAS, the solver changes in three ways:
CDriver::InitializeNumericscreates the general numerics:CUpwGeneralRoe_Flow,CUpwGeneralHLLC_Flow,CGeneralAvgGrad_Flow,CSourceGeneralAxisymmetric_Flow. They read the secondary variables:S_i[0]= dP/drho at constant e,S_i[1]= dP/de at constant rho.CEulerSolver::Upwind_Residualuses the classic path, not the vectorized one. After the MUSCL reconstruction (the second-order left and right states of each edge),ComputeConsistentExtrapolationrecomputes these states in a thermodynamically consistent way. It callsSetTDState_Prho.CConfig::SetPostprocessingallows onlyROE,HLLCandJST(search for “Non-Ideal Compressible Fluids”).
Add an incompressible model
Follow these steps to add a model for CIncEulerSolver and CIncNSSolver.
- Add the enum value, the keyword and the options, as for a compressible model.
- Write the class with
SetTDState_TandSetTDState_h.- They must set
Temperature,DensityandEnthalpy. - Set
CpandCvtoo. Constant values can be set in the constructor, as inCIncIdealGas. - The enthalpy is the sensible enthalpy relative to a reference temperature. The existing models take
STD_REF_TEMP / config->GetTemperature_Ref()as the last constructor argument.
- They must set
- In
CIncEulerSolver::SetNondimensionalization(SU2_CFD/src/solvers/CIncEulerSolver.cpp), add acaseto bothswitchblocks. The incompressible model headers are included in this file. The two blocks are:- the dimensional
auxFluidModel; - the per-thread
FluidModelloop (for (auto& fluidModel : FluidModel)). After the secondswitch, this loop sets the transport models when the flow is viscous.
- the dimensional
- Check
CConfig::SetPostprocessing:- With
INC_DENSITY_MODEL= CONSTANTorBOUSSINESQ, the code setsKind_FluidModel = CONSTANT_DENSITY. - With
INC_DENSITY_MODEL= VARIABLE, it accepts onlyINC_IDEAL_GAS,INC_IDEAL_GAS_POLY,FLUID_MIXTUREandFLUID_FLAMELET. Add the new value there if the density varies. - The same function rejects
DATADRIVEN_FLUIDfor the incompressible solvers.
- With
Non-dimensionalization and free-stream values
SetNondimensionalization sets the free-stream state and the reference values. It creates each fluid model twice:
- a dimensional copy, for the setup;
- non-dimensional copies, one per thread, used during the run.
Compressible free stream (CEulerSolver::SetNondimensionalization):
Temperature_Ref,Viscosity_RefandConductivity_Refare set to 1. So the...ND()getters ofCConfigreturn dimensional values for the auxiliary model. For example,GetMu_ConstantND()returnsMu_Constant / Viscosity_Ref.auxFluidModelis created.- With
FREESTREAM_OPTION= TEMPERATURE_FS,SetTDState_PT(Pressure_FreeStream, Temperature_FreeStream)gives the density. - With
DENSITY_FS,SetTDState_Prhogives the temperature.
- With
GetSoundSpeed()turns the Mach number into the free-stream velocity.- For viscous flows,
SetLaminarViscosityModelandSetThermalConductivityModelare called on the auxiliary model. WithINIT_OPTION= REYNOLDS, the density comes from the Reynolds number, andSetTDState_rhoTgives the pressure.GetStaticEnergy()gives the free-stream energy. - The reference values are set from
REF_DIMENSIONALIZATION:Pressure_Ref,Density_Ref,Temperature_Ref, thenVelocity_Ref = sqrt(Pressure_Ref/Density_Ref),Gas_Constant_Ref = Velocity_Ref^2/Temperature_Ref,Viscosity_Ref,Conductivity_Ref, … - The per-thread models are created with non-dimensional parameters.
SetEnergy_Prho(Pressure_FreeStreamND, Density_FreeStreamND)gives the non-dimensional free-stream energy.Energy_Refis the ratio of the dimensional and the non-dimensional energies.
So the non-dimensional model must be consistent with these scales:
| Quantity | Divided by |
|---|---|
| pressure | Pressure_Ref |
| density | Density_Ref |
| temperature | Temperature_Ref |
| energy and enthalpy | Velocity_Ref^2 |
| gas constant | Gas_Constant_Ref |
The simplest way is to give non-dimensional parameters to the constructor. VW_GAS and PR_GAS do this with Gas_ConstantND and with the critical values divided by the reference values.
Some models cannot be scaled, for example tables or external libraries in physical units. For these, allow only REF_DIMENSIONALIZATION= DIMENSIONAL. CoolProp does this in CConfig::SetPostprocessing:
/*--- Check if CoolProp is used with non-dimensionalization. ---*/
if (Kind_FluidModel == COOLPROP && Ref_NonDim != DIMENSIONAL) {
SU2_MPI::Error("CoolProp can not be used with non-dimensionalization.", CURRENT_FUNCTION);
}
Incompressible (CIncEulerSolver::SetNondimensionalization):
auxFluidModel->SetTDState_T(Temperature_FreeStream, scalar_init)gives the free-stream density.INIT_OPTION_INCdecides what happens next. Either the thermodynamic pressure is scaled (SetPressure) to match the given density, or the density is taken from the model.- The per-thread models get non-dimensional parameters:
Density_FreeStreamND,Specific_Heat_CpND,Gas_ConstantND,Pressure_ThermodynamicND. Right after creation, most of these models getSetTDState_T(Temperature_FreeStreamND).
Transport property models
Viscosity, thermal conductivity and mass diffusivity come from separate objects owned by the fluid model:
unique_ptr<CViscosityModel> LaminarViscosity; /*!< \brief Laminar Viscosity Model */
unique_ptr<CConductivityModel> ThermalConductivity; /*!< \brief Thermal Conductivity Model */
unique_ptr<CDiffusivityModel> MassDiffusivity; /*!< \brief Mass Diffusivity Model */
| Option | Enum and map | Base class | Factory in CFluidModel.cpp |
|---|---|---|---|
VISCOSITY_MODEL |
VISCOSITYMODEL, ViscosityModel_Map |
CViscosityModel: SetViscosity(t, rho) |
MakeLaminarViscosityModel |
CONDUCTIVITY_MODEL, TURBULENT_CONDUCTIVITY_MODEL |
CONDUCTIVITYMODEL, ConductivityModel_Map; CONDUCTIVITYMODEL_TURB, TurbConductivityModel_Map |
CConductivityModel: SetConductivity(t, rho, mu_lam, mu_turb, cp, dmudrho_t, dmudt_rho) |
MakeThermalConductivityModel |
DIFFUSIVITY_MODEL |
DIFFUSIVITYMODEL, Diffusivity_Model_Map |
CDiffusivityModel: SetDiffusivity(rho, mu_lam, cp, kt) |
MakeMassDiffusivityModel |
Existing children:
- viscosity:
CConstantViscosity,CSutherland,CPolynomialViscosity,CCoolPropViscosity; - conductivity:
CConstantConductivity,CConstantConductivityRANS,CConstantPrandtl,CConstantPrandtlRANS,CPolynomialConductivity,CPolynomialConductivityRANS,CCoolPropConductivity; - diffusivity:
CConstantDiffusivity,CConstantSchmidt,CConstantLewisDiffusivity.
For the FLAMELET value of each option, the factory returns nullptr, because CFluidFlamelet overrides the getters and reads the table.
A model sets its value and its derivatives in protected members. CSutherland:
void SetViscosity(su2double t, su2double rho) override {
const su2double t_ref_inv = 1.0 / t_ref_;
const su2double t_nondim = t_ref_inv * t;
mu_ = mu_ref_ * t_nondim * sqrt(t_nondim) * ((t_ref_ + s_) / (t + s_));
/*--- Set Viscosity Derivatives. ---*/
const su2double ts_inv = 1.0 / (t + s_);
dmudrho_t_ = 0.0;
dmudt_rho_ = mu_ref_ * (t_ref_ + s_) * ts_inv * sqrt(t_nondim) * (1.5 * t_ref_inv - t_nondim * ts_inv);
}
To add a transport model:
- Add a value to the enum and its map in
option_structure.hpp, and options for its parameters. - Write the class in
SU2_CFD/include/fluid/, derived from the base class of the table. A header alone is fine. - Add a
casein the factory inSU2_CFD/src/fluid/CFluidModel.cpp, and include the header there. Take the parameters from non-dimensional getters ofCConfig, such asGetMu_RefND(iSpecies). These divide by the reference values, so the same factory serves both the dimensional and the non-dimensional model. TheiSpeciesargument is used byCFluidScalar, which creates one model per species.
How the solvers use it:
- In
SetNondimensionalization, the solvers callSetLaminarViscosityModel(config),SetThermalConductivityModel(config)andSetMassDiffusivityModel(config)on each fluid model. CFluidModel::GetLaminarViscosity()then callsLaminarViscosity->SetViscosity(Temperature, Density). So the state must be set before.GetThermalConductivity()usesMu,Mu_TurbandCp. Only the incompressible solver setsMu_Turb, withSetEddyViscosity. In the compressible solver it stays zero, and the viscous numerics add the turbulent part of the conductivity withPRANDTL_TURB.
NEMO
Nothing on this page applies to non-equilibrium flows (NEMO_EULER, NEMO_NAVIER_STOKES). They use a separate interface, CNEMOGas, a child of CFluidModel. Its children are CSU2TCLib and CMutationTCLib. They are created in CNEMOEulerSolver::SetNondimensionalization and in CNEMONumerics.
Discrete adjoint
Write your model so that automatic differentiation (AD) works. The discrete adjoint records the primal iteration on the AD tape, the record of every operation it differentiates. The fluid model is called inside that iteration: in SetPrimVar for every point, in the boundary conditions, and in the reconstruction. So:
- Write the model with
su2double. A value converted todoublehas no derivative, whether throughSU2_TYPE::GetValueor through a library that works ondouble. CoolProp is such a library: the error message ofCCoolPropsays that it cannot be used withdirectdifforautodiff. - Iterative inversions, such as the Newton loops in
CVanDerWaalsGas::SetTDState_PTor inCDataDrivenFluid, are recorded with all their iterations. This makes the tape larger. - Avoid hidden state between calls. For example,
CVanDerWaalsGaskeeps the compressibility factorZedof the last call as the first guess of its Newton loop. So the result ofSetTDState_PTdepends on the previous call, within the tolerance. - The hand-written derivatives (
dPdrho_e,dPde_rho, …) are recorded and differentiated like any other value. They enter the general Roe and HLLC fluxes (through the secondary variables), the viscous Jacobians and some boundary conditions. Compute them withsu2doublefrom the inputs, and check them with the unit tests below.
An adjoint regression test with a non-ideal fluid is transonic_stator (TestCases/disc_adj_turbomachinery/transonic_stator_2D/transonic_stator.cfg, PR_GAS). It is in serial_regression_AD.py and parallel_regression_AD.py.
Tests
Add unit tests and a regression test for the new model.
Unit tests. Add a TEST_CASE for the new model to both files below. Build with -Denable-tests=true; see Writing Unit Tests.
UnitTests/SU2_CFD/fluid/CFluidModel_tests.cpphasFluidModelChecks(fluid_model, p, T). It checks thatSetTDState_rhoeinvertsSetTDState_PT. It also comparesdPdrho_e,dPde_rho,dTdrho_e,dTde_rhowith finite differences.UnitTests/SU2_CFD/fluid/CFluidModel_tests_AD.cpphasFluidModelADChecks(fluid_model, p, T). It compares the same derivatives,Cp, and the NRBC entropy derivatives with AD. It is built astest_driver_AD.
Regression tests. The non-ideal compressible cases are in TestCases/nicf/. They are in the “NICFD” sections of the regression scripts:
| Test | Configuration | Fluid model | Script |
|---|---|---|---|
ls89_sa |
nicf/LS89/turb_SA_PR.cfg |
PR_GAS |
serial |
edge_VW |
nicf/edge/edge_VW.cfg |
VW_GAS |
serial, parallel, hybrid |
edge_PPR |
nicf/edge/edge_PPR.cfg |
PR_GAS |
serial, parallel, hybrid |
coolprop_fluidModel, coolprop_transportModel |
nicf/coolprop/fluidModel.cfg, transportModel.cfg |
COOLPROP (only on x86_64) |
parallel |
datadriven_fluidModel |
nicf/datadriven/datadriven_nozzle.cfg |
DATADRIVEN_FLUID |
parallel |
Some TestCases/turbomachinery/ cases also use PR_GAS: centrifugal_blade, centrifugal_stage, transonic_stator_2D.
To add a regression test:
- Copy one of these cases and switch it to the new model.
- Add it as a new
TestCase; see Running Regression Tests. - Keep the test short (few iterations).
- If the model is meant for optimization, add an AD test too.
Checklist
- Enum value and keyword in
ENUM_FLUIDMODEL/FluidModel_Map, keyword and options inconfig_template.cfg. - The
SetTDState_...functions that your solver calls, and all the state members and derivatives they must set. - New
.cppinSU2_CFD/src/meson.build. - Both
switchblocks inSetNondimensionalization(dimensional and non-dimensional). - Consistent non-dimensional parameters, or a check that forces
REF_DIMENSIONALIZATION= DIMENSIONAL. - Checks in
CConfig::SetPostprocessing(density model list for incompressible models). - Unit tests with
FluidModelChecksandFluidModelADChecks, and a regression test.
Last checked against SU2 develop 11c177b904.