use coord::Coord;
use surface::points::Points;
pub struct Lattice;
impl Lattice {
pub fn hexagonal(a: f64) -> LatticeBuilder {
let crystal = Crystal::hexagonal(a);
LatticeBuilder::new(crystal)
}
pub fn triclinic(a: f64, b: f64, gamma: f64) -> LatticeBuilder {
let crystal = Crystal::triclinic(a, b, gamma);
LatticeBuilder::new(crystal)
}
}
pub struct LatticeBuilder {
crystal: Crystal,
nx: u64,
ny: u64,
}
impl LatticeBuilder {
pub fn with_size(self, size_x: f64, size_y: f64) -> LatticeBuilder {
let Spacing(dx, dy, _) = self.crystal.spacing();
let nx = (size_x / dx).round() as u64;
let ny = (size_y / dy).round() as u64;
self.with_bins(nx, ny)
}
pub fn finalize(mut self) -> Points {
let coords = match self.crystal.lattice_type {
Hexagonal => self.hexagonal(),
_ => self.generic(),
};
let Spacing(dx, dy, _) = self.crystal.spacing();
let box_size = Coord::new((self.nx as f64) * dx, (self.ny as f64) * dy, 0.0);
Points {
box_size: box_size,
coords: coords,
}
}
fn new(crystal: Crystal) -> LatticeBuilder {
LatticeBuilder {
crystal: crystal,
nx: 0,
ny: 0,
}
}
fn with_bins(mut self, nx: u64, ny: u64) -> LatticeBuilder {
self.nx = nx;
self.ny = ny;
self
}
fn generic(&mut self) -> Vec<Coord> {
let Spacing(dx, dy, dx_per_row) = self.crystal.spacing();
(0..self.ny)
.flat_map(|row| {
(0..self.nx)
.map(move |col| {
Coord::new((col as f64) * dx + (row as f64) * dx_per_row,
(row as f64) * dy,
0.0)
})
})
.collect()
}
fn hexagonal(&mut self) -> Vec<Coord> {
self.nx = ((self.nx as f64 / 3.0).ceil() * 3.0) as u64;
self.ny = ((self.ny as f64 / 2.0).ceil() * 2.0) as u64;
let Spacing(dx, dy, dx_per_row) = self.crystal.spacing();
(0..self.ny)
.flat_map(|row| {
(0..self.nx)
.filter(move |col| (col + row + 1) % 3 > 0)
.map(move |col| {
Coord::new((col as f64) * dx + (row as f64) * dx_per_row,
(row as f64) * dy,
0.0)
})
})
.collect()
}
}
enum LatticeType {
Hexagonal,
Triclinic,
}
use self::LatticeType::*;
struct Crystal {
a: f64,
b: f64,
gamma: f64,
lattice_type: LatticeType,
}
impl Crystal {
fn hexagonal(a: f64) -> Crystal {
Crystal {
a: a,
b: a,
gamma: 2.0 * ::std::f64::consts::PI / 3.0, lattice_type: Hexagonal,
}
}
fn triclinic(a: f64, b: f64, gamma: f64) -> Crystal {
Crystal {
a: a,
b: b,
gamma: gamma,
lattice_type: Triclinic,
}
}
fn spacing(&self) -> Spacing {
let dx = self.a;
let dy = self.b * self.gamma.sin();
let dx_per_row = self.b * self.gamma.cos();
Spacing(dx, dy, dx_per_row)
}
}
struct Spacing(f64, ) in a lattice
f64, f64);
#[cfg(test)]
mod tests {
use super::*;
use std::f64;
#[test]
fn hexagonal_crystal() {
let crystal = Crystal::hexagonal(1.0);
assert_eq!(1.0, crystal.a);
assert_eq!(1.0, crystal.b);
assert_eq!(2.0 * f64::consts::PI / 3.0, crystal.gamma);
}
#[test]
fn triclinic_crystal() {
let crystal = Crystal::triclinic(1.0, 2.0, 3.0);
assert_eq!(1.0, crystal.a);
assert_eq!(2.0, crystal.b);
assert_eq!(3.0, crystal.gamma);
}
#[test]
fn triclinic_lattice() {
let dx = 1.0;
let angle = 60f64.to_radians();
let lattice = Lattice::triclinic(dx, dx, angle)
.with_bins(3, 2)
.finalize();
let dy = dx * angle.sin();
let dx_per_y = dx * angle.cos();
assert_eq!(Coord::new(3.0 * dx, 2.0 * dy, 0.0), lattice.box_size);
let mut iter = lattice.coords.iter();
assert_eq!(Some(&Coord::new(0.0, 0.0, 0.0)), iter.next());
assert_eq!(Some(&Coord::new(dx, 0.0, 0.0)), iter.next());
assert_eq!(Some(&Coord::new(2.0 * dx, 0.0, 0.0)), iter.next());
assert_eq!(Some(&Coord::new(dx_per_y, dy, 0.0)), iter.next());
assert_eq!(Some(&Coord::new(dx_per_y + dx, dy, 0.0)), iter.next());
assert_eq!(Some(&Coord::new(dx_per_y + 2.0 * dx, dy, 0.0)), iter.next());
assert_eq!(None, iter.next());
}
#[test]
fn hexagonal_lattice_has_empty_points() {
let lattice = Lattice::hexagonal(1.0).with_bins(6, 2).finalize();
let crystal = Crystal::hexagonal(1.0);
let Spacing(dx, dy, dx_per_row) = crystal.spacing();
let mut iter = lattice.coords.iter();
assert_eq!(Some(&Coord::new(0.0, 0.0, 0.0)), iter.next());
assert_eq!(Some(&Coord::new(dx, 0.0, 0.0)), iter.next());
assert_eq!(Some(&Coord::new(3.0 * dx, 0.0, 0.0)), iter.next());
assert_eq!(Some(&Coord::new(4.0 * dx, 0.0, 0.0)), iter.next());
assert_eq!(Some(&Coord::new(dx_per_row, dy, 0.0)), iter.next());
assert_eq!(Some(&Coord::new(dx_per_row + 2.0 * dx, dy, 0.0)), iter.next());
assert_eq!(Some(&Coord::new(dx_per_row + 3.0 * dx, dy, 0.0)), iter.next());
assert_eq!(Some(&Coord::new(dx_per_row + 5.0 * dx, dy, 0.0)), iter.next());
assert_eq!(None, iter.next());
}
#[test]
fn hexagonal_lattice_has_corrected_periodicity() {
let lattice = Lattice::hexagonal(1.0).with_bins(4, 1).finalize();
let expected = Lattice::hexagonal(1.0).with_bins(6, 2).finalize();
assert_eq!(expected.coords, lattice.coords);
assert_eq!(expected.box_size, lattice.box_size);
}
#[test]
fn lattice_with_size() {
let lattice = Lattice::triclinic(1.0, 0.5, 90f64.to_radians())
.with_size(2.1, 0.9)
.finalize();
let expected = Lattice::triclinic(1.0, 0.5, 90f64.to_radians())
.with_bins(2, 2)
.finalize();
assert_eq!(expected.coords, lattice.coords);
assert_eq!(expected.box_size, lattice.box_size);
}
#[test]
fn hexagonal_lattice_with_size() {
let lattice = Lattice::hexagonal(1.0).with_size(2.1, 0.9).finalize();
let expected = Lattice::hexagonal(1.0).with_bins(3, 2).finalize();
assert_eq!(expected.coords, lattice.coords);
assert_eq!(expected.box_size, lattice.box_size);
}
#[test]
fn lattice_constructed_without_size_is_empty() {
let lattice = Lattice::hexagonal(1.0).finalize();
assert_eq!(Coord::new(0.0, 0.0, 0.0), lattice.box_size);
assert!(lattice.coords.is_empty());
}
#[test]
fn crystal_spacing() {
let crystal = Crystal::triclinic(1.0, 3.0, f64::consts::PI / 3.0);
let Spacing(dx, dy, dx_per_row) = crystal.spacing();
assert_eq!(1.0, dx);
assert_eq!(3.0 * 3.0f64.sqrt() / 2.0, dy);
assert!((1.5 - dx_per_row).abs() < 1e-6);
}
}