1mod budget_schedule;
2mod loss;
3mod outcome;
4
5pub use budget_schedule::{Budget, BudgetSchedule, PassReport, ScheduleBounds, ScheduleKind};
6pub use loss::{Leaf, LeafLoss, Loss, LossInput, Skipping};
7pub use outcome::{FitOutcome, SearchError, SearchStatus};
8
9use serde::{Deserialize, Serialize};
10use std::fmt;
11use std::str::FromStr;
12
13#[derive(Default, Copy, Debug, Clone, PartialOrd, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(rename_all = "lowercase")]
17pub enum PointSelector {
18 #[default]
20 Mid,
21 First,
25 Random,
27}
28
29impl PointSelector {
30 pub const fn as_str(self) -> &'static str {
32 match self {
33 Self::Mid => "mid",
34 Self::First => "first",
35 Self::Random => "random",
36 }
37 }
38
39 pub const ALL: [Self; 3] = [Self::Mid, Self::First, Self::Random];
41}
42
43impl fmt::Display for PointSelector {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 f.write_str(self.as_str())
46 }
47}
48
49impl FromStr for PointSelector {
50 type Err = String;
51
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
53 match s.trim().to_ascii_lowercase().as_str() {
54 "mid" => Ok(Self::Mid),
55 "first" => Ok(Self::First),
56 "random" => Ok(Self::Random),
57 other => Err(format!(
58 "unknown split selection strategy `{other}` (expected one of: mid, first, random)"
59 )),
60 }
61 }
62}
63
64pub type Cost = f64;
67
68#[derive(Copy, Clone, Debug)]
70pub struct SearchConfig {
71 pub max_depth: usize,
73 pub min_sup: usize,
75 pub max_time: f64,
77 pub max_gap: Cost,
79 pub max_error: Cost,
81 pub cost_step: Cost,
86 pub skipping: Skipping,
88 pub is_root: bool,
90 pub use_heuristic: bool,
92 pub fast_d2: bool,
94 pub point_selector: PointSelector,
96 pub nb_runs: usize,
98 pub discrepancy: usize,
100 pub budget: usize,
102}
103
104impl SearchConfig {
105 #[allow(clippy::too_many_arguments)]
107 pub fn new(
108 min_sup: usize,
109 max_depth: usize,
110 max_time: f64,
111 max_gap: Cost,
112 max_error: Cost,
113 use_heuristic: bool,
114 fast_d2: bool,
115 split_strategy: PointSelector,
116 ) -> Self {
117 Self {
118 max_depth,
119 min_sup,
120 max_time,
121 max_gap,
122 max_error,
123 cost_step: 1.0,
124 skipping: Skipping::Auto,
125 is_root: true,
126 use_heuristic,
127 fast_d2,
128 point_selector: split_strategy,
129 nb_runs: 0,
130 discrepancy: 0,
131 budget: 0,
132 }
133 }
134
135 pub fn cost_step_for(loss: &Loss) -> Cost {
139 if loss.is_misclassification() {
140 1.0
141 } else {
142 0.0
143 }
144 }
145
146 pub fn derive_left(&self) -> Self {
149 let mut left_config = *self;
150 left_config.max_depth -= 1;
151 left_config.is_root = false;
152 left_config.max_gap = ((self.max_gap - (self.max_gap / 2.0).ceil()) / 2.0).floor();
153 left_config
154 }
155
156 pub fn derive_right(&self, left_gap: Cost) -> Self {
159 let mut right_config = *self;
160 right_config.max_depth -= 1;
161 right_config.is_root = false;
162 right_config.max_gap = (self.max_gap - (self.max_gap / 2.0).ceil() - left_gap).max(0.0);
163 right_config
164 }
165}
166
167#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
169pub struct Statistics {
170 pub cache_size: usize,
172 pub cache_hits: usize,
174 pub general_solver_call: usize,
176 pub specialized_solver_call: usize,
178 pub num_samples: usize,
180 pub num_features: usize,
182 pub error: Cost,
184 pub skipping_violations: usize,
188 pub duration: f64,
190}
191
192pub fn classification_error(classes_support: &[usize]) -> (Cost, usize) {
195 let mut max_idx = 0;
196 let mut max_value = 0;
197 let mut total = 0;
198 for (idx, &value) in classes_support.iter().enumerate() {
199 total += value;
200 if value >= max_value {
201 max_value = value;
202 max_idx = idx;
203 }
204 }
205 ((total - max_value) as Cost, max_idx)
206}