Janus 2.0.0
High-performance C++20 dual-mode numerical framework
Loading...
Searching...
No Matches
Sparsity.hpp
Go to the documentation of this file.
1
3#pragma once
4
5#include "Function.hpp"
6#include "JanusError.hpp"
7#include "JanusIO.hpp"
8#include "JanusTypes.hpp"
9#include <casadi/casadi.hpp>
10#include <cmath>
11#include <limits>
12#include <sstream>
13#include <string>
14#include <tuple>
15#include <utility>
16#include <vector>
17
18namespace janus {
19
39 public:
41 SparsityPattern() : sp_(casadi::Sparsity(0, 0)) {}
42
47 explicit SparsityPattern(const casadi::Sparsity &sp) : sp_(sp) {}
48
53 explicit SparsityPattern(const SymbolicScalar &mx) : sp_(mx.sparsity()) {}
54
55 // === Query Interface ===
56
61 int n_rows() const { return static_cast<int>(sp_.size1()); }
62
67 int n_cols() const { return static_cast<int>(sp_.size2()); }
68
73 int nnz() const { return static_cast<int>(sp_.nnz()); }
74
79 double density() const {
80 int total = n_rows() * n_cols();
81 return total > 0 ? static_cast<double>(nnz()) / total : 0.0;
82 }
83
90 bool has_nz(int row, int col) const { return sp_.has_nz(row, col); }
91
96 std::vector<std::pair<int, int>> nonzeros() const {
97 std::vector<std::pair<int, int>> result;
98 result.reserve(nnz());
99
100 auto [row_idx, col_idx] = get_triplet();
101 for (size_t i = 0; i < row_idx.size(); ++i) {
102 result.emplace_back(row_idx[i], col_idx[i]);
103 }
104 return result;
105 }
106
107 // === Export Formats ===
108
113 std::tuple<std::vector<int>, std::vector<int>> get_triplet() const {
114 std::vector<casadi_int> rows, cols;
115 sp_.get_triplet(rows, cols);
116
117 // Convert casadi_int to int
118 std::vector<int> row_int(rows.begin(), rows.end());
119 std::vector<int> col_int(cols.begin(), cols.end());
120 return {row_int, col_int};
121 }
122
127 std::tuple<std::vector<int>, std::vector<int>> get_crs() const {
128 auto crs_sp = sp_.T(); // Transpose to get CRS from CCS
129 std::vector<casadi_int> colind = crs_sp.get_colind();
130 std::vector<casadi_int> row = crs_sp.get_row();
131
132 std::vector<int> row_ptr(colind.begin(), colind.end());
133 std::vector<int> col_idx(row.begin(), row.end());
134 return {row_ptr, col_idx};
135 }
136
141 std::tuple<std::vector<int>, std::vector<int>> get_ccs() const {
142 std::vector<casadi_int> colind = sp_.get_colind();
143 std::vector<casadi_int> row = sp_.get_row();
144
145 std::vector<int> col_ptr(colind.begin(), colind.end());
146 std::vector<int> row_idx(row.begin(), row.end());
147 return {col_ptr, row_idx};
148 }
149
150 // === Visualization ===
151
161 std::string to_string(int max_rows = 40, int max_cols = 80) const {
162 std::ostringstream oss;
163
164 int rows = n_rows();
165 int cols = n_cols();
166
167 // Header
168 oss << "Sparsity: " << rows << "x" << cols << ", nnz=" << nnz()
169 << " (density=" << std::fixed << std::setprecision(3) << density() * 100 << "%)\n";
170
171 // Limit display size
172 int disp_rows = std::min(rows, max_rows);
173 int disp_cols = std::min(cols, max_cols);
174
175 // Top border
176 oss << "┌";
177 for (int j = 0; j < disp_cols; ++j)
178 oss << "─";
179 if (cols > max_cols)
180 oss << "…";
181 oss << "┐\n";
182
183 // Rows
184 for (int i = 0; i < disp_rows; ++i) {
185 oss << "│";
186 for (int j = 0; j < disp_cols; ++j) {
187 oss << (has_nz(i, j) ? '*' : '.');
188 }
189 if (cols > max_cols)
190 oss << "…";
191 oss << "│\n";
192 }
193
194 if (rows > max_rows) {
195 oss << "│";
196 for (int j = 0; j < disp_cols; ++j)
197 oss << "⋮";
198 if (cols > max_cols)
199 oss << "…";
200 oss << "│\n";
201 }
202
203 // Bottom border
204 oss << "└";
205 for (int j = 0; j < disp_cols; ++j)
206 oss << "─";
207 if (cols > max_cols)
208 oss << "…";
209 oss << "┘\n";
210
211 return oss.str();
212 }
213
223 void export_spy_dot(const std::string &filename, const std::string &name = "sparsity") const {
224 std::ofstream file(filename + ".dot");
225 if (!file.is_open()) {
226 throw RuntimeError("Cannot open file: " + filename + ".dot");
227 }
228
229 file << "digraph " << name << " {\n";
230 file << " node [shape=plaintext];\n";
231 file << " labelloc=\"t\";\n";
232 file << " label=\"" << name << " (" << n_rows() << "x" << n_cols() << ", nnz=" << nnz()
233 << ")\";\n\n";
234
235 file << " matrix [label=<\n";
236 file << " <TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">\n";
237
238 // Iterate rows (HTML tables are row-major)
239 for (int i = 0; i < n_rows(); ++i) {
240 file << " <TR>\n";
241 for (int j = 0; j < n_cols(); ++j) {
242 if (has_nz(i, j)) {
243 // Non-zero element: Black (or dark blue)
244 file << " <TD BGCOLOR=\"black\" WIDTH=\"20\" HEIGHT=\"20\"></TD>\n";
245 } else {
246 // Zero element: White (or transparent)
247 file << " <TD BGCOLOR=\"white\" WIDTH=\"20\" HEIGHT=\"20\"></TD>\n";
248 }
249 }
250 file << " </TR>\n";
251 }
252
253 file << " </TABLE>\n";
254 file << " >];\n";
255 file << "}\n";
256 file.close();
257 }
258
268 bool visualize_spy(const std::string &output_base) const {
269 try {
270 export_spy_dot(output_base, output_base);
271 // render_graph is defined in JanusIO.hpp
272 return render_graph(output_base + ".dot", output_base + ".pdf");
273 } catch (...) {
274 return false;
275 }
276 }
277
287 void export_spy_html(const std::string &filename, const std::string &name = "sparsity") const {
288 std::string html_filename = filename + ".html";
289 std::ofstream out(html_filename);
290 if (!out.is_open()) {
291 throw RuntimeError("Cannot open file: " + html_filename);
292 }
293
294 int rows = n_rows();
295 int cols = n_cols();
296 int cell_size = 12; // pixels per cell
297
298 // Limit cell size for very large matrices
299 if (rows > 100 || cols > 100) {
300 cell_size = std::max(3, 1200 / std::max(rows, cols));
301 }
302
303 // Add margins for axis labels
304 int margin_left = 50;
305 int margin_top = 40;
306 int svg_width = cols * cell_size + margin_left + 20;
307 int svg_height = rows * cell_size + margin_top + 20;
308
309 // Determine tick interval based on matrix size
310 int tick_interval = 1;
311 if (rows > 50 || cols > 50)
312 tick_interval = 10;
313 else if (rows > 20 || cols > 20)
314 tick_interval = 5;
315
316 out << R"SPYHTML(<!DOCTYPE html>
317<html lang="en">
318<head>
319 <meta charset="UTF-8">
320 <title>)SPYHTML"
321 << name << R"SPYHTML(</title>
322 <style>
323 * { margin: 0; padding: 0; box-sizing: border-box; }
324 html, body { height: 100%; width: 100%; }
325 body { font-family: 'Segoe UI', sans-serif; background: #1a1a2e; color: #eee; overflow: hidden; display: flex; }
326 #controls { position: fixed; top: 10px; left: 10px; z-index: 100; background: rgba(0,0,0,0.8);
327 padding: 12px; border-radius: 8px; }
328 #controls button { margin: 2px; padding: 8px 14px; cursor: pointer; border: none;
329 border-radius: 4px; background: #4a4a6a; color: white; font-size: 13px; }
330 #controls button:hover { background: #6a6a8a; }
331 #container { flex: 1; cursor: grab; overflow: hidden; height: 100%; }
332 #container:active { cursor: grabbing; }
333 #sidebar { width: 280px; height: 100%; background: #16213e; padding: 16px; overflow-y: auto;
334 border-left: 2px solid #0f3460; }
335 #sidebar h2 { color: #e94560; margin-bottom: 12px; font-size: 16px; }
336 #sidebar .section { margin-bottom: 14px; }
337 #sidebar .label { color: #888; font-size: 11px; text-transform: uppercase; margin-bottom: 4px; }
338 #sidebar .value { background: #0f3460; padding: 10px; border-radius: 6px; font-family: monospace;
339 font-size: 14px; }
340 #sidebar .stat { display: flex; justify-content: space-between; padding: 6px 0;
341 border-bottom: 1px solid #0f3460; }
342 #sidebar .stat-value { color: #4dc3ff; font-weight: bold; }
343 #info { position: fixed; bottom: 10px; left: 10px; background: rgba(0,0,0,0.8);
344 padding: 10px; border-radius: 4px; font-size: 12px; }
345 .nz { cursor: pointer; }
346 .nz:hover { fill: #e94560 !important; }
347 .axis-label { font-family: monospace; font-size: 10px; fill: #666; }
348 .grid-line { stroke: #ddd; stroke-width: 0.5; }
349 </style>
350</head>
351<body>
352 <div id="controls">
353 <button onclick="zoomIn()">Zoom +</button>
354 <button onclick="zoomOut()">Zoom -</button>
355 <button onclick="resetView()">Reset</button>
356 <button onclick="fitToScreen()">Fit</button>
357 </div>
358 <div id="container">
359 <svg id="spy" width=")SPYHTML"
360 << svg_width << R"SPYHTML(" height=")SPYHTML" << svg_height
361 << R"SPYHTML(" style="background:#ffffff;">
362 <g id="matrix" transform="translate()SPYHTML"
363 << margin_left << "," << margin_top << R"SPYHTML()">
364)SPYHTML";
365
366 // Draw grid lines at tick intervals
367 for (int r = 0; r <= rows; r += tick_interval) {
368 out << " <line class=\"grid-line\" x1=\"0\" y1=\"" << (r * cell_size)
369 << "\" x2=\"" << (cols * cell_size) << "\" y2=\"" << (r * cell_size) << "\"/>\n";
370 }
371 for (int c = 0; c <= cols; c += tick_interval) {
372 out << " <line class=\"grid-line\" x1=\"" << (c * cell_size)
373 << "\" y1=\"0\" x2=\"" << (c * cell_size) << "\" y2=\"" << (rows * cell_size)
374 << "\"/>\n";
375 }
376
377 // Draw axis labels
378 for (int r = 0; r <= rows; r += tick_interval) {
379 out << " <text class=\"axis-label\" x=\"-5\" y=\""
380 << (r * cell_size + cell_size / 2)
381 << "\" text-anchor=\"end\" dominant-baseline=\"middle\">" << r << "</text>\n";
383 for (int c = 0; c <= cols; c += tick_interval) {
384 out << " <text class=\"axis-label\" x=\""
385 << (c * cell_size + cell_size / 2) << "\" y=\"-8\" text-anchor=\"middle\">" << c
386 << "</text>\n";
387 }
388
389 // Output non-zero cells as rectangles
390 auto [row_idx, col_idx] = get_triplet();
391 for (size_t i = 0; i < row_idx.size(); ++i) {
392 int r = row_idx[i];
393 int c = col_idx[i];
394 out << " <rect class=\"nz\" x=\"" << (c * cell_size) << "\" y=\""
395 << (r * cell_size) << "\" width=\"" << cell_size << "\" height=\"" << cell_size
396 << "\" fill=\"#1e3a5f\" data-row=\"" << r << "\" data-col=\"" << c << "\"/>\n";
397 }
398
399 out << R"SPYHTML( </g>
400 </svg>
401 </div>
402 <div id="sidebar">
403 <h2>Sparsity Info</h2>
404 <div class="section">
405 <div class="stat"><span>Matrix Size</span><span class="stat-value">)SPYHTML"
406 << rows << " x " << cols << R"SPYHTML(</span></div>
407 <div class="stat"><span>Non-zeros</span><span class="stat-value">)SPYHTML"
408 << nnz() << R"SPYHTML(</span></div>
409 <div class="stat"><span>Density</span><span class="stat-value">)SPYHTML";
410
411 // Format density nicely
412 double dens = density() * 100;
413 out << std::fixed << std::setprecision(2) << dens;
414
415 out << R"SPYHTML(%</span></div>
416 </div>
417 <div class="section">
418 <div class="label">Selected Cell</div>
419 <div class="value" id="cell-info">Click on a non-zero cell</div>
420 </div>
421 <div class="section">
422 <div class="label">Notation</div>
423 <div class="value" id="notation-info">A(row, col)</div>
424 </div>
425 </div>
426 <div id="info">Scroll to zoom - Drag to pan - Click cells for info</div>
427 <script>
428 let scale = 1, panX = 0, panY = 0, isDragging = false, startX, startY;
429 const container = document.getElementById('container');
430 const svg = document.getElementById('spy');
431 const cellInfo = document.getElementById('cell-info');
432 const notationInfo = document.getElementById('notation-info');
433 const svgWidth = )SPYHTML"
434 << svg_width << R"SPYHTML(;
435 const svgHeight = )SPYHTML"
436 << svg_height << R"SPYHTML(;
437 const matrixRows = )SPYHTML"
438 << rows << R"SPYHTML(;
439 const matrixCols = )SPYHTML"
440 << cols << R"SPYHTML(;
441
442 fitToScreen();
443
444 function updateTransform() {
445 svg.style.transform = `translate(${panX}px, ${panY}px) scale(${scale})`;
446 svg.style.transformOrigin = '0 0';
447 }
448 function zoomIn() { scale *= 1.3; updateTransform(); }
449 function zoomOut() { scale /= 1.3; updateTransform(); }
450 function resetView() { scale = 1; panX = 0; panY = 0; updateTransform(); }
451 function fitToScreen() {
452 const availWidth = window.innerWidth - 280;
453 const scaleX = (availWidth - 40) / svgWidth;
454 const scaleY = (window.innerHeight - 40) / svgHeight;
455 scale = Math.min(scaleX, scaleY);
456 panX = (availWidth - svgWidth * scale) / 2;
457 panY = (window.innerHeight - svgHeight * scale) / 2;
458 updateTransform();
459 }
460 container.addEventListener('wheel', e => {
461 e.preventDefault();
462 const rect = container.getBoundingClientRect();
463 const mouseX = e.clientX - rect.left, mouseY = e.clientY - rect.top;
464 const zoomFactor = e.deltaY < 0 ? 1.15 : 0.87;
465 panX = mouseX - (mouseX - panX) * zoomFactor;
466 panY = mouseY - (mouseY - panY) * zoomFactor;
467 scale *= zoomFactor;
468 updateTransform();
469 });
470 container.addEventListener('mousedown', e => {
471 if (e.target.classList.contains('nz')) return;
472 isDragging = true; startX = e.clientX - panX; startY = e.clientY - panY;
473 });
474 container.addEventListener('mousemove', e => { if (isDragging) { panX = e.clientX - startX; panY = e.clientY - startY; updateTransform(); } });
475 container.addEventListener('mouseup', () => isDragging = false);
476 container.addEventListener('mouseleave', () => isDragging = false);
477
478 // Cell interaction
479 let selectedCell = null;
480 svg.querySelectorAll('.nz').forEach(rect => {
481 rect.addEventListener('click', e => {
482 e.stopPropagation();
483 if (selectedCell) selectedCell.style.stroke = 'none';
484 selectedCell = e.target;
485 selectedCell.style.stroke = '#e94560';
486 selectedCell.style.strokeWidth = '2';
487
488 const r = parseInt(e.target.dataset.row);
489 const c = parseInt(e.target.dataset.col);
490 cellInfo.textContent = `Row: ${r}, Col: ${c}`;
491 notationInfo.textContent = `A(${r}, ${c}) = nonzero`;
492 });
493 rect.addEventListener('mouseenter', e => {
494 const r = e.target.dataset.row;
495 const c = e.target.dataset.col;
496 cellInfo.textContent = `Row: ${r}, Col: ${c}`;
497 });
498 });
499 </script>
500</body>
501</html>
502)SPYHTML";
503 out.close();
504 }
505
506 // === Underlying Access ===
507
512 const casadi::Sparsity &casadi_sparsity() const { return sp_; }
513
514 // === Operators ===
515
516 bool operator==(const SparsityPattern &other) const { return sp_ == other.sp_; }
517 bool operator!=(const SparsityPattern &other) const { return sp_ != other.sp_; }
518
519 private:
520 casadi::Sparsity sp_;
521};
522
531 public:
533 GraphColoring() = default;
534
537 explicit GraphColoring(const casadi::Sparsity &coloring) : pattern_(coloring) {}
538
543 int n_entries() const { return pattern_.n_rows(); }
544
549 int n_colors() const { return pattern_.n_cols(); }
550
555 double compression_ratio() const {
556 return n_colors() > 0 ? static_cast<double>(n_entries()) / n_colors() : 0.0;
557 }
558
563 std::vector<int> colorvec() const {
564 std::vector<int> colors(n_entries(), -1);
565 auto [rows, cols] = pattern_.get_triplet();
566 for (size_t i = 0; i < rows.size(); ++i) {
567 colors.at(rows[i]) = cols[i];
568 }
569 return colors;
570 }
571
576 const SparsityPattern &pattern() const { return pattern_; }
577
578 private:
579 SparsityPattern pattern_;
580};
581
589
590namespace detail {
591
592inline std::vector<SymbolicScalar> to_mx_vector(const std::vector<SymbolicArg> &args) {
593 std::vector<SymbolicScalar> ret;
594 ret.reserve(args.size());
595 for (const auto &arg : args) {
596 ret.push_back(arg.get());
597 }
598 return ret;
599}
600
601inline std::vector<SymbolicArg> to_symbolic_args(const std::vector<SymbolicScalar> &args) {
602 std::vector<SymbolicArg> ret;
603 ret.reserve(args.size());
604 for (const auto &arg : args) {
605 ret.emplace_back(arg);
606 }
607 return ret;
608}
609
611 std::vector<SymbolicScalar> nz = expr.get_nonzeros();
612 if (nz.empty()) {
613 return SymbolicScalar(0, 1);
614 }
615 return SymbolicScalar::vertcat(nz);
616}
617
618inline void validate_function_indices(const Function &fn, int output_idx, int input_idx,
619 const char *where) {
620 const auto &cfn = fn.casadi_function();
621 if (output_idx < 0 || output_idx >= cfn.n_out()) {
622 throw InvalidArgument(std::string(where) + ": output_idx out of range");
623 }
624 if (input_idx < 0 || input_idx >= cfn.n_in()) {
625 throw InvalidArgument(std::string(where) + ": input_idx out of range");
626 }
627}
628
629inline std::vector<SymbolicScalar> symbolic_inputs_like(const Function &fn,
630 const std::string &prefix) {
631 const auto &cfn = fn.casadi_function();
632 std::vector<SymbolicScalar> inputs;
633 inputs.reserve(cfn.n_in());
634 for (int i = 0; i < cfn.n_in(); ++i) {
635 inputs.push_back(SymbolicScalar::sym(prefix + cfn.name_in(i), cfn.sparsity_in(i)));
636 }
637 return inputs;
638}
639
640inline GraphColoring make_forward_coloring(const casadi::Sparsity &jac_sparsity) {
641 return GraphColoring(jac_sparsity.T().uni_coloring(jac_sparsity));
642}
643
644inline GraphColoring make_reverse_coloring(const casadi::Sparsity &jac_sparsity) {
645 return GraphColoring(jac_sparsity.uni_coloring(jac_sparsity.T()));
646}
647
654
656make_sparse_jacobian_artifacts(const std::vector<SymbolicArg> &expressions,
657 const std::vector<SymbolicArg> &variables, const std::string &name) {
658 SymbolicScalar expr_cat = SymbolicScalar::vertcat(to_mx_vector(expressions));
659 SymbolicScalar var_cat = SymbolicScalar::vertcat(to_mx_vector(variables));
660 SymbolicScalar jac = SymbolicScalar::jacobian(expr_cat, var_cat);
661
662 Function values_function = name.empty() ? Function(variables, {stack_nonzeros(jac)})
663 : Function(name, variables, {stack_nonzeros(jac)});
664
665 return {
666 SparsityPattern(jac.sparsity()),
667 make_forward_coloring(jac.sparsity()),
668 make_reverse_coloring(jac.sparsity()),
669 std::move(values_function),
670 };
671}
672
674 int input_idx,
675 const std::string &name) {
676 validate_function_indices(fn, output_idx, input_idx, "sparse_jacobian");
677
678 std::vector<SymbolicScalar> inputs = symbolic_inputs_like(fn, "_sj_");
679 std::vector<SymbolicScalar> outputs = fn.casadi_function()(inputs);
680 SymbolicScalar jac = SymbolicScalar::jacobian(outputs.at(output_idx), inputs.at(input_idx));
681
682 std::vector<SymbolicArg> input_args = to_symbolic_args(inputs);
683 Function values_function = name.empty() ? Function(input_args, {stack_nonzeros(jac)})
684 : Function(name, input_args, {stack_nonzeros(jac)});
685
686 return {
687 SparsityPattern(jac.sparsity()),
688 make_forward_coloring(jac.sparsity()),
689 make_reverse_coloring(jac.sparsity()),
690 std::move(values_function),
691 };
692}
693
699
702 const std::vector<SymbolicArg> &variables, const std::string &name) {
703 SymbolicScalar var_cat = SymbolicScalar::vertcat(to_mx_vector(variables));
704 SymbolicScalar hess = SymbolicScalar::hessian(expression.get(), var_cat);
705
706 Function values_function = name.empty() ? Function(variables, {stack_nonzeros(hess)})
707 : Function(name, variables, {stack_nonzeros(hess)});
708
709 return {
710 SparsityPattern(hess.sparsity()),
711 GraphColoring(hess.sparsity().star_coloring()),
712 std::move(values_function),
713 };
714}
715
717 int input_idx,
718 const std::string &name) {
719 validate_function_indices(fn, output_idx, input_idx, "sparse_hessian");
720
721 std::vector<SymbolicScalar> inputs = symbolic_inputs_like(fn, "_sh_");
722 std::vector<SymbolicScalar> outputs = fn.casadi_function()(inputs);
723 if (!outputs.at(output_idx).is_scalar()) {
724 throw InvalidArgument("sparse_hessian: selected function output must be scalar");
725 }
726
727 SymbolicScalar hess = SymbolicScalar::hessian(outputs.at(output_idx), inputs.at(input_idx));
728 std::vector<SymbolicArg> input_args = to_symbolic_args(inputs);
729 Function values_function = name.empty() ? Function(input_args, {stack_nonzeros(hess)})
730 : Function(name, input_args, {stack_nonzeros(hess)});
731
732 return {
733 SparsityPattern(hess.sparsity()),
734 GraphColoring(hess.sparsity().star_coloring()),
735 std::move(values_function),
736 };
737}
738
739inline SparsityPattern function_jacobian_sparsity(const Function &fn, int output_idx,
740 int input_idx) {
741 validate_function_indices(fn, output_idx, input_idx, "get_jacobian_sparsity");
742 std::vector<SymbolicScalar> inputs = symbolic_inputs_like(fn, "_jsp_");
743 std::vector<SymbolicScalar> outputs = fn.casadi_function()(inputs);
744 return SparsityPattern(
745 SymbolicScalar::jacobian(outputs.at(output_idx), inputs.at(input_idx)).sparsity());
746}
747
748} // namespace detail
749
758 public:
763 SparseJacobianEvaluator(const SymbolicArg &expression, const SymbolicArg &variables,
764 const std::string &name = "")
765 : artifacts_(detail::make_sparse_jacobian_artifacts({expression}, {variables}, name)) {}
766
771 SparseJacobianEvaluator(const std::vector<SymbolicArg> &expressions,
772 const std::vector<SymbolicArg> &variables, const std::string &name = "")
773 : artifacts_(detail::make_sparse_jacobian_artifacts(expressions, variables, name)) {}
774
780 SparseJacobianEvaluator(const Function &fn, int output_idx = 0, int input_idx = 0,
781 const std::string &name = "")
782 : artifacts_(detail::make_sparse_jacobian_artifacts(fn, output_idx, input_idx, name)) {}
783
786 const SparsityPattern &sparsity() const { return artifacts_.sparsity; }
787
790 int nnz() const { return artifacts_.sparsity.nnz(); }
791
794 const GraphColoring &forward_coloring() const { return artifacts_.forward_coloring; }
795
798 const GraphColoring &reverse_coloring() const { return artifacts_.reverse_coloring; }
799
807
811 return preferred_mode() == SparseJacobianMode::Forward ? artifacts_.forward_coloring
812 : artifacts_.reverse_coloring;
813 }
814
817 const Function &values_function() const { return artifacts_.values_function; }
818
823 template <typename... Args> auto values(Args &&...args) const {
824 return artifacts_.values_function.eval(std::forward<Args>(args)...);
825 }
826
831 template <typename... Args> auto operator()(Args &&...args) const {
832 return values(std::forward<Args>(args)...);
833 }
834
835 private:
837};
838
847 public:
852 SparseHessianEvaluator(const SymbolicArg &expression, const SymbolicArg &variables,
853 const std::string &name = "")
854 : artifacts_(detail::make_sparse_hessian_artifacts(expression, {variables}, name)) {}
855
860 SparseHessianEvaluator(const SymbolicArg &expression, const std::vector<SymbolicArg> &variables,
861 const std::string &name = "")
862 : artifacts_(detail::make_sparse_hessian_artifacts(expression, variables, name)) {}
863
869 SparseHessianEvaluator(const Function &fn, int output_idx = 0, int input_idx = 0,
870 const std::string &name = "")
871 : artifacts_(detail::make_sparse_hessian_artifacts(fn, output_idx, input_idx, name)) {}
872
875 const SparsityPattern &sparsity() const { return artifacts_.sparsity; }
876
879 int nnz() const { return artifacts_.sparsity.nnz(); }
880
883 const GraphColoring &coloring() const { return artifacts_.coloring; }
884
887 const Function &values_function() const { return artifacts_.values_function; }
888
893 template <typename... Args> auto values(Args &&...args) const {
894 return artifacts_.values_function.eval(std::forward<Args>(args)...);
895 }
896
901 template <typename... Args> auto operator()(Args &&...args) const {
902 return values(std::forward<Args>(args)...);
903 }
904
905 private:
907};
908
909// === Sparsity Query Functions ===
910
922 const SymbolicScalar &vars) {
923 casadi::Sparsity sp = casadi::MX::jacobian_sparsity(expr, vars);
924 return SparsityPattern(sp);
925}
926
937 // Hessian sparsity = Jacobian sparsity of the gradient
938 auto grad = casadi::MX::gradient(expr, vars);
939 casadi::Sparsity sp = casadi::MX::jacobian_sparsity(grad, vars);
940 return SparsityPattern(sp);
941}
942
951inline SparsityPattern get_jacobian_sparsity(const Function &fn, int output_idx = 0,
952 int input_idx = 0) {
953 return detail::function_jacobian_sparsity(fn, output_idx, input_idx);
954}
955
964inline SparsityPattern get_hessian_sparsity(const Function &fn, int output_idx = 0,
965 int input_idx = 0) {
966 detail::validate_function_indices(fn, output_idx, input_idx, "get_hessian_sparsity");
967 std::vector<SymbolicScalar> inputs = detail::symbolic_inputs_like(fn, "_hsp_");
968 std::vector<SymbolicScalar> outputs = fn.casadi_function()(inputs);
969 if (!outputs.at(output_idx).is_scalar()) {
970 throw InvalidArgument("get_hessian_sparsity: selected function output must be scalar");
971 }
972 return SparsityPattern(
973 SymbolicScalar::hessian(outputs.at(output_idx), inputs.at(input_idx)).sparsity());
974}
975
982inline SparsityPattern get_sparsity_in(const Function &fn, int input_idx = 0) {
983 casadi::Sparsity sp = fn.casadi_function().sparsity_in(input_idx);
984 return SparsityPattern(sp);
985}
986
993inline SparsityPattern get_sparsity_out(const Function &fn, int output_idx = 0) {
994 casadi::Sparsity sp = fn.casadi_function().sparsity_out(output_idx);
995 return SparsityPattern(sp);
996}
997
1006 const SymbolicArg &variables,
1007 const std::string &name = "") {
1008 return SparseJacobianEvaluator(expression, variables, name);
1009}
1010
1018inline SparseJacobianEvaluator sparse_jacobian(const std::vector<SymbolicArg> &expressions,
1019 const std::vector<SymbolicArg> &variables,
1020 const std::string &name = "") {
1021 return SparseJacobianEvaluator(expressions, variables, name);
1022}
1023
1032inline SparseJacobianEvaluator sparse_jacobian(const Function &fn, int output_idx = 0,
1033 int input_idx = 0, const std::string &name = "") {
1034 return SparseJacobianEvaluator(fn, output_idx, input_idx, name);
1035}
1036
1045 const SymbolicArg &variables,
1046 const std::string &name = "") {
1047 return SparseHessianEvaluator(expression, variables, name);
1048}
1049
1058 const std::vector<SymbolicArg> &variables,
1059 const std::string &name = "") {
1060 return SparseHessianEvaluator(expression, variables, name);
1061}
1062
1071inline SparseHessianEvaluator sparse_hessian(const Function &fn, int output_idx = 0,
1072 int input_idx = 0, const std::string &name = "") {
1073 return SparseHessianEvaluator(fn, output_idx, input_idx, name);
1074}
1075
1076// =============================================================================
1077// NaN-Propagation Sparsity Detection
1078// =============================================================================
1079
1087
1122template <typename Func>
1123SparsityPattern nan_propagation_sparsity(Func &&fn, int n_inputs, int n_outputs,
1124 const NaNSparsityOptions &opts = {}) {
1125 // Use reference point or default to zeros
1126 NumericVector x0(n_inputs);
1127 if (opts.reference_point.size() == n_inputs) {
1128 x0 = opts.reference_point;
1129 } else {
1130 x0.setZero();
1131 }
1132
1133 // Evaluate at reference point to verify the function works
1134 NumericVector y0 = fn(x0);
1135 if (y0.size() != n_outputs) {
1136 throw InvalidArgument("nan_propagation_sparsity: function returned " +
1137 std::to_string(y0.size()) + " outputs, expected " +
1138 std::to_string(n_outputs));
1139 }
1140
1141 // Build sparsity pattern by probing each input with NaN
1142 std::vector<casadi_int> row_indices;
1143 std::vector<casadi_int> col_indices;
1144
1145 for (int i = 0; i < n_inputs; ++i) {
1146 // Create perturbed input with NaN at position i
1147 NumericVector x_perturbed = x0;
1148 x_perturbed(i) = std::numeric_limits<double>::quiet_NaN();
1149
1150 // Evaluate function
1151 NumericVector y_perturbed = fn(x_perturbed);
1152
1153 // Check which outputs became NaN
1154 for (int j = 0; j < n_outputs; ++j) {
1155 if (std::isnan(y_perturbed(j))) {
1156 // Output j depends on input i
1157 row_indices.push_back(j);
1158 col_indices.push_back(i);
1159 }
1160 }
1161 }
1162
1163 // Construct CasADi sparsity from triplets
1164 casadi::Sparsity sp = casadi::Sparsity::triplet(n_outputs, n_inputs, row_indices, col_indices);
1165 return SparsityPattern(sp);
1166}
1167
1178 const NaNSparsityOptions &opts = {}) {
1179 // Get input/output dimensions from the function
1180 auto sp_in = fn.casadi_function().sparsity_in(0);
1181 auto sp_out = fn.casadi_function().sparsity_out(0);
1182
1183 int n_inputs = static_cast<int>(sp_in.nnz());
1184 int n_outputs = static_cast<int>(sp_out.nnz());
1185
1186 // Create wrapper that evaluates the function with a single vector input
1187 auto eval_fn = [&fn](const NumericVector &x) -> NumericVector {
1188 // Pass as a single Eigen vector (not as separate scalars)
1189 auto results = fn(x);
1190 // Flatten results to a single vector
1191 int total_size = 0;
1192 for (const auto &m : results) {
1193 total_size += static_cast<int>(m.size());
1194 }
1195 NumericVector y(total_size);
1196 int offset = 0;
1197 for (const auto &m : results) {
1198 for (int i = 0; i < m.size(); ++i) {
1199 y(offset + i) = m(i);
1200 }
1201 offset += static_cast<int>(m.size());
1202 }
1203 return y;
1204 };
1205
1206 return nan_propagation_sparsity(eval_fn, n_inputs, n_outputs, opts);
1207}
1208
1209} // namespace janus
Symbolic function wrapper around CasADi with Eigen-native IO.
Custom exception hierarchy for Janus framework.
IO Utilities and Traits for Janus.
Core type aliases for numeric and symbolic Eigen/CasADi interop.
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
Wrapper around a CasADi graph coloring assignment.
Definition Sparsity.hpp:530
int n_entries() const
Number of original entries being colored.
Definition Sparsity.hpp:543
std::vector< int > colorvec() const
0-based color assignment for each original entry
Definition Sparsity.hpp:563
double compression_ratio() const
Compression factor relative to one direction per entry.
Definition Sparsity.hpp:555
int n_colors() const
Number of compressed seed directions (colors).
Definition Sparsity.hpp:549
GraphColoring()=default
Default-construct an empty coloring.
const SparsityPattern & pattern() const
Access the sparse assignment matrix behind the coloring.
Definition Sparsity.hpp:576
GraphColoring(const casadi::Sparsity &coloring)
Construct from a CasADi coloring sparsity.
Definition Sparsity.hpp:537
Input validation failed (e.g., mismatched sizes, invalid parameters).
Definition JanusError.hpp:31
Operation failed at runtime (e.g., CasADi eval with free variables).
Definition JanusError.hpp:41
Cached sparse Hessian evaluator with fixed structural ordering.
Definition Sparsity.hpp:846
SparseHessianEvaluator(const SymbolicArg &expression, const std::vector< SymbolicArg > &variables, const std::string &name="")
Construct from a scalar expression and multiple variable blocks.
Definition Sparsity.hpp:860
auto values(Args &&...args) const
Evaluate and return Hessian non-zero values.
Definition Sparsity.hpp:893
SparseHessianEvaluator(const Function &fn, int output_idx=0, int input_idx=0, const std::string &name="")
Construct from a janus::Function.
Definition Sparsity.hpp:869
int nnz() const
Number of structural non-zeros.
Definition Sparsity.hpp:879
const GraphColoring & coloring() const
Get the star coloring for symmetric compression.
Definition Sparsity.hpp:883
auto operator()(Args &&...args) const
Shorthand for values().
Definition Sparsity.hpp:901
SparseHessianEvaluator(const SymbolicArg &expression, const SymbolicArg &variables, const std::string &name="")
Construct from a scalar expression and single variable block.
Definition Sparsity.hpp:852
const Function & values_function() const
Access the compiled values function.
Definition Sparsity.hpp:887
const SparsityPattern & sparsity() const
Get the Hessian sparsity pattern.
Definition Sparsity.hpp:875
Cached sparse Jacobian evaluator with fixed structural ordering.
Definition Sparsity.hpp:757
const GraphColoring & preferred_coloring() const
Get the coloring for the preferred mode.
Definition Sparsity.hpp:810
SparseJacobianEvaluator(const Function &fn, int output_idx=0, int input_idx=0, const std::string &name="")
Construct from a janus::Function.
Definition Sparsity.hpp:780
const GraphColoring & reverse_coloring() const
Get reverse-mode graph coloring.
Definition Sparsity.hpp:798
auto operator()(Args &&...args) const
Shorthand for values().
Definition Sparsity.hpp:831
const SparsityPattern & sparsity() const
Get the Jacobian sparsity pattern.
Definition Sparsity.hpp:786
auto values(Args &&...args) const
Evaluate and return Jacobian non-zero values.
Definition Sparsity.hpp:823
SparseJacobianMode preferred_mode() const
Determine the cheaper directional mode.
Definition Sparsity.hpp:802
SparseJacobianEvaluator(const SymbolicArg &expression, const SymbolicArg &variables, const std::string &name="")
Construct from a single symbolic expression and variable.
Definition Sparsity.hpp:763
const GraphColoring & forward_coloring() const
Get forward-mode graph coloring.
Definition Sparsity.hpp:794
int nnz() const
Number of structural non-zeros.
Definition Sparsity.hpp:790
SparseJacobianEvaluator(const std::vector< SymbolicArg > &expressions, const std::vector< SymbolicArg > &variables, const std::string &name="")
Construct from multiple symbolic expressions and variables.
Definition Sparsity.hpp:771
const Function & values_function() const
Access the compiled values function.
Definition Sparsity.hpp:817
Definition Sparsity.hpp:38
int n_cols() const
Number of columns in the pattern.
Definition Sparsity.hpp:67
bool operator!=(const SparsityPattern &other) const
Definition Sparsity.hpp:382
bool visualize_spy(const std::string &output_base) const
Export/Render sparsity pattern to PDF via Graphviz.
Definition Sparsity.hpp:268
double density() const
Sparsity density (nnz / total elements).
Definition Sparsity.hpp:79
int n_rows() const
Number of rows in the pattern.
Definition Sparsity.hpp:61
SparsityPattern(const SymbolicScalar &mx)
Construct from CasADi MX (extracts its sparsity).
Definition Sparsity.hpp:53
std::vector< std::pair< int, int > > nonzeros() const
Get all non-zero positions as (row, col) pairs.
Definition Sparsity.hpp:96
std::string to_string(int max_rows=40, int max_cols=80) const
Generate ASCII spy plot of sparsity pattern.
Definition Sparsity.hpp:161
std::tuple< std::vector< int >, std::vector< int > > get_crs() const
Get Compressed Row Storage (CRS) format.
Definition Sparsity.hpp:127
SparsityPattern(const casadi::Sparsity &sp)
Construct from CasADi Sparsity object.
Definition Sparsity.hpp:47
std::tuple< std::vector< int >, std::vector< int > > get_ccs() const
Get Compressed Column Storage (CCS) format.
Definition Sparsity.hpp:141
SparsityPattern()
Default-construct an empty 0x0 pattern.
Definition Sparsity.hpp:41
void export_spy_dot(const std::string &filename, const std::string &name="sparsity") const
Export sparsity pattern to DOT format as a matrix grid (spy plot).
Definition Sparsity.hpp:223
bool operator==(const SparsityPattern &other) const
Definition Sparsity.hpp:381
void export_spy_html(const std::string &filename, const std::string &name="sparsity") const
Export sparsity pattern to an interactive HTML file.
Definition Sparsity.hpp:287
int nnz() const
Number of structural non-zeros.
Definition Sparsity.hpp:73
std::tuple< std::vector< int >, std::vector< int > > get_triplet() const
Get triplet format (row indices, column indices).
Definition Sparsity.hpp:113
bool has_nz(int row, int col) const
Check if a specific element is structurally non-zero.
Definition Sparsity.hpp:90
const casadi::Sparsity & casadi_sparsity() const
Access underlying CasADi Sparsity object.
Definition Sparsity.hpp:377
Universal symbolic argument wrapper for Function inputs/outputs.
Definition JanusTypes.hpp:250
SymbolicScalar get() const
Get underlying CasADi MX object.
Definition JanusTypes.hpp:286
Smooth approximation of ReLU function: softplus(x) = (1/beta) * log(1 + exp(beta * x)).
Definition Diagnostics.hpp:131
SparseHessianArtifacts make_sparse_hessian_artifacts(const SymbolicArg &expression, const std::vector< SymbolicArg > &variables, const std::string &name)
Definition Sparsity.hpp:701
std::vector< SymbolicScalar > to_mx_vector(const std::vector< SymbolicArg > &args)
Definition Sparsity.hpp:592
SparseJacobianArtifacts make_sparse_jacobian_artifacts(const std::vector< SymbolicArg > &expressions, const std::vector< SymbolicArg > &variables, const std::string &name)
Definition Sparsity.hpp:656
SparsityPattern function_jacobian_sparsity(const Function &fn, int output_idx, int input_idx)
Definition Sparsity.hpp:739
GraphColoring make_forward_coloring(const casadi::Sparsity &jac_sparsity)
Definition Sparsity.hpp:640
std::vector< SymbolicScalar > symbolic_inputs_like(const Function &fn, const std::string &prefix)
Definition Sparsity.hpp:629
SymbolicScalar stack_nonzeros(const SymbolicScalar &expr)
Definition Sparsity.hpp:610
GraphColoring make_reverse_coloring(const casadi::Sparsity &jac_sparsity)
Definition Sparsity.hpp:644
std::vector< SymbolicArg > to_symbolic_args(const std::vector< SymbolicScalar > &args)
Definition Sparsity.hpp:601
void validate_function_indices(const Function &fn, int output_idx, int input_idx, const char *where)
Definition Sparsity.hpp:618
Definition Diagnostics.hpp:19
SparsityPattern sparsity_of_hessian(const SymbolicScalar &expr, const SymbolicScalar &vars)
Get Hessian sparsity of a scalar expression.
Definition Sparsity.hpp:936
auto where(const Cond &cond, const T1 &if_true, const T2 &if_false)
Select values based on condition (ternary operator) Returns: cond ? if_true : if_false Supports mixed...
Definition Logic.hpp:43
SparseJacobianMode
Preferred directional mode for a sparse Jacobian evaluator.
Definition Sparsity.hpp:585
@ Forward
Forward-mode (column compression).
Definition Sparsity.hpp:586
@ Reverse
Reverse-mode (row compression).
Definition Sparsity.hpp:587
SparseHessianEvaluator sparse_hessian(const SymbolicArg &expression, const SymbolicArg &variables, const std::string &name="")
Compile a sparse Hessian evaluator from symbolic expressions.
Definition Sparsity.hpp:1044
JanusVector< NumericScalar > NumericVector
Eigen::VectorXd equivalent.
Definition JanusTypes.hpp:67
bool render_graph(const std::string &dot_file, const std::string &output_file)
Export a janus::Function to DOT format for visualization.
Definition JanusIO.hpp:349
SparsityPattern sparsity_of_jacobian(const SymbolicScalar &expr, const SymbolicScalar &vars)
Get Jacobian sparsity without computing the full Jacobian.
Definition Sparsity.hpp:921
SparseJacobianEvaluator sparse_jacobian(const SymbolicArg &expression, const SymbolicArg &variables, const std::string &name="")
Compile a sparse Jacobian evaluator from symbolic expressions.
Definition Sparsity.hpp:1005
SparsityPattern get_hessian_sparsity(const Function &fn, int output_idx=0, int input_idx=0)
Get Hessian sparsity of a scalar janus::Function output.
Definition Sparsity.hpp:964
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
SparsityPattern nan_propagation_sparsity(Func &&fn, int n_inputs, int n_outputs, const NaNSparsityOptions &opts={})
Detect Jacobian sparsity using NaN propagation.
Definition Sparsity.hpp:1123
SparsityPattern get_sparsity_out(const Function &fn, int output_idx=0)
Get output sparsity of a janus::Function.
Definition Sparsity.hpp:993
casadi::MX SymbolicScalar
CasADi MX symbolic scalar.
Definition JanusTypes.hpp:70
SparsityPattern get_sparsity_in(const Function &fn, int input_idx=0)
Get input sparsity of a janus::Function.
Definition Sparsity.hpp:982
Options for NaN-propagation sparsity detection.
Definition Sparsity.hpp:1084
NumericVector reference_point
Point to evaluate at (default: zeros).
Definition Sparsity.hpp:1085
Definition Sparsity.hpp:694
SparsityPattern sparsity
Definition Sparsity.hpp:695
GraphColoring coloring
Definition Sparsity.hpp:696
Function values_function
Definition Sparsity.hpp:697
Definition Sparsity.hpp:648
GraphColoring forward_coloring
Definition Sparsity.hpp:650
GraphColoring reverse_coloring
Definition Sparsity.hpp:651
SparsityPattern sparsity
Definition Sparsity.hpp:649
Function values_function
Definition Sparsity.hpp:652
JanusVector< NumericScalar > NumericVector
Eigen::VectorXd equivalent.
Definition JanusTypes.hpp:67