from discopy.symmetric import Ty, Box, Diagram
egg, white, yolk = Ty("egg"), Ty("white"), Ty("yolk")
crack = Box("crack", egg, white @ yolk)
merge = lambda X: Box("merge", X @ X, X)
# DisCoPy allows string diagrams to be defined as Python functions@Diagram.from_callable(egg @ egg, white @ yolk)defcrack_two_eggs(x, y):
(a, b), (c, d) = crack(x), crack(y)
return (merge(white)(a, c), merge(yolk)(b, d))
# ... or in point-free style using parallel (@) and sequential (>>) compositionassert crack_two_eggs == crack @ crack\
>> white @ Diagram.swap(yolk, white) @ yolk\
>> merge(white) @ merge(yolk)
crack_two_eggs.draw()
By default, DisCoPy diagrams are made of layers with exactly one box in between some (possibly empty) list of wires on its left- and right-hand side.
In more abstract terms, they are arrows in a free premonoidal category where the tensor product is biased to the left, i.e.
f @ g = f @ g.dom >> f.cod @ g != f.dom @ g >> f @ g.cod
We can get more general diagrams by specifying the list of layers inside manually or by calling the method Diagram.foliation.
Wires can be bent using two special kinds of boxes: cups and caps, which satisfy the snake equations.
from discopy.drawing import Equation
from discopy.rigid import Ty, Id, Cup, Cap
x = Ty('x')
left_snake = x @ Cap(x.r, x) >> Cup(x, x.r) @ x
right_snake = Cap(x, x.l) @ x >> x @ Cup(x.l, x)
assert left_snake.normal_form() == Id(x) == right_snake.normal_form()
Equation(left_snake, Id(x), right_snake).draw()
In particular, DisCoPy can draw the grammatical structure of natural language sentences encoded as reductions in a pregroup grammar.
See Lambek, From Word To Sentence (2008) for an introduction.
from discopy.grammar.pregroup import Ty, Word, Cup
s, n = Ty('s'), Ty('n') # sentence and noun
Alice, Bob = Word('Alice', n), Word('Bob', n)
loves = Word('loves', n.r @ s @ n.l)
sentence = Alice @ loves @ Bob >> Cup(n, n.r) @ s @ Cup(n.l, n)
sentence.foliation().draw()
Many other grammatical frameworks can be encoded as diagrams, e.g. cfg (context-free), categorial and dependency grammars.
Functors & Rewrites
Monoidal functors compute the meaning of a diagram, given an interpretation for each wire and for each box.
In particular, tensor-valued functors evaluate a diagram as a tensor network using numpy, PyTorch, TensorFlow, TensorNetwork or JAX.
Applied to pregroup diagrams, DisCoPy implements the
categorical compositional distributional (DisCoCat) models of
Clark, Coecke, Sadrzadeh (2008).
from discopy.cat import Category
from discopy.grammar import pregroup
from discopy.tensor import Dim, Tensor
F = pregroup.Functor(
ob={s: 1, n: 2},
ar={Alice: [1, 0], loves: [[0, 1], [1, 0]], Bob: [0, 1]},
cod=Category(Dim, Tensor))
assert F(sentence)
Diagram-valued functors can fill each box with a complex diagram.
The result can then be simplified using diagram.normalize() to remove the snakes, this is called autonomisation.
from discopy.grammar.pregroup import Cap, Box
defwiring(word):
if word.cod == n: # word is a nounreturn word
if word.cod == n.r @ s @ n.l: # word is a transitive verb
box = Box(word.name, n @ n, s)
return Cap(n.r, n) @ Cap(n, n.l) >> n.r @ box @ n.l
W = pregroup.Functor(ob={s: s, n: n}, ar=wiring)
rewrite_steps = W(sentence).normalize()
sentence.to_gif(*rewrite_steps)
a glorification of the construction of the integers from the natural numbers
i.e. the same way we can freely add inverses to a commutative monoid to get a group, e.g. $\mathbb{N} \hookrightarrow Int(\mathbb{N}) = \mathbb{Z}$ where
$$
Int(M) \ = \ (M \times M) \ / \ \set{ (x, x') \sim (y, y') \ \vert \ x + y' = x' + y }
$$
The only condition is that the monoid needs to be cancellative, i.e. $x + n = y + n \implies x = y$.
The vertical categorification of a cancellative monoid is called a traced category, where the diagrams can have feedback loops:
Given a traced category $C$, we construct $Int(C)$ with objects given by pairs of objects $Ob(Int(C)) = Ob(C) \times Ob(C)$, arrows given by $Int(C)((x_0, x_1), (y_0, y_1)) = C(x_0 \otimes y_1, x_1 \otimes y_0)$ and the composition is given by symmetric feedback:
The structure theorem of Joyal-Street-Verity says that the embedding $C \hookrightarrow Int(C)$ is fully-faithful, i.e. we can remove all the snakes and replace all the cups and caps with feedback loops.
We can use this geometry of interaction to interpret words as processes rather than states:
from discopy.interaction import Ty, Int
from discopy.compact import Ty as T, Diagram as D, Box, Category
N, S = T('N'), T('S')
A, B = Box('A', N, N), Box('B', N, N)
L = Box('L', N @ S @ N, N @ S @ N)
swaps = D.permutation((2, 1, 0), N @ S @ N)
G = pregroup.Functor(
ob={s: Ty[T](S, S), n: Ty[T](N, N)},
ar={Alice: A, loves: swaps >> L, Bob: B},
cod=Int(Category(T, D)))
ALB_trace = (A @ S @ B >> L).trace(left=True).trace(left=False).foliation()
with D.hypergraph_equality:
assert G(sentence).inside == ALB_trace
Equation(sentence.foliation(), ALB_trace, symbol="$\\mapsto$").draw()
Streams and delayed feedback
A key axiom of traced monoidal categories which allows to simplify diagrams is the yanking equation:
If we relax this assumption we get the concept of a feedback category where the objects come with a delay operation and the feedback loops have a more restricted shape:
Given a symmetric category $C$, we can construct a feedback category of monoidal streams $Stream(C)$ where
the objects are infinite sequences of objects $Ob(Stream(C)) = C \times Ob(Stream(C))$,
the arrows are infinite sequences of arrows $Stream(C)(X, Y) = \coprod_{M} Stream(C)(X, Y, M)$ defined by:
$$Stream(C)(X, Y, M) = C(X_0 \otimes M_0, Y_0 \otimes M_1) \times Stream(C)(X^+, Y^+, M^+)$$
where $X_0$ and $X^+$ are the head and the tail of the stream $X$.
This comes with a delay $d(X) \in Ob(Stream(C))$ given by the monoidal unit as head $d(X)_0 = I$ and the given object as tail $d(X)^+ = X$.
The feedback operation is given by:
We can use this to unroll our diagram of the previous section:
from discopy.stream import Ty, Stream
N, S = Ty("N"), Ty("S")
A, B = [Stream.sequence(f, N, N) for f in"AB"]
L = Stream.sequence('L', S.head @ N.delay() @ N.delay(), N @ N)
ALB = (L >> A @ B).feedback(dom=S.head, cod=Ty(), mem=N @ N)
ALB.unroll(2).now.foliation().draw()
Now if we use the python module to interpret each box as a call to a chatbot with the prompt as input, we can get an output along the following lines:
The play is set in a basement with computers everywhere, Alice and Bob are dressed like hackers with black hoodies and nerdy glasses, they have somewhat of a hipster vibe.
Alice: I think I’ve cracked the encryption, but it’s like nothing I’ve seen before. DisCoPy — it’s almost...alive.
Bob: What do you mean, alive? You’re not saying it’s AI, are you? Because if it is, we’re in way over our heads.
Alice: It’s not just AI, Bob. It’s adaptive, learning—like it knows we’re here.
(Bob takes a step back, his face serious as he considers the implications. He glances at the screens around them, suddenly aware of their presence.)
Bob: If that’s true, we’re not just hacking into the system. We’re waking it up. And if it wakes up angry...
Alice: Then we’re the ones who let it loose.
Bob: We need to find the off switch. Now. Before it finds us.
The Python toolkit for computing with string diagrams.
We found that discopy demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.It has 4 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Socket identified 80 fake candidates targeting engineering roles, including suspected North Korean operators, exposing the new reality of hiring as a security function.
By Lauren Valencia, Kirill Boychenko - Sep 17, 2025
Socket detected multiple compromised CrowdStrike npm packages, continuing the "Shai-Hulud" supply chain attack that has now impacted nearly 500 packages.
By Kush Pandya, Peter van der Zee, Olivia Brown, Socket Research Team - Sep 16, 2025