1use crate::ast::{Atomic, Formula, FormulaArena, Var};
2use crate::eval::ExecutionLimits;
3use crate::budget::ResourceBudget;
4use std::collections::{HashMap, HashSet};
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
7pub struct ScopedVar(pub Var, pub usize);
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub struct Constraint {
11 pub v1: ScopedVar,
12 pub v2: ScopedVar,
13 pub weight: i32,
14 pub in_comp: bool,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub struct Edge {
19 pub source: ScopedVar,
20 pub target: ScopedVar,
21 pub weight: i32,
22 pub in_comp: bool,
23}
24
25impl From<Constraint> for Edge {
26 fn from(c: Constraint) -> Self {
27 Edge {
28 source: c.v1,
29 target: c.v2,
30 weight: c.weight,
31 in_comp: c.in_comp,
32 }
33 }
34}
35
36pub fn extract_atom_constraints(
37 atomic: &Atomic,
38 depth: usize,
39 in_comp: bool,
40 edge_count: &mut usize,
41) -> Vec<Constraint> {
42 let mut constraints = Vec::new();
43 match atomic {
44 Atomic::Eq(x, y) => {
45 let sx = ScopedVar(x.clone(), depth);
46 let sy = ScopedVar(y.clone(), depth);
47 constraints.push(Constraint {
48 v1: sx.clone(),
49 v2: sy.clone(),
50 weight: 0,
51 in_comp,
52 });
53 constraints.push(Constraint {
54 v1: sy,
55 v2: sx,
56 weight: 0,
57 in_comp,
58 });
59 *edge_count += 2;
60 }
61 Atomic::Mem(x, y) => {
62 let sx = ScopedVar(x.clone(), depth);
63 let sy = ScopedVar(y.clone(), depth);
64 constraints.push(Constraint {
65 v1: sx.clone(),
66 v2: sy.clone(),
67 weight: 1,
68 in_comp,
69 });
70 constraints.push(Constraint {
71 v1: sy,
72 v2: sx,
73 weight: -1,
74 in_comp,
75 });
76 *edge_count += 2;
77 }
78 Atomic::Lt(x, y) => {
79 let sx = ScopedVar(x.clone(), depth);
80 let sy = ScopedVar(y.clone(), depth);
81 constraints.push(Constraint {
82 v1: sy.clone(),
83 v2: sx.clone(),
84 weight: -1,
85 in_comp,
86 });
87 *edge_count += 1;
88 }
89 Atomic::QPair(p, x, y) => {
90 let sp = ScopedVar(p.clone(), depth);
91 let sx = ScopedVar(x.clone(), depth);
92 let sy = ScopedVar(y.clone(), depth);
93 constraints.push(Constraint { v1: sp.clone(), v2: sx.clone(), weight: 0, in_comp });
94 constraints.push(Constraint { v1: sx, v2: sp.clone(), weight: 0, in_comp });
95 constraints.push(Constraint { v1: sp.clone(), v2: sy.clone(), weight: 0, in_comp });
96 constraints.push(Constraint { v1: sy, v2: sp, weight: 0, in_comp });
97 *edge_count += 4;
98 }
99 Atomic::QProj1(x, p) => {
100 let sx = ScopedVar(x.clone(), depth);
101 let sp = ScopedVar(p.clone(), depth);
102 constraints.push(Constraint { v1: sx.clone(), v2: sp.clone(), weight: 0, in_comp });
103 constraints.push(Constraint { v1: sp, v2: sx, weight: 0, in_comp });
104 *edge_count += 2;
105 }
106 Atomic::QProj2(y, p) => {
107 let sy = ScopedVar(y.clone(), depth);
108 let sp = ScopedVar(p.clone(), depth);
109 constraints.push(Constraint { v1: sy.clone(), v2: sp.clone(), weight: 0, in_comp });
110 constraints.push(Constraint { v1: sp, v2: sy, weight: 0, in_comp });
111 *edge_count += 2;
112 }
113 Atomic::App(z, u, v) => {
114 let sz = ScopedVar(z.clone(), depth);
115 let su = ScopedVar(u.clone(), depth);
116 let sv = ScopedVar(v.clone(), depth);
117 constraints.push(Constraint { v1: sz.clone(), v2: sv.clone(), weight: 0, in_comp });
118 constraints.push(Constraint { v1: sv, v2: sz.clone(), weight: 0, in_comp });
119 constraints.push(Constraint { v1: sz.clone(), v2: su.clone(), weight: 0, in_comp });
120 constraints.push(Constraint { v1: su, v2: sz, weight: 0, in_comp });
121 *edge_count += 4;
122 }
123 Atomic::Lam(z, x, t) => {
124 let sz = ScopedVar(z.clone(), depth);
125 let sx = ScopedVar(x.clone(), depth);
126 let st = ScopedVar(t.clone(), depth);
127 constraints.push(Constraint { v1: sz.clone(), v2: st.clone(), weight: 0, in_comp });
128 constraints.push(Constraint { v1: st, v2: sz.clone(), weight: 0, in_comp });
129 constraints.push(Constraint { v1: sz.clone(), v2: sx.clone(), weight: 0, in_comp });
130 constraints.push(Constraint { v1: sx, v2: sz, weight: 0, in_comp });
131 *edge_count += 4;
132 }
133 }
134 constraints
135}
136
137pub fn extract_dnf_clauses(
138 arena: &FormulaArena,
139 formula_idx: usize,
140 depth: usize,
141 in_comp: bool,
142 budget: &ResourceBudget,
143 edge_count: &mut usize,
144) -> Vec<Vec<Constraint>> {
145 extract_dnf_clauses_aux(arena, formula_idx, false, depth, in_comp, budget, edge_count)
146}
147
148pub fn extract_dnf_clauses_aux(
149 arena: &FormulaArena,
150 formula_idx: usize,
151 is_negated: bool,
152 depth: usize,
153 in_comp: bool,
154 budget: &ResourceBudget,
155 edge_count: &mut usize,
156) -> Vec<Vec<Constraint>> {
157 if depth > budget.max_depth {
158 panic!("Graph Extraction Nesting Limit Exceeded");
159 }
160
161 let formula = match arena.get(formula_idx) {
162 Some(f) => f,
163 None => return vec![Vec::new()],
164 };
165
166 if !is_negated {
167 match formula {
168 Formula::Atom(atomic) => {
169 vec![extract_atom_constraints(atomic, depth, in_comp, edge_count)]
170 }
171 Formula::Neg(f_idx) => {
172 extract_dnf_clauses_aux(arena, *f_idx, true, depth, in_comp, budget, edge_count)
173 }
174 Formula::Conj(f1_idx, f2_idx) => {
175 let left_clauses = extract_dnf_clauses_aux(arena, *f1_idx, false, depth, in_comp, budget, edge_count);
176 let right_clauses = extract_dnf_clauses_aux(arena, *f2_idx, false, depth, in_comp, budget, edge_count);
177 let mut combined = Vec::new();
178 for lc in &left_clauses {
179 for rc in &right_clauses {
180 let mut merged = lc.clone();
181 merged.extend(rc.clone());
182 combined.push(merged);
183 }
184 }
185 if combined.is_empty() {
186 left_clauses
187 } else {
188 combined
189 }
190 }
191 Formula::Disj(f1_idx, f2_idx) => {
192 let mut clauses = extract_dnf_clauses_aux(arena, *f1_idx, false, depth, in_comp, budget, edge_count);
193 clauses.extend(extract_dnf_clauses_aux(arena, *f2_idx, false, depth, in_comp, budget, edge_count));
194 clauses
195 }
196 Formula::Impl(f1_idx, f2_idx) => {
197 let mut clauses = extract_dnf_clauses_aux(arena, *f1_idx, true, depth, in_comp, budget, edge_count);
199 clauses.extend(extract_dnf_clauses_aux(arena, *f2_idx, false, depth, in_comp, budget, edge_count));
200 clauses
201 }
202 Formula::Univ(_, _, f_idx) | Formula::Exist(_, _, f_idx) => {
203 extract_dnf_clauses_aux(arena, *f_idx, false, depth + 1, in_comp, budget, edge_count)
204 }
205 Formula::Comp(_, _, f_idx) => {
206 extract_dnf_clauses_aux(arena, *f_idx, false, depth + 1, true, budget, edge_count)
207 }
208 }
209 } else {
210 match formula {
212 Formula::Atom(atomic) => {
213 vec![extract_atom_constraints(atomic, depth, in_comp, edge_count)]
214 }
215 Formula::Neg(f_idx) => {
216 extract_dnf_clauses_aux(arena, *f_idx, false, depth, in_comp, budget, edge_count)
218 }
219 Formula::Conj(f1_idx, f2_idx) => {
220 let mut clauses = extract_dnf_clauses_aux(arena, *f1_idx, true, depth, in_comp, budget, edge_count);
222 clauses.extend(extract_dnf_clauses_aux(arena, *f2_idx, true, depth, in_comp, budget, edge_count));
223 clauses
224 }
225 Formula::Disj(f1_idx, f2_idx) => {
226 let left_clauses = extract_dnf_clauses_aux(arena, *f1_idx, true, depth, in_comp, budget, edge_count);
228 let right_clauses = extract_dnf_clauses_aux(arena, *f2_idx, true, depth, in_comp, budget, edge_count);
229 let mut combined = Vec::new();
230 for lc in &left_clauses {
231 for rc in &right_clauses {
232 let mut merged = lc.clone();
233 merged.extend(rc.clone());
234 combined.push(merged);
235 }
236 }
237 if combined.is_empty() {
238 left_clauses
239 } else {
240 combined
241 }
242 }
243 Formula::Impl(f1_idx, f2_idx) => {
244 let left_clauses = extract_dnf_clauses_aux(arena, *f1_idx, false, depth, in_comp, budget, edge_count);
246 let right_clauses = extract_dnf_clauses_aux(arena, *f2_idx, true, depth, in_comp, budget, edge_count);
247 let mut combined = Vec::new();
248 for lc in &left_clauses {
249 for rc in &right_clauses {
250 let mut merged = lc.clone();
251 merged.extend(rc.clone());
252 combined.push(merged);
253 }
254 }
255 if combined.is_empty() {
256 left_clauses
257 } else {
258 combined
259 }
260 }
261 Formula::Univ(_, _, f_idx) | Formula::Exist(_, _, f_idx) => {
262 extract_dnf_clauses_aux(arena, *f_idx, true, depth + 1, in_comp, budget, edge_count)
263 }
264 Formula::Comp(_, _, f_idx) => {
265 extract_dnf_clauses_aux(arena, *f_idx, true, depth + 1, true, budget, edge_count)
266 }
267 }
268 }
269}
270
271pub fn extract_constraints_aux(
272 arena: &FormulaArena,
273 formula_idx: usize,
274 depth: usize,
275 in_comp: bool,
276 budget: &ResourceBudget,
277 edge_count: &mut usize,
278) -> Vec<Constraint> {
279 let clauses = extract_dnf_clauses(arena, formula_idx, depth, in_comp, budget, edge_count);
280 if let Some(first) = clauses.into_iter().next() {
281 first
282 } else {
283 Vec::new()
284 }
285}
286
287#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
291pub struct GraphArena {
292 pub vars: Vec<ScopedVar>,
293 pub var_to_idx: HashMap<ScopedVar, usize>,
294 pub edges: Vec<(usize, usize, i32, bool)>, }
296
297impl GraphArena {
298 pub fn new() -> Self {
299 Self {
300 vars: Vec::new(),
301 var_to_idx: HashMap::new(),
302 edges: Vec::new(),
303 }
304 }
305
306 pub fn add_var(&mut self, var: ScopedVar) -> usize {
307 if let Some(&idx) = self.var_to_idx.get(&var) {
308 idx
309 } else {
310 let idx = self.vars.len();
311 self.vars.push(var.clone());
312 self.var_to_idx.insert(var, idx);
313 idx
314 }
315 }
316
317 pub fn from_constraints(constraints: &[Constraint]) -> Self {
318 let mut arena = Self::new();
319 for c in constraints {
320 let u = arena.add_var(c.v1.clone());
321 let v = arena.add_var(c.v2.clone());
322 arena.edges.push((u, v, c.weight, c.in_comp));
323 }
324 arena
325 }
326
327 pub fn collapse_scc_0_weight(&mut self) {
328 }
331
332 pub fn tarjan_scc(&self) -> Vec<Vec<usize>> {
334 let n = self.vars.len();
335 if n == 0 {
336 return Vec::new();
337 }
338
339 let mut adj = vec![Vec::new(); n];
340 for &(u, v, w, _) in &self.edges {
341 if w == 0 {
342 adj[u].push(v);
343 }
344 }
345
346 let mut dfn = vec![None; n];
347 let mut low = vec![0; n];
348 let mut on_stack = vec![false; n];
349 let mut stack = Vec::new();
350 let mut timer = 0;
351 let mut sccs = Vec::new();
352
353 fn dfs(
354 u: usize,
355 adj: &[Vec<usize>],
356 dfn: &mut [Option<usize>],
357 low: &mut [usize],
358 on_stack: &mut [bool],
359 stack: &mut Vec<usize>,
360 timer: &mut usize,
361 sccs: &mut Vec<Vec<usize>>,
362 ) {
363 dfn[u] = Some(*timer);
364 low[u] = *timer;
365 *timer += 1;
366 stack.push(u);
367 on_stack[u] = true;
368
369 for &v in &adj[u] {
370 if dfn[v].is_none() {
371 dfs(v, adj, dfn, low, on_stack, stack, timer, sccs);
372 low[u] = low[u].min(low[v]);
373 } else if on_stack[v] {
374 low[u] = low[u].min(dfn[v].unwrap());
375 }
376 }
377
378 if low[u] == dfn[u].unwrap() {
379 let mut scc = Vec::new();
380 while let Some(v) = stack.pop() {
381 on_stack[v] = false;
382 scc.push(v);
383 if v == u {
384 break;
385 }
386 }
387 sccs.push(scc);
388 }
389 }
390
391 for i in 0..n {
392 if dfn[i].is_none() {
393 dfs(i, &adj, &mut dfn, &mut low, &mut on_stack, &mut stack, &mut timer, &mut sccs);
394 }
395 }
396
397 sccs
398 }
399
400 pub fn kosaraju_scc(&self) -> Vec<Vec<usize>> {
402 self.tarjan_scc()
403 }
404
405 pub fn contract_graph(&self, sccs: &[Vec<usize>]) -> (Vec<usize>, Vec<(usize, usize, i32)>, Vec<usize>) {
406 let n = self.vars.len();
407 let mut rep = vec![0; n];
408 for scc in sccs {
409 let r = *scc.iter().min().unwrap_or(&0);
410 for &u in scc {
411 rep[u] = r;
412 }
413 }
414
415 let mut c_vars = Vec::new();
416 let mut c_vars_set = std::collections::HashSet::new();
417 for &r in &rep {
418 if c_vars_set.insert(r) {
419 c_vars.push(r);
420 }
421 }
422
423 let mut c_edges = std::collections::HashSet::new();
424 for &(u, v, w, _) in &self.edges {
425 let ru = rep[u];
426 let rv = rep[v];
427 if ru != rv || w != 0 {
428 c_edges.insert((ru, rv, w));
429 }
430 }
431
432 (c_vars, c_edges.into_iter().collect(), rep)
433 }
434
435 pub fn isolate_sc_bedrock(&mut self) -> Vec<String> {
439 let mut sc_nodes = HashSet::new();
440
441 for &(u, v, w, in_comp) in &self.edges {
446 if u == v && w != 0 && !in_comp {
447 sc_nodes.insert(u);
448 }
449 }
450
451 if sc_nodes.is_empty() {
452 return Vec::new();
453 }
454
455 let mut actions = Vec::new();
456 let mut new_edges = HashSet::new();
457
458 for &(u, v, w, in_comp) in &self.edges {
459 if u == v && w != 0 && !in_comp {
460 actions.push(format!(
461 "Neutralized SC defining self-loop on {}",
462 self.var_name(u)
463 ));
464 continue;
465 }
466 if sc_nodes.contains(&u) && w == 1 && !in_comp {
468 actions.push(format!(
469 "Severed outgoing +1 offset edge from SC bedrock node {} to {}",
470 self.var_name(u),
471 self.var_name(v)
472 ));
473 continue;
474 }
475 if sc_nodes.contains(&v) && w == -1 && !in_comp {
476 actions.push(format!(
477 "Severed incoming -1 offset edge to SC bedrock node {} from {}",
478 self.var_name(v),
479 self.var_name(u)
480 ));
481 continue;
482 }
483 new_edges.insert((u, v, w, in_comp));
484 }
485 self.edges = new_edges.into_iter().collect();
486
487 let mut unique_actions: Vec<String> = actions
489 .into_iter()
490 .collect::<HashSet<_>>()
491 .into_iter()
492 .collect();
493 unique_actions.sort();
494 unique_actions
495 }
496
497 fn var_name(&self, u: usize) -> String {
498 let var = &self.vars[u];
499 let name = match &var.0 {
500 crate::ast::Var::Free(n) => n.clone(),
501 crate::ast::Var::Bound(idx) => format!("b{}", idx),
502 };
503 format!("{}_{}", name, var.1)
504 }
505
506
507 pub fn topological_sort(&self) -> Option<Vec<usize>> {
508 let n = self.vars.len();
509 let mut in_degree = vec![0; n];
510 let mut adj = vec![Vec::new(); n];
511
512 for &(u, v, _, _) in &self.edges {
513 adj[u].push(v);
514 in_degree[v] += 1;
515 }
516
517 let mut queue = std::collections::VecDeque::new();
518 for i in 0..n {
519 if in_degree[i] == 0 {
520 queue.push_back(i);
521 }
522 }
523
524 let mut order = Vec::new();
525 while let Some(u) = queue.pop_front() {
526 order.push(u);
527 for &v in &adj[u] {
528 in_degree[v] -= 1;
529 if in_degree[v] == 0 {
530 queue.push_back(v);
531 }
532 }
533 }
534
535 if order.len() == n {
536 Some(order)
537 } else {
538 None
539 }
540 }
541
542 pub fn classify_subsystems(&self, d: &[i32]) -> (bool, bool) {
543 let mut base_weight = i32::MIN;
544 for (i, var) in self.vars.iter().enumerate() {
545 if let crate::ast::Var::Free(_) = var.0 {
546 if d[i] > base_weight {
547 base_weight = d[i];
548 }
549 }
550 }
551
552 if base_weight == i32::MIN {
553 for &w in d {
554 if w > base_weight {
555 base_weight = w;
556 }
557 }
558 if base_weight == i32::MIN {
559 base_weight = 0;
560 }
561 }
562
563 let mut is_nfi = true;
564 let mut is_nfp = true;
565
566 for (i, var) in self.vars.iter().enumerate() {
567 let weight = d[i];
568
569 if weight > base_weight + 1 {
570 is_nfi = false;
571 }
572
573 match var.0 {
574 crate::ast::Var::Free(_) => {
575 if weight > base_weight + 1 {
576 is_nfp = false;
577 }
578 }
579 crate::ast::Var::Bound(_) => {
580 if weight > base_weight {
581 is_nfp = false;
582 }
583 }
584 }
585 }
586
587 (is_nfp, is_nfi)
588 }
589
590 pub fn evaluate_topology(&mut self) -> Result<(Vec<i32>, Vec<String>, bool, bool), String> {
595 let sc_actions = self.isolate_sc_bedrock();
597
598 let n = self.vars.len();
599 if n == 0 {
600 return Ok((Vec::new(), sc_actions, true, true));
601 }
602
603 let sccs = self.tarjan_scc();
604 let (c_vars, c_edges, reps) = self.contract_graph(&sccs);
605
606 let mut in_degree = HashMap::new();
607 for &u in &c_vars {
608 in_degree.insert(u, 0);
609 }
610 let mut adj = HashMap::new();
611 for &(u, v, w) in &c_edges {
612 adj.entry(u).or_insert_with(Vec::new).push((v, w));
613 *in_degree.entry(v).or_insert(0) += 1;
614 }
615
616 let mut queue = std::collections::VecDeque::new();
617 for (&u, °) in &in_degree {
618 if deg == 0 {
619 queue.push_back(u);
620 }
621 }
622
623 let mut order = Vec::new();
624 while let Some(u) = queue.pop_front() {
625 order.push(u);
626 if let Some(neighbors) = adj.get(&u) {
627 for &(v, _) in neighbors {
628 if let Some(deg) = in_degree.get_mut(&v) {
629 *deg -= 1;
630 if *deg == 0 {
631 queue.push_back(v);
632 }
633 }
634 }
635 }
636 }
637
638 if order.len() == c_vars.len() {
640 let mut c_d = HashMap::new();
641 for &u in &c_vars {
642 c_d.insert(u, 0);
643 }
644 for &u in &order {
645 let du = *c_d.get(&u).unwrap();
646 if let Some(neighbors) = adj.get(&u) {
647 for &(v, w) in neighbors {
648 let dv = *c_d.get(&v).unwrap();
649 if du + w < dv {
650 c_d.insert(v, du + w);
651 }
652 }
653 }
654 }
655
656 let mut d = vec![0; n];
657 for i in 0..n {
658 d[i] = *c_d.get(&reps[i]).unwrap();
659 }
660
661 let (is_nfp, is_nfi) = self.classify_subsystems(&d);
662 return Ok((d, sc_actions, is_nfp, is_nfi));
663 }
664
665 let mut d = vec![0; n];
667 let mut p: Vec<Option<(usize, i32)>> = vec![None; n];
668
669 for _ in 0..n {
671 let mut changed = false;
672 for &(u, v, w, _) in &self.edges {
673 if d[u] + w < d[v] {
674 d[v] = d[u] + w;
675 p[v] = Some((u, w));
676 changed = true;
677 }
678 }
679 if !changed {
680 break;
681 }
682 }
683
684 let mut collision_vertex = None;
686 for &(u, v, w, _) in &self.edges {
687 if d[u] + w < d[v] {
688 collision_vertex = Some(v);
689 p[v] = Some((u, w));
690 break;
691 }
692 }
693
694 if let Some(mut curr) = collision_vertex {
695 let lambda_star = match ExecutionLimits::compute_for_graph(self) {
696 Some(limits) => limits.mcm,
697 None => f64::NEG_INFINITY,
698 };
699
700 for _ in 0..n {
701 curr = p[curr].unwrap().0;
702 }
703
704 let cycle_start = curr;
705 let mut cycle = Vec::new();
706
707 loop {
708 let (prev, w) = p[curr].unwrap();
709 cycle.push((prev, curr, w));
710 curr = prev;
711 if curr == cycle_start {
712 break;
713 }
714 }
715
716 cycle.reverse();
717
718 let mut result = String::new();
719 result.push_str(&format!("Extensionality Collision: Negative-weight cycle detected (μ* = {:.4})!\n", lambda_star));
720 result.push_str("Engine halted safely (K_ITERATION_HALT)\n");
721 result.push_str("Topological Trace: ");
722
723 let mut sum_str = Vec::new();
724 let mut total_weight = 0;
725 for (u, v, w) in &cycle {
726 let u_var = &self.vars[*u];
727 let v_var = &self.vars[*v];
728
729 let u_name = match &u_var.0 {
730 crate::ast::Var::Free(name) => name.clone(),
731 crate::ast::Var::Bound(idx) => format!("b{}", idx),
732 };
733 let v_name = match &v_var.0 {
734 crate::ast::Var::Free(name) => name.clone(),
735 crate::ast::Var::Bound(idx) => format!("b{}", idx),
736 };
737
738 let u_str = format!("{}_{}", u_name, u_var.1);
739 let v_str = format!("{}_{}", v_name, v_var.1);
740
741 sum_str.push(format!("{} -> {} ({})", u_str, v_str, w));
742 total_weight += w;
743 }
744
745 result.push_str(&sum_str.join(" + "));
746 result.push_str(&format!(" = {}", total_weight));
747
748 return Err(result);
749 }
750
751 let (is_nfp, is_nfi) = self.classify_subsystems(&d);
752 Ok((d, sc_actions, is_nfp, is_nfi))
753 }
754
755 pub fn extract_conflict_clauses(&mut self) -> Vec<Vec<usize>> {
760 let n = self.vars.len();
761 let mut d = vec![0; n];
762 let mut p: Vec<Option<(usize, i32)>> = vec![None; n];
763
764 for _ in 0..n {
766 for &(u, v, w, _) in &self.edges {
767 if d[u] + w < d[v] {
768 d[v] = d[u] + w;
769 p[v] = Some((u, w));
770 }
771 }
772 }
773
774 let mut conflict_clauses = Vec::new();
775 for &(u, v, w, _) in &self.edges {
777 if d[u] + w < d[v] {
778 let mut curr = v;
780 for _ in 0..n {
781 if let Some((prev, _)) = p[curr] {
782 curr = prev;
783 }
784 }
785
786 let cycle_start = curr;
787 let mut cycle = Vec::new();
788
789 loop {
790 if let Some((prev, _)) = p[curr] {
791 cycle.push(curr);
792 curr = prev;
793 if curr == cycle_start {
794 break;
795 }
796 } else {
797 break;
798 }
799 }
800 cycle.reverse();
801
802 let mut sorted_cycle = cycle.clone();
804 sorted_cycle.sort();
805
806 let is_duplicate = conflict_clauses.iter().any(|c: &Vec<usize>| {
807 let mut sc = c.clone();
808 sc.sort();
809 sc == sorted_cycle
810 });
811
812 if !is_duplicate {
813 conflict_clauses.push(cycle);
814 }
815 }
816 }
817
818 conflict_clauses
819 }
820
821 pub fn evaluate_dnf_formula(
824 arena: &FormulaArena,
825 formula_idx: usize,
826 budget: &ResourceBudget,
827 ) -> Result<(Vec<i32>, Vec<String>, bool, bool), String> {
828 let mut edge_count = 0;
829 let clauses = extract_dnf_clauses(arena, formula_idx, 0, false, budget, &mut edge_count);
830 let mut last_err = String::from("No DNF clauses generated");
831 for clause in clauses {
832 let mut graph = GraphArena::from_constraints(&clause);
833 match graph.evaluate_topology() {
834 Ok(res) => return Ok(res),
835 Err(e) => last_err = e,
836 }
837 }
838 Err(last_err)
839 }
840}