qoro-divi 0.3.3__py3-none-any.whl → 0.3.5__py3-none-any.whl
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.
- divi/__init__.py +1 -2
- divi/backends/__init__.py +7 -0
- divi/backends/_circuit_runner.py +46 -0
- divi/{parallel_simulator.py → backends/_parallel_simulator.py} +136 -53
- divi/backends/_qoro_service.py +531 -0
- divi/circuits/__init__.py +5 -0
- divi/circuits/_core.py +226 -0
- divi/{qasm.py → circuits/qasm.py} +21 -2
- divi/{exp → extern}/cirq/_validator.py +9 -7
- divi/qprog/__init__.py +18 -5
- divi/qprog/algorithms/__init__.py +14 -0
- divi/qprog/algorithms/_ansatze.py +311 -0
- divi/qprog/{_qaoa.py → algorithms/_qaoa.py} +69 -41
- divi/qprog/{_vqe.py → algorithms/_vqe.py} +79 -135
- divi/qprog/batch.py +239 -55
- divi/qprog/exceptions.py +9 -0
- divi/qprog/optimizers.py +219 -18
- divi/qprog/quantum_program.py +389 -57
- divi/qprog/workflows/__init__.py +10 -0
- divi/qprog/{_graph_partitioning.py → workflows/_graph_partitioning.py} +3 -34
- divi/qprog/{_qubo_partitioning.py → workflows/_qubo_partitioning.py} +42 -25
- divi/qprog/{_vqe_sweep.py → workflows/_vqe_sweep.py} +59 -26
- divi/reporting/__init__.py +7 -0
- divi/reporting/_pbar.py +112 -0
- divi/{qlogger.py → reporting/_qlogger.py} +37 -2
- divi/{reporter.py → reporting/_reporter.py} +8 -14
- divi/utils.py +49 -10
- {qoro_divi-0.3.3.dist-info → qoro_divi-0.3.5.dist-info}/METADATA +2 -1
- qoro_divi-0.3.5.dist-info/RECORD +69 -0
- divi/_pbar.py +0 -70
- divi/circuits.py +0 -139
- divi/interfaces.py +0 -25
- divi/qoro_service.py +0 -425
- qoro_divi-0.3.3.dist-info/RECORD +0 -62
- /divi/{qpu_system.py → backends/_qpu_system.py} +0 -0
- /divi/{qem.py → circuits/qem.py} +0 -0
- /divi/{exp → extern}/cirq/__init__.py +0 -0
- /divi/{exp → extern}/cirq/_lexer.py +0 -0
- /divi/{exp → extern}/cirq/_parser.py +0 -0
- /divi/{exp → extern}/cirq/_qasm_export.py +0 -0
- /divi/{exp → extern}/cirq/_qasm_import.py +0 -0
- /divi/{exp → extern}/cirq/exception.py +0 -0
- /divi/{exp → extern}/scipy/_cobyla.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/LICENCE.txt +0 -0
- /divi/{exp → extern}/scipy/pyprima/__init__.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/__init__.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/cobyla.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/cobylb.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/geometry.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/initialize.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/trustregion.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/cobyla/update.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/__init__.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/_bounds.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/_linear_constraints.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/_nonlinear_constraints.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/_project.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/checkbreak.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/consts.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/evaluate.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/history.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/infos.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/linalg.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/message.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/powalg.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/preproc.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/present.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/ratio.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/redrho.py +0 -0
- /divi/{exp → extern}/scipy/pyprima/common/selectx.py +0 -0
- {qoro_divi-0.3.3.dist-info → qoro_divi-0.3.5.dist-info}/LICENSE +0 -0
- {qoro_divi-0.3.3.dist-info → qoro_divi-0.3.5.dist-info}/LICENSES/.license-header +0 -0
- {qoro_divi-0.3.3.dist-info → qoro_divi-0.3.5.dist-info}/LICENSES/Apache-2.0.txt +0 -0
- {qoro_divi-0.3.3.dist-info → qoro_divi-0.3.5.dist-info}/WHEEL +0 -0
divi/qprog/batch.py
CHANGED
|
@@ -5,29 +5,25 @@
|
|
|
5
5
|
import atexit
|
|
6
6
|
import traceback
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
|
-
from concurrent.futures import
|
|
9
|
-
from multiprocessing import Event, Manager
|
|
10
|
-
from multiprocessing.synchronize import Event as EventClass
|
|
8
|
+
from concurrent.futures import Future, ThreadPoolExecutor, as_completed
|
|
11
9
|
from queue import Empty, Queue
|
|
12
|
-
from threading import Lock, Thread
|
|
10
|
+
from threading import Event, Lock, Thread
|
|
13
11
|
from typing import Any
|
|
14
12
|
from warnings import warn
|
|
15
13
|
|
|
16
14
|
from rich.console import Console
|
|
17
15
|
from rich.progress import Progress, TaskID
|
|
18
16
|
|
|
19
|
-
from divi.
|
|
20
|
-
from divi.interfaces import CircuitRunner
|
|
21
|
-
from divi.parallel_simulator import ParallelSimulator
|
|
22
|
-
from divi.qlogger import disable_logging
|
|
17
|
+
from divi.backends import CircuitRunner, ParallelSimulator
|
|
23
18
|
from divi.qprog.quantum_program import QuantumProgram
|
|
19
|
+
from divi.reporting import disable_logging, make_progress_bar
|
|
24
20
|
|
|
25
21
|
|
|
26
|
-
def
|
|
22
|
+
def _queue_listener(
|
|
27
23
|
queue: Queue,
|
|
28
24
|
progress_bar: Progress,
|
|
29
25
|
pb_task_map: dict[QuantumProgram, TaskID],
|
|
30
|
-
done_event:
|
|
26
|
+
done_event: Event,
|
|
31
27
|
is_jupyter: bool,
|
|
32
28
|
lock: Lock,
|
|
33
29
|
):
|
|
@@ -62,6 +58,7 @@ def queue_listener(
|
|
|
62
58
|
update_args["refresh"] = is_jupyter
|
|
63
59
|
|
|
64
60
|
progress_bar.update(task_id, **update_args)
|
|
61
|
+
queue.task_done()
|
|
65
62
|
|
|
66
63
|
|
|
67
64
|
def _default_task_function(program: QuantumProgram):
|
|
@@ -92,7 +89,7 @@ class ProgramBatch(ABC):
|
|
|
92
89
|
self.backend = backend
|
|
93
90
|
self._executor = None
|
|
94
91
|
self._task_fn = _default_task_function
|
|
95
|
-
self.
|
|
92
|
+
self._programs = {}
|
|
96
93
|
|
|
97
94
|
self._total_circuit_count = 0
|
|
98
95
|
self._total_run_time = 0.0
|
|
@@ -105,28 +102,67 @@ class ProgramBatch(ABC):
|
|
|
105
102
|
|
|
106
103
|
@property
|
|
107
104
|
def total_circuit_count(self):
|
|
105
|
+
"""
|
|
106
|
+
Get the total number of circuits executed across all programs in the batch.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
int: Cumulative count of circuits submitted by all programs.
|
|
110
|
+
"""
|
|
108
111
|
return self._total_circuit_count
|
|
109
112
|
|
|
110
113
|
@property
|
|
111
114
|
def total_run_time(self):
|
|
115
|
+
"""
|
|
116
|
+
Get the total runtime across all programs in the batch.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
float: Cumulative execution time in seconds across all programs.
|
|
120
|
+
"""
|
|
112
121
|
return self._total_run_time
|
|
113
122
|
|
|
123
|
+
@property
|
|
124
|
+
def programs(self) -> dict:
|
|
125
|
+
"""
|
|
126
|
+
Get a copy of the programs dictionary.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
dict: Copy of the programs dictionary mapping program IDs to
|
|
130
|
+
QuantumProgram instances. Modifications to this dict will not
|
|
131
|
+
affect the internal state.
|
|
132
|
+
"""
|
|
133
|
+
return self._programs.copy()
|
|
134
|
+
|
|
135
|
+
@programs.setter
|
|
136
|
+
def programs(self, value: dict):
|
|
137
|
+
"""Set the programs dictionary."""
|
|
138
|
+
self._programs = value
|
|
139
|
+
|
|
114
140
|
@abstractmethod
|
|
115
141
|
def create_programs(self):
|
|
116
|
-
if len(self.
|
|
142
|
+
if len(self._programs) > 0:
|
|
117
143
|
raise RuntimeError(
|
|
118
144
|
"Some programs already exist. "
|
|
119
145
|
"Clear the program dictionary before creating new ones by using batch.reset()."
|
|
120
146
|
)
|
|
121
147
|
|
|
122
|
-
self.
|
|
123
|
-
self._queue = self._manager.Queue()
|
|
148
|
+
self._queue = Queue()
|
|
124
149
|
|
|
125
150
|
if hasattr(self, "max_iterations"):
|
|
126
151
|
self._done_event = Event()
|
|
127
152
|
|
|
128
153
|
def reset(self):
|
|
129
|
-
|
|
154
|
+
"""
|
|
155
|
+
Reset the batch to its initial state.
|
|
156
|
+
|
|
157
|
+
Clears all programs, stops any running executors, terminates listener threads,
|
|
158
|
+
and stops progress bars. This allows the batch to be reused for a new set of
|
|
159
|
+
programs.
|
|
160
|
+
|
|
161
|
+
Note:
|
|
162
|
+
Any running programs will be forcefully stopped. Results from incomplete
|
|
163
|
+
programs will be lost.
|
|
164
|
+
"""
|
|
165
|
+
self._programs.clear()
|
|
130
166
|
|
|
131
167
|
# Stop any active executor
|
|
132
168
|
if self._executor is not None:
|
|
@@ -145,12 +181,6 @@ class ProgramBatch(ABC):
|
|
|
145
181
|
warn("Listener thread did not terminate within timeout.")
|
|
146
182
|
self._listener_thread = None
|
|
147
183
|
|
|
148
|
-
# Shut down the manager process, which handles the queue cleanup.
|
|
149
|
-
if hasattr(self, "_manager") and self._manager is not None:
|
|
150
|
-
self._manager.shutdown()
|
|
151
|
-
self._manager = None
|
|
152
|
-
self._queue = None
|
|
153
|
-
|
|
154
184
|
# Stop the progress bar if it's still active
|
|
155
185
|
if getattr(self, "_progress_bar", None) is not None:
|
|
156
186
|
try:
|
|
@@ -170,8 +200,21 @@ class ProgramBatch(ABC):
|
|
|
170
200
|
)
|
|
171
201
|
self.reset()
|
|
172
202
|
|
|
173
|
-
def
|
|
174
|
-
|
|
203
|
+
def _add_program_to_executor(self, program: QuantumProgram) -> Future:
|
|
204
|
+
"""
|
|
205
|
+
Add a quantum program to the thread pool executor for execution.
|
|
206
|
+
|
|
207
|
+
Sets up the program with cancellation support and progress tracking, then
|
|
208
|
+
submits it for execution in a separate thread.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
program (QuantumProgram): The quantum program to execute.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Future: A Future object representing the program's execution.
|
|
215
|
+
"""
|
|
216
|
+
if hasattr(program, "_set_cancellation_event"):
|
|
217
|
+
program._set_cancellation_event(self._cancellation_event)
|
|
175
218
|
|
|
176
219
|
if self._progress_bar is not None:
|
|
177
220
|
with self._pb_lock:
|
|
@@ -180,17 +223,39 @@ class ProgramBatch(ABC):
|
|
|
180
223
|
job_name=f"Job {program.job_id}",
|
|
181
224
|
total=self.max_iterations,
|
|
182
225
|
completed=0,
|
|
183
|
-
poll_attempt=0,
|
|
184
226
|
message="",
|
|
185
|
-
final_status="",
|
|
186
227
|
mode=("simulation" if self._is_local else "network"),
|
|
187
228
|
)
|
|
188
229
|
|
|
230
|
+
return self._executor.submit(self._task_fn, program)
|
|
231
|
+
|
|
189
232
|
def run(self, blocking: bool = False):
|
|
233
|
+
"""
|
|
234
|
+
Execute all programs in the batch.
|
|
235
|
+
|
|
236
|
+
Starts all quantum programs in parallel using a thread pool. Can run in
|
|
237
|
+
blocking or non-blocking mode.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
blocking (bool, optional): If True, waits for all programs to complete
|
|
241
|
+
before returning. If False, returns immediately and programs run in
|
|
242
|
+
the background. Defaults to False.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
ProgramBatch: Returns self for method chaining.
|
|
246
|
+
|
|
247
|
+
Raises:
|
|
248
|
+
RuntimeError: If a batch is already running or if no programs have been
|
|
249
|
+
created.
|
|
250
|
+
|
|
251
|
+
Note:
|
|
252
|
+
In non-blocking mode, call `join()` later to wait for completion and
|
|
253
|
+
collect results.
|
|
254
|
+
"""
|
|
190
255
|
if self._executor is not None:
|
|
191
256
|
raise RuntimeError("A batch is already being run.")
|
|
192
257
|
|
|
193
|
-
if len(self.
|
|
258
|
+
if len(self._programs) == 0:
|
|
194
259
|
raise RuntimeError("No programs to run.")
|
|
195
260
|
|
|
196
261
|
self._progress_bar = (
|
|
@@ -199,15 +264,17 @@ class ProgramBatch(ABC):
|
|
|
199
264
|
else None
|
|
200
265
|
)
|
|
201
266
|
|
|
202
|
-
self._executor =
|
|
267
|
+
self._executor = ThreadPoolExecutor()
|
|
268
|
+
self._cancellation_event = Event()
|
|
203
269
|
self.futures = []
|
|
270
|
+
self._future_to_program = {}
|
|
204
271
|
self._pb_task_map = {}
|
|
205
272
|
self._pb_lock = Lock()
|
|
206
273
|
|
|
207
274
|
if self._progress_bar is not None:
|
|
208
275
|
self._progress_bar.start()
|
|
209
276
|
self._listener_thread = Thread(
|
|
210
|
-
target=
|
|
277
|
+
target=_queue_listener,
|
|
211
278
|
args=(
|
|
212
279
|
self._queue,
|
|
213
280
|
self._progress_bar,
|
|
@@ -220,8 +287,10 @@ class ProgramBatch(ABC):
|
|
|
220
287
|
)
|
|
221
288
|
self._listener_thread.start()
|
|
222
289
|
|
|
223
|
-
for program in self.
|
|
224
|
-
self.
|
|
290
|
+
for program in self._programs.values():
|
|
291
|
+
future = self._add_program_to_executor(program)
|
|
292
|
+
self.futures.append(future)
|
|
293
|
+
self._future_to_program[future] = program
|
|
225
294
|
|
|
226
295
|
if not blocking:
|
|
227
296
|
# Arm safety net
|
|
@@ -231,60 +300,175 @@ class ProgramBatch(ABC):
|
|
|
231
300
|
|
|
232
301
|
return self
|
|
233
302
|
|
|
234
|
-
def check_all_done(self):
|
|
303
|
+
def check_all_done(self) -> bool:
|
|
304
|
+
"""
|
|
305
|
+
Check if all programs in the batch have completed execution.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
bool: True if all programs are finished (successfully or with errors),
|
|
309
|
+
False if any are still running.
|
|
310
|
+
"""
|
|
235
311
|
return all(future.done() for future in self.futures)
|
|
236
312
|
|
|
313
|
+
def _collect_completed_results(self, completed_futures: list):
|
|
314
|
+
"""
|
|
315
|
+
Collects results from any futures that have completed successfully.
|
|
316
|
+
Appends (circuit_count, run_time) tuples to the completed_futures list.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
completed_futures: List to append results to
|
|
320
|
+
"""
|
|
321
|
+
for future in self.futures:
|
|
322
|
+
if future.done() and not future.cancelled():
|
|
323
|
+
try:
|
|
324
|
+
completed_futures.append(future.result())
|
|
325
|
+
except Exception:
|
|
326
|
+
pass # Skip failed futures
|
|
327
|
+
|
|
328
|
+
def _handle_cancellation(self):
|
|
329
|
+
"""
|
|
330
|
+
Handles cancellation gracefully, providing accurate feedback by checking
|
|
331
|
+
the result of future.cancel().
|
|
332
|
+
"""
|
|
333
|
+
self._cancellation_event.set()
|
|
334
|
+
|
|
335
|
+
successfully_cancelled = []
|
|
336
|
+
unstoppable_futures = []
|
|
337
|
+
|
|
338
|
+
# --- Phase 1: Attempt to cancel all non-finished tasks ---
|
|
339
|
+
for future, program in self._future_to_program.items():
|
|
340
|
+
if future.done():
|
|
341
|
+
continue
|
|
342
|
+
|
|
343
|
+
task_id = self._pb_task_map.get(program.job_id)
|
|
344
|
+
if self._progress_bar and task_id is not None:
|
|
345
|
+
cancel_result = future.cancel()
|
|
346
|
+
if cancel_result:
|
|
347
|
+
# The task was pending and was successfully cancelled.
|
|
348
|
+
successfully_cancelled.append(program)
|
|
349
|
+
else:
|
|
350
|
+
# The task is already running and cannot be stopped.
|
|
351
|
+
unstoppable_futures.append(future)
|
|
352
|
+
self._progress_bar.update(
|
|
353
|
+
task_id,
|
|
354
|
+
message="Finishing... ⏳",
|
|
355
|
+
refresh=self._is_jupyter,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
# --- Phase 2: Immediately mark the successfully cancelled tasks ---
|
|
359
|
+
for program in successfully_cancelled:
|
|
360
|
+
task_id = self._pb_task_map.get(program.job_id)
|
|
361
|
+
if self._progress_bar and task_id is not None:
|
|
362
|
+
self._progress_bar.update(
|
|
363
|
+
task_id,
|
|
364
|
+
final_status="Cancelled",
|
|
365
|
+
message="Cancelled by user",
|
|
366
|
+
refresh=self._is_jupyter,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
# --- Phase 3: Wait for the unstoppable tasks to finish ---
|
|
370
|
+
if unstoppable_futures:
|
|
371
|
+
for future in as_completed(unstoppable_futures):
|
|
372
|
+
program = self._future_to_program[future]
|
|
373
|
+
task_id = self._pb_task_map.get(program.job_id)
|
|
374
|
+
if self._progress_bar and task_id is not None:
|
|
375
|
+
self._progress_bar.update(
|
|
376
|
+
task_id,
|
|
377
|
+
final_status="Aborted",
|
|
378
|
+
message="Completed during cancellation",
|
|
379
|
+
refresh=self._is_jupyter,
|
|
380
|
+
)
|
|
381
|
+
|
|
237
382
|
def join(self):
|
|
383
|
+
"""
|
|
384
|
+
Wait for all programs in the batch to complete and collect results.
|
|
385
|
+
|
|
386
|
+
Blocks until all programs finish execution, aggregating their circuit counts
|
|
387
|
+
and run times. Handles keyboard interrupts gracefully by attempting to cancel
|
|
388
|
+
remaining programs.
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
bool or None: Returns False if interrupted by KeyboardInterrupt, None otherwise.
|
|
392
|
+
|
|
393
|
+
Raises:
|
|
394
|
+
RuntimeError: If any program fails with an exception, after cancelling
|
|
395
|
+
remaining programs.
|
|
396
|
+
|
|
397
|
+
Note:
|
|
398
|
+
This method should be called after `run(blocking=False)` to wait for
|
|
399
|
+
completion. It's automatically called when using `run(blocking=True)`.
|
|
400
|
+
"""
|
|
238
401
|
if self._executor is None:
|
|
239
402
|
return
|
|
240
403
|
|
|
241
|
-
|
|
404
|
+
completed_futures = []
|
|
242
405
|
try:
|
|
243
|
-
#
|
|
406
|
+
# The as_completed iterator will yield futures as they finish.
|
|
407
|
+
# If a task fails, future.result() will raise the exception immediately.
|
|
244
408
|
for future in as_completed(self.futures):
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
409
|
+
completed_futures.append(future.result())
|
|
410
|
+
|
|
411
|
+
except KeyboardInterrupt:
|
|
412
|
+
|
|
413
|
+
if self._progress_bar is not None:
|
|
414
|
+
self._progress_bar.console.print(
|
|
415
|
+
"[bold yellow]Shutdown signal received, waiting for programs to finish current iteration...[/bold yellow]"
|
|
416
|
+
)
|
|
417
|
+
self._handle_cancellation()
|
|
418
|
+
|
|
419
|
+
# Collect results from any futures that completed before/during cancellation
|
|
420
|
+
self._collect_completed_results(completed_futures)
|
|
421
|
+
|
|
422
|
+
return False
|
|
423
|
+
|
|
424
|
+
except Exception as e:
|
|
425
|
+
# A task has failed. Print the error and cancel the rest.
|
|
426
|
+
print(f"A task failed with an exception. Cancelling remaining tasks...")
|
|
427
|
+
traceback.print_exception(type(e), e, e.__traceback__)
|
|
428
|
+
|
|
429
|
+
# Collect results from any futures that completed before the failure
|
|
430
|
+
self._collect_completed_results(completed_futures)
|
|
431
|
+
|
|
432
|
+
# Cancel all other futures that have not yet completed.
|
|
433
|
+
for f in self.futures:
|
|
434
|
+
f.cancel()
|
|
435
|
+
|
|
436
|
+
# Re-raise a new error to indicate the batch failed.
|
|
437
|
+
raise RuntimeError("Batch execution failed and was cancelled.") from e
|
|
249
438
|
|
|
250
439
|
finally:
|
|
251
|
-
|
|
440
|
+
# Aggregate results from completed futures
|
|
441
|
+
if completed_futures:
|
|
442
|
+
self._total_circuit_count += sum(
|
|
443
|
+
result[0] for result in completed_futures
|
|
444
|
+
)
|
|
445
|
+
self._total_run_time += sum(result[1] for result in completed_futures)
|
|
446
|
+
self.futures.clear()
|
|
447
|
+
|
|
448
|
+
self._executor.shutdown(wait=False)
|
|
252
449
|
self._executor = None
|
|
253
450
|
|
|
254
451
|
if self._progress_bar is not None:
|
|
452
|
+
self._queue.join()
|
|
255
453
|
self._done_event.set()
|
|
256
454
|
self._listener_thread.join()
|
|
257
|
-
|
|
258
|
-
if exceptions:
|
|
259
|
-
for i, exc in enumerate(exceptions, 1):
|
|
260
|
-
print(f"Task {i} failed with exception:")
|
|
261
|
-
traceback.print_exception(type(exc), exc, exc.__traceback__)
|
|
262
|
-
raise RuntimeError("One or more tasks failed. Check logs for details.")
|
|
263
|
-
|
|
264
|
-
if self._progress_bar is not None:
|
|
265
|
-
self._progress_bar.stop()
|
|
266
|
-
|
|
267
|
-
self._total_circuit_count += sum(future.result()[0] for future in self.futures)
|
|
268
|
-
self._total_run_time += sum(future.result()[1] for future in self.futures)
|
|
269
|
-
self.futures.clear()
|
|
455
|
+
self._progress_bar.stop()
|
|
270
456
|
|
|
271
457
|
# After successful cleanup, try to unregister the hook.
|
|
272
|
-
# This will only succeed if it was a non-blocking run.
|
|
273
458
|
try:
|
|
274
459
|
atexit.unregister(self._atexit_cleanup_hook)
|
|
275
460
|
except TypeError:
|
|
276
|
-
# This is expected for blocking runs where the hook was never registered.
|
|
277
461
|
pass
|
|
278
462
|
|
|
279
463
|
@abstractmethod
|
|
280
464
|
def aggregate_results(self):
|
|
281
|
-
if len(self.
|
|
465
|
+
if len(self._programs) == 0:
|
|
282
466
|
raise RuntimeError("No programs to aggregate. Run create_programs() first.")
|
|
283
467
|
|
|
284
468
|
if self._executor is not None:
|
|
285
469
|
self.join()
|
|
286
470
|
|
|
287
|
-
if any(len(program.losses) == 0 for program in self.
|
|
471
|
+
if any(len(program.losses) == 0 for program in self._programs.values()):
|
|
288
472
|
raise RuntimeError(
|
|
289
473
|
"Some/All programs have empty losses. Did you call run()?"
|
|
290
474
|
)
|
divi/qprog/exceptions.py
ADDED