use std::ops::Range;
use burn::{
Tensor,
config::Config,
prelude::{
Backend,
Bool,
Int,
s,
},
tensor::Distribution,
};
use serde::{
Deserialize,
Serialize,
};
pub fn fuzz_state_3d<B: Backend>(
state: Tensor<B, 3, Bool>,
density: f64,
) -> Tensor<B, 3, Bool> {
if density == 0.0 {
return state;
}
let noise: Tensor<B, 3, Bool> = Tensor::<B, 3>::random(
state.shape(),
Distribution::Bernoulli(density),
&state.device(),
)
.equal_elem(1.0);
state.bool_xor(noise)
}
pub fn wrap_state_3d<B: Backend>(state: Tensor<B, 3, Bool>) -> Tensor<B, 3, Bool> {
let top = state.clone().slice(s![1, .., ..]);
let bottom = state.clone().slice(s![-2, .., ..]);
let left = state.clone().slice(s![.., 1, ..]);
let right = state.clone().slice(s![.., -2, ..]);
let front = state.clone().slice(s![.., .., 1]);
let back = state.clone().slice(s![.., .., -2]);
state
.slice_assign(s![0, .., ..], bottom)
.slice_assign(s![-1, .., ..], top)
.slice_assign(s![.., 0, ..], right)
.slice_assign(s![.., -1, ..], left)
.slice_assign(s![.., .., 0], back)
.slice_assign(s![.., .., -1], front)
}
pub fn next_state_wrapped_3d<B: Backend>(
state: Tensor<B, 3, Bool>,
rules: &LifeRules,
) -> Tensor<B, 3, Bool> {
let update = next_interior_3d(state.clone(), rules);
let state = state.slice_assign(s![1..-1, 1..-1, 1..-1], update);
wrap_state_3d(state)
}
pub fn next_interior_3d<B: Backend>(
state: Tensor<B, 3, Bool>,
rules: &LifeRules,
) -> Tensor<B, 3, Bool> {
#[cfg(debug_assertions)]
let [h, w, z] = crate::contracts::unpack_shape_contract!(["h", "w", "z"], &state.dims(),);
let is_live = state.clone().slice(s![1..-1, 1..-1, 1..-1,]);
let int_state = state.clone().int();
let self_count = int_state.clone().slice(s![1..-1, 1..-1, 1..-1,]);
let windows: Tensor<B, 6, Int> = int_state
.unfold::<4, _>(0, 3, 1)
.unfold::<5, _>(1, 3, 1)
.unfold::<6, _>(2, 3, 1);
let window_count = windows.sum_dims(&[3, 4, 5]).squeeze_dims::<3>(&[3, 4, 5]);
let neighbor_count = window_count - self_count;
let spawns = is_live.clone().bool_not().bool_and(
neighbor_count
.clone()
.greater_equal_elem(rules.spawn.start as i32)
.bool_and(neighbor_count.clone().lower_elem(rules.keep.end as i32)),
);
let keeps = is_live.bool_and(
neighbor_count
.clone()
.greater_equal_elem(rules.keep.start as i32)
.bool_and(neighbor_count.clone().lower_elem(rules.keep.end as i32)),
);
let update = spawns.bool_or(keeps);
#[cfg(debug_assertions)]
crate::contracts::assert_shape_contract_periodically!(
["h" - "pad", "w" - "pad", "z" - "pad"],
&update.dims(),
&[("h", h), ("w", w), ("z", z), ("pad", 2)],
);
update
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct LifeRules {
pub spawn: Range<usize>,
pub keep: Range<usize>,
}
impl Default for LifeRules {
fn default() -> Self {
Self {
spawn: 3..4,
keep: 2..4,
}
}
}
#[derive(Config, Debug)]
pub struct ConwayLife3DConfig {
pub shape: [usize; 3],
pub rules: LifeRules,
}
impl ConwayLife3DConfig {
pub fn init<B: Backend>(
self,
device: &B::Device,
) -> ConwayLife3DState<B> {
ConwayLife3DState {
state: Tensor::<B, 3, Int>::zeros(self.shape, device).bool(),
rules: self.rules,
}
}
}
pub struct ConwayLife3DState<B: Backend> {
pub state: Tensor<B, 3, Bool>,
pub rules: LifeRules,
}
impl<B: Backend> ConwayLife3DState<B> {
pub fn device(&self) -> B::Device {
self.state.device()
}
pub fn shape(&self) -> [usize; 3] {
self.state.shape().dims()
}
pub fn fuzz(
&mut self,
density: f64,
) {
if density == 0.0 {
return;
}
let noise: Tensor<B, 3, Bool> = Tensor::<B, 3>::random(
self.shape(),
Distribution::Bernoulli(density),
&self.device(),
)
.equal_elem(1.0);
self.state = self.state.clone().bool_or(noise);
}
pub fn step(&mut self) {
self.state = next_state_wrapped_3d(self.state.clone(), &self.rules);
B::sync(&self.device()).unwrap();
}
}
#[cfg(test)]
mod tests {
use serial_test::serial;
use super::*;
use crate::support::testing::PerformanceBackend;
#[test]
#[serial]
fn test_smoke() {
type B = PerformanceBackend;
let device = Default::default();
let steps = 100;
let grid_size = 20;
let config = ConwayLife3DConfig {
shape: [grid_size, grid_size, grid_size],
rules: LifeRules {
spawn: 3..5,
keep: 2..4,
},
};
let mut game: ConwayLife3DState<B> = config.init(&device);
game.fuzz(0.05);
for _ in 0..steps {
game.step();
}
}
}