Skip to main content

abd_clam/core/cluster/
criteria.rs

1//! Criteria used for partitioning `Cluster`s.
2
3use distances::Number;
4
5use crate::{Cluster, UniBall};
6
7/// A criterion used to decide when to partition a `Cluster`.
8pub trait PartitionCriterion<U: Number>: Send + Sync {
9    /// Check whether a `Cluster` meets the criterion for partitioning.
10    fn check(&self, c: &UniBall<U>) -> bool;
11}
12
13/// The maximum depth of a `Cluster` beyond which it may not be partitioned.
14#[derive(Debug, Clone)]
15pub struct MaxDepth(usize);
16
17impl<U: Number> PartitionCriterion<U> for MaxDepth {
18    fn check(&self, c: &UniBall<U>) -> bool {
19        c.depth() < self.0
20    }
21}
22
23/// The minimum cardinality of a `Cluster` below which it may not be partitioned.
24#[derive(Debug, Clone)]
25pub struct MinCardinality(usize);
26
27impl<U: Number> PartitionCriterion<U> for MinCardinality {
28    fn check(&self, c: &UniBall<U>) -> bool {
29        c.cardinality() > self.0
30    }
31}
32
33/// A collection of criteria used to decide when to partition a `Cluster`.
34#[allow(clippy::module_name_repetitions)]
35pub struct PartitionCriteria<U: Number> {
36    /// The criteria used to decide when to partition a `Cluster`.
37    criteria: Vec<Box<dyn PartitionCriterion<U>>>,
38    /// Whether all criteria must be met for a `Cluster` to be partitioned or if any one criterion
39    /// is sufficient.
40    check_all: bool,
41}
42
43impl<U: Number> PartitionCriterion<U> for PartitionCriteria<U> {
44    fn check(&self, cluster: &UniBall<U>) -> bool {
45        !cluster.is_singleton()
46            && if self.check_all {
47                self.criteria.iter().all(|c| c.check(cluster))
48            } else {
49                self.criteria.iter().any(|c| c.check(cluster))
50            }
51    }
52}
53
54impl<U: Number> Default for PartitionCriteria<U> {
55    fn default() -> Self {
56        Self::new(true).with_min_cardinality(1)
57    }
58}
59
60impl<U: Number> PartitionCriteria<U> {
61    /// Create a new `PartitionCriteria` instance.
62    ///
63    /// # Arguments
64    ///
65    /// * `check_all`: if `true`, all criteria must be met for a `Cluster` to be partitioned, if
66    /// `false`, any one criterion is sufficient.
67    #[must_use]
68    pub fn new(check_all: bool) -> Self {
69        Self {
70            criteria: Vec::new(),
71            check_all,
72        }
73    }
74
75    /// Add the `MaxDepth` criterion to the collection of criteria.
76    ///
77    /// # Arguments
78    ///
79    /// * `threshold`: the maximum depth of a `Cluster` beyond which it may not be partitioned.
80    #[must_use]
81    pub fn with_max_depth(mut self, threshold: usize) -> Self {
82        self.criteria.push(Box::new(MaxDepth(threshold)));
83        self
84    }
85
86    /// Add the `MinCardinality` criterion to the collection of criteria.
87    ///
88    /// # Arguments
89    ///
90    /// * `threshold`: the minimum cardinality of a `Cluster` below which it may not be partitioned.
91    #[must_use]
92    pub fn with_min_cardinality(mut self, threshold: usize) -> Self {
93        self.criteria.push(Box::new(MinCardinality(threshold)));
94        self
95    }
96
97    /// Add a custom criterion to the collection of criteria.
98    ///
99    /// # Arguments
100    ///
101    /// * `c`: the custom criterion to add.
102    #[allow(dead_code)]
103    pub(crate) fn with_custom(mut self, c: Box<dyn PartitionCriterion<U>>) -> Self {
104        self.criteria.push(c);
105        self
106    }
107}