use rand::{Rng, RngExt};
use crate::substrates::ca::observation::CAObserver;
use crate::substrates::ca::rules::CARule;
use crate::substrates::ca::schedule::SynchronousCASchedule;
use crate::substrates::ca::state::CAState;
use crate::universe::InformationUniverse;
#[derive(Debug, Clone)]
pub struct CAUniverse<const N: usize, const R: usize = 1> {
state_space: Vec<CAState<N, R>>,
rules: Vec<CARule<N, R>>,
observer: CAObserver,
schedule: SynchronousCASchedule,
}
impl<const N: usize, const R: usize> CAUniverse<N, R> {
pub fn new(obs_name: &str, rng: &mut impl Rng, n_rules: usize) -> Self {
let state_space: Vec<CAState<N, R>> =
(0..500).map(|_| CAState::<N, R>::random(rng)).collect();
let mut rules = Vec::with_capacity(n_rules);
if R == 1 {
for wn in 0..=255u64 {
rules.push(CARule::<N, R>::from_wolfram_number(wn));
}
}
while rules.len() < n_rules {
rules.push(CARule::<N, R>::random(rng));
}
Self {
state_space,
rules,
observer: CAObserver::from_name(obs_name),
schedule: SynchronousCASchedule::new(),
}
}
pub fn obs_name(&self) -> &str {
self.observer.name()
}
}
impl<const N: usize, const R: usize> InformationUniverse for CAUniverse<N, R> {
type State = CAState<N, R>;
type Rule = CARule<N, R>;
type Observation = CAObserver;
type Schedule = SynchronousCASchedule;
fn state_space(&self) -> &[Self::State] {
&self.state_space
}
fn observation(&self) -> &Self::Observation {
&self.observer
}
fn schedule(&self) -> &Self::Schedule {
&self.schedule
}
fn generate_rules(&self, rng: &mut dyn Rng) -> (Vec<Self::Rule>, f64) {
let wolfram_rules: Vec<&CARule<N, R>> = self
.rules
.iter()
.filter(|r| r.wolfram_number().is_some())
.collect();
if wolfram_rules.is_empty() {
let idx = rng.random_range(0..self.rules.len());
let rule = self.rules[idx].clone();
let lambda = rule.lambda();
let structured_ratio = if !(0.2..=0.8).contains(&lambda) {
0.2
} else {
0.8
};
return (vec![rule], structured_ratio);
}
let idx = rng.random_range(0..wolfram_rules.len());
let rule = wolfram_rules[idx].clone();
let lambda = rule.lambda();
let structured_ratio = if !(0.2..=0.8).contains(&lambda) {
0.2
} else {
0.8
};
(vec![rule], structured_ratio)
}
fn null_rules(&self, rng: &mut dyn Rng) -> Vec<Self::Rule> {
let chaotic: &[u64] = &[30, 45, 86, 106, 135, 149];
if R == 1 {
let size = rng.random_range(1..=3);
let mut rules = Vec::with_capacity(size);
for _ in 0..size {
let idx = rng.random_range(0..chaotic.len());
rules.push(CARule::<N, R>::from_wolfram_number(chaotic[idx]));
}
rules
} else {
let size = rng.random_range(1..=3);
(0..size).map(|_| CARule::<N, R>::random(rng)).collect()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rules::Rule;
use rand::SeedableRng;
use rand::rngs::StdRng;
#[test]
fn test_create_universe() {
let mut rng = StdRng::seed_from_u64(42);
let universe = CAUniverse::<8, 1>::new("full_state", &mut rng, 300);
assert_eq!(universe.obs_name(), "full_state");
assert_eq!(universe.state_space().len(), 500);
}
#[test]
fn test_generate_rules_produces_valid_rule() {
let mut rng = StdRng::seed_from_u64(42);
let universe = CAUniverse::<8, 1>::new("full_state", &mut rng, 300);
let mut test_rng = StdRng::seed_from_u64(0);
let (rules, ratio) = universe.generate_rules(&mut test_rng);
assert_eq!(rules.len(), 1);
assert!(ratio >= 0.0 && ratio <= 1.0);
assert!(rules[0].name().contains("Rule"));
}
#[test]
fn test_null_rules_samples_from_chaotic_pool() {
let mut rng = StdRng::seed_from_u64(42);
let universe = CAUniverse::<8, 1>::new("full_state", &mut rng, 300);
let mut test_rng = StdRng::seed_from_u64(0);
let rules = universe.null_rules(&mut test_rng);
assert!(!rules.is_empty());
let chaotic: &[u64] = &[30, 45, 86, 106, 135, 149];
for rule in &rules {
let wn = rule.wolfram_number().unwrap();
assert!(chaotic.contains(&wn), "Rule {} not in chaotic pool", wn);
}
}
}