use phyz_math::Vec3;
use phyz_model::{Model, State};
use phyz_rigid::{forward_kinematics, total_energy};
#[derive(Debug, Clone)]
pub struct ConservationState {
pub baseline_energy: f64,
pub baseline_momentum: Vec3,
pub baseline_angular_momentum: Vec3,
}
impl ConservationState {
pub fn new(model: &Model, state: &State) -> Self {
Self {
baseline_energy: total_energy(model, state),
baseline_momentum: total_momentum(model, state),
baseline_angular_momentum: total_angular_momentum(model, state),
}
}
}
#[derive(Debug, Clone)]
pub struct ConservationMonitor {
pub energy_error: f64,
pub momentum_error: Vec3,
pub angular_momentum_error: Vec3,
}
impl ConservationMonitor {
pub fn check(baseline: &ConservationState, model: &Model, state: &State) -> Self {
let energy = total_energy(model, state);
let momentum = total_momentum(model, state);
let ang_momentum = total_angular_momentum(model, state);
let energy_error = if baseline.baseline_energy.abs() > 1e-12 {
(energy - baseline.baseline_energy).abs() / baseline.baseline_energy.abs()
} else {
(energy - baseline.baseline_energy).abs()
};
Self {
energy_error,
momentum_error: momentum - baseline.baseline_momentum,
angular_momentum_error: ang_momentum - baseline.baseline_angular_momentum,
}
}
pub fn is_violated(&self, energy_tol: f64, momentum_tol: f64, ang_momentum_tol: f64) -> bool {
self.energy_error > energy_tol
|| self.momentum_error.norm() > momentum_tol
|| self.angular_momentum_error.norm() > ang_momentum_tol
}
pub fn max_relative_error(&self) -> f64 {
let mom_rel = self.momentum_error.norm() / (1.0 + self.momentum_error.norm());
let ang_rel =
self.angular_momentum_error.norm() / (1.0 + self.angular_momentum_error.norm());
self.energy_error.max(mom_rel).max(ang_rel)
}
}
pub fn total_momentum(model: &Model, state: &State) -> Vec3 {
let (_xforms, vels) = forward_kinematics(model, state);
let mut momentum = Vec3::zeros();
for (i, body) in model.bodies.iter().enumerate() {
let mass = body.inertia.mass;
let vel_linear = vels[i].linear();
momentum += mass * vel_linear;
}
momentum
}
pub fn total_angular_momentum(model: &Model, state: &State) -> Vec3 {
let (xforms, vels) = forward_kinematics(model, state);
let mut ang_momentum = Vec3::zeros();
for (i, body) in model.bodies.iter().enumerate() {
let mass = body.inertia.mass;
let xf = &xforms[i];
let vel = &vels[i];
let com_local = body.inertia.com;
let com_world = xf.rot.transpose() * com_local + xf.pos;
let vel_linear = vel.linear();
ang_momentum += com_world.cross(&(mass * vel_linear));
let omega = vel.angular();
let inertia_local = body.inertia.inertia;
let inertia_world = xf.rot.transpose() * inertia_local * xf.rot;
ang_momentum += inertia_world * omega;
}
ang_momentum
}
#[cfg(test)]
mod tests {
use super::*;
use phyz_math::{GRAVITY, Mat3, SpatialInertia, SpatialTransform};
use phyz_model::ModelBuilder;
use phyz_rigid::aba;
#[test]
fn test_conservation_monitor_pendulum() {
let model = ModelBuilder::new()
.gravity(Vec3::new(0.0, -GRAVITY, 0.0))
.dt(0.001)
.add_revolute_body(
"pendulum",
-1,
SpatialTransform::identity(),
SpatialInertia::new(
1.0,
Vec3::new(0.0, -0.5, 0.0),
Mat3::from_diagonal(&Vec3::new(0.083, 0.0, 0.083)),
),
)
.build();
let mut state = model.default_state();
state.q[0] = 0.1;
let baseline = ConservationState::new(&model, &state);
for _ in 0..1000 {
let qdd = aba(&model, &state);
state.v += &qdd * model.dt;
state.q += &state.v * model.dt;
}
let monitor = ConservationMonitor::check(&baseline, &model, &state);
assert!(
monitor.energy_error < 0.01,
"Energy error too large: {}",
monitor.energy_error
);
}
#[test]
fn test_momentum_conservation() {
let model = ModelBuilder::new()
.gravity(Vec3::zeros())
.dt(0.01)
.add_revolute_body(
"pendulum",
-1,
SpatialTransform::identity(),
SpatialInertia::new(
1.0,
Vec3::new(0.0, -0.5, 0.0),
Mat3::from_diagonal(&Vec3::new(0.083, 0.0, 0.083)),
),
)
.build();
let mut state = model.default_state();
state.v[0] = 1.0;
let baseline = ConservationState::new(&model, &state);
for _ in 0..100 {
let qdd = aba(&model, &state);
state.v += &qdd * model.dt;
state.q += &state.v * model.dt;
}
let monitor = ConservationMonitor::check(&baseline, &model, &state);
assert!(
monitor.momentum_error.norm() < 1e-8,
"Momentum error too large: {}",
monitor.momentum_error.norm()
);
}
}