use serde::{Deserialize, Serialize};
use hoomd_microstate::{
Body, Microstate, Site, SiteKey, Transform, boundary::Wrap, property::Position,
};
use hoomd_spatial::PointsNearBall;
use crate::{
DeltaEnergyInsert, DeltaEnergyOne, DeltaEnergyRemove, MaximumInteractionRange, SitePairEnergy,
TotalEnergy,
};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct PairwiseCutoff<E>(pub E);
impl<E> PairwiseCutoff<E> {
#[inline]
pub fn site_pair_energy<S>(&self, site_i: &Site<S>, site_j: &Site<S>) -> f64
where
E: SitePairEnergy<S>,
{
if site_i.body_tag == site_j.body_tag {
return 0.0;
}
self.0
.site_pair_energy(&site_i.properties, &site_j.properties)
}
#[inline(always)]
fn filtered_site_energy_all<B, S, X, C, F, F2>(
&self,
microstate: &Microstate<B, S, X, C>,
site_i_properties: &S,
filter: F,
site_pair_energy: F2,
) -> f64
where
E: SitePairEnergy<S>,
F: Fn(&Site<S>) -> bool,
F2: Fn(&E, &S, &S) -> f64,
{
let mut energy = 0.0;
for site_j in microstate.sites().iter().chain(microstate.ghosts()) {
if filter(site_j) {
let one = site_pair_energy(&self.0, site_i_properties, &site_j.properties);
if one == f64::INFINITY {
return one;
}
energy += one;
}
}
energy
}
#[inline(always)]
fn filtered_site_energy_spatial<P, B, S, X, C, F, F2>(
&self,
microstate: &Microstate<B, S, X, C>,
site_i_properties: &S,
filter: F,
site_pair_energy: F2,
) -> f64
where
E: SitePairEnergy<S> + MaximumInteractionRange,
S: Position<Position = P>,
X: PointsNearBall<P, SiteKey>,
F: Fn(&Site<S>) -> bool,
F2: Fn(&E, &S, &S) -> f64,
{
let mut energy = 0.0;
for site_j in microstate.iter_sites_near(
site_i_properties.position(),
self.0.maximum_interaction_range(),
) {
if filter(site_j) {
let one = site_pair_energy(&self.0, site_i_properties, &site_j.properties);
if one == f64::INFINITY {
return one;
}
energy += one;
}
}
energy
}
#[inline(always)]
fn filtered_site_energy<P, B, S, X, C, F, F2>(
&self,
microstate: &Microstate<B, S, X, C>,
site_i_properties: &S,
filter: F,
site_pair_energy: F2,
) -> f64
where
E: SitePairEnergy<S> + MaximumInteractionRange,
S: Position<Position = P>,
X: PointsNearBall<P, SiteKey>,
F: Fn(&Site<S>) -> bool,
F2: Fn(&E, &S, &S) -> f64,
{
if X::is_all_pairs() {
self.filtered_site_energy_all(microstate, site_i_properties, filter, site_pair_energy)
} else {
self.filtered_site_energy_spatial(
microstate,
site_i_properties,
filter,
site_pair_energy,
)
}
}
#[inline(always)]
fn filtered_body_energy_final<P, B, S, X, C, F>(
&self,
microstate: &Microstate<B, S, X, C>,
body: &Body<B, S>,
filter: F,
) -> f64
where
E: SitePairEnergy<S> + MaximumInteractionRange,
B: Transform<S>,
S: Position<Position = P>,
X: PointsNearBall<P, SiteKey>,
C: Wrap<B> + Wrap<S>,
F: Fn(&Site<S>) -> bool,
{
let mut energy_final = 0.0;
for s in &body.sites {
match microstate.boundary().wrap(body.properties.transform(s)) {
Err(_) => return f64::INFINITY,
Ok(site_i_properties) => {
let one = self.filtered_site_energy(
microstate,
&site_i_properties,
&filter,
E::site_pair_energy,
);
if one == f64::INFINITY {
return one;
}
energy_final += one;
}
}
}
energy_final
}
#[inline(always)]
fn filtered_body_energy_initial<P, B, S, X, C, F>(
&self,
microstate: &Microstate<B, S, X, C>,
body_index: usize,
filter: F,
) -> f64
where
E: SitePairEnergy<S> + MaximumInteractionRange,
S: Position<Position = P>,
X: PointsNearBall<P, SiteKey>,
F: Fn(&Site<S>) -> bool,
{
let mut energy_initial = 0.0;
if !E::is_only_infinite_or_zero() {
for site_i in microstate.iter_body_sites(body_index) {
let one = self.filtered_site_energy(
microstate,
&site_i.properties,
&filter,
E::site_pair_energy_initial,
);
if one == f64::INFINITY {
return one;
}
energy_initial += one;
}
}
energy_initial
}
}
impl<P, B, S, X, C, E> TotalEnergy<Microstate<B, S, X, C>> for PairwiseCutoff<E>
where
E: SitePairEnergy<S> + MaximumInteractionRange,
S: Position<Position = P>,
X: PointsNearBall<P, SiteKey>,
{
#[inline]
fn total_energy(&self, microstate: &Microstate<B, S, X, C>) -> f64 {
let mut total = 0.0;
for site_i in microstate.sites() {
let one = self.filtered_site_energy(
microstate,
&site_i.properties,
|site_j| site_i.site_tag < site_j.site_tag && site_i.body_tag != site_j.body_tag,
E::site_pair_energy,
);
if one == f64::INFINITY {
return one;
}
total += one;
}
total
}
#[inline]
fn delta_energy_total(
&self,
initial_microstate: &Microstate<B, S, X, C>,
final_microstate: &Microstate<B, S, X, C>,
) -> f64 {
let mut energy_final = 0.0;
for site_i in final_microstate.sites() {
let one = self.filtered_site_energy(
final_microstate,
&site_i.properties,
|site_j| site_i.site_tag < site_j.site_tag && site_i.body_tag != site_j.body_tag,
E::site_pair_energy,
);
if one == f64::INFINITY {
return one;
}
energy_final += one;
}
let mut energy_initial = 0.0;
if !E::is_only_infinite_or_zero() {
for site_i in initial_microstate.sites() {
let one = self.filtered_site_energy(
initial_microstate,
&site_i.properties,
|site_j| {
site_i.site_tag < site_j.site_tag && site_i.body_tag != site_j.body_tag
},
E::site_pair_energy_initial,
);
if one == f64::INFINITY {
return -one;
}
energy_initial += one;
}
}
energy_final - energy_initial
}
}
impl<P, B, S, X, C, E> DeltaEnergyOne<B, S, X, C> for PairwiseCutoff<E>
where
E: SitePairEnergy<S> + MaximumInteractionRange,
B: Transform<S>,
S: Position<Position = P>,
X: PointsNearBall<P, SiteKey>,
C: Wrap<B> + Wrap<S>,
{
#[inline]
fn delta_energy_one(
&self,
initial_microstate: &Microstate<B, S, X, C>,
body_index: usize,
final_body: &Body<B, S>,
) -> f64 {
let body_tag = initial_microstate.bodies()[body_index].tag;
let energy_final =
self.filtered_body_energy_final(initial_microstate, final_body, |site_j| {
body_tag != site_j.body_tag
});
if energy_final == f64::INFINITY {
return energy_final;
}
let energy_initial =
self.filtered_body_energy_initial(initial_microstate, body_index, |site_j| {
body_tag != site_j.body_tag
});
energy_final - energy_initial
}
}
impl<P, B, S, X, C, E> DeltaEnergyInsert<B, S, X, C> for PairwiseCutoff<E>
where
E: SitePairEnergy<S> + MaximumInteractionRange,
B: Transform<S>,
S: Position<Position = P>,
X: PointsNearBall<P, SiteKey>,
C: Wrap<B> + Wrap<S>,
{
#[inline]
fn delta_energy_insert(
&self,
initial_microstate: &Microstate<B, S, X, C>,
new_body: &Body<B, S>,
) -> f64 {
self.filtered_body_energy_final(initial_microstate, new_body, |_| true)
}
}
impl<P, B, S, X, C, E> DeltaEnergyRemove<B, S, X, C> for PairwiseCutoff<E>
where
E: SitePairEnergy<S> + MaximumInteractionRange,
S: Position<Position = P>,
X: PointsNearBall<P, SiteKey>,
{
#[inline]
fn delta_energy_remove(
&self,
initial_microstate: &Microstate<B, S, X, C>,
body_index: usize,
) -> f64 {
let body_tag = initial_microstate.bodies()[body_index].tag;
let energy_initial =
self.filtered_body_energy_initial(initial_microstate, body_index, |site_j| {
body_tag != site_j.body_tag
});
-energy_initial
}
}
#[cfg(test)]
mod tests_finite {
use super::*;
use crate::{TotalEnergy, pairwise::Isotropic, univariate::HarmonicRepulsion};
use assert2::check;
use hoomd_geometry::shape::Hypercuboid;
use hoomd_microstate::{
boundary::{Closed, Open},
property::Point,
};
use hoomd_spatial::{AllPairs, VecCell};
use hoomd_vector::{Cartesian, distribution::Ball};
use approxim::assert_relative_eq;
use rand::{
RngExt, SeedableRng,
distr::{Distribution, Uniform},
rngs::StdRng,
};
use rstest::*;
use std::f64::consts::PI;
#[fixture]
fn square() -> Closed<Hypercuboid<2>> {
let cuboid = Hypercuboid {
edge_lengths: [
4.0.try_into()
.expect("hard-coded constant should be positive"),
4.0.try_into()
.expect("hard-coded constant should be positive"),
],
};
Closed(cuboid)
}
mod pairwise_cutoff {
use super::*;
use crate::pairwise::Isotropic;
#[fixture]
fn microstate()
-> Microstate<Point<Cartesian<2>>, Point<Cartesian<2>>, AllPairs<SiteKey>, Open> {
let mut microstate = Microstate::new();
microstate
.extend_bodies([
Body::point(Cartesian::from([0.0, 0.0])),
Body::point(Cartesian::from([1.0, 0.0])),
Body::point(Cartesian::from([0.0, 5.0])),
Body::point(Cartesian::from([1.0, 5.0])),
])
.expect("hard-coded bodies should be in the boundary");
microstate
}
#[rstest]
fn blanket_fn(
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Open,
>,
) {
let pairwise_cutoff = PairwiseCutoff(Isotropic {
interaction: |r| 1.0 / (r * 2.0),
r_cut: 2.0,
});
assert_eq!(pairwise_cutoff.total_energy(µstate), 1.0);
let sites = microstate.sites();
check!(pairwise_cutoff.site_pair_energy(&sites[0], &sites[0]) == 0.0);
check!(pairwise_cutoff.site_pair_energy(&sites[0], &sites[1]) == 0.5);
check!(pairwise_cutoff.site_pair_energy(&sites[0], &sites[2]) == 0.0);
check!(pairwise_cutoff.site_pair_energy(&sites[0], &sites[3]) == 0.0);
check!(pairwise_cutoff.site_pair_energy(&sites[1], &sites[1]) == 0.0);
check!(pairwise_cutoff.site_pair_energy(&sites[1], &sites[2]) == 0.0);
check!(pairwise_cutoff.site_pair_energy(&sites[1], &sites[3]) == 0.0);
check!(pairwise_cutoff.site_pair_energy(&sites[2], &sites[2]) == 0.0);
check!(pairwise_cutoff.site_pair_energy(&sites[2], &sites[3]) == 0.5);
check!(pairwise_cutoff.site_pair_energy(&sites[3], &sites[3]) == 0.0);
}
#[rstest]
fn large_r_cut(
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Open,
>,
) {
let pairwise_cutoff = PairwiseCutoff(Isotropic {
interaction: |r| 1.0 / (r * 2.0),
r_cut: 5.0_f64.next_up(),
});
check!(pairwise_cutoff.total_energy(µstate) == 1.2);
}
#[test]
fn body_exclusion() -> anyhow::Result<()> {
let body_a = Body {
properties: Point::new(Cartesian::from([0.0, 0.0])),
sites: [
Point::new(Cartesian::from([1.0, 1.0])),
Point::new(Cartesian::from([1.0, -1.0])),
Point::new(Cartesian::from([-1.0, 1.0])),
Point::new(Cartesian::from([-1.0, -1.0])),
]
.into(),
};
let body_b = Body {
properties: Point::new(Cartesian::from([3.0, 0.0])),
sites: body_a.sites.clone(),
};
let mut microstate = Microstate::new();
microstate.extend_bodies([body_a, body_b])?;
let pairwise_cutoff = PairwiseCutoff(Isotropic {
interaction: |_r| 1.0,
r_cut: 1.0_f64.next_up(),
});
check!(pairwise_cutoff.total_energy(µstate) == 2.0);
let sites = microstate.sites();
check!(pairwise_cutoff.site_pair_energy(&sites[0], &sites[0]) == 0.0);
check!(pairwise_cutoff.site_pair_energy(&sites[0], &sites[1]) == 0.0);
check!(pairwise_cutoff.site_pair_energy(&sites[0], &sites[2]) == 0.0);
check!(pairwise_cutoff.site_pair_energy(&sites[0], &sites[3]) == 0.0);
check!(pairwise_cutoff.site_pair_energy(&sites[4], &sites[4]) == 0.0);
check!(pairwise_cutoff.site_pair_energy(&sites[4], &sites[5]) == 0.0);
check!(pairwise_cutoff.site_pair_energy(&sites[4], &sites[6]) == 0.0);
check!(pairwise_cutoff.site_pair_energy(&sites[4], &sites[7]) == 0.0);
check!(pairwise_cutoff.site_pair_energy(&sites[0], &sites[6]) == 1.0);
check!(pairwise_cutoff.site_pair_energy(&sites[1], &sites[7]) == 1.0);
Ok(())
}
}
mod delta_energy_one {
use super::*;
#[rstest]
fn site_outside(square: Closed<Hypercuboid<2>>) -> anyhow::Result<()> {
let body = Body {
properties: Point::new(Cartesian::from([0.0, 0.0])),
sites: [Point::new(Cartesian::from([1.0, 0.0]))].into(),
};
let mut final_body = body.clone();
final_body.properties.position[0] = 1.0;
let microstate = Microstate::builder()
.boundary(square)
.bodies([body])
.try_build()?;
let energy = PairwiseCutoff(Isotropic {
interaction: |_r| 0.0,
r_cut: 0.0,
});
check!(energy.delta_energy_one(µstate, 0, &final_body) == f64::INFINITY);
Ok(())
}
#[test]
fn body_exclusion() -> anyhow::Result<()> {
let body_a = Body {
properties: Point::new(Cartesian::from([0.0, 0.0])),
sites: [
Point::new(Cartesian::from([1.0, 1.0])),
Point::new(Cartesian::from([1.0, -1.0])),
Point::new(Cartesian::from([-1.0, 1.0])),
Point::new(Cartesian::from([-1.0, -1.0])),
]
.into(),
};
let body_b = Body {
properties: Point::new(Cartesian::from([3.0, 0.0])),
sites: body_a.sites.clone(),
};
let body_a_final = Body {
properties: Point::new(Cartesian::from([-1.0, 0.0])),
sites: body_a.sites.clone(),
};
let mut microstate = Microstate::new();
microstate.extend_bodies([body_a, body_b])?;
let pairwise_cutoff = PairwiseCutoff(Isotropic {
interaction: |_r| 1.0,
r_cut: 1.0_f64.next_up(),
});
check!(pairwise_cutoff.delta_energy_one(µstate, 0, &body_a_final) == -2.0);
Ok(())
}
#[test]
fn random_moves() -> anyhow::Result<()> {
let body_template = Body {
properties: Point::new(Cartesian::from([0.0, 0.0])),
sites: [
Point::new(Cartesian::from([0.0, 1.0])),
Point::new(Cartesian::from([-1.0, 1.0])),
Point::new(Cartesian::from([-1.0, -1.0])),
]
.into(),
};
let body_a = Body {
properties: Point::new(Cartesian::from([3.0, 0.0])),
sites: body_template.sites.clone(),
};
let body_b = body_template.clone();
let microstate_initial = Microstate::builder().bodies([body_a, body_b]).try_build()?;
let mut microstate_final = microstate_initial.clone();
let harmonic_repulsion: HarmonicRepulsion = HarmonicRepulsion { a: 5.0, r_cut: 5.0 };
let pairwise_cutoff = PairwiseCutoff(Isotropic {
interaction: harmonic_repulsion,
r_cut: 5.0,
});
check!(pairwise_cutoff.total_energy(µstate_initial) != 0.0);
let mut rng = StdRng::seed_from_u64(0);
let r_distribution = Uniform::new(3.0, 6.0)?;
let theta_distribution = Uniform::new(0.0, 2.0 * PI)?;
let mut new_body = body_template.clone();
for _ in 0..1024 {
let r = rng.sample(r_distribution);
let theta = rng.sample(theta_distribution);
new_body.properties.position = [r * theta.cos(), r * theta.sin()].into();
let delta_energy_one =
pairwise_cutoff.delta_energy_one(µstate_initial, 0, &new_body);
microstate_final.update_body_properties(0, new_body.properties)?;
let delta_energy_total = pairwise_cutoff.total_energy(µstate_final)
- pairwise_cutoff.total_energy(µstate_initial);
assert_relative_eq!(delta_energy_one, delta_energy_total, epsilon = 1e-10);
assert_relative_eq!(
pairwise_cutoff.delta_energy_total(µstate_initial, µstate_final),
delta_energy_total,
epsilon = 1e-10
);
}
Ok(())
}
}
mod delta_energy_insert_remove {
use super::*;
#[rstest]
fn site_outside(square: Closed<Hypercuboid<2>>) -> anyhow::Result<()> {
let body = Body {
properties: Point::new(Cartesian::from([0.0, 0.0])),
sites: [Point::new(Cartesian::from([1.0, 0.0]))].into(),
};
let mut new_body = body.clone();
new_body.properties.position[0] = 1.0;
let microstate = Microstate::builder()
.boundary(square)
.bodies([body])
.try_build()?;
let energy = PairwiseCutoff(Isotropic {
interaction: |_r| 0.0,
r_cut: 0.0,
});
check!(energy.delta_energy_insert(µstate, &new_body) == f64::INFINITY);
Ok(())
}
#[test]
fn body_exclusion() -> anyhow::Result<()> {
let body_a_new = Body {
properties: Point::new(Cartesian::from([0.0, 0.0])),
sites: [
Point::new(Cartesian::from([1.0, 1.0])),
Point::new(Cartesian::from([1.0, -1.0])),
Point::new(Cartesian::from([-1.0, 1.0])),
Point::new(Cartesian::from([-1.0, -1.0])),
]
.into(),
};
let body_b = Body {
properties: Point::new(Cartesian::from([3.0, 0.0])),
sites: body_a_new.sites.clone(),
};
let mut microstate = Microstate::new();
microstate.extend_bodies([body_b])?;
let pairwise_cutoff = PairwiseCutoff(Isotropic {
interaction: |_r| 1.0,
r_cut: 1.0_f64.next_up(),
});
check!(pairwise_cutoff.delta_energy_insert(µstate, &body_a_new) == 2.0);
microstate.add_body(body_a_new)?;
check!(pairwise_cutoff.delta_energy_remove(µstate, 1) == -2.0);
Ok(())
}
#[test]
fn random_moves() -> anyhow::Result<()> {
let body_template = Body {
properties: Point::new(Cartesian::from([0.0, 0.0])),
sites: [
Point::new(Cartesian::from([0.0, 1.0])),
Point::new(Cartesian::from([-1.0, 1.0])),
Point::new(Cartesian::from([-1.0, -1.0])),
]
.into(),
};
let body_a = Body {
properties: Point::new(Cartesian::from([3.0, 0.0])),
sites: body_template.sites.clone(),
};
let microstate_initial = Microstate::builder().bodies([body_a]).try_build()?;
let mut microstate_final = microstate_initial.clone();
let harmonic_repulsion: HarmonicRepulsion = HarmonicRepulsion { a: 5.0, r_cut: 5.0 };
let pairwise_cutoff = PairwiseCutoff(Isotropic {
interaction: harmonic_repulsion,
r_cut: 5.0,
});
let mut rng = StdRng::seed_from_u64(2);
let r_distribution = Uniform::new(3.0, 6.0)?;
let theta_distribution = Uniform::new(0.0, 2.0 * PI)?;
for _ in 0..1024 {
let r = rng.sample(r_distribution);
let theta = rng.sample(theta_distribution);
let mut new_body = body_template.clone();
new_body.properties.position = [r * theta.cos(), r * theta.sin()].into();
let delta_energy_insert =
pairwise_cutoff.delta_energy_insert(µstate_initial, &new_body);
let tag = microstate_final.add_body(new_body)?;
let delta_energy_total = pairwise_cutoff.total_energy(µstate_final)
- pairwise_cutoff.total_energy(µstate_initial);
assert_relative_eq!(delta_energy_insert, delta_energy_total, epsilon = 1e-6);
let delta_energy_remove = pairwise_cutoff.delta_energy_remove(µstate_final, 1);
assert_relative_eq!(delta_energy_remove, -delta_energy_total, epsilon = 1e-6);
microstate_final.remove_body(
microstate_final.body_indices()[tag].expect("tag should be present"),
);
}
Ok(())
}
}
#[rstest]
fn spatial_data_consistency(square: Closed<Hypercuboid<2>>) -> anyhow::Result<()> {
const N_BODIES: usize = 2_000;
let r_cut = 0.5;
let mut rng = StdRng::seed_from_u64(0);
let mut microstate_all_pairs = Microstate::builder()
.spatial_data(AllPairs::<SiteKey>::default())
.boundary(square.clone())
.try_build()?;
let cell_list = VecCell::builder()
.nominal_search_radius(r_cut.try_into()?)
.build();
let mut microstate_vec_cell = Microstate::builder()
.boundary(square.clone())
.spatial_data(cell_list)
.try_build()?;
let body_template = Body {
properties: Point::new(Cartesian::from([0.0, 0.0])),
sites: [Point::default()].into(),
};
for _ in 0..N_BODIES {
let mut new_body = body_template.clone();
new_body.properties.position = square.sample(&mut rng);
microstate_all_pairs.add_body(new_body.clone())?;
microstate_vec_cell.add_body(new_body)?;
}
let harmonic_repulsion: HarmonicRepulsion = HarmonicRepulsion { a: 5.0, r_cut };
let pairwise_cutoff = PairwiseCutoff(Isotropic {
interaction: harmonic_repulsion,
r_cut,
});
assert_relative_eq!(
pairwise_cutoff.total_energy(µstate_all_pairs),
pairwise_cutoff.total_energy(µstate_vec_cell)
);
let move_distribution = Ball {
radius: 0.2.try_into()?,
};
for i in (0..N_BODIES).step_by(4) {
assert_relative_eq!(
pairwise_cutoff.delta_energy_remove(µstate_all_pairs, i),
pairwise_cutoff.delta_energy_remove(µstate_vec_cell, i),
epsilon = 1e-10
);
let mut final_body = microstate_all_pairs.bodies()[i].clone();
final_body.item.properties.position += move_distribution.sample(&mut rng);
assert_relative_eq!(
pairwise_cutoff.delta_energy_one(µstate_all_pairs, i, &final_body.item),
pairwise_cutoff.delta_energy_one(µstate_vec_cell, i, &final_body.item),
epsilon = 1e-10
);
}
for _ in 0..N_BODIES / 4 {
let mut new_body = body_template.clone();
new_body.properties.position = square.sample(&mut rng);
assert_relative_eq!(
pairwise_cutoff.delta_energy_insert(µstate_all_pairs, &new_body),
pairwise_cutoff.delta_energy_insert(µstate_vec_cell, &new_body),
epsilon = 1e-10
);
}
Ok(())
}
}
impl<E> MaximumInteractionRange for PairwiseCutoff<E>
where
E: MaximumInteractionRange,
{
#[inline]
fn maximum_interaction_range(&self) -> f64 {
self.0.maximum_interaction_range()
}
}
#[cfg(test)]
mod test_infinite {
use super::*;
use crate::{
TotalEnergy,
pairwise::{HardShape, HardSphere},
};
use assert2::check;
use hoomd_geometry::shape::{Ellipse, Hypercuboid};
use hoomd_microstate::{
boundary::Closed,
property::{OrientedPoint, Point},
};
use hoomd_vector::{Angle, Cartesian};
use rstest::*;
#[fixture]
fn square() -> Closed<Hypercuboid<2>> {
let cuboid = Hypercuboid {
edge_lengths: [
4.0.try_into()
.expect("hard-coded constant should be positive"),
4.0.try_into()
.expect("hard-coded constant should be positive"),
],
};
Closed(cuboid)
}
mod pairwise_cutoff {
use super::*;
#[test]
fn large_r_cut() -> anyhow::Result<()> {
let mut microstate = Microstate::new();
microstate.extend_bodies([
Body::point(Cartesian::from([0.0, 0.0])),
Body::point(Cartesian::from([0.0, 5.0])),
])?;
let r_cut = 5.0_f64.next_up();
let pairwise_cutoff = PairwiseCutoff(HardSphere { diameter: r_cut });
check!(pairwise_cutoff.total_energy(µstate) == f64::INFINITY);
let r_cut = 5.0_f64;
let pairwise_cutoff = PairwiseCutoff(HardSphere { diameter: r_cut });
check!(pairwise_cutoff.total_energy(µstate) == 0.0);
Ok(())
}
#[test]
fn body_exclusion() -> anyhow::Result<()> {
let body_a = Body {
properties: Point::new(Cartesian::from([0.0, 0.0])),
sites: [
Point::new(Cartesian::from([1.0, 1.0])),
Point::new(Cartesian::from([1.0, -1.0])),
Point::new(Cartesian::from([-1.0, 1.0])),
Point::new(Cartesian::from([-1.0, -1.0])),
]
.into(),
};
let body_b = Body {
properties: Point::new(Cartesian::from([4.0, 0.0])),
sites: body_a.sites.clone(),
};
let mut microstate = Microstate::new();
microstate.extend_bodies([body_a, body_b])?;
let r_cut = 1.0_f64.next_up();
let pairwise_cutoff = PairwiseCutoff(HardSphere { diameter: r_cut });
check!(pairwise_cutoff.total_energy(µstate) == 0.0);
let r_cut = 2.0_f64.next_up();
let pairwise_cutoff = PairwiseCutoff(HardSphere { diameter: r_cut });
check!(pairwise_cutoff.total_energy(µstate) == f64::INFINITY);
Ok(())
}
}
mod delta_energy_one {
use super::*;
#[rstest]
fn site_outside(square: Closed<Hypercuboid<2>>) -> anyhow::Result<()> {
let body = Body {
properties: Point::new(Cartesian::from([0.0, 0.0])),
sites: [Point::new(Cartesian::from([1.0, 0.0]))].into(),
};
let mut final_body = body.clone();
final_body.properties.position[0] = 1.0;
let microstate = Microstate::builder()
.boundary(square)
.bodies([body])
.try_build()?;
let energy = PairwiseCutoff(HardSphere { diameter: 0.0 });
check!(energy.delta_energy_one(µstate, 0, &final_body) == f64::INFINITY);
Ok(())
}
#[test]
fn body_exclusion() -> anyhow::Result<()> {
let body_a = Body {
properties: Point::new(Cartesian::from([-1.0, 0.0])),
sites: [
Point::new(Cartesian::from([1.0, 1.0])),
Point::new(Cartesian::from([1.0, -1.0])),
Point::new(Cartesian::from([-1.0, 1.0])),
Point::new(Cartesian::from([-1.0, -1.0])),
]
.into(),
};
let body_b = Body {
properties: Point::new(Cartesian::from([3.0, 0.0])),
sites: body_a.sites.clone(),
};
let body_a_overlap = Body {
properties: Point::new(Cartesian::from([0.0, 0.0])),
sites: body_a.sites.clone(),
};
let body_a_no_overlap = Body {
properties: Point::new(Cartesian::from([-1.0, -1.0])),
sites: body_a.sites.clone(),
};
let mut microstate = Microstate::new();
microstate.extend_bodies([body_a, body_b])?;
let r_cut = 1.0_f64.next_up();
let pairwise_cutoff = PairwiseCutoff(HardSphere { diameter: r_cut });
check!(
pairwise_cutoff.delta_energy_one(µstate, 0, &body_a_overlap) == f64::INFINITY
);
check!(pairwise_cutoff.delta_energy_one(µstate, 0, &body_a_no_overlap) == 0.0);
Ok(())
}
}
mod delta_energy_insert_remove {
use super::*;
#[rstest]
fn site_outside(square: Closed<Hypercuboid<2>>) -> anyhow::Result<()> {
let body = Body {
properties: Point::new(Cartesian::from([0.0, 0.0])),
sites: [Point::new(Cartesian::from([1.0, 0.0]))].into(),
};
let mut new_body = body.clone();
new_body.properties.position[0] = 1.0;
let microstate = Microstate::builder()
.boundary(square)
.bodies([body])
.try_build()?;
let energy = PairwiseCutoff(HardSphere { diameter: 0.0 });
check!(energy.delta_energy_insert(µstate, &new_body) == f64::INFINITY);
Ok(())
}
#[test]
fn body_exclusion() -> anyhow::Result<()> {
let body_a_new = Body {
properties: Point::new(Cartesian::from([0.0, 0.0])),
sites: [
Point::new(Cartesian::from([1.0, 1.0])),
Point::new(Cartesian::from([1.0, -1.0])),
Point::new(Cartesian::from([-1.0, 1.0])),
Point::new(Cartesian::from([-1.0, -1.0])),
]
.into(),
};
let body_b = Body {
properties: Point::new(Cartesian::from([3.0, 0.0])),
sites: body_a_new.sites.clone(),
};
let mut microstate = Microstate::new();
microstate.extend_bodies([body_b])?;
let r_cut = 1.0_f64.next_up();
let pairwise_cutoff = PairwiseCutoff(HardSphere { diameter: r_cut });
check!(pairwise_cutoff.delta_energy_insert(µstate, &body_a_new) == f64::INFINITY);
microstate.add_body(body_a_new)?;
check!(pairwise_cutoff.delta_energy_remove(µstate, 1) == 0.0);
Ok(())
}
#[test]
fn hard_shape_initial() -> anyhow::Result<()> {
let a = OrientedPoint {
position: Cartesian::from([0.0, 0.0]),
orientation: Angle::default(),
};
let b = OrientedPoint {
position: Cartesian::from([1.5, 0.0]),
orientation: Angle::default(),
};
let body_a = Body {
properties: a,
sites: [a].into(),
};
let body_b = Body {
properties: b,
sites: [a].into(),
};
let mut microstate = Microstate::new();
microstate.extend_bodies([body_a, body_b.clone()])?;
let ellipse = Ellipse::with_semi_axes([1.0.try_into()?, 2.0.try_into()?]);
let hard_ellipse = PairwiseCutoff(HardShape(ellipse));
check!(hard_ellipse.total_energy(µstate) == f64::INFINITY);
check!(hard_ellipse.delta_energy_one(µstate, 1, &body_b) == f64::INFINITY);
let mut new_body_b = body_b;
new_body_b.properties.position.coordinates = [2.1, 0.0];
check!(hard_ellipse.delta_energy_one(µstate, 1, &new_body_b) == 0.0);
Ok(())
}
#[test]
fn delta_energy_total() -> anyhow::Result<()> {
let mut microstate_0 = Microstate::new();
microstate_0.extend_bodies([
Body::point(Cartesian::from([0.0, 0.0])),
Body::point(Cartesian::from([0.0, 1.125])),
])?;
let mut microstate_inf = Microstate::new();
microstate_inf.extend_bodies([
Body::point(Cartesian::from([0.0, 0.0])),
Body::point(Cartesian::from([0.0, 0.875])),
])?;
let pairwise_cutoff = PairwiseCutoff(HardSphere { diameter: 1.0 });
check!(pairwise_cutoff.delta_energy_total(µstate_0, µstate_0) == 0.0);
check!(
pairwise_cutoff.delta_energy_total(µstate_0, µstate_inf) == f64::INFINITY
);
check!(pairwise_cutoff.delta_energy_total(µstate_inf, µstate_0) == 0.0);
check!(
pairwise_cutoff.delta_energy_total(µstate_inf, µstate_inf)
== f64::INFINITY
);
Ok(())
}
}
}