Skip to main content

Module multidual

Module multidual 

Source
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§

MultiDual
A real value carrying N simultaneous derivative lanes.

Constants§

SUPPORTED_WIDTHS
The widths for_lanes can dispatch to, narrowest first.

Traits§

LaneOp
A computation that is generic over the lane width, for for_lanes.

Functions§

for_lanes
Call f monomorphised at the width width_for picks for lanes.
width_for
The width a chunk of lanes columns should use: the narrowest supported width that covers it, capped at 16.