contree/algorithms/
mod.rs1mod continuous_tree;
2mod contree_lds;
3mod depth2;
4mod interval_pruner;
5mod shared;
6
7use crate::common::{
8 FitOutcome, PointSelector, ScheduleKind, SearchConfig, SearchError, SearchStatus, Statistics,
9};
10use crate::data::view::DataView;
11use crate::data::Dataset;
12use crate::tree::Tree;
13pub use continuous_tree::ConTree;
14pub use contree_lds::ConTreeLds;
15
16pub(crate) fn support_feasible_splits(
25 possible_splits: &[usize],
26 view_len: usize,
27 min_sup: usize,
28) -> std::ops::Range<usize> {
29 let min_sup = min_sup.max(1);
30 if view_len < 2 * min_sup {
31 return 0..0;
32 }
33 let first = possible_splits.partition_point(|&p| p < min_sup);
34 let last = possible_splits.partition_point(|&p| view_len - p.min(view_len) >= min_sup);
35 first..last.max(first)
36}
37
38pub fn validate(config: &SearchConfig, dataset: &Dataset) -> Result<(), SearchError> {
40 if dataset.count() == 0 {
41 return Err(SearchError::EmptyDataset);
42 }
43 if dataset.num_features() == 0 {
44 return Err(SearchError::NoFeatures);
45 }
46 if !dataset.is_prepared() {
47 return Err(SearchError::UnpreparedDataset);
48 }
49 if config.min_sup == 0 {
50 return Err(SearchError::InvalidParameter {
51 name: "min_sup",
52 reason: "must be at least 1".to_string(),
53 });
54 }
55 if 2 * config.min_sup > dataset.count() {
56 return Err(SearchError::InvalidParameter {
57 name: "min_sup",
58 reason: format!(
59 "{} leaves no room for a split with n_samples={}",
60 config.min_sup,
61 dataset.count()
62 ),
63 });
64 }
65 if config.max_time.is_nan() || config.max_time <= 0.0 {
66 return Err(SearchError::InvalidParameter {
67 name: "max_time",
68 reason: "must be a positive number of seconds".to_string(),
69 });
70 }
71 Ok(())
72}
73
74pub enum GenericConTree {
76 Normal(ConTree),
78 LDS(ConTreeLds),
80}
81
82impl GenericConTree {
83 #[allow(clippy::too_many_arguments)]
86 pub fn new(
87 min_sup: usize,
88 max_depth: usize,
89 max_time: f64,
90 max_error: usize,
91 split_selection_strategy: PointSelector,
92 max_gap: usize,
93 use_heuristic: bool,
94 fast_d2: bool,
95 use_lds: bool,
96 ) -> Self {
97 match use_lds {
98 true => Self::LDS(ConTreeLds::new(
99 min_sup,
100 max_depth,
101 max_time,
102 max_error,
103 split_selection_strategy,
104 max_gap,
105 use_heuristic,
106 fast_d2,
107 )),
108 false => Self::Normal(ConTree::new(
109 min_sup,
110 max_depth,
111 max_time,
112 max_error,
113 split_selection_strategy,
114 max_gap,
115 use_heuristic,
116 fast_d2,
117 )),
118 }
119 }
120
121 pub fn with_budget_schedule(self, schedule: ScheduleKind) -> Self {
124 match self {
125 GenericConTree::LDS(solver) => GenericConTree::LDS(solver.with_schedule(schedule)),
126 other => other,
127 }
128 }
129
130 pub fn fit(&mut self, dataset: &Dataset) -> Result<FitOutcome, SearchError> {
132 match self {
133 GenericConTree::Normal(solver) => solver.fit(dataset),
134 GenericConTree::LDS(solver) => solver.fit(dataset),
135 }
136 }
137
138 pub fn partial_fit(&mut self, view: &DataView<'_>) -> Result<bool, SearchError> {
143 match self {
144 GenericConTree::Normal(_) => Ok(true),
145 GenericConTree::LDS(solver) => Ok(solver.partial_fit(view)),
146 }
147 }
148
149 pub fn stats(&self) -> Statistics {
151 match self {
152 GenericConTree::Normal(solver) => solver.statistics(),
153 GenericConTree::LDS(solver) => *solver.statistics(),
154 }
155 }
156
157 pub fn status(&self) -> SearchStatus {
159 match self {
160 GenericConTree::Normal(solver) => solver.status(),
161 GenericConTree::LDS(solver) => solver.status(),
162 }
163 }
164
165 pub fn tree(&mut self) -> Tree {
167 match self {
168 GenericConTree::Normal(solver) => solver.tree.clone(),
169 GenericConTree::LDS(solver) => solver.get_solution_tree(),
170 }
171 }
172}