Icarus
Vehicle Simulation as a Transformable Computational Graph, built on Vulcan and Janus
Loading...
Searching...
No Matches
LinearModel.hpp
Go to the documentation of this file.
1#pragma once
2
9
11#include <icarus/core/Error.hpp>
12
13#include <Eigen/Dense>
14#include <cstdio>
15#include <fstream>
16#include <iomanip>
17#include <string>
18#include <vector>
19
20namespace icarus::staging {
21
32 Eigen::MatrixXd A;
33 Eigen::MatrixXd B;
34 Eigen::MatrixXd C;
35 Eigen::MatrixXd D;
36
37 std::vector<std::string> state_names;
38 std::vector<std::string> input_names;
39 std::vector<std::string> output_names;
40
42 Eigen::VectorXd x0;
43 Eigen::VectorXd u0;
44 double t0 = 0.0;
45
46 // =========================================================================
47 // Export Methods
48 // =========================================================================
49
56 void ExportMatlab(const std::string &path) const {
57 std::ofstream file(path);
58 if (!file.is_open()) {
59 throw IOError("Failed to open file for MATLAB export: " + path);
60 }
61
62 try {
63 // Enable exceptions for write errors
64 file.exceptions(std::ios::failbit | std::ios::badbit);
65
66 file << std::setprecision(15);
67 file << "% Linear state-space model\n";
68 file << "% Generated by Icarus " << icarus::Version() << "\n";
69 file << "% Operating point: t = " << t0 << "\n\n";
70
71 // Helper to write a matrix
72 auto write_matrix = [&](const std::string &name, const Eigen::MatrixXd &M) {
73 file << name << " = [\n";
74 for (Eigen::Index i = 0; i < M.rows(); ++i) {
75 file << " ";
76 for (Eigen::Index j = 0; j < M.cols(); ++j) {
77 file << M(i, j);
78 if (j < M.cols() - 1)
79 file << ", ";
80 }
81 file << ";\n";
82 }
83 file << "];\n\n";
84 };
85
86 write_matrix("A", A);
87 write_matrix("B", B);
88 write_matrix("C", C);
89 write_matrix("D", D);
90
91 // Create ss object
92 file << "sys = ss(A, B, C, D);\n\n";
93
94 // State names
95 if (!state_names.empty()) {
96 file << "sys.StateName = {";
97 for (std::size_t i = 0; i < state_names.size(); ++i) {
98 file << "'" << state_names[i] << "'";
99 if (i < state_names.size() - 1)
100 file << ", ";
101 }
102 file << "};\n";
103 }
104
105 // Input names
106 if (!input_names.empty()) {
107 file << "sys.InputName = {";
108 for (std::size_t i = 0; i < input_names.size(); ++i) {
109 file << "'" << input_names[i] << "'";
110 if (i < input_names.size() - 1)
111 file << ", ";
112 }
113 file << "};\n";
114 }
115
116 // Output names
117 if (!output_names.empty()) {
118 file << "sys.OutputName = {";
119 for (std::size_t i = 0; i < output_names.size(); ++i) {
120 file << "'" << output_names[i] << "'";
121 if (i < output_names.size() - 1)
122 file << ", ";
123 }
124 file << "};\n";
125 }
126
127 file << "\ndisp('Linear model loaded successfully.');\n";
128 file.flush();
129 } catch (const std::ios_base::failure &e) {
130 throw IOError("Failed to write MATLAB export file '" + path + "': " + e.what());
131 }
132 }
133
137 void ExportNumPy(const std::string &path) const {
138 std::ofstream file(path);
139 if (!file.is_open()) {
140 throw IOError("Failed to open file for NumPy export: " + path);
141 }
142
143 try {
144 // Enable exceptions for write errors
145 file.exceptions(std::ios::failbit | std::ios::badbit);
146
147 file << std::setprecision(15);
148 file << "\"\"\"Linear state-space model\n";
149 file << "Generated by Icarus " << icarus::Version() << "\n";
150 file << "Operating point: t = " << t0 << "\n";
151 file << "\"\"\"\n\n";
152 file << "import numpy as np\n\n";
153
154 // Helper to write a matrix
155 auto write_matrix = [&](const std::string &name, const Eigen::MatrixXd &M) {
156 file << name << " = np.array([\n";
157 for (Eigen::Index i = 0; i < M.rows(); ++i) {
158 file << " [";
159 for (Eigen::Index j = 0; j < M.cols(); ++j) {
160 file << M(i, j);
161 if (j < M.cols() - 1)
162 file << ", ";
163 }
164 file << "]";
165 if (i < M.rows() - 1)
166 file << ",";
167 file << "\n";
168 }
169 file << "])\n\n";
170 };
171
172 write_matrix("A", A);
173 write_matrix("B", B);
174 write_matrix("C", C);
175 write_matrix("D", D);
176
177 // Metadata
178 file << "state_names = [";
179 for (std::size_t i = 0; i < state_names.size(); ++i) {
180 file << "'" << state_names[i] << "'";
181 if (i < state_names.size() - 1)
182 file << ", ";
183 }
184 file << "]\n";
185
186 file << "input_names = [";
187 for (std::size_t i = 0; i < input_names.size(); ++i) {
188 file << "'" << input_names[i] << "'";
189 if (i < input_names.size() - 1)
190 file << ", ";
191 }
192 file << "]\n";
193
194 file << "output_names = [";
195 for (std::size_t i = 0; i < output_names.size(); ++i) {
196 file << "'" << output_names[i] << "'";
197 if (i < output_names.size() - 1)
198 file << ", ";
199 }
200 file << "]\n\n";
201
202 // Operating point
203 file << "x0 = np.array([";
204 for (Eigen::Index i = 0; i < x0.size(); ++i) {
205 file << x0(i);
206 if (i < x0.size() - 1)
207 file << ", ";
208 }
209 file << "])\n";
210
211 file << "u0 = np.array([";
212 for (Eigen::Index i = 0; i < u0.size(); ++i) {
213 file << u0(i);
214 if (i < u0.size() - 1)
215 file << ", ";
216 }
217 file << "])\n";
218
219 file << "t0 = " << t0 << "\n";
220 file.flush();
221 } catch (const std::ios_base::failure &e) {
222 throw IOError("Failed to write NumPy export file '" + path + "': " + e.what());
223 }
224 }
225
229 void ExportJSON(const std::string &path) const {
230 std::ofstream file(path);
231 if (!file.is_open()) {
232 throw IOError("Failed to open file for JSON export: " + path);
233 }
234
235 try {
236 // Enable exceptions for write errors
237 file.exceptions(std::ios::failbit | std::ios::badbit);
238
239 file << std::setprecision(15);
240 file << "{\n";
241 file << " \"generator\": \"Icarus " << icarus::Version() << "\",\n";
242 file << " \"t0\": " << t0 << ",\n";
243
244 // Helper to write a matrix
245 auto write_matrix = [&](const std::string &name, const Eigen::MatrixXd &M, bool last) {
246 file << " \"" << name << "\": [\n";
247 for (Eigen::Index i = 0; i < M.rows(); ++i) {
248 file << " [";
249 for (Eigen::Index j = 0; j < M.cols(); ++j) {
250 file << M(i, j);
251 if (j < M.cols() - 1)
252 file << ", ";
253 }
254 file << "]";
255 if (i < M.rows() - 1)
256 file << ",";
257 file << "\n";
258 }
259 file << " ]" << (last ? "" : ",") << "\n";
260 };
261
262 // Helper to write a string array
263 auto write_strings = [&](const std::string &name, const std::vector<std::string> &arr,
264 bool last) {
265 file << " \"" << name << "\": [";
266 for (std::size_t i = 0; i < arr.size(); ++i) {
267 file << "\"" << arr[i] << "\"";
268 if (i < arr.size() - 1)
269 file << ", ";
270 }
271 file << "]" << (last ? "" : ",") << "\n";
272 };
273
274 // Helper to write a vector
275 auto write_vector = [&](const std::string &name, const Eigen::VectorXd &v, bool last) {
276 file << " \"" << name << "\": [";
277 for (Eigen::Index i = 0; i < v.size(); ++i) {
278 file << v(i);
279 if (i < v.size() - 1)
280 file << ", ";
281 }
282 file << "]" << (last ? "" : ",") << "\n";
283 };
284
285 write_strings("state_names", state_names, false);
286 write_strings("input_names", input_names, false);
287 write_strings("output_names", output_names, false);
288 write_vector("x0", x0, false);
289 write_vector("u0", u0, false);
290 write_matrix("A", A, false);
291 write_matrix("B", B, false);
292 write_matrix("C", C, false);
293 write_matrix("D", D, true);
294
295 file << "}\n";
296 file.flush();
297 } catch (const std::ios_base::failure &e) {
298 throw IOError("Failed to write JSON export file '" + path + "': " + e.what());
299 }
300 }
301
302 // =========================================================================
303 // Analysis Methods
304 // =========================================================================
305
314 [[nodiscard]] int ControllabilityRank() const {
315 const Eigen::Index n = A.rows();
316 const Eigen::Index m = B.cols();
317
318 // If no inputs, system is not controllable
319 if (m == 0 || n == 0) {
320 return 0;
321 }
322
323 Eigen::MatrixXd W(n, n * m);
324 Eigen::MatrixXd Ak = Eigen::MatrixXd::Identity(n, n);
325
326 for (Eigen::Index k = 0; k < n; ++k) {
327 W.block(0, k * m, n, m) = Ak * B;
328 Ak = A * Ak;
329 }
330
331 Eigen::JacobiSVD<Eigen::MatrixXd> svd(W);
332 return static_cast<int>(svd.rank());
333 }
334
343 [[nodiscard]] int ObservabilityRank() const {
344 const Eigen::Index n = A.rows();
345 const Eigen::Index p = C.rows();
346
347 // If no outputs, system is not observable
348 if (p == 0 || n == 0) {
349 return 0;
350 }
351
352 Eigen::MatrixXd O(n * p, n);
353 Eigen::MatrixXd Ak = Eigen::MatrixXd::Identity(n, n);
354
355 for (Eigen::Index k = 0; k < n; ++k) {
356 O.block(k * p, 0, p, n) = C * Ak;
357 Ak = A * Ak;
358 }
359
360 Eigen::JacobiSVD<Eigen::MatrixXd> svd(O);
361 return static_cast<int>(svd.rank());
362 }
363
367 [[nodiscard]] bool IsStable() const {
368 Eigen::EigenSolver<Eigen::MatrixXd> solver(A, false);
369 for (Eigen::Index i = 0; i < A.rows(); ++i) {
370 if (solver.eigenvalues()(i).real() >= 0) {
371 return false;
372 }
373 }
374 return true;
375 }
376
380 [[nodiscard]] Eigen::VectorXcd Eigenvalues() const {
381 Eigen::EigenSolver<Eigen::MatrixXd> solver(A, false);
382 return solver.eigenvalues();
383 }
384};
385
386} // namespace icarus::staging
Core type definitions, concepts, and configuration for Icarus.
Consolidated error handling for Icarus.
File and I/O operation errors.
Definition Error.hpp:356
Definition Simulator.hpp:53
constexpr const char * Version()
Version string (derived from components).
Definition CoreTypes.hpp:152
Linear state-space model.
Definition LinearModel.hpp:31
bool IsStable() const
Check if system is stable (all eigenvalues have negative real part).
Definition LinearModel.hpp:367
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
int ObservabilityRank() const
Compute observability matrix rank.
Definition LinearModel.hpp:343
void ExportJSON(const std::string &path) const
Export to JSON file.
Definition LinearModel.hpp:229
void ExportMatlab(const std::string &path) const
Export to MATLAB .m file.
Definition LinearModel.hpp:56
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::VectorXcd Eigenvalues() const
Get eigenvalues of A matrix.
Definition LinearModel.hpp:380
void ExportNumPy(const std::string &path) const
Export to Python/NumPy .py file.
Definition LinearModel.hpp:137
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
int ControllabilityRank() const
Compute controllability matrix rank.
Definition LinearModel.hpp:314