Skip to main content

karpal_diagram/
coherence.rs

1//! Coherence law witnesses for monoidal categories.
2//!
3//! Provides type-level proofs for the pentagon, triangle, and hexagon
4//! identities that every (braided) monoidal category must satisfy.
5//!
6//! These are *axioms* of the `Tensor` and `Braiding` traits — the
7//! `Justifies` impls declare them true, and the `verify_*` functions
8//! produce type-level `Rewrite` witnesses.
9
10use karpal_proof::rewrite::Justifies;
11#[cfg(any(feature = "std", feature = "alloc"))]
12use karpal_proof::rewrite::Rewrite;
13
14// ---------------------------------------------------------------------------
15// Pentagon identity
16// ---------------------------------------------------------------------------
17
18/// Proof term: both reassociation paths from `((A⊗B)⊗C)⊗D` to `A⊗(B⊗(C⊗D))` coincide.
19///
20/// - **upper** path: `(α ⊗ id) ; α ; (id ⊗ α)`
21/// - **lower** path: `α ; α`
22pub struct PentagonIdentity;
23
24impl<A, B, C, D> Justifies<(((A, B), C), D), (A, (B, (C, D)))> for PentagonIdentity {}
25
26/// Construct a pentagon identity witness.
27///
28/// Returns a type-level `Rewrite` witnessing the pentagon coherence law.
29#[cfg(any(feature = "std", feature = "alloc"))]
30#[allow(clippy::type_complexity)]
31pub fn verify_pentagon<A, B, C, D>() -> Rewrite<(((A, B), C), D), (A, (B, (C, D))), PentagonIdentity>
32{
33    Rewrite::witness()
34}
35
36// ---------------------------------------------------------------------------
37// Triangle identity
38// ---------------------------------------------------------------------------
39
40/// Proof term: both cancellation paths from `(A⊗I)⊗B` to `A⊗B` coincide.
41///
42/// - **left** path: `ρ_A ⊗ id_B`
43/// - **right** path: `α_{A,I,B} ; (id_A ⊗ λ_B)`
44pub struct TriangleIdentity;
45
46impl<A, B> Justifies<((A, ()), B), (A, B)> for TriangleIdentity {}
47
48/// Construct a triangle identity witness.
49///
50/// Returns a type-level `Rewrite` witnessing the triangle coherence law.
51#[cfg(any(feature = "std", feature = "alloc"))]
52#[allow(clippy::type_complexity)]
53pub fn verify_triangle<A, B>() -> Rewrite<((A, ()), B), (A, B), TriangleIdentity> {
54    Rewrite::witness()
55}
56
57// ---------------------------------------------------------------------------
58// Hexagon identity
59// ---------------------------------------------------------------------------
60
61/// Proof term: two paths from `(A⊗B)⊗C` to `(B⊗C)⊗A` coincide.
62///
63/// - **upper** path: `braid ; α⁻¹ ; braid ; α⁻¹`
64/// - **lower** path: `α ; braid`
65pub struct HexagonIdentity;
66
67impl<A, B, C> Justifies<((A, B), C), ((B, C), A)> for HexagonIdentity {}
68
69/// Construct a hexagon identity witness.
70///
71/// Returns a type-level `Rewrite` witnessing the hexagon coherence law.
72#[cfg(any(feature = "std", feature = "alloc"))]
73#[allow(clippy::type_complexity)]
74pub fn verify_hexagon<A, B, C>() -> Rewrite<((A, B), C), ((B, C), A), HexagonIdentity> {
75    Rewrite::witness()
76}
77
78// ---------------------------------------------------------------------------
79// Diagrammatic rewriting bridge
80// ---------------------------------------------------------------------------
81
82/// Justification: diagram equivalence established by runtime normalization.
83///
84/// This bridges the runtime `Diagram::equivalent_to` check with the
85/// `karpal-proof` type-level `Justifies`/`Rewrite` framework. Any two
86/// types `A` and `B` can serve as phantom markers — the real evidence
87/// comes from normalizing the two diagrams and checking they match.
88pub struct ByNormalization;
89
90impl<A, B> Justifies<A, B> for ByNormalization {}
91
92/// Prove two diagrams are equivalent via normalization.
93///
94/// Normalizes both diagrams and, if they match, returns a type-level
95/// `Rewrite` witness. The type parameters `A` and `B` are phantom markers
96/// that let callers distinguish different proof instances.
97///
98/// Returns `None` when the diagrams differ after normalization.
99#[cfg(any(feature = "std", feature = "alloc"))]
100pub fn equivalent_proved<A, B>(
101    a: &crate::Diagram,
102    b: &crate::Diagram,
103) -> Option<Rewrite<A, B, ByNormalization>> {
104    if a.equivalent_to(b) {
105        Some(Rewrite::witness())
106    } else {
107        None
108    }
109}
110
111// ---------------------------------------------------------------------------
112// Compact-closed / yanking witnesses
113// ---------------------------------------------------------------------------
114
115/// Justification: cup/cap yanking equation verified by normalization.
116///
117/// The yanking equation `(cup ⊗ id) ; (id ⊗ cap) = id` is a defining
118/// axiom of compact-closed categories. This witness asserts it holds
119/// for the given diagram arity.
120pub struct ByYanking;
121
122impl<A, B> Justifies<A, B> for ByYanking {}
123
124/// Prove the yanking equation for the given arity.
125///
126/// Builds both left and right yanking diagrams, verifies they normalize
127/// to identity, and returns a type-level `Rewrite` witness.
128///
129/// # Panics
130///
131/// Panics if the yanking diagram fails to normalize to identity —
132/// this should never happen for a correct monoidal diagram implementation.
133#[cfg(any(feature = "std", feature = "alloc"))]
134pub fn prove_yanking<A, B>(arity: usize) -> Rewrite<A, B, ByYanking> {
135    let left_yank = crate::Diagram::cup(arity)
136        .parallel(crate::Diagram::identity(arity))
137        .then(crate::Diagram::identity(arity).parallel(crate::Diagram::cap(arity)));
138
139    let right_yank = crate::Diagram::identity(arity)
140        .parallel(crate::Diagram::cup(arity))
141        .then(crate::Diagram::cap(arity).parallel(crate::Diagram::identity(arity)));
142
143    assert!(
144        left_yank.equivalent_to(&crate::Diagram::identity(arity)),
145        "left yanking equation failed for arity {arity}"
146    );
147    assert!(
148        right_yank.equivalent_to(&crate::Diagram::identity(arity)),
149        "right yanking equation failed for arity {arity}"
150    );
151
152    Rewrite::witness()
153}
154
155// ---------------------------------------------------------------------------
156// Coherence verification integration
157// ---------------------------------------------------------------------------
158
159/// Verification backend for monoidal coherence law certificates.
160#[cfg(any(feature = "std", feature = "alloc"))]
161pub struct CoherenceCertificate;
162
163#[cfg(any(feature = "std", feature = "alloc"))]
164impl karpal_verify::VerificationBackend for CoherenceCertificate {
165    const NAME: &'static str = "karpal-diagram-coherence";
166}
167
168/// Generate verification certificates for the three monoidal coherence laws.
169///
170/// Returns one `Certificate` each for the pentagon, triangle, and hexagon
171/// identities, using the `karpal-proof` test evidence bridge.
172#[cfg(any(feature = "std", feature = "alloc"))]
173pub fn coherence_certificates() -> alloc::vec::Vec<karpal_verify::Certificate> {
174    use karpal_verify::{Obligation, Origin, ProofBridge, ProofEvidence, Term, VerificationTier};
175
176    let pentagon = Obligation {
177        name: "pentagon_coherence".into(),
178        property: "coherence",
179        declarations: alloc::vec![],
180        assumptions: alloc::vec![],
181        conclusion: Term::bool(true),
182        origin: Origin::new("karpal-diagram", "coherence::PentagonIdentity"),
183        tier: VerificationTier::Emergent,
184    };
185
186    let triangle = Obligation {
187        name: "triangle_coherence".into(),
188        property: "coherence",
189        declarations: alloc::vec![],
190        assumptions: alloc::vec![],
191        conclusion: Term::bool(true),
192        origin: Origin::new("karpal-diagram", "coherence::TriangleIdentity"),
193        tier: VerificationTier::Emergent,
194    };
195
196    let hexagon = Obligation {
197        name: "hexagon_coherence".into(),
198        property: "coherence",
199        declarations: alloc::vec![],
200        assumptions: alloc::vec![],
201        conclusion: Term::bool(true),
202        origin: Origin::new("karpal-diagram", "coherence::HexagonIdentity"),
203        tier: VerificationTier::Emergent,
204    };
205
206    let evidence = ProofEvidence::passed_tests("karpal-diagram::coherence::all", 1)
207        .with_notes("runtime coherence verification via crate test suite");
208
209    alloc::vec![
210        ProofBridge::certificate::<CoherenceCertificate>(&pentagon, evidence.clone()),
211        ProofBridge::certificate::<CoherenceCertificate>(&triangle, evidence.clone()),
212        ProofBridge::certificate::<CoherenceCertificate>(&hexagon, evidence),
213    ]
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219    use crate::Tensor;
220    use karpal_arrow::{Category, FnA, Semigroupoid};
221
222    #[test]
223    fn pentagon_produces_witness() {
224        let _: Rewrite<(((i32, u8), bool), String), (i32, (u8, (bool, String))), PentagonIdentity> =
225            verify_pentagon::<i32, u8, bool, String>();
226    }
227
228    #[test]
229    fn triangle_produces_witness() {
230        let _: Rewrite<((i32, ()), String), (i32, String), TriangleIdentity> =
231            verify_triangle::<i32, String>();
232    }
233
234    #[test]
235    fn pentagon_paths_agree() {
236        let input = (((1_i32, 2_u8), true), "end".to_string());
237
238        // Upper path
239        let upper_step1 = FnA::tensor(FnA::associate::<i32, u8, bool>(), FnA::id::<String>());
240        let upper_step2 = FnA::associate::<i32, (u8, bool), String>();
241        let upper_step3 = FnA::tensor(FnA::id::<i32>(), FnA::associate::<u8, bool, String>());
242        let upper = FnA::compose(upper_step3, FnA::compose(upper_step2, upper_step1));
243
244        // Lower path
245        let lower_step1 = FnA::associate::<(i32, u8), bool, String>();
246        let lower_step2 = FnA::associate::<i32, u8, (bool, String)>();
247        let lower = FnA::compose(lower_step2, lower_step1);
248
249        assert_eq!(upper(input.clone()), lower(input));
250    }
251
252    #[test]
253    fn triangle_paths_agree() {
254        let input = ((42_i32, ()), "hello".to_string());
255
256        // Left path: ρ ⊗ id
257        let left = FnA::tensor(FnA::right_unitor::<i32>(), FnA::id::<String>());
258
259        // Right path: α ; (id ⊗ λ)
260        let alpha = FnA::associate::<i32, (), String>();
261        let right_step2 = FnA::tensor(FnA::id::<i32>(), FnA::left_unitor::<String>());
262        let right = FnA::compose(right_step2, alpha);
263
264        assert_eq!(left(input.clone()), right(input.clone()));
265    }
266
267    #[test]
268    fn hexagon_produces_witness() {
269        let _: Rewrite<((i32, u8), bool), ((u8, bool), i32), HexagonIdentity> =
270            verify_hexagon::<i32, u8, bool>();
271    }
272
273    #[test]
274    fn hexagon_paths_agree() {
275        use crate::Braiding;
276
277        let input = ((1_i32, 2_u8), true);
278
279        // Upper path: braid ; α⁻¹ ; braid ; α⁻¹
280        let braid_ab_c = FnA::braid::<(i32, u8), bool>();
281        let assoc_inv_c_a_b = FnA::associate_inv::<bool, i32, u8>();
282        let braid_ca_b = FnA::braid::<(bool, i32), u8>();
283        let assoc_inv_b_c_a = FnA::associate_inv::<u8, bool, i32>();
284        let upper = FnA::compose(
285            assoc_inv_b_c_a,
286            FnA::compose(braid_ca_b, FnA::compose(assoc_inv_c_a_b, braid_ab_c)),
287        );
288
289        // Lower path: α ; braid
290        let assoc_a_b_c = FnA::associate::<i32, u8, bool>();
291        let braid_a_bc = FnA::braid::<i32, (u8, bool)>();
292        let lower = FnA::compose(braid_a_bc, assoc_a_b_c);
293
294        assert_eq!(upper(input.clone()), lower(input));
295    }
296}