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
impl Cell
Sourcepub fn ortho(lx: f64, ly: f64, lz: f64) -> Result<Cell, Error>
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?
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
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}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}Sourcepub fn ortho_origin(
lx: f64,
ly: f64,
lz: f64,
origin: [f64; 3],
) -> Result<Cell, Error>
pub fn ortho_origin( lx: f64, ly: f64, lz: f64, origin: [f64; 3], ) -> Result<Cell, Error>
Diagonal box with an explicit dump-cell origin.
Sourcepub fn from_vectors(
a: [f64; 3],
b: [f64; 3],
c: [f64; 3],
origin: [f64; 3],
) -> Result<Cell, Error>
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?
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
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}Sourcepub fn from_ase(rows: [[f64; 3]; 3]) -> Result<Cell, Error>
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.
Sourcepub fn from_con(rows: [[f64; 3]; 3]) -> Result<Cell, Error>
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.
Sourcepub fn from_con_box(boxl: [f64; 3], angles_deg: [f64; 3]) -> Result<Cell, Error>
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.
Sourcepub fn from_vesin(rows: [[f64; 3]; 3]) -> Result<Cell, Error>
pub fn from_vesin(rows: [[f64; 3]; 3]) -> Result<Cell, Error>
vesin box: rows are lattice vectors a, b, c, origin zero.
Sourcepub fn from_lammps(
xlo: f64,
xhi: f64,
ylo: f64,
yhi: f64,
zlo: f64,
zhi: f64,
xy: f64,
xz: f64,
yz: f64,
) -> Result<Cell, Error>
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?
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}Sourcepub 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>
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).
Sourcepub fn is_ortho(&self) -> bool
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.
Sourcepub fn fractional(&self, r: [f64; 3]) -> [f64; 3]
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.
Sourcepub fn cartesian(&self, s: [f64; 3]) -> [f64; 3]
pub fn cartesian(&self, s: [f64; 3]) -> [f64; 3]
Cartesian from fractional: r = H s + origin.
Examples found in repository?
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
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}Sourcepub fn lattice_shift(&self, na: i32, nb: i32, nc: i32) -> [f64; 3]
pub fn lattice_shift(&self, na: i32, nb: i32, nc: i32) -> [f64; 3]
Cartesian translation by integer lattice counts (na, nb, nc).
Sourcepub fn dist2_shifted(&self, p: [f64; 3], q: [f64; 3], shift: [f64; 3]) -> f64
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.
Sourcepub fn displacement(&self, p: [f64; 3], q: [f64; 3]) -> [f64; 3]
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.
Sourcepub fn displacement_cartesian(&self, p: [f64; 3], q: [f64; 3]) -> [f64; 3]
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.
Sourcepub fn is_restricted(&self) -> bool
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.
Sourcepub fn tilts_reduced(&self) -> bool
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.
Sourcepub fn reduce_tilts(&self) -> Result<Cell, Error>
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).
Sourcepub fn to_restricted(&self) -> Result<Cell, Error>
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.
Sourcepub fn displacement_euclidean(&self, p: [f64; 3], q: [f64; 3]) -> [f64; 3]
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.
Sourcepub fn dist2_shifted_many(
&self,
p: [f64; 3],
qs: &[[f64; 3]],
shift: [f64; 3],
out: &mut [f64],
) -> Result<(), Error>
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.
Sourcepub fn dist2_shifted_indexed(
&self,
p: [f64; 3],
positions: &[[f64; 3]],
indices: &[usize],
shift: [f64; 3],
out: &mut [f64],
) -> Result<(), Error>
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.
Sourcepub fn fractional_matches_cartesian(&self) -> bool
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§
impl Copy for Cell
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> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
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