diff --git a/.gitignore b/.gitignore index 7e03976..b40a96d 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ # Co-sim intermediate CSVs (regenerated by scripts) 9_Firmware/9_2_FPGA/tb/cosim/rtl_doppler_*.csv +9_Firmware/9_2_FPGA/tb/cosim/mf_chain_autocorr.csv 9_Firmware/9_2_FPGA/tb/cosim/compare_doppler_*.csv 9_Firmware/9_2_FPGA/tb/cosim/rtl_multiseg_*.csv diff --git a/5_Simulations/Antenna/aperture_coupled_aeris10_v2.py b/5_Simulations/Antenna/aperture_coupled_aeris10_v2.py index 91da5f5..aa0deed 100644 --- a/5_Simulations/Antenna/aperture_coupled_aeris10_v2.py +++ b/5_Simulations/Antenna/aperture_coupled_aeris10_v2.py @@ -432,7 +432,7 @@ print("=" * 70) print(f" Resonance (Im(Zin)=0): {f_res/1e9:.3f} GHz (target 10.5 GHz)") print(f" S11 at resonance : {s11_min:.2f} dB") print(f" Zin at resonance : {zin[i_res].real:.1f} + j{zin[i_res].imag:.1f} Ω") -print(f" ── at 10.500 GHz exactly:") +print(" ── at 10.500 GHz exactly:") print(f" S11 @ 10.5GHz : {s11_dB[i_op]:.2f} dB") print(f" Zin @ 10.5GHz : {zin[i_op].real:.1f} + j{zin[i_op].imag:.1f} Ω") print(f" VSWR @ 10.5GHz : {vswr[i_op]:.2f}") diff --git a/5_Simulations/Antenna/array_factor_adar1000_aeris10.py b/5_Simulations/Antenna/array_factor_adar1000_aeris10.py index 3074a11..e992acd 100644 --- a/5_Simulations/Antenna/array_factor_adar1000_aeris10.py +++ b/5_Simulations/Antenna/array_factor_adar1000_aeris10.py @@ -261,11 +261,8 @@ def main(): peak_dB_co = [] actual_peak_fw = [] actual_peak_co = [] - # Reference broadside peak for absolute scan-loss - ref_pat = total_pattern_dB(theta_deg, np.zeros(N_TOTAL, dtype=int), h_pat_lin) - # Find peak amplitude (ref) on linear scale by recomputing without normalisation - # Use a simpler proxy: peak in dB rel peak is 0; we want amplitude relative to - # broadside. Compute |E|² broadside vs scanned without normalisation. + # Reference broadside peak for absolute scan-loss — peak in dB rel peak is 0; + # we want amplitude relative to broadside, so compute |E|² without normalisation. def total_amp_lin(theta_deg_arr, phase_codes): af = array_factor_hplane(theta_deg_arr, phase_codes) @@ -374,7 +371,6 @@ def main(): phase_shift = (2 * np.pi * D_X * np.sin(angle_rad)) / LAMBDA phases_continuous = np.array([(n*phase_shift) % (2*np.pi) for n in range(N_TOTAL)]) codes_quant = correct_phase_codes(ang) - phases_quant = codes_to_radians(codes_quant) def total_pattern_dB_continuous(theta_deg_arr, phases_rad, h_pat_lin): k = 2*np.pi/LAMBDA @@ -409,9 +405,9 @@ def main(): # = 4d = 2λ → grating lobes at sin(θ_g) = ±λ/(4d) ± sin(θ_0) = ±0.5 ± sin(θ_0). print() print(" TEST 3: Grating-lobe geometry") - print(f" Element pitch d = λ/2 → no real-space grating lobes at any scan ✓") - print(f" Firmware's 4-elem broadcast → super-pitch d_super = 4d = 2λ") - print(f" → grating lobes appear at sin(θ_g) = ±0.5 ± sin(θ_0)") + print(" Element pitch d = λ/2 → no real-space grating lobes at any scan ✓") + print(" Firmware's 4-elem broadcast → super-pitch d_super = 4d = 2λ") + print(" → grating lobes appear at sin(θ_g) = ±0.5 ± sin(θ_0)") for ang in [0, 15, 30, 45]: sin0 = np.sin(np.deg2rad(ang)) gl = [] diff --git a/5_Simulations/Antenna/edge_fed_aeris10_v3.py b/5_Simulations/Antenna/edge_fed_aeris10_v3.py index 31ca477..9f5a952 100644 --- a/5_Simulations/Antenna/edge_fed_aeris10_v3.py +++ b/5_Simulations/Antenna/edge_fed_aeris10_v3.py @@ -267,7 +267,7 @@ print(f" Edge-fed (inset) on 0.508 mm RO4350B (W={PATCH_W} L={PATCH_L} inset={I print(f" Resonance (R peak + Im=0): {f_res/1e9:.3f} GHz (target 10.5 GHz)") print(f" S11 at resonance : {s11_min:.2f} dB") print(f" Zin at resonance : {zin[i_res].real:.1f} + j{zin[i_res].imag:+.1f} Ω") -print(f" ── at 10.500 GHz exactly:") +print(" ── at 10.500 GHz exactly:") print(f" S11 @ 10.5GHz : {s11_dB[i_op]:.2f} dB") print(f" Zin @ 10.5GHz : {zin[i_op].real:.1f} + j{zin[i_op].imag:+.1f} Ω") print(f" VSWR @ 10.5GHz : {vswr[i_op]:.2f}") @@ -286,7 +286,7 @@ if (f_hi-f_lo) > 0: label=f"BW {(f_hi-f_lo)/1e6:.0f} MHz ({bw_pct:.2f}%)") ax.set_xlabel("Frequency (GHz)") ax.set_ylabel("S11 (dB)") -ax.set_title(f"AERIS-10 Edge-Fed (inset) — 2-layer 0.508 mm RO4350B") +ax.set_title("AERIS-10 Edge-Fed (inset) — 2-layer 0.508 mm RO4350B") ax.set_xlim(F_START/1e9, F_STOP/1e9) ax.set_ylim(-40, 0) ax.grid(True, alpha=0.3) diff --git a/5_Simulations/Antenna/edge_fed_row_aeris10_v3.py b/5_Simulations/Antenna/edge_fed_row_aeris10_v3.py index 0df6803..dca1d28 100644 --- a/5_Simulations/Antenna/edge_fed_row_aeris10_v3.py +++ b/5_Simulations/Antenna/edge_fed_row_aeris10_v3.py @@ -311,7 +311,7 @@ print(f" W={PATCH_W} L={PATCH_L} inset={INSET_DEPTH}/{INSET_GAP} " f"conn={CONN_LEN} pitch={PITCH:.2f}") print(f" Operating mode (nearest 10.5 GHz): {f_res/1e9:.3f} GHz, {s11_min:.2f} dB") print(f" Zin at op mode : {zin[i_res].real:.1f} + j{zin[i_res].imag:+.1f} Ω") -print(f" ── at 10.500 GHz exactly:") +print(" ── at 10.500 GHz exactly:") print(f" S11 @ 10.5GHz : {s11_dB[i_op]:.2f} dB") print(f" Zin @ 10.5GHz : {zin[i_op].real:.1f} + j{zin[i_op].imag:+.1f} Ω") print(f" VSWR @ 10.5GHz : {vswr[i_op]:.2f}") diff --git a/5_Simulations/Antenna/edge_fed_row_nf2ff_aeris10_v3.py b/5_Simulations/Antenna/edge_fed_row_nf2ff_aeris10_v3.py index f042c03..f6e1633 100644 --- a/5_Simulations/Antenna/edge_fed_row_nf2ff_aeris10_v3.py +++ b/5_Simulations/Antenna/edge_fed_row_nf2ff_aeris10_v3.py @@ -261,11 +261,11 @@ bw_h = beamwidth_3dB(theta_deg, pat_dB_h_norm, i_pk_h) print() print("=" * 78) print(f" Far-field NORMALISED pattern at f = {F_TX/1e9:.3f} GHz") -print(f" ── E-plane (φ=90°, along array axis y — array factor lives here) ──") +print(" ── E-plane (φ=90°, along array axis y — array factor lives here) ──") print(f" Broadside (θ=0°) level : {pat_dB_e_norm[i_bs]:.2f} dB (rel peak)") print(f" Peak direction : θ = {theta_deg[i_pk_e]:+.1f}° (peak = {pat_dB_e_norm[i_pk_e]:.2f} dB)") print(f" -3 dB beamwidth : {bw_e:.1f}°") -print(f" ── H-plane (φ=0°, perpendicular to array) ──") +print(" ── H-plane (φ=0°, perpendicular to array) ──") print(f" Broadside (θ=0°) level : {pat_dB_h_norm[i_bs]:.2f} dB (rel peak)") print(f" Peak direction : θ = {theta_deg[i_pk_h]:+.1f}° (peak = {pat_dB_h_norm[i_pk_h]:.2f} dB)") print(f" -3 dB beamwidth : {bw_h:.1f}°") @@ -279,13 +279,13 @@ print("=" * 78) # Plot (normalized to peak = 0 dB) fig, axes = plt.subplots(1, 2, figsize=(14, 5)) for ax, pat_dB, title, peak_deg in [ - (axes[0], pat_dB_h_norm, f"H-plane (φ=0°, xz cut, perp. to array)", + (axes[0], pat_dB_h_norm, "H-plane (φ=0°, xz cut, perp. to array)", theta_deg[i_pk_h]), - (axes[1], pat_dB_e_norm, f"E-plane (φ=90°, yz cut, ALONG array — array factor)", + (axes[1], pat_dB_e_norm, "E-plane (φ=90°, yz cut, ALONG array — array factor)", theta_deg[i_pk_e]), ]: ax.plot(theta_deg, pat_dB, "b-", lw=1.6) - ax.axvline(0, color="r", ls=":", lw=0.8, label=f"broadside (θ=0°)") + ax.axvline(0, color="r", ls=":", lw=0.8, label="broadside (θ=0°)") if abs(peak_deg) > 1.0: ax.axvline(peak_deg, color="g", ls=":", lw=0.8, label=f"peak at θ={peak_deg:+.1f}°") diff --git a/5_Simulations/Antenna/probe_fed_aeris10_v3.py b/5_Simulations/Antenna/probe_fed_aeris10_v3.py index 3483f17..03e2d59 100644 --- a/5_Simulations/Antenna/probe_fed_aeris10_v3.py +++ b/5_Simulations/Antenna/probe_fed_aeris10_v3.py @@ -49,7 +49,6 @@ # FEED_X_MM (default 0; offset along W-axis, normally 0 for centred feed) import os -import sys import time import csv import numpy as np @@ -277,7 +276,7 @@ print("=" * 70) print(f" Resonance (R peak + Im=0): {f_res/1e9:.3f} GHz (target 10.5 GHz)") print(f" S11 at resonance : {s11_min:.2f} dB") print(f" Zin at resonance : {zin[i_res].real:.1f} + j{zin[i_res].imag:.1f} Ω") -print(f" ── at 10.500 GHz exactly:") +print(" ── at 10.500 GHz exactly:") print(f" S11 @ 10.5GHz : {s11_dB[i_op]:.2f} dB") print(f" Zin @ 10.5GHz : {zin[i_op].real:.1f} + j{zin[i_op].imag:.1f} Ω") print(f" VSWR @ 10.5GHz : {vswr[i_op]:.2f}") diff --git a/5_Simulations/Antenna/probe_fed_array_aeris10_v3.py b/5_Simulations/Antenna/probe_fed_array_aeris10_v3.py index 21564b3..d095378 100644 --- a/5_Simulations/Antenna/probe_fed_array_aeris10_v3.py +++ b/5_Simulations/Antenna/probe_fed_array_aeris10_v3.py @@ -36,7 +36,6 @@ # coupling_grid.png — heatmap of |S_jd| dB at 10.5 GHz across array import os -import sys import time import csv import numpy as np @@ -314,7 +313,7 @@ for (i, j) in sorted(DRIVEN_SET): if N_DRIVEN > 1: print() - print(f" Sub-array uniformity:") + print(" Sub-array uniformity:") print(f" S11 min/max/avg : {min(S11_at_op):>6.2f} / {max(S11_at_op):>6.2f} / " f"{sum(S11_at_op)/N_DRIVEN:>6.2f} dB") R_vals = [z.real for z in zin_at_op] @@ -325,7 +324,7 @@ if N_DRIVEN > 1: f"{sum(X_vals)/N_DRIVEN:+5.1f} Ω") # Average port (what the ADAR channel "sees" through ideal 1:8 splitter) Z_avg = sum(zin_at_op) / N_DRIVEN - print(f" Z avg (= what ADAR channel sees through ideal 1:8 splitter):") + print(" Z avg (= what ADAR channel sees through ideal 1:8 splitter):") print(f" Z = {Z_avg.real:.1f} + j{Z_avg.imag:+.1f} Ω, " f"VSWR = {abs((Z_avg-50)/(Z_avg+50)) and (1+abs((Z_avg-50)/(Z_avg+50)))/(1-abs((Z_avg-50)/(Z_avg+50))):.2f}") diff --git a/5_Simulations/Antenna/production_beams_verify_aeris10.py b/5_Simulations/Antenna/production_beams_verify_aeris10.py index 48cc6c1..3d8cbc0 100644 --- a/5_Simulations/Antenna/production_beams_verify_aeris10.py +++ b/5_Simulations/Antenna/production_beams_verify_aeris10.py @@ -257,24 +257,24 @@ def main(): label_at_bp7 = expected_angle(pd_at_bp7) pk_at_bp7 = analyse_beam(theta_deg, total_pattern_dB(theta_deg, matrix1[7], h_pat_lin)[0])[0] print("2) Sign convention check (matrix1[bp=7], phase_diff = +20°):") - print(f" Comment in main.cpp says \"positive steering angles\".") - print(f" Math says positive phase_diff steers to NEGATIVE θ.") + print(" Comment in main.cpp says \"positive steering angles\".") + print(" Math says positive phase_diff steers to NEGATIVE θ.") print(f" Sim peak θ = {pk_at_bp7:+.1f}° (predicted {label_at_bp7:+.1f}°).") if pk_at_bp7 * 1.0 < 0: - print(f" → Comment is MISLEADING: matrix1 actually steers to NEGATIVE elevations.") + print(" → Comment is MISLEADING: matrix1 actually steers to NEGATIVE elevations.") else: - print(f" → Sim agrees with comment (positive steering).") + print(" → Sim agrees with comment (positive steering).") print() # Symmetry / asymmetry print("3) Symmetry of matrix1[bp] vs matrix2[bp]:") print(f" At bp=0: matrix1 → {iter_pos_pks[0]:+5.1f}°, matrix2 → {iter_neg_pks[0]:+5.1f}°") print(f" At bp=14: matrix1 → {iter_pos_pks[14]:+5.1f}°, matrix2 → {iter_neg_pks[14]:+5.1f}°") if abs(abs(iter_pos_pks[0]) - abs(iter_neg_pks[0])) > 5: - print(f" → ASYMMETRIC: matrix1[bp] and matrix2[bp] are NOT mirror images.") - print(f" → Likely indexing intent: matrix2 should use phase_differences[30 - bp]") - print(f" (mirror), not phase_differences[bp + 16] (current).") + print(" → ASYMMETRIC: matrix1[bp] and matrix2[bp] are NOT mirror images.") + print(" → Likely indexing intent: matrix2 should use phase_differences[30 - bp]") + print(" (mirror), not phase_differences[bp + 16] (current).") else: - print(f" → Symmetric.") + print(" → Symmetric.") print() # Coverage all_pks = sorted(iter_pos_pks + [pk0] + iter_neg_pks) @@ -285,20 +285,20 @@ def main(): f"and {all_pks[int(np.argmax(gaps))+1]:+.1f}°") print(f" Smallest gap: {min(g for g in gaps if g > 0.1):.2f}° " f"(near broadside — heavily oversampled)") - print(f" 1/n distribution → dense near broadside, sparse at large angles.") + print(" 1/n distribution → dense near broadside, sparse at large angles.") print() # SLL at extreme scan pk_extreme = max(rows, key=lambda r: abs(r[3] or 0)) print(f"5) Worst-case SLL at max scan: bp={pk_extreme[0]}, " f"matrix1 peak={pk_extreme[3]:+.1f}°, SLL={pk_extreme[5]:+.1f} dB") if pk_extreme[5] > -10: - print(f" → SLL exceeds -10 dB at extreme scan. Significant scan loss + " - f"degraded sidelobe rejection (expected at near-grazing scan).") + print(" → SLL exceeds -10 dB at extreme scan. Significant scan loss + " + "degraded sidelobe rejection (expected at near-grazing scan).") print() - print(f"6) setBeamAngle() (the 4-broadcast bug we found earlier) is DEAD CODE") - print(f" in production. main.cpp uses initializeBeamMatrices() +") - print(f" setCustomBeamPattern16() exclusively. Fixing setBeamAngle() has zero") - print(f" risk of regressing production behaviour.") + print("6) setBeamAngle() (the 4-broadcast bug we found earlier) is DEAD CODE") + print(" in production. main.cpp uses initializeBeamMatrices() +") + print(" setCustomBeamPattern16() exclusively. Fixing setBeamAngle() has zero") + print(" risk of regressing production behaviour.") if __name__ == "__main__": diff --git a/9_Firmware/9_2_FPGA/tb/cosim/compare_independent.py b/9_Firmware/9_2_FPGA/tb/cosim/compare_independent.py index 1158560..e58f0cc 100644 --- a/9_Firmware/9_2_FPGA/tb/cosim/compare_independent.py +++ b/9_Firmware/9_2_FPGA/tb/cosim/compare_independent.py @@ -45,7 +45,13 @@ try: import numpy as np except ImportError: _MISSING.append("numpy") -if importlib.util.find_spec("scipy.signal") is None: +# find_spec("scipy.signal") raises ModuleNotFoundError if the parent package +# scipy is itself absent (rather than returning None). Wrap in try/except so +# the regression runner gets a clean rc=2 SKIP instead of an uncaught crash. +try: + if importlib.util.find_spec("scipy.signal") is None: + _MISSING.append("scipy") +except ModuleNotFoundError: _MISSING.append("scipy") if _MISSING: print( diff --git a/pyproject.toml b/pyproject.toml index 096b43a..2c9bc17 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,3 +57,20 @@ select = [ "v7/hardware.py" = ["F401"] # FPGA cosim scripts: CLI tools — print() is the intended output channel "9_Firmware/9_2_FPGA/tb/cosim/**.py" = ["T20"] +# Antenna sim/verification scripts: engineering-analysis ergonomics. print() +# is the intended output channel; module-header docstrings include "How to +# run" shell examples; comments/labels use Greek letters (µ, λ, π, θ, °) for +# physical units; matplotlib/numpy idioms produce long lines and multi-stmt +# constructs. Keep substantive bug rules (F unused-imports/vars, B core +# bugbears) — drop stylistic noise. +"5_Simulations/Antenna/**.py" = [ + "T20", "ERA", # print + "How to run" header examples + "RUF001","RUF002","RUF003", # Greek letters (µ, λ, π, θ) in strings/comments + "RUF005","RUF015","RUF046","RUF059", # collection/iteration ergonomics + "E501", # long matplotlib/numpy lines + "E701","E702", # one-line setup statements + "B007","B905","BLE001", # unused loop var, zip strict, broad-except diag + "C401","RET504","SIM118", # generator/in-dict ergonomics + "PERF401","PERF402", # comprehension perf hints + "ARG001","E402", # kwargs API + post-import-guard ordering +] diff --git a/uv.lock b/uv.lock index be2432a..38497a5 100644 --- a/uv.lock +++ b/uv.lock @@ -13,6 +13,7 @@ dev = [ { name = "numpy" }, { name = "pytest" }, { name = "ruff" }, + { name = "scipy" }, ] [package.metadata] @@ -23,6 +24,7 @@ dev = [ { name = "numpy", specifier = ">=1.26" }, { name = "pytest", specifier = ">=8" }, { name = "ruff", specifier = ">=0.5" }, + { name = "scipy", specifier = ">=1.13" }, ] [[package]] @@ -214,3 +216,64 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/5e/c927b325bd4c1d3620211a4b96f47864633199feed60fa936025ab27e090/ruff-0.15.11-py3-none-win_amd64.whl", hash = "sha256:8b6756d88d7e234fb0c98c91511aae3cd519d5e3ed271cae31b20f39cb2a12a3", size = 11779692 }, { url = "https://files.pythonhosted.org/packages/63/b6/aeadee5443e49baa2facd51131159fd6301cc4ccfc1541e4df7b021c37dd/ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4", size = 11032614 }, ] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822 } +wheels = [ + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682 }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943 }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980 }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984 }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327 }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165 }, +]