11#include <casadi/casadi.hpp>
106 throw InvalidArgument(context +
": max_alias_row_nnz must be positive");
111 const std::string &context) {
120 const auto in_sp = cfn.sparsity_in(opts.
input_idx);
121 const auto out_sp = cfn.sparsity_out(opts.
output_idx);
122 if (!in_sp.is_dense() || !in_sp.is_column()) {
123 throw InvalidArgument(context +
": selected input must be a dense column vector");
125 if (!out_sp.is_dense() || !out_sp.is_column()) {
126 throw InvalidArgument(context +
": selected output must be a dense column vector");
129 throw InvalidArgument(context +
": selected input and output dimensions must match");
134 std::vector<int> indices(
static_cast<std::size_t
>(n));
135 std::iota(indices.begin(), indices.end(), 0);
140 std::vector<casadi::MX> elems;
141 elems.reserve(
static_cast<std::size_t
>(v.size1() * v.size2()));
142 for (
int i = 0; i < v.size1(); ++i) {
143 for (
int j = 0; j < v.size2(); ++j) {
144 elems.push_back(v(i, j));
151 const std::vector<int> &indices) {
152 std::vector<casadi::MX> subset;
153 subset.reserve(indices.size());
154 for (
int index : indices) {
155 subset.push_back(elems.at(
static_cast<std::size_t
>(index)));
157 return subset.empty() ? casadi::MX(0, 1) : casadi::MX::vertcat(subset);
161 std::vector<int> cols;
162 cols.reserve(
static_cast<std::size_t
>(A.size2()));
163 for (
int col = 0; col < A.size2(); ++col) {
164 if (!A(row, col).is_zero()) {
173 casadi::Function(
"structural_constant_probe", std::vector<casadi::MX>{},
174 std::vector<casadi::MX>{expr});
176 }
catch (
const std::exception &) {
182 const std::vector<int> &nz_cols) {
183 for (
int col : nz_cols) {
197 const std::vector<int> &active_var_indices,
203 casadi::MX::linear_coeff(casadi::MX::vertcat(std::vector<casadi::MX>{expr}), state_symbol,
205 }
catch (
const std::exception &) {
210 std::vector<int> nz_cols;
211 nz_cols.reserve(nz_cols_full.size());
212 for (
int col : nz_cols_full) {
213 if (std::find(active_var_indices.begin(), active_var_indices.end(), col) !=
214 active_var_indices.end()) {
215 nz_cols.push_back(col);
219 if (nz_cols.empty() ||
static_cast<int>(nz_cols.size()) > opts.
max_alias_row_nnz) {
226 int pivot_index = nz_cols.front();
227 for (
int col : nz_cols) {
228 if (col > pivot_index) {
233 casadi::MX rhs = b_row(0);
234 for (
int col : nz_cols) {
235 if (col == pivot_index) {
238 rhs += A_row(0, col) * state_symbol(col);
242 candidate.
replacement = -rhs / A_row(0, pivot_index);
246inline std::vector<int>
erase_value(
const std::vector<int> &values,
int erased_index) {
247 std::vector<int> result;
248 result.reserve(values.size() > 0 ? values.size() - 1 : 0);
249 for (
int value : values) {
250 if (value != erased_index) {
251 result.push_back(value);
257inline std::vector<int>
casadi_to_int(
const std::vector<casadi_int> &values) {
258 return std::vector<int>(values.begin(), values.end());
276 state.
stack.push_back(node);
279 for (
int neighbor : state.
adjacency[node]) {
283 if (state.
index[neighbor] < 0) {
286 }
else if (state.
on_stack[neighbor]) {
292 std::vector<int> component;
293 while (!state.
stack.empty()) {
294 const int top = state.
stack.back();
295 state.
stack.pop_back();
297 component.push_back(top);
306inline std::vector<std::vector<int>>
308 const std::vector<bool> &removed) {
311 std::vector<int>(adjacency.size(), -1),
312 std::vector<int>(adjacency.size(), -1),
314 std::vector<bool>(adjacency.size(),
false),
318 for (std::size_t node = 0; node < adjacency.size(); ++node) {
319 if (removed[node] || state.
index[node] >= 0) {
328 const std::vector<int> &row_indices,
329 const std::vector<int> &col_indices) {
330 const int block_size =
static_cast<int>(std::min(row_indices.size(), col_indices.size()));
331 if (block_size <= 1) {
335 std::vector<std::vector<int>> adjacency(
static_cast<std::size_t
>(block_size));
336 for (
int local = 0; local < block_size; ++local) {
337 const int row = row_indices.at(
static_cast<std::size_t
>(local));
338 const int matched_col = col_indices.at(
static_cast<std::size_t
>(local));
339 for (
int dep_local = 0; dep_local < block_size; ++dep_local) {
340 const int dep_col = col_indices.at(
static_cast<std::size_t
>(dep_local));
341 if (dep_col == matched_col) {
344 if (incidence.has_nz(row, dep_col)) {
345 adjacency[
static_cast<std::size_t
>(local)].push_back(dep_local);
348 auto &neighbors = adjacency[
static_cast<std::size_t
>(local)];
349 std::sort(neighbors.begin(), neighbors.end());
350 neighbors.erase(std::unique(neighbors.begin(), neighbors.end()), neighbors.end());
353 std::vector<bool> removed(
static_cast<std::size_t
>(block_size),
false);
354 std::vector<int> tears;
358 std::vector<int> cyclic_component;
359 for (
const auto &component : components) {
360 if (component.size() > 1) {
361 cyclic_component = component;
365 if (cyclic_component.empty()) {
369 int best_node = cyclic_component.front();
371 for (
int node : cyclic_component) {
372 int score =
static_cast<int>(adjacency[
static_cast<std::size_t
>(node)].size());
373 for (
int other : cyclic_component) {
374 if (std::find(adjacency[
static_cast<std::size_t
>(other)].begin(),
375 adjacency[
static_cast<std::size_t
>(other)].end(),
376 node) != adjacency[
static_cast<std::size_t
>(other)].end()) {
380 if (score > best_score) {
386 removed[
static_cast<std::size_t
>(best_node)] =
true;
387 tears.push_back(col_indices.at(
static_cast<std::size_t
>(best_node)));
408 const auto inputs = cfn.mx_in();
409 const auto outputs = cfn(inputs);
411 const casadi::MX selected_input = inputs.at(
static_cast<std::size_t
>(opts.input_idx));
412 const casadi::MX selected_output = outputs.at(
static_cast<std::size_t
>(opts.output_idx));
414 const casadi::MX state_symbol =
415 janus::sym(cfn.name_in(opts.input_idx) +
"_struct", selected_input.nnz(), 1);
417 std::vector<casadi::MX> residuals =
419 std::vector<casadi::MX>{selected_input},
420 std::vector<casadi::MX>{state_symbol})
429 casadi::MX replacement;
431 std::vector<RawAlias> raw_aliases;
433 bool progress =
true;
436 for (
int residual_index : active_residual_indices) {
439 residuals.at(
static_cast<std::size_t
>(residual_index)), state_symbol,
440 active_var_indices, opts, candidate)) {
446 substitution_exprs.at(
static_cast<std::size_t
>(pivot_original)) = candidate.replacement;
447 const casadi::MX substitution_vector = casadi::MX::vertcat(substitution_exprs);
449 residuals = casadi::MX::substitute(residuals, std::vector<casadi::MX>{state_symbol},
450 std::vector<casadi::MX>{substitution_vector});
451 state_exprs = casadi::MX::substitute(state_exprs, std::vector<casadi::MX>{state_symbol},
452 std::vector<casadi::MX>{substitution_vector});
454 raw_aliases.push_back(RawAlias{residual_index, pivot_original, candidate.replacement});
462 std::vector<int> eliminated_variable_indices;
463 std::vector<int> eliminated_residual_indices;
464 eliminated_variable_indices.reserve(raw_aliases.size());
465 eliminated_residual_indices.reserve(raw_aliases.size());
466 for (
const auto &alias : raw_aliases) {
467 eliminated_variable_indices.push_back(alias.variable_index);
468 eliminated_residual_indices.push_back(alias.residual_index);
471 std::vector<casadi::MX> wrapper_inputs_mx;
472 std::vector<SymbolicArg> wrapper_inputs;
473 wrapper_inputs_mx.reserve(
static_cast<std::size_t
>(cfn.n_in()));
474 wrapper_inputs.reserve(
static_cast<std::size_t
>(cfn.n_in()));
476 casadi::MX reduced_input;
477 for (
int i = 0; i < cfn.n_in(); ++i) {
478 if (i == opts.input_idx) {
480 janus::sym(cfn.name_in(i),
static_cast<int>(active_var_indices.size()), 1);
481 wrapper_inputs_mx.push_back(reduced_input);
482 wrapper_inputs.emplace_back(reduced_input);
484 casadi::MX arg =
janus::sym(cfn.name_in(i), cfn.size1_in(i), cfn.size2_in(i));
485 wrapper_inputs_mx.push_back(arg);
486 wrapper_inputs.emplace_back(arg);
490 std::vector<casadi::MX> subs_from;
491 std::vector<casadi::MX> subs_to;
492 subs_from.reserve(
static_cast<std::size_t
>(cfn.n_in()));
493 subs_to.reserve(
static_cast<std::size_t
>(cfn.n_in()));
495 std::vector<casadi::MX> reduced_state_substitution(selected_input.nnz(), 0.0);
496 for (std::size_t k = 0; k < active_var_indices.size(); ++k) {
497 reduced_state_substitution.at(
static_cast<std::size_t
>(active_var_indices[k])) =
498 reduced_input(
static_cast<int>(k));
500 subs_from.push_back(state_symbol);
501 subs_to.push_back(casadi::MX::vertcat(reduced_state_substitution));
502 for (
int i = 0; i < cfn.n_in(); ++i) {
503 if (i == opts.input_idx) {
506 subs_from.push_back(inputs.at(
static_cast<std::size_t
>(i)));
507 subs_to.push_back(wrapper_inputs_mx.at(
static_cast<std::size_t
>(i)));
510 const std::vector<casadi::MX> reduced_residual_exprs =
511 casadi::MX::substitute(residuals, subs_from, subs_to);
512 const std::vector<casadi::MX> full_state_exprs =
513 casadi::MX::substitute(state_exprs, subs_from, subs_to);
515 const casadi::MX reduced_residual =
517 const casadi::MX reconstructed_state =
518 full_state_exprs.empty() ? casadi::MX(0, 1) : casadi::MX::vertcat(full_state_exprs);
520 std::vector<AliasSubstitution> substitutions;
521 substitutions.reserve(raw_aliases.size());
522 for (
const auto &alias : raw_aliases) {
523 const casadi::MX replacement =
524 casadi::MX::substitute(std::vector<casadi::MX>{alias.replacement}, subs_from, subs_to)
526 substitutions.push_back(
531 Function(cfn.name() +
"_alias_reduced", wrapper_inputs,
532 std::vector<SymbolicArg>{SymbolicArg(reduced_residual)}),
533 Function(cfn.name() +
"_alias_reconstruct", wrapper_inputs,
534 std::vector<SymbolicArg>{SymbolicArg(reconstructed_state)}),
536 eliminated_variable_indices,
537 active_residual_indices,
538 eliminated_residual_indices,
555 const casadi::Sparsity incidence_sp =
558 std::vector<casadi_int> rowperm, colperm, rowblock, colblock, coarse_rowblock, coarse_colblock;
559 incidence_sp.btf(rowperm, colperm, rowblock, colblock, coarse_rowblock, coarse_colblock);
561 std::vector<StructuralBlock> blocks;
562 const std::size_t n_blocks = rowblock.size() > 0 ? rowblock.size() - 1 : 0;
563 blocks.reserve(n_blocks);
564 for (std::size_t block = 0; block < n_blocks; ++block) {
565 std::vector<int> block_rows;
566 std::vector<int> block_cols;
567 for (casadi_int k = rowblock[block]; k < rowblock[block + 1]; ++k) {
568 block_rows.push_back(
static_cast<int>(rowperm[k]));
570 for (casadi_int k = colblock[block]; k < colblock[block + 1]; ++k) {
571 block_cols.push_back(
static_cast<int>(colperm[k]));
600 StructuralTransformOptions reduced_opts = opts;
601 reduced_opts.output_idx = 0;
604 return StructuralAnalysis{std::move(alias), std::move(blt)};
Symbolic function wrapper around CasADi with Eigen-native IO.
Custom exception hierarchy for Janus framework.
Core type aliases for numeric and symbolic Eigen/CasADi interop.
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
const casadi::Sparsity & casadi_sparsity() const
Access underlying CasADi Sparsity object.
Definition Sparsity.hpp:377
Smooth approximation of ReLU function: softplus(x) = (1/beta) * log(1 + exp(beta * x)).
Definition Diagnostics.hpp:131
std::vector< std::vector< int > > strongly_connected_components(const std::vector< std::vector< int > > &adjacency, const std::vector< bool > &removed)
Definition StructuralTransforms.hpp:307
void tarjan_visit(int node, TarjanState &state)
Definition StructuralTransforms.hpp:272
void validate_options(const StructuralTransformOptions &opts, const std::string &context)
Definition StructuralTransforms.hpp:98
std::vector< int > find_row_nonzeros(const casadi::MX &A, int row)
Definition StructuralTransforms.hpp:160
std::vector< int > erase_value(const std::vector< int > &values, int erased_index)
Definition StructuralTransforms.hpp:246
bool coefficients_are_constant(const casadi::MX &A, int row, const std::vector< int > &nz_cols)
Definition StructuralTransforms.hpp:181
casadi::MX vertcat_subset(const std::vector< casadi::MX > &elems, const std::vector< int > &indices)
Definition StructuralTransforms.hpp:150
std::vector< casadi::MX > mx_elements(const casadi::MX &v)
Definition StructuralTransforms.hpp:139
std::vector< int > make_index_vector(int n)
Definition StructuralTransforms.hpp:133
void validate_selected_block(const Function &fn, const StructuralTransformOptions &opts, const std::string &context)
Definition StructuralTransforms.hpp:110
std::vector< int > tearing_recommendation(const casadi::Sparsity &incidence, const std::vector< int > &row_indices, const std::vector< int > &col_indices)
Definition StructuralTransforms.hpp:327
bool try_make_alias_candidate(const casadi::MX &expr, const casadi::MX &state_symbol, const std::vector< int > &active_var_indices, const StructuralTransformOptions &opts, AliasCandidate &candidate)
Definition StructuralTransforms.hpp:196
bool has_no_free_symbols(const casadi::MX &expr)
Definition StructuralTransforms.hpp:171
std::vector< int > casadi_to_int(const std::vector< casadi_int > &values)
Definition StructuralTransforms.hpp:257
Definition Diagnostics.hpp:19
BLTDecomposition block_triangularize(const Function &fn, const StructuralTransformOptions &opts={})
Compute a block-triangular decomposition of a selected residual block.
Definition StructuralTransforms.hpp:550
SymbolicScalar sym(const std::string &name)
Create a named symbolic scalar variable.
Definition JanusTypes.hpp:90
AliasEliminationResult alias_eliminate(const Function &fn, const StructuralTransformOptions &opts={})
Eliminate trivial affine alias rows from a selected residual block.
Definition StructuralTransforms.hpp:402
StructuralAnalysis structural_analyze(const Function &fn, const StructuralTransformOptions &opts={})
Run alias elimination followed by BLT decomposition.
Definition StructuralTransforms.hpp:596
SparsityPattern get_jacobian_sparsity(const Function &fn, int output_idx=0, int input_idx=0)
Get sparsity of a janus::Function Jacobian.
Definition Sparsity.hpp:951
casadi::MX SymbolicScalar
CasADi MX symbolic scalar.
Definition JanusTypes.hpp:70
Result of alias elimination on a selected residual block.
Definition StructuralTransforms.hpp:48
std::vector< int > kept_variable_indices
Original indices of kept variables.
Definition StructuralTransforms.hpp:51
std::vector< AliasSubstitution > substitutions
Applied substitution records.
Definition StructuralTransforms.hpp:55
Function reconstruct_full_input
Maps reduced inputs back to original ordering.
Definition StructuralTransforms.hpp:50
std::vector< int > eliminated_variable_indices
Original indices of eliminated variables.
Definition StructuralTransforms.hpp:52
std::vector< int > eliminated_residual_indices
Original indices of eliminated residuals.
Definition StructuralTransforms.hpp:54
std::vector< int > kept_residual_indices
Original indices of kept residuals.
Definition StructuralTransforms.hpp:53
Function reduced_function
Function with aliases removed.
Definition StructuralTransforms.hpp:49
A single eliminated alias or trivial affine variable relation.
Definition StructuralTransforms.hpp:34
int residual_index
Row that was eliminated.
Definition StructuralTransforms.hpp:35
SymbolicScalar replacement
Expression replacing the eliminated variable.
Definition StructuralTransforms.hpp:37
int eliminated_variable_index
Variable solved for.
Definition StructuralTransforms.hpp:36
Block-triangular decomposition and tearing metadata for a selected block.
Definition StructuralTransforms.hpp:74
std::vector< int > column_block_offsets
Fine column block boundaries.
Definition StructuralTransforms.hpp:79
std::vector< int > coarse_column_block_offsets
Coarse column block boundaries.
Definition StructuralTransforms.hpp:81
std::vector< int > coarse_row_block_offsets
Coarse row block boundaries.
Definition StructuralTransforms.hpp:80
std::vector< StructuralBlock > blocks
Diagonal blocks with tearing info.
Definition StructuralTransforms.hpp:82
std::vector< int > row_permutation
BTF row permutation.
Definition StructuralTransforms.hpp:76
std::vector< int > column_permutation
BTF column permutation.
Definition StructuralTransforms.hpp:77
std::vector< int > row_block_offsets
Fine row block boundaries.
Definition StructuralTransforms.hpp:78
SparsityPattern incidence
Incidence (Jacobian sparsity) of the block.
Definition StructuralTransforms.hpp:75
Combined alias-elimination and BLT analysis pass.
Definition StructuralTransforms.hpp:91
BLTDecomposition blt
BLT decomposition of the reduced system.
Definition StructuralTransforms.hpp:93
AliasEliminationResult alias_elimination
Alias elimination pass result.
Definition StructuralTransforms.hpp:92
One diagonal block in a block-triangular decomposition.
Definition StructuralTransforms.hpp:61
bool is_coupled() const
Check if the block involves multiple coupled equations.
Definition StructuralTransforms.hpp:68
std::vector< int > residual_indices
Residual rows in this block.
Definition StructuralTransforms.hpp:62
std::vector< int > variable_indices
Variable columns in this block.
Definition StructuralTransforms.hpp:63
std::vector< int > tear_variable_indices
Recommended tearing variables.
Definition StructuralTransforms.hpp:64
Definition StructuralTransforms.hpp:191
casadi::MX replacement
Definition StructuralTransforms.hpp:193
int pivot_index
Definition StructuralTransforms.hpp:192
Definition StructuralTransforms.hpp:261
std::vector< int > lowlink
Definition StructuralTransforms.hpp:265
int next_index
Definition StructuralTransforms.hpp:268
const std::vector< std::vector< int > > & adjacency
Definition StructuralTransforms.hpp:262
std::vector< bool > on_stack
Definition StructuralTransforms.hpp:267
std::vector< int > index
Definition StructuralTransforms.hpp:264
std::vector< std::vector< int > > components
Definition StructuralTransforms.hpp:269
std::vector< int > stack
Definition StructuralTransforms.hpp:266
const std::vector< bool > & removed
Definition StructuralTransforms.hpp:263