macro_rules! trace_new_lit {
($iv:expr, $def:expr, $lit:expr) => {
tracing::trace!(
target: "literal",
lit = i32::from($lit),
int_var = $iv.ident(),
is_eq = matches!($def.meaning, IntLitMeaning::Eq(_)),
val = match $def.meaning {
IntLitMeaning::Eq(val) => val,
IntLitMeaning::Less(val) => val,
_ => unreachable!(),
},
"register new literal"
);
};
}
use std::{collections::VecDeque, mem};
use pindakaas::{
Lit as RawLit, Var as RawVar,
solver::propagation::{
ClausePersistence, Propagator as PropagatorExtension,
PropagatorDefinition as PropagatorExtensionDefinition, SearchDecision, SolvingActions,
},
};
use rustc_hash::FxHashMap;
pub(crate) use trace_new_lit;
use tracing::{debug, trace, warn};
use crate::{
Clause, IntVal,
actions::{
BoolInspectionActions, IntEvent, ReasoningContext, ReasoningEngine, Trailed,
TrailingActions,
},
constraints::{BoxedPropagator, Conflict, DeferredReason, Reason},
helpers::bytes::Bytes,
solver::{
IntLitMeaning, SearchStrategy, SwitchTrigger,
activation_list::{ActivationAction, ActivationActionS, ActivationList},
bool_to_int::BoolToIntMap,
branchers::{BoxedBrancher, Directive},
decision::{
Decision,
integer::{IntDecision, OrderStorage},
},
initialization_context::InitializationContext,
queue::PropagatorQueue,
solving_context::SolvingContext,
trail::Trail,
view::{View, boolean::BoolView},
},
};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) struct AdvRef(u32);
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub(crate) struct AdvisorDef {
pub(crate) bool2int: bool,
pub(crate) data: u64,
pub(crate) negated: bool,
pub(crate) propagator: PropRef,
}
#[derive(Clone, Debug, Default)]
pub struct Engine {
pub(crate) propagators: Vec<BoxedPropagator>,
pub(crate) branchers: Vec<BoxedBrancher>,
pub(crate) state: State,
}
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub(crate) struct EngineStatistics {
pub(crate) conflicts: u64,
pub(crate) sat_search_directives: u64,
pub(crate) peak_depth: u32,
pub(crate) propagations: u64,
pub(crate) restarts: u32,
pub(crate) user_search_directives: u64,
pub(crate) eager_literals: u64,
pub(crate) lazy_literals: u64,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct LitPropagation {
pub(crate) lit: RawLit,
pub(crate) reason: Result<Reason<Decision<bool>>, bool>,
pub(crate) event: Option<(Decision<IntVal>, IntEvent)>,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) struct PropRef(u32);
#[derive(Clone, Debug, Default)]
pub struct State {
pub(crate) search_strategy: SearchStrategy,
pub(crate) int_vars: Vec<IntDecision>,
pub(crate) bool_to_int: BoolToIntMap,
pub(crate) trail: Trail,
pub(crate) propagation_queue: VecDeque<LitPropagation>,
pub(crate) reason_map: FxHashMap<RawLit, Reason<Decision<bool>>>,
pub(crate) conflict: Option<Conflict<Decision<bool>>>,
pub(crate) failed: bool,
pub(crate) clauses: VecDeque<Clause<RawLit>>,
pub(crate) statistics: EngineStatistics,
pub(crate) sat_search: bool,
pub(crate) search_trigger: u64,
pub(crate) advisors: Vec<AdvisorDef>,
pub(crate) notify_of_backtrack: Vec<PropRef>,
pub(crate) bool_activation: FxHashMap<RawVar, Vec<ActivationActionS>>,
pub(crate) int_activation: Vec<ActivationList>,
pub(crate) propagator_queue: PropagatorQueue,
last_propagated: Option<(RawLit, Option<(Decision<IntVal>, IntEvent)>)>,
#[cfg(debug_assertions)]
pub(crate) check_int_fixed: Vec<(Decision<IntVal>, IntVal)>,
}
impl AdvRef {
pub(crate) fn from_raw(raw: u32) -> Self {
debug_assert!(raw <= i32::MAX as u32);
Self(raw)
}
pub(crate) fn index(&self) -> usize {
self.0 as usize
}
pub(crate) fn new(index: usize) -> Self {
debug_assert!(index <= i32::MAX as usize);
Self(index as u32)
}
pub(crate) fn raw(&self) -> u32 {
self.0
}
}
impl Engine {
#[cfg(debug_assertions)]
fn debug_check_reason(&mut self, lit: RawLit) {
use rustc_hash::FxHashSet;
use crate::actions::BoolInspectionActions;
if let Some(reason) = self.state.reason_map.get(&lit).cloned() {
if let Reason::Lazy(_) = reason {
self.state.trail.goto_assign_lit(lit);
}
let clause: Clause<_> =
reason.explain(&mut self.propagators, &mut self.state, Some(Decision(lit)));
let mut seen = FxHashSet::default();
for &l in &clause {
if seen.contains(&!l) {
tracing::error!(
target: "solver",
clause = ?clause.iter().map(|&l| i32::from(l)).collect::<Vec<_>>(),
lit_explained = i32::from(lit),
lit_pos = i32::from(!l),
lit_neg = i32::from(l),
"invalid reason: literal and its negation in clause"
);
debug_assert!(
false,
"Both {l} and {} are found in the Reason for {lit}",
!l
);
}
seen.insert(l);
if l == lit {
continue;
}
let val = Decision::<bool>(!l).val(&self.state.trail);
if !val.unwrap_or(false) {
tracing::error!(
target: "solver",
clause = ?clause.iter().map(|&l| i32::from(l)).collect::<Vec<_>>(),
lit_explained = i32::from(lit),
lit_invalid = i32::from(!l),
invalid_val = ?val,
"invalid reason: not all antecedents are known true"
);
}
debug_assert!(
val.unwrap_or(false),
"Literal {} in Reason for {lit} is {val:?}, but should be known true",
!l,
);
}
if let Reason::Lazy(_) = reason {
self.state.trail.reset_to_trail_head();
}
} else {
debug_assert_eq!(
self.state.decision_level(),
0,
"Literal {lit} propagated without reason at non-zero decision level",
);
}
}
fn notify_backtrack<const ARTIFICIAL: bool>(&mut self, new_level: usize, restart: bool) {
self.state
.notify_backtrack::<ARTIFICIAL>(new_level, restart);
let notify = mem::take(&mut self.state.notify_of_backtrack);
for &p in ¬ify {
self.propagators[p.index()].advise_of_backtrack(&mut self.state);
}
self.state.notify_of_backtrack = notify;
}
pub(crate) fn notify_int_advisor(
&mut self,
prop: PropRef,
event: IntEvent,
data: u64,
negated: bool,
) -> bool {
let event = match event {
IntEvent::LowerBound if negated => IntEvent::UpperBound,
IntEvent::UpperBound if negated => IntEvent::LowerBound,
e => e,
};
self.propagators[prop.index()].advise_of_int_change(&mut self.state, data, event)
}
pub(crate) fn notify_lit_advisor(&mut self, prop: PropRef, data: u64, bool2int: bool) -> bool {
if bool2int {
self.propagators[prop.index()].advise_of_int_change(
&mut self.state,
data,
IntEvent::Fixed,
)
} else {
self.propagators[prop.index()].advise_of_bool_change(&mut self.state, data)
}
}
}
impl PropagatorExtension for Engine {
fn add_external_clause(
&mut self,
slv: &mut dyn SolvingActions,
) -> Option<(Clause<RawLit>, ClausePersistence)> {
if !self.state.clauses.is_empty() {
let clause = self.state.clauses.pop_front(); trace!(
target: "solver",
clause = ?clause.as_ref().unwrap().iter().map(|&x| i32::from(x)).collect::<Vec<i32>>(),
"add external clause"
);
clause.map(|c| (c, ClausePersistence::Irreduntant))
} else if !self.state.propagation_queue.is_empty() {
None } else if let Some(conflict) = self.state.conflict.take() {
let ctx = SolvingContext::new(slv, &mut self.state);
let clause: Clause<_> =
conflict
.reason
.explain(&mut self.propagators, ctx.state, conflict.subject);
debug!(
target: "solver",
clause = ?clause.iter().map(|&x| i32::from(x)).collect::<Vec<i32>>(),
"add conflict clause"
);
Some((clause, ClausePersistence::Forgettable))
} else {
None
}
}
fn add_reason_clause(&mut self, propagated_lit: RawLit) -> Clause<RawLit> {
let reason = self.state.reason_map.remove(&propagated_lit);
let clause = if let Some(reason) = reason {
if matches!(reason, Reason::Lazy(_)) {
self.state.trail.goto_assign_lit(propagated_lit);
}
reason.explain(
&mut self.propagators,
&mut self.state,
Some(Decision(propagated_lit)),
)
} else {
vec![propagated_lit]
};
debug!(
target: "solver",
clause = ?clause.iter().map(|&x| i32::from(x)).collect::<Vec<i32>>(),
"add reason clause"
);
clause
}
#[tracing::instrument(target = "solver", level = "debug", skip(self, slv, _sol))]
fn check_solution(
&mut self,
slv: &mut dyn SolvingActions,
_sol: &dyn pindakaas::Valuation,
) -> bool {
use crate::actions::IntDecisionActions;
debug_assert!(!self.state.failed);
debug_assert!(self.state.conflict.is_none());
debug_assert!(self.state.propagation_queue.is_empty());
let level = self.state.decision_level();
self.state.notify_new_decision_level();
let mut ctx = SolvingContext::new(slv, &mut self.state);
for r in (0..ctx.state.int_vars.len()).map(|v| Decision(v as u32)) {
let (lb, ub) = ctx.state.int_vars[r.idx()].bounds(&ctx.state.trail);
if lb != ub {
debug_assert!(matches!(
ctx.state.int_vars[r.idx()].order_encoding,
OrderStorage::Lazy(_)
));
let ub_lit = r.lit(&mut ctx, IntLitMeaning::Less(lb + 1));
if let BoolView::Lit(ub_lit) = ub_lit.0 {
let prev = ctx.state.trail.assign_lit(ub_lit.0);
debug_assert_eq!(prev, None);
}
ctx.state.int_vars[r.idx()].notify_upper_bound(&mut ctx.state.trail, lb);
let activation = mem::take(&mut ctx.state.int_activation[r.idx()]);
activation.for_each_activated_by(IntEvent::Fixed, |action| {
let prop = match action {
ActivationAction::Advise::<AdvRef, _>(adv) => {
let &AdvisorDef {
data, propagator, ..
} = &ctx.state.advisors[adv.index()];
if !self.propagators[propagator.index()].advise_of_int_change(
ctx.state,
data,
IntEvent::Fixed,
) {
return;
}
propagator
}
ActivationAction::Enqueue(prop) => prop,
};
ctx.state.propagator_queue.enqueue_propagator(prop.raw());
});
ctx.state.int_activation[r.idx()] = activation;
}
}
ctx.run_propagators(&mut self.propagators);
debug_assert!(self.state.propagation_queue.is_empty());
let conflict = self.state.conflict.take().map(|c| {
if let Reason::Lazy(DeferredReason {
propagator: prop,
data,
}) = c.reason
{
let reason = self.propagators[prop as usize].explain(
&mut self.state,
c.subject.map(View::from).unwrap_or(true.into()),
data,
);
Conflict {
subject: c.subject,
reason: match Reason::from_view(Reason::from_iter(reason)) {
Err(false) => panic!("invalid lazy reason"), Err(true) => Reason::Eager(Vec::new().into_boxed_slice()),
Ok(r) => r,
},
}
} else {
c
}
});
self.notify_backtrack::<true>(level as usize, false);
debug_assert!(self.state.conflict.is_none());
self.state.conflict = conflict;
let accept = self.state.conflict.is_none();
debug!(target: "solver", accept, "check model");
accept
}
fn decide(&mut self, slv: &mut dyn SolvingActions) -> SearchDecision {
if !self.state.sat_search {
let mut current = self.state.trail.trailed(Trail::CURRENT_BRANCHER);
if current == self.branchers.len() {
self.state.statistics.sat_search_directives += 1;
return SearchDecision::Free;
}
let mut ctx = SolvingContext::new(slv, &mut self.state);
while current < self.branchers.len() {
match self.branchers[current].decide(&mut ctx) {
Directive::Select(lit) => {
let BoolView::Lit(lit) = lit.0 else {
panic!("brancher yielded an already fixed literal");
};
debug_assert!(
lit.val(&ctx).is_none(),
"brancher yielded an already fixed literal"
);
debug!(target: "solver", lit = i32::from(lit.0), "decide");
self.state.statistics.user_search_directives += 1;
return SearchDecision::Assign(lit.0);
}
Directive::Exhausted => {
current += 1;
ctx.set_trailed(Trail::CURRENT_BRANCHER, current);
}
Directive::Consumed => {
self.branchers.remove(current);
}
}
}
}
self.state.statistics.sat_search_directives += 1;
SearchDecision::Free
}
fn notify_assignments(&mut self, lits: &[RawLit]) {
debug!(
target: "solver",
lits = ?lits.iter().map(|&x| i32::from(x)).collect::<Vec<i32>>(),
"assignments"
);
self.state.trail.reset_to_trail_head();
for &lit in lits {
let iv_event = match self.state.trail.assign_lit(lit) {
Some(false) => {
self.state.failed = true;
continue;
}
Some(true) => match self.state.last_propagated {
Some((prev, event)) if lit == prev => {
self.state.last_propagated = None;
event
}
_ => self
.state
.propagation_queue
.iter()
.position(|event| event.lit == lit)
.and_then(|pos| self.state.propagation_queue.remove(pos))
.and_then(|event| event.event),
},
None => None,
};
if !self.state.failed
&& let Some(activations) = self
.state
.bool_activation
.get_mut(&lit.var())
.map(mem::take)
{
for &action in &activations {
let prop = match action.into() {
ActivationAction::Advise::<AdvRef, _>(adv) => {
let &AdvisorDef {
bool2int,
data,
propagator,
..
} = &self.state.advisors[adv.index()];
let enqueue = self.notify_lit_advisor(propagator, data, bool2int);
if !enqueue {
continue;
}
propagator
}
ActivationAction::Enqueue(prop) => prop,
};
self.state.propagator_queue.enqueue_propagator(prop.raw());
}
*self.state.bool_activation.get_mut(&lit.var()).unwrap() = activations;
}
let iv_event = iv_event.or_else(|| {
let (iv, meaning) = self.state.get_int_lit_meaning(Decision(lit))?;
let (lb, ub) = self.state.int_vars[iv.idx()].bounds(&self.state);
let event = match meaning {
IntLitMeaning::Eq(val) if val == lb && val == ub => None,
IntLitMeaning::Eq(val) if val < lb || val > ub => {
trace!(
target: "solver",
lit = i32::from(lit),
lb,
ub,
"invalid eq notification"
);
None
}
IntLitMeaning::Eq(val) => {
#[cfg(debug_assertions)]
{
self.state.check_int_fixed.push((iv, val));
}
if val > lb {
self.state.int_vars[iv.idx()]
.notify_lower_bound(&mut self.state.trail, val);
}
if val < ub {
self.state.int_vars[iv.idx()]
.notify_upper_bound(&mut self.state.trail, val);
}
Some(IntEvent::Fixed)
}
IntLitMeaning::NotEq(i) if i < lb || i > ub => None,
IntLitMeaning::NotEq(_) => Some(IntEvent::Domain),
IntLitMeaning::GreaterEq(new_lb) if new_lb <= lb => None,
IntLitMeaning::GreaterEq(new_lb) => {
trace!(target: "solver", lit = i32::from(lit), lb = new_lb, "new lb");
self.state.int_vars[iv.idx()]
.notify_lower_bound(&mut self.state.trail, new_lb);
Some(if new_lb == ub {
IntEvent::Fixed
} else {
IntEvent::LowerBound
})
}
IntLitMeaning::Less(i) => {
let new_ub = i - 1;
if new_ub < ub {
trace!(
target: "solver",
lit = i32::from(lit),
ub = new_ub,
"new ub"
);
self.state.int_vars[iv.idx()]
.notify_upper_bound(&mut self.state.trail, new_ub);
Some(if new_ub == lb {
IntEvent::Fixed
} else {
IntEvent::UpperBound
})
} else {
None
}
}
}?;
Some((iv, event))
});
if !self.state.failed
&& let Some((iv, event)) = iv_event
{
let activations = mem::take(&mut self.state.int_activation[iv.idx()]);
activations.for_each_activated_by(event, |action| {
let prop = match action {
ActivationAction::Advise::<AdvRef, _>(adv) => {
let &AdvisorDef {
negated,
data,
propagator,
..
} = &self.state.advisors[adv.index()];
let enqueue = self.notify_int_advisor(propagator, event, data, negated);
if !enqueue {
return;
}
propagator
}
ActivationAction::Enqueue(prop) => prop,
};
self.state.propagator_queue.enqueue_propagator(prop.raw());
});
self.state.int_activation[iv.idx()] = activations;
}
}
}
fn notify_backtrack(&mut self, new_level: usize, restart: bool) {
debug!(target: "solver", new_level, restart, "backtrack");
self.notify_backtrack::<false>(new_level, restart);
}
fn notify_new_decision_level(&mut self) {
debug_assert!(!self.state.failed);
debug_assert!(self.state.conflict.is_none());
debug_assert!(self.state.propagation_queue.is_empty());
trace!(target: "solver", "new decision level");
self.state.notify_new_decision_level();
let new_level = self.state.decision_level();
if new_level > self.state.statistics.peak_depth {
self.state.statistics.peak_depth = new_level;
}
}
#[tracing::instrument(
target = "solver",
level = "debug",
skip(self, slv),
fields(level = self.state.decision_level())
)]
fn propagate(&mut self, slv: &mut dyn SolvingActions) -> Option<RawLit> {
debug_assert!(self.state.last_propagated.is_none());
if !self.state.clauses.is_empty() {
return None;
}
if self.state.propagation_queue.is_empty() && self.state.conflict.is_none() {
#[cfg(debug_assertions)]
{
use crate::actions::{BoolInspectionActions, IntInspectionActions};
for (iv, i) in mem::take(&mut self.state.check_int_fixed) {
debug_assert_eq!(iv.val(&self.state), Some(i));
let lb_lit = iv
.try_lit(&self.state, IntLitMeaning::GreaterEq(i))
.unwrap();
let ub_lit = iv.try_lit(&self.state, IntLitMeaning::Less(i + 1)).unwrap();
debug_assert_eq!(lb_lit.val(&self.state), Some(true));
debug_assert_eq!(ub_lit.val(&self.state), Some(true));
}
}
SolvingContext::new(slv, &mut self.state).run_propagators(&mut self.propagators);
}
if !self.state.clauses.is_empty() {
return None;
}
if let Some(LitPropagation { lit, reason, event }) =
self.state.propagation_queue.pop_front()
{
debug!(target: "solver", lit = i32::from(lit), "propagate");
debug_assert!(self.state.trail.sat_value(lit).is_some());
self.state.register_reason(lit, reason);
#[cfg(debug_assertions)]
{
self.debug_check_reason(lit);
}
self.state.last_propagated = Some((lit, event));
Some(lit)
} else {
None
}
}
}
impl PropagatorExtensionDefinition for Engine {
const CHECK_ONLY: bool = false;
const REASON_PERSISTENCE: ClausePersistence = ClausePersistence::Forgettable;
}
impl ReasoningEngine for Engine {
type Atom = View<bool>;
type Conflict = Conflict<Decision<bool>>;
type ExplanationContext<'a> = State;
type InitializationContext<'a> = InitializationContext<'a>;
type NotificationContext<'a> = State;
type PropagationContext<'a> = SolvingContext<'a>;
}
impl PropRef {
pub(crate) const INVALID: PropRef = PropRef(i32::MAX as u32);
pub(crate) fn from_raw(raw: u32) -> Self {
debug_assert!(raw <= i32::MAX as u32);
Self(raw)
}
pub(crate) fn index(&self) -> usize {
self.0 as usize
}
pub(crate) fn new(index: usize) -> Self {
debug_assert!(index <= i32::MAX as usize);
Self(index as u32)
}
pub(crate) fn raw(&self) -> u32 {
self.0
}
}
impl State {
fn decision_level(&self) -> u32 {
self.trail.decision_level()
}
pub(crate) fn get_int_lit_meaning(
&self,
lit: Decision<bool>,
) -> Option<(Decision<IntVal>, IntLitMeaning)> {
let (iv, meaning) = self.bool_to_int.get(lit.0.var())?;
let meaning = match meaning {
None => self.int_vars[iv.idx()].lit_meaning(lit),
Some(IntLitMeaning::Less(i)) if !lit.is_negated() => {
let i = self.int_vars[iv.idx()].tighten_less_lit(i);
IntLitMeaning::Less(i)
}
Some(m) if lit.is_negated() => !m,
Some(m) => m,
};
Some((iv, meaning))
}
fn notify_backtrack<const ARTIFICIAL: bool>(&mut self, level: usize, restart: bool) {
debug_assert!(!ARTIFICIAL || level as u32 == self.trail.decision_level() - 1);
debug_assert!(!ARTIFICIAL || !restart);
self.failed = false;
self.conflict = None;
self.last_propagated = None;
self.propagation_queue.clear();
#[cfg(debug_assertions)]
{
self.check_int_fixed.clear();
}
self.trail.notify_backtrack(level);
while self.propagator_queue.pop().is_some() {}
if ARTIFICIAL {
return;
}
self.statistics.conflicts += 1;
if let SearchStrategy::Interleaved(SwitchTrigger::Conflicts(cfl))
| SearchStrategy::Transition(SwitchTrigger::Conflicts(cfl)) = self.search_strategy
{
self.search_trigger += 1;
if self.search_trigger >= cfl {
self.sat_search = !self.sat_search;
self.search_trigger = 0;
debug!(
target: "solver",
sat_search = self.sat_search,
conflicts = self.statistics.conflicts,
"change search strategy after reaching conflict threshold"
);
if let SearchStrategy::Transition(_) = self.search_strategy {
self.search_strategy = SearchStrategy::Sat;
}
}
}
if restart {
self.statistics.restarts += 1;
if let SearchStrategy::Interleaved(SwitchTrigger::Restarts(rst))
| SearchStrategy::Transition(SwitchTrigger::Restarts(rst)) = self.search_strategy
{
self.search_trigger += 1;
if self.search_trigger >= rst {
self.sat_search = !self.sat_search;
self.search_trigger = 0;
debug!(
target: "solver",
sat_search = self.sat_search,
restarts = self.statistics.restarts,
"change search strategy after reaching restart threshold"
);
if let SearchStrategy::Transition(_) = self.search_strategy {
self.search_strategy = SearchStrategy::Sat;
}
}
}
if level == 0 {
self.reason_map.clear();
}
}
}
fn notify_new_decision_level(&mut self) {
self.trail.notify_new_decision_level();
}
pub(crate) fn register_reason(
&mut self,
lit: RawLit,
built_reason: Result<Reason<Decision<bool>>, bool>,
) {
match built_reason {
Ok(reason) => {
self.reason_map.insert(lit, reason);
}
Err(true) => {
self.reason_map.remove(&lit);
}
Err(false) => unreachable!("invalid reason"),
}
}
pub(crate) fn set_search_strategy(&mut self, strategy: SearchStrategy) {
self.search_strategy = strategy;
self.sat_search = matches!(self.search_strategy, SearchStrategy::Sat);
self.search_trigger = 0;
}
}
impl ReasoningContext for State {
type Atom = <Engine as ReasoningEngine>::Atom;
type Conflict = <Engine as ReasoningEngine>::Conflict;
}
impl TrailingActions for State {
fn set_trailed<T: Bytes>(&mut self, x: Trailed<T>, v: T) -> T {
self.trail.set_trailed(x, v)
}
fn trailed<T: Bytes>(&self, x: Trailed<T>) -> T {
self.trail.trailed(x)
}
}
#[cfg(test)]
mod tests {
use pindakaas::solver::propagation::Propagator as ExternalPropagator;
use crate::{
IntVal,
actions::{
BoolPropagationActions, InitActions, IntDecisionActions, IntEvent, IntInitActions,
IntPropCond, IntPropagationActions, ReasoningEngine,
},
constraints::Propagator,
solver::{
BoolView, Decision, IntLitMeaning, LiteralStrategy, Solver, View, engine::Engine,
},
};
#[test]
fn queued_integer_event_survives_sat_assignment() {
use std::{cell::RefCell, rc::Rc};
#[derive(Clone, Debug)]
struct ProducerAndListener {
req_first: Decision<bool>,
notifications: Rc<RefCell<usize>>,
ge_1_second: View<IntVal>,
done: bool,
}
impl Propagator<Engine> for ProducerAndListener {
fn initialize(
&mut self,
ctx: &mut <Engine as ReasoningEngine>::InitializationContext<'_>,
) {
ctx.enqueue_now(true);
self.ge_1_second
.advise_when(ctx, IntPropCond::LowerBound, 0);
}
fn advise_of_int_change(
&mut self,
_: &mut <Engine as ReasoningEngine>::NotificationContext<'_>,
data: u64,
event: IntEvent,
) -> bool {
assert_eq!(data, 0);
assert_eq!(event, IntEvent::LowerBound);
*self.notifications.borrow_mut() += 1;
false
}
fn propagate(
&mut self,
ctx: &mut <Engine as ReasoningEngine>::PropagationContext<'_>,
) -> Result<(), <Engine as ReasoningEngine>::Conflict> {
assert!(!self.done);
self.done = true;
self.req_first.require(ctx, [])?;
self.ge_1_second.tighten_min(ctx, 1, [])?;
Ok(())
}
}
let mut slv: Solver = Solver::default();
let notifications = Rc::new(RefCell::new(0));
let imply = slv.new_bool_decision();
let var = slv
.new_int_decision(0..=2)
.order_literals(LiteralStrategy::Eager)
.view();
slv.add_propagator(
Box::new(ProducerAndListener {
req_first: imply,
notifications: Rc::clone(¬ifications),
ge_1_second: var,
done: false,
}),
false,
);
let ge_view = var.lit(&mut slv, IntLitMeaning::GreaterEq(1));
let BoolView::Lit(ge) = ge_view.0 else {
unreachable!()
};
slv.add_clause([(!imply).into(), ge_view]).unwrap();
let (mut actions, mut engine) = slv.as_parts_mut();
let propagated = ExternalPropagator::propagate(&mut *engine, &mut actions);
assert_eq!(propagated, Some(imply.0));
assert_eq!(engine.state.propagation_queue.len(), 1);
assert_eq!(engine.state.propagation_queue[0].lit, ge.0);
ExternalPropagator::notify_assignments(&mut *engine, &[imply.0, ge.0]);
assert_eq!(*notifications.borrow(), 1);
let propagated = ExternalPropagator::propagate(&mut *engine, &mut actions);
assert_eq!(propagated, None);
assert_eq!(*notifications.borrow(), 1);
}
}