FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
rotation_warning_utils.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2//
3// Copyright (c) 2026-present FERS Contributors (see AUTHORS.md).
4//
5// See the GNU GPLv2 LICENSE file in the FERS project root for more information.
6
8
9#include <algorithm>
10#include <array>
11#include <cmath>
12#include <format>
13#include <limits>
14#include <numbers>
15#include <numeric>
16#include <ranges>
17#include <string>
18
19#include "core/logging.h"
20
22{
23 namespace
24 {
25 thread_local std::vector<std::string> captured_warnings; ///< Thread-local warning buffer for API callers.
26
27 constexpr RealType kEpsilon = 1e-9; ///< Near-zero tolerance for rotation heuristics.
28 constexpr RealType kMatchTolerance = 1e-3; ///< Match tolerance for common-angle comparisons.
29 constexpr RealType kIrrationalLookingTolerance = 5e-3; ///< Tolerance for non-round radian-looking values.
30
31 /// Common degree values used by unit inference.
32 constexpr std::array<RealType, 12> common_degree_angles = {0.0, 30.0, 45.0, 60.0, 90.0, 120.0,
33 135.0, 150.0, 180.0, 225.0, 270.0, 360.0};
34
35 /// Common radian values used by unit inference.
36 constexpr std::array<RealType, 9> common_radian_angles = {0.0,
37 std::numbers::pi_v<RealType> / 6.0,
38 std::numbers::pi_v<RealType> / 4.0,
39 std::numbers::pi_v<RealType> / 3.0,
40 std::numbers::pi_v<RealType> / 2.0,
41 2.0 * std::numbers::pi_v<RealType> / 3.0,
42 3.0 * std::numbers::pi_v<RealType> / 4.0,
43 5.0 * std::numbers::pi_v<RealType> / 6.0,
44 std::numbers::pi_v<RealType>};
45
46 /// Returns true when two values are within the provided tolerance.
47 [[nodiscard]] bool is_close(const RealType a, const RealType b,
48 const RealType tolerance = kMatchTolerance) noexcept
49 {
50 return std::abs(a - b) <= tolerance;
51 }
52
53 /// Returns true when a value is nearly integral.
54 [[nodiscard]] bool is_near_integer(const RealType value) noexcept
55 {
56 return is_close(value, std::round(value), 1e-6);
57 }
58
59 /// Returns true when a value is nearly a tenth increment.
60 [[nodiscard]] bool is_near_tenth(const RealType value) noexcept
61 {
62 return is_close(value * 10.0, std::round(value * 10.0), kIrrationalLookingTolerance * 10.0);
63 }
64
65 /// Returns true when an absolute value matches a common-angle candidate.
67 const std::ranges::input_range auto& candidates) noexcept
68 {
69 for (const RealType candidate : candidates)
70 {
72 {
73 return true;
74 }
75 }
76 return false;
77 }
78
79 /// Returns true when an absolute value matches a simple rational multiple of pi.
81 {
82 if (abs_value <= kEpsilon)
83 {
84 return false;
85 }
86
87 const RealType pi = std::numbers::pi_v<RealType>;
88 for (int numerator = 1; numerator <= 12; ++numerator)
89 {
90 for (int denominator = 1; denominator <= 12; ++denominator)
91 {
92 if (std::gcd(numerator, denominator) != 1)
93 {
94 continue;
95 }
96
97 const RealType candidate =
98 pi * static_cast<RealType>(numerator) / static_cast<RealType>(denominator);
100 {
101 return true;
102 }
103 }
104 }
105 return false;
106 }
107
108 /// Finds the closest clean sine/cosine value for a radian input.
110 {
111 constexpr std::array<RealType, 5> clean_values = {0.0, 0.5, std::numbers::sqrt2_v<RealType> / 2.0,
112 std::numbers::sqrt3_v<RealType> / 2.0, 1.0};
113
114 RealType best = std::numeric_limits<RealType>::max();
115 for (const RealType value : {std::abs(std::sin(radians)), std::abs(std::cos(radians))})
116 {
117 for (const RealType clean : clean_values)
118 {
119 best = std::min(best, std::abs(value - clean));
120 }
121 }
122 return best;
123 }
124
126 {
127 const RealType pi = std::numbers::pi_v<RealType>;
128 const RealType two_pi = 2.0 * pi;
129 if (abs_value > 360.0)
130 {
131 result.degree_score += 8;
132 }
133 else if (abs_value > 180.0)
134 {
135 result.degree_score += 7;
136 }
137 else if (abs_value > two_pi)
138 {
139 result.degree_score += abs_value < 10.0 ? 3 : 6;
140 }
141 else if (abs_value > pi)
142 {
143 result.degree_score += 2;
144 }
145 }
146
148 {
152 {
153 result.degree_score += 3;
154 }
155 else if (radian_famous && !degree_famous)
156 {
157 result.radian_score += 3;
158 }
159 }
160
162 {
163 const RealType two_pi = 2.0 * std::numbers::pi_v<RealType>;
165 {
166 const RealType rounded = std::round(abs_value);
167 if ((std::fmod(rounded, 45.0) == 0.0) || (std::fmod(rounded, 30.0) == 0.0) ||
168 (std::fmod(rounded, 15.0) == 0.0) || (std::fmod(rounded, 10.0) == 0.0) ||
169 (std::fmod(rounded, 5.0) == 0.0))
170 {
171 result.degree_score += 2;
172 }
173 else
174 {
175 result.degree_score += 1;
176 }
177 }
178 else if ((abs_value <= two_pi + 1.0) && !is_near_tenth(abs_value))
179 {
180 result.radian_score += 1;
181 }
182 }
183
185 {
187 {
188 result.radian_score += 3;
189 }
190 }
191
193 const ValueKind kind) noexcept
194 {
195 const RealType pi = std::numbers::pi_v<RealType>;
196 const RealType two_pi = 2.0 * pi;
197 if ((kind != ValueKind::Angle) || (abs_value > two_pi + 0.5) ||
198 (std::abs(result.degree_score - result.radian_score) > 1))
199 {
200 return;
201 }
202
203 const RealType degree_distance = best_clean_trig_distance(value * pi / 180.0);
206 {
207 ++result.degree_score;
208 }
209 else if (radian_distance + 1e-4 < degree_distance)
210 {
211 ++result.radian_score;
212 }
213 }
214
216 {
217 if (result.degree_score == result.radian_score)
218 {
219 result.confidence = Confidence::None;
220 return;
221 }
222
223 result.inferred_unit = result.degree_score > result.radian_score ? params::RotationAngleUnit::Degrees
225 const int lead = std::abs(result.degree_score - result.radian_score);
226 const int max_score = std::max(result.degree_score, result.radian_score);
227
228 if ((lead >= 5) || ((max_score >= 7) && (lead >= 3)))
229 {
230 result.confidence = Confidence::High;
231 }
232 else if ((lead >= 3) || ((max_score >= 5) && (lead >= 2)))
233 {
234 result.confidence = Confidence::Medium;
235 }
236 else if ((lead >= 2) || (max_score >= 4))
237 {
238 result.confidence = Confidence::Low;
239 }
240 }
241
242 /// Returns the external token for a rotation angle unit.
243 [[nodiscard]] std::string unit_token(const params::RotationAngleUnit unit)
244 {
245 return std::string(params::rotationAngleUnitToken(unit));
246 }
247
248 /// Returns a display token for an inference confidence.
249 [[nodiscard]] std::string confidence_token(const Confidence confidence)
250 {
251 switch (confidence)
252 {
253 case Confidence::High:
254 return "high";
256 return "medium";
257 case Confidence::Low:
258 return "low";
259 case Confidence::None:
260 default:
261 return "none";
262 }
263 }
264 }
265
266 InferenceResult infer_unit_from_value(const RealType value, const ValueKind kind) noexcept
267 {
268 const RealType abs_value = std::abs(value);
269
271 if (abs_value <= kEpsilon)
272 {
273 return result;
274 }
275
282
283 return result;
284 }
285
286 bool should_warn(const Confidence confidence, const WarningSensitivity sensitivity) noexcept
287 {
288 switch (sensitivity)
289 {
291 return confidence == Confidence::High;
293 return (confidence == Confidence::Medium) || (confidence == Confidence::High);
295 return confidence != Confidence::None;
296 default:
297 return false;
298 }
299 }
300
302
303 std::vector<std::string> take_captured_warnings()
304 {
305 std::vector<std::string> warnings = std::move(captured_warnings);
306 captured_warnings.clear();
307 return warnings;
308 }
309
311 const ValueKind kind, const std::string_view source,
312 const std::string_view owner, const std::string_view field)
313 {
315 if ((inference.inferred_unit == declared_unit) || !should_warn(inference.confidence, kWarningSensitivity))
316 {
317 return;
318 }
319
320 const std::string message =
321 std::format("{} rotation {} '{}' looks like {} but '{}' was declared (confidence: {}, value: {}). "
322 "Change rotationangleunit or convert existing values.",
323 source, owner, field, unit_token(inference.inferred_unit), unit_token(declared_unit),
324 confidence_token(inference.confidence), value);
325
326 if (std::ranges::find(captured_warnings, message) == captured_warnings.end())
327 {
328 captured_warnings.push_back(message);
329 }
330
332 }
333}
double RealType
Type for real numbers.
Definition config.h:27
Header file for the logging system.
#define LOG(level,...)
Definition logging.h:19
@ WARNING
Warning level for potentially harmful situations.
RotationAngleUnit
Defines the units used at external rotation-path boundaries.
Definition parameters.h:42
@ Radians
Compass azimuth and elevation expressed in radians.
@ Degrees
Compass azimuth and elevation expressed in degrees.
constexpr std::string_view rotationAngleUnitToken(const RotationAngleUnit unit) noexcept
Converts a rotation angle unit to its XML token.
Definition parameters.h:352
std::vector< std::string > take_captured_warnings()
Returns and clears the thread-local captured rotation warnings.
Confidence
Confidence level for an inferred rotation unit.
@ Low
Weak evidence for the inferred unit.
@ High
Strong evidence for the inferred unit.
@ None
No useful inference could be made.
@ Medium
Moderate evidence for the inferred unit.
InferenceResult infer_unit_from_value(const RealType value, const ValueKind kind) noexcept
Infers the likely unit of a rotation value.
void maybe_warn_about_rotation_value(const RealType value, const params::RotationAngleUnit declared_unit, const ValueKind kind, const std::string_view source, const std::string_view owner, const std::string_view field)
Emits or captures a warning when a rotation value likely uses the wrong unit.
WarningSensitivity
Minimum confidence threshold for emitting rotation-unit warnings.
@ HighConfidence
Warn only on high-confidence mismatches.
@ MediumOrHigh
Warn on medium or high-confidence mismatches.
bool should_warn(const Confidence confidence, const WarningSensitivity sensitivity) noexcept
Returns true when a warning should be emitted for the confidence and sensitivity.
ValueKind
Kind of rotation value being inspected for unit mistakes.
void clear_captured_warnings() noexcept
Clears the thread-local captured rotation warnings.
constexpr WarningSensitivity kWarningSensitivity
Default warning sensitivity used by parser and serializer warnings.
math::Vec3 max
RealType b
RealType a
Result of inferring likely units from a rotation value.