Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/agents/reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ End with totals + verdict: APPROVE / REQUEST CHANGES.
- [ ] SOLID: one concern per class, constructor injection, depend on abstractions, no duplicated logic.

**Interfaces & errors (WARNING)**
- [ ] `virtual ~I() = default` — never `= 0`. No exceptions — `std::optional`/status enums.
- [ ] Interfaces: `protected:` non-virtual `~I() = default` — no `virtual` or `= 0` destructor, no ownership or deletion through an interface pointer. No exceptions — `std::optional`/status enums.

**Testing (WARNING)**
- [ ] `TEST_F` on `float` — no `TYPED_TEST`, no multi-type, never plain `TEST()`.
Expand Down
2 changes: 1 addition & 1 deletion .github/agents/reviewer.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ End with totals + verdict: APPROVE / REQUEST CHANGES.
- [ ] SOLID: one concern per class, constructor injection, depend on abstractions, no duplicated logic.

**Interfaces & errors (WARNING)**
- [ ] `virtual ~I() = default` — never `= 0`. No exceptions — `std::optional`/status enums.
- [ ] Interfaces: `protected:` non-virtual `~I() = default` — no `virtual` or `= 0` destructor, no ownership or deletion through an interface pointer. No exceptions — `std::optional`/status enums.

**Testing (WARNING)**
- [ ] `TEST_F` on `float` — no `TYPED_TEST`, no multi-type, never plain `TEST()`.
Expand Down
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ Essentials (full detail in AGENTS.md):
- **Embedded** — no `#pragma GCC optimize` / `optimize` attribute (they block inlining); the consumer sets optimization flags per TU; `OPTIMIZE_FOR_SPEED` (forced inlining) on hot paths.
- **No comments** (except license/NOLINT). Allman braces, brace-init, PascalCase/camelCase.
- **Tests** — `TEST_F` on `float`, `StrictMock` only, never plain `TEST()`, no redundant cases.
- **No exceptions** — `std::optional`/status enums; interfaces `virtual ~I() = default`.
- **No exceptions** — `std::optional`/status enums; interfaces get a `protected:` non-virtual `~I() = default`, never a virtual destructor.
- **Be terse** — minimal prose; report file paths + pass/fail.
2 changes: 1 addition & 1 deletion .github/instructions/numerical-cpp.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Use `math::IsFinite` for finiteness checks: a consumer may still build with `-ff
- SOLID principles — constructor injection, depend on abstractions
- DRY — extract shared logic into helpers or templates
- RAII for resource management
- **No pure virtual destructors**: Never declare `virtual ~Interface() = 0`. Pure virtual destructors force the compiler to emit a thunk and a separate body, wasting memory on embedded targets. Use `virtual ~Interface() = default` if a virtual destructor is required, or omit it entirely when the class is never deleted through a base pointer.
- **Protected, non-virtual interface destructors**: declare `~Interface() = default` in a `protected:` section, never `virtual ~Interface()` (defaulted or `= 0`). A virtual destructor puts a deleting destructor in every implementation's vtable, which links `operator delete` (and with it `malloc`/`free`) into heap-free firmware, and under multiple inheritance adds a thunk per base. The protected destructor makes deletion through an interface pointer a compile error. Concrete classes declare no destructor unless they need one, and callers hold the concrete type rather than a `std::unique_ptr` to the interface.
- No virtual calls in ISR-callable or real-time critical paths

## Documentation — MANDATORY
Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ resource-constrained embedded systems. Real-time, deterministic, no heap.

## Interfaces & errors

- Interfaces = pure virtual classes; `virtual ~I() = default` — **never** `= 0` destructors.
- Interfaces = pure virtual classes with a `protected:`, non-virtual `~I() = default` — **never** a `virtual` or `= 0` destructor. A virtual one puts a deleting destructor in every vtable, which links `operator delete`/`free` into heap-free firmware.
- Concrete classes declare no destructor unless they need one. Never own or delete through an interface pointer (no `std::unique_ptr<I>`): hold the concrete type, pass `I&`.
- No exceptions. Use `std::optional<T>` or status enums. `assert`/`really_assert` for preconditions.
- SOLID + DIP: constructor injection; depend on abstractions. RAII. No virtual calls in real-time paths.

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ Essentials (full detail in AGENTS.md):
- **Embedded** — no `#pragma GCC optimize` / `optimize` attribute (they block inlining); the consumer sets optimization flags per TU; `OPTIMIZE_FOR_SPEED` (forced inlining) on hot paths.
- **No comments** (except license/NOLINT). Allman braces, brace-init, PascalCase/camelCase.
- **Tests** — `TEST_F` on `float`, `StrictMock` only, never plain `TEST()`, no redundant cases.
- **No exceptions** — `std::optional`/status enums; interfaces `virtual ~I() = default`.
- **No exceptions** — `std::optional`/status enums; interfaces get a `protected:` non-virtual `~I() = default`, never a virtual destructor.
- **Be terse** — minimal prose; report file paths + pass/fail.
8 changes: 5 additions & 3 deletions numerical/analysis/FastFourierTransform.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ namespace analysis
class TwiddleFactors
{
public:
virtual ~TwiddleFactors() = default;
virtual math::Complex<QNumberType>& operator[](std::size_t n) = 0;

protected:
~TwiddleFactors() = default;
};

template<typename QNumberType>
Expand All @@ -21,8 +23,6 @@ namespace analysis
"FastFourierTransform can only be instantiated with math::QNumber types.");

public:
virtual ~FastFourierTransform() = default;

static constexpr std::size_t Log2(std::size_t n)
{
std::size_t result = 0;
Expand All @@ -41,6 +41,8 @@ namespace analysis
virtual VectorReal& Inverse(VectorComplex& input) = 0;

protected:
~FastFourierTransform() = default;

template<typename T>
inline T BitReverse(T x, T logr_n, T bits, T radix)
{
Expand Down
4 changes: 3 additions & 1 deletion numerical/analysis/windowing/Windowing.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ namespace windowing
"Window can only be instantiated with math::QNumber types.");

public:
virtual ~Window() = default;
virtual QNumberType operator()(std::size_t n, std::size_t order) = 0;
virtual QNumberType Power(std::size_t order) = 0;

protected:
~Window() = default;
};

namespace detail
Expand Down
27 changes: 13 additions & 14 deletions numerical/analysis/windowing/test/TestWindowing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ namespace
{
protected:
static constexpr float kEpsilon = 1e-3f;
std::unique_ptr<windowing::Window<QNumType>> window;
};

using WindowingTypes = ::testing::Types<math::Q31, math::Q15, float>;
Expand All @@ -20,7 +19,7 @@ namespace

TYPED_TEST(WindowingTest, HammingWindowValues)
{
this->window = std::make_unique<windowing::HammingWindow<TypeParam>>();
windowing::HammingWindow<TypeParam> window;

struct TestCase
{
Expand All @@ -36,7 +35,7 @@ TYPED_TEST(WindowingTest, HammingWindowValues)

for (const auto& testCase : testCases)
{
TypeParam result = (*this->window)(testCase.n, testCase.order);
TypeParam result = window(testCase.n, testCase.order);
EXPECT_NEAR(math::ToFloat(result), testCase.expected, this->kEpsilon)
<< "Failed for n=" << testCase.n
<< ", order=" << testCase.order;
Expand All @@ -45,7 +44,7 @@ TYPED_TEST(WindowingTest, HammingWindowValues)

TYPED_TEST(WindowingTest, HanningWindowValues)
{
this->window = std::make_unique<windowing::HanningWindow<TypeParam>>();
windowing::HanningWindow<TypeParam> window;

struct TestCase
{
Expand All @@ -61,7 +60,7 @@ TYPED_TEST(WindowingTest, HanningWindowValues)

for (const auto& testCase : testCases)
{
TypeParam result = (*this->window)(testCase.n, testCase.order);
TypeParam result = window(testCase.n, testCase.order);
EXPECT_NEAR(math::ToFloat(result), testCase.expected, this->kEpsilon)
<< "Failed for n=" << testCase.n
<< ", order=" << testCase.order;
Expand All @@ -70,7 +69,7 @@ TYPED_TEST(WindowingTest, HanningWindowValues)

TYPED_TEST(WindowingTest, BlackmanWindowValues)
{
this->window = std::make_unique<windowing::BlackmanWindow<TypeParam>>();
windowing::BlackmanWindow<TypeParam> window;

struct TestCase
{
Expand All @@ -85,7 +84,7 @@ TYPED_TEST(WindowingTest, BlackmanWindowValues)

for (const auto& testCase : testCases)
{
TypeParam result = (*this->window)(testCase.n, testCase.order);
TypeParam result = window(testCase.n, testCase.order);
EXPECT_NEAR(math::ToFloat(result), testCase.expected, this->kEpsilon)
<< "Failed for n=" << testCase.n
<< ", order=" << testCase.order;
Expand All @@ -94,26 +93,26 @@ TYPED_TEST(WindowingTest, BlackmanWindowValues)

TYPED_TEST(WindowingTest, RectangularWindowValues)
{
this->window = std::make_unique<windowing::RectangularWindow<TypeParam>>();
windowing::RectangularWindow<TypeParam> window;

for (std::size_t n = 0; n <= 8; n += 2)
{
TypeParam result = (*this->window)(n, 8);
TypeParam result = window(n, 8);
EXPECT_NEAR(math::ToFloat(result), 0.999f, this->kEpsilon)
<< "Failed for n=" << n;
}
}

TYPED_TEST(WindowingTest, WindowSymmetry)
{
std::vector<std::unique_ptr<windowing::Window<TypeParam>>> windows;
windows.push_back(std::make_unique<windowing::HammingWindow<TypeParam>>());
windows.push_back(std::make_unique<windowing::HanningWindow<TypeParam>>());
windows.push_back(std::make_unique<windowing::BlackmanWindow<TypeParam>>());
windowing::HammingWindow<TypeParam> hamming;
windowing::HanningWindow<TypeParam> hanning;
windowing::BlackmanWindow<TypeParam> blackman;
std::array<windowing::Window<TypeParam>*, 3> windows{ &hamming, &hanning, &blackman };

const std::size_t order = 16;

for (const auto& window : windows)
for (auto* window : windows)
{
for (std::size_t n = 0; n < order / 2; ++n)
{
Expand Down
8 changes: 6 additions & 2 deletions numerical/controllers/implementations/Feedforward2Dof.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ namespace controllers
public:
static_assert(std::is_floating_point_v<T>, "Feedforward supports floating-point types");

virtual ~Feedforward() = default;
virtual T Evaluate(T reference) = 0;
virtual void Reset() = 0;

protected:
~Feedforward() = default;
};

template<typename T>
Expand All @@ -23,9 +25,11 @@ namespace controllers
public:
static_assert(std::is_floating_point_v<T>, "FeedbackLaw supports floating-point types");

virtual ~FeedbackLaw() = default;
virtual T Process(T error) = 0;
virtual void Reset() = 0;

protected:
~FeedbackLaw() = default;
};

template<typename T>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ namespace controllers
using MeasurementVector = math::Vector<T, MeasurementSize>;
using InputVector = math::Vector<T, InputSize>;

virtual ~OutputFeedbackController() = default;
virtual InputVector ComputeControl(const MeasurementVector& measurement) = 0;

protected:
~OutputFeedbackController() = default;
};
}
7 changes: 6 additions & 1 deletion numerical/controllers/interfaces/PidController.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ namespace controllers
"Pid can only be instantiated with math::QNumber types.");

public:
virtual ~AsynchronousPidController() = default;
virtual void Reset() = 0;
virtual void SetTunings(PidTunings<QNumberType> tunings) = 0;
virtual void SetPoint(QNumberType setPoint) = 0;

protected:
~AsynchronousPidController() = default;
};

template<typename QNumberType>
Expand All @@ -38,5 +40,8 @@ namespace controllers
{
public:
virtual QNumberType Process(QNumberType processVariable) = 0;

protected:
~SynchronousPidController() = default;
};
}
4 changes: 3 additions & 1 deletion numerical/controllers/interfaces/PidDriver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ namespace controllers
class PidDriver
{
public:
virtual ~PidDriver() = default;
virtual void Read(const infra::Function<void(QNumberType)>& onDone) = 0;
virtual void ControlAction(QNumberType) = 0;
virtual void Start(std::chrono::system_clock::duration sampleTime) = 0;
virtual void Stop() = 0;

protected:
~PidDriver() = default;
};
}
4 changes: 3 additions & 1 deletion numerical/controllers/interfaces/StateFeedbackController.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ namespace controllers
using StateVector = math::Vector<T, StateSize>;
using InputVector = math::Vector<T, InputSize>;

virtual ~StateFeedbackController() = default;
virtual InputVector ComputeControl(const StateVector& state) = 0;

protected:
~StateFeedbackController() = default;
};
}
10 changes: 6 additions & 4 deletions numerical/estimators/Estimator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ namespace estimators
"OfflineEstimator only supports float or QNumber types");

public:
virtual ~OfflineEstimator() = default;

using CoefficientsMatrix = math::Matrix<T, Features + 1, 1>;
using DesignMatrix = math::Matrix<T, Features + 1, Features + 1>;
using InputMatrix = math::Matrix<T, Features, 1>;

virtual bool Fit(const math::Matrix<T, Samples, Features>& X, const math::Matrix<T, Samples, 1>& y) = 0;
virtual T Predict(const InputMatrix& X) const = 0;
virtual const CoefficientsMatrix& Coefficients() const = 0;

protected:
~OfflineEstimator() = default;
};

template<typename T, std::size_t Features>
Expand All @@ -29,8 +30,6 @@ namespace estimators
"OnlineEstimator only supports float or QNumber types");

public:
virtual ~OnlineEstimator() = default;

using CoefficientsMatrix = math::Matrix<T, Features, 1>;
using DesignMatrix = math::Matrix<T, Features, Features>;
using InputMatrix = math::Matrix<T, Features, 1>;
Expand All @@ -44,5 +43,8 @@ namespace estimators

virtual EstimationMetrics Update(const InputMatrix& x, const math::Matrix<T, 1, 1>& y) = 0;
virtual const CoefficientsMatrix& Coefficients() const = 0;

protected:
~OnlineEstimator() = default;
};
}
5 changes: 3 additions & 2 deletions numerical/nonlinear_control/BacksteppingControl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ namespace nonlinear_control
public:
using StateVector = std::array<T, Order>;

virtual ~StrictFeedbackModel() = default;

virtual T Drift(std::size_t i, const StateVector& x) const = 0;
virtual T Gain(std::size_t i, const StateVector& x) const = 0;
virtual T VirtualDerivative(std::size_t i, const StateVector& x, T alpha) const = 0;

protected:
~StrictFeedbackModel() = default;
};

template<typename T, std::size_t Order>
Expand Down
5 changes: 3 additions & 2 deletions numerical/nonlinear_control/FeedbackLinearization.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ namespace nonlinear_control
using StateVector = math::Vector<T, Dim>;
using DecouplingMatrix = math::SquareMatrix<T, Dim>;

virtual ~ControlAffineModel() = default;

[[nodiscard]] virtual DecouplingMatrix DecouplingMatrixAt(const StateVector& x) const = 0;
[[nodiscard]] virtual StateVector DriftTerm(const StateVector& x) const = 0;

protected:
~ControlAffineModel() = default;
};

template<typename T, std::size_t Dim>
Expand Down
4 changes: 3 additions & 1 deletion numerical/optimization/BayesianOptimization.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ namespace optimization
class BlackBoxObjective
{
public:
virtual ~BlackBoxObjective() = default;
virtual float Evaluate(const ParameterVector& params) = 0;

protected:
~BlackBoxObjective() = default;
};

BayesianOptimization(const BoundsArray& bounds,
Expand Down
5 changes: 3 additions & 2 deletions numerical/optimization/DampingPolicy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ namespace optimization
static_assert(std::is_floating_point_v<T>, "DampingPolicy supports floating-point types only");

public:
virtual ~DampingPolicy() = default;

[[nodiscard]] virtual T DampingSquared(const math::Matrix<T, Residuals, Parameters>& jacobian) const = 0;

protected:
~DampingPolicy() = default;
};

template<typename T, std::size_t Residuals, std::size_t Parameters>
Expand Down
5 changes: 3 additions & 2 deletions numerical/optimization/LeastSquaresProblem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ namespace optimization
static_assert(std::is_floating_point_v<T>, "LeastSquaresProblem supports floating-point types only");

public:
virtual ~LeastSquaresProblem() = default;

[[nodiscard]] virtual math::Vector<T, Residuals> Residual(const State& x) const = 0;
[[nodiscard]] virtual math::Matrix<T, Residuals, Parameters> Jacobian(const State& x) const = 0;
[[nodiscard]] virtual State Retract(const State& x, const math::Vector<T, Parameters>& delta) const = 0;

protected:
~LeastSquaresProblem() = default;
};
}
4 changes: 3 additions & 1 deletion numerical/optimization/ObjectiveFunction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ namespace optimization

virtual QNumberType Cost(const Vector& parameters) = 0;
virtual Vector Gradient(const Vector& parameters) = 0;
virtual ~ObjectiveFunction() = default;

protected:
~ObjectiveFunction() = default;
};
}
Loading
Loading