mod matching;
mod scratch;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
use crate::constraint::traits::{Revision, VarId};
use crate::domain::Domain;
use crate::variable::Variable;
use matching::{NONE, hopcroft_karp, tarjan_scc};
use scratch::{GacScratch, fast_index, resize_tarjan, with_scratch};
pub(crate) static GAC_CORE_CALLS: AtomicU64 = AtomicU64::new(0);
static NEXT_GAC_ID: AtomicU32 = AtomicU32::new(0);
pub fn next_gac_id() -> u32 {
NEXT_GAC_ID.fetch_add(1, Ordering::Relaxed)
}
pub const GAC_MIN_PARTICIPANTS: usize = 3;
pub static GAC_IN_ALLDIFF_ENABLED: AtomicBool = AtomicBool::new(true);
const CACHE_CAP: usize = 8192;
pub fn propagate_gac_core<D: Domain>(
scope: &[VarId],
sentinel: Option<&D::Value>,
variables: &mut [Variable<D>],
depth: usize,
gac_id: Option<u32>,
) -> Revision
where
D::Value: 'static,
{
GAC_CORE_CALLS.fetch_add(1, Ordering::Relaxed);
with_scratch::<D::Value, _>(|s| propagate_inner(scope, sentinel, variables, depth, gac_id, s))
}
fn propagate_inner<D: Domain>(
scope: &[VarId],
sentinel: Option<&D::Value>,
variables: &mut [Variable<D>],
depth: usize,
gac_id: Option<u32>,
s: &mut GacScratch<D::Value>,
) -> Revision
where
D::Value: 'static,
{
s.cur_gen = s.cur_gen.wrapping_add(1);
if s.cur_gen == 0 {
s.val_index_gen.iter_mut().for_each(|g| *g = 0);
s.assigned_mark.iter_mut().for_each(|g| *g = 0);
s.cur_gen = 1;
}
s.assigned_ns.clear();
for &v in scope {
let dom = &variables[v as usize].domain;
if dom.is_empty() {
return Revision::Unsatisfiable;
}
if let Some(val) = dom.singleton_value()
&& sentinel != Some(&val)
{
if let Some(k) = fast_index(&val) {
if k >= s.assigned_mark.len() {
s.assigned_mark.resize(k + 1, 0);
}
s.assigned_mark[k] = s.cur_gen;
}
s.assigned_ns.push(val);
}
}
s.participants.clear();
s.has_sentinel.clear();
for (i, &v) in scope.iter().enumerate() {
let dom = &variables[v as usize].domain;
if dom.is_singleton() {
continue;
}
let dom_has_sentinel = sentinel.map(|sv| dom.contains(sv)).unwrap_or(false);
let non_sentinel_count = dom.size() - dom_has_sentinel as usize;
if non_sentinel_count == 0 {
continue;
}
s.participants.push(i);
s.has_sentinel.push(dom_has_sentinel);
}
if s.participants.is_empty() {
return Revision::Unchanged;
}
let n_vars = s.participants.len();
if sentinel.is_none() && n_vars < GAC_MIN_PARTICIPANTS {
return Revision::Unchanged;
}
s.all_vals.clear();
s.adj.begin();
for pu in 0..n_vars {
let var_id = scope[s.participants[pu]] as usize;
for val in variables[var_id].domain.iter() {
if sentinel == Some(&val) {
continue;
}
let is_assigned = match fast_index(&val) {
Some(k) if k < s.assigned_mark.len() => s.assigned_mark[k] == s.cur_gen,
Some(_) => false,
None => s.assigned_ns.contains(&val),
};
if is_assigned {
continue;
}
let vi = match fast_index(&val) {
Some(k) => {
if k >= s.val_index.len() {
s.val_index.resize(k + 1, 0);
s.val_index_gen.resize(k + 1, 0);
}
if s.val_index_gen[k] == s.cur_gen {
s.val_index[k]
} else {
let idx = s.all_vals.len() as u32;
s.all_vals.push(val);
s.val_index[k] = idx;
s.val_index_gen[k] = s.cur_gen;
idx
}
}
None => match s.all_vals.iter().position(|x| *x == val) {
Some(k) => k as u32,
None => {
s.all_vals.push(val);
(s.all_vals.len() - 1) as u32
}
},
};
s.adj.push(vi);
}
s.adj.finish_row();
}
if s.all_vals.is_empty() {
return finish_all_consumed::<D>(scope, sentinel, variables, depth, s);
}
let n_vals = s.all_vals.len();
s.match_u.clear();
s.match_u.resize(n_vars, NONE);
s.match_v.clear();
s.match_v.resize(n_vals, NONE);
s.dist.clear();
s.dist.resize(n_vars, 0);
if let Some(id) = gac_id
&& (id as usize) < CACHE_CAP
&& let Some(Some(cvec)) = s.cache.get(id as usize)
{
for pu in 0..n_vars {
let Some(Some(cv)) = cvec.get(s.participants[pu]) else {
continue;
};
let vi = match fast_index(cv) {
Some(k) if k < s.val_index.len() && s.val_index_gen[k] == s.cur_gen => {
s.val_index[k]
}
Some(_) => continue,
None => match s.all_vals.iter().position(|x| x == cv) {
Some(k) => k as u32,
None => continue,
},
};
if s.match_v[vi as usize] == NONE && s.adj.row(pu).contains(&vi) {
s.match_u[pu] = vi;
s.match_v[vi as usize] = pu as u32;
}
}
}
hopcroft_karp(
n_vars,
n_vals,
&s.adj,
&mut s.match_u,
&mut s.match_v,
&mut s.dist,
&mut s.queue,
);
for pu in 0..n_vars {
if s.match_u[pu] == NONE && !s.has_sentinel[pu] {
return Revision::Unsatisfiable;
}
}
if let Some(id) = gac_id
&& (id as usize) < CACHE_CAP
{
let idx = id as usize;
if idx >= s.cache.len() {
s.cache.resize_with(idx + 1, || None);
}
let cvec = s.cache[idx].get_or_insert_with(Vec::new);
cvec.clear();
cvec.resize(scope.len(), None);
for pu in 0..n_vars {
if s.match_u[pu] != NONE {
cvec[s.participants[pu]] = Some(s.all_vals[s.match_u[pu] as usize].clone());
}
}
}
let total_nodes = n_vars + n_vals;
s.res_adj.begin();
for u in 0..n_vars {
let matched_vi = s.match_u[u];
for &vi in s.adj.row(u) {
if vi != matched_vi {
s.res_adj.push((n_vars as u32) + vi);
}
}
s.res_adj.finish_row();
}
for vi in 0..n_vals {
let mu = s.match_v[vi];
if mu != NONE {
s.res_adj.push(mu);
}
s.res_adj.finish_row();
}
s.reachable.clear();
s.reachable.resize(total_nodes, false);
s.bfs.clear();
for vi in 0..n_vals {
if s.match_v[vi] == NONE {
let node = n_vars + vi;
s.reachable[node] = true;
s.bfs.push(node as u32);
}
}
for pu in 0..n_vars {
if s.match_u[pu] == NONE && s.has_sentinel[pu] {
s.reachable[pu] = true;
s.bfs.push(pu as u32);
}
}
let mut head = 0;
while head < s.bfs.len() {
let node = s.bfs[head] as usize;
head += 1;
let row = s.res_adj.row(node);
for &next_node in row {
let next = next_node as usize;
if !s.reachable[next] {
s.reachable[next] = true;
s.bfs.push(next as u32);
}
}
}
resize_tarjan(s, total_nodes);
tarjan_scc(
total_nodes,
&s.res_adj,
&mut s.t_index,
&mut s.t_lowlink,
&mut s.t_onstack,
&mut s.t_scc,
&mut s.t_stack,
&mut s.t_call,
);
let mut changed = false;
if !s.assigned_ns.is_empty() {
for pu in 0..n_vars {
let var_id = scope[s.participants[pu]] as usize;
for k in 0..s.assigned_ns.len() {
let val = s.assigned_ns[k].clone();
if variables[var_id].prune(&val, depth) {
changed = true;
}
}
if variables[var_id].domain.is_empty() {
return Revision::Unsatisfiable;
}
}
}
for pu in 0..n_vars {
let var_id = scope[s.participants[pu]] as usize;
let matched_vi = s.match_u[pu];
for &vi in s.adj.row(pu) {
if vi == matched_vi {
continue;
}
let val_node = n_vars + vi as usize;
if s.t_scc[pu] == s.t_scc[val_node] || s.reachable[val_node] {
continue;
}
let val = s.all_vals[vi as usize].clone();
if variables[var_id].prune(&val, depth) {
changed = true;
}
}
if variables[var_id].domain.is_empty() {
return Revision::Unsatisfiable;
}
}
if changed {
Revision::Changed
} else {
Revision::Unchanged
}
}
fn finish_all_consumed<D: Domain>(
scope: &[VarId],
sentinel: Option<&D::Value>,
variables: &mut [Variable<D>],
depth: usize,
s: &mut GacScratch<D::Value>,
) -> Revision {
if sentinel.is_none() {
return Revision::Unsatisfiable;
}
for pu in 0..s.participants.len() {
if !s.has_sentinel[pu] {
return Revision::Unsatisfiable;
}
}
let mut changed = false;
for pu in 0..s.participants.len() {
let var_id = scope[s.participants[pu]] as usize;
for k in 0..s.assigned_ns.len() {
let val = s.assigned_ns[k].clone();
if variables[var_id].prune(&val, depth) {
changed = true;
}
}
if variables[var_id].domain.is_empty() {
return Revision::Unsatisfiable;
}
}
if changed {
Revision::Changed
} else {
Revision::Unchanged
}
}