Skip to main content

karpal_diagram/
coherence.rs

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