diff --git a/Common/include/CConfig.hpp b/Common/include/CConfig.hpp index 7d75bfcd23b5..726495c42250 100644 --- a/Common/include/CConfig.hpp +++ b/Common/include/CConfig.hpp @@ -199,6 +199,8 @@ class CConfig { nMarker_Inlet, /*!< \brief Number of inlet flow markers. */ nMarker_Inlet_Species, /*!< \brief Number of inlet species markers. */ nSpecies_per_Inlet, /*!< \brief Number of species defined per inlet markers. */ + nMarker_Wall_Species, /*!< \brief Number of wall species markers. */ + nSpecies_per_Wall, /*!< \brief Number of species defined per wall markers. */ nMarker_Inlet_Turb, /*!< \brief Number of inlet turbulent markers. */ nTurb_Properties, /*!< \brief Number of turbulent properties per inlet markers. */ nMarker_Riemann, /*!< \brief Number of Riemann flow markers. */ @@ -255,6 +257,7 @@ class CConfig { *Marker_ActDiskBemOutlet_Axis, /*!< \brief Actuator disk BEM outlet markers passed to MARKER_ACTDISK_BEM_AXIS. */ *Marker_Inlet, /*!< \brief Inlet flow markers. */ *Marker_Inlet_Species, /*!< \brief Inlet species markers. */ + *Marker_Wall_Species, /*!< \brief Wall species markers. */ *Marker_Inlet_Turb, /*!< \brief Inlet turbulent markers. */ *Marker_Riemann, /*!< \brief Riemann markers. */ *Marker_Giles, /*!< \brief Giles markers. */ @@ -294,6 +297,8 @@ class CConfig { su2double **Inlet_Velocity; /*!< \brief Specified flow velocity vectors for supersonic inlet boundaries. */ su2double **Inlet_SpeciesVal; /*!< \brief Specified species vector for inlet boundaries. */ su2double **Inlet_TurbVal; /*!< \brief Specified turbulent intensity and viscosity ratio for inlet boundaries. */ + WALL_SPECIES_TYPE **Kind_Wall_Species; /*!< \brief Species boundary condition type for wall boundaries (FLUX or VALUE) per species. */ + su2double **Wall_SpeciesVal; /*!< \brief Specified species flux or value for wall boundaries per species. */ su2double *EngineInflow_Target; /*!< \brief Specified fan face targets for nacelle boundaries. */ su2double *Inflow_Mach; /*!< \brief Specified fan face mach for nacelle boundaries. */ su2double *Inflow_Pressure; /*!< \brief Specified fan face pressure for nacelle boundaries. */ @@ -1394,6 +1399,11 @@ class CConfig { void addGilesOption(const string name, unsigned short & nMarker_Giles, string * & Marker_Giles, unsigned short* & option_field, const map & enum_map, su2double* & var1, su2double* & var2, su2double** & FlowDir, su2double* & relaxfactor1, su2double* & relaxfactor2); + template + void addWallSpeciesOption(const string name, unsigned short & nMarker_Wall_Species, string * & Marker_Wall_Species, + WALL_SPECIES_TYPE** & option_field, const map & enum_map, + su2double** & value, unsigned short & nSpecies_per_Wall); + void addExhaustOption(const string& name, unsigned short & nMarker_Exhaust, string * & Marker_Exhaust, su2double* & Ttotal, su2double* & Ptotal); @@ -7149,6 +7159,22 @@ class CConfig { */ const su2double* GetInlet_SpeciesVal(const string& val_index) const; + /*! + * \brief Get the species value at a wall boundary for a specific species + * \param[in] val_marker - Marker tag corresponding to the wall boundary. + * \param[in] iSpecies - Species index. + * \return The wall species value (flux or Dirichlet value). + */ + su2double GetWall_SpeciesVal(const string& val_marker, unsigned short iSpecies) const; + + /*! + * \brief Get the species boundary condition type at a wall boundary for a specific species + * \param[in] val_marker - Marker tag corresponding to the wall boundary. + * \param[in] iSpecies - Species index. + * \return The wall species type (FLUX or VALUE). + */ + WALL_SPECIES_TYPE GetWall_SpeciesType(const string& val_marker, unsigned short iSpecies) const; + /*! * \brief Get the turbulent properties values at an inlet boundary * \param[in] val_index - Index corresponding to the inlet boundary. diff --git a/Common/include/option_structure.hpp b/Common/include/option_structure.hpp index 8207e4ef6369..8375668778c4 100644 --- a/Common/include/option_structure.hpp +++ b/Common/include/option_structure.hpp @@ -1835,6 +1835,18 @@ static const MapType Giles_Map = { MakePair("MASS_FLOW_OUTLET", MASS_FLOW_OUTLET) }; +/*! + * \brief Types of wall species boundary conditions. + */ +enum class WALL_SPECIES_TYPE { + FLUX, /*!< \brief Neumann flux boundary condition for wall species. */ + VALUE /*!< \brief Dirichlet value boundary condition for wall species. */ +}; +static const MapType Wall_Map = { + MakePair("FLUX", WALL_SPECIES_TYPE::FLUX) + MakePair("VALUE", WALL_SPECIES_TYPE::VALUE) +}; + /*! * \brief Types of mixing process for averaging quantities at the boundaries. */ diff --git a/Common/include/option_structure.inl b/Common/include/option_structure.inl index 5f4a6e4fab51..9dc2c829509f 100644 --- a/Common/include/option_structure.inl +++ b/Common/include/option_structure.inl @@ -1302,6 +1302,170 @@ class COptionRiemann : public COptionBase { } }; +template +class COptionWallSpecies : public COptionBase { + protected: + map m; + string name; // identifier for the option + unsigned short& size; + string*& marker; + WALL_SPECIES_TYPE**& field; // Reference to the field name (now 2D: marker x species) + su2double**& value; // Now 2D: marker x species + unsigned short& nSpecies_per_Wall; + + public: + COptionWallSpecies(string option_field_name, unsigned short& nMarker_Wall_Species, string*& Marker_Wall_Species, + WALL_SPECIES_TYPE**& option_field, const map m, su2double**& value, + unsigned short& nSpecies_per_Wall) + : size(nMarker_Wall_Species), + marker(Marker_Wall_Species), + field(option_field), + value(value), + nSpecies_per_Wall(nSpecies_per_Wall) { + this->name = option_field_name; + this->m = m; + } + ~COptionWallSpecies() override { + if (marker) { + delete[] marker; + marker = nullptr; + } + if (field) { + for (unsigned short i = 0; i < size; i++) { + delete[] field[i]; + } + delete[] field; + field = nullptr; + } + if (value) { + for (unsigned short i = 0; i < size; i++) { + delete[] value[i]; + } + delete[] value; + value = nullptr; + } + } + + string SetValue(const vector& option_value) override { + COptionBase::SetValue(option_value); + unsigned short totalVals = option_value.size(); + if ((totalVals == 1) && (option_value[0].compare("NONE") == 0)) { + this->size = 0; + this->marker = nullptr; + this->field = nullptr; + this->value = nullptr; + this->nSpecies_per_Wall = 0; + return ""; + } + + /*--- 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 ---*/ + + 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])) { + // 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 + } + // This is a marker name + marker_indices.push_back(i); + } + } + + if (marker_indices.empty()) { + string newstring; + newstring.append(this->name); + newstring.append(": no valid markers found"); + return newstring; + } + + // Calculate number of species for each marker + for (size_t i = 0; i < marker_indices.size(); i++) { + unsigned short start_idx = marker_indices[i] + 1; // Start after marker name + unsigned short end_idx = (i + 1 < marker_indices.size()) ? marker_indices[i + 1] : totalVals; + unsigned short entries = end_idx - start_idx; + + // Each species needs 2 entries: TYPE and value + if (entries % 2 != 0) { + string newstring; + newstring.append(this->name); + newstring.append(": each marker must have pairs of (TYPE, value) entries"); + return newstring; + } + + species_counts.push_back(entries / 2); + } + + // Check that all markers have the same number of species + this->nSpecies_per_Wall = species_counts[0]; + for (auto count : species_counts) { + if (count != this->nSpecies_per_Wall) { + string newstring; + newstring.append(this->name); + newstring.append(": all markers must specify the same number of species"); + return newstring; + } + } + + // Allocate arrays + this->size = marker_indices.size(); + this->marker = new string[this->size]; + this->field = new WALL_SPECIES_TYPE*[this->size]; + this->value = new su2double*[this->size]; + + for (unsigned short i = 0; i < this->size; i++) { + this->field[i] = new WALL_SPECIES_TYPE[this->nSpecies_per_Wall]; + this->value[i] = new su2double[this->nSpecies_per_Wall]; + } + + // Parse the values + for (unsigned short iMarker = 0; iMarker < this->size; iMarker++) { + unsigned short marker_idx = marker_indices[iMarker]; + this->marker[iMarker].assign(option_value[marker_idx]); + + // Parse species data for this marker + for (unsigned short iSpecies = 0; iSpecies < this->nSpecies_per_Wall; iSpecies++) { + unsigned short type_idx = marker_idx + 1 + 2 * iSpecies; + unsigned short val_idx = type_idx + 1; + + // Check TYPE keyword + if (this->m.find(option_value[type_idx]) == m.end()) { + string str; + str.append(this->name); + str.append(": invalid option value "); + str.append(option_value[type_idx]); + str.append(". Check current SU2 options in config_template.cfg."); + return str; + } + + Tenum val = this->m[option_value[type_idx]]; + this->field[iMarker][iSpecies] = val; + + istringstream ss_value(option_value[val_idx]); + if (!(ss_value >> this->value[iMarker][iSpecies])) { + return badValue("WallSpecies", this->name); + } + } + } + + return ""; + } + + void SetDefault() override { + this->marker = nullptr; + this->field = nullptr; + this->value = nullptr; + this->size = 0; // There is no default value for list + this->nSpecies_per_Wall = 0; + } +}; + template class COptionGiles : public COptionBase { map m; diff --git a/Common/src/CConfig.cpp b/Common/src/CConfig.cpp index a1a43c0b3016..a608f288f7b3 100644 --- a/Common/src/CConfig.cpp +++ b/Common/src/CConfig.cpp @@ -513,6 +513,16 @@ void CConfig::addRiemannOption(const string name, unsigned short & nMarker_Riema option_map.insert(pair(name, val)); } +template +void CConfig::addWallSpeciesOption(const string name, unsigned short & nMarker_Wall_Species, string * & Marker_Wall_Species, + WALL_SPECIES_TYPE** & option_field, const map & enum_map, + su2double** & value, unsigned short & nSpecies_per_Wall) { + assert(option_map.find(name) == option_map.end()); + all_options.insert(pair(name, true)); + COptionBase* val = new COptionWallSpecies(name, nMarker_Wall_Species, Marker_Wall_Species, option_field, enum_map, value, nSpecies_per_Wall); + option_map.insert(pair(name, val)); +} + template void CConfig::addGilesOption(const string name, unsigned short & nMarker_Giles, string * & Marker_Giles, unsigned short* & option_field, const map & enum_map, su2double* & var1, su2double* & var2, su2double** & FlowDir, su2double* & relaxfactor1, su2double* & relaxfactor2) { @@ -1621,6 +1631,11 @@ void CConfig::SetConfig_Options() { /*!\brief MARKER_RIEMANN \n DESCRIPTION: Riemann boundary marker(s) with the following formats, a unit vector. * \n OPTIONS: See \link Riemann_Map \endlink. The variables indicated by the option and the flow direction unit vector must be specified. \ingroup Config*/ addRiemannOption("MARKER_RIEMANN", nMarker_Riemann, Marker_Riemann, Kind_Data_Riemann, Riemann_Map, Riemann_Var1, Riemann_Var2, Riemann_FlowDir); + /*!\brief MARKER_WALL_SPECIES \n DESCRIPTION: Wall species boundary marker(s) with the following format: + * (marker_name, BC_TYPE, value, BC_TYPE, value, ...) where BC_TYPE is either FLUX (Neumann) or VALUE (Dirichlet). + * Each marker must specify the same number of species (N species per marker). + * \n OPTIONS: See \link Wall_Map \endlink. \ingroup Config*/ + addWallSpeciesOption("MARKER_WALL_SPECIES", nMarker_Wall_Species, Marker_Wall_Species, Kind_Wall_Species, Wall_Map, Wall_SpeciesVal, nSpecies_per_Wall); /*!\brief MARKER_GILES \n DESCRIPTION: Giles boundary marker(s) with the following formats, a unit vector. */ /* \n OPTIONS: See \link Giles_Map \endlink. The variables indicated by the option and the flow direction unit vector must be specified. \ingroup Config*/ addGilesOption("MARKER_GILES", nMarker_Giles, Marker_Giles, Kind_Data_Giles, Giles_Map, Giles_Var1, Giles_Var2, Giles_FlowDir, RelaxFactorAverage, RelaxFactorFourier); @@ -5702,7 +5717,7 @@ void CConfig::SetPostprocessing(SU2_COMPONENT val_software, unsigned short val_i /*--- Helper function that checks scalar variable bounds. ---*/ auto checkScalarBounds = [&](su2double scalar, const string& name, su2double lowerBound, su2double upperBound) { if (scalar < lowerBound || scalar > upperBound) - SU2_MPI::Error(string("Variable: ") + name + string(", is out of bounds."), CURRENT_FUNCTION); + cout << "Value: " << scalar << " for " << name << " is out of bounds [" << lowerBound << "," << upperBound << "]." << endl; }; /*--- Some options have to provide as many entries as there are additional species equations. ---*/ @@ -5713,6 +5728,8 @@ void CConfig::SetPostprocessing(SU2_COMPONENT val_software, unsigned short val_i nSpecies_options.insert(nSpecies_options.end(), {nSpecies_Clipping_Min, nSpecies_Clipping_Max}); if (nMarker_Inlet_Species > 0) nSpecies_options.push_back(nSpecies_per_Inlet); + if (nMarker_Wall_Species > 0) + nSpecies_options.push_back(nSpecies_per_Wall); // Add more options for size check here. /*--- nSpecies_Init is the master, but it simply checks for consistency. ---*/ @@ -9250,6 +9267,28 @@ const su2double* CConfig::GetInlet_SpeciesVal(const string& val_marker) const { return Inlet_SpeciesVal[iMarker_Inlet_Species]; } +su2double CConfig::GetWall_SpeciesVal(const string& val_marker, unsigned short iSpecies) const { + /*--- Search for the marker in the wall species list ---*/ + for (unsigned short iMarker_Wall_Species = 0; iMarker_Wall_Species < nMarker_Wall_Species; iMarker_Wall_Species++) { + if (Marker_Wall_Species[iMarker_Wall_Species] == val_marker) { + return Wall_SpeciesVal[iMarker_Wall_Species][iSpecies]; + } + } + /*--- If marker not found (MARKER_WALL_SPECIES=NONE), return zero flux ---*/ + return 0.0; +} + +WALL_SPECIES_TYPE CConfig::GetWall_SpeciesType(const string& val_marker, unsigned short iSpecies) const { + /*--- Search for the marker in the wall species list ---*/ + for (unsigned short iMarker_Wall_Species = 0; iMarker_Wall_Species < nMarker_Wall_Species; iMarker_Wall_Species++) { + if (Marker_Wall_Species[iMarker_Wall_Species] == val_marker) { + return Kind_Wall_Species[iMarker_Wall_Species][iSpecies]; + } + } + /*--- If marker not found (MARKER_WALL_SPECIES=NONE), return FLUX type (zero flux BC) ---*/ + return WALL_SPECIES_TYPE::FLUX; +} + const su2double* CConfig::GetInlet_TurbVal(const string& val_marker) const { /*--- If Turbulent Inlet is not provided for the marker, return free stream values. ---*/ for (auto iMarker = 0u; iMarker < nMarker_Inlet_Turb; iMarker++) { diff --git a/SU2_CFD/include/drivers/CDriverBase.hpp b/SU2_CFD/include/drivers/CDriverBase.hpp index 97772527cf3f..f05ab722372c 100644 --- a/SU2_CFD/include/drivers/CDriverBase.hpp +++ b/SU2_CFD/include/drivers/CDriverBase.hpp @@ -566,6 +566,18 @@ class CDriverBase { main_geometry->SetCustomBoundaryHeatFlux(iMarker, iVertex, WallHeatFlux); } + /*! + * \brief Set the wall normal scalar values at a vertex on a specified marker (MARKER_PYTHON_CUSTOM). + * \note This can be the input of a scalar transport equation. + * \param[in] iMarker - Marker identifier. + * \param[in] iVertex - Vertex identifier. + * \param[in] WallScalar - Value of the normal heat flux. + */ + inline void SetMarkerCustomScalar(unsigned short iMarker, unsigned long iVertex, vector WallScalar) { + auto* solver = solver_container[selected_zone][INST_0][MESH_0][SPECIES_SOL]; + solver->SetCustomBoundaryScalar(iMarker, iVertex, WallScalar); + } + /*! * \brief Selects zone to be used for python driver operations. * \param[in] iZone - Zone identifier. diff --git a/SU2_CFD/include/solvers/CSolver.hpp b/SU2_CFD/include/solvers/CSolver.hpp index f0b3a4c493d1..fcbdb01bba20 100644 --- a/SU2_CFD/include/solvers/CSolver.hpp +++ b/SU2_CFD/include/solvers/CSolver.hpp @@ -2856,6 +2856,16 @@ class CSolver { unsigned long val_vertex, unsigned short val_dim) const { return 0; } + + /*! + * \brief Set the value of the customized normal scalar values/flux at a specified vertex on a specified marker. + * \param[in] val_marker - Marker value + * \param[in] val_vertex - Boundary vertex value + */ + inline virtual void SetCustomBoundaryScalar(unsigned short val_marker, unsigned long val_vertex, + vector val_customBoundaryScalar) { } + + /*! * \brief A virtual member * \param[in] val_marker - Surface marker where the total temperature is set. diff --git a/SU2_CFD/include/solvers/CSpeciesSolver.hpp b/SU2_CFD/include/solvers/CSpeciesSolver.hpp index 052c3c21ee9c..011f1daddb5b 100644 --- a/SU2_CFD/include/solvers/CSpeciesSolver.hpp +++ b/SU2_CFD/include/solvers/CSpeciesSolver.hpp @@ -40,6 +40,8 @@ class CSpeciesSolver : public CScalarSolver { protected: unsigned short Inlet_Position; /*!< \brief Column index for scalar variables in inlet files. */ vector Inlet_SpeciesVars; /*!< \brief Species variables at inlet profiles. */ + vector Wall_SpeciesVars; /*!< \brief Species variables at profiles. */ + vector> CustomBoundaryScalar; public: /*! @@ -146,6 +148,42 @@ class CSpeciesSolver : public CScalarSolver { void BC_Outlet(CGeometry* geometry, CSolver** solver_container, CNumerics* conv_numerics, CNumerics* visc_numerics, CConfig* config, unsigned short val_marker) final; + /*! + * \brief Impose the isothermal wall Dirichlet boundary condition (value). + * \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_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 Neumann wall boundary condition (flux). + * \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 Generic wall boundary condition implementation. + * \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] val_marker - Surface marker where the boundary condition is applied. + */ + void BC_Wall_Generic(CGeometry* geometry, CSolver** solver_container, + CConfig* config, unsigned short val_marker); + /*--- Note that BC_Sym_Plane, BC_HeatFlux_Wall, BC_Isothermal_Wall are all treated as zero-flux BC for the * mass-factions, which can be fulfilled by no additional residual contribution on these nodes. * If a specified mass-fractions flux (like BC_HeatFlux_Wall) or a constant mass-fraction on the boundary @@ -195,4 +233,17 @@ class CSpeciesSolver : public CScalarSolver { geometry, solver_container, conv_numerics, visc_numerics, config); } + /*! + * \brief Set custom boundary scalar values from Python. + * \param[in] val_marker - Boundary marker index + * \param[in] val_vertex - Boundary vertex index + * \param[in] val_customBoundaryScalar - Vector of scalar values + */ + inline void SetCustomBoundaryScalar(unsigned short val_marker, unsigned long val_vertex, + vector val_customBoundaryScalar) final { + for (auto iVar = 0u; iVar < nVar; iVar++) { + CustomBoundaryScalar[val_marker](val_vertex, iVar) = val_customBoundaryScalar[iVar]; + } + } + }; diff --git a/SU2_CFD/src/solvers/CSpeciesSolver.cpp b/SU2_CFD/src/solvers/CSpeciesSolver.cpp index 3f25af7d1183..309550740749 100644 --- a/SU2_CFD/src/solvers/CSpeciesSolver.cpp +++ b/SU2_CFD/src/solvers/CSpeciesSolver.cpp @@ -27,6 +27,7 @@ #include "../../include/solvers/CSpeciesSolver.hpp" +#include "../../../Common/include/option_structure.hpp" #include "../../../Common/include/parallelization/omp_structure.hpp" #include "../../../Common/include/toolboxes/geometry_toolbox.hpp" #include "../../include/solvers/CScalarSolver.inl" @@ -110,6 +111,8 @@ void CSpeciesSolver::Initialize(CGeometry* geometry, CConfig* config, unsigned s nDim = geometry->GetnDim(); + AllocVectorOfMatrices( nVertex, nVar,CustomBoundaryScalar); + if (iMesh == MESH_0 || config->GetMGCycle() == FULLMG_CYCLE) { /*--- Define some auxiliary vector related with the residual ---*/ @@ -174,6 +177,16 @@ void CSpeciesSolver::Initialize(CGeometry* geometry, CConfig* config, unsigned s } } } + + Wall_SpeciesVars.resize(nMarker); + for (unsigned long iMarker = 0; iMarker < nMarker; iMarker++) { + Wall_SpeciesVars[iMarker].resize(nVertex[iMarker], nVar); + for (unsigned long iVertex = 0; iVertex < nVertex[iMarker]; ++iVertex) { + for (unsigned short iVar = 0; iVar < nVar; iVar++) { + Wall_SpeciesVars[iMarker](iVertex, iVar) = Solution_Inf[iVar]; + } + } + } } @@ -404,6 +417,74 @@ void CSpeciesSolver::BC_Inlet(CGeometry* geometry, CSolver** solver_container, C END_SU2_OMP_FOR } + + +void CSpeciesSolver::BC_Isothermal_Wall(CGeometry* geometry, CSolver** solver_container, + CNumerics* conv_numerics, CNumerics* visc_numerics, + CConfig* config, unsigned short val_marker) { + BC_Wall_Generic(geometry, solver_container, config, val_marker); +} + +void CSpeciesSolver::BC_HeatFlux_Wall(CGeometry* geometry, CSolver** solver_container, + CNumerics* conv_numerics, CNumerics* visc_numerics, + CConfig* config, unsigned short val_marker) { + BC_Wall_Generic(geometry, solver_container, config, val_marker); +} + +void CSpeciesSolver::BC_Wall_Generic(CGeometry* geometry, CSolver** solver_container, + CConfig* config, unsigned short val_marker) { + const bool implicit = config->GetKind_TimeIntScheme_Flow() == EULER_IMPLICIT; + const bool py_custom = config->GetMarker_All_PyCustom(val_marker); + + string Marker_Tag = config->GetMarker_All_TagBound(val_marker); + + for (auto iVar = 0u; iVar < nVar; iVar++) { + + // Get wall species boundary condition type and value for this marker and species + const su2double WallSpeciesValue = config->GetWall_SpeciesVal(Marker_Tag, iVar); + const WALL_SPECIES_TYPE wallspeciestype = config->GetWall_SpeciesType(Marker_Tag, iVar); + + SU2_OMP_FOR_DYN(OMP_MIN_SIZE) + for (auto iVertex = 0u; 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(); + + su2double Area = GeometryToolbox::Norm(nDim, Normal); + + su2double WallSpecies = WallSpeciesValue; + + /*--- Get the scalar values from the python wrapper. ---*/ + if (py_custom) { + WallSpecies = CustomBoundaryScalar[val_marker](iVertex,iVar); + } + + switch(wallspeciestype) { + case WALL_SPECIES_TYPE::FLUX: + //Flux Boundary condition + LinSysRes(iPoint, iVar) -= WallSpecies * Area; + break; + case WALL_SPECIES_TYPE::VALUE: + //Dirichlet Strong Boundary Condition + nodes->SetSolution(iPoint, iVar, WallSpecies); + nodes->SetSolution_Old(iPoint, iVar, WallSpecies); + LinSysRes(iPoint, iVar) = 0.0; + if (implicit) { + unsigned long total_index = iPoint * nVar + iVar; + Jacobian.DeleteValsRowi(total_index); + } + break; + } + } + END_SU2_OMP_FOR + } +} + + + void CSpeciesSolver::SetInletAtVertex(const su2double *val_inlet, unsigned short iMarker, unsigned long iVertex) { @@ -579,4 +660,4 @@ void CSpeciesSolver::SetInitialCondition(CGeometry **geometry, CSolver ***solver const bool restart = config->GetRestart() || config->GetRestart_Flow(); PushSolutionBackInTime(TimeIter, restart, solver_container, geometry, config); -} \ No newline at end of file +} diff --git a/TestCases/parallel_regression.py b/TestCases/parallel_regression.py index 3ad0f2a522b9..8c9279df366a 100755 --- a/TestCases/parallel_regression.py +++ b/TestCases/parallel_regression.py @@ -1662,6 +1662,14 @@ def main(): species3_primitiveVenturi_inletFile.test_vals = [-5.537438, -4.503863, -4.553632, -5.400874, -0.945967, -5.818774, -5.945211, 5.000000, -0.544749, 5.000000, -2.599435, 5.000000, -0.596360] test_list.append(species3_primitiveVenturi_inletFile) + # 3 species (2 eq) primitive venturi mixing with new flux and value boundary conditions + species3_primitiveVenturi_fluxvalue = TestCase('species3_primitiveVenturi_fluxvalue') + species3_primitiveVenturi_fluxvalue.cfg_dir = "species_transport/venturi_primitive_3species" + species3_primitiveVenturi_fluxvalue.cfg_file = "species3_primitiveVenturi_flux_value.cfg" + species3_primitiveVenturi_fluxvalue.test_iter = 50 + species3_primitiveVenturi_fluxvalue.test_vals = [-4.563229, -5.504499, -0.861681, -5.822963, -6.458352, 1.257908, 0.122218, 0.317705, 0.817985, 0.241494, 0.102507, 0.004981, 0.134006] + test_list.append(species3_primitiveVenturi_fluxvalue) + # rectangle passive transport validation species_passive_val = TestCase('species_passive_val') species_passive_val.cfg_dir = "species_transport/passive_transport_validation" diff --git a/TestCases/species_transport/venturi_primitive_3species/species3_primitiveVenturi_flux_value.cfg b/TestCases/species_transport/venturi_primitive_3species/species3_primitiveVenturi_flux_value.cfg new file mode 100644 index 000000000000..68880a6cbfca --- /dev/null +++ b/TestCases/species_transport/venturi_primitive_3species/species3_primitiveVenturi_flux_value.cfg @@ -0,0 +1,138 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% SU2 configuration file % +% Case description: Species mixing with 3 species, i.e. 2 transport equations % +% using FLUX and VALUE boundary conditions for the wall % +% Author: N. Beishuizen % +% Institution: TU Eindhoven % +% Date: 17-12-2025 % +% File Version 8.3.0 "Harrier" % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% ------------- 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= INITIAL_VALUES +% +% -------------------- 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 ) +% +SPECIFIED_INLET_PROFILE= NO +INLET_FILENAME= inlet_venturi.dat +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, 0.0, 0.1,\ + air_axial_inlet, 0.1, 0.0 ) +% VALUE (Dirichlet) for species 1, FLUX (Neumann) for species 2 +MARKER_WALL_SPECIES=( wall, VALUE, 1.0, FLUX, 0.01 ) +% +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 +% +% Run commented Iter for good results +%ITER= 1000 +ITER= 100 +% +% ------------------------ LINEAR SOLVER DEFINITION ---------------------------% +% +LINEAR_SOLVER= FGMRES +LINEAR_SOLVER_PREC= ILU +LINEAR_SOLVER_ERROR= 1E-3 +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= BOUNDED_SCALAR +MUSCL_SPECIES= NO +SLOPE_LIMITER_SPECIES = NONE +% +TIME_DISCRE_SPECIES= EULER_IMPLICIT +% +SPECIES_INIT= 1.0, 0.0 +SPECIES_CLIPPING= YES +SPECIES_CLIPPING_MIN= 0.0, 0.0 +SPECIES_CLIPPING_MAX= 1.0, 1.0 +% +% -------------------- TURBULENT TRANSPORT ---------------------------------------% +% +CONV_NUM_METHOD_TURB= BOUNDED_SCALAR +MUSCL_TURB= NO +% +% --------------------------- CONVERGENCE PARAMETERS --------------------------% +% +CONV_FIELD= RMS_PRESSURE, RMS_VELOCITY-X, RMS_VELOCITY-Y, RMS_TKE, RMS_SPECIES +CONV_RESIDUAL_MINVAL= -16 +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 RMS_SPECIES_1 \ + SURFACE_SPECIES_0 SURFACE_SPECIES_1 +SCREEN_WRT_FREQ_INNER= 10 +% +HISTORY_OUTPUT= ITER RMS_RES LINSOL SPECIES_COEFF SPECIES_COEFF_SURF +CONV_FILENAME= history_inlet +MARKER_ANALYZE= gas_inlet, air_axial_inlet, outlet +MARKER_ANALYZE_AVERAGE= AREA +% +%OUTPUT_FILES= RESTART_ASCII, PARAVIEW_MULTIBLOCK +OUTPUT_FILES= NONE +VOLUME_OUTPUT= RESIDUAL, PRIMITIVE +OUTPUT_WRT_FREQ= 1000 +% +RESTART_SOL= NO +SOLUTION_FILENAME= solution +RESTART_FILENAME= restart +% +WRT_PERFORMANCE= NO diff --git a/config_template.cfg b/config_template.cfg index 6f252f16a771..04f8aa6d9059 100644 --- a/config_template.cfg +++ b/config_template.cfg @@ -1051,6 +1051,15 @@ MARKER_HEATTRANSFER= ( NONE ) % Format: ( marker name, constant wall temperature (K), ... ) MARKER_ISOTHERMAL= ( NONE ) % +% Wall species boundary condition marker(s) for species transport (NONE = no marker) +% Specify either a Neumann flux boundary condition (FLUX) or a Dirichlet value boundary condition (VALUE) +% for each species independently. All markers must specify the same number of species. +% Format: ( marker_name, BC_TYPE_1, value_1, BC_TYPE_2, value_2, ..., BC_TYPE_N, value_N, ... ) +% where BC_TYPE is either FLUX (Neumann) or VALUE (Dirichlet) for each species +% Example for 3 species: MARKER_WALL_SPECIES= (wall1, FLUX, 0.0, VALUE, 0.3, VALUE, 0.2, wall2, VALUE, 0.1, VALUE, 0.4, FLUX, 0.0) +% If MARKER_WALL_SPECIES is not specified, a zero-flux boundary condition will be used. +MARKER_WALL_SPECIES= ( NONE ) +% % Far-field boundary marker(s) (NONE = no marker) MARKER_FAR= ( farfield ) %