Janus 2.0.0
High-performance C++20 dual-mode numerical framework
Loading...
Searching...
No Matches
ODE Integration

Janus provides explicit ODE integrators for simulation with dual-mode (numeric/symbolic) support, plus structure-preserving and stiff mass-matrix integrators for the cases where generic RK integration is the wrong tool. All step functions work in both numeric and symbolic modes, making them suitable for simulation, trajectory optimization, and sensitivity analysis.

Quick Start

#include <janus/janus.hpp>
// Define dynamics: dy/dt = f(t, y)
auto dynamics = [](double t, const janus::NumericVector& y) {
return -0.5 * y; // Exponential decay
};
y(0) = 1.0;
double t = 0.0, dt = 0.1;
// Single step
y = janus::rk4_step(dynamics, y, t, dt);
Umbrella header that includes the entire Janus public API.
JanusVector< Scalar > rk4_step(Func &&f, const JanusVector< Scalar > &x, Scalar t, Scalar dt)
Classic 4th-order Runge-Kutta integration step.
Definition IntegratorStep.hpp:131
JanusVector< NumericScalar > NumericVector
Eigen::VectorXd equivalent.
Definition JanusTypes.hpp:67

Core API

Step Integrators

Function Order Evaluations Use Case
janus::euler_step 1st 1 Fast, low accuracy
janus::rk2_step 2nd 2 Moderate accuracy
janus::rk4_step 4th 4 General purpose
janus::rk45_step 4th/5th 7 Adaptive stepping
janus::stormer_verlet_step 2nd 2 accel evals Symplectic mechanical/orbital systems
janus::rkn4_step 4th 4 accel evals Second-order systems without state augmentation

Trajectory Solvers

  • janus::solve_ivp(dynamics, t_span, y0, n_eval): Full trajectory integration for first-order systems.
  • janus::solve_second_order_ivp(accel, t_span, q0, v0, n_eval, opts): Trajectory integration for second-order systems with separate coordinates and velocities.
  • janus::solve_ivp_mass_matrix(rhs, mass, t_span, y0, n_eval, opts): Stiff and mass-matrix systems M(t,y) y' = f(t,y).

Usage Patterns

RK4 Step (Recommended)

// Harmonic oscillator: y'' = -w^2*y
// State: [y, v] where dy/dt=v, dv/dt=-w^2*y
double omega = 2.0;
auto dynamics = [omega](double t, const janus::NumericVector& s) {
ds << s(1), -omega * omega * s(0);
return ds;
};
state << 1.0, 0.0; // y=1, v=0
double t = 0.0;
for (int i = 0; i < 100; ++i) {
state = janus::rk4_step(dynamics, state, t, 0.01);
t += 0.01;
}

Adaptive Stepping with RK45

double dt = 0.1;
double tol = 1e-6;
while (t < t_final) {
auto result = janus::rk45_step(dynamics, y, t, dt);
if (result.error < tol) {
y = result.y5; // Accept step
t += dt;
dt *= 1.5; // Grow step
} else {
dt *= 0.5; // Shrink step and retry
}
}
RK45Result< Scalar > rk45_step(Func &&f, const JanusVector< Scalar > &x, Scalar t, Scalar dt)
Dormand-Prince RK45 integration step with embedded error estimate.
Definition IntegratorStep.hpp:250

Structure-Preserving Second-Order Steppers

When your dynamics are naturally written as q'' = a(t, q), it is better to integrate that form directly than to augment it into a generic first-order state and run plain RK4.

q << 1.0;
v << 0.0;
auto accel = [](double t, const janus::NumericVector& q_state) {
return (-q_state).eval(); // Harmonic oscillator
};
auto step = janus::stormer_verlet_step(accel, q, v, 0.0, 0.1);
q = step.q;
v = step.v;
SecondOrderStepResult< Scalar > stormer_verlet_step(AccelFunc &&acceleration, const JanusVector< Scalar > &q, const JanusVector< Scalar > &v, Scalar t, Scalar dt)
Stormer-Verlet / velocity-Verlet step for q'' = a(t, q).
Definition IntegratorStep.hpp:167
  • janus::stormer_verlet_step(...) is symplectic and keeps long-horizon energy error bounded for separable Hamiltonian systems.
  • janus::rkn4_step(...) is a higher-order Runge-Kutta-Nystrom method for the same q'' = a(t, q) API.

Trajectory Solver

For full trajectory integration, use solve_ivp:

auto sol = janus::solve_ivp(
dynamics,
{0.0, 10.0}, // t_span
{1.0}, // y0 (initializer list)
100 // n_eval points
);
// sol.t - time points
// sol.y - solution matrix (rows=states, cols=time)
OdeResult< double > solve_ivp(Func &&fun, std::pair< double, double > t_span, const NumericVector &y0, int n_eval=100, double abstol=1e-8, double reltol=1e-6)
Solve initial value problem: dy/dt = f(t, y), y(t0) = y0.
Definition Integrate.hpp:481

For second-order systems, use solve_second_order_ivp(...) to keep coordinates and velocities separate:

accel,
{0.0, 100.0},
{1.0}, // q0
{0.0}, // v0
1000,
opts
);
// sol.q - generalized coordinates, rows = coordinates, cols = time
// sol.v - generalized velocities, rows = velocities, cols = time
@ StormerVerlet
Symplectic Stormer-Verlet / velocity-Verlet.
Definition Integrate.hpp:73
SecondOrderOdeResult< double > solve_second_order_ivp(AccelFunc &&acceleration, std::pair< double, double > t_span, const NumericVector &q0, const NumericVector &v0, int n_eval=100, const SecondOrderIvpOptions &opts={})
Solve a second-order IVP q'' = a(t, q), q(t0) = q0, v(t0) = v0.
Definition Integrate.hpp:558
Options for second-order structure-preserving trajectory integration.
Definition Integrate.hpp:113
SecondOrderIntegratorMethod method
Definition Integrate.hpp:114

Stiff and Mass-Matrix Systems

For systems of the form M(t, y) y' = f(t, y), Janus provides a dedicated solver surface:

opts.substeps = 2;
[](double t, const janus::NumericVector& y) {
rhs << y(1), 1.0 - y(0) - y(1);
return rhs;
},
[](double t, const janus::NumericVector& y) {
janus::NumericMatrix M = janus::NumericMatrix::Zero(2, 2);
M(0, 0) = 1.0; // singular mass matrix encoding an algebraic constraint
return M;
},
{0.0, 1.0},
{0.0, 1.0},
50,
opts
);
JanusMatrix< NumericScalar > NumericMatrix
Eigen::MatrixXd equivalent.
Definition JanusTypes.hpp:66
@ Bdf1
Backward Euler / first-order BDF with Newton iterations.
Definition Integrate.hpp:82
OdeResult< double > solve_ivp_mass_matrix(RhsFunc &&rhs, MassFunc &&mass_matrix, std::pair< double, double > t_span, const NumericVector &y0, int n_eval=100, const MassMatrixIvpOptions &opts={})
Solve M(t, y) y' = f(t, y) with a native numeric stiff integrator.
Definition Integrate.hpp:638
Options for stiff mass-matrix integration.
Definition Integrate.hpp:121
int substeps
Definition Integrate.hpp:123
MassMatrixIntegratorMethod method
Definition Integrate.hpp:122
  • MassMatrixIntegratorMethod::RosenbrockEuler is a one-stage linearly implicit stiff integrator.
  • MassMatrixIntegratorMethod::Bdf1 solves the backward-Euler residual directly and can handle simple singular mass-matrix systems.
  • solve_ivp_mass_matrix_expr(...) uses CasADi IDAS on the symbolic expression path by rewriting the mass-matrix system into a semi-explicit DAE.

Symbolic Mode

All step functions work with symbolic types for optimization:

auto x = janus::sym_vec("x", 2);
auto t = janus::sym("t");
auto dt = janus::sym("dt");
auto x_next = janus::rk4_step(
[](auto t, const auto& x) {
dx << x(1), -4.0 * x(0);
return dx;
},
x, t, dt
);
// x_next is symbolic - can be used in optimization
casadi::Function step_fn("step", {janus::to_mx(x), t, dt}, {janus::to_mx(x_next)});
JanusVector< SymbolicScalar > SymbolicVector
Eigen vector of MX elements.
Definition JanusTypes.hpp:72
SymbolicVector sym_vec(const std::string &name, int size)
Create a symbolic vector preserving the CasADi primitive connection.
Definition JanusTypes.hpp:137
casadi::MX to_mx(const Eigen::MatrixBase< Derived > &e)
Convert Eigen matrix of MX (or numeric) to CasADi MX.
Definition JanusTypes.hpp:189
SymbolicScalar sym(const std::string &name)
Create a named symbolic scalar variable.
Definition JanusTypes.hpp:90

The structure-preserving step APIs are also symbolic-safe:

auto q = janus::sym_vec("q", 1);
auto v = janus::sym_vec("v", 1);
auto t = janus::sym("t");
auto dt = janus::sym("dt");
[](auto time, const auto& q_state) { return (-q_state).eval(); },
q, v, t, dt
);

Component-Based Integration (Icarus Pattern)

For simulation frameworks with component models:

template <typename Scalar>
class IntegrableState {
public:
virtual JanusVector<Scalar> get_state() const = 0;
virtual void set_state(const JanusVector<Scalar>& s) = 0;
virtual JanusVector<Scalar> compute_derivative(Scalar t) const = 0;
};
class RigidBody : public IntegrableState<double> {
Vec3<double> position, velocity, acceleration;
JanusVector<double> get_state() const override { /* ... */ }
void set_state(const JanusVector<double>& s) override { /* ... */ }
JanusVector<double> compute_derivative(double t) const override {
NumericVector dydt(6);
dydt << velocity, acceleration;
return dydt;
}
};
JanusVector< NumericScalar > NumericVector
Eigen::VectorXd equivalent.
Definition JanusTypes.hpp:67

See Also