New features v0.3.3#

1. Classical-quantum maps and mixed circuits

  • discopy.quantum.cqmap implements Bob and Aleks’ classical-quantum maps.

  • Now discopy.quantum.circuit diagrams have two generating objects: bit and qubit.

  • New boxes Discard, Measure and ClassicalGate can be simulated with cqmap or sent to pytket.

2. ZX diagrams and PyZX interface

  • discopy.quantum.zx implements diagrams with spiders, swaps and Hadamard boxes.

  • to_pyzx and from_pyzx methods can be used to turn diagrams into graphs, simplify then back.

3. Parametrised diagrams, formal sums and automatic gradients

  • We can use sympy.Symbols as variables in our diagrams (tensor, circuit or ZX).

  • We can take formal sums of diagrams. TensorFunctor sends formal sums to concrete sums.

  • Given a diagram (tensor, circuit or ZX) with a free variable, we can compute its gradient as a sum.

4. Learning functors, diagrammatically

  • We can use automatic gradients to learn functors (classical and/or quantum) from data.

1. Classical-quantum maps and mixed circuits#

[1]:
from discopy.quantum import *

circuit = H @ X >> CX >> Measure() @ Id(qubit)
circuit.draw()
../_images/notebooks_new-features-0.3.3_2_0.png
[2]:
circuit.eval()
[2]:
CQMap(dom=Q(Dim(2, 2)), cod=C(Dim(2)) @ Q(Dim(2)), array=[0. +0.j, 0. +0.j, 0. +0.j, ..., 0.5+0.j, 0. +0.j, 0. +0.j])
[3]:
circuit.init_and_discard().draw()
../_images/notebooks_new-features-0.3.3_4_0.png
[4]:
circuit.init_and_discard().eval()
[4]:
CQMap(dom=CQ(), cod=C(Dim(2)), array=[0.5+0.j, 0. +0.j])
[5]:
circuit.to_tk()
[5]:
tk.Circuit(2, 1).H(0).X(1).CX(0, 1).Measure(0, 0)
[6]:
from pytket.extensions.qiskit import AerBackend
backend = AerBackend()
circuit.eval(backend)
[6]:
Tensor(dom=Dim(1), cod=Dim(2), array=[0.52832031, 0.47167969])
[7]:
postprocess = ClassicalGate('postprocess', 2, 0, data=[1, 0, 0, 0])
postprocessed_circuit = Ket(0, 0) >> H @ X >> CX >> Measure() @ Measure() >> postprocess
postprocessed_circuit.draw(aspect='auto')
../_images/notebooks_new-features-0.3.3_8_0.png
[8]:
postprocessed_circuit.to_tk()
[8]:
tk.Circuit(2, 2).H(0).X(1).CX(0, 1).Measure(0, 0).Measure(1, 1).post_process(ClassicalGate('postprocess', bit @ bit, Ty(), data=[1, 0, 0, 0]))
[9]:
postprocessed_circuit.eval(backend)
[9]:
Tensor(dom=Dim(1), cod=Dim(1), array=[0.])

2. ZX diagrams and PyZX interface#

[10]:
from discopy.quantum.zx import *
from pyzx import draw

bialgebra = Z(1, 2, .25) @ Z(1, 2, .75) >> Id(1) @ SWAP @ Id(1) >> X(2, 1) @ X(2, 1, .5)
bialgebra.draw(aspect='equal')
draw(bialgebra.to_pyzx())
../_images/notebooks_new-features-0.3.3_12_0.png
[11]:
from pyzx import generate, simplify

graph = generate.cliffordT(2, 5)
print("From DisCoPy:")
Diagram.from_pyzx(graph).draw()

print("To PyZX:")
draw(graph)
simplify.full_reduce(graph)
draw(graph)

print("And back!")
Diagram.from_pyzx(graph).draw()
From DisCoPy:
../_images/notebooks_new-features-0.3.3_13_1.png
To PyZX:
And back!
../_images/notebooks_new-features-0.3.3_13_6.png

3. Parametrised diagrams, formal sums and automatic gradients#

[12]:
from sympy.abc import phi
from discopy.quantum import *
from discopy import drawing

circuit = sqrt(2) @ Ket(0, 0) >> H @ Rx(phi) >> CX >> Bra(0, 1)
drawing.equation(circuit, circuit.subs(phi, .5), symbol="|-->")
../_images/notebooks_new-features-0.3.3_15_0.png
[13]:
gradient = (circuit >> circuit[::-1]).grad(phi)
gradient.draw(draw_type_labels=False)
../_images/notebooks_new-features-0.3.3_16_0.png
[14]:
import numpy as np

x = np.arange(0, 1, 0.05)
y = np.array([circuit.lambdify(phi)(i).measure() for i in x])
dy = np.array([gradient.lambdify(phi)(i).eval().array.real for i in x])
[15]:
from matplotlib import pyplot as plt

plt.subplot(2, 1, 1)
plt.plot(x, y)
plt.ylabel("Amplitude")

plt.subplot(2, 1, 2)
plt.plot(x, dy)
plt.ylabel("Gradient")
[15]:
Text(0, 0.5, 'Gradient')
../_images/notebooks_new-features-0.3.3_18_1.png

4. Learning functors, diagrammatically#

[16]:
from discopy import *

s, n = Ty('s'), Ty('n')
Alice, loves, Bob = Word("Alice", n), Word("loves", n.r @ s @ n.l), Word("Bob", n)

grammar = Cup(n, n.r) @ Id(s) @ Cup(n.l, n)
parsing = {
    "{} {} {}".format(subj, verb, obj): subj @ verb @ obj >> grammar
    for subj in [Alice, Bob] for verb in [loves] for obj in [Alice, Bob]}
pregroup.draw(parsing["Alice loves Bob"], aspect='equal')

print("Our favorite toy dataset:")
corpus = {
    "{} {} {}".format(subj, verb, obj): int(obj != subj)
    for subj in [Alice, Bob] for verb in [loves] for obj in [Alice, Bob]}
for sentence, scalar in corpus.items():
    print("'{}' is {}.".format(sentence, "true" if scalar else "false"))
../_images/notebooks_new-features-0.3.3_20_0.png
Our favorite toy dataset:
'Alice loves Alice' is false.
'Alice loves Bob' is true.
'Bob loves Alice' is true.
'Bob loves Bob' is false.
[17]:
from sympy import symbols

parameters = symbols("a0 a1 b0 b1 c00 c01 c10 c11")

F = TensorFunctor(
    ob={s: 1, n: 2},
    ar={Alice: symbols("a0 a1"),
        Bob: symbols("b0 b1"),
        loves: symbols("c00 c01 c10 c11")})
gradient = F(parsing["Alice loves Bob"]).grad(parameters[0])
gradient
[17]:
Tensor(dom=Dim(1), cod=Dim(1), array=[1.0*b0*c00 + 1.0*b1*c01])
[18]:
gradient.subs(list(zip(parameters, 8 * [0])))
[18]:
Tensor(dom=Dim(1), cod=Dim(1), array=[0])
[19]:
from discopy.quantum import *

gates = {
    Alice: ClassicalGate('Alice', 0, 1, symbols("a0 a1")),
    Bob: ClassicalGate('Bob', 0, 1, symbols("b0 b1")),
    loves: ClassicalGate('loves', 0, 2, symbols("c00 c01 c10 c11"))}

F = CircuitFunctor(ob={s: Ty(), n: bit}, ar=gates)
F(parsing["Alice loves Bob"]).draw()
../_images/notebooks_new-features-0.3.3_23_0.png
[20]:
F(parsing["Alice loves Alice"]).grad(symbols("a0")).draw()
../_images/notebooks_new-features-0.3.3_24_0.png