Skip to content

Commit 44921de

Browse files
authored
Merge pull request #1705 from su2code/feature_custom_functions
User defined functions for history outputs
2 parents b01a639 + 9b99cc6 commit 44921de

File tree

15 files changed

+479
-83
lines changed

15 files changed

+479
-83
lines changed

Common/include/CConfig.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ class CConfig {
445445

446446
bool ReorientElements; /*!< \brief Flag for enabling element reorientation. */
447447
string CustomObjFunc; /*!< \brief User-defined objective function. */
448+
string CustomOutputs; /*!< \brief User-defined functions for outputs. */
448449
unsigned short nDV, /*!< \brief Number of design variables. */
449450
nObj, nObjW; /*! \brief Number of objective functions. */
450451
unsigned short* nDV_Value; /*!< \brief Number of values for each design variable (might be different than 1 if we allow arbitrary movement). */
@@ -5224,6 +5225,11 @@ class CConfig {
52245225
*/
52255226
const string& GetCustomObjFunc() const { return CustomObjFunc; }
52265227

5228+
/*!
5229+
* \brief Get the user expressions for custom outputs.
5230+
*/
5231+
const string& GetCustomOutputs() const { return CustomOutputs; }
5232+
52275233
/*!
52285234
* \brief Get the kind of sensitivity smoothing technique.
52295235
* \return Kind of sensitivity smoothing technique.

Common/src/CConfig.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1932,6 +1932,8 @@ void CConfig::SetConfig_Options() {
19321932

19331933
/*!\brief CUSTOM_OBJFUNC \n DESCRIPTION: User-provided definition of a custom objective function. \ingroup Config*/
19341934
addStringOption("CUSTOM_OBJFUNC", CustomObjFunc, "");
1935+
/*!\brief CUSTOM_OUTPUTS \n DESCRIPTION: User-provided definitions for custom output. \ingroup Config*/
1936+
addStringOption("CUSTOM_OUTPUTS", CustomOutputs, "");
19351937

19361938
/* DESCRIPTION: parameter for the definition of a complex objective function */
19371939
addDoubleOption("DCD_DCL_VALUE", dCD_dCL, 0.0);

SU2_CFD/include/output/CFlowOutput.hpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,84 @@ class CFlowOutput : public CFVMOutput{
199199
*/
200200
void Set_NearfieldInverseDesign(CSolver *solver, const CGeometry *geometry, const CConfig *config);
201201

202+
/*!
203+
* \brief Compute the custom outputs.
204+
* \param[in] solver - The container holding all solution data.
205+
* \param[in] geometry - Geometrical definition of the problem.
206+
* \param[in] config - Definition of the particular problem.
207+
*/
208+
void SetCustomOutputs(const CSolver* const* solver, const CGeometry *geometry, const CConfig *config);
209+
210+
/*!
211+
* \brief Helper for custom outputs, converts variable names to indices and pointers which are then used
212+
* to evaluate the custom expressions.
213+
*/
214+
template <class FlowIndices>
215+
void ConvertVariableSymbolsToIndices(const FlowIndices& idx, CustomOutput& output) const {
216+
217+
static const auto knownVariables =
218+
"TEMPERATURE, TEMPERATURE_VE, VELOCITY_X, VELOCITY_Y, VELOCITY_Z, PRESSURE,\n"
219+
"DENSITY, ENTHALPY, SOUND_SPEED, LAMINAR_VISCOSITY, EDDY_VISCOSITY, THERMAL_CONDUCTIVITY\n"
220+
"TURB[0,1,...], RAD[0,1,...], SPECIES[0,1,...]";
221+
222+
auto IndexOfVariable = [&](const FlowIndices& idx, const std::string& var) {
223+
/*--- Primitives of the flow solver. ---*/
224+
const auto flow_offset = FLOW_SOL * CustomOutput::MAX_VARS_PER_SOLVER;
225+
226+
if ("TEMPERATURE" == var) return flow_offset + idx.Temperature();
227+
if ("TEMPERATURE_VE" == var) return flow_offset + idx.Temperature_ve();
228+
if ("VELOCITY_X" == var) return flow_offset + idx.Velocity();
229+
if ("VELOCITY_Y" == var) return flow_offset + idx.Velocity() + 1;
230+
if ("VELOCITY_Z" == var) return flow_offset + idx.Velocity() + 2;
231+
if ("PRESSURE" == var) return flow_offset + idx.Pressure();
232+
if ("DENSITY" == var) return flow_offset + idx.Density();
233+
if ("ENTHALPY" == var) return flow_offset + idx.Enthalpy();
234+
if ("SOUND_SPEED" == var) return flow_offset + idx.SoundSpeed();
235+
if ("LAMINAR_VISCOSITY" == var) return flow_offset + idx.LaminarViscosity();
236+
if ("EDDY_VISCOSITY" == var) return flow_offset + idx.EddyViscosity();
237+
if ("THERMAL_CONDUCTIVITY" == var) return flow_offset + idx.ThermalConductivity();
238+
239+
/*--- Index-based (no name) access to variables of other solvers. ---*/
240+
auto GetIndex = [](const std::string& s, int nameLen) {
241+
/*--- Extract an int from "name[int]", nameLen is the length of "name". ---*/
242+
return std::stoi(std::string(s.begin() + nameLen + 1, s.end() - 1));
243+
};
244+
if (var.rfind("SPECIES", 0) == 0) return SPECIES_SOL * CustomOutput::MAX_VARS_PER_SOLVER + GetIndex(var, 7);
245+
if (var.rfind("TURB", 0) == 0) return TURB_SOL * CustomOutput::MAX_VARS_PER_SOLVER + GetIndex(var, 4);
246+
if (var.rfind("RAD", 0) == 0) return RAD_SOL * CustomOutput::MAX_VARS_PER_SOLVER + GetIndex(var, 3);
247+
248+
return CustomOutput::NOT_A_VARIABLE;
249+
};
250+
251+
output.otherOutputs.clear();
252+
output.varIndices.clear();
253+
output.varIndices.reserve(output.varSymbols.size());
254+
255+
for (const auto& var : output.varSymbols) {
256+
output.varIndices.push_back(IndexOfVariable(idx, var));
257+
258+
if (output.type == OperationType::FUNCTION && output.varIndices.back() != CustomOutput::NOT_A_VARIABLE) {
259+
SU2_MPI::Error("Custom outputs of type 'Function' cannot reference solver variables.", CURRENT_FUNCTION);
260+
}
261+
/*--- Symbol is a valid solver variable. ---*/
262+
if (output.varIndices.back() < CustomOutput::NOT_A_VARIABLE) continue;
263+
264+
/*--- An index above NOT_A_VARIABLE is not valid with current solver settings. ---*/
265+
if (output.varIndices.back() > CustomOutput::NOT_A_VARIABLE) {
266+
SU2_MPI::Error("Inactive solver variable (" + var + ") used in function " + output.name + "\n"
267+
"E.g. this may only be a variable of the compressible solver.", CURRENT_FUNCTION);
268+
}
269+
270+
/*--- An index equal to NOT_A_VARIABLE may refer to a history output. ---*/
271+
output.varIndices.back() += output.otherOutputs.size();
272+
output.otherOutputs.push_back(GetPtrToHistoryOutput(var));
273+
if (output.otherOutputs.back() == nullptr) {
274+
SU2_MPI::Error("Invalid history output or solver variable (" + var + ") used in function " + output.name +
275+
"\nValid solvers variables: " + knownVariables, CURRENT_FUNCTION);
276+
}
277+
}
278+
}
279+
202280
/*!
203281
* \brief Compute value of the Q criteration for vortex idenfitication
204282
* \param[in] VelocityGradient - Velocity gradients
@@ -238,6 +316,25 @@ class CFlowOutput : public CFVMOutput{
238316
return Q;
239317
}
240318

319+
/*!
320+
* \brief Returns the axisymmetric factor for a point on a marker.
321+
*/
322+
template <class GeoNodes>
323+
inline su2double GetAxiFactor(bool axisymmetric, const GeoNodes& nodes, unsigned long iPoint,
324+
unsigned short iMarker) {
325+
if (!axisymmetric) return 1.0;
326+
327+
if (nodes.GetCoord(iPoint, 1) > EPS) return 2 * PI_NUMBER * nodes.GetCoord(iPoint, 1);
328+
329+
for (const auto jPoint : nodes.GetPoints(iPoint)) {
330+
if (nodes.GetVertex(jPoint, iMarker) >= 0) {
331+
/*--- Not multiplied by two since we need to half the y coordinate. ---*/
332+
return PI_NUMBER * nodes.GetCoord(jPoint, 1);
333+
}
334+
}
335+
return 0.0;
336+
}
337+
241338
/*!
242339
* \brief Write information to meta data file
243340
* \param[in] config - Definition of the particular problem per zone.

SU2_CFD/include/output/COutput.hpp

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ class COutput {
172172
//! Structure to store the value initial residuals for relative residual computation
173173
std::map<string, su2double> initialResiduals;
174174

175-
/** \brief Struct to hold a parsed user-defined expression. */
175+
/*! \brief Struct to hold a parsed user-defined expression. */
176176
struct CustomHistoryOutput {
177177
mel::ExpressionTree<passivedouble> expression;
178178
/*--- Pointers to values in the history output maps, to avoid key lookup every time. ---*/
@@ -186,17 +186,62 @@ class COutput {
186186

187187
CustomHistoryOutput customObjFunc; /*!< \brief User-defined expression for a custom objective. */
188188

189-
/*----------------------------- Volume output ----------------------------*/
189+
/*! \brief Type of operation for custom outputs. */
190+
enum class OperationType { MACRO, FUNCTION, AREA_AVG, AREA_INT, MASSFLOW_AVG, MASSFLOW_INT };
190191

191-
CParallelDataSorter* volumeDataSorter; //!< Volume data sorter
192-
CParallelDataSorter* surfaceDataSorter; //!< Surface data sorter
192+
/*! \brief Struct to hold a parsed custom output function. */
193+
struct CustomOutput {
194+
/*--- First level of parsing the syntax "name : type{func}[markers];". ---*/
195+
std::string name;
196+
OperationType type;
197+
std::string func;
198+
std::vector<std::string> markers;
193199

194-
vector<string> volumeFieldNames; //!< Vector containing the volume field names
195-
unsigned short nVolumeFields; /*!< \brief Number of fields in the volume output */
200+
/*--- Second level, func into expression, and acceleration structures. ---*/
201+
mel::ExpressionTree<passivedouble> expression;
202+
std::vector<std::string> varSymbols;
203+
std::vector<unsigned short> markerIndices;
204+
205+
/*--- The symbols (strings) are associated with an integer index for efficiency. For evaluation this index
206+
is passed to a functor that returns the value associated with the symbol. This functor is an input to "eval()"
207+
and needs to be generated on-the-fly for each point. The functor approach is more generic than a pointer, for
208+
example it allows wrapping the access to multiple solvers. The interpretation of these indices is dictated by
209+
the functor used in eval, for example indices may be established as 32 * solver_idx + variable_idx.
210+
The parts of the code that assign and interpret indices need to be in sync. ---*/
211+
std::vector<unsigned long> varIndices;
212+
213+
/*--- Offset between varIndices of different solvers (see above). Power of 2 to make decoding faster. ---*/
214+
static constexpr unsigned long MAX_VARS_PER_SOLVER = 32;
215+
216+
/*--- Arbitrary number to indicate that a string did not match a variable. ---*/
217+
static constexpr unsigned long NOT_A_VARIABLE = MAX_SOLS * MAX_VARS_PER_SOLVER;
218+
219+
/*--- Other outputs can be referenced in expressions, e.g. to compute variance.
220+
We store pointers to the required outputs to speed-up access. ---*/
221+
std::vector<const su2double*> otherOutputs;
222+
223+
/*--- For evaluation, "vars" is a functor (i.e. has operator()) that returns the value of a variable at a given
224+
point. For example, it can be a wrapper to the primitives pointer, in which case varIndices needs to be setup
225+
with primitive indices. ---*/
226+
template <class Variables>
227+
su2double eval(const Variables& vars) const {
228+
return mel::Eval<su2double>(expression, [&](int iSymbol) {return vars(varIndices[iSymbol]);});
229+
}
230+
};
231+
232+
std::vector<CustomOutput> customOutputs; /*!< \brief User-defined outputs. */
233+
234+
/*----------------------------- Volume output ----------------------------*/
196235

197-
string volumeFilename, //!< Volume output filename
198-
surfaceFilename, //!< Surface output filename
199-
restartFilename; //!< Restart output filename
236+
CParallelDataSorter* volumeDataSorter; //!< Volume data sorter
237+
CParallelDataSorter* surfaceDataSorter; //!< Surface data sorter
238+
239+
vector<string> volumeFieldNames; //!< Vector containing the volume field names
240+
unsigned short nVolumeFields; //!< Number of fields in the volume output
241+
242+
string volumeFilename, //!< Volume output filename
243+
surfaceFilename, //!< Surface output filename
244+
restartFilename; //!< Restart output filename
200245

201246
/** \brief Structure to store information for a volume output field.
202247
*
@@ -628,6 +673,29 @@ class COutput {
628673
}
629674
}
630675

676+
/*!
677+
* \brief Returns a pointer to the value of an history output.
678+
* \note For per-surface outputs the marker index is specified as "name[index]".
679+
*/
680+
inline const su2double* GetPtrToHistoryOutput(const string& name) const {
681+
/*--- Decide if it should be per surface. ---*/
682+
const auto pos = name.find('[');
683+
const su2double* ptr = nullptr;
684+
if (pos == std::string::npos) {
685+
const auto it = historyOutput_Map.find(name);
686+
if (it != historyOutput_Map.end()) {
687+
ptr = &(it->second.value);
688+
}
689+
} else {
690+
const auto idx = std::stoi(std::string(name.begin()+pos+1, name.end()-1));
691+
const auto it = historyOutputPerSurface_Map.find(std::string(name, 0, pos));
692+
if (it != historyOutputPerSurface_Map.end()) {
693+
ptr = &(it->second[idx].value);
694+
}
695+
}
696+
return ptr;
697+
}
698+
631699
/*!
632700
* \brief Setup a custom history output object for a given expression.
633701
* \param[in] expression - Some user-defined math with the history field names as variables.
@@ -726,6 +794,11 @@ class COutput {
726794
*/
727795
void SetCommonHistoryFields();
728796

797+
/*!
798+
* \brief Parses user-defined outputs.
799+
*/
800+
void SetCustomOutputs(const CConfig *config);
801+
729802
/*!
730803
* \brief Load values of the history fields common for all solvers.
731804
* \param[in] config - Definition of the particular problem.

SU2_CFD/include/variables/CIncEulerVariable.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class CIncEulerVariable : public CFlowVariable {
5151
inline IndexType Temperature() const { return nDim+1; }
5252
inline IndexType Density() const { return nDim+2; }
5353
inline IndexType Beta() const { return nDim+3; }
54+
inline IndexType SoundSpeed() const { return Beta(); }
5455
inline IndexType LaminarViscosity() const { return nDim+4; }
5556
inline IndexType EddyViscosity() const { return nDim+5; }
5657
inline IndexType ThermalConductivity() const { return nDim+6; }
@@ -59,6 +60,7 @@ class CIncEulerVariable : public CFlowVariable {
5960

6061
/*--- For compatible interface with NEMO. ---*/
6162
inline IndexType Temperature_ve() const { return std::numeric_limits<IndexType>::max(); }
63+
inline IndexType Enthalpy() const { return std::numeric_limits<IndexType>::max(); }
6264
};
6365

6466
protected:

SU2_CFD/include/variables/CNEMOEulerVariable.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ class CNEMOEulerVariable : public CFlowVariable {
5858
inline IndexType RhoCvve() const {return nSpecies+nDim+7;}
5959
inline IndexType LaminarViscosity() const {return nSpecies+nDim+8;}
6060
inline IndexType EddyViscosity() const {return nSpecies+nDim+9;}
61+
62+
inline IndexType ThermalConductivity() const {return std::numeric_limits<IndexType>::max();}
6163
};
6264

6365
protected:

SU2_CFD/src/output/CFlowCompOutput.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,7 @@ CFlowCompOutput::CFlowCompOutput(const CConfig *config, unsigned short nDim) : C
8787
if (convFields.empty() ) convFields.emplace_back("RMS_DENSITY");
8888

8989
if (config->GetFixed_CL_Mode()) {
90-
bool found = false;
91-
for (unsigned short iField = 0; iField < convFields.size(); iField++)
92-
if (convFields[iField] == "LIFT") found = true;
93-
if (!found) {
90+
if (std::find(convFields.begin(), convFields.end(), "LIFT") != convFields.end()) {
9491
if (rank == MASTER_NODE)
9592
cout<<" Fixed CL: Adding LIFT as Convergence Field to ensure convergence to target CL"<<endl;
9693
convFields.emplace_back("LIFT");
@@ -446,6 +443,8 @@ void CFlowCompOutput::LoadHistoryData(CConfig *config, CGeometry *geometry, CSol
446443

447444
/*--- Keep this as last, since it uses the history values that were set. ---*/
448445

446+
SetCustomOutputs(solver, geometry, config);
447+
449448
SetCustomAndComboObjectives(FLOW_SOL, config, solver);
450449

451450
}

SU2_CFD/src/output/CFlowIncOutput.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,8 @@ void CFlowIncOutput::LoadHistoryData(CConfig *config, CGeometry *geometry, CSolv
278278

279279
/*--- Keep this as last, since it uses the history values that were set. ---*/
280280

281+
SetCustomOutputs(solver, geometry, config);
282+
281283
SetCustomAndComboObjectives(FLOW_SOL, config, solver);
282284

283285
}

0 commit comments

Comments
 (0)