Skip to main content

rayon_ca/
automaton.rs

1use std::{mem::swap};
2use rand::{random_range, random_bool};
3use rayon::prelude::*;
4
5use crate::strats::{conway_next};
6
7pub struct CellularAutomaton {
8	pub x:usize,
9	pub y:usize,
10	/// Field of simulation with self.x and self.y dimensions.
11	pub cells: Vec<Vec<i32>>,
12	/// Temporary field for next generation, swaps with cells after generating all self.next() in self.step()
13	next_cells: Vec<Vec<i32>>,
14	// pub cell_processor: Arc<dyn Fn(&Self, usize, usize) -> i32 + Sync + Send + 'static>,
15	pub cell_processor: fn(&Self, usize, usize) -> i32,
16}
17
18/// Construction
19impl CellularAutomaton
20{
21	/// Constructor with the given dimensions and default (Conway's game of life) processor.
22	/// ---
23	pub fn new(x:usize, y:usize) -> CellularAutomaton{
24		let mut this = CellularAutomaton{
25			cells: vec![vec![0; y]; x],
26			next_cells: vec![vec![0; y]; x],
27			x:x, y:y,
28			cell_processor:conway_next
29		};
30		this.set_xy(x, y, 0);
31		this
32	}
33
34	/// Constructor with the given dimensions and proccesing lambda.
35	/// ---
36	pub fn new_with_processor(
37			x:usize,
38			y:usize,
39			processor: fn(c:&CellularAutomaton, x:usize, y:usize) -> i32
40	) -> CellularAutomaton {
41		let mut this = CellularAutomaton::new(x, y);
42		this.set_processor(processor);
43		this
44	}
45	/// Sets the cell processor lambda for the automaton.
46	/// ---
47	///
48	/// **processor**: fn(*C*:&CellularAutomaton, *X*:usize, *Y*:usize) -> NextState:i32
49	///
50	/// ---
51	/// Allows to use multiple strategies for one field,
52	/// by swapping cell_processor instead of cloning or moving matrices between automatons.
53	pub fn set_processor(&mut self, processor: fn(c:&CellularAutomaton, x:usize, y:usize) -> i32) -> &mut Self
54	{
55		self.cell_processor = processor;
56		self
57	}
58}
59/// Core processing
60impl CellularAutomaton
61{
62	/// Processes the next state of one cell by coordinates via lambda processor self.cell_processor.
63	#[inline]
64	pub fn next(&self, x:usize, y:usize) -> i32
65	{
66		(&self.cell_processor)(self, x, y)
67	}
68	/// Processes field with rayon borrowing unmutable self to self.cell_processor.
69	pub fn step(&mut self) -> &mut Self {
70		self.next_cells = (0..self.x)
71			.into_par_iter()
72			.map(|i| {
73				(0..self.y)
74					.map(|j| self.next(i, j))
75					.collect()
76			})
77			.collect();
78		swap(&mut self.cells, &mut self.next_cells);
79		self
80	}
81	/// Method to process multiple steps of the automaton as one call.
82	///
83	/// Will be more useful in FFI. But still solves convenience of using.
84	pub fn steps(&mut self, steps:u64) -> &mut Self {
85		for _ in 0..steps {
86			self.step();
87		}
88		self
89	}
90}
91/// Field fillings
92impl CellularAutomaton
93{
94	/// TODO: Add randomize strat
95	pub fn randomize(&mut self) -> &mut Self {
96		for x in 0..self.x
97		{
98			for y in 0..self.y
99			{
100				self.cells[x][y] = random_range(0..2);
101			}
102		}
103		self
104	}
105	pub fn randomize_prob(&mut self, alive_probability:f64) -> &mut Self {
106		for x in 0..self.x
107		{
108			for y in 0..self.y
109			{
110				let p = random_bool(alive_probability);
111				self.cells[x][y] = p as i32;
112			}
113		}
114		self
115	}
116	/// Sets the size of the automaton and initializes all cells with the given state.
117	/// ---
118	pub fn set_xy(&mut self, x:usize, y:usize, init_state: i32) -> &mut Self {
119		self.x = x;
120		self.y = y;
121		self.cells = Vec::new();
122		for _ in 0..x {
123			let mut row = Vec::new();
124			for _ in 0..y {
125				row.push(init_state);
126			}
127			self.cells.push(row);
128		}
129		self
130	}
131	// /// Sets the size of the automaton and initializes all cells with the given matrix.
132	// /// ---
133	// /// Allows to use multiple strategies for one field.
134	// /// But better swap cell_processor instead, it will be more perfomant than cloning matrices between automatons.
135	// pub fn set_matrix(&mut self, matrix: &Vec<Vec<i32>>) -> &mut Self {
136	// 	self.x = matrix.len();
137	// 	self.y = matrix[0].len();
138	// 	self.cells = matrix.clone();
139	// 	self
140	// }
141}
142
143#[cfg(test)]
144mod tests {
145	use std::time;
146	#[cfg(feature = "cli")]
147	use colored::{Colorize, ColoredString};
148	use crate::automaton::CellularAutomaton;
149
150	fn bechmark(steps_count: u32, threshold: u128, x: usize, y: usize) -> u128{
151		let mut c:CellularAutomaton = CellularAutomaton::new(x, y);
152		c.set_processor(|automaton, x, y|{
153			let mut living = 0;
154			if x+1 < automaton.x {living += automaton.cells[x+1][y];}
155			if x > 0             {living += automaton.cells[x-1][y];}
156			if y+1 < automaton.y {living += automaton.cells[x][y+1];}
157			if y > 0            {living += automaton.cells[x][y-1];}
158			match living > 2 {
159				true => 0,
160				false => 1,
161			}
162		});
163		c.randomize();
164		let prev = time::Instant::now();
165		for _ in 0..steps_count{
166			c.step();
167		}
168		let elapsed:u128 = prev.elapsed().as_millis();
169		if elapsed > threshold
170		{
171			let message = "Benchmark failed";
172			#[cfg(feature = "cli")]
173			let msg = message.red();
174			#[cfg(not(feature = "cli"))]
175			let msg = message;
176			println!("{}: {} > {}.", msg, elapsed, threshold);
177			assert!(false);
178		}
179		// println!("Done in {}ms ({}ms)", elapsed, threshold);
180		elapsed
181	}
182
183	fn testing(threshold: u128, steps_count: u32, x: usize, y: usize, tests_count:u32) -> u128{
184		println!("{} {} {} {}", steps_count, threshold, x, y);
185		let mut results = 0;
186		let mut mean = 0;
187		for i in 0..tests_count{
188			let result:u128 = bechmark(steps_count, threshold, x, y);
189			mean = (mean * (results) + result) / (results + 1);
190			results += 1;
191			#[cfg(feature = "cli")]
192			let status:ColoredString;
193			#[cfg(feature = "cli")]
194			{
195				if result < threshold{
196					status = "OK".green();
197				} else {
198					status = "FAIL".red();
199				}
200			}
201			#[cfg(not(feature = "cli"))]
202			let status:&'static str;
203			#[cfg(not(feature = "cli"))]
204			{
205				if result < threshold{
206					status = "OK";
207				} else {
208					status = "FAIL";
209				}
210			}
211			println!("Test {}: {:.3}s ({:.3}s): {}",
212				i + 1,
213				result as f64 / 1000.0,
214				threshold as f64 / 1000.0,
215				status);
216		}
217		println!("Mean: {:.3}s", mean as f64 / 1000.0);
218		mean
219	}
220
221	pub const TESTS_AMT:u32 = 3;
222
223	#[test]
224	fn test11_100(){
225		// let mut c = CellularAutomaton::new(x, y);
226		testing(20, 100, 100, 100, TESTS_AMT);
227	}
228	#[test]
229	fn test12_100_200x200(){
230		testing(40, 100, 200, 200, TESTS_AMT);
231	}
232	#[test]
233	fn test13_100_300x300(){
234		testing(50, 100, 300, 300, TESTS_AMT);
235	}
236	#[test]
237	fn test14_100_400x400(){
238		testing(100, 100, 400, 400, TESTS_AMT);
239	}
240	#[test]
241	fn test15_100_1000x1000(){
242		testing(500, 100, 1000, 1000, TESTS_AMT);
243	}
244	#[test]
245	fn test16_1000_1000x1000(){
246		testing(3000, 1000, 1000, 1000, TESTS_AMT);
247	}
248	#[test]
249	fn test21_1000(){
250		testing(200, 1000, 100, 100, TESTS_AMT);
251	}
252	#[test]
253	fn test22_1000_200x200(){
254		testing(500, 1000, 200, 200, TESTS_AMT);
255	}
256	#[test]
257	fn test31_50_100x100(){
258		testing(10, 50, 100, 100, TESTS_AMT);
259	}
260	#[test]
261	fn test32_50_200x200(){
262		testing(20, 50, 200, 200, TESTS_AMT);
263	}
264	#[test]
265	fn test33_50_1000x1000(){
266		testing(200, 50, 1000, 1000, TESTS_AMT);
267	}
268	fn ns2s(n:i128) -> f64{
269		(n as f64) / (10 as f64).powf(9.0)
270	}
271	#[test]
272	fn switching_test(){
273		println!("Switching Test");
274		for _ in 0..TESTS_AMT{
275			switching();
276		}
277	}
278	fn switching(){
279		// use crate::strats::corridors_next;
280		use crate::strats::conway_next;
281		let mut c1 = CellularAutomaton::new(100, 100);
282		let start_time = time::Instant::now();
283		c1.steps(100);
284		let elapsed_time = start_time.elapsed().as_nanos();
285		println!("Elapsed time for c1: {}ns", elapsed_time);
286
287		let mut c2 = CellularAutomaton::new(100, 100);
288
289		let p1_start = time::Instant::now();
290		c2.steps(50);
291		let p1_elapsed = p1_start.elapsed().as_nanos();
292		println!("Elapsed time for p1: {}ns", p1_elapsed);
293
294		c2.set_processor(conway_next);
295
296		let p2_start = time::Instant::now();
297		c2.steps(50);
298		let p2_elapsed = p2_start.elapsed().as_nanos();
299		// let c2_elapsed = p1_start.elapsed().as_nanos();
300		println!("Elapsed time for p2: {}ns", p2_elapsed);
301
302		println!("Difference between ca1 ({}ns) and ca2 ({}ns): {}ns, {}",
303			ns2s(elapsed_time as i128),
304			ns2s((p2_elapsed + p1_elapsed) as i128),
305			ns2s(((p2_elapsed + p1_elapsed) - (elapsed_time)) as i128),
306			((p2_elapsed+p1_elapsed) as f64)/(elapsed_time as f64)
307		);
308	}
309}