From 0a66a89c007b8cf6087cdb2551298d7146bcc94c Mon Sep 17 00:00:00 2001 From: Richie Caputo Date: Thu, 23 Jul 2026 19:45:37 -0400 Subject: [PATCH] TJC-1814: Python 3.14 default; numba-jitted vertex enumeration (4.2x certify-sweep) Python: default and CI ceiling move to 3.14 (requires-python >=3.11,<3.15; matrix 3.11/3.14; regular GIL build - the free-threaded interpreter is deliberately not used). Measured: 3.14 alone is a wash for this workload (certify-sweep 233.8s on 3.13 vs 232.3s on 3.14; the time is numpy/LAPACK and tight float loops). Dependencies: numba 0.66 + llvmlite 0.48 added; numpy pinned <2.5 (numba's ceiling; 2.4.6 resolves - the previous lock already forked to 2.4.6 on 3.11 because numpy 2.5 dropped it). certify_sweep: the batched-LAPACK dual-vertex enumeration (profiled at ~99% of runtime: det+solve on ~24k small systems per call plus array assembly) is replaced by a fused numba kernel - lexicographic combination enumeration, partial-pivot LU with the identical |det| > 1e-10 screen, back substitution, and the identical 1e-9 feasibility slack. Measured on the same machine, sequentially, single runs: - certify-sweep: 233.8s -> 55.3s (4.2x) - full slow suite: 372s -> 192s (6:12 -> 3:12), 3/3 pass - archived transcript reproduced BYTE-FOR-BYTE by the jitted kernel (stock numpy 2.4.6 flips one cosmetic tie-break line; the jit agrees with the numpy-2.5.1-generated archive, so no regeneration was needed) - fast suite 17/17 on 3.14.4 and 3.11 from the same lock Paper Section 4 and Appendix B recharacterize the sweep implementation honestly; PDF regenerated on Typst 0.15.1 (double-compile deterministic). Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yml | 4 +- .github/workflows/release.yml | 2 +- .github/workflows/scientific.yml | 2 +- .python-version | 2 +- README.md | 4 +- paper/goemans_gap_paper.pdf | Bin 400201 -> 400478 bytes paper/goemans_gap_paper.typ | 4 +- pyproject.toml | 8 +- src/goemans_gap/certify_sweep.py | 130 ++++++++++++++++++++++++------- uv.lock | 128 ++++++++++++++++++++---------- 10 files changed, 203 insertions(+), 81 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3d45b5..c7a42c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: strategy: fail-fast: false matrix: - python: ["3.11", "3.13"] + python: ["3.11", "3.14"] steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 @@ -58,7 +58,7 @@ jobs: - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 with: version: "0.11.6" - python-version: "3.13" + python-version: "3.14" enable-cache: true - run: uv run --with cffconvert==2.0.0 cffconvert --validate diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c76d783..55454b4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -54,7 +54,7 @@ jobs: - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 with: version: "0.11.6" - python-version: "3.13" + python-version: "3.14" enable-cache: true - run: uv sync --frozen - run: uv run --with cffconvert==2.0.0 cffconvert --validate diff --git a/.github/workflows/scientific.yml b/.github/workflows/scientific.yml index 214d9df..57ad092 100644 --- a/.github/workflows/scientific.yml +++ b/.github/workflows/scientific.yml @@ -23,7 +23,7 @@ jobs: - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 with: version: "0.11.6" - python-version: "3.13" + python-version: "3.14" enable-cache: true - run: uv sync --frozen - run: uv run --frozen pytest -m slow diff --git a/.python-version b/.python-version index 24ee5b1..6324d40 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.13 +3.14 diff --git a/README.md b/README.md index 6f5c3a7..6f70124 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ exploratory computation. | `verify-goemans` | Exhaustive integer enumeration | Exact 58-versus-60 counterexample and exact violation-budget thresholds | | `amplify-family` | `fractions.Fraction` | Exact checks for the displayed integral families | | `revision-checks` | Exhaustive rational enumeration | Exact checks for the general-family examples, gate collapse, and dummy-terminal embedding | -| `certify-sweep` | Seeded NumPy floating-point vertex enumeration and local search | Numerical stress test on $H^{\ast}$ plus a sampled four-terminal side search; boundary points and cases with more than nine admissible routing vectors are skipped, so this is not a proof certificate | +| `certify-sweep` | Seeded floating-point vertex enumeration (numba-jitted) and local search | Numerical stress test on $H^{\ast}$ plus a sampled four-terminal side search; boundary points and cases with more than nine admissible routing vectors are skipped, so this is not a proof certificate | | `interval-search` | Exhaustive finite structure enumeration plus truncated assignment enumeration and floating-point grid/refinement | Exploratory records in the stated chain subclass; at most the first 16 minimal-transit assignments per structure are examined | | `laned-search` | Seeded floating-point heuristic over every fifth canonical structure | Exploratory records in the stated laned subclass; a deterministic one-fifth structure subsample | @@ -107,7 +107,7 @@ breaks the build. ### Python [uv](https://docs.astral.sh/uv/) 0.11.6 or later is recommended; Python 3.11 -through 3.13 is supported. +through 3.14 is supported, and 3.14 is the default. ```bash uv sync --frozen diff --git a/paper/goemans_gap_paper.pdf b/paper/goemans_gap_paper.pdf index 7ce6c106a020de9e1bb0038baf6af6c10f09db3e..b07494cbd3bdbab837ba9bbdc792e9b3798ec446 100644 GIT binary patch delta 47624 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�gQWE#7xnvlLj-JClgiLdu71SN#_A zIJpItwjiiUHv+E!h!;>MMNyg19T)UqAkLJW0Q7hvK1R!RQa!lzbkUFmWLoImj6|B* z9+S7kV8>I1$eUdYiQAk`RJuRx}J9+Xu^Fovhq-}s6+p)fIYfj$NE5g1*R z75ejaQE;J!h&l8grqIZX)pKH^N&(FAhU>&IBD_waWk^247WJhR=|r3Olpwx?v97{0tPA^$-f^Iyhsh6zKx6{_kqRM$x*9rtgoOzi*g7eEX*bqUW=kRnWyNIi!SiEY zK+I6Ik5Nj>8$r^@8EF{nc&5o1!p|a}MYK%3O|T!GoiF`3@g`HG0<;!gccf+Uj$OE0 z*hea9(u6n~22ex;ZadjTdcy#b3JA8RmX+D(!!1drLYlFT)(j*NaG3>ip{NP&jz}4l zM2TT^{W*#_4luFQ%%A~CRU9b2C9@JR(g1KJ5a)2FAc`CSBpAhdQ08d?f;M9zOBOh^nO+>(E8iI0#L2sBHo=3G^ce08zvX zkzy(>(5p;D#x!d(HYad3LRR!>05Ajtop*=0Iw^U;s7oJ042q-t7(TRze?Xt1*=p6Q f-Zu1U(M%1}|6P;Ny2UaT$ch&O*~*n`)XMY!J@-!I diff --git a/paper/goemans_gap_paper.typ b/paper/goemans_gap_paper.typ index 5a13b98..6960cbb 100644 --- a/paper/goemans_gap_paper.typ +++ b/paper/goemans_gap_paper.typ @@ -316,7 +316,7 @@ _Proof._ The splits are integral and feasible: $(a - b) M - 1 >= 0$ since $a > b #text(weight: "bold")[Concurrent public family.] In the public discussion following Rybin's announcement, H. Juang posted a three-parameter integral counterexample family on the same digraph — Rybin's instance is its member $(b, m, g) = (10, 5, 1)$ up to scaling — and observed that parameters near $m\/b = sqrt(2) - 1$ push the ratio toward $(4 sqrt(2) + 2)\/7$; the reparametrization $a = b + m$ identifies that direction with $delta -> 1\/sqrt(2)$ here, and Juang's example $(70, 29, 1)$ has the demands and unsplittable optimum $2 a b$ of Proposition 4.1's Pell member $(a, b) = (99, 70)$ with a slightly different split. Beyond the family itself, the contributions here are the closed-form ratio, the exact limit, and — with Theorem A's upper bound — that the common value $rho^*$ is the exact, non-attained supremum. -#text(weight: "bold")[Machine verification (verification, not proof).] The formulas of Proposition 4.1 were checked by exhaustive enumeration in exact rational arithmetic (#raw("fractions.Fraction")) for $(a, b)$ in ${(3, 2), (7, 5), (10, 7), (17, 12), (41, 29), (99, 70), (577, 408)}$ and $M$ in ${1, 1000}$. The complete path set of $H^*$ is kernel-checked in Lean (#raw("walk_s_terminal_iff"), Appendix A) and independently re-derived by depth-first search in the Rybin-instance verifier. Separately, a seeded floating-point sweep over $(d, f)$, using NumPy dual-vertex enumeration for each sampled cost problem (4000 random samples plus local search), never exceeded $rho^*$ and approached the exhibited family's boundary configuration $d = (1, 0.70711, 1)$, $f = (0.29289, 0.41421, 0.29289)$. Candidate points with any fractional arc load at or below $10^(-12)$ are excluded because the numerical dual formulation divides by those loads. This sweep is a numerical stress test, not an exact LP certificate. An exact integer violation-budget sweep shows the counterexamples analyzed here are knife-edge at budget $d_max$: at budget $d_max + 1$ (in Rybin's integers) the minimum cost collapses below the fractional cost. They therefore say nothing against the still-open $2 d_max$ or $O(d_max)$ cost conjectures, nor against the Morell-Skutella two-sided conjecture. +#text(weight: "bold")[Machine verification (verification, not proof).] The formulas of Proposition 4.1 were checked by exhaustive enumeration in exact rational arithmetic (#raw("fractions.Fraction")) for $(a, b)$ in ${(3, 2), (7, 5), (10, 7), (17, 12), (41, 29), (99, 70), (577, 408)}$ and $M$ in ${1, 1000}$. The complete path set of $H^*$ is kernel-checked in Lean (#raw("walk_s_terminal_iff"), Appendix A) and independently re-derived by depth-first search in the Rybin-instance verifier. Separately, a seeded floating-point sweep over $(d, f)$, using jit-compiled dual-vertex enumeration (numba) for each sampled cost problem (4000 random samples plus local search), never exceeded $rho^*$ and approached the exhibited family's boundary configuration $d = (1, 0.70711, 1)$, $f = (0.29289, 0.41421, 0.29289)$. Candidate points with any fractional arc load at or below $10^(-12)$ are excluded because the numerical dual formulation divides by those loads. This sweep is a numerical stress test, not an exact LP certificate. An exact integer violation-budget sweep shows the counterexamples analyzed here are knife-edge at budget $d_max$: at budget $d_max + 1$ (in Rybin's integers) the minimum cost collapses below the fractional cost. They therefore say nothing against the still-open $2 d_max$ or $O(d_max)$ cost conjectures, nor against the Morell-Skutella two-sided conjecture. // ============================================================ // Part II @@ -509,7 +509,7 @@ where #raw("hStarRatios") is the parametrized ratio set and #raw("graphRatios") #raw("verify_goemans.py") exhaustively verifies Rybin's instance with exact integer arithmetic, derives all paths by depth-first search, and computes the exact violation-budget sweep. #raw("amplify_family.py") and #raw("revision_checks.py") use exact rational arithmetic for the displayed family members, the gate-gadget collapse, and the dummy-terminal embedding. -The seeded floating-point scripts are evidence, not proof certificates, and their exact budgets and filters are part of the reported scope. #raw("certify_sweep.py") excludes points having any fractional arc load at or below $10^(-12)$. On $H^*$ it evaluates one boundary-near point with $epsilon = 10^(-6)$, 4000 random points, and two 4000-proposal hill climbs. Its four-terminal side search samples 120 of 1248 eligible designs and 120 parameter points per retained design, skips parameter points with more than nine admissible routing load vectors, and subjects only the best retained design to 6000 additional refinement proposals. #raw("interval_search.py") visits all 32, 112, 257, and 973 canonical structures in the four displayed classes, respectively, but retains at most the first 16 Cartesian-product assignments of inclusion-minimal transit sets per structure. It uses the four-value demand grid stated in Section 6 and refines only the best candidate in each class, with 4000 continuous and 300 exit-weight proposals. #raw("laned_search.py") enumerates 927 canonical structures, evaluates the 185 whose deterministic indices are divisible by five, and uses six starts totaling 3000 proposals for each retained structure. Complete outputs are committed with the repository, and the exact scripts are regression-tested byte for byte. The slow numerical tests compare normalized scientific records with those outputs and assert stable guardrails while allowing platform-dependent tie breaking and least-significant digits. +The seeded floating-point scripts are evidence, not proof certificates, and their exact budgets and filters are part of the reported scope. #raw("certify_sweep.py") jit-compiles its dual-vertex enumeration with numba (partial-pivot LU with the same nonsingularity and feasibility thresholds as the batched formulation it replaces; the archived transcript is reproduced byte for byte) and excludes points having any fractional arc load at or below $10^(-12)$. On $H^*$ it evaluates one boundary-near point with $epsilon = 10^(-6)$, 4000 random points, and two 4000-proposal hill climbs. Its four-terminal side search samples 120 of 1248 eligible designs and 120 parameter points per retained design, skips parameter points with more than nine admissible routing load vectors, and subjects only the best retained design to 6000 additional refinement proposals. #raw("interval_search.py") visits all 32, 112, 257, and 973 canonical structures in the four displayed classes, respectively, but retains at most the first 16 Cartesian-product assignments of inclusion-minimal transit sets per structure. It uses the four-value demand grid stated in Section 6 and refines only the best candidate in each class, with 4000 continuous and 300 exit-weight proposals. #raw("laned_search.py") enumerates 927 canonical structures, evaluates the 185 whose deterministic indices are divisible by five, and uses six starts totaling 3000 proposals for each retained structure. Complete outputs are committed with the repository, and the exact scripts are regression-tested byte for byte. The slow numerical tests compare normalized scientific records with those outputs and assert stable guardrails while allowing platform-dependent tie breaking and least-significant digits. #sechead[References] diff --git a/pyproject.toml b/pyproject.toml index 9a50b79..65b5b97 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ version = "0.1.0" description = "Reproducibility suite for the Rybin-digraph extremal theorem for Goemans's cost conjecture" readme = "README.md" authors = [{ name = "A. Richard Caputo III", email = "rcaputo3@tjclp.com" }] -requires-python = ">=3.11,<3.14" +requires-python = ">=3.11,<3.15" license = "MIT" license-files = ["LICENSE"] keywords = ["combinatorial-optimization", "formal-verification", "lean4", "unsplittable-flow"] @@ -16,7 +16,11 @@ classifiers = [ "Topic :: Scientific/Engineering :: Mathematics", ] dependencies = [ - "numpy>=2.0", + # numba 0.66 requires numpy < 2.5; the jit-compiled vertex enumeration in + # certify_sweep reproduces the archived transcript byte-for-byte and runs + # the sweep about 4x faster than the batched-LAPACK formulation. + "numba>=0.66", + "numpy>=2.0,<2.5", ] [project.urls] diff --git a/src/goemans_gap/certify_sweep.py b/src/goemans_gap/certify_sweep.py index 78ac99c..b8a4da2 100644 --- a/src/goemans_gap/certify_sweep.py +++ b/src/goemans_gap/certify_sweep.py @@ -24,6 +24,7 @@ import math import random +import numba import numpy as np RHO = 2 / (2 * math.sqrt(2) - 1) @@ -34,8 +35,103 @@ MAX_TEMPLATE_ROUTING_VECTORS = 9 +@numba.njit(cache=True, fastmath=False) +def _lp_value_kernel(matrix, rhs, routing_count): + """Fused dual-LP vertex enumeration. + + Enumerates basis-row combinations in lexicographic order (matching the + previous ``itertools.combinations`` formulation), factors each + ``k x k`` basis system by partial-pivot LU with the same ``|det| > 1e-10`` + nonsingularity screen, solves by back substitution, and applies the same + ``1e-9`` feasibility slack against every constraint row. Returns ``nan`` + when no basis is nonsingular and ``inf`` when none is feasible. + """ + rows, k = matrix.shape + m = k - 1 + idx = np.empty(m, dtype=np.int64) + for i in range(m): + idx[i] = i + a = np.empty((k, k)) + b = np.empty(k) + v = np.empty(k) + best = np.inf + found_nonsingular = False + while True: + for i in range(m): + row = idx[i] + for j in range(k): + a[i, j] = matrix[row, j] + b[i] = rhs[row] + for j in range(k): + a[m, j] = 1.0 if j < routing_count else 0.0 + b[m] = 1.0 + + det = 1.0 + singular = False + for col in range(k): + pivot_row = col + pivot_mag = abs(a[col, col]) + for r in range(col + 1, k): + mag = abs(a[r, col]) + if mag > pivot_mag: + pivot_mag = mag + pivot_row = r + if pivot_row != col: + for j in range(k): + swap = a[col, j] + a[col, j] = a[pivot_row, j] + a[pivot_row, j] = swap + swap = b[col] + b[col] = b[pivot_row] + b[pivot_row] = swap + det = -det + pivot = a[col, col] + det *= pivot + if pivot == 0.0: + singular = True + break + inv = 1.0 / pivot + for r in range(col + 1, k): + factor = a[r, col] * inv + if factor != 0.0: + for j in range(col, k): + a[r, j] -= factor * a[col, j] + b[r] -= factor * b[col] + + if not singular and abs(det) > 1e-10: + found_nonsingular = True + for r in range(k - 1, -1, -1): + s = b[r] + for j in range(r + 1, k): + s -= a[r, j] * v[j] + v[r] = s / a[r, r] + feasible = True + for r in range(rows): + s = 0.0 + for j in range(k): + s += matrix[r, j] * v[j] + if s > rhs[r] + 1e-9: + feasible = False + break + if feasible and v[routing_count] < best: + best = v[routing_count] + + i = m - 1 + while i >= 0 and idx[i] == rows - m + i: + i -= 1 + if i < 0: + break + idx[i] += 1 + for j in range(i + 1, m): + idx[j] = idx[j - 1] + 1 + + if not found_nonsingular: + return np.nan + return best + + def lp_value(x, routings): - """Approximate max-cost value by NumPy dual-LP vertex enumeration.""" + """Approximate max-cost value by jit-compiled dual-LP vertex enumeration.""" routing_count, arc_count = routings.shape # variables v = (lambda_1..K, t); constraints: # G v <= h : for each arc a: sum_k Y[k,a] lam_k - t x_a <= 0 @@ -49,36 +145,10 @@ def lp_value(x, routings): matrix[arc_count : arc_count + routing_count, :routing_count] = -np.eye( routing_count ) - equality = np.zeros(variable_count) - equality[:routing_count] = 1.0 - combinations = np.array( - list( - itertools.combinations( - range(arc_count + routing_count), variable_count - 1 - ) - ) - ) - systems = np.concatenate( - [ - matrix[combinations], - np.broadcast_to(equality, (len(combinations), 1, variable_count)), - ], - axis=1, - ) - system_rhs = np.concatenate( - [rhs[combinations], np.ones((len(combinations), 1))], axis=1 - ) - determinants = np.abs(np.linalg.det(systems)) - nonsingular = determinants > 1e-10 - if not nonsingular.any(): - return None - vertices = np.linalg.solve( - systems[nonsingular], system_rhs[nonsingular][..., None] - )[..., 0] - feasible = np.all(vertices @ matrix.T <= rhs + 1e-9, axis=1) - if not feasible.any(): + result = _lp_value_kernel(matrix, rhs, routing_count) + if math.isnan(result) or math.isinf(result): return None - return float(vertices[feasible][:, routing_count].min()) + return float(result) # ---------- Rybin digraph ---------- diff --git a/uv.lock b/uv.lock index 038dd4f..e3979f0 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,6 @@ version = 1 revision = 3 -requires-python = ">=3.11, <3.14" +requires-python = ">=3.11, <3.15" resolution-markers = [ "python_full_version >= '3.12'", "python_full_version < '3.12'", @@ -20,8 +20,8 @@ name = "goemans-gap" version = "0.1.0" source = { editable = "." } dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numba" }, + { name = "numpy" }, ] [package.dev-dependencies] @@ -30,7 +30,10 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "numpy", specifier = ">=2.0" }] +requires-dist = [ + { name = "numba", specifier = ">=0.66" }, + { name = "numpy", specifier = ">=2.0,<2.5" }, +] [package.metadata.requires-dev] dev = [{ name = "pytest", specifier = ">=8.0" }] @@ -44,13 +47,70 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] +[[package]] +name = "llvmlite" +version = "0.48.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/a0/acc8ffcd5bdc63df0097e22c719bfcd61b604358343089313a8aebbb24ab/llvmlite-0.48.0.tar.gz", hash = "sha256:543b19f9ef8f3c7c60d1468191e4ee1b1537bf9f8a3d56f64c0ddd98de92edd2", size = 184016, upload-time = "2026-07-02T20:20:05.308Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/55/595981f14fbae9ba966feb12af552b1fe69889e44e64ac883a731ed335e0/llvmlite-0.48.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:56a7e24607d3f02d7b1bae8d29c7e1e423d53143d68b072999777f19678fe77b", size = 40480651, upload-time = "2026-07-01T18:41:18.438Z" }, + { url = "https://files.pythonhosted.org/packages/26/08/0109d1b9cb3f4603f3890e30bc66c65332b79185f12a045343b2ae431f67/llvmlite-0.48.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6fa532d6bb3fd3f0803567c736401c54aecfe1a396d3ad25d2440d220e09f0e7", size = 59890118, upload-time = "2026-07-01T18:41:28.184Z" }, + { url = "https://files.pythonhosted.org/packages/02/eb/c5281be180c789cdffbf45b671884c57d7e61345ef3b0f643a4965e108e8/llvmlite-0.48.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:979a66a3f28a02565383ff463527dce78e9b856298872a361283132488e83591", size = 58343458, upload-time = "2026-07-01T18:41:23.397Z" }, + { url = "https://files.pythonhosted.org/packages/aa/f7/b3222b13f2d424dae3c9e63fde476af25ebccf1f3faf0b52d1b79fc15c70/llvmlite-0.48.0-cp311-cp311-win_amd64.whl", hash = "sha256:efaee0276e5e17c2b99b92e0c974bd484ef5977cf5dbc9168e82b71578edb47f", size = 41864734, upload-time = "2026-07-01T18:41:31.932Z" }, + { url = "https://files.pythonhosted.org/packages/92/a2/28696a9e61e245d1a79816d29d106692a90a2b6e7d78c98b326db70827af/llvmlite-0.48.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:d66c3beb4209087ddd4cf4ed2a0856b6887e6a913bdcf1aacfec9851cf2cba4e", size = 40480651, upload-time = "2026-07-01T18:41:35.694Z" }, + { url = "https://files.pythonhosted.org/packages/80/f2/72409351db66d0a317ec5087e076f31fb7b773a640db8a90ce6b5cac9edd/llvmlite-0.48.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:416fa4c2c66c2c6dc6d0a402648c19206e548efa0aa1eff01ad5cdad0af8217d", size = 59890118, upload-time = "2026-07-01T18:41:44.886Z" }, + { url = "https://files.pythonhosted.org/packages/3a/27/5ae2f3722606360480707adb47f001ad89df8251d06b14ee80336e660b66/llvmlite-0.48.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5e5a5131045b72345c71062ea1a91910dde913792b6c9b28ebb2c1c0a712e98", size = 58343459, upload-time = "2026-07-01T18:41:40.306Z" }, + { url = "https://files.pythonhosted.org/packages/16/78/d824ffff7521cd140dc2006e44ce2bc82e64b48d1b32e90e956308c85a74/llvmlite-0.48.0-cp312-cp312-win_amd64.whl", hash = "sha256:d45c7541a80934ec6d8ab0defe67439494ecd2193cbf852a44ba827808976ac1", size = 41865022, upload-time = "2026-07-01T18:41:48.663Z" }, + { url = "https://files.pythonhosted.org/packages/9c/23/fe9316d14626b42c73ef0b502e724705a6ee9450afe53759c0a99c37c2d7/llvmlite-0.48.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a83a99ef0c05b4ccddf9b6218ed9fe84b653a0caf7c1d9dbe148d6d16c67f518", size = 40480652, upload-time = "2026-07-01T18:41:52.216Z" }, + { url = "https://files.pythonhosted.org/packages/1b/4a/90715fa12006d681270b08d881195b6fab3ec39572e048764a1f7f59fed7/llvmlite-0.48.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8761b9e522f55207e24424fcd98370289eec2710bf8e915c82d1053f642450dc", size = 59890120, upload-time = "2026-07-01T18:42:00.748Z" }, + { url = "https://files.pythonhosted.org/packages/70/5e/7b3e20d64650ca3c80af0cdb664ec4b575ec83d9d4dd05bea8bd31f9bbb6/llvmlite-0.48.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fe5cb59b2063bfa039dcb8ca6481c0181bf552f340d10dcf61d7996a665556e", size = 58343457, upload-time = "2026-07-01T18:41:56.41Z" }, + { url = "https://files.pythonhosted.org/packages/17/97/5a430055d1838cf1fb7a01cfa943300f5e4c026fc6333a522c5e4a03b0c1/llvmlite-0.48.0-cp313-cp313-win_amd64.whl", hash = "sha256:91c7e24e74cde3f02b88aa5acca678373f9e069f3b98531b3dbb3a142d9d10bb", size = 41865022, upload-time = "2026-07-01T18:42:04.57Z" }, + { url = "https://files.pythonhosted.org/packages/8d/8e/8170f2e0c217f88069c333d85bb976e536b332aecfcce606ddbdb249385f/llvmlite-0.48.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:321f1ac39b462603f0b589751aecf2d237d056f6d005749c1752b6f23ec3f074", size = 40480650, upload-time = "2026-07-01T18:42:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e1/05b50692b647cac3c18200ac485b04f342f00ed173c9cc46767274469a15/llvmlite-0.48.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:05f0103c8f2f96a37441337e3643863c01b8e83e530aff38960dcb383c54a065", size = 59890115, upload-time = "2026-07-01T18:42:17.805Z" }, + { url = "https://files.pythonhosted.org/packages/f7/c3/470b8c4ff9ae2db2f9cf5c3e73de76ed908a32788ae9eb5602d43e6a476b/llvmlite-0.48.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37d66fae72802175b0bfe1ea06e624b51e2d7aee6c3c34bbd09739b8f88e8e0b", size = 58343457, upload-time = "2026-07-01T18:42:13.217Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2d/6a5171fb7236ac0895e1a02ccba3735bf291e8597239aa6421894d3c0ba8/llvmlite-0.48.0-cp314-cp314-win_amd64.whl", hash = "sha256:966dcab0a598e2bd8fb5f2cc082cf7b07bae564fc485a3a8692393caf986facf", size = 42986372, upload-time = "2026-07-01T18:42:21.483Z" }, + { url = "https://files.pythonhosted.org/packages/94/e3/7a93e09c9f94e637ca90209ceef0334a9a1d45b0bdb7c92ff922d25d6187/llvmlite-0.48.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7a5c413317050a1d67c34708bde97707f9b2257ef1017f7532d21fe7d9a9ff30", size = 40480654, upload-time = "2026-07-01T18:42:25.076Z" }, + { url = "https://files.pythonhosted.org/packages/27/98/a29133b4728671a175f7d616fab8b1c6e1d8c269d1523581d3160697bfb1/llvmlite-0.48.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d9f952dff6c350c529997423d4fa43abae9722a884ac7ffafc37a3af676e7db", size = 59890119, upload-time = "2026-07-01T18:42:33.88Z" }, + { url = "https://files.pythonhosted.org/packages/1a/cf/7aac11a1f1c7ec54b60c7f6814e87561fb6b55b2f290455d7941eb113420/llvmlite-0.48.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:054aa7d46595935565f276cf0c1659b4f10929c996dd4a606875fae26fba2a23", size = 58343460, upload-time = "2026-07-01T18:42:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/db/41/b96f440c7df5ebba07872cad4e30fbc3560387755b1ea0b629adb76d5ca8/llvmlite-0.48.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d0b3c61aac83b42fb48cc96bffbf57c81b82b2aa92276b7ed6420c814629a99a", size = 42986383, upload-time = "2026-07-01T18:42:37.544Z" }, +] + +[[package]] +name = "numba" +version = "0.66.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llvmlite" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/a0/570e3dc53e5602b49108f62a13e529f1eec8bfc7ef37d49c825924dcf546/numba-0.66.0.tar.gz", hash = "sha256:b900e63a0e26c05ea9a6d5a3a5a0a177cb64c5011887bf43edb8c3ed2c38d363", size = 2806181, upload-time = "2026-07-01T23:12:46.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/02/970796b4daa709604cde22e87a7cda9bde473c278ea4a75f59fe38cee47f/numba-0.66.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:bbd531c327557a9004507fa6bff06c53ab51a7a5776b75261bb9cef1efe2b2ea", size = 2727049, upload-time = "2026-07-01T23:12:11.296Z" }, + { url = "https://files.pythonhosted.org/packages/8c/99/33a6ed9c1a0b5e42efa98eb0edf617d61dca576c82625947377b1d4540c9/numba-0.66.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc6629becb21a867d85401ec89f426dd24c484a4193ade8a38309debfd1529ca", size = 3808870, upload-time = "2026-07-01T23:12:12.944Z" }, + { url = "https://files.pythonhosted.org/packages/04/20/8c51126025211659235b8de2866dfa226984ae0c8273461a3cf374716741/numba-0.66.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aac69f3ccb8af100f5913c1241edc9692bad1cdd2508721713f426eb06c9a659", size = 3514498, upload-time = "2026-07-01T23:12:15.307Z" }, + { url = "https://files.pythonhosted.org/packages/5e/c9/9476940bc6d5caf5c0cf2e4c5feecbf01244bbe6f914614082dd7a3e520e/numba-0.66.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb601841d9e02e6237bb6522e36d0741614be3cfe2b482a6f00a41b5ba209443", size = 2780225, upload-time = "2026-07-01T23:12:16.924Z" }, + { url = "https://files.pythonhosted.org/packages/62/a3/70deb7f88461c1cd5d16aa990c2380604102661a427667b8950dcdccc27f/numba-0.66.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:53ca5900b7cab15109796030113a6b28576bae5ad7bb507ad6dd1360ddd81ba4", size = 2727264, upload-time = "2026-07-01T23:12:18.669Z" }, + { url = "https://files.pythonhosted.org/packages/2d/55/25c319845e9a4e08f16611ddbda56a192eb7b6ed13e1a2bff2da272ffb97/numba-0.66.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0999e3ee1b18c48e1fb51d11af35ef59852c7f4f50569c9550c25faef0616ad1", size = 3866252, upload-time = "2026-07-01T23:12:20.429Z" }, + { url = "https://files.pythonhosted.org/packages/71/ef/a82d6fd6bf1b0fe461651e924d3647eeec9ac17f8eee4896264bf7480930/numba-0.66.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efe0d2d5099790df945e0cb6e1b3104bd965d7bbfac50d62f1d5d1d6ade0825d", size = 3566974, upload-time = "2026-07-01T23:12:22.116Z" }, + { url = "https://files.pythonhosted.org/packages/fc/eb/9e6171e378822ab191c7abcfd3d8cfc8644516f6c7834c22e210e4acc070/numba-0.66.0-cp312-cp312-win_amd64.whl", hash = "sha256:b075a4e7ebc43dc6294f223e2821659656209fd5e0ce53245877c23d66d6e1a9", size = 2797403, upload-time = "2026-07-01T23:12:23.724Z" }, + { url = "https://files.pythonhosted.org/packages/03/52/176c02d005c5c5143cde10a85bbcdcb6236d9e34c3aac089380e0506cd1d/numba-0.66.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:380b2556a2019ccd1e956ae77dd257eaa39403f7520768b626d44b755112785e", size = 2727084, upload-time = "2026-07-01T23:12:25.434Z" }, + { url = "https://files.pythonhosted.org/packages/44/b5/e930010965568fe7f2c6c962fd2849d458cb9f62c3ab7584af8a19a2b40a/numba-0.66.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:939316d5d8619751207b8972a67852b5a7646665298cb4de693cd6bf135152f4", size = 3873663, upload-time = "2026-07-01T23:12:27.308Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ec/5b51457cbe96e4831141d83e892e65191b23a1b78728456c62909d231ace/numba-0.66.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cdf506775d9f02eb92a87bf5c5b1e0d25506fd18cafd769f4ed914a8feac73e7", size = 3573529, upload-time = "2026-07-01T23:12:28.944Z" }, + { url = "https://files.pythonhosted.org/packages/83/7e/cea7710e96913d3c7f2999f16db1b28e6c5be5171cbf40f77f98333a7243/numba-0.66.0-cp313-cp313-win_amd64.whl", hash = "sha256:c5bfe5350284509ab0474390321454c3a8627a188af5b68c910e83df3e2db4a7", size = 2797247, upload-time = "2026-07-01T23:12:30.774Z" }, + { url = "https://files.pythonhosted.org/packages/96/7a/7e0e73550eb4e41ede6e72fb5371f4539537a4d770a3b73fa9b61aea0622/numba-0.66.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:46ae5f2b19e2af3c33c2df100306a90ea2f981c8158b0390f8bf6c20eee7357e", size = 2727296, upload-time = "2026-07-01T23:12:32.39Z" }, + { url = "https://files.pythonhosted.org/packages/0f/26/885774c006de6620ed3d10f45d8e20fe0b8e6aad6d573211a2cbc8b3e528/numba-0.66.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e2b101f23b8b63978d334574d2039f27f0dccfe1d891756f33a2e2f3e4c88cf4", size = 3842720, upload-time = "2026-07-01T23:12:33.938Z" }, + { url = "https://files.pythonhosted.org/packages/93/99/edebf7de890b73973d839dd971cf73734adfb81ffa1b4504f84b9059c3e5/numba-0.66.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:63b943eb2c9ba371908ce2cd6dfc643db51fc40f7966993376a1701bc922f537", size = 3543537, upload-time = "2026-07-01T23:12:35.566Z" }, + { url = "https://files.pythonhosted.org/packages/66/c5/b46ad28ac3681d035ea21365c5e052149062e1a0a9affd0563d2760ea6ff/numba-0.66.0-cp314-cp314-win_amd64.whl", hash = "sha256:bd57790acd20f6a468e0ad333ef6b82355e309a92310fb7dff80e919f01a21a9", size = 2799250, upload-time = "2026-07-01T23:12:37.154Z" }, + { url = "https://files.pythonhosted.org/packages/10/6f/5e77a7397a37dd16f57a7b72e7e470db5227b68e3639df0d13a8e674883d/numba-0.66.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:db7735d15ea17a283d6485b9fa3504769f78fd86e5146638ad5e8da57c031b9e", size = 2730342, upload-time = "2026-07-01T23:12:38.758Z" }, + { url = "https://files.pythonhosted.org/packages/39/fd/e9c9680a3813f3d781c20e5d53c1074801b787d4feecca0472fdd7c05ce1/numba-0.66.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:651a2b53298340956db26ecbe7ab043106b50a40c2807e66d617a4917245a4ab", size = 3878695, upload-time = "2026-07-01T23:12:40.302Z" }, + { url = "https://files.pythonhosted.org/packages/61/3a/9b363287b85fcd4537ea3878793822878b2ac1008a78159d2096fea628de/numba-0.66.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c1144ba1720ea59ad79f4f488ed54d149b2613b357f7e445678b7d0739c70e9", size = 3596323, upload-time = "2026-07-01T23:12:42.805Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f2/dca53d50b8f2289dd01954ace9da261e0487d5b74b188b4304e4ecc3492c/numba-0.66.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d426178fb991a85714c43112a8ea7b9d9579ea856ad8dcdb9c1c3941903ba5be", size = 2804772, upload-time = "2026-07-01T23:12:44.399Z" }, +] + [[package]] name = "numpy" version = "2.4.6" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.12'", -] sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, @@ -96,6 +156,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, @@ -105,39 +186,6 @@ 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" -version = "2.5.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", -] -sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553, upload-time = "2026-07-04T17:08:00.933Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/7b/14687aa674250e5e546f616f486b0d56d3631cd5b2415739141ce40bdcea/numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277", size = 16801574, upload-time = "2026-07-04T17:06:12.423Z" }, - { url = "https://files.pythonhosted.org/packages/e1/19/cc5bb2a3f2913d27d6dbb2c78d25921fabaedc6741d4a5a615a11f3c5bf3/numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1", size = 11772250, upload-time = "2026-07-04T17:06:15.726Z" }, - { url = "https://files.pythonhosted.org/packages/42/77/fdf34a71dd30f54979b18603bee915e0aaf825b07afe79acd60b04b691e2/numpy-2.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0", size = 5331516, upload-time = "2026-07-04T17:06:17.913Z" }, - { url = "https://files.pythonhosted.org/packages/ce/e2/eb7efa015b4cce41e2517bf182a7fce0d7d5b9d9ed76a29bfa0f4fe4505c/numpy-2.5.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e", size = 6664863, upload-time = "2026-07-04T17:06:20.02Z" }, - { url = "https://files.pythonhosted.org/packages/a9/4b/a2b32dd94ee9ffbeecb28152240042a3949db33b1c834d44090b80e1b3b8/numpy-2.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75", size = 15167977, upload-time = "2026-07-04T17:06:21.621Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a9/6e73d68500f80773f65f0654ea932019d6694329a0eb0ed0533de38df376/numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca", size = 16672469, upload-time = "2026-07-04T17:06:24.064Z" }, - { url = "https://files.pythonhosted.org/packages/24/7d/ad3e59015135f5261c95fd4cafeff159c955febd83a99a1d9250c4233815/numpy-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3", size = 16527531, upload-time = "2026-07-04T17:06:26.69Z" }, - { url = "https://files.pythonhosted.org/packages/83/d0/a39b2fbcde9cb17a1dac678f254b33a6336298af9df338824c685425d5e8/numpy-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9", size = 18431940, upload-time = "2026-07-04T17:06:29.521Z" }, - { url = "https://files.pythonhosted.org/packages/04/12/cff070947791c1ed425ff76413189adbdc2fbe215eba7ce7fa454a03c7f8/numpy-2.5.1-cp312-cp312-win32.whl", hash = "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2", size = 6066764, upload-time = "2026-07-04T17:06:32.571Z" }, - { url = "https://files.pythonhosted.org/packages/65/66/53f31807a48a750f9d748da273bc3fcedd12b27ff1f3e373bfec55ef2dc0/numpy-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2", size = 12430966, upload-time = "2026-07-04T17:06:34.926Z" }, - { url = "https://files.pythonhosted.org/packages/2b/2a/d1a88066b1c14186f5d3c0d18c94f17b064511982bab0578d49ee9d43c29/numpy-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b", size = 10350488, upload-time = "2026-07-04T17:06:37.785Z" }, - { url = "https://files.pythonhosted.org/packages/eb/07/ec2a3f0c91761581d4b7104a740791800025983f9a4dc4e73f91a99aeac4/numpy-2.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0bfebd8695f9863592fe744be833a258120b14a9f39da255e8aa8fade2c0ddd1", size = 16796419, upload-time = "2026-07-04T17:06:40.37Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ab/ddb499fc4f8780354395face5b65c7fd107bcd6e1d667a5f07d046956f6f/numpy-2.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30b44a6b53a7ae63c54c089a8726e5563ed302716c5b7ccc85afade40b0e7ff6", size = 11765832, upload-time = "2026-07-04T17:06:42.768Z" }, - { url = "https://files.pythonhosted.org/packages/88/b3/3c28c558a09fc72100c646dac6d2fce8e834c471b0edca01a29996706117/numpy-2.5.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6165343f81b56ef8f514f396989e529b61d9dc709b99421b07e9f3e698e2287d", size = 5325143, upload-time = "2026-07-04T17:06:45.466Z" }, - { url = "https://files.pythonhosted.org/packages/5e/0e/ce19b985bb15c596f4f05954e76cccc77c845083b3b8f938a6c68e523128/numpy-2.5.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4939237038ada79308dda3204ac6462df056b5672b2e25db1149cf873668b3e1", size = 6659749, upload-time = "2026-07-04T17:06:47.288Z" }, - { url = "https://files.pythonhosted.org/packages/2e/20/1ee6614d64332a1bba6411f38e68cb79eec1b2459e20a623777c5c5492a2/numpy-2.5.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c6759f538fb912fc46de0a6b1758ccf7b57bc7c7ebebc23974fdac3de8db0cd", size = 15164716, upload-time = "2026-07-04T17:06:49.494Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a7/2bcd3fdbb87804755c35b729bf8709d62025c5f4cfd7d5b2415997097515/numpy-2.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9726558e8db4a5bf7929a70ae50f63abda4daf0efe810e3bfbab95976f75fc1a", size = 16661440, upload-time = "2026-07-04T17:06:52.061Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d7/a41e3310c886fe457d36e670bbf24fae411aca8a7b6ad92a32afd924077c/numpy-2.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3935f3b419b244a02732676fa5317a9193cc596a4c0646db07e5b421229ac9f7", size = 16526305, upload-time = "2026-07-04T17:06:54.605Z" }, - { url = "https://files.pythonhosted.org/packages/53/75/4333a9a707c1edd3a4e1a0c58eca52c0f31e55089fa80db02b5565b24df7/numpy-2.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc932a65ded7ce9013d120845a2514dcccb1a67bfc8deb8d37633762951904a6", size = 18423008, upload-time = "2026-07-04T17:06:57.54Z" }, - { url = "https://files.pythonhosted.org/packages/ee/90/e314a32b1c11a2ffe818ddad3a57b50b4b6e1b6c487192eb50cdef0415d0/numpy-2.5.1-cp313-cp313-win32.whl", hash = "sha256:4b4ff1608417eb7a59da7b967bbb798cacfe071d2caf526a24281cd562072ed9", size = 6063885, upload-time = "2026-07-04T17:07:00.14Z" }, - { url = "https://files.pythonhosted.org/packages/10/70/800b3fca480af32df9e8ea9f3d4a0c8feb4b32d7f195d174eabbda4829ad/numpy-2.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:6c3fe51bc6a16453d452997053454f309e8e0ed7b42d6b361ce4ac8c32913d74", size = 12425674, upload-time = "2026-07-04T17:07:02.387Z" }, - { url = "https://files.pythonhosted.org/packages/8b/0b/196350c122f50f6ca56846f2d71efd5e0d24b7b2e07355e019b2e2c7a11e/numpy-2.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f7feb014281029e628ba2d5a007407443b06e418b6fe451d1e2adcbc8eba0107", size = 10350256, upload-time = "2026-07-04T17:07:04.878Z" }, -] - [[package]] name = "packaging" version = "26.2"