open_hypergraphs/lax/functor/traits.rs
1//! Strict symmetric monoidal hypergraph functors on lax open hypergraphs.
2use crate::array::vec::VecArray;
3use crate::lax::*;
4use crate::strict::vec::{FiniteFunction, IndexedCoproduct};
5
6/// An easier-to-implement `Functor` trait for lax `OpenHypergraph`
7pub trait Functor<O1, A1, O2, A2> {
8 /// Map a generating object of the theory
9 fn map_object(&self, o: &O1) -> impl ExactSizeIterator<Item = O2>;
10
11 /// Map a single operation of the theory with specified source and target type.
12 /// This must be consistent with `map_object`, i.e. we must have:
13 /// - `F.map_operation(x, s, t).sources == F.map_object(s)`
14 /// - `F.map_operation(x, s, t).targets == F.map_object(t)`
15 /// This condition is *not* checked, but may panic if not satisfied.
16 fn map_operation(&self, a: &A1, source: &[O1], target: &[O1]) -> OpenHypergraph<O2, A2>;
17
18 /// Apply this functor to an [`OpenHypergraph`].
19 /// Once `map_operation` is defined, this can be defined using
20 /// [`super::dyn_functor::define_map_arrow`]
21 /// as `define_map_arrow(self, f)`
22 fn map_arrow(&self, f: &OpenHypergraph<O1, A1>) -> OpenHypergraph<O2, A2>;
23}
24
25/// Define `map_arrow` for a [`Functor`] with `map_object` and `map_operation` already defined.
26/// This will fail if the input term is not quotiented.
27pub fn try_define_map_arrow<O1: Clone, A1, O2: Clone, A2: Clone>(
28 functor: &impl Functor<O1, A1, O2, A2>,
29 f: &OpenHypergraph<O1, A1>,
30) -> Option<OpenHypergraph<O2, A2>> {
31 // The input must be strict (no pending identifications in the quotient map).
32 if !f.hypergraph.is_strict() {
33 return None;
34 }
35
36 // Compute tensored operations and mapped objects
37 let fx = map_operations(functor, f);
38 let fw = map_objects(functor, f);
39
40 // Below here is the same as 'spider_map_arrow' in strict functor impl
41 // Steps 1: build spider maps with lax composition
42 spider_map_arrow(&f, &fw, fx)
43}
44
45/// Like `map_arrow`, but also returns a relation mapping
46/// nodes of the input term to nodes of the output term.
47///
48/// The [`IndexedCoproduct`] models this relation as a mapping from an input node to zero or more
49/// output nodes (i.e., a relation)
50pub fn map_arrow_witness<O1: Clone, A1, O2: Clone, A2: Clone>(
51 functor: &impl Functor<O1, A1, O2, A2>,
52 f: &OpenHypergraph<O1, A1>,
53) -> Option<(OpenHypergraph<O2, A2>, IndexedCoproduct<FiniteFunction>)> {
54 // The input must be strict (no pending identifications in the quotient map).
55 if !f.hypergraph.is_strict() {
56 return None;
57 }
58
59 // Compute tensored operations and mapped objects
60 let fx = map_operations(functor, f);
61 let fw = map_objects(functor, f);
62
63 // Below here is the same as 'spider_map_arrow' in strict functor impl
64 // Steps 1: build spider maps with lax composition
65 let result = spider_map_arrow(&f, &fw, fx)?;
66
67 // Step 2: identify the nodes of `i` within the composite.
68 // Since:
69 // - we composed `(sx ; (i x fx) ; yt)`
70 // - lax follows convention of *concatenating* (so sx nodes are first, then i, then fx, then yt)
71 // - the number of nodes in sx is n = fw_flat.len()
72 // - The number of nodes in i is fw_flat.len() as well
73 // The nodes of i are at the slice
74 // [sx.nodes.len()..sx.nodes.len()+i.nodes.len()].
75 // Which is
76 // [n..n+n]
77 // Then it remains to identify the segment boundaries: these are exactly the *lengths* of the
78 // nested vec fw.
79 // So our "identification" is the IndexedCoproduct with values the injection defined by the
80 // slice above, and segment sizes the sizes of fw.
81 let n: usize = fw.iter().map(|v| v.len()).sum();
82 let total_result_nodes = result.hypergraph.nodes.len();
83
84 let witness_values = FiniteFunction::new(VecArray((n..2 * n).collect()), total_result_nodes)?;
85 let fw_sizes = FiniteFunction::new(VecArray(fw.iter().map(|v| v.len()).collect()), n + 1)?;
86 let witness = IndexedCoproduct::new(fw_sizes, witness_values)?;
87
88 Some((result, witness))
89}
90
91/// Lax analogue of [`crate::strict::functor::spider_map_arrow`].
92///
93/// Given an arrow `f`, its mapped objects `fw`, and its mapped operations `fx`,
94/// build the spider decomposition `sx ; (i ⊗ fx) ; yt` and compose laxly.
95fn spider_map_arrow<O1, A1, O2: Clone, A2: Clone>(
96 f: &OpenHypergraph<O1, A1>,
97 fw: &[Vec<O2>],
98 fx: OpenHypergraph<O2, A2>,
99) -> Option<OpenHypergraph<O2, A2>> {
100 let fw_flat: Vec<O2> = fw.iter().flat_map(|v| v.iter().cloned()).collect();
101 let fw_total = fw_flat.len();
102
103 // Compute injections through fw segments (= map_half_spider in strict functor)
104 let fs = map_half_spider(fw, &f.sources)?;
105 let ft = map_half_spider(fw, &f.targets)?;
106
107 let all_edge_sources: Vec<NodeId> = f
108 .hypergraph
109 .adjacency
110 .iter()
111 .flat_map(|adj| adj.sources.iter().copied())
112 .collect();
113 let all_edge_targets: Vec<NodeId> = f
114 .hypergraph
115 .adjacency
116 .iter()
117 .flat_map(|adj| adj.targets.iter().copied())
118 .collect();
119 let e_s = map_half_spider(fw, &all_edge_sources)?;
120 let e_t = map_half_spider(fw, &all_edge_targets)?;
121
122 // Build i, sx, yt
123 let id_fn = FiniteFunction::identity(fw_total);
124
125 let i = OpenHypergraph::<O2, A2>::identity(fw_flat.clone());
126 let sx = OpenHypergraph::<O2, A2>::spider(fs, (&id_fn + &e_s)?, fw_flat.clone())?;
127 let yt = OpenHypergraph::<O2, A2>::spider((&id_fn + &e_t)?, ft, fw_flat)?;
128
129 // Compose laxly: sx ; (i ⊗ fx) ; yt
130 Some(sx.lax_compose(&i.tensor(&fx))?.lax_compose(&yt)?)
131}
132
133/// Lax analogue of [`crate::strict::functor::map_half_spider`].
134///
135/// Given mapped objects `fw` (as nested lists) and a list of node references,
136/// compute the injection through the segment structure of `fw`.
137/// Each `NodeId(j)` expands to the indices of `F(w_j)` in the flattened `fw`.
138fn map_half_spider<O>(fw: &[Vec<O>], node_ids: &[NodeId]) -> Option<FiniteFunction> {
139 let fw_total: usize = fw.iter().map(|v| v.len()).sum();
140
141 // Encode segment sizes as a FiniteFunction
142 // (same role as fw.sources in the strict functor's IndexedCoproduct)
143 let fw_sizes =
144 FiniteFunction::new(VecArray(fw.iter().map(|v| v.len()).collect()), fw_total + 1)?;
145
146 // Convert NodeIds to a FiniteFunction targeting the node set
147 let node_count = fw.len();
148 let f = FiniteFunction::new(VecArray(node_ids.iter().map(|n| n.0).collect()), node_count)?;
149
150 fw_sizes.injections(&f)
151}
152
153/// Fold all the operations `x₀, x₁, ...` of an OpenHypergraph together to get their tensoring
154/// `F(x₀) ● F(x₁) ● ...`
155fn map_operations<O1: Clone, A1, O2: Clone, A2: Clone>(
156 functor: &impl Functor<O1, A1, O2, A2>,
157 f: &OpenHypergraph<O1, A1>,
158) -> OpenHypergraph<O2, A2> {
159 let mut result = OpenHypergraph::empty();
160 for (i, a) in f.hypergraph.edges.iter().enumerate() {
161 let source: Vec<O1> = f.hypergraph.adjacency[i]
162 .sources
163 .iter()
164 .map(|nid| f.hypergraph.nodes[nid.0].clone())
165 .collect();
166 let target: Vec<O1> = f.hypergraph.adjacency[i]
167 .targets
168 .iter()
169 .map(|nid| f.hypergraph.nodes[nid.0].clone())
170 .collect();
171 result.tensor_assign(functor.map_operation(a, &source, &target));
172 }
173 result
174}
175
176/// Map objects `A₀ ● A₁ ● ...` to `F(A₀) ● F(A₁) ● ...`
177/// returned as a list of lists, where list i is the list of generating objects `F(A_i)`.
178fn map_objects<O1, A1, O2, A2>(
179 functor: &impl Functor<O1, A1, O2, A2>,
180 f: &OpenHypergraph<O1, A1>,
181) -> Vec<Vec<O2>> {
182 f.hypergraph
183 .nodes
184 .iter()
185 .map(|o| functor.map_object(o).collect())
186 .collect()
187}