1use std::cell::RefCell;
17use std::fmt::Debug;
18use std::{collections::HashMap, hash::Hash};
19
20use derivative::Derivative;
21use egglog::ast::{Command, Expr, RunConfig, Schedule, Schema};
22use egglog::prelude::*;
23use nonempty::NonEmpty;
24use thiserror::Error;
25
26use super::{category::*, graph::*, path::*};
27use crate::egglog_util::EGraphUtils;
28use crate::validate::{self, Validate};
29use crate::zero::QualifiedName;
30
31#[derive(Clone, Derivative)]
48#[derivative(Debug(bound = "V: Debug, E: Debug"))]
49#[derivative(Default(bound = "", new = "true"))]
50#[derivative(PartialEq(bound = "V: Eq + Hash, E: Eq + Hash"))]
51#[derivative(Eq(bound = "V: Eq + Hash, E: Eq + Hash"))]
52pub struct FpCategory<V, E> {
53 generators: HashGraph<V, E>,
54 equations: Vec<PathEq<V, E>>,
55 #[derivative(Debug = "ignore", PartialEq = "ignore")]
56 state: RefCell<CategoryEGraph<V, E>>,
57}
58
59pub type QualifiedFpCategory = FpCategory<QualifiedName, QualifiedName>;
61
62impl<V, E> FpCategory<V, E>
63where
64 V: Eq + Clone + Hash,
65 E: Eq + Clone + Hash,
66{
67 pub fn generators(&self) -> &(impl FinGraph<V = V, E = E> + use<V, E>) {
69 &self.generators
70 }
71
72 pub fn equations(&self) -> impl Iterator<Item = &PathEq<V, E>> {
74 self.equations.iter()
75 }
76
77 pub fn is_free(&self) -> bool {
79 self.equations.is_empty()
80 }
81
82 pub fn add_ob_generator(&mut self, v: V) {
84 assert!(self.generators.add_vertex(v.clone()), "Object generator already exists");
85 let state = self.state.get_mut();
86 let expr = state.ob_generator(v);
87 state.egraph.run_action(action!((unquote expr))).unwrap();
88 }
89
90 pub fn add_ob_generators(&mut self, iter: impl IntoIterator<Item = V>) {
92 for v in iter {
93 self.add_ob_generator(v)
94 }
95 }
96
97 pub fn add_mor_generator(&mut self, e: E, dom: V, cod: V) {
99 assert!(
100 self.generators.add_edge(e.clone(), dom.clone(), cod.clone()),
101 "Morphism generator already exists"
102 );
103 let state = self.state.get_mut();
104 let (dom, cod) = (state.ob_generator(dom), state.ob_generator(cod));
105 let mor = state.mor_generator(e);
106 let actions = actions![
107 (unquote mor.clone())
108 (union (unquote dom) (dom (unquote mor.clone())))
109 (union (unquote cod) (cod (unquote mor)))
110 ];
111 state.egraph.run_actions(actions).unwrap();
112 }
113
114 pub fn make_mor_generator(&mut self, e: E) {
116 assert!(self.generators.make_edge(e.clone()), "Morphism generator already exists");
117 let state = self.state.get_mut();
118 let expr = state.mor_generator(e);
119 state.egraph.run_action(action!((unquote expr))).unwrap();
120 }
121
122 pub fn get_dom(&self, e: &E) -> Option<&V> {
124 self.generators.get_src(e)
125 }
126
127 pub fn get_cod(&self, e: &E) -> Option<&V> {
129 self.generators.get_tgt(e)
130 }
131
132 pub fn set_dom(&mut self, e: E, v: V) {
134 assert!(
135 self.generators.set_src(e.clone(), v.clone()).is_none(),
136 "Domain of morphism generator should not already be set"
137 );
138 let state = self.state.get_mut();
139 let (mor, ob) = (state.mor_generator(e), state.ob_generator(v));
140 let action = action!((union (unquote ob) (dom (unquote mor))));
141 state.egraph.run_action(action).unwrap();
142 }
143
144 pub fn set_cod(&mut self, e: E, v: V) {
146 assert!(
147 self.generators.set_tgt(e.clone(), v.clone()).is_none(),
148 "Codomain of morphism generator should not already be set"
149 );
150 let state = self.state.get_mut();
151 let (mor, ob) = (state.mor_generator(e), state.ob_generator(v));
152 let action = action!((union (unquote ob) (cod (unquote mor))));
153 state.egraph.run_action(action).unwrap();
154 }
155
156 pub fn add_equation(&mut self, eq: PathEq<V, E>) {
158 self.equations.push(eq.clone());
159 let (lhs, rhs) = (self.path_expr(eq.lhs), self.path_expr(eq.rhs));
160 let action = action!((union (unquote lhs) (unquote rhs)));
161 self.state.get_mut().egraph.run_action(action).unwrap();
162 }
163
164 pub fn equate(&mut self, lhs: Path<V, E>, rhs: Path<V, E>) {
166 self.add_equation(PathEq::new(lhs, rhs));
167 }
168
169 fn path_expr(&self, path: Path<V, E>) -> Expr {
170 path.map_reduce(
171 |v| {
172 let ob = self.state.borrow_mut().ob_generator(v);
173 expr!((id (unquote ob)))
174 },
175 |e| self.state.borrow_mut().mor_generator(e),
176 |f, g| expr!((compose (unquote f) (unquote g))),
177 )
178 }
179
180 pub fn iter_invalid(&self) -> impl Iterator<Item = InvalidFpCategory<E>> + '_ {
182 let generator_errors = self.generators.iter_invalid().map(|err| match err {
183 InvalidGraph::Src(e) => InvalidFpCategory::Dom(e),
184 InvalidGraph::Tgt(e) => InvalidFpCategory::Cod(e),
185 });
186 let equation_errors = self.equations.iter().enumerate().filter_map(|(i, eq)| {
187 Some(InvalidFpCategory::Eqn(i, eq.validate_in(&self.generators).err()?))
188 });
189 generator_errors.chain(equation_errors)
190 }
191}
192
193impl<V, E> Category for FpCategory<V, E>
194where
195 V: Eq + Clone + Hash,
196 E: Eq + Clone + Hash,
197{
198 type Ob = V;
199 type Mor = Path<V, E>;
200
201 fn has_ob(&self, x: &Self::Ob) -> bool {
202 self.generators.has_vertex(x)
203 }
204 fn has_mor(&self, path: &Self::Mor) -> bool {
205 path.contained_in(&self.generators)
206 }
207 fn dom(&self, path: &Self::Mor) -> Self::Ob {
208 path.src(&self.generators)
209 }
210 fn cod(&self, path: &Self::Mor) -> Self::Ob {
211 path.tgt(&self.generators)
212 }
213
214 fn compose(&self, path: Path<Self::Ob, Self::Mor>) -> Self::Mor {
215 path.flatten_in(&self.generators).expect("Paths should be composable")
216 }
217 fn compose2(&self, path1: Self::Mor, path2: Self::Mor) -> Self::Mor {
218 path1
219 .concat_in(&self.generators, path2)
220 .expect("Target of first path should equal source of second path")
221 }
222
223 fn morphisms_are_equal(&self, path1: Self::Mor, path2: Self::Mor) -> bool {
224 let (lhs, rhs) = (self.path_expr(path1), self.path_expr(path2));
225 self.state.borrow_mut().check_equal(lhs, rhs)
226 }
227}
228
229impl<V, E> FgCategory for FpCategory<V, E>
230where
231 V: Eq + Clone + Hash,
232 E: Eq + Clone + Hash,
233{
234 type ObGen = V;
235 type MorGen = E;
236
237 fn ob_generators(&self) -> impl Iterator<Item = Self::ObGen> {
238 self.generators.vertices()
239 }
240 fn mor_generators(&self) -> impl Iterator<Item = Self::MorGen> {
241 self.generators.edges()
242 }
243 fn mor_generator_dom(&self, f: &Self::MorGen) -> Self::Ob {
244 self.generators.src(f)
245 }
246 fn mor_generator_cod(&self, f: &Self::MorGen) -> Self::Ob {
247 self.generators.tgt(f)
248 }
249}
250
251impl<V, E> Validate for FpCategory<V, E>
252where
253 V: Eq + Clone + Hash,
254 E: Eq + Clone + Hash,
255{
256 type ValidationError = InvalidFpCategory<E>;
257
258 fn validate(&self) -> Result<(), NonEmpty<Self::ValidationError>> {
259 validate::wrap_errors(self.iter_invalid())
260 }
261}
262
263#[derive(Debug, Error)]
265pub enum InvalidFpCategory<E> {
266 #[error("Domain of morphism generator `{0}` is not in the category")]
268 Dom(E),
269
270 #[error("Codomain of morphism generator `{0}` is not in the category")]
272 Cod(E),
273
274 #[error("Path equation `{0}` is not valid: `{1:?}`")]
276 Eqn(usize, NonEmpty<InvalidPathEq>),
277}
278
279#[derive(Clone)]
285struct CategoryEGraph<V, E> {
286 egraph: EGraph,
287 ob_generators: HashMap<V, usize>,
288 mor_generators: HashMap<E, usize>,
289}
290
291impl<V, E> CategoryEGraph<V, E>
292where
293 V: Eq + Hash,
294 E: Eq + Hash,
295{
296 fn ob_generator(&mut self, v: V) -> Expr {
298 let n = self.ob_generators.len();
299 let id: i64 = (*self.ob_generators.entry(v).or_insert(n))
300 .try_into()
301 .expect("Shouldn't have too many object generators");
302 expr!((ObGen (unquote exprs::int(id))))
303 }
304
305 fn mor_generator(&mut self, e: E) -> Expr {
307 let n = self.mor_generators.len();
308 let id: i64 = (*self.mor_generators.entry(e).or_insert(n))
309 .try_into()
310 .expect("Shouldn't have too many morphism generators");
311 expr!((MorGen (unquote exprs::int(id))))
312 }
313}
314
315impl<V, E> CategoryEGraph<V, E> {
316 fn check_equal(&mut self, lhs: Expr, rhs: Expr) -> bool {
320 let schedule = Schedule::Saturate(
321 span!(),
322 Box::new(Schedule::Run(
323 span!(),
324 RunConfig { ruleset: "CatAxioms".into(), until: None },
325 )),
326 );
327 let prog = vec![
328 Command::Action(action!((unquote lhs.clone()))),
329 Command::Action(action!((unquote rhs.clone()))),
330 Command::RunSchedule(schedule),
331 ];
332 self.egraph.run_program(prog).unwrap();
333 self.egraph.check_equal(lhs, rhs).unwrap()
334 }
335}
336
337impl<V, E> Default for CategoryEGraph<V, E> {
338 fn default() -> Self {
339 let mut egraph = EGraph::default();
340 init_category_egraph(&mut egraph).expect("Unexpected egglog error");
341 Self {
342 egraph,
343 ob_generators: Default::default(),
344 mor_generators: Default::default(),
345 }
346 }
347}
348
349fn init_category_egraph(egraph: &mut EGraph) -> Result<(), egglog::Error> {
351 datatype!(egraph, (datatype Ob (ObGen i64 :cost 0)));
353 datatype!(egraph, (datatype Mor (MorGen i64 :cost 0) (compose Mor Mor :cost 1)));
354
355 for name in ["dom", "cod"] {
357 add_constructor(
358 egraph,
359 name,
360 Schema {
361 input: vec!["Mor".into()],
362 output: "Ob".into(),
363 },
364 Some(1),
365 false,
366 )?;
367 }
368 add_constructor(
369 egraph,
370 "id",
371 Schema {
372 input: vec!["Ob".into()],
373 output: "Mor".into(),
374 },
375 Some(1),
376 false,
377 )?;
378
379 add_relation(egraph, "is_mor_valid", vec!["Mor".into()])?;
381
382 add_ruleset(egraph, "CatAxioms")?;
384
385 rule(egraph, "CatAxioms", facts![(= f (MorGen name))], actions![(is_mor_valid f)])?;
387
388 rule(egraph, "CatAxioms", facts![(= f (id x))], actions![(is_mor_valid f)])?;
390
391 rule(
394 egraph,
395 "CatAxioms",
396 facts![
397 (= fg (compose f g))
398 (is_mor_valid f)
399 (is_mor_valid g)
400 (= (cod f) (dom g))
401 ],
402 actions![(is_mor_valid fg)],
403 )?;
404
405 rule(
407 egraph,
408 "CatAxioms",
409 facts![
410 (= fg (compose f g))
411 (is_mor_valid fg)
412 ],
413 actions![
414 (union (dom fg) (dom f))
415 (union (cod fg) (cod g))
416 ],
417 )?;
418 rule(egraph, "CatAxioms", facts![(= lhs (dom (id x)))], actions![(union lhs x)])?;
419 rule(egraph, "CatAxioms", facts![(= lhs (cod (id x)))], actions![(union lhs x)])?;
420
421 rule(
423 egraph,
424 "CatAxioms",
425 facts![
426 (= fgh (compose (compose f g) h))
427 (is_mor_valid fgh)
428 ],
429 actions![(union fgh (compose f (compose g h)))],
430 )?;
431 rule(
432 egraph,
433 "CatAxioms",
434 facts![
435 (= fgh (compose f (compose g h)))
436 (is_mor_valid fgh)
437 ],
438 actions![(union fgh (compose (compose f g) h))],
439 )?;
440
441 rule(
443 egraph,
444 "CatAxioms",
445 facts![(= lhs (compose f (id (cod f))))],
446 actions![(union lhs f)],
447 )?;
448 rule(
449 egraph,
450 "CatAxioms",
451 facts![(= lhs (compose (id (dom f)) f))],
452 actions![(union lhs f)],
453 )?;
454
455 Ok(())
456}
457
458#[cfg(test)]
459use crate::zero::name;
460
461#[cfg(test)]
463pub fn sch_graph() -> QualifiedFpCategory {
464 let mut cat = FpCategory::new();
465 cat.add_ob_generators([name("V"), name("E")]);
466 cat.add_mor_generator(name("src"), name("E"), name("V"));
467 cat.add_mor_generator(name("tgt"), name("E"), name("V"));
468 cat
469}
470
471#[cfg(test)]
473pub fn sch_sgraph() -> QualifiedFpCategory {
474 let mut cat = FpCategory::new();
475 cat.add_ob_generators([name("V"), name("E")]);
476 cat.add_mor_generator(name("src"), name("E"), name("V"));
477 cat.add_mor_generator(name("tgt"), name("E"), name("V"));
478 cat.add_mor_generator(name("inv"), name("E"), name("E"));
479 cat.equate(Path::pair(name("inv"), name("inv")), Path::empty(name("E")));
480 cat.equate(Path::pair(name("inv"), name("src")), Path::single(name("tgt")));
481 cat.equate(Path::pair(name("inv"), name("tgt")), Path::single(name("src")));
482 cat
483}
484
485#[cfg(test)]
487pub fn sch_hgraph() -> QualifiedFpCategory {
488 let mut cat = FpCategory::new();
489 cat.add_ob_generators([name("V"), name("H")]);
490 cat.add_mor_generator(name("vert"), name("H"), name("V"));
491 cat.add_mor_generator(name("inv"), name("H"), name("H"));
492 cat.equate(Path::pair(name("inv"), name("inv")), Path::empty(name("H")));
493 cat
494}
495
496#[cfg(test)]
497mod tests {
498 use super::*;
499 use nonempty::nonempty;
500
501 #[test]
502 fn sch_sgraph_equations() {
503 let sch = sch_sgraph();
504 assert!(!sch.is_free());
505 assert!(sch.validate().is_ok());
506
507 assert!(!sch.morphisms_are_equal(Path::single(name("src")), Path::single(name("tgt"))));
508 assert!(
509 sch.morphisms_are_equal(Path::pair(name("inv"), name("inv")), Path::empty(name("E")))
510 );
511 assert!(sch.morphisms_are_equal(
512 Path::Seq(nonempty![name("inv"), name("inv"), name("inv"), name("src")]),
513 Path::single(name("tgt"))
514 ));
515 }
516}