Skip to content

[QI2-1362] Raw Data #119

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

Merged
merged 9 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions docs/hybrid/basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ def execute(qi: QuantumInterface) -> None:
qi.execute_circuit args:
circuit: a string representation of a quantum circuit
number_of_shots: how often to execute the circuit
raw_data_enabled (default: False): report measurement per shot (if supported by backend type)

qi.execute_circuit return value:
The results of executing the quantum circuit, this is an object with the following attributes
results: The results from iteration n-1.
raw_data: Measurement per shot as a list of strings (or None if disabled).
shots_requested: The number of shots requested by the user for the previous iteration.
shots_done: The number of shots actually run.
"""
Expand All @@ -34,6 +36,8 @@ def execute(qi: QuantumInterface) -> None:
_ = qi.execute_circuit(circuit, 1024)
```

Refer to [this example](./hqca_circuit.py) for some extra ways to interact with the `QuantumInterface`.

### Finalize

The `finalize()` function allows you to aggregate your results. It should return a dictionary which will be stored as the final result. By default it takes a list of all measurements as an argument, but through the use of globals you could also export other data.
Expand Down
24 changes: 20 additions & 4 deletions docs/hybrid/hqca_circuit.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
from typing import Any, Dict, List

from qi2_shared.hybrid.quantum_interface import QuantumInterface
from qiskit.circuit import QuantumCircuit

from qiskit_quantuminspire import cqasm
from qiskit_quantuminspire.hybrid.quantum_interface import QuantumInterface
from qiskit_quantuminspire.hybrid.hybrid_backend import QIHybridBackend


def generate_circuit() -> str:
def generate_circuit() -> QuantumCircuit:
# Create a basic Bell State circuit:
qc = QuantumCircuit(2, 2)
qc.reset(0)
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])

return cqasm.dumps(qc)
return qc


def execute(qi: QuantumInterface) -> None:
Expand All @@ -28,16 +29,31 @@ def execute(qi: QuantumInterface) -> None:
qi.execute_circuit args:
circuit: a string representation of a quantum circuit
number_of_shots: how often to execute the circuit
raw_data_enabled (default: False): report measurement per shot (if supported by backend type)

qi.execute_circuit return value:
The results of executing the quantum circuit, this is an object with the following attributes
results: The results from iteration n-1.
raw_data: Measurement per shot as a list of strings (or None if disabled).
shots_requested: The number of shots requested by the user for the previous iteration.
shots_done: The number of shots actually run.
"""
execution_method: str = "simple"
backend = QIHybridBackend(qi)

for i in range(1, 5):
circuit = generate_circuit()
_ = qi.execute_circuit(circuit, 1024)

if execution_method == "simple":
result = qi.execute_circuit(cqasm.dumps(circuit), 1024)
elif execution_method == "include_raw_data":
# To include measurement results per shot (raw_data):
result = qi.execute_circuit(cqasm.dumps(circuit), 1024, raw_data_enabled=True)
_ = result.raw_data
elif execution_method == "use_hybrid_backend":
# Note that you can also use the QIHybridBackend to run QuantumCircuits directly, in which case the memory
# flag is used to enable/disable raw data.:
result = backend.run(circuit, shots=50, memory=False).result()


def finalize(list_of_measurements: List[Dict[str, Any]]) -> Dict[str, Any]:
Expand Down
26 changes: 11 additions & 15 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ classifiers = [
[tool.poetry.dependencies]
python = "^3.9"
qiskit = "^1.2.0"
qi-compute-api-client = "^0.44.0"
qi-compute-api-client = "^0.46.0"
pydantic = "^2.10.6"
requests = "^2.32.3"
opensquirrel = "0.2.0"
Expand Down
3 changes: 2 additions & 1 deletion qiskit_quantuminspire/hybrid/hybrid_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def status(self) -> BackendStatus:
)

def run(self, run_input: Union[QuantumCircuit, List[QuantumCircuit]], **options: Any) -> QIHybridJob:
job = QIHybridJob(run_input=run_input, backend=self, quantum_interface=self._quantum_interface, **options)
self.set_options(**options)
job = QIHybridJob(run_input=run_input, backend=self, quantum_interface=self._quantum_interface)
job.submit()
return job
6 changes: 4 additions & 2 deletions qiskit_quantuminspire/hybrid/hybrid_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ def submit(self) -> None:

for circuit_data in self.circuits_run_data:
transpiled_circuit = transpile(circuit_data.circuit, backend=self.backend())
result = self._quantum_interface.execute_circuit(cqasm.dumps(transpiled_circuit), options["shots"])
result = self._quantum_interface.execute_circuit(
cqasm.dumps(transpiled_circuit), options.get("shots"), raw_data_enabled=options.get("memory")
)

# Store retrieved results in format expected by Job
circuit_data.results = RawJobResult(
Expand All @@ -50,7 +52,7 @@ def submit(self) -> None:
shots_requested=result.shots_requested,
shots_done=result.shots_done,
results=result.results,
raw_data=None,
raw_data=result.raw_data,
)

@cache
Expand Down
3 changes: 2 additions & 1 deletion tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def create_backend_type(
max_number_of_shots: int = 2048,
name: str = "qi_backend",
id: int = 1,
supports_raw_data: bool = True,
) -> BackendType:
"""Helper for creating a backendtype with only the fields you care about."""
return BackendType(
Expand All @@ -30,7 +31,7 @@ def create_backend_type(
infrastructure="QCI",
description="A Quantum Inspire backend",
native_gateset="",
supports_raw_data=False,
supports_raw_data=supports_raw_data,
)


Expand Down
29 changes: 29 additions & 0 deletions tests/hybrid/test_hybrid_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@

import pytest
from pytest_mock import MockerFixture
from qi2_shared.hybrid.quantum_interface import ExecuteCircuitResult
from qiskit import QuantumCircuit
from qiskit.providers.models.backendstatus import BackendStatus
from qiskit.result.models import ExperimentResultData

from qiskit_quantuminspire import cqasm
from qiskit_quantuminspire.hybrid.hybrid_backend import QIHybridBackend


Expand All @@ -27,3 +31,28 @@ def test_submit(quantum_interface: MagicMock, qi_hybrid_job_mock: MagicMock) ->
backend = QIHybridBackend(quantum_interface)
backend.run([])
qi_hybrid_job_mock.submit.assert_called_once()


def test_raw_data(quantum_interface: MagicMock) -> None:
# Arrange
backend = QIHybridBackend(quantum_interface)
circuit = QuantumCircuit(2, 2)
quantum_interface.execute_circuit.return_value = ExecuteCircuitResult(
shots_requested=5,
shots_done=4,
results={
"00": 1,
"01": 1,
"10": 2,
},
raw_data=["00", "10", "10", "01"],
)

# Act
results = backend.run(circuit, shots=5, memory=True).result()

# Assert
quantum_interface.execute_circuit.assert_called_once_with(cqasm.dumps(circuit), 5, raw_data_enabled=True)
experiment_data = ExperimentResultData(counts={"0x0": 1, "0x1": 1, "0x2": 2}, memory=["0x0", "0x2", "0x2", "0x1"])

assert results.data(circuit) == experiment_data.to_dict()
1 change: 1 addition & 0 deletions tests/hybrid/test_hybrid_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def test_submit(quantum_interface: MagicMock) -> None:
"10": 256,
"11": 256,
},
raw_data=None,
)
job = QIHybridJob(run_input=[circuit], backend=backend, quantum_interface=quantum_interface)
job.submit()
Expand Down
4 changes: 3 additions & 1 deletion tests/test_qi_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ def qi_job_mock(mocker: MockerFixture) -> Generator[MagicMock, None, None]:
def qi_backend_factory(mocker: MockerFixture) -> Callable[..., QIBackend]:
def _generate_qi_backend(params: Optional[Dict[Any, Any]] = None) -> QIBackend:
params = params or {}
backend_type = params.pop("backend_type", create_backend_type(max_number_of_shots=4096))
backend_type = params.pop(
"backend_type", create_backend_type(max_number_of_shots=4096, supports_raw_data=False)
)
is_online = params.pop("backend_online", True)
status = BackendStatus.IDLE if is_online else BackendStatus.OFFLINE

Expand Down
Loading