extern crate nalgebra;
use crate::atom::*;
use crate::constant;
use crate::initiate::NewlyCreated;
use specs::prelude::*;
pub struct Step {
pub n: u64,
}
pub struct Timestep {
pub delta: f64,
}
pub struct EulerIntegrationSystem;
impl<'a> System<'a> for EulerIntegrationSystem {
type SystemData = (
WriteStorage<'a, Position>,
WriteStorage<'a, Velocity>,
ReadExpect<'a, Timestep>,
WriteExpect<'a, Step>,
ReadStorage<'a, Force>,
ReadStorage<'a, Mass>,
);
fn run(&mut self, (mut pos, mut vel, t, mut step, force, mass): Self::SystemData) {
use rayon::prelude::*;
step.n += 1;
(&mut vel, &mut pos, &force, &mass).par_join().for_each(
|(vel, pos, force, mass)| {
euler_update(vel, pos, force, mass, t.delta);
},
);
}
}
pub const INTEGRATE_POSITION_SYSTEM_NAME: &str = "integrate_position";
pub struct VelocityVerletIntegratePositionSystem;
impl<'a> System<'a> for VelocityVerletIntegratePositionSystem {
type SystemData = (
WriteStorage<'a, Position>,
ReadStorage<'a, Velocity>,
ReadExpect<'a, Timestep>,
WriteExpect<'a, Step>,
ReadStorage<'a, Force>,
WriteStorage<'a, OldForce>,
ReadStorage<'a, Mass>,
);
fn run(&mut self, (mut pos, vel, t, mut step, force, mut old_force, mass): Self::SystemData) {
use rayon::prelude::*;
step.n += 1;
let dt = t.delta;
(&mut pos, &vel, &mut old_force, &force, &mass)
.par_join()
.for_each(|(mut pos, vel, mut old_force, force, mass)| {
pos.pos = pos.pos
+ vel.vel * dt
+ force.force / (constant::AMU * mass.value) / 2.0 * dt * dt;
old_force.0 = *force;
});
}
}
pub const INTEGRATE_VELOCITY_SYSTEM_NAME: &str = "integrate_velocity";
pub struct VelocityVerletIntegrateVelocitySystem;
impl<'a> System<'a> for VelocityVerletIntegrateVelocitySystem {
type SystemData = (
WriteStorage<'a, Velocity>,
ReadExpect<'a, Timestep>,
ReadStorage<'a, Force>,
ReadStorage<'a, OldForce>,
ReadStorage<'a, Mass>,
);
fn run(&mut self, (mut vel, t, force, old_force, mass): Self::SystemData) {
use rayon::prelude::*;
let dt = t.delta;
(&mut vel, &force, &old_force, &mass).par_join().for_each(
|(vel, force, old_force, mass)| {
vel.vel += (force.force + old_force.0.force) / (constant::AMU * mass.value) / 2.0 * dt;
},
);
}
}
pub struct AddOldForceToNewAtomsSystem;
impl<'a> System<'a> for AddOldForceToNewAtomsSystem {
type SystemData = (
Entities<'a>,
ReadStorage<'a, NewlyCreated>,
ReadStorage<'a, OldForce>,
Read<'a, LazyUpdate>,
);
fn run(&mut self, (ent, newly_created, old_force, updater): Self::SystemData) {
for (ent, _, _) in (&ent, &newly_created, !&old_force).join() {
updater.insert(ent, OldForce::default());
}
}
}
#[derive(Default)]
pub struct OldForce(Force);
impl Component for OldForce {
type Storage = VecStorage<OldForce>;
}
fn euler_update(vel: &mut Velocity, pos: &mut Position, force: &Force, mass: &Mass, dt: f64) {
pos.pos += vel.vel * dt;
vel.vel += force.force * dt / (constant::AMU * mass.value);
}
pub mod tests {
#[allow(unused_imports)]
use super::*;
extern crate specs;
#[allow(unused_imports)]
use specs::{Builder, DispatcherBuilder, World};
extern crate nalgebra;
#[allow(unused_imports)]
use nalgebra::Vector3;
#[test]
fn test_euler() {
let mut pos = Position {
pos: Vector3::new(1., 1., 1.),
};
let mut vel = Velocity {
vel: Vector3::new(0., 1., 0.),
};
let time = 1.;
let mass = Mass {
value: 1. / constant::AMU,
};
let force = Force {
force: Vector3::new(1., 1., 1.),
};
euler_update(&mut vel, &mut pos, &force, &mass, time);
assert_eq!(vel.vel, Vector3::new(1., 2., 1.));
assert_eq!(pos.pos, Vector3::new(1., 2., 1.));
}
#[test]
fn test_euler_integration() {
let mut world = World::new();
let mut dispatcher = DispatcherBuilder::new()
.with(EulerIntegrationSystem, "integrator", &[])
.build();
dispatcher.setup(&mut world);
let force = Vector3::new(1.0, 0.0, 0.0);
let mass = 1.0;
let atom = world
.create_entity()
.with(Position {
pos: Vector3::new(0.0, 0.0, 0.0),
})
.with(Velocity {
vel: Vector3::new(0.0, 0.0, 0.0),
})
.with(Force { force })
.with(Mass {
value: mass / constant::AMU,
})
.build();
let dt = 1.0e-3;
world.insert(Timestep { delta: dt });
world.insert(Step { n: 0 });
let n_steps = 1_000;
for _i in 0..n_steps {
dispatcher.dispatch(&world);
world.maintain();
}
let a = force / mass;
let expected_v = a * (n_steps as f64 * dt);
assert_approx_eq::assert_approx_eq!(
expected_v.norm(),
world
.read_storage::<Velocity>()
.get(atom)
.expect("atom not found.")
.vel
.norm(),
expected_v.norm() * 0.01
);
let expected_x = a * (n_steps as f64 * dt).powi(2) / 2.0;
assert_approx_eq::assert_approx_eq!(
expected_x.norm(),
world
.read_storage::<Position>()
.get(atom)
.expect("atom not found.")
.pos
.norm(),
expected_x.norm() * 0.01
);
}
#[test]
fn test_add_old_force_system() {
let mut test_world = World::new();
let mut dispatcher = DispatcherBuilder::new()
.with(AddOldForceToNewAtomsSystem, "", &[])
.build();
dispatcher.setup(&mut test_world);
test_world.register::<OldForce>();
let test_entity = test_world.create_entity().with(NewlyCreated {}).build();
dispatcher.dispatch(&test_world);
test_world.maintain();
let old_forces = test_world.read_storage::<OldForce>();
assert!(
old_forces.contains(test_entity),
"OldForce component not added to test entity."
);
}
#[test]
fn test_velocity_verlet_integration() {
let mut world = World::new();
let mut dispatcher = DispatcherBuilder::new()
.with(
VelocityVerletIntegratePositionSystem,
"integrate_position",
&[],
)
.with(
VelocityVerletIntegrateVelocitySystem,
"integrate_velocity",
&["integrate_position"],
)
.build();
dispatcher.setup(&mut world);
let force = Vector3::new(1.0, 0.0, 0.0);
let mass = 1.0;
let atom = world
.create_entity()
.with(Position {
pos: Vector3::new(0.0, 0.0, 0.0),
})
.with(Velocity {
vel: Vector3::new(0.0, 0.0, 0.0),
})
.with(Force { force })
.with(OldForce {
0: Force { force },
})
.with(Mass {
value: mass / constant::AMU,
})
.build();
let dt = 1.0e-3;
world.insert(Timestep { delta: dt });
world.insert(Step { n: 0 });
let n_steps = 1_000;
for _i in 0..n_steps {
dispatcher.dispatch(&world);
world.maintain();
}
let a = force / mass;
let expected_v = a * (n_steps as f64 * dt);
assert_approx_eq::assert_approx_eq!(
expected_v.norm(),
world
.read_storage::<Velocity>()
.get(atom)
.expect("atom not found.")
.vel
.norm(),
expected_v.norm() * 0.01
);
let expected_x = a * (n_steps as f64 * dt).powi(2) / 2.0;
assert_approx_eq::assert_approx_eq!(
expected_x.norm(),
world
.read_storage::<Position>()
.get(atom)
.expect("atom not found.")
.pos
.norm(),
expected_x.norm() * 0.01
);
}
}