How to handle simulations with unknown end-times or event detection in Janus (AutoDiff/Optimization).
The Problem
Standard AutoDiff requires a fixed computational graph.
- You cannot optimize a loop condition (e.g., while (y > 0)).
- If the number of steps changes during optimization, the graph structure changes, breaking the gradient flow.
Pattern 1: Hybrid Pre-Step (Mesh Refinement)
Best for: Long simulations where you need to find an approximate "number of steps" $N$.
Concept
- Pre-Step (Numeric): Run a cheap simulation (no AutoDiff) to find $N$.
- Build Graph (Symbolic): Construct a graph with exactly $N$ steps.
- Optimize: Compute gradients on this fixed graph. If the trajectory changes significantly, re-run the Pre-Step (outer loop).
int steps = find_steps_until_impact_numeric(params);
auto result = simulate_fixed_steps_symbolic(params, steps);
auto jacobian(const Expr &expression, const Vars &...variables)
Computes Jacobian of an expression with respect to variables.
Definition AutoDiff.hpp:109
Pattern 2: Free-Time Formulation (Time Scaling)
Best for: Getting precise event timing (e.g., "Hit target exactly at $t_f$").
Concept
Fix the number of steps $N$ (e.g., 100), and make Time or Timestep ($\Delta t$) an optimization variable.
Problem: Find $T$ such that $y(T) = 0$.
int N = 100;
auto y_final = simulate_scaled_time(y0, v0, T_sym, N);
SymbolicScalar sym(const std::string &name)
Create a named symbolic scalar variable.
Definition JanusTypes.hpp:90
Comparison
| Feature | Pre-Step (Hybrid) | Free-Time (Scaling) |
| Timestep | Fixed ($\Delta t = 0.01$) | Variable ($\Delta t = T/N$) |
| Steps | Variable (determined numerically) | Fixed (constant) |
| Precision | Grid-limited ($\pm \Delta t$) | Exact (continuous) |
| Use Case | Rough trajectory optimization | Exact event detection |
Example Code
See examples/hybrid_sim.cpp for a complete implementation of both patterns.