22#include <janus/math/AutoDiff.hpp>
115 if (symbolic_enabled) {
116 return std::make_unique<SymbolicLinearizer>();
118 return std::make_unique<FiniteDifferenceLinearizer>();
131inline ::icarus::staging::LinearModel
138 const int nx =
static_cast<int>(config.
states.size());
139 const int nu =
static_cast<int>(config.
inputs.size());
140 const int ny =
static_cast<int>(config.
outputs.size());
141 const double h = opts_.step_size;
142 const double t = sim.
Time();
145 throw ConfigError(
"Linearization requires at least one state");
150 for (
int i = 0; i < nx; ++i) {
154 for (
int i = 0; i < nu; ++i) {
159 Eigen::VectorXd state_backup = sim.
GetState();
160 const auto n_states_total = state_backup.size();
164 std::vector<int> state_indices;
165 state_indices.reserve(nx);
168 for (
const auto &state_name : config.
states) {
171 Eigen::Index current_offset = 0;
172 for (
const auto &comp_entry : dict.components) {
174 if (state_name.rfind(comp_entry.name +
".", 0) == 0) {
178 std::string local_state = state_name.substr(comp_entry.name.size() + 1);
181 std::size_t comp_state_size = 0;
182 std::vector<std::string> state_like_outputs;
183 for (
const auto &output : comp_entry.outputs) {
185 std::string local = output.name.substr(comp_entry.name.size() + 1);
186 if (local.find(
"position") != std::string::npos ||
187 local.find(
"velocity") != std::string::npos ||
188 local.find(
"attitude") != std::string::npos ||
189 local.find(
"omega") != std::string::npos ||
190 local.find(
".x") != std::string::npos ||
191 local.find(
".y") != std::string::npos ||
192 local.find(
".z") != std::string::npos) {
193 state_like_outputs.push_back(local);
199 for (std::size_t i = 0; i < state_like_outputs.size(); ++i) {
200 if (local_state == state_like_outputs[i]) {
201 state_indices.push_back(
static_cast<int>(current_offset + i));
211 std::size_t estimated_state_size = 0;
212 for (
const auto &output : comp_entry.outputs) {
213 std::string local = output.name.substr(comp_entry.name.size() + 1);
214 if (local.find(
"position") != std::string::npos ||
215 local.find(
"velocity") != std::string::npos) {
216 estimated_state_size = 6;
220 current_offset +=
static_cast<Eigen::Index
>(estimated_state_size);
227 state_indices.push_back(
static_cast<int>(state_indices.size()));
233 bool mapping_failed =
false;
234 for (
int i = 0; i < nx; ++i) {
235 if (state_indices[i] >=
static_cast<int>(n_states_total)) {
236 mapping_failed =
true;
240 if (mapping_failed) {
243 "[LIN] FiniteDifferenceLinearizer: State index mapping produced out-of-bounds indices. "
244 "Falling back to sequential indices. This may produce incorrect Jacobian results.");
246 if (n_states_total <
static_cast<Eigen::Index
>(nx)) {
247 throw ConfigError(
"FiniteDifferenceLinearizer: Cannot linearize - simulator has only " +
248 std::to_string(n_states_total) +
249 " states but linearization config requires " + std::to_string(nx) +
252 state_indices.clear();
253 for (
int i = 0; i < nx; ++i) {
254 state_indices.push_back(i);
259 model.
A.resize(nx, nx);
261 for (
int j = 0; j < nx; ++j) {
262 int full_idx = state_indices[j];
264 Eigen::VectorXd state_plus = state_backup;
265 Eigen::VectorXd state_minus = state_backup;
267 state_plus(full_idx) += h;
268 state_minus(full_idx) -= h;
279 Eigen::VectorXd col(nx);
280 for (
int i = 0; i < nx; ++i) {
281 int row_idx = state_indices[i];
282 col(i) = (xdot_plus(row_idx) - xdot_minus(row_idx)) / (2.0 * h);
284 model.
A.col(j) = col;
291 model.
B.resize(nx, nu);
292 for (
int j = 0; j < nu; ++j) {
304 Eigen::VectorXd col(nx);
305 for (
int i = 0; i < nx; ++i) {
306 int row_idx = state_indices[i];
307 col(i) = (xdot_plus(row_idx) - xdot_minus(row_idx)) / (2.0 * h);
309 model.
B.col(j) = col;
317 Eigen::VectorXd y(ny);
318 for (
int i = 0; i < ny; ++i) {
325 model.
C.resize(ny, nx);
327 for (
int j = 0; j < nx; ++j) {
328 int full_idx = state_indices[j];
330 Eigen::VectorXd state_plus = state_backup;
331 Eigen::VectorXd state_minus = state_backup;
333 state_plus(full_idx) += h;
334 state_minus(full_idx) -= h;
339 Eigen::VectorXd y_plus = get_y();
344 Eigen::VectorXd y_minus = get_y();
347 model.
C.col(j) = (y_plus - y_minus) / (2.0 * h);
355 model.
D.resize(ny, nu);
356 if (ny > 0 && nu > 0) {
357 for (
int j = 0; j < nu; ++j) {
363 Eigen::VectorXd y_plus = get_y();
368 Eigen::VectorXd y_minus = get_y();
371 model.
D.col(j) = (y_plus - y_minus) / (2.0 * h);
388inline ::icarus::staging::LinearModel
390 using Scalar = janus::SymbolicScalar;
397 const int nx =
static_cast<int>(config.
states.size());
398 const int nu =
static_cast<int>(config.
inputs.size());
399 const int ny =
static_cast<int>(config.
outputs.size());
402 throw ConfigError(
"SymbolicLinearizer: requires at least one state");
408 for (
int i = 0; i < nx; ++i) {
412 for (
int i = 0; i < nu; ++i) {
419 const std::size_t n_states_total = sym_sim.
GetStateSize();
422 auto [x_vec, x_mx] = janus::sym_vec_pair(
"x",
static_cast<int>(n_states_total));
423 Scalar t_sym = janus::sym(
"t");
426 std::vector<Scalar> u_elements;
429 auto [u_v, u_m] = janus::sym_vec_pair(
"u", nu);
430 for (
int i = 0; i < nu; ++i) {
431 u_elements.push_back(u_v(i));
441 for (
int i = 0; i < nu; ++i) {
449 std::vector<int> state_indices;
450 state_indices.reserve(nx);
452 for (
int j = 0; j < nx; ++j) {
454 for (std::size_t bi = 0; bi < bindings.size(); ++bi) {
455 if (config.
states[j] == bindings[bi].name) {
456 state_indices.push_back(
static_cast<int>(bi));
467 if (state_indices.size() !=
static_cast<std::size_t
>(nx)) {
472 "[LIN] SymbolicLinearizer: Could not match " +
473 std::to_string(nx -
static_cast<int>(state_indices.size())) +
474 " state name(s) to state layout. Falling back to index-based mapping. "
475 "This may produce incorrect Jacobian results.");
477 if (n_states_total <
static_cast<std::size_t
>(nx)) {
479 "SymbolicLinearizer: Cannot linearize - symbolic simulator has only " +
480 std::to_string(n_states_total) +
" states but linearization config requires " +
481 std::to_string(nx) +
" states.");
483 state_indices.clear();
484 for (
int i = 0; i < nx; ++i) {
485 state_indices.push_back(i);
490 std::vector<Scalar> xdot_selected;
491 xdot_selected.reserve(nx);
492 for (
int idx : state_indices) {
493 xdot_selected.push_back(xdot_vec(idx));
495 Scalar xdot_mx = Scalar::vertcat(xdot_selected);
499 Scalar A_full_sym = janus::jacobian({xdot_mx}, {x_mx});
504 B_sym = janus::jacobian({xdot_mx}, {u_mx});
508 std::vector<Scalar> y_elements;
509 y_elements.reserve(ny);
510 for (
const auto &output_name : config.
outputs) {
512 y_elements.push_back(sym_sim.
GetSignal(output_name));
514 throw ConfigError(
"SymbolicLinearizer: Unknown output signal '" + output_name +
"'");
518 Scalar C_full_sym, D_sym;
520 Scalar y_mx = Scalar::vertcat(y_elements);
523 C_full_sym = janus::jacobian({y_mx}, {x_mx});
527 D_sym = janus::jacobian({y_mx}, {u_mx});
532 std::vector<janus::SymbolicArg> all_inputs;
533 all_inputs.push_back(t_sym);
534 all_inputs.push_back(x_mx);
536 all_inputs.push_back(u_mx);
539 janus::Function A_full_func(
"A_full", all_inputs, {A_full_sym});
544 Eigen::VectorXd full_state = Eigen::VectorXd::Zero(
static_cast<Eigen::Index
>(n_states_total));
547 Eigen::VectorXd sim_state = sim.
GetState();
548 for (Eigen::Index k = 0;
549 k < std::min(sim_state.size(),
static_cast<Eigen::Index
>(n_states_total)); ++k) {
550 full_state(k) = sim_state(k);
554 for (
int j = 0; j < nx; ++j) {
555 int k = state_indices[j];
556 full_state(k) = model.
x0(j);
561 std::vector<Eigen::MatrixXd> A_full_result;
563 A_full_result = A_full_func(model.
t0, full_state, model.
u0);
565 A_full_result = A_full_func(model.
t0, full_state);
568 model.
A.resize(nx, nx);
569 for (
int i = 0; i < nx; ++i) {
570 for (
int j = 0; j < nx; ++j) {
571 int col_idx = state_indices[j];
572 model.
A(i, j) = A_full_result[0](i, col_idx);
578 janus::Function B_func(
"B", all_inputs, {B_sym});
579 auto B_result = B_func(model.
t0, full_state, model.
u0);
580 model.
B.resize(nx, nu);
581 for (
int i = 0; i < nx; ++i) {
582 for (
int j = 0; j < nu; ++j) {
583 model.
B(i, j) = B_result[0](i, j);
587 model.
B.resize(nx, 0);
592 janus::Function C_full_func(
"C_full", all_inputs, {C_full_sym});
593 std::vector<Eigen::MatrixXd> C_full_result;
595 C_full_result = C_full_func(model.
t0, full_state, model.
u0);
597 C_full_result = C_full_func(model.
t0, full_state);
599 model.
C.resize(ny, nx);
600 for (
int i = 0; i < ny; ++i) {
601 for (
int j = 0; j < nx; ++j) {
602 int col_idx = state_indices[j];
603 model.
C(i, j) = C_full_result[0](i, col_idx);
607 model.
C.resize(0, nx);
611 if (ny > 0 && nu > 0) {
612 janus::Function D_func(
"D", all_inputs, {D_sym});
613 auto D_result = D_func(model.
t0, full_state, model.
u0);
614 model.
D.resize(ny, nu);
615 for (
int i = 0; i < ny; ++i) {
616 for (
int j = 0; j < nu; ++j) {
617 model.
D(i, j) = D_result[0](i, j);
621 model.
D.resize(ny, nu);
Consolidated error handling for Icarus.
Simulator and subsystem configuration structs.
Top-level simulation coordinator.
Core types for staging subsystem (trim, linearization, symbolic).
Lightweight symbolic simulator for graph extraction.
Configuration/parsing errors with optional file context.
Definition Error.hpp:185
void Log(LogLevel level, const std::string &message)
Log raw message.
Definition MissionLogger.hpp:584
Top-level simulation coordinator.
Definition Simulator.hpp:70
double Peek(const std::string &name) const
Read a signal value by name.
Definition Simulator.hpp:190
Eigen::VectorXd GetState() const
Get current state vector.
Definition Simulator.hpp:1097
const SimulatorConfig & GetConfig() const
Get simulator configuration.
Definition Simulator.hpp:300
void Poke(const std::string &name, double value)
Write a signal value by name.
Definition Simulator.hpp:211
Eigen::VectorXd ComputeDerivatives(double t)
Compute derivatives for current state at time t.
Definition Simulator.hpp:1101
void SetState(const Eigen::VectorXd &X)
Set state vector.
Definition Simulator.hpp:1099
MissionLogger & GetLogger()
Get the mission logger.
Definition Simulator.hpp:216
double Time() const
Get current simulation time (MET - derived from epoch).
Definition Simulator.hpp:143
DataDictionary GetDataDictionary() const
Get data dictionary for the simulation.
Definition Simulator.hpp:1191
FiniteDifferenceLinearizer()
Definition Linearizer.hpp:81
FiniteDifferenceLinearizer(Options opts)
Definition Linearizer.hpp:82
::icarus::staging::LinearModel Compute(::icarus::Simulator &sim, const LinearizationConfig &config) override
Compute linear model at current operating point.
Definition Linearizer.hpp:132
Abstract linearizer interface.
Definition Linearizer.hpp:46
virtual ~Linearizer()=default
virtual::icarus::staging::LinearModel Compute(::icarus::Simulator &sim, const LinearizationConfig &config)=0
Compute linear model at current operating point.
Linearizer using symbolic Jacobians.
Definition Linearizer.hpp:101
::icarus::staging::LinearModel Compute(::icarus::Simulator &sim, const LinearizationConfig &config) override
Compute linear model at current operating point.
Definition Linearizer.hpp:389
Lightweight symbolic simulator for graph extraction.
Definition SymbolicSimulatorCore.hpp:48
JanusVector< Scalar > ComputeDerivatives()
Compute derivatives symbolically.
Definition SymbolicSimulatorCore.hpp:101
void SetTime(Scalar t)
Set time (symbolic).
Definition SymbolicSimulatorCore.hpp:90
bool HasSignal(const std::string &name) const
Check if signal exists.
Definition SymbolicSimulatorCore.hpp:182
const std::vector< StateBinding< Scalar > > & GetStateBindings() const
Get state bindings (for introspection).
Definition SymbolicSimulatorCore.hpp:196
void SetState(const JanusVector< Scalar > &x)
Set state vector (symbolic).
Definition SymbolicSimulatorCore.hpp:80
void SetSignal(const std::string &name, Scalar value)
Write signal value (symbolic).
Definition SymbolicSimulatorCore.hpp:177
Scalar GetSignal(const std::string &name) const
Read signal value (symbolic).
Definition SymbolicSimulatorCore.hpp:170
std::size_t GetStateSize() const
Get total state size.
Definition SymbolicSimulatorCore.hpp:149
Definition Simulator.hpp:53
std::unique_ptr< Linearizer > CreateLinearizer(bool symbolic_enabled)
Create appropriate linearizer based on configuration.
Definition Linearizer.hpp:114
Definition AggregationTypes.hpp:13
@ Warning
Potential issues.
Definition Console.hpp:40
Linearization configuration.
Definition SimulatorConfig.hpp:200
std::vector< std::string > states
State variables (for A matrix rows/cols).
Definition SimulatorConfig.hpp:204
std::vector< std::string > inputs
Input variables (for B matrix cols).
Definition SimulatorConfig.hpp:207
std::vector< std::string > outputs
Output variables (for C matrix rows).
Definition SimulatorConfig.hpp:210
Definition Linearizer.hpp:77
double step_size
Finite difference step size.
Definition Linearizer.hpp:78
Linear state-space model.
Definition LinearModel.hpp:31
std::vector< std::string > state_names
Definition LinearModel.hpp:37
Eigen::MatrixXd B
Input matrix (n_states x n_inputs).
Definition LinearModel.hpp:33
double t0
Definition LinearModel.hpp:44
Eigen::MatrixXd D
Feedthrough matrix (n_outputs x n_inputs).
Definition LinearModel.hpp:35
Eigen::MatrixXd C
Output matrix (n_outputs x n_states).
Definition LinearModel.hpp:34
Eigen::MatrixXd A
State matrix (n_states x n_states).
Definition LinearModel.hpp:32
Eigen::VectorXd u0
Definition LinearModel.hpp:43
Eigen::VectorXd x0
Operating point where linearization was performed.
Definition LinearModel.hpp:42
std::vector< std::string > input_names
Definition LinearModel.hpp:38
std::vector< std::string > output_names
Definition LinearModel.hpp:39