Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved support of parametrized gates in DDSIM Backends #293

Merged
merged 16 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions src/mqt/ddsim/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import functools
from concurrent import futures
from typing import TYPE_CHECKING, Any, Callable
from typing import TYPE_CHECKING, Any, Callable, Sequence

from qiskit.providers import JobError, JobStatus, JobV1

Expand Down Expand Up @@ -42,11 +42,18 @@ class DDSIMJob(JobV1):
_executor = futures.ThreadPoolExecutor(max_workers=1)

def __init__(
self, backend: BackendV2, job_id: str, fn: Callable, experiments: list[QuantumCircuit], **args: dict[str, Any]
self,
backend: BackendV2,
job_id: str,
fn: Callable,
experiments: list[QuantumCircuit],
parameter_values: Sequence[Sequence[float]] | None,
**args: dict[str, Any],
) -> None:
super().__init__(backend, job_id)
self._fn = fn
self._experiments = experiments
self._parameter_values = parameter_values
self._args = args
self._future: futures.Future | None = None

Expand All @@ -60,7 +67,9 @@ def submit(self) -> None:
msg = "Job was already submitted!"
raise JobError(msg)

self._future = self._executor.submit(self._fn, self._job_id, self._experiments, **self._args)
self._future = self._executor.submit(
self._fn, self._job_id, self._experiments, self._parameter_values, **self._args
)

@requires_submit
def result(self, timeout: float | None = None):
Expand Down
46 changes: 41 additions & 5 deletions src/mqt/ddsim/qasmsimulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import time
import uuid
from math import log2
from typing import Any
from typing import Any, Sequence

from qiskit import QuantumCircuit
from qiskit.providers import BackendV2, Options
Expand Down Expand Up @@ -52,6 +52,7 @@ def _initialize_target(self) -> None:

def __init__(self, name="qasm_simulator", description="MQT DDSIM QASM Simulator") -> None:
super().__init__(name=name, description=description, backend_version=__version__)
self._simulated_circuits: list[QuantumCircuit] = []
andresbar98 marked this conversation as resolved.
Show resolved Hide resolved
self._initialize_target()

@classmethod
Expand All @@ -73,22 +74,57 @@ def target(self):
def max_circuits(self):
return None

def run(self, quantum_circuits: QuantumCircuit | list[QuantumCircuit], **options: dict[str, Any]) -> DDSIMJob:
@staticmethod
def _bind_parameters(
quantum_circuits: list[QuantumCircuit], parameter_values: Sequence[Sequence[float]] | None = None
andresbar98 marked this conversation as resolved.
Show resolved Hide resolved
andresbar98 marked this conversation as resolved.
Show resolved Hide resolved
) -> list[QuantumCircuit]:
if parameter_values is None:
parameter_values = []
burgholzer marked this conversation as resolved.
Show resolved Hide resolved

bound_circuits = [
qc.bind_parameters(dict(zip(qc.parameters, values)))
for qc, values in zip(quantum_circuits, parameter_values)
]

if len(parameter_values) < len(quantum_circuits):
bound_circuits.extend(quantum_circuits[len(parameter_values) : len(quantum_circuits)])
burgholzer marked this conversation as resolved.
Show resolved Hide resolved

# Preserves circuit's names
andresbar98 marked this conversation as resolved.
Show resolved Hide resolved
for qc_bound, qc_unbound in zip(bound_circuits, quantum_circuits):
qc_bound.name = qc_unbound.name

return bound_circuits

def run(
self,
quantum_circuits: QuantumCircuit | list[QuantumCircuit],
parameter_values: Sequence[Sequence[float]] | None = None,
**options,
) -> DDSIMJob:
if isinstance(quantum_circuits, QuantumCircuit):
quantum_circuits = [quantum_circuits]

job_id = str(uuid.uuid4())
local_job = DDSIMJob(self, job_id, self._run_job, quantum_circuits, **options)
local_job = DDSIMJob(self, job_id, self._run_job, quantum_circuits, parameter_values, **options)
local_job.submit()
return local_job

def _validate(self, quantum_circuits: list[QuantumCircuit]) -> None:
pass

def _run_job(self, job_id: int, quantum_circuits: list[QuantumCircuit], **options: dict[str, Any]) -> Result:
def _run_job(
self,
job_id: int,
quantum_circuits: list[QuantumCircuit],
parameter_values: Sequence[Sequence[float]] | None,
**options: dict[str, Any],
) -> Result:
self._validate(quantum_circuits)
start = time.time()
result_list = [self._run_experiment(q_circ, **options) for q_circ in quantum_circuits]

bound_circuits = self._bind_parameters(quantum_circuits, parameter_values)
result_list = [self._run_experiment(q_circ, **options) for q_circ in bound_circuits]

end = time.time()

return Result(
Expand Down
8 changes: 4 additions & 4 deletions src/mqt/ddsim/unitarysimulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
from qiskit.result.models import ExperimentResult, ExperimentResultData
from qiskit.transpiler import Target

from mqt.ddsim.header import DDSIMHeader
from mqt.ddsim.pyddsim import ConstructionMode, UnitarySimulator, get_matrix
from mqt.ddsim.qasmsimulator import QasmSimulatorBackend
from mqt.ddsim.target import DDSIMTargetBuilder
from .header import DDSIMHeader
from .pyddsim import ConstructionMode, UnitarySimulator, get_matrix
from .qasmsimulator import QasmSimulatorBackend
from .target import DDSIMTargetBuilder

if TYPE_CHECKING:
from qiskit import QuantumCircuit
Expand Down
40 changes: 40 additions & 0 deletions test/python/simulator/test_qasm_simulator.py
andresbar98 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import numpy as np
import pytest
from qiskit import AncillaRegister, ClassicalRegister, QuantumCircuit, QuantumRegister, execute
from qiskit.circuit import Parameter

from mqt.ddsim.qasmsimulator import QasmSimulatorBackend

Expand Down Expand Up @@ -69,6 +70,45 @@ def test_qasm_simulator(circuit: QuantumCircuit, backend: QasmSimulatorBackend,
assert abs(target[key] - counts[key]) < threshold


def test_qasm_simulator_support_parametrized_gates(backend: QasmSimulatorBackend, shots: int):
"""Test backend's adequate support of parametrized gates"""

theta_a = Parameter("theta_a")
theta_b = Parameter("theta_b")
theta_c = Parameter("theta_c")
circuit_1 = QuantumCircuit(2)
circuit_2 = QuantumCircuit(2)
circuit_1.ry(theta_a, 0)
circuit_1.rx(theta_b, 1)
circuit_2.rx(theta_c, 0)

# Test backend's correct functionality with multiple circuit
result = backend.run([circuit_1, circuit_2], [[np.pi / 2, np.pi / 2], [np.pi / 4]], shots=shots).result()
assert result.success

threshold = 0.04 * shots
average = shots / 4
counts_1 = result.get_counts(circuit_1.name)
counts_2 = result.get_counts(circuit_2.name)
target_1 = {
"0": average,
"11": average,
"1": average,
}
target_2 = {
"0": shots * (np.cos(np.pi / 8)) ** 2,
"1": shots * (np.sin(np.pi / 8)) ** 2,
}

for key in target_1:
assert key in counts_1
assert abs(target_1[key] - counts_1[key]) < threshold

for key in target_2:
assert key in counts_2
assert abs(target_2[key] - counts_2[key]) < threshold


def test_qasm_simulator_approximation(backend: QasmSimulatorBackend, shots: int):
"""Test data counts output for single circuit run against reference."""
circuit = QuantumCircuit(2)
Expand Down
Loading