1pub const CATEGORICAL_MASK: i8 = 1;
10pub const DEFAULT_LEFT_MASK: i8 = 2;
12const ZERO_THRESHOLD: f64 = 1e-35_f32 as f64;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum MissingType {
18 None = 0,
19 Zero = 1,
20 NaN = 2,
21}
22
23impl MissingType {
24 pub fn from_decision_type(decision_type: i8) -> MissingType {
25 match (decision_type >> 2) & 3 {
26 1 => MissingType::Zero,
27 2 => MissingType::NaN,
28 _ => MissingType::None,
29 }
30 }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq)]
35pub struct SplitChildren {
36 pub left_value: f64,
37 pub right_value: f64,
38 pub left_count: i64,
39 pub right_count: i64,
40 pub left_weight: f64,
42 pub right_weight: f64,
43 pub gain: f32,
44}
45
46#[derive(Debug, Clone, PartialEq)]
47pub struct Tree {
48 pub num_leaves: usize,
49 pub num_cat: usize,
50 pub split_feature: Vec<i32>,
52 pub split_gain: Vec<f32>,
53 pub threshold: Vec<f64>,
55 pub decision_type: Vec<i8>,
56 pub left_child: Vec<i32>,
57 pub right_child: Vec<i32>,
58 pub leaf_value: Vec<f64>,
59 pub leaf_weight: Vec<f64>,
60 pub leaf_count: Vec<i64>,
61 pub internal_value: Vec<f64>,
62 pub internal_weight: Vec<f64>,
63 pub internal_count: Vec<i64>,
64 pub cat_boundaries: Vec<i32>,
66 pub cat_threshold: Vec<u32>,
68 pub shrinkage: f64,
69 pub leaf_parent: Vec<i32>,
71 pub leaf_depth: Vec<i32>,
73}
74
75impl Default for Tree {
76 fn default() -> Self {
77 Tree::new()
78 }
79}
80
81impl Tree {
82 pub fn new() -> Tree {
84 Tree {
85 num_leaves: 1,
86 num_cat: 0,
87 split_feature: Vec::new(),
88 split_gain: Vec::new(),
89 threshold: Vec::new(),
90 decision_type: Vec::new(),
91 left_child: Vec::new(),
92 right_child: Vec::new(),
93 leaf_value: vec![0.0],
94 leaf_weight: vec![0.0],
95 leaf_count: vec![0],
96 internal_value: Vec::new(),
97 internal_weight: Vec::new(),
98 internal_count: Vec::new(),
99 cat_boundaries: vec![0],
100 cat_threshold: Vec::new(),
101 shrinkage: 1.0,
102 leaf_parent: vec![-1],
103 leaf_depth: vec![0],
104 }
105 }
106
107 pub fn split_numerical(
109 &mut self,
110 leaf: usize,
111 feature: usize,
112 threshold: f64,
113 default_left: bool,
114 missing: MissingType,
115 children: &SplitChildren,
116 ) -> usize {
117 let mut decision_type = (missing as i8) << 2;
118 if default_left {
119 decision_type |= DEFAULT_LEFT_MASK;
120 }
121 self.split(leaf, feature, threshold, decision_type, children)
122 }
123
124 pub fn split_categorical(
126 &mut self,
127 leaf: usize,
128 feature: usize,
129 categories: &[i32],
130 missing: MissingType,
131 children: &SplitChildren,
132 ) -> usize {
133 let bitset = to_bitset(categories);
134 let cat_index = self.num_cat as f64;
135 let last = *self.cat_boundaries.last().expect("cat_boundaries starts with 0");
136 self.cat_boundaries.push(last + bitset.len() as i32);
137 self.cat_threshold.extend_from_slice(&bitset);
138 self.num_cat += 1;
139 let decision_type = CATEGORICAL_MASK | ((missing as i8) << 2);
140 self.split(leaf, feature, cat_index, decision_type, children)
141 }
142
143 fn split(&mut self, leaf: usize, feature: usize, threshold: f64, decision_type: i8, c: &SplitChildren) -> usize {
144 let node = self.num_leaves - 1;
145 let new_leaf = self.num_leaves;
146 let parent = self.leaf_parent[leaf];
147 if parent >= 0 {
148 let parent = parent as usize;
149 if self.left_child[parent] == !(leaf as i32) {
150 self.left_child[parent] = node as i32;
151 } else {
152 self.right_child[parent] = node as i32;
153 }
154 }
155 self.split_feature.push(feature as i32);
156 self.split_gain.push(c.gain);
157 self.threshold.push(threshold);
158 self.decision_type.push(decision_type);
159 self.left_child.push(!(leaf as i32));
160 self.right_child.push(!(new_leaf as i32));
161 self.internal_weight.push(self.leaf_weight[leaf]);
162 self.internal_value.push(self.leaf_value[leaf]);
163 self.internal_count.push(c.left_count + c.right_count);
164 self.leaf_value[leaf] = nan_to_zero(c.left_value);
165 self.leaf_weight[leaf] = c.left_weight;
166 self.leaf_count[leaf] = c.left_count;
167 self.leaf_value.push(nan_to_zero(c.right_value));
168 self.leaf_weight.push(c.right_weight);
169 self.leaf_count.push(c.right_count);
170 self.leaf_parent[leaf] = node as i32;
171 self.leaf_parent.push(node as i32);
172 let depth = self.leaf_depth[leaf] + 1;
173 self.leaf_depth[leaf] = depth;
174 self.leaf_depth.push(depth);
175 self.num_leaves += 1;
176 new_leaf
177 }
178
179 pub fn shrink(&mut self, rate: f64) {
181 for value in self.leaf_value.iter_mut().chain(self.internal_value.iter_mut()) {
182 *value = round_to_zero(*value * rate);
183 }
184 self.shrinkage *= rate;
185 }
186
187 pub fn add_bias(&mut self, bias: f64) {
189 for value in self.leaf_value.iter_mut().chain(self.internal_value.iter_mut()) {
190 *value = round_to_zero(*value + bias);
191 }
192 self.shrinkage = 1.0;
193 }
194
195 pub fn as_constant(&mut self, value: f64) {
197 *self = Tree::new();
198 self.leaf_value[0] = value;
199 }
200
201 pub fn set_leaf_output(&mut self, leaf: usize, value: f64) {
203 self.leaf_value[leaf] = round_to_zero(value);
204 }
205
206 pub fn max_depth(&self) -> i32 {
208 self.leaf_depth.iter().copied().max().unwrap_or(0)
209 }
210}
211
212fn nan_to_zero(value: f64) -> f64 {
213 if value.is_nan() {
214 0.0
215 } else {
216 value
217 }
218}
219
220fn round_to_zero(value: f64) -> f64 {
222 if value.abs() > ZERO_THRESHOLD {
223 value
224 } else {
225 0.0
226 }
227}
228
229pub fn to_bitset(values: &[i32]) -> Vec<u32> {
231 let max = values.iter().copied().filter(|&v| v >= 0).max().unwrap_or(0) as usize;
232 let mut words = vec![0u32; max / 32 + 1];
233 for &value in values.iter().filter(|&&v| v >= 0) {
234 words[value as usize / 32] |= 1 << (value as usize % 32);
235 }
236 words
237}
238
239pub fn in_bitset(words: &[u32], value: i32) -> bool {
241 if value < 0 {
242 return false;
243 }
244 let word = value as usize / 32;
245 word < words.len() && (words[word] >> (value as usize % 32)) & 1 == 1
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 fn children(gain: f32) -> SplitChildren {
253 SplitChildren {
254 left_value: -1.0,
255 right_value: 1.0,
256 left_count: 10,
257 right_count: 20,
258 left_weight: 10.0,
259 right_weight: 20.0,
260 gain,
261 }
262 }
263
264 #[test]
265 fn splits_follow_lightgbm_node_numbering() {
266 let mut tree = Tree::new();
267 let right = tree.split_numerical(0, 3, 0.5, true, MissingType::None, &children(5.0));
268 assert_eq!(right, 1);
269 let right2 = tree.split_numerical(1, 1, 2.0, false, MissingType::NaN, &children(2.0));
270 assert_eq!(right2, 2);
271 tree.split_numerical(0, 0, -1.0, true, MissingType::None, &children(1.0));
272 assert_eq!(tree.left_child, vec![2, -2, -1]);
274 assert_eq!(tree.right_child, vec![1, -3, -4]);
275 assert_eq!(tree.decision_type, vec![2, 8, 2]);
276 assert_eq!(tree.internal_weight[0], 0.0);
277 assert_eq!(tree.leaf_depth, vec![2, 2, 2, 2]);
278 }
279
280 #[test]
281 fn categorical_split_records_bitset() {
282 let mut tree = Tree::new();
283 tree.split_categorical(0, 2, &[1, 3, 33], MissingType::None, &children(1.0));
284 assert_eq!(tree.cat_boundaries, vec![0, 2]);
285 assert_eq!(tree.cat_threshold, vec![0b1010, 0b10]);
286 assert_eq!(tree.threshold, vec![0.0]);
287 assert_eq!(tree.decision_type, vec![1]);
288 assert!(in_bitset(&tree.cat_threshold, 33));
289 assert!(!in_bitset(&tree.cat_threshold, 2));
290 assert!(!in_bitset(&tree.cat_threshold, -1));
291 }
292
293 #[test]
294 fn bias_resets_shrinkage() {
295 let mut tree = Tree::new();
296 tree.split_numerical(0, 0, 0.0, true, MissingType::None, &children(1.0));
297 tree.shrink(0.1);
298 tree.add_bias(2.0);
299 assert_eq!(tree.shrinkage, 1.0);
300 assert_eq!(tree.leaf_value, vec![-0.1 + 2.0, 0.1 + 2.0]);
301 assert_eq!(tree.internal_value, vec![2.0]);
302 }
303}