Expand description
Vector-mode forward AD: one pass through the dynamics, N derivatives out.
tang::Dual carries one derivative alongside the value, so extracting an
n-column Jacobian means running the function n times. Every one of those
runs recomputes the same primal: the same kinematic tree walk, the same
sin/cos per joint, the same memory traffic — all to produce one column.
MultiDual<N> carries N derivatives instead. One pass computes the
primal once and propagates N tangent directions through it, so the
primal’s cost is amortised across the whole chunk rather than paid N
times. That is the difference between the adjoint’s nq + nv dual-ABA
passes per timestep and ⌈(nq + nv) / N⌉ of them.
§Lane-for-lane identical to Dual
Every derivative rule here is the same expression as tang::Dual’s, in
the same association order, applied per lane. Lane k of a MultiDual
computation is therefore bit-identical to a scalar Dual computation
seeded in direction k — not merely close. That is what makes this a safe
substitution inside a rollout that promises bitwise reproducibility, and it
is asserted directly by tests::lanes_match_scalar_dual_bitwise over the
full arithmetic and transcendental surface. (Not an intra-doc link: a
#[cfg(test)] item does not exist in a docs build, so linking to one is an
error under -D warnings.)
Widening is therefore never a numerical change, only a speed one. If a
gradient moves when N changes, that is a bug in this file.
§Choosing N
N is a const parameter, so each width is a separate monomorphisation with
its tangent array in registers. Wider is not automatically better: lanes
beyond the column count are wasted arithmetic, so a 1-DOF model wants
N = 1 and pays for anything larger. for_lanes does that dispatch —
smallest supported width that covers the request.
use phyz_diff::multidual::MultiDual;
use tang::Scalar;
// f(x, y) = x² · y, seeded in both directions at once.
let x = MultiDual::<2>::var(3.0, 0);
let y = MultiDual::<2>::var(5.0, 1);
let f = x * x * y;
assert_eq!(f.real, 45.0);
assert_eq!(f.dual[0], 30.0); // ∂f/∂x = 2xy
assert_eq!(f.dual[1], 9.0); // ∂f/∂y = x²Structs§
- Multi
Dual - A real value carrying
Nsimultaneous derivative lanes.
Constants§
- SUPPORTED_
WIDTHS - The widths
for_lanescan dispatch to, narrowest first.