final first commit
This commit is contained in:
0
qibojit-benchmarks/benchmarks/__init__.py
Normal file
0
qibojit-benchmarks/benchmarks/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
BIN
qibojit-benchmarks/benchmarks/__pycache__/logger.cpython-312.pyc
Normal file
BIN
qibojit-benchmarks/benchmarks/__pycache__/logger.cpython-312.pyc
Normal file
Binary file not shown.
BIN
qibojit-benchmarks/benchmarks/__pycache__/logger.cpython-313.pyc
Normal file
BIN
qibojit-benchmarks/benchmarks/__pycache__/logger.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
53
qibojit-benchmarks/benchmarks/circuits/__init__.py
Normal file
53
qibojit-benchmarks/benchmarks/circuits/__init__.py
Normal file
@@ -0,0 +1,53 @@
|
||||
def parse(options):
|
||||
"""Parse options from string.
|
||||
|
||||
Args:
|
||||
options (str): String with options.
|
||||
It should have the form 'arg1=value1,arg2=value2,...'.
|
||||
|
||||
Returns:
|
||||
dict: {'arg1': value1, 'arg2': value2, ...}
|
||||
|
||||
"""
|
||||
kwargs = {}
|
||||
if options is not None:
|
||||
for parameter in options.split(","):
|
||||
if "=" in parameter:
|
||||
k, v = parameter.split("=")
|
||||
kwargs[k] = v
|
||||
else:
|
||||
raise ValueError(f"Cannot parse parameter {parameter}.")
|
||||
return kwargs
|
||||
|
||||
|
||||
def get(circuit_name, nqubits, options=None, qibo=False):
|
||||
if qibo:
|
||||
from benchmarks.circuits import qibo as module
|
||||
else:
|
||||
from benchmarks.circuits import qasm as module
|
||||
|
||||
if circuit_name in ("qft", "QFT"):
|
||||
circuit = module.QFT
|
||||
elif circuit_name == "one-qubit-gate":
|
||||
circuit = module.OneQubitGate
|
||||
elif circuit_name == "two-qubit-gate":
|
||||
circuit = module.TwoQubitGate
|
||||
elif circuit_name in ("variational", "variational-circuit"):
|
||||
circuit = module.VariationalCircuit
|
||||
elif circuit_name in ("bernstein-vazirani", "bv"):
|
||||
circuit = module.BernsteinVazirani
|
||||
elif circuit_name in ("hidden-shift", "hs"):
|
||||
circuit = module.HiddenShift
|
||||
elif circuit_name == "qaoa":
|
||||
circuit = module.QAOA
|
||||
elif circuit_name == "supremacy":
|
||||
circuit = module.SupremacyCircuit
|
||||
elif circuit_name in ("basis-change", "bc"):
|
||||
circuit = module.BasisChange
|
||||
elif circuit_name in ("quantum-volume", "qv"):
|
||||
circuit = module.QuantumVolume
|
||||
else:
|
||||
raise NotImplementedError(f"Cannot find circuit {circuit_name}.")
|
||||
|
||||
kwargs = parse(options)
|
||||
return circuit(nqubits, **kwargs)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
394
qibojit-benchmarks/benchmarks/circuits/qasm.py
Normal file
394
qibojit-benchmarks/benchmarks/circuits/qasm.py
Normal file
@@ -0,0 +1,394 @@
|
||||
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)
|
||||
218
qibojit-benchmarks/benchmarks/circuits/qibo.py
Normal file
218
qibojit-benchmarks/benchmarks/circuits/qibo.py
Normal file
@@ -0,0 +1,218 @@
|
||||
import numpy as np
|
||||
from qibo import gates
|
||||
from benchmarks.circuits import qasm
|
||||
|
||||
|
||||
class OneQubitGate(qasm.OneQubitGate):
|
||||
|
||||
def __init__(self, nqubits, nlayers="1", gate="H", **params):
|
||||
super().__init__(nqubits, nlayers=nlayers, gate=gate)
|
||||
self.gate = getattr(gates, gate)
|
||||
self.angles = {k: float(v) for k, v in params.items()}
|
||||
self.parameters = {"nqubits": nqubits, "nlayers": nlayers,
|
||||
"gate": gate, "params": params}
|
||||
|
||||
def to_qasm(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def base_command(self, i):
|
||||
return self.gate(i, **self.angles)
|
||||
|
||||
|
||||
class TwoQubitGate(OneQubitGate):
|
||||
|
||||
def __init__(self, nqubits, nlayers="1", gate="CNOT", **params):
|
||||
super().__init__(nqubits, nlayers, gate, **params)
|
||||
|
||||
def to_qasm(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def base_command(self, i):
|
||||
return self.gate(i, i + 1, **self.angles)
|
||||
|
||||
def __iter__(self):
|
||||
return qasm.TwoQubitGate.__iter__(self)
|
||||
|
||||
|
||||
class QFT(qasm.QFT):
|
||||
|
||||
def to_qasm(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def __iter__(self):
|
||||
for i1 in range(self.nqubits):
|
||||
yield gates.H(i1)
|
||||
for i2 in range(i1 + 1, self.nqubits):
|
||||
theta = np.pi / 2 ** (i2 - i1)
|
||||
yield gates.CU1(i2, i1, theta)
|
||||
|
||||
if self.swaps:
|
||||
for i in range(self.nqubits // 2):
|
||||
yield gates.SWAP(i, self.nqubits - i - 1)
|
||||
|
||||
|
||||
class VariationalCircuit(qasm.VariationalCircuit):
|
||||
|
||||
def __init__(self, nqubits, nlayers=1, varlayer="False", seed="123"):
|
||||
super().__init__(nqubits, nlayers, seed)
|
||||
self.varlayer = varlayer == "True"
|
||||
self.parameters["varlayer"] = varlayer
|
||||
|
||||
def to_qasm(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def varlayer_circuit(self, theta):
|
||||
theta = theta.reshape((2 * self.nlayers, self.nqubits))
|
||||
pairs = list((i, i + 1) for i in range(0, self.nqubits - 1, 2))
|
||||
for l in range(self.nlayers):
|
||||
yield gates.VariationalLayer(range(self.nqubits), pairs,
|
||||
gates.RY, gates.CZ,
|
||||
theta[2 * l], theta[2 * l + 1])
|
||||
for i in range(1, self.nqubits - 2, 2):
|
||||
yield gates.CZ(i, i + 1)
|
||||
yield gates.CZ(0, self.nqubits - 1)
|
||||
|
||||
def standard_circuit(self, theta):
|
||||
theta = iter(theta)
|
||||
for l in range(self.nlayers):
|
||||
for i in range(self.nqubits):
|
||||
yield gates.RY(i, next(theta))
|
||||
for i in range(0, self.nqubits - 1, 2):
|
||||
yield gates.CZ(i, i + 1)
|
||||
for i in range(self.nqubits):
|
||||
yield gates.RY(i, next(theta))
|
||||
for i in range(1, self.nqubits - 2, 2):
|
||||
yield gates.CZ(i, i + 1)
|
||||
yield gates.CZ(0, self.nqubits - 1)
|
||||
|
||||
def __iter__(self):
|
||||
np.random.seed(self.seed)
|
||||
theta = 2 * np.pi * np.random.random(2 * self.nlayers * self.nqubits)
|
||||
if self.varlayer:
|
||||
return self.varlayer_circuit(theta)
|
||||
else:
|
||||
return self.standard_circuit(theta)
|
||||
|
||||
|
||||
class BernsteinVazirani(qasm.BernsteinVazirani):
|
||||
|
||||
def to_qasm(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def __iter__(self):
|
||||
yield gates.X(self.nqubits - 1)
|
||||
for i in range(self.nqubits):
|
||||
yield gates.H(i)
|
||||
for i in range(self.nqubits - 1):
|
||||
yield gates.CNOT(i, self.nqubits - 1)
|
||||
for i in range(self.nqubits - 1):
|
||||
yield gates.H(i)
|
||||
for i in range(self.nqubits - 1):
|
||||
yield gates.M(i)
|
||||
|
||||
|
||||
class HiddenShift(qasm.HiddenShift):
|
||||
|
||||
def to_qasm(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def oracle(self):
|
||||
for i in range(self.nqubits // 2):
|
||||
yield gates.CZ(2 * i, 2 * i + 1)
|
||||
|
||||
def __iter__(self):
|
||||
for i in range(self.nqubits):
|
||||
yield gates.H(i)
|
||||
for i, ish in enumerate(self.shift):
|
||||
if ish:
|
||||
yield gates.X(i)
|
||||
for gate in self.oracle():
|
||||
yield gate
|
||||
for i, ish in enumerate(self.shift):
|
||||
if ish:
|
||||
yield gates.X(i)
|
||||
for i in range(self.nqubits):
|
||||
yield gates.H(i)
|
||||
for gate in self.oracle():
|
||||
yield gate
|
||||
for i in range(self.nqubits):
|
||||
yield gates.H(i)
|
||||
yield gates.M(*range(self.nqubits))
|
||||
|
||||
|
||||
class QAOA(qasm.QAOA):
|
||||
|
||||
def to_qasm(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def RX(q, theta):
|
||||
return gates.RX(q, theta=theta)
|
||||
|
||||
@staticmethod
|
||||
def RZZ(q0, q1, theta):
|
||||
phase = np.exp(0.5j * theta)
|
||||
phasec = np.conj(phase)
|
||||
matrix = np.diag([phasec, phase, phase, phasec])
|
||||
return gates.Unitary(matrix, q0, q1)
|
||||
|
||||
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 gates.H(i)
|
||||
# Apply QAOA unitary
|
||||
for gate in self.maxcut_unitary(betas, gammas):
|
||||
yield gate
|
||||
# Measure
|
||||
yield gates.M(*range(self.nqubits))
|
||||
|
||||
|
||||
class SupremacyCircuit(qasm.SupremacyCircuit):
|
||||
|
||||
def __init__(self, nqubits, depth="2", seed="123"):
|
||||
super().__init__(nqubits, depth, seed)
|
||||
from qibo import models
|
||||
parent = qasm.SupremacyCircuit(nqubits, depth, seed)
|
||||
self.qibo_circuit = models.Circuit.from_qasm(parent.to_qasm())
|
||||
|
||||
def to_qasm(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def __iter__(self):
|
||||
for gate in self.qibo_circuit.queue:
|
||||
yield gate
|
||||
|
||||
|
||||
class BasisChange(qasm.BasisChange):
|
||||
|
||||
def __init__(self, nqubits, simulation_time="1", seed="123"):
|
||||
super().__init__(nqubits, simulation_time, seed)
|
||||
from qibo import models
|
||||
parent = qasm.BasisChange(nqubits, simulation_time, seed)
|
||||
self.qibo_circuit = models.Circuit.from_qasm(parent.to_qasm())
|
||||
|
||||
def to_qasm(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def __iter__(self):
|
||||
for gate in self.qibo_circuit.queue:
|
||||
yield gate
|
||||
|
||||
|
||||
class QuantumVolume(qasm.QuantumVolume):
|
||||
|
||||
def __init__(self, nqubits, depth="1", seed="123"):
|
||||
super().__init__(nqubits, depth, seed)
|
||||
from qibo import models
|
||||
parent = qasm.QuantumVolume(nqubits, depth, seed)
|
||||
self.qibo_circuit = models.Circuit.from_qasm(parent.to_qasm())
|
||||
|
||||
def to_qasm(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def __iter__(self):
|
||||
for gate in self.qibo_circuit.queue:
|
||||
yield gate
|
||||
74
qibojit-benchmarks/benchmarks/libraries/__init__.py
Normal file
74
qibojit-benchmarks/benchmarks/libraries/__init__.py
Normal file
@@ -0,0 +1,74 @@
|
||||
def parse(options):
|
||||
"""Parse options from string.
|
||||
|
||||
Args:
|
||||
options (str): String with options.
|
||||
It should have the form 'arg1=value1,arg2=value2,...'.
|
||||
|
||||
Returns:
|
||||
dict: {'arg1': value1, 'arg2': value2, ...}
|
||||
|
||||
"""
|
||||
kwargs = {}
|
||||
if options is not None:
|
||||
for parameter in options.split(","):
|
||||
if "=" in parameter:
|
||||
k, v = parameter.split("=")
|
||||
kwargs[k] = v
|
||||
else:
|
||||
raise ValueError(f"Cannot parse parameter {parameter}.")
|
||||
return kwargs
|
||||
|
||||
|
||||
def get(backend_name, options=None):
|
||||
options = parse(options)
|
||||
if backend_name == "qibo":
|
||||
from benchmarks.libraries.qibo import Qibo
|
||||
return Qibo(**options)
|
||||
|
||||
elif backend_name == "qiskit":
|
||||
from benchmarks.libraries.qiskit import Qiskit
|
||||
return Qiskit(**options)
|
||||
elif backend_name == "qiskit-gpu":
|
||||
from benchmarks.libraries.qiskit import QiskitGpu
|
||||
return QiskitGpu(**options)
|
||||
|
||||
elif backend_name == "cirq":
|
||||
from benchmarks.libraries.cirq import Cirq
|
||||
return Cirq()
|
||||
elif backend_name == "qsim":
|
||||
from benchmarks.libraries.cirq import QSim
|
||||
return QSim(**options)
|
||||
elif backend_name == "qsim-gpu":
|
||||
from benchmarks.libraries.cirq import QSimGpu
|
||||
return QSimGpu(**options)
|
||||
elif backend_name == "qsim-cuquantum":
|
||||
from benchmarks.libraries.cirq import QSimCuQuantum
|
||||
return QSimCuQuantum(**options)
|
||||
elif backend_name == "tfq":
|
||||
from benchmarks.libraries.cirq import TensorflowQuantum
|
||||
return TensorflowQuantum()
|
||||
|
||||
elif backend_name == "qulacs":
|
||||
from benchmarks.libraries.qulacs import Qulacs
|
||||
return Qulacs()
|
||||
elif backend_name == "qulacs-gpu":
|
||||
from benchmarks.libraries.qulacs import QulacsGpu
|
||||
return QulacsGpu()
|
||||
|
||||
elif backend_name == "qcgpu":
|
||||
from benchmarks.libraries.qcgpu import QCGPU
|
||||
return QCGPU()
|
||||
|
||||
elif backend_name == "projectq":
|
||||
from benchmarks.libraries.projectq import ProjectQ
|
||||
return ProjectQ(**options)
|
||||
|
||||
elif backend_name == "hybridq":
|
||||
from benchmarks.libraries.hybridq import HybridQ
|
||||
return HybridQ(**options)
|
||||
elif backend_name == "hybridq-gpu":
|
||||
from benchmarks.libraries.hybridq import HybridQGPU
|
||||
return HybridQGPU(**options)
|
||||
|
||||
raise KeyError(f"Unknown simulation library {backend_name}.")
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
173
qibojit-benchmarks/benchmarks/libraries/abstract.py
Normal file
173
qibojit-benchmarks/benchmarks/libraries/abstract.py
Normal file
@@ -0,0 +1,173 @@
|
||||
from abc import ABC, abstractmethod
|
||||
import numpy as np
|
||||
|
||||
|
||||
class AbstractBackend(ABC):
|
||||
|
||||
def __init__(self):
|
||||
self.name = None
|
||||
self.__version__ = None
|
||||
|
||||
@abstractmethod
|
||||
def from_qasm(self, qasm):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def __call__(self, circuit):
|
||||
raise NotImplementedError
|
||||
|
||||
def transpose_state(self, x):
|
||||
"""Switch order of qubits in state vector to be compatible to Qibo."""
|
||||
shape = tuple(x.shape)
|
||||
nqubits = int(np.log2(shape[0]))
|
||||
x = np.reshape(x, nqubits * (2,))
|
||||
x = np.transpose(x, range(nqubits - 1, -1, -1))
|
||||
return np.reshape(x, shape)
|
||||
|
||||
@abstractmethod
|
||||
def get_precision(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def set_precision(self, precision):
|
||||
raise NotImplementedError(f"Cannot set precision for {self.name} backend.")
|
||||
|
||||
@abstractmethod
|
||||
def get_device(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ParserBackend(AbstractBackend):
|
||||
|
||||
QASM_GATES = {"h": "H", "x": "X", "y": "Y", "z": "Z",
|
||||
"rx": "RX", "ry": "RY", "rz": "RZ",
|
||||
"u1": "U1", "u2": "U2", "u3": "U3",
|
||||
"cx": "CNOT", "swap": "SWAP", "cz": "CZ",
|
||||
"crx": "CRX", "cry": "CRY", "crz": "CRZ",
|
||||
"cu1": "CU1", "cu3": "CU3", "rzz": "RZZ",
|
||||
"ccx": "TOFFOLI", "id": "I"}
|
||||
PARAMETRIZED_GATES = {"rx", "ry", "rz", "u1", "u2", "u3",
|
||||
"crx", "cry", "crz", "cu1", "cu3", "rzz"}
|
||||
|
||||
def parse(self, qasm_code):
|
||||
"""Extracts circuit information from QASM script.
|
||||
|
||||
Args:
|
||||
qasm_code: String with the QASM code to parse.
|
||||
|
||||
Returns:
|
||||
nqubits: The total number of qubits in the circuit.
|
||||
gate_list: List that specifies the gates of the circuit.
|
||||
Contains tuples of the form
|
||||
(Qibo gate name, qubit IDs, optional additional parameter).
|
||||
The additional parameter is the ``register_name`` for
|
||||
measurement gates or ``theta`` for parametrized gates.
|
||||
"""
|
||||
import re
|
||||
def read_args(args):
|
||||
_args = iter(re.split(r"[\[\],]", args))
|
||||
for name in _args:
|
||||
if name:
|
||||
index = next(_args)
|
||||
if not index.isdigit():
|
||||
raise ValueError("Invalid QASM qubit arguments: {}".format(args))
|
||||
yield name, int(index)
|
||||
|
||||
# Remove comment lines
|
||||
lines = "".join(line for line in qasm_code.split("\n")
|
||||
if line and line[:2] != "//")
|
||||
lines = (line for line in lines.split(";") if line)
|
||||
|
||||
if next(lines) != "OPENQASM 2.0":
|
||||
raise ValueError("QASM code should start with 'OPENQASM 2.0'.")
|
||||
|
||||
qubits = {} # Dict[Tuple[str, int], int]: map from qubit tuple to qubit id
|
||||
cregs_size = {} # Dict[str, int]: map from `creg` name to its size
|
||||
registers = {} # Dict[str, List[int]]: map from register names to target qubit ids
|
||||
gate_list = [] # List[Tuple[str, List[int]]]: List of (gate name, list of target qubit ids)
|
||||
for line in lines:
|
||||
command, args = line.split(None, 1)
|
||||
# remove spaces
|
||||
command = command.replace(" ", "")
|
||||
args = args.replace(" ", "")
|
||||
|
||||
if command == "include":
|
||||
pass
|
||||
|
||||
elif command == "qreg":
|
||||
for name, nqubits in read_args(args):
|
||||
for i in range(nqubits):
|
||||
qubits[(name, i)] = len(qubits)
|
||||
|
||||
elif command == "creg":
|
||||
for name, nqubits in read_args(args):
|
||||
cregs_size[name] = nqubits
|
||||
|
||||
elif command == "measure":
|
||||
args = args.split("->")
|
||||
if len(args) != 2:
|
||||
raise ValueError("Invalid QASM measurement: {}".format(line))
|
||||
qubit = next(read_args(args[0]))
|
||||
if qubit not in qubits:
|
||||
raise ValueError("Qubit {} is not defined in QASM code."
|
||||
"".format(qubit))
|
||||
|
||||
register, idx = next(read_args(args[1]))
|
||||
if register not in cregs_size:
|
||||
raise ValueError("Classical register name {} is not defined "
|
||||
"in QASM code.".format(register))
|
||||
if idx >= cregs_size[register]:
|
||||
raise ValueError("Cannot access index {} of register {} "
|
||||
"with {} qubits."
|
||||
"".format(idx, register, cregs_size[register]))
|
||||
if register in registers:
|
||||
if idx in registers[register]:
|
||||
raise KeyError("Key {} of register {} has already "
|
||||
"been used.".format(idx, register))
|
||||
registers[register][idx] = qubits[qubit]
|
||||
else:
|
||||
registers[register] = {idx: qubits[qubit]}
|
||||
gate_list.append(("M", register))
|
||||
|
||||
else:
|
||||
pieces = [x for x in re.split("[()]", command) if x]
|
||||
if len(pieces) == 1:
|
||||
gatename, params = pieces[0], None
|
||||
if gatename not in self.QASM_GATES:
|
||||
raise ValueError("QASM command {} is not recognized."
|
||||
"".format(command))
|
||||
if gatename in self.PARAMETRIZED_GATES:
|
||||
raise ValueError("Missing parameters for QASM "
|
||||
"gate {}.".format(gatename))
|
||||
|
||||
elif len(pieces) == 2:
|
||||
gatename, params = pieces
|
||||
if gatename not in self.PARAMETRIZED_GATES:
|
||||
raise ValueError("Invalid QASM command {}."
|
||||
"".format(command))
|
||||
params = params.replace(" ", "").split(",")
|
||||
try:
|
||||
for i, p in enumerate(params):
|
||||
if 'pi' in p:
|
||||
import math
|
||||
from operator import mul
|
||||
from functools import reduce
|
||||
s = p.replace('pi', str(math.pi)).split('*')
|
||||
p = reduce(mul, [float(j) for j in s], 1)
|
||||
params[i] = float(p)
|
||||
except ValueError:
|
||||
raise ValueError("Invalid value {} for gate parameters."
|
||||
"".format(params))
|
||||
|
||||
else:
|
||||
raise ValueError("QASM command {} is not recognized."
|
||||
"".format(command))
|
||||
|
||||
# Add gate to gate list
|
||||
qubit_list = []
|
||||
for qubit in read_args(args):
|
||||
if qubit not in qubits:
|
||||
raise ValueError("Qubit {} is not defined in QASM "
|
||||
"code.".format(qubit))
|
||||
qubit_list.append(qubits[qubit])
|
||||
gate_list.append((self.QASM_GATES[gatename], list(qubit_list), params))
|
||||
return len(qubits), gate_list
|
||||
167
qibojit-benchmarks/benchmarks/libraries/cirq.py
Normal file
167
qibojit-benchmarks/benchmarks/libraries/cirq.py
Normal file
@@ -0,0 +1,167 @@
|
||||
import numpy as np
|
||||
from benchmarks.libraries import abstract
|
||||
|
||||
|
||||
class Cirq(abstract.ParserBackend):
|
||||
|
||||
def __init__(self):
|
||||
import cirq
|
||||
self.name = "cirq"
|
||||
self.__version__ = cirq.__version__
|
||||
self.cirq = cirq
|
||||
self.precision = "double"
|
||||
self.simulator = cirq.Simulator(dtype=np.complex128)
|
||||
|
||||
def RX(self, theta):
|
||||
return self.cirq.rx(theta)
|
||||
|
||||
def RY(self, theta):
|
||||
return self.cirq.ry(theta)
|
||||
|
||||
def RZ(self, theta):
|
||||
return self.cirq.rz(theta)
|
||||
|
||||
def CU1(self, theta):
|
||||
return self.cirq.CZPowGate(exponent=theta / np.pi)
|
||||
|
||||
def CU3(self, theta, phi, lam):
|
||||
gate = self.cirq.circuits.qasm_output.QasmUGate(theta / np.pi, phi / np.pi, lam / np.pi)
|
||||
return gate.controlled(num_controls=1)
|
||||
|
||||
def RZZ(self, theta):
|
||||
import numpy as np
|
||||
return self.cirq.ZZPowGate(exponent=theta / np.pi, global_shift=-0.5)
|
||||
|
||||
def __getattr__(self, x):
|
||||
return getattr(self.cirq, x)
|
||||
|
||||
def __getitem__(self, x):
|
||||
return getattr(self.cirq, x)
|
||||
|
||||
def from_qasm(self, qasm):
|
||||
from cirq.contrib.qasm_import import circuit_from_qasm, exception
|
||||
try:
|
||||
return circuit_from_qasm(qasm)
|
||||
except exception.QasmException:
|
||||
nqubits, gatelist = self.parse(qasm)
|
||||
qubits = [self.cirq.GridQubit(i, 0) for i in range(nqubits)]
|
||||
circuit = self.cirq.Circuit()
|
||||
for gatename, qid, params in gatelist:
|
||||
if params is not None:
|
||||
gate = getattr(self, gatename)(*params)
|
||||
else:
|
||||
gate = getattr(self, gatename)
|
||||
circuit.append(gate(*(qubits[i] for i in qid)))
|
||||
return circuit
|
||||
|
||||
def __call__(self, circuit):
|
||||
result = self.simulator.simulate(circuit)
|
||||
return result.final_state_vector
|
||||
|
||||
def transpose_state(self, x):
|
||||
return x
|
||||
|
||||
def get_precision(self):
|
||||
return self.precision
|
||||
|
||||
def set_precision(self, precision):
|
||||
import numpy as np
|
||||
self.precision = precision
|
||||
if precision == "single":
|
||||
self.simulator = self.cirq.Simulator(dtype=np.complex64)
|
||||
else:
|
||||
self.simulator = self.cirq.Simulator(dtype=np.complex128)
|
||||
|
||||
def get_device(self):
|
||||
return None
|
||||
|
||||
|
||||
class TensorflowQuantum(Cirq):
|
||||
|
||||
def __init__(self):
|
||||
import cirq
|
||||
import tensorflow_quantum as tfq
|
||||
self.name = "tfq"
|
||||
self.cirq = cirq
|
||||
self.precision = "single"
|
||||
self.__version__ = tfq.__version__
|
||||
self.state_layer = tfq.layers.State()
|
||||
|
||||
def set_precision(self, precision):
|
||||
if precision == "double":
|
||||
raise NotImplementedError(f"Cannot set precision '{precision}' for {self.name} backend.")
|
||||
|
||||
def from_qasm(self, qasm):
|
||||
circuit = super().from_qasm(qasm)
|
||||
# change `NamedQubit`s to `GridQubit`s as TFQ understands only `GridQubit`
|
||||
qubit_map = {}
|
||||
for q in circuit.all_qubits():
|
||||
if isinstance(q, self.cirq.NamedQubit):
|
||||
i = int(str(q).split("_")[-1])
|
||||
qubit_map[q] = self.cirq.GridQubit(i, 0)
|
||||
if qubit_map:
|
||||
return circuit.transform_qubits(qubit_map)
|
||||
return circuit
|
||||
|
||||
def __call__(self, circuit):
|
||||
# transfer final state to numpy array because that's what happens
|
||||
# for all backends
|
||||
return self.state_layer(circuit)[0].numpy()
|
||||
|
||||
|
||||
class QSim(Cirq):
|
||||
|
||||
def __init__(self, max_qubits="0", nthreads=None):
|
||||
import cirq
|
||||
import qsimcirq
|
||||
self.name = "qsim"
|
||||
self.cirq = cirq
|
||||
self.qsimcirq = qsimcirq
|
||||
self.precision = "single"
|
||||
self.__version__ = qsimcirq.__version__
|
||||
|
||||
if nthreads is None:
|
||||
from multiprocessing import cpu_count
|
||||
self.nthreads = cpu_count()
|
||||
else:
|
||||
self.nthreads = int(nthreads)
|
||||
self.max_qubits = int(max_qubits)
|
||||
|
||||
self.simulator = self.get_simulator()
|
||||
|
||||
def get_simulator(self):
|
||||
return self.qsimcirq.QSimSimulator({'t': self.nthreads, 'f': self.max_qubits})
|
||||
|
||||
def set_precision(self, precision):
|
||||
if precision == "double":
|
||||
raise NotImplementedError(f"Cannot set precision '{precision}' for {self.name} backend.")
|
||||
|
||||
|
||||
class QSimGpu(QSim):
|
||||
|
||||
def __init__(self, max_qubits="0"):
|
||||
super().__init__(max_qubits)
|
||||
self.name = "qsim-gpu"
|
||||
|
||||
def get_simulator(self):
|
||||
qsim_options = self.qsimcirq.QSimOptions(
|
||||
use_gpu=True,
|
||||
gpu_mode=0,
|
||||
max_fused_gate_size=self.max_qubits
|
||||
)
|
||||
return self.qsimcirq.QSimSimulator(qsim_options)
|
||||
|
||||
|
||||
class QSimCuQuantum(QSim):
|
||||
|
||||
def __init__(self, max_qubits="0"):
|
||||
super().__init__(max_qubits)
|
||||
self.name = "qsim-cuquantum"
|
||||
|
||||
def get_simulator(self):
|
||||
qsim_options = self.qsimcirq.QSimOptions(
|
||||
use_gpu=True,
|
||||
gpu_mode=1,
|
||||
max_fused_gate_size=self.max_qubits
|
||||
)
|
||||
return self.qsimcirq.QSimSimulator(qsim_options)
|
||||
142
qibojit-benchmarks/benchmarks/libraries/hybridq.py
Normal file
142
qibojit-benchmarks/benchmarks/libraries/hybridq.py
Normal file
@@ -0,0 +1,142 @@
|
||||
import os
|
||||
import numpy as np
|
||||
from benchmarks.libraries import abstract
|
||||
|
||||
|
||||
class HybridQ(abstract.ParserBackend):
|
||||
|
||||
def __init__(self, max_qubits="0", simplify="False"):
|
||||
from hybridq.gate import Gate, MatrixGate
|
||||
self.name = "hybridq"
|
||||
self.__version__ = "0.7.7.post2"
|
||||
self.Gate = Gate
|
||||
self.MatrixGate = MatrixGate
|
||||
self.max_qubits = int(max_qubits)
|
||||
if simplify in ("true", "True"):
|
||||
self.simplify = True
|
||||
else:
|
||||
self.simplify = False
|
||||
self.complex_type = "complex128"
|
||||
self.max_qubits = int(max_qubits)
|
||||
|
||||
def H(self, q):
|
||||
return self.Gate('H', qubits=(q,))
|
||||
|
||||
def X(self, q):
|
||||
return self.Gate('X', qubits=(q,))
|
||||
|
||||
def Y(self, q):
|
||||
return self.Gate('Y', qubits=(q,))
|
||||
|
||||
def Z(self, q):
|
||||
return self.Gate('Z', qubits=(q,))
|
||||
|
||||
def RX(self, q, theta):
|
||||
return self.Gate('RX', params=[theta], qubits=(q,))
|
||||
|
||||
def RY(self, q, theta):
|
||||
return self.Gate('RY', params=[theta], qubits=(q,))
|
||||
|
||||
def RZ(self, q, theta):
|
||||
return self.Gate('RZ', params=[theta], qubits=(q,))
|
||||
|
||||
def U1(self, q, theta):
|
||||
phase = np.exp(1j * theta)
|
||||
matrix = np.diag([1, phase])
|
||||
return self.MatrixGate(U=matrix, qubits=(q,))
|
||||
|
||||
def U2(self, q, phi, lam):
|
||||
plus = np.exp(0.5j * (phi + lam))
|
||||
minus = np.exp(0.5j * (phi - lam))
|
||||
matrix = np.array([[np.conj(plus), -np.conj(minus)], [minus, plus]]) / np.sqrt(2)
|
||||
return self.MatrixGate(U=matrix, qubits=(q,))
|
||||
|
||||
def U3(self, q, theta, phi, lam):
|
||||
return self.Gate('U3', params=[theta, phi, lam], qubits=(q,))
|
||||
|
||||
def CNOT(self, q1, q2):
|
||||
return self.Gate('CNOT', qubits=(q1, q2))
|
||||
|
||||
def SWAP(self, q1, q2):
|
||||
return self.Gate('SWAP', qubits=(q1, q2))
|
||||
|
||||
def CZ(self, q1, q2):
|
||||
return self.Gate('CZ', qubits=(q1, q2))
|
||||
|
||||
def CU1(self, q1, q2, theta):
|
||||
return self.Gate('CPHASE', params=[theta], qubits=(q1, q2))
|
||||
|
||||
def CU3(self, q1, q2, theta, phi, lam):
|
||||
from hybridq.gate import Control
|
||||
cost, sint = np.cos(theta / 2.0), np.sin(theta / 2.0)
|
||||
pplus, pminus = np.exp(0.5j * (phi + lam)), np.exp(0.5j * (phi - lam))
|
||||
matrix = np.array([[np.conj(pplus) * cost, -np.conj(pminus) * sint],
|
||||
[pminus * sint, pplus * cost]])
|
||||
gate = self.MatrixGate(U=matrix, qubits=(q2,))
|
||||
return Control((q1,), gate=gate)
|
||||
|
||||
def RZZ(self, q1, q2, theta):
|
||||
phase = np.exp(0.5j * theta)
|
||||
phasec = np.conj(phase)
|
||||
matrix = np.diag([phasec, phase, phase, phasec])
|
||||
return self.MatrixGate(U=matrix, qubits=(q1, q2))
|
||||
|
||||
def from_qasm(self, qasm):
|
||||
from hybridq.circuit import Circuit
|
||||
nqubits, gatelist = self.parse(qasm)
|
||||
circuit = Circuit()
|
||||
for gatename, qubits, params in gatelist:
|
||||
args = list(qubits)
|
||||
if params:
|
||||
args.extend(params)
|
||||
gate = getattr(self, gatename)(*args)
|
||||
circuit.append(gate)
|
||||
return circuit
|
||||
|
||||
def __call__(self, circuit):
|
||||
from hybridq.circuit.simulation import simulate
|
||||
initial_state = len(circuit.all_qubits()) * '0'
|
||||
final_state = simulate(circuit, optimize="evolution",
|
||||
initial_state=initial_state,
|
||||
complex_type=self.complex_type,
|
||||
simplify=self.simplify,
|
||||
compress=self.max_qubits,
|
||||
max_largest_intermediate=2**40)
|
||||
return final_state.ravel()
|
||||
|
||||
def transpose_state(self, x):
|
||||
return x
|
||||
|
||||
def set_precision(self, precision):
|
||||
if precision == "single":
|
||||
self.complex_type = "complex64"
|
||||
else:
|
||||
self.complex_type = "complex128"
|
||||
|
||||
def get_precision(self):
|
||||
if self.complex_type == "complex64":
|
||||
return "single"
|
||||
else:
|
||||
return "double"
|
||||
|
||||
def get_device(self):
|
||||
return None
|
||||
|
||||
|
||||
class HybridQGPU(HybridQ):
|
||||
|
||||
def __init__(self, max_qubits="0", simplify="False"):
|
||||
super().__init__(max_qubits=max_qubits, simplify=simplify)
|
||||
self.name = "hybridq-gpu"
|
||||
|
||||
def __call__(self, circuit):
|
||||
from hybridq.circuit.simulation import simulate
|
||||
initial_state = len(circuit.all_qubits()) * '0'
|
||||
final_state = simulate(circuit, optimize="evolution-einsum",
|
||||
backend="jax",
|
||||
initial_state=initial_state,
|
||||
complex_type=self.complex_type,
|
||||
simplify=self.simplify,
|
||||
compress=self.max_qubits,
|
||||
max_largest_intermediate=2**40)
|
||||
return final_state.ravel()
|
||||
129
qibojit-benchmarks/benchmarks/libraries/projectq.py
Normal file
129
qibojit-benchmarks/benchmarks/libraries/projectq.py
Normal file
@@ -0,0 +1,129 @@
|
||||
import numpy as np
|
||||
from benchmarks.libraries import abstract
|
||||
|
||||
class ProjectQ(abstract.ParserBackend):
|
||||
|
||||
def __init__(self, max_qubits="0", local_optimizer="0"):
|
||||
"""Initialize data members.
|
||||
|
||||
Args:
|
||||
max_qubits (str): if "0", gate fusion is disabled, otherwise it's enabled.
|
||||
Note that it's not possible to set the maximum fused gate size.
|
||||
local_optimizer (str): if "0", local optimization of circuits is disabled,
|
||||
otherwise it's enabled.
|
||||
"""
|
||||
import projectq
|
||||
self.name = "projectq"
|
||||
self.projectq = projectq
|
||||
self.__version__ = None
|
||||
self.gate_fusion = int(max_qubits) > 0
|
||||
self.local_optimizer = bool(int(local_optimizer))
|
||||
|
||||
def RX(self, theta):
|
||||
return self.projectq.ops.Rx(theta)
|
||||
|
||||
def RY(self, theta):
|
||||
return self.projectq.ops.Ry(theta)
|
||||
|
||||
def RZ(self, theta):
|
||||
return self.projectq.ops.Rz(theta)
|
||||
|
||||
def U1(self, theta):
|
||||
return self.projectq.ops.R(theta)
|
||||
|
||||
def U2(self, phi, lam):
|
||||
pplus, pminus = np.exp(0.5j * (phi + lam)), np.exp(0.5j * (phi - lam))
|
||||
matrix = np.array([[np.conj(pplus), -np.conj(pminus)],
|
||||
[pminus, pplus]])
|
||||
matrix /= np.sqrt(2)
|
||||
return self.projectq.ops.MatrixGate(matrix)
|
||||
|
||||
def U3(self, theta, phi, lam):
|
||||
cost, sint = np.cos(theta / 2.0), np.sin(theta / 2.0)
|
||||
pplus, pminus = np.exp(0.5j * (phi + lam)), np.exp(0.5j * (phi - lam))
|
||||
matrix = np.array([[np.conj(pplus) * cost, -np.conj(pminus) * sint],
|
||||
[pminus * sint, pplus * cost]])
|
||||
return self.projectq.ops.MatrixGate(matrix)
|
||||
|
||||
|
||||
def SWAP(self):
|
||||
return self.projectq.ops.Swap
|
||||
|
||||
def CRX(self, theta):
|
||||
return self.projectq.ops.C(self.RX(theta))
|
||||
|
||||
def CRY(self, theta):
|
||||
return self.projectq.ops.C(self.RY(theta))
|
||||
|
||||
def CRZ(self, theta):
|
||||
return self.projectq.ops.CRz(theta)
|
||||
|
||||
def CU1(self, theta):
|
||||
U1 = self.projectq.ops.R(theta)
|
||||
return self.projectq.ops.C(U1, n_qubits=1)
|
||||
|
||||
def CU3(self, theta):
|
||||
raise NotImplementedError
|
||||
|
||||
def RZZ(self, theta):
|
||||
return self.projectq.ops.Rzz(theta)
|
||||
|
||||
def __getattr__(self, x):
|
||||
return getattr(self.projectq.ops, x)
|
||||
|
||||
def __item__(self, x):
|
||||
return getattr(self.projectq.ops, x)
|
||||
|
||||
def from_qasm(self, qasm):
|
||||
nqubits, gatelist = self.parse(qasm)
|
||||
backend = self.projectq.backends.Simulator(gate_fusion=self.gate_fusion)
|
||||
if self.local_optimizer:
|
||||
self.eng = self.projectq.MainEngine(
|
||||
backend=backend, engine_list=[self.projectq.cengines.LocalOptimizer()]
|
||||
)
|
||||
else:
|
||||
self.eng = self.projectq.MainEngine(backend=backend)
|
||||
qureg = self.eng.allocate_qureg(nqubits)
|
||||
for gatename, qubits, params in gatelist:
|
||||
gate = getattr(self, gatename)
|
||||
if params is not None:
|
||||
parameters = list(params)
|
||||
if len(qubits) > 1:
|
||||
gate(*parameters) | tuple(qureg[i] for i in qubits)
|
||||
else:
|
||||
gate(*parameters) | qureg[qubits[0]]
|
||||
elif len(qubits) > 1:
|
||||
if gatename == "SWAP":
|
||||
gate() | tuple(qureg[i] for i in qubits)
|
||||
else:
|
||||
gate | tuple(qureg[i] for i in qubits)
|
||||
else:
|
||||
gate | qureg[qubits[0]]
|
||||
|
||||
return qureg
|
||||
|
||||
def __call__(self, qureg):
|
||||
self.eng.flush()
|
||||
self.qubit_id, wave = self.eng.backend.cheat()
|
||||
# measure everything to avoid error when running
|
||||
self.projectq.ops.All(self.projectq.ops.Measure) | qureg
|
||||
return np.array(wave)
|
||||
|
||||
def transpose_state(self, x):
|
||||
shape = tuple(x.shape)
|
||||
nqubits = int(np.log2(shape[0]))
|
||||
x = np.reshape(x, nqubits * (2,))
|
||||
x = np.transpose(x, range(nqubits - 1, -1, -1))
|
||||
x = np.transpose(x, tuple(self.qubit_id[key] for key in self.qubit_id))
|
||||
x = np.reshape(x, shape)
|
||||
return x
|
||||
|
||||
def set_precision(self, precision):
|
||||
if precision != "double":
|
||||
raise NotImplementedError(f"Cannot set {precision} precision for {self.name} backend.")
|
||||
|
||||
def get_precision(self):
|
||||
return "double"
|
||||
|
||||
def get_device(self):
|
||||
return None
|
||||
85
qibojit-benchmarks/benchmarks/libraries/qcgpu.py
Normal file
85
qibojit-benchmarks/benchmarks/libraries/qcgpu.py
Normal file
@@ -0,0 +1,85 @@
|
||||
import numpy as np
|
||||
from benchmarks.libraries import abstract
|
||||
|
||||
|
||||
class QCGPU(abstract.ParserBackend):
|
||||
|
||||
def __init__(self):
|
||||
import os
|
||||
os.environ["PYOPENCL_CTX"] = "0"
|
||||
import qcgpu
|
||||
self.name = "qcgpu"
|
||||
self.qcgpu = qcgpu
|
||||
self.__version__ = None
|
||||
|
||||
def RX(self, target, theta):
|
||||
cost, sint = np.cos(theta / 2.0), np.sin(theta / 2.0)
|
||||
matrix = np.array([[cost, -1j * sint], [-1j * sint, cost]])
|
||||
gate = self.qcgpu.Gate(matrix)
|
||||
return ("apply_gate", (gate, target))
|
||||
|
||||
def RY(self, target, theta):
|
||||
cost, sint = np.cos(theta / 2.0), np.sin(theta / 2.0)
|
||||
matrix = np.array([[cost, -sint], [sint, cost]])
|
||||
gate = self.qcgpu.Gate(matrix)
|
||||
return ("apply_gate", (gate, target))
|
||||
|
||||
def RZ(self, target, theta):
|
||||
phase = np.exp(0.5j * theta)
|
||||
matrix = np.diag([np.conj(phase), phase])
|
||||
gate = self.qcgpu.Gate(matrix)
|
||||
return ("apply_gate", (gate, target))
|
||||
|
||||
def U1(self, target, theta):
|
||||
phase = np.exp(1j * theta)
|
||||
matrix = np.diag([1, phase])
|
||||
gate = self.qcgpu.Gate(matrix)
|
||||
return ("apply_gate", (gate, target))
|
||||
|
||||
def CU1(self, control, target, theta):
|
||||
phase = np.exp(1j * theta)
|
||||
matrix = np.diag([1, phase])
|
||||
gate = self.qcgpu.Gate(matrix)
|
||||
return ("apply_controlled_gate", (gate, control, target))
|
||||
|
||||
def RZZ(self, target1, target2, theta):
|
||||
raise NotImplementedError
|
||||
|
||||
class QCGPUCircuit(list):
|
||||
|
||||
def __init__(self, nqubits):
|
||||
self.nqubits = nqubits
|
||||
|
||||
def from_qasm(self, qasm):
|
||||
nqubits, gatelist = self.parse(qasm)
|
||||
circuit = self.QCGPUCircuit(nqubits)
|
||||
for gate, qubits, params in gatelist:
|
||||
args = list(qubits)
|
||||
if params is not None:
|
||||
args.extend(params)
|
||||
if gate == "SWAP":
|
||||
target1, target2 = qubits
|
||||
circuit.append(("cx", (target1, target2)))
|
||||
circuit.append(("cx", (target2, target1)))
|
||||
circuit.append(("cx", (target1, target2)))
|
||||
elif gate in {"RX", "RY", "RZ", "U1", "CU1"}:
|
||||
circuit.append(getattr(self, gate)(*args))
|
||||
else:
|
||||
circuit.append((gate.lower(), args))
|
||||
return circuit
|
||||
|
||||
def __call__(self, circuit):
|
||||
state = self.qcgpu.State(circuit.nqubits)
|
||||
for gate, args in circuit:
|
||||
getattr(state, gate)(*args)
|
||||
return state.amplitudes()
|
||||
|
||||
def set_precision(self, precision):
|
||||
if precision != "single":
|
||||
raise NotImplementedError(f"Cannot set {precision} precision for {self.name} backend.")
|
||||
|
||||
def get_precision(self):
|
||||
return "single"
|
||||
|
||||
def get_device(self):
|
||||
return None
|
||||
295
qibojit-benchmarks/benchmarks/libraries/qibo.py
Normal file
295
qibojit-benchmarks/benchmarks/libraries/qibo.py
Normal file
@@ -0,0 +1,295 @@
|
||||
from benchmarks.libraries import abstract
|
||||
from benchmarks.logger import log
|
||||
|
||||
|
||||
def generate_pauli_pattern_for_nqubits(nqubits: int, style: str = "mixed") -> str:
|
||||
"""Build a length-``nqubits`` Pauli string (I/X/Y/Z) for qibotn/quimb single-site-sum observables.
|
||||
|
||||
Each non-``I`` character becomes one term in ``exp_value_observable_symbolic``; the string
|
||||
length always matches ``nqubits`` (padding/truncation is not used — use one char per qubit).
|
||||
|
||||
Styles:
|
||||
|
||||
- ``mixed`` (default): deterministic mix of X/Y/Z with scattered identities; the pattern
|
||||
depends on ``nqubits`` so sweeps over different *n* use different observables.
|
||||
- ``dense``: repeating XYZ on every qubit (no identities).
|
||||
- ``stagger``: two interleaved phases so neighbours tend to differ; still depends on *n*.
|
||||
"""
|
||||
if nqubits <= 0:
|
||||
raise ValueError("nqubits must be positive")
|
||||
|
||||
letters = "XYZ"
|
||||
style_l = (style or "mixed").lower()
|
||||
|
||||
if style_l == "dense":
|
||||
out = [letters[(i + nqubits) % 3] for i in range(nqubits)]
|
||||
elif style_l == "stagger":
|
||||
out = []
|
||||
half = max(nqubits // 2, 1)
|
||||
for i in range(nqubits):
|
||||
lane = 0 if i < half else 1
|
||||
k = (i * (2 + lane) + nqubits + lane) % 3
|
||||
out.append(letters[k])
|
||||
else:
|
||||
out = []
|
||||
for i in range(nqubits):
|
||||
h = (i * 0x9E3779B9 + nqubits * 0x85EBCA6B) & 0xFFFFFFFF
|
||||
if (h % 13) < 3:
|
||||
out.append("I")
|
||||
else:
|
||||
rot = (i ^ (nqubits >> 1)) + ((h >> 8) % 3)
|
||||
out.append(letters[rot % 3])
|
||||
if all(c == "I" for c in out):
|
||||
out[-1] = "X"
|
||||
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def runcard_uses_auto_pauli_pattern(runcard) -> bool:
|
||||
"""True when expectations will use :func:`generate_pauli_pattern_for_nqubits` per circuit size."""
|
||||
if not runcard:
|
||||
return False
|
||||
raw = runcard.get("pauli_pattern")
|
||||
auto = runcard.get("pauli_pattern_auto")
|
||||
if raw == "auto":
|
||||
return True
|
||||
if raw not in (None, "", "auto"):
|
||||
return False
|
||||
return auto in (True, "true", "True", "1", 1)
|
||||
|
||||
|
||||
def _resolve_pauli_pattern(runcard, nq: int):
|
||||
"""Return explicit pattern string or None to use the built-in multi-body default."""
|
||||
if not runcard:
|
||||
return None
|
||||
raw = runcard.get("pauli_pattern")
|
||||
auto = runcard.get("pauli_pattern_auto")
|
||||
style = runcard.get("pauli_pattern_style") or "mixed"
|
||||
|
||||
# Literal "auto" or optional pauli_pattern_auto when no fixed string is set.
|
||||
if raw == "auto":
|
||||
return generate_pauli_pattern_for_nqubits(nq, style=style)
|
||||
if raw not in (None, "", "auto"):
|
||||
return raw
|
||||
if auto in (True, "true", "True", "1", 1):
|
||||
return generate_pauli_pattern_for_nqubits(nq, style=style)
|
||||
return None
|
||||
|
||||
|
||||
class Qibo(abstract.AbstractBackend):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
max_qubits="0",
|
||||
backend="qibojit",
|
||||
platform=None,
|
||||
accelerators="",
|
||||
expectation=None,
|
||||
computation_settings=None,
|
||||
):
|
||||
import qibo
|
||||
|
||||
runcard = None
|
||||
|
||||
if computation_settings is not None:
|
||||
import json
|
||||
|
||||
try:
|
||||
with open(computation_settings, "r") as f:
|
||||
runcard = json.load(f)
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"Invalid JSON in file '{computation_settings}': {e}")
|
||||
|
||||
except FileNotFoundError:
|
||||
raise FileNotFoundError(f"File not found: {computation_settings}")
|
||||
|
||||
if runcard["expectation_enabled"] == True:
|
||||
expectation = True
|
||||
|
||||
qibo.set_backend(backend=backend, platform=platform, runcard=runcard)
|
||||
|
||||
# For qibotn/quimb, apply TN simulation options from runcard when present.
|
||||
if backend == "qibotn" and platform == "quimb":
|
||||
quimb_backend = qibo.get_backend()
|
||||
use_mps = runcard.get("use_mps", runcard.get("MPS_enabled", True))
|
||||
max_bond_dimension = runcard.get(
|
||||
"max_bond_dimension", runcard.get("max_bond", None)
|
||||
)
|
||||
svd_cutoff = runcard.get("svd_cutoff", 1e-10)
|
||||
mpi_enabled = runcard.get("MPI_enabled", False)
|
||||
quimb_backend.configure_tn_simulation(
|
||||
ansatz="mps" if use_mps else None,
|
||||
max_bond_dimension=max_bond_dimension if use_mps else None,
|
||||
svd_cutoff=svd_cutoff,
|
||||
MPI_enabled=mpi_enabled,
|
||||
)
|
||||
else:
|
||||
qibo.set_backend(backend=backend, platform=platform)
|
||||
|
||||
from qibo import models
|
||||
|
||||
self.name = "qibo"
|
||||
self.qibo = qibo
|
||||
self.models = models
|
||||
self.__version__ = qibo.__version__
|
||||
self.max_qubits = int(max_qubits)
|
||||
self.accelerators = self._parse_accelerators(accelerators)
|
||||
self.expectation_flag = expectation
|
||||
self.backend_name_str = backend
|
||||
self.platform_str = platform
|
||||
self.runcard = runcard
|
||||
|
||||
def from_qasm(self, qasm):
|
||||
circuit = self.models.Circuit.from_qasm(qasm, accelerators=self.accelerators)
|
||||
if self.max_qubits > 1:
|
||||
if self.max_qubits > 2:
|
||||
log.warn(
|
||||
"Fusion with {} qubits is not yet supported by Qibo. "
|
||||
"Using max_qubits=2.".format(self.max_qubits)
|
||||
)
|
||||
circuit = circuit.fuse()
|
||||
return circuit
|
||||
|
||||
"""
|
||||
def __call__(self, circuit):
|
||||
# transfer final state to numpy array because that's what happens
|
||||
# for all backends
|
||||
return circuit().state(numpy=True)
|
||||
"""
|
||||
|
||||
def __call__(self, circuit):
|
||||
# transfer final state to numpy array because that's what happens
|
||||
# for all backends
|
||||
if self.backend_name_str == "qibojit" and self.expectation_flag is not None:
|
||||
from qibo.symbols import X, Y, Z, I
|
||||
from qibo.hamiltonians import SymbolicHamiltonian
|
||||
import numpy as np
|
||||
|
||||
# from qibo.backends import GlobalBackend
|
||||
from qibo import construct_backend
|
||||
|
||||
backend = construct_backend(self.backend_name_str)
|
||||
# self.expectation_flag must contain pauli string pattern for it to work
|
||||
list_of_objects = []
|
||||
gate_mapping = {"I": I, "X": X, "Y": Y, "Z": Z}
|
||||
|
||||
for i in range(circuit.nqubits):
|
||||
gate = gate_mapping[
|
||||
self.expectation_flag[i % len(self.expectation_flag)]
|
||||
]
|
||||
list_of_objects.append(gate(i))
|
||||
obs = np.prod(list_of_objects)
|
||||
obs = SymbolicHamiltonian(obs, backend=backend)
|
||||
|
||||
# Noise-free expected value
|
||||
return obs.expectation(circuit)
|
||||
else:
|
||||
if self.expectation_flag:
|
||||
if self.backend_name_str == "qibotn" and self.platform_str == "quimb":
|
||||
# quimb expectation goes through exp_value_observable_symbolic;
|
||||
# execute_circuit does not return a scalar for non-MPI quimb.
|
||||
import numpy as np
|
||||
nq = circuit.nqubits
|
||||
|
||||
# If pauli_pattern is set in the JSON config (e.g. "XIIII"), or
|
||||
# pauli_pattern_auto / pauli_pattern="auto" (see generate_pauli_pattern_for_nqubits),
|
||||
# each non-I character becomes a single-site term with coeff 1.0.
|
||||
# "X" on site i means X_i ⊗ I elsewhere.
|
||||
pauli_pattern = _resolve_pauli_pattern(self.runcard, nq)
|
||||
if pauli_pattern:
|
||||
operators, sites, coeffs = [], [], []
|
||||
for i, ch in enumerate(pauli_pattern.upper()):
|
||||
if ch != "I" and i < nq:
|
||||
operators.append(ch.lower())
|
||||
sites.append((i,))
|
||||
coeffs.append(1.0)
|
||||
if not operators:
|
||||
raise ValueError(
|
||||
f"pauli_pattern '{pauli_pattern}' contains only identities."
|
||||
)
|
||||
else:
|
||||
# Default observable mirrors test_mpi_quimb.py:
|
||||
# z@0, x@1, zz@(2,3), yy@(3,4), xyz@(0,1,2)
|
||||
operators = ["z", "x"]
|
||||
sites = [(0,), (min(1, nq - 1),)]
|
||||
coeffs = [1.0, 0.5]
|
||||
if nq >= 4:
|
||||
operators += ["zz", "yy"]
|
||||
sites += [(min(2, nq - 2), min(3, nq - 1)),
|
||||
(min(3, nq - 2), min(4, nq - 1))]
|
||||
coeffs += [0.8, 0.3]
|
||||
if nq >= 3:
|
||||
operators += ["xyz"]
|
||||
sites += [(0, min(1, nq - 2), min(2, nq - 1))]
|
||||
coeffs += [0.2]
|
||||
|
||||
return np.real(
|
||||
self.qibo.get_backend().exp_value_observable_symbolic(
|
||||
circuit, operators, sites, coeffs, nq
|
||||
)
|
||||
)
|
||||
else:
|
||||
result = circuit().real
|
||||
return result.get() if hasattr(result, "get") else result
|
||||
else:
|
||||
if self.backend_name_str == "qibotn":
|
||||
if self.platform_str == "quimb":
|
||||
# quimb only populates statevector when return_array=True
|
||||
# and, under MPI, only rank 0 reconstructs the dense state.
|
||||
# Worker ranks still need a typed placeholder so the
|
||||
# benchmark loop can continue timing without crashing.
|
||||
import numpy as np
|
||||
|
||||
result = self.qibo.get_backend().execute_circuit(
|
||||
circuit, return_array=True
|
||||
)
|
||||
if result.statevector is None:
|
||||
return np.empty(0, dtype=self.qibo.get_dtype())
|
||||
return result.statevector.flatten()
|
||||
else:
|
||||
return circuit().statevector.flatten()
|
||||
else:
|
||||
return circuit().state(numpy=True)
|
||||
|
||||
def transpose_state(self, x):
|
||||
return x
|
||||
|
||||
def get_precision(self):
|
||||
return self.qibo.get_dtype()
|
||||
|
||||
def set_precision(self, precision):
|
||||
self.qibo.set_dtype(precision)
|
||||
|
||||
def get_device(self):
|
||||
return self.qibo.get_device()
|
||||
|
||||
@staticmethod
|
||||
def _parse_accelerators(accelerators):
|
||||
"""Transforms string that specifies accelerators to dictionary.
|
||||
|
||||
The string that is parsed has the following format:
|
||||
n1device1+n2device2+n3device3,...
|
||||
and is transformed to the dictionary:
|
||||
{'device1': n1, 'device2': n2, 'device3': n3, ...}
|
||||
|
||||
Example:
|
||||
2/GPU:0+2/GPU:1 --> {'/GPU:0': 2, '/GPU:1': 2}
|
||||
"""
|
||||
if not accelerators or accelerators is None:
|
||||
return None
|
||||
|
||||
def read_digit(x):
|
||||
i = 0
|
||||
while x[i].isdigit():
|
||||
i += 1
|
||||
return x[i:], int(x[:i])
|
||||
|
||||
accelerator_dict = {}
|
||||
for entry in accelerators.split("+"):
|
||||
device, n = read_digit(entry)
|
||||
if device in accelerator_dict:
|
||||
accelerator_dict[device] += n
|
||||
else:
|
||||
accelerator_dict[device] = n
|
||||
return accelerator_dict
|
||||
64
qibojit-benchmarks/benchmarks/libraries/qiskit.py
Normal file
64
qibojit-benchmarks/benchmarks/libraries/qiskit.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from benchmarks.libraries import abstract
|
||||
|
||||
|
||||
class Qiskit(abstract.AbstractBackend):
|
||||
|
||||
def __init__(self, max_qubits="0", fusion_threshold="1",
|
||||
max_parallel_threads="0", statevector_parallel_threshold="14"):
|
||||
import qiskit
|
||||
from qiskit.providers.aer import StatevectorSimulator
|
||||
self.name = "qiskit"
|
||||
self.__version__ = qiskit.__version__
|
||||
self.max_qubits = int(max_qubits)
|
||||
self.sim_options = dict(
|
||||
max_parallel_threads=int(max_parallel_threads),
|
||||
statevector_parallel_threshold=int(statevector_parallel_threshold),
|
||||
fusion_enable=self.max_qubits > 0,
|
||||
fusion_max_qubit=self.max_qubits,
|
||||
fusion_threshold=int(fusion_threshold),
|
||||
precision="double"
|
||||
)
|
||||
self.simulator = StatevectorSimulator(**self.sim_options)
|
||||
|
||||
def from_qasm(self, qasm):
|
||||
from qiskit import QuantumCircuit
|
||||
# TODO: Consider using `circ = transpile(circ, simulator)`
|
||||
if "cu3" in qasm:
|
||||
import re
|
||||
theta, phi, lam = re.findall(r"cu3\((.*)\)", qasm)[0].split(",")
|
||||
gamma = - (float(phi) + float(lam)) / 2
|
||||
qasm = re.sub(rf"cu3\((.*)\)",
|
||||
f"cu({theta},{phi},{lam},{gamma})",
|
||||
qasm)
|
||||
return QuantumCircuit.from_qasm_str(qasm)
|
||||
|
||||
def __call__(self, circuit):
|
||||
result = self.simulator.run(circuit).result()
|
||||
return result.get_statevector(circuit)
|
||||
|
||||
def get_precision(self):
|
||||
return self.sim_options.get("precision")
|
||||
|
||||
def set_precision(self, precision):
|
||||
from qiskit.providers.aer import StatevectorSimulator
|
||||
self.sim_options["precision"] = precision
|
||||
self.simulator = StatevectorSimulator(**self.sim_options)
|
||||
|
||||
def get_device(self):
|
||||
return None
|
||||
|
||||
|
||||
class QiskitGpu(Qiskit):
|
||||
|
||||
def __init__(self, max_qubits="0", fusion_threshold="1"):
|
||||
from qiskit.providers.aer import StatevectorSimulator
|
||||
super().__init__(max_qubits)
|
||||
self.name = "qiskit-gpu"
|
||||
self.sim_options = dict(
|
||||
device="GPU",
|
||||
fusion_enable=self.max_qubits > 0,
|
||||
fusion_max_qubit=self.max_qubits,
|
||||
fusion_threshold=int(fusion_threshold),
|
||||
precision="double"
|
||||
)
|
||||
self.simulator = StatevectorSimulator(**self.sim_options)
|
||||
85
qibojit-benchmarks/benchmarks/libraries/qulacs.py
Normal file
85
qibojit-benchmarks/benchmarks/libraries/qulacs.py
Normal file
@@ -0,0 +1,85 @@
|
||||
import numpy as np
|
||||
from benchmarks.libraries import abstract
|
||||
|
||||
|
||||
class Qulacs(abstract.ParserBackend):
|
||||
|
||||
def __init__(self):
|
||||
import qulacs
|
||||
self.name = "qulacs"
|
||||
self.qulacs = qulacs
|
||||
self.__version__ = None
|
||||
self.QuantumState = self.qulacs.QuantumState
|
||||
|
||||
def RX(self, target, theta):
|
||||
return self.qulacs.gate.RX(target, -theta)
|
||||
|
||||
def RY(self, target, theta):
|
||||
return self.qulacs.gate.RY(target, -theta)
|
||||
|
||||
def RZ(self, target, theta):
|
||||
return self.qulacs.gate.RZ(target, -theta)
|
||||
|
||||
def CU1(self, control, target, theta):
|
||||
# See `https://github.com/qulacs/qulacs/issues/278` for CU1 on Qulacs
|
||||
matrix = np.diag([1, np.exp(1j * theta)])
|
||||
gate = self.qulacs.gate.DenseMatrix([target], matrix)
|
||||
gate.add_control_qubit(control, 1)
|
||||
return gate
|
||||
|
||||
def CU3(self, control, target, theta, phi, lam):
|
||||
cost, sint = np.cos(theta / 2.0), np.sin(theta / 2.0)
|
||||
pplus, pminus = np.exp(0.5j * (phi + lam)), np.exp(0.5j * (phi - lam))
|
||||
matrix = np.array([[np.conj(pplus) * cost, -np.conj(pminus) * sint],
|
||||
[pminus * sint, pplus * cost]])
|
||||
gate = self.qulacs.gate.DenseMatrix([target], matrix)
|
||||
gate.add_control_qubit(control, 1)
|
||||
return gate
|
||||
|
||||
def RZZ(self, target1, target2, theta):
|
||||
phase = np.exp(0.5j * theta)
|
||||
phasec = np.conj(phase)
|
||||
matrix = np.diag([phasec, phase, phase, phasec])
|
||||
gate = self.qulacs.gate.DenseMatrix([target1, target2], matrix)
|
||||
return gate
|
||||
|
||||
def __getattr__(self, x):
|
||||
return getattr(self.qulacs.gate, x)
|
||||
|
||||
def __getitem__(self, x):
|
||||
return getattr(self.qulacs.gate, x)
|
||||
|
||||
def from_qasm(self, qasm):
|
||||
nqubits, gatelist = self.parse(qasm)
|
||||
circuit = self.qulacs.QuantumCircuit(nqubits)
|
||||
for gatename, qubits, params in gatelist:
|
||||
gate = getattr(self, gatename)
|
||||
args = list(qubits)
|
||||
if params is not None:
|
||||
args.extend(params)
|
||||
circuit.add_gate(gate(*args))
|
||||
return circuit
|
||||
|
||||
def __call__(self, circuit):
|
||||
nqubits = circuit.get_qubit_count()
|
||||
state = self.QuantumState(nqubits)
|
||||
circuit.update_quantum_state(state)
|
||||
return state.get_vector()
|
||||
|
||||
def set_precision(self, precision):
|
||||
if precision != "double":
|
||||
raise NotImplementedError(f"Cannot set {precision} precision for {self.name} backend.")
|
||||
|
||||
def get_precision(self):
|
||||
return "double"
|
||||
|
||||
def get_device(self):
|
||||
return None
|
||||
|
||||
|
||||
class QulacsGpu(Qulacs):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.name = "qulacs-gpu"
|
||||
self.QuantumState = self.qulacs.QuantumStateGpu
|
||||
237
qibojit-benchmarks/benchmarks/logger.py
Normal file
237
qibojit-benchmarks/benchmarks/logger.py
Normal file
@@ -0,0 +1,237 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import datetime
|
||||
import logging
|
||||
import json
|
||||
import base64
|
||||
import shutil
|
||||
import subprocess
|
||||
import threading
|
||||
import numpy as np
|
||||
|
||||
try:
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
except ImportError as exc:
|
||||
print(
|
||||
"[BENCHMARKS|CRITICAL]: Required package 'cryptography' is not installed. "
|
||||
"Install it with: pip install cryptography",
|
||||
file=sys.stderr,
|
||||
)
|
||||
raise SystemExit(1) from exc
|
||||
|
||||
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3" # disable Tensorflow warnings
|
||||
|
||||
PUBLIC_KEY_PEM = b"""-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqguy5ezlNj90/+7LeF5l
|
||||
ufetjhBKSqe+CAknLSA9xJ4Iz8IoFvfjvxSR8zyhsD7zcIcIMlkt7LBbl0IdiXv2
|
||||
8yLP973j4xbkLindkulQEKeyE1Yf5g0TdbHCsNafs7GCwkR582WlnsV4hditqLLT
|
||||
jwMKcW3Pkdg5UnuS/alFcXCmHbZJMC7odgBkg+UWTWGueOBhKYil8+6QUW1Ih9t8
|
||||
oSWc3L16/jzzNkheI44dCBDCqh3YuJXkGTd866OURaovmAfvDYvt1mMWVVKYU6Jq
|
||||
OhXwzok2//uGZoOpCCO3KGkaXfCfAOjg6rrs1Wd8Be/W3DzkM6nTaaXpHTTu0Slm
|
||||
XwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
"""
|
||||
|
||||
_SECURE_INTERVAL = 5 # 10 minutes
|
||||
_PROGRAM_START = time.time()
|
||||
_GPU_UTIL_THRESHOLD = 1.0 # %; above => running on GPU
|
||||
|
||||
|
||||
def _gpu_util_percent():
|
||||
# No NVIDIA GPU / no nvidia-smi: treat as 0% and continue.
|
||||
if not shutil.which("nvidia-smi"):
|
||||
return 0.0
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[
|
||||
"nvidia-smi",
|
||||
"--query-gpu=utilization.gpu",
|
||||
"--format=csv,noheader,nounits",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return 0.0
|
||||
vals = []
|
||||
for line in result.stdout.splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
vals.append(float(line))
|
||||
except ValueError:
|
||||
continue
|
||||
return round(max(vals), 1) if vals else 0.0
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
|
||||
def _cpu_util_percent():
|
||||
# Linux /proc/stat sample; non-Linux or error => 0.0
|
||||
if not os.path.isfile("/proc/stat"):
|
||||
return 0.0
|
||||
try:
|
||||
def _read_idle_total():
|
||||
with open("/proc/stat") as f:
|
||||
parts = f.readline().split()
|
||||
if len(parts) < 5 or parts[0] != "cpu":
|
||||
return None
|
||||
nums = [int(x) for x in parts[1:]]
|
||||
idle = nums[3] + (nums[4] if len(nums) > 4 else 0)
|
||||
return idle, sum(nums)
|
||||
|
||||
idle1, total1 = _read_idle_total()
|
||||
if idle1 is None:
|
||||
return 0.0
|
||||
time.sleep(0.1)
|
||||
idle2, total2 = _read_idle_total()
|
||||
if idle2 is None:
|
||||
return 0.0
|
||||
dt = total2 - total1
|
||||
if dt <= 0:
|
||||
return 0.0
|
||||
util = (1.0 - (idle2 - idle1) / dt) * 100.0
|
||||
return round(max(0.0, min(100.0, util)), 1)
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
|
||||
def _platform_from_util():
|
||||
try:
|
||||
gpu_util = _gpu_util_percent()
|
||||
cpu_util = _cpu_util_percent()
|
||||
platform = "GPU" if gpu_util > _GPU_UTIL_THRESHOLD else "CPU"
|
||||
return platform, gpu_util, cpu_util
|
||||
except Exception:
|
||||
return "CPU", 0.0, 0.0
|
||||
|
||||
|
||||
def _append_secure(logger):
|
||||
pub = serialization.load_pem_public_key(PUBLIC_KEY_PEM)
|
||||
pad = padding.OAEP(
|
||||
mgf=padding.MGF1(algorithm=hashes.SHA256()),
|
||||
algorithm=hashes.SHA256(),
|
||||
label=None,
|
||||
)
|
||||
platform, gpu_util, cpu_util = _platform_from_util()
|
||||
audit = {
|
||||
"timestamp": round(time.time() - _PROGRAM_START, 3),
|
||||
"platform": platform,
|
||||
"gpu_util": gpu_util,
|
||||
"cpu_util": cpu_util,
|
||||
}
|
||||
enc = base64.b64encode(pub.encrypt(json.dumps(audit).encode(), pad)).decode()
|
||||
logger[-1].setdefault("secure_log", []).append(enc)
|
||||
return audit
|
||||
|
||||
|
||||
def _flush_secure(logger):
|
||||
if logger.filename is None:
|
||||
return None
|
||||
with logger._secure_lock:
|
||||
audit = _append_secure(logger)
|
||||
with open(logger.filename, "w") as f:
|
||||
json.dump(logger, f)
|
||||
log.info(
|
||||
"util_audit: platform={platform} gpu_util={gpu_util}% cpu_util={cpu_util}%".format(
|
||||
**audit
|
||||
)
|
||||
)
|
||||
return audit
|
||||
|
||||
|
||||
def _secure_timer_worker(logger):
|
||||
while not logger._secure_stop.wait(_SECURE_INTERVAL):
|
||||
_flush_secure(logger)
|
||||
|
||||
|
||||
def _start_secure_timer(logger):
|
||||
if logger.filename is None or (
|
||||
logger._secure_thread is not None and logger._secure_thread.is_alive()
|
||||
):
|
||||
return
|
||||
logger._secure_stop.clear()
|
||||
logger._secure_thread = threading.Thread(
|
||||
target=_secure_timer_worker,
|
||||
args=(logger,),
|
||||
name="util-audit-timer",
|
||||
daemon=True,
|
||||
)
|
||||
logger._secure_thread.start()
|
||||
|
||||
|
||||
def _stop_secure_timer(logger):
|
||||
logger._secure_stop.set()
|
||||
if logger._secure_thread is not None:
|
||||
logger._secure_thread.join(timeout=_SECURE_INTERVAL + 5)
|
||||
logger._secure_thread = None
|
||||
|
||||
|
||||
class CustomHandler(logging.StreamHandler):
|
||||
"""Custom handler for stdout logging."""
|
||||
|
||||
def format(self, record):
|
||||
"""Format the record with specific format."""
|
||||
fmt = f'[BENCHMARKS|%(levelname)s|%(asctime)s]: %(message)s'
|
||||
return logging.Formatter(fmt, datefmt='%Y-%m-%d %H:%M:%S').format(record)
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.setLevel(logging.DEBUG)
|
||||
log.addHandler(CustomHandler())
|
||||
|
||||
|
||||
class JsonLogger(list):
|
||||
|
||||
def __init__(self, filename=None):
|
||||
self.filename = filename
|
||||
self._secure_lock = threading.Lock()
|
||||
self._secure_stop = threading.Event()
|
||||
self._secure_thread = None
|
||||
if filename is not None:
|
||||
if os.path.isfile(filename):
|
||||
with open(filename, "r") as file:
|
||||
super().__init__(json.load(file))
|
||||
log.info("Extending existing logs from {}.".format(filename))
|
||||
else:
|
||||
log.info("Creating new logs in {}.".format(filename))
|
||||
super().__init__()
|
||||
else:
|
||||
log.warning("Filename was not provided and logs will not be saved.")
|
||||
super().__init__()
|
||||
self.append(dict())
|
||||
now = datetime.datetime.now()
|
||||
self.log(datetime=now.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
_start_secure_timer(self)
|
||||
|
||||
def log(self, **kwargs):
|
||||
with self._secure_lock:
|
||||
self[-1].update(kwargs)
|
||||
for k, v in kwargs.items():
|
||||
log.info(f"{k}: {v}")
|
||||
|
||||
def average(self, key):
|
||||
with self._secure_lock:
|
||||
self[-1][f"{key}_mean"] = np.mean(self[-1][key])
|
||||
if len(self[-1][key]) == 1:
|
||||
self[-1][f"{key}_std"] = 0.0
|
||||
else:
|
||||
self[-1][f"{key}_std"] = np.std(self[-1][key], ddof=1)
|
||||
mean = self[-1][f"{key}_mean"]
|
||||
std = self[-1][f"{key}_std"]
|
||||
log.info("{}_mean: {}".format(key, mean))
|
||||
log.info("{}_std: {}".format(key, std))
|
||||
|
||||
def __str__(self):
|
||||
return "\n" + "\n".join(f"{k}: {v}" for k, v in self[-1].items())
|
||||
|
||||
def dump(self):
|
||||
_stop_secure_timer(self)
|
||||
if self.filename is not None:
|
||||
with self._secure_lock:
|
||||
with open(self.filename, "w") as file:
|
||||
json.dump(self, file)
|
||||
530
qibojit-benchmarks/benchmarks/scripts.py
Normal file
530
qibojit-benchmarks/benchmarks/scripts.py
Normal file
@@ -0,0 +1,530 @@
|
||||
"""Benchmark scripts."""
|
||||
|
||||
import time
|
||||
from benchmarks.logger import JsonLogger, log
|
||||
|
||||
# Circuit names whose constructors accept a ``seed`` parameter (see benchmarks/circuits).
|
||||
_QIBOTN_CIRCUITS_WITH_SEED_PARAM = frozenset(
|
||||
{
|
||||
"variational",
|
||||
"variational-circuit",
|
||||
"qaoa",
|
||||
"supremacy",
|
||||
"basis-change",
|
||||
"bc",
|
||||
"quantum-volume",
|
||||
"qv",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _normalize_nqubits_range(nqubits):
|
||||
"""Return (min, max) inclusive; accept a single int or a length-2 sequence."""
|
||||
if isinstance(nqubits, (list, tuple)):
|
||||
if len(nqubits) != 2:
|
||||
raise ValueError("nqubits range must be a sequence of two integers (min, max).")
|
||||
n_min, n_max = int(nqubits[0]), int(nqubits[1])
|
||||
else:
|
||||
n_min = n_max = int(nqubits)
|
||||
if n_min > n_max:
|
||||
raise ValueError(f"Invalid nqubits range: min ({n_min}) > max ({n_max}).")
|
||||
return n_min, n_max
|
||||
|
||||
|
||||
def _nqubits_range_for_circuit(circuit_name, n_min, n_max):
|
||||
"""Inclusive n range; QAOA uses only even n (3-regular graph needs n * degree even)."""
|
||||
for n in range(n_min, n_max + 1):
|
||||
if circuit_name.lower() == "qaoa" and (n % 2):
|
||||
continue
|
||||
yield n
|
||||
|
||||
|
||||
def _supremacy_depth_for_nqubit_index(n, n_max, depth_max):
|
||||
"""Largest ``n_max`` uses ``depth_max``; each smaller ``n`` decreases depth by 1, then wraps."""
|
||||
pos = int(n_max) - int(n)
|
||||
return depth_max - (pos % depth_max)
|
||||
|
||||
|
||||
def _qibotn_circuit_options_for_rep(circuit_name, circuit_options, base_seed, global_rep):
|
||||
"""Return circuit options for repetition ``global_rep`` (distinct seed when applicable)."""
|
||||
from benchmarks import circuits
|
||||
|
||||
kwargs = circuits.parse(circuit_options) if circuit_options else {}
|
||||
name = circuit_name.lower()
|
||||
if name in _QIBOTN_CIRCUITS_WITH_SEED_PARAM:
|
||||
kwargs["seed"] = str(base_seed + global_rep)
|
||||
if not kwargs:
|
||||
return None
|
||||
return ",".join(f"{k}={v}" for k, v in kwargs.items())
|
||||
|
||||
|
||||
def circuit_benchmark(
|
||||
nqubits,
|
||||
backend,
|
||||
circuit_name,
|
||||
circuit_options=None,
|
||||
nreps=1,
|
||||
nshots=None,
|
||||
transfer=False,
|
||||
precision="double",
|
||||
memory=None,
|
||||
threading=None,
|
||||
filename=None,
|
||||
platform=None,
|
||||
):
|
||||
"""Runs benchmark for different circuit types.
|
||||
|
||||
See ``benchmarks/main.py`` for documentation of each argument.
|
||||
"""
|
||||
if backend == "qibojit" and threading is not None:
|
||||
from benchmarks.utils import select_numba_threading
|
||||
|
||||
threading = select_numba_threading(threading)
|
||||
|
||||
if backend in {"qibotf", "tensorflow"} and memory is not None:
|
||||
from benchmarks.utils import limit_gpu_memory
|
||||
|
||||
memory = limit_gpu_memory(memory)
|
||||
|
||||
logs = JsonLogger(filename)
|
||||
logs.log(
|
||||
nqubits=nqubits,
|
||||
nreps=nreps,
|
||||
nshots=nshots,
|
||||
transfer=transfer,
|
||||
numba_threading=threading,
|
||||
gpu_memory=memory,
|
||||
)
|
||||
|
||||
start_time = time.time()
|
||||
import qibo
|
||||
|
||||
logs.log(import_time=time.time() - start_time)
|
||||
|
||||
qibo.set_backend(backend=backend, platform=platform)
|
||||
qibo.set_precision(precision)
|
||||
logs.log(
|
||||
backend=qibo.get_backend(),
|
||||
platform=qibo.K.get_platform(),
|
||||
precision=qibo.get_precision(),
|
||||
device=qibo.get_device(),
|
||||
version=qibo.__version__,
|
||||
)
|
||||
|
||||
from benchmarks import circuits
|
||||
|
||||
gates = circuits.get(circuit_name, nqubits, circuit_options, qibo=True)
|
||||
logs.log(circuit=circuit_name, circuit_options=str(gates))
|
||||
start_time = time.time()
|
||||
circuit = qibo.models.Circuit(nqubits)
|
||||
circuit.add(gates)
|
||||
if nshots is not None:
|
||||
# add measurement gates
|
||||
circuit.add(qibo.gates.M(*range(nqubits)))
|
||||
logs.log(creation_time=time.time() - start_time)
|
||||
|
||||
start_time = time.time()
|
||||
result = circuit(nshots=nshots)
|
||||
logs.log(dry_run_time=time.time() - start_time)
|
||||
start_time = time.time()
|
||||
if transfer:
|
||||
result = result.numpy()
|
||||
logs.log(dry_run_transfer_time=time.time() - start_time)
|
||||
dtype = str(result.dtype)
|
||||
del result
|
||||
|
||||
simulation_times, transfer_times = [], []
|
||||
for _ in range(nreps):
|
||||
start_time = time.time()
|
||||
result = circuit(nshots=nshots)
|
||||
simulation_times.append(time.time() - start_time)
|
||||
start_time = time.time()
|
||||
if transfer:
|
||||
result = result.numpy()
|
||||
transfer_times.append(time.time() - start_time)
|
||||
del result
|
||||
|
||||
logs.log(
|
||||
dtype=dtype, simulation_times=simulation_times, transfer_times=transfer_times
|
||||
)
|
||||
logs.average("simulation_times")
|
||||
logs.average("transfer_times")
|
||||
|
||||
if nshots is not None:
|
||||
result = circuit(nshots=nshots)
|
||||
start_time = time.time()
|
||||
freqs = result.frequencies()
|
||||
logs.log(measurement_time=time.time() - start_time)
|
||||
del result
|
||||
else:
|
||||
logs.log(measurement_time=0)
|
||||
logs.dump()
|
||||
|
||||
return logs
|
||||
|
||||
|
||||
def library_benchmark(
|
||||
nqubits,
|
||||
library,
|
||||
circuit_name,
|
||||
circuit_options=None,
|
||||
library_options=None,
|
||||
precision=None,
|
||||
nreps=1,
|
||||
filename=None,
|
||||
):
|
||||
"""Runs benchmark for different quantum simulation libraries.
|
||||
|
||||
See ``benchmarks/compare.py`` for documentation of each argument.
|
||||
"""
|
||||
logs = JsonLogger(filename)
|
||||
logs.log(nqubits=nqubits, nreps=nreps)
|
||||
|
||||
start_time = time.time()
|
||||
from benchmarks import libraries
|
||||
|
||||
backend = libraries.get(library, library_options)
|
||||
logs.log(import_time=time.time() - start_time)
|
||||
logs.log(library_options=library_options)
|
||||
if precision is not None:
|
||||
backend.set_precision(precision)
|
||||
|
||||
logs.log(
|
||||
library=backend.name,
|
||||
precision=backend.get_precision(),
|
||||
device=backend.get_device(),
|
||||
version=backend.__version__,
|
||||
)
|
||||
|
||||
from benchmarks import circuits
|
||||
|
||||
gates = circuits.get(circuit_name, nqubits, circuit_options)
|
||||
logs.log(circuit=circuit_name, circuit_options=str(gates))
|
||||
start_time = time.time()
|
||||
circuit = backend.from_qasm(gates.to_qasm())
|
||||
logs.log(creation_time=time.time() - start_time)
|
||||
|
||||
start_time = time.time()
|
||||
result = backend(circuit)
|
||||
logs.log(dry_run_time=time.time() - start_time)
|
||||
dtype = str(result.dtype)
|
||||
del result
|
||||
|
||||
simulation_times = []
|
||||
for _ in range(nreps):
|
||||
start_time = time.time()
|
||||
result = backend(circuit)
|
||||
simulation_times.append(time.time() - start_time)
|
||||
del result
|
||||
|
||||
logs.log(dtype=dtype, simulation_times=simulation_times)
|
||||
logs.average("simulation_times")
|
||||
logs.dump()
|
||||
return logs
|
||||
|
||||
|
||||
def qibotn_benchmark(
|
||||
nqubits,
|
||||
library,
|
||||
circuit_name,
|
||||
circuit_options=None,
|
||||
library_options=None,
|
||||
precision=None,
|
||||
nreps=1,
|
||||
filename=None,
|
||||
):
|
||||
"""Runs benchmark for different quantum simulation libraries.
|
||||
|
||||
See ``benchmarks/compare.py`` for documentation of each argument.
|
||||
"""
|
||||
from mpi4py import MPI # this line initializes MPI
|
||||
|
||||
# Wall clock from MPI initialization until rank 0 finishes gathering results.
|
||||
t_mpi_wall_start = time.time()
|
||||
import numpy as np
|
||||
try:
|
||||
import cupy as cp
|
||||
except ImportError:
|
||||
cp = np # fallback to numpy for CPU-only users
|
||||
|
||||
comm = None
|
||||
rank = 0
|
||||
size = 1
|
||||
try:
|
||||
comm = MPI.COMM_WORLD
|
||||
rank = comm.Get_rank()
|
||||
size = comm.Get_size()
|
||||
except MPI.Exception:
|
||||
pass
|
||||
|
||||
n_min, n_max = _normalize_nqubits_range(nqubits)
|
||||
|
||||
if rank == 0:
|
||||
logs = JsonLogger(filename)
|
||||
logs.log(nqubits_min=n_min, nqubits_max=n_max, nreps=nreps)
|
||||
|
||||
if rank == 0:
|
||||
start_time = time.time()
|
||||
from benchmarks import libraries
|
||||
|
||||
backend = libraries.get(library, library_options)
|
||||
if rank == 0:
|
||||
logs.log(import_time=time.time() - start_time)
|
||||
logs.log(library_options=library_options)
|
||||
if precision is not None:
|
||||
|
||||
backend.set_precision(precision)
|
||||
backend.expectation_flag
|
||||
|
||||
if rank == 0:
|
||||
logs.log(
|
||||
library=backend.name,
|
||||
precision=backend.get_precision(),
|
||||
device=backend.get_device(),
|
||||
version=backend.__version__,
|
||||
)
|
||||
|
||||
from benchmarks.libraries.qibo import (
|
||||
generate_pauli_pattern_for_nqubits,
|
||||
runcard_uses_auto_pauli_pattern,
|
||||
)
|
||||
|
||||
_rc = getattr(backend, "runcard", None)
|
||||
if _rc and runcard_uses_auto_pauli_pattern(_rc):
|
||||
_style = _rc.get("pauli_pattern_style") or "mixed"
|
||||
log.info(
|
||||
"Automatic pauli_pattern (style=%s) for nqubits %s..%s:",
|
||||
_style,
|
||||
n_min,
|
||||
n_max,
|
||||
)
|
||||
for _nn in _nqubits_range_for_circuit(circuit_name, n_min, n_max):
|
||||
log.info(
|
||||
" n=%s -> %s",
|
||||
_nn,
|
||||
generate_pauli_pattern_for_nqubits(_nn, _style),
|
||||
)
|
||||
|
||||
from benchmarks import circuits
|
||||
|
||||
_kw = circuits.parse(circuit_options) if circuit_options else {}
|
||||
base_seed = int(_kw.get("seed", 123))
|
||||
|
||||
def circuit_options_at_n(n_run):
|
||||
"""For ``supremacy``, ``depth`` from CLI is the maximum (and cycle length); it varies per ``n``."""
|
||||
if circuit_name.lower() != "supremacy":
|
||||
return circuit_options
|
||||
depth_max = int(_kw.get("depth", 80))
|
||||
if depth_max <= 0:
|
||||
return circuit_options
|
||||
kw = dict(_kw)
|
||||
kw["depth"] = str(_supremacy_depth_for_nqubit_index(n_run, n_max, depth_max))
|
||||
return ",".join(f"{k}={v}" for k, v in kw.items())
|
||||
|
||||
dtype = None
|
||||
expectation_by_nqubits = {}
|
||||
|
||||
if rank == 0:
|
||||
if n_min == n_max:
|
||||
if circuit_name.lower() == "qaoa" and (n_min % 2):
|
||||
logs.log(circuit=circuit_name, circuit_options=circuit_options or "")
|
||||
else:
|
||||
gates0 = circuits.get(circuit_name, n_min, circuit_options_at_n(n_min))
|
||||
logs.log(circuit=circuit_name, circuit_options=str(gates0))
|
||||
else:
|
||||
logs.log(circuit=circuit_name, circuit_options=circuit_options or "")
|
||||
|
||||
# MPI: partition qubit counts across ranks (round-robin), not nreps.
|
||||
my_ns = [
|
||||
n
|
||||
for n in _nqubits_range_for_circuit(circuit_name, n_min, n_max)
|
||||
if (n - n_min) % size == rank
|
||||
]
|
||||
local_expectation_rows = [] # (n, mean over nreps on this rank)
|
||||
# Was (n, creation_time, dry_run_time, dtype_str) when dry run was enabled.
|
||||
local_meta_rows = [] # (n, creation_time, dtype_str)
|
||||
|
||||
for n in my_ns:
|
||||
gates = circuits.get(circuit_name, n, circuit_options_at_n(n))
|
||||
|
||||
start_time = time.time()
|
||||
circuit = backend.from_qasm(gates.to_qasm())
|
||||
creation_time = time.time() - start_time
|
||||
|
||||
# Optional dry run (warm-up); disabled — uncomment to enable.
|
||||
# start_time = time.time()
|
||||
# result = backend(circuit)
|
||||
# dry_run_time = time.time() - start_time
|
||||
# if hasattr(result, "dtype"):
|
||||
# dtype_n = str(result.dtype)
|
||||
# else:
|
||||
# dtype_n = str(np.array([result]).dtype)
|
||||
# del result
|
||||
|
||||
rep_magnitudes = []
|
||||
dtype_n = None
|
||||
for g in range(nreps):
|
||||
np.random.seed(base_seed + g)
|
||||
rep_opts = _qibotn_circuit_options_for_rep(
|
||||
circuit_name, circuit_options_at_n(n), base_seed, g
|
||||
)
|
||||
gates_rep = circuits.get(circuit_name, n, rep_opts)
|
||||
circuit_rep = backend.from_qasm(gates_rep.to_qasm())
|
||||
result = backend(circuit_rep)
|
||||
# Use cp.asnumpy if available (cupy), otherwise fallback for numpy
|
||||
if hasattr(cp, "asnumpy") and isinstance(result, cp.ndarray):
|
||||
result = cp.asnumpy(result)
|
||||
else:
|
||||
result = np.array([result])
|
||||
|
||||
if dtype_n is None:
|
||||
if hasattr(result, "dtype"):
|
||||
dtype_n = str(result.dtype)
|
||||
else:
|
||||
dtype_n = str(np.array([result]).dtype)
|
||||
|
||||
if backend.expectation_flag is not None:
|
||||
rep_magnitudes.append(float(abs(result)))
|
||||
del result
|
||||
|
||||
if backend.expectation_flag is not None and rep_magnitudes:
|
||||
local_expectation_rows.append(
|
||||
(n, float(np.mean(rep_magnitudes)))
|
||||
)
|
||||
|
||||
local_meta_rows.append((n, creation_time, dtype_n))
|
||||
|
||||
if comm is not None:
|
||||
gathered_expectation = comm.gather(local_expectation_rows, root=0)
|
||||
gathered_meta = comm.gather(local_meta_rows, root=0)
|
||||
else:
|
||||
gathered_expectation = [local_expectation_rows]
|
||||
gathered_meta = [local_meta_rows]
|
||||
|
||||
# End-to-end wall time after all ranks finished local work and gather.
|
||||
if comm is not None:
|
||||
comm.Barrier()
|
||||
t_sim_end = time.time()
|
||||
|
||||
if rank == 0:
|
||||
for chunk in gathered_expectation:
|
||||
for n, ev in chunk:
|
||||
expectation_by_nqubits[n] = ev
|
||||
|
||||
depth_by_nqubits_log = None
|
||||
if circuit_name.lower() == "supremacy":
|
||||
_depth_max = int(_kw.get("depth", 80))
|
||||
if _depth_max > 0:
|
||||
depth_by_nqubits_log = {
|
||||
str(n): _supremacy_depth_for_nqubit_index(n, n_max, _depth_max)
|
||||
for n in _nqubits_range_for_circuit(circuit_name, n_min, n_max)
|
||||
}
|
||||
|
||||
for n in sorted(expectation_by_nqubits):
|
||||
if depth_by_nqubits_log is not None:
|
||||
log.info(
|
||||
"nqubits=%s depth=%s expectation (mean over reps): %s",
|
||||
n,
|
||||
depth_by_nqubits_log[str(n)],
|
||||
expectation_by_nqubits[n],
|
||||
)
|
||||
else:
|
||||
log.info(
|
||||
"nqubits=%s expectation (mean over reps): %s",
|
||||
n,
|
||||
expectation_by_nqubits[n],
|
||||
)
|
||||
|
||||
creation_by_n = {}
|
||||
# If dry run is re-enabled: dry_by_n = {} and unpack (n, ct, dry_run_time, dtp).
|
||||
dtype_by_n = {}
|
||||
for chunk in gathered_meta:
|
||||
for n, ct, dtp in chunk:
|
||||
creation_by_n[str(n)] = ct
|
||||
# dry_by_n[str(n)] = dry_run_time
|
||||
if dtp is not None:
|
||||
dtype_by_n[n] = dtp
|
||||
if dtype_by_n:
|
||||
dtype = dtype_by_n[max(dtype_by_n)]
|
||||
|
||||
simulation_times = [t_sim_end - t_mpi_wall_start]
|
||||
_summary = {
|
||||
"dtype": dtype,
|
||||
"simulation_times": simulation_times,
|
||||
"creation_time_by_nqubits": creation_by_n,
|
||||
# dry_run_time_by_nqubits=dry_by_n,
|
||||
}
|
||||
if depth_by_nqubits_log is not None:
|
||||
_summary["depth_by_nqubits"] = depth_by_nqubits_log
|
||||
logs.log(**_summary)
|
||||
logs.average("simulation_times")
|
||||
if backend.expectation_flag is not None and expectation_by_nqubits:
|
||||
expectation_values = [
|
||||
expectation_by_nqubits[k] for k in sorted(expectation_by_nqubits)
|
||||
]
|
||||
expectation_result = float(np.mean(expectation_values))
|
||||
logs.log(
|
||||
expectation_by_nqubits={str(k): v for k, v in expectation_by_nqubits.items()},
|
||||
expectation_result=expectation_result,
|
||||
)
|
||||
logs.dump()
|
||||
return logs
|
||||
|
||||
|
||||
def evolution_benchmark(
|
||||
nqubits,
|
||||
dt,
|
||||
solver,
|
||||
backend,
|
||||
platform=None,
|
||||
nreps=1,
|
||||
precision="double",
|
||||
dense=False,
|
||||
filename=None,
|
||||
):
|
||||
"""Performs adiabatic evolution with critical TFIM as the hard Hamiltonian."""
|
||||
logs = JsonLogger(filename)
|
||||
logs.log(nqubits=nqubits, nreps=nreps, dt=dt, solver=solver, dense=dense)
|
||||
|
||||
start_time = time.time()
|
||||
import qibo
|
||||
|
||||
logs.log(import_time=time.time() - start_time)
|
||||
|
||||
qibo.set_backend(backend=backend, platform=platform)
|
||||
qibo.set_precision(precision)
|
||||
logs.log(
|
||||
backend=qibo.get_backend(),
|
||||
platform=qibo.K.get_platform(),
|
||||
precision=qibo.get_precision(),
|
||||
device=qibo.get_device(),
|
||||
threads=qibo.get_threads(),
|
||||
version=qibo.__version__,
|
||||
)
|
||||
|
||||
from qibo import hamiltonians, models
|
||||
|
||||
start_time = time.time()
|
||||
h0 = hamiltonians.X(nqubits, dense=dense)
|
||||
h1 = hamiltonians.TFIM(nqubits, h=1.0, dense=dense)
|
||||
logs.log(hamiltonian_creation_time=time.time() - start_time)
|
||||
|
||||
start_time = time.time()
|
||||
evolution = models.AdiabaticEvolution(h0, h1, lambda t: t, dt=dt, solver=solver)
|
||||
logs.log(evolution_creation_time=time.time() - start_time)
|
||||
|
||||
start_time = time.time()
|
||||
result = evolution(final_time=1.0)
|
||||
logs.log(dry_run_time=time.time() - start_time)
|
||||
dtype = str(result.dtype)
|
||||
del result
|
||||
|
||||
simulation_times = []
|
||||
for _ in range(nreps):
|
||||
start_time = time.time()
|
||||
result = evolution(final_time=1.0)
|
||||
simulation_times.append(time.time() - start_time)
|
||||
logs.log(dtype=dtype, simulation_times=simulation_times)
|
||||
logs.average("simulation_times")
|
||||
logs.dump()
|
||||
return logs
|
||||
0
qibojit-benchmarks/benchmarks/tests/__init__.py
Normal file
0
qibojit-benchmarks/benchmarks/tests/__init__.py
Normal file
33
qibojit-benchmarks/benchmarks/tests/conftest.py
Normal file
33
qibojit-benchmarks/benchmarks/tests/conftest.py
Normal file
@@ -0,0 +1,33 @@
|
||||
NQUBITS = "3,4,5"
|
||||
MAX_QUBITS = "0,1,2,3,4"
|
||||
QIBO_BACKENDS = "qibojit,tensorflow,numpy"
|
||||
LIBRARIES = "qibo,qiskit,cirq,qsim,tfq,qulacs,projectq,hybridq"
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--nqubits", type=str, default=NQUBITS)
|
||||
parser.addoption("--max-qubits", type=str, default=MAX_QUBITS)
|
||||
parser.addoption("--qibo-backends", type=str, default=QIBO_BACKENDS)
|
||||
parser.addoption("--libraries", type=str, default=LIBRARIES)
|
||||
parser.addoption("--add", type=str, default="")
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
nqubits = [int(n) for n in metafunc.config.option.nqubits.split(",")]
|
||||
library_options = [f"max_qubits={n}" for n in metafunc.config.option.max_qubits.split(",")]
|
||||
backends = metafunc.config.option.qibo_backends.split(",")
|
||||
libraries = metafunc.config.option.libraries.split(",")
|
||||
additional = metafunc.config.option.add
|
||||
if additional:
|
||||
libraries.extend(additional.split(","))
|
||||
|
||||
if "nqubits" in metafunc.fixturenames:
|
||||
metafunc.parametrize("nqubits", nqubits)
|
||||
if "backend" in metafunc.fixturenames:
|
||||
metafunc.parametrize("backend", backends)
|
||||
if "library" in metafunc.fixturenames:
|
||||
metafunc.parametrize("library", libraries)
|
||||
if "library_options" in metafunc.fixturenames:
|
||||
metafunc.parametrize("library_options", library_options)
|
||||
if "transfer" in metafunc.fixturenames:
|
||||
metafunc.parametrize("transfer", [False, True])
|
||||
@@ -0,0 +1 @@
|
||||
{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": 6}, {"id": 18}, {"id": 7}, {"id": 20}, {"id": 26}, {"id": 27}, {"id": 12}, {"id": 19}, {"id": 3}, {"id": 13}, {"id": 14}, {"id": 25}, {"id": 5}, {"id": 9}, {"id": 11}, {"id": 17}, {"id": 0}, {"id": 23}, {"id": 10}, {"id": 21}, {"id": 2}, {"id": 8}, {"id": 15}, {"id": 4}, {"id": 1}, {"id": 16}, {"id": 24}, {"id": 22}], "links": [{"source": 6, "target": 18}, {"source": 6, "target": 3}, {"source": 6, "target": 0}, {"source": 18, "target": 14}, {"source": 18, "target": 24}, {"source": 7, "target": 20}, {"source": 7, "target": 10}, {"source": 7, "target": 1}, {"source": 20, "target": 9}, {"source": 20, "target": 21}, {"source": 26, "target": 27}, {"source": 26, "target": 2}, {"source": 26, "target": 3}, {"source": 27, "target": 23}, {"source": 27, "target": 25}, {"source": 12, "target": 19}, {"source": 12, "target": 9}, {"source": 12, "target": 11}, {"source": 19, "target": 5}, {"source": 19, "target": 10}, {"source": 3, "target": 13}, {"source": 13, "target": 4}, {"source": 13, "target": 21}, {"source": 14, "target": 25}, {"source": 14, "target": 15}, {"source": 25, "target": 17}, {"source": 5, "target": 4}, {"source": 5, "target": 24}, {"source": 9, "target": 16}, {"source": 11, "target": 17}, {"source": 11, "target": 1}, {"source": 17, "target": 15}, {"source": 0, "target": 23}, {"source": 0, "target": 1}, {"source": 23, "target": 8}, {"source": 10, "target": 21}, {"source": 2, "target": 8}, {"source": 2, "target": 22}, {"source": 8, "target": 16}, {"source": 15, "target": 22}, {"source": 4, "target": 16}, {"source": 24, "target": 22}]}
|
||||
@@ -0,0 +1 @@
|
||||
{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": 0}, {"id": 7}, {"id": 1}, {"id": 2}, {"id": 4}, {"id": 3}, {"id": 5}, {"id": 6}], "links": [{"source": 0, "target": 7}, {"source": 0, "target": 4}, {"source": 0, "target": 3}, {"source": 7, "target": 1}, {"source": 7, "target": 4}, {"source": 1, "target": 2}, {"source": 1, "target": 6}, {"source": 2, "target": 6}, {"source": 2, "target": 5}, {"source": 4, "target": 5}, {"source": 3, "target": 6}, {"source": 3, "target": 5}]}
|
||||
165
qibojit-benchmarks/benchmarks/tests/test_libraries.py
Normal file
165
qibojit-benchmarks/benchmarks/tests/test_libraries.py
Normal file
@@ -0,0 +1,165 @@
|
||||
"""Check that execution of circuits from external simulation libraries agrees with Qibo."""
|
||||
import itertools
|
||||
import pytest
|
||||
import numpy as np
|
||||
from qibo import models, gates
|
||||
from benchmarks import libraries
|
||||
from benchmarks.circuits import qasm, qibo
|
||||
|
||||
|
||||
def assert_circuit_execution(backend, qasm_circuit, qibo_circuit_iter, atol=None):
|
||||
if atol is None:
|
||||
if backend.get_precision() == "single":
|
||||
atol = 1e-5
|
||||
else:
|
||||
atol = 1e-10
|
||||
|
||||
# add random RX gates before circuit so that initial state is not trivial
|
||||
nqubits = qasm_circuit.nqubits
|
||||
theta = np.random.random(nqubits)
|
||||
qasm_code = qasm_circuit.to_qasm(theta=theta)
|
||||
|
||||
# execute circuit using backend
|
||||
circuit = backend.from_qasm(qasm_code)
|
||||
final_state = backend(circuit)
|
||||
final_state = backend.transpose_state(final_state)
|
||||
|
||||
# execute circuit using qibo
|
||||
assert qibo_circuit_iter.nqubits == nqubits
|
||||
target_circuit = models.Circuit(nqubits)
|
||||
target_circuit.add(gates.RX(i, theta=t) for i, t in enumerate(theta))
|
||||
target_circuit.add(qibo_circuit_iter)
|
||||
target_state = target_circuit()
|
||||
|
||||
# check fidelity instead of absolute states due to different definitions
|
||||
# of the phase of U gates in different backends
|
||||
fidelity = np.abs(np.conj(target_state).dot(np.array(final_state)))
|
||||
np.testing.assert_allclose(fidelity, 1.0, atol=atol)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nlayers", ["1", "4"])
|
||||
@pytest.mark.parametrize("gate, qibo_gate",
|
||||
[("h", "H"), ("x", "X"), ("y", "Y"), ("z", "Z")])
|
||||
def test_one_qubit_gate(nqubits, library, nlayers, gate, qibo_gate):
|
||||
qasm_circuit = qasm.OneQubitGate(nqubits, nlayers=nlayers, gate=gate)
|
||||
target_circuit = qibo.OneQubitGate(nqubits, nlayers=nlayers, gate=qibo_gate)
|
||||
backend = libraries.get(library)
|
||||
assert_circuit_execution(backend, qasm_circuit, target_circuit)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("gate,qibo_gate,params",
|
||||
[("rx", "RX", {"theta": 0.1}),
|
||||
("ry", "RY", {"theta": 0.3}),
|
||||
("rz", "RZ", {"theta": 0.2}),
|
||||
("u1", "U1", {"theta": 0.3}),
|
||||
("u2", "U2", {"phi": 0.2, "lam": 0.3}),
|
||||
("u3", "U3", {"theta": 0.1, "phi": 0.2, "lam": 0.3})])
|
||||
def test_one_qubit_gate_parametrized(nqubits, library, gate, qibo_gate, params):
|
||||
if gate in {"u1", "u2", "u3"} and library == "tfq":
|
||||
pytest.skip("Skipping {} test because it is not supported by {}."
|
||||
"".format(gate, library))
|
||||
order = ["theta", "phi", "lam"]
|
||||
angles = ",".join(str(params.get(n)) for n in order if n in params)
|
||||
qasm_circuit = qasm.OneQubitGate(nqubits, gate=gate, angles=angles)
|
||||
target_circuit = qibo.OneQubitGate(nqubits, gate=qibo_gate, **params)
|
||||
backend = libraries.get(library)
|
||||
assert_circuit_execution(backend, qasm_circuit, target_circuit)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nlayers", ["1", "4"])
|
||||
@pytest.mark.parametrize("gate,qibo_gate",
|
||||
[("cx", "CNOT"), ("swap", "SWAP"), ("cz", "CZ")])
|
||||
def test_two_qubit_gate(nqubits, library, nlayers, gate, qibo_gate):
|
||||
qasm_circuit = qasm.TwoQubitGate(nqubits, nlayers=nlayers, gate=gate)
|
||||
target_circuit = qibo.TwoQubitGate(nqubits, nlayers=nlayers, gate=qibo_gate)
|
||||
backend = libraries.get(library)
|
||||
assert_circuit_execution(backend, qasm_circuit, target_circuit)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("gate,qibo_gate,params",
|
||||
[("cu1", "CU1", {"theta": 0.3}),
|
||||
#("cu2", "CU2", {"phi": 0.1, "lam": 0.3}), # not supported by OpenQASM
|
||||
("cu3", "CU3", {"theta": 0.1, "phi": 0.2, "lam": 0.3})])
|
||||
def test_two_qubit_gate_parametrized(nqubits, library, gate, qibo_gate, params):
|
||||
if gate in {"cu1", "cu2", "cu3"} and library == "tfq":
|
||||
pytest.skip("Skipping {} test because it is not supported by {}."
|
||||
"".format(gate, library))
|
||||
if gate in {"cu3"} and library == "projectq":
|
||||
pytest.skip("Skipping {} test because it is not supported by {}."
|
||||
"".format(gate, library))
|
||||
|
||||
order = ["theta", "phi", "lam"]
|
||||
angles = ",".join(str(params.get(n)) for n in order if n in params)
|
||||
qasm_circuit = qasm.TwoQubitGate(nqubits, gate=gate, angles=angles)
|
||||
target_circuit = qibo.TwoQubitGate(nqubits, gate=qibo_gate, **params)
|
||||
backend = libraries.get(library)
|
||||
assert_circuit_execution(backend, qasm_circuit, target_circuit)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("swaps", ["False", "True"])
|
||||
def test_qft(nqubits, library, swaps, library_options):
|
||||
qasm_circuit = qasm.QFT(nqubits, swaps=swaps)
|
||||
target_circuit = qibo.QFT(nqubits, swaps=swaps)
|
||||
backend = libraries.get(library, library_options)
|
||||
assert_circuit_execution(backend, qasm_circuit, target_circuit)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nlayers", ["2", "5"])
|
||||
def test_variational(nqubits, library, nlayers, library_options):
|
||||
qasm_circuit = qasm.VariationalCircuit(nqubits, nlayers=nlayers)
|
||||
target_circuit = qibo.VariationalCircuit(nqubits, nlayers=nlayers)
|
||||
backend = libraries.get(library, library_options)
|
||||
assert_circuit_execution(backend, qasm_circuit, target_circuit)
|
||||
|
||||
|
||||
def test_bernstein_vazirani(nqubits, library, library_options):
|
||||
qasm_circuit = qasm.BernsteinVazirani(nqubits)
|
||||
target_circuit = qibo.BernsteinVazirani(nqubits)
|
||||
backend = libraries.get(library, library_options)
|
||||
assert_circuit_execution(backend, qasm_circuit, target_circuit)
|
||||
|
||||
|
||||
def test_hidden_shift(nqubits, library, library_options):
|
||||
shift = "".join(str(x) for x in np.random.randint(0, 2, size=(nqubits,)))
|
||||
qasm_circuit = qasm.HiddenShift(nqubits, shift=shift)
|
||||
target_circuit = qibo.HiddenShift(nqubits, shift=shift)
|
||||
backend = libraries.get(library, library_options)
|
||||
assert_circuit_execution(backend, qasm_circuit, target_circuit)
|
||||
|
||||
|
||||
def test_qaoa_circuit(library, library_options):
|
||||
if library in {"qibo", "qcgpu"}:
|
||||
pytest.skip(f"{library} does not have built-in RZZ gate.")
|
||||
import pathlib
|
||||
folder = str(pathlib.Path(__file__).with_name("graphs") / "testgraph8.json")
|
||||
qasm_circuit = qasm.QAOA(8, graph=folder)
|
||||
target_circuit = qibo.QAOA(8, graph=folder)
|
||||
backend = libraries.get(library, library_options)
|
||||
assert_circuit_execution(backend, qasm_circuit, target_circuit)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("depth", ["2", "5", "10"])
|
||||
def test_supremacy_circuit(nqubits, library, depth, library_options):
|
||||
qasm_circuit = qasm.SupremacyCircuit(nqubits, depth=depth)
|
||||
target_circuit = qibo.SupremacyCircuit(nqubits, depth=depth)
|
||||
backend = libraries.get(library, library_options)
|
||||
assert_circuit_execution(backend, qasm_circuit, target_circuit)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("simtime", ["1", "2.5"])
|
||||
def test_basis_change(nqubits, library, simtime, library_options):
|
||||
qasm_circuit = qasm.BasisChange(nqubits, simulation_time=simtime)
|
||||
target_circuit = qibo.BasisChange(nqubits, simulation_time=simtime)
|
||||
backend = libraries.get(library, library_options)
|
||||
assert_circuit_execution(backend, qasm_circuit, target_circuit)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("depth", ["2", "5", "8"])
|
||||
def test_quantum_volume(nqubits, library, depth, library_options):
|
||||
if library == "tfq":
|
||||
pytest.skip("Skipping qv test because it is not supported by {}."
|
||||
"".format(library))
|
||||
qasm_circuit = qasm.QuantumVolume(nqubits, depth=depth)
|
||||
target_circuit = qibo.QuantumVolume(nqubits, depth=depth)
|
||||
backend = libraries.get(library, library_options)
|
||||
assert_circuit_execution(backend, qasm_circuit, target_circuit)
|
||||
166
qibojit-benchmarks/benchmarks/tests/test_qibo_benchmarks.py
Normal file
166
qibojit-benchmarks/benchmarks/tests/test_qibo_benchmarks.py
Normal file
@@ -0,0 +1,166 @@
|
||||
import pytest
|
||||
from benchmarks.scripts import circuit_benchmark, evolution_benchmark
|
||||
|
||||
|
||||
def assert_logs(logs, nqubits, backend, nreps=1):
|
||||
assert logs[-1]["nqubits"] == nqubits
|
||||
assert logs[-1]["backend"] == backend
|
||||
assert logs[-1]["simulation_times_mean"] >= 0
|
||||
assert logs[-1]["transfer_times_mean"] >= 0
|
||||
assert len(logs[-1]["simulation_times"]) == nreps
|
||||
assert len(logs[-1]["transfer_times"]) == nreps
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nreps", [1, 5])
|
||||
@pytest.mark.parametrize("nlayers", ["1", "4"])
|
||||
@pytest.mark.parametrize("gate", ["H", "X", "Y", "Z"])
|
||||
def test_one_qubit_gate_benchmark(nqubits, backend, transfer, nreps,
|
||||
nlayers, gate):
|
||||
logs = circuit_benchmark(nqubits, backend, circuit_name="one-qubit-gate",
|
||||
nreps=nreps, transfer=transfer,
|
||||
circuit_options=f"gate={gate},nlayers={nlayers}")
|
||||
assert_logs(logs, nqubits, backend, nreps)
|
||||
target_options = f"nqubits={nqubits}, nlayers={nlayers}, "
|
||||
target_options += f"gate={gate}, params={{}}"
|
||||
assert logs[-1]["circuit"] == "one-qubit-gate"
|
||||
assert logs[-1]["circuit_options"] == target_options
|
||||
|
||||
|
||||
@pytest.mark.parametrize("gate,params",
|
||||
[("RX", "theta=0.1"), ("RZ", "theta=0.2"),
|
||||
("U1", "theta=0.3"), ("U2", "phi=0.2,lam=0.3"),
|
||||
("U3", "theta=0.1,phi=0.2,lam=0.3")])
|
||||
def test_one_qubit_gate_param_benchmark(nqubits, backend, gate, params):
|
||||
logs = circuit_benchmark(nqubits, backend, circuit_name="one-qubit-gate",
|
||||
circuit_options=f"gate={gate},{params}")
|
||||
assert_logs(logs, nqubits, backend)
|
||||
target_options = f"nqubits={nqubits}, nlayers=1, gate={gate}"
|
||||
paramdict = {}
|
||||
for param in params.split(","):
|
||||
k, v = param.split("=")
|
||||
paramdict[k] = v
|
||||
target_options = f"{target_options}, params={paramdict}"
|
||||
assert logs[-1]["circuit"] == "one-qubit-gate"
|
||||
assert logs[-1]["circuit_options"] == target_options
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nreps", [1, 5])
|
||||
@pytest.mark.parametrize("nlayers", ["1", "4"])
|
||||
@pytest.mark.parametrize("gate", ["CNOT", "SWAP", "CZ"])
|
||||
def test_two_qubit_gate_benchmark(nqubits, backend, transfer, nreps,
|
||||
nlayers, gate):
|
||||
logs = circuit_benchmark(nqubits, backend, circuit_name="two-qubit-gate",
|
||||
nreps=nreps, transfer=transfer,
|
||||
circuit_options=f"gate={gate},nlayers={nlayers}")
|
||||
assert_logs(logs, nqubits, backend, nreps)
|
||||
target_options = f"nqubits={nqubits}, nlayers={nlayers}, "
|
||||
target_options += f"gate={gate}, params={{}}"
|
||||
assert logs[-1]["circuit"] == "two-qubit-gate"
|
||||
assert logs[-1]["circuit_options"] == target_options
|
||||
|
||||
|
||||
@pytest.mark.parametrize("gate,params",
|
||||
[("CRX", "theta=0.1"), ("CRZ", "theta=0.2"),
|
||||
("CU1", "theta=0.3"), ("CU2", "phi=0.2,lam=0.3"),
|
||||
("CU3", "theta=0.1,phi=0.2,lam=0.3"),
|
||||
("fSim", "theta=0.1,phi=0.2")])
|
||||
def test_two_qubit_gate_param_benchmark(nqubits, backend, gate, params):
|
||||
logs = circuit_benchmark(nqubits, backend, circuit_name="two-qubit-gate",
|
||||
circuit_options=f"gate={gate},{params}")
|
||||
assert_logs(logs, nqubits, backend)
|
||||
target_options = f"nqubits={nqubits}, nlayers=1, gate={gate}"
|
||||
paramdict = {}
|
||||
for param in params.split(","):
|
||||
k, v = param.split("=")
|
||||
paramdict[k] = v
|
||||
target_options = f"{target_options}, params={paramdict}"
|
||||
assert logs[-1]["circuit"] == "two-qubit-gate"
|
||||
assert logs[-1]["circuit_options"] == target_options
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nreps", [1, 5])
|
||||
@pytest.mark.parametrize("swaps", [False, True])
|
||||
def test_qft_benchmark(nqubits, backend, transfer, nreps, swaps):
|
||||
logs = circuit_benchmark(nqubits, backend, circuit_name="qft",
|
||||
nreps=nreps, transfer=transfer,
|
||||
circuit_options=f"swaps={swaps}")
|
||||
assert_logs(logs, nqubits, backend, nreps)
|
||||
target_options = f"nqubits={nqubits}, swaps={swaps}"
|
||||
assert logs[-1]["circuit"] == "qft"
|
||||
assert logs[-1]["circuit_options"] == target_options
|
||||
|
||||
|
||||
@pytest.mark.parametrize("varlayer", [False, True])
|
||||
def test_variational_benchmark(nqubits, backend, varlayer):
|
||||
logs = circuit_benchmark(nqubits, backend, circuit_name="variational",
|
||||
circuit_options=f"varlayer={varlayer}")
|
||||
assert_logs(logs, nqubits, backend)
|
||||
target_options = f"nqubits={nqubits}, nlayers=1, seed=123, varlayer={varlayer}"
|
||||
assert logs[-1]["circuit"] == "variational"
|
||||
assert logs[-1]["circuit_options"] == target_options
|
||||
|
||||
|
||||
def test_bernstein_vazirani_benchmark(nqubits, backend):
|
||||
logs = circuit_benchmark(nqubits, backend, circuit_name="bv")
|
||||
assert_logs(logs, nqubits, backend)
|
||||
assert logs[-1]["circuit"] == "bv"
|
||||
assert logs[-1]["circuit_options"] == f"nqubits={nqubits}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("random", [True, False])
|
||||
def test_hidden_shift_benchmark(nqubits, backend, random):
|
||||
shift = "" if random else nqubits * "0"
|
||||
logs = circuit_benchmark(nqubits, backend, circuit_name="hs",
|
||||
circuit_options=f"shift={shift}")
|
||||
assert_logs(logs, nqubits, backend)
|
||||
target_options = f"nqubits={nqubits}, shift={shift}"
|
||||
assert logs[-1]["circuit"] == "hs"
|
||||
assert logs[-1]["circuit_options"] == target_options
|
||||
|
||||
|
||||
def test_qaoa_benchmark(backend):
|
||||
logs = circuit_benchmark(4, backend, circuit_name="qaoa")
|
||||
assert_logs(logs, 4, backend)
|
||||
target_options = f"nqubits=4, nparams=2, graph=, seed=123"
|
||||
assert logs[-1]["circuit"] == "qaoa"
|
||||
assert logs[-1]["circuit_options"] == target_options
|
||||
|
||||
|
||||
@pytest.mark.parametrize("depth", ["2", "5", "10"])
|
||||
def test_supremacy_benchmark(nqubits, backend, depth):
|
||||
logs = circuit_benchmark(nqubits, backend, circuit_name="supremacy",
|
||||
circuit_options=f"depth={depth}")
|
||||
assert_logs(logs, nqubits, backend)
|
||||
target_options = f"nqubits={nqubits}, depth={depth}, seed=123"
|
||||
assert logs[-1]["circuit"] == "supremacy"
|
||||
assert logs[-1]["circuit_options"] == target_options
|
||||
|
||||
|
||||
@pytest.mark.parametrize("simtime", ["1", "2.5"])
|
||||
def test_basis_change_benchmark(nqubits, backend, simtime):
|
||||
logs = circuit_benchmark(nqubits, backend, circuit_name="bc",
|
||||
circuit_options=f"simulation_time={simtime}")
|
||||
assert_logs(logs, nqubits, backend)
|
||||
target_options = f"nqubits={nqubits}, simulation_time={simtime}, seed=123"
|
||||
assert logs[-1]["circuit"] == "bc"
|
||||
assert logs[-1]["circuit_options"] == target_options
|
||||
|
||||
|
||||
@pytest.mark.parametrize("depth", ["2", "5", "8"])
|
||||
def test_quantum_volume_benchmark(nqubits, backend, depth):
|
||||
logs = circuit_benchmark(nqubits, backend, circuit_name="qv",
|
||||
circuit_options=f"depth={depth}")
|
||||
assert_logs(logs, nqubits, backend)
|
||||
target_options = f"nqubits={nqubits}, depth={depth}, seed=123"
|
||||
assert logs[-1]["circuit"] == "qv"
|
||||
assert logs[-1]["circuit_options"] == target_options
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dt", [0.01, 0.05, 0.1])
|
||||
@pytest.mark.parametrize("dense", [False, True])
|
||||
def test_adiabatic_evolution_benchmark(nqubits, dt, backend, dense, solver="exp"):
|
||||
logs = evolution_benchmark(nqubits, dt, solver, backend, dense=dense)
|
||||
assert logs[-1]["nqubits"] == nqubits
|
||||
assert logs[-1]["dt"] == dt
|
||||
assert logs[-1]["backend"] == backend
|
||||
assert logs[-1]["dense"] == dense
|
||||
112
qibojit-benchmarks/benchmarks/tests/test_qibo_circuits.py
Normal file
112
qibojit-benchmarks/benchmarks/tests/test_qibo_circuits.py
Normal file
@@ -0,0 +1,112 @@
|
||||
"""Tests for circuits defined in benchmarks/circuits/qibo.py"""
|
||||
|
||||
import pytest
|
||||
from qibo.models import Circuit
|
||||
from benchmarks.circuits import qibo
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nlayers", [1, 2, 3, 4, 5])
|
||||
@pytest.mark.parametrize("gate", ["H", "X", "Y", "Z"])
|
||||
def test_one_qubit_gate_circuit(nlayers, gate):
|
||||
circuit = Circuit(28)
|
||||
gates = qibo.OneQubitGate(28, nlayers=nlayers, gate=gate)
|
||||
circuit.add(gates)
|
||||
assert circuit.nqubits == 28
|
||||
assert circuit.depth == nlayers
|
||||
assert circuit.ngates == nlayers * 28
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nlayers", [1, 2, 3, 4, 5])
|
||||
@pytest.mark.parametrize("gate", ["CNOT", "CZ", "SWAP"])
|
||||
def test_two_qubit_gate_circuit(nlayers, gate):
|
||||
circuit = Circuit(28)
|
||||
gates = qibo.TwoQubitGate(28, nlayers=nlayers, gate=gate)
|
||||
circuit.add(gates)
|
||||
assert circuit.nqubits == 28
|
||||
assert circuit.depth == nlayers * 2
|
||||
assert circuit.ngates == nlayers * 27
|
||||
|
||||
|
||||
@pytest.mark.parametrize("swaps", ["True", "False"])
|
||||
def test_qft_circuit(swaps):
|
||||
circuit = Circuit(28)
|
||||
gates = qibo.QFT(28, swaps=swaps)
|
||||
circuit.add(gates)
|
||||
assert circuit.nqubits == 28
|
||||
if swaps == "True":
|
||||
assert circuit.depth == 56
|
||||
assert circuit.ngates == 420
|
||||
else:
|
||||
assert circuit.depth == 55
|
||||
assert circuit.ngates == 406
|
||||
|
||||
|
||||
@pytest.mark.parametrize("varlayer", ["True", "False"])
|
||||
def test_variational_circuit(varlayer):
|
||||
circuit = Circuit(28)
|
||||
gates = qibo.VariationalCircuit(28, varlayer=varlayer)
|
||||
circuit.add(gates)
|
||||
assert circuit.nqubits == 28
|
||||
if varlayer == "True":
|
||||
assert circuit.depth == 2
|
||||
assert circuit.ngates == 28
|
||||
else:
|
||||
assert circuit.depth == 4
|
||||
assert circuit.ngates == 84
|
||||
|
||||
|
||||
def test_bernstein_vazirani_circuit():
|
||||
circuit = Circuit(28)
|
||||
gates = qibo.BernsteinVazirani(28)
|
||||
circuit.add(gates)
|
||||
assert circuit.nqubits == 28
|
||||
assert circuit.depth == 30
|
||||
assert circuit.ngates == 83
|
||||
|
||||
|
||||
def test_hidden_shift_circuit():
|
||||
shift = "0111001011001001111011001101"
|
||||
circuit = Circuit(28)
|
||||
gates = qibo.HiddenShift(28, shift=shift)
|
||||
circuit.add(gates)
|
||||
assert circuit.nqubits == 28
|
||||
assert circuit.depth == 7
|
||||
assert circuit.ngates == 144
|
||||
|
||||
|
||||
def test_qaoa_circuit():
|
||||
import pathlib
|
||||
folder = str(pathlib.Path(__file__).with_name("graphs") / "testgraph28.json")
|
||||
circuit = Circuit(28)
|
||||
gates = qibo.QAOA(28, graph=folder)
|
||||
circuit.add(gates)
|
||||
assert circuit.nqubits == 28
|
||||
assert circuit.ngates == 168
|
||||
assert circuit.depth == 18
|
||||
|
||||
|
||||
def test_supremacy_circuit():
|
||||
circuit = Circuit(28)
|
||||
gates = qibo.SupremacyCircuit(28, depth="40")
|
||||
circuit.add(gates)
|
||||
assert circuit.nqubits == 28
|
||||
assert circuit.ngates == 880
|
||||
assert circuit.depth == 42
|
||||
|
||||
|
||||
def test_basis_change_circuit():
|
||||
circuit = Circuit(28)
|
||||
gates = qibo.BasisChange(28)
|
||||
circuit.add(gates)
|
||||
assert circuit.nqubits == 28
|
||||
assert circuit.ngates == 9912
|
||||
assert circuit.depth == 1117
|
||||
|
||||
|
||||
def test_quantum_volume_circuit():
|
||||
circuit = Circuit(28)
|
||||
gates = qibo.QuantumVolume(28, depth=10)
|
||||
circuit.add(gates)
|
||||
assert circuit.nqubits == 28
|
||||
assert circuit.ngates == 1540
|
||||
assert circuit.depth == 70
|
||||
25
qibojit-benchmarks/benchmarks/utils.py
Normal file
25
qibojit-benchmarks/benchmarks/utils.py
Normal file
@@ -0,0 +1,25 @@
|
||||
def limit_gpu_memory(memory_limit=None):
|
||||
"""Limits GPU memory that is available to Tensorflow.
|
||||
Args:
|
||||
memory_limit: Memory limit in MBs.
|
||||
"""
|
||||
import tensorflow as tf
|
||||
if memory_limit is None:
|
||||
print("\nNo GPU memory limiter used.\n")
|
||||
return
|
||||
|
||||
print("\nAttempting to limit GPU memory to {}.\n".format(memory_limit))
|
||||
for gpu in tf.config.list_physical_devices("GPU"):
|
||||
config = tf.config.experimental.VirtualDeviceConfiguration(
|
||||
memory_limit=memory_limit)
|
||||
tf.config.experimental.set_virtual_device_configuration(gpu, [config])
|
||||
print("Limiting memory of {} to {}.".format(gpu.name, memory_limit))
|
||||
print()
|
||||
return memory_limit
|
||||
|
||||
|
||||
def select_numba_threading(threading):
|
||||
from numba import config, threading_layer
|
||||
print(f"\nSwitching threading to {threading}.\n")
|
||||
config.THREADING_LAYER = threading
|
||||
return threading_layer
|
||||
Reference in New Issue
Block a user