Janus 2.0.0
High-performance C++20 dual-mode numerical framework
Loading...
Searching...
No Matches
Structural Diagnostics

Janus exposes a structural preflight layer that answers two common model-quality questions: (1) are the selected states structurally observable from the chosen outputs, and (2) are the selected parameters structurally identifiable from the chosen outputs. The implementation works from the symbolic Jacobian sparsity pattern of a janus::Function and lives in <janus/core/Diagnostics.hpp>. This is a symbolic-mode analysis – it answers whether the measurement layout can separate variables based on symbolic dependence alone, not numeric coefficient values.

Quick Start

#include <janus/janus.hpp>
auto x = janus::sym("x", 3, 1);
auto y = janus::SymbolicScalar::vertcat({
x(0) + x(1),
x(1),
});
janus::Function h("sensor_model", {x}, {y});
// obs.structural_rank, obs.rank_deficiency, obs.deficient_local_indices, etc.
Wrapper around casadi::Function providing Eigen-native IO.
Definition Function.hpp:46
Umbrella header that includes the entire Janus public API.
SymbolicScalar sym(const std::string &name)
Create a named symbolic scalar variable.
Definition JanusTypes.hpp:90
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

Core API

#include <janus/janus.hpp>
// Observability analysis on input block 0
// Identifiability analysis on input block 1
// Combined diagnostics
opts.state_input_idx = 0;
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
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

StructuralSensitivityOptions exposes:

  • output_indices: optional subset of function outputs to analyze; defaults to all outputs

StructuralDiagnosticsOptions adds:

  • state_input_idx: input block interpreted as the state vector
  • parameter_input_idx: input block interpreted as the parameter vector

At least one of state_input_idx or parameter_input_idx must be provided for the combined helper.

StructuralSensitivityReport returns:

  • structural_rank: structural rank of the selected Jacobian block
  • rank_deficiency: n_variables - structural_rank
  • deficient_local_indices: all input entries in a deficient structural component
  • zero_sensitivity_local_indices: entries with no structural dependence on chosen outputs
  • deficiency_groups: connected variable/output groups where structural rank is too small
  • issues: user-facing remediation hints

The report also carries flattened input and output labels so you can connect a deficiency back to the original function blocks.

Usage Patterns

When To Use This

Use structural diagnostics before:

  • fitting parameters against a measurement model
  • introducing estimator states into a filter or smoother
  • sending a large reduced-order model into an optimizer and wondering why some degrees of freedom drift

Typical setup:

janus::Function h("h", {x, p}, {y});

where x is a dense column-vector state block, p is a dense column-vector parameter block, and y contains the measured or otherwise selected outputs.

Observability Analysis

auto x = janus::sym("x", 3, 1);
auto y = janus::SymbolicScalar::vertcat({
x(0) + x(1),
x(1),
});
janus::Function h("sensor_model", {x}, {y});

Here:

  • x[2] has zero structural sensitivity
  • the structural rank is 2 / 3
  • the diagnostic suggests adding a sensor depending on x[2] or constraining/fixing it

Identifiability Analysis

auto x = janus::sym("x");
auto p = janus::sym("p", 4, 1);
auto y = janus::SymbolicScalar::vertcat({
p(0) + p(1) + x,
p(1) + p(2),
});
janus::Function h("calibration_model", {x, p}, {y});

Here:

  • p[3] is unused and therefore immediately unidentifiable
  • p[0], p[1], and p[2] share only two structurally independent output rows
  • the report surfaces one coupled deficiency group and recommends adding measurements that separate that block

Combined Diagnostics

If one measurement model carries both estimation states and calibration parameters, run both checks together:

opts.state_input_idx = 0;
// report.observability
// report.identifiability
// report.has_deficiency()

Example Walkthrough: structural_diagnostics_demo.cpp

The example examples/math/structural_diagnostics_demo.cpp demonstrates:

  1. An observability gap caused by an unmeasured state.
  2. An identifiability gap caused by a coupled parameter block plus an unused parameter.
  3. The combined state-plus-parameter report on a shared measurement model.

Build and run:

ninja -C build structural_diagnostics_demo
./build/examples/structural_diagnostics_demo

Diagnostics & Troubleshooting

Current Limits

The current implementation is deliberately structural and local:

  • selected input blocks must be dense column vectors
  • selected outputs must be dense
  • conclusions are based on symbolic Jacobian sparsity, not numeric coefficient values
  • a structurally full-rank report does not guarantee good conditioning or practical estimator quality

That makes this pass useful as an early symbolic filter before you spend time on solver tuning, experiment design, or estimator debugging.

See Also