10#include <casadi/casadi.hpp>
90 std::vector<DiagnosticInputRef>
inputs;
95 std::vector<StructuralDiagnosticIssue>
issues;
118 std::optional<StructuralSensitivityReport>
120 std::optional<StructuralSensitivityReport>
134 std::sort(values.begin(), values.end());
135 values.erase(std::unique(values.begin(), values.end()), values.end());
139inline std::vector<casadi_int>
to_casadi_int(
const std::vector<int> &values) {
140 return std::vector<casadi_int>(values.begin(), values.end());
143inline std::string
join_labels(
const std::vector<std::string> &labels) {
144 std::ostringstream oss;
145 for (std::size_t i = 0; i < labels.size(); ++i) {
157 return "observability";
159 return "identifiability";
187 return "add sensors that depend on them or constrain/fix them";
189 return "add measurements that separate them or constrain/fix them";
194inline std::string
input_label(
const std::string &input_name,
int local_index) {
195 return input_name +
"[" + std::to_string(local_index) +
"]";
198inline std::string
output_label(
const std::string &output_name,
int rows,
int cols,
200 if (rows == 1 && cols == 1) {
203 const int row = flat_index % rows;
204 const int col = flat_index / rows;
206 return output_name +
"[" + std::to_string(row) +
"]";
209 return output_name +
"[" + std::to_string(col) +
"]";
211 return output_name +
"(" + std::to_string(row) +
"," + std::to_string(col) +
")";
215 const std::vector<int> &requested,
216 const std::string &context) {
217 if (requested.empty()) {
218 std::vector<int>
all(
static_cast<std::size_t
>(cfn.n_out()));
219 std::iota(
all.begin(),
all.end(), 0);
223 std::vector<int> indices;
224 indices.reserve(requested.size());
225 for (
int output_idx : requested) {
226 if (output_idx < 0 || output_idx >= cfn.n_out()) {
229 indices.push_back(output_idx);
232 if (indices.size() != requested.size()) {
233 throw InvalidArgument(context +
": output_indices must not contain duplicates");
239 const std::string &context) {
240 if (input_idx < 0 || input_idx >= cfn.n_in()) {
244 const casadi::Sparsity input_sp = cfn.sparsity_in(input_idx);
245 if (!input_sp.is_dense() || !input_sp.is_column()) {
246 throw InvalidArgument(context +
": selected input must be a dense column vector");
251 const std::vector<int> &output_indices,
252 const std::string &context) {
253 for (
int output_idx : output_indices) {
254 const casadi::Sparsity output_sp = cfn.sparsity_out(output_idx);
255 if (!output_sp.is_dense()) {
262 return casadi::MX::reshape(output, output.numel(), 1);
265inline std::vector<DiagnosticInputRef>
make_input_refs(
const casadi::Function &cfn,
int input_idx) {
266 std::vector<DiagnosticInputRef> refs;
267 refs.reserve(
static_cast<std::size_t
>(cfn.nnz_in(input_idx)));
268 for (
int local_index = 0; local_index < cfn.nnz_in(input_idx); ++local_index) {
278 const std::vector<int> &output_indices) {
279 std::vector<DiagnosticOutputRef> refs;
281 for (
int output_idx : output_indices) {
282 const int rows = cfn.size1_out(output_idx);
283 const int cols = cfn.size2_out(output_idx);
284 const std::string &name = cfn.name_out(output_idx);
285 for (
int linear = 0; linear < rows * cols; ++linear) {
300 const std::vector<int> &output_indices) {
301 std::vector<casadi::MX> flattened;
302 flattened.reserve(output_indices.size());
303 for (
int output_idx : output_indices) {
304 flattened.push_back(
flatten_output(outputs.at(
static_cast<std::size_t
>(output_idx))));
306 if (flattened.empty()) {
307 return casadi::MX(0, 1);
309 return casadi::MX::vertcat(flattened);
318 const int n_rows =
static_cast<int>(sp.size1());
319 const int n_cols =
static_cast<int>(sp.size2());
321 std::vector<casadi_int> row_indices_ci;
322 std::vector<casadi_int> col_indices_ci;
323 sp.get_triplet(row_indices_ci, col_indices_ci);
325 std::vector<std::vector<int>> row_to_cols(
static_cast<std::size_t
>(n_rows));
326 std::vector<std::vector<int>> col_to_rows(
static_cast<std::size_t
>(n_cols));
327 for (std::size_t k = 0; k < row_indices_ci.size(); ++k) {
328 const int row =
static_cast<int>(row_indices_ci[k]);
329 const int col =
static_cast<int>(col_indices_ci[k]);
330 row_to_cols.at(
static_cast<std::size_t
>(row)).push_back(col);
331 col_to_rows.at(
static_cast<std::size_t
>(col)).push_back(row);
334 std::vector<bool> row_visited(
static_cast<std::size_t
>(n_rows),
false);
335 std::vector<bool> col_visited(
static_cast<std::size_t
>(n_cols),
false);
336 std::vector<BipartiteComponent> components;
338 for (
int start_col = 0; start_col < n_cols; ++start_col) {
339 if (col_visited.at(
static_cast<std::size_t
>(start_col))) {
344 std::queue<std::pair<bool, int>> frontier;
345 frontier.push({
false, start_col});
346 col_visited.at(
static_cast<std::size_t
>(start_col)) =
true;
348 while (!frontier.empty()) {
349 const auto [is_row, index] = frontier.front();
353 component.
rows.push_back(index);
354 for (
int col : row_to_cols.at(
static_cast<std::size_t
>(index))) {
355 if (!col_visited.at(
static_cast<std::size_t
>(col))) {
356 col_visited.at(
static_cast<std::size_t
>(col)) =
true;
357 frontier.push({
false, col});
361 component.
cols.push_back(index);
362 for (
int row : col_to_rows.at(
static_cast<std::size_t
>(index))) {
363 if (!row_visited.at(
static_cast<std::size_t
>(row))) {
364 row_visited.at(
static_cast<std::size_t
>(row)) =
true;
365 frontier.push({
true, row});
373 components.push_back(std::move(component));
380 return static_cast<int>(casadi::Sparsity::sprank(sp));
387 std::vector<casadi_int> mapping;
388 const casadi::Sparsity sub = sp.sub(rows, cols, mapping);
393 std::vector<int> zeros;
394 zeros.reserve(
static_cast<std::size_t
>(sp.size2()));
395 for (
int col = 0; col < sp.size2(); ++col) {
396 bool has_nonzero =
false;
397 for (
int nz = sp.colind(col); nz < sp.colind(col + 1); ++nz) {
398 if (sp.row(nz) >= 0) {
404 zeros.push_back(col);
411 const std::vector<int> &indices) {
412 std::vector<std::string> labels;
413 labels.reserve(indices.size());
414 for (
int index : indices) {
415 labels.push_back(inputs.at(
static_cast<std::size_t
>(index)).label);
421 const std::vector<int> &rows) {
422 std::vector<std::string> labels;
423 labels.reserve(rows.size());
424 for (
int row : rows) {
425 labels.push_back(outputs.at(
static_cast<std::size_t
>(row)).label);
430inline std::vector<StructuralDiagnosticIssue>
432 const std::vector<DiagnosticOutputRef> &outputs,
433 const std::vector<int> &zero_sensitivity_local_indices,
434 const std::vector<StructuralDeficiencyGroup> &deficiency_groups) {
435 std::vector<StructuralDiagnosticIssue> issues;
437 if (!zero_sensitivity_local_indices.empty()) {
439 "Selected outputs have no structural dependence on " +
442 zero_sensitivity_local_indices,
447 for (
const auto &group : deficiency_groups) {
448 if (group.output_rows.empty()) {
452 "Selected outputs only provide structural rank " +
453 std::to_string(group.structural_rank) +
" for " +
454 std::to_string(group.input_local_indices.size()) +
" " +
458 "independent measurements involving {" +
461 group.input_local_indices,
472 const std::string context =
"analyze_structural_" +
property_name(property);
476 const std::vector<int> output_indices =
480 const std::vector<casadi::MX> inputs = cfn.mx_in();
481 const std::vector<casadi::MX> outputs = cfn(inputs);
483 const casadi::MX selected_input = inputs.at(
static_cast<std::size_t
>(input_idx));
485 const casadi::Sparsity jac_sp =
486 casadi::MX::jacobian(selected_outputs, selected_input).sparsity();
501 for (
int output_idx : output_indices) {
502 report.
output_names.push_back(cfn.name_out(output_idx));
508 for (
const auto &component : components) {
509 if (component.cols.empty()) {
514 if (component_rank <
static_cast<int>(component.cols.size())) {
519 static_cast<int>(component.cols.size()) - component_rank,
526 deficient.insert(deficient.end(), group.input_local_indices.begin(),
527 group.input_local_indices.end());
577 throw InvalidArgument(
"analyze_structural_diagnostics: at least one of state_input_idx or "
578 "parameter_input_idx must be non-negative");
Symbolic function wrapper around CasADi with Eigen-native IO.
Custom exception hierarchy for Janus framework.
Sparsity pattern analysis, graph coloring, and sparse derivative evaluators.
Wrapper around casadi::Function providing Eigen-native IO.
Definition Function.hpp:46
const casadi::Function & casadi_function() const
Access the underlying CasADi function.
Definition Function.hpp:186
Input validation failed (e.g., mismatched sizes, invalid parameters).
Definition JanusError.hpp:31
Definition Sparsity.hpp:38
Smooth approximation of ReLU function: softplus(x) = (1/beta) * log(1 + exp(beta * x)).
Definition Diagnostics.hpp:131
std::vector< BipartiteComponent > connected_components(const casadi::Sparsity &sp)
Definition Diagnostics.hpp:317
void validate_input_block(const casadi::Function &cfn, int input_idx, const std::string &context)
Definition Diagnostics.hpp:238
void validate_output_blocks(const casadi::Function &cfn, const std::vector< int > &output_indices, const std::string &context)
Definition Diagnostics.hpp:250
std::string output_label(const std::string &output_name, int rows, int cols, int flat_index)
Definition Diagnostics.hpp:198
std::string property_subject_singular(StructuralProperty property)
Definition Diagnostics.hpp:174
casadi::MX collect_selected_outputs(const std::vector< casadi::MX > &outputs, const std::vector< int > &output_indices)
Definition Diagnostics.hpp:299
std::string input_label(const std::string &input_name, int local_index)
Definition Diagnostics.hpp:194
std::vector< std::string > labels_for_inputs(const std::vector< DiagnosticInputRef > &inputs, const std::vector< int > &indices)
Definition Diagnostics.hpp:410
std::vector< DiagnosticOutputRef > make_output_refs(const casadi::Function &cfn, const std::vector< int > &output_indices)
Definition Diagnostics.hpp:277
std::vector< int > sort_unique(std::vector< int > values)
Definition Diagnostics.hpp:133
std::string property_fix_hint(StructuralProperty property)
Definition Diagnostics.hpp:184
std::string property_subject_plural(StructuralProperty property)
Definition Diagnostics.hpp:164
int structural_rank(const casadi::Sparsity &sp)
Definition Diagnostics.hpp:379
std::vector< DiagnosticInputRef > make_input_refs(const casadi::Function &cfn, int input_idx)
Definition Diagnostics.hpp:265
int structural_rank_of_component(const casadi::Sparsity &sp, const BipartiteComponent &component)
Definition Diagnostics.hpp:383
std::vector< std::string > labels_for_outputs(const std::vector< DiagnosticOutputRef > &outputs, const std::vector< int > &rows)
Definition Diagnostics.hpp:420
std::string join_labels(const std::vector< std::string > &labels)
Definition Diagnostics.hpp:143
std::vector< casadi_int > to_casadi_int(const std::vector< int > &values)
Definition Diagnostics.hpp:139
std::vector< int > canonical_output_indices(const casadi::Function &cfn, const std::vector< int > &requested, const std::string &context)
Definition Diagnostics.hpp:214
std::vector< StructuralDiagnosticIssue > build_issues(StructuralProperty property, const std::vector< DiagnosticInputRef > &inputs, const std::vector< DiagnosticOutputRef > &outputs, const std::vector< int > &zero_sensitivity_local_indices, const std::vector< StructuralDeficiencyGroup > &deficiency_groups)
Definition Diagnostics.hpp:431
std::vector< int > zero_sensitivity_columns(const casadi::Sparsity &sp)
Definition Diagnostics.hpp:392
casadi::MX flatten_output(const casadi::MX &output)
Definition Diagnostics.hpp:261
StructuralSensitivityReport analyze_property(const Function &fn, int input_idx, StructuralProperty property, const StructuralSensitivityOptions &opts)
Definition Diagnostics.hpp:469
std::string property_name(StructuralProperty property)
Definition Diagnostics.hpp:154
Definition Diagnostics.hpp:19
StructuralProperty
Structural property being analyzed from a symbolic sensitivity pattern.
Definition Diagnostics.hpp:24
@ Identifiability
Parameter identifiability from outputs.
Definition Diagnostics.hpp:26
@ Observability
State observability from outputs.
Definition Diagnostics.hpp:25
StructuralDiagnosticsReport analyze_structural_diagnostics(const Function &fn, const StructuralDiagnosticsOptions &opts)
Run structural observability and identifiability checks together.
Definition Diagnostics.hpp:575
StructuralSensitivityReport analyze_structural_identifiability(const Function &fn, int parameter_input_idx, const StructuralSensitivityOptions &opts={})
Analyze which parameters are structurally identifiable from selected outputs.
Definition Diagnostics.hpp:561
StructuralSensitivityReport analyze_structural_observability(const Function &fn, int state_input_idx=0, const StructuralSensitivityOptions &opts={})
Analyze which states are structurally observable from selected outputs.
Definition Diagnostics.hpp:547
auto all(const Eigen::MatrixBase< Derived > &a)
Returns true if all elements are true (non-zero).
Definition Logic.hpp:504
Structural rank analysis of selected outputs with respect to one input block.
Definition Diagnostics.hpp:79
One scalarized row in the selected output stack.
Definition Diagnostics.hpp:49
int output_idx
Index of the output block.
Definition Diagnostics.hpp:51
int local_col
Column within the output block.
Definition Diagnostics.hpp:53
int local_row
Row within the output block.
Definition Diagnostics.hpp:52
int flat_row
Row in the flattened output stack.
Definition Diagnostics.hpp:50
std::string label
Human-readable label.
Definition Diagnostics.hpp:54
One structurally deficient connected component in the sensitivity graph.
Definition Diagnostics.hpp:60
int structural_rank
Rank of this sub-block.
Definition Diagnostics.hpp:63
int rank_deficiency
Number of structurally deficient inputs.
Definition Diagnostics.hpp:64
std::vector< int > output_rows
Outputs in this connected component.
Definition Diagnostics.hpp:62
std::vector< int > input_local_indices
Inputs in this connected component.
Definition Diagnostics.hpp:61
One user-facing structural diagnostic with an attached remediation hint.
Definition Diagnostics.hpp:70
std::vector< int > output_rows
Affected output rows.
Definition Diagnostics.hpp:73
std::vector< int > input_local_indices
Affected input indices.
Definition Diagnostics.hpp:72
std::string message
Human-readable diagnostic message.
Definition Diagnostics.hpp:71
Combined observability and identifiability analysis options.
Definition Diagnostics.hpp:108
int state_input_idx
State input block index (-1 to skip observability).
Definition Diagnostics.hpp:110
int parameter_input_idx
Parameter input block index (-1 to skip identifiability).
Definition Diagnostics.hpp:111
std::vector< int > output_indices
Output indices to analyze (empty = all).
Definition Diagnostics.hpp:109
Combined structural diagnostics report.
Definition Diagnostics.hpp:117
std::optional< StructuralSensitivityReport > identifiability
Identifiability result (if requested).
Definition Diagnostics.hpp:121
std::optional< StructuralSensitivityReport > observability
Observability result (if requested).
Definition Diagnostics.hpp:119
bool has_deficiency() const
Check if any analysis found a rank deficiency.
Definition Diagnostics.hpp:125
Output-selection options shared by the structural diagnostics helpers.
Definition Diagnostics.hpp:34
std::vector< int > output_indices
Indices of outputs to analyze (empty = all).
Definition Diagnostics.hpp:35
Structural rank analysis of selected outputs with respect to one input block.
Definition Diagnostics.hpp:79
std::vector< StructuralDeficiencyGroup > deficiency_groups
Connected deficient components.
Definition Diagnostics.hpp:94
std::vector< std::string > output_names
Names of selected outputs.
Definition Diagnostics.hpp:84
int rank_deficiency
variable_dimension - structural_rank
Definition Diagnostics.hpp:88
std::vector< StructuralDiagnosticIssue > issues
User-facing diagnostic messages.
Definition Diagnostics.hpp:95
StructuralProperty property
Analysis type.
Definition Diagnostics.hpp:80
std::vector< DiagnosticInputRef > inputs
Per-element input metadata.
Definition Diagnostics.hpp:90
int output_dimension
Number of scalar outputs.
Definition Diagnostics.hpp:86
bool full_rank() const
Check if the Jacobian has full structural rank.
Definition Diagnostics.hpp:99
std::vector< int > output_indices
Selected output indices.
Definition Diagnostics.hpp:83
std::vector< int > deficient_local_indices
All structurally deficient inputs.
Definition Diagnostics.hpp:92
std::vector< DiagnosticOutputRef > outputs
Per-element output metadata.
Definition Diagnostics.hpp:91
SparsityPattern sensitivity_sparsity
Jacobian sparsity pattern.
Definition Diagnostics.hpp:89
int input_idx
Analyzed input block index.
Definition Diagnostics.hpp:81
int structural_rank
Structural rank of Jacobian.
Definition Diagnostics.hpp:87
int variable_dimension
Number of scalar variables.
Definition Diagnostics.hpp:85
std::string input_name
Name of the input block.
Definition Diagnostics.hpp:82
std::vector< int > zero_sensitivity_local_indices
Inputs with zero Jacobian columns.
Definition Diagnostics.hpp:93
Definition Diagnostics.hpp:312
std::vector< int > cols
Definition Diagnostics.hpp:314
std::vector< int > rows
Definition Diagnostics.hpp:313