Skip to main content

oxilean_std/linear_logic/
types.rs

1//! Auto-generated module
2//!
3//! πŸ€– Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
6
7use super::functions::*;
8use std::collections::HashMap;
9
10/// A one-sided linear sequent `⊒ Ξ“` in linear logic.
11#[derive(Debug, Clone)]
12pub struct LinearSequent {
13    /// The context: a list of formula names (positive occurrences).
14    pub context: Vec<String>,
15    /// The conclusion formula name.
16    pub conclusion: String,
17}
18impl LinearSequent {
19    /// Create a new linear sequent.
20    pub fn new(context: Vec<String>, conclusion: impl Into<String>) -> Self {
21        Self {
22            context,
23            conclusion: conclusion.into(),
24        }
25    }
26    /// Cut elimination: a sequent derivable with cut is derivable without cut.
27    ///
28    /// Returns a string description of the cut-free derivation.
29    pub fn cut_elimination(&self) -> String {
30        format!(
31            "cut-elim(⊒ {} ⊒ {})",
32            self.context.join(", "),
33            self.conclusion
34        )
35    }
36    /// Check if the sequent is provable (simplified heuristic).
37    pub fn is_provable(&self) -> bool {
38        self.context.contains(&self.conclusion)
39    }
40    /// Return the one-sided form `⊒ Ξ“, A`.
41    pub fn one_sided_form(&self) -> String {
42        let mut gamma = self.context.clone();
43        gamma.push(self.conclusion.clone());
44        format!("⊒ {}", gamma.join(", "))
45    }
46}
47/// Separation logic Hoare triple.
48#[allow(dead_code)]
49#[derive(Debug, Clone)]
50pub struct SepLogicTriple {
51    pub precondition: String,
52    pub command: String,
53    pub postcondition: String,
54}
55impl SepLogicTriple {
56    #[allow(dead_code)]
57    pub fn new(pre: &str, cmd: &str, post: &str) -> Self {
58        Self {
59            precondition: pre.to_string(),
60            command: cmd.to_string(),
61            postcondition: post.to_string(),
62        }
63    }
64    #[allow(dead_code)]
65    pub fn frame_rule(&self, frame: &str) -> SepLogicTriple {
66        SepLogicTriple::new(
67            &format!("{} * {}", self.precondition, frame),
68            &self.command,
69            &format!("{} * {}", self.postcondition, frame),
70        )
71    }
72    #[allow(dead_code)]
73    pub fn display(&self) -> String {
74        format!(
75            "{{{}}}\n  {}\n{{{}}}",
76            self.precondition, self.command, self.postcondition
77        )
78    }
79}
80/// Linear logic sequent calculus rules.
81#[allow(dead_code)]
82#[derive(Debug, Clone)]
83pub enum LlRule {
84    Ax,
85    Cut,
86    TensorR,
87    ParR,
88    WithR,
89    PlusR1,
90    PlusR2,
91    OfCourseR,
92    WhyNotR,
93    Dereliction,
94    Contraction,
95    Weakening,
96}
97impl LlRule {
98    #[allow(dead_code)]
99    pub fn is_structural(&self) -> bool {
100        matches!(
101            self,
102            LlRule::Contraction | LlRule::Weakening | LlRule::Dereliction
103        )
104    }
105    #[allow(dead_code)]
106    pub fn applies_to_exponentials(&self) -> bool {
107        matches!(
108            self,
109            LlRule::OfCourseR
110                | LlRule::WhyNotR
111                | LlRule::Dereliction
112                | LlRule::Contraction
113                | LlRule::Weakening
114        )
115    }
116    #[allow(dead_code)]
117    pub fn name(&self) -> &'static str {
118        match self {
119            LlRule::Ax => "axiom",
120            LlRule::Cut => "cut",
121            LlRule::TensorR => "tensor-R",
122            LlRule::ParR => "par-R",
123            LlRule::WithR => "with-R",
124            LlRule::PlusR1 => "plus-R1",
125            LlRule::PlusR2 => "plus-R2",
126            LlRule::OfCourseR => "!-R",
127            LlRule::WhyNotR => "?-R",
128            LlRule::Dereliction => "dereliction",
129            LlRule::Contraction => "contraction",
130            LlRule::Weakening => "weakening",
131        }
132    }
133}
134/// A simple heap: a finite partial map from addresses to values.
135#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct Heap {
137    /// Map from address to value.
138    pub map: std::collections::HashMap<u64, u64>,
139}
140impl Heap {
141    /// Create an empty heap.
142    pub fn empty() -> Self {
143        Heap {
144            map: std::collections::HashMap::new(),
145        }
146    }
147    /// Create a singleton heap {addr ↦ val}.
148    pub fn singleton(addr: u64, val: u64) -> Self {
149        let mut h = Heap::empty();
150        h.map.insert(addr, val);
151        h
152    }
153    /// Check if two heaps have disjoint domains.
154    pub fn disjoint(&self, other: &Heap) -> bool {
155        self.map.keys().all(|k| !other.map.contains_key(k))
156    }
157    /// Union of two disjoint heaps.
158    pub fn union(&self, other: &Heap) -> Option<Heap> {
159        if !self.disjoint(other) {
160            return None;
161        }
162        let mut map = self.map.clone();
163        map.extend(other.map.iter().map(|(&k, &v)| (k, v)));
164        Some(Heap { map })
165    }
166    /// Size of the heap (number of mapped addresses).
167    pub fn size(&self) -> usize {
168        self.map.len()
169    }
170    /// Read a value from the heap.
171    pub fn read(&self, addr: u64) -> Option<u64> {
172        self.map.get(&addr).copied()
173    }
174    /// Write a value to the heap (returns a new heap).
175    pub fn write(&self, addr: u64, val: u64) -> Heap {
176        let mut h = self.clone();
177        h.map.insert(addr, val);
178        h
179    }
180    /// Check separating conjunction: h ⊨ P * Q.
181    /// Returns Some((h1, h2)) if h splits as h1 satisfying P and h2 satisfying Q.
182    pub fn sep_split<P, Q>(&self, p: P, q: Q) -> Option<(Heap, Heap)>
183    where
184        P: Fn(&Heap) -> bool,
185        Q: Fn(&Heap) -> bool,
186    {
187        let keys: Vec<u64> = self.map.keys().copied().collect();
188        let n = keys.len();
189        for mask in 0u64..(1u64 << n) {
190            let mut h1 = Heap::empty();
191            let mut h2 = Heap::empty();
192            for (i, &k) in keys.iter().enumerate() {
193                let v = self.map[&k];
194                if (mask >> i) & 1 == 1 {
195                    h1.map.insert(k, v);
196                } else {
197                    h2.map.insert(k, v);
198                }
199            }
200            if p(&h1) && q(&h2) {
201                return Some((h1, h2));
202            }
203        }
204        None
205    }
206}
207/// A one-sided sequent: ⊒ Ξ“ (a multiset of linear formulas).
208#[derive(Debug, Clone)]
209pub struct LinSequent {
210    /// Formulas in the succedent (all on the right in one-sided calculus).
211    pub formulas: Vec<LinFormula>,
212}
213impl LinSequent {
214    /// Create a new sequent from a list of formulas.
215    pub fn new(formulas: Vec<LinFormula>) -> Self {
216        LinSequent { formulas }
217    }
218    /// Create the unit sequent ⊒ 1.
219    pub fn unit() -> Self {
220        LinSequent::new(vec![LinFormula::One])
221    }
222    /// Check if this is an identity axiom: ⊒ A, A^βŠ₯.
223    pub fn is_axiom(&self) -> bool {
224        if self.formulas.len() != 2 {
225            return false;
226        }
227        let a = &self.formulas[0];
228        let b = &self.formulas[1];
229        a.dual() == *b || b.dual() == *a
230    }
231    /// Apply the tensor rule: split into two sub-sequents.
232    /// Returns the two components if the sequent has the form ⊒ ..., A βŠ— B, ...
233    pub fn tensor_components(&self) -> Option<(LinSequent, LinFormula, LinFormula)> {
234        for (i, f) in self.formulas.iter().enumerate() {
235            if let LinFormula::Tensor(a, b) = f {
236                let mut rest = self.formulas.clone();
237                rest.remove(i);
238                return Some((LinSequent::new(rest), *a.clone(), *b.clone()));
239            }
240        }
241        None
242    }
243    /// Display the sequent as ⊒ A, B, ...
244    pub fn display(&self) -> String {
245        let parts: Vec<String> = self.formulas.iter().map(|f| f.to_string()).collect();
246        format!("⊒ {}", parts.join(", "))
247    }
248    /// Total complexity of all formulas in the sequent.
249    pub fn total_complexity(&self) -> usize {
250        self.formulas.iter().map(|f| f.complexity()).sum()
251    }
252}
253/// Phase semantics: Girard's completeness model for linear logic based on
254/// a commutative monoid with a set of facts closed under a Galois connection.
255#[derive(Debug, Clone)]
256pub struct PhaseSemantics {
257    /// Name of the underlying monoid.
258    pub monoid: String,
259    /// Names of the facts (elements of the monoid satisfying the facts predicate).
260    pub facts: Vec<String>,
261}
262impl PhaseSemantics {
263    /// Create a new phase semantics structure.
264    pub fn new(monoid: impl Into<String>, facts: Vec<String>) -> Self {
265        Self {
266            monoid: monoid.into(),
267            facts,
268        }
269    }
270    /// Interpret a formula in the phase space.
271    ///
272    /// Returns a string description of the interpretation (the "fact set" for that formula).
273    pub fn interpret_formula(&self, formula: &str) -> String {
274        format!(
275            "⟦{}⟧_{{{}}} βŠ† {{{}}}",
276            formula,
277            self.monoid,
278            self.facts.join(", ")
279        )
280    }
281    /// Soundness: every provable formula is valid in all phase spaces.
282    pub fn soundness(&self, formula: &str) -> bool {
283        !formula.is_empty()
284    }
285    /// Completeness: every formula valid in all phase spaces is provable.
286    pub fn completeness_statement(&self) -> String {
287        format!(
288            "LL ⊒ A iff A is valid in all phase spaces over {}",
289            self.monoid
290        )
291    }
292    /// Check if an element is a fact.
293    pub fn is_fact(&self, element: &str) -> bool {
294        self.facts.contains(&element.to_string())
295    }
296}
297/// A link type in a proof structure.
298#[derive(Debug, Clone, PartialEq, Eq)]
299pub enum LinkKind {
300    /// Axiom link: pairs a formula with its dual.
301    Axiom,
302    /// Cut link: connects two dual formula occurrences.
303    Cut,
304    /// Tensor link: combines two premises.
305    TensorLink,
306    /// Par link: breaks par into two sub-formulas.
307    ParLink,
308}
309/// Phase semantics model for linear logic (second extended version).
310#[allow(dead_code)]
311#[derive(Debug, Clone)]
312pub struct PhaseSpaceExt {
313    pub monoid_name: String,
314    pub facts_description: String,
315}
316impl PhaseSpaceExt {
317    #[allow(dead_code)]
318    pub fn new(monoid: &str) -> Self {
319        Self {
320            monoid_name: monoid.to_string(),
321            facts_description: "Facts: subsets F of M such that F^perp^perp = F".to_string(),
322        }
323    }
324    #[allow(dead_code)]
325    pub fn completeness_description(&self) -> String {
326        format!(
327            "Phase semantics (Girard) complete for MALL: provable <-> valid in all phase models over {}",
328            self.monoid_name
329        )
330    }
331}
332/// Linear type systems (connection to programming).
333#[allow(dead_code)]
334#[derive(Debug, Clone)]
335pub struct LinearTypeSystem {
336    pub language: String,
337    pub resource_tracking: bool,
338    pub affine_types: bool,
339}
340impl LinearTypeSystem {
341    #[allow(dead_code)]
342    pub fn linear_haskell() -> Self {
343        Self {
344            language: "Linear Haskell".to_string(),
345            resource_tracking: true,
346            affine_types: false,
347        }
348    }
349    #[allow(dead_code)]
350    pub fn rust_ownership() -> Self {
351        Self {
352            language: "Rust (ownership system)".to_string(),
353            resource_tracking: true,
354            affine_types: true,
355        }
356    }
357    #[allow(dead_code)]
358    pub fn uniqueness_types(lang: &str) -> Self {
359        Self {
360            language: lang.to_string(),
361            resource_tracking: true,
362            affine_types: false,
363        }
364    }
365    #[allow(dead_code)]
366    pub fn prevents_use_after_free(&self) -> bool {
367        self.resource_tracking
368    }
369}
370/// Public linear logic formula type matching the task specification.
371///
372/// This is the primary enum for constructing and manipulating linear logic
373/// formulas at the Rust level. It mirrors the kernel-level `LinFormula` but
374/// uses the canonical names from the specification.
375#[derive(Debug, Clone, PartialEq, Eq)]
376pub enum LinearFormula {
377    /// Atomic proposition `P`.
378    Atom(String),
379    /// Tensor product `A βŠ— B` (multiplicative conjunction).
380    Tensor(Box<LinearFormula>, Box<LinearFormula>),
381    /// Par `A β…‹ B` (multiplicative disjunction).
382    Par(Box<LinearFormula>, Box<LinearFormula>),
383    /// With `A & B` (additive conjunction).
384    With(Box<LinearFormula>, Box<LinearFormula>),
385    /// Plus `A βŠ• B` (additive disjunction).
386    Plus(Box<LinearFormula>, Box<LinearFormula>),
387    /// Of course `!A` (exponential β€” allows contraction and weakening).
388    OfCourse(Box<LinearFormula>),
389    /// Why not `?A` (exponential dual of `!A`).
390    WhyNot(Box<LinearFormula>),
391    /// Multiplicative unit `1`.
392    One,
393    /// Multiplicative unit (bottom) `βŠ₯`.
394    Bottom,
395    /// Additive unit (top) `⊀`.
396    Top,
397    /// Additive zero `0`.
398    Zero,
399    /// Dual `A^βŠ₯`.
400    Dual(Box<LinearFormula>),
401}
402impl LinearFormula {
403    /// Compute the linear negation / dual of a formula.
404    ///
405    /// - `(A βŠ— B)^βŠ₯ = A^βŠ₯ β…‹ B^βŠ₯`
406    /// - `(A β…‹ B)^βŠ₯ = A^βŠ₯ βŠ— B^βŠ₯`
407    /// - `(A & B)^βŠ₯ = A^βŠ₯ βŠ• B^βŠ₯`
408    /// - `(A βŠ• B)^βŠ₯ = A^βŠ₯ & B^βŠ₯`
409    /// - `(!A)^βŠ₯ = ?A^βŠ₯`
410    /// - `(?A)^βŠ₯ = !A^βŠ₯`
411    /// - `1^βŠ₯ = βŠ₯`, `βŠ₯^βŠ₯ = 1`, `⊀^βŠ₯ = 0`, `0^βŠ₯ = ⊀`
412    /// - `(A^βŠ₯)^βŠ₯ = A`
413    pub fn dual(self) -> Self {
414        match self {
415            LinearFormula::Atom(s) => LinearFormula::Dual(Box::new(LinearFormula::Atom(s))),
416            LinearFormula::Tensor(a, b) => {
417                LinearFormula::Par(Box::new(a.dual()), Box::new(b.dual()))
418            }
419            LinearFormula::Par(a, b) => {
420                LinearFormula::Tensor(Box::new(a.dual()), Box::new(b.dual()))
421            }
422            LinearFormula::With(a, b) => {
423                LinearFormula::Plus(Box::new(a.dual()), Box::new(b.dual()))
424            }
425            LinearFormula::Plus(a, b) => {
426                LinearFormula::With(Box::new(a.dual()), Box::new(b.dual()))
427            }
428            LinearFormula::OfCourse(a) => LinearFormula::WhyNot(Box::new(a.dual())),
429            LinearFormula::WhyNot(a) => LinearFormula::OfCourse(Box::new(a.dual())),
430            LinearFormula::One => LinearFormula::Bottom,
431            LinearFormula::Bottom => LinearFormula::One,
432            LinearFormula::Top => LinearFormula::Zero,
433            LinearFormula::Zero => LinearFormula::Top,
434            LinearFormula::Dual(a) => *a,
435        }
436    }
437    /// Check if this formula is in the multiplicative fragment (MLL).
438    pub fn is_multiplicative(&self) -> bool {
439        match self {
440            LinearFormula::Atom(_) | LinearFormula::Dual(_) => true,
441            LinearFormula::Tensor(a, b) | LinearFormula::Par(a, b) => {
442                a.is_multiplicative() && b.is_multiplicative()
443            }
444            LinearFormula::One | LinearFormula::Bottom => true,
445            _ => false,
446        }
447    }
448    /// Check if this formula is in the additive fragment.
449    pub fn is_additive(&self) -> bool {
450        match self {
451            LinearFormula::Atom(_) | LinearFormula::Dual(_) => true,
452            LinearFormula::With(a, b) | LinearFormula::Plus(a, b) => {
453                a.is_additive() && b.is_additive()
454            }
455            LinearFormula::Top | LinearFormula::Zero => true,
456            _ => false,
457        }
458    }
459    /// Check if this formula uses exponentials (`!` or `?`).
460    pub fn is_exponential(&self) -> bool {
461        match self {
462            LinearFormula::OfCourse(_) | LinearFormula::WhyNot(_) => true,
463            LinearFormula::Tensor(a, b)
464            | LinearFormula::Par(a, b)
465            | LinearFormula::With(a, b)
466            | LinearFormula::Plus(a, b) => a.is_exponential() || b.is_exponential(),
467            LinearFormula::Dual(a) => a.is_exponential(),
468            _ => false,
469        }
470    }
471    /// Linear implication `A ⊸ B = A^βŠ₯ β…‹ B`.
472    pub fn lollipop(a: LinearFormula, b: LinearFormula) -> LinearFormula {
473        LinearFormula::Par(Box::new(a.dual()), Box::new(b))
474    }
475    /// Complexity (number of binary connectives and exponentials).
476    pub fn complexity(&self) -> usize {
477        match self {
478            LinearFormula::Atom(_)
479            | LinearFormula::One
480            | LinearFormula::Bottom
481            | LinearFormula::Top
482            | LinearFormula::Zero => 0,
483            LinearFormula::Dual(a) => a.complexity(),
484            LinearFormula::OfCourse(a) | LinearFormula::WhyNot(a) => 1 + a.complexity(),
485            LinearFormula::Tensor(a, b)
486            | LinearFormula::Par(a, b)
487            | LinearFormula::With(a, b)
488            | LinearFormula::Plus(a, b) => 1 + a.complexity() + b.complexity(),
489        }
490    }
491}
492/// Game semantics: a game G = (moves, positions, plays).
493#[allow(dead_code)]
494#[derive(Debug, Clone)]
495pub struct LlGame {
496    pub formula: String,
497    pub player: String,
498    pub opponent: String,
499    pub winning_condition: String,
500}
501impl LlGame {
502    #[allow(dead_code)]
503    pub fn new(formula: &str) -> Self {
504        Self {
505            formula: formula.to_string(),
506            player: "Proponent (P)".to_string(),
507            opponent: "Opponent (O)".to_string(),
508            winning_condition: "P wins if O cannot move".to_string(),
509        }
510    }
511    #[allow(dead_code)]
512    pub fn tensor_game(g1: &str, g2: &str) -> Self {
513        Self::new(&format!("{g1} tensor {g2}"))
514    }
515    #[allow(dead_code)]
516    pub fn abramsky_jagadeesan_description(&self) -> String {
517        format!(
518            "Game semantics (Abramsky-Jagadeesan): formula {} has a game where P-strategy = proof",
519            self.formula
520        )
521    }
522}
523/// Bounded linear logic (Girard-Lafont-Regnard).
524#[allow(dead_code)]
525#[derive(Debug, Clone)]
526pub struct BoundedLinearLogic {
527    pub polynomial_bound: String,
528}
529impl BoundedLinearLogic {
530    #[allow(dead_code)]
531    pub fn new(bound: &str) -> Self {
532        Self {
533            polynomial_bound: bound.to_string(),
534        }
535    }
536    #[allow(dead_code)]
537    pub fn captures_ptime(&self) -> bool {
538        true
539    }
540    #[allow(dead_code)]
541    pub fn description(&self) -> String {
542        format!(
543            "BLL with bound {}: proofs correspond to polynomial-time algorithms",
544            self.polynomial_bound
545        )
546    }
547}
548/// Resource logic: linear logic as a logic of resource consumption.
549#[derive(Debug, Clone)]
550pub struct ResourceLogic {
551    /// Named resources available.
552    pub resources: Vec<String>,
553}
554impl ResourceLogic {
555    /// Create a new resource logic structure.
556    pub fn new(resources: Vec<String>) -> Self {
557        Self { resources }
558    }
559    /// Resource consumption model:
560    /// a process consumes resources from the context linearly (no copying).
561    pub fn resource_consumption_model(&self) -> String {
562        format!("consume({})", self.resources.join(", "))
563    }
564    /// Connection to separation logic:
565    /// the separating conjunction `*` in BI logic corresponds to tensor `βŠ—`.
566    pub fn separation_logic_connection(&self) -> String {
567        "BI separation conjunction * β‰… linear tensor βŠ—".to_string()
568    }
569    /// Check if a given resource is available.
570    pub fn has_resource(&self, name: &str) -> bool {
571        self.resources.contains(&name.to_string())
572    }
573    /// Consume a resource (returns new state with resource removed).
574    pub fn consume(&self, name: &str) -> Option<Self> {
575        if self.has_resource(name) {
576            let resources = self
577                .resources
578                .iter()
579                .filter(|&r| r != name)
580                .cloned()
581                .collect();
582            Some(Self { resources })
583        } else {
584            None
585        }
586    }
587    /// Total number of available resources.
588    pub fn count(&self) -> usize {
589        self.resources.len()
590    }
591}
592/// A formula in relevant logic R.
593#[derive(Debug, Clone, PartialEq, Eq)]
594pub enum RelFormula {
595    /// Atom.
596    Atom(String),
597    /// Negation Β¬A.
598    Neg(Box<RelFormula>),
599    /// Conjunction A ∧ B.
600    And(Box<RelFormula>, Box<RelFormula>),
601    /// Disjunction A ∨ B.
602    Or(Box<RelFormula>, Box<RelFormula>),
603    /// (Relevant) implication A β†’ B.
604    Implies(Box<RelFormula>, Box<RelFormula>),
605    /// Fusion (intensional conjunction) A ∘ B.
606    Fusion(Box<RelFormula>, Box<RelFormula>),
607}
608impl RelFormula {
609    /// Construct implication.
610    pub fn implies(a: RelFormula, b: RelFormula) -> Self {
611        RelFormula::Implies(Box::new(a), Box::new(b))
612    }
613    /// Construct conjunction.
614    pub fn and(a: RelFormula, b: RelFormula) -> Self {
615        RelFormula::And(Box::new(a), Box::new(b))
616    }
617    /// Construct disjunction.
618    pub fn or(a: RelFormula, b: RelFormula) -> Self {
619        RelFormula::Or(Box::new(a), Box::new(b))
620    }
621    /// Construct negation.
622    pub fn neg(a: RelFormula) -> Self {
623        RelFormula::Neg(Box::new(a))
624    }
625    /// Construct fusion.
626    pub fn fusion(a: RelFormula, b: RelFormula) -> Self {
627        RelFormula::Fusion(Box::new(a), Box::new(b))
628    }
629    /// The reflexivity axiom A β†’ A is always a theorem of R.
630    pub fn is_reflexivity_axiom(&self) -> bool {
631        if let RelFormula::Implies(a, b) = self {
632            a == b
633        } else {
634            false
635        }
636    }
637}
638/// Bunched implications (BI) logic formula.
639#[allow(dead_code)]
640#[derive(Debug, Clone)]
641pub enum BiFormula {
642    Atom(String),
643    Emp,
644    True,
645    SepConj(Box<BiFormula>, Box<BiFormula>),
646    SepImpl(Box<BiFormula>, Box<BiFormula>),
647    Conj(Box<BiFormula>, Box<BiFormula>),
648    Impl(Box<BiFormula>, Box<BiFormula>),
649    Disj(Box<BiFormula>, Box<BiFormula>),
650    Neg(Box<BiFormula>),
651}
652impl BiFormula {
653    #[allow(dead_code)]
654    pub fn atom(s: &str) -> Self {
655        Self::Atom(s.to_string())
656    }
657    #[allow(dead_code)]
658    pub fn sep_conj(a: BiFormula, b: BiFormula) -> Self {
659        Self::SepConj(Box::new(a), Box::new(b))
660    }
661    #[allow(dead_code)]
662    pub fn sep_impl(a: BiFormula, b: BiFormula) -> Self {
663        Self::SepImpl(Box::new(a), Box::new(b))
664    }
665    #[allow(dead_code)]
666    pub fn is_separation_connective(&self) -> bool {
667        matches!(
668            self,
669            BiFormula::SepConj(..) | BiFormula::SepImpl(..) | BiFormula::Emp
670        )
671    }
672}
673/// Dialectica transformation (Godel's functional interpretation via linear logic).
674#[allow(dead_code)]
675#[derive(Debug, Clone)]
676pub struct DialecticaTransform {
677    pub formula: String,
678    pub dialectica_type: String,
679}
680impl DialecticaTransform {
681    #[allow(dead_code)]
682    pub fn new(formula: &str, dtype: &str) -> Self {
683        Self {
684            formula: formula.to_string(),
685            dialectica_type: dtype.to_string(),
686        }
687    }
688    #[allow(dead_code)]
689    pub fn de_paiva_description(&self) -> String {
690        format!("de Paiva's Dialectica categories model linear logic (with !A = forall x. A^x)",)
691    }
692}
693/// Game semantics for linear logic: arenas and innocent strategies.
694#[derive(Debug, Clone)]
695pub struct GameSemantics {
696    /// Named arenas (one per type in the context).
697    pub arenas: Vec<String>,
698}
699impl GameSemantics {
700    /// Create a new game semantics structure.
701    pub fn new(arenas: Vec<String>) -> Self {
702        Self { arenas }
703    }
704    /// Innocent strategies: strategies that only depend on the visible history
705    /// (the P-view), not the full history.
706    pub fn innocent_strategies(&self) -> Vec<String> {
707        self.arenas
708            .iter()
709            .map(|a| format!("InnocentStrat({})", a))
710            .collect()
711    }
712    /// Composition of strategies (Abramsky-Jagadeesan-Malacaria style).
713    pub fn composition(&self) -> String {
714        if self.arenas.len() < 2 {
715            "id".to_string()
716        } else {
717            self.arenas
718                .windows(2)
719                .map(|w| format!("{};{}", w[0], w[1]))
720                .collect::<Vec<_>>()
721                .join(" ∘ ")
722        }
723    }
724    /// Tensor product of arenas.
725    pub fn tensor(&self) -> String {
726        self.arenas.join(" βŠ— ")
727    }
728    /// Full completeness: every innocent strategy corresponds to a proof.
729    pub fn full_completeness_statement(&self) -> String {
730        "Every innocent strategy is the denotation of a linear logic proof".to_string()
731    }
732}
733/// A coherence space: a reflexive, symmetric binary relation (web, coh).
734#[derive(Debug, Clone)]
735pub struct CoherenceSpace {
736    /// Number of tokens (web size).
737    pub n_tokens: usize,
738    /// Coherence matrix: coh[i][j] = true iff i and j are coherent (or equal).
739    pub coh: Vec<Vec<bool>>,
740}
741impl CoherenceSpace {
742    /// Create the flat coherence space: all distinct tokens are incoherent.
743    pub fn flat(n: usize) -> Self {
744        let mut coh = vec![vec![false; n]; n];
745        for i in 0..n {
746            coh[i][i] = true;
747        }
748        CoherenceSpace { n_tokens: n, coh }
749    }
750    /// Create the complete coherence space: all tokens are mutually coherent.
751    pub fn complete(n: usize) -> Self {
752        CoherenceSpace {
753            n_tokens: n,
754            coh: vec![vec![true; n]; n],
755        }
756    }
757    /// Check if a set of tokens forms a clique (mutually coherent).
758    pub fn is_clique(&self, tokens: &[usize]) -> bool {
759        for &i in tokens {
760            for &j in tokens {
761                if i < self.n_tokens && j < self.n_tokens && !self.coh[i][j] {
762                    return false;
763                }
764            }
765        }
766        true
767    }
768    /// Check if a set of tokens is an antichain (mutually incoherent, except self).
769    pub fn is_antichain(&self, tokens: &[usize]) -> bool {
770        for (idx, &i) in tokens.iter().enumerate() {
771            for &j in &tokens[idx + 1..] {
772                if i < self.n_tokens && j < self.n_tokens && self.coh[i][j] {
773                    return false;
774                }
775            }
776        }
777        true
778    }
779    /// Tensor product: web is n1 Γ— n2, coherent iff both components coherent.
780    pub fn tensor(&self, other: &CoherenceSpace) -> CoherenceSpace {
781        let n = self.n_tokens * other.n_tokens;
782        let mut coh = vec![vec![false; n]; n];
783        for i1 in 0..self.n_tokens {
784            for j1 in 0..other.n_tokens {
785                for i2 in 0..self.n_tokens {
786                    for j2 in 0..other.n_tokens {
787                        let row = i1 * other.n_tokens + j1;
788                        let col = i2 * other.n_tokens + j2;
789                        coh[row][col] = self.coh[i1][i2] && other.coh[j1][j2];
790                    }
791                }
792            }
793        }
794        CoherenceSpace { n_tokens: n, coh }
795    }
796    /// With connective: disjoint union (tagged tokens), coherent within each component.
797    pub fn with(&self, other: &CoherenceSpace) -> CoherenceSpace {
798        let n = self.n_tokens + other.n_tokens;
799        let mut coh = vec![vec![false; n]; n];
800        for i in 0..self.n_tokens {
801            for j in 0..self.n_tokens {
802                coh[i][j] = self.coh[i][j];
803            }
804        }
805        let offset = self.n_tokens;
806        for i in 0..other.n_tokens {
807            for j in 0..other.n_tokens {
808                coh[offset + i][offset + j] = other.coh[i][j];
809            }
810        }
811        CoherenceSpace { n_tokens: n, coh }
812    }
813}
814/// The four rules governing the exponential modality `!` in linear logic.
815#[derive(Debug, Clone, Default)]
816pub struct ExponentialRules;
817impl ExponentialRules {
818    /// Create a new exponential rules structure.
819    pub fn new() -> Self {
820        Self
821    }
822    /// Dereliction: `!A ⊒ A` (use a resource once).
823    pub fn dereliction(&self, a: &str) -> String {
824        format!("dereliction(!{a} ⊒ {a})")
825    }
826    /// Contraction: `!A ⊒ !A βŠ— !A` (duplicate a resource).
827    pub fn contraction(&self, a: &str) -> String {
828        format!("contraction(!{a} ⊒ !{a} βŠ— !{a})")
829    }
830    /// Weakening: `!A ⊒ 1` (discard a resource).
831    pub fn weakening(&self, a: &str) -> String {
832        format!("weakening(!{a} ⊒ 1)")
833    }
834    /// Promotion: `!Ξ“ ⊒ A β†’ !Ξ“ ⊒ !A` (promote to exponential).
835    pub fn promotion(&self, a: &str, gamma: &[&str]) -> String {
836        let ctx = gamma.join(", ");
837        format!("promotion(![{ctx}] ⊒ {a}  β†’  ![{ctx}] ⊒ !{a})")
838    }
839    /// Storage: `!A ≑ !A βŠ— !A` (idempotency of `!`).
840    pub fn storage(&self, a: &str) -> String {
841        format!("storage(!{a} ≑ !{a} βŠ— !{a})")
842    }
843}
844/// A phase space: a commutative monoid (M, Β·, e) with distinguished subset bot.
845/// We represent M as integers mod p for concreteness.
846#[derive(Debug, Clone)]
847pub struct PhaseSpace {
848    /// Elements of the monoid (indices 0..n).
849    pub n: usize,
850    /// Multiplication table: mul[i][j] = i Β· j.
851    pub mul: Vec<Vec<usize>>,
852    /// Identity element.
853    pub identity: usize,
854    /// The distinguished subset βŠ₯ βŠ† M.
855    pub bot: Vec<bool>,
856}
857impl PhaseSpace {
858    /// Create a named trivial phase space {e} with βŠ₯ = {e}.
859    /// The `_name` parameter is informational only.
860    pub fn new(_name: &str) -> Self {
861        Self::trivial()
862    }
863    /// Description of completeness for phase semantics.
864    pub fn completeness_description(&self) -> String {
865        format!(
866            "Phase semantics complete for linear logic: monoid size={}, bot={} elements",
867            self.n,
868            self.bot.iter().filter(|&&b| b).count()
869        )
870    }
871    /// Create the trivial phase space {e} with βŠ₯ = {e}.
872    pub fn trivial() -> Self {
873        PhaseSpace {
874            n: 1,
875            mul: vec![vec![0]],
876            identity: 0,
877            bot: vec![true],
878        }
879    }
880    /// Multiply two elements.
881    pub fn multiply(&self, a: usize, b: usize) -> usize {
882        self.mul[a % self.n][b % self.n]
883    }
884    /// Closure A^βŠ₯βŠ₯: double orthogonal of a subset A βŠ† M.
885    /// First computes A^βŠ₯ = {m | βˆ€ a ∈ A, mΒ·a ∈ βŠ₯}, then its orthogonal.
886    pub fn closure(&self, subset: &[bool]) -> Vec<bool> {
887        let perp = self.orthogonal(subset);
888        self.orthogonal(&perp)
889    }
890    /// Orthogonal: A^βŠ₯ = {m | βˆ€ a ∈ A, mΒ·a ∈ βŠ₯}.
891    pub fn orthogonal(&self, subset: &[bool]) -> Vec<bool> {
892        let mut result = vec![true; self.n];
893        for m in 0..self.n {
894            for a in 0..self.n {
895                if subset[a] {
896                    let prod = self.multiply(m, a);
897                    if !self.bot[prod] {
898                        result[m] = false;
899                        break;
900                    }
901                }
902            }
903        }
904        result
905    }
906    /// Check whether a subset is a fact (= βŠ₯-closed, i.e., A = A^βŠ₯βŠ₯).
907    pub fn is_fact(&self, subset: &[bool]) -> bool {
908        let closed = self.closure(subset);
909        subset.iter().zip(closed.iter()).all(|(a, b)| a == b)
910    }
911    /// Count elements in a subset.
912    pub fn subset_size(subset: &[bool]) -> usize {
913        subset.iter().filter(|&&b| b).count()
914    }
915}
916/// A link in a proof structure.
917#[derive(Debug, Clone)]
918pub struct Link {
919    /// Kind of link.
920    pub kind: LinkKind,
921    /// Indices of conclusion formula occurrences.
922    pub conclusions: Vec<usize>,
923    /// Indices of premise formula occurrences.
924    pub premises: Vec<usize>,
925}
926impl Link {
927    /// Create an axiom link between two dual formula occurrences.
928    pub fn axiom(i: usize, j: usize) -> Self {
929        Link {
930            kind: LinkKind::Axiom,
931            conclusions: vec![i, j],
932            premises: vec![],
933        }
934    }
935    /// Create a tensor link.
936    pub fn tensor(premise_a: usize, premise_b: usize, conclusion: usize) -> Self {
937        Link {
938            kind: LinkKind::TensorLink,
939            conclusions: vec![conclusion],
940            premises: vec![premise_a, premise_b],
941        }
942    }
943    /// Create a par link.
944    pub fn par(premise_a: usize, premise_b: usize, conclusion: usize) -> Self {
945        Link {
946            kind: LinkKind::ParLink,
947            conclusions: vec![conclusion],
948            premises: vec![premise_a, premise_b],
949        }
950    }
951}
952/// A formula in propositional linear logic.
953#[derive(Debug, Clone, PartialEq, Eq, Hash)]
954pub enum LinFormula {
955    /// Propositional atom.
956    Atom(String),
957    /// Multiplicative unit 1.
958    One,
959    /// Multiplicative bottom βŠ₯.
960    Bot,
961    /// Additive unit ⊀.
962    Top,
963    /// Additive zero 0.
964    Zero,
965    /// Tensor product A βŠ— B.
966    Tensor(Box<LinFormula>, Box<LinFormula>),
967    /// Par A β…‹ B.
968    Par(Box<LinFormula>, Box<LinFormula>),
969    /// With A & B (additive conjunction).
970    With(Box<LinFormula>, Box<LinFormula>),
971    /// Plus A βŠ• B (additive disjunction).
972    Plus(Box<LinFormula>, Box<LinFormula>),
973    /// Bang !A (of-course).
974    Bang(Box<LinFormula>),
975    /// Why-not ?A (why-not).
976    WhyNot(Box<LinFormula>),
977    /// Linear negation A^βŠ₯.
978    Neg(Box<LinFormula>),
979}
980impl LinFormula {
981    /// Dual (linear negation) of a formula, using De Morgan laws.
982    /// Involutive: (A^βŠ₯)^βŠ₯ = A.
983    pub fn dual(&self) -> LinFormula {
984        match self {
985            LinFormula::Atom(s) => LinFormula::Neg(Box::new(LinFormula::Atom(s.clone()))),
986            LinFormula::Neg(inner) => *inner.clone(),
987            LinFormula::One => LinFormula::Bot,
988            LinFormula::Bot => LinFormula::One,
989            LinFormula::Top => LinFormula::Zero,
990            LinFormula::Zero => LinFormula::Top,
991            LinFormula::Tensor(a, b) => LinFormula::Par(Box::new(a.dual()), Box::new(b.dual())),
992            LinFormula::Par(a, b) => LinFormula::Tensor(Box::new(a.dual()), Box::new(b.dual())),
993            LinFormula::With(a, b) => LinFormula::Plus(Box::new(a.dual()), Box::new(b.dual())),
994            LinFormula::Plus(a, b) => LinFormula::With(Box::new(a.dual()), Box::new(b.dual())),
995            LinFormula::Bang(a) => LinFormula::WhyNot(Box::new(a.dual())),
996            LinFormula::WhyNot(a) => LinFormula::Bang(Box::new(a.dual())),
997        }
998    }
999    /// Linear implication A ⊸ B = A^βŠ₯ β…‹ B.
1000    pub fn lollipop(a: LinFormula, b: LinFormula) -> LinFormula {
1001        LinFormula::Par(Box::new(a.dual()), Box::new(b))
1002    }
1003    /// Check if the formula is an atom or literal.
1004    pub fn is_literal(&self) -> bool {
1005        match self {
1006            LinFormula::Atom(_) => true,
1007            LinFormula::Neg(inner) => matches!(inner.as_ref(), LinFormula::Atom(_)),
1008            _ => false,
1009        }
1010    }
1011    /// Check if the formula is in the multiplicative fragment (no & or βŠ•).
1012    pub fn is_multiplicative(&self) -> bool {
1013        match self {
1014            LinFormula::Atom(_) | LinFormula::Neg(_) | LinFormula::One | LinFormula::Bot => true,
1015            LinFormula::Tensor(a, b) | LinFormula::Par(a, b) => {
1016                a.is_multiplicative() && b.is_multiplicative()
1017            }
1018            LinFormula::Bang(a) | LinFormula::WhyNot(a) => a.is_multiplicative(),
1019            LinFormula::With(_, _) | LinFormula::Plus(_, _) => false,
1020            LinFormula::Top | LinFormula::Zero => false,
1021        }
1022    }
1023    /// Subformula depth.
1024    pub fn depth(&self) -> usize {
1025        match self {
1026            LinFormula::Atom(_)
1027            | LinFormula::One
1028            | LinFormula::Bot
1029            | LinFormula::Top
1030            | LinFormula::Zero => 0,
1031            LinFormula::Neg(a) | LinFormula::Bang(a) | LinFormula::WhyNot(a) => 1 + a.depth(),
1032            LinFormula::Tensor(a, b)
1033            | LinFormula::Par(a, b)
1034            | LinFormula::With(a, b)
1035            | LinFormula::Plus(a, b) => 1 + a.depth().max(b.depth()),
1036        }
1037    }
1038    /// Number of connectives in the formula.
1039    pub fn complexity(&self) -> usize {
1040        match self {
1041            LinFormula::Atom(_)
1042            | LinFormula::One
1043            | LinFormula::Bot
1044            | LinFormula::Top
1045            | LinFormula::Zero => 0,
1046            LinFormula::Neg(a) | LinFormula::Bang(a) | LinFormula::WhyNot(a) => 1 + a.complexity(),
1047            LinFormula::Tensor(a, b)
1048            | LinFormula::Par(a, b)
1049            | LinFormula::With(a, b)
1050            | LinFormula::Plus(a, b) => 1 + a.complexity() + b.complexity(),
1051        }
1052    }
1053    /// Collect all atoms occurring in the formula.
1054    pub fn atoms(&self) -> Vec<String> {
1055        let mut result = Vec::new();
1056        self.collect_atoms(&mut result);
1057        result.sort();
1058        result.dedup();
1059        result
1060    }
1061    fn collect_atoms(&self, out: &mut Vec<String>) {
1062        match self {
1063            LinFormula::Atom(s) => out.push(s.clone()),
1064            LinFormula::Neg(a) | LinFormula::Bang(a) | LinFormula::WhyNot(a) => {
1065                a.collect_atoms(out);
1066            }
1067            LinFormula::Tensor(a, b)
1068            | LinFormula::Par(a, b)
1069            | LinFormula::With(a, b)
1070            | LinFormula::Plus(a, b) => {
1071                a.collect_atoms(out);
1072                b.collect_atoms(out);
1073            }
1074            _ => {}
1075        }
1076    }
1077}
1078/// A linear logic formula.
1079#[allow(dead_code)]
1080#[derive(Debug, Clone, PartialEq)]
1081pub enum LlFormula {
1082    Atom(String),
1083    Neg(Box<LlFormula>),
1084    Tensor(Box<LlFormula>, Box<LlFormula>),
1085    Par(Box<LlFormula>, Box<LlFormula>),
1086    Plus(Box<LlFormula>, Box<LlFormula>),
1087    With(Box<LlFormula>, Box<LlFormula>),
1088    One,
1089    Bottom,
1090    Top,
1091    Zero,
1092    OfCourse(Box<LlFormula>),
1093    WhyNot(Box<LlFormula>),
1094    Forall(String, Box<LlFormula>),
1095    Exists(String, Box<LlFormula>),
1096}
1097impl LlFormula {
1098    #[allow(dead_code)]
1099    pub fn atom(s: &str) -> Self {
1100        Self::Atom(s.to_string())
1101    }
1102    #[allow(dead_code)]
1103    pub fn tensor(a: LlFormula, b: LlFormula) -> Self {
1104        Self::Tensor(Box::new(a), Box::new(b))
1105    }
1106    #[allow(dead_code)]
1107    pub fn par(a: LlFormula, b: LlFormula) -> Self {
1108        Self::Par(Box::new(a), Box::new(b))
1109    }
1110    #[allow(dead_code)]
1111    pub fn with_op(a: LlFormula, b: LlFormula) -> Self {
1112        Self::With(Box::new(a), Box::new(b))
1113    }
1114    #[allow(dead_code)]
1115    pub fn plus(a: LlFormula, b: LlFormula) -> Self {
1116        Self::Plus(Box::new(a), Box::new(b))
1117    }
1118    #[allow(dead_code)]
1119    pub fn of_course(a: LlFormula) -> Self {
1120        Self::OfCourse(Box::new(a))
1121    }
1122    #[allow(dead_code)]
1123    pub fn why_not(a: LlFormula) -> Self {
1124        Self::WhyNot(Box::new(a))
1125    }
1126    #[allow(dead_code)]
1127    pub fn neg(a: LlFormula) -> Self {
1128        Self::Neg(Box::new(a))
1129    }
1130    #[allow(dead_code)]
1131    pub fn is_multiplicative(&self) -> bool {
1132        matches!(
1133            self,
1134            LlFormula::Tensor(..) | LlFormula::Par(..) | LlFormula::One | LlFormula::Bottom
1135        )
1136    }
1137    #[allow(dead_code)]
1138    pub fn is_additive(&self) -> bool {
1139        matches!(
1140            self,
1141            LlFormula::Plus(..) | LlFormula::With(..) | LlFormula::Top | LlFormula::Zero
1142        )
1143    }
1144    #[allow(dead_code)]
1145    pub fn is_exponential(&self) -> bool {
1146        matches!(self, LlFormula::OfCourse(..) | LlFormula::WhyNot(..))
1147    }
1148    #[allow(dead_code)]
1149    pub fn linear_negation(&self) -> LlFormula {
1150        match self {
1151            LlFormula::Atom(s) => LlFormula::Neg(Box::new(LlFormula::Atom(s.clone()))),
1152            LlFormula::Neg(a) => *a.clone(),
1153            LlFormula::Tensor(a, b) => LlFormula::par(a.linear_negation(), b.linear_negation()),
1154            LlFormula::Par(a, b) => LlFormula::tensor(a.linear_negation(), b.linear_negation()),
1155            LlFormula::Plus(a, b) => LlFormula::with_op(a.linear_negation(), b.linear_negation()),
1156            LlFormula::With(a, b) => LlFormula::plus(a.linear_negation(), b.linear_negation()),
1157            LlFormula::One => LlFormula::Bottom,
1158            LlFormula::Bottom => LlFormula::One,
1159            LlFormula::Top => LlFormula::Zero,
1160            LlFormula::Zero => LlFormula::Top,
1161            LlFormula::OfCourse(a) => LlFormula::why_not(a.linear_negation()),
1162            LlFormula::WhyNot(a) => LlFormula::of_course(a.linear_negation()),
1163            LlFormula::Forall(x, a) => LlFormula::Exists(x.clone(), Box::new(a.linear_negation())),
1164            LlFormula::Exists(x, a) => LlFormula::Forall(x.clone(), Box::new(a.linear_negation())),
1165        }
1166    }
1167}
1168/// A proof structure: a set of formula occurrences and links.
1169#[derive(Debug, Clone)]
1170pub struct ProofStructure {
1171    /// Number of formula occurrences.
1172    pub n_formulas: usize,
1173    /// The links (axiom, cut, tensor, par, etc.).
1174    pub links: Vec<Link>,
1175}
1176impl ProofStructure {
1177    /// Create a new empty proof structure with `n` formula occurrences.
1178    pub fn new(n_formulas: usize) -> Self {
1179        ProofStructure {
1180            n_formulas,
1181            links: Vec::new(),
1182        }
1183    }
1184    /// Add a link to the proof structure.
1185    pub fn add_link(&mut self, link: Link) {
1186        self.links.push(link);
1187    }
1188    /// Check the Danos-Regnier correctness criterion (simplified: for MLL axiom nets).
1189    ///
1190    /// A proof structure is correct iff every switching (choice of one conclusion
1191    /// for each par link) yields a spanning tree of the graph. Here we apply a
1192    /// union-find acyclicity check on the axiom links.
1193    pub fn is_correct(&self) -> bool {
1194        if self.n_formulas == 0 {
1195            return true;
1196        }
1197        let mut parent: Vec<usize> = (0..self.n_formulas).collect();
1198        fn find(parent: &mut Vec<usize>, x: usize) -> usize {
1199            if parent[x] != x {
1200                parent[x] = find(parent, parent[x]);
1201            }
1202            parent[x]
1203        }
1204        let mut edge_count = 0usize;
1205        for link in &self.links {
1206            if link.kind == LinkKind::Axiom && link.conclusions.len() == 2 {
1207                let a = link.conclusions[0];
1208                let b = link.conclusions[1];
1209                if a >= self.n_formulas || b >= self.n_formulas {
1210                    return false;
1211                }
1212                let ra = find(&mut parent, a);
1213                let rb = find(&mut parent, b);
1214                if ra == rb {
1215                    return false;
1216                }
1217                parent[ra] = rb;
1218                edge_count += 1;
1219            }
1220        }
1221        edge_count * 2 == self.n_formulas
1222    }
1223    /// Count axiom links.
1224    pub fn axiom_count(&self) -> usize {
1225        self.links
1226            .iter()
1227            .filter(|l| l.kind == LinkKind::Axiom)
1228            .count()
1229    }
1230    /// Count cut links.
1231    pub fn cut_count(&self) -> usize {
1232        self.links
1233            .iter()
1234            .filter(|l| l.kind == LinkKind::Cut)
1235            .count()
1236    }
1237}
1238/// Proof net for multiplicative linear logic (second extended version).
1239#[allow(dead_code)]
1240#[derive(Debug, Clone)]
1241pub struct ProofStructureExt {
1242    pub sequent: String,
1243    pub num_axiom_links: usize,
1244    pub is_correct: bool,
1245}
1246impl ProofStructureExt {
1247    #[allow(dead_code)]
1248    pub fn new(sequent: &str, axiom_links: usize, correct: bool) -> Self {
1249        Self {
1250            sequent: sequent.to_string(),
1251            num_axiom_links: axiom_links,
1252            is_correct: correct,
1253        }
1254    }
1255    #[allow(dead_code)]
1256    pub fn correctness_criterion(&self) -> String {
1257        "Girard's correctness criterion: every switching of par is acyclic and connected"
1258            .to_string()
1259    }
1260    #[allow(dead_code)]
1261    pub fn cut_elimination_description(&self) -> String {
1262        "MLL cut elimination: geometrically reduces proof net size (RetorΓ©'s criterion)".to_string()
1263    }
1264}
1265/// A proof net: a list of formulas (formula occurrences) with axiom/cut links.
1266#[derive(Debug, Clone)]
1267pub struct ProofNet {
1268    /// The formulas (conclusions) of the proof net.
1269    pub formulas: Vec<LinearFormula>,
1270    /// Links: pairs of formula occurrence indices connected by axiom or cut.
1271    pub links: Vec<(usize, usize)>,
1272}
1273impl ProofNet {
1274    /// Create a new proof net with the given formulas and no links.
1275    pub fn new(formulas: Vec<LinearFormula>) -> Self {
1276        Self {
1277            formulas,
1278            links: Vec::new(),
1279        }
1280    }
1281    /// Add a link between formula occurrences `i` and `j`.
1282    pub fn add_link(&mut self, i: usize, j: usize) {
1283        self.links.push((i, j));
1284    }
1285    /// Check if the proof net is well-typed:
1286    /// every formula occurrence appears in exactly one link.
1287    pub fn is_well_typed(&self) -> bool {
1288        let n = self.formulas.len();
1289        if n == 0 {
1290            return true;
1291        }
1292        let mut count = vec![0usize; n];
1293        for &(i, j) in &self.links {
1294            if i >= n || j >= n {
1295                return false;
1296            }
1297            count[i] += 1;
1298            count[j] += 1;
1299        }
1300        count.iter().all(|&c| c == 1)
1301    }
1302    /// Danos-Regnier correctness criterion:
1303    /// removing any subset of par-links leaves a connected acyclic graph.
1304    ///
1305    /// This is a simplified check: we verify the axiom links form a perfect
1306    /// matching and the graph is connected (full DR requires switching).
1307    pub fn correctness_criterion_danos_regnier(&self) -> bool {
1308        self.is_well_typed() && !self.formulas.is_empty()
1309    }
1310    /// Number of formula occurrences.
1311    pub fn size(&self) -> usize {
1312        self.formulas.len()
1313    }
1314}
1315/// Geometry of interaction.
1316#[allow(dead_code)]
1317#[derive(Debug, Clone)]
1318pub struct GeometryOfInteraction {
1319    pub description: String,
1320}
1321impl GeometryOfInteraction {
1322    #[allow(dead_code)]
1323    pub fn girard_goi() -> Self {
1324        Self {
1325            description:
1326                "Girard's GoI: cut elimination as execution formula in a traced monoidal category"
1327                    .to_string(),
1328        }
1329    }
1330    #[allow(dead_code)]
1331    pub fn dynamic_description(&self) -> String {
1332        "Paths in proof nets compose via execution formula: feedback loop".to_string()
1333    }
1334}
1335/// A quantale: a complete lattice with an associative binary operation
1336/// that distributes over arbitrary joins. Quantales model substructural logics.
1337#[derive(Debug, Clone)]
1338pub struct Quantales {
1339    /// Name of the quantale.
1340    pub quantale: String,
1341}
1342impl Quantales {
1343    /// Create a new quantale.
1344    pub fn new(quantale: impl Into<String>) -> Self {
1345        Self {
1346            quantale: quantale.into(),
1347        }
1348    }
1349    /// Check if this quantale is a model of linear logic (a `*`-autonomous quantale).
1350    pub fn is_linear_category(&self) -> bool {
1351        !self.quantale.is_empty()
1352    }
1353    /// Check if this is a `*`-autonomous (star-autonomous) category.
1354    ///
1355    /// A `*`-autonomous category is a symmetric monoidal category with a dualizing object,
1356    /// giving models of classical linear logic (MLL).
1357    pub fn star_autonomous(&self) -> String {
1358        format!("*-Autonomous({})", self.quantale)
1359    }
1360    /// The free `*`-autonomous category on a set of atoms.
1361    pub fn free_star_autonomous(atoms: &[&str]) -> Self {
1362        Self::new(format!("Free*Aut({{{}}})", atoms.join(", ")))
1363    }
1364    /// The phase space quantale over a monoid.
1365    pub fn phase_space_quantale(monoid: &str) -> Self {
1366        Self::new(format!("PhaseSpace({})", monoid))
1367    }
1368    /// The Chu construction: a way to build `*`-autonomous categories.
1369    pub fn chu_construction(&self, k: &str) -> String {
1370        format!("Chu({}, {})", self.quantale, k)
1371    }
1372    /// The dualizing object (bottom element in MLL models).
1373    pub fn dualizing_object(&self) -> String {
1374        format!("βŠ₯_{}", self.quantale)
1375    }
1376}