Quantum bit and the usual gates in torch tensors straight from the Wikipedia.
Apply gate to a state
For example this chain evaluates to zero:
>>> from functools import reduce
>>> s = reduce( apply, [Zero(), H(), PauliZ(), H(), PauliX()])
>>> Measure.one(s)
0
Swap places of a bit
>>> one = Combine(Zero(), One())
>>> Measure.one(one)
1
>>> one
tensor([[0.+0.j],
[1.+0.j],
[0.+0.j],
[0.+0.j]])
>>> two = apply(one, SWAP())
>>> Measure.one(two)
2
>>> two
tensor([[0.+0.j],
[0.+0.j],
[1.+0.j],
[0.+0.j]])
Qubit that evaluates as zero every single time
>>> Zero
|0>
>>> Zero()
tensor([[1.+0.j],
[0.+0.j]])
>>> Measure.one(Zero())
0
Qubit that evaluates as one every single time
>>> One
|1>
>>> One()
tensor([[0.+0.j],
[1.+0.j]])
>>> Measure.one(One())
1
Qubit that evaluates as one and zero evenly
>>> Plus
|+>
>>> Plus()
tensor([[0.7071+0.j],
[0.7071+0.j]])
Qubit that evaluates as one and zero evenly
>>> Minus
|->
>>> Minus()
tensor([[ 0.7071+0.j],
[-0.7071+0.j]])
Simulates the measure process of the qubit
>>> Measure.one(One())
1
Use Kronecker product of two arrays to combine qubits.
>>> Combine(Zero(),Zero())
tensor([[1.+0.j],
[0.+0.j],
[0.+0.j],
[0.+0.j]])
>>> from functools import reduce
>>> reduce(Combine, [One(), Zero(), Zero()])
tensor([[0.+0.j],
[0.+0.j],
[0.+0.j],
[0.+0.j],
[1.+0.j],
[0.+0.j],
[0.+0.j],
[0.+0.j]])
>>> Combine(One(), Zero(), Zero())
tensor([[0.+0.j],
[0.+0.j],
[0.+0.j],
[0.+0.j],
[1.+0.j],
[0.+0.j],
[0.+0.j],
[0.+0.j]])
Each row represents the probability of getting it's index's value as a result
>>> Measure.one(Combine(Zero(),Zero()))
0
>>> Measure.one( Combine(One(), Combine(Zero(),Zero())) )
4
The equal is a test if the two qubit states
>>> equal(One(), One())
True
>>> equal(One(), Zero())
False
Identity gate
>>> Identity
Identity
>>> Identity()
tensor([[1.+0.j, 0.+0.j],
[0.+0.j, 1.+0.j]])
>>> Identity(2)
tensor([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j]])
Hadamard gate
>>> H
H
>>> H()
tensor([[ 0.7071+0.j, 0.7071+0.j],
[ 0.7071+0.j, -0.7071+0.j]])
Pauli X gate
>>> PauliX
X
>>> PauliX()
tensor([[0.+0.j, 1.+0.j],
[1.+0.j, 0.+0.j]])
Pauli Y gate
>>> PauliY
Y
>>> PauliY()
tensor([[0.+0.j, -0.-1.j],
[0.+1.j, 0.+0.j]])
Pauli Z gate
>>> PauliZ
Z
>>> PauliZ()
tensor([[ 1.+0.j, 0.+0.j],
[ 0.+0.j, -1.+0.j]])
Phase (S, P) gate
>>> Phase
P
>>> Phase()
tensor([[1.+0.j, 0.+0.j],
[0.+0.j, 0.+1.j]])
R is the custom phase shift gate
>>> from math import pi
>>> R(pi/4)
R(0.7853981633974483)
>>> R(pi/4)()
tensor([[1.0000+0.0000j, 0.0000+0.0000j],
[0.0000+0.0000j, 0.7071+0.7071j]])
CNOT is the Controlled Not gate (CX)
>>> CNOT
CX
>>> CNOT()
tensor([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
[0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j]])
CPauliZ is the Controlled Pauli Z gate (CZ)
>>> CPauliZ
CZ
>>> CPauliZ()
tensor([[ 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[ 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],
[ 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
[ 0.+0.j, 0.+0.j, 0.+0.j, -1.+0.j]])
SWAP is the qbit swap gate
>>> SWAP
SWAP
>>> SWAP()
tensor([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
[0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j]])
P1 is the First Pauli Group done from the cross product of [-1, 1, -1j, 1j] and [Identity(), PauliX(), PauliY(), PauliZ()]
>>> P1
P1
>>> p1 = list(P1())
>>> len(p1)
16
>>> equal(p1[0], -1*Identity())
True
>>> equal(p1[1], 1*Identity())
True
>>> equal(p1[2], -1j*Identity())
True
>>> equal(p1[3], 1j*Identity())
True
>>> equal(p1[15], 1j*PauliZ())
True
p1 is a group, so these apply:
Associativy:
>>> all([ any([equal(apply(a,b), c) for c in P1()]) for a in P1() for b in P1()])
True
Identity:
>>> equal( Identity(), p1[1])
True
>>> all([ equal(apply(a, Identity()), a) for a in P1()])
True
Inverse element:
>>> all([ any([equal(apply(a, b), Identity()) for b in P1()]) for a in P1() ])
True
Pn is the n:th Pauli group instance
>>> P2 = Pn(2)
>>> P2
P2
>>> p2 = list(P2())
>>> len(p2)
1024
>>> p2[0]
tensor([[-1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[ 0.+0.j, -1.+0.j, 0.+0.j, 0.+0.j],
[ 0.+0.j, 0.+0.j, -1.+0.j, 0.+0.j],
[ 0.+0.j, 0.+0.j, 0.+0.j, -1.+0.j]])
>>> p2[1]
tensor([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j]])
p2 is a group, so these apply:
Associativy:
>>> import random
>>> all_products = [apply(a,b) for a in p2 for b in p2]
>>> all([ any([equal(p, c) for c in p2]) for p in random.sample(all_products, 1000)])
True
Identity:
>>> equal(Identity(2), p2[1])
True
>>> i2 = p2[1]
>>> all([ equal(apply(a, i2), a) for a in p2])
True
Inverse element:
>>> all([ any([equal(apply(a, b), i2) for b in p2]) for a in random.sample(p2, 20)])
True
S_5_1_3 error correction code encodes one logical qubit into five physical qubits. S_5_1_3 protects against an arbitrary single-qubit error and it has code distance three codes.
>>> S_5_1_3
S_5_1_3
>>> S_5_1_3.g1()
tensor([[ 0.+0.j, 1.+0.j, 1.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 1.+0.j, 0.+0.j],
[ 1.+0.j, 0.+0.j, 0.+0.j, -1.+0.j, 0.+0.j, -1.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 1.+0.j]])
>>> S_5_1_3.g2()
tensor([[ 1.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 1.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
[ 0.+0.j, 1.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, -1.+0.j, 0.+0.j, -1.+0.j, 1.+0.j, 0.+0.j]])
>>> S_5_1_3.g3()
tensor([[ 0.+0.j, 1.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 1.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
[ 1.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, -1.+0.j, 0.+0.j, -1.+0.j]])
>>> S_5_1_3.g4()
tensor([[ 1.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 1.+0.j, 0.+0.j],
[ 0.+0.j, -1.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, -1.+0.j]])