# This file is part of QuTiP: Quantum Toolbox in Python.
#
# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names
# of its contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
###############################################################################
import numpy as np
from qutip.operators import sigmax, sigmay, sigmaz, identity
from qutip.tensor import tensor
from qutip.qip.circuit import QubitCircuit
from qutip.qip.models.circuitprocessor import CircuitProcessor
[docs]class SpinChain(CircuitProcessor):
"""
Representation of the physical implementation of a quantum
program/algorithm on a spin chain qubit system.
"""
def __init__(self, N, correct_global_phase=True,
sx=None, sz=None, sxsy=None):
"""
Parameters
----------
sx: Integer/List
The delta for each of the qubits in the system.
sz: Integer/List
The epsilon for each of the qubits in the system.
sxsy: Integer/List
The interaction strength for each of the qubit pair in the system.
"""
super(SpinChain, self).__init__(N, correct_global_phase)
self.sx_ops = [tensor([sigmax() if m == n else identity(2)
for n in range(N)])
for m in range(N)]
self.sz_ops = [tensor([sigmaz() if m == n else identity(2)
for n in range(N)])
for m in range(N)]
self.sxsy_ops = []
for n in range(N - 1):
x = [identity(2)] * N
x[n] = x[n + 1] = sigmax()
y = [identity(2)] * N
y[n] = y[n + 1] = sigmay()
self.sxsy_ops.append(tensor(x) + tensor(y))
if sx is None:
self.sx_coeff = [0.25 * 2 * np.pi] * N
elif not isinstance(sx, list):
self.sx_coeff = [sx * 2 * np.pi] * N
else:
self.sx_coeff = sx
if sz is None:
self.sz_coeff = [1.0 * 2 * np.pi] * N
elif not isinstance(sz, list):
self.sz_coeff = [sz * 2 * np.pi] * N
else:
self.sz_coeff = sz
if sxsy is None:
self.sxsy_coeff = [0.1 * 2 * np.pi] * (N - 1)
elif not isinstance(sxsy, list):
self.sxsy_coeff = [sxsy * 2 * np.pi] * (N - 1)
else:
self.sxsy_coeff = sxsy
[docs] def get_ops_and_u(self):
return (self.sx_ops + self.sz_ops + self.sxsy_ops,
np.hstack((self.sx_u, self.sz_u, self.sxsy_u)))
[docs] def load_circuit(self, qc):
gates = self.optimize_circuit(qc).gates
self.global_phase = 0
self.sx_u = np.zeros((len(gates), len(self.sx_ops)))
self.sz_u = np.zeros((len(gates), len(self.sz_ops)))
self.sxsy_u = np.zeros((len(gates), len(self.sxsy_ops)))
self.T_list = []
n = 0
for gate in gates:
if gate.name == "ISWAP":
g = self.sxsy_coeff[min(gate.targets)]
if min(gate.targets) == 0 and max(gate.targets) == self.N - 1:
self.sxsy_u[n, self.N - 1] = -g
else:
self.sxsy_u[n, min(gate.targets)] = -g
T = np.pi / (4 * g)
self.T_list.append(T)
n += 1
elif gate.name == "SQRTISWAP":
g = self.sxsy_coeff[min(gate.targets)]
if min(gate.targets) == 0 and max(gate.targets) == self.N - 1:
self.sxsy_u[n, self.N - 1] = -g
else:
self.sxsy_u[n, min(gate.targets)] = -g
T = np.pi / (8 * g)
self.T_list.append(T)
n += 1
elif gate.name == "RZ":
g = self.sz_coeff[gate.targets[0]]
self.sz_u[n, gate.targets[0]] = np.sign(gate.arg_value) * g
T = abs(gate.arg_value) / (2 * g)
self.T_list.append(T)
n += 1
elif gate.name == "RX":
g = self.sx_coeff[gate.targets[0]]
self.sx_u[n, gate.targets[0]] = np.sign(gate.arg_value) * g
T = abs(gate.arg_value) / (2 * g)
self.T_list.append(T)
n += 1
elif gate.name == "GLOBALPHASE":
self.global_phase += gate.arg_value
else:
raise ValueError("Unsupported gate %s" % gate.name)
[docs] def adjacent_gates(self, qc, setup="linear"):
"""
Method to resolve 2 qubit gates with non-adjacent control/s or target/s
in terms of gates with adjacent interactions for linear/circular spin
chain system.
Parameters
----------
qc: QubitCircuit
The circular spin chain circuit to be resolved
setup: Boolean
Linear of Circular spin chain setup
Returns
----------
qc: QubitCircuit
Returns QubitCircuit of resolved gates for the qubit circuit in the
desired basis.
"""
qc_t = QubitCircuit(qc.N, qc.reverse_states)
swap_gates = ["SWAP", "ISWAP", "SQRTISWAP", "SQRTSWAP", "BERKELEY",
"SWAPalpha"]
N = qc.N
for gate in qc.gates:
if gate.name == "CNOT" or gate.name == "CSIGN":
start = min([gate.targets[0], gate.controls[0]])
end = max([gate.targets[0], gate.controls[0]])
if (setup == "linear" or
(setup == "circular" and (end - start) <= N // 2)):
i = start
while i < end:
if (start + end - i - i == 1 and
(end - start + 1) % 2 == 0):
# Apply required gate if control and target are
# adjacent to each other, provided |control-target|
# is even.
if end == gate.controls[0]:
qc_t.add_gate(gate.name, targets=[i],
controls=[i + 1])
else:
qc_t.add_gate(gate.name, targets=[i + 1],
controls=[i])
elif (start + end - i - i == 2 and
(end - start + 1) % 2 == 1):
# Apply a swap between i and its adjacent gate,
# then the required gate if and then another swap
# if control and target have one qubit between
# them, provided |control-target| is odd.
qc_t.add_gate("SWAP", targets=[i, i + 1])
if end == gate.controls[0]:
qc_t.add_gate(gate.name, targets=[i + 1],
controls=[i + 2])
else:
qc_t.add_gate(gate.name, targets=[i + 2],
controls=[i + 1])
qc_t.add_gate("SWAP", [i, i + 1])
i += 1
else:
# Swap the target/s and/or control with their
# adjacent qubit to bring them closer.
qc_t.add_gate("SWAP", [i, i + 1])
qc_t.add_gate("SWAP", [start + end - i - 1,
start + end - i])
i += 1
elif (end - start) < N - 1:
"""
If the resolving has to go backwards, the path is first
mapped to a separate circuit and then copied back to the
original circuit.
"""
temp = QubitCircuit(N - end + start)
i = 0
while i < (N - end + start):
if (N + start - end - i - i == 1 and
(N - end + start + 1) % 2 == 0):
if end == gate.controls[0]:
temp.add_gate(gate.name, targets=[i],
controls=[i + 1])
else:
temp.add_gate(gate.name, targets=[i + 1],
controls=[i])
elif (N + start - end - i - i == 2 and
(N - end + start + 1) % 2 == 1):
temp.add_gate("SWAP", targets=[i, i + 1])
if end == gate.controls[0]:
temp.add_gate(gate.name, targets=[i + 2],
controls=[i + 1])
else:
temp.add_gate(gate.name, targets=[i + 1],
controls=[i + 2])
temp.add_gate("SWAP", [i, i + 1])
i += 1
else:
temp.add_gate("SWAP", [i, i + 1])
temp.add_gate("SWAP",
[N + start - end - i - 1,
N + start - end - i])
i += 1
j = 0
for gate in temp.gates:
if (j < N - end - 2):
if gate.name in ["CNOT", "CSIGN"]:
qc_t.add_gate(gate.name, end + gate.targets[0],
end + gate.controls[0])
else:
qc_t.add_gate(gate.name,
[end + gate.targets[0],
end + gate.targets[1]])
elif (j == N - end - 2):
if gate.name in ["CNOT", "CSIGN"]:
qc_t.add_gate(gate.name, end + gate.targets[0],
(end + gate.controls[0]) % N)
else:
qc_t.add_gate(gate.name,
[end + gate.targets[0],
(end + gate.targets[1]) % N])
else:
if gate.name in ["CNOT", "CSIGN"]:
qc_t.add_gate(gate.name,
(end + gate.targets[0]) % N,
(end + gate.controls[0]) % N)
else:
qc_t.add_gate(gate.name,
[(end + gate.targets[0]) % N,
(end + gate.targets[1]) % N])
j = j + 1
elif (end - start) == N - 1:
qc_t.add_gate(gate.name, gate.targets, gate.controls)
elif gate.name in swap_gates:
start = min([gate.targets[0], gate.targets[1]])
end = max([gate.targets[0], gate.targets[1]])
if (setup == "linear" or
(setup == "circular" and (end - start) <= N // 2)):
i = start
while i < end:
if (start + end - i - i == 1 and
(end - start + 1) % 2 == 0):
qc_t.add_gate(gate.name, [i, i + 1])
elif ((start + end - i - i) == 2 and
(end - start + 1) % 2 == 1):
qc_t.add_gate("SWAP", [i, i + 1])
qc_t.add_gate(gate.name, [i + 1, i + 2])
qc_t.add_gate("SWAP", [i, i + 1])
i += 1
else:
qc_t.add_gate("SWAP", [i, i + 1])
qc_t.add_gate("SWAP", [start + end - i - 1,
start + end - i])
i += 1
else:
temp = QubitCircuit(N - end + start)
i = 0
while i < (N - end + start):
if (N + start - end - i - i == 1 and
(N - end + start + 1) % 2 == 0):
temp.add_gate(gate.name, [i, i + 1])
elif (N + start - end - i - i == 2 and
(N - end + start + 1) % 2 == 1):
temp.add_gate("SWAP", [i, i + 1])
temp.add_gate(gate.name, [i + 1, i + 2])
temp.add_gate("SWAP", [i, i + 1])
i += 1
else:
temp.add_gate("SWAP", [i, i + 1])
temp.add_gate("SWAP", [N + start - end - i - 1,
N + start - end - i])
i += 1
j = 0
for gate in temp.gates:
if(j < N - end - 2):
qc_t.add_gate(gate.name, [end + gate.targets[0],
end + gate.targets[1]])
elif(j == N - end - 2):
qc_t.add_gate(gate.name,
[end + gate.targets[0],
(end + gate.targets[1]) % N])
else:
qc_t.add_gate(gate.name,
[(end + gate.targets[0]) % N,
(end + gate.targets[1]) % N])
j = j + 1
else:
qc_t.add_gate(gate.name, gate.targets, gate.controls,
gate.arg_value, gate.arg_label)
return qc_t
[docs] def optimize_circuit(self, qc):
self.qc0 = qc
self.qc1 = self.adjacent_gates(self.qc0)
self.qc2 = self.qc1.resolve_gates(
basis=["SQRTISWAP", "ISWAP", "RX", "RZ"])
return self.qc2
[docs]class LinearSpinChain(SpinChain):
"""
Representation of the physical implementation of a quantum
program/algorithm on a spin chain qubit system arranged in a linear
formation. It is a sub-class of SpinChain.
"""
def __init__(self, N, correct_global_phase=True,
sx=None, sz=None, sxsy=None):
super(LinearSpinChain, self).__init__(N, correct_global_phase,
sx, sz, sxsy)
[docs] def get_ops_labels(self):
return ([r"$\sigma_x^%d$" % n for n in range(self.N)] +
[r"$\sigma_z^%d$" % n for n in range(self.N)] +
[r"$\sigma_x^%d\sigma_x^{%d} + \sigma_y^%d\sigma_y^{%d}$"
% (n, n, n + 1, n + 1) for n in range(self.N - 1)])
[docs] def adjacent_gates(self, qc):
return super(LinearSpinChain, self).adjacent_gates(qc, "linear")
[docs]class CircularSpinChain(SpinChain):
"""
Representation of the physical implementation of a quantum
program/algorithm on a spin chain qubit system arranged in a circular
formation. It is a sub-class of SpinChain.
"""
def __init__(self, N, correct_global_phase=True,
sx=None, sz=None, sxsy=None):
super(CircularSpinChain, self).__init__(N, correct_global_phase,
sx, sz, sxsy)
x = [identity(2)] * N
x[0] = x[N - 1] = sigmax()
y = [identity(2)] * N
y[0] = y[N - 1] = sigmay()
self.sxsy_ops.append(tensor(x) + tensor(y))
if sxsy is None:
self.sxsy_coeff = [0.1 * 2 * np.pi] * N
elif not isinstance(sxsy, list):
self.sxsy_coeff = [sxsy * 2 * np.pi] * N
else:
self.sxsy_coeff = sxsy
[docs] def get_ops_labels(self):
return ([r"$\sigma_x^%d$" % n for n in range(self.N)] +
[r"$\sigma_z^%d$" % n for n in range(self.N)] +
[r"$\sigma_x^%d\sigma_x^{%d} + \sigma_y^%d\sigma_y^{%d}$"
% (n, n, (n + 1) % self.N, (n + 1) % self.N)
for n in range(self.N)])
[docs] def adjacent_gates(self, qc):
return super(CircularSpinChain, self).adjacent_gates(qc, "circular")