qoro-divi 0.3.4__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of qoro-divi might be problematic. Click here for more details.
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/PKG-INFO +16 -1
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/README.md +14 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/backends/__init__.py +1 -1
- qoro_divi-0.4.0/divi/backends/_circuit_runner.py +67 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/backends/_parallel_simulator.py +145 -49
- qoro_divi-0.4.0/divi/backends/_qoro_service.py +693 -0
- qoro_divi-0.4.0/divi/backends/_qpu_system.py +94 -0
- qoro_divi-0.4.0/divi/circuits/_core.py +245 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/circuits/qasm.py +20 -3
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/cirq/_validator.py +12 -3
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/qprog/__init__.py +1 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/qprog/algorithms/_ansatze.py +112 -12
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/qprog/algorithms/_qaoa.py +179 -110
- qoro_divi-0.4.0/divi/qprog/algorithms/_vqe.py +320 -0
- qoro_divi-0.4.0/divi/qprog/batch.py +507 -0
- qoro_divi-0.4.0/divi/qprog/exceptions.py +9 -0
- qoro_divi-0.4.0/divi/qprog/optimizers.py +485 -0
- qoro_divi-0.4.0/divi/qprog/quantum_program.py +257 -0
- qoro_divi-0.4.0/divi/qprog/variational_quantum_algorithm.py +786 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/qprog/workflows/_graph_partitioning.py +43 -38
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/qprog/workflows/_qubo_partitioning.py +41 -24
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/qprog/workflows/_vqe_sweep.py +67 -39
- qoro_divi-0.4.0/divi/reporting/_pbar.py +112 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/reporting/_qlogger.py +35 -1
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/reporting/_reporter.py +11 -20
- qoro_divi-0.4.0/divi/utils.py +220 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/pyproject.toml +6 -1
- qoro_divi-0.3.4/divi/backends/_circuit_runner.py +0 -25
- qoro_divi-0.3.4/divi/backends/_qoro_service.py +0 -424
- qoro_divi-0.3.4/divi/backends/_qpu_system.py +0 -20
- qoro_divi-0.3.4/divi/circuits/_core.py +0 -125
- qoro_divi-0.3.4/divi/qprog/algorithms/_vqe.py +0 -186
- qoro_divi-0.3.4/divi/qprog/batch.py +0 -288
- qoro_divi-0.3.4/divi/qprog/optimizers.py +0 -200
- qoro_divi-0.3.4/divi/qprog/quantum_program.py +0 -434
- qoro_divi-0.3.4/divi/reporting/_pbar.py +0 -70
- qoro_divi-0.3.4/divi/utils.py +0 -124
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/LICENSE +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/LICENSES/.license-header +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/LICENSES/Apache-2.0.txt +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/__init__.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/circuits/__init__.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/circuits/qem.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/cirq/__init__.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/cirq/_lexer.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/cirq/_parser.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/cirq/_qasm_export.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/cirq/_qasm_import.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/cirq/exception.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/_cobyla.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/LICENCE.txt +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/__init__.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/cobyla/__init__.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/cobyla/cobyla.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/cobyla/cobylb.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/cobyla/geometry.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/cobyla/initialize.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/cobyla/trustregion.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/cobyla/update.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/common/__init__.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/common/_bounds.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/common/_linear_constraints.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/common/_nonlinear_constraints.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/common/_project.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/common/checkbreak.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/common/consts.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/common/evaluate.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/common/history.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/common/infos.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/common/linalg.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/common/message.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/common/powalg.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/common/preproc.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/common/present.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/common/ratio.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/common/redrho.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/extern/scipy/pyprima/common/selectx.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/qprog/algorithms/__init__.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/qprog/workflows/__init__.py +0 -0
- {qoro_divi-0.3.4 → qoro_divi-0.4.0}/divi/reporting/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: qoro-divi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A Python library to automate generating, parallelizing, and executing quantum programs.
|
|
5
5
|
Author: Ahmed Darwish
|
|
6
6
|
Author-email: ahmed@qoroquantum.de
|
|
@@ -17,6 +17,7 @@ Requires-Dist: networkx (>=3.5,<4.0)
|
|
|
17
17
|
Requires-Dist: pennylane (>=0.42.3,<0.43.0)
|
|
18
18
|
Requires-Dist: ply (>=3.11,<4.0)
|
|
19
19
|
Requires-Dist: pymetis (>=2025.1.1,<2026.0.0)
|
|
20
|
+
Requires-Dist: pymoo (>=0.6.1.5,<0.7.0.0)
|
|
20
21
|
Requires-Dist: python-dotenv (>=1.1.1,<2.0.0)
|
|
21
22
|
Requires-Dist: qiskit (<2.0)
|
|
22
23
|
Requires-Dist: qiskit-aer (>=0.17.1,<0.18.0)
|
|
@@ -62,3 +63,17 @@ pip install qoro-divi
|
|
|
62
63
|
- Full documentation is available at: <https://docs.qoroquantum.net/divi>
|
|
63
64
|
- Tutorials can be found in the `tutorials` folder.
|
|
64
65
|
|
|
66
|
+
## 🧪 Testing
|
|
67
|
+
|
|
68
|
+
To run the test suite:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Run all tests
|
|
72
|
+
pytest
|
|
73
|
+
|
|
74
|
+
# Run only API tests (requires API token)
|
|
75
|
+
pytest --run-api-tests
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Note:** Some tests require a Qoro API token to test the cloud REST API. Set the `QORO_API_KEY` environment variable or use the `--api-token` option. For local development, you can create a `.env`.
|
|
79
|
+
|
|
@@ -30,3 +30,17 @@ pip install qoro-divi
|
|
|
30
30
|
|
|
31
31
|
- Full documentation is available at: <https://docs.qoroquantum.net/divi>
|
|
32
32
|
- Tutorials can be found in the `tutorials` folder.
|
|
33
|
+
|
|
34
|
+
## 🧪 Testing
|
|
35
|
+
|
|
36
|
+
To run the test suite:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Run all tests
|
|
40
|
+
pytest
|
|
41
|
+
|
|
42
|
+
# Run only API tests (requires API token)
|
|
43
|
+
pytest --run-api-tests
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Note:** Some tests require a Qoro API token to test the cloud REST API. Set the `QORO_API_KEY` environment variable or use the `--api-token` option. For local development, you can create a `.env`.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CircuitRunner(ABC):
|
|
9
|
+
"""
|
|
10
|
+
A generic interface for anything that can "run" quantum circuits.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, shots: int):
|
|
14
|
+
if shots <= 0:
|
|
15
|
+
raise ValueError(f"Shots must be a positive integer. Got {shots}.")
|
|
16
|
+
|
|
17
|
+
self._shots = shots
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def shots(self):
|
|
21
|
+
"""
|
|
22
|
+
Get the number of measurement shots for circuit execution.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
int: Number of shots configured for this runner.
|
|
26
|
+
"""
|
|
27
|
+
return self._shots
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def supports_expval(self) -> bool:
|
|
32
|
+
"""
|
|
33
|
+
Whether the backend supports expectation value measurements.
|
|
34
|
+
"""
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def is_async(self) -> bool:
|
|
40
|
+
"""
|
|
41
|
+
Whether the backend executes circuits asynchronously.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
bool: True if the backend returns a job ID and requires polling
|
|
45
|
+
for results (e.g., QoroService). False if the backend
|
|
46
|
+
returns results immediately (e.g., ParallelSimulator).
|
|
47
|
+
"""
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def submit_circuits(self, circuits: dict[str, str], **kwargs):
|
|
52
|
+
"""
|
|
53
|
+
Submit quantum circuits for execution.
|
|
54
|
+
|
|
55
|
+
This abstract method must be implemented by subclasses to define how
|
|
56
|
+
circuits are executed on their respective backends (simulator, hardware, etc.).
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
circuits (dict[str, str]): Dictionary mapping circuit labels to their
|
|
60
|
+
OpenQASM string representations.
|
|
61
|
+
**kwargs: Additional backend-specific parameters for circuit execution.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
The return type depends on the backend implementation. Typically returns
|
|
65
|
+
measurement results or a job identifier.
|
|
66
|
+
"""
|
|
67
|
+
pass
|
|
@@ -66,12 +66,15 @@ class ParallelSimulator(CircuitRunner):
|
|
|
66
66
|
simulation_seed: int | None = None,
|
|
67
67
|
qiskit_backend: Backend | Literal["auto"] | None = None,
|
|
68
68
|
noise_model: NoiseModel | None = None,
|
|
69
|
+
_deterministic_execution: bool = False,
|
|
69
70
|
):
|
|
70
71
|
"""
|
|
71
|
-
A
|
|
72
|
+
A parallel wrapper around Qiskit's AerSimulator using Qiskit's built-in parallelism.
|
|
72
73
|
|
|
73
74
|
Args:
|
|
74
|
-
n_processes (int, optional): Number of parallel processes to use for
|
|
75
|
+
n_processes (int, optional): Number of parallel processes to use for transpilation and
|
|
76
|
+
simulation. Defaults to 2. This sets both the transpile num_processes and
|
|
77
|
+
AerSimulator's max_parallel_experiments.
|
|
75
78
|
shots (int, optional): Number of shots to perform. Defaults to 5000.
|
|
76
79
|
simulation_seed (int, optional): Seed for the random number generator to ensure reproducibility. Defaults to None.
|
|
77
80
|
qiskit_backend (Backend | Literal["auto"] | None, optional): A Qiskit backend to initiate the simulator from.
|
|
@@ -92,62 +95,152 @@ class ParallelSimulator(CircuitRunner):
|
|
|
92
95
|
self.simulation_seed = simulation_seed
|
|
93
96
|
self.qiskit_backend = qiskit_backend
|
|
94
97
|
self.noise_model = noise_model
|
|
98
|
+
self._deterministic_execution = _deterministic_execution
|
|
95
99
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
shots: int,
|
|
100
|
-
simulation_seed: int | None = None,
|
|
101
|
-
qiskit_backend: Backend | None = None,
|
|
102
|
-
noise_model: NoiseModel | None = None,
|
|
103
|
-
):
|
|
104
|
-
circuit_label, circuit = circuit_data
|
|
100
|
+
def set_seed(self, seed: int):
|
|
101
|
+
"""
|
|
102
|
+
Set the random seed for circuit simulation.
|
|
105
103
|
|
|
106
|
-
|
|
104
|
+
Args:
|
|
105
|
+
seed (int): Seed value for the random number generator used in simulation.
|
|
106
|
+
"""
|
|
107
|
+
self.simulation_seed = seed
|
|
107
108
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
@property
|
|
110
|
+
def supports_expval(self) -> bool:
|
|
111
|
+
"""
|
|
112
|
+
Whether the backend supports expectation value measurements.
|
|
113
|
+
"""
|
|
114
|
+
return False
|
|
113
115
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
@property
|
|
117
|
+
def is_async(self) -> bool:
|
|
118
|
+
"""
|
|
119
|
+
Whether the backend executes circuits asynchronously.
|
|
120
|
+
"""
|
|
121
|
+
return False
|
|
120
122
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
def _execute_circuits_deterministically(
|
|
124
|
+
self, circuit_labels: list[str], transpiled_circuits: list, resolved_backend
|
|
125
|
+
) -> list[dict]:
|
|
126
|
+
"""
|
|
127
|
+
Execute circuits individually for debugging purposes.
|
|
123
128
|
|
|
124
|
-
|
|
125
|
-
|
|
129
|
+
This method ensures deterministic results by running each circuit with its own
|
|
130
|
+
simulator instance and the same seed. Used internally for debugging non-deterministic
|
|
131
|
+
behavior in batch execution.
|
|
126
132
|
|
|
127
|
-
|
|
133
|
+
Args:
|
|
134
|
+
circuit_labels: List of circuit labels
|
|
135
|
+
transpiled_circuits: List of transpiled QuantumCircuit objects
|
|
136
|
+
resolved_backend: Resolved backend for simulator creation
|
|
128
137
|
|
|
129
|
-
|
|
130
|
-
|
|
138
|
+
Returns:
|
|
139
|
+
List of result dictionaries
|
|
140
|
+
"""
|
|
141
|
+
results = []
|
|
142
|
+
for i, (label, transpiled_circuit) in enumerate(
|
|
143
|
+
zip(circuit_labels, transpiled_circuits)
|
|
144
|
+
):
|
|
145
|
+
# Create a new simulator instance for each circuit with the same seed
|
|
146
|
+
if resolved_backend is not None:
|
|
147
|
+
circuit_simulator = AerSimulator.from_backend(resolved_backend)
|
|
148
|
+
else:
|
|
149
|
+
circuit_simulator = AerSimulator(noise_model=self.noise_model)
|
|
150
|
+
|
|
151
|
+
if self.simulation_seed is not None:
|
|
152
|
+
circuit_simulator.set_option("seed_simulator", self.simulation_seed)
|
|
153
|
+
|
|
154
|
+
# Run the single circuit
|
|
155
|
+
job = circuit_simulator.run(transpiled_circuit, shots=self.shots)
|
|
156
|
+
circuit_result = job.result()
|
|
157
|
+
counts = circuit_result.get_counts(0)
|
|
158
|
+
results.append({"label": label, "results": dict(counts)})
|
|
159
|
+
|
|
160
|
+
return results
|
|
131
161
|
|
|
132
162
|
def submit_circuits(self, circuits: dict[str, str]):
|
|
163
|
+
"""
|
|
164
|
+
Submit multiple circuits for parallel simulation using Qiskit's built-in parallelism.
|
|
165
|
+
|
|
166
|
+
Uses Qiskit's native batch transpilation and execution, which handles parallelism
|
|
167
|
+
internally.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
circuits (dict[str, str]): Dictionary mapping circuit labels to OpenQASM
|
|
171
|
+
string representations.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
list[dict]: List of result dictionaries, each containing:
|
|
175
|
+
- 'label' (str): Circuit identifier
|
|
176
|
+
- 'results' (dict): Measurement counts as {bitstring: count}
|
|
177
|
+
"""
|
|
133
178
|
logger.debug(
|
|
134
179
|
f"Simulating {len(circuits)} circuits with {self.n_processes} processes"
|
|
135
180
|
)
|
|
136
181
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
182
|
+
# Convert QASM strings to QuantumCircuit objects
|
|
183
|
+
circuit_labels = list(circuits.keys())
|
|
184
|
+
qiskit_circuits = [
|
|
185
|
+
QuantumCircuit.from_qasm_str(qasm) for qasm in circuits.values()
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
# Determine backend for transpilation
|
|
189
|
+
if self.qiskit_backend == "auto":
|
|
190
|
+
# For "auto", find the maximum number of qubits across all circuits to determine backend
|
|
191
|
+
max_qubits_circ = max(qiskit_circuits, key=lambda x: x.num_qubits)
|
|
192
|
+
resolved_backend = _find_best_fake_backend(max_qubits_circ)[-1]()
|
|
193
|
+
elif self.qiskit_backend is not None:
|
|
194
|
+
resolved_backend = self.qiskit_backend
|
|
195
|
+
else:
|
|
196
|
+
resolved_backend = None
|
|
197
|
+
|
|
198
|
+
# Create simulator
|
|
199
|
+
if resolved_backend is not None:
|
|
200
|
+
aer_simulator = AerSimulator.from_backend(resolved_backend)
|
|
201
|
+
else:
|
|
202
|
+
aer_simulator = AerSimulator(noise_model=self.noise_model)
|
|
203
|
+
|
|
204
|
+
# Set simulator options for parallelism
|
|
205
|
+
# Note: We don't set seed_simulator here because we need different seeds for each circuit
|
|
206
|
+
# to ensure deterministic results when running multiple circuits in parallel
|
|
207
|
+
aer_simulator.set_options(max_parallel_experiments=self.n_processes)
|
|
208
|
+
|
|
209
|
+
# Batch transpile all circuits (Qiskit handles parallelism internally)
|
|
210
|
+
transpiled_circuits = transpile(
|
|
211
|
+
qiskit_circuits, aer_simulator, num_processes=self.n_processes
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Use deterministic execution for debugging if enabled
|
|
215
|
+
if self._deterministic_execution:
|
|
216
|
+
return self._execute_circuits_deterministically(
|
|
217
|
+
circuit_labels, transpiled_circuits, resolved_backend
|
|
150
218
|
)
|
|
219
|
+
|
|
220
|
+
# Batch execution with metadata checking for non-deterministic behavior
|
|
221
|
+
job = aer_simulator.run(transpiled_circuits, shots=self.shots)
|
|
222
|
+
batch_result = job.result()
|
|
223
|
+
|
|
224
|
+
# Check metadata to detect non-deterministic behavior
|
|
225
|
+
metadata = batch_result.metadata
|
|
226
|
+
parallel_experiments = metadata.get("parallel_experiments", 1)
|
|
227
|
+
omp_nested = metadata.get("omp_nested", False)
|
|
228
|
+
|
|
229
|
+
# If parallel execution is detected and we have a seed, warn about potential non-determinism
|
|
230
|
+
if parallel_experiments > 1 and self.simulation_seed is not None:
|
|
231
|
+
logger.warning(
|
|
232
|
+
f"Parallel execution detected (parallel_experiments={parallel_experiments}, "
|
|
233
|
+
f"omp_nested={omp_nested}). Results may not be deterministic across different "
|
|
234
|
+
"grouping strategies. Consider enabling deterministic mode for "
|
|
235
|
+
"deterministic results."
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Extract results and match with labels
|
|
239
|
+
results = []
|
|
240
|
+
for i, label in enumerate(circuit_labels):
|
|
241
|
+
counts = batch_result.get_counts(i)
|
|
242
|
+
results.append({"label": label, "results": dict(counts)})
|
|
243
|
+
|
|
151
244
|
return results
|
|
152
245
|
|
|
153
246
|
@staticmethod
|
|
@@ -187,15 +280,18 @@ class ParallelSimulator(CircuitRunner):
|
|
|
187
280
|
|
|
188
281
|
op_name = node.name
|
|
189
282
|
|
|
283
|
+
# Determine qubit indices for the operation
|
|
190
284
|
if node.num_clbits == 1:
|
|
191
285
|
idx = (node.cargs[0]._index,)
|
|
192
|
-
|
|
193
|
-
if op_name != "measure" and node.num_qubits > 0:
|
|
286
|
+
elif op_name != "measure" and node.num_qubits > 0:
|
|
194
287
|
idx = tuple(qarg._index for qarg in node.qargs)
|
|
288
|
+
else:
|
|
289
|
+
# Skip operations without qubits or measurements without classical bits
|
|
290
|
+
continue
|
|
195
291
|
|
|
196
292
|
try:
|
|
197
293
|
total_run_time_s += (
|
|
198
|
-
|
|
294
|
+
resolved_backend.instruction_durations.duration_by_name_qubits[
|
|
199
295
|
(op_name, idx)
|
|
200
296
|
][0]
|
|
201
297
|
)
|
|
@@ -209,7 +305,7 @@ class ParallelSimulator(CircuitRunner):
|
|
|
209
305
|
@staticmethod
|
|
210
306
|
def estimate_run_time_batch(
|
|
211
307
|
circuits: list[str] | None = None,
|
|
212
|
-
|
|
308
|
+
precomputed_durations: list[float] | None = None,
|
|
213
309
|
n_qpus: int = 5,
|
|
214
310
|
**transpilation_kwargs,
|
|
215
311
|
) -> float:
|
|
@@ -226,7 +322,7 @@ class ParallelSimulator(CircuitRunner):
|
|
|
226
322
|
"""
|
|
227
323
|
|
|
228
324
|
# Compute the run time estimates for each given circuit, in descending order
|
|
229
|
-
if
|
|
325
|
+
if precomputed_durations is None:
|
|
230
326
|
with Pool() as p:
|
|
231
327
|
estimated_run_times = p.map(
|
|
232
328
|
partial(
|
|
@@ -238,7 +334,7 @@ class ParallelSimulator(CircuitRunner):
|
|
|
238
334
|
)
|
|
239
335
|
estimated_run_times_sorted = sorted(estimated_run_times, reverse=True)
|
|
240
336
|
else:
|
|
241
|
-
estimated_run_times_sorted = sorted(
|
|
337
|
+
estimated_run_times_sorted = sorted(precomputed_durations, reverse=True)
|
|
242
338
|
|
|
243
339
|
# Just return the longest run time if there are enough QPUs
|
|
244
340
|
if n_qpus >= len(estimated_run_times_sorted):
|