pub(crate) mod activation_list;
pub(crate) mod bool_to_int;
pub mod branchers;
pub(crate) mod decision;
pub(crate) mod engine;
pub(crate) mod initialization_context;
pub(crate) mod queue;
pub(crate) mod solution;
pub(crate) mod solving_context;
pub(crate) mod trail;
pub(crate) mod view;
use std::{
any::Any,
cell::{RefCell, RefMut},
fmt::Debug,
hash::Hash,
mem,
num::NonZero,
ops::{Add, AddAssign, Not},
rc::Rc,
};
use bon::{Builder, bon};
use itertools::Itertools;
pub use pindakaas::solver::cadical::Cadical;
use pindakaas::{
ClauseDatabase, ClauseDatabaseTools, Lit as RawLit, Unsatisfiable, VarRange,
solver::{
Assumptions, FailedAssumptions, LearnCallback, SolveResult as SatSolveResult,
TerminateCallback,
propagation::{ExternalPropagation, SolvingActions},
},
};
use rangelist::IntervalIterator;
use rustc_hash::FxHashMap;
use tracing::{debug, warn};
pub use crate::solver::{
decision::{Decision, DecisionReference},
solution::{AnyView, Solution, Valuation, Value},
view::{DefaultView, View, boolean::BoolView, integer::IntView},
};
use crate::{
Clause, IntSet, IntVal,
actions::{
BrancherInitActions, ConstructionActions, DecisionActions, IntDecisionActions,
IntInspectionActions, PostingActions, ReasoningContext, ReasoningEngine, Trailed,
TrailingActions,
},
constraints::{BoxedPropagator, Conflict},
helpers::bytes::Bytes,
solver::{
branchers::BoxedBrancher,
decision::integer::{DirectStorage, IntDecision, LazyOrderStorage, OrderStorage},
engine::{Engine, PropRef},
initialization_context::InitializationContext,
queue::PropagatorInfo,
},
views::LinearBoolView,
};
pub trait AssumptionChecker {
fn fail(&self, bv: View<bool>) -> bool;
}
#[derive(Debug, Eq, PartialEq)]
pub struct CollectSolutionsIn<'a, View: Valuation> {
vars: Vec<View>,
store: &'a mut Vec<Vec<View::Val>>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct InitStatistics {
pub bool_decisions: usize,
pub int_decisions: usize,
pub propagators: usize,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum IntLitMeaning {
Eq(IntVal),
NotEq(IntVal),
GreaterEq(IntVal),
Less(IntVal),
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum LiteralStrategy {
Eager,
#[default]
Lazy,
}
pub(crate) struct NoAssumptions;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[non_exhaustive]
pub enum SearchStrategy {
#[default]
Branchers,
Sat,
Transition(SwitchTrigger),
Interleaved(SwitchTrigger),
}
pub trait SolutionCallback {
fn on_solution(&mut self, sol: Solution<'_>);
}
#[derive(Builder)]
#[builder(
builder_type(
name = SolveArgs,
vis = "pub",
doc {
/// Builder to configure and execute a solve operation.
///
/// Obtained by calling [`Solver::solve()`]. The builder uses type-state
/// parameters to enforce that each option is set at most once and that
/// terminal methods are only callable on a fully configured instance.
///
/// # Terminal methods
///
/// Call one of the following to execute the solve and consume the builder:
///
/// - **[`.satisfy()`][SolveArgs::satisfy]**: search for any satisfying
/// assignment.
/// - **[`.minimize(obj)`][SolveArgs::minimize]**: find the assignment
/// minimizing `obj` via branch-and-bound.
/// - **[`.maximize(obj)`][SolveArgs::maximize]**: find the assignment
/// maximizing `obj` via branch-and-bound.
}
),
generics(setters(name = "with_{}", vis = "")),
start_fn(vis = "", name = builder_internal),
finish_fn(vis = "", name = finalize_internal),
)]
struct SolveArgsComplete<'a, Sat, S, F>
where
S: SolutionCallback,
F: FnOnce(&dyn AssumptionChecker),
{
#[builder(setters(name = solver_internal, vis = ""))]
solver: &'a mut Solver<Sat>,
#[builder(default = Vec::new(), with = FromIterator::from_iter)]
assuming: Vec<View<bool>>,
#[builder(setters(name = on_solution_internal, vis = ""))]
on_solution: Option<S>,
#[builder(setters(name = on_failure_internal, vis = ""))]
on_failure: Option<F>,
#[builder(setters(name = all_solutions_internal, vis = ""))]
all_solutions: Option<Vec<AnyView>>,
bind_using_clauses: Option<bool>,
}
#[derive(Debug)]
pub struct Solver<Sat = Cadical> {
pub(crate) sat: Sat,
pub(crate) engine: Rc<RefCell<Engine>>,
}
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub struct SolverStatistics {
pub conflicts: u64,
pub sat_search_directives: u64,
pub peak_depth: u32,
pub cp_propagator_calls: u64,
pub restarts: u32,
pub user_search_directives: u64,
pub eager_literals: u64,
pub lazy_literals: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Status {
Satisfied,
Unsatisfiable,
Complete,
Unknown,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum SwitchTrigger {
Conflicts(u64),
Restarts(u64),
}
pub type TerminationSignal = pindakaas::solver::TermSignal;
fn trace_learned_clause(clause: &mut dyn Iterator<Item = RawLit>) {
debug!(
target: "solver",
clause = ?clause.map(i32::from).collect::<Vec<i32>>(),
"learn clause"
);
}
impl<A: FailedAssumptions> AssumptionChecker for A {
fn fail(&self, bv: View<bool>) -> bool {
match bv.0 {
BoolView::Lit(lit) => self.fail(lit.0),
BoolView::Const(false) => true,
BoolView::Const(true) => false,
}
}
}
impl<'a, View: Valuation> SolutionCallback for CollectSolutionsIn<'a, View> {
fn on_solution(&mut self, sol: Solution<'_>) {
self.store
.push(self.vars.iter().map(|v| Valuation::val(v, sol)).collect());
}
}
impl<F> SolutionCallback for F
where
F: for<'a> FnMut(Solution<'a>),
{
fn on_solution(&mut self, sol: Solution<'_>) {
self(sol);
}
}
impl IntLitMeaning {
pub(crate) fn defining_clauses(
&self,
lit: RawLit,
prev: Option<RawLit>,
next: Option<RawLit>,
) -> Vec<Clause<RawLit>> {
let mut ret = Vec::<Clause<RawLit>>::new();
match self {
IntLitMeaning::Eq(_) => {
let prev = prev.expect("prev should contain the GreaterEq literal for the value"); let next =
next.expect("next should contain the GreaterEq literal for the next value");
ret.push(vec![!lit, !prev]); ret.push(vec![!lit, next]); ret.push(vec![lit, prev, !next]); }
IntLitMeaning::Less(_) => {
if let Some(prev) = prev {
ret.push(vec![!prev, lit]); }
if let Some(next) = next {
ret.push(vec![!lit, next]); }
}
_ => unreachable!(),
}
ret
}
}
impl Not for IntLitMeaning {
type Output = IntLitMeaning;
fn not(self) -> Self::Output {
match self {
IntLitMeaning::Eq(i) => IntLitMeaning::NotEq(i),
IntLitMeaning::NotEq(i) => IntLitMeaning::Eq(i),
IntLitMeaning::GreaterEq(i) => IntLitMeaning::Less(i),
IntLitMeaning::Less(i) => IntLitMeaning::GreaterEq(i),
}
}
}
impl AssumptionChecker for NoAssumptions {
fn fail(&self, bv: View<bool>) -> bool {
matches!(bv.0, BoolView::Const(false))
}
}
impl<'a, Sat, S, F, State: solve_args::State> SolveArgs<'a, Sat, S, F, State>
where
S: SolutionCallback,
F: FnOnce(&dyn AssumptionChecker),
{
pub fn all_solutions<T: Into<AnyView>>(
self,
views: impl IntoIterator<Item = T>,
) -> SolveArgs<'a, Sat, S, F, solve_args::SetAllSolutions<State>>
where
State::AllSolutions: solve_args::IsUnset,
{
self.maybe_all_solutions(Some(views))
}
pub fn collect_solutions_in<'b, View: Valuation>(
self,
vars: Vec<View>,
store: &'b mut Vec<Vec<View::Val>>,
) -> SolveArgs<'a, Sat, CollectSolutionsIn<'b, View>, F, solve_args::SetOnSolution<State>>
where
State::OnSolution: solve_args::IsUnset,
{
self.with_s::<CollectSolutionsIn<'b, View>>()
.on_solution_internal(CollectSolutionsIn { vars, store })
}
pub fn maximize(self, objective: impl Into<View<IntVal>>) -> (Status, Option<IntVal>)
where
Sat: ExternalPropagation + Assumptions,
State::Solver: solve_args::IsSet,
{
let objective = objective.into();
let (status, opt) = self.minimize(-objective);
(status, opt.map(|v| -v))
}
pub fn maybe_all_solutions<T: Into<AnyView>>(
self,
views: Option<impl IntoIterator<Item = T>>,
) -> SolveArgs<'a, Sat, S, F, solve_args::SetAllSolutions<State>>
where
State::AllSolutions: solve_args::IsUnset,
{
self.maybe_all_solutions_internal(views.map(|v| v.into_iter().map(|v| v.into()).collect()))
}
pub fn minimize(self, objective: impl Into<View<IntVal>>) -> (Status, Option<IntVal>)
where
Sat: ExternalPropagation + Assumptions,
State::Solver: solve_args::IsSet,
{
use Status::*;
let SolveArgsComplete {
solver,
assuming,
mut on_solution,
on_failure,
all_solutions,
bind_using_clauses,
} = self.finalize_internal();
let mut bind_using_clauses = bind_using_clauses.unwrap_or(false);
if bind_using_clauses && all_solutions.is_some() {
warn!("bind_using_clauses option ignored because all_solutions is enabled");
bind_using_clauses = false;
}
let Ok(mut assumptions): Result<Vec<RawLit>, _> = assuming
.into_iter()
.filter_map(|bv| match bv.0 {
BoolView::Lit(lit) => Some(Ok(lit.0)),
BoolView::Const(true) => None,
BoolView::Const(false) => Some(Err(())),
})
.collect()
else {
if let Some(on_failure) = on_failure {
on_failure(&NoAssumptions);
}
return (Unsatisfiable, None);
};
let objective = objective.into();
let mut obj_curr = None;
let obj_bound = objective.min(solver);
let mut obj_assump = None;
let mut vals: Vec<Value> = vec![
Value::Int(0);
if let Some(all_solutions) = &all_solutions {
all_solutions.len()
} else {
0
}
];
debug!(target: "solver", obj_bound, "start branch and bound");
let (status, obj) = loop {
let result = solver
.sat
.solve_assuming(assumptions.iter().cloned().chain(obj_assump));
match result {
SatSolveResult::Satisfied(ref sol) => {
let sol = Solution {
sat: sol,
state: &solver.engine.borrow().state,
};
obj_curr = Some(Valuation::val(&objective, sol));
if let Some(callback) = &mut on_solution {
callback.on_solution(sol);
}
if let Some(vars) = &all_solutions {
for (i, v) in vars.iter().enumerate() {
vals[i] = Valuation::val(v, sol);
}
}
debug!(
target: "solver",
?obj_curr,
obj_bound,
"sat solve result"
);
}
SatSolveResult::Unsatisfiable(fail) => {
break if obj_curr.is_none() {
if let Some(on_failure) = on_failure {
on_failure(&fail);
}
(Unsatisfiable, None)
} else {
(Complete, obj_curr)
};
}
SatSolveResult::Unknown => {
break if obj_curr.is_none() {
(Unknown, None)
} else {
(Satisfied, obj_curr)
};
}
}
drop(result);
if obj_curr == Some(obj_bound) {
break (Complete, obj_curr);
} else {
let bound_lit = objective.lit(solver, IntLitMeaning::Less(obj_curr.unwrap()));
debug!(
target: "solver",
clause = bind_using_clauses,
lit = i32::from({
let BoolView::Lit(l) = bound_lit.0 else {
unreachable!()
};
l.0
}),
"add objective bound"
);
if bind_using_clauses {
solver.add_clause([bound_lit]).unwrap();
} else {
let BoolView::Lit(l) = bound_lit.0 else {
unreachable!()
};
obj_assump = Some(l.0);
}
}
};
if all_solutions.is_none() || status != Complete {
return (status, obj);
}
let vars = all_solutions.unwrap();
let BoolView::Lit(opt_lit) = objective
.lit(solver, IntLitMeaning::Eq(obj_curr.unwrap()))
.0
else {
unreachable!()
};
assumptions.push(opt_lit.0);
loop {
solver.add_no_good(&vars, &vals).unwrap();
let result = solver.sat.solve_assuming(assumptions.clone());
match result {
SatSolveResult::Satisfied(ref sol) => {
let sol = Solution {
sat: sol,
state: &solver.engine.borrow().state,
};
obj_curr = Some(Valuation::val(&objective, sol));
if let Some(callback) = &mut on_solution {
callback.on_solution(sol);
}
for (i, v) in vars.iter().enumerate() {
vals[i] = Valuation::val(v, sol);
}
debug!(
target: "solver",
?obj_curr,
obj_bound,
"sat solve result"
);
}
SatSolveResult::Unsatisfiable(_) => break (Complete, obj_curr),
SatSolveResult::Unknown => break (Satisfied, obj_curr),
}
}
}
pub fn on_failure<NewF>(
self,
on_failure: NewF,
) -> SolveArgs<'a, Sat, S, NewF, solve_args::SetOnFailure<State>>
where
State::OnFailure: solve_args::IsUnset,
NewF: FnOnce(&dyn AssumptionChecker),
{
self.with_f::<NewF>().on_failure_internal(on_failure)
}
pub fn on_solution<NewS>(
self,
on_solution: NewS,
) -> SolveArgs<'a, Sat, NewS, F, solve_args::SetOnSolution<State>>
where
State::OnSolution: solve_args::IsUnset,
NewS: for<'b> FnMut(Solution<'b>),
{
self.with_s::<NewS>().on_solution_internal(on_solution)
}
pub fn satisfy(self) -> Status
where
Sat: ExternalPropagation + Assumptions,
State: solve_args::IsComplete,
{
let SolveArgsComplete {
solver,
assuming,
mut on_solution,
on_failure,
all_solutions,
..
} = self.finalize_internal();
let Ok(assumptions): Result<Vec<RawLit>, _> = assuming
.into_iter()
.filter_map(|bv| match bv.0 {
BoolView::Lit(lit) => Some(Ok(lit.0)),
BoolView::Const(true) => None,
BoolView::Const(false) => Some(Err(())),
})
.collect()
else {
if let Some(on_failure) = on_failure {
on_failure(&NoAssumptions);
}
return Status::Unsatisfiable;
};
let mut has_solution = false;
loop {
let result = solver.sat.solve_assuming(assumptions.clone());
let vals: Vec<_> = match result {
SatSolveResult::Satisfied(ref sol) => {
let sol = Solution {
sat: sol,
state: &solver.engine.borrow().state,
};
has_solution = true;
if let Some(callback) = &mut on_solution {
callback.on_solution(sol);
}
if let Some(vars) = &all_solutions {
vars.iter().map(|v| v.val(sol)).collect()
} else {
break Status::Satisfied;
}
}
SatSolveResult::Unsatisfiable(fail) => {
if let Some(on_failure) = on_failure {
on_failure(&fail);
}
break if has_solution {
Status::Complete
} else {
Status::Unsatisfiable
};
}
SatSolveResult::Unknown => {
break if has_solution {
Status::Satisfied
} else {
Status::Unknown
};
}
};
drop(result);
debug_assert!(all_solutions.is_some());
let vars = all_solutions.as_ref().unwrap();
if solver.add_no_good(vars, &vals).is_err() {
break Status::Complete;
}
}
}
}
impl<Sat: ClauseDatabase> Solver<Sat> {
pub fn add_clause<Iter>(
&mut self,
clause: Iter,
) -> Result<(), <Self as ReasoningContext>::Conflict>
where
Iter: IntoIterator,
Iter::Item: Into<View<bool>>,
{
let clause = clause.into_iter().map(Into::into).collect_vec();
match ClauseDatabaseTools::add_clause(&mut self.sat, clause.clone()) {
Ok(()) => Ok(()),
Err(Unsatisfiable) => Err(Conflict::new(
self,
None,
clause.into_iter().map(|l| !l).collect_vec(),
)),
}
}
}
impl<Sat: ExternalPropagation + Assumptions> Solver<Sat> {
pub fn solve<'a>(
&'a mut self,
) -> SolveArgs<
'a,
Sat,
for<'b> fn(Solution<'b>),
for<'b> fn(&'b dyn AssumptionChecker),
solve_args::SetSolver,
> {
SolveArgsComplete::builder_internal().solver_internal(self)
}
pub fn solve_assuming(
&mut self,
assumptions: impl IntoIterator<Item = View<bool>>,
mut on_sol: impl FnMut(Solution<'_>),
on_fail: impl FnOnce(&dyn AssumptionChecker),
) -> Status {
let Ok(assumptions): Result<Vec<RawLit>, _> = assumptions
.into_iter()
.filter_map(|bv| match bv.0 {
BoolView::Lit(lit) => Some(Ok(lit.0)),
BoolView::Const(true) => None,
BoolView::Const(false) => Some(Err(())),
})
.collect()
else {
on_fail(&NoAssumptions);
return Status::Unsatisfiable;
};
let result = self.sat.solve_assuming(assumptions);
match result {
SatSolveResult::Satisfied(value) => {
let sol = Solution {
sat: &value,
state: &self.engine.borrow().state,
};
on_sol(sol);
Status::Satisfied
}
SatSolveResult::Unsatisfiable(fail) => {
on_fail(&fail);
Status::Unsatisfiable
}
SatSolveResult::Unknown => Status::Unknown,
}
}
}
#[bon]
impl<Sat: ExternalPropagation> Solver<Sat> {
#[doc(hidden)]
pub fn add_no_good(
&mut self,
vars: &[AnyView],
vals: &[Value],
) -> Result<(), <Self as ReasoningContext>::Conflict> {
let clause = vars
.iter()
.zip_eq(vals)
.map(|(var, val)| match *var {
AnyView::Bool(bv) => match val {
Value::Bool(true) => !bv,
Value::Bool(false) => bv,
_ => unreachable!(),
},
AnyView::Int(iv) => {
let Value::Int(val) = val.clone() else {
unreachable!()
};
iv.lit(self, IntLitMeaning::NotEq(val))
}
})
.collect_vec();
debug!(
target: "solver",
clause = ?clause
.iter()
.filter_map(|&x| if let BoolView::Lit(x) = x.0 {
Some(i32::from(x.0))
} else {
None
})
.collect::<Vec<i32>>(),
"add solution nogood"
);
self.add_clause(clause)
}
pub(crate) fn add_propagator(&mut self, propagator: BoxedPropagator, from_model: bool) {
let mut handle = self.engine.borrow_mut();
let engine = &mut *handle;
engine.propagators.push(propagator);
let prop_ref = PropRef::new(engine.propagators.len() - 1);
let mut ctx = InitializationContext::new(&mut engine.state, prop_ref);
engine.propagators[prop_ref.index()].initialize(&mut ctx);
let priority = ctx.priority();
let enqueue = ctx.enqueue(from_model);
let new_observed = mem::take(&mut ctx.observed_variables);
engine.state.propagator_queue.info.push(PropagatorInfo {
enqueued: false,
priority,
});
debug_assert_eq!(
prop_ref.index(),
engine.state.propagator_queue.info.len() - 1
);
if enqueue {
engine
.state
.propagator_queue
.enqueue_propagator(prop_ref.raw());
}
drop(handle);
for v in new_observed {
{
self.engine.borrow_mut().state.trail.grow_to_boolvar(v);
}
self.sat.add_observed_var(v);
}
}
pub(crate) fn as_parts_mut(&mut self) -> (impl SolvingActions + '_, RefMut<'_, Engine>) {
struct SA<'a, O>(&'a mut O);
impl<O: ExternalPropagation> SolvingActions for SA<'_, O> {
fn is_decision(&mut self, _: RawLit) -> bool {
false
}
fn new_observed_var(&mut self) -> pindakaas::Var {
self.0.new_observed_var()
}
fn phase(&mut self, lit: RawLit) {
self.0.phase(lit);
}
fn unphase(&mut self, lit: RawLit) {
self.0.unphase(lit);
}
}
(SA(&mut self.sat), self.engine.borrow_mut())
}
pub fn init_statistics(&self) -> InitStatistics {
InitStatistics {
bool_decisions: self.engine.borrow().state.statistics.eager_literals as usize
+ self.engine.borrow().state.statistics.lazy_literals as usize,
int_decisions: self.engine.borrow().state.int_vars.len(),
propagators: self.engine.borrow().propagators.len(),
}
}
pub fn new_bool_decision(&mut self) -> Decision<bool> {
let lit = self.sat.new_lit();
self.engine.borrow_mut().state.statistics.eager_literals += 1;
Decision(lit)
}
#[builder(finish_fn(name = view, doc {
/// Finalize the builder and return a [`View`] of the new integer decision.
///
/// This always succeeds, even for domains with one or two values, which
/// are represented more compactly than a full integer decision.
}))]
#[allow(
clippy::missing_docs_in_private_items,
reason = "unable to document domain member on generated builder"
)]
pub fn new_int_decision(
&mut self,
#[builder(start_fn, into)] domain: IntSet,
#[builder(default)] mut order_literals: LiteralStrategy,
#[builder(default)] direct_literals: LiteralStrategy,
) -> View<IntVal> {
let orig_domain_len = domain.card();
assert_ne!(
orig_domain_len,
Some(0),
"Unable to create integer variable empty domain"
);
if orig_domain_len == Some(1) {
return (*domain.lower_bound().unwrap()).into();
}
let lb = *domain.lower_bound().unwrap();
let ub = *domain.upper_bound().unwrap();
if orig_domain_len == Some(2) {
let lit = self.new_bool_decision();
return LinearBoolView::new(NonZero::new(ub - lb).unwrap(), lb, lit).into();
}
if (direct_literals, order_literals) == (LiteralStrategy::Eager, LiteralStrategy::Lazy) {
warn!(
target: "solver",
"coercing order_literals to eager because direct_literals is eager"
);
order_literals = LiteralStrategy::Eager;
};
let mut engine = self.engine.borrow_mut();
let upper_bound = engine.state.trail.track(ub);
let order_encoding = match order_literals {
LiteralStrategy::Eager => {
let card = orig_domain_len
.expect("unable to create literals eagerly for domains that exceed usize::MAX");
engine.state.statistics.eager_literals += (card - 1) as u64;
OrderStorage::Eager {
lower_bound: engine.state.trail.track(lb),
storage: self.sat.new_var_range(card - 1),
}
}
LiteralStrategy::Lazy => {
OrderStorage::Lazy(LazyOrderStorage::new_in(&mut engine.state.trail))
}
};
let direct_encoding = match direct_literals {
LiteralStrategy::Eager => {
let card = orig_domain_len
.expect("unable to create literals eagerly for domains that exceed usize::MAX");
engine.state.statistics.eager_literals += (card - 2) as u64;
DirectStorage::Eager(self.sat.new_var_range(card - 2))
}
LiteralStrategy::Lazy => DirectStorage::Lazy(FxHashMap::default()),
};
drop(engine);
if let OrderStorage::Eager { storage, .. } = &order_encoding {
let mut direct_enc_iter = if let DirectStorage::Eager(vars) = &direct_encoding {
Some(*vars)
} else {
None
}
.into_iter()
.flatten();
for (ord_i, ord_j) in (*storage).tuple_windows() {
let ord_i: RawLit = ord_i.into(); let ord_j: RawLit = ord_j.into(); self.sat.add_clause([!ord_i, ord_j]).unwrap(); if matches!(direct_encoding, DirectStorage::Eager(_)) {
let eq_i: RawLit = direct_enc_iter.next().unwrap().into();
self.sat.add_clause([!eq_i, !ord_i]).unwrap(); self.sat.add_clause([!eq_i, ord_j]).unwrap(); self.sat.add_clause([eq_i, ord_i, !ord_j]).unwrap(); }
}
debug_assert!(direct_enc_iter.next().is_none());
}
let mut engine = self.engine.borrow_mut();
engine.state.int_vars.push(IntDecision {
direct_encoding,
domain,
order_encoding,
upper_bound,
});
let iv = Decision((engine.state.int_vars.len() - 1) as u32);
engine.state.int_activation.push(Default::default());
debug_assert_eq!(
engine.state.int_vars.len(),
engine.state.int_activation.len()
);
if let OrderStorage::Eager { storage, .. } = engine.state.int_vars[iv.idx()].order_encoding
{
let mut vars = storage;
if let DirectStorage::Eager(vars2) = &engine.state.int_vars[iv.idx()].direct_encoding {
debug_assert_eq!(Into::<i32>::into(vars.end()) + 1, vars2.start().into());
vars = VarRange::new(vars.start(), vars2.end());
}
engine.state.bool_to_int.insert_eager(vars, iv);
engine
.state
.trail
.grow_to_boolvar(vars.clone().next_back().unwrap());
for l in vars {
self.sat.add_observed_var(l);
}
}
iv.into()
}
pub fn set_search_strategy(&mut self, strategy: SearchStrategy) {
self.engine.borrow_mut().state.set_search_strategy(strategy);
}
pub fn solver_statistics(&self) -> SolverStatistics {
let cp_stats = &self.engine.borrow().state.statistics;
SolverStatistics {
conflicts: cp_stats.conflicts,
sat_search_directives: cp_stats.sat_search_directives,
peak_depth: cp_stats.peak_depth,
cp_propagator_calls: cp_stats.propagations,
restarts: cp_stats.restarts,
user_search_directives: cp_stats.user_search_directives,
eager_literals: cp_stats.eager_literals,
lazy_literals: cp_stats.lazy_literals,
}
}
}
impl<Sat: TerminateCallback> Solver<Sat> {
pub fn set_terminate_callback<F: FnMut() -> TerminationSignal + 'static>(
&mut self,
cb: Option<F>,
) {
self.sat.set_terminate_callback(cb);
}
}
impl<Sat: LearnCallback> Solver<Sat> {
pub fn set_learn_callback<F: FnMut(&mut dyn Iterator<Item = Decision<bool>>) + 'static>(
&mut self,
cb: Option<F>,
) {
if let Some(mut f) = cb {
self.sat
.set_learn_callback(Some(move |clause: &mut dyn Iterator<Item = RawLit>| {
trace_learned_clause(clause);
let mut clause = clause.map(Decision);
f(&mut clause);
}));
} else {
self.sat.set_learn_callback(Some(trace_learned_clause));
}
}
}
impl<Sat: ExternalPropagation> BrancherInitActions for Solver<Sat> {
fn ensure_decidable<T: DefaultView>(&mut self, view: impl Into<View<T>>) {
let view: View<T> = view.into();
let any: &dyn Any = &view;
if let Some(view) = any.downcast_ref::<View<bool>>() {
match view.0 {
BoolView::Lit(var) => {
let var = var.0.var();
self.engine.borrow_mut().state.trail.grow_to_boolvar(var);
self.sat.add_observed_var(var);
}
BoolView::Const(_) => {}
}
} else if let Some(view) = any.downcast_ref::<View<IntVal>>() {
match view.0 {
IntView::Bool(LinearBoolView { var, .. }) => {
let var = var.0.var();
self.engine.borrow_mut().state.trail.grow_to_boolvar(var);
self.sat.add_observed_var(var);
}
_ => {
}
}
} else {
unreachable!()
}
}
fn push_brancher(&mut self, brancher: BoxedBrancher) {
self.engine.borrow_mut().branchers.push(brancher);
}
}
impl Clone for Solver<Cadical> {
fn clone(&self) -> Self {
let mut sat = self.sat.shallow_clone();
let engine: Engine = self.engine.borrow().clone();
let engine = Rc::new(RefCell::new(engine));
sat.connect_propagator(Rc::clone(&engine));
for var in sat.emitted_vars() {
if self.sat.is_observed(var.into()) {
sat.add_observed_var(var);
}
}
Solver { sat, engine }
}
}
impl<Sat: ExternalPropagation> ConstructionActions for Solver<Sat> {
fn new_trailed<T: Bytes>(&mut self, init: T) -> Trailed<T> {
self.engine.borrow_mut().state.trail.track(init)
}
}
impl<Sat: ExternalPropagation> DecisionActions for Solver<Sat> {
fn num_conflicts(&self) -> u64 {
self.engine.borrow().state.statistics.conflicts
}
}
impl<Sat: Default + ExternalPropagation + LearnCallback> Default for Solver<Sat> {
fn default() -> Self {
let mut sat = Sat::default();
let engine = Rc::default();
sat.set_learn_callback(Some(trace_learned_clause));
sat.connect_propagator(Rc::clone(&engine));
Self { sat, engine }
}
}
impl<Sat: ExternalPropagation> PostingActions for Solver<Sat> {
fn add_clause(
&mut self,
clause: impl IntoIterator<Item = Self::Atom>,
) -> Result<(), Self::Conflict> {
Solver::add_clause(self, clause)
}
fn add_propagator(&mut self, propagator: BoxedPropagator) {
self.add_propagator(propagator, false);
}
}
impl<Sat> ReasoningContext for Solver<Sat> {
type Atom = <Engine as ReasoningEngine>::Atom;
type Conflict = <Engine as ReasoningEngine>::Conflict;
}
impl<Sat> TrailingActions for Solver<Sat> {
fn set_trailed<T: Bytes>(&mut self, i: Trailed<T>, v: T) -> T {
self.engine.borrow_mut().state.set_trailed(i, v)
}
fn trailed<T: Bytes>(&self, i: Trailed<T>) -> T {
self.engine.borrow().state.trailed(i)
}
}
impl Add for SolverStatistics {
type Output = SolverStatistics;
fn add(mut self, other: SolverStatistics) -> SolverStatistics {
self += other;
self
}
}
impl AddAssign for SolverStatistics {
fn add_assign(&mut self, other: SolverStatistics) {
self.conflicts += other.conflicts;
self.sat_search_directives += other.sat_search_directives;
self.peak_depth = self.peak_depth.max(other.peak_depth);
self.cp_propagator_calls += other.cp_propagator_calls;
self.restarts += other.restarts;
self.user_search_directives += other.user_search_directives;
self.eager_literals = self.eager_literals.max(other.eager_literals);
self.lazy_literals = self.lazy_literals.max(other.lazy_literals);
}
}
impl<'a, Sat, State> SolverNewIntDecisionBuilder<'a, Sat, State>
where
Sat: ExternalPropagation,
State: solver_new_int_decision_builder::State,
{
pub fn decision(self) -> Decision<IntVal>
where
State: solver_new_int_decision_builder::IsComplete,
{
let IntView::Linear(lin_view) = self.view().0 else {
panic!("domain did not contain at least three values");
};
debug_assert_eq!(lin_view.offset, 0);
debug_assert_eq!(lin_view.scale.get(), 1);
lin_view.var
}
}