Skip to main content

Cell

Struct Cell 

Source
pub struct Cell { /* private fields */ }
Expand description

Periodic parallelepiped: columns of H, origin, and an ortho flag.

let cell = Cell::ortho(10.0, 11.0, 12.0)?;
assert!(cell.is_ortho());
let sheared = Cell::from_vectors(
    [10.0, 0.0, 0.0],
    [5.0, 8.66, 0.0],
    [0.0, 0.0, 10.0],
    [0.0, 0.0, 0.0],
)?;
assert!(!sheared.is_ortho());

Implementations§

Source§

impl Cell

Source

pub fn ortho(lx: f64, ly: f64, lz: f64) -> Result<Cell, Error>

Diagonal box with origin at zero.

Sets Self::is_ortho so Self::dist2 and Self::lattice_shift skip the two 3x3 matvecs.

Examples found in repository?
examples/knn_scale.rs (line 28)
26fn cubic(nside: usize) -> (Vec<[f64; 3]>, Cell) {
27    let boxl = nside as f64 * 3.125;
28    let cell = Cell::ortho(boxl, boxl, boxl).expect("box");
29    let mut xyz = Vec::with_capacity(nside * nside * nside);
30    let a = 3.125;
31    for iz in 0..nside {
32        for iy in 0..nside {
33            for ix in 0..nside {
34                xyz.push([ix as f64 * a, iy as f64 * a, iz as f64 * a]);
35            }
36        }
37    }
38    (xyz, cell)
39}
More examples
Hide additional examples
examples/knn_hot.rs (line 59)
56fn slab(n: usize) -> (Vec<[f64; 3]>, Cell) {
57    let nside = nside_of(n);
58    let film = nside as f64 * 3.125;
59    let cell = Cell::ortho(film, film, film * 2.0).expect("slab");
60    let mut xyz = Vec::with_capacity(n);
61    for i in 0..n {
62        let ix = i % nside;
63        let iy = (i / nside) % nside;
64        let iz = i / (nside * nside);
65        xyz.push([
66            (ix as f64 + 0.5) / nside as f64 * film,
67            (iy as f64 + 0.5) / nside as f64 * film,
68            (iz as f64 + 0.5) / nside as f64 * film,
69        ]);
70    }
71    (xyz, cell)
72}
73
74/// Tall orthorhombic column at the cubic spacing. Points occupy a band
75/// and the rest of z is vacuum, as in a seams nanotube trajectory.
76fn column(n: usize) -> (Vec<[f64; 3]>, Cell) {
77    let nside = nside_of(n);
78    let side = nside as f64 * 3.125;
79    let cell = Cell::ortho(side, side, side * 4.0).expect("column");
80    let mut xyz = Vec::with_capacity(n);
81    for i in 0..n {
82        let ix = i % nside;
83        let iy = (i / nside) % nside;
84        let iz = i / (nside * nside);
85        xyz.push([
86            (ix as f64 + 0.5) / nside as f64 * side,
87            (iy as f64 + 0.5) / nside as f64 * side,
88            (iz as f64 + 0.5) / nside as f64 * side + side * 1.5,
89        ]);
90    }
91    (xyz, cell)
92}
93
94fn system(shape: &str, n: usize) -> (Vec<[f64; 3]>, Cell) {
95    match shape {
96        "cubic" => {
97            let boxl = 50.0;
98            (
99                lattice(n, boxl),
100                Cell::ortho(boxl, boxl, boxl).expect("box"),
101            )
102        }
103        "slab" => slab(n),
104        "column" => column(n),
105        "hex" => {
106            // rgsaddle hexagonal cell, scaled to the cubic spacing.
107            let cell = Cell::from_vectors(
108                [50.0, 0.0, 0.0],
109                [25.0, 43.30127018922193, 0.0],
110                [0.0, 0.0, 55.55555555555556],
111                [0.0, 0.0, 0.0],
112            )
113            .expect("hex");
114            (frac_grid(&cell, n), cell)
115        }
116        "tilt" => {
117            // genice sH dump (BOX BOUNDS xy xz yz), scaled to the cubic spacing.
118            let s = 2.85;
119            let cell = Cell::from_lammps(
120                -6.2106057 * s,
121                24.8424228 * s,
122                0.0,
123                10.7570846 * s,
124                0.0,
125                20.131291 * s,
126                -6.2106057 * s,
127                0.0,
128                0.0,
129            )
130            .expect("tilt");
131            (frac_grid(&cell, n), cell)
132        }
133        other => panic!("unknown KNN_HOT_SHAPE {other}"),
134    }
135}
examples/pairs_scale.rs (line 71)
56fn main() {
57    let n = env_usize("PAIRS_N", 4096);
58    let reps = env_usize("PAIRS_REPS", 5).max(1);
59    let half = std::env::var("PAIRS_HALF").ok().as_deref() == Some("1");
60    let hint = std::env::var("PAIRS_HINT")
61        .ok()
62        .and_then(|s| s.parse().ok());
63    let threads = std::env::var("RAYON_NUM_THREADS").unwrap_or_else(|_| "default".to_string());
64    let mut xyz = fill(n);
65    if let Some(seed) = std::env::var("PAIRS_SHUFFLE")
66        .ok()
67        .and_then(|s| s.parse().ok())
68    {
69        shuffle(&mut xyz, seed);
70    }
71    let cell = Cell::ortho(18.0, 18.0, 18.0).expect("box");
72    linkcell::pop_engage();
73    linkcell::pop_prepare();
74    let mut pairs = Vec::new();
75    let mut early = Vec::new();
76    for _ in 0..3 {
77        let t = Instant::now();
78        pairs = pairs_within(&xyz, &cell, 4.0, None, hint, half).expect("pairs");
79        early.push(t.elapsed().as_secs_f64() * 1e3);
80    }
81    linkcell::pop_reset();
82    let t0 = Instant::now();
83    let mut acc = 0usize;
84    for _ in 0..reps {
85        let got = pairs_within(&xyz, &cell, 4.0, None, hint, half).expect("pairs");
86        acc += got.len();
87    }
88    let wall_ns = t0.elapsed().as_nanos() as u64;
89    let ms = wall_ns as f64 / 1e6;
90    println!(
91        "n={} half={} hint={:?} pairs={} cold={:.3} call2={:.3} call3={:.3} reps={} ms_per={:.4} acc={}",
92        xyz.len(),
93        half as u8,
94        hint,
95        pairs.len(),
96        early[0],
97        early[1],
98        early[2],
99        reps,
100        ms / reps as f64,
101        acc
102    );
103    let useful = linkcell::pop_snapshot();
104    let (lb, ce, pe) = linkcell::pop_efficiencies(&useful, wall_ns);
105    let list = useful
106        .iter()
107        .map(|ns| ns.to_string())
108        .collect::<Vec<_>>()
109        .join(",");
110    println!(
111        "pop shape=pairs-{n}-h{} threads={threads} n={n} reps={reps} wall_ns={wall_ns} useful_ns={list} lb={lb:.4} ce={ce:.4} pe={pe:.4}",
112        half as u8
113    );
114}
Source

pub fn ortho_origin( lx: f64, ly: f64, lz: f64, origin: [f64; 3], ) -> Result<Cell, Error>

Diagonal box with an explicit dump-cell origin.

Source

pub fn from_vectors( a: [f64; 3], b: [f64; 3], c: [f64; 3], origin: [f64; 3], ) -> Result<Cell, Error>

Parallelepiped from lattice vectors a, b, c and an origin.

Vectors are the columns of H. A non-diagonal H clears Self::is_ortho.

Inlined so the caller writes the cell once. An outlined return stores it and then copies it.

Examples found in repository?
examples/knn_scale.rs (lines 45-50)
43fn tilt(nside: usize) -> (Vec<[f64; 3]>, Cell) {
44    let lx = nside as f64 * 3.125;
45    let cell = Cell::from_vectors(
46        [lx, 0.0, 0.0],
47        [lx * 0.2, lx * 0.9, 0.0],
48        [lx * 0.05, lx * -0.08, lx * 0.95],
49        [0.0, 0.0, 0.0],
50    )
51    .expect("tilt");
52    let mut xyz = Vec::with_capacity(nside * nside * nside);
53    let nf = nside as f64;
54    for iz in 0..nside {
55        for iy in 0..nside {
56            for ix in 0..nside {
57                let s = [
58                    (ix as f64 + 0.5) / nf,
59                    (iy as f64 + 0.5) / nf,
60                    (iz as f64 + 0.5) / nf,
61                ];
62                xyz.push(cell.cartesian(s));
63            }
64        }
65    }
66    (xyz, cell)
67}
More examples
Hide additional examples
examples/knn_hot.rs (lines 107-112)
94fn system(shape: &str, n: usize) -> (Vec<[f64; 3]>, Cell) {
95    match shape {
96        "cubic" => {
97            let boxl = 50.0;
98            (
99                lattice(n, boxl),
100                Cell::ortho(boxl, boxl, boxl).expect("box"),
101            )
102        }
103        "slab" => slab(n),
104        "column" => column(n),
105        "hex" => {
106            // rgsaddle hexagonal cell, scaled to the cubic spacing.
107            let cell = Cell::from_vectors(
108                [50.0, 0.0, 0.0],
109                [25.0, 43.30127018922193, 0.0],
110                [0.0, 0.0, 55.55555555555556],
111                [0.0, 0.0, 0.0],
112            )
113            .expect("hex");
114            (frac_grid(&cell, n), cell)
115        }
116        "tilt" => {
117            // genice sH dump (BOX BOUNDS xy xz yz), scaled to the cubic spacing.
118            let s = 2.85;
119            let cell = Cell::from_lammps(
120                -6.2106057 * s,
121                24.8424228 * s,
122                0.0,
123                10.7570846 * s,
124                0.0,
125                20.131291 * s,
126                -6.2106057 * s,
127                0.0,
128                0.0,
129            )
130            .expect("tilt");
131            (frac_grid(&cell, n), cell)
132        }
133        other => panic!("unknown KNN_HOT_SHAPE {other}"),
134    }
135}
Source

pub fn from_ase(rows: [[f64; 3]; 3]) -> Result<Cell, Error>

ASE-style 3x3 cell: rows are lattice vectors a, b, c.

Origin is zero. Matches atoms.cell in ASE.

Source

pub fn from_con(rows: [[f64; 3]; 3]) -> Result<Cell, Error>

CON lattice: rows are lattice vectors a, b, c, origin zero.

Same storage as readcon lattice_vectors and an eOn CON header that already holds the 3x3 basis.

Source

pub fn from_con_box(boxl: [f64; 3], angles_deg: [f64; 3]) -> Result<Cell, Error>

CON / crystallographic lengths and angles (degrees).

alpha is the angle at the origin between b and c, beta between a and c, gamma between a and b. The basis is the standard reduced triclinic frame used by ASE and eOn.

Source

pub fn from_vesin(rows: [[f64; 3]; 3]) -> Result<Cell, Error>

vesin box: rows are lattice vectors a, b, c, origin zero.

Source

pub fn from_lammps( xlo: f64, xhi: f64, ylo: f64, yhi: f64, zlo: f64, zhi: f64, xy: f64, xz: f64, yz: f64, ) -> Result<Cell, Error>

LAMMPS restricted-triclinic box from xlo xhi ylo yhi zlo zhi and tilt factors xy, xz, yz.

Recovers the same H and origin as a dump ITEM BOX BOUNDS line whose bound lo/hi already include the tilt padding.

Examples found in repository?
examples/knn_hot.rs (lines 119-129)
94fn system(shape: &str, n: usize) -> (Vec<[f64; 3]>, Cell) {
95    match shape {
96        "cubic" => {
97            let boxl = 50.0;
98            (
99                lattice(n, boxl),
100                Cell::ortho(boxl, boxl, boxl).expect("box"),
101            )
102        }
103        "slab" => slab(n),
104        "column" => column(n),
105        "hex" => {
106            // rgsaddle hexagonal cell, scaled to the cubic spacing.
107            let cell = Cell::from_vectors(
108                [50.0, 0.0, 0.0],
109                [25.0, 43.30127018922193, 0.0],
110                [0.0, 0.0, 55.55555555555556],
111                [0.0, 0.0, 0.0],
112            )
113            .expect("hex");
114            (frac_grid(&cell, n), cell)
115        }
116        "tilt" => {
117            // genice sH dump (BOX BOUNDS xy xz yz), scaled to the cubic spacing.
118            let s = 2.85;
119            let cell = Cell::from_lammps(
120                -6.2106057 * s,
121                24.8424228 * s,
122                0.0,
123                10.7570846 * s,
124                0.0,
125                20.131291 * s,
126                -6.2106057 * s,
127                0.0,
128                0.0,
129            )
130            .expect("tilt");
131            (frac_grid(&cell, n), cell)
132        }
133        other => panic!("unknown KNN_HOT_SHAPE {other}"),
134    }
135}
Source

pub fn from_lammps_bounds( xspan: f64, yspan: f64, zspan: f64, xy: f64, xz: f64, yz: f64, xlo_b: f64, ylo_b: f64, zlo_b: f64, ) -> Result<Cell, Error>

LAMMPS dump bound spans plus tilts and bound lo.

xspan, yspan, zspan are xhi_bound - xlo_bound etc. xy, xz, yz are the tilt factors. xlo_b, ylo_b, zlo_b are the bound lo. Inverse of xlo_bound = xlo + min(0, xy, xz, xy+xz).

Source

pub fn is_ortho(&self) -> bool

True when H is diagonal. Distances and lattice shifts then skip the two 3x3 matvecs and use three independent wraps.

Source

pub fn a(&self) -> [f64; 3]

Lattice vector a (first column of H).

Source

pub fn b(&self) -> [f64; 3]

Lattice vector b.

Source

pub fn c(&self) -> [f64; 3]

Lattice vector c.

Source

pub fn origin(&self) -> [f64; 3]

Dump-cell origin.

Source

pub fn widths(&self) -> [f64; 3]

Perpendicular widths of the three faces.

Source

pub fn h(&self) -> [[f64; 3]; 3]

Columns of H.

Source

pub fn hinv(&self) -> [[f64; 3]; 3]

Inverse of H, stored by columns.

Source

pub fn fractional(&self, r: [f64; 3]) -> [f64; 3]

Fractional coordinates in [0, 1).

Orthorhombic boxes divide by the three widths. The general path is s = Hinv (r - origin), then wrap.

Source

pub fn cartesian(&self, s: [f64; 3]) -> [f64; 3]

Cartesian from fractional: r = H s + origin.

Examples found in repository?
examples/knn_hot.rs (line 45)
33fn frac_grid(cell: &Cell, n: usize) -> Vec<[f64; 3]> {
34    let nside = ((n as f64).cbrt().ceil() as usize).max(1);
35    let mut xyz = Vec::with_capacity(n);
36    for i in 0..n {
37        let ix = i % nside;
38        let iy = (i / nside) % nside;
39        let iz = i / (nside * nside);
40        let s = [
41            (ix as f64 + 0.5) / nside as f64,
42            (iy as f64 + 0.5) / nside as f64,
43            (iz as f64 + 0.5) / nside as f64,
44        ];
45        xyz.push(cell.cartesian(s));
46    }
47    xyz
48}
More examples
Hide additional examples
examples/knn_scale.rs (line 62)
43fn tilt(nside: usize) -> (Vec<[f64; 3]>, Cell) {
44    let lx = nside as f64 * 3.125;
45    let cell = Cell::from_vectors(
46        [lx, 0.0, 0.0],
47        [lx * 0.2, lx * 0.9, 0.0],
48        [lx * 0.05, lx * -0.08, lx * 0.95],
49        [0.0, 0.0, 0.0],
50    )
51    .expect("tilt");
52    let mut xyz = Vec::with_capacity(nside * nside * nside);
53    let nf = nside as f64;
54    for iz in 0..nside {
55        for iy in 0..nside {
56            for ix in 0..nside {
57                let s = [
58                    (ix as f64 + 0.5) / nf,
59                    (iy as f64 + 0.5) / nf,
60                    (iz as f64 + 0.5) / nf,
61                ];
62                xyz.push(cell.cartesian(s));
63            }
64        }
65    }
66    (xyz, cell)
67}
Source

pub fn lattice_shift(&self, na: i32, nb: i32, nc: i32) -> [f64; 3]

Cartesian translation by integer lattice counts (na, nb, nc).

Source

pub fn dist2_shifted(&self, p: [f64; 3], q: [f64; 3], shift: [f64; 3]) -> f64

Squared Cartesian distance after applying a lattice shift to q.

This is not a minimum-image wrap. The caller supplies the image via Self::lattice_shift.

Source

pub fn displacement(&self, p: [f64; 3], q: [f64; 3]) -> [f64; 3]

Minimum-image displacement from p to q: wrap(q - p).

Orthorhombic boxes wrap each axis independently. The general path is ds = wrap(Hinv (q - p)), then dr = H ds.

Source

pub fn dist2(&self, p: [f64; 3], q: [f64; 3]) -> f64

Squared minimum-image distance.

Source

pub fn displacement_cartesian(&self, p: [f64; 3], q: [f64; 3]) -> [f64; 3]

Euclidean MIC by checking the 27 nearest lattice images.

Self::displacement wraps in fractional coordinates. That is the LAMMPS / eOn / ASE convention and is the Cartesian nearest image on a reduced (restricted-triclinic) cell. A highly skewed H can make a neighbouring image shorter in Cartesian space. This walk is the check those codes omit.

Source

pub fn is_restricted(&self) -> bool

True when a is along x and b lies in the xy plane.

This is the GROMACS / LAMMPS / HOOMD restricted-triclinic frame. Those engines refuse or rotate any other orientation before wrap.

Source

pub fn tilts_reduced(&self) -> bool

True when the cell is restricted and the tilts sit inside the GROMACS correct_box / LAMMPS half-box limits |xy| <= lx/2, |xz| <= lx/2, |yz| <= ly/2.

On that domain the fractional wrap is the Euclidean MIC, which is why LAMMPS Domain::minimum_image and HOOMD BoxDim::minImage never search extra images.

Source

pub fn reduce_tilts(&self) -> Result<Cell, Error>

Unimodular tilt reduction: GROMACS correct_box.

Subtracts integer lattice vectors so |xy|, |xz|, |yz| sit inside half the corresponding edge. Same Cartesian lattice, new basis. No-op when Self::tilts_reduced is already true. A general orientation is rotated into the restricted frame first (LAMMPS define_general_triclinic).

Source

pub fn to_restricted(&self) -> Result<Cell, Error>

Rotate a general parallelepiped onto the restricted-triclinic frame (LAMMPS general-to-restricted). Identity when already restricted.

Source

pub fn displacement_euclidean(&self, p: [f64; 3], q: [f64; 3]) -> [f64; 3]

Euclidean nearest image, including displacements longer than the cell.

An orthorhombic box is the per-axis wrap. Otherwise Smith, CCP5 Newsletter 1989: the engine wrap is nearest when its length is strictly below half the smallest face altitude. That is the cutoff regime of GROMACS, LAMMPS, and HOOMD. Linkcell k-NN has no cutoff. A hex-prism body diagonal is an engine wrap that is longer than another image.

Past that test the Selling superbasis of H is handed to McKilliam, Grant, and Clarkson, SIAM J. Discrete Math. 28, 1405 (2014). The superbasis is cached on the calling thread, keyed by H, and is not stored in the cell. A tie keeps the engine vector. Self::displacement stays that engine vector on the caller’s H.

Source

pub fn dist2_shifted_many( &self, p: [f64; 3], qs: &[[f64; 3]], shift: [f64; 3], out: &mut [f64], ) -> Result<(), Error>

Squared distances from p to each q, with one lattice shift already applied to every q.

Rapaport’s linked-cell pair: |q + shift - p|^2. No wrap and no Hinv. out has one entry per q.

Source

pub fn dist2_shifted_indexed( &self, p: [f64; 3], positions: &[[f64; 3]], indices: &[usize], shift: [f64; 3], out: &mut [f64], ) -> Result<(), Error>

Squared distances from p to positions named by indices, with one lattice shift already applied.

A linked-cell bin is an index list into the neighbour array, not a contiguous slice. This gathers a chunk of those positions and runs the same kernel as Self::dist2_shifted_many. The gather copies every point before the kernel runs, and on a long index list that copy was slower than the inlined subtract, so the production walk keeps the subtract.

out has one entry per index. An index past positions panics.

Source

pub fn dist2_euclidean(&self, p: [f64; 3], q: [f64; 3]) -> f64

Squared Euclidean MIC distance.

Source

pub fn fractional_matches_cartesian(&self) -> bool

True when fractional wrap matches the 27-image check on a grid of short Cartesian probes (the cutoff-scale displacements MD engines actually wrap).

A hex prism at the tilt limit can still make a body-diagonal fractional wrap longer than a neighbouring image. Production codes ignore that because the cutoff is well below half the long diagonal.

Trait Implementations§

Source§

impl Clone for Cell

Source§

fn clone(&self) -> Cell

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for Cell

Source§

impl Debug for Cell

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl PartialEq for Cell

Source§

fn eq(&self, other: &Cell) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Cell

Auto Trait Implementations§

§

impl Freeze for Cell

§

impl RefUnwindSafe for Cell

§

impl Send for Cell

§

impl Sync for Cell

§

impl Unpin for Cell

§

impl UnsafeUnpin for Cell

§

impl UnwindSafe for Cell

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.