use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use phyz_diff::symbolic::CompiledStepJacobians;
use phyz_diff::{finite_diff_jacobians, semi_implicit_step_jacobians};
use phyz_math::{GRAVITY, Mat3, SpatialInertia, SpatialTransform, Vec3};
use phyz_model::{Model, ModelBuilder};
fn make_pendulum() -> Model {
ModelBuilder::new()
.gravity(Vec3::new(0.0, -GRAVITY, 0.0))
.dt(0.001)
.add_revolute_body(
"link1",
-1,
SpatialTransform::identity(),
SpatialInertia::new(
1.0,
Vec3::new(0.0, -0.5, 0.0),
Mat3::from_diagonal(&Vec3::new(1.0 / 12.0, 0.0, 1.0 / 12.0)),
),
)
.build()
}
fn make_double_pendulum() -> Model {
let length = 1.0;
let mass = 1.0;
ModelBuilder::new()
.gravity(Vec3::new(0.0, -GRAVITY, 0.0))
.dt(0.001)
.add_revolute_body(
"link1",
-1,
SpatialTransform::identity(),
SpatialInertia::new(
mass,
Vec3::new(0.0, -length / 2.0, 0.0),
Mat3::from_diagonal(&Vec3::new(
mass * length * length / 12.0,
0.0,
mass * length * length / 12.0,
)),
),
)
.add_revolute_body(
"link2",
0,
SpatialTransform::from_translation(Vec3::new(0.0, -length, 0.0)),
SpatialInertia::new(
mass,
Vec3::new(0.0, -length / 2.0, 0.0),
Mat3::from_diagonal(&Vec3::new(
mass * length * length / 12.0,
0.0,
mass * length * length / 12.0,
)),
),
)
.build()
}
fn make_chain(n: usize) -> Model {
let length = 1.0;
let mass = 1.0;
let inertia = SpatialInertia::new(
mass,
Vec3::new(0.0, -length / 2.0, 0.0),
Mat3::from_diagonal(&Vec3::new(
mass * length * length / 12.0,
0.0,
mass * length * length / 12.0,
)),
);
let mut builder = ModelBuilder::new()
.gravity(Vec3::new(0.0, -GRAVITY, 0.0))
.dt(0.001);
for i in 0..n {
let parent = if i == 0 { -1 } else { (i - 1) as i32 };
let xf = if i == 0 {
SpatialTransform::identity()
} else {
SpatialTransform::from_translation(Vec3::new(0.0, -length, 0.0))
};
builder = builder.add_revolute_body(&format!("link{}", i + 1), parent, xf, inertia);
}
builder.build()
}
fn random_state(model: &Model) -> phyz_model::State {
let mut state = model.default_state();
for i in 0..model.nq {
state.q[i] = 0.3 + 0.1 * i as f64;
}
for i in 0..model.nv {
state.v[i] = 0.1 - 0.05 * i as f64;
}
state
}
fn pack_inputs(state: &phyz_model::State, nq: usize, nv: usize) -> Vec<f64> {
let mut inputs = Vec::with_capacity(nq + nv + nv);
inputs.extend_from_slice(state.q.as_slice());
inputs.extend_from_slice(state.v.as_slice());
inputs.extend_from_slice(state.ctrl.as_slice());
inputs
}
fn bench_single_pendulum(c: &mut Criterion) {
let mut group = c.benchmark_group("single_pendulum");
let model = make_pendulum();
let state = random_state(&model);
let inputs = pack_inputs(&state, model.nq, model.nv);
group.bench_function("fd_jacobians", |b| {
b.iter(|| finite_diff_jacobians(&model, &state, 1e-6));
});
group.bench_function("analytical_jacobians", |b| {
b.iter(|| semi_implicit_step_jacobians(&model, &state));
});
group.bench_function("symbolic_build", |b| {
b.iter(|| CompiledStepJacobians::build(&model));
});
let compiled = CompiledStepJacobians::build(&model);
group.bench_function("symbolic_eval", |b| {
b.iter(|| compiled.eval(&inputs));
});
group.finish();
}
fn bench_double_pendulum(c: &mut Criterion) {
let mut group = c.benchmark_group("double_pendulum");
let model = make_double_pendulum();
let state = random_state(&model);
let inputs = pack_inputs(&state, model.nq, model.nv);
group.bench_function("fd_jacobians", |b| {
b.iter(|| finite_diff_jacobians(&model, &state, 1e-6));
});
group.bench_function("analytical_jacobians", |b| {
b.iter(|| semi_implicit_step_jacobians(&model, &state));
});
group.bench_function("symbolic_build", |b| {
b.iter(|| CompiledStepJacobians::build(&model));
});
let compiled = CompiledStepJacobians::build(&model);
group.bench_function("symbolic_eval", |b| {
b.iter(|| compiled.eval(&inputs));
});
group.finish();
}
fn bench_chain_scaling(c: &mut Criterion) {
let mut group = c.benchmark_group("chain_scaling");
group.sample_size(20);
for &n in &[1, 2, 4] {
let model = make_chain(n);
let state = random_state(&model);
let inputs = pack_inputs(&state, model.nq, model.nv);
group.bench_with_input(BenchmarkId::new("fd_jacobians", n), &n, |b, _| {
b.iter(|| finite_diff_jacobians(&model, &state, 1e-6));
});
group.bench_with_input(BenchmarkId::new("analytical_jacobians", n), &n, |b, _| {
b.iter(|| semi_implicit_step_jacobians(&model, &state));
});
group.bench_with_input(BenchmarkId::new("symbolic_build", n), &n, |b, _| {
b.iter(|| CompiledStepJacobians::build(&model));
});
let compiled = CompiledStepJacobians::build(&model);
group.bench_with_input(BenchmarkId::new("symbolic_eval", n), &n, |b, _| {
b.iter(|| compiled.eval(&inputs));
});
}
group.finish();
}
fn bench_compiled_reuse(c: &mut Criterion) {
let mut group = c.benchmark_group("compiled_reuse");
group.sample_size(10);
for &n in &[1, 2, 4] {
let model = make_chain(n);
let states: Vec<Vec<f64>> = (0..100)
.map(|seed| {
let mut state = model.default_state();
for i in 0..model.nq {
state.q[i] = 0.1 * ((seed * 7 + i * 13) % 31) as f64 / 31.0;
}
for i in 0..model.nv {
state.v[i] = 0.05 * ((seed * 11 + i * 17) % 31) as f64 / 31.0;
}
pack_inputs(&state, model.nq, model.nv)
})
.collect();
let fd_states: Vec<phyz_model::State> = (0..100)
.map(|seed| {
let mut state = model.default_state();
for i in 0..model.nq {
state.q[i] = 0.1 * ((seed * 7 + i * 13) % 31) as f64 / 31.0;
}
for i in 0..model.nv {
state.v[i] = 0.05 * ((seed * 11 + i * 17) % 31) as f64 / 31.0;
}
state
})
.collect();
group.bench_with_input(BenchmarkId::new("fd_100_evals", n), &n, |b, _| {
b.iter(|| {
for s in &fd_states {
std::hint::black_box(finite_diff_jacobians(&model, s, 1e-6));
}
});
});
group.bench_with_input(
BenchmarkId::new("symbolic_build_plus_100_evals", n),
&n,
|b, _| {
b.iter(|| {
let compiled = CompiledStepJacobians::build(&model);
for inputs in &states {
std::hint::black_box(compiled.eval(inputs));
}
});
},
);
let compiled = CompiledStepJacobians::build(&model);
group.bench_with_input(
BenchmarkId::new("symbolic_100_evals_prebuilt", n),
&n,
|b, _| {
b.iter(|| {
for inputs in &states {
std::hint::black_box(compiled.eval(inputs));
}
});
},
);
}
group.finish();
}
criterion_group!(
benches,
bench_single_pendulum,
bench_double_pendulum,
bench_chain_scaling,
bench_compiled_reuse,
);
criterion_main!(benches);