5#include <casadi/casadi.hpp>
39template <
typename Derived>
40void print(
const std::string &label,
const Eigen::MatrixBase<Derived> &mat) {
41 std::cout << label <<
":\n" << mat <<
"\n" << std::endl;
51template <
typename Derived>
52void disp(
const std::string &label,
const Eigen::MatrixBase<Derived> &mat) {
62template <
typename Derived>
auto eval(
const Eigen::MatrixBase<Derived> &mat) {
63 using Scalar =
typename Derived::Scalar;
64 if constexpr (std::is_same_v<Scalar, SymbolicScalar>) {
66 SymbolicScalar flat = SymbolicScalar::zeros(mat.rows(), mat.cols());
67 for (
int i = 0; i < mat.rows(); ++i) {
68 for (
int j = 0; j < mat.cols(); ++j) {
69 flat(i, j) = mat(i, j);
74 casadi::Function f(
"f", {}, {flat});
75 auto res = f(std::vector<casadi::DM>{});
76 casadi::DM res_dm = res[0];
79 for (
int i = 0; i < mat.rows(); ++i) {
80 for (
int j = 0; j < mat.cols(); ++j) {
81 res_eigen(i, j) =
static_cast<double>(res_dm(i, j));
85 }
catch (
const std::exception &e) {
86 throw RuntimeError(
"eval failed (expression contains free variables): " +
87 std::string(e.what()));
99 if (val.size1() != 1 || val.size2() != 1) {
100 throw RuntimeError(
"eval scalar failed: expected 1x1 symbolic expression, got " +
101 std::to_string(val.size1()) +
"x" + std::to_string(val.size2()));
104 casadi::Function f(
"f", {}, {val});
105 auto res = f(std::vector<casadi::DM>{});
106 casadi::DM res_dm = res[0];
107 return static_cast<double>(res_dm);
108 }
catch (
const std::exception &e) {
109 throw RuntimeError(
"eval scalar failed: " + std::string(e.what()));
117template <
typename T, std::enable_if_t<std::is_arithmetic_v<T>,
int> = 0> T
eval(
const T &val) {
132 result.reserve(s.size());
159 if (result.size() > 40) {
160 result = result.substr(0, 37) +
"...";
169 if (mx.is_symbolic()) {
170 std::ostringstream ss;
174 if (mx.is_constant()) {
175 std::ostringstream ss;
177 std::string s = ss.str();
179 s = s.substr(0, 17) +
"...";
183 std::ostringstream ss;
185 std::string s = ss.str();
188 if (s.find(
"sq(") == 0)
190 if (s.find(
"sin(") == 0)
192 if (s.find(
"cos(") == 0)
194 if (s.find(
"exp(") == 0)
196 if (s.find(
"log(") == 0)
198 if (s.find(
"sqrt(") == 0)
200 if (s.find(
"tanh(") == 0)
205 s = s.substr(0, 27) +
"...";
222 const std::string &name =
"expression") {
224 std::vector<SymbolicScalar> free_vars = SymbolicScalar::symvar(expr);
227 std::string dot_filename = filename +
".dot";
228 std::ofstream out(dot_filename);
229 if (!out.is_open()) {
230 throw RuntimeError(
"Failed to open file for writing: " + dot_filename);
233 out <<
"digraph \"" << name <<
"\" {\n";
234 out <<
" rankdir=BT;\n";
235 out <<
" splines=ortho;\n";
236 out <<
" node [shape=box, style=\"rounded,filled\", fontname=\"Helvetica\"];\n";
237 out <<
" edge [color=\"#666666\", arrowsize=0.7];\n\n";
240 out <<
" labelloc=\"t\";\n";
242 out <<
" fontsize=16;\n\n";
245 std::map<std::string, int> node_ids;
246 std::vector<std::pair<int, int>> edges;
247 std::set<const void *> visited;
248 int node_counter = 0;
251 std::vector<SymbolicScalar> queue = {expr};
252 std::map<const void *, int> ptr_to_id;
254 while (!queue.empty()) {
258 const void *ptr = current.get();
259 if (visited.count(ptr))
263 int current_id = node_counter++;
264 ptr_to_id[ptr] = current_id;
267 casadi_int n = current.n_dep();
268 for (casadi_int i = 0; i < n; ++i) {
270 queue.push_back(dep);
278 while (!queue.empty()) {
282 const void *ptr = current.get();
283 if (visited.count(ptr))
287 int current_id = ptr_to_id[ptr];
291 std::string color =
"#87CEEB";
292 std::string shape =
"box";
294 if (current.is_symbolic()) {
297 }
else if (current.is_constant()) {
300 }
else if (current.n_dep() == 0) {
305 <<
"\", fillcolor=\"" << color <<
"\", shape=" << shape <<
"];\n";
308 casadi_int n = current.n_dep();
309 for (casadi_int i = 0; i < n; ++i) {
311 const void *dep_ptr = dep.get();
312 int dep_id = ptr_to_id[dep_ptr];
313 out <<
" node_" << dep_id <<
" -> node_" << current_id <<
";\n";
314 queue.push_back(dep);
319 if (!ptr_to_id.empty()) {
320 const void *out_ptr = expr.get();
321 int out_id = ptr_to_id[out_ptr];
322 out <<
"\n // Output marker\n";
323 out <<
" output [label=\"Output\", shape=doublecircle, fillcolor=\"#FFD700\"];\n";
324 out <<
" node_" << out_id <<
" -> output;\n";
349inline bool render_graph(
const std::string &dot_file,
const std::string &output_file) {
351 std::string format =
"pdf";
352 size_t dot_pos = output_file.rfind(
'.');
353 if (dot_pos != std::string::npos) {
354 format = output_file.substr(dot_pos + 1);
358 std::string cmd =
"dot -T" + format +
" \"" + dot_file +
"\" -o \"" + output_file +
"\"";
360 int result = std::system(cmd.c_str());
376 return render_graph(output_base +
".dot", output_base +
".pdf");
377 }
catch (
const std::exception &) {
389 for (
char c : content) {
399 escaped +=
"\\u003C";
438 const std::string &escaped_dot,
const std::string &node_data_json,
439 const std::string &edges_json,
440 const std::string &extra_header_js =
"") {
441 out << R
"HTMLSTART(<!DOCTYPE html>
444 <meta charset="UTF-8">
446 << title << R"HTMLMID(</title>
447 <script src="https://cdnjs.cloudflare.com/ajax/libs/viz.js/2.1.2/viz.js"></script>
448 <script src="https://cdnjs.cloudflare.com/ajax/libs/viz.js/2.1.2/full.render.js"></script>
450 * { margin: 0; padding: 0; box-sizing: border-box; }
451 html, body { height: 100%; width: 100%; }
452 body { font-family: 'Segoe UI', sans-serif; background: #1a1a2e; color: #eee; overflow: hidden; display: flex; }
453 #controls { position: fixed; top: 10px; left: 10px; z-index: 100; background: rgba(0,0,0,0.8);
454 padding: 12px; border-radius: 8px; }
455 #controls button { margin: 2px; padding: 8px 14px; cursor: pointer; border: none;
456 border-radius: 4px; background: #4a4a6a; color: white; font-size: 13px; }
457 #controls button:hover { background: #6a6a8a; }
458 #graph { flex: 1; cursor: grab; overflow: hidden; height: 100%; }
459 #graph:active { cursor: grabbing; }
460 #graph svg { display: block; }
461 #sidebar { width: 320px; height: 100%; background: #16213e; padding: 16px; overflow-y: auto;
462 border-left: 2px solid #0f3460; }
463 #sidebar h2 { color: #e94560; margin-bottom: 12px; font-size: 16px; }
464 #sidebar .section { margin-bottom: 16px; }
465 #sidebar .label { color: #888; font-size: 11px; text-transform: uppercase; margin-bottom: 4px; }
466 #sidebar .value { background: #0f3460; padding: 10px; border-radius: 6px; font-family: monospace;
467 font-size: 13px; word-break: break-all; white-space: pre-wrap; max-height: 200px; overflow-y: auto; }
468 #sidebar .type-badge { display: inline-block; padding: 3px 8px; border-radius: 4px; font-size: 11px;
470 .type-input { background: #90EE90; color: #000; }
471 .type-constant { background: #FFE4B5; color: #000; }
472 .type-operation { background: #87CEEB; color: #000; }
473 .type-leaf { background: #DDA0DD; color: #000; }
474 #info { position: fixed; bottom: 10px; left: 10px; background: rgba(0,0,0,0.8);
475 padding: 10px; border-radius: 4px; font-size: 12px; }
476 #stats { position: fixed; top: 10px; right: 340px; background: rgba(0,0,0,0.8);
477 padding: 10px; border-radius: 4px; font-size: 12px; }
478 .node-highlighted polygon, .node-highlighted ellipse, .node-highlighted path { stroke: #e94560 !important; stroke-width: 3px !important; }
479 .edge-highlighted path { stroke: #e94560 !important; stroke-width: 2px !important; }
480 .edge-highlighted polygon { stroke: #e94560 !important; fill: #e94560 !important; }
481 svg .node { cursor: pointer; }
482 svg .node:hover polygon, svg .node:hover ellipse { stroke: #fff !important; stroke-width: 2px !important; }
487 <button onclick="zoomIn()">Zoom +</button>
488 <button onclick="zoomOut()">Zoom -</button>
489 <button onclick="resetView()">Reset</button>
490 <button onclick="fitToScreen()">Fit</button>
492 <div id="graph"></div>
496 <p style="color:#666; font-style:italic;">Click on a node to see details</p>
499 <div id="info">Scroll to zoom - Drag to pan - Click nodes for details</div>
500 <div id="stats"></div>
502 const dotSrc = ")HTMLMID"
503 << escaped_dot << R"HTMLMID2(";
504 const nodeData = )HTMLMID2"
505 << node_data_json << R"HTMLMID3(;
506 const edges = )HTMLMID3"
507 << edges_json << R"HTMLEND(;
511 if (!extra_header_js.empty()) {
512 out <<
" " << extra_header_js <<
"\n";
516 let scale = 1, panX = 0, panY = 0, isDragging = false, startX, startY;
517 let selectedNode = null;
518 const container = document.getElementById('graph');
519 const sidebar = document.getElementById('node-info');
521 new Viz().renderSVGElement(dotSrc).then(svg => {
522 container.appendChild(svg);
523 svg.style.transformOrigin = '0 0';
526 setupNodeInteraction(svg);
529 function updateTransform(svg) {
530 svg.style.transform = `translate(${panX}px, ${panY}px) scale(${scale})`;
532 function zoomIn() { scale *= 1.3; updateTransform(container.querySelector('svg')); }
533 function zoomOut() { scale /= 1.3; updateTransform(container.querySelector('svg')); }
534 function resetView() { scale = 1; panX = 0; panY = 0; updateTransform(container.querySelector('svg')); }
535 function fitToScreen() {
536 const svg = container.querySelector('svg');
538 const bbox = svg.getBBox();
539 const availWidth = window.innerWidth - 320;
540 const scaleX = (availWidth - 40) / (bbox.width + 40);
541 const scaleY = (window.innerHeight - 40) / (bbox.height + 40);
542 scale = Math.min(scaleX, scaleY);
543 panX = (availWidth - bbox.width * scale) / 2;
544 panY = (window.innerHeight - bbox.height * scale) / 2;
545 updateTransform(svg);
548 function setupPanZoom(svg) {
549 container.addEventListener('wheel', e => {
551 const rect = container.getBoundingClientRect();
552 const mouseX = e.clientX - rect.left, mouseY = e.clientY - rect.top;
553 const zoomFactor = e.deltaY < 0 ? 1.1 : 0.9;
554 panX = mouseX - (mouseX - panX) * zoomFactor;
555 panY = mouseY - (mouseY - panY) * zoomFactor;
557 updateTransform(svg);
559 container.addEventListener('mousedown', e => {
560 if (e.target.closest('.node')) return;
561 isDragging = true; startX = e.clientX - panX; startY = e.clientY - panY;
563 container.addEventListener('mousemove', e => { if (isDragging) { panX = e.clientX - startX; panY = e.clientY - startY; updateTransform(svg); } });
564 container.addEventListener('mouseup', () => isDragging = false);
565 container.addEventListener('mouseleave', () => isDragging = false);
568 function setupNodeInteraction(svg) {
569 const nodes = svg.querySelectorAll('.node');
570 nodes.forEach(node => {
571 const nodeId = node.id;
572 node.addEventListener('click', e => {
574 selectNode(svg, nodeId);
579 function selectNode(svg, nodeId) {
580 svg.querySelectorAll('.node-highlighted').forEach(n => n.classList.remove('node-highlighted'));
581 svg.querySelectorAll('.edge-highlighted').forEach(e => e.classList.remove('edge-highlighted'));
583 const node = svg.getElementById(nodeId);
586 node.classList.add('node-highlighted');
587 selectedNode = nodeId;
589 const nodeNum = parseInt(nodeId.replace('node_', '').replace('output_', '-'));
590 edges.forEach(([from, to]) => {
591 if (from === nodeNum || to === nodeNum || to === -nodeNum - 1) {
592 svg.querySelectorAll('.edge').forEach(edge => {
593 const title = edge.querySelector('title');
595 const edgeStr = title.textContent;
596 const toId = to < 0 ? `output_${-to - 1}` : `node_${to}`;
597 if (edgeStr.includes(`node_${from}`) && edgeStr.includes(toId)) {
598 edge.classList.add('edge-highlighted');
605 const data = nodeData[nodeId];
607 const nodeLabel = data.short || data.label || '';
608 const fullExpr = data.full || data.label || '';
609 sidebar.innerHTML = `
610 <div class="section">
611 <div class="label">Node ID</div>
612 <div class="value">${data.id} <span class="type-badge type-${data.type}">${data.type}</span></div>
614 <div class="section">
615 <div class="label">Label</div>
616 <div class="value">${escapeHtml(nodeLabel)}</div>
618 ${fullExpr !== nodeLabel ? `<div class="section">
619 <div class="label">Full Expression</div>
620 <div class="value">${escapeHtml(fullExpr)}</div>
622 <div class="section">
623 <div class="label">Dependencies (${data.deps.length})</div>
624 <div class="value">${data.deps.length > 0 ? data.deps.map(d => 'node_' + d).join(', ') : 'None'}</div>
627 } else if (nodeId === 'output' || nodeId.startsWith('output_')) {
628 const outIdx = nodeId.replace('output_', '').replace('output', '0');
629 sidebar.innerHTML = `
630 <div class="section">
631 <div class="label">Node</div>
632 <div class="value">Output[${outIdx}] <span class="type-badge" style="background:#FFD700;color:#000;">output</span></div>
634 <div class="section">
635 <div class="label">Description</div>
636 <div class="value">Output element ${outIdx} of the expression</div>
642 function escapeHtml(str) {
643 return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
664 const std::string &name =
"expression") {
666 std::ostringstream dot_stream;
667 std::ostringstream node_data_stream;
670 std::vector<SymbolicScalar> free_vars = SymbolicScalar::symvar(expr);
672 dot_stream <<
"digraph \"" << name <<
"\" {\n";
673 dot_stream <<
" rankdir=BT;\n";
674 dot_stream <<
" splines=ortho;\n";
675 dot_stream <<
" node [shape=box, style=\"rounded,filled\", fontname=\"Helvetica\"];\n";
676 dot_stream <<
" edge [color=\"#666666\", arrowsize=0.7];\n\n";
677 dot_stream <<
" labelloc=\"t\";\n";
679 dot_stream <<
" fontsize=16;\n\n";
682 std::set<const void *> visited;
683 std::map<const void *, int> ptr_to_id;
684 int node_counter = 0;
686 std::vector<SymbolicScalar> queue = {expr};
687 while (!queue.empty()) {
691 const void *ptr = current.get();
692 if (visited.count(ptr))
695 ptr_to_id[ptr] = node_counter++;
697 casadi_int n = current.n_dep();
698 for (casadi_int i = 0; i < n; ++i) {
699 queue.push_back(current.dep(i));
704 node_data_stream <<
"{";
705 std::ostringstream edges_stream;
707 bool first_node =
true;
708 bool first_edge =
true;
713 while (!queue.empty()) {
717 const void *ptr = current.get();
718 if (visited.count(ptr))
722 int current_id = ptr_to_id[ptr];
725 std::ostringstream full_expr;
726 current.disp(full_expr,
false);
727 std::string full_label = full_expr.str();
731 std::string node_type =
"operation";
732 std::string color =
"#87CEEB";
733 std::string shape =
"box";
735 if (current.is_symbolic()) {
739 }
else if (current.is_constant()) {
742 node_type =
"constant";
743 }
else if (current.n_dep() == 0) {
750 node_data_stream <<
",";
752 node_data_stream <<
"\"node_" << current_id <<
"\":{";
753 node_data_stream <<
"\"id\":" << current_id <<
",";
756 node_data_stream <<
"\"type\":\"" << node_type <<
"\",";
757 node_data_stream <<
"\"deps\":[";
759 casadi_int n = current.n_dep();
760 for (casadi_int i = 0; i < n; ++i) {
762 node_data_stream <<
",";
764 node_data_stream << ptr_to_id[dep.get()];
766 node_data_stream <<
"]}";
768 dot_stream <<
" node_" << current_id <<
" [label=\""
770 <<
"\", shape=" << shape <<
", id=\"node_" << current_id <<
"\"];\n";
772 for (casadi_int i = 0; i < n; ++i) {
774 const void *dep_ptr = dep.get();
775 int dep_id = ptr_to_id[dep_ptr];
776 dot_stream <<
" node_" << dep_id <<
" -> node_" << current_id <<
";\n";
781 edges_stream <<
"[" << dep_id <<
"," << current_id <<
"]";
783 queue.push_back(dep);
788 if (!ptr_to_id.empty()) {
789 const void *out_ptr = expr.get();
790 int out_id = ptr_to_id[out_ptr];
791 dot_stream <<
"\n output [label=\"Output\", shape=doublecircle, fillcolor=\"#FFD700\", "
793 dot_stream <<
" node_" << out_id <<
" -> output;\n";
797 edges_stream <<
"[" << out_id <<
",-1]";
800 node_data_stream <<
"}";
803 std::string dot_content = dot_stream.str();
806 std::string html_filename = filename +
".html";
807 std::ofstream out(html_filename);
808 if (!out.is_open()) {
809 throw RuntimeError(
"Failed to open file for writing: " + html_filename);
831 if (elem.is_symbolic()) {
834 if (elem.is_constant()) {
835 double val =
static_cast<double>(elem);
842 std::ostringstream oss;
843 oss << std::setprecision(4) << val;
846 if (!elem.is_leaf()) {
849 casadi_int op = elem.op();
852 case casadi::OP_ASSIGN:
860 case casadi::OP_SQRT:
872 case casadi::OP_ASIN:
874 case casadi::OP_ACOS:
876 case casadi::OP_ATAN:
878 case casadi::OP_SINH:
880 case casadi::OP_COSH:
882 case casadi::OP_TANH:
884 case casadi::OP_ASINH:
886 case casadi::OP_ACOSH:
888 case casadi::OP_ATANH:
890 case casadi::OP_FABS:
892 case casadi::OP_FLOOR:
894 case casadi::OP_CEIL:
896 case casadi::OP_SIGN:
900 case casadi::OP_ERFINV:
916 case casadi::OP_ATAN2:
918 case casadi::OP_FMIN:
920 case casadi::OP_FMAX:
922 case casadi::OP_FMOD:
924 case casadi::OP_COPYSIGN:
926 case casadi::OP_HYPOT:
944 case casadi::OP_IF_ELSE_ZERO:
945 return "if_else_zero";
948 return "op" + std::to_string(op);
957inline void get_sx_node_style(
const casadi::SXElem &elem, std::string &color, std::string &shape) {
958 if (elem.is_symbolic()) {
961 }
else if (elem.is_constant()) {
964 }
else if (!elem.is_leaf()) {
966 casadi_int op = elem.op();
968 if (op == casadi::OP_ADD || op == casadi::OP_SUB || op == casadi::OP_MUL ||
969 op == casadi::OP_DIV || op == casadi::OP_NEG) {
971 }
else if (op == casadi::OP_SIN || op == casadi::OP_COS || op == casadi::OP_TAN ||
972 op == casadi::OP_ASIN || op == casadi::OP_ACOS || op == casadi::OP_ATAN) {
974 }
else if (op == casadi::OP_EXP || op == casadi::OP_LOG || op == casadi::OP_SQRT ||
975 op == casadi::OP_SQ || op == casadi::OP_POW) {
977 }
else if (op == casadi::OP_LT || op == casadi::OP_LE || op == casadi::OP_EQ ||
978 op == casadi::OP_NE || op == casadi::OP_AND || op == casadi::OP_OR) {
1003 const std::string &name =
"expression") {
1004 std::string dot_filename = filename +
".dot";
1005 std::ofstream out(dot_filename);
1006 if (!out.is_open()) {
1007 throw RuntimeError(
"Failed to open file for writing: " + dot_filename);
1010 out <<
"digraph \"" << name <<
"\" {\n";
1011 out <<
" rankdir=BT;\n";
1012 out <<
" splines=ortho;\n";
1013 out <<
" node [shape=box, style=\"rounded,filled\", fontname=\"Helvetica\"];\n";
1014 out <<
" edge [color=\"#666666\", arrowsize=0.7];\n\n";
1015 out <<
" labelloc=\"t\";\n";
1017 out <<
" fontsize=16;\n\n";
1020 std::map<const void *, int> ptr_to_id;
1021 std::set<const void *> visited;
1022 std::vector<casadi::SXElem> queue;
1023 int node_counter = 0;
1026 const std::vector<casadi::SXElem> &nz = expr.nonzeros();
1027 casadi_int n_elem =
static_cast<casadi_int
>(nz.size());
1030 for (casadi_int i = 0; i < n_elem; ++i) {
1031 queue.push_back(nz[i]);
1035 size_t queue_idx = 0;
1036 while (queue_idx < queue.size()) {
1037 casadi::SXElem current = queue[queue_idx++];
1038 const void *ptr = current.get();
1039 if (visited.count(ptr))
1041 visited.insert(ptr);
1042 ptr_to_id[ptr] = node_counter++;
1044 casadi_int n = current.n_dep();
1045 for (casadi_int i = 0; i < n; ++i) {
1046 queue.push_back(current.dep(i));
1053 for (casadi_int i = 0; i < n_elem; ++i) {
1054 queue.push_back(nz[i]);
1057 std::vector<int> output_node_ids;
1058 for (casadi_int i = 0; i < n_elem; ++i) {
1059 const void *ptr = nz[i].get();
1060 output_node_ids.push_back(ptr_to_id[ptr]);
1064 while (queue_idx < queue.size()) {
1065 casadi::SXElem current = queue[queue_idx++];
1066 const void *ptr = current.get();
1067 if (visited.count(ptr))
1069 visited.insert(ptr);
1071 int current_id = ptr_to_id[ptr];
1073 std::string color, shape;
1077 <<
"\", fillcolor=\"" << color <<
"\", shape=" << shape <<
"];\n";
1079 casadi_int n = current.n_dep();
1080 for (casadi_int i = 0; i < n; ++i) {
1081 casadi::SXElem dep = current.dep(i);
1082 const void *dep_ptr = dep.get();
1083 int dep_id = ptr_to_id[dep_ptr];
1084 out <<
" node_" << dep_id <<
" -> node_" << current_id <<
";\n";
1085 queue.push_back(dep);
1090 if (!output_node_ids.empty()) {
1091 out <<
"\n // Output markers\n";
1092 for (
size_t i = 0; i < output_node_ids.size(); ++i) {
1093 out <<
" output_" << i <<
" [label=\"out[" << i
1094 <<
"]\", shape=doublecircle, fillcolor=\"#FFD700\"];\n";
1095 out <<
" node_" << output_node_ids[i] <<
" -> output_" << i <<
";\n";
1114 const std::string &name =
"expression") {
1115 std::ostringstream dot_stream;
1116 std::ostringstream node_data_stream;
1117 std::ostringstream edges_stream;
1119 dot_stream <<
"digraph \"" << name <<
"\" {\n";
1120 dot_stream <<
" rankdir=BT;\n";
1121 dot_stream <<
" splines=ortho;\n";
1122 dot_stream <<
" node [shape=box, style=\"rounded,filled\", fontname=\"Helvetica\"];\n";
1123 dot_stream <<
" edge [color=\"#666666\", arrowsize=0.7];\n\n";
1124 dot_stream <<
" labelloc=\"t\";\n";
1126 dot_stream <<
" fontsize=16;\n\n";
1129 std::map<const void *, int> ptr_to_id;
1130 std::set<const void *> visited;
1131 std::vector<casadi::SXElem> queue;
1132 int node_counter = 0;
1135 const std::vector<casadi::SXElem> &nz = expr.nonzeros();
1136 casadi_int n_elem =
static_cast<casadi_int
>(nz.size());
1138 for (casadi_int i = 0; i < n_elem; ++i) {
1139 queue.push_back(nz[i]);
1143 size_t queue_idx = 0;
1144 while (queue_idx < queue.size()) {
1145 casadi::SXElem current = queue[queue_idx++];
1146 const void *ptr = current.get();
1147 if (visited.count(ptr))
1149 visited.insert(ptr);
1150 ptr_to_id[ptr] = node_counter++;
1152 casadi_int n = current.n_dep();
1153 for (casadi_int i = 0; i < n; ++i) {
1154 queue.push_back(current.dep(i));
1159 std::vector<int> output_node_ids;
1160 for (casadi_int i = 0; i < n_elem; ++i) {
1161 const void *ptr = nz[i].get();
1162 output_node_ids.push_back(ptr_to_id[ptr]);
1168 for (casadi_int i = 0; i < n_elem; ++i) {
1169 queue.push_back(nz[i]);
1172 node_data_stream <<
"{";
1173 edges_stream <<
"[";
1174 bool first_node =
true;
1175 bool first_edge =
true;
1178 while (queue_idx < queue.size()) {
1179 casadi::SXElem current = queue[queue_idx++];
1180 const void *ptr = current.get();
1181 if (visited.count(ptr))
1183 visited.insert(ptr);
1185 int current_id = ptr_to_id[ptr];
1187 std::string color, shape;
1191 std::string node_type =
"operation";
1192 if (current.is_symbolic())
1193 node_type =
"input";
1194 else if (current.is_constant())
1195 node_type =
"constant";
1199 node_data_stream <<
",";
1201 node_data_stream <<
"\"node_" << current_id <<
"\":{";
1202 node_data_stream <<
"\"id\":" << current_id <<
",";
1204 node_data_stream <<
"\"type\":\"" << node_type <<
"\",";
1205 node_data_stream <<
"\"deps\":[";
1207 casadi_int n = current.n_dep();
1208 for (casadi_int i = 0; i < n; ++i) {
1210 node_data_stream <<
",";
1211 casadi::SXElem dep = current.dep(i);
1212 node_data_stream << ptr_to_id[dep.get()];
1214 node_data_stream <<
"]}";
1217 <<
"\", fillcolor=\"" << color <<
"\", shape=" << shape <<
", id=\"node_"
1218 << current_id <<
"\"];\n";
1220 for (casadi_int i = 0; i < n; ++i) {
1221 casadi::SXElem dep = current.dep(i);
1222 const void *dep_ptr = dep.get();
1223 int dep_id = ptr_to_id[dep_ptr];
1224 dot_stream <<
" node_" << dep_id <<
" -> node_" << current_id <<
";\n";
1227 edges_stream <<
",";
1229 edges_stream <<
"[" << dep_id <<
"," << current_id <<
"]";
1231 queue.push_back(dep);
1236 for (
size_t i = 0; i < output_node_ids.size(); ++i) {
1237 dot_stream <<
" output_" << i <<
" [label=\"out[" << i
1238 <<
"]\", shape=doublecircle, fillcolor=\"#FFD700\", id=\"output_" << i
1240 dot_stream <<
" node_" << output_node_ids[i] <<
" -> output_" << i <<
";\n";
1243 edges_stream <<
",";
1245 edges_stream <<
"[" << output_node_ids[i] <<
",-" << (i + 1) <<
"]";
1248 dot_stream <<
"}\n";
1249 node_data_stream <<
"}";
1250 edges_stream <<
"]";
1252 std::string dot_content = dot_stream.str();
1255 std::string html_filename = filename +
".html";
1256 std::ofstream out(html_filename);
1257 if (!out.is_open()) {
1258 throw RuntimeError(
"Failed to open file for writing: " + html_filename);
1261 std::string stats_js =
"const nodeCount = Object.keys(nodeData).length;\n"
1262 " document.getElementById('stats').textContent = "
1263 "'Nodes: ' + nodeCount + ' | Edges: ' + edges.length;";
1266 edges_stream.str(), stats_js);
1292 const std::string &name =
"") {
1294 std::string graph_name = name.empty() ? fn.name() : name;
1297 casadi::Function expanded = fn.expand();
1300 std::vector<casadi::SX> sx_inputs;
1301 for (casadi_int i = 0; i < expanded.n_in(); ++i) {
1302 sx_inputs.push_back(
1303 casadi::SX::sym(expanded.name_in(i), expanded.size1_in(i), expanded.size2_in(i)));
1307 std::vector<casadi::SX> sx_outputs = expanded(sx_inputs);
1310 casadi::SX combined = casadi::SX::vertcat(sx_outputs);
1338 }
catch (
const std::exception &) {
Custom exception hierarchy for Janus framework.
Core type aliases for numeric and symbolic Eigen/CasADi interop.
Wrapper around casadi::Function providing Eigen-native IO.
Definition Function.hpp:46
Operation failed at runtime (e.g., CasADi eval with free variables).
Definition JanusError.hpp:41
std::string escape_for_json(const std::string &s)
Escape a string for embedding in JSON.
Definition JanusIO.hpp:409
void get_sx_node_style(const casadi::SXElem &elem, std::string &color, std::string &shape)
Get node styling based on SX element type.
Definition JanusIO.hpp:957
std::string get_op_name(const SymbolicScalar &mx)
Get a short description of an MX operation type.
Definition JanusIO.hpp:168
void write_graph_html(std::ostream &out, const std::string &title, const std::string &escaped_dot, const std::string &node_data_json, const std::string &edges_json, const std::string &extra_header_js="")
Write a complete interactive graph HTML page.
Definition JanusIO.hpp:437
std::string escape_dot_label(const std::string &s)
Escape special characters for DOT format.
Definition JanusIO.hpp:130
std::string get_sx_operation(const casadi::SXElem &elem)
Map CasADi SX operation codes to readable labels.
Definition JanusIO.hpp:830
std::string escape_for_js(const std::string &content)
Escape a string for embedding in a JavaScript string literal.
Definition JanusIO.hpp:387
Definition Diagnostics.hpp:19
void export_graph_html(const SymbolicScalar &expr, const std::string &filename, const std::string &name="expression")
Export a symbolic expression to an interactive HTML file.
Definition JanusIO.hpp:663
void export_graph_deep(const casadi::Function &fn, const std::string &filename, DeepGraphFormat format=DeepGraphFormat::HTML, const std::string &name="")
Export a CasADi Function to deep graph format showing all operations.
Definition JanusIO.hpp:1290
JanusMatrix< NumericScalar > NumericMatrix
Eigen::MatrixXd equivalent.
Definition JanusTypes.hpp:66
bool visualize_graph(const SymbolicScalar &expr, const std::string &output_base)
Convenience function: export expression to DOT and render to PDF.
Definition JanusIO.hpp:373
void export_graph_dot(const SymbolicScalar &expr, const std::string &filename, const std::string &name="expression")
Export a symbolic expression to DOT format for visualization.
Definition JanusIO.hpp:221
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
void export_sx_graph_html(const casadi::SX &expr, const std::string &filename, const std::string &name="expression")
Export an SX expression to an interactive HTML file for deep visualization.
Definition JanusIO.hpp:1113
DeepGraphFormat
Export format for deep graph visualization.
Definition JanusIO.hpp:1273
@ DOT
Graphviz DOT text format.
Definition JanusIO.hpp:1274
@ HTML
Self-contained interactive HTML.
Definition JanusIO.hpp:1275
@ PDF
Rendered PDF via Graphviz.
Definition JanusIO.hpp:1276
bool visualize_graph_deep(const casadi::Function &fn, const std::string &output_base)
Convenience function: export Function to deep graph and render to PDF.
Definition JanusIO.hpp:1334
void print(const std::string &label, const Eigen::MatrixBase< Derived > &mat)
Print a matrix to stdout with a label.
Definition JanusIO.hpp:40
auto eval(const Eigen::MatrixBase< Derived > &mat)
Evaluate a symbolic matrix to a numeric Eigen matrix.
Definition JanusIO.hpp:62
void disp(const std::string &label, const Eigen::MatrixBase< Derived > &mat)
Deprecated alias for print.
Definition JanusIO.hpp:52
casadi::MX SymbolicScalar
CasADi MX symbolic scalar.
Definition JanusTypes.hpp:70
void export_sx_graph_dot(const casadi::SX &expr, const std::string &filename, const std::string &name="expression")
Export an SX expression to DOT format for deep visualization.
Definition JanusIO.hpp:1002