Skip to content

Commit 2a5f046

Browse files
gabrielfrasantosgithub-actions[bot]Copilot
authored
feat!: add math custom layer (#238)
* add math custom layer * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix: correct std::sqrtf usage and restore missing array include Co-authored-by: gabrielfrasantos <21131318+gabrielfrasantos@users.noreply.github.com> * fix: resolve macOS constexpr compile failure in SavitzkyGolayFilter Co-authored-by: gabrielfrasantos <21131318+gabrielfrasantos@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: gabrielfrasantos <21131318+gabrielfrasantos@users.noreply.github.com>
1 parent 48e80e3 commit 2a5f046

67 files changed

Lines changed: 623 additions & 306 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/instructions/numerical-cpp.instructions.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ Write generic `template<typename T[, std::size_t N]>` with
2727
Do not implement `Q15`/`Q31` — the generic `T` keeps that a cheap future add.
2828
Use `std::numbers::pi_v<float>` — never hardcode `3.14159265f`.
2929

30+
## Math Functions
31+
32+
Use `math::Sin`, `math::Cos`, `math::Abs`, `math::Sqrt`, etc. from
33+
`numerical/math/Math.hpp` in all production code.
34+
Never call `std::sin`, `std::cos`, `std::abs`, `std::sqrt`, or any other `<cmath>` function directly in production headers or source files.
35+
If a required `<cmath>` function is not yet wrapped in `Math.hpp`, add it there first — then use `math::FunctionName` at the call site.
36+
37+
Each function is guarded by `#ifndef MATH_<NAME>_OVERRIDE` (e.g. `MATH_SIN_OVERRIDE`).
38+
To replace a function with a platform-specific implementation, define the corresponding macro
39+
(e.g. `add_compile_definitions(MATH_SIN_OVERRIDE)` in CMake) and provide your own
40+
`template<typename T> constexpr T math::Sin(T x)` in a header that is included before any call site.
41+
3042
## Embedded Optimizations
3143

3244
Every algorithm header MUST include:

.github/instructions/testing.instructions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ TEST_F(TestDare, solves_simple_system)
4444
- `TEST_F` macros go **outside** the anonymous namespace
4545
- Include `<gtest/gtest.h>` (not `<gmock/gmock.h>`) unless gmock matchers are needed
4646
- Use `testing::StrictMock<MockType>` for strict mock expectations
47+
- **Math in tests**: use `std::sin`, `std::cos`, `std::abs`, etc. directly — never `math::Sin`, `math::Cos`, `math::Abs`, etc. in test code
4748
- **ONLY `StrictMock`**: Never use `testing::NiceMock<>` or bare mock instantiation — `NiceMock` silences unexpected-call warnings, masking test gaps; `StrictMock` enforces all interactions explicitly
4849
- Test `float` only (single type) — no multi-type tests
4950
- **No redundant tests** — implement exactly the spec's enumerated cases; no overlapping/extra cases

AGENTS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ resource-constrained embedded systems. Real-time, deterministic, no heap.
5454
`neural_network`, `optimization`, `regularization`, `solvers`, and new:
5555
`robust_control`, `nonlinear_control`.
5656

57+
## Math functions
58+
59+
- **Production code** calls `math::Sin`, `math::Cos`, `math::Abs`, etc. from
60+
`numerical/math/Math.hpp`. Never call `std::` cmath functions directly in production.
61+
- If a needed `<cmath>` function is missing from `Math.hpp`, add a `constexpr` wrapper there first,
62+
then use `math::FunctionName` at the call site.
63+
- Each function is individually overridable at compile time via `#ifndef MATH_<NAME>_OVERRIDE`
64+
(e.g. `MATH_SIN_OVERRIDE`). Define the macro in CMake and supply your own template definition.
65+
- **Unit tests** call `std::sin`, `std::cos`, `std::abs`, etc. directly. Never use `math::` in tests.
66+
5767
## Testing
5868

5969
- GoogleTest. **`TEST_F` on `float`** — no `TYPED_TEST`, no multi-type. **Never plain `TEST()`**.

README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,77 @@ xhost +local:docker
8989
> **Note:** The Dev Container sets `DISPLAY=host.docker.internal:0.0` to forward GUI windows over TCP. On Linux, if you prefer Unix socket forwarding, you can override `DISPLAY` to `:0` inside the container and add a bind mount for `/tmp/.X11-unix`.
9090
9191

92+
## Math Function Overrides
93+
94+
All math functions used by the library (`Sin`, `Cos`, `Abs`, `Sqrt`, `Pow`, …) are centralised in
95+
`numerical/math/Math.hpp` under the `math::` namespace.
96+
Each function ships with a default `constexpr` implementation that forwards to the corresponding
97+
`std::` counterpart, but every one of them can be replaced at compile time with a
98+
platform-specific implementation — CORDIC, hardware intrinsics, look-up tables, etc.
99+
100+
### How to override a function
101+
102+
**1. Define the override macro in CMake** (suppresses the default definition):
103+
104+
```cmake
105+
target_compile_definitions(your_target PRIVATE MATH_SIN_OVERRIDE MATH_COS_OVERRIDE)
106+
```
107+
108+
**2. Provide your own template definition** in a header included before any call site:
109+
110+
```cpp
111+
// platform/PlatformMath.hpp
112+
#pragma once
113+
namespace math
114+
{
115+
template<typename T>
116+
constexpr T Sin(T x) { return cordic_sin(static_cast<float>(x)); }
117+
118+
template<typename T>
119+
constexpr T Cos(T x) { return cordic_cos(static_cast<float>(x)); }
120+
}
121+
```
122+
123+
**3. Include your header** before `numerical/math/Math.hpp` — or use a CMake forced-include so it
124+
applies to every translation unit automatically:
125+
126+
```cmake
127+
target_compile_options(your_target PRIVATE -include platform/PlatformMath.hpp)
128+
```
129+
130+
### Available override macros
131+
132+
| Macro | Function |
133+
|--------------------------|------------------|
134+
| `MATH_ABS_OVERRIDE` | `math::Abs` |
135+
| `MATH_SQRT_OVERRIDE` | `math::Sqrt` |
136+
| `MATH_SIN_OVERRIDE` | `math::Sin` |
137+
| `MATH_COS_OVERRIDE` | `math::Cos` |
138+
| `MATH_TAN_OVERRIDE` | `math::Tan` |
139+
| `MATH_ASIN_OVERRIDE` | `math::Asin` |
140+
| `MATH_ACOS_OVERRIDE` | `math::Acos` |
141+
| `MATH_ATAN_OVERRIDE` | `math::Atan` |
142+
| `MATH_ATAN2_OVERRIDE` | `math::Atan2` |
143+
| `MATH_EXP_OVERRIDE` | `math::Exp` |
144+
| `MATH_LOG_OVERRIDE` | `math::Log` |
145+
| `MATH_LOG10_OVERRIDE` | `math::Log10` |
146+
| `MATH_LOG2_OVERRIDE` | `math::Log2` |
147+
| `MATH_POW_OVERRIDE` | `math::Pow` |
148+
| `MATH_SINH_OVERRIDE` | `math::Sinh` |
149+
| `MATH_COSH_OVERRIDE` | `math::Cosh` |
150+
| `MATH_TANH_OVERRIDE` | `math::Tanh` |
151+
| `MATH_HYPOT_OVERRIDE` | `math::Hypot` |
152+
| `MATH_COPYSIGN_OVERRIDE` | `math::Copysign` |
153+
| `MATH_FMOD_OVERRIDE` | `math::Fmod` |
154+
| `MATH_CEIL_OVERRIDE` | `math::Ceil` |
155+
| `MATH_FLOOR_OVERRIDE` | `math::Floor` |
156+
| `MATH_ROUND_OVERRIDE` | `math::Round` |
157+
| `MATH_ERFC_OVERRIDE` | `math::Erfc` |
158+
159+
Overrides are fully compile-time: the default body is excluded from the translation unit, so the
160+
replacement is used even for inlined calls. Non-overridden functions continue to use the `std::`
161+
defaults unchanged.
162+
92163
## Roadmap
93164

94165
Planned algorithms and components are tracked in [ROADMAP.md](ROADMAP.md) — a prioritized backlog

numerical/analysis/Decibels.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
#endif
66

77
#include "numerical/math/CompilerOptimizations.hpp"
8+
#include "numerical/math/Math.hpp"
89
#include <algorithm>
9-
#include <cmath>
1010
#include <type_traits>
1111

1212
namespace analysis
@@ -24,14 +24,14 @@ namespace analysis
2424
static_assert(std::is_floating_point_v<T>, "ToDecibels supports floating-point types only");
2525
if (ratio <= T{ 0 })
2626
return DecibelFloor<T>::value;
27-
return std::max(T{ 20 } * std::log10(ratio), DecibelFloor<T>::value);
27+
return std::max(T{ 20 } * math::Log10(ratio), DecibelFloor<T>::value);
2828
}
2929

3030
template<typename T>
3131
OPTIMIZE_FOR_SPEED T FromDecibels(T db)
3232
{
3333
static_assert(std::is_floating_point_v<T>, "FromDecibels supports floating-point types only");
34-
return std::pow(T{ 10 }, db / T{ 20 });
34+
return math::Pow(T{ 10 }, db / T{ 20 });
3535
}
3636

3737
template<typename T>

numerical/analysis/DiscreteCosineTransform.hpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#include "numerical/analysis/FastFourierTransform.hpp"
99
#include "numerical/math/CompilerOptimizations.hpp"
1010
#include "numerical/math/ComplexNumber.hpp"
11-
#include <cmath>
11+
#include "numerical/math/Math.hpp"
1212
#include <numbers>
1313

1414
namespace analysis
@@ -59,17 +59,17 @@ namespace analysis
5959

6060
auto& fftResult = fft.Forward(reordered);
6161

62-
output[0] = QNumberType(math::ToFloat(fftResult[0].Real()) / std::sqrt(static_cast<float>(Length)));
62+
output[0] = QNumberType(math::ToFloat(fftResult[0].Real()) / math::Sqrt(static_cast<float>(Length)));
6363

6464
for (std::size_t k = 1; k < Length; ++k)
6565
{
6666
float angle = -static_cast<float>(k) * std::numbers::pi_v<float> / (2.0f * Length);
67-
float scale = 2.0f / std::sqrt(static_cast<float>(Length));
67+
float scale = 2.0f / math::Sqrt(static_cast<float>(Length));
6868

6969
float real = math::ToFloat(fftResult[k].Real());
7070
float imag = math::ToFloat(fftResult[k].Imaginary());
7171

72-
output[k] = QNumberType((real * std::cos(angle) - imag * std::sin(angle)) * scale);
72+
output[k] = QNumberType((real * math::Cos(angle) - imag * math::Sin(angle)) * scale);
7373
}
7474

7575
return output;
@@ -78,7 +78,7 @@ namespace analysis
7878
template<typename QNumberType, std::size_t Length>
7979
typename DiscreteConsineTransform<QNumberType, Length>::VectorReal& DiscreteConsineTransform<QNumberType, Length>::Inverse(VectorReal& input)
8080
{
81-
float sqrtN = std::sqrt(static_cast<float>(Length));
81+
float sqrtN = math::Sqrt(static_cast<float>(Length));
8282

8383
complexBuffer[0] = math::Complex<QNumberType>{ QNumberType(math::ToFloat(input[0]) * sqrtN), QNumberType(0.0f) };
8484

@@ -88,8 +88,8 @@ namespace analysis
8888
float imag = -math::ToFloat(input[Length - k]) * sqrtN / 2.0f;
8989

9090
float angle = static_cast<float>(k) * std::numbers::pi_v<float> / (2.0f * static_cast<float>(Length));
91-
float cosine = std::cos(angle);
92-
float sine = std::sin(angle);
91+
float cosine = math::Cos(angle);
92+
float sine = math::Sin(angle);
9393

9494
complexBuffer[k] = math::Complex<QNumberType>{ QNumberType(real * cosine - imag * sine), QNumberType(real * sine + imag * cosine) };
9595
}

numerical/analysis/DiscreteWaveletTransform.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
#include "infra/util/BoundedVector.hpp"
88
#include "numerical/math/CompilerOptimizations.hpp"
9+
#include "numerical/math/Math.hpp"
910
#include <array>
10-
#include <cmath>
1111
#include <cstddef>
1212
#include <type_traits>
1313

numerical/analysis/GoertzelAlgorithm.hpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
#include "numerical/math/CompilerOptimizations.hpp"
88
#include "numerical/math/ComplexNumber.hpp"
9-
#include <cmath>
9+
#include "numerical/math/Math.hpp"
1010
#include <cstddef>
1111
#include <numbers>
1212
#include <type_traits>
@@ -42,9 +42,9 @@ namespace analysis
4242

4343
template<typename T>
4444
GoertzelAlgorithm<T>::GoertzelAlgorithm(std::size_t k, std::size_t blockLength)
45-
: coeff{ T{ 2 } * std::cos(T{ 2 } * std::numbers::pi_v<T> * static_cast<T>(k) / static_cast<T>(blockLength)) }
46-
, cosine{ std::cos(T{ 2 } * std::numbers::pi_v<T> * static_cast<T>(k) / static_cast<T>(blockLength)) }
47-
, sine{ std::sin(T{ 2 } * std::numbers::pi_v<T> * static_cast<T>(k) / static_cast<T>(blockLength)) }
45+
: coeff{ T{ 2 } * math::Cos(T{ 2 } * std::numbers::pi_v<T> * static_cast<T>(k) / static_cast<T>(blockLength)) }
46+
, cosine{ math::Cos(T{ 2 } * std::numbers::pi_v<T> * static_cast<T>(k) / static_cast<T>(blockLength)) }
47+
, sine{ math::Sin(T{ 2 } * std::numbers::pi_v<T> * static_cast<T>(k) / static_cast<T>(blockLength)) }
4848
, blockSize{ blockLength }
4949
{}
5050

@@ -82,7 +82,7 @@ namespace analysis
8282
template<typename T>
8383
T GoertzelAlgorithm<T>::Magnitude() const
8484
{
85-
return std::sqrt(s1 * s1 + s2 * s2 - coeff * s1 * s2);
85+
return math::Sqrt(s1 * s1 + s2 * s2 - coeff * s1 * s2);
8686
}
8787

8888
template<typename T>
@@ -96,7 +96,7 @@ namespace analysis
9696
template<typename T>
9797
T GoertzelAlgorithm<T>::Coefficient(std::size_t k, std::size_t blockLength)
9898
{
99-
return T{ 2 } * std::cos(T{ 2 } * std::numbers::pi_v<T> * static_cast<T>(k) / static_cast<T>(blockLength));
99+
return T{ 2 } * math::Cos(T{ 2 } * std::numbers::pi_v<T> * static_cast<T>(k) / static_cast<T>(blockLength));
100100
}
101101
}
102102

numerical/analysis/HilbertTransform.hpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
#include "numerical/analysis/FastFourierTransform.hpp"
99
#include "numerical/math/CompilerOptimizations.hpp"
1010
#include "numerical/math/ComplexNumber.hpp"
11+
#include "numerical/math/Math.hpp"
1112
#include <array>
12-
#include <cmath>
1313
#include <numbers>
1414
#include <type_traits>
1515

@@ -101,13 +101,13 @@ namespace analysis
101101
template<typename T, std::size_t N>
102102
T AnalyticSignalFft<T, N>::InstantaneousAmplitude(Complex a)
103103
{
104-
return std::sqrt(a.Real() * a.Real() + a.Imaginary() * a.Imaginary());
104+
return math::Sqrt(a.Real() * a.Real() + a.Imaginary() * a.Imaginary());
105105
}
106106

107107
template<typename T, std::size_t N>
108108
T AnalyticSignalFft<T, N>::InstantaneousPhase(Complex a)
109109
{
110-
return std::atan2(a.Imaginary(), a.Real());
110+
return math::Atan2(a.Imaginary(), a.Real());
111111
}
112112

113113
template<typename T, std::size_t N>
@@ -133,7 +133,7 @@ namespace analysis
133133
int k{ static_cast<int>(i) - static_cast<int>(centerTap) };
134134
if (k != 0 && (k % 2) != 0)
135135
{
136-
T w{ T(0.54) - T(0.46) * std::cos(T(2) * std::numbers::pi_v<T> * static_cast<T>(i) / static_cast<T>(Taps - 1)) };
136+
T w{ T(0.54) - T(0.46) * math::Cos(T(2) * std::numbers::pi_v<T> * static_cast<T>(i) / static_cast<T>(Taps - 1)) };
137137
coeff[i] = (T(2) / (std::numbers::pi_v<T> * static_cast<T>(k))) * w;
138138
}
139139
}

numerical/analysis/SignalDetectors.hpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#endif
66

77
#include "numerical/math/CompilerOptimizations.hpp"
8-
#include <cmath>
8+
#include "numerical/math/Math.hpp"
99
#include <cstdint>
1010
#include <type_traits>
1111

@@ -71,7 +71,7 @@ namespace analysis
7171
template<typename T>
7272
OPTIMIZE_FOR_SPEED T PeakHold<T>::Update(T x)
7373
{
74-
T m{ std::abs(x) };
74+
T m{ math::Abs(x) };
7575
peak = (m > peak * decay) ? m : peak * decay;
7676
return peak;
7777
}
@@ -90,7 +90,7 @@ namespace analysis
9090
template<typename T>
9191
OPTIMIZE_FOR_SPEED bool ZeroCrossingCounter<T>::Update(T x)
9292
{
93-
bool crossed{ (previous < T{ 0 } ? x > T{ 0 } : x < T{ 0 }) && (std::abs(x) > hysteresis) };
93+
bool crossed{ (previous < T{ 0 } ? x > T{ 0 } : x < T{ 0 }) && (math::Abs(x) > hysteresis) };
9494
if (crossed)
9595
++count;
9696
previous = x;
@@ -119,7 +119,7 @@ namespace analysis
119119
OPTIMIZE_FOR_SPEED T RmsEnvelope<T>::Update(T x)
120120
{
121121
meanSquare += alpha * (x * x - meanSquare);
122-
return std::sqrt(meanSquare);
122+
return math::Sqrt(meanSquare);
123123
}
124124

125125
template<typename T>

0 commit comments

Comments
 (0)