use ::ndarray::{prelude::*, NdIndex};
use ::rand::prelude::*;
pub struct Lattice {
dims: [usize; 2],
n_of_spins: i32,
inner: Array2<i32>,
neighbors: Array2<[[usize; 2]; 4]>,
}
impl Lattice {
pub fn new(dims: [usize; 2]) -> Self
{
let inner = Array2::from_shape_fn(dims, |_| {
*[-1, 1].choose(&mut SmallRng::from_entropy()).unwrap()
});
Self::from_array(inner)
}
pub fn inner(
&self,
) -> ndarray::ArrayView<i32, ndarray::Dim<[ndarray::Ix; 2]>> {
self.inner.view()
}
pub fn from_array(array: Array2<i32>) -> Self {
assert!(
array.iter().all(|spin| *spin == 1 || *spin == -1),
"Invalid spin value."
);
let roll_index = |ix: usize, amt: i32, max: usize| {
let max = max as i32;
((ix as i32 + amt + max) % max) as usize
};
let (width, height) = array.dim();
let neighbors = Array2::from_shape_fn((width, height), |ix| {
[
[roll_index(ix.0, 1, width), ix.1], [ix.0, roll_index(ix.1, 1, height)], [roll_index(ix.0, -1, width), ix.1], [ix.0, roll_index(ix.1, -1, height)], ]
});
Lattice {
dims: [width, height],
inner: array,
n_of_spins: width as i32 * height as i32,
neighbors,
}
}
pub fn dims(&self) -> [usize; 2] {
self.dims
}
fn spin_times_all_neighbors<I>(&self, ix: I) -> i32
where
I: NdIndex<ndarray::Dim<[ndarray::Ix; 2]>> + Copy,
{
self.inner[ix]
* self.neighbors[ix]
.iter()
.map(|n_ix| self.inner[*n_ix])
.sum::<i32>()
}
fn spin_times_two_neighbors<I>(&self, ix: I) -> i32
where
I: NdIndex<ndarray::Dim<[ndarray::Ix; 2]>> + Copy,
{
self.inner[ix]
* self.neighbors[ix][0..2]
.iter()
.map(|n_ix| self.inner[*n_ix])
.sum::<i32>()
}
pub fn measure_E_diff<I>(&self, ix: I) -> f64
where
I: NdIndex<ndarray::Dim<[ndarray::Ix; 2]>> + Copy,
{
2.0 * f64::from(self.spin_times_all_neighbors(ix))
}
pub fn measure_E_diff_with_h<I>(&self, ix: I, h: &Array2<f64>) -> f64
where
I: NdIndex<ndarray::Dim<[ndarray::Ix; 2]>> + Copy,
{
2.0 * (f64::from(self.spin_times_all_neighbors(ix))
+ f64::from(self.inner[ix]) * h[ix])
}
pub fn measure_E(&self) -> f64 {
-f64::from(
self.inner
.indexed_iter()
.map(|(ix, _)| self.spin_times_two_neighbors(ix))
.sum::<i32>(),
)
}
pub fn measure_E_with_h(&self, h: &Array2<f64>) -> f64 {
-f64::from(
self.inner
.indexed_iter()
.map(|(ix, _)| self.spin_times_two_neighbors(ix))
.sum::<i32>(),
) - (self.inner.map(|s| f64::from(*s)) * h).sum()
}
pub fn measure_I(&self) -> f64 {
f64::from(self.inner.sum().abs()) / f64::from(self.n_of_spins)
}
pub fn flip_spin<I>(&mut self, ix: I)
where
I: NdIndex<ndarray::Dim<[ndarray::Ix; 2]>> + Copy,
{
*self.inner.get_mut(ix).unwrap() *= -1;
}
pub fn gen_random_index<R: RngCore>(&mut self, rng: &mut R) -> [usize; 2] {
[
rng.gen_range(0, self.dims[0] as u64) as usize,
rng.gen_range(0, self.dims[1] as u64) as usize,
]
}
}
#[cfg(test)]
mod test {
use ::pretty_assertions::assert_eq;
use super::*;
fn float_error(x: f64, t: f64) -> f64 {
(x - t).abs() / t
}
#[test]
fn test_lattice_new() {
let lattice = Lattice::new([17, 10]);
assert_eq!(lattice.dims(), [17, 10]);
}
#[test]
fn test_lattice_from_array() {
let array = Array::from_shape_vec((2, 2), vec![1, -1, 1, -1]).unwrap();
let lattice = Lattice::from_array(array);
assert_eq!(lattice.dims(), [2, 2]);
}
#[test]
fn test_spin_times_neighbors() {
let spins = [-1, -1, 1, 1, 1, 1, 1, 1, -1];
let array = Array::from_shape_vec((3, 3), spins.to_vec()).unwrap();
let lattice = Lattice::from_array(array);
let product = lattice.spin_times_all_neighbors((1, 1));
assert_eq!(product, 2);
}
#[test]
fn test_measure_E_difference() {
let array =
Array::from_shape_vec((3, 3), vec![-1, -1, 1, 1, 1, 1, -1, 1, 1])
.unwrap();
let lattice = Lattice::from_array(array);
let E_diff = lattice.measure_E_diff((1, 1));
assert_eq!(E_diff, 4.0);
}
#[test]
fn test_measure_E_difference_in_magnetic_field() {
let array =
Array::from_shape_vec((3, 3), vec![-1, -1, 1, 1, 1, 1, -1, 1, 1])
.unwrap();
let h = Array::from_shape_vec(
(3, 3),
vec![-1.0, -1.0, 1.0, 1.0, -7.0, 1.0, -1.0, 1.0, 1.0],
)
.unwrap();
let lattice = Lattice::from_array(array);
let E_diff = lattice.measure_E_diff_with_h((1, 1), &h);
assert_eq!(E_diff, -10.0);
}
#[test]
fn test_measure_E() {
let array =
Array::from_shape_vec((3, 3), vec![-1, -1, -1, 1, 1, -1, 1, 1, -1])
.unwrap();
let lattice = Lattice::from_array(array);
let E = lattice.measure_E();
assert_eq!(E, -2.0);
}
#[test]
fn test_measure_E_in_magnetic_field() {
let array =
Array::from_shape_vec((3, 3), vec![-1, -1, -1, 1, 1, -1, 1, 1, -1])
.unwrap();
let h = Array::from_shape_vec(
(3, 3),
vec![-1.0, -1.0, 1.0, 1.0, -7.0, 1.0, -1.0, 1.0, 1.0],
)
.unwrap();
let lattice = Lattice::from_array(array);
let E = lattice.measure_E_with_h(&h);
assert_eq!(E, 5.0);
}
#[test]
fn test_measure_I() {
let array = Array::from_shape_vec((2, 2), vec![-1, -1, -1, 1]).unwrap();
let lattice = Lattice::from_array(array);
let I = lattice.measure_I();
assert_eq!(I, 0.5);
}
#[test]
fn test_flip_spin() {
let array = Array::from_shape_vec(
(3, 3),
vec![-1, -1, -1, -1, 1, 1, -1, -1, 1],
)
.unwrap();
let mut lattice = Lattice::from_array(array);
let E_1 = lattice.measure_E();
lattice.flip_spin((1, 1));
let E_2 = lattice.measure_E();
assert!(float_error(E_2 - E_1, -4.0) < 0.01);
}
}