use core::fmt;
use crate::threading::parallel_chunk_len;
use rayon::prelude::*;
#[derive(Debug, Clone, PartialEq)]
pub enum BoundaryError {
InvalidBounds {
axis: usize,
min: f64,
max: f64,
},
InvalidVectorDimension {
label: &'static str,
expected: usize,
got: usize,
},
InvalidFlatVectorListLength {
label: &'static str,
dim: usize,
len: usize,
},
InconsistentFlatVectorListLength {
expected: usize,
got: usize,
},
}
impl fmt::Display for BoundaryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidBounds { axis, min, max } => write!(
f,
"boundary bounds on axis {axis} must be finite with min < max and a representable required span; got min={min}, max={max}"
),
Self::InvalidVectorDimension {
label,
expected,
got,
} => write!(f, "{label} vector has dimension {got}; expected {expected}"),
Self::InvalidFlatVectorListLength { label, dim, len } => write!(
f,
"flat {label} data has length {len}, which is not divisible by dimension {dim}"
),
Self::InconsistentFlatVectorListLength { expected, got } => write!(
f,
"flat velocity data has length {got}; expected {expected} to match positions"
),
}
}
}
impl std::error::Error for BoundaryError {}
pub trait ContinuousBoundary: Sync {
fn dim(&self) -> usize;
fn may_fail_after_validation(&self) -> bool {
true
}
fn apply_position(&self, r: &mut [f64]) -> Result<(), BoundaryError>;
fn apply_position_velocity(&self, r: &mut [f64], v: &mut [f64]) -> Result<(), BoundaryError> {
validate_vector_len("velocity", self.dim(), v.len())?;
let mut flip_mask = vec![0; self.dim()];
self.apply_position_with_velocity_flip_mask(r, &mut flip_mask)?;
for (velocity, &flip) in v.iter_mut().zip(flip_mask.iter()) {
if flip == 1 {
*velocity = -*velocity;
}
}
Ok(())
}
fn apply_positions(&self, positions: &mut [f64]) -> Result<(), BoundaryError> {
validate_flat_vector_list("positions", self.dim(), positions.len())?;
let min_vectors_per_job = parallel_chunk_len(positions.len() / self.dim()).unwrap_or(1);
positions
.par_chunks_mut(self.dim())
.with_min_len(min_vectors_per_job)
.try_for_each(|r| self.apply_position(r))
}
fn apply_positions_velocities(
&self,
positions: &mut [f64],
velocities: &mut [f64],
) -> Result<(), BoundaryError> {
validate_flat_vector_list("positions", self.dim(), positions.len())?;
validate_flat_vector_list("velocities", self.dim(), velocities.len())?;
if positions.len() != velocities.len() {
return Err(BoundaryError::InconsistentFlatVectorListLength {
expected: positions.len(),
got: velocities.len(),
});
}
let min_vectors_per_job = parallel_chunk_len(positions.len() / self.dim()).unwrap_or(1);
positions
.par_chunks_mut(self.dim())
.zip(velocities.par_chunks_mut(self.dim()))
.with_min_len(min_vectors_per_job)
.try_for_each(|(r, v)| self.apply_position_velocity(r, v))
}
fn apply_position_with_velocity_flip_mask(
&self,
r: &mut [f64],
flip_mask: &mut [u8],
) -> Result<(), BoundaryError> {
validate_vector_len("velocity_flip_mask", self.dim(), flip_mask.len())?;
flip_mask.fill(0);
self.apply_position(r)
}
}
fn validate_bounds(min: &[f64], max: &[f64]) -> Result<(), BoundaryError> {
if min.is_empty() {
return Err(BoundaryError::InvalidVectorDimension {
label: "bounds",
expected: 1,
got: 0,
});
}
if min.len() != max.len() {
return Err(BoundaryError::InvalidVectorDimension {
label: "bounds",
expected: min.len(),
got: max.len(),
});
}
for d in 0..min.len() {
if !min[d].is_finite() || !max[d].is_finite() || max[d] <= min[d] {
return Err(BoundaryError::InvalidBounds {
axis: d,
min: min[d],
max: max[d],
});
}
}
Ok(())
}
#[inline]
fn validate_vector_len(
label: &'static str,
expected: usize,
got: usize,
) -> Result<(), BoundaryError> {
if expected != got {
return Err(BoundaryError::InvalidVectorDimension {
label,
expected,
got,
});
}
Ok(())
}
#[inline]
fn validate_flat_vector_list(
label: &'static str,
dim: usize,
len: usize,
) -> Result<(), BoundaryError> {
if dim == 0 || !len.is_multiple_of(dim) {
return Err(BoundaryError::InvalidFlatVectorListLength { label, dim, len });
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct PeriodicBox {
min: Vec<f64>,
max: Vec<f64>,
}
impl PeriodicBox {
pub fn new(min: &[f64], max: &[f64]) -> Result<Self, BoundaryError> {
validate_bounds(min, max)?;
for axis in 0..min.len() {
if !(max[axis] - min[axis]).is_finite() {
return Err(BoundaryError::InvalidBounds {
axis,
min: min[axis],
max: max[axis],
});
}
}
Ok(Self {
min: min.to_vec(),
max: max.to_vec(),
})
}
#[inline]
pub fn dim(&self) -> usize {
self.min.len()
}
#[inline]
pub fn min(&self) -> &[f64] {
&self.min
}
#[inline]
pub fn max(&self) -> &[f64] {
&self.max
}
}
impl ContinuousBoundary for PeriodicBox {
fn may_fail_after_validation(&self) -> bool {
false
}
#[inline]
fn dim(&self) -> usize {
self.dim()
}
fn apply_position_velocity(&self, r: &mut [f64], v: &mut [f64]) -> Result<(), BoundaryError> {
validate_vector_len("velocity", self.dim(), v.len())?;
self.apply_position(r)
}
fn apply_position(&self, r: &mut [f64]) -> Result<(), BoundaryError> {
validate_vector_len("position", self.dim(), r.len())?;
for (d, x) in r.iter_mut().enumerate() {
if !x.is_finite() {
continue;
}
let lo = self.min[d];
let hi = self.max[d];
let w = hi - lo;
*x = lo + shifted_remainder(*x, lo, w);
if *x >= hi {
*x = lo;
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ClampBox {
min: Vec<f64>,
max: Vec<f64>,
}
impl ClampBox {
pub fn new(min: &[f64], max: &[f64]) -> Result<Self, BoundaryError> {
validate_bounds(min, max)?;
Ok(Self {
min: min.to_vec(),
max: max.to_vec(),
})
}
#[inline]
pub fn dim(&self) -> usize {
self.min.len()
}
#[inline]
pub fn min(&self) -> &[f64] {
&self.min
}
#[inline]
pub fn max(&self) -> &[f64] {
&self.max
}
}
impl ContinuousBoundary for ClampBox {
fn may_fail_after_validation(&self) -> bool {
false
}
#[inline]
fn dim(&self) -> usize {
self.dim()
}
fn apply_position_velocity(&self, r: &mut [f64], v: &mut [f64]) -> Result<(), BoundaryError> {
validate_vector_len("velocity", self.dim(), v.len())?;
self.apply_position(r)
}
fn apply_position(&self, r: &mut [f64]) -> Result<(), BoundaryError> {
validate_vector_len("position", self.dim(), r.len())?;
for (d, x) in r.iter_mut().enumerate() {
*x = x.clamp(self.min[d], self.max[d]);
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ReflectBox {
min: Vec<f64>,
max: Vec<f64>,
}
impl ReflectBox {
pub fn new(min: &[f64], max: &[f64]) -> Result<Self, BoundaryError> {
validate_bounds(min, max)?;
for axis in 0..min.len() {
if !(2.0 * (max[axis] - min[axis])).is_finite() {
return Err(BoundaryError::InvalidBounds {
axis,
min: min[axis],
max: max[axis],
});
}
}
Ok(Self {
min: min.to_vec(),
max: max.to_vec(),
})
}
#[inline]
pub fn dim(&self) -> usize {
self.min.len()
}
#[inline]
pub fn min(&self) -> &[f64] {
&self.min
}
#[inline]
pub fn max(&self) -> &[f64] {
&self.max
}
}
impl ContinuousBoundary for ReflectBox {
fn may_fail_after_validation(&self) -> bool {
false
}
#[inline]
fn dim(&self) -> usize {
self.dim()
}
fn apply_position(&self, r: &mut [f64]) -> Result<(), BoundaryError> {
validate_vector_len("position", self.dim(), r.len())?;
for (axis, value) in r.iter_mut().enumerate() {
*value = reflect(*value, self.min[axis], self.max[axis]).0;
}
Ok(())
}
fn apply_position_velocity(&self, r: &mut [f64], v: &mut [f64]) -> Result<(), BoundaryError> {
validate_vector_len("position", self.dim(), r.len())?;
validate_vector_len("velocity", self.dim(), v.len())?;
for (axis, (position, velocity)) in r.iter_mut().zip(v).enumerate() {
let (value, flip) = reflect(*position, self.min[axis], self.max[axis]);
*position = value;
if flip {
*velocity = -*velocity;
}
}
Ok(())
}
fn apply_position_with_velocity_flip_mask(
&self,
r: &mut [f64],
flip_mask: &mut [u8],
) -> Result<(), BoundaryError> {
validate_vector_len("position", self.dim(), r.len())?;
validate_vector_len("velocity_flip_mask", self.dim(), flip_mask.len())?;
for (axis, (position, mask)) in r.iter_mut().zip(flip_mask).enumerate() {
let (value, flip) = reflect(*position, self.min[axis], self.max[axis]);
*position = value;
*mask = u8::from(flip);
}
Ok(())
}
}
fn shifted_remainder(value: f64, origin: f64, period: f64) -> f64 {
let shifted = value - origin;
if shifted.is_finite() {
shifted.rem_euclid(period)
} else {
(value.rem_euclid(period) - origin.rem_euclid(period)).rem_euclid(period)
}
}
fn reflect(value: f64, low: f64, high: f64) -> (f64, bool) {
if !value.is_finite() || (low..=high).contains(&value) {
return (value, false);
}
let width = high - low;
let offset = shifted_remainder(value, low, 2.0 * width);
let folded = if offset <= width {
low + offset
} else {
high - (offset - width)
};
let flip = if value < low {
offset >= width
} else {
offset > width || offset == 0.0
};
(folded.clamp(low, high), flip)
}