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_constraints_aux(
37 arena: &FormulaArena,
38 formula_idx: usize,
39 depth: usize,
40 in_comp: bool,
41 budget: &ResourceBudget,
42 edge_count: &mut usize,
43) -> Vec<Constraint> {
44 if depth > budget.max_depth {
45 panic!("Graph Extraction Nesting Limit Exceeded");
46 }
47 let mut constraints = Vec::new();
48
49 let formula = match arena.get(formula_idx) {
50 Some(f) => f,
51 None => return constraints,
52 };
53
54 match formula {
55 Formula::Atom(atomic) => match atomic {
56 Atomic::Eq(x, y) => {
57 let sx = ScopedVar(x.clone(), depth);
58 let sy = ScopedVar(y.clone(), depth);
59 constraints.push(Constraint {
60 v1: sx.clone(),
61 v2: sy.clone(),
62 weight: 0,
63 in_comp,
64 });
65 constraints.push(Constraint {
66 v1: sy,
67 v2: sx,
68 weight: 0,
69 in_comp,
70 });
71 *edge_count += 2;
72 }
73 Atomic::Mem(x, y) => {
74 let sx = ScopedVar(x.clone(), depth);
75 let sy = ScopedVar(y.clone(), depth);
76 constraints.push(Constraint {
77 v1: sx.clone(),
78 v2: sy.clone(),
79 weight: 1,
80 in_comp,
81 });
82 constraints.push(Constraint {
83 v1: sy,
84 v2: sx,
85 weight: -1,
86 in_comp,
87 });
88 *edge_count += 2;
89 }
90 Atomic::Lt(x, y) => {
91 let sx = ScopedVar(x.clone(), depth);
92 let sy = ScopedVar(y.clone(), depth);
93 constraints.push(Constraint {
94 v1: sy.clone(),
95 v2: sx.clone(),
96 weight: -1,
97 in_comp,
98 });
99 *edge_count += 1;
100 }
101 _ => {}
102 },
103 Formula::Neg(f_idx) => {
104 constraints.extend(extract_constraints_aux(arena, *f_idx, depth, in_comp, budget, edge_count));
105 }
106 Formula::Conj(f1_idx, f2_idx)
107 | Formula::Disj(f1_idx, f2_idx)
108 | Formula::Impl(f1_idx, f2_idx) => {
109 constraints.extend(extract_constraints_aux(arena, *f1_idx, depth, in_comp, budget, edge_count));
110 constraints.extend(extract_constraints_aux(arena, *f2_idx, depth, in_comp, budget, edge_count));
111 }
112 Formula::Univ(_, _, f_idx) | Formula::Exist(_, _, f_idx) => {
113 constraints.extend(extract_constraints_aux(arena, *f_idx, depth + 1, in_comp, budget, edge_count));
114 }
115 Formula::Comp(_, _, f_idx) => {
116 constraints.extend(extract_constraints_aux(arena, *f_idx, depth + 1, true, budget, edge_count));
117 }
118 }
119 if *edge_count > budget.max_graph_edges {
120 panic!("Graph Edge Limit Exceeded");
121 }
122 constraints
123}
124
125#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
129pub struct GraphArena {
130 pub vars: Vec<ScopedVar>,
131 pub var_to_idx: HashMap<ScopedVar, usize>,
132 pub edges: Vec<(usize, usize, i32, bool)>, }
134
135impl GraphArena {
136 pub fn new() -> Self {
137 Self {
138 vars: Vec::new(),
139 var_to_idx: HashMap::new(),
140 edges: Vec::new(),
141 }
142 }
143
144 pub fn add_var(&mut self, var: ScopedVar) -> usize {
145 if let Some(&idx) = self.var_to_idx.get(&var) {
146 idx
147 } else {
148 let idx = self.vars.len();
149 self.vars.push(var.clone());
150 self.var_to_idx.insert(var, idx);
151 idx
152 }
153 }
154
155 pub fn from_constraints(constraints: &[Constraint]) -> Self {
156 let mut arena = Self::new();
157 for c in constraints {
158 let u = arena.add_var(c.v1.clone());
159 let v = arena.add_var(c.v2.clone());
160 arena.edges.push((u, v, c.weight, c.in_comp));
161 }
162 arena
163 }
164
165 pub fn collapse_scc_0_weight(&mut self) {
166 }
169
170 pub fn kosaraju_scc(&self) -> Vec<Vec<usize>> {
172 let n = self.vars.len();
173 if n == 0 {
174 return Vec::new();
175 }
176
177 let mut adj = vec![Vec::new(); n];
178 let mut rev_adj = vec![Vec::new(); n];
179 for &(u, v, w, _) in &self.edges {
180 if w == 0 {
181 adj[u].push(v);
182 rev_adj[v].push(u);
183 }
184 }
185
186 let mut visited = vec![false; n];
187 let mut finish_order = Vec::new();
188
189 fn dfs1(u: usize, adj: &[Vec<usize>], visited: &mut [bool], finish_order: &mut Vec<usize>) {
190 visited[u] = true;
191 for &v in &adj[u] {
192 if !visited[v] {
193 dfs1(v, adj, visited, finish_order);
194 }
195 }
196 finish_order.push(u);
197 }
198
199 for i in 0..n {
200 if !visited[i] {
201 dfs1(i, &adj, &mut visited, &mut finish_order);
202 }
203 }
204
205 visited.fill(false);
206 let mut sccs = Vec::new();
207
208 fn dfs2(u: usize, rev_adj: &[Vec<usize>], visited: &mut [bool], current_scc: &mut Vec<usize>) {
209 visited[u] = true;
210 current_scc.push(u);
211 for &v in &rev_adj[u] {
212 if !visited[v] {
213 dfs2(v, rev_adj, visited, current_scc);
214 }
215 }
216 }
217
218 for &u in finish_order.iter().rev() {
219 if !visited[u] {
220 let mut current_scc = Vec::new();
221 dfs2(u, &rev_adj, &mut visited, &mut current_scc);
222 sccs.push(current_scc);
223 }
224 }
225
226 sccs
227 }
228
229 pub fn contract_graph(&self, sccs: &[Vec<usize>]) -> (Vec<usize>, Vec<(usize, usize, i32)>, Vec<usize>) {
230 let n = self.vars.len();
231 let mut rep = vec![0; n];
232 for scc in sccs {
233 let r = *scc.iter().min().unwrap_or(&0);
234 for &u in scc {
235 rep[u] = r;
236 }
237 }
238
239 let mut c_vars = Vec::new();
240 let mut c_vars_set = std::collections::HashSet::new();
241 for &r in &rep {
242 if c_vars_set.insert(r) {
243 c_vars.push(r);
244 }
245 }
246
247 let mut c_edges = std::collections::HashSet::new();
248 for &(u, v, w, _) in &self.edges {
249 let ru = rep[u];
250 let rv = rep[v];
251 if ru != rv || w != 0 {
252 c_edges.insert((ru, rv, w));
253 }
254 }
255
256 (c_vars, c_edges.into_iter().collect(), rep)
257 }
258
259 pub fn isolate_sc_bedrock(&mut self) -> Vec<String> {
263 let mut sc_nodes = HashSet::new();
264
265 for &(u, v, w, in_comp) in &self.edges {
270 if u == v && w != 0 && !in_comp {
271 sc_nodes.insert(u);
272 }
273 }
274
275 if sc_nodes.is_empty() {
276 return Vec::new();
277 }
278
279 let mut actions = Vec::new();
280 let mut new_edges = HashSet::new();
281
282 for &(u, v, w, in_comp) in &self.edges {
283 if u == v && w != 0 && !in_comp {
284 actions.push(format!(
285 "Neutralized SC defining self-loop on {}",
286 self.var_name(u)
287 ));
288 continue;
289 }
290 if sc_nodes.contains(&u) && w == 1 && !in_comp {
292 actions.push(format!(
293 "Severed outgoing +1 offset edge from SC bedrock node {} to {}",
294 self.var_name(u),
295 self.var_name(v)
296 ));
297 continue;
298 }
299 if sc_nodes.contains(&v) && w == -1 && !in_comp {
300 actions.push(format!(
301 "Severed incoming -1 offset edge to SC bedrock node {} from {}",
302 self.var_name(v),
303 self.var_name(u)
304 ));
305 continue;
306 }
307 new_edges.insert((u, v, w, in_comp));
308 }
309 self.edges = new_edges.into_iter().collect();
310
311 let mut unique_actions: Vec<String> = actions
313 .into_iter()
314 .collect::<HashSet<_>>()
315 .into_iter()
316 .collect();
317 unique_actions.sort();
318 unique_actions
319 }
320
321 fn var_name(&self, u: usize) -> String {
322 let var = &self.vars[u];
323 let name = match &var.0 {
324 crate::ast::Var::Free(n) => n.clone(),
325 crate::ast::Var::Bound(idx) => format!("b{}", idx),
326 };
327 format!("{}_{}", name, var.1)
328 }
329
330
331 pub fn topological_sort(&self) -> Option<Vec<usize>> {
332 let n = self.vars.len();
333 let mut in_degree = vec![0; n];
334 let mut adj = vec![Vec::new(); n];
335
336 for &(u, v, _, _) in &self.edges {
337 adj[u].push(v);
338 in_degree[v] += 1;
339 }
340
341 let mut queue = std::collections::VecDeque::new();
342 for i in 0..n {
343 if in_degree[i] == 0 {
344 queue.push_back(i);
345 }
346 }
347
348 let mut order = Vec::new();
349 while let Some(u) = queue.pop_front() {
350 order.push(u);
351 for &v in &adj[u] {
352 in_degree[v] -= 1;
353 if in_degree[v] == 0 {
354 queue.push_back(v);
355 }
356 }
357 }
358
359 if order.len() == n {
360 Some(order)
361 } else {
362 None
363 }
364 }
365
366 pub fn classify_subsystems(&self, d: &[i32]) -> (bool, bool) {
367 let mut base_weight = i32::MIN;
368 for (i, var) in self.vars.iter().enumerate() {
369 if let crate::ast::Var::Free(_) = var.0 {
370 if d[i] > base_weight {
371 base_weight = d[i];
372 }
373 }
374 }
375
376 if base_weight == i32::MIN {
377 for &w in d {
378 if w > base_weight {
379 base_weight = w;
380 }
381 }
382 if base_weight == i32::MIN {
383 base_weight = 0;
384 }
385 }
386
387 let mut is_nfi = true;
388 let mut is_nfp = true;
389
390 for (i, var) in self.vars.iter().enumerate() {
391 let weight = d[i];
392
393 if weight > base_weight + 1 {
394 is_nfi = false;
395 }
396
397 match var.0 {
398 crate::ast::Var::Free(_) => {
399 if weight > base_weight + 1 {
400 is_nfp = false;
401 }
402 }
403 crate::ast::Var::Bound(_) => {
404 if weight > base_weight {
405 is_nfp = false;
406 }
407 }
408 }
409 }
410
411 (is_nfp, is_nfi)
412 }
413
414 pub fn evaluate_topology(&mut self) -> Result<(Vec<i32>, Vec<String>, bool, bool), String> {
419 let sc_actions = self.isolate_sc_bedrock();
421
422 let n = self.vars.len();
423 if n == 0 {
424 return Ok((Vec::new(), sc_actions, true, true));
425 }
426
427 let sccs = self.kosaraju_scc();
428 let (c_vars, c_edges, reps) = self.contract_graph(&sccs);
429
430 let mut in_degree = HashMap::new();
431 for &u in &c_vars {
432 in_degree.insert(u, 0);
433 }
434 let mut adj = HashMap::new();
435 for &(u, v, w) in &c_edges {
436 adj.entry(u).or_insert_with(Vec::new).push((v, w));
437 *in_degree.entry(v).or_insert(0) += 1;
438 }
439
440 let mut queue = std::collections::VecDeque::new();
441 for (&u, °) in &in_degree {
442 if deg == 0 {
443 queue.push_back(u);
444 }
445 }
446
447 let mut order = Vec::new();
448 while let Some(u) = queue.pop_front() {
449 order.push(u);
450 if let Some(neighbors) = adj.get(&u) {
451 for &(v, _) in neighbors {
452 if let Some(deg) = in_degree.get_mut(&v) {
453 *deg -= 1;
454 if *deg == 0 {
455 queue.push_back(v);
456 }
457 }
458 }
459 }
460 }
461
462 if order.len() == c_vars.len() {
464 let mut c_d = HashMap::new();
465 for &u in &c_vars {
466 c_d.insert(u, 0);
467 }
468 for &u in &order {
469 let du = *c_d.get(&u).unwrap();
470 if let Some(neighbors) = adj.get(&u) {
471 for &(v, w) in neighbors {
472 let dv = *c_d.get(&v).unwrap();
473 if du + w < dv {
474 c_d.insert(v, du + w);
475 }
476 }
477 }
478 }
479
480 let mut d = vec![0; n];
481 for i in 0..n {
482 d[i] = *c_d.get(&reps[i]).unwrap();
483 }
484
485 let (is_nfp, is_nfi) = self.classify_subsystems(&d);
486 return Ok((d, sc_actions, is_nfp, is_nfi));
487 }
488
489 let mut d = vec![0; n];
491 let mut p: Vec<Option<(usize, i32)>> = vec![None; n];
492
493 for _ in 0..n {
495 let mut changed = false;
496 for &(u, v, w, _) in &self.edges {
497 if d[u] + w < d[v] {
498 d[v] = d[u] + w;
499 p[v] = Some((u, w));
500 changed = true;
501 }
502 }
503 if !changed {
504 break;
505 }
506 }
507
508 let mut collision_vertex = None;
510 for &(u, v, w, _) in &self.edges {
511 if d[u] + w < d[v] {
512 collision_vertex = Some(v);
513 p[v] = Some((u, w));
514 break;
515 }
516 }
517
518 if let Some(mut curr) = collision_vertex {
519 let lambda_star = match ExecutionLimits::compute_for_graph(self) {
520 Some(limits) => limits.mcm,
521 None => f64::NEG_INFINITY,
522 };
523
524 for _ in 0..n {
525 curr = p[curr].unwrap().0;
526 }
527
528 let cycle_start = curr;
529 let mut cycle = Vec::new();
530
531 loop {
532 let (prev, w) = p[curr].unwrap();
533 cycle.push((prev, curr, w));
534 curr = prev;
535 if curr == cycle_start {
536 break;
537 }
538 }
539
540 cycle.reverse();
541
542 let mut result = String::new();
543 result.push_str(&format!("Extensionality Collision: Negative-weight cycle detected (μ* = {:.4})!\n", lambda_star));
544 result.push_str("Engine halted safely (K_ITERATION_HALT)\n");
545 result.push_str("Topological Trace: ");
546
547 let mut sum_str = Vec::new();
548 let mut total_weight = 0;
549 for (u, v, w) in &cycle {
550 let u_var = &self.vars[*u];
551 let v_var = &self.vars[*v];
552
553 let u_name = match &u_var.0 {
554 crate::ast::Var::Free(name) => name.clone(),
555 crate::ast::Var::Bound(idx) => format!("b{}", idx),
556 };
557 let v_name = match &v_var.0 {
558 crate::ast::Var::Free(name) => name.clone(),
559 crate::ast::Var::Bound(idx) => format!("b{}", idx),
560 };
561
562 let u_str = format!("{}_{}", u_name, u_var.1);
563 let v_str = format!("{}_{}", v_name, v_var.1);
564
565 sum_str.push(format!("{} -> {} ({})", u_str, v_str, w));
566 total_weight += w;
567 }
568
569 result.push_str(&sum_str.join(" + "));
570 result.push_str(&format!(" = {}", total_weight));
571
572 return Err(result);
573 }
574
575 let (is_nfp, is_nfi) = self.classify_subsystems(&d);
576 Ok((d, sc_actions, is_nfp, is_nfi))
577 }
578
579 pub fn extract_conflict_clauses(&mut self) -> Vec<Vec<usize>> {
584 let n = self.vars.len();
585 let mut d = vec![0; n];
586 let mut p: Vec<Option<(usize, i32)>> = vec![None; n];
587
588 for _ in 0..n {
590 for &(u, v, w, _) in &self.edges {
591 if d[u] + w < d[v] {
592 d[v] = d[u] + w;
593 p[v] = Some((u, w));
594 }
595 }
596 }
597
598 let mut conflict_clauses = Vec::new();
599 for &(u, v, w, _) in &self.edges {
601 if d[u] + w < d[v] {
602 let mut curr = v;
604 for _ in 0..n {
605 if let Some((prev, _)) = p[curr] {
606 curr = prev;
607 }
608 }
609
610 let cycle_start = curr;
611 let mut cycle = Vec::new();
612
613 loop {
614 if let Some((prev, _)) = p[curr] {
615 cycle.push(curr);
616 curr = prev;
617 if curr == cycle_start {
618 break;
619 }
620 } else {
621 break;
622 }
623 }
624 cycle.reverse();
625
626 let mut sorted_cycle = cycle.clone();
628 sorted_cycle.sort();
629
630 let is_duplicate = conflict_clauses.iter().any(|c: &Vec<usize>| {
631 let mut sc = c.clone();
632 sc.sort();
633 sc == sorted_cycle
634 });
635
636 if !is_duplicate {
637 conflict_clauses.push(cycle);
638 }
639 }
640 }
641
642 conflict_clauses
643 }
644}