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
andqubit
.New boxes
Discard
,Measure
andClassicalGate
can be simulated withcqmap
or sent topytket
.
2. ZX diagrams and PyZX interface
discopy.quantum.zx
implements diagrams with spiders, swaps and Hadamard boxes.to_pyzx
andfrom_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()
[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()
[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')
[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())
[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:
To PyZX:
And back!
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="|-->")
[13]:
gradient = (circuit >> circuit[::-1]).grad(phi)
gradient.draw(draw_type_labels=False)
[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')
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"))
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()
[20]:
F(parsing["Alice loves Alice"]).grad(symbols("a0")).draw()