open_hypergraphs/lib.rs
1//! # Open Hypergraphs
2//!
3//! `open-hypergraphs` is a [GPU-accelerated](#data-parallelism) implementation of the
4//! [OpenHypergraph](crate::open_hypergraph::OpenHypergraph)
5//! datastructure from the paper
6//! ["Data-Parallel Algorithms for String Diagrams"](https://arxiv.org/pdf/2305.01041).
7//! Open hypergraphs are used for representing, evaluating, and differentiating large networks of operations with multiple
8//! inputs and outputs.
9//!
10//! Here's a drawing of an open hypergraph with labeled nodes `●` and hyperedges `□`.
11//!
12//! ```text
13//! /───────────────────────────────────
14//! ╱
15//! ───────────●
16//! i8\ ┌─────┐
17//! \─────┤ │ ┌─────┐
18//! 2 │ Sub ├───●────┤ Neg ├───●───
19//! ─────●─────────────┤ │ i8 └─────┘ i8
20//! i8 └─────┘
21//! ```
22//!
23//! This open hypergraph represents a circuit with two inputs.
24//! Call the two inputs `x` and `y`, then
25//! this circuit computes `x` on its first output and
26//! `- (x - y)` on its second.
27//!
28//! See the [datastructure](#datastructure) section for a formal definition.
29//!
30//! # What are Open Hypergraphs For?
31//!
32//! Open Hypergraphs are a general, differentiable and data-parallel datastructure for *syntax*.
33//! Here's a few examples of suitable uses:
34//!
35//! - Differentiable array programs for deep learning in [catgrad](https://catgrad.com)
36//! - Terms in [first order logic](https://arxiv.org/pdf/2401.07055)
37//! - Programs in the [λ-calculus](https://en.wikipedia.org/wiki/Cartesian_closed_category)
38//! - [Circuits with feedback](https://arxiv.org/pdf/2201.10456)
39//! - [Interaction nets](https://dl.acm.org/doi/10.1006/inco.1997.2643)
40//!
41//! Open Hypergraphs have some unique advantages compared to tree-based representations of syntax.
42//! For example, they can represent operations with *multiple outputs*, and structures with
43//! *feedback*.
44//! See the [comparison to trees and graphs](#comparison-to-trees-and-graphs) for more detail.
45//!
46//! Differentiability of open hypergraphs (as used in [catgrad](https://catgrad.com))
47//! comes from the [data-parallel algorithm](crate::functor::optic::Optic) for generalised
48//! ahead-of-time automatic differentiation by optic composition.
49//! You can read more about this in the papers
50//! ["Categorical Foundations of Gradient-Based Learning"](https://arxiv.org/abs/2103.01931)
51//! and ["Data-Parallel Algorithms for String Diagrams"](https://arxiv.org/pdf/2305.01041).
52//! See the [Theory](#theory) section for more pointers.
53//!
54//! # Usage
55//!
56//! If you're new to the library, you should start with the [`crate::lax`] module.
57//! This provides a mutable, imperative, single-threaded interface to building open hypergraphs
58//! which should be familiar if you've used a graph library before.
59//!
60//! We can build the example open hypergraph above as follows:
61//!
62//! ```rust
63//! use open_hypergraphs::lax::*;
64//!
65//! pub enum NodeLabel { I8 };
66//! pub enum EdgeLabel { Sub, Neg };
67//!
68//! #[test]
69//! fn build() -> OpenHypergraph<NodeLabel, EdgeLabel> {
70//! use NodeLabel::*;
71//! use EdgeLabel::*;
72//!
73//! // Create an empty OpenHypergraph.
74//! let mut example = OpenHypergraph::<NodeLabel, EdgeLabel>::empty();
75//!
76//! // Create all 4 nodes
77//! let x = example.new_node(I8);
78//! let a = example.new_node(I8);
79//! let y = example.new_node(I8);
80//! let z = example.new_node(I8);
81//!
82//! // Add the "Sub" hyperedge with source nodes `[x, y]` and targets `[a]`
83//! let sub = example.new_edge(Sub, Hyperedge { sources: vec![x, y], targets: vec![a] });
84//!
85//! // Add the 'Neg' hyperedge with sources `[a]` and targets `[z]`
86//! let sub = example.new_edge(Neg, Hyperedge { sources: vec![a], targets: vec![z] });
87//!
88//! // set the sources and targets of the example
89//! example.sources = vec![x, y];
90//! example.targets = vec![z];
91//!
92//! // return the example
93//! example
94//! }
95//! ```
96//!
97//! The [`crate::lax::var::Var`] struct is a helper on top of the imperative interface which
98//! reduces some boilerplate, especially when operators are involved.
99//! We can rewrite the above example as follows:
100//!
101//! ```ignore
102//! pub fn example() {
103//! let state = OpenHypergraph::empty();
104//! let x = Var::new(state, I8);
105//! let y = Var::new(state, I8);
106//! let (z0, z1) = (x.clone(), -(x - y));
107//! }
108//! ```
109//!
110//! See `examples/adder.rs` for a more complete example using this interface to build an n-bit full
111//! adder from half-adder circuits.
112//!
113//! # Datastructure
114//!
115//! Before giving the formal definition, let's revisit the example above.
116//!
117//! ```text
118//! /───────────────────────────────────
119//! 0╱
120//! ───────────●
121//! i8\ ┌─────┐
122//! \─────┤ │ 1 ┌─────┐ 3
123//! 2 │ Sub ├───●────┤ Neg ├───●───
124//! ─────●─────────────┤ │ i8 └─────┘ i8
125//! i8 └─────┘
126//! ```
127//!
128//! There are 4 nodes in this open hypergraph, depicted as `●` with a label `i8` and a
129//! node ID in the set `{0..3}`.
130//! There are two hyperedges depicted as a boxes labeled `Sub` and `Neg`.
131//!
132//! Each hyperedge has an *ordered list* of sources and targets.
133//! For example, the `Sub` edge has sources `[0, 2]` and targets `[1]`,
134//! while `Neg` has sources `[1]` and targets `[3]`.
135//! Note: the order is important!
136//! Without it, we couldn't represent non-commutative operations like `Sub`.
137//!
138//! As well as the sources and targets for each *hyperedge*, the whole "open hypergraph" also has
139//! sources and targets.
140//! These are drawn as dangling wires on the left and right.
141//! In this example, the sources are `[0, 2]`, and the targets are `[0, 3]`.
142//!
143//! Observe that there are no restrictions on how many times a node can appear as a source or
144//! target of both hyperedges and the open hypergraph as a whole.
145//! For example, node `0` is a source and target of the open hypergraph, *and* a source of the
146//! `Sub` edge.
147//! Another example: node `1` is not a source or target of the open hypergraph, although it *is* a
148//! target of the `Sub` hyperedge and a source of the `Neg` hyperedge.
149//!
150//! It's also possible to have nodes which are neither sources nor targets of the open hypergraph
151//! *or* any hyperedge, but that isn't pictured here. See the [theory](#theory) section for more
152//! detail.
153//!
154//! # Formal Definition
155//!
156//! Formally, an open hypergraph is a triple of:
157//!
158//! 1. A Hypergraph `h` with `N ∈ ℕ` nodes
159//! 2. An array `s` of length `A ∈ ℕ` whose elements `s_i ∈ {0..N-1}` are nodes
160//! 3. An array `t` of length `B ∈ ℕ` whose elements `t_i ∈ {0..N-1}` are nodes
161//!
162//! Concretely, the particular kind of hypergraph in an *open* hypergraph has:
163//!
164//! - A finite set of `N` nodes, labeled with an element from a set `Σ₀`
165//! - A finite set of `E` *hyperedges*, labeled from the set `Σ₁`
166//! - For each hyperedge `e ∈ E`,
167//! - An ordered array of *source nodes*
168//! - An ordered array of *target nodes*
169//!
170//! # Comparison to Trees and Graphs
171//!
172//! Let's compare the open hypergraph representation of the example term above against *tree* and
173//! *graph* representations.
174//!
175//! When considered as a tree, the term `(x, - (x - y))` can be drawn as follows:
176//!
177//! ```text
178//! Pair
179//! / \
180//! / Neg
181//! x |
182//! Sub
183//! / \
184//! x y
185//! ```
186//!
187//! There are two problems here:
188//!
189//! 1. To handle multiple outputs, we had to include a tuple constructor "Pair" in our language.
190//! 2. The "sharing" of variables is not evident from the tree structure: x is used twice, but we
191//! have to compare strings to "discover" that fact.
192//!
193//! In contrast, the open hypergraph:
194//!
195//! 1. Allows for terms with **multiple outputs**, without having to introduce a tuple type to the
196//! language.
197//! 2. Encodes the **sharing** of variables naturally by allowing nodes to appear in multiple
198//! sources and targets.
199//!
200//! Another common approach is to use a *graph* for syntax where nodes are operations, and an edge
201//! between two nodes indicates the *output* of the source node is the *input* of the target.
202//! Problems:
203//!
204//! 1. Nodes don't distinguish the order of edges, so argument order has to be tracked separately
205//! 2. There is no notion of input or output to the whole system.
206//!
207//! In contrast, the open hypergraph:
208//!
209//! 1. Naturally handles operations with multiple ordered inputs and outputs (as *hyperedges*)
210//! 2. Comes equipped with global source and target nodes
211//!
212//! Open Hypergraphs have general utility because they model any system which can be described in terms of symmetric monoidal
213//! categories.
214//! Some examples are listed [above](#what-are-open-hypergraphs-for);
215//! see the [Theory](#theory) section for more pointers to detail on the mathematical
216//! underpinnings.
217//!
218//! # Theory
219//!
220//! Formally, an `OpenHypergraph<Σ₀, Σ₁>` is an arrow of
221//! the free [symmetric monoidal category](https://en.wikipedia.org/wiki/Symmetric_monoidal_category)
222//! presented by the signature `(Σ₀, Σ₁)` plus "Special Frobenius" structure.
223//!
224//! This extra structure is sometimes useful (e.g. in autodiff), but can be removed by restricting
225//! the open hypergraph such that nodes always appear in exactly one source and target.
226//! This condition is called "monogamous acyclicity".
227//!
228//! A complete mathematical explanation can be found in the papers
229//! [String Diagram Rewrite Theory I](https://arxiv.org/abs/2012.01847),
230//! [II](https://arxiv.org/abs/2104.14686),
231//! and
232//! [III](https://arxiv.org/abs/2109.06049),
233//! which also includes details on how to *rewrite* open hypergraphs.
234//!
235//! The implementation in *this* library is based on the data-parallel algorithms described in the
236//! paper [Data Parallel Algorithms for String Diagrams](https://arxiv.org/pdf/2305.01041).
237//! In particular, the "generalised autodiff" algorithm can be found in sections 9 and 10 of that
238//! paper.
239
240pub mod array;
241pub mod category;
242pub mod finite_function;
243pub mod indexed_coproduct;
244pub mod operations;
245pub mod semifinite;
246
247pub mod hypergraph;
248pub mod open_hypergraph;
249
250pub mod eval;
251pub mod functor;
252pub mod layer;
253
254// imperative interface to building open hypergraphs
255pub mod lax;
256
257pub mod prelude {
258 //! Type alises for Open Hypergraphs using the [`VecKind`] array backend.
259 pub use crate::array::vec::*;
260 pub use crate::category::*;
261
262 pub type OpenHypergraph<Obj, Arr> = crate::open_hypergraph::OpenHypergraph<VecKind, Obj, Arr>;
263 pub type Hypergraph<Obj, Arr> = crate::hypergraph::Hypergraph<VecKind, Obj, Arr>;
264 pub type FiniteFunction = crate::finite_function::FiniteFunction<VecKind>;
265 pub type SemifiniteFunction<T> = crate::semifinite::SemifiniteFunction<VecKind, T>;
266 pub type IndexedCoproduct<F> = crate::indexed_coproduct::IndexedCoproduct<VecKind, F>;
267}