# monoidal.Diagram¶

class discopy.monoidal.Diagram(dom, cod, boxes, offsets, layers=None)

Defines a diagram given dom, cod, a list of boxes and offsets.

Parameters
• dom (monoidal.Ty) – Domain of the diagram.

• cod (monoidal.Ty) – Codomain of the diagram.

• boxes (list of Diagram) – Boxes of the diagram.

• offsets (list of int) – Offsets of each box in the diagram.

• layers (list of Layer, optional) – Layers of the diagram, computed from boxes and offsets if None.

Raises

AxiomError – Whenever the boxes do not compose.

Examples

>>> x, y, z, w = Ty('x'), Ty('y'), Ty('z'), Ty('w')
>>> f0, f1, g = Box('f0', x, y), Box('f1', z, w), Box('g', y @ w, y)
>>> d = Diagram(x @ z, y, [f0, f1, g], [0, 1, 0])
>>> assert d == f0 @ f1 >> g

>>> d.draw(figsize=(2, 2),
...        path='docs/_static/imgs/monoidal/arrow-example.png')


Methods

 Computes the depth of a diagram by foliating it. Downcasting to discopy.monoidal.Diagram. draw(**params) Draws a diagram using networkx and matplotlib. Takes a diagram of diagrams and returns a diagram. foliate([yield_slices]) Generator yielding the interchanger steps in the foliation of self. Returns a diagram with normal_form diagrams of depth 1 as boxes such that its flattening gives the original diagram back. from_tree(tree) Decodes a tree as an arrow. interchange(i, j[, left]) Returns a new diagram with boxes i and j interchanged. lambdify(*symbols, **kwargs) Turns a symbolic diagram into a function from parameters to diagram. normal_form([normalizer]) Returns the normal form of a diagram. normalize([left]) Implements normalisation of connected diagrams, see arXiv:1804.07832. Called when drawing bubbles. Replace each bubble by::. permutation(perm[, dom, ar_factory]) Returns the diagram that encodes a permutation of wires. permute(*perm) Returns self >> self.permutation(perm, self.dom). subclass(ar_factory) Decorator for subclasses of Diagram. subs(*args) Substitute a variable by an expression. swap(left, right[, ar_factory, swap_factory]) Returns a diagram that swaps the left with the right wires. tensor([other]) Returns the horizontal composition of ‘self’ with a diagram ‘other’. then(*others) Returns the composition of self with arrows others. to_gif(*diagrams, **params) Builds a gif with the normalisation steps. Encodes an arrow as a tree. Computes the width of a diagram, i.e. the maximum number of parallel wires.

Attributes

 layers A discopy.cat.Arrow with Layer boxes such that. offsets The offset of a box is the number of wires to its left.
static upgrade(old)

Allows class inheritance.

downgrade()

Downcasting to discopy.monoidal.Diagram.

to_tree()

Encodes an arrow as a tree.

classmethod from_tree(tree)

Decodes a tree as an arrow.

property offsets

The offset of a box is the number of wires to its left.

property layers

A discopy.cat.Arrow with Layer boxes such that:

diagram == Id(diagram.dom).then(*[
Id(left) @ box @ Id(right)
for left, box, right in diagram.layers])


This is accessed using python slices:

diagram[i:j] == Diagram(
dom=diagram.layers[i].dom,
cod=diagram.layers[j - 1].cod,
boxes=diagram.boxes[i:j],
offsets=diagram.offsets[i:j],
layers=diagram.layers[i:j])

then(*others)

Returns the composition of self with arrows others.

This method is called using the binary operators >> and <<:

>>> x, y, z = Ob('x'), Ob('y'), Ob('z')
>>> f, g, h = Box('f', x, y), Box('g', y, z), Box('h', z, x)
>>> assert f.then(g) == f >> g == g << f

Parameters

others (cat.Arrow) – such that self.cod == others[0].dom and all(x.cod == y.dom for x, y in zip(others, others[1:]).

Returns

arrow – such that arrow.boxes == self.boxes + sum(other.boxes for other in others, []).

Return type

cat.Arrow

Raises

cat.AxiomError – whenever self and others do not compose.

Notes

We can check the axioms of categories (i.e. composition is unital and associative):

>>> assert f >> Id(y) == f == Id(x) >> f
>>> assert (f >> g) >> h == f >> (g >> h)

tensor(other=None, *rest)

Returns the horizontal composition of ‘self’ with a diagram ‘other’.

This method is called using the binary operator @:

>>> x, y, z, w = Ty('x'), Ty('y'), Ty('z'), Ty('w')
>>> f0, f1 = Box('f0', x, y), Box('f1', z, w)
>>> assert f0 @ f1 == f0.tensor(f1) == f0 @ Id(z) >> Id(y) @ f1

>>> (f0 @ f1).draw(
...     figsize=(2, 2),
...     path='docs/_static/imgs/monoidal/tensor-example.png')

Parameters

other (Diagram) –

Returns

diagram – the tensor of ‘self’ and ‘other’.

Return type

Diagram

subs(*args)

Substitute a variable by an expression.

Parameters
• var (sympy.Symbol) – Subtituted variable.

• expr (sympy.Expr) – Substituting expression.

Returns

arrow

Return type

Arrow

Note

You can give a list of (var, expr) pairs for multiple substitution.

Examples

>>> from sympy.abc import phi, psi
>>> x, y = Ob('x'), Ob('y')
>>> f = Box('f', x, y, data={"Alice": [phi + 1]})
>>> g = Box('g', y, x, data={"Bob": [psi / 2]})
>>> assert (f >> g).subs(phi, phi + 1) == f.subs(phi, phi + 1) >> g
>>> assert (f >> g).subs(phi, 1) == f.subs(phi, 1) >> g
>>> assert (f >> g).subs(psi, 1) == f >> g.subs(psi, 1)

lambdify(*symbols, **kwargs)

Turns a symbolic diagram into a function from parameters to diagram.

Parameters
• symbols (list of sympy.Symbol) – Inputs of the lambda.

• kwargs (any) – Passed to sympy.lambdify

Returns

lambda – Takes concrete values returns concrete diagrams.

Return type

callable

Examples

>>> from sympy.abc import phi, psi
>>> x, y, z = Ob('x'), Ob('y'), Ob('z')
>>> f, g = Box('f', x, y, data=phi), Box('g', y, z, data=psi)
>>> assert f.lambdify(psi)(42) == f
>>> assert (f >> g).lambdify(phi, psi)(42, 43)\
...     == Box('f', x, y, data=42) >> Box('g', y, z, data=43)

static swap(left, right, ar_factory=None, swap_factory=None)

Returns a diagram that swaps the left with the right wires.

Parameters
Returns

diagram – with diagram.dom == left @ right

Return type

monoidal.Diagram

static permutation(perm, dom=None, ar_factory=None)

Returns the diagram that encodes a permutation of wires.

Parameters
• perm (list of int) – such that i goes to perm[i]

• dom (monoidal.Ty, optional) – of the same length as perm, default is PRO(len(perm)).

Returns

diagram

Return type

monoidal.Diagram

permute(*perm)

Returns self >> self.permutation(perm, self.dom).

Parameters

perm (list of int) – such that i goes to perm[i]

Examples

>>> x, y, z = Ty('x'), Ty('y'), Ty('z')
>>> assert Id(x @ y @ z).permute(2, 1, 0).cod == z @ y @ x

static subclass(ar_factory)

Decorator for subclasses of Diagram.

open_bubbles()

Called when drawing bubbles. Replace each bubble by:

open_bubble\
>> Id(left) @ open_bubbles(bubble.inside) @ Id(right)\
>> close_bubble


for left = Ty(bubble.drawing_name) and right = Ty(""). Diagram.downgrade() gets called in the process.

draw(**params)

Draws a diagram using networkx and matplotlib.

Parameters
• draw_as_nodes (bool, optional) – Whether to draw boxes as nodes, default is False.

• color (string, optional) – Color of the box or node, default is white ('#ffffff') for boxes and red ('#ff0000') for nodes.

• textpad (pair of floats, optional) – Padding between text and wires, default is (0.1, 0.1).

• draw_type_labels (bool, optional) – Whether to draw type labels, default is False.

• draw_box_labels (bool, optional) – Whether to draw box labels, default is True.

• aspect (string, optional) – Aspect ratio, one of ['auto', 'equal'].

• margins (tuple, optional) – Margins, default is (0.05, 0.05).

• nodesize (float, optional) – Node size for spiders and controlled gates.

• fontsize (int, optional) – Font size for the boxes, default is 12.

• fontsize_types (int, optional) – Font size for the types, default is 12.

• figsize (tuple, optional) – Figure size.

• path (str, optional) – Where to save the image, if None we call plt.show().

• to_tikz (bool, optional) – Whether to output tikz code instead of matplotlib.

• asymmetry (float, optional) – Make a box and its dagger mirror images, default is .25 * any(box.is_dagger for box in diagram.boxes).

to_gif(*diagrams, **params)

Builds a gif with the normalisation steps.

Parameters
• diagrams (Diagram, optional) – Sequence of diagrams to draw.

• path (str) – Where to save the image, if None a gif gets created.

• timestep (int, optional) – Time step in milliseconds, default is 500.

• loop (bool, optional) – Whether to loop, default is False

• params (any, optional) – Passed to Diagram.draw().

interchange(i, j, left=False)

Returns a new diagram with boxes i and j interchanged.

Gets called recursively whenever i < j + 1 or j < i - 1.

Parameters
• i (int) – Index of the box to interchange.

• j (int) – Index of the new position for the box.

• left (bool, optional) – Whether to apply left interchangers.

Notes

By default, we apply only right exchange moves:

top >> Id(left @ box1.dom @ mid) @ box0 @ Id(right)
>> Id(left) @ box1 @ Id(mid @ box0.cod @ right) >> bottom


gets rewritten to:

top >> Id(left) @ box1 @ Id(mid @ box0.dom @ right)
>> Id(left @ box1.cod @ mid) @ box0 @ Id(right) >> bottom

normalize(left=False)

Implements normalisation of connected diagrams, see arXiv:1804.07832.

Parameters

left (bool, optional) – Passed to Diagram.interchange().

Yields

diagram (Diagram) – Rewrite steps.

Examples

>>> from discopy.monoidal import *
>>> s0, s1 = Box('s0', Ty(), Ty()), Box('s1', Ty(), Ty())
>>> gen = (s0 @ s1).normalize()
>>> for _ in range(3): print(next(gen))
s1 >> s0
s0 >> s1
s1 >> s0

normal_form(normalizer=None, **params)

Returns the normal form of a diagram.

Parameters
Raises

NotImplementedError – Whenever normalizer yields the same rewrite steps twice.

foliate(yield_slices=False)

Generator yielding the interchanger steps in the foliation of self.

Yields

diagram (Diagram) – Rewrite steps of the foliation.

Parameters

yield_slices (bool, optional) – Yield the list of slices of self as last output, used in Diagram.foliation().

Examples

>>> from discopy.monoidal import *
>>> x, y = Ty('x'), Ty('y')
>>> f0, f1 = Box('f0', x, y), Box('f1', y, x)
>>> d = (f0 @ Id(x) >> f0.dagger() @ f1.dagger()) @ (f0 >> f1)
>>> *_, slices = d.foliate(yield_slices=True)
>>> print(slices[0])
f0 @ Id(x @ x) >> Id(y) @ f1[::-1] @ Id(x) >> Id(y @ y) @ f0
>>> print(slices[1])
f0[::-1] @ Id(y @ y) >> Id(x @ y) @ f1

>>> d.draw(figsize=(4, 2),
...        path='docs/_static/imgs/monoidal/foliate-example-1a.png')

>>> drawing.equation(
...     *slices, symbol=', ', figsize=(4, 2),
...     path='docs/_static/imgs/monoidal/foliate-example-1b.png')

>>> ket = Box('ket', Ty(), x)
>>> scalar = Box('scalar', Ty(), Ty())
>>> kets = scalar @ ket @ scalar @ ket
>>> a = kets.foliate()
>>> assert next(a) == kets

>>> kets.draw(figsize=(2, 2),
...           path='docs/_static/imgs/monoidal/foliate-example-2.png')

flatten()

Takes a diagram of diagrams and returns a diagram.

>>> from discopy.monoidal import *
>>> x, y = Ty('x'), Ty('y')
>>> f0, f1 = Box('f0', x, y), Box('f1', y, x)
>>> g = Box('g', x @ y, y)
>>> d = (Id(y) @ f0 @ Id(x) >> f0.dagger() @ Id(y) @ f0 >>\
...      g @ f1 >> f1 @ Id(x)).normal_form()
>>> assert d.foliation().flatten().normal_form() == d
>>> assert d.foliation().dagger().flatten()\
...     == d.foliation().flatten().dagger()

foliation()

Returns a diagram with normal_form diagrams of depth 1 as boxes such that its flattening gives the original diagram back.

>>> from discopy.monoidal import *
>>> x, y = Ty('x'), Ty('y')
>>> f0, f1 = Box('f0', x, y), Box('f1', y, x)
>>> d = f0 @ Id(y) >> f0.dagger() @ f1
>>> assert d.foliation().boxes[0] == f0 @ f1
>>> assert d.foliation().flatten().normal_form() == d
>>> assert d.foliation().flatten()\
...     == d[::-1].foliation()[::-1].flatten()\
...     == d[::-1].foliation().flatten()[::-1]
>>> assert d.foliation().flatten().foliation() == d.foliation()
>>> g = Box('g', x @ x, x @ y)
>>> diagram = (d >> g >> d) @ (d >> g >> d)
>>> slices = diagram.foliation()
>>> assert slices.boxes[0] == f0 @ f1 @ f0 @ f1
>>> *_, last_diagram = diagram.foliate()
>>> assert last_diagram == slices.flatten()

depth()

Computes the depth of a diagram by foliating it.

>>> from discopy.monoidal import *
>>> x, y = Ty('x'), Ty('y')
>>> f, g = Box('f', x, y), Box('g', y, x)
>>> assert Id(x @ y).depth() == 0
>>> assert f.depth() == 1
>>> assert (f @ g).depth() == 1
>>> assert (f >> g).depth() == 2

width()

Computes the width of a diagram, i.e. the maximum number of parallel wires.

>>> from discopy.monoidal import *
>>> x = Ty('x')
>>> f = Box('f', x, x ** 4)
>>> assert (f @ Id(x ** 2) >> Id(x ** 2) @ f.dagger()).width() == 6

layer_factory

alias of Layer

bubble_factory

alias of Bubble

id

alias of Id

sum

alias of Sum