use std::fmt::Debug;
use dyn_clone::DynClone;
use crate::{
IntVal,
actions::{
BoolInspectionActions, BrancherInitActions, DecisionActions, IntDecisionActions,
IntInspectionActions, ReasoningContext, Trailed,
},
solver::{
Decision, IntLitMeaning,
solving_context::SolvingContext,
view::{View, boolean::BoolView, integer::IntView},
},
};
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct BoolBrancher {
vars: Vec<Decision<bool>>,
var_sel: DecisionSelection,
val_sel: DomainSelection,
next: Trailed<usize>,
}
pub(crate) type BoxedBrancher = Box<dyn for<'a> Brancher<SolvingContext<'a>>>;
pub trait Brancher<D: DecisionActions>: Debug + DynClone {
fn decide(&mut self, actions: &mut D) -> Directive;
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum Directive {
Select(View<bool>),
Exhausted,
Consumed,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IntBrancher {
vars: Vec<View<IntVal>>,
var_sel: DecisionSelection,
val_sel: DomainSelection,
next: Trailed<usize>,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum DomainSelection {
IndomainMax,
IndomainMin,
OutdomainMax,
OutdomainMin,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum DecisionSelection {
AntiFirstFail,
FirstFail,
InputOrder,
Largest,
Smallest,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WarmStartBrancher {
decisions: Vec<Decision<bool>>,
conflicts: u64,
}
impl BoolBrancher {
pub fn new_in(
solver: &mut impl BrancherInitActions,
vars: Vec<View<bool>>,
var_sel: DecisionSelection,
val_sel: DomainSelection,
) {
let vars: Vec<_> = vars
.into_iter()
.filter_map(|b| match b.0 {
BoolView::Lit(l) => {
solver.ensure_decidable::<bool>(b);
Some(l)
}
BoolView::Const(_) => None,
})
.collect();
let next = solver.new_trailed(0);
solver.push_brancher(Box::new(BoolBrancher {
vars,
var_sel,
val_sel,
next,
}));
}
}
impl<E> Brancher<E> for BoolBrancher
where
E: DecisionActions,
Decision<bool>: BoolInspectionActions<E>,
{
fn decide(&mut self, ctx: &mut E) -> Directive {
let begin = ctx.trailed(self.next);
if begin == self.vars.len() {
return Directive::Exhausted;
}
debug_assert!(matches!(
self.var_sel,
DecisionSelection::InputOrder
| DecisionSelection::Smallest
| DecisionSelection::Largest
| DecisionSelection::FirstFail
| DecisionSelection::AntiFirstFail
));
let mut loc = None;
for (i, &var) in self.vars.iter().enumerate().skip(begin) {
if var.val(ctx).is_none() {
loc = Some(i);
break;
}
}
let var = if let Some(first_unfixed) = loc {
ctx.set_trailed(self.next, first_unfixed);
self.vars[first_unfixed]
} else {
return Directive::Exhausted;
};
Directive::Select(
match self.val_sel {
DomainSelection::IndomainMin | DomainSelection::OutdomainMax => !var,
DomainSelection::IndomainMax | DomainSelection::OutdomainMin => var,
}
.into(),
)
}
}
impl Clone for BoxedBrancher {
fn clone(&self) -> BoxedBrancher {
dyn_clone::clone_box(&**self)
}
}
impl IntBrancher {
pub fn new_in(
solver: &mut impl BrancherInitActions,
vars: Vec<View<IntVal>>,
var_sel: DecisionSelection,
val_sel: DomainSelection,
) {
let vars: Vec<_> = vars
.into_iter()
.filter(|i| !matches!(i.0, IntView::Const(_)))
.collect();
for &v in &vars {
solver.ensure_decidable(v);
}
let next = solver.new_trailed(0);
solver.push_brancher(Box::new(IntBrancher {
vars,
var_sel,
val_sel,
next,
}));
}
}
impl<D> Brancher<D> for IntBrancher
where
D: DecisionActions + ReasoningContext<Atom = View<bool>>,
View<IntVal>: IntDecisionActions<D>,
{
fn decide(&mut self, actions: &mut D) -> Directive {
let begin = actions.trailed(self.next);
if begin == self.vars.len() {
return Directive::Exhausted;
}
let score = |var: View<IntVal>| match self.var_sel {
DecisionSelection::AntiFirstFail | DecisionSelection::FirstFail => {
let (lb, ub) = var.bounds(actions);
ub - lb
}
DecisionSelection::InputOrder => 0,
DecisionSelection::Largest => var.max(actions),
DecisionSelection::Smallest => var.min(actions),
};
let is_better = |incumbent_score, new_score| match self.var_sel {
DecisionSelection::AntiFirstFail | DecisionSelection::Largest => {
incumbent_score < new_score
}
DecisionSelection::FirstFail | DecisionSelection::Smallest => {
incumbent_score > new_score
}
DecisionSelection::InputOrder => unreachable!(),
};
let mut first_unfixed = begin;
let mut selection = None;
for i in begin..self.vars.len() {
if self.vars[i].min(actions) == self.vars[i].max(actions) {
let unfixed_var = self.vars[first_unfixed];
let fixed_var = self.vars[i];
self.vars[first_unfixed] = fixed_var;
self.vars[i] = unfixed_var;
first_unfixed += 1;
} else if let Some((_, sel_score)) = selection {
let new_score = score(self.vars[i]);
if is_better(sel_score, new_score) {
selection = Some((self.vars[i], new_score));
}
} else {
selection = Some((self.vars[i], score(self.vars[i])));
if self.var_sel == DecisionSelection::InputOrder {
break;
}
}
}
let Some((next_var, _)) = selection else {
return Directive::Exhausted;
};
actions.set_trailed(self.next, first_unfixed);
let view = next_var.lit(
actions,
match self.val_sel {
DomainSelection::IndomainMin => IntLitMeaning::Less(next_var.min(actions) + 1),
DomainSelection::IndomainMax => IntLitMeaning::GreaterEq(next_var.max(actions)),
DomainSelection::OutdomainMin => {
IntLitMeaning::GreaterEq(next_var.min(actions) + 1)
}
DomainSelection::OutdomainMax => IntLitMeaning::Less(next_var.max(actions)),
},
);
Directive::Select(view)
}
}
impl WarmStartBrancher {
pub fn new_in(solver: &mut impl BrancherInitActions, decisions: Vec<View<bool>>) {
let mut filtered_decision = Vec::new();
for d in decisions {
match d.0 {
BoolView::Lit(l) => {
solver.ensure_decidable::<bool>(d);
filtered_decision.push(l);
}
BoolView::Const(false) => break,
BoolView::Const(true) => {}
}
}
if !filtered_decision.is_empty() {
filtered_decision.reverse();
solver.push_brancher(Box::new(WarmStartBrancher {
decisions: filtered_decision,
conflicts: solver.num_conflicts(),
}));
}
}
}
impl<Context> Brancher<Context> for WarmStartBrancher
where
Context: DecisionActions,
Decision<bool>: BoolInspectionActions<Context>,
{
fn decide(&mut self, ctx: &mut Context) -> Directive {
if ctx.num_conflicts() > self.conflicts {
return Directive::Consumed;
}
while let Some(lit) = self.decisions.pop() {
match lit.val(ctx) {
Some(true) => {}
Some(false) => return Directive::Consumed,
None => return Directive::Select(lit.into()),
}
}
Directive::Consumed
}
}