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