1use crate::algorithms::common::errors::ErrorWrapper;
2use crate::algorithms::common::heuristics::Heuristic;
3use crate::algorithms::common::types::{
4 BranchingChoice, BranchingPolicy, FitError, LowerBoundPolicy, NodeDataType, RuleType,
5 SearchResult, SearchStatistics,
6};
7use crate::algorithms::optimal::depth2::OptimalDepth2Tree;
8use crate::algorithms::optimal::dl85::config::DL85Config;
9use crate::algorithms::optimal::rules::common::{SimilarityLowerBoundRule, TimeLimitRule};
10use crate::algorithms::optimal::rules::{
11 DiscrepancyRule, GainRule, Rule, RuleContext, RuleManager,
12};
13use crate::algorithms::optimal::Reason;
14use crate::algorithms::TreeSearchAlgorithm;
15use crate::caching::{CacheEntry, CacheKey, Caching, Index, SearchPath};
16use crate::cover::similarities::SimilarityCover;
17use crate::cover::Cover;
18use crate::globals::{attribute, float_is_null, item};
19use crate::tree::{NodeInfos, Tree, TreeNode};
20
21mod builder;
22pub mod config;
23
24pub use builder::DL85Builder;
25
26pub struct DL85<C, D, E, H>
42where
43 C: Caching + ?Sized,
44 D: OptimalDepth2Tree + ?Sized,
45 E: ErrorWrapper + ?Sized,
46 H: Heuristic + ?Sized,
47{
48 config: DL85Config,
49 cache: Box<C>,
50 error_fn: Box<E>,
51 depth2_search: Box<D>,
52 heuristic_fn: Box<H>,
53 search_rules: RuleManager,
54 node_rules: RuleManager,
55 time_rule: TimeLimitRule,
56 similarity_rule: SimilarityLowerBoundRule,
57 statistics: SearchStatistics,
58 tree: Tree,
59 root_candidates: Vec<usize>,
60 gain_gap: f64,
61}
62
63impl<C, D, E, H> TreeSearchAlgorithm for DL85<C, D, E, H>
64where
65 C: Caching + ?Sized,
66 D: OptimalDepth2Tree + ?Sized,
67 E: ErrorWrapper + ?Sized,
68 H: Heuristic + ?Sized,
69{
70 fn fit(&mut self, cover: &mut Cover) -> Result<(), FitError> {
72 let mut result = SearchResult {
73 reason: Reason::RuleReason,
74 ..Default::default()
75 };
76
77 while result.reason == Reason::RuleReason && !self.time_rule.exhausted() {
78 result = self.partial_fit(cover);
79 }
80 Ok(())
81 }
82
83 fn tree(&self) -> &Tree {
84 &self.tree
85 }
86}
87
88impl<C, D, E, H> DL85<C, D, E, H>
89where
90 C: Caching + ?Sized,
91 D: OptimalDepth2Tree + ?Sized,
92 E: ErrorWrapper + ?Sized,
93 H: Heuristic + ?Sized,
94{
95 #[allow(clippy::too_many_arguments)]
97 pub fn new(
98 config: DL85Config,
99 cache: Box<C>,
100 depth2_search: Box<D>,
101 error_fn: Box<E>,
102 heuristic_fn: Box<H>,
103 node_rules: RuleManager,
104 search_rules: RuleManager,
105 time_rule: TimeLimitRule,
106 ) -> Self {
107 Self {
108 config,
109 cache,
110 error_fn,
111 depth2_search,
112 heuristic_fn,
113 search_rules,
114 node_rules,
115 time_rule,
116 similarity_rule: SimilarityLowerBoundRule::new(),
117 statistics: SearchStatistics::default(),
118 tree: Tree::default(),
119 root_candidates: vec![],
120 gain_gap: 0.0,
121 }
122 }
123
124 pub fn config(&self) -> DL85Config {
126 self.config
127 }
128
129 pub fn partial_fit(&mut self, cover: &mut Cover) -> SearchResult {
136 self.statistics.increment_restarts();
137
138 let mut root_context = RuleContext::default();
139
140 if self.statistics.restarts() <= 1 {
141 self.cache.init();
142 self.statistics.num_attributes = cover.num_attributes;
143 self.statistics.num_samples = cover.count();
144
145 if let Some(discrepancy_rule) = self.search_rules.get_rule_mut::<DiscrepancyRule>() {
146 discrepancy_rule.update_to_true_limit(
147 self.statistics.num_attributes,
148 self.config.base.max_depth,
149 );
150 }
151
152 let (error, label) = self.compute_leaf_error(cover);
153 self.cache.update_root().map(|updater| {
154 updater
155 .leaf_error(error)
156 .output(label)
157 .size(self.statistics.num_samples)
158 });
159
160 let mut candidates =
161 self.get_candidates(cover, self.config.base.min_support, None, None);
162 self.heuristic_fn.compute(cover, &mut candidates);
163 self.root_candidates = candidates;
164
165 let bound = <f64>::min(error, self.config.base.max_error);
166
167 let branch_item = usize::MAX;
168 root_context.item(branch_item);
169 root_context.position(0);
170 root_context.discrepancy(0);
171 root_context.upper_bound(bound);
172 root_context.error(bound);
173
174 self.time_rule.activate();
175 self.node_rules.activate_all();
176 self.search_rules.activate_all();
177 if self.config.use_similarity_lb() {
178 self.similarity_rule.activate();
179 }
180 } else {
181 root_context.upper_bound(self.statistics.tree_error);
182 root_context.error(self.statistics.tree_error);
183 }
184 let root_index = self.cache.root_index();
185 let node_ub = self
186 .cache
187 .root()
188 .map_or(f64::INFINITY, |node| node.upper_bound());
189
190 root_context.node_upper_bound(node_ub);
191 root_context.support(self.statistics.num_samples);
192
193 let mut similarity = SimilarityCover::default();
194 let mut search_path = SearchPath::new();
195 let candidates = std::mem::take(&mut self.root_candidates);
196
197 let mut result = self.recursive_search(
198 cover,
199 &mut search_path,
200 &candidates,
201 0,
202 usize::MAX,
203 root_index,
204 &mut similarity,
205 &mut root_context,
206 );
207
208 self.root_candidates = candidates;
209
210 if self.statistics.restarts() <= 1 || self.gain_gap <= 0.0 {
211 if let Some(gain_rule) = self.search_rules.get_rule_mut::<GainRule>() {
212 gain_rule.update_gap_delta(self.gain_gap);
213 }
214 }
215
216 if result.reason == Reason::RuleReason {
217 self.node_rules.relax_all();
218 self.search_rules.relax_all();
219 }
220
221 if !self.node_rules.is_active() && !self.search_rules.is_active() {
222 result.reason = Reason::Done;
223 }
224
225 self.statistics.duration = self.time_rule.elapsed_seconds();
226 self.statistics.tree_error = result.error;
227 self.statistics.cache_size = self.cache.size();
228 self.build_solution_tree();
229 result
230 }
231
232 #[allow(clippy::too_many_arguments)]
235 fn recursive_search(
236 &mut self,
237 cover: &mut Cover,
238 path: &mut SearchPath,
239 candidates: &[usize],
240 depth: usize,
241 parent_item: usize,
242 parent_index: Index,
243 similarity: &mut SimilarityCover,
244 parent_context: &mut RuleContext,
245 ) -> SearchResult {
246 self.statistics.increment_search_space();
247
248 let mut subtree_upper_bound = parent_context.upper_bound;
249 let parent_key = parent_index.to_cache_key(path);
250 let result = self.evaluate(parent_context, &parent_key, RuleType::Node);
251
252 if !result.0 {
253 return SearchResult {
254 error: result.2,
255 has_intersected: false,
256 reason: result.1,
257 };
258 }
259
260 if !parent_index.is_new() {
261 cover.branch_on(parent_item);
262 }
263
264 if self.config.use_similarity_lb() {
265 let similarity = similarity.compute_similarity(cover.sparse());
266 parent_context.node_lower_bound = similarity.max(parent_context.node_lower_bound);
267 let result = self.evaluate_node(parent_context, &parent_key, RuleType::Similarity);
268 if !result.0 {
269 return SearchResult {
270 error: result.2,
271 has_intersected: true,
272 reason: result.1,
273 };
274 }
275 }
276
277 let mut node_candidates = self.get_candidates(
278 cover,
279 self.config.base.min_support,
280 Some(candidates),
281 Some(attribute(parent_item)),
282 );
283
284 if node_candidates.is_empty() {
285 let error = self
286 .cache
287 .update_node(&parent_key)
288 .map_or(f64::INFINITY, |updater| updater.leaf().get_error());
289 return SearchResult {
290 error,
291 has_intersected: true,
292 reason: Reason::NoCandidates,
293 };
294 }
295
296 if self.config.use_depth2_optimization() && self.config.base.max_depth - depth <= 2 {
297 let result = self.apply_specialized_depth2_search(
298 cover,
299 &node_candidates,
300 parent_index,
301 subtree_upper_bound,
302 path,
303 self.config.base.max_depth - depth,
304 );
305
306 match result {
307 Err(_) => {}
308 Ok(search_result) => return search_result,
309 }
310 }
311
312 let mut scores = vec![];
313 if self.config.always_sort {
314 scores = self.heuristic_fn.compute(cover, &mut node_candidates);
315 }
316
317 let mut subtree_similarity_data = SimilarityCover::default();
318 let mut min_lower_bound = <f64>::INFINITY;
319
320 let mut rule_pruned = false;
321 for (position, &child) in node_candidates.iter().enumerate() {
322 let mut branch_context = RuleContext::default();
323 branch_context.discrepancy(parent_context.discrepancy + position);
324 branch_context.position(position);
325
326 if scores.len() > 1 {
327 branch_context.gain(parent_context.gain + (scores[0] - scores[position]));
328 if self.gain_gap <= 0.0
329 || (self.statistics.restarts() <= 1 && branch_context.gain < self.gain_gap)
330 {
331 self.gain_gap = branch_context.gain;
332 }
333 }
334
335 let search_result = self.evaluate_node(&branch_context, &parent_key, RuleType::Search);
336
337 if !search_result.0 {
338 return SearchResult {
339 error: search_result.2,
340 has_intersected: true,
341 reason: search_result.1,
342 };
343 }
344
345 let (first_branch, first_lb, second_lb) =
346 self.determine_branch_strategy(child, path, cover, &subtree_similarity_data);
347 branch_context.depth(depth + 1);
348 let branch_item = item(child, first_branch);
349 branch_context.item(branch_item);
350 branch_context.node_lower_bound(first_lb);
351 branch_context.upper_bound(subtree_upper_bound);
352
353 let (first_result, branch_key) = self.process_branch(
354 cover,
355 path,
356 &mut branch_context,
357 &mut subtree_similarity_data,
358 &node_candidates,
359 depth,
360 );
361
362 if first_result.error >= subtree_upper_bound - second_lb {
365 min_lower_bound = self
366 .cache
367 .node(&branch_key)
368 .map_or(min_lower_bound, |node| {
369 let stored_lb = match first_result.error.is_finite() {
370 true => first_result.error + second_lb,
371 false => node.lower_bound() + second_lb,
372 };
373 stored_lb.min(min_lower_bound)
374 });
375 self.statistics.increment_sibling_pruning();
376 continue;
377 }
378
379 let mut branch_context = RuleContext::default();
380 let right_ub = subtree_upper_bound - first_result.error;
381 let branch_item = item(child, 1 - first_branch);
382 branch_context.item(branch_item);
383 branch_context.depth(depth + 1);
384 branch_context.position(position);
385 branch_context.discrepancy(parent_context.discrepancy + position);
386 branch_context.node_lower_bound(second_lb);
387 branch_context.upper_bound(right_ub);
388
389 let (second_result, _) = self.process_branch(
390 cover,
391 path,
392 &mut branch_context,
393 &mut subtree_similarity_data,
394 &node_candidates,
395 depth,
396 );
397
398 rule_pruned |= first_result.reason == Reason::RuleReason
399 || second_result.reason == Reason::RuleReason;
400
401 let subtree_error = first_result.error + second_result.error;
402 if subtree_error < subtree_upper_bound {
403 subtree_upper_bound = subtree_error;
404 let optimal = self
405 .cache
406 .update_node(&parent_key)
407 .is_some_and(|mut updater| {
408 updater = updater.error(subtree_error).test(child);
409
410 if float_is_null(updater.get_lower_bound() - subtree_error) {
411 updater.upper_bound(parent_context.upper_bound).optimal();
412 return true;
413 }
414 false
415 });
416
417 if optimal {
418 return SearchResult {
419 error: subtree_error,
420 has_intersected: true,
421 reason: Reason::Done,
422 };
423 }
424 } else {
425 min_lower_bound = min_lower_bound.min(subtree_error);
426 }
427 }
428
429 let error = self
430 .cache
431 .update_node(&parent_key)
432 .map_or(f64::INFINITY, |mut updater| {
433 if rule_pruned {
434 updater = updater.upper_bound(f64::INFINITY);
435 } else {
436 updater = updater.optimal().upper_bound(parent_context.upper_bound);
437 }
438 let error = updater.get_error();
439 if error.is_infinite() {
440 let lb = updater
441 .get_lower_bound()
442 .max(min_lower_bound.max(parent_context.upper_bound));
443 updater.lower_bound(lb);
444 }
445 error
446 });
447
448 SearchResult {
449 error,
450 has_intersected: true,
451 reason: if rule_pruned {
452 Reason::RuleReason
453 } else {
454 Reason::Done
455 },
456 }
457 }
458
459 fn process_branch(
462 &mut self,
463 cover: &mut Cover,
464 path: &mut SearchPath,
465 branch_context: &mut RuleContext,
466 similarity_cover: &mut SimilarityCover,
467 candidates: &[usize],
468 current_depth: usize,
469 ) -> (SearchResult, CacheKey) {
470 path.push(branch_context.item);
471 let branch_key_vec = path.to_sorted_vec();
472 let branch_index = self.cache.insert(&branch_key_vec);
473 let branch_key = branch_index.to_cache_key(path);
474
475 if branch_index.is_new() {
476 let size = cover.branch_on(branch_context.item);
477 branch_context.support(size);
478 let error = self.compute_leaf_error(cover);
479 branch_context.leaf_error(error.0);
480 branch_context.node_upper_bound(f64::INFINITY);
481
482 self.cache.update_node(&branch_key).map(|updater| {
483 updater
484 .leaf_error(error.0)
485 .output(error.1)
486 .lower_bound(branch_context.node_lower_bound)
487 .size(size)
488 });
489 } else {
490 self.statistics.increment_cache_hits();
491 if let Some(node) = self.cache.node(&branch_key) {
492 branch_context.error(node.error());
493 branch_context.support(node.size());
494 branch_context.node_upper_bound(node.upper_bound());
495 branch_context.leaf_error(node.leaf_error())
496 }
497 }
498 let first_result = self.recursive_search(
499 cover,
500 path,
501 candidates,
502 current_depth + 1,
503 branch_context.item,
504 branch_index,
505 similarity_cover,
506 branch_context,
507 );
508
509 self.backtrack(
510 cover,
511 path,
512 branch_index,
513 &branch_context.item,
514 &first_result,
515 similarity_cover,
516 );
517
518 (first_result, branch_key)
519 }
520
521 fn evaluate(
524 &mut self,
525 context: &RuleContext,
526 key: &CacheKey,
527 rule_type: RuleType,
528 ) -> (bool, Reason, f64) {
529 let time_result = self.evaluate_node(context, key, RuleType::Time);
530 if !time_result.0 {
531 return time_result;
532 }
533 self.evaluate_node(context, key, rule_type)
534 }
535
536 fn evaluate_node(
538 &mut self,
539 context: &RuleContext,
540 key: &CacheKey,
541 rule_type: RuleType,
542 ) -> (bool, Reason, f64) {
543 let result = match rule_type {
544 RuleType::Node => self.node_rules.evaluate(context),
545 RuleType::Search => self.search_rules.evaluate(context),
546 RuleType::Time => self.time_rule.evaluate(context),
547 RuleType::Similarity => self.similarity_rule.evaluate(context),
548 };
549
550 let mut error = f64::INFINITY;
551 if let Some(mut updater) = self.cache.update_node(key) {
552 if let Some(bound) = result.modified_bound {
553 updater = updater.upper_bound(bound);
554 }
555
556 if result.optimal.unwrap_or(false) {
557 updater = updater.optimal();
558 }
559
560 if result.leaf.unwrap_or(false) {
561 updater = updater.leaf();
562 }
563
564 if rule_type == RuleType::Similarity {
565 updater = updater.lower_bound(context.node_lower_bound)
566 }
567 error = updater.get_error().min(updater.get_leaf_error());
568 }
569
570 (result.continue_search, result.reason, error)
571 }
572
573 fn compute_leaf_error(&self, cover: &mut Cover) -> (f64, f64) {
576 if self.config.data_type == NodeDataType::ClassesSupport {
577 return self.error_fn.compute(&cover.labels_count());
578 }
579 self.error_fn.compute(&cover.to_vec())
580 }
581
582 pub fn statistics(&self) -> &SearchStatistics {
584 &self.statistics
585 }
586
587 pub fn elapsed_seconds(&self) -> f64 {
589 self.time_rule.elapsed_seconds()
590 }
591
592 pub fn time_is_exhausted(&self) -> bool {
594 self.time_rule.exhausted()
595 }
596
597 fn determine_branch_strategy(
602 &self,
603 attribute: usize,
604 path: &mut SearchPath,
605 cover: &mut Cover,
606 similarity: &SimilarityCover,
607 ) -> BranchingChoice {
608 let mut branch_first = 0;
609 let mut bounds = [0.0, 0.0];
610
611 match self.config.branching_policy {
612 BranchingPolicy::Default => {}
613 BranchingPolicy::Dynamic => {
614 bounds = self.get_cached_branch_bounds(attribute, path);
615 if let LowerBoundPolicy::Similarity = self.config.lower_bound_policy {
616 self.enhance_bounds_with_similarity(&mut bounds, attribute, cover, similarity);
617 }
618 branch_first = (bounds[1] > bounds[0]) as usize;
619 }
620 }
621
622 let first_bound = bounds[branch_first];
623 let second_bound = bounds[1 - branch_first];
624
625 (branch_first, first_bound, second_bound)
626 }
627
628 fn get_cached_branch_bounds(&self, attribute: usize, path: &mut SearchPath) -> [f64; 2] {
631 let mut bounds = [0.0; 2];
632 for (branch, lb) in bounds.iter_mut().enumerate() {
633 let branch_item = item(attribute, branch);
634 path.push(branch_item);
635 let key = path.to_key();
636 if let Some(node) = self.cache.node(&key) {
637 let error = node.error();
638 *lb = match error.is_finite() {
639 true => error,
640 false => node.lower_bound(),
641 }
642 }
643 path.remove(&branch_item)
644 }
645 bounds
646 }
647
648 fn enhance_bounds_with_similarity(
650 &self,
651 bounds: &mut [f64; 2],
652 attribute: usize,
653 cover: &mut Cover,
654 similarity: &SimilarityCover,
655 ) {
656 for (branch, lb) in bounds.iter_mut().enumerate() {
657 let branch_item = item(attribute, branch);
658 cover.branch_on(branch_item);
659 let similarity_lb = similarity.compute_similarity(cover.sparse());
660 *lb = lb.max(similarity_lb);
661 cover.backtrack()
662 }
663 }
664
665 fn backtrack(
668 &mut self,
669 cover: &mut Cover,
670 path: &mut SearchPath,
671 index: Index,
672 item: &usize,
673 search_result: &SearchResult,
674 similarity: &mut SimilarityCover,
675 ) {
676 if !(index.is_new() || search_result.has_intersected) {
677 cover.branch_on(*item);
678 }
679
680 if self.config.use_similarity_lb() && search_result.reason == Reason::LowerBoundConstrained
681 {
682 let key = index.to_cache_key(path);
683 if let Some(node) = self.cache.node(&key) {
684 similarity.update(cover.sparse(), node.lower_bound())
685 }
686 }
687 cover.backtrack();
688 path.remove(item);
689 }
690
691 fn apply_specialized_depth2_search(
693 &mut self,
694 cover: &mut Cover,
695 _candidates: &[usize],
696 parent_index: Index,
697 upper_bound: f64,
698 path: &mut SearchPath,
699 depth: usize,
700 ) -> Result<SearchResult, FitError> {
701 let key = parent_index.to_cache_key(path);
702 if let Some(node) = self.cache.node(&key) {
703 if upper_bound < node.lower_bound() {
704 return Ok(SearchResult {
705 error: node.error(),
706 has_intersected: true,
707 reason: Reason::LowerBoundConstrained,
708 });
709 }
710 }
711 let tree_result = self
712 .depth2_search
713 .fit(self.config.base.min_support, depth, cover, None);
714 match tree_result {
715 Err(err) => Err(err),
716 Ok(tree) => {
717 let error = tree.root_error();
718 self.cache_specialized_depth2_tree_results(
719 path,
720 parent_index,
721 &tree,
722 tree.get_root_index(),
723 );
724
725 Ok(SearchResult {
726 error,
727 has_intersected: true,
728 reason: Reason::FromSpecializedAlgorithm,
729 })
730 }
731 }
732 }
733
734 fn cache_specialized_depth2_tree_results(
736 &mut self,
737 path: &mut SearchPath,
738 parent_index: Index,
739 tree: &Tree,
740 tree_index: usize,
741 ) {
742 let parent_key = parent_index.to_cache_key(path);
743 let node_test = tree.node_test(tree_index);
744 if let Some(updater) = self.cache.update_node(&parent_key) {
745 let error = tree.node_error(tree_index);
746 let updater = updater
747 .error(error)
748 .leaf_error(error)
749 .upper_bound(error)
750 .optimal();
751 match node_test {
752 Some(test) => {
753 updater.test(test);
754 }
755 None => {
756 updater
757 .leaf()
758 .output(tree.node_output(tree_index).unwrap_or(0.0));
759 }
760 }
761 }
762 let Some(node_test) = node_test else {
764 return;
765 };
766
767 let children = tree.node_children(tree_index);
768 let children = [children.0, children.1];
769 for (branch, &tree_branch_index) in children.iter().enumerate() {
770 if tree_branch_index > 0 {
771 let branch_item = item(node_test, branch);
772 path.push(branch_item);
773 let branch_key_vec = path.to_sorted_vec();
774 let cache_branch_index = self.cache.insert(&branch_key_vec);
775 self.cache_specialized_depth2_tree_results(
776 path,
777 cache_branch_index,
778 tree,
779 tree_branch_index,
780 );
781 path.remove(&branch_item);
782 }
783 }
784 }
785
786 pub fn cache_entry_to_tree_entry(&self, cache_entry: &CacheEntry) -> NodeInfos {
788 NodeInfos {
789 error: cache_entry.error(),
790 out: if cache_entry.is_leaf() {
791 Some(cache_entry.out())
792 } else {
793 None
794 },
795 test: if cache_entry.is_leaf() {
796 None
797 } else {
798 Some(cache_entry.test())
799 },
800 metric: None,
801 }
802 }
803
804 fn build_solution_tree(&mut self) {
806 let mut tree = Tree::default();
807 let mut path = SearchPath::new();
808 if let Some(cache_root) = self.cache.root() {
809 let unsolved = cache_root.error().is_infinite() && !cache_root.is_leaf();
813 if unsolved && cache_root.leaf_error() < self.config.base.max_error {
814 tree.add_root(TreeNode::new(NodeInfos {
815 test: None,
816 error: cache_root.leaf_error(),
817 metric: None,
818 out: Some(cache_root.out()),
819 }));
820 } else {
821 let tree_entry = self.cache_entry_to_tree_entry(cache_root);
822 let root = tree.add_root(TreeNode::new(tree_entry));
823 self.build_tree_branches(cache_root.test(), &mut path, &mut tree, root);
824 }
825 }
826 self.tree = tree;
827 }
828
829 fn build_tree_branches(
830 &self,
831 attribute: usize,
832 path: &mut SearchPath,
833 tree: &mut Tree,
834 index: usize,
835 ) {
836 if attribute == usize::MAX {
837 return;
838 }
839
840 for branch in 0..2 {
841 let branch_item = item(attribute, branch);
842 path.push(branch_item);
843 let key = path.to_key();
844 if let Some(node) = self.cache.node(&key) {
845 let branch_entry = self.cache_entry_to_tree_entry(node);
846 let child_index = tree.add_node(index, branch == 0, TreeNode::new(branch_entry));
847 if !node.is_leaf() {
848 self.build_tree_branches(node.test(), path, tree, child_index);
849 }
850 }
851 path.remove(&branch_item);
852 }
853 }
854}
855
856#[cfg(test)]
857mod dl85_test {
858 use crate::algorithms::common::errors::NativeError;
859 use crate::algorithms::common::heuristics::NoHeuristic;
860 use crate::algorithms::common::types::OptimalDepth2Policy;
861 use crate::algorithms::optimal::depth2::ErrorMinimizer;
862 use crate::algorithms::optimal::dl85::DL85Builder;
863 use crate::algorithms::TreeSearchAlgorithm;
864 use crate::caching::Trie;
865 use crate::reader::data_reader::DataReader;
866 use std::path::Path;
867
868 #[test]
869 fn run() -> Result<(), Box<dyn std::error::Error>> {
870 let reader = DataReader::default();
871 let path = Path::new("test_data/anneal.txt");
872 let mut cover = reader.read_file(path)?;
873
874 let error_fn = Box::<NativeError>::default();
875
876 let depth2 = Box::new(ErrorMinimizer::new(error_fn.clone()));
877
878 let mut algo = DL85Builder::default()
879 .max_depth(2)
880 .min_support(50)
881 .max_time(10.0)
882 .specialization(OptimalDepth2Policy::Enabled)
883 .cache(Box::<Trie>::default())
884 .heuristic(Box::<NoHeuristic>::default())
885 .depth2_search(depth2)
886 .error_function(error_fn)
887 .build()?;
888 algo.fit(&mut cover)?;
889
890 println!("Search statistics: {:#?}", algo.statistics);
891 println!("Execution time: {:.3}s", algo.time_rule.elapsed_seconds());
892
893 println!("{}", algo.tree);
894
895 Ok(())
896 }
897}