diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 41e68e02..1a4b7f8c 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -22,7 +22,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: @@ -33,7 +33,7 @@ jobs: uv-version: "0.11.16" enable-cache: true - - run: uv sync --locked --python ${{ matrix.python }} --extra dev + - run: uv sync --locked --python ${{ matrix.python }} --no-default-groups --extra dev --extra stim - run: uv run --no-sync pytest -m "not pyzx" @@ -42,7 +42,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: @@ -53,6 +53,6 @@ jobs: uv-version: "0.11.16" enable-cache: true - - run: uv sync --locked --python 3.13 --extra dev --extra pyzx + - run: uv sync --locked --python 3.13 --no-default-groups --extra dev --extra pyzx --extra stim - run: uv run --no-sync pytest -m pyzx diff --git a/CHANGELOG.md b/CHANGELOG.md index 77841982..c411fb12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- **QEC Stim MPP Import**: Added utilities for building `StabilizerCode` inputs from Stim `MPP` layers, including sparse Stim qubit id mapping, coordinate import, multi-layer selection, detector/logical-observable import, and the optional `graphqomb[stim]` extra. + ### Changed - **Development Tooling**: Use uv as the default dependency manager for local development, CI, documentation builds, and publishing workflows. diff --git a/graphqomb/qec/__init__.py b/graphqomb/qec/__init__.py new file mode 100644 index 00000000..35d2e270 --- /dev/null +++ b/graphqomb/qec/__init__.py @@ -0,0 +1 @@ +"""qec submodule.""" diff --git a/graphqomb/qec/qeccode.py b/graphqomb/qec/qeccode.py new file mode 100644 index 00000000..077eb0fc --- /dev/null +++ b/graphqomb/qec/qeccode.py @@ -0,0 +1,375 @@ +"""QEC Code object.""" + +from __future__ import annotations + +from enum import Enum, auto +from typing import TYPE_CHECKING, NamedTuple + +from scipy.sparse import csr_array + +from graphqomb.common import Axis, AxisMeasBasis, Sign +from graphqomb.graphstate import GraphState + +if TYPE_CHECKING: + from collections.abc import Mapping + + +_TYPE_II_CHAIN_LENGTH = 3 + + +class YFoliation(Enum): + """Y-foliation graph-state builder variant.""" + + TYPE_I = auto() + TYPE_II = auto() + + +class StabilizerCode: + """A stabilizer code.""" + + def __init__( + self, + stabilizer_matrix: csr_array, + *, + stabilizer_coords: Mapping[int, tuple[float, ...]] | None = None, + qubit_coords: Mapping[int, tuple[float, ...]] | None = None, + ) -> None: + if stabilizer_matrix.shape[1] % 2 != 0: + msg = "Stabilizer matrix must have an even number of columns." + raise ValueError(msg) + if stabilizer_coords is not None: + _validate_coordinate_lengths(stabilizer_coords, expected_lengths={3}, label="stabilizer_coords") + if qubit_coords is not None: + _validate_coordinate_lengths(qubit_coords, expected_lengths={2, 3}, label="qubit_coords") + + self.hx = csr_array(stabilizer_matrix[:, : stabilizer_matrix.shape[1] // 2]) + self.hz = csr_array(stabilizer_matrix[:, stabilizer_matrix.shape[1] // 2 :]) + + self.stabilizer_coord = stabilizer_coords + self.qubit_coord = qubit_coords + + @property + def num_stabilizers(self) -> int: + """Number of stabilizers.""" + return int(self.hx.shape[0]) + + @property + def num_qubits(self) -> int: + """Number of qubits.""" + return int(self.hx.shape[1]) + + +class StabilizerGraphStateBuildResult(NamedTuple): + """Result of building a graph state from a stabilizer code.""" + + graph: GraphState + data_nodes: dict[tuple[int, int], int] + ancilla_nodes: dict[int, int] + + +class _DataLayerPlan(NamedTuple): + """Data-node layer layout for graph-state construction.""" + + data_layers: dict[int, tuple[int, ...]] + coordinate_z_by_layer: dict[tuple[int, int], float] + meas_basis_by_qubit: dict[int, AxisMeasBasis] + y_foliation: YFoliation + + +class _StabilizerSupport(NamedTuple): + """Sparse support sets for one stabilizer row.""" + + hx: set[int] + hz: set[int] + + +def build_graph_state( + code: StabilizerCode, + z_base: int = 0, + *, + y_foliation: YFoliation = YFoliation.TYPE_I, +) -> StabilizerGraphStateBuildResult: + """Build a graph-state unit from a stabilizer code. + + Parameters + ---------- + code : `StabilizerCode` + Stabilizer code to convert. The X support is connected to the upper + data layer and the Z support is connected to the lower data layer. + z_base : `int`, optional + Lower data-layer index. The builder creates layers `z_base` and + `z_base + 1`, by default 0. + y_foliation : `YFoliation`, optional + Foliation variant. Type II uses a three-node Y-measured data chain only + for qubits that have an Hx=Hz=1 support in at least one stabilizer row. + + Returns + ------- + `StabilizerGraphStateBuildResult` + Graph state and maps from stabilizer/data indices to graph nodes. + + Raises + ------ + TypeError + If z_base is not an integer. + """ + if not isinstance(z_base, int): + msg = "z_base must be an integer." + raise TypeError(msg) + + graph = GraphState() + x_meas_basis = AxisMeasBasis(Axis.X, Sign.PLUS) + + data_layer_plan = _data_layer_plan( + code, + z_base=z_base, + y_foliation=y_foliation, + ) + data_nodes = _add_layered_data_nodes(graph, code, data_layer_plan) + ancilla_nodes = _add_ancilla_nodes(graph, code, data_nodes, data_layer_plan, x_meas_basis) + + return StabilizerGraphStateBuildResult(graph, data_nodes, ancilla_nodes) + + +def _data_layer_plan( + code: StabilizerCode, + *, + z_base: int, + y_foliation: YFoliation, +) -> _DataLayerPlan: + data_layers: dict[int, tuple[int, ...]] = {} + coordinate_z_by_layer: dict[tuple[int, int], float] = {} + meas_basis_by_qubit: dict[int, AxisMeasBasis] = {} + x_meas_basis = AxisMeasBasis(Axis.X, Sign.PLUS) + y_meas_basis = AxisMeasBasis(Axis.Y, Sign.PLUS) + y_chain_qubits: set[int] = _qubits_with_y_support(code) if y_foliation is YFoliation.TYPE_II else set() + + for qubit in range(code.num_qubits): + if qubit in y_chain_qubits: + layers = (z_base, z_base + 1, z_base + 2) + coordinate_zs = ( + float(z_base), + float(z_base) + 0.5, + float(z_base + 1), + ) + meas_basis = y_meas_basis + else: + layers = (z_base, z_base + 1) + coordinate_zs = (float(z_base), float(z_base + 1)) + meas_basis = x_meas_basis + + data_layers[qubit] = layers + meas_basis_by_qubit[qubit] = meas_basis + for layer, coordinate_z in zip(layers, coordinate_zs, strict=True): + coordinate_z_by_layer[qubit, layer] = coordinate_z + + return _DataLayerPlan( + data_layers=data_layers, + coordinate_z_by_layer=coordinate_z_by_layer, + meas_basis_by_qubit=meas_basis_by_qubit, + y_foliation=y_foliation, + ) + + +def _add_layered_data_nodes( + graph: GraphState, + code: StabilizerCode, + data_layer_plan: _DataLayerPlan, +) -> dict[tuple[int, int], int]: + """Add layered data nodes. + + Returns + ------- + `dict`[`tuple`[`int`, `int`], `int`] + Mapping from physical qubit and z layer to graph node. + """ + data_nodes: dict[tuple[int, int], int] = {} + for qubit in range(code.num_qubits): + previous_node: int | None = None + for layer in data_layer_plan.data_layers[qubit]: + node = graph.add_node( + coordinate=_data_coordinate(code, qubit, data_layer_plan.coordinate_z_by_layer[qubit, layer]) + ) + if previous_node is not None: + graph.add_edge(previous_node, node) + graph.assign_meas_basis(node, data_layer_plan.meas_basis_by_qubit[qubit]) + data_nodes[qubit, layer] = node + previous_node = node + + return data_nodes + + +def _add_ancilla_nodes( + graph: GraphState, + code: StabilizerCode, + data_nodes: dict[tuple[int, int], int], + data_layer_plan: _DataLayerPlan, + meas_basis: AxisMeasBasis, +) -> dict[int, int]: + """Add ancilla nodes and stabilizer-support edges. + + Returns + ------- + `dict`[`int`, `int`] + Mapping from stabilizer row index to graph node. + """ + ancilla_nodes: dict[int, int] = {} + hx = code.hx.copy() + hz = code.hz.copy() + hx.eliminate_zeros() + hz.eliminate_zeros() + for stabilizer in range(code.num_stabilizers): + explicit_ancilla_coord = _explicit_ancilla_coordinate(code, stabilizer) + ancilla_node = graph.add_node(coordinate=explicit_ancilla_coord) + graph.assign_meas_basis(ancilla_node, meas_basis) + ancilla_nodes[stabilizer] = ancilla_node + + connected_data_nodes = _connect_stabilizer_support( + graph, + ancilla_node=ancilla_node, + support=_StabilizerSupport( + hx=set(_row_support(hx, stabilizer)), + hz=set(_row_support(hz, stabilizer)), + ), + data_nodes=data_nodes, + data_layer_plan=data_layer_plan, + ) + + if explicit_ancilla_coord is None: + inferred_coord = _average_node_coordinates(graph, connected_data_nodes) + if inferred_coord is not None: + graph.set_coordinate(ancilla_node, inferred_coord) + + return ancilla_nodes + + +def _connect_stabilizer_support( + graph: GraphState, + *, + ancilla_node: int, + support: _StabilizerSupport, + data_nodes: dict[tuple[int, int], int], + data_layer_plan: _DataLayerPlan, +) -> list[int]: + connected_data_nodes: list[int] = [] + for qubit in sorted(support.hx | support.hz): + layers = data_layer_plan.data_layers[qubit] + if data_layer_plan.y_foliation is YFoliation.TYPE_II and len(layers) == _TYPE_II_CHAIN_LENGTH: + layer = _type_ii_support_layer(layers, has_x=qubit in support.hx, has_z=qubit in support.hz) + data_node = data_nodes[qubit, layer] + graph.add_edge(ancilla_node, data_node) + connected_data_nodes.append(data_node) + continue + + if qubit in support.hz: + data_node = data_nodes[qubit, layers[0]] + graph.add_edge(ancilla_node, data_node) + connected_data_nodes.append(data_node) + if qubit in support.hx: + data_node = data_nodes[qubit, layers[-1]] + graph.add_edge(ancilla_node, data_node) + connected_data_nodes.append(data_node) + + return connected_data_nodes + + +def _type_ii_support_layer(layers: tuple[int, ...], *, has_x: bool, has_z: bool) -> int: + if has_z and not has_x: + return layers[0] + if has_z and has_x: + return layers[1] + return layers[2] + + +def _qubits_with_y_support(code: StabilizerCode) -> set[int]: + hx = code.hx.copy() + hz = code.hz.copy() + hx.eliminate_zeros() + hz.eliminate_zeros() + + y_qubits: set[int] = set() + for stabilizer in range(code.num_stabilizers): + y_qubits.update(set(_row_support(hx, stabilizer)) & set(_row_support(hz, stabilizer))) + return y_qubits + + +def _validate_coordinate_lengths( + coordinates: Mapping[int, tuple[float, ...]], + *, + expected_lengths: set[int], + label: str, +) -> None: + """Validate coordinate tuple lengths. + + Raises + ------ + ValueError + If any coordinate has an invalid length. + """ + for index, coord in coordinates.items(): + if len(coord) not in expected_lengths: + expected = " or ".join(str(length) for length in sorted(expected_lengths)) + msg = f"{label}[{index}] must have length {expected}." + raise ValueError(msg) + + +def _data_coordinate(code: StabilizerCode, qubit: int, z: float) -> tuple[float, float, float] | None: + """Return the 3D coordinate of a layered data node. + + Returns + ------- + `tuple`[`float`, `float`, `float`] | `None` + Lifted 3D coordinate, or None when the qubit has no coordinate. + """ + if code.qubit_coord is None or qubit not in code.qubit_coord: + return None + coord = code.qubit_coord[qubit] + return (float(coord[0]), float(coord[1]), float(z)) + + +def _explicit_ancilla_coordinate(code: StabilizerCode, stabilizer: int) -> tuple[float, float, float] | None: + """Return an explicitly supplied ancilla coordinate, if present. + + Returns + ------- + `tuple`[`float`, `float`, `float`] | `None` + Explicit 3D coordinate, or None when no coordinate is supplied. + """ + if code.stabilizer_coord is None or stabilizer not in code.stabilizer_coord: + return None + coord = code.stabilizer_coord[stabilizer] + return (float(coord[0]), float(coord[1]), float(coord[2])) + + +def _row_support(matrix: csr_array, row: int) -> list[int]: + """Return nonzero column indices in a CSR sparse row. + + Returns + ------- + `list`[`int`] + Nonzero column indices for the row. + """ + start = int(matrix.indptr[row]) + end = int(matrix.indptr[row + 1]) + return [int(col) for col in matrix.indices[start:end]] + + +def _average_node_coordinates(graph: GraphState, nodes: list[int]) -> tuple[float, float, float] | None: + """Return the componentwise average of node coordinates when all are available. + + Returns + ------- + `tuple`[`float`, `float`, `float`] | `None` + Average coordinate, or None when no average can be inferred. + """ + if not nodes: + return None + coordinates = graph.coordinates + if any(node not in coordinates for node in nodes): + return None + + return ( + sum(coordinates[node][0] for node in nodes) / len(nodes), + sum(coordinates[node][1] for node in nodes) / len(nodes), + sum(coordinates[node][2] for node in nodes) / len(nodes), + ) diff --git a/graphqomb/qec/stim_mpp.py b/graphqomb/qec/stim_mpp.py new file mode 100644 index 00000000..0ad9ee3b --- /dev/null +++ b/graphqomb/qec/stim_mpp.py @@ -0,0 +1,433 @@ +"""Build stabilizer-code inputs from Stim MPP layers.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from pathlib import Path +from typing import TYPE_CHECKING + +import stim +from scipy.sparse import csr_array, lil_array + +from graphqomb.qec.qeccode import StabilizerCode + +if TYPE_CHECKING: + from collections.abc import Mapping, Sequence + + +PauliSupport = tuple[tuple[int, str], ...] + + +@dataclass(frozen=True) +class _MppProductRecord: + record_index: int + support: PauliSupport + + +def _empty_logical_observable_record_indices() -> dict[int, frozenset[int]]: + return {} + + +@dataclass(frozen=True) +class StimMppExtraction: + """Stabilizer-code data extracted from Stim MPP products. + + Attributes + ---------- + code : `StabilizerCode` + Dense-column stabilizer code using the ``[Hx | Hz]`` convention. + stim_to_column : `dict`[`int`, `int`] + Mapping from original Stim qubit ids to dense matrix columns. + column_to_stim : `dict`[`int`, `int`] + Inverse dense-column mapping. + supports : `tuple`[`PauliSupport`, ...] + Original Stim Pauli supports, one support per stabilizer row. + detector_rows : `tuple`[`frozenset`[`int`], ...] + Detector groups as selected-MPP stabilizer row indices. If a Stim + detector also references measurements outside the selected MPP products, + only rows represented in this extraction are included here. + logical_observable_rows : `dict`[`int`, `frozenset`[`int`]] + Logical observables as selected-MPP stabilizer row indices, keyed by + Stim logical observable index. External measurement records are ignored + in this row view. + detector_record_indices : `tuple`[`frozenset`[`int`], ...] + Absolute Stim measurement-record indices for detectors that touch at + least one MPP product represented in this extraction. + logical_observable_record_indices : `dict`[`int`, `frozenset`[`int`]] + Absolute Stim measurement-record indices for logical observables that + touch at least one MPP product represented in this extraction. + """ + + code: StabilizerCode + stim_to_column: dict[int, int] + column_to_stim: dict[int, int] + supports: tuple[PauliSupport, ...] + detector_rows: tuple[frozenset[int], ...] + logical_observable_rows: dict[int, frozenset[int]] + detector_record_indices: tuple[frozenset[int], ...] = () + logical_observable_record_indices: dict[int, frozenset[int]] = field( + default_factory=_empty_logical_observable_record_indices + ) + + def detector_groups(self, ancilla_nodes: Mapping[int, int]) -> list[set[int]]: + """Return detector groups mapped to graph node ids for ``qompile``. + + Parameters + ---------- + ancilla_nodes : `collections.abc.Mapping`[`int`, `int`] + Mapping from selected-MPP stabilizer rows to graph node ids. + + Returns + ------- + `list`[`set`[`int`]] + Detector groups suitable for ``qompile(..., parity_check_group=...)``. + """ + return [_map_rows_to_nodes(rows, ancilla_nodes, "detector") for rows in self.detector_rows] + + def logical_observables(self, ancilla_nodes: Mapping[int, int]) -> dict[int, set[int]]: + """Return logical observables mapped to graph node ids for ``qompile``. + + Parameters + ---------- + ancilla_nodes : `collections.abc.Mapping`[`int`, `int`] + Mapping from selected-MPP stabilizer rows to graph node ids. + + Returns + ------- + `dict`[`int`, `set`[`int`]] + Logical observables suitable for ``qompile(..., logical_observables=...)``. + """ + return { + logical_idx: _map_rows_to_nodes(rows, ancilla_nodes, f"logical observable {logical_idx}") + for logical_idx, rows in self.logical_observable_rows.items() + } + + +@dataclass(frozen=True) +class _StimMppAnnotations: + detector_rows: tuple[frozenset[int], ...] + logical_observable_rows: dict[int, frozenset[int]] + detector_record_indices: tuple[frozenset[int], ...] + logical_observable_record_indices: dict[int, frozenset[int]] + + +def stabilizer_code_from_stim_file( + path: str | Path, + *, + mpp_layer: int | None = 0, + coord_dims: int = 2, +) -> StimMppExtraction: + """Build a stabilizer code from MPP products in a Stim file. + + Returns + ------- + `StimMppExtraction` + Extracted stabilizer code, qubit mapping, and Pauli supports. + """ + return stabilizer_code_from_stim_text( + Path(path).read_text(encoding="utf-8"), + mpp_layer=mpp_layer, + coord_dims=coord_dims, + ) + + +def stabilizer_code_from_stim_text( + text: str, + *, + mpp_layer: int | None = 0, + coord_dims: int = 2, +) -> StimMppExtraction: + """Build a stabilizer code from MPP products in Stim text. + + The selected MPP products are interpreted as a parity-check matrix using + the ``[Hx | Hz]`` convention. ``X`` and ``Y`` targets set entries in + ``Hx``; ``Z`` and ``Y`` targets set entries in ``Hz``. By default, + ``mpp_layer=0`` selects the first contiguous MPP layer. Pass + ``mpp_layer=None`` to select all MPP products in the flattened Stim file. + + Returns + ------- + `StimMppExtraction` + Extracted stabilizer code, qubit mapping, and Pauli supports. + + Raises + ------ + ValueError + If the requested MPP layer or coordinate format is invalid. + """ + if mpp_layer is not None and mpp_layer < 0: + msg = "mpp_layer must be non-negative." + raise ValueError(msg) + if coord_dims not in {2, 3}: + msg = "coord_dims must be 2 or 3." + raise ValueError(msg) + + circuit = stim.Circuit(text).flattened() + coordinate_by_stim_id = _extract_qubit_coordinates(circuit, coord_dims=coord_dims) + layers = _extract_mpp_layers(circuit) + + selected_layer = _select_mpp_products(layers, mpp_layer=mpp_layer) + supports = tuple(product.support for product in selected_layer) + if not supports: + layer_label = "file" if mpp_layer is None else f"layer {mpp_layer}" + msg = f"MPP {layer_label} is empty." + raise ValueError(msg) + record_to_row = {product.record_index: row for row, product in enumerate(selected_layer)} + annotations = _extract_selected_mpp_annotations( + circuit, + record_to_row=record_to_row, + ) + + matrix, stim_to_column, column_to_stim, qubit_coords = _build_stabilizer_data( + supports, + coordinate_by_stim_id, + ) + + return StimMppExtraction( + code=StabilizerCode(matrix, qubit_coords=qubit_coords), + stim_to_column=stim_to_column, + column_to_stim=column_to_stim, + supports=supports, + detector_rows=annotations.detector_rows, + logical_observable_rows=annotations.logical_observable_rows, + detector_record_indices=annotations.detector_record_indices, + logical_observable_record_indices=annotations.logical_observable_record_indices, + ) + + +def _build_stabilizer_data( + supports: Sequence[PauliSupport], + coordinate_by_stim_id: Mapping[int, tuple[float, ...]], +) -> tuple[csr_array, dict[int, int], dict[int, int], dict[int, tuple[float, ...]]]: + stim_ids = sorted({qid for support in supports for qid, _pauli in support}) + stim_to_column = {qid: column for column, qid in enumerate(stim_ids)} + column_to_stim = {column: qid for qid, column in stim_to_column.items()} + + num_qubits = len(stim_ids) + matrix = lil_array((len(supports), 2 * num_qubits), dtype=bool) + for row, support in enumerate(supports): + for stim_id, pauli in support: + column = stim_to_column[stim_id] + if pauli in {"X", "Y"}: + matrix[row, column] = True + if pauli in {"Z", "Y"}: + matrix[row, num_qubits + column] = True + + qubit_coords = {stim_to_column[qid]: coord for qid, coord in coordinate_by_stim_id.items() if qid in stim_to_column} + return matrix.tocsr(), stim_to_column, column_to_stim, qubit_coords + + +def _extract_qubit_coordinates( + circuit: stim.Circuit, + *, + coord_dims: int, +) -> dict[int, tuple[float, ...]]: + coordinates: dict[int, tuple[float, ...]] = {} + for instruction in circuit: + if not isinstance(instruction, stim.CircuitInstruction): + msg = "Flattened Stim circuit unexpectedly contains a repeat block." + raise TypeError(msg) + if instruction.name != "QUBIT_COORDS": + continue + args = instruction.gate_args_copy() + if len(args) < coord_dims: + msg = f"QUBIT_COORDS has {len(args)} coordinate(s), fewer than requested coord_dims={coord_dims}." + raise ValueError(msg) + coord = tuple(float(value) for value in args[:coord_dims]) + for target in instruction.targets_copy(): + coordinates[int(target.value)] = coord + return coordinates + + +def _extract_mpp_layers(circuit: stim.Circuit) -> list[list[_MppProductRecord]]: + layers: list[list[_MppProductRecord]] = [] + current_layer: list[_MppProductRecord] | None = None + measurement_count = 0 + + for instruction in circuit: + if not isinstance(instruction, stim.CircuitInstruction): + msg = "Flattened Stim circuit unexpectedly contains a repeat block." + raise TypeError(msg) + if instruction.name == "MPP": + if current_layer is None: + current_layer = [] + products = _mpp_targets_to_products(instruction.targets_copy()) + if len(products) != instruction.num_measurements: + msg = "Stim MPP instruction measurement count does not match its parsed product count." + raise ValueError(msg) + current_layer.extend( + _MppProductRecord(record_index=measurement_count + offset, support=support) + for offset, support in enumerate(products) + ) + elif current_layer is not None: + layers.append(current_layer) + current_layer = None + measurement_count += instruction.num_measurements + + if current_layer is not None: + layers.append(current_layer) + return layers + + +def _select_mpp_products( + layers: Sequence[Sequence[_MppProductRecord]], + *, + mpp_layer: int | None, +) -> list[_MppProductRecord]: + if mpp_layer is None: + return [product for layer in layers for product in layer] + if mpp_layer >= len(layers): + msg = f"Stim circuit has {len(layers)} MPP layer(s); cannot select layer {mpp_layer}." + raise ValueError(msg) + return list(layers[mpp_layer]) + + +def _extract_selected_mpp_annotations( + circuit: stim.Circuit, + *, + record_to_row: Mapping[int, int], +) -> _StimMppAnnotations: + detector_rows: list[frozenset[int]] = [] + detector_record_indices: list[frozenset[int]] = [] + logical_observable_rows: dict[int, set[int]] = {} + logical_observable_record_indices: dict[int, set[int]] = {} + measurement_count = 0 + + for instruction in circuit: + if not isinstance(instruction, stim.CircuitInstruction): + msg = "Flattened Stim circuit unexpectedly contains a repeat block." + raise TypeError(msg) + + if instruction.name == "DETECTOR": + rows, record_indices = _record_targets_to_selected_mpp_rows( + instruction.targets_copy(), + measurement_count=measurement_count, + record_to_row=record_to_row, + instruction_name=instruction.name, + ) + if rows is not None: + detector_rows.append(frozenset(rows)) + detector_record_indices.append(record_indices) + elif instruction.name == "OBSERVABLE_INCLUDE": + logical_idx = _observable_index(instruction) + rows, record_indices = _record_targets_to_selected_mpp_rows( + instruction.targets_copy(), + measurement_count=measurement_count, + record_to_row=record_to_row, + instruction_name=f"OBSERVABLE_INCLUDE({logical_idx})", + ) + if rows is not None: + logical_observable_rows.setdefault(logical_idx, set()).symmetric_difference_update(rows) + logical_observable_record_indices.setdefault(logical_idx, set()).symmetric_difference_update( + record_indices + ) + + measurement_count += instruction.num_measurements + + return _StimMppAnnotations( + detector_rows=tuple(detector_rows), + logical_observable_rows={ + logical_idx: frozenset(rows) for logical_idx, rows in sorted(logical_observable_rows.items()) + }, + detector_record_indices=tuple(detector_record_indices), + logical_observable_record_indices={ + logical_idx: frozenset(records) + for logical_idx, records in sorted(logical_observable_record_indices.items()) + }, + ) + + +def _record_targets_to_selected_mpp_rows( + targets: Sequence[stim.GateTarget], + *, + measurement_count: int, + record_to_row: Mapping[int, int], + instruction_name: str, +) -> tuple[set[int] | None, frozenset[int]]: + rows: set[int] = set() + record_indices: set[int] = set() + saw_selected_record = False + + for target in targets: + if not target.is_measurement_record_target: + msg = f"{instruction_name} contains unsupported target {target!r}; only rec targets are supported." + raise ValueError(msg) + record_index = measurement_count + int(target.value) + if record_index in record_indices: + record_indices.remove(record_index) + else: + record_indices.add(record_index) + row = record_to_row.get(record_index) + if row is None: + continue + saw_selected_record = True + if row in rows: + rows.remove(row) + else: + rows.add(row) + + if not saw_selected_record: + return None, frozenset(record_indices) + return rows, frozenset(record_indices) + + +def _observable_index(instruction: stim.CircuitInstruction) -> int: + args = instruction.gate_args_copy() + if len(args) != 1 or not args[0].is_integer(): + msg = "OBSERVABLE_INCLUDE must have one integer observable index." + raise ValueError(msg) + return int(args[0]) + + +def _mpp_targets_to_products(targets: Sequence[stim.GateTarget]) -> list[PauliSupport]: + products: list[PauliSupport] = [] + current: list[tuple[int, str]] = [] + seen_in_current: set[int] = set() + expect_pauli = True + + for target in targets: + if target.is_combiner: + if expect_pauli: + msg = "Invalid MPP target list: unexpected combiner." + raise ValueError(msg) + expect_pauli = True + continue + + pauli = _target_pauli(target) + if current and not expect_pauli: + products.append(tuple(current)) + current = [] + seen_in_current = set() + + qid = int(target.value) + if qid in seen_in_current: + msg = f"Invalid MPP product: qubit {qid} appears more than once." + raise ValueError(msg) + current.append((qid, pauli)) + seen_in_current.add(qid) + expect_pauli = False + + if expect_pauli: + msg = "Invalid MPP target list: trailing combiner or empty product." + raise ValueError(msg) + products.append(tuple(current)) + return products + + +def _target_pauli(target: stim.GateTarget) -> str: + if target.is_x_target: + return "X" + if target.is_y_target: + return "Y" + if target.is_z_target: + return "Z" + msg = f"Unsupported MPP target: {target!r}." + raise ValueError(msg) + + +def _map_rows_to_nodes(rows: frozenset[int], ancilla_nodes: Mapping[int, int], label: str) -> set[int]: + missing_rows = sorted(row for row in rows if row not in ancilla_nodes) + if missing_rows: + msg = f"Cannot map {label}; ancilla node map is missing stabilizer row(s): {missing_rows}." + raise ValueError(msg) + return {ancilla_nodes[row] for row in rows} diff --git a/pyproject.toml b/pyproject.toml index 4619ceb6..534d8de9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ dependencies = [ "networkx", "numpy>=1.22,<3", "ortools>=9,<10", + "scipy>=1.15.3", "typing-extensions", ] @@ -53,11 +54,11 @@ dependencies = [ dev = [ "mypy==2.1.0", "pre-commit==4.6.0", - "pyright==1.1.410", - "pytest==9.0.3", + "pyright==1.1.411", + "pytest==9.1.1", "pytest-cov==7.1.0", - "ruff==0.15.17", - "types-networkx==3.6.1.20260518", + "ruff==0.15.20", + "types-networkx==3.6.1.20260624", ] doc = [ "furo>=2025.12.19", @@ -68,6 +69,9 @@ doc = [ pyzx = [ "pyzx>=0.10", ] +stim = [ + "stim>=1.15", +] [project.urls] Homepage = "https://github.com/TeamGraphix/graphqomb" @@ -187,3 +191,24 @@ reportUnknownLambdaType = "information" reportUnknownMemberType = "information" reportUnknownParameterType = "information" reportUnknownVariableType = "information" + +[tool.uv] +default-groups = ["dev", "stim", "pyzx"] + +[dependency-groups] +dev = [ + "mypy==2.1.0", + "pre-commit==4.6.0", + "pyright==1.1.411", + "pytest==9.1.1", + "pytest-cov==7.1.0", + "ruff==0.15.20", + "scipy-stubs>=1.15.3.0", + "types-networkx==3.6.1.20260624", +] +stim = [ + "stim>=1.15", +] +pyzx = [ + "pyzx>=0.10", +] diff --git a/tests/test_qec.py b/tests/test_qec.py new file mode 100644 index 00000000..a23953c5 --- /dev/null +++ b/tests/test_qec.py @@ -0,0 +1,206 @@ +"""Tests for QEC graph-state builders.""" + +from __future__ import annotations + +import math +from typing import Any, cast + +import pytest +from scipy.sparse import csr_array + +from graphqomb.common import Axis, AxisMeasBasis, Sign +from graphqomb.qec.qeccode import StabilizerCode, YFoliation, build_graph_state + + +def _matrix(data: list[list[int]]) -> csr_array[Any, tuple[int, int]]: + return cast("csr_array[Any, tuple[int, int]]", csr_array(data)) + + +def _assert_axis_meas_basis(meas_basis: object, axis: Axis) -> None: + assert isinstance(meas_basis, AxisMeasBasis) + assert meas_basis.axis == axis + assert meas_basis.sign == Sign.PLUS + expected_angle = math.pi / 2 if axis == Axis.Y else 0.0 + assert math.isclose(meas_basis.angle, expected_angle) + + +def test_build_graph_state_connects_stabilizer_supports() -> None: + matrix = _matrix( + [ + [0, 0, 0, 1, 0, 2], # Z support on q0 and q2. + [0, 3, 0, 0, 0, 0], # X support on q1. + [1, 0, 0, 1, 0, 0], # X and Z support on q0. + ] + ) + code = StabilizerCode(matrix) + + result = build_graph_state(code) + graph = result.graph + + for qubit in range(3): + assert graph.has_edge(result.data_nodes[qubit, 0], result.data_nodes[qubit, 1]) + + ancilla0 = result.ancilla_nodes[0] + assert graph.has_edge(ancilla0, result.data_nodes[0, 0]) + assert graph.has_edge(ancilla0, result.data_nodes[2, 0]) + + ancilla1 = result.ancilla_nodes[1] + assert graph.has_edge(ancilla1, result.data_nodes[1, 1]) + + ancilla2 = result.ancilla_nodes[2] + assert graph.has_edge(ancilla2, result.data_nodes[0, 0]) + assert graph.has_edge(ancilla2, result.data_nodes[0, 1]) + + assert graph.number_of_nodes() == 9 + assert graph.number_of_edges() == 8 + + +def test_build_graph_state_assigns_x_measurement_to_all_nodes() -> None: + code = StabilizerCode(_matrix([[1, 0, 0, 1]])) + + result = build_graph_state(code) + + for node in result.graph.nodes: + _assert_axis_meas_basis(result.graph.meas_bases[node], Axis.X) + + +def test_build_graph_state_returns_index_to_node_maps() -> None: + code = StabilizerCode(_matrix([[1, 0, 0, 1]])) + + result = build_graph_state(code) + + assert set(result.data_nodes) == {(0, 0), (0, 1), (1, 0), (1, 1)} + assert set(result.ancilla_nodes) == {0} + assert set(result.data_nodes.values()).isdisjoint(result.ancilla_nodes.values()) + + +def test_build_graph_state_type_ii_routes_y_support_through_three_node_y_chain() -> None: + code = StabilizerCode( + _matrix( + [ + [0, 1], # Z support. + [1, 1], # Y support. + [1, 0], # X support. + ] + ) + ) + + result = build_graph_state(code, y_foliation=YFoliation.TYPE_II) + graph = result.graph + + assert set(result.data_nodes) == {(0, 0), (0, 1), (0, 2)} + assert graph.has_edge(result.data_nodes[0, 0], result.data_nodes[0, 1]) + assert graph.has_edge(result.data_nodes[0, 1], result.data_nodes[0, 2]) + assert graph.has_edge(result.ancilla_nodes[0], result.data_nodes[0, 0]) + assert graph.has_edge(result.ancilla_nodes[1], result.data_nodes[0, 1]) + assert graph.has_edge(result.ancilla_nodes[2], result.data_nodes[0, 2]) + assert not graph.has_edge(result.ancilla_nodes[1], result.data_nodes[0, 0]) + assert not graph.has_edge(result.ancilla_nodes[1], result.data_nodes[0, 2]) + + for data_node in result.data_nodes.values(): + _assert_axis_meas_basis(graph.meas_bases[data_node], Axis.Y) + for ancilla_node in result.ancilla_nodes.values(): + _assert_axis_meas_basis(graph.meas_bases[ancilla_node], Axis.X) + + assert graph.number_of_nodes() == 6 + assert graph.number_of_edges() == 5 + + +def test_build_graph_state_type_ii_keeps_two_node_x_chain_without_y_support() -> None: + code = StabilizerCode( + _matrix( + [ + [0, 1], # Z support. + [1, 0], # X support. + ] + ) + ) + + result = build_graph_state(code, y_foliation=YFoliation.TYPE_II) + graph = result.graph + + assert set(result.data_nodes) == {(0, 0), (0, 1)} + assert graph.has_edge(result.data_nodes[0, 0], result.data_nodes[0, 1]) + assert graph.has_edge(result.ancilla_nodes[0], result.data_nodes[0, 0]) + assert graph.has_edge(result.ancilla_nodes[1], result.data_nodes[0, 1]) + for data_node in result.data_nodes.values(): + _assert_axis_meas_basis(graph.meas_bases[data_node], Axis.X) + + +def test_build_graph_state_type_ii_aligns_three_node_chain_output_z_with_two_node_chain() -> None: + code = StabilizerCode( + _matrix([[1, 1]]), + qubit_coords={0: (10.0, 20.0)}, + ) + + result = build_graph_state(code, z_base=5, y_foliation=YFoliation.TYPE_II) + coords = result.graph.coordinates + + assert set(result.data_nodes) == {(0, 5), (0, 6), (0, 7)} + assert coords[result.data_nodes[0, 5]] == (10.0, 20.0, 5.0) + assert coords[result.data_nodes[0, 6]] == (10.0, 20.0, 5.5) + assert coords[result.data_nodes[0, 7]] == (10.0, 20.0, 6.0) + assert coords[result.ancilla_nodes[0]] == (10.0, 20.0, 5.5) + + +def test_build_graph_state_lifts_coordinates_to_shifted_3d_layers() -> None: + code = StabilizerCode( + _matrix([[0, 1, 1, 0]]), + qubit_coords={ + 0: (10.0, 20.0), + 1: (30.0, 40.0, 999.0), + }, + ) + + result = build_graph_state(code, z_base=5) + coords = result.graph.coordinates + + assert set(result.data_nodes) == {(0, 5), (0, 6), (1, 5), (1, 6)} + assert coords[result.data_nodes[0, 5]] == (10.0, 20.0, 5.0) + assert coords[result.data_nodes[0, 6]] == (10.0, 20.0, 6.0) + assert coords[result.data_nodes[1, 5]] == (30.0, 40.0, 5.0) + assert coords[result.data_nodes[1, 6]] == (30.0, 40.0, 6.0) + assert coords[result.ancilla_nodes[0]] == (20.0, 30.0, 5.5) + + +def test_build_graph_state_explicit_ancilla_coordinate_overrides_average() -> None: + code = StabilizerCode( + _matrix([[0, 1, 1, 0]]), + stabilizer_coords={0: (1.0, 2.0, 3.0)}, + qubit_coords={ + 0: (10.0, 20.0), + 1: (30.0, 40.0), + }, + ) + + result = build_graph_state(code) + + assert result.graph.coordinates[result.ancilla_nodes[0]] == (1.0, 2.0, 3.0) + + +def test_build_graph_state_allows_missing_coordinates() -> None: + code = StabilizerCode(_matrix([[1, 0, 0, 1]])) + + result = build_graph_state(code) + + assert result.graph.coordinates == {} + + +def test_stabilizer_code_rejects_odd_column_matrix() -> None: + with pytest.raises(ValueError, match="even number of columns"): + StabilizerCode(_matrix([[1, 0, 1]])) + + +def test_stabilizer_code_rejects_invalid_coordinate_lengths() -> None: + with pytest.raises(ValueError, match=r"qubit_coords\[0\] must have length 2 or 3"): + StabilizerCode(_matrix([[1, 0]]), qubit_coords={0: (1.0,)}) + + with pytest.raises(ValueError, match=r"stabilizer_coords\[0\] must have length 3"): + StabilizerCode(_matrix([[1, 0]]), stabilizer_coords={0: (1.0, 2.0)}) + + +def test_build_graph_state_rejects_non_integer_z_base() -> None: + code = StabilizerCode(_matrix([[1, 0]])) + + with pytest.raises(TypeError, match="z_base must be an integer"): + build_graph_state(code, z_base=0.5) # type: ignore[arg-type] diff --git a/tests/test_stim_mpp.py b/tests/test_stim_mpp.py new file mode 100644 index 00000000..f15c43b7 --- /dev/null +++ b/tests/test_stim_mpp.py @@ -0,0 +1,176 @@ +"""Tests for building QEC codes from Stim MPP layers.""" + +from __future__ import annotations + +import pytest + +from graphqomb.qec.qeccode import build_graph_state +from graphqomb.qec.stim_mpp import stabilizer_code_from_stim_text + + +def test_stabilizer_code_from_stim_mpp_sets_x_z_and_y_support() -> None: + extraction = stabilizer_code_from_stim_text( + """ + QUBIT_COORDS(10, 20, 30) 0 + QUBIT_COORDS(11, 21, 31) 1 + QUBIT_COORDS(12, 22, 32) 2 + MPP X0*Y1*Z2 + """ + ) + + matrix = (extraction.code.hx.toarray(), extraction.code.hz.toarray()) + + assert extraction.stim_to_column == {0: 0, 1: 1, 2: 2} + assert extraction.supports == (((0, "X"), (1, "Y"), (2, "Z")),) + assert matrix[0].tolist() == [[True, True, False]] + assert matrix[1].tolist() == [[False, True, True]] + assert extraction.code.qubit_coord == { + 0: (10.0, 20.0), + 1: (11.0, 21.0), + 2: (12.0, 22.0), + } + + +def test_stabilizer_code_from_stim_mpp_preserves_3d_coordinates_when_requested() -> None: + extraction = stabilizer_code_from_stim_text( + """ + QUBIT_COORDS(10, 20, 30) 0 + QUBIT_COORDS(11, 21, 31) 1 + MPP X0*Z1 + """, + coord_dims=3, + ) + + assert extraction.code.qubit_coord == { + 0: (10.0, 20.0, 30.0), + 1: (11.0, 21.0, 31.0), + } + + +def test_stabilizer_code_from_stim_mpp_handles_sparse_stim_ids_and_multiple_products() -> None: + extraction = stabilizer_code_from_stim_text( + """ + QUBIT_COORDS(1, 2) 10 + QUBIT_COORDS(3, 4) 12 + QUBIT_COORDS(5, 6) 99 + MPP X10*Y12 Z99 + TICK + MPP Z10*Z12 + """ + ) + + hx = extraction.code.hx.toarray().tolist() + hz = extraction.code.hz.toarray().tolist() + + assert extraction.stim_to_column == {10: 0, 12: 1, 99: 2} + assert extraction.column_to_stim == {0: 10, 1: 12, 2: 99} + assert extraction.supports == (((10, "X"), (12, "Y")), ((99, "Z"),)) + assert hx == [[True, True, False], [False, False, False]] + assert hz == [[False, True, False], [False, False, True]] + assert extraction.detector_rows == () + assert extraction.logical_observable_rows == {} + + +def test_stabilizer_code_from_stim_mpp_can_select_later_mpp_layer() -> None: + extraction = stabilizer_code_from_stim_text( + """ + QUBIT_COORDS(1, 2) 10 + QUBIT_COORDS(3, 4) 12 + MPP X10 + TICK + MPP Z10*Z12 + """, + mpp_layer=1, + ) + + assert extraction.stim_to_column == {10: 0, 12: 1} + assert extraction.supports == (((10, "Z"), (12, "Z")),) + assert extraction.code.hx.toarray().tolist() == [[False, False]] + assert extraction.code.hz.toarray().tolist() == [[True, True]] + + +def test_stabilizer_code_from_stim_mpp_rejects_missing_layer() -> None: + with pytest.raises(ValueError, match=r"has 1 MPP layer"): + stabilizer_code_from_stim_text("MPP X0\n", mpp_layer=1) + + +def test_stabilizer_code_from_stim_mpp_builds_graph_state() -> None: + extraction = stabilizer_code_from_stim_text( + """ + QUBIT_COORDS(0, 0) 0 + QUBIT_COORDS(1, 0) 1 + QUBIT_COORDS(2, 0) 2 + MPP X0*Y1*Z2 + MPP Z0*Z2 + """ + ) + + result = build_graph_state(extraction.code) + + assert result.graph.number_of_nodes() == 8 + assert result.graph.number_of_edges() == 9 + assert result.graph.coordinates[result.data_nodes[0, 0]] == (0.0, 0.0, 0.0) + assert result.graph.coordinates[result.data_nodes[0, 1]] == (0.0, 0.0, 1.0) + + +def test_stabilizer_code_from_stim_mpp_reads_detectors_and_observables() -> None: + extraction = stabilizer_code_from_stim_text( + """ + MPP X0*X1 Z2 X3 + DETECTOR rec[-1] rec[-3] + OBSERVABLE_INCLUDE(5) rec[-2] + """ + ) + + result = build_graph_state(extraction.code) + + assert extraction.detector_rows == (frozenset({0, 2}),) + assert extraction.logical_observable_rows == {5: frozenset({1})} + assert extraction.detector_record_indices == (frozenset({0, 2}),) + assert extraction.logical_observable_record_indices == {5: frozenset({1})} + assert extraction.detector_groups(result.ancilla_nodes) == [ + {result.ancilla_nodes[0], result.ancilla_nodes[2]}, + ] + assert extraction.logical_observables(result.ancilla_nodes) == {5: {result.ancilla_nodes[1]}} + + +def test_stabilizer_code_from_stim_mpp_accumulates_observable_rows_by_parity() -> None: + extraction = stabilizer_code_from_stim_text( + """ + MPP X0 X1 + OBSERVABLE_INCLUDE(2) rec[-1] + OBSERVABLE_INCLUDE(2) rec[-2] rec[-1] + """ + ) + + assert extraction.logical_observable_rows == {2: frozenset({0})} + assert extraction.logical_observable_record_indices == {2: frozenset({0})} + + +def test_stabilizer_code_from_stim_mpp_keeps_selected_rows_when_detector_refs_external_records() -> None: + extraction = stabilizer_code_from_stim_text( + """ + M 99 + MPP X0 + DETECTOR rec[-1] rec[-2] + """ + ) + + assert extraction.detector_rows == (frozenset({0}),) + assert extraction.detector_record_indices == (frozenset({0, 1}),) + + +def test_stabilizer_code_from_stim_mpp_can_select_all_mpp_layers() -> None: + extraction = stabilizer_code_from_stim_text( + """ + MPP X0 + TICK + MPP Z0 + DETECTOR rec[-1] rec[-2] + """, + mpp_layer=None, + ) + + assert extraction.supports == (((0, "X"),), ((0, "Z"),)) + assert extraction.detector_rows == (frozenset({0, 1}),) + assert extraction.detector_record_indices == (frozenset({0, 1}),) diff --git a/uv.lock b/uv.lock index 0b24c5e6..b6b6dd85 100644 --- a/uv.lock +++ b/uv.lock @@ -712,6 +712,8 @@ dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "ortools" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "typing-extensions" }, ] @@ -736,6 +738,28 @@ doc = [ pyzx = [ { name = "pyzx" }, ] +stim = [ + { name = "stim" }, +] + +[package.dev-dependencies] +dev = [ + { name = "mypy" }, + { name = "pre-commit" }, + { name = "pyright" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ruff" }, + { name = "scipy-stubs", version = "1.15.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy-stubs", version = "1.17.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "types-networkx" }, +] +pyzx = [ + { name = "pyzx" }, +] +stim = [ + { name = "stim" }, +] [package.metadata] requires-dist = [ @@ -747,17 +771,33 @@ requires-dist = [ { name = "numpy", specifier = ">=1.22,<3" }, { name = "ortools", specifier = ">=9,<10" }, { name = "pre-commit", marker = "extra == 'dev'", specifier = "==4.6.0" }, - { name = "pyright", marker = "extra == 'dev'", specifier = "==1.1.410" }, - { name = "pytest", marker = "extra == 'dev'", specifier = "==9.0.3" }, + { name = "pyright", marker = "extra == 'dev'", specifier = "==1.1.411" }, + { name = "pytest", marker = "extra == 'dev'", specifier = "==9.1.1" }, { name = "pytest-cov", marker = "extra == 'dev'", specifier = "==7.1.0" }, { name = "pyzx", marker = "extra == 'pyzx'", specifier = ">=0.10" }, - { name = "ruff", marker = "extra == 'dev'", specifier = "==0.15.17" }, + { name = "ruff", marker = "extra == 'dev'", specifier = "==0.15.20" }, + { name = "scipy", specifier = ">=1.15.3" }, { name = "sphinx", marker = "extra == 'doc'", specifier = ">=8.1.3" }, { name = "sphinx-gallery", marker = "extra == 'doc'", specifier = ">=0.17.0" }, - { name = "types-networkx", marker = "extra == 'dev'", specifier = "==3.6.1.20260518" }, + { name = "stim", marker = "extra == 'stim'", specifier = ">=1.15" }, + { name = "types-networkx", marker = "extra == 'dev'", specifier = "==3.6.1.20260624" }, { name = "typing-extensions" }, ] -provides-extras = ["dev", "doc", "pyzx"] +provides-extras = ["dev", "doc", "pyzx", "stim"] + +[package.metadata.requires-dev] +dev = [ + { name = "mypy", specifier = "==2.1.0" }, + { name = "pre-commit", specifier = "==4.6.0" }, + { name = "pyright", specifier = "==1.1.411" }, + { name = "pytest", specifier = "==9.1.1" }, + { name = "pytest-cov", specifier = "==7.1.0" }, + { name = "ruff", specifier = "==0.15.20" }, + { name = "scipy-stubs", specifier = ">=1.15.3.0" }, + { name = "types-networkx", specifier = "==3.6.1.20260624" }, +] +pyzx = [{ name = "pyzx", specifier = ">=0.10" }] +stim = [{ name = "stim", specifier = ">=1.15" }] [[package]] name = "identify" @@ -1579,6 +1619,62 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, ] +[[package]] +name = "numpy-typing-compat" +version = "20251206.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/5f/29fd5f29b0a5d96e2def96ecba3112fc330ecd16e8c97c2b332563c5e201/numpy_typing_compat-20251206.2.4.tar.gz", hash = "sha256:59882d23aaff054a2536da80564012cdce33487657be4d79c5925bb8705fcabc", size = 5011, upload-time = "2025-12-06T20:02:04.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/7c/5c2892e6bc0628a2ccf4e938e1e2db22794657ccb374672d66e20d73839e/numpy_typing_compat-20251206.2.4-py3-none-any.whl", hash = "sha256:a82e723bd20efaa4cf2886709d4264c144f1f2b609bda83d1545113b7e47a5b5", size = 6300, upload-time = "2025-12-06T20:01:57.578Z" }, +] + +[[package]] +name = "optype" +version = "0.9.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/88/3c/9d59b0167458b839273ad0c4fc5f62f787058d8f5aed7f71294963a99471/optype-0.9.3.tar.gz", hash = "sha256:5f09d74127d316053b26971ce441a4df01f3a01943601d3712dd6f34cdfbaf48", size = 96143, upload-time = "2025-03-31T17:00:08.392Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/d8/ac50e2982bdc2d3595dc2bfe3c7e5a0574b5e407ad82d70b5f3707009671/optype-0.9.3-py3-none-any.whl", hash = "sha256:2935c033265938d66cc4198b0aca865572e635094e60e6e79522852f029d9e8d", size = 84357, upload-time = "2025-03-31T17:00:06.464Z" }, +] + +[[package]] +name = "optype" +version = "0.17.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.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' 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'", +] +dependencies = [ + { name = "typing-extensions", marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/86/e6f1f6f3487492dfcf3b7a2d4e2534d27af6ac05b364b276706906c34865/optype-0.17.1.tar.gz", hash = "sha256:07bfa32b795dea28fba8605a6288d36370d072f25183fb9c29b5a90f4b6f5638", size = 53572, upload-time = "2026-05-17T22:13:28.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/d4/c6a2b043e33f0dd012486dcebe0593585588d400175d22aad42049c88321/optype-0.17.1-py3-none-any.whl", hash = "sha256:82f2508ca31cb21e53a41648482d890fe1f5c6cb153720551af41161555adaf1", size = 65954, upload-time = "2026-05-17T22:13:27.549Z" }, +] + +[package.optional-dependencies] +numpy = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy-typing-compat", marker = "python_full_version >= '3.11'" }, +] + [[package]] name = "ortools" version = "9.15.6755" @@ -2033,20 +2129,20 @@ wheels = [ [[package]] name = "pyright" -version = "1.1.410" +version = "1.1.411" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/53/e4d8ea1391bd4355231be6f91bf239479aa0014260ed3fb5526eeb12a1f2/pyright-1.1.410.tar.gz", hash = "sha256:07a073b8ba6749826773c1269773efa11b93440d9a6aa60419d9a3172d6dc488", size = 4062013, upload-time = "2026-06-01T17:35:48.894Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/ab/265f7dc69d28113ebba19092e57b075f41543b2ed048429c5f56e2b88eac/pyright-1.1.411.tar.gz", hash = "sha256:d885a0551f2e763b089a02702174e7f4ba77548cddabc972ab86d1f7f1b0f998", size = 4112861, upload-time = "2026-06-25T02:14:06.37Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/33/288b5868fa00846dacf249633719d747893e54aebd196b9968ac1878a5d3/pyright-1.1.410-py3-none-any.whl", hash = "sha256:5e961bed37cacf96b3f7cd7b1da39b350a9239aa2e69138d0e88f728cfaf296c", size = 6082448, upload-time = "2026-06-01T17:35:46.387Z" }, + { url = "https://files.pythonhosted.org/packages/0a/49/385be530a6a5b78d1cbcd5c2e38debc8959a2fc6bdb716f4e581002979fc/pyright-1.1.411-py3-none-any.whl", hash = "sha256:dc7c72a8e2700c55baa127554040e067041ea53ccfd50bf96308cc4291c7d5d9", size = 6181526, upload-time = "2026-06-25T02:14:04.691Z" }, ] [[package]] name = "pytest" -version = "9.0.3" +version = "9.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -2057,9 +2153,9 @@ dependencies = [ { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] [[package]] @@ -2218,27 +2314,206 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.17" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/a9/3abdf488f1bf3d24c699415e454ed554a6350d5d89ce183be1ee0a3361ac/ruff-0.15.17.tar.gz", hash = "sha256:2ec446937fd16c8c4de2674a209cc5af64d9c6f17d21fbf1151054fa0bcf5219", size = 4743346, upload-time = "2026-06-11T17:54:47.663Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/4d/e11259f5da07cb6afb2d074c31bf09da9671993f7329d4f15d2fdc458301/ruff-0.15.17-py3-none-linux_armv6l.whl", hash = "sha256:d9feddb927fc68bd295f5eebc587a7e42cfaf9b65f60ca4a2386febff575da8f", size = 10856677, upload-time = "2026-06-11T17:54:49.533Z" }, - { url = "https://files.pythonhosted.org/packages/29/3e/772d679e1a0dc058e58875bd2c0cb713a0530877b4a76fee3c7966df0d49/ruff-0.15.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:25805a226d741c47d274a35ad5c10a7dde175fcddfa511d7cf3da0a21eb3eab7", size = 11223443, upload-time = "2026-06-11T17:55:00.573Z" }, - { url = "https://files.pythonhosted.org/packages/68/58/bd41f7688b2fd5623012605130ed70e60aa7f2244baa3d5066bdd61530c8/ruff-0.15.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f6ad73b14c2d18a3bf8ad7cb6974294d7f613a7898604826058e6ac64918ef4d", size = 10566458, upload-time = "2026-06-11T17:55:07.52Z" }, - { url = "https://files.pythonhosted.org/packages/d8/5b/733371013fcf1ec339e477ece6ab42bfe10bdd9bba8ee88a9516aa56bfc0/ruff-0.15.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ba0c1e4f95bcb3869d0d30cbd5917071ef2e28665abfec970cdab0492c713ed", size = 10914483, upload-time = "2026-06-11T17:55:05.501Z" }, - { url = "https://files.pythonhosted.org/packages/bd/cc/6f24251cc0252f7239391ccb85833f320efad14ebe5b443943f37ced6332/ruff-0.15.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81647960f10bff57d2e51cadd0c3950fe598400c852863a038720ef5b8cca91e", size = 10647497, upload-time = "2026-06-11T17:54:57.733Z" }, - { url = "https://files.pythonhosted.org/packages/68/dd/0d10c17ce1a1624d6fc3156309c3f834fdb5dfaad026ec90c85684f3990e/ruff-0.15.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e01a84ddbc8c16c23055ba3924476850f1bbc1917cebbb9376665a63e74260d", size = 11416967, upload-time = "2026-06-11T17:54:51.461Z" }, - { url = "https://files.pythonhosted.org/packages/2f/91/556bfb156f6144f355e831c23db00b2fc4120f86b3ce81cc5f7fd2df51f3/ruff-0.15.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fe9f653152f8f294f9f7e03bf3a453d8b4a27f7a59c78c8666167f2b17b96c", size = 12335770, upload-time = "2026-06-11T17:54:45.793Z" }, - { url = "https://files.pythonhosted.org/packages/88/82/8b5999aa13355e926f06d9f42a32dcca862f623bf0363785ff89d607dffd/ruff-0.15.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c0fe88a7676e7a05b73174d4d4a59cb2ac21ff8263583f87a81a6018475a978", size = 11575441, upload-time = "2026-06-11T17:54:32.661Z" }, - { url = "https://files.pythonhosted.org/packages/11/93/f10377bb04109ca0e8cbc483ff1982c54b6d418210041776f93e8cdc7fa9/ruff-0.15.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecfc3c7878fff94633ab0348524e093f9ce3243080416dd7d14f8ba400174719", size = 11557614, upload-time = "2026-06-11T17:54:34.698Z" }, - { url = "https://files.pythonhosted.org/packages/c7/a6/eeeae7f7d5493df41649ab3db92f086b2d0a30199e4efdf8e3dd7a033f24/ruff-0.15.17-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b8461180b22420b1bdc289909410930761629fddf2a5aaf60fae1ab26cedc4c4", size = 11544450, upload-time = "2026-06-11T17:54:39.042Z" }, - { url = "https://files.pythonhosted.org/packages/32/88/5991ce565129a24dd4a00db1254b3b5db2e53018cbe4018ea5a89738e727/ruff-0.15.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6eccbe50a038b503e7140b441aa9c7fc8c1f36edf23ebef9f4165c2f28f568b7", size = 10892524, upload-time = "2026-06-11T17:55:09.432Z" }, - { url = "https://files.pythonhosted.org/packages/f5/1d/0fdd248313425f55223968af04b0a42125466a8d88d21c1d99c6af0a51e8/ruff-0.15.17-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:382fc0521025f5a8ad447d8bdd523545d0d7646adb718eb1c2dac5065ec27c0f", size = 10659573, upload-time = "2026-06-11T17:54:36.824Z" }, - { url = "https://files.pythonhosted.org/packages/9e/0e/072e8260deb9461062ce9311ced27a8e541229a6ffd483013dd37661e43e/ruff-0.15.17-py3-none-musllinux_1_2_i686.whl", hash = "sha256:456d41fcd1b2777ad63f09a6e7121d43f7b688bbc76a800c10f7f8fb1f912c3f", size = 11127818, upload-time = "2026-06-11T17:55:03.124Z" }, - { url = "https://files.pythonhosted.org/packages/ab/b4/55060a34163121498014696b5f656db5b8c6963768f227dbf0d76b311073/ruff-0.15.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b1a04bcc94ae6194e9db05d16ad31f298a7194bfbcb08258bbe589cee1d587b8", size = 11655901, upload-time = "2026-06-11T17:54:53.562Z" }, - { url = "https://files.pythonhosted.org/packages/49/71/9b29d6b87cef468d697f43c6a91e3fae4a80185779d7d5a4ef27d173439f/ruff-0.15.17-py3-none-win32.whl", hash = "sha256:596065960ab1ff593f744220c9fe6580eda00a95003cffa9f4048bb5b1bf0392", size = 10925574, upload-time = "2026-06-11T17:54:55.723Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b2/8fc77f3723228836fa5d12497eb71c808f83782e10d058d2b15cfa14640b/ruff-0.15.17-py3-none-win_amd64.whl", hash = "sha256:6769e5fa1710b179b92e0bfa5a51735b35baea9013dadb06d5f44cbcf9547084", size = 12058788, upload-time = "2026-06-11T17:54:41.042Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c7/c53e8dbff9c9dc4b7928773421ae294a5d28fcb8dcda1a089579d3a7e510/ruff-0.15.17-py3-none-win_arm64.whl", hash = "sha256:f3be1fbb34bcdfd146240d8fb92a709d4c2c8191348580a3c044ec60fa0b4456", size = 11355275, upload-time = "2026-06-11T17:54:43.635Z" }, +version = "0.15.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" }, + { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" }, + { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" }, + { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" }, + { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" }, + { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" }, + { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" }, + { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" }, + { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" }, + { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" }, + { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" }, + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, + { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" }, + { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" }, + { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" }, + { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" }, + { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" }, + { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" }, + { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" }, + { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.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.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' 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'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, + { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" }, + { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, +] + +[[package]] +name = "scipy-stubs" +version = "1.15.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "optype", version = "0.9.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/35c43bd7d412add4adcd68475702571b2489b50c40b6564f808b2355e452/scipy_stubs-1.15.3.0.tar.gz", hash = "sha256:e8f76c9887461cf9424c1e2ad78ea5dac71dd4cbb383dc85f91adfe8f74d1e17", size = 275699, upload-time = "2025-05-08T16:58:35.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/42/cd8dc81f8060de1f14960885ad5b2d2651f41de8b93d09f3f919d6567a5a/scipy_stubs-1.15.3.0-py3-none-any.whl", hash = "sha256:a251254cf4fd6e7fb87c55c1feee92d32ddbc1f542ecdf6a0159cdb81c2fb62d", size = 459062, upload-time = "2025-05-08T16:58:33.356Z" }, +] + +[[package]] +name = "scipy-stubs" +version = "1.17.1.5" +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.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' 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'", +] +dependencies = [ + { name = "optype", version = "0.17.1", source = { registry = "https://pypi.org/simple" }, extra = ["numpy"], marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/30/7a2e621918d1317ab972f797161131f2635648ad5d92baf0695dd009e4f9/scipy_stubs-1.17.1.5.tar.gz", hash = "sha256:284b1dd1dd46107a614971d170030d310cd88b2ac6b483f85285ee0ff87720bd", size = 399933, upload-time = "2026-05-25T21:34:33.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/26/d4bc2ba3427a623f79a6c10c8f427c7a55b56eb8b3eddc369319d97f741b/scipy_stubs-1.17.1.5-py3-none-any.whl", hash = "sha256:58ebf054a86c000c72e8982e121c4ead0d3d9ba7a6c38aa5fa71b07f96a427fd", size = 607388, upload-time = "2026-05-25T21:34:32.073Z" }, ] [[package]] @@ -2465,6 +2740,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, ] +[[package]] +name = "stim" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/bd/c0f5d6241cdb806400a63e0c7febe22982ddf1c8761142bb0ea6b51d3f50/stim-1.16.0.tar.gz", hash = "sha256:9092a996429dcf616d8d4ca01fec886b7b310caf9425a39fcfe499005636bd52", size = 882497, upload-time = "2026-05-22T05:41:56.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/c4/b2cb8ed667da891b4835826eba3cb582c577ccc6980f15fd0e84d5e6491e/stim-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d467b5599de7e171651a1ff7fac353dd64d78df75d9c63e7d870d576eb0fd64b", size = 2043460, upload-time = "2026-05-22T05:41:19.371Z" }, + { url = "https://files.pythonhosted.org/packages/71/cc/f7ebbe95361ce7b5c5da47ce024543846d6aca9fbbedd6833aeb33dccf15/stim-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84b777678cc12e808b3d6747d57e07eed951f8ec18047a936dfa25dbc950a2e2", size = 1889327, upload-time = "2026-05-22T05:41:21.869Z" }, + { url = "https://files.pythonhosted.org/packages/c5/30/90d9ed81b10c4850e108b81346c2c6e2784126945342d591272ed090e027/stim-1.16.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41c02d52af847e0ab886c35751e8f7e17e2e0d055e390ece93a50a12dcf87e56", size = 5235305, upload-time = "2026-05-22T05:41:23.6Z" }, + { url = "https://files.pythonhosted.org/packages/4c/43/4e5036fdc3c8bdfb4d774a4fff0cb8a0f44609ae18aa858ae37f61649cd1/stim-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:609666b77197d45705e1579a9840123a53cebe614e8bf1d496c0df24f797e653", size = 2753255, upload-time = "2026-05-22T05:41:25.49Z" }, + { url = "https://files.pythonhosted.org/packages/3f/1d/3619629c01407eef4042c22299afaf8d3f5fc3b0b26c98ffb4cd86f7cc1e/stim-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d44f858f3d5d1107e674886facbb011ed85438f0888278d6aa3c1f42d9e5a5f", size = 2046328, upload-time = "2026-05-22T05:41:27.345Z" }, + { url = "https://files.pythonhosted.org/packages/be/37/3742215d5fd4e090ce7ae964e43d978d1ac9865b5865530a6d5f1063a124/stim-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1890bda08adfd6daa875d1d5e41039c5ae8c58faabc7cd1813f8d8de9f1536a2", size = 1892693, upload-time = "2026-05-22T05:41:28.958Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d8/a84d59b49395e4f8e080991915929c69e1e6a40c411a9c0998904dbf8bef/stim-1.16.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd2d28093818f981f8dedb2be160b62d818279b81748d72dc4fca6cdd160291d", size = 5248218, upload-time = "2026-05-22T05:41:31.191Z" }, + { url = "https://files.pythonhosted.org/packages/b8/87/a845b9d17ab5e352eee30169a58bd8213e85ff47c6340daff2f8a25ed28e/stim-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:b367f90826e796417932ea46c0e53d80e3c5bebac0c6633c0aa07a8fcaa4298b", size = 2755461, upload-time = "2026-05-22T05:41:33.426Z" }, + { url = "https://files.pythonhosted.org/packages/20/d9/8b70c39860e67e233178a9ef595c15a7ec718f0b88bc424ae2c6ad8f97da/stim-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3e2883216fb4f57411531c99dfa1838c0d8201c619ccd01b197b9afd54d326cf", size = 2067905, upload-time = "2026-05-22T05:41:34.9Z" }, + { url = "https://files.pythonhosted.org/packages/51/e3/45a8c0889f4629027cbdd8ed8e9f5824b5bd8b12b0f5d24be27ec0c2a3dc/stim-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b8580dfbe5ff27594dd15a83b24262630b82cf089d1463d29dbee90ffbc6898b", size = 1902644, upload-time = "2026-05-22T05:41:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/e4/71/db565f5bd0f273414e90d85551eed1c75f7b689847bd605a7cf0c4c0386d/stim-1.16.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:883b24c3c95fa69818e931e8b60daf4fc8195d159d321e7548d8706dbac16558", size = 5307850, upload-time = "2026-05-22T05:41:37.69Z" }, + { url = "https://files.pythonhosted.org/packages/bb/65/0ddfa6f76c4a137aefeee4d51a65945e0ec77cc2221322f13a5768f4c488/stim-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:85b9c544c65377a42c6b5c9a57616a5ed5580e1cabb286a604800a045c7a12fb", size = 2760794, upload-time = "2026-05-22T05:41:39.731Z" }, + { url = "https://files.pythonhosted.org/packages/e4/cf/aeb85a75eca23907ba32c09c95340489a60ff608a9a18ebd26b9ae2e2323/stim-1.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1822adb4920f4616b253792fe1893ad07a9bea8cbc45b2f4cdf7c75a8083cd00", size = 2068071, upload-time = "2026-05-22T05:41:42.237Z" }, + { url = "https://files.pythonhosted.org/packages/21/40/9499fb076b819b479bffad35afd34e84a93c86f117fb5431dab211adff4f/stim-1.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:66dbc6532169df84d127ceede353b15445e845a94aa008c5d0a4062b51cc2b02", size = 1902498, upload-time = "2026-05-22T05:41:43.564Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/6a9bca80b6ffe0fc4a3b0375ac18ff9fb1e815d152691332e55d1a9e5fa2/stim-1.16.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22a009bad1e7816aeb70bdeafb6789e4840710da070f69cde91e2ac6cf1e800", size = 5307953, upload-time = "2026-05-22T05:41:45.32Z" }, + { url = "https://files.pythonhosted.org/packages/d7/15/db7f4b80a0903e0962f5d824ca8cb5a962836399bd45fda59961782ebb20/stim-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:55231457b5897b0ef798059aae01d9e8247fc8b2089e8a27f9f4bb13b0342af2", size = 2760785, upload-time = "2026-05-22T05:41:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f8/4266593404f455c676bfe95785f865d77e98578fc4a35af9835653a65598/stim-1.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1e78fe10ab4c28af9ceb57ac8ed895f3efcde9607d851ef931c53d1f6f80bc8e", size = 2062900, upload-time = "2026-05-22T05:41:48.975Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d1/a898d8e587a8718daabaf886f2d0c22c126a691886765096a0776b5b463c/stim-1.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1fa60a13ef921d6e6aea420e2fe7551d71a4a07b513d86da89e39259dd3739f9", size = 1898162, upload-time = "2026-05-22T05:41:50.973Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b2/8535802f6eb5230678b067cde35b81fc612c612698f192b31164c6a19b9f/stim-1.16.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f94040cbeeff786e7fb8860249e54d9ba3f777b5b098d21f1eccd072d3a3c32a", size = 5311829, upload-time = "2026-05-22T05:41:53.291Z" }, + { url = "https://files.pythonhosted.org/packages/f5/85/be0fe295a5c5426aec0a2f8497360d9db8083d62c04de81bf744d29a131e/stim-1.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:ed3aa70f82690946ba6f70abb9dad448d49ab9499fa5161de5f286da87c30ab1", size = 2825951, upload-time = "2026-05-22T05:41:55.018Z" }, +] + [[package]] name = "tomli" version = "2.4.1" @@ -2542,15 +2849,15 @@ wheels = [ [[package]] name = "types-networkx" -version = "3.6.1.20260518" +version = "3.6.1.20260624" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f9/9f/e6fe2543aecbfe4d67fe86f280afae1e6d532c49755c5fb6c1f05f8af91e/types_networkx-3.6.1.20260518.tar.gz", hash = "sha256:af3ab153815b366c3695a0015cc75aece9b3eb0e5df56bb60ffee0b7cec8f630", size = 73977, upload-time = "2026-05-18T06:06:05.573Z" } +sdist = { url = "https://files.pythonhosted.org/packages/44/f9/975c2bb35407c241fb788e9c75b2e2642af43a6c9cf81056a119dd98e59c/types_networkx-3.6.1.20260624.tar.gz", hash = "sha256:f031a9ee0bd27de4ab6d3e35a10f3492a4befc7b8c348607892b0ccb1b415d4d", size = 74060, upload-time = "2026-06-24T05:56:25.279Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/4f/422a91e9fa008b804746fc2ce4da5bcb7eca4bfb45c31e8282fbb3664601/types_networkx-3.6.1.20260518-py3-none-any.whl", hash = "sha256:574304deea33828fb32cdca757af84f79b67bd80164aa1725c52a95d4a9347e6", size = 162454, upload-time = "2026-05-18T06:06:04.417Z" }, + { url = "https://files.pythonhosted.org/packages/14/d4/67136d3323dd8a8ecc8124531f737dee97819811026da20a2a72949f462a/types_networkx-3.6.1.20260624-py3-none-any.whl", hash = "sha256:071734f88733afaaa06cbb62eb8c603626f4f9aa18424771acbdd3a638828d9a", size = 162457, upload-time = "2026-06-24T05:56:24.063Z" }, ] [[package]]