use crate::bitscan::pop_lowest_bit;
use crate::constraint::{ConstraintEnum, Revision, VarId};
use crate::domain::Domain;
use crate::solver::Trail;
use crate::solver::adjacency::Adjacency;
use crate::variable::Variable;
use crate::{SolveStats, Unsatisfiable};
pub(crate) struct BitsetWorklist {
words: Vec<u64>,
capacity: usize,
}
impl BitsetWorklist {
pub(crate) fn new(capacity: usize) -> Self {
Self {
words: vec![0; capacity.div_ceil(64)],
capacity,
}
}
fn clear(&mut self) {
self.words.fill(0);
}
fn fill_full(&mut self) {
self.words.fill(u64::MAX);
let remainder = self.capacity % 64;
if remainder != 0
&& let Some(last) = self.words.last_mut()
{
*last = (1u64 << remainder) - 1;
}
}
fn insert(&mut self, idx: usize) {
self.words[idx / 64] |= 1u64 << (idx % 64);
}
fn contains(&self, idx: usize) -> bool {
self.words[idx / 64] & (1u64 << (idx % 64)) != 0
}
fn pop(&mut self) -> Option<usize> {
for (wi, word) in self.words.iter_mut().enumerate() {
if let Some(bit) = pop_lowest_bit(word) {
return Some(wi * 64 + bit);
}
}
None
}
}
pub(crate) fn ac3_full<D: Domain>(
variables: &mut [Variable<D>],
constraints: &[ConstraintEnum<D>],
adjacency: &Adjacency,
stats: &mut SolveStats,
worklist: &mut BitsetWorklist,
depth: usize,
) -> Result<(), Unsatisfiable>
where
D::Value: PartialEq + 'static,
{
worklist.fill_full();
while let Some(idx) = worklist.pop() {
match constraints[idx].revise(variables, depth) {
Revision::Unchanged => {}
Revision::Changed => {
stats.propagations += 1;
for &neighbor in adjacency.neighbors_of_constraint(idx) {
worklist.insert(neighbor as usize);
}
}
Revision::Unsatisfiable => return Err(Unsatisfiable),
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn ac3_from_variable<D: Domain>(
var: VarId,
variables: &mut [Variable<D>],
constraints: &[ConstraintEnum<D>],
adjacency: &Adjacency,
assignment: &[Option<D::Value>],
stats: &mut SolveStats,
trail: &mut Trail,
worklist: &mut BitsetWorklist,
depth: usize,
) -> Option<usize>
where
D::Value: PartialEq + 'static,
{
worklist.clear();
for &ci in adjacency.constraints_for(var) {
let ci = ci as usize;
let scope = constraints[ci].scope();
if scope
.iter()
.any(|&v| v != var && assignment[v as usize].is_none())
{
worklist.insert(ci);
}
}
while let Some(idx) = worklist.pop() {
match constraints[idx].revise(variables, depth) {
Revision::Unchanged => {}
Revision::Changed => {
stats.propagations += 1;
let scope = constraints[idx].scope();
for &v in scope {
trail.push(v);
}
if scope
.iter()
.any(|&v| variables[v as usize].domain.is_empty())
{
return Some(idx);
}
for &neighbor in adjacency.neighbors_of_constraint(idx) {
let n = neighbor as usize;
if !worklist.contains(n) {
let scope = constraints[n].scope();
if scope.iter().any(|&v| assignment[v as usize].is_none()) {
worklist.insert(n);
}
}
}
}
Revision::Unsatisfiable => {
let scope = constraints[idx].scope();
for &v in scope {
trail.push(v);
}
return Some(idx);
}
}
}
None
}