Skip to main content

dtrees_rs/algorithms/optimal/rules/
gain.rs

1use crate::algorithms::optimal::rules::core::Reason;
2use crate::algorithms::optimal::rules::helpers::StepStrategy;
3use crate::algorithms::optimal::rules::{Rule, RuleContext, RuleResult, RuleState};
4
5/// Restricts a pass to the paths whose accumulated heuristic loss stays
6/// within a gap.
7///
8/// At each node, choosing a feature other than the best-ranked one loses
9/// the difference between their heuristic scores. The rule cuts the paths
10/// whose total loss exceeds the gap; relaxing it widens the gap by `delta`
11/// times the next step of its [`StepStrategy`], up to `limit`.
12pub struct GainRule {
13    gap: f64,
14    delta: f64,
15    limit: f64,
16    increment: Box<dyn StepStrategy>,
17    priority: u8,
18    delay: u8,
19    relaxable: bool,
20    state: RuleState,
21}
22
23impl GainRule {
24    /// A rule starting at `gap`, widened in steps of `delta` up to `limit`.
25    pub fn new(gap: f64, delta: f64, limit: f64, increment: Box<dyn StepStrategy>) -> Self {
26        Self {
27            gap,
28            delta,
29            limit,
30            increment,
31            priority: 91,
32            delay: 0,
33            relaxable: true,
34            state: RuleState::Disabled,
35        }
36    }
37
38    /// Sets the evaluation priority.
39    pub fn with_priority(mut self, priority: u8) -> Self {
40        self.priority = priority;
41        self
42    }
43
44    /// Sets the number of passes before the rule takes effect.
45    pub fn with_delay(mut self, delay: u8) -> Self {
46        self.delay = delay;
47        self
48    }
49
50    /// Sets the gap of the first pass.
51    pub fn with_gap(mut self, gap: f64) -> Self {
52        self.gap = gap;
53        self
54    }
55
56    /// Sets the largest gap.
57    pub fn with_limit(mut self, limit: f64) -> Self {
58        self.limit = limit;
59        self
60    }
61
62    /// Sets the step of the gap; ignored unless positive. The search sets it
63    /// to the smallest loss seen in the first pass.
64    pub fn update_gap_delta(&mut self, delta: f64) {
65        if delta <= 0.0 {
66            return;
67        }
68        self.delta = delta;
69    }
70
71    /// Lowers the largest gap to `limit`.
72    pub fn update_limit(&mut self, limit: f64) {
73        self.limit = self.limit.min(limit);
74    }
75}
76
77impl Rule for GainRule {
78    fn evaluate(&self, context: &RuleContext) -> RuleResult {
79        if !self.is_active() {
80            return RuleResult::continue_search();
81        }
82        if context.gain > self.gap {
83            RuleResult::stop_with_bound(f64::INFINITY, Reason::RuleReason)
84        } else {
85            RuleResult::continue_search()
86        }
87    }
88
89    fn priority(&self) -> u8 {
90        self.priority
91    }
92
93    fn description(&self) -> String {
94        "Gain rule".to_string()
95    }
96
97    fn state(&self) -> RuleState {
98        self.state
99    }
100
101    fn activate(&mut self) {
102        self.state = RuleState::Active
103    }
104
105    fn deactivate(&mut self) {
106        self.state = RuleState::Disabled
107    }
108
109    fn is_relaxable(&self) -> bool {
110        self.relaxable
111    }
112
113    fn relax(&mut self) {
114        if !self.is_active() {
115            return;
116        }
117        if self.is_relaxable() && self.gap >= self.limit {
118            self.deactivate();
119            return;
120        }
121        self.gap = self.delta * self.increment.next() as f64;
122        if self.gap >= self.limit {
123            self.gap = self.limit
124        }
125    }
126
127    fn delay(&self) -> u8 {
128        self.delay
129    }
130
131    fn as_any(&self) -> &dyn std::any::Any {
132        self
133    }
134
135    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
136        self
137    }
138}