Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/catlog/src/one/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod category;
pub mod fin_category;
pub mod graph;
pub mod graph_algorithms;
pub mod monoidal;
pub mod path;

pub use self::category::*;
Expand Down
219 changes: 219 additions & 0 deletions packages/catlog/src/one/monoidal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/*! Monoidal categories: interfaces and basic constructions.
*/

use super::category::Category;
use crate::one::Path;
use crate::zero::{FinSet, SingletonSet};

/// (Strict) [Monoidal category](https://ncatlab.org/nlab/show/monoidal+category)
///
/// A category equipped with a "⊗" monoidal product operation on its objects
/// and morphisms. This must be an associative operation with a unit, so the
/// interface requires taking a vector of objects (which bakes in some
/// associativity in an unbiased way).
pub trait MonoidalCategory: Category {
/// Unbiased monoidal product. When given an empty vector, returns the unit.
/// It should satisfy that prod_ob([prod_ob(a), prod_ob(b)])==prod_ob(a++b)
/// This implicitly captures unitality and associativity.
fn prod_ob(&self, x: Vec<Self::Ob>) -> Self::Ob;

/// Monoidal product of morphisms
/// This must be compatible with `prod_ob` in the following way:
/// dom(f⊗g) = dom(f)⊗dom(g), codom(f⊗g) = codom(f)⊗codom(g)
fn prod_mor(&self, x: Vec<Self::Mor>) -> Self::Mor;
}

/// A colored PRO ([product category](https://ncatlab.org/nlab/show/PRO)) is a
/// strict monoidal category whose objects are freely generated by a finite set.
/// We distinguish between Vec<ObGen> and Ob because there may be a performance
/// reason for working with objects rather than vectors of generators, for
/// example a PRO with a singleton ObSet can have its objects be natural numbers.
/// However there should be a bijection between Vec<ObGen> and the subset of
/// terms of type Ob for which `has_ob` is true.
pub trait PRO {
/// Object types (colors)
type ObGen: Eq + Clone;
/// Rust type of the finite set of colors
type ObSet: FinSet<Elem = Self::ObGen>;
/// Rust type of the objects (sequences of colors)
type Ob: Eq + Clone;
/// Rust type of the morphisms
type Mor: Eq + Clone;

/// The generating objects for the category.
fn object_generators(&self) -> Self::ObSet;

/// Associate each sequence of generators with an Ob
fn otimes(&self, xs: Vec<Self::ObGen>) -> Self::Ob;

/// Inverse of otimes: Vec<ObGen> is in bijection with `Ob.filter(has_ob)`
/// This should be defined only when `has_ob` is true.
fn otimes_inv(&self, x: &Self::Ob) -> Vec<Self::ObGen>;

/// Does the category contain the value as an object?
fn has_ob(&self, f: &Self::Ob) -> bool;

/// Does the category contain the value as a morphism?
fn has_mor(&self, f: &Self::Mor) -> bool;

/// Gets the domain of a morphism in the category.
fn dom(&self, f: &Self::Mor) -> Self::Ob;

/// Gets the codomain of a morphism in the category.
fn cod(&self, f: &Self::Mor) -> Self::Ob;

/// Horizontally compose morphisms
fn hcompose(&self, path: Path<Self::Ob, Self::Mor>) -> Self::Mor;

/// Vertically compose morphisms
fn vcompose(&self, path: Vec<Self::Mor>) -> Self::Mor;
}

impl<T> Category for T
where
T: PRO,
{
type Ob = T::Ob;
type Mor = T::Mor;

fn has_ob(&self, h: &Self::Ob) -> bool {
self.has_ob(h)
}

fn has_mor(&self, h: &Self::Mor) -> bool {
self.has_mor(h)
}

fn dom(&self, h: &Self::Mor) -> Self::Ob {
self.dom(h)
}

fn cod(&self, h: &Self::Mor) -> Self::Ob {
self.cod(h)
}

fn compose(&self, path: Path<Self::Ob, Self::Mor>) -> Self::Mor {
self.hcompose(path)
}
}

impl<T> MonoidalCategory for T
where
T: PRO,
{
// Convert Obs back to lists, concatenate the lists, and then get back an Ob
fn prod_ob(&self, xs: Vec<Self::Ob>) -> Self::Ob {
self.otimes(xs.into_iter().flat_map(|x| self.otimes_inv(&x)).collect())
}

fn prod_mor(&self, fs: Vec<Self::Mor>) -> Self::Mor {
self.vcompose(fs)
}
}

#[derive(Default, Clone, Debug, PartialEq, Eq)]
/// The [Augmented Simplex Category](https://ncatlab.org/nlab/show/simplex+category#Definition)
/// has ordered finite sets as objects (we restrict attention to the skeleton of
/// FinSet) and order-preserving maps as morphisms.
struct AugSimplexCat {}

#[derive(Clone, Debug, PartialEq, Eq, Default)]
/// An ad hoc data structure for morphisms in SkelFinSet which can be
/// cloned. [crate::zero::Function] cannot be cloned.
struct CloneableFinFn(Vec<usize>, usize);

impl<'h> IntoIterator for &'h CloneableFinFn {
type Item = <&'h Vec<usize> as IntoIterator>::Item;
type IntoIter = <&'h Vec<usize> as IntoIterator>::IntoIter;

fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}

impl PRO for AugSimplexCat {
type ObGen = ();
type ObSet = SingletonSet;
type Ob = usize;
type Mor = CloneableFinFn;

fn object_generators(&self) -> SingletonSet {
Default::default()
}

fn has_ob(&self, _: &usize) -> bool {
true
}

/// Check if the map is order preserving
fn has_mor(&self, f: &CloneableFinFn) -> bool {
f.into_iter().zip(f.0[1..].iter()).all(|(x, y)| x <= y)
}

fn otimes(&self, x: Vec<()>) -> usize {
x.len()
}
fn otimes_inv(&self, x: &usize) -> Vec<()> {
vec![(); *x]
}
fn dom(&self, f: &CloneableFinFn) -> usize {
f.0.len()
}
// TODO Fix this by using a better hom than just vector
fn cod(&self, f: &CloneableFinFn) -> usize {
*f.into_iter().max().unwrap()
}

/// Horizontally compose morphisms: this should be upstreamed to some
/// general composition of Functions in **Set**
/// Because Rust 0-indexes, a "-1" is needed.
fn hcompose(&self, path: Path<usize, CloneableFinFn>) -> CloneableFinFn {
match path {
Path::Id(x) => CloneableFinFn((1..x + 1).collect(), x),
Path::Seq(xs) => xs.tail.into_iter().fold(xs.head, |acc, nxt| {
CloneableFinFn(acc.0.into_iter().map(|x| nxt.0[x - 1]).collect(), nxt.1)
}),
}
}

/// Vertically compose morphisms
fn vcompose(&self, path: Vec<CloneableFinFn>) -> CloneableFinFn {
path.into_iter().fold(Default::default(), |acc, nxt| {
let previous_cod = acc.1;
let transposed_fn = nxt.into_iter().map(|i| i + previous_cod);
CloneableFinFn(acc.0.into_iter().chain(transposed_fn).collect(), previous_cod + nxt.1)
})
}
}

#[cfg(test)]
mod tests {
use super::*;
use nonempty::nonempty;

#[test]
fn aug_simp_cat() {
let s: AugSimplexCat = Default::default();
assert!(PRO::has_ob(&s, &3000));

let f: CloneableFinFn = CloneableFinFn(vec![1, 3], 4);
let domf = PRO::dom(&s, &f);
assert_eq!(domf, 2);
let bad_f: CloneableFinFn = CloneableFinFn(vec![3, 1, 4], 4);
assert!(PRO::has_mor(&s, &f.clone()));
assert!(!PRO::has_mor(&s, &bad_f.clone()));

// f⊗f
let ff = s.prod_mor(vec![f.clone(), f.clone()]);
let expected = CloneableFinFn(vec![1, 3, 5, 7], 8);
assert_eq!(expected, ff);
assert_ne!(f.clone(), ff);
assert_eq!(PRO::dom(&s, &ff), s.prod_ob(vec![2, 2]));

// horizontal composition
let i2 = s.hcompose(Path::Id(2));
let expected = CloneableFinFn(vec![1, 2], 2);
assert_eq!(expected, i2);
assert_eq!(s.hcompose(Path::Seq(nonempty![i2, f.clone()])), f)
}
}
24 changes: 24 additions & 0 deletions packages/catlog/src/zero/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,30 @@ impl<T> FinSet for AttributedSkelSet<T> {
}
}

/// A set containing a single element, ().
#[derive(Clone, Debug, From, Into, Derivative, PartialEq, Eq, Default)]
pub struct SingletonSet;

impl Set for SingletonSet {
type Elem = ();

fn contains(&self, _: &()) -> bool {
true
}
}

impl FinSet for SingletonSet {
fn iter(&self) -> impl Iterator<Item = ()> {
[()].iter().cloned()
}
fn len(&self) -> usize {
1
}
fn is_empty(&self) -> bool {
false
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down