use crate::math::{Fix128, Vec3Fix};
use crate::pair_potential::{PairPotential, PairPotentialError, Truncated};
use crate::world_participant::{
ObservationSink, Participant, ParticipantFault, ParticipantKind, StateError, SubstepCtx,
};
#[cfg(not(feature = "std"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MdError {
NonPositiveBoxLength,
BoxTooSmall,
LengthMismatch,
NonPositiveMass,
NonPositiveTimestep,
NonPositiveBoltzmannConstant,
TooFewParticles,
Potential {
i: usize,
j: usize,
error: PairPotentialError,
},
}
impl core::fmt::Display for MdError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::NonPositiveBoxLength => f.write_str("periodic box lengths must be positive"),
Self::BoxTooSmall => {
f.write_str("periodic box side is smaller than twice the cutoff radius")
}
Self::LengthMismatch => {
f.write_str("positions, velocities and masses must have the same length")
}
Self::NonPositiveMass => f.write_str("particle masses must be positive"),
Self::NonPositiveTimestep => f.write_str("time step must be positive"),
Self::NonPositiveBoltzmannConstant => {
f.write_str("Boltzmann constant must be positive")
}
Self::TooFewParticles => f.write_str("temperature needs at least two particles"),
Self::Potential { i, j, error } => write!(f, "pair ({i}, {j}): {error}"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for MdError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum MdStepError {
Md(MdError),
Overflow,
PairBelowResolution {
i: usize,
j: usize,
},
}
impl From<MdError> for MdStepError {
fn from(e: MdError) -> Self {
Self::Md(e)
}
}
impl core::fmt::Display for MdStepError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Md(e) => core::fmt::Display::fmt(e, f),
Self::Overflow => f.write_str("a value of the step is outside the fixed-point range"),
Self::PairBelowResolution { i, j } => write!(
f,
"pair ({i}, {j}) is closer than the length resolution of the fixed-point range"
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for MdStepError {}
pub(crate) fn checked_sub(a: Fix128, b: Fix128) -> Option<Fix128> {
let ra = ((a.hi as i128) << 64) | (a.lo as i128);
let rb = ((b.hi as i128) << 64) | (b.lo as i128);
let d = ra.checked_sub(rb)?;
Some(Fix128::from_raw((d >> 64) as i64, d as u64))
}
pub(crate) fn checked_quotient(a: Fix128, b: Fix128) -> Option<Fix128> {
if b.is_zero() {
return None;
}
let mag = |f: Fix128| (((f.hi as i128) << 64) | (f.lo as i128)).unsigned_abs();
if mag(a) / mag(b) >= 1u128 << 63 {
return None;
}
Some(a / b)
}
fn checked_add3(a: Vec3Fix, b: Vec3Fix) -> Option<Vec3Fix> {
Some(Vec3Fix::new(
a.x.checked_add(b.x)?,
a.y.checked_add(b.y)?,
a.z.checked_add(b.z)?,
))
}
fn checked_sub3(a: Vec3Fix, b: Vec3Fix) -> Option<Vec3Fix> {
Some(Vec3Fix::new(
checked_sub(a.x, b.x)?,
checked_sub(a.y, b.y)?,
checked_sub(a.z, b.z)?,
))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PeriodicBox {
lengths: Vec3Fix,
}
fn wrap_scalar(x: Fix128, l: Fix128) -> Fix128 {
if x >= Fix128::ZERO && x < l {
return x;
}
let k = (x / l).floor();
let mut y = x - k * l;
while y < Fix128::ZERO {
y = y + l;
}
while y >= l {
y = y - l;
}
y
}
fn wrap_scalar_checked(x: Fix128, l: Fix128) -> Option<Fix128> {
if x >= Fix128::ZERO && x < l {
return Some(x);
}
let k = checked_quotient(x, l)?.floor();
let mut y = checked_sub(x, k.checked_mul(l)?)?;
while y < Fix128::ZERO {
y = y.checked_add(l)?;
}
while y >= l {
y = checked_sub(y, l)?;
}
Some(y)
}
impl PeriodicBox {
pub fn new(lengths: Vec3Fix) -> Result<Self, MdError> {
if lengths.x <= Fix128::ZERO || lengths.y <= Fix128::ZERO || lengths.z <= Fix128::ZERO {
return Err(MdError::NonPositiveBoxLength);
}
Ok(Self { lengths })
}
pub fn cubic(length: Fix128) -> Result<Self, MdError> {
Self::new(Vec3Fix::new(length, length, length))
}
#[must_use]
pub const fn lengths(&self) -> Vec3Fix {
self.lengths
}
#[must_use]
pub fn wrap(&self, p: Vec3Fix) -> Vec3Fix {
Vec3Fix::new(
wrap_scalar(p.x, self.lengths.x),
wrap_scalar(p.y, self.lengths.y),
wrap_scalar(p.z, self.lengths.z),
)
}
#[must_use]
pub fn minimum_image(&self, d: Vec3Fix) -> Vec3Fix {
let h = Vec3Fix::new(
self.lengths.x.half(),
self.lengths.y.half(),
self.lengths.z.half(),
);
self.wrap(d + h) - h
}
fn wrap_checked(&self, p: Vec3Fix) -> Option<Vec3Fix> {
Some(Vec3Fix::new(
wrap_scalar_checked(p.x, self.lengths.x)?,
wrap_scalar_checked(p.y, self.lengths.y)?,
wrap_scalar_checked(p.z, self.lengths.z)?,
))
}
fn minimum_image_checked(&self, d: Vec3Fix) -> Option<Vec3Fix> {
let h = Vec3Fix::new(
self.lengths.x.half(),
self.lengths.y.half(),
self.lengths.z.half(),
);
checked_sub3(self.wrap_checked(checked_add3(d, h)?)?, h)
}
fn check_cutoff(&self, cutoff: Fix128) -> Result<(), MdError> {
let two_rc = cutoff.double();
if self.lengths.x < two_rc || self.lengths.y < two_rc || self.lengths.z < two_rc {
return Err(MdError::BoxTooSmall);
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PairForces {
pub forces: Vec<Vec3Fix>,
pub potential_energy: Fix128,
}
fn accumulate_pair<P: PairPotential>(
potential: &Truncated<P>,
periodic_box: &PeriodicBox,
positions: &[Vec3Fix],
(i, j): (usize, usize),
cutoff_sq: Fix128,
out: &mut PairForces,
) -> Result<(), MdError> {
let d = periodic_box.minimum_image(positions[i] - positions[j]);
let r_sq = d.length_squared();
if r_sq >= cutoff_sq {
return Ok(());
}
let wrap_err = |error| MdError::Potential { i, j, error };
let r = r_sq.sqrt();
let f = potential.force(r).map_err(wrap_err)?;
let u = potential.energy(r).map_err(wrap_err)?;
let fv = d * (f / r);
out.forces[i] = out.forces[i] + fv;
out.forces[j] = out.forces[j] - fv;
out.potential_energy = out.potential_energy + u;
Ok(())
}
fn accumulate_pair_checked<P: PairPotential>(
potential: &Truncated<P>,
periodic_box: &PeriodicBox,
positions: &[Vec3Fix],
(i, j): (usize, usize),
cutoff_sq: Fix128,
out: &mut PairForces,
) -> Result<(), MdStepError> {
let ovf = MdStepError::Overflow;
let d = periodic_box
.minimum_image_checked(checked_sub3(positions[i], positions[j]).ok_or(ovf)?)
.ok_or(ovf)?;
let r_sq = d.checked_length_squared().ok_or(ovf)?;
if r_sq >= cutoff_sq {
return Ok(());
}
if r_sq.is_zero() && d != Vec3Fix::ZERO {
return Err(MdStepError::PairBelowResolution { i, j });
}
let wrap_err = |error| MdStepError::Md(MdError::Potential { i, j, error });
let r = r_sq.sqrt();
let f = potential.force(r).map_err(wrap_err)?;
let u = potential.energy(r).map_err(wrap_err)?;
let fv = d
.checked_scale(checked_quotient(f, r).ok_or(ovf)?)
.ok_or(ovf)?;
out.forces[i] = checked_add3(out.forces[i], fv).ok_or(ovf)?;
out.forces[j] = checked_sub3(out.forces[j], fv).ok_or(ovf)?;
out.potential_energy = out.potential_energy.checked_add(u).ok_or(ovf)?;
Ok(())
}
fn empty_forces(n: usize) -> PairForces {
PairForces {
forces: vec![Vec3Fix::ZERO; n],
potential_energy: Fix128::ZERO,
}
}
pub fn pair_forces_all_pairs<P: PairPotential>(
potential: &Truncated<P>,
periodic_box: &PeriodicBox,
positions: &[Vec3Fix],
) -> Result<PairForces, MdError> {
periodic_box.check_cutoff(potential.cutoff())?;
let cutoff_sq = potential.cutoff() * potential.cutoff();
let mut out = empty_forces(positions.len());
for i in 0..positions.len() {
for j in (i + 1)..positions.len() {
accumulate_pair(
potential,
periodic_box,
positions,
(i, j),
cutoff_sq,
&mut out,
)?;
}
}
Ok(out)
}
fn cells_along(length: Fix128, cutoff: Fix128) -> usize {
let width = cutoff + cutoff.shr_bits(20);
let n = (length / width).hi;
if n < 1 {
1
} else {
n as usize
}
}
fn cell_coord(x: Fix128, n: usize, length: Fix128) -> usize {
let c = (x * Fix128::from_int(n as i64) / length).hi;
c.clamp(0, n as i64 - 1) as usize
}
fn neighbour_cells(c: usize, n: usize) -> ([usize; 3], usize) {
match n {
1 => ([0, 0, 0], 1),
2 => ([0, 1, 0], 2),
_ => {
let mut a = [(c + n - 1) % n, c, (c + 1) % n];
a.sort_unstable();
(a, 3)
}
}
}
pub fn pair_forces_cell_list<P: PairPotential>(
potential: &Truncated<P>,
periodic_box: &PeriodicBox,
positions: &[Vec3Fix],
) -> Result<PairForces, MdError> {
let cutoff = potential.cutoff();
periodic_box.check_cutoff(cutoff)?;
let cutoff_sq = cutoff * cutoff;
let l = periodic_box.lengths();
let dims = [
cells_along(l.x, cutoff),
cells_along(l.y, cutoff),
cells_along(l.z, cutoff),
];
let coords: Vec<[usize; 3]> = positions
.iter()
.map(|p| {
let p = periodic_box.wrap(*p);
[
cell_coord(p.x, dims[0], l.x),
cell_coord(p.y, dims[1], l.y),
cell_coord(p.z, dims[2], l.z),
]
})
.collect();
let mut out = empty_forces(positions.len());
for_each_cell_pair(dims, &coords, |pair| {
accumulate_pair(
potential,
periodic_box,
positions,
pair,
cutoff_sq,
&mut out,
)
})?;
Ok(out)
}
fn pair_forces_cell_list_checked<P: PairPotential>(
potential: &Truncated<P>,
periodic_box: &PeriodicBox,
positions: &[Vec3Fix],
) -> Result<PairForces, MdStepError> {
let ovf = MdStepError::Overflow;
let cutoff = potential.cutoff();
periodic_box.check_cutoff(cutoff)?;
let cutoff_sq = cutoff.checked_mul(cutoff).ok_or(ovf)?;
let l = periodic_box.lengths();
let along = |length: Fix128| -> Result<usize, MdStepError> {
let width = cutoff.checked_add(cutoff.shr_bits(20)).ok_or(ovf)?;
let n = checked_quotient(length, width).ok_or(ovf)?.hi;
Ok(if n < 1 { 1 } else { n as usize })
};
let dims = [along(l.x)?, along(l.y)?, along(l.z)?];
let coord = |x: Fix128, n: usize, length: Fix128| -> Result<usize, MdStepError> {
let xn = x.checked_mul(Fix128::from_int(n as i64)).ok_or(ovf)?;
let c = checked_quotient(xn, length).ok_or(ovf)?.hi;
Ok(c.clamp(0, n as i64 - 1) as usize)
};
let mut coords: Vec<[usize; 3]> = Vec::with_capacity(positions.len());
for p in positions {
let p = periodic_box.wrap_checked(*p).ok_or(ovf)?;
coords.push([
coord(p.x, dims[0], l.x)?,
coord(p.y, dims[1], l.y)?,
coord(p.z, dims[2], l.z)?,
]);
}
let mut out = empty_forces(positions.len());
for_each_cell_pair(dims, &coords, |pair| {
accumulate_pair_checked(
potential,
periodic_box,
positions,
pair,
cutoff_sq,
&mut out,
)
})?;
Ok(out)
}
fn for_each_cell_pair<E>(
dims: [usize; 3],
coords: &[[usize; 3]],
mut visit: impl FnMut((usize, usize)) -> Result<(), E>,
) -> Result<(), E> {
let n_cells = dims[0] * dims[1] * dims[2];
let flat = |c: [usize; 3]| c[0] + dims[0] * (c[1] + dims[1] * c[2]);
let mut starts = vec![0usize; n_cells + 1];
for c in coords {
starts[flat(*c) + 1] += 1;
}
for k in 0..n_cells {
starts[k + 1] += starts[k];
}
let mut cursor = starts.clone();
let mut members = vec![0usize; coords.len()];
for (idx, c) in coords.iter().enumerate() {
let cell = flat(*c);
members[cursor[cell]] = idx;
cursor[cell] += 1;
}
for (i, ci) in coords.iter().enumerate() {
let (zs, nz) = neighbour_cells(ci[2], dims[2]);
let (ys, ny) = neighbour_cells(ci[1], dims[1]);
let (xs, nx) = neighbour_cells(ci[0], dims[0]);
for &cz in &zs[..nz] {
for &cy in &ys[..ny] {
for &cx in &xs[..nx] {
let cell = flat([cx, cy, cz]);
for &j in &members[starts[cell]..starts[cell + 1]] {
if j > i {
visit((i, j))?;
}
}
}
}
}
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct VelocityVerlet<P> {
potential: Truncated<P>,
periodic_box: PeriodicBox,
positions: Vec<Vec3Fix>,
velocities: Vec<Vec3Fix>,
masses: Vec<Fix128>,
forces: Vec<Vec3Fix>,
potential_energy: Fix128,
}
impl<P: PairPotential> VelocityVerlet<P> {
pub fn new(
potential: Truncated<P>,
periodic_box: PeriodicBox,
positions: Vec<Vec3Fix>,
velocities: Vec<Vec3Fix>,
masses: Vec<Fix128>,
) -> Result<Self, MdError> {
if positions.len() != velocities.len() || positions.len() != masses.len() {
return Err(MdError::LengthMismatch);
}
if masses.iter().any(|m| *m <= Fix128::ZERO) {
return Err(MdError::NonPositiveMass);
}
periodic_box.check_cutoff(potential.cutoff())?;
let positions: Vec<Vec3Fix> = positions.iter().map(|p| periodic_box.wrap(*p)).collect();
let pf = pair_forces_cell_list(&potential, &periodic_box, &positions)?;
Ok(Self {
potential,
periodic_box,
positions,
velocities,
masses,
forces: pf.forces,
potential_energy: pf.potential_energy,
})
}
pub fn step(&mut self, dt: Fix128) -> Result<(), MdError> {
if dt <= Fix128::ZERO {
return Err(MdError::NonPositiveTimestep);
}
let half = dt.half();
let kicks: Vec<Fix128> = self.masses.iter().map(|m| half / *m).collect();
let v_half: Vec<Vec3Fix> = self
.velocities
.iter()
.zip(&self.forces)
.zip(&kicks)
.map(|((v, f), k)| *v + *f * *k)
.collect();
let x_new: Vec<Vec3Fix> = self
.positions
.iter()
.zip(&v_half)
.map(|(x, v)| self.periodic_box.wrap(*x + *v * dt))
.collect();
let pf = pair_forces_cell_list(&self.potential, &self.periodic_box, &x_new)?;
self.velocities = v_half
.iter()
.zip(&pf.forces)
.zip(&kicks)
.map(|((v, f), k)| *v + *f * *k)
.collect();
self.positions = x_new;
self.forces = pf.forces;
self.potential_energy = pf.potential_energy;
Ok(())
}
pub fn try_step(&mut self, dt: Fix128) -> Result<(), MdStepError> {
if dt <= Fix128::ZERO {
return Err(MdError::NonPositiveTimestep.into());
}
let ovf = MdStepError::Overflow;
let half = dt.half();
let kicks = self
.masses
.iter()
.map(|m| checked_quotient(half, *m))
.collect::<Option<Vec<Fix128>>>()
.ok_or(ovf)?;
let kick = |v: &Vec3Fix, f: &Vec3Fix, k: &Fix128| checked_add3(*v, f.checked_scale(*k)?);
let v_half = self
.velocities
.iter()
.zip(&self.forces)
.zip(&kicks)
.map(|((v, f), k)| kick(v, f, k))
.collect::<Option<Vec<Vec3Fix>>>()
.ok_or(ovf)?;
let x_new = self
.positions
.iter()
.zip(&v_half)
.map(|(x, v)| {
self.periodic_box
.wrap_checked(checked_add3(*x, v.checked_scale(dt)?)?)
})
.collect::<Option<Vec<Vec3Fix>>>()
.ok_or(ovf)?;
let pf = pair_forces_cell_list_checked(&self.potential, &self.periodic_box, &x_new)?;
let v_new = v_half
.iter()
.zip(&pf.forces)
.zip(&kicks)
.map(|((v, f), k)| kick(v, f, k))
.collect::<Option<Vec<Vec3Fix>>>()
.ok_or(ovf)?;
self.velocities = v_new;
self.positions = x_new;
self.forces = pf.forces;
self.potential_energy = pf.potential_energy;
Ok(())
}
#[must_use]
pub fn positions(&self) -> &[Vec3Fix] {
&self.positions
}
#[must_use]
pub fn velocities(&self) -> &[Vec3Fix] {
&self.velocities
}
#[must_use]
pub fn forces(&self) -> &[Vec3Fix] {
&self.forces
}
#[must_use]
pub fn masses(&self) -> &[Fix128] {
&self.masses
}
#[must_use]
pub const fn periodic_box(&self) -> &PeriodicBox {
&self.periodic_box
}
#[must_use]
pub const fn potential(&self) -> &Truncated<P> {
&self.potential
}
#[must_use]
pub const fn potential_energy(&self) -> Fix128 {
self.potential_energy
}
#[must_use]
pub fn kinetic_energy(&self) -> Fix128 {
self.masses
.iter()
.zip(&self.velocities)
.fold(Fix128::ZERO, |acc, (m, v)| {
acc + (*m * v.length_squared()).half()
})
}
#[must_use]
pub fn total_energy(&self) -> Fix128 {
self.kinetic_energy() + self.potential_energy
}
#[must_use]
pub fn momentum(&self) -> Vec3Fix {
self.masses
.iter()
.zip(&self.velocities)
.fold(Vec3Fix::ZERO, |acc, (m, v)| acc + *v * *m)
}
pub fn instantaneous_temperature(&self, boltzmann_constant: Fix128) -> Result<Fix128, MdError> {
if boltzmann_constant <= Fix128::ZERO {
return Err(MdError::NonPositiveBoltzmannConstant);
}
let n = self.positions.len();
if n < 2 {
return Err(MdError::TooFewParticles);
}
let dof = Fix128::from_int(3 * n as i64 - 3);
Ok(self.kinetic_energy().double() / (dof * boltzmann_constant))
}
}
pub const MD_PARTICIPANT_KIND: ParticipantKind = ParticipantKind::new(u32::from_be_bytes(*b"MDVV"));
pub const MD_OBS_KINETIC: u32 = 0;
pub const MD_OBS_POTENTIAL: u32 = 1;
pub const MD_OBS_TOTAL: u32 = 2;
pub const MD_OBS_TEMPERATURE: u32 = 3;
const MD_STATE_VERSION: u32 = 1;
const MD_HEADER_LEN: usize = 4 + 8 + 3 * 16 + 16 + 8;
const MD_PARTICLE_LEN: usize = 10 * 16;
#[derive(Debug, Clone)]
pub struct MdParticipant<P> {
system: VelocityVerlet<P>,
boltzmann_constant: Fix128,
digest: u64,
}
impl<P: PairPotential> MdParticipant<P> {
pub fn new(system: VelocityVerlet<P>, boltzmann_constant: Fix128) -> Result<Self, MdError> {
if boltzmann_constant <= Fix128::ZERO {
return Err(MdError::NonPositiveBoltzmannConstant);
}
let mut b = Vec::new();
let l = system.periodic_box.lengths();
for f in [l.x, l.y, l.z, system.potential.cutoff(), boltzmann_constant] {
md_put_fix(&mut b, f);
}
let digest = md_fnv1a64(&b);
Ok(Self {
system,
boltzmann_constant,
digest,
})
}
#[must_use]
pub const fn system(&self) -> &VelocityVerlet<P> {
&self.system
}
#[must_use]
pub const fn boltzmann_constant(&self) -> Fix128 {
self.boltzmann_constant
}
}
impl<P: PairPotential + Send> Participant for MdParticipant<P> {
fn kind(&self) -> ParticipantKind {
MD_PARTICIPANT_KIND
}
fn substep(&mut self, _ctx: &mut SubstepCtx<'_>, h: Fix128) -> Result<(), ParticipantFault> {
self.system.try_step(h).map_err(|e| match e {
MdStepError::Overflow
| MdStepError::PairBelowResolution { .. }
| MdStepError::Md(MdError::Potential {
error: PairPotentialError::Overflow,
..
}) => ParticipantFault::OutOfRange,
_ => ParticipantFault::InvalidState,
})
}
fn observe(&self, out: &mut ObservationSink) {
let k = self.system.kinetic_energy();
let u = self.system.potential_energy();
out.push(MD_OBS_KINETIC, k);
out.push(MD_OBS_POTENTIAL, u);
out.push(MD_OBS_TOTAL, k + u);
if let Ok(t) = self
.system
.instantaneous_temperature(self.boltzmann_constant)
{
out.push(MD_OBS_TEMPERATURE, t);
}
}
fn write_state(&self, out: &mut Vec<u8>) {
let s = &self.system;
out.extend_from_slice(&MD_STATE_VERSION.to_le_bytes());
out.extend_from_slice(&self.digest.to_le_bytes());
let l = s.periodic_box.lengths();
for f in [l.x, l.y, l.z, s.potential.cutoff()] {
md_put_fix(out, f);
}
out.extend_from_slice(&(s.positions.len() as u64).to_le_bytes());
for i in 0..s.positions.len() {
md_put_fix(out, s.masses[i]);
for v in [s.positions[i], s.velocities[i], s.forces[i]] {
for f in [v.x, v.y, v.z] {
md_put_fix(out, f);
}
}
}
md_put_fix(out, s.potential_energy);
}
fn check_state(&self, bytes: &[u8]) -> Result<(), StateError> {
if bytes.len() < MD_HEADER_LEN {
return Err(StateError::Length {
expected: MD_HEADER_LEN,
found: bytes.len(),
});
}
if md_read_u32(bytes, 0) != MD_STATE_VERSION || md_read_u64(bytes, 4) != self.digest {
return Err(StateError::InvalidValue);
}
let l = self.system.periodic_box.lengths();
let own = [l.x, l.y, l.z, self.system.potential.cutoff()];
if (0..4).any(|k| md_get_fix(bytes, 12 + 16 * k) != own[k]) {
return Err(StateError::InvalidValue);
}
let count = md_read_u64(bytes, MD_HEADER_LEN - 8);
let expected = usize::try_from(count)
.ok()
.and_then(|n| n.checked_mul(MD_PARTICLE_LEN))
.and_then(|b| b.checked_add(MD_HEADER_LEN + 16))
.unwrap_or(usize::MAX);
if bytes.len() != expected {
return Err(StateError::Length {
expected,
found: bytes.len(),
});
}
let masses_positive = bytes[MD_HEADER_LEN..bytes.len() - 16]
.chunks_exact(MD_PARTICLE_LEN)
.all(|c| md_get_fix(c, 0) > Fix128::ZERO);
if !masses_positive {
return Err(StateError::InvalidValue);
}
Ok(())
}
fn read_state(&mut self, bytes: &[u8]) {
let body = &bytes[MD_HEADER_LEN..bytes.len() - 16];
let n = body.len() / MD_PARTICLE_LEN;
let s = &mut self.system;
s.masses = Vec::with_capacity(n);
s.positions = Vec::with_capacity(n);
s.velocities = Vec::with_capacity(n);
s.forces = Vec::with_capacity(n);
for c in body.chunks_exact(MD_PARTICLE_LEN) {
let v = |k: usize| {
Vec3Fix::new(
md_get_fix(c, 16 + 48 * k),
md_get_fix(c, 32 + 48 * k),
md_get_fix(c, 48 + 48 * k),
)
};
s.masses.push(md_get_fix(c, 0));
s.positions.push(v(0));
s.velocities.push(v(1));
s.forces.push(v(2));
}
s.potential_energy = md_get_fix(bytes, bytes.len() - 16);
}
}
fn md_fnv1a64(bytes: &[u8]) -> u64 {
bytes.iter().fold(0xcbf2_9ce4_8422_2325_u64, |h, &b| {
(h ^ u64::from(b)).wrapping_mul(0x0000_0100_0000_01b3)
})
}
fn md_put_fix(out: &mut Vec<u8>, f: Fix128) {
out.extend_from_slice(&f.hi.to_le_bytes());
out.extend_from_slice(&f.lo.to_le_bytes());
}
fn md_read_u32(b: &[u8], at: usize) -> u32 {
let mut a = [0u8; 4];
a.copy_from_slice(&b[at..at + 4]);
u32::from_le_bytes(a)
}
fn md_read_u64(b: &[u8], at: usize) -> u64 {
let mut a = [0u8; 8];
a.copy_from_slice(&b[at..at + 8]);
u64::from_le_bytes(a)
}
fn md_get_fix(b: &[u8], at: usize) -> Fix128 {
Fix128::from_raw(md_read_u64(b, at) as i64, md_read_u64(b, at + 8))
}