open_hypergraphs/strict/hypergraph/
arrow.rs1use super::object::Hypergraph;
2use crate::array::{Array, ArrayKind, NaturalArray};
3use crate::category::Arrow;
4use crate::finite_function::FiniteFunction;
5use crate::indexed_coproduct::IndexedCoproduct;
6use crate::strict::graph::{
7 node_adjacency, node_adjacency_from_incidence, sparse_relative_indegree,
8};
9
10use core::fmt::Debug;
11use num_traits::{One, Zero};
12
13fn successors<K: ArrayKind>(
14 adjacency: &IndexedCoproduct<K, FiniteFunction<K>>,
15 frontier: &K::Index,
16) -> K::Index
17where
18 K::Type<K::I>: NaturalArray<K>,
19{
20 if frontier.is_empty() {
21 return K::Index::empty();
22 }
23
24 let f = FiniteFunction::new(frontier.clone(), adjacency.len()).unwrap();
25 let (g, _) = sparse_relative_indegree(adjacency, &f);
26 g.table
27}
28
29fn filter_unvisited<K: ArrayKind>(visited: &K::Index, candidates: &K::Index) -> K::Index
30where
31 K::Type<K::I>: NaturalArray<K>,
32{
33 if candidates.is_empty() {
34 return K::Index::empty();
35 }
36
37 let visited_on_candidates = visited.gather(candidates.get_range(..));
38 let unvisited_ix = visited_on_candidates.zero();
39 candidates.gather(unvisited_ix.get_range(..))
40}
41
42#[derive(Debug)]
43pub enum InvalidHypergraphArrow {
44 TypeMismatchW,
45 TypeMismatchX,
46 NotNaturalW,
47 NotNaturalX,
48 NotNaturalS,
49 NotNaturalT,
50}
51
52pub(crate) fn validate_hypergraph_morphism<K: ArrayKind, O, A>(
53 source: &Hypergraph<K, O, A>,
54 target: &Hypergraph<K, O, A>,
55 w: &FiniteFunction<K>,
56 x: &FiniteFunction<K>,
57) -> Result<(), InvalidHypergraphArrow>
58where
59 K::Type<K::I>: NaturalArray<K>,
60 K::Type<O>: Array<K, O> + PartialEq,
61 K::Type<A>: Array<K, A> + PartialEq,
62{
63 if w.source() != source.w.len() || w.target() != target.w.len() {
64 return Err(InvalidHypergraphArrow::TypeMismatchW);
65 }
66 if x.source() != source.x.len() || x.target() != target.x.len() {
67 return Err(InvalidHypergraphArrow::TypeMismatchX);
68 }
69
70 let composed_w = (w >> &target.w).ok_or(InvalidHypergraphArrow::TypeMismatchW)?;
72 if source.w != composed_w {
73 return Err(InvalidHypergraphArrow::NotNaturalW);
74 }
75
76 let composed_x = (x >> &target.x).ok_or(InvalidHypergraphArrow::TypeMismatchX)?;
78 if source.x != composed_x {
79 return Err(InvalidHypergraphArrow::NotNaturalX);
80 }
81
82 let s_lhs = source
84 .s
85 .map_values(w)
86 .ok_or(InvalidHypergraphArrow::NotNaturalS)?;
87 let s_rhs = target
88 .s
89 .map_indexes(x)
90 .ok_or(InvalidHypergraphArrow::NotNaturalS)?;
91 if s_lhs != s_rhs {
92 return Err(InvalidHypergraphArrow::NotNaturalS);
93 }
94
95 let t_lhs = source
97 .t
98 .map_values(w)
99 .ok_or(InvalidHypergraphArrow::NotNaturalT)?;
100 let t_rhs = target
101 .t
102 .map_indexes(x)
103 .ok_or(InvalidHypergraphArrow::NotNaturalT)?;
104 if t_lhs != t_rhs {
105 return Err(InvalidHypergraphArrow::NotNaturalT);
106 }
107
108 Ok(())
109}
110
111pub struct HypergraphArrow<K: ArrayKind, O, A> {
112 pub source: Hypergraph<K, O, A>,
114
115 pub target: Hypergraph<K, O, A>,
117
118 pub w: FiniteFunction<K>,
120
121 pub x: FiniteFunction<K>,
123}
124
125pub(crate) fn is_convex_subgraph_morphism<K: ArrayKind, O, A>(
126 source: &Hypergraph<K, O, A>,
127 target: &Hypergraph<K, O, A>,
128 w: &FiniteFunction<K>,
129 x: &FiniteFunction<K>,
130) -> bool
131where
132 K::Type<K::I>: NaturalArray<K>,
133 K::Type<O>: Array<K, O> + PartialEq,
134 K::Type<A>: Array<K, A> + PartialEq,
135{
136 if validate_hypergraph_morphism(source, target, w, x).is_err() {
137 return false;
138 }
139 if !w.is_injective() || !x.is_injective() {
140 return false;
141 }
142
143 let g = target;
144 let n_nodes = g.w.len();
145 let n_edges = g.x.len();
146
147 let mut edge_mask = K::Index::fill(K::I::zero(), n_edges.clone());
149 edge_mask.scatter_assign_constant(&x.table, K::I::one());
150 let outside_edge_ix = edge_mask.zero();
151 let outside_edges = if let Some(e) = FiniteFunction::new(outside_edge_ix, n_edges) {
152 e
153 } else {
154 return false;
155 };
156
157 let s_in = if let Some(s) = g.s.map_indexes(x) {
159 s
160 } else {
161 return false;
162 };
163 let t_in = if let Some(t) = g.t.map_indexes(x) {
164 t
165 } else {
166 return false;
167 };
168 let adj_in = node_adjacency_from_incidence(&s_in, &t_in);
169
170 let s_out = if let Some(s) = g.s.map_indexes(&outside_edges) {
172 s
173 } else {
174 return false;
175 };
176 let t_out = if let Some(t) = g.t.map_indexes(&outside_edges) {
177 t
178 } else {
179 return false;
180 };
181 let adj_out = node_adjacency_from_incidence(&s_out, &t_out);
182
183 let adj_all = node_adjacency(g);
185
186 let mut visited0 = K::Index::fill(K::I::zero(), n_nodes.clone());
192 let mut visited1 = K::Index::fill(K::I::zero(), n_nodes.clone());
193 let mut frontier0 = w.table.clone();
194 let mut frontier1 = K::Index::empty();
195
196 visited0.scatter_assign_constant(&frontier0, K::I::one());
198
199 while !frontier0.is_empty() || !frontier1.is_empty() {
200 let next0: K::Index = successors::<K>(&adj_in, &frontier0);
202 let next1_from0 = successors::<K>(&adj_out, &frontier0);
204 let next1_from1 = successors::<K>(&adj_all, &frontier1);
206
207 let next0: K::Index = filter_unvisited::<K>(&visited0, &next0);
209
210 let next1: K::Index = {
211 let merged = next1_from0.concatenate(&next1_from1);
212 if merged.is_empty() {
213 K::Index::empty()
214 } else {
215 let (unique, _) = merged.sparse_bincount();
216 filter_unvisited::<K>(&visited1, &unique)
217 }
218 };
219
220 if next0.is_empty() && next1.is_empty() {
221 break;
222 }
223
224 visited0.scatter_assign_constant(&next0, K::I::one());
226 visited1.scatter_assign_constant(&next1, K::I::one());
227 frontier0 = next0;
228 frontier1 = next1;
229 }
230
231 let reached_selected = visited1.gather(w.table.get_range(..));
233 !reached_selected.max().map_or(false, |m| m >= K::I::one())
234}
235
236impl<K: ArrayKind, O, A> HypergraphArrow<K, O, A>
237where
238 K::Type<O>: Array<K, O> + PartialEq,
239 K::Type<A>: Array<K, A> + PartialEq,
240{
241 pub fn new(
243 source: Hypergraph<K, O, A>,
244 target: Hypergraph<K, O, A>,
245 w: FiniteFunction<K>,
246 x: FiniteFunction<K>,
247 ) -> Result<Self, InvalidHypergraphArrow>
248 where
249 K::Type<K::I>: NaturalArray<K>,
250 {
251 HypergraphArrow {
252 source,
253 target,
254 w,
255 x,
256 }
257 .validate()
258 }
259
260 pub fn validate(self) -> Result<Self, InvalidHypergraphArrow>
262 where
263 K::Type<K::I>: NaturalArray<K>,
264 {
265 validate_hypergraph_morphism(&self.source, &self.target, &self.w, &self.x)?;
266 Ok(self)
267 }
268
269 pub fn is_monomorphism(&self) -> bool
271 where
272 K::Type<K::I>: NaturalArray<K>,
273 {
274 self.w.is_injective() && self.x.is_injective()
275 }
276
277 pub fn is_convex_subgraph(&self) -> bool
280 where
281 K::Type<K::I>: NaturalArray<K>,
282 {
283 is_convex_subgraph_morphism(&self.source, &self.target, &self.w, &self.x)
284 }
285}
286
287impl<K: ArrayKind, O: Debug, A: Debug> Debug for HypergraphArrow<K, O, A>
288where
289 K::Index: Debug,
290 K::Type<K::I>: Debug,
291 K::Type<A>: Debug,
292 K::Type<O>: Debug,
293{
294 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295 f.debug_struct("HypergraphArrow")
296 .field("source", &self.source)
297 .field("target", &self.target)
298 .field("w", &self.w)
299 .field("x", &self.x)
300 .finish()
301 }
302}
303
304impl<K: ArrayKind, O: Debug, A: Debug> Clone for HypergraphArrow<K, O, A>
305where
306 K::Type<K::I>: Clone,
307 K::Type<A>: Clone,
308 K::Type<O>: Clone,
309{
310 fn clone(&self) -> Self {
311 Self {
312 source: self.source.clone(),
313 target: self.target.clone(),
314 w: self.w.clone(),
315 x: self.x.clone(),
316 }
317 }
318}