DNDSR 0.1.0.dev1+gcd065ad
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
CLDriver.hpp
Go to the documentation of this file.
1/** @file CLDriver.hpp
2 * @brief Iterative angle-of-attack driver for targeting a desired lift coefficient.
3 *
4 * Provides the CLDriverSettings configuration struct and the CLDriver controller
5 * class. During a CFD simulation the CLDriver monitors the running lift coefficient,
6 * detects convergence over a sliding window, estimates the CL-vs-AoA slope, and
7 * adjusts the angle of attack to drive CL toward a user-specified target.
8 */
9#pragma once
10
11#include "DNDS/JsonUtil.hpp"
12#include "DNDS/ConfigParam.hpp"
13#include <set>
14#include "DNDS/Defines.hpp"
15#include "DNDS/MPI.hpp"
16#include "Geom/Geometric.hpp"
17
18namespace DNDS::Euler
19{
20
21 /**
22 * @brief JSON-configurable settings for the CL (lift coefficient) driver.
23 *
24 * Controls the iterative angle-of-attack (AoA) adjustment loop that seeks
25 * to match a target lift coefficient. Parameters govern the AoA rotation
26 * axis, force reference directions, normalization areas/pressures,
27 * under-relaxation, and both short-window and long-window convergence
28 * criteria.
29 */
31 {
32 real AOAInit = 0.0; ///< Initial angle of attack in degrees.
33 std::string AOAAxis = "z"; ///< Coordinate axis about which AoA rotation is applied ("x", "y", or "z").
34 std::string CL0Axis = "y"; ///< Coordinate axis defining the zero-AoA lift direction ("x", "y", or "z").
35 std::string CD0Axis = "x"; ///< Coordinate axis defining the zero-AoA drag direction ("x", "y", or "z").
36 real refArea = 1.0; ///< Reference area for aerodynamic coefficient normalization.
37 real refDynamicPressure = 0.5; ///< Reference dynamic pressure (0.5 * rho_inf * V_inf^2) for coefficient normalization.
38 real targetCL = 0.0; ///< Target lift coefficient that the driver attempts to achieve.
39 real CLIncrementRelax = 0.25; ///< Under-relaxation factor applied to each AoA increment (0,1]. // reduce each alpha increment
40 real thresholdTargetRatio = 0.5; ///< Fraction of |targetCL - lastCL| used to tighten the convergence threshold near the target. // reduce CL convergence threshold when close to the target CL
41
42 index nIterStartDrive = INT32_MAX; ///< Solver iteration at which the CL driver becomes active.
43 index nIterConvergeMin = 50; ///< Minimum number of iterations before the CL convergence window is evaluated.
44 real CLconvergeThreshold = 1e-3; ///< Maximum deviation within the sliding window for CL to be considered converged.
45 index CLconvergeWindow = 10; ///< Number of most-recent CL samples in the sliding convergence window.
46
47 index CLconvergeLongWindow = 100; ///< Number of consecutive iterations within tolerance required for final (long-window) convergence. // for converged-at-target exit of main iteration loop
48 real CLconvergeLongThreshold = 1e-4; ///< CL error tolerance for the long-window converged-at-target check.
49 bool CLconvergeLongStrictAoA = false; ///< If true, reset the long-window counter whenever the AoA is updated.
50
52 {
53 DNDS_FIELD(AOAInit, "Initial angle of attack (degrees)");
54 DNDS_FIELD(AOAAxis, "Rotation axis for AoA",
55 DNDS::Config::enum_values({"x", "y", "z"}));
56 DNDS_FIELD(CL0Axis, "Lift force reference axis",
57 DNDS::Config::enum_values({"x", "y", "z"}));
58 DNDS_FIELD(CD0Axis, "Drag force reference axis",
59 DNDS::Config::enum_values({"x", "y", "z"}));
60 DNDS_FIELD(refArea, "Reference area for force coefficients",
62 DNDS_FIELD(refDynamicPressure, "Reference dynamic pressure",
64 DNDS_FIELD(targetCL, "Target lift coefficient");
65 DNDS_FIELD(CLIncrementRelax, "Under-relaxation for AoA increment",
66 DNDS::Config::range(0.0, 1.0));
67 DNDS_FIELD(thresholdTargetRatio, "Reduce CL threshold when near target",
68 DNDS::Config::range(0.0, 1.0));
69 DNDS_FIELD(nIterStartDrive, "Iteration to start CL driver",
71 DNDS_FIELD(nIterConvergeMin, "Min iterations before CL convergence check",
73 DNDS_FIELD(CLconvergeThreshold, "CL convergence window tolerance",
75 DNDS_FIELD(CLconvergeWindow, "Number of samples in CL convergence window",
77 DNDS_FIELD(CLconvergeLongWindow, "Long window for converged-at-target exit",
79 DNDS_FIELD(CLconvergeLongThreshold, "Long-window CL tolerance",
81 DNDS_FIELD(CLconvergeLongStrictAoA, "Reset long counter on AoA update");
82 }
83 };
84
85 /**
86 * @brief Lift-coefficient driver controller.
87 *
88 * Called once per solver iteration with the current CL value. Maintains a
89 * circular sliding window of recent CL samples. When the window is full and
90 * the maximum deviation from the mean falls below a (possibly tightened)
91 * threshold, the driver estimates the local CL slope dCL/dAoA from the
92 * previous and current converged CL values, clamps it to [0.9, 10] times
93 * the thin-airfoil theoretical slope (2*pi per radian ≈ pi^2/90 per degree),
94 * and computes a new angle of attack with under-relaxation.
95 *
96 * A separate long-window counter tracks how many consecutive iterations the
97 * CL error stays within CLconvergeLongThreshold; when the counter reaches
98 * CLconvergeLongWindow the solver may terminate.
99 */
101 {
102 CLDriverSettings settings; ///< Configuration parameters for this driver instance.
103 real lastCL{veryLargeReal}; ///< CL value from the previous converged window (sentinel = not yet set).
104 real lastAOA{veryLargeReal}; ///< AoA corresponding to @ref lastCL (sentinel = not yet set).
105 Eigen::VectorXd CLHistory; ///< Circular buffer holding the most recent CL samples.
106 index CLHistorySize = 0; ///< Total number of CL samples pushed (may exceed window size).
107 index CLHistoryHead = 0; ///< Current write position (head) in the circular buffer.
108 index CLAtTargetAcc = 0; ///< Consecutive-iteration counter for the long-window convergence check.
109
110 /**
111 * @brief Push a new CL sample into the circular history buffer.
112 * @param CL The lift coefficient value for the current iteration.
113 */
114 void PushCL_(real CL)
115 {
116 CLHistoryHead = mod<index>(CLHistoryHead + 1, CLHistory.size());
117 CLHistory(CLHistoryHead) = CL;
118 CLHistorySize++;
119 }
120 /**
121 * @brief Reset the CL history buffer, clearing all stored samples.
122 */
123 void ClearCL_()
124 {
125 CLHistory.setConstant(veryLargeReal);
126 CLHistorySize = 0;
127 CLHistoryHead = 0;
128 }
129
130 real AOA{0.0}; ///< Current angle of attack in degrees.
131
132 public:
133 /**
134 * @brief Construct a CLDriver from the given settings.
135 *
136 * Validates axis strings, allocates the CL history window, and sets the
137 * initial angle of attack from CLDriverSettings::AOAInit.
138 *
139 * @param settingsIn Fully populated CLDriverSettings instance.
140 */
141 CLDriver(const CLDriverSettings &settingsIn) : settings(settingsIn)
142 {
143 auto assertOnAxisString = [](const std::string &ax)
144 {
145 DNDS_assert(ax.size() == 1);
146 DNDS_assert(std::set<std::string>({"x", "y", "z"}).count(ax) == 1);
147 };
148 assertOnAxisString(settings.AOAAxis);
149 assertOnAxisString(settings.CL0Axis);
150 assertOnAxisString(settings.CD0Axis);
151
152 DNDS_assert(settings.CLconvergeWindow >= 2);
153 AOA = settings.AOAInit;
154 CLHistory.resize(settings.CLconvergeWindow);
155 CLHistory.setConstant(veryLargeReal);
156 }
157
158 /**
159 * @brief Get the current angle of attack.
160 * @return Current AoA in degrees.
161 */
163 {
164 return AOA;
165 }
166
167 /**
168 * @brief Main driver update — call once per solver iteration.
169 *
170 * Pushes the current CL into the sliding window, updates the long-window
171 * convergence counter, and (if the driver is active and the window has
172 * converged) computes a new AoA based on the estimated dCL/dAoA slope.
173 *
174 * The slope is clamped to [0.9, 10] times the thin-airfoil theoretical
175 * value (pi^2/90 per degree). The AoA increment is under-relaxed by
176 * CLDriverSettings::CLIncrementRelax.
177 *
178 * @param iter Current solver iteration number.
179 * @param CL Lift coefficient computed at this iteration.
180 * @param mpi MPI communicator info (rank 0 prints log messages).
181 */
182 void Update(index iter, real CL, const MPIInfo &mpi)
183 {
184 PushCL_(CL);
185 real curCLErr = std::abs(settings.targetCL - CL);
186 // if (mpi.rank == 0)
187 // std::cout << fmt::format("curCLErr {}, n = {}", curCLErr, CLAtTargetAcc) << std::endl;
188 if (curCLErr <= settings.CLconvergeLongThreshold)
189 CLAtTargetAcc++;
190 else
191 CLAtTargetAcc = 0;
192
193 if (iter < settings.nIterStartDrive)
194 return;
195 if (CLHistorySize >= CLHistory.size() && CLHistorySize >= settings.nIterConvergeMin)
196 {
197 real curCL = CLHistory.mean();
198 real currentCLThreshold = settings.CLconvergeThreshold;
199 currentCLThreshold = std::min(currentCLThreshold, std::abs(settings.targetCL - lastCL) * settings.thresholdTargetRatio);
200 real maxCLDeviation = std::max(CLHistory.maxCoeff() - curCL, curCL - CLHistory.minCoeff());
201 if (maxCLDeviation <= currentCLThreshold) // do AoA Update
202 {
203 real CLSlope = (lastCL - CL) / (lastAOA - AOA);
204 real CLSlopeStandard = sqr(pi) / 90.;
205 if (lastCL == veryLargeReal || lastAOA == veryLargeReal)
206 CLSlope = CLSlopeStandard;
207
208 if (CLSlope < 0)
209 CLSlope = CLSlopeStandard; //! warning, assuming positive CLSlope now
210 // if (std::abs(CLSlope) > 10 * CLSlopeStandard)
211 // CLSlope = CLSlopeStandard;
212 // if (std::abs(CLSlope) < 0.9 * CLSlopeStandard)
213 // CLSlope = CLSlopeStandard;
214 CLSlope = std::min(CLSlope, 10 * CLSlopeStandard);
215 CLSlope = std::max(CLSlope, 0.9 * CLSlopeStandard);
216
217 real AOANew = AOA + (settings.targetCL - CL) / CLSlope * settings.CLIncrementRelax;
218
219 lastAOA = AOA;
220 lastCL = curCL;
221 AOA = AOANew;
222 if (settings.CLconvergeLongStrictAoA)
223 CLAtTargetAcc = 0;
224 ClearCL_();
225
226 if (mpi.rank == 0)
227 log() << fmt::format("=== CLDriver at iter [{}], CL converged = [{}+-{:.1e}], CLSlope = [{}], newAOA [{}]",
228 iter, curCL, maxCLDeviation, CLSlope, AOA)
229 << std::endl;
230 }
231 }
232 }
233
234 /**
235 * @brief Check whether the long-window convergence criterion is satisfied.
236 *
237 * Returns true when the CL error has remained within
238 * CLDriverSettings::CLconvergeLongThreshold for at least
239 * CLDriverSettings::CLconvergeLongWindow consecutive iterations.
240 * The solver may use this to terminate the main iteration loop.
241 *
242 * @return True if converged at the target CL for sufficiently many iterations.
243 */
245 {
246 return CLAtTargetAcc >= settings.CLconvergeLongWindow;
247 }
248
249 /**
250 * @brief Compute the rotation matrix that rotates the freestream from AoA = 0 to the current AoA.
251 *
252 * Supports rotation about the z-axis (standard 2-D convention) and the
253 * y-axis. The sign convention follows the right-hand rule for z and a
254 * negated angle for y so that positive AoA corresponds to positive lift
255 * in the standard aerodynamic frame.
256 *
257 * @return 3×3 rotation matrix (Geom::tGPoint) encoding the current AoA.
258 */
260 {
261 if (settings.AOAAxis == "z")
262 {
263 return Geom::RotZ(AOA);
264 }
265 else if (settings.AOAAxis == "y")
266 {
267 return Geom::RotY(-AOA);
268 }
269 else
270 {
271 DNDS_assert_info(false, "AOAAxis not supported");
272 return Geom::RotY(-AOA);
273 }
274 }
275
276 /**
277 * @brief Get the unit vector for the zero-AoA lift direction.
278 *
279 * Returns a unit vector along CLDriverSettings::CL0Axis.
280 * Currently supports "y" and "z".
281 *
282 * @return 3-D unit vector (Geom::tPoint) in the lift direction.
283 */
285 {
286 if (settings.CL0Axis == "y")
287 return Geom::tPoint{0, 1, 0};
288 else if (settings.CL0Axis == "z")
289 return Geom::tPoint{0, 0, 1};
290 else
291 {
292 DNDS_assert_info(false, "CL0Axis not supported");
293 return Geom::tPoint{0, 0, 1};
294 }
295 }
296
297 /**
298 * @brief Get the unit vector for the zero-AoA drag direction.
299 *
300 * Returns a unit vector along CLDriverSettings::CD0Axis.
301 * Currently supports "x" only.
302 *
303 * @return 3-D unit vector (Geom::tPoint) in the drag direction.
304 */
306 {
307 if (settings.CD0Axis == "x")
308 return Geom::tPoint{1, 0, 0};
309 else
310 {
311 DNDS_assert_info(false, "CD0Axis not supported");
312 return Geom::tPoint{1, 0, 0};
313 }
314 }
315
316 /**
317 * @brief Compute the multiplicative factor that converts an integrated force to
318 * a non-dimensional aerodynamic coefficient.
319 *
320 * The factor is 1 / (refArea * refDynamicPressure).
321 *
322 * @return Force-to-coefficient ratio (dimensionless).
323 */
325 {
326 return 1. / (settings.refArea * settings.refDynamicPressure);
327 }
328 };
329
330}
pybind11-style configuration registration with macro-based field declaration and namespace-scoped tag...
#define DNDS_FIELD(name_, desc_,...)
Register a field inside a DNDS_DECLARE_CONFIG body.
Core type aliases, constants, and metaprogramming utilities for the DNDS framework.
#define DNDS_assert_info(expr, info)
Debug-only assertion with an extra std::string info message.
Definition Errors.hpp:113
#define DNDS_assert(expr)
Debug-only assertion (compiled out when DNDS_NDEBUG is defined). Prints the expression + file/line + ...
Definition Errors.hpp:108
JSON-to-Eigen conversion utilities and nlohmann_json helper macros.
MPI wrappers: MPIInfo, collective operations, type mapping, CommStrategy.
Lift-coefficient driver controller.
Definition CLDriver.hpp:101
void Update(index iter, real CL, const MPIInfo &mpi)
Main driver update — call once per solver iteration.
Definition CLDriver.hpp:182
real GetAOA()
Get the current angle of attack.
Definition CLDriver.hpp:162
Geom::tPoint GetCL0Direction()
Get the unit vector for the zero-AoA lift direction.
Definition CLDriver.hpp:284
Geom::tPoint GetCD0Direction()
Get the unit vector for the zero-AoA drag direction.
Definition CLDriver.hpp:305
bool ConvergedAtTarget()
Check whether the long-window convergence criterion is satisfied.
Definition CLDriver.hpp:244
Geom::tGPoint GetAOARotation()
Compute the rotation matrix that rotates the freestream from AoA = 0 to the current AoA.
Definition CLDriver.hpp:259
real GetForce2CoeffRatio()
Compute the multiplicative factor that converts an integrated force to a non-dimensional aerodynamic ...
Definition CLDriver.hpp:324
CLDriver(const CLDriverSettings &settingsIn)
Construct a CLDriver from the given settings.
Definition CLDriver.hpp:141
EnumValuesTag enum_values(std::vector< std::string > vals)
Create an enum allowed-values tag.
RangeTag range(double min)
Create a minimum-only range constraint.
Eigen::Vector3d tPoint
Definition Geometric.hpp:9
tGPoint RotZ(real theta)
Eigen::Matrix3d tGPoint
Definition Geometric.hpp:11
tGPoint RotY(real theta)
DNDS_CONSTANT const real pi
π in double precision (matches DNDS_E_PI macro).
Definition Defines.hpp:199
int64_t index
Global row / DOF index type (signed 64-bit; handles multi-billion-cell meshes).
Definition Defines.hpp:107
constexpr T sqr(const T &a)
a * a, constexpr. Works for all arithmetic types.
Definition Defines.hpp:517
double real
Canonical floating-point scalar used throughout DNDSR (double precision).
Definition Defines.hpp:105
std::ostream & log()
Return the current DNDSR log stream (either std::cout or the installed file).
Definition Defines.cpp:49
DNDS_CONSTANT const real veryLargeReal
Catch-all upper bound ("practically infinity") for physical scalars.
Definition Defines.hpp:190
JSON-configurable settings for the CL (lift coefficient) driver.
Definition CLDriver.hpp:31
std::string CD0Axis
Coordinate axis defining the zero-AoA drag direction ("x", "y", or "z").
Definition CLDriver.hpp:35
real CLIncrementRelax
Under-relaxation factor applied to each AoA increment (0,1]. // reduce each alpha increment.
Definition CLDriver.hpp:39
index CLconvergeWindow
Number of most-recent CL samples in the sliding convergence window.
Definition CLDriver.hpp:45
index CLconvergeLongWindow
Number of consecutive iterations within tolerance required for final (long-window) convergence....
Definition CLDriver.hpp:47
std::string AOAAxis
Coordinate axis about which AoA rotation is applied ("x", "y", or "z").
Definition CLDriver.hpp:33
DNDS_DECLARE_CONFIG(CLDriverSettings)
Definition CLDriver.hpp:51
index nIterConvergeMin
Minimum number of iterations before the CL convergence window is evaluated.
Definition CLDriver.hpp:43
real CLconvergeLongThreshold
CL error tolerance for the long-window converged-at-target check.
Definition CLDriver.hpp:48
real thresholdTargetRatio
Fraction of |targetCL - lastCL| used to tighten the convergence threshold near the target....
Definition CLDriver.hpp:40
real targetCL
Target lift coefficient that the driver attempts to achieve.
Definition CLDriver.hpp:38
real refArea
Reference area for aerodynamic coefficient normalization.
Definition CLDriver.hpp:36
std::string CL0Axis
Coordinate axis defining the zero-AoA lift direction ("x", "y", or "z").
Definition CLDriver.hpp:34
real CLconvergeThreshold
Maximum deviation within the sliding window for CL to be considered converged.
Definition CLDriver.hpp:44
index nIterStartDrive
Solver iteration at which the CL driver becomes active.
Definition CLDriver.hpp:42
real refDynamicPressure
Reference dynamic pressure (0.5 * rho_inf * V_inf^2) for coefficient normalization.
Definition CLDriver.hpp:37
bool CLconvergeLongStrictAoA
If true, reset the long-window counter whenever the AoA is updated.
Definition CLDriver.hpp:49
real AOAInit
Initial angle of attack in degrees.
Definition CLDriver.hpp:32
Lightweight bundle of an MPI communicator and the calling rank's coordinates.
Definition MPI.hpp:215