From 129b60f4d475326b139aa15d016cb010b1ac0fa6 Mon Sep 17 00:00:00 2001 From: Shaobo Zhou Date: Sat, 29 Mar 2025 19:20:52 +0100 Subject: [PATCH 01/57] Update predictor(adding callbacks) --- src/mqt/predictor/rl/example_test.py | 6 ++ src/mqt/predictor/rl/predictor.py | 100 +++++++++++++++++++++------ 2 files changed, 83 insertions(+), 23 deletions(-) create mode 100644 src/mqt/predictor/rl/example_test.py diff --git a/src/mqt/predictor/rl/example_test.py b/src/mqt/predictor/rl/example_test.py new file mode 100644 index 000000000..f13162dc6 --- /dev/null +++ b/src/mqt/predictor/rl/example_test.py @@ -0,0 +1,6 @@ +from predictor import Predictor + +rl_pred = Predictor( + figure_of_merit="expected_fidelity", device_name="ibm_washington" +) +rl_pred.train_model(timesteps=100000, model_name="sample_model_rl") \ No newline at end of file diff --git a/src/mqt/predictor/rl/predictor.py b/src/mqt/predictor/rl/predictor.py index db6783782..6febe1dd4 100644 --- a/src/mqt/predictor/rl/predictor.py +++ b/src/mqt/predictor/rl/predictor.py @@ -3,11 +3,15 @@ from __future__ import annotations import logging +import os from typing import TYPE_CHECKING from sb3_contrib import MaskablePPO from sb3_contrib.common.maskable.policies import MaskableMultiInputActorCriticPolicy from sb3_contrib.common.maskable.utils import get_action_masks +from stable_baselines3.common.logger import configure +from stable_baselines3.common.callbacks import CheckpointCallback, EvalCallback, BaseCallback + from mqt.predictor import reward, rl @@ -18,6 +22,24 @@ PATH_LENGTH = 260 +class OffsetCheckpointCallback(BaseCallback): + def __init__(self, save_freq, save_path, name_prefix, offset=0, verbose=0): + super().__init__(verbose) + self.save_freq = save_freq + self.save_path = save_path + self.name_prefix = name_prefix + self.offset = offset + + def _on_step(self) -> bool: + total_steps = self.num_timesteps + self.offset + if total_steps % self.save_freq == 0: + path = f"{self.save_path}/{self.name_prefix}_{total_steps}_steps.zip" + self.model.save(path) + if self.verbose > 0: + print(f"✅ Saved checkpoint: {path}") + return True + + class Predictor: """The Predictor class is used to train a reinforcement learning model for a given figure of merit and device such that it acts as a compiler.""" @@ -66,38 +88,70 @@ def compile_as_predicted( def train_model( self, - timesteps: int = 1000, + timesteps: int = 100000, model_name: str = "model", verbose: int = 2, test: bool = False, + trained: int = 0, ) -> None: - """Trains all models for the given reward functions and device. + """Train or resume model training with offset checkpointing. Arguments: - timesteps: The number of timesteps to train the model. Defaults to 1000. - model_name: The name of the model. Defaults to "model". - verbose: The verbosity level. Defaults to 2. - test: Whether to train the model for testing purposes. Defaults to False. + timesteps: Total training timesteps desired. + model_name: Prefix for saved model and logs. + verbose: Verbosity level for PPO. + test: If True, uses tiny n_steps for quick test. + trained: Number of timesteps already trained (for resuming). """ - if test: - n_steps = 10 - progress_bar = False + n_steps = 10 if test else 2048 + progress_bar = not test + + log_dir = f"./{model_name}_{self.figure_of_merit}_{self.device_name}" + ckpt_path = f"./checkpoints/{model_name}_{trained}_steps.zip" + + logger.debug(f"🔁 Checking for checkpoint: {ckpt_path}") + + if os.path.exists(ckpt_path): + logger.info(f"📦 Loading checkpoint from {ckpt_path}") + model = MaskablePPO.load( + ckpt_path, + env=self.env, + tensorboard_log=log_dir, + verbose=verbose, + device="cuda", # or "cpu" depending on your setup + ) else: - n_steps = 2048 - progress_bar = True - - logger.debug("Start training for: " + self.figure_of_merit + " on " + self.device_name) - model = MaskablePPO( - MaskableMultiInputActorCriticPolicy, - self.env, - verbose=verbose, - tensorboard_log="./" + model_name + "_" + self.figure_of_merit + "_" + self.device_name, - gamma=0.98, - n_steps=n_steps, + logger.info(f"🆕 No checkpoint found, starting fresh training") + model = MaskablePPO( + MaskableMultiInputActorCriticPolicy, + self.env, + verbose=verbose, + tensorboard_log=log_dir, + gamma=0.98, + n_steps=n_steps, + ) + + remaining = timesteps - trained + + callback = OffsetCheckpointCallback( + save_freq=1000, + save_path="./checkpoints", + name_prefix=model_name, + offset=trained, + verbose=1, ) - # Training Loop: In each iteration, the agent collects n_steps steps (rollout), - # updates the policy for n_epochs, and then repeats the process until total_timesteps steps have been taken. - model.learn(total_timesteps=timesteps, progress_bar=progress_bar) + + tb_log_name = "ppo" + new_logger = configure(folder=os.path.join(log_dir, tb_log_name), format_strings=["stdout", "tensorboard"]) + model.set_logger(new_logger) + model.learn( + total_timesteps=remaining, + tb_log_name=tb_log_name, + callback=callback, + progress_bar=progress_bar, + ) + model.save( rl.helper.get_path_trained_model() / (model_name + "_" + self.figure_of_merit + "_" + self.device_name) ) + logger.info("✅ Final model saved.") \ No newline at end of file From 08889bd2c8ba130c21092503ce7cf56095ede815 Mon Sep 17 00:00:00 2001 From: Shaobo Zhou Date: Thu, 10 Apr 2025 16:06:35 +0200 Subject: [PATCH 02/57] Update --- src/mqt/predictor/rl/helper.py | 7 +- src/mqt/predictor/rl/predictor.py | 102 +++++++----------------------- 2 files changed, 29 insertions(+), 80 deletions(-) diff --git a/src/mqt/predictor/rl/helper.py b/src/mqt/predictor/rl/helper.py index 1b6831049..083c28939 100644 --- a/src/mqt/predictor/rl/helper.py +++ b/src/mqt/predictor/rl/helper.py @@ -351,7 +351,7 @@ def get_state_sample(max_qubits: int | None = None) -> tuple[QuantumCircuit, str Returns: A tuple containing the random quantum circuit and the path to the file from which it was read. """ - file_list = list(get_path_training_circuits().glob("*.qasm")) + """ file_list = list(get_path_training_circuits().glob("*.qasm")) path_zip = get_path_training_circuits() / "training_data_compilation.zip" if len(file_list) == 0 and path_zip.exists(): @@ -359,7 +359,10 @@ def get_state_sample(max_qubits: int | None = None) -> tuple[QuantumCircuit, str zip_ref.extractall(get_path_training_circuits()) file_list = list(get_path_training_circuits().glob("*.qasm")) - assert len(file_list) > 0 + assert len(file_list) > 0 """ + #base_path = get_path_training_circuits() / "mqt_bench_training" + base_path = get_path_training_circuits() / "training_data_compilation" + file_list = list(base_path.rglob("*.qasm")) found_suitable_qc = False while not found_suitable_qc: diff --git a/src/mqt/predictor/rl/predictor.py b/src/mqt/predictor/rl/predictor.py index 6febe1dd4..64ebae0a6 100644 --- a/src/mqt/predictor/rl/predictor.py +++ b/src/mqt/predictor/rl/predictor.py @@ -3,15 +3,11 @@ from __future__ import annotations import logging -import os from typing import TYPE_CHECKING from sb3_contrib import MaskablePPO from sb3_contrib.common.maskable.policies import MaskableMultiInputActorCriticPolicy from sb3_contrib.common.maskable.utils import get_action_masks -from stable_baselines3.common.logger import configure -from stable_baselines3.common.callbacks import CheckpointCallback, EvalCallback, BaseCallback - from mqt.predictor import reward, rl @@ -22,24 +18,6 @@ PATH_LENGTH = 260 -class OffsetCheckpointCallback(BaseCallback): - def __init__(self, save_freq, save_path, name_prefix, offset=0, verbose=0): - super().__init__(verbose) - self.save_freq = save_freq - self.save_path = save_path - self.name_prefix = name_prefix - self.offset = offset - - def _on_step(self) -> bool: - total_steps = self.num_timesteps + self.offset - if total_steps % self.save_freq == 0: - path = f"{self.save_path}/{self.name_prefix}_{total_steps}_steps.zip" - self.model.save(path) - if self.verbose > 0: - print(f"✅ Saved checkpoint: {path}") - return True - - class Predictor: """The Predictor class is used to train a reinforcement learning model for a given figure of merit and device such that it acts as a compiler.""" @@ -88,70 +66,38 @@ def compile_as_predicted( def train_model( self, - timesteps: int = 100000, + timesteps: int = 1000, model_name: str = "model", verbose: int = 2, test: bool = False, - trained: int = 0, ) -> None: - """Train or resume model training with offset checkpointing. + """Trains all models for the given reward functions and device. Arguments: - timesteps: Total training timesteps desired. - model_name: Prefix for saved model and logs. - verbose: Verbosity level for PPO. - test: If True, uses tiny n_steps for quick test. - trained: Number of timesteps already trained (for resuming). + timesteps: The number of timesteps to train the model. Defaults to 1000. + model_name: The name of the model. Defaults to "model". + verbose: The verbosity level. Defaults to 2. + test: Whether to train the model for testing purposes. Defaults to False. """ - n_steps = 10 if test else 2048 - progress_bar = not test - - log_dir = f"./{model_name}_{self.figure_of_merit}_{self.device_name}" - ckpt_path = f"./checkpoints/{model_name}_{trained}_steps.zip" - - logger.debug(f"🔁 Checking for checkpoint: {ckpt_path}") - - if os.path.exists(ckpt_path): - logger.info(f"📦 Loading checkpoint from {ckpt_path}") - model = MaskablePPO.load( - ckpt_path, - env=self.env, - tensorboard_log=log_dir, - verbose=verbose, - device="cuda", # or "cpu" depending on your setup - ) + if test: + n_steps = 10 + progress_bar = False else: - logger.info(f"🆕 No checkpoint found, starting fresh training") - model = MaskablePPO( - MaskableMultiInputActorCriticPolicy, - self.env, - verbose=verbose, - tensorboard_log=log_dir, - gamma=0.98, - n_steps=n_steps, - ) - - remaining = timesteps - trained - - callback = OffsetCheckpointCallback( - save_freq=1000, - save_path="./checkpoints", - name_prefix=model_name, - offset=trained, - verbose=1, + n_steps = 500 + progress_bar = True + + logger.debug("Start training for: " + self.figure_of_merit + " on " + self.device_name) + model = MaskablePPO( + MaskableMultiInputActorCriticPolicy, + self.env, + verbose=verbose, + tensorboard_log="./" + model_name + "_" + self.figure_of_merit + "_" + self.device_name, + gamma=0.98, + n_steps=n_steps, ) - - tb_log_name = "ppo" - new_logger = configure(folder=os.path.join(log_dir, tb_log_name), format_strings=["stdout", "tensorboard"]) - model.set_logger(new_logger) - model.learn( - total_timesteps=remaining, - tb_log_name=tb_log_name, - callback=callback, - progress_bar=progress_bar, - ) - + # Training Loop: In each iteration, the agent collects n_steps steps (rollout), + # updates the policy for n_epochs, and then repeats the process until total_timesteps steps have been taken. + model.learn(total_timesteps=timesteps, progress_bar=progress_bar) model.save( rl.helper.get_path_trained_model() / (model_name + "_" + self.figure_of_merit + "_" + self.device_name) - ) - logger.info("✅ Final model saved.") \ No newline at end of file + ) \ No newline at end of file From 78dc1aa91d35a90ede315336608491622bdff854 Mon Sep 17 00:00:00 2001 From: Liu Keyu Date: Tue, 29 Jul 2025 14:31:42 +0200 Subject: [PATCH 03/57] Implement new mapping actions Update action space and feature space Update actions Update action space --- src/mqt/predictor/rl/actions.py | 271 ++++++++++++++++++++++++++- src/mqt/predictor/rl/helper.py | 183 +++++++++++++++++- src/mqt/predictor/rl/predictorenv.py | 142 +++++++------- 3 files changed, 524 insertions(+), 72 deletions(-) diff --git a/src/mqt/predictor/rl/actions.py b/src/mqt/predictor/rl/actions.py index fecca51df..de7d98a98 100644 --- a/src/mqt/predictor/rl/actions.py +++ b/src/mqt/predictor/rl/actions.py @@ -51,7 +51,9 @@ from qiskit.transpiler.passes import ( ApplyLayout, BasisTranslator, + BasicSwap, Collect2qBlocks, + CollectCliffords, CommutativeCancellation, CommutativeInverseCancellation, ConsolidateBlocks, @@ -67,7 +69,9 @@ OptimizeCliffords, RemoveDiagonalGatesBeforeMeasure, SabreLayout, + SabreSwap, Size, + TrivialLayout, UnitarySynthesis, VF2Layout, VF2PostLayout, @@ -80,6 +84,8 @@ get_bqskit_native_gates, ) +from mqt.predictor.rl.helper import SafeAIRouting, get_openqasm_gates + if TYPE_CHECKING: from collections.abc import Callable from typing import Any @@ -125,6 +131,7 @@ class Action: Callable[..., tuple[Any, ...] | Circuit], ] ) + stochastic: bool = False @dataclass @@ -180,7 +187,7 @@ def remove_action(name: str) -> None: "Optimize1qGatesDecomposition", CompilationOrigin.QISKIT, PassType.OPT, - [Optimize1qGatesDecomposition()], + [Optimize1qGatesDecomposition(basis=get_openqasm_gates())], ) ) @@ -240,16 +247,18 @@ def remove_action(name: str) -> None: "OptimizeCliffords", CompilationOrigin.QISKIT, PassType.OPT, - [OptimizeCliffords()], + [CollectCliffords(), OptimizeCliffords()], ) ) register_action( - DeviceIndependentAction( + DeviceDependentAction( "Opt2qBlocks", CompilationOrigin.QISKIT, PassType.OPT, - [Collect2qBlocks(), ConsolidateBlocks(), UnitarySynthesis()], + transpile_pass=lambda device: [Collect2qBlocks(), + ConsolidateBlocks(basis_gates=device.operation_names), + UnitarySynthesis(basis_gates=device.operation_names, coupling_map=device.build_coupling_map())], ) ) @@ -393,9 +402,69 @@ def remove_action(name: str) -> None: "SabreMapping", CompilationOrigin.QISKIT, PassType.MAPPING, - transpile_pass=lambda device: [ - SabreLayout(coupling_map=CouplingMap(device.build_coupling_map()), skip_routing=False) - ], + stochastic=True, + # Qiskit O3 by default uses (max_iterations, layout_trials, swap_trials) = (4, 20, 20) + transpile_pass=lambda device, max_iteration=(20,20): [ + SabreLayout( + coupling_map=CouplingMap(device.build_coupling_map()), + skip_routing=False, + layout_trials=max_iteration[0], + swap_trials=max_iteration[1], + max_iterations=4, + seed=None + ), + ], + ) +) + +register_action( + DeviceDependentAction( + "SabreLayout+BasicSwap", + CompilationOrigin.QISKIT, + PassType.MAPPING, + stochastic=True, + transpile_pass=lambda device, max_iteration=(20,20): [ + SabreLayout( + coupling_map=CouplingMap(device.build_coupling_map()), + skip_routing=True, + layout_trials=max_iteration[0], + swap_trials=max_iteration[1], + max_iterations=4, + seed=None + ), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + BasicSwap(coupling_map=CouplingMap(device.build_coupling_map())), + ], + ) +) + +register_action( + DeviceDependentAction( + "SabreLayout+AIRouting", + CompilationOrigin.QISKIT, + PassType.MAPPING, + stochastic=True, + transpile_pass=lambda device, max_iteration=(20,20): [ + SabreLayout( + coupling_map=CouplingMap(device.build_coupling_map()), + skip_routing=True, + layout_trials=max_iteration[0], + swap_trials=max_iteration[1], + max_iterations=4, + seed=None + ), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + SafeAIRouting( + coupling_map=device.build_coupling_map(), + optimization_level=3, + layout_mode="optimize", + local_mode=True + ), + ], ) ) @@ -421,6 +490,194 @@ def remove_action(name: str) -> None: ) ) +register_action( + DeviceDependentAction( + name="DenseLayout+BasicSwap", + origin=CompilationOrigin.QISKIT, + pass_type=PassType.MAPPING, + transpile_pass=lambda device: [ + DenseLayout(coupling_map=CouplingMap(device.build_coupling_map())), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + BasicSwap(coupling_map=CouplingMap(device.build_coupling_map())), + ], + ) +) + +register_action( + DeviceDependentAction( + name="DenseLayout+SabreSwap", + origin=CompilationOrigin.QISKIT, + pass_type=PassType.MAPPING, + stochastic=True, + transpile_pass=lambda device, max_iteration=(20,20): [ + DenseLayout(coupling_map=CouplingMap(device.build_coupling_map())), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + SabreSwap(coupling_map=CouplingMap(device.build_coupling_map()), heuristic="decay", trials=max_iteration[1], seed=None), + ], + ) +) + +register_action( + DeviceDependentAction( + name="DenseLayout+AIRouting", + origin=CompilationOrigin.QISKIT, + pass_type=PassType.MAPPING, + stochastic=True, + transpile_pass=lambda device: [ + DenseLayout(coupling_map=CouplingMap(device.build_coupling_map())), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + SafeAIRouting( + coupling_map=device.build_coupling_map(), + optimization_level=3, + layout_mode="optimize", + local_mode=True + ), + ], + ) +) + +register_action( + DeviceDependentAction( + name="VF2Layout+BasicSwap", + origin=CompilationOrigin.QISKIT, + pass_type=PassType.MAPPING, + transpile_pass=lambda device: [ + VF2Layout( + coupling_map=CouplingMap(device.build_coupling_map()), + target=device, + ), + ConditionalController( + [ + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + ], + condition=lambda property_set: property_set["VF2Layout_stop_reason"] + == VF2LayoutStopReason.SOLUTION_FOUND, + ), + ConditionalController( + [ + TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + ], + # Run if VF2Layout did not find a solution + condition=lambda property_set: property_set["VF2Layout_stop_reason"] != VF2LayoutStopReason.SOLUTION_FOUND, + ), + BasicSwap(coupling_map=CouplingMap(device.build_coupling_map())), + ], + ) +) + +register_action( + DeviceDependentAction( + name="VF2Layout+SabreSwap", + origin=CompilationOrigin.QISKIT, + pass_type=PassType.MAPPING, + stochastic=True, + transpile_pass=lambda device, max_iteration=(20,20): [ + VF2Layout( + coupling_map=CouplingMap(device.build_coupling_map()), + target=device, + ), + ConditionalController( + [ + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + ], + condition=lambda property_set: property_set["VF2Layout_stop_reason"] + == VF2LayoutStopReason.SOLUTION_FOUND, + ), + ConditionalController( + [ + TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + ], + # Run if VF2Layout did not find a solution + condition=lambda property_set: property_set["VF2Layout_stop_reason"] != VF2LayoutStopReason.SOLUTION_FOUND, + ), + SabreSwap(coupling_map=CouplingMap(device.build_coupling_map()), heuristic="decay", trials=max_iteration[1], seed=None), + ], + ) +) + +register_action( + DeviceDependentAction( + name="VF2Layout+AIRouting", + origin=CompilationOrigin.QISKIT, + pass_type=PassType.MAPPING, + stochastic=True, + transpile_pass=lambda device:[ + VF2Layout( + coupling_map=CouplingMap(device.build_coupling_map()), + target=device, + ), + ConditionalController( + [ + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + ], + condition=lambda property_set: property_set["VF2Layout_stop_reason"] + == VF2LayoutStopReason.SOLUTION_FOUND, + ), + ConditionalController( + [ + TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + ], + # Run if VF2Layout did not find a solution + condition=lambda property_set: property_set["VF2Layout_stop_reason"] != VF2LayoutStopReason.SOLUTION_FOUND, + ), + SafeAIRouting( + coupling_map=device.build_coupling_map(), + optimization_level=3, + layout_mode="optimize", + local_mode=True + ), + ], + ) +) + +register_action( + DeviceDependentAction( + name="SabreLayout+AIRouting", + origin=CompilationOrigin.QISKIT, + stochastic=True, + pass_type=PassType.MAPPING, + transpile_pass=lambda device, max_iteration=(20, 20): [ + SabreLayout( + coupling_map=CouplingMap(device.build_coupling_map()), + skip_routing=True, + layout_trials=max_iteration[0], + swap_trials=1, + max_iterations=4, + ), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + SafeAIRouting( + coupling_map=device.build_coupling_map(), + optimization_level=3, + layout_mode="optimize", + local_mode=True, + ), + ], + ) +) + register_action( DeviceDependentAction( "BasisTranslator", diff --git a/src/mqt/predictor/rl/helper.py b/src/mqt/predictor/rl/helper.py index 68bb9401d..f2c948c85 100644 --- a/src/mqt/predictor/rl/helper.py +++ b/src/mqt/predictor/rl/helper.py @@ -16,6 +16,10 @@ import numpy as np from qiskit import QuantumCircuit +from qiskit.converters import circuit_to_dag, dag_to_circuit +from qiskit.circuit import ClassicalRegister, QuantumRegister +from qiskit.transpiler import PassManager +from qiskit_ibm_transpiler.ai.routing import AIRouting from mqt.predictor.utils import calc_supermarq_features @@ -28,7 +32,140 @@ logger = logging.getLogger("mqt-predictor") +def extract_cregs_and_measurements(qc): + cregs = [ClassicalRegister(cr.size, name=cr.name) for cr in qc.cregs] + measurements = [ + (item.operation, item.qubits, item.clbits) + for item in qc.data + if item.operation.name == "measure" + ] + return cregs, measurements +def remove_cregs(qc): + qregs = [QuantumRegister(qr.size, name=qr.name) for qr in qc.qregs] + new_qc = QuantumCircuit(*qregs) + old_to_new = {} + for orig_qr, new_qr in zip(qc.qregs, new_qc.qregs): + for idx in range(orig_qr.size): + old_to_new[orig_qr[idx]] = new_qr[idx] + for item in qc.data: + instr = item.operation + qargs = [old_to_new[q] for q in item.qubits] + if instr.name not in ("measure", "barrier"): + new_qc.append(instr, qargs) + return new_qc + +def add_cregs_and_measurements(qc, cregs, measurements, qubit_map=None): + for cr in cregs: + qc.add_register(cr) + for instr, qargs, cargs in measurements: + if qubit_map: + new_qargs = [qubit_map[q] for q in qargs] + else: + new_qargs = qargs + qc.append(instr, new_qargs, cargs) + return qc + +class SafeAIRouting(AIRouting): + """ + Remove cregs before AIRouting and add them back afterwards + Necessary because there are cases AIRouting can't handle + """ + def run(self, dag): + # 1. Convert input dag to circuit + qc_orig = dag_to_circuit(dag) + + # 2. Extract classical registers and measurement instructions + cregs, measurements = extract_cregs_and_measurements(qc_orig) + + # 3. Remove cregs and measurements + qc_noclassical = remove_cregs(qc_orig) + + # 4. Convert back to dag and run routing (AIRouting) + dag_noclassical = circuit_to_dag(qc_noclassical) + dag_routed = super().run(dag_noclassical) + + # 5. Convert routed dag to circuit for restoration + qc_routed = dag_to_circuit(dag_routed) + + # 6. Build mapping from original qubits to qubits in routed circuit + final_layout = getattr(self, "property_set", {}).get("final_layout", None) + if final_layout is None and hasattr(dag_routed, "property_set"): + final_layout = dag_routed.property_set.get("final_layout", None) + + qubit_map = {} + for virt in qc_orig.qubits: + phys = final_layout[virt] + if isinstance(phys, int): + qubit_map[virt] = qc_routed.qubits[phys] + else: + try: + idx = qc_routed.qubits.index(phys) + except ValueError: + raise RuntimeError(f"Physical qubit {phys} not found in output circuit!") + qubit_map[virt] = qc_routed.qubits[idx] + # 7. Restore classical registers and measurement instructions + qc_final = add_cregs_and_measurements(qc_routed, cregs, measurements, qubit_map) + # 8. Return as dag + return circuit_to_dag(qc_final) + +def best_of_n_passmanager( + action, device, qc, max_iteration=(20,20), + metric_fn=None, +): + """ + Runs the given transpile_pass multiple times and keeps the best result. + action: the action dict with a 'transpile_pass' key (lambda/device->[passes]) + device: the backend or device + qc: input circuit + max_iteration: number of times to try + metric_fn: function(circ) -> float for scoring + require_layout: skip outputs with missing layouts + """ + best_val = None + best_result = None + best_property_set = None + + if action["name"] == "SabreLayout+AIRouting": + all_passes = action.transpile_pass(device, max_iteration) + else: + all_passes = action.transpile_pass(device) + + layout_passes = all_passes[:-1] + routing_pass = all_passes[-1:] + + # Run layout once + layout_pm = PassManager(layout_passes) + try: + layouted_qc = layout_pm.run(qc) + layout_props = dict(layout_pm.property_set) + except Exception as e: + return qc, {} + + # Run routing multiple times and optimize for the given metric + for i in range(max_iteration[1]): + pm = PassManager(routing_pass) + pm.property_set.update(layout_props) + try: + out_circ = pm.run(layouted_qc) + prop_set = dict(pm.property_set) + + val = metric_fn(out_circ) if metric_fn else out_circ.depth() + if best_val is None or val < best_val: + best_val = val + best_result = out_circ + best_property_set = prop_set + if best_val == 0: + break + except Exception as e: + print(f"[Routing] Trial {i+1} failed: {e}") + continue + if best_result is not None: + return best_result, best_property_set + else: + print("All mapping attempts failed; returning original circuit.") + return qc, {} + def get_state_sample(max_qubits: int, path_training_circuits: Path, rng: Generator) -> tuple[QuantumCircuit, str]: """Returns a random quantum circuit from the training circuits folder. @@ -68,8 +205,51 @@ def get_state_sample(max_qubits: int, path_training_circuits: Path, rng: Generat return qc, str(file_list[random_index]) +def get_openqasm_gates() -> list[str]: + """ Returns a list of all quantum gates within the openQASM 2.0 standard header. + + According to https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/qasm/libs/qelib1.inc + Removes generic single qubit gates u1, u2, u3 since they are no meaningful features for RL -def create_feature_dict(qc: QuantumCircuit) -> dict[str, int | NDArray[np.float64]]: + """ + return [ + "cx", + "id", + "u", + "p", + "x", + "y", + "z", + "h", + "r", + "s", + "sdg", + "t", + "tdg", + "rx", + "ry", + "rz", + "sx", + "sxdg", + "cz", + "cy", + "swap", + "ch", + "ccx", + "cswap", + "crx", + "cry", + "crz", + "cu1", + "cp", + "csx", + "cu", + "rxx", + "rzz", + "rccx", + ] + +def create_feature_dict(qc: QuantumCircuit, basis_gates: list[str], coupling_map) -> dict[str, int | NDArray[np.float64]]: """Creates a feature dictionary for a given quantum circuit. Arguments: @@ -82,7 +262,6 @@ def create_feature_dict(qc: QuantumCircuit) -> dict[str, int | NDArray[np.float6 "num_qubits": qc.num_qubits, "depth": qc.depth(), } - supermarq_features = calc_supermarq_features(qc) # for all dict values, put them in a list each feature_dict["program_communication"] = np.array([supermarq_features.program_communication], dtype=np.float32) diff --git a/src/mqt/predictor/rl/predictorenv.py b/src/mqt/predictor/rl/predictorenv.py index fbaa4875f..41d5121af 100644 --- a/src/mqt/predictor/rl/predictorenv.py +++ b/src/mqt/predictor/rl/predictorenv.py @@ -38,7 +38,8 @@ from joblib import load from pytket.circuit import Qubit from pytket.extensions.qiskit import qiskit_to_tk, tk_to_qiskit -from qiskit import QuantumCircuit +from qiskit import QuantumCircuit, transpile +from qiskit.circuit.library import RCCXGate from qiskit.passmanager.flow_controllers import DoWhileController from qiskit.transpiler import CouplingMap, PassManager, Target, TranspileLayout from qiskit.transpiler.passes import CheckMap, GatesInBasis @@ -54,7 +55,7 @@ figure_of_merit, ) from mqt.predictor.rl.actions import CompilationOrigin, DeviceDependentAction, PassType, get_actions_by_pass_type -from mqt.predictor.rl.helper import create_feature_dict, get_path_training_circuits, get_state_sample +from mqt.predictor.rl.helper import create_feature_dict, get_path_training_circuits, get_state_sample, get_openqasm_gates, best_of_n_passmanager from mqt.predictor.rl.parsing import ( final_layout_bqskit_to_qiskit, final_layout_pytket_to_qiskit, @@ -89,8 +90,6 @@ def __init__( self.action_set = {} self.actions_synthesis_indices = [] - self.actions_layout_indices = [] - self.actions_routing_indices = [] self.actions_mapping_indices = [] self.actions_opt_indices = [] self.actions_final_optimization_indices = [] @@ -110,14 +109,6 @@ def __init__( self.action_set[index] = elem self.actions_synthesis_indices.append(index) index += 1 - for elem in action_dict[PassType.LAYOUT]: - self.action_set[index] = elem - self.actions_layout_indices.append(index) - index += 1 - for elem in action_dict[PassType.ROUTING]: - self.action_set[index] = elem - self.actions_routing_indices.append(index) - index += 1 for elem in action_dict[PassType.OPT]: self.action_set[index] = elem self.actions_opt_indices.append(index) @@ -163,6 +154,7 @@ def __init__( } self.observation_space = Dict(spaces) self.filename = "" + self.max_iter = (20,20) # (layout_trials, routing _trials) def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any, Any]]: """Executes the given action and returns the new state, the reward, whether the episode is done, whether the episode is truncated and additional information. @@ -202,9 +194,14 @@ def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any reward_val = 0 done = False - # in case the Qiskit.QuantumCircuit has unitary or u gates in it, decompose them (because otherwise qiskit will throw an error when applying the BasisTranslator + # in case the Qiskit.QuantumCircuit has unitary or u gates or clifford in it, decompose them (because otherwise qiskit will throw an error when applying the BasisTranslator if self.state.count_ops().get("unitary"): - self.state = self.state.decompose(gates_to_decompose="unitary") + temp_circ = self.state.decompose(gates_to_decompose="unitary") + # qiskit fallback to ['id', 'u1', 'u2', 'u3', 'cx'] by default according to https://quantum.cloud.ibm.com/docs/en/api/qiskit/0.24/transpiler + self.state = transpile(temp_circ, basis_gates=get_openqasm_gates(), optimization_level=0) + elif self.state.count_ops().get("clifford"): + temp_circ = self.state.decompose(gates_to_decompose="clifford") + self.state = transpile(temp_circ, basis_gates=get_openqasm_gates(), optimization_level=0) self.state._layout = self.layout # noqa: SLF001 obs = create_feature_dict(self.state) @@ -319,55 +316,76 @@ def apply_action(self, action_index: int) -> QuantumCircuit | None: raise ValueError(msg) def _apply_qiskit_action(self, action: Action, action_index: int) -> QuantumCircuit: - if action.name == "QiskitO3" and isinstance(action, DeviceDependentAction): - passes = action.transpile_pass( - self.device.operation_names, - CouplingMap(self.device.build_coupling_map()) if self.layout else None, - ) - pm = PassManager([DoWhileController(passes, do_while=action.do_while)]) + if action.get("stochastic", False): + metric_fn = lambda circ: circ.count_ops().get("swap", 0) + # for stochastic actions, pass the layout/routing trials parameter + max_iteration = self.max_iter + if "Sabre" in action["name"] and "AIRouting" not in action["name"]: + # Internal trials for Sabre + transpile_pass = action.transpile_pass(self.device, max_iteration) + pm = PassManager(transpile_pass) + altered_qc = pm.run(self.state) + pm_property_set = dict(pm.property_set) + elif "AIRouting" in action["name"]: + # Run AIRouting in custom loop + altered_qc, pm_property_set = best_of_n_passmanager( + action, + self.device, + self.state, + max_iteration=max_iteration, + metric_fn=metric_fn, + ) else: - transpile_pass = ( - action.transpile_pass(self.device) if callable(action.transpile_pass) else action.transpile_pass - ) - pm = PassManager(transpile_pass) - - altered_qc = pm.run(self.state) - - if action_index in ( - self.actions_layout_indices + self.actions_mapping_indices + self.actions_final_optimization_indices - ): - altered_qc = self._handle_qiskit_layout_postprocessing(action, pm, altered_qc) - - elif action_index in self.actions_routing_indices and self.layout: - self.layout.final_layout = pm.property_set["final_layout"] - + if action.name == "QiskitO3" and isinstance(action, DeviceDependentAction): + passes = action.transpile_pass( + self.device.operation_names, + CouplingMap(self.device.build_coupling_map()) if self.layout else None, + ) + pm = PassManager([DoWhileController(passes, do_while=action.do_while)]) + altered_qc = pm.run(self.state) + pm_property_set = dict(pm.property_set) if hasattr(pm, "property_set") else {} + else: + transpile_pass = ( + action.transpile_pass(self.device) if callable(action.transpile_pass) else action.transpile_pass + ) + pm = PassManager(transpile_pass) + altered_qc = pm.run(self.state) + pm_property_set = dict(pm.property_set) if hasattr(pm, "property_set") else {} + if action_index in ( + self.actions_mapping_indices + self.actions_final_optimization_indices + ): + pm_property_set = dict(pm.property_set) + altered_qc = self._handle_qiskit_layout_postprocessing(action, pm_property_set, altered_qc) + return altered_qc - + def _handle_qiskit_layout_postprocessing( - self, action: Action, pm: PassManager, altered_qc: QuantumCircuit + self, action: Action, pm_property_set: dict, altered_qc: QuantumCircuit, ) -> QuantumCircuit: if action.name == "VF2PostLayout": - assert pm.property_set["VF2PostLayout_stop_reason"] is not None - post_layout = pm.property_set["post_layout"] + assert pm_property_set["VF2PostLayout_stop_reason"] is not None + post_layout = pm_property_set["post_layout"] if post_layout: altered_qc, _ = postprocess_vf2postlayout(altered_qc, post_layout, self.layout) - elif action.name == "VF2Layout": - assert pm.property_set["VF2Layout_stop_reason"] == VF2LayoutStopReason.SOLUTION_FOUND - assert pm.property_set["layout"] else: - assert pm.property_set["layout"] + assert pm_property_set["layout"] - if pm.property_set["layout"]: + if pm_property_set["layout"]: self.layout = TranspileLayout( - initial_layout=pm.property_set["layout"], - input_qubit_mapping=pm.property_set["original_qubit_indices"], - final_layout=pm.property_set["final_layout"], + initial_layout=pm_property_set["layout"], + input_qubit_mapping=pm_property_set["original_qubit_indices"], + final_layout=pm_property_set["final_layout"], _output_qubit_list=altered_qc.qubits, _input_qubit_count=self.num_qubits_uncompiled_circuit, ) + if pm_property_set["final_layout"]: + self.layout.final_layout = pm_property_set["final_layout"] return altered_qc def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircuit: + if any(isinstance(gate[0], RCCXGate) for gate in self.state.data): + # RCCX could cause error + self.state = transpile(self.state, basis_gates=get_openqasm_gates()[:-1], optimization_level=0) tket_qc = qiskit_to_tk(self.state, preserve_param_uuid=True) transpile_pass = ( action.transpile_pass(self.device) if callable(action.transpile_pass) else action.transpile_pass @@ -383,7 +401,8 @@ def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircui if action_index in self.actions_routing_indices: assert self.layout is not None self.layout.final_layout = final_layout_pytket_to_qiskit(tket_qc, altered_qc) - + # Decompose to the allowed gates (by default generic u gates are used) + altered_qc = transpile(altered_qc, basis_gates=get_openqasm_gates(), optimization_level=0) return altered_qc def _apply_bqskit_action(self, action: Action, action_index: int) -> QuantumCircuit: @@ -424,25 +443,22 @@ def _apply_bqskit_action(self, action: Action, action_index: int) -> QuantumCirc def determine_valid_actions_for_state(self) -> list[int]: """Determines and returns the valid actions for the current state.""" - check_nat_gates = GatesInBasis(target=self.device) + check_nat_gates = GatesInBasis(basis_gates=self.device.basis_gates) check_nat_gates(self.state) only_nat_gates = check_nat_gates.property_set["all_gates_in_basis"] - if not only_nat_gates: - actions = self.actions_synthesis_indices + self.actions_opt_indices - if self.layout is not None: - actions += self.actions_routing_indices - return actions - - check_mapping = CheckMap(coupling_map=self.device.build_coupling_map()) + check_mapping = CheckMap(coupling_map=CouplingMap(self.device.coupling_map)) check_mapping(self.state) mapped = check_mapping.property_set["is_swap_mapped"] - if mapped and self.layout is not None: # The circuit is correctly mapped. - return [self.action_terminate_index, *self.actions_opt_indices] - - if self.layout is not None: # The circuit is not yet mapped but a layout is set. - return self.actions_routing_indices + if not only_nat_gates: # not native gates yet + actions = self.actions_synthesis_indices + self.actions_opt_indices + return actions - # No layout applied yet - return self.actions_mapping_indices + self.actions_layout_indices + self.actions_opt_indices + if mapped and self.layout is not None: # The circuit is correctly mapped. + return [self.action_terminate_index, *self.actions_opt_indices, *self.actions_final_optimization_indices] + else: + # The circuit is not mapped yet + # Or the circuit was mapped but some optimization actions change its structure and the circuit is again unmapped + # In this case, re-do mapping completely as the previous layout is not optimal on the new structure + return self.actions_mapping_indices + self.actions_opt_indices \ No newline at end of file From a3ba8367324590b1b01f731c702e5052b11e3d3b Mon Sep 17 00:00:00 2001 From: Liu Keyu Date: Tue, 29 Jul 2025 17:51:24 +0200 Subject: [PATCH 04/57] Fix: resolve pre-commit issues and add missing annotations Fix: resolve pre-commit issues and add missing annotations Fix: resolve pre-commit issues and add missing annotations Remove example_test.py Remove example_test.py --- pyproject.toml | 1 + src/mqt/predictor/rl/example_test.py | 6 -- src/mqt/predictor/rl/helper.py | 94 +++++++++++++++++++++------- src/mqt/predictor/rl/predictorenv.py | 9 +-- 4 files changed, 79 insertions(+), 31 deletions(-) delete mode 100644 src/mqt/predictor/rl/example_test.py diff --git a/pyproject.toml b/pyproject.toml index 2d8a4f988..5c66b12d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ dependencies = [ "numpy>=1.22,<2; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict numpy v2 for macOS x86 since it is not supported anymore since torch v2.3.0 "torch>=2.2.2,<2.3.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch v2.3.0 for macOS x86 since it is not supported anymore. "typing-extensions>=4.1", # for `assert_never` + "qiskit-ibm-transpiler>=0.2.0", ] classifiers = [ diff --git a/src/mqt/predictor/rl/example_test.py b/src/mqt/predictor/rl/example_test.py deleted file mode 100644 index f13162dc6..000000000 --- a/src/mqt/predictor/rl/example_test.py +++ /dev/null @@ -1,6 +0,0 @@ -from predictor import Predictor - -rl_pred = Predictor( - figure_of_merit="expected_fidelity", device_name="ibm_washington" -) -rl_pred.train_model(timesteps=100000, model_name="sample_model_rl") \ No newline at end of file diff --git a/src/mqt/predictor/rl/helper.py b/src/mqt/predictor/rl/helper.py index f2c948c85..1ea9cf6a7 100644 --- a/src/mqt/predictor/rl/helper.py +++ b/src/mqt/predictor/rl/helper.py @@ -12,16 +12,18 @@ import logging from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional, Callable, List, Tuple, Dict, Any import numpy as np from qiskit import QuantumCircuit from qiskit.converters import circuit_to_dag, dag_to_circuit -from qiskit.circuit import ClassicalRegister, QuantumRegister -from qiskit.transpiler import PassManager +from qiskit.circuit import ClassicalRegister, QuantumRegister, Instruction +from qiskit.transpiler import PassManager, Target +from qiskit.dagcircuit import DAGCircuit from qiskit_ibm_transpiler.ai.routing import AIRouting from mqt.predictor.utils import calc_supermarq_features +from mqt.predictor.rl.actions import Action if TYPE_CHECKING: from numpy.random import Generator @@ -32,7 +34,16 @@ logger = logging.getLogger("mqt-predictor") -def extract_cregs_and_measurements(qc): +def extract_cregs_and_measurements(qc: QuantumCircuit) -> Tuple[List[ClassicalRegister], List[tuple[Instruction, List, List]]]: + """ + Extracts classical registers and measurement operations from a quantum circuit. + + Args: + qc: The input QuantumCircuit. + + Returns: + A tuple containing a list of classical registers and a list of measurement operations. + """ cregs = [ClassicalRegister(cr.size, name=cr.name) for cr in qc.cregs] measurements = [ (item.operation, item.qubits, item.clbits) @@ -41,7 +52,16 @@ def extract_cregs_and_measurements(qc): ] return cregs, measurements -def remove_cregs(qc): +def remove_cregs(qc: QuantumCircuit) -> QuantumCircuit: + """ + Removes classical registers and measurement operations from the circuit. + + Args: + qc: The input QuantumCircuit. + + Returns: + A new QuantumCircuit with only quantum operations (no cregs or measurements). + """ qregs = [QuantumRegister(qr.size, name=qr.name) for qr in qc.qregs] new_qc = QuantumCircuit(*qregs) old_to_new = {} @@ -55,7 +75,24 @@ def remove_cregs(qc): new_qc.append(instr, qargs) return new_qc -def add_cregs_and_measurements(qc, cregs, measurements, qubit_map=None): +def add_cregs_and_measurements( + qc: QuantumCircuit, + cregs: List[ClassicalRegister], + measurements: List[Tuple[Instruction, List, List]], + qubit_map: Optional[Dict] = None, +) -> QuantumCircuit: + """ + Adds classical registers and measurement operations back to the quantum circuit. + + Args: + qc: The quantum circuit to which cregs and measurements are added. + cregs: List of ClassicalRegister to add. + measurements: List of measurement instructions as tuples (Instruction, qubits, clbits). + qubit_map: Optional dictionary mapping original qubits to new qubits. + + Returns: + The modified QuantumCircuit with cregs and measurements added. + """ for cr in cregs: qc.add_register(cr) for instr, qargs, cargs in measurements: @@ -68,10 +105,15 @@ def add_cregs_and_measurements(qc, cregs, measurements, qubit_map=None): class SafeAIRouting(AIRouting): """ - Remove cregs before AIRouting and add them back afterwards - Necessary because there are cases AIRouting can't handle + Custom AIRouting wrapper that removes classical registers before routing. + + This prevents failures in AIRouting when classical bits are present by + temporarily removing classical registers and measurements and restoring + them after routing is completed. """ - def run(self, dag): + def run(self, dag: DAGCircuit) -> DAGCircuit: + """Run the routing pass on a DAGCircuit.""" + # 1. Convert input dag to circuit qc_orig = dag_to_circuit(dag) @@ -101,8 +143,8 @@ def run(self, dag): else: try: idx = qc_routed.qubits.index(phys) - except ValueError: - raise RuntimeError(f"Physical qubit {phys} not found in output circuit!") + except ValueError as err: + raise RuntimeError(f"Physical qubit {phys} not found in output circuit!") from err qubit_map[virt] = qc_routed.qubits[idx] # 7. Restore classical registers and measurement instructions qc_final = add_cregs_and_measurements(qc_routed, cregs, measurements, qubit_map) @@ -110,17 +152,27 @@ def run(self, dag): return circuit_to_dag(qc_final) def best_of_n_passmanager( - action, device, qc, max_iteration=(20,20), - metric_fn=None, -): + action: Action, + device: Target, + qc: QuantumCircuit, + max_iteration: Tuple[int, int] = (20, 20), + metric_fn: Optional[Callable[[QuantumCircuit], float]] = None, +)-> tuple[QuantumCircuit, Dict[str, Any]]: """ Runs the given transpile_pass multiple times and keeps the best result. - action: the action dict with a 'transpile_pass' key (lambda/device->[passes]) - device: the backend or device - qc: input circuit - max_iteration: number of times to try - metric_fn: function(circ) -> float for scoring - require_layout: skip outputs with missing layouts + + Args: + action: The action dictionary with a 'transpile_pass' key + (lambda device -> [passes]). + device: The target backend or device. + qc: The input quantum circuit. + max_iteration: A tuple (layout_trials, routing_trials) specifying + how many times to try. + metric_fn: Optional function to score circuits; defaults to circuit depth. + + Returns: + A tuple containing the best transpiled circuit and its corresponding + property set. """ best_val = None best_result = None @@ -249,7 +301,7 @@ def get_openqasm_gates() -> list[str]: "rccx", ] -def create_feature_dict(qc: QuantumCircuit, basis_gates: list[str], coupling_map) -> dict[str, int | NDArray[np.float64]]: +def create_feature_dict(qc: QuantumCircuit) -> dict[str, int | NDArray[np.float64]]: """Creates a feature dictionary for a given quantum circuit. Arguments: diff --git a/src/mqt/predictor/rl/predictorenv.py b/src/mqt/predictor/rl/predictorenv.py index 41d5121af..5b9a0c668 100644 --- a/src/mqt/predictor/rl/predictorenv.py +++ b/src/mqt/predictor/rl/predictorenv.py @@ -316,17 +316,18 @@ def apply_action(self, action_index: int) -> QuantumCircuit | None: raise ValueError(msg) def _apply_qiskit_action(self, action: Action, action_index: int) -> QuantumCircuit: - if action.get("stochastic", False): - metric_fn = lambda circ: circ.count_ops().get("swap", 0) + if getattr(action, "stochastic", False): + def metric_fn(circ: QuantumCircuit) -> float: + return float(circ.count_ops().get("swap", 0)) # for stochastic actions, pass the layout/routing trials parameter max_iteration = self.max_iter - if "Sabre" in action["name"] and "AIRouting" not in action["name"]: + if "Sabre" in action.name and "AIRouting" not in action.name: # Internal trials for Sabre transpile_pass = action.transpile_pass(self.device, max_iteration) pm = PassManager(transpile_pass) altered_qc = pm.run(self.state) pm_property_set = dict(pm.property_set) - elif "AIRouting" in action["name"]: + elif "AIRouting" in action.name: # Run AIRouting in custom loop altered_qc, pm_property_set = best_of_n_passmanager( action, From 5935e6f1fb288d8d03137ae03c538f2dc8dac74f Mon Sep 17 00:00:00 2001 From: Liu Keyu Date: Tue, 29 Jul 2025 18:54:01 +0200 Subject: [PATCH 05/57] Fix: resolve pre-commit issues and add missing annotations Fix: resolve pre-commit issues and add missing annotations Fix: resolve pre-commit issues and add missing annotations Fix: resolve pre-commit issues and add missing annotations --- pyproject.toml | 4 ++-- src/mqt/predictor/rl/helper.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5c66b12d5..4297fbfd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ dependencies = [ "numpy>=1.22,<2; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict numpy v2 for macOS x86 since it is not supported anymore since torch v2.3.0 "torch>=2.2.2,<2.3.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch v2.3.0 for macOS x86 since it is not supported anymore. "typing-extensions>=4.1", # for `assert_never` - "qiskit-ibm-transpiler>=0.2.0", + "qiskit-ibm-transpiler[ai-local-mode]>=0.2.0" ] classifiers = [ @@ -164,7 +164,7 @@ implicit_reexport = true # recent versions of `gym` are typed, but stable-baselines3 pins a very old version of gym. # qiskit is not yet marked as typed, but is typed mostly. # the other libraries do not have type stubs. -module = ["qiskit.*", "joblib.*", "sklearn.*", "matplotlib.*", "gymnasium.*", "mqt.bench.*", "sb3_contrib.*", "bqskit.*", "qiskit_ibm_runtime.*", "networkx.*", "stable_baselines3.*"] +module = ["qiskit.*", "joblib.*", "sklearn.*", "matplotlib.*", "gymnasium.*", "mqt.bench.*", "sb3_contrib.*", "bqskit.*", "qiskit_ibm_runtime.*", "networkx.*", "stable_baselines3.*","qiskit_ibm_transpiler.*"] ignore_missing_imports = true [tool.ruff] diff --git a/src/mqt/predictor/rl/helper.py b/src/mqt/predictor/rl/helper.py index 1ea9cf6a7..88351d673 100644 --- a/src/mqt/predictor/rl/helper.py +++ b/src/mqt/predictor/rl/helper.py @@ -17,10 +17,10 @@ import numpy as np from qiskit import QuantumCircuit from qiskit.converters import circuit_to_dag, dag_to_circuit -from qiskit.circuit import ClassicalRegister, QuantumRegister, Instruction +from qiskit.circuit import ClassicalRegister, QuantumRegister, Instruction, Qubit from qiskit.transpiler import PassManager, Target from qiskit.dagcircuit import DAGCircuit -from qiskit_ibm_transpiler.ai.routing import AIRouting +from qiskit_ibm_transpiler.ai.routing import AIRouting from mqt.predictor.utils import calc_supermarq_features from mqt.predictor.rl.actions import Action @@ -34,7 +34,7 @@ logger = logging.getLogger("mqt-predictor") -def extract_cregs_and_measurements(qc: QuantumCircuit) -> Tuple[List[ClassicalRegister], List[tuple[Instruction, List, List]]]: +def extract_cregs_and_measurements(qc: QuantumCircuit) -> Tuple[List[ClassicalRegister], List[tuple[Instruction, List[Any], List[Any]]]]: """ Extracts classical registers and measurement operations from a quantum circuit. @@ -78,8 +78,8 @@ def remove_cregs(qc: QuantumCircuit) -> QuantumCircuit: def add_cregs_and_measurements( qc: QuantumCircuit, cregs: List[ClassicalRegister], - measurements: List[Tuple[Instruction, List, List]], - qubit_map: Optional[Dict] = None, + measurements: List[Tuple[Instruction, List[Any], List[Any]]], + qubit_map: Optional[Dict[Qubit,Qubit]] = None, ) -> QuantumCircuit: """ Adds classical registers and measurement operations back to the quantum circuit. From f71fb29e59910af98db8cfbbecb62d9f4fda1d87 Mon Sep 17 00:00:00 2001 From: Liu Keyu Date: Wed, 30 Jul 2025 18:39:40 +0200 Subject: [PATCH 06/57] Fix: resolve pre-commit issues and add missing annotations --- src/mqt/predictor/rl/helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqt/predictor/rl/helper.py b/src/mqt/predictor/rl/helper.py index 88351d673..4438c1964 100644 --- a/src/mqt/predictor/rl/helper.py +++ b/src/mqt/predictor/rl/helper.py @@ -178,7 +178,7 @@ def best_of_n_passmanager( best_result = None best_property_set = None - if action["name"] == "SabreLayout+AIRouting": + if action.name == "SabreLayout+AIRouting": all_passes = action.transpile_pass(device, max_iteration) else: all_passes = action.transpile_pass(device) From 3c7592b9bfe7762627b1fb31f8ddfc0c021bb2e5 Mon Sep 17 00:00:00 2001 From: Liu Keyu Date: Wed, 30 Jul 2025 19:14:29 +0200 Subject: [PATCH 07/57] Fix: resolve pre-commit issues and add missing annotations --- src/mqt/predictor/rl/actions.py | 3 ++- src/mqt/predictor/rl/helper.py | 18 ++++++++++++++---- src/mqt/predictor/rl/predictorenv.py | 4 ++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/mqt/predictor/rl/actions.py b/src/mqt/predictor/rl/actions.py index de7d98a98..d330576be 100644 --- a/src/mqt/predictor/rl/actions.py +++ b/src/mqt/predictor/rl/actions.py @@ -46,7 +46,7 @@ ) from qiskit.passmanager import ConditionalController from qiskit.transpiler import ( - CouplingMap, + CouplingMap, Target ) from qiskit.transpiler.passes import ( ApplyLayout, @@ -126,6 +126,7 @@ class Action: transpile_pass: ( list[qiskit_BasePass | tket_BasePass] | Callable[..., list[qiskit_BasePass | tket_BasePass]] + | Callable[[Target, tuple[int, int]], list[qiskit_BasePass | tket_BasePass]] | Callable[ ..., Callable[..., tuple[Any, ...] | Circuit], diff --git a/src/mqt/predictor/rl/helper.py b/src/mqt/predictor/rl/helper.py index 4438c1964..1d7eb20ac 100644 --- a/src/mqt/predictor/rl/helper.py +++ b/src/mqt/predictor/rl/helper.py @@ -20,7 +20,6 @@ from qiskit.circuit import ClassicalRegister, QuantumRegister, Instruction, Qubit from qiskit.transpiler import PassManager, Target from qiskit.dagcircuit import DAGCircuit -from qiskit_ibm_transpiler.ai.routing import AIRouting from mqt.predictor.utils import calc_supermarq_features from mqt.predictor.rl.actions import Action @@ -28,6 +27,9 @@ if TYPE_CHECKING: from numpy.random import Generator from numpy.typing import NDArray + from qiskit_ibm_transpiler.ai.routing import AIRouting +else: + AIRouting = object # type: ignore[misc] import zipfile from importlib import resources @@ -103,7 +105,7 @@ def add_cregs_and_measurements( qc.append(instr, new_qargs, cargs) return qc -class SafeAIRouting(AIRouting): +class SafeAIRouting(AIRouting): # type: ignore[misc] """ Custom AIRouting wrapper that removes classical registers before routing. @@ -137,9 +139,15 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: qubit_map = {} for virt in qc_orig.qubits: - phys = final_layout[virt] + try: + phys = final_layout[virt] # This is now safe due to above check + except KeyError as err: + raise RuntimeError(f"Virtual qubit {virt} not found in final layout!") from err if isinstance(phys, int): - qubit_map[virt] = qc_routed.qubits[phys] + try: + qubit_map[virt] = qc_routed.qubits[phys] + except IndexError as err: + raise RuntimeError(f"Physical index {phys} is out of range in routed circuit!") from err else: try: idx = qc_routed.qubits.index(phys) @@ -183,6 +191,8 @@ def best_of_n_passmanager( else: all_passes = action.transpile_pass(device) + assert isinstance(all_passes, list) + layout_passes = all_passes[:-1] routing_pass = all_passes[-1:] diff --git a/src/mqt/predictor/rl/predictorenv.py b/src/mqt/predictor/rl/predictorenv.py index 5b9a0c668..cd44a37ec 100644 --- a/src/mqt/predictor/rl/predictorenv.py +++ b/src/mqt/predictor/rl/predictorenv.py @@ -361,7 +361,7 @@ def metric_fn(circ: QuantumCircuit) -> float: return altered_qc def _handle_qiskit_layout_postprocessing( - self, action: Action, pm_property_set: dict, altered_qc: QuantumCircuit, + self, action: Action, pm_property_set: dict[str, any], altered_qc: QuantumCircuit, ) -> QuantumCircuit: if action.name == "VF2PostLayout": assert pm_property_set["VF2PostLayout_stop_reason"] is not None @@ -379,7 +379,7 @@ def _handle_qiskit_layout_postprocessing( _output_qubit_list=altered_qc.qubits, _input_qubit_count=self.num_qubits_uncompiled_circuit, ) - if pm_property_set["final_layout"]: + if self.layout is not None and pm_property_set["final_layout"]: self.layout.final_layout = pm_property_set["final_layout"] return altered_qc From 6db5c27b45c2febcaab67957e940d7d6b70bcc34 Mon Sep 17 00:00:00 2001 From: Liu Keyu Date: Fri, 1 Aug 2025 19:42:43 +0200 Subject: [PATCH 08/57] Fix mypy errors --- pyproject.toml | 6 +- src/mqt/predictor/rl/actions.py | 523 +++++++++++++++++---------- src/mqt/predictor/rl/helper.py | 220 ++--------- src/mqt/predictor/rl/predictorenv.py | 82 +++-- 4 files changed, 419 insertions(+), 412 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4297fbfd2..825fcefb3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,8 @@ dependencies = [ "numpy>=1.22,<2; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict numpy v2 for macOS x86 since it is not supported anymore since torch v2.3.0 "torch>=2.2.2,<2.3.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch v2.3.0 for macOS x86 since it is not supported anymore. "typing-extensions>=4.1", # for `assert_never` - "qiskit-ibm-transpiler[ai-local-mode]>=0.2.0" + "qiskit-ibm-transpiler>=0.11.1", + "qiskit-ibm-ai-local-transpiler>=0.3.2" ] classifiers = [ @@ -248,3 +249,6 @@ fom = "fom" [tool.repo-review] ignore = ["GH200"] + +[tool.uv] +override-dependencies = ["networkx==2.8.5"] diff --git a/src/mqt/predictor/rl/actions.py b/src/mqt/predictor/rl/actions.py index d330576be..626921958 100644 --- a/src/mqt/predictor/rl/actions.py +++ b/src/mqt/predictor/rl/actions.py @@ -14,7 +14,7 @@ from collections import defaultdict from dataclasses import dataclass from enum import Enum -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from bqskit import MachineModel from bqskit import compile as bqskit_compile @@ -26,7 +26,8 @@ RemoveRedundancies, RoutingPass, ) -from qiskit.circuit import StandardEquivalenceLibrary +from qiskit import QuantumCircuit +from qiskit.circuit import ClassicalRegister, Instruction, QuantumRegister, Qubit, StandardEquivalenceLibrary from qiskit.circuit.library import ( CXGate, CYGate, @@ -44,14 +45,13 @@ YGate, ZGate, ) +from qiskit.converters import circuit_to_dag, dag_to_circuit from qiskit.passmanager import ConditionalController -from qiskit.transpiler import ( - CouplingMap, Target -) +from qiskit.transpiler import CouplingMap from qiskit.transpiler.passes import ( ApplyLayout, - BasisTranslator, BasicSwap, + BasisTranslator, Collect2qBlocks, CollectCliffords, CommutativeCancellation, @@ -78,20 +78,20 @@ ) from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason from qiskit.transpiler.preset_passmanagers import common +from qiskit_ibm_transpiler.ai.routing import AIRouting from mqt.predictor.rl.parsing import ( PreProcessTKETRoutingAfterQiskitLayout, get_bqskit_native_gates, ) -from mqt.predictor.rl.helper import SafeAIRouting, get_openqasm_gates - if TYPE_CHECKING: from collections.abc import Callable from typing import Any from bqskit import Circuit from pytket._tket.passes import BasePass as tket_BasePass + from qiskit.dagcircuit import DAGCircuit from qiskit.transpiler.basepasses import BasePass as qiskit_BasePass @@ -126,13 +126,12 @@ class Action: transpile_pass: ( list[qiskit_BasePass | tket_BasePass] | Callable[..., list[qiskit_BasePass | tket_BasePass]] - | Callable[[Target, tuple[int, int]], list[qiskit_BasePass | tket_BasePass]] | Callable[ ..., Callable[..., tuple[Any, ...] | Circuit], ] ) - stochastic: bool = False + stochastic: bool | None = False @dataclass @@ -183,6 +182,51 @@ def remove_action(name: str) -> None: del _ACTIONS[name] +def get_openqasm_gates() -> list[str]: + """Returns a list of all quantum gates within the openQASM 2.0 standard header. + + According to https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/qasm/libs/qelib1.inc + Removes generic single qubit gates u1, u2, u3 since they are no meaningful features for RL + + """ + return [ + "cx", + "id", + "u", + "p", + "x", + "y", + "z", + "h", + "r", + "s", + "sdg", + "t", + "tdg", + "rx", + "ry", + "rz", + "sx", + "sxdg", + "cz", + "cy", + "swap", + "ch", + "ccx", + "cswap", + "crx", + "cry", + "crz", + "cu1", + "cp", + "csx", + "cu", + "rxx", + "rzz", + "rccx", + ] + + register_action( DeviceIndependentAction( "Optimize1qGatesDecomposition", @@ -257,9 +301,11 @@ def remove_action(name: str) -> None: "Opt2qBlocks", CompilationOrigin.QISKIT, PassType.OPT, - transpile_pass=lambda device: [Collect2qBlocks(), - ConsolidateBlocks(basis_gates=device.operation_names), - UnitarySynthesis(basis_gates=device.operation_names, coupling_map=device.build_coupling_map())], + transpile_pass=lambda native_gate, coupling_map: [ + Collect2qBlocks(), + ConsolidateBlocks(basis_gates=native_gate), + UnitarySynthesis(basis_gates=native_gate, coupling_map=coupling_map), + ], ) ) @@ -405,16 +451,16 @@ def remove_action(name: str) -> None: PassType.MAPPING, stochastic=True, # Qiskit O3 by default uses (max_iterations, layout_trials, swap_trials) = (4, 20, 20) - transpile_pass=lambda device, max_iteration=(20,20): [ - SabreLayout( - coupling_map=CouplingMap(device.build_coupling_map()), - skip_routing=False, - layout_trials=max_iteration[0], - swap_trials=max_iteration[1], - max_iterations=4, - seed=None - ), - ], + transpile_pass=lambda device, max_iteration=(20, 20): [ + SabreLayout( + coupling_map=CouplingMap(device.build_coupling_map()), + skip_routing=False, + layout_trials=max_iteration[0], + swap_trials=max_iteration[1], + max_iterations=4, + seed=None, + ), + ], ) ) @@ -424,20 +470,20 @@ def remove_action(name: str) -> None: CompilationOrigin.QISKIT, PassType.MAPPING, stochastic=True, - transpile_pass=lambda device, max_iteration=(20,20): [ - SabreLayout( - coupling_map=CouplingMap(device.build_coupling_map()), - skip_routing=True, - layout_trials=max_iteration[0], - swap_trials=max_iteration[1], - max_iterations=4, - seed=None - ), - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - BasicSwap(coupling_map=CouplingMap(device.build_coupling_map())), - ], + transpile_pass=lambda device, max_iteration=(20, 20): [ + SabreLayout( + coupling_map=CouplingMap(device.build_coupling_map()), + skip_routing=True, + layout_trials=max_iteration[0], + swap_trials=max_iteration[1], + max_iterations=4, + seed=None, + ), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + BasicSwap(coupling_map=CouplingMap(device.build_coupling_map())), + ], ) ) @@ -447,25 +493,20 @@ def remove_action(name: str) -> None: CompilationOrigin.QISKIT, PassType.MAPPING, stochastic=True, - transpile_pass=lambda device, max_iteration=(20,20): [ - SabreLayout( - coupling_map=CouplingMap(device.build_coupling_map()), - skip_routing=True, - layout_trials=max_iteration[0], - swap_trials=max_iteration[1], - max_iterations=4, - seed=None - ), - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - SafeAIRouting( - coupling_map=device.build_coupling_map(), - optimization_level=3, - layout_mode="optimize", - local_mode=True - ), - ], + transpile_pass=lambda device, max_iteration=(20, 20): [ + SabreLayout( + coupling_map=CouplingMap(device.build_coupling_map()), + skip_routing=True, + layout_trials=max_iteration[0], + swap_trials=max_iteration[1], + max_iterations=4, + seed=None, + ), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + SafeAIRouting(coupling_map=device.build_coupling_map(), optimization_level=3, layout_mode="optimize"), + ], ) ) @@ -495,14 +536,14 @@ def remove_action(name: str) -> None: DeviceDependentAction( name="DenseLayout+BasicSwap", origin=CompilationOrigin.QISKIT, - pass_type=PassType.MAPPING, + pass_type=PassType.MAPPING, transpile_pass=lambda device: [ - DenseLayout(coupling_map=CouplingMap(device.build_coupling_map())), - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - BasicSwap(coupling_map=CouplingMap(device.build_coupling_map())), - ], + DenseLayout(coupling_map=CouplingMap(device.build_coupling_map())), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + BasicSwap(coupling_map=CouplingMap(device.build_coupling_map())), + ], ) ) @@ -511,14 +552,19 @@ def remove_action(name: str) -> None: name="DenseLayout+SabreSwap", origin=CompilationOrigin.QISKIT, pass_type=PassType.MAPPING, - stochastic=True, - transpile_pass=lambda device, max_iteration=(20,20): [ - DenseLayout(coupling_map=CouplingMap(device.build_coupling_map())), - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - SabreSwap(coupling_map=CouplingMap(device.build_coupling_map()), heuristic="decay", trials=max_iteration[1], seed=None), - ], + stochastic=True, + transpile_pass=lambda device, max_iteration=(20, 20): [ + DenseLayout(coupling_map=CouplingMap(device.build_coupling_map())), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + SabreSwap( + coupling_map=CouplingMap(device.build_coupling_map()), + heuristic="decay", + trials=max_iteration[1], + seed=None, + ), + ], ) ) @@ -526,20 +572,15 @@ def remove_action(name: str) -> None: DeviceDependentAction( name="DenseLayout+AIRouting", origin=CompilationOrigin.QISKIT, - pass_type=PassType.MAPPING, + pass_type=PassType.MAPPING, stochastic=True, transpile_pass=lambda device: [ - DenseLayout(coupling_map=CouplingMap(device.build_coupling_map())), - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - SafeAIRouting( - coupling_map=device.build_coupling_map(), - optimization_level=3, - layout_mode="optimize", - local_mode=True - ), - ], + DenseLayout(coupling_map=CouplingMap(device.build_coupling_map())), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + SafeAIRouting(coupling_map=device.build_coupling_map(), optimization_level=3, layout_mode="optimize"), + ], ) ) @@ -547,33 +588,34 @@ def remove_action(name: str) -> None: DeviceDependentAction( name="VF2Layout+BasicSwap", origin=CompilationOrigin.QISKIT, - pass_type=PassType.MAPPING, + pass_type=PassType.MAPPING, transpile_pass=lambda device: [ - VF2Layout( - coupling_map=CouplingMap(device.build_coupling_map()), - target=device, - ), - ConditionalController( - [ - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - ], - condition=lambda property_set: property_set["VF2Layout_stop_reason"] - == VF2LayoutStopReason.SOLUTION_FOUND, - ), - ConditionalController( - [ - TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - ], - # Run if VF2Layout did not find a solution - condition=lambda property_set: property_set["VF2Layout_stop_reason"] != VF2LayoutStopReason.SOLUTION_FOUND, - ), - BasicSwap(coupling_map=CouplingMap(device.build_coupling_map())), - ], + VF2Layout( + coupling_map=CouplingMap(device.build_coupling_map()), + target=device, + ), + ConditionalController( + [ + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + ], + condition=lambda property_set: property_set["VF2Layout_stop_reason"] + == VF2LayoutStopReason.SOLUTION_FOUND, + ), + ConditionalController( + [ + TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + ], + # Run if VF2Layout did not find a solution + condition=lambda property_set: property_set["VF2Layout_stop_reason"] + != VF2LayoutStopReason.SOLUTION_FOUND, + ), + BasicSwap(coupling_map=CouplingMap(device.build_coupling_map())), + ], ) ) @@ -581,34 +623,40 @@ def remove_action(name: str) -> None: DeviceDependentAction( name="VF2Layout+SabreSwap", origin=CompilationOrigin.QISKIT, - pass_type=PassType.MAPPING, + pass_type=PassType.MAPPING, stochastic=True, - transpile_pass=lambda device, max_iteration=(20,20): [ - VF2Layout( - coupling_map=CouplingMap(device.build_coupling_map()), - target=device, - ), - ConditionalController( - [ - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - ], - condition=lambda property_set: property_set["VF2Layout_stop_reason"] - == VF2LayoutStopReason.SOLUTION_FOUND, - ), - ConditionalController( - [ - TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - ], - # Run if VF2Layout did not find a solution - condition=lambda property_set: property_set["VF2Layout_stop_reason"] != VF2LayoutStopReason.SOLUTION_FOUND, - ), - SabreSwap(coupling_map=CouplingMap(device.build_coupling_map()), heuristic="decay", trials=max_iteration[1], seed=None), - ], + transpile_pass=lambda device, max_iteration=(20, 20): [ + VF2Layout( + coupling_map=CouplingMap(device.build_coupling_map()), + target=device, + ), + ConditionalController( + [ + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + ], + condition=lambda property_set: property_set["VF2Layout_stop_reason"] + == VF2LayoutStopReason.SOLUTION_FOUND, + ), + ConditionalController( + [ + TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + ], + # Run if VF2Layout did not find a solution + condition=lambda property_set: property_set["VF2Layout_stop_reason"] + != VF2LayoutStopReason.SOLUTION_FOUND, + ), + SabreSwap( + coupling_map=CouplingMap(device.build_coupling_map()), + heuristic="decay", + trials=max_iteration[1], + seed=None, + ), + ], ) ) @@ -616,65 +664,34 @@ def remove_action(name: str) -> None: DeviceDependentAction( name="VF2Layout+AIRouting", origin=CompilationOrigin.QISKIT, - pass_type=PassType.MAPPING, - stochastic=True, - transpile_pass=lambda device:[ - VF2Layout( - coupling_map=CouplingMap(device.build_coupling_map()), - target=device, - ), - ConditionalController( - [ - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - ], - condition=lambda property_set: property_set["VF2Layout_stop_reason"] - == VF2LayoutStopReason.SOLUTION_FOUND, - ), - ConditionalController( - [ - TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - ], - # Run if VF2Layout did not find a solution - condition=lambda property_set: property_set["VF2Layout_stop_reason"] != VF2LayoutStopReason.SOLUTION_FOUND, - ), - SafeAIRouting( - coupling_map=device.build_coupling_map(), - optimization_level=3, - layout_mode="optimize", - local_mode=True - ), - ], - ) -) - -register_action( - DeviceDependentAction( - name="SabreLayout+AIRouting", - origin=CompilationOrigin.QISKIT, + pass_type=PassType.MAPPING, stochastic=True, - pass_type=PassType.MAPPING, - transpile_pass=lambda device, max_iteration=(20, 20): [ - SabreLayout( - coupling_map=CouplingMap(device.build_coupling_map()), - skip_routing=True, - layout_trials=max_iteration[0], - swap_trials=1, - max_iterations=4, + transpile_pass=lambda device: [ + VF2Layout( + coupling_map=CouplingMap(device.build_coupling_map()), + target=device, ), - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - SafeAIRouting( - coupling_map=device.build_coupling_map(), - optimization_level=3, - layout_mode="optimize", - local_mode=True, + ConditionalController( + [ + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + ], + condition=lambda property_set: property_set["VF2Layout_stop_reason"] + == VF2LayoutStopReason.SOLUTION_FOUND, ), + ConditionalController( + [ + TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + ], + # Run if VF2Layout did not find a solution + condition=lambda property_set: property_set["VF2Layout_stop_reason"] + != VF2LayoutStopReason.SOLUTION_FOUND, + ), + SafeAIRouting(coupling_map=device.build_coupling_map(), optimization_level=3, layout_mode="optimize"), ], ) ) @@ -723,3 +740,125 @@ def get_actions_by_pass_type() -> dict[PassType, list[Action]]: for action in _ACTIONS.values(): result[action.pass_type].append(action) return result + + +def extract_cregs_and_measurements( + qc: QuantumCircuit, +) -> tuple[list[ClassicalRegister], list[tuple[Instruction, list[Any], list[Any]]]]: + """Extracts classical registers and measurement operations from a quantum circuit. + + Args: + qc: The input QuantumCircuit. + + Returns: + A tuple containing a list of classical registers and a list of measurement operations. + """ + cregs = [ClassicalRegister(cr.size, name=cr.name) for cr in qc.cregs] + measurements = [(item.operation, item.qubits, item.clbits) for item in qc.data if item.operation.name == "measure"] + return cregs, measurements + + +def remove_cregs(qc: QuantumCircuit) -> QuantumCircuit: + """Removes classical registers and measurement operations from the circuit. + + Args: + qc: The input QuantumCircuit. + + Returns: + A new QuantumCircuit with only quantum operations (no cregs or measurements). + """ + qregs = [QuantumRegister(qr.size, name=qr.name) for qr in qc.qregs] + new_qc = QuantumCircuit(*qregs) + old_to_new = {} + for orig_qr, new_qr in zip(qc.qregs, new_qc.qregs, strict=False): + for idx in range(orig_qr.size): + old_to_new[orig_qr[idx]] = new_qr[idx] + for item in qc.data: + instr = item.operation + qargs = [old_to_new[q] for q in item.qubits] + if instr.name not in ("measure", "barrier"): + new_qc.append(instr, qargs) + return new_qc + + +def add_cregs_and_measurements( + qc: QuantumCircuit, + cregs: list[ClassicalRegister], + measurements: list[tuple[Instruction, list[Any], list[Any]]], + qubit_map: dict[Qubit, Qubit] | None = None, +) -> QuantumCircuit: + """Adds classical registers and measurement operations back to the quantum circuit. + + Args: + qc: The quantum circuit to which cregs and measurements are added. + cregs: List of ClassicalRegister to add. + measurements: List of measurement instructions as tuples (Instruction, qubits, clbits). + qubit_map: Optional dictionary mapping original qubits to new qubits. + + Returns: + The modified QuantumCircuit with cregs and measurements added. + """ + for cr in cregs: + qc.add_register(cr) + for instr, qargs, cargs in measurements: + new_qargs = [qubit_map[q] for q in qargs] if qubit_map else qargs + qc.append(instr, new_qargs, cargs) + return qc + + +class SafeAIRouting(AIRouting): # type: ignore[misc] + """Custom AIRouting wrapper that removes classical registers before routing. + + This prevents failures in AIRouting when classical bits are present by + temporarily removing classical registers and measurements and restoring + them after routing is completed. + """ + + def run(self, dag: DAGCircuit) -> DAGCircuit: + """Run the routing pass on a DAGCircuit.""" + # 1. Convert input dag to circuit + qc_orig = dag_to_circuit(dag) + + # 2. Extract classical registers and measurement instructions + cregs, measurements = extract_cregs_and_measurements(qc_orig) + + # 3. Remove cregs and measurements + qc_noclassical = remove_cregs(qc_orig) + + # 4. Convert back to dag and run routing (AIRouting) + dag_noclassical = circuit_to_dag(qc_noclassical) + dag_routed = super().run(dag_noclassical) + + # 5. Convert routed dag to circuit for restoration + qc_routed = dag_to_circuit(dag_routed) + + # 6. Build mapping from original qubits to qubits in routed circuit + final_layout = getattr(self, "property_set", {}).get("final_layout", None) + if final_layout is None and hasattr(dag_routed, "property_set"): + final_layout = dag_routed.property_set.get("final_layout", None) + + assert final_layout is not None, "final_layout is None — cannot map virtual qubits" + qubit_map = {} + for virt in qc_orig.qubits: + try: + phys = final_layout[virt] + except KeyError as err: + msg = f"Virtual qubit {virt} not found in final layout!" + raise RuntimeError(msg) from err + if isinstance(phys, int): + try: + qubit_map[virt] = qc_routed.qubits[phys] + except IndexError as err: + msg = f"Physical index {phys} is out of range in routed circuit!" + raise RuntimeError(msg) from err + else: + try: + idx = qc_routed.qubits.index(phys) + except ValueError as err: + msg = f"Physical qubit {phys} not found in output circuit!" + raise RuntimeError(msg) from err + qubit_map[virt] = qc_routed.qubits[idx] + # 7. Restore classical registers and measurement instructions + qc_final = add_cregs_and_measurements(qc_routed, cregs, measurements, qubit_map) + # 8. Return as dag + return circuit_to_dag(qc_final) diff --git a/src/mqt/predictor/rl/helper.py b/src/mqt/predictor/rl/helper.py index 1d7eb20ac..558ba262f 100644 --- a/src/mqt/predictor/rl/helper.py +++ b/src/mqt/predictor/rl/helper.py @@ -12,162 +12,38 @@ import logging from pathlib import Path -from typing import TYPE_CHECKING, Optional, Callable, List, Tuple, Dict, Any +from typing import TYPE_CHECKING, Any import numpy as np from qiskit import QuantumCircuit -from qiskit.converters import circuit_to_dag, dag_to_circuit -from qiskit.circuit import ClassicalRegister, QuantumRegister, Instruction, Qubit from qiskit.transpiler import PassManager, Target -from qiskit.dagcircuit import DAGCircuit from mqt.predictor.utils import calc_supermarq_features -from mqt.predictor.rl.actions import Action if TYPE_CHECKING: + from collections.abc import Callable + from numpy.random import Generator from numpy.typing import NDArray - from qiskit_ibm_transpiler.ai.routing import AIRouting -else: - AIRouting = object # type: ignore[misc] + + from mqt.predictor.rl.actions import Action + + # from mqt.predictor.rl.actions import Action import zipfile from importlib import resources logger = logging.getLogger("mqt-predictor") -def extract_cregs_and_measurements(qc: QuantumCircuit) -> Tuple[List[ClassicalRegister], List[tuple[Instruction, List[Any], List[Any]]]]: - """ - Extracts classical registers and measurement operations from a quantum circuit. - - Args: - qc: The input QuantumCircuit. - - Returns: - A tuple containing a list of classical registers and a list of measurement operations. - """ - cregs = [ClassicalRegister(cr.size, name=cr.name) for cr in qc.cregs] - measurements = [ - (item.operation, item.qubits, item.clbits) - for item in qc.data - if item.operation.name == "measure" - ] - return cregs, measurements - -def remove_cregs(qc: QuantumCircuit) -> QuantumCircuit: - """ - Removes classical registers and measurement operations from the circuit. - - Args: - qc: The input QuantumCircuit. - - Returns: - A new QuantumCircuit with only quantum operations (no cregs or measurements). - """ - qregs = [QuantumRegister(qr.size, name=qr.name) for qr in qc.qregs] - new_qc = QuantumCircuit(*qregs) - old_to_new = {} - for orig_qr, new_qr in zip(qc.qregs, new_qc.qregs): - for idx in range(orig_qr.size): - old_to_new[orig_qr[idx]] = new_qr[idx] - for item in qc.data: - instr = item.operation - qargs = [old_to_new[q] for q in item.qubits] - if instr.name not in ("measure", "barrier"): - new_qc.append(instr, qargs) - return new_qc - -def add_cregs_and_measurements( - qc: QuantumCircuit, - cregs: List[ClassicalRegister], - measurements: List[Tuple[Instruction, List[Any], List[Any]]], - qubit_map: Optional[Dict[Qubit,Qubit]] = None, -) -> QuantumCircuit: - """ - Adds classical registers and measurement operations back to the quantum circuit. - - Args: - qc: The quantum circuit to which cregs and measurements are added. - cregs: List of ClassicalRegister to add. - measurements: List of measurement instructions as tuples (Instruction, qubits, clbits). - qubit_map: Optional dictionary mapping original qubits to new qubits. - - Returns: - The modified QuantumCircuit with cregs and measurements added. - """ - for cr in cregs: - qc.add_register(cr) - for instr, qargs, cargs in measurements: - if qubit_map: - new_qargs = [qubit_map[q] for q in qargs] - else: - new_qargs = qargs - qc.append(instr, new_qargs, cargs) - return qc - -class SafeAIRouting(AIRouting): # type: ignore[misc] - """ - Custom AIRouting wrapper that removes classical registers before routing. - - This prevents failures in AIRouting when classical bits are present by - temporarily removing classical registers and measurements and restoring - them after routing is completed. - """ - def run(self, dag: DAGCircuit) -> DAGCircuit: - """Run the routing pass on a DAGCircuit.""" - - # 1. Convert input dag to circuit - qc_orig = dag_to_circuit(dag) - - # 2. Extract classical registers and measurement instructions - cregs, measurements = extract_cregs_and_measurements(qc_orig) - - # 3. Remove cregs and measurements - qc_noclassical = remove_cregs(qc_orig) - - # 4. Convert back to dag and run routing (AIRouting) - dag_noclassical = circuit_to_dag(qc_noclassical) - dag_routed = super().run(dag_noclassical) - - # 5. Convert routed dag to circuit for restoration - qc_routed = dag_to_circuit(dag_routed) - - # 6. Build mapping from original qubits to qubits in routed circuit - final_layout = getattr(self, "property_set", {}).get("final_layout", None) - if final_layout is None and hasattr(dag_routed, "property_set"): - final_layout = dag_routed.property_set.get("final_layout", None) - - qubit_map = {} - for virt in qc_orig.qubits: - try: - phys = final_layout[virt] # This is now safe due to above check - except KeyError as err: - raise RuntimeError(f"Virtual qubit {virt} not found in final layout!") from err - if isinstance(phys, int): - try: - qubit_map[virt] = qc_routed.qubits[phys] - except IndexError as err: - raise RuntimeError(f"Physical index {phys} is out of range in routed circuit!") from err - else: - try: - idx = qc_routed.qubits.index(phys) - except ValueError as err: - raise RuntimeError(f"Physical qubit {phys} not found in output circuit!") from err - qubit_map[virt] = qc_routed.qubits[idx] - # 7. Restore classical registers and measurement instructions - qc_final = add_cregs_and_measurements(qc_routed, cregs, measurements, qubit_map) - # 8. Return as dag - return circuit_to_dag(qc_final) def best_of_n_passmanager( action: Action, device: Target, qc: QuantumCircuit, - max_iteration: Tuple[int, int] = (20, 20), - metric_fn: Optional[Callable[[QuantumCircuit], float]] = None, -)-> tuple[QuantumCircuit, Dict[str, Any]]: - """ - Runs the given transpile_pass multiple times and keeps the best result. + max_iteration: tuple[int, int] = (20, 20), + metric_fn: Callable[[QuantumCircuit], float] | None = None, +) -> tuple[QuantumCircuit, dict[str, Any]]: + """Runs the given transpile_pass multiple times and keeps the best result. Args: action: The action dictionary with a 'transpile_pass' key @@ -186,12 +62,21 @@ def best_of_n_passmanager( best_result = None best_property_set = None - if action.name == "SabreLayout+AIRouting": - all_passes = action.transpile_pass(device, max_iteration) + if callable(action.transpile_pass): + try: + if action.name == "SabreLayout+AIRouting": + all_passes = action.transpile_pass(device, max_iteration) + else: + all_passes = action.transpile_pass(device) + except TypeError as e: + msg = f"Error calling transpile_pass for {action.name}: {e}" + raise ValueError(msg) from e else: - all_passes = action.transpile_pass(device) + all_passes = action.transpile_pass - assert isinstance(all_passes, list) + if not isinstance(all_passes, list): + msg = f"Expected list of passes, got {type(all_passes)}" + raise TypeError(msg) layout_passes = all_passes[:-1] routing_pass = all_passes[-1:] @@ -201,7 +86,7 @@ def best_of_n_passmanager( try: layouted_qc = layout_pm.run(qc) layout_props = dict(layout_pm.property_set) - except Exception as e: + except Exception: return qc, {} # Run routing multiple times and optimize for the given metric @@ -220,14 +105,16 @@ def best_of_n_passmanager( if best_val == 0: break except Exception as e: - print(f"[Routing] Trial {i+1} failed: {e}") + print(f"[Routing] Trial {i + 1} failed: {e}") continue if best_result is not None: + if best_property_set is None: + best_property_set = {} return best_result, best_property_set - else: - print("All mapping attempts failed; returning original circuit.") - return qc, {} - + print("All mapping attempts failed; returning original circuit.") + return qc, {} + + def get_state_sample(max_qubits: int, path_training_circuits: Path, rng: Generator) -> tuple[QuantumCircuit, str]: """Returns a random quantum circuit from the training circuits folder. @@ -267,49 +154,6 @@ def get_state_sample(max_qubits: int, path_training_circuits: Path, rng: Generat return qc, str(file_list[random_index]) -def get_openqasm_gates() -> list[str]: - """ Returns a list of all quantum gates within the openQASM 2.0 standard header. - - According to https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/qasm/libs/qelib1.inc - Removes generic single qubit gates u1, u2, u3 since they are no meaningful features for RL - - """ - return [ - "cx", - "id", - "u", - "p", - "x", - "y", - "z", - "h", - "r", - "s", - "sdg", - "t", - "tdg", - "rx", - "ry", - "rz", - "sx", - "sxdg", - "cz", - "cy", - "swap", - "ch", - "ccx", - "cswap", - "crx", - "cry", - "crz", - "cu1", - "cp", - "csx", - "cu", - "rxx", - "rzz", - "rccx", - ] def create_feature_dict(qc: QuantumCircuit) -> dict[str, int | NDArray[np.float64]]: """Creates a feature dictionary for a given quantum circuit. diff --git a/src/mqt/predictor/rl/predictorenv.py b/src/mqt/predictor/rl/predictorenv.py index cd44a37ec..b152f2c47 100644 --- a/src/mqt/predictor/rl/predictorenv.py +++ b/src/mqt/predictor/rl/predictorenv.py @@ -39,11 +39,9 @@ from pytket.circuit import Qubit from pytket.extensions.qiskit import qiskit_to_tk, tk_to_qiskit from qiskit import QuantumCircuit, transpile -from qiskit.circuit.library import RCCXGate from qiskit.passmanager.flow_controllers import DoWhileController from qiskit.transpiler import CouplingMap, PassManager, Target, TranspileLayout from qiskit.transpiler.passes import CheckMap, GatesInBasis -from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason from mqt.predictor.hellinger import get_hellinger_model_path from mqt.predictor.reward import ( @@ -54,8 +52,19 @@ expected_fidelity, figure_of_merit, ) -from mqt.predictor.rl.actions import CompilationOrigin, DeviceDependentAction, PassType, get_actions_by_pass_type -from mqt.predictor.rl.helper import create_feature_dict, get_path_training_circuits, get_state_sample, get_openqasm_gates, best_of_n_passmanager +from mqt.predictor.rl.actions import ( + CompilationOrigin, + DeviceDependentAction, + PassType, + get_actions_by_pass_type, + get_openqasm_gates, +) +from mqt.predictor.rl.helper import ( + best_of_n_passmanager, + create_feature_dict, + get_path_training_circuits, + get_state_sample, +) from mqt.predictor.rl.parsing import ( final_layout_bqskit_to_qiskit, final_layout_pytket_to_qiskit, @@ -90,6 +99,8 @@ def __init__( self.action_set = {} self.actions_synthesis_indices = [] + self.actions_layout_indices = [] + self.actions_routing_indices = [] self.actions_mapping_indices = [] self.actions_opt_indices = [] self.actions_final_optimization_indices = [] @@ -113,6 +124,14 @@ def __init__( self.action_set[index] = elem self.actions_opt_indices.append(index) index += 1 + for elem in action_dict[PassType.LAYOUT]: + self.action_set[index] = elem + self.actions_layout_indices.append(index) + index += 1 + for elem in action_dict[PassType.ROUTING]: + self.action_set[index] = elem + self.actions_routing_indices.append(index) + index += 1 for elem in action_dict[PassType.MAPPING]: self.action_set[index] = elem self.actions_mapping_indices.append(index) @@ -154,7 +173,7 @@ def __init__( } self.observation_space = Dict(spaces) self.filename = "" - self.max_iter = (20,20) # (layout_trials, routing _trials) + self.max_iter = (20, 20) # (layout_trials, routing _trials) def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any, Any]]: """Executes the given action and returns the new state, the reward, whether the episode is done, whether the episode is truncated and additional information. @@ -317,12 +336,15 @@ def apply_action(self, action_index: int) -> QuantumCircuit | None: def _apply_qiskit_action(self, action: Action, action_index: int) -> QuantumCircuit: if getattr(action, "stochastic", False): + def metric_fn(circ: QuantumCircuit) -> float: return float(circ.count_ops().get("swap", 0)) + # for stochastic actions, pass the layout/routing trials parameter max_iteration = self.max_iter if "Sabre" in action.name and "AIRouting" not in action.name: # Internal trials for Sabre + assert callable(action.transpile_pass) transpile_pass = action.transpile_pass(self.device, max_iteration) pm = PassManager(transpile_pass) altered_qc = pm.run(self.state) @@ -330,19 +352,22 @@ def metric_fn(circ: QuantumCircuit) -> float: elif "AIRouting" in action.name: # Run AIRouting in custom loop altered_qc, pm_property_set = best_of_n_passmanager( - action, + action, self.device, - self.state, + self.state, max_iteration=max_iteration, metric_fn=metric_fn, ) else: - if action.name == "QiskitO3" and isinstance(action, DeviceDependentAction): + if action.name in ["QiskitO3", "Opt2qBlocks"] and isinstance(action, DeviceDependentAction): passes = action.transpile_pass( self.device.operation_names, CouplingMap(self.device.build_coupling_map()) if self.layout else None, ) - pm = PassManager([DoWhileController(passes, do_while=action.do_while)]) + if action.name == "QiskitO3": + pm = PassManager([DoWhileController(passes, do_while=action.do_while)]) + else: + pm = PassManager(passes) altered_qc = pm.run(self.state) pm_property_set = dict(pm.property_set) if hasattr(pm, "property_set") else {} else: @@ -352,16 +377,17 @@ def metric_fn(circ: QuantumCircuit) -> float: pm = PassManager(transpile_pass) altered_qc = pm.run(self.state) pm_property_set = dict(pm.property_set) if hasattr(pm, "property_set") else {} - if action_index in ( - self.actions_mapping_indices + self.actions_final_optimization_indices - ): + if action_index in (self.actions_mapping_indices + self.actions_final_optimization_indices): pm_property_set = dict(pm.property_set) altered_qc = self._handle_qiskit_layout_postprocessing(action, pm_property_set, altered_qc) - + return altered_qc - + def _handle_qiskit_layout_postprocessing( - self, action: Action, pm_property_set: dict[str, any], altered_qc: QuantumCircuit, + self, + action: Action, + pm_property_set: dict[str, Any], + altered_qc: QuantumCircuit, ) -> QuantumCircuit: if action.name == "VF2PostLayout": assert pm_property_set["VF2PostLayout_stop_reason"] is not None @@ -384,9 +410,6 @@ def _handle_qiskit_layout_postprocessing( return altered_qc def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircuit: - if any(isinstance(gate[0], RCCXGate) for gate in self.state.data): - # RCCX could cause error - self.state = transpile(self.state, basis_gates=get_openqasm_gates()[:-1], optimization_level=0) tket_qc = qiskit_to_tk(self.state, preserve_param_uuid=True) transpile_pass = ( action.transpile_pass(self.device) if callable(action.transpile_pass) else action.transpile_pass @@ -397,14 +420,13 @@ def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircui qbs = tket_qc.qubits tket_qc.rename_units({qbs[i]: Qubit("q", i) for i in range(len(qbs))}) - altered_qc = tk_to_qiskit(tket_qc) + altered_qc = tk_to_qiskit(tket_qc, replace_implicit_swaps=True) if action_index in self.actions_routing_indices: assert self.layout is not None self.layout.final_layout = final_layout_pytket_to_qiskit(tket_qc, altered_qc) # Decompose to the allowed gates (by default generic u gates are used) - altered_qc = transpile(altered_qc, basis_gates=get_openqasm_gates(), optimization_level=0) - return altered_qc + return transpile(altered_qc, basis_gates=get_openqasm_gates(), optimization_level=0) def _apply_bqskit_action(self, action: Action, action_index: int) -> QuantumCircuit: """Applies the given BQSKit action to the current state and returns the altered state. @@ -444,22 +466,20 @@ def _apply_bqskit_action(self, action: Action, action_index: int) -> QuantumCirc def determine_valid_actions_for_state(self) -> list[int]: """Determines and returns the valid actions for the current state.""" - check_nat_gates = GatesInBasis(basis_gates=self.device.basis_gates) + check_nat_gates = GatesInBasis(target=self.device) check_nat_gates(self.state) only_nat_gates = check_nat_gates.property_set["all_gates_in_basis"] - check_mapping = CheckMap(coupling_map=CouplingMap(self.device.coupling_map)) + check_mapping = CheckMap(coupling_map=CouplingMap(self.device.build_coupling_map())) check_mapping(self.state) mapped = check_mapping.property_set["is_swap_mapped"] - if not only_nat_gates: # not native gates yet - actions = self.actions_synthesis_indices + self.actions_opt_indices - return actions + if not only_nat_gates: # not native gates yet + return self.actions_synthesis_indices + self.actions_opt_indices if mapped and self.layout is not None: # The circuit is correctly mapped. return [self.action_terminate_index, *self.actions_opt_indices, *self.actions_final_optimization_indices] - else: - # The circuit is not mapped yet - # Or the circuit was mapped but some optimization actions change its structure and the circuit is again unmapped - # In this case, re-do mapping completely as the previous layout is not optimal on the new structure - return self.actions_mapping_indices + self.actions_opt_indices \ No newline at end of file + # The circuit is not mapped yet + # Or the circuit was mapped but some optimization actions change its structure and the circuit is again unmapped + # In this case, re-do mapping completely as the previous layout is not optimal on the new structure + return self.actions_mapping_indices + self.actions_opt_indices From 47841c5824e1f89304f54bd1379e72e2d8a7f6a2 Mon Sep 17 00:00:00 2001 From: Liu Keyu Date: Fri, 1 Aug 2025 19:46:36 +0200 Subject: [PATCH 09/57] Fix mypy errors --- uv.lock | 205 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 128 insertions(+), 77 deletions(-) diff --git a/uv.lock b/uv.lock index 64725fb60..215a0a69d 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.13'", @@ -11,6 +11,9 @@ resolution-markers = [ "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')", ] +[manifest] +overrides = [{ name = "networkx", specifier = "==2.8.5" }] + [[package]] name = "absl-py" version = "2.3.1" @@ -107,6 +110,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, ] +[[package]] +name = "backoff" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, +] + [[package]] name = "beautifulsoup4" version = "4.13.4" @@ -1492,8 +1504,7 @@ name = "mqt-bench" version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "networkx", version = "3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "networkx" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin')" }, { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13' and 'x86_64' not in platform_machine) or (python_full_version >= '3.11' and sys_platform != 'darwin') or (python_full_version >= '3.13' and sys_platform == 'darwin')" }, { name = "qiskit", extra = ["qasm3-import"] }, @@ -1515,6 +1526,9 @@ dependencies = [ { name = "pytket" }, { name = "pytket-qiskit" }, { name = "qiskit" }, + { name = "qiskit-ibm-ai-local-transpiler" }, + { name = "qiskit-ibm-transpiler" }, + { name = "rich" }, { name = "sb3-contrib", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, { name = "sb3-contrib", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, { name = "scikit-learn" }, @@ -1570,6 +1584,9 @@ requires-dist = [ { name = "pytket", specifier = ">=1.29.0" }, { name = "pytket-qiskit", specifier = ">=0.60.0" }, { name = "qiskit", specifier = "!=1.3.2" }, + { name = "qiskit-ibm-ai-local-transpiler", specifier = ">=0.3.2" }, + { name = "qiskit-ibm-transpiler", specifier = ">=0.11.1" }, + { name = "rich", specifier = ">=12.6.0" }, { name = "sb3-contrib", specifier = ">=2.0.0" }, { name = "scikit-learn", specifier = ">=1.5.1" }, { name = "tensorboard", specifier = ">=2.17.0" }, @@ -1696,31 +1713,11 @@ wheels = [ [[package]] name = "networkx" -version = "3.4.2" +version = "2.8.5" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')", -] -sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/af/fe794d69bcc4882c4905b49c8b207b8dc4dd7ea26e6142781eac8203ea05/networkx-2.8.5.tar.gz", hash = "sha256:15a7b81a360791c458c55a417418ea136c13378cfdc06a2dcdc12bd2f9cf09c1", size = 1955125, upload-time = "2022-07-18T21:25:02.609Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, -] - -[[package]] -name = "networkx" -version = "3.5" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.13'", - "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", - "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", -] -sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065, upload-time = "2025-05-29T11:35:07.804Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, + { url = "https://files.pythonhosted.org/packages/95/1b/14b5b17c52f7329b875e4ad2dcad23c808778b42ef6d250a7223d4dc378a/networkx-2.8.5-py3-none-any.whl", hash = "sha256:a762f4b385692d9c3a6f2912d058d76d29a827deaedf9e63ed14d397b8030687", size = 2020253, upload-time = "2022-07-18T21:24:59.008Z" }, ] [[package]] @@ -2493,8 +2490,7 @@ dependencies = [ { name = "graphviz" }, { name = "jinja2" }, { name = "lark" }, - { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "networkx", version = "3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "networkx" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin')" }, { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13' and 'x86_64' not in platform_machine) or (python_full_version >= '3.11' and sys_platform != 'darwin') or (python_full_version >= '3.13' and sys_platform == 'darwin')" }, { name = "qwasm" }, @@ -2523,7 +2519,7 @@ wheels = [ [[package]] name = "pytket-qiskit" -version = "0.71.0" +version = "0.66.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin')" }, @@ -2532,10 +2528,9 @@ dependencies = [ { name = "qiskit" }, { name = "qiskit-aer" }, { name = "qiskit-ibm-runtime" }, - { name = "symengine" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/31/7150385fe20c9d946d73a2251f50630ddf7e612e99f8427d06cb91932e20/pytket_qiskit-0.71.0-py3-none-any.whl", hash = "sha256:4e64f44fdc1cbd4c1f94d4f9de59c7b393ff1cda639c06de5cad01c770fc3000", size = 57187, upload-time = "2025-07-21T09:27:09.443Z" }, + { url = "https://files.pythonhosted.org/packages/73/4d/6395c0a6a2e6f08f493b031887aadc2b276d12b21c3b9b6f30036a43b498/pytket_qiskit-0.66.0-py3-none-any.whl", hash = "sha256:15d72ae6190e2280356e64bf387ca2d2d51714c395739b364fe3bbf9bd4bd096", size = 56193, upload-time = "2025-03-26T15:18:24.58Z" }, ] [[package]] @@ -2675,7 +2670,7 @@ wheels = [ [[package]] name = "qiskit" -version = "2.1.1" +version = "1.4.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dill" }, @@ -2686,17 +2681,21 @@ dependencies = [ { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "scipy", version = "1.16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "stevedore" }, + { name = "symengine" }, + { name = "sympy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/39/97/903041c27db9105f5036a579b2b7ee2a0caa21c6061fa29e0ccbd35aa19a/qiskit-2.1.1.tar.gz", hash = "sha256:148f62d314cd138a1f4da305c6293b19c73115323e77123b13a048cdcbc1fcdd", size = 3600302, upload-time = "2025-07-10T21:30:11.665Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/ac/25993f01e39cf04c9dccfd83f9b0817b5b8b1766f4afb8c34e6fba85155f/qiskit-1.4.3.tar.gz", hash = "sha256:e4e311190c159578f253d219dc860241ccb71955ca08725bb092c74cf7fbdac5", size = 3939859, upload-time = "2025-05-12T15:28:05.09Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/3a/26a99184f830be25a14ef7cbc40f19b98721ac018aa0c4db18c5dedc774c/qiskit-2.1.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:00f8fe1980a80e8e79e01352f2502cd3ca4fe89d07872bc9a244ba9cba304a7d", size = 7283884, upload-time = "2025-07-10T21:30:02.576Z" }, - { url = "https://files.pythonhosted.org/packages/60/a1/2a63ab52a5166346599df7bec2e41fd2be9876ccf01637c73603beebdcb5/qiskit-2.1.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:e00985d1b348bf8cc60b686a64a150af2043538e81a61b48400ff71064588fdd", size = 6817779, upload-time = "2025-07-10T21:30:04.513Z" }, - { url = "https://files.pythonhosted.org/packages/ba/87/1a184f6dcd614f57362d9b0be1c718ff857c1a3d69ca0f426c84adda6755/qiskit-2.1.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10f546148c86fd15cdbc68270e1d3937a021bd718779dff95f912c8b7cfad7bd", size = 7437467, upload-time = "2025-07-10T21:30:06.524Z" }, - { url = "https://files.pythonhosted.org/packages/82/d7/d0fc92d20b266692db8aedc5e259a2d4cff76613ec155178f855144c7a71/qiskit-2.1.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3765cef67ccc8b3d54b86ede2266c282039da045be1b10e64660a4bbf815680", size = 7696275, upload-time = "2025-07-11T13:15:14.672Z" }, - { url = "https://files.pythonhosted.org/packages/ba/30/ba384695bc9c9cb5795ef3ab47f33eb579460dc928115918d4eb0046ae9f/qiskit-2.1.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca95a1ab710eb3e0fc65dd876734b5c6fc6df1bb78249f86913d77ed1984983f", size = 7411176, upload-time = "2025-07-11T13:15:16.62Z" }, - { url = "https://files.pythonhosted.org/packages/75/dc/551c6afe0aa087ebe53988173290904ded5bf93e56b22503d8311d421d82/qiskit-2.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b41c66958c2e0550bc1047524cd754dce2277bea0a1db1085c052cc5bb07ec6", size = 7458353, upload-time = "2025-07-10T21:30:08.223Z" }, - { url = "https://files.pythonhosted.org/packages/83/6c/988791f56e6ab2999e1a4c7e60831b0f02c3e29a467400bced501ce6b637/qiskit-2.1.1-cp39-abi3-win_amd64.whl", hash = "sha256:e7225798221ecf0c8a5cf2e96c37d6d9d00674a412e8c397542fe387909b80db", size = 7234838, upload-time = "2025-07-10T21:30:10.121Z" }, + { url = "https://files.pythonhosted.org/packages/66/d5/126a6ddc7a9138b436cfee3772c5b082ea8f8216feed729f02c5bda32d66/qiskit-1.4.3-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:65caf291bbe93f136b85281cb4a807c138fc4315fc17bdda9382cb4bfa18abdf", size = 6570998, upload-time = "2025-05-12T15:27:51.659Z" }, + { url = "https://files.pythonhosted.org/packages/67/95/c4bf1fe91c456570c9536e5505ccd878f358f4b8e6b06c8fd8cbc09093a0/qiskit-1.4.3-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:6e9f41eca2f4d522f59e48a47eb8b81ed3fdd35f56cefb41cc0db47768ccde47", size = 6251292, upload-time = "2025-05-12T15:27:53.929Z" }, + { url = "https://files.pythonhosted.org/packages/23/70/713fae76998831e9cb458e3605a9429915b9f9fbb92244c8699903868be2/qiskit-1.4.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a04c6baca379bae8e6fc626bd06f0dda3f2ee7961205839b5a431b7f8d1e3978", size = 6396599, upload-time = "2025-05-12T18:02:22.029Z" }, + { url = "https://files.pythonhosted.org/packages/d6/93/6cadc22a5184e4e868c28dd6a83beacf6e4641088f106e3cd97e0cd160a8/qiskit-1.4.3-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ca6eeaf29ae2cf3789e7474b062845c59c6e4164276569949f9170cfcaec6b1", size = 6730964, upload-time = "2025-05-12T15:27:55.962Z" }, + { url = "https://files.pythonhosted.org/packages/ef/9c/53a7fd3a18ec3526e56fa68d9af1a164ba4cea2302a03be7b621678b1694/qiskit-1.4.3-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc82934e5b5192a75adca57bfa8194166e6bce1023da3b82e02db6c279a5d0fc", size = 6801019, upload-time = "2025-05-12T18:02:24.857Z" }, + { url = "https://files.pythonhosted.org/packages/40/a2/1355e3a8a00f9310ea80ab5887095bd3122918c8a61d62895e6519e442c0/qiskit-1.4.3-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:03355819fb9747a937bfffb0aba59d44c4654536b87c62dcd00d4e4e1550190f", size = 7895620, upload-time = "2025-05-12T18:02:27.104Z" }, + { url = "https://files.pythonhosted.org/packages/85/35/a442b69b1507112f4fba392354d933f1ef65f66dc72bfce0c03be27f70c7/qiskit-1.4.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d90b25ad017839b0bf1b5d05d8bd4066b7f83e3c14839947994f5fd56a5f5e2", size = 6762841, upload-time = "2025-05-12T15:27:58.379Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f8/c9d116275bec7d0942f364ed6461b384e2ccd1c6bb5df8fe5d998d24ae41/qiskit-1.4.3-cp39-abi3-win32.whl", hash = "sha256:74cb30e34189ca9c1263994705a0cf8d97cead5f7375dedab3be4e783173785d", size = 6138538, upload-time = "2025-05-12T15:28:00.754Z" }, + { url = "https://files.pythonhosted.org/packages/ae/a7/7f736138e67f3f89e4a8b42bb480a9dcd582da9a87f4d723a3c4221766b0/qiskit-1.4.3-cp39-abi3-win_amd64.whl", hash = "sha256:554b5ad8c6198881923998fd0124660eec534c985c731d1e3c09c6421377c29e", size = 6517946, upload-time = "2025-05-12T15:28:03.189Z" }, ] [package.optional-dependencies] @@ -2752,6 +2751,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f6/f2/0e2260961c721ba0889e3f7fcdd69e5a7a24694b135d7ad5b6fe55e84e16/qiskit_aer-0.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:de793b75f19d762981198d6c770c991a5d4e0d32bc1768416cfa1520e28bdcf8", size = 9524145, upload-time = "2025-06-04T12:05:20.308Z" }, ] +[[package]] +name = "qiskit-ibm-ai-local-transpiler" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema" }, + { name = "networkx" }, + { name = "qiskit" }, + { name = "qiskit-ibm-runtime" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/2b/d4a31925f23a4614587bd256f1a5b46d53f667971243df2bff62ceb1286e/qiskit_ibm_ai_local_transpiler-0.4.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:6b13b0978b0bb906c1175a63b1c2c00070ed7f3ff9c302d9b8a8e104f5a8ec39", size = 97513406, upload-time = "2025-07-02T15:25:39.934Z" }, + { url = "https://files.pythonhosted.org/packages/54/70/b80685df8b5b957c6e61e44eec9c13f0b72c8a4458734048c5de875d1eb8/qiskit_ibm_ai_local_transpiler-0.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:94a59d360c4470dac7a8467b86cd3787430b917c3d7aa1635279087459168a19", size = 98170843, upload-time = "2025-07-02T15:24:43.794Z" }, + { url = "https://files.pythonhosted.org/packages/10/ec/d62bb53f8dfc16cc908696809f5508b161f8c6709f6be44b410281c4e184/qiskit_ibm_ai_local_transpiler-0.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4933917d137e07a7c889e11c9db30d6f48fc09fe1ecd2e6f856bf451e2e5fba8", size = 97637275, upload-time = "2025-07-02T15:23:20.273Z" }, + { url = "https://files.pythonhosted.org/packages/78/34/807162739e60cbd1f9365d5d00ce583445c4b5ac5e381384c892b69ebf25/qiskit_ibm_ai_local_transpiler-0.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:d4a426b7798a4064487a1d50b0bf019ea1f5eec2a74580aaa2cb945c9b35b385", size = 97416246, upload-time = "2025-07-02T15:23:46.745Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f3/d653647764d13142f028ede9cd772fdb04e73259af622c3207210936d073/qiskit_ibm_ai_local_transpiler-0.4.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0dd30e5679fb7c2371a11380a3b2c485410a3cb5f23628a94188d50cb4f197ff", size = 97513399, upload-time = "2025-07-02T15:23:59.758Z" }, + { url = "https://files.pythonhosted.org/packages/ba/20/1885c4064bbfd9919e0e03ccff97c3b4afdbeb3bc8effaf9253b5502afc8/qiskit_ibm_ai_local_transpiler-0.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f3fa93eb41ad5c14480b726c9737a98017c3a1723326e18c2b2c3c964a6de0b9", size = 98170686, upload-time = "2025-07-02T15:21:48.295Z" }, + { url = "https://files.pythonhosted.org/packages/58/c4/072723658abfd11169a63462396e8765b09f5a204a8d9401ad44da0e5004/qiskit_ibm_ai_local_transpiler-0.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8703192d174ef0f71272bb50fe74b5612690cd9ee9e933a3346567327a04e8eb", size = 97636848, upload-time = "2025-07-02T15:23:38.279Z" }, + { url = "https://files.pythonhosted.org/packages/ae/01/4b4dc70144d08e0694d3a92aa0c7486a9dbb48f8442a7ada92d14f1bdfc7/qiskit_ibm_ai_local_transpiler-0.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:5af2024463e2c34e58e56ead767d6b0f9ee14c3d4d1a01fce3f2ca0230479147", size = 97416835, upload-time = "2025-07-02T15:24:51.17Z" }, + { url = "https://files.pythonhosted.org/packages/31/f7/1e3a0eaf8250ba30a1c0dd4bb9a375d414cf18015cba5d6a85f369de13b9/qiskit_ibm_ai_local_transpiler-0.4.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7d601b5f508582a6aec4f2fbe04ed50cc52d30e133fc8051178e5558ecaabe13", size = 97512749, upload-time = "2025-07-02T15:27:47.411Z" }, + { url = "https://files.pythonhosted.org/packages/05/fc/df0b6e3a9bdd1690aad940144130f247825fa0710270baba9614805d0071/qiskit_ibm_ai_local_transpiler-0.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d99f87b76c2ae68678afa6f70f8632842a017c1a917774a89de12c9db58f557", size = 98169638, upload-time = "2025-07-02T15:20:13.497Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b4/90a577916747678edcde64dba3a724796ed8cb99915a3e3962f90791ae23/qiskit_ibm_ai_local_transpiler-0.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a49da8a583a117b4bc1c51d92a2075718b0cd7e5f000afbc80235e52b2568f70", size = 97636994, upload-time = "2025-07-02T15:23:41.209Z" }, + { url = "https://files.pythonhosted.org/packages/7a/cb/bd0f3995d06c9a2cafa7c49f990dd15e4a2b6860fd43cf6c29d9b9446018/qiskit_ibm_ai_local_transpiler-0.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:aeaa9acacda590f5fd3f668bc16a75f2a515ba6002a1088c617b1b1546076886", size = 97417172, upload-time = "2025-07-02T15:23:07.882Z" }, +] + [[package]] name = "qiskit-ibm-runtime" version = "0.40.1" @@ -2773,6 +2797,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/2e/7ef9afa14f94affb7afd472edd70358c1a0c66e5cbeac1b2a29ce84b8a5d/qiskit_ibm_runtime-0.40.1-py3-none-any.whl", hash = "sha256:5a67e4c55b6e0a2bc9041c1ec7b8c5adc43a50f7dfb8a2521a0c2f3ed4aaa712", size = 3196409, upload-time = "2025-06-05T15:59:49.117Z" }, ] +[[package]] +name = "qiskit-ibm-transpiler" +version = "0.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backoff" }, + { name = "networkx" }, + { name = "qiskit" }, + { name = "qiskit-qasm3-import" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/ac/d6f176107a0a07aeae583dbde3e7812cd31f670f0d285a0167ece51f040e/qiskit_ibm_transpiler-0.13.1.tar.gz", hash = "sha256:83631565b3adb6ce3825a82af8b6276e3f5e87d193c6efd01b7edae3bf56b31c", size = 46935, upload-time = "2025-07-21T09:42:49.367Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/a4/4127dccc7ecfca71d0df5310ced026790ffd1e8da196fc360011416bcffa/qiskit_ibm_transpiler-0.13.1-py3-none-any.whl", hash = "sha256:5ebd20cf2b6fea77a2cf0263a113bfe37f4e2ac2a836469e4fe714565f886060", size = 61069, upload-time = "2025-07-21T09:42:47.889Z" }, +] + [[package]] name = "qiskit-qasm3-import" version = "0.6.0" @@ -2841,6 +2881,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/5d/836b97537a390cf811b0488490c389c5a614f0a93acb23f347bd37a2d914/requests_ntlm-1.3.0-py3-none-any.whl", hash = "sha256:4c7534a7d0e482bb0928531d621be4b2c74ace437e88c5a357ceb7452d25a510", size = 6577, upload-time = "2024-06-09T23:52:03.241Z" }, ] +[[package]] +name = "rich" +version = "14.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, +] + [[package]] name = "roman-numerals-py" version = "3.1.0" @@ -3614,39 +3667,39 @@ wheels = [ [[package]] name = "symengine" -version = "0.14.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/eb/ae/051d3e2ffd128f4a8fa67d380bb7be45637d39e206b4c3737c087a3d4b71/symengine-0.14.1.tar.gz", hash = "sha256:4e9e4b4ec56371c2151803ae0403dab07dd1c74ce88e9abca433bebeb6e2f0f0", size = 938282, upload-time = "2025-04-21T04:03:04.916Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/64/7a9c565d804cc1aafdf4941a2fa3985a6b660ac70ac15080e5112113beeb/symengine-0.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0f977360c9993dae163951386ea5b865d8310f323cf11234e62b6535f330d3ac", size = 25935332, upload-time = "2025-04-21T04:00:42.174Z" }, - { url = "https://files.pythonhosted.org/packages/08/9d/deba31c5a38cf26244298ebb63fee5d730638d23b6ab055ecf4988bcf485/symengine-0.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:524249b3224617f91e4e0b81c4134fa496024ddf67b383163586d74683f064a3", size = 22999100, upload-time = "2025-04-21T04:00:46.425Z" }, - { url = "https://files.pythonhosted.org/packages/87/77/13b5faf6b7873df3f2365aa2e43c7dac3e5cb7f037d7d0b60c648132fba9/symengine-0.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f2fb2b9ba7038cba436783a5c1bddc40ba84c7812a4b9c50571fd47ff265038", size = 60837233, upload-time = "2025-04-21T04:00:50.435Z" }, - { url = "https://files.pythonhosted.org/packages/46/cb/9bce97ad8135a32b47e90b3f57084bdc0bb844aa866b3ca0bc88987a2ac4/symengine-0.14.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a010412317d93e37fbee3f4bc04886408653e863997993424e7b9e4d8c85c150", size = 90826835, upload-time = "2025-04-21T04:00:55.849Z" }, - { url = "https://files.pythonhosted.org/packages/5e/8f/3c335e44db300f4d91f7b098661a2add403367833cc27d26dc235520661a/symengine-0.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbb14724ab6a6844d04adbd48d7aadcadfe4974193e87eedac93963a6e88bf53", size = 50356243, upload-time = "2025-04-21T04:01:00.867Z" }, - { url = "https://files.pythonhosted.org/packages/fc/fd/d9f33b8a08bdfc07fff733c7f869deb5a0e8c93f5cb95ed4cb9d16294dfb/symengine-0.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:4635fa8855fcadae8c60f27f498388699b7ee88c6be7c3e23564eb0907f6b397", size = 18755307, upload-time = "2025-04-21T04:01:04.187Z" }, - { url = "https://files.pythonhosted.org/packages/ee/f8/689900a258a68b121d7c61a6705b5332969dd95517f9be1d66a4937a17e1/symengine-0.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:43a394acef2355bbcbed089c8f83a7dbdac28f4f979e094b549f571fc411dea6", size = 25992552, upload-time = "2025-04-21T04:01:07.102Z" }, - { url = "https://files.pythonhosted.org/packages/6e/10/82291c78d6dbc17b2e4371559a3945920ecc536ec36b6c8dde28165064f9/symengine-0.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7eb37cd5a9eb0c1c5e35c515139fd1221d937b269aef3569b92c7bb4e251be4", size = 23052876, upload-time = "2025-04-21T04:01:09.679Z" }, - { url = "https://files.pythonhosted.org/packages/30/1a/2199515b7d4f3aa74262e3eb31916a593c4daa7da53bb2c9cbd2cc13fad8/symengine-0.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7657663cfb0d87d66848f7cacda0b2447e127584bc9ec65120e75ce68b21b809", size = 60873086, upload-time = "2025-04-21T04:01:14.285Z" }, - { url = "https://files.pythonhosted.org/packages/07/ce/1e7d9abea217cee8a543ff1fb395b4462e9a4df8072e25b42984e8a6938e/symengine-0.14.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94535da1085a4927364fbb4a8705f7f504897ac53da2cc6d6a8edc897e49c4da", size = 90878511, upload-time = "2025-04-21T04:01:21.764Z" }, - { url = "https://files.pythonhosted.org/packages/6e/23/4bcd98c8a79bc0d584786eb765f33dffca1136fe9b506c6e336d61fad844/symengine-0.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a32a29b9462446c9e7f3bf464c7faa847ac2a2d75f0f3fd2f7c0606f9b9384cb", size = 50397966, upload-time = "2025-04-21T04:01:27.732Z" }, - { url = "https://files.pythonhosted.org/packages/a2/c2/72612eae87f3d0ca58ae4e2207eaee9f8c1dfddc6862a1dc8e2257d67e14/symengine-0.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:f639a488886a066c4d8f19f2a4fd2213de2ea428153d8e1bf425c14f8bc74d57", size = 18803262, upload-time = "2025-04-21T04:01:31.136Z" }, - { url = "https://files.pythonhosted.org/packages/59/ab/3ef6b6385b277511a2e80f5425629af6566c491200ced6ba4a5b33db6b9c/symengine-0.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4c657fba40960dc143d90b20fe49ae769d5c82b323b9dd459f5e3dea4796df8f", size = 25962842, upload-time = "2025-04-21T04:01:34.359Z" }, - { url = "https://files.pythonhosted.org/packages/58/8d/e47e7e9166b4d53cacb0ad722164a6a2bfbb459d62bee105a05998f7b2aa/symengine-0.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9789c62a8ade18f368241525e910dae745fdde1f3b8bb782e5ac8bbfad505e92", size = 23046175, upload-time = "2025-04-21T04:01:37.275Z" }, - { url = "https://files.pythonhosted.org/packages/be/76/6c2227ca8076c238b65341beb3ce4a5bb29174afce3eb9f124b319242693/symengine-0.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcab68fe738a50df3a1b2415f75395c588aab237cb636818332e6cda7ddcf5fb", size = 60719492, upload-time = "2025-04-21T04:01:40.806Z" }, - { url = "https://files.pythonhosted.org/packages/bb/d8/e2dad8babfbb6c8abdb2c443aefca0ae65078c2a0c1f791117a2e659b005/symengine-0.14.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f58a88571a5f7ceca499d7f7834c00d129a55d4c2104a463ba8e3ec24c252e89", size = 90545323, upload-time = "2025-04-21T04:01:46.143Z" }, - { url = "https://files.pythonhosted.org/packages/64/4f/84948cd8384deb43465c14daea81e53159b1c22a2fd55c4f087c47f180df/symengine-0.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a55b8f78541d57a28beda6971bed0a7ddbd585148bb030221f7ca3a0c8e2517", size = 50278629, upload-time = "2025-04-21T04:01:51.673Z" }, - { url = "https://files.pythonhosted.org/packages/d6/7f/4d6b6998a69af3614cc4cb5953acc23b116b35e0596be4dd991397f0cdce/symengine-0.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:5091ce86728022d2c7e0e864ab23070494c1f24fb430ab3437d46806e854651e", size = 18754027, upload-time = "2025-04-21T04:01:55.355Z" }, - { url = "https://files.pythonhosted.org/packages/db/09/c382abfa3e83f11be0ade2ab063f11cc4a3a906b5194f62956856ba766b6/symengine-0.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9859c0c926475dfed666707afba04639a6d46ab48bc463faf50c79b6513de861", size = 25960338, upload-time = "2025-04-21T04:01:57.962Z" }, - { url = "https://files.pythonhosted.org/packages/13/8d/d4b541515b0fa5c390109572d035d397e28ac6f4bd69bee88806b231a65f/symengine-0.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:431545ef66b20efa24e5de9d754ffb184ad9ed29743740c0e9d815c1a1ec37a8", size = 23040715, upload-time = "2025-04-21T04:02:00.584Z" }, - { url = "https://files.pythonhosted.org/packages/4a/c7/4ca78ac8df7dacf35bfa77411c317d65adc6add07f58a4d210060b678511/symengine-0.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e69fe8be6e1d1f5209abeb7a8308a074d981b9f1f166086f23f6eed20d861e7f", size = 60716721, upload-time = "2025-04-21T04:02:04.19Z" }, - { url = "https://files.pythonhosted.org/packages/04/92/1d1d9084ccf1749af477273ac952ab68970175a62ef093b32d83dd3efc53/symengine-0.14.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b51617f42213ceb49786b474195d17e7d62e492d0592caa4b404f0e1b1acf58", size = 90546321, upload-time = "2025-04-21T04:02:09.873Z" }, - { url = "https://files.pythonhosted.org/packages/8d/23/922e6fd625ef08c4b703790d3accdcc48e6b340538de25a29ee8b5bebe6c/symengine-0.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e91af61b854e82ae5382e5f91e66d83ac007c0b19b3946f747bc4fc6ca25d66", size = 50277827, upload-time = "2025-04-21T04:02:15.465Z" }, - { url = "https://files.pythonhosted.org/packages/7b/05/29090873cc7b3d23b492a192fa51ada148c80de016db2dc29225230f6499/symengine-0.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:d232d03ceb008d6eb24cb3e1f423af44d3d78cfe09aed801d9fc68ef7b41b611", size = 18753449, upload-time = "2025-04-21T04:02:38.332Z" }, - { url = "https://files.pythonhosted.org/packages/d8/23/0a542b0d3af0b017c129852265aa1a208f152855fd9feb0c1480d846b784/symengine-0.14.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1324f94852cf2541be29de31899d00bdcab547fea9e9534bcc548062c3f33038", size = 25893789, upload-time = "2025-04-21T04:02:19.407Z" }, - { url = "https://files.pythonhosted.org/packages/e3/38/d22630f658238e33138ceb57b368fd99555472f5e4be561bbc27cf4b134d/symengine-0.14.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e4b61cb16a559f2e098d52285b27fac4e6ee15194368cef5b78727f71a490bb5", size = 22991458, upload-time = "2025-04-21T04:02:22.396Z" }, - { url = "https://files.pythonhosted.org/packages/be/28/9ae2ee8c3a6e5b35b25a42625de4cd8ef432121dd4ff75bca6809b9e61ac/symengine-0.14.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58f8e1756c9f8cdb7f6cdf189b3752f20cb464f82bf1c7a3873156b12c567896", size = 60604042, upload-time = "2025-04-21T04:02:25.915Z" }, - { url = "https://files.pythonhosted.org/packages/7d/c0/233806912e5515505a058d2527a53117e0e6cd8e59525f1c7cb870805a47/symengine-0.14.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a96f1adfed8cfad62a4f58fe2f32b42718e0938afef0bffeb8131d862ca71a9b", size = 90280432, upload-time = "2025-04-21T04:02:31.02Z" }, - { url = "https://files.pythonhosted.org/packages/bd/93/a6f379c6bd9554efc4cd5475e75fc164d6c44e69d98ddf6553f12a2532aa/symengine-0.14.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c4f500fdd09cbd3cde34a027a964e47b4fc7bfcd5ec8b9a4e654a37036d364b", size = 50183106, upload-time = "2025-04-21T04:02:35.537Z" }, +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/ff/a2041497a482a0ae585672fe12cc8983c7fc46ce792811b55a067e5e5516/symengine-0.13.0.tar.gz", hash = "sha256:ab83a08897ebf12579702c2b71ba73d4732fb706cc4291d810aedf39c690c14c", size = 114237, upload-time = "2024-09-30T22:06:25.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/ed/964f75a2dc5b0e2c97b732f44dfb9c40fe7c0f5e21a1ecc2edff89db3d81/symengine-0.13.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:259fd4111c7a70c72bdff5686de1949e8132baeb612eacdaf8837720c6fe449b", size = 25867912, upload-time = "2024-09-30T22:03:05.097Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ce/c74dfbaf487a428984644b3cb5e1131c4808f60eab1456f37a107f20b87a/symengine-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:44f2eb28a1e36db0bbd6679435412f79da9743bf9c1cb3eff25e0c343b7ddd48", size = 22711016, upload-time = "2024-09-30T22:03:09.144Z" }, + { url = "https://files.pythonhosted.org/packages/4b/65/df699e2b4c2bbf608394b05d1095d77b3f02569e09960f7840f708c7c5dc/symengine-0.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d141712fa14d9138bd19e64b10392f850c68d88cd7db29f1bda33e32d1095559", size = 61000047, upload-time = "2024-09-30T22:03:13.613Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a9/623d97da0da9615367484127dd3b5d1049675832fb9a6a3572f2e072bb86/symengine-0.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:830226d933bfcdb93546e4062541627d9a3bc7a178a63fb16c002eb5c5221938", size = 90017189, upload-time = "2024-09-30T22:03:20.545Z" }, + { url = "https://files.pythonhosted.org/packages/79/a0/d30329ef1bd2b188cb5bbf6d9394e72fa9f38b33a6caa2128ee187716259/symengine-0.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a08090163819a0bbfa97d64bd2d8dac2c5268147ed9c242799d7f7e8728a6f4e", size = 49698796, upload-time = "2024-09-30T22:03:31.169Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e3/cf99b6353b46b8d9ff05e8d7d24764c8afed7c46b888eece99c6e04cc295/symengine-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:1e435dcd8ed25e4c7c21ab1c0376be910efc7f35da76d532367df27b359f0358", size = 17830331, upload-time = "2024-09-30T22:03:35.649Z" }, + { url = "https://files.pythonhosted.org/packages/47/a7/e69cff22c2b169b469faa390f0102fbdefe1dfede893a086454483db0fc3/symengine-0.13.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:da0eba7e106095cdce88eb275c8a9d7c4586ad88f229394c53e1184155c00745", size = 25878286, upload-time = "2024-09-30T22:03:39.216Z" }, + { url = "https://files.pythonhosted.org/packages/60/40/bb3faf5a3d4cc99cf5252cc3f4f5267568abd4aae6439374623841cc0025/symengine-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b0c175f4f895a73a925508af03faf7efd6cad8593256bbdb5346bd996d3ec5c8", size = 22721741, upload-time = "2024-09-30T22:03:42.574Z" }, + { url = "https://files.pythonhosted.org/packages/de/da/d6350f60f11abc7ee56fcb6b996deba7b31584b5942a4c5db7d3d4f324dd/symengine-0.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e58d1e2abd08381aa0cf24c88c0e8b7f592df92619b51e32d36835fbd2dd6ae8", size = 60999516, upload-time = "2024-09-30T22:03:47.206Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ee/db107db7d0557aa8dbc9485741a60dd4f869d3da32180710506f9ba942b9/symengine-0.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1db745f2c7a3c5e83510cf4decb43201f43552dfb05ad8af9787c89708be9ede", size = 90019965, upload-time = "2024-09-30T22:03:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/8f/f0/0642f5d9681ff26e57c317d0514315cf1b0dfe1bc8f68ee14a13a270d704/symengine-0.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2572c98b09ac284db6ecff63f6170461194dc94c4209afd34c092ec67873d85", size = 49690621, upload-time = "2024-09-30T22:03:59.467Z" }, + { url = "https://files.pythonhosted.org/packages/54/85/4300eda41959e3839115b2cb0ddb21818804f5eb129cf92eb9cc55c6efe5/symengine-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:12727f02a2919f005aee48e68e0cbb70cf857b19385857b4d985d1c9b075f620", size = 17831939, upload-time = "2024-09-30T22:04:03.649Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b2/786d3efb98ae1c8e0a9869d6901f24a6633bd621f9e4e1427c86bff35abb/symengine-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cf91d24f1bfd6d53228593c7804dd106b71b19674d5afc4fa322d516e1793bdd", size = 25853458, upload-time = "2024-09-30T22:04:06.435Z" }, + { url = "https://files.pythonhosted.org/packages/3f/74/78b9e7f17c9b9b345d8ef0dbdefebbfc2c7f00693949c6b5808f8a9e54d8/symengine-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5615b7eb68890917abd390ebb10434a949165f6064741c1a8cc345fee14e855", size = 22713256, upload-time = "2024-09-30T22:04:10.002Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a8/69f429946e9a9e63a141dcbe519df8a8c3d56aa75fa8440716871efd8b75/symengine-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb92bdf0890de264abaeacbfbdbd4dd7444b94057bd47958d913b662e549ad8a", size = 60847852, upload-time = "2024-09-30T22:04:14.415Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/a613d5fa73c1b05eb4f0a4bc2bfa21314d37c89dcdb355da684b5f9bea46/symengine-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3bce486fbc0b87970ed1b10ca9d5cafb1fd6b66382fe631261d83592851d7e", size = 89691094, upload-time = "2024-09-30T22:04:21.012Z" }, + { url = "https://files.pythonhosted.org/packages/90/9f/5a171a9edff23eb0e33e0a2dca295a8b25fd64491ebe8cf765e489bd2bcd/symengine-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7e6bae9cfcdde2775d92fbb0abe3ef04e32f65ebc4c2d164ca33f4da202d4a7", size = 49584342, upload-time = "2024-09-30T22:04:28.117Z" }, + { url = "https://files.pythonhosted.org/packages/07/16/a69dffc665e5007a0b4f1500fc401316aa070d109f83bc6887802dce199d/symengine-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:bced0a1dbdb94737c299384c85ddbad6944ce8dadc334f7bb8dbbd8f6c965807", size = 17787218, upload-time = "2024-09-30T22:04:32.066Z" }, + { url = "https://files.pythonhosted.org/packages/f2/51/36ea8d87b61f34d585d09ff585cdc2ba136c501e0a96e861fe81fd0efa5b/symengine-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d34df77971538e4c29f2d8e5ef7f459c2179465e6cdb7dfd48b79b87ecd8f4d", size = 25838153, upload-time = "2024-09-30T22:04:35.743Z" }, + { url = "https://files.pythonhosted.org/packages/2a/f7/73cd707633c01760df27d340ac2868788fa0a35034aa09f51f6a289d9a07/symengine-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab2661d9b18867e7c6edbfa7a74b8b0a2a694bd24aa08003dc3214f77cb9d6f2", size = 22706111, upload-time = "2024-09-30T22:04:38.922Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e7/a1ddc4cc3f2604b18965342523cddd6160b6406d1ef21b58a2dea1c46312/symengine-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53f27b9013878ee4419d8e853664d8ae4b68419e3f4b9b5b7f503d32bf904755", size = 60848269, upload-time = "2024-09-30T22:04:43.816Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f5/b127d236a8ccce8b85cec8bcb0221cbb6a4e651d6f5511da925e10714a4c/symengine-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:27987f75ce08c64f453e2b9b74fec6ffc5ca418c4deca0b75580979d4a4e242a", size = 89679345, upload-time = "2024-09-30T22:04:50.53Z" }, + { url = "https://files.pythonhosted.org/packages/49/d0/b07c71dfb6f7f47f90495d67cb0b2264f94f2fb9380e528fe4fcec4c5cfa/symengine-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9ea9410330ea15ed4137d7a0a3c43caccacb71490e18036ce5182d08c93baf8", size = 49582795, upload-time = "2024-09-30T22:04:56.705Z" }, + { url = "https://files.pythonhosted.org/packages/de/c6/3ce562ec269b033a738b99aa75729d564bfe465a765a5b5810200924f5c9/symengine-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:5031eb7a5c6675d5195bb57f93cc7d9ac5a7a9a826d4ad6f6b2927746ed7e6e6", size = 17786679, upload-time = "2024-09-30T22:05:30.754Z" }, + { url = "https://files.pythonhosted.org/packages/ca/0f/02031b4ed54fb088ade94082e4658160333c2d5c6629ca7e567ea5a23720/symengine-0.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ce0e5dfb19943bcf3e44a4485bcac4c5533ba3705c63083494eed0b3bf246076", size = 25795209, upload-time = "2024-09-30T22:05:05.713Z" }, + { url = "https://files.pythonhosted.org/packages/34/c0/03e9e34a4e2af73dd786fb966ee3ad7ffa57659e79e4ac390be9e9f6aded/symengine-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c3b77dc54bf1181f6bd3b3338c4e6e5973a8b0fa20a189d15563ef5626e57b04", size = 22670484, upload-time = "2024-09-30T22:05:08.721Z" }, + { url = "https://files.pythonhosted.org/packages/9d/13/66c8510403089ee157f0a8930a2e043e6f4de9ee514c8084adb958934058/symengine-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca7c3f6c168f6f5b06b421833c3d3baae56067a94b671bdffbe09b8e4fefd9be", size = 60739889, upload-time = "2024-09-30T22:05:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/70/fe/d0c778204d855257865e713fcbfef11dde34621e07fee439d4d117f43203/symengine-0.13.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:847523de682416811bacb3ad11507e663b3522fbb35cd27184757e9956d0eaf0", size = 89405899, upload-time = "2024-09-30T22:05:20.504Z" }, + { url = "https://files.pythonhosted.org/packages/66/f6/f546e527caf35b7d0f14dbcea278134d4e46d7431821b9ca9f5ec3388a6a/symengine-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2fc1b7d96426463f0c9011e9fb88459d906477c1baa8a996dde6fb2bfa99d4", size = 49474632, upload-time = "2024-09-30T22:05:26.083Z" }, ] [[package]] @@ -3762,8 +3815,7 @@ dependencies = [ { name = "filelock", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, { name = "fsspec", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, { name = "jinja2", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "networkx", version = "3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "networkx", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, { name = "sympy", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, { name = "typing-extensions", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, ] @@ -3790,8 +3842,7 @@ dependencies = [ { name = "filelock", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, { name = "fsspec", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, { name = "jinja2", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, - { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')" }, - { name = "networkx", version = "3.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13' and 'x86_64' not in platform_machine) or (python_full_version >= '3.11' and sys_platform != 'darwin') or (python_full_version >= '3.13' and sys_platform == 'darwin')" }, + { name = "networkx", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, From b1ac8ce1a9d49df18af267a548bae0e0f0496da8 Mon Sep 17 00:00:00 2001 From: Liu Keyu Date: Sun, 3 Aug 2025 11:27:24 +0200 Subject: [PATCH 10/57] Fix dependencies issues --- pyproject.toml | 9 +- src/mqt/predictor/rl/predictorenv.py | 12 +- uv.lock | 452 +++++++++++++++------------ 3 files changed, 264 insertions(+), 209 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 825fcefb3..e1acedfb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ dependencies = [ "mqt.bench>=2.0.0", "qiskit!=1.3.2", # 1.3.2 causes a Qiskit error when using the CommutativeInverseCancellation pass, see https://github.com/Qiskit/qiskit/issues/13742 "pytket>=1.29.0", # lowest version that supports the used pytket AutoRebase pass instead of auto_rebase - "pytket_qiskit>=0.60.0", + "pytket_qiskit>=0.71.0", "sb3_contrib>=2.0.0", "tqdm>=4.66.0", "rich>=12.6.0", @@ -44,7 +44,8 @@ dependencies = [ "numpy>=1.24; python_version >= '3.11'", "numpy>=1.22", "numpy>=1.22,<2; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict numpy v2 for macOS x86 since it is not supported anymore since torch v2.3.0 - "torch>=2.2.2,<2.3.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch v2.3.0 for macOS x86 since it is not supported anymore. + "torch>=2.2.2,<2.3.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", + "torch>=2.4.0; python_version >= '3.12' and sys_platform != 'darwin' and 'x86_64' not in platform_machine", "typing-extensions>=4.1", # for `assert_never` "qiskit-ibm-transpiler>=0.11.1", "qiskit-ibm-ai-local-transpiler>=0.3.2" @@ -251,4 +252,6 @@ fom = "fom" ignore = ["GH200"] [tool.uv] -override-dependencies = ["networkx==2.8.5"] +override-dependencies = [ + "networkx==2.8.5", +] diff --git a/src/mqt/predictor/rl/predictorenv.py b/src/mqt/predictor/rl/predictorenv.py index b152f2c47..5f1d4603e 100644 --- a/src/mqt/predictor/rl/predictorenv.py +++ b/src/mqt/predictor/rl/predictorenv.py @@ -391,21 +391,21 @@ def _handle_qiskit_layout_postprocessing( ) -> QuantumCircuit: if action.name == "VF2PostLayout": assert pm_property_set["VF2PostLayout_stop_reason"] is not None - post_layout = pm_property_set["post_layout"] + post_layout = pm_property_set.get("post_layout") if post_layout: altered_qc, _ = postprocess_vf2postlayout(altered_qc, post_layout, self.layout) - else: - assert pm_property_set["layout"] - if pm_property_set["layout"]: + layout = pm_property_set.get("layout") + if layout: self.layout = TranspileLayout( - initial_layout=pm_property_set["layout"], + initial_layout=layout, input_qubit_mapping=pm_property_set["original_qubit_indices"], final_layout=pm_property_set["final_layout"], _output_qubit_list=altered_qc.qubits, _input_qubit_count=self.num_qubits_uncompiled_circuit, ) - if self.layout is not None and pm_property_set["final_layout"]: + + if self.layout is not None and pm_property_set.get("final_layout"): self.layout.final_layout = pm_property_set["final_layout"] return altered_qc diff --git a/uv.lock b/uv.lock index 215a0a69d..4e3e5bdde 100644 --- a/uv.lock +++ b/uv.lock @@ -2,9 +2,11 @@ version = 1 revision = 3 requires-python = ">=3.10" resolution-markers = [ - "python_full_version >= '3.13'", + "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", "python_full_version < '3.11' and 'x86_64' in platform_machine and sys_platform == 'darwin'", @@ -164,11 +166,11 @@ wheels = [ [[package]] name = "certifi" -version = "2025.7.14" +version = "2025.8.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" }, + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, ] [[package]] @@ -404,9 +406,11 @@ name = "contourpy" version = "1.3.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13'", + "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", ] @@ -935,8 +939,10 @@ name = "gymnasium" version = "1.2.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13'", - "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", + "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')", ] @@ -1074,9 +1080,11 @@ name = "ipython" version = "9.4.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13'", + "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", ] @@ -1405,7 +1413,7 @@ wheels = [ [[package]] name = "matplotlib" -version = "3.10.3" +version = "3.10.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, @@ -1420,41 +1428,62 @@ dependencies = [ { name = "pyparsing" }, { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/26/91/d49359a21893183ed2a5b6c76bec40e0b1dcbf8ca148f864d134897cfc75/matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0", size = 34799811, upload-time = "2025-05-08T19:10:54.39Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/ea/2bba25d289d389c7451f331ecd593944b3705f06ddf593fa7be75037d308/matplotlib-3.10.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:213fadd6348d106ca7db99e113f1bea1e65e383c3ba76e8556ba4a3054b65ae7", size = 8167862, upload-time = "2025-05-08T19:09:39.563Z" }, - { url = "https://files.pythonhosted.org/packages/41/81/cc70b5138c926604e8c9ed810ed4c79e8116ba72e02230852f5c12c87ba2/matplotlib-3.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3bec61cb8221f0ca6313889308326e7bb303d0d302c5cc9e523b2f2e6c73deb", size = 8042149, upload-time = "2025-05-08T19:09:42.413Z" }, - { url = "https://files.pythonhosted.org/packages/4a/9a/0ff45b6bfa42bb16de597e6058edf2361c298ad5ef93b327728145161bbf/matplotlib-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c21ae75651c0231b3ba014b6d5e08fb969c40cdb5a011e33e99ed0c9ea86ecb", size = 8453719, upload-time = "2025-05-08T19:09:44.901Z" }, - { url = "https://files.pythonhosted.org/packages/85/c7/1866e972fed6d71ef136efbc980d4d1854ab7ef1ea8152bbd995ca231c81/matplotlib-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a49e39755580b08e30e3620efc659330eac5d6534ab7eae50fa5e31f53ee4e30", size = 8590801, upload-time = "2025-05-08T19:09:47.404Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b9/748f6626d534ab7e255bdc39dc22634d337cf3ce200f261b5d65742044a1/matplotlib-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf4636203e1190871d3a73664dea03d26fb019b66692cbfd642faafdad6208e8", size = 9402111, upload-time = "2025-05-08T19:09:49.474Z" }, - { url = "https://files.pythonhosted.org/packages/1f/78/8bf07bd8fb67ea5665a6af188e70b57fcb2ab67057daa06b85a08e59160a/matplotlib-3.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:fd5641a9bb9d55f4dd2afe897a53b537c834b9012684c8444cc105895c8c16fd", size = 8057213, upload-time = "2025-05-08T19:09:51.489Z" }, - { url = "https://files.pythonhosted.org/packages/f5/bd/af9f655456f60fe1d575f54fb14704ee299b16e999704817a7645dfce6b0/matplotlib-3.10.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0ef061f74cd488586f552d0c336b2f078d43bc00dc473d2c3e7bfee2272f3fa8", size = 8178873, upload-time = "2025-05-08T19:09:53.857Z" }, - { url = "https://files.pythonhosted.org/packages/c2/86/e1c86690610661cd716eda5f9d0b35eaf606ae6c9b6736687cfc8f2d0cd8/matplotlib-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96985d14dc5f4a736bbea4b9de9afaa735f8a0fc2ca75be2fa9e96b2097369d", size = 8052205, upload-time = "2025-05-08T19:09:55.684Z" }, - { url = "https://files.pythonhosted.org/packages/54/51/a9f8e49af3883dacddb2da1af5fca1f7468677f1188936452dd9aaaeb9ed/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5f0283da91e9522bdba4d6583ed9d5521566f63729ffb68334f86d0bb98049", size = 8465823, upload-time = "2025-05-08T19:09:57.442Z" }, - { url = "https://files.pythonhosted.org/packages/e7/e3/c82963a3b86d6e6d5874cbeaa390166458a7f1961bab9feb14d3d1a10f02/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdfa07c0ec58035242bc8b2c8aae37037c9a886370eef6850703d7583e19964b", size = 8606464, upload-time = "2025-05-08T19:09:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/0e/34/24da1027e7fcdd9e82da3194c470143c551852757a4b473a09a012f5b945/matplotlib-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c0b9849a17bce080a16ebcb80a7b714b5677d0ec32161a2cc0a8e5a6030ae220", size = 9413103, upload-time = "2025-05-08T19:10:03.208Z" }, - { url = "https://files.pythonhosted.org/packages/a6/da/948a017c3ea13fd4a97afad5fdebe2f5bbc4d28c0654510ce6fd6b06b7bd/matplotlib-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:eef6ed6c03717083bc6d69c2d7ee8624205c29a8e6ea5a31cd3492ecdbaee1e1", size = 8065492, upload-time = "2025-05-08T19:10:05.271Z" }, - { url = "https://files.pythonhosted.org/packages/eb/43/6b80eb47d1071f234ef0c96ca370c2ca621f91c12045f1401b5c9b28a639/matplotlib-3.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ab1affc11d1f495ab9e6362b8174a25afc19c081ba5b0775ef00533a4236eea", size = 8179689, upload-time = "2025-05-08T19:10:07.602Z" }, - { url = "https://files.pythonhosted.org/packages/0f/70/d61a591958325c357204870b5e7b164f93f2a8cca1dc6ce940f563909a13/matplotlib-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a818d8bdcafa7ed2eed74487fdb071c09c1ae24152d403952adad11fa3c65b4", size = 8050466, upload-time = "2025-05-08T19:10:09.383Z" }, - { url = "https://files.pythonhosted.org/packages/e7/75/70c9d2306203148cc7902a961240c5927dd8728afedf35e6a77e105a2985/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748ebc3470c253e770b17d8b0557f0aa85cf8c63fd52f1a61af5b27ec0b7ffee", size = 8456252, upload-time = "2025-05-08T19:10:11.958Z" }, - { url = "https://files.pythonhosted.org/packages/c4/91/ba0ae1ff4b3f30972ad01cd4a8029e70a0ec3b8ea5be04764b128b66f763/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed70453fd99733293ace1aec568255bc51c6361cb0da94fa5ebf0649fdb2150a", size = 8601321, upload-time = "2025-05-08T19:10:14.47Z" }, - { url = "https://files.pythonhosted.org/packages/d2/88/d636041eb54a84b889e11872d91f7cbf036b3b0e194a70fa064eb8b04f7a/matplotlib-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbed9917b44070e55640bd13419de83b4c918e52d97561544814ba463811cbc7", size = 9406972, upload-time = "2025-05-08T19:10:16.569Z" }, - { url = "https://files.pythonhosted.org/packages/b1/79/0d1c165eac44405a86478082e225fce87874f7198300bbebc55faaf6d28d/matplotlib-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:cf37d8c6ef1a48829443e8ba5227b44236d7fcaf7647caa3178a4ff9f7a5be05", size = 8067954, upload-time = "2025-05-08T19:10:18.663Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c1/23cfb566a74c696a3b338d8955c549900d18fe2b898b6e94d682ca21e7c2/matplotlib-3.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9f2efccc8dcf2b86fc4ee849eea5dcaecedd0773b30f47980dc0cbeabf26ec84", size = 8180318, upload-time = "2025-05-08T19:10:20.426Z" }, - { url = "https://files.pythonhosted.org/packages/6c/0c/02f1c3b66b30da9ee343c343acbb6251bef5b01d34fad732446eaadcd108/matplotlib-3.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ddbba06a6c126e3301c3d272a99dcbe7f6c24c14024e80307ff03791a5f294e", size = 8051132, upload-time = "2025-05-08T19:10:22.569Z" }, - { url = "https://files.pythonhosted.org/packages/b4/ab/8db1a5ac9b3a7352fb914133001dae889f9fcecb3146541be46bed41339c/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748302b33ae9326995b238f606e9ed840bf5886ebafcb233775d946aa8107a15", size = 8457633, upload-time = "2025-05-08T19:10:24.749Z" }, - { url = "https://files.pythonhosted.org/packages/f5/64/41c4367bcaecbc03ef0d2a3ecee58a7065d0a36ae1aa817fe573a2da66d4/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a80fcccbef63302c0efd78042ea3c2436104c5b1a4d3ae20f864593696364ac7", size = 8601031, upload-time = "2025-05-08T19:10:27.03Z" }, - { url = "https://files.pythonhosted.org/packages/12/6f/6cc79e9e5ab89d13ed64da28898e40fe5b105a9ab9c98f83abd24e46d7d7/matplotlib-3.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55e46cbfe1f8586adb34f7587c3e4f7dedc59d5226719faf6cb54fc24f2fd52d", size = 9406988, upload-time = "2025-05-08T19:10:29.056Z" }, - { url = "https://files.pythonhosted.org/packages/b1/0f/eed564407bd4d935ffabf561ed31099ed609e19287409a27b6d336848653/matplotlib-3.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:151d89cb8d33cb23345cd12490c76fd5d18a56581a16d950b48c6ff19bb2ab93", size = 8068034, upload-time = "2025-05-08T19:10:31.221Z" }, - { url = "https://files.pythonhosted.org/packages/3e/e5/2f14791ff69b12b09e9975e1d116d9578ac684460860ce542c2588cb7a1c/matplotlib-3.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c26dd9834e74d164d06433dc7be5d75a1e9890b926b3e57e74fa446e1a62c3e2", size = 8218223, upload-time = "2025-05-08T19:10:33.114Z" }, - { url = "https://files.pythonhosted.org/packages/5c/08/30a94afd828b6e02d0a52cae4a29d6e9ccfcf4c8b56cc28b021d3588873e/matplotlib-3.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:24853dad5b8c84c8c2390fc31ce4858b6df504156893292ce8092d190ef8151d", size = 8094985, upload-time = "2025-05-08T19:10:35.337Z" }, - { url = "https://files.pythonhosted.org/packages/89/44/f3bc6b53066c889d7a1a3ea8094c13af6a667c5ca6220ec60ecceec2dabe/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68f7878214d369d7d4215e2a9075fef743be38fa401d32e6020bab2dfabaa566", size = 8483109, upload-time = "2025-05-08T19:10:37.611Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c7/473bc559beec08ebee9f86ca77a844b65747e1a6c2691e8c92e40b9f42a8/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6929fc618cb6db9cb75086f73b3219bbb25920cb24cee2ea7a12b04971a4158", size = 8618082, upload-time = "2025-05-08T19:10:39.892Z" }, - { url = "https://files.pythonhosted.org/packages/d8/e9/6ce8edd264c8819e37bbed8172e0ccdc7107fe86999b76ab5752276357a4/matplotlib-3.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c7818292a5cc372a2dc4c795e5c356942eb8350b98ef913f7fda51fe175ac5d", size = 9413699, upload-time = "2025-05-08T19:10:42.376Z" }, - { url = "https://files.pythonhosted.org/packages/1b/92/9a45c91089c3cf690b5badd4be81e392ff086ccca8a1d4e3a08463d8a966/matplotlib-3.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4f23ffe95c5667ef8a2b56eea9b53db7f43910fa4a2d5472ae0f72b64deab4d5", size = 8139044, upload-time = "2025-05-08T19:10:44.551Z" }, - { url = "https://files.pythonhosted.org/packages/3d/d1/f54d43e95384b312ffa4a74a4326c722f3b8187aaaa12e9a84cdf3037131/matplotlib-3.10.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:86ab63d66bbc83fdb6733471d3bff40897c1e9921cba112accd748eee4bce5e4", size = 8162896, upload-time = "2025-05-08T19:10:46.432Z" }, - { url = "https://files.pythonhosted.org/packages/24/a4/fbfc00c2346177c95b353dcf9b5a004106abe8730a62cb6f27e79df0a698/matplotlib-3.10.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a48f9c08bf7444b5d2391a83e75edb464ccda3c380384b36532a0962593a1751", size = 8039702, upload-time = "2025-05-08T19:10:49.634Z" }, - { url = "https://files.pythonhosted.org/packages/6a/b9/59e120d24a2ec5fc2d30646adb2efb4621aab3c6d83d66fb2a7a182db032/matplotlib-3.10.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb73d8aa75a237457988f9765e4dfe1c0d2453c5ca4eabc897d4309672c8e014", size = 8594298, upload-time = "2025-05-08T19:10:51.738Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/43/91/f2939bb60b7ebf12478b030e0d7f340247390f402b3b189616aad790c366/matplotlib-3.10.5.tar.gz", hash = "sha256:352ed6ccfb7998a00881692f38b4ca083c691d3e275b4145423704c34c909076", size = 34804044, upload-time = "2025-07-31T18:09:33.805Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/89/5355cdfe43242cb4d1a64a67cb6831398b665ad90e9702c16247cbd8d5ab/matplotlib-3.10.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5d4773a6d1c106ca05cb5a5515d277a6bb96ed09e5c8fab6b7741b8fcaa62c8f", size = 8229094, upload-time = "2025-07-31T18:07:36.507Z" }, + { url = "https://files.pythonhosted.org/packages/34/bc/ba802650e1c69650faed261a9df004af4c6f21759d7a1ec67fe972f093b3/matplotlib-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc88af74e7ba27de6cbe6faee916024ea35d895ed3d61ef6f58c4ce97da7185a", size = 8091464, upload-time = "2025-07-31T18:07:38.864Z" }, + { url = "https://files.pythonhosted.org/packages/ac/64/8d0c8937dee86c286625bddb1902efacc3e22f2b619f5b5a8df29fe5217b/matplotlib-3.10.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:64c4535419d5617f7363dad171a5a59963308e0f3f813c4bed6c9e6e2c131512", size = 8653163, upload-time = "2025-07-31T18:07:41.141Z" }, + { url = "https://files.pythonhosted.org/packages/11/dc/8dfc0acfbdc2fc2336c72561b7935cfa73db9ca70b875d8d3e1b3a6f371a/matplotlib-3.10.5-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a277033048ab22d34f88a3c5243938cef776493f6201a8742ed5f8b553201343", size = 9490635, upload-time = "2025-07-31T18:07:42.936Z" }, + { url = "https://files.pythonhosted.org/packages/54/02/e3fdfe0f2e9fb05f3a691d63876639dbf684170fdcf93231e973104153b4/matplotlib-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e4a6470a118a2e93022ecc7d3bd16b3114b2004ea2bf014fff875b3bc99b70c6", size = 9539036, upload-time = "2025-07-31T18:07:45.18Z" }, + { url = "https://files.pythonhosted.org/packages/c1/29/82bf486ff7f4dbedfb11ccc207d0575cbe3be6ea26f75be514252bde3d70/matplotlib-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:7e44cada61bec8833c106547786814dd4a266c1b2964fd25daa3804f1b8d4467", size = 8093529, upload-time = "2025-07-31T18:07:49.553Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c7/1f2db90a1d43710478bb1e9b57b162852f79234d28e4f48a28cc415aa583/matplotlib-3.10.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:dcfc39c452c6a9f9028d3e44d2d721484f665304857188124b505b2c95e1eecf", size = 8239216, upload-time = "2025-07-31T18:07:51.947Z" }, + { url = "https://files.pythonhosted.org/packages/82/6d/ca6844c77a4f89b1c9e4d481c412e1d1dbabf2aae2cbc5aa2da4a1d6683e/matplotlib-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:903352681b59f3efbf4546985142a9686ea1d616bb054b09a537a06e4b892ccf", size = 8102130, upload-time = "2025-07-31T18:07:53.65Z" }, + { url = "https://files.pythonhosted.org/packages/1d/1e/5e187a30cc673a3e384f3723e5f3c416033c1d8d5da414f82e4e731128ea/matplotlib-3.10.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:080c3676a56b8ee1c762bcf8fca3fe709daa1ee23e6ef06ad9f3fc17332f2d2a", size = 8666471, upload-time = "2025-07-31T18:07:55.304Z" }, + { url = "https://files.pythonhosted.org/packages/03/c0/95540d584d7d645324db99a845ac194e915ef75011a0d5e19e1b5cee7e69/matplotlib-3.10.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b4984d5064a35b6f66d2c11d668565f4389b1119cc64db7a4c1725bc11adffc", size = 9500518, upload-time = "2025-07-31T18:07:57.199Z" }, + { url = "https://files.pythonhosted.org/packages/ba/2e/e019352099ea58b4169adb9c6e1a2ad0c568c6377c2b677ee1f06de2adc7/matplotlib-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3967424121d3a46705c9fa9bdb0931de3228f13f73d7bb03c999c88343a89d89", size = 9552372, upload-time = "2025-07-31T18:07:59.41Z" }, + { url = "https://files.pythonhosted.org/packages/b7/81/3200b792a5e8b354f31f4101ad7834743ad07b6d620259f2059317b25e4d/matplotlib-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:33775bbeb75528555a15ac29396940128ef5613cf9a2d31fb1bfd18b3c0c0903", size = 8100634, upload-time = "2025-07-31T18:08:01.801Z" }, + { url = "https://files.pythonhosted.org/packages/52/46/a944f6f0c1f5476a0adfa501969d229ce5ae60cf9a663be0e70361381f89/matplotlib-3.10.5-cp311-cp311-win_arm64.whl", hash = "sha256:c61333a8e5e6240e73769d5826b9a31d8b22df76c0778f8480baf1b4b01c9420", size = 7978880, upload-time = "2025-07-31T18:08:03.407Z" }, + { url = "https://files.pythonhosted.org/packages/66/1e/c6f6bcd882d589410b475ca1fc22e34e34c82adff519caf18f3e6dd9d682/matplotlib-3.10.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:00b6feadc28a08bd3c65b2894f56cf3c94fc8f7adcbc6ab4516ae1e8ed8f62e2", size = 8253056, upload-time = "2025-07-31T18:08:05.385Z" }, + { url = "https://files.pythonhosted.org/packages/53/e6/d6f7d1b59413f233793dda14419776f5f443bcccb2dfc84b09f09fe05dbe/matplotlib-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee98a5c5344dc7f48dc261b6ba5d9900c008fc12beb3fa6ebda81273602cc389", size = 8110131, upload-time = "2025-07-31T18:08:07.293Z" }, + { url = "https://files.pythonhosted.org/packages/66/2b/bed8a45e74957549197a2ac2e1259671cd80b55ed9e1fe2b5c94d88a9202/matplotlib-3.10.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a17e57e33de901d221a07af32c08870ed4528db0b6059dce7d7e65c1122d4bea", size = 8669603, upload-time = "2025-07-31T18:08:09.064Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a7/315e9435b10d057f5e52dfc603cd353167ae28bb1a4e033d41540c0067a4/matplotlib-3.10.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97b9d6443419085950ee4a5b1ee08c363e5c43d7176e55513479e53669e88468", size = 9508127, upload-time = "2025-07-31T18:08:10.845Z" }, + { url = "https://files.pythonhosted.org/packages/7f/d9/edcbb1f02ca99165365d2768d517898c22c6040187e2ae2ce7294437c413/matplotlib-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ceefe5d40807d29a66ae916c6a3915d60ef9f028ce1927b84e727be91d884369", size = 9566926, upload-time = "2025-07-31T18:08:13.186Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d9/6dd924ad5616c97b7308e6320cf392c466237a82a2040381163b7500510a/matplotlib-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:c04cba0f93d40e45b3c187c6c52c17f24535b27d545f757a2fffebc06c12b98b", size = 8107599, upload-time = "2025-07-31T18:08:15.116Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f3/522dc319a50f7b0279fbe74f86f7a3506ce414bc23172098e8d2bdf21894/matplotlib-3.10.5-cp312-cp312-win_arm64.whl", hash = "sha256:a41bcb6e2c8e79dc99c5511ae6f7787d2fb52efd3d805fff06d5d4f667db16b2", size = 7978173, upload-time = "2025-07-31T18:08:21.518Z" }, + { url = "https://files.pythonhosted.org/packages/8d/05/4f3c1f396075f108515e45cb8d334aff011a922350e502a7472e24c52d77/matplotlib-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:354204db3f7d5caaa10e5de74549ef6a05a4550fdd1c8f831ab9bca81efd39ed", size = 8253586, upload-time = "2025-07-31T18:08:23.107Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2c/e084415775aac7016c3719fe7006cdb462582c6c99ac142f27303c56e243/matplotlib-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b072aac0c3ad563a2b3318124756cb6112157017f7431626600ecbe890df57a1", size = 8110715, upload-time = "2025-07-31T18:08:24.675Z" }, + { url = "https://files.pythonhosted.org/packages/52/1b/233e3094b749df16e3e6cd5a44849fd33852e692ad009cf7de00cf58ddf6/matplotlib-3.10.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d52fd5b684d541b5a51fb276b2b97b010c75bee9aa392f96b4a07aeb491e33c7", size = 8669397, upload-time = "2025-07-31T18:08:26.778Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ec/03f9e003a798f907d9f772eed9b7c6a9775d5bd00648b643ebfb88e25414/matplotlib-3.10.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7a09ae2f4676276f5a65bd9f2bd91b4f9fbaedf49f40267ce3f9b448de501f", size = 9508646, upload-time = "2025-07-31T18:08:28.848Z" }, + { url = "https://files.pythonhosted.org/packages/91/e7/c051a7a386680c28487bca27d23b02d84f63e3d2a9b4d2fc478e6a42e37e/matplotlib-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ba6c3c9c067b83481d647af88b4e441d532acdb5ef22178a14935b0b881188f4", size = 9567424, upload-time = "2025-07-31T18:08:30.726Z" }, + { url = "https://files.pythonhosted.org/packages/36/c2/24302e93ff431b8f4173ee1dd88976c8d80483cadbc5d3d777cef47b3a1c/matplotlib-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:07442d2692c9bd1cceaa4afb4bbe5b57b98a7599de4dabfcca92d3eea70f9ebe", size = 8107809, upload-time = "2025-07-31T18:08:33.928Z" }, + { url = "https://files.pythonhosted.org/packages/0b/33/423ec6a668d375dad825197557ed8fbdb74d62b432c1ed8235465945475f/matplotlib-3.10.5-cp313-cp313-win_arm64.whl", hash = "sha256:48fe6d47380b68a37ccfcc94f009530e84d41f71f5dae7eda7c4a5a84aa0a674", size = 7978078, upload-time = "2025-07-31T18:08:36.764Z" }, + { url = "https://files.pythonhosted.org/packages/51/17/521fc16ec766455c7bb52cc046550cf7652f6765ca8650ff120aa2d197b6/matplotlib-3.10.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b80eb8621331449fc519541a7461987f10afa4f9cfd91afcd2276ebe19bd56c", size = 8295590, upload-time = "2025-07-31T18:08:38.521Z" }, + { url = "https://files.pythonhosted.org/packages/f8/12/23c28b2c21114c63999bae129fce7fd34515641c517ae48ce7b7dcd33458/matplotlib-3.10.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47a388908e469d6ca2a6015858fa924e0e8a2345a37125948d8e93a91c47933e", size = 8158518, upload-time = "2025-07-31T18:08:40.195Z" }, + { url = "https://files.pythonhosted.org/packages/81/f8/aae4eb25e8e7190759f3cb91cbeaa344128159ac92bb6b409e24f8711f78/matplotlib-3.10.5-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b6b49167d208358983ce26e43aa4196073b4702858670f2eb111f9a10652b4b", size = 8691815, upload-time = "2025-07-31T18:08:42.238Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ba/450c39ebdd486bd33a359fc17365ade46c6a96bf637bbb0df7824de2886c/matplotlib-3.10.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a8da0453a7fd8e3da114234ba70c5ba9ef0e98f190309ddfde0f089accd46ea", size = 9522814, upload-time = "2025-07-31T18:08:44.914Z" }, + { url = "https://files.pythonhosted.org/packages/89/11/9c66f6a990e27bb9aa023f7988d2d5809cb98aa39c09cbf20fba75a542ef/matplotlib-3.10.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52c6573dfcb7726a9907b482cd5b92e6b5499b284ffacb04ffbfe06b3e568124", size = 9573917, upload-time = "2025-07-31T18:08:47.038Z" }, + { url = "https://files.pythonhosted.org/packages/b3/69/8b49394de92569419e5e05e82e83df9b749a0ff550d07631ea96ed2eb35a/matplotlib-3.10.5-cp313-cp313t-win_amd64.whl", hash = "sha256:a23193db2e9d64ece69cac0c8231849db7dd77ce59c7b89948cf9d0ce655a3ce", size = 8181034, upload-time = "2025-07-31T18:08:48.943Z" }, + { url = "https://files.pythonhosted.org/packages/47/23/82dc435bb98a2fc5c20dffcac8f0b083935ac28286413ed8835df40d0baa/matplotlib-3.10.5-cp313-cp313t-win_arm64.whl", hash = "sha256:56da3b102cf6da2776fef3e71cd96fcf22103a13594a18ac9a9b31314e0be154", size = 8023337, upload-time = "2025-07-31T18:08:50.791Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e0/26b6cfde31f5383503ee45dcb7e691d45dadf0b3f54639332b59316a97f8/matplotlib-3.10.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:96ef8f5a3696f20f55597ffa91c28e2e73088df25c555f8d4754931515512715", size = 8253591, upload-time = "2025-07-31T18:08:53.254Z" }, + { url = "https://files.pythonhosted.org/packages/c1/89/98488c7ef7ea20ea659af7499628c240a608b337af4be2066d644cfd0a0f/matplotlib-3.10.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:77fab633e94b9da60512d4fa0213daeb76d5a7b05156840c4fd0399b4b818837", size = 8112566, upload-time = "2025-07-31T18:08:55.116Z" }, + { url = "https://files.pythonhosted.org/packages/52/67/42294dfedc82aea55e1a767daf3263aacfb5a125f44ba189e685bab41b6f/matplotlib-3.10.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27f52634315e96b1debbfdc5c416592edcd9c4221bc2f520fd39c33db5d9f202", size = 9513281, upload-time = "2025-07-31T18:08:56.885Z" }, + { url = "https://files.pythonhosted.org/packages/e7/68/f258239e0cf34c2cbc816781c7ab6fca768452e6bf1119aedd2bd4a882a3/matplotlib-3.10.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:525f6e28c485c769d1f07935b660c864de41c37fd716bfa64158ea646f7084bb", size = 9780873, upload-time = "2025-07-31T18:08:59.241Z" }, + { url = "https://files.pythonhosted.org/packages/89/64/f4881554006bd12e4558bd66778bdd15d47b00a1f6c6e8b50f6208eda4b3/matplotlib-3.10.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1f5f3ec4c191253c5f2b7c07096a142c6a1c024d9f738247bfc8e3f9643fc975", size = 9568954, upload-time = "2025-07-31T18:09:01.244Z" }, + { url = "https://files.pythonhosted.org/packages/06/f8/42779d39c3f757e1f012f2dda3319a89fb602bd2ef98ce8faf0281f4febd/matplotlib-3.10.5-cp314-cp314-win_amd64.whl", hash = "sha256:707f9c292c4cd4716f19ab8a1f93f26598222cd931e0cd98fbbb1c5994bf7667", size = 8237465, upload-time = "2025-07-31T18:09:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/cf/f8/153fd06b5160f0cd27c8b9dd797fcc9fb56ac6a0ebf3c1f765b6b68d3c8a/matplotlib-3.10.5-cp314-cp314-win_arm64.whl", hash = "sha256:21a95b9bf408178d372814de7baacd61c712a62cae560b5e6f35d791776f6516", size = 8108898, upload-time = "2025-07-31T18:09:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ee/c4b082a382a225fe0d2a73f1f57cf6f6f132308805b493a54c8641006238/matplotlib-3.10.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a6b310f95e1102a8c7c817ef17b60ee5d1851b8c71b63d9286b66b177963039e", size = 8295636, upload-time = "2025-07-31T18:09:07.306Z" }, + { url = "https://files.pythonhosted.org/packages/30/73/2195fa2099718b21a20da82dfc753bf2af58d596b51aefe93e359dd5915a/matplotlib-3.10.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:94986a242747a0605cb3ff1cb98691c736f28a59f8ffe5175acaeb7397c49a5a", size = 8158575, upload-time = "2025-07-31T18:09:09.083Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e9/a08cdb34618a91fa08f75e6738541da5cacde7c307cea18ff10f0d03fcff/matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ff10ea43288f0c8bab608a305dc6c918cc729d429c31dcbbecde3b9f4d5b569", size = 9522815, upload-time = "2025-07-31T18:09:11.191Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/34d8b7e0d1bb6d06ef45db01dfa560d5a67b1c40c0b998ce9ccde934bb09/matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6adb644c9d040ffb0d3434e440490a66cf73dbfa118a6f79cd7568431f7a012", size = 9783514, upload-time = "2025-07-31T18:09:13.307Z" }, + { url = "https://files.pythonhosted.org/packages/12/09/d330d1e55dcca2e11b4d304cc5227f52e2512e46828d6249b88e0694176e/matplotlib-3.10.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4fa40a8f98428f789a9dcacd625f59b7bc4e3ef6c8c7c80187a7a709475cf592", size = 9573932, upload-time = "2025-07-31T18:09:15.335Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3b/f70258ac729aa004aca673800a53a2b0a26d49ca1df2eaa03289a1c40f81/matplotlib-3.10.5-cp314-cp314t-win_amd64.whl", hash = "sha256:95672a5d628b44207aab91ec20bf59c26da99de12b88f7e0b1fb0a84a86ff959", size = 8322003, upload-time = "2025-07-31T18:09:17.416Z" }, + { url = "https://files.pythonhosted.org/packages/5b/60/3601f8ce6d76a7c81c7f25a0e15fde0d6b66226dd187aa6d2838e6374161/matplotlib-3.10.5-cp314-cp314t-win_arm64.whl", hash = "sha256:2efaf97d72629e74252e0b5e3c46813e9eeaa94e011ecf8084a971a31a97f40b", size = 8153849, upload-time = "2025-07-31T18:09:19.673Z" }, + { url = "https://files.pythonhosted.org/packages/e4/eb/7d4c5de49eb78294e1a8e2be8a6ecff8b433e921b731412a56cd1abd3567/matplotlib-3.10.5-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b5fa2e941f77eb579005fb804026f9d0a1082276118d01cc6051d0d9626eaa7f", size = 8222360, upload-time = "2025-07-31T18:09:21.813Z" }, + { url = "https://files.pythonhosted.org/packages/16/8a/e435db90927b66b16d69f8f009498775f4469f8de4d14b87856965e58eba/matplotlib-3.10.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1fc0d2a3241cdcb9daaca279204a3351ce9df3c0e7e621c7e04ec28aaacaca30", size = 8087462, upload-time = "2025-07-31T18:09:23.504Z" }, + { url = "https://files.pythonhosted.org/packages/0b/dd/06c0e00064362f5647f318e00b435be2ff76a1bdced97c5eaf8347311fbe/matplotlib-3.10.5-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8dee65cb1424b7dc982fe87895b5613d4e691cc57117e8af840da0148ca6c1d7", size = 8659802, upload-time = "2025-07-31T18:09:25.256Z" }, + { url = "https://files.pythonhosted.org/packages/dc/d6/e921be4e1a5f7aca5194e1f016cb67ec294548e530013251f630713e456d/matplotlib-3.10.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:160e125da27a749481eaddc0627962990f6029811dbeae23881833a011a0907f", size = 8233224, upload-time = "2025-07-31T18:09:27.512Z" }, + { url = "https://files.pythonhosted.org/packages/ec/74/a2b9b04824b9c349c8f1b2d21d5af43fa7010039427f2b133a034cb09e59/matplotlib-3.10.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac3d50760394d78a3c9be6b28318fe22b494c4fcf6407e8fd4794b538251899b", size = 8098539, upload-time = "2025-07-31T18:09:29.629Z" }, + { url = "https://files.pythonhosted.org/packages/fc/66/cd29ebc7f6c0d2a15d216fb572573e8fc38bd5d6dec3bd9d7d904c0949f7/matplotlib-3.10.5-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c49465bf689c4d59d174d0c7795fb42a21d4244d11d70e52b8011987367ac61", size = 8672192, upload-time = "2025-07-31T18:09:31.407Z" }, ] [[package]] @@ -1501,7 +1530,7 @@ wheels = [ [[package]] name = "mqt-bench" -version = "2.0.0" +version = "2.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "networkx" }, @@ -1510,9 +1539,9 @@ dependencies = [ { name = "qiskit", extra = ["qasm3-import"] }, { name = "scikit-learn" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/c6/2b9e60596a77eff566dc2dc3ebb0292be2f28cbfecaaa945628f54e50b34/mqt_bench-2.0.0.tar.gz", hash = "sha256:cf9883a806f93f060f5dc7759d87c4a80659a1b726e0d663a831fd6556dc1599", size = 870449, upload-time = "2025-06-24T10:25:39.964Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/c4/0bc70612ec338bf24ae8d94a1319a9151f0618f5b14eb6132cba36b479aa/mqt_bench-2.0.1.tar.gz", hash = "sha256:0e6d9d7c28577e3a8d3ef95a19642d634390f8a620efae9f6087e3360759ce92", size = 890344, upload-time = "2025-07-28T12:34:27.612Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/2c/b42f4ebdc21f08cd2f056dece821e97f0d69fa31662a254a4072e9e031c8/mqt_bench-2.0.0-py3-none-any.whl", hash = "sha256:c1d1d67f9c2a6ccd61ed7ca6e4f145dffeefde7dc27687e652f7bd52f0bd22e9", size = 71017, upload-time = "2025-06-24T10:25:38.461Z" }, + { url = "https://files.pythonhosted.org/packages/05/22/ac7ab7398708af9f205b595e4f2a1559622d7ddbf9a6f41b2a3f143e4b31/mqt_bench-2.0.1-py3-none-any.whl", hash = "sha256:5e0d0765688aef43ef334967fbb98433f6b56574f54e9ee89c421ead3f1038f7", size = 71008, upload-time = "2025-07-28T12:34:25.713Z" }, ] [[package]] @@ -1534,6 +1563,7 @@ dependencies = [ { name = "scikit-learn" }, { name = "tensorboard" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' and 'x86_64' not in platform_machine and sys_platform != 'darwin'" }, { name = "tqdm" }, { name = "typing-extensions" }, ] @@ -1582,7 +1612,7 @@ requires-dist = [ { name = "numpy", marker = "python_full_version >= '3.13'", specifier = ">=2.1" }, { name = "numpy", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'", specifier = ">=1.22,<2" }, { name = "pytket", specifier = ">=1.29.0" }, - { name = "pytket-qiskit", specifier = ">=0.60.0" }, + { name = "pytket-qiskit", specifier = ">=0.71.0" }, { name = "qiskit", specifier = "!=1.3.2" }, { name = "qiskit-ibm-ai-local-transpiler", specifier = ">=0.3.2" }, { name = "qiskit-ibm-transpiler", specifier = ">=0.11.1" }, @@ -1591,6 +1621,7 @@ requires-dist = [ { name = "scikit-learn", specifier = ">=1.5.1" }, { name = "tensorboard", specifier = ">=2.17.0" }, { name = "torch", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'", specifier = ">=2.2.2,<2.3.0" }, + { name = "torch", marker = "python_full_version >= '3.12' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", specifier = ">=2.4.0" }, { name = "tqdm", specifier = ">=4.66.0" }, { name = "typing-extensions", specifier = ">=4.1" }, ] @@ -1763,8 +1794,10 @@ name = "numpy" version = "2.3.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13'", - "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", + "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", ] sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" } @@ -2519,7 +2552,7 @@ wheels = [ [[package]] name = "pytket-qiskit" -version = "0.66.0" +version = "0.71.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin')" }, @@ -2528,9 +2561,10 @@ dependencies = [ { name = "qiskit" }, { name = "qiskit-aer" }, { name = "qiskit-ibm-runtime" }, + { name = "symengine" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/73/4d/6395c0a6a2e6f08f493b031887aadc2b276d12b21c3b9b6f30036a43b498/pytket_qiskit-0.66.0-py3-none-any.whl", hash = "sha256:15d72ae6190e2280356e64bf387ca2d2d51714c395739b364fe3bbf9bd4bd096", size = 56193, upload-time = "2025-03-26T15:18:24.58Z" }, + { url = "https://files.pythonhosted.org/packages/a0/31/7150385fe20c9d946d73a2251f50630ddf7e612e99f8427d06cb91932e20/pytket_qiskit-0.71.0-py3-none-any.whl", hash = "sha256:4e64f44fdc1cbd4c1f94d4f9de59c7b393ff1cda639c06de5cad01c770fc3000", size = 57187, upload-time = "2025-07-21T09:27:09.443Z" }, ] [[package]] @@ -2610,92 +2644,100 @@ wheels = [ [[package]] name = "pyzmq" -version = "27.0.0" +version = "27.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "implementation_name == 'pypy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/06/50a4e9648b3e8b992bef8eb632e457307553a89d294103213cfd47b3da69/pyzmq-27.0.0.tar.gz", hash = "sha256:b1f08eeb9ce1510e6939b6e5dcd46a17765e2333daae78ecf4606808442e52cf", size = 280478, upload-time = "2025-06-13T14:09:07.087Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/09/1681d4b047626d352c083770618ac29655ab1f5c20eee31dc94c000b9b7b/pyzmq-27.0.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:b973ee650e8f442ce482c1d99ca7ab537c69098d53a3d046676a484fd710c87a", size = 1329291, upload-time = "2025-06-13T14:06:57.945Z" }, - { url = "https://files.pythonhosted.org/packages/9d/b2/9c9385225fdd54db9506ed8accbb9ea63ca813ba59d43d7f282a6a16a30b/pyzmq-27.0.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:661942bc7cd0223d569d808f2e5696d9cc120acc73bf3e88a1f1be7ab648a7e4", size = 905952, upload-time = "2025-06-13T14:07:03.232Z" }, - { url = "https://files.pythonhosted.org/packages/41/73/333c72c7ec182cdffe25649e3da1c3b9f3cf1cede63cfdc23d1384d4a601/pyzmq-27.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50360fb2a056ffd16e5f4177eee67f1dd1017332ea53fb095fe7b5bf29c70246", size = 666165, upload-time = "2025-06-13T14:07:04.667Z" }, - { url = "https://files.pythonhosted.org/packages/a5/fe/fc7b9c1a50981928e25635a926653cb755364316db59ccd6e79cfb9a0b4f/pyzmq-27.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf209a6dc4b420ed32a7093642843cbf8703ed0a7d86c16c0b98af46762ebefb", size = 853755, upload-time = "2025-06-13T14:07:06.93Z" }, - { url = "https://files.pythonhosted.org/packages/8c/4c/740ed4b6e8fa160cd19dc5abec8db68f440564b2d5b79c1d697d9862a2f7/pyzmq-27.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c2dace4a7041cca2fba5357a2d7c97c5effdf52f63a1ef252cfa496875a3762d", size = 1654868, upload-time = "2025-06-13T14:07:08.224Z" }, - { url = "https://files.pythonhosted.org/packages/97/00/875b2ecfcfc78ab962a59bd384995186818524ea957dc8ad3144611fae12/pyzmq-27.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:63af72b2955fc77caf0a77444baa2431fcabb4370219da38e1a9f8d12aaebe28", size = 2033443, upload-time = "2025-06-13T14:07:09.653Z" }, - { url = "https://files.pythonhosted.org/packages/60/55/6dd9c470c42d713297c5f2a56f7903dc1ebdb4ab2edda996445c21651900/pyzmq-27.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e8c4adce8e37e75c4215297d7745551b8dcfa5f728f23ce09bf4e678a9399413", size = 1891288, upload-time = "2025-06-13T14:07:11.099Z" }, - { url = "https://files.pythonhosted.org/packages/28/5d/54b0ef50d40d7c65a627f4a4b4127024ba9820f2af8acd933a4d30ae192e/pyzmq-27.0.0-cp310-cp310-win32.whl", hash = "sha256:5d5ef4718ecab24f785794e0e7536436698b459bfbc19a1650ef55280119d93b", size = 567936, upload-time = "2025-06-13T14:07:12.468Z" }, - { url = "https://files.pythonhosted.org/packages/18/ea/dedca4321de748ca48d3bcdb72274d4d54e8d84ea49088d3de174bd45d88/pyzmq-27.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:e40609380480b3d12c30f841323f42451c755b8fece84235236f5fe5ffca8c1c", size = 628686, upload-time = "2025-06-13T14:07:14.051Z" }, - { url = "https://files.pythonhosted.org/packages/d4/a7/fcdeedc306e71e94ac262cba2d02337d885f5cdb7e8efced8e5ffe327808/pyzmq-27.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:6b0397b0be277b46762956f576e04dc06ced265759e8c2ff41a0ee1aa0064198", size = 559039, upload-time = "2025-06-13T14:07:15.289Z" }, - { url = "https://files.pythonhosted.org/packages/44/df/84c630654106d9bd9339cdb564aa941ed41b023a0264251d6743766bb50e/pyzmq-27.0.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:21457825249b2a53834fa969c69713f8b5a79583689387a5e7aed880963ac564", size = 1332718, upload-time = "2025-06-13T14:07:16.555Z" }, - { url = "https://files.pythonhosted.org/packages/c1/8e/f6a5461a07654d9840d256476434ae0ff08340bba562a455f231969772cb/pyzmq-27.0.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1958947983fef513e6e98eff9cb487b60bf14f588dc0e6bf35fa13751d2c8251", size = 908248, upload-time = "2025-06-13T14:07:18.033Z" }, - { url = "https://files.pythonhosted.org/packages/7c/93/82863e8d695a9a3ae424b63662733ae204a295a2627d52af2f62c2cd8af9/pyzmq-27.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0dc628b5493f9a8cd9844b8bee9732ef587ab00002157c9329e4fc0ef4d3afa", size = 668647, upload-time = "2025-06-13T14:07:19.378Z" }, - { url = "https://files.pythonhosted.org/packages/f3/85/15278769b348121eacdbfcbd8c4d40f1102f32fa6af5be1ffc032ed684be/pyzmq-27.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7bbe9e1ed2c8d3da736a15694d87c12493e54cc9dc9790796f0321794bbc91f", size = 856600, upload-time = "2025-06-13T14:07:20.906Z" }, - { url = "https://files.pythonhosted.org/packages/d4/af/1c469b3d479bd095edb28e27f12eee10b8f00b356acbefa6aeb14dd295d1/pyzmq-27.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc1091f59143b471d19eb64f54bae4f54bcf2a466ffb66fe45d94d8d734eb495", size = 1657748, upload-time = "2025-06-13T14:07:22.549Z" }, - { url = "https://files.pythonhosted.org/packages/8c/f4/17f965d0ee6380b1d6326da842a50e4b8b9699745161207945f3745e8cb5/pyzmq-27.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7011ade88c8e535cf140f8d1a59428676fbbce7c6e54fefce58bf117aefb6667", size = 2034311, upload-time = "2025-06-13T14:07:23.966Z" }, - { url = "https://files.pythonhosted.org/packages/e0/6e/7c391d81fa3149fd759de45d298003de6cfab343fb03e92c099821c448db/pyzmq-27.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c386339d7e3f064213aede5d03d054b237937fbca6dd2197ac8cf3b25a6b14e", size = 1893630, upload-time = "2025-06-13T14:07:25.899Z" }, - { url = "https://files.pythonhosted.org/packages/0e/e0/eaffe7a86f60e556399e224229e7769b717f72fec0706b70ab2c03aa04cb/pyzmq-27.0.0-cp311-cp311-win32.whl", hash = "sha256:0546a720c1f407b2172cb04b6b094a78773491497e3644863cf5c96c42df8cff", size = 567706, upload-time = "2025-06-13T14:07:27.595Z" }, - { url = "https://files.pythonhosted.org/packages/c9/05/89354a8cffdcce6e547d48adaaf7be17007fc75572123ff4ca90a4ca04fc/pyzmq-27.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:15f39d50bd6c9091c67315ceb878a4f531957b121d2a05ebd077eb35ddc5efed", size = 630322, upload-time = "2025-06-13T14:07:28.938Z" }, - { url = "https://files.pythonhosted.org/packages/fa/07/4ab976d5e1e63976719389cc4f3bfd248a7f5f2bb2ebe727542363c61b5f/pyzmq-27.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c5817641eebb391a2268c27fecd4162448e03538387093cdbd8bf3510c316b38", size = 558435, upload-time = "2025-06-13T14:07:30.256Z" }, - { url = "https://files.pythonhosted.org/packages/93/a7/9ad68f55b8834ede477842214feba6a4c786d936c022a67625497aacf61d/pyzmq-27.0.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:cbabc59dcfaac66655c040dfcb8118f133fb5dde185e5fc152628354c1598e52", size = 1305438, upload-time = "2025-06-13T14:07:31.676Z" }, - { url = "https://files.pythonhosted.org/packages/ba/ee/26aa0f98665a22bc90ebe12dced1de5f3eaca05363b717f6fb229b3421b3/pyzmq-27.0.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:cb0ac5179cba4b2f94f1aa208fbb77b62c4c9bf24dd446278b8b602cf85fcda3", size = 895095, upload-time = "2025-06-13T14:07:33.104Z" }, - { url = "https://files.pythonhosted.org/packages/cf/85/c57e7ab216ecd8aa4cc7e3b83b06cc4e9cf45c87b0afc095f10cd5ce87c1/pyzmq-27.0.0-cp312-abi3-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53a48f0228eab6cbf69fde3aa3c03cbe04e50e623ef92ae395fce47ef8a76152", size = 651826, upload-time = "2025-06-13T14:07:34.831Z" }, - { url = "https://files.pythonhosted.org/packages/69/9a/9ea7e230feda9400fb0ae0d61d7d6ddda635e718d941c44eeab22a179d34/pyzmq-27.0.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:111db5f395e09f7e775f759d598f43cb815fc58e0147623c4816486e1a39dc22", size = 839750, upload-time = "2025-06-13T14:07:36.553Z" }, - { url = "https://files.pythonhosted.org/packages/08/66/4cebfbe71f3dfbd417011daca267539f62ed0fbc68105357b68bbb1a25b7/pyzmq-27.0.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c8878011653dcdc27cc2c57e04ff96f0471e797f5c19ac3d7813a245bcb24371", size = 1641357, upload-time = "2025-06-13T14:07:38.21Z" }, - { url = "https://files.pythonhosted.org/packages/ac/f6/b0f62578c08d2471c791287149cb8c2aaea414ae98c6e995c7dbe008adfb/pyzmq-27.0.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:c0ed2c1f335ba55b5fdc964622254917d6b782311c50e138863eda409fbb3b6d", size = 2020281, upload-time = "2025-06-13T14:07:39.599Z" }, - { url = "https://files.pythonhosted.org/packages/37/b9/4f670b15c7498495da9159edc374ec09c88a86d9cd5a47d892f69df23450/pyzmq-27.0.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e918d70862d4cfd4b1c187310015646a14e1f5917922ab45b29f28f345eeb6be", size = 1877110, upload-time = "2025-06-13T14:07:41.027Z" }, - { url = "https://files.pythonhosted.org/packages/66/31/9dee25c226295b740609f0d46db2fe972b23b6f5cf786360980524a3ba92/pyzmq-27.0.0-cp312-abi3-win32.whl", hash = "sha256:88b4e43cab04c3c0f0d55df3b1eef62df2b629a1a369b5289a58f6fa8b07c4f4", size = 559297, upload-time = "2025-06-13T14:07:42.533Z" }, - { url = "https://files.pythonhosted.org/packages/9b/12/52da5509800f7ff2d287b2f2b4e636e7ea0f001181cba6964ff6c1537778/pyzmq-27.0.0-cp312-abi3-win_amd64.whl", hash = "sha256:dce4199bf5f648a902ce37e7b3afa286f305cd2ef7a8b6ec907470ccb6c8b371", size = 619203, upload-time = "2025-06-13T14:07:43.843Z" }, - { url = "https://files.pythonhosted.org/packages/93/6d/7f2e53b19d1edb1eb4f09ec7c3a1f945ca0aac272099eab757d15699202b/pyzmq-27.0.0-cp312-abi3-win_arm64.whl", hash = "sha256:56e46bbb85d52c1072b3f809cc1ce77251d560bc036d3a312b96db1afe76db2e", size = 551927, upload-time = "2025-06-13T14:07:45.51Z" }, - { url = "https://files.pythonhosted.org/packages/19/62/876b27c4ff777db4ceba1c69ea90d3c825bb4f8d5e7cd987ce5802e33c55/pyzmq-27.0.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c36ad534c0c29b4afa088dc53543c525b23c0797e01b69fef59b1a9c0e38b688", size = 1340826, upload-time = "2025-06-13T14:07:46.881Z" }, - { url = "https://files.pythonhosted.org/packages/43/69/58ef8f4f59d3bcd505260c73bee87b008850f45edca40ddaba54273c35f4/pyzmq-27.0.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:67855c14173aec36395d7777aaba3cc527b393821f30143fd20b98e1ff31fd38", size = 897283, upload-time = "2025-06-13T14:07:49.562Z" }, - { url = "https://files.pythonhosted.org/packages/43/15/93a0d0396700a60475ad3c5d42c5f1c308d3570bc94626b86c71ef9953e0/pyzmq-27.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8617c7d43cd8ccdb62aebe984bfed77ca8f036e6c3e46dd3dddda64b10f0ab7a", size = 660567, upload-time = "2025-06-13T14:07:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/0e/b3/fe055513e498ca32f64509abae19b9c9eb4d7c829e02bd8997dd51b029eb/pyzmq-27.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:67bfbcbd0a04c575e8103a6061d03e393d9f80ffdb9beb3189261e9e9bc5d5e9", size = 847681, upload-time = "2025-06-13T14:07:52.77Z" }, - { url = "https://files.pythonhosted.org/packages/b6/4f/ff15300b00b5b602191f3df06bbc8dd4164e805fdd65bb77ffbb9c5facdc/pyzmq-27.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5cd11d46d7b7e5958121b3eaf4cd8638eff3a720ec527692132f05a57f14341d", size = 1650148, upload-time = "2025-06-13T14:07:54.178Z" }, - { url = "https://files.pythonhosted.org/packages/c4/6f/84bdfff2a224a6f26a24249a342e5906993c50b0761e311e81b39aef52a7/pyzmq-27.0.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:b801c2e40c5aa6072c2f4876de8dccd100af6d9918d4d0d7aa54a1d982fd4f44", size = 2023768, upload-time = "2025-06-13T14:07:55.714Z" }, - { url = "https://files.pythonhosted.org/packages/64/39/dc2db178c26a42228c5ac94a9cc595030458aa64c8d796a7727947afbf55/pyzmq-27.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20d5cb29e8c5f76a127c75b6e7a77e846bc4b655c373baa098c26a61b7ecd0ef", size = 1885199, upload-time = "2025-06-13T14:07:57.166Z" }, - { url = "https://files.pythonhosted.org/packages/c7/21/dae7b06a1f8cdee5d8e7a63d99c5d129c401acc40410bef2cbf42025e26f/pyzmq-27.0.0-cp313-cp313t-win32.whl", hash = "sha256:a20528da85c7ac7a19b7384e8c3f8fa707841fd85afc4ed56eda59d93e3d98ad", size = 575439, upload-time = "2025-06-13T14:07:58.959Z" }, - { url = "https://files.pythonhosted.org/packages/eb/bc/1709dc55f0970cf4cb8259e435e6773f9946f41a045c2cb90e870b7072da/pyzmq-27.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d8229f2efece6a660ee211d74d91dbc2a76b95544d46c74c615e491900dc107f", size = 639933, upload-time = "2025-06-13T14:08:00.777Z" }, - { url = "https://files.pythonhosted.org/packages/09/6f/be6523a7f3821c0b5370912ef02822c028611360e0d206dd945bdbf9eaef/pyzmq-27.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:656c1866505a5735d0660b7da6d7147174bbf59d4975fc2b7f09f43c9bc25745", size = 835950, upload-time = "2025-06-13T14:08:35Z" }, - { url = "https://files.pythonhosted.org/packages/c6/1e/a50fdd5c15018de07ab82a61bc460841be967ee7bbe7abee3b714d66f7ac/pyzmq-27.0.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74175b9e12779382432dd1d1f5960ebe7465d36649b98a06c6b26be24d173fab", size = 799876, upload-time = "2025-06-13T14:08:36.849Z" }, - { url = "https://files.pythonhosted.org/packages/88/a1/89eb5b71f5a504f8f887aceb8e1eb3626e00c00aa8085381cdff475440dc/pyzmq-27.0.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8c6de908465697a8708e4d6843a1e884f567962fc61eb1706856545141d0cbb", size = 567400, upload-time = "2025-06-13T14:08:38.95Z" }, - { url = "https://files.pythonhosted.org/packages/56/aa/4571dbcff56cfb034bac73fde8294e123c975ce3eea89aff31bf6dc6382b/pyzmq-27.0.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c644aaacc01d0df5c7072826df45e67301f191c55f68d7b2916d83a9ddc1b551", size = 747031, upload-time = "2025-06-13T14:08:40.413Z" }, - { url = "https://files.pythonhosted.org/packages/46/e0/d25f30fe0991293c5b2f5ef3b070d35fa6d57c0c7428898c3ab4913d0297/pyzmq-27.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:10f70c1d9a446a85013a36871a296007f6fe4232b530aa254baf9da3f8328bc0", size = 544726, upload-time = "2025-06-13T14:08:41.997Z" }, - { url = "https://files.pythonhosted.org/packages/98/a6/92394373b8dbc1edc9d53c951e8d3989d518185174ee54492ec27711779d/pyzmq-27.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd1dc59763effd1576f8368047c9c31468fce0af89d76b5067641137506792ae", size = 835948, upload-time = "2025-06-13T14:08:43.516Z" }, - { url = "https://files.pythonhosted.org/packages/56/f3/4dc38d75d9995bfc18773df3e41f2a2ca9b740b06f1a15dbf404077e7588/pyzmq-27.0.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:60e8cc82d968174650c1860d7b716366caab9973787a1c060cf8043130f7d0f7", size = 799874, upload-time = "2025-06-13T14:08:45.017Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ba/64af397e0f421453dc68e31d5e0784d554bf39013a2de0872056e96e58af/pyzmq-27.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14fe7aaac86e4e93ea779a821967360c781d7ac5115b3f1a171ced77065a0174", size = 567400, upload-time = "2025-06-13T14:08:46.855Z" }, - { url = "https://files.pythonhosted.org/packages/63/87/ec956cbe98809270b59a22891d5758edae147a258e658bf3024a8254c855/pyzmq-27.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6ad0562d4e6abb785be3e4dd68599c41be821b521da38c402bc9ab2a8e7ebc7e", size = 747031, upload-time = "2025-06-13T14:08:48.419Z" }, - { url = "https://files.pythonhosted.org/packages/be/8a/4a3764a68abc02e2fbb0668d225b6fda5cd39586dd099cee8b2ed6ab0452/pyzmq-27.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:9df43a2459cd3a3563404c1456b2c4c69564daa7dbaf15724c09821a3329ce46", size = 544726, upload-time = "2025-06-13T14:08:49.903Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/30/5f/557d2032a2f471edbcc227da724c24a1c05887b5cda1e3ae53af98b9e0a5/pyzmq-27.0.1.tar.gz", hash = "sha256:45c549204bc20e7484ffd2555f6cf02e572440ecf2f3bdd60d4404b20fddf64b", size = 281158, upload-time = "2025-08-03T05:05:40.352Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/0b/ccf4d0b152a6a11f0fc01e73978202fe0e8fe0e91e20941598e83a170bee/pyzmq-27.0.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:90a4da42aa322de8a3522461e3b5fe999935763b27f69a02fced40f4e3cf9682", size = 1329293, upload-time = "2025-08-03T05:02:56.001Z" }, + { url = "https://files.pythonhosted.org/packages/bc/76/48706d291951b1300d3cf985e503806901164bf1581f27c4b6b22dbab2fa/pyzmq-27.0.1-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e648dca28178fc879c814cf285048dd22fd1f03e1104101106505ec0eea50a4d", size = 905953, upload-time = "2025-08-03T05:02:59.061Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8a/df3135b96712068d184c53120c7dbf3023e5e362a113059a4f85cd36c6a0/pyzmq-27.0.1-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bca8abc31799a6f3652d13f47e0b0e1cab76f9125f2283d085a3754f669b607", size = 666165, upload-time = "2025-08-03T05:03:00.789Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ed/341a7148e08d2830f480f53ab3d136d88fc5011bb367b516d95d0ebb46dd/pyzmq-27.0.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:092f4011b26d6b0201002f439bd74b38f23f3aefcb358621bdc3b230afc9b2d5", size = 853756, upload-time = "2025-08-03T05:03:03.347Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bc/d26fe010477c3e901f0f5a3e70446950dde9aa217f1d1a13534eb0fccfe5/pyzmq-27.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6f02f30a4a6b3efe665ab13a3dd47109d80326c8fd286311d1ba9f397dc5f247", size = 1654870, upload-time = "2025-08-03T05:03:05.331Z" }, + { url = "https://files.pythonhosted.org/packages/32/21/9b488086bf3f55b2eb26db09007a3962f62f3b81c5c6295a6ff6aaebd69c/pyzmq-27.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f293a1419266e3bf3557d1f8778f9e1ffe7e6b2c8df5c9dca191caf60831eb74", size = 2033444, upload-time = "2025-08-03T05:03:07.318Z" }, + { url = "https://files.pythonhosted.org/packages/3d/53/85b64a792223cd43393d25e03c8609df41aac817ea5ce6a27eceeed433ee/pyzmq-27.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ce181dd1a7c6c012d0efa8ab603c34b5ee9d86e570c03415bbb1b8772eeb381c", size = 1891289, upload-time = "2025-08-03T05:03:08.96Z" }, + { url = "https://files.pythonhosted.org/packages/23/5b/078aae8fe1c4cdba1a77a598870c548fd52b4d4a11e86b8116bbef47d9f3/pyzmq-27.0.1-cp310-cp310-win32.whl", hash = "sha256:f65741cc06630652e82aa68ddef4986a3ab9073dd46d59f94ce5f005fa72037c", size = 566693, upload-time = "2025-08-03T05:03:10.711Z" }, + { url = "https://files.pythonhosted.org/packages/24/e1/4471fff36416ebf1ffe43577b9c7dcf2ff4798f2171f0d169640a48d2305/pyzmq-27.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:44909aa3ed2234d69fe81e1dade7be336bcfeab106e16bdaa3318dcde4262b93", size = 631649, upload-time = "2025-08-03T05:03:12.232Z" }, + { url = "https://files.pythonhosted.org/packages/e8/4c/8edac8dd56f223124aa40403d2c097bbad9b0e2868a67cad9a2a029863aa/pyzmq-27.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:4401649bfa0a38f0f8777f8faba7cd7eb7b5b8ae2abc7542b830dd09ad4aed0d", size = 559274, upload-time = "2025-08-03T05:03:13.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/18/a8e0da6ababbe9326116fb1c890bf1920eea880e8da621afb6bc0f39a262/pyzmq-27.0.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:9729190bd770314f5fbba42476abf6abe79a746eeda11d1d68fd56dd70e5c296", size = 1332721, upload-time = "2025-08-03T05:03:15.237Z" }, + { url = "https://files.pythonhosted.org/packages/75/a4/9431ba598651d60ebd50dc25755402b770322cf8432adcc07d2906e53a54/pyzmq-27.0.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:696900ef6bc20bef6a242973943574f96c3f97d2183c1bd3da5eea4f559631b1", size = 908249, upload-time = "2025-08-03T05:03:16.933Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/e624e1793689e4e685d2ee21c40277dd4024d9d730af20446d88f69be838/pyzmq-27.0.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f96a63aecec22d3f7fdea3c6c98df9e42973f5856bb6812c3d8d78c262fee808", size = 668649, upload-time = "2025-08-03T05:03:18.49Z" }, + { url = "https://files.pythonhosted.org/packages/6c/29/0652a39d4e876e0d61379047ecf7752685414ad2e253434348246f7a2a39/pyzmq-27.0.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c512824360ea7490390566ce00bee880e19b526b312b25cc0bc30a0fe95cb67f", size = 856601, upload-time = "2025-08-03T05:03:20.194Z" }, + { url = "https://files.pythonhosted.org/packages/36/2d/8d5355d7fc55bb6e9c581dd74f58b64fa78c994079e3a0ea09b1b5627cde/pyzmq-27.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dfb2bb5e0f7198eaacfb6796fb0330afd28f36d985a770745fba554a5903595a", size = 1657750, upload-time = "2025-08-03T05:03:22.055Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f4/cd032352d5d252dc6f5ee272a34b59718ba3af1639a8a4ef4654f9535cf5/pyzmq-27.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4f6886c59ba93ffde09b957d3e857e7950c8fe818bd5494d9b4287bc6d5bc7f1", size = 2034312, upload-time = "2025-08-03T05:03:23.578Z" }, + { url = "https://files.pythonhosted.org/packages/e4/1a/c050d8b6597200e97a4bd29b93c769d002fa0b03083858227e0376ad59bc/pyzmq-27.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b99ea9d330e86ce1ff7f2456b33f1bf81c43862a5590faf4ef4ed3a63504bdab", size = 1893632, upload-time = "2025-08-03T05:03:25.167Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/173ce21d5097e7fcf284a090e8beb64fc683c6582b1f00fa52b1b7e867ce/pyzmq-27.0.1-cp311-cp311-win32.whl", hash = "sha256:571f762aed89025ba8cdcbe355fea56889715ec06d0264fd8b6a3f3fa38154ed", size = 566587, upload-time = "2025-08-03T05:03:26.769Z" }, + { url = "https://files.pythonhosted.org/packages/53/ab/22bd33e7086f0a2cc03a5adabff4bde414288bb62a21a7820951ef86ec20/pyzmq-27.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee16906c8025fa464bea1e48128c048d02359fb40bebe5333103228528506530", size = 632873, upload-time = "2025-08-03T05:03:28.685Z" }, + { url = "https://files.pythonhosted.org/packages/90/14/3e59b4a28194285ceeff725eba9aa5ba8568d1cb78aed381dec1537c705a/pyzmq-27.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:ba068f28028849da725ff9185c24f832ccf9207a40f9b28ac46ab7c04994bd41", size = 558918, upload-time = "2025-08-03T05:03:30.085Z" }, + { url = "https://files.pythonhosted.org/packages/0e/9b/c0957041067c7724b310f22c398be46399297c12ed834c3bc42200a2756f/pyzmq-27.0.1-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:af7ebce2a1e7caf30c0bb64a845f63a69e76a2fadbc1cac47178f7bb6e657bdd", size = 1305432, upload-time = "2025-08-03T05:03:32.177Z" }, + { url = "https://files.pythonhosted.org/packages/8e/55/bd3a312790858f16b7def3897a0c3eb1804e974711bf7b9dcb5f47e7f82c/pyzmq-27.0.1-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:8f617f60a8b609a13099b313e7e525e67f84ef4524b6acad396d9ff153f6e4cd", size = 895095, upload-time = "2025-08-03T05:03:33.918Z" }, + { url = "https://files.pythonhosted.org/packages/20/50/fc384631d8282809fb1029a4460d2fe90fa0370a0e866a8318ed75c8d3bb/pyzmq-27.0.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d59dad4173dc2a111f03e59315c7bd6e73da1a9d20a84a25cf08325b0582b1a", size = 651826, upload-time = "2025-08-03T05:03:35.818Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0a/2356305c423a975000867de56888b79e44ec2192c690ff93c3109fd78081/pyzmq-27.0.1-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f5b6133c8d313bde8bd0d123c169d22525300ff164c2189f849de495e1344577", size = 839751, upload-time = "2025-08-03T05:03:37.265Z" }, + { url = "https://files.pythonhosted.org/packages/d7/1b/81e95ad256ca7e7ccd47f5294c1c6da6e2b64fbace65b84fe8a41470342e/pyzmq-27.0.1-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:58cca552567423f04d06a075f4b473e78ab5bdb906febe56bf4797633f54aa4e", size = 1641359, upload-time = "2025-08-03T05:03:38.799Z" }, + { url = "https://files.pythonhosted.org/packages/50/63/9f50ec965285f4e92c265c8f18344e46b12803666d8b73b65d254d441435/pyzmq-27.0.1-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:4b9d8e26fb600d0d69cc9933e20af08552e97cc868a183d38a5c0d661e40dfbb", size = 2020281, upload-time = "2025-08-03T05:03:40.338Z" }, + { url = "https://files.pythonhosted.org/packages/02/4a/19e3398d0dc66ad2b463e4afa1fc541d697d7bc090305f9dfb948d3dfa29/pyzmq-27.0.1-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2329f0c87f0466dce45bba32b63f47018dda5ca40a0085cc5c8558fea7d9fc55", size = 1877112, upload-time = "2025-08-03T05:03:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/bf/42/c562e9151aa90ed1d70aac381ea22a929d6b3a2ce4e1d6e2e135d34fd9c6/pyzmq-27.0.1-cp312-abi3-win32.whl", hash = "sha256:57bb92abdb48467b89c2d21da1ab01a07d0745e536d62afd2e30d5acbd0092eb", size = 558177, upload-time = "2025-08-03T05:03:43.979Z" }, + { url = "https://files.pythonhosted.org/packages/40/96/5c50a7d2d2b05b19994bf7336b97db254299353dd9b49b565bb71b485f03/pyzmq-27.0.1-cp312-abi3-win_amd64.whl", hash = "sha256:ff3f8757570e45da7a5bedaa140489846510014f7a9d5ee9301c61f3f1b8a686", size = 618923, upload-time = "2025-08-03T05:03:45.438Z" }, + { url = "https://files.pythonhosted.org/packages/13/33/1ec89c8f21c89d21a2eaff7def3676e21d8248d2675705e72554fb5a6f3f/pyzmq-27.0.1-cp312-abi3-win_arm64.whl", hash = "sha256:df2c55c958d3766bdb3e9d858b911288acec09a9aab15883f384fc7180df5bed", size = 552358, upload-time = "2025-08-03T05:03:46.887Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a0/f26e276211ec8090a4d11e4ec70eb8a8b15781e591c1d44ce62f372963a0/pyzmq-27.0.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:497bd8af534ae55dc4ef67eebd1c149ff2a0b0f1e146db73c8b5a53d83c1a5f5", size = 1122287, upload-time = "2025-08-03T05:03:48.838Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d8/af4b507e4f7eeea478cc8ee873995a6fd55582bfb99140593ed460e1db3c/pyzmq-27.0.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:a066ea6ad6218b4c233906adf0ae67830f451ed238419c0db609310dd781fbe7", size = 1155756, upload-time = "2025-08-03T05:03:50.907Z" }, + { url = "https://files.pythonhosted.org/packages/ac/55/37fae0013e11f88681da42698e550b08a316d608242551f65095cc99232a/pyzmq-27.0.1-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:72d235d6365ca73d8ce92f7425065d70f5c1e19baa458eb3f0d570e425b73a96", size = 1340826, upload-time = "2025-08-03T05:03:52.568Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e4/3a87854c64b26fcf63a9d1b6f4382bd727d4797c772ceb334a97b7489be9/pyzmq-27.0.1-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:313a7b374e3dc64848644ca348a51004b41726f768b02e17e689f1322366a4d9", size = 897283, upload-time = "2025-08-03T05:03:54.167Z" }, + { url = "https://files.pythonhosted.org/packages/17/3e/4296c6b0ad2d07be11ae1395dccf9cae48a0a655cf9be1c3733ad2b591d1/pyzmq-27.0.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:119ce8590409702394f959c159d048002cbed2f3c0645ec9d6a88087fc70f0f1", size = 660565, upload-time = "2025-08-03T05:03:56.152Z" }, + { url = "https://files.pythonhosted.org/packages/72/41/a33ba3aa48b45b23c4cd4ac49aafde46f3e0f81939f2bfb3b6171a437122/pyzmq-27.0.1-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45c3e00ce16896ace2cd770ab9057a7cf97d4613ea5f2a13f815141d8b6894b9", size = 847680, upload-time = "2025-08-03T05:03:57.696Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8c/bf2350bb25b3b58d2e5b5d2290ffab0e923f0cc6d02288d3fbf4baa6e4d1/pyzmq-27.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:678e50ec112bdc6df5a83ac259a55a4ba97a8b314c325ab26b3b5b071151bc61", size = 1650151, upload-time = "2025-08-03T05:03:59.387Z" }, + { url = "https://files.pythonhosted.org/packages/f7/1a/a5a07c54890891344a8ddc3d5ab320dd3c4e39febb6e4472546e456d5157/pyzmq-27.0.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d0b96c30be9f9387b18b18b6133c75a7b1b0065da64e150fe1feb5ebf31ece1c", size = 2023766, upload-time = "2025-08-03T05:04:01.883Z" }, + { url = "https://files.pythonhosted.org/packages/62/5e/514dcff08f02c6c8a45a6e23621901139cf853be7ac5ccd0b9407c3aa3de/pyzmq-27.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88dc92d9eb5ea4968123e74db146d770b0c8d48f0e2bfb1dbc6c50a8edb12d64", size = 1885195, upload-time = "2025-08-03T05:04:03.923Z" }, + { url = "https://files.pythonhosted.org/packages/c8/91/87f74f98a487fbef0b115f6025e4a295129fd56b2b633a03ba7d5816ecc2/pyzmq-27.0.1-cp313-cp313t-win32.whl", hash = "sha256:6dcbcb34f5c9b0cefdfc71ff745459241b7d3cda5b27c7ad69d45afc0821d1e1", size = 574213, upload-time = "2025-08-03T05:04:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/e6/d7/07f7d0d7f4c81e08be7b60e52ff2591c557377c017f96204d33d5fca1b07/pyzmq-27.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9fd0fda730461f510cfd9a40fafa5355d65f5e3dbdd8d6dfa342b5b3f5d1949", size = 640202, upload-time = "2025-08-03T05:04:07.439Z" }, + { url = "https://files.pythonhosted.org/packages/ab/83/21d66bcef6fb803647a223cbde95111b099e2176277c0cbc8b099c485510/pyzmq-27.0.1-cp313-cp313t-win_arm64.whl", hash = "sha256:56a3b1853f3954ec1f0e91085f1350cc57d18f11205e4ab6e83e4b7c414120e0", size = 561514, upload-time = "2025-08-03T05:04:09.071Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0b/d5ea75cf46b52cdce85a85200c963cb498932953df443892238be49b1a01/pyzmq-27.0.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f98f6b7787bd2beb1f0dde03f23a0621a0c978edf673b7d8f5e7bc039cbe1b60", size = 1340836, upload-time = "2025-08-03T05:04:10.774Z" }, + { url = "https://files.pythonhosted.org/packages/be/4c/0dbce882550e17db6846b29e9dc242aea7590e7594e1ca5043e8e58fff2d/pyzmq-27.0.1-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:351bf5d8ca0788ca85327fda45843b6927593ff4c807faee368cc5aaf9f809c2", size = 897236, upload-time = "2025-08-03T05:04:13.221Z" }, + { url = "https://files.pythonhosted.org/packages/1b/22/461e131cf16b8814f3c356fa1ea0912697dbc4c64cddf01f7756ec704c1e/pyzmq-27.0.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5268a5a9177afff53dc6d70dffe63114ba2a6e7b20d9411cc3adeba09eeda403", size = 660374, upload-time = "2025-08-03T05:04:15.032Z" }, + { url = "https://files.pythonhosted.org/packages/3f/0c/bbd65a814395bf4fc3e57c6c13af27601c07e4009bdfb75ebcf500537bbd/pyzmq-27.0.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4aca06ba295aa78bec9b33ec028d1ca08744c36294338c41432b7171060c808", size = 847497, upload-time = "2025-08-03T05:04:16.967Z" }, + { url = "https://files.pythonhosted.org/packages/1e/df/3d1f4a03b561d824cbd491394f67591957e2f1acf6dc85d96f970312a76a/pyzmq-27.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1c363c6dc66352331d5ad64bb838765c6692766334a6a02fdb05e76bd408ae18", size = 1650028, upload-time = "2025-08-03T05:04:19.398Z" }, + { url = "https://files.pythonhosted.org/packages/41/c9/a3987540f59a412bdaae3f362f78e00e6769557a598c63b7e32956aade5a/pyzmq-27.0.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:87aebf4acd7249bdff8d3df03aed4f09e67078e6762cfe0aecf8d0748ff94cde", size = 2023808, upload-time = "2025-08-03T05:04:21.145Z" }, + { url = "https://files.pythonhosted.org/packages/b0/a5/c388f4cd80498a8eaef7535f2a8eaca0a35b82b87a0b47fa1856fc135004/pyzmq-27.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e4f22d67756518d71901edf73b38dc0eb4765cce22c8fe122cc81748d425262b", size = 1884970, upload-time = "2025-08-03T05:04:22.908Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ac/b2a89a1ed90526a1b9a260cdc5cd42f055fd44ee8d2a59902b5ac35ddeb1/pyzmq-27.0.1-cp314-cp314t-win32.whl", hash = "sha256:8c62297bc7aea2147b472ca5ca2b4389377ad82898c87cabab2a94aedd75e337", size = 586905, upload-time = "2025-08-03T05:04:24.492Z" }, + { url = "https://files.pythonhosted.org/packages/68/62/7aa5ea04e836f7a788b2a67405f83011cef59ca76d7bac91d1fc9a0476da/pyzmq-27.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:bee5248d5ec9223545f8cc4f368c2d571477ae828c99409125c3911511d98245", size = 660503, upload-time = "2025-08-03T05:04:26.382Z" }, + { url = "https://files.pythonhosted.org/packages/89/32/3836ed85947b06f1d67c07ce16c00b0cf8c053ab0b249d234f9f81ff95ff/pyzmq-27.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0fc24bf45e4a454e55ef99d7f5c8b8712539200ce98533af25a5bfa954b6b390", size = 575098, upload-time = "2025-08-03T05:04:27.974Z" }, + { url = "https://files.pythonhosted.org/packages/6f/87/fc96f224dd99070fe55d0afc37ac08d7d4635d434e3f9425b232867e01b9/pyzmq-27.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:544b995a6a1976fad5d7ff01409b4588f7608ccc41be72147700af91fd44875d", size = 835950, upload-time = "2025-08-03T05:05:04.193Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b6/802d96017f176c3a7285603d9ed2982550095c136c6230d3e0b53f52c7e5/pyzmq-27.0.1-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0f772eea55cccce7f45d6ecdd1d5049c12a77ec22404f6b892fae687faa87bee", size = 799876, upload-time = "2025-08-03T05:05:06.263Z" }, + { url = "https://files.pythonhosted.org/packages/4e/52/49045c6528007cce385f218f3a674dc84fc8b3265330d09e57c0a59b41f4/pyzmq-27.0.1-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9d63d66059114a6756d09169c9209ffceabacb65b9cb0f66e6fc344b20b73e6", size = 567402, upload-time = "2025-08-03T05:05:08.028Z" }, + { url = "https://files.pythonhosted.org/packages/bc/fe/c29ac0d5a817543ecf0cb18f17195805bad0da567a1c64644aacf11b2779/pyzmq-27.0.1-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1da8e645c655d86f0305fb4c65a0d848f461cd90ee07d21f254667287b5dbe50", size = 747030, upload-time = "2025-08-03T05:05:10.116Z" }, + { url = "https://files.pythonhosted.org/packages/17/d1/cc1fbfb65b4042016e4e035b2548cdfe0945c817345df83aa2d98490e7fc/pyzmq-27.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1843fd0daebcf843fe6d4da53b8bdd3fc906ad3e97d25f51c3fed44436d82a49", size = 544567, upload-time = "2025-08-03T05:05:11.856Z" }, + { url = "https://files.pythonhosted.org/packages/b4/1a/49f66fe0bc2b2568dd4280f1f520ac8fafd73f8d762140e278d48aeaf7b9/pyzmq-27.0.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7fb0ee35845bef1e8c4a152d766242164e138c239e3182f558ae15cb4a891f94", size = 835949, upload-time = "2025-08-03T05:05:13.798Z" }, + { url = "https://files.pythonhosted.org/packages/49/94/443c1984b397eab59b14dd7ae8bc2ac7e8f32dbc646474453afcaa6508c4/pyzmq-27.0.1-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f379f11e138dfd56c3f24a04164f871a08281194dd9ddf656a278d7d080c8ad0", size = 799875, upload-time = "2025-08-03T05:05:15.632Z" }, + { url = "https://files.pythonhosted.org/packages/30/f1/fd96138a0f152786a2ba517e9c6a8b1b3516719e412a90bb5d8eea6b660c/pyzmq-27.0.1-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b978c0678cffbe8860ec9edc91200e895c29ae1ac8a7085f947f8e8864c489fb", size = 567403, upload-time = "2025-08-03T05:05:17.326Z" }, + { url = "https://files.pythonhosted.org/packages/16/57/34e53ef2b55b1428dac5aabe3a974a16c8bda3bf20549ba500e3ff6cb426/pyzmq-27.0.1-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ebccf0d760bc92a4a7c751aeb2fef6626144aace76ee8f5a63abeb100cae87f", size = 747032, upload-time = "2025-08-03T05:05:19.074Z" }, + { url = "https://files.pythonhosted.org/packages/81/b7/769598c5ae336fdb657946950465569cf18803140fe89ce466d7f0a57c11/pyzmq-27.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:77fed80e30fa65708546c4119840a46691290efc231f6bfb2ac2a39b52e15811", size = 544566, upload-time = "2025-08-03T05:05:20.798Z" }, ] [[package]] name = "qiskit" -version = "1.4.3" +version = "2.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dill" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin')" }, { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13' and 'x86_64' not in platform_machine) or (python_full_version >= '3.11' and sys_platform != 'darwin') or (python_full_version >= '3.13' and sys_platform == 'darwin')" }, - { name = "python-dateutil" }, { name = "rustworkx" }, { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "scipy", version = "1.16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "stevedore" }, - { name = "symengine" }, - { name = "sympy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/ac/25993f01e39cf04c9dccfd83f9b0817b5b8b1766f4afb8c34e6fba85155f/qiskit-1.4.3.tar.gz", hash = "sha256:e4e311190c159578f253d219dc860241ccb71955ca08725bb092c74cf7fbdac5", size = 3939859, upload-time = "2025-05-12T15:28:05.09Z" } +sdist = { url = "https://files.pythonhosted.org/packages/39/97/903041c27db9105f5036a579b2b7ee2a0caa21c6061fa29e0ccbd35aa19a/qiskit-2.1.1.tar.gz", hash = "sha256:148f62d314cd138a1f4da305c6293b19c73115323e77123b13a048cdcbc1fcdd", size = 3600302, upload-time = "2025-07-10T21:30:11.665Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/66/d5/126a6ddc7a9138b436cfee3772c5b082ea8f8216feed729f02c5bda32d66/qiskit-1.4.3-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:65caf291bbe93f136b85281cb4a807c138fc4315fc17bdda9382cb4bfa18abdf", size = 6570998, upload-time = "2025-05-12T15:27:51.659Z" }, - { url = "https://files.pythonhosted.org/packages/67/95/c4bf1fe91c456570c9536e5505ccd878f358f4b8e6b06c8fd8cbc09093a0/qiskit-1.4.3-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:6e9f41eca2f4d522f59e48a47eb8b81ed3fdd35f56cefb41cc0db47768ccde47", size = 6251292, upload-time = "2025-05-12T15:27:53.929Z" }, - { url = "https://files.pythonhosted.org/packages/23/70/713fae76998831e9cb458e3605a9429915b9f9fbb92244c8699903868be2/qiskit-1.4.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a04c6baca379bae8e6fc626bd06f0dda3f2ee7961205839b5a431b7f8d1e3978", size = 6396599, upload-time = "2025-05-12T18:02:22.029Z" }, - { url = "https://files.pythonhosted.org/packages/d6/93/6cadc22a5184e4e868c28dd6a83beacf6e4641088f106e3cd97e0cd160a8/qiskit-1.4.3-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ca6eeaf29ae2cf3789e7474b062845c59c6e4164276569949f9170cfcaec6b1", size = 6730964, upload-time = "2025-05-12T15:27:55.962Z" }, - { url = "https://files.pythonhosted.org/packages/ef/9c/53a7fd3a18ec3526e56fa68d9af1a164ba4cea2302a03be7b621678b1694/qiskit-1.4.3-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc82934e5b5192a75adca57bfa8194166e6bce1023da3b82e02db6c279a5d0fc", size = 6801019, upload-time = "2025-05-12T18:02:24.857Z" }, - { url = "https://files.pythonhosted.org/packages/40/a2/1355e3a8a00f9310ea80ab5887095bd3122918c8a61d62895e6519e442c0/qiskit-1.4.3-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:03355819fb9747a937bfffb0aba59d44c4654536b87c62dcd00d4e4e1550190f", size = 7895620, upload-time = "2025-05-12T18:02:27.104Z" }, - { url = "https://files.pythonhosted.org/packages/85/35/a442b69b1507112f4fba392354d933f1ef65f66dc72bfce0c03be27f70c7/qiskit-1.4.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d90b25ad017839b0bf1b5d05d8bd4066b7f83e3c14839947994f5fd56a5f5e2", size = 6762841, upload-time = "2025-05-12T15:27:58.379Z" }, - { url = "https://files.pythonhosted.org/packages/f1/f8/c9d116275bec7d0942f364ed6461b384e2ccd1c6bb5df8fe5d998d24ae41/qiskit-1.4.3-cp39-abi3-win32.whl", hash = "sha256:74cb30e34189ca9c1263994705a0cf8d97cead5f7375dedab3be4e783173785d", size = 6138538, upload-time = "2025-05-12T15:28:00.754Z" }, - { url = "https://files.pythonhosted.org/packages/ae/a7/7f736138e67f3f89e4a8b42bb480a9dcd582da9a87f4d723a3c4221766b0/qiskit-1.4.3-cp39-abi3-win_amd64.whl", hash = "sha256:554b5ad8c6198881923998fd0124660eec534c985c731d1e3c09c6421377c29e", size = 6517946, upload-time = "2025-05-12T15:28:03.189Z" }, + { url = "https://files.pythonhosted.org/packages/76/3a/26a99184f830be25a14ef7cbc40f19b98721ac018aa0c4db18c5dedc774c/qiskit-2.1.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:00f8fe1980a80e8e79e01352f2502cd3ca4fe89d07872bc9a244ba9cba304a7d", size = 7283884, upload-time = "2025-07-10T21:30:02.576Z" }, + { url = "https://files.pythonhosted.org/packages/60/a1/2a63ab52a5166346599df7bec2e41fd2be9876ccf01637c73603beebdcb5/qiskit-2.1.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:e00985d1b348bf8cc60b686a64a150af2043538e81a61b48400ff71064588fdd", size = 6817779, upload-time = "2025-07-10T21:30:04.513Z" }, + { url = "https://files.pythonhosted.org/packages/ba/87/1a184f6dcd614f57362d9b0be1c718ff857c1a3d69ca0f426c84adda6755/qiskit-2.1.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10f546148c86fd15cdbc68270e1d3937a021bd718779dff95f912c8b7cfad7bd", size = 7437467, upload-time = "2025-07-10T21:30:06.524Z" }, + { url = "https://files.pythonhosted.org/packages/82/d7/d0fc92d20b266692db8aedc5e259a2d4cff76613ec155178f855144c7a71/qiskit-2.1.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3765cef67ccc8b3d54b86ede2266c282039da045be1b10e64660a4bbf815680", size = 7696275, upload-time = "2025-07-11T13:15:14.672Z" }, + { url = "https://files.pythonhosted.org/packages/ba/30/ba384695bc9c9cb5795ef3ab47f33eb579460dc928115918d4eb0046ae9f/qiskit-2.1.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca95a1ab710eb3e0fc65dd876734b5c6fc6df1bb78249f86913d77ed1984983f", size = 7411176, upload-time = "2025-07-11T13:15:16.62Z" }, + { url = "https://files.pythonhosted.org/packages/75/dc/551c6afe0aa087ebe53988173290904ded5bf93e56b22503d8311d421d82/qiskit-2.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b41c66958c2e0550bc1047524cd754dce2277bea0a1db1085c052cc5bb07ec6", size = 7458353, upload-time = "2025-07-10T21:30:08.223Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/988791f56e6ab2999e1a4c7e60831b0f02c3e29a467400bced501ce6b637/qiskit-2.1.1-cp39-abi3-win_amd64.whl", hash = "sha256:e7225798221ecf0c8a5cf2e96c37d6d9d00674a412e8c397542fe387909b80db", size = 7234838, upload-time = "2025-07-10T21:30:10.121Z" }, ] [package.optional-dependencies] @@ -3073,8 +3115,10 @@ name = "sb3-contrib" version = "2.7.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13'", - "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", + "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')", ] @@ -3192,9 +3236,11 @@ name = "scipy" version = "1.16.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13'", + "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", ] @@ -3347,9 +3393,11 @@ name = "sphinx" version = "8.2.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13'", + "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", ] @@ -3530,47 +3578,47 @@ wheels = [ [[package]] name = "sqlalchemy" -version = "2.0.41" +version = "2.0.42" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/66/45b165c595ec89aa7dcc2c1cd222ab269bc753f1fc7a1e68f8481bd957bf/sqlalchemy-2.0.41.tar.gz", hash = "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9", size = 9689424, upload-time = "2025-05-14T17:10:32.339Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/12/d7c445b1940276a828efce7331cb0cb09d6e5f049651db22f4ebb0922b77/sqlalchemy-2.0.41-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b1f09b6821406ea1f94053f346f28f8215e293344209129a9c0fcc3578598d7b", size = 2117967, upload-time = "2025-05-14T17:48:15.841Z" }, - { url = "https://files.pythonhosted.org/packages/6f/b8/cb90f23157e28946b27eb01ef401af80a1fab7553762e87df51507eaed61/sqlalchemy-2.0.41-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1936af879e3db023601196a1684d28e12f19ccf93af01bf3280a3262c4b6b4e5", size = 2107583, upload-time = "2025-05-14T17:48:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c2/eef84283a1c8164a207d898e063edf193d36a24fb6a5bb3ce0634b92a1e8/sqlalchemy-2.0.41-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2ac41acfc8d965fb0c464eb8f44995770239668956dc4cdf502d1b1ffe0d747", size = 3186025, upload-time = "2025-05-14T17:51:51.226Z" }, - { url = "https://files.pythonhosted.org/packages/bd/72/49d52bd3c5e63a1d458fd6d289a1523a8015adedbddf2c07408ff556e772/sqlalchemy-2.0.41-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81c24e0c0fde47a9723c81d5806569cddef103aebbf79dbc9fcbb617153dea30", size = 3186259, upload-time = "2025-05-14T17:55:22.526Z" }, - { url = "https://files.pythonhosted.org/packages/4f/9e/e3ffc37d29a3679a50b6bbbba94b115f90e565a2b4545abb17924b94c52d/sqlalchemy-2.0.41-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23a8825495d8b195c4aa9ff1c430c28f2c821e8c5e2d98089228af887e5d7e29", size = 3126803, upload-time = "2025-05-14T17:51:53.277Z" }, - { url = "https://files.pythonhosted.org/packages/8a/76/56b21e363f6039978ae0b72690237b38383e4657281285a09456f313dd77/sqlalchemy-2.0.41-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:60c578c45c949f909a4026b7807044e7e564adf793537fc762b2489d522f3d11", size = 3148566, upload-time = "2025-05-14T17:55:24.398Z" }, - { url = "https://files.pythonhosted.org/packages/3b/92/11b8e1b69bf191bc69e300a99badbbb5f2f1102f2b08b39d9eee2e21f565/sqlalchemy-2.0.41-cp310-cp310-win32.whl", hash = "sha256:118c16cd3f1b00c76d69343e38602006c9cfb9998fa4f798606d28d63f23beda", size = 2086696, upload-time = "2025-05-14T17:55:59.136Z" }, - { url = "https://files.pythonhosted.org/packages/5c/88/2d706c9cc4502654860f4576cd54f7db70487b66c3b619ba98e0be1a4642/sqlalchemy-2.0.41-cp310-cp310-win_amd64.whl", hash = "sha256:7492967c3386df69f80cf67efd665c0f667cee67032090fe01d7d74b0e19bb08", size = 2110200, upload-time = "2025-05-14T17:56:00.757Z" }, - { url = "https://files.pythonhosted.org/packages/37/4e/b00e3ffae32b74b5180e15d2ab4040531ee1bef4c19755fe7926622dc958/sqlalchemy-2.0.41-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6375cd674fe82d7aa9816d1cb96ec592bac1726c11e0cafbf40eeee9a4516b5f", size = 2121232, upload-time = "2025-05-14T17:48:20.444Z" }, - { url = "https://files.pythonhosted.org/packages/ef/30/6547ebb10875302074a37e1970a5dce7985240665778cfdee2323709f749/sqlalchemy-2.0.41-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9f8c9fdd15a55d9465e590a402f42082705d66b05afc3ffd2d2eb3c6ba919560", size = 2110897, upload-time = "2025-05-14T17:48:21.634Z" }, - { url = "https://files.pythonhosted.org/packages/9e/21/59df2b41b0f6c62da55cd64798232d7349a9378befa7f1bb18cf1dfd510a/sqlalchemy-2.0.41-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f9dc8c44acdee06c8fc6440db9eae8b4af8b01e4b1aee7bdd7241c22edff4f", size = 3273313, upload-time = "2025-05-14T17:51:56.205Z" }, - { url = "https://files.pythonhosted.org/packages/62/e4/b9a7a0e5c6f79d49bcd6efb6e90d7536dc604dab64582a9dec220dab54b6/sqlalchemy-2.0.41-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c11ceb9a1f482c752a71f203a81858625d8df5746d787a4786bca4ffdf71c6", size = 3273807, upload-time = "2025-05-14T17:55:26.928Z" }, - { url = "https://files.pythonhosted.org/packages/39/d8/79f2427251b44ddee18676c04eab038d043cff0e764d2d8bb08261d6135d/sqlalchemy-2.0.41-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:911cc493ebd60de5f285bcae0491a60b4f2a9f0f5c270edd1c4dbaef7a38fc04", size = 3209632, upload-time = "2025-05-14T17:51:59.384Z" }, - { url = "https://files.pythonhosted.org/packages/d4/16/730a82dda30765f63e0454918c982fb7193f6b398b31d63c7c3bd3652ae5/sqlalchemy-2.0.41-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03968a349db483936c249f4d9cd14ff2c296adfa1290b660ba6516f973139582", size = 3233642, upload-time = "2025-05-14T17:55:29.901Z" }, - { url = "https://files.pythonhosted.org/packages/04/61/c0d4607f7799efa8b8ea3c49b4621e861c8f5c41fd4b5b636c534fcb7d73/sqlalchemy-2.0.41-cp311-cp311-win32.whl", hash = "sha256:293cd444d82b18da48c9f71cd7005844dbbd06ca19be1ccf6779154439eec0b8", size = 2086475, upload-time = "2025-05-14T17:56:02.095Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8e/8344f8ae1cb6a479d0741c02cd4f666925b2bf02e2468ddaf5ce44111f30/sqlalchemy-2.0.41-cp311-cp311-win_amd64.whl", hash = "sha256:3d3549fc3e40667ec7199033a4e40a2f669898a00a7b18a931d3efb4c7900504", size = 2110903, upload-time = "2025-05-14T17:56:03.499Z" }, - { url = "https://files.pythonhosted.org/packages/3e/2a/f1f4e068b371154740dd10fb81afb5240d5af4aa0087b88d8b308b5429c2/sqlalchemy-2.0.41-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:81f413674d85cfd0dfcd6512e10e0f33c19c21860342a4890c3a2b59479929f9", size = 2119645, upload-time = "2025-05-14T17:55:24.854Z" }, - { url = "https://files.pythonhosted.org/packages/9b/e8/c664a7e73d36fbfc4730f8cf2bf930444ea87270f2825efbe17bf808b998/sqlalchemy-2.0.41-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:598d9ebc1e796431bbd068e41e4de4dc34312b7aa3292571bb3674a0cb415dd1", size = 2107399, upload-time = "2025-05-14T17:55:28.097Z" }, - { url = "https://files.pythonhosted.org/packages/5c/78/8a9cf6c5e7135540cb682128d091d6afa1b9e48bd049b0d691bf54114f70/sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a104c5694dfd2d864a6f91b0956eb5d5883234119cb40010115fd45a16da5e70", size = 3293269, upload-time = "2025-05-14T17:50:38.227Z" }, - { url = "https://files.pythonhosted.org/packages/3c/35/f74add3978c20de6323fb11cb5162702670cc7a9420033befb43d8d5b7a4/sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6145afea51ff0af7f2564a05fa95eb46f542919e6523729663a5d285ecb3cf5e", size = 3303364, upload-time = "2025-05-14T17:51:49.829Z" }, - { url = "https://files.pythonhosted.org/packages/6a/d4/c990f37f52c3f7748ebe98883e2a0f7d038108c2c5a82468d1ff3eec50b7/sqlalchemy-2.0.41-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b46fa6eae1cd1c20e6e6f44e19984d438b6b2d8616d21d783d150df714f44078", size = 3229072, upload-time = "2025-05-14T17:50:39.774Z" }, - { url = "https://files.pythonhosted.org/packages/15/69/cab11fecc7eb64bc561011be2bd03d065b762d87add52a4ca0aca2e12904/sqlalchemy-2.0.41-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41836fe661cc98abfae476e14ba1906220f92c4e528771a8a3ae6a151242d2ae", size = 3268074, upload-time = "2025-05-14T17:51:51.736Z" }, - { url = "https://files.pythonhosted.org/packages/5c/ca/0c19ec16858585d37767b167fc9602593f98998a68a798450558239fb04a/sqlalchemy-2.0.41-cp312-cp312-win32.whl", hash = "sha256:a8808d5cf866c781150d36a3c8eb3adccfa41a8105d031bf27e92c251e3969d6", size = 2084514, upload-time = "2025-05-14T17:55:49.915Z" }, - { url = "https://files.pythonhosted.org/packages/7f/23/4c2833d78ff3010a4e17f984c734f52b531a8c9060a50429c9d4b0211be6/sqlalchemy-2.0.41-cp312-cp312-win_amd64.whl", hash = "sha256:5b14e97886199c1f52c14629c11d90c11fbb09e9334fa7bb5f6d068d9ced0ce0", size = 2111557, upload-time = "2025-05-14T17:55:51.349Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ad/2e1c6d4f235a97eeef52d0200d8ddda16f6c4dd70ae5ad88c46963440480/sqlalchemy-2.0.41-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443", size = 2115491, upload-time = "2025-05-14T17:55:31.177Z" }, - { url = "https://files.pythonhosted.org/packages/cf/8d/be490e5db8400dacc89056f78a52d44b04fbf75e8439569d5b879623a53b/sqlalchemy-2.0.41-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc", size = 2102827, upload-time = "2025-05-14T17:55:34.921Z" }, - { url = "https://files.pythonhosted.org/packages/a0/72/c97ad430f0b0e78efaf2791342e13ffeafcbb3c06242f01a3bb8fe44f65d/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1", size = 3225224, upload-time = "2025-05-14T17:50:41.418Z" }, - { url = "https://files.pythonhosted.org/packages/5e/51/5ba9ea3246ea068630acf35a6ba0d181e99f1af1afd17e159eac7e8bc2b8/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a", size = 3230045, upload-time = "2025-05-14T17:51:54.722Z" }, - { url = "https://files.pythonhosted.org/packages/78/2f/8c14443b2acea700c62f9b4a8bad9e49fc1b65cfb260edead71fd38e9f19/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d", size = 3159357, upload-time = "2025-05-14T17:50:43.483Z" }, - { url = "https://files.pythonhosted.org/packages/fc/b2/43eacbf6ccc5276d76cea18cb7c3d73e294d6fb21f9ff8b4eef9b42bbfd5/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23", size = 3197511, upload-time = "2025-05-14T17:51:57.308Z" }, - { url = "https://files.pythonhosted.org/packages/fa/2e/677c17c5d6a004c3c45334ab1dbe7b7deb834430b282b8a0f75ae220c8eb/sqlalchemy-2.0.41-cp313-cp313-win32.whl", hash = "sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f", size = 2082420, upload-time = "2025-05-14T17:55:52.69Z" }, - { url = "https://files.pythonhosted.org/packages/e9/61/e8c1b9b6307c57157d328dd8b8348ddc4c47ffdf1279365a13b2b98b8049/sqlalchemy-2.0.41-cp313-cp313-win_amd64.whl", hash = "sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df", size = 2108329, upload-time = "2025-05-14T17:55:54.495Z" }, - { url = "https://files.pythonhosted.org/packages/1c/fc/9ba22f01b5cdacc8f5ed0d22304718d2c758fce3fd49a5372b886a86f37c/sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576", size = 1911224, upload-time = "2025-05-14T17:39:42.154Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/5a/03/a0af991e3a43174d6b83fca4fb399745abceddd1171bdabae48ce877ff47/sqlalchemy-2.0.42.tar.gz", hash = "sha256:160bedd8a5c28765bd5be4dec2d881e109e33b34922e50a3b881a7681773ac5f", size = 9749972, upload-time = "2025-07-29T12:48:09.323Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/12/33ff43214c2c6cc87499b402fe419869d2980a08101c991daae31345e901/sqlalchemy-2.0.42-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:172b244753e034d91a826f80a9a70f4cbac690641207f2217f8404c261473efe", size = 2130469, upload-time = "2025-07-29T13:25:15.215Z" }, + { url = "https://files.pythonhosted.org/packages/63/c4/4d2f2c21ddde9a2c7f7b258b202d6af0bac9fc5abfca5de367461c86d766/sqlalchemy-2.0.42-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be28f88abd74af8519a4542185ee80ca914933ca65cdfa99504d82af0e4210df", size = 2120393, upload-time = "2025-07-29T13:25:16.367Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0d/5ff2f2dfbac10e4a9ade1942f8985ffc4bd8f157926b1f8aed553dfe3b88/sqlalchemy-2.0.42-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98b344859d282fde388047f1710860bb23f4098f705491e06b8ab52a48aafea9", size = 3206173, upload-time = "2025-07-29T13:29:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/1f/59/71493fe74bd76a773ae8fa0c50bfc2ccac1cbf7cfa4f9843ad92897e6dcf/sqlalchemy-2.0.42-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97978d223b11f1d161390a96f28c49a13ce48fdd2fed7683167c39bdb1b8aa09", size = 3206910, upload-time = "2025-07-29T13:24:50.58Z" }, + { url = "https://files.pythonhosted.org/packages/a9/51/01b1d85bbb492a36b25df54a070a0f887052e9b190dff71263a09f48576b/sqlalchemy-2.0.42-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e35b9b000c59fcac2867ab3a79fc368a6caca8706741beab3b799d47005b3407", size = 3145479, upload-time = "2025-07-29T13:29:02.3Z" }, + { url = "https://files.pythonhosted.org/packages/fa/78/10834f010e2a3df689f6d1888ea6ea0074ff10184e6a550b8ed7f9189a89/sqlalchemy-2.0.42-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bc7347ad7a7b1c78b94177f2d57263113bb950e62c59b96ed839b131ea4234e1", size = 3169605, upload-time = "2025-07-29T13:24:52.135Z" }, + { url = "https://files.pythonhosted.org/packages/0c/75/e6fdd66d237582c8488dd1dfa90899f6502822fbd866363ab70e8ac4a2ce/sqlalchemy-2.0.42-cp310-cp310-win32.whl", hash = "sha256:739e58879b20a179156b63aa21f05ccacfd3e28e08e9c2b630ff55cd7177c4f1", size = 2098759, upload-time = "2025-07-29T13:23:55.809Z" }, + { url = "https://files.pythonhosted.org/packages/a5/a8/366db192641c2c2d1ea8977e7c77b65a0d16a7858907bb76ea68b9dd37af/sqlalchemy-2.0.42-cp310-cp310-win_amd64.whl", hash = "sha256:1aef304ada61b81f1955196f584b9e72b798ed525a7c0b46e09e98397393297b", size = 2122423, upload-time = "2025-07-29T13:23:56.968Z" }, + { url = "https://files.pythonhosted.org/packages/ea/3c/7bfd65f3c2046e2fb4475b21fa0b9d7995f8c08bfa0948df7a4d2d0de869/sqlalchemy-2.0.42-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c34100c0b7ea31fbc113c124bcf93a53094f8951c7bf39c45f39d327bad6d1e7", size = 2133779, upload-time = "2025-07-29T13:25:18.446Z" }, + { url = "https://files.pythonhosted.org/packages/66/17/19be542fe9dd64a766090e90e789e86bdaa608affda6b3c1e118a25a2509/sqlalchemy-2.0.42-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad59dbe4d1252448c19d171dfba14c74e7950b46dc49d015722a4a06bfdab2b0", size = 2123843, upload-time = "2025-07-29T13:25:19.749Z" }, + { url = "https://files.pythonhosted.org/packages/14/fc/83e45fc25f0acf1c26962ebff45b4c77e5570abb7c1a425a54b00bcfa9c7/sqlalchemy-2.0.42-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9187498c2149919753a7fd51766ea9c8eecdec7da47c1b955fa8090bc642eaa", size = 3294824, upload-time = "2025-07-29T13:29:03.879Z" }, + { url = "https://files.pythonhosted.org/packages/b9/81/421efc09837104cd1a267d68b470e5b7b6792c2963b8096ca1e060ba0975/sqlalchemy-2.0.42-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f092cf83ebcafba23a247f5e03f99f5436e3ef026d01c8213b5eca48ad6efa9", size = 3294662, upload-time = "2025-07-29T13:24:53.715Z" }, + { url = "https://files.pythonhosted.org/packages/2f/ba/55406e09d32ed5e5f9e8aaec5ef70c4f20b4ae25b9fa9784f4afaa28e7c3/sqlalchemy-2.0.42-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc6afee7e66fdba4f5a68610b487c1f754fccdc53894a9567785932dbb6a265e", size = 3229413, upload-time = "2025-07-29T13:29:05.638Z" }, + { url = "https://files.pythonhosted.org/packages/d4/c4/df596777fce27bde2d1a4a2f5a7ddea997c0c6d4b5246aafba966b421cc0/sqlalchemy-2.0.42-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:260ca1d2e5910f1f1ad3fe0113f8fab28657cee2542cb48c2f342ed90046e8ec", size = 3255563, upload-time = "2025-07-29T13:24:55.17Z" }, + { url = "https://files.pythonhosted.org/packages/16/ed/b9c4a939b314400f43f972c9eb0091da59d8466ef9c51d0fd5b449edc495/sqlalchemy-2.0.42-cp311-cp311-win32.whl", hash = "sha256:2eb539fd83185a85e5fcd6b19214e1c734ab0351d81505b0f987705ba0a1e231", size = 2098513, upload-time = "2025-07-29T13:23:58.946Z" }, + { url = "https://files.pythonhosted.org/packages/91/72/55b0c34e39feb81991aa3c974d85074c356239ac1170dfb81a474b4c23b3/sqlalchemy-2.0.42-cp311-cp311-win_amd64.whl", hash = "sha256:9193fa484bf00dcc1804aecbb4f528f1123c04bad6a08d7710c909750fa76aeb", size = 2123380, upload-time = "2025-07-29T13:24:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/ac31a9821fc70a7376321fb2c70fdd7eadbc06dadf66ee216a22a41d6058/sqlalchemy-2.0.42-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:09637a0872689d3eb71c41e249c6f422e3e18bbd05b4cd258193cfc7a9a50da2", size = 2132203, upload-time = "2025-07-29T13:29:19.291Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ba/fd943172e017f955d7a8b3a94695265b7114efe4854feaa01f057e8f5293/sqlalchemy-2.0.42-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3cb3ec67cc08bea54e06b569398ae21623534a7b1b23c258883a7c696ae10df", size = 2120373, upload-time = "2025-07-29T13:29:21.049Z" }, + { url = "https://files.pythonhosted.org/packages/ea/a2/b5f7d233d063ffadf7e9fff3898b42657ba154a5bec95a96f44cba7f818b/sqlalchemy-2.0.42-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e87e6a5ef6f9d8daeb2ce5918bf5fddecc11cae6a7d7a671fcc4616c47635e01", size = 3317685, upload-time = "2025-07-29T13:26:40.837Z" }, + { url = "https://files.pythonhosted.org/packages/86/00/fcd8daab13a9119d41f3e485a101c29f5d2085bda459154ba354c616bf4e/sqlalchemy-2.0.42-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b718011a9d66c0d2f78e1997755cd965f3414563b31867475e9bc6efdc2281d", size = 3326967, upload-time = "2025-07-29T13:22:31.009Z" }, + { url = "https://files.pythonhosted.org/packages/a3/85/e622a273d648d39d6771157961956991a6d760e323e273d15e9704c30ccc/sqlalchemy-2.0.42-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:16d9b544873fe6486dddbb859501a07d89f77c61d29060bb87d0faf7519b6a4d", size = 3255331, upload-time = "2025-07-29T13:26:42.579Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a0/2c2338b592c7b0a61feffd005378c084b4c01fabaf1ed5f655ab7bd446f0/sqlalchemy-2.0.42-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21bfdf57abf72fa89b97dd74d3187caa3172a78c125f2144764a73970810c4ee", size = 3291791, upload-time = "2025-07-29T13:22:32.454Z" }, + { url = "https://files.pythonhosted.org/packages/41/19/b8a2907972a78285fdce4c880ecaab3c5067eb726882ca6347f7a4bf64f6/sqlalchemy-2.0.42-cp312-cp312-win32.whl", hash = "sha256:78b46555b730a24901ceb4cb901c6b45c9407f8875209ed3c5d6bcd0390a6ed1", size = 2096180, upload-time = "2025-07-29T13:16:08.952Z" }, + { url = "https://files.pythonhosted.org/packages/48/1f/67a78f3dfd08a2ed1c7be820fe7775944f5126080b5027cc859084f8e223/sqlalchemy-2.0.42-cp312-cp312-win_amd64.whl", hash = "sha256:4c94447a016f36c4da80072e6c6964713b0af3c8019e9c4daadf21f61b81ab53", size = 2123533, upload-time = "2025-07-29T13:16:11.705Z" }, + { url = "https://files.pythonhosted.org/packages/e9/7e/25d8c28b86730c9fb0e09156f601d7a96d1c634043bf8ba36513eb78887b/sqlalchemy-2.0.42-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:941804f55c7d507334da38133268e3f6e5b0340d584ba0f277dd884197f4ae8c", size = 2127905, upload-time = "2025-07-29T13:29:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/e5/a1/9d8c93434d1d983880d976400fcb7895a79576bd94dca61c3b7b90b1ed0d/sqlalchemy-2.0.42-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d3d06a968a760ce2aa6a5889fefcbdd53ca935735e0768e1db046ec08cbf01", size = 2115726, upload-time = "2025-07-29T13:29:23.496Z" }, + { url = "https://files.pythonhosted.org/packages/a2/cc/d33646fcc24c87cc4e30a03556b611a4e7bcfa69a4c935bffb923e3c89f4/sqlalchemy-2.0.42-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cf10396a8a700a0f38ccd220d940be529c8f64435c5d5b29375acab9267a6c9", size = 3246007, upload-time = "2025-07-29T13:26:44.166Z" }, + { url = "https://files.pythonhosted.org/packages/67/08/4e6c533d4c7f5e7c4cbb6fe8a2c4e813202a40f05700d4009a44ec6e236d/sqlalchemy-2.0.42-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cae6c2b05326d7c2c7c0519f323f90e0fb9e8afa783c6a05bb9ee92a90d0f04", size = 3250919, upload-time = "2025-07-29T13:22:33.74Z" }, + { url = "https://files.pythonhosted.org/packages/5c/82/f680e9a636d217aece1b9a8030d18ad2b59b5e216e0c94e03ad86b344af3/sqlalchemy-2.0.42-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f50f7b20677b23cfb35b6afcd8372b2feb348a38e3033f6447ee0704540be894", size = 3180546, upload-time = "2025-07-29T13:26:45.648Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a2/8c8f6325f153894afa3775584c429cc936353fb1db26eddb60a549d0ff4b/sqlalchemy-2.0.42-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d88a1c0d66d24e229e3938e1ef16ebdbd2bf4ced93af6eff55225f7465cf350", size = 3216683, upload-time = "2025-07-29T13:22:34.977Z" }, + { url = "https://files.pythonhosted.org/packages/39/44/3a451d7fa4482a8ffdf364e803ddc2cfcafc1c4635fb366f169ecc2c3b11/sqlalchemy-2.0.42-cp313-cp313-win32.whl", hash = "sha256:45c842c94c9ad546c72225a0c0d1ae8ef3f7c212484be3d429715a062970e87f", size = 2093990, upload-time = "2025-07-29T13:16:13.036Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9e/9bce34f67aea0251c8ac104f7bdb2229d58fb2e86a4ad8807999c4bee34b/sqlalchemy-2.0.42-cp313-cp313-win_amd64.whl", hash = "sha256:eb9905f7f1e49fd57a7ed6269bc567fcbbdac9feadff20ad6bd7707266a91577", size = 2120473, upload-time = "2025-07-29T13:16:14.502Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/ba2546ab09a6adebc521bf3974440dc1d8c06ed342cceb30ed62a8858835/sqlalchemy-2.0.42-py3-none-any.whl", hash = "sha256:defcdff7e661f0043daa381832af65d616e060ddb54d3fe4476f51df7eaa1835", size = 1922072, upload-time = "2025-07-29T13:09:17.061Z" }, ] [[package]] @@ -3620,8 +3668,10 @@ name = "stable-baselines3" version = "2.7.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13'", - "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", + "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')", ] @@ -3667,39 +3717,39 @@ wheels = [ [[package]] name = "symengine" -version = "0.13.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/ff/a2041497a482a0ae585672fe12cc8983c7fc46ce792811b55a067e5e5516/symengine-0.13.0.tar.gz", hash = "sha256:ab83a08897ebf12579702c2b71ba73d4732fb706cc4291d810aedf39c690c14c", size = 114237, upload-time = "2024-09-30T22:06:25.16Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/ed/964f75a2dc5b0e2c97b732f44dfb9c40fe7c0f5e21a1ecc2edff89db3d81/symengine-0.13.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:259fd4111c7a70c72bdff5686de1949e8132baeb612eacdaf8837720c6fe449b", size = 25867912, upload-time = "2024-09-30T22:03:05.097Z" }, - { url = "https://files.pythonhosted.org/packages/a3/ce/c74dfbaf487a428984644b3cb5e1131c4808f60eab1456f37a107f20b87a/symengine-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:44f2eb28a1e36db0bbd6679435412f79da9743bf9c1cb3eff25e0c343b7ddd48", size = 22711016, upload-time = "2024-09-30T22:03:09.144Z" }, - { url = "https://files.pythonhosted.org/packages/4b/65/df699e2b4c2bbf608394b05d1095d77b3f02569e09960f7840f708c7c5dc/symengine-0.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d141712fa14d9138bd19e64b10392f850c68d88cd7db29f1bda33e32d1095559", size = 61000047, upload-time = "2024-09-30T22:03:13.613Z" }, - { url = "https://files.pythonhosted.org/packages/ff/a9/623d97da0da9615367484127dd3b5d1049675832fb9a6a3572f2e072bb86/symengine-0.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:830226d933bfcdb93546e4062541627d9a3bc7a178a63fb16c002eb5c5221938", size = 90017189, upload-time = "2024-09-30T22:03:20.545Z" }, - { url = "https://files.pythonhosted.org/packages/79/a0/d30329ef1bd2b188cb5bbf6d9394e72fa9f38b33a6caa2128ee187716259/symengine-0.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a08090163819a0bbfa97d64bd2d8dac2c5268147ed9c242799d7f7e8728a6f4e", size = 49698796, upload-time = "2024-09-30T22:03:31.169Z" }, - { url = "https://files.pythonhosted.org/packages/e8/e3/cf99b6353b46b8d9ff05e8d7d24764c8afed7c46b888eece99c6e04cc295/symengine-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:1e435dcd8ed25e4c7c21ab1c0376be910efc7f35da76d532367df27b359f0358", size = 17830331, upload-time = "2024-09-30T22:03:35.649Z" }, - { url = "https://files.pythonhosted.org/packages/47/a7/e69cff22c2b169b469faa390f0102fbdefe1dfede893a086454483db0fc3/symengine-0.13.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:da0eba7e106095cdce88eb275c8a9d7c4586ad88f229394c53e1184155c00745", size = 25878286, upload-time = "2024-09-30T22:03:39.216Z" }, - { url = "https://files.pythonhosted.org/packages/60/40/bb3faf5a3d4cc99cf5252cc3f4f5267568abd4aae6439374623841cc0025/symengine-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b0c175f4f895a73a925508af03faf7efd6cad8593256bbdb5346bd996d3ec5c8", size = 22721741, upload-time = "2024-09-30T22:03:42.574Z" }, - { url = "https://files.pythonhosted.org/packages/de/da/d6350f60f11abc7ee56fcb6b996deba7b31584b5942a4c5db7d3d4f324dd/symengine-0.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e58d1e2abd08381aa0cf24c88c0e8b7f592df92619b51e32d36835fbd2dd6ae8", size = 60999516, upload-time = "2024-09-30T22:03:47.206Z" }, - { url = "https://files.pythonhosted.org/packages/d1/ee/db107db7d0557aa8dbc9485741a60dd4f869d3da32180710506f9ba942b9/symengine-0.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1db745f2c7a3c5e83510cf4decb43201f43552dfb05ad8af9787c89708be9ede", size = 90019965, upload-time = "2024-09-30T22:03:54.128Z" }, - { url = "https://files.pythonhosted.org/packages/8f/f0/0642f5d9681ff26e57c317d0514315cf1b0dfe1bc8f68ee14a13a270d704/symengine-0.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2572c98b09ac284db6ecff63f6170461194dc94c4209afd34c092ec67873d85", size = 49690621, upload-time = "2024-09-30T22:03:59.467Z" }, - { url = "https://files.pythonhosted.org/packages/54/85/4300eda41959e3839115b2cb0ddb21818804f5eb129cf92eb9cc55c6efe5/symengine-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:12727f02a2919f005aee48e68e0cbb70cf857b19385857b4d985d1c9b075f620", size = 17831939, upload-time = "2024-09-30T22:04:03.649Z" }, - { url = "https://files.pythonhosted.org/packages/a1/b2/786d3efb98ae1c8e0a9869d6901f24a6633bd621f9e4e1427c86bff35abb/symengine-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cf91d24f1bfd6d53228593c7804dd106b71b19674d5afc4fa322d516e1793bdd", size = 25853458, upload-time = "2024-09-30T22:04:06.435Z" }, - { url = "https://files.pythonhosted.org/packages/3f/74/78b9e7f17c9b9b345d8ef0dbdefebbfc2c7f00693949c6b5808f8a9e54d8/symengine-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5615b7eb68890917abd390ebb10434a949165f6064741c1a8cc345fee14e855", size = 22713256, upload-time = "2024-09-30T22:04:10.002Z" }, - { url = "https://files.pythonhosted.org/packages/1d/a8/69f429946e9a9e63a141dcbe519df8a8c3d56aa75fa8440716871efd8b75/symengine-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb92bdf0890de264abaeacbfbdbd4dd7444b94057bd47958d913b662e549ad8a", size = 60847852, upload-time = "2024-09-30T22:04:14.415Z" }, - { url = "https://files.pythonhosted.org/packages/f5/3c/a613d5fa73c1b05eb4f0a4bc2bfa21314d37c89dcdb355da684b5f9bea46/symengine-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3bce486fbc0b87970ed1b10ca9d5cafb1fd6b66382fe631261d83592851d7e", size = 89691094, upload-time = "2024-09-30T22:04:21.012Z" }, - { url = "https://files.pythonhosted.org/packages/90/9f/5a171a9edff23eb0e33e0a2dca295a8b25fd64491ebe8cf765e489bd2bcd/symengine-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7e6bae9cfcdde2775d92fbb0abe3ef04e32f65ebc4c2d164ca33f4da202d4a7", size = 49584342, upload-time = "2024-09-30T22:04:28.117Z" }, - { url = "https://files.pythonhosted.org/packages/07/16/a69dffc665e5007a0b4f1500fc401316aa070d109f83bc6887802dce199d/symengine-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:bced0a1dbdb94737c299384c85ddbad6944ce8dadc334f7bb8dbbd8f6c965807", size = 17787218, upload-time = "2024-09-30T22:04:32.066Z" }, - { url = "https://files.pythonhosted.org/packages/f2/51/36ea8d87b61f34d585d09ff585cdc2ba136c501e0a96e861fe81fd0efa5b/symengine-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d34df77971538e4c29f2d8e5ef7f459c2179465e6cdb7dfd48b79b87ecd8f4d", size = 25838153, upload-time = "2024-09-30T22:04:35.743Z" }, - { url = "https://files.pythonhosted.org/packages/2a/f7/73cd707633c01760df27d340ac2868788fa0a35034aa09f51f6a289d9a07/symengine-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab2661d9b18867e7c6edbfa7a74b8b0a2a694bd24aa08003dc3214f77cb9d6f2", size = 22706111, upload-time = "2024-09-30T22:04:38.922Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e7/a1ddc4cc3f2604b18965342523cddd6160b6406d1ef21b58a2dea1c46312/symengine-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53f27b9013878ee4419d8e853664d8ae4b68419e3f4b9b5b7f503d32bf904755", size = 60848269, upload-time = "2024-09-30T22:04:43.816Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f5/b127d236a8ccce8b85cec8bcb0221cbb6a4e651d6f5511da925e10714a4c/symengine-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:27987f75ce08c64f453e2b9b74fec6ffc5ca418c4deca0b75580979d4a4e242a", size = 89679345, upload-time = "2024-09-30T22:04:50.53Z" }, - { url = "https://files.pythonhosted.org/packages/49/d0/b07c71dfb6f7f47f90495d67cb0b2264f94f2fb9380e528fe4fcec4c5cfa/symengine-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9ea9410330ea15ed4137d7a0a3c43caccacb71490e18036ce5182d08c93baf8", size = 49582795, upload-time = "2024-09-30T22:04:56.705Z" }, - { url = "https://files.pythonhosted.org/packages/de/c6/3ce562ec269b033a738b99aa75729d564bfe465a765a5b5810200924f5c9/symengine-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:5031eb7a5c6675d5195bb57f93cc7d9ac5a7a9a826d4ad6f6b2927746ed7e6e6", size = 17786679, upload-time = "2024-09-30T22:05:30.754Z" }, - { url = "https://files.pythonhosted.org/packages/ca/0f/02031b4ed54fb088ade94082e4658160333c2d5c6629ca7e567ea5a23720/symengine-0.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ce0e5dfb19943bcf3e44a4485bcac4c5533ba3705c63083494eed0b3bf246076", size = 25795209, upload-time = "2024-09-30T22:05:05.713Z" }, - { url = "https://files.pythonhosted.org/packages/34/c0/03e9e34a4e2af73dd786fb966ee3ad7ffa57659e79e4ac390be9e9f6aded/symengine-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c3b77dc54bf1181f6bd3b3338c4e6e5973a8b0fa20a189d15563ef5626e57b04", size = 22670484, upload-time = "2024-09-30T22:05:08.721Z" }, - { url = "https://files.pythonhosted.org/packages/9d/13/66c8510403089ee157f0a8930a2e043e6f4de9ee514c8084adb958934058/symengine-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca7c3f6c168f6f5b06b421833c3d3baae56067a94b671bdffbe09b8e4fefd9be", size = 60739889, upload-time = "2024-09-30T22:05:13.331Z" }, - { url = "https://files.pythonhosted.org/packages/70/fe/d0c778204d855257865e713fcbfef11dde34621e07fee439d4d117f43203/symengine-0.13.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:847523de682416811bacb3ad11507e663b3522fbb35cd27184757e9956d0eaf0", size = 89405899, upload-time = "2024-09-30T22:05:20.504Z" }, - { url = "https://files.pythonhosted.org/packages/66/f6/f546e527caf35b7d0f14dbcea278134d4e46d7431821b9ca9f5ec3388a6a/symengine-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2fc1b7d96426463f0c9011e9fb88459d906477c1baa8a996dde6fb2bfa99d4", size = 49474632, upload-time = "2024-09-30T22:05:26.083Z" }, +version = "0.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/ae/051d3e2ffd128f4a8fa67d380bb7be45637d39e206b4c3737c087a3d4b71/symengine-0.14.1.tar.gz", hash = "sha256:4e9e4b4ec56371c2151803ae0403dab07dd1c74ce88e9abca433bebeb6e2f0f0", size = 938282, upload-time = "2025-04-21T04:03:04.916Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/64/7a9c565d804cc1aafdf4941a2fa3985a6b660ac70ac15080e5112113beeb/symengine-0.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0f977360c9993dae163951386ea5b865d8310f323cf11234e62b6535f330d3ac", size = 25935332, upload-time = "2025-04-21T04:00:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/08/9d/deba31c5a38cf26244298ebb63fee5d730638d23b6ab055ecf4988bcf485/symengine-0.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:524249b3224617f91e4e0b81c4134fa496024ddf67b383163586d74683f064a3", size = 22999100, upload-time = "2025-04-21T04:00:46.425Z" }, + { url = "https://files.pythonhosted.org/packages/87/77/13b5faf6b7873df3f2365aa2e43c7dac3e5cb7f037d7d0b60c648132fba9/symengine-0.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f2fb2b9ba7038cba436783a5c1bddc40ba84c7812a4b9c50571fd47ff265038", size = 60837233, upload-time = "2025-04-21T04:00:50.435Z" }, + { url = "https://files.pythonhosted.org/packages/46/cb/9bce97ad8135a32b47e90b3f57084bdc0bb844aa866b3ca0bc88987a2ac4/symengine-0.14.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a010412317d93e37fbee3f4bc04886408653e863997993424e7b9e4d8c85c150", size = 90826835, upload-time = "2025-04-21T04:00:55.849Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8f/3c335e44db300f4d91f7b098661a2add403367833cc27d26dc235520661a/symengine-0.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbb14724ab6a6844d04adbd48d7aadcadfe4974193e87eedac93963a6e88bf53", size = 50356243, upload-time = "2025-04-21T04:01:00.867Z" }, + { url = "https://files.pythonhosted.org/packages/fc/fd/d9f33b8a08bdfc07fff733c7f869deb5a0e8c93f5cb95ed4cb9d16294dfb/symengine-0.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:4635fa8855fcadae8c60f27f498388699b7ee88c6be7c3e23564eb0907f6b397", size = 18755307, upload-time = "2025-04-21T04:01:04.187Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f8/689900a258a68b121d7c61a6705b5332969dd95517f9be1d66a4937a17e1/symengine-0.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:43a394acef2355bbcbed089c8f83a7dbdac28f4f979e094b549f571fc411dea6", size = 25992552, upload-time = "2025-04-21T04:01:07.102Z" }, + { url = "https://files.pythonhosted.org/packages/6e/10/82291c78d6dbc17b2e4371559a3945920ecc536ec36b6c8dde28165064f9/symengine-0.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7eb37cd5a9eb0c1c5e35c515139fd1221d937b269aef3569b92c7bb4e251be4", size = 23052876, upload-time = "2025-04-21T04:01:09.679Z" }, + { url = "https://files.pythonhosted.org/packages/30/1a/2199515b7d4f3aa74262e3eb31916a593c4daa7da53bb2c9cbd2cc13fad8/symengine-0.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7657663cfb0d87d66848f7cacda0b2447e127584bc9ec65120e75ce68b21b809", size = 60873086, upload-time = "2025-04-21T04:01:14.285Z" }, + { url = "https://files.pythonhosted.org/packages/07/ce/1e7d9abea217cee8a543ff1fb395b4462e9a4df8072e25b42984e8a6938e/symengine-0.14.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94535da1085a4927364fbb4a8705f7f504897ac53da2cc6d6a8edc897e49c4da", size = 90878511, upload-time = "2025-04-21T04:01:21.764Z" }, + { url = "https://files.pythonhosted.org/packages/6e/23/4bcd98c8a79bc0d584786eb765f33dffca1136fe9b506c6e336d61fad844/symengine-0.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a32a29b9462446c9e7f3bf464c7faa847ac2a2d75f0f3fd2f7c0606f9b9384cb", size = 50397966, upload-time = "2025-04-21T04:01:27.732Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c2/72612eae87f3d0ca58ae4e2207eaee9f8c1dfddc6862a1dc8e2257d67e14/symengine-0.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:f639a488886a066c4d8f19f2a4fd2213de2ea428153d8e1bf425c14f8bc74d57", size = 18803262, upload-time = "2025-04-21T04:01:31.136Z" }, + { url = "https://files.pythonhosted.org/packages/59/ab/3ef6b6385b277511a2e80f5425629af6566c491200ced6ba4a5b33db6b9c/symengine-0.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4c657fba40960dc143d90b20fe49ae769d5c82b323b9dd459f5e3dea4796df8f", size = 25962842, upload-time = "2025-04-21T04:01:34.359Z" }, + { url = "https://files.pythonhosted.org/packages/58/8d/e47e7e9166b4d53cacb0ad722164a6a2bfbb459d62bee105a05998f7b2aa/symengine-0.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9789c62a8ade18f368241525e910dae745fdde1f3b8bb782e5ac8bbfad505e92", size = 23046175, upload-time = "2025-04-21T04:01:37.275Z" }, + { url = "https://files.pythonhosted.org/packages/be/76/6c2227ca8076c238b65341beb3ce4a5bb29174afce3eb9f124b319242693/symengine-0.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcab68fe738a50df3a1b2415f75395c588aab237cb636818332e6cda7ddcf5fb", size = 60719492, upload-time = "2025-04-21T04:01:40.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d8/e2dad8babfbb6c8abdb2c443aefca0ae65078c2a0c1f791117a2e659b005/symengine-0.14.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f58a88571a5f7ceca499d7f7834c00d129a55d4c2104a463ba8e3ec24c252e89", size = 90545323, upload-time = "2025-04-21T04:01:46.143Z" }, + { url = "https://files.pythonhosted.org/packages/64/4f/84948cd8384deb43465c14daea81e53159b1c22a2fd55c4f087c47f180df/symengine-0.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a55b8f78541d57a28beda6971bed0a7ddbd585148bb030221f7ca3a0c8e2517", size = 50278629, upload-time = "2025-04-21T04:01:51.673Z" }, + { url = "https://files.pythonhosted.org/packages/d6/7f/4d6b6998a69af3614cc4cb5953acc23b116b35e0596be4dd991397f0cdce/symengine-0.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:5091ce86728022d2c7e0e864ab23070494c1f24fb430ab3437d46806e854651e", size = 18754027, upload-time = "2025-04-21T04:01:55.355Z" }, + { url = "https://files.pythonhosted.org/packages/db/09/c382abfa3e83f11be0ade2ab063f11cc4a3a906b5194f62956856ba766b6/symengine-0.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9859c0c926475dfed666707afba04639a6d46ab48bc463faf50c79b6513de861", size = 25960338, upload-time = "2025-04-21T04:01:57.962Z" }, + { url = "https://files.pythonhosted.org/packages/13/8d/d4b541515b0fa5c390109572d035d397e28ac6f4bd69bee88806b231a65f/symengine-0.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:431545ef66b20efa24e5de9d754ffb184ad9ed29743740c0e9d815c1a1ec37a8", size = 23040715, upload-time = "2025-04-21T04:02:00.584Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c7/4ca78ac8df7dacf35bfa77411c317d65adc6add07f58a4d210060b678511/symengine-0.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e69fe8be6e1d1f5209abeb7a8308a074d981b9f1f166086f23f6eed20d861e7f", size = 60716721, upload-time = "2025-04-21T04:02:04.19Z" }, + { url = "https://files.pythonhosted.org/packages/04/92/1d1d9084ccf1749af477273ac952ab68970175a62ef093b32d83dd3efc53/symengine-0.14.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b51617f42213ceb49786b474195d17e7d62e492d0592caa4b404f0e1b1acf58", size = 90546321, upload-time = "2025-04-21T04:02:09.873Z" }, + { url = "https://files.pythonhosted.org/packages/8d/23/922e6fd625ef08c4b703790d3accdcc48e6b340538de25a29ee8b5bebe6c/symengine-0.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e91af61b854e82ae5382e5f91e66d83ac007c0b19b3946f747bc4fc6ca25d66", size = 50277827, upload-time = "2025-04-21T04:02:15.465Z" }, + { url = "https://files.pythonhosted.org/packages/7b/05/29090873cc7b3d23b492a192fa51ada148c80de016db2dc29225230f6499/symengine-0.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:d232d03ceb008d6eb24cb3e1f423af44d3d78cfe09aed801d9fc68ef7b41b611", size = 18753449, upload-time = "2025-04-21T04:02:38.332Z" }, + { url = "https://files.pythonhosted.org/packages/d8/23/0a542b0d3af0b017c129852265aa1a208f152855fd9feb0c1480d846b784/symengine-0.14.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1324f94852cf2541be29de31899d00bdcab547fea9e9534bcc548062c3f33038", size = 25893789, upload-time = "2025-04-21T04:02:19.407Z" }, + { url = "https://files.pythonhosted.org/packages/e3/38/d22630f658238e33138ceb57b368fd99555472f5e4be561bbc27cf4b134d/symengine-0.14.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e4b61cb16a559f2e098d52285b27fac4e6ee15194368cef5b78727f71a490bb5", size = 22991458, upload-time = "2025-04-21T04:02:22.396Z" }, + { url = "https://files.pythonhosted.org/packages/be/28/9ae2ee8c3a6e5b35b25a42625de4cd8ef432121dd4ff75bca6809b9e61ac/symengine-0.14.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58f8e1756c9f8cdb7f6cdf189b3752f20cb464f82bf1c7a3873156b12c567896", size = 60604042, upload-time = "2025-04-21T04:02:25.915Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c0/233806912e5515505a058d2527a53117e0e6cd8e59525f1c7cb870805a47/symengine-0.14.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a96f1adfed8cfad62a4f58fe2f32b42718e0938afef0bffeb8131d862ca71a9b", size = 90280432, upload-time = "2025-04-21T04:02:31.02Z" }, + { url = "https://files.pythonhosted.org/packages/bd/93/a6f379c6bd9554efc4cd5475e75fc164d6c44e69d98ddf6553f12a2532aa/symengine-0.14.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c4f500fdd09cbd3cde34a027a964e47b4fc7bfcd5ec8b9a4e654a37036d364b", size = 50183106, upload-time = "2025-04-21T04:02:35.537Z" }, ] [[package]] @@ -3833,8 +3883,10 @@ name = "torch" version = "2.7.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13'", - "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", + "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", + "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')", ] From 5f8473c6f102f1dd5cfd6198ec4382fe3b9d85e0 Mon Sep 17 00:00:00 2001 From: Liu Keyu Date: Sun, 3 Aug 2025 13:53:59 +0200 Subject: [PATCH 11/57] Fix dependency issues --- pyproject.toml | 3 +-- uv.lock | 58 ++++++++++---------------------------------------- 2 files changed, 12 insertions(+), 49 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e1acedfb7..964435876 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,8 +44,7 @@ dependencies = [ "numpy>=1.24; python_version >= '3.11'", "numpy>=1.22", "numpy>=1.22,<2; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict numpy v2 for macOS x86 since it is not supported anymore since torch v2.3.0 - "torch>=2.2.2,<2.3.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", - "torch>=2.4.0; python_version >= '3.12' and sys_platform != 'darwin' and 'x86_64' not in platform_machine", + "torch>=2.7.1,<2.8.0; sys_platform == 'darwin' and python_version < '3.13'", "typing-extensions>=4.1", # for `assert_never` "qiskit-ibm-transpiler>=0.11.1", "qiskit-ibm-ai-local-transpiler>=0.3.2" diff --git a/uv.lock b/uv.lock index 4e3e5bdde..812e3df7c 100644 --- a/uv.lock +++ b/uv.lock @@ -1562,8 +1562,7 @@ dependencies = [ { name = "sb3-contrib", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, { name = "scikit-learn" }, { name = "tensorboard" }, - { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' and 'x86_64' not in platform_machine and sys_platform != 'darwin'" }, + { name = "torch", marker = "python_full_version < '3.13' and sys_platform == 'darwin'" }, { name = "tqdm" }, { name = "typing-extensions" }, ] @@ -1620,8 +1619,7 @@ requires-dist = [ { name = "sb3-contrib", specifier = ">=2.0.0" }, { name = "scikit-learn", specifier = ">=1.5.1" }, { name = "tensorboard", specifier = ">=2.17.0" }, - { name = "torch", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'", specifier = ">=2.2.2,<2.3.0" }, - { name = "torch", marker = "python_full_version >= '3.12' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", specifier = ">=2.4.0" }, + { name = "torch", marker = "python_full_version < '3.13' and sys_platform == 'darwin'", specifier = ">=2.7.1,<2.8.0" }, { name = "tqdm", specifier = ">=4.66.0" }, { name = "typing-extensions", specifier = ">=4.1" }, ] @@ -3656,7 +3654,7 @@ dependencies = [ { name = "matplotlib", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, { name = "pandas", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "torch", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fa/3b/13aacfe41697455f559449ad0dc5f51b4f30aed8fb000131225c64cc60f4/stable_baselines3-2.4.1.tar.gz", hash = "sha256:3bbf0e46b9aa4b1fd2696ff4c806ddb8ba719966ef0f71fba61f7a177e563c81", size = 212611, upload-time = "2025-01-07T13:22:42.455Z" } wheels = [ @@ -3682,7 +3680,7 @@ dependencies = [ { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')" }, { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13' and 'x86_64' not in platform_machine) or (python_full_version >= '3.11' and sys_platform != 'darwin') or (python_full_version >= '3.13' and sys_platform == 'darwin')" }, { name = "pandas", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "torch", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/cc/9a334071fae143bc7177e17a3191db83c1a4bf9038b09c4c5a34e427ca33/stable_baselines3-2.7.0.tar.gz", hash = "sha256:5258561e5becd15234274262cf09fcb9a082a73c2c67a85322f5652a05195ec4", size = 219012, upload-time = "2025-07-25T09:54:35.113Z" } wheels = [ @@ -3852,49 +3850,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] -[[package]] -name = "torch" -version = "2.2.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "python_full_version < '3.11' and 'x86_64' in platform_machine and sys_platform == 'darwin'", -] -dependencies = [ - { name = "filelock", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "fsspec", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "jinja2", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "networkx", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "sympy", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "typing-extensions", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/55/7192974ab13e5e5577f45d14ce70d42f5a9a686b4f57bbe8c9ab45c4a61a/torch-2.2.2-cp310-none-macosx_10_9_x86_64.whl", hash = "sha256:b2e2200b245bd9f263a0d41b6a2dab69c4aca635a01b30cca78064b0ef5b109e", size = 150788930, upload-time = "2024-03-27T21:08:09.98Z" }, - { url = "https://files.pythonhosted.org/packages/33/6b/21496316c9b8242749ee2a9064406271efdf979e91d440e8a3806b5e84bf/torch-2.2.2-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:877b3e6593b5e00b35bbe111b7057464e76a7dd186a287280d941b564b0563c2", size = 59707286, upload-time = "2024-03-27T21:10:28.154Z" }, - { url = "https://files.pythonhosted.org/packages/3f/14/e105b8ef6d324e789c1589e95cb0ab63f3e07c2216d68b1178b7c21b7d2a/torch-2.2.2-cp311-none-macosx_10_9_x86_64.whl", hash = "sha256:95b9b44f3bcebd8b6cd8d37ec802048c872d9c567ba52c894bba90863a439059", size = 150796474, upload-time = "2024-03-27T21:09:29.142Z" }, - { url = "https://files.pythonhosted.org/packages/96/23/18b9c16c18a77755e7f15173821c7100f11e6b3b7717bea8d729bdeb92c0/torch-2.2.2-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:49aa4126ede714c5aeef7ae92969b4b0bbe67f19665106463c39f22e0a1860d1", size = 59714938, upload-time = "2024-03-27T21:09:34.709Z" }, - { url = "https://files.pythonhosted.org/packages/79/78/29dcab24a344ffd9ee9549ec0ab2c7885c13df61cde4c65836ee275efaeb/torch-2.2.2-cp312-none-macosx_10_9_x86_64.whl", hash = "sha256:eb4d6e9d3663e26cd27dc3ad266b34445a16b54908e74725adb241aa56987533", size = 150797270, upload-time = "2024-03-27T21:08:29.623Z" }, - { url = "https://files.pythonhosted.org/packages/4a/0e/e4e033371a7cba9da0db5ccb507a9174e41b9c29189a932d01f2f61ecfc0/torch-2.2.2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:bf9558da7d2bf7463390b3b2a61a6a3dbb0b45b161ee1dd5ec640bf579d479fc", size = 59678388, upload-time = "2024-03-27T21:08:35.869Z" }, -] - [[package]] name = "torch" version = "2.7.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", - "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", - "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", - "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')", -] dependencies = [ - { name = "filelock", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, - { name = "fsspec", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, - { name = "jinja2", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, - { name = "networkx", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, @@ -3909,10 +3873,10 @@ dependencies = [ { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "setuptools", marker = "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform != 'darwin') or (python_full_version >= '3.13' and sys_platform == 'darwin')" }, - { name = "sympy", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "setuptools", marker = "python_full_version >= '3.12'" }, + { name = "sympy" }, { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "typing-extensions" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/6a/27/2e06cb52adf89fe6e020963529d17ed51532fc73c1e6d1b18420ef03338c/torch-2.7.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a103b5d782af5bd119b81dbcc7ffc6fa09904c423ff8db397a1e6ea8fd71508f", size = 99089441, upload-time = "2025-06-04T17:38:48.268Z" }, From 7491ec01f5f615335fc7cdb6b070be176ae12067 Mon Sep 17 00:00:00 2001 From: Liu Keyu Date: Sun, 3 Aug 2025 14:30:54 +0200 Subject: [PATCH 12/57] Add missing zip file --- pyproject.toml | 2 +- .../training_data_compilation.zip | Bin 0 -> 592854 bytes uv.lock | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 src/mqt/predictor/rl/training_data/training_circuits/training_data_compilation.zip diff --git a/pyproject.toml b/pyproject.toml index 964435876..84b31abd6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ dependencies = [ "numpy>=1.24; python_version >= '3.11'", "numpy>=1.22", "numpy>=1.22,<2; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict numpy v2 for macOS x86 since it is not supported anymore since torch v2.3.0 - "torch>=2.7.1,<2.8.0; sys_platform == 'darwin' and python_version < '3.13'", + "torch>=2.7.1,<2.8.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch v2.3.0 for macOS x86 since it is not supported anymore. "typing-extensions>=4.1", # for `assert_never` "qiskit-ibm-transpiler>=0.11.1", "qiskit-ibm-ai-local-transpiler>=0.3.2" diff --git a/src/mqt/predictor/rl/training_data/training_circuits/training_data_compilation.zip b/src/mqt/predictor/rl/training_data/training_circuits/training_data_compilation.zip new file mode 100644 index 0000000000000000000000000000000000000000..2e51e3333fa1b0d741ac129fbe8b994e88abfa9a GIT binary patch literal 592854 zcmd43bxd7byN8QQi@Q_YU5iU`cXxMpm*Vd3PH}fF?(XjHTHwO|ZgS7JPkVE+|Jsll z60)+gVEmqEzGIAcOldJ-5F`MI_iy#A$ghk8sHuNQ4V$#!Am^w%pWN>$s9E5k zI3FMo?#MB9zi>J@Ro0H*CvfZv!EV`Kw`c(TSZp501Jk2nLX5f*Z6ji-@ZdJ^mw0MT zH;`+^G*zB&DRQC-7~2uWrTUSeMVPdFn=RwVaj+mY8!u$J{hRpLX zFb=6v4E^Qb8ci>RzwgksH%nkdx^-&e;_l4v7k$+lwXVCg!jTVStAj%?J#-yG_N!Hg zXqTy&*%KU`=NqV#Cr3VOIW+?pdnUP!em z_-eMNjrPJKEd~xT97!7N`#vNeV=4XHQO3s}VXt?G++`qOh6kvIKPKb^dO)JJUm`$V&Q(MFMEP=-9d1u z5@X}DVw9uWvkA6XEn@l*# zeSS0p(==0w`C_sA4c(hWJFi%*5d)M{Sv;`nXz8RM{Yns`s5KkE!ET%)cV;!_!_^`n z6Y6(*gh#kC8AI;+!p-w94>ylJFL!st*bibF;6p+DsbY;qySxTS&o4h6q}7_h7cYAv zJlIcttI}O>!Mk9dtSoe?WqT;l0SL zw;7@ViIkX7R8@{UjW_Kr@dBLti(dO4ldxWa$x=1KqZ6)gdOjO?OOt0M1#m^UW6<~l zaK&MS$bODGs+s|1nM%0GGauc#%3_09gE-J9fZpkoA0>u}=qC_mh>$gjGWi@AoK++- z#qP`Hdb9+&y08Lm52EAm))+q;JIWTFDf#l_LC|`+=CZSmqA`p5ap`>`UI?< zgI&98TAh4z8PAkx)-#M`tm5a^yMtku-yPibI175s_J#$T!m4%0MfN zOfKRVW+ckN1mz!7@S8OKt0)Kr{XGRt|4qT4 z%8*#G$iFCfM4mJ67S{f?75_gW2qIBNghC@Z(;r76Fha(^U zT&~p_%<^U07qbfVQ0k)bb&gk>oChu&=M!fPr8}-WITNWUmACwOK6PVsy?^0u7Zf$B zyjvG0U_^B^*arne_fmz3BlTE@Q#^L08;TDC7E^PZ4P}8dxf9{1EkHt0@ZYG^hxdNo zHAk%vKPg;>O{CB$X$ODNfuqrwK%}-W)B~CnPGl#-6w44L5;Rqv(aaytUAg1Z?dX~} z3DYznwS-Z%C&f~78<(|-Plx|*?msj%!1wC9+7bb-gh=U}AV$Q|609mxcf7p4oB4C= zIx$k}_+AUwM`Nakuf*JI;ghveXqz})?RHC`K-o{2AcA$Ph5PHGHSOorx{mLhsS?^k z;9FnjOLsh!LiZ3d#Pq2r6?0&GA%lex;IIh%`Ne}m)$Ofvm&X<(xqhDd`)$8eY-=%G zkOKFxYV-P{LBr?x(>gBmrYCQYLeURp(eeeS%V5F#^Yv{V)cdi4fWh+z(P3-^>0&f2 zFIpVd1xM)O4_J%yW6=fB2FH>*QyA_vS_}kE50m#BKJVD}ogq~XhbLt}*m{A*!6q6^ zwOH}&wM|0o_JF1_9+X~4&z2MKs)}V7GM(x3?zfq&dwVo`FkP%UjO+GYbY8?o`$UuF z0_PGaM)oZAKcapxw7w$7iv3<4eZ=e^GxVD*{;L>D2Kzlj%>T{M?H}$WaT4zg!3CVZ zGo%?4-|5`9XQos#BYJAl5Wcir0v_nhG6x0`K=>m4<~Ca03zDNiVbgg%!d}4%1p{=& zi@ZpjxR;D&AVHt2pF5|28@oc);Sd!E${Vm^6did;2{BMO71hcZa_&19Q5PLM6n9Ew)!Kke=am|2hKfjXU~He^JiRm&V70Ys z?wA_Z_wcGqIjNKO&u<(OhYRqM0<1B%rlXAc-MfrK=nC-oy3xLQAJ9c=EU(rYE=H~` z90NUnf9nYGy7l$}pCCli83Kt!+YaJuE!zkU-pT=o;b09{F?EXT9Z)9xhR{*A@i|Y2 z5)IKe5YBExWO=MSe;=Q#GPu~=UkL7V7eFi{49((2O)ifa++r?anw_^85Zy^a)*PN7 zb&S6bS|KVGSV#jXCd-#aOPxs64U@1|I)uXP)T|xG97K>wlN{{jMiuPGyPuCUs0F0x zvwP*tl4kdy)~cz{6MZk1xj-H$jX&x`DNMo?4b!&6VRq8JQt!qa<`pXNUt7-TlW47> z`k=6T=(Zy+=@27=0ZRigO)5$|J^=h46&9}g4&X;d|1p5S3G%-Rz@Om12ax6e3gCa{ z(vQe|G$Md%38aJ7YX6x_Q+&*&w~Oi~79x;IKIYOC^A0=8ybYjVmEDEg32)&A4*W{4 zaz;iF+4R-oFyre?zb9}{j;=P~;G8KKQMJ&hHfS@` zkoRUbi?={tIxe%6nXFGeRY1aq0Z*%-Pdi3wXhEoumuEh$4h?Fak#wjSncHjQa|?ka z#%`TnCP_UO_8d<|6Sr!D&!Dk;yegUL(XAcmjPcD8$;=fP2l!CB{p*PbB4!fz6t)Yc5=*NCp2oO zzLlXL996fxu!Du{9G4I*vK}8J?Aj1We;3y92wK_53`r6rV=Z^s+wFoqO}0mSpfOVW zF|JUgO#eZgj~=bT2pzM*3LTe0HyxesXyd?{i5J)h!CQ#F(y8wRe}w!W6Z{(z{#67c zLjIm$*8e8>Pi1JlMCA0xE%XH$B@w0A#(eyNAk-fOVZ~7gv;jGgkx4C7VyVC+5%zN3 zhB=ijTntnJc90{(W!>gibiqzpS%^aN{Q7`iJAr`3bszQM7I;(p%L;FGd5|^`LIFaw z#8h0X)_k7&d_v>VaWnOG-y`0mZkrX~T$(wKd3(*yhGRKiX~E)2`P9wjWi-lg8cKQg zXnUSmYQnPNq+y=LVU}yh0adF*i>v^sf}lOnec&W427q_o@kqEOSgm z%2v(EhME+!chzIpF=OQi1$z+nkVSRzJb1&&wf|dSD|LW%j?C_8dp)*n&*Y$z((Zdn z>%9d~#u84kI$qdx+{i(vy!UfRf%!64S*g7FeE9nCd~m0y--)Hcic14=eWo^Ww=^nL$czJ0J(QN?t>?U!);S~EmQs7zn$;!^?*Rtlw0>@%w;;J%ApfeVN z4a62)A0B$3Jf<&tZt?_vtS@F?;}+k@Vp`ixhML&V`?a;!&nHHMZ-&20!ElYOO$mp> z@JRc3AbN+1x~uv1-~&e5;Qyxl)9ODiWr zM@J|Mw;Hkc^!)or5%KI+OyReV8W{+{wi=@e3N9MYfz`gZv0W{v##CtqMWegK82!3R z!hWd+RQpa~uv3iMw-EdO>%%Q{Df3RnO?%A4yPtS-yL9xqOK_&4jgDG8y>*KKW!-?B zOc!od6b#X*Sspn&}7 zdOEE~^tEkAwCbKLa$&yJkSSlk+}Kpfe%#vu?K#`)4$3C?w@||fv^8J4qxqGWmQf;T0Q z;hY^wglv3Ls-Wzs;Djr8|0=9xEUs-acU9?T^YtD+hdTvlN=j=7TwFZm1vF`EY`u@=MiuDnp!0N4?27AFno zb}Y6wdX;3QOZCKdu?uze9%)0O@pe8Y$_ z5r)RA$7=)t{SHt9nH>XUKA6zzU;#y4N)x+%iq>de{g!&E#blhtTZN*I=bl}jD?_^ZXNVh$!DTMk|B7Q$vypkK-)faISW z)aA7?1YU}C)J})WYYIa9&2s|^?g|ex6Z&fNw3sh1@Ex^a<(T4LXxmc}1vzu+fXBZW zjxeAwoS{c1Ux@dLyAtaaaL56&e;+lr}E~E{jh$xuwIXe$q?j|J!YGSmJGJg>@ac1=1^R>uf(oq?rd*c5fBC@ z004_8iGERgf6?JY^xi~#o+USKGx51u3*kC8b=&Y#%(hr^rkHKnxPw&5yH#+fZfd%0iY==lOH}pJSbT*$}g-+m}^zg=w?_Mdyb z2>&RCEp4XKq<)TRWsj%1jgWJDIqNx^i675v-J_9OJhxIRB(d~D=^5`^OC=dQ*#%`b zPsKXE-`@HT7s(G7NDC9pO1K>r!p{bijOULj$S@HlvJpU6&f1w^vFK)IVchzlM_+{v zoG5YS#qe4RcF)MlBs&lWDIbRC4R_CAjc7n=>Rsp~Uo+yU&q!O~Py6HvX6Y?Tg@y=3 zG(Rv2wfxoc&;r4gKIN08v<(%=wl+OgqW(qSCtQ%YI$gNv>wJ*S^Mou#Uf@r9Xu|1K zqCuD>S?Hp)81XM^?RZnM-eKh7d`03E#7!e-Bv8b)06J>X$?%^*2dpd-uJrWJjTgT+4?f zAzZ_Sn)xONYJ%{VxCpZ;H$@|}rk@-v6o*Aat{i|CqSY*t^_n{lp_-0KCWBM3!~iqM z^^-Fb=<53h%=)4SEt&`Pn=|)tC^j{SPMpugg+2d7OU@!`nxKhXT%oyE-qrNvm5Qwl za`Q#|P9j5j)BFC)d+RKA?_46p&}^XBH0 zZl>kV34O1n*F$4*Ay-$)^}e}_MZJ#Mf)c_<%U=i~Xii~)P*{FZ*?MU7n@JGK_TmBJ zUkmsOu&41DBG4n#-M4!hHvd|_z$~gTbbU9}2gH9i6#oDJ4(sno|5quh9QqH6qWf<} z{b>a;LF_|O{pXKJ-S3JrPhsdWLLr?1dS5|=UQ(OzqckJX(OXO~D1^OfNem%~CPvPM zwyoUjcljlh8?9J+5?D=EVzK5qta+uUSZlBrpKAVMboaQ~@Oi{hA^^)|a2DZo)inO4 z)~x2VoT{ezvDvJ_MX810(begwbsm42JMpnmN_JQf&f!yUe4Z z32Bx?v7;_KHMyB?C!=Xv3ZQ-*pXxMOIqkbLJY+xLd!nXhm10S(r7vCi$~(J0)>&Q^ zIP>>@^LBhLUz_u4yF2UNRq$DEEjlVtZzisnSml7;)VUj9T^;ZE^{~77ti^34IaAou zOqL;)O8;z4nt9Ab>-4fYy_!vxS$&np%?4e_!Ix$j=JGLK7B z=wh0}`n@<>F*GA5br-)o(LOVzM9y zZcbZkZ^LRdY0fx`ol0$2V_;C5y%0j^dihm8NRP$|u!Vp30kuhaY=2Iu* z9Tmx$%v)K%fsW?=R4ZozLOi(!HeX+U|~(+xQy zkKsV{P0g>U(^w~Nm(~T*VS$oXTxCkIMTfUsc=hg}UkRNArwo&Vx(?4-Dg=^UFeQ3Y zR*Xd#Xa$%yzF)mAOg%K7(Gm?XPxeL=2XhBrm9}h^$_6%pp0p4*zH_^tf470ZDzjyU z0?TOtUr=|26_S42QxvYqCY>nEdc_k`PFFp_M4WNkR^p2&6ZJNTXs3;foAP5DdY#UM z=@fGjQnGMKvaz4=;sTiH`;`{-wH{FRV7U`m`7u#2&ps+wE}ve2ST>g=PIgEzKdD-V zOuQE>{t7_xH6JY)om{aPEgByY`3G^Y_K+Z5RJ9Bgtn%*qu}mAk41HOmED~%-S`n(O z;N^jsR;;iY20~~pvA7IJux<`9PUVmnq<~A{M%>U8nAte8c;5 zIqF7v!tha{hL(kshg{w2yhcY^;GC9hg<~O}>uBkVo*ZuKQ88yde;saecyQ+s=R zarsx^!QR`}6yenyqZWWZM7QUOIw7g{lV;+o^7_D^?dzO%_p!JSZwF5GBCP|HvpvOD zW5R_>H0|{ot7Tg7^vPMPp2iKQS~N@PP0gg%Y85ON^OUVY9`lZ{a?Um{)h_mY&WC)vmCRLPJF*!pKh&LJk=v{8(#NTATP9} zXt%F_w!b*Hr^v--75Ea!#^wPI(%fno*iwxzBGNV@*|c7TjV zqx?p9%f^ilqr!CF1RnOVO0?cz?2R7=>O#+U=8h~>>(u{0|{$!6&xl$)t?vrTXsZ=on91iOD0W5vzPC{oy78l}q!r)v}^bqrc zKnml0($BIYL%y=gLFoQKpK>LAyGaqHHi)^l>Cp=`sp5I%^lKC?c)>(eq5#Q0)AQgN zM)69c8EinV^n$z39nM5#r;~@6&xMoOhs1rC^r@aGChdhvzXGw`f=&97T&ydR8jZAD zD2bW^I7G{B(>wtswLts|Hhh#GB3rAOM~dsIP^2Us@OV(F9Gk}^5lmDDNjgL#gJQY` zx!kK&rm{xHI@Ri%<4#T62ZrJH34oDVYT^bh8?gH>Oex&WUg6$j`R0-7`bT#0i^!`9 zV?lJr*kNsQcY&wJQ@9oMtA>%N9927ehcFy5KH^uufiFgA=zHRV#C8Sva$l#T57Uwd z#K+9_nT@FFg25Ry>{<>wOfDyLta99%G1;KTP^C-TIn>m%m&6lzljtYO(57nsKaGcb4jE zd{#P}IlI5SK32KpfNYxws`R;5>0P+fyxwyG&ZaqflPCEmp6INMg!bxV)E-#%&%4OSuGEjv9z%|DQ>*o8PpZ zG!gCLb4vr|*3+Ux&NbaFTAI~5@OAO|T3#kBmpe3T+3j4SH|jO5mdaIF;h)P-aft7& z|J$VHMgMJ5@1|qe%yJlyYiTB_+imQP^NPekc5CkW?eQ`0GqM>U%7GyjH+*Rv)tS? zX@;S1W|vxeJV?Mv(R`}Z!ZwocTh&&)H6uA_z`1C>j1ANTxzTn+{*&iw6Hv)BzIJ%R zzQkV`j$Gz$rh;;4|GzS_8YzHU)Q8U7HXjWih!j0YnW{@2B%Cm8leCU101h=iYmac! zSoW;q7mq#?5S3htzqBF+HSf|rbl*oADf^!?Qcbs*G^{qvpFW|_a1*2eNl*hRr;Jx# zyOwsymXzxob|`27nb?x?Jmmm#@jX@FokAaS)Ug-BKayVC)hRJR`nkSd;VCmsh2>5bEO(!`n zZN(m*Y%r7xAknU3HP>E_=7)WFm~_TgXcx;Iz4i40@s!EfFg9Fjt5sF7W6Q1Qo~gR- z0`fkfWlq`Et>C89jLTl{12n5q)~|qce!h;beQ8LqFGwpnmn6cHGUQ%6JMutJ3?&=M zxfg}1T}-HbnSP*YL-J7*NJ%0JTcD5%fUb}R@0Y+}Sz^>DM&Te(3_|~y{V#Ztm&8~@ z!RoPu%t~VW1B9*%zKZlln&i<86-dbLuGzGKyd(oUQNcpih?+_W=mn}0)P6(JBBD&7 z=-iK1YZY`n!9tlpB!~=lHmgE>&?(dTR z(AYnh^lx1EuaY#&`^F8@|6GK$H#7KOz5ah#f&4BbCe_UY*M6@g&YDg3?4Q7=n**r{ zMIMcLS!toPxB$LtsrGn(CL7n6iMi=PnV>9nSDgr7R z$6GKcLvEv)yzsR$v|ZdHzue*sNHqikKVpUJM?Gp9K8Es&o2w?LrKQ$fC!C6FiEuS> zJZ*`7fo_O7x2mgV{x17~qCWdm^GVB*JaPHRX#u-oi$?`s{Y*a{FJ;Q)7k&3W$Yk&? z##XK(ier1<09C-RfZPRubD=TgR#V(94@tRE;`?deyBLG;haN{Tey#1l>ETiu$G&P_ zoEIq@m*u_2?`y6tWOC%vHXTON?UdkcTHb+vL<TfjsDOWLCQr#b|`S)a&&U_5buZwJ8!NJ52y3)a|@VLz~`O$Q94q5AK z5qOvon`QSI*DaifW&2%z>O`UP2Q&*IKnPUdqwRvcv^kRJ@bxLOlB#j`I5ECvfJY_= zFis=P91%L;F@({wYn(u=&Q zkejN;B8%#H2EhE@zD!~B#F)bD;S15=vA^qZ!_nl02`u1zP^3^zxsSlsq+XKrMa?N7 zBG&8?(v~2B;fd=I}{b7DeRiq3*X_?~ABvZ=IF>6jkCSkYYmM+nTs^RE zaX=XN=5XzLI5Ld`T5)bstHSB@xpVho#YROB4c`O93W6o?*b! zOTK(&4J>4=bPl!z?UY%N1?<#9k5kpH!^|>f$C`CRH{V8w^UfkziI~hR(w#Z){4@y=dI{YxREi`1)0qU!zSgipUDKI%bwr)rD4cz_ce=-(eKmmPX@su^ z_9dmGTf1`uDHm8!NSTxQ1ZM{K!?b)UQC}O&me``(t zgK5*{|2nPLwyy-i>EK5Yn?oc9O{)#uJUn6X)AqBYHI@Y(%09nM%&ObO<3K&76LaZFm;~;^>PsWwqUj(A1coohCx#%sJC|MMCffw$ zVJ!+JLxoMUMfGBiOl1^X zs?Dd(4VK3aRtq>^t&6%Y6PImLg(8Ob!Pnc@J=@+Mwj&^6a(SypP+O7>lrURJ3kF^u zO&QV5(_ThTxYWdBGIRH8le1v2*(lqlgKr95dVH9BnUO4nx$I%*JHJO4$@0bk)XIuH zd1|y?Pt|q9a022Wj+d-r-R*pnijcIE@cVThYi)lbJl`uP%or?BG=xSN)BeTuC?|3F zndGRV;{aUh7om1sj4T?xDyam`Qx_ul6o5=jUKW5{H_HONPsRoYG`#OjvCNfMISJ*x zm875S5+SKK0{8s!=|pe_zSkfSU3>tKM#k9>Hs|0-yXY%i_E}ZJtY7|#-&^T+=-n+R zYh-~7=-soZFiv>+XYdfm>|OQ0Ss_A9XrGVCXE=As&Jjp3?0-6LPAg(qyWBc5Sbxl% zj~)%cO?lz&sr*?P8jOOd5vCdjWRA#O`o{P64gfd=JdiQo**g#)sriRM{3creDj;Uw zJBt4|5PxeG{==ffI}jfYdu{hh%5JLCN6v;=UgkuEQ3;<_wFRs`8ul1-GEJ7M-*5^S zEmj!wDH|?ZU98p?27Bq=`_>liGf+AfxK=BFSeqOYoB!&?!3kswY;0!sy|(=ZgIBd+_8Rb9&<%WKOikwzRv+iB0yEvCQlnA|IkIqAa2)E{arx zZ>|K7NuIwyt*FS0V|>S@26+|L1CWg{7G|y8M08ICmc!1DNx5api>C<+XG@mamZ~s+ zQM~*dkjxgO%xGx&bW7pMp4JU8iDzD=C=sM;HCTLhspm$olrCs=oGK|`$_Dp|c2?ku zHxW=YnuWp@%{=1N3yF!pY@a9(@6`CME4zJ8;eZxB&_QpEbYkJLE&<7|mNKTFEGhTg z@#sKoAzv9}zLy)Vrm`lJoj!QS(B>p#&Q$Ol7&O*i%a5IOK&kFXZZ^@`EEs91i4Cc; z&l1XzMr_#h(DSmwC|*o}fb0uc*u=dmnK0-l=J-I23-U(fG!9)DrDqOlC<138juJB% z+-x3wrV^GfOJ0UmuPv^289Ec%6MdCkmEkgeGMKT@#K7j*7{!jyf8APES5=`&-V6AT zxcx(tev`?66-nX{za!~yExUi%2uW8Mwp^x#Ykj5&N}KA=9?;-I%;Nc+mxyoSw}*1` zW!RixCQ>=!bnjeDrCwyve)Xn)X)!sSh>U)C0Gx#)qrl}h)clHwj68APQe2Q`=j=5b zbkJSU8&H$Z0n{>+L!!i(glcEqAcmeyq^#OTmjOhb=CO1naFNZrz)! zshKi_3k04&8Xr90Vb9TSj7((=1vF_iU2fcjRpu~DyH0M)R=c+aVxZ;=mekId=$>zq zgtpbGX5mO3_h13+M$WN;M$CYNlM<)vxEjG{#d1HC1_v&sa6oq^StS@d+P`KshbCfABb~ak}q`?$QN+Uvq zJp#Rt86EL;M~Zh4wuU7;-fUG@M4Wg@v~bhPeHSA2BK%h(?A4Yi=B|h*Lj5m`{sPAK z&$z`W$2o=Bi!U;o3i+i^Og(E4&vf@^2}d+qoY}iE+u546d9;-)0svfE+_e~TU@ecKyirr_EIL5E+(_<~FsL8M7 z6~>Q`2^UWt8B+@wDachV_Uuzgt`(SyDPmHVoL2XCmo7D9sd9O;!Em`&xgI!j#hZQ| z49_o*-=BA^!b@%%%2QS^8m7dCDsWK{hc%yvrcN3w>~V{c3&4-QuJj|WQ(-bdm5`h31Cd=31LA}P2dtgxY_dFF zB^)7Si#8Z6EGcMPl4{m#(4i)??WfxMt%MhCM-|e5ia)Mgdu1kEDzV>)K+{Ve_V@r^ zJ3b~xtu}zdkL0#80>=bU1_Jt1lHeV#t#fC8u?|YK591Q~Bh55oLJPD751Ah5ws-d< zn-+}Y15&fdUYLmq{EUrLm8qmm6Y^Z2HQ>-9n^xEXCGtE*leO7+7y$~&&!90;ld<>& z%Wd>}7Ke6>MLLpvYjw+AoADo#L(%EU$b4)Bmg~iRbC0(2PP<)f)D%4D$&t&Lap2&R zHK0_ia$l$_sM@Q6)9D2boaw`~k}#yfiy$U88qL5`tO;S9@fq9abd^W6kbQU^tbgGI z$qRvD(k3etpvx`ZK@Nl;9QXse&4Xi3^rwJ3f#X~Q0v8lXz+X?^brF#3NMq%~P_yHp z0{U^mW8hc-0b-~(0Vg=BTjS2RIL9OyL?^z`ZD+j98M4QpZehP>KfSIu-jDcvS8p@R z009nj3A?2`qg(5r&|B#D@BfAyixcZ1?jjDlK+Y)5?f44xQKya&Mn{&v1O1Wue+cw% z(D+vYo%8v3K>w|4^^ZVHSWWZ5y-(bJxRR4P7~Z+5K|LDc zdHh_}X4J(^1Wlz04ciUH9oEfA_!^(Ly<2dj!GFgwJmt_=|HwwOD5t_zQ6ZGOYMCfr zU_w7QoVPL(Yr>pTF=R7KS5ovcW1;vX_&&vg&Ig42VsrY`Z(Pqe<;ssn%ZhuwFb>Zov*K$f+*`zx7;{mqU1yf^G* z;7VY3;8fsH;8I{f_trQO;R@MIz-Jtnh|y_b zJMx5u1Yf7eW_?LIFtdC7fNjVSfTuoL*3OJ&!-Zf3IILgTySabgdJ($S7K;Qatdm`V zG-|5f%EK`~l!5V&n(_u?1k%C_1b64CI`HR_o?|V0HFD{8WIIQxt(U|H#~Yk~wEY$; z^;uOv!Z;2wE3zfj;rg5Ll8zo#V%Ff?qprSLX(OGl*pgfAH%h6H=(2SBG_%};4V9Ht zHmz{eWI0g;)YH}lhT~XUc{782d42z}^m2=I8leU!94wo!kcQ*#E1Eyj$I^jqfrc)J z<|)R)O(tz>?R9?$m|(MUl0 zCL*YLiJ+n9u#&v<}9cM zKk67TW{a!R`l2hED^v^GJ=(c$w_Z-DNaP@O0Y^b|(p}P-Zl4};r($-~QNhz+N4Htr z^@AVY)$qa3KUBkSAp2LT!S}tz3K`&kw1)q+=KuG0(Lc>$mAWjKS>f8=EANcv&q5jK zG5pnlpUi#kst@Xa?xCFUI?f0hWA#_3nZLEiA;;%^n|H9vbxC76hc*e;r0h*S@^omS zPOaD~UI|u^i??tfNLcTEmQvy*Wxn_I-T}P;ZCcdDTeHBZT4RhaI_I`Zaaf%56lq#H zS(au(g>_PBD(%&4D^;^uu7_<=3jH}fEmze{7OJp{a_XtEA|HBNui=Os8o_4o@gpUD z--8IJ${UF7;^@3V2`>_~dVHMA-TisIu;bN(rpS9(#iOHnd0l?%XMJ;NqVmH&YGTb| zeQ|rxQB`Y;lGDa^SYsKI(aX%}#BYqjWyuY^0mFOO zqxj)*kx|m)AeJ!tpOpF#6Q9@pKn;(P5QKL<-kBzLr+kTmoHQo!iEJ5Gki%O%udGTy zs%&s*M~gQ{Mkf$?Y!f;To)w`ZUnJx26@2zHy3{^$XJVr(th-Y8+~4&{mKvHI)Q_K9 z3*vTKS$?l43fW=D?7o8Tb-r7od?k||d=1iAD7|8xm4!H)p!T&_Idq84-dQzVWCTqM zA}j->%eQWSxN2~X!ej!YXD95rc=QYtat^acmU%%DpBWw;1 zBm59Nh6nxAcV^9E5)Skf>0^EMAD-lVLbH=3C&@vG=(gSZcH!cZyAve%4djy|h<7uA zX%qV?LhD-CL22A6m!@fVqGeNgDvk(gHEhDc&kT8n+T_}dFdS87jL}huXshBgmo}ZX zs7CugA8jUjZ3IKuOpyzW2-rWR*NMV=*^3Q7c|*$06%0M1Jq^6q9AU)ojXt~~#Xt98?dg7qs;dI!voPDg?9DU-QBWN{%HSqP};ZASg+W(qbY{jjP zh`sNKd@%WE-F$hc^>@$G`XjjiD&K^>HzNJUH-GEZ{G)FqtT0*N+K!bdZAF1xn0g}d z(1Af^zqEmvV9v1Qf{U#6@=*<|ia1`MvroB7*nX`pS!ujxwOYfBX&D~Od#Y>7T&FHP z<({3kmm6I+Y__m0S&Tdyo-c*@p?Dt;luzSLEsrKaJZn{2uUfuTpt|CvRyTa;*dL3r!LZ6WyRBQZpxCf{+OyZAfAwB4j9V4lx>uDov>13yizghPnrG52%Jr8- zO0?*#!OjxQN%!EiKK(i*@2e8rdN_#W=6-p--PY0~SLoR_w&Ctret*kU2fKzOozZbC zYN1?*W%r7d{=-$K?)Ajg?I#Qu)^*rK%3F;4$c9zQpdP}KE$RO3V1CR%q(EvI4=ga8 zdhRv8GG75(Q8nm|;4rthO6UHgI}FDG#0@y-Nv5!DI5|G?&vXR6Ki~-P_WYAjb@3t{ z`u5)L0{X@!Z`Cx|X*DT?0{D2!s7S(ES}zann__KW-_>(S2N(21ffu}D;5gQmc3KbE z*dh!V$R5LsFGBP|{*=x*T)XYF{+pEBlsy*>PLMlo5fA2_LpxUbW%M2~#Wy2AFt#(v zA;mgkO`ZKVdLHv*cRFG<^4&DWfO#BF#MD?_@p@TFGTQ`}jDEl3>)C~ow7P&M=iOVM zWXYi_6{GMfbN_p6hx)NZ{>LGH?Q-o-?4-!$*eREFP;C-zzoqA=V2^Ejw(l8EVfV)F zo!*7BjU!>(-@fQ0fWWS0()1gdS=I8NV-;^ac>8a=%Qlx&kwuD{fQLlxKkD)k_pvAR zSYcsOT76;lDAJsY9UtOFC0FdD!37SupQ0Grql7j~ZSU=;k;gtjm?1L0QaC1lvL{C7 z0FoGiW|OF|2+1O3e;ndMlp*dx64xZ(A&U1%_g_gBFQi_u!!5{43?b=&Kne!RjI*yN zH``TokP$!ekNt5cJM{$8giZ+xwUjMMSxQYP3>2G5k~(myZQCJzgnkDl#!JXNJIFaL zpa#-*7oWjPrai$x$&$Pvhu~I#2t9~<812ZT658t-y#k}aijtR}ha32KX+p7?L1f-R{H!8^3DPPELStJQGUX>1zm`$+rTWkU>@jrM zlY8c8#kQ>&Gqpa=r^#RbHn>9FZId&PT4;A-G& z_`~o8`9}mspy_bwxO@3}`FzW!3$Up3-u~)1wA>WV&3c#E2k8G)V!yG$ze-{Q?~OXY zk=WnbB>yy_QvT~S-N#Ot+Y(9y2g9$m_XE1xZe;bV0(PJ8wAoGd7$fu+C7a*c2`D~^ z*tC}0<$isynK)sHCZ|nq#jYzG)kc@RO7<7)RclAh=T{W3t!4{p<~=G^x#dCfsJk>} z8gEkqwf>Kj~z%1V}aGodSinYgP_rLx9nC#hE^L9Cj+pidg|$}{@N+t0_wH{ zYMtb~%bLNj6XE}rXv9Qk48l$^$*wyMbhXBm+ZQhwjb8+G^v{B{O>mzS-sVTbT8dXe`ZVw|jmHp|e$1#5G zA6Z@4QIW&3Q%&bUl?b#xY#LSvn2BBRu4icHGSA+CQZ+WoX#K(3(Y$aR&R$&DnWt@v zFl}}TzT3bE5!)h%CCmvN2xQ>B{nzgFcm&(n)0-p_kO7zwlDH*N(Y(5Bzj`OLntayy z!0}kr-1@mvEi);*4(*VyryI07&cT8k(hOV*AK)FaLKk%`rtXIbb#G-7{XH-w*E(KA zGpQYjVgQh0{X}a{?_C-RIUn(zd8^Uk<@j8R<)fToz9ZOIFcdWVkLM==X_aKSPc%Xx zJ%ALJ#!FKwm4=$;plc$qTZ=#{56nh&YVQQGRW&qke4!KPx-;+Y_ZtJv)X1M@Zq|a=j{Hrp@;zqMNFT$>e#S&0w>eeU zl~_w{n6+=Cr*UK}*Vi2sBXi0@2JYn8_QH^w4W61Buop>FN>2*q^DTzvAPdHcLVDVA zB0BK+5Dv}6L}W*>bt8o6L$Wn}jS&Y5VFQ8+7WfiOd_NDgd#mg8Ooq!IP(e98NLUt$0kfStd9;^&lGE#uArX| zQO^*ohR(2`4pq+*>zZz_zY0z-dm;nPub3fqrn+Yx{XcFzY&w{_e|O`DKmMs3e21ClB>ih}ID6p+yQff2M$GDdtNpSjB4e$;xs{g=xdo%sX6kT!^VR zS$gt$TeWv6_XCN!cUiZ)f)}{(Ry~WObu$(Koyw|A;(dY)omGRyn~%EC$GS2}T5**D zk!k{r#{`*5T_M#ORQZ-Nf0y~=}teUksP8s^EQ{Q(S1-uDDA;}K%uIMf6r5A5~weHK)3 zdA>7Yy#t&{h0QnrCYkYOS(b2 zB&89gk&y20Zlt@rk&sqGkdW?1knS#lv%dbG=M4|;eYWq}WB8xL;SgQx`rPxH_nd3a zHj1zn)Osy?ea>oZ%oqNh2GHBsvYUqt0a~J+al*Zl`gWV*sLwZ{Rjg26Fxlq?P)?JL zwSF*Bf6|R59$1|h_N=^uq0|;*|I(d`t%E|t*u0xHLgnpv4=Ts#rn0jsT@qiAHXuQy zbme+m4eFD%BpAv%(}<%lEB1+q8hbD!sJ?}hYXQfZ^7L{E@Z& zhCZs{KV=*m>70Uo&A#ItEHfrDK#o_fkGvj8DZU&$TmR!?)la&Y<1OMMN+ea=&Z^;V`Hg#RO*0B}%{_YjS)jnf-?9lnCJ`UuoNQfV~ zVe@o=2zB$=7f(({aN;u>zPNr_dvJ|QhBt(j!?xQ|IWTIBA#{K}TwJ^g#3P=hTKhTC zC1xzB!W>W}=%xL!A|H9Pf0ZH^f1}7>`n`TLKH4(&WjIjY?moQ7=+as6V~~+<9)ZOA zFtX${Khnm)-+N-gYC$nylYWMLcaX4eYAD*#nR=OaDS^cZe-)rc9AQ^wqef^N+;0fA z&g3$~FNb1=5IdkcWNm-VLJNPT#K&AON^+~jm-jpnOLeO4Q!}e+R55P=y5sk6E1u() zVXGbjY}&MieQeA4YWn(`x=*7FLO#m#=j@6cK+F5t_P{je9dNv9ixGUwN7p2|snYBl z&(>s2Hb{aNXf+o2#-nh2NeR2x(9*Urv2YMyJ*zlO05`MDdTgq!8?nse&_<-;^x#Ke zdCiTVngze8<67Uy;qd|0y}qqb-kI;Sjc;fflr7Q6WOr#4x}Q9~78bp|eU2381Ih(1 zFgxYKCF!6L&IdW)5+3Au>O9O)xy|fss?J=T5Ds76KDo+8%i#@ZNS|oUc~JiavMo{5l^}M^K<`?*5|8AT!P%C#IqSDgxCUO4-owu0YY|Lr)n_WMiosXjp!(40 z`=|~NK%$_x_{S1`Jw5~TuM$zxIbmkzAo+{;+TD!$AICP6P>V{(1WhKv6YLB$tm zQA2f8cp$d?RUlr;#3~(&#z<3e3Ubb&gj-Zx)FFA@ z5m@qQ8tQPkQnO&Z2V@hjlYv?A`ggC{>CS^qky7PCOpZ-3_Ci)78_#TrT()?NuZJtW2m`CJ5|Ao_e~^Bk&%5(H6*Nc&+qV8>$g4c7 zXvCI4tP1=cd2`PHlr*DA;Z!34$%5YMA4~R;SN~T@R`)lO{iW6EH~pxVvD%;(YtYD9 z$u|AC_L(3FO)}M;{Qb(s-1Ex0Ko%>zi5VZ3K7qOifzxl$`O+kNn>?HKEN^f<)Fg=N ztE+5K315^2C}YNd`lkQDE>(}XQSYQCifX-Zn?YjzH~MZ z?Iyn^#A*H0^mFMdGCZAmh1nf3hG+sg6~KTW|x zm&2F+!JkYjbB|{j%SplX94WKyP~+(NRrSq_)615Vm2ji!>J}xSG!TRoSr#>FIQF{f z;#2;Enjvn!^S#1WSWQX|<~rLted;i~?x;awSK~FU)qCyYYTn~SO_?C)jfX-_>CEvs%F7K#bWM$#hgEjl>rA1Gu2zJ{ZGR<3wpF@ zu*>!yg3NDl7ZAFUImWAOjQMRCl2I|^504seIiyT66l6~^s+8UEjc`58*(V~gNG4_S z%#HIGJNu29lgc6I^7Pt#;`R!B{aGwO_0^nSN4RvOBtfU-NF?;`48Wc0ojih0jqwdC$(_>OJQI%+tFFAI zZgNmcO(1%1HH}msD?$&K|aM9v)R6)90v9MXotXCAwa=k+H#6>{hyb}ap_OF)Y-Q^vI4&>#qDOgyllIyztma2l@mHz&_%~|)r8nlcY90XXk`TJ|=$^DN zEzq+#HBEbwpjomwSf|*6@2DR^{hkU?GcZQ~k_Gx@H3nS3A!mBcv;~~-o;F#{nKL4N>{dl+U#Hh z;|T+`bvNe|3QT1WMcjV%()|dF@4C*;wM>3s_&w1|p`Uef)szT5T+tq)bFG&@J>L_& zYlLwx)~3J|jI8%*(Mf+J{x0&+NwKOM?B>hmCnwab#2?2Q+EHgnODhd(D&-0Wi=0&UazLik&Oee6#dZqUm0=^#g3ulRioJb3oTQfUa}cvu8zWeKrsVAvB-W z!J0OWY}5SwlFK8pJg6SfHt6;Lv9=%4$X}&xI^dh0$K3HR%^kmU$8mjtwsCw8p9gYa zYr;iXCOV*%DACt9q;Q&9=MhC5Q%pfZ^iXI|I1?N0!6lOUwl8mb8}_jtrN}s0l(>Om z^MV(0Pqu=c6CYJSG`$#+5<=-yRw?){(9e2QubjP)0V5*dTw0)+%ushg$XZo?3g)ODQ*CaUSQ zYi#7i{8W1JHHHvW3aHPv;H^1Y3k?Kvt9@MHb+XdXOA#IMgM& z%<_8vgl&QmEjFVt$iKxmODr-+V|hPU_!t5w^T2zbjB~lB2zkSa>`=jP3E$Tbld~+Z zDb{Br3JZ+EUv>S$0xR``jAhee!)>pDAp!B@O+WeJu_d7KrQ4?rgVvJkOW3=`o#Q7? zD%+lp^Xg01QyQBo=B4o~{n2kt-XqQ5TcCd^l>33TnCji-!OjVvPdqp;4AvdB>VqaJ zC1fuNoZg@xMaTf5$)gG7u(tJlogS1261*F?=>n7v62l)W{Sn3dRZ4dN{&S#7k-uL0 z{n`-pyW6w4pC2yz0H#DucPn4d!d?cwWQ@wvdLPTudXfmSF?3R|J^2kb$dj~mpeg0r zd>r0fjiZR_pr$HS7G1}090OB{RqQrXxQBijOk4|D@dPr4Lc~>!$d4OZR}(H(jkUeo zoF2clQ9xwCNu{A2X=^CrDWcX@J0>^nBvK3aN1dwbQrhxHKQjxx!PP?b^?1~m1=iY3 zvQL{2o@z0Rp~#)I2XkrfF8)Ar7bWY?&;Rx`05%q5)dnIxM|KDhJ61X|6mgxa71ZmI zn(aLK57;^_&vSJfT2l%_r$rSwq&dqxTzTEr{lfZ7Aqx>Eox-?&M9(hnv01kVsZle& z%1@$!!j@Dg3V!fg*=-me4vt>V^7%L+u#(Fg%>m)e&v%L@O1Ql5uTw+5rOSNNgj~Aa z?p>Hm>22t&5gr07*(mS%h9_~*cp7uQOcwer`-K;&F>ZGJZ%M)jXv{AVsV(i71 zpVG(9Ik_Cpar2PxZAyhH7JY*tPSl-f5RE1iDv2(G9t~X35Dh*Xe4hF`)xEB|?r3Xj z>@Cj!498wD=dA z{wG{60zZ<)M(ljZ31AelRw}TAR%Gaf#^K>{yv@Z$DU`94K47~J&-on4_6c<)Aj$g0 z#V=MPbj|NWm!-CcNzpJNmA{=it07?Pe1CQ~L67NQ+5({tNsbe_>Wg$(I#vGT++7Y_ zDv;#<=@sf{II(-^SAhyFwgLKeD|@}O+3`t<<4_D~0$XD4M)iYTJI$MN&q6cUxMQ$| zscBy~`*ZccRYhQQy&j>&xM= z5K{dmPSsFSs$XzsrbcLX81aP;(-!Jk73|%D`{b2YTkZ>f-6O-gPyw30NQq^sJHz0f zAgZTw88mE}3ps)4mPIsMGZJ@au4~7WOp9L%v)o^E8$)QakeIjEcF1SjwgRKvMOVL5 zVinQ5mTr z=|p*HAjjOnEyc9c63xQd7lRjy^5gEI34RR26RmWKOY2iE!l7aX6qiAG>GGECsUe19 z84CF`nHMS5M)8GPGG&V6X1@7uZymcL>#%g{sqG_EbJm29bh2yr3WVJaqweVBn351G z8wjSy^z6-!)biy_N=eBGl>pllcwo7N$*U~OjQcrqPuH?i3Y4Kz02%5UW*D3?PvJ2l zR3HMsT|aZvH@l)Y)(J&wq4=5{+IAEB8djnNwbL9{Z(O0xXsqNlBa~cs<#qB`OD~jy zPVr0l@n(Akt(^08e4z1l4hzsiwEmmRP0Eh>d9>h<+k-% zSPf(Tw2ODAZYJj-r6y~)m?(4CM0)=;w2hqYWJww_9oo|R0XI9Ph$kD0J7I|p#sN%T z<}l}@sa-tVt$ko{|2fn2NO@XntL;0zY-dDeXinTtnx`htt2KJP!dgT&w$zodK_^i; zh(SCro+G>1Qd^-S4)ifUl^4o$L@aX8;@^`TRK?k7(?#f^D7B28vkX^PbrcL}6q`FX6#z%6aTfxScJ4MLM62>Z?-R#IdJxq-_hY zu@XLwu5EmICl)Ej5*qjJyM8NTF0AKUj8gpWn0HK(r72hvo&6j6RjT$5&uyk`9k4P+ zOsme{zZM@|x9gZp{3dN${*q(qq}a-FR$a_&Dc z#GeGo49y5(H3;QjM;&N-p-^^HeS8g5vC20gF#dg$xAVvYlBS6*^jwD{eVNhZGKRzj1HiYQFk=-@k0$23Pn z&*Z6g(3oBbYwy@{DkixK3y}7UPM%$mmGII9YesLGKXy5tgxz@uC@#;Z^E4_OgNQE65D0U|drxyfrPvi*Y zzy=8hp9~TXf-w*KLv2Py8z)L5>d@gCH_@SF*chiQo=D0Dt4!2 zl!I$b+GRHVYrR=i6qBDbc!m)*5-*PZ-jtMGla@{WT4OA9nsTHwS;=eV3rwly8{YG0 z4koviqbUY{QdtZA2eTJ=Y~CgnR8rs z`-0M-MXVx!F8IRXf+_5y*X@t$yQ@1Q4dt{Kr910&=v$STOf~s4J8%a4;ACcb{+=d2g`kt1gs#;XYzs|{zADE$ND1fW+p*%%{&>zk zA}UEZS#Nyt^=W z|8j=-oq6ag@tXApzwAzBE_U z_{JQg#U9DU^mWDwmfOZDq-Qc6xM68=SfZtks$X6z*{WWvgw>|0*t6FpdM_LVc|3(K zcXfOx{n-h$qZE06drud`^X}qW|MvKnzow8gMrCr~E%k^tz0Mn{_mPL3ie=qI_0jAP zO`)at_UA9yZm&Nt#qhXVD+94c0_Jw;i5!1m0#68hmBy21$kBfDtu#?uM2&X^5Yf?g zR#}YG(F%8V_QUg|1Cf4^U>PVQBzk!y+)msX#WMaR5RPj?*J9d4klg{XC zV&AZ$#}5$So}!sU9Cj{DU4y%KCcvGl8n}Yiuz#w_(MTkv~`_pHITw1{1Ys>FBYO6t5s_2A{ zo{25*zU*zLPS-vY=f~ipX1G!CDvQblGMlp;_8}{g^U+@Ed)wOGHT$}buq2ogu{WUV z+pPjo;R971D7CQ=^!h|SHDOGqdTlocXEz;!ts!JF?VKi+*PY28{qA8ww*R2XBcRDs z;Jveh#PU3fIDK`Qe;b>a@=G=`+l&QstPsXgvvrQ6LfREdgqjg-kF_ltlMB1tO84sl z+++^_5_~8ma2!-2lxks=a=!Ga?*+o|a$29QT)$YVUh@!p2h#zLsRP+c(Dxd;!wluD zhqe@AIy55cO)^|HOCK}w3)^dbMjx2dtnpo)06%kvKqC3-TRS;z4fRgBF}VS`&)0|i z7qo4WUPX7tl^OK6pA5agUPfy}ckl@h)x@O|)CoB4(ll!^>NlfNvRBc38oZQJh47sVO zQXUp@G+yT55q%*#huu))&gmYR@ac3aRtTZs%0q60{f!jtDbz70<3zh};91asCj+0d ztEri-Xm}}7R4LCu#-ZVc<39c?v^-pnX==(KC8x5dnQsPn#}ZbVn`nWQi2nMEG1r8$ zLpwm0MtFNbv6;r<-39XlSK5EXDV9P`HjN~vq6`w}3YQqD0AtUFlzQnssax3V;lc8~ zssYK~ujip{c?dU4@idyg8iZfDMA@C#h1tc+zP_~+OERsu!*gN7IEr;m$wF{pZtUw& zWc)gx>*aAhaHCO|_XQv796TAt2fJEvC9aw~b1S@!MA$R*%eI*A4%BRbUR``%q62EQdi0OOLBkKc%Di4yL`RJx6PoaC>=+UJ48FFW6G8zs>Xo&QNMEt;A^@#T6%!vR8O)Y* z1IY+{8mTQ;rW$9!Bs+ZGKX6+lrP_Kc{wH4T|Wf{w<6TVxl@G0F>UCt91 zzL*OdNv0A`hjHKtCp#$s_IFWI=en914MOKolv}*)sy7#oVoE~!t;G*Iz%Ht%qwzWIOmeiKoLS+KG#!M9cdQ`AXOa|P0V33hibqKyaR+W!b){S(GisX#Wh z_PuhADK|hNppAsJ7oAXgwK5VR=p zsL?$`(esO$9J$lB2GU8gP`=Aoi}|;W8{}5m_$6TDtg)m*Op8&GAE!LYh`sV4=~(k* z^zjGFLX;JU5Ww%XJ$yL)arKQZCEicw;UN0uP3MiYTboWn@HV@n6BY5e0U0x6B&xl+ zefKVd*?CLh$0IjPIDuj&wFsJ5@F7Yvw7H=LaJeM~w7Dq-xVcRQJh?J5$DRl>2x97q zmDLC)PnQ}`FRBG?puzYcO;D>PZK%LnAWhJ#U)o5V!>s&_ZWRKi+;RYQAYJ}Bb&rVm zucGeechvn-&iyTQ|1G+i!;uB9Q<)s)&%vm;9_ zQk$|sk?!ojVApEF`hL}^G2EwdW;p8OUi>BtptD9LW+nc=ok zqkdc2hug^=@nu!*Y}kxR_nPmJ9S)k2GOeob`O~*}ogTtkfwphCN0>i;GguDMQ6-uS zS{|u6@XNCpHrTu=cGJE^ltE|D)9;e9Q|`B(?1(dcr97*IR>)Ow!5yOcV{|sfE$Hd1 z+8q)+qGA*y^3=BZ|21_L|DC$S9aOcQ%m1Jbeqty4BS^Wlxwx9^YPDh*!*u1)ONdad z`SKeZ;6#WiYgRNfB^`>-CSHg4j7~D`+l;zSbA`>o>4`j%7iw!a%Clu1P2v^~yHN_R z4gL5P?gUQw08*yzcz z-}$luj*=U2lzwb*GnCwEKAwJT@H4dBWjAiEKgSn!_zG`o00e?G`sV~bBILh{Kwe<; z_hX40>n|HDf19}dHv$2F8P4@z{3XLb{&M!Gzg*apB5h~+Y`ygLh@VCt?L=x~J!f?% zMaxA}n$m^I>CG*WxOvTxy)^?RZkl2iJfOr)G2bknq{hokrQ5xP(qlgT#(X-0Ko{~5 zl4n5v;`_+u7pnw{qyb7s=jV(@#Fs^MOQHE@xBDY8h}A-o~92+-u+xaWt;nRl8XU&xEUBV`f15o@I8{EsF8g@d=4J>Nbih!)_Zf^0;?g zA8_YR4BWXdqg$Hs$2k6zx`kmk1-{BUoQhKm<$p_zd)Jg1-(`OdD+9Vk+=}=(O&KPi z{W3TQY;>ZGyA}%j60G$E27(k9CmdRYQ(}lckR-T&57fTnh)A*`4&PoqH$@LUB*$;}exhpgz z{RBoKg;9ke#!<#08&Mk}K=)6`5Mc&+vyFrA)|lcRQY(<$G0t?|x8pn{H3X z_n6jy^3Ie4S8>%v|;&l=cWKI(jHYc|~ zXvk61E|}9>)GQvz#F=Il&&~8&^k*)o6&-jpI?d=irIEhb6n9lb>{!i25RP)+cPza*nE}xjt!56pw6M-hZ5oQQVI4+73t2i+=&Ai)iOa&KK0>VD09Vq z6>-SeT`=^U{Uk)3-CO2yo7rOCSP`2!R51$h0mmi>>;z9JCehSsRPSa)YON5AP z_Z(xYk;X=`IZ>}lZG-QF#@Zy)uIuDA0V9Ua6E<)muuE{+fz>yKOv1v_H%gypWp#u6 zJS5)b>B&J?p<=UnLcvQj+4LB)!VCq@zVM)G-vmX)Etf2W5xsV52>Vf=VQ9O#T{bX0 z_$!s{Y^W^g_`0J9QTEtKU>86na;%K;kM{@V=W$cBAax;oCX(SP{9YBgxZP3g;E6+qRYVzQl+A%zGSC%;2-74=@vr} z83;|BfJy~2!dCtr_`BH%NB43Ce7_Yyq%Ha7TDk%wlz+TA@Yox%%j-pKNmB z=APJtQpEoen~zBRuVS=#``_5KGX>o02jQiru5V3=gaDhSyV}Yn zCQhZ$YV?Yc1_PX*X7}%qU%$~FaQPZXG6bQ+m>c4`jbYA9nQQ?JAy>(pNHiNK?6IE? z+t(H2fOU%ZCz!P&xa@Mo$dE6pUQ*L%_+s;%{hOMYoS;=Nf@XRogoABF{-t^@X-H!ChL)ruQuso6Z?@RUI7{07gAOnXfbdGMybuW-3?I;4HWQf1NEo}O+ zxt&^`CQVC=A)wD$v)!eKFXnH^Xu)X0Xb?2_-SR7^6n>&yPmi7yL!yxlw!m`b=tIj5 za-Wtj6!96dAQc^Vf?Ja^k0N>zm6%*cDjJ8qnZ(3G36J8myf%8hjhXPoSP5l;5Vwoy z<}nR135kM;kXUwv&pRPnFcx1sa6fQLC^4vhcvysZEEH@SCP|&smA0R@wk@6&-yf7% z{*PFFMDl+XtMI@O;YV%lFBSd&-&nO3S_0x|6B@?eP}4*y6nGcISD% z3%b#n#izsxRGK_VWmtPxMEXn}kv>5|4Th)(yV=8RJ)7QHo1Sq(ZXCval5idCw(*|t zQiDOR_y2^|FI^|g2FF16VasjwvUN&OvqtX+FCGi1mYweK1)<_gAd>2YQcnpUeg7A$ zn{J*fhpVbTbdn5eAll~Tki_KD0VR{SEVxdIG4xhC5V_dXj4(*zXCj>G^(uT3)sGAZ z$9BOgGO=}5t4^Z7V;ghOxf^)%e#cV)I|>Jh+3*RwjdeRlWct@LO4Aoox?y&l<4HNB zSuEd8U&VEp$A_nU1V<|1dUXUWKnCG@CF?V+F|>xx`@g}}bzG})IBW%=+(d-hlW zU68o{+yXyBz+Z)~^zYF9rJvxp=>Dsx#p`EJOFd%!D`_8ahLNS%DN}1<F0$+Q zz>+Vg;waTjkp`-Hka6eAd8N5Yyah9yU%W~+-*=VK@OH@8#PQEx-sQ9)`JG0b`b22xT5RK$v+66ze@#mmHucH}!7SgSXex$aH9%SL>3I!y6&i47LGH_tEh zn+$Mh16X-&1Y1Ye?HTw8K}C-otFkG+;tdMtS6|MTRZQx*$=G&s1?lufwv9eqQtak3 zTGE`Xg)7)^8Bsn&ETy=JJbi_^t~1p+PF1QoH9SL<_<$Au^DyruIjuq+d$$2CC zULj_c9m2<@{iYHVyNj^?dcI?WVRA2HLC3sRwWSB`aq+YP-%i&ci4jJ#T%Jypefgwu~CljR9 ze|81;VC=qzK(!PcEa>sEhu$a$+eFH*n{ox8z_6(ll7JXO>U~S%d@1MoZ3tUn6CrD< zf70;!!RV>shd?DI8jp;^{Uhr-i#n4!g8;$~iIC&=jj6AgDX*C&@YN#&dngS76H*Z1SF(1YzS;TDhe76B?*;+ypTe6zE81`2AIC@82Bpq`g-pGEf9UZ z9J3BYV3xQ*7~8ofzRv_O1_}MoF@A)GzY629-(mbqE5q;Foa6uX8Tl!F`Qn?n&Mz#! zLiBQTpwGxl_bcb1NUaR(g_tR3gD;j%EQsUKkxELA+?Uq6PUpzNW+X>dZktt@_Ovd_ zm2wF_M_GEmZwWs9&^s4&oJ=j84%PZf4E1(_POU+jdG>jIDI-h?!}bwewizFn$D2O@BEH_Ys( ztu~MK;Tkq+X4r!Mb)?rlq`JW%w;ca(x7-xdR=Lm+*{IOqh)tm49?J)~W$T^VIbKq> zR6A}ueVN$;K9^XzS(VE8fZcEb5<;*MdR-eZ?Lt3z@>f9hBGVx#cvOwwplU?w_YTL4xw2dpGX)5&~Y6 z=EuLoaA#4Aouh^aUV7ni7Cqn4a-y_U&e7bQ>zd%Wr5zh?3I^i)95#vz?)}->wfEF| zYaeFkt}N6zagzMRKb$`6Jo{LGf4_^I=Sqv!I!&CNT)d(}Lg!3JZMHfQ0L0<3wcf$9X}zBFy$Ful&K>d%lJz;f>{<31lVbscrv3NX1xQ9ovmJ3}{e^Zp6tYYr`<3eT zH~elUYcd}?n#(_w#`v-*a+SIk0zC(kFJgLxJdrcJoj{^r4ZlUQKCy@WX37uO2YN+R>iT;?ej~AJnPLMt&W4zl(Q!p|5T$ zx~AlJGuZl@4K_8T!yT5qE$_L$A~-Ij!Y-v@tAACop<~9(2$Kb}3uX>Sv;K(!;D_(yWY7gq7~MvJy75W-CA?}Z;i zcC0wHUP*Tr&W&9`Q~!*&2{TQ1_263zQdl7tjv;Ud?X#l-RcUc^N`$G?uT1LM^#KRYXZBX~+Q{iBe$|q}-D&7#-<7Pc-O#>RNj@@eJNgxZ(6faMq$m_pielGkUnA-v0YAA{b#H_?5mLJD{pA=gR zLz@!l@cUAZ91$g}xylcgE`5A2)H(02>w^!mgqvIg-OBaubPhEr4SWgn)v=E=#MJZZ zi_Jk#;X?%ZYri$^rsii$NZ+lpogv0c3YW0cPu2yoKNZbjXwsK#pUi7t5_b^=xr%=2 z80cu|$m>9Mr`Hi7sKk@IH{zeQ6>rbNS{QCgn zK}`HP@sEJ>R}pXaJK}$7Ao<;AR|)f0X+l6)zr!e7YE1a5ALLIO1ALU|o>Zskj(rZk zCf6$cq*kv5=6tcG`sRk{U@Zr6WwMDh|05HCyXy7G9x%SLdWj z)zk}#!7e1)2q!_w!e=OzkjdF8l28|~l9hh{_UT=d8RbP)%~n2lsr<_m3s(fz2hF)>UbFAmWFchRkd~8&8OPkBkM44OkpX zO^R#F=k!K1i41;JPxu+t;pX*JOio@8f zQ*ArGtHs13;U$j^2gVf|ZR7?xF(TYHJI0hl%qY~L2mm^%Tt^7?k;aQ!=KAIab^236 z!>);wP1Ds<#~z(G0r5}RK%pJ2_g*o|c!x|nL_=HFj#W8HL4-H%ol<8ihg1HnthNMP zlT7sr=`Mtcq%nhS=98fUZf}y+{S2n|NU%-BuNn|T_%B>5P9Ymg~k}$ zi5Uxa2X<@r{w0Qssp=wYB=YsTBAJAhhQLK z(@|hixyex|Y@|tKSJH*D+ogOJd~3lwz>ACm{wn@Uf5-nX?JK|K|K|>3(9E|^##x^$|GKr$48RiN z53gG==PPD9Y=Lbxmee(Idl0vWaSV!2{c&GF>{8P1xY8CWQV8@nFQ^83p>F1479@&O zHyE7$%hyl*!d>RdJ z*06pPL#L9XzH@nJMMSA+!!~`c*Y5jWshz1kcXD}@9{mLvVwGEii<20^)2?jDjEUyt z?oAR|0qe-{Q61_8z z&E(G$;0D|pQD&@!wtP!p4tWB>mFX;C0t!8|-JSGYWG66+d8RDz8b9`?a(%dCu)BlL zBL-C&T$bVqW%kQ#9z;7}X zQZXE7K9MhQ@h189-vj@#<{_Moa(fH7IT6mWJyGu+=n(Mipw+c$rB_etZt-0}ITOg9B>mTKPY*G00ht}0J8czgX75Yr)0-uzn%t1bpPzAw9zK*UI$JC5HEjz8ax8k zUv(PX{N^-Z`=uxBw@Lng_JlE6ftja_8t{OcP~3qglr|{-e-*kD_ZhdmnJDc{qZPq) zK66DAct*z)im&Eblv--&5tK0OOeteG8F^Yk;z|33rW9X`_UClsKFqvA!ze+ znOW0aT9PwOI4(l*EfP%zZREISYy#D@SJ8!g$eMFXUfWMIRg^gs=qdHx6wRF5N+%Cl zfJ*la(v(rXY9yj;NVDP~8w2v`r`p-1s;mQI0go1R=pCoR)SdI{m{mDy-AOzVHZOa= z-8v7fMMJcQ#thXi$MX<+d(ZZUV=^xjX%>86T+2Z&)}O~gM5NuO25Y@uUcza+s`82` z1^zkI5)Lp|jrQ4gZZ^VBU@Nwp$c`Tz*OAVPws&r%)MCLndlQ|ll5e-P?+CiI9R!XL zBxRwKnV;Z};G4b%F5;7u-jN!eD#g}@y5i!Vz=_HSQNDJOvSVSXBI)V}ir-(SfNEMC zP)&PM^bBZExjGTRUir_aO^?c9NEezFWnVvR54`GCs4RpNt&vSX<))SEaxFrvEa(&V zks{yiiCh8f^nNH$#$J2jfB%s$>w+)G^)!<}c|mB6A|f&Q3@ppih6}mj`1=l=8veHz zuV&*6k%Rf+oaXn3<}B;nH#mC_g7!_u>lNn~EZ7s5axTo~p!e_%w zhnE#EB2xwm=dwWIoE0dX_x!vANRroP1q$aK6A8*Z_#lhm`hQ>%F2=2YwFsb#fuE_u zn*YVmk=vqe-#N~J!vTcPA0G~nG4@v-4ko}13eXPQ{}@8_b3N8C{Q$pdPcwS=(!7fq z5j1f-ceBTvm8RUc1AfuS;kAa95zPE3);RZD0_xe1cW*R%^@^zPmf`{!Aor^4>!-gV z))KWtPoUB>3)*les-rh!*`+KS*~P72%EG$rfL}3^G+xvq=txSUS~jyVDw{X57sCYf zAu);&t{Kh1vkbE}By3EH6G;x!V|ZVNUdzN*%-ifJ>zq)sB z`C#YaAt*Ft)S+db_Fm-G`3!d+_j#G$dw~_~v!Wk)dYlOt`NidMQe)Oij>@iUB@OD; z_lHy*ypX})tUr&(N!}y}kho{F(Ow~dvPV^U{D=J~Po97t+V3Sn3_Q;NvUu!&`(*zr zJybuchhK`!zqu*0e5VUqO3}T13hV^XIkjfCffCmL*gK)W9EAMVt-hmBd%SMa+FB&f zaAjP|YjYva2G`%+!qGC}Hb4U@n~*LVGk)2I8!H@68<@N<*VB;FN*d82y4mjZw4?4Y zqqaW{MbU`sW?%wu2&oiIpVj|Z!QGq4DPr(c*0G?NGxKJ23_fMw6PNl``Nvk?9DI?( zr&Pi`>!Z(zF1rH}=sx(yZ6@7~A0M1Qi9JbAR^u`ZtloLgQjq=aV?W$Bp_h>NTst0;WTaX0T z)Kw_(_`Co`z`!1`kMchN3IRRFKUByg&-$-Y$o8WO`K5mO8--}U8eNM$st8lQ&6QPFG}Ea85#<4N%)lt;^j4|Rt9d$S#Oqbq|^2B!TImR2ae4t zH*~uYNK(K@1=RFj;9;WePHlLB-)_0?lE`h^H83lPp&T1u)D0NRqb_n2wY45(B41j& z>xJaUl!{Mcqp4MttH{T=SB=q_Mg9o5a5>GC=uBEoZme+#fh-VC8MiKyQb0Rfb!Vh9 zY}-Rqex1GkX|{Hta&b43hf6}rK9s-c2ZyqmaZ?dh zaPnvHeVlI^z3{Mxe|RS&3=QJ zHUw#i2c)59v~{$k$UZF?r_qKIlakkIEkSJJkf`diSqZxRK8zjoB|I7|vlpdeR6oh-3it1bXOysHaEX%wMIax<~c&%ccHr z`q8vwUcU4f0=CR}gh>tW-}@U_prgy}bgNkIRUB78E3|+zCEwa_M_9*5 z%{G>qKP}2jtPiR!iS+;4q}-NUXZbo;Nh;pHVuWy^^x$Qe) zcf)Rr2JicMzP;ZB4y6rO=Z6na!ZW(4^IgzXxza~e%tUQEIufH z`d-kOh(#rePC;}6)^YoM-aMjTfyXSQ`SX3{OQgut5*Z}U{Mk<$o~jXLYj4b6airqt zn6h6)jF*v4_0|$B(#A~TSuzRVYTey<$q1eer`=*({%CP-t8P5?j*+90?59>C!9>R+ zL!xFE!IRrUdz*Cmb6dE|v=F->pfJ#D_(O#~@~-|Wh21@>uwSl?f9H{6K|$~5#trJ0 zB~49%2?mzYH%rMS;VrLQX$qv*d=&dfy#F8O-a4uZZd=$_It2uzQ@XoDy1N?z=}u{B zX$b-8mXPjlkl1v0Bi$hV+ne{?d(Y>^bH49A<2Qzbv4_I&A6(C~=3H~FHJ6PKGjxMn zpTl)(YP2F;WWgs5x&ijeOenM1QmTM;47RxidH?cf2dDZgJ@8&z3UG2cK8-AJj>j5_ z+Uon&AG0m@NLho{1cEi&B&KEsjrYrR6=$nyqw)zAwG5JyvqLsN%hW^cYwF4JQYtkx zOK~u+qP9>Uq)s)2n)r7Ox|i7O#_xCBKye(arP8fuNT3f;ISs?UK5(x-Y3$3R&%3B0 zpawm@)?(K`KuOU12^Wh{$jCno&l}g~6JInDIPmX`Q~U8k`g}6|L=kKaJ2PrmwOpyn zFT^6apiFQgnAhgMzim_?a^mcMnj+f(>Jxc72<2O$*23)ds|#!j6{AfPr+llbO~ecQK@_8XAL392BSX!YWmp#A z;e33e^;;rp#j$vnA;--3fds8GJD%65a%_yun^5)jEQOml;`zvs7-RNs7`)}F(ny&@ zWBU*Fu{-?R<4Y{r`gUKN!%Z{@ifm?{S?SLpTba(lSQ*V=T3OCOSQ*Ta&c5w`%<^Zw zJIb_x^nr%xZ_@X~IQ~)eeE|MypqGCC&tk`~-OzuRqeTli_=39H+=k!{VZTHcPT7{` zHl)X!<2v6wLl)FbfHVC#dw&yHI+^_yqj{rg0nwj`=euYutD2te3LXq;T^wjxxM&$f zT!|>m5<Q14-}9_!SEFEXoo;ymhP0C2I!$#B$HYG;5SqTEa0_rZhfORP zFEH`(F8>n)SD)u+W`k6*enJkaE7%^qti%2{XC`GyNrq;aBJc80%)_`uWaO2`vCNIn z=6>LQ&SVD5`97n8tQ6X%dam0w&qC$lf;Y|WjU(#=-Gd!K23YLhZRFs=30-h_|* zoI!@PSB)i#f+9LQ_LCT z&dXfB+YZiOCiFSj=0`bH!t$4O$n};|&$)AvJh^3euhOH;fHbaR_{gdc1+Q@X#!P4n zq~mtzHFRt29RVvI>lz&^nru1#ez9A|8}&LAK80fvo+9aqm@l;rHi5yN1DkRMnpuY#!Bt66^*5{ar74)<=SJt(wx~PD9zRAApd82J-Jh`ow7eQIKK)yQ4ob zbN*6+|81@)W7E!n06eLK{>nr=DK^eoJciAzLHg#+TA;J35LvrUW30dnMLGO-yQvq`o%&Nc>q*RQ4IvBtUVUkseSXp6w;OxqVy(3DU=bRQ^ z46iivT4V-yfYbtkUrVJDE*h$Ys@$C`{|GH`$8-uwOwY|@9!Fq3^h{nU(X!P}er_HH`We>FOfTv8 z!M7e841ji7_ztU8bOf%Wy^TKboSjsx=oZ2ruWp!lN^{}}4h;)(9jgv4x>w=2X z7d2V|mQTncij4}KQAt?@5|I;$Wnfc?Qcx0!05E{4&C1YbPX2mt(E1ry(9Js%$Si1F z|1Psn4E`U*tl87d{!+RAU9uS?Wm5=@_LsLI3RB@_M98L~&RW(5&EtCo4bOUN1Q0VA zWk+ugO93sgN7<7xGssD1;Cpsrn913o5Z(A z#Y2}>%#cY-pJ`vKe4vV)sER+OpjJzcktTZn>%!4|vTBtA z}se#35i`x2pA$} zD+0F`SHn%vON-*Vih>ZuzB%E<@1SEeH+c!_M{Y$a`cZo$z8?co; zFtPB0%iI!;+nX-K(SHN&7@x5lvZ1=CPd#hE5`m*3QD2irqfnM0Md3~2ef@QnC7@)7 z(8V)rX=OBwu*y7aR`3QXRgawyw&OWd>IS?nT{)M8(oZ(iAJ9eV+_zYcO9_K=h z4pY1ydn(LS94>|?`X&QCKR@Nh_XqS^br_i>aGqNhj^3Nj=D$wK-pV)05i7iTX41y7 zreuG5XU6Q%CT!+RsmPAkgr~5905uRnz?GQDxrtsQAm|7B@GPQW!D91*d zZ5NbK)#p>$)Vc{ZLlUX;ah;#L>sRZs>?*L!-Sncy;Ov*IvYfu2og3s5eEpF)54Mmv zubAv)GCX%odk?s^7|3$F@k!Y;;ehQ}4ZV|d5QJ0|08ZYoS;P`i>xCu1qKm@|N@bH~ zP-jyMQfH%Qu#P6y!)$hjHx%;j$U*q|a-p?lHpD)FKxca!a@Yw39R$PQ1^o$P{wUD7 zfvvco$e@4Oh4{@RuA}q_deaTHe=3efC2EiLhv#_1I)TO1aav`5MW&%69M(pp+xK4O z!ZQYRxwqncC%iLeea!_~^Db3hCR1+uiiNXzcoz3%TT?}H<@ev2qBh_aXv(Q1fGek| zZ@X0n`^R!64YQTi+1A$;Um6Z(dITo?4C0+l5$AAD8F{j|&)qic)f{H9tq1Xz@+-N& z81IRT?YQoUY`2~8mBQP$btTMudCv}V*iz7>Hr{$%A7B66{qj!RGWvo7X)DFRh1|&$ zXZhmJI);S2=)Qam2d#xY7c4H*Eu5eT@; zX#dBCTtxU$bPK0#R*9Z&sY4NIO{iL8owB#O&4jM@RxfG8UEkV6S$`v%ya`EG{SO(qRtL3}=FsoD8tb0Y+*Pu7dCEQDz;*LFb^nlSw4w{+W*^=c zEc4AU!bn&;XC|jW{6{+2dcoKOc|uKO$M7GC>7<#Y0@T9P1;iiyaGYXz3Xpu?q{9)3 zLlmI+io3k~>mjzcMK`QtK<+_o{9W#!0O*h6KKyC!f7u)P&Dkv-rN=YgO*d*VyzJ#> zvF;wyF7GiWrc#r0MP2-kK6Ydy z{W9~KTvykHkFq}NV-|;o$Tk(7F9Vuf3W}9UrD;S9_gI_|b6l*73;t%Y`?1~dyOOS) z?IeNmj-wHN|CMvM&*plP6A1PkHTESP7c9!vuPet-$3xlfzTXhi5x&NnSwFb4V_zdA z!aH8>8R|JK(erNls4hB}&RNjmTGF*3L68Wy{?oGt(KlwqyK@_{{L(6YPoqhID|Hto zi~+10)6my0+|DoRnhB_Y?|U*q1BgtBRS06p{%bQNN_=841~Sb2PdoNukP}LNNf#3H ztaj9LUf#@-R~isg>^QZK1EW%Dz#~Z#iO7w{I#TSPrz-Ylk(0}db4=q0)!kgt`;y8@ z0I+oGQ=^>p{06ShQE*Kc=P-t|jit;I z)PyB%QRpgu&EcNyc=3 zJg!H3;`kFcfF^)w`MV}Ofz%&m0^n&Ae%Z45oe9eS6anTc4M7p$pG^n_nebNx;4+}g zIC+c!vM-Ni@YaiM&h+feM2SGDPRK>td)I}Z*_bbgr7Aq4;zGYgL<{pEYM%M2W^&)+ z|97r41vH^d4UpGahr|8ax!WTQ3G`@=K^<_z;LCLK_4~r;$7JVA9Kr)f+ znxmw3DgnD1t^oxt{@u^3pLgfGB^Xg%+gv*@?7e5H>|B&@iUX$aTeQSR6bkuQe?m>% za7B-4G`IDoy1bHR1nWjHjIawXYgISMzK1iE;bA6X1XBRB1B(JHYZt!Z#8Z7Q4hxI} zGz^MD0cE*v=C+3XGoOOB+)i$i6h?=L-@8=!NSV;!VUyhj5}C;!MLPvlOO}xDQNhq@ zs8P)3sL|jp2A}2nXQQ{V?!u*bIf%O^$1nl&AB?i3-LAawq&7Ekc?M`?iwY<2rYV)V;i((hIP$}%=tZxEVJsYQ9&KM;Ky z7N_2q+|SuLP)*XB0GG~1oJCIbA=z&^^q4kEmf~@(PP@Ygmmv1aZ<=?mx~CsJC(Kqo z&tuEHdMP(#k%o4sYh)HTG-@BGq7eNtNH?^2O0x2%M%U$hIx)I_J8~^&SJh)rnU})O zJjfYs&h8fTAbQ<{ug6}gryOtXc)APcvtv+M8D4NsvotRyFXke*oi9hYQj#|RVhKG* zr#v>#_37pP<>`>O9C7HF%J{*?L9N2-X1cQ#c-fVAMM$Qf>f!1U*u*YX^aYKczS*D_ zcrNC1!$8I!jvjS=8b&&XmvSOJsQ;q{*og1OU1bm{z9JZKr2rpBDq2vImx?`qE5`h)QaL^z3p><9mqD)T{w}3GvAz!pw~^5MB5|{ z209rIx?X)Jdn1x(Z0iBNSheMWyx@9Kr*O`k{}m)4V#HcyR%`myY}4jxEzFCwa=vQm zd(U563)T4J{P?cZ;xOjErPYY2TQRlaav6GEL>I$-D#4M66of}%%s-%&z=Vbj8EE{` z7y*C+z~T~f92^Gz9vQ%dNeJU39cI07Y+)X9Q7zm{Xtf0minwoSto3UGf_hUt!`tXY-rHxe0>?PfHm-QRiovLEAwF)GQ>_2=MNp7 zO7DrBAF6r{67;o!8+S9{~D~%P@n}rv=Xi2K3yEdnw?AsjI zYdDzbSwu@w*eWP=tJ=MZJRpLhiw`ZS2`DLCJd~?yVHNP)>o0qanGcK|x%*Iy&`yB_ z*DDG0Jp9(h93I)=2mc*A(63fz3tzZ-&tf~du-p{*^WU~;xi6NndXFE|!Yxgu;tyRT zUfC`p>WibGy$)gQBJJ`wkhBwF%=+$;0P_V!8g>&R##f`QQeQW$Gi{CNtI+pnT?qz) zj5*)K=b^1jLp?&u48xt*FWIvyle&%$jKPy4gx#z&oN)#v3-Wkb$+zXfk=>G_f69SYjILOoe&Ky93~tB8xUcG(ZnB#&&Qn$%(%05?hdOh`kL{9 z%MSM)V9j*L(4;R*Ve4A`(tPfcpeG3Qeoe+=Y)0`-DL?Bi~Nbpl@z&G&b2LtvO*yVDnz@0yPtH`x(|@lVF!%|)2@lyPf1 z6|OTmwbjLHzZ@*8*z~B?SkPZY0~`f&A`)cUIk4U^HBLlBmLXdTS`tskPAiu>7;O3K zAU7aeidvFQla@)fZFX+@(tf1PhGz@Haf#vGMzTa6C7LGA7AutykJnH7#MdTDl;T?? z=mwb-;0RxBK|lJqQ1NYU*T4kI0|M;t%JT$;f0R6cr{(!&zwdVol~@6Xq{m}2a5968 zNB-eNslanGG1^RKN%=NkaNnp}7W!8D2=a?u2_^g^Kn}}%v-JYydZJC1SkG{8JEb4l4ymJ@RH+*@_!1pnN zdO`LSycV9_{U3X{!qfz+*c-;W3mW_uR7l(JudLA_f+p`3g4m0e)O@hIH)XO2Y}^@h z`-T$D1)XG_1Xy$GvlA5nGbSuXVx^?hsFrZfkaNDCZQc4Nyd2&yz=uCTddcR|<>e|f z7jTwrAr@B+nY|vIdw+V9loiGwy0I?VpkVZv;~h2mverqV+jwx)aq{ap+HD=P8N7OeT2d9eAx@>|)10riAV zMB}mJ%6U?X@qz$xfPZ34q7+S#&%&m!qr^}LU1mm>lC4LIx!8^6 z&|NRNsV;7-oUtL-m7iZ^WZxNnp&CYK#vhOaSHo8z3&_<%ahhB!R?|zUyH;r?0AnJ3tiJ<+2qP`5~yoKQf*TlbZlkn=<1BM9geXOx=;*YkNpz_wTY^c^*%Co&V*F#CqFoY z_xmLjvb-#YFQ%Ldv!FYD_q-Ffyz_;RBdu5i&*J{JsZx}Si`e#@YSmXpZmyki78KWF zRT=sYlxfEa!FA=L#L>(B9CJCFQ3WogG3j;fqC`>gpJh9RF3TM2`SgKCwiw~{2#HZY zuy#mC5b)3f{nKB!e?oB-zlI$}NI?z!@M*gn8O2c)v%QQ9m)qaKiDJ!S7b+>USW2%y zCM$nIjzCOqe(ap&?(L1=c~JUp`T(UqT$FF7G6h&W2^m!SScF*NY~*y#-MAApV+GQ_ z%WG9C^#DcM!darXtO`5H6LpFh-{@i!%M+=bdUq7_u?oK^I;c9x5ZTsd;T*9LKp-a~ z`@#TVeEf6$^#vUS-(*M-W|tp!hoERYQEw}yIdwa{@botVTnh(khqAmi}H7nCL{M%MaB--)?u*)SwgY}%q&HQc5PW%~W2U)W57c>6g1-pSWp&L($SUA-T<^BSdoiLSsZWE{CamYgWoZN4@k~-z}%I4kX zx#P(b+Db6E2S314H~pRoq6#8sU;%4MvB=V}Zlo05N=wR?N@NEH`RqYn6K+oJ= z(pChy7au@TOp6s3QwZo8h{eC_*%QG2QJ$qe?b$DzuD?4=6)R^0EI@#6BdH8BGWh*f zfoM|xs{+xuIGNWS!FWGrqc{EKd3XH4=KkK?U6T%~$Y$u=oRtonH=v3KYp%B338&)T zL|0dh#q5QN77pq;ixM&Q)y4rWMEi9lF_MFnNlo9+H%%DH|29K=HYb6msPQ- zwz{!%3(4F)$DWnJnk_(_bHKCKRwkHpY9Zb!>d~Pu=5%V7+XMr4=D1tmnL9NfC-VO` z=10Jv*3#m2e{R4K7B5g$({S4TB`9l7#R8_`fgn!6)^eV}!K35M6KmSba>;fvye0+H z0kZfz>k5@8Ff#4>S`1bpr?tuB3eX7f2uKTP3s?$x2!shFD^0J)a&+72=gLYbbeef7cv%K zIsHi27l&~aW-x-_BDtf;7utdldh=;Fn22~7s}1?Zj;$>Wq!R@P9|m5C*WGxA&~Zzk z(+taJi}k2z*N2|V>o5=e)P*%yG;hoLH}$0TFGEr?b8snH-~tu(jaIYneyp!;?;K>k zKj2Hb%NW>Gy2K0l4EN1fF-k?*yxlx=oU_oaCU!#kF`hjJxQayTD*dWqG%#ovNAT25 z@aGURFupK}C=f4MNMXoKWs&8FvkCLkH3akp#(bB3H$_%N0HN)nE|;9zdBAVZk$ygg zxGYT#B~_q$AUyxBdQUL?N2z!Gw0ghn`~L2HIIunrJR$Sf25PkgdBnmqaJKXYgauWa znoMZ1kynOxqa1@h_ji_2$^+Vx=L*0LROMt6pm{SgN>$!~94_p+`T_@wiff>G@v$s7 zDQ##K5{Yu9VbPr)3pKejHG|^~OE=Z|_4O@^MHgQWRDtGI7jtnr6h}Ilu<2adc}PYB zp9r0ELZ7P-ZBRlj(HX_9pOBA27_TWdnZkEpBzFkp2p=P=yS->^TtOCETpNi@8(Pxi ze{f^O{2H3KIJ0lIKhV);wTs?)_ka{i!Bu$>$=Ue!zWL*xZM6-@?oblGG$)wYdV*cD zee4Zq4`+|BAtjHGJf;AJ0Ed9MfVzOGzv9gjYz~Z1%#^o}sb~N^WB`cNf`a3A8cJNX z09XeV*6OyeTU5M@)uc(?*#WIN1=#KqCrY2yY`V(l40T}!TT@ugsU2UBv5#A1;mlIN zyzr@p@fAy#86AqXHccbMgb0v@2z}!Y(S|#|sm}meMfu*HZWis%JsoEj4MBHV*&z^v z2Uk^PZnF5ZiS&LDe=0!Dt*4QD@frOjd=~l`zL{AiCR186nN}~?Nw5qNbRL0O)|E{M z+uhdLxVK}_$iTYN1Edr>T$rz5l!CHpw`uM$WtLLFAm#+~de|mh3Nx3QX`5-raP){` zA+nwASGw| zG8+6Ur(yBkAXVA$&?w#g_~na(2MhyQ!19bbO*9B zjm{DdKU9UpnBfa!x7hx z3KM|h|I#1TAI%@#|CK+6Kc+uc09MDh_At;F_^A`ex`#L?SWj3Mnxsx!S*)Ac5mzzO zxe8QWUzJ*&^XnjQXTVv(LDAb5aQb0zjNv`B9IuoX%vL)W@i=1J{#-jeolot#*`hshe!qPN zV>(u;s)<(=FJ30Mni@@5=Sw_@G@um>YT0QzFx}9v6uSS^%|6%jE=HNx!jV^L*OQT!GkS8Rd!L%HT%Xw}tv*{s=ick2-DQ zmcG||lM0@*qJ_kZ_&e_!h^>ok8d5`drhoF)F^fpVR{Dk74234iqR3!-9<6PiUQP|Q z=;Dg?ddMH#ZdOvwcVtY*N`)joAf2`?%cFbGuY^}zUbWT?-G_e#oYteD7G-~~%GDIp z?9n9GwDQb#GUjU=#_gr)<>{5~)$X=jO`b{SJTo7(eEgc{(whs)=OfXSuqbf) zHu1_x(}Zp5mE#*ViwAQ&9jgcGy(R12cS}7Dbd8m7Pp*sWtP|eWdQ0ji9wz0IMe3_O z&@`oAsD;a3WV0tYdxK38MN58`i;kg6+N(FUaJGfruM9AZ`bx zE&RntKtcQ?12G&HN}xZxy0N}28dxR*w4ks*mI6!=Bkqd~hQRikUEhr5LSkkHv=`qD2kl@yfc?IC^|oJ%W?vL3Vc8_eo2?$7TRwhq#M&S}G`#L3P{ z!igfGLp;et$}=Npfa1714n%p{QK`>P~zg^f#ToAp(Mm50wumnKuL;A21GnEE z?b=02atm*S$K{@$>mpx?7UPJdrUp>COebIiAg(cUzPc+2AqSEQl2a09QU}rs(o<4q zG6!Ra&EZ1Mihq1LY$WSKq?`N>iM?&v*m!xL z3r}yA(AaENOkj+OZyg`M8I+sfi>l5#uS}oUp`sMStj;=+MUdKdbR_(~*TNLL)IpsU zLFEV)Gw5`k^&*2lz~~1V_B8&Ns8s}xC?;^f%umQ~h0&OZ5nvg-pMPeD9Mu-$i7J_;DTgTVg+}~-9Z?yD-e$=One>C zHiD?rD#XY1Mo^GRSWuKnTu_opT2PirUQm%qSx}WpT~L!rTTquxR}}bZo*635_%D6` z>CZX6kqs?Kaz?u zw*Unmibfg57H=B!t=*~$nwg^wF0v~@?B3JT<#KsdFe&j%;#ENpa?gQO+3Bv8?wwQo z?GF!?b*D1>e(!?~Cn7)V7Kdb8F6r|BALHx0aK4|%r&l+-TYB^nSw?&mE?y3r=m#tE zmVKlmfXkpOecQYc{)3}9vcpp!o3uNep~Nkx*JPR!)D|*T5tR{D#=r>sRTB{Do52~a z5$ziNCAuzpCi*HGD+Xw*1IPbZ3;b@^!-~N8AKsP$y``;mGJ@yoEPSrBAEw4VG9l|& zbvISr5)aO^h}YBaDy_SeQjTMcdB8plFS5kK>IzYKZFSK;ugA*9>8En1U%vv2aC!JK ze)&3ADaNFLqHVyhtVLb|LL=MP<8#-QnlR6izNqWGKluSY4TG_ z{81(+0)IdJXN1p8?BCj&Iyk;{e5Yq-ZS>9-cu?5E!pu?cmwlh#1Y(t_Hd$bssX$=c zvQGH;^3qDSG8t)>5v{`T#jqgA+;^h~igAzz;fDucId25P9xnF>T96MS7OqYXPg%sPng6@b;cv=JG%&`nP z-LkYjUz>O-Ggb=|E4swrnFvMG?H_P?^ScUAuEwIdIR|S-ah>hg-E48#9MaD%jP*e6 zi51}~9_LFVAK3FKbbbN2SddS>|8Q$@-{icysW96zk~==S?6wu&RH;9i6Bvlio^46)CO_JlY5vt>2SK2^`0NYJc(n zFrK3y?Yx!+sVc_B5cEl-Gqe%AGY5KL^3Cw z_1?uL15I2!?yA|9MhLfqcgLeJ`zkQ&8xcDN?W<3Btz*|sG4kff!Qxp)D8WgD5i4aZ z>5_hLt_xWtHSH;xsByTJ0s1XM8n1El$l$dsF3*`FB70uk53q%xV;1%qHxpkJ z1?5AtRP8qSgPXugHEJu}?Yrp=*r->Ll$(Yj#3PeQ4`3mg4C4gnu4$JVb(&@6J8#yF zb7X!g$ZVoSQa*EP=M~cF+1xe?jH_mx&hO&jz9y!1@=~Qtid3$v$^J2ZP<%QS15fqE zM#6AC&Xi?aW+^quTj1#A`lzwl-5u;wq40xavjv=gA)HIB-R5eciagf*n{&noZOA%r zl>?i~k;##D4|^Lq+61SlubE%xmF#l2NiJUo^Yvvj`Wu_F;Yjc=(B^+eUU06FAoE4? zl>=685+X%lYGN?ppf!d~h zSh_+dZ$4szqcvTPOEAr}Nus+39`doBm(}j4Sa=1*Q;W`!gk-~GsZL=P6JH)fixhy7 zYy=)4U{>aTixan7BP*CAKtdcIog_XFWrra5`MC-f#Aw=Qh0$oKC|9Ra!wG~7?pVb3 zDD3jmD(9tvwV#e$Wtp+x2>bU0%3d0(%+0HHHlK5xf6TS>+_GJ#c^e#WVcx7=QgMa2 z8z=lKJZc5E-9I?&*cQq!%9LvmQi_hliiLIX8&x<4i<(m<`$SY+V|3C53r^nA8E%eH zEU+L5;4ql{n#z1@aBx41@bREtBqn${YZ;YDl!%NNPlO9)hT`_f8?;GlEaGhG1Y{L7 zjQ=sKPmJ{+#VV+U*S})*mn!>j%GD}S{nlB)7JrvKT?*`$t@=X5GQ=w8InGm3EDI_X z49jF<;W7-enbkWDk%&ayFQcy>4(C0R-`UbDH^pdsw|}{mw`dDDO~3a23Yde*P-(SS z&AwuK=bcI8*Q4rZS@lerpeSxW9dSj)orRCur$hHhw-wf%rIdMdxTJi~gD<9jC0kTz zKp|SxsGRw8|95S(rE?NTo897NVF_7#X!dFESxvPQR2RP(>QTkQ~>dP>)i0!g*)UcR5rSk;~hu|`Qqx%PLHSYdpiM&mUI$k4KG1HXAX6e)>F%uzkS850w_v9L)#TFXVGf@F+YItFrsxt69tvC<*X>yKx~jdgii6^5I@bX&KAV$1;vX5cb*79N z04^?1p)M8XEZ<53toBmuM zzw{XzKH75%UJ7t|S9wVNH6NVIM~X{s)>J|5hCl=SAJ4u|Q1C}loceDl{-rki+eEfs zc9#K7Wa}#AHQ5&u*}ZJa%9k(2=dmK;j6eFsBur*HWA|7&3EXl!>lWvT4=(QV?34Jo zlNF|<@4ydM`bjbtXdrHQzn^bJM8<|CA=OBJ#aIU$4ROy3C!B?8do@ELz{*ZkX&Ev5 zPQ-Sad6tpmIKpDu%=3q;ggsxHf{K!|UHF+odLn7&L=HgQ(roIyQ`uyvc-dbeL=m37 z@rJ3&_yo1O3DqJO4N(m__v1QD5Q?-v6YI}}^t1LYtzK@SRP0{5dBw_O0akKdIoD1P zI-8@DfnB0}J}S2KRVJ?c_2@oxVXQuSd52-`qr>|HsOBRmw8~k#UIT|OSL%?#uJ9k|Jqq7MvQ}j8(8?U>v7gRUY0~TlICi9LwWjRYt zD2RRaMm!b4FQqC*06h+jBLkxv8!_dK0czf=?-kjMx1BWHoW4=zD<)I28?hjv0rs!m z41E{IdBoG69q*UA?mXXncJa*a!+Syp!qYbgZw`C{z~Pb5UZCNtbLn6He0-se74r0L zA`ocMSpUaBKLNxa1@!2@0s5D+=DI^hSnbEWpnNj&ElwL znDH`cg!(e9FSPq|O-!tb^;)$Cv03wdyaY=!MH9E-LBZLHlp<@iot(s^?RH>;^0~$l zuziOd{NjArr=wENVmOT1CKGzDTPGi{JQfG~h&hB5Jryif&+iV-!lkI@M9}jZmF_eg z+W2#40L^gNXqCM7y^74jq;)*CCmi<}B1_&Lo-^L}R|uPm4Ile29Jeuf5cWE8!lZ9Y zL!r7}FxLq4Q)wHku1Qm=J=r%iI%}R$l@*Y4 zF38@t$XcLdbjbsiQlzkv8A#Rj<)nz*!mxK%xcrq)`5y9smW;+Huf(8eXfSslaDhfb zABsi_FaD(Oew)K)yjk{~z=H1RsAC-!Q1u)K)*4;GDJdxJ z#`E&5kX_2kgOWZK5nE1XE~eV!C~6K$CoC5U`<5kzF~xD)fbl9JTB&-0iCkjFqJ;u> zGO-00X!bD0SZvqjwT6SL#FiC?D0N0kvsym}5fU+uuvYB8^#MkKZSXCzHZdz*w>Es! z1;k^K3--lXu1tP4_6nZUNn~Xm(xiz^JLlVONOV9ZQ!IbpedhZf{@jKn&lRiEEI=^) zt6iyB-_R_p$PQou;;v^ZzGEwGC>^N^sn&FGDz0s;b+EBpeCw+xgSfN*3Wlurp!VXNaG=|2sjBHw_)|70HGGAnjWy`{FN*)s7k_M7Tvo-{&rKE%k z1FW0yit@|za~18$7JM?DPR9;1o%T)l8)FmUN0B%`3*MSFmyV-O!|uVZLL3Wk3U3C_ z2hXDhpazh$$y)L#a4EQD+Gn10PQp5zJb*|CC|lE&14#!#@O#p!fO!00)YzUP<&Q#L z@ZX{S%iXl!qTU8X{juuq^Wp5rlfpBkE=TEVmC^Uyj3K zAetEz(zK|SRs^8)NV7s%P~Rv$60^z`88E#NgC&)9J2Omk99c@CJ$jL&XBf47cbFa8GFh3bwPv1RQi2&v& z;E^8hr>b6^u_7ig%o7IkR~l8%mD1;Rx<^QQ8Xu`We`3J7Y=+eh|rQM%Lt|V#$+N{qQ%+3U2>2qS=ddf z=p8m*$ln(t+55#YMzmjf#D2@PPNCAV6msDHj5`vN@SbIV40`(c-t*OG$8R>@Z2HXm z%!A`Vv*AF@fC`iz2eLp$GpC>-g@xqW;=gYJZixdq5Qh=~83`zNsZqqz6@ zcijJSjqG=On9(wz`&e&4H%92|KD2FeKfX9UXO(rKdamD^q>dvEtcZXz7#zzmY7yeo zR1+R%8ci)PwdBuhgesnT(*-Z(N zm{{=R_Jx(MG>?oAv z6;^CeTQRu#_T0f0zF+b1h3vY_un0QvjtBLce~oAk!(849mB!HG)&ecxCH9_#yK%Cp zd9W|DSNX=sfeo_9-Nn_$&*K|rc_BQo?z#S=tYx!oj7h@0)vrPfaBg60a{k_UzN!z0 z6T9l|6|2Fn#6?Rc$aGP;7s}?elWeRkr>4fhnSPXoy|R>Ls*<7d%$gRlh(?CB2%(6! zh^5GCDY$E9{wMY;3l0LUEozhIz;kRiVHnW>ErHqyoWz~n%VA~4aGJ|$Cby;?=7jza zKRU_}I#IvF@`H2x@J4Y(xe+@Mp9&uc9|X4tx1;8u=8#j7Q^}3Xj^BSs8X57funC$P z`|D`jtr{}(C{P6uB>%YzPmuISsZjmzRQP4@@3$&|LI4`@HHH9I95#K+e}n)~wzHt3 zgLyL+WmHr^UDgRA&kq>v-aG@^Ja3Q-SiZW?e8p2U znMfNb`dj05eT(A}Y>#LABdCgNhU;4>^*(>t4H${?;U=g9Gs!7lm8 z7xz6MzEV0|cpo_nT@ zul#)4wG+XX`Y~Fs$yEQ}fJ&{XkQEW~y zwR!Y4ff-*@b>2g&_s9jM$M5-Hr+vEP0_H_aJ6j3u6_YVn5PnNyM9IWi(O1$yC2}jw zEi7qe5Op#KL^oym(gJeoGP=ISsnUjN4(<#V=#P_9S+NYJ{PZK&^;F;UVMkZ@dlQFm zh*}!aRgsdbXFP9k z3Gv18j@*r27~xNIm7YIVd)j^4!E?ZKpq*drdH7N99;IDYxi0a3+w(glP=&{=xqUn& zGFzM^yA3o1#Ljoke_pcxUl^J6f~ff9|R(Y z=pPk%?Hc_VHb0X^WQi+yR+g!=%Yt01pmtGk_n^Q-C5+BQ6gtovxN=B|tw(5JX<9n0 z<&>Yd4Vb9vf{v?hDv%K|*St}(k$zEs_+sIi2?z7h7bKhd_}AWQcRxr%BqY?hEBhEI z8NL}9!M?LU@maQ9In4qt9pqB<;cKjK)`=T%_@}uty_>C1LU=fY)qVT8cF4Z7)NNb79QpYFG51$tS$5t2KYU$uceiwh zq;xBtBHb+|-QArcNOy~Li-2@@gMxr`ih$t%y!2V?U5`J#@8dn(aSz0;bAG;aT#YoLk8O5PyXsac3$^vE1a1PYDK3q@mHX{_pwt{9Y(B)a zsSV*%@O<>T_*Q;4yc;wzQKTWBe&8H;N+Rc{V#3yecXq6=j$Pt#g^w+_yE zd3dA;ia)W`g8bnzKp((}qK9H+{#`-G$Mx`gpk%USFvOa4&IZ|n7}3kv*D3kE`#Vs` zi^8P55ze}eK^Tf4JSqb6_3!zAF1w8&NPPyv(Vwg37qIr1PA*C#GU;dHNQUl?XcW|kf4qkMU#O1+&m*3q&3f=epSn3Y#1~VfkF1mI)$1~A& zb8A)Sj8co@`k;9qMiN(5x72^>pKN$iFPyMDdpIkQ)IIMOcIeNUVdcKwy)-|RC{pgL ziuJCbpqy0O>;(>i2FHCM(B2bu*n$`o_QX_0cBRTC;by>~#BGrf`W-G;;CeWO*H1dX)Tc+v zshO0}b9W(R&cE;4iu@umQ~4YAputVa#J8oB2V19ty9+gey+=tHV<{Q~HCv%r3TIL* z&s}WkEv?JD)IH;x?M*C8KbfmEO0QVDw3ei(G-_847o;VeS*h!2G-Nc0v2zm0iHaBZ zQf|Iou7WY%vogTs17Slo;S>?HwN$$(K!t zYF^3SFS7ZOL3|)E1zJ346KxX=gyK-ut`r$%&N(QZy#WfK1~eOJqdVT@lU!|U`e^bv-^}pZL z&mihqqQD8c^hM%&X=Ae=MY^p-WW`wDn#QxcU&dP9O>JpRVv-1{0wXtYu&Yu#j=`sT zp((1JheTXYD__MP);+8@Reud#c9#Pb91v8PFa{oFnVO07sHErFS4t`{5q|hknS%OQ z*FN{L*W*KVh^_GowQJjk*qZaD@3uzxyE|6@7XCR;X4!DWbe(JC>Wh7sHI^3esDt@> z*5y&loctYYi;p2CLF|&??1=` z)Kl_9J&X(C5cL2l*^6L^dfMq%kK^vOCa?v8iM6wv3?tBh2qm^bi1|)x;*#9J;4`pQ zA%Ukps;Ojb4q5`8RNxG$x{BOYg>C`U<&EKm7yitaUw*a;vy%2$5iZIdL9ly8v}_S~ zV=_Uyk~L<@y8_lAMW@H}gZm7{E@oC%C7D*XGs`7=GAr{koKd{pCAH89E_>kc8Kd(p z>lb@Q`4_NK?9Z9*0dL;cZ0v06zSfPkgiHp`@HUunL0M;(vAyE?BpA)qkF97f2EZt__a!C~6KSpR zXw6<|l=IQ9@yh^CTi17|?aMsGXeQ(X~RyIS(?R?B_l$;gxi1}ho^2UN-4@544*vZW@VDMFe{*q5zHK` zB#3DAGs$7erB9a;w;t~`ptIGqo&+tV#)>rBSA8UG`R=z#7p{?ZIEqQt<}eQmP3z&< zU)?0H3?p+H%Du$KXC{$=R5u=w&3@A;2U5PmM?lJ#5*YXs8MWmt#C|$n%lYaa(M7l2 zjGQa=E!+Ip)k>n5zgU;H|YnaT32Fm9mMC+n7qwA|971=W$%dUhb^rCIi|Mwlqw z%KEWY@*&5RdZ-;&<**H6%j!T(irUKyklM;%*I?<$i$Ii-1L^_|C7%cBgweM2!Zr^4 zjh@!b&zfintBusvR%Gcbt4fnIWG>kQe1bdS$maQ=iB~d)Z3K@6VOQP3EwX+D8}Nd9 zQe8@CVTlwVG!RrNGy_x`G$B+o^aH36XjrI1Xd0+EG(&PAX^fzbralkRll$d}$>sV2 z{62Xb2t)_chw%q&5=Z2F0#_tX9*LeU73}ZPX@Z@&OVf(Yqte-) z=2{6)7^@7KRdTV=X|wElYVoF5(Y0tzFREQc$6I^khjNga?ijJW+^H#H% zg;hN^I6<_0s!WvQtb)Rkj zjCDG&BF^sXAL;4#2n7KKKw%K}{#;?d0Q9d?nEbye?B~(5zYU(`;-=UE_wNdK)~01( zk3X)1nmXQ)7aDgT1DWokZk9`Nqp6ah$FEQ3t7KX7>e<5FC5gb$INjiq)59KO&v?xG zKrETKlB<|3bP>F8&sc}pDC$?(Z80?{KA&KBJ;g9LUbXLpn_44UUB7g0HFj=SzOU+R zL6x+_{Ly9EY+>W%(Gr!64y~HU-XzsXix|&Ttc~@7V!IkgS%-jg7?;u+BH8?xB2TST z3+`cA%2Eu|dWmwucVTjeZ%Z3!7P^zrH>= z8+yNSX;C15Kn2ul4jxTRiLzA$sLv?fSYn(9hi3DAtB+g!i+zx$?O*3vKItJ0b9u2Hq-d7v4nrQeUP8^MWgZoKgqm(kTD+e-ugfh~p0DqEtn>z&;XHZL zkcGea=s8$dYq8~?I6LF=cXNUH!}u+rJVz*Xg*T+i%t9EZ6IJ zUt$L^W)$+EHUx@cP#Xq?Jh+X3Vi?>;K_L&-Mo%#e)yBT%cKkiTXTvUmBm}4q!s8#S z?pM(MRjLd9H`V=A;Qd|Dl?3q>aU}oaE4FwJ!6DU&w#qH$Ea2)X-#5X2O&yhJ%O&-# z9g9KO&L1?GNtMVc@c?B2f0n!Wc_go6F_jfeKDI#oHM)nluS)zmX+%EXN&a$NxdK6= zeLICM8^QaPr)=8%Cs(-ImvaMEr~My?cl!Hd;=Cz~ty-d6+1u8R#GG=(x{D248r}wk z9I(#}MLH}jW_g+zXI!o%5W}coupFnw*`Mi$eqBLRJ)xt8SvlfSnb9jKg`=0^79t=yV||v=d?S~v5HUL;tZ}d-ac8KsM1owDnQ$1 z!?wR_-B&z8A+)h_`apWNjV0CBn!ifc@Qvps#9I^sy1gE4Dc_(TL9%@P-9R%;s9_fQ z=a*vDRD{0oj=kPkJ^!|J#Xs$yeqre&`;`?|Ng;~p)tKazd+tD4k)=XQ*~0ZO_XuA4 zE9=+7atcu)&&#!Qofr>TCLI|@2*-N4=;s!BEY}R9gO)MtT-aCDN?&sD=6kbDc~V}W zG{ceN%4@{8pdp7$%yjSzz4(%&kxcHrE8fYaDzg%;{6b4#J$SK!irT!Y6e9!Ct1qir z$^O8oopDSzsu+D1)=M#^jNx(cwy8QeS;*(h>CDI*G!MQLk}T%B=x6E2q-Unbpl6}SEMYEj5Tye$gNLDgpWa5G^NH}mg6u#UNFX=a`{ka#VbdK1}xPpx~CxVbi9IIQdWc$jE}HRE0YR7QPbu59GG@XbJSP+xRI`u#p{k{pk^%hP)Iux^$k@k zB;MST&B5c%8Ata$#U^*3Jm2uuPZ-u|RkPPco9fVU3<3sz5ei4Ww_O}cd{eH^FwtLV zTF#G3q^%MM$;QU{D=N zmltu8s7>3_W{F0s)wZnXvKB5c)R>cIv2dxJiSgA4wKLaFlTmYK)`XvhVkV15WOtG< z7bw|fm+s^Tb8``}8IDw>*e&~WfSz*YGv(IR?=BH2Gpsz1U(mjIOfK#0V)WF7lLvNY zd$8+y*+@uU;N|Y*tQT$J`lOz3iQCL2(XQSFO~_|)akjm2Ic~1LPq_+sQB&`F?-vpp z`7#v8J&X~}QyP*>rv#y`<4%JkfexThppF$Q`d%~uFp3eKiviqF|9=E_P!3q@QeZyh z<1SDJ+{I)LAL?#l?rPTeU~d+C)<_c&Bm~tz7vwK^{;LFe{cnQ&sj&T9K>~vh{>K%a z-*m}@hxySLsy)5%SdZ^5^lW@EA9%X-dhI3O$p+=UG_Q`QPGhelC{IIpXG+aaGnciL z7zY+Q(F?vxK%zZrTM0|~B#KdJZFd#Pj{3yu%1ucw`^t(1ie5a8hPk(grvo2{wZ;Y% z&W$OGjhfDv=v&r~j9d(jXhI#DX5R-qbm2{&inZilyxWZ#@)dNGv%}~$5`5dfv({IO za@D$n-GPI(<@0e~)CZICDWh3(K8ZBhR zC9@9)46I?#cjD7yC0#7p;_sFFw+HaRFOm{?rCGDnYyy!VF-{OmK)Ut?E_%?XMDk1`2R=7oWU;iNF4G>GR0Q^%mZ?+-c@lAG%Ku z9<2>nDt5XJ59jZS74Zt(HBM7df8l!bo&XG3$Q-=&d@N)v7*%9dnES~0VH}YiVP^3v zFy}R^#d4~1I^jt7j@MVX<5q%9$gBMvpg&%^q z&<;e?GC?1>N|NAw+>sAWUQB-Z?yt&TC-NHu(u6?!=hFNI*?*NZxqumi|CEQh|5HW% zx6&MdQL+5p#gu@OHMn#8CU54_^WGxzshPt0pdJEExr0i*vE0z^>SC8 z^Gflsf-Ys#W*uJj?%vqV6I7WcjP!9X`N()Xyl3+NEln(lG*^%SfA#-Lldog#NX5la zg(lRpX?7x@4KCz@eRFbA56 zq8RD!3XapCoPL-O_oZgD?QXZxa(ge;!268-a_7tT;V0eadG=g3O|n%g@1KO(=NTz* z=W`7TPV2*|M4^Ao+Nh zFU)vXq3S6(3t8|Mz3f?!vF>w4+(}2i_xg2NZ45r6JwN;u|NN>LH+qp*KWe|auwM!1 zZYM%uN@-46XQ=YL=1gS*IcxB6Z<$?C;Sxqk%#3A)RF1g`bKWd=S(v$n%rgttB14nM zFY=mO9BjK2Wf+>A_ppXfw-pffr=bPjccQArF;=G!Bhy0)=vUBu%v(kOG1yK`Jh_d@Su|@AO@P4c{~XXebMJc zQ^@@kyazu9>O}Lm02`ura)PUo*B3zZ@W;`heHeEQ&}S$?PEf0u5HG!Jkf+^vnBH+J z81n1%=cp1fc4UO1ePg_g8S}8UaW!~1)Twb34j3OzwLEm_ z!w2}ZLmE#4_37WG>eNI_JP)ld=)qu}R)@#y)z>3?x`4V_SQ_$#rHrSkWQ!KvoSfL# zZcQDqp|RGAJm7TZp0uf|+TMm!DW8>&dFr^q!oxSWRbgR*T``s`X;CQ_=ti_{ zSXta+M?Cm#9xfB1Jk8Vd6H%|CaFm(xMnO5? zt%rd5P`3)-IuH%Z1qm&~e!IWU^&%Cn+lrkH)ra&)^3^p%SJxGgD+J^}m+LQR|EuI$ z@^5nesd)e*)nCgMGIiSj=nA)swYeXCGB&BM(;s!-?m)w)(Fp#X3UO|IX1`*f%e-GU zixxT@!g_o*byD^Yvq`p@4mnY-!NTM7idj_@$1zvG`R*wtWUaaT!>=EmGQK^a9Gag~ ztSy>KEdAU(6+1nom2&vr*Hx?uVqUqUps~Fxl$g__IC^tbH6XO%$_cdG2PDR;*c6y*Z3|uYLr& z$Rqg)TjlP<)A{=QgNNyAj?*hr2P?uPen#0NDX<<7nL=xy<2^pv-PEejtF%xrFbi(k z$RWD0Th{8JgyqQ-F43UP2U^)azavE-@{4-ccw|79Eqv>4XrgwBJTq&*-EK;*c&czf zLArr{8I|;$ZT}p(eC;yI^Lj0M&ZxWl#n?8T{FfE#rkEn;(XlR-3`-7+p{r)cx{Mv` z(H7nc#0%i5pVf%DuN~=%yz`#*kX{S>yQ}m761XaB(Nu%9wmm3s;tf?rAnySP! z49L1`>)IiaUl8wAc-zcY+WYU3^N>}bI*4~Z5Ir)KKY75@DJQDNeBFA6w-49$pi}Fu z;ajqG<{W$Cm@0Z!ZyUU8aP^*e)5(dVWn}>xbnh+uGZDV!IM_?7PCdP|%;uLh_;0ED z1Z(=Jl$Lq@o%|ZypDdn>MUz)C#L$o*z$apZz#uRI2um488ABOP8LJw}U*j>XGG;ZJ zp+YCEKPq?$)KK@f#HHzzkX@E5y2_2{P1qxlGBgGQXct6@D9R#Q04{+KmjrQx6OeLP zDH6fcaMC>Si=F*&tHa`gDF6GLDlx$Vd(t&Iq?36ymp(iU&@Y7SKiBUs@c*my`{Cd8 z`%}-t?}{f$kWskb^()TS{6%UpVgrYu8oX4w5_!S1pTlQORo8&MAXANYb5u*S_Z9>u zIn6gcP0A?RkFwm8=$;{NJ(vI7Ihy_vDFpVd+LW-Yzu(y_%_XnGx{0I0kkL_vNY|84_~HH7V1gzc3Zk?`WtY zA26AVq-cGrc%tHh-`u$8@b$`t_Q~cZDGT|iEI}Pw`3C;NX+xGu77lnfHppf(%+=yc zB-i}$HH8jl9M)r|r6;XI%->k>AT>(w>#t`4F%m0mraEu3F9TOHds>&p?)L&qPLr@+ z0rDOCk9_-#x&ZlZ0rHigqr_Iw%E)dm$f zwlhSzMRV=}thtYrcfjL7LXMDG3{RSChw;ozHRDrZ82r7K41TF{luWAW%v_h?!_E;! z)}G;aEr-2najzXtVwq?{PI=iJ8<`z>866wx9C>MwSj9|>-HHie;c;O{VIX&@LDXQ&d4T2dV@a!4y;<5P(|1 z!(JW0>vpdVP%#`t)17dDiswbGp`nSdvA%uxoRty$j1mDULm>ZiDgQzVf0dL;fH|W7 z7~%fZD)GB#I{%Xm{?WaYKGE%pf@2xrnp!7T%hZx%)=P^e!$Y%V8F}YL6Zs0!P&CWy z!Su2Oqrw9I#B5(^`4iU-q4JdVp!wPr^3TWTf-ftc5}Lp0)56^_Pbk{gDb}1~cx!qy zSsIG&sN3bXVs@;DDbS9%itL)wPBQ`WJZVZbex#Ry~K2%h!mehG~4x zKbxTK^|5@mJE6b12B)xwCc z44jR-PjY4W!|<|{gSU3yO)=07N1iSG&D+qVg|GUI zP50X>>+UB%PO?5-vWG&PN-y@^SR{dt1Iw&tmuHSn`FN7RTZF_+*?bL+5Shy8mKtmB zQl|XH1)f5_`V;J+QcEAkWDtb#A{i&I(nqLx zmk`2YDQQCVncqDNMSq7rLI-_y!l)-JHQfU+UX z|GBb%A&kFD*&6?*?4MdRey8l@@2g!x|Kntn7i98T1%QdGR~9Sh?Yhxo^LfOd3M6F0 z>{Wjg)@h)=FA7feHVrwVqLHB#E-&JrF7&{itHDlDF?;pd`0}Bk;B7{ZC2;_Qxk^i{ zL+c}~lX~tvyBMq%4?6|D&z_p9hl@_+tBD`J6b&jyL>)S_q51gj1KZC%+k0m^qM26u zZkuB-8B3LJ?YyZ78_}{oRf>o|w`1dnPw|nyPdd~OYZts~65?r@Dce^t{ND7fIPc7o z$E%*OB+n^<@UZLd+YhGGQGB;u2S+zj+;;}(eHArG*oz$tEnyU723*4UHbbk`*;r za%qOvSsJtFaTR-82qw3%9B6Lnq=OyH zV>Eoi$3Vb5-;v~h?*hglF zdC~vphw3NaL?E8HK1Q2!1t4+={(mm=Uufj75_$Z;i2UaX!M}^7|9>Kfc;ZoYNqj8q zhR_WeGjh*V z+KGCM2x4z-)Ae7?6|6pysSGvc;~mg10%GYj0o7U;_g$%7-{ngUU5bIk;m(IMZ(_YX zPTf3eGTq~~pA6}|{$aqo5@dHv0cX76mA>{&+-B}mnomG1?fpHL_Tjq=xhc|5sI{n@ zahP=!f7-(Dt6@7c%Q=`7m~+TiBHgazhHdZ~{IU!Prh9(`)3*O>!O29vwc(qTw1!yk zHJ<55zD)8~6L{2ny=C`o@>D9r_v6A*tCa1XeeWEg@z+OHhR3b8l4@oxd<>#I>mk+0pc(ahM5NMCCRPI&DyY10lP~D^%0bAox?QD*nf=Ef0 zH($~4GDr1n z?P{^g3F|m0SQ&6Ox&m*gxxXy3qCB@jaDEO*TVFy*WKc`O9<@@@d2#&d(qhL{=qk3N zKv>K9Mb)IX3@O^PO>~=0^kN~O1QDx=uhf3Ql#ZH|spD_X`=!oaF;Q+&eQ?IYy9(ZW zV9_JZL%#7P-{>LUN%5zRVFG`UKNtl>*bm)LNDoC%pb1-TY)IOf4!k`fh8qOQ!Zm=8 zH%0Th!}hrZmC&h$kH3n-fOtWYUR1(AsmV|Y{7c~Vn2K2x=5Yl@KLvIfaoD|_+!!kN;7|z=u3bnZX}>z3=(pFYWDxk z)c#HE{WyL^u^Y06#y9fuSj;!Z$9XC7qKx?JIh4FTZ=c~AW75TJPzM(G*+FS2?WNhf zxtjH^lM(A_ee5t)6|xK)G(mjFOQLjzl4~pK*gWusCy6(h!`+f}e)isrt$Y^^4{UAq z2{d7a%J;hYyO`2b#ONuP+rEACTng%`k=7ffPYWIJh5u<5&+k6=Uxmu@KcezeS?9N?$SDOwsv=(_UusDhypSQm!dB>w zA#|Y>*vLy}jKK+2rMTX+h3`cxUvlbjB4_A5rXW_avg&`(V-IK&aRwTX2gFlB``mZ+gqDFO0@dWC4Y8Bwc86%vkqDGzTS_;AUvMTW&+=2}ZqH8c4nQag%&IXZVX9q4 zN%f5Mj-E+UDi)jg&PTlDTGU_FT9-!499gv(^%!-mcc+GHy@4{LKSlV8t6%3rqyk1LJ{o0=PhLHolj>W&C}VV1Vxl`KEt}%`ZOuUxf|i z_0sQen4iiEzws#^$Nv{L(Mf#NwCT~roV${)=6HqbR+}8jP!k6B0-anHietBY?-Et|knzn$5MBsQZGeCoN; zD&CZw=!OM1!vGz^k!lxr!Wa8!lLXN${u5KOoNhg>6X|@+XC3%b0RcC3Xek&>_^DKh z-94O%%u0CsBVaVkMjs{trA&^s%ID@_1tz%^71LW<#6XQ9drOg{Q5lNm+L}f^J)?3{ z4SuGV+*vMrEq7IK9b`S+Or{?+@NMa~HMz@}>prEaC&yjQbLt|UU3%q$7*19sF@U6u zzJtV$K9AHx*27eN(aG{7yHw^cA&vzA1o^^$2+=Q&;je;7@*g4kDaZYrimcgxL&W}n zAnKJ0V2ly`arcD9qCRusyA;@V1e-MB+p2Ku`n(;6r%ztjPLB8MK1pW5Xu%^edM4?L z*^0;>8m1^V#Q?6k-BItZ=fcc#%l}-7FfL`#>prfi&UCGMy!ss ze9qs9YV+g<7>15&endtK+&*~babd$_40!^a=vfA6O2w^nRP2(Iq770)&!KqQOUx7# zPF`{$c+v>x`Yp8=iy4sV>pS1 zrPiyx0hzel;i$)8QS7~veyl6U*>zSl=gSm4q;;&=Ng+`BzTl#8$xtws>IYAUOzKFw z_56**^U^nJ@VWd+tpNd)f#E4(sRwT_8S+X$0k_i9-0emm8Udvi#omYIR45Ig+MYq# zUCxZ4P1BJpwCZCnW+cMo)X0=6pV}m=XI3R$)muedRmAi0sD)s)LGT^6ikuClIYT-y z$5S3hE1feb!m%nDXOJ9wl;b_gd-DS)&ohaA<_dDVF)C!S_8jE5ZHCC09+)hW`%-Nf z5%>e*1^V-QKm6*9)iXh6058b5|3kcfaX^0+UXYx@?@iuM$-dutN(n;{r&{XrDpF;x z)W8Er8xgUfMhup*rCgIQA{S3#l24K9i|77y<;R(e%ngB!1ddR;iR7^Ka&iI>^g=A% zD5OY(c^fx;1Y79nYE#A22d#1Jq1zf_D~#ZpE9=K2^;}QfC(ss)jrmAnk6<&**tkjR zgfHZx`<_Xfy;EV}o|K4T4A@h|=LxWVDssWBZhy`CULI4dr)y=%a~rUF%hUOdmlu1V zPfVau5*|~(Jmph|GSB2245Zw!D5u{|{t{~Eci9{%>s2wbMC-=>_(L+knId*^>C~{* zp|erztpe%?IZwyo7HY$V8I?V}wxlX&V!*@!l%|*&(AyWlEfy6Ku?Jwr>2%vgK<2JT z!bJPV$dD8#k@KBgzn`IBe-%bLk)>$2Sv>Qn#cl#JV#LQ(55li&6L8gxZHzpom;^p{ zU-fA9y{F{QT`!q#25vDFXhzvkq}=#jCUr;Mdd4|y&4){RY`U%{hj6Sgu=L~XUW;Z8 ziWoAzEx=|{FX81k2LXA8dl4;s7c^l~LrRo%mgiBG+x3ZlVmBRk^PVC48XF6=ZY1Ra zD#pn~s4>#gCixK)(>t=ocZyZ=$`{Aw;Qyy}N^>P;YR`_GAF8mu0Xe?Bb zSPwA2E`7dZG2XrSX14Qir_X*`t-7sMSauy`41NZx_vfdmqi_~A72EWm51a=_z(zm= zow4v0%;Wf9zb6e~4EDJs0O}w|@`u#@;@ti!>hOU1%A}y*Wek2EC;MCDIbzpM@grq0 zibnAD<^Qh3Y?cx-e92Stn$(1(+~+!-0R9<~xrnPKU>ZLF#`DG3fB+ZeN254w-;F1` zn5A_N7&TR_rho|@FrL)vF`E>-ucS5PBc34-z0_Y|C1U^I<1H@Y3pI4YAXl0}Z`sKq$m|0=tR_G%r_I8(-b@#gQuG07H23 zw>vhgEw8jTVPbq=pR{S5+FQm^e_3`9(SNexkR%jyp%tNG&BaAT7!yM)t(KoQkrMl@ z74}+faYW@nJdCE6YEf!*)ZI0AgYuwefo5@KOpf&VDD!vpMDKhE)?j+4xbB3y>vrLH z`$T3dtG!>4F*|@>`}6s0i;`k!WAIbdP&kU(i0%9L1onVSVN0QbwrmK1ygyHHo*df?ptH4pKtBmAofRQ_iIe|j|an`q#1qK>nU35K@T1QDvh0k&rUEvbHP_nU zDp;&UOTr>KRd+E^T&8~!Hj4YrOhl-xutKgGuWajJ*haua0Pv7Zw@ZI;qZ%pj>?*n} zJugy@x({xK+0s+13^$;xp*8CTLD4F120M~U$>yO|ZS4lZduO6ceSO|B`^zI83VL0y zwn{XU5yOdEXf}>?_t&4>^sDW6MA+|iy;832in>RdvU?>mLnB5XSyXUNr0otnZ}B5f z?$QSK%eeqG=P z_=eV7m|WY=KHut|lgTlXv7o-^5-W~K6<0PMnaDh315vIRThEb;sYbkzEM_^u@b?sN z2hUhjkr~TLQsgN(rc3hlS0cn$k4@`7c^0*iJ3VorMWAASe`i(8>k{}#a5gh_ZshFn z&K6m?>uDJG$7dz<%>16FuwwZJ6{N)*=g2K?AN(pZRek3mcVe_lMKb-&g{V~tmnspH zLUNHTXC^27V9)90VprzrtI<@}YQov)x|KdWldSQqv#fQR%zDPJ)mwYe6beV`hPJ?w z8BWs8g!7qUF59k$%Y&IoMW7W4;Uo6Dd-vgyYEM=E#U`Y+|cA@ovPXLk_TM0;uX zt{qmuJCL~NxcHVK+=LQ;qOeGI&=&m+ebfCcaC~~bIqQvUetoKTEOZ=dP?-_V;B~DL z?)#79;x$Fvo`a!7X^F92_4~4Dbfau5z0ZKnM1#nIr-@XnPfBtWXHMni`MR|2+f`># zJBXW%Hz}WVZcH7PjB#{LIM}tf9cH0l+ItS6%iGsr)j7uRCv#?j+%zD@L~qB2cIxv zG4k+B6dv54u=6=hlTbM6Cu3qRKNFK(B!U-~zznsN&F)M3Qjf+~KJ4mhl7> zGZfs=ta%%L3#so%&fPKnyi>lTnp3mJ;R>J%ImSOl^%rOTSD||M+)%MH0nf7+w< z->6n!DFLq*ll9B$V$yYHpfMW7P~(9S|2OCF3^#0)hQ6GQdfEWDsJq(Xp00N{_fZax zV5&F#JM;?PB~0-p(3uwd<;H^{$StZNx`KrE_-rAPKlSk&8n(sSIz3hrbipG&-Lk60 z>eSPD7QrLW_C&|jq?$y@tfw^_`aX#vyT{4(nV#?S_I&Uia(n1H0+)(sW~>yal=;SmSj&CU1+qobu+SO0 zR5YBMCg`2jD&acVZ4XEb>`i$FDDm??bip|mvmfI0WQsQ}4iZFj_65BN*nKv7`>l}K z$d_06c^nY(P@`)c??q&WaaPdI^>uL0#cp&$i)`I6;;uQ!5%&Q;DfnNjoJdEKh}y*5u_aC>sG z`6s@{rI?RWgQu$ZJxVp-X6`a`x^$_`7VV`(6r^qi)ZS(VIt%wO;`);PXsYBgV1ExzY(wx3(+Fg9rKxc1RI zcbO;u%)R?`dCv5h5EcF*3G=gKNwf5CyN9lG+Du9ee#*#&fnp)DQk%N>>8DwzrN?p$ zUzy75j(`H53xCRkpS_=VzerDS5$|?_UoM2cnH_wHSY&Tqj z3UNta9-VD$LdCCw6str{jn^PU)=L)O1R&i79l{sjbo6~m7I%Pt~edpa5TrW;C|ElMjk$Mfb=W|0{zU)~=N5ZviXQMok>(cw>wrc%s zeE6j5syMPXIbQ?9QZEp4SD@oO?nl)Ujs?*NwrYq^$$~HL@>3LYcRLE{m-0^=#lNgZ z1-50k9 zi`i7&6(Z;2u*z9dU}+57w;Gm&{Vy<-lNuDK34v;rXaFR z-+1d`!rqm^o<|y+UGk&y1x2ouuV~IKF`M zR4}Xej?K6!35&$3%WYlX-CF5^kt?^bi#*8FAI1Tl4;%rH2rh+Z06U@YsHedbf|1ej zV3>t8Ax-U5KcQ?2e#GmagiEmkR(#IhHu9Zk2ca&$mjKU@v;I?_e*wf_#dF0!^ZZlw z;CH>MKlY|Tc%I0LIC*hg4@@$i{H$ub^w>$~0@P!xEHMvpCibFYV$y_AUuB;NkkiVn zCkZz8b!hml>&J~VYCZMv-(nMdvu}aWO$dYkDRi4yMM?%L8y@yHhuV4DAZYjLiUNZR zr>;vAZugSRY<{o~uPafk_M4bE$Z{+#uM({#tD;vz0{sPVc6yf|MZ1)ECRt~AlfMs* z=CoVC8=4Z%pz8Jp=7jn9U08ex~aI?Da{21heZjxpw`F$i!*ofHtn0*j8B~K z>$h67(_emk#=p&)7uEG{`dwvIT2yn?o$n{(k3M)3N8zhj?R1`M=T(+AOMVU6?smP5 zM(Vqd&8HK!7UUIK?FQbhrxR&mWp|Y?7g5%av`HH4Xg2jv4i<`R)-h5lJUn4FV6IQs zE>$e6_ID>-osCQVWHJ|0dEkb@_Rc$^`Pk;@<$X54OkaI97#{L1kpOIeFa?+rBqmBE zD%;89&jQv05rREHbt{zqf&TaX6a5+eBhi|ONY_R>$g&jU%1;tT(;2B7k<>27(d4dR04!UPXA8{{{R`&MD%djaN;Q=SF{ihf3KtEMw z;srJO_}7r_)LZc`hfsObE7|1o3fV~Wru)^8XX~kjL zt;{PpWE3@?a3~yI3fQA_+qXt2&1I3BE*ca`yA^2j%eTvLkG8})NU%1IZ~j2M&rw+E ze-Ss;-A_DR&Z>e(M925oCEDclou?U)8d!7icy}$hH_bC8c|IMO+|1$8 zgyx2J>!JNbN@8RgXoB!}F5kWjZ;S}xot>6-Q4JA&fga?6 zJ^ZlCn`F3EbT33INON-_W)gja|BbB>k_P$nFiM1g08w$%fPRDocuG1+%rJ<$q8 zV3#3v4=sjsoGnE|a0!&k1Z@7y$;#o$sk2w~?86ojX)uKXC?6r-++PLZkyR3U_I_yy$` zyg$RLf73C)9@kjh=l;Qi6T6S&re|#BnPm^Tt3B;3;_4$`!vllQUuz5=@nmVG7c9^+ z@LG}faNGzY@k9sK-hF+FBSCd@MW-O3U)YLU(}-%j0SuXP38UTh zBpleoAaxhiH7&R6kzKTHf5tJ=61N{pAAu!x_(t$xaYh_(zT74UxoWr=)j4LShpW9BGy zmWzK|_(A?sBeL!8*P6J zHnuJ`Q`V~<+HjN4g=!TC$)+Qp+=>?+Eqj#)%UV$mHTs2PYxZecd3-p5*X`ctanHP4 zSMod!q+K{Ay9;AJ-?tl+D75ccTy$xdor&v++m8%?sN20Vx1A~QFxXoVz2lqv-RJ91 z+sN3MYj7i_!up>Rg)%ci;jNtt?C z!XPXjt5JxY^85RCd~w#C$n6W__EqCAt*nI(OqO;nTnN7rwy@|3DU3y9gWyH|9YitW z`QkC+`Qx$TAH-t4mtqTdvq z^}fFm2NK!WxRRq>g@vme7&)+$atRG$KFFT&raXAoS{zbKmVQ1`5w9++guWImycdl% zIn~N&=^pPF*_@RxNXk<&R$6?s`O&3O{C*aKl6#9xBxQ^*Q z>yyQmcUd@i$W&vz~QGP7BJ7iHzWt}UuFw=U;;99|*=*75tZXb1hV8()M4 zi$2}@Dy2)La2>peo@0H`U)LI4fB2}rE*l7QUpSL5pdu(|pGs*Q3im9f}$k=2RS>D77FWz{v+E!ADsgI$csmCIi`z;mMVO5)kc3a`Nua_VFoiD}D~AuF0qa#>V z2zs`Q^@6|{w|VasQ=xXLcg|~MPFJyByUh7_gsm*mZhL%ay?p`PwXX=D)>J<~&u>%w zmeSCvp|q9YDBg)(C*dxR0j&R&4zH)14el38z|K3C6bp}`;|ZSkG?ojW#HP_>*ChRR zK*p-n_^?`d=MDi1qyQ3w$swdb0CT~@aQJWeQy`2}V@QJ2VFTGkr!d5b0*meia&66> zeSdT?1y_B*%f|L8-Y^Tp9GoF)oLfQ<5G zQ$0HPvM1~Ad52`1!9=IAGdy5C-^9n*A02a6|2zZQ>>ZWf1aPqjjT<)nnpCmLa= zsbSZ_g0+ZpMU8%AY>p~FK=%JJ_m)9*E=txg?(XgqT!Om?cXyZI?!nzHxCD21CqQs_ zCuneo;NRxlx$igkyvdoF_s)+~yNcRH?LSXduhrda_0t{bgbbD>=CNO!(}uy-l@I~1 zn~?^==!xp0BQ#cV8D2HqX=`i?yH}`f6TVA<$bREMiSCMGKY-vGZ*M(ftVL^XbU3D4 z*p@w9pp~$DYb9jgT8R>ndRPt<-$vpLHZW^p4(rgircFMGxo>~C zuD-$zXqNz(yoJ!e3zJ{O+20B#LVpdDKe=zen_Wqeu}%Mn^sh2*sULi%9uAm2)(6`-_woSp$jKL_x4!0TN>Gs(>Wxu!JGpR)*fvLMSx`(_V>YIDd)cZ) zfh!AVB+X{u*+U&la%&P>PMEL1c5p}Z=yAA53eZX^TqEjY@`^w7R05QwV@4UmZ6mZb zo2Kfu1%nlt?@kB39ib0bv$X8F;~1XhDGED8w2mqrlB6?Qui`K&TkI5?EbIX$% zN*PNv<-UoHZ#(do!3xnNrN?hZf(0b?Q=?8p>5#G-x-Tq_Cr!#y9e{}=25@C+=~T9o zH0hKbpVs4yZD}%af5uY6oqCX=Goj1}9jCLhG26r7Tv!U`ymNDG6D?&b#h%MrT?z9f zwPzl`%xAdja=;F;&YDGy2?HJnGR%1&ixu|XJZ2V~iiz3GxNk5s4mHnCOfYg9o1BT- zOxa1$*136QO)tv^as5Hy4iXVa9%PdkXc&kGN`T5Y1o$1~xd0G3Fab;sZFc~0B7_HE zC5Au@#Ciw#E0a9Vk16$y)ju#p5N?+G3c$=;JpH?v`9;wEtuW*D*O>W}+xMH&%lcnZ z3l9i1u0?_BvTmt$+ee!>@%61C7kU$;u+9e}JUk*HW6U5>MYr;}R38g}e2UPr$51H> z*F6!r|IprUL#bH*L6C_&f8C$EB^O*eApvKLW5!J$hZ#~OeTF^@5KnK`KsHMMw+d6 zUKLzrDcwPm9hoROF~P&UyJ%Z0RWl!frav=SUrg`CHGVoMh}R`KD2UTVIq)u}8ZkD2 zVP0aTxQXh5&LM0yVr4MaMK+!xx13{CnudNZUbS{yr$lfS7|1N}n5rcm#2sQpSg#KhO z{>GuRR(d<9( zuGdMRTqxkDd#l}b^>=iJ7OLIkxEMaZVTeL8BN{|$6vLvXn_3d(;$Sx7be3j{z$f#e zl3>|hs)JVt(nm7p&OK6BC=9%(zW=I8JoVmru zP2s~x@#8xiq$2y$A;4yqD7_G@aC_eW)4_mjJK;AB?AfgWK%^St0(%ikIIrMzI+xDz z+P$^U_3_%39f7^)xjAbYrZKl7wW6g$O6y>Ccz$|5+$n>I3Pb|L0wfHi1Y`*000bJ8 zij*oZJt;8`@KK;)_lXkZYM$p?0h9F)pO=xcbOC4K`=#3JVfxc?i@>oQ>TU3kk|}=pYv1Ejk)0k z%QF!bX*X&&)HN70puPDPx{At4`Z;S;2|eo_R?TH!IC24{q?mG6adS^M*JyjlUW+Hx zG3XtlAW%1|FA>Nlwg3{yIT{f_JwQML1LlE-$^{%l1Tz4V0}Wt_E%Fn@;qZk0ZnSBk z9}98G!YB^vj)RqNpnJ&wHpOv^Q=&o$z|>p3{kxd@MNs~&Fm?XdnEHjK`72NGUxeb6 z@2~&ABCt{lDFjGM;%owMIwqH zM~=iIww%yX^jjsv(dPy6A{dGUuZsl3Pk%nLT#wC;Rk5thm^dhX`N=V8nz*$bwMKS3 zbIru}?c&5GVp#{ZVGT_tnQwWhnO|(26;g%U-ENNhU!Y*!sab5gP) zH+*<}JfE+xMD-KP15OtS=((u?P0yq?aoS^uGg1cRJ^i??`imyy$z@x1?|}{sQqD(Q-$0i1Z!Sn^oPSgKf>Sh`q-Sf<$b@$Z+*cOw7siEwtkvd{f*ecvIwXCLY|yo zG-g(1&21^1noyRj&Gkdj)I#dZf^})}RjZIZjx0mDsKK>g7?ST9y1>xND4$;?QUDu+ zDRNL_p-WH|DU4+YQ)0P^&4j*#5i~go4FwKEqoaPLob~!=sb>o)^}63mJ-fG3ZyZqS z0WKtl0CT*RdX#`t53sU70662V)SG!L^}JzU2D&YBK7ewD7}=Px%{HSP?avBe4=0wex;E% zQ5_2Skp=f$eWt?Ah3g8di5{ZVu(jtwFMS0cLLm>XbH#*n)vfod{U_=8l@koLHs;Dh zzEcL7_V*HwhfUkj#`OqAx-zZ=UW9Y#fMbdu?5}RzQL}{M?$3E1UbgR7j*7AwU;t@r z?ydI2zOiEt@qoRURdtD zj#6w4JJ|;!DIz%{B_cHPi5{Qh$JLN?w{F5|^XEztsmeV; z?pq(NYRt8(CAQXDyj|x!Zb&z9cb)!vm(b!mP;;}Fy4HSadezoTv6c?+wQB%tnWbdg z6f&29Bi1l9NocdH?GZ8;>d=t%JOoJDa^N#tjOwS4)=SO}2Va9M5Cyq`Y>-Z@)>J;a zM5RUB1%^hLO0$rIwJqqim}(RTrp4tk4NM#`KaoZnl`hR(yB7%g68R!ALcI%gN7@q_ z^pAx$Kn@H-x+K~WQil-SQwKL74Vt9gSZCQI|Dmd$@=Ur1BX`4dS(YAK zmU2BAHK!w7}5lLGwNPgv{p zF&ZvyYtyD`MKav^y$w>X6i4H|^QVL5PCS4&1CkZ)6gq^{idx9o^|IJ5IP>MBClYKo zvO7Dyo-WE6#}*_!tq>M66%frF&6{GQe;Qo`OUX ztP#kG=!k5Jy~jMNl9CUZ@T(GWd*>e|;#-**mpDbu{;KVA62Psj&O%vVT}-CjlEm4= zE9(A4DSRp(VG3UimQs1Rt{Xc@ zsL;?bi3iTRdMHwhVD4PDB3{s**tN=-&cM^XY1Lz6-g_gBPd5jl=PUf-!R@uNl=`slvLKexgp0`m~VY_Sb~u4f=V*v7bn%K}RfwQTZ@e&z6q)&TaE)wgA3=Rbfq^&h}P z|0nRG{t9?ky<8IG9rqRERiZDBnJl-C?7`{Zo_}Vjp3^)uoFA=L73k4)(Lc3bABB7Q z6LW%SfVhLCg0z4vf!u>&gL0B`8VyR0i~pyOd1kr~f`1lNGxUk_BukT$#LTp;0iiT* zudJOG@iBN4wxqo*x-xf*>V-PycvKLsU|(pM6gRyHHPrTSlvFB80NhMZko3u9%`BE; z0QYE*i{tLGaAHt7aR1st5?si;wVK!>Bj@DC_%Q?Wo}XtcSB=gaO@%R-rM)JHc5%y? zb~Vq=A6=5tbn;BqiZuX-YY+?^2qi*1ls}f1YE)3ss?;)UJ3sN)#LXS|UOVgw`Mv{W zJ(zcaBrripdkQJAUm}I50;yq=kT4X~p$xEt3XoJ4V#oYcLfTH-+pQ4G`37IQfqcn; z;DE&;x`}-Af#5*J5xNC@$$;U&#WA||egB!>HrH(ZUa`mk`A`0ziSYmava0nDfn-8t z6-^NU!?zIqH(~gzIR0C~kQedofce`?_O{M8#*R(~P9}QRCdL-d)_N8;#wK=p_7;wo z7EXFhfAZmeQ*`#LN!f0*A$9JnL3v8RE;5^mkHewn2*B1it+AQgiXztL$pq?*)RdF` zz<55x*O)Rw%b|M!a~Ld&$-v~?m`sqooR zWvi_0?5j2FxU*6=Tf)10>y{6BR&&Di+9LB#kLS+P2#-6Vd!EtaUSX}1GP(Wb8!r~%(}*>1m1IjgLnXDFHGKkl zpN{6Aw{2A$YhL%yE7A$AoyCW10|P6}o{qHqs#=5ST`ip)Y`Z!xPsry*M-e)AuevdL zAu&HwwG6TXg`D1HLA7u0w=Toe?)1d%W2vi&UU%ceXI9AVJC_k;Vpb7j?qgvAks6+( zwa%HV#y~-^9D_&4-T0Hsq2xMgbu1l@%_D#J)xAU(oJtHuQujJO1~a0>;_g|*jw75z z%0kS_#1t?T6WCaa`ewu8l2?NpwT2u7e51>Fmm)8hR|aIr#Z9~&7>UX(JIF!2Z7gCh zz_1GyTC%2eko9WB19A*Ovs^O z0@7fHBv{CZKk#B2ou^fy=sEIr)rs<(i#{Iz#$yWyhCm-0vMimg*j(62FK2t`}Rq zBq$x+;H3cjBQpX{H}gqHB0cl0{RbQf7gT)A`UmLi3F_5jjImNVE;WHGlF9cuK1!#i z3oh4)oRAeIlC6^ZhKSc2B^%|od#<5*Qu+QVJatk?uMA8XHy4ZCo1b@;T5fX>>59Yi z>wJ{+i1DQp>+kRt1n`+y*k1Iz4P5uHJraanuDLbaCfSTzISy1GyinAY->6?s+hGyz zsH*hQ)|jfEF{L_j>^D|j6ZR$q_9q1*rE=k-gXmFY*UnP*B0tohPEQx_`w^UJ9YG~D zGJHhZM{X@q(e^_@j1o`IYWHAGkL}6Wjil%hcf=sIV2dPaP-vd~x z)GTS2E5iCn97jPPG3Re7^~5Pw6Xu*u_KH*pcnukGdJWQdvM?TFQ%82vJoTfE=4qu? zg<(xNkRu;wzt0s5l-0n{pqk!23iu3Uj2Ry>JZ!*@BQYYoK+1yQe3c7i7fA`#59L6f zLSjKShRT9s3zQDTB9RfPg&tQ75k0-yX*>4$V{MB6{sl@RV0`(F?tV|{%m5<%m#r25 zb58tQk$V*2Y03YJ-2Yey`e$nYO^>syCSyy+iqx^MR(}g-&b(cF)Y}jjE1*el?uXt& zjF2E9EM|PkluFiJRpimScDg)Ni)+ncA2#!>eYSLRS(etHd*9fhsblqdg+FazSEp{d zaYV;+J>z+<-P?6JVC+T(Rakn!FD8$G4r9!9Va3$3!#e$D&URsC%%Q1~YNYPyQ70;m zl|A|7veKGh-t|bwv1Vm$wL_PdfRJ#%>}uDmVSUW?F8$uUZE^KRVIJhDV`M6yKkL^4OZV>O&d2I6nuPB2*2 za0Z(DsG~XVT+fX?j0i5BGsaNu$JXWsV^Mu1poj#+0?_YMO;ZrfKWY9f#-r&`7kTc5rRRSbm)Dj!>_8qJ~l zPC`hMHY2vgiayy6K2=FxKuFPFsEXHyGCd_#Q8tk%$}@y<7&K0XQ90K;0Hy2osjTa6 z!+qXoiCWGs^h~3ps|>#qji~``@?`Z{7MI87A>R zm*BwrZ=r|EEw+OCcPT+O!Af=cX1Q6yU%*bZ#6 zd%x6!EDWUzXPCC=T%v{Qry!R-`IusT13`#v*m#H%#H=+fO)WqbiwbVrH&NI20#-4s zQB!A`ay!poLRo-t7x^8!V}EI<^FgDvPe2toKyxc{?(x9mmHFH9g6PSmk`jd!F5E@V zITNt_0t?wmJk6(^^Jj)~&N1>D%?J|>27PwiS4Yb|0b&%u`)SqvnvJa{sotM$YWBZqlxJ}0=8YlUL ziJZpjDyl5sU0p@lPZ*-XEWzoZcNg=Wx;#2fku5!_kp^tTi2ctLmXXl5Q+PwnVd3NCl{=C6Y{n zP~Ji1rwzg}eeqcN#>{uu zhTEJ5ul}$_tV-NVUP!HaXXM`b)S>S6a(S_L`@CT3pIeDhL!EV-EQ5V4Y-{rYT%V(<&nmbK1=ju%nfT=b|27dkA9WIAgmzPkS}UzoX4){fua905~M*dZM0qejp|Dz zu)JcL{c#CP#u&INcK{A;-7?ITEpi9B?)6lf{eS>4z4&to0gnn24w@Pq$L;KBdoJXh zmQgbb7Pa%jfr=o&~cq$oeh#X&xU@*^ovmtaI z6Ai^@qE*cr+PJ?uo)HhN!@KsI`pWrE_@?-#0H+8n{A1TM8ZjX%DWGqCi@pCRoc;F# z^lybTSirNA{;9`8!udcZvy{$NTN>?`w8G$AUR#(?uAxhqUSs*qB|R1&ggYsu*5Igc3IBBF+KcbX zFzF)K%FVI-Cw2Z@=g>-Y{hCLH2h4@Q<9Z#cU5_YivMu3fBYY05Qpz)ao_0@NfAiL{7WmGYpB^nC~ua4of_%2lvJ*fA40to(YEi*x*OPt z3IuHf<0Mls+b-QVK4?2sOKKWTNialVGs2xryBEMWc+L_<9dqh(IM^nl&IjBo<^>Qm z%|v9cr7W4$G4)E^dXmFg$+N0@((UAf6a+SD!k(~94$`m&!0HgnKGt8#YDpmgiJM#} z4QtI54xI7;2@-HQNO0nK9{KJ3$Y}cNe-jGibGzB40@}0=L|sr0E|{G2B2!mnDH<^y ztM<*}6>}&Pin!t2Jdizfon&3NzVN%4#Frlh@6Odou4un8_f0h}&1ozTc;s(X1X;aY zUb^*zbwlZc*nx2XJAj=;>b<#!4fW2C#yYrxDkG2nUZ zY#p3TZLKV9?G0=V{x#|RX;$<%+GArJ<>pSEVVVTj!JZmuoaZE+o>O3e~_^p{muGuv@pOXYSoU zKl3u|gEJV7tT^}Y6IXpcDRuN*12 zgvCKk|4pe;DLnSU+Yc}LcH0?WEnl0(nPWLqWyC5jO(x^aOMD}sYD39EK97Fc8K%QH zTFy+ZhW_wUAzm9_Vk|dG#cR=Y;mBiBUnP;f1_3)L|A|8sYOZ)*{@k#ovgV3K zW2o&Q93*}wyXF_j03w%GB3@n|X|(>+4rGD?Fo~`X_vP$G6nV`_S*+~_pp!WSKhQt{ zTRPu4)idO^u!an%$|(zrNsWkcEb>BsPm_$PKvYfnfRC-AoTpqqeTuwbX0-k&x;NIZ zs8Upu?GUW2q_?wc?SmCHVpzNKPF6+1pk1K_XccopSo`Jin$UIoTTE))Cf!^HR1rnQ zL`B$<5GK$9VXYPv9BHSAEIq)i1y8hr2uEZ{b67M75UVq2Sq>E^J>w{8!DMZ-ml_CZ zo{+(Op~1(zcO1dV%>opH!187B@fW@m4vP+~yfBh}3F~l%;`|h;y9kLzG-~S-A5Yzu z$0LPsk1`=7ND+g*$LL;iojWz+6yXjSH%O~I<9?iMoYoy%zozvFAVCVkf$>1SYJuoh z09Rr7(VhJ9dV$|+xY%bvo_h=R--DX=UqSsp!^7VS(hPtDG5-am|CHf=^Ilq}N{@%Me!G=n7^KR)jxUc!|W{aE^%bZJ< z%eyLPO09Ae6R&PMv0cVy_e!W;lRTo^7K;Z8myT^Bwsv>P^=L=Dkki|jsxD1b${WYp z*A(h!0}dUNT)ujiWyw@JuKC~D-^nyb59-ZWH^sB6NbX*%$~YsivCpJ(5t$@CID@wE zX~ZBpuR>p%=%`VaUCptr$G&4WnCle1EV{*tP2Ov|AT1ye4DV;kL{`b7P~$ z+ut&@d%w13oL&mq@I3bNiCv?fUoHOI&mpH6&t_!L266;?bz3+vIb*XWiq7Qc2X%i8 zP=B+=Z|W2Ab9i8g}vV3lNGWyubo2efD8hRwO8fAgz4G?C&cr|sW0-!}KRw;u4 zb%HQ4EO{ssY52Ic1l}9pv_v;5|#|h>26Nhwih&J+E2yNK~PgHS`p3h+gJ?q(k6V-B? zQfKi!&cwy$;mB+FU^p}jdOQD;+=!_VvFwK@<3P}7Kt{k4Y7z)`%aha@DDhq}mg?LF zZ-mwQqtdM|u0v=Vq%|HgH!M+ByenS@CHeD+bU2-?n=#u;hA~U1w7}E_&Uk>wd*u}u zxB#e4cX>=V1xQY@$%={i*`OREL_yfh8Cf=_D-BuOX$Mr}P=DYNO#jxLlL`*nwsJ2# zz0i_`TYBGB{FdS4aic>iU*o_`w{6z!eJ8=RlwIj@hsj7QhQ3*3)FCwYD2nEbAW5G= zCx6#?2g`;y&yi1PA8gaDzz+>wToMOV{C$2#Phucd7u1xfL2qt!yX?11Q>j{r+q(#@ zbV}_a=76f)e({+(MAnDsjs(I2!UE<1dMyKXYyfp^0C#MFoH_9N4W^`zbQx@>EeVL?kc9kr&-F=yc@k0=*C5O7IHl@0q1EvWV_e<^L za4z@aqoTDN!;8zy_{!5-CzV-?4!t7Rd&7>3wZ`yPIQ41a5weSPLfHyL|3m0foeSB zn|VMO&QJrosr7hGCIPdCO3Tdw8L)g&l}6?r58WKb zi<#*^#Y`HG-+d&4L|ahdlgUtm{Ep`vrjs1u5lr$0I zcSfVrZg(Jr1}KfmoH=j(1d5=XXaiAt;UGV$@vUytDXDI+ikJ$)PAUWqg!y1oQv@*X zPt%}g9P(gSfuR)b_G&92o~pvlp)XNwu~Y4_B4Dq;^pT^^VOjcEC=JMXOb0_n-d0s; zLt1bXhl@6e9%gj7(8zhcDk-VTdHu8%zO0%tbu|k(opDU{zV4{w4>b$xbe2;@c+z%7 zN*S3^nG{roX6=;yYWiiD-z+jb+KtYW^)Jk~7i*@zO->VeGWrGuY&D>6n;NC##~L7q zoa|SxJZYs9gEM^LSI3iesQ7HU)3;fpE0L3lX=GwmcjwHs2)PIir@;8`J#PAOH^R!i z2PK+!8w4bQWfE%9ynW>GOT`e*%ecFPNOf61E-%|s+s@`P77<%xYBxqEc0HoZNjS#n zJUp-Jvoq@gi=nND^SiSs6U#@NkD=~DuRG}-CXl+VUhQT$-ORz;ypciqex&r6KM>~) z??phtRfZ6YTv~j+SeG+&OjT5NRLr)PG%k#8_9$^)X%Mi7o*r)@Ga!Ixz()9C{Z%_5M4UQ1KFzV3#q^WYUztysgG16G+a-(Da=z4MceAZF3SPsiZ>$ts# zSA`L=wJ)Pp&StH{a)g-fqH==MtlX47u;%trb=9Y&%vy(sb;VT8Yx(4~i9MqK>dLXD zy=>T|j>a{n?6Oj~oWP~&ppPVoppI|u1jF7ooRV6`BL2d=?m1FDU^n|H!nRyWz70|CBfd^$zDkdcZShZ)c%7jwMMja zgQwNe`c8DmK4F@3gYKz};$fVHW0$4s*g~ai$C@dS+T5j5UFE*HVVli7A>#LghmEo_ zy-&{vYmbDVJhgI)+1188?sqRL>2-N$_0qJcFP;v^=7hC;XbE2Id^%e+3w_j@qn;#M z2R?j?y2Vkj%!3Uk5gqx;mA=8gLF{|ry*D(ZZZxEhe7Wv{;bTX_(V*8w5|33x5eg5H zr~th@ka7bM=A+~2!VH{AzPgAHXpkKRT!=uuvxiD-L#h$M6jjN%RlnsEW-ijrB`(78 zhWd=yLIz9bCwPUm_<_J7vzalTvs$LEO<j?Fi{BDp!a3FF?0=dMR`mt=2@OIX)J1i&nqu)1G#3!NC8hgqa+kDTbNj(<;|5~S{Tv)nH`als#O7-Cbec&6kr`%swu2vk{i zP+HwU*}w>CP2c6BM4emDbB$snbW>pI7Ny0b^oH99tb4NIC@P2ID>9YtoX^XE&n>_y ziI?!wzbeb^e{IbN&gn;jZ|*Bekq%qJ#uIaQn3;4WMpaTn8YO1_Ry~tbWXP3`?Fpg< zeaMc~t1O{o${{HI8E>^Wl{K#W3mqRBH%7OJgtrzDtPu+TXb~4z%$T~g1xEo^!O9t zJ2X0!=N4sWFSzmXh%!K{C)6K!((HDjjVLF1{tEMlyZKH}q}fts!A zBMg1n(OT~(4pthQuEes(TDS7kGVcqEuC5NB4sAYOUb=bqvi{a(hFiq+ewmil)sw1p z)RDbI;zJ{ijqUY?ep0&CudLth=AO+~o$H?*-Cf<7HCHrKHIkek zGu*z5O_ar~91?tXZL{X|@aSks-U*pF^~4PA!Y`<4!$3O>W#U_rR14Q3t=ph zXO7vHo!PCz6UfdnMJO@sw73#+%<6UNj+qcomBCJdNBx}5%4#4uL1#*3mt~@gpxl4t z=4cc4x+WV>#jhH~j}?$7cY|u8a-#P})0Pe0_P!+XMjARL%06ePNr6kaxR_~-A1{yt$CvnFPE>_fF(EpTTwGvI%)GqiR;-Z?PGU=+f;_{NyxNbg zM3Rd|j_>I10-)g(o9h#=eAk0%_#sj;s2*h*q3p_u$FE_G0xk+WDb}YXec|C89FhZ8 zj+ke836qJa=`D|nKTK)VTYLzQz1o%N&0gRuY~SBDZHkeg#HV*~kO&=7Si z3I3RN9P3AxNRvhXzA20aoB-=L_Ej+j=Fb?RSO`791&ReV1>qMP)98O5B_x`I zz$d11YXC-`9Yc)e8H@04wL&>1p^rg2c-2y0{6N{Em+8Hlc^q#ny(sBP6ilq3B+Tt2wro}$%f zeJ)};`~jeG3JuAk{N0_79|B)tLh?&xlE4`>VkJYZx5SY+stihLt&c%l;2-?I7EU`b zvxO3XnMaxj$~F|hA{2m4!=bnVh?|fV2sY0}Jz2^AUmB0iA=&I+}wXeeXpn$tIO`Wih`` znK1gHZDtHhsex@{J&4PDWe!T|fjvY!r08oL)Mov-gqh8`^;6G%5^eY-srtR24&}Pa)P~o>;6$JF8|P#Qr!xDa)lSS_;vr|~(r>w1^I<+x7OVJr2KAdlGx|A}bF zGeZ{szVoAtO|8)`Tc5&~QGozIW>7?kaX4lyviCpqCr39S118`M7@r6W4wja$t?WG- zxp=j*vMS^zFp5LU|M3nE0rB(8FhJmbOAr4WY48_0P@c^f$&M*gao>n}Bto zxWIOMj_rARI-V1+Y=-!r9>A7`vKvb2ibkPNIh!j96qrsDsgHu zH=b*5*=kRG{q|GdO>HNWAl1Ot;PRkPRp9)@3({DvroHlLPq1$uae2G6YESm)XG%Ya1d)16intE;-ju!jCJ8>qCe%@C}&?ed}&wP!)8uj0$cZKZn4c0hLFv^ zEac5RiJ69Gh~<+V+O&Irs+X+>IVbmoFV-$?D8R1C^M(33m(3B9r9X<7ap%e~1N@XLtrC;w;-`5!6JLD5U{?<}6d-(dgI zFEYIo0Q)yU{QI!~1#13Quulg_t$zvoUudYmm_*D32$0)iZ~y@k%5xbbOxj`aeh~;< zQ{R)S@g%neop&+1kqKuY?3nE2<)O0%T?$nTx|&ks)9g0seekrC=JV_-eWhC6ZT6u> zV+n@!`xQ2|xu>qfnf(!7^$J@WV*CA_338djnZtc&t%`YTB|6)#@|8KOHjzQA&QFWC za+xZw>|<6l$}uBj4q7_4O|I#PGY2y>N*&EwM*hWu~pI8{=1~Gtz5j-d4-- zZv4tI+DbCm?N7VgCa?Sp#}}_VO&k0#5BCH7d@71F{l@NX&F;+~OI`2A#vI03Pd9X= zDekPVT%BKwo}HOAcCu;AY0lJL?-oLHPlwl3TFZ_q!z~|gc%8^4nM4iIl@Kl-+jd>o zuJBKD87F^?Swi8DL#}>^+L0cjWt0e!dTlS36JerIJ&|`6FuF#|NF`IWo%pG{ws5CN z36rG+m-E4avzXiS^En>bF?liL;8#Y;@OPAgxpC~&#oEYdpm9R3vz^PN6l~L2LyH^; zC6WhX=J(2$-~d!r+DbE|2RE zHP;r%(eMMZ!b?YT$^;Fr3UCwwLFcpXFug>5ev01}bx#ZN2#v<@db|;v3Wn?XNRD=8 zf|3gwx}myay^)$Rxe%Z65CQ4AXs%jdJ8LQ?2}=`6$*n4Oip>H78fu?92ts_~Eu<9_ z2l_tIwErDt0v3;04++N>)y!#bABa=8DHE5^aDylPPf)MV-X%raZ82X{9JLZc6%WGIrp<+#lSRTPMq` ztXv(-8xLgjvk{zMB$Ae15#DB#C=z{Pw_>q9fF}5tEcOer?-wjq08q;Q6N~Ze#Ac+z z1C+AHWWyEuG_9$C?c%5@4#Mz+`sW*OQXg?6sioe%+{OIC50|{-ZhTdr%uheFABjG? z-n6=vezvY#WLcP->zXT?NY*lP_ED=b@;N=un6U!ipV(&&S)lB6%Ugqh)zEec#!8ZFC_fz3k~5PsnLq zC(8-y)~90KVwZ-IZzI`&E#(_ZGQnqNT~7}e`x6_lwz0gGo>X{WY<#pwKi_Y#RwrePw@t90JG4=rWVL%ftSPz3_16L1pDHCuL9ZSC zER8O2b?>?e5SnW4A`DdU-0txpq?z#`2w)Tm@twZ*{>)U!J*)sSRA$Qd_m6%{?#TAW zQ{Xq;ZD>_eaHaI3O>&*08^Fr&e8Ol&vLFM(;AWnYp6sTWsZ<>Aueh$arzhf!rhWS` zf^9nOVClJJFnE;mg_WZ7G{pC$r_zc`nc*l+RHJD9Gp4qH@iPl=cHo{dqN3fgDu0BP zm=P-yj>f^|C23HZ!%3)T+Q;`E-)g+NPc*m*R>Z4!AZhi)dSfLGhGXZA%U=t{Q20vZ zHbHj}TLriW1QU1*IP;-1)@;w*1{X!a&2r30k_>Y`=_8PQfrk<6( z_6gPqf<6~G2pC4F?(X%$BFQCxdv^f>VaO2}9l3zGrZHi-(5M11C{Ea@Ax2NCqWrJ9 zEgXC*24Xn1>hBzR@azzNnnL-K4f=N>6Gh(aqS3vl?d71@tTfq~gto$rW~NvlhrGT+ zmIM|?OdrQ*Cp;p<_Ol7T?PEG{IT@1TMB<7W{u*jWB{eiyJw`^N%!6VB`3@q&oSzF` zG4jA=1VWzBaTBT|(0(rW1B^p>h%C$J(o7+tUI(7RsPMkTc0$yXMsqw60-f8**+64# z%XcE66+8$KFg1zCXjt^qcA<+$%YH1s#aH@x3zNnZ z9rhz~HU?HDK#&;iD%E2#5wt*3D6w4JSu{b~k1a1AnL&;V+TA6Op(| zyEKq4(xL#f62^PvFptmt;lzaw@1GNfoPs|u)HQcchMoooEN{rA@ZE`kp5$Et5;W|f zvaB*2zA*>IxOPtv!amNyfdw1>4y?e{idqEVXpME%VO{U)IE2LI%_BBHWv!O;%i68O zDGcZh1U1|$ZZqT0^-b7-YX~a1W!z2_o=;NJJs}pQKq-Ziz80lWDTTwnRHRRYZ#Ags zW0_Dzj3!!S=8iWv4Fz^eL}(@22*aC$|pu*Ck&1 z)3%WthZ+rCXG|a0vvr?E|FsjD0l!_9bG3|#y{ps56=zrPHeHvp^bOmEg@`d2iin7n zkg=chBd1qIyIY@@yfxOU*wzNd_O70f?w>9$>|NX&QgkZW>c)C+jz4mCb*^Y9vhVHw zm^w<{eIVHHJnfvM9TfuRe>%VI{mDBvc=5a>$KUmQ@!Ztae&mL+o>5^#&(Et(*M{jj zB3A~R_Wde?-OSAOXqRwpt?HtQ-7I-`!qU!?KaSt~1*&Y4zr@GkHSwk7dFG*LyX9pc z@1=h%UJRixO}q1@{h^fKHl!#qQbLiUfS?d~FOja-e{Ip?jti0F`y-LUAeI~A2#_Ts z=}-m_a{6sELNhewkb-vDht+R)L9<~-dw6ReZn&%b`z#|_!irdrH@Riy_ZEXpGbV-D?Wk7_cGgF%;mPiD!*T+)zK8INx)f6Nd z-C|K;r!Ej=yFEM8zi-?u-7}<;ce15++K1hR21SoJY^eGimxDHh`jK4Wq|5}3ES>1i zZ(6=eICp$%@f)@@hXUJ#LW!x9zrB_nqYyG@scg&MG@5P6AsG1;TB;?pYMg{Q;2@Oz zwz%oZF&MTD1B)+`1zf3So5gf;tbpLfK2idFre=YhsRsTlc7b2C%--Ykt%wFx25p<0 z?`3}4H1x1_0{FxW8IA#mfaNeZGx7IeB-Qih#`Smwax(-4f3W|Lxwnd{b6wK5ClVyM z1ef3v++Bh@1b26LcL?t8?(PI9K(OHMF2UX9pRC@0_t)#o+TGvS`(T~GJI9zO4C=m~ zdaLTWOQN9;#h{*osu(n$eD8~vzwmKfNcq=#s7Hcx3=9byXfw!qefJHd3qOCVjp}?Q zMvm_J%x*Fb2{Gs|M-e6m0p`N8s7!mQWeOS%!IA!kC1V=$3-5Y{K3Cslf_tL$VL#T5)7Y*YG|Oa21rfVX)s%Q_g91sqv{e&hjn3ko+6mxtW5%n zVY~TBaAFzn1_L}p!fVHyM+Qx?;?TsBJ=KKev2H842?;GuzBqM|5=~Az9zi3oGcidq zTtvvpT1Y0pjQc7ua!Aih_fX3hYYad^$>Dyftw0ihtof=3P#ckl$TS=m_*TPXi05At zI`bYZA09+x7&1kW+O7Lrd23LBg}I^wJd9`;j!839K%U<5$o!mNeoKGqmA+)s3|F>Z zDyNxhLLemf*NrvU^hDsUm%hjnJnRJ=Blvj8ew|O@LdAV%EAzO1Byi0-I`SON`IZ%+ zXwLxO$qh=EECehmy3XobcsPpJK*WX;C)bBx{;nA2+HeG@kitpv&@&IU#vZor=oE=s z{2!Rq4Y4_e-51QWGl2Kd>@yb&8IMBuy~=A^Ix#cY3XX%JeqDb#!?{&h?)&T-R*N4o78I$ef*vZ8HVDYI_u^b-IZhmr+5gR%h|fD#4UhcW@1hf)E% zgL=Zx*&utaSqI?4AdJ@lKyYD@#%n$Tf1$_{ywbiMP_rr8+a|!e=o`BK^#kfReE(Y= zP`JRO_~ZZCgK)An{HIrE{-P7_7%TH;OVoFIk2yw^AqUsGW%?p2N^=0D!{dtn)f`qFq)Hh>LIv~oGlVMzyO#?98gh6KEQnr-T_#8j9I;e~K+(bDNA>cj0PM2n`)z(T89sUU?p|4QB6T750(3C(E`t?`o>q z(eB+o=J=QBmKyzu+>OU}o+*2+E0IZIen~Yu$Az0=(aL2`s}vWNSL2S@ zlc@(2l~p=0lZ?f!W!{0`H}0px8}K zZxC}I(I{=OkNV1c*fOgYoOLO6 zPM?;EEj(B3GvCO$-A9{RwV@(&i8^R`6X&}4`zRHF$eMu7rg1}P#>{b>&Whlod=tw1 zU>2dpZp=vzY!v7tD8_qE=KjC#d%bwqk(xgdepDs`Rj|h4~>wEal{cC^yHQ*q=Gz_zc|bW z7k7kb(YqDL>W?_$;MKb;GTGg#LeKI1XD{Xc;Zz^lOl4sdh;59i$Hi0~{8&Ii^O=Ys zix{W+^GtPG==0()`cod6U5dz$18YawoG~o4@=*n_oai}A7BpErecYoBvmPYSi2x7) zVE{ZJ62K17jPiavy>|Pi&qaX}e2dM0g9LvQz<(FZ~j83XWTH?X#4Jof0uzWARF@JT!7n zFc$!Z@g!%I2Yd zvFBIP9~{%H?yku%DUuytj~=eq=a28Yb4JN>Xin$sWuy~dcz*imdD!u&3VRDA^3GIU zily*;S{6p{3vy1$oiu~N$wyN1b&En5KVFBH+Y zwe?ZoDRhTHfZh^Lr+LUJm~6#I4`C1`crBoZz;D85tADqG8(Z>=3Y|^&MNw1#dTX>j zj6w#8AD^Y%MWy4m+Z<%Bmd+*@9R(lYQG{h+I7VX|g3`a0{~3zmAJxMmjWBhZKh=qk zUqa(=fd4S(6Y<9_EDyjP6e8G1VILeHg{MDK(Y9V=^;n>o-=h5AAm-ml;cq48@4&X? zKNs_#$~}M4sIO3vio$07qhkj=Pq}JE5A4x_6EVlsrs%+-P+f~pt)HEKn7cXwZ314k zhT_gu_!~u+x)|uDiLwSnLJk}52yT{ID@RUy?cGtAaFz&8dUxAhJTI4^LOp3^37Cgi zFwlTsGR+cd8k5U^Ip%Rbj-D@AIl>QOHzUD2(RUBuq`;l}c=B~)!=*usmzQ_=xH4gh zX<(x!uC@6DzNb*I9Ol~NK>nx5ZacEf#{`9UZDRe-KOKc%H1MQ3M-c6;l9csIh|Qpf z6OyA@c5U%xOT(TWmWqgotZzKq=2skob_q_iCappRwCtCgi%K~1tb_V-Lg)TH34RZkOs$l}!4ajDa(?l>rY*SA@WtraB+EhR6_ z9YvF#r4S*UrnE^7FsX^&4;54XHq2--yu-hf(G?#44hPm-z~@EU|M7gOa%+i z>P;I<(_l_Ikea{@nS)UqO_8!Ck5JxjtDVKFev!z}`1COFy{XSWuKjX-@ZIxi0*~s0 zUo5d2R64pJbLx}wc!s$l618n8IQSXD_e)3CeSn<1aXLKhp>#nC6rCH#YeCB=%yh6m z>McPotOuFzmzNhq0okq~Zk(B~odsleqp@HwdC=PYZg}Le-8u_uOkZoOK=fwRXv(?&H!-Iu8inz3!}osu1);^dv(sQhT<8hr$J%Ad3DZHKdf=qQVk0t={To-=P|i6T8+Lm z8rYe9QeZx8wOsIG8r$hEUn)H+jZpPu0D5)e^VONdvUPnI12JvXNY!?#SU6c6njQ(# zY^82h(;S*PsY%O4+ui7(L`ZNgMQ}7eX*t=jY0%)|Gsj>*tTO$CG~O?w!EqddzYN)*J?}B2&X=3Z)-?RJPG+~_L)De?Q+(D0*Se~c z!-OWY&TpB=O9!(@uTb7vW3$hDN;7O~$3>^_tz)=-eX3muUfbPTKu`{2!$4ywe25MR z*5W*P9;hHBa5JQ0h<@ZVjlSDmMPV4L+Yyo28X$!tfQ}X1h<~8_`JE1iQ=Lw&e;Xv^ z3oK3TEsi_#kSIA(PWZOPh6x3UdI+k#q*^op$xVEeoIP(lJeF~&PAHFAf!i)rtrV4r zn9C@d%0FBk8d%MKjobGcp#>t)xSbxomW=Jz&55I(Lln{r$2++~Mm z6tSEU@_~=ZDtdrBH-aMM!Eda&vfwuqBszChwRqH{(vfnUwAoIPknzgsswm)ue6*FP zV@b?3^Q56j+=za2Rp>&qA*i1d^PfXDd(cD68(?Xop`xAS?pR`&n~Jstz11p$tFmkG zG!C=;CJ!*cqpE0Xty1Upr^5(bUhoBHiS|_}zX^Wr;^`=_<-u=3!DZlY|Acfdlmg@V zt+*5E`US~>#*jms5=P1XA^&?ey!8O>s)b;8VaL1rr5I>r!wbz&zu6LEJWz-ol2N8_ zA=B%e&^X1UGq>w6qoz-qQZfY!*vZhaIH~-jXI| z@0;vJ=J#-m1sfzk7R(B~mKsV|8wyHO5n3B*TEq)c4_7={4XLMBELCyDuVrydrB@tQ zRyOn++&ny{(=2mX6^mLQ)vSjV+fQdH^b?AUN)Zg*5m#ZZ0gEPaU1bMna?QfqS@$n& zwMRpZ)Vt_rR!kglmw{N(xfYMMe3sHb$_@)Tpf0TD;>q$3-8$RMoe^f71_bV5eTD0akXR_7XG52wD7z_93-sU0_P{Z_eihXCrSzCP|=|s(g}wkkm|MA#OOv8 zOHIeEL5OLyM-&h>Ws#^;UBuWxgbrZ}j1g6WKgul-++;vbifIlv2tPtBV5~9S3ZQb= z)r|~^V~`$Vqu9z1w+~-Nye=BmLNlMg7ahg5y&Tl|!;j#v~UMosk9F638{nXpZhNU*mNdgeq8 z7Z17!l?AtjyX~)_&^k2Pr+m3>IYxZTb4=R6tXHBAOb&eU-B0AKf{m{bLAmNmBayPE zFZER9w)9zQDH#^@BlJCU>@w}640Z9t;2d&{OC<2 zdUwufT@h2zdy0a&zpfeFt!~rJ_DDr|sY4Mg&;HqTv@R^1i6Ar~4 z8Z=BOn{0{UX2-?bF9+`TB~kMfn!;MM-cqq~szEKBQY-vfI~`R#3-O}BUQ%{g`$6V? z90(mqGzc3=GKdqX1w=En4JIE7A0}`m7UnwH$K$O(uGf825^3}Y((6su{|0*fM*Dv& zdQHRrPxSiDqJ-bjt6fPBc)1SN>$VEnM0ni{<)fvxajU1Mf#J2O_UT2TLXt;&r*)xb*wIPjK$OspEZ#6_t7&TM zR-f7R-JJ=`(z3fjTSa^2KqKLjgH_<`bbCzz?h?KjoeCx*u3Eox4F+ zh6|{U8Dq4y-Cnre6M zl5Z4W>~_$&1&_5SWerhQv5*CabNb&nOMUy)>0~^&#dF`K+3}5{Os^zJMcL4iJ0R$O zt#0Ae`|082VDHwNQR=`>NhxjV(K4o;1GQ-fD}1qv#yF^@c@!RhD=Vo^>hV;%Y(n)q zY0gcUz%3DgIOz5o$FYc~mU}+SVNR_I4OP>Z4R_ICuXdH_=0`w)p!Lev);x=Z(m&zX zJ!xt28Fo};uN(zt(kjmYn`B8C2C_`TXF#H!kmXKP6O;yeGxj45G=mzGis$C%m|Xo> za^#YTUvbGiA%FZ}SF@^a`Ps*LxZu|yiK7N84aG*V9^iuld=MT+P)b8g24qGP5gqb7 zl2Jov5qrd*iG3$~d&ukapQ&{x(xK0W6x2D|Mnb-^xgbXQyq%485vhSO!{)g#hyurd z=8G#h6V=sJz+PapDEzgX2n-4a;1MNV)Z0@G4_86vOU#Zu z`U^gi%m+-2bZE6lAJE+P-3?a~?wP0FB1S3`5q0X8K#cPs%CH@&~K zITehW;7%{vBzANAZ%zQrO+V`#6QO!Jb(gJjH)i00LHw!;#4mPc;%LBe!44(@X)j)0 zQ0dmetPsyUH!NzE7O?n*%ce*cKsHmUM+UnZi-P>oL+xl0Jy$ueMbP}ZeXwL1#n=2q ztIDs~o|*wt99$Z@eIKkZROA>!5FO4qfJ+qFJ2zz6~KLp6PR6w3}C5%VZd&!f@akj-rH^(U`+mKPYutwu#QUY9mmcl0LXkK zeTeyzeOS7^|H$gbTJ$GYfLwe7#=n7!zoE+Cii`8`f4X)2e>I%w>1b?q?JX4*-~fUkdXP@$afiV`Ydd!(Ps?FR7(Sb-=HgE}^Aas(*02 zbXu|+5~z?Enz=clJ5EJ!VpaTB75eC=jURg0v*Gk=_szq_#pZB$vf|L6bJ=p)uwnTK zzRaq>)}Fg&Rry%!xr6G&|8mrMU8vo=s6%)=s&3_ca8tIy()Fg6?Gk<MqBrpi>d?+ZK=(MYIP$amrZ;G@3K% z)+s@N6aGB5c@0PRh{#E6&=!6E`Ny!yhR*e#-XMDodaw(u7!zLJIYvak30EW0bokbRYbT{Ah? zq2OdQ`~gVzk@exYFc^pEDkf5pt`p*gbmH5?ae}>gc|u+B zbVw=5ARP4AuW``&iRIp}ngmlzTvqDYvt(ivCaYm0(NPn_sDa&KO7BC%`Q-EpiC;B& z<*6XE)S3aFq$3g+0axBLOefLROPH1Y<;HC>Z$hb{gDPSkJ2ZdH381)=}#%u`XOJZPvi$Z?Mcpxi6GgK_swJs0a zJ4P;Ov)~?`zoof9bRH)TOXCzlDG?6f>6-csjvnVlTHMj<3Ra6mIG>Q}%P;X|_U4^$ z_?7fd!Q!mswVN%0xqvVcEkhA;m7yR1qcgEE5!O_o13cNJ!L(nDf?jvwtLbJe(Jb`W zLG!c^)|WTd>|gzOTqjcG@=WcIn7|z1hEbR&>OF_5#X!pm30!|Fd%4BHTt)mk2w zW|%E_P~lz5IbG5p?~(HqBS@PGda^#7f- zf6iMG5)nZMTltg#P095j|}okAeLy zF1GF47>%kr{7^IZy5Ym71%0odjgB#_eTt2)N$qV<4Ul!73G9bYLq8I>`rI;}>#OQk zI%sNZ-Ri2GUFC3B4@&~wW5~;nkDnD| z+S!0fC`9-?Ee+ZqNoXg?lUL1r9Zy!H1$73S9asXPeAEH=y+vPXI2>vWDq9!`{m>G)lMoCw)~e+;J8WuJm1l5d zm+(~*a6a@U^5Wd=P(R8cll$%i(i041UA zB%V&eGfCMv-e-ky4@cJES&EJkTurACg#V-n@&1T}@cwDzDsz!D0JX8sP&!>N+y|OB z@pvj#ih@n<3a?y3uD;Jx?M7xvPc%84SUi_4UC=g|rwB`O$wtGRiEy#@oY<2OL0cM| z6gj+MU;@LUwSLw0vDdFE=R-!{<>WS1Z#>mFeuO}22x4BrHvQG0jM|Y0(9E1`iIq2@i*+D{dky-hEccm0&8Elb9*T~ zF+InFX2WxiC`b+ohjyT*=AvOd{YD>%-_KHDLQz;ey#@!J%|OCr1|Zs%_kMFJUxcxU zHgCjv9(6ldc*41lX7wQl`0W>Lk#!mm2654Eqe|iJjE;yFUvI_hxe%>EIo(;xX5-A5t>jAk-l)0N0wxO+$z5v>PNcl#m1*5cJ}Cmya9rS)1Z5v zgJ8_1G9{>s!gXDA%ZnD8FgFg2#JpJ=kq>H-TLoo{9CbJ5BLIs2dtLtuMc^E7b7T)mF^!=BK!8`5&h9#&5 zsvV#(eE&K*rPQ^ai8{M4Uv4ay`Cve5qFW|kv zwaxcbV>3xCrG*jvGiBPA7DFA<@ph|LkLR-uYP13i?;6TlTa;xyJ5Q`GJUZ77&EyVB zPaHcfuL!qC3bPPcFZkUYRs!cqR6agZkBqA@SQ`MBo?i%Y4OS$un7~;x|XW8R&0ox2l;!Z~Kg{bR<%sC|TEZ^^BUi$8?h zQPqxgBAFXa@7NG^mKbz36pDjjy9ttC(}n&>^<(>F?I$gSSW*It5au5>^k;gcu@IzG z8UUn^k4erxsHU~kSb!9P003|Q_`eQt3Sdb5AA6<#AEM)L z72V!ICI7SN{?mNaU%W78#7VsQ%J-kXpq6CVRvcDA5&6gsg)7w=m3*_BPl; zhKh`FifqGO6b5*Xj;x)GUKm?VOIOi`pK^u{P7k6j6}8Y+<$ul`$*ru)a26{`2=Q@2 zuofogm$)^RDmIkDDAIIR9#EQ#+YTC)X-;Kfm0afzGzKmvE!D~)NY$-s75&<<8NcV6 za$VRrQvT%{qm*Ven<=_9?m6ozXH9a_u$Tt{N1fi$nR4@3JTd5XaelJf^PDEr1dQ-l zyA)cd%g>sYp+f8e)HgH4Vl0m5QkFbvv~k4NNeR#4c=WNxnlFjI2oPNDOGC5I@F9nRE{LMS5%p8bR!ccTr@cmh`-luE4>h)m&f4 zn_eNajfeVdx)5z_xs+8 zqVQDpRJAt7-eqT)sj}{4`ySEw++Z)7vAXaU{!ye1d6K91sVbaIG4qIS!QH~F)4f=R zg#>P=(R3=CuQP&+!h~cSAKg@61W=+$R#JnzETyPVSqHZm`|K(|b;j$w+;yJamE2WkC5gxY9E znl!_U%X8OT7YxHJX%VQ#kKUks*%xXmbQjrJQ4TZ$7oP6|}>-oe1(c(vg* zu1)0b->&X09m+VLb}VDm+RS`1-U^z@r$Z0h%)!Z-x!WwAQ&;FisJlDp)V%&NvQ{!` ziLEG8)#WCJqmPVuiu2=Yx5lGlp?b@}-+q-q+~8(?8E*?d?>asim6Q-Y=6mLpul^aV zXmL7{>P@?cixcEY>fp=bXkO>xtnhTm9pZ-lE=M?m1o9X;p2~Pmz(+C( ztGcL$>5>R(&^ytOYB0Y9Mbe%;E|$JjC5}TfSK!6vp(rj{cxky$V>qC2xtxZi#1-)} zmo5Nzsw0Du-71F}r%kSabP?14QiBG2X(gYve|I1{|BLqCC6fGnAmn-*96gvIorse3 zI5<)>l)m=VZ8AqQKGh-uxygNAEbgfw02H3_)UO zWQi-9gFugo5vOm6dO8zA{kzi+hQ*)6eI2ZE_`^|U48ku;DZ@%vQeJFlA2-rO3<}0{ z;&dUboul4602%?DK5BfpUDWIFHq<7muYXL$ki=%~ylrrQ3&ej}ZNG`uzm?h;fMY-Z zOl^Of=lKigsD^}YprZ|N{f}$t2Qt26H)CTbqbA_O6X3~|oy*$WEe6|Q68pmzE1}5P zg3MNuk5Xv4?Pd3Nbd;BKl;N^4S;or9^?JqPA|XWqC^vaVFS;bRT6N6{b7zPWzs)^>uIar+iO=2EiO-AuN+Xl=`BOjo|6Ko zH)AI?gE~WQD^%6XUtV*MxoW?h$9{6F7;A!6t!Zl9o_2#CK28Khp`TW1dABgFwp_}w zXDwG21-dp;Sq;;xX!3I6M+M%J(UZQL5c?R+Z!`QXoeAFeO>h-Oep&aZI+G~dik2oD9Bx7H zfK^617&$Omg{YB>$@qT4$aQjOvg%1Eu=N8DBNiP_HJ#3*XlIy3ToRP8;1@l35CFXR zoOutQgr{a#m<$L(aMT3;bVJmtxovdc%X9@8a#w#e6O1eoAwJuI#T16Ni-yo;> zBBU7nk!jv96?ct^Ml`XEP%5(~qP?wg(RyF`X*44EMJdobb)we^#CFQ;id`r`b=kAyr& zr+CLyL^|1x3dw}QPY5z?LdW#J+4A7?^ix~7{Mgu>z4H3v0Iz(43*3fG@WCI3&kbN227c2Oc`@>%`N02VGPSkVWrT#X}e~& z3^=PRD<9&16(=i5O)UQmHDyoNWZjz8pZ|JNyPA0YeJe9SohsW0jm!J>rtLBEU8*;} z$HUXj*1{nzKIjJbu2ve_QhGEmFDDKRjk%pDn`dS(PF%^wPR1%r*HUlZ zqR;kvL(pGB*~Q#k729p}=et5`DG11P-$~Z~unp9Y`4B5cs4oan%`A=WEq?NoTv!l^ zzS34}L)Jx24pEm+r$@?bVLb(zEO;H-PI$yzLzxYoYAAM*G#=U^Zf()%l?t0(O>rrs z*m-jV`j-Be?s89kkR`S0+N4|JVqN?Qw{Yk4eiLF`sY@OwmqW+w<%;m87V(sz-ut2A z(ex9#*?YAy_odl$&aewwIG9$?>V#|5om`` z*h-6YY#cOGjt2})%O8Dj#5ADG5-MO;ugekg!ZWCnAlyR1@G&(iMo{OsZ_a6b^nb`%ZR4cOW*#kjRKb1CSWsyrLT1k$NW! zay9|vFU3pPv@%vL_ z=REqOd0`6A4Ft40K~?9)m1VUjy`J?2`zA|W9P*EBt9O^Hi(j{t%leVs08W4#zyaU@ zWEo@`EFUZ%(ge~3p&H&~`1ucyI-84y-f18T-c<50qu@7E`dgtO1UQ=V&rtBEnUlXj zL3^TilW9E7V8yY%)#}7><276P76O5BEGVYvs;^MI6e6E?kOf#@+ z0|52;lk0cUnN>vV~2)I zbI(1c9actVk6!c!KOqj!%c7*3px5SR^hd+>Q-nH-Td~-{wia+>U`*TKW|Fmmd^H^LZ((e|3E?` z;s~Go_9IX@kTlRKy!TR+ny?^vn{YA|_z>mlFtAj)Ixoci$uBK;cyKUMT8YhcgBVg! zFDc|psQ;HbkD;z)Gogct;4o;sCJ*RTAu_{@z0|ya5rE$UjcdlOrB7zd;YIHO*;nx+R^XS3zBgpc zPt|veB{-qyXMRD)F>(rX+5nA)o070KvE_H04sQ8qw7X}IfvyX>2Y^@z4%d3$QyhOb z4T#XE{dJ0(8Yin@#HVBM(kYLTBxPqLh4$Qwd48XyTaQc_qleLd|Y*@vyxjSj)N8LiCT0_WiOqXduv`lxnK7yulIQxP-l+zc$-C(Bpc?mdjV1bBJ^pB>My zPgKDIqlF1RfJTC%fkuFS0F44g_Y3z!^^5dF^NaXi9oY#)3M5pguG140u#EMlzJD1p zzme$Q3NenrDUW}KnBR|RcsqD;PT$vfSJ?J3b*YmkiOp+E`3&F>>Sq7*j zwgW4*ogD(6RDH!IVS*+kxPt8|O@w>xn9`&pw=2g_&vuKi6I@brBWTHXwHVrc5bJyyXu@-ws-hY#*0OwWV2M5K=~!5Yl3qtxl|3ezln6p&#}Y zx^^xsS7=L<%E-tZKe;WAqNh1sR$ElH4qkXM4r9hiYGyvnUz_iFE^z6=P#c^b^eZdx zI9td)-A;^IYdv4xv}$XyPJiDqvE}S^Z9Yjwzg-#8`&v<@tr01F>v`*L|7z~$%$~Ye zCGl##r1-k=$%4piGoJwba|q?yGIhs|$^6E&nq)U`F}xkDI|c%s{6~HyJBWG#mgDvD z&&ZNG(%}QJ@5CC2GEECn-PK`9|97e@BwsKfdV}E`zSWz-vQvDAh*>xwQ^#h z^g5_|8{FkV6#}^d1M82Uc|lgT5)lr@jxocXIjKLDDs5DMm#0!vDWYeMtFg5RGvz$`Ck6Nm`K%v8r-zM|hT0nj}Cq z6f^riPEk^geo$=3i2@Vod~#T2KiIuHHkO({0{}%%uWM|#lP_eb= zI*-OSTk*mAoeH_A56iGUbU3W{XXO`=a93e_IQ1|5baw$YH`)@hh) z^bxFpRg7D9xzoO`*w4sk9eidAX{`0atFCR<2A3k<4{NEnyo`Zil6ET}Q+?4R$9AAU zkk$@>ese+_-GO$p^%bpDG1zs%lXsDc2>;AK^GIErIf%-03i|GpvwWM8l7uUYdfalO zt|wg$$rH8De&M^6k=OV2qU`s&7&O}ZVdD_2)&K~?VE)Pe#sRYJ9vT4(*YQ{LuyKaq zQ0pu@dN`VAJ@sTU!A^{Pog;s8uSDubNnd8Oa76o(?|X1T6|tZxO^WiJ=GodwF9l~! zLHX>>}maXmB+VxYjsuB>aHjdeij346fe@|8E7Z zIN;R8fAdiKQ-|r#Q(J#IOQ31JrGV%;R#kliEqkz>^6j$WZP~Hb`1i_v>`ekGIP8h5 z9w~L|&deH=rEl1>eUtCMP?XvmMOK}18ymH$D6Fw4mm#DXKDu8wx0NYb)-AK3&)oMP z96#3Ks%UB}CPz!hre}|#g&njWHZE#3Ni}plT(_)HH622!W@@il7nQp@+AmO@rXJ{e zHmxe*H!h{M&V}`DZRxkSsLYmKH?FQ)AA}jzd|SzM#te&eYdI8{T77qVcmQ&sPz2BQ z_%gC^#X0@){PC#Y+1ktHbwtMcQ~A^{3(tm@woa2$<))FH2b_auFGr5FmBtb4nd7l& zFZPUzmRHuA<`u)o^Du=^$+4k3N9?A#RaUdw!cTZXYEqb}?2ZY!?-I;AxJkw4(5*|aL zvEOh&2D}^!9F)SAYrEPD7xUvMBf5)nBQT3CP;%RH-(*on*w(ETqn8QmDUT-fsSjw% zOd8$vi7?^s!(lAKd$&X>2)_mY6v{`7`63wU>X2?Ml!@m0W!pApPbl+)tJ-!1m8;IL zKB7v#np>R794R1v8dx(>hb@E_+7{ANms@28fVb5lFf3l1E8ES0JjvLBao2zvj+>HdrH*i7R*2 zm4Z@*-llYhSLUvam^%tZm&~6|j)&n!h-G@6d_$psq;V+65THm>in67vn(QKh!fTlK zBALOstKwn=F3Vd?;#rM5^};xI&562=JSnCZg3DVXs(=z;wlyqYOxdHYl>a(V1MIkj zdR#%DxIyz~>9ZThB1$Tu3GF0RTR1F{UuQ-PFk?psZfL6|nnmJ$h@C^FX5onK2-hbe zL2%<|V(#Cgw+L13sZ}57c!W{B7*sOv$ZIlpxcG%j_0OqpC491^Pw&w zql0(OP0EbOYjTRDB_9?kCYrGDdTFLg&il#CE~D>YB%L?|pZBYg62#OZUN-vbZW|>rW*B9nfrm4QMsM z1hg1%0lp2o4!sT<4IK?S4h?iVhpq-agWk`4l6k|U-VM~ZJs=+6$nn>B{40C&{{>$D zR(!li{2%!ErxAj`czGQ8$1WY$Onb{Ib;;XOs{+M;#B-G45tZB_7Zt^suWgYb|}}Rv~WV z`DhNvN7vULYwJ&yEB~F3>1JG=92p~4OS@GEH_o}d(#B;cuMqfiyp8ys8>#J3A`%nd zim6^&)2^GUow;cmu(}B$#Km%5R<_)|OH&jGAA675>`lAA^=lq83nvzbq(_Cs9l;rV z3>HKAI1zA00N;!c<)adgoHvzk68Tna{+Y;6!)$>L5acuDfY#4G#g#>W-9( zK3GU*6z<4YRK^C)V_!0!dyp>+!VTVUeN^MBYpY8}^F`_P2$}t~E+MPgC3YhOo?ct$ zi;kL39tpmhnr=y80WFn)u4&-TK3R}g%^6H!)(0=OyMphuXkIyI*m-w7vg%2;-+jFVQG(>@aqRLac#p&@O6sWNekiJi)rsQrwGGfeKO&h zfNbx`_Rq*^h@-o6Aw*!eDy;D%#hJuuRG=jqNDbmBaV5Foib=s?oeJ>V4ST4ONmUO1 zkPq%A$b?je!yMNu5Ifdz5lQS4wkvCn(;86|vb)p#Dp$Pc=tY}R2*QrikE@(YzBkIo z-F?B8v9Ag`jZV>ngj7SHgQ`=>yuM98PBr<`C!)zj|M})&PZsJXxL$B9ID2O{fm4YW^()^>N*=j)V$4B-|W7k{jt9Wi0+ftHxGnb6t-N z{vPdGRdt`0lV6d`N_Dk{av{mdJqrPf4M%Q?RFxvrWaEW{1S4+UmxYfL;=zITD`j3as8Af8;1Dn{$o|AVU)n%$RRVUzdrtz!!hpo; z{P{A1VThf`x>SGv;y_UKf+4pJ*Z!>uskxt0fp#-I_H)a_lnKeOV=4%W2pY4C&LF^B z0yIoEBnObkj-=j3iYT_INwFhnIuM$YnoM>4G8jpgIJ)SOn|g6!YH_WEdwC|YiqaHe z{wlYwD&Ak)ScC`jg-55gF3{fTH`vqKB^Kcl(%|OgDiES{rmZ$CXIaqc|5Dmso{)oyx5`J7=ABs)XOU2I^T{Kyu;Gq-}2_f1V*4U3O}ns6=uPp}_mBE%AwYSduql zS2}Zd08R`;5EW@(U-DzlX+7#q^n)$99L~Kij;v&`@4y`w3yeE42{{yE1kw#ss3_tM z&#UO*;}@5h?&gIFQ+V}?XDYvk0e$5e4Ke2eVRje!O9A1<`VY?VkKepp==*p`LE!x7 z=OnNf&)ULDQrn1~#dSNX@_*uTs|FuH^wPmnz#)A`r~J7li*S)cJIAi^t^Jj}n3^tf zGgxq-D8>rmN0N+-9QVM)Az54Ds``?9&bq4#4eaD)jzn_2->{b+Cv9sT%lk{OU!A_f zAn6Flj}XAYa)1=DXp;$G05E_!01EH^7$=`vBUdH?R>t4r?tibY|A*lGTd9j5SpWa0 z>iSJ>|2OK&Nce}kynyN|3Pd}R07W(N1)8k!yMJj$S(1!b+s)59_5kNQYi`dXfXkivJEDZx?YWS zhioudRJBj5@f=P9^#v5C4;Lm63@IKj7v~SV&vT#FSsdBv4PTm5&21W|ql~^HH}SNl zQ&n_;4WDlI?-1W61-4=z5AkH>3GxMEegE~MZiO?d!fu-jL+ABYan zu=XWTz0vN$?fvrZND=wbbVJ$ee(ys`4u=DVoC2TB%ugW#1G2@TDe}L>k9RZ?*pdl_ zHw$OWs)*BML*?r#(jW!K6Oj^-1%&u34lV_X74+w4WJsLUPv9e6DTCj}6dfx(u^vk; zzW-VE^(OgsU7ci&emO9Y=FyZ(f6={sxv)AnS@(O%dgwO^`csLgh-Hd-?v^062$EH z+C-K>vUAjKDc@z7S!ZqJV51}oh3fkfJJSA=X5;BN%sb$ZL^yq5_9|Lj(r4qTEP~hd z&VPL_;u|I^n+Z5}zOXAJz! z^2xI4ZA-L2l{t!@{zBr8K~o>lXn|yyiM27jo=-3Ba`k5#Ez*$vjl5z~R9WtQuU07T zr?gU&G7@zU-P6<8eg4MEly%TOjj_PAZT3gQmFk(6lH@Ah$~46Zw_Mb)>y8L5#zxoD zP|d!~=h3F48cWvJB`$AQ*TEsfn1qgz#b-)&rF*j_8b1-d&muE>r3-S)>Nl{MV=jQx z=M$7Z4#HQ-rVPVXB`JAH>xFpZ&qj0^H6RqFg-!kgcOfgh(_SJ2L5`XkD(Gd$9#iF6 z&vs`hSLn}fX}yAZG>e|{*U>zNW4sS7#lv6$DO|p@X|q*NYnrES8YIw_p%)W#GwG@G z(;Z$I-+QhO4K5C?dA*xo&u?$TLtE1BT09nMKiycVbKk!+aPV?wU0|63TYCBR(vlXx zu@_qS|1tMgQFUnBnl7%v-5r8MaDuxBcXxO95Zs;M?v~&#!QI{6CAd?Qwa>m)dsTAo zskUD74h?3T|JO(NK9Qb6&lYV~9-SL40Afx%>>{)yI?mpM#F8X$<_P?SqyRT(rYK@J zOKR^uge)+2Y5452oTnLRFwy6+m8gZ7K_-f_ICWqs3uX>DJ9%>#Z?bG4P@k`DC-wY8PRUW1(3hN^pTN_rm37siOCCohhIwc-ZZO2>D(>*XZfXOL8D5g6{QZF)f>m#t}%W za4sU|4c0Kp+vbXFPS(`cicBh+ag%CFVYIK^wH(DBE61gIoF3!Awp0bHpySeHBBY|X z%@8j2OmCo~%x-kyTIKLg264MCkp#B7&5Gip4r*^^H6!4NYZQT*W0&UA$};h6^*zY5 zX5@~s@jU6+HYNx4WEb}2HUY zPq1?oC35Licc-dww2a5UXOO?tK&>=(mARO-Z3{Wt9~2~GW^W29uT`NU$QNzHEPkR5 zRbk?yaI;p@=>rdQ)UtQ469bBv*h+|+A|aqxc_Hs9#mb1`YMrgnlIohdeZ@Z)o?vb> zhd{xFwCgu>7L%i}>ly-Y1$EN4d=Y%D>O=t|27)FeCPW4z@r5QNAw=O5<|Ffs^pycZ z0geM=1OVI}zo7Y#GDk%S|Ft{ecis}?--40fbk_B}om=^9 z&lF&AO@01i*!C^~a5O5b^`!1Ee*@2o`1nCmId zmFpZ+s%+Hi83mN`-h~GTHOryn#zkf6n|bD=lM%;7Prfq{?3wWvjQilSa$yXf0|FU+&#=w?Je zB%oHV=#!PaY3IBj!R~@pMd8(!xE}QP$=X^q%MLO9seCh8+}J0`)`uzhR<+^=zPIJS>p6L)V%la#tN&)n64}eTXxc{9H5R-d{=V7#h+#rT$UhOVj4#4+g)6_sBhj zSVqY;><82=A|aZxqZ)wF0_R*N^rK$;#V~Q;0MWpWYk_}>Je7Hjo?56pC3Vo3z$d>V zCy|oO`R`26P6c<+3rlT@dXSH895^C3n*Phbpy(_76!!598 zJHy80X*$yUsrs>|`4a|>{s-2U9*)Dni``p?5}d$X$X4e0+!oMY#%bV(!5h_R?c)}O zX2}goW-|`Ulz2Uh7F<5=K*nPXMx#=LtAQq%R|=^L3ccq1=p8?j18KS|1s24u2O3`N z?(){@z8>N=L}vEv@yZ=-Boz_-&a?r?XkJCJ$H@p8J6#sKNhh8d5`G2#%90l`po0}m zYgT9yEzt8}0k}z$Oo-X_*kBX1HX_4wBDP?dxlDmRnKu9Q0sCnDdvn4eN#YEpyTItU z@$8+{5KZ2T@(!_)4q4bn9?>XIp7b4Iq%kTk%O3#?Q1RY|%>J)W z@Yjsl-|{PO{^2it#J9DeQw*prdn&3PAn}~u-Y>!R$jD+t;I!WW&CY+-|Cr0;YF2!r zRti#$n*)T&f;?N2*_fQK(nz;kwYG1KcD-YymE)Ed%Dyvw@FJ{tiQ^V40pAGU3NA)s zlZ%Z;NFXuVp`k?QrBX4L-k@BlF;8uyCLd}XF_#2vyk_2RGfTX7inV0LNg4Z)f&pi> z7&~eyUy)+AFXrcX)>Kz<(==xY7cYn2G0>MPt^)}(IF~!7W6iVD`sgmnZnRaTx_#+v zJTNmj8b&Gm>ctY7lTgh&aG3hU6M1p2{$SZme|sJ(M~TF4Qlwz~&i>k1t>wnjxg!8s z0;_WzDmR940co9&k9K&X=VO|O=TYW7xA3>MQ|v7RH-=$f)a4;bI;5zlpexTllPK4g zTg=1_*;QvG4R!&SCv7EvoD7mous(wmSu4hIIH~}U1QAdv-h&%~>yoVarY$3)J(owDE_#iv0Nq5ZDE%FfsO;A8-0H?PE_#Ujs5Q zXl`D+uLCtfZN+L6_kGrRPbzlJN)&iq1hOSun6Bq)Y!`OhmM{GBdE#yuG<;H;3-`L2 z9V)(^GVG=+FN;Hpuy?8Zz(7J^wXS5ESpxAPbO5b$cR&If3*mt#Ks%q<-Xbc8XfxJp zz#Pt-ihd8v`@c=B{g0*gN0CAg*wFXikn*>1@HgH|e&UezA|GntEmu!bX&v!R<1kAM z;Fv>G@g%Fcvl2#cL&OYF=hx^KFB673yW>1URm|SC#;3jE2zK<$DmV9!lA-PTab-g4 zhT_T6J=@}%T5F23G`oJjckiyS8s%`q#=-=K(S^w`?$3l7B?p&NN#lB&EL(dwU+5&2 zlU@5&+$gPwF1(%a`%*{9B?`skca0P)OeUDaOor@{xi%4-MjoQks0nU0xpcvhz+5kP zE(*M&kj!7buTCC!=8mFxE5L+p83*?cR@_rU*oKbjGIh86Z0JOCjbfgWhB9~OdfvnF znrJUMD{{34h>1f(pjA%?qFy{cKBdaKbzjHnJZS7kJezuSxJQYu;T#3X~(k0+tH|8muBNz1Eg4cQAmT z!!WR0N^cWEw(4-mQL#L9myY2_hD9A8p(Cd{qX`S5og!i;oU?8TX)j z;QPH;%lpD^4{8CMa^(S2df*<4l~^GWI#B58EMSL-C$8$H2%p>pib2hT>gKA72AedMU2ex+vy(5v2We0ZA2<$ezOX5gPqGN}zu1yZmtFabj3jS@?<{JffXU zDjZ`X{0ws|AL#;vJ!T7A`TQEJOFhb?SpVTaMtP!Xqw%uTvpD(c3HzV_dgU6iCdmB( zsgO5O|5I>(q5MAz-0y%^p8p2iUj$CSc;epDAb^1$z#989cfb?(k?B#Kj1*9rK`Yva zvlEjCMVFr9l;M5hu69lv>9rOUQzBDqPiC{}WorJ29fELwa&>E|`GOR+74S9jzG1Mk zXIo8EQ=290>l#eFSm9(^4nerc=2i=8wOex7=FMJ9T12UzbZ^{!Rj9V0ol9I4Mx*O{ zbYU`Ym6?^z6Vt1^yRXufeG4^3RpuN+6_*poZ7E<;a#@jb1Q+(5EjB%%h9Ba9TOJ&} z(J}=g0y7cXJh@*z?_ZVzI;1k-LYEFkE;unLZttZTKZnmzn0)E%<4Dxm)j?Z`jh&*+C4)k?n~<2yqTq&_(c?A*o*g{%Qn__37(D#%gNx@ z1uwK-XrHsn<6#EG#@uO63m~IcA(hWsBt2Nrc&+B=?!FxqE*5DgMm(mmY|GwOan3KHr@X=f|R; ztxzw%VS}oZiKUOKuSyQo>GB(f&chww02zHaI<92_n zYZi1ctsfPM0BHkJrM-{c%jeNm>{2@n9@E~T-P^u5&bu85h%9{uX7n&`cLDsyQnl$E z$IR-SGyW&9K8q>`>0TqXZKFr1S=w)tA2h|8plupv=mqlEbddXo@rj-PwR4}~_E?V>kneco@t@k9UqJgu*_@ew&+Puo z*RgeS{BP@)ziAvvh+l{V_|<`DkEpZDHD9V+!{TZHezh$dDB3a+UE1`N`jlnl(x|Vi z%Q1~Kh*Xe-+SqO8D=vM=*_T`5PcB$0QS03n(Nvwa^S;|y%jNBLh^QPLh$t8zF#4F* zAPHc>s|274FM|Y(G$lzk#O=lf6F~<7&0j#GYLaly>Pgp=x1S2*lB^q&Bu)+lcdh(0 zl{mj+wbkO6;0KgMoFU`JKysUIsLw|A9!YtRmXdFpXQO7)cjCH#bvHfr&9Kd0WdDJW z+q1)(R7-R#OeZoyTDmNcCfay#`Hkb>Wy@>+5cKcg7rml6#-`wrq9SGG%##NQMwV;2 zzA}IP=}6(Qks_|xI9v}XTfU}@@OZu0wtL_1z<=d0H~#SHHYfs!En$8L%x(ACn58s+ z-tSQ3+>1r>#K2fN*G*vzb~}L75J?ye0ux1SzdYs#{v=@}VQ4O4I989pYH-<@!sU8+ z9vEYwX}GPgtdQ#b;}aR&YW!prO|P`*80?%>UNItri0nI}ZY1mK^bm3p{%|N`(hn)V z6HBY}<{3?+$LWqP<_KSb;(xBwKZ-M#e-HZo=KueNZ}N-BJt0BEW|09i@NG8UCQAXH zhd|wIlHAJWMA^z^icY{*qf{D%oUhsJ)yvtBQ4SUgTHtu^dG!nt)D52J2yi9}<`Imw z2RU9DqdepD0d+&;YjOiU@b4PA=IjI+AmGJ+qkIHPBvQ28l!K1 z^XO^tj*8`vY%*0*z70Mro$^q?aB37L&Gjfst}poD)3k7%1_x6HsB27fItkX)78Ce! zW541=w0XVSd)yxM!P!b{MM-J#R2pMz(p*(1=M0zASBS!v5_D`P-Fb0SJzMQ$h}2hA zqQpZNF$f0h_LAiZXmV1%iS8P-JZLu+JYU^}EqP0)EKb1NZQ8I_@CLiE-#7 z+Y5YU-yh@|mZsw-UpdyMD3;7)-v!aj{>m3lwGc!Bie$$`hRRfcNG40^ja71fUx58xGwJTOUBnts8>~v#^gT>QW_?>%~-VWhb(j~hy0Ag&W`!=Bi?9?5{eiC-F z@#h&Ru?}Y*j6EHx{3L?5taG=My@XF$DMK~G=MBxluQ2fBE+RLVOCRHz6R#ulWBc(q zWtNMLUmP_VnMmy;i~=Ql)h-n@5rOV(T!o^w30koUV1Yw@ieA|S5YNdee^O@|Dlmb6QWjAzOj!1MYs^{JR=_GN>&#tK z*+_^E>>YWbl#^Rts%|&?aVIoa=>c}fEUXM0kByBj=qHRFp^eR!?V_|AEoXQE;Wg)0QG^sbGLQw*=VP-lp$5*;$wWD`o=CdFRq zWy*YRRf5| zzBXP5Elt$Xoi5ziJnlHxocpL$;GC3rH~x^K9QYhZ_*s*3axM~vyQmy&6b*_9tscJ% zKQ7TBw_y@S_HZ^D8E`b^{HAtFQ&A!NxEGvqs2kjfa^3Y;BNH7NFrv5<-XW}EsNsmXgY15u^GS>= zo;`ZDA1j8_w|AdTCW-{XwhgQz#<+%bn)2aYiaNOx zacY|Fv={$QOZF|V&MtfDL99J|F4XUBEpzA2oq$n-mnX^tr}&r=jKRc(R7LPq-?pkr z-!%h2MUt6Wd836yZ|q8J$ZJUfaXAfC*!6s9&)J0_$nA)=ndI>0^`Rtf?`=;{mhM`M zpmdyI2-QGW`%;6-x7ko>UgtB;Xdp~rDWFgwGN35HW@IGb7@!XzAAl9X6hZc(_Q4pC z&cR>)GFwjjL}I>xaNv#Te{8mXf&Cw4wg~>+N&XwN_4nfEZ;tFFycIu1{<^>AVJAO- z5q&xEe>P(ro1*9enlTa#r*d9faR(+%S?gK?QPgs8@A@~QQCn0=dG~e>?b4NWV&^O~ z%SyOPFCPu>6FplVtHI->le73ZFpMLa)o5aM*p#YP72V|HGudW1OJNHPhejOm%8UnY zD93KxF658HINi#QPF{?^yLx#^tK6zJQ61KJYZu<$>$jFBt?F;&f0;{aS*$*lz5po} z3xGTeea4tQjPWUK-W_NeEu~6bB~^&YkXkfh&HHs{YUPuUr>!9_E{*M1X;k-bS7$bn?ZK^4;tNSMV$fRNk@R~A6_A(b-- z=|m>w1XsrSJS&bc2yZxyY`TS;i_KNw>8g{Osy2pNd`X821>DVM`Z4+IVp*P!o=Grz zo}DsQVX&)DK^UFtjV2%IRAnEB-J#{q>}kLa)SV8)u;+4 z7Jp>eM}42%GIq=W_~@u0R1rli1%3xiWji?uvqlbEDHYqAQQPZW`@x(zduBlC)6nS4 zBn;itM0Dd*LnCU|b`vogZ0Xn13G=>{6MB&bIjwA|sW?rKVB4aYI3(C3&vJFiHrf0v zIWvI@5*}}v&~p?cnz7~fL{gx3yZFE^M~>p*nIKQX{*B9+an<2va^-sveJcZ~UV&Xg zaaC(D1Fz#7!`N$tCH;wTxRvmQ`ck(kv{;v;yoBIDC-|G{uRRnaMECc(H8zb$bniYr zEINrwLJUQ`Zwa2n9Y>SCF!?3ZOpu=ywrS~#%^9#jV)Qw8*R_T+2Y#^gVy=s!(_Ogn^pnUucG{PX zM6%yG=cSAPc+Y*lBL+|R)0OoH3Ed;Vo#li7!qw_ja|m}vn4SB2=~QDw|JDLjR3jH_ zW*^E_{@L4(x1;>xBunkyw5$d6y~SPdahn}eUDNM4ZUphzzX zmIx#nfq;FXI)HS+`hohPWPxRovOu#)I>0)FeBl6R6fuA>fKh=$+1TgZG+ze9xKIm*4KN%AqYpQ3q~U~u}G ztl3e}-ehWVRQyKh^wh*7T2Yett1t8vik{zl;!JAM!9g{7fW|7MjzB2Qlv$kS&|+tP z6yQkK^%N8h2x?WbvdPsfotW}`GSi-}v6kShmuswGT0?R>7|hBVQ! zHd)~;)ZB7EKY2WvdyL{e4bHN}Mji5;7^ZDhUlNY73q4brB2@kO7*ys{qBBK!8%1X9 z;qCBgE}RNn6AHraM>ufyT4kt9zcHkyd2wybyKrVkh5~gEUc{Q{f?G*-O~RL8v_-1c z=Pd_=`J}x-tcpB7H!@VdF(0w97zAeilHfR13!O;o;p*-fv}z$LG-^cEScA=PVLMxF zxS-#(S6dw7Ul3$sL3d1gheuuA&Hb?dEp9-Fcu}4ua}m$@F~me!jlhiozi$_GTGK zI_PrF_|A58Oq9vLJ!?}z9_JMEtap_8FgHVb&5Kb{XRg%JvJ(>K$6gbwGbp& zQA}f~cgDjmFl?UDTYw>%J_pj=aJj_LW~WR+^N@~<^&Td2SsI3UTd#=rJjfJ0zMJw+ zD6!EJT@+X5i446OJWFOIG>s4f1+-4AY>{0Txzo>fWIHK6bwSC?P z{50NjT9;F1)knyv@2@ta52&V*@!#*8Y;P0Ci^M^Jk;EkXu4qP6C!jeXjUCHy1h3)o zDg>vB(vQ+h)6-cbLy5;iAJA*$rFEFtLSI(eonE!MlC(VMUoqD4fIxuo_`vuaf!+bO zD`5dG04oA1LS_No+5hV?ya@+FEdwNB-n9P@@%{xMe-z&RaQ_|Ne~ZN4(Eb(PDl0Zi ztcX6hI{a6~c(}-7-oaw~ppvd8S?J)gP6TTa9m4S)X9!F>I3 z_4!ocN}tG^@@#I6b?8CoXUlSklNMd-g>sp9@FyK>PFc${a0x|~N$u=RTJ+61D_Yj2 zCe)edIjdGRRQ}^?YpQ@=kI)Eo2yeJ4 z3bvr0$odeQ#(Lu`7)1h1d10DAh5roPM^b^sx{n1)O3FzkD+(O8nE5mTJQwb>H-o1P zbJboCxkqt0fpZk|E}H}-gko3Wz&S{7P9aG#6T`8yS|U&6V>a}-e$Z0QK;W@kHMlG!v;bt1N88XcSjI#j=p;)B zoiLkryASk3S61(PT%D-N(kz^UoccFC#$}kPzODNl7_TMYzOarGWy$Mpe0GXNu~nPa(PKina!4N;PpA3Gcp@bk!34xzJYkxC zx^?V7NPhc%Mi`D8vs_LC1D$e=imio4q4Hbuz;rVIz{E)8GR6m|-Qkhe3GIxAzKPX# ziYk|IDa;hE+oL|q@|ja4M0ebN+`D^7A@seK-)olt zYQi1{VNVi}0B*>;p?6suBx|PYZzrVREl&@%ZeWyE77mu5Nu>Z&nkHm8GZ9$060nlC?8;lrdZ)@fsy<3{K93giVm^Az;Der%YZh z>s2o|AeW-rLdP~Mis0cYp?nnbCP;lpRKhy?N#r@Q`V-LQKf-v>6Il-x?4u= z!0-==wz1@-;j4}*!t|yA$BExio*G20Im1c0BE;f*nKgT#O7813UxIv0Q5&I!(h4b! zphJ?NC5_A5VmquU-f?%cOQH0-3b_puqX$f(Jgt1Pc{s56=4AW>lWfE_%|hS9?)3HW z(0BC)L);iJZ!rpl!;f?Q*S_#U{#wgcfb;z(gx_nO6Y87 zn@O)T<`0wNODm#b*fb%y#+V<2EQUVode_xoWX-d_Rso3uOSdxkSLT1tVMdPkrjsTr zvqz3efLt;e(QS{*Nwih0t@>8$tV93f+>tZelUBBZrYZ=e+(=cC)#AgC-lj_T_yXrP zSaxK8BcvReZlrZPgt5E(FI{Bq347Ztm-q7T#4%NIcjp? z9t!v~4=JK6b9+vVqarLd-ylgoqQcE|R!<7-VRR!BWZBy&WsjaLIC=7ze?z=iHp6NV zWL7^InaQ|&Jn7z-z;&eC=$K2M%y~r52x6EH#Iqwy7ps5 zqStY%*6WxX`|9RF9JkA_NX}K5ViPN(sUit zbcECkq6mc4t48Nz@=KDR=!Z{qwIl_a0X* z2R+!IQ&;1vr!*E6wAkE|8AQaV2(~PB7hCXe`!%3C`%YOlkO;X6N%%bYM18Nbihshq zS^9`6<>D!T`rqX757hq)4gFE-rv)@T{965g>tg>+ay=mK-&P*Rf^}G|^G;m*P5drQ zR@0{?YMAGwvtTIUaW%ycYZijYd`x8Wq(7bk$(aWbyLUX|=~gg$+MROKgiP|P$zQJ6 zTs5CfiQcmi29HCLAtgiFy=#kyK+TWgJ4l*D{Xs>6WY$=(H3xQFCqIpht5{Amui9LS zVdu$(ua^k>l@j)(&9^Z;Y_On>bB5)+Az zFPKmdF$l$$NFb#y5?ZLWN!>!}YqVbm`&o*-FZPV0@7n`But!#Ul_u^(S7y>=ZjrvkD`X5qrz5jw zP6mlEo1tloTT|imRC>S{GL7O1gE_EnRR?L~<2iJ$21GV!7LqaO#W%bdX4uN0JQ-`U zq6@XeMu5dE@$XH^5T!EtqXZ%|JFIL2mKp|5X>2$$x3uTEbo~rw!Kpp1+yQjb)sAbN z7$`ipDPrd49pLY5b4|@Gw0E5SWlCsGU$2hy!T}DrSfXu>YO!3{nl;S zI~*x>OK57E-_aRGqp=o$@EdF@Z%Qg~Y-9S?hsdwk89=p`HW%=OgXYv-KZ(hh@)^m` zpPwtmmkDW-*-nb^uzN*svGrk+r2(Ape$sr@BcY)(RQjdsE`U&X|O>*368tmwQOfVXy{~?|;yQ7wAd#rEeZLAytH1oAwJcDIR}F*2owlMiNEp za|~6RPPH8OO>f$9aU80|+BizT>U&n5!b|F8FF@J}*Ow9GY4ZxZG7h_nG@9tVFpbu1 zCGzgmzRS9fD4#r9=;~7B2mw464jH&??Fj<|3F+mfY(vk$(;wD(S!}QYd!j)Ad!oSs zd!k_hd!o_5;jhycHM#%QDE?yu-S-th%A1t_0V%)G+aE>BXFyi=*Kfz)vN69|ffx|~ zZ_f$Ff_@>v?&ecVOS{T2f9gymB-{h+C4Eu%ze(l8?Ww6?7*=ri<9Z!|@a3a5= zKpdr3_DZ%37dcj>H3@@meQo(C)-}W?NtR9MM!hmQ#slz|dCam}x(VDx}TS z%^|tKHL>>Y7X_l<7#fD%In}+E)r_kJy*E@TVpmTnoijdD$B)Dw7OqoKf3QZ0Pxhcm&s*mAX2{NC%#;9c(fO{nkh zjnfz&#+_U=J{ey>D-sz(8?X~0MX`rWxE7__vr?axGWApHbA@w+QHC1WuRJ%~f1svN zh<5sjJK7#@Yu^2WdoE6({&-GFz{zs7l-!_2lva*7sti@g`cB&IlJacg{LK9mJi+K7 z#bZT6@tdPHwt%;6G>2_ra#k{})A)$R(%A7j?tN_vb`Eq76%8EyuX-6ohX^4qA3KOA zkW=%&-kRyJzOxvB%;1~E{sBS1(BU6N5FOyI#IFhZ+xq6;cwZ6m(qVt?Xu3z!r(fWs z(a5Yrd0#WW>bx|*DuGayE8X4eOhYZ+od4>z6IEulKls^q6A*idrXXE-or~`t@Sr?k z0T7a>4T#Z*egZ~#Lr56j);9#h7U8YiSqBELNM%Zj|_-Enj%Qvec{(Uu~$;NP~Emil&+fmc(_?sH^*h^U z(9R3wQFJY}bFfj9uwD@lnLudTel}YzKZN(*N2GnWy5=Qy!HikQ0|%}N&563M)Z4A` z7BL*NfIT?ptF3=-Fb(#7)y%Z|=Io~WlBxF^$wzj4hfw|ST!tKfdOjPW=7dH#IV<+; zY@3wZ(5ec|_Kmz{BUDF-clO8udL2+k%)0SW*etawCeVj|o>ks^Rzq9w3z&0)8PKP` z(m})_*qFNSFGAX%Y$Ybg3?O$a>AQ}-_$VGr6n5dYRodRfHW51T@7;9`j0V!#?CJI! zHdW_4857qi%j?+Sm{&ekU7kKH)>Hr-TdteWmdL)h*Tg-M-*fd}G5y=i zBnJExz!;(kXmteK`K_%lffQ^BsRc$BjAd1NeN2f0i>`y9jP2q&YhkJry#a7#JV1x` zfTk$~j7%#`$wv4Bnla0Z4R!Gju>1@*IgF(5wlhid3-cUu(J|Rv@%x0BV0PXFtyvI2 z`U(}mRkp-8>LJ1JSIsBi_4UJkN-TeeZa-lpaU=ump3_6lgyLdHg>Ztikl<;`T2uuGBn-4D(GI&W zjp1dTm{Rycgsl;CPPWU)Yp5KKp%ul3zl=aCQX3W_Qw{qzzZfP3Qn%xUa=$5({+A7Z zcy$*cu5d;e({9EsA|cOJ92_1!&4zLm)3a?J8v_SpTEAoAz%LJNfl@Q7Q4#ZS51b_?5@A1se>%#V~1a^2dG zXx6<=@g>-Sia`G}NJ4ZZ!~~iJW(CrN`~kdIG=LP^6*YSp_O^PcNCp25Bv-lj6`~>;AJPwgA<%f4 zABdjC%nqWrd_roeC)YDu@>?#!4UkJfVUVV!C>eI?s|7=VY*bKt zH*(o!Z$EAi3eUT>KW86X-wsO z1!GKsQRdTT_7xW+AxeG9JxU3NuIh=9+@Hh;t})XZoldvI8sd{M-#ZhcFh*L1No*$S zj+#08UEw!JHkJ!t5qjI%UzP7%zbsRqWg=o>oZt2aNf&HMerk;%*9K;#>u8B+(`kML zIcVTJOc&ln)7=6YmRy>bGA+-mvx&I3g$}e}CxFTKYF@DqsyPW;e9CsrCcGPOZy8_LnnmPh5ZH^4BibK0v-Zd1U?Ns46+Mk4eY{a&cAg3*H}+7dsm+l0Khke{vJS5 zfOh|n(6)QiS z*_^Z4&56~QLXJr_`d%KlD;M>J@WF78INZlPBCnBz*S}CzdC`q9HT1(6iVUb*b?#!g zs`?xezK7*Q%qH3-694iZBvS(PV)A`fSmES$)hw%!^`HXxsJdTQSZo4_iPi!3@zH~9 zdCtClV}~WRUV(*brhS8woOc=9NO(~UesxAdaoH(M8lw;*i*0$SN$ItSH+|%Zm@2iw z_Wu`-K@cY#^fG=n{ZQc&P(kBH1IvQ7vfX#CJ6OF6e;NE7aoNNul8zft_DRGk_)~5d zg;qcD5mJDq-=JW*!}H=z#nyKNhNJ~;45}Jw<^Ve9FI?JB(0*e&JUkU^K3+g|MUQ+K z3qxk59V2qB`*49b&$~SA*tMX2XPB=I2vxg2*1V#(P4!zvKJaJVJR?U(R{fT4m_5Oh z%psJJPP#LM*a8;nE;NMIxDbz>!oF@ar$e;$*9{v8wga|F z%<|!HVUJ&|7jO($0@ZI09(Gpfa)>+}%;ty6(SSCZPm%3_@qq!Y9>8`$c)(78bukWn zTz>O_bur7p`d~kR2cVE4^GL*2Kq&I-rSJc`t0k51_tP1G=$nN938KHy`5%So^6wG- z@SpY6-(*Vwh}r<^sexyg)MYDqac|j@`pG=$#nZ*|#nS>U_HL^(bg_5hbuy1qgPvNZxBl==B8^NYBpzg9SMB5vPX+LD;Y+`D?h9)UInnYsso6T+D*R|p(f zX!4@5T8V8gVKylp(#NsVtgSC4imYvCtk6K=n$Mjc8BK;CaB#ZPmDJkh-c(J?HlLdR z(T3|W_mfoY+xY!rBDs0$zJvx3{Eg#_3G3t2&eqEFV`Y62?V+*p(34yJhy9URt|hXb zljqdbh1q1$nrE4Xs8qGnSBw)o$ND4Z^edG`o#!SGx5k{WMiP1AiMIUtIZ|!hCFtqh zl5Rd|x=^=7IZ5PGr=kO6rSH4UM8qWId*x@SJcRd1_z5<=NVl1`aAV=w-`u&& zt5p1)fNYZ&=tefRD08Wox{?2gcmL(5D6E_sVW`=@ z-f4g<64<_Ev7EMfh~iJhL$31pn7pG4Rkc#I#n!bQ$i_ybW zNyP=kNo`%Hf|bj2ltrRrrw5A^?8UHV21R~8F6Q!}q$#B6R{sRceS%P9W84m$wceCT z=9AE*<`-svUmy=d^wm)sAMRin=c`cKhfaMY@*f9hhQ6+>eDC-faau2>pGb4q zJGs!qWqTQ0v8?F*vT@x`3KOJuI%2EVYl`6Bz38{7AENa$)n%y6VN zi-Y5sxHO_=J)4q|W8jVT?SEw8=hs-JM@#2!EjKJl&_%l}%p4WPluv`7_}HLH-5|%e z*ToGzy0HB0Z%u(05;UU-k7S7+NU24jNtfV47gux6{U z`!OQ+sj7{m`FG&U&%SKS_CDsHelh?7ppQ_^dVU5ozhnc-CTD#<>u*w z*mTL(Y5Oz#3QzGO--kvmcPkz^o z7F(i;E~>V~e%!lnDl|8)NGPJtskQQy`^=MP%Q9|LYPx}E*R`wlNw#tcrqZfh@sp;q z^qPkwh?H$%GxChZl!bsdJ;F-3h6Cl)4!1h;vBEn^$Oy4PBVGg^@@luA^1&ti8H0Xf z#&h?bD?=BgtOn_8o{Cz$qM*{bVn?~#+}*pDw`!tcJ2k)qEYx{fhhbsC6tv> zc|dCTn~`&PX1%qq!J?gNhnNs$t6+(=AzHYU)xPZM!-&Y__$Sj?pfJ@~2CE(wvad>h z$z;3AzzSK2AfO=M2?A-b%C%4MTRZs-53R+0)o?K9@ZpwF5e)PPk=1EQNHQ?zF2s6s zrKG&XmLLlZ5S;103Pq@YZe0zJyv4kmJ$D~e!n#y$XY>vWe1(Yl-t#&Z&KN$w%#-24 zU`Hd#knj;EF0IrIrCP~pw49V7nYdRNGs%<#VM?RB7Z_GN>S(YInZ&%T z_73WpC}l#~GhKqRA?+sb85_jDASs54J3jrOET{<_WH4aZL>9Q$zpHG}K>XM!&E}zi6%ov@3r4!3Stp zd`4p{8}D$$_Y%<0fwq1}6_akqXeF8~roox+cX5;soYz^DpSskzw_LereAr}566zS^ za1D>6Em#6JNBm_QrJ(v=+rJ7ZloD{_R?MK!X`@atI!VHpMq4t$fi@z5!YFsC7`QCa zZw_R(DiIr9z49>T$eI-=-WWGuo@8bFJ&LvVLAOi;`ii#`t)f)}i7JXeQr+g0^#_|r zu$Ydk^q{_h=i>)67}SmLBw>yVh9K?#vcie827`m+~qg+bMK{npE*WJ)bH~`H)j6d2}F3=9^wQ>v!mfxMkzEYDHU;E zJ8F}bzOGH(=(*OmTY3^lD-%+HSb>K<{MAY5`To0+Dxeoz4gY8AbYBUp8bpJm|S-vcGDaw(R1_>hm{5 zk|DE2x7FnO^qMw=Tl)-|OI06!w4`WrESzNi3((x3hLG7`LFKpcs5mj*+7J)|0$awB zr=N1i-7Mo1y2ZbLa8klSXTnNCo-I*?DCngbY&%m?5}u?XFaFS7W*;lgtCP~LbXVbY z(v;X#*dO_DecO(`_%fV9?{f8W=-kZnmJ@{3alP1$Q^N>ZU6Mlqn#6ex+jUib z=#y$2_!%g@)6jB;Rded%|6=Z~g6eFxwq4xa-622-?t$P05AJTk-QC^YCAhl;cXtm? z2<{&IpJc85fBQ?3y;r^KBu7tG=BxluU-#(I-FJ^cv+}V%vN3X5xb>Sl!<62D6Pg3) z;+6&u1RJb<>pQcP;uM92TwSR;`-isgevaS<-n>78qM^D4N-kQ?Xq>F7sHFruNC{3W zF)!IsJuU3}pmBea6F6zwav_spU6Yu=7ebZMq7|UtCe~aKI0ZwpS}A4rsJ<-%c$^|r zn&<1QpL_Xsmyb0Gxq(X|4#BkHPr*)c*du?nE$O?R@3#Oz`7&<*7|K@$_pgG|8{l4i zEhheE!umtSkMP)E_sIfIJYc>Q*o){#$CyuLAvwoF~`i z(CE%a_!^Om2yG|uc+u8bDn^|6w9=4YdhL*x-rnfH*8z0rUg|Q3cilp%v*)_BgEX#!YMs|UAs5$ZB+?>_w8S^G zP^xz^%>LtL-uWyrmv>60DN`nivWiDz0dlAdrSjjX77nD;lvSJe%BL@_*t1+@zNo6{ zJHo3L)_z+Y$`om}D{NBAw{4oQd;>*#xbZOj$aRP+=(qH#jeFJ8z3K4@2yH{<%lwr| zgS1tZ!dngGA9I*BWZ!VEaO-(`##LnR$NQXJRo<=89+U2o9+4i9p6G3VarS7_r=)|5 zZyX?xkI~O$z1tTuwBd542>glnfj=;OBKT)^fvP7Rm97v4J!*_7Cc9yQ?hZ%r>45vU z!5!@I2}Ey>85k!$U03s#_aLZ*DpX-!8fyf^u+3zVV*>C z^7B`V1oOP-EP>e^;o+eH@Z#s}z=B)oFBaimK&dfiFa|OCyKU2jN40_`k0sOQ266H7 z_6Hrn_23XqB##H?CI<+-7P0G zbbW$-FX$XEDy0}k4_(fW&W;bJ3r-NB=#$A!R!SXUgUjn}@=QXG?q_L4>s6nGmk4ji zRi+yI=M3Msy*r{li@$?uW7PUu(KSD!`1s%kulytKM;NlKP2cc$!<-Lzj}!%?iGnl1 z+F`L+n?0l?+k*Y;o)y@%P{2~6$*3uE6hFVN7)X&yuBZL*5k1uI;V43vfrC3&M>Bv1 zM`reA`|OsDF6cy49CT_;B_zS}6Ma>oHG@bSL5P@rCoDmQLh#AOXHt{vdzUV)t^@{- ztv7>;pkKc%uV3?%eIDT8-$2`g0M&E*fm!gm_jr5ZTl2W_s~mvlnw*rn)2n>_B4{Q{I#!!n;(+cz7Ztw|y# zeN_{OIp;kmh%IG`6f9;y&^;$Fh9AIWYJ|h+lTa88RM7f&ZF6K+jQoqQDun017Bxk+ z7&-Kvq-2eXZI$ug7ni}Q;?x+QeE(_1zHSq}Sq-4uq($1gmBoJrd1>Zprgh3Z@dn|D ziXusw-P0xQ-ae#gcP^)5ji9|{+0#=PE>=apYhw`jRgtOt-3(_x;>_to z77O*|X&I=MDrpUK0D2#{Z(Rimph1?W2T`HO75VYCR7VPwYXKi?D=4#>$!g9u5*W6N zcvK`IKZcM0*->NOXE&*w*XsfZkt13!Er>yiKiMV6=H0H1ETth?e2f8H5ZSq|2P6Z2 zU$&YzgC=d@k3~LUO!}}}dW1Cd13H_sw|Q$PQ5nKE-|?D+3C-ENugRC}Evr_%@-wCA zaQA@sfscTttp17JmWi$0fuQEUHT{`oDjcdhzMfA^Q_*8-#|`S!~g*Yl_a zt92tE)235@Vz*~tN5*hLo?Mo%*5=&;j~O7EF_Xf{FwE-FtR7sy5o1Mece5i_IlNWg*QfrSI`B2AW@$HWXYGd{YVvOu?XK zWpycnb^UoCuG*Yg?$*>L@_i~{zXub2_vuO7l;sGQVni0)`O%|kbLT9X4X8fZ7a!## zBe5$k#Z;^Wjmr2&Bbipj+#59|nRm<%7e(0RpDhE*pOCSySfbEjOZbd8Cq zuu#r>jc5s>-f1#eiJ+JehA))&wn_P@veOh~1}Fi~&lF}jZ}vqH__!vXSw;1ec0tnn zjt`5O^C!2Rar#yx97XATKR6h-)H0~7Ip275BeX*`z|yR$e`urxVF$tkMga;2g7*^k z0`DN{09{8$0uF*20MZAO2P*Gad&y^Ne}c}g05tG|$v@V>D=ht08u$tL<1m5#m$&QJ zZp`n_u0LFkjQ?LEnBjb?uZfT1Ri!oZMsFoX5H&FpoUWBx?0RxJoSR{k-Ly*S8FyMu zK2sjEJ(aRNuvbgTT$jJjVr{~^gpqIWoW3{*;cEZ~L+1RbD-}XB-g!UfRnLUOymXRo zJe*xcYcPu+P-zMQSVCUo;H9=SD+9>eD4J!-#a7was$o7gziZ-Y?PV2t ziCFm@GT9)c*nBca;draDbk&aGD&CT}%4qqup-XMA?7Y5muBByJZ-8DG&$icW2WF)A zK;vSchPk;g%*A?#{7o+k72RRWE?KYbb^9?DSr969T-RK(%t*Pnz)Uo5QBkf1?XWD!$ij7_fPwqmHbgWuc>k8*X&eWuB=u>d26mO>?= zX+mN1$(7=X*DscnYNrl2u7!4V-X8C|aoHYMQtvxUt2>Ew1wpt*w^i$W7~K-(MeYDz zhl&Cc0W$((2B87M044y6^n&6A6j>!A%>t1|rUbEy|5r-%B}?7nCqN%B2>n?fBmlTx zH(&jcv%g9pg#c^Ws{;AWkn+b_V)Uh2x&O;;F-1@l{O9)>Vj@KFxXc|v$4H^fEX-ew zRo|HFWMnkj_tyy?mRlU2{J0~yR|N=!&$3uSE@f?9*&&Ou>F^4L=EqLS&akisF6B(?dn+DW>(86Hmh5Co? z$MjP$)6w04o%36Z#R*~|3$0lkuoYZU?EFB6ug9?w$oL+>MN>|!*u9Bk!ZO!*Yx^l% zUIHczJYN=Fwsj7*mpwuh@6C1Sw>w$f@iLKKmU9<527r=;qfy+D(BmFYpgt-*A@#dg zu}k5FHul!L)p56tnMcGHXRQb;v~Bdkie09JZThJls-p7+AnW;eh{%p`d}Kb{vIdrd z7BRtmhz}|1>LZ8;FXJvgAH^?8%Lk(&UoYbCF)uHF@90*SOB{_d5J#laJvjV@Ie%{3 zMs4{jvoXhTr#zGW=9oqbf-Y5OE${KPb`ebb?)X8m~eCXG1PbIzVTV%kA%?n%qTsf}*_*W^%0^l2YRXM*I zLjI&2#b5aqpt*>$tE%+DPf~}&GC!~>8$Q_5m;6c&kY9N;8aE$Sys=nrf*_xsX7(6o zy5JfYoknw~FR_uqyk_IXcrULi$7~p3=s7WcQ42ch;*X`{Ch;R_k4$p1zfS0C5l6nv zqugABSwWo25KxKOA$-EnI3%4f&_s#FXp%W~MIePB@}v3~thyq+#WhPtYV9g3Qv<1e zk-)5PRWs`eD5tapz4iL#^d@tuoPc=|}E9RrsDKg6+ z)+A%}O-Jc|nUzFko7U`?)NdM%=7S?=`Y$+d7%pUfQo4`0%YWFn%@F5LoNQ}bl^Y2f zLN^eDm4ZsVU6Q=omUo#s!qu1Q*F_3_ujnAxD;H=b3S5<>M->#$k?@}R-e$at4BL`Q zP%ay6l-3-+%MPpU$c$1NjO+bI^$#TRbd&UtpXlfL4F&1*$Vjo#pW*A;Nk72(UPLDb z+{b!tPVqDr&fZ>)<0*S8HH{HHsT(ZZ+mTIZtI42MV0=vhcREqV$%d ze_L7>)+$l$3Lj3JX!7InJ0$&i-^3Ht*v{UE?E7miFLB?#lcrD9!9X}(PLQxbp@6d1 z1i&gl)lf*lNXSH>fX1PSyad41K-3_1Kz1~zVw{g3@9Ufjo__VxaQIfF zj|m#AM|Rk%4>Eo_7WkPz2FzVHRaar_4v}*QQr;+j-Xs{~d189F#n-5r4-A#F*G*7+IkRkNzXhpC2K`>3({X|E4y^ExZE)?kc=a9+`4LH^GoUG zopP^uy!^e3PKHQWZMci<-Nf#PEYu!$i_NiiuCXKcAaAyt9Lw=YEv*k8p8RBeHHFJ) z2b;3l&8@15utBK@%_?X|b$8*{JYG*}LjJd!EQ9umCEFj4-(4nLUU#WjPOqM?hO7>) z8e*oGtP3HB<$AcczOkmRiGvYkpy*gT8FDlSiIp-~1`iY8wIOyF%4j*V5c;oY~Q!427U6AQC zw~kpqv=lTlSniKF7)C}DQcZ_Nt|}3cUa8cNEpYA}TR;VN_ z7+XhToTCX-pGkz_Bzf%U91a1yU3k1SR(bz+*L%(Q%mD5^9|P%lTY1&;auU@L*}}F^ z;X~A|VVo!;vTZe#J^b1qsMI#0r zpINS0S#7KQlEdtBF#(h{?o1RSe~g2tI|Fc7157tLs#Aa?S4Dpi5fR}fs~V>9ZL0bd z>7ZD;qKi<1=iv0kcbGv7aej<*>{zC;7QmMr$*COjC~mKJ%4w#kE99oOj^)12D9t|oL;_13_BSkvGjtXzk>gY1q(qpV;zGtUR=X-4)+@W`9pl9|->3b+ zO?&8`MRah#nphE;nYrfaNBb=mKuwfWU3{^%(72U-y?WwL5kF)p<3uf`qDjJ_6F970YRvbj^a{`Qr|{#r~)Fe=|8i1pDPQ63@caqo%w}62RP*7 z6zlfe3ks2N2{(3a1L32Yg~}{h^Q<`bHXc1jwGln-cf7I_q~LGt7#a|LYT5s>c>sa`i<}LO_{^TYSSgRsI+}cYEn_dlX61J5nyd# zcA{D$JD`S+GGrAb4uD|~kLX?0F9Tl|jWmJ?jmRsM#b+ zX9UvBYl^zmBZ%5sx42t&R!KAMFm((vTrU{@yaKM84Gqn7l6vES%^vt>#-_mUo4`Bj zCyfIpJvQ)H=qH{W-^tzT8lo{BeKn${2i4xau-mPwY}X{E)q!;b?QQ z&}0H1TPlnm)Tp${`7(qmbXOoPPZNkWB2GWy3x^*>xQ+tN)jg`p-H14~Fxv;;{cu z9R48Qo_OTz5T^tX^cu7OV zQSN1FG8z2A)WtcOO&HRI(sfX8Oa{AG@0&DiSMBkq^K?_VEUvja*?A!GVJcsVF>6qpqbxHd~Oc|}Tf-B?Y>WRfVk0B^d-inY~xI+0Z?ujkEQ$i0dirC+=Ew z!LMG@DRhJA0qcPqfEFzI^a7`=E%kRh0Guzw_s`*cWsLtSI5_~$rdNaHZ_)h^htUer z|MyNHLhBhsmHwdSHxx>{iPbt&qmwRLeHBTBf5BPxtT{+%4F_@1GVZ#XgDAF9z<8eJ z|L7%EOH_s;6)*kZU2wDnl+D;7mqET~-sLG6*QIB`r=$p+ax-MIF`*_XlfJxM=W|ym zSh61Wj+32TslimNxtz2bP4Elpajs}lp@lj!r%_NwK}Ni}`!UW~SS}g7Rp?RRcvF<8 z_4M0dxw#N0cxy2yDdiTQU?>B~bn6(+y+*bLmveP&`#g*$Q4_WFsCLsLV~8j7D!z9=fWyTe$W?s}d3tMpdZInLI6JMb zwtw*EnW5k(jVzGul+1}xrvHABDmkNE7igIHyeh~@v(+;5q;t2w4zD78K*u8cR7a;Y z8W9<$rRehH!Ml z?upw4YH-DB(GGY4ZUN*J`xJb@5XsQ(mydGixXLa9fbGk`{&Q?!8U4Qs+sZ#-`(#N%cW$%=+}!?~T! z@>Ck_RA~3Eu+cd%8c`azv&<;#4*t`9CX=5cXm8w>Q|$D(s7y!0oR>*BP|=!dPGkI7 zSIM=RfD*)t=qX=^5W-Z~>0tf3AFO=jI^tOmYk+5KpX9dN?cYeZLz zf@PLwlq&MpJny=GNqA|g7!q`~XHmO@ozHyMu>u~eKR)X4wmsSNkU<3^^J}RTyxa+W zH86E=@-|Cc`}*FgUciJH5eLUE7mdY`5!L&0rE-YPX5Y8M97FH8f;Lv1%6@!W z{v=lXRhDLzFO8HR8Bp-q&zj{RDeFNP^CK06K!)0&NNpsb~w(1UVJg5>^OoPg$P;a@D*+PMe` zv@$x>O$eb?(&<+nf}?{^u4O8IBHzs)jl0W#Gv8_mcTS?>O!f;`XhuvQ80e>x zxKnDPZ(?k!CK)M^s=3WrskkM(%*tkUMw6-`$k;eC@e$C&F|lLm%x17oVYx0nWgC4y z5EeM|upjHTAe*5OIAmTLE$<#^GEN=Q)6_i_Zt3X>uhbWVE?0-n=#Qa!lQq`&A@g=4 zpIL-(duIgHi@O7uh#(3GjR5V}p1&DaWx?IATUJ{D-}4Xv`4=euIr6WN^H(8%^e5zh zvpD?``TyC3q^p&sU%+BbeJKdidQ?AoUBBOnWT|UTubn1nKKrbROFvfX=eOf-xQgwW zOLa#g?{3!UA)xdjOBL3lhd`lya`SixOr`{B)F?yWfS>RSv(TDqc#0w{*{o>^u0^io zhJq_=wuygJ?7A;6CWnY`M4w$Zg=EG2+wvQ{`6+E%i&M>9NlR*_7ZcnS$)&bC+b+LV z028F6jZ=*#>@Dt1P7GEyOC%fR7`;JK&)Xl+m@ZS$z25+VTnr(Bt4?gWP+|H_x&@uh z$wA>G|4MEZ-lg<%T3wQ~b832vSg!(i4qgp_dSTY{aVG~3^(Hp>n&78NEcOgL;|Rj(ia z=CbJ+dnMOlT+4wXuXQUs>cYFgMI({c?Iv)=fSTdeaA{s^9B)4B*4p89C^VBC;{OpbZs(9h%pR3{(xc(|t)B?Esx4G@%fQ)mBa7E|jc;sy3KfEW_)&0(4+K&UPs}UkpEQFb1OMy1(1fJ%Frs~5A)*2G20p?tnfZJnbinQ7P5 zI?!4{dX2eHQI?ez_=_<@fbvdh^sXE%TuO`(A8o~^a--wZ2Bh|(1 zuOFT5YoWQ{KW#Y!8ttN1r!ET1&K6u~W(8`d>HEv!@|fjlhJx;E&9&Fd93|K*vtH}= z?HZvHq%XD}=nJn#>MXZcCRh2R!@kw|aFH^MXx91zbHbO;{F1VDn+v4dj3Ijh&?{S6 zf|8lGWjhQsN2lONIj-+_7i)@2HQ>6*D3?)t*f9T^ZbwpjN-5GfX?40IdOUA_b7nG4 zThe8$SS!NY6>Yc>=4BBNHf7-6D@H*CNK$EB_^SD3?PJfza|`vpY1A~m!^|~2)aRD% zq`LxH>$1FwRgh1g3F)xUTA;Ua8$Vj|OjiMs6Ba}gK>|Snnj!%s0c_Q1UL;;1UW{HS z0Am{HIs;Hc>_V!?C>C{tj%@0ecp^{0_{)Nk7ToL!0k;Rv4%yR1hQWxhd@sh{|*!xknp# z(E53Nbe#O6Ea4&^?a)rC5-RWF`_NlgmXXs7(O}Jw8Ds-ZGw6u;30QT48>y zZ1Kw-iO}0g$+}{Jo$`_;4Xo9Z8zP<%A|;IpjnH+6k|~LoG&J`jrs*xEvxugKKlvoGut^97>%HZV&3Jwz)T+X zr#S-4C+F3dnFTRZD?_@ysI_3|!G>Gxa$+EK$ZBGo@LDrx3!0@Ovi(}qBH$}7#$zvB zjjdJ`PTiAqNt7F2{?c~*r?By`Q5x^7Wt05XD!cN?Y{e zuB4|p{%}f-6-Z*w=A+c9OPOyA1?MYM2p`a6Lb2e}dmNEtuZ}R=qhVVcf?F~7#RkA!HCzdEo7oqL3+#p80fYnz1vCQ;17zz3jPxD}G>Sb6r~`i;C>J3K{^?ixrrufz z#|Kc_|94^1-%U-wZ)?5+>|docM}TGPS6$NIfAQaKe}CB51V}9saF4S=jBBv4t;{ea%4I5`=MVKOBGENm-}l*V0ecg2`T+(5R)H zm$f_hivT2W8#_=k3M448WI-yt^(B=2Yt-Q(V&b_&^J17^wI;Vt%;m zzTgIuIF6w?=t<+k$(1BqWH3A*#|VKT^fK^62puny)APgBbw;U<3MOHrdJ^a(hY2v8 zz2V2`#6tTD%co5b_>jIT1)5c_h)1s_Y&d)9fniffy}s}>Elz+n)l6Y%fR4H@3+j9g z+P#)k3!aaFJr0R4^+kJz6N6wA;9FL3fg)ML8tYgH-eraX^v_Gpg@OH}PDceS^-3P9 zsIGYh_PP~2wfWbI`2H`t4DM%Il|Y}aA7!>+bx2lCwUmBd!4s1?HGZJz1=2>O2kHR@ z;r$L!-jx?fluHyy2bVUGK7l@v6(T#38sf;0e_0M6xNphq0P1|<_s{d-?=4II6UhH6 zd3phSVXw>cH_z9fmRIrOmdh{lym%|kIxXVi%}|f}Qh2*LH+)fJ27HqkJMO1HHZjDi~+~_QB(>bt$*r^QOCIyhe?K}k*!mSy9!o`bSVemp%%wL%#$Cqh#hi(l#t%~_V&hx?;J?4yqUCQrG zgorMJrDZFnnArh?HwYS5ae~1ZnLkchomI9@+A>AiVe)=HAsod5Izya*>Z8%lbTI_S zz$aFO<56r?nfC+`?0-0vca8qY6d~bhvD{?@vu*U#Nl`a?q2BQ)3GS+VNu019Y26B? z>FsCDW}$K3jufRg--4`E1!o7gXBRdf+gFZX8kc;^^x(=WD!flOE55mq$x79183v8W zzaD)oSNe{AHIDV%w5D*^yy!MRJx4PddmL-D>b|QlVUg%C!IOv~3keGg3uz0>U>m2B zVxi6?{;e;86SA5?N~7FtQ-Wsfv=8Tfte@FPY~2WoqDqQOtzw6+xboa`JH5os_y%Vj zz%y~rv>T0^&z;m@My{UOm7+hv9%#mD7+}0PqINr_rfZl^mG&+$O!YI&=V7?%8X3!v z;*GXNYArLNQ3A>fd$L%>7h_#VTn9sTelt8D$hjy+%%RSFwvcE$vHbkuUsV0gED0yy z*SvP*h~at@BZ{$MbRl%O z$weO%F1xo-Zj=n}f@fv@y!ta?c}psf@0Gn8Fe~EHBE>em@>0>)P1DqrdzW=F(d^Ql z>cLJa)l63oQBtZkop#szR%&`#c7I<%@=()Cm`ycpd3)lr=RY_%;6G6qF)XZ$F?-B z@N&k#6d~9btxn_}%H#=h1aU7u3L}2vF+{0@|B^&OyiNR$)jF)L9%i--MTC{Etaz9& zLT*UI6~|28tI1L38YJC-4$=-Qpu9bFb9f6y;hwSh2NHY%=VEB%2ZZ(?&@%?+ZXvuH#u~j7wf9fRu zLNhWiIS`UN(5MBljgquFwyEu=E3g@Fm_XZt1=qPeHqvSZ$Jcc0I#a+WR2yDL6#eJR^eAL3II2SB7*oFO9FjWy^R%k|FV^}2(<&OIA^+YpF z3C{2XN~mZ!;>3N5FAuo)c zjUX8%7XGx-B=D++Gc8)(`|2(4xXQ69i~kymycn!>8`=E zw&yhag1@3AFfavrU>9ryorvY-N*|Ee&iV>@kM=Vtvl68$fXaCuhPNBi+29#f z#n$EMBg^yCOFb0P`4~S6H-Q>SSz5rC_Lz-1-vygwzF(1V#mSdnI$pk4Ftu@tofVA< z`l`cmQF$ zg|3e;_hK=RDTk;EyX!^iVsY8$;EyCxO#&4-SDyj3G7E@0@{4bZ<5RPPniP&g>pl;A zhW5U+3CIK(Mu`MuZ(wKVEO*k$P9n}F%~hDg)>h~uRNJC9KlOR8(3xDQLynK0n&fY+ zj)=nXwD1(Nt4MBMou;ZJbEtqZ3WX-+Y?9DnddLK)Vfn=(`$rjgxO+=r`*l^^L_uaKC5Ig6x6;HkQ1;$md4iD=wzcka)O%NxuX_qJUU^4^IoaFPI;LS4 zu)`y6GvefCU>n5=AtP40yl4o~WnDoL@O9e;cI6ebA4ilq}sdTK! z4v$zg;)^ecEMrFIAlX2{HsA*m=N3jQZI&}J5ii@>LNC!@(2?IRRg?W+ZT zC$kY@TH{6|kN$F%42!iwfycN3(w8XfG%~t2UB?*~d2qX!3T(@^3ip7|25MoM=S*{` zq)Sk6{kF0U(D9&IBY(av1HZ?PVBQw|e9*ug*K5pr;dDfZ`@xnWC3-Y)?|VM4wx%|( zNDEL|BPKhNA0`-*EL|?|KuZ)b@eMzMUl%Mmk{DeaeJ7E9fDK}7;I|s0$Evu0tr*(7 zNn!~Ar;jg6`!52?D{}m+0*MUVp90A%Zh%(<$-lRrAJH)qt7TIcA~t)PQBXvzmi*C| zkph{oEXr2M%u_4?d(TpB4-BZ#??x{cFCyMBi2`E8l)ZCd$hE(EC=->MhMmhNhxWn8 z%}7x)NlEst!GxiAN+s+RS}6(gk|n`KnJM+uoYR0ZhNF2G&8$Cu7KheQSZpEF zUa(ZJycWX}-WastB2X&3>+n77BlG(9SJoX(XCo8*2@U&2f~D=z!=n$Y&lmR;6e#O5 zm@9d+v1Hw|OK;>b?K#9n$v}!Y9P$}G`xYf#U{Fd!&b3Wik3tvb@;&M|zP&8$ua~;}l?73efQ%K*!WW$-n>(C+W9rhB9x-V!`Ar4Vh;RZ58zdZco3i!;Y{Rsn=A+{u| z2>qE2_`G|&q6Ey~v3nRPZ7P$_vqlEHVg$_IVfW0%e3_L8tJ@ZrL-@*KfTSh|QMvX+v()iok$=jtckAGZ4^TUV^#Gw2KC}8zw@ft@g4gGVsNTz}+flkW6X8ph$p?jo-QlicHi=iKz0;m~;lzX2-Iw5k-W@F+ULCC+ z-ce4#E>Lt(M7f$G-bAfL-cioPp!~=Fcu+&IFa-4MoC2Q_5fML)`)o2fd0?Pvm!5mR z%BIds;47CH=CcmD~{N~}fe!T;_LBdi5paVLfOg);Qg*s?` zkFTI;Gp{#>(Q%AJ^fGvBl!0O5fLOi35svYsV+8`&NSK$lo_jm{MQk^6a{i&?Gz*>i z%Cq*(U0=%Tj4wyk=BMkuy#tbJ+kUdd{f;X>#Z+bk!f|fX@m8JsdynHKm&(nzS7#5b zJum5#18psv^f!$-uDCpWHBY5TcV0*ozngM+z8_UuxmQ&e^%cNTX`SU29S zp?+`NP%;mX`mx?pK3h~UalqxD92|izodfdPh2vx;lW98j0Y6ZS%=fOi@#8yVKrUt# zf#RMX4R3g)BA&lD`WwJ(?e1@sv4!StECrs_mo)c@QV)9BwZYZbJ&%|+>~b(J%#DT~ zjp(TNwAHNCAkx)8o3na8UG?u2thKr6ZsroO)thRrNd>q!rYO0h4gP%Cu(mxKWoq_7 zyFw=|_g(8aH8z^ylg`Fk|GhSUY*ilId z3`(MKR*afH)Mzt4UGm;9kB-`~ZzORXg|V8+(paX_l#-HADE$3y(c2*M@3g*2W4Yp@ z=)qGb%HgK@t)rmPsbR}txssyjSqf^G@`3ZgWq6|$*P;7=Z6IN|Rs;(s;Ung2zkrdK zEgz*E@x>1Q%0?=ZgcpbEMlT57E?~+AbFnV0ORx+z(uymkIbRH>uw}{v__vR`1P@Rn zzJ$lOmuJLU!9#4vxzXk2BB@8)N_eTOZ`@TjSO>fAar!B_QgtT`OHSwL1fuXDX+$p!sBn)8U{1`?F+U;0f(ICgd>rdcvzxOR+h5Sv znbkyJ-QmO#M9~A^dy6j-Wy{h}14}V*hKFO%Sij#9V&@a4fYD>&ogHGI-)|S8gIyXe z4s^7=ayU_S>y)3a7VR;-Ik#bl0-n!#OyIJ+qu+uMFevHk0pX+LGVEpk;nA~sKJj*E zRigGr!i*y$HbDkfzDG_7Jy9 zbR=vmr%uYGjo$2aL>NJ6r%e=#07C}%2Ezsq215t;_oIPfcP8-1gXV<6ffho8A&Zlt zjFuM4?m`8V!_+^vI(NDTkTqh0d=@$5zL|3y4|B`W+? z@k|NvPx0&(gU%~ym3iWl#pb`_S*eRlN*RIpEbmp_3pBwgv_S=dAwg(WNDn7t@Km*(vySm4E%l>RmS5r-x(pFJ_V>XOh zgj%)2a3U|+zROR6w|o7yW`6S4ZXYL$U=eUZCv){^GBS4a6JCRnYBrt*OUu=$F80ss zr$Gk=rT60GILZYV+G_VFEe@fX#2xj;T4wWy{X&QDm$>gwb?^0RZXdQHKSj2?KMwSX za+{ecwYTBpUT;jbEc%M{?I0qiim!3i3~O~gJXRJJjI;b)j!>)NYLht8kM(%kGLv&| z;5zABTU_E?z*z`^o8n7{6lwi2d)rQ>Ioi?`0HWdh#N0KBpi=Al zv`f{OhZUT}H>3W9#*gyqTtY}TQa*r!8Rgeb%QO5RHD22rfNXP}nWHVP?P6i18TK-Lv8h z#w(#vM0^vgj1LK%q+YVifaJ9o4^w}8dE-(tcF~{SyvWJtr&fll-&n;-y!t*JsUl7) z=!X9dmS?cd_*Wif1ckMwd_*E~gF|(co1<1+;N`;f3gqMS<1UU;yq3&qJ72=*gQV`7 z$BN0U`lm0TKZr0b+rV0;Hs&5c!Og^aBGq(WE&+!rozwIOG{bNr0ApfwBEH znA&$B<62(eNKW8j${^nJyk0^{H`{MslDvfwo0EM&P<)9R|3y%ICA9oiLGc#pPeJiF zf7VMtd^IRaM&<|ulDsGMvR0>e-zL^N!qALK$s1PmYrhI{Xpf9#;a7$ySH)01bMsEG zDD;YzsN6ee?;qtisRP2@*W=>E`os1bS7$v1twaHL-TwA(I|V#x17rBH>cyZ9kSbTV;kPqh;6&!}RJFh{i7MYq9(C6beja z-D;_6`?akI{d*C1F!(iX^N;3X_Z_Q3U8o;|KaCy%qT;{PJo~l8CCK`%;F(`(-cXa{ zOPaUJHc%h&Oqs1j2Ls& zSYozus^@mAZ4)2FQnib5(;dBq?i$PgljpTRJ`E=peA42nv6hKU%`XAuc`Wx5@ zB7G->1Z?wd9)|NgLf7+~`6(Zi)@|Q^z`40HXzG)CI6s@pwb^Mq>2L2FrWn8(NLkhg zNw*BKL9mb|izA-#pZb;P(FBo;LI@N>RFMs4fuu7Ds&wKbF;@|@LsEf>mO|Nk8)Vsj zr&McfNV{VIqw`?ydVPr5* z-RG|Q?Kp(qiHhDK_uH^{l=R};kpjq^#6H-KDGQ}=iU7NyV2#jhPC*`G&^=hU(i>A`fw*VEtmlui`n zL`(bl(Kf7%0S1lYxf2Es<2wv9Mi&g)whlPTMI{Iy%1?Bw@d7Q3sae=H&Ntzgvp$#O zD{g~b`3IsEZ>{JiH(VI!^fLgF^&|DE^SA&K!_08;uU~Ojf-G=NChUw+cIHr@MuD!LxM|>|IP7DKKQ$N$W%)#2MO3Y=v__7*vumM=r9UTu_D1uBcl*p>^(2Fwu}(oDBYM;0nJZJfLYvff;4=j_F-?K zBSKh*4FVKrV&tJE7;dxN|G;jYSEW@=6Wi?(9QDg*^@z>q+%zKz9E`X2pB@X=;VfK|^ z^;d=25P)449SBgF>;Kvpeicmm?MUMf4gdvdaq%8{q_!O;I9J(k+cMUj@1S4~gcLOn z&CjCpbKqIs$&5pvS98&e3*0VR4WaZ-y~2ybYPIM^A&0(PD)XPg_sg{fO|V9tUvh3V zleLnEb?TXR)?)Cn5tgw`9cV`jz5mHl=U*kO|0e2*Gqf)W9#bFt;zE}znHVIUCw^C;KHTy0sjQO zB_qE}Xy!!b^i5bH)GUl7yKP3XHc~Wx4|oBaQhHljvbpDWyN%llSu?f;rYmvTtkJV< zl?~K0DwW7_0{TNHks>jL2LK63s3or$*?S-$pqF>}XGDkqZ}8G| zy}ub7!0K)-70mv6>*uY8By97>cA#zDO5zRjJ`9#lYLc0r1F8-jVjZvsE_AU=MfkSV zEpo;SAUXmRV%0Tr38QLlf4GcVX!ErTL<*kSAF4e$S6d0Zh+_ncQ4A8rmFueIv4z^S$Yo}5Zh?=CFA@Yv;&f^2 zcx(@0csf9icES1PmBO3w2AjAiwMH<~9u{_aa39|W&Lgy74`0=iZ}w3dj|F8ovlm== z;$TMSNucb zW)M9HfClXanu2m5mJC_yy67lgYFzm&69;tIeukMZ$#@0Go{=l8?AfaeCmHwUZ^AZ2 zF?2I+AK~#Wd5s3cM~EK1uP8VKC}Fkzgou*^&5*U>#`6MNW>DnMRQA(w4c>9RQ`>CL zm&PZvB*Rn4fjbN@N+Ivlm?w_5CX3_CAC;l7)=ZtT*Ty_@c#h>;xnN4Ox(8xvjKbsvu}gmD|ZxtJQ*lXRZTD1%f&h(Vpc0 zF!z>0btv1mFz)W|?(XjHPOzZCU4jI6cXxM!JHa)$TX2T}0Yb31$hr6X?%kD~{p!5^ zBR?oABq^#^k1=|7_nc!Gsf`ANQ(2x&vp~tvcjv9i3#JHLH`lEe+l;F=+Tn^+EPM;j zJ<`jwRUBDtIkJ&^l8F7KyXkS4(-9%bzKGI9g ziSpp#ZZ|M55w%_{M;5Pf(nQr=MZ|@!N}B5wYj-2eaau<3#B_NuUPegeb9NKu6T;6W zFhpQNw1;eg6oEY5`guEdXyqZU3_#uM%KWFO`^ED8QK&omHR}Fyc>c|#O_1tO)Ol~# zv}{SkJKJa$3jxA_y0Fwp?D|7&D`ti{7F_6_JvDP(;h9g%?lPSw`Jc3~dJCv#S#m)HT! zoe2>2Sx!6~9&b&TO%I}*> zp^eg}KLJ%*RghM)xH86%J9N^zxn8D9R5vp>GYz6yG=U87x~c~65dCH)t%9s&p=V5e z{Y6sf8v?Gqx6<|G-HJ14U2_=aD>zhl-B!BqG!f`|YIreAFzlDG*KxR#ZH^32*7Cfi zX1gw=>!u5-@E!ETIVh@dQXazQWF;31>dtZ>fFvcT^0ME2@Ya{iL{5fxzj_+FZeA?O zVeEm{Bj4*+!Hvx-wNSSb18mNU? z1?LQN%az+|d3!Fx$WUaD6>a^!A+e<+vDft=Iev+u1-j#;wH77Ap6zpS?c-Z0KU04g z!E0J~i@W2JW$oNmeL@k>mIT(!AYQ)txy9%5gO!z;C7`-X1G?f&N4$*pPrOILk{6yv z_gr1L;ZPXR!B7Oy(NM_Heo)xZVNmGML7{Xg0_TFSXN>1P*1dcHj9-`fKZWrx@bE{$ z7!oj%^$QsPWsvMQ!G?qD5gd}&A^5Pa_96Bp`R7`0%ujF?&mfQ-+KXeW4F$@(oExDp^ zlr>mdZ&9d7;;T)LHh^C6^QgXBciyh{am-N{JC;%kW)pM?et5nQ#iGa%BODWLRy{0M zhSXEyJGJAGp251oWoJBDlvimp#ZI0#ytZj3#L=i;nb!3m7)p5ls-exsc-1*Bq!(!n zTte3#(M@MNS-n(b-kKN|xKrVtZjce1J)ah8!PQWg%#4`9PMephiOK|;2ePtP2a^R) zo`(%#@*RsNp0tA9)c<>oe*yXR7I_0Zy1tO^hC0Ng2}{T0C;EM0pcigBUH&xUhT$i?yCosica21rFMj?T3*5hJU5u{ z*HD|??z)DfrboY{bY@O$>)A7BH+=&=-#ZL8pMuZbD;bj&0TKcg0`39n0qOzm0geSH z0`-OR1@eXS1?!;!Vi@`f{c6N!qy+%zAZInczg28S!F&SHK$wcTV`8LPzQi&*~rzpM0 z?@TS9a~W)uY@Jt*@kgKYOAgL}@{dEQimk);;;ReDgmY*EMn^5w@II&)%dO_yN!K5* z{U`(H!FOJ6nH*+Pao72C^m!G{B$b}7a zh@@|Mx@x&`K|x(!EU?=o>=yX&co}FB$*Jrxj(CtI;npaCh8^~By-pEsS@5CYhv}0O z>fSB;U5%;T^-aSCu13>)iaRch3&nx<#nF5p<(DzExjn9Z2a$UoFZU$|+odF9>`|>= z2XuvNUDVM!9aLcW01ge7U88z4^h7616tf;Np>{g8y!t#eHc>w~LQ!x>ttu$=C4R8V z4?$dJGg=nKIYAS41Lpjdz@gc5-gkd{Xe#N*G2Ldt0p94Nr??-fx0 zDOrAjx<866BY>HWpGg}3`wH!^(}cg-D5CynhyYMynBaDdH91*qGOUiFxE(FRyVn$f z7_#;74~iXib(HNI!_?V}tk>hZoS#{~DHE3KsWx>_H=ZiZ1EMi>8}0+n2^R`NqanqZ zO=anl8>9f!@jOkX+)X(1Avr@$)DQ2MRBWSDHq7a#-qnmL6qK}8cs86osJdmCE}pPg zHJR$nd#@OzJ6F{2ui42-n8`d;E4G~lw>2Mz6><$oy!p_j`@kg~hP6GiXy;}?AmG(4 z$u>|?D4_N*rcsblzp;oLk8qNq9-pcE1e-+&{ahzQbFB}4F#*KG;MSMj8_2i|uW;Y4 z`gF_vkxQ`rgAvU3OtDASR0mWJ-x`Cynr8JV)*wjn7*j@fpi*_YAJiyQXr7oBWloTK zZPp3J7F?=*=vXgF z!Q&Z)6m!T4WP!y%l+6@DT7_TofMM{6`7VmnzYeV>#tQBi`#vJ%h$a-+mP5OY%X8;N zw@?$Yfpd+wrA=m#QED+Kfc1x`^%Ex-j8eW7EPZ_|f{L9jiUqTx#LJgK7~bbvm*=?j z*~R$9?c8Z8(BJv3(LoGhfax3b<8tT4tKGKFEAJwPb!XVy`o-@3ew$_h%Ot@Zw$IVp z2hD7%v*nkY8g2DLBlYR6MP*xSyIb%omeibL^n$IH3-k9LF7^9_i1dksa`kh6_5cre z#)A5J`hg0&3j+zi7X}gb5C+cs>Yw)b^JG!M^tdJtpxP@Oe^0f)=8*n9X8$Oz*#J73 zf5o+53_kth;x{V!HQO31=ryZe-$oUr>61Ye+q;CejI!18z3LTkVrya8d!QMM7at}2 ziM&Rs>YgE=&4Wx|d93)!Wr1}J>HCy7@!^H0$#cuWl`nA*A?lRzecAS-1>i}rwq{0B zU3fAoc%zZK51Sz$rH(t&;wupkR;tnyb{J&KBl-5!^%HFL2rE{ZI^Hk{w@Lf6)dc)_ z1C~KYr*PAzhul=8w^&kHncmjC8wSpnTh;R8eY4{Zo#9999Pby0yR)s?NzY!;w#>Z; zXSWTL-TlO~D|XoQhm1XISDJdk-EYH7HG6b6HM5^wrhQ$TS6vZoh9*z(;rm03pSy6= za|kBJxFm(|XxG68jaeTDm<&2C4Xx8`Q)S-N7`A#%7+Ei47%NeVAkpW=rUFcvC!1yu z{aO5akQ>4%y|D|orX+8hAOK0Nk+7B@;C7;lpKy1tmKTRj>(Git-kJ1ZgW?HfC14wQ zzMYYp@RR(;4BwiH+y}-cAZg8`#LDtzOtk>mAR7i=%O{MP6G&pjNbNh{^wI9ba1Nt8 z|J4DsejY!<7YD+Q77};X{kz?2$;YlW^s5KQlwrHefy1+JMP-t6U2OBAD+$G776@F* zIc9wL@i<2JBJrVnu+l`yMd6Yc#giR))9&N2fm>6-cl$L z`y4&>6$E{dlk3!5s#Nsi3(jr_w+7Fy4rZ4pVm1V1)3P=cclt=*Gkrcy+t_sTe9LQ; zm1vrs#AvLib=_sWcBXOt%&4h%-@5L+0&)g9(0VUm2^%{wbPfs%z7GZg;sydq>?i^z zavF#Qwgm?4@9YmI%q{Gfo0Tifq`+ihZ}`OhuVKI4VY!(A08?MV{2w#*7l{9(m?{To z_Wc!8|MG|K1M_UMU% z65|A?F)Kd*W9iQ4fXgMTc-Om?c44*SS)AcM49z8tb1$6kLqf<4n^O3F>LKF%utk<6Mip7Y{xPG`iT0u*zD9@LnG6JS7mCL#v*BKfO+QP>i|g5YI&Ppc;!hQcsraszeMn^;W)GW%Oc(; zDGpEMX&n6HK+`bTGl_2DGy$YNvvpi1QV0A!X={eBU~;>}v(`DxJ$@J5=U}aLirna; zGSFkGbeKHuFD9bt+wY}#dcuPWk!UgE#poo-DY6@Aa>G(np!)Kn+8O1z(8-%3(og_9 z@K`bb9Y}_~)@O*N>hYs^LcF<~?iaYRw(>P~{PM8j#=!Qy4;PX?J{YYKtw()q0$uu1 zgHJ)$Scixs)t^{uPRc6{27i3p7nPkRG`QZvp5h-|%M$65bED>Kc3Z24)9yH93_}Ck z2Y>Uavqu;(d*escK_nEbA4^2VNQKmZ$p$qD3juZl9swb_p#8<$y36V1XZbskwe!9Y z!1Px#{KriH1!Mjwrb{CHn(2Re4*X5mdz7Z09f0ZH7d5r+Aamqh(K|~SfTR<5VNi4)O8%7LrXFxQi zCpdZU))KuldxS=>>Cr%UQYuhgppIN~cazVL+*}k=Jt^*DajYL<9&SRW`*myftglu9 zInB%)kQH=x@Nl8VRL+}Aq!GzTuPEcQi@;0LH08>QNm~q6K^!4(&A)?7__|d9HaZC+~CFb#g_qz}(^L%p0 zyfXJ{2LAB*4({Hm3wQV8sN_@6)>;Fw)#nL1!-v3r(RQe{M*SsH62iFi5jjjks@6kcGfSV{WC2ovlR?F$z`xuyzLj{vS2mW?1~&Gi z{NRJ6H~YjI$0X^~-be0J#nh9k%?-Og&uggMYxQ=6!(x*PyEsjCZMB2O&s`pys&3a- z8-ifkL!ReDv8N|L-kue`EIkIliwLW5TaN*P27(6y&V>MyB?pZK%7qjHjYSZG@x=xL z72*KG266?s#MuJj!Qe@k2t8==m6yalV$#0+^uawfW0`~pybiq5>OaJPoDn-|B7T|Edc}=Uq2P~776~@>Oh(}WK5C*6D5$tY{B1)g0vBS2# zY0TM`MJ<{YLUp;sdf-K=?P<<>O}t8XvH0$CHFG9F8$(;ijJE!}v90aJXYu-VtR}wh z+Dq_<5o!R7%T`s?FF=Q{r6Cw0|&&*2$SGlFYT{q%>AYxoT2^#IkN z(|lJuK9NJy2=8uIN1;E;@fV!>3{hs*8CcptpH5M4j8hBXECp^W*yL z!HzQ+)RTRLb1Ylo#9Xm#^mN(LJNRf#FG0f#LJ{VG9A0D@hPJ z{i257SJPv@O!gLx<^)Pcb&E&9tt3DQxYoWx#!5X=ya>H6Ia& z&mqI4_o8jF*z)YpH@!fTl_f_$nfvhZz?`k&RC;FWV>3u_awjHT;vs&%Arm(H^pNot z*^&ChtW1qew!YT=KCqd?0jGZHmgHe<92BJ+(Yyi2KAC~cfVEYo+fB#HCo zFp|~Z>XZsnW0c_fux!U;w1i0UO448)-tbnnpAjk?RinRL89RoGVy^%~r-fH7)||Rp zj2rdZS5;@})m(|zQC_}qd~S~pb6wFOk&hk*m z$lLM|&Tus>naCODj4xh)3T{un3I3{eW_OajzrF4eV38-}?U&o*?U%=N0n0^t3QLTZ z--RShd=ATu7T*;s^yvm15_}Vr4+l?+a?9I-V12enAnyYXkbC|k=Ih(e94RNG4nTlj zIr<+9&@YJmM+s0MpelQ9fde!cn%X)W*_vCMni#yZH8Hg}aCqlr_0HMgFJ;&7%)sJg zU&A(h0bv_qm~7O43ESvtkXz9+)b5ddykIa4N{?lF@V?f90CR# ze)(Hu{$+Fj%_gZHCvTVe>a6`3uFTLdvt6?;aa@f?owTAmQ@x@)7vML-GP>e>$?&A~ z;%Nf~Ohz`@F*$y6*~9H6qN^s+j(O+;867@hxZ{8$r4tk)P};A^@e#1uWJrL z?hI*IVDYnsVxHK+&A_A{uEpn4-auNH-MlNnxO(QD_hh(Ay+7YI=4<@MjmK;JM%PVv zHEfq>iws3-=vS$A*{|^qluHe%1678l2; zuN5;N$)@m3dzOu_*%S~YxabxXa-Sn6*3D!j@0E`*A0>yqpCEpRpPlQy^2|pj4k!N| zS5yOBUHoPd$$Ev7+myxZt@dn(RcOWC*eHxXFGQ24@msy_t8nwh6Ei5z0=MccQs)FR zN`3^KTkgZy^{9Xd^jOwd+FUcFs^_1e1uOP4eGdTb>uUZ3p#8;?|52bd1nd|8UQU0p zA^hToRy|Jl_3bcd^B%pxJ1@!KZc6>9H@5exO~Kh}IU+=dQNt%+2NA~O4>c3yhS1PC z*AsY%phaJ`kMhflJc5L8m9WUGXoz3n5r+s-10@0sN#vKjNdy3?9E*cO>sa8W1~{s+ z5!GV*69d(HycUx(w=mU91wo@LO`EDbaG=yO(l8DE`j#p*I&K_y_;Zpq&H714dglby znM}QWd~aWlKiclvtwkxkO=4!*5FHO4hKg~kMJgNLbrN1097-76h#Qiqh{{;hn@Qgf z-%f{4mnF^vzn1?8C; z@9!4fms}>_D)-WMQJtf&x;EJ96mFk2)%dr5Fjn*C1XE>-N0l>R56ErXc(Hbmu+#*m{>Q=u`Scov$|v^Ts%avM>$L1RRQuk+}%*S5%rZWKPKxGo})x1 z|6m)M{SNT6e!9KC|9<&^+ERoNH@A1M0^M@buZ@Am{fcN(g{jW97e28fL7F>u33Xo1 zyYrgMSZBuNN|u_X%2Z8fjXBznXLg&7DD`)%i8!|JNXIY-dw-ZJ!0X-`m6aGtV7xaT zymDo?#$Kls|1IulX0d9O8Q<86wKfj^QB|MDw_2I|3gu`Lenck#L~N$h9-ijjxc%puKF-=AyQV zcC895i-VSXulCCq%b`|a&G|zz4ugVaPMQSCMgWkf!lnt7_@Pq}Pw7(Pq#L(%*mx3E zv9U|=C0IFJ1y(Wt1FJUdJ*sN~s#P@_EFw~Is8V1b6rRx=c>xNl_3zts;&#JMr1ygp zCk(dgPN+o{r2EYa@&pqd1o1}|+o&scZbLO9EZXB#qRs*78WB2=1m>KVS#CY)=t;4B z9HMF}!}SqETbo4RuWn|qkNTP^nNoNlL@-jUMz*SVFyo9(zO6GKq6v!LeSC3%gK4jv z=w>b-ZQ(t6#=}&;6UoZgFjX+sKrRVYU!t~FZ~TuG{Ie@Ae}<0ZWhQw-3FvsY1W!9zn}fqZ9B(#k+M5?A>m~Q_ifO! z`>AKe0bAZUo86T(o85tQJWa))R@1+vPV5@GG@>r%Q^&FcZhy?|jugMIrZ2ych7ezD z711@29`H?%E%>e4zakh{B0cuj0JM0;%RfMiUx4b5qJ;p!bpGGd;xBECziEonOZXqO zn34HpXl#-G8v3z5(V*Op>tYm2sl|%Lq%Z@ZMFjAwd!f`Ncg{sCCT={KfP?ltyy~RA zF{oNN>x81xbMV7X47LcZUm;n^Pqaa zK6N+|(JB|t(}Z2u_Ppc%e+AuSxzBuSV?z%MyVa|C18)o{Zm^1|*uPQ*A25%_b!YfE zzMV*Vo563bB1YAlL9f-@lI|_1)OIzX9d1O1ShY*rZ2hA}^-Mj}8Xx;Zm+h?u#R`pipIT}kf zGj%hxx^S$Zs$Pkrlhbo~N|YvwzUhhQlkl1+YcCKyLp_ZEhAQ1tZ!P7 zP#_NSB=w-61J;S>YR%FuAqP;!7Hr(0rva?dw&#rPys!X z|DHX6>4f}E6{VLbZMO!G{tC-%#WrME=*wf0!iR1XbvS0&sIby;~^#pCJkbM z3$I>4UizbTItgozOYAgb_KSJYRF}-OELVj-yb^-ws$Kl9V{89_aDNca_LkZJWSZJ~ zEhj4q>RCm0(fQCCe5Q2fV3{B9e0QXF^rNmz0xGm)h&Y@7LF6jK-xB^uY;>cK5ngw>BP z8r)bXl>?HfWYotj*2MHGepu{`9DhLa0>pzj0OCPFBO_KI${F+(A)(!}ywp8WVt&}- zI_nVRdFs5ZKm>VI4$h{kakt$KKJoJhuA?@G<0k1Q;U;M(VGmTkl~ArIRqm&GtJYRI z*JY(HvdMUoWM9QwH(rMq&D;y7Z=9WMysT4SbK+Ee85S`3!{I;f zN!W(G<$}D1!Bn&O9X*vo=$nJjx(Zk*BpC29u(+>B57IE-Fw!vpF!C1yd$4nUrW5y{ zNASw1-4JE~*Iq&R4{+@lNd2R@cK)|q`%5F`-*N5vC)XBw#p4rz{wLSM{+nz1$|%xN zK(Acm_Sy{%C;3iR(LojD6I5|Xw5am!3T$ZetNnLo;ZoXMW>euzh;Oc_;$MpvlooXS z%d@TY!u8=r`|AYmd=?PWIjMP|G3Uz&!n^F+0O1DuH9x}*e$)!40KyGSR|zK~hZAor z^b5~}+uZDljqnB`2-NO)<{Wn~n((u7Q*WEwCBXOb+1PamUMLm&u;3_ifbIkOKp! z6W1@5f#@SXmQiPH;#+=RweIqHcy#~%{Ruc&e%($jsXI#%#a8FOIGPOa+r(4x{1y00 z$!gAB#u{I;D5r!v-1$aJ_h5+LLn44?Njgb5Nm@x*16A7+%oVAx8`$gSh0eHWv%yZ2 zf00sw$y9=c?M64Sqt^wJY<)3E*&-3#Sr@#j4lFC7q8Yf;i+$Td+v{NDCIn80ea&lH zG0$Rsu4fvoDO<~l;S5h@IHK~w)ft_*Zy$|#V1I*N(g7^Gvo81KJ#Z=0%@a|DAvQF%-h!>;Gaj1P&$d8oK1M4PKvX=GGsO*SJ>2q#twu{H_R$otDGk)R%Lb z^h*cM^;B(CPhB(lGn-WQdb?X?i>ksV^5MCXbv}Q^KG*A8miF*_A-K&t2MjR6Vnm5Z zpi5sOA^u#zBq$ch8fY{i)xclqSU6m0UpTmD1GN6r&t1^%-~rbFz|2?3{sYYX1+M=n zW?llCpI`effB)9-*Iwb@6_J4Ck)LO)dvwiuY1*KWDCTVJpV@Kb&HHQ=fYAY;i+X zRPLc+1Z;(dORrnuKr-d(hm46=v*SC$Zo!@@fcyFU!0J8K);`(hoL^u-SLy6W4Ms*z+gXngq6!P&3fg4!wy(!*pF$ z?~Nz#Qf<t(Mw1gI|6rv!X#r_pBV^Sc*sHi-6DQ?j=p%5NhKfEp zmxY#1MtRxqKcN6~J)mGYb$q$)ub`Nj_c{j|Y6%1L90!kfKs`*)csWmR-r2EjyRE0V z9i2>Hq2)j6*Pqm8^{hZ>1tgQ`#><&^WSEclxjMBkH;i^P+@l~EoI20|xXft$|8bd& zfb1uq%OI(LavH#8->*VM_9uQvkAkmvgQ3!pUlRd*eg*pP`Ai3(!2i_?&9B(-M-dtZ zV9fqEg#Jr==5H=bY}7|=0d;NX18pdvtOabfXi5KLt2IdjY_$>8%jf>jp5pXmlJtf> z>30Mnc4lHU;Zu;>n5Ic5n9dWtYOjP2MG%~RdR`UFFP0#qU<>sS`Y4Z3NxEg77n%V`wp&wtzj@GnIg#a)F zplDF156BB^)3~{gC$M|5=E&*x@>=SCc^?y;f6f7@ZYL$fY!$B5@$I?#Io&c6k|z(p zgy}-o@dlzwZ|l;-K8xRb>lezopcv zE?07pu&@kBZERGeLR%6O(@0SzEmSZZ+HPYlQNY*uSY4XN zAZQ&KU9hn|(LKII(L}zv(Yd~c(T2WKcyU1fy#AmhE@CiHTTsBE#ce;3x2DsdC4_h^dnJZ}>IKk5Y%);t|>y6;}(Y8IPF;>dq$z z;js{>anu*G8}X>N-rMDZ={BT7n-g24DGIPW=3Co zzH|rI$lu9+Djrmb+q_&?zCXqA#O#Nch0pe7ZT@^D2mI}!rC*D=g^a_NK?0b~p{@?B z1*{k1^E{O=g%TDmNXito6|p2Rdt6){*LSFr!5~1!teB&5kVY1_DFYaFE(IGKM!hZ; zcMtY66PGl*3W7rF^IB(<#r2njMqH1h&%Ukl&|6x0O1MvY(r&{R$x={;JE zP^3DsdnQHhW+Lq#TXaEK0nl`q+h`<_H~vf~;2y>zs(c!~9Dx%k5$2#&M9{&g5ln-` zfNuc_YO$OU0t29Y#TkZbOB|5(&I5G%LYBY^DaMhamh~AJ2zDPPKO?>gpPbhcW8t+K zM?lO%CuYK0xy-94;etSwO5G+|qASw9`^+q!`ZB@Jdz+JSX@d>?P#Fo7p&vGlP$Fy8 zaPNF5$(xSwRbW;}8a@I}Z!;3ciz_HXw`1a|mmx>rzau4!2 z3LJ7Cxrms(ti706awrlp9WfHJE{TNL+b}TXGIEtSxvKA(D%R;Y)czHYVvZ%bg8_Jw zd8OJvAt}G$;~ym{DX@PlDZgkA_{Gz@i^ji<6@JzlKuu!dbF{!P4MLu>E4iKM{KG7$ z$YEp|@n-^4A7TJ$DMczo_6@ANr;hAaJaE&J8;RgzR>({xa08QBmwf1B%t?LrD z!V{w-I)R^&`?MFBK-Gi5kc~49KkmJ154k=+bSB(nUbpy=zcqQD!6YFZU3{n*uc)?N zy&(`@Y%!Xa5A0NWI|X>A!S>|5WTvWR7?ol3;$>6j$oH7JGI_FV`~i5UdFl(XbF<)@ zJCkAC8+$i7Js;N{iVq$9{n*td@evyT1JaLcSM~CP_wM+rTGV_h-)oNEv3jAPCNK|D z4AOl5)YaD>099bs(k`N7R+uJgY?mOfOw6fS{dJt~bEVk%5ek&T$Kn}fI#p%Mcz`AwhdX1h!YK(_jih-Smp#1{mZ9dugc#E2?UjX-HfM)U^DXg+F?ayhoO z$;knJXpA^G{*GL`95vL+1j>$=fYwErGg>l|Cq=g_Wu=H67k>zc+$n5WN3RN$LJ&F9xEwMvU8IL?w<~0$h#j2N z`Gl~^Cd=ihJh9Z7Cxw11GZ?FCx?7G?Xq3Mh(@=XqVgFi2^O6qaLx>b{y4IcH1yWtJ zATzkJ`)ff1b6X?kHa`Dyu7Wo>G~sph8HoAr3~mY66{55Y{2=wnE{r?+rn#p&R>l6^ zN4XkrR@&Nk1e{KgGt#C7@V*B;&K}1H17BG!&_q}7W+&m{px#1*gbt%9h3oi(_-p$E zlQfCJh4!OB6RF4+`VP~@0h2U~As`;&Wx1QX0Rj#bGk0RDyue>89NX0UT@`>Fz0&lb zkfUF4`Hzw#Sb*pG-&8oiXzlp-a-_cokRuf|a*Efm13PKlhUR?8Wit|zIO{RTHOLx9WoS@%XK=0cyN+ao%=ft`HrnVDsI6#}A8nkckMWluNt*9AA>vQ1dZ`r`)T&!` z`kC(=?4%m)*qCqny2`>AoJGSYp4Zy6X&v1c2BZ@(oml9pS8wgxqF>|OY}~{f*lp&r zD=bQOj+bmtBbSO@%Ng6aT|8<=U?VKV=soNd6!jPkpXp-wKiay?qK#R7EJVgc)~z1y zM{C2jA0$2A+OOj%iEdq8ToL7*cks5jWi;cQ*jdlX;4~x=#&Ma>#^qO#g=9g;iZnlh1Ha+pwzGpF-L@gWrh=|qi7pae_VX_}AX6%?@aS)7Y~lonf|azSlf z3{K-1ve<4E4qic1qD@$lYN40!%pRZT4lY_uW>YKi)xkF+byHB?XE$S*Wab-Tg_Et} zev5L<+J-twb+^LG=xgjw<7))?04@%BK^pq5hSc{}4QZe@chm|k zqrx#VfTr(zp$u~->)uVsXVd!+7`WCT!!yu~BjcaGYEX@gd%)lp5h7v${wxMV=mES# z{uvm9VPuU2Cg%VzzL3iyxK4n1sfo-@Z!ePB1fY8n_1NU9yQST+pQbHU}fL z%rH}Gz#D&ADbV^i_k#>0w9(4nmt+=^2+)$jIpUPShJuRxP;;T8VL>;OcF{%4dI;BC z(!lr3u;kw4Xe-&`v_Q!ix2#PyXg-e>zL*3naXnr=a5TRtrfg3MRu5W@LNT@EMf$dT1HSAceN(%xjWZQ$qL`Cst&CLJqNd@8 z5e^L5i_rpvHJOaq&kJzCHNPhBDm8XCyWEWlkZX72o;#o+k0Zk&uOZ_hhmqC>+Xo7X z9fzVJ&mf~9r;)OV?M4qH=i%jj1?eoRH#~dZzC2EA_6lB{?0`RXyC;0rG{sc{_7s4o zy>k7Z(6nC&!XKq+VSq`SztOb6bnpD885~ezMX&O}tsl?^+Rgx@=pyPinfrot_MBD6 z2)EO=*`v78IiHe6o6mSgJbOMBO1+0coBfIx#)l%FZbbD=eeA8hH-YFd!T5F)%|jQj z|6K6$`FTt#+?c*$&W!dtaS4b;P!5P4p0==nHz z`QA4QAPzUtr&AbghBv}Fqm|xBjLYe>kybksA)gaix*eq=4z&p#R`b{?PFybI?|ZjB z9Na}|nMg(Q*yW_ZEu_=$d^$hw*icM_CK1C|tg2Xh>06KTk^|GY?{apjJ7cIT(|clS z*F4#*m>*Jn@oD++aNl`|;L_}P^L%-v6smc!J$t$qW{ag*SlQkg zh8hc`q%)}pCBpVS@sN^pd;xX(k74QrQs7fk%1+Zx&Zw(KBoiu2r7jc(DP^{sx&-c~ zv#KQnxyY;bL=!4&WqrMKYr4;8LolYE)T2u77)8|9%7exXG9W7>~cCm%}=WhoKFHrKCK{7r2|2>=JFMxAAm(qt&^D&7z-bfr?T+MzK=yxWdeT( zNb#HZ3TU5U!uvWpq18F?d?e))8koP(O)!FhT>pdtR?i*M42(2xj`wE&=_SR=2gk>@ zIL;0PP0kZF*C;*%NU%uWm#~L|C_5x=q{)eJc~5>P9AY~f(TMdvQ=l>9ohZxs%DAG4 zf&=H29(?G9Jpv5TLd%r5T42zTomLPpoWl8IEg?WTL5)@RgHf_9e*J$ zf0Q~h1IB>AuoFl8SQQl* zWvZ*OEiiS_g~QK9wg>Lte_bN>%q7aQ!-XN3G||O_ai-}&Tm5!3SzME#|D5ph=ssqe zN+D`WC8Nq&H>+k_>(SA2;BqcG-?l4r#dv!DiFDxn+d@z?$M&NR#-d8c;$j=B`eIg1 zjDuJEdj3;h=&cK;i`~$n(%_J6^R-?7hD+!V{iel@ivoAexX40IfyKp|PR82D<}SF= zCR#w;o?v)mWJA>4dr~u5<)(saXRxmjg!8|?DG%3o&{#` zVbf^qy1LO6Avqg9Wf}R)iWI2YCOlc~KeS$WPuDFJp}y2DX}YM|KFC{Vw0WehSKPc* z%-3{3J>K6BZ3>17l$EHjpBii;6n(d+HaN)&|8WyiGG9b*(7&g+_xxVx?CHSx1K!TZ ztM}VC-9w5Q&&@%N_zMsVW?EKSCq`9qv&3uy%u!&5YBdP7%@FfDQ`>iF>7_+ zRb)jPX4L4Y;~VI5NL;@BGBPJMEjM3n5+LhR0Z^-b3gLNEj2;w|7!Cv^tYx1dikMO5 z)^+I~6?_y!Xi<>#V<(XCR%xZ_tyXK6$8MvrcV!5v!NNr^`gaDK30h zE>27Gczi9(L8*8#fs*R=!v<=Dj10mC*2a#$U}uNCdlNRGGlFR0Jd+1Z^E2DEEmRWHxqdQ>(>PXz?G7>1rS5v!X;zH8|JDp zVBs+reu_mA#elvi&{z9hyD5XsT1_C1Zb)Uem(hq(kvwHvSZvXu1ZjU19Un&N$PEq41 z6%v0nD#s2L&u&WS*)*n%K-1`QIv-z)!w!J7R@0Wf22W_ng_8*Z-xajyGK+w=0fknq zHtvc_(Cx+IYl1jlAUfPHU~hC@X&&ytU*ka+*34^wDri~}K6+#B7AZd~rcPWo6Wx|q z4F0jr%_G&&=q8;fzs~0ZK|+{&@6&kp?eZu%y6)u2*va#~`-`h@;8o(Ggu19-?NQxK zYi`#@Vc<6i!8}3o3uC+~Q(wAih(a%t4WEaQDv&AuQIs4qORSr2*yrJPmAXqf0xb2^K`A8A!?K+tqiO()qX<-=Vh)QmQ)Hs>aquCz%H3 zGA^%)^||`254hfY^LAtsY{c|1N5#_2=8utC<7k%#8kKM&i$uj4Ol39`BFlJ}t*=WY z@s9F8VMpY0*K0G^9GWT)Ra13!md)Rkd-Qt^95CP7StlJ&x+2|ZLChwLVHFgnA!oF9 zJ01wEJ3(EPP);R%!U(8>>%tN^{+YKDqK`U)4)};)Kj!~uEd63F{wOTP1DfacrgKKZ zGH!KJGN^=?Sc!(`QbbR-fH5CGQ~XW}O%~;ytaVnr)^tMyv4T?NZ973y*_#WLXu$j) z(s}j*?}bzE!DmE(HT2}_Lo+;7uZG{}d{KN_tfb>yQH4x`D*s%zW7N^7Q*U&!7P<9h zog^-|@N>6ztQ5~_YQJy;Gp;r`^@$p+)=uhgH--(313ecqdb1K`|rI8IEU|n z;bK!Q{23vVhH)?RR?DqSnyZa}Tre9INd8uuG|aog|bDWR(Iv@lLyM!)005? z7&dM+keE;_$t^=EN;>ZY{)HJ#yc~4wkz_GSwW;xzu*N%H_CPO6cWOgM*)rk<9>#+% zgGF{2(rA&gxM~zMXhCXcLkcfc8#jQLJx8n4PcQrJ{dcW(e8MIxUpK~1t~|V7nn1$8 z_9nb*sUQIk5IHpwQVM{Coup0t9{85m!@xaHn?k-BG#uB}dg-F?t(LWRNUHF+HBoq3 zxN?B8eL}_?eS=7pfUhHB_?nm_3Oso`+Ar95XDOap;f-wBZ9C0RM}ji74D7)^;~&UV zac7G%vp`+kz2v%26@Gm5NMWDk&DM^D=LjLNFDg{R7hka>B=E!zX&o!S$BhD&a64On zD@mk56fIOGgzu;A$KAUGV|M+siDT496kQL1$=Bujf5zl5*7T3UBpsmM{CAlAOAYxd zf4`8xAl07cpy;V>h+7_mZYjAfB?(XjH1b27$KnMg2?(XjH65QS0-3bta z^E2LWmrBEG#|X&Vr)nNgg;ML$X&1ss9Rfe4HD2 zpeofSe2d&c*S3gTOSd8)?|L|zmB}9@dE0H>CxiAh4+fE6U^&PrRy(7V@nn%-K-?b8 zMP;>A8Y1XWNftLYK!C&kn!Y-)+Rfp-GSR>0Ma+1T;%|h~#5mqlJ+W0aif2~%+|vHq z&Ey8(_XjY=7tJ&-`iX?YRbqZLxP|J^`E(K#kRMQ?d zL`PoBRGTV$?}mjXL*LjMis%0h%mIoEahAyc6BMpL9GFw^l0cHm##^|CLu!3H2%z4# zgZ*kRMub;x1 zpW$!es)2Pg^w>e0)4 zE3PD4B?(&bNV5y-r4%LgPoTFWx0R}KD+@Ifu4SPesmtdlS0;*`_UaB=?+epf`0pTBR>Wk zw5&OUTt8s92o(A-2b!lTbx|(HrS9F)cb4=;?iTLKT0VzDpR(%mGqj-qKymH@agQ!d zf7%cOnC{O@Jav~Xcd$PNC{!F>GiL*OC%#J zHmv}~zlq^rEB+UX`J)tX3TPqtC&mAwYv&h=4_A?W8#~c{LoMcIS=##pH0@NZWL9cL zy8f&pWu*j?&{hzR2ovh8lU|O&R6=n$B$@qYJvG{9BPjAXD$gU&R}e=v${cOZ67q_` z-E&D-fC9dY5<{9Q@oD|W=ZA{4zmDrb+$<0?-saOfF3Jr2%r^L1QJYC0$Q!>(1A zB~mET<^#xZl8OxMD4;j+Y8%zdKZW!v^eV>ZcN>BL?X?n1r)M zlNn#>k+JUX6;szx%c&=5F~NnEld-mEBqTL?OW67g3D!CSy)6QoVRLgf2#whs8Kl>^ zwVx=5rCIyZ%7BzU_DHvx+BX{y7>qW{+}!-;4$~gI!;v39s3NaN`M`W+WUX~0Ii|Uk zmkSn6*uw6T+t z%2!*~EwO2}v@ON=YXWBtxI|m_)x!l)g0$5;n(6_V{oa2wDee-w%|h`pmyAMDr6@}Y z67@xNg8mU+WC>bPW(hltb&V?{h|dIEs#Yj__d7H7be7a|4B_bUuxt6`Ly^BywzLu% zT4?F9HlL^N42!KZ)G9^sYKh$1=j-V<7?+^J;__orG?S6XV7lHuy7|qQo0;|f5NVNA zw~miqKLMaTGUw2(a}H5*%|iy_JG#gSZ8u!?j5}NlJqtaY+zpI}e79NlY!!1zT{}F@ zJQ<_%jVFuYLY~3SuI-4hyoP9fX?423E0|kAA_%1@Vu}-HUC>}tK^bn`35!g%i-$~A zrz;@gLNx#$+D$s)hsd7_o(hgTrm4<*r@0!;cydjM9ExMps!=ZK98gYnwMAiF*345C zFi(NF_uS>$j5z|A&BulTjIb4fEYv>))oVb1VNLh0dp8;cP?FE&KNIyleo75FN1i0X+*yA04px0eutbl6DhR;DZ2= zfTjS5z7a6*CjT2UgT9$=|} zwJ5g*A5=Y_A=9rR(CWvv+C{>A53f8vLQJ`S2JB7=?Ta&2jLD*3!B$<-%yqo6(o*-( za((|`=wKx^kZKNXnTm=TJSmlViPer_d*zuJY(=z=h1bT#N6r z=InGa72V6x50U2Ulk~_F5^{Jwbzc}{H<9^~hZuWDL&Y}q)}VN5O}BCcJf!u9ExPEV z=BwBvhQIH4PB)KwdXrPr@pZD~tB+68sa{-IT*z*dSV|eg)Tg!{#@8z9jZOPJQ2b zaQ(lxvPkeof%tj(feN?-;Pey#5%3TICcN=m`cKc64s#5u1pv@D?fxE6DuAm0xo7LQ z0{^23y$4i_|0AJ)>n!;PLjQA5{L29@ZdQh?6vsCtTCV7@HmV{7G{IR8LPp6xUW9@6 zf2~y;1l$udJTA5{#gTrNxjqz(mhc~@%~4dU%onk0J_|=qjKGUL;t?}S?lY#MV2L38 ztf^FJu0*AB?YvV>W);F`r=x`pFkMe&Cc;S<675QpFj+ zr75~st#O1FZEI85QBq?2xSp#HXxS?Jwzm+JKemHd%SPbs>-ppE>1cq2*q{w6RHuWt z9Ib`B#HPC20B3{NfAd4}#^DeDs|nGllY^S~3xPQ4kuEQG_bl8U5-3u5A*EE=Guqe=-xO1b za9EC0j;#$_$)fV~v(9X2k>%KX%G*87b4Uz>3}O06^$%^vkR3_%sZQP_%!G^^uCVNb zpkSvGe5o|?XGgC2u0cX5Ej)c*JYqYi$U~eck%bNFh{6*W0TmetVoB(ScSdVWqWfXG zngHay6Z%d{+;2h$82rcTo`T=j#S^#|NK9?Ds(q!by)M^J?|xYE%mH`!JtP61v+zQc zuZeWNkr&-t7dPp`FMJzVyLN}Ih4B-zTUB?>LwNJ}>{z6-KJoQ zICf5)Tp4E8e^4OTJ~`r4-G&@+=?_0LF}<8dUN5d<|7_w$ars77S|X$>T)}KLFE7(N zV>#dHxL%)`EG*7Gxsaypr91;S`DEmF_6eu~2m+`Y2w1@I6Btl2xDt>8tO5`NECY}| ztUVAEEEJFgtOSKU>9gUm@0$+>vKPT90U-V+{(q17F9`Xg5Z?#P*#Aew|JLd8n;z>o zCp-gi!uRSQ(tuo0RvezHsfj+-X-|Ss-+kf8v&lkQsbpE}?j*O}=9nm}AIc6j>u0`b zcSx61zRq5yTS(1$ksa@gra7rGA4b!7dJmXKpB{VK5TKl)qv+8$Mk6~kpd-uH+Cn;a zcTlm8Dm(WDHEgZ;s&(J^^#l4q43FwS3DHTx5<%t6cW*xO5!Gm#QZfskx zZl%;^bswq|qtyEpp6URySuO&;H9mK4=_Kp3T6#S@{*BI8S8;bCJJUJW2Y1c*-MMV< zGISb?g-Tn~T49|R;-HJKuT9CcXO9Ko3$zRK<7LnC_iT2)M^y!`HZNh!^JQnpy}C1` zzO9w(JheJ2`PkhY{csjR?j~c7!DiY?S0wt6%zKb^iPqB<*F>RGGKGXIP87w|wPd+< z&Gj&uY{E&JLhI1h6!UI-?fnfURi&fqCY;klS;t`Ws8bFUrJmEm52KpR6H@V}JcZ{5 ziz$@H3cXm$1B5a`)0s435qc48P&%f5?4ZudDtW-+!J!b6_I~7ENRUa1$If7}$=+p= z{Un^Cw%CZFLpD+JGB)Afxapg|OT-LP5&1nc?D;4x`j{sH&`@I7+0g`$yB-@*`h<1+ z%O`%*@|MVO&0;CWASKoK7J1kw(LyB=7Vay3ULwL@qVbdc^vXoJHZd%sg*!9|ET^Kh2H!f}^QI&3 zu%gn#7eWQn=-ielvp=Gu(Di47#o%14ZCw7Q{`x2GCO3O~aM z_7oQio0IF=-wfJ%2)`k@pxYC~HlF?`WVFqnXmkaz6mJaudrR>P(Ecb(aRsQ{`$tRh zw><6dEQR_SU~>hc?}eVh8MH#;58MHQ|7&xF0Lgr0{t?V8uLH^er(Iz_g6f3X#xtMx zfTZ$W^?xiyk6hm;+P^G?){_xnJEcy{S%Zvyw2q?-`mB6d^%*)pDYv&&wIZGv!5^Jc*PUErALCZHuulsg1BkV-^Sq_-#b*_0gKVCj3gaqhKl$>2}HBl|~89gY_5?btpoh+SJ z)A`m85JWw{sEP`7tHz)ob)6K%8w`T*H|7V}L03 z3<^}!DeW*hl%3I2z&TmB{80ttM=7_!E3Y3{wTVhIB1M#}E&(YD$_A4l->%p>9$F;0 z{6$0Z7-*#OaC@$cwqZd;bw$mgt~nt{6mX_uGwuEMoA5-Xwp}~4yvqr@q*g_;_9j2O zn7|hrUI?~-WF58F&~tIey`3X<_>33v{RW(EbRrnGE^}SJ=l;7Q$d|B>{FCW15gR#0 zfUP(|)pBpEE?f^5+BU#idU)>#5HA3$F3wf=7CUU34NtFq?vJ|_L4-gQgaW|m;sZs# zATyDYO6?Lw=LWz)2O@KkvPdOGF;Ex?cN_YX93wgq#15Pqh`iG5{dGgJyuk(G2c#9> zi2V0<=NH)iQFey|>Ho*>{4HVsTf6hN8w;@ma6|C|nU&>F=B4Mjc0c56eKJ><;6 zjgV8NejwukKZL-6W?lW{!BLg1v$uLR%tJZerXI1DfBR*t8a8xHGpl!3bAO?T+<}JA z8oPR#CBA5ZpKYwf`eY``v4OMBc3#Cgv&2y^6N^&`a5{mRhllU{n4%UJ%Ayv*yx`NsF}VGZF2Y})Vv zP68=+z`8>eUbW?VopFLG4GlXCnC8kWSj*q3xq%txUSg*o=n3tUCLZD}&|Q;8ExP@8 zy`U+Ug`~I0>12G*k;zd@h9&orzD5yO0r8Z}uC1~#t79-pQ4+a`Gl+*GSQzjkHUcrt zq_?!yDEKAg;%A|UD?(8!sS#yK&??-=V0jl?yvOelK{*$=3|B-3B~FVG@E`;)@_bJ_ z>E!d>&wt!I9My+&Kq3P8y_#cI+=rp}`78{Mdc{Q3KE(Ju#bK)$u^q8SZQuMDPvF45 za@vu;{~91UrDcSbUMrQUSb|DLj#6(L7lP?{AQRxnZIzR^%8A07&&PnEE=I{HOT>&KNLl@6Ak~v0lnCdz(`T>hzOVuSELrH`vj3cw-x5n zsRaX-ztBtW3gMDILg;G>mB3iB23Qdms}l@1bXuOeQ?Eldl)eM;9x6Jyl9&NpQTGj| zI3L0!!!9!nge2DK5MXBpvt|TI{IsJmYwOuXuZmzR2Z=&yY`p1x}~O8;VL6I zjkCL1GwEdR2%YT-NFVl$b|bi*M~Ckzhu~f75RVbIODyN3!v)2q{dX>&a*sI{jajng zRWmOn)4X}bYj%?bEDOs$N2az)p(l-Ntm-j9O+ZmVEkH2>+aPsf#~_r@#&l890_Px{ z(5Q3@QGSlVq9DuQEbk8$gZx_MGio3s`4nC@q3zWk{@S~VK{;p>2C!jobpLxB_6rm6 zN7*oIz<-MI7V5OM{l|3~zp-8Q(eiJJw!RDR&?#MseElbKh}gF=sO@JBie9Cj44blL za1dW5Tgrc~EeKcyYu>62LGC~Aw=?c=d}$m;9gtckZyJP$Da})UzdK>y`N|_$1g9XQ z5fB?}Du~ow&tU}+5 zN8h14k39?#4>8GM_ip!uE06v`CNePT=B}42mS=Ss`6mV`<%}rQ+EmnNn7ZPWd)L~`34Mp7{#L-uy&zgIT#-$I1{55oAP z6g>(k4Ed#^|8|}DO}TYn>|Z%Cz?uyhEBZVo2K<{N5kTtr%%(AXS!?byu$`d|gTBnP z&D^Uls5G<`mFjUeGUUFG>3T9_|NWF!_OaOI=)I`c%Zvm>0q|F=;$0Y`JK~Q1wi`B9$ECJF|d-Fp5gT zY4+~7PQ~zPA!s~4MoGEfA=bxO@-KaUoN?gu_P($3x;esM%5R8Jd8noaYq9E8H1)Ib zZgjz>y(@Y^_Fm)ic-364WE8>ES*od5A5l+;r3U{QY6q$rsn~9aiJSsKnI{QDlhb&U zz~}t!h`^`frh*)o<0&6u_3q)->*xUO8#HN`IWD*&ZUO1fMmWfU;4fbnwLw_EP-M7b zei+7t`^*8>4I;5*lepKAOAc%*D~UW6(0|0T*G2I;0I5}Y@WWt^XeS|9LJUVk?Mk=t zaiT+c{-jfC;`noZ-RpQ3$;GX^LJAL58cb=tsZ_zX^A((+Z01a)w^snC?fu01= z&=FBf^q~sbMn0Z{;bydr`6C>>@TD%=5?SIt60R{QQ?7BT8lew}+ejrZmW+aFxQIo36~7r1l6YvaBLA&vB~owDXpyA`r-4sAGX zsy;b)38kgud09{e9g1NR>IQ0-LUM9oI=jsoVrjd=xw)d;ZK~E$2SE{~&YPu$$=rxq zEBCQQS8axL$+ppfZNb6xq)GSSYL)b%B!d-6l7!cI4O87?jdD2(bD-%{V}s|VCik^f znGjhZo-c}vdhU?t|n$ZlGgvy+JYIfK4flsgR9MZ$5 z2*tBc>Y4GjAJ?bSX`Q3n39)N=Rp_izS2E8|xiTQY_qkdd14Et=bqvBp>E-)7#dGB| z`Bzr~RE2vhtCMIMN1ikbVo=|hn~~lfgq2PWCxdJHNO$Um*X&!J+vW0>Z%qfu=H&bA zaPn0}k!_2Ik*!&H6dMu?C`sl;iR9a_($9YK>yb?lAEwC7{!zNTVEe1RD24I|XrV{S&Qa?84((wE3{(mJ z%7lCoip{uu${vwZt_w`aC_>a7(pwlo1XSgTBx9g=N=x?)GvgXNU#`ho$#j2OW?;Qy zH{ervuJCem02u?30xupx`c z6h+qK6L$0GdQsW)$7Y5(q`2tW=dx0w_dd%lq*#wFtL#>kB%It(ET6d=G)VM}su89Sc-yBC@%&0P>s4Oo#d&(8Ch~RfSi*b?8 zHKaqnJ6dmRrX5`-Qa8!IH$8Zu`mpS1g`N%%crDjErFk%h)-8Wn6T0H#ZekSGG?qt% zeZW_tqmE6r8(Xe#xE`xK4=0i#%d?6&W0(A%5WEmdqgbEb2 z6qz6ya$MIA&jutfuKZ8j>lCD~Dz`lRTsPWFl9|Hmn_NQR88G4{o*9|U_l{fSKJXsG zbR;U9#rAnHyl6Opv4J1+yGsSqzH{#4WvpixAo1C+9^6knRk4xe>|7oCwkm%~$BJg= z&baBE(i;noEQ=$k{O+s%(n5W2$osC^3RiF_fasv&8Vv2Cq5PYQP~ff-kt*`C zc@W(T1c3$QEA}CM4Vln8?JBUquW0I$(`N=@iVzj^B24>IvMtZ%3^4EVQ00_PJbW*B zXt^cM5^4yhR(oo6r7u z(PX9}O!BSt?pc@@kTK=~f&}9Ag9Ew&;scZl_X5poJcGSCVA-8~=MDhoZy@~lnE!&7 zKMM0eKy3M!nE$P``FEJB%i5tZ{#BIKo+mNX&-|jR>@Z*I&bL~%aI!3 z|Kq~lE!CxrbQ%H-8LGI%veRRN+3r-53p;wDAZhg=*^4mMT9RzsI6pb&3Z=Q_VbRZ6 z4FLKV6?nyaF_LT7eF;9NFN@o*``2Z4e55!NY9{({gHKb&(!%qu7SfJ-2{(Iv=5ZeQ{p=uY&a z_++EVCkdixW3U!ZLCKuWHrCNxkD^aB3|w6Y9t=6$YU|T&Jq~?ADwlR>xN--Fs(wVdaB=e_5}dS`Qo$L&_= zj~qI$h9sVff-h;bqq%s(+Y4ri?Sag0##fR)9jCG-{7xI;p7q^M@;L7f?g9sn*5OsY z(zCLdtKB%I`*p|VN0p$~b@ez@!;5j?&n-oks!vAG_4gIlO2p#)!3ld5KqHKTe~=z3Q^qUZbu0GKc9sqK|dKZL<(~=u6L;B zX96sJ0sNsA-?eWc@LcvE4i3zO?CW}t+<$!M=am%V=iVKKKX`H*&9sN)RvAW6|4A=j z?)jeUCkg!Hjqm((g~ma~xJ8V=1Z&p3QXb50rEI&VBJ2 zPM5{-d$g%pPLJ`~&^W3nseJA1C{$(1VDJtb)z1|_atfO#{3ZE_Q zOO=3?Nq3M?3p&<9A(EXS)om}QO`HB$nvBgeGX)=f!;)lnSS-hl`7>S%DvUHGooAtN z@0Cly5QO|nK?Xf=>6Q7pBqbT*TN@y?TZ0w;$g6!&JfLIsV( z61t|m`hcI4J*`GKoW`*+O0etd5j2A`WJ^1a9(^T_KQMVOdEXQw0Rj6#bph#vRRL8& zZ31s15rPmB3qWK^-9x@vj`~5NMkGK~=#8!a-g5i`z(2}zTmoz(8X&s$|HV3g)e-)i zi~E?;|84=}1-8VioauE1WNB3B0B!ho!NT}oG+88fIB1`}((05zkkdHJ-Ar9g^Xo5q z%BiMh@lz2vdRg3d zA1&XtmWNt_d+Aug;#j%ej@}~#flAgU!Y`XtZ=l)@hhyD{1*S&MJZK}+-A|jEKLLT!e6FTDGs5ihn)OAX3-$>4GZ4htr+zd1^Q}bR z2!aed30tFxTZ<5HX0nGz?YcpwS0IZ-VWCz(_Q7TBPAhRf0xzt^e6hgd?p78zQ;s~N zQaJ+y&TcuTQb!v6V4>VfNUy^6$fbMjMZWMe5~B(BS#c;jgATW=i~S0*F#fsv2nCn>eL5a~--1b;+W^z({018l>_PB>1X05em?m`1T-GlE zY*c~|rh09BH<>+GAnQj5{9f57&?`Bz0-vg-uxGQ#EuHS%b7AYhLq0Zc@P#plOWV@l zc3DIwo?rW}$NR!qt0+Z@AZJNXk*1XG(Mx+@#dtc@D23{4U?D<$pY8XHC9O-3s0u|T zdyPwN#Se`Y(SkCCf`K`}sj%3nH-B4|$r@l}SIrQ;&nc zfmeQ-x18+>w}nTm4KD+G6bKhM4VVQaefF=^5fP9@Y#~6)Z|D8rYWWwU_@lJk3yALg zLd$;(+x#ZDJ_l$x`Ck?B%W6@hU-5it{#VBdRih<>q`kCCcM$aC^L_oNn5wD(VR~NK zQ$TB$yXh$@^rL=ZU(@g^eZx>fnLS(SS@7P(>6Swi=#up?RPepv-H?c6H{UpJsp)u) z<4>dgk}|q>>4XV27nC`pn~<@Dgz^y+jamnti9|QankDOP!?-?Cje*;eP;s_mbQgT$ z7=g8-igGQ_b?%0KOhc&6%bDk@r2)+RjmQq~_SdH$FDszAm=KDbv}C2xChV}>lulot zf0`nS!kYQk^toNJ1czQ~Jghd9R&NBh_qFG@@2yu{q8kq7i{zp@6f1>qpS5|{Mz#hR zLCWE|Ptt$13-qLA`z$65Gn^-s4Ys7jeP^pSizH3HK6V@ht~=rkeSo*>`#5!~yB0{- z2fEUvL)#*jTY6WR&eh|SZAV{tF0=4D^zND8X*0uz2GZuq0Sr19s%f)4ArmXA6jEOr z9)xs3TbdzQEG5>A&%qCe**!(O{A6IVE5DLXB?XkThV#=4lW)(Zf5q&nza;hEO6u z4-0Z@G}B9$I}mnviT|Tz4`VDd4$Z24IdBV-JULK z(AvijZ6@fi`XiOsD_vosnZ-K|H)OVGQepY|)7R55W(z-Hy!+yX zN?9H9^98R=quc22H@d6A3(?aWH^Dm(7a}Ao)y8sI2?ez%S*R3P*~Sisa}(N~(RDnY zHVqbJ8tvC(TDwI+ckr&)yG5nXKkJsv8NFjYZnW5wf6yuBE#6?jGvq3+RTi(6FCaCF#v(WaCNBTxR^&GHoA{KUUc;^pAQiHSUX+l@^W&d^5oGXL#C{Bt#l zSP?Cjxa?nw&qSPhqHuxX4_k2%1@rMb zK$r~XUC@(M(70!F-Wbau%l60+Xp-ag^Vf#*M-u(!RerVBycJ{JPs4WdXxY_1 zA$8vt%qRKyS>a?mHs*X=gD*_czI)O}vIrE7FwPfJ_SF+JNO9hsM5ekEx^wSKVk?TmujH$;>Tbd8oOP$zg_1MoJMiC6{y>zD$)uD(b5ndrLGJ2z_Nmy*8?A6Lizg)gMSd8zj67 zr7GUfHBKtbwV10UndWl&a7#pQ#&;3u?k7T41K~wb>1lwEJo&NX=3!*6Fx5@35}fS1 zM)@nG+l>L;c)^3tR8Vi^1RZy}Me-}^jNQ*i6Eg+h2%g5h!}*Ef`oQ|W`uuuTtC98b zt#}z03%GGiQkzA8Pba-L-2=`*VXy%xIV5e-%aqs6`xtY#pm0#L_tHBbuqKr*s%$)i zki-nHy12iZGy69r%Wa_!;W5m06^X7ZH-+xPW3pG3lXswoDRc0L4HaPcBd~iL9!Phf zu8>&Sd9sY!`V_%+E{6E(vT8AMPHExfLo15^WG+B2U&OCKJ!uICq05~m>YTTn3*Cqw zSG8H{Z!y;!$o2eAjQyzRWGFK%o(UZ-iGq`$pXW4u3MHlT^(A_dCgjtMRE!I}520n% zquY+&_m5VF;*Rtol6#~U4PW4hI0Bx-8dXzoKHXa`I;ah3VBV>~pECt(x`wQgV{TzzUpZKyKqt1&GFJ;H7me z*$THH&t)`WZSSmH8?{c(Rf<0Djz2TEyP_P}sYmKA0p2u|fMES15CzaIkP46r=qAu? z*?)$TrZ`&Vqyq?kQ~7^O@GnsKqX>ooi~;-w!GDV){N^TkrmpiAt@pX8tOB@9=~F8` zUu!@ZZwDNXt(v`8I0XW|y1^=?;&fw&#R9jnPHuqgsw?&+ZD+@;a>pr^gY2T#!joGv zG}AbqMncnAGC6$cFj`x8W0o*k7FuDmP$k133ovq?fqKwb&6qUvVdXC|zHqN`VzRTFZwIAe@(ULo+Q`9&4}_ToZX*t2a{Egcv|tFk&@f1PaQ(D6Lw*N}ujUI|k{5!O|9~L7YN}S)I0_Kl4hRSEYJtVkpk@ zeHGN7u1#kB1S3-MiTWE5#hiELI>9iAR92k1U0{Kl-bGT}>%3who3-TMrJd;$;Jtaj~7EpbX`qUTx@MYd6s9FQ}Ot*qi={udeO*v*Bei z55Ehyiw6i72)_%ji&ua{z#WJSh*y9s3+A5v&C`>n?=67>0(WmL{kL}F7r^~dcH#rT z4&nlArGveRiMxT3^FP)p{>EIWMvvHjW(WFcgpO%o#lPaG*j{z*&RK@6* z>87X#Hp)V{-LN8L5vl22^)jufK6||Mlgf)Z0cB|e%Z|p8A$;+_y(nTJ-Mt;t?^VFJ-x8Vhh-?eZZtGRTBUG!o+6TSq?L$G@_|WA1u%df4rLOY0%AY*z37C_s%X z5!P)4zDLeu=L?5u2`8#XGjmEtYfCb|&F z`)fzs7TJ5j3D&H_^;da^@|~@pd|R8ckR9c*XR1U>B`*qo@VsD%((`AqtpsIGAo3m4 z5z`_Jz!*$zei}{-^mM>rMR%KQL$}*01yq0s3AqNI-ui4Vxw{^^ZOl?mYh$(Gj~E|1 zmNT>yVYQIE9m>L8ZMVov(ve6T&;s05j70#9ulNTodBS!C^%j*I#WSUszk(1xak;lGsWFAn;TlIin5%JgrMl;5N~Ok!lMRv8e#T)dzO(~bMnYAnkV;y~3!t;e`p z*k(g&sJRA}8EZ{fJPN*gnJkSjqrThm$V_$<6N>9LGuah{eS}Z9CoD(SRaJQLFI*b| z%4X|=?HCVRN!xfI*PXcRUr`RiVpl)cZA5`&;&Yu?@h&A7S?(Jao+pR_@CBXiK{Kft`6kG>K;H z*t@xK@$k-_HMl3!>MZnYKe4?n?yGv4byKVQJM6z-w1J$O`MDl}bs zW1^$<*2v3Mz0CC%`1?2dx2R!5PEoLKg*W>!moPno`HdXpjh)suEoEx6mtAr~!j}|y zn*)~H1c%k9XtgvW?Q26xYF@c>IP`K*=W}XiNZG!Q6pT5$Yj4eye0d^DGcT@DG=Yte zwB+9;c=Pkb1)5*O0P1}^+JC9uU!4CRrQSBcb>{c({%>K4-{eemVrA{p-qiaPsz~Qt z+NUo(<4mGznZKLBQu-bJR{v`)_po7?d*a8U*LH|g9c5^X%bW498(4xV-Ri}?9s57w zAl5%m#^7iOKEUEPY?vWogVPv!#ORQpD#sPrZ zrFTsPnWTRI3S#?fmH(tWobHy=L>f~JIIbIvS1Avh6hvKFYu?-#G_~HqsZ(*PMl}7o z!YXckTK)+Gw})t7yuFfKsDP#BXV#YtSN0PEbO|1p8Q~&(b7z^ummw;O+Xv5U#%<%oJ*O2w0 zlY?8G!ERLkus3y%<)|iyL-?m&>T2s1ELHP7MLj`RbGn$u(BT3 z5!2u%5jEgrcz72te)^q=- z*6CTO85Wx?Ql#Qt^Y7-*#aaP5mHwuXIsGmU1OB>KAGk#@Dk8X#>&aK#!ZDP)xLyHSxs?J+;8Cry$JfaNpO-Ags7c@)PbO|@BXYpR=R{NOUolwKP& zZ)g%`becAdo4+~zAxl-z6w4%Hrlg9aG0fj{F7|9{P-Xl?7$1Cw4Cqz3SlPMML-c;W z`%&}w^o(_?aQ{i=$(;&Jn{}JQ>FepkF`s$eqVSF9%}o1zxp&`P*CQywk8j7tVT+>M zhWo_zR|^WOX4gNtI-XTVjB6cfEZg)c(-nCXnR0t)uudv1xz!MCbFQT9#vD#>JVA}5 zArD^CCDeOxG0c>~ulk?z;H72N@|EU28D&JV1H&I`)DXBoa(;Dz2+^N};Q;O`$P4I%O?boZDXh`d4KJ zFJsv<0ERcE{7V>qA*VkIhV6fZ;cu_h-vo96Fue7n{ik1n1r5_$hAlOos$R)!#U}Tz z;rN>$do>#-gWO@z&kZYm86e8(-iZ?rGafd@06ysWOsR?ug#oQNt8d{H3U@fXcptwK zm!Re_%67ZsOheL#P9{p_0%(xD)jW#9v1q(3Ye~{l^EGpOI2vQVd1&CjW#(?u?C(ApW(Xv`|CYS&@rpLx6eph8pU zlz=v6E`Dp>$uw}y&7i{uwC==61-J%lO1e1T^}Zf>=ym#hk)PD+or5;oS&#Q?!)8Qs{9Y`LHQK zvgmQZCa@_WXWTD;Jx44MCWy0Yx?capTb8z9*!J3Q3kC3>j%^j5uN{aX2f73FhcJpDFOPPB!}S zcoh&KEc&Qiqk5R5wU({CFn7ilQMo`pj(~j7(zDjw1&M|s+jv2}z5a9lHb=(Fr}0q8 z-al4%_2_QSGkkc%ggL56xqomEofbuglWYX-I#i|KLC68w-iOA;D!#Bp1$YfZRweZ~ z2caWkd82%sah!}-YAHS3F$StO7ei;52{Ob0UqW^5Naz*I^x(U5%{QTaa-EVv_^G%S z9#5~Dr^XKeAM#*vfAvUG6Xf~fkja;u0JX(xHgz2ZKn+4ze-Vjg#sdGA-P*C4A%;ii zAmqmyPmpMVJVY!Jc*Z~q>nGNv+jx&>x||QzY#PK8WW8KkGcv26J1o7G{A<7FTeTR_ z zxWON6tuT|^Y?VB|T8lKTF~ZW+Rc&$%c}T#D*rz~jO1mc3Tb92PoOC-XHfV~2R4d(@ z{AJ>&E_QDGl{}2Y9d#4P`E-!D`JP^A_s9EFNCOJNDagkfSpa-w0QgXb{c5211Reqk z1ZJSo1Ri^y{<^+>z9Kx513XjS)cG#~{Dp-7C;)o^1r5Ik@NZKte+%IM^jHsoh9C>T z^$kMUH6IX}Tz6ej_0qcCM7-!<-W!R^BRKZ0{AOfGBbsvAw&T|SSvDq1zNR>$ev z7(&H@Qq1EG!1JvjG{qId6eaXRo`P1uXXwktTuR?7jVYH}F;c}6-E3^<&ANhNL8rfcGY(k@1dEW{=-R=VH$s2MFYzffXU47ua=c^s^eBfS@rwGFdU z^r7|43^`KCs=8fp1$E|KtW7`~ab823LYk*GFS{kfu+NG_Z&fecKE()czxn!6-aEbb)E%VuNR6l|LzY|Os*1thJhtr*w}dNLE2 zRJl^Yx)me|0)EzJaSRtXsqxd|Il4afB9onQ7_HNeVYCcZF%T?$0p|Ncgif7=!*|?w2qX z1uuB^`_61s{&Lp7qLU$Kl~7#O&uD_w>MJydl~C|YbF-bPUfK0A%jwpJVPYfDVfsz0 z8IOZ&yUX5nV++>0LE8xX($xiE6dH;!aTOGx!)h#`DdpzyEP9IYah2x4&dUeBZx>9a z_G&(F0IqLZ{+GD^LhyeSuIYf;wRk|kOC>LgNzlnG44jcXTRJVI#n|&<>B~3!Uu(2^)mh>J-${!J4Vw9!;hCj z{mhZmWA{IpJFBQXvu;b{?oQC)?(XjH?(PJ4cemiK!2$#)I0Oss9)i2O^+)}uM~_n{ zsncWBO)j|M^4oJh@0PXZx~h3K6q%U`CkYOPh8?m)9oUmDRG6g*FnL}nE5|CrSHjJ> ztoaV3@JJS^mlt)k?Jw6mapUIb-L6-7p&?R?Td<)cDvqqa>VRe5S9o1_AX=vPxRwta zn@EA(-+rXuFkkt!>WT^tnXgjYF1$=aWEtE9TqpHDc_U3EqIX-G)~kw#t-)b0|DZ>W z71N{sGW^m36tP}K(K?DEW-9w%q|NJoOaeC*!CU{15kxp9PXmG>nm zG2AI*+eDm_9PIEQy4&pgi4V2$ksMi#5FgbFav3sXQ=Nb zHctMV3A;#L8X(TxP(cx_A>I2`ArZR*N1?W--2q&R!e6xgBh00+hth8uGa ze5>@igR3>qX|-+I`DEMT!!J;jl;N>sHuT+TmziWOG2JQeGBKT3{*fwhu!Qov_7s@m zS|SLOLH18Hn(^}bf@0Qz0nvxT| z1|z^gnO6rHn-g%B+7`@-z8yV*uUMseHYcPyI_a31%gd(xlwm@Yl*#S!3nI`zM&)80 zfp!xzxp5XgD-`>=UtxM=ME4**ST~hmQRpdxBv(>^OR2MfXH;82uxKknB>$Jooo%}R za<~c4`=x4t!{6lpdxuj1jPbud(EQ3Hf0V`H02NRFVDZ0YUjEMFs(#bnS z1tBJ=fB?3{Hy`T-ylwIVv zbgqj|WLVSi7GKEN-S~MV5oirLHzny%)vD{Fy*ZtWIGsZmZZ;-ZXAN|v8hF3LT~@+# z@@1B@Z_VDg5GoQx{+DS_1-WMw@Hkv>dVe^uToz~P4kS> zZ)G3nX3h0I>E?=&V9U&_9#YqhDU&I+uElq$lw5L{THJGBP)h&8sI#duW*lJ!UoBzp4wWYG)3rI zFAosP&%dfwd|u~UU-NnL6<#T*R&T7z)prg3%9(E7R{RO4vqdkP>rz7QmgN=tHLktA z)J{PkqJjK|j#vaHL2wHRybq)xxQx&$yf~`!8x>~%!yTL9Cz=)&&ZMsfkyiHzd!v+F zTc7A)X@jRYO~DI=L^x-5Csa~YT6xmH5Mjz3QiO>%NM%c_i&9lB6kz2>TIx}#eW=DR zq!Z)p*-bu;V=Fx00--8{MNR#f<6vQ<1vrE#;7vfa*=YDx#~LFV;JyRlh}j|2L+NU^ zj{jb85#a`I5R0aWMi?x=64B7{+jx|=)h%kQwS8d;h4 zY^_b=$9g~(=LR<_sS<4Q=oH-N{Au&&t?OqE7f+3PB|Knm-Ss6#Gws z4+ELPCW8)RmI0~IOn^)n1xN{OgQ)tKgO>x(!q$Ub)p7qPN|Fxmdlod{Cicx)|CS!V zu-_j=4^lv|?my7uZ#kI1dEhlwTaI03L-M&*NAhsg(m**aViO2}kXTQ2o$LVji8nP( z9h7P$J?6sPS{IHGj>rIlo!~Ne-*E^{rG>d@+&>s-Jm62#W{q#PFR$fH*i^rGdFd2L zbQ0&-CMx9G+^(h=)Zfji-|cu`{pftMY~{>X#Fdjf;4&AWlapa(F&KrjB7r|}&a{Hw zf9yJPsGKry_kCcOjh|K23omXu+9*-)O9#vI50}$H*bz%P@{2Y2-1fO1?{$515}eGA z&o^!+@=kP4jr{S0DzXF)-<&djoa8$Nne%0}kqO2fv$kFBV!B0MHot1`g3Cn8##X<} zeI!%hiW%c;sXnmE6QM&r@W$3X>=qc<$ z`gF^C9_9JPN%#56+iI}F`f@9l=63z_+EKO7r9d?S_9KC>KE$o#Ls@AY7g8KwU=LO& zXEcZybYY-kuZ2N9!$(DPlqaUrOyAkmdF*i)%KCgA*napg5A1n}^H{=8+EmoVwaJeC z>B*9Cu#7b9q|L#kTkJ{Y!@8<{3Xl4c=HU(tF#j6J3G4I(MO_&S11 zlT!~Gkdet&fo@FB)N3l_$RPxh`CGmcrje)D8Ndoe zqPf%9AMsFxn}BO-(D|bcMHUH0Rfgk5;6R(0DpkWBz*2|~kxZq)4k|KXbK|&C#~YcU zqaaeED>VWBC6#iNNm?96B3~l#I zyr5hle^3^f9|R}#3tU~@u+-LgkvYi&bM40YxA3BjSz0@}8ry_!gJq(i?nY6FMJk#E z+bG=X@^_1kj*kPd2on8ft$mG@xeZvNU^HJu4uP@7&Obtp(4W&GQbiN-Bp3IALlH$2 z=~T{<&KaO}+gw-GfE7aN$-aK5#}+-a!d|C~CUU-x`pnd*Q+&R$xDq*1+{z<9P=2aA zO^<0qu?0*=g*^w@dYJ^?y}ucikBHMed@Vk=f36g+<9}jq6~)f83Hl}tC<2~P!_ zf};Xl&Q=DYVy^&NwwQ;Z&?YD?Fmg=w^XJaee_EQY-DB1y0rYuu=D(%SFYNnA(Z>SN zw*3$E`CHY&Z>|$e0ddy=`T)AckXo84X(D>(mj#=@~!}7VYfbmmIhWR4_Nn)3f8Yvwo&cRt7HXD#lJm1D%Tl1Nz-=b|BzOLKd9@_K!c|M)r#@T4igk_%| zq|Ph%uIXlC`UQJ?`raD49cPleKEwT#fAYhAGEc)lxX(xEyF}}D1j8k!b5!B5!iA5; z5DzoPc2VorJpW933OAd2%9N0)^ll_1K}*A7hj%K>I(nmpxq`Q#sW!{WmPg7LlGlr_7O@69QhE(3kcoB zhz<5oG#*tm{b+)Udh=u7MCb3 zXOWiaZftoT;%90mNE=4`3&69G0xL+Ffk=BEr0W!BAmzMB$$+GS15>Vep)b0i@vj4Z zVmdOEVrmzCSBB!kh&P9L?(gTk4ePU zm*ni=oCyphjZsN-V*LcpYa1lsIc^vGa($p-zaB#pPfe&fdsEJ*=5w0q+wLtyj(Y~D zo}UQGqiIv)9G?B|v_kmn$@2c8{R55GjK)pxRe!B0u;M} z{U6}vakxlh>SPtJBs%4Ot2Xpb zTy94W*9MEF&jTk4H>2@NC$aX=Jl4c=pItmY^?Ny4dY(_+mX2(?dbR6aTIuhoPVYW1 zzh9qU?rm9LHM6mw`Eua3std6uevZ)ceVg`fMyCT_M|o@g+C!wSs*rHWNv#e_>C;PYCg- z!rsYEEC-r!b0QM4X2loQcg_9f({Aa*y7}3u!aMg>SiG0B-=q-&&{AMN}{E@ z(o-$Y4Keg&`n9ta9`-4q5d5^*728kb&(e*(N83+d!4AN2E@i zU@lj{Kb|Gxm%|zH!t;e``rXcWX=&(_2N&a&YJMKxBItUS1vf*`q^ki$;c*vSkBm*) zaTo4bW$YrtjMNo!UtS2Kj>EE2j1HlufFv^&XpMhD{~qdzoF$D+H_ z!bx1~KF7rN4~ZHpmebiB?erK@U34WyEF%MhJrPD<5{H31QIPd`EL=_JSzXw5Cu;}T z-6zwVn{b|<9-2Ggv$Z0;&+XPHruP_l(!-jz1}W{4T=wQu3e1CMoiC3w47p1LPj9s9e1px% zk_+NsTPg#gD1r6ad8CkpLT4szYIeK8*5+$fGVgP_V0%+)>E&bO)Sy?_;j+2-xTbDDQ4^{gg!p_PbvvB!dLs8l zNq&{1(d~8?&$`*>hn;S*#Bz)vYcN-dkbyM_Jr3Uw5e+hg+A1 zhvQp)KE9``fv{ovgXeMrSNhL!y4NQ&?N{0sPp59D>ZP?_<#M?KFX*p+Jh!=BuM4_< zM?yaq#|<2VIW9ONIZ8aJV!2|;V%cKp?oE2PpoUPYbNt3h<|W%U-B|7GQCF-AtB|6D zX4VJp9+Jswzj0LoM;pT6QV>tzLOJCzu7$yp20&9w5yU2164UI$V7U$FaT4v;jWJlK zxQbbTB8(<*raDe-`a4WPQP#u_x4@+eaarRwO>rF+I?=r^R+Z8k%{q^m#7+8Oc^uF+ z!0B!j$oLU#8KtLR71bv5AY*NTvCII0J96~y2s|2$Bgx4O2qpge zXqLe8Tp;YId?w>b8nHHNg#YApO1%G#z0gmR_ymiAfdY=DOkgOiNmS&C%j9qlRw z1IM^p3t-Ft`o(V9@u-ba-$O|M^JcqXsB!aH?zoHhMr)c|_HDob&NSC*!0k&M$?JUY zk`0!m-ri1cr%`wMLSQPedDG(G(Tb^liHcJu32VAE8< zo>L4i^zU6DC%aquf$~Saud4G#>$9r*1d*%j{kviO1+^RvA!tgr!K;G~O{*B4S*<_a z4-N_}JF{DjXW4_K8@SuAE_DxoKIy2>x4PR_%j)x7Ki_+Hu6DGyr#uR)o@90$>Bny8 zui?`_pSTX(V0ex_Xt~Y5Kk+$-DW>Qjy_?YKM64wn>8uzjvw3JeET8qMMJb*kt(*hb zDsPSA8dO4R_pr%4G`Rm@WnFL*n>yOU-OdL2RUX0b(noQcYGhLFSz28@sdz2ZURJx| z(L|}3tYo^yK6aFUb4j#q2qrBuQtH{& zd-l__#TD}N{*ER7SjY?{1~Sdy&(~-~m5eC6%asf#j!c3p(_lTtkY@o!n zHoF3^KHdpj1x$gX2Qt9aCr)vQdXo&97ELHwgNt!E^hu5m6*PLPvpe1x)2&-00R|GTBJ;e7J~RB7ziDTaayADBjkK(B!JYPoX%Hjy8(5M znl`L8>pk83)-r;zQH(kl)IOmg5Tu|I2%T?&sVef+Zgyqv+U1AZF8JK=;74sHK%QPO znJP=!L)sspo`F@ng(QyPY>v=`CPCP1&x&9$)Dw0xeo`Hgtx!-_mr%D#N@tsN*I8m{ z$QPI_vNV#~4J41oLwY%>6*d_T5d|79K9O96u$Z9AKtZ(O%LfJ8_~K-Qb^g;t)u*#lMi$TpTKt4s}|K57V_bqH(x(8e(&WtORVVn{Q%HjSXmw`RAB9WmSXuxCy z!VdyxNz~q_!h>LBWtYZ1=pcq9(^%y59YyKFsVjbmQCed3s>O*060F-q^x+#F6e%K+ zq0+_O7?j3lxNga`HoqL2R4A-hz z?+}ZsEVKPL@S|0k9A!R2^OeL^QGDXFMY5sA`0UgQ4VH_u)UK$6@Jd;}uHC3oO%%c^ zFz+2nrd-d&@4Go9<^=F<<%b!qnI%K+!&TD~9~-B*M#1U$x>-AL9IAEYD@vy%TEEFB zY@SVWd5^}NM~#+l2ve%R2ZX9HePU8pRZ=ZEC_0c~m1Iq~O0r^}C7m@bmoFcmiJysA z59kN3u%n{?j94QuNDW4VeDMxA3O9H0^=<3?`V-=PJOJWvfcv)){{;pAD2Q7FYBk^e z&!UXq<$C>M->j-IBGt!$wt7oV_eII_vW(#e8d!)UaUp^$d$Yt;1w5M%iDiYK{gh&4 zMb-fC?gwGY72^sk;v@3!JppwTJZglU8xAz%WX`l(G@WA$Y$Nt4QXBbS47j55mT$-2 zZMa||*Wo;8Nafora0E+oALrk6xN%GEdXSZ)h*-)$SxAJ;!0CMhiN+3y^>ln2E7i!>*2e|k9Vqv7>}w((yBVD@!kf)rhbd$Og4ces2_1JoA12or%Wh# zIn*pjCq^!~3M6xf)fT4Vz3OLrzjo(4z+t%I-Eq%UXFare+=f#D)S|6L+`(U!t{yn0 z`&U3fKySa`?^QzjkMH`w`H@fyTtS)#G}lV+H1RHTk#R4e#bnVFeQ&6ZIsj=0agg`Cq<3#?;nGD8M8$#timdENe><>HjG{qvWt=)be7>EPy3nmRHH(lI?%0!S( zas2s`Ndm$Q54~ zRt^rhY9ZL*Lj~Z2IVGhrceS$xlTQmi4Fd70IG`s@&7Ki|0QnrS7lCK;3HcCEF=jFr zu)3#wK$zU<)Wm>|S(Szm~lCt8k&{d(1&MXmh>`|m)opOmp z37A$AR|}}Rp13AaG4)n=CKs^p2sdg&fu1s2h&ziEZA~@fB?6v1)Js&pcy1C*ntUhaH5t zu;N?~>-1O6o#nMOSEV=XSdPU-3cf(>s@%gfokCU}^0s{GRRN1J9>t7Ocv9jNP82x# z`NFoRf89F@95Rh+0(AJcO#e!UzgWvZN{4}f%hlhp?Qgrj-xRvJC{4=s077)u)mP4n zAX*!7DB{yB0_h=A264{wm&;k0*ji)v*O%))Du(;CH0|a%*?BE91dDgb&T}TMPt?FD z4W<9YCh26*r&%UK)6Pp11Lb));fNG^rXn#2J0xhs51Yw9@btK z9N3^_$_m|EVyTe9m<$u5l3f@bb}vUNhWzfZ`Y>li>QT->9!YEgEp_~hX&9=A*gHhM zbu;>?uv<=r;L?DFl1#46iRJSc38<;ZH5Mq>L_a~z2=x23#>`c#vgw(k{rAtDGqq!L zt$ReZ2}^)xY#c|~>MKeM9zM;&OeG!d3g1IxcdW^Lea11>-eKV{zPsU**aifzgX)(N~c_alg#gQ`nMLV-|*5!Symp9nb8i&N95?Hapa3qp`5@#tW$+HPVHi=8_%j0P+&aN@i7Sm1U?OJrUBU*RW3g)uXp4*-XXvoK& zLHMoLjSbUP^86*xQXahto9b!M-&S!#$jM(K8^a?aB1 z99h_)_ZHtbJ~FTg658En{R!P>Ej5vvk6W;r;w-t!WpGsr2g_r?Lik6 zx;m8RUR}0G!#`%n@6M?hO`B*HbN(a39RsDtM+84xtUMiysv>+c|La*HzvQA^K$GtH zR7b^J2j4(we*>O=G*cW?@Q1h$!Qb#>kTcv_1T78}=gkE;oVJRX*{GV0VHj2tnMEKg$0%Eto|JZ0YG<-Amzn1;+2@FDUW3u04jP^K zQ6-aE_$iCXWv=!>@?hs`^E9&^n7X@e-3j0B9~&AC%+A8 zEBA1FIaK4R+aU?3)jh0ByC*6C{Pf8AbvK=LQ|0&w|E{;0I(H^q!kvWAcOMfz489NM zZ1G;p=p!6}0H?c0x%SJssG<3DCK+`TF>2{SVobem%Y^T4fu|zbbd~oF>df1hWVf24=~K zdvPH}pbdy!eK_|IqXskOkx^{cOqtM<#Dj>Gv88)zGDCglv5azvzYgINCvJyvjA2&QEW>0dplscYEEnZ0NCM8E&s|6zmU`)We0OW z+tBaq@QZ$+UtHg~sQlFc*M3P|$!kk(bVmMv-OyIC(|7!|DRR~m2R>S zDUMt9RHm&~A)ViI^lhZA_;{^wW*HE%o;yVss~SVWFyYLEwIar28tC@4+a$BwB-GE7E0c3mCdByO-K^ z4g~Fww^9@kncvr^mJGl?!S33aDs*wD(Hzw_KwDzpyr<_DRX{oG)1t3y#)FE-SZuS~ z75T^}oB!^yeyHB?^(M>v3neG7_xf0=65^a*+a zR!5K1A?niTy0Mn0hZ+4~+px`)(F>79?FO*C{PotXSi6){epBi%4H&>GNc0ka0mH6_x%KToryhU(+f z{jS1<@A0KXeX{9d6%E~yKALh=PfuoD_KIq;Y?-TA<}aK{d`A`J@7l%7L3mJ@t?k%) zS4xMF2U&+sHfXRZK!~6)9}UNSMaXy_a7OpFle|PP3aY`usyY^oTY;yuNAm-zgiKdN zk1&u7?~IatM11o*DeNMq5Apeq)dG1vLk2rW1PXCLn+G8aFpFRrm%5PENiJJ z;m)@YP^lw9FS-OaO1mfcaaPOXDRHa$`B7?eYD+1WjvCS2&5Y7BeRs>Lfp$K#;9gix zRC>6Cs?|#LS1>E~zd9vW?{zqxsqh*!K!tWgRNA%k%B;>1hhF z@h}KF*Z3f^Ol58Xu6sUi3X|tQfg0a4QnMe~-7F_k4?AeltE+tVX-FGIkiE`4T7HOl z)`{jvkdMAO-9FQcJ@wPa?p9(P(G5v=9+Z;&ET?yuA|&65GMv)LgQ^hBVes*YQnhL5K(BYe_MaRX1rtnM-Eb zfrOZGPBeZBRN+5BA5;>$`yW}SVO zbju3fBNi+9V`=M=)y0alto)o$=c(PrlQWm;Z29-{lJSR4GVR&>oeBD|aBm4b&D_IA zClC8U<`E0Au>P>|CPZX&>}iBtpJ&&{?U&wL?jGjWG}h&V{pRwa*?eECk2Fr}>QReF z6LXz|9oG1Zas79W?k2rntY6hPA9FDg57y@FI5l=?Z`ItX+f~=B>|yO=n%K6mQPjk5 zzCowzpocnIh7F*d-i&IG^Abcr@@F^}rcJGla>DF!P#nr_zla|380Mu@l^N(LzUB$! zBx9jX4jNa5z7Q$)p|D%$=SDwqCANtR@uPbPvVE^0{v1dxN~0?zzhzNS2o*rg8WPLK z^#N_L=K+g@$(^II28sjGJuSy^H46_64$N_KJ<`eFJ47-#oFSJ_Nt_N`82bJ)ewP^Vh>j>tUiM+w$>nyPa3YV8<&GIL4yTuua%Ho!U-}1agmkrDhx`i z5!5?@FAPU#86eV%c_5=>DfQO{@|>}5V1CHWZ?Y&-(B7)QBZ?1E-cWLkF8JzhvK4Xw zM+1sJ&hwto%$?1Y5eO@?LnEW2%}Q1%Abgd~{wJn=LrF7fqQFCMi&WS$1#? zQi$W@q^~a@~Jl#6Tg0cnlHXD879gJDvldES1asve3(wB z8zw?H;PS;EaQWg769?lj6({9S6-VW76=&s-flmu$#bX6z#cO4Vp5L6h_WW1iX_D;p z-2wmvZ#MaNAozus{wNTv0s1R`3xdDBQ2ql5-iB=Ls#kIVOC4ul`!)j7mqNUMQ+qd z%Ub>gY?A`3cEO-{PL6`>a58Da>7*iUk{nlL8)23N& zd80Mvy$g4uN|0h7t<1EIJ_HB9?@i=$mt~;x$s5>E{m*e|krEY<{-{846c_omPVYcX zc6nTu&aN4q!ppOJx0x;$E@wd(Z^#`x-dVg?k&;GyMqgtLQVL;~EWkrCr8v@d%GwL< zi$;sAS;x%ykr2)s*q6i3^flljOnI};DWDG=i-NDELkY2ch7<+GO*kGoIRz9xrj{rW zCTN4~Jmn%quB)pLX(7D*TeHYVtlHGO`bP3laT;1nG;3ze(fMZd5~NNtA^8zglJ0~O ziB?iwR!318iTP=ErI1XFhvpkwd}uJa2ts>YLliILp>)D(Q)7E)?_*UNDd4n0AfREG zHpr|qp&L#i8B{=a*(2NyG^5(*1;}KmI>bBb+iwZVl36LnuQCRxe4Lmoz*Fcgpj)fQ zd!Ld~Z#0fTiHhjUA*Y675puE;PaT+Lh+d}ThCu_lx!A0Pa(jgo#GM3z;=c$s?AL-| zp7lgCbR#>^n%awz=-!=0djM4vDxAkNSwUc-tIK)MJ%F&B#t$1db07*ueXXvjCr%s5Mc|?#iUcXLs;O6c`q!c0AtJ974e&tmX0d+>jiw$_bnbEFk){7u(f_Eb$I`gU=nHP&OU^rpTPi`Fh>?`~FD+ImNg9J#U+ zjZ(-(XGumKvnz-DJ=bZL@uDz12OqTNAj7ErWx zcbXGzG<~sB{)($k+P0KRKZ}BLdpmn<)A)Hr-^Q&h=f{BU?Z6AP(ydPYxajTE?$^M! zQge1Zv9rQ6;{)-1@$G@{aZ7RE;@Ey%`2!Vm1ARXSfTs*aJq$@s$`ylw=9C3mb?i(p$pDHWYZuH=9fT z?io3UW3yk0Fz^e;<`BL-qyREdul9LQonXqNWOSj}R}qHc5h0SYN*d4moW>sND*-V1 zG{M$L%+?RzV2^7Q5@RgWPT4ZnqFCr)C2W?)C3u3|;OcDI{3*Zo8_-~JriDhHD~KWN ze~IctgXop~88>oT4+24!(Is}o94&1Uf$SAAGTZ(Ezm|nWu~HTlHo}T=u0*KX!5}y* zbVMSMR5HQY>Av4G>!Ul<3kmv5olZ-e6E6x>7K-4SMi0r&xA+Xg>SAO2Isr1=0$xh+ zJYrBF3hE<#haT8PbLiS=j!e0Q(pvd&Mx*i+CaNizK&4^?(CtWEh?iXXk;ER-9r>&@ zE+mB8Y0)U~Vo@519#~DdEr^=>`t9HWvj^w7jhS|nygZqj0D-bJMUN3mNLsTX&2Q|F z;C^7dDt%@C+eQ2Wc9cJL*q8=?v;`kvlB8gx=VnYH;+pr;{QPL9ltqxkpll9NpF z#^*Snmj{M+lQ2xO=6JW273j_-n+5u?*keT9-lfz|gw>9dK0mugFGPP*o^2}OhWFsFV=r^>A4o;) z^!1c*E$f22nLX9#d%n3Y<>zBoJbzSutn=dIXFKY(zFe`(fA`|otY4+qwqx(g)3E+y z{BeK$kX@noboI`Q{rTngpgT-ozW0zr2ul>p6iXG$1xpso7E2e)=Q}fbH1_+FV8-1O zxOMofW%uJI-YjH7rjBx1~=0mxyP!4Dl(ZDYM`*YJI9C>Wl*r zM187c>Up(8#B}$R11fZ2Bwu1&k&hk2WKLI)2ptI{rC;>MfDgn-v59};!zGZ-PI1g^ zWGVQ^yYG9IqQC_?nF6uOg+^_K35Q>SprA-kneD-kNn1pKv=6gyX$+jf6%&Viu)8U_ zgGlW`x&Wo0bJj6Mrw$!m4Ry zOq6zqoF-@qlnD(aplEpQkE$cVC-0A4P^l9?Zj_B=6g*>f?EDQKEJO+&A`+bv?kT%! zd#H!>XN8gx4)DO~Vf3)(p=dSuAP8qRgy+qKB~T!qET}_Z4xL{E10n)!tVO!puAow- zGd>u2)GjcTmiiG&0}C=f=r@rIh~1LGE1O~>SWnLgqtY_bgn%n}5ntu9qqE)iWQ7i| zcsYq#c<2Bd((-Cb)+=Or8w7g_lKuDOk)oBU=pZ`jR_3q~-fKh2FcL~5=&%zsR6xz| z0upZfqtHl)bZ7|LM35OUb*9$|-w(aZe_8>74u{??@dYmJn@kR%cLcGzW59?o-b)Cj z!w?meL`nJ)5=$1GXAKjP;zZ23uHWb~-yqA*o-!?1gK+#YV{XdRu5I2h>;cH0*P2jB zzBRU~_SqqU2XTIsk-6>BvANoi8jPB-0!l5Ql1YQKuG~O%usleWxB^-&sFF>CwyxE{ zW$++q))*rJ7s-{G_d6nyU>DqbrLSYduWt{H#llg{^8gOMQR3fm@E0igqd2GqILqSy z=cUBb?tdQy^qW1JYJ$Fe5CdBO`aK%EmtfK)NT&1(3aadt?X>86G-B~eOlB3h)Z!Cr zXB&8^u_WpVZ>N`>WP(J=Bk{wXkFmX4boJAgpEKA%x0?m-n@%Xs@lkEp}b!DG!B- zI3}P?q)^iS@ow<-`s;4$!0f||3-pNsY+U`yJY`Q=A1^7g)3e1!3Ph7ahd4oLU0Io} z$@F03cU;8P+bQxiMGE{(AH#X!I%7 z06coXt<>+eNclfr5dMFc^N*5a@Bbplznyn}6W*_ysQnh+AF_UrUf5Bi9RN>KHKU?s zscyBtWINNL`e^W-1)G)4fZLEB5dS)A`W_`Q{q#Ghv6uq_zrsj6$_^fvq2N0-1Zs-c zuHG9{r2IJrlYXkv>gk_kKXFg2LCe{oiq@?PgNfIPh9CCK)ImmpLqs_eNFlv;-?eRg zJ>(*z?k3^fiH%epR6QL2@#DQ`j;D~Z&k2Ma=ZFSIAMK)B?zR3sW)rU@-aF!-@O{|S zb2M?NX=njvKRmkYz)Q2q4pRoSHUmGF%i<{KmSuQ{_?fMD%~gEC)@m;77HrjXEwwKS z`B+$QbQq$-9*g#T?8d&QZjPxUbRubdkZQrQzlxSSK%jVa`8K5D`5yR5d{wMydqd;B zPQ7~`lX*u+bBtCHw}pO`{-(9@KI?4=T*%S=+To%*#J<})TDVO|B^}Ps!_UN0OA)2` zn6S9fji|D@taHj|EBNjo_^QypA!RJ4%1o!cIzEYcflt}LH14txSW4W3y|Ggk)5z?t z0`F}p|FvF!vEF}_Ug-cIiGRZ9-?qWOdG`X01^R!3RRH<=N5z(HfX8MfFX<&!uP9H3 zZ;N=Qwr_(~C|=vy7Bm$|iwtwz%+_l2DSjl){LE|i2uwgEjNVdXQ+N$+TaD8URs*G8 zB%`!)3K2W*10v87@?s`POh%0DkE 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

Hea*`xh?&%5sCU-Hga>cQj(WV3MuM=#%2&#Bm7J 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 literal 0 HcmV?d00001 diff --git a/uv.lock b/uv.lock index 812e3df7c..8db53de16 100644 --- a/uv.lock +++ b/uv.lock @@ -1562,7 +1562,7 @@ dependencies = [ { name = "sb3-contrib", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, { name = "scikit-learn" }, { name = "tensorboard" }, - { name = "torch", marker = "python_full_version < '3.13' and sys_platform == 'darwin'" }, + { name = "torch", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, { name = "tqdm" }, { name = "typing-extensions" }, ] @@ -1619,7 +1619,7 @@ requires-dist = [ { name = "sb3-contrib", specifier = ">=2.0.0" }, { name = "scikit-learn", specifier = ">=1.5.1" }, { name = "tensorboard", specifier = ">=2.17.0" }, - { name = "torch", marker = "python_full_version < '3.13' and sys_platform == 'darwin'", specifier = ">=2.7.1,<2.8.0" }, + { name = "torch", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'", specifier = ">=2.7.1,<2.8.0" }, { name = "tqdm", specifier = ">=4.66.0" }, { name = "typing-extensions", specifier = ">=4.1" }, ] From 3346842d2b437739dbcec32532d5bb2c6fe1d9ae Mon Sep 17 00:00:00 2001 From: Liu Keyu Date: Sun, 3 Aug 2025 19:11:03 +0200 Subject: [PATCH 13/57] Fix issue with Python 3.13 --- pyproject.toml | 2 +- uv.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 84b31abd6..7224218bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ dependencies = [ "torch>=2.7.1,<2.8.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch v2.3.0 for macOS x86 since it is not supported anymore. "typing-extensions>=4.1", # for `assert_never` "qiskit-ibm-transpiler>=0.11.1", - "qiskit-ibm-ai-local-transpiler>=0.3.2" + "qiskit-ibm-ai-local-transpiler>=0.3.2; python_version < '3.13'" ] classifiers = [ diff --git a/uv.lock b/uv.lock index 8db53de16..25bf474e6 100644 --- a/uv.lock +++ b/uv.lock @@ -1555,7 +1555,7 @@ dependencies = [ { name = "pytket" }, { name = "pytket-qiskit" }, { name = "qiskit" }, - { name = "qiskit-ibm-ai-local-transpiler" }, + { name = "qiskit-ibm-ai-local-transpiler", marker = "python_full_version < '3.13'" }, { name = "qiskit-ibm-transpiler" }, { name = "rich" }, { name = "sb3-contrib", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, @@ -1613,7 +1613,7 @@ requires-dist = [ { name = "pytket", specifier = ">=1.29.0" }, { name = "pytket-qiskit", specifier = ">=0.71.0" }, { name = "qiskit", specifier = "!=1.3.2" }, - { name = "qiskit-ibm-ai-local-transpiler", specifier = ">=0.3.2" }, + { name = "qiskit-ibm-ai-local-transpiler", marker = "python_full_version < '3.13'", specifier = ">=0.3.2" }, { name = "qiskit-ibm-transpiler", specifier = ">=0.11.1" }, { name = "rich", specifier = ">=12.6.0" }, { name = "sb3-contrib", specifier = ">=2.0.0" }, @@ -2796,10 +2796,10 @@ name = "qiskit-ibm-ai-local-transpiler" version = "0.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jsonschema" }, - { name = "networkx" }, - { name = "qiskit" }, - { name = "qiskit-ibm-runtime" }, + { name = "jsonschema", marker = "python_full_version < '3.13'" }, + { name = "networkx", marker = "python_full_version < '3.13'" }, + { name = "qiskit", marker = "python_full_version < '3.13'" }, + { name = "qiskit-ibm-runtime", marker = "python_full_version < '3.13'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/a2/2b/d4a31925f23a4614587bd256f1a5b46d53f667971243df2bff62ceb1286e/qiskit_ibm_ai_local_transpiler-0.4.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:6b13b0978b0bb906c1175a63b1c2c00070ed7f3ff9c302d9b8a8e104f5a8ec39", size = 97513406, upload-time = "2025-07-02T15:25:39.934Z" }, From 6c67349fb0dab8d99f0c4257982e435cc261f6a7 Mon Sep 17 00:00:00 2001 From: Liu Keyu Date: Sun, 3 Aug 2025 21:08:34 +0200 Subject: [PATCH 14/57] Remove Python 3.13 from noxfile.py due to compatibility issue --- noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index 9e13b37f9..9fe9986e4 100644 --- a/noxfile.py +++ b/noxfile.py @@ -29,7 +29,7 @@ # TODO(denialhaag): Add 3.14 when all dependencies support it # https://github.com/munich-quantum-toolkit/predictor/issues/420 -PYTHON_ALL_VERSIONS = ["3.10", "3.11", "3.12", "3.13"] +PYTHON_ALL_VERSIONS = ["3.10", "3.11", "3.12"] if os.environ.get("CI", None): nox.options.error_on_missing_interpreters = True From 2692b96c191e6a4e03e5cfb25d3101c9f414702d Mon Sep 17 00:00:00 2001 From: Liu Keyu Date: Mon, 4 Aug 2025 16:52:26 +0200 Subject: [PATCH 15/57] Skip minimums session on Windows due to CI slowness --- noxfile.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/noxfile.py b/noxfile.py index 9fe9986e4..8a7e78ddc 100644 --- a/noxfile.py +++ b/noxfile.py @@ -12,6 +12,7 @@ import argparse import os +import platform import shutil from typing import TYPE_CHECKING @@ -82,6 +83,9 @@ def tests(session: nox.Session) -> None: @nox.session(reuse_venv=True, venv_backend="uv", python=PYTHON_ALL_VERSIONS) def minimums(session: nox.Session) -> None: """Test the minimum versions of dependencies.""" + if platform.system() == "Windows": + session.skip("Too slow on Windows — skipping minimums session.") + return _run_tests( session, install_args=["--resolution=lowest-direct"], From f4874e60c77bccce33181451f9603b8df0e1c74b Mon Sep 17 00:00:00 2001 From: Liu Keyu Date: Tue, 5 Aug 2025 15:25:40 +0200 Subject: [PATCH 16/57] Fix bugs Fix bugs Fix bugs Fix bugs --- src/mqt/predictor/rl/actions.py | 74 ++++---------------------- src/mqt/predictor/rl/predictorenv.py | 11 ++-- tests/compilation/test_predictor_rl.py | 6 +-- 3 files changed, 19 insertions(+), 72 deletions(-) diff --git a/src/mqt/predictor/rl/actions.py b/src/mqt/predictor/rl/actions.py index 58609b4e5..dc2e13717 100644 --- a/src/mqt/predictor/rl/actions.py +++ b/src/mqt/predictor/rl/actions.py @@ -50,7 +50,6 @@ from qiskit.transpiler import CouplingMap from qiskit.transpiler.passes import ( ApplyLayout, - BasicSwap, BasisTranslator, Collect2qBlocks, CollectCliffords, @@ -125,6 +124,7 @@ class Action: pass_type: PassType transpile_pass: ( list[qiskit_BasePass | tket_BasePass] + | Callable[..., list[Any]] | Callable[..., list[qiskit_BasePass | tket_BasePass]] | Callable[ ..., @@ -144,7 +144,8 @@ class DeviceDependentAction(Action): """Action that represents a device-specific compilation pass that can be applied to a specific device.""" transpile_pass: ( - Callable[..., list[qiskit_BasePass | tket_BasePass]] + Callable[..., list[Any]] + | Callable[..., list[qiskit_BasePass | tket_BasePass]] | Callable[ ..., Callable[..., tuple[Any, ...] | Circuit], @@ -466,7 +467,7 @@ def get_openqasm_gates() -> list[str]: register_action( DeviceDependentAction( - "SabreLayout+BasicSwap", + "SabreLayout+AIRouting", CompilationOrigin.QISKIT, PassType.MAPPING, stochastic=True, @@ -482,26 +483,19 @@ def get_openqasm_gates() -> list[str]: FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), EnlargeWithAncilla(), ApplyLayout(), - BasicSwap(coupling_map=CouplingMap(device.build_coupling_map())), + SafeAIRouting(coupling_map=device.build_coupling_map(), optimization_level=3, layout_mode="improve"), ], ) ) register_action( DeviceDependentAction( - "SabreLayout+AIRouting", + "AIRouting", CompilationOrigin.QISKIT, PassType.MAPPING, stochastic=True, - transpile_pass=lambda device, max_iteration=(20, 20): [ - SabreLayout( - coupling_map=CouplingMap(device.build_coupling_map()), - skip_routing=True, - layout_trials=max_iteration[0], - swap_trials=max_iteration[1], - max_iterations=4, - seed=None, - ), + transpile_pass=lambda device: [ + TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), EnlargeWithAncilla(), ApplyLayout(), @@ -532,20 +526,6 @@ def get_openqasm_gates() -> list[str]: ) ) -register_action( - DeviceDependentAction( - name="DenseLayout+BasicSwap", - origin=CompilationOrigin.QISKIT, - pass_type=PassType.MAPPING, - transpile_pass=lambda device: [ - DenseLayout(coupling_map=CouplingMap(device.build_coupling_map())), - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - BasicSwap(coupling_map=CouplingMap(device.build_coupling_map())), - ], - ) -) register_action( DeviceDependentAction( @@ -579,45 +559,11 @@ def get_openqasm_gates() -> list[str]: FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), EnlargeWithAncilla(), ApplyLayout(), - SafeAIRouting(coupling_map=device.build_coupling_map(), optimization_level=3, layout_mode="optimize"), + SafeAIRouting(coupling_map=device.build_coupling_map(), optimization_level=3, layout_mode="improve"), ], ) ) -register_action( - DeviceDependentAction( - name="VF2Layout+BasicSwap", - origin=CompilationOrigin.QISKIT, - pass_type=PassType.MAPPING, - transpile_pass=lambda device: [ - VF2Layout( - coupling_map=CouplingMap(device.build_coupling_map()), - target=device, - ), - ConditionalController( - [ - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - ], - condition=lambda property_set: property_set["VF2Layout_stop_reason"] - == VF2LayoutStopReason.SOLUTION_FOUND, - ), - ConditionalController( - [ - TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - ], - # Run if VF2Layout did not find a solution - condition=lambda property_set: property_set["VF2Layout_stop_reason"] - != VF2LayoutStopReason.SOLUTION_FOUND, - ), - BasicSwap(coupling_map=CouplingMap(device.build_coupling_map())), - ], - ) -) register_action( DeviceDependentAction( @@ -691,7 +637,7 @@ def get_openqasm_gates() -> list[str]: condition=lambda property_set: property_set["VF2Layout_stop_reason"] != VF2LayoutStopReason.SOLUTION_FOUND, ), - SafeAIRouting(coupling_map=device.build_coupling_map(), optimization_level=3, layout_mode="optimize"), + SafeAIRouting(coupling_map=device.build_coupling_map(), optimization_level=3, layout_mode="improve"), ], ) ) diff --git a/src/mqt/predictor/rl/predictorenv.py b/src/mqt/predictor/rl/predictorenv.py index 5f1d4603e..14983a0a1 100644 --- a/src/mqt/predictor/rl/predictorenv.py +++ b/src/mqt/predictor/rl/predictorenv.py @@ -188,6 +188,7 @@ def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any RuntimeError: If no valid actions are left. """ self.used_actions.append(str(self.action_set[action].name)) + logger.info(f"Applying: {self.action_set[action].name!s}") altered_qc = self.apply_action(action) if not altered_qc: return ( @@ -377,9 +378,9 @@ def metric_fn(circ: QuantumCircuit) -> float: pm = PassManager(transpile_pass) altered_qc = pm.run(self.state) pm_property_set = dict(pm.property_set) if hasattr(pm, "property_set") else {} - if action_index in (self.actions_mapping_indices + self.actions_final_optimization_indices): - pm_property_set = dict(pm.property_set) - altered_qc = self._handle_qiskit_layout_postprocessing(action, pm_property_set, altered_qc) + if action_index in (self.actions_mapping_indices + self.actions_final_optimization_indices): + pm_property_set = dict(pm.property_set) + altered_qc = self._handle_qiskit_layout_postprocessing(action, pm_property_set, altered_qc) return altered_qc @@ -466,7 +467,7 @@ def _apply_bqskit_action(self, action: Action, action_index: int) -> QuantumCirc def determine_valid_actions_for_state(self) -> list[int]: """Determines and returns the valid actions for the current state.""" - check_nat_gates = GatesInBasis(target=self.device) + check_nat_gates = GatesInBasis(basis_gates=self.device.operation_names) check_nat_gates(self.state) only_nat_gates = check_nat_gates.property_set["all_gates_in_basis"] @@ -477,7 +478,7 @@ def determine_valid_actions_for_state(self) -> list[int]: if not only_nat_gates: # not native gates yet return self.actions_synthesis_indices + self.actions_opt_indices - if mapped and self.layout is not None: # The circuit is correctly mapped. + if mapped and self.layout is not None: # The circuit is correctly mapped return [self.action_terminate_index, *self.actions_opt_indices, *self.actions_final_optimization_indices] # The circuit is not mapped yet # Or the circuit was mapped but some optimization actions change its structure and the circuit is again unmapped diff --git a/tests/compilation/test_predictor_rl.py b/tests/compilation/test_predictor_rl.py index b7001d65b..ac62c2fae 100644 --- a/tests/compilation/test_predictor_rl.py +++ b/tests/compilation/test_predictor_rl.py @@ -37,7 +37,7 @@ def test_predictor_env_reset_from_string() -> None: device = get_device("ibm_eagle_127") predictor = Predictor(figure_of_merit="expected_fidelity", device=device) qasm_path = Path("test.qasm") - qc = get_benchmark("dj", BenchmarkLevel.ALG, 3) + qc = get_benchmark("dj", BenchmarkLevel.INDEP, 3) with qasm_path.open("w", encoding="utf-8") as f: dump(qc, f) assert predictor.env.reset(qc=qasm_path)[0] == create_feature_dict(qc) @@ -69,7 +69,7 @@ def test_qcompile_with_newly_trained_models() -> None: """ figure_of_merit = "expected_fidelity" device = get_device("ibm_falcon_127") - qc = get_benchmark("ghz", BenchmarkLevel.ALG, 3) + qc = get_benchmark("ghz", BenchmarkLevel.INDEP, 3) predictor = Predictor(figure_of_merit=figure_of_merit, device=device) model_name = "model_" + figure_of_merit + "_" + device.description @@ -95,7 +95,7 @@ def test_qcompile_with_newly_trained_models() -> None: def test_qcompile_with_false_input() -> None: """Test the qcompile function with false input.""" - qc = get_benchmark("dj", BenchmarkLevel.ALG, 5) + qc = get_benchmark("dj", BenchmarkLevel.INDEP, 5) with pytest.raises(ValueError, match=re.escape("figure_of_merit must not be None if predictor_singleton is None.")): rl_compile(qc, device=get_device("quantinuum_h2_56"), figure_of_merit=None) with pytest.raises(ValueError, match=re.escape("device must not be None if predictor_singleton is None.")): From 54eec91444622a0d23097c08e7dcf3f14e21ea2e Mon Sep 17 00:00:00 2001 From: Liu Keyu Date: Tue, 5 Aug 2025 17:22:14 +0200 Subject: [PATCH 17/57] Fix bugs --- noxfile.py | 4 + pyproject.toml | 2 +- src/mqt/predictor/ml/predictor.py | 8 +- src/mqt/predictor/rl/actions.py | 1 + src/mqt/predictor/rl/predictorenv.py | 7 +- uv.lock | 448 +++++++++++---------------- 6 files changed, 190 insertions(+), 280 deletions(-) diff --git a/noxfile.py b/noxfile.py index 8a7e78ddc..ee7980b69 100644 --- a/noxfile.py +++ b/noxfile.py @@ -67,7 +67,9 @@ def _run_tests( "test", *install_args, "pytest", + "-v", *pytest_run_args, + "--disable-warnings", *session.posargs, "--cov-config=pyproject.toml", env=env, @@ -137,5 +139,7 @@ def docs(session: nox.Session) -> None: "--frozen", "sphinx-autobuild" if serve else "sphinx-build", *shared_args, + "-v", + "--disable-warnings", env=env, ) diff --git a/pyproject.toml b/pyproject.toml index 742cba154..e161979cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ dependencies = [ "numpy>=1.24; python_version >= '3.11'", "numpy>=1.22", "numpy>=1.22,<2; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict numpy v2 for macOS x86 since it is not supported anymore since torch v2.3.0 - "torch>=2.7.1,<2.8.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch v2.3.0 for macOS x86 since it is not supported anymore. + "torch>=2.7.1,<2.8.0", "typing-extensions>=4.1", # for `assert_never` "qiskit-ibm-transpiler>=0.11.1", "qiskit-ibm-ai-local-transpiler>=0.3.2; python_version < '3.13'" diff --git a/src/mqt/predictor/ml/predictor.py b/src/mqt/predictor/ml/predictor.py index 3f0ec5497..3b844caf2 100644 --- a/src/mqt/predictor/ml/predictor.py +++ b/src/mqt/predictor/ml/predictor.py @@ -205,8 +205,7 @@ def compile_training_circuits( self, path_uncompiled_circuits: Path | None = None, path_compiled_circuits: Path | None = None, - timeout: int = 600, - num_workers: int = -1, + timeout: int = 6000, ) -> None: """Compiles all circuits in the given directory with the given timeout and saves them in the given directory. @@ -227,7 +226,7 @@ def compile_training_circuits( with zipfile.ZipFile(str(path_zip), "r") as zip_ref: zip_ref.extractall(path_uncompiled_circuits) - Parallel(n_jobs=num_workers, verbose=100)( + Parallel(n_jobs=1, verbose=100)( delayed(self._compile_all_circuits_devicewise)( device, timeout, path_uncompiled_circuits, path_compiled_circuits, logger.level ) @@ -239,7 +238,6 @@ def generate_training_data( path_uncompiled_circuits: Path | None = None, path_compiled_circuits: Path | None = None, path_training_data: Path | None = None, - num_workers: int = -1, ) -> None: """Creates and saves training data from all generated training samples. @@ -267,7 +265,7 @@ def generate_training_data( names_list = [] scores_list = [] - results = Parallel(n_jobs=num_workers, verbose=100)( + results = Parallel(n_jobs=1, verbose=100)( delayed(self._generate_training_sample)( filename.name, path_uncompiled_circuits, diff --git a/src/mqt/predictor/rl/actions.py b/src/mqt/predictor/rl/actions.py index dc2e13717..7d0b856d3 100644 --- a/src/mqt/predictor/rl/actions.py +++ b/src/mqt/predictor/rl/actions.py @@ -495,6 +495,7 @@ def get_openqasm_gates() -> list[str]: PassType.MAPPING, stochastic=True, transpile_pass=lambda device: [ + ### Requires a initial layout, but "optimize" mode overwrites it TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), EnlargeWithAncilla(), diff --git a/src/mqt/predictor/rl/predictorenv.py b/src/mqt/predictor/rl/predictorenv.py index 14983a0a1..a3cbae20b 100644 --- a/src/mqt/predictor/rl/predictorenv.py +++ b/src/mqt/predictor/rl/predictorenv.py @@ -341,7 +341,6 @@ def _apply_qiskit_action(self, action: Action, action_index: int) -> QuantumCirc def metric_fn(circ: QuantumCircuit) -> float: return float(circ.count_ops().get("swap", 0)) - # for stochastic actions, pass the layout/routing trials parameter max_iteration = self.max_iter if "Sabre" in action.name and "AIRouting" not in action.name: # Internal trials for Sabre @@ -359,6 +358,10 @@ def metric_fn(circ: QuantumCircuit) -> float: max_iteration=max_iteration, metric_fn=metric_fn, ) + else: + msg = f"Unknown stochastic action: {action.name}" + raise ValueError(msg) + else: if action.name in ["QiskitO3", "Opt2qBlocks"] and isinstance(action, DeviceDependentAction): passes = action.transpile_pass( @@ -378,8 +381,8 @@ def metric_fn(circ: QuantumCircuit) -> float: pm = PassManager(transpile_pass) altered_qc = pm.run(self.state) pm_property_set = dict(pm.property_set) if hasattr(pm, "property_set") else {} + if action_index in (self.actions_mapping_indices + self.actions_final_optimization_indices): - pm_property_set = dict(pm.property_set) altered_qc = self._handle_qiskit_layout_postprocessing(action, pm_property_set, altered_qc) return altered_qc diff --git a/uv.lock b/uv.lock index 25bf474e6..d9e9e8d01 100644 --- a/uv.lock +++ b/uv.lock @@ -2,11 +2,9 @@ version = 1 revision = 3 requires-python = ">=3.10" resolution-markers = [ - "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", + "python_full_version >= '3.13'", "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", + "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", "python_full_version < '3.11' and 'x86_64' in platform_machine and sys_platform == 'darwin'", @@ -406,11 +404,9 @@ name = "contourpy" version = "1.3.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", + "python_full_version >= '3.13'", "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", + "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", ] @@ -495,87 +491,87 @@ wheels = [ [[package]] name = "coverage" -version = "7.10.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/87/0e/66dbd4c6a7f0758a8d18044c048779ba21fb94856e1edcf764bd5403e710/coverage-7.10.1.tar.gz", hash = "sha256:ae2b4856f29ddfe827106794f3589949a57da6f0d38ab01e24ec35107979ba57", size = 819938, upload-time = "2025-07-27T14:13:39.045Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/e7/0f4e35a15361337529df88151bddcac8e8f6d6fd01da94a4b7588901c2fe/coverage-7.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1c86eb388bbd609d15560e7cc0eb936c102b6f43f31cf3e58b4fd9afe28e1372", size = 214627, upload-time = "2025-07-27T14:11:01.211Z" }, - { url = "https://files.pythonhosted.org/packages/e0/fd/17872e762c408362072c936dbf3ca28c67c609a1f5af434b1355edcb7e12/coverage-7.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b4ba0f488c1bdb6bd9ba81da50715a372119785458831c73428a8566253b86b", size = 215015, upload-time = "2025-07-27T14:11:03.988Z" }, - { url = "https://files.pythonhosted.org/packages/54/50/c9d445ba38ee5f685f03876c0f8223469e2e46c5d3599594dca972b470c8/coverage-7.10.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083442ecf97d434f0cb3b3e3676584443182653da08b42e965326ba12d6b5f2a", size = 241995, upload-time = "2025-07-27T14:11:05.983Z" }, - { url = "https://files.pythonhosted.org/packages/cc/83/4ae6e0f60376af33de543368394d21b9ac370dc86434039062ef171eebf8/coverage-7.10.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c1a40c486041006b135759f59189385da7c66d239bad897c994e18fd1d0c128f", size = 243253, upload-time = "2025-07-27T14:11:07.424Z" }, - { url = "https://files.pythonhosted.org/packages/49/90/17a4d9ac7171be364ce8c0bb2b6da05e618ebfe1f11238ad4f26c99f5467/coverage-7.10.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3beb76e20b28046989300c4ea81bf690df84ee98ade4dc0bbbf774a28eb98440", size = 245110, upload-time = "2025-07-27T14:11:09.152Z" }, - { url = "https://files.pythonhosted.org/packages/e1/f7/edc3f485d536ed417f3af2b4969582bcb5fab456241721825fa09354161e/coverage-7.10.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc265a7945e8d08da28999ad02b544963f813a00f3ed0a7a0ce4165fd77629f8", size = 243056, upload-time = "2025-07-27T14:11:10.586Z" }, - { url = "https://files.pythonhosted.org/packages/58/2c/c4c316a57718556b8d0cc8304437741c31b54a62934e7c8c551a7915c2f4/coverage-7.10.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:47c91f32ba4ac46f1e224a7ebf3f98b4b24335bad16137737fe71a5961a0665c", size = 241731, upload-time = "2025-07-27T14:11:12.145Z" }, - { url = "https://files.pythonhosted.org/packages/f7/93/c78e144c6f086043d0d7d9237c5b880e71ac672ed2712c6f8cca5544481f/coverage-7.10.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1a108dd78ed185020f66f131c60078f3fae3f61646c28c8bb4edd3fa121fc7fc", size = 242023, upload-time = "2025-07-27T14:11:13.573Z" }, - { url = "https://files.pythonhosted.org/packages/8f/e1/34e8505ca81fc144a612e1cc79fadd4a78f42e96723875f4e9f1f470437e/coverage-7.10.1-cp310-cp310-win32.whl", hash = "sha256:7092cc82382e634075cc0255b0b69cb7cada7c1f249070ace6a95cb0f13548ef", size = 217130, upload-time = "2025-07-27T14:11:15.11Z" }, - { url = "https://files.pythonhosted.org/packages/75/2b/82adfce6edffc13d804aee414e64c0469044234af9296e75f6d13f92f6a2/coverage-7.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:ac0c5bba938879c2fc0bc6c1b47311b5ad1212a9dcb8b40fe2c8110239b7faed", size = 218015, upload-time = "2025-07-27T14:11:16.836Z" }, - { url = "https://files.pythonhosted.org/packages/20/8e/ef088112bd1b26e2aa931ee186992b3e42c222c64f33e381432c8ee52aae/coverage-7.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b45e2f9d5b0b5c1977cb4feb5f594be60eb121106f8900348e29331f553a726f", size = 214747, upload-time = "2025-07-27T14:11:18.217Z" }, - { url = "https://files.pythonhosted.org/packages/2d/76/a1e46f3c6e0897758eb43af88bb3c763cb005f4950769f7b553e22aa5f89/coverage-7.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a7a4d74cb0f5e3334f9aa26af7016ddb94fb4bfa11b4a573d8e98ecba8c34f1", size = 215128, upload-time = "2025-07-27T14:11:19.706Z" }, - { url = "https://files.pythonhosted.org/packages/78/4d/903bafb371a8c887826ecc30d3977b65dfad0e1e66aa61b7e173de0828b0/coverage-7.10.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d4b0aab55ad60ead26159ff12b538c85fbab731a5e3411c642b46c3525863437", size = 245140, upload-time = "2025-07-27T14:11:21.261Z" }, - { url = "https://files.pythonhosted.org/packages/55/f1/1f8f09536f38394a8698dd08a0e9608a512eacee1d3b771e2d06397f77bf/coverage-7.10.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dcc93488c9ebd229be6ee1f0d9aad90da97b33ad7e2912f5495804d78a3cd6b7", size = 246977, upload-time = "2025-07-27T14:11:23.15Z" }, - { url = "https://files.pythonhosted.org/packages/57/cc/ed6bbc5a3bdb36ae1bca900bbbfdcb23b260ef2767a7b2dab38b92f61adf/coverage-7.10.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa309df995d020f3438407081b51ff527171cca6772b33cf8f85344b8b4b8770", size = 249140, upload-time = "2025-07-27T14:11:24.743Z" }, - { url = "https://files.pythonhosted.org/packages/10/f5/e881ade2d8e291b60fa1d93d6d736107e940144d80d21a0d4999cff3642f/coverage-7.10.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cfb8b9d8855c8608f9747602a48ab525b1d320ecf0113994f6df23160af68262", size = 246869, upload-time = "2025-07-27T14:11:26.156Z" }, - { url = "https://files.pythonhosted.org/packages/53/b9/6a5665cb8996e3cd341d184bb11e2a8edf01d8dadcf44eb1e742186cf243/coverage-7.10.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:320d86da829b012982b414c7cdda65f5d358d63f764e0e4e54b33097646f39a3", size = 244899, upload-time = "2025-07-27T14:11:27.622Z" }, - { url = "https://files.pythonhosted.org/packages/27/11/24156776709c4e25bf8a33d6bb2ece9a9067186ddac19990f6560a7f8130/coverage-7.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dc60ddd483c556590da1d9482a4518292eec36dd0e1e8496966759a1f282bcd0", size = 245507, upload-time = "2025-07-27T14:11:29.544Z" }, - { url = "https://files.pythonhosted.org/packages/43/db/a6f0340b7d6802a79928659c9a32bc778ea420e87a61b568d68ac36d45a8/coverage-7.10.1-cp311-cp311-win32.whl", hash = "sha256:4fcfe294f95b44e4754da5b58be750396f2b1caca8f9a0e78588e3ef85f8b8be", size = 217167, upload-time = "2025-07-27T14:11:31.349Z" }, - { url = "https://files.pythonhosted.org/packages/f5/6f/1990eb4fd05cea4cfabdf1d587a997ac5f9a8bee883443a1d519a2a848c9/coverage-7.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:efa23166da3fe2915f8ab452dde40319ac84dc357f635737174a08dbd912980c", size = 218054, upload-time = "2025-07-27T14:11:33.202Z" }, - { url = "https://files.pythonhosted.org/packages/b4/4d/5e061d6020251b20e9b4303bb0b7900083a1a384ec4e5db326336c1c4abd/coverage-7.10.1-cp311-cp311-win_arm64.whl", hash = "sha256:d12b15a8c3759e2bb580ffa423ae54be4f184cf23beffcbd641f4fe6e1584293", size = 216483, upload-time = "2025-07-27T14:11:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/a5/3f/b051feeb292400bd22d071fdf933b3ad389a8cef5c80c7866ed0c7414b9e/coverage-7.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6b7dc7f0a75a7eaa4584e5843c873c561b12602439d2351ee28c7478186c4da4", size = 214934, upload-time = "2025-07-27T14:11:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e4/a61b27d5c4c2d185bdfb0bfe9d15ab4ac4f0073032665544507429ae60eb/coverage-7.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:607f82389f0ecafc565813aa201a5cade04f897603750028dd660fb01797265e", size = 215173, upload-time = "2025-07-27T14:11:38.005Z" }, - { url = "https://files.pythonhosted.org/packages/8a/01/40a6ee05b60d02d0bc53742ad4966e39dccd450aafb48c535a64390a3552/coverage-7.10.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f7da31a1ba31f1c1d4d5044b7c5813878adae1f3af8f4052d679cc493c7328f4", size = 246190, upload-time = "2025-07-27T14:11:39.887Z" }, - { url = "https://files.pythonhosted.org/packages/11/ef/a28d64d702eb583c377255047281305dc5a5cfbfb0ee36e721f78255adb6/coverage-7.10.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51fe93f3fe4f5d8483d51072fddc65e717a175490804e1942c975a68e04bf97a", size = 248618, upload-time = "2025-07-27T14:11:41.841Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ad/73d018bb0c8317725370c79d69b5c6e0257df84a3b9b781bda27a438a3be/coverage-7.10.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e59d00830da411a1feef6ac828b90bbf74c9b6a8e87b8ca37964925bba76dbe", size = 250081, upload-time = "2025-07-27T14:11:43.705Z" }, - { url = "https://files.pythonhosted.org/packages/2d/dd/496adfbbb4503ebca5d5b2de8bed5ec00c0a76558ffc5b834fd404166bc9/coverage-7.10.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:924563481c27941229cb4e16eefacc35da28563e80791b3ddc5597b062a5c386", size = 247990, upload-time = "2025-07-27T14:11:45.244Z" }, - { url = "https://files.pythonhosted.org/packages/18/3c/a9331a7982facfac0d98a4a87b36ae666fe4257d0f00961a3a9ef73e015d/coverage-7.10.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ca79146ee421b259f8131f153102220b84d1a5e6fb9c8aed13b3badfd1796de6", size = 246191, upload-time = "2025-07-27T14:11:47.093Z" }, - { url = "https://files.pythonhosted.org/packages/62/0c/75345895013b83f7afe92ec595e15a9a525ede17491677ceebb2ba5c3d85/coverage-7.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2b225a06d227f23f386fdc0eab471506d9e644be699424814acc7d114595495f", size = 247400, upload-time = "2025-07-27T14:11:48.643Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a9/98b268cfc5619ef9df1d5d34fee408ecb1542d9fd43d467e5c2f28668cd4/coverage-7.10.1-cp312-cp312-win32.whl", hash = "sha256:5ba9a8770effec5baaaab1567be916c87d8eea0c9ad11253722d86874d885eca", size = 217338, upload-time = "2025-07-27T14:11:50.258Z" }, - { url = "https://files.pythonhosted.org/packages/fe/31/22a5440e4d1451f253c5cd69fdcead65e92ef08cd4ec237b8756dc0b20a7/coverage-7.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:9eb245a8d8dd0ad73b4062135a251ec55086fbc2c42e0eb9725a9b553fba18a3", size = 218125, upload-time = "2025-07-27T14:11:52.034Z" }, - { url = "https://files.pythonhosted.org/packages/d6/2b/40d9f0ce7ee839f08a43c5bfc9d05cec28aaa7c9785837247f96cbe490b9/coverage-7.10.1-cp312-cp312-win_arm64.whl", hash = "sha256:7718060dd4434cc719803a5e526838a5d66e4efa5dc46d2b25c21965a9c6fcc4", size = 216523, upload-time = "2025-07-27T14:11:53.965Z" }, - { url = "https://files.pythonhosted.org/packages/ef/72/135ff5fef09b1ffe78dbe6fcf1e16b2e564cd35faeacf3d63d60d887f12d/coverage-7.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ebb08d0867c5a25dffa4823377292a0ffd7aaafb218b5d4e2e106378b1061e39", size = 214960, upload-time = "2025-07-27T14:11:55.959Z" }, - { url = "https://files.pythonhosted.org/packages/b1/aa/73a5d1a6fc08ca709a8177825616aa95ee6bf34d522517c2595484a3e6c9/coverage-7.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f32a95a83c2e17422f67af922a89422cd24c6fa94041f083dd0bb4f6057d0bc7", size = 215220, upload-time = "2025-07-27T14:11:57.899Z" }, - { url = "https://files.pythonhosted.org/packages/8d/40/3124fdd45ed3772a42fc73ca41c091699b38a2c3bd4f9cb564162378e8b6/coverage-7.10.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c4c746d11c8aba4b9f58ca8bfc6fbfd0da4efe7960ae5540d1a1b13655ee8892", size = 245772, upload-time = "2025-07-27T14:12:00.422Z" }, - { url = "https://files.pythonhosted.org/packages/42/62/a77b254822efa8c12ad59e8039f2bc3df56dc162ebda55e1943e35ba31a5/coverage-7.10.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7f39edd52c23e5c7ed94e0e4bf088928029edf86ef10b95413e5ea670c5e92d7", size = 248116, upload-time = "2025-07-27T14:12:03.099Z" }, - { url = "https://files.pythonhosted.org/packages/1d/01/8101f062f472a3a6205b458d18ef0444a63ae5d36a8a5ed5dd0f6167f4db/coverage-7.10.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab6e19b684981d0cd968906e293d5628e89faacb27977c92f3600b201926b994", size = 249554, upload-time = "2025-07-27T14:12:04.668Z" }, - { url = "https://files.pythonhosted.org/packages/8f/7b/e51bc61573e71ff7275a4f167aecbd16cb010aefdf54bcd8b0a133391263/coverage-7.10.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5121d8cf0eacb16133501455d216bb5f99899ae2f52d394fe45d59229e6611d0", size = 247766, upload-time = "2025-07-27T14:12:06.234Z" }, - { url = "https://files.pythonhosted.org/packages/4b/71/1c96d66a51d4204a9d6d12df53c4071d87e110941a2a1fe94693192262f5/coverage-7.10.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df1c742ca6f46a6f6cbcaef9ac694dc2cb1260d30a6a2f5c68c5f5bcfee1cfd7", size = 245735, upload-time = "2025-07-27T14:12:08.305Z" }, - { url = "https://files.pythonhosted.org/packages/13/d5/efbc2ac4d35ae2f22ef6df2ca084c60e13bd9378be68655e3268c80349ab/coverage-7.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:40f9a38676f9c073bf4b9194707aa1eb97dca0e22cc3766d83879d72500132c7", size = 247118, upload-time = "2025-07-27T14:12:09.903Z" }, - { url = "https://files.pythonhosted.org/packages/d1/22/073848352bec28ca65f2b6816b892fcf9a31abbef07b868487ad15dd55f1/coverage-7.10.1-cp313-cp313-win32.whl", hash = "sha256:2348631f049e884839553b9974f0821d39241c6ffb01a418efce434f7eba0fe7", size = 217381, upload-time = "2025-07-27T14:12:11.535Z" }, - { url = "https://files.pythonhosted.org/packages/b7/df/df6a0ff33b042f000089bd11b6bb034bab073e2ab64a56e78ed882cba55d/coverage-7.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:4072b31361b0d6d23f750c524f694e1a417c1220a30d3ef02741eed28520c48e", size = 218152, upload-time = "2025-07-27T14:12:13.182Z" }, - { url = "https://files.pythonhosted.org/packages/30/e3/5085ca849a40ed6b47cdb8f65471c2f754e19390b5a12fa8abd25cbfaa8f/coverage-7.10.1-cp313-cp313-win_arm64.whl", hash = "sha256:3e31dfb8271937cab9425f19259b1b1d1f556790e98eb266009e7a61d337b6d4", size = 216559, upload-time = "2025-07-27T14:12:14.807Z" }, - { url = "https://files.pythonhosted.org/packages/cc/93/58714efbfdeb547909feaabe1d67b2bdd59f0597060271b9c548d5efb529/coverage-7.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1c4f679c6b573a5257af6012f167a45be4c749c9925fd44d5178fd641ad8bf72", size = 215677, upload-time = "2025-07-27T14:12:16.68Z" }, - { url = "https://files.pythonhosted.org/packages/c0/0c/18eaa5897e7e8cb3f8c45e563e23e8a85686b4585e29d53cacb6bc9cb340/coverage-7.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:871ebe8143da284bd77b84a9136200bd638be253618765d21a1fce71006d94af", size = 215899, upload-time = "2025-07-27T14:12:18.758Z" }, - { url = "https://files.pythonhosted.org/packages/84/c1/9d1affacc3c75b5a184c140377701bbf14fc94619367f07a269cd9e4fed6/coverage-7.10.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:998c4751dabf7d29b30594af416e4bf5091f11f92a8d88eb1512c7ba136d1ed7", size = 257140, upload-time = "2025-07-27T14:12:20.357Z" }, - { url = "https://files.pythonhosted.org/packages/3d/0f/339bc6b8fa968c346df346068cca1f24bdea2ddfa93bb3dc2e7749730962/coverage-7.10.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:780f750a25e7749d0af6b3631759c2c14f45de209f3faaa2398312d1c7a22759", size = 259005, upload-time = "2025-07-27T14:12:22.007Z" }, - { url = "https://files.pythonhosted.org/packages/c8/22/89390864b92ea7c909079939b71baba7e5b42a76bf327c1d615bd829ba57/coverage-7.10.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:590bdba9445df4763bdbebc928d8182f094c1f3947a8dc0fc82ef014dbdd8324", size = 261143, upload-time = "2025-07-27T14:12:23.746Z" }, - { url = "https://files.pythonhosted.org/packages/2c/56/3d04d89017c0c41c7a71bd69b29699d919b6bbf2649b8b2091240b97dd6a/coverage-7.10.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b2df80cb6a2af86d300e70acb82e9b79dab2c1e6971e44b78dbfc1a1e736b53", size = 258735, upload-time = "2025-07-27T14:12:25.73Z" }, - { url = "https://files.pythonhosted.org/packages/cb/40/312252c8afa5ca781063a09d931f4b9409dc91526cd0b5a2b84143ffafa2/coverage-7.10.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d6a558c2725bfb6337bf57c1cd366c13798bfd3bfc9e3dd1f4a6f6fc95a4605f", size = 256871, upload-time = "2025-07-27T14:12:27.767Z" }, - { url = "https://files.pythonhosted.org/packages/1f/2b/564947d5dede068215aaddb9e05638aeac079685101462218229ddea9113/coverage-7.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e6150d167f32f2a54690e572e0a4c90296fb000a18e9b26ab81a6489e24e78dd", size = 257692, upload-time = "2025-07-27T14:12:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/93/1b/c8a867ade85cb26d802aea2209b9c2c80613b9c122baa8c8ecea6799648f/coverage-7.10.1-cp313-cp313t-win32.whl", hash = "sha256:d946a0c067aa88be4a593aad1236493313bafaa27e2a2080bfe88db827972f3c", size = 218059, upload-time = "2025-07-27T14:12:31.076Z" }, - { url = "https://files.pythonhosted.org/packages/a1/fe/cd4ab40570ae83a516bf5e754ea4388aeedd48e660e40c50b7713ed4f930/coverage-7.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e37c72eaccdd5ed1130c67a92ad38f5b2af66eeff7b0abe29534225db2ef7b18", size = 219150, upload-time = "2025-07-27T14:12:32.746Z" }, - { url = "https://files.pythonhosted.org/packages/8d/16/6e5ed5854be6d70d0c39e9cb9dd2449f2c8c34455534c32c1a508c7dbdb5/coverage-7.10.1-cp313-cp313t-win_arm64.whl", hash = "sha256:89ec0ffc215c590c732918c95cd02b55c7d0f569d76b90bb1a5e78aa340618e4", size = 217014, upload-time = "2025-07-27T14:12:34.406Z" }, - { url = "https://files.pythonhosted.org/packages/54/8e/6d0bfe9c3d7121cf936c5f8b03e8c3da1484fb801703127dba20fb8bd3c7/coverage-7.10.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:166d89c57e877e93d8827dac32cedae6b0277ca684c6511497311249f35a280c", size = 214951, upload-time = "2025-07-27T14:12:36.069Z" }, - { url = "https://files.pythonhosted.org/packages/f2/29/e3e51a8c653cf2174c60532aafeb5065cea0911403fa144c9abe39790308/coverage-7.10.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bed4a2341b33cd1a7d9ffc47df4a78ee61d3416d43b4adc9e18b7d266650b83e", size = 215229, upload-time = "2025-07-27T14:12:37.759Z" }, - { url = "https://files.pythonhosted.org/packages/e0/59/3c972080b2fa18b6c4510201f6d4dc87159d450627d062cd9ad051134062/coverage-7.10.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ddca1e4f5f4c67980533df01430184c19b5359900e080248bbf4ed6789584d8b", size = 245738, upload-time = "2025-07-27T14:12:39.453Z" }, - { url = "https://files.pythonhosted.org/packages/2e/04/fc0d99d3f809452654e958e1788454f6e27b34e43f8f8598191c8ad13537/coverage-7.10.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:37b69226001d8b7de7126cad7366b0778d36777e4d788c66991455ba817c5b41", size = 248045, upload-time = "2025-07-27T14:12:41.387Z" }, - { url = "https://files.pythonhosted.org/packages/5e/2e/afcbf599e77e0dfbf4c97197747250d13d397d27e185b93987d9eaac053d/coverage-7.10.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2f22102197bcb1722691296f9e589f02b616f874e54a209284dd7b9294b0b7f", size = 249666, upload-time = "2025-07-27T14:12:43.056Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ae/bc47f7f8ecb7a06cbae2bf86a6fa20f479dd902bc80f57cff7730438059d/coverage-7.10.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1e0c768b0f9ac5839dac5cf88992a4bb459e488ee8a1f8489af4cb33b1af00f1", size = 247692, upload-time = "2025-07-27T14:12:44.83Z" }, - { url = "https://files.pythonhosted.org/packages/b6/26/cbfa3092d31ccba8ba7647e4d25753263e818b4547eba446b113d7d1efdf/coverage-7.10.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:991196702d5e0b120a8fef2664e1b9c333a81d36d5f6bcf6b225c0cf8b0451a2", size = 245536, upload-time = "2025-07-27T14:12:46.527Z" }, - { url = "https://files.pythonhosted.org/packages/56/77/9c68e92500e6a1c83d024a70eadcc9a173f21aadd73c4675fe64c9c43fdf/coverage-7.10.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae8e59e5f4fd85d6ad34c2bb9d74037b5b11be072b8b7e9986beb11f957573d4", size = 246954, upload-time = "2025-07-27T14:12:49.279Z" }, - { url = "https://files.pythonhosted.org/packages/7f/a5/ba96671c5a669672aacd9877a5987c8551501b602827b4e84256da2a30a7/coverage-7.10.1-cp314-cp314-win32.whl", hash = "sha256:042125c89cf74a074984002e165d61fe0e31c7bd40ebb4bbebf07939b5924613", size = 217616, upload-time = "2025-07-27T14:12:51.214Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3c/e1e1eb95fc1585f15a410208c4795db24a948e04d9bde818fe4eb893bc85/coverage-7.10.1-cp314-cp314-win_amd64.whl", hash = "sha256:a22c3bfe09f7a530e2c94c87ff7af867259c91bef87ed2089cd69b783af7b84e", size = 218412, upload-time = "2025-07-27T14:12:53.429Z" }, - { url = "https://files.pythonhosted.org/packages/b0/85/7e1e5be2cb966cba95566ba702b13a572ca744fbb3779df9888213762d67/coverage-7.10.1-cp314-cp314-win_arm64.whl", hash = "sha256:ee6be07af68d9c4fca4027c70cea0c31a0f1bc9cb464ff3c84a1f916bf82e652", size = 216776, upload-time = "2025-07-27T14:12:55.482Z" }, - { url = "https://files.pythonhosted.org/packages/62/0f/5bb8f29923141cca8560fe2217679caf4e0db643872c1945ac7d8748c2a7/coverage-7.10.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d24fb3c0c8ff0d517c5ca5de7cf3994a4cd559cde0315201511dbfa7ab528894", size = 215698, upload-time = "2025-07-27T14:12:57.225Z" }, - { url = "https://files.pythonhosted.org/packages/80/29/547038ffa4e8e4d9e82f7dfc6d152f75fcdc0af146913f0ba03875211f03/coverage-7.10.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1217a54cfd79be20512a67ca81c7da3f2163f51bbfd188aab91054df012154f5", size = 215902, upload-time = "2025-07-27T14:12:59.071Z" }, - { url = "https://files.pythonhosted.org/packages/e1/8a/7aaa8fbfaed900147987a424e112af2e7790e1ac9cd92601e5bd4e1ba60a/coverage-7.10.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:51f30da7a52c009667e02f125737229d7d8044ad84b79db454308033a7808ab2", size = 257230, upload-time = "2025-07-27T14:13:01.248Z" }, - { url = "https://files.pythonhosted.org/packages/e5/1d/c252b5ffac44294e23a0d79dd5acf51749b39795ccc898faeabf7bee903f/coverage-7.10.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ed3718c757c82d920f1c94089066225ca2ad7f00bb904cb72b1c39ebdd906ccb", size = 259194, upload-time = "2025-07-27T14:13:03.247Z" }, - { url = "https://files.pythonhosted.org/packages/16/ad/6c8d9f83d08f3bac2e7507534d0c48d1a4f52c18e6f94919d364edbdfa8f/coverage-7.10.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc452481e124a819ced0c25412ea2e144269ef2f2534b862d9f6a9dae4bda17b", size = 261316, upload-time = "2025-07-27T14:13:04.957Z" }, - { url = "https://files.pythonhosted.org/packages/d6/4e/f9bbf3a36c061e2e0e0f78369c006d66416561a33d2bee63345aee8ee65e/coverage-7.10.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9d6f494c307e5cb9b1e052ec1a471060f1dea092c8116e642e7a23e79d9388ea", size = 258794, upload-time = "2025-07-27T14:13:06.715Z" }, - { url = "https://files.pythonhosted.org/packages/87/82/e600bbe78eb2cb0541751d03cef9314bcd0897e8eea156219c39b685f869/coverage-7.10.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fc0e46d86905ddd16b85991f1f4919028092b4e511689bbdaff0876bd8aab3dd", size = 256869, upload-time = "2025-07-27T14:13:08.933Z" }, - { url = "https://files.pythonhosted.org/packages/ce/5d/2fc9a9236c5268f68ac011d97cd3a5ad16cc420535369bedbda659fdd9b7/coverage-7.10.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80b9ccd82e30038b61fc9a692a8dc4801504689651b281ed9109f10cc9fe8b4d", size = 257765, upload-time = "2025-07-27T14:13:10.778Z" }, - { url = "https://files.pythonhosted.org/packages/8a/05/b4e00b2bd48a2dc8e1c7d2aea7455f40af2e36484ab2ef06deb85883e9fe/coverage-7.10.1-cp314-cp314t-win32.whl", hash = "sha256:e58991a2b213417285ec866d3cd32db17a6a88061a985dbb7e8e8f13af429c47", size = 218420, upload-time = "2025-07-27T14:13:12.882Z" }, - { url = "https://files.pythonhosted.org/packages/77/fb/d21d05f33ea27ece327422240e69654b5932b0b29e7fbc40fbab3cf199bf/coverage-7.10.1-cp314-cp314t-win_amd64.whl", hash = "sha256:e88dd71e4ecbc49d9d57d064117462c43f40a21a1383507811cf834a4a620651", size = 219536, upload-time = "2025-07-27T14:13:14.718Z" }, - { url = "https://files.pythonhosted.org/packages/a6/68/7fea94b141281ed8be3d1d5c4319a97f2befc3e487ce33657fc64db2c45e/coverage-7.10.1-cp314-cp314t-win_arm64.whl", hash = "sha256:1aadfb06a30c62c2eb82322171fe1f7c288c80ca4156d46af0ca039052814bab", size = 217190, upload-time = "2025-07-27T14:13:16.85Z" }, - { url = "https://files.pythonhosted.org/packages/0f/64/922899cff2c0fd3496be83fa8b81230f5a8d82a2ad30f98370b133c2c83b/coverage-7.10.1-py3-none-any.whl", hash = "sha256:fa2a258aa6bf188eb9a8948f7102a83da7c430a0dce918dbd8b60ef8fcb772d7", size = 206597, upload-time = "2025-07-27T14:13:37.221Z" }, +version = "7.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/76/17780846fc7aade1e66712e1e27dd28faa0a5d987a1f433610974959eaa8/coverage-7.10.2.tar.gz", hash = "sha256:5d6e6d84e6dd31a8ded64759626627247d676a23c1b892e1326f7c55c8d61055", size = 820754, upload-time = "2025-08-04T00:35:17.511Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/5f/5ce748ab3f142593698aff5f8a0cf020775aa4e24b9d8748b5a56b64d3f8/coverage-7.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:79f0283ab5e6499fd5fe382ca3d62afa40fb50ff227676a3125d18af70eabf65", size = 215003, upload-time = "2025-08-04T00:33:02.977Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ed/507088561217b000109552139802fa99c33c16ad19999c687b601b3790d0/coverage-7.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4545e906f595ee8ab8e03e21be20d899bfc06647925bc5b224ad7e8c40e08b8", size = 215391, upload-time = "2025-08-04T00:33:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/79/1b/0f496259fe137c4c5e1e8eaff496fb95af88b71700f5e57725a4ddbe742b/coverage-7.10.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ae385e1d58fbc6a9b1c315e5510ac52281e271478b45f92ca9b5ad42cf39643f", size = 242367, upload-time = "2025-08-04T00:33:07.189Z" }, + { url = "https://files.pythonhosted.org/packages/b9/8e/5a8835fb0122a2e2a108bf3527931693c4625fdc4d953950a480b9625852/coverage-7.10.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f0cbe5f7dd19f3a32bac2251b95d51c3b89621ac88a2648096ce40f9a5aa1e7", size = 243627, upload-time = "2025-08-04T00:33:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/c3/96/6a528429c2e0e8d85261764d0cd42e51a429510509bcc14676ee5d1bb212/coverage-7.10.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd17f427f041f6b116dc90b4049c6f3e1230524407d00daa2d8c7915037b5947", size = 245485, upload-time = "2025-08-04T00:33:10.29Z" }, + { url = "https://files.pythonhosted.org/packages/bf/82/1fba935c4d02c33275aca319deabf1f22c0f95f2c0000bf7c5f276d6f7b4/coverage-7.10.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7f10ca4cde7b466405cce0a0e9971a13eb22e57a5ecc8b5f93a81090cc9c7eb9", size = 243429, upload-time = "2025-08-04T00:33:11.909Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a8/c8dc0a57a729fc93be33ab78f187a8f52d455fa8f79bfb379fe23b45868d/coverage-7.10.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3b990df23dd51dccce26d18fb09fd85a77ebe46368f387b0ffba7a74e470b31b", size = 242104, upload-time = "2025-08-04T00:33:13.467Z" }, + { url = "https://files.pythonhosted.org/packages/b9/6f/0b7da1682e2557caeed299a00897b42afde99a241a01eba0197eb982b90f/coverage-7.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc3902584d25c7eef57fb38f440aa849a26a3a9f761a029a72b69acfca4e31f8", size = 242397, upload-time = "2025-08-04T00:33:14.682Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e4/54dc833dadccd519c04a28852f39a37e522bad35d70cfe038817cdb8f168/coverage-7.10.2-cp310-cp310-win32.whl", hash = "sha256:9dd37e9ac00d5eb72f38ed93e3cdf2280b1dbda3bb9b48c6941805f265ad8d87", size = 217502, upload-time = "2025-08-04T00:33:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/c3/e7/2f78159c4c127549172f427dff15b02176329327bf6a6a1fcf1f603b5456/coverage-7.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:99d16f15cb5baf0729354c5bd3080ae53847a4072b9ba1e10957522fb290417f", size = 218388, upload-time = "2025-08-04T00:33:17.4Z" }, + { url = "https://files.pythonhosted.org/packages/6e/53/0125a6fc0af4f2687b4e08b0fb332cd0d5e60f3ca849e7456f995d022656/coverage-7.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c3b210d79925a476dfc8d74c7d53224888421edebf3a611f3adae923e212b27", size = 215119, upload-time = "2025-08-04T00:33:19.101Z" }, + { url = "https://files.pythonhosted.org/packages/0e/2e/960d9871de9152dbc9ff950913c6a6e9cf2eb4cc80d5bc8f93029f9f2f9f/coverage-7.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf67d1787cd317c3f8b2e4c6ed1ae93497be7e30605a0d32237ac37a37a8a322", size = 215511, upload-time = "2025-08-04T00:33:20.32Z" }, + { url = "https://files.pythonhosted.org/packages/3f/34/68509e44995b9cad806d81b76c22bc5181f3535bca7cd9c15791bfd8951e/coverage-7.10.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:069b779d03d458602bc0e27189876e7d8bdf6b24ac0f12900de22dd2154e6ad7", size = 245513, upload-time = "2025-08-04T00:33:21.896Z" }, + { url = "https://files.pythonhosted.org/packages/ef/d4/9b12f357413248ce40804b0f58030b55a25b28a5c02db95fb0aa50c5d62c/coverage-7.10.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c2de4cb80b9990e71c62c2d3e9f3ec71b804b1f9ca4784ec7e74127e0f42468", size = 247350, upload-time = "2025-08-04T00:33:23.917Z" }, + { url = "https://files.pythonhosted.org/packages/b6/40/257945eda1f72098e4a3c350b1d68fdc5d7d032684a0aeb6c2391153ecf4/coverage-7.10.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75bf7ab2374a7eb107602f1e07310cda164016cd60968abf817b7a0b5703e288", size = 249516, upload-time = "2025-08-04T00:33:25.5Z" }, + { url = "https://files.pythonhosted.org/packages/ff/55/8987f852ece378cecbf39a367f3f7ec53351e39a9151b130af3a3045b83f/coverage-7.10.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3f37516458ec1550815134937f73d6d15b434059cd10f64678a2068f65c62406", size = 247241, upload-time = "2025-08-04T00:33:26.767Z" }, + { url = "https://files.pythonhosted.org/packages/df/ae/da397de7a42a18cea6062ed9c3b72c50b39e0b9e7b2893d7172d3333a9a1/coverage-7.10.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:de3c6271c482c250d3303fb5c6bdb8ca025fff20a67245e1425df04dc990ece9", size = 245274, upload-time = "2025-08-04T00:33:28.494Z" }, + { url = "https://files.pythonhosted.org/packages/4e/64/7baa895eb55ec0e1ec35b988687ecd5d4475ababb0d7ae5ca3874dd90ee7/coverage-7.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:98a838101321ac3089c9bb1d4bfa967e8afed58021fda72d7880dc1997f20ae1", size = 245882, upload-time = "2025-08-04T00:33:30.048Z" }, + { url = "https://files.pythonhosted.org/packages/24/6c/1fd76a0bd09ae75220ae9775a8290416d726f0e5ba26ea72346747161240/coverage-7.10.2-cp311-cp311-win32.whl", hash = "sha256:f2a79145a531a0e42df32d37be5af069b4a914845b6f686590739b786f2f7bce", size = 217541, upload-time = "2025-08-04T00:33:31.376Z" }, + { url = "https://files.pythonhosted.org/packages/5f/2d/8c18fb7a6e74c79fd4661e82535bc8c68aee12f46c204eabf910b097ccc9/coverage-7.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:e4f5f1320f8ee0d7cfa421ceb257bef9d39fd614dd3ddcfcacd284d4824ed2c2", size = 218426, upload-time = "2025-08-04T00:33:32.976Z" }, + { url = "https://files.pythonhosted.org/packages/da/40/425bb35e4ff7c7af177edf5dffd4154bc2a677b27696afe6526d75c77fec/coverage-7.10.2-cp311-cp311-win_arm64.whl", hash = "sha256:d8f2d83118f25328552c728b8e91babf93217db259ca5c2cd4dd4220b8926293", size = 217116, upload-time = "2025-08-04T00:33:34.302Z" }, + { url = "https://files.pythonhosted.org/packages/4e/1e/2c752bdbbf6f1199c59b1a10557fbb6fb3dc96b3c0077b30bd41a5922c1f/coverage-7.10.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:890ad3a26da9ec7bf69255b9371800e2a8da9bc223ae5d86daeb940b42247c83", size = 215311, upload-time = "2025-08-04T00:33:35.524Z" }, + { url = "https://files.pythonhosted.org/packages/68/6a/84277d73a2cafb96e24be81b7169372ba7ff28768ebbf98e55c85a491b0f/coverage-7.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38fd1ccfca7838c031d7a7874d4353e2f1b98eb5d2a80a2fe5732d542ae25e9c", size = 215550, upload-time = "2025-08-04T00:33:37.109Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e7/5358b73b46ac76f56cc2de921eeabd44fabd0b7ff82ea4f6b8c159c4d5dc/coverage-7.10.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:76c1ffaaf4f6f0f6e8e9ca06f24bb6454a7a5d4ced97a1bc466f0d6baf4bd518", size = 246564, upload-time = "2025-08-04T00:33:38.33Z" }, + { url = "https://files.pythonhosted.org/packages/7c/0e/b0c901dd411cb7fc0cfcb28ef0dc6f3049030f616bfe9fc4143aecd95901/coverage-7.10.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86da8a3a84b79ead5c7d0e960c34f580bc3b231bb546627773a3f53c532c2f21", size = 248993, upload-time = "2025-08-04T00:33:39.555Z" }, + { url = "https://files.pythonhosted.org/packages/0e/4e/a876db272072a9e0df93f311e187ccdd5f39a190c6d1c1f0b6e255a0d08e/coverage-7.10.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99cef9731c8a39801830a604cc53c93c9e57ea8b44953d26589499eded9576e0", size = 250454, upload-time = "2025-08-04T00:33:41.023Z" }, + { url = "https://files.pythonhosted.org/packages/64/d6/1222dc69f8dd1be208d55708a9f4a450ad582bf4fa05320617fea1eaa6d8/coverage-7.10.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea58b112f2966a8b91eb13f5d3b1f8bb43c180d624cd3283fb33b1cedcc2dd75", size = 248365, upload-time = "2025-08-04T00:33:42.376Z" }, + { url = "https://files.pythonhosted.org/packages/62/e3/40fd71151064fc315c922dd9a35e15b30616f00146db1d6a0b590553a75a/coverage-7.10.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:20f405188d28da9522b7232e51154e1b884fc18d0b3a10f382d54784715bbe01", size = 246562, upload-time = "2025-08-04T00:33:43.663Z" }, + { url = "https://files.pythonhosted.org/packages/fc/14/8aa93ddcd6623ddaef5d8966268ac9545b145bce4fe7b1738fd1c3f0d957/coverage-7.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:64586ce42bbe0da4d9f76f97235c545d1abb9b25985a8791857690f96e23dc3b", size = 247772, upload-time = "2025-08-04T00:33:45.068Z" }, + { url = "https://files.pythonhosted.org/packages/07/4e/dcb1c01490623c61e2f2ea85cb185fa6a524265bb70eeb897d3c193efeb9/coverage-7.10.2-cp312-cp312-win32.whl", hash = "sha256:bc2e69b795d97ee6d126e7e22e78a509438b46be6ff44f4dccbb5230f550d340", size = 217710, upload-time = "2025-08-04T00:33:46.378Z" }, + { url = "https://files.pythonhosted.org/packages/79/16/e8aab4162b5f80ad2e5e1f54b1826e2053aa2f4db508b864af647f00c239/coverage-7.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:adda2268b8cf0d11f160fad3743b4dfe9813cd6ecf02c1d6397eceaa5b45b388", size = 218499, upload-time = "2025-08-04T00:33:48.048Z" }, + { url = "https://files.pythonhosted.org/packages/06/7f/c112ec766e8f1131ce8ce26254be028772757b2d1e63e4f6a4b0ad9a526c/coverage-7.10.2-cp312-cp312-win_arm64.whl", hash = "sha256:164429decd0d6b39a0582eaa30c67bf482612c0330572343042d0ed9e7f15c20", size = 217154, upload-time = "2025-08-04T00:33:49.299Z" }, + { url = "https://files.pythonhosted.org/packages/8d/04/9b7a741557f93c0ed791b854d27aa8d9fe0b0ce7bb7c52ca1b0f2619cb74/coverage-7.10.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:aca7b5645afa688de6d4f8e89d30c577f62956fefb1bad021490d63173874186", size = 215337, upload-time = "2025-08-04T00:33:50.61Z" }, + { url = "https://files.pythonhosted.org/packages/02/a4/8d1088cd644750c94bc305d3cf56082b4cdf7fb854a25abb23359e74892f/coverage-7.10.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:96e5921342574a14303dfdb73de0019e1ac041c863743c8fe1aa6c2b4a257226", size = 215596, upload-time = "2025-08-04T00:33:52.33Z" }, + { url = "https://files.pythonhosted.org/packages/01/2f/643a8d73343f70e162d8177a3972b76e306b96239026bc0c12cfde4f7c7a/coverage-7.10.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:11333094c1bff621aa811b67ed794865cbcaa99984dedea4bd9cf780ad64ecba", size = 246145, upload-time = "2025-08-04T00:33:53.641Z" }, + { url = "https://files.pythonhosted.org/packages/1f/4a/722098d1848db4072cda71b69ede1e55730d9063bf868375264d0d302bc9/coverage-7.10.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6eb586fa7d2aee8d65d5ae1dd71414020b2f447435c57ee8de8abea0a77d5074", size = 248492, upload-time = "2025-08-04T00:33:55.366Z" }, + { url = "https://files.pythonhosted.org/packages/3f/b0/8a6d7f326f6e3e6ed398cde27f9055e860a1e858317001835c521673fb60/coverage-7.10.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d358f259d8019d4ef25d8c5b78aca4c7af25e28bd4231312911c22a0e824a57", size = 249927, upload-time = "2025-08-04T00:33:57.042Z" }, + { url = "https://files.pythonhosted.org/packages/bb/21/1aaadd3197b54d1e61794475379ecd0f68d8fc5c2ebd352964dc6f698a3d/coverage-7.10.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5250bda76e30382e0a2dcd68d961afcab92c3a7613606e6269855c6979a1b0bb", size = 248138, upload-time = "2025-08-04T00:33:58.329Z" }, + { url = "https://files.pythonhosted.org/packages/48/65/be75bafb2bdd22fd8bf9bf63cd5873b91bb26ec0d68f02d4b8b09c02decb/coverage-7.10.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a91e027d66eff214d88d9afbe528e21c9ef1ecdf4956c46e366c50f3094696d0", size = 246111, upload-time = "2025-08-04T00:33:59.899Z" }, + { url = "https://files.pythonhosted.org/packages/5e/30/a4f0c5e249c3cc60e6c6f30d8368e372f2d380eda40e0434c192ac27ccf5/coverage-7.10.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:228946da741558904e2c03ce870ba5efd9cd6e48cbc004d9a27abee08100a15a", size = 247493, upload-time = "2025-08-04T00:34:01.619Z" }, + { url = "https://files.pythonhosted.org/packages/85/99/f09b9493e44a75cf99ca834394c12f8cb70da6c1711ee296534f97b52729/coverage-7.10.2-cp313-cp313-win32.whl", hash = "sha256:95e23987b52d02e7c413bf2d6dc6288bd5721beb518052109a13bfdc62c8033b", size = 217756, upload-time = "2025-08-04T00:34:03.277Z" }, + { url = "https://files.pythonhosted.org/packages/2d/bb/cbcb09103be330c7d26ff0ab05c4a8861dd2e254656fdbd3eb7600af4336/coverage-7.10.2-cp313-cp313-win_amd64.whl", hash = "sha256:f35481d42c6d146d48ec92d4e239c23f97b53a3f1fbd2302e7c64336f28641fe", size = 218526, upload-time = "2025-08-04T00:34:04.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/8f/8bfb4e0bca52c00ab680767c0dd8cfd928a2a72d69897d9b2d5d8b5f63f5/coverage-7.10.2-cp313-cp313-win_arm64.whl", hash = "sha256:65b451949cb789c346f9f9002441fc934d8ccedcc9ec09daabc2139ad13853f7", size = 217176, upload-time = "2025-08-04T00:34:05.973Z" }, + { url = "https://files.pythonhosted.org/packages/1e/25/d458ba0bf16a8204a88d74dbb7ec5520f29937ffcbbc12371f931c11efd2/coverage-7.10.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8415918856a3e7d57a4e0ad94651b761317de459eb74d34cc1bb51aad80f07e", size = 216058, upload-time = "2025-08-04T00:34:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1c/af4dfd2d7244dc7610fed6d59d57a23ea165681cd764445dc58d71ed01a6/coverage-7.10.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f287a25a8ca53901c613498e4a40885b19361a2fe8fbfdbb7f8ef2cad2a23f03", size = 216273, upload-time = "2025-08-04T00:34:09.073Z" }, + { url = "https://files.pythonhosted.org/packages/8e/67/ec5095d4035c6e16368226fa9cb15f77f891194c7e3725aeefd08e7a3e5a/coverage-7.10.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:75cc1a3f8c88c69bf16a871dab1fe5a7303fdb1e9f285f204b60f1ee539b8fc0", size = 257513, upload-time = "2025-08-04T00:34:10.403Z" }, + { url = "https://files.pythonhosted.org/packages/1c/47/be5550b57a3a8ba797de4236b0fd31031f88397b2afc84ab3c2d4cf265f6/coverage-7.10.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca07fa78cc9d26bc8c4740de1abd3489cf9c47cc06d9a8ab3d552ff5101af4c0", size = 259377, upload-time = "2025-08-04T00:34:12.138Z" }, + { url = "https://files.pythonhosted.org/packages/37/50/b12a4da1382e672305c2d17cd3029dc16b8a0470de2191dbf26b91431378/coverage-7.10.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2e117e64c26300032755d4520cd769f2623cde1a1d1c3515b05a3b8add0ade1", size = 261516, upload-time = "2025-08-04T00:34:13.608Z" }, + { url = "https://files.pythonhosted.org/packages/db/41/4d3296dbd33dd8da178171540ca3391af7c0184c0870fd4d4574ac290290/coverage-7.10.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:daaf98009977f577b71f8800208f4d40d4dcf5c2db53d4d822787cdc198d76e1", size = 259110, upload-time = "2025-08-04T00:34:15.089Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f1/b409959ecbc0cec0e61e65683b22bacaa4a3b11512f834e16dd8ffbc37db/coverage-7.10.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ea8d8fe546c528535c761ba424410bbeb36ba8a0f24be653e94b70c93fd8a8ca", size = 257248, upload-time = "2025-08-04T00:34:16.501Z" }, + { url = "https://files.pythonhosted.org/packages/48/ab/7076dc1c240412e9267d36ec93e9e299d7659f6a5c1e958f87e998b0fb6d/coverage-7.10.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fe024d40ac31eb8d5aae70215b41dafa264676caa4404ae155f77d2fa95c37bb", size = 258063, upload-time = "2025-08-04T00:34:18.338Z" }, + { url = "https://files.pythonhosted.org/packages/1e/77/f6b51a0288f8f5f7dcc7c89abdd22cf514f3bc5151284f5cd628917f8e10/coverage-7.10.2-cp313-cp313t-win32.whl", hash = "sha256:8f34b09f68bdadec122ffad312154eda965ade433559cc1eadd96cca3de5c824", size = 218433, upload-time = "2025-08-04T00:34:19.71Z" }, + { url = "https://files.pythonhosted.org/packages/7b/6d/547a86493e25270ce8481543e77f3a0aa3aa872c1374246b7b76273d66eb/coverage-7.10.2-cp313-cp313t-win_amd64.whl", hash = "sha256:71d40b3ac0f26fa9ffa6ee16219a714fed5c6ec197cdcd2018904ab5e75bcfa3", size = 219523, upload-time = "2025-08-04T00:34:21.171Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d5/3c711e38eaf9ab587edc9bed232c0298aed84e751a9f54aaa556ceaf7da6/coverage-7.10.2-cp313-cp313t-win_arm64.whl", hash = "sha256:abb57fdd38bf6f7dcc66b38dafb7af7c5fdc31ac6029ce373a6f7f5331d6f60f", size = 217739, upload-time = "2025-08-04T00:34:22.514Z" }, + { url = "https://files.pythonhosted.org/packages/71/53/83bafa669bb9d06d4c8c6a055d8d05677216f9480c4698fb183ba7ec5e47/coverage-7.10.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a3e853cc04987c85ec410905667eed4bf08b1d84d80dfab2684bb250ac8da4f6", size = 215328, upload-time = "2025-08-04T00:34:23.991Z" }, + { url = "https://files.pythonhosted.org/packages/1d/6c/30827a9c5a48a813e865fbaf91e2db25cce990bd223a022650ef2293fe11/coverage-7.10.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0100b19f230df72c90fdb36db59d3f39232391e8d89616a7de30f677da4f532b", size = 215608, upload-time = "2025-08-04T00:34:25.437Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a0/c92d85948056ddc397b72a3d79d36d9579c53cb25393ed3c40db7d33b193/coverage-7.10.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9c1cd71483ea78331bdfadb8dcec4f4edfb73c7002c1206d8e0af6797853f5be", size = 246111, upload-time = "2025-08-04T00:34:26.857Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/d695cf86b2559aadd072c91720a7844be4fb82cb4a3b642a2c6ce075692d/coverage-7.10.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9f75dbf4899e29a37d74f48342f29279391668ef625fdac6d2f67363518056a1", size = 248419, upload-time = "2025-08-04T00:34:28.726Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0a/03206aec4a05986e039418c038470d874045f6e00426b0c3879adc1f9251/coverage-7.10.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7df481e7508de1c38b9b8043da48d94931aefa3e32b47dd20277e4978ed5b95", size = 250038, upload-time = "2025-08-04T00:34:30.061Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9b/b3bd6bd52118c12bc4cf319f5baba65009c9beea84e665b6b9f03fa3f180/coverage-7.10.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:835f39e618099325e7612b3406f57af30ab0a0af350490eff6421e2e5f608e46", size = 248066, upload-time = "2025-08-04T00:34:31.53Z" }, + { url = "https://files.pythonhosted.org/packages/80/cc/bfa92e261d3e055c851a073e87ba6a3bff12a1f7134233e48a8f7d855875/coverage-7.10.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:12e52b5aa00aa720097d6947d2eb9e404e7c1101ad775f9661ba165ed0a28303", size = 245909, upload-time = "2025-08-04T00:34:32.943Z" }, + { url = "https://files.pythonhosted.org/packages/12/80/c8df15db4847710c72084164f615ae900af1ec380dce7f74a5678ccdf5e1/coverage-7.10.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:718044729bf1fe3e9eb9f31b52e44ddae07e434ec050c8c628bf5adc56fe4bdd", size = 247329, upload-time = "2025-08-04T00:34:34.388Z" }, + { url = "https://files.pythonhosted.org/packages/04/6f/cb66e1f7124d5dd9ced69f889f02931419cb448125e44a89a13f4e036124/coverage-7.10.2-cp314-cp314-win32.whl", hash = "sha256:f256173b48cc68486299d510a3e729a96e62c889703807482dbf56946befb5c8", size = 218007, upload-time = "2025-08-04T00:34:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e1/3d4be307278ce32c1b9d95cc02ee60d54ddab784036101d053ec9e4fe7f5/coverage-7.10.2-cp314-cp314-win_amd64.whl", hash = "sha256:2e980e4179f33d9b65ac4acb86c9c0dde904098853f27f289766657ed16e07b3", size = 218802, upload-time = "2025-08-04T00:34:37.35Z" }, + { url = "https://files.pythonhosted.org/packages/ec/66/1e43bbeb66c55a5a5efec70f1c153cf90cfc7f1662ab4ebe2d844de9122c/coverage-7.10.2-cp314-cp314-win_arm64.whl", hash = "sha256:14fb5b6641ab5b3c4161572579f0f2ea8834f9d3af2f7dd8fbaecd58ef9175cc", size = 217397, upload-time = "2025-08-04T00:34:39.15Z" }, + { url = "https://files.pythonhosted.org/packages/81/01/ae29c129217f6110dc694a217475b8aecbb1b075d8073401f868c825fa99/coverage-7.10.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e96649ac34a3d0e6491e82a2af71098e43be2874b619547c3282fc11d3840a4b", size = 216068, upload-time = "2025-08-04T00:34:40.648Z" }, + { url = "https://files.pythonhosted.org/packages/a2/50/6e9221d4139f357258f36dfa1d8cac4ec56d9d5acf5fdcc909bb016954d7/coverage-7.10.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1a2e934e9da26341d342d30bfe91422bbfdb3f1f069ec87f19b2909d10d8dcc4", size = 216285, upload-time = "2025-08-04T00:34:42.441Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ec/89d1d0c0ece0d296b4588e0ef4df185200456d42a47f1141335f482c2fc5/coverage-7.10.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:651015dcd5fd9b5a51ca79ece60d353cacc5beaf304db750407b29c89f72fe2b", size = 257603, upload-time = "2025-08-04T00:34:43.899Z" }, + { url = "https://files.pythonhosted.org/packages/82/06/c830af66734671c778fc49d35b58339e8f0687fbd2ae285c3f96c94da092/coverage-7.10.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81bf6a32212f9f66da03d63ecb9cd9bd48e662050a937db7199dbf47d19831de", size = 259568, upload-time = "2025-08-04T00:34:45.519Z" }, + { url = "https://files.pythonhosted.org/packages/60/57/f280dd6f1c556ecc744fbf39e835c33d3ae987d040d64d61c6f821e87829/coverage-7.10.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d800705f6951f75a905ea6feb03fff8f3ea3468b81e7563373ddc29aa3e5d1ca", size = 261691, upload-time = "2025-08-04T00:34:47.019Z" }, + { url = "https://files.pythonhosted.org/packages/54/2b/c63a0acbd19d99ec32326164c23df3a4e18984fb86e902afdd66ff7b3d83/coverage-7.10.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:248b5394718e10d067354448dc406d651709c6765669679311170da18e0e9af8", size = 259166, upload-time = "2025-08-04T00:34:48.792Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c5/cd2997dcfcbf0683634da9df52d3967bc1f1741c1475dd0e4722012ba9ef/coverage-7.10.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5c61675a922b569137cf943770d7ad3edd0202d992ce53ac328c5ff68213ccf4", size = 257241, upload-time = "2025-08-04T00:34:51.038Z" }, + { url = "https://files.pythonhosted.org/packages/16/26/c9e30f82fdad8d47aee90af4978b18c88fa74369ae0f0ba0dbf08cee3a80/coverage-7.10.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:52d708b5fd65589461381fa442d9905f5903d76c086c6a4108e8e9efdca7a7ed", size = 258139, upload-time = "2025-08-04T00:34:52.533Z" }, + { url = "https://files.pythonhosted.org/packages/c9/99/bdb7bd00bebcd3dedfb895fa9af8e46b91422993e4a37ac634a5f1113790/coverage-7.10.2-cp314-cp314t-win32.whl", hash = "sha256:916369b3b914186b2c5e5ad2f7264b02cff5df96cdd7cdad65dccd39aa5fd9f0", size = 218809, upload-time = "2025-08-04T00:34:54.075Z" }, + { url = "https://files.pythonhosted.org/packages/eb/5e/56a7852e38a04d1520dda4dfbfbf74a3d6dec932c20526968f7444763567/coverage-7.10.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5b9d538e8e04916a5df63052d698b30c74eb0174f2ca9cd942c981f274a18eaf", size = 219926, upload-time = "2025-08-04T00:34:55.643Z" }, + { url = "https://files.pythonhosted.org/packages/e0/12/7fbe6b9c52bb9d627e9556f9f2edfdbe88b315e084cdecc9afead0c3b36a/coverage-7.10.2-cp314-cp314t-win_arm64.whl", hash = "sha256:04c74f9ef1f925456a9fd23a7eef1103126186d0500ef9a0acb0bd2514bdc7cc", size = 217925, upload-time = "2025-08-04T00:34:57.564Z" }, + { url = "https://files.pythonhosted.org/packages/18/d8/9b768ac73a8ac2d10c080af23937212434a958c8d2a1c84e89b450237942/coverage-7.10.2-py3-none-any.whl", hash = "sha256:95db3750dd2e6e93d99fa2498f3a1580581e49c494bddccc6f85c5c21604921f", size = 206973, upload-time = "2025-08-04T00:35:15.918Z" }, ] [package.optional-dependencies] @@ -585,49 +581,49 @@ toml = [ [[package]] name = "cryptography" -version = "45.0.5" +version = "45.0.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/1e/49527ac611af559665f71cbb8f92b332b5ec9c6fbc4e88b0f8e92f5e85df/cryptography-45.0.5.tar.gz", hash = "sha256:72e76caa004ab63accdf26023fccd1d087f6d90ec6048ff33ad0445abf7f605a", size = 744903, upload-time = "2025-07-02T13:06:25.941Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/fb/09e28bc0c46d2c547085e60897fea96310574c70fb21cd58a730a45f3403/cryptography-45.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:101ee65078f6dd3e5a028d4f19c07ffa4dd22cce6a20eaa160f8b5219911e7d8", size = 7043092, upload-time = "2025-07-02T13:05:01.514Z" }, - { url = "https://files.pythonhosted.org/packages/b1/05/2194432935e29b91fb649f6149c1a4f9e6d3d9fc880919f4ad1bcc22641e/cryptography-45.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a264aae5f7fbb089dbc01e0242d3b67dffe3e6292e1f5182122bdf58e65215d", size = 4205926, upload-time = "2025-07-02T13:05:04.741Z" }, - { url = "https://files.pythonhosted.org/packages/07/8b/9ef5da82350175e32de245646b1884fc01124f53eb31164c77f95a08d682/cryptography-45.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e74d30ec9c7cb2f404af331d5b4099a9b322a8a6b25c4632755c8757345baac5", size = 4429235, upload-time = "2025-07-02T13:05:07.084Z" }, - { url = "https://files.pythonhosted.org/packages/7c/e1/c809f398adde1994ee53438912192d92a1d0fc0f2d7582659d9ef4c28b0c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3af26738f2db354aafe492fb3869e955b12b2ef2e16908c8b9cb928128d42c57", size = 4209785, upload-time = "2025-07-02T13:05:09.321Z" }, - { url = "https://files.pythonhosted.org/packages/d0/8b/07eb6bd5acff58406c5e806eff34a124936f41a4fb52909ffa4d00815f8c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e6c00130ed423201c5bc5544c23359141660b07999ad82e34e7bb8f882bb78e0", size = 3893050, upload-time = "2025-07-02T13:05:11.069Z" }, - { url = "https://files.pythonhosted.org/packages/ec/ef/3333295ed58d900a13c92806b67e62f27876845a9a908c939f040887cca9/cryptography-45.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:dd420e577921c8c2d31289536c386aaa30140b473835e97f83bc71ea9d2baf2d", size = 4457379, upload-time = "2025-07-02T13:05:13.32Z" }, - { url = "https://files.pythonhosted.org/packages/d9/9d/44080674dee514dbb82b21d6fa5d1055368f208304e2ab1828d85c9de8f4/cryptography-45.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d05a38884db2ba215218745f0781775806bde4f32e07b135348355fe8e4991d9", size = 4209355, upload-time = "2025-07-02T13:05:15.017Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d8/0749f7d39f53f8258e5c18a93131919ac465ee1f9dccaf1b3f420235e0b5/cryptography-45.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad0caded895a00261a5b4aa9af828baede54638754b51955a0ac75576b831b27", size = 4456087, upload-time = "2025-07-02T13:05:16.945Z" }, - { url = "https://files.pythonhosted.org/packages/09/d7/92acac187387bf08902b0bf0699816f08553927bdd6ba3654da0010289b4/cryptography-45.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9024beb59aca9d31d36fcdc1604dd9bbeed0a55bface9f1908df19178e2f116e", size = 4332873, upload-time = "2025-07-02T13:05:18.743Z" }, - { url = "https://files.pythonhosted.org/packages/03/c2/840e0710da5106a7c3d4153c7215b2736151bba60bf4491bdb421df5056d/cryptography-45.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:91098f02ca81579c85f66df8a588c78f331ca19089763d733e34ad359f474174", size = 4564651, upload-time = "2025-07-02T13:05:21.382Z" }, - { url = "https://files.pythonhosted.org/packages/2e/92/cc723dd6d71e9747a887b94eb3827825c6c24b9e6ce2bb33b847d31d5eaa/cryptography-45.0.5-cp311-abi3-win32.whl", hash = "sha256:926c3ea71a6043921050eaa639137e13dbe7b4ab25800932a8498364fc1abec9", size = 2929050, upload-time = "2025-07-02T13:05:23.39Z" }, - { url = "https://files.pythonhosted.org/packages/1f/10/197da38a5911a48dd5389c043de4aec4b3c94cb836299b01253940788d78/cryptography-45.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:b85980d1e345fe769cfc57c57db2b59cff5464ee0c045d52c0df087e926fbe63", size = 3403224, upload-time = "2025-07-02T13:05:25.202Z" }, - { url = "https://files.pythonhosted.org/packages/fe/2b/160ce8c2765e7a481ce57d55eba1546148583e7b6f85514472b1d151711d/cryptography-45.0.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3562c2f23c612f2e4a6964a61d942f891d29ee320edb62ff48ffb99f3de9ae8", size = 7017143, upload-time = "2025-07-02T13:05:27.229Z" }, - { url = "https://files.pythonhosted.org/packages/c2/e7/2187be2f871c0221a81f55ee3105d3cf3e273c0a0853651d7011eada0d7e/cryptography-45.0.5-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3fcfbefc4a7f332dece7272a88e410f611e79458fab97b5efe14e54fe476f4fd", size = 4197780, upload-time = "2025-07-02T13:05:29.299Z" }, - { url = "https://files.pythonhosted.org/packages/b9/cf/84210c447c06104e6be9122661159ad4ce7a8190011669afceeaea150524/cryptography-45.0.5-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:460f8c39ba66af7db0545a8c6f2eabcbc5a5528fc1cf6c3fa9a1e44cec33385e", size = 4420091, upload-time = "2025-07-02T13:05:31.221Z" }, - { url = "https://files.pythonhosted.org/packages/3e/6a/cb8b5c8bb82fafffa23aeff8d3a39822593cee6e2f16c5ca5c2ecca344f7/cryptography-45.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9b4cf6318915dccfe218e69bbec417fdd7c7185aa7aab139a2c0beb7468c89f0", size = 4198711, upload-time = "2025-07-02T13:05:33.062Z" }, - { url = "https://files.pythonhosted.org/packages/04/f7/36d2d69df69c94cbb2473871926daf0f01ad8e00fe3986ac3c1e8c4ca4b3/cryptography-45.0.5-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2089cc8f70a6e454601525e5bf2779e665d7865af002a5dec8d14e561002e135", size = 3883299, upload-time = "2025-07-02T13:05:34.94Z" }, - { url = "https://files.pythonhosted.org/packages/82/c7/f0ea40f016de72f81288e9fe8d1f6748036cb5ba6118774317a3ffc6022d/cryptography-45.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0027d566d65a38497bc37e0dd7c2f8ceda73597d2ac9ba93810204f56f52ebc7", size = 4450558, upload-time = "2025-07-02T13:05:37.288Z" }, - { url = "https://files.pythonhosted.org/packages/06/ae/94b504dc1a3cdf642d710407c62e86296f7da9e66f27ab12a1ee6fdf005b/cryptography-45.0.5-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:be97d3a19c16a9be00edf79dca949c8fa7eff621763666a145f9f9535a5d7f42", size = 4198020, upload-time = "2025-07-02T13:05:39.102Z" }, - { url = "https://files.pythonhosted.org/packages/05/2b/aaf0adb845d5dabb43480f18f7ca72e94f92c280aa983ddbd0bcd6ecd037/cryptography-45.0.5-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:7760c1c2e1a7084153a0f68fab76e754083b126a47d0117c9ed15e69e2103492", size = 4449759, upload-time = "2025-07-02T13:05:41.398Z" }, - { url = "https://files.pythonhosted.org/packages/91/e4/f17e02066de63e0100a3a01b56f8f1016973a1d67551beaf585157a86b3f/cryptography-45.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6ff8728d8d890b3dda5765276d1bc6fb099252915a2cd3aff960c4c195745dd0", size = 4319991, upload-time = "2025-07-02T13:05:43.64Z" }, - { url = "https://files.pythonhosted.org/packages/f2/2e/e2dbd629481b499b14516eed933f3276eb3239f7cee2dcfa4ee6b44d4711/cryptography-45.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a", size = 4554189, upload-time = "2025-07-02T13:05:46.045Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ea/a78a0c38f4c8736287b71c2ea3799d173d5ce778c7d6e3c163a95a05ad2a/cryptography-45.0.5-cp37-abi3-win32.whl", hash = "sha256:1e1da5accc0c750056c556a93c3e9cb828970206c68867712ca5805e46dc806f", size = 2911769, upload-time = "2025-07-02T13:05:48.329Z" }, - { url = "https://files.pythonhosted.org/packages/79/b3/28ac139109d9005ad3f6b6f8976ffede6706a6478e21c889ce36c840918e/cryptography-45.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97", size = 3390016, upload-time = "2025-07-02T13:05:50.811Z" }, - { url = "https://files.pythonhosted.org/packages/f8/8b/34394337abe4566848a2bd49b26bcd4b07fd466afd3e8cce4cb79a390869/cryptography-45.0.5-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:206210d03c1193f4e1ff681d22885181d47efa1ab3018766a7b32a7b3d6e6afd", size = 3575762, upload-time = "2025-07-02T13:05:53.166Z" }, - { url = "https://files.pythonhosted.org/packages/8b/5d/a19441c1e89afb0f173ac13178606ca6fab0d3bd3ebc29e9ed1318b507fc/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c648025b6840fe62e57107e0a25f604db740e728bd67da4f6f060f03017d5097", size = 4140906, upload-time = "2025-07-02T13:05:55.914Z" }, - { url = "https://files.pythonhosted.org/packages/4b/db/daceb259982a3c2da4e619f45b5bfdec0e922a23de213b2636e78ef0919b/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b8fa8b0a35a9982a3c60ec79905ba5bb090fc0b9addcfd3dc2dd04267e45f25e", size = 4374411, upload-time = "2025-07-02T13:05:57.814Z" }, - { url = "https://files.pythonhosted.org/packages/6a/35/5d06ad06402fc522c8bf7eab73422d05e789b4e38fe3206a85e3d6966c11/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:14d96584701a887763384f3c47f0ca7c1cce322aa1c31172680eb596b890ec30", size = 4140942, upload-time = "2025-07-02T13:06:00.137Z" }, - { url = "https://files.pythonhosted.org/packages/65/79/020a5413347e44c382ef1f7f7e7a66817cd6273e3e6b5a72d18177b08b2f/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57c816dfbd1659a367831baca4b775b2a5b43c003daf52e9d57e1d30bc2e1b0e", size = 4374079, upload-time = "2025-07-02T13:06:02.043Z" }, - { url = "https://files.pythonhosted.org/packages/9b/c5/c0e07d84a9a2a8a0ed4f865e58f37c71af3eab7d5e094ff1b21f3f3af3bc/cryptography-45.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b9e38e0a83cd51e07f5a48ff9691cae95a79bea28fe4ded168a8e5c6c77e819d", size = 3321362, upload-time = "2025-07-02T13:06:04.463Z" }, - { url = "https://files.pythonhosted.org/packages/c0/71/9bdbcfd58d6ff5084687fe722c58ac718ebedbc98b9f8f93781354e6d286/cryptography-45.0.5-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8c4a6ff8a30e9e3d38ac0539e9a9e02540ab3f827a3394f8852432f6b0ea152e", size = 3587878, upload-time = "2025-07-02T13:06:06.339Z" }, - { url = "https://files.pythonhosted.org/packages/f0/63/83516cfb87f4a8756eaa4203f93b283fda23d210fc14e1e594bd5f20edb6/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bd4c45986472694e5121084c6ebbd112aa919a25e783b87eb95953c9573906d6", size = 4152447, upload-time = "2025-07-02T13:06:08.345Z" }, - { url = "https://files.pythonhosted.org/packages/22/11/d2823d2a5a0bd5802b3565437add16f5c8ce1f0778bf3822f89ad2740a38/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:982518cd64c54fcada9d7e5cf28eabd3ee76bd03ab18e08a48cad7e8b6f31b18", size = 4386778, upload-time = "2025-07-02T13:06:10.263Z" }, - { url = "https://files.pythonhosted.org/packages/5f/38/6bf177ca6bce4fe14704ab3e93627c5b0ca05242261a2e43ef3168472540/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:12e55281d993a793b0e883066f590c1ae1e802e3acb67f8b442e721e475e6463", size = 4151627, upload-time = "2025-07-02T13:06:13.097Z" }, - { url = "https://files.pythonhosted.org/packages/38/6a/69fc67e5266bff68a91bcb81dff8fb0aba4d79a78521a08812048913e16f/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:5aa1e32983d4443e310f726ee4b071ab7569f58eedfdd65e9675484a4eb67bd1", size = 4385593, upload-time = "2025-07-02T13:06:15.689Z" }, - { url = "https://files.pythonhosted.org/packages/f6/34/31a1604c9a9ade0fdab61eb48570e09a796f4d9836121266447b0eaf7feb/cryptography-45.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e357286c1b76403dd384d938f93c46b2b058ed4dfcdce64a770f0537ed3feb6f", size = 3331106, upload-time = "2025-07-02T13:06:18.058Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/d6/0d/d13399c94234ee8f3df384819dc67e0c5ce215fb751d567a55a1f4b028c7/cryptography-45.0.6.tar.gz", hash = "sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719", size = 744949, upload-time = "2025-08-05T23:59:27.93Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/29/2793d178d0eda1ca4a09a7c4e09a5185e75738cc6d526433e8663b460ea6/cryptography-45.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74", size = 7042702, upload-time = "2025-08-05T23:58:23.464Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b6/cabd07410f222f32c8d55486c464f432808abaa1f12af9afcbe8f2f19030/cryptography-45.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f", size = 4206483, upload-time = "2025-08-05T23:58:27.132Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9e/f9c7d36a38b1cfeb1cc74849aabe9bf817990f7603ff6eb485e0d70e0b27/cryptography-45.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf", size = 4429679, upload-time = "2025-08-05T23:58:29.152Z" }, + { url = "https://files.pythonhosted.org/packages/9c/2a/4434c17eb32ef30b254b9e8b9830cee4e516f08b47fdd291c5b1255b8101/cryptography-45.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5", size = 4210553, upload-time = "2025-08-05T23:58:30.596Z" }, + { url = "https://files.pythonhosted.org/packages/ef/1d/09a5df8e0c4b7970f5d1f3aff1b640df6d4be28a64cae970d56c6cf1c772/cryptography-45.0.6-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2", size = 3894499, upload-time = "2025-08-05T23:58:32.03Z" }, + { url = "https://files.pythonhosted.org/packages/79/62/120842ab20d9150a9d3a6bdc07fe2870384e82f5266d41c53b08a3a96b34/cryptography-45.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08", size = 4458484, upload-time = "2025-08-05T23:58:33.526Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/1bc3634d45ddfed0871bfba52cf8f1ad724761662a0c792b97a951fb1b30/cryptography-45.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402", size = 4210281, upload-time = "2025-08-05T23:58:35.445Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fe/ffb12c2d83d0ee625f124880a1f023b5878f79da92e64c37962bbbe35f3f/cryptography-45.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42", size = 4456890, upload-time = "2025-08-05T23:58:36.923Z" }, + { url = "https://files.pythonhosted.org/packages/8c/8e/b3f3fe0dc82c77a0deb5f493b23311e09193f2268b77196ec0f7a36e3f3e/cryptography-45.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05", size = 4333247, upload-time = "2025-08-05T23:58:38.781Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a6/c3ef2ab9e334da27a1d7b56af4a2417d77e7806b2e0f90d6267ce120d2e4/cryptography-45.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453", size = 4565045, upload-time = "2025-08-05T23:58:40.415Z" }, + { url = "https://files.pythonhosted.org/packages/31/c3/77722446b13fa71dddd820a5faab4ce6db49e7e0bf8312ef4192a3f78e2f/cryptography-45.0.6-cp311-abi3-win32.whl", hash = "sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159", size = 2928923, upload-time = "2025-08-05T23:58:41.919Z" }, + { url = "https://files.pythonhosted.org/packages/38/63/a025c3225188a811b82932a4dcc8457a26c3729d81578ccecbcce2cb784e/cryptography-45.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec", size = 3403805, upload-time = "2025-08-05T23:58:43.792Z" }, + { url = "https://files.pythonhosted.org/packages/5b/af/bcfbea93a30809f126d51c074ee0fac5bd9d57d068edf56c2a73abedbea4/cryptography-45.0.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0", size = 7020111, upload-time = "2025-08-05T23:58:45.316Z" }, + { url = "https://files.pythonhosted.org/packages/98/c6/ea5173689e014f1a8470899cd5beeb358e22bb3cf5a876060f9d1ca78af4/cryptography-45.0.6-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394", size = 4198169, upload-time = "2025-08-05T23:58:47.121Z" }, + { url = "https://files.pythonhosted.org/packages/ba/73/b12995edc0c7e2311ffb57ebd3b351f6b268fed37d93bfc6f9856e01c473/cryptography-45.0.6-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9", size = 4421273, upload-time = "2025-08-05T23:58:48.557Z" }, + { url = "https://files.pythonhosted.org/packages/f7/6e/286894f6f71926bc0da67408c853dd9ba953f662dcb70993a59fd499f111/cryptography-45.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3", size = 4199211, upload-time = "2025-08-05T23:58:50.139Z" }, + { url = "https://files.pythonhosted.org/packages/de/34/a7f55e39b9623c5cb571d77a6a90387fe557908ffc44f6872f26ca8ae270/cryptography-45.0.6-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3", size = 3883732, upload-time = "2025-08-05T23:58:52.253Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b9/c6d32edbcba0cd9f5df90f29ed46a65c4631c4fbe11187feb9169c6ff506/cryptography-45.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301", size = 4450655, upload-time = "2025-08-05T23:58:53.848Z" }, + { url = "https://files.pythonhosted.org/packages/77/2d/09b097adfdee0227cfd4c699b3375a842080f065bab9014248933497c3f9/cryptography-45.0.6-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5", size = 4198956, upload-time = "2025-08-05T23:58:55.209Z" }, + { url = "https://files.pythonhosted.org/packages/55/66/061ec6689207d54effdff535bbdf85cc380d32dd5377173085812565cf38/cryptography-45.0.6-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016", size = 4449859, upload-time = "2025-08-05T23:58:56.639Z" }, + { url = "https://files.pythonhosted.org/packages/41/ff/e7d5a2ad2d035e5a2af116e1a3adb4d8fcd0be92a18032917a089c6e5028/cryptography-45.0.6-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3", size = 4320254, upload-time = "2025-08-05T23:58:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/82/27/092d311af22095d288f4db89fcaebadfb2f28944f3d790a4cf51fe5ddaeb/cryptography-45.0.6-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9", size = 4554815, upload-time = "2025-08-05T23:59:00.283Z" }, + { url = "https://files.pythonhosted.org/packages/7e/01/aa2f4940262d588a8fdf4edabe4cda45854d00ebc6eaac12568b3a491a16/cryptography-45.0.6-cp37-abi3-win32.whl", hash = "sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02", size = 2912147, upload-time = "2025-08-05T23:59:01.716Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bc/16e0276078c2de3ceef6b5a34b965f4436215efac45313df90d55f0ba2d2/cryptography-45.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b", size = 3390459, upload-time = "2025-08-05T23:59:03.358Z" }, + { url = "https://files.pythonhosted.org/packages/56/d2/4482d97c948c029be08cb29854a91bd2ae8da7eb9c4152461f1244dcea70/cryptography-45.0.6-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:705bb7c7ecc3d79a50f236adda12ca331c8e7ecfbea51edd931ce5a7a7c4f012", size = 3576812, upload-time = "2025-08-05T23:59:04.833Z" }, + { url = "https://files.pythonhosted.org/packages/ec/24/55fc238fcaa122855442604b8badb2d442367dfbd5a7ca4bb0bd346e263a/cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:826b46dae41a1155a0c0e66fafba43d0ede1dc16570b95e40c4d83bfcf0a451d", size = 4141694, upload-time = "2025-08-05T23:59:06.66Z" }, + { url = "https://files.pythonhosted.org/packages/f9/7e/3ea4fa6fbe51baf3903806a0241c666b04c73d2358a3ecce09ebee8b9622/cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:cc4d66f5dc4dc37b89cfef1bd5044387f7a1f6f0abb490815628501909332d5d", size = 4375010, upload-time = "2025-08-05T23:59:08.14Z" }, + { url = "https://files.pythonhosted.org/packages/50/42/ec5a892d82d2a2c29f80fc19ced4ba669bca29f032faf6989609cff1f8dc/cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f68f833a9d445cc49f01097d95c83a850795921b3f7cc6488731e69bde3288da", size = 4141377, upload-time = "2025-08-05T23:59:09.584Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d7/246c4c973a22b9c2931999da953a2c19cae7c66b9154c2d62ffed811225e/cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3b5bf5267e98661b9b888a9250d05b063220dfa917a8203744454573c7eb79db", size = 4374609, upload-time = "2025-08-05T23:59:11.923Z" }, + { url = "https://files.pythonhosted.org/packages/78/6d/c49ccf243f0a1b0781c2a8de8123ee552f0c8a417c6367a24d2ecb7c11b3/cryptography-45.0.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2384f2ab18d9be88a6e4f8972923405e2dbb8d3e16c6b43f15ca491d7831bd18", size = 3322156, upload-time = "2025-08-05T23:59:13.597Z" }, + { url = "https://files.pythonhosted.org/packages/61/69/c252de4ec047ba2f567ecb53149410219577d408c2aea9c989acae7eafce/cryptography-45.0.6-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fc022c1fa5acff6def2fc6d7819bbbd31ccddfe67d075331a65d9cfb28a20983", size = 3584669, upload-time = "2025-08-05T23:59:15.431Z" }, + { url = "https://files.pythonhosted.org/packages/e3/fe/deea71e9f310a31fe0a6bfee670955152128d309ea2d1c79e2a5ae0f0401/cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3de77e4df42ac8d4e4d6cdb342d989803ad37707cf8f3fbf7b088c9cbdd46427", size = 4153022, upload-time = "2025-08-05T23:59:16.954Z" }, + { url = "https://files.pythonhosted.org/packages/60/45/a77452f5e49cb580feedba6606d66ae7b82c128947aa754533b3d1bd44b0/cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:599c8d7df950aa68baa7e98f7b73f4f414c9f02d0e8104a30c0182a07732638b", size = 4386802, upload-time = "2025-08-05T23:59:18.55Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b9/a2f747d2acd5e3075fdf5c145c7c3568895daaa38b3b0c960ef830db6cdc/cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:31a2b9a10530a1cb04ffd6aa1cd4d3be9ed49f7d77a4dafe198f3b382f41545c", size = 4152706, upload-time = "2025-08-05T23:59:20.044Z" }, + { url = "https://files.pythonhosted.org/packages/81/ec/381b3e8d0685a3f3f304a382aa3dfce36af2d76467da0fd4bb21ddccc7b2/cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:e5b3dda1b00fb41da3af4c5ef3f922a200e33ee5ba0f0bc9ecf0b0c173958385", size = 4386740, upload-time = "2025-08-05T23:59:21.525Z" }, + { url = "https://files.pythonhosted.org/packages/0a/76/cf8d69da8d0b5ecb0db406f24a63a3f69ba5e791a11b782aeeefef27ccbb/cryptography-45.0.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:629127cfdcdc6806dfe234734d7cb8ac54edaf572148274fa377a7d3405b0043", size = 3331874, upload-time = "2025-08-05T23:59:23.017Z" }, ] [[package]] @@ -641,27 +637,27 @@ wheels = [ [[package]] name = "debugpy" -version = "1.8.15" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/3a9a28ddb750a76eaec445c7f4d3147ea2c579a97dbd9e25d39001b92b21/debugpy-1.8.15.tar.gz", hash = "sha256:58d7a20b7773ab5ee6bdfb2e6cf622fdf1e40c9d5aef2857d85391526719ac00", size = 1643279, upload-time = "2025-07-15T16:43:29.135Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/51/0b4315169f0d945271db037ae6b98c0548a2d48cc036335cd1b2f5516c1b/debugpy-1.8.15-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:e9a8125c85172e3ec30985012e7a81ea5e70bbb836637f8a4104f454f9b06c97", size = 2084890, upload-time = "2025-07-15T16:43:31.239Z" }, - { url = "https://files.pythonhosted.org/packages/36/cc/a5391dedb079280d7b72418022e00ba8227ae0b5bc8b2e3d1ecffc5d6b01/debugpy-1.8.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fd0b6b5eccaa745c214fd240ea82f46049d99ef74b185a3517dad3ea1ec55d9", size = 3561470, upload-time = "2025-07-15T16:43:32.515Z" }, - { url = "https://files.pythonhosted.org/packages/e8/92/acf64b92010c66b33c077dee3862c733798a2c90e7d14b25c01d771e2a0d/debugpy-1.8.15-cp310-cp310-win32.whl", hash = "sha256:8181cce4d344010f6bfe94a531c351a46a96b0f7987750932b2908e7a1e14a55", size = 5229194, upload-time = "2025-07-15T16:43:33.997Z" }, - { url = "https://files.pythonhosted.org/packages/3f/f5/c58c015c9ff78de35901bea3ab4dbf7946d7a4aa867ee73875df06ba6468/debugpy-1.8.15-cp310-cp310-win_amd64.whl", hash = "sha256:af2dcae4e4cd6e8b35f982ccab29fe65f7e8766e10720a717bc80c464584ee21", size = 5260900, upload-time = "2025-07-15T16:43:35.413Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b3/1c44a2ed311199ab11c2299c9474a6c7cd80d19278defd333aeb7c287995/debugpy-1.8.15-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:babc4fb1962dd6a37e94d611280e3d0d11a1f5e6c72ac9b3d87a08212c4b6dd3", size = 2183442, upload-time = "2025-07-15T16:43:36.733Z" }, - { url = "https://files.pythonhosted.org/packages/f6/69/e2dcb721491e1c294d348681227c9b44fb95218f379aa88e12a19d85528d/debugpy-1.8.15-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f778e68f2986a58479d0ac4f643e0b8c82fdd97c2e200d4d61e7c2d13838eb53", size = 3134215, upload-time = "2025-07-15T16:43:38.116Z" }, - { url = "https://files.pythonhosted.org/packages/17/76/4ce63b95d8294dcf2fd1820860b300a420d077df4e93afcaa25a984c2ca7/debugpy-1.8.15-cp311-cp311-win32.whl", hash = "sha256:f9d1b5abd75cd965e2deabb1a06b0e93a1546f31f9f621d2705e78104377c702", size = 5154037, upload-time = "2025-07-15T16:43:39.471Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a7/e5a7c784465eb9c976d84408873d597dc7ce74a0fc69ed009548a1a94813/debugpy-1.8.15-cp311-cp311-win_amd64.whl", hash = "sha256:62954fb904bec463e2b5a415777f6d1926c97febb08ef1694da0e5d1463c5c3b", size = 5178133, upload-time = "2025-07-15T16:43:40.969Z" }, - { url = "https://files.pythonhosted.org/packages/ab/4a/4508d256e52897f5cdfee6a6d7580974811e911c6d01321df3264508a5ac/debugpy-1.8.15-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:3dcc7225cb317469721ab5136cda9ff9c8b6e6fb43e87c9e15d5b108b99d01ba", size = 2511197, upload-time = "2025-07-15T16:43:42.343Z" }, - { url = "https://files.pythonhosted.org/packages/99/8d/7f6ef1097e7fecf26b4ef72338d08e41644a41b7ee958a19f494ffcffc29/debugpy-1.8.15-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:047a493ca93c85ccede1dbbaf4e66816794bdc214213dde41a9a61e42d27f8fc", size = 4229517, upload-time = "2025-07-15T16:43:44.14Z" }, - { url = "https://files.pythonhosted.org/packages/3f/e8/e8c6a9aa33a9c9c6dacbf31747384f6ed2adde4de2e9693c766bdf323aa3/debugpy-1.8.15-cp312-cp312-win32.whl", hash = "sha256:b08e9b0bc260cf324c890626961dad4ffd973f7568fbf57feb3c3a65ab6b6327", size = 5276132, upload-time = "2025-07-15T16:43:45.529Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ad/231050c6177b3476b85fcea01e565dac83607b5233d003ff067e2ee44d8f/debugpy-1.8.15-cp312-cp312-win_amd64.whl", hash = "sha256:e2a4fe357c92334272eb2845fcfcdbec3ef9f22c16cf613c388ac0887aed15fa", size = 5317645, upload-time = "2025-07-15T16:43:46.968Z" }, - { url = "https://files.pythonhosted.org/packages/28/70/2928aad2310726d5920b18ed9f54b9f06df5aa4c10cf9b45fa18ff0ab7e8/debugpy-1.8.15-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:f5e01291ad7d6649aed5773256c5bba7a1a556196300232de1474c3c372592bf", size = 2495538, upload-time = "2025-07-15T16:43:48.927Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c6/9b8ffb4ca91fac8b2877eef63c9cc0e87dd2570b1120054c272815ec4cd0/debugpy-1.8.15-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94dc0f0d00e528d915e0ce1c78e771475b2335b376c49afcc7382ee0b146bab6", size = 4221874, upload-time = "2025-07-15T16:43:50.282Z" }, - { url = "https://files.pythonhosted.org/packages/55/8a/9b8d59674b4bf489318c7c46a1aab58e606e583651438084b7e029bf3c43/debugpy-1.8.15-cp313-cp313-win32.whl", hash = "sha256:fcf0748d4f6e25f89dc5e013d1129ca6f26ad4da405e0723a4f704583896a709", size = 5275949, upload-time = "2025-07-15T16:43:52.079Z" }, - { url = "https://files.pythonhosted.org/packages/72/83/9e58e6fdfa8710a5e6ec06c2401241b9ad48b71c0a7eb99570a1f1edb1d3/debugpy-1.8.15-cp313-cp313-win_amd64.whl", hash = "sha256:73c943776cb83e36baf95e8f7f8da765896fd94b05991e7bc162456d25500683", size = 5317720, upload-time = "2025-07-15T16:43:53.703Z" }, - { url = "https://files.pythonhosted.org/packages/07/d5/98748d9860e767a1248b5e31ffa7ce8cb7006e97bf8abbf3d891d0a8ba4e/debugpy-1.8.15-py2.py3-none-any.whl", hash = "sha256:bce2e6c5ff4f2e00b98d45e7e01a49c7b489ff6df5f12d881c67d2f1ac635f3d", size = 5282697, upload-time = "2025-07-15T16:44:07.996Z" }, +version = "1.8.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/d4/722d0bcc7986172ac2ef3c979ad56a1030e3afd44ced136d45f8142b1f4a/debugpy-1.8.16.tar.gz", hash = "sha256:31e69a1feb1cf6b51efbed3f6c9b0ef03bc46ff050679c4be7ea6d2e23540870", size = 1643809, upload-time = "2025-08-06T18:00:02.647Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/fd/f1b75ebc61d90882595b81d808efd3573c082e1c3407850d9dccac4ae904/debugpy-1.8.16-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:2a3958fb9c2f40ed8ea48a0d34895b461de57a1f9862e7478716c35d76f56c65", size = 2085511, upload-time = "2025-08-06T18:00:05.067Z" }, + { url = "https://files.pythonhosted.org/packages/df/5e/c5c1934352871128b30a1a144a58b5baa546e1b57bd47dbed788bad4431c/debugpy-1.8.16-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5ca7314042e8a614cc2574cd71f6ccd7e13a9708ce3c6d8436959eae56f2378", size = 3562094, upload-time = "2025-08-06T18:00:06.66Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d5/2ebe42377e5a78dc786afc25e61ee83c5628d63f32dfa41092597d52fe83/debugpy-1.8.16-cp310-cp310-win32.whl", hash = "sha256:8624a6111dc312ed8c363347a0b59c5acc6210d897e41a7c069de3c53235c9a6", size = 5234277, upload-time = "2025-08-06T18:00:08.429Z" }, + { url = "https://files.pythonhosted.org/packages/54/f8/e774ad16a60b9913213dbabb7472074c5a7b0d84f07c1f383040a9690057/debugpy-1.8.16-cp310-cp310-win_amd64.whl", hash = "sha256:fee6db83ea5c978baf042440cfe29695e1a5d48a30147abf4c3be87513609817", size = 5266011, upload-time = "2025-08-06T18:00:10.162Z" }, + { url = "https://files.pythonhosted.org/packages/63/d6/ad70ba8b49b23fa286fb21081cf732232cc19374af362051da9c7537ae52/debugpy-1.8.16-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:67371b28b79a6a12bcc027d94a06158f2fde223e35b5c4e0783b6f9d3b39274a", size = 2184063, upload-time = "2025-08-06T18:00:11.885Z" }, + { url = "https://files.pythonhosted.org/packages/aa/49/7b03e88dea9759a4c7910143f87f92beb494daaae25560184ff4ae883f9e/debugpy-1.8.16-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2abae6dd02523bec2dee16bd6b0781cccb53fd4995e5c71cc659b5f45581898", size = 3134837, upload-time = "2025-08-06T18:00:13.782Z" }, + { url = "https://files.pythonhosted.org/packages/5d/52/b348930316921de7565fbe37a487d15409041713004f3d74d03eb077dbd4/debugpy-1.8.16-cp311-cp311-win32.whl", hash = "sha256:f8340a3ac2ed4f5da59e064aa92e39edd52729a88fbde7bbaa54e08249a04493", size = 5159142, upload-time = "2025-08-06T18:00:15.391Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ef/9aa9549ce1e10cea696d980292e71672a91ee4a6a691ce5f8629e8f48c49/debugpy-1.8.16-cp311-cp311-win_amd64.whl", hash = "sha256:70f5fcd6d4d0c150a878d2aa37391c52de788c3dc680b97bdb5e529cb80df87a", size = 5183117, upload-time = "2025-08-06T18:00:17.251Z" }, + { url = "https://files.pythonhosted.org/packages/61/fb/0387c0e108d842c902801bc65ccc53e5b91d8c169702a9bbf4f7efcedf0c/debugpy-1.8.16-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:b202e2843e32e80b3b584bcebfe0e65e0392920dc70df11b2bfe1afcb7a085e4", size = 2511822, upload-time = "2025-08-06T18:00:18.526Z" }, + { url = "https://files.pythonhosted.org/packages/37/44/19e02745cae22bf96440141f94e15a69a1afaa3a64ddfc38004668fcdebf/debugpy-1.8.16-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64473c4a306ba11a99fe0bb14622ba4fbd943eb004847d9b69b107bde45aa9ea", size = 4230135, upload-time = "2025-08-06T18:00:19.997Z" }, + { url = "https://files.pythonhosted.org/packages/f3/0b/19b1ba5ee4412f303475a2c7ad5858efb99c90eae5ec627aa6275c439957/debugpy-1.8.16-cp312-cp312-win32.whl", hash = "sha256:833a61ed446426e38b0dd8be3e9d45ae285d424f5bf6cd5b2b559c8f12305508", size = 5281271, upload-time = "2025-08-06T18:00:21.281Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e0/bc62e2dc141de53bd03e2c7cb9d7011de2e65e8bdcdaa26703e4d28656ba/debugpy-1.8.16-cp312-cp312-win_amd64.whl", hash = "sha256:75f204684581e9ef3dc2f67687c3c8c183fde2d6675ab131d94084baf8084121", size = 5323149, upload-time = "2025-08-06T18:00:23.033Z" }, + { url = "https://files.pythonhosted.org/packages/62/66/607ab45cc79e60624df386e233ab64a6d8d39ea02e7f80e19c1d451345bb/debugpy-1.8.16-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:85df3adb1de5258dca910ae0bb185e48c98801ec15018a263a92bb06be1c8787", size = 2496157, upload-time = "2025-08-06T18:00:24.361Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a0/c95baae08a75bceabb79868d663a0736655e427ab9c81fb848da29edaeac/debugpy-1.8.16-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee89e948bc236a5c43c4214ac62d28b29388453f5fd328d739035e205365f0b", size = 4222491, upload-time = "2025-08-06T18:00:25.806Z" }, + { url = "https://files.pythonhosted.org/packages/5b/2f/1c8db6ddd8a257c3cd2c46413b267f1d5fa3df910401c899513ce30392d6/debugpy-1.8.16-cp313-cp313-win32.whl", hash = "sha256:cf358066650439847ec5ff3dae1da98b5461ea5da0173d93d5e10f477c94609a", size = 5281126, upload-time = "2025-08-06T18:00:27.207Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ba/c3e154ab307366d6c5a9c1b68de04914e2ce7fa2f50d578311d8cc5074b2/debugpy-1.8.16-cp313-cp313-win_amd64.whl", hash = "sha256:b5aea1083f6f50023e8509399d7dc6535a351cc9f2e8827d1e093175e4d9fa4c", size = 5323094, upload-time = "2025-08-06T18:00:29.03Z" }, + { url = "https://files.pythonhosted.org/packages/52/57/ecc9ae29fa5b2d90107cd1d9bf8ed19aacb74b2264d986ae9d44fe9bdf87/debugpy-1.8.16-py2.py3-none-any.whl", hash = "sha256:19c9521962475b87da6f673514f7fd610328757ec993bf7ec0d8c96f9a325f9e", size = 5287700, upload-time = "2025-08-06T18:00:42.333Z" }, ] [[package]] @@ -914,44 +910,16 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/34/80/de3eb55eb581815342d097214bed4c59e806b05f1b3110df03b2280d6dfd/grpcio-1.74.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd3c71aeee838299c5887230b8a1822795325ddfea635edd82954c1eaa831e24", size = 4489214, upload-time = "2025-07-24T18:53:59.771Z" }, ] -[[package]] -name = "gymnasium" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "python_full_version < '3.11' and 'x86_64' in platform_machine and sys_platform == 'darwin'", -] -dependencies = [ - { name = "cloudpickle", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "farama-notifications", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "typing-extensions", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4e/12/1047b8fdbfcdce74022048d916e844ad7e6e1114d81d26a7aed657e3a76d/gymnasium-1.0.0.tar.gz", hash = "sha256:9d2b66f30c1b34fe3c2ce7fae65ecf365d0e9982d2b3d860235e773328a3b403", size = 821389, upload-time = "2024-10-08T10:01:02.49Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/85/f5039ce2df5f0789ff1240f08a59f3e8c92e4c5f99543b7aad7388532f7c/gymnasium-1.0.0-py3-none-any.whl", hash = "sha256:b6f40e1e24c5bd419361e1a5b86a9117d2499baecc3a660d44dfff4c465393ad", size = 958120, upload-time = "2024-10-08T10:01:00.337Z" }, -] - [[package]] name = "gymnasium" version = "1.2.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", - "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", - "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", - "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')", -] dependencies = [ - { name = "cloudpickle", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, - { name = "farama-notifications", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')" }, + { name = "cloudpickle" }, + { name = "farama-notifications" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin')" }, { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13' and 'x86_64' not in platform_machine) or (python_full_version >= '3.11' and sys_platform != 'darwin') or (python_full_version >= '3.13' and sys_platform == 'darwin')" }, - { name = "typing-extensions", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fd/17/c2a0e15c2cd5a8e788389b280996db927b923410de676ec5c7b2695e9261/gymnasium-1.2.0.tar.gz", hash = "sha256:344e87561012558f603880baf264ebc97f8a5c997a957b0c9f910281145534b0", size = 821142, upload-time = "2025-06-27T08:21:20.262Z" } wheels = [ @@ -1026,7 +994,7 @@ wheels = [ [[package]] name = "ipykernel" -version = "6.30.0" +version = "6.30.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "appnope", marker = "sys_platform == 'darwin'" }, @@ -1044,9 +1012,9 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/27/9e6e30ed92f2ac53d29f70b09da8b2dc456e256148e289678fa0e825f46a/ipykernel-6.30.0.tar.gz", hash = "sha256:b7b808ddb2d261aae2df3a26ff3ff810046e6de3dfbc6f7de8c98ea0a6cb632c", size = 165125, upload-time = "2025-07-21T10:36:09.259Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/76/11082e338e0daadc89c8ff866185de11daf67d181901038f9e139d109761/ipykernel-6.30.1.tar.gz", hash = "sha256:6abb270161896402e76b91394fcdce5d1be5d45f456671e5080572f8505be39b", size = 166260, upload-time = "2025-08-04T15:47:35.018Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/3d/00813c3d9b46e3dcd88bd4530e0a3c63c0509e5d8c9eff34723ea243ab04/ipykernel-6.30.0-py3-none-any.whl", hash = "sha256:fd2936e55c4a1c2ee8b1e5fa6a372b8eecc0ab1338750dee76f48fa5cca1301e", size = 117264, upload-time = "2025-07-21T10:36:06.854Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl", hash = "sha256:aa6b9fb93dca949069d8b85b6c79b2518e32ac583ae9c7d37c51d119e18b3fb4", size = 117484, upload-time = "2025-08-04T15:47:32.622Z" }, ] [[package]] @@ -1080,11 +1048,9 @@ name = "ipython" version = "9.4.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", + "python_full_version >= '3.13'", "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", + "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", ] @@ -1558,11 +1524,10 @@ dependencies = [ { name = "qiskit-ibm-ai-local-transpiler", marker = "python_full_version < '3.13'" }, { name = "qiskit-ibm-transpiler" }, { name = "rich" }, - { name = "sb3-contrib", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "sb3-contrib", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "sb3-contrib" }, { name = "scikit-learn" }, { name = "tensorboard" }, - { name = "torch", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "torch" }, { name = "tqdm" }, { name = "typing-extensions" }, ] @@ -1619,7 +1584,7 @@ requires-dist = [ { name = "sb3-contrib", specifier = ">=2.0.0" }, { name = "scikit-learn", specifier = ">=1.5.1" }, { name = "tensorboard", specifier = ">=2.17.0" }, - { name = "torch", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'", specifier = ">=2.7.1,<2.8.0" }, + { name = "torch", specifier = ">=2.7.1,<2.8.0" }, { name = "tqdm", specifier = ">=4.66.0" }, { name = "typing-extensions", specifier = ">=4.1" }, ] @@ -1792,10 +1757,8 @@ name = "numpy" version = "2.3.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", - "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", + "python_full_version >= '3.13'", + "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", ] sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" } @@ -2818,7 +2781,7 @@ wheels = [ [[package]] name = "qiskit-ibm-runtime" -version = "0.40.1" +version = "0.41.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ibm-platform-services" }, @@ -2832,9 +2795,9 @@ dependencies = [ { name = "requests-ntlm" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b0/90/c16c0a83400b29aa9f76f463ebeb3b5a4abc711eebd6ddd27509fa0a02b4/qiskit_ibm_runtime-0.40.1.tar.gz", hash = "sha256:83fd172357a28d69b3289009a724ec14d70c4367dbb5aa140ffd63aa101c3ae1", size = 3198615, upload-time = "2025-06-05T15:59:51.692Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/0f/8dce46911ad83dde9b593ee56136b9ba05950551bf8242a83e4108e9a5df/qiskit_ibm_runtime-0.41.0.tar.gz", hash = "sha256:c4a1b79026419a911b32da802c5b6a694c23a1bbf63854205e13a6c4bbd65cd2", size = 1472034, upload-time = "2025-08-05T18:23:56.891Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/2e/7ef9afa14f94affb7afd472edd70358c1a0c66e5cbeac1b2a29ce84b8a5d/qiskit_ibm_runtime-0.40.1-py3-none-any.whl", hash = "sha256:5a67e4c55b6e0a2bc9041c1ec7b8c5adc43a50f7dfb8a2521a0c2f3ed4aaa712", size = 3196409, upload-time = "2025-06-05T15:59:49.117Z" }, + { url = "https://files.pythonhosted.org/packages/9b/97/cc8edf7f4500cb3e99c187c214977880396ea2462c2c28ece98db47a8075/qiskit_ibm_runtime-0.41.0-py3-none-any.whl", hash = "sha256:09692ef0b2fc8562298f5f0ebee394486233359149326490b59bb16ea5b68f2b", size = 1418666, upload-time = "2025-08-05T18:23:54.956Z" }, ] [[package]] @@ -3091,37 +3054,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3e/79/9bdd52d2a33d468c81c1827de1b588080cb055d1d3561b194ab7bf2635b5/rustworkx-0.16.0-cp39-abi3-win_amd64.whl", hash = "sha256:905df608843c32fa45ac023687769fe13056edf7584474c801d5c50705d76e9b", size = 1953559, upload-time = "2025-01-24T01:22:06.136Z" }, ] -[[package]] -name = "sb3-contrib" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "python_full_version < '3.11' and 'x86_64' in platform_machine and sys_platform == 'darwin'", -] -dependencies = [ - { name = "stable-baselines3", version = "2.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c7/4e/dd31aba603634a7b9473b502fe85b694e4392c448eae0d345640b87211c2/sb3_contrib-2.4.0.tar.gz", hash = "sha256:a3709d97ddd529c45e3d56a5ae7e61a380bb2e11bacb9a3e6951ac0c481350ca", size = 89358, upload-time = "2024-11-18T10:20:53.214Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/50/de/33d3b00116488de9371a56009089ab408d949cda7057e2560c8efae810df/sb3_contrib-2.4.0-py3-none-any.whl", hash = "sha256:725d90157028a94c69804f2e7332128518fb7cbab39e7e141d2d0355547a72ab", size = 92752, upload-time = "2024-11-18T10:20:51.074Z" }, -] - [[package]] name = "sb3-contrib" version = "2.7.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", - "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", - "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", - "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')", -] dependencies = [ - { name = "stable-baselines3", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "stable-baselines3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7e/23/2831ccecc8fc2fcda401804f3ed1dc3b81f7c4ad3cb32dc6b1cd79f91c49/sb3_contrib-2.7.0.tar.gz", hash = "sha256:87281ae64e6965e46ac24b58778241d2b07b29996044491267ed844c1a75acfe", size = 90289, upload-time = "2025-07-25T13:02:07.208Z" } wheels = [ @@ -3234,11 +3172,9 @@ name = "scipy" version = "1.16.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", + "python_full_version >= '3.13'", "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", + "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", ] @@ -3391,11 +3327,9 @@ name = "sphinx" version = "8.2.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", + "python_full_version >= '3.13'", "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", + "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", ] @@ -3563,15 +3497,15 @@ wheels = [ [[package]] name = "sphinxext-opengraph" -version = "0.10.0" +version = "0.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/27/d8e269030e1f063494ee2b8110256de8fa05b1ef532c76e6807b5c42c8b1/sphinxext_opengraph-0.10.0.tar.gz", hash = "sha256:5781149c1dfc306c3701661acdd02c512c2e6f21d2253487bf0b39639017fc24", size = 1027562, upload-time = "2025-04-04T21:35:19.646Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/8f/6c0d869ac0fd25ae90510350800312f80bd0618d19f093e70fca3d670b32/sphinxext_opengraph-0.12.0.tar.gz", hash = "sha256:38bc17addd5d8bccc148daa7c6c13845c329c2687378ee8fdfc81942cc72a79d", size = 1026784, upload-time = "2025-08-04T21:53:36.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/3f/bb8bbee4d26aa2abd46e76025697040708e1b0d37c9f198877cebf0e3ba0/sphinxext_opengraph-0.10.0-py3-none-any.whl", hash = "sha256:8afd33f96a02d9506d9dc8f284840888ca8948482ac93015a68d88493df43712", size = 1004031, upload-time = "2025-04-04T21:35:17.637Z" }, + { url = "https://files.pythonhosted.org/packages/1b/26/8388918251df75086c7fcd9cd7e231f08774e3f4ce6fc911ff105a87a300/sphinxext_opengraph-0.12.0-py3-none-any.whl", hash = "sha256:f60892d5fa4cc25889b0baa5ec1c778a8ef87aa7c1f4bb5f93f11befdb0d7b9c", size = 1004063, upload-time = "2025-08-04T21:53:34.641Z" }, ] [[package]] @@ -3639,48 +3573,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/35/dd/698e7a1701908c62bfe371c197eba258182c3f41a3fc51ae588f8d46f3a7/sspilib-0.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:44b89f866e0d14c8393dbc5a49c59296dd7b83a7ca97a0f9d6bd49cc46a04498", size = 528415, upload-time = "2025-04-30T19:37:13.551Z" }, ] -[[package]] -name = "stable-baselines3" -version = "2.4.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "python_full_version < '3.11' and 'x86_64' in platform_machine and sys_platform == 'darwin'", -] -dependencies = [ - { name = "cloudpickle", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "gymnasium", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "matplotlib", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "pandas", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "torch", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fa/3b/13aacfe41697455f559449ad0dc5f51b4f30aed8fb000131225c64cc60f4/stable_baselines3-2.4.1.tar.gz", hash = "sha256:3bbf0e46b9aa4b1fd2696ff4c806ddb8ba719966ef0f71fba61f7a177e563c81", size = 212611, upload-time = "2025-01-07T13:22:42.455Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/96/04e1e987b5dd8ac0f192e74ad740fb5d72b4c9acd04f3ee13f5f9d6eacae/stable_baselines3-2.4.1-py3-none-any.whl", hash = "sha256:f74cde94a15c1d9d90343e73d790e1898b4c1cbb3f09eeeb580303d33f3fc4d6", size = 183963, upload-time = "2025-01-07T13:22:39.666Z" }, -] - [[package]] name = "stable-baselines3" version = "2.7.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.13' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version >= '3.13' and 'x86_64' in platform_machine) or (python_full_version >= '3.13' and sys_platform == 'darwin')", - "python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform != 'darwin'", - "(python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform != 'darwin') or (python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin')", - "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", - "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')", -] dependencies = [ - { name = "cloudpickle", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, - { name = "gymnasium", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, - { name = "matplotlib", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')" }, + { name = "cloudpickle" }, + { name = "gymnasium" }, + { name = "matplotlib" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin')" }, { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13' and 'x86_64' not in platform_machine) or (python_full_version >= '3.11' and sys_platform != 'darwin') or (python_full_version >= '3.13' and sys_platform == 'darwin')" }, - { name = "pandas", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, - { name = "torch", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "pandas" }, + { name = "torch" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/cc/9a334071fae143bc7177e17a3191db83c1a4bf9038b09c4c5a34e427ca33/stable_baselines3-2.7.0.tar.gz", hash = "sha256:5258561e5becd15234274262cf09fcb9a082a73c2c67a85322f5652a05195ec4", size = 219012, upload-time = "2025-07-25T09:54:35.113Z" } wheels = [ From 845f7de230b82d7b33c91ccf069b20b0ebe697a6 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Thu, 7 Aug 2025 12:52:05 +0200 Subject: [PATCH 18/57] Use default Qiskit settings for VF2Layout and add assertion for native gate check --- src/mqt/predictor/rl/actions.py | 4 ++++ tests/compilation/test_predictor_rl.py | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/src/mqt/predictor/rl/actions.py b/src/mqt/predictor/rl/actions.py index 7d0b856d3..0b88c90aa 100644 --- a/src/mqt/predictor/rl/actions.py +++ b/src/mqt/predictor/rl/actions.py @@ -576,6 +576,8 @@ def get_openqasm_gates() -> list[str]: VF2Layout( coupling_map=CouplingMap(device.build_coupling_map()), target=device, + call_limit=30000000, + max_trials=250000, ), ConditionalController( [ @@ -617,6 +619,8 @@ def get_openqasm_gates() -> list[str]: VF2Layout( coupling_map=CouplingMap(device.build_coupling_map()), target=device, + call_limit=30000000, + max_trials=250000, ), ConditionalController( [ diff --git a/tests/compilation/test_predictor_rl.py b/tests/compilation/test_predictor_rl.py index ac62c2fae..9e4c1ded2 100644 --- a/tests/compilation/test_predictor_rl.py +++ b/tests/compilation/test_predictor_rl.py @@ -19,6 +19,7 @@ from qiskit.circuit.library import CXGate from qiskit.qasm2 import dump from qiskit.transpiler import InstructionProperties, Target +from qiskit.transpiler.passes import GatesInBasis from mqt.predictor.rl import Predictor, rl_compile from mqt.predictor.rl.actions import ( @@ -89,8 +90,14 @@ def test_qcompile_with_newly_trained_models() -> None: ) qc_compiled, compilation_information = rl_compile(qc, device=device, figure_of_merit=figure_of_merit) + + check_nat_gates = GatesInBasis(basis_gates=device.operation_names) + check_nat_gates(qc_compiled) + only_nat_gates = check_nat_gates.property_set["all_gates_in_basis"] + assert qc_compiled.layout is not None assert compilation_information is not None + assert only_nat_gates, "Circuit should only contain native gates but was not detected as such" def test_qcompile_with_false_input() -> None: From 34189365b1ce9e3a7c24572e605a0b0c3c62c694 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Thu, 7 Aug 2025 14:03:44 +0200 Subject: [PATCH 19/57] Debug --- noxfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index ee7980b69..ff94cf423 100644 --- a/noxfile.py +++ b/noxfile.py @@ -70,6 +70,7 @@ def _run_tests( "-v", *pytest_run_args, "--disable-warnings", + "--log-cli-level=INFO", *session.posargs, "--cov-config=pyproject.toml", env=env, @@ -140,6 +141,6 @@ def docs(session: nox.Session) -> None: "sphinx-autobuild" if serve else "sphinx-build", *shared_args, "-v", - "--disable-warnings", + "--log-cli-level=INFO--disable-warnings", env=env, ) From ae870cc37930cca4a69628babe4ef2b2ef9f9ff5 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Thu, 7 Aug 2025 14:56:59 +0200 Subject: [PATCH 20/57] Fix missing argument --- .../test_estimated_hellinger_distance.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/hellinger_distance/test_estimated_hellinger_distance.py b/tests/hellinger_distance/test_estimated_hellinger_distance.py index d13151d51..6a92e94a4 100644 --- a/tests/hellinger_distance/test_estimated_hellinger_distance.py +++ b/tests/hellinger_distance/test_estimated_hellinger_distance.py @@ -220,17 +220,15 @@ def test_train_and_qcompile_with_hellinger_model(source_path: Path, target_path: if sys.platform == "win32": with pytest.warns(RuntimeWarning, match=re.escape("Timeout is not supported on Windows.")): ml_predictor.compile_training_circuits( - timeout=600, path_compiled_circuits=target_path, path_uncompiled_circuits=source_path, num_workers=1 + timeout=6000, path_compiled_circuits=target_path, path_uncompiled_circuits=source_path ) else: ml_predictor.compile_training_circuits( - timeout=600, path_compiled_circuits=target_path, path_uncompiled_circuits=source_path, num_workers=1 + timeout=6000, path_compiled_circuits=target_path, path_uncompiled_circuits=source_path ) # Generate training data from the compiled circuits - ml_predictor.generate_training_data( - path_uncompiled_circuits=source_path, path_compiled_circuits=target_path, num_workers=1 - ) + ml_predictor.generate_training_data(path_uncompiled_circuits=source_path, path_compiled_circuits=target_path) for file in [ "training_data_estimated_hellinger_distance.npy", From 861bc621a76647dd3694928e8bcbe7b249cf612f Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Thu, 7 Aug 2025 15:38:57 +0200 Subject: [PATCH 21/57] Fix warning issues --- noxfile.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/noxfile.py b/noxfile.py index ff94cf423..cc639edc0 100644 --- a/noxfile.py +++ b/noxfile.py @@ -69,7 +69,6 @@ def _run_tests( "pytest", "-v", *pytest_run_args, - "--disable-warnings", "--log-cli-level=INFO", *session.posargs, "--cov-config=pyproject.toml", @@ -141,6 +140,6 @@ def docs(session: nox.Session) -> None: "sphinx-autobuild" if serve else "sphinx-build", *shared_args, "-v", - "--log-cli-level=INFO--disable-warnings", + "--log-cli-level=INFO", env=env, ) From fa989b6962b4109534d24ebdc7f9840ab5db54ab Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Thu, 7 Aug 2025 17:49:39 +0200 Subject: [PATCH 22/57] Fix window runtime warning problem --- src/mqt/predictor/ml/predictor.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/mqt/predictor/ml/predictor.py b/src/mqt/predictor/ml/predictor.py index 3b844caf2..30f89fb65 100644 --- a/src/mqt/predictor/ml/predictor.py +++ b/src/mqt/predictor/ml/predictor.py @@ -12,6 +12,7 @@ import logging import sys +import warnings import zipfile from importlib import resources from pathlib import Path @@ -191,7 +192,13 @@ def _compile_all_circuits_devicewise( if (path_compiled_circuits / (target_filename + ".qasm")).exists(): continue try: - res = timeout_watcher(rl_compile, [qc, device, self.figure_of_merit, rl_pred], timeout) + if sys.platform == "win32": + warnings.warn( + "Timeout is not supported on Windows. Running without timeout.", RuntimeWarning, stacklevel=2 + ) + res = rl_compile(qc, device, self.figure_of_merit, rl_pred) + else: + res = timeout_watcher(rl_compile, [qc, device, self.figure_of_merit, rl_pred], timeout) if isinstance(res, tuple): compiled_qc = res[0] with Path(path_compiled_circuits / (target_filename + ".qasm")).open("w", encoding="utf-8") as f: @@ -205,7 +212,7 @@ def compile_training_circuits( self, path_uncompiled_circuits: Path | None = None, path_compiled_circuits: Path | None = None, - timeout: int = 6000, + timeout: int = 600, ) -> None: """Compiles all circuits in the given directory with the given timeout and saves them in the given directory. From 405bd398e76d345cb2b550b2ab13cbba41bb6e71 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Thu, 7 Aug 2025 18:19:12 +0200 Subject: [PATCH 23/57] Fix window runtime warning problem --- src/mqt/predictor/ml/predictor.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/mqt/predictor/ml/predictor.py b/src/mqt/predictor/ml/predictor.py index 30f89fb65..0ca7b5a72 100644 --- a/src/mqt/predictor/ml/predictor.py +++ b/src/mqt/predictor/ml/predictor.py @@ -12,7 +12,6 @@ import logging import sys -import warnings import zipfile from importlib import resources from pathlib import Path @@ -192,13 +191,7 @@ def _compile_all_circuits_devicewise( if (path_compiled_circuits / (target_filename + ".qasm")).exists(): continue try: - if sys.platform == "win32": - warnings.warn( - "Timeout is not supported on Windows. Running without timeout.", RuntimeWarning, stacklevel=2 - ) - res = rl_compile(qc, device, self.figure_of_merit, rl_pred) - else: - res = timeout_watcher(rl_compile, [qc, device, self.figure_of_merit, rl_pred], timeout) + res = timeout_watcher(rl_compile, [qc, device, self.figure_of_merit, rl_pred], timeout) if isinstance(res, tuple): compiled_qc = res[0] with Path(path_compiled_circuits / (target_filename + ".qasm")).open("w", encoding="utf-8") as f: @@ -233,12 +226,18 @@ def compile_training_circuits( with zipfile.ZipFile(str(path_zip), "r") as zip_ref: zip_ref.extractall(path_uncompiled_circuits) - Parallel(n_jobs=1, verbose=100)( - delayed(self._compile_all_circuits_devicewise)( - device, timeout, path_uncompiled_circuits, path_compiled_circuits, logger.level + if sys.platform != "win32": + Parallel(n_jobs=1, verbose=100)( + delayed(self._compile_all_circuits_devicewise)( + device, timeout, path_uncompiled_circuits, path_compiled_circuits, logger.level + ) + for device in self.devices ) - for device in self.devices - ) + else: + for device in self.devices: + self._compile_all_circuits_devicewise( + device, timeout, path_uncompiled_circuits, path_compiled_circuits, logger.level + ) def generate_training_data( self, From 7b2f321bc3a9aac5ea90144c34fe79ce649db41c Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Thu, 7 Aug 2025 18:34:52 +0200 Subject: [PATCH 24/57] Add time limit for VF2PostLayout --- src/mqt/predictor/rl/actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqt/predictor/rl/actions.py b/src/mqt/predictor/rl/actions.py index 0b88c90aa..005948d25 100644 --- a/src/mqt/predictor/rl/actions.py +++ b/src/mqt/predictor/rl/actions.py @@ -395,7 +395,7 @@ def get_openqasm_gates() -> list[str]: "VF2PostLayout", CompilationOrigin.QISKIT, PassType.FINAL_OPT, - transpile_pass=lambda device: VF2PostLayout(target=device), + transpile_pass=lambda device: VF2PostLayout(target=device, time_limit=100), ) ) From b67d0a601783d4e7d7944c089825cf69c62bafc1 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Thu, 7 Aug 2025 19:06:13 +0200 Subject: [PATCH 25/57] Fix windows runtime warning problem Fix windows runtime warning problem Fix windows runtime warning issue --- src/mqt/predictor/ml/predictor.py | 5 ++++- .../test_estimated_hellinger_distance.py | 13 +++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/mqt/predictor/ml/predictor.py b/src/mqt/predictor/ml/predictor.py index 0ca7b5a72..155c09e04 100644 --- a/src/mqt/predictor/ml/predictor.py +++ b/src/mqt/predictor/ml/predictor.py @@ -191,7 +191,10 @@ def _compile_all_circuits_devicewise( if (path_compiled_circuits / (target_filename + ".qasm")).exists(): continue try: - res = timeout_watcher(rl_compile, [qc, device, self.figure_of_merit, rl_pred], timeout) + if sys.platform == "win32": + res = rl_compile(qc, device, self.figure_of_merit, rl_pred) + else: + res = timeout_watcher(rl_compile, [qc, device, self.figure_of_merit, rl_pred], timeout) if isinstance(res, tuple): compiled_qc = res[0] with Path(path_compiled_circuits / (target_filename + ".qasm")).open("w", encoding="utf-8") as f: diff --git a/tests/hellinger_distance/test_estimated_hellinger_distance.py b/tests/hellinger_distance/test_estimated_hellinger_distance.py index 6a92e94a4..14d2ac3ea 100644 --- a/tests/hellinger_distance/test_estimated_hellinger_distance.py +++ b/tests/hellinger_distance/test_estimated_hellinger_distance.py @@ -11,7 +11,6 @@ from __future__ import annotations import re -import sys import warnings from pathlib import Path from typing import TYPE_CHECKING @@ -217,15 +216,9 @@ def test_train_and_qcompile_with_hellinger_model(source_path: Path, target_path: dump(qc, f) # Generate compiled circuits (using trained RL model) - if sys.platform == "win32": - with pytest.warns(RuntimeWarning, match=re.escape("Timeout is not supported on Windows.")): - ml_predictor.compile_training_circuits( - timeout=6000, path_compiled_circuits=target_path, path_uncompiled_circuits=source_path - ) - else: - ml_predictor.compile_training_circuits( - timeout=6000, path_compiled_circuits=target_path, path_uncompiled_circuits=source_path - ) + ml_predictor.compile_training_circuits( + timeout=6000, path_compiled_circuits=target_path, path_uncompiled_circuits=source_path + ) # Generate training data from the compiled circuits ml_predictor.generate_training_data(path_uncompiled_circuits=source_path, path_compiled_circuits=target_path) From bf7c9ee42d36ef0aefeb33404aab5b46f11d79b1 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Fri, 29 Aug 2025 16:32:37 +0200 Subject: [PATCH 26/57] Add new actions --- noxfile.py | 4 - src/mqt/predictor/reward.py | 7 +- src/mqt/predictor/rl/actions.py | 412 ++++++++++--------------- src/mqt/predictor/rl/helper.py | 89 +----- src/mqt/predictor/rl/predictor.py | 20 +- src/mqt/predictor/rl/predictorenv.py | 119 ++++--- tests/compilation/test_predictor_rl.py | 54 ++-- 7 files changed, 281 insertions(+), 424 deletions(-) diff --git a/noxfile.py b/noxfile.py index cc639edc0..8a7e78ddc 100644 --- a/noxfile.py +++ b/noxfile.py @@ -67,9 +67,7 @@ def _run_tests( "test", *install_args, "pytest", - "-v", *pytest_run_args, - "--log-cli-level=INFO", *session.posargs, "--cov-config=pyproject.toml", env=env, @@ -139,7 +137,5 @@ def docs(session: nox.Session) -> None: "--frozen", "sphinx-autobuild" if serve else "sphinx-build", *shared_args, - "-v", - "--log-cli-level=INFO", env=env, ) diff --git a/src/mqt/predictor/reward.py b/src/mqt/predictor/reward.py index 86632873d..38be96bce 100644 --- a/src/mqt/predictor/reward.py +++ b/src/mqt/predictor/reward.py @@ -67,7 +67,12 @@ def expected_fidelity(qc: QuantumCircuit, device: Target, precision: int = 10) - specific_fidelity = 1 - device[gate_type][first_qubit_idx,].error else: second_qubit_idx = calc_qubit_index(qargs, qc.qregs, 1) - specific_fidelity = 1 - device[gate_type][first_qubit_idx, second_qubit_idx].error + try: + specific_fidelity = 1 - device[gate_type][first_qubit_idx, second_qubit_idx].error + except KeyError: + # try flipped orientation + specific_fidelity = 1 - device[gate_type][second_qubit_idx, first_qubit_idx].error + res *= specific_fidelity diff --git a/src/mqt/predictor/rl/actions.py b/src/mqt/predictor/rl/actions.py index 005948d25..a8975426f 100644 --- a/src/mqt/predictor/rl/actions.py +++ b/src/mqt/predictor/rl/actions.py @@ -50,6 +50,7 @@ from qiskit.transpiler import CouplingMap from qiskit.transpiler.passes import ( ApplyLayout, + BasicSwap, BasisTranslator, Collect2qBlocks, CollectCliffords, @@ -132,6 +133,7 @@ class Action: ] ) stochastic: bool | None = False + preserve: bool | None = False @dataclass @@ -233,7 +235,8 @@ def get_openqasm_gates() -> list[str]: "Optimize1qGatesDecomposition", CompilationOrigin.QISKIT, PassType.OPT, - [Optimize1qGatesDecomposition(basis=get_openqasm_gates())], + preserve=True, + transpile_pass= lambda device: [Optimize1qGatesDecomposition(basis=device.operation_names)], ) ) @@ -243,6 +246,7 @@ def get_openqasm_gates() -> list[str]: CompilationOrigin.QISKIT, PassType.OPT, [CommutativeCancellation()], + preserve=True, ) ) @@ -252,6 +256,7 @@ def get_openqasm_gates() -> list[str]: CompilationOrigin.QISKIT, PassType.OPT, [CommutativeInverseCancellation()], + preserve=True, ) ) @@ -261,6 +266,7 @@ def get_openqasm_gates() -> list[str]: CompilationOrigin.QISKIT, PassType.OPT, [RemoveDiagonalGatesBeforeMeasure()], + preserve=True, ) ) @@ -285,6 +291,7 @@ def get_openqasm_gates() -> list[str]: (SXGate(), SXdgGate()), ]) ], + preserve=True, ) ) @@ -307,6 +314,7 @@ def get_openqasm_gates() -> list[str]: ConsolidateBlocks(basis_gates=native_gate), UnitarySynthesis(basis_gates=native_gate, coupling_map=coupling_map), ], + preserve=True, ) ) @@ -346,56 +354,70 @@ def get_openqasm_gates() -> list[str]: ) ) -register_action( - DeviceDependentAction( - "QiskitO3", - CompilationOrigin.QISKIT, - PassType.OPT, - transpile_pass=lambda native_gate, coupling_map: [ - Collect2qBlocks(), - ConsolidateBlocks(basis_gates=native_gate), - UnitarySynthesis(basis_gates=native_gate, coupling_map=coupling_map), - Optimize1qGatesDecomposition(basis=native_gate), - CommutativeCancellation(basis_gates=native_gate), - GatesInBasis(native_gate), - ConditionalController( - common.generate_translation_passmanager( - target=None, basis_gates=native_gate, coupling_map=coupling_map - ).to_flow_controller(), - condition=lambda property_set: not property_set["all_gates_in_basis"], - ), - Depth(recurse=True), - FixedPoint("depth"), - Size(recurse=True), - FixedPoint("size"), - MinimumPoint(["depth", "size"], "optimization_loop"), - ], - do_while=lambda property_set: not property_set["optimization_loop_minimum_point"], - ) -) +# register_action( +# DeviceDependentAction( +# "QiskitO3", +# CompilationOrigin.QISKIT, +# PassType.OPT, +# transpile_pass=lambda native_gate, coupling_map: [ +# Collect2qBlocks(), +# ConsolidateBlocks(basis_gates=native_gate), +# UnitarySynthesis(basis_gates=native_gate, coupling_map=coupling_map), +# Optimize1qGatesDecomposition(basis=native_gate), +# CommutativeCancellation(basis_gates=native_gate), +# GatesInBasis(native_gate), +# ConditionalController( +# common.generate_translation_passmanager( +# target=None, basis_gates=native_gate, coupling_map=coupling_map +# ).to_flow_controller(), +# condition=lambda property_set: not property_set["all_gates_in_basis"], +# ), +# Depth(recurse=True), +# FixedPoint("depth"), +# Size(recurse=True), +# FixedPoint("size"), +# MinimumPoint(["depth", "size"], "optimization_loop"), +# ], +# do_while=lambda property_set: not property_set["optimization_loop_minimum_point"], +# ) +# ) + +# register_action( +# DeviceDependentAction( +# "BQSKitO2", +# CompilationOrigin.BQSKIT, +# PassType.OPT, +# transpile_pass=lambda circuit: bqskit_compile( +# circuit, +# optimization_level=1 if os.getenv("GITHUB_ACTIONS") == "true" else 2, +# synthesis_epsilon=1e-1 if os.getenv("GITHUB_ACTIONS") == "true" else 1e-8, +# max_synthesis_size=2 if os.getenv("GITHUB_ACTIONS") == "true" else 3, +# seed=10, +# num_workers=1 if os.getenv("GITHUB_ACTIONS") == "true" else -1, +# ), +# ) +# ) register_action( DeviceDependentAction( - "BQSKitO2", - CompilationOrigin.BQSKIT, - PassType.OPT, - transpile_pass=lambda circuit: bqskit_compile( - circuit, - optimization_level=1 if os.getenv("GITHUB_ACTIONS") == "true" else 2, - synthesis_epsilon=1e-1 if os.getenv("GITHUB_ACTIONS") == "true" else 1e-8, - max_synthesis_size=2 if os.getenv("GITHUB_ACTIONS") == "true" else 3, - seed=10, - num_workers=1 if os.getenv("GITHUB_ACTIONS") == "true" else -1, - ), + "VF2PostLayout", + CompilationOrigin.QISKIT, + PassType.FINAL_OPT, + transpile_pass=lambda device: VF2PostLayout(target=device, call_limit=30000000, max_trials=250000), ) ) register_action( DeviceDependentAction( - "VF2PostLayout", + "TrivialLayout", CompilationOrigin.QISKIT, - PassType.FINAL_OPT, - transpile_pass=lambda device: VF2PostLayout(target=device, time_limit=100), + PassType.LAYOUT, + transpile_pass=lambda device: [ + TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + ], ) ) @@ -419,7 +441,7 @@ def get_openqasm_gates() -> list[str]: CompilationOrigin.QISKIT, PassType.LAYOUT, transpile_pass=lambda device: [ - VF2Layout(target=device), + VF2Layout(target=device, call_limit=30000000, max_trials=250000), ConditionalController( [ FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), @@ -433,6 +455,23 @@ def get_openqasm_gates() -> list[str]: ) ) +# register_action( +# DeviceDependentAction( +# "SabreLayout", +# CompilationOrigin.QISKIT, +# PassType.LAYOUT, +# transpile_pass=lambda device: [ +# SabreLayout( +# coupling_map=CouplingMap(device.build_coupling_map()), +# skip_routing=True, +# ), +# FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), +# EnlargeWithAncilla(), +# ApplyLayout(), +# ], +# ) +# ) + register_action( DeviceDependentAction( "RoutingPass", @@ -447,205 +486,96 @@ def get_openqasm_gates() -> list[str]: register_action( DeviceDependentAction( - "SabreMapping", + "BasicSwap", CompilationOrigin.QISKIT, - PassType.MAPPING, - stochastic=True, - # Qiskit O3 by default uses (max_iterations, layout_trials, swap_trials) = (4, 20, 20) - transpile_pass=lambda device, max_iteration=(20, 20): [ - SabreLayout( - coupling_map=CouplingMap(device.build_coupling_map()), - skip_routing=False, - layout_trials=max_iteration[0], - swap_trials=max_iteration[1], - max_iterations=4, - seed=None, - ), - ], - ) -) - -register_action( - DeviceDependentAction( - "SabreLayout+AIRouting", - CompilationOrigin.QISKIT, - PassType.MAPPING, - stochastic=True, - transpile_pass=lambda device, max_iteration=(20, 20): [ - SabreLayout( - coupling_map=CouplingMap(device.build_coupling_map()), - skip_routing=True, - layout_trials=max_iteration[0], - swap_trials=max_iteration[1], - max_iterations=4, - seed=None, - ), - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - SafeAIRouting(coupling_map=device.build_coupling_map(), optimization_level=3, layout_mode="improve"), - ], + PassType.ROUTING, + transpile_pass=lambda device: [BasicSwap(coupling_map=CouplingMap(device.build_coupling_map()))], ) ) +# register_action( +# DeviceDependentAction( +# "SabreSwap", +# CompilationOrigin.QISKIT, +# PassType.ROUTING, +# stochastic=True, +# transpile_pass=lambda device: [ +# SabreSwap(coupling_map=CouplingMap(device.build_coupling_map()), heuristic="decay") +# ], +# ) +# ) + +# register_action( +# DeviceDependentAction( +# "AIRouting", +# CompilationOrigin.QISKIT, +# PassType.ROUTING, +# stochastic=True, +# transpile_pass=lambda device: [ +# SafeAIRouting( +# coupling_map=device.build_coupling_map(), +# optimization_level=3, +# layout_mode="improve", +# local_mode=True +# ) +# ], +# ) +# ) register_action( DeviceDependentAction( - "AIRouting", + "SabreMapping", CompilationOrigin.QISKIT, PassType.MAPPING, stochastic=True, + # Qiskit O3 by default uses (max_iterations, layout_trials, swap_trials) = (4, 20, 20) transpile_pass=lambda device: [ - ### Requires a initial layout, but "optimize" mode overwrites it - TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - SafeAIRouting(coupling_map=device.build_coupling_map(), optimization_level=3, layout_mode="optimize"), - ], - ) -) - -register_action( - DeviceDependentAction( - "BQSKitMapping", - CompilationOrigin.BQSKIT, - PassType.MAPPING, - transpile_pass=lambda device: lambda bqskit_circuit: bqskit_compile( - bqskit_circuit, - model=MachineModel( - num_qudits=device.num_qubits, - gate_set=get_bqskit_native_gates(device), - coupling_graph=[(elem[0], elem[1]) for elem in device.build_coupling_map()], - ), - with_mapping=True, - optimization_level=1 if os.getenv("GITHUB_ACTIONS") == "true" else 2, - synthesis_epsilon=1e-1 if os.getenv("GITHUB_ACTIONS") == "true" else 1e-8, - max_synthesis_size=2 if os.getenv("GITHUB_ACTIONS") == "true" else 3, - seed=10, - num_workers=1 if os.getenv("GITHUB_ACTIONS") == "true" else -1, - ), - ) -) - - -register_action( - DeviceDependentAction( - name="DenseLayout+SabreSwap", - origin=CompilationOrigin.QISKIT, - pass_type=PassType.MAPPING, - stochastic=True, - transpile_pass=lambda device, max_iteration=(20, 20): [ - DenseLayout(coupling_map=CouplingMap(device.build_coupling_map())), - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - SabreSwap( - coupling_map=CouplingMap(device.build_coupling_map()), - heuristic="decay", - trials=max_iteration[1], - seed=None, - ), - ], - ) -) - -register_action( - DeviceDependentAction( - name="DenseLayout+AIRouting", - origin=CompilationOrigin.QISKIT, - pass_type=PassType.MAPPING, - stochastic=True, - transpile_pass=lambda device: [ - DenseLayout(coupling_map=CouplingMap(device.build_coupling_map())), - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - SafeAIRouting(coupling_map=device.build_coupling_map(), optimization_level=3, layout_mode="improve"), - ], - ) -) - - -register_action( - DeviceDependentAction( - name="VF2Layout+SabreSwap", - origin=CompilationOrigin.QISKIT, - pass_type=PassType.MAPPING, - stochastic=True, - transpile_pass=lambda device, max_iteration=(20, 20): [ - VF2Layout( - coupling_map=CouplingMap(device.build_coupling_map()), - target=device, - call_limit=30000000, - max_trials=250000, - ), - ConditionalController( - [ - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - ], - condition=lambda property_set: property_set["VF2Layout_stop_reason"] - == VF2LayoutStopReason.SOLUTION_FOUND, - ), - ConditionalController( - [ - TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - ], - # Run if VF2Layout did not find a solution - condition=lambda property_set: property_set["VF2Layout_stop_reason"] - != VF2LayoutStopReason.SOLUTION_FOUND, - ), - SabreSwap( + SabreLayout( coupling_map=CouplingMap(device.build_coupling_map()), - heuristic="decay", - trials=max_iteration[1], + skip_routing=False, seed=None, ), ], ) ) -register_action( - DeviceDependentAction( - name="VF2Layout+AIRouting", - origin=CompilationOrigin.QISKIT, - pass_type=PassType.MAPPING, - stochastic=True, - transpile_pass=lambda device: [ - VF2Layout( - coupling_map=CouplingMap(device.build_coupling_map()), - target=device, - call_limit=30000000, - max_trials=250000, - ), - ConditionalController( - [ - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - ], - condition=lambda property_set: property_set["VF2Layout_stop_reason"] - == VF2LayoutStopReason.SOLUTION_FOUND, - ), - ConditionalController( - [ - TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - ], - # Run if VF2Layout did not find a solution - condition=lambda property_set: property_set["VF2Layout_stop_reason"] - != VF2LayoutStopReason.SOLUTION_FOUND, - ), - SafeAIRouting(coupling_map=device.build_coupling_map(), optimization_level=3, layout_mode="improve"), - ], - ) -) +# register_action( +# DeviceDependentAction( +# "AIRouting_opt", +# CompilationOrigin.QISKIT, +# PassType.MAPPING, +# stochastic=True, +# transpile_pass=lambda device: [ +# ### Requires a initial layout, but "optimize" mode overwrites it +# TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), +# FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), +# EnlargeWithAncilla(), +# ApplyLayout(), +# SafeAIRouting(coupling_map=device.build_coupling_map(), optimization_level=3, layout_mode="optimize"), +# ], +# ) +# ) + +# register_action( +# DeviceDependentAction( +# "BQSKitMapping", +# CompilationOrigin.BQSKIT, +# PassType.MAPPING, +# transpile_pass=lambda device: lambda bqskit_circuit: bqskit_compile( +# bqskit_circuit, +# model=MachineModel( +# num_qudits=device.num_qubits, +# gate_set=get_bqskit_native_gates(device), +# coupling_graph=[(elem[0], elem[1]) for elem in device.build_coupling_map()], +# ), +# with_mapping=True, +# optimization_level=1 if os.getenv("GITHUB_ACTIONS") == "true" else 2, +# synthesis_epsilon=1e-1 if os.getenv("GITHUB_ACTIONS") == "true" else 1e-8, +# max_synthesis_size=2 if os.getenv("GITHUB_ACTIONS") == "true" else 3, +# seed=10, +# num_workers=1 if os.getenv("GITHUB_ACTIONS") == "true" else -1, +# ), +# ) +# ) register_action( DeviceDependentAction( @@ -658,22 +588,22 @@ def get_openqasm_gates() -> list[str]: ) ) -register_action( - DeviceDependentAction( - "BQSKitSynthesis", - CompilationOrigin.BQSKIT, - PassType.SYNTHESIS, - transpile_pass=lambda device: lambda bqskit_circuit: bqskit_compile( - bqskit_circuit, - model=MachineModel(bqskit_circuit.num_qudits, gate_set=get_bqskit_native_gates(device)), - optimization_level=1 if os.getenv("GITHUB_ACTIONS") == "true" else 2, - synthesis_epsilon=1e-1 if os.getenv("GITHUB_ACTIONS") == "true" else 1e-8, - max_synthesis_size=2 if os.getenv("GITHUB_ACTIONS") == "true" else 3, - seed=10, - num_workers=1 if os.getenv("GITHUB_ACTIONS") == "true" else -1, - ), - ) -) +# register_action( +# DeviceDependentAction( +# "BQSKitSynthesis", +# CompilationOrigin.BQSKIT, +# PassType.SYNTHESIS, +# transpile_pass=lambda device: lambda bqskit_circuit: bqskit_compile( +# bqskit_circuit, +# model=MachineModel(bqskit_circuit.num_qudits, gate_set=get_bqskit_native_gates(device)), +# optimization_level=1 if os.getenv("GITHUB_ACTIONS") == "true" else 2, +# synthesis_epsilon=1e-1 if os.getenv("GITHUB_ACTIONS") == "true" else 1e-8, +# max_synthesis_size=2 if os.getenv("GITHUB_ACTIONS") == "true" else 3, +# seed=10, +# num_workers=1 if os.getenv("GITHUB_ACTIONS") == "true" else -1, +# ), +# ) +# ) register_action( DeviceIndependentAction( diff --git a/src/mqt/predictor/rl/helper.py b/src/mqt/predictor/rl/helper.py index 558ba262f..f23ca3320 100644 --- a/src/mqt/predictor/rl/helper.py +++ b/src/mqt/predictor/rl/helper.py @@ -12,109 +12,22 @@ import logging from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING import numpy as np from qiskit import QuantumCircuit -from qiskit.transpiler import PassManager, Target from mqt.predictor.utils import calc_supermarq_features if TYPE_CHECKING: - from collections.abc import Callable - from numpy.random import Generator from numpy.typing import NDArray - from mqt.predictor.rl.actions import Action - - # from mqt.predictor.rl.actions import Action - import zipfile from importlib import resources logger = logging.getLogger("mqt-predictor") - -def best_of_n_passmanager( - action: Action, - device: Target, - qc: QuantumCircuit, - max_iteration: tuple[int, int] = (20, 20), - metric_fn: Callable[[QuantumCircuit], float] | None = None, -) -> tuple[QuantumCircuit, dict[str, Any]]: - """Runs the given transpile_pass multiple times and keeps the best result. - - Args: - action: The action dictionary with a 'transpile_pass' key - (lambda device -> [passes]). - device: The target backend or device. - qc: The input quantum circuit. - max_iteration: A tuple (layout_trials, routing_trials) specifying - how many times to try. - metric_fn: Optional function to score circuits; defaults to circuit depth. - - Returns: - A tuple containing the best transpiled circuit and its corresponding - property set. - """ - best_val = None - best_result = None - best_property_set = None - - if callable(action.transpile_pass): - try: - if action.name == "SabreLayout+AIRouting": - all_passes = action.transpile_pass(device, max_iteration) - else: - all_passes = action.transpile_pass(device) - except TypeError as e: - msg = f"Error calling transpile_pass for {action.name}: {e}" - raise ValueError(msg) from e - else: - all_passes = action.transpile_pass - - if not isinstance(all_passes, list): - msg = f"Expected list of passes, got {type(all_passes)}" - raise TypeError(msg) - - layout_passes = all_passes[:-1] - routing_pass = all_passes[-1:] - - # Run layout once - layout_pm = PassManager(layout_passes) - try: - layouted_qc = layout_pm.run(qc) - layout_props = dict(layout_pm.property_set) - except Exception: - return qc, {} - - # Run routing multiple times and optimize for the given metric - for i in range(max_iteration[1]): - pm = PassManager(routing_pass) - pm.property_set.update(layout_props) - try: - out_circ = pm.run(layouted_qc) - prop_set = dict(pm.property_set) - - val = metric_fn(out_circ) if metric_fn else out_circ.depth() - if best_val is None or val < best_val: - best_val = val - best_result = out_circ - best_property_set = prop_set - if best_val == 0: - break - except Exception as e: - print(f"[Routing] Trial {i + 1} failed: {e}") - continue - if best_result is not None: - if best_property_set is None: - best_property_set = {} - return best_result, best_property_set - print("All mapping attempts failed; returning original circuit.") - return qc, {} - - def get_state_sample(max_qubits: int, path_training_circuits: Path, rng: Generator) -> tuple[QuantumCircuit, str]: """Returns a random quantum circuit from the training circuits folder. diff --git a/src/mqt/predictor/rl/predictor.py b/src/mqt/predictor/rl/predictor.py index 3ffe07733..558e43021 100644 --- a/src/mqt/predictor/rl/predictor.py +++ b/src/mqt/predictor/rl/predictor.py @@ -51,7 +51,7 @@ def __init__( def compile_as_predicted( self, qc: QuantumCircuit, - ) -> tuple[QuantumCircuit, list[str]]: + ) -> tuple[QuantumCircuit, float, list[str]]: """Compiles a given quantum circuit such that the given figure of merit is maximized by using the respectively trained optimized compiler. Arguments: @@ -76,10 +76,19 @@ def compile_as_predicted( action = int(action) action_item = self.env.action_set[action] used_compilation_passes.append(action_item.name) - obs, _reward_val, terminated, truncated, _info = self.env.step(action) + # if action_item.name == "terminate": + # swap_count = self.env.state.count_ops().get("swap", 0) + # depth = self.env.state.depth() + # critical_depth = reward.crit_depth(self.env.state) + # esp= reward.estimated_success_probability(self.env.state, self.env.device) + obs, reward_val, terminated, truncated, _info = self.env.step(action) if not self.env.error_occurred: - return self.env.state, used_compilation_passes + return ( + self.env.state, + reward_val, + used_compilation_passes, + ) # swap_count, depth, critical_depth, esp msg = "Error occurred during compilation." raise RuntimeError(msg) @@ -110,7 +119,7 @@ def train_model( n_steps = 2048 n_epochs = 10 batch_size = 64 - progress_bar = True + progress_bar = False logger.debug("Start training for: " + self.figure_of_merit + " on " + self.device_name) model = MaskablePPO( @@ -181,4 +190,5 @@ def rl_compile( else: predictor = predictor_singleton - return predictor.compile_as_predicted(qc) + qc, _, info = predictor.compile_as_predicted(qc) + return qc, info diff --git a/src/mqt/predictor/rl/predictorenv.py b/src/mqt/predictor/rl/predictorenv.py index a3cbae20b..a7239c232 100644 --- a/src/mqt/predictor/rl/predictorenv.py +++ b/src/mqt/predictor/rl/predictorenv.py @@ -42,6 +42,7 @@ from qiskit.passmanager.flow_controllers import DoWhileController from qiskit.transpiler import CouplingMap, PassManager, Target, TranspileLayout from qiskit.transpiler.passes import CheckMap, GatesInBasis +from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason from mqt.predictor.hellinger import get_hellinger_model_path from mqt.predictor.reward import ( @@ -60,7 +61,6 @@ get_openqasm_gates, ) from mqt.predictor.rl.helper import ( - best_of_n_passmanager, create_feature_dict, get_path_training_circuits, get_state_sample, @@ -104,6 +104,7 @@ def __init__( self.actions_mapping_indices = [] self.actions_opt_indices = [] self.actions_final_optimization_indices = [] + self.actions_preserving_indices = [] self.used_actions: list[str] = [] self.device = device @@ -123,6 +124,8 @@ def __init__( for elem in action_dict[PassType.OPT]: self.action_set[index] = elem self.actions_opt_indices.append(index) + if getattr(elem, "preserve", False): + self.actions_preserving_indices.append(index) index += 1 for elem in action_dict[PassType.LAYOUT]: self.action_set[index] = elem @@ -209,6 +212,7 @@ def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any if action == self.action_terminate_index: reward_val = self.calculate_reward() + logger.info(f"Fidelity: {reward_val}") done = True else: reward_val = 0 @@ -334,83 +338,57 @@ def apply_action(self, action_index: int) -> QuantumCircuit | None: return self._apply_bqskit_action(action, action_index) msg = f"Origin {action.origin} not supported." raise ValueError(msg) - + def _apply_qiskit_action(self, action: Action, action_index: int) -> QuantumCircuit: - if getattr(action, "stochastic", False): - - def metric_fn(circ: QuantumCircuit) -> float: - return float(circ.count_ops().get("swap", 0)) - - max_iteration = self.max_iter - if "Sabre" in action.name and "AIRouting" not in action.name: - # Internal trials for Sabre - assert callable(action.transpile_pass) - transpile_pass = action.transpile_pass(self.device, max_iteration) - pm = PassManager(transpile_pass) - altered_qc = pm.run(self.state) - pm_property_set = dict(pm.property_set) - elif "AIRouting" in action.name: - # Run AIRouting in custom loop - altered_qc, pm_property_set = best_of_n_passmanager( - action, - self.device, - self.state, - max_iteration=max_iteration, - metric_fn=metric_fn, - ) + if action.name in ["QiskitO3", "Opt2qBlocks"] and isinstance(action, DeviceDependentAction): + passes = action.transpile_pass( + self.device.operation_names, + CouplingMap(self.device.build_coupling_map()) if self.layout else None, + ) + if action.name == "QiskitO3": + pm = PassManager([DoWhileController(passes, do_while=action.do_while)]) else: - msg = f"Unknown stochastic action: {action.name}" - raise ValueError(msg) - + pm = PassManager(passes) else: - if action.name in ["QiskitO3", "Opt2qBlocks"] and isinstance(action, DeviceDependentAction): - passes = action.transpile_pass( - self.device.operation_names, - CouplingMap(self.device.build_coupling_map()) if self.layout else None, - ) - if action.name == "QiskitO3": - pm = PassManager([DoWhileController(passes, do_while=action.do_while)]) - else: - pm = PassManager(passes) - altered_qc = pm.run(self.state) - pm_property_set = dict(pm.property_set) if hasattr(pm, "property_set") else {} - else: - transpile_pass = ( - action.transpile_pass(self.device) if callable(action.transpile_pass) else action.transpile_pass - ) - pm = PassManager(transpile_pass) - altered_qc = pm.run(self.state) - pm_property_set = dict(pm.property_set) if hasattr(pm, "property_set") else {} + transpile_pass = ( + action.transpile_pass(self.device) if callable(action.transpile_pass) else action.transpile_pass + ) + pm = PassManager(transpile_pass) - if action_index in (self.actions_mapping_indices + self.actions_final_optimization_indices): - altered_qc = self._handle_qiskit_layout_postprocessing(action, pm_property_set, altered_qc) + altered_qc = pm.run(self.state) + + if action_index in ( + self.actions_layout_indices + self.actions_mapping_indices + self.actions_final_optimization_indices + ): + altered_qc = self._handle_qiskit_layout_postprocessing(action, pm, altered_qc) + + elif action_index in self.actions_routing_indices and self.layout: + self.layout.final_layout = pm.property_set["final_layout"] return altered_qc def _handle_qiskit_layout_postprocessing( - self, - action: Action, - pm_property_set: dict[str, Any], - altered_qc: QuantumCircuit, + self, action: Action, pm: PassManager, altered_qc: QuantumCircuit ) -> QuantumCircuit: if action.name == "VF2PostLayout": - assert pm_property_set["VF2PostLayout_stop_reason"] is not None - post_layout = pm_property_set.get("post_layout") + assert pm.property_set["VF2PostLayout_stop_reason"] is not None + post_layout = pm.property_set["post_layout"] if post_layout: altered_qc, _ = postprocess_vf2postlayout(altered_qc, post_layout, self.layout) + elif action.name == "VF2Layout": + if pm.property_set["VF2Layout_stop_reason"] == VF2LayoutStopReason.SOLUTION_FOUND: + assert pm.property_set["layout"] + else: + assert pm.property_set["layout"] - layout = pm_property_set.get("layout") - if layout: + if pm.property_set["layout"]: self.layout = TranspileLayout( - initial_layout=layout, - input_qubit_mapping=pm_property_set["original_qubit_indices"], - final_layout=pm_property_set["final_layout"], + initial_layout=pm.property_set["layout"], + input_qubit_mapping=pm.property_set["original_qubit_indices"], + final_layout=pm.property_set["final_layout"], _output_qubit_list=altered_qc.qubits, _input_qubit_count=self.num_qubits_uncompiled_circuit, ) - - if self.layout is not None and pm_property_set.get("final_layout"): - self.layout.final_layout = pm_property_set["final_layout"] return altered_qc def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircuit: @@ -479,11 +457,20 @@ def determine_valid_actions_for_state(self) -> list[int]: mapped = check_mapping.property_set["is_swap_mapped"] if not only_nat_gates: # not native gates yet - return self.actions_synthesis_indices + self.actions_opt_indices + if not mapped: + return self.actions_synthesis_indices + self.actions_opt_indices + else: + return self.actions_synthesis_indices + self.actions_preserving_indices if mapped and self.layout is not None: # The circuit is correctly mapped return [self.action_terminate_index, *self.actions_opt_indices, *self.actions_final_optimization_indices] - # The circuit is not mapped yet - # Or the circuit was mapped but some optimization actions change its structure and the circuit is again unmapped - # In this case, re-do mapping completely as the previous layout is not optimal on the new structure - return self.actions_mapping_indices + self.actions_opt_indices + + if self.layout is not None: + # The circuit is not yet mapped but a layout is set. + return self.actions_routing_indices + else: + # The circuit already fulfils coupling map but no layout is assigned, could explore better layout options + if mapped: + return self.actions_preserving_indices + self.actions_layout_indices + self.actions_mapping_indices + else: + return self.actions_opt_indices + self.actions_layout_indices + self.actions_mapping_indices diff --git a/tests/compilation/test_predictor_rl.py b/tests/compilation/test_predictor_rl.py index 9e4c1ded2..d90423ab1 100644 --- a/tests/compilation/test_predictor_rl.py +++ b/tests/compilation/test_predictor_rl.py @@ -14,6 +14,7 @@ from pathlib import Path import pytest +from qiskit_ibm_runtime import QiskitRuntimeService from mqt.bench import BenchmarkLevel, get_benchmark from mqt.bench.targets import get_device from qiskit.circuit.library import CXGate @@ -30,7 +31,7 @@ register_action, remove_action, ) -from mqt.predictor.rl.helper import create_feature_dict, get_path_trained_model +from mqt.predictor.rl.helper import create_feature_dict def test_predictor_env_reset_from_string() -> None: @@ -68,30 +69,45 @@ def test_qcompile_with_newly_trained_models() -> None: Important: Those trained models are used in later tests and must not be deleted. To test ESP as well, training must be done with a device that provides all relevant information (i.e. T1, T2 and gate times). """ + # figure_of_merit = "expected_fidelity" + # device = get_device("ibm_eagle_127") + # qc = get_benchmark("ghz", BenchmarkLevel.INDEP, 20) + # predictor = Predictor(figure_of_merit=figure_of_merit, device=device) + + # model_name = "model_" + figure_of_merit + "_" + device.description + # model_path = Path(get_path_trained_model() / (model_name + ".zip")) + # if not model_path.exists(): + # with pytest.raises( + # FileNotFoundError, + # match=re.escape( + # "The RL model 'model_expected_fidelity_ibm_falcon_127' is not trained yet. Please train the model before using it." + # ), + # ): + # rl_compile(qc, device=device, figure_of_merit=figure_of_merit) figure_of_merit = "expected_fidelity" - device = get_device("ibm_falcon_127") - qc = get_benchmark("ghz", BenchmarkLevel.INDEP, 3) - predictor = Predictor(figure_of_merit=figure_of_merit, device=device) - - model_name = "model_" + figure_of_merit + "_" + device.description - model_path = Path(get_path_trained_model() / (model_name + ".zip")) - if not model_path.exists(): - with pytest.raises( - FileNotFoundError, - match=re.escape( - "The RL model 'model_expected_fidelity_ibm_falcon_127' is not trained yet. Please train the model before using it." - ), - ): - rl_compile(qc, device=device, figure_of_merit=figure_of_merit) + + api_token = "f--aKMqeiWLjkaazIlxEFVKxJ5cVgAqhxbP9K87iPbP2" + available_devices = ["ibm_brisbane", "ibm_torino"] + device = available_devices[0] + + service = QiskitRuntimeService(channel="ibm_cloud", token=api_token) + backend = service.backend(device) + backend.target.description = "ibm_brisbane" # HACK + print(backend.target) + predictor = Predictor(figure_of_merit=figure_of_merit, device=backend.target) + qc = get_benchmark("ghz", BenchmarkLevel.INDEP, 17, target=backend.target) predictor.train_model( - timesteps=100, - test=True, + timesteps=100000, + test=False, ) - qc_compiled, compilation_information = rl_compile(qc, device=device, figure_of_merit=figure_of_merit) + qc_compiled, reward, compilation_information = rl_compile( + qc, device=backend.target, figure_of_merit=figure_of_merit + ) + print(f"Fidelity: {reward}") - check_nat_gates = GatesInBasis(basis_gates=device.operation_names) + check_nat_gates = GatesInBasis(basis_gates=backend.target.operation_names) check_nat_gates(qc_compiled) only_nat_gates = check_nat_gates.property_set["all_gates_in_basis"] From 6d2733f231599d811b295d2e3f13cc79467496c6 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Fri, 29 Aug 2025 16:39:30 +0200 Subject: [PATCH 27/57] Add new actions --- tests/compilation/test_predictor_rl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/compilation/test_predictor_rl.py b/tests/compilation/test_predictor_rl.py index d90423ab1..43211af0a 100644 --- a/tests/compilation/test_predictor_rl.py +++ b/tests/compilation/test_predictor_rl.py @@ -14,13 +14,13 @@ from pathlib import Path import pytest -from qiskit_ibm_runtime import QiskitRuntimeService from mqt.bench import BenchmarkLevel, get_benchmark from mqt.bench.targets import get_device from qiskit.circuit.library import CXGate from qiskit.qasm2 import dump from qiskit.transpiler import InstructionProperties, Target from qiskit.transpiler.passes import GatesInBasis +from qiskit_ibm_runtime import QiskitRuntimeService from mqt.predictor.rl import Predictor, rl_compile from mqt.predictor.rl.actions import ( @@ -86,7 +86,7 @@ def test_qcompile_with_newly_trained_models() -> None: # rl_compile(qc, device=device, figure_of_merit=figure_of_merit) figure_of_merit = "expected_fidelity" - api_token = "f--aKMqeiWLjkaazIlxEFVKxJ5cVgAqhxbP9K87iPbP2" + api_token = "" available_devices = ["ibm_brisbane", "ibm_torino"] device = available_devices[0] From 878185af1099cfc79b03542da3c2548443381981 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Wed, 3 Sep 2025 13:21:10 +0200 Subject: [PATCH 28/57] Add evaluation code for baseline model --- src/mqt/predictor/reward.py | 16 ++++-- src/mqt/predictor/rl/actions.py | 58 ++++++++++--------- src/mqt/predictor/rl/helper.py | 9 ++- src/mqt/predictor/rl/predictor.py | 72 +++++++++++++++++------- src/mqt/predictor/rl/predictorenv.py | 77 ++++++++++++++++++++------ tests/compilation/test_predictor_rl.py | 59 ++++++++++++++++++-- 6 files changed, 216 insertions(+), 75 deletions(-) diff --git a/src/mqt/predictor/reward.py b/src/mqt/predictor/reward.py index 38be96bce..3cc726eed 100644 --- a/src/mqt/predictor/reward.py +++ b/src/mqt/predictor/reward.py @@ -73,7 +73,6 @@ def expected_fidelity(qc: QuantumCircuit, device: Target, precision: int = 10) - # try flipped orientation specific_fidelity = 1 - device[gate_type][second_qubit_idx, first_qubit_idx].error - res *= specific_fidelity return float(np.round(res, precision).item()) @@ -146,7 +145,10 @@ def estimated_success_probability(qc: QuantumCircuit, device: Target, precision: else: # multi-qubit gate second_qubit_idx = calc_qubit_index(qargs, qc.qregs, 1) active_qubits.add(second_qubit_idx) - duration = device[gate_type][first_qubit_idx, second_qubit_idx].duration + try: + duration = device[gate_type][first_qubit_idx, second_qubit_idx].duration + except KeyError: + duration = device[gate_type][second_qubit_idx, first_qubit_idx].duration op_times.append((gate_type, [first_qubit_idx, second_qubit_idx], duration, "s")) exec_time_per_qubit[first_qubit_idx] += duration exec_time_per_qubit[second_qubit_idx] += duration @@ -187,6 +189,7 @@ def estimated_success_probability(qc: QuantumCircuit, device: Target, precision: res = 1.0 for instr in scheduled_circ.data: + instruction = instr.operation qargs = instr.qubits gate_type = instruction.name @@ -199,6 +202,7 @@ def estimated_success_probability(qc: QuantumCircuit, device: Target, precision: if len(qargs) == 1: if gate_type == "measure": + res *= 1 - device[gate_type][first_qubit_idx,].error continue if gate_type == "delay": @@ -207,16 +211,18 @@ def estimated_success_probability(qc: QuantumCircuit, device: Target, precision: # only consider active qubits if first_qubit_idx not in active_qubits: continue - + + dt=5e-10 # discrete time unit used in duration res *= np.exp( - -instruction.duration + -instruction.duration * dt / min(device.qubit_properties[first_qubit_idx].t1, device.qubit_properties[first_qubit_idx].t2) ) continue res *= 1 - device[gate_type][first_qubit_idx,].error + else: second_qubit_idx = calc_qubit_index(qargs, qc.qregs, 1) - res *= 1 - device[gate_type][first_qubit_idx, second_qubit_idx].error + res *= 1 - device[gate_type][first_qubit_idx, second_qubit_idx].error if qiskit_version >= "2.0.0": for i in range(device.num_qubits): diff --git a/src/mqt/predictor/rl/actions.py b/src/mqt/predictor/rl/actions.py index a8975426f..8c73f6b70 100644 --- a/src/mqt/predictor/rl/actions.py +++ b/src/mqt/predictor/rl/actions.py @@ -10,14 +10,11 @@ from __future__ import annotations -import os from collections import defaultdict from dataclasses import dataclass from enum import Enum from typing import TYPE_CHECKING, Any -from bqskit import MachineModel -from bqskit import compile as bqskit_compile from pytket.architecture import Architecture from pytket.passes import ( CliffordSimp, @@ -58,31 +55,23 @@ CommutativeInverseCancellation, ConsolidateBlocks, DenseLayout, - Depth, EnlargeWithAncilla, - FixedPoint, FullAncillaAllocation, - GatesInBasis, InverseCancellation, - MinimumPoint, Optimize1qGatesDecomposition, OptimizeCliffords, RemoveDiagonalGatesBeforeMeasure, SabreLayout, - SabreSwap, - Size, TrivialLayout, UnitarySynthesis, VF2Layout, VF2PostLayout, ) from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason -from qiskit.transpiler.preset_passmanagers import common from qiskit_ibm_transpiler.ai.routing import AIRouting from mqt.predictor.rl.parsing import ( PreProcessTKETRoutingAfterQiskitLayout, - get_bqskit_native_gates, ) if TYPE_CHECKING: @@ -230,13 +219,22 @@ def get_openqasm_gates() -> list[str]: ] +# register_action( +# DeviceIndependentAction( +# "Optimize1qGatesDecomposition", +# CompilationOrigin.QISKIT, +# PassType.OPT, +# preserve=True, +# transpile_pass=lambda device: [Optimize1qGatesDecomposition(basis=device.operation_names)], +# ) +# ) + register_action( DeviceIndependentAction( "Optimize1qGatesDecomposition", CompilationOrigin.QISKIT, PassType.OPT, - preserve=True, - transpile_pass= lambda device: [Optimize1qGatesDecomposition(basis=device.operation_names)], + [Optimize1qGatesDecomposition()], ) ) @@ -304,17 +302,26 @@ def get_openqasm_gates() -> list[str]: ) ) +# register_action( +# DeviceDependentAction( +# "Opt2qBlocks", +# CompilationOrigin.QISKIT, +# PassType.OPT, +# transpile_pass=lambda native_gate, coupling_map: [ +# Collect2qBlocks(), +# ConsolidateBlocks(basis_gates=native_gate), +# UnitarySynthesis(basis_gates=native_gate, coupling_map=coupling_map), +# ], +# preserve=True, +# ) +# ) + register_action( - DeviceDependentAction( + DeviceIndependentAction( "Opt2qBlocks", CompilationOrigin.QISKIT, PassType.OPT, - transpile_pass=lambda native_gate, coupling_map: [ - Collect2qBlocks(), - ConsolidateBlocks(basis_gates=native_gate), - UnitarySynthesis(basis_gates=native_gate, coupling_map=coupling_map), - ], - preserve=True, + [Collect2qBlocks(), ConsolidateBlocks(), UnitarySynthesis()], ) ) @@ -403,7 +410,7 @@ def get_openqasm_gates() -> list[str]: "VF2PostLayout", CompilationOrigin.QISKIT, PassType.FINAL_OPT, - transpile_pass=lambda device: VF2PostLayout(target=device, call_limit=30000000, max_trials=250000), + transpile_pass=lambda device: VF2PostLayout(target=device), ) ) @@ -441,7 +448,7 @@ def get_openqasm_gates() -> list[str]: CompilationOrigin.QISKIT, PassType.LAYOUT, transpile_pass=lambda device: [ - VF2Layout(target=device, call_limit=30000000, max_trials=250000), + VF2Layout(target=device), ConditionalController( [ FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), @@ -462,7 +469,7 @@ def get_openqasm_gates() -> list[str]: # PassType.LAYOUT, # transpile_pass=lambda device: [ # SabreLayout( -# coupling_map=CouplingMap(device.build_coupling_map()), +# coupling_map=CouplingMap(device.build_coupling_map()), # skip_routing=True, # ), # FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), @@ -514,7 +521,7 @@ def get_openqasm_gates() -> list[str]: # SafeAIRouting( # coupling_map=device.build_coupling_map(), # optimization_level=3, -# layout_mode="improve", +# layout_mode="improve", # local_mode=True # ) # ], @@ -532,7 +539,6 @@ def get_openqasm_gates() -> list[str]: SabreLayout( coupling_map=CouplingMap(device.build_coupling_map()), skip_routing=False, - seed=None, ), ], ) @@ -540,7 +546,7 @@ def get_openqasm_gates() -> list[str]: # register_action( # DeviceDependentAction( -# "AIRouting_opt", +# "AIRouting_opt", # CompilationOrigin.QISKIT, # PassType.MAPPING, # stochastic=True, diff --git a/src/mqt/predictor/rl/helper.py b/src/mqt/predictor/rl/helper.py index f23ca3320..c3b8ead5e 100644 --- a/src/mqt/predictor/rl/helper.py +++ b/src/mqt/predictor/rl/helper.py @@ -11,6 +11,7 @@ from __future__ import annotations import logging +import ctypes from pathlib import Path from typing import TYPE_CHECKING @@ -28,6 +29,7 @@ logger = logging.getLogger("mqt-predictor") + def get_state_sample(max_qubits: int, path_training_circuits: Path, rng: Generator) -> tuple[QuantumCircuit, str]: """Returns a random quantum circuit from the training circuits folder. @@ -88,7 +90,6 @@ def create_feature_dict(qc: QuantumCircuit) -> dict[str, int | NDArray[np.float6 feature_dict["entanglement_ratio"] = np.array([supermarq_features.entanglement_ratio], dtype=np.float32) feature_dict["parallelism"] = np.array([supermarq_features.parallelism], dtype=np.float32) feature_dict["liveness"] = np.array([supermarq_features.liveness], dtype=np.float32) - return feature_dict @@ -105,3 +106,9 @@ def get_path_trained_model() -> Path: def get_path_training_circuits() -> Path: """Returns the path to the training circuits folder used for RL training.""" return get_path_training_data() / "training_circuits" + +def trim_memory() -> None: + try: + ctypes.CDLL("libc.so.6").malloc_trim(0) + except Exception as e: + pass # Not available on all platforms diff --git a/src/mqt/predictor/rl/predictor.py b/src/mqt/predictor/rl/predictor.py index 558e43021..39ead54f2 100644 --- a/src/mqt/predictor/rl/predictor.py +++ b/src/mqt/predictor/rl/predictor.py @@ -13,6 +13,8 @@ import logging from pathlib import Path from typing import TYPE_CHECKING +from collections import deque +from copy import deepcopy from sb3_contrib import MaskablePPO from sb3_contrib.common.maskable.policies import MaskableMultiInputActorCriticPolicy @@ -21,12 +23,13 @@ from mqt.predictor.rl.helper import get_path_trained_model, logger from mqt.predictor.rl.predictorenv import PredictorEnv +from mqt.predictor.reward import figure_of_merit, crit_depth, estimated_success_probability if TYPE_CHECKING: from qiskit import QuantumCircuit from qiskit.transpiler import Target - from mqt.predictor.reward import figure_of_merit + from mqt.predictor.reward import figure_of_merit, crit_depth, estimated_success_probability class Predictor: @@ -70,17 +73,39 @@ def compile_as_predicted( used_compilation_passes = [] terminated = False truncated = False + recent_actions = deque(maxlen=8) + blocked_actions = set() while not (terminated or truncated): action_masks = get_action_masks(self.env) - action, _ = trained_rl_model.predict(obs, action_masks=action_masks) + if blocked_actions: + action_masks = deepcopy(action_masks) + for idx in blocked_actions: + action_masks[idx] = False + action, _ = trained_rl_model.predict(obs, action_masks=action_masks, deterministic=True) action = int(action) + + recent_actions.append(action) + + max_cycle_length = 4 + + def is_cycle(lst, k): + if len(lst) < 2*k: + return False + return lst[-2*k:-k] == lst[-k:] + + for k in range(3, max_cycle_length+1): + if is_cycle(list(recent_actions), k): + print(f"Avoiding {k}-cycle infinite loop pattern") + for cyc_action in set(list(recent_actions)[-k:]): + blocked_actions.add(cyc_action) + break + action_item = self.env.action_set[action] used_compilation_passes.append(action_item.name) - # if action_item.name == "terminate": - # swap_count = self.env.state.count_ops().get("swap", 0) - # depth = self.env.state.depth() - # critical_depth = reward.crit_depth(self.env.state) - # esp= reward.estimated_success_probability(self.env.state, self.env.device) + if action_item.name == "terminate": + depth = self.env.state.depth() + critical_depth = crit_depth(self.env.state) + esp= estimated_success_probability(self.env.state, self.env.device) obs, reward_val, terminated, truncated, _info = self.env.step(action) if not self.env.error_occurred: @@ -88,7 +113,7 @@ def compile_as_predicted( self.env.state, reward_val, used_compilation_passes, - ) # swap_count, depth, critical_depth, esp + depth, critical_depth, esp) msg = "Error occurred during compilation." raise RuntimeError(msg) @@ -122,20 +147,27 @@ def train_model( progress_bar = False logger.debug("Start training for: " + self.figure_of_merit + " on " + self.device_name) - model = MaskablePPO( - MaskableMultiInputActorCriticPolicy, - self.env, - verbose=verbose, - tensorboard_log="./" + model_name + "_" + self.figure_of_merit + "_" + self.device_name, - gamma=0.98, - n_steps=n_steps, - batch_size=batch_size, - n_epochs=n_epochs, - ) + # model = MaskablePPO( + # MaskableMultiInputActorCriticPolicy, + # self.env, + # verbose=verbose, + # tensorboard_log="./" + model_name + "_" + self.figure_of_merit + "_" + self.device_name, + # gamma=0.98, + # n_steps=n_steps, + # batch_size=batch_size, + # n_epochs=n_epochs, + # ) + model = MaskablePPO.load( + get_path_trained_model() / "model_expected_fidelity_ibm_brisbane.zip", + env=self.env, + verbose=verbose, + device="cuda", + ) # Training Loop: In each iteration, the agent collects n_steps steps (rollout), # updates the policy for n_epochs, and then repeats the process until total_timesteps steps have been taken. model.learn(total_timesteps=timesteps, progress_bar=progress_bar) model.save(get_path_trained_model() / (model_name + "_" + self.figure_of_merit + "_" + self.device_name)) + print("Model saved") def load_model(model_name: str) -> MaskablePPO: @@ -190,5 +222,5 @@ def rl_compile( else: predictor = predictor_singleton - qc, _, info = predictor.compile_as_predicted(qc) - return qc, info + qc, reward, info, depth, critical_depth, esp = predictor.compile_as_predicted(qc) + return qc, reward, info, depth, critical_depth, esp diff --git a/src/mqt/predictor/rl/predictorenv.py b/src/mqt/predictor/rl/predictorenv.py index a7239c232..f300fda0d 100644 --- a/src/mqt/predictor/rl/predictorenv.py +++ b/src/mqt/predictor/rl/predictorenv.py @@ -12,6 +12,7 @@ import logging import sys +import gc from typing import TYPE_CHECKING, Any if sys.version_info >= (3, 11) and TYPE_CHECKING: # pragma: no cover @@ -64,6 +65,7 @@ create_feature_dict, get_path_training_circuits, get_state_sample, + trim_memory ) from mqt.predictor.rl.parsing import ( final_layout_bqskit_to_qiskit, @@ -166,7 +168,7 @@ def __init__( self.rng = np.random.default_rng(10) spaces = { - "num_qubits": Discrete(128), + "num_qubits": Discrete(134), "depth": Discrete(1000000), "program_communication": Box(low=0, high=1, shape=(1,), dtype=np.float32), "critical_depth": Box(low=0, high=1, shape=(1,), dtype=np.float32), @@ -192,8 +194,16 @@ def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any """ self.used_actions.append(str(self.action_set[action].name)) logger.info(f"Applying: {self.action_set[action].name!s}") + if self.num_steps > 70: + return ( + create_feature_dict(self.state), + 0, + True, + False, + {}, + ) altered_qc = self.apply_action(action) - if not altered_qc: + if not altered_qc : return ( create_feature_dict(self.state), 0, @@ -203,6 +213,9 @@ def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any ) self.state: QuantumCircuit = altered_qc + + del altered_qc + gc.collect() self.num_steps += 1 self.valid_actions = self.determine_valid_actions_for_state() @@ -223,13 +236,18 @@ def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any temp_circ = self.state.decompose(gates_to_decompose="unitary") # qiskit fallback to ['id', 'u1', 'u2', 'u3', 'cx'] by default according to https://quantum.cloud.ibm.com/docs/en/api/qiskit/0.24/transpiler self.state = transpile(temp_circ, basis_gates=get_openqasm_gates(), optimization_level=0) + del temp_circ + gc.collect() elif self.state.count_ops().get("clifford"): temp_circ = self.state.decompose(gates_to_decompose="clifford") self.state = transpile(temp_circ, basis_gates=get_openqasm_gates(), optimization_level=0) + del temp_circ + gc.collect() self.state._layout = self.layout # noqa: SLF001 - obs = create_feature_dict(self.state) - return obs, reward_val, done, False, {} + #obs = create_feature_dict(self.state) + trim_memory() + return create_feature_dict(self.state), reward_val, done, False, {} def calculate_reward(self) -> float: """Calculates and returns the reward for the current state.""" @@ -283,6 +301,7 @@ def reset( self.num_qubits_uncompiled_circuit = self.state.num_qubits self.has_parameterized_gates = len(self.state.parameters) > 0 + trim_memory() return create_feature_dict(self.state), {} def action_masks(self) -> list[bool]: @@ -337,8 +356,11 @@ def apply_action(self, action_index: int) -> QuantumCircuit | None: if action.origin == CompilationOrigin.BQSKIT: return self._apply_bqskit_action(action, action_index) msg = f"Origin {action.origin} not supported." + + gc.collect() + trim_memory() raise ValueError(msg) - + def _apply_qiskit_action(self, action: Action, action_index: int) -> QuantumCircuit: if action.name in ["QiskitO3", "Opt2qBlocks"] and isinstance(action, DeviceDependentAction): passes = action.transpile_pass( @@ -408,6 +430,7 @@ def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircui assert self.layout is not None self.layout.final_layout = final_layout_pytket_to_qiskit(tket_qc, altered_qc) # Decompose to the allowed gates (by default generic u gates are used) + del tket_qc return transpile(altered_qc, basis_gates=get_openqasm_gates(), optimization_level=0) def _apply_bqskit_action(self, action: Action, action_index: int) -> QuantumCircuit: @@ -456,21 +479,41 @@ def determine_valid_actions_for_state(self) -> list[int]: check_mapping(self.state) mapped = check_mapping.property_set["is_swap_mapped"] + logger.info(f"Mapped:{mapped}") + if not only_nat_gates: # not native gates yet - if not mapped: + if not mapped: return self.actions_synthesis_indices + self.actions_opt_indices - else: - return self.actions_synthesis_indices + self.actions_preserving_indices + return self.actions_synthesis_indices + self.actions_preserving_indices if mapped and self.layout is not None: # The circuit is correctly mapped - return [self.action_terminate_index, *self.actions_opt_indices, *self.actions_final_optimization_indices] - - if self.layout is not None: + return [self.action_terminate_index, *self.actions_opt_indices] #*self.actions_final_optimization_indices] + + if self.layout is not None: # The circuit is not yet mapped but a layout is set. return self.actions_routing_indices - else: - # The circuit already fulfils coupling map but no layout is assigned, could explore better layout options - if mapped: - return self.actions_preserving_indices + self.actions_layout_indices + self.actions_mapping_indices - else: - return self.actions_opt_indices + self.actions_layout_indices + self.actions_mapping_indices + # The circuit already fulfils coupling map but no layout is assigned, could explore better layout options + if mapped: + return self.actions_preserving_indices + self.actions_layout_indices + self.actions_mapping_indices + return self.actions_opt_indices + self.actions_layout_indices + self.actions_mapping_indices + + + + # if not only_nat_gates: + # actions = self.actions_synthesis_indices + self.actions_opt_indices + # if self.layout is not None: + # actions += self.actions_routing_indices + # return actions + + # check_mapping = CheckMap(coupling_map=self.device.build_coupling_map()) + # check_mapping(self.state) + # mapped = check_mapping.property_set["is_swap_mapped"] + + # if mapped and self.layout is not None: # The circuit is correctly mapped. + # return [self.action_terminate_index, *self.actions_opt_indices] + + # if self.layout is not None: # The circuit is not yet mapped but a layout is set. + # return self.actions_routing_indices + + # # No layout applied yet + # return self.actions_mapping_indices + self.actions_layout_indices + self.actions_opt_indices diff --git a/tests/compilation/test_predictor_rl.py b/tests/compilation/test_predictor_rl.py index 43211af0a..9f1205a57 100644 --- a/tests/compilation/test_predictor_rl.py +++ b/tests/compilation/test_predictor_rl.py @@ -12,15 +12,16 @@ import re from pathlib import Path +import pandas as pd import pytest +from qiskit_ibm_runtime import QiskitRuntimeService from mqt.bench import BenchmarkLevel, get_benchmark from mqt.bench.targets import get_device from qiskit.circuit.library import CXGate from qiskit.qasm2 import dump from qiskit.transpiler import InstructionProperties, Target from qiskit.transpiler.passes import GatesInBasis -from qiskit_ibm_runtime import QiskitRuntimeService from mqt.predictor.rl import Predictor, rl_compile from mqt.predictor.rl.actions import ( @@ -31,7 +32,8 @@ register_action, remove_action, ) -from mqt.predictor.rl.helper import create_feature_dict +from mqt.predictor.rl.helper import create_feature_dict, get_path_training_circuits +from qiskit import QuantumCircuit def test_predictor_env_reset_from_string() -> None: @@ -93,19 +95,18 @@ def test_qcompile_with_newly_trained_models() -> None: service = QiskitRuntimeService(channel="ibm_cloud", token=api_token) backend = service.backend(device) backend.target.description = "ibm_brisbane" # HACK - print(backend.target) + print(backend.configuration().dt) predictor = Predictor(figure_of_merit=figure_of_merit, device=backend.target) qc = get_benchmark("ghz", BenchmarkLevel.INDEP, 17, target=backend.target) predictor.train_model( - timesteps=100000, + timesteps=30000, test=False, ) - qc_compiled, reward, compilation_information = rl_compile( + qc_compiled, compilation_information = rl_compile( qc, device=backend.target, figure_of_merit=figure_of_merit ) - print(f"Fidelity: {reward}") check_nat_gates = GatesInBasis(basis_gates=backend.target.operation_names) check_nat_gates(qc_compiled) @@ -152,3 +153,49 @@ def test_register_action() -> None: with pytest.raises(KeyError, match=re.escape("No action with name wrong_action_name is registered")): remove_action("wrong_action_name") + + +def test_evaluations() -> None: + test_dir = get_path_training_circuits() / "new_indep_circuits" / "test" + results_dir = Path(__file__).resolve().parent / "results" / "baseline" + results_dir.mkdir(parents=True, exist_ok=True) + output_path = results_dir / "info.csv" + figure_of_merit = "expected_fidelity" + + api_token = "" + available_devices = ["ibm_brisbane", "ibm_torino"] + device = available_devices[0] + + service = QiskitRuntimeService(channel="ibm_cloud", token=api_token) + backend = service.backend(device) + backend.target.description = "ibm_brisbane" # HACK + model_results = [] + model_label= "baseline" + for file_path in test_dir.glob("*.qasm"): + file_name = file_path.name + print(f"File: {file_name}") + qc = QuantumCircuit.from_qasm_file(str(file_path)) + qc_compiled, reward, info, depth, critical_depth, esp = rl_compile( + qc, device=backend.target, figure_of_merit=figure_of_merit + ) + model_results.append({ + "model": model_label, + "file": file_path.name, + "depth": depth, + "crit_depth": critical_depth, + "esp": esp, + "reward": reward, + "ep_length_mean": len(info) + }) + print(f"✅ Size {qc.num_qubits} | File: {file_path.name} | " + f"Reward: {reward:.4f} | " + f"Depth: {depth} | " + f"Critical Depth: {critical_depth} |" + f"ESP: {esp} |" + f"Mean Steps: {len(info):.1f}") + + df = pd.DataFrame(model_results) + df.sort_values(by=["depth", "model"], inplace=True) + df.to_csv(output_path, index=False) + print(f"📁 Results saved to: {output_path}") + From eae11a2c4a41e7c57494effecc94a668cea09509 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Fri, 5 Sep 2025 15:42:49 +0200 Subject: [PATCH 29/57] Set up code for testing new model --- docs/setup.md | 2 +- noxfile.py | 5 +- pyproject.toml | 4 +- src/mqt/predictor/reward.py | 4 +- src/mqt/predictor/rl/actions.py | 301 +++++++++++++++--------- src/mqt/predictor/rl/helper.py | 9 - src/mqt/predictor/rl/predictor.py | 24 +- src/mqt/predictor/rl/predictorenv.py | 308 +++++++++++++++++-------- tests/compilation/test_predictor_rl.py | 16 +- uv.lock | 10 +- 10 files changed, 444 insertions(+), 239 deletions(-) diff --git a/docs/setup.md b/docs/setup.md index f37bc88e8..da572bb00 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -109,7 +109,7 @@ After setup, any quantum circuit can be compiled for the most suitable device wi from mqt.predictor import qcompile from mqt.bench import get_benchmark, BenchmarkLevel -uncompiled_qc = get_benchmark("ghz", level=BenchmarkLevel.ALG, circuit_size=5) +uncompiled_qc = get_benchmark("ghz", level=BenchmarkLevel.INDEP, circuit_size=5) compiled_qc, compilation_info, selected_device = qcompile( uncompiled_qc, figure_of_merit="expected_fidelity" ) diff --git a/noxfile.py b/noxfile.py index 8a7e78ddc..bc2755126 100644 --- a/noxfile.py +++ b/noxfile.py @@ -30,7 +30,7 @@ # TODO(denialhaag): Add 3.14 when all dependencies support it # https://github.com/munich-quantum-toolkit/predictor/issues/420 -PYTHON_ALL_VERSIONS = ["3.10", "3.11", "3.12"] +PYTHON_ALL_VERSIONS = ["3.10", "3.11", "3.12", "3.13"] if os.environ.get("CI", None): nox.options.error_on_missing_interpreters = True @@ -83,9 +83,6 @@ def tests(session: nox.Session) -> None: @nox.session(reuse_venv=True, venv_backend="uv", python=PYTHON_ALL_VERSIONS) def minimums(session: nox.Session) -> None: """Test the minimum versions of dependencies.""" - if platform.system() == "Windows": - session.skip("Too slow on Windows — skipping minimums session.") - return _run_tests( session, install_args=["--resolution=lowest-direct"], diff --git a/pyproject.toml b/pyproject.toml index e161979cf..a8adfea9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ dependencies = [ "numpy>=1.24; python_version >= '3.11'", "numpy>=1.22", "numpy>=1.22,<2; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict numpy v2 for macOS x86 since it is not supported anymore since torch v2.3.0 - "torch>=2.7.1,<2.8.0", + "torch>=2.7.1,<2.8.0" , "typing-extensions>=4.1", # for `assert_never` "qiskit-ibm-transpiler>=0.11.1", "qiskit-ibm-ai-local-transpiler>=0.3.2; python_version < '3.13'" @@ -253,5 +253,5 @@ ignore = ["GH200"] [tool.uv] override-dependencies = [ - "networkx==2.8.5", + "networkx==2.8.5; python_version < '3.13'", # qiskit-ibm-ai-local-transpiler requires this specific version of networkx ] diff --git a/src/mqt/predictor/reward.py b/src/mqt/predictor/reward.py index 3cc726eed..65282c6dc 100644 --- a/src/mqt/predictor/reward.py +++ b/src/mqt/predictor/reward.py @@ -70,7 +70,7 @@ def expected_fidelity(qc: QuantumCircuit, device: Target, precision: int = 10) - try: specific_fidelity = 1 - device[gate_type][first_qubit_idx, second_qubit_idx].error except KeyError: - # try flipped orientation + # try flipped orientation (for uni-directional devices) specific_fidelity = 1 - device[gate_type][second_qubit_idx, first_qubit_idx].error res *= specific_fidelity @@ -212,7 +212,7 @@ def estimated_success_probability(qc: QuantumCircuit, device: Target, precision: if first_qubit_idx not in active_qubits: continue - dt=5e-10 # discrete time unit used in duration + dt = device.dt # discrete time unit used in duration res *= np.exp( -instruction.duration * dt / min(device.qubit_properties[first_qubit_idx].t1, device.qubit_properties[first_qubit_idx].t2) diff --git a/src/mqt/predictor/rl/actions.py b/src/mqt/predictor/rl/actions.py index 8c73f6b70..cacd7ab10 100644 --- a/src/mqt/predictor/rl/actions.py +++ b/src/mqt/predictor/rl/actions.py @@ -14,6 +14,7 @@ from dataclasses import dataclass from enum import Enum from typing import TYPE_CHECKING, Any +import sys from pytket.architecture import Architecture from pytket.passes import ( @@ -23,6 +24,7 @@ RemoveRedundancies, RoutingPass, ) +from pytket.placement import GraphPlacement, NoiseAwarePlacement from qiskit import QuantumCircuit from qiskit.circuit import ClassicalRegister, Instruction, QuantumRegister, Qubit, StandardEquivalenceLibrary from qiskit.circuit.library import ( @@ -62,6 +64,7 @@ OptimizeCliffords, RemoveDiagonalGatesBeforeMeasure, SabreLayout, + SabreSwap, TrivialLayout, UnitarySynthesis, VF2Layout, @@ -218,23 +221,13 @@ def get_openqasm_gates() -> list[str]: "rccx", ] - -# register_action( -# DeviceIndependentAction( -# "Optimize1qGatesDecomposition", -# CompilationOrigin.QISKIT, -# PassType.OPT, -# preserve=True, -# transpile_pass=lambda device: [Optimize1qGatesDecomposition(basis=device.operation_names)], -# ) -# ) - register_action( - DeviceIndependentAction( + DeviceDependentAction( "Optimize1qGatesDecomposition", CompilationOrigin.QISKIT, PassType.OPT, - [Optimize1qGatesDecomposition()], + preserve=True, + transpile_pass=lambda device: [Optimize1qGatesDecomposition(basis=device.operation_names)], ) ) @@ -302,26 +295,17 @@ def get_openqasm_gates() -> list[str]: ) ) -# register_action( -# DeviceDependentAction( -# "Opt2qBlocks", -# CompilationOrigin.QISKIT, -# PassType.OPT, -# transpile_pass=lambda native_gate, coupling_map: [ -# Collect2qBlocks(), -# ConsolidateBlocks(basis_gates=native_gate), -# UnitarySynthesis(basis_gates=native_gate, coupling_map=coupling_map), -# ], -# preserve=True, -# ) -# ) - register_action( - DeviceIndependentAction( + DeviceDependentAction( "Opt2qBlocks", CompilationOrigin.QISKIT, PassType.OPT, - [Collect2qBlocks(), ConsolidateBlocks(), UnitarySynthesis()], + transpile_pass=lambda native_gate, coupling_map: [ + Collect2qBlocks(), + ConsolidateBlocks(basis_gates=native_gate), + UnitarySynthesis(basis_gates=native_gate, coupling_map=coupling_map), + ], + preserve=True, ) ) @@ -410,21 +394,7 @@ def get_openqasm_gates() -> list[str]: "VF2PostLayout", CompilationOrigin.QISKIT, PassType.FINAL_OPT, - transpile_pass=lambda device: VF2PostLayout(target=device), - ) -) - -register_action( - DeviceDependentAction( - "TrivialLayout", - CompilationOrigin.QISKIT, - PassType.LAYOUT, - transpile_pass=lambda device: [ - TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), - FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), - EnlargeWithAncilla(), - ApplyLayout(), - ], + transpile_pass=lambda device: VF2PostLayout(target=device, time_limit=100), ) ) @@ -462,22 +432,27 @@ def get_openqasm_gates() -> list[str]: ) ) -# register_action( -# DeviceDependentAction( -# "SabreLayout", -# CompilationOrigin.QISKIT, -# PassType.LAYOUT, -# transpile_pass=lambda device: [ -# SabreLayout( -# coupling_map=CouplingMap(device.build_coupling_map()), -# skip_routing=True, -# ), -# FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), -# EnlargeWithAncilla(), -# ApplyLayout(), -# ], -# ) -# ) +register_action( + DeviceDependentAction( + "GraphPlacement", + CompilationOrigin.TKET, + PassType.LAYOUT, + transpile_pass=lambda device: [ + GraphPlacement(Architecture(list(device.build_coupling_map())),timeout=5000, maximum_matches=5000) + ], + ) +) + +register_action( + DeviceDependentAction( + "NoiseAwarePlacement", + CompilationOrigin.TKET, + PassType.LAYOUT, + transpile_pass=lambda device, node_err, edge_err, readout_err: [ + NoiseAwarePlacement(Architecture(list(device.build_coupling_map())), node_err, edge_err, readout_err, timeout=5000, maximum_matches=5000) + ], + ) +) register_action( DeviceDependentAction( @@ -493,40 +468,49 @@ def get_openqasm_gates() -> list[str]: register_action( DeviceDependentAction( - "BasicSwap", + "SabreSwap", CompilationOrigin.QISKIT, PassType.ROUTING, - transpile_pass=lambda device: [BasicSwap(coupling_map=CouplingMap(device.build_coupling_map()))], + stochastic=True, + transpile_pass=lambda device: [ + SabreSwap(coupling_map=CouplingMap(device.build_coupling_map()), heuristic="decay") + ], ) ) -# register_action( -# DeviceDependentAction( -# "SabreSwap", -# CompilationOrigin.QISKIT, -# PassType.ROUTING, -# stochastic=True, -# transpile_pass=lambda device: [ -# SabreSwap(coupling_map=CouplingMap(device.build_coupling_map()), heuristic="decay") -# ], -# ) -# ) +if sys.version_info < (3, 13): + register_action( + DeviceDependentAction( + "AIRouting", + CompilationOrigin.QISKIT, + PassType.ROUTING, + stochastic=True, + transpile_pass=lambda device: [ + SafeAIRouting( + coupling_map=device.build_coupling_map(), + optimization_level=3, + layout_mode="improve", + local_mode=True + ) + ], + ) + ) -# register_action( -# DeviceDependentAction( -# "AIRouting", -# CompilationOrigin.QISKIT, -# PassType.ROUTING, -# stochastic=True, -# transpile_pass=lambda device: [ -# SafeAIRouting( -# coupling_map=device.build_coupling_map(), -# optimization_level=3, -# layout_mode="improve", -# local_mode=True -# ) -# ], -# ) -# ) + register_action( + DeviceDependentAction( + "AIRouting_opt", + CompilationOrigin.QISKIT, + PassType.MAPPING, + stochastic=True, + transpile_pass=lambda device: [ + ### Requires a initial layout, but "optimize" mode overwrites it + SabreLayout(coupling_map=CouplingMap(device.build_coupling_map()), skip_routing=True, max_iterations=1), + FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout(), + SafeAIRouting(coupling_map=device.build_coupling_map(), optimization_level=3, layout_mode="optimize"), + ], + ) + ) register_action( DeviceDependentAction( @@ -539,28 +523,12 @@ def get_openqasm_gates() -> list[str]: SabreLayout( coupling_map=CouplingMap(device.build_coupling_map()), skip_routing=False, + max_iterations=1 ), ], ) ) -# register_action( -# DeviceDependentAction( -# "AIRouting_opt", -# CompilationOrigin.QISKIT, -# PassType.MAPPING, -# stochastic=True, -# transpile_pass=lambda device: [ -# ### Requires a initial layout, but "optimize" mode overwrites it -# TrivialLayout(coupling_map=CouplingMap(device.build_coupling_map())), -# FullAncillaAllocation(coupling_map=CouplingMap(device.build_coupling_map())), -# EnlargeWithAncilla(), -# ApplyLayout(), -# SafeAIRouting(coupling_map=device.build_coupling_map(), optimization_level=3, layout_mode="optimize"), -# ], -# ) -# ) - # register_action( # DeviceDependentAction( # "BQSKitMapping", @@ -749,3 +717,124 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: qc_final = add_cregs_and_measurements(qc_routed, cregs, measurements, qubit_map) # 8. Return as dag return circuit_to_dag(qc_final) + +def extract_cregs_and_measurements( + qc: QuantumCircuit, +) -> tuple[list[ClassicalRegister], list[tuple[Instruction, list[Any], list[Any]]]]: + """Extracts classical registers and measurement operations from a quantum circuit. + + Args: + qc: The input QuantumCircuit. + + Returns: + A tuple containing a list of classical registers and a list of measurement operations. + """ + cregs = [ClassicalRegister(cr.size, name=cr.name) for cr in qc.cregs] + measurements = [(item.operation, item.qubits, item.clbits) for item in qc.data if item.operation.name == "measure"] + return cregs, measurements + + +def remove_cregs(qc: QuantumCircuit) -> QuantumCircuit: + """Removes classical registers and measurement operations from the circuit. + + Args: + qc: The input QuantumCircuit. + + Returns: + A new QuantumCircuit with only quantum operations (no cregs or measurements). + """ + qregs = [QuantumRegister(qr.size, name=qr.name) for qr in qc.qregs] + new_qc = QuantumCircuit(*qregs) + old_to_new = {} + for orig_qr, new_qr in zip(qc.qregs, new_qc.qregs, strict=False): + for idx in range(orig_qr.size): + old_to_new[orig_qr[idx]] = new_qr[idx] + for item in qc.data: + instr = item.operation + qargs = [old_to_new[q] for q in item.qubits] + if instr.name not in ("measure", "barrier"): + new_qc.append(instr, qargs) + return new_qc + + +def add_cregs_and_measurements( + qc: QuantumCircuit, + cregs: list[ClassicalRegister], + measurements: list[tuple[Instruction, list[Any], list[Any]]], + qubit_map: dict[Qubit, Qubit] | None = None, +) -> QuantumCircuit: + """Adds classical registers and measurement operations back to the quantum circuit. + + Args: + qc: The quantum circuit to which cregs and measurements are added. + cregs: List of ClassicalRegister to add. + measurements: List of measurement instructions as tuples (Instruction, qubits, clbits). + qubit_map: Optional dictionary mapping original qubits to new qubits. + + Returns: + The modified QuantumCircuit with cregs and measurements added. + """ + for cr in cregs: + qc.add_register(cr) + for instr, qargs, cargs in measurements: + new_qargs = [qubit_map[q] for q in qargs] if qubit_map else qargs + qc.append(instr, new_qargs, cargs) + return qc + + +class SafeAIRouting(AIRouting): # type: ignore[misc] + """Custom AIRouting wrapper that removes classical registers before routing. + + This prevents failures in AIRouting when classical bits are present by + temporarily removing classical registers and measurements and restoring + them after routing is completed. + """ + + def run(self, dag: DAGCircuit) -> DAGCircuit: + """Run the routing pass on a DAGCircuit.""" + # 1. Convert input dag to circuit + qc_orig = dag_to_circuit(dag) + + # 2. Extract classical registers and measurement instructions + cregs, measurements = extract_cregs_and_measurements(qc_orig) + + # 3. Remove cregs and measurements + qc_noclassical = remove_cregs(qc_orig) + + # 4. Convert back to dag and run routing (AIRouting) + dag_noclassical = circuit_to_dag(qc_noclassical) + dag_routed = super().run(dag_noclassical) + + # 5. Convert routed dag to circuit for restoration + qc_routed = dag_to_circuit(dag_routed) + + # 6. Build mapping from original qubits to qubits in routed circuit + final_layout = getattr(self, "property_set", {}).get("final_layout", None) + if final_layout is None and hasattr(dag_routed, "property_set"): + final_layout = dag_routed.property_set.get("final_layout", None) + + assert final_layout is not None, "final_layout is None — cannot map virtual qubits" + qubit_map = {} + for virt in qc_orig.qubits: + try: + phys = final_layout[virt] + except KeyError as err: + msg = f"Virtual qubit {virt} not found in final layout!" + raise RuntimeError(msg) from err + if isinstance(phys, int): + try: + qubit_map[virt] = qc_routed.qubits[phys] + except IndexError as err: + msg = f"Physical index {phys} is out of range in routed circuit!" + raise RuntimeError(msg) from err + else: + try: + idx = qc_routed.qubits.index(phys) + except ValueError as err: + msg = f"Physical qubit {phys} not found in output circuit!" + raise RuntimeError(msg) from err + qubit_map[virt] = qc_routed.qubits[idx] + # 7. Restore classical registers and measurement instructions + qc_final = add_cregs_and_measurements(qc_routed, cregs, measurements, qubit_map) + # 8. Return as dag + return circuit_to_dag(qc_final) \ No newline at end of file diff --git a/src/mqt/predictor/rl/helper.py b/src/mqt/predictor/rl/helper.py index c3b8ead5e..2226cc151 100644 --- a/src/mqt/predictor/rl/helper.py +++ b/src/mqt/predictor/rl/helper.py @@ -11,13 +11,11 @@ from __future__ import annotations import logging -import ctypes from pathlib import Path from typing import TYPE_CHECKING import numpy as np from qiskit import QuantumCircuit - from mqt.predictor.utils import calc_supermarq_features if TYPE_CHECKING: @@ -29,7 +27,6 @@ logger = logging.getLogger("mqt-predictor") - def get_state_sample(max_qubits: int, path_training_circuits: Path, rng: Generator) -> tuple[QuantumCircuit, str]: """Returns a random quantum circuit from the training circuits folder. @@ -106,9 +103,3 @@ def get_path_trained_model() -> Path: def get_path_training_circuits() -> Path: """Returns the path to the training circuits folder used for RL training.""" return get_path_training_data() / "training_circuits" - -def trim_memory() -> None: - try: - ctypes.CDLL("libc.so.6").malloc_trim(0) - except Exception as e: - pass # Not available on all platforms diff --git a/src/mqt/predictor/rl/predictor.py b/src/mqt/predictor/rl/predictor.py index 39ead54f2..fea4fac76 100644 --- a/src/mqt/predictor/rl/predictor.py +++ b/src/mqt/predictor/rl/predictor.py @@ -66,7 +66,7 @@ def compile_as_predicted( Raises: RuntimeError: If an error occurs during compilation. """ - trained_rl_model = load_model("model_" + self.figure_of_merit + "_" + self.device_name) + trained_rl_model = load_model("model_new_actions_" + self.figure_of_merit + "_" + self.device_name) obs, _ = self.env.reset(qc, seed=0) @@ -147,18 +147,18 @@ def train_model( progress_bar = False logger.debug("Start training for: " + self.figure_of_merit + " on " + self.device_name) - # model = MaskablePPO( - # MaskableMultiInputActorCriticPolicy, - # self.env, - # verbose=verbose, - # tensorboard_log="./" + model_name + "_" + self.figure_of_merit + "_" + self.device_name, - # gamma=0.98, - # n_steps=n_steps, - # batch_size=batch_size, - # n_epochs=n_epochs, - # ) + model = MaskablePPO( + MaskableMultiInputActorCriticPolicy, + self.env, + verbose=verbose, + tensorboard_log="./" + model_name + "_" + self.figure_of_merit + "_" + self.device_name, + gamma=0.98, + n_steps=n_steps, + batch_size=batch_size, + n_epochs=n_epochs, + ) model = MaskablePPO.load( - get_path_trained_model() / "model_expected_fidelity_ibm_brisbane.zip", + get_path_trained_model() / "model_new_actions_expected_fidelity_ibm_torino.zip", env=self.env, verbose=verbose, device="cuda", diff --git a/src/mqt/predictor/rl/predictorenv.py b/src/mqt/predictor/rl/predictorenv.py index f300fda0d..684378d5d 100644 --- a/src/mqt/predictor/rl/predictorenv.py +++ b/src/mqt/predictor/rl/predictorenv.py @@ -31,18 +31,21 @@ import warnings from typing import cast +import ctypes import numpy as np from bqskit.ext import bqskit_to_qiskit, qiskit_to_bqskit from gymnasium import Env from gymnasium.spaces import Box, Dict, Discrete from joblib import load -from pytket.circuit import Qubit +from pytket.circuit import Qubit, Node from pytket.extensions.qiskit import qiskit_to_tk, tk_to_qiskit from qiskit import QuantumCircuit, transpile +from qiskit.circuit import StandardEquivalenceLibrary +from qiskit.passmanager import PropertySet from qiskit.passmanager.flow_controllers import DoWhileController -from qiskit.transpiler import CouplingMap, PassManager, Target, TranspileLayout -from qiskit.transpiler.passes import CheckMap, GatesInBasis +from qiskit.transpiler import CouplingMap, PassManager, Target, TranspileLayout, Layout +from qiskit.transpiler.passes import CheckMap, GatesInBasis, BasisTranslator, SetLayout, EnlargeWithAncilla, FullAncillaAllocation, ApplyLayout from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason from mqt.predictor.hellinger import get_hellinger_model_path @@ -65,7 +68,6 @@ create_feature_dict, get_path_training_circuits, get_state_sample, - trim_memory ) from mqt.predictor.rl.parsing import ( final_layout_bqskit_to_qiskit, @@ -178,8 +180,8 @@ def __init__( } self.observation_space = Dict(spaces) self.filename = "" - self.max_iter = (20, 20) # (layout_trials, routing _trials) - + self.max_iter = 20 + self.node_err = self.edge_err = self.readout_err = None def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any, Any]]: """Executes the given action and returns the new state, the reward, whether the episode is done, whether the episode is truncated and additional information. @@ -194,14 +196,6 @@ def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any """ self.used_actions.append(str(self.action_set[action].name)) logger.info(f"Applying: {self.action_set[action].name!s}") - if self.num_steps > 70: - return ( - create_feature_dict(self.state), - 0, - True, - False, - {}, - ) altered_qc = self.apply_action(action) if not altered_qc : return ( @@ -225,7 +219,7 @@ def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any if action == self.action_terminate_index: reward_val = self.calculate_reward() - logger.info(f"Fidelity: {reward_val}") + logger.info(f"{self.reward_function}: {reward_val}") done = True else: reward_val = 0 @@ -245,21 +239,31 @@ def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any gc.collect() self.state._layout = self.layout # noqa: SLF001 - #obs = create_feature_dict(self.state) - trim_memory() + self.trim_memory() return create_feature_dict(self.state), reward_val, done, False, {} - def calculate_reward(self) -> float: + def calculate_reward(self, qc=None) -> float: """Calculates and returns the reward for the current state.""" - if self.reward_function == "expected_fidelity": - return expected_fidelity(self.state, self.device) - if self.reward_function == "estimated_success_probability": - return estimated_success_probability(self.state, self.device) - if self.reward_function == "estimated_hellinger_distance": - return estimated_hellinger_distance(self.state, self.device, self.hellinger_model) - if self.reward_function == "critical_depth": - return crit_depth(self.state) - assert_never(self.state) + if qc==None: + if self.reward_function == "expected_fidelity": + return expected_fidelity(self.state, self.device) + if self.reward_function == "estimated_success_probability": + return estimated_success_probability(self.state, self.device) + if self.reward_function == "estimated_hellinger_distance": + return estimated_hellinger_distance(self.state, self.device, self.hellinger_model) + if self.reward_function == "critical_depth": + return crit_depth(self.state) + assert_never(self.state) + else: + if self.reward_function == "expected_fidelity": + return expected_fidelity(qc, self.device) + if self.reward_function == "estimated_success_probability": + return estimated_success_probability(qc, self.device) + if self.reward_function == "estimated_hellinger_distance": + return estimated_hellinger_distance(qc, self.device, self.hellinger_model) + if self.reward_function == "critical_depth": + return crit_depth(qc) + def render(self) -> None: """Renders the current state.""" @@ -282,6 +286,7 @@ def reset( The initial state and additional information. """ super().reset(seed=seed) + self.trim_memory() if isinstance(qc, QuantumCircuit): self.state = qc elif qc: @@ -301,14 +306,14 @@ def reset( self.num_qubits_uncompiled_circuit = self.state.num_qubits self.has_parameterized_gates = len(self.state.parameters) > 0 - trim_memory() + return create_feature_dict(self.state), {} def action_masks(self) -> list[bool]: """Returns a list of valid actions for the current state.""" action_mask = [action in self.valid_actions for action in self.action_set] - # it is not clear how tket will handle the layout, so we remove all actions that are from "origin"=="tket" if a layout is set + # # it is not clear how tket will handle the layout, so we remove all actions that are from "origin"=="tket" if a layout is set if self.layout is not None: action_mask = [ action_mask[i] and self.action_set[i].origin != CompilationOrigin.TKET for i in range(len(action_mask)) @@ -358,69 +363,205 @@ def apply_action(self, action_index: int) -> QuantumCircuit | None: msg = f"Origin {action.origin} not supported." gc.collect() - trim_memory() + self.trim_memory() raise ValueError(msg) + + def fom_aware_compile( + self, + action: Action, + device: Target, + qc: QuantumCircuit, + max_iteration: int = 4 + ) -> tuple[QuantumCircuit, PropertySet|None]: + best_result = None + best_property_set = None + best_fom = -1.0 + best_swap_count = float("inf") # for fallback + + for i in range(max_iteration): + pm = PassManager(action.transpile_pass(device)) + try: + out_circ = pm.run(qc) + prop_set = dict(pm.property_set) + + try: + # Synthesize for lookahead fidelity (Mapping could insert non-local SWAP gates) + if self.reward_function in ["expected_fidelity", "estimated_success_probability", "estimated_hellinger_distance"]: + synth_pass = PassManager([BasisTranslator(StandardEquivalenceLibrary, target_basis=device.operation_names)]) + synth_circ = synth_pass.run(out_circ.copy()) + fom = self.calculate_reward(synth_circ) + del synth_circ + + if fom > best_fom: + print(f"New best {self.reward_function}: {fom}") + best_fom = fom + best_result = out_circ + best_property_set = prop_set + else: + fom = self.calculate_reward(out_circ) + if fom < best_fom: + print(f"New best {self.reward_function}: {fom}") + best_fom = fom + best_result = out_circ + best_property_set = prop_set + + except Exception as e: + logger.warning(f"[Fallback to SWAP counts] Synthesis or fidelity computation failed: {e}") + swap_count = out_circ.count_ops().get("swap", 0) + if best_result is None or (best_fom == -1.0 and swap_count < best_swap_count): + print(f"[Fallback] New best (lower) swap count: {swap_count}") + best_swap_count = swap_count + best_result = out_circ + best_property_set = prop_set + + except Exception as e: + logger.error(f"[Error] Pass failed at iteration {i+1}: {e}") + continue + gc.collect() + if best_result is not None: + return best_result, best_property_set + else: + logger.error("All attempts failed.") + return qc, {} def _apply_qiskit_action(self, action: Action, action_index: int) -> QuantumCircuit: - if action.name in ["QiskitO3", "Opt2qBlocks"] and isinstance(action, DeviceDependentAction): - passes = action.transpile_pass( - self.device.operation_names, - CouplingMap(self.device.build_coupling_map()) if self.layout else None, + if getattr(action, "stochastic", False): # Wrap stochastic action to optimize for the used figure of merit + altered_qc, pm_property_set = self.fom_aware_compile( + action, + self.device, + self.state, + max_iteration=self.max_iter, ) - if action.name == "QiskitO3": - pm = PassManager([DoWhileController(passes, do_while=action.do_while)]) - else: - pm = PassManager(passes) else: - transpile_pass = ( - action.transpile_pass(self.device) if callable(action.transpile_pass) else action.transpile_pass - ) - pm = PassManager(transpile_pass) - - altered_qc = pm.run(self.state) - - if action_index in ( - self.actions_layout_indices + self.actions_mapping_indices + self.actions_final_optimization_indices - ): - altered_qc = self._handle_qiskit_layout_postprocessing(action, pm, altered_qc) - + if action.name in ["QiskitO3", "Opt2qBlocks"] and isinstance(action, DeviceDependentAction): + passes = action.transpile_pass( + self.device.operation_names, + CouplingMap(self.device.build_coupling_map()) if self.layout else None, + ) + if action.name == "QiskitO3": + pm = PassManager([DoWhileController(passes, do_while=action.do_while)]) + else: + pm = PassManager(passes) + altered_qc = pm.run(self.state) + pm_property_set = dict(pm.property_set) if hasattr(pm, "property_set") else {} + else: + transpile_pass = ( + action.transpile_pass(self.device) if callable(action.transpile_pass) else action.transpile_pass + ) + pm = PassManager(transpile_pass) + altered_qc = pm.run(self.state) + pm_property_set = dict(pm.property_set) if hasattr(pm, "property_set") else {} + + if action_index in (self.actions_layout_indices + self.actions_mapping_indices + self.actions_final_optimization_indices): + altered_qc = self._handle_qiskit_layout_postprocessing(action, pm_property_set, altered_qc) elif action_index in self.actions_routing_indices and self.layout: - self.layout.final_layout = pm.property_set["final_layout"] + self.layout.final_layout = pm_property_set["final_layout"] + + del pm_property_set + gc.collect() return altered_qc def _handle_qiskit_layout_postprocessing( - self, action: Action, pm: PassManager, altered_qc: QuantumCircuit + self, + action: Action, + pm_property_set: dict[str, Any], + altered_qc: QuantumCircuit, ) -> QuantumCircuit: if action.name == "VF2PostLayout": - assert pm.property_set["VF2PostLayout_stop_reason"] is not None - post_layout = pm.property_set["post_layout"] + assert pm_property_set["VF2PostLayout_stop_reason"] is not None + post_layout = pm_property_set.get("post_layout") if post_layout: altered_qc, _ = postprocess_vf2postlayout(altered_qc, post_layout, self.layout) elif action.name == "VF2Layout": - if pm.property_set["VF2Layout_stop_reason"] == VF2LayoutStopReason.SOLUTION_FOUND: - assert pm.property_set["layout"] + if pm_property_set["VF2Layout_stop_reason"] == VF2LayoutStopReason.SOLUTION_FOUND: + assert pm_property_set["layout"] else: - assert pm.property_set["layout"] + assert pm_property_set["layout"] - if pm.property_set["layout"]: + layout = pm_property_set.get("layout") + if layout: self.layout = TranspileLayout( - initial_layout=pm.property_set["layout"], - input_qubit_mapping=pm.property_set["original_qubit_indices"], - final_layout=pm.property_set["final_layout"], + initial_layout=layout, + input_qubit_mapping=pm_property_set.get("original_qubit_indices"), + final_layout=pm_property_set.get("final_layout"), _output_qubit_list=altered_qc.qubits, _input_qubit_count=self.num_qubits_uncompiled_circuit, ) + + if self.layout is not None and pm_property_set.get("final_layout"): + self.layout.final_layout = pm_property_set["final_layout"] return altered_qc def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircuit: tket_qc = qiskit_to_tk(self.state, preserve_param_uuid=True) - transpile_pass = ( - action.transpile_pass(self.device) if callable(action.transpile_pass) else action.transpile_pass - ) + if action.name == "NoiseAwarePlacement": + if self.node_err==None or self.edge_err==None or self.readout_err==None: + node_err: dict = {} + edge_err: dict = {} + readout_err: dict = {} + # Calculate avg node, edge and readout error + for op_name in self.device.operation_names: + inst_props = self.device[op_name] # this is a dict-like object + for qtuple, props in inst_props.items(): + if props is None or not hasattr(props, "error") or props.error is None: + continue + if len(qtuple) == 1: # single-qubit op + q = qtuple[0] + node_err[Node(q)] = props.error + elif len(qtuple) == 2: # two-qubit op + q1, q2 = qtuple + edge_err[(Node(q1), Node(q2))] = props.error + + # Readout errors (they are in the Target under "measure") + if "measure" in self.device: + for (q,), props in self.device["measure"].items(): + if props is not None and hasattr(props, "error") and props.error is not None: + readout_err[Node(q)] = props.error + self.node_err = node_err + self.edge_err = edge_err + self.readout_err = readout_err + transpile_pass = (action.transpile_pass(self.device, self.node_err, self.edge_err, self.readout_err)) + else: + transpile_pass = ( + action.transpile_pass(self.device) if callable(action.transpile_pass) else action.transpile_pass + ) assert isinstance(transpile_pass, list) - for p in transpile_pass: - p.apply(tket_qc) + if action_index in self.actions_layout_indices: + try: + placement = transpile_pass[0].get_placement_map(tket_qc) + qc_tmp = tk_to_qiskit(tket_qc, replace_implicit_swaps=True) + qiskit_mapping = { + qc_tmp.qubits[i]: placement[list(placement.keys())[i]].index[0] + for i in range(len(placement)) + } + layout = Layout(qiskit_mapping) + pm = PassManager([ + SetLayout(layout), + FullAncillaAllocation(coupling_map=CouplingMap(self.device.build_coupling_map())), + EnlargeWithAncilla(), + ApplyLayout() + ]) + altered_qc = pm.run(qc_tmp) + self.layout = TranspileLayout( + initial_layout=pm.property_set.get("layout"), + input_qubit_mapping=pm.property_set["original_qubit_indices"], + final_layout=pm.property_set["final_layout"], + _output_qubit_list=altered_qc.qubits, + _input_qubit_count=self.num_qubits_uncompiled_circuit, + ) + del qc_tmp + del tket_qc + gc.collect() + return altered_qc + except Exception as e: + print(f"[Warning] Placement failed ({action.name}): {e}. Falling back to the original circuit.") + altered_qc = tk_to_qiskit(tket_qc, replace_implicit_swaps=True) + return altered_qc + + else: + for p in transpile_pass: + p.apply(tket_qc) qbs = tket_qc.qubits tket_qc.rename_units({qbs[i]: Qubit("q", i) for i in range(len(qbs))}) @@ -429,9 +570,11 @@ def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircui if action_index in self.actions_routing_indices: assert self.layout is not None self.layout.final_layout = final_layout_pytket_to_qiskit(tket_qc, altered_qc) - # Decompose to the allowed gates (by default generic u gates are used) + del tket_qc - return transpile(altered_qc, basis_gates=get_openqasm_gates(), optimization_level=0) + gc.collect() + return altered_qc + def _apply_bqskit_action(self, action: Action, action_index: int) -> QuantumCircuit: """Applies the given BQSKit action to the current state and returns the altered state. @@ -479,7 +622,7 @@ def determine_valid_actions_for_state(self) -> list[int]: check_mapping(self.state) mapped = check_mapping.property_set["is_swap_mapped"] - logger.info(f"Mapped:{mapped}") + logger.info(f"Mapped:{mapped}, Native:{only_nat_gates}") if not only_nat_gates: # not native gates yet if not mapped: @@ -487,7 +630,7 @@ def determine_valid_actions_for_state(self) -> list[int]: return self.actions_synthesis_indices + self.actions_preserving_indices if mapped and self.layout is not None: # The circuit is correctly mapped - return [self.action_terminate_index, *self.actions_opt_indices] #*self.actions_final_optimization_indices] + return [self.action_terminate_index, *self.actions_preserving_indices, *self.actions_final_optimization_indices] if self.layout is not None: # The circuit is not yet mapped but a layout is set. @@ -497,23 +640,8 @@ def determine_valid_actions_for_state(self) -> list[int]: return self.actions_preserving_indices + self.actions_layout_indices + self.actions_mapping_indices return self.actions_opt_indices + self.actions_layout_indices + self.actions_mapping_indices - - - # if not only_nat_gates: - # actions = self.actions_synthesis_indices + self.actions_opt_indices - # if self.layout is not None: - # actions += self.actions_routing_indices - # return actions - - # check_mapping = CheckMap(coupling_map=self.device.build_coupling_map()) - # check_mapping(self.state) - # mapped = check_mapping.property_set["is_swap_mapped"] - - # if mapped and self.layout is not None: # The circuit is correctly mapped. - # return [self.action_terminate_index, *self.actions_opt_indices] - - # if self.layout is not None: # The circuit is not yet mapped but a layout is set. - # return self.actions_routing_indices - - # # No layout applied yet - # return self.actions_mapping_indices + self.actions_layout_indices + self.actions_opt_indices + def trim_memory(self): + try: + ctypes.CDLL("libc.so.6").malloc_trim(0) + except Exception as e: + pass # Not available on all platforms diff --git a/tests/compilation/test_predictor_rl.py b/tests/compilation/test_predictor_rl.py index 9f1205a57..25b7f8f48 100644 --- a/tests/compilation/test_predictor_rl.py +++ b/tests/compilation/test_predictor_rl.py @@ -90,18 +90,18 @@ def test_qcompile_with_newly_trained_models() -> None: api_token = "" available_devices = ["ibm_brisbane", "ibm_torino"] - device = available_devices[0] + device = available_devices[1] service = QiskitRuntimeService(channel="ibm_cloud", token=api_token) backend = service.backend(device) - backend.target.description = "ibm_brisbane" # HACK - print(backend.configuration().dt) + backend.target.description = "ibm_torino" # HACK predictor = Predictor(figure_of_merit=figure_of_merit, device=backend.target) qc = get_benchmark("ghz", BenchmarkLevel.INDEP, 17, target=backend.target) predictor.train_model( - timesteps=30000, + timesteps=10000, test=False, + model_name="model_new_actions" ) qc_compiled, compilation_information = rl_compile( @@ -157,20 +157,20 @@ def test_register_action() -> None: def test_evaluations() -> None: test_dir = get_path_training_circuits() / "new_indep_circuits" / "test" - results_dir = Path(__file__).resolve().parent / "results" / "baseline" + results_dir = Path(__file__).resolve().parent / "results" / "new_actions" results_dir.mkdir(parents=True, exist_ok=True) output_path = results_dir / "info.csv" figure_of_merit = "expected_fidelity" api_token = "" available_devices = ["ibm_brisbane", "ibm_torino"] - device = available_devices[0] + device = available_devices[1] service = QiskitRuntimeService(channel="ibm_cloud", token=api_token) backend = service.backend(device) - backend.target.description = "ibm_brisbane" # HACK + backend.target.description = "ibm_torino" # HACK model_results = [] - model_label= "baseline" + model_label= "new_actions" for file_path in test_dir.glob("*.qasm"): file_name = file_path.name print(f"File: {file_name}") diff --git a/uv.lock b/uv.lock index d9e9e8d01..7f0a518db 100644 --- a/uv.lock +++ b/uv.lock @@ -12,7 +12,7 @@ resolution-markers = [ ] [manifest] -overrides = [{ name = "networkx", specifier = "==2.8.5" }] +overrides = [{ name = "networkx", marker = "python_full_version < '3.13'", specifier = "==2.8.5" }] [[package]] name = "absl-py" @@ -1499,7 +1499,7 @@ name = "mqt-bench" version = "2.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "networkx" }, + { name = "networkx", marker = "python_full_version < '3.13'" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin')" }, { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13' and 'x86_64' not in platform_machine) or (python_full_version >= '3.11' and sys_platform != 'darwin') or (python_full_version >= '3.13' and sys_platform == 'darwin')" }, { name = "qiskit", extra = ["qasm3-import"] }, @@ -2484,7 +2484,7 @@ dependencies = [ { name = "graphviz" }, { name = "jinja2" }, { name = "lark" }, - { name = "networkx" }, + { name = "networkx", marker = "python_full_version < '3.13'" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin')" }, { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13' and 'x86_64' not in platform_machine) or (python_full_version >= '3.11' and sys_platform != 'darwin') or (python_full_version >= '3.13' and sys_platform == 'darwin')" }, { name = "qwasm" }, @@ -2806,7 +2806,7 @@ version = "0.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "backoff" }, - { name = "networkx" }, + { name = "networkx", marker = "python_full_version < '3.13'" }, { name = "qiskit" }, { name = "qiskit-qasm3-import" }, { name = "requests" }, @@ -3762,7 +3762,7 @@ dependencies = [ { name = "filelock" }, { name = "fsspec" }, { name = "jinja2" }, - { name = "networkx" }, + { name = "networkx", marker = "python_full_version < '3.13'" }, { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, From 68306ec03cb95bf2f9627677b542b50f127be39a Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Fri, 5 Sep 2025 16:33:57 +0200 Subject: [PATCH 30/57] Reset --- src/mqt/predictor/ml/predictor.py | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/mqt/predictor/ml/predictor.py b/src/mqt/predictor/ml/predictor.py index 155c09e04..fc470a409 100644 --- a/src/mqt/predictor/ml/predictor.py +++ b/src/mqt/predictor/ml/predictor.py @@ -191,10 +191,7 @@ def _compile_all_circuits_devicewise( if (path_compiled_circuits / (target_filename + ".qasm")).exists(): continue try: - if sys.platform == "win32": - res = rl_compile(qc, device, self.figure_of_merit, rl_pred) - else: - res = timeout_watcher(rl_compile, [qc, device, self.figure_of_merit, rl_pred], timeout) + res = timeout_watcher(rl_compile, [qc, device, self.figure_of_merit, rl_pred], timeout) if isinstance(res, tuple): compiled_qc = res[0] with Path(path_compiled_circuits / (target_filename + ".qasm")).open("w", encoding="utf-8") as f: @@ -209,6 +206,7 @@ def compile_training_circuits( path_uncompiled_circuits: Path | None = None, path_compiled_circuits: Path | None = None, timeout: int = 600, + num_workers: int = -1 ) -> None: """Compiles all circuits in the given directory with the given timeout and saves them in the given directory. @@ -229,24 +227,19 @@ def compile_training_circuits( with zipfile.ZipFile(str(path_zip), "r") as zip_ref: zip_ref.extractall(path_uncompiled_circuits) - if sys.platform != "win32": - Parallel(n_jobs=1, verbose=100)( - delayed(self._compile_all_circuits_devicewise)( - device, timeout, path_uncompiled_circuits, path_compiled_circuits, logger.level - ) - for device in self.devices + Parallel(n_jobs=num_workers, verbose=100)( + delayed(self._compile_all_circuits_devicewise)( + device, timeout, path_uncompiled_circuits, path_compiled_circuits, logger.level ) - else: - for device in self.devices: - self._compile_all_circuits_devicewise( - device, timeout, path_uncompiled_circuits, path_compiled_circuits, logger.level - ) + for device in self.devices + ) def generate_training_data( self, path_uncompiled_circuits: Path | None = None, path_compiled_circuits: Path | None = None, path_training_data: Path | None = None, + num_workers: int = -1 ) -> None: """Creates and saves training data from all generated training samples. @@ -274,7 +267,7 @@ def generate_training_data( names_list = [] scores_list = [] - results = Parallel(n_jobs=1, verbose=100)( + results = Parallel(n_jobs=num_workers, verbose=100)( delayed(self._generate_training_sample)( filename.name, path_uncompiled_circuits, From fcba8fa810f1fe80ea6f4ba52be1ec6498254db9 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Sat, 6 Sep 2025 15:22:58 +0200 Subject: [PATCH 31/57] Add new actions --- noxfile.py | 1 - pyproject.toml | 4 +- src/mqt/predictor/ml/predictor.py | 4 +- src/mqt/predictor/reward.py | 13 +- src/mqt/predictor/rl/actions.py | 397 ++++++++----------------- src/mqt/predictor/rl/predictor.py | 58 +--- src/mqt/predictor/rl/predictorenv.py | 178 +++++------ tests/compilation/test_predictor_rl.py | 111 ++----- 8 files changed, 252 insertions(+), 514 deletions(-) diff --git a/noxfile.py b/noxfile.py index bc2755126..9e13b37f9 100644 --- a/noxfile.py +++ b/noxfile.py @@ -12,7 +12,6 @@ import argparse import os -import platform import shutil from typing import TYPE_CHECKING diff --git a/pyproject.toml b/pyproject.toml index a8adfea9b..bdcf1b7db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,9 +44,9 @@ dependencies = [ "numpy>=1.24; python_version >= '3.11'", "numpy>=1.22", "numpy>=1.22,<2; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict numpy v2 for macOS x86 since it is not supported anymore since torch v2.3.0 - "torch>=2.7.1,<2.8.0" , + "torch>=2.2.2,<2.3.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch v2.3.0 for macOS x86 since it is not supported anymore. "typing-extensions>=4.1", # for `assert_never` - "qiskit-ibm-transpiler>=0.11.1", + "qiskit-ibm-transpiler>=0.11.1; python_version < '3.13'", "qiskit-ibm-ai-local-transpiler>=0.3.2; python_version < '3.13'" ] diff --git a/src/mqt/predictor/ml/predictor.py b/src/mqt/predictor/ml/predictor.py index fc470a409..3f0ec5497 100644 --- a/src/mqt/predictor/ml/predictor.py +++ b/src/mqt/predictor/ml/predictor.py @@ -206,7 +206,7 @@ def compile_training_circuits( path_uncompiled_circuits: Path | None = None, path_compiled_circuits: Path | None = None, timeout: int = 600, - num_workers: int = -1 + num_workers: int = -1, ) -> None: """Compiles all circuits in the given directory with the given timeout and saves them in the given directory. @@ -239,7 +239,7 @@ def generate_training_data( path_uncompiled_circuits: Path | None = None, path_compiled_circuits: Path | None = None, path_training_data: Path | None = None, - num_workers: int = -1 + num_workers: int = -1, ) -> None: """Creates and saves training data from all generated training samples. diff --git a/src/mqt/predictor/reward.py b/src/mqt/predictor/reward.py index 65282c6dc..8394d6eee 100644 --- a/src/mqt/predictor/reward.py +++ b/src/mqt/predictor/reward.py @@ -189,7 +189,6 @@ def estimated_success_probability(qc: QuantumCircuit, device: Target, precision: res = 1.0 for instr in scheduled_circ.data: - instruction = instr.operation qargs = instr.qubits gate_type = instruction.name @@ -202,7 +201,6 @@ def estimated_success_probability(qc: QuantumCircuit, device: Target, precision: if len(qargs) == 1: if gate_type == "measure": - res *= 1 - device[gate_type][first_qubit_idx,].error continue if gate_type == "delay": @@ -211,18 +209,19 @@ def estimated_success_probability(qc: QuantumCircuit, device: Target, precision: # only consider active qubits if first_qubit_idx not in active_qubits: continue - - dt = device.dt # discrete time unit used in duration + + dt = device.dt # discrete time unit used in duration res *= np.exp( - -instruction.duration * dt + -instruction.duration + * dt / min(device.qubit_properties[first_qubit_idx].t1, device.qubit_properties[first_qubit_idx].t2) ) continue res *= 1 - device[gate_type][first_qubit_idx,].error - + else: second_qubit_idx = calc_qubit_index(qargs, qc.qregs, 1) - res *= 1 - device[gate_type][first_qubit_idx, second_qubit_idx].error + res *= 1 - device[gate_type][first_qubit_idx, second_qubit_idx].error if qiskit_version >= "2.0.0": for i in range(device.num_qubits): diff --git a/src/mqt/predictor/rl/actions.py b/src/mqt/predictor/rl/actions.py index cacd7ab10..05bffd82a 100644 --- a/src/mqt/predictor/rl/actions.py +++ b/src/mqt/predictor/rl/actions.py @@ -10,12 +10,15 @@ from __future__ import annotations +import os +import sys from collections import defaultdict from dataclasses import dataclass from enum import Enum -from typing import TYPE_CHECKING, Any -import sys +from typing import TYPE_CHECKING +from bqskit import MachineModel +from bqskit import compile as bqskit_compile from pytket.architecture import Architecture from pytket.passes import ( CliffordSimp, @@ -49,7 +52,6 @@ from qiskit.transpiler import CouplingMap from qiskit.transpiler.passes import ( ApplyLayout, - BasicSwap, BasisTranslator, Collect2qBlocks, CollectCliffords, @@ -57,24 +59,30 @@ CommutativeInverseCancellation, ConsolidateBlocks, DenseLayout, + Depth, EnlargeWithAncilla, + FixedPoint, FullAncillaAllocation, + GatesInBasis, InverseCancellation, + MinimumPoint, Optimize1qGatesDecomposition, OptimizeCliffords, RemoveDiagonalGatesBeforeMeasure, SabreLayout, SabreSwap, - TrivialLayout, + Size, UnitarySynthesis, VF2Layout, VF2PostLayout, ) from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason +from qiskit.transpiler.preset_passmanagers import common from qiskit_ibm_transpiler.ai.routing import AIRouting from mqt.predictor.rl.parsing import ( PreProcessTKETRoutingAfterQiskitLayout, + get_bqskit_native_gates, ) if TYPE_CHECKING: @@ -177,50 +185,6 @@ def remove_action(name: str) -> None: del _ACTIONS[name] -def get_openqasm_gates() -> list[str]: - """Returns a list of all quantum gates within the openQASM 2.0 standard header. - - According to https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/qasm/libs/qelib1.inc - Removes generic single qubit gates u1, u2, u3 since they are no meaningful features for RL - - """ - return [ - "cx", - "id", - "u", - "p", - "x", - "y", - "z", - "h", - "r", - "s", - "sdg", - "t", - "tdg", - "rx", - "ry", - "rz", - "sx", - "sxdg", - "cz", - "cy", - "swap", - "ch", - "ccx", - "cswap", - "crx", - "cry", - "crz", - "cu1", - "cp", - "csx", - "cu", - "rxx", - "rzz", - "rccx", - ] - register_action( DeviceDependentAction( "Optimize1qGatesDecomposition", @@ -345,49 +309,49 @@ def get_openqasm_gates() -> list[str]: ) ) -# register_action( -# DeviceDependentAction( -# "QiskitO3", -# CompilationOrigin.QISKIT, -# PassType.OPT, -# transpile_pass=lambda native_gate, coupling_map: [ -# Collect2qBlocks(), -# ConsolidateBlocks(basis_gates=native_gate), -# UnitarySynthesis(basis_gates=native_gate, coupling_map=coupling_map), -# Optimize1qGatesDecomposition(basis=native_gate), -# CommutativeCancellation(basis_gates=native_gate), -# GatesInBasis(native_gate), -# ConditionalController( -# common.generate_translation_passmanager( -# target=None, basis_gates=native_gate, coupling_map=coupling_map -# ).to_flow_controller(), -# condition=lambda property_set: not property_set["all_gates_in_basis"], -# ), -# Depth(recurse=True), -# FixedPoint("depth"), -# Size(recurse=True), -# FixedPoint("size"), -# MinimumPoint(["depth", "size"], "optimization_loop"), -# ], -# do_while=lambda property_set: not property_set["optimization_loop_minimum_point"], -# ) -# ) - -# register_action( -# DeviceDependentAction( -# "BQSKitO2", -# CompilationOrigin.BQSKIT, -# PassType.OPT, -# transpile_pass=lambda circuit: bqskit_compile( -# circuit, -# optimization_level=1 if os.getenv("GITHUB_ACTIONS") == "true" else 2, -# synthesis_epsilon=1e-1 if os.getenv("GITHUB_ACTIONS") == "true" else 1e-8, -# max_synthesis_size=2 if os.getenv("GITHUB_ACTIONS") == "true" else 3, -# seed=10, -# num_workers=1 if os.getenv("GITHUB_ACTIONS") == "true" else -1, -# ), -# ) -# ) +register_action( + DeviceDependentAction( + "QiskitO3", + CompilationOrigin.QISKIT, + PassType.OPT, + transpile_pass=lambda native_gate, coupling_map: [ + Collect2qBlocks(), + ConsolidateBlocks(basis_gates=native_gate), + UnitarySynthesis(basis_gates=native_gate, coupling_map=coupling_map), + Optimize1qGatesDecomposition(basis=native_gate), + CommutativeCancellation(basis_gates=native_gate), + GatesInBasis(native_gate), + ConditionalController( + common.generate_translation_passmanager( + target=None, basis_gates=native_gate, coupling_map=coupling_map + ).to_flow_controller(), + condition=lambda property_set: not property_set["all_gates_in_basis"], + ), + Depth(recurse=True), + FixedPoint("depth"), + Size(recurse=True), + FixedPoint("size"), + MinimumPoint(["depth", "size"], "optimization_loop"), + ], + do_while=lambda property_set: not property_set["optimization_loop_minimum_point"], + ) +) + +register_action( + DeviceDependentAction( + "BQSKitO2", + CompilationOrigin.BQSKIT, + PassType.OPT, + transpile_pass=lambda circuit: bqskit_compile( + circuit, + optimization_level=1 if os.getenv("GITHUB_ACTIONS") == "true" else 2, + synthesis_epsilon=1e-1 if os.getenv("GITHUB_ACTIONS") == "true" else 1e-8, + max_synthesis_size=2 if os.getenv("GITHUB_ACTIONS") == "true" else 3, + seed=10, + num_workers=1 if os.getenv("GITHUB_ACTIONS") == "true" else -1, + ), + ) +) register_action( DeviceDependentAction( @@ -438,7 +402,7 @@ def get_openqasm_gates() -> list[str]: CompilationOrigin.TKET, PassType.LAYOUT, transpile_pass=lambda device: [ - GraphPlacement(Architecture(list(device.build_coupling_map())),timeout=5000, maximum_matches=5000) + GraphPlacement(Architecture(list(device.build_coupling_map())), timeout=5000, maximum_matches=5000) ], ) ) @@ -449,7 +413,14 @@ def get_openqasm_gates() -> list[str]: CompilationOrigin.TKET, PassType.LAYOUT, transpile_pass=lambda device, node_err, edge_err, readout_err: [ - NoiseAwarePlacement(Architecture(list(device.build_coupling_map())), node_err, edge_err, readout_err, timeout=5000, maximum_matches=5000) + NoiseAwarePlacement( + Architecture(list(device.build_coupling_map())), + node_err, + edge_err, + readout_err, + timeout=5000, + maximum_matches=5000, + ) ], ) ) @@ -485,13 +456,13 @@ def get_openqasm_gates() -> list[str]: PassType.ROUTING, stochastic=True, transpile_pass=lambda device: [ - SafeAIRouting( - coupling_map=device.build_coupling_map(), - optimization_level=3, - layout_mode="improve", - local_mode=True - ) - ], + SafeAIRouting( + coupling_map=device.build_coupling_map(), + optimization_level=3, + layout_mode="improve", + local_mode=True, + ) + ], ) ) @@ -518,38 +489,33 @@ def get_openqasm_gates() -> list[str]: CompilationOrigin.QISKIT, PassType.MAPPING, stochastic=True, - # Qiskit O3 by default uses (max_iterations, layout_trials, swap_trials) = (4, 20, 20) transpile_pass=lambda device: [ - SabreLayout( - coupling_map=CouplingMap(device.build_coupling_map()), - skip_routing=False, - max_iterations=1 - ), + SabreLayout(coupling_map=CouplingMap(device.build_coupling_map()), skip_routing=False, max_iterations=1), ], ) ) -# register_action( -# DeviceDependentAction( -# "BQSKitMapping", -# CompilationOrigin.BQSKIT, -# PassType.MAPPING, -# transpile_pass=lambda device: lambda bqskit_circuit: bqskit_compile( -# bqskit_circuit, -# model=MachineModel( -# num_qudits=device.num_qubits, -# gate_set=get_bqskit_native_gates(device), -# coupling_graph=[(elem[0], elem[1]) for elem in device.build_coupling_map()], -# ), -# with_mapping=True, -# optimization_level=1 if os.getenv("GITHUB_ACTIONS") == "true" else 2, -# synthesis_epsilon=1e-1 if os.getenv("GITHUB_ACTIONS") == "true" else 1e-8, -# max_synthesis_size=2 if os.getenv("GITHUB_ACTIONS") == "true" else 3, -# seed=10, -# num_workers=1 if os.getenv("GITHUB_ACTIONS") == "true" else -1, -# ), -# ) -# ) +register_action( + DeviceDependentAction( + "BQSKitMapping", + CompilationOrigin.BQSKIT, + PassType.MAPPING, + transpile_pass=lambda device: lambda bqskit_circuit: bqskit_compile( + bqskit_circuit, + model=MachineModel( + num_qudits=device.num_qubits, + gate_set=get_bqskit_native_gates(device), + coupling_graph=[(elem[0], elem[1]) for elem in device.build_coupling_map()], + ), + with_mapping=True, + optimization_level=1 if os.getenv("GITHUB_ACTIONS") == "true" else 2, + synthesis_epsilon=1e-1 if os.getenv("GITHUB_ACTIONS") == "true" else 1e-8, + max_synthesis_size=2 if os.getenv("GITHUB_ACTIONS") == "true" else 3, + seed=10, + num_workers=1 if os.getenv("GITHUB_ACTIONS") == "true" else -1, + ), + ) +) register_action( DeviceDependentAction( @@ -562,22 +528,22 @@ def get_openqasm_gates() -> list[str]: ) ) -# register_action( -# DeviceDependentAction( -# "BQSKitSynthesis", -# CompilationOrigin.BQSKIT, -# PassType.SYNTHESIS, -# transpile_pass=lambda device: lambda bqskit_circuit: bqskit_compile( -# bqskit_circuit, -# model=MachineModel(bqskit_circuit.num_qudits, gate_set=get_bqskit_native_gates(device)), -# optimization_level=1 if os.getenv("GITHUB_ACTIONS") == "true" else 2, -# synthesis_epsilon=1e-1 if os.getenv("GITHUB_ACTIONS") == "true" else 1e-8, -# max_synthesis_size=2 if os.getenv("GITHUB_ACTIONS") == "true" else 3, -# seed=10, -# num_workers=1 if os.getenv("GITHUB_ACTIONS") == "true" else -1, -# ), -# ) -# ) +register_action( + DeviceDependentAction( + "BQSKitSynthesis", + CompilationOrigin.BQSKIT, + PassType.SYNTHESIS, + transpile_pass=lambda device: lambda bqskit_circuit: bqskit_compile( + bqskit_circuit, + model=MachineModel(bqskit_circuit.num_qudits, gate_set=get_bqskit_native_gates(device)), + optimization_level=1 if os.getenv("GITHUB_ACTIONS") == "true" else 2, + synthesis_epsilon=1e-1 if os.getenv("GITHUB_ACTIONS") == "true" else 1e-8, + max_synthesis_size=2 if os.getenv("GITHUB_ACTIONS") == "true" else 3, + seed=10, + num_workers=1 if os.getenv("GITHUB_ACTIONS") == "true" else -1, + ), + ) +) register_action( DeviceIndependentAction( @@ -599,7 +565,7 @@ def get_actions_by_pass_type() -> dict[PassType, list[Action]]: def extract_cregs_and_measurements( qc: QuantumCircuit, -) -> tuple[list[ClassicalRegister], list[tuple[Instruction, list[Any], list[Any]]]]: +) -> tuple[list[ClassicalRegister], list[tuple[Instruction, list[Qubit], list[ClassicalRegister]]]]: """Extracts classical registers and measurement operations from a quantum circuit. Args: @@ -639,7 +605,7 @@ def remove_cregs(qc: QuantumCircuit) -> QuantumCircuit: def add_cregs_and_measurements( qc: QuantumCircuit, cregs: list[ClassicalRegister], - measurements: list[tuple[Instruction, list[Any], list[Any]]], + measurements: list[tuple[Instruction, list[Qubit], list[ClassicalRegister]]], qubit_map: dict[Qubit, Qubit] | None = None, ) -> QuantumCircuit: """Adds classical registers and measurement operations back to the quantum circuit. @@ -671,23 +637,17 @@ class SafeAIRouting(AIRouting): # type: ignore[misc] def run(self, dag: DAGCircuit) -> DAGCircuit: """Run the routing pass on a DAGCircuit.""" - # 1. Convert input dag to circuit qc_orig = dag_to_circuit(dag) - - # 2. Extract classical registers and measurement instructions + # Extract classical registers and measurement instructions cregs, measurements = extract_cregs_and_measurements(qc_orig) - - # 3. Remove cregs and measurements + # Remove cregs and measurements qc_noclassical = remove_cregs(qc_orig) - - # 4. Convert back to dag and run routing (AIRouting) + # Convert back to dag and run routing (AIRouting) dag_noclassical = circuit_to_dag(qc_noclassical) dag_routed = super().run(dag_noclassical) - - # 5. Convert routed dag to circuit for restoration + # Convert routed dag to circuit for restoration qc_routed = dag_to_circuit(dag_routed) - - # 6. Build mapping from original qubits to qubits in routed circuit + # Build mapping from original qubits to qubits in routed circuit final_layout = getattr(self, "property_set", {}).get("final_layout", None) if final_layout is None and hasattr(dag_routed, "property_set"): final_layout = dag_routed.property_set.get("final_layout", None) @@ -713,128 +673,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: msg = f"Physical qubit {phys} not found in output circuit!" raise RuntimeError(msg) from err qubit_map[virt] = qc_routed.qubits[idx] - # 7. Restore classical registers and measurement instructions + # Restore classical registers and measurement instructions qc_final = add_cregs_and_measurements(qc_routed, cregs, measurements, qubit_map) - # 8. Return as dag + # Return as dag return circuit_to_dag(qc_final) - -def extract_cregs_and_measurements( - qc: QuantumCircuit, -) -> tuple[list[ClassicalRegister], list[tuple[Instruction, list[Any], list[Any]]]]: - """Extracts classical registers and measurement operations from a quantum circuit. - - Args: - qc: The input QuantumCircuit. - - Returns: - A tuple containing a list of classical registers and a list of measurement operations. - """ - cregs = [ClassicalRegister(cr.size, name=cr.name) for cr in qc.cregs] - measurements = [(item.operation, item.qubits, item.clbits) for item in qc.data if item.operation.name == "measure"] - return cregs, measurements - - -def remove_cregs(qc: QuantumCircuit) -> QuantumCircuit: - """Removes classical registers and measurement operations from the circuit. - - Args: - qc: The input QuantumCircuit. - - Returns: - A new QuantumCircuit with only quantum operations (no cregs or measurements). - """ - qregs = [QuantumRegister(qr.size, name=qr.name) for qr in qc.qregs] - new_qc = QuantumCircuit(*qregs) - old_to_new = {} - for orig_qr, new_qr in zip(qc.qregs, new_qc.qregs, strict=False): - for idx in range(orig_qr.size): - old_to_new[orig_qr[idx]] = new_qr[idx] - for item in qc.data: - instr = item.operation - qargs = [old_to_new[q] for q in item.qubits] - if instr.name not in ("measure", "barrier"): - new_qc.append(instr, qargs) - return new_qc - - -def add_cregs_and_measurements( - qc: QuantumCircuit, - cregs: list[ClassicalRegister], - measurements: list[tuple[Instruction, list[Any], list[Any]]], - qubit_map: dict[Qubit, Qubit] | None = None, -) -> QuantumCircuit: - """Adds classical registers and measurement operations back to the quantum circuit. - - Args: - qc: The quantum circuit to which cregs and measurements are added. - cregs: List of ClassicalRegister to add. - measurements: List of measurement instructions as tuples (Instruction, qubits, clbits). - qubit_map: Optional dictionary mapping original qubits to new qubits. - - Returns: - The modified QuantumCircuit with cregs and measurements added. - """ - for cr in cregs: - qc.add_register(cr) - for instr, qargs, cargs in measurements: - new_qargs = [qubit_map[q] for q in qargs] if qubit_map else qargs - qc.append(instr, new_qargs, cargs) - return qc - - -class SafeAIRouting(AIRouting): # type: ignore[misc] - """Custom AIRouting wrapper that removes classical registers before routing. - - This prevents failures in AIRouting when classical bits are present by - temporarily removing classical registers and measurements and restoring - them after routing is completed. - """ - - def run(self, dag: DAGCircuit) -> DAGCircuit: - """Run the routing pass on a DAGCircuit.""" - # 1. Convert input dag to circuit - qc_orig = dag_to_circuit(dag) - - # 2. Extract classical registers and measurement instructions - cregs, measurements = extract_cregs_and_measurements(qc_orig) - - # 3. Remove cregs and measurements - qc_noclassical = remove_cregs(qc_orig) - - # 4. Convert back to dag and run routing (AIRouting) - dag_noclassical = circuit_to_dag(qc_noclassical) - dag_routed = super().run(dag_noclassical) - - # 5. Convert routed dag to circuit for restoration - qc_routed = dag_to_circuit(dag_routed) - - # 6. Build mapping from original qubits to qubits in routed circuit - final_layout = getattr(self, "property_set", {}).get("final_layout", None) - if final_layout is None and hasattr(dag_routed, "property_set"): - final_layout = dag_routed.property_set.get("final_layout", None) - - assert final_layout is not None, "final_layout is None — cannot map virtual qubits" - qubit_map = {} - for virt in qc_orig.qubits: - try: - phys = final_layout[virt] - except KeyError as err: - msg = f"Virtual qubit {virt} not found in final layout!" - raise RuntimeError(msg) from err - if isinstance(phys, int): - try: - qubit_map[virt] = qc_routed.qubits[phys] - except IndexError as err: - msg = f"Physical index {phys} is out of range in routed circuit!" - raise RuntimeError(msg) from err - else: - try: - idx = qc_routed.qubits.index(phys) - except ValueError as err: - msg = f"Physical qubit {phys} not found in output circuit!" - raise RuntimeError(msg) from err - qubit_map[virt] = qc_routed.qubits[idx] - # 7. Restore classical registers and measurement instructions - qc_final = add_cregs_and_measurements(qc_routed, cregs, measurements, qubit_map) - # 8. Return as dag - return circuit_to_dag(qc_final) \ No newline at end of file diff --git a/src/mqt/predictor/rl/predictor.py b/src/mqt/predictor/rl/predictor.py index fea4fac76..3ffe07733 100644 --- a/src/mqt/predictor/rl/predictor.py +++ b/src/mqt/predictor/rl/predictor.py @@ -13,8 +13,6 @@ import logging from pathlib import Path from typing import TYPE_CHECKING -from collections import deque -from copy import deepcopy from sb3_contrib import MaskablePPO from sb3_contrib.common.maskable.policies import MaskableMultiInputActorCriticPolicy @@ -23,13 +21,12 @@ from mqt.predictor.rl.helper import get_path_trained_model, logger from mqt.predictor.rl.predictorenv import PredictorEnv -from mqt.predictor.reward import figure_of_merit, crit_depth, estimated_success_probability if TYPE_CHECKING: from qiskit import QuantumCircuit from qiskit.transpiler import Target - from mqt.predictor.reward import figure_of_merit, crit_depth, estimated_success_probability + from mqt.predictor.reward import figure_of_merit class Predictor: @@ -54,7 +51,7 @@ def __init__( def compile_as_predicted( self, qc: QuantumCircuit, - ) -> tuple[QuantumCircuit, float, list[str]]: + ) -> tuple[QuantumCircuit, list[str]]: """Compiles a given quantum circuit such that the given figure of merit is maximized by using the respectively trained optimized compiler. Arguments: @@ -66,54 +63,23 @@ def compile_as_predicted( Raises: RuntimeError: If an error occurs during compilation. """ - trained_rl_model = load_model("model_new_actions_" + self.figure_of_merit + "_" + self.device_name) + trained_rl_model = load_model("model_" + self.figure_of_merit + "_" + self.device_name) obs, _ = self.env.reset(qc, seed=0) used_compilation_passes = [] terminated = False truncated = False - recent_actions = deque(maxlen=8) - blocked_actions = set() while not (terminated or truncated): action_masks = get_action_masks(self.env) - if blocked_actions: - action_masks = deepcopy(action_masks) - for idx in blocked_actions: - action_masks[idx] = False - action, _ = trained_rl_model.predict(obs, action_masks=action_masks, deterministic=True) + action, _ = trained_rl_model.predict(obs, action_masks=action_masks) action = int(action) - - recent_actions.append(action) - - max_cycle_length = 4 - - def is_cycle(lst, k): - if len(lst) < 2*k: - return False - return lst[-2*k:-k] == lst[-k:] - - for k in range(3, max_cycle_length+1): - if is_cycle(list(recent_actions), k): - print(f"Avoiding {k}-cycle infinite loop pattern") - for cyc_action in set(list(recent_actions)[-k:]): - blocked_actions.add(cyc_action) - break - action_item = self.env.action_set[action] used_compilation_passes.append(action_item.name) - if action_item.name == "terminate": - depth = self.env.state.depth() - critical_depth = crit_depth(self.env.state) - esp= estimated_success_probability(self.env.state, self.env.device) - obs, reward_val, terminated, truncated, _info = self.env.step(action) + obs, _reward_val, terminated, truncated, _info = self.env.step(action) if not self.env.error_occurred: - return ( - self.env.state, - reward_val, - used_compilation_passes, - depth, critical_depth, esp) + return self.env.state, used_compilation_passes msg = "Error occurred during compilation." raise RuntimeError(msg) @@ -144,7 +110,7 @@ def train_model( n_steps = 2048 n_epochs = 10 batch_size = 64 - progress_bar = False + progress_bar = True logger.debug("Start training for: " + self.figure_of_merit + " on " + self.device_name) model = MaskablePPO( @@ -157,17 +123,10 @@ def train_model( batch_size=batch_size, n_epochs=n_epochs, ) - model = MaskablePPO.load( - get_path_trained_model() / "model_new_actions_expected_fidelity_ibm_torino.zip", - env=self.env, - verbose=verbose, - device="cuda", - ) # Training Loop: In each iteration, the agent collects n_steps steps (rollout), # updates the policy for n_epochs, and then repeats the process until total_timesteps steps have been taken. model.learn(total_timesteps=timesteps, progress_bar=progress_bar) model.save(get_path_trained_model() / (model_name + "_" + self.figure_of_merit + "_" + self.device_name)) - print("Model saved") def load_model(model_name: str) -> MaskablePPO: @@ -222,5 +181,4 @@ def rl_compile( else: predictor = predictor_singleton - qc, reward, info, depth, critical_depth, esp = predictor.compile_as_predicted(qc) - return qc, reward, info, depth, critical_depth, esp + return predictor.compile_as_predicted(qc) diff --git a/src/mqt/predictor/rl/predictorenv.py b/src/mqt/predictor/rl/predictorenv.py index 684378d5d..01ae26ee1 100644 --- a/src/mqt/predictor/rl/predictorenv.py +++ b/src/mqt/predictor/rl/predictorenv.py @@ -12,7 +12,6 @@ import logging import sys -import gc from typing import TYPE_CHECKING, Any if sys.version_info >= (3, 11) and TYPE_CHECKING: # pragma: no cover @@ -25,27 +24,34 @@ from pathlib import Path from bqskit import Circuit + from qiskit.passmanager import PropertySet from mqt.predictor.rl.actions import Action import warnings from typing import cast -import ctypes import numpy as np from bqskit.ext import bqskit_to_qiskit, qiskit_to_bqskit from gymnasium import Env from gymnasium.spaces import Box, Dict, Discrete from joblib import load -from pytket.circuit import Qubit, Node +from pytket.circuit import Node, Qubit from pytket.extensions.qiskit import qiskit_to_tk, tk_to_qiskit -from qiskit import QuantumCircuit, transpile +from qiskit import QuantumCircuit from qiskit.circuit import StandardEquivalenceLibrary -from qiskit.passmanager import PropertySet from qiskit.passmanager.flow_controllers import DoWhileController -from qiskit.transpiler import CouplingMap, PassManager, Target, TranspileLayout, Layout -from qiskit.transpiler.passes import CheckMap, GatesInBasis, BasisTranslator, SetLayout, EnlargeWithAncilla, FullAncillaAllocation, ApplyLayout +from qiskit.transpiler import CouplingMap, Layout, PassManager, Target, TranspileLayout +from qiskit.transpiler.passes import ( + ApplyLayout, + BasisTranslator, + CheckMap, + EnlargeWithAncilla, + FullAncillaAllocation, + GatesInBasis, + SetLayout, +) from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason from mqt.predictor.hellinger import get_hellinger_model_path @@ -62,7 +68,6 @@ DeviceDependentAction, PassType, get_actions_by_pass_type, - get_openqasm_gates, ) from mqt.predictor.rl.helper import ( create_feature_dict, @@ -170,7 +175,7 @@ def __init__( self.rng = np.random.default_rng(10) spaces = { - "num_qubits": Discrete(134), + "num_qubits": Discrete(self.device.num_qubits + 1), "depth": Discrete(1000000), "program_communication": Box(low=0, high=1, shape=(1,), dtype=np.float32), "critical_depth": Box(low=0, high=1, shape=(1,), dtype=np.float32), @@ -180,8 +185,11 @@ def __init__( } self.observation_space = Dict(spaces) self.filename = "" - self.max_iter = 20 - self.node_err = self.edge_err = self.readout_err = None + self.max_iter = 10 + self.node_err: dict[Node, float] | None = None + self.edge_err: dict[tuple[Node, Node], float] | None = None + self.readout_err: dict[Node, float] | None = None + def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any, Any]]: """Executes the given action and returns the new state, the reward, whether the episode is done, whether the episode is truncated and additional information. @@ -195,9 +203,8 @@ def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any RuntimeError: If no valid actions are left. """ self.used_actions.append(str(self.action_set[action].name)) - logger.info(f"Applying: {self.action_set[action].name!s}") altered_qc = self.apply_action(action) - if not altered_qc : + if not altered_qc: return ( create_feature_dict(self.state), 0, @@ -208,8 +215,6 @@ def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any self.state: QuantumCircuit = altered_qc - del altered_qc - gc.collect() self.num_steps += 1 self.valid_actions = self.determine_valid_actions_for_state() @@ -219,7 +224,6 @@ def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any if action == self.action_terminate_index: reward_val = self.calculate_reward() - logger.info(f"{self.reward_function}: {reward_val}") done = True else: reward_val = 0 @@ -227,24 +231,17 @@ def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any # in case the Qiskit.QuantumCircuit has unitary or u gates or clifford in it, decompose them (because otherwise qiskit will throw an error when applying the BasisTranslator if self.state.count_ops().get("unitary"): - temp_circ = self.state.decompose(gates_to_decompose="unitary") - # qiskit fallback to ['id', 'u1', 'u2', 'u3', 'cx'] by default according to https://quantum.cloud.ibm.com/docs/en/api/qiskit/0.24/transpiler - self.state = transpile(temp_circ, basis_gates=get_openqasm_gates(), optimization_level=0) - del temp_circ - gc.collect() + self.state = self.state.decompose(gates_to_decompose="unitary") elif self.state.count_ops().get("clifford"): - temp_circ = self.state.decompose(gates_to_decompose="clifford") - self.state = transpile(temp_circ, basis_gates=get_openqasm_gates(), optimization_level=0) - del temp_circ - gc.collect() + self.state = self.state.decompose(gates_to_decompose="clifford") self.state._layout = self.layout # noqa: SLF001 - self.trim_memory() + return create_feature_dict(self.state), reward_val, done, False, {} - def calculate_reward(self, qc=None) -> float: + def calculate_reward(self, qc: QuantumCircuit | None = None) -> float: """Calculates and returns the reward for the current state.""" - if qc==None: + if qc is None: if self.reward_function == "expected_fidelity": return expected_fidelity(self.state, self.device) if self.reward_function == "estimated_success_probability": @@ -263,7 +260,7 @@ def calculate_reward(self, qc=None) -> float: return estimated_hellinger_distance(qc, self.device, self.hellinger_model) if self.reward_function == "critical_depth": return crit_depth(qc) - + assert_never(self.state) def render(self) -> None: """Renders the current state.""" @@ -286,7 +283,7 @@ def reset( The initial state and additional information. """ super().reset(seed=seed) - self.trim_memory() + if isinstance(qc, QuantumCircuit): self.state = qc elif qc: @@ -362,22 +359,28 @@ def apply_action(self, action_index: int) -> QuantumCircuit | None: return self._apply_bqskit_action(action, action_index) msg = f"Origin {action.origin} not supported." - gc.collect() - self.trim_memory() raise ValueError(msg) - + def fom_aware_compile( - self, - action: Action, - device: Target, - qc: QuantumCircuit, - max_iteration: int = 4 - ) -> tuple[QuantumCircuit, PropertySet|None]: + self, action: Action, device: Target, qc: QuantumCircuit, max_iteration: int = 4 + ) -> tuple[QuantumCircuit, PropertySet | None]: + """Run a stochastic pass multiple times optimizing for the given figure of merit. + + Args: + action: The action containing the transpile pass. + device: The compilation target device. + qc: The input quantum circuit. + max_iteration: Maximum number of attempts to run the pass. + + Returns: + A tuple of the best circuit found and its property set (if available). + """ best_result = None best_property_set = None best_fom = -1.0 best_swap_count = float("inf") # for fallback + assert callable(action.transpile_pass), "Mapping action should be callable" for i in range(max_iteration): pm = PassManager(action.transpile_pass(device)) try: @@ -386,18 +389,23 @@ def fom_aware_compile( try: # Synthesize for lookahead fidelity (Mapping could insert non-local SWAP gates) - if self.reward_function in ["expected_fidelity", "estimated_success_probability", "estimated_hellinger_distance"]: - synth_pass = PassManager([BasisTranslator(StandardEquivalenceLibrary, target_basis=device.operation_names)]) + if self.reward_function in [ + "expected_fidelity", + "estimated_success_probability", + "estimated_hellinger_distance", + ]: + synth_pass = PassManager([ + BasisTranslator(StandardEquivalenceLibrary, target_basis=device.operation_names) + ]) synth_circ = synth_pass.run(out_circ.copy()) fom = self.calculate_reward(synth_circ) - del synth_circ if fom > best_fom: print(f"New best {self.reward_function}: {fom}") best_fom = fom best_result = out_circ best_property_set = prop_set - else: + else: fom = self.calculate_reward(out_circ) if fom < best_fom: print(f"New best {self.reward_function}: {fom}") @@ -409,27 +417,26 @@ def fom_aware_compile( logger.warning(f"[Fallback to SWAP counts] Synthesis or fidelity computation failed: {e}") swap_count = out_circ.count_ops().get("swap", 0) if best_result is None or (best_fom == -1.0 and swap_count < best_swap_count): - print(f"[Fallback] New best (lower) swap count: {swap_count}") best_swap_count = swap_count best_result = out_circ best_property_set = prop_set - except Exception as e: - logger.error(f"[Error] Pass failed at iteration {i+1}: {e}") + except Exception: + logger.exception(f"[Error] Pass failed at iteration {i + 1}") continue - gc.collect() + if best_result is not None: return best_result, best_property_set - else: - logger.error("All attempts failed.") - return qc, {} + logger.error("All attempts failed.") + return qc, {} def _apply_qiskit_action(self, action: Action, action_index: int) -> QuantumCircuit: - if getattr(action, "stochastic", False): # Wrap stochastic action to optimize for the used figure of merit + pm_property_set: PropertySet | None = {} + if getattr(action, "stochastic", False): # Wrap stochastic action to optimize for the used figure of merit altered_qc, pm_property_set = self.fom_aware_compile( - action, + action, self.device, - self.state, + self.state, max_iteration=self.max_iter, ) else: @@ -452,22 +459,23 @@ def _apply_qiskit_action(self, action: Action, action_index: int) -> QuantumCirc altered_qc = pm.run(self.state) pm_property_set = dict(pm.property_set) if hasattr(pm, "property_set") else {} - if action_index in (self.actions_layout_indices + self.actions_mapping_indices + self.actions_final_optimization_indices): + if action_index in ( + self.actions_layout_indices + self.actions_mapping_indices + self.actions_final_optimization_indices + ): altered_qc = self._handle_qiskit_layout_postprocessing(action, pm_property_set, altered_qc) - elif action_index in self.actions_routing_indices and self.layout: + elif action_index in self.actions_routing_indices and self.layout and pm_property_set is not None: self.layout.final_layout = pm_property_set["final_layout"] - del pm_property_set - gc.collect() - return altered_qc def _handle_qiskit_layout_postprocessing( self, action: Action, - pm_property_set: dict[str, Any], + pm_property_set: dict[str, Any] | None, altered_qc: QuantumCircuit, ) -> QuantumCircuit: + if not pm_property_set: + return altered_qc if action.name == "VF2PostLayout": assert pm_property_set["VF2PostLayout_stop_reason"] is not None post_layout = pm_property_set.get("post_layout") @@ -496,10 +504,11 @@ def _handle_qiskit_layout_postprocessing( def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircuit: tket_qc = qiskit_to_tk(self.state, preserve_param_uuid=True) if action.name == "NoiseAwarePlacement": - if self.node_err==None or self.edge_err==None or self.readout_err==None: - node_err: dict = {} - edge_err: dict = {} - readout_err: dict = {} + if self.node_err is None or self.edge_err is None or self.readout_err is None: + node_err: dict[Node, float] = {} + edge_err: dict[tuple[Node, Node], float] = {} + readout_err: dict[Node, float] = {} + # Calculate avg node, edge and readout error for op_name in self.device.operation_names: inst_props = self.device[op_name] # this is a dict-like object @@ -511,7 +520,7 @@ def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircui node_err[Node(q)] = props.error elif len(qtuple) == 2: # two-qubit op q1, q2 = qtuple - edge_err[(Node(q1), Node(q2))] = props.error + edge_err[Node(q1), Node(q2)] = props.error # Readout errors (they are in the Target under "measure") if "measure" in self.device: @@ -521,7 +530,8 @@ def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircui self.node_err = node_err self.edge_err = edge_err self.readout_err = readout_err - transpile_pass = (action.transpile_pass(self.device, self.node_err, self.edge_err, self.readout_err)) + assert callable(action.transpile_pass) + transpile_pass = action.transpile_pass(self.device, self.node_err, self.edge_err, self.readout_err) else: transpile_pass = ( action.transpile_pass(self.device) if callable(action.transpile_pass) else action.transpile_pass @@ -530,19 +540,25 @@ def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircui if action_index in self.actions_layout_indices: try: placement = transpile_pass[0].get_placement_map(tket_qc) + except Exception as e: + print(f"[Warning] Placement failed ({action.name}): {e}. Falling back to original circuit.") + return tk_to_qiskit(tket_qc, replace_implicit_swaps=True) + else: qc_tmp = tk_to_qiskit(tket_qc, replace_implicit_swaps=True) + qiskit_mapping = { - qc_tmp.qubits[i]: placement[list(placement.keys())[i]].index[0] - for i in range(len(placement)) + qc_tmp.qubits[i]: placement[list(placement.keys())[i]].index[0] for i in range(len(placement)) } layout = Layout(qiskit_mapping) + pm = PassManager([ SetLayout(layout), FullAncillaAllocation(coupling_map=CouplingMap(self.device.build_coupling_map())), EnlargeWithAncilla(), - ApplyLayout() + ApplyLayout(), ]) altered_qc = pm.run(qc_tmp) + self.layout = TranspileLayout( initial_layout=pm.property_set.get("layout"), input_qubit_mapping=pm.property_set["original_qubit_indices"], @@ -550,13 +566,6 @@ def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircui _output_qubit_list=altered_qc.qubits, _input_qubit_count=self.num_qubits_uncompiled_circuit, ) - del qc_tmp - del tket_qc - gc.collect() - return altered_qc - except Exception as e: - print(f"[Warning] Placement failed ({action.name}): {e}. Falling back to the original circuit.") - altered_qc = tk_to_qiskit(tket_qc, replace_implicit_swaps=True) return altered_qc else: @@ -570,11 +579,8 @@ def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircui if action_index in self.actions_routing_indices: assert self.layout is not None self.layout.final_layout = final_layout_pytket_to_qiskit(tket_qc, altered_qc) - - del tket_qc - gc.collect() + return altered_qc - def _apply_bqskit_action(self, action: Action, action_index: int) -> QuantumCircuit: """Applies the given BQSKit action to the current state and returns the altered state. @@ -622,15 +628,17 @@ def determine_valid_actions_for_state(self) -> list[int]: check_mapping(self.state) mapped = check_mapping.property_set["is_swap_mapped"] - logger.info(f"Mapped:{mapped}, Native:{only_nat_gates}") - if not only_nat_gates: # not native gates yet if not mapped: return self.actions_synthesis_indices + self.actions_opt_indices return self.actions_synthesis_indices + self.actions_preserving_indices if mapped and self.layout is not None: # The circuit is correctly mapped - return [self.action_terminate_index, *self.actions_preserving_indices, *self.actions_final_optimization_indices] + return [ + self.action_terminate_index, + *self.actions_preserving_indices, + *self.actions_final_optimization_indices, + ] if self.layout is not None: # The circuit is not yet mapped but a layout is set. @@ -639,9 +647,3 @@ def determine_valid_actions_for_state(self) -> list[int]: if mapped: return self.actions_preserving_indices + self.actions_layout_indices + self.actions_mapping_indices return self.actions_opt_indices + self.actions_layout_indices + self.actions_mapping_indices - - def trim_memory(self): - try: - ctypes.CDLL("libc.so.6").malloc_trim(0) - except Exception as e: - pass # Not available on all platforms diff --git a/tests/compilation/test_predictor_rl.py b/tests/compilation/test_predictor_rl.py index 25b7f8f48..ebb8f8e34 100644 --- a/tests/compilation/test_predictor_rl.py +++ b/tests/compilation/test_predictor_rl.py @@ -12,16 +12,14 @@ import re from pathlib import Path -import pandas as pd import pytest -from qiskit_ibm_runtime import QiskitRuntimeService from mqt.bench import BenchmarkLevel, get_benchmark from mqt.bench.targets import get_device from qiskit.circuit.library import CXGate from qiskit.qasm2 import dump -from qiskit.transpiler import InstructionProperties, Target -from qiskit.transpiler.passes import GatesInBasis +from qiskit.transpiler import CouplingMap, InstructionProperties, Target +from qiskit.transpiler.passes import CheckMap, GatesInBasis from mqt.predictor.rl import Predictor, rl_compile from mqt.predictor.rl.actions import ( @@ -32,8 +30,7 @@ register_action, remove_action, ) -from mqt.predictor.rl.helper import create_feature_dict, get_path_training_circuits -from qiskit import QuantumCircuit +from mqt.predictor.rl.helper import create_feature_dict, get_path_trained_model def test_predictor_env_reset_from_string() -> None: @@ -71,50 +68,40 @@ def test_qcompile_with_newly_trained_models() -> None: Important: Those trained models are used in later tests and must not be deleted. To test ESP as well, training must be done with a device that provides all relevant information (i.e. T1, T2 and gate times). """ - # figure_of_merit = "expected_fidelity" - # device = get_device("ibm_eagle_127") - # qc = get_benchmark("ghz", BenchmarkLevel.INDEP, 20) - # predictor = Predictor(figure_of_merit=figure_of_merit, device=device) - - # model_name = "model_" + figure_of_merit + "_" + device.description - # model_path = Path(get_path_trained_model() / (model_name + ".zip")) - # if not model_path.exists(): - # with pytest.raises( - # FileNotFoundError, - # match=re.escape( - # "The RL model 'model_expected_fidelity_ibm_falcon_127' is not trained yet. Please train the model before using it." - # ), - # ): - # rl_compile(qc, device=device, figure_of_merit=figure_of_merit) figure_of_merit = "expected_fidelity" - - api_token = "" - available_devices = ["ibm_brisbane", "ibm_torino"] - device = available_devices[1] - - service = QiskitRuntimeService(channel="ibm_cloud", token=api_token) - backend = service.backend(device) - backend.target.description = "ibm_torino" # HACK - predictor = Predictor(figure_of_merit=figure_of_merit, device=backend.target) - qc = get_benchmark("ghz", BenchmarkLevel.INDEP, 17, target=backend.target) + device = get_device("ibm_falcon_127") + qc = get_benchmark("ghz", BenchmarkLevel.ALG, 3) + predictor = Predictor(figure_of_merit=figure_of_merit, device=device) + + model_name = "model_" + figure_of_merit + "_" + device.description + model_path = Path(get_path_trained_model() / (model_name + ".zip")) + if not model_path.exists(): + with pytest.raises( + FileNotFoundError, + match=re.escape( + "The RL model 'model_expected_fidelity_ibm_falcon_127' is not trained yet. Please train the model before using it." + ), + ): + rl_compile(qc, device=device, figure_of_merit=figure_of_merit) predictor.train_model( - timesteps=10000, - test=False, - model_name="model_new_actions" + timesteps=100, + test=True, ) - qc_compiled, compilation_information = rl_compile( - qc, device=backend.target, figure_of_merit=figure_of_merit - ) + qc_compiled, compilation_information = rl_compile(qc, device=device, figure_of_merit=figure_of_merit) - check_nat_gates = GatesInBasis(basis_gates=backend.target.operation_names) + check_nat_gates = GatesInBasis(basis_gates=device.operation_names) check_nat_gates(qc_compiled) only_nat_gates = check_nat_gates.property_set["all_gates_in_basis"] + check_mapping = CheckMap(coupling_map=CouplingMap(device.build_coupling_map())) + check_mapping(qc_compiled) + mapped = check_mapping.property_set["is_swap_mapped"] assert qc_compiled.layout is not None assert compilation_information is not None - assert only_nat_gates, "Circuit should only contain native gates but was not detected as such" + assert only_nat_gates, "Circuit should only contain native gates but was not detected as such." + assert mapped, "Circuit should be mapped to the device's coupling map." def test_qcompile_with_false_input() -> None: @@ -153,49 +140,3 @@ def test_register_action() -> None: with pytest.raises(KeyError, match=re.escape("No action with name wrong_action_name is registered")): remove_action("wrong_action_name") - - -def test_evaluations() -> None: - test_dir = get_path_training_circuits() / "new_indep_circuits" / "test" - results_dir = Path(__file__).resolve().parent / "results" / "new_actions" - results_dir.mkdir(parents=True, exist_ok=True) - output_path = results_dir / "info.csv" - figure_of_merit = "expected_fidelity" - - api_token = "" - available_devices = ["ibm_brisbane", "ibm_torino"] - device = available_devices[1] - - service = QiskitRuntimeService(channel="ibm_cloud", token=api_token) - backend = service.backend(device) - backend.target.description = "ibm_torino" # HACK - model_results = [] - model_label= "new_actions" - for file_path in test_dir.glob("*.qasm"): - file_name = file_path.name - print(f"File: {file_name}") - qc = QuantumCircuit.from_qasm_file(str(file_path)) - qc_compiled, reward, info, depth, critical_depth, esp = rl_compile( - qc, device=backend.target, figure_of_merit=figure_of_merit - ) - model_results.append({ - "model": model_label, - "file": file_path.name, - "depth": depth, - "crit_depth": critical_depth, - "esp": esp, - "reward": reward, - "ep_length_mean": len(info) - }) - print(f"✅ Size {qc.num_qubits} | File: {file_path.name} | " - f"Reward: {reward:.4f} | " - f"Depth: {depth} | " - f"Critical Depth: {critical_depth} |" - f"ESP: {esp} |" - f"Mean Steps: {len(info):.1f}") - - df = pd.DataFrame(model_results) - df.sort_values(by=["depth", "model"], inplace=True) - df.to_csv(output_path, index=False) - print(f"📁 Results saved to: {output_path}") - From e5b75186a68a85413d97506cd7726ddf6235c4fb Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Mon, 8 Sep 2025 17:27:41 +0200 Subject: [PATCH 32/57] Fix dependencies --- pyproject.toml | 4 +- src/mqt/predictor/rl/actions.py | 100 ++++++++++--------- src/mqt/predictor/rl/helper.py | 2 + uv.lock | 170 ++++++++++++++++++++++++++------ 4 files changed, 196 insertions(+), 80 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bdcf1b7db..c3a370b03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ dependencies = [ "torch>=2.2.2,<2.3.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch v2.3.0 for macOS x86 since it is not supported anymore. "typing-extensions>=4.1", # for `assert_never` "qiskit-ibm-transpiler>=0.11.1; python_version < '3.13'", - "qiskit-ibm-ai-local-transpiler>=0.3.2; python_version < '3.13'" + "qiskit-ibm-ai-local-transpiler>=0.3.2; python_version < '3.13'", ] classifiers = [ @@ -253,5 +253,5 @@ ignore = ["GH200"] [tool.uv] override-dependencies = [ - "networkx==2.8.5; python_version < '3.13'", # qiskit-ibm-ai-local-transpiler requires this specific version of networkx + "networkx==2.8.5", ] diff --git a/src/mqt/predictor/rl/actions.py b/src/mqt/predictor/rl/actions.py index 05bffd82a..510653781 100644 --- a/src/mqt/predictor/rl/actions.py +++ b/src/mqt/predictor/rl/actions.py @@ -78,7 +78,9 @@ ) from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason from qiskit.transpiler.preset_passmanagers import common -from qiskit_ibm_transpiler.ai.routing import AIRouting + +if sys.version_info < (3, 13): + from qiskit_ibm_transpiler.ai.routing import AIRouting from mqt.predictor.rl.parsing import ( PreProcessTKETRoutingAfterQiskitLayout, @@ -627,53 +629,55 @@ def add_cregs_and_measurements( return qc -class SafeAIRouting(AIRouting): # type: ignore[misc] - """Custom AIRouting wrapper that removes classical registers before routing. - - This prevents failures in AIRouting when classical bits are present by - temporarily removing classical registers and measurements and restoring - them after routing is completed. - """ +if sys.version_info < (3, 13): - def run(self, dag: DAGCircuit) -> DAGCircuit: - """Run the routing pass on a DAGCircuit.""" - qc_orig = dag_to_circuit(dag) - # Extract classical registers and measurement instructions - cregs, measurements = extract_cregs_and_measurements(qc_orig) - # Remove cregs and measurements - qc_noclassical = remove_cregs(qc_orig) - # Convert back to dag and run routing (AIRouting) - dag_noclassical = circuit_to_dag(qc_noclassical) - dag_routed = super().run(dag_noclassical) - # Convert routed dag to circuit for restoration - qc_routed = dag_to_circuit(dag_routed) - # Build mapping from original qubits to qubits in routed circuit - final_layout = getattr(self, "property_set", {}).get("final_layout", None) - if final_layout is None and hasattr(dag_routed, "property_set"): - final_layout = dag_routed.property_set.get("final_layout", None) - - assert final_layout is not None, "final_layout is None — cannot map virtual qubits" - qubit_map = {} - for virt in qc_orig.qubits: - try: - phys = final_layout[virt] - except KeyError as err: - msg = f"Virtual qubit {virt} not found in final layout!" - raise RuntimeError(msg) from err - if isinstance(phys, int): - try: - qubit_map[virt] = qc_routed.qubits[phys] - except IndexError as err: - msg = f"Physical index {phys} is out of range in routed circuit!" - raise RuntimeError(msg) from err - else: + class SafeAIRouting(AIRouting): # type: ignore[misc] + """Custom AIRouting wrapper that removes classical registers before routing. + + This prevents failures in AIRouting when classical bits are present by + temporarily removing classical registers and measurements and restoring + them after routing is completed. + """ + + def run(self, dag: DAGCircuit) -> DAGCircuit: + """Run the routing pass on a DAGCircuit.""" + qc_orig = dag_to_circuit(dag) + # Extract classical registers and measurement instructions + cregs, measurements = extract_cregs_and_measurements(qc_orig) + # Remove cregs and measurements + qc_noclassical = remove_cregs(qc_orig) + # Convert back to dag and run routing (AIRouting) + dag_noclassical = circuit_to_dag(qc_noclassical) + dag_routed = super().run(dag_noclassical) + # Convert routed dag to circuit for restoration + qc_routed = dag_to_circuit(dag_routed) + # Build mapping from original qubits to qubits in routed circuit + final_layout = getattr(self, "property_set", {}).get("final_layout", None) + if final_layout is None and hasattr(dag_routed, "property_set"): + final_layout = dag_routed.property_set.get("final_layout", None) + + assert final_layout is not None, "final_layout is None — cannot map virtual qubits" + qubit_map = {} + for virt in qc_orig.qubits: try: - idx = qc_routed.qubits.index(phys) - except ValueError as err: - msg = f"Physical qubit {phys} not found in output circuit!" + phys = final_layout[virt] + except KeyError as err: + msg = f"Virtual qubit {virt} not found in final layout!" raise RuntimeError(msg) from err - qubit_map[virt] = qc_routed.qubits[idx] - # Restore classical registers and measurement instructions - qc_final = add_cregs_and_measurements(qc_routed, cregs, measurements, qubit_map) - # Return as dag - return circuit_to_dag(qc_final) + if isinstance(phys, int): + try: + qubit_map[virt] = qc_routed.qubits[phys] + except IndexError as err: + msg = f"Physical index {phys} is out of range in routed circuit!" + raise RuntimeError(msg) from err + else: + try: + idx = qc_routed.qubits.index(phys) + except ValueError as err: + msg = f"Physical qubit {phys} not found in output circuit!" + raise RuntimeError(msg) from err + qubit_map[virt] = qc_routed.qubits[idx] + # Restore classical registers and measurement instructions + qc_final = add_cregs_and_measurements(qc_routed, cregs, measurements, qubit_map) + # Return as dag + return circuit_to_dag(qc_final) diff --git a/src/mqt/predictor/rl/helper.py b/src/mqt/predictor/rl/helper.py index 2226cc151..e5fe95d52 100644 --- a/src/mqt/predictor/rl/helper.py +++ b/src/mqt/predictor/rl/helper.py @@ -16,6 +16,7 @@ import numpy as np from qiskit import QuantumCircuit + from mqt.predictor.utils import calc_supermarq_features if TYPE_CHECKING: @@ -27,6 +28,7 @@ logger = logging.getLogger("mqt-predictor") + def get_state_sample(max_qubits: int, path_training_circuits: Path, rng: Generator) -> tuple[QuantumCircuit, str]: """Returns a random quantum circuit from the training circuits folder. diff --git a/uv.lock b/uv.lock index 7f0a518db..8a844219a 100644 --- a/uv.lock +++ b/uv.lock @@ -12,7 +12,7 @@ resolution-markers = [ ] [manifest] -overrides = [{ name = "networkx", marker = "python_full_version < '3.13'", specifier = "==2.8.5" }] +overrides = [{ name = "networkx", specifier = "==2.8.5" }] [[package]] name = "absl-py" @@ -910,16 +910,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/34/80/de3eb55eb581815342d097214bed4c59e806b05f1b3110df03b2280d6dfd/grpcio-1.74.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd3c71aeee838299c5887230b8a1822795325ddfea635edd82954c1eaa831e24", size = 4489214, upload-time = "2025-07-24T18:53:59.771Z" }, ] +[[package]] +name = "gymnasium" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", + "python_full_version < '3.11' and 'x86_64' in platform_machine and sys_platform == 'darwin'", +] +dependencies = [ + { name = "cloudpickle", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "farama-notifications", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/12/1047b8fdbfcdce74022048d916e844ad7e6e1114d81d26a7aed657e3a76d/gymnasium-1.0.0.tar.gz", hash = "sha256:9d2b66f30c1b34fe3c2ce7fae65ecf365d0e9982d2b3d860235e773328a3b403", size = 821389, upload-time = "2024-10-08T10:01:02.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/85/f5039ce2df5f0789ff1240f08a59f3e8c92e4c5f99543b7aad7388532f7c/gymnasium-1.0.0-py3-none-any.whl", hash = "sha256:b6f40e1e24c5bd419361e1a5b86a9117d2499baecc3a660d44dfff4c465393ad", size = 958120, upload-time = "2024-10-08T10:01:00.337Z" }, +] + [[package]] name = "gymnasium" version = "1.2.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", + "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')", +] dependencies = [ - { name = "cloudpickle" }, - { name = "farama-notifications" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin')" }, + { name = "cloudpickle", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "farama-notifications", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')" }, { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13' and 'x86_64' not in platform_machine) or (python_full_version >= '3.11' and sys_platform != 'darwin') or (python_full_version >= '3.13' and sys_platform == 'darwin')" }, - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fd/17/c2a0e15c2cd5a8e788389b280996db927b923410de676ec5c7b2695e9261/gymnasium-1.2.0.tar.gz", hash = "sha256:344e87561012558f603880baf264ebc97f8a5c997a957b0c9f910281145534b0", size = 821142, upload-time = "2025-06-27T08:21:20.262Z" } wheels = [ @@ -1499,7 +1525,7 @@ name = "mqt-bench" version = "2.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "networkx", marker = "python_full_version < '3.13'" }, + { name = "networkx" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin')" }, { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13' and 'x86_64' not in platform_machine) or (python_full_version >= '3.11' and sys_platform != 'darwin') or (python_full_version >= '3.13' and sys_platform == 'darwin')" }, { name = "qiskit", extra = ["qasm3-import"] }, @@ -1522,12 +1548,13 @@ dependencies = [ { name = "pytket-qiskit" }, { name = "qiskit" }, { name = "qiskit-ibm-ai-local-transpiler", marker = "python_full_version < '3.13'" }, - { name = "qiskit-ibm-transpiler" }, + { name = "qiskit-ibm-transpiler", marker = "python_full_version < '3.13'" }, { name = "rich" }, - { name = "sb3-contrib" }, + { name = "sb3-contrib", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "sb3-contrib", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, { name = "scikit-learn" }, { name = "tensorboard" }, - { name = "torch" }, + { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, { name = "tqdm" }, { name = "typing-extensions" }, ] @@ -1579,12 +1606,12 @@ requires-dist = [ { name = "pytket-qiskit", specifier = ">=0.71.0" }, { name = "qiskit", specifier = "!=1.3.2" }, { name = "qiskit-ibm-ai-local-transpiler", marker = "python_full_version < '3.13'", specifier = ">=0.3.2" }, - { name = "qiskit-ibm-transpiler", specifier = ">=0.11.1" }, + { name = "qiskit-ibm-transpiler", marker = "python_full_version < '3.13'", specifier = ">=0.11.1" }, { name = "rich", specifier = ">=12.6.0" }, { name = "sb3-contrib", specifier = ">=2.0.0" }, { name = "scikit-learn", specifier = ">=1.5.1" }, { name = "tensorboard", specifier = ">=2.17.0" }, - { name = "torch", specifier = ">=2.7.1,<2.8.0" }, + { name = "torch", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'", specifier = ">=2.2.2,<2.3.0" }, { name = "tqdm", specifier = ">=4.66.0" }, { name = "typing-extensions", specifier = ">=4.1" }, ] @@ -2484,7 +2511,7 @@ dependencies = [ { name = "graphviz" }, { name = "jinja2" }, { name = "lark" }, - { name = "networkx", marker = "python_full_version < '3.13'" }, + { name = "networkx" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin')" }, { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13' and 'x86_64' not in platform_machine) or (python_full_version >= '3.11' and sys_platform != 'darwin') or (python_full_version >= '3.13' and sys_platform == 'darwin')" }, { name = "qwasm" }, @@ -2805,11 +2832,11 @@ name = "qiskit-ibm-transpiler" version = "0.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "backoff" }, + { name = "backoff", marker = "python_full_version < '3.13'" }, { name = "networkx", marker = "python_full_version < '3.13'" }, - { name = "qiskit" }, - { name = "qiskit-qasm3-import" }, - { name = "requests" }, + { name = "qiskit", marker = "python_full_version < '3.13'" }, + { name = "qiskit-qasm3-import", marker = "python_full_version < '3.13'" }, + { name = "requests", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8a/ac/d6f176107a0a07aeae583dbde3e7812cd31f670f0d285a0167ece51f040e/qiskit_ibm_transpiler-0.13.1.tar.gz", hash = "sha256:83631565b3adb6ce3825a82af8b6276e3f5e87d193c6efd01b7edae3bf56b31c", size = 46935, upload-time = "2025-07-21T09:42:49.367Z" } wheels = [ @@ -3054,12 +3081,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3e/79/9bdd52d2a33d468c81c1827de1b588080cb055d1d3561b194ab7bf2635b5/rustworkx-0.16.0-cp39-abi3-win_amd64.whl", hash = "sha256:905df608843c32fa45ac023687769fe13056edf7584474c801d5c50705d76e9b", size = 1953559, upload-time = "2025-01-24T01:22:06.136Z" }, ] +[[package]] +name = "sb3-contrib" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", + "python_full_version < '3.11' and 'x86_64' in platform_machine and sys_platform == 'darwin'", +] +dependencies = [ + { name = "stable-baselines3", version = "2.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/4e/dd31aba603634a7b9473b502fe85b694e4392c448eae0d345640b87211c2/sb3_contrib-2.4.0.tar.gz", hash = "sha256:a3709d97ddd529c45e3d56a5ae7e61a380bb2e11bacb9a3e6951ac0c481350ca", size = 89358, upload-time = "2024-11-18T10:20:53.214Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/de/33d3b00116488de9371a56009089ab408d949cda7057e2560c8efae810df/sb3_contrib-2.4.0-py3-none-any.whl", hash = "sha256:725d90157028a94c69804f2e7332128518fb7cbab39e7e141d2d0355547a72ab", size = 92752, upload-time = "2024-11-18T10:20:51.074Z" }, +] + [[package]] name = "sb3-contrib" version = "2.7.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", + "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')", +] dependencies = [ - { name = "stable-baselines3" }, + { name = "stable-baselines3", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7e/23/2831ccecc8fc2fcda401804f3ed1dc3b81f7c4ad3cb32dc6b1cd79f91c49/sb3_contrib-2.7.0.tar.gz", hash = "sha256:87281ae64e6965e46ac24b58778241d2b07b29996044491267ed844c1a75acfe", size = 90289, upload-time = "2025-07-25T13:02:07.208Z" } wheels = [ @@ -3573,18 +3623,46 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/35/dd/698e7a1701908c62bfe371c197eba258182c3f41a3fc51ae588f8d46f3a7/sspilib-0.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:44b89f866e0d14c8393dbc5a49c59296dd7b83a7ca97a0f9d6bd49cc46a04498", size = 528415, upload-time = "2025-04-30T19:37:13.551Z" }, ] +[[package]] +name = "stable-baselines3" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", + "python_full_version < '3.11' and 'x86_64' in platform_machine and sys_platform == 'darwin'", +] +dependencies = [ + { name = "cloudpickle", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "gymnasium", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "matplotlib", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "pandas", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fa/3b/13aacfe41697455f559449ad0dc5f51b4f30aed8fb000131225c64cc60f4/stable_baselines3-2.4.1.tar.gz", hash = "sha256:3bbf0e46b9aa4b1fd2696ff4c806ddb8ba719966ef0f71fba61f7a177e563c81", size = 212611, upload-time = "2025-01-07T13:22:42.455Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/96/04e1e987b5dd8ac0f192e74ad740fb5d72b4c9acd04f3ee13f5f9d6eacae/stable_baselines3-2.4.1-py3-none-any.whl", hash = "sha256:f74cde94a15c1d9d90343e73d790e1898b4c1cbb3f09eeeb580303d33f3fc4d6", size = 183963, upload-time = "2025-01-07T13:22:39.666Z" }, +] + [[package]] name = "stable-baselines3" version = "2.7.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", + "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')", +] dependencies = [ - { name = "cloudpickle" }, - { name = "gymnasium" }, - { name = "matplotlib" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin')" }, + { name = "cloudpickle", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "gymnasium", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "matplotlib", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')" }, { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13' and 'x86_64' not in platform_machine) or (python_full_version >= '3.11' and sys_platform != 'darwin') or (python_full_version >= '3.13' and sys_platform == 'darwin')" }, - { name = "pandas" }, - { name = "torch" }, + { name = "pandas", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/cc/9a334071fae143bc7177e17a3191db83c1a4bf9038b09c4c5a34e427ca33/stable_baselines3-2.7.0.tar.gz", hash = "sha256:5258561e5becd15234274262cf09fcb9a082a73c2c67a85322f5652a05195ec4", size = 219012, upload-time = "2025-07-25T09:54:35.113Z" } wheels = [ @@ -3754,15 +3832,47 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] +[[package]] +name = "torch" +version = "2.2.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", + "python_full_version < '3.11' and 'x86_64' in platform_machine and sys_platform == 'darwin'", +] +dependencies = [ + { name = "filelock", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "fsspec", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "jinja2", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "networkx", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "sympy", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/55/7192974ab13e5e5577f45d14ce70d42f5a9a686b4f57bbe8c9ab45c4a61a/torch-2.2.2-cp310-none-macosx_10_9_x86_64.whl", hash = "sha256:b2e2200b245bd9f263a0d41b6a2dab69c4aca635a01b30cca78064b0ef5b109e", size = 150788930, upload-time = "2024-03-27T21:08:09.98Z" }, + { url = "https://files.pythonhosted.org/packages/33/6b/21496316c9b8242749ee2a9064406271efdf979e91d440e8a3806b5e84bf/torch-2.2.2-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:877b3e6593b5e00b35bbe111b7057464e76a7dd186a287280d941b564b0563c2", size = 59707286, upload-time = "2024-03-27T21:10:28.154Z" }, + { url = "https://files.pythonhosted.org/packages/3f/14/e105b8ef6d324e789c1589e95cb0ab63f3e07c2216d68b1178b7c21b7d2a/torch-2.2.2-cp311-none-macosx_10_9_x86_64.whl", hash = "sha256:95b9b44f3bcebd8b6cd8d37ec802048c872d9c567ba52c894bba90863a439059", size = 150796474, upload-time = "2024-03-27T21:09:29.142Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/18b9c16c18a77755e7f15173821c7100f11e6b3b7717bea8d729bdeb92c0/torch-2.2.2-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:49aa4126ede714c5aeef7ae92969b4b0bbe67f19665106463c39f22e0a1860d1", size = 59714938, upload-time = "2024-03-27T21:09:34.709Z" }, + { url = "https://files.pythonhosted.org/packages/79/78/29dcab24a344ffd9ee9549ec0ab2c7885c13df61cde4c65836ee275efaeb/torch-2.2.2-cp312-none-macosx_10_9_x86_64.whl", hash = "sha256:eb4d6e9d3663e26cd27dc3ad266b34445a16b54908e74725adb241aa56987533", size = 150797270, upload-time = "2024-03-27T21:08:29.623Z" }, + { url = "https://files.pythonhosted.org/packages/4a/0e/e4e033371a7cba9da0db5ccb507a9174e41b9c29189a932d01f2f61ecfc0/torch-2.2.2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:bf9558da7d2bf7463390b3b2a61a6a3dbb0b45b161ee1dd5ec640bf579d479fc", size = 59678388, upload-time = "2024-03-27T21:08:35.869Z" }, +] + [[package]] name = "torch" version = "2.7.1" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", + "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')", +] dependencies = [ - { name = "filelock" }, - { name = "fsspec" }, - { name = "jinja2" }, - { name = "networkx", marker = "python_full_version < '3.13'" }, + { name = "filelock", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "fsspec", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "jinja2", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "networkx", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, @@ -3777,10 +3887,10 @@ dependencies = [ { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "setuptools", marker = "python_full_version >= '3.12'" }, - { name = "sympy" }, + { name = "setuptools", marker = "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform != 'darwin') or (python_full_version >= '3.13' and sys_platform == 'darwin')" }, + { name = "sympy", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/6a/27/2e06cb52adf89fe6e020963529d17ed51532fc73c1e6d1b18420ef03338c/torch-2.7.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a103b5d782af5bd119b81dbcc7ffc6fa09904c423ff8db397a1e6ea8fd71508f", size = 99089441, upload-time = "2025-06-04T17:38:48.268Z" }, From 907ce2b027cbc232a0a0bab82be921c1419f54e0 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Mon, 8 Sep 2025 17:56:44 +0200 Subject: [PATCH 33/57] Fix dependencies --- pyproject.toml | 2 +- uv.lock | 58 +++++++++++++++++++++++++------------------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c3a370b03..8ee0e59ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ dependencies = [ "numpy>=1.24; python_version >= '3.11'", "numpy>=1.22", "numpy>=1.22,<2; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict numpy v2 for macOS x86 since it is not supported anymore since torch v2.3.0 - "torch>=2.2.2,<2.3.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch v2.3.0 for macOS x86 since it is not supported anymore. + "torch>=2.8.0,<2.9.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch v2.3.0 for macOS x86 since it is not supported anymore. "typing-extensions>=4.1", # for `assert_never` "qiskit-ibm-transpiler>=0.11.1; python_version < '3.13'", "qiskit-ibm-ai-local-transpiler>=0.3.2; python_version < '3.13'", diff --git a/uv.lock b/uv.lock index 8a844219a..5f5b8cbb2 100644 --- a/uv.lock +++ b/uv.lock @@ -1554,7 +1554,7 @@ dependencies = [ { name = "sb3-contrib", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, { name = "scikit-learn" }, { name = "tensorboard" }, - { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, { name = "tqdm" }, { name = "typing-extensions" }, ] @@ -1611,7 +1611,7 @@ requires-dist = [ { name = "sb3-contrib", specifier = ">=2.0.0" }, { name = "scikit-learn", specifier = ">=1.5.1" }, { name = "tensorboard", specifier = ">=2.17.0" }, - { name = "torch", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'", specifier = ">=2.2.2,<2.3.0" }, + { name = "torch", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'", specifier = ">=2.8.0,<2.9.0" }, { name = "tqdm", specifier = ">=4.66.0" }, { name = "typing-extensions", specifier = ">=4.1" }, ] @@ -3638,7 +3638,7 @@ dependencies = [ { name = "matplotlib", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, { name = "pandas", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fa/3b/13aacfe41697455f559449ad0dc5f51b4f30aed8fb000131225c64cc60f4/stable_baselines3-2.4.1.tar.gz", hash = "sha256:3bbf0e46b9aa4b1fd2696ff4c806ddb8ba719966ef0f71fba61f7a177e563c81", size = 212611, upload-time = "2025-01-07T13:22:42.455Z" } wheels = [ @@ -3832,32 +3832,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] -[[package]] -name = "torch" -version = "2.2.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "python_full_version < '3.11' and 'x86_64' in platform_machine and sys_platform == 'darwin'", -] -dependencies = [ - { name = "filelock", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "fsspec", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "jinja2", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "networkx", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "sympy", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "typing-extensions", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/55/7192974ab13e5e5577f45d14ce70d42f5a9a686b4f57bbe8c9ab45c4a61a/torch-2.2.2-cp310-none-macosx_10_9_x86_64.whl", hash = "sha256:b2e2200b245bd9f263a0d41b6a2dab69c4aca635a01b30cca78064b0ef5b109e", size = 150788930, upload-time = "2024-03-27T21:08:09.98Z" }, - { url = "https://files.pythonhosted.org/packages/33/6b/21496316c9b8242749ee2a9064406271efdf979e91d440e8a3806b5e84bf/torch-2.2.2-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:877b3e6593b5e00b35bbe111b7057464e76a7dd186a287280d941b564b0563c2", size = 59707286, upload-time = "2024-03-27T21:10:28.154Z" }, - { url = "https://files.pythonhosted.org/packages/3f/14/e105b8ef6d324e789c1589e95cb0ab63f3e07c2216d68b1178b7c21b7d2a/torch-2.2.2-cp311-none-macosx_10_9_x86_64.whl", hash = "sha256:95b9b44f3bcebd8b6cd8d37ec802048c872d9c567ba52c894bba90863a439059", size = 150796474, upload-time = "2024-03-27T21:09:29.142Z" }, - { url = "https://files.pythonhosted.org/packages/96/23/18b9c16c18a77755e7f15173821c7100f11e6b3b7717bea8d729bdeb92c0/torch-2.2.2-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:49aa4126ede714c5aeef7ae92969b4b0bbe67f19665106463c39f22e0a1860d1", size = 59714938, upload-time = "2024-03-27T21:09:34.709Z" }, - { url = "https://files.pythonhosted.org/packages/79/78/29dcab24a344ffd9ee9549ec0ab2c7885c13df61cde4c65836ee275efaeb/torch-2.2.2-cp312-none-macosx_10_9_x86_64.whl", hash = "sha256:eb4d6e9d3663e26cd27dc3ad266b34445a16b54908e74725adb241aa56987533", size = 150797270, upload-time = "2024-03-27T21:08:29.623Z" }, - { url = "https://files.pythonhosted.org/packages/4a/0e/e4e033371a7cba9da0db5ccb507a9174e41b9c29189a932d01f2f61ecfc0/torch-2.2.2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:bf9558da7d2bf7463390b3b2a61a6a3dbb0b45b161ee1dd5ec640bf579d479fc", size = 59678388, upload-time = "2024-03-27T21:08:35.869Z" }, -] - [[package]] name = "torch" version = "2.7.1" @@ -3915,6 +3889,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b1/29/beb45cdf5c4fc3ebe282bf5eafc8dfd925ead7299b3c97491900fe5ed844/torch-2.7.1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:988b0cbc4333618a1056d2ebad9eb10089637b659eb645434d0809d8d937b946", size = 68645708, upload-time = "2025-06-04T17:34:39.852Z" }, ] +[[package]] +name = "torch" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", + "python_full_version < '3.11' and 'x86_64' in platform_machine and sys_platform == 'darwin'", +] +dependencies = [ + { name = "filelock", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "fsspec", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "jinja2", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "networkx", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "setuptools", marker = "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "sympy", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/d6/e6d4c57e61c2b2175d3aafbfb779926a2cfd7c32eeda7c543925dceec923/torch-2.8.0-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:a3f16a58a9a800f589b26d47ee15aca3acf065546137fc2af039876135f4c760", size = 73611154, upload-time = "2025-08-06T14:53:10.919Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5e/05a5c46085d9b97e928f3f037081d3d2b87fb4b4195030fc099aaec5effc/torch-2.8.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:5ae0524688fb6707c57a530c2325e13bb0090b745ba7b4a2cd6a3ce262572916", size = 73621174, upload-time = "2025-08-06T14:53:25.44Z" }, + { url = "https://files.pythonhosted.org/packages/be/66/5c9a321b325aaecb92d4d1855421e3a055abd77903b7dab6575ca07796db/torch-2.8.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:619c2869db3ada2c0105487ba21b5008defcc472d23f8b80ed91ac4a380283b0", size = 73630478, upload-time = "2025-08-06T14:53:57.144Z" }, + { url = "https://files.pythonhosted.org/packages/de/69/8b7b13bba430f5e21d77708b616f767683629fc4f8037564a177d20f90ed/torch-2.8.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:1a62a1ec4b0498930e2543535cf70b1bef8c777713de7ceb84cd79115f553767", size = 73915128, upload-time = "2025-08-06T14:54:34.769Z" }, + { url = "https://files.pythonhosted.org/packages/04/6e/650bb7f28f771af0cb791b02348db8b7f5f64f40f6829ee82aa6ce99aabe/torch-2.8.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:7b677e17f5a3e69fdef7eb3b9da72622f8d322692930297e4ccb52fefc6c8211", size = 73632395, upload-time = "2025-08-06T14:55:28.645Z" }, +] + [[package]] name = "tornado" version = "6.5.1" From 70dcd7d2b22a24e3a1e1e2dc3123e899741a0e92 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Mon, 8 Sep 2025 19:41:08 +0200 Subject: [PATCH 34/57] Fix dependencies --- pyproject.toml | 1 + uv.lock | 44 ++++++++++---------------------------------- 2 files changed, 11 insertions(+), 34 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8ee0e59ce..8bf021e36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ dependencies = [ "pytket>=1.29.0", # lowest version that supports the used pytket AutoRebase pass instead of auto_rebase "pytket_qiskit>=0.71.0", "sb3_contrib>=2.0.0", + "stable-baselines3>=2.7.0", "tqdm>=4.66.0", "rich>=12.6.0", "scikit-learn>=1.5.1", diff --git a/uv.lock b/uv.lock index 5f5b8cbb2..a0b74975a 100644 --- a/uv.lock +++ b/uv.lock @@ -1553,6 +1553,7 @@ dependencies = [ { name = "sb3-contrib", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, { name = "sb3-contrib", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, { name = "scikit-learn" }, + { name = "stable-baselines3" }, { name = "tensorboard" }, { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, { name = "tqdm" }, @@ -1610,6 +1611,7 @@ requires-dist = [ { name = "rich", specifier = ">=12.6.0" }, { name = "sb3-contrib", specifier = ">=2.0.0" }, { name = "scikit-learn", specifier = ">=1.5.1" }, + { name = "stable-baselines3", specifier = ">=2.7.0" }, { name = "tensorboard", specifier = ">=2.17.0" }, { name = "torch", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'", specifier = ">=2.8.0,<2.9.0" }, { name = "tqdm", specifier = ">=4.66.0" }, @@ -3091,7 +3093,7 @@ resolution-markers = [ "python_full_version < '3.11' and 'x86_64' in platform_machine and sys_platform == 'darwin'", ] dependencies = [ - { name = "stable-baselines3", version = "2.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, + { name = "stable-baselines3", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c7/4e/dd31aba603634a7b9473b502fe85b694e4392c448eae0d345640b87211c2/sb3_contrib-2.4.0.tar.gz", hash = "sha256:a3709d97ddd529c45e3d56a5ae7e61a380bb2e11bacb9a3e6951ac0c481350ca", size = 89358, upload-time = "2024-11-18T10:20:53.214Z" } wheels = [ @@ -3109,7 +3111,7 @@ resolution-markers = [ "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')", ] dependencies = [ - { name = "stable-baselines3", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "stable-baselines3", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7e/23/2831ccecc8fc2fcda401804f3ed1dc3b81f7c4ad3cb32dc6b1cd79f91c49/sb3_contrib-2.7.0.tar.gz", hash = "sha256:87281ae64e6965e46ac24b58778241d2b07b29996044491267ed844c1a75acfe", size = 90289, upload-time = "2025-07-25T13:02:07.208Z" } wheels = [ @@ -3623,46 +3625,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/35/dd/698e7a1701908c62bfe371c197eba258182c3f41a3fc51ae588f8d46f3a7/sspilib-0.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:44b89f866e0d14c8393dbc5a49c59296dd7b83a7ca97a0f9d6bd49cc46a04498", size = 528415, upload-time = "2025-04-30T19:37:13.551Z" }, ] -[[package]] -name = "stable-baselines3" -version = "2.4.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.12.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and 'x86_64' in platform_machine and sys_platform == 'darwin'", - "python_full_version < '3.11' and 'x86_64' in platform_machine and sys_platform == 'darwin'", -] -dependencies = [ - { name = "cloudpickle", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "gymnasium", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "matplotlib", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "pandas", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fa/3b/13aacfe41697455f559449ad0dc5f51b4f30aed8fb000131225c64cc60f4/stable_baselines3-2.4.1.tar.gz", hash = "sha256:3bbf0e46b9aa4b1fd2696ff4c806ddb8ba719966ef0f71fba61f7a177e563c81", size = 212611, upload-time = "2025-01-07T13:22:42.455Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/96/04e1e987b5dd8ac0f192e74ad740fb5d72b4c9acd04f3ee13f5f9d6eacae/stable_baselines3-2.4.1-py3-none-any.whl", hash = "sha256:f74cde94a15c1d9d90343e73d790e1898b4c1cbb3f09eeeb580303d33f3fc4d6", size = 183963, upload-time = "2025-01-07T13:22:39.666Z" }, -] - [[package]] name = "stable-baselines3" version = "2.7.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.13'", - "(python_full_version == '3.12.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.12.*' and sys_platform != 'darwin')", - "(python_full_version == '3.11.*' and 'x86_64' not in platform_machine) or (python_full_version == '3.11.*' and sys_platform != 'darwin')", - "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')", -] dependencies = [ - { name = "cloudpickle", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "cloudpickle" }, + { name = "gymnasium", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, { name = "gymnasium", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, - { name = "matplotlib", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and 'x86_64' not in platform_machine) or (python_full_version < '3.11' and sys_platform != 'darwin')" }, + { name = "matplotlib" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin')" }, { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13' and 'x86_64' not in platform_machine) or (python_full_version >= '3.11' and sys_platform != 'darwin') or (python_full_version >= '3.13' and sys_platform == 'darwin')" }, - { name = "pandas", marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "pandas" }, { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13' or (python_full_version < '3.13' and 'x86_64' not in platform_machine) or sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/cc/9a334071fae143bc7177e17a3191db83c1a4bf9038b09c4c5a34e427ca33/stable_baselines3-2.7.0.tar.gz", hash = "sha256:5258561e5becd15234274262cf09fcb9a082a73c2c67a85322f5652a05195ec4", size = 219012, upload-time = "2025-07-25T09:54:35.113Z" } wheels = [ From eb27098628671cda269bbec755ebcf9ab90183fd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 17:55:02 +0000 Subject: [PATCH 35/57] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- uv.lock | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/uv.lock b/uv.lock index 5294eb3f5..a0b74975a 100644 --- a/uv.lock +++ b/uv.lock @@ -3096,7 +3096,7 @@ dependencies = [ { name = "stable-baselines3", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c7/4e/dd31aba603634a7b9473b502fe85b694e4392c448eae0d345640b87211c2/sb3_contrib-2.4.0.tar.gz", hash = "sha256:a3709d97ddd529c45e3d56a5ae7e61a380bb2e11bacb9a3e6951ac0c481350ca", size = 89358, upload-time = "2024-11-18T10:20:53.214Z" } -wheels = [ +wheels = [ { url = "https://files.pythonhosted.org/packages/50/de/33d3b00116488de9371a56009089ab408d949cda7057e2560c8efae810df/sb3_contrib-2.4.0-py3-none-any.whl", hash = "sha256:725d90157028a94c69804f2e7332128518fb7cbab39e7e141d2d0355547a72ab", size = 92752, upload-time = "2024-11-18T10:20:51.074Z" }, ] @@ -4014,4 +4014,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50e wheels = [ { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, ] - From 2688c0e45d6834623f2e2d190ba27a5af06aad9b Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Mon, 8 Sep 2025 17:56:44 +0200 Subject: [PATCH 36/57] Fix dependencies Fix dependencies Fix dependencies Update Dependencies Update test command Fix multiprocessing on Python 3.13 Fix multiprocessing on Python 3.13 --- noxfile.py | 1 + pyproject.toml | 4 +-- src/mqt/predictor/ml/predictor.py | 48 ++++++++++++++++++++++--------- uv.lock | 4 +-- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/noxfile.py b/noxfile.py index 9e13b37f9..3b1876a5c 100644 --- a/noxfile.py +++ b/noxfile.py @@ -64,6 +64,7 @@ def _run_tests( "--no-dev", "--group", "test", + "--frozen", *install_args, "pytest", *pytest_run_args, diff --git a/pyproject.toml b/pyproject.toml index 8bf021e36..a505249fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,8 +47,8 @@ dependencies = [ "numpy>=1.22,<2; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict numpy v2 for macOS x86 since it is not supported anymore since torch v2.3.0 "torch>=2.8.0,<2.9.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch v2.3.0 for macOS x86 since it is not supported anymore. "typing-extensions>=4.1", # for `assert_never` - "qiskit-ibm-transpiler>=0.11.1; python_version < '3.13'", - "qiskit-ibm-ai-local-transpiler>=0.3.2; python_version < '3.13'", + "qiskit-ibm-transpiler==0.13.1; python_version < '3.13'", + "qiskit-ibm-ai-local-transpiler==0.4.2; python_version < '3.13'", ] classifiers = [ diff --git a/src/mqt/predictor/ml/predictor.py b/src/mqt/predictor/ml/predictor.py index 3f0ec5497..0a7a62382 100644 --- a/src/mqt/predictor/ml/predictor.py +++ b/src/mqt/predictor/ml/predictor.py @@ -61,6 +61,8 @@ logger = logging.getLogger("mqt-predictor") +NO_PARALLEL = sys.platform == "win32" and sys.version_info >= (3, 13) + def setup_device_predictor( devices: list[Target], @@ -227,12 +229,20 @@ def compile_training_circuits( with zipfile.ZipFile(str(path_zip), "r") as zip_ref: zip_ref.extractall(path_uncompiled_circuits) - Parallel(n_jobs=num_workers, verbose=100)( - delayed(self._compile_all_circuits_devicewise)( - device, timeout, path_uncompiled_circuits, path_compiled_circuits, logger.level + # On Windows + Python 3.13, joblib's default "loky" process backend is broken + # (missing `_posixsubprocess`). Fall back to no multiprocessing. + if NO_PARALLEL: + for device in self.devices: + self._compile_all_circuits_devicewise( + device, timeout, path_uncompiled_circuits, path_compiled_circuits, logger.level + ) + else: + Parallel(n_jobs=num_workers, verbose=100)( + delayed(self._compile_all_circuits_devicewise)( + device, timeout, path_uncompiled_circuits, path_compiled_circuits, logger.level + ) + for device in self.devices ) - for device in self.devices - ) def generate_training_data( self, @@ -267,15 +277,27 @@ def generate_training_data( names_list = [] scores_list = [] - results = Parallel(n_jobs=num_workers, verbose=100)( - delayed(self._generate_training_sample)( - filename.name, - path_uncompiled_circuits, - path_compiled_circuits, - logger.level, + if NO_PARALLEL: + results = Parallel(n_jobs=1, verbose=100)( + delayed(self._generate_training_sample)( + filename.name, + path_uncompiled_circuits, + path_compiled_circuits, + logger.level, + ) + for filename in path_uncompiled_circuits.glob("*.qasm") ) - for filename in path_uncompiled_circuits.glob("*.qasm") - ) + else: + results = Parallel(n_jobs=num_workers, verbose=100)( + delayed(self._generate_training_sample)( + filename.name, + path_uncompiled_circuits, + path_compiled_circuits, + logger.level, + ) + for filename in path_uncompiled_circuits.glob("*.qasm") + ) + for sample in results: training_sample, circuit_name, scores = sample if all(score == -1 for score in scores): diff --git a/uv.lock b/uv.lock index a0b74975a..9c915f7fe 100644 --- a/uv.lock +++ b/uv.lock @@ -1606,8 +1606,8 @@ requires-dist = [ { name = "pytket", specifier = ">=1.29.0" }, { name = "pytket-qiskit", specifier = ">=0.71.0" }, { name = "qiskit", specifier = "!=1.3.2" }, - { name = "qiskit-ibm-ai-local-transpiler", marker = "python_full_version < '3.13'", specifier = ">=0.3.2" }, - { name = "qiskit-ibm-transpiler", marker = "python_full_version < '3.13'", specifier = ">=0.11.1" }, + { name = "qiskit-ibm-ai-local-transpiler", marker = "python_full_version < '3.13'", specifier = "==0.4.2" }, + { name = "qiskit-ibm-transpiler", marker = "python_full_version < '3.13'", specifier = "==0.13.1" }, { name = "rich", specifier = ">=12.6.0" }, { name = "sb3-contrib", specifier = ">=2.0.0" }, { name = "scikit-learn", specifier = ">=1.5.1" }, From 840d23da68c610fa7d89ffb61551d1bfdbc5f28a Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Tue, 9 Sep 2025 17:52:47 +0200 Subject: [PATCH 37/57] Fix multiprocessing on Python 3.13 Fix runtime warning error --- pyproject.toml | 2 ++ src/mqt/predictor/ml/predictor.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a505249fc..262e5d5a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,6 +130,8 @@ filterwarnings = [ 'ignore:.*qiskit.providers.models is deprecated since Qiskit 1.2*:DeprecationWarning:', 'ignore:.*The class ``qiskit.qobj.*`` is deprecated as of Qiskit 1.3.*:DeprecationWarning:', 'ignore:.*The property ``qiskit.circuit.instruction.Instruction.*`` is deprecated as of qiskit 1.3.0.*:DeprecationWarning:', + 'ignore:.*Timeout is not supported on Windows\\.:RuntimeWarning', + ] [tool.coverage] diff --git a/src/mqt/predictor/ml/predictor.py b/src/mqt/predictor/ml/predictor.py index 0a7a62382..b05c51efd 100644 --- a/src/mqt/predictor/ml/predictor.py +++ b/src/mqt/predictor/ml/predictor.py @@ -427,7 +427,10 @@ def train_random_forest_model( if not training_data: training_data = self._get_prepared_training_data() num_cv = min(len(training_data.y_train), 5) - mdl = GridSearchCV(mdl, tree_param, cv=num_cv, n_jobs=8).fit(training_data.X_train, training_data.y_train) + if NO_PARALLEL: + mdl = GridSearchCV(mdl, tree_param, cv=num_cv, n_jobs=1).fit(training_data.X_train, training_data.y_train) + else: + mdl = GridSearchCV(mdl, tree_param, cv=num_cv, n_jobs=8).fit(training_data.X_train, training_data.y_train) joblib_dump(mdl, save_mdl_path) logger.info("Random Forest model is trained and saved.") From ac406ac1903fe372989d6f5152401baff20d6bec Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Tue, 9 Sep 2025 21:35:21 +0200 Subject: [PATCH 38/57] Fix timeout watcher Add comments Update actions --- pyproject.toml | 4 +- src/mqt/predictor/ml/predictor.py | 54 ++++++++------------- src/mqt/predictor/rl/actions.py | 41 ++++++++++++---- src/mqt/predictor/rl/predictorenv.py | 71 +++++++++++++--------------- src/mqt/predictor/utils.py | 3 +- 5 files changed, 87 insertions(+), 86 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 262e5d5a0..1fbb96977 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ dependencies = [ "numpy>=1.22,<2; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict numpy v2 for macOS x86 since it is not supported anymore since torch v2.3.0 "torch>=2.8.0,<2.9.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch v2.3.0 for macOS x86 since it is not supported anymore. "typing-extensions>=4.1", # for `assert_never` - "qiskit-ibm-transpiler==0.13.1; python_version < '3.13'", + "qiskit-ibm-transpiler==0.13.1; python_version < '3.13'", # Do not support python 3.13 yet "qiskit-ibm-ai-local-transpiler==0.4.2; python_version < '3.13'", ] @@ -256,5 +256,5 @@ ignore = ["GH200"] [tool.uv] override-dependencies = [ - "networkx==2.8.5", + "networkx==2.8.5", # Required by `qiskit-ibm-transpiler` ] diff --git a/src/mqt/predictor/ml/predictor.py b/src/mqt/predictor/ml/predictor.py index b05c51efd..c1d6f1b15 100644 --- a/src/mqt/predictor/ml/predictor.py +++ b/src/mqt/predictor/ml/predictor.py @@ -231,18 +231,13 @@ def compile_training_circuits( # On Windows + Python 3.13, joblib's default "loky" process backend is broken # (missing `_posixsubprocess`). Fall back to no multiprocessing. - if NO_PARALLEL: - for device in self.devices: - self._compile_all_circuits_devicewise( - device, timeout, path_uncompiled_circuits, path_compiled_circuits, logger.level - ) - else: - Parallel(n_jobs=num_workers, verbose=100)( - delayed(self._compile_all_circuits_devicewise)( - device, timeout, path_uncompiled_circuits, path_compiled_circuits, logger.level - ) - for device in self.devices + num_jobs = 1 if NO_PARALLEL else num_workers + Parallel(n_jobs=num_jobs, verbose=100)( + delayed(self._compile_all_circuits_devicewise)( + device, timeout, path_uncompiled_circuits, path_compiled_circuits, logger.level ) + for device in self.devices + ) def generate_training_data( self, @@ -277,26 +272,16 @@ def generate_training_data( names_list = [] scores_list = [] - if NO_PARALLEL: - results = Parallel(n_jobs=1, verbose=100)( - delayed(self._generate_training_sample)( - filename.name, - path_uncompiled_circuits, - path_compiled_circuits, - logger.level, - ) - for filename in path_uncompiled_circuits.glob("*.qasm") - ) - else: - results = Parallel(n_jobs=num_workers, verbose=100)( - delayed(self._generate_training_sample)( - filename.name, - path_uncompiled_circuits, - path_compiled_circuits, - logger.level, - ) - for filename in path_uncompiled_circuits.glob("*.qasm") + num_jobs = 1 if NO_PARALLEL else num_workers + results = Parallel(n_jobs=num_jobs, verbose=100)( + delayed(self._generate_training_sample)( + filename.name, + path_uncompiled_circuits, + path_compiled_circuits, + logger.level, ) + for filename in path_uncompiled_circuits.glob("*.qasm") + ) for sample in results: training_sample, circuit_name, scores = sample @@ -427,11 +412,10 @@ def train_random_forest_model( if not training_data: training_data = self._get_prepared_training_data() num_cv = min(len(training_data.y_train), 5) - if NO_PARALLEL: - mdl = GridSearchCV(mdl, tree_param, cv=num_cv, n_jobs=1).fit(training_data.X_train, training_data.y_train) - else: - mdl = GridSearchCV(mdl, tree_param, cv=num_cv, n_jobs=8).fit(training_data.X_train, training_data.y_train) - + num_jobs = 1 if NO_PARALLEL else 8 + mdl = GridSearchCV(mdl, tree_param, cv=num_cv, n_jobs=num_jobs).fit( + training_data.X_train, training_data.y_train + ) joblib_dump(mdl, save_mdl_path) logger.info("Random Forest model is trained and saved.") diff --git a/src/mqt/predictor/rl/actions.py b/src/mqt/predictor/rl/actions.py index 510653781..c83bcf766 100644 --- a/src/mqt/predictor/rl/actions.py +++ b/src/mqt/predictor/rl/actions.py @@ -135,7 +135,7 @@ class Action: ] ) stochastic: bool | None = False - preserve: bool | None = False + preserve_layout: bool | None = False @dataclass @@ -188,11 +188,22 @@ def remove_action(name: str) -> None: register_action( - DeviceDependentAction( + DeviceIndependentAction( "Optimize1qGatesDecomposition", CompilationOrigin.QISKIT, PassType.OPT, - preserve=True, + # If no basis_gates are passed, fallback 1q Euler gates: + # https://github.com/Qiskit/qiskit/blob/main/qiskit/synthesis/one_qubit/one_qubit_decompose.py#L46 + [Optimize1qGatesDecomposition()], + ) +) + +register_action( + DeviceDependentAction( + "Optimize1qGatesDecomposition_preserve", + CompilationOrigin.QISKIT, + PassType.OPT, + preserve_layout=True, transpile_pass=lambda device: [Optimize1qGatesDecomposition(basis=device.operation_names)], ) ) @@ -203,7 +214,7 @@ def remove_action(name: str) -> None: CompilationOrigin.QISKIT, PassType.OPT, [CommutativeCancellation()], - preserve=True, + preserve_layout=True, ) ) @@ -213,7 +224,7 @@ def remove_action(name: str) -> None: CompilationOrigin.QISKIT, PassType.OPT, [CommutativeInverseCancellation()], - preserve=True, + preserve_layout=True, ) ) @@ -223,7 +234,7 @@ def remove_action(name: str) -> None: CompilationOrigin.QISKIT, PassType.OPT, [RemoveDiagonalGatesBeforeMeasure()], - preserve=True, + preserve_layout=True, ) ) @@ -248,7 +259,7 @@ def remove_action(name: str) -> None: (SXGate(), SXdgGate()), ]) ], - preserve=True, + preserve_layout=True, ) ) @@ -262,16 +273,27 @@ def remove_action(name: str) -> None: ) register_action( - DeviceDependentAction( + DeviceIndependentAction( "Opt2qBlocks", CompilationOrigin.QISKIT, PassType.OPT, + # If no arguments is passed, decompose to U(θ, φ, λ) and CX + # https://github.com/Qiskit/qiskit/blob/stable/2.1/qiskit/transpiler/passes/synthesis/default_unitary_synth_plugin.py + [Collect2qBlocks(), ConsolidateBlocks(), UnitarySynthesis()], + ) +) + +register_action( + DeviceDependentAction( + "Opt2qBlocks_preserve", + CompilationOrigin.QISKIT, + PassType.OPT, transpile_pass=lambda native_gate, coupling_map: [ Collect2qBlocks(), ConsolidateBlocks(basis_gates=native_gate), UnitarySynthesis(basis_gates=native_gate, coupling_map=coupling_map), ], - preserve=True, + preserve_layout=True, ) ) @@ -336,6 +358,7 @@ def remove_action(name: str) -> None: MinimumPoint(["depth", "size"], "optimization_loop"), ], do_while=lambda property_set: not property_set["optimization_loop_minimum_point"], + preserve_layout=True, ) ) diff --git a/src/mqt/predictor/rl/predictorenv.py b/src/mqt/predictor/rl/predictorenv.py index 01ae26ee1..830a85293 100644 --- a/src/mqt/predictor/rl/predictorenv.py +++ b/src/mqt/predictor/rl/predictorenv.py @@ -113,7 +113,7 @@ def __init__( self.actions_mapping_indices = [] self.actions_opt_indices = [] self.actions_final_optimization_indices = [] - self.actions_preserving_indices = [] + self.actions_mapping_preserving_indices = [] self.used_actions: list[str] = [] self.device = device @@ -134,7 +134,7 @@ def __init__( self.action_set[index] = elem self.actions_opt_indices.append(index) if getattr(elem, "preserve", False): - self.actions_preserving_indices.append(index) + self.actions_mapping_preserving_indices.append(index) index += 1 for elem in action_dict[PassType.LAYOUT]: self.action_set[index] = elem @@ -241,26 +241,16 @@ def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any def calculate_reward(self, qc: QuantumCircuit | None = None) -> float: """Calculates and returns the reward for the current state.""" - if qc is None: - if self.reward_function == "expected_fidelity": - return expected_fidelity(self.state, self.device) - if self.reward_function == "estimated_success_probability": - return estimated_success_probability(self.state, self.device) - if self.reward_function == "estimated_hellinger_distance": - return estimated_hellinger_distance(self.state, self.device, self.hellinger_model) - if self.reward_function == "critical_depth": - return crit_depth(self.state) - assert_never(self.state) - else: - if self.reward_function == "expected_fidelity": - return expected_fidelity(qc, self.device) - if self.reward_function == "estimated_success_probability": - return estimated_success_probability(qc, self.device) - if self.reward_function == "estimated_hellinger_distance": - return estimated_hellinger_distance(qc, self.device, self.hellinger_model) - if self.reward_function == "critical_depth": - return crit_depth(qc) - assert_never(self.state) + circuit = self.state if qc is None else qc + if self.reward_function == "expected_fidelity": + return expected_fidelity(circuit, self.device) + if self.reward_function == "estimated_success_probability": + return estimated_success_probability(circuit, self.device) + if self.reward_function == "estimated_hellinger_distance": + return estimated_hellinger_distance(circuit, self.device, self.hellinger_model) + if self.reward_function == "critical_depth": + return crit_depth(circuit) + assert_never(circuit) def render(self) -> None: """Renders the current state.""" @@ -310,7 +300,7 @@ def action_masks(self) -> list[bool]: """Returns a list of valid actions for the current state.""" action_mask = [action in self.valid_actions for action in self.action_set] - # # it is not clear how tket will handle the layout, so we remove all actions that are from "origin"=="tket" if a layout is set + # it is not clear how tket will handle the layout, so we remove all actions that are from "origin"=="tket" if a layout is set if self.layout is not None: action_mask = [ action_mask[i] and self.action_set[i].origin != CompilationOrigin.TKET for i in range(len(action_mask)) @@ -620,30 +610,35 @@ def _apply_bqskit_action(self, action: Action, action_index: int) -> QuantumCirc def determine_valid_actions_for_state(self) -> list[int]: """Determines and returns the valid actions for the current state.""" + # Check if all gates are native to the device check_nat_gates = GatesInBasis(basis_gates=self.device.operation_names) check_nat_gates(self.state) only_nat_gates = check_nat_gates.property_set["all_gates_in_basis"] - + # Check if the circuit is mapped to the device coupling graph check_mapping = CheckMap(coupling_map=CouplingMap(self.device.build_coupling_map())) check_mapping(self.state) mapped = check_mapping.property_set["is_swap_mapped"] - if not only_nat_gates: # not native gates yet + if not only_nat_gates: + # Circuit still has non-native gates if not mapped: + # Allow synthesis and optimization actions return self.actions_synthesis_indices + self.actions_opt_indices - return self.actions_synthesis_indices + self.actions_preserving_indices - - if mapped and self.layout is not None: # The circuit is correctly mapped - return [ - self.action_terminate_index, - *self.actions_preserving_indices, - *self.actions_final_optimization_indices, - ] - + # Allow synthesis and mapping-preserving actions (to not ) + return self.actions_synthesis_indices + self.actions_mapping_preserving_indices + # Circuit has only native gates + if mapped: + if self.layout is not None: + # The circuits is correctly compiled, terminate or do further mapping-preserving optimizations + return [ + self.action_terminate_index, + *self.actions_mapping_preserving_indices, + *self.actions_final_optimization_indices, + ] + # No layout is assigned, assign a valid layout + return self.actions_mapping_preserving_indices + self.actions_layout_indices + self.actions_mapping_indices if self.layout is not None: - # The circuit is not yet mapped but a layout is set. + # Not mapped yet but a layout is assigned in the last step, do routing return self.actions_routing_indices - # The circuit already fulfils coupling map but no layout is assigned, could explore better layout options - if mapped: - return self.actions_preserving_indices + self.actions_layout_indices + self.actions_mapping_indices + # Not mapped yet, do general optimizations/layout/mapping return self.actions_opt_indices + self.actions_layout_indices + self.actions_mapping_indices diff --git a/src/mqt/predictor/utils.py b/src/mqt/predictor/utils.py index faf3d0596..4112dfb13 100644 --- a/src/mqt/predictor/utils.py +++ b/src/mqt/predictor/utils.py @@ -15,7 +15,6 @@ import sys from dataclasses import dataclass from typing import TYPE_CHECKING, Any -from warnings import warn import networkx as nx import numpy as np @@ -52,7 +51,7 @@ def timeout_watcher( TimeoutExceptionError: If the function call exceeds the timeout limit. """ if sys.platform == "win32": - warn("Timeout is not supported on Windows.", category=RuntimeWarning, stacklevel=2) + logger.info("Timeout is not supported on Windows; running without timeout.") return func(*args) if isinstance(args, tuple | list) else func(args) class TimeoutExceptionError(Exception): # Custom exception class From 97c81bb3bf08beb8c66e276f21bfb6392e1b2f7e Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Mon, 15 Sep 2025 15:52:16 +0200 Subject: [PATCH 39/57] Update comments and restructure Adjust comments --- src/mqt/predictor/rl/parsing.py | 28 +++++++++++++++ src/mqt/predictor/rl/predictorenv.py | 53 +++++++++------------------- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/src/mqt/predictor/rl/parsing.py b/src/mqt/predictor/rl/parsing.py index 552817556..816e7aa48 100644 --- a/src/mqt/predictor/rl/parsing.py +++ b/src/mqt/predictor/rl/parsing.py @@ -241,3 +241,31 @@ def postprocess_vf2postlayout( altered_qc = apply_layout(qc) return altered_qc, apply_layout + + +def prepare_noise_data(device: Target) -> tuple[dict[Node, float], dict[tuple[Node, Node], float], dict[Node, float]]: + """Extract node, edge, and readout errors from the device target.""" + node_err: dict[Node, float] = {} + edge_err: dict[tuple[Node, Node], float] = {} + readout_err: dict[Node, float] = {} + + # Collect errors from operation properties + for op_name in device.operation_names: + inst_props = device[op_name] + for qtuple, props in inst_props.items(): + if props is None or not hasattr(props, "error") or props.error is None: + continue + if len(qtuple) == 1: # single-qubit op + q = qtuple[0] + node_err[Node(q)] = props.error + elif len(qtuple) == 2: # two-qubit op + q1, q2 = qtuple + edge_err[Node(q1), Node(q2)] = props.error + + # Collect readout errors + if "measure" in device: + for (q,), props in device["measure"].items(): + if props is not None and hasattr(props, "error") and props.error is not None: + readout_err[Node(q)] = props.error + + return node_err, edge_err, readout_err diff --git a/src/mqt/predictor/rl/predictorenv.py b/src/mqt/predictor/rl/predictorenv.py index 830a85293..8e2b5e1d4 100644 --- a/src/mqt/predictor/rl/predictorenv.py +++ b/src/mqt/predictor/rl/predictorenv.py @@ -78,6 +78,7 @@ final_layout_bqskit_to_qiskit, final_layout_pytket_to_qiskit, postprocess_vf2postlayout, + prepare_noise_data, ) logger = logging.getLogger("mqt-predictor") @@ -430,7 +431,7 @@ def _apply_qiskit_action(self, action: Action, action_index: int) -> QuantumCirc max_iteration=self.max_iter, ) else: - if action.name in ["QiskitO3", "Opt2qBlocks"] and isinstance(action, DeviceDependentAction): + if action.name in ["QiskitO3", "Opt2qBlocks_preserve"] and isinstance(action, DeviceDependentAction): passes = action.transpile_pass( self.device.operation_names, CouplingMap(self.device.build_coupling_map()) if self.layout else None, @@ -494,32 +495,9 @@ def _handle_qiskit_layout_postprocessing( def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircuit: tket_qc = qiskit_to_tk(self.state, preserve_param_uuid=True) if action.name == "NoiseAwarePlacement": + # Handle NoiseAwarePlacement separately (requires error data) if self.node_err is None or self.edge_err is None or self.readout_err is None: - node_err: dict[Node, float] = {} - edge_err: dict[tuple[Node, Node], float] = {} - readout_err: dict[Node, float] = {} - - # Calculate avg node, edge and readout error - for op_name in self.device.operation_names: - inst_props = self.device[op_name] # this is a dict-like object - for qtuple, props in inst_props.items(): - if props is None or not hasattr(props, "error") or props.error is None: - continue - if len(qtuple) == 1: # single-qubit op - q = qtuple[0] - node_err[Node(q)] = props.error - elif len(qtuple) == 2: # two-qubit op - q1, q2 = qtuple - edge_err[Node(q1), Node(q2)] = props.error - - # Readout errors (they are in the Target under "measure") - if "measure" in self.device: - for (q,), props in self.device["measure"].items(): - if props is not None and hasattr(props, "error") and props.error is not None: - readout_err[Node(q)] = props.error - self.node_err = node_err - self.edge_err = edge_err - self.readout_err = readout_err + self.node_err, self.edge_err, self.readout_err = prepare_noise_data(self.device) assert callable(action.transpile_pass) transpile_pass = action.transpile_pass(self.device, self.node_err, self.edge_err, self.readout_err) else: @@ -527,6 +505,7 @@ def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircui action.transpile_pass(self.device) if callable(action.transpile_pass) else action.transpile_pass ) assert isinstance(transpile_pass, list) + # Map TKET placement into a Qiskit layout if action_index in self.actions_layout_indices: try: placement = transpile_pass[0].get_placement_map(tket_qc) @@ -609,36 +588,36 @@ def _apply_bqskit_action(self, action: Action, action_index: int) -> QuantumCirc return bqskit_to_qiskit(bqskit_compiled_qc) def determine_valid_actions_for_state(self) -> list[int]: - """Determines and returns the valid actions for the current state.""" - # Check if all gates are native to the device + """Determine the valid actions for the current circuit state.""" + # Check if circuit uses only native gates check_nat_gates = GatesInBasis(basis_gates=self.device.operation_names) check_nat_gates(self.state) only_nat_gates = check_nat_gates.property_set["all_gates_in_basis"] - # Check if the circuit is mapped to the device coupling graph + + # Check if circuit is mapped to the device coupling map check_mapping = CheckMap(coupling_map=CouplingMap(self.device.build_coupling_map())) check_mapping(self.state) mapped = check_mapping.property_set["is_swap_mapped"] if not only_nat_gates: - # Circuit still has non-native gates if not mapped: - # Allow synthesis and optimization actions + # Non-native + unmapped → synthesis or optimization return self.actions_synthesis_indices + self.actions_opt_indices - # Allow synthesis and mapping-preserving actions (to not ) + # Non-native + mapped → synthesis or mapping-preserving return self.actions_synthesis_indices + self.actions_mapping_preserving_indices - # Circuit has only native gates + if mapped: if self.layout is not None: - # The circuits is correctly compiled, terminate or do further mapping-preserving optimizations + # Native + fully mapped & layout assigned → terminate or fine-tune return [ self.action_terminate_index, *self.actions_mapping_preserving_indices, *self.actions_final_optimization_indices, ] - # No layout is assigned, assign a valid layout + # Mapped but no explicit layout yet → explore layout/mapping improvements return self.actions_mapping_preserving_indices + self.actions_layout_indices + self.actions_mapping_indices if self.layout is not None: - # Not mapped yet but a layout is assigned in the last step, do routing + # Layout chosen but not mapped → must do routing return self.actions_routing_indices - # Not mapped yet, do general optimizations/layout/mapping + # Not mapped and without layout → explore layout/mapping/optimizations return self.actions_opt_indices + self.actions_layout_indices + self.actions_mapping_indices From 470365d9451202e6db2f0e34488ebb66be5947c5 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Mon, 15 Sep 2025 17:23:25 +0200 Subject: [PATCH 40/57] Adjust test circuits from ALG to INDEP For test purpose --- noxfile.py | 1 + src/mqt/predictor/rl/predictorenv.py | 1 + tests/device_selection/test_predictor_ml.py | 6 +++--- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/noxfile.py b/noxfile.py index 3b1876a5c..83ff540c3 100644 --- a/noxfile.py +++ b/noxfile.py @@ -67,6 +67,7 @@ def _run_tests( "--frozen", *install_args, "pytest", + "--log-cli-level=INFO", *pytest_run_args, *session.posargs, "--cov-config=pyproject.toml", diff --git a/src/mqt/predictor/rl/predictorenv.py b/src/mqt/predictor/rl/predictorenv.py index 8e2b5e1d4..a0ebd5b75 100644 --- a/src/mqt/predictor/rl/predictorenv.py +++ b/src/mqt/predictor/rl/predictorenv.py @@ -204,6 +204,7 @@ def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any RuntimeError: If no valid actions are left. """ self.used_actions.append(str(self.action_set[action].name)) + logger.info(f"{self.action_set[action].name!s}") altered_qc = self.apply_action(action) if not altered_qc: return ( diff --git a/tests/device_selection/test_predictor_ml.py b/tests/device_selection/test_predictor_ml.py index 0b2f1485f..803c822a6 100644 --- a/tests/device_selection/test_predictor_ml.py +++ b/tests/device_selection/test_predictor_ml.py @@ -43,7 +43,7 @@ def test_setup_device_predictor_with_prediction(path_uncompiled_circuits: Path, path_compiled_circuits.mkdir() for i in range(2, 8): - qc = get_benchmark("ghz", BenchmarkLevel.ALG, i) + qc = get_benchmark("ghz", BenchmarkLevel.INDEP, i) path = path_uncompiled_circuits / f"qc{i}.qasm" with path.open("w", encoding="utf-8") as f: dump(qc, f) @@ -63,7 +63,7 @@ def test_setup_device_predictor_with_prediction(path_uncompiled_circuits: Path, assert (data_path / "names_list_expected_fidelity.npy").exists() assert (data_path / "scores_list_expected_fidelity.npy").exists() - test_qc = get_benchmark("ghz", BenchmarkLevel.ALG, 3) + test_qc = get_benchmark("ghz", BenchmarkLevel.INDEP, 3) predicted = predict_device_for_figure_of_merit(test_qc, figure_of_merit="expected_fidelity") assert predicted.description == "ibm_falcon_127" @@ -93,7 +93,7 @@ def test_remove_files(path_uncompiled_circuits: Path, path_compiled_circuits: Pa def test_predict_device_for_figure_of_merit_no_suitable_device() -> None: """Test the prediction of the device for a given figure of merit with a wrong device name.""" num_qubits = 130 - qc = get_benchmark("ghz", BenchmarkLevel.ALG, num_qubits) + qc = get_benchmark("ghz", BenchmarkLevel.INDEP, num_qubits) with pytest.raises( ValueError, match=re.escape(f"No suitable device found for the given quantum circuit with {num_qubits} qubits.") ): From caaf2248bf612d2a30e083e85fd33c2496155c47 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Mon, 15 Sep 2025 18:18:53 +0200 Subject: [PATCH 41/57] Update max synthesis size for bqskit --- noxfile.py | 1 - src/mqt/predictor/rl/actions.py | 2 +- src/mqt/predictor/rl/predictorenv.py | 5 +---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/noxfile.py b/noxfile.py index 83ff540c3..3b1876a5c 100644 --- a/noxfile.py +++ b/noxfile.py @@ -67,7 +67,6 @@ def _run_tests( "--frozen", *install_args, "pytest", - "--log-cli-level=INFO", *pytest_run_args, *session.posargs, "--cov-config=pyproject.toml", diff --git a/src/mqt/predictor/rl/actions.py b/src/mqt/predictor/rl/actions.py index c83bcf766..a2f775bed 100644 --- a/src/mqt/predictor/rl/actions.py +++ b/src/mqt/predictor/rl/actions.py @@ -563,7 +563,7 @@ def remove_action(name: str) -> None: model=MachineModel(bqskit_circuit.num_qudits, gate_set=get_bqskit_native_gates(device)), optimization_level=1 if os.getenv("GITHUB_ACTIONS") == "true" else 2, synthesis_epsilon=1e-1 if os.getenv("GITHUB_ACTIONS") == "true" else 1e-8, - max_synthesis_size=2 if os.getenv("GITHUB_ACTIONS") == "true" else 3, + max_synthesis_size=3, seed=10, num_workers=1 if os.getenv("GITHUB_ACTIONS") == "true" else -1, ), diff --git a/src/mqt/predictor/rl/predictorenv.py b/src/mqt/predictor/rl/predictorenv.py index a0ebd5b75..eb6b35cfd 100644 --- a/src/mqt/predictor/rl/predictorenv.py +++ b/src/mqt/predictor/rl/predictorenv.py @@ -204,7 +204,6 @@ def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any RuntimeError: If no valid actions are left. """ self.used_actions.append(str(self.action_set[action].name)) - logger.info(f"{self.action_set[action].name!s}") altered_qc = self.apply_action(action) if not altered_qc: return ( @@ -393,14 +392,12 @@ def fom_aware_compile( fom = self.calculate_reward(synth_circ) if fom > best_fom: - print(f"New best {self.reward_function}: {fom}") best_fom = fom best_result = out_circ best_property_set = prop_set else: fom = self.calculate_reward(out_circ) if fom < best_fom: - print(f"New best {self.reward_function}: {fom}") best_fom = fom best_result = out_circ best_property_set = prop_set @@ -511,7 +508,7 @@ def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircui try: placement = transpile_pass[0].get_placement_map(tket_qc) except Exception as e: - print(f"[Warning] Placement failed ({action.name}): {e}. Falling back to original circuit.") + logger.warning(f"Placement failed ({action.name}): {e}. Falling back to original circuit.") return tk_to_qiskit(tket_qc, replace_implicit_swaps=True) else: qc_tmp = tk_to_qiskit(tket_qc, replace_implicit_swaps=True) From 468da2e2961251bd831bdb6571758667c2572c94 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Wed, 17 Sep 2025 16:03:16 +0200 Subject: [PATCH 42/57] Add tests for more coverage --- src/mqt/predictor/reward.py | 11 +-- src/mqt/predictor/rl/actions.py | 27 ++---- tests/compilation/test_predictor_rl.py | 124 ++++++++++++++++++------- 3 files changed, 101 insertions(+), 61 deletions(-) diff --git a/src/mqt/predictor/reward.py b/src/mqt/predictor/reward.py index 8394d6eee..c97485cd3 100644 --- a/src/mqt/predictor/reward.py +++ b/src/mqt/predictor/reward.py @@ -67,11 +67,7 @@ def expected_fidelity(qc: QuantumCircuit, device: Target, precision: int = 10) - specific_fidelity = 1 - device[gate_type][first_qubit_idx,].error else: second_qubit_idx = calc_qubit_index(qargs, qc.qregs, 1) - try: - specific_fidelity = 1 - device[gate_type][first_qubit_idx, second_qubit_idx].error - except KeyError: - # try flipped orientation (for uni-directional devices) - specific_fidelity = 1 - device[gate_type][second_qubit_idx, first_qubit_idx].error + specific_fidelity = 1 - device[gate_type][first_qubit_idx, second_qubit_idx].error res *= specific_fidelity @@ -145,10 +141,7 @@ def estimated_success_probability(qc: QuantumCircuit, device: Target, precision: else: # multi-qubit gate second_qubit_idx = calc_qubit_index(qargs, qc.qregs, 1) active_qubits.add(second_qubit_idx) - try: - duration = device[gate_type][first_qubit_idx, second_qubit_idx].duration - except KeyError: - duration = device[gate_type][second_qubit_idx, first_qubit_idx].duration + duration = device[gate_type][first_qubit_idx, second_qubit_idx].duration op_times.append((gate_type, [first_qubit_idx, second_qubit_idx], duration, "s")) exec_time_per_qubit[first_qubit_idx] += duration exec_time_per_qubit[second_qubit_idx] += duration diff --git a/src/mqt/predictor/rl/actions.py b/src/mqt/predictor/rl/actions.py index a2f775bed..d979ca5ca 100644 --- a/src/mqt/predictor/rl/actions.py +++ b/src/mqt/predictor/rl/actions.py @@ -676,31 +676,18 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: qc_routed = dag_to_circuit(dag_routed) # Build mapping from original qubits to qubits in routed circuit final_layout = getattr(self, "property_set", {}).get("final_layout", None) - if final_layout is None and hasattr(dag_routed, "property_set"): - final_layout = dag_routed.property_set.get("final_layout", None) - assert final_layout is not None, "final_layout is None — cannot map virtual qubits" qubit_map = {} for virt in qc_orig.qubits: - try: - phys = final_layout[virt] - except KeyError as err: - msg = f"Virtual qubit {virt} not found in final layout!" - raise RuntimeError(msg) from err + assert virt in final_layout, f"Virtual qubit {virt} not found in final layout!" + phys = final_layout[virt] if isinstance(phys, int): - try: - qubit_map[virt] = qc_routed.qubits[phys] - except IndexError as err: - msg = f"Physical index {phys} is out of range in routed circuit!" - raise RuntimeError(msg) from err + assert 0 <= phys < len(qc_routed.qubits), f"Physical index {phys} out of range in routed circuit!" + qubit_map[virt] = qc_routed.qubits[phys] else: - try: - idx = qc_routed.qubits.index(phys) - except ValueError as err: - msg = f"Physical qubit {phys} not found in output circuit!" - raise RuntimeError(msg) from err - qubit_map[virt] = qc_routed.qubits[idx] - # Restore classical registers and measurement instructions + assert phys in qc_routed.qubits, f"Physical qubit {phys} not found in output circuit!" + qubit_map[virt] = qc_routed.qubits[qc_routed.qubits.index(phys)] + # Restore classical registers and measurement instructions qc_final = add_cregs_and_measurements(qc_routed, cregs, measurements, qubit_map) # Return as dag return circuit_to_dag(qc_final) diff --git a/tests/compilation/test_predictor_rl.py b/tests/compilation/test_predictor_rl.py index ebb8f8e34..7577cb7a9 100644 --- a/tests/compilation/test_predictor_rl.py +++ b/tests/compilation/test_predictor_rl.py @@ -12,10 +12,12 @@ import re from pathlib import Path +from typing import TYPE_CHECKING import pytest from mqt.bench import BenchmarkLevel, get_benchmark from mqt.bench.targets import get_device +from qiskit import QuantumCircuit from qiskit.circuit.library import CXGate from qiskit.qasm2 import dump from qiskit.transpiler import CouplingMap, InstructionProperties, Target @@ -23,6 +25,7 @@ from mqt.predictor.rl import Predictor, rl_compile from mqt.predictor.rl.actions import ( + Action, CompilationOrigin, DeviceIndependentAction, PassType, @@ -32,6 +35,9 @@ ) from mqt.predictor.rl.helper import create_feature_dict, get_path_trained_model +if TYPE_CHECKING: + from _pytest.monkeypatch import MonkeyPatch + def test_predictor_env_reset_from_string() -> None: """Test the reset function of the predictor environment with a quantum circuit given as a string as input.""" @@ -68,40 +74,41 @@ def test_qcompile_with_newly_trained_models() -> None: Important: Those trained models are used in later tests and must not be deleted. To test ESP as well, training must be done with a device that provides all relevant information (i.e. T1, T2 and gate times). """ - figure_of_merit = "expected_fidelity" + figure_of_merits = ["expected_fidelity", "critical_depth"] device = get_device("ibm_falcon_127") qc = get_benchmark("ghz", BenchmarkLevel.ALG, 3) - predictor = Predictor(figure_of_merit=figure_of_merit, device=device) - - model_name = "model_" + figure_of_merit + "_" + device.description - model_path = Path(get_path_trained_model() / (model_name + ".zip")) - if not model_path.exists(): - with pytest.raises( - FileNotFoundError, - match=re.escape( - "The RL model 'model_expected_fidelity_ibm_falcon_127' is not trained yet. Please train the model before using it." - ), - ): - rl_compile(qc, device=device, figure_of_merit=figure_of_merit) - - predictor.train_model( - timesteps=100, - test=True, - ) - - qc_compiled, compilation_information = rl_compile(qc, device=device, figure_of_merit=figure_of_merit) - - check_nat_gates = GatesInBasis(basis_gates=device.operation_names) - check_nat_gates(qc_compiled) - only_nat_gates = check_nat_gates.property_set["all_gates_in_basis"] - check_mapping = CheckMap(coupling_map=CouplingMap(device.build_coupling_map())) - check_mapping(qc_compiled) - mapped = check_mapping.property_set["is_swap_mapped"] - - assert qc_compiled.layout is not None - assert compilation_information is not None - assert only_nat_gates, "Circuit should only contain native gates but was not detected as such." - assert mapped, "Circuit should be mapped to the device's coupling map." + for figure_of_merit in figure_of_merits: + predictor = Predictor(figure_of_merit=figure_of_merit, device=device) + + model_name = "model_" + figure_of_merit + "_" + device.description + model_path = Path(get_path_trained_model() / (model_name + ".zip")) + if not model_path.exists(): + with pytest.raises( + FileNotFoundError, + match=re.escape( + "The RL model 'model_expected_fidelity_ibm_falcon_127' is not trained yet. Please train the model before using it." + ), + ): + rl_compile(qc, device=device, figure_of_merit=figure_of_merit) + + predictor.train_model( + timesteps=100, + test=True, + ) + + qc_compiled, compilation_information = rl_compile(qc, device=device, figure_of_merit=figure_of_merit) + + check_nat_gates = GatesInBasis(basis_gates=device.operation_names) + check_nat_gates(qc_compiled) + only_nat_gates = check_nat_gates.property_set["all_gates_in_basis"] + check_mapping = CheckMap(coupling_map=CouplingMap(device.build_coupling_map())) + check_mapping(qc_compiled) + mapped = check_mapping.property_set["is_swap_mapped"] + + assert qc_compiled.layout is not None + assert compilation_information is not None + assert only_nat_gates, "Circuit should only contain native gates but was not detected as such." + assert mapped, "Circuit should be mapped to the device's coupling map." def test_qcompile_with_false_input() -> None: @@ -124,6 +131,59 @@ def test_warning_for_unidirectional_device() -> None: Predictor(figure_of_merit="expected_fidelity", device=target) +def test_fom_aware_compile_fallback(monkeypatch: MonkeyPatch) -> None: + """Force calculate_reward to fail so the swap-count fallback is triggered.""" + # A simple circuit with 2 qubits and a swap to test swap-count fallback + qc = QuantumCircuit(2) + qc.swap(0, 1) + + dummy_action = Action( + name="DummyAction", + origin=CompilationOrigin.QISKIT, + pass_type=PassType.MAPPING, + transpile_pass=lambda _device: [], # no passes applied + ) + + predictor = Predictor(figure_of_merit="estimated_success_probability", device=get_device("ibm_eagle_127")) + monkeypatch.setattr( + predictor.env, "calculate_reward", lambda _circ: (_ for _ in ()).throw(RuntimeError("fake error")) + ) + compiled_qc, prop_set = predictor.env.fom_aware_compile(dummy_action, None, qc, max_iteration=1) + + assert isinstance(compiled_qc, QuantumCircuit) + assert isinstance(prop_set, dict) + assert "swap" in compiled_qc.count_ops() + + +def test_tket_action_layout_failure() -> None: + """Force TKET layout placement to fail to hit the except branch.""" + qc = QuantumCircuit(1) + + class FakePass: + def get_placement_map(self, _: object) -> None: + msg = "fake placement failure" + raise RuntimeError(msg) + + def apply(self, _: object) -> None: + pass + + dummy_action = Action( + name="DummyLayout", + origin=CompilationOrigin.TKET, + pass_type=PassType.LAYOUT, + transpile_pass=lambda _device: [FakePass()], + ) + + predictor = Predictor(figure_of_merit="estimated_success_probability", device=get_device("ibm_eagle_127")) + predictor.env.actions_layout_indices.append(0) + predictor.env.state = qc + apply_tket = predictor.env._apply_tket_action # noqa: SLF001 + result_qc = apply_tket(dummy_action, 0) + + assert isinstance(result_qc, QuantumCircuit) + assert result_qc.num_qubits == 1 + + def test_register_action() -> None: """Test the register_action function.""" action = DeviceIndependentAction( From 737a9f24b0822dce45a1f587327f61d38ba8b071 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Thu, 18 Sep 2025 11:10:18 +0200 Subject: [PATCH 43/57] Update tests --- tests/compilation/test_predictor_rl.py | 68 +++++++++++++------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/tests/compilation/test_predictor_rl.py b/tests/compilation/test_predictor_rl.py index 7577cb7a9..e0891a38d 100644 --- a/tests/compilation/test_predictor_rl.py +++ b/tests/compilation/test_predictor_rl.py @@ -74,41 +74,41 @@ def test_qcompile_with_newly_trained_models() -> None: Important: Those trained models are used in later tests and must not be deleted. To test ESP as well, training must be done with a device that provides all relevant information (i.e. T1, T2 and gate times). """ - figure_of_merits = ["expected_fidelity", "critical_depth"] + figure_of_merit = "expected_fidelity" device = get_device("ibm_falcon_127") qc = get_benchmark("ghz", BenchmarkLevel.ALG, 3) - for figure_of_merit in figure_of_merits: - predictor = Predictor(figure_of_merit=figure_of_merit, device=device) - - model_name = "model_" + figure_of_merit + "_" + device.description - model_path = Path(get_path_trained_model() / (model_name + ".zip")) - if not model_path.exists(): - with pytest.raises( - FileNotFoundError, - match=re.escape( - "The RL model 'model_expected_fidelity_ibm_falcon_127' is not trained yet. Please train the model before using it." - ), - ): - rl_compile(qc, device=device, figure_of_merit=figure_of_merit) - - predictor.train_model( - timesteps=100, - test=True, - ) - - qc_compiled, compilation_information = rl_compile(qc, device=device, figure_of_merit=figure_of_merit) - - check_nat_gates = GatesInBasis(basis_gates=device.operation_names) - check_nat_gates(qc_compiled) - only_nat_gates = check_nat_gates.property_set["all_gates_in_basis"] - check_mapping = CheckMap(coupling_map=CouplingMap(device.build_coupling_map())) - check_mapping(qc_compiled) - mapped = check_mapping.property_set["is_swap_mapped"] - - assert qc_compiled.layout is not None - assert compilation_information is not None - assert only_nat_gates, "Circuit should only contain native gates but was not detected as such." - assert mapped, "Circuit should be mapped to the device's coupling map." + + predictor = Predictor(figure_of_merit=figure_of_merit, device=device) + + model_name = "model_" + figure_of_merit + "_" + device.description + model_path = Path(get_path_trained_model() / (model_name + ".zip")) + if not model_path.exists(): + with pytest.raises( + FileNotFoundError, + match=re.escape( + "The RL model 'model_expected_fidelity_ibm_falcon_127' is not trained yet. Please train the model before using it." + ), + ): + rl_compile(qc, device=device, figure_of_merit=figure_of_merit) + + predictor.train_model( + timesteps=100, + test=True, + ) + + qc_compiled, compilation_information = rl_compile(qc, device=device, figure_of_merit=figure_of_merit) + + check_nat_gates = GatesInBasis(basis_gates=device.operation_names) + check_nat_gates(qc_compiled) + only_nat_gates = check_nat_gates.property_set["all_gates_in_basis"] + check_mapping = CheckMap(coupling_map=CouplingMap(device.build_coupling_map())) + check_mapping(qc_compiled) + mapped = check_mapping.property_set["is_swap_mapped"] + + assert qc_compiled.layout is not None + assert compilation_information is not None + assert only_nat_gates, "Circuit should only contain native gates but was not detected as such." + assert mapped, "Circuit should be mapped to the device's coupling map." def test_qcompile_with_false_input() -> None: @@ -144,7 +144,7 @@ def test_fom_aware_compile_fallback(monkeypatch: MonkeyPatch) -> None: transpile_pass=lambda _device: [], # no passes applied ) - predictor = Predictor(figure_of_merit="estimated_success_probability", device=get_device("ibm_eagle_127")) + predictor = Predictor(figure_of_merit="critical_depth", device=get_device("ibm_eagle_127")) monkeypatch.setattr( predictor.env, "calculate_reward", lambda _circ: (_ for _ in ()).throw(RuntimeError("fake error")) ) From 94c25ab0212686fcadd9c74b7906ef77380d8ca7 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Thu, 18 Sep 2025 13:45:26 +0200 Subject: [PATCH 44/57] Update override --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1fbb96977..7eb9e2bd5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -256,5 +256,5 @@ ignore = ["GH200"] [tool.uv] override-dependencies = [ - "networkx==2.8.5", # Required by `qiskit-ibm-transpiler` + "networkx==2.8.5; python_version < '3.13'", # Required by `qiskit-ibm-transpiler` ] From 3ff922e3383313dfc3baa835bb0b21d68838cfaa Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Thu, 18 Sep 2025 14:09:54 +0200 Subject: [PATCH 45/57] Update comments Update --- pyproject.toml | 2 +- tests/compilation/test_predictor_rl.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7eb9e2bd5..1fbb96977 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -256,5 +256,5 @@ ignore = ["GH200"] [tool.uv] override-dependencies = [ - "networkx==2.8.5; python_version < '3.13'", # Required by `qiskit-ibm-transpiler` + "networkx==2.8.5", # Required by `qiskit-ibm-transpiler` ] diff --git a/tests/compilation/test_predictor_rl.py b/tests/compilation/test_predictor_rl.py index e0891a38d..96f0a6d58 100644 --- a/tests/compilation/test_predictor_rl.py +++ b/tests/compilation/test_predictor_rl.py @@ -132,8 +132,7 @@ def test_warning_for_unidirectional_device() -> None: def test_fom_aware_compile_fallback(monkeypatch: MonkeyPatch) -> None: - """Force calculate_reward to fail so the swap-count fallback is triggered.""" - # A simple circuit with 2 qubits and a swap to test swap-count fallback + """Test fallback of the fom_aware_compile function in case of a compilation failure.""" qc = QuantumCircuit(2) qc.swap(0, 1) @@ -156,7 +155,7 @@ def test_fom_aware_compile_fallback(monkeypatch: MonkeyPatch) -> None: def test_tket_action_layout_failure() -> None: - """Force TKET layout placement to fail to hit the except branch.""" + """Test fallback in case of TKET layout placement failure.""" qc = QuantumCircuit(1) class FakePass: From 782caf8f8f0ff98b1044ddc78679b741050dd200 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Mon, 22 Sep 2025 22:09:38 +0200 Subject: [PATCH 46/57] Update noxfile.py and CHANGELOG.md --- CHANGELOG.md | 1 + noxfile.py | 32 ++++++++++++++++++++++---------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be86348b8..0648923b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Changed +- 🔧 Changed test circuit level for RL predictor from ALG to INDEP - 🔥 Drop support for x86 macOS systems ([#421]) ([**@denialhaag**]) ## [2.3.0] - 2025-07-29 diff --git a/noxfile.py b/noxfile.py index 3b1876a5c..439bc8d0d 100644 --- a/noxfile.py +++ b/noxfile.py @@ -11,14 +11,16 @@ from __future__ import annotations import argparse +import contextlib import os import shutil +import tempfile from typing import TYPE_CHECKING import nox if TYPE_CHECKING: - from collections.abc import Sequence + from collections.abc import Generator, Sequence nox.needs_version = ">=2024.3.2" @@ -35,6 +37,17 @@ nox.options.error_on_missing_interpreters = True +@contextlib.contextmanager +def preserve_lockfile() -> Generator[None]: + """Preserve the lockfile by moving it to a temporary directory.""" + with tempfile.TemporaryDirectory() as temp_dir_name: + shutil.move("uv.lock", f"{temp_dir_name}/uv.lock") + try: + yield + finally: + shutil.move(f"{temp_dir_name}/uv.lock", "uv.lock") + + @nox.session(reuse_venv=True) def lint(session: nox.Session) -> None: """Run the linter.""" @@ -64,7 +77,6 @@ def _run_tests( "--no-dev", "--group", "test", - "--frozen", *install_args, "pytest", *pytest_run_args, @@ -83,14 +95,14 @@ def tests(session: nox.Session) -> None: @nox.session(reuse_venv=True, venv_backend="uv", python=PYTHON_ALL_VERSIONS) def minimums(session: nox.Session) -> None: """Test the minimum versions of dependencies.""" - _run_tests( - session, - install_args=["--resolution=lowest-direct"], - pytest_run_args=["-Wdefault"], - ) - env = {"UV_PROJECT_ENVIRONMENT": session.virtualenv.location} - session.run("uv", "tree", "--frozen", env=env) - session.run("uv", "lock", "--refresh", env=env) + with preserve_lockfile(): + _run_tests( + session, + install_args=["--resolution=lowest-direct"], + pytest_run_args=["-Wdefault"], + ) + env = {"UV_PROJECT_ENVIRONMENT": session.virtualenv.location} + session.run("uv", "tree", "--frozen", env=env) @nox.session(reuse_venv=True, venv_backend="uv", python=PYTHON_ALL_VERSIONS) From 44e0e4047af48a56a5c264e0e891a689ffda345e Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Tue, 23 Sep 2025 13:45:42 +0200 Subject: [PATCH 47/57] Clean up venv after each session to free up space --- noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index 439bc8d0d..d1f98ac60 100644 --- a/noxfile.py +++ b/noxfile.py @@ -86,7 +86,7 @@ def _run_tests( ) -@nox.session(reuse_venv=True, python=PYTHON_ALL_VERSIONS) +@nox.session(reuse_venv=not os.environ.get("CI"), python=PYTHON_ALL_VERSIONS) def tests(session: nox.Session) -> None: """Run the test suite.""" _run_tests(session) From 350bae5a82b9f36b34e0051af3419864101ae0f5 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Tue, 23 Sep 2025 20:40:24 +0200 Subject: [PATCH 48/57] Clean up venv after each session to free up space --- noxfile.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/noxfile.py b/noxfile.py index d1f98ac60..c1bc0fb43 100644 --- a/noxfile.py +++ b/noxfile.py @@ -13,6 +13,7 @@ import argparse import contextlib import os +import pathlib import shutil import tempfile from typing import TYPE_CHECKING @@ -57,6 +58,16 @@ def lint(session: nox.Session) -> None: session.run("pre-commit", "run", "--all-files", *session.posargs, external=True) +def _cleanup(session: nox.Session) -> None: + """Remove this session's virtualenv to save disk space in CI.""" + venv_dir = session.virtualenv.location + if venv_dir and pathlib.Path(venv_dir).exists(): + shutil.rmtree(venv_dir, ignore_errors=True) + session.log(f"Cleaned up {venv_dir}") + shutil.rmtree(pathlib.Path("~/.cache").expanduser(), ignore_errors=True) + session.run("uv", "cache", "clean", "--all", external=True) + + def _run_tests( session: nox.Session, *, @@ -90,6 +101,8 @@ def _run_tests( def tests(session: nox.Session) -> None: """Run the test suite.""" _run_tests(session) + if os.environ.get("CI"): + _cleanup(session) @nox.session(reuse_venv=True, venv_backend="uv", python=PYTHON_ALL_VERSIONS) @@ -103,6 +116,8 @@ def minimums(session: nox.Session) -> None: ) env = {"UV_PROJECT_ENVIRONMENT": session.virtualenv.location} session.run("uv", "tree", "--frozen", env=env) + if os.environ.get("CI"): + _cleanup(session) @nox.session(reuse_venv=True, venv_backend="uv", python=PYTHON_ALL_VERSIONS) From 91208d194d353ab47d884f9f46b54ee04849a21e Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Tue, 23 Sep 2025 22:46:12 +0200 Subject: [PATCH 49/57] Clean up venv after each session to free up space --- noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index c1bc0fb43..bde534424 100644 --- a/noxfile.py +++ b/noxfile.py @@ -65,7 +65,7 @@ def _cleanup(session: nox.Session) -> None: shutil.rmtree(venv_dir, ignore_errors=True) session.log(f"Cleaned up {venv_dir}") shutil.rmtree(pathlib.Path("~/.cache").expanduser(), ignore_errors=True) - session.run("uv", "cache", "clean", "--all", external=True) + session.run("uv", "cache", "clean", external=True) def _run_tests( From 622d409ad00078e51c1f7c55a3e7598d2096a6a7 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Wed, 24 Sep 2025 01:40:00 +0200 Subject: [PATCH 50/57] Clean up venv after each session to free up space --- noxfile.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index bde534424..f2093b742 100644 --- a/noxfile.py +++ b/noxfile.py @@ -65,7 +65,11 @@ def _cleanup(session: nox.Session) -> None: shutil.rmtree(venv_dir, ignore_errors=True) session.log(f"Cleaned up {venv_dir}") shutil.rmtree(pathlib.Path("~/.cache").expanduser(), ignore_errors=True) - session.run("uv", "cache", "clean", external=True) + # Clean GitHub Actions temp cache + gha_temp = pathlib.Path("/home/runner/work/_temp/setup-uv-cache") + if gha_temp.exists(): + shutil.rmtree(gha_temp, ignore_errors=True) + session.log(f"Cleaned GitHub Actions uv temp cache at {gha_temp}") def _run_tests( From fa008a61bb986fa47f46940f21097904a3d56dbc Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Thu, 25 Sep 2025 20:11:30 +0200 Subject: [PATCH 51/57] Update noxfile --- noxfile.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/noxfile.py b/noxfile.py index f2093b742..beb069798 100644 --- a/noxfile.py +++ b/noxfile.py @@ -124,17 +124,6 @@ def minimums(session: nox.Session) -> None: _cleanup(session) -@nox.session(reuse_venv=True, venv_backend="uv", python=PYTHON_ALL_VERSIONS) -def qiskit(session: nox.Session) -> None: - """Tests against the latest version of Qiskit.""" - _run_tests( - session, - extra_command=["uv", "pip", "install", "qiskit[qasm3-import] @ git+https://github.com/Qiskit/qiskit.git"], - ) - env = {"UV_PROJECT_ENVIRONMENT": session.virtualenv.location} - session.run("uv", "pip", "show", "qiskit", env=env) - - @nox.session(reuse_venv=True) def docs(session: nox.Session) -> None: """Build the docs. Use "--non-interactive" to avoid serving. Pass "-b linkcheck" to check links.""" From 0c98e5656853617892812cdfcd33416c4c60cdc9 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Thu, 25 Sep 2025 21:13:29 +0200 Subject: [PATCH 52/57] Update ibm runtime dependency --- pyproject.toml | 1 + uv.lock | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 1fbb96977..0d81d676d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ dependencies = [ "numpy>=1.22,<2; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict numpy v2 for macOS x86 since it is not supported anymore since torch v2.3.0 "torch>=2.8.0,<2.9.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch v2.3.0 for macOS x86 since it is not supported anymore. "typing-extensions>=4.1", # for `assert_never` + "qiskit_ibm_runtime>=0.40.0,<0.42.0", "qiskit-ibm-transpiler==0.13.1; python_version < '3.13'", # Do not support python 3.13 yet "qiskit-ibm-ai-local-transpiler==0.4.2; python_version < '3.13'", ] diff --git a/uv.lock b/uv.lock index 9c915f7fe..b33cf00e2 100644 --- a/uv.lock +++ b/uv.lock @@ -1548,6 +1548,7 @@ dependencies = [ { name = "pytket-qiskit" }, { name = "qiskit" }, { name = "qiskit-ibm-ai-local-transpiler", marker = "python_full_version < '3.13'" }, + { name = "qiskit-ibm-runtime" }, { name = "qiskit-ibm-transpiler", marker = "python_full_version < '3.13'" }, { name = "rich" }, { name = "sb3-contrib", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'" }, @@ -1607,6 +1608,7 @@ requires-dist = [ { name = "pytket-qiskit", specifier = ">=0.71.0" }, { name = "qiskit", specifier = "!=1.3.2" }, { name = "qiskit-ibm-ai-local-transpiler", marker = "python_full_version < '3.13'", specifier = "==0.4.2" }, + { name = "qiskit-ibm-runtime", specifier = ">=0.40.0,<0.42.0" }, { name = "qiskit-ibm-transpiler", marker = "python_full_version < '3.13'", specifier = "==0.13.1" }, { name = "rich", specifier = ">=12.6.0" }, { name = "sb3-contrib", specifier = ">=2.0.0" }, @@ -3683,6 +3685,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/cb/9bce97ad8135a32b47e90b3f57084bdc0bb844aa866b3ca0bc88987a2ac4/symengine-0.14.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a010412317d93e37fbee3f4bc04886408653e863997993424e7b9e4d8c85c150", size = 90826835, upload-time = "2025-04-21T04:00:55.849Z" }, { url = "https://files.pythonhosted.org/packages/5e/8f/3c335e44db300f4d91f7b098661a2add403367833cc27d26dc235520661a/symengine-0.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbb14724ab6a6844d04adbd48d7aadcadfe4974193e87eedac93963a6e88bf53", size = 50356243, upload-time = "2025-04-21T04:01:00.867Z" }, { url = "https://files.pythonhosted.org/packages/fc/fd/d9f33b8a08bdfc07fff733c7f869deb5a0e8c93f5cb95ed4cb9d16294dfb/symengine-0.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:4635fa8855fcadae8c60f27f498388699b7ee88c6be7c3e23564eb0907f6b397", size = 18755307, upload-time = "2025-04-21T04:01:04.187Z" }, + { url = "https://files.pythonhosted.org/packages/65/eb/875243e2856ef203f102cfefe54b81f4a08daed2c7b2ad929e8b82282d93/symengine-0.14.1-cp311-abi3-macosx_10_13_x86_64.whl", hash = "sha256:182d7ca746622764e50af4f926eb9733bd4d1fddb9526faa67bc6ca88b333e23", size = 26038565, upload-time = "2025-09-14T15:23:14.361Z" }, + { url = "https://files.pythonhosted.org/packages/13/85/58acf0f28ae556205044bce9228a4e0e3bb532e0d4a81f3af84e0c45d95b/symengine-0.14.1-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:6fbe25be42ba2040f09d464c06a7812f8e8d04c8087abbad914be561deac2141", size = 23108837, upload-time = "2025-09-14T15:23:17.395Z" }, + { url = "https://files.pythonhosted.org/packages/a2/00/0355f1a261d608b1eaa972071e73070bc415cd1932e90574354e98f46254/symengine-0.14.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:78ac61effb97f63eca70bb4ececb2cb329b09f1d587cfab51768bd3c04543010", size = 61943252, upload-time = "2025-09-14T15:23:21.076Z" }, + { url = "https://files.pythonhosted.org/packages/47/b4/41374adf5f7e60576862e4b4df1519225001fe2f230a3d15c67aa273ea48/symengine-0.14.1-cp311-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5ee52a0aaacafc032dbe5ca57c0b3d87a228e9ef6e88676a4ecc0111fb647b9e", size = 94027577, upload-time = "2025-09-14T15:23:26.165Z" }, + { url = "https://files.pythonhosted.org/packages/52/af/0de548738de262f2288cdefc02df35aaa8c398369643619f59a9c2b5a0b0/symengine-0.14.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:577dd78b33f29e7713443588c576013ede4757cb8aedc36bcc405cf85844d7f9", size = 51085045, upload-time = "2025-09-14T15:23:30.185Z" }, + { url = "https://files.pythonhosted.org/packages/e2/d3/fb148c2a2fdefa8f1630f3a0da0170d77ee8e1ac3baf7804504ddc9baacd/symengine-0.14.1-cp311-abi3-win_amd64.whl", hash = "sha256:c1142fd44cc952025185521c1f8a756af8625955f41ad7b947df2b81a14ce7f3", size = 18739305, upload-time = "2025-09-14T15:23:33.031Z" }, { url = "https://files.pythonhosted.org/packages/ee/f8/689900a258a68b121d7c61a6705b5332969dd95517f9be1d66a4937a17e1/symengine-0.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:43a394acef2355bbcbed089c8f83a7dbdac28f4f979e094b549f571fc411dea6", size = 25992552, upload-time = "2025-04-21T04:01:07.102Z" }, { url = "https://files.pythonhosted.org/packages/6e/10/82291c78d6dbc17b2e4371559a3945920ecc536ec36b6c8dde28165064f9/symengine-0.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7eb37cd5a9eb0c1c5e35c515139fd1221d937b269aef3569b92c7bb4e251be4", size = 23052876, upload-time = "2025-04-21T04:01:09.679Z" }, { url = "https://files.pythonhosted.org/packages/30/1a/2199515b7d4f3aa74262e3eb31916a593c4daa7da53bb2c9cbd2cc13fad8/symengine-0.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7657663cfb0d87d66848f7cacda0b2447e127584bc9ec65120e75ce68b21b809", size = 60873086, upload-time = "2025-04-21T04:01:14.285Z" }, @@ -3706,6 +3714,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/be/28/9ae2ee8c3a6e5b35b25a42625de4cd8ef432121dd4ff75bca6809b9e61ac/symengine-0.14.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58f8e1756c9f8cdb7f6cdf189b3752f20cb464f82bf1c7a3873156b12c567896", size = 60604042, upload-time = "2025-04-21T04:02:25.915Z" }, { url = "https://files.pythonhosted.org/packages/7d/c0/233806912e5515505a058d2527a53117e0e6cd8e59525f1c7cb870805a47/symengine-0.14.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a96f1adfed8cfad62a4f58fe2f32b42718e0938afef0bffeb8131d862ca71a9b", size = 90280432, upload-time = "2025-04-21T04:02:31.02Z" }, { url = "https://files.pythonhosted.org/packages/bd/93/a6f379c6bd9554efc4cd5475e75fc164d6c44e69d98ddf6553f12a2532aa/symengine-0.14.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c4f500fdd09cbd3cde34a027a964e47b4fc7bfcd5ec8b9a4e654a37036d364b", size = 50183106, upload-time = "2025-04-21T04:02:35.537Z" }, + { url = "https://files.pythonhosted.org/packages/e7/92/31a04faad202197006ad91166a5cb8b513b3238ab4796faabaacece56665/symengine-0.14.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4b85100df4b0e1f6bd2f036539f55961f1e95e77d73fa7beefddbf43359dfda0", size = 26129364, upload-time = "2025-09-14T15:23:35.883Z" }, + { url = "https://files.pythonhosted.org/packages/f7/35/5811158a9a9121f4acc219dd1d1e2c0efec8746d0ddef73890b8348f5e47/symengine-0.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a5938a252c3516f77654689a3e858becc20effce8d34832d1d293ce23ca0d27", size = 23188595, upload-time = "2025-09-14T15:23:38.981Z" }, + { url = "https://files.pythonhosted.org/packages/03/05/c327964f2aeb73a85f5fba80e5357b637794c886389af05718915dd390d8/symengine-0.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:744b01250d15ecdfad63fa7d590a251598794bd977d35d0fab6cb18e8742a493", size = 61923804, upload-time = "2025-09-14T15:23:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/98/f0/f7d488b93081d25a47e974335808cbb6ece96aaaf15abbb72d5d35ee5eab/symengine-0.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:9fd6465d3a797d44e5e4d3b540c4416f18ddccb77768267f0f1e783c44d97bbc", size = 94038098, upload-time = "2025-09-14T15:23:48.45Z" }, + { url = "https://files.pythonhosted.org/packages/22/45/45e84f7f3154d61df6de21b5e38912f9ce91bd7422072a575fe9a3b2ed43/symengine-0.14.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9d1fe80abee5ad6f53bd64c38e25737bb526eff6d24d8428df0f92283e592799", size = 51127881, upload-time = "2025-09-14T15:23:52.894Z" }, + { url = "https://files.pythonhosted.org/packages/ce/30/6b63d1dc9047587cee5cb9d3a4d75041e8173d839eb4c962ad357730a928/symengine-0.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:faf659b9436dcc5ade7f6085d17d42181c18d78ddcaf91de16cd9a412fc77357", size = 18878832, upload-time = "2025-09-14T15:23:55.911Z" }, ] [[package]] From 34a1c7cb992d8afea493b42b543227c775a88928 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Thu, 25 Sep 2025 23:02:18 +0200 Subject: [PATCH 53/57] Update noxfile --- noxfile.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/noxfile.py b/noxfile.py index beb069798..76b997ad9 100644 --- a/noxfile.py +++ b/noxfile.py @@ -105,8 +105,6 @@ def _run_tests( def tests(session: nox.Session) -> None: """Run the test suite.""" _run_tests(session) - if os.environ.get("CI"): - _cleanup(session) @nox.session(reuse_venv=True, venv_backend="uv", python=PYTHON_ALL_VERSIONS) @@ -120,8 +118,6 @@ def minimums(session: nox.Session) -> None: ) env = {"UV_PROJECT_ENVIRONMENT": session.virtualenv.location} session.run("uv", "tree", "--frozen", env=env) - if os.environ.get("CI"): - _cleanup(session) @nox.session(reuse_venv=True) From a8d069af09b21ce5c60920267a2623aa1070e5d0 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Fri, 26 Sep 2025 00:25:24 +0200 Subject: [PATCH 54/57] Update noxfile --- noxfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/noxfile.py b/noxfile.py index 76b997ad9..c57689799 100644 --- a/noxfile.py +++ b/noxfile.py @@ -90,6 +90,7 @@ def _run_tests( "uv", "run", "--no-dev", + "--no-cache", "--group", "test", *install_args, From 114b79bf9887eb2dfa92eff49bb4121ddb568367 Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Fri, 26 Sep 2025 01:57:07 +0200 Subject: [PATCH 55/57] Update noxfile --- noxfile.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/noxfile.py b/noxfile.py index c57689799..b5a748558 100644 --- a/noxfile.py +++ b/noxfile.py @@ -60,16 +60,17 @@ def lint(session: nox.Session) -> None: def _cleanup(session: nox.Session) -> None: """Remove this session's virtualenv to save disk space in CI.""" - venv_dir = session.virtualenv.location - if venv_dir and pathlib.Path(venv_dir).exists(): - shutil.rmtree(venv_dir, ignore_errors=True) - session.log(f"Cleaned up {venv_dir}") - shutil.rmtree(pathlib.Path("~/.cache").expanduser(), ignore_errors=True) - # Clean GitHub Actions temp cache - gha_temp = pathlib.Path("/home/runner/work/_temp/setup-uv-cache") - if gha_temp.exists(): - shutil.rmtree(gha_temp, ignore_errors=True) - session.log(f"Cleaned GitHub Actions uv temp cache at {gha_temp}") + version = session.python + if version != "3.13": # keep cache for last run + venv_dir = session.virtualenv.location + if venv_dir and pathlib.Path(venv_dir).exists(): + shutil.rmtree(venv_dir, ignore_errors=True) + session.log(f"Cleaned up {venv_dir}") + shutil.rmtree(pathlib.Path("~/.cache").expanduser(), ignore_errors=True) + gha_temp = pathlib.Path("/home/runner/work/_temp/setup-uv-cache") + if gha_temp.exists(): + shutil.rmtree(gha_temp, ignore_errors=True) + session.log(f"Cleaned GitHub Actions uv temp cache at {gha_temp}") def _run_tests( @@ -90,7 +91,6 @@ def _run_tests( "uv", "run", "--no-dev", - "--no-cache", "--group", "test", *install_args, @@ -106,6 +106,8 @@ def _run_tests( def tests(session: nox.Session) -> None: """Run the test suite.""" _run_tests(session) + if os.environ.get("CI"): + _cleanup(session) @nox.session(reuse_venv=True, venv_backend="uv", python=PYTHON_ALL_VERSIONS) @@ -119,6 +121,8 @@ def minimums(session: nox.Session) -> None: ) env = {"UV_PROJECT_ENVIRONMENT": session.virtualenv.location} session.run("uv", "tree", "--frozen", env=env) + if os.environ.get("CI"): + _cleanup(session) @nox.session(reuse_venv=True) From aaf14b1b550557c08930ff4c2a1094ac2121849f Mon Sep 17 00:00:00 2001 From: Zhou Shaobo Date: Fri, 26 Sep 2025 15:37:34 +0200 Subject: [PATCH 56/57] Fetch update from main --- pyproject.toml | 6 ++++-- src/mqt/predictor/rl/parsing.py | 5 +++-- uv.lock | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0d81d676d..889016b3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,9 +30,12 @@ dynamic = ["version"] dependencies = [ "mqt.bench>=2.0.0", - "qiskit!=1.3.2", # 1.3.2 causes a Qiskit error when using the CommutativeInverseCancellation pass, see https://github.com/Qiskit/qiskit/issues/13742 + "qiskit>=1.3.3", "pytket>=1.29.0", # lowest version that supports the used pytket AutoRebase pass instead of auto_rebase "pytket_qiskit>=0.71.0", + # TODO(denialhaag): Remove once pytket_qiskit is updated + # https://github.com/munich-quantum-toolkit/predictor/issues/471 + "qiskit-ibm-runtime>=0.30.0,<0.42.0", "sb3_contrib>=2.0.0", "stable-baselines3>=2.7.0", "tqdm>=4.66.0", @@ -47,7 +50,6 @@ dependencies = [ "numpy>=1.22,<2; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict numpy v2 for macOS x86 since it is not supported anymore since torch v2.3.0 "torch>=2.8.0,<2.9.0; sys_platform == 'darwin' and 'x86_64' in platform_machine and python_version < '3.13'", # Restrict torch v2.3.0 for macOS x86 since it is not supported anymore. "typing-extensions>=4.1", # for `assert_never` - "qiskit_ibm_runtime>=0.40.0,<0.42.0", "qiskit-ibm-transpiler==0.13.1; python_version < '3.13'", # Do not support python 3.13 yet "qiskit-ibm-ai-local-transpiler==0.4.2; python_version < '3.13'", ] diff --git a/src/mqt/predictor/rl/parsing.py b/src/mqt/predictor/rl/parsing.py index 816e7aa48..6f35152d3 100644 --- a/src/mqt/predictor/rl/parsing.py +++ b/src/mqt/predictor/rl/parsing.py @@ -18,6 +18,7 @@ from pytket.circuit import Node from pytket.placement import place_with_map from qiskit import QuantumCircuit, QuantumRegister +from qiskit.converters import circuit_to_dag, dag_to_circuit from qiskit.transpiler import Layout, PassManager, Target, TranspileLayout from qiskit.transpiler.passes import ApplyLayout @@ -239,8 +240,8 @@ def postprocess_vf2postlayout( apply_layout.property_set["final_layout"] = layout_before.final_layout apply_layout.property_set["post_layout"] = post_layout - altered_qc = apply_layout(qc) - return altered_qc, apply_layout + altered_qc = apply_layout.run(circuit_to_dag(qc)) + return dag_to_circuit(altered_qc), apply_layout def prepare_noise_data(device: Target) -> tuple[dict[Node, float], dict[tuple[Node, Node], float], dict[Node, float]]: diff --git a/uv.lock b/uv.lock index b33cf00e2..db59b3577 100644 --- a/uv.lock +++ b/uv.lock @@ -1606,9 +1606,9 @@ requires-dist = [ { name = "numpy", marker = "python_full_version < '3.13' and 'x86_64' in platform_machine and sys_platform == 'darwin'", specifier = ">=1.22,<2" }, { name = "pytket", specifier = ">=1.29.0" }, { name = "pytket-qiskit", specifier = ">=0.71.0" }, - { name = "qiskit", specifier = "!=1.3.2" }, + { name = "qiskit", specifier = ">=1.3.3" }, { name = "qiskit-ibm-ai-local-transpiler", marker = "python_full_version < '3.13'", specifier = "==0.4.2" }, - { name = "qiskit-ibm-runtime", specifier = ">=0.40.0,<0.42.0" }, + { name = "qiskit-ibm-runtime", specifier = ">=0.30.0,<0.42.0" }, { name = "qiskit-ibm-transpiler", marker = "python_full_version < '3.13'", specifier = "==0.13.1" }, { name = "rich", specifier = ">=12.6.0" }, { name = "sb3-contrib", specifier = ">=2.0.0" }, From 97af7bba4b32d16badb0b3c5000348d721e6beae Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 13:48:27 +0000 Subject: [PATCH 57/57] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mqt/predictor/rl/actions.py | 3 ++- src/mqt/predictor/rl/parsing.py | 3 ++- src/mqt/predictor/rl/predictorenv.py | 6 ++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/mqt/predictor/rl/actions.py b/src/mqt/predictor/rl/actions.py index d979ca5ca..a48ac83b1 100644 --- a/src/mqt/predictor/rl/actions.py +++ b/src/mqt/predictor/rl/actions.py @@ -29,7 +29,7 @@ ) from pytket.placement import GraphPlacement, NoiseAwarePlacement from qiskit import QuantumCircuit -from qiskit.circuit import ClassicalRegister, Instruction, QuantumRegister, Qubit, StandardEquivalenceLibrary +from qiskit.circuit import ClassicalRegister, QuantumRegister, StandardEquivalenceLibrary from qiskit.circuit.library import ( CXGate, CYGate, @@ -93,6 +93,7 @@ from bqskit import Circuit from pytket._tket.passes import BasePass as tket_BasePass + from qiskit.circuit import Instruction, Qubit from qiskit.dagcircuit import DAGCircuit from qiskit.transpiler.basepasses import BasePass as qiskit_BasePass diff --git a/src/mqt/predictor/rl/parsing.py b/src/mqt/predictor/rl/parsing.py index 3f2c517ac..e406fbe44 100644 --- a/src/mqt/predictor/rl/parsing.py +++ b/src/mqt/predictor/rl/parsing.py @@ -248,7 +248,8 @@ def postprocess_vf2postlayout( altered_qc = apply_layout.run(circuit_to_dag(qc)) return dag_to_circuit(altered_qc), apply_layout - + + def prepare_noise_data(device: Target) -> tuple[dict[Node, float], dict[tuple[Node, Node], float], dict[Node, float]]: """Extract node, edge, and readout errors from the device target.""" node_err: dict[Node, float] = {} diff --git a/src/mqt/predictor/rl/predictorenv.py b/src/mqt/predictor/rl/predictorenv.py index 9555892b4..53d2c9df2 100644 --- a/src/mqt/predictor/rl/predictorenv.py +++ b/src/mqt/predictor/rl/predictorenv.py @@ -24,7 +24,9 @@ from pathlib import Path from bqskit import Circuit + from pytket.circuit import Node from qiskit.passmanager import PropertySet + from qiskit.transpiler import Target from mqt.predictor.reward import figure_of_merit from mqt.predictor.rl.actions import Action @@ -38,12 +40,12 @@ from gymnasium import Env from gymnasium.spaces import Box, Dict, Discrete from joblib import load -from pytket.circuit import Node, Qubit +from pytket.circuit import Qubit from pytket.extensions.qiskit import qiskit_to_tk, tk_to_qiskit from qiskit import QuantumCircuit from qiskit.circuit import StandardEquivalenceLibrary from qiskit.passmanager.flow_controllers import DoWhileController -from qiskit.transpiler import CouplingMap, Layout, PassManager, Target, TranspileLayout +from qiskit.transpiler import CouplingMap, Layout, PassManager, TranspileLayout from qiskit.transpiler.passes import ( ApplyLayout, BasisTranslator,