1mod matching;
36
37use std::any::Any;
38use std::cell::RefCell;
39use std::collections::HashMap;
40use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
41
42use crate::constraint::traits::{Revision, VarId};
43use crate::domain::Domain;
44use crate::variable::Variable;
45
46use matching::{NONE, hopcroft_karp, reset_adj, tarjan_scc};
47
48pub static GAC_CORE_CALLS: AtomicU64 = AtomicU64::new(0);
50
51static NEXT_GAC_ID: AtomicU32 = AtomicU32::new(0);
53
54pub fn next_gac_id() -> u32 {
56 NEXT_GAC_ID.fetch_add(1, Ordering::Relaxed)
57}
58
59pub const GAC_MIN_PARTICIPANTS: usize = 3;
63
64pub static GAC_IN_ALLDIFF_ENABLED: AtomicBool = AtomicBool::new(true);
68
69const CACHE_CAP: usize = 8192;
72
73struct GacScratch<V> {
82 participants: Vec<usize>,
83 has_sentinel: Vec<bool>,
84 assigned_ns: Vec<V>,
85 all_vals: Vec<V>,
86 adj: Vec<Vec<u32>>,
87 match_u: Vec<u32>,
88 match_v: Vec<u32>,
89 dist: Vec<u32>,
90 queue: Vec<u32>,
91 res_adj: Vec<Vec<u32>>,
92 reachable: Vec<bool>,
93 bfs: Vec<u32>,
94 t_index: Vec<u32>,
95 t_lowlink: Vec<u32>,
96 t_onstack: Vec<bool>,
97 t_scc: Vec<u32>,
98 t_stack: Vec<u32>,
99 t_call: Vec<(u32, u32)>,
100 cache: HashMap<u32, Vec<Option<V>>>,
101}
102
103impl<V> Default for GacScratch<V> {
104 fn default() -> Self {
105 Self {
106 participants: Vec::new(),
107 has_sentinel: Vec::new(),
108 assigned_ns: Vec::new(),
109 all_vals: Vec::new(),
110 adj: Vec::new(),
111 match_u: Vec::new(),
112 match_v: Vec::new(),
113 dist: Vec::new(),
114 queue: Vec::new(),
115 res_adj: Vec::new(),
116 reachable: Vec::new(),
117 bfs: Vec::new(),
118 t_index: Vec::new(),
119 t_lowlink: Vec::new(),
120 t_onstack: Vec::new(),
121 t_scc: Vec::new(),
122 t_stack: Vec::new(),
123 t_call: Vec::new(),
124 cache: HashMap::new(),
125 }
126 }
127}
128
129thread_local! {
130 static SCRATCH: RefCell<Box<dyn Any>> = RefCell::new(Box::new(()));
134}
135
136fn with_scratch<V, R>(f: impl FnOnce(&mut GacScratch<V>) -> R) -> R
137where
138 V: 'static,
139{
140 SCRATCH.with(|slot| {
141 let mut b = slot.borrow_mut();
142 if !b.is::<GacScratch<V>>() {
143 *b = Box::new(GacScratch::<V>::default());
144 }
145 let s = b.downcast_mut::<GacScratch<V>>().unwrap();
146 f(s)
147 })
148}
149
150pub fn propagate_gac_core<D: Domain>(
164 scope: &[VarId],
165 sentinel: Option<&D::Value>,
166 variables: &mut [Variable<D>],
167 depth: usize,
168 gac_id: Option<u32>,
169) -> Revision
170where
171 D::Value: 'static,
172{
173 GAC_CORE_CALLS.fetch_add(1, Ordering::Relaxed);
174 with_scratch::<D::Value, _>(|s| propagate_inner(scope, sentinel, variables, depth, gac_id, s))
175}
176
177fn propagate_inner<D: Domain>(
178 scope: &[VarId],
179 sentinel: Option<&D::Value>,
180 variables: &mut [Variable<D>],
181 depth: usize,
182 gac_id: Option<u32>,
183 s: &mut GacScratch<D::Value>,
184) -> Revision {
185 s.assigned_ns.clear();
187 for &v in scope {
188 let dom = &variables[v as usize].domain;
189 if dom.is_empty() {
190 return Revision::Unsatisfiable;
191 }
192 if let Some(val) = dom.singleton_value()
193 && sentinel != Some(&val)
194 {
195 s.assigned_ns.push(val);
196 }
197 }
198
199 s.participants.clear();
200 s.has_sentinel.clear();
201 for (i, &v) in scope.iter().enumerate() {
202 let dom = &variables[v as usize].domain;
203 if dom.is_singleton() {
204 continue;
205 }
206 let dom_has_sentinel = sentinel.map(|sv| dom.contains(sv)).unwrap_or(false);
207 let non_sentinel_count = dom.size() - dom_has_sentinel as usize;
208 if non_sentinel_count == 0 {
209 continue;
211 }
212 s.participants.push(i);
213 s.has_sentinel.push(dom_has_sentinel);
214 }
215
216 if s.participants.is_empty() {
217 return Revision::Unchanged;
218 }
219 let n_vars = s.participants.len();
220
221 if sentinel.is_none() && n_vars < GAC_MIN_PARTICIPANTS {
224 return Revision::Unchanged;
225 }
226
227 s.all_vals.clear();
229 reset_adj(&mut s.adj, n_vars);
230 for pu in 0..n_vars {
231 let var_id = scope[s.participants[pu]] as usize;
232 for val in variables[var_id].domain.iter() {
233 if sentinel == Some(&val) {
234 continue;
235 }
236 if s.assigned_ns.contains(&val) {
237 continue;
238 }
239 let vi = match s.all_vals.iter().position(|x| *x == val) {
240 Some(k) => k as u32,
241 None => {
242 s.all_vals.push(val);
243 (s.all_vals.len() - 1) as u32
244 }
245 };
246 s.adj[pu].push(vi);
247 }
248 }
249
250 if s.all_vals.is_empty() {
252 return finish_all_consumed::<D>(scope, sentinel, variables, depth, s);
253 }
254 let n_vals = s.all_vals.len();
255
256 s.match_u.clear();
258 s.match_u.resize(n_vars, NONE);
259 s.match_v.clear();
260 s.match_v.resize(n_vals, NONE);
261 s.dist.clear();
262 s.dist.resize(n_vars, 0);
263
264 if let Some(id) = gac_id
265 && let Some(cvec) = s.cache.get(&id)
266 {
267 for pu in 0..n_vars {
268 let Some(Some(cv)) = cvec.get(s.participants[pu]) else {
269 continue;
270 };
271 let Some(vi) = s.all_vals.iter().position(|x| x == cv) else {
272 continue;
273 };
274 let vi = vi as u32;
275 if s.match_v[vi as usize] == NONE && s.adj[pu].contains(&vi) {
276 s.match_u[pu] = vi;
277 s.match_v[vi as usize] = pu as u32;
278 }
279 }
280 }
281
282 hopcroft_karp(
283 n_vars,
284 n_vals,
285 &s.adj,
286 &mut s.match_u,
287 &mut s.match_v,
288 &mut s.dist,
289 &mut s.queue,
290 );
291
292 for pu in 0..n_vars {
294 if s.match_u[pu] == NONE && !s.has_sentinel[pu] {
295 return Revision::Unsatisfiable;
296 }
297 }
298
299 if let Some(id) = gac_id {
301 if s.cache.len() > CACHE_CAP {
302 s.cache.clear();
303 }
304 let cvec = s.cache.entry(id).or_default();
305 cvec.clear();
306 cvec.resize(scope.len(), None);
307 for pu in 0..n_vars {
308 if s.match_u[pu] != NONE {
309 cvec[s.participants[pu]] = Some(s.all_vals[s.match_u[pu] as usize].clone());
310 }
311 }
312 }
313
314 let total_nodes = n_vars + n_vals;
316 reset_adj(&mut s.res_adj, total_nodes);
317 for u in 0..n_vars {
318 let matched_vi = s.match_u[u];
319 for &vi in &s.adj[u] {
320 let val_node = (n_vars as u32) + vi;
321 if vi == matched_vi {
322 s.res_adj[val_node as usize].push(u as u32);
323 } else {
324 s.res_adj[u].push(val_node);
325 }
326 }
327 }
328
329 s.reachable.clear();
331 s.reachable.resize(total_nodes, false);
332 s.bfs.clear();
333 for vi in 0..n_vals {
334 if s.match_v[vi] == NONE {
335 let node = n_vars + vi;
336 s.reachable[node] = true;
337 s.bfs.push(node as u32);
338 }
339 }
340 for pu in 0..n_vars {
341 if s.match_u[pu] == NONE && s.has_sentinel[pu] {
342 s.reachable[pu] = true;
343 s.bfs.push(pu as u32);
344 }
345 }
346 let mut head = 0;
347 while head < s.bfs.len() {
348 let node = s.bfs[head] as usize;
349 head += 1;
350 for i in 0..s.res_adj[node].len() {
351 let next = s.res_adj[node][i] as usize;
352 if !s.reachable[next] {
353 s.reachable[next] = true;
354 s.bfs.push(next as u32);
355 }
356 }
357 }
358
359 resize_tarjan(s, total_nodes);
361 tarjan_scc(
362 total_nodes,
363 &s.res_adj,
364 &mut s.t_index,
365 &mut s.t_lowlink,
366 &mut s.t_onstack,
367 &mut s.t_scc,
368 &mut s.t_stack,
369 &mut s.t_call,
370 );
371
372 let mut changed = false;
374
375 if !s.assigned_ns.is_empty() {
378 for pu in 0..n_vars {
379 let var_id = scope[s.participants[pu]] as usize;
380 for k in 0..s.assigned_ns.len() {
381 let val = s.assigned_ns[k].clone();
382 if variables[var_id].prune(&val, depth) {
383 changed = true;
384 }
385 }
386 if variables[var_id].domain.is_empty() {
387 return Revision::Unsatisfiable;
388 }
389 }
390 }
391
392 for pu in 0..n_vars {
395 let var_id = scope[s.participants[pu]] as usize;
396 let matched_vi = s.match_u[pu];
397 for i in 0..s.adj[pu].len() {
398 let vi = s.adj[pu][i];
399 if vi == matched_vi {
400 continue;
401 }
402 let val_node = n_vars + vi as usize;
403 if s.t_scc[pu] == s.t_scc[val_node] || s.reachable[val_node] {
404 continue;
405 }
406 let val = s.all_vals[vi as usize].clone();
407 if variables[var_id].prune(&val, depth) {
408 changed = true;
409 }
410 }
411 if variables[var_id].domain.is_empty() {
412 return Revision::Unsatisfiable;
413 }
414 }
415
416 if changed {
417 Revision::Changed
418 } else {
419 Revision::Unchanged
420 }
421}
422
423fn finish_all_consumed<D: Domain>(
426 scope: &[VarId],
427 sentinel: Option<&D::Value>,
428 variables: &mut [Variable<D>],
429 depth: usize,
430 s: &mut GacScratch<D::Value>,
431) -> Revision {
432 if sentinel.is_none() {
434 return Revision::Unsatisfiable;
435 }
436 for pu in 0..s.participants.len() {
440 if !s.has_sentinel[pu] {
441 return Revision::Unsatisfiable;
442 }
443 }
444 let mut changed = false;
445 for pu in 0..s.participants.len() {
446 let var_id = scope[s.participants[pu]] as usize;
447 for k in 0..s.assigned_ns.len() {
448 let val = s.assigned_ns[k].clone();
449 if variables[var_id].prune(&val, depth) {
450 changed = true;
451 }
452 }
453 if variables[var_id].domain.is_empty() {
454 return Revision::Unsatisfiable;
455 }
456 }
457 if changed {
458 Revision::Changed
459 } else {
460 Revision::Unchanged
461 }
462}
463
464fn resize_tarjan<V>(s: &mut GacScratch<V>, n: usize) {
465 s.t_index.resize(n, NONE);
466 s.t_lowlink.resize(n, 0);
467 s.t_onstack.clear();
468 s.t_onstack.resize(n, false);
469 s.t_scc.resize(n, NONE);
470}