Skip to content

Commit 7f7f0cf

Browse files
feat: add method for retrieving relevant brancher events (#138)
For some instances (e.g. `2023/code-generator/unison.mzn`, with `2023/code-generator/mips_gcc.flow.find_regno_partial.dzn`), it showed up in profiling that the dynamic brancher went through a lot of branchers upon certain events (in this case the `on_unassign_integer` event, even though only a single brancher was interested in these events). To alleviate this issue, this PR adds a method for `Brancher`s, `VariableSelector`s, and `ValueSelector`s which allows them to indicate which events they are interested in; this allows the `DynamicBrancher` to only call the `Brancher`s which are interested in it.
1 parent 49bf281 commit 7f7f0cf

37 files changed

+350
-23
lines changed

Cargo.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pumpkin-solver/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ clap = { version = "4.5.17", features = ["derive"] }
2626
env_logger = "0.10.0"
2727
bitfield-struct = "0.9.2"
2828
num = "0.4.3"
29+
enum-map = "2.7.3"
2930

3031
[dev-dependencies]
3132
clap = { version = "4.5.17", features = ["derive"] }

pumpkin-solver/src/branching/brancher.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
use enum_map::Enum;
2+
13
#[cfg(doc)]
24
use crate::basic_types::Random;
35
use crate::basic_types::SolutionReference;
46
#[cfg(doc)]
57
use crate::branching;
68
#[cfg(doc)]
9+
use crate::branching::branchers::dynamic_brancher::DynamicBrancher;
10+
#[cfg(doc)]
711
use crate::branching::value_selection::ValueSelector;
812
#[cfg(doc)]
913
use crate::branching::variable_selection::VariableSelector;
@@ -39,33 +43,53 @@ pub trait Brancher {
3943

4044
/// A function which is called after a conflict has been found and processed but (currently)
4145
/// does not provide any additional information.
46+
///
47+
/// To receive information about this event, use [`BrancherEvent::Conflict`] in
48+
/// [`Self::subscribe_to_events`]
4249
fn on_conflict(&mut self) {}
4350

4451
/// A function which is called whenever a backtrack occurs in the [`Solver`].
52+
///
53+
/// To receive information about this event, use [`BrancherEvent::Backtrack`] in
54+
/// [`Self::subscribe_to_events`]
4555
fn on_backtrack(&mut self) {}
4656

4757
/// This method is called when a solution is found; this will either be called when a new
4858
/// incumbent solution is found (i.e. a solution with a better objective value than previously
4959
/// known) or when a new solution is found when iterating over solutions using
5060
/// [`SolutionIterator`].
61+
///
62+
/// To receive information about this event, use [`BrancherEvent::Solution`] in
63+
/// [`Self::subscribe_to_events`]
5164
fn on_solution(&mut self, _solution: SolutionReference) {}
5265

5366
/// A function which is called after a [`DomainId`] is unassigned during backtracking (i.e. when
5467
/// it was fixed but is no longer), specifically, it provides `variable` which is the
5568
/// [`DomainId`] which has been reset and `value` which is the value to which the variable was
5669
/// previously fixed. This method could thus be called multiple times in a single
5770
/// backtracking operation by the solver.
71+
///
72+
/// To receive information about this event, use [`BrancherEvent::UnassignInteger`] in
73+
/// [`Self::subscribe_to_events`]
5874
fn on_unassign_integer(&mut self, _variable: DomainId, _value: i32) {}
5975

6076
/// A function which is called when a [`Predicate`] appears in a conflict during conflict
6177
/// analysis.
78+
///
79+
/// To receive information about this event, use
80+
/// [`BrancherEvent::AppearanceInConflictPredicate`] in [`Self::subscribe_to_events`]
6281
fn on_appearance_in_conflict_predicate(&mut self, _predicate: Predicate) {}
6382

6483
/// This method is called whenever a restart is performed.
84+
/// To receive information about this event, use [`BrancherEvent::Restart`] in
85+
/// [`Self::subscribe_to_events`]
6586
fn on_restart(&mut self) {}
6687

6788
/// Called after backtracking.
6889
/// Used to reset internal data structures to account for the backtrack.
90+
///
91+
/// To receive information about this event, use [`BrancherEvent::Synchronise`] in
92+
/// [`Self::subscribe_to_events`]
6993
fn synchronise(&mut self, _assignments: &Assignments) {}
7094

7195
/// This method returns whether a restart is *currently* pointless for the [`Brancher`].
@@ -81,4 +105,31 @@ pub trait Brancher {
81105
fn is_restart_pointless(&mut self) -> bool {
82106
true
83107
}
108+
109+
/// Indicates which [`BrancherEvent`] are relevant for this particular [`Brancher`].
110+
///
111+
/// This can be used by [`Brancher::subscribe_to_events`] to determine upon which
112+
/// events which [`VariableSelector`] should be called.
113+
fn subscribe_to_events(&self) -> Vec<BrancherEvent>;
114+
}
115+
116+
/// The events which can occur for a [`Brancher`]. Used for returning which events are relevant in
117+
/// [`Brancher::subscribe_to_events`], [`VariableSelector::subscribe_to_events`],
118+
/// and [`ValueSelector::subscribe_to_events`].
119+
#[derive(Debug, Clone, Copy, Enum, Hash, PartialEq, Eq)]
120+
pub enum BrancherEvent {
121+
/// Event for when a conflict is detected
122+
Conflict,
123+
/// Event for when a backtrack is performed
124+
Backtrack,
125+
/// Event for when a solution has been found
126+
Solution,
127+
/// Event for when an integer variable has become unassigned
128+
UnassignInteger,
129+
/// Event for when a predicate appears during conflict analysis
130+
AppearanceInConflictPredicate,
131+
/// Event for when a restart occurs
132+
Restart,
133+
/// Event which is called with the new state after a backtrack has occurred
134+
Synchronise,
84135
}

pumpkin-solver/src/branching/branchers/alternating_brancher.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//! on the strategy specified in [`AlternatingStrategy`].
33
44
use crate::basic_types::SolutionReference;
5+
use crate::branching::brancher::BrancherEvent;
56
use crate::branching::Brancher;
67
use crate::branching::SelectionContext;
78
use crate::engine::predicates::predicate::Predicate;
@@ -208,6 +209,16 @@ impl<OtherBrancher: Brancher> Brancher for AlternatingBrancher<OtherBrancher> {
208209
self.other_brancher.synchronise(assignments);
209210
}
210211
}
212+
213+
fn subscribe_to_events(&self) -> Vec<BrancherEvent> {
214+
// We require the restart event and on solution event for the alternating brancher itself;
215+
// additionally, it will be interested in the events of its sub-branchers
216+
[BrancherEvent::Restart, BrancherEvent::Solution]
217+
.into_iter()
218+
.chain(self.default_brancher.subscribe_to_events())
219+
.chain(self.other_brancher.subscribe_to_events())
220+
.collect()
221+
}
211222
}
212223

213224
#[cfg(test)]

pumpkin-solver/src/branching/branchers/autonomous_search.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::basic_types::SolutionReference;
55
use crate::branching::value_selection::RandomSplitter;
66
use crate::branching::variable_selection::RandomSelector;
77
use crate::branching::Brancher;
8+
use crate::branching::BrancherEvent;
89
use crate::branching::SelectionContext;
910
use crate::containers::KeyValueHeap;
1011
use crate::containers::StorageKey;
@@ -292,6 +293,19 @@ impl<BackupBrancher: Brancher> Brancher for AutonomousSearch<BackupBrancher> {
292293
fn is_restart_pointless(&mut self) -> bool {
293294
false
294295
}
296+
297+
fn subscribe_to_events(&self) -> Vec<BrancherEvent> {
298+
[
299+
BrancherEvent::Solution,
300+
BrancherEvent::Conflict,
301+
BrancherEvent::Backtrack,
302+
BrancherEvent::Synchronise,
303+
BrancherEvent::AppearanceInConflictPredicate,
304+
]
305+
.into_iter()
306+
.chain(self.backup_brancher.subscribe_to_events())
307+
.collect()
308+
}
295309
}
296310

297311
#[cfg(test)]

pumpkin-solver/src/branching/branchers/dynamic_brancher.rs

Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
use std::cmp::min;
77
use std::fmt::Debug;
88

9+
use enum_map::EnumMap;
10+
11+
use crate::basic_types::HashSet;
912
use crate::basic_types::SolutionReference;
13+
use crate::branching::brancher::BrancherEvent;
1014
use crate::branching::Brancher;
1115
use crate::branching::SelectionContext;
1216
use crate::engine::predicates::predicate::Predicate;
@@ -28,6 +32,9 @@ use crate::engine::Assignments;
2832
pub struct DynamicBrancher {
2933
branchers: Vec<Box<dyn Brancher>>,
3034
brancher_index: usize,
35+
36+
relevant_event_to_index: EnumMap<BrancherEvent, Vec<usize>>,
37+
relevant_events: Vec<BrancherEvent>,
3138
}
3239

3340
impl Debug for DynamicBrancher {
@@ -40,14 +47,36 @@ impl DynamicBrancher {
4047
/// Creates a new [`DynamicBrancher`] with the provided `branchers`. It will attempt to use the
4148
/// `branchers` in the order in which they were provided.
4249
pub fn new(branchers: Vec<Box<dyn Brancher>>) -> Self {
50+
let mut relevant_event_to_index: EnumMap<BrancherEvent, Vec<usize>> = EnumMap::default();
51+
let mut relevant_events = HashSet::new();
52+
53+
// The dynamic brancher will reset the indices upon these events so they should be called
54+
let _ = relevant_events.insert(BrancherEvent::Solution);
55+
let _ = relevant_events.insert(BrancherEvent::Conflict);
56+
57+
branchers.iter().enumerate().for_each(|(index, brancher)| {
58+
for event in brancher.subscribe_to_events() {
59+
relevant_event_to_index[event].push(index);
60+
let _ = relevant_events.insert(event);
61+
}
62+
});
4363
Self {
4464
branchers,
4565
brancher_index: 0,
66+
67+
relevant_event_to_index,
68+
relevant_events: relevant_events.into_iter().collect(),
4669
}
4770
}
4871

4972
pub fn add_brancher(&mut self, brancher: Box<dyn Brancher>) {
50-
self.branchers.push(brancher)
73+
for event in brancher.subscribe_to_events() {
74+
self.relevant_event_to_index[event].push(self.branchers.len());
75+
if !self.relevant_events.contains(&event) {
76+
self.relevant_events.push(event);
77+
}
78+
}
79+
self.branchers.push(brancher);
5180
}
5281
}
5382

@@ -70,46 +99,50 @@ impl Brancher for DynamicBrancher {
7099
// A conflict has occurred, we do not know which brancher now can select a variable, reset
71100
// to the first one
72101
self.brancher_index = 0;
73-
self.branchers
74-
.iter_mut()
75-
.for_each(|brancher| brancher.on_conflict());
102+
self.relevant_event_to_index[BrancherEvent::Conflict]
103+
.iter()
104+
.for_each(|&brancher_index| self.branchers[brancher_index].on_conflict());
76105
}
77106

78107
fn on_backtrack(&mut self) {
79-
self.branchers
80-
.iter_mut()
81-
.for_each(|brancher| brancher.on_backtrack());
108+
self.relevant_event_to_index[BrancherEvent::Backtrack]
109+
.iter()
110+
.for_each(|&brancher_index| self.branchers[brancher_index].on_backtrack());
82111
}
83112

84113
fn on_unassign_integer(&mut self, variable: DomainId, value: i32) {
85-
self.branchers
86-
.iter_mut()
87-
.for_each(|brancher| brancher.on_unassign_integer(variable, value));
114+
self.relevant_event_to_index[BrancherEvent::UnassignInteger]
115+
.iter()
116+
.for_each(|&brancher_index| {
117+
self.branchers[brancher_index].on_unassign_integer(variable, value)
118+
});
88119
}
89120

90121
fn on_appearance_in_conflict_predicate(&mut self, predicate: Predicate) {
91-
self.branchers
92-
.iter_mut()
93-
.for_each(|brancher| brancher.on_appearance_in_conflict_predicate(predicate));
122+
self.relevant_event_to_index[BrancherEvent::AppearanceInConflictPredicate]
123+
.iter()
124+
.for_each(|&brancher_index| {
125+
self.branchers[brancher_index].on_appearance_in_conflict_predicate(predicate)
126+
});
94127
}
95128

96129
fn on_solution(&mut self, solution: SolutionReference) {
97130
self.brancher_index = 0;
98-
self.branchers
99-
.iter_mut()
100-
.for_each(|brancher| brancher.on_solution(solution));
131+
self.relevant_event_to_index[BrancherEvent::Solution]
132+
.iter()
133+
.for_each(|&brancher_index| self.branchers[brancher_index].on_solution(solution));
101134
}
102135

103136
fn on_restart(&mut self) {
104-
self.branchers
105-
.iter_mut()
106-
.for_each(|brancher| brancher.on_restart());
137+
self.relevant_event_to_index[BrancherEvent::Restart]
138+
.iter()
139+
.for_each(|&brancher_index| self.branchers[brancher_index].on_restart());
107140
}
108141

109142
fn synchronise(&mut self, assignments: &Assignments) {
110-
self.branchers
111-
.iter_mut()
112-
.for_each(|brancher| brancher.synchronise(assignments));
143+
self.relevant_event_to_index[BrancherEvent::Synchronise]
144+
.iter()
145+
.for_each(|&brancher_index| self.branchers[brancher_index].synchronise(assignments));
113146
}
114147

115148
fn is_restart_pointless(&mut self) -> bool {
@@ -120,4 +153,8 @@ impl Brancher for DynamicBrancher {
120153
.iter_mut()
121154
.all(|brancher| brancher.is_restart_pointless())
122155
}
156+
157+
fn subscribe_to_events(&self) -> Vec<BrancherEvent> {
158+
self.relevant_events.clone()
159+
}
123160
}

pumpkin-solver/src/branching/branchers/independent_variable_value_brancher.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use std::marker::PhantomData;
55

66
use crate::basic_types::SolutionReference;
7+
use crate::branching::brancher::BrancherEvent;
78
use crate::branching::value_selection::ValueSelector;
89
use crate::branching::variable_selection::VariableSelector;
910
use crate::branching::Brancher;
@@ -89,4 +90,12 @@ where
8990
fn is_restart_pointless(&mut self) -> bool {
9091
self.variable_selector.is_restart_pointless() && self.value_selector.is_restart_pointless()
9192
}
93+
94+
fn subscribe_to_events(&self) -> Vec<BrancherEvent> {
95+
self.variable_selector
96+
.subscribe_to_events()
97+
.into_iter()
98+
.chain(self.value_selector.subscribe_to_events())
99+
.collect()
100+
}
92101
}

pumpkin-solver/src/branching/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ pub mod tie_breaking;
7171
pub mod value_selection;
7272
pub mod variable_selection;
7373

74-
pub use brancher::Brancher;
74+
pub use brancher::*;
7575
pub use selection_context::SelectionContext;
7676

7777
#[cfg(doc)]

pumpkin-solver/src/branching/value_selection/dynamic_value_selector.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::fmt::Debug;
22

33
use super::ValueSelector;
44
use crate::basic_types::SolutionReference;
5+
use crate::branching::brancher::BrancherEvent;
56
#[cfg(doc)]
67
use crate::branching::branchers::dynamic_brancher::DynamicBrancher;
78
use crate::branching::SelectionContext;
@@ -46,4 +47,8 @@ impl<Var> ValueSelector<Var> for DynamicValueSelector<Var> {
4647
fn is_restart_pointless(&mut self) -> bool {
4748
self.selector.is_restart_pointless()
4849
}
50+
51+
fn subscribe_to_events(&self) -> Vec<BrancherEvent> {
52+
self.selector.subscribe_to_events()
53+
}
4954
}

0 commit comments

Comments
 (0)