Gate Decomposition¶
Adapted from qgopt/docs/gate-decomposition
It's known that any two-qubits gate can be decomposed as follows:
u1 u2
cnot
u3 u4
cnot
u5 u6
cnot
u7 u8
where each line represent one layer of either single-qubit gates or CNOT gates. In this tutorial, we will show how to perform gate such decomposition for any two-qubits gate.
In [1]:
Copied!
import numpy as np
import torch
try:
import numqi
except ImportError:
%pip install numqi
import numqi
import numpy as np
import torch
try:
import numqi
except ImportError:
%pip install numqi
import numqi
In [2]:
Copied!
class GateDecompositionModel(torch.nn.Module):
def __init__(self):
super().__init__()
self.manifold = numqi.manifold.SpecialOrthogonal(dim=2, batch_size=8, dtype=torch.complex128)
self.unitary_dag = None #we will set this in .set_unitary()
self.cnot = torch.tensor([[1,0,0,0], [0,1,0,0], [0,0,0,1], [0,0,1,0]], dtype=torch.complex128)
def set_unitary(self, np0):
assert np0.shape==(4,4)
assert np.abs(np0@np0.T.conj() - np.eye(4)).max() < 1e-10, 'unitary matrix required'
self.unitary_dag = torch.tensor(np0.T.conj().copy(), dtype=torch.complex128)
def get_unitary(self, return_numpy=True):
ulist = self.manifold()
matU = torch.kron(ulist[0], ulist[1])
for i in range(3):
matU = self.cnot @ matU
matU = torch.kron(ulist[2*i+2], ulist[2*i+3]) @ matU
ret = ulist, matU
if return_numpy:
ret = ret[0].detach().numpy(), ret[1].detach().numpy()
return ret
def forward(self):
_,matU = self.get_unitary(return_numpy=False)
tmp0 = torch.trace(self.unitary_dag @ matU)
# unitary infidelity https://docs.q-ctrl.com/references/qctrl/Graphs/Graph/unitary_infidelity.html
loss = 1 - (tmp0.real**2 + tmp0.imag**2) / 16
return loss
class GateDecompositionModel(torch.nn.Module):
def __init__(self):
super().__init__()
self.manifold = numqi.manifold.SpecialOrthogonal(dim=2, batch_size=8, dtype=torch.complex128)
self.unitary_dag = None #we will set this in .set_unitary()
self.cnot = torch.tensor([[1,0,0,0], [0,1,0,0], [0,0,0,1], [0,0,1,0]], dtype=torch.complex128)
def set_unitary(self, np0):
assert np0.shape==(4,4)
assert np.abs(np0@np0.T.conj() - np.eye(4)).max() < 1e-10, 'unitary matrix required'
self.unitary_dag = torch.tensor(np0.T.conj().copy(), dtype=torch.complex128)
def get_unitary(self, return_numpy=True):
ulist = self.manifold()
matU = torch.kron(ulist[0], ulist[1])
for i in range(3):
matU = self.cnot @ matU
matU = torch.kron(ulist[2*i+2], ulist[2*i+3]) @ matU
ret = ulist, matU
if return_numpy:
ret = ret[0].detach().numpy(), ret[1].detach().numpy()
return ret
def forward(self):
_,matU = self.get_unitary(return_numpy=False)
tmp0 = torch.trace(self.unitary_dag @ matU)
# unitary infidelity https://docs.q-ctrl.com/references/qctrl/Graphs/Graph/unitary_infidelity.html
loss = 1 - (tmp0.real**2 + tmp0.imag**2) / 16
return loss
Different from QGopt, we choose unitary infidelity as loss function qctrl/unitary_infidelity
$$ \mathcal{L}=1 - \lvert\mathrm{Tr}[V(\theta) U^\dagger]\rvert^2 / d^2 $$
for that we ignore the global phase of the target unitary $V$. Above $d$ denotes the dimension of the Hilbert space $d=4$, $U$ for the target untiary matrix and $V(\theta)$ is the parametrized unitary matrix composed of single-qubit gates and CNOT gates.
In [3]:
Copied!
target_unitary = numqi.random.rand_special_orthogonal_matrix(4, tag_complex=True)
print(f'target unitary:\n{np.around(target_unitary, 3)}')
tmp0 = target_unitary @ target_unitary.T.conj()
print(f'unitary check (U U^dag): \n{np.around(tmp0, 3)}')
target_unitary = numqi.random.rand_special_orthogonal_matrix(4, tag_complex=True)
print(f'target unitary:\n{np.around(target_unitary, 3)}')
tmp0 = target_unitary @ target_unitary.T.conj()
print(f'unitary check (U U^dag): \n{np.around(tmp0, 3)}')
target unitary: [[ 0.232-0.153j 0.427+0.587j -0.287+0.552j 0.005-0.09j ] [ 0.718+0.145j -0.216-0.247j 0.159+0.175j -0.276-0.473j] [ 0.071-0.18j 0.355-0.448j -0.7 -0.245j -0.29 -0.033j] [-0.411-0.425j -0.197+0.012j -0.073+0.03j 0.142-0.765j]] unitary check (U U^dag): [[ 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]]
In [4]:
Copied!
model = GateDecompositionModel()
model.set_unitary(target_unitary)
theta_optim = numqi.optimize.minimize(model, theta0='uniform', num_repeat=1, print_freq=5, tol=1e-10)
model = GateDecompositionModel()
model.set_unitary(target_unitary)
theta_optim = numqi.optimize.minimize(model, theta0='uniform', num_repeat=1, print_freq=5, tol=1e-10)
[step=0][time=0.013 seconds] loss=0.7230776691889222 [step=5][time=0.039 seconds] loss=0.13019596904965303 [step=10][time=0.030 seconds] loss=0.0006483885335120121 [step=15][time=0.031 seconds] loss=2.688432843012123e-08 [round=0] min(f)=4.66817251520979e-10, current(f)=4.66817251520979e-10
In [5]:
Copied!
ulist, matU = model.get_unitary()
print('first several single-qubit unitary matrices:')
for i in range(3):
print(f'U{i} = \n{np.around(ulist[i], 3)}')
ulist, matU = model.get_unitary()
print('first several single-qubit unitary matrices:')
for i in range(3):
print(f'U{i} = \n{np.around(ulist[i], 3)}')
first several single-qubit unitary matrices: U0 = [[-0.016+0.015j -0.671-0.741j] [ 0.671-0.741j -0.016-0.015j]] U1 = [[ 0.023+0.422j 0.33 -0.844j] [-0.33 -0.844j 0.023-0.422j]] U2 = [[-0.184-0.07j -0.531-0.824j] [ 0.531-0.824j -0.184+0.07j ]]
In [6]:
Copied!
print(f'final 2-qubit unitary matrix:\n{np.around(matU, 3)}')
tmp0 = matU @ target_unitary.T.conj()
print(f'check unitary (V U^dag):\n{np.around(tmp0,5)}')
print(f'final 2-qubit unitary matrix:\n{np.around(matU, 3)}')
tmp0 = matU @ target_unitary.T.conj()
print(f'check unitary (V U^dag):\n{np.around(tmp0,5)}')
final 2-qubit unitary matrix: [[ 0.056-0.273j 0.717+0.113j 0.188+0.593j -0.061-0.067j] [ 0.61 -0.405j -0.327-0.021j 0.236+0.011j -0.53 -0.14j ] [-0.077-0.178j -0.066-0.568j -0.668+0.322j -0.229+0.182j] [-0.591-0.01j -0.131+0.148j -0.03 +0.073j -0.441-0.642j]] check unitary (V U^dag): [[ 7.0711e-01-7.0711e-01j -1.0000e-05-0.0000e+00j 2.0000e-05-2.0000e-05j 1.0000e-05+0.0000e+00j] [-0.0000e+00-1.0000e-05j 7.0711e-01-7.0710e-01j 0.0000e+00+0.0000e+00j 0.0000e+00-0.0000e+00j] [-2.0000e-05+2.0000e-05j 0.0000e+00+0.0000e+00j 7.0710e-01-7.0711e-01j -0.0000e+00-0.0000e+00j] [ 0.0000e+00+1.0000e-05j -0.0000e+00+0.0000e+00j -0.0000e+00-0.0000e+00j 7.0711e-01-7.0711e-01j]]