diff --git a/pyproject.toml b/pyproject.toml index 46f4e564..f3a81904 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ classifiers = [ ] requires-python = ">=3.8" dependencies = [ - "qiskit[qasm3-import]>=0.45.0" + "qiskit[qasm3-import]>=1.0.0" ] dynamic = ["version"] @@ -143,14 +143,7 @@ log_cli_level = "INFO" xfail_strict = true filterwarnings = [ "error", - "ignore:.*qiskit.__qiskit_version__.*:DeprecationWarning:qiskit:", - "ignore:.*qiskit.utils.algorithm_globals.QiskitAlgorithmGlobals*:DeprecationWarning:qiskit", - "ignore:.*Building a flow controller with keyword arguments is going to be deprecated*:PendingDeprecationWarning:qiskit", - "ignore:.*qiskit.extensions module is pending deprecation*:PendingDeprecationWarning:qiskit", 'ignore:.*datetime\.datetime\.utcfromtimestamp.*:DeprecationWarning:', - "ignore:.*qiskit.utils.parallel*:DeprecationWarning:qiskit", - "ignore:.*qiskit.tools.events*:DeprecationWarning:qiskit", - "ignore:.*qiskit.qasm*:DeprecationWarning:qiskit", ] [tool.coverage] @@ -172,10 +165,6 @@ warn_unreachable = true explicit_package_bases = true pretty = true -[[tool.mypy.overrides]] -module = ["qiskit.*"] -ignore_missing_imports = true - [tool.ruff] line-length = 120 extend-include = ["*.ipynb"] diff --git a/src/mqt/ddsim/hybridqasmsimulator.py b/src/mqt/ddsim/hybridqasmsimulator.py index fc4e6ed7..76af937d 100644 --- a/src/mqt/ddsim/hybridqasmsimulator.py +++ b/src/mqt/ddsim/hybridqasmsimulator.py @@ -61,7 +61,7 @@ def _run_experiment(self, qc: QuantumCircuit, **options: Any) -> ExperimentResul nthreads = int(options.get("nthreads", local_hardware_info()["cpus"])) if mode == "amplitude": hybrid_mode = HybridMode.amplitude - max_qubits = self.max_qubits() + max_qubits = 30 # hard-coded 16GiB memory limit algorithm_qubits = qc.num_qubits if algorithm_qubits > max_qubits: msg = "Not enough memory available to simulate the circuit even on a single thread" diff --git a/src/mqt/ddsim/hybridstatevectorsimulator.py b/src/mqt/ddsim/hybridstatevectorsimulator.py index 8e42668c..ed8da9d2 100644 --- a/src/mqt/ddsim/hybridstatevectorsimulator.py +++ b/src/mqt/ddsim/hybridstatevectorsimulator.py @@ -13,7 +13,7 @@ class HybridStatevectorSimulatorBackend(HybridQasmSimulatorBackend): _SHOW_STATE_VECTOR = True _HSF_SV_TARGET = Target( description="MQT DDSIM HSF Statevector Simulator Target", - num_qubits=HybridQasmSimulatorBackend.max_qubits(), + num_qubits=30, # corresponds to 16GiB memory for storing the full statevector ) def __init__(self) -> None: diff --git a/src/mqt/ddsim/pathstatevectorsimulator.py b/src/mqt/ddsim/pathstatevectorsimulator.py index 9b0aa014..fb041273 100644 --- a/src/mqt/ddsim/pathstatevectorsimulator.py +++ b/src/mqt/ddsim/pathstatevectorsimulator.py @@ -13,7 +13,7 @@ class PathStatevectorSimulatorBackend(PathQasmSimulatorBackend): _SHOW_STATE_VECTOR = True _Path_SV_TARGET = Target( description="MQT DDSIM Simulation Path Framework Statevector Target", - num_qubits=PathQasmSimulatorBackend.max_qubits(), + num_qubits=30, # corresponds to 16GiB memory for storing the full statevector ) def __init__(self) -> None: diff --git a/src/mqt/ddsim/primitives/estimator.py b/src/mqt/ddsim/primitives/estimator.py index fba6e233..3243e349 100644 --- a/src/mqt/ddsim/primitives/estimator.py +++ b/src/mqt/ddsim/primitives/estimator.py @@ -7,14 +7,9 @@ import numpy as np from qiskit.circuit import QuantumCircuit -from qiskit.primitives.base import BaseEstimator, EstimatorResult -from qiskit.primitives.primitive_job import PrimitiveJob -from qiskit.primitives.utils import ( - _circuit_key, # noqa: PLC2701 - _observable_key, # noqa: PLC2701 - init_observable, -) -from qiskit.quantum_info import Pauli, PauliList, SparsePauliOp +from qiskit.primitives import Estimator as QiskitEstimator +from qiskit.primitives import EstimatorResult +from qiskit.quantum_info import Pauli, PauliList from mqt.ddsim.pyddsim import CircuitSimulator from mqt.ddsim.qasmsimulator import QasmSimulatorBackend @@ -22,12 +17,11 @@ if TYPE_CHECKING: from qiskit.circuit import Parameter from qiskit.circuit.parameterexpression import ParameterValueType - from qiskit.quantum_info.operators.base_operator import BaseOperator Parameters = Union[Mapping[Parameter, ParameterValueType], Sequence[ParameterValueType]] -class Estimator(BaseEstimator): +class Estimator(QiskitEstimator): """DDSIM implementation of qiskit's sampler. Code adapted from Qiskit's BackendEstimator class. """ @@ -152,47 +146,6 @@ def _observable_circuit(num_qubits: int, pauli: Pauli) -> tuple[QuantumCircuit, return obs_circuit, qubit_indices - def _run( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | SparsePauliOp], - parameter_values: Sequence[Parameters], - **run_options: dict[str, Any], - ) -> PrimitiveJob: - circuit_indices = [] - for circuit in circuits: - key_circ = _circuit_key(circuit) - index = self._circuit_ids.get(key_circ) - if index is not None: - circuit_indices.append(index) - else: - num_circuits = len(self._circuits) - circuit_indices.append(num_circuits) - self._circuit_ids[key_circ] = num_circuits - self._circuits.append(circuit) - self._parameters.append(circuit.parameters) - observable_indices = [] - for observable in observables: - observable_copy = init_observable(observable) - key_obs = _observable_key(observable_copy) - index = self._observable_ids.get(key_obs) - if index is not None: - observable_indices.append(index) - else: - num_observables = len(self._observables) - observable_indices.append(num_observables) - self._observable_ids[key_obs] = num_observables - self._observables.append(observable_copy) - job = PrimitiveJob( - self._call, - circuit_indices, - observable_indices, - parameter_values, - **run_options, - ) - job.submit() - return job - def _call( self, circuits: Sequence[int], diff --git a/src/mqt/ddsim/primitives/sampler.py b/src/mqt/ddsim/primitives/sampler.py index 81063f04..e0b562ce 100644 --- a/src/mqt/ddsim/primitives/sampler.py +++ b/src/mqt/ddsim/primitives/sampler.py @@ -5,9 +5,8 @@ import math from typing import TYPE_CHECKING, Any, Mapping, Sequence, Union -from qiskit.primitives.base import BaseSampler, SamplerResult -from qiskit.primitives.primitive_job import PrimitiveJob -from qiskit.primitives.utils import _circuit_key # noqa: PLC2701 +from qiskit.primitives import SamplerResult +from qiskit.primitives.sampler import Sampler as QiskitSampler from qiskit.result import QuasiDistribution, Result from mqt.ddsim.qasmsimulator import QasmSimulatorBackend @@ -15,12 +14,11 @@ if TYPE_CHECKING: from qiskit.circuit import Parameter from qiskit.circuit.parameterexpression import ParameterValueType - from qiskit.circuit.quantumcircuit import QuantumCircuit Parameters = Union[Mapping[Parameter, ParameterValueType], Sequence[ParameterValueType]] -class Sampler(BaseSampler): +class Sampler(QiskitSampler): _BACKEND = QasmSimulatorBackend() def __init__( @@ -41,39 +39,10 @@ def __init__( def backend(self) -> QasmSimulatorBackend: return self._BACKEND - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Parameters], - **run_options: Any, - ) -> PrimitiveJob: - """Stores circuits and parameters within the instance. - Executes _call function. - - Args: - circuits: List of quantum circuits to simulate - parameter_values: List of parameters associated with those circuits - run_options: Additional run options. - - Returns: - PrimitiveJob. - """ - circuit_indices = [] - for circuit in circuits: - key = _circuit_key(circuit) - index = self._circuit_ids.get(key) - if index is not None: - circuit_indices.append(index) - else: - num_circuits = len(self._circuits) - circuit_indices.append(num_circuits) - self._circuit_ids[key] = num_circuits - self._circuits.append(circuit) - self._parameters.append(circuit.parameters) - - job = PrimitiveJob(self._call, circuit_indices, parameter_values, **run_options) - job.submit() - return job + @property + def num_circuits(self) -> int: + """The number of circuits stored in the sampler.""" + return len(self._circuits) def _call( self, diff --git a/src/mqt/ddsim/qasmsimulator.py b/src/mqt/ddsim/qasmsimulator.py index 8d373bb9..a0fa2424 100644 --- a/src/mqt/ddsim/qasmsimulator.py +++ b/src/mqt/ddsim/qasmsimulator.py @@ -4,7 +4,6 @@ import time import uuid -from math import log2 from typing import TYPE_CHECKING, Any, Mapping, Sequence, Union from qiskit import QuantumCircuit @@ -13,7 +12,6 @@ from qiskit.result import Result from qiskit.result.models import ExperimentResult, ExperimentResultData from qiskit.transpiler import Target -from qiskit.utils.multiprocessing import local_hardware_info from . import __version__ from .header import DDSIMHeader @@ -34,14 +32,6 @@ class QasmSimulatorBackend(BackendV2): _SHOW_STATE_VECTOR = False _TARGET = Target(description="MQT DDSIM Simulator Target", num_qubits=128) - @staticmethod - def max_qubits(for_matrix: bool = False) -> int: - max_complex = local_hardware_info()["memory"] * (1024**3) / 16 - max_qubits = int(log2(max_complex)) - if for_matrix: - max_qubits = max_qubits // 2 - return max_qubits - @staticmethod def _add_operations_to_target(target: Target) -> None: DDSIMTargetBuilder.add_0q_gates(target) diff --git a/src/mqt/ddsim/statevectorsimulator.py b/src/mqt/ddsim/statevectorsimulator.py index 2d1c7685..eac6af0e 100644 --- a/src/mqt/ddsim/statevectorsimulator.py +++ b/src/mqt/ddsim/statevectorsimulator.py @@ -13,7 +13,7 @@ class StatevectorSimulatorBackend(QasmSimulatorBackend): _SHOW_STATE_VECTOR = True _SV_TARGET = Target( description="MQT DDSIM Statevector Simulator Target", - num_qubits=QasmSimulatorBackend.max_qubits(), + num_qubits=30, # corresponds to 16GiB memory for storing the full statevector ) def __init__(self) -> None: diff --git a/src/mqt/ddsim/unitarysimulator.py b/src/mqt/ddsim/unitarysimulator.py index 3c12a2fa..fa9fe6b9 100644 --- a/src/mqt/ddsim/unitarysimulator.py +++ b/src/mqt/ddsim/unitarysimulator.py @@ -26,7 +26,7 @@ class UnitarySimulatorBackend(QasmSimulatorBackend): _US_TARGET = Target( description="MQT DDSIM Unitary Simulator Target", - num_qubits=QasmSimulatorBackend.max_qubits(for_matrix=True), + num_qubits=15, # corresponds to 16GiB memory for storing the full matrix ) @staticmethod diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index 52ff8b1d..00000000 --- a/test/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# this is just here so that python imports work properly diff --git a/test/python/constraints.txt b/test/python/constraints.txt index aac0312c..1f62a3c0 100644 --- a/test/python/constraints.txt +++ b/test/python/constraints.txt @@ -2,4 +2,4 @@ scikit-build-core==0.6.1 setuptools-scm==7.0.0 pybind11==2.11.0 pytest==7.0.0 -qiskit==0.45.0 +qiskit==1.0.0 diff --git a/test/python/generate_benchmarks.py b/test/python/generate_benchmarks.py deleted file mode 100644 index e3e49fc0..00000000 --- a/test/python/generate_benchmarks.py +++ /dev/null @@ -1,187 +0,0 @@ -from __future__ import annotations - -import numpy as np -from qiskit import AncillaRegister, ClassicalRegister, QuantumCircuit, QuantumRegister -from qiskit.circuit.library import QFT, GraphState, GroverOperator - - -# measure qubits in reverse order which is better suited for DD-based simulation -def measure(qc: QuantumCircuit, q: QuantumRegister, c: ClassicalRegister): - for i in reversed(range(q.size)): - qc.measure(q[i], c[i]) - - -def ghz(n: int, include_measurements: bool = True): - q = QuantumRegister(n, "q") - c = ClassicalRegister(n, "c") - qc = QuantumCircuit(q, c, name="ghz_state") - qc.h(q[-1]) - for i in range(1, n): - qc.cx(q[n - i], q[n - i - 1]) - if include_measurements: - measure(qc, q, c) - return qc - - -def qft(n: int, include_measurements: bool = True): - q = QuantumRegister(n, "q") - c = ClassicalRegister(n, "c") - qc = QuantumCircuit(q, c, name="qft") - qc.compose(QFT(num_qubits=n), inplace=True) - if include_measurements: - measure(qc, q, c) - return qc - - -def qft_entangled(n: int, include_measurements: bool = True): - qc = ghz(n, include_measurements=False) - qc.compose(qft(n, include_measurements), inplace=True) - qc.name = "qft_entangled" - return qc - - -def grover(n: int, include_measurements: bool = True): - from qiskit.algorithms import Grover - - q = QuantumRegister(n, "q") - flag = AncillaRegister(1, "flag") - c = ClassicalRegister(n) - - state_preparation = QuantumCircuit(q, flag) - state_preparation.h(q) - state_preparation.x(flag) - - oracle = QuantumCircuit(q, flag) - oracle.mcp(np.pi, q, flag) - - operator = GroverOperator(oracle) - iterations = Grover.optimal_num_iterations(1, n) - - qc = QuantumCircuit(q, flag, c, name="grover") - qc.compose(state_preparation, inplace=True) - - qc.compose(operator.power(iterations), inplace=True) - if include_measurements: - measure(qc, q, c) - - return qc - - -def graph_state(n, include_measurements: bool = True): - import networkx as nx - - q = QuantumRegister(n, "q") - c = ClassicalRegister(n, "c") - qc = QuantumCircuit(q, c, name="graph_state") - - g = nx.random_regular_graph(3, n) - a = nx.convert_matrix.to_numpy_array(g) - qc.compose(GraphState(a), inplace=True) - if include_measurements: - measure(qc, q, c) - return qc - - -def w_state(n: int, include_measurements: bool = True): - q = QuantumRegister(n, "q") - c = ClassicalRegister(n, "c") - qc = QuantumCircuit(q, c, name="w_state") - - def f_gate(qc: QuantumCircuit, q: QuantumRegister, i: int, j: int, n: int, k: int): - theta = np.arccos(np.sqrt(1 / (n - k + 1))) - qc.ry(-theta, q[j]) - qc.cz(q[i], q[j]) - qc.ry(theta, q[j]) - - qc.x(q[-1]) - for p in range(1, n): - f_gate(qc, q, n - p, n - p - 1, n, p) - for p in reversed(range(1, n)): - qc.cx(p - 1, p) - - if include_measurements: - measure(qc, q, c) - - return qc - - -def shor(n: int, a: int = 2, include_measurements: bool = True): - from qiskit.algorithms.factorizers import Shor - - qc = Shor().construct_circuit(n, a, include_measurements) - qc.name = "shor_" + str(n) + "_" + str(a) - return qc - - -def qpe_exact(n: int, include_measurements: bool = True): - import random - from fractions import Fraction - - q = QuantumRegister(n, "q") - psi = QuantumRegister(1, "psi") - c = ClassicalRegister(n + 1, "c") - qc = QuantumCircuit(q, psi, c, name="qpe_exact") - - # get random n-bit string as target phase - theta = 0 - while theta == 0: - theta = random.getrandbits(n) - lam = Fraction(0, 1) - # print("theta : ", theta, "correspond to", theta / (1 << n), "bin: ") - for i in range(n): - if theta & (1 << (n - i - 1)): - lam += Fraction(1, (1 << i)) - - qc.x(psi) - qc.h(q) - - for i in range(n): - angle = (lam * (1 << i)) % 2 - if angle > 1: - angle -= 2 - if angle != 0: - qc.cp(angle * np.pi, psi, q[i]) - - qc.compose(QFT(num_qubits=n, inverse=True), inplace=True, qubits=list(range(n))) - - if include_measurements: - measure(qc, q, c) - - return qc - - -def qpe_inexact(n: int, include_measurements: bool = True): - import random - from fractions import Fraction - - q = QuantumRegister(n, "q") - psi = QuantumRegister(1, "psi") - c = ClassicalRegister(n + 1, "c") - qc = QuantumCircuit(q, psi, c, name="qpe_inexact") - - # get random n+1-bit string as target phase - theta = 0 - while theta == 0 or (theta & 1) == 0: - theta = random.getrandbits(n + 1) - lam = Fraction(0, 1) - # print("theta : ", theta, "correspond to", theta / (1 << (n+1)), "bin: ") - for i in range(n + 1): - if theta & (1 << (n - i)): - lam += Fraction(1, (1 << i)) - - qc.x(psi) - qc.h(q) - - for i in range(n): - angle = (lam * (1 << i)) % 2 - if angle > 1: - angle -= 2 - if angle != 0: - qc.cp(angle * np.pi, psi, q[i]) - - qc.compose(QFT(num_qubits=n, inverse=True), inplace=True, qubits=list(range(n))) - - if include_measurements: - measure(qc, q, c) - - return qc diff --git a/test/python/primitives/test_sampler.py b/test/python/primitives/test_sampler.py index f920428e..365d7617 100644 --- a/test/python/primitives/test_sampler.py +++ b/test/python/primitives/test_sampler.py @@ -86,7 +86,11 @@ def test_sample_run_multiple_circuits(circuits: list[QuantumCircuit], sampler: S """Test Sampler.run() with multiple circuits.""" bell_1 = circuits[0] bell_2 = circuits[1] - target = [{0: 0.5, 1: 0, 2: 0, 3: 0.5}, {0: 0, 1: 0.5, 2: 0.5, 3: 0}, {0: 0.5, 1: 0, 2: 0, 3: 0.5}] + target = [ + {0: 0.5, 1: 0, 2: 0, 3: 0.5}, + {0: 0, 1: 0.5, 2: 0.5, 3: 0}, + {0: 0.5, 1: 0, 2: 0, 3: 0.5}, + ] result = sampler.run([bell_1, bell_2, bell_1], shots=shots).result() compare_probs(result.quasi_dists, target) @@ -150,12 +154,12 @@ def test_sequential_run(circuits: list[QuantumCircuit], sampler: Sampler, shots: # First run result = sampler.run(qc_1, parameter_values[0], shots=shots).result() compare_probs(result.quasi_dists[0].binary_probabilities(), target[0]) - number_stored_circuits_first_run = len(sampler.circuits) + number_stored_circuits_first_run = sampler.num_circuits # Second run result = sampler.run([qc_2, qc_1], [parameter_values[2], parameter_values[1]], shots=shots).result() compare_probs(result.quasi_dists[0].binary_probabilities(), target[2]) compare_probs(result.quasi_dists[1].binary_probabilities(), target[1]) - number_stored_circuits_second_run = len(sampler.circuits) + number_stored_circuits_second_run = sampler.num_circuits assert number_stored_circuits_second_run == number_stored_circuits_first_run + 1