DNDSR 0.2.1
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
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 // clang-format off
54 DNDS_FIELD(AOAInit, "Initial angle of attack (degrees)");
55 DNDS_FIELD(AOAAxis, "Rotation axis for AoA",
56 DNDS::Config::enum_values({"x", "y", "z"}));
57 DNDS_FIELD(CL0Axis, "Lift force reference axis",
58 DNDS::Config::enum_values({"x", "y", "z"}));
59 DNDS_FIELD(CD0Axis, "Drag force reference axis",
60 DNDS::Config::enum_values({"x", "y", "z"}));
61 DNDS_FIELD(refArea, "Reference area for force coefficients",
63 DNDS_FIELD(refDynamicPressure, "Reference dynamic pressure",
65 DNDS_FIELD(targetCL, "Target lift coefficient");
66 DNDS_FIELD(CLIncrementRelax, "Under-relaxation for AoA increment",
67 DNDS::Config::range(0.0, 1.0));
68 DNDS_FIELD(thresholdTargetRatio, "Reduce CL threshold when near target",
69 DNDS::Config::range(0.0, 1.0));
70 DNDS_FIELD(nIterStartDrive, "Iteration to start CL driver",
72 DNDS_FIELD(nIterConvergeMin, "Min iterations before CL convergence check",
74 DNDS_FIELD(CLconvergeThreshold, "CL convergence window tolerance",
76 DNDS_FIELD(CLconvergeWindow, "Number of samples in CL convergence window",
78 DNDS_FIELD(CLconvergeLongWindow, "Long window for converged-at-target exit",
80 DNDS_FIELD(CLconvergeLongThreshold, "Long-window CL tolerance",
82 DNDS_FIELD(CLconvergeLongStrictAoA, "Reset long counter on AoA update");
83 // clang-format on
84 }
85 };
86
87 /**
88 * @brief Lift-coefficient driver controller.
89 *
90 * Called once per solver iteration with the current CL value. Maintains a
91 * circular sliding window of recent CL samples. When the window is full and
92 * the maximum deviation from the mean falls below a (possibly tightened)
93 * threshold, the driver estimates the local CL slope dCL/dAoA from the
94 * previous and current converged CL values, clamps it to [0.9, 10] times
95 * the thin-airfoil theoretical slope (2*pi per radian ≈ pi^2/90 per degree),
96 * and computes a new angle of attack with under-relaxation.
97 *
98 * A separate long-window counter tracks how many consecutive iterations the
99 * CL error stays within CLconvergeLongThreshold; when the counter reaches
100 * CLconvergeLongWindow the solver may terminate.
101 */
103 {
104 CLDriverSettings settings; ///< Configuration parameters for this driver instance.
105 real lastCL{veryLargeReal}; ///< CL value from the previous converged window (sentinel = not yet set).
106 real lastAOA{veryLargeReal}; ///< AoA corresponding to @ref lastCL (sentinel = not yet set).
107 Eigen::VectorXd CLHistory; ///< Circular buffer holding the most recent CL samples.
108 index CLHistorySize = 0; ///< Total number of CL samples pushed (may exceed window size).
109 index CLHistoryHead = 0; ///< Current write position (head) in the circular buffer.
110 index CLAtTargetAcc = 0; ///< Consecutive-iteration counter for the long-window convergence check.
111
112 /**
113 * @brief Push a new CL sample into the circular history buffer.
114 * @param CL The lift coefficient value for the current iteration.
115 */
116 void PushCL_(real CL)
117 {
118 CLHistoryHead = mod<index>(CLHistoryHead + 1, CLHistory.size());
119 CLHistory(CLHistoryHead) = CL;
120 CLHistorySize++;
121 }
122 /**
123 * @brief Reset the CL history buffer, clearing all stored samples.
124 */
125 void ClearCL_()
126 {
127 CLHistory.setConstant(veryLargeReal);
128 CLHistorySize = 0;
129 CLHistoryHead = 0;
130 }
131
132 real AOA{0.0}; ///< Current angle of attack in degrees.
133
134 public:
135 /**
136 * @brief Construct a CLDriver from the given settings.
137 *
138 * Validates axis strings, allocates the CL history window, and sets the
139 * initial angle of attack from CLDriverSettings::AOAInit.
140 *
141 * @param settingsIn Fully populated CLDriverSettings instance.
142 */
143 CLDriver(const CLDriverSettings &settingsIn) : settings(settingsIn)
144 {
145 auto assertOnAxisString = [](const std::string &ax)
146 {
147 DNDS_assert(ax.size() == 1);
148 DNDS_assert(std::set<std::string>({"x", "y", "z"}).count(ax) == 1);
149 };
150 assertOnAxisString(settings.AOAAxis);
151 assertOnAxisString(settings.CL0Axis);
152 assertOnAxisString(settings.CD0Axis);
153
154 DNDS_assert(settings.CLconvergeWindow >= 2);
155 AOA = settings.AOAInit;
156 CLHistory.resize(settings.CLconvergeWindow);
157 CLHistory.setConstant(veryLargeReal);
158 }
159
160 /**
161 * @brief Get the current angle of attack.
162 * @return Current AoA in degrees.
163 */
165 {
166 return AOA;
167 }
168
169 /**
170 * @brief Main driver update — call once per solver iteration.
171 *
172 * Pushes the current CL into the sliding window, updates the long-window
173 * convergence counter, and (if the driver is active and the window has
174 * converged) computes a new AoA based on the estimated dCL/dAoA slope.
175 *
176 * The slope is clamped to [0.9, 10] times the thin-airfoil theoretical
177 * value (pi^2/90 per degree). The AoA increment is under-relaxed by
178 * CLDriverSettings::CLIncrementRelax.
179 *
180 * @param iter Current solver iteration number.
181 * @param CL Lift coefficient computed at this iteration.
182 * @param mpi MPI communicator info (rank 0 prints log messages).
183 */
184 void Update(index iter, real CL, const MPIInfo &mpi)
185 {
186 PushCL_(CL);
187 real curCLErr = std::abs(settings.targetCL - CL);
188 // if (mpi.rank == 0)
189 // std::cout << fmt::format("curCLErr {}, n = {}", curCLErr, CLAtTargetAcc) << std::endl;
190 if (curCLErr <= settings.CLconvergeLongThreshold)
191 CLAtTargetAcc++;
192 else
193 CLAtTargetAcc = 0;
194
195 if (iter < settings.nIterStartDrive)
196 return;
197 if (CLHistorySize >= CLHistory.size() && CLHistorySize >= settings.nIterConvergeMin)
198 {
199 real curCL = CLHistory.mean();
200 real currentCLThreshold = settings.CLconvergeThreshold;
201 currentCLThreshold = std::min(currentCLThreshold, std::abs(settings.targetCL - lastCL) * settings.thresholdTargetRatio);
202 real maxCLDeviation = std::max(CLHistory.maxCoeff() - curCL, curCL - CLHistory.minCoeff());
203 if (maxCLDeviation <= currentCLThreshold) // do AoA Update
204 {
205 real CLSlope = (lastCL - CL) / (lastAOA - AOA);
206 real CLSlopeStandard = sqr(pi) / 90.;
207 if (lastCL == veryLargeReal || lastAOA == veryLargeReal)
208 CLSlope = CLSlopeStandard;
209
210 if (CLSlope < 0)
211 CLSlope = CLSlopeStandard; //! warning, assuming positive CLSlope now
212 // if (std::abs(CLSlope) > 10 * CLSlopeStandard)
213 // CLSlope = CLSlopeStandard;
214 // if (std::abs(CLSlope) < 0.9 * CLSlopeStandard)
215 // CLSlope = CLSlopeStandard;
216 CLSlope = std::min(CLSlope, 10 * CLSlopeStandard);
217 CLSlope = std::max(CLSlope, 0.9 * CLSlopeStandard);
218
219 real AOANew = AOA + (settings.targetCL - CL) / CLSlope * settings.CLIncrementRelax;
220
221 lastAOA = AOA;
222 lastCL = curCL;
223 AOA = AOANew;
224 if (settings.CLconvergeLongStrictAoA)
225 CLAtTargetAcc = 0;
226 ClearCL_();
227
228 if (mpi.rank == 0)
229 log() << fmt::format("=== CLDriver at iter [{}], CL converged = [{}+-{:.1e}], CLSlope = [{}], newAOA [{}]",
230 iter, curCL, maxCLDeviation, CLSlope, AOA)
231 << std::endl;
232 }
233 }
234 }
235
236 /**
237 * @brief Check whether the long-window convergence criterion is satisfied.
238 *
239 * Returns true when the CL error has remained within
240 * CLDriverSettings::CLconvergeLongThreshold for at least
241 * CLDriverSettings::CLconvergeLongWindow consecutive iterations.
242 * The solver may use this to terminate the main iteration loop.
243 *
244 * @return True if converged at the target CL for sufficiently many iterations.
245 */
247 {
248 return CLAtTargetAcc >= settings.CLconvergeLongWindow;
249 }
250
251 /**
252 * @brief Compute the rotation matrix that rotates the freestream from AoA = 0 to the current AoA.
253 *
254 * Supports rotation about the z-axis (standard 2-D convention) and the
255 * y-axis. The sign convention follows the right-hand rule for z and a
256 * negated angle for y so that positive AoA corresponds to positive lift
257 * in the standard aerodynamic frame.
258 *
259 * @return 3×3 rotation matrix (Geom::tGPoint) encoding the current AoA.
260 */
262 {
263 if (settings.AOAAxis == "z")
264 {
265 return Geom::RotZ(AOA);
266 }
267 else if (settings.AOAAxis == "y")
268 {
269 return Geom::RotY(-AOA);
270 }
271 else
272 {
273 DNDS_assert_info(false, "AOAAxis not supported");
274 return Geom::RotY(-AOA);
275 }
276 }
277
278 /**
279 * @brief Get the unit vector for the zero-AoA lift direction.
280 *
281 * Returns a unit vector along CLDriverSettings::CL0Axis.
282 * Currently supports "y" and "z".
283 *
284 * @return 3-D unit vector (Geom::tPoint) in the lift direction.
285 */
287 {
288 if (settings.CL0Axis == "y")
289 return Geom::tPoint{0, 1, 0};
290 else if (settings.CL0Axis == "z")
291 return Geom::tPoint{0, 0, 1};
292 else
293 {
294 DNDS_assert_info(false, "CL0Axis not supported");
295 return Geom::tPoint{0, 0, 1};
296 }
297 }
298
299 /**
300 * @brief Get the unit vector for the zero-AoA drag direction.
301 *
302 * Returns a unit vector along CLDriverSettings::CD0Axis.
303 * Currently supports "x" only.
304 *
305 * @return 3-D unit vector (Geom::tPoint) in the drag direction.
306 */
308 {
309 if (settings.CD0Axis == "x")
310 return Geom::tPoint{1, 0, 0};
311 else
312 {
313 DNDS_assert_info(false, "CD0Axis not supported");
314 return Geom::tPoint{1, 0, 0};
315 }
316 }
317
318 /**
319 * @brief Compute the multiplicative factor that converts an integrated force to
320 * a non-dimensional aerodynamic coefficient.
321 *
322 * The factor is 1 / (refArea * refDynamicPressure).
323 *
324 * @return Force-to-coefficient ratio (dimensionless).
325 */
327 {
328 return 1. / (settings.refArea * settings.refDynamicPressure);
329 }
330 };
331
332}
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:117
#define DNDS_assert(expr)
Debug-only assertion (compiled out when DNDS_NDEBUG is defined). Prints the expression + file/line + ...
Definition Errors.hpp:112
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:103
void Update(index iter, real CL, const MPIInfo &mpi)
Main driver update — call once per solver iteration.
Definition CLDriver.hpp:184
real GetAOA()
Get the current angle of attack.
Definition CLDriver.hpp:164
Geom::tPoint GetCL0Direction()
Get the unit vector for the zero-AoA lift direction.
Definition CLDriver.hpp:286
Geom::tPoint GetCD0Direction()
Get the unit vector for the zero-AoA drag direction.
Definition CLDriver.hpp:307
bool ConvergedAtTarget()
Check whether the long-window convergence criterion is satisfied.
Definition CLDriver.hpp:246
Geom::tGPoint GetAOARotation()
Compute the rotation matrix that rotates the freestream from AoA = 0 to the current AoA.
Definition CLDriver.hpp:261
real GetForce2CoeffRatio()
Compute the multiplicative factor that converts an integrated force to a non-dimensional aerodynamic ...
Definition CLDriver.hpp:326
CLDriver(const CLDriverSettings &settingsIn)
Construct a CLDriver from the given settings.
Definition CLDriver.hpp:143
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:204
int64_t index
Global row / DOF index type (signed 64-bit; handles multi-billion-cell meshes).
Definition Defines.hpp:112
constexpr T sqr(const T &a)
a * a, constexpr. Works for all arithmetic types.
Definition Defines.hpp:522
double real
Canonical floating-point scalar used throughout DNDSR (double precision).
Definition Defines.hpp:110
std::ostream & log()
Return the current DNDSR log stream (either std::cout or the installed file).
Definition Defines.cpp:50
DNDS_CONSTANT const real veryLargeReal
Catch-all upper bound ("practically infinity") for physical scalars.
Definition Defines.hpp:195
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:231