use std::{
fmt::{Debug, Display, Formatter},
iter,
num::NonZeroU32,
ops::ControlFlow,
};
use elsa::FrozenMap;
use crate::solver::conditions::Disjunction;
use crate::{
Interner, NameId, Requirement,
internal::{
arena::{Arena, ArenaId},
id::{ClauseId, LearntClauseId, StringId, VersionSetId},
},
solver::{
VariableId, conditions::DisjunctionId, decision_map::DecisionMap,
decision_tracker::DecisionTracker, variable_map::VariableMap,
},
};
#[derive(Copy, Clone, Debug)]
pub(crate) enum Clause {
InstallRoot,
Requires(VariableId, Option<DisjunctionId>, Requirement),
ForbidMultipleInstances(VariableId, Literal, NameId),
Constrains(VariableId, VariableId, VersionSetId),
Lock(VariableId, VariableId),
Learnt(LearntClauseId),
Excluded(VariableId, StringId),
AnyOf(VariableId, VariableId),
}
impl Clause {
fn requires(
parent: VariableId,
requirement: Requirement,
candidates: impl IntoIterator<Item = VariableId>,
condition: Option<(DisjunctionId, &[Literal])>,
decision_tracker: &DecisionTracker,
) -> (Self, Option<[Literal; 2]>, bool) {
assert_ne!(decision_tracker.assigned_value(parent), Some(false));
let kind = Clause::Requires(parent, condition.map(|d| d.0), requirement);
let condition_literals = condition
.into_iter()
.flat_map(|(_, candidates)| candidates)
.copied()
.peekable();
let candidate_literals = candidates
.into_iter()
.map(|candidate| candidate.positive())
.peekable();
let mut literals = condition_literals.chain(candidate_literals).peekable();
let Some(&first_literal) = literals.peek() else {
return (kind, None, false);
};
match literals.find(|&c| c.eval(decision_tracker.map()) != Some(false)) {
Some(watched_candidate) => (kind, Some([parent.negative(), watched_candidate]), false),
None => {
(kind, Some([parent.negative(), first_literal]), true)
}
}
}
fn constrains(
parent: VariableId,
forbidden_solvable: VariableId,
via: VersionSetId,
decision_tracker: &DecisionTracker,
) -> (Self, Option<[Literal; 2]>, bool) {
assert_ne!(decision_tracker.assigned_value(parent), Some(false));
let conflict = decision_tracker.assigned_value(forbidden_solvable) == Some(true);
(
Clause::Constrains(parent, forbidden_solvable, via),
Some([parent.negative(), forbidden_solvable.negative()]),
conflict,
)
}
fn forbid_multiple(
candidate: VariableId,
constrained_candidate: Literal,
name: NameId,
) -> (Self, Option<[Literal; 2]>) {
(
Clause::ForbidMultipleInstances(candidate, constrained_candidate, name),
Some([candidate.negative(), constrained_candidate]),
)
}
fn root() -> (Self, Option<[Literal; 2]>) {
(Clause::InstallRoot, None)
}
fn exclude(candidate: VariableId, reason: StringId) -> (Self, Option<[Literal; 2]>) {
(Clause::Excluded(candidate, reason), None)
}
fn lock(
locked_candidate: VariableId,
other_candidate: VariableId,
) -> (Self, Option<[Literal; 2]>) {
(
Clause::Lock(locked_candidate, other_candidate),
Some([VariableId::root().negative(), other_candidate.negative()]),
)
}
fn learnt(
learnt_clause_id: LearntClauseId,
literals: &[Literal],
) -> (Self, Option<[Literal; 2]>) {
debug_assert!(!literals.is_empty());
(
Clause::Learnt(learnt_clause_id),
if literals.len() == 1 {
None
} else {
Some([*literals.first().unwrap(), *literals.last().unwrap()])
},
)
}
fn any_of(selected_var: VariableId, other_var: VariableId) -> (Self, Option<[Literal; 2]>) {
let kind = Clause::AnyOf(selected_var, other_var);
(kind, Some([selected_var.positive(), other_var.negative()]))
}
pub fn try_fold_literals<B, C, F>(
&self,
learnt_clauses: &Arena<LearntClauseId, Vec<Literal>>,
requirements_to_sorted_candidates: &FrozenMap<
Requirement,
Vec<Vec<VariableId>>,
ahash::RandomState,
>,
disjunction_to_candidates: &Arena<DisjunctionId, Disjunction>,
init: C,
mut visit: F,
) -> ControlFlow<B, C>
where
F: FnMut(C, Literal) -> ControlFlow<B, C>,
{
match *self {
Clause::InstallRoot => unreachable!(),
Clause::Excluded(solvable, _) => visit(init, solvable.negative()),
Clause::Learnt(learnt_id) => learnt_clauses[learnt_id]
.iter()
.copied()
.try_fold(init, visit),
Clause::Requires(solvable_id, disjunction, match_spec_id) => {
iter::once(solvable_id.negative())
.chain(
disjunction
.into_iter()
.flat_map(|d| disjunction_to_candidates[d].literals.iter())
.copied(),
)
.chain(
requirements_to_sorted_candidates[&match_spec_id]
.iter()
.flatten()
.map(|&s| s.positive()),
)
.try_fold(init, visit)
}
Clause::Constrains(s1, s2, _) => [s1.negative(), s2.negative()]
.into_iter()
.try_fold(init, visit),
Clause::ForbidMultipleInstances(s1, s2, _) => {
[s1.negative(), s2].into_iter().try_fold(init, visit)
}
Clause::Lock(_, s) => [s.negative(), VariableId::root().negative()]
.into_iter()
.try_fold(init, visit),
Clause::AnyOf(selected, variable) => [selected.positive(), variable.negative()]
.into_iter()
.try_fold(init, visit),
}
}
pub fn visit_literals(
&self,
learnt_clauses: &Arena<LearntClauseId, Vec<Literal>>,
requirements_to_sorted_candidates: &FrozenMap<
Requirement,
Vec<Vec<VariableId>>,
ahash::RandomState,
>,
disjunction_to_candidates: &Arena<DisjunctionId, Disjunction>,
mut visit: impl FnMut(Literal),
) {
let _ = self.try_fold_literals(
learnt_clauses,
requirements_to_sorted_candidates,
disjunction_to_candidates,
(),
|_, lit| {
visit(lit);
ControlFlow::<()>::Continue(())
},
);
}
pub fn display<'i, I: Interner>(
&self,
variable_map: &'i VariableMap,
interner: &'i I,
) -> ClauseDisplay<'i, I> {
ClauseDisplay {
kind: *self,
variable_map,
interner,
}
}
}
#[derive(Clone)]
pub(crate) struct WatchedLiterals {
pub watched_literals: [Literal; 2],
pub(crate) next_watches: [Option<ClauseId>; 2],
}
impl WatchedLiterals {
pub fn root() -> (Option<Self>, Clause) {
let (kind, watched_literals) = Clause::root();
(Self::from_kind_and_initial_watches(watched_literals), kind)
}
pub fn requires(
candidate: VariableId,
requirement: Requirement,
matching_candidates: impl IntoIterator<Item = VariableId>,
condition: Option<(DisjunctionId, &[Literal])>,
decision_tracker: &DecisionTracker,
) -> (Option<Self>, bool, Clause) {
let (kind, watched_literals, conflict) = Clause::requires(
candidate,
requirement,
matching_candidates,
condition,
decision_tracker,
);
(
Self::from_kind_and_initial_watches(watched_literals),
conflict,
kind,
)
}
pub fn constrains(
candidate: VariableId,
constrained_package: VariableId,
requirement: VersionSetId,
decision_tracker: &DecisionTracker,
) -> (Option<Self>, bool, Clause) {
let (kind, watched_literals, conflict) = Clause::constrains(
candidate,
constrained_package,
requirement,
decision_tracker,
);
(
Self::from_kind_and_initial_watches(watched_literals),
conflict,
kind,
)
}
pub fn lock(
locked_candidate: VariableId,
other_candidate: VariableId,
) -> (Option<Self>, Clause) {
let (kind, watched_literals) = Clause::lock(locked_candidate, other_candidate);
(Self::from_kind_and_initial_watches(watched_literals), kind)
}
pub fn forbid_multiple(
candidate: VariableId,
other_candidate: Literal,
name: NameId,
) -> (Option<Self>, Clause) {
let (kind, watched_literals) = Clause::forbid_multiple(candidate, other_candidate, name);
(Self::from_kind_and_initial_watches(watched_literals), kind)
}
pub fn learnt(
learnt_clause_id: LearntClauseId,
literals: &[Literal],
) -> (Option<Self>, Clause) {
let (kind, watched_literals) = Clause::learnt(learnt_clause_id, literals);
(Self::from_kind_and_initial_watches(watched_literals), kind)
}
pub fn exclude(candidate: VariableId, reason: StringId) -> (Option<Self>, Clause) {
let (kind, watched_literals) = Clause::exclude(candidate, reason);
(Self::from_kind_and_initial_watches(watched_literals), kind)
}
pub fn any_of(selected_var: VariableId, other_var: VariableId) -> (Option<Self>, Clause) {
let (kind, watched_literals) = Clause::any_of(selected_var, other_var);
(Self::from_kind_and_initial_watches(watched_literals), kind)
}
fn from_kind_and_initial_watches(watched_literals: Option<[Literal; 2]>) -> Option<Self> {
let watched_literals = watched_literals?;
debug_assert!(watched_literals[0] != watched_literals[1]);
Some(Self {
watched_literals,
next_watches: [None, None],
})
}
pub fn next_unwatched_literal(
&self,
clause: &Clause,
learnt_clauses: &Arena<LearntClauseId, Vec<Literal>>,
requirement_to_sorted_candidates: &FrozenMap<
Requirement,
Vec<Vec<VariableId>>,
ahash::RandomState,
>,
disjunction_to_candidates: &Arena<DisjunctionId, Disjunction>,
decision_map: &DecisionMap,
for_watch_index: usize,
) -> Option<Literal> {
let other_watch_index = 1 - for_watch_index;
match clause {
Clause::InstallRoot => unreachable!(),
Clause::Excluded(_, _) => unreachable!(),
Clause::Constrains(..) | Clause::ForbidMultipleInstances(..) | Clause::Lock(..) => {
None
}
clause => {
let next = clause.try_fold_literals(
learnt_clauses,
requirement_to_sorted_candidates,
disjunction_to_candidates,
(),
|_, lit| {
if self.watched_literals[other_watch_index] != lit
&& lit.eval(decision_map).unwrap_or(true)
{
ControlFlow::Break(lit)
} else {
ControlFlow::Continue(())
}
},
);
match next {
ControlFlow::Break(lit) => Some(lit),
ControlFlow::Continue(_) => None,
}
}
}
}
}
#[repr(transparent)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) struct Literal(NonZeroU32);
impl Literal {
pub fn new(variable: VariableId, negate: bool) -> Self {
let variable_idx = variable.to_usize();
let encoded_literal = variable_idx << 1 | negate as usize;
Self::from_usize(encoded_literal)
}
}
impl ArenaId for Literal {
fn from_usize(x: usize) -> Self {
let idx: u32 = (x + 1).try_into().expect("watched literal id too big");
unsafe { Literal(NonZeroU32::new_unchecked(idx)) }
}
fn to_usize(self) -> usize {
self.0.get() as usize - 1
}
}
impl Literal {
pub fn negate(&self) -> bool {
(self.0.get() & 1) == 0
}
pub(crate) fn satisfying_value(self) -> bool {
!self.negate()
}
#[inline]
pub(crate) fn variable(self) -> VariableId {
VariableId::from_usize(self.to_usize() >> 1)
}
#[inline(always)]
pub(crate) fn eval(self, decision_map: &DecisionMap) -> Option<bool> {
decision_map
.value(self.variable())
.map(|value| value != self.negate())
}
}
impl VariableId {
pub fn positive(self) -> Literal {
Literal::new(self, false)
}
pub fn negative(self) -> Literal {
Literal::new(self, true)
}
}
pub(crate) struct ClauseDisplay<'i, I: Interner> {
kind: Clause,
interner: &'i I,
variable_map: &'i VariableMap,
}
impl<I: Interner> Display for ClauseDisplay<'_, I> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.kind {
Clause::InstallRoot => write!(f, "InstallRoot"),
Clause::Excluded(variable, reason) => {
write!(
f,
"Excluded({}({:?}), {})",
variable.display(self.variable_map, self.interner),
variable,
self.interner.display_string(reason)
)
}
Clause::Learnt(learnt_id) => write!(f, "Learnt({learnt_id:?})"),
Clause::Requires(variable, condition, requirement) => {
write!(
f,
"Requires({}({:?}), {}, condition={:?})",
variable.display(self.variable_map, self.interner),
variable,
requirement.display(self.interner),
condition
)
}
Clause::Constrains(v1, v2, version_set_id) => {
write!(
f,
"Constrains({}({:?}), {}({:?}), {})",
v1.display(self.variable_map, self.interner),
v1,
v2.display(self.variable_map, self.interner),
v2,
self.interner.display_version_set(version_set_id)
)
}
Clause::ForbidMultipleInstances(v1, v2, name) => {
write!(
f,
"ForbidMultipleInstances({}({:?}), {}({:?}), {})",
v1.display(self.variable_map, self.interner),
v1,
v2.variable().display(self.variable_map, self.interner),
v2,
self.interner.display_name(name)
)
}
Clause::Lock(locked, other) => {
write!(
f,
"Lock({}({:?}), {}({:?}))",
locked.display(self.variable_map, self.interner),
locked,
other.display(self.variable_map, self.interner),
other,
)
}
Clause::AnyOf(variable, other) => {
write!(
f,
"AnyOf({}({:?}), {}({:?}))",
variable.display(self.variable_map, self.interner),
variable,
other.display(self.variable_map, self.interner),
other,
)
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{internal::arena::ArenaId, solver::decision::Decision};
#[test]
#[allow(clippy::bool_assert_comparison)]
fn test_literal_satisfying_value() {
let lit = VariableId::root().negative();
assert_eq!(lit.satisfying_value(), false);
let lit = VariableId::root().positive();
assert_eq!(lit.satisfying_value(), true);
}
#[test]
fn test_literal_eval() {
let mut decision_map = DecisionMap::default();
let literal = VariableId::root().positive();
let negated_literal = VariableId::root().negative();
assert_eq!(literal.eval(&decision_map), None);
assert_eq!(negated_literal.eval(&decision_map), None);
decision_map.set(VariableId::root(), true, 1);
assert_eq!(literal.eval(&decision_map), Some(true));
assert_eq!(negated_literal.eval(&decision_map), Some(false));
decision_map.set(VariableId::root(), false, 1);
assert_eq!(literal.eval(&decision_map), Some(false));
assert_eq!(negated_literal.eval(&decision_map), Some(true));
}
#[test]
fn test_requires_with_and_without_conflict() {
let mut decisions = DecisionTracker::default();
let parent = VariableId::from_usize(1);
let candidate1 = VariableId::from_usize(2);
let candidate2 = VariableId::from_usize(3);
let (clause, conflict, _kind) = WatchedLiterals::requires(
parent,
VersionSetId::from_usize(0).into(),
[candidate1, candidate2],
None,
&decisions,
);
assert!(!conflict);
assert_eq!(
clause.as_ref().unwrap().watched_literals[0].variable(),
parent
);
assert_eq!(
clause.unwrap().watched_literals[1].variable(),
candidate1.into()
);
decisions
.try_add_decision(
Decision::new(candidate1.into(), false, ClauseId::from_usize(0)),
1,
)
.unwrap();
let (clause, conflict, _kind) = WatchedLiterals::requires(
parent,
VersionSetId::from_usize(0).into(),
[candidate1, candidate2],
None,
&decisions,
);
assert!(!conflict);
assert_eq!(
clause.as_ref().unwrap().watched_literals[0].variable(),
parent
);
assert_eq!(
clause.as_ref().unwrap().watched_literals[1].variable(),
candidate2.into()
);
decisions
.try_add_decision(
Decision::new(candidate2.into(), false, ClauseId::install_root()),
1,
)
.unwrap();
let (clause, conflict, _kind) = WatchedLiterals::requires(
parent,
VersionSetId::from_usize(0).into(),
[candidate1, candidate2],
None,
&decisions,
);
assert!(conflict);
assert_eq!(
clause.as_ref().unwrap().watched_literals[0].variable(),
parent
);
assert_eq!(
clause.as_ref().unwrap().watched_literals[1].variable(),
candidate1.into()
);
decisions
.try_add_decision(Decision::new(parent, false, ClauseId::install_root()), 1)
.unwrap();
let panicked = std::panic::catch_unwind(|| {
WatchedLiterals::requires(
parent,
VersionSetId::from_usize(0).into(),
[candidate1, candidate2],
None,
&decisions,
)
})
.is_err();
assert!(panicked);
}
#[test]
fn test_constrains_with_and_without_conflict() {
let mut decisions = DecisionTracker::default();
let parent = VariableId::from_usize(1);
let forbidden = VariableId::from_usize(2);
let (clause, conflict, _kind) =
WatchedLiterals::constrains(parent, forbidden, VersionSetId::from_usize(0), &decisions);
assert!(!conflict);
assert_eq!(
clause.as_ref().unwrap().watched_literals[0].variable(),
parent
);
assert_eq!(
clause.as_ref().unwrap().watched_literals[1].variable(),
forbidden
);
decisions
.try_add_decision(Decision::new(forbidden, true, ClauseId::install_root()), 1)
.unwrap();
let (clause, conflict, _kind) =
WatchedLiterals::constrains(parent, forbidden, VersionSetId::from_usize(0), &decisions);
assert!(conflict);
assert_eq!(
clause.as_ref().unwrap().watched_literals[0].variable(),
parent
);
assert_eq!(
clause.as_ref().unwrap().watched_literals[1].variable(),
forbidden
);
decisions
.try_add_decision(Decision::new(parent, false, ClauseId::install_root()), 1)
.unwrap();
let panicked = std::panic::catch_unwind(|| {
WatchedLiterals::constrains(parent, forbidden, VersionSetId::from_usize(0), &decisions)
})
.is_err();
assert!(panicked);
}
#[test]
fn test_watched_literals_size() {
assert_eq!(std::mem::size_of::<WatchedLiterals>(), 16);
}
#[test]
fn test_literal_size() {
assert_eq!(std::mem::size_of::<Literal>(), 4);
assert_eq!(
std::mem::size_of::<Literal>(),
std::mem::size_of::<Option<Literal>>()
);
assert_eq!(
std::mem::size_of::<Literal>() * 2,
std::mem::size_of::<[Literal; 2]>()
);
assert_eq!(
std::mem::size_of::<Literal>() * 2,
std::mem::size_of::<[Option<Literal>; 2]>()
);
assert_eq!(
std::mem::size_of::<Literal>() * 2,
std::mem::size_of::<Option<[Literal; 2]>>()
);
}
#[test]
fn test_watched_literal_size() {
assert_eq!(std::mem::size_of::<WatchedLiterals>(), 16);
assert_eq!(
std::mem::size_of::<Option<WatchedLiterals>>(),
std::mem::size_of::<WatchedLiterals>()
);
}
}