1use crate::NQuin;
2
3pub const MAX_STABLE_MODELS: usize = 8;
4
5pub fn enumerate_stable_models(
8 base: &NQuin,
9 rules: &[NQuin],
10 out_worlds: &mut [u64; MAX_STABLE_MODELS],
11) -> usize {
12 if rules.is_empty() {
13 out_worlds[0] = base.context;
14 return 1;
15 }
16
17 let mut num_worlds = 1;
18 out_worlds[0] = base.context;
19
20 for rule in rules.iter().take(3) {
23 let current_worlds = num_worlds;
25 for w in 0..current_worlds {
26 if num_worlds < MAX_STABLE_MODELS {
27 out_worlds[num_worlds] = out_worlds[w] ^ rule.subject ^ rule.object;
29 num_worlds += 1;
30 }
31 }
32 }
33
34 num_worlds
35}
36
37pub const ASP_MAX_ATOMS: usize = 12; pub const ASP_MAX_BODY: usize = 6;
50
51#[derive(Clone, Copy)]
54pub struct AspRule {
55 pub head: u64,
56 pub pos: [u64; ASP_MAX_BODY],
57 pub pos_len: usize,
58 pub neg: [u64; ASP_MAX_BODY],
59 pub neg_len: usize,
60}
61
62impl AspRule {
63 pub fn new(head: u64, pos: &[u64], neg: &[u64]) -> Self {
64 let mut r = AspRule {
65 head,
66 pos: [0; ASP_MAX_BODY],
67 pos_len: 0,
68 neg: [0; ASP_MAX_BODY],
69 neg_len: 0,
70 };
71 for &a in pos.iter().take(ASP_MAX_BODY) {
72 r.pos[r.pos_len] = a;
73 r.pos_len += 1;
74 }
75 for &a in neg.iter().take(ASP_MAX_BODY) {
76 r.neg[r.neg_len] = a;
77 r.neg_len += 1;
78 }
79 r
80 }
81 pub fn fact(head: u64) -> Self {
82 Self::new(head, &[], &[])
83 }
84 pub fn constraint(pos: &[u64], neg: &[u64]) -> Self {
85 Self::new(0, pos, neg)
86 }
87}
88
89pub fn compute_answer_sets(atoms: &[u64], rules: &[AspRule], out: &mut [u64]) -> usize {
93 let n = atoms.len().min(ASP_MAX_ATOMS);
94 let idx = |a: u64| -> Option<usize> { atoms[..n].iter().position(|&x| x == a) };
95 let removed_by_reduct = |r: &AspRule, cand: u64| -> bool {
96 for &na in &r.neg[..r.neg_len] {
98 if let Some(ni) = idx(na) {
99 if cand & (1u64 << ni) != 0 {
100 return true;
101 }
102 }
103 }
104 false
105 };
106 let body_pos_in = |r: &AspRule, m: u64| -> bool {
107 for &pa in &r.pos[..r.pos_len] {
108 match idx(pa) {
109 Some(pi) if m & (1u64 << pi) != 0 => {}
110 _ => return false,
111 }
112 }
113 true
114 };
115
116 let mut found = 0usize;
117 let total: u64 = 1u64 << n;
118 for cand in 0..total {
119 let mut m: u64 = 0;
121 loop {
122 let mut changed = false;
123 for r in rules {
124 if r.head == 0 || removed_by_reduct(r, cand) {
125 continue;
126 }
127 if body_pos_in(r, m) {
128 if let Some(hi) = idx(r.head) {
129 if m & (1u64 << hi) == 0 {
130 m |= 1u64 << hi;
131 changed = true;
132 }
133 }
134 }
135 }
136 if !changed {
137 break;
138 }
139 }
140 if m != cand {
142 continue;
143 }
144 let mut ok = true;
146 for r in rules {
147 if r.head != 0 || removed_by_reduct(r, cand) {
148 continue;
149 }
150 if body_pos_in(r, m) {
151 ok = false;
152 break;
153 }
154 }
155 if ok {
156 if found >= out.len() {
157 break;
158 }
159 out[found] = m;
160 found += 1;
161 }
162 }
163 found
164}
165
166#[inline]
170pub fn atom_index(atoms: &[u64], a: u64) -> Option<usize> {
171 atoms.iter().take(ASP_MAX_ATOMS).position(|&x| x == a)
172}
173
174#[inline]
175fn body_holds(atoms: &[u64], model: u64, pos: &[u64], neg: &[u64]) -> bool {
176 for &p in pos {
177 match atom_index(atoms, p) {
178 Some(i) if model & (1u64 << i) != 0 => {}
179 _ => return false,
180 }
181 }
182 for &nn in neg {
183 if let Some(i) = atom_index(atoms, nn) {
184 if model & (1u64 << i) != 0 {
185 return false;
186 }
187 }
188 }
189 true
190}
191
192pub fn ground_rule(template: &AspRule, var: u64, domain: &[u64], out: &mut [AspRule]) -> usize {
199 let subst = |a: u64, d: u64| if a == var { d } else { a };
200 let mut n = 0usize;
201 for &d in domain {
202 if n >= out.len() {
203 break;
204 }
205 let mut g = *template;
206 g.head = subst(g.head, d);
207 for i in 0..g.pos_len {
208 g.pos[i] = subst(g.pos[i], d);
209 }
210 for i in 0..g.neg_len {
211 g.neg[i] = subst(g.neg[i], d);
212 }
213 out[n] = g;
214 n += 1;
215 }
216 n
217}
218
219#[derive(Clone, Copy)]
224pub struct WeakConstraint {
225 pub pos: [u64; ASP_MAX_BODY],
226 pub pos_len: usize,
227 pub neg: [u64; ASP_MAX_BODY],
228 pub neg_len: usize,
229 pub weight: i64,
230}
231
232impl WeakConstraint {
233 pub fn new(pos: &[u64], neg: &[u64], weight: i64) -> Self {
234 let mut w = WeakConstraint {
235 pos: [0; ASP_MAX_BODY],
236 pos_len: 0,
237 neg: [0; ASP_MAX_BODY],
238 neg_len: 0,
239 weight,
240 };
241 for &a in pos.iter().take(ASP_MAX_BODY) {
242 w.pos[w.pos_len] = a;
243 w.pos_len += 1;
244 }
245 for &a in neg.iter().take(ASP_MAX_BODY) {
246 w.neg[w.neg_len] = a;
247 w.neg_len += 1;
248 }
249 w
250 }
251}
252
253pub fn model_penalty(atoms: &[u64], model: u64, weak: &[WeakConstraint]) -> i64 {
256 let mut total = 0i64;
257 for w in weak {
258 if body_holds(atoms, model, &w.pos[..w.pos_len], &w.neg[..w.neg_len]) {
259 total += w.weight;
260 }
261 }
262 total
263}
264
265pub fn optimal_answer_set(
269 atoms: &[u64],
270 rules: &[AspRule],
271 weak: &[WeakConstraint],
272 buf: &mut [u64],
273) -> Option<(u64, i64)> {
274 let k = compute_answer_sets(atoms, rules, buf);
275 if k == 0 {
276 return None;
277 }
278 let mut best = (buf[0], model_penalty(atoms, buf[0], weak));
279 for &m in &buf[1..k] {
280 let p = model_penalty(atoms, m, weak);
281 if p < best.1 {
282 best = (m, p);
283 }
284 }
285 Some(best)
286}
287
288pub fn cautious_consequences(models: &[u64]) -> u64 {
293 match models.split_first() {
294 Some((&first, rest)) => rest.iter().fold(first, |acc, &m| acc & m),
295 None => 0,
296 }
297}
298
299pub fn brave_consequences(models: &[u64]) -> u64 {
301 models.iter().fold(0u64, |acc, &m| acc | m)
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq)]
310pub enum AspOutcome {
311 Stable(usize),
313 NoStableModel,
315}
316
317pub fn answer_sets_or_paraconsistent(
321 atoms: &[u64],
322 rules: &[AspRule],
323 out: &mut [u64],
324) -> AspOutcome {
325 let k = compute_answer_sets(atoms, rules, out);
326 if k == 0 {
327 AspOutcome::NoStableModel
328 } else {
329 AspOutcome::Stable(k)
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 #[test]
339 fn answer_sets_even_loop_and_constraint() {
340 let (p, q) = (101u64, 202u64);
341 let atoms = [p, q];
342 let prog = [AspRule::new(p, &[], &[q]), AspRule::new(q, &[], &[p])];
344 let mut out = [0u64; 8];
345 let k = compute_answer_sets(&atoms, &prog, &mut out);
346 assert_eq!(k, 2, "even loop has exactly two stable models");
347 let bp = 1u64 << 0; let bq = 1u64 << 1; assert!(
350 out[..k].contains(&bp) && out[..k].contains(&bq),
351 "the two answer sets are {{p}} and {{q}}"
352 );
353
354 let prog2 = [
356 AspRule::new(p, &[], &[q]),
357 AspRule::new(q, &[], &[p]),
358 AspRule::constraint(&[q], &[]),
359 ];
360 let mut out2 = [0u64; 8];
361 let k2 = compute_answer_sets(&atoms, &prog2, &mut out2);
362 assert_eq!(k2, 1, "the constraint prunes {{q}}");
363 assert_eq!(out2[0], bp, "only {{p}} remains");
364 }
365
366 #[test]
367 fn test_enumerate_stable_models() {
368 let base = NQuin {
369 subject: 0,
370 predicate: 0,
371 object: 0,
372 context: 42,
373 metadata: 0,
374 parity: 0,
375 };
376 let mut out_worlds = [0; MAX_STABLE_MODELS];
377
378 let count = enumerate_stable_models(&base, &[], &mut out_worlds);
380 assert_eq!(count, 1);
381 assert_eq!(out_worlds[0], 42);
382
383 let rule = NQuin {
385 subject: 10,
386 predicate: 0,
387 object: 20,
388 context: 0,
389 metadata: 0,
390 parity: 0,
391 };
392 let count2 = enumerate_stable_models(&base, &[rule], &mut out_worlds);
393 assert_eq!(count2, 2);
394 assert_eq!(out_worlds[0], 42);
395 assert_eq!(out_worlds[1], 42 ^ 10 ^ 20);
396 }
397
398 #[test]
399 fn grounder_instantiates_a_template_over_a_domain() {
400 let var = crate::q_hash("var:X");
402 let node = |x: u64| x; let template = AspRule::fact(var);
404 let (a, b, c) = (node(11), node(22), node(33));
405 let mut out = [AspRule::fact(0); 8];
406 let n = ground_rule(&template, var, &[a, b, c], &mut out);
407 assert_eq!(n, 3);
408 assert_eq!(out[0].head, a);
409 assert_eq!(out[1].head, b);
410 assert_eq!(out[2].head, c);
411 }
412
413 #[test]
414 fn weak_constraints_select_the_optimal_model() {
415 let (p, q) = (101u64, 202u64);
416 let atoms = [p, q];
417 let prog = [AspRule::new(p, &[], &[q]), AspRule::new(q, &[], &[p])];
419 let weak = [WeakConstraint::new(&[q], &[], 1)];
420 let mut buf = [0u64; 8];
421 let (best, penalty) = optimal_answer_set(&atoms, &prog, &weak, &mut buf).unwrap();
422 assert_eq!(best, 1u64 << 0, "optimal model is {{p}} (no penalty)");
423 assert_eq!(penalty, 0);
424 assert_eq!(model_penalty(&atoms, 1u64 << 1, &weak), 1);
426 }
427
428 #[test]
429 fn cautious_and_brave_consequences() {
430 let (p, q) = (101u64, 202u64);
431 let atoms = [p, q];
432 let prog = [AspRule::new(p, &[], &[q]), AspRule::new(q, &[], &[p])];
433 let mut buf = [0u64; 8];
434 let k = compute_answer_sets(&atoms, &prog, &mut buf);
435 assert_eq!(k, 2);
436 assert_eq!(cautious_consequences(&buf[..k]), 0);
438 assert_eq!(brave_consequences(&buf[..k]), (1u64 << 0) | (1u64 << 1));
439 }
440
441 #[test]
442 fn no_stable_model_routes_to_paraconsistent() {
443 let p = 101u64;
444 let atoms = [p];
445 let prog = [AspRule::new(p, &[], &[p])];
447 let mut out = [0u64; 8];
448 assert_eq!(
449 answer_sets_or_paraconsistent(&atoms, &prog, &mut out),
450 AspOutcome::NoStableModel
451 );
452 let prog2 = [AspRule::fact(p)];
454 assert_eq!(
455 answer_sets_or_paraconsistent(&atoms, &prog2, &mut out),
456 AspOutcome::Stable(1)
457 );
458 }
459}