Skip to main content

dtrees_rs/algorithms/optimal/dl85/
builder.rs

1use crate::algorithms::common::errors::ErrorWrapper;
2use crate::algorithms::common::heuristics::Heuristic;
3use crate::algorithms::common::types::{
4    BranchingPolicy, LowerBoundPolicy, NodeDataType, OptimalDepth2Policy,
5};
6use crate::algorithms::optimal::depth2::OptimalDepth2Tree;
7use crate::algorithms::optimal::dl85::config::DL85Config;
8use crate::algorithms::optimal::dl85::DL85;
9use crate::algorithms::optimal::rules::common::{
10    LowerBoundRule, MaxDepthRule, MinSupportRule, PureNodeRule, TimeLimitRule, UsableNodeRule,
11};
12use crate::algorithms::optimal::rules::{Rule, RuleManager};
13use crate::caching::Caching;
14
15/// Builder for [`DL85`].
16///
17/// A cache, a depth-2 solver, an error function and a heuristic are required.
18/// The pure-node, lower-bound and already-solved rules are always included;
19/// depth, support and time limits add their own rules.
20///
21/// ```
22/// use dtrees_rs::algorithms::common::errors::NativeError;
23/// use dtrees_rs::algorithms::common::heuristics::NoHeuristic;
24/// use dtrees_rs::algorithms::optimal::depth2::ErrorMinimizer;
25/// use dtrees_rs::algorithms::optimal::dl85::DL85Builder;
26/// use dtrees_rs::caching::Trie;
27///
28/// let error_fn = Box::<NativeError>::default();
29/// let dl85 = DL85Builder::default()
30///     .max_depth(3)
31///     .min_support(5)
32///     .max_time(60.0)
33///     .cache(Box::<Trie>::default())
34///     .heuristic(Box::<NoHeuristic>::default())
35///     .depth2_search(Box::new(ErrorMinimizer::new(error_fn.clone())))
36///     .error_function(error_fn)
37///     .build();
38/// assert!(dl85.is_ok());
39/// ```
40pub struct DL85Builder<C, D, E, H>
41where
42    C: Caching + ?Sized,
43    D: OptimalDepth2Tree + ?Sized,
44    E: ErrorWrapper + ?Sized,
45    H: Heuristic + ?Sized,
46{
47    config: DL85Config,
48    cache: Option<Box<C>>,
49    depth2_search: Option<Box<D>>,
50    error_fn: Option<Box<E>>,
51    heuristic_fn: Option<Box<H>>,
52    nodes_rules: RuleManager,
53    search_rules: RuleManager,
54    time_rule: TimeLimitRule,
55}
56
57impl<C, D, E, H> Default for DL85Builder<C, D, E, H>
58where
59    C: Caching + ?Sized,
60    D: OptimalDepth2Tree + ?Sized,
61    E: ErrorWrapper + ?Sized,
62    H: Heuristic + ?Sized,
63{
64    fn default() -> Self {
65        let builder = Self {
66            config: DL85Config::default(),
67            cache: None,
68            depth2_search: None,
69            error_fn: None,
70            heuristic_fn: None,
71            nodes_rules: RuleManager::new(),
72            search_rules: RuleManager::new(),
73            time_rule: TimeLimitRule::default(),
74        };
75        builder.default_rules()
76    }
77}
78
79impl<C, D, E, H> DL85Builder<C, D, E, H>
80where
81    C: Caching + ?Sized,
82    D: OptimalDepth2Tree + ?Sized,
83    E: ErrorWrapper + ?Sized,
84    H: Heuristic + ?Sized,
85{
86    /// A builder with the default rules and no limits.
87    pub fn new() -> Self {
88        Self::default()
89    }
90
91    /// Minimum number of instances in each leaf.
92    pub fn min_support(mut self, value: usize) -> Self {
93        self.config.base.min_support = value;
94        self.nodes_rules
95            .add_rule(Box::new(MinSupportRule::new(value)));
96        self
97    }
98
99    /// Maximum depth of the tree.
100    pub fn max_depth(mut self, value: usize) -> Self {
101        self.config.base.max_depth = value;
102        self.nodes_rules
103            .add_rule(Box::new(MaxDepthRule::new(value)));
104        self
105    }
106
107    /// Only trees with a lower error are accepted.
108    pub fn max_error(mut self, value: f64) -> Self {
109        self.config.base.max_error = value;
110        self
111    }
112
113    /// Time limit in seconds for the whole search.
114    pub fn max_time(mut self, value: f64) -> Self {
115        self.config.base.max_time = value;
116        self.time_rule = TimeLimitRule::new(value);
117        self
118    }
119
120    /// Adds the rules every search needs: already-solved nodes, pure nodes
121    /// and the lower bound. Called by [`Self::new`].
122    pub fn default_rules(mut self) -> Self {
123        self.nodes_rules.add_rule(Box::new(UsableNodeRule::new()));
124        self.nodes_rules.add_rule(Box::new(PureNodeRule::new()));
125        self.nodes_rules.add_rule(Box::new(LowerBoundRule::new()));
126        self
127    }
128
129    /// The solver used for depth-2 subtrees.
130    pub fn depth2_search(mut self, search: Box<D>) -> Self {
131        self.depth2_search = Some(search);
132        self
133    }
134
135    /// Adds a rule evaluated on entering each node.
136    pub fn add_node_rule(mut self, rule: Box<dyn Rule>) -> Self {
137        self.nodes_rules.add_rule(rule);
138        self
139    }
140
141    /// Adds several node rules.
142    pub fn add_node_rules(mut self, rules: Vec<Box<dyn Rule>>) -> Self {
143        for rule in rules {
144            self.nodes_rules.add_rule(rule)
145        }
146        self
147    }
148
149    /// Adds a rule evaluated before branching on each candidate feature,
150    /// such as [`DiscrepancyRule`](crate::algorithms::optimal::rules::DiscrepancyRule)
151    /// or [`TopkRule`](crate::algorithms::optimal::rules::TopkRule).
152    pub fn add_search_rule(mut self, rule: Box<dyn Rule>) -> Self {
153        self.search_rules.add_rule(rule);
154        self
155    }
156
157    /// Adds several search rules.
158    pub fn add_search_rules(mut self, rules: Vec<Box<dyn Rule>>) -> Self {
159        for rule in rules {
160            self.search_rules.add_rule(rule)
161        }
162        self
163    }
164
165    /// Sort the features by the heuristic at every node, not only at the root.
166    pub fn always_sort(mut self, value: bool) -> Self {
167        self.config.always_sort = value;
168        self
169    }
170
171    /// Whether to use the depth-2 solver.
172    pub fn specialization(mut self, value: OptimalDepth2Policy) -> Self {
173        self.config.optimal_depth2policy = value;
174        self
175    }
176
177    /// Whether to use the similarity lower bound.
178    pub fn lower_bound_strategy(mut self, value: LowerBoundPolicy) -> Self {
179        self.config.lower_bound_policy = value;
180        self
181    }
182
183    /// Which branch of a feature is searched first.
184    pub fn branching_strategy(mut self, value: BranchingPolicy) -> Self {
185        self.config.branching_policy = value;
186        self
187    }
188
189    /// What the error function receives: class counts or instance ids.
190    pub fn node_exposed_data(mut self, value: NodeDataType) -> Self {
191        self.config.data_type = value;
192        self
193    }
194
195    /// The cache of subproblems.
196    pub fn cache(mut self, value: Box<C>) -> Self {
197        self.cache = Some(value);
198        self
199    }
200
201    /// The error of a leaf.
202    pub fn error_function(mut self, value: Box<E>) -> Self {
203        self.error_fn = Some(value);
204        self
205    }
206
207    /// The heuristic used to order features.
208    pub fn heuristic(mut self, value: Box<H>) -> Self {
209        self.heuristic_fn = Some(value);
210        self
211    }
212
213    /// Builds the search, or says which required part is missing.
214    pub fn build(self) -> Result<DL85<C, D, E, H>, String> {
215        let cache = self.cache.ok_or("Cache is required")?;
216        let depth2 = self.depth2_search.ok_or("Depth-2 search is required")?;
217        let error_function = self.error_fn.ok_or("Error function is required")?;
218        let heuristic = self.heuristic_fn.ok_or("Heuristic is required")?;
219
220        Ok(DL85::new(
221            self.config,
222            cache,
223            depth2,
224            error_function,
225            heuristic,
226            self.nodes_rules,
227            self.search_rules,
228            self.time_rule,
229        ))
230    }
231}