cellular_raza_core/backend/chili/simulation_flow.rs
1use cellular_raza_concepts::IndexError;
2use itertools::Itertools;
3
4use std::{
5 collections::{BTreeMap, BTreeSet},
6 marker::PhantomData,
7 sync::atomic::AtomicBool,
8};
9
10#[cfg(feature = "tracing")]
11use tracing::instrument;
12
13use super::errors::SimulationError;
14
15///
16/// This very simple implementation uses the [hurdles::Barrier] struct which should
17/// in theory perform faster than the [std::sync::Barrier] struct from the standard library.
18///
19/// By using the [SyncSubDomains] trait, we can automatically create a collection of syncers
20/// which can then be simply given to the respective threads and handle synchronization.
21/// ```
22/// # use std::collections::BTreeMap;
23/// # use cellular_raza_core::backend::chili::{BarrierSync, FromMap, SyncSubDomains};
24/// let map = BTreeMap::from_iter([
25/// (0, std::collections::BTreeSet::from([1])),
26/// (1, std::collections::BTreeSet::from([0])),
27/// ]);
28/// let mut syncers = BarrierSync::from_map(&map).unwrap();
29/// assert_eq!(syncers.len(), 2);
30///
31/// let mut syncer_0 = syncers.remove(&0).unwrap();
32/// let mut syncer_1 = syncers.remove(&1).unwrap();
33///
34/// // Define the number of iterations to run
35/// let n_iterations = 10;
36/// let shared_counter = std::sync::Arc::new(std::sync::Mutex::new(0_i64));
37///
38/// let shared_counter_0 = std::sync::Arc::clone(&shared_counter);
39/// let handle_0 = std::thread::spawn(move || {
40/// for _ in 0..n_iterations {
41/// syncer_0.sync().unwrap();
42/// *shared_counter_0.lock().unwrap() += 1;
43/// syncer_0.sync().unwrap();
44/// }
45/// });
46///
47/// for i in 0..n_iterations {
48/// syncer_1.sync().unwrap();
49/// syncer_1.sync().unwrap();
50/// assert_eq!(*shared_counter.lock().unwrap(), i+1);
51/// }
52/// handle_0.join();
53/// ```
54pub struct BarrierSync {
55 barrier: hurdles::Barrier,
56 got_error: std::sync::Arc<AtomicBool>,
57}
58
59/// Validates a given map.
60///
61/// This algorithm checks if every keys neighbors also contain the specified key.
62/// If this is not the case, the map cannot be considered valid.
63/// Note that this algorithm does not check if all keys are connected.
64/// This means, disjoint sets are allowed.
65///
66/// ```
67/// use cellular_raza_core::backend::chili::validate_map;
68///
69/// let new_map = std::collections::BTreeMap::from([
70/// (1_usize, std::collections::BTreeSet::from([0,2])),
71/// (2_usize, std::collections::BTreeSet::from([1,3])),
72/// (3_usize, std::collections::BTreeSet::from([2,0])),
73/// (0_usize, std::collections::BTreeSet::from([3,1])),
74/// ]);
75///
76/// let is_valid = validate_map(&new_map);
77/// assert_eq!(is_valid, true);
78/// ```
79#[cfg_attr(feature = "tracing", instrument(skip_all))]
80pub fn validate_map<I>(map: &std::collections::BTreeMap<I, BTreeSet<I>>) -> bool
81where
82 I: Eq + core::hash::Hash + Clone + Ord,
83{
84 for (index, neighbors) in map.iter() {
85 if neighbors.iter().any(|n| match map.get(n) {
86 Some(reverse_neighbors) => !reverse_neighbors.contains(index),
87 None => true,
88 }) {
89 return false;
90 }
91 }
92 true
93}
94
95/// Undirected graph
96///
97/// This datatype is planned to be used in order to create the simulation subdomains.
98/// There are no plans to extend the use of this object.
99// TODO actually use the specifically designed UDGraph instead of FromMap trait.
100pub struct UDGraph<I>(pub(crate) Vec<(I, I)>);
101
102impl<I> UDGraph<I> {
103 /// Construct a new undirected graph.
104 ///
105 /// ```
106 /// # use cellular_raza_core::backend::chili::UDGraph;
107 /// let ud_graph: UDGraph<usize> = UDGraph::new();
108 /// ```
109 pub fn new() -> Self {
110 Self(Vec::new())
111 }
112
113 /// Push an additional connection between nodes to the UDGraph
114 ///
115 /// This will return an option in either of two cases
116 /// 1. The connection is already contained in the Graph
117 /// 2. The nodes are identical, ie. `new_connection.0==new_connection.1`
118 ///
119 /// ```
120 /// # use cellular_raza_core::backend::chili::UDGraph;
121 /// let mut ud_graph = UDGraph::new();
122 /// assert_eq!(ud_graph.push((1, 2)), None);
123 /// assert_eq!(ud_graph.push((1, 3)), None);
124 ///
125 /// // These cases will not return `None` due to the
126 /// // specified reasons above
127 /// assert_eq!(ud_graph.push((1, 2)), Some((1, 2)));
128 /// assert_eq!(ud_graph.push((1, 1)), Some((1, 1)));
129 /// ```
130 pub fn push(&mut self, new_connection: (I, I)) -> Option<(I, I)>
131 where
132 I: PartialEq,
133 {
134 if new_connection.0 == new_connection.1 {
135 return Some(new_connection);
136 }
137 if self
138 .0
139 .iter()
140 .any(|connection| connection == &new_connection)
141 {
142 return Some(new_connection);
143 }
144 self.0.push(new_connection);
145 None
146 }
147
148 /// Extends the [UDGraph] with new connections
149 ///
150 /// Will return all connections which can not be added by the [push](UDGraph::push) method.
151 ///
152 /// ```
153 /// # use cellular_raza_core::backend::chili::UDGraph;
154 /// let mut ud_graph = UDGraph::new();
155 /// let new_connections = [
156 /// (1_f64, 2_f64),
157 /// (2_f64, 3_f64),
158 /// (3_f64, 4_f64),
159 /// ];
160 /// let res = ud_graph.extend(new_connections);
161 /// assert_eq!(res, vec![]);
162 /// ```
163 pub fn extend<J>(&mut self, new_connections: J) -> Vec<(I, I)>
164 where
165 I: PartialEq,
166 J: IntoIterator<Item = (I, I)>,
167 {
168 new_connections
169 .into_iter()
170 .filter_map(|new_connection| self.push(new_connection))
171 .collect()
172 }
173
174 /// Clears the [UDGraph] thus removing all connections.
175 ///
176 /// See [std::vec::Vec::clear].
177 pub fn clear(&mut self) {
178 self.0.clear()
179 }
180
181 /// Drains the [UDGraph], thus returning an iterator over the specified elements.
182 ///
183 /// See [std::vec::Vec::drain].
184 pub fn drain<R>(&mut self, range: R) -> std::vec::Drain<'_, (I, I)>
185 where
186 R: core::ops::RangeBounds<usize>,
187 {
188 self.0.drain(range)
189 }
190
191 /// Returns all nodes currently stored in the [UDGraph].
192 ///
193 /// ```
194 /// # use cellular_raza_core::backend::chili::UDGraph;
195 /// let mut ud_graph = UDGraph::new();
196 /// ud_graph.push(("a", "s"));
197 /// ud_graph.push(("a", "K"));
198 /// ud_graph.push(("h", "s"));
199 ///
200 /// assert_eq!(ud_graph.nodes(), vec![&"a", &"s", &"K", &"h"]);
201 /// ```
202 pub fn nodes(&self) -> Vec<&I>
203 where
204 I: Clone + Eq + core::hash::Hash,
205 {
206 self.0
207 .iter()
208 .map(|(c1, c2)| [c1, c2].into_iter())
209 .flatten()
210 .unique()
211 .collect()
212 }
213}
214
215impl<I> IntoIterator for UDGraph<I> {
216 type Item = (I, I);
217 type IntoIter = std::vec::IntoIter<(I, I)>;
218
219 /// Consumes the graph and returns iterator over elements.
220 ///
221 /// See [std::vec::Vec::into_iter].
222 fn into_iter(self) -> std::vec::IntoIter<(I, I)> {
223 self.0.into_iter()
224 }
225}
226
227impl<I> UDGraph<I>
228where
229 I: core::hash::Hash + Clone + Eq + Ord,
230{
231 /// Convert the [UDGraph] into a regular [BTreeMap].
232 ///
233 /// ```
234 /// # use cellular_raza_core::backend::chili::UDGraph;
235 /// let mut ud_graph = UDGraph::new();
236 /// ud_graph.push((1, 2));
237 /// ud_graph.push((2, 3));
238 /// ud_graph.push((3, 1));
239 ///
240 /// let map = ud_graph.to_btree();
241 /// assert_eq!(map.keys().len(), 3);
242 /// ```
243 pub fn to_btree(self) -> BTreeMap<I, BTreeSet<I>> {
244 let mut map: BTreeMap<_, BTreeSet<I>> = self
245 .0
246 .iter()
247 .map(|x| {
248 [
249 (x.0.clone(), BTreeSet::new()),
250 (x.1.clone(), BTreeSet::new()),
251 ]
252 .into_iter()
253 })
254 .flatten()
255 .collect();
256 self.0.iter().for_each(|(c1, c2)| {
257 map.entry(c1.clone()).and_modify(|v| {
258 v.insert(c2.clone());
259 });
260 map.entry(c2.clone()).and_modify(|v| {
261 v.insert(c1.clone());
262 });
263 });
264 map
265 }
266}
267
268impl<I> core::ops::Deref for UDGraph<I> {
269 type Target = Vec<(I, I)>;
270
271 fn deref(&self) -> &Self::Target {
272 &self.0
273 }
274}
275
276impl<I> From<UDGraph<I>> for PhantomData<I> {
277 #[allow(unused)]
278 fn from(value: UDGraph<I>) -> Self {
279 PhantomData
280 }
281}
282
283/// Construct a BTreeMap of the type from a graph
284///
285/// The types should be connected according to the connections specified in the graph.
286/// Afterwards, this BTreeMap can over multiple threads and used since all components
287/// are connected according the the initial graph.
288pub trait BuildFromGraph<I>
289where
290 Self: Sized,
291 I: Clone + Eq + core::hash::Hash + Ord,
292{
293 /// Builds the BTreeMap
294 fn build_from_graph(graph: UDGraph<I>) -> Result<BTreeMap<I, Self>, IndexError>;
295}
296
297impl<I> BuildFromGraph<I> for PhantomData<I>
298where
299 Self: Sized,
300 I: Clone + Eq + core::hash::Hash + Ord,
301{
302 fn build_from_graph(graph: UDGraph<I>) -> Result<BTreeMap<I, Self>, IndexError> {
303 Ok(graph
304 .into_iter()
305 .map(|(key, _)| (key, PhantomData::<I>))
306 .collect())
307 }
308}
309
310// TODO migrate to FromGraph eventually!
311/// Constructs a collection of Items from a map (graph)
312pub trait FromMap<I>
313where
314 Self: Sized,
315{
316 /// [SubDomains](cellular_raza_concepts::SubDomain) can be neighboring each
317 /// other via complicated graphs.
318 /// An easy way to represent this is by using a [BTreeMap]. We want to create Barriers which match
319 /// the specified subdomain indices.
320 fn from_map(map: &BTreeMap<I, BTreeSet<I>>) -> Result<BTreeMap<I, Self>, IndexError>
321 where
322 I: Eq + core::hash::Hash + Clone + Ord;
323}
324
325impl<I> FromMap<I> for PhantomData<I> {
326 fn from_map(map: &BTreeMap<I, BTreeSet<I>>) -> Result<BTreeMap<I, Self>, IndexError>
327 where
328 I: Eq + core::hash::Hash + Clone + Ord,
329 {
330 Ok(map
331 .into_iter()
332 .map(|(key, _)| (key.clone(), PhantomData::<I>))
333 .collect())
334 }
335}
336
337/// Responsible for syncing the simulation between different threads.
338pub trait SyncSubDomains {
339 /// Function which forces connected syncers to wait for each other.
340 /// This approach does not necessarily require all threads to wait but can mean that
341 /// only depending threads wait for each other.
342 fn sync(&mut self) -> Result<(), SimulationError>;
343 /// TODO
344 fn store_error(
345 &mut self,
346 maybe_error: Result<(), SimulationError>,
347 ) -> Result<bool, SimulationError>;
348}
349
350impl<I> FromMap<I> for BarrierSync {
351 fn from_map(map: &BTreeMap<I, BTreeSet<I>>) -> Result<BTreeMap<I, Self>, IndexError>
352 where
353 I: Eq + core::hash::Hash + Clone + Ord,
354 {
355 let barrier = hurdles::Barrier::new(map.len());
356 let got_error = std::sync::Arc::new(AtomicBool::new(false));
357 let res = map
358 .keys()
359 .map(|i| {
360 (
361 i.clone(),
362 Self {
363 barrier: barrier.clone(),
364 got_error: std::sync::Arc::clone(&got_error),
365 },
366 )
367 })
368 .collect();
369 Ok(res)
370 }
371}
372
373impl<I> BuildFromGraph<I> for BarrierSync
374where
375 I: Clone + Eq + core::hash::Hash + std::cmp::Ord,
376{
377 fn build_from_graph(graph: UDGraph<I>) -> Result<BTreeMap<I, Self>, IndexError> {
378 let barrier = hurdles::Barrier::new(graph.len());
379 let got_error = std::sync::Arc::new(AtomicBool::new(false));
380 let res = graph
381 .nodes()
382 .into_iter()
383 .map(|key| {
384 (
385 key.clone(),
386 Self {
387 barrier: barrier.clone(),
388 got_error: std::sync::Arc::clone(&got_error),
389 },
390 )
391 })
392 .collect();
393 Ok(res)
394 }
395}
396
397impl SyncSubDomains for BarrierSync {
398 fn sync(&mut self) -> Result<(), SimulationError> {
399 self.barrier.wait();
400 match self.got_error.load(std::sync::atomic::Ordering::Relaxed) {
401 true => Err(SimulationError::OtherThreadError(format!(
402 "Another thread returned an error. Winding down."
403 ))),
404 false => Ok(()),
405 }
406 }
407
408 fn store_error(
409 &mut self,
410 maybe_error: Result<(), SimulationError>,
411 ) -> Result<bool, SimulationError> {
412 match maybe_error {
413 Ok(_) => Ok(false),
414 Err(SimulationError::OtherThreadError(_)) => Ok(true),
415 Err(x) => {
416 self.got_error
417 .store(true, std::sync::atomic::Ordering::Relaxed);
418 self.barrier.wait();
419 Err(x)
420 }
421 }
422 }
423}
424
425/// Handles communications between different simulation processes.
426///
427/// Often times, information needs to be exchanged between threads.
428/// For example, positional and force information of cells living at the boundary.
429///
430/// The receiver is referenced by the index `I` and will obtain the message `T`.
431/// The trait was designed around the [crossbeam_channel] sender-receiver pair.
432/// However, it should allow for more generic setups where eg. information could be shared
433/// by different means such as sharing memory.
434///
435/// Between the [Communicator::send] and [Communicator::receive] method, a synchronization step
436/// needs to happen. Otherwise, dataraces can occur and invalidate given results.
437/// See the [Sync] trait for more details on syncing between threads.
438pub trait Communicator<I, T>
439where
440 Self: Sized,
441{
442 /// Sends information to a particular receiver.
443 fn send(&mut self, receiver: &I, message: T) -> Result<(), SimulationError>;
444 /// Receives the information previously sent
445 ///
446 /// When implementing this trait, make sure to empty any existing queue or shared memory.
447 /// Otherwise received messages will be stacking up, using up more memory+
448 /// and yielding wrong results.
449 fn receive(&mut self) -> Vec<T>;
450}
451
452/// Sender-Receiver [Communicator] based on [crossbeam_channel].
453///
454/// This struct contains one receiver and multiple senders.
455/// It can be constructed by using the [FromMap] trait.
456/// ```
457/// # use cellular_raza_core::backend::chili::{ChannelComm, Communicator, FromMap};
458/// let map = std::collections::BTreeMap::from([
459/// (0, std::collections::BTreeSet::from([1])),
460/// (1, std::collections::BTreeSet::from([0, 2])),
461/// (2, std::collections::BTreeSet::from([1])),
462/// ]);
463///
464/// // Construct multiple communicators from a given map.
465/// let mut channel_comms = ChannelComm::from_map(&map).unwrap();
466///
467/// // Send a message from 0 to 1
468/// channel_comms.get_mut(&0).unwrap().send(&1, true);
469/// channel_comms.get_mut(&0).unwrap().send(&1, false);
470/// // Receive all elements at communicator 1
471/// let elements = channel_comms.get_mut(&1).unwrap().receive();
472///
473/// assert_eq!(elements, vec![true, false]);
474/// ```
475#[derive(Clone)]
476pub struct ChannelComm<I, T> {
477 senders: std::collections::BTreeMap<I, crossbeam_channel::Sender<T>>,
478 receiver: crossbeam_channel::Receiver<T>,
479}
480
481impl<T, I> FromMap<I> for ChannelComm<I, T>
482where
483 I: Ord,
484{
485 fn from_map(map: &BTreeMap<I, BTreeSet<I>>) -> Result<BTreeMap<I, Self>, IndexError>
486 where
487 I: Clone + core::hash::Hash + Eq,
488 {
489 let channels: BTreeMap<_, _> = map
490 .keys()
491 .into_iter()
492 .map(|sender_key| {
493 let (s, r) = crossbeam_channel::unbounded::<T>();
494 (sender_key, (s, r))
495 })
496 .collect();
497 let mut comms = BTreeMap::new();
498 for key in map.keys().into_iter() {
499 let senders = map
500 .get(&key)
501 .ok_or(IndexError("Network of communicators could not be constructed due to incorrect entries in map".into()))?
502 .clone()
503 .into_iter()
504 .map(|connected_key| (connected_key.clone(), channels[&connected_key].0.clone())).collect();
505
506 let comm = ChannelComm {
507 senders,
508 receiver: channels[&key].1.clone(),
509 };
510 comms.insert(key.clone(), comm);
511 }
512 Ok(comms)
513 }
514}
515
516#[cfg(test)]
517mod test_channel_comm {
518 use itertools::Itertools;
519
520 use super::*;
521
522 #[test]
523 fn test_from_map() -> Result<(), IndexError> {
524 let map = BTreeMap::from([
525 (0_usize, BTreeSet::from([3, 1])),
526 (1_usize, BTreeSet::from([0, 2])),
527 (2_usize, BTreeSet::from([1, 3])),
528 (3_usize, BTreeSet::from([2, 0])),
529 ]);
530 assert!(validate_map(&map));
531 let channel_comms = ChannelComm::<usize, ()>::from_map(&map)?;
532 assert_eq!(channel_comms.len(), 4);
533 for i in 0..4 {
534 assert!(channel_comms.keys().contains(&i));
535 }
536 for i in 0..4 {
537 let higher = (i + 1) % 4;
538 let lower = (i + 5) % 4;
539 assert!(channel_comms[&i].senders.contains_key(&lower));
540 assert!(channel_comms[&i].senders.contains_key(&higher));
541 }
542 Ok(())
543 }
544
545 #[test]
546 fn test_from_map_2() -> Result<(), Box<dyn std::error::Error>> {
547 let map = BTreeMap::from([
548 (0, BTreeSet::from([1, 2, 3])),
549 (1, BTreeSet::from([0, 2, 3])),
550 (2, BTreeSet::from([0, 1, 3])),
551 (3, BTreeSet::from([0, 1, 2])),
552 ]);
553 assert!(validate_map(&map));
554 let channel_comms = ChannelComm::<usize, Option<()>>::from_map(&map)?;
555 for i in 0..4 {
556 assert!(channel_comms.keys().contains(&i));
557 }
558 for i in 0..4 {
559 for j in 0..4 {
560 let contains = channel_comms[&i].senders.keys().contains(&j);
561 assert_eq!(contains, i != j);
562 }
563 }
564 Ok(())
565 }
566
567 fn from_map_n_line(n: usize) -> Result<(), Box<dyn std::error::Error>> {
568 if n <= 1 {
569 return Ok(());
570 }
571 let mut map =
572 BTreeMap::from([(0, BTreeSet::from([1, n])), (n, BTreeSet::from([n - 1, 0]))]);
573 for i in 1..n {
574 map.insert(i, BTreeSet::from([i - 1, i + 1]));
575 }
576 assert!(validate_map(&map));
577 let mut channel_comms = ChannelComm::<usize, usize>::from_map(&map)?;
578 channel_comms
579 .iter_mut()
580 .map(|(key, comm)| {
581 let recv_key = (key + 1) % (n + 1);
582 comm.send(&recv_key, *key)
583 })
584 .collect::<Result<Vec<_>, _>>()?;
585 channel_comms.iter_mut().for_each(|(key, comm)| {
586 let results = comm.receive();
587 assert!(results.len() > 0);
588 if key > &0 {
589 assert_eq!(results, vec![key - 1]);
590 } else {
591 assert_eq!(results, vec![n]);
592 }
593 });
594 Ok(())
595 }
596
597 #[test]
598 fn from_map_lines() {
599 for i in 2..100 {
600 from_map_n_line(i).unwrap();
601 }
602 }
603
604 #[test]
605 fn test_send() -> Result<(), Box<dyn std::error::Error>> {
606 let map = BTreeMap::from([
607 (1_usize, BTreeSet::from([2])),
608 (2_usize, BTreeSet::from([1])),
609 ]);
610 let mut channel_comms = ChannelComm::<usize, bool>::from_map(&map)?;
611 channel_comms.get_mut(&1).unwrap().send(&2, true)?;
612 channel_comms.get_mut(&2).unwrap().send(&1, false)?;
613 Ok(())
614 }
615
616 #[test]
617 fn test_empty_receive() -> Result<(), Box<dyn std::error::Error>> {
618 let map = BTreeMap::from([
619 (1_usize, BTreeSet::from([2])),
620 (2_usize, BTreeSet::from([1])),
621 ]);
622 let mut channel_comms = ChannelComm::<usize, f64>::from_map(&map)?;
623 for (_, comm) in channel_comms.iter_mut() {
624 let received_elements = comm.receive().into_iter().collect::<Vec<_>>();
625 assert_eq!(received_elements.len(), 0);
626 }
627 Ok(())
628 }
629
630 #[test]
631 fn test_send_receive() -> Result<(), Box<dyn std::error::Error>> {
632 let map = BTreeMap::from([
633 (0, BTreeSet::from([1, 2])),
634 (1, BTreeSet::from([0, 2])),
635 (2, BTreeSet::from([0, 1])),
636 ]);
637 let mut channel_comms = ChannelComm::from_map(&map)?;
638
639 // Send a dummy value
640 for (index, comm) in channel_comms.iter_mut() {
641 let next_index = (index + 1) % map.len();
642 comm.send(&next_index, next_index as f64)?;
643 }
644
645 // Receive the value
646 for (index, comm) in channel_comms.iter_mut() {
647 let received_elements = comm.receive().into_iter().collect::<Vec<_>>();
648 assert_eq!(received_elements, vec![*index as f64]);
649 }
650 Ok(())
651 }
652
653 #[test]
654 fn test_send_plain_voxel() -> Result<(), Box<dyn std::error::Error>> {
655 use crate::backend::chili::SubDomainPlainIndex;
656 let map = BTreeMap::from([
657 (
658 SubDomainPlainIndex(0),
659 BTreeSet::from([SubDomainPlainIndex(1), SubDomainPlainIndex(2)]),
660 ),
661 (
662 SubDomainPlainIndex(1),
663 BTreeSet::from([SubDomainPlainIndex(0), SubDomainPlainIndex(2)]),
664 ),
665 (
666 SubDomainPlainIndex(2),
667 BTreeSet::from([SubDomainPlainIndex(0), SubDomainPlainIndex(1)]),
668 ),
669 ]);
670 let mut channel_comms = ChannelComm::from_map(&map)?;
671
672 // Send a dummy value
673 for (index, comm) in channel_comms.iter_mut() {
674 let index = index.0;
675 let next_index = SubDomainPlainIndex((index + 1) % map.len());
676 comm.send(&next_index, next_index)?;
677 }
678
679 // Receive the value
680 for (index, comm) in channel_comms.iter_mut() {
681 let received_elements = comm.receive().into_iter().collect::<Vec<_>>();
682 assert_eq!(received_elements, vec![*index]);
683 }
684
685 Ok(())
686 }
687}
688
689impl<I, T> Communicator<I, T> for ChannelComm<I, T>
690where
691 I: core::hash::Hash + Eq + Ord,
692{
693 fn receive(&mut self) -> Vec<T> {
694 self.receiver.try_iter().collect()
695 }
696
697 fn send(&mut self, receiver: &I, message: T) -> Result<(), SimulationError> {
698 let sender = self
699 .senders
700 .get(&receiver)
701 .ok_or(super::IndexError(format!(
702 "could not find specified receiver"
703 )))?;
704 sender.send(message)?;
705 Ok(())
706 }
707}
708
709#[doc(hidden)]
710#[allow(unused)]
711mod test_derive_communicator {
712 /// ```
713 /// use cellular_raza_core::backend::chili::{SimulationError, ChannelComm, Communicator};
714 /// #[derive(Communicator)]
715 /// #[CommunicatorCorePath(cellular_raza_core)]
716 /// struct MyComm<I, T> {
717 /// #[Comm(I, T)]
718 /// comm: ChannelComm<I, T>
719 /// }
720 /// ```
721 fn default() {}
722
723 /// ```
724 /// use cellular_raza_core::backend::chili::{SimulationError, ChannelComm, Communicator};
725 /// #[derive(Communicator)]
726 /// #[CommunicatorCorePath(cellular_raza_core)]
727 /// struct MyDouble<I> {
728 /// #[Comm(I, String)]
729 /// comm1: ChannelComm<I, String>,
730 /// #[Comm(I, f64)]
731 /// comm2: ChannelComm<I, f64>,
732 /// }
733 /// ```
734 fn two_communicators_explicit() {}
735
736 /// ```
737 /// use cellular_raza_core::backend::chili::{SimulationError, ChannelComm, Communicator};
738 /// struct Message<T>(T);
739 /// #[derive(Communicator)]
740 /// #[CommunicatorCorePath(cellular_raza_core)]
741 /// struct MyDouble<I, T> {
742 /// #[Comm(I, Message<T>)]
743 /// comm1: ChannelComm<I, Message<T>>,
744 /// #[Comm(I, f64)]
745 /// comm2: ChannelComm<I, f64>,
746 /// }
747 /// ```
748 fn two_communicators_generic_one() {}
749}
750
751#[doc(hidden)]
752#[allow(unused)]
753mod test_derive_from_map {
754 /// ```
755 /// use cellular_raza_core::{
756 /// backend::chili::{ChannelComm, FromMap},
757 /// };
758 /// use cellular_raza_concepts::IndexError;
759 /// #[derive(FromMap)]
760 /// #[FromMapIndex(usize)]
761 /// struct MyNewComm {
762 /// channel_comm_1: ChannelComm<usize, String>,
763 /// channel_comm_2: ChannelComm<usize, (f64, f32)>,
764 /// }
765 /// ```
766 fn default() {}
767
768 /// ```
769 /// use cellular_raza_core::{
770 /// backend::chili::{ChannelComm, FromMap},
771 /// };
772 /// use cellular_raza_concepts::IndexError;
773 /// #[derive(FromMap)]
774 /// #[FromMapIndex(I)]
775 /// struct MyNewComm<I> {
776 /// channel_comm_1: ChannelComm<I, String>,
777 /// channel_comm_2: ChannelComm<I, (f64, f32)>,
778 /// }
779 /// ```
780 fn generic_index() {}
781
782 /// ```
783 /// use cellular_raza_core::{
784 /// backend::chili::{ChannelComm, FromMap},
785 /// };
786 /// use cellular_raza_concepts::IndexError;
787 /// #[derive(FromMap)]
788 /// #[FromMapIndex(i16)]
789 /// struct MyNewComm<T>
790 /// where
791 /// T: Clone,
792 /// {
793 /// channel_comm_1: ChannelComm<i16, T>,
794 /// channel_comm_2: ChannelComm<i16, (f64, f32)>,
795 /// }
796 /// ```
797 fn where_clause() {}
798
799 /// ```
800 /// use cellular_raza_core::{
801 /// backend::chili::{ChannelComm, FromMap},
802 /// };
803 /// use cellular_raza_concepts::IndexError;
804 /// #[derive(FromMap)]
805 /// #[FromMapIndex(I)]
806 /// struct MyNewComm<I>
807 /// where
808 /// I: std::fmt::Display,
809 /// {
810 /// channel_comm_1: ChannelComm<I, f64>,
811 /// channel_comm_2: ChannelComm<I, (f64, f32)>,
812 /// }
813 /// ```
814 fn where_clause_on_index() {}
815}
816
817#[doc(hidden)]
818#[allow(unused)]
819mod test_build_communicator {
820 macro_rules! test_build_communicator(
821 (
822 name:$func_name:ident,
823 aspects:[$($asp:ident),*]
824 ) => {
825 /// ```
826 /// use cellular_raza_core::backend::chili::build_communicator;
827 /// build_communicator!(
828 /// aspects: [
829 #[doc = stringify!($($asp),*)]
830 /// ],
831 /// core_path: cellular_raza_core,
832 /// communicator_name: __MyComm,
833 /// );
834 /// let mut map = std::collections::BTreeMap::new();
835 /// map.insert(0, std::collections::BTreeSet::from([1]));
836 /// map.insert(1, std::collections::BTreeSet::from([0]));
837 /// use cellular_raza_core::backend::chili::{ReactionsContactInformation, FromMap,
838 /// Communicator, PosInformation, ForceInformation, VoxelPlainIndex, SendCell};
839 /// let mut communicator = __MyComm::from_map(&map).unwrap().remove(&0).unwrap();
840 /// macro_rules! test_aspect (
841 /// (Mechanics) => {
842 /// communicator.send(&1, SendCell(
843 /// VoxelPlainIndex::new(1),
844 /// format!("MyCell"),
845 /// format!("AuxStorage")
846 /// ));
847 /// };
848 /// (Interaction) => {
849 /// communicator.send(&1, PosInformation {
850 /// pos: 1u8,
851 /// vel: 1.0,
852 /// info: (),
853 /// cell_index_in_vector: 1,
854 /// index_sender: VoxelPlainIndex::new(0),
855 /// index_receiver: VoxelPlainIndex::new(1),
856 /// });
857 /// communicator.send(&1, ForceInformation {
858 /// force: 0.1,
859 /// cell_index_in_vector: 0,
860 /// index_sender: VoxelPlainIndex::new(0),
861 /// });
862 /// };
863 /// (ReactionsContact) => {
864 /// communicator.send(&1, ReactionsContactInformation {
865 /// pos: 1u8,
866 /// intracellular: [0.0, 1.0],
867 /// info: "hi",
868 /// cell_index_in_vector: 0,
869 /// index_sender: VoxelPlainIndex::new(0),
870 /// index_receiver: VoxelPlainIndex::new(1),
871 /// });
872 /// };
873 /// );
874 #[doc = concat!($(
875 concat!("test_aspect!(", stringify!($asp), ");")
876 ,)*)]
877 /// ```
878 #[allow(non_snake_case)]
879 fn $func_name () {}
880 };
881 );
882
883 cellular_raza_core_proc_macro::run_test_for_aspects!(
884 test: test_build_communicator,
885 aspects: [Mechanics, Interaction, ReactionsContact]
886 );
887
888 /// ```compile_fail
889 /// build_communicator!(
890 /// aspects: [Mechanics, Cycle],
891 /// communicator_name: __MyComm,
892 /// );
893 /// ```
894 fn without_path() {}
895}
896
897#[cfg(test)]
898mod test_sync {
899 use super::*;
900 use std::sync::*;
901
902 fn test_single_map<S>(map: BTreeMap<usize, BTreeSet<usize>>)
903 where
904 S: 'static + SyncSubDomains + FromMap<usize> + Send + Sync,
905 {
906 // Define the number of threads and iterations to use
907 let n_iterations = 1_000;
908 let n_threads = map.len();
909
910 // We count the number of iterations via this mutex.
911 // Individual threads will increment their counter by +1 each time they are executed
912 let iteration_counter =
913 Arc::new(Mutex::new(Vec::from_iter((0..n_threads).map(|_| 0_usize))));
914
915 // Create a barrier from which we
916 let syncers = S::from_map(&map).unwrap();
917 let handles = syncers
918 .into_iter()
919 .map(|(n_thread, mut syncer)| {
920 let iteration_counter_thread = Arc::clone(&iteration_counter);
921 std::thread::spawn(move || {
922 for n_iteration in 0..n_iterations {
923 syncer.sync().unwrap();
924 iteration_counter_thread.lock().unwrap()[n_thread] += 1;
925 syncer.sync().unwrap();
926 let current_value = iteration_counter_thread.lock().unwrap().clone();
927 assert_eq!(current_value, vec![n_iteration + 1; n_threads]);
928 }
929 })
930 })
931 .collect::<Vec<_>>();
932
933 for handle in handles.into_iter() {
934 handle.join().unwrap();
935 }
936 }
937
938 fn test_multiple_maps<S>()
939 where
940 S: 'static + SyncSubDomains + FromMap<usize> + Send + Sync,
941 {
942 let map0 = BTreeMap::from_iter([(0, BTreeSet::from([1])), (1, BTreeSet::from([0]))]);
943 test_single_map::<S>(map0);
944
945 let map1 = BTreeMap::from_iter([
946 (0, BTreeSet::from([1, 2])),
947 (1, BTreeSet::from([0, 2])),
948 (2, BTreeSet::from([0, 1])),
949 ]);
950 test_single_map::<S>(map1);
951
952 let map2 = BTreeMap::from_iter([
953 (0, BTreeSet::from([1, 2, 3])),
954 (1, BTreeSet::from([0, 2, 3])),
955 (2, BTreeSet::from([0, 1, 3])),
956 (3, BTreeSet::from([0, 1, 2])),
957 ]);
958 test_single_map::<S>(map2);
959
960 let map3 = BTreeMap::from_iter([
961 (0, BTreeSet::from([1, 2])),
962 (1, BTreeSet::from([0, 3])),
963 (2, BTreeSet::from([0, 3])),
964 (3, BTreeSet::from([1, 2])),
965 ]);
966 test_single_map::<S>(map3);
967
968 let map4 = BTreeMap::from_iter([
969 (0, BTreeSet::from([1])),
970 (1, BTreeSet::from([2])),
971 (2, BTreeSet::from([3])),
972 (3, BTreeSet::from([4])),
973 (4, BTreeSet::from([0])),
974 ]);
975 test_single_map::<BarrierSync>(map4);
976 }
977
978 #[test]
979 fn barrier_sync() {
980 test_multiple_maps::<BarrierSync>();
981 }
982}