Skip to main content

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}