diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 16b92ce8..586541bf 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -5,7 +5,7 @@ Before submitting, please check the following: - Format added code by `uv run ruff` - Type checking by `uv run mypy` and `uv run pyright` - Make sure the checks (github actions) pass. -- Check that the docs compile without errors (run `uv run sphinx-build -W docs/source docs/build` after `uv sync --extra doc`.) +- Check that the docs compile without errors (run `uv run sphinx-build -W docs/source docs/build` after `uv sync --extra doc --extra stim`.) Then, please fill in below: diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ab693bc2..ec647b9a 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -30,7 +30,7 @@ jobs: enable-cache: true - name: Install dependencies - run: uv sync --locked --python 3.13 --extra dev --extra pyzx + run: uv sync --locked --python 3.13 --extra dev --extra pyzx --extra stim - name: Run pytest with coverage run: uv run --no-sync pytest --cov=graphqomb --cov-report=term-missing --cov-report=xml --cov-branch diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 0dcac0bd..83d8ed4a 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -22,7 +22,7 @@ jobs: uv-version: "0.11.16" enable-cache: true - - run: uv sync --locked --python 3.13 --extra doc + - run: uv sync --locked --python 3.13 --extra doc --extra stim - name: Make docs run: uv run --no-sync sphinx-build -W docs/source docs/build diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 683df7d3..eb9455ef 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -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 }} --extra dev --extra stim - run: uv run --no-sync pytest -m "not pyzx" @@ -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 --extra dev --extra pyzx --extra stim - run: uv run --no-sync pytest -m pyzx diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index c00a4dfe..7ca8f48e 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -26,6 +26,6 @@ jobs: uv-version: "0.11.16" enable-cache: true - - run: uv sync --locked --python 3.10 --extra dev + - run: uv sync --locked --python 3.10 --extra dev --extra stim - run: uv run --no-sync ${{ matrix.tool }} diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 987cd8ad..eb55becf 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -21,3 +21,4 @@ python: command: sync extras: - doc + - stim diff --git a/CHANGELOG.md b/CHANGELOG.md index 77841982..44cabffa 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 unsigned Stim `MPP` layers, including sparse Stim qubit id mapping, coordinate import, multi-layer selection, detector/logical-observable import, and the optional `graphqomb[stim]` extra. Signed products using inverted Pauli targets are rejected because stabilizer signs are not retained. + ### Changed - **Development Tooling**: Use uv as the default dependency manager for local development, CI, documentation builds, and publishing workflows. diff --git a/README.md b/README.md index 73080d63..dce030ce 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,12 @@ Install optional PyZX integration: uv add "graphqomb[pyzx]" ``` +Install optional Stim integration: + +```bash +uv add "graphqomb[stim]" +``` + ### From Source ```bash @@ -107,6 +113,8 @@ If you already have a graph-state design and explicit feedforward maps, you can - **Architecture overview**: https://graphqomb.readthedocs.io/en/latest/architecture.html - **Example gallery**: https://graphqomb.readthedocs.io/en/latest/gallery/index.html - **API reference**: https://graphqomb.readthedocs.io/en/latest/references.html +- **QEC graph-state builder reference**: https://graphqomb.readthedocs.io/en/latest/qec.html +- **Stim MPP import reference**: https://graphqomb.readthedocs.io/en/latest/stim_mpp.html - **Stim compiler reference**: https://graphqomb.readthedocs.io/en/latest/stim_compiler.html ## Current Scope diff --git a/docs/source/qec.rst b/docs/source/qec.rst new file mode 100644 index 00000000..e0c33f3d --- /dev/null +++ b/docs/source/qec.rst @@ -0,0 +1,14 @@ +QEC graph-state builder +======================= + +``graphqomb.qec.qeccode`` provides the stabilizer-code representation and the +Type I and Type II graph-state builders used by the QEC helper workflow. + +Coordinate tuples are retained without restricting their dimensionality. The +graph-state builder uses the first two qubit-coordinate components for the data +plane and the first three explicitly supplied stabilizer-coordinate components +for ancilla placement. + +.. automodule:: graphqomb.qec.qeccode + :members: + :show-inheritance: diff --git a/docs/source/references.rst b/docs/source/references.rst index 05571380..1d252c18 100644 --- a/docs/source/references.rst +++ b/docs/source/references.rst @@ -21,6 +21,8 @@ Module reference pauli_frame qompiler scheduler + qec + stim_mpp stim_compiler noise_model visualizer diff --git a/docs/source/stim_mpp.rst b/docs/source/stim_mpp.rst new file mode 100644 index 00000000..e10c538e --- /dev/null +++ b/docs/source/stim_mpp.rst @@ -0,0 +1,39 @@ +Stim MPP import +=============== + +Install the optional Stim integration before importing this module: + +.. code-block:: console + + uv add "graphqomb[stim]" + +The importer converts unsigned Stim ``MPP`` products into a +:class:`graphqomb.qec.qeccode.StabilizerCode`. It does not retain stabilizer +signs. Consequently, signed products written with an inverted Pauli target, +such as ``MPP !X0*Z1``, raise :class:`ValueError` instead of silently losing +the sign. + +The result also provides dense-column mappings for sparse Stim qubit ids and +can map selected ``DETECTOR`` and ``OBSERVABLE_INCLUDE`` records to graph-state +ancilla nodes. + +Set ``coord_dims`` to the number of leading ``QUBIT_COORDS`` components to +retain. The importer does not restrict this value to two or three dimensions; +each referenced coordinate declaration must contain at least the requested +number of components. + +.. code-block:: python + + from graphqomb.qec.qeccode import build_graph_state + from graphqomb.qec.stim_mpp import stabilizer_code_from_stim_text + + extraction = stabilizer_code_from_stim_text("MPP X0*Z2") + graph_result = build_graph_state(extraction.code) + detector_groups = extraction.detector_groups(graph_result.ancilla_nodes) + +API reference +------------- + +.. automodule:: graphqomb.qec.stim_mpp + :members: + :show-inheritance: 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..c8c5be86 --- /dev/null +++ b/graphqomb/qec/qeccode.py @@ -0,0 +1,357 @@ +"""QEC Code object.""" + +from __future__ import annotations + +from enum import Enum, auto +from typing import TYPE_CHECKING, Any, 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 +Coordinate = tuple[float, ...] + + +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[Any, tuple[int, int]], + *, + stabilizer_coords: Mapping[int, Coordinate] | None = None, + qubit_coords: Mapping[int, Coordinate] | None = None, + ) -> None: + 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`` is the constructed graph state with data and ancilla nodes, + edges, coordinates, and measurement bases. ``data_nodes`` maps each + ``(physical_qubit, data_layer)`` pair to its graph node id. + ``ancilla_nodes`` maps each stabilizer row index to its ancilla graph node + id. + """ + + 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): + layers: tuple[int, ...] + coordinate_zs: tuple[float, ...] + 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 _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..153e6b7d --- /dev/null +++ b/graphqomb/qec/stim_mpp.py @@ -0,0 +1,435 @@ +"""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, Any + +import numpy as np +import stim +from scipy.sparse import csr_array, lil_array + +from graphqomb.qec.qeccode import Coordinate, 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 + + +@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=dict) + + 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. + + Signed MPP products are not supported. A Stim target inversion such as + ``!X0`` raises ``ValueError`` because stabilizer signs are not retained. + + 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. + Signed MPP products are not supported because ``StabilizerCode`` does not + retain stabilizer signs. A Stim target inversion such as ``!X0`` is + therefore rejected instead of being silently discarded. + + Returns + ------- + `StimMppExtraction` + Extracted stabilizer code, qubit mapping, and Pauli supports. + + Raises + ------ + ValueError + If the requested MPP layer or coordinate format is invalid, or if an + MPP product is signed using an inverted Pauli target. + """ + if mpp_layer is not None and mpp_layer < 0: + msg = "mpp_layer must be non-negative." + 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, Coordinate], +) -> tuple[csr_array[Any, tuple[int, int]], dict[int, int], dict[int, int], dict[int, Coordinate]]: + 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=np.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} + stabilizer_matrix = csr_array(matrix, shape=(len(supports), 2 * num_qubits)) + return stabilizer_matrix, stim_to_column, column_to_stim, qubit_coords + + +def _extract_qubit_coordinates( + circuit: stim.Circuit, + *, + coord_dims: int, +) -> dict[int, Coordinate]: + coordinates: dict[int, Coordinate] = {} + 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 + + if target.is_inverted_result_target: + msg = "Signed MPP products are not supported; inverted Pauli targets cannot be imported." + raise ValueError(msg) + 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 cb83f456..c04e0ccc 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", ] @@ -57,6 +58,7 @@ dev = [ "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", ] doc = [ @@ -68,6 +70,9 @@ doc = [ pyzx = [ "pyzx>=0.10", ] +stim = [ + "stim>=1.15", +] [project.urls] Homepage = "https://github.com/TeamGraphix/graphqomb" diff --git a/tests/test_qec.py b/tests/test_qec.py new file mode 100644 index 00000000..06b42905 --- /dev/null +++ b/tests/test_qec.py @@ -0,0 +1,204 @@ +"""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_preserves_higher_dimensional_coordinates() -> None: + code = StabilizerCode( + _matrix([[1, 0]]), + stabilizer_coords={0: (1.0, 2.0, 3.0, 4.0)}, + qubit_coords={0: (5.0, 6.0, 7.0, 8.0)}, + ) + + assert code.stabilizer_coord == {0: (1.0, 2.0, 3.0, 4.0)} + assert code.qubit_coord == {0: (5.0, 6.0, 7.0, 8.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..68c65ab4 --- /dev/null +++ b/tests/test_stim_mpp.py @@ -0,0 +1,193 @@ +"""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_preserves_higher_dimensional_coordinates() -> None: + extraction = stabilizer_code_from_stim_text( + """ + QUBIT_COORDS(10, 20, 30, 40) 0 + MPP X0 + """, + coord_dims=4, + ) + + assert extraction.code.qubit_coord == {0: (10.0, 20.0, 30.0, 40.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_rejects_signed_product() -> None: + with pytest.raises(ValueError, match="Signed MPP products are not supported"): + stabilizer_code_from_stim_text("MPP !X0*Y1\n") + + +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 e9443597..c73283cf 100644 --- a/uv.lock +++ b/uv.lock @@ -713,6 +713,9 @@ 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 = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "typing-extensions" }, ] @@ -724,6 +727,9 @@ dev = [ { 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 = "scipy-stubs", version = "1.18.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "types-networkx" }, ] doc = [ @@ -737,6 +743,9 @@ doc = [ pyzx = [ { name = "pyzx" }, ] +stim = [ + { name = "stim" }, +] [package.metadata] requires-dist = [ @@ -753,12 +762,15 @@ requires-dist = [ { 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.20" }, + { name = "scipy", specifier = ">=1.15.3" }, + { name = "scipy-stubs", marker = "extra == 'dev'", specifier = ">=1.15.3.0" }, { name = "sphinx", marker = "extra == 'doc'", specifier = ">=8.1.3" }, { name = "sphinx-gallery", marker = "extra == 'doc'", specifier = ">=0.17.0" }, + { 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]] name = "identify" @@ -1583,6 +1595,107 @@ 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" } +resolution-markers = [ + "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/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 = "numpy-typing-compat" +version = "20260602.2.4" +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'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/73/e331473d3db84a8e8883ac07bfd63a8ce9eb7196acbb672bda1f5b8d3294/numpy_typing_compat-20260602.2.4.tar.gz", hash = "sha256:e4eb661f312a7ad5805677967d5879e04fd7b97627fe910121ce7b1f43aa748c", size = 4603, upload-time = "2026-06-02T15:52:38.572Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/a8/94811eedac4cef5ef7df4b24e06715fa371724782e86a4573f5d172c8473/numpy_typing_compat-20260602.2.4-py3-none-any.whl", hash = "sha256:78d33917d5f6921f8d1c549db347a5b8d9768853e36796b08208c81f5b620977", size = 5879, upload-time = "2026-06-02T15:52:33.214Z" }, +] + +[[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.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.*'" }, +] +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", version = "20251206.2.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, +] + +[[package]] +name = "optype" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.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'", +] +dependencies = [ + { name = "typing-extensions", marker = "python_full_version == '3.12.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/51/51dc9b1009e020f44703933d4d1ee3429c647c026ce7806b37ee2b257998/optype-0.18.0.tar.gz", hash = "sha256:ea10dee61b15ca299ed0d97025d362585c4dfc5481159bb999a1d0d414bbcb04", size = 59967, upload-time = "2026-06-07T22:13:17.534Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/91/2b064a117cb2593bc3eb04ee994134ca2a6bf2162c92961769947e3258cc/optype-0.18.0-py3-none-any.whl", hash = "sha256:91822ed8516e7a4f225ba53d30f291776c35f31553fb952d7cda98286679c5a6", size = 73410, upload-time = "2026-06-07T22:13:16.324Z" }, +] + +[package.optional-dependencies] +numpy = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy-typing-compat", version = "20260602.2.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] + [[package]] name = "ortools" version = "9.15.6755" @@ -2245,6 +2358,252 @@ wheels = [ { 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.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" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.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'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/19/ca10ead60b0acc80b2b833c2c4a4f2ff753d0f58b811f70d911c7e94a25c/scipy-1.18.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7bd21faaf5a1a3b2eff922d02db5f191b99a6518db9078a8fb23169f6d22259a", size = 31056519, upload-time = "2026-06-19T14:59:45.203Z" }, + { url = "https://files.pythonhosted.org/packages/96/72/1e6442a00cd2924d361aa1b642ab6373ec35c6fabf311a760be9f76e0f13/scipy-1.18.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:265915e79107de9f946b855e50d7470d5893ec3f54b342e1aa6201cbdcd8bb6b", size = 28681889, upload-time = "2026-06-19T14:59:48.103Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2d/11dd93d21e147a73ba22bd75c0b9208d3a2e0ec76d53170ce7d9029b1015/scipy-1.18.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9ab7b758be6940954a713ee466e2043e9f6e2ed965c1fce5c91039f4be3d90a9", size = 20423580, upload-time = "2026-06-19T14:59:50.665Z" }, + { url = "https://files.pythonhosted.org/packages/9c/01/93552f75e0d2a7dd115a45e59209c51e8d514daff02fc887d2623be06fe1/scipy-1.18.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:97b6cddaaee0a779ef6b5ca83c9604b27cc16b2b8fc22c142652df8793319fb8", size = 23054441, upload-time = "2026-06-19T14:59:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/3c/23/21f5e703643d66f21faa6b4c73195bfcad70c55efcb4f1ab327cd7c4101a/scipy-1.18.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:52a96e21517c7292375c0e27dd796a811f03fcea5fd4d108fdfea8145dcf17ab", size = 33968720, upload-time = "2026-06-19T14:59:56.415Z" }, + { url = "https://files.pythonhosted.org/packages/dd/aa/1b939f6c67ed68635bb538e6752d3dacc02f66535182e939a89581a44e9c/scipy-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f55797419e16e7f30cf88ffb3113ce0467f00cfe3f70d5c281730b21769bfc2", size = 35287115, upload-time = "2026-06-19T14:59:59.411Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ff/eec46be7e9234208f801062b53e1983085eddebd693f6c9bfb03b459830d/scipy-1.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ad033410e2e0672ffdc1042110cef20e1c46f8fd0616cee1d44d8d58fad8fc11", size = 35577989, upload-time = "2026-06-19T15:00:02.235Z" }, + { url = "https://files.pythonhosted.org/packages/84/ca/210d4759c7210bb7d269437421959b39a33434e2776b60c5cb8a763bb30a/scipy-1.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a55985d54c769c872e64b7f4c8a81cc30ef700cc04296abbbf3705439c126de", size = 37421717, upload-time = "2026-06-19T15:00:05.102Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/9a9edb45345bd6744da5ddfb6628e5d5185920494c6a67ec45b6381004cb/scipy-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:71ccc8faa2dd16ac310233203474a8b5cb67f10dedd54a3116d34943f4b19132", size = 36597428, upload-time = "2026-06-19T15:00:08.112Z" }, + { url = "https://files.pythonhosted.org/packages/99/0e/33f32a2a58987e26aec0f7df252cbbad1e90ae77bdbc76f40dd4ed0cf0ea/scipy-1.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:d88363fd9d8fbd3511bd273f1a49efb2a540773ddf92a91d57498ce7dd7f3e76", size = 24351481, upload-time = "2026-06-19T15:00:11.103Z" }, + { url = "https://files.pythonhosted.org/packages/05/52/9c0136c2de7ae0779b7b366447766cec6d9f0702c56bb8ffeb04c8fd3af4/scipy-1.18.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:09143f676d157d9f546d663504ef9c1becb819824f1afc018814176411942446", size = 31036107, upload-time = "2026-06-19T15:00:14.03Z" }, + { url = "https://files.pythonhosted.org/packages/02/73/0291a64843270f4efb86cdcf2ee0f2048631b65ec6b405398b2b4dbf11bf/scipy-1.18.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5efe260f69417b97ddae455bfb5a95e8359f7f66ad7fa9522a60feb66f169520", size = 28663303, upload-time = "2026-06-19T15:00:16.819Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0f/10ffa0b697a572f4e0d48b92a88895d366422f019f723e7e14a84c050dac/scipy-1.18.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:68363b7eaacd8b5dd426df56d782cc156468ac79a127a1b87ca597d6e2e82197", size = 20404960, upload-time = "2026-06-19T15:00:19.635Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d2/e896cea21ba8edd6c81d4c55b1ffcc717e79698dcbebf9641b4cfb4c6622/scipy-1.18.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:c5557d8be5da8e41353fcd4d21491fdbab83b062fc579e94dc09a7c8ab4f669b", size = 23034074, upload-time = "2026-06-19T15:00:22.107Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b2/e83ea34279a52c03374477c74006256ec78df65fc877baa4617d6de1d202/scipy-1.18.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d13bca67c096d89fb95ced0d8921807300fce0275643aef9533cc63a0773468", size = 33942038, upload-time = "2026-06-19T15:00:24.964Z" }, + { url = "https://files.pythonhosted.org/packages/f6/af/e8fe5fb136f51e2b01678b92cb4106d10d8cd68ec147ead2e7cb0ac75398/scipy-1.18.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a46f9273dbd0eb1cefba61c9b8648b4dfe3cbc14a080176f9a73e44b8336dc7f", size = 35266390, upload-time = "2026-06-19T15:00:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/3a/49/2c5cbb907b56695fc67517811d1db234dfd83381a84814ec220aded2794d/scipy-1.18.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5aba46108853ddfc77906b6557aac839d2b52e900c1d72a1180adaaab58d265f", size = 35551324, upload-time = "2026-06-19T15:00:31.014Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/eda39f7a2d306ff0ffc574afd13c0bbb6d10a603d9a413998ee269487a80/scipy-1.18.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6f758e35f12757b5d95c00bc6de2438e229c2664b7a92e96f205959d9f2dfa4", size = 37404785, upload-time = "2026-06-19T15:00:34.072Z" }, + { url = "https://files.pythonhosted.org/packages/b7/d2/ae881ee28d014f38e0ccbfd974a06a919ba9af34f1f74bf42b5301891d63/scipy-1.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:1afac4a847207c7ff8efd321734a50b06d0280b3b2a2c0fc2f413101747ad7c7", size = 36554943, upload-time = "2026-06-19T15:00:36.903Z" }, + { url = "https://files.pythonhosted.org/packages/70/3a/21154e2d54eb3639c6bf4dbae2e531c68356bfe95990daa30df33b30d556/scipy-1.18.0-cp313-cp313-win_arm64.whl", hash = "sha256:c5dbddf60e58c2312316d097271a8e73d40eaf2eabfa4d95ed7d3695bbf2ce7b", size = 24350911, upload-time = "2026-06-19T15:00:40.062Z" }, + { url = "https://files.pythonhosted.org/packages/78/b5/915a19b3de2f7430062b509653563db1633ddbb6f021b06731521115d4e2/scipy-1.18.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4c256ee70c0d1a8a2ace807e199ccd4e3f57037433842abb3fb36bc17eaa9578", size = 31036253, upload-time = "2026-06-19T15:00:43.216Z" }, + { url = "https://files.pythonhosted.org/packages/d7/88/b72def7262e150d16be13fca37a96481138d624e700340bc3362a7588929/scipy-1.18.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:2ef3abc54a4ffc53765374b0d5728532dfdd2585ed23f6b11c206a1f0b1b9af8", size = 28673758, upload-time = "2026-06-19T15:00:46.663Z" }, + { url = "https://files.pythonhosted.org/packages/91/02/2e636a61a525632c373cf6a9c24442a3ffb79e364d38e98b32042964ac32/scipy-1.18.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2a6af57bd9e4a75d70e4117e78a1bbee84f79ae3fbb6d0111005d6ebcc4cb8d", size = 20415514, upload-time = "2026-06-19T15:00:49.399Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b6/2135974442f6aba159d9d39d774a1c8cb19947016725d69fecc685df45bf/scipy-1.18.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:3f1ac564d3bf6c03d861d2cd87a1bea0da2887136f7fb1bf519c05a8971452d6", size = 23034398, upload-time = "2026-06-19T15:00:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e6/ba89ec5abf6ee9257c0d1ec985573f3ae32742c24bc03e016388a40b1b15/scipy-1.18.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40395a5fcd1abee49a5c7aaa98c29db393eedc835138560a588c47ec16156690", size = 33998032, upload-time = "2026-06-19T15:00:54.838Z" }, + { url = "https://files.pythonhosted.org/packages/7f/c4/bc41eb19b0fd0db868f4132920879019318d80cc522ad8f2bca4611af808/scipy-1.18.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ca01e8ae69f1b18e9a58d91afead31be3cef0dd905a10249dac559ee15460a0", size = 35283333, upload-time = "2026-06-19T15:00:58.152Z" }, + { url = "https://files.pythonhosted.org/packages/53/a4/cbdeef6eb3830a8462a9d4ada814de5fc984345cc9ecf17cbec51a036f1e/scipy-1.18.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7a7f3b01647384dbc3a711e8c6778e0aabbe93959249fef5c7393396bcac0867", size = 35610216, upload-time = "2026-06-19T15:01:01.155Z" }, + { url = "https://files.pythonhosted.org/packages/80/4d/b2b82502b65f661d1b789c1665dcdf315d5f12194e06fc0b37946294ebae/scipy-1.18.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6aa94e78ec192a30063a5e72e561c28af769dc311190b24fe91774eff1969709", size = 37418960, upload-time = "2026-06-19T15:01:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/902d836831474b0ab5a37d16404f7bc5fafd9efba632890e271ba952635f/scipy-1.18.0-cp314-cp314-win_amd64.whl", hash = "sha256:2d8bbdc6c817f5b4006a54d799d4f5bab6f910193cbb9a1ff310833d4d270f61", size = 37288845, upload-time = "2026-06-19T15:01:07.822Z" }, + { url = "https://files.pythonhosted.org/packages/b6/43/8d73b337a3bdb14daa0314f0434210747c02d79d729ce1777574a817dcf6/scipy-1.18.0-cp314-cp314-win_arm64.whl", hash = "sha256:18e9575f1569b2c54174e6159d32942e03731177f63dce7975f0a0c88d102f5b", size = 24988971, upload-time = "2026-06-19T15:01:11.076Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b4/f11918b0508a2787031a0499a03fbe3546f3bb5ca05d01038c45b278c09a/scipy-1.18.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f351e0dd702687d12a402b867a1b4146a256923e1c38317cbc472f6372b94707", size = 31399325, upload-time = "2026-06-19T15:01:13.723Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d1/1f287b57c0ff0ee5185dff3946d92c8017d39b0e431f0ae79a3ff1859512/scipy-1.18.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7c7a51b33ce387193c97f228320cf8e87361daa1bba750638677729598b3e677", size = 29092110, upload-time = "2026-06-19T15:01:16.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1a/7b74eb6c392fdcb27d414c0e7558a6d0231eb3b6d73571f479bb81ea8794/scipy-1.18.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:84031d7b052a54fae2f8632e0ec802073d385476eb9a63079bce6e23ef9283d4", size = 20833811, upload-time = "2026-06-19T15:01:20.488Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ad/f3941716320a7b9cb4d68734a903b45fe16eff5fb7da7e16f2e619304979/scipy-1.18.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:56abf29a7c067dde59be8b9a22d606a4ea1b2f2a4b756d9d903c62818f5dacce", size = 23396644, upload-time = "2026-06-19T15:01:23.364Z" }, + { url = "https://files.pythonhosted.org/packages/22/22/1446b62ffe07f9719b7d9b1b6a4e05a772833ae8f441fe4c22c34c9b250f/scipy-1.18.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ad44305cfa24b1ba5803cbbebf033590ccbac1aa5d612d727b785325ab408b0", size = 34079318, upload-time = "2026-06-19T15:01:26.002Z" }, + { url = "https://files.pythonhosted.org/packages/56/3b/b87da667098bb470fa30c7011b0ba351ee976dd395c78798c66e941665a3/scipy-1.18.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:945c1761b93f38d7f99ae81ae80c63e621471608c7eeead563f6df025585cd58", size = 35324320, upload-time = "2026-06-19T15:01:28.881Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a1/c7932f91909759b0267f75fdea34e91309f96b895757534b76a90b6b4344/scipy-1.18.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1a4441f15d620578772a49e5ab48c0ee1f7a0220e387110283062729136b2553", size = 35699541, upload-time = "2026-06-19T15:01:31.968Z" }, + { url = "https://files.pythonhosted.org/packages/f7/86/5185061a1fcc41d18c5dc2463969b3a3964b31d9ac67b2fb05d4c7ff7670/scipy-1.18.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9aac6192fac56bf2ca534389d24623f07b39ff83317d58287285e7fbd622ff76", size = 37472480, upload-time = "2026-06-19T15:01:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/31/8e/f04c68e39919a010d34f2ee1367fd705b0a25a02f609d755f0bfbc0a15fc/scipy-1.18.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e40baea28ae7f5475c779741e2d90b1247c78531207b49c7030e698ff81cee3f", size = 37365390, upload-time = "2026-06-19T15:01:38.091Z" }, + { url = "https://files.pythonhosted.org/packages/d5/19/969dc072906c84dd0a3b05dcf57ea750936087d7873549e408b35cfc3f97/scipy-1.18.0-cp314-cp314t-win_arm64.whl", hash = "sha256:368e0a705903c466aa5f08eefb39e6b1b6b2d659e7352a31fd9e2438365be0f8", size = 25279661, upload-time = "2026-06-19T15:01:40.817Z" }, +] + +[[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.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]] +name = "scipy-stubs" +version = "1.18.0.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'", +] +dependencies = [ + { name = "optype", version = "0.18.0", source = { registry = "https://pypi.org/simple" }, extra = ["numpy"], marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a0/c6/c4c80bd13c0eec99121922c50cd1c72600672307b28e4ba67f636d6b298d/scipy_stubs-1.18.0.1.tar.gz", hash = "sha256:761cb35106200c90a37034d3c8120d3899d673de1e5879d3a2ac1d6816c62327", size = 410862, upload-time = "2026-07-12T21:09:29.993Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/0c/78404528783677077f072288724ff8ef8381bbc219b110e3147e60361b6d/scipy_stubs-1.18.0.1-py3-none-any.whl", hash = "sha256:704408c5f03a33924c9fed6dc9e25926dc331ed508d52f1beece0188525cebad", size = 616217, upload-time = "2026-07-12T21:09:28.45Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -2469,6 +2828,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"