Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion hoomd-manifold/src/sphere.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ impl<const N: usize> Spherical<N> {
#[inline]
#[must_use]
pub fn from_cartesian_coordinates(point: Cartesian<N>) -> Spherical<N> {
let rad = point.norm();
let rad = point.norm_squared();
assert_relative_eq!(rad, 1.0_f64, epsilon = 1e-6);
Spherical { point }
}
Expand Down
114 changes: 112 additions & 2 deletions hoomd-mc/src/hypercuboid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ use serde_with::serde_as;
use std::array;

use hoomd_geometry::shape::Hypercuboid;
use hoomd_microstate::boundary::{Closed, Periodic};
use hoomd_manifold::Spherical;
use hoomd_microstate::boundary::{Closed, ClosedSpherical, Periodic};
use hoomd_utility::valid::PositiveReal;
use hoomd_vector::Cartesian;

Expand Down Expand Up @@ -136,6 +137,49 @@ impl<const N: usize> Checkerboard<Cartesian<N>> for HypercuboidCheckerboard<N> {
}
}

impl<const N: usize> Checkerboard<Spherical<N>> for HypercuboidCheckerboard<N> {
#[inline]
fn point_to_space_index(&self, point: &Spherical<N>) -> Option<usize> {
let p = *point.point() - self.origin;
let mut space_multi_index: [i64; N] =
array::from_fn(|i| (p.coordinates[i] / self.space_width[i]).floor() as i64);

for (index, shape) in izip!(&mut space_multi_index, self.shape) {
// The origin is in the lower left corner of the box and may be up
// to one space width to the left of simulation boundary. Therefore,
// negative indices are out of bounds (and should never been seen
// for wrapped inputs).
if *index < 0 {
return None;
}

if *index as usize >= shape {
// In non-periodic boundaries, the checkerboard extends one full
// space to the right of the simulation boundary so that when
// it is shifted to the left it will still cover the boundary.
// Therefore, any points outside the checkerboard shape are
// invalid.
return None;
}
}

Some(Self::multi_index_to_index(
array::from_fn(|i| space_multi_index[i] as usize),
self.shape,
))
}

#[inline]
fn space_indices_by_color(&self) -> &[Vec<usize>] {
&self.space_indices_by_color
}

#[inline]
fn num_spaces(&self) -> usize {
self.shape.iter().product()
}
}

impl<const N: usize> HypercuboidCheckerboard<N> {
/// Collapse a multi-dimensional index to a single value in `[0, num_spaces]`.
#[inline]
Expand Down Expand Up @@ -198,7 +242,7 @@ impl<const N: usize> HypercuboidCheckerboard<N> {
/// Partition the space indices by color.
#[expect(
clippy::todo,
reason = "there are no known use-cases for parallel 4D, 5D, ... simulations at this time"
reason = "there are no known use-cases for parallel 5D, 6D, ... simulations at this time"
)]
fn construct_space_indices_by_color(shape: [usize; N]) -> Vec<Vec<usize>> {
for width in shape {
Expand Down Expand Up @@ -255,6 +299,40 @@ impl<const N: usize> HypercuboidCheckerboard<N> {
return result;
}

if N == 4 {
for offset_l in 0..=1 {
for offset_k in 0..=1 {
for offset_j in 0..=1 {
for offset_i in 0..=1 {
let mut space_indices = Vec::new();
let mut multi_index = [0; N];

for l in 0..shape[0] / 2 {
multi_index[0] = 2 * l + offset_l;
for k in 0..shape[1] / 2 {
multi_index[1] = 2 * k + offset_k;
for j in 0..shape[2] / 2 {
multi_index[2] = 2 * j + offset_j;
for i in 0..shape[3] / 2 {
multi_index[3] = 2 * i + offset_i;
space_indices.push(Self::multi_index_to_index(
multi_index,
shape,
));
}
}
}
}

result.push(space_indices);
}
}
}
}

return result;
}

todo!("Implement a general method");
}

Expand Down Expand Up @@ -391,6 +469,38 @@ impl<const N: usize> Cover<Cartesian<N>> for Periodic<Hypercuboid<N>> {
}
}

impl<const N: usize> Cover<Spherical<N>> for ClosedSpherical<N> {
type Checkerboard = HypercuboidCheckerboard<N>;
#[inline]
fn cover<R: Rng + ?Sized>(
&self,
rng: &mut R,
interaction_range: PositiveReal,
) -> Self::Checkerboard {
HypercuboidCheckerboard::new(
rng,
interaction_range,
[2.0.try_into().expect("hard-coded positive number"); N],
[false; N],
)
}

#[inline]
fn cover_into<R: Rng + ?Sized>(
&self,
checkerboard: &mut Self::Checkerboard,
rng: &mut R,
interaction_range: PositiveReal,
) {
checkerboard.update(
rng,
interaction_range,
[2.0.try_into().expect("hard-coded positive number"); N],
[false; N],
);
}
}

#[cfg(test)]
mod tests {
use assert2::{assert, check};
Expand Down
2 changes: 2 additions & 0 deletions hoomd-microstate/src/boundary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ use arrayvec::ArrayVec;
use thiserror::Error;

mod closed;
mod closed_spherical;
mod open;
mod periodic;

pub use closed::Closed;
pub use closed_spherical::ClosedSpherical;
pub use open::Open;
pub use periodic::Periodic;

Expand Down
63 changes: 63 additions & 0 deletions hoomd-microstate/src/boundary/closed_spherical.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) 2024-2026 The Regents of the University of Michigan.
// Part of hoomd-rs, released under the BSD 3-Clause License.

//! Implement `ClosedSpherical`

use arrayvec::ArrayVec;
use serde::{Deserialize, Serialize};

use super::{Error, GenerateGhosts, MAX_GHOSTS, Wrap};
use crate::property::Position;
use hoomd_manifold::Spherical;

/// [`ClosedSpherical<N>`] implements a hypercubic box enclosing a unit-radius
/// $`(N-1)`$-sphere. Use `ClosedSpherical` alongside [`SphericalVecCell`] to
/// implement [`ParallelSweep`] for [`Spherical`] bodies. `ClosedSpherical` is
/// otherwise functionally identical to using [`Open`] for [`Spherical`]
/// simulations.
///
/// # Example
/// ```
/// use hoomd_microstate::boundary::ClosedSpherical;
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let closed_spherical: ClosedSpherical<3> = ClosedSpherical {};
/// # Ok(())
/// # }
/// ```
///
/// Similar to [`Closed`], `ClosedSpherical` does not wrap bodies and sites,
/// nor does it generate ghost sites. However, unlike [`Closed`],
/// `ClosedSpherical` does not implement `IsPointInside`, `MapPoint`,
/// `Scale`, or `Volume`, as these methods are unrelated to the boundary
/// conditions for `Spherical` simulations.
///
/// [`SphericalVecCell`]: hoomd_spatial::SphericalVecCell;
/// [`ParallelSweep`]: hoomd_mc::ParallelSweep;
/// [`Spherical`]: hoomd_manifold::Spherical;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ClosedSpherical<const N: usize> {}

impl<BS, const N: usize> Wrap<BS> for ClosedSpherical<N>
where
BS: Position<Position = Spherical<N>>,
{
#[inline]
fn wrap(&self, properties: BS) -> Result<BS, Error> {
Ok(properties)
}
}

impl<S, const N: usize> GenerateGhosts<S> for ClosedSpherical<N>
where
S: Default,
{
#[inline]
fn maximum_interaction_range(&self) -> f64 {
std::f64::consts::PI
}
#[inline]
fn generate_ghosts(&self, _site_properties: &S) -> ArrayVec<S, MAX_GHOSTS> {
ArrayVec::new()
}
}
2 changes: 2 additions & 0 deletions hoomd-spatial/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ rustc-hash.workspace = true
serde.workspace = true
serde_with.workspace = true

hoomd-manifold.workspace = true
hoomd-vector.workspace = true
hoomd-utility.workspace = true

[dev-dependencies]
approxim.workspace = true
assert2.workspace = true
rand.workspace = true
rstest.workspace = true
2 changes: 2 additions & 0 deletions hoomd-spatial/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ use hoomd_utility::valid::PositiveReal;

mod all_pairs;
mod hash_cell;
mod spherical_vec_cell;
mod vec_cell;

pub use all_pairs::AllPairs;
pub use hash_cell::{HashCell, HashCellBuilder};
pub use spherical_vec_cell::{SphericalVecCell, SphericalVecCellBuilder};
pub use vec_cell::{VecCell, VecCellBuilder};

/// Allow incremental updates to points in the spatial data.
Expand Down
Loading