395 lines
14 KiB
Python
395 lines
14 KiB
Python
import numpy as np
|
|
from abc import abstractmethod
|
|
|
|
|
|
class AbstractCircuit:
|
|
|
|
def __init__(self, nqubits):
|
|
self.nqubits = nqubits
|
|
self.parameters = {}
|
|
|
|
@abstractmethod
|
|
def __iter__(self):
|
|
raise NotImplementedError
|
|
|
|
def to_qasm(self, theta=None):
|
|
"""Creates the circuit in OpenQASM format.
|
|
|
|
Args:
|
|
theta (np.ndarray): If not ``None`` ``RX`` gates with the given
|
|
angles are added before the actual circuit gates so that the
|
|
initial state is non-trivial. Useful for testing.
|
|
|
|
Returns:
|
|
A string with the circuit in OpenQASM format.
|
|
"""
|
|
code = ['OPENQASM 2.0;', 'include "qelib1.inc";',
|
|
f'qreg q[{self.nqubits}];', f'creg m[{self.nqubits}];']
|
|
if theta is not None:
|
|
code.extend(f"rx({t}) q[{i}];" for i, t in enumerate(theta))
|
|
code.extend(iter(self))
|
|
return "\n".join(code)
|
|
|
|
def __str__(self):
|
|
return ", ".join(f"{k}={v}" for k, v in self.parameters.items())
|
|
|
|
|
|
class OneQubitGate(AbstractCircuit):
|
|
"""Applies a specific one qubit gate to all qubits."""
|
|
|
|
def __init__(self, nqubits, nlayers="1", gate="h", angles=""):
|
|
super().__init__(nqubits)
|
|
self.gate = gate
|
|
self.nlayers = int(nlayers)
|
|
self.angles = angles
|
|
self.parameters = {"nqubits": nqubits, "nlayers": nlayers,
|
|
"gate": gate, "params": angles}
|
|
|
|
def base_command(self, i):
|
|
if self.angles:
|
|
return "{}({}) q[{}];".format(self.gate, self.angles, i)
|
|
else:
|
|
return "{} q[{}];".format(self.gate, i)
|
|
|
|
def __iter__(self):
|
|
for _ in range(self.nlayers):
|
|
for i in range(self.nqubits):
|
|
yield self.base_command(i)
|
|
|
|
|
|
class TwoQubitGate(OneQubitGate):
|
|
"""Applies a specific two qubit gate to all pairs of adjacent qubits."""
|
|
|
|
def __init__(self, nqubits, nlayers="1", gate="cx", angles=""):
|
|
super().__init__(nqubits, nlayers, gate, angles)
|
|
|
|
def base_command(self, i):
|
|
if self.angles:
|
|
return "{}({}) q[{}],q[{}];".format(self.gate, self.angles, i, i + 1)
|
|
else:
|
|
return "{} q[{}],q[{}];".format(self.gate, i, i + 1)
|
|
|
|
def __iter__(self):
|
|
for _ in range(self.nlayers):
|
|
for i in range(0, self.nqubits - 1, 2):
|
|
yield self.base_command(i)
|
|
for i in range(1, self.nqubits - 1, 2):
|
|
yield self.base_command(i)
|
|
|
|
|
|
class QFT(AbstractCircuit):
|
|
"""Applies the Quantum Fourier Transform."""
|
|
|
|
def __init__(self, nqubits, swaps="True"):
|
|
super().__init__(nqubits)
|
|
self.swaps = swaps == "True"
|
|
self.parameters = {"nqubits": nqubits, "swaps": swaps}
|
|
|
|
def __iter__(self):
|
|
for i1 in range(self.nqubits):
|
|
yield f"h q[{i1}];"
|
|
for i2 in range(i1 + 1, self.nqubits):
|
|
theta = np.pi / 2 ** (i2 - i1)
|
|
yield f"cu1({theta}) q[{i2}],q[{i1}];"
|
|
|
|
if self.swaps:
|
|
for i in range(self.nqubits // 2):
|
|
yield f"swap q[{i}],q[{self.nqubits - i - 1}];"
|
|
|
|
|
|
class VariationalCircuit(AbstractCircuit):
|
|
"""Example variational circuit consisting of alternating layers of RY and CZ gates."""
|
|
|
|
def __init__(self, nqubits, nlayers="1", seed="123"):
|
|
super().__init__(nqubits)
|
|
self.nlayers = int(nlayers)
|
|
self.seed = int(seed)
|
|
self.parameters = {"nqubits": nqubits, "nlayers": nlayers, "seed": seed}
|
|
|
|
def __iter__(self):
|
|
nparams = 2 * self.nlayers * self.nqubits
|
|
np.random.seed(self.seed)
|
|
theta = iter(2 * np.pi * np.random.random(nparams))
|
|
for l in range(self.nlayers):
|
|
for i in range(self.nqubits):
|
|
yield f"ry({next(theta)}) q[{i}];"
|
|
for i in range(0, self.nqubits - 1, 2):
|
|
yield f"cz q[{i}],q[{i + 1}];"
|
|
for i in range(self.nqubits):
|
|
yield f"ry({next(theta)}) q[{i}];"
|
|
for i in range(1, self.nqubits - 2, 2):
|
|
yield f"cz q[{i}],q[{i + 1}];"
|
|
yield f"cz q[{0}],q[{self.nqubits - 1}];"
|
|
|
|
|
|
class BernsteinVazirani(AbstractCircuit):
|
|
"""Applies the Bernstein-Vazirani algorithm from Qiskit/openqasm.
|
|
|
|
See `https://github.com/Qiskit/openqasm/tree/0af8b8489f32d46692b3a3a1421e98c611cd86cc/benchmarks/bv`
|
|
for the OpenQASM code.
|
|
Note that `Barrier` gates are excluded for simulation.
|
|
"""
|
|
|
|
def __init__(self, nqubits):
|
|
super().__init__(nqubits)
|
|
self.parameters = {"nqubits": nqubits}
|
|
|
|
def __iter__(self):
|
|
yield f"x q[{self.nqubits - 1}];"
|
|
for i in range(self.nqubits):
|
|
yield f"h q[{i}];"
|
|
for i in range(self.nqubits - 1):
|
|
yield f"cx q[{i}],q[{self.nqubits - 1}];"
|
|
for i in range(self.nqubits - 1):
|
|
yield f"h q[{i}];"
|
|
#for i in range(self.nqubits - 1):
|
|
# yield f"measure m[{i}];"
|
|
|
|
|
|
class HiddenShift(AbstractCircuit):
|
|
"""Applies the Hidden Shift algorithm.
|
|
|
|
See `https://github.com/quantumlib/Cirq/blob/master/examples/hidden_shift_algorithm.py`
|
|
for the Cirq code.
|
|
If the shift (hidden bitstring) is not given then it is randomly generated
|
|
using `np.random.randint`.
|
|
"""
|
|
|
|
def __init__(self, nqubits, shift=""):
|
|
super().__init__(nqubits)
|
|
if len(shift):
|
|
if len(shift) != nqubits:
|
|
raise ValueError("Shift bitstring of length {} was given for "
|
|
"circuit of {} qubits."
|
|
"".format(len(shift), nqubits))
|
|
self.shift = [int(x) for x in shift]
|
|
else:
|
|
self.shift = np.random.randint(0, 2, size=(self.nqubits,))
|
|
self.parameters = {"nqubits": nqubits, "shift": shift}
|
|
|
|
def oracle(self):
|
|
for i in range(self.nqubits // 2):
|
|
yield f"cz q[{2 * i}],q[{2 * i + 1}];"
|
|
|
|
def __iter__(self):
|
|
for i in range(self.nqubits):
|
|
yield f"h q[{i}];"
|
|
for i, ish in enumerate(self.shift):
|
|
if ish:
|
|
yield f"x q[{i}];"
|
|
for gate in self.oracle():
|
|
yield gate
|
|
for i, ish in enumerate(self.shift):
|
|
if ish:
|
|
yield f"x q[{i}];"
|
|
for i in range(self.nqubits):
|
|
yield f"h q[{i}];"
|
|
for gate in self.oracle():
|
|
yield gate
|
|
for i in range(self.nqubits):
|
|
yield f"h q[{i}];"
|
|
#for i in range(self.nqubits):
|
|
# yield f"measure m[{i}];"
|
|
|
|
|
|
class QAOA(AbstractCircuit):
|
|
"""Example QAOA circuit for a MaxCut problem instance.
|
|
|
|
See `https://github.com/quantumlib/Cirq/blob/master/examples/qaoa.py` for
|
|
the Cirq code.
|
|
If a JSON file containing the node link structure is given then the graph
|
|
is loaded using `networkx.readwrite.json_graph.node_link_graph`, otherwise
|
|
the graph is generated randomly using `networkx.random_regular_graph`.
|
|
Note that different graphs may lead to different performance as the graph
|
|
structure affects circuit depth.
|
|
"""
|
|
|
|
def __init__(self, nqubits, nparams="2", graph="", seed="123"):
|
|
super().__init__(nqubits)
|
|
import networkx
|
|
self.nparams = int(nparams)
|
|
self.seed = int(seed)
|
|
if len(graph):
|
|
import json
|
|
with open(graph, "r") as file:
|
|
data = json.load(file)
|
|
self.graph = networkx.readwrite.json_graph.node_link_graph(data)
|
|
else:
|
|
self.graph = networkx.random_regular_graph(
|
|
3, self.nqubits, seed=self.seed
|
|
)
|
|
self.parameters = {"nqubits": nqubits, "nparams": nparams,
|
|
"graph": graph, "seed": seed}
|
|
|
|
@staticmethod
|
|
def RX(q, theta):
|
|
return f"rx({theta}) q[{q}];"
|
|
|
|
@staticmethod
|
|
def RZZ(q0, q1, theta):
|
|
return f"rzz({theta}) q[{q0}],q[{q1}];"
|
|
|
|
def maxcut_unitary(self, betas, gammas):
|
|
for beta, gamma in zip(betas, gammas):
|
|
for i, j in self.graph.edges:
|
|
yield self.RZZ(i, j, -0.5 * gamma)
|
|
for i in range(self.nqubits):
|
|
yield self.RX(i, 2 * beta)
|
|
|
|
def dump(self, dir):
|
|
"""Saves graph data as JSON in given directory."""
|
|
import json
|
|
import networkx
|
|
data = networkx.readwrite.json_graph.node_link_data(self.graph)
|
|
with open(dir, "w") as file:
|
|
json.dump(data, file)
|
|
|
|
def __iter__(self):
|
|
np.random.seed(self.seed)
|
|
betas = np.random.uniform(-np.pi, np.pi, size=self.nparams)
|
|
gammas = np.random.uniform(-np.pi, np.pi, size=self.nparams)
|
|
# Prepare uniform superposition
|
|
for i in range(self.nqubits):
|
|
yield f"h q[{i}];"
|
|
# Apply QAOA unitary
|
|
for gate in self.maxcut_unitary(betas, gammas):
|
|
yield gate
|
|
# Measure
|
|
# yield gates.M(*range(self.nqubits))
|
|
|
|
|
|
class SupremacyCircuit(AbstractCircuit):
|
|
"""Random circuit by Boixo et al 2018 for demonstrating quantum supremacy.
|
|
|
|
See `https://github.com/quantumlib/Cirq/blob/v0.11.0/cirq-core/cirq/experiments/google_v2_supremacy_circuit.py`
|
|
for the Cirq code.
|
|
This circuit is constructed using `cirq` by exporting to OpenQASM and
|
|
importing back to Qibo.
|
|
"""
|
|
|
|
def __init__(self, nqubits, depth="2", seed="123"):
|
|
super().__init__(nqubits)
|
|
self.depth = int(depth)
|
|
self.seed = int(seed)
|
|
self.parameters = {"nqubits": nqubits, "depth": depth, "seed": seed}
|
|
self.cirq_circuit = self.create_cirq_circuit()
|
|
|
|
def create_cirq_circuit(self):
|
|
import cirq
|
|
from cirq.experiments import google_v2_supremacy_circuit as spc
|
|
qubits = [cirq.GridQubit(i, 0) for i in range(self.nqubits)]
|
|
return spc.generate_boixo_2018_supremacy_circuits_v2(qubits, self.depth, self.seed)
|
|
|
|
def __iter__(self):
|
|
qasm = self.cirq_circuit.to_qasm()
|
|
for line in qasm.split("\n"):
|
|
first_word = line.split(" ")[0]
|
|
if first_word not in {"//", "OPENQASM", "include", "qreg"}:
|
|
if first_word == "sx":
|
|
yield line.replace("sx", "rx(pi*0.5)") # see issue #13
|
|
else:
|
|
yield line
|
|
|
|
|
|
class BasisChange(AbstractCircuit):
|
|
"""Basis change fermionic circuit.
|
|
|
|
See `https://quantumai.google/openfermion/tutorials/circuits_1_basis_change`
|
|
for OpenFermion/Cirq code.
|
|
This circuit is constructed using `openfermion` and `cirq` by exporting
|
|
to OpenQASM and importing back to Qibo.
|
|
"""
|
|
|
|
def __init__(self, nqubits, simulation_time="1", seed="123"):
|
|
super().__init__(nqubits)
|
|
self.simulation_time = float(simulation_time)
|
|
self.seed = int(seed)
|
|
self.parameters = {"nqubits": nqubits, "simulation_time": simulation_time,
|
|
"seed": seed}
|
|
self.openfermion_circuit = self.create_openfermion_circuit()
|
|
|
|
def create_openfermion_circuit(self):
|
|
import cirq
|
|
import openfermion
|
|
# Generate the random one-body operator.
|
|
T = openfermion.random_hermitian_matrix(self.nqubits, seed=self.seed)
|
|
# Diagonalize T and obtain basis transformation matrix (aka "u").
|
|
eigenvalues, eigenvectors = np.linalg.eigh(T)
|
|
basis_transformation_matrix = eigenvectors.transpose()
|
|
# Initialize the qubit register.
|
|
qubits = cirq.LineQubit.range(self.nqubits)
|
|
# Start circuit with the inverse basis rotation, print out this step.
|
|
inverse_basis_rotation = cirq.inverse(openfermion.bogoliubov_transform(qubits, basis_transformation_matrix))
|
|
circuit = cirq.Circuit(inverse_basis_rotation)
|
|
# Add diagonal phase rotations to circuit.
|
|
for k, eigenvalue in enumerate(eigenvalues):
|
|
phase = -eigenvalue * self.simulation_time
|
|
circuit.append(cirq.rz(rads=phase).on(qubits[k]))
|
|
# Finally, restore basis.
|
|
basis_rotation = openfermion.bogoliubov_transform(qubits, basis_transformation_matrix)
|
|
circuit.append(basis_rotation)
|
|
return circuit
|
|
|
|
def __iter__(self):
|
|
qasm = self.openfermion_circuit.to_qasm()
|
|
for line in qasm.split("\n"):
|
|
first_word = line.split(" ")[0]
|
|
if first_word not in {"//", "OPENQASM", "include", "qreg"}:
|
|
yield line
|
|
|
|
|
|
class QuantumVolume(AbstractCircuit):
|
|
"""Quantum Volume circuit from Qiskit.
|
|
|
|
See `https://qiskit.org/documentation/stubs/qiskit.circuit.library.QuantumVolume.html`
|
|
for the Qiskit model.
|
|
This circuit is constructed using `qiskit` by exporting to OpenQASM and
|
|
importing back to Qibo.
|
|
"""
|
|
|
|
def __init__(self, nqubits, depth="1", seed="123"):
|
|
super().__init__(nqubits)
|
|
self.depth = int(depth)
|
|
self.seed = int(seed)
|
|
self.parameters = {"nqubits": nqubits, "depth": depth, "seed": seed}
|
|
self.qiskit_circuit = self.create_qiskit_circuit()
|
|
self.expression_symbols = {"*", "/"}
|
|
self.expression_symbols.update(str(x) for x in range(10))
|
|
|
|
def create_qiskit_circuit(self):
|
|
from qiskit.circuit.library import QuantumVolume
|
|
circuit = QuantumVolume(self.nqubits, self.depth, seed=self.seed)
|
|
return circuit.decompose().decompose()
|
|
|
|
def __iter__(self):
|
|
raise NotImplementedError("Iteration is not available for "
|
|
"`QuantumVolume` because it is prepared "
|
|
"using Qiskit.")
|
|
|
|
def evaluate_pi(self, qasm):
|
|
left = qasm.find("pi")
|
|
if left < 0:
|
|
return qasm
|
|
|
|
import sympy
|
|
right = left + 2
|
|
left = left - 1
|
|
while qasm[left] in self.expression_symbols:
|
|
left -= 1
|
|
while qasm[right] in self.expression_symbols:
|
|
right += 1
|
|
expr = qasm[left + 1: right]
|
|
evaluated = sympy.sympify(expr).evalf()
|
|
return self.evaluate_pi(qasm.replace(expr, str(evaluated)))
|
|
|
|
def __iter__(self):
|
|
qasm = self.qiskit_circuit.qasm()
|
|
for line in qasm.split("\n"):
|
|
first_word = line.split(" ")[0]
|
|
if first_word not in {"//", "OPENQASM", "include", "qreg"}:
|
|
yield line.replace("1/(15*pi)", str(1.0 / (15.0 * np.pi)))
|
|
|
|
def to_qasm(self, theta=None):
|
|
qasm = super().to_qasm(theta)
|
|
return self.evaluate_pi(qasm)
|