belief_spread/
belief.rs

1use std::{
2    cell::RefCell,
3    collections::HashMap,
4    fmt::Debug,
5    hash::{Hash, Hasher},
6    rc::Rc,
7};
8
9use crate::{errors::OutOfRangeError, BehaviourPtr, Named, UUIDd};
10use anyhow::Result;
11use by_address::ByAddress;
12use uuid::Uuid;
13
14/// A [Rc] [RefCell] pointer to a [Belief] that is compared by address;
15pub type BeliefPtr = ByAddress<Rc<RefCell<dyn Belief>>>;
16
17impl From<BasicBelief> for BeliefPtr {
18    /// Convert from a [BasicBelief] into a [BeliefPtr].
19    ///
20    /// This consumes the [BasicBelief].
21    ///
22    /// # Arguments
23    /// - `b`: The [BasicBelief] to convert.
24    ///
25    /// # Returns
26    /// The [BeliefPtr].
27    ///
28    /// # Examples
29    /// ```
30    /// use belief_spread::{BasicBelief, BeliefPtr};
31    ///
32    /// let b = BasicBelief::new("Belief 1".to_string());
33    /// let b_ptr = BeliefPtr::from(b);
34    /// ```
35    ///
36    /// ```
37    /// use belief_spread::{BasicBelief, BeliefPtr};
38    ///
39    /// let b = BasicBelief::new("Belief 1".to_string());
40    /// let b_ptr: BeliefPtr = b.into();
41    /// ```
42    fn from(b: BasicBelief) -> Self {
43        ByAddress(Rc::new(RefCell::new(b)))
44    }
45}
46
47/// A Belief.
48pub trait Belief: Named + UUIDd {
49    /// Gets the perception, returning a [f64] if found.
50    ///
51    /// The perception is the amount an agent performing the behaviour can be
52    /// assumed to be driven by this belief.
53    ///
54    /// This is a value between -1 and +1.
55    ///
56    /// # Arguments
57    /// - `behaviour`: The behaviour.
58    ///
59    /// # Returns
60    /// The value, if found.
61    fn get_perception(&self, behaviour: &BehaviourPtr) -> Option<f64>;
62
63    /// Sets the perception.
64    ///
65    /// The perception is the amount an agent performing the behaviour can be
66    /// assumed to be driven by this belief.
67    ///
68    /// Deletes a behaviour if no perception is supplied.
69    ///
70    /// This is a value between -1 and +1.
71    ///
72    /// # Arguments
73    /// - `behaviour`: The [BehaviourPtr].
74    /// - `perception`: The new perception.
75    ///
76    /// # Returns
77    /// A [Result], [Ok] if nothing is wrong, or an [Err] with a
78    /// [OutOfRangeError], if the perception is not in range [-1, +1].
79    fn set_perception(
80        &mut self,
81        behaviour: BehaviourPtr,
82        perception: Option<f64>,
83    ) -> Result<(), OutOfRangeError>;
84
85    /// Gets the relationship, returning a [f64] if found.
86    ///
87    /// The relationship is the amount another belief can be deemed to be
88    /// compatible with holding this belief, given that you already hold this
89    /// belief.
90    ///
91    /// This is a value between -1 and +1.
92    ///
93    /// # Arguments
94    /// - `belief`: The other [Belief].
95    ///
96    /// # Returns
97    /// The value, if found.
98    fn get_relationship(&self, belief: &BeliefPtr) -> Option<f64>;
99
100    /// Sets the relationship.
101    ///
102    /// The relationship is the amount another belief can be deemed to be
103    /// compatible with holding this belief, given that you already hold this
104    /// belief.
105    ///
106    /// Deletes a belief is no relationship is supplied.
107    ///
108    /// This is a value between -1 and +1.
109    ///
110    /// # Arguments
111    /// - `belief`: The other [Belief].
112    /// - `relationship`: The new relationship
113    ///
114    /// # Returns
115    /// A [Result], [Ok] if nothing is wrong, or an [Err] with a
116    /// [OutOfRangeError], if the relationship is not in range [-1, +1].
117    fn set_relationship(
118        &mut self,
119        belief: BeliefPtr,
120        relationship: Option<f64>,
121    ) -> Result<(), OutOfRangeError>;
122}
123
124/// An implementation of [Belief].
125#[derive(Clone)]
126pub struct BasicBelief {
127    name: String,
128    uuid: Uuid,
129    perception: HashMap<BehaviourPtr, f64>,
130    relationship: HashMap<BeliefPtr, f64>,
131}
132
133impl Debug for BasicBelief {
134    #[cfg(not(tarpaulin_include))]
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        f.debug_struct("BasicBelief")
137            .field("name", &self.name)
138            .field("uuid", &self.uuid)
139            .finish()
140    }
141}
142
143impl BasicBelief {
144    /// Create a new [BasicBelief] with random [Uuid].
145    ///
146    /// # Arguments
147    /// - `name`: The name of the [BasicBelief].
148    ///
149    /// # Returns
150    /// The new [BasicBelief] with a [Uuid] generated using
151    /// [`uuid::Uuid::new_v4`].
152    ///
153    /// # Examples
154    ///
155    /// ```
156    /// use belief_spread::BasicBelief;
157    ///
158    /// let b = BasicBelief::new("Belief 1".to_string());
159    /// ```
160    pub fn new(name: String) -> Self {
161        Self::new_with_uuid(name, Uuid::new_v4())
162    }
163
164    /// Create a new [BasicBelief] with specified [Uuid].
165    ///
166    /// # Arguments
167    /// - `name`: The name of the [BasicBelief].
168    /// - `uuid`: The [Uuid] of the [BasicBelief].
169    ///
170    /// # Returns
171    /// The new [BasicBelief].
172    ///
173    /// # Examples
174    ///
175    /// ```
176    /// use belief_spread::BasicBelief;
177    /// use uuid::Uuid;
178    ///
179    /// let b = BasicBelief::new_with_uuid("Belief 1".to_string(), Uuid::new_v4());
180    /// ```
181    pub fn new_with_uuid(name: String, uuid: Uuid) -> Self {
182        Self {
183            name,
184            uuid,
185            perception: HashMap::new(),
186            relationship: HashMap::new(),
187        }
188    }
189}
190
191impl Belief for BasicBelief {
192    /// Gets the perception, returning a [f64] if found.
193    ///
194    /// The perception is the amount an agent performing the behaviour can be
195    /// assumed to be driven by this belief.
196    ///
197    /// This is a value between -1 and +1.
198    ///
199    /// # Arguments
200    /// - `behaviour`: The behaviour.
201    ///
202    /// # Returns
203    /// The value, if found.
204    ///
205    /// # Examples
206    /// ```
207    /// use belief_spread::{BasicBelief, BasicBehaviour, Belief, BehaviourPtr};
208    ///
209    /// let mut b = BasicBelief::new("Belief 1".to_string());
210    /// let behaviour = BasicBehaviour::new("Behaviour 1".to_string());
211    /// let beh_ptr: BehaviourPtr = behaviour.into();
212    /// b.set_perception(beh_ptr.clone(), Some(0.1));
213    /// assert_eq!(b.get_perception(&beh_ptr).unwrap(), 0.1);
214    /// ```
215    fn get_perception(&self, behaviour: &BehaviourPtr) -> Option<f64> {
216        self.perception.get(behaviour).cloned()
217    }
218
219    /// Sets the perception.
220    ///
221    /// The perception is the amount an agent performing the behaviour can be
222    /// assumed to be driven by this belief.
223    ///
224    /// Deletes a behaviour if no perception is supplied.
225    ///
226    /// This is a value between -1 and +1.
227    ///
228    /// # Arguments
229    /// - `behaviour`: The [BehaviourPtr].
230    /// - `perception`: The new perception.
231    ///
232    /// # Returns
233    /// A [Result], [Ok] if nothing is wrong, or an [Err] with a
234    /// [OutOfRangeError], if the perception is not in range [-1, +1].
235    ///
236    /// # Examples
237    /// ```
238    /// use belief_spread::{BasicBelief, BasicBehaviour, Belief, BehaviourPtr};
239    ///
240    /// let mut b = BasicBelief::new("Belief 1".to_string());
241    /// let behaviour = BasicBehaviour::new("Behaviour 1".to_string());
242    /// let beh_ptr: BehaviourPtr = behaviour.into();
243    /// b.set_perception(beh_ptr.clone(), Some(0.1));
244    /// assert_eq!(b.get_perception(&beh_ptr).unwrap(), 0.1);
245    /// ```
246    fn set_perception(
247        &mut self,
248        behaviour: BehaviourPtr,
249        perception: Option<f64>,
250    ) -> Result<(), OutOfRangeError> {
251        match perception {
252            None => {
253                self.perception.remove(&behaviour);
254                Ok(())
255            }
256            Some(x) if x > 1.0 => Err(OutOfRangeError::TooHigh {
257                found: x,
258                min: -1.0,
259                max: 1.0,
260            }),
261            Some(x) if x < -1.0 => Err(OutOfRangeError::TooLow {
262                found: x,
263                min: -1.0,
264                max: 1.0,
265            }),
266            Some(x) => {
267                self.perception.insert(behaviour, x);
268                Ok(())
269            }
270        }
271    }
272
273    /// Gets the relationship, returning a [f64] if found.
274    ///
275    /// The relationship is the amount another belief can be deemed to be
276    /// compatible with holding this belief, given that you already hold this
277    /// belief.
278    ///
279    /// This is a value between -1 and +1.
280    ///
281    /// # Arguments
282    /// - `belief`: The other [Belief].
283    ///
284    /// # Returns
285    /// The value, if found.
286    ///
287    /// # Examples
288    ///
289    /// ```
290    /// use belief_spread::{BasicBelief, BasicBehaviour, Belief, UUIDd, Named, BeliefPtr};
291    ///
292    /// let mut b = BasicBelief::new("Belief 1".to_string());
293    /// let b2 = BasicBelief::new("Belief 2". to_string());
294    /// let b2_ptr: BeliefPtr = b2.into();
295    /// b.set_relationship(b2_ptr.clone(), Some(0.1));
296    /// assert_eq!(b.get_relationship(&b2_ptr).unwrap(), 0.1);
297    /// ```
298    fn get_relationship(&self, belief: &BeliefPtr) -> Option<f64> {
299        self.relationship.get(belief).cloned()
300    }
301
302    /// Sets the relationship.
303    ///
304    /// The relationship is the amount another belief can be deemed to be
305    /// compatible with holding this belief, given that you already hold this
306    /// belief.
307    ///
308    /// Deletes a belief is no relationship is supplied.
309    ///
310    /// This is a value between -1 and +1.
311    ///
312    /// # Arguments
313    /// - `belief`: The other [Belief].
314    /// - `relationship`: The new relationship
315    ///
316    /// # Returns
317    /// A [Result], [Ok] if nothing is wrong, or an [Err] with a
318    /// [OutOfRangeError], if the relationship is not in range [-1, +1].
319    ///
320    /// # Examples
321    ///
322    /// ```
323    /// use belief_spread::{BasicBelief, BasicBehaviour, Belief, UUIDd, Named, BeliefPtr};
324    ///
325    /// let mut b = BasicBelief::new("Belief 1".to_string());
326    /// let b2 = BasicBelief::new("Belief 2". to_string());
327    /// let b2_ptr: BeliefPtr = b2.into();
328    /// b.set_relationship(b2_ptr.clone(), Some(0.1));
329    /// assert_eq!(b.get_relationship(&b2_ptr).unwrap(), 0.1);
330    /// ```
331    fn set_relationship(
332        &mut self,
333        belief: BeliefPtr,
334        relationship: Option<f64>,
335    ) -> Result<(), OutOfRangeError> {
336        match relationship {
337            None => {
338                self.relationship.remove(&belief);
339                Ok(())
340            }
341            Some(x) if x > 1.0 => Err(OutOfRangeError::TooHigh {
342                found: x,
343                min: -1.0,
344                max: 1.0,
345            }),
346            Some(x) if x < -1.0 => Err(OutOfRangeError::TooLow {
347                found: x,
348                min: -1.0,
349                max: 1.0,
350            }),
351            Some(x) => {
352                self.relationship.insert(belief, x);
353                Ok(())
354            }
355        }
356    }
357}
358
359impl Named for BasicBelief {
360    /// Get the name of the [BasicBelief].
361    fn name(&self) -> &str {
362        return &self.name;
363    }
364
365    /// Set the name of the [BasicBelief].
366    fn set_name(&mut self, name: String) {
367        self.name = name;
368    }
369}
370
371impl UUIDd for BasicBelief {
372    /// Get the UUID of the [BasicBelief].
373    fn uuid(&self) -> &Uuid {
374        return &self.uuid;
375    }
376
377    /// Set the UUID of the [BasicBelief].
378    fn set_uuid(&mut self, u: Uuid) {
379        self.uuid = u;
380    }
381}
382
383impl PartialEq for BasicBelief {
384    /// Compare if two BasicBeliefs are equal, solely by their UUID.
385    fn eq(&self, other: &Self) -> bool {
386        self.uuid == other.uuid
387    }
388}
389
390impl Hash for BasicBelief {
391    fn hash<H: Hasher>(&self, state: &mut H) {
392        self.uuid.hash(state);
393    }
394}
395
396impl Eq for BasicBelief {}
397
398#[cfg(test)]
399mod tests {
400    use crate::BasicBehaviour;
401    use std::collections::hash_map::DefaultHasher;
402
403    use super::*;
404
405    #[test]
406    fn new_with_uuid_assigns_uuid() {
407        let uuid = Uuid::new_v4();
408        let b = BasicBelief::new_with_uuid("b".to_string(), uuid.clone());
409        assert_eq!(b.uuid(), &uuid);
410    }
411
412    #[test]
413    fn new_with_uuid_assigns_name() {
414        let b = BasicBelief::new_with_uuid("b".to_string(), Uuid::new_v4());
415        assert_eq!(b.name(), "b");
416    }
417
418    #[test]
419    fn new_assigns_random_uuid() {
420        let b1 = BasicBelief::new("b1".to_string());
421        let b2 = BasicBelief::new("b2".to_string());
422        assert_ne!(b1.uuid(), b2.uuid());
423    }
424
425    #[test]
426    fn new_assigns_name() {
427        let b = BasicBelief::new("b".to_string());
428        assert_eq!(b.name(), "b");
429    }
430
431    #[test]
432    fn name_returns_name() {
433        let b = BasicBelief::new("b".to_string());
434        assert_eq!(b.name(), "b");
435    }
436
437    #[test]
438    fn set_name_sets_name() {
439        let mut b = BasicBelief::new("b".to_string());
440        assert_eq!(b.name(), "b");
441        b.set_name("bnew".to_string());
442        assert_eq!(b.name(), "bnew");
443    }
444
445    #[test]
446    fn uuid_returns_uuid() {
447        let uuid = Uuid::new_v4();
448        let mut b = BasicBelief::new_with_uuid("b".to_string(), uuid.clone());
449        assert_eq!(b.uuid(), &uuid);
450        let uuid2 = Uuid::new_v4();
451        b.set_uuid(uuid2.clone());
452        assert_eq!(b.uuid(), &uuid2);
453    }
454
455    #[test]
456    fn set_when_valid_and_get_relationship_when_exists() {
457        let b = BasicBelief::new("belief".to_string());
458        let b_ptr: BeliefPtr = b.into();
459        assert!(!b_ptr
460            .borrow_mut()
461            .set_relationship(b_ptr.clone(), Some(0.2))
462            .is_err());
463        assert_eq!(b_ptr.borrow().get_relationship(&b_ptr).unwrap(), 0.2);
464    }
465
466    #[test]
467    fn get_relationship_when_not_exists() {
468        let b = BasicBelief::new("belief".to_string());
469        let b_ptr: BeliefPtr = b.into();
470        assert_eq!(b_ptr.borrow().get_relationship(&b_ptr.clone()), None);
471    }
472
473    #[test]
474    fn set_delete_when_valid_and_get_relationship_when_not_exists() {
475        let b = BasicBelief::new("belief".to_string());
476        let b_ptr: BeliefPtr = b.into();
477        assert!(!b_ptr
478            .borrow_mut()
479            .set_relationship(b_ptr.clone(), Some(0.2))
480            .is_err());
481        assert_eq!(b_ptr.borrow().get_relationship(&b_ptr).unwrap(), 0.2);
482        assert!(!b_ptr
483            .borrow_mut()
484            .set_relationship(b_ptr.clone(), None)
485            .is_err());
486        assert_eq!(b_ptr.borrow().get_relationship(&b_ptr), None);
487    }
488
489    #[test]
490    fn set_relationship_when_too_low() {
491        let b = BasicBelief::new("belief".to_string());
492        let b_ptr: BeliefPtr = b.into();
493        let res = b_ptr
494            .borrow_mut()
495            .set_relationship(b_ptr.clone(), Some(-1.1));
496        let expected_error = OutOfRangeError::TooLow {
497            found: -1.1,
498            min: -1.0,
499            max: 1.0,
500        };
501        assert_eq!(res.unwrap_err(), expected_error);
502    }
503
504    #[test]
505    fn set_relationship_when_too_high() {
506        let b = BasicBelief::new("belief".to_string());
507        let b_ptr: BeliefPtr = b.into();
508        let res = b_ptr
509            .borrow_mut()
510            .set_relationship(b_ptr.clone(), Some(1.1));
511        let expected_error = OutOfRangeError::TooHigh {
512            found: 1.1,
513            min: -1.0,
514            max: 1.0,
515        };
516        assert_eq!(res.unwrap_err(), expected_error);
517    }
518
519    #[test]
520    fn set_when_valid_and_get_perception_when_exists() {
521        let mut b = BasicBelief::new("belief".to_string());
522        let beh = BasicBehaviour::new("behaviour".to_string());
523        let beh_ptr: BehaviourPtr = beh.into();
524        assert!(!b.set_perception(beh_ptr.clone(), Some(0.1)).is_err());
525        assert_eq!(b.get_perception(&beh_ptr).unwrap(), 0.1);
526    }
527
528    #[test]
529    fn get_perception_when_not_exists() {
530        let b = BasicBelief::new("belief".to_string());
531        let beh = BasicBehaviour::new("behaviour".to_string());
532        let beh_ptr: BehaviourPtr = beh.into();
533        assert_eq!(b.get_perception(&beh_ptr), None);
534    }
535
536    #[test]
537    fn set_when_valid_delete_and_get_perception_when_not_exists() {
538        let mut b = BasicBelief::new("belief".to_string());
539        let beh = BasicBehaviour::new("behaviour".to_string());
540        let beh_ptr: BehaviourPtr = beh.into();
541        assert!(!b.set_perception(beh_ptr.clone(), Some(0.1)).is_err());
542        assert_eq!(b.get_perception(&beh_ptr).unwrap(), 0.1);
543        assert!(!b.set_perception(beh_ptr.clone(), None).is_err());
544        assert_eq!(b.get_perception(&beh_ptr), None);
545    }
546
547    #[test]
548    fn set_perception_when_too_low() {
549        let mut b = BasicBelief::new("belief".to_string());
550        let beh = BasicBehaviour::new("behaviour".to_string());
551        let beh_ptr: BehaviourPtr = beh.into();
552        let res = b.set_perception(beh_ptr.clone(), Some(-1.1));
553        let expected_error = OutOfRangeError::TooLow {
554            found: -1.1,
555            min: -1.0,
556            max: 1.0,
557        };
558        assert_eq!(res.unwrap_err(), expected_error);
559    }
560
561    #[test]
562    fn set_perception_when_too_high() {
563        let mut b = BasicBelief::new("belief".to_string());
564        let beh = BasicBehaviour::new("behaviour".to_string());
565        let beh_ptr: BehaviourPtr = beh.into();
566        let res = b.set_perception(beh_ptr.clone(), Some(1.1));
567        let expected_error = OutOfRangeError::TooHigh {
568            found: 1.1,
569            min: -1.0,
570            max: 1.0,
571        };
572
573        assert_eq!(res.unwrap_err(), expected_error);
574    }
575
576    #[test]
577    fn test_equals_when_uuids_match_but_name_doesnt() {
578        let uuid1 = Uuid::new_v4();
579        let b1 = BasicBelief::new_with_uuid("b1".to_string(), uuid1);
580        let b2 = BasicBelief::new_with_uuid("b2".to_string(), uuid1);
581        assert_eq!(b1, b2);
582    }
583
584    #[test]
585    fn test_equals_when_uuids_dont_match() {
586        let uuid1 = Uuid::new_v4();
587        let uuid2 = Uuid::new_v4();
588        let b1 = BasicBelief::new_with_uuid("b1".to_string(), uuid1);
589        let b2 = BasicBelief::new_with_uuid("b2".to_string(), uuid2);
590        assert_ne!(b1, b2);
591    }
592
593    #[test]
594    fn test_hash_when_uuids_match_but_name_doesnt() {
595        let uuid1 = Uuid::new_v4();
596        let b1 = BasicBelief::new_with_uuid("b1".to_string(), uuid1);
597        let b2 = BasicBelief::new_with_uuid("b2".to_string(), uuid1);
598        let mut hasher1 = DefaultHasher::new();
599        let mut hasher2 = DefaultHasher::new();
600        b1.hash(&mut hasher1);
601        b2.hash(&mut hasher2);
602        assert_eq!(hasher1.finish(), hasher2.finish());
603    }
604
605    #[test]
606    fn test_hash_when_uuids_dont_match() {
607        let uuid1 = Uuid::new_v4();
608        let uuid2 = Uuid::new_v4();
609        let b1 = BasicBelief::new_with_uuid("b1".to_string(), uuid1);
610        let b2 = BasicBelief::new_with_uuid("b2".to_string(), uuid2);
611        let mut hasher1 = DefaultHasher::new();
612        let mut hasher2 = DefaultHasher::new();
613        b1.hash(&mut hasher1);
614        b2.hash(&mut hasher2);
615        assert_ne!(hasher1.finish(), hasher2.finish());
616    }
617
618    #[test]
619    fn test_from() {
620        let b = BasicBelief::new("b1".to_string());
621        let b_ptr: BeliefPtr = BeliefPtr::from(b);
622        assert_eq!(b_ptr.borrow().name(), "b1");
623    }
624
625    #[test]
626    fn test_into() {
627        let b = BasicBelief::new("b1".to_string());
628        let b_ptr: BeliefPtr = b.into();
629        assert_eq!(b_ptr.borrow().name(), "b1");
630    }
631}