akreon-control
Optimal and robust control and state estimation in C++26.
Loading...
Searching...
No Matches
discretization.hpp
Go to the documentation of this file.
1/*
2 * Copyright © 2026 Akreon LLC
3 * SPDX-License-Identifier: Apache-2.0
4 */
5#pragma once
6
8#include "akreon/linalg/operator/matrix_inverse.hpp"
9#include "akreon/linalg/spectral/matrix_exp.hpp"
10
11#include <algorithm>
12#include <cmath>
13#include <limits>
14#include <stdexcept>
15
16namespace control {
36template <ContinuousStateSpaceLike SS>
37 requires linalg::Real<ss_type_t<SS>>
38constexpr auto c2d_zoh(const SS& continuous, ss_type_t<SS> dt) {
39 using T = ss_type_t<SS>;
40 constexpr std::size_t N = linalg::mat_rows_v<ss_plant_t<SS>>;
41 constexpr std::size_t M = linalg::mat_cols_v<ss_control_t<SS>>;
42 static_assert(N > 0 && M > 0);
43 static_assert(std::same_as<T, linalg::matrix_scalar_t<ss_control_t<SS>>> &&
44 std::same_as<T, linalg::matrix_scalar_t<ss_output_t<SS>>>);
45 using Plant = linalg::Matrix<T, N, N>;
46 using Input = linalg::Matrix<T, N, M>;
47 using Discrete =
49 if (!std::isfinite(dt) || dt <= T(0))
50 throw std::invalid_argument(
51 "ZOH sample period must be finite and positive");
52 const auto check_finite = [](const auto& matrix) {
53 for (std::size_t i = 0; i < linalg::mat_rows_v<decltype(matrix)>; ++i)
54 for (std::size_t j = 0; j < linalg::mat_cols_v<decltype(matrix)>; ++j)
55 if (!std::isfinite(matrix(i, j)))
56 throw std::invalid_argument("ZOH model matrices must be finite");
57 };
62
63 linalg::Matrix<T, N + M, N + M> augmented{};
64 for (std::size_t i = 0; i < N; ++i) {
65 for (std::size_t j = 0; j < N; ++j)
66 augmented(i, j) = dt * continuous.A(i, j);
67 for (std::size_t j = 0; j < M; ++j)
68 augmented(i, N + j) = dt * continuous.B(i, j);
69 }
70 // Check for overflow in dt*A or dt*B before entering the eigensolver.
72 const auto exponential = linalg::matrix_exp(augmented);
73 constexpr T tolerance =
74 std::max(T(1e-8), T(100) * std::numeric_limits<T>::epsilon());
75 for (std::size_t i = 0; i < N + M; ++i) {
76 for (std::size_t j = 0; j < N + M; ++j) {
77 const auto value = exponential(i, j);
78 if (!std::isfinite(value.real()) || !std::isfinite(value.imag()) ||
79 linalg::constexpr_abs(value.imag()) >
80 tolerance * std::max(T(1), linalg::constexpr_abs(value.real())))
81 throw std::runtime_error(
82 "ZOH matrix exponential is nonfinite or nonreal");
83 if (i >= N) {
84 const T expected = i == j ? T(1) : T(0);
85 if (linalg::constexpr_abs(value.real() - expected) > tolerance)
86 throw std::runtime_error(
87 "ZOH matrix exponential has an invalid lower block");
88 }
89 }
90 }
91 Plant Ad{};
92 Input Bd{};
93 for (std::size_t i = 0; i < N; ++i) {
94 for (std::size_t j = 0; j < N; ++j) Ad(i, j) = exponential(i, j).real();
95 for (std::size_t j = 0; j < M; ++j) Bd(i, j) = exponential(i, N + j).real();
96 }
97 if (continuous.D) return Discrete(Ad, Bd, continuous.C, *continuous.D);
98 return Discrete(Ad, Bd, continuous.C);
99}
100} // namespace control
constexpr bool is_continuous_v
check if continuous or not
typename ss_traits< linalg::remove_cvref_t< StateSpace > >::T ss_type_t
extract scalar type from ss
constexpr auto c2d_zoh(const SS &continuous, ss_type_t< SS > dt)
Convert a real continuous state-space model using zero-order hold.
forward decl of state space