Janus uses computational graphs (directed acyclic graphs built by CasADi) to represent symbolic expressions. This guide explains how to export and visualize those graphs for debugging, optimization analysis, teaching, and documentation. Graph visualization works in symbolic mode only, since numeric-mode expressions do not build a traceable graph.
Quick Start
Umbrella header that includes the entire Janus public API.
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
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
T sin(const T &x)
Computes sine of x.
Definition Trig.hpp:21
SymbolicScalar sym(const std::string &name)
Create a named symbolic scalar variable.
Definition JanusTypes.hpp:90
Core API
All functions live in <janus/core/JanusIO.hpp>.
| Function | Description |
| janus::export_graph_dot(expr, filename, title) | Export a symbolic expression to DOT format |
| janus::render_graph(dot_file, output_file) | Render a DOT file to PDF, PNG, or SVG |
| janus::visualize_graph(expr, base) | Convenience wrapper: export DOT then render to PDF |
| janus::export_graph_html(expr, filename, title) | Export an interactive HTML graph with pan/zoom and node details |
- Note
- render_graph and visualize_graph require Graphviz installed. In NixOS, it is included in the dev shell. The HTML export has no external dependencies.
Usage Patterns
What is a Computational Graph?
A computational graph is a DAG where each node is either:
- Input nodes (leaves): Symbolic variables or constants
- Operation nodes (internal): Mathematical operations (+, *, sin, etc.)
- Output node (root): The final result
When you write symbolic expressions in Janus, CasADi builds this graph internally. It enables:
- Automatic differentiation – Traverse the graph to compute gradients
- Code generation – Compile the graph to efficient C code
- Optimization – Symbolic solvers operate on the graph structure
Simple Expression Graph
auto y = x * x + 2.0 * x + 1.0;
This creates a tree structure:
(+)
/ \
(+) 1.0
/ \
(*) (*)
/ \ / \
x x 2 x
Two-Step Export (DOT then Render)
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
Interactive HTML Export
The HTML output is recommended for exploring complex graphs:
- Click nodes to see the full expression in the sidebar
- Pan/zoom with mouse drag and scroll
- Connection highlighting when a node is selected
Understanding Graph Layout
The graph uses bottom-to-top layout (rankdir=BT):
- Inputs (variables and constants) are at the bottom
- Operations build upward
- The final output is at the top
Node colors:
- Green ellipses: Input variables (id, iq, Rs, Ld, etc.)
- Yellow ellipses: Constants (1.5, etc.)
- Blue boxes: Operations (multiply, add, subtract)
- Gold circle: Output node
Shared Subexpressions
CasADi automatically detects common subexpressions. If iq appears in multiple places, the graph reuses the same node – this is a key optimization for automatic differentiation.
Deep vs. Shallow Graphs
| Expression | Depth | Use Case |
| x + y | 1 | Simple operations |
| sin(x) * cos(y) | 2 | Transcendental functions |
| motor.voltage_d(...) | 3-4 | Engineering models |
| P_elec (full motor) | 6+ | Nested function calls |
Real Example: Electric Motor Power
The graph_visualization.cpp example models a Permanent Magnet Synchronous Motor (PMSM).
MotorModel<janus::SymbolicScalar> motor{Rs, Ld, Lq, lambda, p, J, B};
auto Vd = motor.voltage_d(id, iq, did_dt, omega_e);
auto Vq = motor.voltage_q(id, iq, diq_dt, omega_e);
auto P_elec = motor.electrical_power(Vd, Vq, id, iq);
The power expression P = 1.5 * (Vd*id + Vq*iq) expands to include all the intermediate terms from the voltage equations, creating a deep graph.
Advanced Usage
Jacobian Graphs
You can visualize the graph of derivatives:
auto T_e = motor.electromagnetic_torque(id, iq);
auto jacobian(const Expr &expression, const Vars &...variables)
Computes Jacobian of an expression with respect to variables.
Definition AutoDiff.hpp:109
The Jacobian graph shows how CasADi computes gradients by applying the chain rule symbolically.
Practical Applications
- Debugging – Verify your expression has the expected structure
- Optimization analysis – See which variables affect the output
- Teaching – Demonstrate automatic differentiation concepts
- Documentation – Generate figures for papers and reports
Running the Example
cd /path/to/janus
./scripts/build.sh
./build/examples/graph_visualization
# View generated graphs (PDF requires Graphviz)
xdg-open graph_power.pdf
xdg-open graph_dynamics.pdf
# Or open interactive HTML in browser (no dependencies)
xdg-open graph_power.html # Or: explorer.exe graph_power.html (WSL)
xdg-open graph_dynamics.html
See Also