use grass_app::prelude::*;
use grass_scheduler::prelude::*;
use dirt_atom::{DemAtom, MaterialTable};
use soil_core::{Atom, ParticleSimScheduleSet, ParticlesWith, Write};
#[inline]
fn quat_from_axis_angle(axis: [f64; 3], angle: f64) -> [f64; 4] {
let half = angle * 0.5;
let s = half.sin();
[half.cos(), axis[0] * s, axis[1] * s, axis[2] * s]
}
#[inline]
fn quat_mul(a: [f64; 4], b: [f64; 4]) -> [f64; 4] {
[
a[0] * b[0] - a[1] * b[1] - a[2] * b[2] - a[3] * b[3],
a[0] * b[1] + a[1] * b[0] + a[2] * b[3] - a[3] * b[2],
a[0] * b[2] - a[1] * b[3] + a[2] * b[0] + a[3] * b[1],
a[0] * b[3] + a[1] * b[2] - a[2] * b[1] + a[3] * b[0],
]
}
pub struct RotationalDynamicsPlugin;
impl Plugin for RotationalDynamicsPlugin {
fn build(&self, app: &mut App) {
app.add_update_system(initial_rotation, ParticleSimScheduleSet::InitialIntegration)
.add_update_system(final_rotation, ParticleSimScheduleSet::FinalIntegration);
}
}
pub fn initial_rotation(
atoms: Res<Atom>,
particles: ParticlesWith<'_, Write<DemAtom>>,
material_table: Res<MaterialTable>,
) {
particles.with(|mut dem| {
let dt = atoms.dt;
let nlocal = atoms.nlocal as usize;
let track_orientation = material_table.track_orientation;
for i in 0..nlocal {
let inv_inertia = dem.inv_inertia[i];
if inv_inertia == 0.0 {
continue;
}
dem.omega[i][0] += 0.5 * dt * dem.torque[i][0] * inv_inertia;
dem.omega[i][1] += 0.5 * dt * dem.torque[i][1] * inv_inertia;
dem.omega[i][2] += 0.5 * dt * dem.torque[i][2] * inv_inertia;
if track_orientation {
let ox = dem.omega[i][0];
let oy = dem.omega[i][1];
let oz = dem.omega[i][2];
let omega_mag = (ox * ox + oy * oy + oz * oz).sqrt();
let angle = omega_mag * dt;
if angle > 1e-14 {
let inv = 1.0 / omega_mag;
let axis = [ox * inv, oy * inv, oz * inv];
let dq = quat_from_axis_angle(axis, angle);
dem.quaternion[i] = quat_mul(dq, dem.quaternion[i]);
}
}
}
});
}
pub fn final_rotation(atoms: Res<Atom>, particles: ParticlesWith<'_, Write<DemAtom>>) {
particles.with(|mut dem| {
let dt = atoms.dt;
let nlocal = atoms.nlocal as usize;
for i in 0..nlocal {
let inv_inertia = dem.inv_inertia[i];
if inv_inertia == 0.0 {
continue;
}
dem.omega[i][0] += 0.5 * dt * dem.torque[i][0] * inv_inertia;
dem.omega[i][1] += 0.5 * dt * dem.torque[i][1] * inv_inertia;
dem.omega[i][2] += 0.5 * dt * dem.torque[i][2] * inv_inertia;
}
});
}
#[cfg(test)]
mod tests {
use super::*;
use dirt_atom::{DemAtom, MaterialTable};
use dirt_test_utils::push_dem_test_atom;
use soil_core::{Atom, AtomDataRegistry};
#[test]
fn angular_acceleration_from_torque() {
let mut app = App::new();
let radius = 0.001;
let mut atom = Atom::new();
let mut dem = DemAtom::new();
let dt = 1e-7;
atom.dt = dt;
push_dem_test_atom(&mut atom, &mut dem, 0, [0.0; 3], radius);
let mass = atom.mass[0] as f64;
let inertia = 0.4 * mass * radius * radius;
dem.torque[0][2] = 1.0;
atom.nlocal = 1;
atom.natoms = 1;
let mut registry = AtomDataRegistry::new();
registry.try_register(dem, atom.len()).unwrap();
app.add_resource(atom);
app.add_resource(registry);
app.add_resource(MaterialTable::new()); app.add_update_system(initial_rotation, ParticleSimScheduleSet::InitialIntegration);
app.organize_systems();
app.run();
let registry = app.get_resource_ref::<AtomDataRegistry>().unwrap();
let dem = registry.expect::<DemAtom>("test");
let expected_omega_z = 0.5 * dt * 1.0 / inertia;
assert!(
(dem.omega[0][2] - expected_omega_z).abs() < 1e-20,
"omega_z should be {}, got {}",
expected_omega_z,
dem.omega[0][2]
);
}
#[test]
fn quaternion_updates_from_angular_velocity() {
let mut app = App::new();
let radius = 0.001;
let mut atom = Atom::new();
let mut dem = DemAtom::new();
atom.dt = 1e-5;
push_dem_test_atom(&mut atom, &mut dem, 0, [0.0; 3], radius);
dem.omega[0][2] = 100.0;
atom.nlocal = 1;
atom.natoms = 1;
let mut registry = AtomDataRegistry::new();
registry.try_register(dem, atom.len()).unwrap();
app.add_resource(atom);
app.add_resource(registry);
let mut mt = MaterialTable::new();
mt.track_orientation = true; app.add_resource(mt);
app.add_update_system(initial_rotation, ParticleSimScheduleSet::InitialIntegration);
app.organize_systems();
app.run();
let registry = app.get_resource_ref::<AtomDataRegistry>().unwrap();
let dem = registry.expect::<DemAtom>("test");
let q = dem.quaternion[0];
let dot = q[0]; let angle = 2.0 * dot.clamp(-1.0, 1.0).acos();
assert!(
angle > 1e-10,
"quaternion should have rotated, angle = {}",
angle
);
}
}