diff --git a/pyproject.toml b/pyproject.toml index daf64a097..d899d1bc3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,9 @@ dependencies = [ "typing-extensions>=4.1", # for `assert_never` "qiskit-ibm-transpiler>=0.15.0; sys_platform != 'win32' or python_version != '3.13'", # pulls qiskit-serverless, which pulls ray[default], and the resolved ray 2.54.0 has no win_amd64 cp313 wheel "qiskit-ibm-ai-local-transpiler>=0.5.5", + "torch-geometric>=2.7.0", + "optuna>=3.0.0", + "safetensors>=0.7.0" ] classifiers = [ @@ -134,21 +137,24 @@ filterwarnings = [ 'ignore:.*The property ``qiskit.circuit.instruction.Instruction.*`` is deprecated as of qiskit 1.3.0.*:DeprecationWarning:', # Windows: Python 3.13 can emit a RuntimeWarning about unsupported timeouts; keep tests strict otherwise. 'ignore:.*Timeout is not supported on Windows\\.:RuntimeWarning', + 'ignore:.*torch_geometric.distributed.*:DeprecationWarning:', + 'ignore:.*torch.jit.script.*:DeprecationWarning:', + "ignore:Failing to pass a value to the 'type_params' parameter of 'typing\\._eval_type':DeprecationWarning" ] [tool.coverage] run.source = ["mqt.predictor"] +run.disable_warnings = [ + "no-sysmon", +] report.exclude_also = [ '\.\.\.', 'if TYPE_CHECKING:', 'raise AssertionError', 'raise NotImplementedError', ] -run.disable_warnings = [ - "no-sysmon", -] show_missing = true skip_empty = true precision = 1 @@ -251,6 +257,10 @@ wille = "wille" anc = "anc" aer = "aer" fom = "fom" +TPE = "TPE" +TPESampler = "TPESampler" +gae = "gae" +GAE = "GAE" [tool.repo-review] diff --git a/src/mqt/predictor/hellinger/utils.py b/src/mqt/predictor/hellinger/utils.py index e1348ff54..218d9d0c7 100644 --- a/src/mqt/predictor/hellinger/utils.py +++ b/src/mqt/predictor/hellinger/utils.py @@ -63,7 +63,18 @@ def calc_device_specific_features( - The single and multi qubit gate ratio """ if ignore_gates is None: - ignore_gates = ["barrier", "id", "measure"] + ignore_gates = [ + "barrier", + "id", + "measure", + "if_else", + "while_loop", + "for_loop", + "switch_case", + "box", + "break", + "continue", + ] ignored_ops = set(ignore_gates) # Targets may advertise control-flow ops like ``if_else``; keep only actual gate features. @@ -141,12 +152,13 @@ def calc_device_specific_features( return np.array(list(feature_dict.values())) -def get_hellinger_model_path(device: Target) -> Path: - """Returns the path to the trained model folder resulting from the machine learning training.""" - training_data_path = Path(str(resources.files("mqt.predictor"))) / "ml" / "training_data" - model_path = ( - training_data_path - / "trained_model" - / ("trained_hellinger_distance_regressor_" + device.description + ".joblib") +def get_hellinger_model_path(device: Target, gnn: bool = False) -> Path: + """Returns the path to the trained model file resulting from the machine learning training.""" + training_data_path = Path(str(resources.files("mqt.predictor"))) / "ml" / "training_data" / "trained_model" + device_description = str(device.description) + filename = ( + (f"trained_hellinger_distance_regressor_gnn_{device_description}.pth") + if gnn + else (f"trained_hellinger_distance_regressor_{device_description}.joblib") ) - return Path(model_path) + return training_data_path / filename diff --git a/src/mqt/predictor/ml/__init__.py b/src/mqt/predictor/ml/__init__.py index 98f7257a7..242465291 100644 --- a/src/mqt/predictor/ml/__init__.py +++ b/src/mqt/predictor/ml/__init__.py @@ -13,4 +13,9 @@ from mqt.predictor.ml import helper from mqt.predictor.ml.predictor import Predictor, predict_device_for_figure_of_merit, setup_device_predictor -__all__ = ["Predictor", "helper", "predict_device_for_figure_of_merit", "setup_device_predictor"] +__all__ = [ + "Predictor", + "helper", + "predict_device_for_figure_of_merit", + "setup_device_predictor", +] diff --git a/src/mqt/predictor/ml/gnn.py b/src/mqt/predictor/ml/gnn.py new file mode 100644 index 000000000..09ee6559c --- /dev/null +++ b/src/mqt/predictor/ml/gnn.py @@ -0,0 +1,297 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Graph neural network models using SAGEConv layers.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +import torch +import torch.nn as nn +import torch.nn.functional as functional +from torch_geometric.nn import ( + GraphNorm, + SAGEConv, + SAGPooling, + global_mean_pool, +) + +if TYPE_CHECKING: + from collections.abc import Callable + + from torch_geometric.data import Data + + +class GraphConvolutionSage(nn.Module): + """Graph convolutional encoder using SAGEConv layers.""" + + def __init__( + self, + in_feats: int, + hidden_dim: int, + num_conv_wo_resnet: int, + num_resnet_layers: int, + *, + conv_activation: Callable[..., torch.Tensor] = functional.leaky_relu, + conv_act_kwargs: dict[str, Any] | None = None, + dropout_p: float = 0.2, + bidirectional: bool = True, + use_sag_pool: bool = False, + sag_ratio: float = 0.7, + sag_nonlinearity: Callable[..., torch.Tensor] = torch.tanh, + ) -> None: + """Initialize the graph convolutional encoder. + + The encoder consists of a stack of SAGEConv layers followed by + optional SAGPooling before the global readout. + + Args: + in_feats: Dimensionality of the node features. + hidden_dim: Output dimensionality of the first SAGEConv layer. + num_conv_wo_resnet: Number of SAGEConv layers before residual + connections are introduced. + num_resnet_layers: Number of SAGEConv layers with residual + connections. + conv_activation: Activation function applied after each graph + convolution. Defaults to torch.nn.functional.leaky_relu. + conv_act_kwargs: Additional keyword arguments passed to + conv_activation. Defaults to None. + dropout_p: Dropout probability applied after each graph layer. + Defaults to 0.2. + bidirectional: If True, apply message passing in both + directions (forward and reversed edges) and average the + results. Defaults to True. + use_sag_pool: If True, apply a single SAGPooling layer after + the convolutions and before readout. Defaults to False. + sag_ratio: Fraction of nodes to keep in SAGPooling. Must be in + (0, 1]. Defaults to 0.7. + sag_nonlinearity: Nonlinearity used inside SAGPooling for score + computation. Defaults to torch.tanh. + """ + super().__init__() + + if num_conv_wo_resnet < 1: + msg = "num_conv_wo_resnet must be at least 1" + raise ValueError(msg) + + self.conv_activation = conv_activation + self.conv_act_kwargs = conv_act_kwargs or {} + self.bidirectional = bidirectional + self.use_sag_pool = use_sag_pool + + # --- GRAPH ENCODER --- + self.convs: nn.ModuleList[SAGEConv] = nn.ModuleList() + self.norms: nn.ModuleList[GraphNorm] = nn.ModuleList() + + # First layer: SAGE + self.convs.append(SAGEConv(in_feats, hidden_dim)) + out_dim = hidden_dim + self.graph_emb_dim = out_dim + self.norms.append(GraphNorm(out_dim)) + + # Subsequent layers: SAGE with fixed width == out_dim + for _ in range(num_conv_wo_resnet - 1): + self.convs.append(SAGEConv(out_dim, out_dim)) + self.norms.append(GraphNorm(out_dim)) + for _ in range(num_resnet_layers): + self.convs.append(SAGEConv(out_dim, out_dim)) + self.norms.append(GraphNorm(out_dim)) + + self.drop = nn.Dropout(dropout_p) + # Start residuals after the initial non-residual stack + self._residual_start = num_conv_wo_resnet + # Expose the final node embedding width + self.out_dim = out_dim + + # --- SAGPooling layer (applied once, after all convs) --- + # Uses SAGEConv internally for attention scoring to match the stack. + if self.use_sag_pool: + if not (0.0 < sag_ratio <= 1.0): + msg = "sag_ratio must be in (0, 1]" + raise ValueError(msg) + self.sag_pool: SAGPooling | None = SAGPooling( + in_channels=self.out_dim, + ratio=sag_ratio, + GNN=SAGEConv, # ty: ignore[invalid-argument-type] + nonlinearity=sag_nonlinearity, + ) + else: + self.sag_pool = None + + def _apply_conv_bidir( + self, + conv: SAGEConv, + x: torch.Tensor, + edge_index: torch.Tensor, + ) -> torch.Tensor: + """Apply a SAGEConv layer in forward and backward directions and average. + + Args: + conv: Convolution layer taken from self.convs. + x: Node feature matrix of shape [num_nodes, in_channels]. + edge_index: Edge index tensor of shape [2, num_edges]. + + Returns: + Tensor with updated node features of shape + [num_nodes, out_channels]. + """ + x_f = conv(x, edge_index) + if not self.bidirectional: + return x_f + x_b = conv(x, edge_index.flip(0)) + return (x_f + x_b) / 2 + + def forward(self, data: Data) -> torch.Tensor: + """Encode a batch of graphs and return pooled graph embeddings. + + The input batch of graphs is processed by the SAGEConv stack, + optionally followed by SAGPooling, and finally aggregated with + global mean pooling. + + Args: + data: Batched torch_geometric.data.Data object. + Expected attributes: + - x: Node features of shape [num_nodes, in_feats]. + - edge_index: Edge indices of shape [2, num_edges]. + - batch: Graph indices for each node of shape + [num_nodes]. + + Returns: + Tensor of shape [num_graphs, out_dim] containing one embedding + per input graph. + """ + x, edge_index, batch = data.x, data.edge_index, data.batch + assert x is not None + assert edge_index is not None + + for i, conv in enumerate(self.convs): + x_new = self._apply_conv_bidir(conv, x, edge_index) + x_new = self.norms[i](x_new, batch=batch) + x_new = self.conv_activation(x_new, **self.conv_act_kwargs) + x_new = self.drop(x_new) + + x = x_new if i < self._residual_start else x + x_new + + # --- SAGPooling (hierarchical pooling before readout) --- + if self.sag_pool is not None: + # SAGPooling may also return edge_attr, perm, score; we ignore those here. + x, edge_index, _, batch, _, _ = self.sag_pool( + x, + edge_index, + batch=batch, + ) + + return global_mean_pool(x, batch) + + +class GNN(nn.Module): + """Graph neural network with a SAGE-based encoder and MLP head. + + This model first encodes each input graph using GraphConvolutionSage + and then applies a feed-forward neural network to the resulting graph + embeddings to produce the final prediction. + """ + + def __init__( + self, + in_feats: int, + hidden_dim: int, + num_conv_wo_resnet: int, + num_resnet_layers: int, + mlp_units: list[int], + *, + conv_activation: Callable[..., torch.Tensor] = functional.leaky_relu, + conv_act_kwargs: dict[str, Any] | None = None, + mlp_activation: Callable[..., torch.Tensor] = functional.leaky_relu, + mlp_act_kwargs: dict[str, Any] | None = None, + dropout_p: float = 0.2, + bidirectional: bool = True, + output_dim: int = 1, + use_sag_pool: bool = False, + sag_ratio: float = 0.7, + sag_nonlinearity: Callable[..., torch.Tensor] = torch.tanh, + ) -> None: + """Initialize the GNN model. + + Args: + in_feats: Dimensionality of the input node features. + hidden_dim: Hidden dimensionality of the SAGEConv layers. + num_conv_wo_resnet: Number of SAGEConv layers before residual + connections are introduced in the encoder. + num_resnet_layers: Number of SAGEConv layers with residual + connections in the encoder. + mlp_units: List specifying the number of units in each hidden + layer of the MLP head. + conv_activation: Activation function applied after each graph + convolution. Defaults to torch.nn.functional.leaky_relu. + conv_act_kwargs: Additional keyword arguments passed to + conv_activation. Defaults to None. + mlp_activation: Activation function applied after each MLP layer. + Defaults to torch.nn.functional.leaky_relu. + mlp_act_kwargs: Additional keyword arguments passed to + mlp_activation. Defaults to None. + dropout_p: Dropout probability applied in the model (graph encoder and the MLP). + Defaults to 0.2. + bidirectional: If True, apply bidirectional message passing in + the encoder. Defaults to True. + output_dim: Dimensionality of the model output (e.g. number of + targets per graph). Defaults to 1. + use_sag_pool: If True, enable SAGPooling in the encoder. + Defaults to False. + sag_ratio: Fraction of nodes to keep in SAGPooling. Must be in + (0, 1]. Defaults to 0.7. + sag_nonlinearity: Nonlinearity used inside SAGPooling for score + computation. Defaults to torch.tanh. + """ + super().__init__() + + # Graph encoder + self.graph_conv = GraphConvolutionSage( + in_feats=in_feats, + hidden_dim=hidden_dim, + num_conv_wo_resnet=num_conv_wo_resnet, + num_resnet_layers=num_resnet_layers, + conv_activation=conv_activation, + conv_act_kwargs=conv_act_kwargs, + dropout_p=dropout_p, + bidirectional=bidirectional, + use_sag_pool=use_sag_pool, + sag_ratio=sag_ratio, + sag_nonlinearity=sag_nonlinearity, + ) + + self.mlp_activation = mlp_activation + self.mlp_act_kwargs = mlp_act_kwargs or {} + last_dim = self.graph_conv.graph_emb_dim + self.mlp_drop = nn.Dropout(dropout_p) + self.fcs: nn.ModuleList[nn.Linear] = nn.ModuleList() + for out_dim_ in mlp_units: + self.fcs.append(nn.Linear(last_dim, out_dim_)) + last_dim = out_dim_ + self.out = nn.Linear(last_dim, output_dim) + + def forward(self, data: Data) -> torch.Tensor: + """Compute predictions for a batch of graphs. + + The input graphs are encoded into graph embeddings by the + GraphConvolutionSage encoder, then passed through the MLP head + to obtain final predictions. + + Args: + data: Batched torch_geometric.data.Data object + containing the graphs to be evaluated. + + Returns: + Tensor of shape [num_graphs, output_dim] with the model + predictions for each graph in the batch. + """ + x = self.graph_conv(data) + for fc in self.fcs: + x = self.mlp_drop(self.mlp_activation(fc(x), **self.mlp_act_kwargs)) + return self.out(x) diff --git a/src/mqt/predictor/ml/helper.py b/src/mqt/predictor/ml/helper.py index ca9ba9029..1d10f4092 100644 --- a/src/mqt/predictor/ml/helper.py +++ b/src/mqt/predictor/ml/helper.py @@ -15,12 +15,23 @@ from pathlib import Path from typing import TYPE_CHECKING -from mqt.predictor.utils import calc_supermarq_features, get_openqasm_gates +import numpy as np +import torch +from qiskit import transpile +from qiskit.converters import circuit_to_dag +from qiskit.dagcircuit import DAGOpNode +from qiskit.transpiler import PassManager +from qiskit.transpiler.passes import RemoveBarriers +from sklearn.metrics import accuracy_score, classification_report, mean_absolute_error, mean_squared_error, r2_score + +from mqt.predictor.utils import calc_supermarq_features if TYPE_CHECKING: - import numpy as np from numpy._typing import NDArray from qiskit import QuantumCircuit + from torch import nn + from torch_geometric.data import Data + from torch_geometric.loader import DataLoader def get_path_training_data() -> Path: @@ -40,6 +51,13 @@ def get_path_trained_model(figure_of_merit: str) -> Path: return get_path_training_data() / "trained_model" / ("trained_clf_" + figure_of_merit + ".joblib") +def get_path_trained_model_gnn(figure_of_merit: str) -> Path: + """Return the path to the GNN checkpoint file for the given figure of merit.""" + base_dir = get_path_training_data() / "trained_model" + filename = f"trained_gnn_{figure_of_merit}.pth" + return base_dir / filename + + def get_path_training_circuits() -> Path: """Returns the path to the training circuits folder.""" return get_path_training_data() / "training_circuits" @@ -50,6 +68,90 @@ def get_path_training_circuits_compiled() -> Path: return get_path_training_data() / "training_circuits_compiled" +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 + return [ + "u3", + "u2", + "u1", + "cx", + "id", + "u0", + "u", + "p", + "x", + "y", + "z", + "h", + "s", + "sdg", + "t", + "tdg", + "rx", + "ry", + "rz", + "sx", + "sxdg", + "cz", + "cy", + "swap", + "ch", + "ccx", + "cswap", + "crx", + "cry", + "crz", + "cu1", + "cp", + "cu3", + "csx", + "cu", + "rxx", + "rzz", + "rccx", + "rc3x", + "c3x", + "c3sqrtx", + "c4x", + ] + + +def get_openqasm3_gates() -> list[str]: + """Returns a list of all quantum gates within the openQASM 3.0 standard header.""" + # according to https://openqasm.com/language/standard_library.html#standard-library + # Snapshot from OpenQASM 3.0 specification (version 3.0) + # Verify against latest spec when Qiskit or OpenQASM updates + return [ + "x", + "y", + "z", + "h", + "s", + "sdg", + "t", + "tdg", + "sx", + "p", + "rx", + "ry", + "rz", + "u", + "cx", + "cy", + "cz", + "ch", + "cp", + "crx", + "cry", + "crz", + "cu", + "swap", + "ccx", + "cswap", + ] + + def dict_to_featurevector(gate_dict: dict[str, int]) -> dict[str, int]: """Calculates and returns the feature vector of a given quantum circuit gate dictionary.""" res_dct = dict.fromkeys(get_openqasm_gates(), 0) @@ -70,7 +172,7 @@ def create_feature_vector(qc: QuantumCircuit) -> list[int | float]: The feature dictionary of the given quantum circuit. """ ops_list = qc.count_ops() - ops_list_dict = dict_to_featurevector(ops_list) # ty: ignore[invalid-argument-type] + ops_list_dict = dict_to_featurevector(ops_list) feature_dict = {} for key in ops_list_dict: @@ -88,14 +190,435 @@ def create_feature_vector(qc: QuantumCircuit) -> list[int | float]: return list(feature_dict.values()) +def create_dag(qc: QuantumCircuit) -> tuple[torch.Tensor, torch.Tensor, int]: + """Creates and returns the feature-annotated DAG of the quantum circuit. + + This is particularly useful for GNN-based models. It is needed to convert the quantum circuit + into a graph representation. Then, each node is annotated with features such as one-hot gate encoding, + sin/cos of parameters, arity, number of controls, number of parameters, critical path flag, fan-in and fan-out. + + Arguments: + qc: The quantum circuit to be converted to a DAG. + + Returns: + node_vector: features per node = [one-hot gate, sin/cos params, arity, controls, + num_params, critical_flag, fan_in, fan_out] + edge_index: 2 for E tensor of edges (src, dst) + number_of_gates: number of nodes in the DAG. + """ + # 0) cleanup & DAG + pm = PassManager(RemoveBarriers()) + qc = pm.run(qc) + try: + qc = transpile(qc, optimization_level=0, basis_gates=get_openqasm3_gates()) + except Exception as e: + msg = f"Transpilation failed: {e}" + raise ValueError(msg) from e + dag = circuit_to_dag(qc) + + unique_gates = [*get_openqasm3_gates(), "measure"] + gate2idx = {g: i for i, g in enumerate(unique_gates)} + number_gates = len(unique_gates) + + def _safe_float(val: object, default: float = 0.0) -> float: + try: + return float(val) # type: ignore[arg-type] + except (TypeError, ValueError): + return default + + # --- parameters sin/cos (max 3 param) --- + def param_vector(node: DAGOpNode, dim: int = 3) -> list[float]: + """Return [sin(p1), cos(p1), sin(p2), cos(p2), sin(p3), cos(p3)].""" + raw_params = getattr(node.op, "params", []) + params = [_safe_float(v) for v in raw_params[:dim]] + params += [0.0] * (dim - len(params)) + + out: list[float] = [] + for p in params: + out.extend([np.sin(p), np.cos(p)]) + return out # len = 2*dim + + nodes = list(dag.op_nodes()) + number_nodes = len(nodes) + + # prealloc + onehots = torch.zeros((number_nodes, number_gates), dtype=torch.float32) + num_params = torch.zeros((number_nodes, 1), dtype=torch.float32) + params = torch.zeros((number_nodes, 6), dtype=torch.float32) + arity = torch.zeros((number_nodes, 1), dtype=torch.float32) + controls = torch.zeros((number_nodes, 1), dtype=torch.float32) + fan_in = torch.zeros((number_nodes, 1), dtype=torch.float32) + fan_out = torch.zeros((number_nodes, 1), dtype=torch.float32) + + for i, node in enumerate(nodes): + name = node.op.name + if name not in unique_gates: + msg = f"Unknown gate: {name}" + raise ValueError(msg) + onehots[i, gate2idx[name]] = 1.0 + params[i] = torch.tensor(param_vector(node), dtype=torch.float32) + arity[i] = float(len(node.qargs)) + controls[i] = float(getattr(node.op, "num_ctrl_qubits", 0)) + num_params[i] = float(len(getattr(node.op, "params", []))) + preds = [p for p in dag.predecessors(node) if isinstance(p, DAGOpNode)] + succs = [s for s in dag.successors(node) if isinstance(s, DAGOpNode)] + fan_in[i] = len(preds) + fan_out[i] = len(succs) + + # edges DAG + idx_map = {node: i for i, node in enumerate(nodes)} + edges: list[list[int]] = [] + for src, dst, _ in dag.edges(): + if src in idx_map and dst in idx_map: + edges.append([idx_map[src], idx_map[dst]]) + if edges: + edge_index = torch.tensor(edges, dtype=torch.long).t().contiguous() + else: + edge_index = torch.empty((2, 0), dtype=torch.long) + + # --- critical path detection --- + topo_nodes = list(dag.topological_op_nodes()) + if not topo_nodes: + # No operation nodes: return node features with zero critical flags + critical_flag = torch.zeros((number_nodes, 1), dtype=torch.float32) + node_vector = torch.cat([onehots, params, arity, controls, num_params, critical_flag, fan_in, fan_out], dim=1) + return node_vector, edge_index, number_nodes + + dist_in = dict.fromkeys(topo_nodes, 0) + for node in topo_nodes: + preds = [p for p in dag.predecessors(node) if isinstance(p, DAGOpNode)] + if preds: + dist_in[node] = max(dist_in.get(p, 0) + 1 for p in preds) + + dist_out = dict.fromkeys(topo_nodes, 0) + for node in reversed(topo_nodes): + succs = [s for s in dag.successors(node) if isinstance(s, DAGOpNode)] + if succs: + dist_out[node] = max(dist_out.get(s, 0) + 1 for s in succs) + + critical_len = max(dist_in.get(n, 0) + dist_out.get(n, 0) for n in topo_nodes) + + critical_flag = torch.zeros((number_nodes, 1), dtype=torch.float32) + for i, node in enumerate(nodes): + # set critical flag to 1 if on critical path + if dist_in.get(node, 0) + dist_out.get(node, 0) == critical_len: + critical_flag[i] = 1.0 + + # final concat of features + node_vector = torch.cat([onehots, params, arity, controls, num_params, critical_flag, fan_in, fan_out], dim=1) + + return node_vector, edge_index, number_nodes + + +def get_gnn_input_features() -> int: + """Calculate GNN input feature dimension. + + Components: + - len(get_openqasm3_gates()) + 1: one-hot gate encoding (including 'measure') + - 6: sin/cos for up to 3 parameters + - 1: arity + - 1: controls + - 1: num_params + - 1: critical_flag + - 2: fan_in, fan_out + """ + return len(get_openqasm3_gates()) + 1 + 6 + 1 + 1 + 1 + 1 + 2 + + +def get_results_classes(preds: torch.Tensor, targets: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: + """Return predicted and target class indices. + + Arguments: + preds: model predictions + targets: ground truth targets (one-hot encoded or integer class labels) + + Returns: + pred_idx: predicted class indices + targets_idx: target class indices + """ + pred_idx = torch.argmax(preds, dim=1) + if targets.dim() == 1: + targets_idx = targets.long() + elif targets.size(1) == 1: + targets_idx = targets.view(-1).long() + else: + targets_idx = torch.argmax(targets, dim=1) + return pred_idx, targets_idx + + +# --------------------------------------------------- +# Evaluation +# --------------------------------------------------- +def evaluate_classification_model( + model: nn.Module, + loader: DataLoader, + loss_fn: nn.Module, + device: str | torch.device, + *, + return_arrays: bool = False, + verbose: bool = False, +) -> tuple[float, dict[str, float | str], tuple[np.ndarray, np.ndarray] | None]: + """Evaluate a classification model with the given loss function and compute accuracy metrics. + + Arguments: + model: classification model to be evaluated + loader: data loader for the evaluation dataset + loss_fn: loss function for evaluation + device: device to be used for evaluation (cuda or cpu) + return_arrays: whether to return prediction and target arrays + verbose: whether to print the metrics results. + + Returns: + avg_loss: average loss over the loader + metrics: {"custom_accuracy": ..., "classification_report": ..., "mse": ..., "rmse": ..., "mae": ..., "r2": ...} + arrays: (preds, y_true) if return_arrays=True, else None. + """ + if isinstance(device, str): + device = torch.device(device) + metrics: dict[str, float | str] = {} + model.eval() + total_loss, total = 0.0, 0 + all_preds, all_targets = [], [] + + with torch.no_grad(): + for batch in loader: + batch_device = batch.to(device) + preds = model(batch_device) + preds = torch.clamp(preds, 0.0, 1.0) + targets = batch_device.y.float() + + if targets.dim() == 1: + targets = targets.unsqueeze(1) + if preds.shape != targets.shape: + msg = f"Shape mismatch: preds {preds.shape} vs targets {targets.shape}" + raise ValueError(msg) + + bs = targets.size(0) + loss = loss_fn(preds, targets) + total_loss += loss.item() * bs + total += bs + + all_preds.append(preds.detach().cpu()) + all_targets.append(targets.detach().cpu()) + + avg_loss = total_loss / max(1, total) + metrics = {"loss": float(avg_loss)} + if not all_preds or not all_targets: + arrays = (np.array([]), np.array([])) if return_arrays else None + return avg_loss, metrics, arrays + + preds = torch.cat(all_preds, dim=0) + targets = torch.cat(all_targets, dim=0) + + # --- compute accuracy --- + pred_classes, target_classes = get_results_classes(preds, targets) + acc = accuracy_score(target_classes, pred_classes) + classification_report_res = classification_report(target_classes, pred_classes, zero_division=0) + metrics["custom_accuracy"] = float(acc) + metrics["classification_report"] = classification_report_res + + if verbose: + mse = mean_squared_error(targets.numpy().reshape(-1), preds.numpy().reshape(-1)) + mae = mean_absolute_error(targets.numpy().reshape(-1), preds.numpy().reshape(-1)) + rmse = float(np.sqrt(mse)) + if targets.size(0) < 2 or torch.all(targets == targets[0]): + r2 = float("nan") + else: + r2 = float(r2_score(targets.numpy().reshape(-1), preds.numpy().reshape(-1))) + metrics.update({"mse": float(mse), "rmse": float(rmse), "mae": float(mae), "r2": float(r2)}) + + arrays = (preds.numpy(), targets.numpy()) if return_arrays else None + return avg_loss, metrics, arrays + + +def evaluate_regression_model( + model: nn.Module, + loader: DataLoader, + loss_fn: nn.Module, + device: str | torch.device, + *, + return_arrays: bool = False, + verbose: bool = False, +) -> tuple[float, dict[str, float | str], tuple[np.ndarray, np.ndarray] | None]: + """Evaluate a regression model (logits = scalar predictions). + + Arguments: + model: regression model to be evaluated + loader: data loader for the evaluation dataset + loss_fn: loss function for evaluation + device: device to be used for evaluation (cuda or cpu) + return_arrays: whether to return prediction and target arrays + verbose: whether to print the metrics results. + + Returns: + avg_loss: average loss over the loader + metrics: {"rmse": ..., "mae": ..., "r2": ...} + arrays: (preds, y_true) if return_arrays=True, else None + """ + if isinstance(device, str): + device = torch.device(device) + model.eval() + total_loss, total = 0.0, 0 + all_preds, all_targets = [], [] + + with torch.no_grad(): + for batch in loader: + batch_device = batch.to(device) + logits = model(batch_device) + y = batch_device.y.float().view_as(logits) + + loss = loss_fn(logits, y) + bs = y.numel() + total_loss += loss.item() * bs + total += bs + + preds_1d = logits.view(-1).detach().cpu().numpy() + y_1d = y.view(-1).detach().cpu().numpy() + all_preds.append(preds_1d) + all_targets.append(y_1d) + + avg_loss = total_loss / max(1, total) + preds = np.concatenate(all_preds, axis=0) if all_preds else np.array([]) + y_true = np.concatenate(all_targets, axis=0) if all_targets else np.array([]) + + metrics: dict[str, float | str] = {"loss": float(avg_loss)} + if preds.size > 0: + rmse = float(np.sqrt(mean_squared_error(y_true, preds))) + mae = float(mean_absolute_error(y_true, preds)) + r2 = float("nan") if y_true.shape[0] < 2 or np.all(y_true == y_true[0]) else float(r2_score(y_true, preds)) + metrics.update({"rmse": rmse, "mae": mae, "r2": r2}) + + if verbose: + print(f"[Eval] loss={avg_loss:.6f} | rmse={rmse:.6f} | mae={mae:.6f} | r2={metrics['r2']:.6f}") + + arrays = (preds, y_true) if return_arrays else None + return avg_loss, metrics, arrays + + +def train_model( + model: nn.Module, + train_loader: DataLoader, + optimizer: torch.optim.Optimizer, + loss_fn: nn.Module, + num_epochs: int, + task: str, + *, + device: str | torch.device | None = None, + verbose: bool = True, + val_loader: DataLoader | None = None, + patience: int = 10, + min_delta: float = 0.0, + restore_best: bool = True, +) -> None: + """Trains a GNN model with optional early stopping based on validation loss (MSE Loss). + + Arguments: + model: regression model to be trained + train_loader: training set split into mini-batch + optimizer: optimizer for model training + loss_fn: loss function for training + num_epochs: number of training epochs + task: either "classification" or "regression" + device: device to be used for training (gpu or cpu) + verbose: whether to print progress messages + val_loader: validation set split into mini-batch (optional) + patience: number of epochs with no improvement after which training will be stopped + min_delta: minimum change in the monitored quantity to qualify as an improvement + restore_best: whether to restore model weights from the epoch with the best validation loss + """ + if device is None: + device = "cuda" if torch.cuda.is_available() else "cpu" + elif isinstance(device, str): + device = torch.device(device) + model.to(device) + + best_state, best_metric = None, float("inf") + epochs_no_improve = 0 + + for epoch in range(1, num_epochs + 1): + model.train() + running_loss, total = 0.0, 0 + + for batch in train_loader: + batch_device = batch.to(device) + preds = model(batch_device) + targets = batch_device.y + if targets.dim() == 1: + targets = targets.unsqueeze(1) + loss = loss_fn(preds, targets) + + optimizer.zero_grad() + loss.backward() + optimizer.step() + + bs = targets.size(0) + running_loss += loss.item() * bs + total += bs + + train_loss = running_loss / max(1, total) + + if val_loader is not None: + if task == "classification": + # evaluate on validation set the loss function and metrics connected to classification + # Passed the model, val_loader, loss function, device, and + # verbose (leaving the user decide if also wants to print metrics during evaluation) + val_loss, val_metrics, _ = evaluate_classification_model( + model, val_loader, loss_fn, device=str(device), verbose=verbose + ) + elif task == "regression": + # evaluate on validation set the loss function and metrics connected to regression + # Passed the model, val_loader, loss function, device, and + # verbose (leaving the user decide if also wants to print metrics during evaluation) + val_loss, val_metrics, _ = evaluate_regression_model( + model, val_loader, loss_fn, device=str(device), verbose=verbose + ) + else: + # raise an error if task not classification or regression + msg = "Task variable not regression or classification" + raise ValueError(msg) + improved = (best_metric - val_loss) > min_delta + if improved: + best_metric = val_loss + best_state = {k: v.detach().cpu() for k, v in model.state_dict().items()} + epochs_no_improve = 0 + else: + epochs_no_improve += 1 + + if verbose: + if task == "classification": + print( + f"Epoch {epoch:03d}/{num_epochs} | train_loss={train_loss:.6f} | " + f"val_loss={val_loss:.6f} | acc={val_metrics.get('custom_accuracy', 0):.4f} | patience={epochs_no_improve}/{patience}" + ) + else: + r2 = val_metrics.get("r2", 0) + + print( + f"Epoch {epoch:03d}/{num_epochs} | train_loss={train_loss:.6f} | " + f"val_loss={val_loss:.6f} | r2={r2:.4f} | patience={epochs_no_improve}/{patience}" + ) + + if epochs_no_improve >= patience: + if verbose: + print(f"Early stopping at epoch {epoch}.") + break + else: + if verbose: + print(f"Epoch {epoch:03d}/{num_epochs} | train_loss={train_loss:.6f}") + + if restore_best and best_state is not None: + model.load_state_dict(best_state) + model.to(device) + + @dataclass class TrainingData: - """Dataclass for the training data.""" + """Container for training/test data for both classical (numpy) and GNN (graph) models.""" - X_train: NDArray[np.float64] - y_train: NDArray[np.float64] - X_test: NDArray[np.float64] | None = None - y_test: NDArray[np.float64] | None = None + X_train: NDArray[np.float64] | list[Data] + y_train: NDArray[np.float64] | torch.Tensor + X_test: NDArray[np.float64] | list[Data] | None = None + y_test: NDArray[np.float64] | torch.Tensor | None = None indices_train: list[int] | None = None indices_test: list[int] | None = None names_list: list[str] | None = None diff --git a/src/mqt/predictor/ml/predictor.py b/src/mqt/predictor/ml/predictor.py index 84d461919..4280ba292 100644 --- a/src/mqt/predictor/ml/predictor.py +++ b/src/mqt/predictor/ml/predictor.py @@ -16,26 +16,51 @@ import zipfile from importlib import resources from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, TypedDict + +from joblib import dump as joblib_dump +from torch import nn +from torch_geometric.loader import DataLoader +from typing_extensions import Unpack + +from mqt.predictor.ml.gnn import GNN + +if sys.version_info >= (3, 11) and TYPE_CHECKING: # pragma: no cover + pass + +import gc +import json import matplotlib.pyplot as plt import numpy as np +import optuna +import torch from joblib import Parallel, delayed, load -from joblib import dump as joblib_dump from mqt.bench.targets import get_device +from optuna.samplers import TPESampler + +# cspell:disable-next-line from qiskit import QuantumCircuit from qiskit.qasm2 import dump +from safetensors.torch import load_file, save_file from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor -from sklearn.model_selection import GridSearchCV, train_test_split +from sklearn.model_selection import GridSearchCV, KFold, train_test_split +from torch_geometric.data import Data from mqt.predictor.hellinger import get_hellinger_model_path from mqt.predictor.ml.helper import ( TrainingData, + create_dag, create_feature_vector, + evaluate_classification_model, + evaluate_regression_model, + get_gnn_input_features, get_path_trained_model, + get_path_trained_model_gnn, get_path_training_circuits, get_path_training_circuits_compiled, get_path_training_data, + train_model, ) from mqt.predictor.reward import ( crit_depth, @@ -48,10 +73,16 @@ from mqt.predictor.utils import timeout_watcher if TYPE_CHECKING: + from numpy._typing import NDArray from qiskit.transpiler import Target from mqt.predictor.reward import figure_of_merit + +GNNSample = tuple[torch.Tensor, torch.Tensor | None, torch.Tensor, int, str] +FeatureSample = tuple[list[float], str] +TrainingSample = GNNSample | FeatureSample + plt.rcParams["font.family"] = "Times New Roman" logger = logging.getLogger("mqt-predictor") @@ -61,6 +92,15 @@ NO_PARALLEL = sys.platform == "win32" and sys.version_info >= (3, 13) +class TrainGNNKwargs(TypedDict, total=False): + """Arguments for training the GNN model.""" + + num_epochs: int + num_trials: int + verbose: bool + patience: int + + def setup_device_predictor( devices: list[Target], figure_of_merit: figure_of_merit = "expected_fidelity", @@ -68,6 +108,9 @@ def setup_device_predictor( path_compiled_circuits: Path | None = None, path_training_data: Path | None = None, timeout: int = 600, + *, + gnn: bool = False, + **gnn_kwargs: Unpack[TrainGNNKwargs], ) -> bool: """Sets up the device predictor for the given figure of merit. @@ -78,33 +121,37 @@ def setup_device_predictor( path_compiled_circuits: The path to the directory where the compiled circuits should be saved. Defaults to None. path_training_data: The path to the directory where the generated training data should be saved. Defaults to None. timeout: The timeout in seconds for the compilation of a single circuit. Defaults to 600. + gnn: Whether to use a GNN for training. Defaults to False. + gnn_kwargs: Additional keyword arguments for GNN training. Returns: True if the setup was successful, False otherwise. """ - predictor = Predictor( - figure_of_merit=figure_of_merit, - devices=devices, - ) + predictor = Predictor(figure_of_merit=figure_of_merit, devices=devices, gnn=gnn) try: - logger.info(f"Start the training for the figure of merit: {figure_of_merit}") + logger.info("Start the training for the figure of merit: %s", figure_of_merit) # Step 1: Generate compiled circuits for all devices predictor.compile_training_circuits( path_uncompiled_circuits=path_uncompiled_circuits, path_compiled_circuits=path_compiled_circuits, timeout=timeout, ) - logger.info(f"Generated compiled circuit for {figure_of_merit}") + logger.info("Generated compiled circuit for %s", figure_of_merit) # Step 2: Generate training data from the compiled circuits predictor.generate_training_data( path_uncompiled_circuits=path_uncompiled_circuits, path_compiled_circuits=path_compiled_circuits, path_training_data=path_training_data, ) - logger.info(f"Generated training data for {figure_of_merit}") + logger.info("Generated training data for %s", figure_of_merit) + # Step 3: Train the random forest classifier - predictor.train_random_forest_model() - logger.info(f"Trained random forest classifier for {figure_of_merit}") + if not predictor.gnn: + predictor.train_random_forest_model() + logger.info("Trained random forest classifier for %s", figure_of_merit) + else: + predictor.train_gnn_model(**gnn_kwargs) + logger.info("Trained random GNN for %s", figure_of_merit) except FileNotFoundError: logger.exception("File not found during setup.") @@ -128,6 +175,8 @@ def __init__( self, devices: list[Target], figure_of_merit: figure_of_merit = "expected_fidelity", + *, + gnn: bool = False, logger_level: int = logging.INFO, ) -> None: """Initializes the Predictor class. @@ -136,12 +185,13 @@ def __init__( figure_of_merit: The figure of merit to be used for training. devices: The devices to be used for training. logger_level: The level of the logger. Defaults to logging.INFO. - + gnn: Decide if using GNN or other models """ logger.setLevel(logger_level) self.figure_of_merit = figure_of_merit self.devices = devices + self.gnn = gnn self.devices.sort( key=lambda x: x.description ) # sorting is necessary to determine the ground truth label later on when generating the training data @@ -182,7 +232,7 @@ def _compile_all_circuits_devicewise( for filename in path_uncompiled_circuits.iterdir(): if filename.suffix != ".qasm": continue - qc = QuantumCircuit.from_qasm_file(filename) # ty: ignore[invalid-argument-type] + qc = QuantumCircuit.from_qasm_file(filename) if qc.num_qubits > dev_max_qubits: continue @@ -277,18 +327,49 @@ def generate_training_data( ) 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): + if all(score == -1 for score in scores.values()): continue - training_data.append(training_sample) + + if self.gnn: + x, _y, edge_idx, n_nodes, target_label = training_sample + value_device = [scores.get(dev.description, -1.0) for dev in self.devices] + gnn_training_sample = Data( + x=x, + # unsqueeze to avoid concatenation issues later on + y=torch.tensor(value_device, dtype=torch.float32).unsqueeze(0), + edge_index=edge_idx, + num_nodes=n_nodes, + target_label=target_label, + ) + + training_data.append(gnn_training_sample if self.gnn else training_sample) names_list.append(circuit_name) scores_list.append(scores) with resources.as_file(path_training_data) as path: - data = np.asarray(training_data, dtype=object) - np.save(str(path / ("training_data_" + self.figure_of_merit + ".npy")), data) + if self.gnn: + dataset_dir = path / f"graph_dataset_{self.figure_of_merit}" + dataset_dir.mkdir(parents=True, exist_ok=True) + + for idx, data in enumerate(training_data): + # data is a torch_geometric.data.Data object + tensors = { + "x": data.x, # node features + "y": data.y, # target values per device + "edge_index": data.edge_index, + "num_nodes": torch.tensor([data.num_nodes], dtype=torch.int64), + } + save_file(tensors, str(dataset_dir / f"{idx}.safetensors")) + + # target_label is a string; save it separately + label_path = dataset_dir / f"{idx}.label" + label_path.write_text(str(data.target_label), encoding="utf-8") + else: + data = np.asarray(training_data, dtype=object) + np.save(str(path / ("training_data_" + self.figure_of_merit + ".npy")), data) + data = np.asarray(names_list, dtype=str) np.save(str(path / ("names_list_" + self.figure_of_merit + ".npy")), data) data = np.asarray(scores_list, dtype=object) @@ -300,7 +381,7 @@ def _generate_training_sample( path_uncompiled_circuit: Path, path_compiled_circuits: Path, logger_level: int = logging.INFO, - ) -> tuple[tuple[list[Any], Any], str, list[float]]: + ) -> tuple[TrainingSample, str, dict[str, float]]: """Handles to create a training sample from a given file. Arguments: @@ -359,15 +440,277 @@ def _generate_training_sample( if num_not_empty_entries == 0: logger.warning("no compiled circuits found for:" + str(file)) - scores_list = list(scores.values()) + scores_list = scores target_label = max(scores, key=lambda k: scores[k]) - qc = QuantumCircuit.from_qasm_file(path_uncompiled_circuit / file) # ty: ignore[invalid-argument-type] - feature_vec = create_feature_vector(qc) - training_sample = (feature_vec, target_label) + qc = QuantumCircuit.from_qasm_file(path_uncompiled_circuit / file) + training_sample: TrainingSample + if self.gnn: + x, edge_index, number_of_gates = create_dag(qc) + training_sample = (x, None, edge_index, number_of_gates, target_label) + else: + feature_vec = create_feature_vector(qc) + training_sample = (feature_vec, target_label) circuit_name = str(file).split(".")[0] return training_sample, circuit_name, scores_list + def objective( + self, + trial: optuna.Trial, + dataset: NDArray[np.float64] | list[Data], + task: str, + in_feats: int, + num_outputs: int, + loss_fn: nn.Module, + k_folds: int, + batch_size: int = 32, + num_epochs: int = 10, + patience: int = 10, + device: str | None = None, + *, + verbose: bool = False, + ) -> float: + """Objective function for Optuna GNN hyperparameter optimization. + + Arguments: + trial: The Optuna trial object. + dataset: Dataset used for training and validation (classical or GNN). + task: Task type, e.g. "classification", or "regression". + in_feats: Number of input node features. + num_outputs: Number of model outputs (classes or regression targets). + loss_fn: Loss function to optimize. + k_folds: Number of cross-validation folds. + batch_size: Batch size for training. + num_epochs: Maximum number of training epochs per fold. + patience: Early-stopping patience (epochs without improvement). + device: Device to use for training (e.g. "cpu" or "cuda"), or None. + verbose: Whether to print verbose output during training. + + Returns: + mean_val: The mean validation metric considering the k-folds. + """ + # Type of device used + if device is None: + device = "cuda" if torch.cuda.is_available() else "cpu" + device_obj = torch.device(device) + + hidden_dim = trial.suggest_int("hidden_dim", 8, 64) + num_conv_wo_resnet = trial.suggest_int("num_conv_wo_resnet", 1, 3) + num_resnet_layers = trial.suggest_int("num_resnet_layers", 1, 9) + dropout = trial.suggest_categorical("dropout", [0.0, 0.1, 0.2, 0.3]) + sag_pool = trial.suggest_categorical("sag_pool", [False, True]) + bidirectional = trial.suggest_categorical("bidirectional", [False, True]) # True, False]) + mlp_options = [ + "none", + "32", + "64", + "128", + "256", + "64,32", + "128,32", + "128,64", + "256,32", + "256,64", + "256,128", + "128,64,32", + "256,128,64", + ] + + mlp_str = trial.suggest_categorical("mlp", mlp_options) + mlp_units = [] if mlp_str == "none" else [int(x) for x in mlp_str.split(",")] + lr = trial.suggest_categorical("lr", [1e-2, 1e-3, 1e-4]) + # Ensure at least 2 folds + max_splits = len(dataset) + k_folds = min(max(2, k_folds), max_splits) + if k_folds < 2: + msg = f"Not enough samples ({len(dataset)}) for k-folds (k={k_folds})." + raise ValueError(msg) + # Split into k-folds, stratified not needed because classification treated as regression + kf = KFold(n_splits=k_folds, shuffle=True) + fold_val_best_losses: list[float] = [] + for _fold_idx, (train_idx, val_idx) in enumerate(kf.split(range(len(dataset)))): + train_subset = [dataset[i] for i in train_idx] + val_subset = [dataset[i] for i in val_idx] + # Transform the data into loaders + train_loader = DataLoader(train_subset, batch_size=batch_size, shuffle=True) + val_loader = DataLoader(val_subset, batch_size=batch_size, shuffle=False) + # Define the GNN + model = GNN( + in_feats=in_feats, + num_conv_wo_resnet=num_conv_wo_resnet, + hidden_dim=hidden_dim, + num_resnet_layers=num_resnet_layers, + mlp_units=mlp_units, + output_dim=num_outputs, + dropout_p=dropout, + bidirectional=bidirectional, + use_sag_pool=sag_pool, + sag_ratio=0.7, + conv_activation=torch.nn.functional.leaky_relu, + mlp_activation=torch.nn.functional.leaky_relu, + ).to(device_obj) + + optimizer = torch.optim.Adam(model.parameters(), lr=lr) + # Based on the task, do a training and evaluation for regression or classification + train_model( + model, + train_loader, + optimizer, + loss_fn, + task=task, + num_epochs=num_epochs, + device=device, + verbose=verbose, + val_loader=val_loader, + patience=patience, + min_delta=0.0, + restore_best=True, + ) + if task == "regression": + val_loss, _, _ = evaluate_regression_model( + model, val_loader, loss_fn, device=device, return_arrays=False, verbose=verbose + ) + else: + val_loss, _, _ = evaluate_classification_model( + model, val_loader, loss_fn, device=device, return_arrays=False, verbose=verbose + ) + + fold_val_best_losses.append(float(val_loss)) + del train_loader, val_loader, train_subset, val_subset, optimizer, model + if device_obj.type == "cuda": + torch.cuda.empty_cache() + gc.collect() + # Take the mean value + return float(np.mean(fold_val_best_losses)) + + def train_gnn_model( + self, + training_data: TrainingData | None = None, + num_epochs: int = 10, + num_trials: int = 2, + patience: int = 10, + *, + verbose: bool = False, + ) -> nn.Module: + """Train the GNN model(s) and return the trained model. + + Arguments: + training_data: The training data to use for training the model. + num_epochs: The number of epochs to train the model. + num_trials: The number of trials to run for hyperparameter optimization. + verbose: Whether to print verbose output during training. + patience: The patience variable for early stopping. + + Returns: + The trained GNN model. + """ + # Figure out outputs and save path + if self.figure_of_merit == "hellinger_distance": + if len(self.devices) != 1: + msg = "A single device must be provided for Hellinger distance model training." + raise ValueError(msg) + num_outputs = 1 + save_mdl_path = str(get_hellinger_model_path(self.devices[0], gnn=True)) + else: + num_outputs = max(1, len(self.devices)) + save_mdl_path = str(get_path_trained_model_gnn(self.figure_of_merit)) + + # Prepare data + if training_data is None: + training_data = self._get_prepared_training_data() + number_in_features = get_gnn_input_features() + loss_fn = nn.MSELoss() + task = "regression" if self.figure_of_merit == "hellinger_distance" else "classification" + sampler_obj = TPESampler(n_startup_trials=10) + study = optuna.create_study(study_name="Best GNN Model", direction="minimize", sampler=sampler_obj) + k_folds = min(len(training_data.y_train), 5) + + def _obj(trial: optuna.Trial) -> float: + return self.objective( + trial=trial, + dataset=training_data.X_train, + task=task, + in_feats=number_in_features, + num_outputs=num_outputs, + loss_fn=loss_fn, + k_folds=k_folds, + num_epochs=num_epochs, + patience=patience, + verbose=verbose, + ) + + study.optimize(_obj, n_trials=num_trials) + dict_best_hyper = study.best_trial.params # user_attrs.get("best_hparams") + # Build model (ensure final layer outputs raw logits/no activation) + json_dict = study.best_trial.params + mlp_str = dict_best_hyper["mlp"] + mlp_units = [] if mlp_str == "none" else [int(x) for x in mlp_str.split(",")] + + json_dict["num_outputs"] = len(self.devices) if self.figure_of_merit != "hellinger_distance" else 1 + + model = GNN( + in_feats=get_gnn_input_features(), + num_conv_wo_resnet=dict_best_hyper["num_conv_wo_resnet"], + hidden_dim=dict_best_hyper["hidden_dim"], + num_resnet_layers=dict_best_hyper["num_resnet_layers"], + mlp_units=mlp_units, + output_dim=json_dict["num_outputs"], + dropout_p=dict_best_hyper["dropout"], + bidirectional=dict_best_hyper["bidirectional"], + use_sag_pool=dict_best_hyper["sag_pool"], + sag_ratio=0.7, + conv_activation=torch.nn.functional.leaky_relu, + mlp_activation=torch.nn.functional.leaky_relu, + ).to("cuda" if torch.cuda.is_available() else "cpu") + + json_path = Path(save_mdl_path).with_suffix(".json") # works whether save_mdl_path is str or Path + json_dict["class_labels"] = [dev.description for dev in self.devices] + with json_path.open("w", encoding="utf-8") as f: + json.dump(json_dict, f, indent=4) + + # Device handling + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + model.to(device) + # Optimizer + optimizer = torch.optim.Adam(model.parameters(), lr=dict_best_hyper["lr"]) + # Train-validation split (needed for early stopping) + # This portion is separated from test set + x_train, x_val, _y_train, _y_val = train_test_split( + training_data.X_train, training_data.y_train, test_size=0.2, random_state=5 + ) + # Dataloader + train_loader = DataLoader(x_train, batch_size=16, shuffle=True) + val_loader = DataLoader(x_val, batch_size=16, shuffle=False) + train_model( + model, + train_loader, + optimizer, + loss_fn, + task=task, + num_epochs=num_epochs, + device=device, + verbose=verbose, + val_loader=val_loader, + patience=patience, + min_delta=0.0, + restore_best=True, + ) + if verbose and training_data.X_test is not None and len(training_data.X_test) > 0: + test_loader = DataLoader(training_data.X_test, batch_size=16, shuffle=False) # ty: ignore[invalid-argument-type] + if task == "regression": + avg_loss_test, dict_results, _ = evaluate_regression_model( + model, test_loader, loss_fn=loss_fn, device=device, verbose=verbose + ) + else: + avg_loss_test, dict_results, _ = evaluate_classification_model( + model, test_loader, loss_fn=loss_fn, device=device, verbose=verbose + ) + print(f"Test loss: {avg_loss_test:.4f}, {dict_results}") + + # Save the model + torch.save(model.state_dict(), save_mdl_path) + return model + def train_random_forest_model( self, training_data: TrainingData | None = None ) -> RandomForestRegressor | RandomForestClassifier: @@ -425,23 +768,66 @@ def _get_prepared_training_data(self) -> TrainingData: """ with resources.as_file(get_path_training_data() / "training_data_aggregated") as path: prefix = f"{self.figure_of_merit}.npy" - file_data = path / f"training_data_{prefix}" file_names = path / f"names_list_{prefix}" file_scores = path / f"scores_list_{prefix}" - if file_data.is_file() and file_names.is_file() and file_scores.is_file(): - training_data = np.load(file_data, allow_pickle=True) + if not self.gnn: + file_data = path / f"training_data_{prefix}" + else: + # New safetensors directory for GNN data + dataset_dir = path / f"graph_dataset_{self.figure_of_merit}" + + if (file_names.is_file() and file_scores.is_file()) and ( + (not self.gnn and file_data.is_file()) or (self.gnn and dataset_dir.is_dir()) + ): + if not self.gnn: + training_data = np.load(file_data, allow_pickle=True) + else: + # Reconstruct list[Data] from safetensors files + training_data = [] + # assume files are named 0.safetensors, 1.safetensors, ... + for sf in sorted(dataset_dir.glob("*.safetensors")): + idx = sf.stem + tensors = load_file(str(sf)) + + data = Data( + x=tensors["x"], + y=tensors["y"], + edge_index=tensors["edge_index"], + num_nodes=int(tensors["num_nodes"][0].item()), + ) + + # restore string label + label_path = dataset_dir / f"{idx}.label" + if label_path.is_file(): + data.target_label = label_path.read_text(encoding="utf-8").strip() + else: + data.target_label = "" # or raise error if you prefer + + training_data.append(data) + names_list = list(np.load(file_names, allow_pickle=True)) - scores_list = [list(scores) for scores in np.load(file_scores, allow_pickle=True)] + raw_scores = np.load(file_scores, allow_pickle=True) + scores_list: list[list[float]] = [] + for scores in raw_scores: + if isinstance(scores, dict): + # Legacy format: dict[device_name, score] + scores_list.append(list(scores.values())) + else: + # New format: list/array of per-device scores + scores_list.append(list(scores)) else: msg = "Training data not found." raise FileNotFoundError(msg) - x_list, y_list = zip(*training_data, strict=False) - x = np.array(x_list, dtype=np.float64) - y = np.array(y_list, dtype=str) + if not self.gnn: + x_list, y_list = zip(*training_data, strict=False) + x = np.array(x_list, dtype=np.float64) + y = np.array(y_list, dtype=str) + else: + x = training_data + y = np.array([el.target_label for el in training_data]) indices = np.arange(len(y), dtype=np.int64) - x_train, x_test, y_train, y_test, indices_train, indices_test = train_test_split( x, y, indices, test_size=0.3, random_state=5 ) @@ -459,13 +845,14 @@ def _get_prepared_training_data(self) -> TrainingData: def predict_device_for_figure_of_merit( - qc: Path | QuantumCircuit, figure_of_merit: figure_of_merit = "expected_fidelity" + qc: Path | QuantumCircuit, figure_of_merit: figure_of_merit = "expected_fidelity", *, gnn: bool = False ) -> Target: """Returns the probabilities for all supported quantum devices to be the most suitable one for the given quantum circuit. Arguments: qc: The QuantumCircuit or Path to the respective qasm file. figure_of_merit: The figure of merit to be used for compilation. + gnn: Whether to use a GNN for prediction. Defaults to False. Returns: The probabilities for all supported quantum devices to be the most suitable one for the given quantum circuit. @@ -475,24 +862,61 @@ def predict_device_for_figure_of_merit( ValueError: If no suitable device is found for the given quantum circuit. """ if isinstance(qc, Path) and qc.exists(): - qc = QuantumCircuit.from_qasm_file(qc) # ty: ignore[invalid-argument-type] + qc = QuantumCircuit.from_qasm_file(qc) assert isinstance(qc, QuantumCircuit) - - path = get_path_trained_model(figure_of_merit) + path = get_path_trained_model(figure_of_merit) if not gnn else get_path_trained_model_gnn(figure_of_merit) if not path.exists(): error_msg = "The ML model is not trained yet. Please train the model before using it." logger.error(error_msg) raise FileNotFoundError(error_msg) - clf = load(path) - - feature_vector = create_feature_vector(qc) - - probabilities = clf.predict_proba([feature_vector])[0] - class_labels = clf.classes_ - # sort all devices with decreasing probabilities - sorted_devices = np.array([ - label for _, label in sorted(zip(probabilities, class_labels, strict=False), reverse=True) - ]) + if not gnn: + clf = load(path) + + feature_vector = create_feature_vector(qc) + + probabilities = clf.predict_proba([feature_vector])[0] + class_labels = clf.classes_ + # sort all devices with decreasing probabilities + sorted_devices = np.array([ + label for _, label in sorted(zip(probabilities, class_labels, strict=False), reverse=True) + ]) + else: + # Open the json file save_mdl_path[:-4] + ".json" + with Path.open(path.with_suffix(".json"), encoding="utf-8") as f: + json_dict = json.load(f) + + mlp_str = json_dict["mlp"] + mlp_units = [] if mlp_str == "none" else [int(x) for x in mlp_str.split(",")] + device_str = "cuda" if torch.cuda.is_available() else "cpu" + gnn_model = GNN( + in_feats=get_gnn_input_features(), + num_conv_wo_resnet=json_dict["num_conv_wo_resnet"], + hidden_dim=json_dict["hidden_dim"], + num_resnet_layers=json_dict["num_resnet_layers"], + mlp_units=mlp_units, + output_dim=json_dict["num_outputs"], + dropout_p=json_dict["dropout"], + bidirectional=json_dict["bidirectional"], + use_sag_pool=json_dict["sag_pool"], + sag_ratio=0.7, + conv_activation=torch.nn.functional.leaky_relu, + mlp_activation=torch.nn.functional.leaky_relu, + ).to(device_str) + gnn_model.load_state_dict(torch.load(path, weights_only=True, map_location=device_str)) + x, edge_index, number_of_gates = create_dag(qc) + feature_vector = Data(x=x, edge_index=edge_index, num_nodes=number_of_gates).to(device_str) + gnn_model.eval() + class_labels = json_dict["class_labels"] + with torch.no_grad(): + outputs = gnn_model(feature_vector) + outputs = outputs.squeeze(0) + assert class_labels is not None + if len(class_labels) != len(outputs): + msg = "outputs and class_labels must be same length" + raise ValueError(msg) + + pairs = sorted(zip(outputs.tolist(), class_labels, strict=False), reverse=True) + sorted_devices = np.array([label for _, label in pairs]) for dev_name in sorted_devices: dev = get_device(dev_name) diff --git a/src/mqt/predictor/ml/training_data/trained_model/trained_clf_expected_fidelity.joblib b/src/mqt/predictor/ml/training_data/trained_model/trained_clf_expected_fidelity.joblib deleted file mode 100644 index 576f8f16c..000000000 Binary files a/src/mqt/predictor/ml/training_data/trained_model/trained_clf_expected_fidelity.joblib and /dev/null differ diff --git a/src/mqt/predictor/ml/training_data/trained_model/trained_hellinger_distance_regressor_quantinuum_h2_56.joblib b/src/mqt/predictor/ml/training_data/trained_model/trained_hellinger_distance_regressor_quantinuum_h2_56.joblib deleted file mode 100644 index fdafdf0b6..000000000 Binary files a/src/mqt/predictor/ml/training_data/trained_model/trained_hellinger_distance_regressor_quantinuum_h2_56.joblib and /dev/null differ diff --git a/src/mqt/predictor/rl/__init__.py b/src/mqt/predictor/rl/__init__.py index e11e350cd..1b3d40817 100644 --- a/src/mqt/predictor/rl/__init__.py +++ b/src/mqt/predictor/rl/__init__.py @@ -13,6 +13,11 @@ from importlib import import_module from typing import TYPE_CHECKING +from mqt.predictor.rl.gnn import SAGEActorCritic +from mqt.predictor.rl.gnn_ppo import create_gnn_policy, train_ppo_with_gnn +from mqt.predictor.rl.predictor import Predictor, rl_compile +from mqt.predictor.rl.predictorenv import PredictorEnv + if TYPE_CHECKING: from mqt.predictor.rl.experiments.data_generation import ( GeneratedBenchmarkCircuit, @@ -43,15 +48,18 @@ "PredictorEnv", "PredictorEvaluationResult", "RLTrainingResult", + "SAGEActorCritic", "TrainTestGenerationResult", "TrainTestSplit", "collect_working_benchmark_circuits", "compute_feature_importance", + "create_gnn_policy", "evaluate_main", "evaluate_trained_predictor", "generate_rl_train_test_data", "rl_compile", "run_rl_training", + "train_ppo_with_gnn", ] _NAME_TO_MODULE = { diff --git a/src/mqt/predictor/rl/actions.py b/src/mqt/predictor/rl/actions.py index 0e32d63d2..a155801cc 100644 --- a/src/mqt/predictor/rl/actions.py +++ b/src/mqt/predictor/rl/actions.py @@ -10,7 +10,7 @@ from __future__ import annotations -import os +# import os import sys import warnings from collections import defaultdict @@ -18,8 +18,8 @@ from enum import Enum from typing import TYPE_CHECKING -from bqskit import MachineModel -from bqskit import compile as bqskit_compile +# from bqskit import MachineModel +# from bqskit import compile as bqskit_compile from pytket.architecture import Architecture from pytket.passes import ( CliffordSimp, @@ -78,30 +78,39 @@ from mqt.predictor.rl.parsing import ( PreProcessTKETRoutingAfterQiskitLayout, - get_bqskit_native_gates, + # get_bqskit_native_gates, ) IS_WIN_PY313 = sys.platform == "win32" and sys.version_info[:2] == (3, 13) + +# Try to import AIRouting; it is optional (requires qiskit-ibm-transpiler). +HAS_AI_ROUTING = False if not IS_WIN_PY313: - # qiskit-ibm-transpiler currently emits import-time warnings - # these can not be ignored by the filterwarnings in pyproject.toml - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", - message=r"invalid escape sequence '\\w'", - category=DeprecationWarning, - ) - warnings.filterwarnings( - "ignore", - message=r"invalid escape sequence '\\w'", - category=SyntaxWarning, - ) - warnings.filterwarnings( - "ignore", - message=r'"is" with (?:a literal|\'str\' literal)', - category=SyntaxWarning, - ) - from qiskit_ibm_transpiler.ai.routing import AIRouting + try: + # qiskit-ibm-transpiler currently emits import-time warnings + # these cannot be suppressed via pyproject.toml filterwarnings + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + message=r"invalid escape sequence '\\w'", + category=DeprecationWarning, + ) + warnings.filterwarnings( + "ignore", + message=r"invalid escape sequence '\\w'", + category=SyntaxWarning, + ) + warnings.filterwarnings( + "ignore", + message=r'"is" with (?:a literal|\'str\' literal)', + category=SyntaxWarning, + ) + from qiskit_ibm_transpiler.ai.routing import AIRouting + from qiskit_ibm_transpiler.local_routing.routing.inference import RoutingInference + + HAS_AI_ROUTING = True + except ImportError: + pass if TYPE_CHECKING: @@ -179,6 +188,10 @@ class DeviceDependentAction(Action): # Registry of actions _ACTIONS: dict[str, Action] = {} +_AI_ROUTING_RUNTIME_STATE: dict[str, bool | RuntimeError | None] = { + "validated": False, + "error": None, +} def register_action(action: Action) -> Action: @@ -206,6 +219,37 @@ def remove_action(name: str) -> None: del _ACTIONS[name] +def ensure_ai_routing_runtime_available() -> None: + """Validate that AIRouting is usable at runtime and preload its model once. + + Importing ``qiskit_ibm_transpiler`` is not enough: the routing model is loaded + lazily on first use via ``RoutingInference()``, which may trigger a network + download. Predictor should fail before training starts if that model is not + locally available and cannot be downloaded. + """ + if not HAS_AI_ROUTING or bool(_AI_ROUTING_RUNTIME_STATE["validated"]): + return + cached_error = _AI_ROUTING_RUNTIME_STATE["error"] + if isinstance(cached_error, RuntimeError): + raise cached_error + + try: + RoutingInference() + except Exception as exc: + runtime_error = RuntimeError( + "AIRouting is installed but not usable: the qiskit-ibm-transpiler routing model " + "could not be loaded during startup. Predictor fails early here because otherwise " + "training would repeatedly hit the same runtime error mid-episode. Ensure the " + "routing model is already cached locally or that the machine has network access " + "for the initial download. The model repository can be overridden with " + "QISKIT_TRANSPILER_ROUTING_REPO_ID." + ) + _AI_ROUTING_RUNTIME_STATE["error"] = runtime_error + raise runtime_error from exc + + _AI_ROUTING_RUNTIME_STATE["validated"] = True + + register_action( DeviceIndependentAction( "Optimize1qGatesDecomposition", @@ -366,7 +410,7 @@ def remove_action(name: str) -> None: ) ) -#register_action( +# register_action( # DeviceDependentAction( # "BQSKitO2", # CompilationOrigin.BQSKIT, @@ -380,7 +424,7 @@ def remove_action(name: str) -> None: # num_workers=-1, # ), # ) -#) +# ) register_action( DeviceDependentAction( @@ -479,7 +523,7 @@ def remove_action(name: str) -> None: ) ) -if not IS_WIN_PY313: +if HAS_AI_ROUTING: register_action( DeviceDependentAction( "AIRouting", @@ -526,7 +570,7 @@ def remove_action(name: str) -> None: ) ) -#register_action( +# register_action( # DeviceDependentAction( # "BQSKitMapping", # CompilationOrigin.BQSKIT, @@ -548,7 +592,7 @@ def remove_action(name: str) -> None: # ) # ), # ) -#) +# ) register_action( DeviceDependentAction( @@ -561,7 +605,7 @@ def remove_action(name: str) -> None: ) ) -#register_action( +# register_action( # DeviceDependentAction( # "BQSKitSynthesis", # CompilationOrigin.BQSKIT, @@ -578,7 +622,7 @@ def remove_action(name: str) -> None: # ) # ), # ) -#) +# ) register_action( DeviceIndependentAction( @@ -690,7 +734,7 @@ def add_cregs_and_measurements( return qc -if not IS_WIN_PY313: +if HAS_AI_ROUTING: class SafeAIRouting(AIRouting): """Custom AIRouting wrapper that removes classical registers before routing. diff --git a/src/mqt/predictor/rl/approx_reward.py b/src/mqt/predictor/rl/approx_reward.py index f2cfade97..4aa2ee0a0 100644 --- a/src/mqt/predictor/rl/approx_reward.py +++ b/src/mqt/predictor/rl/approx_reward.py @@ -21,7 +21,18 @@ from qiskit import QuantumCircuit from qiskit.transpiler import InstructionProperties, Target -ALWAYS_EXCLUDED_OPS: set[str] = {"barrier", "delay", "id"} +ALWAYS_EXCLUDED_OPS: set[str] = { + "barrier", + "delay", + "id", + "if_else", + "while_loop", + "for_loop", + "switch_case", + "box", + "break", + "continue", +} def _get_operation_arity(device: Target, name: str) -> int | None: diff --git a/src/mqt/predictor/rl/experiments/evaluation.py b/src/mqt/predictor/rl/experiments/evaluation.py index b3dadc608..d25046d63 100644 --- a/src/mqt/predictor/rl/experiments/evaluation.py +++ b/src/mqt/predictor/rl/experiments/evaluation.py @@ -335,12 +335,12 @@ def rollout_circuit( ) step_count += 1 - if not (terminated or truncated) and step_count >= max_steps: - hit_step_limit = True - if predictor.env.action_terminate_index in predictor.env.valid_actions: - terminate_item = predictor.env.action_set[predictor.env.action_terminate_index] - used_compilation_passes.append(terminate_item.name) - obs, _reward_value, terminated, truncated, _ = predictor.env.step(predictor.env.action_terminate_index) + if not (terminated or truncated) and step_count >= max_steps: + hit_step_limit = True + if predictor.env.action_terminate_index in predictor.env.valid_actions: + terminate_item = predictor.env.action_set[predictor.env.action_terminate_index] + used_compilation_passes.append(terminate_item.name) + obs, _reward_value, terminated, truncated, _ = predictor.env.step(predictor.env.action_terminate_index) figure_of_merit_value, figure_of_merit_kind = predictor.env.calculate_reward(mode="auto") @@ -358,15 +358,28 @@ def rollout_circuit( ) -def clone_observation(obs: Observation) -> PolicyObservation: +def clone_observation(obs: object) -> PolicyObservation: """Return a detached copy of an observation dictionary for policy inference.""" - return {key: clone_feature_value(value) for key, value in obs.items()} + if not isinstance(obs, dict): + msg = f"Expected a flat observation dictionary, received {type(obs).__name__}." + raise TypeError(msg) + + cloned: PolicyObservation = {} + for key, value in obs.items(): + if not isinstance(key, str): + msg = f"Expected string observation keys, received {type(key).__name__}." + raise TypeError(msg) + cloned[key] = clone_feature_value(value) + return cloned -def clone_feature_value(value: FeatureValue) -> NDArray[np.float32]: +def clone_feature_value(value: object) -> NDArray[np.float32]: """Clone a feature value into a float32 array for policy inference.""" if isinstance(value, np.ndarray): return np.array(value, copy=True, dtype=np.float32) + if not isinstance(value, int): + msg = f"Expected int or ndarray observation values, received {type(value).__name__}." + raise TypeError(msg) return np.array([value], dtype=np.float32) diff --git a/src/mqt/predictor/rl/experiments/train_eval_sweep.py b/src/mqt/predictor/rl/experiments/train_eval_sweep.py deleted file mode 100644 index 35df02393..000000000 --- a/src/mqt/predictor/rl/experiments/train_eval_sweep.py +++ /dev/null @@ -1,1022 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -"""Run a resumable ESP-focused RL sweep over the selected devices and MDP variants. - -The script is intentionally narrow: - -- figure of merit is fixed to ``estimated_success_probability`` -- devices default to ``ibm_boston_156`` and ``iqm_garnet_20`` -- all RL MDP variants are trained -- both stochastic and deterministic evaluations are run -- optional locked baseline pipelines are evaluated - -The goal is a clean SLURM-friendly entry point that: - -- starts a fresh sweep when the output directory is empty -- resumes from saved checkpoints when a previous batch did not finish -- logs progress to one human-readable log file -- saves per-run JSON results that can be inspected later -""" - -from __future__ import annotations - -import argparse -import json -import logging -import math -import os -import shutil -import signal -import tempfile -import traceback -from contextlib import contextmanager -from dataclasses import asdict, dataclass, is_dataclass -from datetime import datetime, timezone -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from mqt.bench.targets import get_device -from sb3_contrib import MaskablePPO -from stable_baselines3.common.callbacks import BaseCallback - -from mqt.predictor.reward import esp_data_available -from mqt.predictor.rl.experiments.evaluation import evaluate_trained_predictor -from mqt.predictor.rl.experiments.pipeline_evaluation import run_selected_pipelines -from mqt.predictor.rl.experiments.training import run_rl_training - -if TYPE_CHECKING: - from collections.abc import Generator - - from qiskit.transpiler import Target - - from mqt.predictor.reward import figure_of_merit - from mqt.predictor.rl.experiments.evaluation import PredictorEvaluationResult - from mqt.predictor.rl.experiments.pipeline_evaluation import PipelineEvaluationResult - - -FIGURE_OF_MERIT: figure_of_merit = "estimated_success_probability" -DEFAULT_DEVICES = ("ibm_boston_156", "iqm_garnet_20") -DEFAULT_MDPS = ("paper", "flexible", "thesis", "hybrid") -DEFAULT_PIPELINES = ("qiskit_o3", "tket_o2") -EVALUATION_MODES = (("stochastic", False), ("deterministic", True)) - - -@dataclass(frozen=True, slots=True) -class RLRunSpec: - """One RL training and evaluation configuration.""" - - device_name: str - mdp: str - - @property - def label(self) -> str: - """Return a short human-readable run label.""" - return f"{self.device_name}/{self.mdp}" - - -def parse_args() -> argparse.Namespace: - """Parse command-line arguments.""" - parser = argparse.ArgumentParser( - description="Run a resumable RL sweep for estimated_success_probability.", - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - ) - parser.add_argument( - "--devices", - nargs="+", - default=list(DEFAULT_DEVICES), - help="Target devices to process.", - ) - parser.add_argument( - "--mdps", - nargs="+", - default=list(DEFAULT_MDPS), - choices=list(DEFAULT_MDPS), - help="MDP variants to train and evaluate.", - ) - parser.add_argument("--timesteps", type=int, default=100000, help="Training timesteps per RL model.") - parser.add_argument( - "--checkpoint-frequency", - type=int, - default=2048, - help="PPO checkpoint cadence in timesteps.", - ) - parser.add_argument("--train-verbose", type=int, default=1, help="Verbosity passed to PPO.") - parser.add_argument("--max-steps", type=int, default=200, help="Maximum evaluation rollout steps.") - parser.add_argument("--seed", type=int, default=0, help="Evaluation seed.") - parser.add_argument( - "--output-dir", - type=Path, - default=Path.cwd() / "rl_experiment_runs" / "estimated_success_probability_sweep", - help="Stable output directory used for logs, checkpoints, and results.", - ) - parser.add_argument("--train-dir", type=Path, default=None, help="Optional training-circuit directory.") - parser.add_argument("--test-dir", type=Path, default=None, help="Optional test-circuit directory.") - parser.add_argument( - "--skip-pipelines", - action="store_true", - help="Skip the locked baseline pipeline evaluations.", - ) - parser.add_argument( - "--test-training", - action="store_true", - help="Use the lightweight training mode from the existing training helper.", - ) - parser.add_argument( - "--force", - action="store_true", - help="Ignore completed markers and rerun every phase.", - ) - parser.add_argument( - "--fail-fast", - action="store_true", - help="Stop at the first failed training, evaluation, or pipeline run.", - ) - parser.add_argument( - "--dry-run", - action="store_true", - help="Print the planned sweep and exit.", - ) - return parser.parse_args() - - -def now_utc_iso() -> str: - """Return the current UTC timestamp in ISO format.""" - return datetime.now(tz=timezone.utc).replace(microsecond=0).isoformat() - - -def configure_logging(output_dir: Path) -> logging.Logger: - """Configure file and console logging for the sweep.""" - output_dir.mkdir(parents=True, exist_ok=True) - logger = logging.getLogger("mqt-predictor.esp-sweep") - logger.setLevel(logging.INFO) - logger.handlers.clear() - logger.propagate = False - - formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s") - - file_handler = logging.FileHandler(output_dir / "sweep.log", encoding="utf-8") - file_handler.setFormatter(formatter) - logger.addHandler(file_handler) - - stream_handler = logging.StreamHandler() - stream_handler.setFormatter(formatter) - logger.addHandler(stream_handler) - return logger - - -def sanitize_json_value(value: object) -> object: - """Convert objects into JSON-compatible values.""" - if is_dataclass(value) and not isinstance(value, type): - return sanitize_json_value(asdict(value)) - - if isinstance(value, Path): - return str(value) - - if isinstance(value, dict): - return {str(key): sanitize_json_value(val) for key, val in value.items()} - - if isinstance(value, list | tuple): - return [sanitize_json_value(item) for item in value] - - if isinstance(value, set | frozenset): - return [sanitize_json_value(item) for item in sorted(value)] - - if hasattr(value, "item"): - try: - item_method = value.item - if callable(item_method): - return sanitize_json_value(item_method()) # type: ignore[misc] - except (TypeError, ValueError): - pass - - if isinstance(value, float): - return value if math.isfinite(value) else None - - return value - - -def atomic_write_json(path: Path, payload: object) -> None: - """Atomically write one JSON file.""" - path.parent.mkdir(parents=True, exist_ok=True) - with tempfile.NamedTemporaryFile( - "w", - encoding="utf-8", - dir=path.parent, - prefix=f"{path.name}.", - suffix=".tmp", - delete=False, - ) as handle: - json.dump(sanitize_json_value(payload), handle, indent=2, sort_keys=True) - handle.write("\n") - handle.flush() - os.fsync(handle.fileno()) - temp_path = Path(handle.name) - - temp_path.replace(path) - - -def load_json(path: Path, fallback: dict[str, Any]) -> dict[str, Any]: - """Load a JSON file or return the provided fallback value.""" - if not path.is_file(): - return json.loads(json.dumps(fallback)) - return json.loads(path.read_text(encoding="utf-8")) - - -def copy_file_atomically(source: Path, destination: Path) -> Path: - """Copy a file into place atomically.""" - if not source.is_file(): - msg = f"Expected artifact '{source}' does not exist." - raise FileNotFoundError(msg) - - destination.parent.mkdir(parents=True, exist_ok=True) - with tempfile.NamedTemporaryFile( - "wb", - dir=destination.parent, - prefix=f"{destination.name}.", - suffix=".tmp", - delete=False, - ) as handle: - temp_path = Path(handle.name) - - shutil.copy2(source, temp_path) - temp_path.replace(destination) - return destination - - -def install_signal_handlers() -> None: - """Convert TERM/INT signals into KeyboardInterrupt for graceful shutdown.""" - - def handle_interrupt(signum: int, _frame: object) -> None: - msg = f"Received signal {signum}." - raise KeyboardInterrupt(msg) - - signal.signal(signal.SIGTERM, handle_interrupt) - signal.signal(signal.SIGINT, handle_interrupt) - - -def rl_run_directory(output_dir: Path, spec: RLRunSpec) -> Path: - """Return the output directory for one RL run.""" - return output_dir / spec.device_name / "rl" / spec.mdp - - -def pipeline_directory(output_dir: Path, device_name: str, pipeline_name: str) -> Path: - """Return the output directory for one pipeline run.""" - return output_dir / device_name / "pipelines" / pipeline_name - - -def empty_rl_status(spec: RLRunSpec) -> dict[str, Any]: - """Return the initial status document for one RL run.""" - return { - "device": spec.device_name, - "figure_of_merit": FIGURE_OF_MERIT, - "mdp": spec.mdp, - "created_at": now_utc_iso(), - "updated_at": now_utc_iso(), - "training": {"status": "pending", "completed_timesteps": 0}, - "evaluations": {mode_name: {"status": "pending"} for mode_name, _ in EVALUATION_MODES}, - } - - -def empty_pipeline_status(device_name: str, pipeline_name: str) -> dict[str, Any]: - """Return the initial status document for one pipeline run.""" - return { - "device": device_name, - "figure_of_merit": FIGURE_OF_MERIT, - "pipeline": pipeline_name, - "created_at": now_utc_iso(), - "updated_at": now_utc_iso(), - "result": {"status": "pending"}, - } - - -def save_rl_status(path: Path, status: dict[str, Any]) -> None: - """Persist one RL run status document.""" - status["updated_at"] = now_utc_iso() - atomic_write_json(path, status) - - -def save_pipeline_status(path: Path, status: dict[str, Any]) -> None: - """Persist one pipeline status document.""" - status["updated_at"] = now_utc_iso() - atomic_write_json(path, status) - - -def checkpoint_sort_key(path: Path) -> int: - """Return the timestep suffix encoded in a checkpoint filename.""" - for part in reversed(path.stem.split("_")): - if part.isdigit(): - return int(part) - return -1 - - -def find_latest_checkpoint(checkpoint_dir: Path) -> Path | None: - """Return the newest PPO checkpoint in a directory.""" - if not checkpoint_dir.is_dir(): - return None - - checkpoint_files = sorted(checkpoint_dir.glob("model_checkpoint_*_steps.zip"), key=checkpoint_sort_key) - if not checkpoint_files: - return None - return checkpoint_files[-1] - - -def get_model_num_timesteps(model_path: Path) -> int: - """Load a PPO checkpoint and return its completed timestep count.""" - return int(MaskablePPO.load(model_path).num_timesteps) - - -class TrainingHeartbeatCallback(BaseCallback): - """Update the on-disk training status during long PPO runs.""" - - def __init__( - self, - *, - spec: RLRunSpec, - target_timesteps: int, - initial_timesteps: int, - status: dict[str, Any], - status_path: Path, - logger: logging.Logger, - log_frequency: int, - ) -> None: - """Initialize the heartbeat callback.""" - super().__init__() - self.spec = spec - self.target_timesteps = target_timesteps - self.status = status - self.status_path = status_path - self.progress_logger = logger - self.log_frequency = max(1, log_frequency) - self.last_logged_timesteps = initial_timesteps - - def _on_step(self) -> bool: - current_timesteps = int(self.model.num_timesteps) - if current_timesteps - self.last_logged_timesteps < self.log_frequency: - return True - - self.last_logged_timesteps = current_timesteps - self.status["training"]["completed_timesteps"] = current_timesteps - self.status["training"]["last_progress_at"] = now_utc_iso() - save_rl_status(self.status_path, self.status) - self.progress_logger.info( - "Training %s: %d/%d timesteps (%.1f%%).", - self.spec.label, - current_timesteps, - self.target_timesteps, - (current_timesteps / self.target_timesteps) * 100.0 if self.target_timesteps > 0 else 100.0, - ) - return True - - -def summarize_predictor_evaluation(result: PredictorEvaluationResult) -> dict[str, Any]: - """Return a compact summary for one RL evaluation result.""" - figure_values = [circuit.figure_of_merit_value for circuit in result.circuits] - average_figure_value = sum(figure_values) / len(figure_values) if figure_values else None - return { - "evaluated_circuits": len(result.circuits), - "average_figure_of_merit_value": average_figure_value, - "average_metrics": sanitize_json_value(result.average_metrics), - "step_limit_hits": sum(1 for circuit in result.circuits if circuit.hit_step_limit), - "action_effectiveness": { - "total_uses": result.action_effectiveness.total_uses, - "total_effective_uses": result.action_effectiveness.total_effective_uses, - "overall_effectiveness_ratio": result.action_effectiveness.overall_effectiveness_ratio, - }, - } - - -def summarize_pipeline_evaluation(result: PipelineEvaluationResult) -> dict[str, Any]: - """Return a compact summary for one pipeline result.""" - figure_values = [circuit.figure_of_merit_value for circuit in result.circuits] - average_figure_value = sum(figure_values) / len(figure_values) if figure_values else None - return { - "evaluated_circuits": len(result.circuits), - "average_figure_of_merit_value": average_figure_value, - "average_metrics": sanitize_json_value(result.average_metrics), - "action_effectiveness": { - "total_uses": result.action_effectiveness.total_uses, - "total_effective_uses": result.action_effectiveness.total_effective_uses, - "overall_effectiveness_ratio": result.action_effectiveness.overall_effectiveness_ratio, - }, - } - - -@contextmanager -def working_directory(path: Path) -> Generator[None, None, None]: - """Temporarily change the working directory.""" - previous = Path.cwd() - os.chdir(path) - try: - yield - finally: - os.chdir(previous) - - -def resolve_device(device_name: str) -> Target: - """Load one target device and ensure ESP is supported.""" - device = get_device(device_name) - if not esp_data_available(device): - msg = f"Device '{device_name}' does not provide the calibration data required for ESP." - raise ValueError(msg) - return device - - -def get_single_pipeline_result( - results: list[PipelineEvaluationResult], - pipeline_name: str, -) -> PipelineEvaluationResult: - """Return the single expected pipeline result.""" - if len(results) != 1: - msg = f"Expected one pipeline result for '{pipeline_name}', received {len(results)}." - raise RuntimeError(msg) - return results[0] - - -def finalize_training_from_checkpoint( - *, - spec: RLRunSpec, - status_path: Path, - status: dict[str, Any], - checkpoint_path: Path, - checkpoint_dir: Path, - model_copy_path: Path, - training_result_path: Path, - args: argparse.Namespace, - logger: logging.Logger, -) -> Path: - """Convert an already-complete checkpoint into the final saved model artifact.""" - completed_timesteps = get_model_num_timesteps(checkpoint_path) - copied_model_path = copy_file_atomically(checkpoint_path, model_copy_path) - training_payload = { - "completed_at": now_utc_iso(), - "device": spec.device_name, - "figure_of_merit": FIGURE_OF_MERIT, - "mdp": spec.mdp, - "timesteps": args.timesteps, - "completed_timesteps": completed_timesteps, - "saved_model_path": str(copied_model_path), - "source_model_path": str(checkpoint_path), - "checkpoint_directory": str(checkpoint_dir), - } - atomic_write_json(training_result_path, training_payload) - - status["training"] = { - "status": "completed", - "finished_at": now_utc_iso(), - "timesteps": args.timesteps, - "completed_timesteps": completed_timesteps, - "verbose": args.train_verbose, - "checkpoint_directory": str(checkpoint_dir), - "checkpoint_frequency": args.checkpoint_frequency, - "resumed_from_checkpoint": str(checkpoint_path), - "saved_model_path": str(copied_model_path), - "result_path": str(training_result_path), - } - save_rl_status(status_path, status) - logger.info("Finalized completed checkpoint for %s.", spec.label) - return copied_model_path - - -def run_training_phase( - *, - spec: RLRunSpec, - device: Target, - run_dir: Path, - args: argparse.Namespace, - logger: logging.Logger, -) -> Path: - """Train one RL configuration or resume it from the latest checkpoint.""" - status_path = run_dir / "status.json" - status = load_json(status_path, empty_rl_status(spec)) - - model_copy_path = run_dir / "artifacts" / f"model_{spec.device_name}_{spec.mdp}.zip" - training_result_path = run_dir / "training.json" - checkpoint_dir = run_dir / "checkpoints" - - if ( - not args.force - and status["training"]["status"] == "completed" - and model_copy_path.is_file() - and training_result_path.is_file() - ): - logger.info("Skipping completed training for %s.", spec.label) - return model_copy_path - - checkpoint_path = None if args.force else find_latest_checkpoint(checkpoint_dir) - completed_timesteps = 0 if checkpoint_path is None else get_model_num_timesteps(checkpoint_path) - - if checkpoint_path is not None and completed_timesteps >= args.timesteps: - return finalize_training_from_checkpoint( - spec=spec, - status_path=status_path, - status=status, - checkpoint_path=checkpoint_path, - checkpoint_dir=checkpoint_dir, - model_copy_path=model_copy_path, - training_result_path=training_result_path, - args=args, - logger=logger, - ) - - remaining_timesteps = args.timesteps - completed_timesteps - if checkpoint_path is None: - logger.info("Training %s for %d timesteps.", spec.label, args.timesteps) - else: - logger.info( - "Resuming %s from %s at %d/%d timesteps.", - spec.label, - checkpoint_path.name, - completed_timesteps, - args.timesteps, - ) - - status["training"] = { - "status": "running", - "started_at": status["training"].get("started_at", now_utc_iso()), - "timesteps": args.timesteps, - "completed_timesteps": completed_timesteps, - "verbose": args.train_verbose, - "checkpoint_directory": str(checkpoint_dir), - "checkpoint_frequency": args.checkpoint_frequency, - "resumed_from_checkpoint": str(checkpoint_path) if checkpoint_path is not None else None, - } - save_rl_status(status_path, status) - - try: - heartbeat = TrainingHeartbeatCallback( - spec=spec, - target_timesteps=args.timesteps, - initial_timesteps=completed_timesteps, - status=status, - status_path=status_path, - logger=logger, - log_frequency=args.checkpoint_frequency, - ) - with working_directory(run_dir): - training_result = run_rl_training( - device=device, - figure_of_merit=FIGURE_OF_MERIT, - mdp=spec.mdp, - timesteps=remaining_timesteps, - verbose=args.train_verbose, - test=args.test_training, - path_training_circuits=args.train_dir, - checkpoint_directory=checkpoint_dir, - checkpoint_frequency=args.checkpoint_frequency, - resume_from_checkpoint=checkpoint_path, - callback=heartbeat, - ) - - copied_model_path = copy_file_atomically(training_result.model_path, model_copy_path) - training_payload = { - "completed_at": now_utc_iso(), - "device": spec.device_name, - "figure_of_merit": FIGURE_OF_MERIT, - "mdp": spec.mdp, - "timesteps": args.timesteps, - "completed_timesteps": training_result.total_timesteps, - "saved_model_path": str(copied_model_path), - "source_model_path": str(training_result.model_path), - "checkpoint_directory": str(checkpoint_dir), - "resumed_from_checkpoint": ( - str(training_result.resumed_from_checkpoint) - if training_result.resumed_from_checkpoint is not None - else None - ), - } - atomic_write_json(training_result_path, training_payload) - - status["training"] = { - "status": "completed", - "started_at": status["training"]["started_at"], - "finished_at": now_utc_iso(), - "timesteps": args.timesteps, - "completed_timesteps": training_result.total_timesteps, - "verbose": args.train_verbose, - "checkpoint_directory": str(checkpoint_dir), - "checkpoint_frequency": args.checkpoint_frequency, - "resumed_from_checkpoint": ( - str(training_result.resumed_from_checkpoint) - if training_result.resumed_from_checkpoint is not None - else None - ), - "saved_model_path": str(copied_model_path), - "result_path": str(training_result_path), - } - save_rl_status(status_path, status) - except KeyboardInterrupt: - latest_checkpoint = find_latest_checkpoint(checkpoint_dir) - latest_timesteps = ( - completed_timesteps if latest_checkpoint is None else get_model_num_timesteps(latest_checkpoint) - ) - status["training"] = { - "status": "interrupted", - "started_at": status["training"]["started_at"], - "finished_at": now_utc_iso(), - "timesteps": args.timesteps, - "completed_timesteps": latest_timesteps, - "checkpoint_directory": str(checkpoint_dir), - "checkpoint_frequency": args.checkpoint_frequency, - "resumed_from_checkpoint": str(latest_checkpoint) if latest_checkpoint is not None else None, - } - save_rl_status(status_path, status) - raise - except Exception as exc: - latest_checkpoint = find_latest_checkpoint(checkpoint_dir) - latest_timesteps = ( - completed_timesteps if latest_checkpoint is None else get_model_num_timesteps(latest_checkpoint) - ) - status["training"] = { - "status": "failed", - "started_at": status["training"]["started_at"], - "finished_at": now_utc_iso(), - "timesteps": args.timesteps, - "completed_timesteps": latest_timesteps, - "checkpoint_directory": str(checkpoint_dir), - "checkpoint_frequency": args.checkpoint_frequency, - "resumed_from_checkpoint": str(latest_checkpoint) if latest_checkpoint is not None else None, - "error": str(exc), - "traceback": traceback.format_exc(), - } - save_rl_status(status_path, status) - raise - else: - logger.info("Completed training for %s.", spec.label) - return copied_model_path - - -def run_evaluation_phase( - *, - spec: RLRunSpec, - device: Target, - run_dir: Path, - model_path: Path, - mode_name: str, - deterministic: bool, - args: argparse.Namespace, - logger: logging.Logger, -) -> None: - """Evaluate one trained RL model in one rollout mode.""" - status_path = run_dir / "status.json" - status = load_json(status_path, empty_rl_status(spec)) - result_path = run_dir / f"evaluation_{mode_name}.json" - - if not args.force and status["evaluations"][mode_name]["status"] == "completed" and result_path.is_file(): - logger.info("Skipping completed %s evaluation for %s.", mode_name, spec.label) - return - - logger.info("Evaluating %s with %s rollout.", spec.label, mode_name) - status["evaluations"][mode_name] = { - "status": "running", - "started_at": now_utc_iso(), - "seed": args.seed, - "max_steps": args.max_steps, - "deterministic": deterministic, - } - save_rl_status(status_path, status) - - try: - result = evaluate_trained_predictor( - model_path=model_path, - device=device, - figure_of_merit=FIGURE_OF_MERIT, - mdp=spec.mdp, - path_training_circuits=args.train_dir, - path_test_circuits=args.test_dir, - max_steps=args.max_steps, - deterministic=deterministic, - seed=args.seed, - ) - atomic_write_json( - result_path, - { - "completed_at": now_utc_iso(), - "device": spec.device_name, - "figure_of_merit": FIGURE_OF_MERIT, - "mdp": spec.mdp, - "evaluation_mode": mode_name, - "deterministic": deterministic, - "result": result, - }, - ) - status["evaluations"][mode_name] = { - "status": "completed", - "started_at": status["evaluations"][mode_name]["started_at"], - "finished_at": now_utc_iso(), - "seed": args.seed, - "max_steps": args.max_steps, - "deterministic": deterministic, - "result_path": str(result_path), - "summary": summarize_predictor_evaluation(result), - } - save_rl_status(status_path, status) - logger.info("Completed %s evaluation for %s.", mode_name, spec.label) - except KeyboardInterrupt: - status["evaluations"][mode_name] = { - "status": "interrupted", - "started_at": status["evaluations"][mode_name]["started_at"], - "finished_at": now_utc_iso(), - "seed": args.seed, - "max_steps": args.max_steps, - "deterministic": deterministic, - } - save_rl_status(status_path, status) - raise - except Exception as exc: - status["evaluations"][mode_name] = { - "status": "failed", - "started_at": status["evaluations"][mode_name]["started_at"], - "finished_at": now_utc_iso(), - "seed": args.seed, - "max_steps": args.max_steps, - "deterministic": deterministic, - "error": str(exc), - "traceback": traceback.format_exc(), - } - save_rl_status(status_path, status) - raise - - -def run_pipeline_phase( - *, - device_name: str, - pipeline_name: str, - output_dir: Path, - args: argparse.Namespace, - logger: logging.Logger, -) -> None: - """Evaluate one locked baseline pipeline for one device.""" - status_path = pipeline_directory(output_dir, device_name, pipeline_name) / "status.json" - result_path = pipeline_directory(output_dir, device_name, pipeline_name) / "result.json" - status = load_json(status_path, empty_pipeline_status(device_name, pipeline_name)) - - if not args.force and status["result"]["status"] == "completed" and result_path.is_file(): - logger.info("Skipping completed pipeline %s for %s.", pipeline_name, device_name) - return - - logger.info("Evaluating pipeline %s for %s.", pipeline_name, device_name) - status["result"] = {"status": "running", "started_at": now_utc_iso()} - save_pipeline_status(status_path, status) - - try: - results = run_selected_pipelines( - pipeline_names=[pipeline_name], - device_name=device_name, - figure_of_merit_name=FIGURE_OF_MERIT, - path_training_circuits=args.train_dir, - path_test_circuits=args.test_dir, - ) - result = get_single_pipeline_result(results, pipeline_name) - atomic_write_json( - result_path, - { - "completed_at": now_utc_iso(), - "device": device_name, - "figure_of_merit": FIGURE_OF_MERIT, - "pipeline": pipeline_name, - "result": result, - }, - ) - status["result"] = { - "status": "completed", - "started_at": status["result"]["started_at"], - "finished_at": now_utc_iso(), - "result_path": str(result_path), - "summary": summarize_pipeline_evaluation(result), - } - save_pipeline_status(status_path, status) - logger.info("Completed pipeline %s for %s.", pipeline_name, device_name) - except KeyboardInterrupt: - status["result"] = { - "status": "interrupted", - "started_at": status["result"]["started_at"], - "finished_at": now_utc_iso(), - } - save_pipeline_status(status_path, status) - raise - except Exception as exc: - status["result"] = { - "status": "failed", - "started_at": status["result"]["started_at"], - "finished_at": now_utc_iso(), - "error": str(exc), - "traceback": traceback.format_exc(), - } - save_pipeline_status(status_path, status) - raise - - -def write_manifest(output_dir: Path, args: argparse.Namespace) -> None: - """Write the static sweep configuration.""" - atomic_write_json( - output_dir / "manifest.json", - { - "created_at": now_utc_iso(), - "figure_of_merit": FIGURE_OF_MERIT, - "devices": args.devices, - "mdps": args.mdps, - "timesteps": args.timesteps, - "checkpoint_frequency": args.checkpoint_frequency, - "train_verbose": args.train_verbose, - "max_steps": args.max_steps, - "seed": args.seed, - "output_dir": str(args.output_dir), - "train_dir": args.train_dir, - "test_dir": args.test_dir, - "skip_pipelines": args.skip_pipelines, - "test_training": args.test_training, - "force": args.force, - }, - ) - - -def write_progress(output_dir: Path, devices: list[str], mdps: list[str], include_pipelines: bool) -> dict[str, Any]: - """Compute and persist a simple progress summary.""" - training_total = len(devices) * len(mdps) - evaluation_total = training_total * len(EVALUATION_MODES) - pipeline_total = len(devices) * len(DEFAULT_PIPELINES) if include_pipelines else 0 - - training_completed = 0 - training_failed = 0 - evaluation_completed = 0 - evaluation_failed = 0 - pipeline_completed = 0 - pipeline_failed = 0 - - for device_name in devices: - for mdp in mdps: - status = load_json( - rl_run_directory(output_dir, RLRunSpec(device_name=device_name, mdp=mdp)) / "status.json", - empty_rl_status(RLRunSpec(device_name=device_name, mdp=mdp)), - ) - if status["training"]["status"] == "completed": - training_completed += 1 - elif status["training"]["status"] == "failed": - training_failed += 1 - - for mode_name, _ in EVALUATION_MODES: - phase_status = status["evaluations"][mode_name]["status"] - if phase_status == "completed": - evaluation_completed += 1 - elif phase_status == "failed": - evaluation_failed += 1 - - if include_pipelines: - for pipeline_name in DEFAULT_PIPELINES: - status = load_json( - pipeline_directory(output_dir, device_name, pipeline_name) / "status.json", - empty_pipeline_status(device_name, pipeline_name), - ) - phase_status = status["result"]["status"] - if phase_status == "completed": - pipeline_completed += 1 - elif phase_status == "failed": - pipeline_failed += 1 - - progress = { - "updated_at": now_utc_iso(), - "figure_of_merit": FIGURE_OF_MERIT, - "training": { - "completed": training_completed, - "failed": training_failed, - "total": training_total, - }, - "evaluations": { - "completed": evaluation_completed, - "failed": evaluation_failed, - "total": evaluation_total, - }, - "pipelines": { - "completed": pipeline_completed, - "failed": pipeline_failed, - "total": pipeline_total, - }, - } - atomic_write_json(output_dir / "progress.json", progress) - return progress - - -def log_progress(logger: logging.Logger, progress: dict[str, Any]) -> None: - """Log a compact progress summary.""" - logger.info( - "Progress: training %d/%d, evaluations %d/%d, pipelines %d/%d.", - progress["training"]["completed"], - progress["training"]["total"], - progress["evaluations"]["completed"], - progress["evaluations"]["total"], - progress["pipelines"]["completed"], - progress["pipelines"]["total"], - ) - - -def print_plan(args: argparse.Namespace) -> None: - """Print the planned sweep and exit.""" - specs = [RLRunSpec(device_name=device_name, mdp=mdp) for device_name in args.devices for mdp in args.mdps] - print(f"Figure of merit: {FIGURE_OF_MERIT}") - print(f"Devices: {', '.join(args.devices)}") - print(f"MDPs: {', '.join(args.mdps)}") - print(f"Timesteps: {args.timesteps}") - print(f"Checkpoint frequency: {args.checkpoint_frequency}") - print(f"Output directory: {args.output_dir}") - print(f"Pipelines: {'disabled' if args.skip_pipelines else ', '.join(DEFAULT_PIPELINES)}") - print(f"Planned RL runs: {len(specs)}") - for index, spec in enumerate(specs, start=1): - print(f" [{index:02d}] {spec.label}") - - -def main() -> None: - """Run the full ESP sweep.""" - args = parse_args() - args.output_dir = args.output_dir.resolve() - - if args.dry_run: - print_plan(args) - return - - install_signal_handlers() - args.output_dir.mkdir(parents=True, exist_ok=True) - logger = configure_logging(args.output_dir) - write_manifest(args.output_dir, args) - - logger.info("Starting ESP RL sweep.") - logger.info("Devices: %s", ", ".join(args.devices)) - logger.info("MDPs: %s", ", ".join(args.mdps)) - logger.info("Output directory: %s", args.output_dir) - - include_pipelines = not args.skip_pipelines - - try: - for device_name in args.devices: - device = resolve_device(device_name) - logger.info("Processing device %s.", device_name) - - for mdp in args.mdps: - spec = RLRunSpec(device_name=device_name, mdp=mdp) - run_dir = rl_run_directory(args.output_dir, spec) - run_dir.mkdir(parents=True, exist_ok=True) - - try: - model_path = run_training_phase( - spec=spec, - device=device, - run_dir=run_dir, - args=args, - logger=logger, - ) - for mode_name, deterministic in EVALUATION_MODES: - run_evaluation_phase( - spec=spec, - device=device, - run_dir=run_dir, - model_path=model_path, - mode_name=mode_name, - deterministic=deterministic, - args=args, - logger=logger, - ) - except Exception: - logger.exception("RL run failed for %s.", spec.label) - if args.fail_fast: - raise - finally: - progress = write_progress(args.output_dir, args.devices, args.mdps, include_pipelines) - log_progress(logger, progress) - - if include_pipelines: - for pipeline_name in DEFAULT_PIPELINES: - pipeline_run_dir = pipeline_directory(args.output_dir, device_name, pipeline_name) - pipeline_run_dir.mkdir(parents=True, exist_ok=True) - try: - run_pipeline_phase( - device_name=device_name, - pipeline_name=pipeline_name, - output_dir=args.output_dir, - args=args, - logger=logger, - ) - except Exception: - logger.exception("Pipeline %s failed for %s.", pipeline_name, device_name) - if args.fail_fast: - raise - finally: - progress = write_progress(args.output_dir, args.devices, args.mdps, include_pipelines) - log_progress(logger, progress) - except KeyboardInterrupt: - progress = write_progress(args.output_dir, args.devices, args.mdps, include_pipelines) - log_progress(logger, progress) - logger.exception("Sweep interrupted. Rerun the same command to continue from saved checkpoints and results.") - raise - - progress = write_progress(args.output_dir, args.devices, args.mdps, include_pipelines) - log_progress(logger, progress) - logger.info("Sweep finished. Results are in %s", args.output_dir) - - -if __name__ == "__main__": - main() diff --git a/src/mqt/predictor/rl/experiments/training.py b/src/mqt/predictor/rl/experiments/training.py index 29e5eac97..702cfcef5 100644 --- a/src/mqt/predictor/rl/experiments/training.py +++ b/src/mqt/predictor/rl/experiments/training.py @@ -39,6 +39,64 @@ class RLTrainingResult: resumed_from_checkpoint: Path | None = None +def _gnn_training_kwargs( + *, + iterations: int | None, + steps: int | None, + num_epochs: int | None, + minibatch_size: int | None, + hidden_dim: int | None, + num_conv_wo_resnet: int | None, + num_resnet_layers: int | None, + dropout_p: float | None, + bidirectional: bool | None, + lr: float | None, + gnn_lr: float | None, +) -> dict[str, object]: + """Build the optional GNN training kwargs expected by ``Predictor.train_model``.""" + train_kwargs: dict[str, object] = {} + if iterations is not None: + train_kwargs["iterations"] = iterations + if steps is not None: + train_kwargs["steps"] = steps + if num_epochs is not None: + train_kwargs["num_epochs"] = num_epochs + if minibatch_size is not None: + train_kwargs["minibatch_size"] = minibatch_size + if hidden_dim is not None: + train_kwargs["hidden_dim"] = hidden_dim + if num_conv_wo_resnet is not None: + train_kwargs["num_conv_wo_resnet"] = num_conv_wo_resnet + if num_resnet_layers is not None: + train_kwargs["num_resnet_layers"] = num_resnet_layers + if dropout_p is not None: + train_kwargs["dropout_p"] = dropout_p + if bidirectional is not None: + train_kwargs["bidirectional"] = bidirectional + if lr is not None: + train_kwargs["lr"] = lr + if gnn_lr is not None: + train_kwargs["gnn_lr"] = gnn_lr + return train_kwargs + + +def _resolve_total_timesteps_for_result( + *, + graph: bool, + test: bool, + timesteps: int, + iterations: int | None, + steps: int | None, +) -> int: + """Return the total number of environment steps represented by the training run.""" + if not graph: + return timesteps + + resolved_iterations = iterations if iterations is not None else (10 if test else 1000) + resolved_steps = steps if steps is not None else (20 if test else 2048) + return resolved_iterations * resolved_steps + + def run_rl_training( device: Target, figure_of_merit: figure_of_merit = "expected_fidelity", @@ -50,6 +108,21 @@ def run_rl_training( checkpoint_directory: str | Path | None = None, checkpoint_frequency: int | None = None, resume_from_checkpoint: str | Path | None = None, + reward_scale: float = 1.0, + no_effect_penalty: float = -0.001, + max_episode_steps: int | None = None, + graph: bool = False, + iterations: int | None = None, + steps: int | None = None, + num_epochs: int | None = None, + minibatch_size: int | None = None, + hidden_dim: int | None = None, + num_conv_wo_resnet: int | None = None, + num_resnet_layers: int | None = None, + dropout_p: float | None = None, + bidirectional: bool | None = None, + lr: float | None = None, + gnn_lr: float | None = None, callback: BaseCallback | None = None, ) -> RLTrainingResult: """Train an RL predictor on an existing training split.""" @@ -58,15 +131,37 @@ def run_rl_training( ) resolved_checkpoint_directory = Path(checkpoint_directory) if checkpoint_directory is not None else None resolved_resume_from_checkpoint = Path(resume_from_checkpoint) if resume_from_checkpoint is not None else None + gnn_train_kwargs = _gnn_training_kwargs( + iterations=iterations, + steps=steps, + num_epochs=num_epochs, + minibatch_size=minibatch_size, + hidden_dim=hidden_dim, + num_conv_wo_resnet=num_conv_wo_resnet, + num_resnet_layers=num_resnet_layers, + dropout_p=dropout_p, + bidirectional=bidirectional, + lr=lr, + gnn_lr=gnn_lr, + ) predictor = Predictor( figure_of_merit=figure_of_merit, device=device, mdp=mdp, path_training_circuits=training_directory, + reward_scale=reward_scale, + no_effect_penalty=no_effect_penalty, + max_episode_steps=max_episode_steps, + graph=graph, ) callbacks: list[BaseCallback] = [] - if resolved_checkpoint_directory is not None and checkpoint_frequency is not None and checkpoint_frequency > 0: + if ( + not graph + and resolved_checkpoint_directory is not None + and checkpoint_frequency is not None + and checkpoint_frequency > 0 + ): resolved_checkpoint_directory.mkdir(parents=True, exist_ok=True) callbacks.append( CheckpointCallback( @@ -82,17 +177,34 @@ def run_rl_training( timesteps=timesteps, verbose=verbose, test=test, - callback=CallbackList(callbacks) if callbacks else None, - resume_from=resolved_resume_from_checkpoint, + callback=CallbackList(callbacks) if callbacks and not graph else None, + resume_from=resolved_resume_from_checkpoint if not graph else None, + **gnn_train_kwargs, ) - model_path = get_path_trained_model() / f"model_{figure_of_merit}_{device.description}.zip" + model_name = ( + f"gnn_{figure_of_merit}_{device.description}.pt" + if graph + else f"model_{figure_of_merit}_{device.description}.zip" + ) + model_path = get_path_trained_model() / model_name + if graph: + total_timesteps = _resolve_total_timesteps_for_result( + graph=graph, + test=test, + timesteps=timesteps, + iterations=iterations, + steps=steps, + ) + else: + assert trained_model is not None + total_timesteps = int(trained_model.num_timesteps) return RLTrainingResult( model_path=model_path, training_directory=training_directory, - total_timesteps=int(trained_model.num_timesteps), - checkpoint_directory=resolved_checkpoint_directory, - resumed_from_checkpoint=resolved_resume_from_checkpoint, + total_timesteps=total_timesteps, + checkpoint_directory=resolved_checkpoint_directory if not graph else None, + resumed_from_checkpoint=resolved_resume_from_checkpoint if not graph else None, ) @@ -117,6 +229,11 @@ def main() -> None: choices=["paper", "flexible", "thesis", "hybrid"], help="MDP transition policy used by the RL environment.", ) + parser.add_argument( + "--graph", + action="store_true", + help="Train the graph-based GNN policy instead of MaskablePPO.", + ) parser.add_argument("--timesteps", type=int, default=10000, help="Training timesteps.") parser.add_argument("--verbose", type=int, default=1, help="Training verbosity passed to PPO.") parser.add_argument( @@ -130,6 +247,18 @@ def main() -> None: default=None, help="Optional path to the existing training circuits.", ) + parser.add_argument( + "--reward-scale", + type=float, + default=1.0, + help="Scaling factor for reward deltas inside the predictor environment.", + ) + parser.add_argument( + "--no-effect-penalty", + type=float, + default=-0.001, + help="Penalty applied when an action does not change the circuit.", + ) parser.add_argument( "--checkpoint-dir", type=Path, @@ -148,6 +277,38 @@ def main() -> None: default=None, help="Optional PPO checkpoint to resume training from.", ) + parser.add_argument( + "--max-episode-steps", + type=int, + default=None, + help="Optional hard cap on environment steps per training episode.", + ) + parser.add_argument("--iterations", type=int, default=None, help="GNN PPO iterations.") + parser.add_argument("--steps", type=int, default=None, help="GNN PPO steps per iteration.") + parser.add_argument("--num-epochs", type=int, default=None, help="GNN PPO update epochs.") + parser.add_argument("--minibatch-size", type=int, default=None, help="GNN PPO minibatch size.") + parser.add_argument("--hidden-dim", type=int, default=None, help="GNN hidden dimension.") + parser.add_argument( + "--num-conv-wo-resnet", + type=int, + default=None, + help="Number of non-residual graph convolution layers in the GNN encoder.", + ) + parser.add_argument( + "--num-resnet-layers", + type=int, + default=None, + help="Number of residual graph convolution layers in the GNN encoder.", + ) + parser.add_argument("--dropout-p", type=float, default=None, help="GNN dropout probability.") + parser.add_argument( + "--bidirectional", + action=argparse.BooleanOptionalAction, + default=None, + help="Enable or disable bidirectional message passing in the GNN encoder.", + ) + parser.add_argument("--lr", type=float, default=None, help="Learning rate for PPO actor/critic heads.") + parser.add_argument("--gnn-lr", type=float, default=None, help="Learning rate for the GNN encoder.") args = parser.parse_args() result = run_rl_training( @@ -158,9 +319,24 @@ def main() -> None: verbose=args.verbose, test=args.test, path_training_circuits=args.train_dir, + reward_scale=args.reward_scale, + no_effect_penalty=args.no_effect_penalty, checkpoint_directory=args.checkpoint_dir, checkpoint_frequency=args.checkpoint_frequency, resume_from_checkpoint=args.resume_from_checkpoint, + max_episode_steps=args.max_episode_steps, + graph=args.graph, + iterations=args.iterations, + steps=args.steps, + num_epochs=args.num_epochs, + minibatch_size=args.minibatch_size, + hidden_dim=args.hidden_dim, + num_conv_wo_resnet=args.num_conv_wo_resnet, + num_resnet_layers=args.num_resnet_layers, + dropout_p=args.dropout_p, + bidirectional=args.bidirectional, + lr=args.lr, + gnn_lr=args.gnn_lr, ) print(f"Training directory: {result.training_directory}") diff --git a/src/mqt/predictor/rl/gnn.py b/src/mqt/predictor/rl/gnn.py new file mode 100644 index 000000000..9c8b2a03b --- /dev/null +++ b/src/mqt/predictor/rl/gnn.py @@ -0,0 +1,229 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""GNN actor-critic architecture for the RL compilation predictor.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +import torch +import torch.nn as nn +import torch.nn.functional as f +from torch_geometric.nn import GraphNorm, SAGEConv, global_mean_pool + +if TYPE_CHECKING: + from collections.abc import Callable + + from torch_geometric.data import Data + + +class GraphConvolutionSageEncoder(nn.Module): + """SAGEConv + GraphNorm encoder producing a graph embedding via global pooling.""" + + def __init__( + self, + in_feats: int, + hidden_dim: int, + num_conv_wo_resnet: int, + num_resnet_layers: int, + *, + conv_activation: Callable[..., torch.Tensor] = f.leaky_relu, + conv_act_kwargs: dict[str, Any] | None = None, + dropout_p: float = 0.2, + bidirectional: bool = True, + ) -> None: + """Initialize the GraphConvolutionSageEncoder. + + Args: + in_feats: Dimension of input node features. + hidden_dim: Dimension of hidden layers and output graph embedding. + num_conv_wo_resnet: Number of initial convolutional layers without residual connections. + num_resnet_layers: Number of subsequent convolutional layers with residual connections. + conv_activation: Activation function to apply after each convolutional layer. + conv_act_kwargs: Optional keyword arguments for the activation function. + dropout_p: Dropout probability to apply after each convolutional layer. + bidirectional: If True, apply each SAGEConv in both directions and average the results. + """ + super().__init__() + + if num_conv_wo_resnet < 1: + msg = "num_conv_wo_resnet must be at least 1" + raise ValueError(msg) + + self.conv_activation = conv_activation + self.conv_act_kwargs = conv_act_kwargs or {} + self.bidirectional = bidirectional + + self.convs: nn.ModuleList = nn.ModuleList() + self.norms: nn.ModuleList = nn.ModuleList() + + # first layer + self.convs.append(SAGEConv(in_feats, hidden_dim)) + self.norms.append(GraphNorm(hidden_dim)) + + # remaining non-residual layers + for _ in range(num_conv_wo_resnet - 1): + self.convs.append(SAGEConv(hidden_dim, hidden_dim)) + self.norms.append(GraphNorm(hidden_dim)) + + # residual layers (same width) + for _ in range(num_resnet_layers): + self.convs.append(SAGEConv(hidden_dim, hidden_dim)) + self.norms.append(GraphNorm(hidden_dim)) + + self.drop = nn.Dropout(dropout_p) + self._residual_start = num_conv_wo_resnet + self.out_dim = hidden_dim + self.graph_emb_dim = hidden_dim + + def _apply_conv_bidir( + self, + conv: SAGEConv, + x: torch.Tensor, + edge_index: torch.Tensor, + ) -> torch.Tensor: + """Apply the convolution in both directions if bidirectional is True, otherwise apply it once. + + Args: + conv: The SAGEConv layer to apply. + x: Node feature tensor of shape [num_nodes, in_feats]. + edge_index: Edge index tensor of shape [2, num_edges]. + + Returns: + The output node features after applying the convolution (and averaging if bidirectional). + """ + x_f = conv(x, edge_index) + if not self.bidirectional: + return x_f + x_b = conv(x, edge_index.flip(0)) + return 0.5 * (x_f + x_b) + + def forward(self, data: Data) -> torch.Tensor: + """Encode the input graph data into a graph embedding. + + Args: + data: A PyG Data object containing at least 'x' (node features) and 'edge_index' (graph connectivity). + + Returns: + A tensor of shape [num_graphs, graph_emb_dim] representing the encoded graph embeddings. + """ + x, edge_index, batch = data.x, data.edge_index, data.batch + assert x is not None + assert edge_index is not None + + for i, conv in enumerate(self.convs): + x_new = self._apply_conv_bidir(conv, x, edge_index) # ty: ignore[invalid-argument-type] + x_new = self.norms[i](x_new, batch=batch) + x_new = self.conv_activation(x_new, **self.conv_act_kwargs) + x_new = self.drop(x_new) + + # residual only after the initial stack + x = x_new if i < self._residual_start else (x + x_new) + + # graph readout + return global_mean_pool(x, batch) # [num_graphs, hidden_dim] + + +class SAGEActorCritic(nn.Module): + """Actor-Critic using the SAGE encoder. + + Model for RL predictor, composed of a SAGEConv encoder followed by separate actor and critic MLP heads. + """ + + def __init__( + self, + in_feats: int, + hidden_dim: int, + num_conv_wo_resnet: int, + num_resnet_layers: int, + num_actions: int, + *, + dropout_p: float = 0.2, + bidirectional: bool = True, + global_feature_dim: int = 0, + ) -> None: + """Initialize the SAGEActorCritic model. + + Args: + in_feats: Dimension of input node features. + hidden_dim: Dimension of hidden layers and graph embedding. + num_conv_wo_resnet: Number of initial convolutional layers without residual connections in the encoder. + num_resnet_layers: Number of subsequent convolutional layers with residual connections in the encoder. + num_actions: Number of discrete actions for the actor head output. + dropout_p: Dropout probability to apply in the encoder and trunk. + bidirectional: If True, apply each SAGEConv in both directions and average the results. + global_feature_dim: If > 0, the dimension of the flat RL observation + vector concatenated to the graph embedding before the actor and + critic heads. + """ + super().__init__() + + self.global_feature_dim = global_feature_dim + # Same encoder for actor and critic + self.encoder = GraphConvolutionSageEncoder( + in_feats=in_feats, + hidden_dim=hidden_dim, + num_conv_wo_resnet=num_conv_wo_resnet, + num_resnet_layers=num_resnet_layers, + dropout_p=dropout_p, + bidirectional=bidirectional, + ) + + emb_dim = self.encoder.graph_emb_dim + # Input to trunk combines graph embedding with optional global features. + trunk_in_dim = emb_dim + global_feature_dim + # 1 Layer of FFNN after the encoder before actor and critic heads, to allow them to diverge more. + self.trunk = nn.Sequential( + nn.Linear(trunk_in_dim, emb_dim), + nn.LeakyReLU(), + nn.Dropout(dropout_p), + ) + # 2-layer MLP actor and critic heads with shared embedding dimension, no activation on output layer. + self.actor = nn.Sequential( + nn.Linear(emb_dim, emb_dim), + nn.LeakyReLU(), + nn.Linear(emb_dim, num_actions), + ) + + self.critic = nn.Sequential( + nn.Linear(emb_dim, emb_dim), + nn.LeakyReLU(), + nn.Linear(emb_dim, 1), + ) + + def forward(self, data: Data) -> tuple[torch.Tensor, torch.Tensor]: + """Returns action logits and values. + + Args: + data: A PyG Data or Batch object. When global_feature_dim > 0 + the object must contain a global_features attribute of shape + [B, global_feature_dim] holding the flat RL observation + features (one row per graph in the batch). + """ + output_encoder = self.encoder(data) # [B, emb_dim] + + if self.global_feature_dim > 0: + global_features = getattr(data, "global_features", None) + if global_features is not None: + # Reshape to [B, global_feature_dim] in case PyG batched it differently. + global_features = global_features.view(output_encoder.shape[0], self.global_feature_dim).to( + output_encoder.device + ) + output_encoder = torch.cat( + [output_encoder, global_features], dim=-1 + ) # [B, emb_dim + global_feature_dim] + else: + # No global features available — pad with zeros so the model still runs. + pad = torch.zeros(output_encoder.shape[0], self.global_feature_dim, device=output_encoder.device) + output_encoder = torch.cat([output_encoder, pad], dim=-1) + + output_common_model = self.trunk(output_encoder) # [B, emb_dim] + logits = self.actor(output_common_model) # Dimension batch, num_actions + value = self.critic(output_common_model) # Dimension batch, 1 + return logits, value diff --git a/src/mqt/predictor/rl/gnn_ppo.py b/src/mqt/predictor/rl/gnn_ppo.py new file mode 100644 index 000000000..dbe7ed3ee --- /dev/null +++ b/src/mqt/predictor/rl/gnn_ppo.py @@ -0,0 +1,485 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""GNN-based PPO implementation for training a RL compilation predictor on variable-size circuit graphs.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import TYPE_CHECKING + +import numpy as np +import torch +import torch.nn as nn +from torch.distributions import Categorical +from torch_geometric.data import Batch + +from mqt.predictor.rl.gnn import SAGEActorCritic + +if TYPE_CHECKING: + from torch_geometric.data import Data + + from mqt.predictor.rl.predictorenv import PredictorEnv + + +@dataclass +class RolloutBuffer: + """Collect data during rollout and convert to tensors for PPO update. Data collected in lists during rollout, then converted to tensors in finalize() for efficient batch processing during PPO updates. + + Object saved: + - graphs: list of PyG Data objects (the circuit graph at each step) + - actions_list: list of ints (action indices taken) + - log_probs_list: list of floats (log probabilities of taken actions) + - values_list: list of floats (critic value estimates at each step) + - rewards_list: list of floats (rewards received at each step) + - dones_list: list of bools (whether episode ended at each step) + - masks_list: list of np.ndarray of bools (action masks at each step). + """ + + graphs: list[Data] = field(default_factory=list) + actions_list: list[int] = field(default_factory=list) + log_probs_list: list[float] = field(default_factory=list) + values_list: list[float] = field(default_factory=list) + rewards_list: list[float] = field(default_factory=list) + dones_list: list[bool] = field(default_factory=list) + masks_list: list[np.ndarray] = field(default_factory=list) + + # Finalized tensors for PPO update (initialized in finalize()) + actions: torch.Tensor = field(init=False) + log_probs: torch.Tensor = field(init=False) + values: torch.Tensor = field(init=False) + rewards: torch.Tensor = field(init=False) + dones: torch.Tensor = field(init=False) + masks: torch.Tensor = field(init=False) + + def clear(self) -> None: + """Clear all collected data to start a new rollout.""" + self.graphs = [] + self.actions_list = [] + self.log_probs_list = [] + self.values_list = [] + self.rewards_list = [] + self.dones_list = [] + self.masks_list = [] + + def add( + self, + graph: Data, + action: int, + log_prob: float, + value: float, + reward: float, + done: bool, + mask: np.ndarray | list[bool], + ) -> None: + """Add a step to the rollout buffer. + + Args: + graph: PyG Data object representing the circuit graph at this step + action: int, index of the action taken + log_prob: float, log probability of the taken action under the current policy + value: float, critic value estimate for the current state + reward: float, reward received after taking the action + done: bool, whether the episode ended after this step + mask: np.ndarray or list of bools, action mask for this step (True for valid actions, False for invalid). + """ + self.graphs.append(graph) + self.actions_list.append(action) + self.log_probs_list.append(log_prob) + self.values_list.append(value) + self.rewards_list.append(reward) + self.dones_list.append(done) + self.masks_list.append(np.asarray(mask, dtype=np.bool_)) + + def finalize(self, device: str) -> None: + """Convert collected lists to tensors for PPO update. + + Args: + device: 'cuda' or 'cpu' where the tensors should be located. + """ + self.actions = torch.tensor(self.actions_list, dtype=torch.long, device=device) + self.log_probs = torch.tensor(self.log_probs_list, dtype=torch.float32, device=device) + self.values = torch.tensor(self.values_list, dtype=torch.float32, device=device) + self.rewards = torch.tensor(self.rewards_list, dtype=torch.float32, device=device) + self.dones = torch.tensor(self.dones_list, dtype=torch.float32, device=device) + self.masks = torch.tensor(np.stack(self.masks_list), device=device, dtype=torch.bool) + + +def compute_gae( + rewards: torch.Tensor, + dones: torch.Tensor, + values: torch.Tensor, + last_value: torch.Tensor, + gamma: float = 0.98, + lam: float = 0.95, +) -> tuple[torch.Tensor, torch.Tensor]: + """Compute Generalized Advantage Estimation (GAE) for the PPO update. + + Args: + rewards: shape (T,) + dones: shape (T,) + values: shape (T,) + last_value: scalar tensor — V(s_T) for bootstrapping + gamma: discount factor + lam: GAE lambda. + + Returns: + returns: shape (T,) + advantages: shape (T,) + """ + # Takes the number of timesteps + timesteps = rewards.size(0) + # Initialize tensors for advantages and returns + advantages = torch.zeros(timesteps, device=rewards.device, dtype=torch.float32) + + gae = 0.0 + # for each timestep t, starting from the end of the trajectory and moving backwards + for t in reversed(range(timesteps)): + next_value = last_value if t == timesteps - 1 else values[t + 1] + not_done = 1.0 - dones[t] + delta = rewards[t] + gamma * next_value * not_done - values[t] + gae = delta + gamma * lam * not_done * gae + advantages[t] = gae + + returns = advantages + values + return returns, advantages + + +def collect_rollout( + env: PredictorEnv, + policy: SAGEActorCritic, + steps: int, + device: str, +) -> tuple[RolloutBuffer, torch.Tensor, list[float]]: + """Collect a rollout trajectory of given number of steps using the current policy. + + Args: + env: Environment returning PyG Data from reset()/step() + policy: GNN actor-critic + steps: Number of steps to collect + device: 'cuda' or 'cpu' + + Returns: + buffer: populated RolloutBuffer + last_value: bootstrap value for GAE + episode_rewards: total reward accumulated per completed episode + """ + buffer = RolloutBuffer() + episode_rewards: list[float] = [] + episode_return: float = 0.0 + + obs, _ = env.reset() + + policy.eval() + with torch.no_grad(): + for _ in range(steps): + # Convert the current observation (PyG Data) to a batch of size 1 and move to device + batch = Batch.from_data_list([obs]).to(device) # ty: ignore[invalid-argument-type,unresolved-attribute] + # Get action logits and value estimate from the policy + logits, value = policy(batch) + # get the mask of valid action from the environment + mask_cpu = env.action_masks() + if not any(mask_cpu): + # If no valid actions, we can't do anything; treat as episode end and reset. + obs, _ = env.reset() + episode_return = 0.0 + continue + + mask = torch.as_tensor(mask_cpu, device=device, dtype=torch.bool) + # Mask invalid actions by setting their logits to -inf, so they have zero probability after softmax. + # Invert mask because True means valid, but we want to set invalid (False) logits to -inf. + logits_masked = logits.masked_fill(~mask.unsqueeze(0), float("-inf")) + # Create a categorical distribution over the masked logits. + dist = Categorical(logits=logits_masked) + # Sample an action from the masked distribution and get its log probability. + action = dist.sample() + log_prob = dist.log_prob(action) + + try: + next_obs, reward, terminated, truncated, _info = env.step(int(action.item())) + except (RuntimeError, ValueError, TypeError, AssertionError) as e: + print(f"Error occurred while stepping in environment: {e}") + obs, _ = env.reset() + episode_return = 0.0 + continue + # Consider episode done if either terminated or truncated is True. + done = terminated or truncated + # Update episode return and add transition to buffer. + episode_return += float(reward) + + buffer.add( + graph=obs, # ty: ignore[invalid-argument-type] + action=int(action.item()), + log_prob=float(log_prob.item()), + value=float(value.squeeze(-1).item()), + reward=float(reward), + done=bool(done), + mask=mask_cpu, + ) + + if done: + episode_rewards.append(episode_return) + episode_return = 0.0 + obs, _ = env.reset() + else: + obs = next_obs + + # Bootstrap value for the current (possibly mid-episode) obs. + last_batch = Batch.from_data_list([obs]).to(device) # ty: ignore[invalid-argument-type,unresolved-attribute] + _, last_value = policy(last_batch) + last_value = last_value.squeeze(-1).squeeze(0) + + return buffer, last_value, episode_rewards + + +def ppo_update( + policy: SAGEActorCritic, + optimizer: torch.optim.Optimizer, + buffer: RolloutBuffer, + last_value: torch.Tensor, + device: str, + gamma: float = 0.98, + lam: float = 0.95, + clip_range: float = 0.2, + value_coef: float = 0.5, + entropy_coef: float = 0.01, + value_clip_range: float = 0.2, + max_grad_norm: float = 0.5, + epochs: int = 10, + minibatch_size: int = 64, + target_kl: float | None = 0.01, +) -> dict[str, float]: + """PPO update with variable-size graph minibatching. + + Uses value clipping, KL early stopping, and clipped surrogate objective. + + Returns: + metrics: dict with keys 'mean_kl', 'policy_loss', 'value_loss', 'entropy' + """ + # If no data was collected, return zero metrics to avoid errors in PPO update. + if len(buffer.graphs) == 0: + return {"mean_kl": 0.0, "policy_loss": 0.0, "value_loss": 0.0, "entropy": 0.0} + # Convert collected data to tensors for PPO update. + buffer.finalize(device=device) + # Compute GAE advantages and returns. + returns, advantages = compute_gae( + rewards=buffer.rewards, + dones=buffer.dones, + values=buffer.values, + last_value=last_value, + gamma=gamma, + lam=lam, + ) + # Calculate the standard deviation of advantages for normalization, and handle the case where it's zero or non-finite to avoid division errors. + adv_std = advantages.std(unbiased=False) + if not torch.isfinite(adv_std) or adv_std < 1e-8: + adv_std = torch.tensor(1.0, device=advantages.device) + # Normalize advantages to have mean 0 and std 1 for more stable PPO updates. + advantages = (advantages - advantages.mean()) / adv_std + + graphs = buffer.graphs + actions = buffer.actions + old_log_probs = buffer.log_probs + old_values = buffer.values + + num_circuits = len(graphs) + # Clamp minibatch size to the number of samples so we don't get empty batches. + effective_mb = min(minibatch_size, num_circuits) + indices = np.arange(num_circuits) + rng = np.random.default_rng() + + all_kl: list[float] = [] + all_policy_loss: list[float] = [] + all_value_loss: list[float] = [] + all_entropy: list[float] = [] + # train policy for the specified number of epochs, shuffling and creating minibatches each epoch + policy.train() + for _epoch in range(epochs): + rng.shuffle(indices) + + for start in range(0, num_circuits, effective_mb): + mb_idx = indices[start : start + effective_mb] + + mb_graphs = [graphs[i] for i in mb_idx] + mb_batch = Batch.from_data_list(mb_graphs).to(device) # ty: ignore[unresolved-attribute] + + mb_actions = actions[mb_idx] + mb_old_logp = old_log_probs[mb_idx] + mb_old_values = old_values[mb_idx] + mb_returns = returns[mb_idx] + mb_adv = advantages[mb_idx] + + logits, new_values = policy(mb_batch) + mb_masks = buffer.masks[mb_idx] + logits = logits.masked_fill(~mb_masks, float("-inf")) + dist = Categorical(logits=logits) + new_logp = dist.log_prob(mb_actions) + entropy = dist.entropy().mean() + new_values = new_values.squeeze(-1) + # Compute the PPO clipped surrogate objective for the policy loss. + logp_diff = torch.clamp(new_logp - mb_old_logp, -20.0, 20.0) + ratio = torch.exp(logp_diff) + surr1 = ratio * mb_adv + surr2 = torch.clamp(ratio, 1 - clip_range, 1 + clip_range) * mb_adv + policy_loss = -torch.min(surr1, surr2).mean() + + value_pred_clipped = mb_old_values + torch.clamp( + new_values - mb_old_values, -value_clip_range, value_clip_range + ) + vloss1 = (new_values - mb_returns).pow(2) + vloss2 = (value_pred_clipped - mb_returns).pow(2) + value_loss = torch.max(vloss1, vloss2).mean() + + loss = policy_loss + value_coef * value_loss - entropy_coef * entropy + + optimizer.zero_grad() + loss.backward() + nn.utils.clip_grad_norm_(policy.parameters(), max_grad_norm) + optimizer.step() + + with torch.no_grad(): + kl = (mb_old_logp - new_logp).mean().item() + all_kl.append(kl) + all_policy_loss.append(policy_loss.item()) + all_value_loss.append(value_loss.item()) + all_entropy.append(entropy.item()) + + mean_kl = float(np.mean(all_kl)) if all_kl else 0.0 + if target_kl is not None and mean_kl > target_kl: + break + + return { + "mean_kl": float(np.mean(all_kl)) if all_kl else 0.0, + "policy_loss": float(np.mean(all_policy_loss)) if all_policy_loss else 0.0, + "value_loss": float(np.mean(all_value_loss)) if all_value_loss else 0.0, + "entropy": float(np.mean(all_entropy)) if all_entropy else 0.0, + } + + +def train_ppo_with_gnn( + env: PredictorEnv, + policy: SAGEActorCritic, + num_iterations: int = 1000, + steps_per_iteration: int = 2048, + num_epochs: int = 10, + minibatch_size: int = 64, + lr: float = 3e-4, + gnn_lr: float = 1e-4, + gamma: float = 0.98, + gae_lambda: float = 0.95, + clip_range: float = 0.2, + value_clip_range: float = 0.2, + value_coef: float = 0.5, + entropy_coef: float = 0.01, + max_grad_norm: float = 0.5, + target_kl: float | None = 0.01, + device: str = "cuda" if torch.cuda.is_available() else "cpu", +) -> SAGEActorCritic: + """Train a GNN-PPO agent on variable-size circuit graphs. + + Args: + env: Environment with graph observations (graph=True). + policy: SAGEActorCritic model. + num_iterations: Number of PPO iterations. + steps_per_iteration: Environment steps per rollout. + num_epochs: PPO update epochs per rollout. + minibatch_size: Minibatch size for PPO updates. + lr: Learning rate for actor/critic heads and trunk. + gnn_lr: Learning rate for GNN encoder layers. + gamma: Discount factor (default 0.98, matching non-GNN MaskablePPO). + gae_lambda: GAE lambda. + clip_range: PPO clip range. + value_clip_range: Value function clip range. + value_coef: Value loss coefficient. + entropy_coef: Entropy bonus coefficient. + max_grad_norm: Gradient clipping norm. + target_kl: KL early stopping threshold (None to disable). + device: Torch device. + verbose: Print progress every 10 iterations. + log_file: Optional path to a CSV file where per-iteration training metrics + are appended. Each row contains: iteration, mean_ep_reward, + std_ep_reward, num_episodes, policy_loss, value_loss, entropy, mean_kl. + + Returns: + Trained policy. + """ + policy = policy.to(device) + + optimizer = torch.optim.Adam([ + {"params": policy.encoder.convs.parameters(), "lr": gnn_lr}, + {"params": policy.encoder.norms.parameters(), "lr": gnn_lr}, + {"params": policy.trunk.parameters(), "lr": lr}, + {"params": policy.actor.parameters(), "lr": lr}, + {"params": policy.critic.parameters(), "lr": lr}, + ]) + + for _iteration in range(num_iterations): + buffer, last_value, _ep_rewards = collect_rollout( + env=env, + policy=policy, + steps=steps_per_iteration, + device=device, + ) + # Perform PPO update with the collected rollout data and get training metrics. + ppo_update( + policy=policy, + optimizer=optimizer, + buffer=buffer, + last_value=last_value, + device=device, + gamma=gamma, + lam=gae_lambda, + clip_range=clip_range, + value_clip_range=value_clip_range, + value_coef=value_coef, + entropy_coef=entropy_coef, + max_grad_norm=max_grad_norm, + epochs=num_epochs, + minibatch_size=minibatch_size, + target_kl=target_kl, + ) + + return policy + + +def create_gnn_policy( + node_feature_dim: int, + num_actions: int, + hidden_dim: int = 128, + num_conv_wo_resnet: int = 2, + num_resnet_layers: int = 5, + dropout_p: float = 0.2, + bidirectional: bool = True, + global_feature_dim: int = 0, +) -> SAGEActorCritic: + """Factory function for creating an SAGEActorCritic policy. + + Args: + node_feature_dim: Number of input node features. + num_actions: Number of compilation pass actions (env.action_space.n). + hidden_dim: Hidden dimension of the GNN layers. + num_conv_wo_resnet: Number of convolutional layers without residual connection. + num_resnet_layers: Number of residual convolutional layers. + dropout_p: Dropout probability. + bidirectional: Whether to use bidirectional message passing. + global_feature_dim: Number of flat RL observation features concatenated + to the graph embedding before the actor/critic heads (0 to disable). + + Returns: + Initialized SAGEActorCritic. + """ + return SAGEActorCritic( + in_feats=node_feature_dim, + hidden_dim=hidden_dim, + num_conv_wo_resnet=num_conv_wo_resnet, + num_resnet_layers=num_resnet_layers, + num_actions=num_actions, + dropout_p=dropout_p, + bidirectional=bidirectional, + global_feature_dim=global_feature_dim, + ) diff --git a/src/mqt/predictor/rl/helper.py b/src/mqt/predictor/rl/helper.py index 92eaf46d3..1a42046df 100644 --- a/src/mqt/predictor/rl/helper.py +++ b/src/mqt/predictor/rl/helper.py @@ -11,11 +11,19 @@ from __future__ import annotations import logging +import zipfile +from importlib import resources from pathlib import Path from typing import TYPE_CHECKING import numpy as np +import torch from qiskit import QuantumCircuit +from qiskit.converters import circuit_to_dag +from qiskit.dagcircuit import DAGOpNode +from qiskit.transpiler import PassManager +from qiskit.transpiler.passes import RemoveBarriers +from torch_geometric.data import Data from mqt.predictor.utils import calc_supermarq_features, get_openqasm_gates_for_rl @@ -23,12 +31,27 @@ from numpy.random import Generator from numpy.typing import NDArray -import zipfile -from importlib import resources logger = logging.getLogger("mqt-predictor") +NON_GATE_OBSERVATION_FEATURES = [ + "num_qubits", + "depth", + "program_communication", + "critical_depth", + "entanglement_ratio", + "parallelism", + "liveness", + "measure", +] +FLAT_RL_FEATURE_NAMES = [*NON_GATE_OBSERVATION_FEATURES, *get_openqasm_gates_for_rl()] + +# Number of flat RL observation features appended to each PyG Data object when +# graph=True. These match the regular MaskablePPO observation features in order. +GLOBAL_FEATURE_DIM: int = len(FLAT_RL_FEATURE_NAMES) + + def predicted_action_to_index(action: object) -> int: """Normalize an SB3 action prediction to a scalar action index. @@ -114,6 +137,155 @@ def get_num_qubits_from_filename(path: Path) -> int | None: return None +def get_bqskit_gates() -> list[str]: + """Returns a list of gate names matching the OpenQASM 3.0 standard library gates supported in BQSKit.""" + return [ + # --- 1-qubit gates --- + "id", + "x", + "y", + "z", + "h", + "s", + "sdg", + "t", + "tdg", + "sx", + "rx", + "ry", + "rz", + "u", + "u1", + "u2", + "u3", + # --- Controlled 1-qubit gates --- + "cx", + "cy", + "cz", + "ch", + "crx", + "cry", + "crz", + "cp", + "cu", + "cu1", + "cu2", + "cu3", + # --- 2-qubit gates --- + "swap", + "iswap", + "ecr", + "rzz", + "rxx", + "ryy", + "zz", + # --- 3-qubit gates --- + "ccx", + # --- Others --- + "reset", + ] + + +def create_dag(qc: QuantumCircuit) -> tuple[torch.Tensor, torch.Tensor, int]: + """Create a feature-annotated DAG representation of a quantum circuit for GNN models. + + Each node corresponds to a gate operation and is annotated with: + one-hot gate encoding, sin/cos of parameters (up to 3), arity, number of + control qubits, number of parameters, critical-path flag, fan-in, fan-out. + + Args: + qc: The quantum circuit to convert. + + Returns: + node_vector: Float tensor of shape (N, F) with node features. + edge_index: Long tensor of shape (2, E) with directed edges. + number_of_gates: Number of operation nodes N. + """ + pm = PassManager(RemoveBarriers()) + qc = pm.run(qc) + dag = circuit_to_dag(qc) + + unique_gates = [*get_bqskit_gates(), "measure", "other"] + gate2idx = {g: i for i, g in enumerate(unique_gates)} + number_gates = len(unique_gates) + + def _safe_float(val: object, default: float = 0.0) -> float: + try: + return float(val) # type: ignore[arg-type] + except (TypeError, ValueError): + return default + + def param_vector(node: DAGOpNode, dim: int = 3) -> list[float]: + raw_params = getattr(node.op, "params", []) + params = [_safe_float(v) for v in raw_params[:dim]] + params += [0.0] * (dim - len(params)) + out: list[float] = [] + for p in params: + out.extend([np.sin(p), np.cos(p)]) + return out # length = 2 * dim + + nodes = list(dag.op_nodes()) + number_nodes = len(nodes) + + onehots = torch.zeros((number_nodes, number_gates), dtype=torch.float32) + num_params = torch.zeros((number_nodes, 1), dtype=torch.float32) + params = torch.zeros((number_nodes, 6), dtype=torch.float32) + arity = torch.zeros((number_nodes, 1), dtype=torch.float32) + controls = torch.zeros((number_nodes, 1), dtype=torch.float32) + fan_in = torch.zeros((number_nodes, 1), dtype=torch.float32) + fan_out = torch.zeros((number_nodes, 1), dtype=torch.float32) + + for i, node in enumerate(nodes): + gate_name = node.op.name + idx = gate2idx.get(gate_name, gate2idx["other"]) + onehots[i, idx] = 1.0 + params[i] = torch.tensor(param_vector(node), dtype=torch.float32) + arity[i] = float(len(node.qargs)) + controls[i] = float(getattr(node.op, "num_ctrl_qubits", 0)) + num_params[i] = float(len(getattr(node.op, "params", []))) + preds = [p for p in dag.predecessors(node) if isinstance(p, DAGOpNode)] + succs = [s for s in dag.successors(node) if isinstance(s, DAGOpNode)] + fan_in[i] = len(preds) + fan_out[i] = len(succs) + + idx_map = {node: i for i, node in enumerate(nodes)} + edges: list[list[int]] = [] + for src, dst, _ in dag.edges(): + if src in idx_map and dst in idx_map: + edges.append([idx_map[src], idx_map[dst]]) + if edges: + edge_index = torch.tensor(edges, dtype=torch.long).t().contiguous() + else: + edge_index = torch.empty((2, 0), dtype=torch.long) + + topo_nodes = list(dag.topological_op_nodes()) + if not topo_nodes: + critical_flag = torch.zeros((number_nodes, 1), dtype=torch.float32) + node_vector = torch.cat([onehots, params, arity, controls, num_params, critical_flag, fan_in, fan_out], dim=1) + return node_vector, edge_index, number_nodes + + dist_in: dict[DAGOpNode, int] = dict.fromkeys(topo_nodes, 0) + for node in topo_nodes: + preds = [p for p in dag.predecessors(node) if isinstance(p, DAGOpNode)] + if preds: + dist_in[node] = max(dist_in.get(p, 0) + 1 for p in preds) + + dist_out: dict[DAGOpNode, int] = dict.fromkeys(topo_nodes, 0) + for node in reversed(topo_nodes): + succs = [s for s in dag.successors(node) if isinstance(s, DAGOpNode)] + if succs: + dist_out[node] = max(dist_out.get(s, 0) + 1 for s in succs) + + critical_len = max(dist_in.get(n, 0) + dist_out.get(n, 0) for n in topo_nodes) + critical_flag = torch.zeros((number_nodes, 1), dtype=torch.float32) + for i, node in enumerate(nodes): + if dist_in.get(node, 0) + dist_out.get(node, 0) == critical_len: + critical_flag[i] = 1.0 + + node_vector = torch.cat([onehots, params, arity, controls, num_params, critical_flag, fan_in, fan_out], dim=1) + return node_vector, edge_index, number_nodes + + def count_ops_by_name(qc: QuantumCircuit) -> dict[str, int]: """Return count_ops with string keys (gate names).""" raw = qc.count_ops() @@ -136,27 +308,59 @@ def dict_to_featurevector(gate_dict: dict[str, int]) -> dict[str, float]: return res_dct -def create_feature_dict(qc: QuantumCircuit) -> dict[str, int | NDArray[np.float32]]: - """Creates a feature dictionary for a given quantum circuit.""" +def create_flat_feature_dict(qc: QuantumCircuit) -> dict[str, int | NDArray[np.float32]]: + """Create the regular flat RL observation dictionary in a stable feature order.""" ops = count_ops_by_name(qc) total = sum(v for k, v in ops.items() if k != "barrier") ops_list_dict = dict_to_featurevector(ops) + supermarq_features = calc_supermarq_features(qc) - feature_dict: dict[str, int | NDArray[np.float32]] = { - **{key: np.array([val], dtype=np.float32) for key, val in ops_list_dict.items()}, - "measure": np.array([ops.get("measure", 0) / total if total > 0 else 0.0], dtype=np.float32), + feature_values: dict[str, int | NDArray[np.float32]] = { "num_qubits": int(qc.num_qubits), "depth": int(qc.depth()), + "program_communication": np.array([supermarq_features.program_communication], dtype=np.float32), + "critical_depth": np.array([supermarq_features.critical_depth], dtype=np.float32), + "entanglement_ratio": np.array([supermarq_features.entanglement_ratio], dtype=np.float32), + "parallelism": np.array([supermarq_features.parallelism], dtype=np.float32), + "liveness": np.array([supermarq_features.liveness], dtype=np.float32), + "measure": np.array([ops.get("measure", 0) / total if total > 0 else 0.0], dtype=np.float32), + **{key: np.array([ops_list_dict[key]], dtype=np.float32) for key in get_openqasm_gates_for_rl()}, } + return {key: feature_values[key] for key in FLAT_RL_FEATURE_NAMES} + + +def create_flat_feature_tensor(qc: QuantumCircuit) -> torch.Tensor: + """Return the regular flat RL observation as a single-row float tensor.""" + feature_dict = create_flat_feature_dict(qc) + flat_values: list[float] = [] + for key in FLAT_RL_FEATURE_NAMES: + value = feature_dict[key] + if isinstance(value, int): + flat_values.append(float(value)) + else: + flat_values.extend(np.asarray(value, dtype=np.float32).reshape(-1).tolist()) + return torch.tensor([flat_values], dtype=torch.float32) - supermarq_features = calc_supermarq_features(qc) - feature_dict["program_communication"] = np.array([supermarq_features.program_communication], dtype=np.float32) - feature_dict["critical_depth"] = np.array([supermarq_features.critical_depth], dtype=np.float32) - 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 +def create_feature_dict(qc: QuantumCircuit, *, graph: bool = False) -> dict[str, int | NDArray[np.float32]] | Data: + """Creates a feature representation for a given quantum circuit. + + Args: + qc: The quantum circuit to represent. + graph: If True, returns a PyG ``Data`` object suitable for GNN models. + If False (default), returns the flat feature dictionary used by MaskablePPO. + + Returns: + A PyG ``Data`` object when ``graph=True``, otherwise a feature dictionary. + """ + if graph: + node_vector, edge_index, number_nodes = create_dag(qc) + # Attach the complete regular RL observation vector so the GNN actor/critic + # sees graph structure plus the same flat features used by MaskablePPO. + global_feature_vector = create_flat_feature_tensor(qc) + return Data(x=node_vector, edge_index=edge_index, num_nodes=number_nodes, global_features=global_feature_vector) + + return create_flat_feature_dict(qc) def get_path_training_data() -> Path: diff --git a/src/mqt/predictor/rl/predictor.py b/src/mqt/predictor/rl/predictor.py index fe4b400ef..4aa778f73 100644 --- a/src/mqt/predictor/rl/predictor.py +++ b/src/mqt/predictor/rl/predictor.py @@ -10,16 +10,21 @@ from __future__ import annotations -import logging from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, TypeAlias +import numpy as np +import torch +from numpy.typing import NDArray 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.utils import set_random_seed +from torch.distributions import Categorical +from torch_geometric.data import Batch, Data -from mqt.predictor.rl.helper import get_path_trained_model, logger, predicted_action_to_index +from mqt.predictor.rl.gnn_ppo import create_gnn_policy, train_ppo_with_gnn +from mqt.predictor.rl.helper import GLOBAL_FEATURE_DIM, get_path_trained_model, logger, predicted_action_to_index from mqt.predictor.rl.predictorenv import PredictorEnv if TYPE_CHECKING: @@ -28,6 +33,32 @@ from stable_baselines3.common.callbacks import BaseCallback from mqt.predictor.reward import figure_of_merit + from mqt.predictor.rl.gnn import SAGEActorCritic + + +MaskablePPOObservationValue: TypeAlias = NDArray[np.int64] | NDArray[np.float32] +MaskablePPOObservation: TypeAlias = dict[str, MaskablePPOObservationValue] + + +def _as_maskable_ppo_observation(obs: object) -> MaskablePPOObservation: + """Normalize flat env observations to the ndarray-only form expected by SB3.""" + if isinstance(obs, Data): + msg = "MaskablePPO requires flat observations. Construct the predictor with graph=False." + raise TypeError(msg) + if not isinstance(obs, dict): + msg = f"Expected a flat observation dictionary, received {type(obs).__name__}." + raise TypeError(msg) + + normalized_obs: MaskablePPOObservation = {} + for key, value in obs.items(): + if not isinstance(key, str): + msg = f"Expected string observation keys, received {type(key).__name__}." + raise TypeError(msg) + if isinstance(value, int): + normalized_obs[key] = np.asarray(value, dtype=np.int64) + else: + normalized_obs[key] = np.asarray(value, dtype=np.float32) + return normalized_obs class Predictor: @@ -39,19 +70,40 @@ def __init__( device: Target, mdp: str = "paper", path_training_circuits: Path | None = None, - logger_level: int = logging.INFO, + reward_scale: float = 1.0, + no_effect_penalty: float = -0.001, + max_episode_steps: int | None = None, + graph: bool = False, ) -> None: - """Initializes the Predictor object.""" - logger.setLevel(logger_level) + """Initializes the Predictor object. + Arguments: + figure_of_merit: The figure of merit to optimize during compilation. + device: The target quantum device. + mdp: The MDP formulation to use ("paper" or "alternative"). Defaults to "paper". + path_training_circuits: Path to training circuits. Defaults to None. + reward_scale: Scaling factor for rewards/penalties proportional to fidelity changes. + no_effect_penalty: Step penalty applied when an action does not change the circuit. + max_episode_steps: Optional hard cap on environment steps per episode. + logger_level: Logging level. Defaults to INFO. + graph: If True, uses a GNN-based policy with PyG graph observations + (including circuit-level global features matching the non-GNN policy). + If False (default), uses the flat-feature MaskablePPO policy. + """ + self.graph = graph self.env = PredictorEnv( reward_function=figure_of_merit, device=device, - mdp=mdp, path_training_circuits=path_training_circuits, + reward_scale=reward_scale, + no_effect_penalty=no_effect_penalty, + max_episode_steps=max_episode_steps, + graph=graph, + mdp=mdp, ) self.device_name = device.description self.figure_of_merit = figure_of_merit + self.gnn_model: SAGEActorCritic | None = None def compile_as_predicted( self, @@ -68,20 +120,62 @@ def compile_as_predicted( Raises: RuntimeError: If an error occurs during compilation. """ + if not self.graph: + return self._compile_with_maskable_ppo(qc) + return self._compile_with_gnn(qc) + + def _compile_with_maskable_ppo(self, qc: QuantumCircuit | str) -> tuple[QuantumCircuit, list[str]]: + """Compiles the quantum circuit using a trained MaskablePPO model. + + Arguments: + qc: The quantum circuit to be compiled. It can be a Quantum Circuit object or a string for the path to a qasm file. + + Returns: + A tuple containing the compiled quantum circuit and a list of the compilation passes used. + """ trained_rl_model = load_model("model_" + self.figure_of_merit + "_" + self.device_name) obs, _ = self.env.reset(qc, seed=0) - used_compilation_passes = [] + used_compilation_passes: list[str] = [] + step_records: list[dict] = [] terminated = False truncated = False while not (terminated or truncated): + depth_before = self.env.state.depth() + gates_before = sum(v for k, v in self.env.state.count_ops().items() if k != "barrier") + action_masks = get_action_masks(self.env) - action, _ = trained_rl_model.predict(obs, action_masks=action_masks) + action, _ = trained_rl_model.predict(_as_maskable_ppo_observation(obs), action_masks=action_masks) action = predicted_action_to_index(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) + obs, reward, terminated, truncated, _info = self.env.step(action) + + depth_after = self.env.state.depth() + gates_after = sum(v for k, v in self.env.state.count_ops().items() if k != "barrier") + step_records.append({ + "step": len(step_records), + "action": action_item.name, + "depth_before": depth_before, + "gates_before": gates_before, + "depth_after": depth_after, + "gates_after": gates_after, + "reward": float(reward), + }) + + log_path = get_path_trained_model() / "actions_maskable_ppo.txt" + with log_path.open("a", encoding="utf-8") as f: + circuit_name = self.env.filename or str(qc) + f.write(f"circuit={circuit_name}\n") + for r in step_records: + f.write( + f" step={r['step']} action={r['action']}" + f" depth={r['depth_before']}->{r['depth_after']}" + f" gates={r['gates_before']}->{r['gates_after']}" + f" reward={r['reward']:.6f}\n" + ) + f.write("\n") if not self.env.error_occurred: return self.env.export_circuit(), used_compilation_passes @@ -89,6 +183,105 @@ def compile_as_predicted( msg = "Error occurred during compilation." raise RuntimeError(msg) + def _compile_with_gnn(self, qc: QuantumCircuit | str) -> tuple[QuantumCircuit, list[str]]: + """Compiles the quantum circuit using a trained GNN-based PPO model. + + Arguments: + qc: The quantum circuit to be compiled. It can be a Quantum Circuit object or a string for the path to a qasm file. + + Returns: + A tuple containing the compiled quantum circuit and a list of the compilation passes used. + + """ + obs, _ = self.env.reset(qc, seed=0) + + policy = self._load_gnn_model(obs.x.shape[1]) # ty: ignore[unresolved-attribute] + torch_device = "cuda" if torch.cuda.is_available() else "cpu" + policy = policy.to(torch_device) + policy.eval() + + used_passes: list[str] = [] + terminated = False + truncated = False + + with torch.no_grad(): + while not (terminated or truncated): + # get the observable in the form of a PyG Batch (with batch size 1) and move to the same device as the model + batch_obs = Batch.from_data_list([obs]).to(torch_device) # ty: ignore[invalid-argument-type,unresolved-attribute] + # evaluate the policy to get action logits and values + logits, _val = policy(batch_obs) + # mask the action not allowed by the environment + mask = torch.tensor(self.env.action_masks(), dtype=torch.bool, device=torch_device) + logits = logits.masked_fill(~mask.unsqueeze(0), float("-inf")) + # sample an action from the masked distribution + dist = Categorical(logits=logits.squeeze(0)) + action = int(dist.sample().item()) + action_name = self.env.action_set[action].name + # append the action name to the list of used passes and step records for logging + used_passes.append(action_name) + # apply the action to the environment and get the new observation, reward, and done flags + obs, _, terminated, truncated, _ = self.env.step(action) + + if not self.env.error_occurred: + return self.env.state, used_passes + + msg = "Error occurred during compilation." + raise RuntimeError(msg) + + def _load_gnn_model(self, node_feature_dim: int) -> SAGEActorCritic: + """Load a saved GNN model checkpoint. + + Arguments: + node_feature_dim: The dimension of the node features in the graph observations + Returns: + The loaded GNN model. + """ + model_path = get_path_trained_model() / f"gnn_{self.figure_of_merit}_{self.device_name}.pt" + if not model_path.is_file(): + msg = ( + f"The GNN RL model 'gnn_{self.figure_of_merit}_{self.device_name}' is not trained yet. " + "Please train the model before using it." + ) + raise FileNotFoundError(msg) + # load the checkpoint and extract the state dict and config + checkpoint = torch.load(model_path, map_location="cpu", weights_only=False) + if isinstance(checkpoint, dict) and "state_dict" in checkpoint: + state_dict = checkpoint["state_dict"] + cfg = checkpoint.get("config", {}) + else: + state_dict = checkpoint + cfg = {} + # extract model hyperparameters from the config, using defaults if not present + num_actions = self.env.action_space.n # ty: ignore[unresolved-attribute] + hidden_dim = int(cfg.get("hidden_dim", 128)) + num_conv_wo_resnet = int(cfg.get("num_conv_wo_resnet", 2)) + num_resnet_layers = int(cfg.get("num_resnet_layers", 5)) + dropout_p = float(cfg.get("dropout_p", 0.2)) + bidirectional = bool(cfg.get("bidirectional", True)) + global_feature_dim = int(cfg.get("global_feature_dim", 0)) + + if "node_feature_dim" in cfg and int(cfg["node_feature_dim"]) != node_feature_dim: + msg = f"node_feature_dim mismatch: checkpoint={cfg['node_feature_dim']} current={node_feature_dim}" + raise RuntimeError(msg) + if "num_actions" in cfg and int(cfg["num_actions"]) != num_actions: + msg = f"num_actions mismatch: checkpoint={cfg['num_actions']} current={num_actions}" + raise RuntimeError(msg) + + # create a new model instance with the same hyperparameters and load the state dict + policy = create_gnn_policy( + node_feature_dim=node_feature_dim, + num_actions=num_actions, + hidden_dim=hidden_dim, + num_conv_wo_resnet=num_conv_wo_resnet, + num_resnet_layers=num_resnet_layers, + dropout_p=dropout_p, + bidirectional=bidirectional, + global_feature_dim=global_feature_dim, + ) + # load the state dict into the model + policy.load_state_dict(state_dict, strict=True) + return policy + def train_model( self, timesteps: int = 1000, @@ -96,25 +289,66 @@ def train_model( test: bool = False, callback: BaseCallback | None = None, resume_from: Path | None = None, + **kwargs: object, + ) -> MaskablePPO | None: + """Trains a model for the given reward function and device. + + Arguments: + timesteps: Training timesteps for MaskablePPO (ignored in GNN mode). Defaults to 1000. + verbose: Verbosity level (MaskablePPO only). Defaults to 2. + test: Use reduced hyperparameters for fast testing. Defaults to False. + callback: Optional SB3 callback used during training (MaskablePPO only). Defaults to None. + resume_from: Optional path to a previously saved PPO checkpoint (MaskablePPO only). Defaults to None. + **kwargs: Additional hyperparameters for GNN training: + - iterations (int): PPO iterations. Defaults to 1000. + - steps (int): Steps per iteration. Defaults to 2048. + - num_epochs (int): PPO update epochs. Defaults to 10. + - minibatch_size (int): Minibatch size. Defaults to 64. + - hidden_dim (int): GNN hidden dimension. Defaults to 128. + - num_conv_wo_resnet (int): Non-residual conv layers. Defaults to 2. + - num_resnet_layers (int): Residual conv layers. Defaults to 5. + - dropout_p (float): Dropout probability. Defaults to 0.2. + - bidirectional (bool): Bidirectional message passing. Defaults to True. + - lr (float): Learning rate for actor/critic heads. Defaults to 3e-4. + - gnn_lr (float): Learning rate for GNN encoder. Defaults to 1e-4. + """ + if self.graph: + self._train_gnn(test=test, **kwargs) + return None + + return self._train_maskable_ppo( + timesteps=timesteps, + verbose=verbose, + test=test, + callback=callback, + resume_from=resume_from, + ) + + def _train_maskable_ppo( + self, + timesteps: int, + verbose: int, + test: bool, + callback: BaseCallback | None = None, + resume_from: Path | None = None, ) -> MaskablePPO: - """Trains all models for the given reward functions and device. + """Trains a MaskablePPO model. Arguments: - timesteps: The number of timesteps to train the model. Defaults to 1000. - verbose: The verbosity level. Defaults to 2. - test: Whether to train the model for testing purposes. Defaults to False. + timesteps: Total training timesteps. + verbose: Verbosity level for training. + test: If True, uses reduced hyperparameters for fast testing. callback: Optional SB3 callback used during training. resume_from: Optional path to a previously saved PPO checkpoint. """ if test: - set_random_seed(0) # for reproducibility + set_random_seed(0) n_steps = 32 n_epochs = 2 batch_size = 8 progress_bar = False else: set_random_seed(0) - # default PPO values n_steps = 2048 n_epochs = 10 batch_size = 64 @@ -151,6 +385,134 @@ def train_model( model.save(get_path_trained_model() / ("model_" + self.figure_of_merit + "_" + self.device_name)) return model + def _train_gnn(self, test: bool = False, **kwargs: object) -> None: + """Trains the GNN model. + + Arguments: + test: If True, uses reduced hyperparameters for fast testing. + **kwargs: Additional hyperparameters for GNN training (see train_model). + """ + if test: + # Set init values for testing + kwargs.setdefault("iterations", 10) + kwargs.setdefault("steps", 20) + kwargs.setdefault("num_epochs", 1) + kwargs.setdefault("minibatch_size", 32) + kwargs.setdefault("hidden_dim", 128) + kwargs.setdefault("num_conv_wo_resnet", 3) + kwargs.setdefault("num_resnet_layers", 5) + + sample_obs, _ = self.env.reset() + node_feature_dim = sample_obs.x.shape[1] # ty: ignore[unresolved-attribute] + + hidden_dim = int(kwargs.get("hidden_dim", 128)) # ty: ignore[invalid-argument-type] + num_conv_wo_resnet = int(kwargs.get("num_conv_wo_resnet", 3)) # ty: ignore[invalid-argument-type] + num_resnet_layers = int(kwargs.get("num_resnet_layers", 5)) # ty: ignore[invalid-argument-type] + dropout_p = float(kwargs.get("dropout_p", 0.2)) # ty: ignore[invalid-argument-type] + bidirectional = bool(kwargs.get("bidirectional", True)) + + policy = create_gnn_policy( + node_feature_dim=node_feature_dim, + num_actions=self.env.action_space.n, # ty: ignore[unresolved-attribute] + hidden_dim=hidden_dim, + num_conv_wo_resnet=num_conv_wo_resnet, + num_resnet_layers=num_resnet_layers, + dropout_p=dropout_p, + bidirectional=bidirectional, + global_feature_dim=GLOBAL_FEATURE_DIM, + ) + + self.gnn_model = train_ppo_with_gnn( + env=self.env, + policy=policy, + num_iterations=int(kwargs.get("iterations", 1000)), # type: ignore[arg-type] + steps_per_iteration=int(kwargs.get("steps", 2048)), # type: ignore[arg-type] + num_epochs=int(kwargs.get("num_epochs", 10)), # type: ignore[arg-type] + minibatch_size=int(kwargs.get("minibatch_size", 64)), # type: ignore[arg-type] + lr=float(kwargs.get("lr", 3e-4)), # type: ignore[arg-type] + gnn_lr=float(kwargs.get("gnn_lr", 1e-4)), # type: ignore[arg-type] + ) + + model_path = get_path_trained_model() / f"gnn_{self.figure_of_merit}_{self.device_name}.pt" + ckpt = { + "state_dict": self.gnn_model.state_dict(), + "config": { + "hidden_dim": hidden_dim, + "num_conv_wo_resnet": num_conv_wo_resnet, + "num_resnet_layers": num_resnet_layers, + "dropout_p": dropout_p, + "bidirectional": bidirectional, + "node_feature_dim": node_feature_dim, + "num_actions": self.env.action_space.n, # ty: ignore[unresolved-attribute] + "global_feature_dim": GLOBAL_FEATURE_DIM, + }, + } + torch.save(ckpt, model_path) + + def _train_gnn(self, test: bool = False, **kwargs: object) -> None: + """Trains the GNN model. + + Arguments: + test: If True, uses reduced hyperparameters for fast testing. + **kwargs: Additional hyperparameters for GNN training (see train_model). + """ + if test: + # Set init values for testing + kwargs.setdefault("iterations", 10) + kwargs.setdefault("steps", 20) + kwargs.setdefault("num_epochs", 1) + kwargs.setdefault("minibatch_size", 32) + kwargs.setdefault("hidden_dim", 128) + kwargs.setdefault("num_conv_wo_resnet", 3) + kwargs.setdefault("num_resnet_layers", 5) + + sample_obs, _ = self.env.reset() + node_feature_dim = sample_obs.x.shape[1] # ty: ignore[unresolved-attribute] + + hidden_dim = int(kwargs.get("hidden_dim", 128)) # ty: ignore[invalid-argument-type] + num_conv_wo_resnet = int(kwargs.get("num_conv_wo_resnet", 3)) # ty: ignore[invalid-argument-type] + num_resnet_layers = int(kwargs.get("num_resnet_layers", 5)) # ty: ignore[invalid-argument-type] + dropout_p = float(kwargs.get("dropout_p", 0.2)) # ty: ignore[invalid-argument-type] + bidirectional = bool(kwargs.get("bidirectional", True)) + + policy = create_gnn_policy( + node_feature_dim=node_feature_dim, + num_actions=self.env.action_space.n, # ty: ignore[unresolved-attribute] + hidden_dim=hidden_dim, + num_conv_wo_resnet=num_conv_wo_resnet, + num_resnet_layers=num_resnet_layers, + dropout_p=dropout_p, + bidirectional=bidirectional, + global_feature_dim=GLOBAL_FEATURE_DIM, + ) + + self.gnn_model = train_ppo_with_gnn( + env=self.env, + policy=policy, + num_iterations=int(kwargs.get("iterations", 1000)), # type: ignore[arg-type] + steps_per_iteration=int(kwargs.get("steps", 2048)), # type: ignore[arg-type] + num_epochs=int(kwargs.get("num_epochs", 10)), # type: ignore[arg-type] + minibatch_size=int(kwargs.get("minibatch_size", 64)), # type: ignore[arg-type] + lr=float(kwargs.get("lr", 3e-4)), # type: ignore[arg-type] + gnn_lr=float(kwargs.get("gnn_lr", 1e-4)), # type: ignore[arg-type] + ) + + model_path = get_path_trained_model() / f"gnn_{self.figure_of_merit}_{self.device_name}.pt" + ckpt = { + "state_dict": self.gnn_model.state_dict(), + "config": { + "hidden_dim": hidden_dim, + "num_conv_wo_resnet": num_conv_wo_resnet, + "num_resnet_layers": num_resnet_layers, + "dropout_p": dropout_p, + "bidirectional": bidirectional, + "node_feature_dim": node_feature_dim, + "num_actions": self.env.action_space.n, # ty: ignore[unresolved-attribute] + "global_feature_dim": GLOBAL_FEATURE_DIM, + }, + } + torch.save(ckpt, model_path) + def load_model(model_name: str) -> MaskablePPO: """Loads a trained model from the trained model folder. @@ -169,7 +531,6 @@ def load_model(model_name: str) -> MaskablePPO: return MaskablePPO.load(path / (model_name + ".zip")) error_msg = f"The RL model '{model_name}' is not trained yet. Please train the model before using it." - logger.error(error_msg) raise FileNotFoundError(error_msg) @@ -178,6 +539,7 @@ def rl_compile( device: Target | None, figure_of_merit: figure_of_merit | None = "expected_fidelity", predictor_singleton: Predictor | None = None, + graph: bool = False, ) -> tuple[QuantumCircuit, list[str]]: """Compiles a given quantum circuit to a device optimizing for the given figure of merit. @@ -186,6 +548,7 @@ def rl_compile( device: The device to compile to. figure_of_merit: The figure of merit to be used for compilation. Defaults to "expected_fidelity". predictor_singleton: A predictor object that is used for compilation to reduce compilation time when compiling multiple quantum circuits. If None, a new predictor object is created. Defaults to None. + graph: If True, uses the GNN-based policy. Ignored when ``predictor_singleton`` is provided. Defaults to False. Returns: A tuple containing the compiled quantum circuit and the compilation information. If compilation fails, False is returned. @@ -200,7 +563,7 @@ def rl_compile( if device is None: msg = "device must not be None if predictor_singleton is None." raise ValueError(msg) - predictor = Predictor(figure_of_merit=figure_of_merit, device=device) + predictor = Predictor(figure_of_merit=figure_of_merit, device=device, graph=graph) else: predictor = predictor_singleton diff --git a/src/mqt/predictor/rl/predictorenv.py b/src/mqt/predictor/rl/predictorenv.py index 457a9a1ff..53070d426 100644 --- a/src/mqt/predictor/rl/predictorenv.py +++ b/src/mqt/predictor/rl/predictorenv.py @@ -39,6 +39,7 @@ from gymnasium import Env from gymnasium.spaces import Box, Dict, Discrete from joblib import load +from numpy.typing import NDArray from pytket.circuit import Qubit from pytket.extensions.qiskit import qiskit_to_tk, tk_to_qiskit from pytket.placement import Placement @@ -55,6 +56,7 @@ SetLayout, ) from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason +from torch_geometric.data import Data from mqt.predictor.hellinger import get_hellinger_model_path from mqt.predictor.reward import ( @@ -68,6 +70,7 @@ CompilationOrigin, DeviceDependentAction, PassType, + ensure_ai_routing_runtime_available, get_actions_by_pass_type, ) from mqt.predictor.rl.approx_reward import ( @@ -91,6 +94,11 @@ logger = logging.getLogger("mqt-predictor") +FeatureValue = int | NDArray[np.float32] +FlatObservation = dict[str, FeatureValue] +EnvironmentObservation = FlatObservation | Data + + def _layout_output_qubits(layout: TranspileLayout) -> list[Any]: """Return the materialized output wires tracked by a TranspileLayout.""" output_qubits = layout._output_qubit_list # noqa: SLF001 @@ -131,6 +139,8 @@ def __init__( path_training_circuits: Path | None = None, reward_scale: float = 1.0, no_effect_penalty: float = -0.001, + max_episode_steps: int | None = None, + graph: bool = False, ) -> None: """Initializes the PredictorEnv object. @@ -145,12 +155,16 @@ def __init__( path_training_circuits: The path to the training circuits folder. Defaults to None, which uses the default path. reward_scale: Scaling factor for rewards/penalties proportional to fidelity changes. no_effect_penalty: Step penalty applied when an action does not change the circuit (no-op). + max_episode_steps: Optional hard cap on environment steps per episode. When reached without + taking the terminate action, the episode ends with ``truncated=True``. + graph: If True, observations are returned as PyG Data objects for GNN-based agents. Defaults to False. Raises: ValueError: If the reward function is "estimated_success_probability" and no calibration data is available for the device or if the reward function is "estimated_hellinger_distance" and no trained model is available for the device. """ logger.info("Init env: " + reward_function) + self.graph = graph self.path_training_circuits = path_training_circuits or get_path_training_circuits() self.action_set = {} @@ -206,6 +220,9 @@ def __init__( self.action_set[index] = action_dict[PassType.TERMINATE][0] self.action_terminate_index = index + if any(action.name in {"AIRouting", "AIRouting_opt"} for action in self.action_set.values()): + ensure_ai_routing_runtime_available() + if reward_function == "estimated_success_probability" and not esp_data_available(self.device): msg = f"Missing calibration data for ESP calculation on {self.device.description}." raise ValueError(msg) @@ -245,6 +262,7 @@ def __init__( self.readout_err: dict[Node, float] | None = None self.reward_scale = reward_scale self.no_effect_penalty = no_effect_penalty + self.max_episode_steps = max_episode_steps self.prev_reward: float | None = None self.prev_reward_kind: str | None = None self.episode_count = 0 @@ -277,10 +295,11 @@ def _apply_and_update(self, action: int) -> QuantumCircuit | None: return altered_qc - def _create_observation(self, qc: QuantumCircuit | None = None) -> dict[str, Any]: + def _create_observation(self, qc: QuantumCircuit | None = None) -> EnvironmentObservation: """Create an observation directly from the actual circuit state.""" circuit = self.state if qc is None else qc - return create_feature_dict(circuit) + graph = self.graph if qc is None else False + return create_feature_dict(circuit, graph=graph) def export_circuit(self, qc: QuantumCircuit | None = None) -> QuantumCircuit: """Return a copy of a circuit with the current env layout attached.""" @@ -306,6 +325,10 @@ def _log_step_reward(self, step_index: int, action_name: str, reward_val: float, reward_val, ) + def _episode_budget_exhausted(self) -> bool: + """Return whether the current episode reached the configured step cap.""" + return self.max_episode_steps is not None and self.num_steps >= self.max_episode_steps + def _get_compilation_state_flags(self) -> tuple[bool, bool, bool]: """Return `(synthesized, laid_out, routed)` for the current circuit state.""" if self.compilation_state_flags is not None: @@ -323,7 +346,7 @@ def get_compilation_state_flags(self) -> tuple[bool, bool, bool]: """Return `(synthesized, laid_out, routed)` for the current circuit state.""" return self._get_compilation_state_flags() - def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any, Any]]: + def step(self, action: int) -> tuple[EnvironmentObservation, float, bool, bool, dict[Any, Any]]: """Run one environment step. This method: @@ -343,6 +366,7 @@ def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any self.used_actions.append(action_name) logger.info("Episode %d step %d: applying %s", self.episode_count, step_index, action_name) previous_state_flags = self._get_compilation_state_flags() + previous_circuit = self.export_circuit() altered_qc = self._apply_and_update(action) if altered_qc is None: @@ -350,43 +374,69 @@ def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any return self._create_observation(), 0.0, True, False, {} done = action == self.action_terminate_index + truncated = False + info: dict[Any, Any] = {} if self.reward_function == "estimated_hellinger_distance": reward_val = self.calculate_reward(mode="exact")[0] if done else 0.0 - self._log_step_reward(step_index, action_name, reward_val, done) - return self._create_observation(), reward_val, done, False, {} - - # Lazy init: compute prev_reward only once per episode (or if missing) - if self.prev_reward is None: - self.prev_reward, self.prev_reward_kind = self.calculate_reward(mode="auto") + if not done and self._episode_budget_exhausted(): + truncated = True + info = { + "time_limit_reached": True, + "max_episode_steps": self.max_episode_steps, + } + self._log_step_reward(step_index, action_name, reward_val, done or truncated) + return self._create_observation(), reward_val, done, truncated, info if done: assert action in self.valid_actions, "Terminate action is not valid but was chosen." self.prev_reward, self.prev_reward_kind = self.calculate_reward(mode="exact") reward_val = self.prev_reward else: + assert self.prev_reward is not None + assert self.prev_reward_kind is not None current_state_flags = self._get_compilation_state_flags() new_val, new_kind = self.calculate_reward(mode="auto") - delta_reward = new_val - self.prev_reward reward_kind_changed = self.prev_reward_kind != new_kind state_changed = any( not before and after for before, after in zip(previous_state_flags, current_state_flags, strict=True) ) - if reward_kind_changed or state_changed: - delta_reward = 0.0 + if reward_kind_changed: + previous_compare = ( + self.prev_reward + if self.prev_reward_kind == "approx" + else self.calculate_reward(qc=previous_circuit, mode="approx")[0] + ) + new_compare = new_val if new_kind == "approx" else self.calculate_reward(mode="approx")[0] + delta_reward = new_compare - previous_compare + else: + delta_reward = new_val - self.prev_reward if not isclose(delta_reward, 0.0, abs_tol=1e-12): reward_val = self.reward_scale * delta_reward - elif reward_kind_changed or state_changed: + elif state_changed: reward_val = 0.0 else: reward_val = self.no_effect_penalty self.prev_reward, self.prev_reward_kind = new_val, new_kind + if not done and self._episode_budget_exhausted(): + truncated = True + info = { + "time_limit_reached": True, + "max_episode_steps": self.max_episode_steps, + } + logger.info( + "Episode %d step %d: reached max_episode_steps=%d, truncating episode", + self.episode_count, + step_index, + self.max_episode_steps, + ) + obs = self._create_observation() - self._log_step_reward(step_index, action_name, reward_val, done) - return obs, reward_val, done, False, {} + self._log_step_reward(step_index, action_name, reward_val, done or truncated) + return obs, reward_val, done, truncated, info def calculate_reward(self, qc: QuantumCircuit | None = None, mode: str = "auto") -> tuple[float, str]: """Compute the reward for a circuit and report whether it was computed exactly or approximately. @@ -496,7 +546,7 @@ def reset( qc: Path | str | QuantumCircuit | None = None, seed: int | None = None, options: dict[str, Any] | None = None, # noqa: ARG002 - ) -> tuple[dict[str, Any], dict[str, Any]]: + ) -> tuple[EnvironmentObservation, dict[str, Any]]: """Resets the environment to the given state or a random state. Arguments: @@ -535,8 +585,11 @@ def reset( self.error_occurred = False - self.prev_reward = None - self.prev_reward_kind = None + if self.reward_function == "estimated_hellinger_distance": + self.prev_reward = None + self.prev_reward_kind = None + else: + self.prev_reward, self.prev_reward_kind = self.calculate_reward(mode="auto") self.num_qubits_uncompiled_circuit = self.state.num_qubits self.has_parameterized_gates = len(self.state.parameters) > 0 @@ -665,7 +718,7 @@ def fom_aware_compile( except (QiskitError, TranspilerError, RuntimeError, ValueError, TypeError) as e: logger.warning(f"[Fallback to SWAP counts] Synthesis or fidelity computation failed: {e}") - swap_count = out_circ.count_ops().get("swap", 0) # ty: ignore[no-matching-overload] + swap_count = out_circ.count_ops().get("swap", 0) if best_result is None or swap_count < best_swap_count: best_swap_count = swap_count best_result = out_circ @@ -724,9 +777,9 @@ def _apply_qiskit_action(self, action: Action, action_index: int) -> QuantumCirc # BasisTranslator errors on unitary gates; decompose them immediately so # the circuit is always in a consistent state after a Qiskit action. - if altered_qc.count_ops().get("unitary"): # ty: ignore[invalid-argument-type] + if altered_qc.count_ops().get("unitary"): altered_qc = altered_qc.decompose(gates_to_decompose="unitary") - elif altered_qc.count_ops().get("clifford"): # ty: ignore[invalid-argument-type] + elif altered_qc.count_ops().get("clifford"): altered_qc = altered_qc.decompose(gates_to_decompose="clifford") return altered_qc @@ -848,6 +901,7 @@ 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))}) + # Replace implicit wire swaps with explicit SWAP gates during conversion to Qiskit altered_qc = tk_to_qiskit(tket_qc, replace_implicit_swaps=True) if action_index in self.actions_routing_indices: diff --git a/tests/compilation/test_ai_routing.py b/tests/compilation/test_ai_routing.py index 0b4f53d4c..bd1fe2364 100644 --- a/tests/compilation/test_ai_routing.py +++ b/tests/compilation/test_ai_routing.py @@ -19,19 +19,19 @@ from qiskit.converters import circuit_to_dag, dag_to_circuit from qiskit.transpiler import Layout -from mqt.predictor.rl.actions import IS_WIN_PY313 +from mqt.predictor.rl.actions import HAS_AI_ROUTING, IS_WIN_PY313 if TYPE_CHECKING: from qiskit.dagcircuit import DAGCircuit -if not IS_WIN_PY313: +if HAS_AI_ROUTING: from mqt.predictor.rl.actions import AIRouting, SafeAIRouting +_skip_reason = "SafeAIRouting requires qiskit-ibm-transpiler and is disabled on Windows + Python 3.13" +pytestmark = pytest.mark.skipif(not HAS_AI_ROUTING or IS_WIN_PY313, reason=_skip_reason) -pytestmark = pytest.mark.skipif(IS_WIN_PY313, reason="SafeAIRouting is disabled on Windows + Python 3.13") - -@pytest.mark.skipif(IS_WIN_PY313, reason="SafeAIRouting is unavailable on this platform") +@pytest.mark.skipif(not HAS_AI_ROUTING or IS_WIN_PY313, reason=_skip_reason) def test_safe_airouting_preserves_and_remaps_measurements(monkeypatch: pytest.MonkeyPatch) -> None: """Ensure SafeAIRouting preserves classical structure and remaps measured qubits.""" q0 = QuantumRegister(2, "qa") diff --git a/tests/compilation/test_helper_rl.py b/tests/compilation/test_helper_rl.py index a933f1f31..881273d02 100644 --- a/tests/compilation/test_helper_rl.py +++ b/tests/compilation/test_helper_rl.py @@ -14,14 +14,18 @@ from typing import TYPE_CHECKING, cast import numpy as np +import torch from mqt.bench import BenchmarkLevel, get_benchmark from mqt.bench.targets import get_device from qiskit import transpile from qiskit.transpiler import PassManager from qiskit.transpiler.passes.layout.vf2_post_layout import VF2PostLayoutStopReason +from torch_geometric.data import Batch, Data from mqt.predictor.rl.actions import PassType, get_actions_by_pass_type -from mqt.predictor.rl.helper import create_feature_dict, get_path_trained_model, get_path_training_circuits +from mqt.predictor.rl.gnn import SAGEActorCritic +from mqt.predictor.rl.gnn_ppo import create_gnn_policy +from mqt.predictor.rl.helper import create_dag, create_feature_dict, get_path_trained_model, get_path_training_circuits from mqt.predictor.rl.parsing import postprocess_vf2postlayout if TYPE_CHECKING: @@ -92,3 +96,105 @@ def test_vf2_layout_and_postlayout() -> None: _, pass_manager = postprocess_vf2postlayout(altered_qc, pm.property_set["post_layout"], qc_transpiled.layout) assert initial_layout_before != pass_manager.property_set["initial_layout"] + + +def test_create_dag_output_shapes() -> None: + """Test that create_dag returns tensors with correct dtypes and shapes.""" + qc = get_benchmark("ghz", BenchmarkLevel.ALG, 3) + node_vector, edge_index, number_of_gates = create_dag(qc) + + assert isinstance(node_vector, torch.Tensor) + assert isinstance(edge_index, torch.Tensor) + assert isinstance(number_of_gates, int) + assert number_of_gates > 0 + assert node_vector.shape == (number_of_gates, node_vector.shape[1]) + assert node_vector.shape[1] > 0 + assert node_vector.dtype == torch.float32 + assert edge_index.shape[0] == 2 + assert edge_index.dtype == torch.long + + +def test_create_feature_dict_graph_mode() -> None: + """Test that create_feature_dict with graph=True returns a PyG Data object.""" + qc = get_benchmark("dj", BenchmarkLevel.ALG, 5) + data = create_feature_dict(qc, graph=True) + + assert isinstance(data, Data) + assert isinstance(data.x, torch.Tensor) + assert isinstance(data.edge_index, torch.Tensor) + assert data.x.shape[0] > 0 + assert data.edge_index.shape[0] == 2 + # node feature dim must be consistent with create_dag + _, _, num_nodes = create_dag(qc) + assert data.x.shape[0] == num_nodes + + +def test_sage_actor_critic_forward() -> None: + """Test SAGEActorCritic forward pass produces tensors with correct shapes.""" + qc = get_benchmark("ghz", BenchmarkLevel.ALG, 3) + node_vector, edge_index, num_nodes = create_dag(qc) + in_feats = node_vector.shape[1] + num_actions = 42 + + model = SAGEActorCritic( + in_feats=in_feats, + hidden_dim=32, + num_conv_wo_resnet=1, + num_resnet_layers=1, + num_actions=num_actions, + ) + model.eval() + + data = Data(x=node_vector, edge_index=edge_index, num_nodes=num_nodes) + batch = Batch.from_data_list([data]) + with torch.no_grad(): + logits, value = model(batch) + + assert logits.shape == (1, num_actions) + assert value.shape == (1, 1) + + +def test_sage_actor_critic_batch_forward() -> None: + """Test SAGEActorCritic forward pass on a batch of graphs.""" + qc = get_benchmark("ghz", BenchmarkLevel.ALG, 3) + node_vector, edge_index, num_nodes = create_dag(qc) + in_feats = node_vector.shape[1] + num_actions = 10 + batch_size = 4 + + model = SAGEActorCritic( + in_feats=in_feats, + hidden_dim=32, + num_conv_wo_resnet=1, + num_resnet_layers=1, + num_actions=num_actions, + ) + model.eval() + + graphs = [Data(x=node_vector, edge_index=edge_index, num_nodes=num_nodes) for _ in range(batch_size)] + batch = Batch.from_data_list(graphs) + with torch.no_grad(): + logits, value = model(batch) + + assert logits.shape == (batch_size, num_actions) + assert value.shape == (batch_size, 1) + + +def test_create_gnn_policy_factory() -> None: + """Test that create_gnn_policy returns a correctly configured SAGEActorCritic.""" + qc = get_benchmark("ghz", BenchmarkLevel.ALG, 3) + node_vector, _, _ = create_dag(qc) + in_feats = node_vector.shape[1] + num_actions = 15 + hidden_dim = 64 + + policy = create_gnn_policy( + node_feature_dim=in_feats, + num_actions=num_actions, + hidden_dim=hidden_dim, + num_conv_wo_resnet=1, + num_resnet_layers=1, + ) + + assert isinstance(policy, SAGEActorCritic) + assert policy.encoder.out_dim == hidden_dim diff --git a/tests/compilation/test_predictor_rl.py b/tests/compilation/test_predictor_rl.py index ff3449ba3..3b079713f 100644 --- a/tests/compilation/test_predictor_rl.py +++ b/tests/compilation/test_predictor_rl.py @@ -12,15 +12,18 @@ import re from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any import pytest +import torch 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 +from qiskit.transpiler import InstructionProperties, Target +from qiskit.transpiler.passes import GatesInBasis +from torch_geometric.data import Data from mqt.predictor.rl import Predictor, rl_compile from mqt.predictor.rl.actions import ( @@ -35,6 +38,8 @@ from mqt.predictor.rl.helper import create_feature_dict, get_path_trained_model if TYPE_CHECKING: + from collections.abc import Generator + from _pytest.monkeypatch import MonkeyPatch from mqt.predictor.reward import figure_of_merit @@ -69,46 +74,6 @@ def test_predictor_env_hellinger_error() -> None: Predictor(figure_of_merit="estimated_hellinger_distance", device=device) -def test_qcompile_with_newly_trained_models() -> None: - """Test the qcompile function with a newly trained model. - - 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_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=1000, - test=True, - ) - - qc_compiled, compilation_information = rl_compile(qc, device=device, figure_of_merit=figure_of_merit) - - assert qc_compiled.layout is not None - assert compilation_information is not None - assert predictor.env.is_circuit_synthesized(qc_compiled), ( - "Circuit should only contain native gates but was not detected as such." - ) - assert predictor.env.is_circuit_routed(qc_compiled, CouplingMap(device.build_coupling_map())), ( - "Circuit should be mapped to the device's coupling map." - ) - - def test_qcompile_with_false_input() -> None: """Test the qcompile function with false input.""" qc = get_benchmark("dj", BenchmarkLevel.ALG, 5) @@ -118,6 +83,45 @@ def test_qcompile_with_false_input() -> None: rl_compile(qc, device=None, figure_of_merit="expected_fidelity") +@pytest.mark.parametrize( + ("graph", "device_name", "train_kwargs"), + [ + pytest.param(False, "ibm_falcon_127", {"timesteps": 10}, id="ppo-ibm_falcon_127"), + pytest.param(False, "quantinuum_h2_56", {"timesteps": 10}, id="ppo-quantinuum_h2_56"), + pytest.param( + True, + "ibm_falcon_127", + {"iterations": 2, "steps": 5, "num_epochs": 1, "minibatch_size": 4}, + id="gnn-ibm_falcon_127", + ), + ], +) +@pytest.mark.usefixtures("_cleanup_gnn_model_expected_fidelity_ibm_falcon_127") +def test_train_and_compile(graph: bool, device_name: str, train_kwargs: dict[str, Any]) -> None: + """Test the training and compilation pipeline for both MaskablePPO and GNN approaches. + + Important: The non-GNN 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(device_name) + qc = get_benchmark("ghz", BenchmarkLevel.ALG, 3) + predictor = Predictor(figure_of_merit=figure_of_merit, device=device, graph=graph) + predictor.train_model(test=True, **train_kwargs) + + qc_compiled, compilation_info = predictor.compile_as_predicted(qc) + + assert isinstance(qc_compiled, QuantumCircuit) + assert compilation_info is not None + assert len(compilation_info) > 0 + if not graph: + check_nat_gates = GatesInBasis(basis_gates=device.operation_names) + check_nat_gates(qc_compiled) + assert check_nat_gates.property_set["all_gates_in_basis"], ( + "Circuit should only contain native gates but was not detected as such" + ) + + def test_warning_for_unidirectional_device() -> None: """Test the warning for a unidirectional device.""" target = Target() @@ -229,3 +233,87 @@ def test_approx_reward_paths_use_cached_per_gate_maps(fom: figure_of_merit) -> N assert len(predictor.env.dur_by_gate) > 0 # tbar is optional depending on backend calibration; just sanity-check type assert predictor.env.tbar is None or predictor.env.tbar > 0.0 + + +@pytest.mark.parametrize("graph", [False, True]) +def test_predictor_env_observation_type(graph: bool) -> None: + """Test that PredictorEnv returns the correct observation type based on graph mode.""" + device = get_device("ibm_falcon_127") + predictor = Predictor(figure_of_merit="expected_fidelity", device=device, graph=graph) + qc = get_benchmark("ghz", BenchmarkLevel.ALG, 3) + obs, _ = predictor.env.reset(qc=qc) + + if graph: + assert isinstance(obs, Data) + assert isinstance(obs.x, torch.Tensor) + assert isinstance(obs.edge_index, torch.Tensor) + assert obs.x.shape[0] > 0 + assert obs.edge_index.shape[0] == 2 + else: + assert isinstance(obs, dict) + + +@pytest.mark.parametrize( + ("graph", "model_file", "error_match"), + [ + pytest.param( + False, + "model_critical_depth_ibm_falcon_127.zip", + "The RL model 'model_critical_depth_ibm_falcon_127' is not trained yet.", + id="ppo", + ), + pytest.param( + True, + "gnn_critical_depth_ibm_falcon_127.pt", + "The GNN RL model 'gnn_critical_depth_ibm_falcon_127' is not trained yet.", + id="gnn", + ), + ], +) +def test_model_not_trained_raises(graph: bool, model_file: str, error_match: str) -> None: + """Test that compile_as_predicted raises FileNotFoundError when the model is not trained.""" + device = get_device("ibm_falcon_127") + predictor = Predictor(figure_of_merit="critical_depth", device=device, graph=graph) + model_path = get_path_trained_model() / model_file + if model_path.exists(): + model_path.unlink() + + qc = get_benchmark("ghz", BenchmarkLevel.ALG, 3) + with pytest.raises(FileNotFoundError, match=re.escape(error_match)): + predictor.compile_as_predicted(qc) + + +@pytest.fixture +def _cleanup_gnn_model_expected_fidelity_ibm_falcon_127() -> Generator[None, None, None]: + """Remove the GNN checkpoint created by GNN training tests.""" + yield + model_path = get_path_trained_model() / "gnn_expected_fidelity_ibm_falcon_127.pt" + if model_path.exists(): + model_path.unlink() + + +@pytest.mark.usefixtures("_cleanup_gnn_model_expected_fidelity_ibm_falcon_127") +def test_gnn_checkpoint_config_saved_correctly() -> None: + """Test that the GNN checkpoint stores config metadata required for model reload.""" + figure_of_merit = "expected_fidelity" + device = get_device("ibm_falcon_127") + + predictor = Predictor(figure_of_merit=figure_of_merit, device=device, graph=True) + predictor.train_model(test=True, iterations=2, steps=5, num_epochs=1, minibatch_size=4) + + model_path = get_path_trained_model() / f"gnn_{figure_of_merit}_{device.description}.pt" + checkpoint = torch.load(model_path, map_location="cpu", weights_only=False) + + assert "state_dict" in checkpoint + assert "config" in checkpoint + cfg = checkpoint["config"] + for key in ( + "hidden_dim", + "num_conv_wo_resnet", + "num_resnet_layers", + "dropout_p", + "bidirectional", + "node_feature_dim", + "num_actions", + ): + assert key in cfg, f"Missing config key: {key}" diff --git a/tests/device_selection/test_helper_ml.py b/tests/device_selection/test_helper_ml.py index 87f69f009..09b60aee6 100644 --- a/tests/device_selection/test_helper_ml.py +++ b/tests/device_selection/test_helper_ml.py @@ -10,9 +10,12 @@ from __future__ import annotations +import torch from mqt.bench import BenchmarkLevel, get_benchmark +from qiskit import QuantumCircuit from mqt.predictor.ml.helper import ( + create_dag, create_feature_vector, get_openqasm_gates, get_path_training_circuits, @@ -28,6 +31,40 @@ def test_create_feature_vector() -> None: assert feature_vector is not None +def test_create_dag() -> None: + """Test the creation of a DAG.""" + qc = get_benchmark("dj", BenchmarkLevel.INDEP, 3).decompose() + node_vector, edge_index, number_nodes = create_dag(qc) + assert isinstance(node_vector, torch.Tensor) + assert isinstance(edge_index, torch.Tensor) + assert number_nodes > 0 + assert node_vector.shape[0] == number_nodes + assert edge_index.dtype == torch.long + assert edge_index.ndim == 2 + assert edge_index.shape[0] == 2 + + +def test_empty_circuit_dag() -> None: + """Test the creation of a DAG from an empty quantum circuit.""" + qc = QuantumCircuit(2) + + node_vector, edge_index, number_nodes = create_dag(qc) + + # No nodes + assert number_nodes == 0 + + # node_vector empty with shape + assert isinstance(node_vector, torch.Tensor) + assert node_vector.ndim == 2 + assert node_vector.shape[0] == 0 + assert node_vector.shape[1] > 0 + + # edge_index empty (2, 0) and dtype long as in the code + assert isinstance(edge_index, torch.Tensor) + assert edge_index.dtype == torch.long + assert tuple(edge_index.shape) == (2, 0) + + def test_get_openqasm_gates() -> None: """Test the retrieval of the OpenQASM gates.""" assert get_openqasm_gates() is not None diff --git a/tests/device_selection/test_predictor_ml.py b/tests/device_selection/test_predictor_ml.py index db07cd1c4..510f7c1c0 100644 --- a/tests/device_selection/test_predictor_ml.py +++ b/tests/device_selection/test_predictor_ml.py @@ -35,7 +35,12 @@ def path_compiled_circuits() -> Path: return Path("./test_compiled_circuits") -def test_setup_device_predictor_with_prediction(path_uncompiled_circuits: Path, path_compiled_circuits: Path) -> None: +@pytest.mark.parametrize( + ("gnn", "verbose"), [(False, False), (True, False), (True, True)], ids=["rf", "gnn", "gnn_verbose"] +) +def test_setup_device_predictor_with_prediction( + path_uncompiled_circuits: Path, path_compiled_circuits: Path, gnn: bool, verbose: bool +) -> None: """Test the full training pipeline and prediction using a mock device.""" if not path_uncompiled_circuits.exists(): path_uncompiled_circuits.mkdir() @@ -49,24 +54,77 @@ def test_setup_device_predictor_with_prediction(path_uncompiled_circuits: Path, dump(qc, f) device = get_device("ibm_falcon_127") - success = setup_device_predictor( devices=[device], figure_of_merit="expected_fidelity", path_uncompiled_circuits=path_uncompiled_circuits, path_compiled_circuits=path_compiled_circuits, + gnn=gnn, + verbose=verbose, + num_epochs=2, + num_trials=1, ) assert success data_path = get_path_training_data() / "training_data_aggregated" - assert (data_path / "training_data_expected_fidelity.npy").exists() + if gnn: + dataset_dir = data_path / "graph_dataset_expected_fidelity" + assert dataset_dir.exists() + assert dataset_dir.is_dir() + assert any(f.suffix == ".safetensors" for f in dataset_dir.iterdir()) + assert (data_path / "names_list_expected_fidelity.npy").exists() + assert (data_path / "scores_list_expected_fidelity.npy").exists() + else: + assert (data_path / "training_data_expected_fidelity.npy").exists() + 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) + predicted = predict_device_for_figure_of_merit(test_qc, figure_of_merit="expected_fidelity", gnn=gnn) + + assert predicted.description == "ibm_falcon_127" + + +def test_setup_multidevice_predictor_with_prediction_gnn( + path_uncompiled_circuits: Path, path_compiled_circuits: Path +) -> None: + """Test the full training pipeline for GNN on multiple devices.""" + if not path_uncompiled_circuits.exists(): + path_uncompiled_circuits.mkdir() + if not path_compiled_circuits.exists(): + path_compiled_circuits.mkdir() + + for i in range(2, 8): + qc = get_benchmark("ghz", BenchmarkLevel.ALG, i) + path = path_uncompiled_circuits / f"qc{i}.qasm" + with path.open("w", encoding="utf-8") as f: + dump(qc, f) + + device = [get_device("ibm_falcon_127"), get_device("quantinuum_h2_56")] + success = setup_device_predictor( + devices=device, + figure_of_merit="expected_fidelity", + path_uncompiled_circuits=path_uncompiled_circuits, + path_compiled_circuits=path_compiled_circuits, + gnn=True, + verbose=False, + num_epochs=2, + num_trials=1, + ) + assert success + + data_path = get_path_training_data() / "training_data_aggregated" + dataset_dir = data_path / "graph_dataset_expected_fidelity" + assert dataset_dir.exists() + assert dataset_dir.is_dir() + assert any(f.suffix == ".safetensors" for f in dataset_dir.iterdir()) 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) - predicted = predict_device_for_figure_of_merit(test_qc, figure_of_merit="expected_fidelity") + predicted = predict_device_for_figure_of_merit(test_qc, figure_of_merit="expected_fidelity", gnn=True) - assert predicted.description == "ibm_falcon_127" + assert predicted.description in ("ibm_falcon_127", "quantinuum_h2_56") def test_remove_files(path_uncompiled_circuits: Path, path_compiled_circuits: Path) -> None: @@ -85,9 +143,13 @@ def test_remove_files(path_uncompiled_circuits: Path, path_compiled_circuits: Pa data_path = get_path_training_data() / "training_data_aggregated" if data_path.exists(): - for file in data_path.iterdir(): - if file.suffix == ".npy": + for file in list(data_path.iterdir()): + if file.is_file() and file.suffix in (".npy", ".pt", ".safetensors", ".label"): file.unlink() + elif file.is_dir() and file.name.startswith("graph_dataset_"): + for sub in file.iterdir(): + sub.unlink() + file.rmdir() def test_predict_device_for_figure_of_merit_no_suitable_device() -> None: @@ -100,8 +162,9 @@ def test_predict_device_for_figure_of_merit_no_suitable_device() -> None: predict_device_for_figure_of_merit(qc) -def test_get_prepared_training_data_false_input() -> None: +@pytest.mark.parametrize("gnn", [False, True], ids=["rf", "gnn"]) +def test_get_prepared_training_data_false_input(gnn: bool) -> None: """Test the retrieval of prepared training data.""" - pred = Predictor(devices=[], figure_of_merit="expected_fidelity") + pred = Predictor(devices=[], figure_of_merit="expected_fidelity", gnn=gnn) with pytest.raises(FileNotFoundError, match=re.escape("Training data not found.")): pred._get_prepared_training_data() # noqa: SLF001 diff --git a/tests/hellinger_distance/test_estimated_hellinger_distance.py b/tests/hellinger_distance/test_estimated_hellinger_distance.py index 02e5fa670..4d533958e 100644 --- a/tests/hellinger_distance/test_estimated_hellinger_distance.py +++ b/tests/hellinger_distance/test_estimated_hellinger_distance.py @@ -21,31 +21,38 @@ from mqt.bench.targets import get_available_device_names, get_device from qiskit import QuantumCircuit from qiskit.qasm2 import dump +from torch_geometric.data import Batch, Data from mqt.predictor.hellinger import calc_device_specific_features, hellinger_distance from mqt.predictor.ml import Predictor as ml_Predictor from mqt.predictor.ml import predict_device_for_figure_of_merit -from mqt.predictor.ml.helper import TrainingData, get_path_training_data +from mqt.predictor.ml.helper import TrainingData, create_dag, get_path_training_data from mqt.predictor.rl import Predictor as rl_Predictor if TYPE_CHECKING: + from collections.abc import Iterator + from qiskit.transpiler import Target -@pytest.fixture +torch = pytest.importorskip("torch", exc_type=ModuleNotFoundError) +pytest.importorskip("torch_geometric", exc_type=ModuleNotFoundError) + + +@pytest.fixture(scope="module") def source_path() -> Path: """Return the source path.""" return Path("./test_uncompiled_circuits") -@pytest.fixture +@pytest.fixture(scope="module") def target_path() -> Path: """Return the target path.""" return Path("./test_compiled_circuits") -@pytest.fixture -def device() -> Path: +@pytest.fixture(scope="module") +def device() -> Target: """Return the target device.""" return get_device("quantinuum_h2_56") @@ -151,9 +158,12 @@ def test_hellinger_distance_error() -> None: hellinger_distance(p=invalid, q=valid) -def test_train_random_forest_regressor_and_predict(device: Target) -> None: - """Test the training of the random forest regressor. The trained model is saved and used in the following tests.""" - # Setup the training environment +@pytest.mark.parametrize( + ("model_type", "verbose"), [("rf", False), ("gnn", False), ("gnn", True)], ids=["rf", "gnn", "gnn_verbose"] +) +def test_train_model_and_predict(device: Target, model_type: str, verbose: bool) -> None: + """Test the training of the RF and GNN models. The trained models are saved and used in the following tests.""" + gnn = model_type == "gnn" n_circuits = 20 qc = QuantumCircuit(device.num_qubits) @@ -161,8 +171,12 @@ def test_train_random_forest_regressor_and_predict(device: Target) -> None: qc.cz(0, i) # 1. Feature Extraction - feature_vector = calc_device_specific_features(qc, device) - feature_vector_list = np.tile(feature_vector, (n_circuits, 1)) + if not gnn: + feature_vector = calc_device_specific_features(qc, device) + feature_vector_list = np.tile(feature_vector, (n_circuits, 1)) + else: + x, edge_index, number_of_gates = create_dag(qc) + training_sample = [(x, edge_index, number_of_gates)] * n_circuits # 2. Label Generation rng = np.random.default_rng() @@ -171,20 +185,61 @@ def test_train_random_forest_regressor_and_predict(device: Target) -> None: noiseless = np.zeros_like(noisy) noiseless[0] = 1.0 distance_label = hellinger_distance(noisy, noiseless) - labels_list = np.full(n_circuits, distance_label) - training_data = TrainingData(X_train=feature_vector_list, y_train=labels_list) + labels_list = [distance_label] * n_circuits + # Creation of the training data object, which is different for RF and GNN models + if not gnn: + labels_array = np.asarray(labels_list, dtype=np.float64) + training_data = TrainingData(X_train=feature_vector_list, y_train=labels_array) + else: + training_data_list = [] + for i in range(n_circuits): + x, edge_idx, n_nodes = training_sample[i] + gnn_training_sample = Data( + x=x, + y=torch.tensor(labels_list[i], dtype=torch.float32), + edge_index=edge_idx, + num_nodes=n_nodes, + ) + training_data_list.append(gnn_training_sample) + training_data = TrainingData(X_train=training_data_list, y_train=np.asarray(labels_list, dtype=np.float32)) # 3. Model Training - pred = ml_Predictor(figure_of_merit="hellinger_distance", devices=[device]) - trained_model = pred.train_random_forest_model(training_data) - - assert np.isclose(trained_model.predict([feature_vector]), distance_label) - - -def test_train_and_qcompile_with_hellinger_model(source_path: Path, target_path: Path, device: Target) -> None: - """Test the entire predictor toolchain with the Hellinger distance model that was trained in the previous test.""" + pred = ml_Predictor(figure_of_merit="hellinger_distance", devices=[device], gnn=gnn) + if gnn: + trained_model = pred.train_gnn_model(training_data, num_epochs=5, patience=2, verbose=verbose) + else: + trained_model = pred.train_random_forest_model(training_data) + + if not gnn: + from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor # noqa: PLC0415 + + assert isinstance(trained_model, (RandomForestRegressor, RandomForestClassifier)) + assert np.isclose(trained_model.predict([feature_vector]), distance_label) + else: + import torch.nn as nn # noqa: PLC0415 + + assert isinstance(trained_model, nn.Module) + trained_model.eval() + model_device = next(trained_model.parameters()).device + with torch.no_grad(): + batch = Batch.from_data_list(training_data.X_train).to(model_device) # ty: ignore[invalid-argument-type,unresolved-attribute] + out = trained_model(batch) + out = out.squeeze(-1) + predicted_values = out.cpu().numpy() + labels = np.asarray(labels_list, dtype=np.float32) + # interested just in the shape not the actual values, since the model is not really trained + assert predicted_values.shape == labels.shape + + +@pytest.mark.parametrize( + ("model_type", "verbose"), [("rf", False), ("gnn", False), ("gnn", True)], ids=["rf", "gnn", "gnn_verbose"] +) +def test_train_and_qcompile_with_hellinger_model( + source_path: Path, target_path: Path, device: Target, model_type: str, verbose: bool +) -> None: + """Test the entire predictor toolchain for estimating the Hellinger distance with both RF and GNN.""" figure_of_merit = "estimated_hellinger_distance" - + gnn = model_type == "gnn" with warnings.catch_warnings(): warnings.filterwarnings( "ignore", @@ -202,7 +257,7 @@ def test_train_and_qcompile_with_hellinger_model(source_path: Path, target_path: ) # 2. Setup and train the machine learning model for device selection - ml_predictor = ml_Predictor(devices=[device], figure_of_merit=figure_of_merit) + ml_predictor = ml_Predictor(devices=[device], figure_of_merit=figure_of_merit, gnn=gnn) # Prepare uncompiled circuits if not source_path.exists(): @@ -222,22 +277,35 @@ def test_train_and_qcompile_with_hellinger_model(source_path: Path, target_path: ) # Generate training data from the compiled circuits - ml_predictor.generate_training_data(path_uncompiled_circuits=source_path, path_compiled_circuits=target_path) - - for file in [ - "training_data_estimated_hellinger_distance.npy", - "names_list_estimated_hellinger_distance.npy", - "scores_list_estimated_hellinger_distance.npy", - ]: - path = get_path_training_data() / "training_data_aggregated" / file - assert path.exists() + ml_predictor.generate_training_data( + path_uncompiled_circuits=source_path, path_compiled_circuits=target_path, num_workers=1 + ) + if gnn: + dataset_dir = ( + get_path_training_data() / "training_data_aggregated" / "graph_dataset_estimated_hellinger_distance" + ) + assert dataset_dir.exists() + assert dataset_dir.is_dir() + # check for controlling that the name starts with a number and ends with .safetensors asdefined in the predictor + assert any(f.is_file() and f.suffix == ".safetensors" and f.stem.isdigit() for f in dataset_dir.iterdir()) + else: + for file in [ + "training_data_estimated_hellinger_distance.npy", + "names_list_estimated_hellinger_distance.npy", + "scores_list_estimated_hellinger_distance.npy", + ]: + path = get_path_training_data() / "training_data_aggregated" / file + assert path.exists() # Train the ML model - ml_predictor.train_random_forest_model() + if gnn: + ml_predictor.train_gnn_model(verbose=verbose) + else: + ml_predictor.train_random_forest_model() qc = get_benchmark("ghz", BenchmarkLevel.ALG, 3) # Test the prediction - predicted_dev = predict_device_for_figure_of_merit(qc, figure_of_merit) + predicted_dev = predict_device_for_figure_of_merit(qc, figure_of_merit, gnn=gnn) assert predicted_dev.description in get_available_device_names() @@ -254,3 +322,53 @@ def test_predict_device_for_estimated_hellinger_distance_no_device_provided() -> ValueError, match=re.escape("A single device must be provided for Hellinger distance model training.") ): pred.train_random_forest_model(training_data) + + +@pytest.fixture(scope="module", autouse=True) +def cleanup_artifacts(source_path: Path, target_path: Path) -> Iterator[None]: + """Remove files and directories created during testing in this module.""" + # NOTE: This cleanup is intentionally conservative: we only delete *.qasm files that + # tests generate and then try to remove the directory. + # We do NOT use shutil.rmtree() to avoid deleting unexpected/non-test files. + # This means Path.rmdir() can fail if extra files/dirs exist (e.g., partial runs or + # platform artifacts). If this becomes flaky, switch to shutil.rmtree(). + + # Let the tests run + yield + + # Cleanup compiled/uncompiled circuit files and directories + if source_path.exists(): + for file in source_path.iterdir(): + if file.suffix == ".qasm": + file.unlink() + source_path.rmdir() + + if target_path.exists(): + for file in target_path.iterdir(): + if file.suffix == ".qasm": + file.unlink() + target_path.rmdir() + + # Cleanup training data artifacts + data_path = get_path_training_data() / "training_data_aggregated" + if data_path.exists(): + for file in list(data_path.iterdir()): + if file.is_file() and file.suffix in (".npy", ".pt", ".safetensors", ".label"): + file.unlink() + elif file.is_dir() and file.name.startswith("graph_dataset_"): + for sub in file.iterdir(): + if sub.is_file(): + sub.unlink() + elif sub.is_dir(): + # If nested dirs appear, consider switching to shutil.rmtree as noted above + for nested in sub.iterdir(): + nested.unlink() + sub.rmdir() + file.rmdir() + + # Cleanup trained model artifacts + model_path = get_path_training_data() / "trained_model" + if model_path.exists(): + for file in model_path.iterdir(): + if file.suffix in (".joblib", ".pth", ".json"): + file.unlink() diff --git a/uv.lock b/uv.lock index 15a83f154..935ee4740 100644 --- a/uv.lock +++ b/uv.lock @@ -55,14 +55,14 @@ name = "aiohttp" version = "3.13.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiohappyeyeballs", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, - { name = "aiosignal", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, { name = "async-timeout", marker = "python_full_version < '3.11'" }, - { name = "attrs", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, - { name = "frozenlist", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, - { name = "multidict", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, - { name = "propcache", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, - { name = "yarl", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } wheels = [ @@ -187,7 +187,7 @@ name = "aiosignal" version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "frozenlist", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "frozenlist" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } @@ -204,6 +204,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, ] +[[package]] +name = "alembic" +version = "1.18.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725, upload-time = "2026-02-10T16:00:47.195Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893, upload-time = "2026-02-10T16:00:49.997Z" }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -242,14 +257,14 @@ wheels = [ [[package]] name = "astroid" -version = "4.1.1" +version = "4.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/4c/569eefb533ce71bc9f4f12a4a0d7f0ba27e500681dec1312d4657e849d20/astroid-4.1.1.tar.gz", hash = "sha256:445d831fe785df8c670bbb46b900b8424b82f85b4af187103f71a63a63ebed43", size = 412522, upload-time = "2026-02-23T02:36:30.315Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/fd/24475b7cfb70298e8921bc077adb46a3fe77887422545d8a061573e130ee/astroid-4.1.2.tar.gz", hash = "sha256:d6c4a52bfcda4bbeb7359dead642b0248b90f7d9a07e690230bd86fefd6d37f1", size = 414896, upload-time = "2026-03-22T19:16:42.075Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/53/bac4724684064bfee95ece0bb6caf3887e509006845e25388a12cac26d0c/astroid-4.1.1-py3-none-any.whl", hash = "sha256:6b28522096f7e7a36ffcf3be60e77de15e2411ab3a713184beac33fb8f20c0c9", size = 279273, upload-time = "2026-02-23T02:36:28.676Z" }, + { url = "https://files.pythonhosted.org/packages/54/97/4ee9b0438e85bf0a808a89ef0be357319252ab27e1b313ae0aef7aeaa5a6/astroid-4.1.2-py3-none-any.whl", hash = "sha256:21312e682c0866dc5a309ee57e4b88ea92751b9955a58b1c31371cbbeb088707", size = 279956, upload-time = "2026-03-22T19:16:40.062Z" }, ] [[package]] @@ -272,11 +287,11 @@ wheels = [ [[package]] name = "attrs" -version = "25.4.0" +version = "26.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] [[package]] @@ -342,11 +357,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.1.4" +version = "2026.2.25" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, ] [[package]] @@ -433,91 +448,107 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/21/a2b1505639008ba2e6ef03733a81fc6cfd6a07ea6139a2b76421230b8dad/charset_normalizer-3.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4167a621a9a1a986c73777dbc15d4b5eac8ac5c10393374109a343d4013ec765", size = 283319, upload-time = "2026-03-06T06:00:26.433Z" }, - { url = "https://files.pythonhosted.org/packages/70/67/df234c29b68f4e1e095885c9db1cb4b69b8aba49cf94fac041db4aaf1267/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f64c6bf8f32f9133b668c7f7a7cbdbc453412bc95ecdbd157f3b1e377a92990", size = 189974, upload-time = "2026-03-06T06:00:28.222Z" }, - { url = "https://files.pythonhosted.org/packages/df/7f/fc66af802961c6be42e2c7b69c58f95cbd1f39b0e81b3365d8efe2a02a04/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:568e3c34b58422075a1b49575a6abc616d9751b4d61b23f712e12ebb78fe47b2", size = 207866, upload-time = "2026-03-06T06:00:29.769Z" }, - { url = "https://files.pythonhosted.org/packages/c9/23/404eb36fac4e95b833c50e305bba9a241086d427bb2167a42eac7c4f7da4/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:036c079aa08a6a592b82487f97c60b439428320ed1b2ea0b3912e99d30c77765", size = 203239, upload-time = "2026-03-06T06:00:31.086Z" }, - { url = "https://files.pythonhosted.org/packages/4b/2f/8a1d989bfadd120c90114ab33e0d2a0cbde05278c1fc15e83e62d570f50a/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:340810d34ef83af92148e96e3e44cb2d3f910d2bf95e5618a5c467d9f102231d", size = 196529, upload-time = "2026-03-06T06:00:32.608Z" }, - { url = "https://files.pythonhosted.org/packages/a5/0c/c75f85ff7ca1f051958bb518cd43922d86f576c03947a050fbedfdfb4f15/charset_normalizer-3.4.5-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:cd2d0f0ec9aa977a27731a3209ebbcacebebaf41f902bd453a928bfd281cf7f8", size = 184152, upload-time = "2026-03-06T06:00:33.93Z" }, - { url = "https://files.pythonhosted.org/packages/f9/20/4ed37f6199af5dde94d4aeaf577f3813a5ec6635834cda1d957013a09c76/charset_normalizer-3.4.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b362bcd27819f9c07cbf23db4e0e8cd4b44c5ecd900c2ff907b2b92274a7412", size = 195226, upload-time = "2026-03-06T06:00:35.469Z" }, - { url = "https://files.pythonhosted.org/packages/28/31/7ba1102178cba7c34dcc050f43d427172f389729e356038f0726253dd914/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:77be992288f720306ab4108fe5c74797de327f3248368dfc7e1a916d6ed9e5a2", size = 192933, upload-time = "2026-03-06T06:00:36.83Z" }, - { url = "https://files.pythonhosted.org/packages/4b/23/f86443ab3921e6a60b33b93f4a1161222231f6c69bc24fb18f3bee7b8518/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:8b78d8a609a4b82c273257ee9d631ded7fac0d875bdcdccc109f3ee8328cfcb1", size = 185647, upload-time = "2026-03-06T06:00:38.367Z" }, - { url = "https://files.pythonhosted.org/packages/82/44/08b8be891760f1f5a6d23ce11d6d50c92981603e6eb740b4f72eea9424e2/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ba20bdf69bd127f66d0174d6f2a93e69045e0b4036dc1ca78e091bcc765830c4", size = 209533, upload-time = "2026-03-06T06:00:41.931Z" }, - { url = "https://files.pythonhosted.org/packages/3b/5f/df114f23406199f8af711ddccfbf409ffbc5b7cdc18fa19644997ff0c9bb/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:76a9d0de4d0eab387822e7b35d8f89367dd237c72e82ab42b9f7bf5e15ada00f", size = 195901, upload-time = "2026-03-06T06:00:43.978Z" }, - { url = "https://files.pythonhosted.org/packages/07/83/71ef34a76fe8aa05ff8f840244bda2d61e043c2ef6f30d200450b9f6a1be/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8fff79bf5978c693c9b1a4d71e4a94fddfb5fe744eb062a318e15f4a2f63a550", size = 204950, upload-time = "2026-03-06T06:00:45.202Z" }, - { url = "https://files.pythonhosted.org/packages/58/40/0253be623995365137d7dc68e45245036207ab2227251e69a3d93ce43183/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c7e84e0c0005e3bdc1a9211cd4e62c78ba80bc37b2365ef4410cd2007a9047f2", size = 198546, upload-time = "2026-03-06T06:00:46.481Z" }, - { url = "https://files.pythonhosted.org/packages/ed/5c/5f3cb5b259a130895ef5ae16b38eaf141430fa3f7af50cd06c5d67e4f7b2/charset_normalizer-3.4.5-cp310-cp310-win32.whl", hash = "sha256:58ad8270cfa5d4bef1bc85bd387217e14ff154d6630e976c6f56f9a040757475", size = 132516, upload-time = "2026-03-06T06:00:47.924Z" }, - { url = "https://files.pythonhosted.org/packages/a5/c3/84fb174e7770f2df2e1a2115090771bfbc2227fb39a765c6d00568d1aab4/charset_normalizer-3.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:02a9d1b01c1e12c27883b0c9349e0bcd9ae92e727ff1a277207e1a262b1cbf05", size = 142906, upload-time = "2026-03-06T06:00:49.389Z" }, - { url = "https://files.pythonhosted.org/packages/d7/b2/6f852f8b969f2cbd0d4092d2e60139ab1af95af9bb651337cae89ec0f684/charset_normalizer-3.4.5-cp310-cp310-win_arm64.whl", hash = "sha256:039215608ac7b358c4da0191d10fc76868567fbf276d54c14721bdedeb6de064", size = 133258, upload-time = "2026-03-06T06:00:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/8f/9e/bcec3b22c64ecec47d39bf5167c2613efd41898c019dccd4183f6aa5d6a7/charset_normalizer-3.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:610f72c0ee565dfb8ae1241b666119582fdbfe7c0975c175be719f940e110694", size = 279531, upload-time = "2026-03-06T06:00:52.252Z" }, - { url = "https://files.pythonhosted.org/packages/58/12/81fd25f7e7078ab5d1eedbb0fac44be4904ae3370a3bf4533c8f2d159acd/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60d68e820af339df4ae8358c7a2e7596badeb61e544438e489035f9fbf3246a5", size = 188006, upload-time = "2026-03-06T06:00:53.8Z" }, - { url = "https://files.pythonhosted.org/packages/ae/6e/f2d30e8c27c1b0736a6520311982cf5286cfc7f6cac77d7bc1325e3a23f2/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b473fc8dca1c3ad8559985794815f06ca3fc71942c969129070f2c3cdf7281", size = 205085, upload-time = "2026-03-06T06:00:55.311Z" }, - { url = "https://files.pythonhosted.org/packages/d0/90/d12cefcb53b5931e2cf792a33718d7126efb116a320eaa0742c7059a95e4/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d4eb8ac7469b2a5d64b5b8c04f84d8bf3ad340f4514b98523805cbf46e3b3923", size = 200545, upload-time = "2026-03-06T06:00:56.532Z" }, - { url = "https://files.pythonhosted.org/packages/03/f4/44d3b830a20e89ff82a3134912d9a1cf6084d64f3b95dcad40f74449a654/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bcb3227c3d9aaf73eaaab1db7ccd80a8995c509ee9941e2aae060ca6e4e5d81", size = 193863, upload-time = "2026-03-06T06:00:57.823Z" }, - { url = "https://files.pythonhosted.org/packages/25/4b/f212119c18a6320a9d4a730d1b4057875cdeabf21b3614f76549042ef8a8/charset_normalizer-3.4.5-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:75ee9c1cce2911581a70a3c0919d8bccf5b1cbc9b0e5171400ec736b4b569497", size = 181827, upload-time = "2026-03-06T06:00:59.323Z" }, - { url = "https://files.pythonhosted.org/packages/74/00/b26158e48b425a202a92965f8069e8a63d9af1481dfa206825d7f74d2a3c/charset_normalizer-3.4.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d1401945cb77787dbd3af2446ff2d75912327c4c3a1526ab7955ecf8600687c", size = 191085, upload-time = "2026-03-06T06:01:00.546Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c2/1c1737bf6fd40335fe53d28fe49afd99ee4143cc57a845e99635ce0b9b6d/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a45e504f5e1be0bd385935a8e1507c442349ca36f511a47057a71c9d1d6ea9e", size = 190688, upload-time = "2026-03-06T06:01:02.479Z" }, - { url = "https://files.pythonhosted.org/packages/5a/3d/abb5c22dc2ef493cd56522f811246a63c5427c08f3e3e50ab663de27fcf4/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e09f671a54ce70b79a1fc1dc6da3072b7ef7251fadb894ed92d9aa8218465a5f", size = 183077, upload-time = "2026-03-06T06:01:04.231Z" }, - { url = "https://files.pythonhosted.org/packages/44/33/5298ad4d419a58e25b3508e87f2758d1442ff00c2471f8e0403dab8edad5/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d01de5e768328646e6a3fa9e562706f8f6641708c115c62588aef2b941a4f88e", size = 206706, upload-time = "2026-03-06T06:01:05.773Z" }, - { url = "https://files.pythonhosted.org/packages/7b/17/51e7895ac0f87c3b91d276a449ef09f5532a7529818f59646d7a55089432/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:131716d6786ad5e3dc542f5cc6f397ba3339dc0fb87f87ac30e550e8987756af", size = 191665, upload-time = "2026-03-06T06:01:07.473Z" }, - { url = "https://files.pythonhosted.org/packages/90/8f/cce9adf1883e98906dbae380d769b4852bb0fa0004bc7d7a2243418d3ea8/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a374cc0b88aa710e8865dc1bd6edb3743c59f27830f0293ab101e4cf3ce9f85", size = 201950, upload-time = "2026-03-06T06:01:08.973Z" }, - { url = "https://files.pythonhosted.org/packages/08/ca/bce99cd5c397a52919e2769d126723f27a4c037130374c051c00470bcd38/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d31f0d1671e1534e395f9eb84a68e0fb670e1edb1fe819a9d7f564ae3bc4e53f", size = 195830, upload-time = "2026-03-06T06:01:10.155Z" }, - { url = "https://files.pythonhosted.org/packages/87/4f/2e3d023a06911f1281f97b8f036edc9872167036ca6f55cc874a0be6c12c/charset_normalizer-3.4.5-cp311-cp311-win32.whl", hash = "sha256:cace89841c0599d736d3d74a27bc5821288bb47c5441923277afc6059d7fbcb4", size = 132029, upload-time = "2026-03-06T06:01:11.706Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1f/a853b73d386521fd44b7f67ded6b17b7b2367067d9106a5c4b44f9a34274/charset_normalizer-3.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:f8102ae93c0bc863b1d41ea0f4499c20a83229f52ed870850892df555187154a", size = 142404, upload-time = "2026-03-06T06:01:12.865Z" }, - { url = "https://files.pythonhosted.org/packages/b4/10/dba36f76b71c38e9d391abe0fd8a5b818790e053c431adecfc98c35cd2a9/charset_normalizer-3.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:ed98364e1c262cf5f9363c3eca8c2df37024f52a8fa1180a3610014f26eac51c", size = 132796, upload-time = "2026-03-06T06:01:14.106Z" }, - { url = "https://files.pythonhosted.org/packages/9c/b6/9ee9c1a608916ca5feae81a344dffbaa53b26b90be58cc2159e3332d44ec/charset_normalizer-3.4.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade", size = 280976, upload-time = "2026-03-06T06:01:15.276Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d8/a54f7c0b96f1df3563e9190f04daf981e365a9b397eedfdfb5dbef7e5c6c/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0294916d6ccf2d069727d65973c3a1ca477d68708db25fd758dd28b0827cff54", size = 189356, upload-time = "2026-03-06T06:01:16.511Z" }, - { url = "https://files.pythonhosted.org/packages/42/69/2bf7f76ce1446759a5787cb87d38f6a61eb47dbbdf035cfebf6347292a65/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc57a0baa3eeedd99fafaef7511b5a6ef4581494e8168ee086031744e2679467", size = 206369, upload-time = "2026-03-06T06:01:17.853Z" }, - { url = "https://files.pythonhosted.org/packages/10/9c/949d1a46dab56b959d9a87272482195f1840b515a3380e39986989a893ae/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ed1a9a204f317ef879b32f9af507d47e49cd5e7f8e8d5d96358c98373314fc60", size = 203285, upload-time = "2026-03-06T06:01:19.473Z" }, - { url = "https://files.pythonhosted.org/packages/67/5c/ae30362a88b4da237d71ea214a8c7eb915db3eec941adda511729ac25fa2/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad83b8f9379176c841f8865884f3514d905bcd2a9a3b210eaa446e7d2223e4d", size = 196274, upload-time = "2026-03-06T06:01:20.728Z" }, - { url = "https://files.pythonhosted.org/packages/b2/07/c9f2cb0e46cb6d64fdcc4f95953747b843bb2181bda678dc4e699b8f0f9a/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:a118e2e0b5ae6b0120d5efa5f866e58f2bb826067a646431da4d6a2bdae7950e", size = 184715, upload-time = "2026-03-06T06:01:22.194Z" }, - { url = "https://files.pythonhosted.org/packages/36/64/6b0ca95c44fddf692cd06d642b28f63009d0ce325fad6e9b2b4d0ef86a52/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:754f96058e61a5e22e91483f823e07df16416ce76afa4ebf306f8e1d1296d43f", size = 193426, upload-time = "2026-03-06T06:01:23.795Z" }, - { url = "https://files.pythonhosted.org/packages/50/bc/a730690d726403743795ca3f5bb2baf67838c5fea78236098f324b965e40/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c300cefd9b0970381a46394902cd18eaf2aa00163f999590ace991989dcd0fc", size = 191780, upload-time = "2026-03-06T06:01:25.053Z" }, - { url = "https://files.pythonhosted.org/packages/97/4f/6c0bc9af68222b22951552d73df4532b5be6447cee32d58e7e8c74ecbb7b/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c108f8619e504140569ee7de3f97d234f0fbae338a7f9f360455071ef9855a95", size = 185805, upload-time = "2026-03-06T06:01:26.294Z" }, - { url = "https://files.pythonhosted.org/packages/dd/b9/a523fb9b0ee90814b503452b2600e4cbc118cd68714d57041564886e7325/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d1028de43596a315e2720a9849ee79007ab742c06ad8b45a50db8cdb7ed4a82a", size = 208342, upload-time = "2026-03-06T06:01:27.55Z" }, - { url = "https://files.pythonhosted.org/packages/4d/61/c59e761dee4464050713e50e27b58266cc8e209e518c0b378c1580c959ba/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:19092dde50335accf365cce21998a1c6dd8eafd42c7b226eb54b2747cdce2fac", size = 193661, upload-time = "2026-03-06T06:01:29.051Z" }, - { url = "https://files.pythonhosted.org/packages/1c/43/729fa30aad69783f755c5ad8649da17ee095311ca42024742701e202dc59/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4354e401eb6dab9aed3c7b4030514328a6c748d05e1c3e19175008ca7de84fb1", size = 204819, upload-time = "2026-03-06T06:01:30.298Z" }, - { url = "https://files.pythonhosted.org/packages/87/33/d9b442ce5a91b96fc0840455a9e49a611bbadae6122778d0a6a79683dd31/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a68766a3c58fde7f9aaa22b3786276f62ab2f594efb02d0a1421b6282e852e98", size = 198080, upload-time = "2026-03-06T06:01:31.478Z" }, - { url = "https://files.pythonhosted.org/packages/56/5a/b8b5a23134978ee9885cee2d6995f4c27cc41f9baded0a9685eabc5338f0/charset_normalizer-3.4.5-cp312-cp312-win32.whl", hash = "sha256:1827734a5b308b65ac54e86a618de66f935a4f63a8a462ff1e19a6788d6c2262", size = 132630, upload-time = "2026-03-06T06:01:33.056Z" }, - { url = "https://files.pythonhosted.org/packages/70/53/e44a4c07e8904500aec95865dc3f6464dc3586a039ef0df606eb3ac38e35/charset_normalizer-3.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:728c6a963dfab66ef865f49286e45239384249672cd598576765acc2a640a636", size = 142856, upload-time = "2026-03-06T06:01:34.489Z" }, - { url = "https://files.pythonhosted.org/packages/ea/aa/c5628f7cad591b1cf45790b7a61483c3e36cf41349c98af7813c483fd6e8/charset_normalizer-3.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:75dfd1afe0b1647449e852f4fb428195a7ed0588947218f7ba929f6538487f02", size = 132982, upload-time = "2026-03-06T06:01:35.641Z" }, - { url = "https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23", size = 280788, upload-time = "2026-03-06T06:01:37.126Z" }, - { url = "https://files.pythonhosted.org/packages/0e/09/6003e7ffeb90cc0560da893e3208396a44c210c5ee42efff539639def59b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:165c7b21d19365464e8f70e5ce5e12524c58b48c78c1f5a57524603c1ab003f8", size = 188890, upload-time = "2026-03-06T06:01:38.73Z" }, - { url = "https://files.pythonhosted.org/packages/42/1e/02706edf19e390680daa694d17e2b8eab4b5f7ac285e2a51168b4b22ee6b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:28269983f25a4da0425743d0d257a2d6921ea7d9b83599d4039486ec5b9f911d", size = 206136, upload-time = "2026-03-06T06:01:40.016Z" }, - { url = "https://files.pythonhosted.org/packages/c7/87/942c3def1b37baf3cf786bad01249190f3ca3d5e63a84f831e704977de1f/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d27ce22ec453564770d29d03a9506d449efbb9fa13c00842262b2f6801c48cce", size = 202551, upload-time = "2026-03-06T06:01:41.522Z" }, - { url = "https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819", size = 195572, upload-time = "2026-03-06T06:01:43.208Z" }, - { url = "https://files.pythonhosted.org/packages/20/ea/dfb1792a8050a8e694cfbde1570ff97ff74e48afd874152d38163d1df9ae/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:c23eb3263356d94858655b3e63f85ac5d50970c6e8febcdde7830209139cc37d", size = 184438, upload-time = "2026-03-06T06:01:44.755Z" }, - { url = "https://files.pythonhosted.org/packages/72/12/c281e2067466e3ddd0595bfaea58a6946765ace5c72dfa3edc2f5f118026/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e6302ca4ae283deb0af68d2fbf467474b8b6aedcd3dab4db187e07f94c109763", size = 193035, upload-time = "2026-03-06T06:01:46.051Z" }, - { url = "https://files.pythonhosted.org/packages/ba/4f/3792c056e7708e10464bad0438a44708886fb8f92e3c3d29ec5e2d964d42/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e51ae7d81c825761d941962450f50d041db028b7278e7b08930b4541b3e45cb9", size = 191340, upload-time = "2026-03-06T06:01:47.547Z" }, - { url = "https://files.pythonhosted.org/packages/e7/86/80ddba897127b5c7a9bccc481b0cd36c8fefa485d113262f0fe4332f0bf4/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:597d10dec876923e5c59e48dbd366e852eacb2b806029491d307daea6b917d7c", size = 185464, upload-time = "2026-03-06T06:01:48.764Z" }, - { url = "https://files.pythonhosted.org/packages/4d/00/b5eff85ba198faacab83e0e4b6f0648155f072278e3b392a82478f8b988b/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5cffde4032a197bd3b42fd0b9509ec60fb70918d6970e4cc773f20fc9180ca67", size = 208014, upload-time = "2026-03-06T06:01:50.371Z" }, - { url = "https://files.pythonhosted.org/packages/c8/11/d36f70be01597fd30850dde8a1269ebc8efadd23ba5785808454f2389bde/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2da4eedcb6338e2321e831a0165759c0c620e37f8cd044a263ff67493be8ffb3", size = 193297, upload-time = "2026-03-06T06:01:51.933Z" }, - { url = "https://files.pythonhosted.org/packages/1a/1d/259eb0a53d4910536c7c2abb9cb25f4153548efb42800c6a9456764649c0/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:65a126fb4b070d05340a84fc709dd9e7c75d9b063b610ece8a60197a291d0adf", size = 204321, upload-time = "2026-03-06T06:01:53.887Z" }, - { url = "https://files.pythonhosted.org/packages/84/31/faa6c5b9d3688715e1ed1bb9d124c384fe2fc1633a409e503ffe1c6398c1/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7a80a9242963416bd81f99349d5f3fce1843c303bd404f204918b6d75a75fd6", size = 197509, upload-time = "2026-03-06T06:01:56.439Z" }, - { url = "https://files.pythonhosted.org/packages/fd/a5/c7d9dd1503ffc08950b3260f5d39ec2366dd08254f0900ecbcf3a6197c7c/charset_normalizer-3.4.5-cp313-cp313-win32.whl", hash = "sha256:f1d725b754e967e648046f00c4facc42d414840f5ccc670c5670f59f83693e4f", size = 132284, upload-time = "2026-03-06T06:01:57.812Z" }, - { url = "https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7", size = 142630, upload-time = "2026-03-06T06:01:59.062Z" }, - { url = "https://files.pythonhosted.org/packages/31/41/1c4b7cc9f13bd9d369ce3bc993e13d374ce25fa38a2663644283ecf422c1/charset_normalizer-3.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:93b3b2cc5cf1b8743660ce77a4f45f3f6d1172068207c1defc779a36eea6bb36", size = 133254, upload-time = "2026-03-06T06:02:00.281Z" }, - { url = "https://files.pythonhosted.org/packages/43/be/0f0fd9bb4a7fa4fb5067fb7d9ac693d4e928d306f80a0d02bde43a7c4aee/charset_normalizer-3.4.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8197abe5ca1ffb7d91e78360f915eef5addff270f8a71c1fc5be24a56f3e4873", size = 280232, upload-time = "2026-03-06T06:02:01.508Z" }, - { url = "https://files.pythonhosted.org/packages/28/02/983b5445e4bef49cd8c9da73a8e029f0825f39b74a06d201bfaa2e55142a/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2aecdb364b8a1802afdc7f9327d55dad5366bc97d8502d0f5854e50712dbc5f", size = 189688, upload-time = "2026-03-06T06:02:02.857Z" }, - { url = "https://files.pythonhosted.org/packages/d0/88/152745c5166437687028027dc080e2daed6fe11cfa95a22f4602591c42db/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a66aa5022bf81ab4b1bebfb009db4fd68e0c6d4307a1ce5ef6a26e5878dfc9e4", size = 206833, upload-time = "2026-03-06T06:02:05.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0f/ebc15c8b02af2f19be9678d6eed115feeeccc45ce1f4b098d986c13e8769/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d77f97e515688bd615c1d1f795d540f32542d514242067adcb8ef532504cb9ee", size = 202879, upload-time = "2026-03-06T06:02:06.446Z" }, - { url = "https://files.pythonhosted.org/packages/38/9c/71336bff6934418dc8d1e8a1644176ac9088068bc571da612767619c97b3/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01a1ed54b953303ca7e310fafe0fe347aab348bd81834a0bcd602eb538f89d66", size = 195764, upload-time = "2026-03-06T06:02:08.763Z" }, - { url = "https://files.pythonhosted.org/packages/b7/95/ce92fde4f98615661871bc282a856cf9b8a15f686ba0af012984660d480b/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:b2d37d78297b39a9eb9eb92c0f6df98c706467282055419df141389b23f93362", size = 183728, upload-time = "2026-03-06T06:02:10.137Z" }, - { url = "https://files.pythonhosted.org/packages/1c/e7/f5b4588d94e747ce45ae680f0f242bc2d98dbd4eccfab73e6160b6893893/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e71bbb595973622b817c042bd943c3f3667e9c9983ce3d205f973f486fec98a7", size = 192937, upload-time = "2026-03-06T06:02:11.663Z" }, - { url = "https://files.pythonhosted.org/packages/f9/29/9d94ed6b929bf9f48bf6ede6e7474576499f07c4c5e878fb186083622716/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cd966c2559f501c6fd69294d082c2934c8dd4719deb32c22961a5ac6db0df1d", size = 192040, upload-time = "2026-03-06T06:02:13.489Z" }, - { url = "https://files.pythonhosted.org/packages/15/d2/1a093a1cf827957f9445f2fe7298bcc16f8fc5e05c1ed2ad1af0b239035e/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d5e52d127045d6ae01a1e821acfad2f3a1866c54d0e837828538fabe8d9d1bd6", size = 184107, upload-time = "2026-03-06T06:02:14.83Z" }, - { url = "https://files.pythonhosted.org/packages/0f/7d/82068ce16bd36135df7b97f6333c5d808b94e01d4599a682e2337ed5fd14/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:30a2b1a48478c3428d047ed9690d57c23038dac838a87ad624c85c0a78ebeb39", size = 208310, upload-time = "2026-03-06T06:02:16.165Z" }, - { url = "https://files.pythonhosted.org/packages/84/4e/4dfb52307bb6af4a5c9e73e482d171b81d36f522b21ccd28a49656baa680/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d8ed79b8f6372ca4254955005830fd61c1ccdd8c0fac6603e2c145c61dd95db6", size = 192918, upload-time = "2026-03-06T06:02:18.144Z" }, - { url = "https://files.pythonhosted.org/packages/08/a4/159ff7da662cf7201502ca89980b8f06acf3e887b278956646a8aeb178ab/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c5af897b45fa606b12464ccbe0014bbf8c09191e0a66aab6aa9d5cf6e77e0c94", size = 204615, upload-time = "2026-03-06T06:02:19.821Z" }, - { url = "https://files.pythonhosted.org/packages/d6/62/0dd6172203cb6b429ffffc9935001fde42e5250d57f07b0c28c6046deb6b/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1088345bcc93c58d8d8f3d783eca4a6e7a7752bbff26c3eee7e73c597c191c2e", size = 197784, upload-time = "2026-03-06T06:02:21.86Z" }, - { url = "https://files.pythonhosted.org/packages/c7/5e/1aab5cb737039b9c59e63627dc8bbc0d02562a14f831cc450e5f91d84ce1/charset_normalizer-3.4.5-cp314-cp314-win32.whl", hash = "sha256:ee57b926940ba00bca7ba7041e665cc956e55ef482f851b9b65acb20d867e7a2", size = 133009, upload-time = "2026-03-06T06:02:23.289Z" }, - { url = "https://files.pythonhosted.org/packages/40/65/e7c6c77d7aaa4c0d7974f2e403e17f0ed2cb0fc135f77d686b916bf1eead/charset_normalizer-3.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4481e6da1830c8a1cc0b746b47f603b653dadb690bcd851d039ffaefe70533aa", size = 143511, upload-time = "2026-03-06T06:02:26.195Z" }, - { url = "https://files.pythonhosted.org/packages/ba/91/52b0841c71f152f563b8e072896c14e3d83b195c188b338d3cc2e582d1d4/charset_normalizer-3.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:97ab7787092eb9b50fb47fa04f24c75b768a606af1bcba1957f07f128a7219e4", size = 133775, upload-time = "2026-03-06T06:02:27.473Z" }, - { url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z" }, +version = "3.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/8c/2c56124c6dc53a774d435f985b5973bc592f42d437be58c0c92d65ae7296/charset_normalizer-3.4.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95", size = 298751, upload-time = "2026-03-15T18:50:00.003Z" }, + { url = "https://files.pythonhosted.org/packages/86/2a/2a7db6b314b966a3bcad8c731c0719c60b931b931de7ae9f34b2839289ee/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd", size = 200027, upload-time = "2026-03-15T18:50:01.702Z" }, + { url = "https://files.pythonhosted.org/packages/68/f2/0fe775c74ae25e2a3b07b01538fc162737b3e3f795bada3bc26f4d4d495c/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:259695e2ccc253feb2a016303543d691825e920917e31f894ca1a687982b1de4", size = 220741, upload-time = "2026-03-15T18:50:03.194Z" }, + { url = "https://files.pythonhosted.org/packages/10/98/8085596e41f00b27dd6aa1e68413d1ddda7e605f34dd546833c61fddd709/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dda86aba335c902b6149a02a55b38e96287157e609200811837678214ba2b1db", size = 215802, upload-time = "2026-03-15T18:50:05.859Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ce/865e4e09b041bad659d682bbd98b47fb490b8e124f9398c9448065f64fee/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51fb3c322c81d20567019778cb5a4a6f2dc1c200b886bc0d636238e364848c89", size = 207908, upload-time = "2026-03-15T18:50:07.676Z" }, + { url = "https://files.pythonhosted.org/packages/a8/54/8c757f1f7349262898c2f169e0d562b39dcb977503f18fdf0814e923db78/charset_normalizer-3.4.6-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:4482481cb0572180b6fd976a4d5c72a30263e98564da68b86ec91f0fe35e8565", size = 194357, upload-time = "2026-03-15T18:50:09.327Z" }, + { url = "https://files.pythonhosted.org/packages/6f/29/e88f2fac9218907fc7a70722b393d1bbe8334c61fe9c46640dba349b6e66/charset_normalizer-3.4.6-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39f5068d35621da2881271e5c3205125cc456f54e9030d3f723288c873a71bf9", size = 205610, upload-time = "2026-03-15T18:50:10.732Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c5/21d7bb0cb415287178450171d130bed9d664211fdd59731ed2c34267b07d/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8bea55c4eef25b0b19a0337dc4e3f9a15b00d569c77211fa8cde38684f234fb7", size = 203512, upload-time = "2026-03-15T18:50:12.535Z" }, + { url = "https://files.pythonhosted.org/packages/a4/be/ce52f3c7fdb35cc987ad38a53ebcef52eec498f4fb6c66ecfe62cfe57ba2/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f0cdaecd4c953bfae0b6bb64910aaaca5a424ad9c72d85cb88417bb9814f7550", size = 195398, upload-time = "2026-03-15T18:50:14.236Z" }, + { url = "https://files.pythonhosted.org/packages/81/a0/3ab5dd39d4859a3555e5dadfc8a9fa7f8352f8c183d1a65c90264517da0e/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:150b8ce8e830eb7ccb029ec9ca36022f756986aaaa7956aad6d9ec90089338c0", size = 221772, upload-time = "2026-03-15T18:50:15.581Z" }, + { url = "https://files.pythonhosted.org/packages/04/6e/6a4e41a97ba6b2fa87f849c41e4d229449a586be85053c4d90135fe82d26/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e68c14b04827dd76dcbd1aeea9e604e3e4b78322d8faf2f8132c7138efa340a8", size = 205759, upload-time = "2026-03-15T18:50:17.047Z" }, + { url = "https://files.pythonhosted.org/packages/db/3b/34a712a5ee64a6957bf355b01dc17b12de457638d436fdb05d01e463cd1c/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3778fd7d7cd04ae8f54651f4a7a0bd6e39a0cf20f801720a4c21d80e9b7ad6b0", size = 216938, upload-time = "2026-03-15T18:50:18.44Z" }, + { url = "https://files.pythonhosted.org/packages/cb/05/5bd1e12da9ab18790af05c61aafd01a60f489778179b621ac2a305243c62/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dad6e0f2e481fffdcf776d10ebee25e0ef89f16d691f1e5dee4b586375fdc64b", size = 210138, upload-time = "2026-03-15T18:50:19.852Z" }, + { url = "https://files.pythonhosted.org/packages/bd/8e/3cb9e2d998ff6b21c0a1860343cb7b83eba9cdb66b91410e18fc4969d6ab/charset_normalizer-3.4.6-cp310-cp310-win32.whl", hash = "sha256:74a2e659c7ecbc73562e2a15e05039f1e22c75b7c7618b4b574a3ea9118d1557", size = 144137, upload-time = "2026-03-15T18:50:21.505Z" }, + { url = "https://files.pythonhosted.org/packages/d8/8f/78f5489ffadb0db3eb7aff53d31c24531d33eb545f0c6f6567c25f49a5ff/charset_normalizer-3.4.6-cp310-cp310-win_amd64.whl", hash = "sha256:aa9cccf4a44b9b62d8ba8b4dd06c649ba683e4bf04eea606d2e94cfc2d6ff4d6", size = 154244, upload-time = "2026-03-15T18:50:22.81Z" }, + { url = "https://files.pythonhosted.org/packages/e4/74/e472659dffb0cadb2f411282d2d76c60da1fc94076d7fffed4ae8a93ec01/charset_normalizer-3.4.6-cp310-cp310-win_arm64.whl", hash = "sha256:e985a16ff513596f217cee86c21371b8cd011c0f6f056d0920aa2d926c544058", size = 143312, upload-time = "2026-03-15T18:50:24.074Z" }, + { url = "https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e", size = 293582, upload-time = "2026-03-15T18:50:25.454Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b7/b1a117e5385cbdb3205f6055403c2a2a220c5ea80b8716c324eaf75c5c95/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9", size = 197240, upload-time = "2026-03-15T18:50:27.196Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5f/2574f0f09f3c3bc1b2f992e20bce6546cb1f17e111c5be07308dc5427956/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d", size = 217363, upload-time = "2026-03-15T18:50:28.601Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d1/0ae20ad77bc949ddd39b51bf383b6ca932f2916074c95cad34ae465ab71f/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de", size = 212994, upload-time = "2026-03-15T18:50:30.102Z" }, + { url = "https://files.pythonhosted.org/packages/60/ac/3233d262a310c1b12633536a07cde5ddd16985e6e7e238e9f3f9423d8eb9/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73", size = 204697, upload-time = "2026-03-15T18:50:31.654Z" }, + { url = "https://files.pythonhosted.org/packages/25/3c/8a18fc411f085b82303cfb7154eed5bd49c77035eb7608d049468b53f87c/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c", size = 191673, upload-time = "2026-03-15T18:50:33.433Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a7/11cfe61d6c5c5c7438d6ba40919d0306ed83c9ab957f3d4da2277ff67836/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc", size = 201120, upload-time = "2026-03-15T18:50:35.105Z" }, + { url = "https://files.pythonhosted.org/packages/b5/10/cf491fa1abd47c02f69687046b896c950b92b6cd7337a27e6548adbec8e4/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f", size = 200911, upload-time = "2026-03-15T18:50:36.819Z" }, + { url = "https://files.pythonhosted.org/packages/28/70/039796160b48b18ed466fde0af84c1b090c4e288fae26cd674ad04a2d703/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef", size = 192516, upload-time = "2026-03-15T18:50:38.228Z" }, + { url = "https://files.pythonhosted.org/packages/ff/34/c56f3223393d6ff3124b9e78f7de738047c2d6bc40a4f16ac0c9d7a1cb3c/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398", size = 218795, upload-time = "2026-03-15T18:50:39.664Z" }, + { url = "https://files.pythonhosted.org/packages/e8/3b/ce2d4f86c5282191a041fdc5a4ce18f1c6bd40a5bd1f74cf8625f08d51c1/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e", size = 201833, upload-time = "2026-03-15T18:50:41.552Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9b/b6a9f76b0fd7c5b5ec58b228ff7e85095370282150f0bd50b3126f5506d6/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed", size = 213920, upload-time = "2026-03-15T18:50:43.33Z" }, + { url = "https://files.pythonhosted.org/packages/ae/98/7bc23513a33d8172365ed30ee3a3b3fe1ece14a395e5fc94129541fc6003/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021", size = 206951, upload-time = "2026-03-15T18:50:44.789Z" }, + { url = "https://files.pythonhosted.org/packages/32/73/c0b86f3d1458468e11aec870e6b3feac931facbe105a894b552b0e518e79/charset_normalizer-3.4.6-cp311-cp311-win32.whl", hash = "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e", size = 143703, upload-time = "2026-03-15T18:50:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e3/76f2facfe8eddee0bbd38d2594e709033338eae44ebf1738bcefe0a06185/charset_normalizer-3.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4", size = 153857, upload-time = "2026-03-15T18:50:47.563Z" }, + { url = "https://files.pythonhosted.org/packages/e2/dc/9abe19c9b27e6cd3636036b9d1b387b78c40dedbf0b47f9366737684b4b0/charset_normalizer-3.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316", size = 142751, upload-time = "2026-03-15T18:50:49.234Z" }, + { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154, upload-time = "2026-03-15T18:50:50.88Z" }, + { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191, upload-time = "2026-03-15T18:50:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674, upload-time = "2026-03-15T18:50:54.102Z" }, + { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259, upload-time = "2026-03-15T18:50:55.616Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276, upload-time = "2026-03-15T18:50:57.054Z" }, + { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161, upload-time = "2026-03-15T18:50:58.686Z" }, + { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452, upload-time = "2026-03-15T18:51:00.196Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272, upload-time = "2026-03-15T18:51:01.703Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622, upload-time = "2026-03-15T18:51:03.526Z" }, + { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056, upload-time = "2026-03-15T18:51:05.269Z" }, + { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751, upload-time = "2026-03-15T18:51:06.858Z" }, + { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563, upload-time = "2026-03-15T18:51:08.564Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265, upload-time = "2026-03-15T18:51:10.312Z" }, + { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229, upload-time = "2026-03-15T18:51:11.694Z" }, + { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277, upload-time = "2026-03-15T18:51:13.004Z" }, + { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817, upload-time = "2026-03-15T18:51:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823, upload-time = "2026-03-15T18:51:15.755Z" }, + { url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527, upload-time = "2026-03-15T18:51:17.177Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388, upload-time = "2026-03-15T18:51:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563, upload-time = "2026-03-15T18:51:20.374Z" }, + { url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587, upload-time = "2026-03-15T18:51:21.807Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724, upload-time = "2026-03-15T18:51:23.508Z" }, + { url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956, upload-time = "2026-03-15T18:51:25.239Z" }, + { url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923, upload-time = "2026-03-15T18:51:26.682Z" }, + { url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366, upload-time = "2026-03-15T18:51:28.129Z" }, + { url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752, upload-time = "2026-03-15T18:51:29.556Z" }, + { url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296, upload-time = "2026-03-15T18:51:30.921Z" }, + { url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956, upload-time = "2026-03-15T18:51:32.399Z" }, + { url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652, upload-time = "2026-03-15T18:51:34.214Z" }, + { url = "https://files.pythonhosted.org/packages/f1/11/897052ea6af56df3eef3ca94edafee410ca699ca0c7b87960ad19932c55e/charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", size = 143940, upload-time = "2026-03-15T18:51:36.15Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", size = 154101, upload-time = "2026-03-15T18:51:37.876Z" }, + { url = "https://files.pythonhosted.org/packages/01/a5/7abf15b4c0968e47020f9ca0935fb3274deb87cb288cd187cad92e8cdffd/charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", size = 143109, upload-time = "2026-03-15T18:51:39.565Z" }, + { url = "https://files.pythonhosted.org/packages/25/6f/ffe1e1259f384594063ea1869bfb6be5cdb8bc81020fc36c3636bc8302a1/charset_normalizer-3.4.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", size = 294458, upload-time = "2026-03-15T18:51:41.134Z" }, + { url = "https://files.pythonhosted.org/packages/56/60/09bb6c13a8c1016c2ed5c6a6488e4ffef506461aa5161662bd7636936fb1/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", size = 199277, upload-time = "2026-03-15T18:51:42.953Z" }, + { url = "https://files.pythonhosted.org/packages/00/50/dcfbb72a5138bbefdc3332e8d81a23494bf67998b4b100703fd15fa52d81/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", size = 218758, upload-time = "2026-03-15T18:51:44.339Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/d79a9a191bb75f5aa81f3aaaa387ef29ce7cb7a9e5074ba8ea095cc073c2/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", size = 215299, upload-time = "2026-03-15T18:51:45.871Z" }, + { url = "https://files.pythonhosted.org/packages/76/7e/bc8911719f7084f72fd545f647601ea3532363927f807d296a8c88a62c0d/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", size = 206811, upload-time = "2026-03-15T18:51:47.308Z" }, + { url = "https://files.pythonhosted.org/packages/e2/40/c430b969d41dda0c465aa36cc7c2c068afb67177bef50905ac371b28ccc7/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", size = 193706, upload-time = "2026-03-15T18:51:48.849Z" }, + { url = "https://files.pythonhosted.org/packages/48/15/e35e0590af254f7df984de1323640ef375df5761f615b6225ba8deb9799a/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", size = 202706, upload-time = "2026-03-15T18:51:50.257Z" }, + { url = "https://files.pythonhosted.org/packages/5e/bd/f736f7b9cc5e93a18b794a50346bb16fbfd6b37f99e8f306f7951d27c17c/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", size = 202497, upload-time = "2026-03-15T18:51:52.012Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ba/2cc9e3e7dfdf7760a6ed8da7446d22536f3d0ce114ac63dee2a5a3599e62/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", size = 193511, upload-time = "2026-03-15T18:51:53.723Z" }, + { url = "https://files.pythonhosted.org/packages/9e/cb/5be49b5f776e5613be07298c80e1b02a2d900f7a7de807230595c85a8b2e/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", size = 220133, upload-time = "2026-03-15T18:51:55.333Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/99f1b5dad345accb322c80c7821071554f791a95ee50c1c90041c157ae99/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", size = 203035, upload-time = "2026-03-15T18:51:56.736Z" }, + { url = "https://files.pythonhosted.org/packages/87/9a/62c2cb6a531483b55dddff1a68b3d891a8b498f3ca555fbcf2978e804d9d/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", size = 216321, upload-time = "2026-03-15T18:51:58.17Z" }, + { url = "https://files.pythonhosted.org/packages/6e/79/94a010ff81e3aec7c293eb82c28f930918e517bc144c9906a060844462eb/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", size = 208973, upload-time = "2026-03-15T18:51:59.998Z" }, + { url = "https://files.pythonhosted.org/packages/2a/57/4ecff6d4ec8585342f0c71bc03efaa99cb7468f7c91a57b105bcd561cea8/charset_normalizer-3.4.6-cp314-cp314-win32.whl", hash = "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", size = 144610, upload-time = "2026-03-15T18:52:02.213Z" }, + { url = "https://files.pythonhosted.org/packages/80/94/8434a02d9d7f168c25767c64671fead8d599744a05d6a6c877144c754246/charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", size = 154962, upload-time = "2026-03-15T18:52:03.658Z" }, + { url = "https://files.pythonhosted.org/packages/46/4c/48f2cdbfd923026503dfd67ccea45c94fd8fe988d9056b468579c66ed62b/charset_normalizer-3.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", size = 143595, upload-time = "2026-03-15T18:52:05.123Z" }, + { url = "https://files.pythonhosted.org/packages/31/93/8878be7569f87b14f1d52032946131bcb6ebbd8af3e20446bc04053dc3f1/charset_normalizer-3.4.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", size = 314828, upload-time = "2026-03-15T18:52:06.831Z" }, + { url = "https://files.pythonhosted.org/packages/06/b6/fae511ca98aac69ecc35cde828b0a3d146325dd03d99655ad38fc2cc3293/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", size = 208138, upload-time = "2026-03-15T18:52:08.239Z" }, + { url = "https://files.pythonhosted.org/packages/54/57/64caf6e1bf07274a1e0b7c160a55ee9e8c9ec32c46846ce59b9c333f7008/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", size = 224679, upload-time = "2026-03-15T18:52:10.043Z" }, + { url = "https://files.pythonhosted.org/packages/aa/cb/9ff5a25b9273ef160861b41f6937f86fae18b0792fe0a8e75e06acb08f1d/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", size = 223475, upload-time = "2026-03-15T18:52:11.854Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/440635fc093b8d7347502a377031f9605a1039c958f3cd18dcacffb37743/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", size = 215230, upload-time = "2026-03-15T18:52:13.325Z" }, + { url = "https://files.pythonhosted.org/packages/cd/24/afff630feb571a13f07c8539fbb502d2ab494019492aaffc78ef41f1d1d0/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", size = 199045, upload-time = "2026-03-15T18:52:14.752Z" }, + { url = "https://files.pythonhosted.org/packages/e5/17/d1399ecdaf7e0498c327433e7eefdd862b41236a7e484355b8e0e5ebd64b/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", size = 211658, upload-time = "2026-03-15T18:52:16.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/38/16baa0affb957b3d880e5ac2144caf3f9d7de7bc4a91842e447fbb5e8b67/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", size = 210769, upload-time = "2026-03-15T18:52:17.782Z" }, + { url = "https://files.pythonhosted.org/packages/05/34/c531bc6ac4c21da9ddfddb3107be2287188b3ea4b53b70fc58f2a77ac8d8/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", size = 201328, upload-time = "2026-03-15T18:52:19.553Z" }, + { url = "https://files.pythonhosted.org/packages/fa/73/a5a1e9ca5f234519c1953608a03fe109c306b97fdfb25f09182babad51a7/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", size = 225302, upload-time = "2026-03-15T18:52:21.043Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f6/cd782923d112d296294dea4bcc7af5a7ae0f86ab79f8fefbda5526b6cfc0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659", size = 211127, upload-time = "2026-03-15T18:52:22.491Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c5/0b6898950627af7d6103a449b22320372c24c6feda91aa24e201a478d161/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", size = 222840, upload-time = "2026-03-15T18:52:24.113Z" }, + { url = "https://files.pythonhosted.org/packages/7d/25/c4bba773bef442cbdc06111d40daa3de5050a676fa26e85090fc54dd12f0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", size = 216890, upload-time = "2026-03-15T18:52:25.541Z" }, + { url = "https://files.pythonhosted.org/packages/35/1a/05dacadb0978da72ee287b0143097db12f2e7e8d3ffc4647da07a383b0b7/charset_normalizer-3.4.6-cp314-cp314t-win32.whl", hash = "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", size = 155379, upload-time = "2026-03-15T18:52:27.05Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7a/d269d834cb3a76291651256f3b9a5945e81d0a49ab9f4a498964e83c0416/charset_normalizer-3.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", size = 169043, upload-time = "2026-03-15T18:52:28.502Z" }, + { url = "https://files.pythonhosted.org/packages/23/06/28b29fba521a37a8932c6a84192175c34d49f84a6d4773fa63d05f9aff22/charset_normalizer-3.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", size = 148523, upload-time = "2026-03-15T18:52:29.956Z" }, + { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" }, ] [[package]] @@ -751,115 +782,115 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/d4/7827d9ffa34d5d4d752eec907022aa417120936282fc488306f5da08c292/coverage-7.13.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fc31c787a84f8cd6027eba44010517020e0d18487064cd3d8968941856d1415", size = 219152, upload-time = "2026-02-09T12:56:11.974Z" }, - { url = "https://files.pythonhosted.org/packages/35/b0/d69df26607c64043292644dbb9dc54b0856fabaa2cbb1eeee3331cc9e280/coverage-7.13.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a32ebc02a1805adf637fc8dec324b5cdacd2e493515424f70ee33799573d661b", size = 219667, upload-time = "2026-02-09T12:56:13.33Z" }, - { url = "https://files.pythonhosted.org/packages/82/a4/c1523f7c9e47b2271dbf8c2a097e7a1f89ef0d66f5840bb59b7e8814157b/coverage-7.13.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e24f9156097ff9dc286f2f913df3a7f63c0e333dcafa3c196f2c18b4175ca09a", size = 246425, upload-time = "2026-02-09T12:56:14.552Z" }, - { url = "https://files.pythonhosted.org/packages/f8/02/aa7ec01d1a5023c4b680ab7257f9bfde9defe8fdddfe40be096ac19e8177/coverage-7.13.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8041b6c5bfdc03257666e9881d33b1abc88daccaf73f7b6340fb7946655cd10f", size = 248229, upload-time = "2026-02-09T12:56:16.31Z" }, - { url = "https://files.pythonhosted.org/packages/35/98/85aba0aed5126d896162087ef3f0e789a225697245256fc6181b95f47207/coverage-7.13.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a09cfa6a5862bc2fc6ca7c3def5b2926194a56b8ab78ffcf617d28911123012", size = 250106, upload-time = "2026-02-09T12:56:18.024Z" }, - { url = "https://files.pythonhosted.org/packages/96/72/1db59bd67494bc162e3e4cd5fbc7edba2c7026b22f7c8ef1496d58c2b94c/coverage-7.13.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:296f8b0af861d3970c2a4d8c91d48eb4dd4771bcef9baedec6a9b515d7de3def", size = 252021, upload-time = "2026-02-09T12:56:19.272Z" }, - { url = "https://files.pythonhosted.org/packages/9d/97/72899c59c7066961de6e3daa142d459d47d104956db43e057e034f015c8a/coverage-7.13.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e101609bcbbfb04605ea1027b10dc3735c094d12d40826a60f897b98b1c30256", size = 247114, upload-time = "2026-02-09T12:56:21.051Z" }, - { url = "https://files.pythonhosted.org/packages/39/1f/f1885573b5970235e908da4389176936c8933e86cb316b9620aab1585fa2/coverage-7.13.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aa3feb8db2e87ff5e6d00d7e1480ae241876286691265657b500886c98f38bda", size = 248143, upload-time = "2026-02-09T12:56:22.585Z" }, - { url = "https://files.pythonhosted.org/packages/a8/cf/e80390c5b7480b722fa3e994f8202807799b85bc562aa4f1dde209fbb7be/coverage-7.13.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4fc7fa81bbaf5a02801b65346c8b3e657f1d93763e58c0abdf7c992addd81a92", size = 246152, upload-time = "2026-02-09T12:56:23.748Z" }, - { url = "https://files.pythonhosted.org/packages/44/bf/f89a8350d85572f95412debb0fb9bb4795b1d5b5232bd652923c759e787b/coverage-7.13.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:33901f604424145c6e9c2398684b92e176c0b12df77d52db81c20abd48c3794c", size = 249959, upload-time = "2026-02-09T12:56:25.209Z" }, - { url = "https://files.pythonhosted.org/packages/f7/6e/612a02aece8178c818df273e8d1642190c4875402ca2ba74514394b27aba/coverage-7.13.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:bb28c0f2cf2782508a40cec377935829d5fcc3ad9a3681375af4e84eb34b6b58", size = 246416, upload-time = "2026-02-09T12:56:26.475Z" }, - { url = "https://files.pythonhosted.org/packages/cb/98/b5afc39af67c2fa6786b03c3a7091fc300947387ce8914b096db8a73d67a/coverage-7.13.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d107aff57a83222ddbd8d9ee705ede2af2cc926608b57abed8ef96b50b7e8f9", size = 247025, upload-time = "2026-02-09T12:56:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/51/30/2bba8ef0682d5bd210c38fe497e12a06c9f8d663f7025e9f5c2c31ce847d/coverage-7.13.4-cp310-cp310-win32.whl", hash = "sha256:a6f94a7d00eb18f1b6d403c91a88fd58cfc92d4b16080dfdb774afc8294469bf", size = 221758, upload-time = "2026-02-09T12:56:29.051Z" }, - { url = "https://files.pythonhosted.org/packages/78/13/331f94934cf6c092b8ea59ff868eb587bc8fe0893f02c55bc6c0183a192e/coverage-7.13.4-cp310-cp310-win_amd64.whl", hash = "sha256:2cb0f1e000ebc419632bbe04366a8990b6e32c4e0b51543a6484ffe15eaeda95", size = 222693, upload-time = "2026-02-09T12:56:30.366Z" }, - { url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" }, - { url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" }, - { url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" }, - { url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" }, - { url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" }, - { url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" }, - { url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" }, - { url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" }, - { url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" }, - { url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" }, - { url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" }, - { url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" }, - { url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" }, - { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, - { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, - { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, - { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, - { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, - { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, - { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, - { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, - { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, - { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, - { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, - { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, - { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, - { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, - { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, - { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, - { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, - { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, - { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, - { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, - { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, - { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, - { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, - { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, - { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, - { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, - { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, - { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, - { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, - { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, - { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, - { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, - { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, - { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, - { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, - { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" }, - { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" }, - { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" }, - { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" }, - { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" }, - { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" }, - { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" }, - { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" }, - { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" }, - { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" }, - { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" }, - { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" }, - { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" }, - { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" }, - { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" }, - { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" }, - { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" }, - { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" }, - { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" }, - { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" }, - { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" }, - { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" }, - { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" }, - { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" }, - { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" }, - { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" }, - { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" }, - { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, +version = "7.13.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/33/e8c48488c29a73fd089f9d71f9653c1be7478f2ad6b5bc870db11a55d23d/coverage-7.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0723d2c96324561b9aa76fb982406e11d93cdb388a7a7da2b16e04719cf7ca5", size = 219255, upload-time = "2026-03-17T10:29:51.081Z" }, + { url = "https://files.pythonhosted.org/packages/da/bd/b0ebe9f677d7f4b74a3e115eec7ddd4bcf892074963a00d91e8b164a6386/coverage-7.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52f444e86475992506b32d4e5ca55c24fc88d73bcbda0e9745095b28ef4dc0cf", size = 219772, upload-time = "2026-03-17T10:29:52.867Z" }, + { url = "https://files.pythonhosted.org/packages/48/cc/5cb9502f4e01972f54eedd48218bb203fe81e294be606a2bc93970208013/coverage-7.13.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:704de6328e3d612a8f6c07000a878ff38181ec3263d5a11da1db294fa6a9bdf8", size = 246532, upload-time = "2026-03-17T10:29:54.688Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d8/3217636d86c7e7b12e126e4f30ef1581047da73140614523af7495ed5f2d/coverage-7.13.5-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a1a6d79a14e1ec1832cabc833898636ad5f3754a678ef8bb4908515208bf84f4", size = 248333, upload-time = "2026-03-17T10:29:56.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/30/2002ac6729ba2d4357438e2ed3c447ad8562866c8c63fc16f6dfc33afe56/coverage-7.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79060214983769c7ba3f0cee10b54c97609dca4d478fa1aa32b914480fd5738d", size = 250211, upload-time = "2026-03-17T10:29:57.938Z" }, + { url = "https://files.pythonhosted.org/packages/6c/85/552496626d6b9359eb0e2f86f920037c9cbfba09b24d914c6e1528155f7d/coverage-7.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:356e76b46783a98c2a2fe81ec79df4883a1e62895ea952968fb253c114e7f930", size = 252125, upload-time = "2026-03-17T10:29:59.388Z" }, + { url = "https://files.pythonhosted.org/packages/44/21/40256eabdcbccdb6acf6b381b3016a154399a75fe39d406f790ae84d1f3c/coverage-7.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cef0cdec915d11254a7f549c1170afecce708d30610c6abdded1f74e581666d", size = 247219, upload-time = "2026-03-17T10:30:01.199Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/96e2a6c3f21a0ea77d7830b254a1542d0328acc8d7bdf6a284ba7e529f77/coverage-7.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dc022073d063b25a402454e5712ef9e007113e3a676b96c5f29b2bda29352f40", size = 248248, upload-time = "2026-03-17T10:30:03.317Z" }, + { url = "https://files.pythonhosted.org/packages/da/ba/8477f549e554827da390ec659f3c38e4b6d95470f4daafc2d8ff94eaa9c2/coverage-7.13.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9b74db26dfea4f4e50d48a4602207cd1e78be33182bc9cbf22da94f332f99878", size = 246254, upload-time = "2026-03-17T10:30:04.832Z" }, + { url = "https://files.pythonhosted.org/packages/55/59/bc22aef0e6aa179d5b1b001e8b3654785e9adf27ef24c93dc4228ebd5d68/coverage-7.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ad146744ca4fd09b50c482650e3c1b1f4dfa1d4792e0a04a369c7f23336f0400", size = 250067, upload-time = "2026-03-17T10:30:06.535Z" }, + { url = "https://files.pythonhosted.org/packages/de/1b/c6a023a160806a5137dca53468fd97530d6acad24a22003b1578a9c2e429/coverage-7.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c555b48be1853fe3997c11c4bd521cdd9a9612352de01fa4508f16ec341e6fe0", size = 246521, upload-time = "2026-03-17T10:30:08.486Z" }, + { url = "https://files.pythonhosted.org/packages/2d/3f/3532c85a55aa2f899fa17c186f831cfa1aa434d88ff792a709636f64130e/coverage-7.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7034b5c56a58ae5e85f23949d52c14aca2cfc6848a31764995b7de88f13a1ea0", size = 247126, upload-time = "2026-03-17T10:30:09.966Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2e/b9d56af4a24ef45dfbcda88e06870cb7d57b2b0bfa3a888d79b4c8debd76/coverage-7.13.5-cp310-cp310-win32.whl", hash = "sha256:eb7fdf1ef130660e7415e0253a01a7d5a88c9c4d158bcf75cbbd922fd65a5b58", size = 221860, upload-time = "2026-03-17T10:30:11.393Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cc/d938417e7a4d7f0433ad4edee8bb2acdc60dc7ac5af19e2a07a048ecbee3/coverage-7.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:3e1bb5f6c78feeb1be3475789b14a0f0a5b47d505bfc7267126ccbd50289999e", size = 222788, upload-time = "2026-03-17T10:30:12.886Z" }, + { url = "https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d", size = 219381, upload-time = "2026-03-17T10:30:14.68Z" }, + { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" }, + { url = "https://files.pythonhosted.org/packages/55/2f/e0e5b237bffdb5d6c530ce87cc1d413a5b7d7dfd60fb067ad6d254c35c76/coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642", size = 250303, upload-time = "2026-03-17T10:30:17.748Z" }, + { url = "https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b", size = 252218, upload-time = "2026-03-17T10:30:19.804Z" }, + { url = "https://files.pythonhosted.org/packages/da/69/2f47bb6fa1b8d1e3e5d0c4be8ccb4313c63d742476a619418f85740d597b/coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686", size = 254326, upload-time = "2026-03-17T10:30:21.321Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d0/79db81da58965bd29dabc8f4ad2a2af70611a57cba9d1ec006f072f30a54/coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743", size = 256267, upload-time = "2026-03-17T10:30:23.094Z" }, + { url = "https://files.pythonhosted.org/packages/e5/32/d0d7cc8168f91ddab44c0ce4806b969df5f5fdfdbb568eaca2dbc2a04936/coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75", size = 250430, upload-time = "2026-03-17T10:30:25.311Z" }, + { url = "https://files.pythonhosted.org/packages/4d/06/a055311d891ddbe231cd69fdd20ea4be6e3603ffebddf8704b8ca8e10a3c/coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209", size = 252017, upload-time = "2026-03-17T10:30:27.284Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f6/d0fd2d21e29a657b5f77a2fe7082e1568158340dceb941954f776dce1b7b/coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a", size = 250080, upload-time = "2026-03-17T10:30:29.481Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ab/0d7fb2efc2e9a5eb7ddcc6e722f834a69b454b7e6e5888c3a8567ecffb31/coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e", size = 253843, upload-time = "2026-03-17T10:30:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/ba/6f/7467b917bbf5408610178f62a49c0ed4377bb16c1657f689cc61470da8ce/coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd", size = 249802, upload-time = "2026-03-17T10:30:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/75/2c/1172fb689df92135f5bfbbd69fc83017a76d24ea2e2f3a1154007e2fb9f8/coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8", size = 250707, upload-time = "2026-03-17T10:30:35.2Z" }, + { url = "https://files.pythonhosted.org/packages/67/21/9ac389377380a07884e3b48ba7a620fcd9dbfaf1d40565facdc6b36ec9ef/coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf", size = 221880, upload-time = "2026-03-17T10:30:36.775Z" }, + { url = "https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9", size = 222816, upload-time = "2026-03-17T10:30:38.891Z" }, + { url = "https://files.pythonhosted.org/packages/12/a6/1d3f6155fb0010ca68eba7fe48ca6c9da7385058b77a95848710ecf189b1/coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028", size = 221483, upload-time = "2026-03-17T10:30:40.463Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, + { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, + { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, + { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, + { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, + { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, + { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, + { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, + { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, + { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, + { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, + { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, + { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, + { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, + { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, + { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, + { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, + { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, + { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, + { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, + { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, + { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" }, + { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" }, + { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, + { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, + { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" }, + { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, + { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, + { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, + { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, ] [package.optional-dependencies] @@ -946,10 +977,10 @@ wheels = [ [[package]] name = "cuda-pathfinder" -version = "1.4.2" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/de/8ca2b613042550dcf9ef50c596c8b1f602afda92cf9032ac28a73f6ee410/cuda_pathfinder-1.4.2-py3-none-any.whl", hash = "sha256:eb354abc20278f8609dc5b666a24648655bef5613c6dfe78a238a6fd95566754", size = 44779, upload-time = "2026-03-10T21:57:30.974Z" }, + { url = "https://files.pythonhosted.org/packages/93/66/0c02bd330e7d976f83fa68583d6198d76f23581bcbb5c0e98a6148f326e5/cuda_pathfinder-1.5.0-py3-none-any.whl", hash = "sha256:498f90a9e9de36044a7924742aecce11c50c49f735f1bc53e05aa46de9ea4110", size = 49739, upload-time = "2026-03-24T21:14:30.869Z" }, ] [[package]] @@ -1115,59 +1146,59 @@ wheels = [ [[package]] name = "fonttools" -version = "4.62.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/96/686339e0fda8142b7ebed39af53f4a5694602a729662f42a6209e3be91d0/fonttools-4.62.0.tar.gz", hash = "sha256:0dc477c12b8076b4eb9af2e440421b0433ffa9e1dcb39e0640a6c94665ed1098", size = 3579521, upload-time = "2026-03-09T16:50:06.217Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/e0/9db48ec7f6b95bae7b20667ded54f18dba8e759ef66232c8683822ae26fc/fonttools-4.62.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:62b6a3d0028e458e9b59501cf7124a84cd69681c433570e4861aff4fb54a236c", size = 2873527, upload-time = "2026-03-09T16:48:12.416Z" }, - { url = "https://files.pythonhosted.org/packages/dd/45/86eccfdc922cb9fafc63189a9793fa9f6dd60e68a07be42e454ef2c0deae/fonttools-4.62.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:966557078b55e697f65300b18025c54e872d7908d1899b7314d7c16e64868cb2", size = 2417427, upload-time = "2026-03-09T16:48:15.122Z" }, - { url = "https://files.pythonhosted.org/packages/d3/98/f547a1fceeae81a9a5c6461bde2badac8bf50bda7122a8012b32b1e65396/fonttools-4.62.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cf34861145b516cddd19b07ae6f4a61ea1c6326031b960ec9ddce8ee815e888", size = 4934993, upload-time = "2026-03-09T16:48:18.186Z" }, - { url = "https://files.pythonhosted.org/packages/5c/57/a23a051fcff998fdfabdd33c6721b5bad499da08b586d3676993410071f0/fonttools-4.62.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e2ff573de2775508c8a366351fb901c4ced5dc6cf2d87dd15c973bedcdd5216", size = 4892154, upload-time = "2026-03-09T16:48:20.736Z" }, - { url = "https://files.pythonhosted.org/packages/e2/62/e27644b433dc6db1d47bc6028a27d772eec5cc8338e24a9a1fce5d7120aa/fonttools-4.62.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:55b189a1b3033860a38e4e5bd0626c5aa25c7ce9caee7bc784a8caec7a675401", size = 4911635, upload-time = "2026-03-09T16:48:23.174Z" }, - { url = "https://files.pythonhosted.org/packages/7e/e2/1bf141911a5616bacfe9cf237c80ccd69d0d92482c38c0f7f6a55d063ad9/fonttools-4.62.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:825f98cd14907c74a4d0a3f7db8570886ffce9c6369fed1385020febf919abf6", size = 5031492, upload-time = "2026-03-09T16:48:25.095Z" }, - { url = "https://files.pythonhosted.org/packages/2f/59/790c292f4347ecfa77d9c7e0d1d91e04ab227f6e4a337ed4fe37ca388048/fonttools-4.62.0-cp310-cp310-win32.whl", hash = "sha256:c858030560f92a054444c6e46745227bfd3bb4e55383c80d79462cd47289e4b5", size = 1507656, upload-time = "2026-03-09T16:48:26.973Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ee/08c0b7f8bac6e44638de6fe9a3e710a623932f60eccd58912c4d4743516d/fonttools-4.62.0-cp310-cp310-win_amd64.whl", hash = "sha256:9bf75eb69330e34ad2a096fac67887102c8537991eb6cac1507fc835bbb70e0a", size = 1556540, upload-time = "2026-03-09T16:48:30.359Z" }, - { url = "https://files.pythonhosted.org/packages/e4/33/63d79ca41020dd460b51f1e0f58ad1ff0a36b7bcbdf8f3971d52836581e9/fonttools-4.62.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:196cafef9aeec5258425bd31a4e9a414b2ee0d1557bca184d7923d3d3bcd90f9", size = 2870816, upload-time = "2026-03-09T16:48:32.39Z" }, - { url = "https://files.pythonhosted.org/packages/c0/7a/9aeec114bc9fc00d757a41f092f7107863d372e684a5b5724c043654477c/fonttools-4.62.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:153afc3012ff8761b1733e8fbe5d98623409774c44ffd88fbcb780e240c11d13", size = 2416127, upload-time = "2026-03-09T16:48:34.627Z" }, - { url = "https://files.pythonhosted.org/packages/5a/71/12cfd8ae0478b7158ffa8850786781f67e73c00fd897ef9d053415c5f88b/fonttools-4.62.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13b663fb197334de84db790353d59da2a7288fd14e9be329f5debc63ec0500a5", size = 5100678, upload-time = "2026-03-09T16:48:36.454Z" }, - { url = "https://files.pythonhosted.org/packages/8a/d7/8e4845993ee233c2023d11babe9b3dae7d30333da1d792eeccebcb77baab/fonttools-4.62.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:591220d5333264b1df0d3285adbdfe2af4f6a45bbf9ca2b485f97c9f577c49ff", size = 5070859, upload-time = "2026-03-09T16:48:38.786Z" }, - { url = "https://files.pythonhosted.org/packages/ae/a0/287ae04cd883a52e7bb1d92dfc4997dcffb54173761c751106845fa9e316/fonttools-4.62.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:579f35c121528a50c96bf6fcb6a393e81e7f896d4326bf40e379f1c971603db9", size = 5076689, upload-time = "2026-03-09T16:48:41.886Z" }, - { url = "https://files.pythonhosted.org/packages/6d/4e/a2377ad26c36fcd3e671a1c316ea5ed83107de1588e2d897a98349363bc7/fonttools-4.62.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:44956b003151d5a289eba6c71fe590d63509267c37e26de1766ba15d9c589582", size = 5202053, upload-time = "2026-03-09T16:48:43.867Z" }, - { url = "https://files.pythonhosted.org/packages/44/2e/ad0472e69b02f83dc88983a9910d122178461606404be5b4838af6d1744a/fonttools-4.62.0-cp311-cp311-win32.whl", hash = "sha256:42c7848fa8836ab92c23b1617c407a905642521ff2d7897fe2bf8381530172f1", size = 2292852, upload-time = "2026-03-09T16:48:46.962Z" }, - { url = "https://files.pythonhosted.org/packages/77/ce/f5a4c42c117f8113ce04048053c128d17426751a508f26398110c993a074/fonttools-4.62.0-cp311-cp311-win_amd64.whl", hash = "sha256:4da779e8f342a32856075ddb193b2a024ad900bc04ecb744014c32409ae871ed", size = 2344367, upload-time = "2026-03-09T16:48:48.818Z" }, - { url = "https://files.pythonhosted.org/packages/ab/9d/7ad1ffc080619f67d0b1e0fa6a0578f0be077404f13fd8e448d1616a94a3/fonttools-4.62.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:22bde4dc12a9e09b5ced77f3b5053d96cf10c4976c6ac0dee293418ef289d221", size = 2870004, upload-time = "2026-03-09T16:48:50.837Z" }, - { url = "https://files.pythonhosted.org/packages/4d/8b/ba59069a490f61b737e064c3129453dbd28ee38e81d56af0d04d7e6b4de4/fonttools-4.62.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7199c73b326bad892f1cb53ffdd002128bfd58a89b8f662204fbf1daf8d62e85", size = 2414662, upload-time = "2026-03-09T16:48:53.295Z" }, - { url = "https://files.pythonhosted.org/packages/8c/8c/c52a4310de58deeac7e9ea800892aec09b00bb3eb0c53265b31ec02be115/fonttools-4.62.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d732938633681d6e2324e601b79e93f7f72395ec8681f9cdae5a8c08bc167e72", size = 5032975, upload-time = "2026-03-09T16:48:55.718Z" }, - { url = "https://files.pythonhosted.org/packages/0b/a1/d16318232964d786907b9b3613b8409f74cf0be2da400854509d3a864e43/fonttools-4.62.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:31a804c16d76038cc4e3826e07678efb0a02dc4f15396ea8e07088adbfb2578e", size = 4988544, upload-time = "2026-03-09T16:48:57.715Z" }, - { url = "https://files.pythonhosted.org/packages/b2/8d/7e745ca3e65852adc5e52a83dc213fe1b07d61cb5b394970fcd4b1199d1e/fonttools-4.62.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:090e74ac86e68c20150e665ef8e7e0c20cb9f8b395302c9419fa2e4d332c3b51", size = 4971296, upload-time = "2026-03-09T16:48:59.678Z" }, - { url = "https://files.pythonhosted.org/packages/e6/d4/b717a4874175146029ca1517e85474b1af80c9d9a306fc3161e71485eea5/fonttools-4.62.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8f086120e8be9e99ca1288aa5ce519833f93fe0ec6ebad2380c1dee18781f0b5", size = 5122503, upload-time = "2026-03-09T16:49:02.464Z" }, - { url = "https://files.pythonhosted.org/packages/cb/4b/92cfcba4bf8373f51c49c5ae4b512ead6fbda7d61a0e8c35a369d0db40a0/fonttools-4.62.0-cp312-cp312-win32.whl", hash = "sha256:37a73e5e38fd05c637daede6ffed5f3496096be7df6e4a3198d32af038f87527", size = 2281060, upload-time = "2026-03-09T16:49:04.385Z" }, - { url = "https://files.pythonhosted.org/packages/cd/06/cc96468781a4dc8ae2f14f16f32b32f69bde18cb9384aad27ccc7adf76f7/fonttools-4.62.0-cp312-cp312-win_amd64.whl", hash = "sha256:658ab837c878c4d2a652fcbb319547ea41693890e6434cf619e66f79387af3b8", size = 2331193, upload-time = "2026-03-09T16:49:06.598Z" }, - { url = "https://files.pythonhosted.org/packages/82/c7/985c1670aa6d82ef270f04cde11394c168f2002700353bd2bde405e59b8f/fonttools-4.62.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:274c8b8a87e439faf565d3bcd3f9f9e31bca7740755776a4a90a4bfeaa722efa", size = 2864929, upload-time = "2026-03-09T16:49:09.331Z" }, - { url = "https://files.pythonhosted.org/packages/c1/dc/c409c8ceec0d3119e9ab0b7b1a2e3c76d1f4d66e4a9db5c59e6b7652e7df/fonttools-4.62.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93e27131a5a0ae82aaadcffe309b1bae195f6711689722af026862bede05c07c", size = 2412586, upload-time = "2026-03-09T16:49:11.378Z" }, - { url = "https://files.pythonhosted.org/packages/5f/ac/8e300dbf7b4d135287c261ffd92ede02d9f48f0d2db14665fbc8b059588a/fonttools-4.62.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83c6524c5b93bad9c2939d88e619fedc62e913c19e673f25d5ab74e7a5d074e5", size = 5013708, upload-time = "2026-03-09T16:49:14.063Z" }, - { url = "https://files.pythonhosted.org/packages/fb/bc/60d93477b653eeb1ddf5f9ec34be689b79234d82dbdded269ac0252715b8/fonttools-4.62.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:106aec9226f9498fc5345125ff7200842c01eda273ae038f5049b0916907acee", size = 4964355, upload-time = "2026-03-09T16:49:16.515Z" }, - { url = "https://files.pythonhosted.org/packages/cb/eb/6dc62bcc3c3598c28a3ecb77e69018869c3e109bd83031d4973c059d318b/fonttools-4.62.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:15d86b96c79013320f13bc1b15f94789edb376c0a2d22fb6088f33637e8dfcbc", size = 4953472, upload-time = "2026-03-09T16:49:18.494Z" }, - { url = "https://files.pythonhosted.org/packages/82/b3/3af7592d9b254b7b7fec018135f8776bfa0d1ad335476c2791b1334dc5e4/fonttools-4.62.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f16c07e5250d5d71d0f990a59460bc5620c3cc456121f2cfb5b60475699905f", size = 5094701, upload-time = "2026-03-09T16:49:21.67Z" }, - { url = "https://files.pythonhosted.org/packages/31/3d/976645583ab567d3ee75ff87b33aa1330fa2baeeeae5fc46210b4274dd45/fonttools-4.62.0-cp313-cp313-win32.whl", hash = "sha256:d31558890f3fa00d4f937d12708f90c7c142c803c23eaeb395a71f987a77ebe3", size = 2279710, upload-time = "2026-03-09T16:49:23.812Z" }, - { url = "https://files.pythonhosted.org/packages/f5/7a/e25245a30457595740041dba9d0ea8ec1b2517f2f1a6a741f15eba1a4edc/fonttools-4.62.0-cp313-cp313-win_amd64.whl", hash = "sha256:6826a5aa53fb6def8a66bf423939745f415546c4e92478a7c531b8b6282b6c3b", size = 2330291, upload-time = "2026-03-09T16:49:26.237Z" }, - { url = "https://files.pythonhosted.org/packages/1a/64/61f69298aa6e7c363dcf00dd6371a654676900abe27d1effd1a74b43e5d0/fonttools-4.62.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:4fa5a9c716e2f75ef34b5a5c2ca0ee4848d795daa7e6792bf30fd4abf8993449", size = 2864222, upload-time = "2026-03-09T16:49:28.285Z" }, - { url = "https://files.pythonhosted.org/packages/c6/57/6b08756fe4455336b1fe160ab3c11fccc90768ccb6ee03fb0b45851aace4/fonttools-4.62.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:625f5cbeb0b8f4e42343eaeb4bc2786718ddd84760a2f5e55fdd3db049047c00", size = 2410674, upload-time = "2026-03-09T16:49:30.504Z" }, - { url = "https://files.pythonhosted.org/packages/6f/86/db65b63bb1b824b63e602e9be21b18741ddc99bcf5a7850f9181159ae107/fonttools-4.62.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6247e58b96b982709cd569a91a2ba935d406dccf17b6aa615afaed37ac3856aa", size = 4999387, upload-time = "2026-03-09T16:49:32.593Z" }, - { url = "https://files.pythonhosted.org/packages/86/c8/c6669e42d2f4efd60d38a3252cebbb28851f968890efb2b9b15f9d1092b0/fonttools-4.62.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:840632ea9c1eab7b7f01c369e408c0721c287dfd7500ab937398430689852fd1", size = 4912506, upload-time = "2026-03-09T16:49:34.927Z" }, - { url = "https://files.pythonhosted.org/packages/2e/49/0ae552aa098edd0ec548413fbf818f52ceb70535016215094a5ce9bf8f70/fonttools-4.62.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:28a9ea2a7467a816d1bec22658b0cce4443ac60abac3e293bdee78beb74588f3", size = 4951202, upload-time = "2026-03-09T16:49:37.1Z" }, - { url = "https://files.pythonhosted.org/packages/71/65/ae38fc8a4cea6f162d74cf11f58e9aeef1baa7d0e3d1376dabd336c129e5/fonttools-4.62.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5ae611294f768d413949fd12693a8cba0e6332fbc1e07aba60121be35eac68d0", size = 5060758, upload-time = "2026-03-09T16:49:39.464Z" }, - { url = "https://files.pythonhosted.org/packages/db/3d/bb797496f35c60544cd5af71ffa5aad62df14ef7286908d204cb5c5096fe/fonttools-4.62.0-cp314-cp314-win32.whl", hash = "sha256:273acb61f316d07570a80ed5ff0a14a23700eedbec0ad968b949abaa4d3f6bb5", size = 2283496, upload-time = "2026-03-09T16:49:42.448Z" }, - { url = "https://files.pythonhosted.org/packages/2e/9f/91081ffe5881253177c175749cce5841f5ec6e931f5d52f4a817207b7429/fonttools-4.62.0-cp314-cp314-win_amd64.whl", hash = "sha256:a5f974006d14f735c6c878fc4b117ad031dc93638ddcc450ca69f8fd64d5e104", size = 2335426, upload-time = "2026-03-09T16:49:44.228Z" }, - { url = "https://files.pythonhosted.org/packages/f8/65/f47f9b3db1ec156a1f222f1089ba076b2cc9ee1d024a8b0a60c54258517e/fonttools-4.62.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0361a7d41d86937f1f752717c19f719d0fde064d3011038f9f19bdf5fc2f5c95", size = 2947079, upload-time = "2026-03-09T16:49:46.471Z" }, - { url = "https://files.pythonhosted.org/packages/52/73/bc62e5058a0c22cf02b1e0169ef0c3ca6c3247216d719f95bead3c05a991/fonttools-4.62.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4108c12773b3c97aa592311557c405d5b4fc03db2b969ed928fcf68e7b3c887", size = 2448802, upload-time = "2026-03-09T16:49:48.328Z" }, - { url = "https://files.pythonhosted.org/packages/2b/df/bfaa0e845884935355670e6e68f137185ab87295f8bc838db575e4a66064/fonttools-4.62.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b448075f32708e8fb377fe7687f769a5f51a027172c591ba9a58693631b077a8", size = 5137378, upload-time = "2026-03-09T16:49:50.223Z" }, - { url = "https://files.pythonhosted.org/packages/32/32/04f616979a18b48b52e634988b93d847b6346260faf85ecccaf7e2e9057f/fonttools-4.62.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5f1fa8cc9f1a56a3e33ee6b954d6d9235e6b9d11eb7a6c9dfe2c2f829dc24db", size = 4920714, upload-time = "2026-03-09T16:49:53.172Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2e/274e16689c1dfee5c68302cd7c444213cfddd23cf4620374419625037ec6/fonttools-4.62.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f8c8ea812f82db1e884b9cdb663080453e28f0f9a1f5027a5adb59c4cc8d38d1", size = 5016012, upload-time = "2026-03-09T16:49:55.762Z" }, - { url = "https://files.pythonhosted.org/packages/7f/0c/b08117270626e7117ac2f89d732fdd4386ec37d2ab3a944462d29e6f89a1/fonttools-4.62.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:03c6068adfdc67c565d217e92386b1cdd951abd4240d65180cec62fa74ba31b2", size = 5042766, upload-time = "2026-03-09T16:49:57.726Z" }, - { url = "https://files.pythonhosted.org/packages/11/83/a48b73e54efa272ee65315a6331b30a9b3a98733310bc11402606809c50e/fonttools-4.62.0-cp314-cp314t-win32.whl", hash = "sha256:d28d5baacb0017d384df14722a63abe6e0230d8ce642b1615a27d78ffe3bc983", size = 2347785, upload-time = "2026-03-09T16:49:59.698Z" }, - { url = "https://files.pythonhosted.org/packages/f8/27/c67eab6dc3525bdc39586511b1b3d7161e972dacc0f17476dbaf932e708b/fonttools-4.62.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3f9e20c4618f1e04190c802acae6dc337cb6db9fa61e492fd97cd5c5a9ff6d07", size = 2413914, upload-time = "2026-03-09T16:50:02.251Z" }, - { url = "https://files.pythonhosted.org/packages/9c/57/c2487c281dde03abb2dec244fd67059b8d118bd30a653cbf69e94084cb23/fonttools-4.62.0-py3-none-any.whl", hash = "sha256:75064f19a10c50c74b336aa5ebe7b1f89fd0fb5255807bfd4b0c6317098f4af3", size = 1152427, upload-time = "2026-03-09T16:50:04.074Z" }, +version = "4.62.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/ff/532ed43808b469c807e8cb6b21358da3fe6fd51486b3a8c93db0bb5d957f/fonttools-4.62.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ad5cca75776cd453b1b035b530e943334957ae152a36a88a320e779d61fc980c", size = 2873740, upload-time = "2026-03-13T13:52:11.822Z" }, + { url = "https://files.pythonhosted.org/packages/85/e4/2318d2b430562da7227010fb2bb029d2fa54d7b46443ae8942bab224e2a0/fonttools-4.62.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b3ae47e8636156a9accff64c02c0924cbebad62854c4a6dbdc110cd5b4b341a", size = 2417649, upload-time = "2026-03-13T13:52:14.605Z" }, + { url = "https://files.pythonhosted.org/packages/4c/28/40f15523b5188598018e7956899fed94eb7debec89e2dd70cb4a8df90492/fonttools-4.62.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b9e288b4da2f64fd6180644221749de651703e8d0c16bd4b719533a3a7d6e3", size = 4935213, upload-time = "2026-03-13T13:52:17.399Z" }, + { url = "https://files.pythonhosted.org/packages/42/09/7dbe3d7023f57d9b580cfa832109d521988112fd59dddfda3fddda8218f9/fonttools-4.62.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bca7a1c1faf235ffe25d4f2e555246b4750220b38de8261d94ebc5ce8a23c23", size = 4892374, upload-time = "2026-03-13T13:52:20.175Z" }, + { url = "https://files.pythonhosted.org/packages/d1/2d/84509a2e32cb925371560ef5431365d8da2183c11d98e5b4b8b4e42426a5/fonttools-4.62.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4e0fcf265ad26e487c56cb12a42dffe7162de708762db951e1b3f755319507d", size = 4911856, upload-time = "2026-03-13T13:52:22.777Z" }, + { url = "https://files.pythonhosted.org/packages/a5/80/df28131379eed93d9e6e6fccd3bf6e3d077bebbfe98cc83f21bbcd83ed02/fonttools-4.62.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2d850f66830a27b0d498ee05adb13a3781637b1826982cd7e2b3789ef0cc71ae", size = 5031712, upload-time = "2026-03-13T13:52:25.14Z" }, + { url = "https://files.pythonhosted.org/packages/3d/03/3c8f09aad64230cd6d921ae7a19f9603c36f70930b00459f112706f6769a/fonttools-4.62.1-cp310-cp310-win32.whl", hash = "sha256:486f32c8047ccd05652aba17e4a8819a3a9d78570eb8a0e3b4503142947880ed", size = 1507878, upload-time = "2026-03-13T13:52:28.149Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ec/f53f626f8f3e89f4cadd8fc08f3452c8fd182c951ad5caa35efac22b29ab/fonttools-4.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:5a648bde915fba9da05ae98856987ca91ba832949a9e2888b48c47ef8b96c5a9", size = 1556766, upload-time = "2026-03-13T13:52:30.814Z" }, + { url = "https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7", size = 2871039, upload-time = "2026-03-13T13:52:33.127Z" }, + { url = "https://files.pythonhosted.org/packages/24/7f/66d3f8a9338a9b67fe6e1739f47e1cd5cee78bd3bc1206ef9b0b982289a5/fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14", size = 2416346, upload-time = "2026-03-13T13:52:35.676Z" }, + { url = "https://files.pythonhosted.org/packages/aa/53/5276ceba7bff95da7793a07c5284e1da901cf00341ce5e2f3273056c0cca/fonttools-4.62.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6acb4109f8bee00fec985c8c7afb02299e35e9c94b57287f3ea542f28bd0b0a7", size = 5100897, upload-time = "2026-03-13T13:52:38.102Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b", size = 5071078, upload-time = "2026-03-13T13:52:41.305Z" }, + { url = "https://files.pythonhosted.org/packages/e3/be/d378fca4c65ea1956fee6d90ace6e861776809cbbc5af22388a090c3c092/fonttools-4.62.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a5d8825e1140f04e6c99bb7d37a9e31c172f3bc208afbe02175339e699c710e1", size = 5076908, upload-time = "2026-03-13T13:52:44.122Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d9/ae6a1d0693a4185a84605679c8a1f719a55df87b9c6e8e817bfdd9ef5936/fonttools-4.62.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:268abb1cb221e66c014acc234e872b7870d8b5d4657a83a8f4205094c32d2416", size = 5202275, upload-time = "2026-03-13T13:52:46.591Z" }, + { url = "https://files.pythonhosted.org/packages/54/6c/af95d9c4efb15cabff22642b608342f2bd67137eea6107202d91b5b03184/fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53", size = 2293075, upload-time = "2026-03-13T13:52:48.711Z" }, + { url = "https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2", size = 2344593, upload-time = "2026-03-13T13:52:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" }, + { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, + { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" }, + { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" }, + { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" }, + { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" }, + { url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155, upload-time = "2026-03-13T13:53:16.132Z" }, + { url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802, upload-time = "2026-03-13T13:53:18.878Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926, upload-time = "2026-03-13T13:53:21.379Z" }, + { url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575, upload-time = "2026-03-13T13:53:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693, upload-time = "2026-03-13T13:53:26.569Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920, upload-time = "2026-03-13T13:53:29.329Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928, upload-time = "2026-03-13T13:53:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514, upload-time = "2026-03-13T13:53:34.991Z" }, + { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442, upload-time = "2026-03-13T13:53:37.509Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901, upload-time = "2026-03-13T13:53:40.55Z" }, + { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608, upload-time = "2026-03-13T13:53:42.798Z" }, + { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726, upload-time = "2026-03-13T13:53:45.405Z" }, + { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422, upload-time = "2026-03-13T13:53:48.326Z" }, + { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979, upload-time = "2026-03-13T13:53:51.366Z" }, + { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733, upload-time = "2026-03-13T13:53:53.606Z" }, + { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663, upload-time = "2026-03-13T13:53:56.23Z" }, + { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288, upload-time = "2026-03-13T13:53:59.397Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023, upload-time = "2026-03-13T13:54:01.642Z" }, + { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599, upload-time = "2026-03-13T13:54:04.495Z" }, + { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933, upload-time = "2026-03-13T13:54:07.557Z" }, + { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232, upload-time = "2026-03-13T13:54:10.611Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987, upload-time = "2026-03-13T13:54:13.569Z" }, + { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021, upload-time = "2026-03-13T13:54:16.98Z" }, + { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147, upload-time = "2026-03-13T13:54:19.416Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, ] [[package]] @@ -1326,7 +1357,7 @@ dependencies = [ { name = "google-auth", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "googleapis-common-protos", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "proto-plus", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, - { name = "protobuf", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "protobuf", version = "6.33.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "requests", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/22/98/586ec94553b569080caef635f98a3723db36a38eac0e3d7eb3ea9d2e4b9a/google_api_core-2.30.0.tar.gz", hash = "sha256:02edfa9fab31e17fc0befb5f161b3bf93c9096d99aed584625f38065c511ad9b", size = 176959, upload-time = "2026-02-18T20:28:11.926Z" } @@ -1336,16 +1367,15 @@ wheels = [ [[package]] name = "google-auth" -version = "2.49.0" +version = "2.49.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "pyasn1-modules", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, - { name = "rsa", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/59/7371175bfd949abfb1170aa076352131d7281bd9449c0f978604fc4431c3/google_auth-2.49.0.tar.gz", hash = "sha256:9cc2d9259d3700d7a257681f81052db6737495a1a46b610597f4b8bafe5286ae", size = 333444, upload-time = "2026-03-06T21:53:06.07Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/80/6a696a07d3d3b0a92488933532f03dbefa4a24ab80fb231395b9a2a1be77/google_auth-2.49.1.tar.gz", hash = "sha256:16d40da1c3c5a0533f57d268fe72e0ebb0ae1cc3b567024122651c045d879b64", size = 333825, upload-time = "2026-03-12T19:30:58.135Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/45/de64b823b639103de4b63dd193480dce99526bd36be6530c2dba85bf7817/google_auth-2.49.0-py3-none-any.whl", hash = "sha256:f893ef7307f19cf53700b7e2f61b5a6affe3aa0edf9943b13788920ab92d8d87", size = 240676, upload-time = "2026-03-06T21:52:38.304Z" }, + { url = "https://files.pythonhosted.org/packages/e9/eb/c6c2478d8a8d633460be40e2a8a6f8f429171997a35a96f81d3b680dec83/google_auth-2.49.1-py3-none-any.whl", hash = "sha256:195ebe3dca18eddd1b3db5edc5189b76c13e96f29e73043b923ebcf3f1a860f7", size = 240737, upload-time = "2026-03-12T19:30:53.159Z" }, ] [[package]] @@ -1353,7 +1383,7 @@ name = "googleapis-common-protos" version = "1.73.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "protobuf", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "protobuf", version = "6.33.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/99/96/a0205167fa0154f4a542fd6925bdc63d039d88dab3588b875078107e6f06/googleapis_common_protos-1.73.0.tar.gz", hash = "sha256:778d07cd4fbeff84c6f7c72102f0daf98fa2bfd3fa8bea426edc545588da0b5a", size = 147323, upload-time = "2026-03-06T21:53:09.727Z" } wheels = [ @@ -1502,34 +1532,34 @@ wheels = [ [[package]] name = "hf-xet" -version = "1.4.0" +version = "1.4.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/68/01/928fd82663fb0ab455551a178303a2960e65029da66b21974594f3a20a94/hf_xet-1.4.0.tar.gz", hash = "sha256:48e6ba7422b0885c9bbd8ac8fdf5c4e1306c3499b82d489944609cc4eae8ecbd", size = 660350, upload-time = "2026-03-11T18:50:03.354Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/4b/2351e30dddc6f3b47b3da0a0693ec1e82f8303b1a712faa299cf3552002b/hf_xet-1.4.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:76725fcbc5f59b23ac778f097d3029d6623e3cf6f4057d99d1fce1a7e3cff8fc", size = 3796397, upload-time = "2026-03-11T18:49:47.382Z" }, - { url = "https://files.pythonhosted.org/packages/48/3d/3db90ec0afb4e26e3330b1346b89fe0e9a3b7bfc2d6a2b2262787790d25f/hf_xet-1.4.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:76f1f73bee81a6e6f608b583908aa24c50004965358ac92c1dc01080a21bcd09", size = 3556235, upload-time = "2026-03-11T18:49:45.785Z" }, - { url = "https://files.pythonhosted.org/packages/57/6e/2a662af2cbc6c0a64ebe9fcdb8faf05b5205753d45a75a3011bb2209d0b4/hf_xet-1.4.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1818c2e5d6f15354c595d5111c6eb0e5a30a6c5c1a43eeaec20f19607cff0b34", size = 4213145, upload-time = "2026-03-11T18:49:38.009Z" }, - { url = "https://files.pythonhosted.org/packages/b9/4a/47c129affb540767e0e3e101039a95f4a73a292ec689c26e8f0c5b633f9d/hf_xet-1.4.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:70764d295f485db9cc9a6af76634ea00ec4f96311be7485f8f2b6144739b4ccf", size = 3991951, upload-time = "2026-03-11T18:49:36.396Z" }, - { url = "https://files.pythonhosted.org/packages/76/81/ec516cfc6281cfeef027b0919166b2fe11ab61fbe6131a2c43fafbed8b68/hf_xet-1.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9d3bd2a1e289f772c715ca88cdca8ceb3d8b5c9186534d5925410e531d849a3e", size = 4193205, upload-time = "2026-03-11T18:49:54.415Z" }, - { url = "https://files.pythonhosted.org/packages/49/48/0945b5e542ed6c6ce758b589b27895a449deab630dfcdee5a6ee0f699d21/hf_xet-1.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:06da3797f1fdd9a8f8dbc8c1bddfa0b914789b14580c375d29c32ee35c2c66ca", size = 4431022, upload-time = "2026-03-11T18:49:55.677Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ad/a4859c55ab4b67a4fde2849be8bde81917f54062050419b821071f199a9c/hf_xet-1.4.0-cp313-cp313t-win_amd64.whl", hash = "sha256:30b9d8f384ccec848124d51d883e91f3c88d430589e02a7b6d867730ab8d53ac", size = 3674977, upload-time = "2026-03-11T18:50:06.369Z" }, - { url = "https://files.pythonhosted.org/packages/4b/17/5bf3791e3a53e597913c2a775a48a98aaded9c2ddb5d1afaedabb55e2ed8/hf_xet-1.4.0-cp313-cp313t-win_arm64.whl", hash = "sha256:07ffdbf7568fa3245b24d949f0f3790b5276fb7293a5554ac4ec02e5f7e2b38d", size = 3536778, upload-time = "2026-03-11T18:50:04.974Z" }, - { url = "https://files.pythonhosted.org/packages/3f/a1/05a7f9d6069bf78405d3fc2464b6c76b167128501e13b4f1d6266e1d1f54/hf_xet-1.4.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:e2731044f3a18442f9f7a3dcf03b96af13dee311f03846a1df1f0553a3ea0fc6", size = 3796727, upload-time = "2026-03-11T18:49:52.889Z" }, - { url = "https://files.pythonhosted.org/packages/ac/8a/67abc642c2b32efcb7a257cdad8555c2904e23f18a1b4fec3aef1ebfe0fc/hf_xet-1.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b6f3729335fbc4baef60fe14fe32ef13ac9d377bdc898148c541e20c6056b504", size = 3555869, upload-time = "2026-03-11T18:49:51.313Z" }, - { url = "https://files.pythonhosted.org/packages/19/3d/4765367c64ee70db15fa771d5b94bf12540b85076a1d3210ebbfec42d477/hf_xet-1.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9c0c9f052738a024073d332c573275c8e33697a3ef3f5dd2fb4ef98216e1e74a", size = 4212980, upload-time = "2026-03-11T18:49:44.21Z" }, - { url = "https://files.pythonhosted.org/packages/0e/bf/6ad99ee0e7ca2318f912a87318e493d82d8f9aace6be81f774bd14b996df/hf_xet-1.4.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:f44b2324be75bfa399735996ac299fd478684c48ce47d12a42b5f24b1a99ccb8", size = 3991136, upload-time = "2026-03-11T18:49:42.512Z" }, - { url = "https://files.pythonhosted.org/packages/50/aa/932e25c69699076088f57e3c14f83ccae87bac25e755994f3362acc908d5/hf_xet-1.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:01de78b1ceddf8b38da001f7cc728b3bc3eb956948b18e8a1997ad6fc80fbe9d", size = 4192676, upload-time = "2026-03-11T18:50:00.216Z" }, - { url = "https://files.pythonhosted.org/packages/5c/0a/5e41339a294fd3450948989a47ecba9824d5bc1950cf767f928ecaf53a55/hf_xet-1.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cac8616e7a974105c3494735313f5ab0fb79b5accadec1a7a992859a15536a9", size = 4430729, upload-time = "2026-03-11T18:50:01.923Z" }, - { url = "https://files.pythonhosted.org/packages/9c/c1/c3d8ed9b7118e9166b0cf71dfd501da82f1abe306387e34e0f3ee59553ec/hf_xet-1.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3a5d9cb25095ceb3beab4843ae2d1b3e5746371ddbf2e5849f7be6a7d6f44df4", size = 3674989, upload-time = "2026-03-11T18:50:12.633Z" }, - { url = "https://files.pythonhosted.org/packages/65/bc/ea26cf774063cb09d7aaaa6cba9d341fb72b42ea99b8a94ca254dbafbbb0/hf_xet-1.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9b777674499dc037317db372c90a2dd91329b5f1ee93c645bb89155bb974f5bf", size = 3536805, upload-time = "2026-03-11T18:50:11.082Z" }, - { url = "https://files.pythonhosted.org/packages/9f/f9/a0b01945726aea81d2f213457cd5f5102a51e6fd1ca9f9769f561fb57501/hf_xet-1.4.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:981d2b5222c3baadf9567c135cf1d1073786f546b7745686978d46b5df179e16", size = 3799223, upload-time = "2026-03-11T18:49:49.884Z" }, - { url = "https://files.pythonhosted.org/packages/5d/30/ee62b0c00412f49a7e6f509f0104ee8808692278d247234336df48029349/hf_xet-1.4.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:cc8bd050349d0d7995ce7b3a3a18732a2a8062ce118a82431602088abb373428", size = 3560682, upload-time = "2026-03-11T18:49:48.633Z" }, - { url = "https://files.pythonhosted.org/packages/93/d0/0fe5c44dbced465a651a03212e1135d0d7f95d19faada692920cb56f8e38/hf_xet-1.4.0-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5d0c38d2a280d814280b8c15eead4a43c9781e7bf6fc37843cffab06dcdc76b9", size = 4218323, upload-time = "2026-03-11T18:49:40.921Z" }, - { url = "https://files.pythonhosted.org/packages/73/df/7b3c99a4e50442039eae498e5c23db634538eb3e02214109880cf1165d4c/hf_xet-1.4.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6a883f0250682ea888a1bd0af0631feda377e59ad7aae6fb75860ecee7ae0f93", size = 3997156, upload-time = "2026-03-11T18:49:39.634Z" }, - { url = "https://files.pythonhosted.org/packages/a9/26/47dfedf271c21d95346660ae1698e7ece5ab10791fa6c4f20c59f3713083/hf_xet-1.4.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:99e1d9255fe8ecdf57149bb0543d49e7b7bd8d491ddf431eb57e114253274df5", size = 4199052, upload-time = "2026-03-11T18:49:57.097Z" }, - { url = "https://files.pythonhosted.org/packages/8d/c0/346b9aad1474e881e65f998d5c1981695f0af045bc7a99204d9d86759a89/hf_xet-1.4.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b25f06ce42bd2d5f2e79d4a2d72f783d3ac91827c80d34a38cf8e5290dd717b0", size = 4434346, upload-time = "2026-03-11T18:49:58.67Z" }, - { url = "https://files.pythonhosted.org/packages/2e/d6/88ce9d6caa397c3b935263d5bcbe3ebf6c443f7c76098b8c523d206116b9/hf_xet-1.4.0-cp37-abi3-win_amd64.whl", hash = "sha256:8d6d7816d01e0fa33f315c8ca21b05eca0ce4cdc314f13b81d953e46cc6db11d", size = 3678921, upload-time = "2026-03-11T18:50:09.496Z" }, - { url = "https://files.pythonhosted.org/packages/65/eb/17d99ed253b28a9550ca479867c66a8af4c9bcd8cdc9a26b0c8007c2000a/hf_xet-1.4.0-cp37-abi3-win_arm64.whl", hash = "sha256:cb8d9549122b5b42f34b23b14c6b662a88a586a919d418c774d8dbbc4b3ce2aa", size = 3541054, upload-time = "2026-03-11T18:50:07.963Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/09/08/23c84a26716382c89151b5b447b4beb19e3345f3a93d3b73009a71a57ad3/hf_xet-1.4.2.tar.gz", hash = "sha256:b7457b6b482d9e0743bd116363239b1fa904a5e65deede350fbc0c4ea67c71ea", size = 672357, upload-time = "2026-03-13T06:58:51.077Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/06/e8cf74c3c48e5485c7acc5a990d0d8516cdfb5fdf80f799174f1287cc1b5/hf_xet-1.4.2-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:ac8202ae1e664b2c15cdfc7298cbb25e80301ae596d602ef7870099a126fcad4", size = 3796125, upload-time = "2026-03-13T06:58:33.177Z" }, + { url = "https://files.pythonhosted.org/packages/66/d4/b73ebab01cbf60777323b7de9ef05550790451eb5172a220d6b9845385ec/hf_xet-1.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6d2f8ee39fa9fba9af929f8c0d0482f8ee6e209179ad14a909b6ad78ffcb7c81", size = 3555985, upload-time = "2026-03-13T06:58:31.797Z" }, + { url = "https://files.pythonhosted.org/packages/ff/e7/ded6d1bd041c3f2bca9e913a0091adfe32371988e047dd3a68a2463c15a2/hf_xet-1.4.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4642a6cf249c09da8c1f87fe50b24b2a3450b235bf8adb55700b52f0ea6e2eb6", size = 4212085, upload-time = "2026-03-13T06:58:24.323Z" }, + { url = "https://files.pythonhosted.org/packages/97/c1/a0a44d1f98934f7bdf17f7a915b934f9fca44bb826628c553589900f6df8/hf_xet-1.4.2-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:769431385e746c92dc05492dde6f687d304584b89c33d79def8367ace06cb555", size = 3988266, upload-time = "2026-03-13T06:58:22.887Z" }, + { url = "https://files.pythonhosted.org/packages/7a/82/be713b439060e7d1f1d93543c8053d4ef2fe7e6922c5b31642eaa26f3c4b/hf_xet-1.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c9dd1c1bc4cc56168f81939b0e05b4c36dd2d28c13dc1364b17af89aa0082496", size = 4188513, upload-time = "2026-03-13T06:58:40.858Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/cbd4188b22abd80ebd0edbb2b3e87f2633e958983519980815fb8314eae5/hf_xet-1.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fca58a2ae4e6f6755cc971ac6fcdf777ea9284d7e540e350bb000813b9a3008d", size = 4428287, upload-time = "2026-03-13T06:58:42.601Z" }, + { url = "https://files.pythonhosted.org/packages/b2/4e/84e45b25e2e3e903ed3db68d7eafa96dae9a1d1f6d0e7fc85120347a852f/hf_xet-1.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:163aab46854ccae0ab6a786f8edecbbfbaa38fcaa0184db6feceebf7000c93c0", size = 3665574, upload-time = "2026-03-13T06:58:53.881Z" }, + { url = "https://files.pythonhosted.org/packages/ee/71/c5ac2b9a7ae39c14e91973035286e73911c31980fe44e7b1d03730c00adc/hf_xet-1.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:09b138422ecbe50fd0c84d4da5ff537d27d487d3607183cd10e3e53f05188e82", size = 3528760, upload-time = "2026-03-13T06:58:52.187Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0f/fcd2504015eab26358d8f0f232a1aed6b8d363a011adef83fe130bff88f7/hf_xet-1.4.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:949dcf88b484bb9d9276ca83f6599e4aa03d493c08fc168c124ad10b2e6f75d7", size = 3796493, upload-time = "2026-03-13T06:58:39.267Z" }, + { url = "https://files.pythonhosted.org/packages/82/56/19c25105ff81731ca6d55a188b5de2aa99d7a2644c7aa9de1810d5d3b726/hf_xet-1.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:41659966020d59eb9559c57de2cde8128b706a26a64c60f0531fa2318f409418", size = 3555797, upload-time = "2026-03-13T06:58:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/8933c073186849b5e06762aa89847991d913d10a95d1603eb7f2c3834086/hf_xet-1.4.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c588e21d80010119458dd5d02a69093f0d115d84e3467efe71ffb2c67c19146", size = 4212127, upload-time = "2026-03-13T06:58:30.539Z" }, + { url = "https://files.pythonhosted.org/packages/eb/01/f89ebba4e369b4ed699dcb60d3152753870996f41c6d22d3d7cac01310e1/hf_xet-1.4.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a296744d771a8621ad1d50c098d7ab975d599800dae6d48528ba3944e5001ba0", size = 3987788, upload-time = "2026-03-13T06:58:29.139Z" }, + { url = "https://files.pythonhosted.org/packages/84/4d/8a53e5ffbc2cc33bbf755382ac1552c6d9af13f623ed125fe67cc3e6772f/hf_xet-1.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f563f7efe49588b7d0629d18d36f46d1658fe7e08dce3fa3d6526e1c98315e2d", size = 4188315, upload-time = "2026-03-13T06:58:48.017Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b8/b7a1c1b5592254bd67050632ebbc1b42cc48588bf4757cb03c2ef87e704a/hf_xet-1.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5b2e0132c56d7ee1bf55bdb638c4b62e7106f6ac74f0b786fed499d5548c5570", size = 4428306, upload-time = "2026-03-13T06:58:49.502Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0c/40779e45b20e11c7c5821a94135e0207080d6b3d76e7b78ccb413c6f839b/hf_xet-1.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:2f45c712c2fa1215713db10df6ac84b49d0e1c393465440e9cb1de73ecf7bbf6", size = 3665826, upload-time = "2026-03-13T06:58:59.88Z" }, + { url = "https://files.pythonhosted.org/packages/51/4c/e2688c8ad1760d7c30f7c429c79f35f825932581bc7c9ec811436d2f21a0/hf_xet-1.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:6d53df40616f7168abfccff100d232e9d460583b9d86fa4912c24845f192f2b8", size = 3529113, upload-time = "2026-03-13T06:58:58.491Z" }, + { url = "https://files.pythonhosted.org/packages/b4/86/b40b83a2ff03ef05c4478d2672b1fc2b9683ff870e2b25f4f3af240f2e7b/hf_xet-1.4.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:71f02d6e4cdd07f344f6844845d78518cc7186bd2bc52d37c3b73dc26a3b0bc5", size = 3800339, upload-time = "2026-03-13T06:58:36.245Z" }, + { url = "https://files.pythonhosted.org/packages/64/2e/af4475c32b4378b0e92a587adb1aa3ec53e3450fd3e5fe0372a874531c00/hf_xet-1.4.2-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e9b38d876e94d4bdcf650778d6ebbaa791dd28de08db9736c43faff06ede1b5a", size = 3559664, upload-time = "2026-03-13T06:58:34.787Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4c/781267da3188db679e601de18112021a5cb16506fe86b246e22c5401a9c4/hf_xet-1.4.2-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:77e8c180b7ef12d8a96739a4e1e558847002afe9ea63b6f6358b2271a8bdda1c", size = 4217422, upload-time = "2026-03-13T06:58:27.472Z" }, + { url = "https://files.pythonhosted.org/packages/68/47/d6cf4a39ecf6c7705f887a46f6ef5c8455b44ad9eb0d391aa7e8a2ff7fea/hf_xet-1.4.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c3b3c6a882016b94b6c210957502ff7877802d0dbda8ad142c8595db8b944271", size = 3992847, upload-time = "2026-03-13T06:58:25.989Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ef/e80815061abff54697239803948abc665c6b1d237102c174f4f7a9a5ffc5/hf_xet-1.4.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9d9a634cc929cfbaf2e1a50c0e532ae8c78fa98618426769480c58501e8c8ac2", size = 4193843, upload-time = "2026-03-13T06:58:44.59Z" }, + { url = "https://files.pythonhosted.org/packages/54/75/07f6aa680575d9646c4167db6407c41340cbe2357f5654c4e72a1b01ca14/hf_xet-1.4.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6b0932eb8b10317ea78b7da6bab172b17be03bbcd7809383d8d5abd6a2233e04", size = 4432751, upload-time = "2026-03-13T06:58:46.533Z" }, + { url = "https://files.pythonhosted.org/packages/cd/71/193eabd7e7d4b903c4aa983a215509c6114915a5a237525ec562baddb868/hf_xet-1.4.2-cp37-abi3-win_amd64.whl", hash = "sha256:ad185719fb2e8ac26f88c8100562dbf9dbdcc3d9d2add00faa94b5f106aea53f", size = 3671149, upload-time = "2026-03-13T06:58:57.07Z" }, + { url = "https://files.pythonhosted.org/packages/b4/7e/ccf239da366b37ba7f0b36095450efae4a64980bdc7ec2f51354205fdf39/hf_xet-1.4.2-cp37-abi3-win_arm64.whl", hash = "sha256:32c012286b581f783653e718c1862aea5b9eb140631685bb0c5e7012c8719a87", size = 3533426, upload-time = "2026-03-13T06:58:55.46Z" }, ] [[package]] @@ -1587,6 +1617,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7c/73/41a531906b581c96c04ab60122e97e5b3065e3ba6a7a2f0b868f7d754065/ibm_platform_services-0.74.0-py3-none-any.whl", hash = "sha256:041fc3ec6e46b18a1e08d290de81fdde0f27bed0c55fcc11ca505233c03bf651", size = 381755, upload-time = "2026-02-27T17:27:55.203Z" }, ] +[[package]] +name = "ibm-quantum-schemas" +version = "0.5.20260320" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "qiskit", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "samplomatic", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/37/0b266ba46430a6df872099fa00f0c0a8082f823cfcf2d59f8d7b37f8de36/ibm_quantum_schemas-0.5.20260320.tar.gz", hash = "sha256:d442c876dac5b04a58590681257883a7d4c4fbe1a19576f0de9718b81fbf4f08", size = 85689, upload-time = "2026-03-20T17:20:10.489Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/6a/7c3c25dd98bb2746a1023839f560cbcc9c0dd21172fba09f6c79590edc5c/ibm_quantum_schemas-0.5.20260320-py3-none-any.whl", hash = "sha256:58ce17988b62d4dc672e1966109a36671d48f2a7f32bcdcb4138ea7a81df7e30", size = 88639, upload-time = "2026-03-20T17:20:02.251Z" }, +] + [[package]] name = "idna" version = "3.11" @@ -1609,14 +1653,43 @@ wheels = [ name = "importlib-metadata" version = "8.7.1" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11'", +] dependencies = [ - { name = "zipp" }, + { name = "zipp", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, ] +[[package]] +name = "importlib-metadata" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.13.*' and sys_platform == 'win32'", +] +dependencies = [ + { name = "zipp", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7", size = 27789, upload-time = "2026-03-20T06:42:55.665Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -1634,7 +1707,8 @@ dependencies = [ { name = "appnope", marker = "sys_platform == 'darwin'" }, { name = "comm" }, { name = "debugpy" }, - { name = "ipython" }, + { name = "ipython", version = "8.38.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "ipython", version = "9.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, { name = "jupyter-client" }, { name = "jupyter-core" }, { name = "matplotlib-inline" }, @@ -1654,17 +1728,31 @@ wheels = [ name = "ipython" version = "8.38.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11'", +] dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "decorator" }, + { name = "colorama", marker = "python_full_version != '3.13.*' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "jedi" }, - { name = "matplotlib-inline" }, + { name = "jedi", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "matplotlib-inline", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit" }, - { name = "pygments" }, - { name = "stack-data" }, - { name = "traitlets" }, + { name = "prompt-toolkit", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "pygments", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "stack-data", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "traitlets", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e5/61/1810830e8b93c72dcd3c0f150c80a00c3deb229562d9423807ec92c3a539/ipython-8.38.0.tar.gz", hash = "sha256:9cfea8c903ce0867cc2f23199ed8545eb741f3a69420bfcf3743ad1cec856d39", size = 5513996, upload-time = "2026-01-05T10:59:06.901Z" } @@ -1672,13 +1760,48 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9f/df/db59624f4c71b39717c423409950ac3f2c8b2ce4b0aac843112c7fb3f721/ipython-8.38.0-py3-none-any.whl", hash = "sha256:750162629d800ac65bb3b543a14e7a74b0e88063eac9b92124d4b2aa3f6d8e86", size = 831813, upload-time = "2026-01-05T10:59:04.239Z" }, ] +[[package]] +name = "ipython" +version = "9.11.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.13.*' and sys_platform == 'win32'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "jedi", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "matplotlib-inline", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "pygments", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "stack-data", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "traitlets", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/28/a4698eda5a8928a45d6b693578b135b753e14fa1c2b36ee9441e69a45576/ipython-9.11.0.tar.gz", hash = "sha256:2a94bc4406b22ecc7e4cb95b98450f3ea493a76bec8896cda11b78d7752a6667", size = 4427354, upload-time = "2026-03-05T08:57:30.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl", hash = "sha256:6922d5bcf944c6e525a76a0a304451b60a2b6f875e86656d8bc2dfda5d710e19", size = 624222, upload-time = "2026-03-05T08:57:28.94Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + [[package]] name = "ipywidgets" version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "comm", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, - { name = "ipython", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "ipython", version = "8.38.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "jupyterlab-widgets", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "traitlets", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "widgetsnbextension", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, @@ -1755,7 +1878,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "click" }, - { name = "importlib-metadata" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "importlib-metadata", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, { name = "nbclient" }, { name = "nbformat" }, { name = "pyyaml" }, @@ -1960,6 +2084,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, ] +[[package]] +name = "mako" +version = "1.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, +] + [[package]] name = "markdown" version = "3.10.2" @@ -2215,8 +2351,8 @@ wheels = [ [[package]] name = "mqt-bench" -version = "2.1.1.dev120+g66fc99251" -source = { git = "https://github.com/munich-quantum-toolkit/bench.git?rev=qce-experiments#66fc99251fb482a4e77469db35d763889c5db796" } +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "networkx" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, @@ -2225,6 +2361,10 @@ dependencies = [ { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "scikit-learn", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] +sdist = { url = "https://files.pythonhosted.org/packages/4e/db/09b516994e17d6ceac4c5d5d10b5e52b3a0ab33e6b2e640448172430c97f/mqt_bench-2.2.1.tar.gz", hash = "sha256:93f919096c8c0663b1802462b6fb45a69887ad82445bf009181c01ba3b3d4494", size = 911722, upload-time = "2026-03-23T15:58:05.914Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/1f/30bc2ac12441ab1962fac2c4e76e1dbeb60c44edee064b633221aec8c73e/mqt_bench-2.2.1-py3-none-any.whl", hash = "sha256:7d832dc979ae9d5e7d876cd11eb497c7811c079360dc2571b5b9d91c87dc701f", size = 81590, upload-time = "2026-03-23T15:58:04.471Z" }, +] [[package]] name = "mqt-predictor" @@ -2234,18 +2374,22 @@ dependencies = [ { name = "mqt-bench" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "optuna" }, { name = "pytket" }, { name = "pytket-qiskit" }, { name = "qiskit" }, { name = "qiskit-ibm-ai-local-transpiler" }, - { name = "qiskit-ibm-runtime" }, + { name = "qiskit-ibm-runtime", version = "0.45.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "qiskit-ibm-runtime", version = "0.46.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, { name = "qiskit-ibm-transpiler", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "rich" }, + { name = "safetensors" }, { name = "sb3-contrib" }, { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "scikit-learn", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "stable-baselines3" }, { name = "tensorboard" }, + { name = "torch-geometric" }, { name = "tqdm" }, { name = "typing-extensions" }, ] @@ -2285,6 +2429,7 @@ requires-dist = [ { name = "numpy", marker = "python_full_version >= '3.11'", specifier = ">=1.24" }, { name = "numpy", marker = "python_full_version >= '3.12'", specifier = ">=1.26" }, { name = "numpy", marker = "python_full_version >= '3.13'", specifier = ">=2.1" }, + { name = "optuna", specifier = ">=3.0.0" }, { name = "pytket", specifier = ">=1.29.0" }, { name = "pytket-qiskit", specifier = ">=0.65.0" }, { name = "qiskit", specifier = ">=1.4.2" }, @@ -2292,10 +2437,12 @@ requires-dist = [ { name = "qiskit-ibm-runtime", specifier = ">=0.37.0" }, { name = "qiskit-ibm-transpiler", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'", specifier = ">=0.15.0" }, { name = "rich", specifier = ">=12.6.0" }, + { name = "safetensors", specifier = ">=0.7.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-geometric", specifier = ">=2.7.0" }, { name = "tqdm", specifier = ">=4.66.0" }, { name = "typing-extensions", specifier = ">=4.1" }, ] @@ -2529,9 +2676,11 @@ name = "myst-nb" version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "importlib-metadata", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, { name = "ipykernel" }, - { name = "ipython" }, + { name = "ipython", version = "8.38.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "ipython", version = "9.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, { name = "jupyter-cache" }, { name = "myst-parser", version = "4.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "myst-parser", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, @@ -2998,32 +3147,32 @@ parser = [ [[package]] name = "opentelemetry-api" -version = "1.39.1" +version = "1.40.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "typing-extensions", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c", size = 65767, upload-time = "2025-12-11T13:32:39.182Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950", size = 66356, upload-time = "2025-12-11T13:32:17.304Z" }, + { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.39.1" +version = "1.40.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/9d/22d241b66f7bbde88a3bfa6847a351d2c46b84de23e71222c6aae25c7050/opentelemetry_exporter_otlp_proto_common-1.39.1.tar.gz", hash = "sha256:763370d4737a59741c89a67b50f9e39271639ee4afc999dadfe768541c027464", size = 20409, upload-time = "2025-12-11T13:32:40.885Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/02/ffc3e143d89a27ac21fd557365b98bd0653b98de8a101151d5805b5d4c33/opentelemetry_exporter_otlp_proto_common-1.39.1-py3-none-any.whl", hash = "sha256:08f8a5862d64cc3435105686d0216c1365dc5701f86844a8cd56597d0c764fde", size = 18366, upload-time = "2025-12-11T13:32:20.2Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.39.1" +version = "1.40.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, @@ -3034,28 +3183,28 @@ dependencies = [ { name = "opentelemetry-sdk", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "typing-extensions", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/53/48/b329fed2c610c2c32c9366d9dc597202c9d1e58e631c137ba15248d8850f/opentelemetry_exporter_otlp_proto_grpc-1.39.1.tar.gz", hash = "sha256:772eb1c9287485d625e4dbe9c879898e5253fea111d9181140f51291b5fec3ad", size = 24650, upload-time = "2025-12-11T13:32:41.429Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/7f/b9e60435cfcc7590fa87436edad6822240dddbc184643a2a005301cc31f4/opentelemetry_exporter_otlp_proto_grpc-1.40.0.tar.gz", hash = "sha256:bd4015183e40b635b3dab8da528b27161ba83bf4ef545776b196f0fb4ec47740", size = 25759, upload-time = "2026-03-04T14:17:24.4Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/a3/cc9b66575bd6597b98b886a2067eea2693408d2d5f39dad9ab7fc264f5f3/opentelemetry_exporter_otlp_proto_grpc-1.39.1-py3-none-any.whl", hash = "sha256:fa1c136a05c7e9b4c09f739469cbdb927ea20b34088ab1d959a849b5cc589c18", size = 19766, upload-time = "2025-12-11T13:32:21.027Z" }, + { url = "https://files.pythonhosted.org/packages/96/6f/7ee0980afcbdcd2d40362da16f7f9796bd083bf7f0b8e038abfbc0300f5d/opentelemetry_exporter_otlp_proto_grpc-1.40.0-py3-none-any.whl", hash = "sha256:2aa0ca53483fe0cf6405087a7491472b70335bc5c7944378a0a8e72e86995c52", size = 20304, upload-time = "2026-03-04T14:17:05.942Z" }, ] [[package]] name = "opentelemetry-exporter-prometheus" -version = "0.60b1" +version = "0.61b0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "opentelemetry-sdk", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "prometheus-client", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/14/39/7dafa6fff210737267bed35a8855b6ac7399b9e582b8cf1f25f842517012/opentelemetry_exporter_prometheus-0.60b1.tar.gz", hash = "sha256:a4011b46906323f71724649d301b4dc188aaa068852e814f4df38cc76eac616b", size = 14976, upload-time = "2025-12-11T13:32:42.944Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/20/9e818fd364d12e8d0cfdce4a3b2d82e24d98c4ceebb315de6b6770b5f214/opentelemetry_exporter_prometheus-0.61b0.tar.gz", hash = "sha256:7c4919bd8e79abd62b610767e80f42c9c3a06c5183f4dd9141eedeb57aea284b", size = 15136, upload-time = "2026-03-04T14:17:26.275Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/0d/4be6bf5477a3eb3d917d2f17d3c0b6720cd6cb97898444a61d43cc983f5c/opentelemetry_exporter_prometheus-0.60b1-py3-none-any.whl", hash = "sha256:49f59178de4f4590e3cef0b8b95cf6e071aae70e1f060566df5546fad773b8fd", size = 13019, upload-time = "2025-12-11T13:32:23.974Z" }, + { url = "https://files.pythonhosted.org/packages/02/4a/b65d40e94d1d930aee73a1a2857211ee6ab10ce3686cbdae5eea78cd9d34/opentelemetry_exporter_prometheus-0.61b0-py3-none-any.whl", hash = "sha256:3013b41f4370143d48d219a2351473761423e5882fa4c213811eaefacba39cb7", size = 13149, upload-time = "2026-03-04T14:17:08.983Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.60b1" +version = "0.61b0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, @@ -3063,14 +3212,14 @@ dependencies = [ { name = "packaging", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "wrapt", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/41/0f/7e6b713ac117c1f5e4e3300748af699b9902a2e5e34c9cf443dde25a01fa/opentelemetry_instrumentation-0.60b1.tar.gz", hash = "sha256:57ddc7974c6eb35865af0426d1a17132b88b2ed8586897fee187fd5b8944bd6a", size = 31706, upload-time = "2025-12-11T13:36:42.515Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/d2/6788e83c5c86a2690101681aeef27eeb2a6bf22df52d3f263a22cee20915/opentelemetry_instrumentation-0.60b1-py3-none-any.whl", hash = "sha256:04480db952b48fb1ed0073f822f0ee26012b7be7c3eac1a3793122737c78632d", size = 33096, upload-time = "2025-12-11T13:35:33.067Z" }, + { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, ] [[package]] name = "opentelemetry-instrumentation-requests" -version = "0.60b1" +version = "0.61b0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, @@ -3078,57 +3227,98 @@ dependencies = [ { name = "opentelemetry-semantic-conventions", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "opentelemetry-util-http", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9d/4a/bb9d47d7424fc33aeba75275256ae6e6031f44b6a9a3f778d611c0c3ac27/opentelemetry_instrumentation_requests-0.60b1.tar.gz", hash = "sha256:9a1063c16c44a3ba6e81870c4fa42a0fac3ecef5a4d60a11d0976eec9046f3d4", size = 16366, upload-time = "2025-12-11T13:37:12.456Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/c7/7a47cb85c7aa93a9c820552e414889185bcf91245271d12e5d443e5f834d/opentelemetry_instrumentation_requests-0.61b0.tar.gz", hash = "sha256:15f879ce8fb206bd7e6fdc61663ea63481040a845218c0cf42902ce70bd7e9d9", size = 18379, upload-time = "2026-03-04T14:20:46.959Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/7f/969b59a5acccb4c35317421843d63d7853ad7a18078ca3a9b80c248be448/opentelemetry_instrumentation_requests-0.60b1-py3-none-any.whl", hash = "sha256:eec9fac3fab84737f663a2e08b12cb095b4bd67643b24587a8ecfa3cf4d0ca4c", size = 13141, upload-time = "2025-12-11T13:36:23.696Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a1/a7a133b273d1f53950f16a370fc94367eff472c9c2576e8e9e28c62dcc9f/opentelemetry_instrumentation_requests-0.61b0-py3-none-any.whl", hash = "sha256:cce19b379949fe637eb73ba39b02c57d2d0805447ca6d86534aa33fcb141f683", size = 14207, upload-time = "2026-03-04T14:19:51.765Z" }, ] [[package]] name = "opentelemetry-proto" -version = "1.39.1" +version = "1.40.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "protobuf", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "protobuf", version = "6.33.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/1d/f25d76d8260c156c40c97c9ed4511ec0f9ce353f8108ca6e7561f82a06b2/opentelemetry_proto-1.39.1.tar.gz", hash = "sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8", size = 46152, upload-time = "2025-12-11T13:32:48.681Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/95/b40c96a7b5203005a0b03d8ce8cd212ff23f1793d5ba289c87a097571b18/opentelemetry_proto-1.39.1-py3-none-any.whl", hash = "sha256:22cdc78efd3b3765d09e68bfbd010d4fc254c9818afd0b6b423387d9dee46007", size = 72535, upload-time = "2025-12-11T13:32:33.866Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.39.1" +version = "1.40.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "opentelemetry-semantic-conventions", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "typing-extensions", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6", size = 171460, upload-time = "2025-12-11T13:32:49.369Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/98/e91cf858f203d86f4eccdf763dcf01cf03f1dae80c3750f7e635bfa206b6/opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c", size = 132565, upload-time = "2025-12-11T13:32:35.069Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.60b1" +version = "0.61b0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "typing-extensions", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953", size = 137935, upload-time = "2025-12-11T13:32:50.487Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/5e/5958555e09635d09b75de3c4f8b9cae7335ca545d77392ffe7331534c402/opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb", size = 219982, upload-time = "2025-12-11T13:32:36.955Z" }, + { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, ] [[package]] name = "opentelemetry-util-http" -version = "0.60b1" +version = "0.61b0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/50/fc/c47bb04a1d8a941a4061307e1eddfa331ed4d0ab13d8a9781e6db256940a/opentelemetry_util_http-0.60b1.tar.gz", hash = "sha256:0d97152ca8c8a41ced7172d29d3622a219317f74ae6bb3027cfbdcf22c3cc0d6", size = 11053, upload-time = "2025-12-11T13:37:25.115Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/3c/f0196223efc5c4ca19f8fad3d5462b171ac6333013335ce540c01af419e9/opentelemetry_util_http-0.61b0.tar.gz", hash = "sha256:1039cb891334ad2731affdf034d8fb8b48c239af9b6dd295e5fabd07f1c95572", size = 11361, upload-time = "2026-03-04T14:20:57.01Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/5c/d3f1733665f7cd582ef0842fb1d2ed0bc1fba10875160593342d22bba375/opentelemetry_util_http-0.60b1-py3-none-any.whl", hash = "sha256:66381ba28550c91bee14dcba8979ace443444af1ed609226634596b4b0faf199", size = 8947, upload-time = "2025-12-11T13:36:37.151Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e5/c08aaaf2f64288d2b6ef65741d2de5454e64af3e050f34285fb1907492fe/opentelemetry_util_http-0.61b0-py3-none-any.whl", hash = "sha256:8e715e848233e9527ea47e275659ea60a57a75edf5206a3b937e236a6da5fc33", size = 9281, upload-time = "2026-03-04T14:20:08.364Z" }, +] + +[[package]] +name = "optuna" +version = "4.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alembic" }, + { name = "colorlog" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "sqlalchemy" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/9b/62f120fb2ecbc4338bee70c5a3671c8e561714f3aa1a046b897ff142050e/optuna-4.8.0.tar.gz", hash = "sha256:6f7043e9f8ecb5e607af86a7eb00fb5ec2be26c3b08c201209a73d36aff37a38", size = 482603, upload-time = "2026-03-16T04:59:58.659Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/24/7c731839566d30dc70556d9824ef17692d896c15e3df627bce8c16f753e1/optuna-4.8.0-py3-none-any.whl", hash = "sha256:c57a7682679c36bfc9bca0da430698179e513874074b71bebedb0334964ab930", size = 419456, upload-time = "2026-03-16T04:59:56.977Z" }, +] + +[[package]] +name = "orjson" +version = "3.11.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/45/b268004f745ede84e5798b48ee12b05129d19235d0e15267aa57dcdb400b/orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49", size = 6144992, upload-time = "2026-02-02T15:38:49.29Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/fa/92fc5d3d402b87a8b28277a9ed35386218a6a5287c7fe5ee9b9f02c53fb2/orjson-3.11.7-cp310-cp310-win32.whl", hash = "sha256:ae9e0b37a834cef7ce8f99de6498f8fad4a2c0bf6bfc3d02abd8ed56aa15b2de", size = 127968, upload-time = "2026-02-02T15:37:23.178Z" }, + { url = "https://files.pythonhosted.org/packages/07/29/a576bf36d73d60df06904d3844a9df08e25d59eba64363aaf8ec2f9bff41/orjson-3.11.7-cp310-cp310-win_amd64.whl", hash = "sha256:d772afdb22555f0c58cfc741bdae44180122b3616faa1ecadb595cd526e4c993", size = 125128, upload-time = "2026-02-02T15:37:24.329Z" }, + { url = "https://files.pythonhosted.org/packages/d4/0b/ccb7ee1a65b37e8eeb8b267dc953561d72370e85185e459616d4345bab34/orjson-3.11.7-cp311-cp311-win32.whl", hash = "sha256:c43b8b5bab288b6b90dac410cca7e986a4fa747a2e8f94615aea407da706980d", size = 127828, upload-time = "2026-02-02T15:37:42.241Z" }, + { url = "https://files.pythonhosted.org/packages/af/9e/55c776dffda3f381e0f07d010a4f5f3902bf48eaba1bb7684d301acd4924/orjson-3.11.7-cp311-cp311-win_amd64.whl", hash = "sha256:6543001328aa857187f905308a028935864aefe9968af3848401b6fe80dbb471", size = 124941, upload-time = "2026-02-02T15:37:43.444Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8e/424a620fa7d263b880162505fb107ef5e0afaa765b5b06a88312ac291560/orjson-3.11.7-cp311-cp311-win_arm64.whl", hash = "sha256:1ee5cc7160a821dfe14f130bc8e63e7611051f964b463d9e2a3a573204446a4d", size = 126245, upload-time = "2026-02-02T15:37:45.18Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e4/15ecc67edb3ddb3e2f46ae04475f2d294e8b60c1825fbe28a428b93b3fbd/orjson-3.11.7-cp312-cp312-win32.whl", hash = "sha256:f4f7c956b5215d949a1f65334cf9d7612dde38f20a95f2315deef167def91a6f", size = 127923, upload-time = "2026-02-02T15:38:02.75Z" }, + { url = "https://files.pythonhosted.org/packages/34/70/2e0855361f76198a3965273048c8e50a9695d88cd75811a5b46444895845/orjson-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:bf742e149121dc5648ba0a08ea0871e87b660467ef168a3a5e53bc1fbd64bb74", size = 125007, upload-time = "2026-02-02T15:38:04.032Z" }, + { url = "https://files.pythonhosted.org/packages/68/40/c2051bd19fc467610fed469dc29e43ac65891571138f476834ca192bc290/orjson-3.11.7-cp312-cp312-win_arm64.whl", hash = "sha256:26c3b9132f783b7d7903bf1efb095fed8d4a3a85ec0d334ee8beff3d7a4749d5", size = 126089, upload-time = "2026-02-02T15:38:05.297Z" }, + { url = "https://files.pythonhosted.org/packages/0a/5b/6ebcf3defc1aab3a338ca777214966851e92efb1f30dc7fc8285216e6d1b/orjson-3.11.7-cp313-cp313-win32.whl", hash = "sha256:7477aa6a6ec6139c5cb1cc7b214643592169a5494d200397c7fc95d740d5fcf3", size = 127914, upload-time = "2026-02-02T15:38:23.511Z" }, + { url = "https://files.pythonhosted.org/packages/00/04/c6f72daca5092e3117840a1b1e88dfc809cc1470cf0734890d0366b684a1/orjson-3.11.7-cp313-cp313-win_amd64.whl", hash = "sha256:b9f95dcdea9d4f805daa9ddf02617a89e484c6985fa03055459f90e87d7a0757", size = 124986, upload-time = "2026-02-02T15:38:24.836Z" }, + { url = "https://files.pythonhosted.org/packages/03/ba/077a0f6f1085d6b806937246860fafbd5b17f3919c70ee3f3d8d9c713f38/orjson-3.11.7-cp313-cp313-win_arm64.whl", hash = "sha256:800988273a014a0541483dc81021247d7eacb0c845a9d1a34a422bc718f41539", size = 126045, upload-time = "2026-02-02T15:38:26.216Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d3/f19b47ce16820cc2c480f7f1723e17f6d411b3a295c60c8ad3aa9ff1c96a/orjson-3.11.7-cp314-cp314-win32.whl", hash = "sha256:89e13dd3f89f1c38a9c9eba5fbf7cdc2d1feca82f5f290864b4b7a6aac704576", size = 127997, upload-time = "2026-02-02T15:38:45.06Z" }, + { url = "https://files.pythonhosted.org/packages/12/df/172771902943af54bf661a8d102bdf2e7f932127968080632bda6054b62c/orjson-3.11.7-cp314-cp314-win_amd64.whl", hash = "sha256:845c3e0d8ded9c9271cd79596b9b552448b885b97110f628fb687aee2eed11c1", size = 124985, upload-time = "2026-02-02T15:38:46.388Z" }, + { url = "https://files.pythonhosted.org/packages/6f/1c/f2a8d8a1b17514660a614ce5f7aac74b934e69f5abc2700cc7ced882a009/orjson-3.11.7-cp314-cp314-win_arm64.whl", hash = "sha256:4a2e9c5be347b937a2e0203866f12bba36082e89b402ddb9e927d5822e43088d", size = 126038, upload-time = "2026-02-02T15:38:47.703Z" }, ] [[package]] @@ -3555,7 +3745,7 @@ name = "proto-plus" version = "1.27.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "protobuf", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "protobuf", version = "6.33.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3a/02/8832cde80e7380c600fbf55090b6ab7b62bd6825dbedde6d6657c15a1f8e/proto_plus-1.27.1.tar.gz", hash = "sha256:912a7460446625b792f6448bade9e55cd4e41e6ac10e27009ef71a7f317fa147", size = 56929, upload-time = "2026-02-02T17:34:49.035Z" } wheels = [ @@ -3564,17 +3754,45 @@ wheels = [ [[package]] name = "protobuf" -version = "6.33.5" +version = "6.33.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, - { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, - { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, - { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, - { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, +] + +[[package]] +name = "protobuf" +version = "7.34.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.13.*' and sys_platform == 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/6b/a0e95cad1ad7cc3f2c6821fcab91671bd5b78bd42afb357bb4765f29bc41/protobuf-7.34.1.tar.gz", hash = "sha256:9ce42245e704cc5027be797c1db1eb93184d44d1cdd71811fb2d9b25ad541280", size = 454708, upload-time = "2026-03-20T17:34:47.036Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/29/64de04a0ac142fb685fd09999bc3d337943fb386f3a0ec57f92fd8203f97/protobuf-7.34.1-cp310-abi3-win32.whl", hash = "sha256:34b84ce27680df7cca9f231043ada0daa55d0c44a2ddfaa58ec1d0d89d8bf60a", size = 426628, upload-time = "2026-03-20T17:34:42.536Z" }, + { url = "https://files.pythonhosted.org/packages/4d/87/cb5e585192a22b8bd457df5a2c16a75ea0db9674c3a0a39fc9347d84e075/protobuf-7.34.1-cp310-abi3-win_amd64.whl", hash = "sha256:e97b55646e6ce5cbb0954a8c28cd39a5869b59090dfaa7df4598a7fba869468c", size = 437901, upload-time = "2026-03-20T17:34:44.112Z" }, + { url = "https://files.pythonhosted.org/packages/88/95/608f665226bca68b736b79e457fded9a2a38c4f4379a4a7614303d9db3bc/protobuf-7.34.1-py3-none-any.whl", hash = "sha256:bb3812cd53aefea2b028ef42bd780f5b96407247f20c6ef7c679807e9d188f11", size = 170715, upload-time = "2026-03-20T17:34:45.384Z" }, ] [[package]] @@ -3683,11 +3901,11 @@ wheels = [ [[package]] name = "pyasn1" -version = "0.6.2" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/b6/6e630dff89739fcd427e3f72b3d905ce0acb85a45d4ec3e2678718a3487f/pyasn1-0.6.2.tar.gz", hash = "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b", size = 146586, upload-time = "2026-01-16T18:04:18.534Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/b5/a96872e5184f354da9c84ae119971a0a4c221fe9b27a4d94bd43f2596727/pyasn1-0.6.2-py3-none-any.whl", hash = "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf", size = 83371, upload-time = "2026-01-16T18:04:17.174Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, ] [[package]] @@ -3702,6 +3920,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, ] +[[package]] +name = "pybase64" +version = "1.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/b8/4ed5c7ad5ec15b08d35cc79ace6145d5c1ae426e46435f4987379439dfea/pybase64-1.4.3.tar.gz", hash = "sha256:c2ed274c9e0ba9c8f9c4083cfe265e66dd679126cd9c2027965d807352f3f053", size = 137272, upload-time = "2025-12-06T13:27:04.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/95/f987081bf6bc1d1eda3012dae1b06ad427732ef9933a632cb8b58f9917f8/pybase64-1.4.3-cp310-cp310-win32.whl", hash = "sha256:4bdd07ef017515204ee6eaab17e1ad05f83c0ccb5af8ae24a0fe6d9cb5bb0b7a", size = 33622, upload-time = "2025-12-06T13:22:47.348Z" }, + { url = "https://files.pythonhosted.org/packages/79/28/c169a769fe90128f16d394aad87b2096dd4bf2f035ae0927108a46b617df/pybase64-1.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:5db0b6bbda15110db2740c61970a8fda3bf9c93c3166a3f57f87c7865ed1125c", size = 35799, upload-time = "2025-12-06T13:22:48.731Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f2/bdbe6af0bd4f3fe5bc70e77ead7f7d523bb9d3ca3ad50ac42b9adbb9ca14/pybase64-1.4.3-cp310-cp310-win_arm64.whl", hash = "sha256:f96367dfc82598569aa02b1103ebd419298293e59e1151abda2b41728703284b", size = 31158, upload-time = "2025-12-06T13:22:50.021Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2c/8338b6d3da3c265002839e92af0a80d6db88385c313c73f103dfb800c857/pybase64-1.4.3-cp311-cp311-win32.whl", hash = "sha256:e9a8b81984e3c6fb1db9e1614341b0a2d98c0033d693d90c726677db1ffa3a4c", size = 33639, upload-time = "2025-12-06T13:23:11.9Z" }, + { url = "https://files.pythonhosted.org/packages/39/dc/32efdf2f5927e5449cc341c266a1bbc5fecd5319a8807d9c5405f76e6d02/pybase64-1.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:a90a8fa16a901fabf20de824d7acce07586e6127dc2333f1de05f73b1f848319", size = 35797, upload-time = "2025-12-06T13:23:13.174Z" }, + { url = "https://files.pythonhosted.org/packages/da/59/eda4f9cb0cbce5a45f0cd06131e710674f8123a4d570772c5b9694f88559/pybase64-1.4.3-cp311-cp311-win_arm64.whl", hash = "sha256:61d87de5bc94d143622e94390ec3e11b9c1d4644fe9be3a81068ab0f91056f59", size = 31160, upload-time = "2025-12-06T13:23:15.696Z" }, + { url = "https://files.pythonhosted.org/packages/75/2e/a9e28941c6dab6f06e6d3f6783d3373044be9b0f9a9d3492c3d8d2260ac0/pybase64-1.4.3-cp312-cp312-win32.whl", hash = "sha256:7bca1ed3a5df53305c629ca94276966272eda33c0d71f862d2d3d043f1e1b91a", size = 33686, upload-time = "2025-12-06T13:23:37.848Z" }, + { url = "https://files.pythonhosted.org/packages/83/e3/507ab649d8c3512c258819c51d25c45d6e29d9ca33992593059e7b646a33/pybase64-1.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:9f2da8f56d9b891b18b4daf463a0640eae45a80af548ce435be86aa6eff3603b", size = 35833, upload-time = "2025-12-06T13:23:38.877Z" }, + { url = "https://files.pythonhosted.org/packages/bc/8a/6eba66cd549a2fc74bb4425fd61b839ba0ab3022d3c401b8a8dc2cc00c7a/pybase64-1.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:0631d8a2d035de03aa9bded029b9513e1fee8ed80b7ddef6b8e9389ffc445da0", size = 31185, upload-time = "2025-12-06T13:23:39.908Z" }, + { url = "https://files.pythonhosted.org/packages/5c/8d/20b68f11adfc4c22230e034b65c71392e3e338b413bf713c8945bd2ccfb3/pybase64-1.4.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:27fdff227a0c0e182e0ba37a99109645188978b920dfb20d8b9c17eeee370d0d", size = 30932, upload-time = "2025-12-06T13:23:43.348Z" }, + { url = "https://files.pythonhosted.org/packages/f7/79/b1b550ac6bff51a4880bf6e089008b2e1ca16f2c98db5e039a08ac3ad157/pybase64-1.4.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2a8204f1fdfec5aa4184249b51296c0de95445869920c88123978304aad42df1", size = 31394, upload-time = "2025-12-06T13:23:44.317Z" }, + { url = "https://files.pythonhosted.org/packages/82/70/b5d7c5932bf64ee1ec5da859fbac981930b6a55d432a603986c7f509c838/pybase64-1.4.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:874fc2a3777de6baf6aa921a7aa73b3be98295794bea31bd80568a963be30767", size = 38078, upload-time = "2025-12-06T13:23:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/af/51/0f5714af7aeef96e30f968e4371d75ad60558aaed3579d7c6c8f1c43c18a/pybase64-1.4.3-cp313-cp313-win32.whl", hash = "sha256:b1623730c7892cf5ed0d6355e375416be6ef8d53ab9b284f50890443175c0ac3", size = 33684, upload-time = "2025-12-06T13:24:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ad/0cea830a654eb08563fb8214150ef57546ece1cc421c09035f0e6b0b5ea9/pybase64-1.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:8369887590f1646a5182ca2fb29252509da7ae31d4923dbb55d3e09da8cc4749", size = 35832, upload-time = "2025-12-06T13:24:06.35Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0d/eec2a8214989c751bc7b4cad1860eb2c6abf466e76b77508c0f488c96a37/pybase64-1.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:860b86bca71e5f0237e2ab8b2d9c4c56681f3513b1bf3e2117290c1963488390", size = 31175, upload-time = "2025-12-06T13:24:07.419Z" }, + { url = "https://files.pythonhosted.org/packages/42/10/abb7757c330bb869ebb95dab0c57edf5961ffbd6c095c8209cbbf75d117d/pybase64-1.4.3-cp313-cp313t-win32.whl", hash = "sha256:46d75c9387f354c5172582a9eaae153b53a53afeb9c19fcf764ea7038be3bd8b", size = 33965, upload-time = "2025-12-06T13:24:28.548Z" }, + { url = "https://files.pythonhosted.org/packages/63/a0/2d4e5a59188e9e6aed0903d580541aaea72dcbbab7bf50fb8b83b490b6c3/pybase64-1.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:d7344625591d281bec54e85cbfdab9e970f6219cac1570f2aa140b8c942ccb81", size = 36207, upload-time = "2025-12-06T13:24:29.646Z" }, + { url = "https://files.pythonhosted.org/packages/1f/05/95b902e8f567b4d4b41df768ccc438af618f8d111e54deaf57d2df46bd76/pybase64-1.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:28a3c60c55138e0028313f2eccd321fec3c4a0be75e57a8d3eb883730b1b0880", size = 31505, upload-time = "2025-12-06T13:24:30.687Z" }, + { url = "https://files.pythonhosted.org/packages/e3/71/cf62b261d431857e8e054537a5c3c24caafa331de30daede7b2c6c558501/pybase64-1.4.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8f183ac925a48046abe047360fe3a1b28327afb35309892132fe1915d62fb282", size = 30939, upload-time = "2025-12-06T13:24:34.001Z" }, + { url = "https://files.pythonhosted.org/packages/24/3e/d12f92a3c1f7c6ab5d53c155bff9f1084ba997a37a39a4f781ccba9455f3/pybase64-1.4.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30bf3558e24dcce4da5248dcf6d73792adfcf4f504246967e9db155be4c439ad", size = 31401, upload-time = "2025-12-06T13:24:35.11Z" }, + { url = "https://files.pythonhosted.org/packages/9b/3d/9c27440031fea0d05146f8b70a460feb95d8b4e3d9ca8f45c972efb4c3d3/pybase64-1.4.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a674b419de318d2ce54387dd62646731efa32b4b590907800f0bd40675c1771d", size = 38075, upload-time = "2025-12-06T13:24:36.53Z" }, + { url = "https://files.pythonhosted.org/packages/b9/84/5a3dce8d7a0040a5c0c14f0fe1311cd8db872913fa04438071b26b0dac04/pybase64-1.4.3-cp314-cp314-win32.whl", hash = "sha256:28b2a1bb0828c0595dc1ea3336305cd97ff85b01c00d81cfce4f92a95fb88f56", size = 34200, upload-time = "2025-12-06T13:24:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/57/bc/ce7427c12384adee115b347b287f8f3cf65860b824d74fe2c43e37e81c1f/pybase64-1.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:33338d3888700ff68c3dedfcd49f99bfc3b887570206130926791e26b316b029", size = 36323, upload-time = "2025-12-06T13:25:01.708Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1b/2b8ffbe9a96eef7e3f6a5a7be75995eebfb6faaedc85b6da6b233e50c778/pybase64-1.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:62725669feb5acb186458da2f9353e88ae28ef66bb9c4c8d1568b12a790dfa94", size = 31584, upload-time = "2025-12-06T13:25:02.801Z" }, + { url = "https://files.pythonhosted.org/packages/28/86/a236ecfc5b494e1e922da149689f690abc84248c7c1358f5605b8c9fdd60/pybase64-1.4.3-cp314-cp314t-win32.whl", hash = "sha256:343b1901103cc72362fd1f842524e3bb24978e31aea7ff11e033af7f373f66ab", size = 34513, upload-time = "2025-12-06T13:25:24.592Z" }, + { url = "https://files.pythonhosted.org/packages/56/ce/ca8675f8d1352e245eb012bfc75429ee9cf1f21c3256b98d9a329d44bf0f/pybase64-1.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:57aff6f7f9dea6705afac9d706432049642de5b01080d3718acc23af87c5af76", size = 36702, upload-time = "2025-12-06T13:25:25.72Z" }, + { url = "https://files.pythonhosted.org/packages/3b/30/4a675864877397179b09b720ee5fcb1cf772cf7bebc831989aff0a5f79c1/pybase64-1.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:e906aa08d4331e799400829e0f5e4177e76a3281e8a4bc82ba114c6b30e405c9", size = 31904, upload-time = "2025-12-06T13:25:26.826Z" }, + { url = "https://files.pythonhosted.org/packages/4f/5b/19c725dc3aaa6281f2ce3ea4c1628d154a40dd99657d1381995f8096768b/pybase64-1.4.3-graalpy311-graalpy242_311_native-win_amd64.whl", hash = "sha256:03cea70676ffbd39a1ab7930a2d24c625b416cacc9d401599b1d29415a43ab6a", size = 35880, upload-time = "2025-12-06T13:26:24.663Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6d/41faa414cde66ec023b0ca8402a8f11cb61731c3dc27c082909cbbd1f929/pybase64-1.4.3-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:f7537fa22ae56a0bf51e4b0ffc075926ad91c618e1416330939f7ef366b58e3b", size = 36231, upload-time = "2025-12-06T13:26:31.656Z" }, + { url = "https://files.pythonhosted.org/packages/95/b7/4d37bd3577d1aa6c732dc099087fe027c48873e223de3784b095e5653f8b/pybase64-1.4.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bd47076f736b27a8b0f9b30d93b6bb4f5af01b0dc8971f883ed3b75934f39a99", size = 36125, upload-time = "2025-12-06T13:26:39.78Z" }, + { url = "https://files.pythonhosted.org/packages/ee/2b/e18ee7c5ee508a82897f021c1981533eca2940b5f072fc6ed0906c03a7a7/pybase64-1.4.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:debf737e09b8bf832ba86f5ecc3d3dbd0e3021d6cd86ba4abe962d6a5a77adb3", size = 36134, upload-time = "2025-12-06T13:26:47.35Z" }, +] + [[package]] name = "pybtex" version = "0.25.1" @@ -3882,11 +4139,14 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.11.0" +version = "2.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, ] [[package]] @@ -3931,16 +4191,16 @@ wheels = [ [[package]] name = "pytest-cov" -version = "7.0.0" +version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] [[package]] @@ -3957,20 +4217,20 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.1.3" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d7/7e/9f3b0dd3a074a6c3e1e79f35e465b1f2ee4b262d619de00cfce523cc9b24/python_discovery-1.1.3.tar.gz", hash = "sha256:7acca36e818cd88e9b2ba03e045ad7e93e1713e29c6bbfba5d90202310b7baa5", size = 56945, upload-time = "2026-03-10T15:08:15.038Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/90/bcce6b46823c9bec1757c964dc37ed332579be512e17a30e9698095dcae4/python_discovery-1.2.0.tar.gz", hash = "sha256:7d33e350704818b09e3da2bd419d37e21e7c30db6e0977bb438916e06b41b5b1", size = 58055, upload-time = "2026-03-19T01:43:08.248Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/80/73211fc5bfbfc562369b4aa61dc1e4bf07dc7b34df7b317e4539316b809c/python_discovery-1.1.3-py3-none-any.whl", hash = "sha256:90e795f0121bc84572e737c9aa9966311b9fde44ffb88a5953b3ec9b31c6945e", size = 31485, upload-time = "2026-03-10T15:08:13.06Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3c/2005227cb951df502412de2fa781f800663cccbef8d90ec6f1b371ac2c0d/python_discovery-1.2.0-py3-none-any.whl", hash = "sha256:1e108f1bbe2ed0ef089823d28805d5ad32be8e734b86a5f212bf89b71c266e4a", size = 31524, upload-time = "2026-03-19T01:43:07.045Z" }, ] [[package]] name = "pytket" -version = "2.15.0" +version = "2.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "graphviz" }, @@ -3986,21 +4246,21 @@ dependencies = [ { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/fc/29bb412acc4eddc0c239e72829841903a3c05aa8a697bcf09857e0bf3e08/pytket-2.15.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:bc74b0f7c1d82ca78e8551898af4e495b85ad5cdd3555d2b440b8ba9b1b22996", size = 5545820, upload-time = "2026-03-06T16:14:04.561Z" }, - { url = "https://files.pythonhosted.org/packages/99/63/accaa09e0c22e901d215a36bea06837d36526269575ed56e4929f5a6e9fa/pytket-2.15.0-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:2bced5c39e0fe6f1ff0a49c535457c28d30223476bdf5167750ef33bfb01de80", size = 6234625, upload-time = "2026-03-06T16:14:07.269Z" }, - { url = "https://files.pythonhosted.org/packages/09/5f/d3c4e909443b3db4b28610b2b973c354d7ef84884f7dcdf3ddc4ddfca92d/pytket-2.15.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6fffa0fd0342f6f40383bd63e3eba689fbea528a46355e327bcf340b4c88eb81", size = 7578383, upload-time = "2026-03-06T16:14:09.222Z" }, - { url = "https://files.pythonhosted.org/packages/fa/7a/3c484d1e41c5b6546b541a2d2442c11e4ffbecdfe07e835660fe4e97f858/pytket-2.15.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ed39f31efc61c7300dc2fa9a9560a9308f52478df7a9a708e39985946ad58e9b", size = 8315661, upload-time = "2026-03-06T16:14:11.489Z" }, - { url = "https://files.pythonhosted.org/packages/99/e2/b35608917e91439345b0728aeb2fc4f936483934e05f17751871c701bb17/pytket-2.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:a46998ceb0db73334dba8d9461cc9c74205a69d0352ff4cffc0882a26e174ab4", size = 9787575, upload-time = "2026-03-06T16:14:13.881Z" }, - { url = "https://files.pythonhosted.org/packages/33/4b/044fd8912071040a9f98f2fd85a506093c8387e964238b381114b679f81d/pytket-2.15.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c48e39632cb923df213e0c7b5d5890bb5b0595a753ba694c94a76c6b3975a88b", size = 5547759, upload-time = "2026-03-06T16:14:16.474Z" }, - { url = "https://files.pythonhosted.org/packages/a0/0e/7cf5cc85d8bca150f25c4ca682abc6325cadd98154e798b5157fb98cb9a9/pytket-2.15.0-cp311-cp311-macosx_15_0_x86_64.whl", hash = "sha256:28aa4e25248123e5d67b1415e3d9d8ec384283ea88d577839f6d7659f5232579", size = 6237033, upload-time = "2026-03-06T16:14:18.324Z" }, - { url = "https://files.pythonhosted.org/packages/d3/04/fd10c47e1b3c20382fbe7fc33f8d3cd20463bbb55e5785ee5ef510663928/pytket-2.15.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30c48893dee117d47771962833347225571fd87f3ef1807b5b3d18fcf06ef271", size = 7579634, upload-time = "2026-03-06T16:14:20.064Z" }, - { url = "https://files.pythonhosted.org/packages/5a/35/b0a5f93b6715d6acac4860f00298eb38aba3bcb8c018a776d33603d58122/pytket-2.15.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13ff34fdb516ebd3a203ff782b02680d9f3d5d5e04ee0c986aac3faab0f3bc98", size = 8316190, upload-time = "2026-03-06T16:14:22.164Z" }, - { url = "https://files.pythonhosted.org/packages/e1/c4/cc295210bd8d9270d70d038fe4a8752fc0c9c38c49e20ca443843b4e5e24/pytket-2.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:31893e38e1294fec67d81e9c4a85a137a5912136eaca0f6d61dd1ef358244214", size = 9788736, upload-time = "2026-03-06T16:14:24.421Z" }, - { url = "https://files.pythonhosted.org/packages/b6/bf/2e693cd9a80dd57f94cc10d5003622ce33dc824c88713c37760a9329a495/pytket-2.15.0-cp312-abi3-macosx_14_0_arm64.whl", hash = "sha256:035815cdf331e97c6c2cd56f60882b30f768721975d7c2f082f5d044237b503e", size = 5528681, upload-time = "2026-03-06T16:14:26.697Z" }, - { url = "https://files.pythonhosted.org/packages/62/35/11e65debec9d99286cc8ac84aa3f0404710a84852f318468bfc59e0ca205/pytket-2.15.0-cp312-abi3-macosx_15_0_x86_64.whl", hash = "sha256:06672344186a51b97ff7b779e71585092e425475abbabf535a65c23786afb042", size = 6218603, upload-time = "2026-03-06T16:14:28.736Z" }, - { url = "https://files.pythonhosted.org/packages/2c/e6/ef8ff958f7d03f48f6e22d23bb6a2ea15ad7c89c9d3de42bcc88b568b23a/pytket-2.15.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb12d0598ba47ef18c25544fcb83a063e7e67c9cce36de127ead507014c657c3", size = 7529689, upload-time = "2026-03-06T16:14:30.599Z" }, - { url = "https://files.pythonhosted.org/packages/56/20/8e1599d5a7c9ce28cf78371f6b9d364f4d1739b682279aba352b9b8f5c6b/pytket-2.15.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7947db076949df22f3941cc90290afc470a73ade5d4dc1e6509d5fc58965ad5", size = 8258227, upload-time = "2026-03-06T16:14:32.417Z" }, - { url = "https://files.pythonhosted.org/packages/a5/dd/68fdb8117de1d8684b3416a42611fb64e59f0d674500062e3fdc50dd78b7/pytket-2.15.0-cp312-abi3-win_amd64.whl", hash = "sha256:1afae2c1b08692c9ac04a4cc9421e3aa039cb7e468d4bd2011a802f67c88dfb1", size = 9761390, upload-time = "2026-03-06T16:14:34.463Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3c/ea309a72e0c1ca8e51f66ee6adb28887bc50af0864192fa1dfbb260a6aba/pytket-2.16.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:ea53862f3f8ee2b48b564048d0d6684285abe96aedffbc9f387ead78540648a8", size = 5548141, upload-time = "2026-03-25T14:52:37.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/c6/66ebf277713d915e902b6de0a3e57105874af72e6dc869692229247cb8c9/pytket-2.16.0-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:859597eaf8c50b0dc4ed3c6f5bba01bfaa020995bd84bf9503650f3cb30e9be3", size = 6236954, upload-time = "2026-03-25T14:52:39.746Z" }, + { url = "https://files.pythonhosted.org/packages/02/51/daa28cff7c720cbed715f84c5ea843c571285af33e285afea2a776fedb52/pytket-2.16.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f25a60846202a1e9c5f165fb4112503b96638361f5a7d619f854676045dd7494", size = 7580705, upload-time = "2026-03-25T14:52:41.862Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e4/f47935bbf44a5c68b4f3dee51b4d97ef6417b07b7c34f6401297beaed21e/pytket-2.16.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e6b87e9810e80ab57539619ddf6f9d4639cd2aea1a01fb58433e48677aadb00c", size = 8317981, upload-time = "2026-03-25T14:52:43.806Z" }, + { url = "https://files.pythonhosted.org/packages/17/df/291ac431bbe8f0249171e4014e7f1dc9c4b63d961a1b09404ea8d986bb85/pytket-2.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:1e548c94443a934ecd318609988535a3b2741443b12716955328263ee69811f8", size = 9790329, upload-time = "2026-03-25T14:52:45.85Z" }, + { url = "https://files.pythonhosted.org/packages/9b/74/5c28d78be92d9b464908793d950f00125d08ac1f36683803fbdabdd9a80e/pytket-2.16.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:e069de503b6fc86d3a46ff4c17ab60374215c3713b0659d0a07eb88b9aee6716", size = 5550081, upload-time = "2026-03-25T14:52:48.711Z" }, + { url = "https://files.pythonhosted.org/packages/22/d9/85e3754d84c90c3fc80a9aa728f2a1335de0c60c6f210449651b6dd80687/pytket-2.16.0-cp311-cp311-macosx_15_0_x86_64.whl", hash = "sha256:c005ca4b76b1c896110e6ab810a9c466140f66c11b74dcf36f8695b4a4a3c28e", size = 6239367, upload-time = "2026-03-25T14:52:50.648Z" }, + { url = "https://files.pythonhosted.org/packages/7c/84/a7dd8e96c8ebfa2b5a8fcfb31cb7619685cd4bb40363edaf301f20dda2fc/pytket-2.16.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:825c7b8f98f69e0a9e73008131ad53a0e7eb786e373d4f7d4b5b0865b7a4fd5a", size = 7581960, upload-time = "2026-03-25T14:52:53.139Z" }, + { url = "https://files.pythonhosted.org/packages/58/28/63e7429910065a107bc651fe7cf83581ea3c292560c7a2d5ea20591aa582/pytket-2.16.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e55a82f066a31015f4223bc556aefb06c38eedab01d91030af0b4e7c8fffebf2", size = 8318509, upload-time = "2026-03-25T14:52:55.603Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ea/4b2561423c24212d96d63b1655fdcf86c5d9803455b42b5cfe1f96e4c705/pytket-2.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:5db78f3600c9626ea050c3a35267097950bfd4e0b002469c066d2a5ffbbca8ab", size = 9791412, upload-time = "2026-03-25T14:52:57.876Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c0/e5fa70ba5d6fcc58ef009938c2037022830ab16d71eba2564abdeff5c40c/pytket-2.16.0-cp312-abi3-macosx_14_0_arm64.whl", hash = "sha256:30e918546c5c9a64157eb59fdbc64a436517ef23b52d0c4ccf04e1707709ae80", size = 5530999, upload-time = "2026-03-25T14:53:00.271Z" }, + { url = "https://files.pythonhosted.org/packages/12/00/387750d1fcf08fc2935b566c71bf7556b810072c28c3415cf495e9b3f625/pytket-2.16.0-cp312-abi3-macosx_15_0_x86_64.whl", hash = "sha256:581d3de487a6098c95b695947104e9ab1a934b5081fa5cf17d78a97ff8b8b701", size = 6220914, upload-time = "2026-03-25T14:53:02.614Z" }, + { url = "https://files.pythonhosted.org/packages/d1/7f/68d933790e9a9353a07147744336468e61df57dd28976665c071ec6ec9e1/pytket-2.16.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:58fce2fb058dace4c0335ce83a49dc8061f78c9c363fb5439706435cb9be1797", size = 7532010, upload-time = "2026-03-25T14:53:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/29/e5/6891f73f189065a8616c44473748eb2c3e4feb8ce4ec85bfa50fb031bb7e/pytket-2.16.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc0a6be06dc03c38423775cdd5bd9e6df81518f9e78128d9deaca20663eff358", size = 8260551, upload-time = "2026-03-25T14:53:07.337Z" }, + { url = "https://files.pythonhosted.org/packages/c8/23/d856913e5c5501e82890791ec9934add317e6f344a7d495e5dac141cc9a0/pytket-2.16.0-cp312-abi3-win_amd64.whl", hash = "sha256:b00f7eb42a84bb77d1b33a460e78392f51026724a1ec7e090a1f018db51c566e", size = 9764004, upload-time = "2026-03-25T14:53:09.658Z" }, ] [[package]] @@ -4013,8 +4273,10 @@ dependencies = [ { name = "pytket" }, { name = "qiskit" }, { name = "qiskit-aer" }, - { name = "qiskit-ibm-runtime" }, - { name = "symengine" }, + { name = "qiskit-ibm-runtime", version = "0.45.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "qiskit-ibm-runtime", version = "0.46.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "symengine", version = "0.13.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "symengine", version = "0.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/b1/e1/fc355cc6f7a7a33a3842761d6b5a05b4d53b2327079a4da6f17560df1f77/pytket_qiskit-0.77.0-py3-none-any.whl", hash = "sha256:257bc937d80da6e2aaea4ee3a9d8b211e049dc9bfa9ef8a9e93c07f70daad326", size = 90751, upload-time = "2026-02-02T11:04:25.616Z" }, @@ -4168,7 +4430,7 @@ wheels = [ [[package]] name = "qiskit" -version = "2.3.0" +version = "2.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dill" }, @@ -4180,15 +4442,15 @@ dependencies = [ { name = "stevedore" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/8e/d5279454717d780f32eec4dfa6a7ef256ea7382b2f8fef2c715fc6f1180e/qiskit-2.3.0.tar.gz", hash = "sha256:e0a00c6681b8d04171c5cdb928837d992676f8aa4a07c390d446e54babaf6c1e", size = 3903621, upload-time = "2026-01-08T19:36:33.691Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/9a/a366dd56b6a4520e5b2856e47d573c6813b4c3f62f28969a37205fd845ab/qiskit-2.3.1.tar.gz", hash = "sha256:7b3b7e1c8a50f7f423204143a1bd9f21bf27659c57459d582eaff4035d8d7f75", size = 3909915, upload-time = "2026-03-13T00:43:03.257Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/79/22a6c61a94fef8eac09f968355c15c0d3b0800110152bbcf9cbed5bfc807/qiskit-2.3.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bfeba21f216f64b051f9ac98653e9999d4c1bcb4f328aca06ad059aaef86586b", size = 8791989, upload-time = "2026-01-08T21:32:53.48Z" }, - { url = "https://files.pythonhosted.org/packages/d7/55/bb8efb2b6e46e5b8c7d147973646fa71677e63ecd2378af4db2829795be6/qiskit-2.3.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:5f29f3b6a2b5738463bc667ddb2bcc79a83fb434ab9a8033487b64a399f71bef", size = 7872461, upload-time = "2026-01-08T19:36:26.458Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c6/d928648d417c5c2e0c50481ef4f197f2ad8ca74d361e163e0c5d73253cbf/qiskit-2.3.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b437262a14ab961703153f79f38de5fde408601b0eda1f9c34b57498c912ef33", size = 8223957, upload-time = "2026-01-08T19:36:28.767Z" }, - { url = "https://files.pythonhosted.org/packages/54/76/33ee3519c3ff88b6154ccce13d24a33c0dc0fc9b22df8f216f1833cb0981/qiskit-2.3.0-cp310-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7c413dace7a9b1d30bbbfeb2f90c4564dfcf6ed3c0744e026d4aca84f8fbd2bb", size = 9042802, upload-time = "2026-01-08T21:32:55.692Z" }, - { url = "https://files.pythonhosted.org/packages/73/9f/478788b16f653cdf151c7f2f040ba41b97e355d3bf9348ee2f0f685a5ea6/qiskit-2.3.0-cp310-abi3-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:ae0b1ec4c24ff8867599a5e8a46efaaf6d5477d1c4533427b7ae516054f52b2f", size = 8701281, upload-time = "2026-01-08T21:32:57.947Z" }, - { url = "https://files.pythonhosted.org/packages/de/22/fd8e7d4869641f60d9b7ce6d688b59520c8c8be8c8e4b51d76fb85b59f42/qiskit-2.3.0-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:24fd70ee3ac47fe029096e39b1c39abf3e7f145c723df90f9600468099c12aa4", size = 8888244, upload-time = "2026-01-08T19:36:30.259Z" }, - { url = "https://files.pythonhosted.org/packages/c3/bb/0fbcf43f9daba43f1eb31ecf7d36afcbcf705457d9df6e8e6f38f07b4851/qiskit-2.3.0-cp310-abi3-win_amd64.whl", hash = "sha256:8679a426725785701629f0af905a7a5b6bc25b0dcd7fe4f4c9f8be63d89e832c", size = 8630467, upload-time = "2026-01-08T19:36:31.958Z" }, + { url = "https://files.pythonhosted.org/packages/47/70/dba6afec66d2bf4df64b7059cd144bf0c775d998cb57b941d1065b331d28/qiskit-2.3.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:25a85465e8c860627e8a565cb9af824b2df1a2c42f5958b1a7b014aa085b32d6", size = 8614500, upload-time = "2026-03-13T02:30:24.247Z" }, + { url = "https://files.pythonhosted.org/packages/55/0b/47d67d58b4de120e10d49255b635429bc162a982b0cd58cf75b1a9f594fa/qiskit-2.3.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:b1404b72987d0c7db32203a87565775115f6a77b2a3539d40eb1e354df6d08bb", size = 7819922, upload-time = "2026-03-13T00:42:54.205Z" }, + { url = "https://files.pythonhosted.org/packages/02/f0/2af86ce0e11b06248bc6a66550acf98c19e606cb317c27bab4fbed2ce37f/qiskit-2.3.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:68eec14320654065a158bf73b3d6fffaa31e865fe61b0db1510cfeeea76c87b0", size = 8176336, upload-time = "2026-03-13T00:42:56.891Z" }, + { url = "https://files.pythonhosted.org/packages/8a/fc/0bf71353c68b9090ed05e5c1d08730301c58a2b00f3001f0050f032bdcc9/qiskit-2.3.1-cp310-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:96545ad531c5e2ff6eecd348501620e0db0f3d73f49c20037ec3b5b2e9268f94", size = 8915234, upload-time = "2026-03-13T02:30:27.055Z" }, + { url = "https://files.pythonhosted.org/packages/1b/0c/19ffdb15d2e72156de406fe27cad26c6015071a385e6b48c6db87d8ce676/qiskit-2.3.1-cp310-abi3-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a1a030b4a0cd75f3299c9866a3e9489a02078efc343abd42eec74ad2a5f2fa80", size = 8513126, upload-time = "2026-03-13T02:30:30.288Z" }, + { url = "https://files.pythonhosted.org/packages/11/06/41f4197fcb3c38e99f22f400ea25375c9057190cd094cd6c0f97be730796/qiskit-2.3.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:350027279bb171e9746fee5756c674872df97f4608b079129d4edb25b5bb1fdd", size = 8833222, upload-time = "2026-03-13T00:42:59.092Z" }, + { url = "https://files.pythonhosted.org/packages/a5/db/6ddc52eaf22ab1f95ee6251ff7a375c0a1a7453b5864528992303b166198/qiskit-2.3.1-cp310-abi3-win_amd64.whl", hash = "sha256:f884abd92c0173e246705f89f8b4808badaa0e1b167ed3277a3cb83c2564b4b8", size = 8639580, upload-time = "2026-03-13T00:43:01.022Z" }, ] [package.optional-dependencies] @@ -4196,7 +4458,7 @@ qasm3-import = [ { name = "qiskit-qasm3-import" }, ] qpy-compat = [ - { name = "symengine", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "symengine", version = "0.13.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "sympy", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, ] @@ -4290,7 +4552,8 @@ dependencies = [ { name = "jsonschema" }, { name = "networkx" }, { name = "qiskit" }, - { name = "qiskit-ibm-runtime" }, + { name = "qiskit-ibm-runtime", version = "0.45.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "qiskit-ibm-runtime", version = "0.46.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/4a/f8/ca66875254a7d97b139aecf59ee415709acbad48059010a62e4d3e4a2298/qiskit_ibm_ai_local_transpiler-0.5.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:44483902c501253f3ccab414eb9abd0d2f913bb673989b4840788888cec3be5a", size = 97495155, upload-time = "2025-12-01T09:40:35.925Z" }, @@ -4319,23 +4582,63 @@ wheels = [ name = "qiskit-ibm-runtime" version = "0.45.1" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11'", +] dependencies = [ - { name = "ibm-platform-services" }, + { name = "ibm-platform-services", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "packaging" }, - { name = "pydantic" }, - { name = "python-dateutil" }, - { name = "qiskit" }, - { name = "requests" }, - { name = "requests-ntlm" }, - { name = "urllib3" }, + { name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13') or (python_full_version >= '3.11' and sys_platform != 'win32') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, + { name = "packaging", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "pydantic", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "python-dateutil", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "qiskit", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "requests", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "requests-ntlm", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "urllib3", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cc/50/82917ad29bfeab4f03163de8fe61b8832811c7413baa857300b6cc461619/qiskit_ibm_runtime-0.45.1.tar.gz", hash = "sha256:cc8e9360ba5e76df6c6663bd37644ce3e1252554eb1faba0fa5922fc95cd38c2", size = 1525919, upload-time = "2026-02-12T17:17:55.735Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ee/98/92edc7c34deaf698db5bcaa31e0bbd43d4e317b2526b9fdf2b5a957956e4/qiskit_ibm_runtime-0.45.1-py3-none-any.whl", hash = "sha256:b48a791e414f85d0d835fa8df110ba0e996393b06e2458ad498c7e68fc06e7da", size = 1453522, upload-time = "2026-02-12T17:17:54.023Z" }, ] +[[package]] +name = "qiskit-ibm-runtime" +version = "0.46.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.13.*' and sys_platform == 'win32'", +] +dependencies = [ + { name = "ibm-platform-services", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "ibm-quantum-schemas", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "packaging", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "pybase64", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "pydantic", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "python-dateutil", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "qiskit", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "requests", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "requests-ntlm", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "samplomatic", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "urllib3", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/fb/fec3db1c1278808d50f7c35bdb14e05e4b3fe0a5bbaeb50a587d9f6e2fa5/qiskit_ibm_runtime-0.46.1.tar.gz", hash = "sha256:22d0fa71ccffd9db03f19e3d05fc5da6bacdf54bb5a8347b25a275623de82645", size = 1565577, upload-time = "2026-03-23T21:05:42.403Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/3a/135cbeb2de66b43b6a31fab309912c4fbee0be525f392eb1e4baf6d2e55c/qiskit_ibm_runtime-0.46.1-py3-none-any.whl", hash = "sha256:da5f1d4e7876722a345c61d3fb6416b3d3df726a7a760e70f6388e1339fd7f49", size = 1490117, upload-time = "2026-03-23T21:05:40.807Z" }, +] + [[package]] name = "qiskit-ibm-transpiler" version = "0.18.0" @@ -4384,14 +4687,14 @@ wheels = [ [[package]] name = "qiskit-serverless" -version = "0.30.0" +version = "0.30.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "certifi", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "cloudpickle", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, - { name = "importlib-metadata", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, - { name = "ipython", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "ipython", version = "8.38.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "ipywidgets", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.13') or (python_full_version >= '3.11' and sys_platform != 'win32') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, @@ -4401,15 +4704,15 @@ dependencies = [ { name = "opentelemetry-sdk", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "pyarrow", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "qiskit", extra = ["qpy-compat"], marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, - { name = "qiskit-ibm-runtime", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "qiskit-ibm-runtime", version = "0.45.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "ray", extra = ["default"], marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "requests", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "tqdm", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "zipp", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/5d/747b875afc82ee80df16f0084a9b145727917b52fc29f4375f09beaf7811/qiskit_serverless-0.30.0.tar.gz", hash = "sha256:d4e0a4fa14f596916a4adc86df8d5e9a9a0c75aae919eb81c3d9fbacb2e4f66e", size = 39614, upload-time = "2026-03-04T17:09:05.42Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/2c/eeb6c7d73a41c8ae1c57833770b8f5364d66230d043cb87608d524fda0ee/qiskit_serverless-0.30.1.tar.gz", hash = "sha256:11f809f7ef190c8e77d1ce0937311966bf9d4f72bed6df27f9f020f7e1594f8f", size = 54126, upload-time = "2026-03-19T16:48:05.958Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/fb/2606a7e7b29af2658c909936e1178c07cff69abd8e854a31fb66c2687b5e/qiskit_serverless-0.30.0-py3-none-any.whl", hash = "sha256:91055c94b950aeee5c44e823162a19b6c5ba547cb60f8a56c35fdef1a4063179", size = 54865, upload-time = "2026-03-04T17:09:04.118Z" }, + { url = "https://files.pythonhosted.org/packages/65/03/a6a7c9791f16a7e9bf6b4e0aad72423fa02255a80c854343db7e9ff23637/qiskit_serverless-0.30.1-py3-none-any.whl", hash = "sha256:3d22c11b698feaee7967bdaa10d7316d45f2c04afc89e1263cb1a46de122e83b", size = 73728, upload-time = "2026-03-19T16:48:04.974Z" }, ] [[package]] @@ -4426,7 +4729,7 @@ wheels = [ [[package]] name = "ray" -version = "2.54.0" +version = "2.54.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, @@ -4434,26 +4737,26 @@ dependencies = [ { name = "jsonschema", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "msgpack", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "packaging", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, - { name = "protobuf", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "protobuf", version = "6.33.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "pyyaml", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, { name = "requests", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/64/13/b86d791b41f33220335eba18fc4841f1ebddae41e562c6a216846404c88d/ray-2.54.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:a22937f09ee74a43171df338d84b45ef882c1c05748947ca9d5343a44d4b9379", size = 70097079, upload-time = "2026-02-18T04:04:35.409Z" }, - { url = "https://files.pythonhosted.org/packages/e0/bb/f54980d45ecfd0ceb39b6a966bd64fc0597746af1917d7fe3cbdb9f72752/ray-2.54.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:1e63e491155695d527513ffe9d33a6aeb3f3cdccb6309adadfd6f8dd7c0300f7", size = 71951024, upload-time = "2026-02-18T04:04:42.817Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b1/8cc4e45a3ce87aabcb70696b448b20840bcbaa5c98bdb4807a2749541fda/ray-2.54.0-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:2d140409e4ca06d8d6a06f71d441b53f6edcd930ebe67a6988f652915db81070", size = 72783364, upload-time = "2026-02-18T04:04:48.311Z" }, - { url = "https://files.pythonhosted.org/packages/12/79/7fb2f5698319cd28f0599fc9848a77dd7a64e0d82486c78dd94c6dce5095/ray-2.54.0-cp310-cp310-win_amd64.whl", hash = "sha256:86da6ff60b57394aa47158b2f3fc2616a87492e828983451f04e676b192b49ce", size = 27452281, upload-time = "2026-02-18T04:04:53.252Z" }, - { url = "https://files.pythonhosted.org/packages/08/58/6209b2231947f3c8df09ce1436f1c76c4a11fcafd57c8def852dcbb6d8ef/ray-2.54.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:8e39dd56b47a0a1820d5a5a54385bbe54d1d67e1093736d12d8ed4e99d0fa455", size = 70098998, upload-time = "2026-02-18T04:04:58.801Z" }, - { url = "https://files.pythonhosted.org/packages/ac/29/7871f4206e6b00a9bb784c16dad32ccd01e9df5a93545db92de220eb2871/ray-2.54.0-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:491ae56ab80d8822c4eaf4d5bb96dcf32a6231d8d7b76eb8034400eb9be1bb18", size = 72066630, upload-time = "2026-02-18T04:05:04.957Z" }, - { url = "https://files.pythonhosted.org/packages/1d/e8/d2c8ebd9cd945abc817b01ad02a29df78cdb86cd07d764587e16977389d0/ray-2.54.0-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:928bb09245a3c6f7c3c113ba8eafc69f948da9602d7f33e8251ecdf97c157615", size = 72895723, upload-time = "2026-02-18T04:05:10.686Z" }, - { url = "https://files.pythonhosted.org/packages/7e/96/a5ea3a149a943475cda1d68fdcdb14c86251826c652c232ae853600ad7e7/ray-2.54.0-cp311-cp311-win_amd64.whl", hash = "sha256:1e786330de55b3ba2228e36ec305381a9b86f0b01a8b6072c5811c3bc4dd9a3d", size = 27448371, upload-time = "2026-02-18T04:05:16.34Z" }, - { url = "https://files.pythonhosted.org/packages/0e/16/45eefb51eb1767342a6dbf41af0b432279e422e56160705fcd1098a7ec53/ray-2.54.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:cf5c33b4b13850ec24a5bd5f9d9e0a8161f8e586bfd297e52913d170dec447fe", size = 70084880, upload-time = "2026-02-18T04:05:22.007Z" }, - { url = "https://files.pythonhosted.org/packages/60/ad/e07aca3637e9c3ec4857ec4366208099cf8488ece8061a9925ba29b66382/ray-2.54.0-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:795ae21d6b764245d3f521bc5833446d58569e7dfde9c5777417eb285d87450f", size = 72107346, upload-time = "2026-02-18T04:05:27.999Z" }, - { url = "https://files.pythonhosted.org/packages/9e/b9/cc5ea8460c3dc602e6b7198277a7c59ba2b8929374ab22efa8df9f3deac8/ray-2.54.0-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:a972afd5aa3dda99d0b2f369b5f62e5dd95865ab7d37bf2e0a0e0d2cfbd9b325", size = 72967230, upload-time = "2026-02-18T04:05:33.771Z" }, - { url = "https://files.pythonhosted.org/packages/de/d7/744de3b1bb881701330ddcbb2f6efaccd65915d564ece899a3838f9fb105/ray-2.54.0-cp312-cp312-win_amd64.whl", hash = "sha256:2ee074ede491d0aacfa339c003f5d7a15826e1e2a72ce873234ccbc0446e19b3", size = 27427353, upload-time = "2026-02-18T04:05:38.853Z" }, - { url = "https://files.pythonhosted.org/packages/7f/f2/5c0161d10445e703b7d01413ab54ec1cc5e27032555279d296df89b9c4ee/ray-2.54.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5ad77961fea16c697a0fb0e51216dd39c0bec28868cde54ac668edd58d12b8ae", size = 70030991, upload-time = "2026-02-18T04:05:43.966Z" }, - { url = "https://files.pythonhosted.org/packages/fd/8c/4a4a38eaec6e9614076a96967f58540f4f8d4aa0c793f43150c5df23cb9a/ray-2.54.0-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:8952c23a8aa94f10728c2d16e0dc3732d09aa0e6254801757ff494984a214f45", size = 72013826, upload-time = "2026-02-18T04:05:49.866Z" }, - { url = "https://files.pythonhosted.org/packages/42/ac/e7ec2a406bd755f61c7090460fa5ab3f09b00c3c2d8db6d0b559f78a30eb/ray-2.54.0-cp313-cp313-manylinux2014_x86_64.whl", hash = "sha256:ab89e6089abb6e46fb98fdd96d399b31a852d79127cd8ac00746c61d93defa2c", size = 72880209, upload-time = "2026-02-18T04:05:55.498Z" }, + { url = "https://files.pythonhosted.org/packages/af/cf/9a6e33b59e1a12428b4fbd6cc38f7e32d116ccde4c72e15c3f76a22bf36d/ray-2.54.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:2ea650e648acc6e76edd98c694657fd1fcb1cd97700d944a7d20da90269e9810", size = 70088753, upload-time = "2026-03-25T22:40:08.213Z" }, + { url = "https://files.pythonhosted.org/packages/a1/64/fd46863a479ca62c0110f8f56db71edb871ddba137d4701efba0c5951600/ray-2.54.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:6425f15cfe6a298366b53c8658350f94ced2c548802ca3b69f94b87db16e97c5", size = 71702573, upload-time = "2026-03-25T22:40:15.403Z" }, + { url = "https://files.pythonhosted.org/packages/55/96/7911234a14b891320e652b5ae258050f98584f22a8e33afba9ad43ab27c9/ray-2.54.1-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:054985194bd32f4464c93f9318d247fac61e1f32ac221565ecfdc81ab8c75d0b", size = 72537837, upload-time = "2026-03-25T22:40:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/41/2a/5cac846ed9b247bc47c3b2618b8b550c52e56fb82be923cae37d9c1161aa/ray-2.54.1-cp310-cp310-win_amd64.whl", hash = "sha256:512587412e2f5e1753adabfdfa4dd9cff1dc509601e36fd5fab671e448ae4dac", size = 27452712, upload-time = "2026-03-25T22:40:26.351Z" }, + { url = "https://files.pythonhosted.org/packages/d0/1b/f08534e625011fe07017b788ade9bde7570e2e5e0687984d51ced2935c69/ray-2.54.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0240496af274af7cd3b1b1d015f23b88e5fdafe59bfdc040e5f229e0aff5dff", size = 70090027, upload-time = "2026-03-25T22:40:32.816Z" }, + { url = "https://files.pythonhosted.org/packages/5b/90/3455fce4485140aed0f00433fd55294365f1b707dfd547cad6427212bca2/ray-2.54.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:86c51eafd3e84dad59c1ef4cf97b3ac8c088af0705782ee915e31bca5880597a", size = 71798478, upload-time = "2026-03-25T22:40:39.058Z" }, + { url = "https://files.pythonhosted.org/packages/34/61/04bb126d798962970cca5c88394edee862e91bf97b5e6abbee1478e0f9fc/ray-2.54.1-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:e095dfe9c521a04e5930520b4a82ea82d61903d4cd2f3270fbc5dfbdb41b9c72", size = 72631241, upload-time = "2026-03-25T22:40:44.981Z" }, + { url = "https://files.pythonhosted.org/packages/82/eb/d5a27dc5f07d9f1e50a3b573305ae6272eb5a43c8323994d6168bffa443e/ray-2.54.1-cp311-cp311-win_amd64.whl", hash = "sha256:ea90bed0110e0ce3ff6571e7a0c800920a3c6d299d29b8eac020dac362667169", size = 27449001, upload-time = "2026-03-25T22:40:49.852Z" }, + { url = "https://files.pythonhosted.org/packages/d9/ca/f3274e0d513c44949ea9167c12c07f9971e5f25ef22b698448a6ca831434/ray-2.54.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:645ebfb73cfd32bd510a05ed9f2738a18d6db69929cae9701d749f2740dbfd9a", size = 70076126, upload-time = "2026-03-25T22:40:55.188Z" }, + { url = "https://files.pythonhosted.org/packages/51/6f/bf1b7a6d4424c19add99eb17398c7522473502193540b679f8b94fbf2d72/ray-2.54.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:cd452b61ae2e0daf9271f5a554614397429cc2731681bae10fe72316dadc2749", size = 71831684, upload-time = "2026-03-25T22:41:01.356Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/b33d5006823f8c1c8760887cf1190194f4b06de858b3d17e37bd930a6a62/ray-2.54.1-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:4c6f7e23dda62a32f94083141c3f97e9c4246e3ae4ae2bc488bcd8fd0311f54a", size = 72688748, upload-time = "2026-03-25T22:41:07.43Z" }, + { url = "https://files.pythonhosted.org/packages/a9/7d/02b46d3fe644e1feef62b9e4ebf8cbfc17c6b2d283763208abc52c3dc85e/ray-2.54.1-cp312-cp312-win_amd64.whl", hash = "sha256:673a895c0c4a716ed772552baa3f5b8d7d1f7a4b34e04787fdfe6fe3049ed0d8", size = 27427871, upload-time = "2026-03-25T22:41:12.485Z" }, + { url = "https://files.pythonhosted.org/packages/80/30/90f9f8f0fcba72b898c40854e020c9d5330f33b4ccd711747cc07e061416/ray-2.54.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:d05f477d1518a00fd5880644e889a7a3eaf64ae5d1f8f239a682d052ad2a383d", size = 70023037, upload-time = "2026-03-25T22:41:17.895Z" }, + { url = "https://files.pythonhosted.org/packages/c8/5d/fe0e8ac47f6b362c81f391d7f8d2a6858d0bafcc2c37631dc5cc04a16545/ray-2.54.1-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:2766f0230806480c38a9a94502087f1d4aea919f38521a28781690613b0290a4", size = 71738623, upload-time = "2026-03-25T22:41:23.898Z" }, + { url = "https://files.pythonhosted.org/packages/1b/22/48008a626e719baee2012080b960687cc6417b572b363c1c29fe23d119c3/ray-2.54.1-cp313-cp313-manylinux2014_x86_64.whl", hash = "sha256:0c3ae2943176e7b239c78b825a5b2bf4135d90280083a0e19c0a75a5db4d836f", size = 72603355, upload-time = "2026-03-25T22:41:29.802Z" }, ] [package.optional-dependencies] @@ -4490,7 +4793,7 @@ wheels = [ [[package]] name = "requests" -version = "2.32.5" +version = "2.33.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -4498,9 +4801,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, + { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" }, ] [[package]] @@ -4662,18 +4965,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, ] -[[package]] -name = "rsa" -version = "4.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyasn1", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, -] - [[package]] name = "rustworkx" version = "0.17.1" @@ -4723,6 +5014,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/5b/632a58724221ef03d78ab65062e82a1010e1bef8e8e0b9d7c6d7b8044841/safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:473b32699f4200e69801bf5abf93f1a4ecd432a70984df164fc22ccf39c4a6f3", size = 531885, upload-time = "2025-11-19T15:18:27.146Z" }, ] +[[package]] +name = "samplomatic" +version = "0.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "orjson", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "pybase64", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "qiskit", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, + { name = "rustworkx", marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/e3/6febd25b68efd905eab7b142f0673182eccb8ac9b25af9c4b4d7cd5bea2b/samplomatic-0.17.1.tar.gz", hash = "sha256:205626dede3a9621ac9d790c57fdd807b1bffd3da1fc35832b3e83dba3359f7f", size = 1197910, upload-time = "2026-03-16T15:49:18.733Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/09/fb30fd315c453c9674ca00fe661d6574e6d5d696115b26a6218c244fce39/samplomatic-0.17.1-py3-none-any.whl", hash = "sha256:8c4a5d3d2d33a96cfddb27bda99838ad81ddd55cb1332769f8e82259eb48a970", size = 205256, upload-time = "2026-03-16T15:49:17.194Z" }, +] + [[package]] name = "sb3-contrib" version = "2.7.1" @@ -5465,6 +5772,20 @@ wheels = [ name = "symengine" version = "0.13.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11'", +] 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" }, @@ -5498,6 +5819,23 @@ wheels = [ { 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]] +name = "symengine" +version = "0.14.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.13.*' and sys_platform == 'win32'", +] +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/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/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/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/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/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/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]] name = "sympy" version = "1.14.0" @@ -5531,7 +5869,8 @@ dependencies = [ { name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "packaging" }, { name = "pillow" }, - { name = "protobuf" }, + { name = "protobuf", version = "6.33.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "protobuf", version = "7.34.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.13.*' and sys_platform == 'win32'" }, { name = "setuptools" }, { name = "tensorboard-data-server" }, { name = "werkzeug" }, @@ -5685,6 +6024,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/66/4d/35352043ee0eaffdeff154fad67cd4a31dbed7ff8e3be1cc4549717d6d51/torch-2.10.0-cp314-cp314t-win_amd64.whl", hash = "sha256:71283a373f0ee2c89e0f0d5f446039bdabe8dbc3c9ccf35f0f784908b0acd185", size = 113995816, upload-time = "2026-01-21T16:22:05.312Z" }, ] +[[package]] +name = "torch-geometric" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "psutil" }, + { name = "pyparsing" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/63/b210152635902da7fe79fcdd16517fae108f457a0ed22c737e702a9afbae/torch_geometric-2.7.0.tar.gz", hash = "sha256:f9099e4aece1a9f618c84dbaac33a77f43139736698c7e8bddf3301ef1f2e8d4", size = 876725, upload-time = "2025-10-15T20:48:03.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/4dffd7300500465e0b4a2ae917dcb2ce771de0b9a772670365799a27c024/torch_geometric-2.7.0-py3-none-any.whl", hash = "sha256:6e0cd3ad824d484651ef5d308fc66c687bfcf5ba040d56d1e0fe0f81f365e292", size = 1275346, upload-time = "2025-10-15T20:48:01.949Z" }, +] + [[package]] name = "tornado" version = "6.5.5" @@ -5861,14 +6221,14 @@ wheels = [ [[package]] name = "werkzeug" -version = "3.1.6" +version = "3.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/f1/ee81806690a87dab5f5653c1f146c92bc066d7f4cebc603ef88eb9e13957/werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25", size = 864736, upload-time = "2026-02-19T15:17:18.884Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/43/76ded108b296a49f52de6bac5192ca1c4be84e886f9b5c9ba8427d9694fd/werkzeug-3.1.7.tar.gz", hash = "sha256:fb8c01fe6ab13b9b7cdb46892b99b1d66754e1d7ab8e542e865ec13f526b5351", size = 875700, upload-time = "2026-03-24T01:08:07.687Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/ec/d58832f89ede95652fd01f4f24236af7d32b70cab2196dfcc2d2fd13c5c2/werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131", size = 225166, upload-time = "2026-02-19T15:17:17.475Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b2/0bba9bbb4596d2d2f285a16c2ab04118f6b957d8441566e1abb892e6a6b2/werkzeug-3.1.7-py3-none-any.whl", hash = "sha256:4b314d81163a3e1a169b6a0be2a000a0e204e8873c5de6586f453c55688d422f", size = 226295, upload-time = "2026-03-24T01:08:06.133Z" }, ] [[package]] @@ -5958,14 +6318,132 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] +[[package]] +name = "xxhash" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6cc7e18688ee8bf1e42d57e7e0777636bd47524c43c7/xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6", size = 85160, upload-time = "2025-10-02T14:37:08.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/ee/f9f1d656ad168681bb0f6b092372c1e533c4416b8069b1896a175c46e484/xxhash-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:87ff03d7e35c61435976554477a7f4cd1704c3596a89a8300d5ce7fc83874a71", size = 32845, upload-time = "2025-10-02T14:33:51.573Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b1/93508d9460b292c74a09b83d16750c52a0ead89c51eea9951cb97a60d959/xxhash-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f572dfd3d0e2eb1a57511831cf6341242f5a9f8298a45862d085f5b93394a27d", size = 30807, upload-time = "2025-10-02T14:33:52.964Z" }, + { url = "https://files.pythonhosted.org/packages/07/55/28c93a3662f2d200c70704efe74aab9640e824f8ce330d8d3943bf7c9b3c/xxhash-3.6.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:89952ea539566b9fed2bbd94e589672794b4286f342254fad28b149f9615fef8", size = 193786, upload-time = "2025-10-02T14:33:54.272Z" }, + { url = "https://files.pythonhosted.org/packages/c1/96/fec0be9bb4b8f5d9c57d76380a366f31a1781fb802f76fc7cda6c84893c7/xxhash-3.6.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e6f2ffb07a50b52465a1032c3cf1f4a5683f944acaca8a134a2f23674c2058", size = 212830, upload-time = "2025-10-02T14:33:55.706Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a0/c706845ba77b9611f81fd2e93fad9859346b026e8445e76f8c6fd057cc6d/xxhash-3.6.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b5b848ad6c16d308c3ac7ad4ba6bede80ed5df2ba8ed382f8932df63158dd4b2", size = 211606, upload-time = "2025-10-02T14:33:57.133Z" }, + { url = "https://files.pythonhosted.org/packages/67/1e/164126a2999e5045f04a69257eea946c0dc3e86541b400d4385d646b53d7/xxhash-3.6.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a034590a727b44dd8ac5914236a7b8504144447a9682586c3327e935f33ec8cc", size = 444872, upload-time = "2025-10-02T14:33:58.446Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4b/55ab404c56cd70a2cf5ecfe484838865d0fea5627365c6c8ca156bd09c8f/xxhash-3.6.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a8f1972e75ebdd161d7896743122834fe87378160c20e97f8b09166213bf8cc", size = 193217, upload-time = "2025-10-02T14:33:59.724Z" }, + { url = "https://files.pythonhosted.org/packages/45/e6/52abf06bac316db33aa269091ae7311bd53cfc6f4b120ae77bac1b348091/xxhash-3.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ee34327b187f002a596d7b167ebc59a1b729e963ce645964bbc050d2f1b73d07", size = 210139, upload-time = "2025-10-02T14:34:02.041Z" }, + { url = "https://files.pythonhosted.org/packages/34/37/db94d490b8691236d356bc249c08819cbcef9273a1a30acf1254ff9ce157/xxhash-3.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:339f518c3c7a850dd033ab416ea25a692759dc7478a71131fe8869010d2b75e4", size = 197669, upload-time = "2025-10-02T14:34:03.664Z" }, + { url = "https://files.pythonhosted.org/packages/b7/36/c4f219ef4a17a4f7a64ed3569bc2b5a9c8311abdb22249ac96093625b1a4/xxhash-3.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:bf48889c9630542d4709192578aebbd836177c9f7a4a2778a7d6340107c65f06", size = 210018, upload-time = "2025-10-02T14:34:05.325Z" }, + { url = "https://files.pythonhosted.org/packages/fd/06/bfac889a374fc2fc439a69223d1750eed2e18a7db8514737ab630534fa08/xxhash-3.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5576b002a56207f640636056b4160a378fe36a58db73ae5c27a7ec8db35f71d4", size = 413058, upload-time = "2025-10-02T14:34:06.925Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d1/555d8447e0dd32ad0930a249a522bb2e289f0d08b6b16204cfa42c1f5a0c/xxhash-3.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af1f3278bd02814d6dedc5dec397993b549d6f16c19379721e5a1d31e132c49b", size = 190628, upload-time = "2025-10-02T14:34:08.669Z" }, + { url = "https://files.pythonhosted.org/packages/d1/15/8751330b5186cedc4ed4b597989882ea05e0408b53fa47bcb46a6125bfc6/xxhash-3.6.0-cp310-cp310-win32.whl", hash = "sha256:aed058764db109dc9052720da65fafe84873b05eb8b07e5e653597951af57c3b", size = 30577, upload-time = "2025-10-02T14:34:10.234Z" }, + { url = "https://files.pythonhosted.org/packages/bb/cc/53f87e8b5871a6eb2ff7e89c48c66093bda2be52315a8161ddc54ea550c4/xxhash-3.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:e82da5670f2d0d98950317f82a0e4a0197150ff19a6df2ba40399c2a3b9ae5fb", size = 31487, upload-time = "2025-10-02T14:34:11.618Z" }, + { url = "https://files.pythonhosted.org/packages/9f/00/60f9ea3bb697667a14314d7269956f58bf56bb73864f8f8d52a3c2535e9a/xxhash-3.6.0-cp310-cp310-win_arm64.whl", hash = "sha256:4a082ffff8c6ac07707fb6b671caf7c6e020c75226c561830b73d862060f281d", size = 27863, upload-time = "2025-10-02T14:34:12.619Z" }, + { url = "https://files.pythonhosted.org/packages/17/d4/cc2f0400e9154df4b9964249da78ebd72f318e35ccc425e9f403c392f22a/xxhash-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b47bbd8cf2d72797f3c2772eaaac0ded3d3af26481a26d7d7d41dc2d3c46b04a", size = 32844, upload-time = "2025-10-02T14:34:14.037Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ec/1cc11cd13e26ea8bc3cb4af4eaadd8d46d5014aebb67be3f71fb0b68802a/xxhash-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2b6821e94346f96db75abaa6e255706fb06ebd530899ed76d32cd99f20dc52fa", size = 30809, upload-time = "2025-10-02T14:34:15.484Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/19fe357ea348d98ca22f456f75a30ac0916b51c753e1f8b2e0e6fb884cce/xxhash-3.6.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d0a9751f71a1a65ce3584e9cae4467651c7e70c9d31017fa57574583a4540248", size = 194665, upload-time = "2025-10-02T14:34:16.541Z" }, + { url = "https://files.pythonhosted.org/packages/90/3b/d1f1a8f5442a5fd8beedae110c5af7604dc37349a8e16519c13c19a9a2de/xxhash-3.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b29ee68625ab37b04c0b40c3fafdf24d2f75ccd778333cfb698f65f6c463f62", size = 213550, upload-time = "2025-10-02T14:34:17.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ef/3a9b05eb527457d5db13a135a2ae1a26c80fecd624d20f3e8dcc4cb170f3/xxhash-3.6.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6812c25fe0d6c36a46ccb002f40f27ac903bf18af9f6dd8f9669cb4d176ab18f", size = 212384, upload-time = "2025-10-02T14:34:19.182Z" }, + { url = "https://files.pythonhosted.org/packages/0f/18/ccc194ee698c6c623acbf0f8c2969811a8a4b6185af5e824cd27b9e4fd3e/xxhash-3.6.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4ccbff013972390b51a18ef1255ef5ac125c92dc9143b2d1909f59abc765540e", size = 445749, upload-time = "2025-10-02T14:34:20.659Z" }, + { url = "https://files.pythonhosted.org/packages/a5/86/cf2c0321dc3940a7aa73076f4fd677a0fb3e405cb297ead7d864fd90847e/xxhash-3.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:297b7fbf86c82c550e12e8fb71968b3f033d27b874276ba3624ea868c11165a8", size = 193880, upload-time = "2025-10-02T14:34:22.431Z" }, + { url = "https://files.pythonhosted.org/packages/82/fb/96213c8560e6f948a1ecc9a7613f8032b19ee45f747f4fca4eb31bb6d6ed/xxhash-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dea26ae1eb293db089798d3973a5fc928a18fdd97cc8801226fae705b02b14b0", size = 210912, upload-time = "2025-10-02T14:34:23.937Z" }, + { url = "https://files.pythonhosted.org/packages/40/aa/4395e669b0606a096d6788f40dbdf2b819d6773aa290c19e6e83cbfc312f/xxhash-3.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7a0b169aafb98f4284f73635a8e93f0735f9cbde17bd5ec332480484241aaa77", size = 198654, upload-time = "2025-10-02T14:34:25.644Z" }, + { url = "https://files.pythonhosted.org/packages/67/74/b044fcd6b3d89e9b1b665924d85d3f400636c23590226feb1eb09e1176ce/xxhash-3.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:08d45aef063a4531b785cd72de4887766d01dc8f362a515693df349fdb825e0c", size = 210867, upload-time = "2025-10-02T14:34:27.203Z" }, + { url = "https://files.pythonhosted.org/packages/bc/fd/3ce73bf753b08cb19daee1eb14aa0d7fe331f8da9c02dd95316ddfe5275e/xxhash-3.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:929142361a48ee07f09121fe9e96a84950e8d4df3bb298ca5d88061969f34d7b", size = 414012, upload-time = "2025-10-02T14:34:28.409Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b3/5a4241309217c5c876f156b10778f3ab3af7ba7e3259e6d5f5c7d0129eb2/xxhash-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51312c768403d8540487dbbfb557454cfc55589bbde6424456951f7fcd4facb3", size = 191409, upload-time = "2025-10-02T14:34:29.696Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/99bfbc15fb9abb9a72b088c1d95219fc4782b7d01fc835bd5744d66dd0b8/xxhash-3.6.0-cp311-cp311-win32.whl", hash = "sha256:d1927a69feddc24c987b337ce81ac15c4720955b667fe9b588e02254b80446fd", size = 30574, upload-time = "2025-10-02T14:34:31.028Z" }, + { url = "https://files.pythonhosted.org/packages/65/79/9d24d7f53819fe301b231044ea362ce64e86c74f6e8c8e51320de248b3e5/xxhash-3.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:26734cdc2d4ffe449b41d186bbeac416f704a482ed835d375a5c0cb02bc63fef", size = 31481, upload-time = "2025-10-02T14:34:32.062Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/15cd0e3e8772071344eab2961ce83f6e485111fed8beb491a3f1ce100270/xxhash-3.6.0-cp311-cp311-win_arm64.whl", hash = "sha256:d72f67ef8bf36e05f5b6c65e8524f265bd61071471cd4cf1d36743ebeeeb06b7", size = 27861, upload-time = "2025-10-02T14:34:33.555Z" }, + { url = "https://files.pythonhosted.org/packages/9a/07/d9412f3d7d462347e4511181dea65e47e0d0e16e26fbee2ea86a2aefb657/xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c", size = 32744, upload-time = "2025-10-02T14:34:34.622Z" }, + { url = "https://files.pythonhosted.org/packages/79/35/0429ee11d035fc33abe32dca1b2b69e8c18d236547b9a9b72c1929189b9a/xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204", size = 30816, upload-time = "2025-10-02T14:34:36.043Z" }, + { url = "https://files.pythonhosted.org/packages/b7/f2/57eb99aa0f7d98624c0932c5b9a170e1806406cdbcdb510546634a1359e0/xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490", size = 194035, upload-time = "2025-10-02T14:34:37.354Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ed/6224ba353690d73af7a3f1c7cdb1fc1b002e38f783cb991ae338e1eb3d79/xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2", size = 212914, upload-time = "2025-10-02T14:34:38.6Z" }, + { url = "https://files.pythonhosted.org/packages/38/86/fb6b6130d8dd6b8942cc17ab4d90e223653a89aa32ad2776f8af7064ed13/xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa", size = 212163, upload-time = "2025-10-02T14:34:39.872Z" }, + { url = "https://files.pythonhosted.org/packages/ee/dc/e84875682b0593e884ad73b2d40767b5790d417bde603cceb6878901d647/xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0", size = 445411, upload-time = "2025-10-02T14:34:41.569Z" }, + { url = "https://files.pythonhosted.org/packages/11/4f/426f91b96701ec2f37bb2b8cec664eff4f658a11f3fa9d94f0a887ea6d2b/xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2", size = 193883, upload-time = "2025-10-02T14:34:43.249Z" }, + { url = "https://files.pythonhosted.org/packages/53/5a/ddbb83eee8e28b778eacfc5a85c969673e4023cdeedcfcef61f36731610b/xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9", size = 210392, upload-time = "2025-10-02T14:34:45.042Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c2/ff69efd07c8c074ccdf0a4f36fcdd3d27363665bcdf4ba399abebe643465/xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e", size = 197898, upload-time = "2025-10-02T14:34:46.302Z" }, + { url = "https://files.pythonhosted.org/packages/58/ca/faa05ac19b3b622c7c9317ac3e23954187516298a091eb02c976d0d3dd45/xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374", size = 210655, upload-time = "2025-10-02T14:34:47.571Z" }, + { url = "https://files.pythonhosted.org/packages/d4/7a/06aa7482345480cc0cb597f5c875b11a82c3953f534394f620b0be2f700c/xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d", size = 414001, upload-time = "2025-10-02T14:34:49.273Z" }, + { url = "https://files.pythonhosted.org/packages/23/07/63ffb386cd47029aa2916b3d2f454e6cc5b9f5c5ada3790377d5430084e7/xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae", size = 191431, upload-time = "2025-10-02T14:34:50.798Z" }, + { url = "https://files.pythonhosted.org/packages/0f/93/14fde614cadb4ddf5e7cebf8918b7e8fac5ae7861c1875964f17e678205c/xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb", size = 30617, upload-time = "2025-10-02T14:34:51.954Z" }, + { url = "https://files.pythonhosted.org/packages/13/5d/0d125536cbe7565a83d06e43783389ecae0c0f2ed037b48ede185de477c0/xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c", size = 31534, upload-time = "2025-10-02T14:34:53.276Z" }, + { url = "https://files.pythonhosted.org/packages/54/85/6ec269b0952ec7e36ba019125982cf11d91256a778c7c3f98a4c5043d283/xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829", size = 27876, upload-time = "2025-10-02T14:34:54.371Z" }, + { url = "https://files.pythonhosted.org/packages/33/76/35d05267ac82f53ae9b0e554da7c5e281ee61f3cad44c743f0fcd354f211/xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec", size = 32738, upload-time = "2025-10-02T14:34:55.839Z" }, + { url = "https://files.pythonhosted.org/packages/31/a8/3fbce1cd96534a95e35d5120637bf29b0d7f5d8fa2f6374e31b4156dd419/xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1", size = 30821, upload-time = "2025-10-02T14:34:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ea/d387530ca7ecfa183cb358027f1833297c6ac6098223fd14f9782cd0015c/xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6", size = 194127, upload-time = "2025-10-02T14:34:59.21Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0c/71435dcb99874b09a43b8d7c54071e600a7481e42b3e3ce1eb5226a5711a/xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263", size = 212975, upload-time = "2025-10-02T14:35:00.816Z" }, + { url = "https://files.pythonhosted.org/packages/84/7a/c2b3d071e4bb4a90b7057228a99b10d51744878f4a8a6dd643c8bd897620/xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546", size = 212241, upload-time = "2025-10-02T14:35:02.207Z" }, + { url = "https://files.pythonhosted.org/packages/81/5f/640b6eac0128e215f177df99eadcd0f1b7c42c274ab6a394a05059694c5a/xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89", size = 445471, upload-time = "2025-10-02T14:35:03.61Z" }, + { url = "https://files.pythonhosted.org/packages/5e/1e/3c3d3ef071b051cc3abbe3721ffb8365033a172613c04af2da89d5548a87/xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d", size = 193936, upload-time = "2025-10-02T14:35:05.013Z" }, + { url = "https://files.pythonhosted.org/packages/2c/bd/4a5f68381939219abfe1c22a9e3a5854a4f6f6f3c4983a87d255f21f2e5d/xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7", size = 210440, upload-time = "2025-10-02T14:35:06.239Z" }, + { url = "https://files.pythonhosted.org/packages/eb/37/b80fe3d5cfb9faff01a02121a0f4d565eb7237e9e5fc66e73017e74dcd36/xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db", size = 197990, upload-time = "2025-10-02T14:35:07.735Z" }, + { url = "https://files.pythonhosted.org/packages/d7/fd/2c0a00c97b9e18f72e1f240ad4e8f8a90fd9d408289ba9c7c495ed7dc05c/xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42", size = 210689, upload-time = "2025-10-02T14:35:09.438Z" }, + { url = "https://files.pythonhosted.org/packages/93/86/5dd8076a926b9a95db3206aba20d89a7fc14dd5aac16e5c4de4b56033140/xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11", size = 414068, upload-time = "2025-10-02T14:35:11.162Z" }, + { url = "https://files.pythonhosted.org/packages/af/3c/0bb129170ee8f3650f08e993baee550a09593462a5cddd8e44d0011102b1/xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd", size = 191495, upload-time = "2025-10-02T14:35:12.971Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3a/6797e0114c21d1725e2577508e24006fd7ff1d8c0c502d3b52e45c1771d8/xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799", size = 30620, upload-time = "2025-10-02T14:35:14.129Z" }, + { url = "https://files.pythonhosted.org/packages/86/15/9bc32671e9a38b413a76d24722a2bf8784a132c043063a8f5152d390b0f9/xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392", size = 31542, upload-time = "2025-10-02T14:35:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/39/c5/cc01e4f6188656e56112d6a8e0dfe298a16934b8c47a247236549a3f7695/xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6", size = 27880, upload-time = "2025-10-02T14:35:16.315Z" }, + { url = "https://files.pythonhosted.org/packages/f3/30/25e5321c8732759e930c555176d37e24ab84365482d257c3b16362235212/xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702", size = 32956, upload-time = "2025-10-02T14:35:17.413Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3c/0573299560d7d9f8ab1838f1efc021a280b5ae5ae2e849034ef3dee18810/xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db", size = 31072, upload-time = "2025-10-02T14:35:18.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1c/52d83a06e417cd9d4137722693424885cc9878249beb3a7c829e74bf7ce9/xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54", size = 196409, upload-time = "2025-10-02T14:35:20.31Z" }, + { url = "https://files.pythonhosted.org/packages/e3/8e/c6d158d12a79bbd0b878f8355432075fc82759e356ab5a111463422a239b/xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f", size = 215736, upload-time = "2025-10-02T14:35:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/bc/68/c4c80614716345d55071a396cf03d06e34b5f4917a467faf43083c995155/xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5", size = 214833, upload-time = "2025-10-02T14:35:23.32Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e9/ae27c8ffec8b953efa84c7c4a6c6802c263d587b9fc0d6e7cea64e08c3af/xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1", size = 448348, upload-time = "2025-10-02T14:35:25.111Z" }, + { url = "https://files.pythonhosted.org/packages/d7/6b/33e21afb1b5b3f46b74b6bd1913639066af218d704cc0941404ca717fc57/xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee", size = 196070, upload-time = "2025-10-02T14:35:26.586Z" }, + { url = "https://files.pythonhosted.org/packages/96/b6/fcabd337bc5fa624e7203aa0fa7d0c49eed22f72e93229431752bddc83d9/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd", size = 212907, upload-time = "2025-10-02T14:35:28.087Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d3/9ee6160e644d660fcf176c5825e61411c7f62648728f69c79ba237250143/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729", size = 200839, upload-time = "2025-10-02T14:35:29.857Z" }, + { url = "https://files.pythonhosted.org/packages/0d/98/e8de5baa5109394baf5118f5e72ab21a86387c4f89b0e77ef3e2f6b0327b/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292", size = 213304, upload-time = "2025-10-02T14:35:31.222Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1d/71056535dec5c3177eeb53e38e3d367dd1d16e024e63b1cee208d572a033/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf", size = 416930, upload-time = "2025-10-02T14:35:32.517Z" }, + { url = "https://files.pythonhosted.org/packages/dc/6c/5cbde9de2cd967c322e651c65c543700b19e7ae3e0aae8ece3469bf9683d/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033", size = 193787, upload-time = "2025-10-02T14:35:33.827Z" }, + { url = "https://files.pythonhosted.org/packages/19/fa/0172e350361d61febcea941b0cc541d6e6c8d65d153e85f850a7b256ff8a/xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec", size = 30916, upload-time = "2025-10-02T14:35:35.107Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e6/e8cf858a2b19d6d45820f072eff1bea413910592ff17157cabc5f1227a16/xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8", size = 31799, upload-time = "2025-10-02T14:35:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/56/15/064b197e855bfb7b343210e82490ae672f8bc7cdf3ddb02e92f64304ee8a/xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746", size = 28044, upload-time = "2025-10-02T14:35:37.195Z" }, + { url = "https://files.pythonhosted.org/packages/7e/5e/0138bc4484ea9b897864d59fce9be9086030825bc778b76cb5a33a906d37/xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e", size = 32754, upload-time = "2025-10-02T14:35:38.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/d7/5dac2eb2ec75fd771957a13e5dda560efb2176d5203f39502a5fc571f899/xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405", size = 30846, upload-time = "2025-10-02T14:35:39.6Z" }, + { url = "https://files.pythonhosted.org/packages/fe/71/8bc5be2bb00deb5682e92e8da955ebe5fa982da13a69da5a40a4c8db12fb/xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3", size = 194343, upload-time = "2025-10-02T14:35:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/52badfb2aecec2c377ddf1ae75f55db3ba2d321c5e164f14461c90837ef3/xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6", size = 213074, upload-time = "2025-10-02T14:35:42.29Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2b/ae46b4e9b92e537fa30d03dbc19cdae57ed407e9c26d163895e968e3de85/xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063", size = 212388, upload-time = "2025-10-02T14:35:43.929Z" }, + { url = "https://files.pythonhosted.org/packages/f5/80/49f88d3afc724b4ac7fbd664c8452d6db51b49915be48c6982659e0e7942/xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7", size = 445614, upload-time = "2025-10-02T14:35:45.216Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ba/603ce3961e339413543d8cd44f21f2c80e2a7c5cfe692a7b1f2cccf58f3c/xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b", size = 194024, upload-time = "2025-10-02T14:35:46.959Z" }, + { url = "https://files.pythonhosted.org/packages/78/d1/8e225ff7113bf81545cfdcd79eef124a7b7064a0bba53605ff39590b95c2/xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd", size = 210541, upload-time = "2025-10-02T14:35:48.301Z" }, + { url = "https://files.pythonhosted.org/packages/6f/58/0f89d149f0bad89def1a8dd38feb50ccdeb643d9797ec84707091d4cb494/xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0", size = 198305, upload-time = "2025-10-02T14:35:49.584Z" }, + { url = "https://files.pythonhosted.org/packages/11/38/5eab81580703c4df93feb5f32ff8fa7fe1e2c51c1f183ee4e48d4bb9d3d7/xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152", size = 210848, upload-time = "2025-10-02T14:35:50.877Z" }, + { url = "https://files.pythonhosted.org/packages/5e/6b/953dc4b05c3ce678abca756416e4c130d2382f877a9c30a20d08ee6a77c0/xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11", size = 414142, upload-time = "2025-10-02T14:35:52.15Z" }, + { url = "https://files.pythonhosted.org/packages/08/a9/238ec0d4e81a10eb5026d4a6972677cbc898ba6c8b9dbaec12ae001b1b35/xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5", size = 191547, upload-time = "2025-10-02T14:35:53.547Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ee/3cf8589e06c2164ac77c3bf0aa127012801128f1feebf2a079272da5737c/xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f", size = 31214, upload-time = "2025-10-02T14:35:54.746Z" }, + { url = "https://files.pythonhosted.org/packages/02/5d/a19552fbc6ad4cb54ff953c3908bbc095f4a921bc569433d791f755186f1/xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad", size = 32290, upload-time = "2025-10-02T14:35:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/b1/11/dafa0643bc30442c887b55baf8e73353a344ee89c1901b5a5c54a6c17d39/xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679", size = 28795, upload-time = "2025-10-02T14:35:57.162Z" }, + { url = "https://files.pythonhosted.org/packages/2c/db/0e99732ed7f64182aef4a6fb145e1a295558deec2a746265dcdec12d191e/xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4", size = 32955, upload-time = "2025-10-02T14:35:58.267Z" }, + { url = "https://files.pythonhosted.org/packages/55/f4/2a7c3c68e564a099becfa44bb3d398810cc0ff6749b0d3cb8ccb93f23c14/xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67", size = 31072, upload-time = "2025-10-02T14:35:59.382Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d9/72a29cddc7250e8a5819dad5d466facb5dc4c802ce120645630149127e73/xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad", size = 196579, upload-time = "2025-10-02T14:36:00.838Z" }, + { url = "https://files.pythonhosted.org/packages/63/93/b21590e1e381040e2ca305a884d89e1c345b347404f7780f07f2cdd47ef4/xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b", size = 215854, upload-time = "2025-10-02T14:36:02.207Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b8/edab8a7d4fa14e924b29be877d54155dcbd8b80be85ea00d2be3413a9ed4/xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b", size = 214965, upload-time = "2025-10-02T14:36:03.507Z" }, + { url = "https://files.pythonhosted.org/packages/27/67/dfa980ac7f0d509d54ea0d5a486d2bb4b80c3f1bb22b66e6a05d3efaf6c0/xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca", size = 448484, upload-time = "2025-10-02T14:36:04.828Z" }, + { url = "https://files.pythonhosted.org/packages/8c/63/8ffc2cc97e811c0ca5d00ab36604b3ea6f4254f20b7bc658ca825ce6c954/xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a", size = 196162, upload-time = "2025-10-02T14:36:06.182Z" }, + { url = "https://files.pythonhosted.org/packages/4b/77/07f0e7a3edd11a6097e990f6e5b815b6592459cb16dae990d967693e6ea9/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99", size = 213007, upload-time = "2025-10-02T14:36:07.733Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d8/bc5fa0d152837117eb0bef6f83f956c509332ce133c91c63ce07ee7c4873/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3", size = 200956, upload-time = "2025-10-02T14:36:09.106Z" }, + { url = "https://files.pythonhosted.org/packages/26/a5/d749334130de9411783873e9b98ecc46688dad5db64ca6e04b02acc8b473/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6", size = 213401, upload-time = "2025-10-02T14:36:10.585Z" }, + { url = "https://files.pythonhosted.org/packages/89/72/abed959c956a4bfc72b58c0384bb7940663c678127538634d896b1195c10/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93", size = 417083, upload-time = "2025-10-02T14:36:12.276Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b3/62fd2b586283b7d7d665fb98e266decadf31f058f1cf6c478741f68af0cb/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518", size = 193913, upload-time = "2025-10-02T14:36:14.025Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/c19c42c5b3f5a4aad748a6d5b4f23df3bed7ee5445accc65a0fb3ff03953/xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119", size = 31586, upload-time = "2025-10-02T14:36:15.603Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/4cc450345be9924fd5dc8c590ceda1db5b43a0a889587b0ae81a95511360/xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f", size = 32526, upload-time = "2025-10-02T14:36:16.708Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c9/7243eb3f9eaabd1a88a5a5acadf06df2d83b100c62684b7425c6a11bcaa8/xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95", size = 28898, upload-time = "2025-10-02T14:36:17.843Z" }, + { url = "https://files.pythonhosted.org/packages/93/1e/8aec23647a34a249f62e2398c42955acd9b4c6ed5cf08cbea94dc46f78d2/xxhash-3.6.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0f7b7e2ec26c1666ad5fc9dbfa426a6a3367ceaf79db5dd76264659d509d73b0", size = 30662, upload-time = "2025-10-02T14:37:01.743Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/b14510b38ba91caf43006209db846a696ceea6a847a0c9ba0a5b1adc53d6/xxhash-3.6.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5dc1e14d14fa0f5789ec29a7062004b5933964bb9b02aae6622b8f530dc40296", size = 41056, upload-time = "2025-10-02T14:37:02.879Z" }, + { url = "https://files.pythonhosted.org/packages/50/55/15a7b8a56590e66ccd374bbfa3f9ffc45b810886c8c3b614e3f90bd2367c/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:881b47fc47e051b37d94d13e7455131054b56749b91b508b0907eb07900d1c13", size = 36251, upload-time = "2025-10-02T14:37:04.44Z" }, + { url = "https://files.pythonhosted.org/packages/62/b2/5ac99a041a29e58e95f907876b04f7067a0242cb85b5f39e726153981503/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6dc31591899f5e5666f04cc2e529e69b4072827085c1ef15294d91a004bc1bd", size = 32481, upload-time = "2025-10-02T14:37:05.869Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d9/8d95e906764a386a3d3b596f3c68bb63687dfca806373509f51ce8eea81f/xxhash-3.6.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:15e0dac10eb9309508bfc41f7f9deaa7755c69e35af835db9cb10751adebc35d", size = 31565, upload-time = "2025-10-02T14:37:06.966Z" }, +] + [[package]] name = "yarl" version = "1.23.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "idna", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, - { name = "multidict", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, - { name = "propcache", marker = "python_full_version != '3.13.*' or sys_platform != 'win32'" }, + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, ] sdist = { url = "https://files.pythonhosted.org/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5", size = 194676, upload-time = "2026-03-01T22:07:53.373Z" } wheels = [