akreon-control
Optimal and robust control and state estimation in C++26.
Loading...
Searching...
No Matches
luenberger_observer.hpp
Go to the documentation of this file.
1/*
2 * Copyright © 2025-2026 Akreon LLC
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6#pragma once
7
8#include "akreon/linalg/assert.hpp"
12#include "akreon/convex/convex_problem.hpp"
13#include "akreon/convex/intersection.hpp"
14#include "akreon/convex/sets/lmi.hpp"
15#include "akreon/convex/variable.hpp"
16#include "akreon/linalg/operator/matrix_inverse.hpp"
17#include "akreon/linalg/operator/matrix_pencil.hpp"
18
19namespace control {
20
43template <ContinuousPlantModelLike ObsModel>
46 using Model = linalg::remove_cvref_t<ObsModel>;
47
50
51 static constexpr std::size_t StateDim = plant_traits<Model>::StateDim;
52 static constexpr std::size_t OutputDim = plant_traits<Model>::MeasurementDim;
53
54 using QMat = linalg::Matrix<T, StateDim, StateDim>;
55 using WMat = linalg::Matrix<T, StateDim, OutputDim>;
56 using LMat = linalg::Matrix<T, StateDim, OutputDim>;
57 using FMat = QMat;
58
59 static constexpr std::size_t QDim = StateDim * (StateDim + 1) / 2;
60 static constexpr std::size_t WDim = StateDim * OutputDim;
61 static constexpr std::size_t ZDim = QDim + WDim;
62
63 using ZSpace = linalg::RowVector<T, ZDim>;
64 using Fs = linalg::Vector<FMat, ZDim>;
65
74 template <convex::AtomLike ZVar>
75 constexpr auto operator()(const ObsModel& model, const ZVar& Z,
76 T margin = 1e-6) const {
77 return apply(model, Z, margin);
78 }
79
88 template <convex::AtomLike ZVar>
89 constexpr auto apply(const ObsModel& model, const ZVar& Z,
90 T margin = 1e-6) const {
91 static_assert(convex::is_var_type_v<ZVar>, "Input atom is not a var");
92 using ZDom = convex::meta_dom_t<convex::atom_meta_t<ZVar>>;
93 static_assert(convex::dom_vartype_v<ZDom> == convex::VarType::FREE,
94 "Merged decision var must be free");
95 using ZVarSpace = convex::dom_space_t<ZDom>;
96 static_assert(std::same_as<ZVarSpace, ZSpace>,
97 "Expected merged decision size [svec(Q); vec(W)]");
98
100 const auto q_lmi = q_constraint(Z, margin);
101 return convex::Intersection(q_lmi, stability_lmi);
102 }
103
112 template <convex::AtomLike ZVar>
113 constexpr auto q_constraint(const ZVar& Z, T margin = 1e-6) const {
114 const auto I = linalg::make_identity<T, StateDim>();
115 const auto Fq =
116 linalg::MatrixPencil<QMat, QDim>{}.template apply_sym_mat<QMat>(
117 [](const QMat& E) -> QMat { return E; });
118
119 Fs F_blks{};
120 for (std::size_t i = 0; i < QDim; ++i) F_blks(i) = Fq(i);
121 for (std::size_t i = 0; i < WDim; ++i) F_blks(QDim + i) = QMat{};
122
123 return convex::LMI(F_blks, T(-1) * margin * I, Z);
124 }
125
134 template <convex::AtomLike ZVar>
135 constexpr auto stability_constraint(const ObsModel& model,
136 const ZVar& Z) const {
137 auto M_q = [&](const QMat& E) -> FMat {
138 const auto& A = model.dynamics_matrix();
139 return T(-1) * (linalg::transpose(A) * E + E * A);
140 };
141
142 auto M_w = [&](const WMat& E) -> FMat {
143 const auto C_y = model.measurement_matrix();
144 return E * C_y + linalg::transpose(C_y) * linalg::transpose(E);
145 };
146
147 const auto Fq_local =
148 linalg::MatrixPencil<FMat, QDim>{}.template apply_sym_mat<QMat>(M_q);
149 const auto Fw_local =
150 linalg::MatrixPencil<FMat, WDim>{}.template apply_full<WMat>(M_w);
151
152 Fs F_blks{};
153 for (std::size_t i = 0; i < QDim; ++i) F_blks(i) = Fq_local(i);
154 for (std::size_t i = 0; i < WDim; ++i) F_blks(QDim + i) = Fw_local(i);
155
156 return convex::LMI(F_blks, FMat{}, Z);
157 }
158
169 constexpr auto L(const QMat& Q, const WMat& W) const {
171 }
172};
173
184template <ContinuousPlantModelLike ObsModel, convex::AtomLike ZVar>
185constexpr auto make_observer_problem(const ObsModel& model, const ZVar& Z,
186 const auto& obj,
189 const auto constraints = obs(model, Z, margin);
190
191 const auto prob_constraints = convex::make_problem(Z).in_set(constraints);
192
193 return prob_constraints.minimize(obj);
194}
195
205template <ContinuousPlantModelLike ObsModel, convex::AtomLike ZVar>
206constexpr auto make_observer_problem(const ObsModel& model, const ZVar& Z,
209 const auto constraints = obs(model, Z, margin);
210
211 const auto prob_constraints = convex::make_problem(Z).in_set(constraints);
212
213 return prob_constraints.feasibility();
214}
215
216} // namespace control
constexpr bool is_continuous_v
check if continuous or not
constexpr auto make_observer_problem(const ObsModel &model, const ZVar &Z, const auto &obj, plant_type_t< ObsModel > margin=1e-6)
make observer problem
constexpr auto recover_state_observer_gain(const QMat &Q, const WMat &W)
recover state-observer gain from a lifted gain variable
typename plant_traits< linalg::remove_cvref_t< Plant > >::State plant_state_vec_t
extract state vector from plant
typename plant_traits< linalg::remove_cvref_t< Plant > >::T plant_type_t
extract scalar type from plant
constexpr auto apply(const ObsModel &model, const ZVar &Z, T margin=1e-6) const
apply
linalg::Matrix< T, StateDim, OutputDim > LMat
static constexpr std::size_t ZDim
static constexpr std::size_t StateDim
static constexpr std::size_t WDim
linalg::Vector< FMat, ZDim > Fs
linalg::RowVector< T, ZDim > ZSpace
constexpr auto q_constraint(const ZVar &Z, T margin=1e-6) const
q_constraint
static constexpr std::size_t QDim
constexpr auto stability_constraint(const ObsModel &model, const ZVar &Z) const
stability_constraint
static constexpr std::size_t OutputDim
linalg::Matrix< T, StateDim, OutputDim > WMat
constexpr auto L(const QMat &Q, const WMat &W) const
L.
linalg::Matrix< T, StateDim, StateDim > QMat
plant_state_vec_t< Model > State
constexpr auto operator()(const ObsModel &model, const ZVar &Z, T margin=1e-6) const
operator overload (form luenberger observer)
linalg::remove_cvref_t< ObsModel > Model
extracting alias and types
base type traits for plant model