akreon-control
Optimal and robust control and state estimation in C++26.
Loading...
Searching...
No Matches
h2_state_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"
11#include "akreon/convex/composed/trace.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
81template <ContinuousPlantModelLike ObsModel>
84 using Model = linalg::remove_cvref_t<ObsModel>;
85
87
88 static constexpr std::size_t StateDim = plant_traits<Model>::StateDim;
89 static constexpr std::size_t ExogenousDim = plant_traits<Model>::ExogenousDim;
90 static constexpr std::size_t MeasurementDim =
92 static constexpr std::size_t PerformanceDim =
94
95 using QMat = linalg::Matrix<T, StateDim, StateDim>;
96 using WMat = linalg::Matrix<T, StateDim, MeasurementDim>;
97 using LMat = linalg::Matrix<T, StateDim, MeasurementDim>;
98 using ZMat = linalg::Matrix<T, ExogenousDim, ExogenousDim>;
99
100 static constexpr std::size_t QDim = StateDim * (StateDim + 1) / 2;
101 static constexpr std::size_t WDim = StateDim * MeasurementDim;
102 static constexpr std::size_t ZDim = ExogenousDim * (ExogenousDim + 1) / 2;
103 static constexpr std::size_t DecVarDim = QDim + WDim + ZDim;
104
105 using DecVarSpace = linalg::RowVector<T, DecVarDim>;
106
115 template <convex::AtomLike DecVar>
116 constexpr auto operator()(const ObsModel& model, const DecVar& Z_hat,
117 T margin = 1e-6) const {
118 return apply(model, Z_hat, margin);
119 }
120
129 template <convex::AtomLike DecVar>
130 constexpr auto apply(const ObsModel& model, const DecVar& Z_hat,
131 T margin = 1e-6) const {
132 static_assert(convex::is_var_type_v<DecVar>, "Input atom is not a var");
133 using DecVarDom = convex::meta_dom_t<convex::atom_meta_t<DecVar>>;
134 static_assert(convex::dom_vartype_v<DecVarDom> == convex::VarType::FREE,
135 "Merged decision var must be free");
136 static_assert(std::same_as<DecVarSpace, convex::dom_space_t<DecVarDom>>,
137 "Expected merged decision size [svec(Q); vec(W); svec(Z)]");
138
140 const auto z_lmi = z_constraint(model, Z_hat, margin);
141 return convex::Intersection(z_lmi, stability_lmi);
142 }
143
155 template <convex::AtomLike DecVar>
156 constexpr auto stability_constraint(const ObsModel& model,
157 const DecVar& Z_hat) const {
158 using FMat = QMat;
159 using Fs = linalg::Vector<FMat, DecVarDim>;
160
161 auto M_q = [&](const QMat& E) -> FMat {
162 const auto& A = model.dynamics_matrix();
163 return T(-1) * (linalg::transpose(A) * E + E * A);
164 };
165
166 auto M_w = [&](const WMat& E) -> FMat {
167 const auto& C = model.measurement_matrix();
168 return linalg::transpose(C) * linalg::transpose(E) + E * C;
169 };
170
171 const auto C_e = model.performance_matrix();
172 const auto F_0 = T(-1) * (linalg::transpose(C_e) * C_e);
173
174 const auto F_q_local =
175 linalg::MatrixPencil<FMat, QDim>{}.template apply_sym_mat<QMat>(M_q);
176 const auto F_w_local =
177 linalg::MatrixPencil<FMat, WDim>{}.template apply_full<WMat>(M_w);
178
179 Fs F_blks{};
180 for (std::size_t i = 0; i < QDim; ++i) F_blks(i) = F_q_local(i);
181 for (std::size_t i = 0; i < WDim; ++i) F_blks(QDim + i) = F_w_local(i);
182
183 return convex::LMI(F_blks, F_0, Z_hat);
184 }
185
203 template <convex::AtomLike DecVar>
204 constexpr auto z_constraint(const ObsModel& model, const DecVar& Z_hat,
205 T margin = 1e-6) const {
206 constexpr std::size_t LmiDim = ExogenousDim + StateDim;
207 using FMat = linalg::Matrix<T, LmiDim, LmiDim>;
208 using Fs = linalg::Vector<FMat, DecVarDim>;
209 auto M_z = [](const ZMat& E) -> FMat {
210 FMat F{};
211 return linalg::set_block<0, ExogenousDim, 0, ExogenousDim>(F, E);
212 };
213 auto M_q = [&](const QMat& E) -> FMat {
214 FMat F{};
215 const auto B_w = model.exogenous_matrix();
216 F = linalg::set_block<ExogenousDim, LmiDim, ExogenousDim, LmiDim>(F, E);
217 F = linalg::set_block<0, ExogenousDim, ExogenousDim, LmiDim>(
218 F, linalg::transpose(B_w) * E);
219 F = linalg::set_block<ExogenousDim, LmiDim, 0, ExogenousDim>(F, E * B_w);
220 return F;
221 };
222 auto M_w = [&](const WMat& E) -> FMat {
223 FMat F{};
224 const auto D_w = model.template feedthrough<PortRole::MEASUREMENT,
226 F = linalg::set_block<0, ExogenousDim, ExogenousDim, LmiDim>(
227 F, T(-1) * linalg::transpose(D_w) * linalg::transpose(E));
228 F = linalg::set_block<ExogenousDim, LmiDim, 0, ExogenousDim>(
229 F, T(-1) * E * D_w);
230 return F;
231 };
232
233 const auto F_z_local =
234 linalg::MatrixPencil<FMat, ZDim>{}.template apply_sym_mat<ZMat>(M_z);
235 const auto F_q_local =
236 linalg::MatrixPencil<FMat, QDim>{}.template apply_sym_mat<QMat>(M_q);
237 const auto F_w_local =
238 linalg::MatrixPencil<FMat, WDim>{}.template apply_full<WMat>(M_w);
239
240 Fs F_blks{};
241 for (std::size_t i = 0; i < QDim; ++i) F_blks(i) = F_q_local(i);
242 for (std::size_t i = 0; i < WDim; ++i) F_blks(QDim + i) = F_w_local(i);
243 for (std::size_t i = 0; i < ZDim; ++i)
244 F_blks(QDim + WDim + i) = F_z_local(i);
245
246 const auto I = linalg::make_identity<T, LmiDim>();
247 return convex::LMI(F_blks, T(-1) * margin * I, Z_hat);
248 }
249
259 template <convex::AtomLike DecVar>
260 constexpr auto objective(const DecVar& Z_hat) const {
261 return convex::make_trace_block<QDim + WDim, linalg::SymmetricMatrix<ZMat>>(
262 Z_hat);
263 }
264
275 constexpr auto L(const QMat& Q, const WMat& W) const {
277 }
278};
279
289template <ContinuousPlantModelLike ObsModel, convex::AtomLike DecVar>
291 const ObsModel& model, const DecVar& Z_hat,
294 const auto lmis = h2(model, Z_hat, margin);
295 const auto prob = convex::make_problem(Z_hat).in_set(lmis);
296 const auto obj = h2.objective(Z_hat);
297 return prob.minimize(obj);
298}
299
300} // namespace control
constexpr bool is_continuous_v
check if continuous or not
@ EXOGENOUS
w: disturbance, reference, noise, or other external input
@ MEASUREMENT
y: measured output available for feedback/estimation
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 > >::T plant_type_t
extract scalar type from plant
constexpr auto make_h2_state_observer_problem(const ObsModel &model, const DecVar &Z_hat, plant_type_t< ObsModel > margin=1e-6)
make h2 state observer problem
static constexpr std::size_t PerformanceDim
constexpr auto z_constraint(const ObsModel &model, const DecVar &Z_hat, T margin=1e-6) const
z_constraint
constexpr auto objective(const DecVar &Z_hat) const
objective
linalg::RowVector< T, DecVarDim > DecVarSpace
constexpr auto stability_constraint(const ObsModel &model, const DecVar &Z_hat) const
stability_constraint
constexpr auto apply(const ObsModel &model, const DecVar &Z_hat, T margin=1e-6) const
apply
linalg::Matrix< T, StateDim, MeasurementDim > LMat
static constexpr std::size_t ZDim
static constexpr std::size_t MeasurementDim
linalg::Matrix< T, ExogenousDim, ExogenousDim > ZMat
plant_type_t< Model > T
static constexpr std::size_t WDim
static constexpr std::size_t QDim
static constexpr std::size_t StateDim
linalg::Matrix< T, StateDim, StateDim > QMat
constexpr auto operator()(const ObsModel &model, const DecVar &Z_hat, T margin=1e-6) const
operator overload to form H2 state observer
static constexpr std::size_t ExogenousDim
linalg::Matrix< T, StateDim, MeasurementDim > WMat
constexpr auto L(const QMat &Q, const WMat &W) const
L.
linalg::remove_cvref_t< ObsModel > Model
extracting alias and types
static constexpr std::size_t DecVarDim
base type traits for plant model