diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl new file mode 100644 index 0000000..f35bc45 --- /dev/null +++ b/.beads/issues.jsonl @@ -0,0 +1,40 @@ +{"id":"Python-VPP-1yf","title":"Tactical statistics and race narrative","description":"Track and display tactical events during races: (1) how many tacks/gybes cost net time vs gained position, (2) dirty air encounters and their duration, (3) split vs cover decisions and outcomes, (4) layline arrival timing. Show as a summary table and optionally annotate the course trace plot with tactical events. Helps answer 'did the trailing boat's split strategy pay off?'","status":"closed","priority":3,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T22:45:23.0688536Z","created_by":"Thomas Dickson","updated_at":"2026-03-02T07:30:37.698199Z","closed_at":"2026-03-02T07:30:37.698199Z","close_reason":"Added tactical stats tracking: shadow_seconds, shadow_encounters per boat in run_single(), and tactics_summary in run_monte_carlo()."} +{"id":"Python-VPP-2ko","title":"Validate VPP predictions against real Daring race data","description":"If actual Daring race results or GPS tracks are available, compare VPP predictions against them to validate the model.","status":"closed","priority":3,"issue_type":"task","owner":"tajdickson@protonmail.com","created_at":"2026-02-27T23:10:19.7556293Z","created_by":"Thomas Dickson","updated_at":"2026-03-02T07:37:32.5413026Z","closed_at":"2026-03-02T07:37:32.5413026Z","close_reason":"No real Daring race data (GPS tracks or race results) exists in the repository. Only cached VPP polars are available. Deferring until actual race data is obtained for validation."} +{"id":"Python-VPP-4pk","title":"Parallelise TWS/TWA grid computation","description":"The outer TWS/TWA/sail loop in VPPMod.run() solves ~1,170 independent equilibrium points sequentially. Parallelise using multiprocessing.Pool or concurrent.futures.ProcessPoolExecutor. Requires refactoring to make state thread-safe — currently mutates self.aero.sails per sail config. Expected 4-8x speedup on multi-core machines. This is the single biggest performance lever.","status":"closed","priority":1,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T22:39:35.0822337Z","created_by":"Thomas Dickson","updated_at":"2026-03-02T07:23:10.6007035Z","closed_at":"2026-03-02T07:23:10.6007035Z","close_reason":"Added method='parallel' using ProcessPoolExecutor. Each grid point solved independently with its own AeroMod/HydroMod. Fixed Appendage lambda pickling issue. Results match iterative exactly.","dependencies":[{"issue_id":"Python-VPP-4pk","depends_on_id":"Python-VPP-g6c","type":"blocks","created_at":"2026-03-01T22:39:41.0146331Z","created_by":"Thomas Dickson"},{"issue_id":"Python-VPP-4pk","depends_on_id":"Python-VPP-566","type":"blocks","created_at":"2026-03-01T22:39:41.1562398Z","created_by":"Thomas Dickson"}]} +{"id":"Python-VPP-566","title":"LRU caching for force model evaluations","description":"Add functools.lru_cache or manual caching to: (1) HydroMod._get_Rr() resistance interpolator results keyed on (fn, btr, lvr), (2) AeroMod sail coefficient lookups keyed on (awa, flat), (3) wind triangle results keyed on (vb, tws, twa). The 5-DOF method already has a force cache — extend similar caching to the iterative method. Expected ~20% speedup.","status":"closed","priority":2,"issue_type":"task","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T22:39:35.0822337Z","created_by":"Thomas Dickson","updated_at":"2026-03-01T22:55:42.984733Z","closed_at":"2026-03-01T22:55:42.984733Z","close_reason":"Added lru_cache to wind_triangle(), eliminated redundant sail.cl()/cd() calls in _get_coeffs() by caching per-sail values."} +{"id":"Python-VPP-5hv","title":"Add ShortKeel appendage subclass","status":"closed","priority":2,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-02-28T09:31:10.1166279Z","created_by":"Thomas Dickson","updated_at":"2026-02-28T09:33:06.1071542Z","closed_at":"2026-02-28T09:33:06.1071542Z","close_reason":"Implemented ShortKeel class, updated API/tests/UI/runDaring"} +{"id":"Python-VPP-61v","title":"Add help descriptions to Compare page input parameters","description":"Add Streamlit help= tooltips to all input fields on the Compare page config tabs. Same parameter set as VPP page (yacht, keel, rudder, sails) but rendered via render_config_tab(). Since both pages share the same fields, the help text definitions should live in a shared location (e.g. a dict in utils.py).","status":"closed","priority":3,"issue_type":"task","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T22:32:53.383541Z","created_by":"Thomas Dickson","updated_at":"2026-03-02T07:35:30.2263522Z","closed_at":"2026-03-02T07:35:30.2263522Z","close_reason":"Applied FIELD_HELP tooltips to Compare page render_config_tab().","dependencies":[{"issue_id":"Python-VPP-61v","depends_on_id":"Python-VPP-u1u","type":"blocks","created_at":"2026-03-01T22:33:06.0029039Z","created_by":"Thomas Dickson"}]} +{"id":"Python-VPP-63r","title":"Phase 1: Replace NLopt with scipy 5-DOF optimizer","status":"closed","priority":1,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-02-28T13:36:33.4610921Z","created_by":"Thomas Dickson","updated_at":"2026-02-28T13:51:53.1686105Z","closed_at":"2026-02-28T13:51:53.1686105Z","close_reason":"Implemented scipy 5-DOF optimizer and sail coefficient improvements"} +{"id":"Python-VPP-6jk","title":"Eliminate draws in match race simulation","description":"Match race simulation can produce draws which is unrealistic — in real racing there's always a winner. Investigate root cause: likely both boats following identical optimal VMG angles with identical wind. Potential fixes: (1) add minimum trim noise by default so boats always diverge, (2) use sub-second timing resolution, (3) add micro-random positioning offsets at start, (4) tiebreak by closest-to-mark at any point. A draw should be extremely rare, not common.","status":"closed","priority":2,"issue_type":"bug","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T22:39:36.4372036Z","created_by":"Thomas Dickson","updated_at":"2026-03-01T22:46:18.6947051Z","closed_at":"2026-03-01T22:46:18.6947051Z","close_reason":"Fixed with pin-win start offset and fractional crossing time"} +{"id":"Python-VPP-6mm","title":"Improve match race plot quality and differentiation","description":"Match race pyplot figures need better visual differentiation: (1) Use distinct markers for each boat on course trace plot (e.g. boat A = circles, boat B = triangles), (2) Add mark/buoy positions to trace plot, (3) Improve figure sizing and DPI for Streamlit rendering, (4) Add grid lines, (5) Consider different line styles (solid vs dashed). Current plots use only colour to distinguish boats which is insufficient for colourblind users and small screens.","status":"closed","priority":2,"issue_type":"task","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T22:39:36.4500919Z","created_by":"Thomas Dickson","updated_at":"2026-03-01T22:48:33.4809759Z","closed_at":"2026-03-01T22:48:33.4809759Z","close_reason":"Improved all three plot functions with distinct markers, line styles, marks, grid, and higher DPI"} +{"id":"Python-VPP-6nv","title":"Tune depowering steps for finer flat/red optimisation","description":"Currently only flat=0.95 kicks in at 10 kts. The iteration could be more granular to find the optimal flat/red combination that maximises speed while respecting the heel limit.","status":"closed","priority":2,"issue_type":"task","owner":"tajdickson@protonmail.com","created_at":"2026-02-27T23:10:17.0270695Z","created_by":"Thomas Dickson","updated_at":"2026-02-28T13:18:09.2072956Z","closed_at":"2026-02-28T13:18:09.2072956Z","close_reason":"Finer flat/RED steps (0.02/0.1), 1-degree margin check so depowering progresses through both stages"} +{"id":"Python-VPP-82s","title":"Step 5: Match Race UI tab","status":"closed","priority":2,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T21:57:56.9379526Z","created_by":"Thomas Dickson","updated_at":"2026-03-01T22:21:01.0881893Z","closed_at":"2026-03-01T22:21:01.0881893Z","close_reason":"Closed"} +{"id":"Python-VPP-8d9","title":"Add ORC spinnaker variants and CL_low sail data files","status":"closed","priority":2,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-02-28T14:49:41.2044759Z","created_by":"Thomas Dickson","updated_at":"2026-03-01T21:24:28.7901334Z","closed_at":"2026-03-01T21:24:28.7901334Z","close_reason":"Closed"} +{"id":"Python-VPP-9dc","title":"Tactical imperfection model","description":"Add configurable probability of making suboptimal tactical decisions (wrong tack call, delayed reaction). Parameter: tactical_error_rate (default 0). Configurable from Match Race dashboard.","status":"open","priority":4,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T22:14:32.2363068Z","created_by":"Thomas Dickson","updated_at":"2026-03-01T22:14:32.2363068Z"} +{"id":"Python-VPP-9pg","title":"Pluggable WindModel API for race simulation","description":"Refactor wind state out of Race into a pluggable WindModel class hierarchy. Base class with update(dt, rng) -\u003e (tws, twd). Implementations: ConstantWind, BrownianWind, MeanRevertingWind, etc. Designed for future extension to full wind field simulation (spatial variation, gusts, thermal effects). Must be serialisable for reproducibility.","status":"closed","priority":2,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T22:14:34.5950703Z","created_by":"Thomas Dickson","updated_at":"2026-03-01T22:21:01.0552157Z","closed_at":"2026-03-01T22:21:01.0552157Z","close_reason":"Closed"} +{"id":"Python-VPP-a33","title":"Add help descriptions to Match Race page input parameters","description":"Add Streamlit help= tooltips to Match Race page inputs: boat config fields (shared with VPP/Compare), race parameters (leg distance, tack/gybe penalties), wind model parameters (sigma, mean-reversion), and stochastic parameters (trim noise, penalty std). Some sliders already have help text — ensure all do.","status":"closed","priority":3,"issue_type":"task","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T22:32:56.289884Z","created_by":"Thomas Dickson","updated_at":"2026-03-02T07:35:30.3680098Z","closed_at":"2026-03-02T07:35:30.3680098Z","close_reason":"Applied FIELD_HELP tooltips to Match Race page render_boat_config() and added help= to all race/wind/stochastic sliders.","dependencies":[{"issue_id":"Python-VPP-a33","depends_on_id":"Python-VPP-u1u","type":"blocks","created_at":"2026-03-01T22:33:06.1465091Z","created_by":"Thomas Dickson"}]} +{"id":"Python-VPP-a5i","title":"Model fin keel instead of bulbous keel","description":"Investigate replacing the current bulbous keel model with a fin keel model. Review hydrodynamic differences and what changes are needed in the VPP.","status":"closed","priority":2,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-02-27T23:09:43.7331571Z","created_by":"Thomas Dickson","updated_at":"2026-02-28T13:03:25.1679686Z","closed_at":"2026-02-28T13:03:25.1679686Z","close_reason":"Covered by ShortKeel appendage implementation"} +{"id":"Python-VPP-ak2","title":"Support multiple sail types per section","status":"open","priority":2,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-03-02T22:07:49.46987Z","created_by":"Thomas Dickson","updated_at":"2026-03-02T22:07:49.46987Z"} +{"id":"Python-VPP-c0k","title":"Add error handling to API endpoint","status":"closed","priority":1,"issue_type":"bug","owner":"tajdickson@protonmail.com","created_at":"2026-02-28T08:29:56.9094414Z","created_by":"Thomas Dickson","updated_at":"2026-02-28T08:33:01.6422404Z","closed_at":"2026-02-28T08:33:01.6422404Z","close_reason":"Closed"} +{"id":"Python-VPP-ccv","title":"Trim/crew error noise model","description":"Add configurable Gaussian noise on achieved boat speed to simulate imperfect sail trim. Parameter: trim_sigma (default 0). bs *= (1 - trim_sigma * abs(N(0,1))). Each boat gets independent noise per timestep. Configurable from Match Race dashboard.","status":"closed","priority":3,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T22:14:25.6656283Z","created_by":"Thomas Dickson","updated_at":"2026-03-01T22:30:33.4419063Z","closed_at":"2026-03-01T22:30:33.4419063Z","close_reason":"Implemented in RaceMod (trim_sigma, tack_penalty_std, gybe_penalty_std) and WindMod (MeanRevertingWind tws_sigma), all configurable from Match Race UI"} +{"id":"Python-VPP-cza","title":"Handle error responses in Streamlit frontend","status":"closed","priority":1,"issue_type":"bug","owner":"tajdickson@protonmail.com","created_at":"2026-02-28T08:29:57.7088057Z","created_by":"Thomas Dickson","updated_at":"2026-02-28T08:33:01.6453294Z","closed_at":"2026-02-28T08:33:01.6453294Z","close_reason":"Closed"} +{"id":"Python-VPP-dk6","title":"Replace matplotlib plots with interactive Plotly charts","description":"Replace static matplotlib polar plots and charts across all Streamlit pages (VPP, Compare, Match Race) with interactive Plotly figures. This enables zoom, hover tooltips, and better UX in the browser.","status":"open","priority":1,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-03-02T22:11:53.508841Z","created_by":"Thomas Dickson","updated_at":"2026-03-02T22:11:53.508841Z"} +{"id":"Python-VPP-ebm","title":"Step 4: Match racing simulation engine","status":"closed","priority":2,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T21:57:56.3664394Z","created_by":"Thomas Dickson","updated_at":"2026-03-01T22:12:26.408006Z","closed_at":"2026-03-01T22:12:26.408006Z","close_reason":"Closed"} +{"id":"Python-VPP-g6c","title":"Analytical wind triangle solution","description":"Replace fsolve numerical root finder in AeroMod._update_windTriangle() with closed-form law-of-cosines solution. Eliminates ~1-2ms per force evaluation across thousands of calls. Currently uses scipy.optimize.fsolve to solve: awa_residual = vb*sin(awa) - tws*sin(twa-awa). This has an analytical solution via the velocity triangle.","status":"closed","priority":2,"issue_type":"task","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T22:39:35.092081Z","created_by":"Thomas Dickson","updated_at":"2026-03-01T22:52:21.4622884Z","closed_at":"2026-03-01T22:52:21.4622884Z","close_reason":"Replaced fsolve with analytical law-of-cosines solution in wind_triangle(). Removed scipy.optimize.fsolve dependency from AeroMod."} +{"id":"Python-VPP-got","title":"Phase 2: Sail coefficient improvements","status":"closed","priority":1,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-02-28T13:36:37.1890968Z","created_by":"Thomas Dickson","updated_at":"2026-03-01T21:30:45.6376086Z","closed_at":"2026-03-01T21:30:45.6376086Z","close_reason":"All Phase 2 work complete: cubic splines, pluggable data sources, user-loadable polars, ORC sail type variants with UI selectors and API tests"} +{"id":"Python-VPP-jbh","title":"Step 2: Added resistance in waves","status":"closed","priority":2,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T21:57:54.642162Z","created_by":"Thomas Dickson","updated_at":"2026-03-01T22:01:36.3204872Z","closed_at":"2026-03-01T22:01:36.3204872Z","close_reason":"Closed"} +{"id":"Python-VPP-kqr","title":"Expose flat/red values in Streamlit UI and polar plots","description":"The flat/red depowering values are stored in the results array but not surfaced in the Streamlit UI or polar plots. Make them visible so users can see what depowering is being applied.","status":"closed","priority":2,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-02-27T23:10:18.570781Z","created_by":"Thomas Dickson","updated_at":"2026-02-28T08:37:09.196496Z","closed_at":"2026-02-28T08:37:09.196496Z","close_reason":"Added depowering polar plots (Flat \u0026 RED) and data table to Streamlit UI"} +{"id":"Python-VPP-lv8","title":"Add comparison page to Streamlit UI","description":"Add a new Streamlit page that lets users compare VPP performance between two configurations (e.g. different sail plans, keel shapes, or crew weights). Should overlay polars and show delta speed tables.","status":"closed","priority":2,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-02-28T08:46:22.0022617Z","created_by":"Thomas Dickson","updated_at":"2026-02-28T08:59:22.4254801Z","closed_at":"2026-02-28T08:59:22.4254801Z","close_reason":"Comparison page implemented with overlaid polars, VMG table, and delta speed table"} +{"id":"Python-VPP-nmg","title":"Leg-by-leg race breakdown","description":"After a Monte Carlo run, show per-leg statistics: which boat won each leg (upwind vs downwind), mean time per leg type, and where the advantage comes from. Helps answer 'is my boat faster upwind but losing it all downwind?' Display as a stacked bar chart or table showing cumulative time advantage per leg.","status":"closed","priority":3,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T22:45:23.0828301Z","created_by":"Thomas Dickson","updated_at":"2026-03-02T07:28:41.6100471Z","closed_at":"2026-03-02T07:28:41.6100471Z","close_reason":"Added per-leg timing to run_single() (leg_times_A, leg_times_B, leg_types) and leg_stats aggregation to run_monte_carlo()."} +{"id":"Python-VPP-pqs","title":"Step 1: Surface roughness (ITTC 1978)","status":"closed","priority":2,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T21:57:53.4735589Z","created_by":"Thomas Dickson","updated_at":"2026-03-01T22:01:36.2900998Z","closed_at":"2026-03-01T22:01:36.2900998Z","close_reason":"Closed"} +{"id":"Python-VPP-pvk","title":"Use distinct markers for different boats on comparison plots","description":"When comparing two configs with the same boat name, the legend shows identical labels (e.g. 'Daring 8' twice). Use distinct markers (circles vs triangles) or line styles to visually distinguish Config A from Config B regardless of name.","status":"closed","priority":2,"issue_type":"bug","owner":"tajdickson@protonmail.com","created_at":"2026-02-28T09:05:29.703104Z","created_by":"Thomas Dickson","updated_at":"2026-02-28T09:12:14.7321723Z","closed_at":"2026-02-28T09:12:14.7321723Z","close_reason":"Implemented with distinct colours (C0-C5) and markers (o, s, ^, D, v, P) per config"} +{"id":"Python-VPP-s0g","title":"Wind speed variation (TWS Brownian motion)","description":"Add mean-reverting Brownian motion to TWS in addition to wind direction shifts. Parameters: tws_sigma, tws_mean_reversion_rate. Changes optimal VMG angles and boat speed mid-race. Should use the pluggable WindModel API. Configurable from Match Race dashboard.","status":"closed","priority":3,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T22:14:29.2772425Z","created_by":"Thomas Dickson","updated_at":"2026-03-01T22:30:33.4346795Z","closed_at":"2026-03-01T22:30:33.4346795Z","close_reason":"Implemented in RaceMod (trim_sigma, tack_penalty_std, gybe_penalty_std) and WindMod (MeanRevertingWind tws_sigma), all configurable from Match Race UI"} +{"id":"Python-VPP-t3o","title":"Validate inputs in VPPMod.set_analysis","status":"closed","priority":1,"issue_type":"bug","owner":"tajdickson@protonmail.com","created_at":"2026-02-28T08:29:56.5329709Z","created_by":"Thomas Dickson","updated_at":"2026-02-28T08:33:01.6396669Z","closed_at":"2026-02-28T08:33:01.6396669Z","close_reason":"Closed"} +{"id":"Python-VPP-t73","title":"What-if parameter sensitivity analysis","description":"Allow the user to lock all parameters except one (e.g. keel depth, sail area, displacement) and sweep it across a range. Show how win probability, mean delta, and VMG change as that parameter varies. Identifies crossover points where one design overtakes another. Could be a dedicated UI section or a mode toggle on the Match Race page.","status":"closed","priority":2,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T22:45:23.0760706Z","created_by":"Thomas Dickson","updated_at":"2026-03-02T07:26:53.2430094Z","closed_at":"2026-03-02T07:26:53.2430094Z","close_reason":"Added run_parameter_sweep() function in RaceMod that sweeps any parameter via factory callables and returns win probability curves."} +{"id":"Python-VPP-u1u","title":"Add help descriptions to VPP page input parameters","description":"Add Streamlit help= tooltips to all input fields on the VPP page (yacht particulars, keel, rudder, main sail, jib, kite text_inputs). Many fields like 'Lwl', 'Bwl', 'Tc' are cryptic to new users. Each st.text_input should get a help='...' parameter explaining the physical meaning and expected units.","status":"closed","priority":3,"issue_type":"task","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T22:32:51.1679171Z","created_by":"Thomas Dickson","updated_at":"2026-03-02T07:33:33.7982004Z","closed_at":"2026-03-02T07:33:33.7982004Z","close_reason":"Added FIELD_HELP dict with tooltips for all yacht/keel/rudder/sail fields. Applied help= to all st.text_input calls on VPP page."} +{"id":"Python-VPP-u7t","title":"Track progress of current VPP simulation","description":"Track the progress and status of the ongoing VPP simulation run, including polar computation, solver convergence, and result rendering.","status":"closed","priority":1,"issue_type":"task","owner":"tajdickson@protonmail.com","created_at":"2026-03-02T22:11:39.508792Z","created_by":"Thomas Dickson","updated_at":"2026-03-02T22:24:23.7655787Z","closed_at":"2026-03-02T22:24:23.7655787Z","close_reason":"Added progress_callback to VPP.run() and run_vpp_direct() with st.progress() in the VPP page"} +{"id":"Python-VPP-ukz","title":"Tack/gybe penalty variance","description":"Replace fixed tack/gybe penalties with stochastic ones drawn from N(mean, std). Parameters: tack_penalty_mean, tack_penalty_std, gybe_penalty_mean, gybe_penalty_std. Configurable from Match Race dashboard.","status":"closed","priority":3,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T22:14:27.0367468Z","created_by":"Thomas Dickson","updated_at":"2026-03-01T22:30:33.4387776Z","closed_at":"2026-03-01T22:30:33.4387776Z","close_reason":"Implemented in RaceMod (trim_sigma, tack_penalty_std, gybe_penalty_std) and WindMod (MeanRevertingWind tws_sigma), all configurable from Match Race UI"} +{"id":"Python-VPP-vak","title":"Step 6: Explainer boxes","status":"closed","priority":2,"issue_type":"task","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T21:57:57.2862782Z","created_by":"Thomas Dickson","updated_at":"2026-03-01T22:21:01.1193067Z","closed_at":"2026-03-01T22:21:01.1193067Z","close_reason":"Closed"} +{"id":"Python-VPP-vht","title":"Step 3: Higher fidelity defaults","status":"closed","priority":2,"issue_type":"task","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T21:57:55.0870313Z","created_by":"Thomas Dickson","updated_at":"2026-03-01T22:01:36.3502491Z","closed_at":"2026-03-01T22:01:36.3502491Z","close_reason":"Closed"} +{"id":"Python-VPP-vui","title":"TWS sensitivity sweep for match racing","description":"Add a mode that runs the same boat matchup across a range of TWS values (e.g. 6-20 kts) and displays a heatmap or line chart of win probability vs wind speed. Answers: 'At what wind speed does my boat's advantage disappear?' Could also sweep Hs to show conditions sensitivity. Display as a colour-mapped grid or line plot with confidence bands.","status":"closed","priority":2,"issue_type":"feature","owner":"tajdickson@protonmail.com","created_at":"2026-03-01T22:45:23.0688536Z","created_by":"Thomas Dickson","updated_at":"2026-03-02T07:25:00.1670546Z","closed_at":"2026-03-02T07:25:00.1670546Z","close_reason":"Added Race.run_tws_sweep() that runs monte carlo at each TWS and returns win probabilities per wind speed."} +{"id":"Python-VPP-yy2","title":"Investigate leeway angle estimation","description":"Review how the leeway angle is currently being estimated in the VPP. Check the physics model, any assumptions, and whether the approach is appropriate.","status":"closed","priority":2,"issue_type":"task","owner":"tajdickson@protonmail.com","created_at":"2026-02-27T23:09:10.9775174Z","created_by":"Thomas Dickson","updated_at":"2026-02-28T13:08:05.3094925Z","closed_at":"2026-02-28T13:08:05.3094925Z","close_reason":"Investigated leeway model: removed incorrect max(0,leeway) clamp, clarified radians conversion, removed dead _cl method"} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..807d598 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ + +# Use bd merge for beads JSONL files +.beads/issues.jsonl merge=beads diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 9362e0f..6554fea 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -1,31 +1,18 @@ -# This workflows will upload a Python Package using Twine when a release is created -# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries - -name: Upload Python Package +name: Publish to PyPI on: release: types: [created] jobs: - deploy: - - runs-on: self-hosted - + publish: + runs-on: ubuntu-latest + permissions: + id-token: write steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - python setup.py sdist bdist_wheel - twine upload dist/* + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - name: Build package + run: uv build + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a2cf7c1..f1f0083 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,30 +1,26 @@ name: Run tests -on: [push] +on: + push: + branches: [master, main] + pull_request: + branches: [master, main] jobs: - example-1: - name: Testing (${{ matrix.python-version }}, ${{ matrix.os }}) + test: + name: Test (Python ${{ matrix.python-version }}, ${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: ["ubuntu-latest"] - python-version: ["3.10"] + os: [ubuntu-latest] + python-version: ["3.11", "3.12", "3.13"] steps: - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Check repo out - uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref }} - fetch-depth: 0 - - name: Install dependencies - run: | - pip install -r requirements.txt - - name: Run tests - shell: bash -l {0} - run: | - pytest -vv + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: uv sync --extra dev --extra api + - name: Run tests + run: uv run pytest -vv diff --git a/.gitignore b/.gitignore index c503985..e542c46 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,33 @@ -ORC_VPP_2019.pdf -sphinx/_build/ -src/__pycache__/ -dev.py -venv -.vscode +# Python +__pycache__/ *.pyc +*.pyo +*.egg-info/ +dist/ +build/ + +# Environments +.venv/ +venv/ + +# IDE +.vscode/ +.idea/ + +# Sphinx +sphinx/_build/ + +# pytest +.pytest_cache/ + +# Claude Code +.claude/ +CLAUDE.md +AGENTS.md +.beads/ + +# Project +ORC_VPP_2019.pdf dev.py -/.pytest_cache -/__pycache__/ -.pytest_cache \ No newline at end of file + +.playwright-cli/ \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..aa84d2e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,48 @@ +# Project Guidelines + +## Running Commands + +- Always use `uv run` to execute Python commands (e.g., `uv run python runVPP.py`, `uv run pytest tests/ -v`) +- Do not use bare `python` or `pytest` — the project uses `uv` for dependency management +- When you fix a bug or implement a feature write a test to prove that it works. + +## BEADS + +This project uses **bd** (beads) for issue tracking. Run `bd onboard` to get started. + +## Quick Reference + +```bash +bd ready # Find available work +bd show # View issue details +bd update --status in_progress # Claim work +bd close # Complete work +bd sync # Sync with git +``` + +## Landing the Plane (Session Completion) + +**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds. + +**MANDATORY WORKFLOW:** + +1. **File issues for remaining work** - Create issues for anything that needs follow-up +2. **Run quality gates** (if code changed) - Tests, linters, builds +3. **Update issue status** - Close finished work, update in-progress items +4. **PUSH TO REMOTE** - This is MANDATORY: + ```bash + git pull --rebase + bd sync + git push + git status # MUST show "up to date with origin" + ``` +5. **Clean up** - Clear stashes, prune remote branches +6. **Verify** - All changes committed AND pushed +7. **Hand off** - Provide context for next session + +**CRITICAL RULES:** +- Work is NOT complete until `git push` succeeds +- NEVER stop before pushing - that leaves work stranded locally +- NEVER say "ready to push when you are" - YOU must push +- If push fails, resolve and retry until it succeeds + diff --git a/Polars.png b/Polars.png index cc71e54..6a13939 100644 Binary files a/Polars.png and b/Polars.png differ diff --git a/Polars_5dof.png b/Polars_5dof.png new file mode 100644 index 0000000..321af63 Binary files /dev/null and b/Polars_5dof.png differ diff --git a/Polars_iterative.png b/Polars_iterative.png new file mode 100644 index 0000000..6a13939 Binary files /dev/null and b/Polars_iterative.png differ diff --git a/README.md b/README.md index fa22337..ec34a16 100644 --- a/README.md +++ b/README.md @@ -64,17 +64,16 @@ Follow the steps below to contribute to this project. ### Install dependencies -Install the required dependencies from the `requirements.txt` file. +Install the project using [uv](https://docs.astral.sh/uv/): -If using `pip` then `pip install requirements.txt`. +```bash +uv sync --extra dev +``` -If using `conda` then follow these steps to create an environment with the right dependencies: +If using `pip`: ```bash -conda create --name Python-VPP \ - && conda config --add channels conda-forge \ - && conda activate Python-VPP \ - && conda install -y --file requirements.txt +pip install -e ".[dev]" ``` ### Run tests diff --git a/SailChart.png b/SailChart.png index 1e83311..e80cdbd 100644 Binary files a/SailChart.png and b/SailChart.png differ diff --git a/dat/Daring/righting_moment.json b/dat/Daring/righting_moment.json new file mode 100644 index 0000000..4faf95d --- /dev/null +++ b/dat/Daring/righting_moment.json @@ -0,0 +1,4 @@ +{ + "Heel": [0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0], + "GZ": [0.000, 0.120, 0.230, 0.310, 0.350, 0.330, 0.260] +} diff --git a/dat/orc/asym_cl_kite.dat b/dat/orc/asym_cl_kite.dat new file mode 100644 index 0000000..64d8303 --- /dev/null +++ b/dat/orc/asym_cl_kite.dat @@ -0,0 +1,5 @@ +# asymmetric spinnaker centerline tack lift and drag coefficients (ORC VPP). first row is awa +0.02639,,,,,,,,,,, +0.0000,28.000,41.000,50.000,60.000,67.000,75.000,100.000,115.000,130.000,150.000,180.000 +0.0000,0.19100,0.28000,0.36600,0.52300,0.44800,0.55600,0.75700,0.79000,0.77600,0.62000,0.40000 +0.0000,0.02600,1.01800,1.27700,1.47100,1.51300,1.44400,1.13700,0.82900,0.56000,0.25000,-0.12000 diff --git a/dat/orc/asym_pole_kite.dat b/dat/orc/asym_pole_kite.dat new file mode 100644 index 0000000..4a94b72 --- /dev/null +++ b/dat/orc/asym_pole_kite.dat @@ -0,0 +1,5 @@ +# asymmetric spinnaker pole tack lift and drag coefficients (ORC VPP). first row is awa +0.02639,,,,,,,,,,, +0.0000,28.000,41.000,50.000,60.000,67.000,75.000,100.000,115.000,130.000,150.000,180.000 +0.0000,0.17000,0.23800,0.30600,0.45900,0.39200,0.49300,0.79100,0.89400,0.93600,0.93600,0.93600 +0.0000,0.08500,1.11400,1.36000,1.51300,1.54800,1.47900,1.20700,0.95600,0.70600,0.42500,0.00000 diff --git a/dat/orc/jib.dat b/dat/orc/jib.dat new file mode 100644 index 0000000..1bd52b0 --- /dev/null +++ b/dat/orc/jib.dat @@ -0,0 +1,5 @@ +# jib lift drag and lift coefficient (high). first row is awa +0.016,,,,,,,,, +0.00000,7.0000,15.0000,20.0000,27.0000,50.0000,60.0000,100.0000,150.0000,180.0000 +0.003,0.05000,0.03200,0.03100,0.03700,0.25000,0.35000,0.73000,0.95000,0.90000 +0.00000,0.00000,1.10000,1.47500,1.50000,1.45000,1.25000,0.40000,0.00000,-0.10000 \ No newline at end of file diff --git a/dat/orc/jib_low.dat b/dat/orc/jib_low.dat new file mode 100644 index 0000000..b4b58a1 --- /dev/null +++ b/dat/orc/jib_low.dat @@ -0,0 +1,5 @@ +# jib lift drag and lift coefficient (low performance rig). first row is awa +0.016,,,,,,,,, +0.00000,7.0000,15.0000,20.0000,27.0000,50.0000,60.0000,100.0000,150.0000,180.0000 +0.003,0.05000,0.03200,0.03100,0.03700,0.25000,0.35000,0.73000,0.95000,0.90000 +0.00000,0.00000,1.00000,1.37500,1.45000,1.43000,1.25000,0.40000,0.00000,-0.10000 diff --git a/dat/orc/kite.dat b/dat/orc/kite.dat new file mode 100644 index 0000000..d8b6dc7 --- /dev/null +++ b/dat/orc/kite.dat @@ -0,0 +1,5 @@ +# kite lift drag and lift coefficient (high). first row is awa +0.02639,,,,,,,,,,,, +0.0000,28.000,41.000,50.000,60.000,67.000,75.000,100.000,115.000,130.000,150.000,170.000,180.000 +0.0000,0.19152,0.28152,0.35496,0.43920,0.48960,0.53280,0.61920,0.65880,0.67320,0.67320,0.67320,0.67320 +0.0000,-0.02484,0.69437,0.90677,1.04400,1.08000,1.08000,0.95760,0.81360,0.61200,0.32400,0.10800,0.00000 \ No newline at end of file diff --git a/dat/orc/main.dat b/dat/orc/main.dat new file mode 100644 index 0000000..12dc64e --- /dev/null +++ b/dat/orc/main.dat @@ -0,0 +1,5 @@ +# mainsail lift drag and lift coefficient (high). first row is awa +0.01379,,,,,,,,, +0.00000,7.0000,9.0000,12.0000,28.0000,60.0000,90.0000,120.0000,150.0000,180.000 +0.03448,0.01724,0.01466,0.01466,0.02586,0.11302,0.38250,0.96888,1.31578,1.34483 +0.00000,0.94828,1.13793,1.25000,1.42681,1.38319,1.26724,0.93103,0.38793,-0.11207 \ No newline at end of file diff --git a/dat/orc/main_low.dat b/dat/orc/main_low.dat new file mode 100644 index 0000000..2784d4f --- /dev/null +++ b/dat/orc/main_low.dat @@ -0,0 +1,5 @@ +# mainsail lift drag and lift coefficient (low performance rig). first row is awa +0.01379,,,,,,,,, +0.00000,7.0000,9.0000,12.0000,28.0000,60.0000,90.0000,120.0000,150.0000,180.000 +0.04300,0.02600,0.02300,0.02300,0.03300,0.11302,0.38250,0.96888,1.31578,1.34483 +0.00000,0.86200,1.05200,1.16400,1.34700,1.23900,1.12500,0.83800,0.29600,-0.11207 diff --git a/dat/orc/sym_kite.dat b/dat/orc/sym_kite.dat new file mode 100644 index 0000000..dd45e31 --- /dev/null +++ b/dat/orc/sym_kite.dat @@ -0,0 +1,5 @@ +# symmetric spinnaker lift and drag coefficients (ORC VPP). first row is awa +0.02639,,,,,,,,,,, +0.0000,28.000,41.000,50.000,60.000,67.000,75.000,100.000,115.000,130.000,150.000,180.000 +0.0000,0.21300,0.32100,0.42500,0.58700,0.59800,0.61900,0.85000,0.91100,0.93500,0.93500,0.93500 +0.0000,0.00000,0.97800,1.24100,1.45400,1.45600,1.43700,1.19000,0.95100,0.70600,0.42500,0.00000 diff --git a/dat/polars_Daring_5.5m.json b/dat/polars_Daring_5.5m.json new file mode 100644 index 0000000..f980095 --- /dev/null +++ b/dat/polars_Daring_5.5m.json @@ -0,0 +1 @@ +{"name": "Daring", "results": [[[[1.2254361364110362, 1.1205304315717708, 8.646916208050413, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[1.7194072304802177, 1.3160520855068498, 5.202205599112657, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[2.049998772779813, 1.4371328960879486, 3.995109897086957, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[2.2892182707766766, 1.50648241296533, 3.355842322995613, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[2.482994272846284, 1.545274885632866, 2.9237672403652493, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[2.640410725695732, 1.5603777155989353, 2.610050980923144, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[2.7727761703987412, 1.5575878154291465, 2.3629713860545025, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[2.8777058596565404, 1.5363490103574442, 2.165241810972713, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[2.958441126798166, 1.4990020072347607, 2.001044014793009, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[3.019475096060067, 1.4483168312716486, 1.8587563144510575, 1.0, 2.0], [2.560021946980627, 2.066701578558054, 3.4616422503305198, 1.0, 2.0]], [[3.0619375345570274, 1.3855871770414279, 1.732388301449776, 1.0, 2.0], [2.757797194372542, 2.156281785880404, 3.124911630010696, 1.0, 2.0]], [[3.085935824566328, 1.3119318228231651, 1.618242039052518, 1.0, 2.0], [2.9258010271389385, 2.2113730865038277, 2.8577298550782637, 1.0, 2.0]], [[3.0903129414119914, 1.2283875841694376, 1.5144731516783685, 1.0, 2.0], [3.067307352252179, 2.2310787180256613, 2.6319857267603077, 1.0, 2.0]], [[3.0788054845637047, 1.1366308079189573, 1.4150546788079343, 1.0, 2.0], [3.185151617603154, 2.2169745598301738, 2.432717589487536, 1.0, 2.0]], [[3.052061804483426, 1.0375512421174142, 1.316809998082596, 1.0, 2.0], [3.276717788219107, 2.1703177707252532, 2.25674661248869, 1.0, 2.0]], [[3.007918443844489, 0.93179828677709, 1.2187266680498974, 1.0, 2.0], [3.3458442398423944, 2.096461847488146, 2.0966857770592884, 1.0, 2.0]], [[2.9450446595864603, 0.8221643485719533, 1.121715691802899, 1.0, 2.0], [3.397277245601016, 2.002556607970123, 1.9481649848059615, 1.0, 2.0]], [[2.865819743888335, 0.7136919711715152, 1.0273607407811964, 1.0, 2.0], [3.432598457402484, 1.8929184133801717, 1.8092367467148334, 1.0, 2.0]], [[2.772777514543281, 0.6111300357110454, 0.9382522306289032, 1.0, 2.0], [3.4513149744215794, 1.7685759669180616, 1.6773358117535715, 1.0, 2.0]], [[2.670082441382429, 0.5178689124302976, 0.8555833181042202, 1.0, 2.0], [3.4525306689590627, 1.630413215838624, 1.5501300163651957, 1.0, 2.0]], [[2.563522926105463, 0.43552297204460627, 0.7783779006343332, 1.0, 2.0], [3.4348484465627824, 1.4792569011968628, 1.425385735107694, 1.0, 2.0]], [[2.4555968514161566, 0.36445005600316666, 0.7071746099364067, 1.0, 2.0], [3.3966351078473633, 1.316496488634346, 1.3010612216074031, 1.0, 2.0]], [[2.3457538059819014, 0.30464541730180883, 0.6446858070828998, 1.0, 2.0], [3.338703230820719, 1.1469892640382047, 1.1763896168652554, 1.0, 2.0]], [[2.2379854494814415, 0.25692245827537963, 0.594328486069944, 1.0, 2.0], [3.2636339885461414, 0.9770814758146602, 1.0514199229132424, 1.0, 2.0]], [[2.137669534334437, 0.22103841810907957, 0.5580256936672652, 1.0, 2.0], [3.1704654229978813, 0.8118068573216787, 0.9280025620709584, 1.0, 2.0]], [[2.048942692988345, 0.1950300515253354, 0.5341662888614727, 1.0, 2.0], [3.0617369945522968, 0.6571681253587572, 0.807706059555781, 1.0, 2.0]], [[1.972967620053955, 0.17587169446290155, 0.5183579855562093, 1.0, 2.0], [2.9424362350019218, 0.5181356538942362, 0.6915425516255314, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.815155739269146, 0.3979958111404883, 0.5820849376928021, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.6795436266856276, 0.29873564470094577, 0.4833591032021285, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.546900447783828, 0.22116877028499715, 0.39587250209773767, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.4193106252491425, 0.1615545356117227, 0.3187110233825481, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.292885190215043, 0.11628695512541314, 0.25252292030728873, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.1764327148744296, 0.08467375195309665, 0.2009691898087171, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.078534029555272, 0.06474533646551021, 0.1657854070538777, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.003934609921269, 0.051728755855087516, 0.1405222547565308, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [1.9514824426196509, 0.040690380471187414, 0.11536836015646561, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [1.9173295655353875, 0.029949433945596356, 0.08739486081945305, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [1.8973709031117425, 0.01967789689391265, 0.058462661620519225, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [1.8899804204683053, 0.010498437453390767, 0.03145258672723819, 1.0, 2.0]]], [[[1.8706959828438783, 2.539437784346465, 8.431530524922623, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[2.6531898148749846, 2.976264191809856, 4.986396232517818, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[3.105880376918116, 3.1887373174529405, 3.9337633379585046, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[3.4360428287916513, 3.302432071988122, 3.345390736522287, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[3.696303268339366, 3.3560521009865867, 2.944025380104406, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[3.8995918616713467, 3.3648687868008462, 2.6526049948450456, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[4.06707507052804, 3.342602235234407, 2.4201442458561506, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[4.191184418389932, 3.2859574909218803, 2.235283631582615, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[4.290296607061465, 3.2058421822773937, 2.0753046252247347, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[4.365544008155386, 3.10283111733684, 1.9337623933016597, 1.0, 2.0], [3.8108706033839925, 4.2580177035735, 3.4971877474208606, 1.0, 2.0]], [[4.418503998015266, 2.97727823510932, 1.8053723253559755, 1.0, 2.0], [4.075791262802305, 4.391890576941331, 3.193822512184731, 1.0, 2.0]], [[4.445680894204461, 2.827596292789636, 1.6890101264027053, 1.0, 2.0], [4.29023988659191, 4.463586487197507, 2.954091710425823, 1.0, 2.0]], [[4.450992495752114, 2.6548970619123753, 1.57986190588352, 1.0, 2.0], [4.47055552888239, 4.477591128597661, 2.7409579716754484, 1.0, 2.0]], [[4.438748683824175, 2.459075841003561, 1.4729710771811753, 1.0, 2.0], [4.609843208311292, 4.434331589088622, 2.554443706563041, 1.0, 2.0]], [[4.4060967421403685, 2.2442624749476843, 1.3665462589605364, 1.0, 2.0], [4.722973085190123, 4.34543779542856, 2.3788618236697956, 1.0, 2.0]], [[4.350261082190401, 2.0160362783830044, 1.26018788865881, 1.0, 2.0], [4.812387035584889, 4.219715463305959, 2.2140589617322277, 1.0, 2.0]], [[4.271781540928382, 1.7827316562128417, 1.1554408216898786, 1.0, 2.0], [4.877572012914596, 4.063770020766868, 2.0614912389997917, 1.0, 2.0]], [[4.1748900793641734, 1.5547346194776674, 1.0539361014895838, 1.0, 2.0], [4.921610713187921, 3.879732840736529, 1.9165832044550115, 1.0, 2.0]], [[4.06637078294876, 1.3419548331064375, 0.9574339920331304, 1.0, 2.0], [4.9450376839587875, 3.6667084641757755, 1.7760931908302147, 1.0, 2.0]], [[3.938696768343987, 1.145186520802659, 0.86909004227314, 1.0, 2.0], [4.946173158288612, 3.4220563686973264, 1.6380866382960453, 1.0, 2.0]], [[3.8009543947577167, 0.9688694844548387, 0.7873423341602083, 1.0, 2.0], [4.922906533060289, 3.1418631639168355, 1.500652149309412, 1.0, 2.0]], [[3.661593197680879, 0.8156426406905555, 0.7116173344076974, 1.0, 2.0], [4.8750956634244735, 2.824890005714228, 1.3629517426212778, 1.0, 2.0]], [[3.511908289673889, 0.684441418872733, 0.6461256514001542, 1.0, 2.0], [4.802587387629242, 2.4716554063654965, 1.225914856970465, 1.0, 2.0]], [[3.3633969588657524, 0.5786575154563575, 0.5926732984828185, 1.0, 2.0], [4.7060083994258415, 2.105829410288338, 1.09054078305635, 1.0, 2.0]], [[3.2248663346018263, 0.4981434298500806, 0.5526483671025872, 1.0, 2.0], [4.591511279681694, 1.7552238264103368, 0.9572773033922417, 1.0, 2.0]], [[3.0932577024040615, 0.4388948087430352, 0.5274757402808079, 1.0, 2.0], [4.46261371078769, 1.430208585700505, 0.8279153100650313, 1.0, 2.0]], [[2.9825179001092548, 0.3951386153475808, 0.5096661653176581, 1.0, 2.0], [4.310228070963548, 1.134172158051882, 0.7058198247395144, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.145694261981329, 0.8774211704027115, 0.5919458854254485, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.972919647097163, 0.6645316566262531, 0.4891270798703892, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.792749573502266, 0.4947364299053389, 0.3992504384041548, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.6187381360210233, 0.36285473630708687, 0.31989500004446436, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.4391519302467124, 0.26163887662065205, 0.2525392150171604, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.273848208294988, 0.1904740139126649, 0.1998406608598633, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.130498341020369, 0.14532878506623978, 0.16409254554799, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.0197911661262578, 0.11590149568057323, 0.13867513635851664, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.943473915292899, 0.0910344067352443, 0.11346502816970613, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.893775691761757, 0.06691386597225095, 0.08572343875997299, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.8647418222165504, 0.04388728137437807, 0.05719721737625368, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.8540020664984618, 0.02333827256295437, 0.030662408269582272, 1.0, 2.0]]], [[[2.467592912038856, 4.152976646437254, 8.565621500733045, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[3.524328245491638, 4.7496888170165965, 4.995198835189989, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[4.056369388842237, 5.007054676717467, 4.028152585621664, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[4.427668240872698, 5.127668576238828, 3.4795892639446437, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[4.704872304804672, 5.162561367031421, 3.1053963644605145, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[4.932231006367611, 5.150001039200741, 2.8168612718603105, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.115758393310813, 5.095101226968987, 2.584936689227108, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.2623104904016715, 5.0043722051827055, 2.39125337293096, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.362398238567784, 4.873496353155128, 2.2309125552603093, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.439163517010839, 4.717666305914139, 2.0852268849520956, 1.0, 2.0], [4.905574215473284, 6.834565090336558, 3.699486101747985, 1.0, 2.0]], [[5.492964183105675, 4.537666303208209, 1.9504686114812084, 1.0, 2.0], [5.2103355376257605, 7.0778206494569735, 3.4054599811865387, 1.0, 2.0]], [[5.521276740351525, 4.333859125784628, 1.825622667274952, 1.0, 2.0], [5.445224338265997, 7.183384489848274, 3.1732371371200654, 1.0, 2.0]], [[5.52871265637473, 4.106181059335094, 1.7045640696873083, 1.0, 2.0], [5.643501174386927, 7.16345390524434, 2.957121085048121, 1.0, 2.0]], [[5.516169478675315, 3.8528909180785362, 1.5839832282962243, 1.0, 2.0], [5.745103486201134, 6.990644537006319, 2.799757861611587, 1.0, 2.0]], [[5.481091620300526, 3.5714041500679614, 1.4629625141295075, 1.0, 2.0], [5.830063208818552, 6.752681924324197, 2.6380064118079427, 1.0, 2.0]], [[5.4219332542935925, 3.263181994059185, 1.3427683633102407, 1.0, 2.0], [5.900974416008924, 6.479680459526573, 2.477258690134909, 1.0, 2.0]], [[5.341332828742203, 2.9346110834551844, 1.2255261879875379, 1.0, 2.0], [5.956914437114042, 6.177865010454475, 2.3173346071666927, 1.0, 2.0]], [[5.2450033927619915, 2.5940192224495013, 1.1131713667616727, 1.0, 2.0], [5.9958984233711305, 5.848106628499006, 2.1570154580881873, 1.0, 2.0]], [[5.117101512161371, 2.247435721175114, 1.0112063029515816, 1.0, 2.0], [6.015757542320577, 5.490428791979523, 1.995118115797019, 1.0, 2.0]], [[4.979865937044188, 1.9313348979886613, 0.9156069882764317, 1.0, 2.0], [6.014376662266854, 5.10443252143265, 1.830673528171668, 1.0, 2.0]], [[4.837931417269866, 1.6493487444902861, 0.8261608121014882, 1.0, 2.0], [5.992035142553033, 4.694642654571608, 1.664506785996876, 1.0, 2.0]], [[4.679430868588579, 1.3986029005959733, 0.7459790439717524, 1.0, 2.0], [5.950798763361205, 4.266180473978561, 1.4982051571722408, 1.0, 2.0]], [[4.521975486780659, 1.1868244553135738, 0.6748807338397474, 1.0, 2.0], [5.892838042434139, 3.821675611557528, 1.3335295926537625, 1.0, 2.0]], [[4.361715283551243, 1.01394057462579, 0.6169141563503429, 1.0, 2.0], [5.820071308598359, 3.3619299366541555, 1.1725206125000363, 1.0, 2.0]], [[4.202035656168584, 0.8800185847723347, 0.5746643730583687, 1.0, 2.0], [5.7349589953542575, 2.886838067083739, 1.017226852297702, 1.0, 2.0]], [[4.060156869400802, 0.779927863224949, 0.5438771080017694, 1.0, 2.0], [5.63775876676519, 2.3948149429342096, 0.8694274909898554, 1.0, 2.0]], [[3.9298168583728743, 0.7039907620302686, 0.5229372278592409, 1.0, 2.0], [5.469126337573013, 1.9092237853101957, 0.738681700431756, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.292121501379797, 1.490717124941953, 0.6175983960610887, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.0942290139775, 1.1397465492249346, 0.5102555862598647, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.893427733936021, 0.8578998739923691, 0.41554149522840905, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.68880534113116, 0.6342284687716473, 0.3325104229421658, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.4871791491990844, 0.4616600331706934, 0.26132737511873533, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.291242206832603, 0.33915975316256186, 0.20683525352942086, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.12268288686718, 0.2602598749049176, 0.16930429991309473, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.991399719046609, 0.20768850166546418, 0.14218885047519284, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.895753811220888, 0.16306865966932396, 0.11600919683986491, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.8332897384024944, 0.11984619510825611, 0.08749219548611424, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.7967340706815227, 0.07865632245650304, 0.05836025540899003, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.783191858648608, 0.0419294938830303, 0.03135080565781854, 1.0, 2.0]]], [[[3.0744722131987254, 5.932477507542169, 8.577311607672842, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[4.28200726349995, 6.856305632437686, 5.170430787284467, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[4.856265100434159, 7.2650543906857665, 4.239607208016746, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.266230019159355, 7.455298922903569, 3.6830307735933983, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.558799135368486, 7.474560023237083, 3.3107317699110292, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.735659610953936, 7.339579091495394, 3.0620569820311627, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.855178470923703, 7.129541890406668, 2.865725787921991, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.955046372122786, 6.892399648645256, 2.6878287653979864, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.036062877723716, 6.632446415510186, 2.5234214610092365, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.072071204962138, 6.332510869935987, 2.3813753171392853, 1.0, 2.0], [5.7881882446107795, 11.248268939111865, 4.024451865337073, 1.0, 2.0]], [[6.093108027193791, 6.022467275401236, 2.2436114987598708, 1.0, 2.0], [6.0149567050414445, 11.54563468374618, 3.8178265201103874, 1.0, 2.0]], [[6.105509598207972, 5.7023824395450005, 2.103378883653498, 1.0, 2.0], [6.136048929889325, 11.49277341044666, 3.6729058337283864, 1.0, 2.0]], [[6.109758476287105, 5.368615192473026, 1.9578029712958447, 1.0, 2.0], [6.229506396544334, 11.211157276045144, 3.5132209805050247, 1.0, 2.0]], [[6.104677358311753, 5.019326419420344, 1.8066614215665093, 1.0, 2.0], [6.3119426020002765, 10.789216658888913, 3.340398014721883, 1.0, 2.0]], [[6.089431819615176, 4.658195689378292, 1.6530298785276942, 1.0, 2.0], [6.385003445246029, 10.270298529088965, 3.1625827583587327, 1.0, 2.0]], [[6.06491464686775, 4.292209275437511, 1.501292931890937, 1.0, 2.0], [6.446891848615136, 9.709254768666009, 2.9782818095280343, 1.0, 2.0]], [[6.020574336440605, 3.9212605799665043, 1.358048816044808, 1.0, 2.0], [6.482605636819052, 9.093052532678255, 2.7943740519770475, 1.0, 2.0]], [[5.946212454226167, 3.5450179236156902, 1.2278456382183753, 1.0, 2.0], [6.505933322183292, 8.388327170568752, 2.598684829787263, 1.0, 2.0]], [[5.864292752781788, 3.173405960142662, 1.1054177355164012, 1.0, 2.0], [6.516230263044912, 7.6073150394511435, 2.392648761234444, 1.0, 2.0]], [[5.776747039317365, 2.8058707274823322, 0.990826127962993, 1.0, 2.0], [6.514164434427465, 6.833915635195712, 2.180032171652726, 1.0, 2.0]], [[5.685495137644575, 2.44312044783712, 0.8842233213370754, 1.0, 2.0], [6.5008083314202505, 6.1486318393242465, 1.9647001097256518, 1.0, 2.0]], [[5.5540754368003435, 2.0989974108732183, 0.7931421952998369, 1.0, 2.0], [6.476871811895334, 5.520990037581167, 1.7509974014806808, 1.0, 2.0]], [[5.390812565694829, 1.7989818565870355, 0.718529253757463, 1.0, 2.0], [6.4415967124359845, 4.9306428094882175, 1.5432481135690708, 1.0, 2.0]], [[5.232296671096308, 1.5552637793732755, 0.6566984025344725, 1.0, 2.0], [6.384474744395748, 4.3581811434685145, 1.3467342028706206, 1.0, 2.0]], [[5.059110830144907, 1.3638342317715242, 0.6138007392317614, 1.0, 2.0], [6.317320722295194, 3.7986730275439715, 1.1599166970585044, 1.0, 2.0]], [[4.906163311162754, 1.2189924897218716, 0.5817897598506153, 1.0, 2.0], [6.241917561094769, 3.244730883659563, 0.9848828515515118, 1.0, 2.0]], [[4.771644045754406, 1.1064203515821622, 0.5572430244458948, 1.0, 2.0], [6.160472297313343, 2.692169404276718, 0.8238592194096309, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.07568361811248, 2.1561109682791884, 0.6781980048062397, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.9456423047537115, 1.686123668083861, 0.5538059921800056, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.793437447545232, 1.292660457835374, 0.44587340780989176, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.6441722017710845, 0.9714142064975412, 0.35067455687231275, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.422202605205401, 0.7145078725402443, 0.27628706235209816, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.216882350691285, 0.5320401878250312, 0.2190809019447614, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.025320114226891, 0.41290008859620286, 0.18049949786580122, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.879570902460469, 0.33102172833277, 0.15149972368058576, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.774954302018101, 0.25986930313629175, 0.12300995410981345, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.704567816405513, 0.1911371904883132, 0.09262500862715933, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.6632181361549545, 0.1257835380588657, 0.061865384331241904, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.647840538731383, 0.067547614426488, 0.03346208800722465, 1.0, 2.0]]], [[[3.6272297430961338, 8.588158397162585, 8.735383050722739, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[4.952420033906878, 10.094102765849929, 5.383587390141141, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.558615830618181, 10.719653042916493, 4.4582753752763535, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.8478060458757035, 10.727501302101254, 4.026116058597883, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.056405785074533, 10.555647302439883, 3.70892193069309, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.1516353688451435, 10.174978249516666, 3.5036753475062925, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.2332352098917, 9.787704693352314, 3.313448232748185, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.302150064984935, 9.387373543482653, 3.1333025955193934, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.359038203636372, 8.942176217058087, 2.959305809499433, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.402318434948601, 8.452816142155326, 2.7905901828552824, 1.0, 2.0], [6.259982860000991, 16.75135969563248, 4.687992680522821, 1.0, 2.0]], [[6.433076607136774, 7.916658108220447, 2.6223567353538444, 1.0, 2.0], [6.425118250304741, 16.88059591788343, 4.499653676732196, 1.0, 2.0]], [[6.451314043872028, 7.331765120020586, 2.4498219377839585, 1.0, 2.0], [6.539185531603319, 16.662949167901274, 4.3165459411553995, 1.0, 2.0]], [[6.4560399900589704, 6.7586337810408565, 2.270559944892339, 1.0, 2.0], [6.636975693877369, 16.23855208402282, 4.119989562179069, 1.0, 2.0]], [[6.450362405195594, 6.219175846961057, 2.085920420103279, 1.0, 2.0], [6.726769031553196, 15.681038851232742, 3.915460054749505, 1.0, 2.0]], [[6.429303918374468, 5.709694144943746, 1.9041530107445352, 1.0, 2.0], [6.8063841578441115, 14.979486354392462, 3.702596608473507, 1.0, 2.0]], [[6.39620976609009, 5.231305724010891, 1.7293241086808964, 1.0, 2.0], [6.8686193691497985, 14.112797170165164, 3.4833970150208065, 1.0, 2.0]], [[6.35488605850579, 4.783334855356189, 1.5642911835126052, 1.0, 2.0], [6.908761867117753, 13.06814398138671, 3.2568074418405146, 1.0, 2.0]], [[6.3069789120696615, 4.359091014956909, 1.4092966446195292, 1.0, 2.0], [6.934291404798618, 11.874481497204897, 3.0175547295464087, 1.0, 2.0]], [[6.253765795105351, 3.953297072835584, 1.2643506061000942, 1.0, 2.0], [6.945809003428763, 10.573086001485777, 2.770228920339481, 1.0, 2.0]], [[6.19650910428801, 3.5625147313191703, 1.1295570115535978, 1.0, 2.0], [6.944295325294136, 9.341251014649067, 2.5174492019668877, 1.0, 2.0]], [[6.136308738307639, 3.186217116370276, 1.0058206019805884, 1.0, 2.0], [6.9302365234061085, 8.18648650541904, 2.2642485802850034, 1.0, 2.0]], [[6.074311649343791, 2.8271735984442796, 0.8947250729359714, 1.0, 2.0], [6.904597276401171, 7.066702031862693, 2.0156140062071093, 1.0, 2.0]], [[5.980657717633935, 2.4845560074327047, 0.8040610635746075, 1.0, 2.0], [6.868317273070611, 6.157206752625523, 1.7736436391764694, 1.0, 2.0]], [[5.863800753861672, 2.1867626569797842, 0.7337051618735599, 1.0, 2.0], [6.815429855281086, 5.38443874430952, 1.5439009321196402, 1.0, 2.0]], [[5.754914818557919, 1.9489446784272633, 0.6769753375579712, 1.0, 2.0], [6.748222904656038, 4.689641684271162, 1.3286426666780211, 1.0, 2.0]], [[5.657401810917914, 1.7590160297758042, 0.6308922281902354, 1.0, 2.0], [6.6728847455502756, 4.045674561136876, 1.1293023965354354, 1.0, 2.0]], [[5.5297546099727, 1.6056481489607781, 0.601851484234537, 1.0, 2.0], [6.592134714755469, 3.4347546985297006, 0.947370691931275, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.508647049022498, 2.8448746603868367, 0.7836669092108958, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.415696578333778, 2.2694966776844723, 0.6386837714663552, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.301665065761776, 1.7653442562932875, 0.5124982587844992, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.188473468799158, 1.347378075571872, 0.40236859523591173, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.079992751991712, 1.0169754158150295, 0.31125645115423084, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.934608775559729, 0.7750837786080641, 0.245726414338871, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.791511292333112, 0.6097085070284971, 0.20022894828743595, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.681621509605334, 0.48926523884749407, 0.16498295414039363, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.586936274702732, 0.3834952312452794, 0.13253172476372452, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.512453633811943, 0.28242104944093643, 0.09966668102590984, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.468499379862629, 0.18652386179481714, 0.06670879472749953, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.452073092389055, 0.10110191562121085, 0.036398293847223054, 1.0, 2.0]]], [[[4.039527562516499, 12.18308433843932, 9.262406774259114, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.563117079414679, 14.466908841956982, 5.570856163187931, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.983373942330772, 14.766765705336294, 4.889056534936809, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.177139503988851, 14.539933974994117, 4.527757185042362, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.320794318252125, 14.182270076267452, 4.241636060076225, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.447401441519195, 13.755123416202103, 3.9854757824453646, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.5247337693305205, 13.207952107190513, 3.779402725626929, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.590290448448165, 12.59588282319633, 3.58248477233439, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.64430284640505, 11.915075601002423, 3.3904965455458496, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.6856226242989765, 11.163934839815518, 3.201485865447267, 1.0, 2.0], [6.60249708283912, 23.297629222708057, 5.413567445163149, 1.0, 2.0]], [[6.716342765486497, 10.330635829827715, 3.0071919023485414, 1.0, 2.0], [6.76190290014202, 23.288865498839055, 5.183058497685235, 1.0, 2.0]], [[6.736114534943306, 9.508931985160276, 2.8019777091038383, 1.0, 2.0], [6.897207335895568, 22.86952736566357, 4.939046410705389, 1.0, 2.0]], [[6.742270171125046, 8.67985613056417, 2.588810795279412, 1.0, 2.0], [7.0072478933949105, 22.177743950108294, 4.705424496487626, 1.0, 2.0]], [[6.734426898923641, 7.820061273346923, 2.3754714016252314, 1.0, 2.0], [7.10847350486548, 21.24424144203384, 4.462970859967316, 1.0, 2.0]], [[6.714611095255375, 6.997024629064002, 2.1674841718845346, 1.0, 2.0], [7.197312390023393, 20.0268094998848, 4.211175121793948, 1.0, 2.0]], [[6.685794061179124, 6.31763605840407, 1.9690983652767604, 1.0, 2.0], [7.270094669982985, 18.90139361289512, 3.942243648717256, 1.0, 2.0]], [[6.649690492480198, 5.735586427337385, 1.782171495028333, 1.0, 2.0], [7.323398244153203, 17.560196464776297, 3.664085265389843, 1.0, 2.0]], [[6.6074571565792235, 5.2173662905918805, 1.6070486010985787, 1.0, 2.0], [7.357803187308336, 16.039743598014603, 3.3811839699624446, 1.0, 2.0]], [[6.560389094616081, 4.743641773326023, 1.4434223851528407, 1.0, 2.0], [7.374382805670062, 14.388784185483228, 3.0970235916280626, 1.0, 2.0]], [[6.50942709274323, 4.305510336876989, 1.2925099732812517, 1.0, 2.0], [7.373661845721073, 12.655013818897423, 2.8144787070733708, 1.0, 2.0]], [[6.4555999987952175, 3.900163709749801, 1.1561822053105475, 1.0, 2.0], [7.3567042209469, 10.888595212383784, 2.5362051183382266, 1.0, 2.0]], [[6.381578846458187, 3.5250617209439823, 1.0410271287758268, 1.0, 2.0], [7.324655240829613, 9.281103409721904, 2.2626466882293523, 1.0, 2.0]], [[6.305185744856231, 3.1887903607370176, 0.943964465404951, 1.0, 2.0], [7.278457246550866, 7.868343278695177, 1.9966358968560372, 1.0, 2.0]], [[6.231650071269651, 2.894361236902386, 0.8639932387637835, 1.0, 2.0], [7.219271728020214, 6.627329032926938, 1.7421846125126152, 1.0, 2.0]], [[6.16352814670872, 2.638758221885433, 0.7984694288645476, 1.0, 2.0], [7.1491103427239135, 5.684344320258055, 1.5025666512941969, 1.0, 2.0]], [[6.10266316558077, 2.414897877179956, 0.7434534755357202, 1.0, 2.0], [7.070833185824202, 4.895365218271313, 1.2811223331156791, 1.0, 2.0]], [[6.049610137660569, 2.2207492403103823, 0.6949585644044587, 1.0, 2.0], [6.9873511714050895, 4.19503093766572, 1.079431744650972, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.900784557737791, 3.546691925798071, 0.8972714990053031, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.80328257872641, 2.9227447300569933, 0.7350441564079776, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.692905606281222, 2.3122776805744487, 0.5923358817950902, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.583520787607685, 1.7886550493867144, 0.4693619638996501, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.479898774274338, 1.3806077943823054, 0.36985912302505153, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.367183945767722, 1.0825668706879263, 0.296680416563151, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.264867557642532, 0.8703229851191849, 0.24339089153102142, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.186400991073882, 0.7043506397589965, 0.19990365233969706, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.130468152981063, 0.5518711145114247, 0.15823679837188662, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.092668012348151, 0.4061201929396309, 0.11728071028467324, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.070045802509995, 0.2695283670704626, 0.07823244480130762, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.061486964565083, 0.14860364884217883, 0.0432828105178405, 1.0, 2.0]]], [[[4.484325325379685, 16.5017056500776, 9.507700873982555, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.91865350573012, 18.745069647980273, 6.062983976974454, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.221160006949585, 18.76746879269199, 5.487248504579976, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.430312192123474, 18.492582704630454, 5.069080171384702, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.566837496106737, 18.040509579085654, 4.762670665424978, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.681958043220073, 17.504179991336102, 4.4923449816738, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.781763310321603, 16.887763565006388, 4.2436679358811675, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.864020526025974, 16.177988386223156, 4.011455808937844, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.918907763881405, 15.359752502123783, 3.7991951581067465, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.962254116209337, 14.444757493210156, 3.5859496819249994, 1.0, 2.0], [6.8278186787803525, 30.834337462434934, 6.961415512857271, 1.0, 2.0]], [[6.995234806425072, 13.415765887754048, 3.363300607286826, 1.0, 2.0], [7.00342319068165, 30.66285776074565, 6.619002832185302, 1.0, 2.0]], [[7.015668047598264, 12.268894263203661, 3.129554599985167, 1.0, 2.0], [7.239511822674065, 29.99999940593966, 5.510278280795211, 1.0, 2.0]], [[7.021550278494119, 11.037531239887286, 2.8907502887605707, 1.0, 2.0], [7.3769898610414, 29.432814568709738, 5.297029613677712, 1.0, 2.0]], [[7.013879428906579, 9.813839194714443, 2.653939584788569, 1.0, 2.0], [7.513033265588983, 28.431594640531966, 4.970340806424406, 1.0, 2.0]], [[6.995497242561006, 8.784987606395893, 2.423804926168942, 1.0, 2.0], [7.631208586603216, 27.07920076606369, 4.638768466308474, 1.0, 2.0]], [[6.968657176267232, 7.801376082017946, 2.2061887495487054, 1.0, 2.0], [7.725610289459256, 25.329887397165773, 4.306043242247729, 1.0, 2.0]], [[6.934602648315469, 6.916039310867272, 2.0008863449739525, 1.0, 2.0], [7.794938987942262, 23.203493899303893, 3.978692868162919, 1.0, 2.0]], [[6.894502957356525, 6.210488296088968, 1.807751065608342, 1.0, 2.0], [7.840238337827836, 20.750472591348892, 3.6615586305504846, 1.0, 2.0]], [[6.848972904708291, 5.616657535499993, 1.62802948208848, 1.0, 2.0], [7.862988032345535, 18.57062489997063, 3.34763861930216, 1.0, 2.0]], [[6.787560046220868, 5.0959903785636405, 1.4671997886503312, 1.0, 2.0], [7.863797379263989, 16.482551177788068, 3.04066485061798, 1.0, 2.0]], [[6.7227405881454105, 4.640446447207367, 1.3246240024873897, 1.0, 2.0], [7.844001209216317, 14.342343028657483, 2.743906627489419, 1.0, 2.0]], [[6.656535921018625, 4.242799427348785, 1.201699066481601, 1.0, 2.0], [7.804737336950292, 12.199695450745219, 2.4566010767611606, 1.0, 2.0]], [[6.591112018029047, 3.901646584557925, 1.099650953799308, 1.0, 2.0], [7.74751112650143, 10.106533917516009, 2.1790837591701737, 1.0, 2.0]], [[6.528919557785369, 3.610972870117769, 1.0160023815217738, 1.0, 2.0], [7.674506383194503, 8.44018410317642, 1.9109813716410362, 1.0, 2.0]], [[6.472149910525918, 3.3616419458296023, 0.9468611788556067, 1.0, 2.0], [7.588006071893942, 6.958279678351489, 1.658765705233999, 1.0, 2.0]], [[6.412536872319223, 3.142412581548078, 0.8903994806506331, 1.0, 2.0], [7.491943190679957, 5.873048225109807, 1.4235876662162767, 1.0, 2.0]], [[6.353887649516527, 2.9416425804557442, 0.8410892253353784, 1.0, 2.0], [7.3896900695079095, 5.012718321966485, 1.2079656527428466, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.283052145889655, 4.26484675352124, 1.0114299624584693, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.172278347281164, 3.5763544358649684, 0.8335676871730212, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.059012298839661, 2.9219528160942403, 0.675555605692981, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.94746737320656, 2.30746637742044, 0.54125462119968, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.012555856674646, -2.8590519676261868e-05, -0.7379912665892253, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.728479359387773, 1.4556539451522426, 0.35583350780957845, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.635817179460859, 1.19103357663911, 0.29601290733608837, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.565220729028778, 0.9717653261285559, 0.24443318970722355, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.5145792211566125, 0.7652493812831448, 0.1941120332627825, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.480053999209073, 0.5668159468289986, 0.14464477039275117, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.459227950898506, 0.3805555930268385, 0.09754425821968238, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.451248945969792, 0.2150022188991485, 0.055283836509057716, 1.0, 2.0]]], [[[5.094651974579863, 21.87018429989767, 9.14940679938708, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.144263305997046, 24.004463696837817, 6.703923214136647, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.44358798865614, 23.919256930778598, 6.072793087098443, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.630069499718187, 23.408844228302634, 5.646242342853737, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.795954091862972, 22.77221351797716, 5.275476061483389, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.921749686928916, 21.96089200198071, 4.970725529662224, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.020884432894838, 20.99055528268326, 4.702670325603514, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.106436464006536, 19.905041030879865, 4.447758724603893, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.175839117630556, 18.976748972381504, 4.199982467811195, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.232113416602713, 17.91536166177241, 3.9508978238593944, 1.0, 2.0], [7.092419136440295, 33.83912096635177, 5.999999999999999, 0.7999999999999998, 2.0]], [[7.275036238500418, 16.699811696444076, 3.6928953053344773, 1.0, 2.0], [7.172722410242505, 33.509508650072085, 5.999999999999999, 0.7999999999999998, 2.0]], [[7.300721711287029, 15.33636312883414, 3.428089770208465, 1.0, 2.0], [7.297631573084078, 33.486677420298626, 5.999999999999999, 0.8199999999999998, 2.0]], [[7.30815394217838, 13.880417831243625, 3.164693878887489, 1.0, 2.0], [7.465575034130366, 33.792958746239655, 5.999999999999999, 0.8599999999999999, 2.0]], [[7.299638729945333, 12.396975923125906, 2.9090422274005174, 1.0, 2.0], [7.704803497269035, 33.65190174874534, 5.721079758926345, 0.8999999999999999, 2.0]], [[7.278908997626949, 10.94518751639577, 2.665928914528607, 1.0, 2.0], [7.973695505000149, 33.65693013945511, 5.46746206874963, 0.96, 2.0]], [[7.248134142201148, 9.628401862885768, 2.4353568667206176, 1.0, 2.0], [8.158040048994009, 32.559303046941665, 5.185333220000191, 1.0, 2.0]], [[7.20852929397571, 8.533967659941775, 2.216735965580516, 1.0, 2.0], [8.288555647493814, 29.654020355594266, 4.2812495449283725, 1.0, 2.0]], [[7.161714809043888, 7.509009792180262, 2.0115039962822205, 1.0, 2.0], [8.342763895884175, 26.993904706121434, 3.9035032601695465, 1.0, 2.0]], [[7.108847542174188, 6.657066417178027, 1.8208201396333703, 1.0, 2.0], [8.370434083373736, 24.006806294265868, 3.551661230410243, 1.0, 2.0]], [[7.051178300049259, 5.995154481229294, 1.6478748775169776, 1.0, 2.0], [8.373007179475138, 20.75863692834987, 3.2261901064900353, 1.0, 2.0]], [[6.99035801778649, 5.457333736683059, 1.4962609249720888, 1.0, 2.0], [8.352861543258129, 18.06353590620416, 2.9122542392206108, 1.0, 2.0]], [[6.928490321513709, 5.014369863828597, 1.3678724410975471, 1.0, 2.0], [8.31110240348825, 15.547099960521138, 2.6105396891617976, 1.0, 2.0]], [[6.867902435312685, 4.649557597181783, 1.262206846573735, 1.0, 2.0], [8.24951931461366, 13.072085033573794, 2.322111998981568, 1.0, 2.0]], [[6.80132039500972, 4.347448410417161, 1.1792179757627894, 1.0, 2.0], [8.17072500083289, 10.70856263739708, 2.0472725584053815, 1.0, 2.0]], [[6.738118660106176, 4.092667751683931, 1.1112310970542278, 1.0, 2.0], [8.078329004139121, 8.76889246851043, 1.785121372028533, 1.0, 2.0]], [[6.683211038200025, 3.8688707965516143, 1.05149812729228, 1.0, 2.0], [7.965428825052528, 7.138853399007136, 1.5427799409916363, 1.0, 2.0]], [[6.6362274061489686, 3.6608377175077274, 0.9952256209282043, 1.0, 2.0], [7.842744392400482, 5.953960110827039, 1.3179708695260863, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.714109859224545, 5.036994823219145, 1.1107754448469929, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.579694835835064, 4.246669313980828, 0.9220947057666044, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.442250499628127, 3.5323111485473677, 0.7547473287771352, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.307887778855837, 2.88576634704195, 0.6137386019279225, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.18264806493053, 2.3222073260415534, 0.502256074302551, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.072844345333071, 1.8925184533394799, 0.4174261964965807, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.983112023954585, 1.5671817380906334, 0.35094878049065154, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.915079140892514, 1.2856506855066354, 0.2910840413652241, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.866012872653916, 1.0166782189344665, 0.2319892973665472, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.828478117959888, 0.7575426270206516, 0.1740391408237853, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.803919859051619, 0.5137832355505532, 0.11868287055723728, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.794411877528704, 0.2960852264618836, 0.06863620128428292, 1.0, 2.0]]], [[[5.239332056451019, 27.415288183743893, 10.216647063583872, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.2897957715378165, 29.39416280290847, 7.5289152300556506, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.600360411681702, 29.245197268082855, 6.797543305550665, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.832608814790953, 28.789534676776128, 6.253254483054812, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.998782705795852, 28.097996392083957, 5.839570258459924, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.141153165438312, 27.26142785139635, 5.477341495098694, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.265672564481034, 26.269724058642765, 5.146879588866029, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.371594662872126, 25.099874953772566, 4.840390121941954, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.4583013740458295, 23.733158799354218, 4.552035220502035, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.529086584107301, 22.12340346371383, 4.267136374136022, 1.0, 2.0], [6.778315557174634, 31.691573803108497, 5.999999999999999, 0.62, 0.899999999999999]], [[7.582197189116295, 20.231614680019984, 3.9801494211730017, 1.0, 2.0], [6.870118958020797, 31.695478742655972, 5.999999999999999, 0.62, 0.899999999999999]], [[7.6138737424741025, 18.604369658220357, 3.686453099609278, 1.0, 2.0], [6.945912528147888, 31.344778494495742, 5.999999999999999, 0.62, 0.899999999999999]], [[7.623487170957492, 16.935056647392674, 3.4007447003853617, 1.0, 2.0], [7.034087988329554, 30.573268761300557, 5.891563232654268, 0.62, 0.899999999999999]], [[7.6145674228726286, 15.237912666762952, 3.1292095424237534, 1.0, 2.0], [7.345784262085853, 33.623970099068316, 5.716687476008333, 0.6399999999999997, 2.0]], [[7.591499719558748, 13.575452662410779, 2.874345114343952, 1.0, 2.0], [7.675865235973898, 33.64486893353354, 5.400489747889605, 0.6999999999999997, 2.0]], [[7.556513742049596, 11.97161290996916, 2.6345087822133406, 1.0, 2.0], [8.05995844939496, 33.68817504137765, 5.070109748558772, 0.7799999999999998, 2.0]], [[7.51124238859874, 10.445371739889255, 2.4088665520620993, 1.0, 2.0], [8.435430312302907, 33.664203341720984, 4.795138004539108, 0.8799999999999999, 2.0]], [[7.457476114106494, 9.18200922147399, 2.1955657089744185, 1.0, 2.0], [8.779522569562994, 33.90555574697603, 4.638875516978371, 1.0, 2.0]], [[7.3966000426523735, 8.091105800739388, 1.9980826693809728, 1.0, 2.0], [8.8639813384304, 29.80166808533521, 3.7779157457804677, 1.0, 2.0]], [[7.330122458689714, 7.134663826973303, 1.821195975303051, 1.0, 2.0], [8.870937707624028, 26.34538309194243, 3.4006107797408274, 1.0, 2.0]], [[7.26035626554402, 6.421406965151531, 1.6671824670731326, 1.0, 2.0], [8.8510563717393, 22.62430679771653, 3.0614454305057435, 1.0, 2.0]], [[7.189419949763848, 5.8854060659773655, 1.5393382888665041, 1.0, 2.0], [8.806296376829623, 19.070904427210404, 2.749207632305507, 1.0, 2.0]], [[7.120852454105224, 5.468691872106864, 1.4351537216679122, 1.0, 2.0], [8.739859477351459, 16.231781093024395, 2.4494462725760946, 1.0, 2.0]], [[7.0576744529552204, 5.13609914437256, 1.3501702813542864, 1.0, 2.0], [8.654632993686494, 13.500137566484286, 2.1671513431383174, 1.0, 2.0]], [[7.001799168616514, 4.85971850659582, 1.2782833580268385, 1.0, 2.0], [8.554848837297085, 10.946174563964062, 1.9004282219433888, 1.0, 2.0]], [[6.95365097217417, 4.617472771577808, 1.2137075646869557, 1.0, 2.0], [8.442864742324128, 8.855332228203068, 1.6483365117502147, 1.0, 2.0]], [[6.912667039822334, 4.390890205473397, 1.1513087720166342, 1.0, 2.0], [8.313388581474689, 7.143642546910204, 1.4145485395728241, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [8.17683525726965, 5.907892258426841, 1.1963250986536198, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [8.030697679458036, 4.9571185617261255, 0.9971378531917371, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.86753704946749, 4.153075191628574, 0.8235626272153183, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.709008907235316, 3.462142609827311, 0.6781630457772098, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.562368546236133, 2.8773781914244716, 0.5636174067843639, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.43500450523736, 2.388893861263614, 0.4757517776897964, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.3318241783502955, 1.9958134114999928, 0.404748620281771, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.254012672010611, 1.6447939245277705, 0.3380392904337977, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.197390938677085, 1.3059491740756957, 0.2710225549861916, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.158410434693116, 0.9776067636858864, 0.2043205254461559, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.1346555595970145, 0.6680537625579663, 0.14033755808114648, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.125372518850474, 0.39085259904674036, 0.08238236864028278, 1.0, 2.0]]]], "sails": ["MN1 + J1", "MN1 + S1"], "twa": [28.0, 32.0, 36.0, 40.0, 44.0, 48.0, 52.0, 56.0, 60.0, 64.0, 68.0, 72.0, 76.0, 80.0, 84.0, 88.0, 92.0, 96.0, 100.0, 104.0, 108.0, 112.0, 116.0, 120.0, 124.0, 128.0, 132.0, 136.0, 140.0, 144.0, 148.0, 152.0, 156.0, 160.0, 164.0, 168.0, 172.0, 176.0, 180.0], "tws": [2.0576, 3.0864, 4.1152, 5.144, 6.1728, 7.201599999999999, 8.2304, 9.2592, 10.288]} \ No newline at end of file diff --git a/dat/polars_YD41.json b/dat/polars_YD41.json new file mode 100644 index 0000000..d6a3bd5 --- /dev/null +++ b/dat/polars_YD41.json @@ -0,0 +1 @@ +{"name": "YD41", "results": [[[[1.466443204466604, 1.9687236635422556, 9.744372563963667, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[2.1720303711293996, 2.453096912515347, 5.531985750853971, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[2.5805310238664863, 2.7313866252033225, 4.376929495858492, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[2.9039146927016426, 2.9146670952734364, 3.7112808645435815, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[3.1746678038633656, 3.0329197656697455, 3.249206904895008, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[3.3959354436770393, 3.0945444851968564, 2.90740737260603, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[3.5826369069803463, 3.117805068964392, 2.63645809828962, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[3.7340452396056336, 3.107194968776223, 2.418903678467735, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[3.8530794217521245, 3.0658273953577257, 2.2384437634863223, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[3.9458569837200574, 2.998692277091182, 2.0828082304710214, 1.0, 2.0], [2.8875400292098345, 3.262039654400316, 3.7333011562554224, 1.0, 2.0]], [[4.0112434373235795, 2.9055342006915037, 1.947223158975777, 1.0, 2.0], [3.1323028133869384, 3.426888532590636, 3.3646681099161153, 1.0, 2.0]], [[4.051367615214732, 2.787544870503339, 1.8262231003921967, 1.0, 2.0], [3.3471480298356773, 3.5538672565724174, 3.0793251220647893, 1.0, 2.0]], [[4.066885279592211, 2.64472345206314, 1.7164631247544897, 1.0, 2.0], [3.536457931670645, 3.640917440078428, 2.841052832064935, 1.0, 2.0]], [[4.059816750921132, 2.477185230421188, 1.614442581654355, 1.0, 2.0], [3.69821507047096, 3.685268695905525, 2.6361732180106827, 1.0, 2.0]], [[4.02879501967821, 2.2900315421291935, 1.5190417064916752, 1.0, 2.0], [3.828103974393655, 3.6845078014672588, 2.458392454866436, 1.0, 2.0]], [[3.9740914404980203, 2.089359914043716, 1.428243827990522, 1.0, 2.0], [3.930967336607002, 3.6402342110477908, 2.295324747664335, 1.0, 2.0]], [[3.8997429652050073, 1.8786505303329295, 1.3375962718879835, 1.0, 2.0], [4.006816730829385, 3.5538947129486553, 2.143397036918551, 1.0, 2.0]], [[3.8074895431658784, 1.6596104513916887, 1.2431824968849148, 1.0, 2.0], [4.056398024961588, 3.428149379254611, 2.0001162645048693, 1.0, 2.0]], [[3.6923583397779485, 1.4325973514725254, 1.1442929606881083, 1.0, 2.0], [4.08161163777306, 3.2672948319562383, 1.8638970593303739, 1.0, 2.0]], [[3.5431122928454672, 1.2009451940692293, 1.0447880510088887, 1.0, 2.0], [4.08570851495697, 3.0771918441537056, 1.7338849712729054, 1.0, 2.0]], [[3.3724994413656493, 0.9831441679017222, 0.9467674787373512, 1.0, 2.0], [4.070250669991137, 2.8592937321757206, 1.6086752942125486, 1.0, 2.0]], [[3.19497418089981, 0.7937094561317898, 0.8538886609991772, 1.0, 2.0], [4.03470561180401, 2.609997855525801, 1.4862254047088455, 1.0, 2.0]], [[3.010744577170006, 0.6336317289703843, 0.7690615896853764, 1.0, 2.0], [3.97791828114129, 2.328081182677475, 1.364384555864072, 1.0, 2.0]], [[2.830173673899916, 0.5044700064446328, 0.6928448987413969, 1.0, 2.0], [3.8975595347058376, 2.0294481301570446, 1.240628923810526, 1.0, 2.0]], [[2.6600290701375044, 0.4055746672513865, 0.6289902987710352, 1.0, 2.0], [3.7923082614234946, 1.7210377560164627, 1.1130611056070263, 1.0, 2.0]], [[2.4998011983736195, 0.3341831688968562, 0.5844054883793821, 1.0, 2.0], [3.6649706575505863, 1.4152754924719901, 0.9817836824541604, 1.0, 2.0]], [[2.3620814619730774, 0.2860474427126747, 0.5576380631952852, 1.0, 2.0], [3.5140168308904647, 1.1231306288263765, 0.8493286351382265, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.349554832264497, 0.8603103619715405, 0.7179796851677082, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.1759412913624328, 0.6350139184517128, 0.5915252699131046, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.9941862243209756, 0.4521556659814602, 0.47578091018471186, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.8179125070408717, 0.31479351150847973, 0.3749083395299914, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.6528854236248343, 0.2152846741063297, 0.2887582996711997, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.4936682395108942, 0.14473300131291522, 0.21811110435138142, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.3540250425481553, 0.10066906996887316, 0.16842004190729562, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.2463491062857717, 0.07554124546881906, 0.1373075727923463, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.1729171669316885, 0.05792925734053906, 0.1115819329255666, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.126733194900092, 0.04152568195780101, 0.08302837111758321, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.1003641515331806, 0.02607159326453988, 0.05330391050485933, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [2.090852702985287, 0.012666082036369469, 0.0261378348744319, 1.0, 2.0]]], [[[2.4737716972693136, 4.239412055366887, 8.105210714606995, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[3.3431322606735128, 4.86011749701818, 5.305472561757502, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[3.908811739528998, 5.256908073968549, 4.292066658565061, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[4.345737393076694, 5.521811135487358, 3.6923305001427322, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[4.679365503318879, 5.663199116375839, 3.285201482956084, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[4.9621862204167755, 5.728411670523687, 2.963140445284797, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.20384604791947, 5.73803788712105, 2.701285906603281, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.408751656312433, 5.7026630409286705, 2.483822601833063, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.592037223961068, 5.636526483095674, 2.293097945551904, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.733432355768417, 5.524872917093433, 2.1309444266091453, 1.0, 2.0], [4.32880202548838, 6.170840852932877, 3.7015683447749193, 1.0, 2.0]], [[5.827938552827057, 5.367786387861557, 1.9924720986308297, 1.0, 2.0], [4.656031312081139, 6.51240728135608, 3.386665340901227, 1.0, 2.0]], [[5.882998801158907, 5.174844029238509, 1.8699119370624289, 1.0, 2.0], [4.949125464566528, 6.793350123274788, 3.124339547050959, 1.0, 2.0]], [[5.9028296857097855, 4.95214183815529, 1.7585516371506515, 1.0, 2.0], [5.209017506628885, 6.991374867841316, 2.895894745668189, 1.0, 2.0]], [[5.892395792733069, 4.704514604130625, 1.6542260658779344, 1.0, 2.0], [5.434642504814564, 7.08547338223687, 2.6913978993878964, 1.0, 2.0]], [[5.846627275239901, 4.4304981971563455, 1.5569309711580994, 1.0, 2.0], [5.630204004005143, 7.070688556052892, 2.501830487662018, 1.0, 2.0]], [[5.7690642665420935, 4.130534256329744, 1.462839075652214, 1.0, 2.0], [5.783930104502855, 6.94570299300006, 2.331330359720353, 1.0, 2.0]], [[5.654353261904591, 3.7970465000159352, 1.369772563364473, 1.0, 2.0], [5.89018116560321, 6.722290919738361, 2.1780146317630265, 1.0, 2.0]], [[5.50815206555406, 3.4268758634656633, 1.2728270114258955, 1.0, 2.0], [5.957314802786952, 6.429382152303828, 2.0343257751959993, 1.0, 2.0]], [[5.32300530391665, 3.010201772049142, 1.1720284858113454, 1.0, 2.0], [5.991938586300841, 6.094988036787457, 1.8972048615396049, 1.0, 2.0]], [[5.130866647232881, 2.5699325031099725, 1.0661059319946216, 1.0, 2.0], [5.998386847897338, 5.738584107012653, 1.765811500846544, 1.0, 2.0]], [[4.9133241495958355, 2.123916870061351, 0.9634702538693399, 1.0, 2.0], [5.977067280802476, 5.363638172964464, 1.6383592409060224, 1.0, 2.0]], [[4.692210785641474, 1.7363229739299713, 0.865912729323033, 1.0, 2.0], [5.926917316839581, 4.968167445754529, 1.5128270910719752, 1.0, 2.0]], [[4.4676315428486095, 1.4067299548961283, 0.7751709118012813, 1.0, 2.0], [5.844810893360401, 4.546491559569983, 1.3873767873465506, 1.0, 2.0]], [[4.250250874514544, 1.1363295162292086, 0.6918306839424475, 1.0, 2.0], [5.722533692257139, 4.086825796394425, 1.2605063596478059, 1.0, 2.0]], [[4.011505372142932, 0.9164580265579376, 0.6249281345941338, 1.0, 2.0], [5.558018538208868, 3.5826047528204943, 1.130782499938136, 1.0, 2.0]], [[3.7855576139873435, 0.7551794261397828, 0.5759811779114802, 1.0, 2.0], [5.361779835037904, 3.0350824231027764, 0.9972616158233976, 1.0, 2.0]], [[3.587364727573536, 0.6442966132630777, 0.5447200785904941, 1.0, 2.0], [5.156664544329106, 2.450695647772455, 0.8601333147287983, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.933741068728726, 1.8858304140173443, 0.7253266320789954, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.701409877731261, 1.402118185985001, 0.5960753501604621, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.462630093478949, 1.0088142660229173, 0.47786487286300927, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.232501252330723, 0.7092681077069728, 0.3743914027981775, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.9927292545077275, 0.48575263267485635, 0.28764526034204585, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.763727037817833, 0.32655420615687825, 0.216093484782218, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.5595474482692775, 0.22601610130131203, 0.16544594374605753, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.399169785855831, 0.16860227906745454, 0.13388534869494778, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.2895012522299623, 0.1289442564553336, 0.10839614450279286, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.2206020704173444, 0.09226576104219801, 0.08045243839647087, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.1813245758636053, 0.05776697212422583, 0.0514812869375764, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [3.167183267914418, 0.027886920081926125, 0.02508003197389535, 1.0, 2.0]]], [[[3.4160236519060483, 6.876493082484946, 7.609850656320179, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[4.435839532533728, 8.256516945447286, 5.261663820638255, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.089993290839504, 9.13978045194458, 4.341801284285353, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.688137908921774, 9.834409753238024, 3.693248011804889, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.170146155545628, 10.300089554806501, 3.2479002773052916, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.53695025455404, 10.475140953295988, 2.9284339524296974, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.8168804750535275, 10.422746984963046, 2.685358809847546, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.0187849720317175, 10.18347481307092, 2.495391563990694, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.170255059526085, 9.8494021952405, 2.334679222971469, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.286176726706274, 9.471721980794177, 2.1919123713319975, 1.0, 2.0], [5.716118213535412, 11.922300149963686, 3.6548221459414965, 1.0, 2.0]], [[7.362198226188086, 9.01626171254767, 2.0647705900977047, 1.0, 2.0], [6.17411096630814, 12.847487673090924, 3.3000693728708117, 1.0, 2.0]], [[7.3938597832135, 8.481882536530017, 1.9509566633652773, 1.0, 2.0], [6.5579264012544165, 13.55172062292723, 3.0367729295963883, 1.0, 2.0]], [[7.40581428087879, 7.906069442881667, 1.8406253054915909, 1.0, 2.0], [6.871512751487709, 13.993055339733411, 2.828796061766008, 1.0, 2.0]], [[7.396716724711996, 7.298232990952195, 1.7331150454896, 1.0, 2.0], [7.110172482998592, 14.134054036945466, 2.659745808582534, 1.0, 2.0]], [[7.367108912318681, 6.733854080061361, 1.6264194711658773, 1.0, 2.0], [7.299239690768222, 13.992317451171928, 2.504092221278225, 1.0, 2.0]], [[7.303354895793049, 6.197336507103388, 1.5209415078787725, 1.0, 2.0], [7.420000579437259, 13.560979305800466, 2.3680568266588895, 1.0, 2.0]], [[7.207640266405301, 5.672094871305494, 1.4130721359579417, 1.0, 2.0], [7.496743637083899, 12.897915898801978, 2.2373699616557596, 1.0, 2.0]], [[7.083211621031964, 5.1495841668297615, 1.300987301930698, 1.0, 2.0], [7.547155555372408, 12.073112207078978, 2.106477182882998, 1.0, 2.0]], [[6.921674118849875, 4.620590600817437, 1.187086183608728, 1.0, 2.0], [7.574482828704005, 11.135935533922382, 1.9769698683880905, 1.0, 2.0]], [[6.706691699783162, 4.0767076005471985, 1.076119889700563, 1.0, 2.0], [7.580062409926973, 10.102371389180423, 1.8474975386669357, 1.0, 2.0]], [[6.456736771694664, 3.534230195833946, 0.970136608989394, 1.0, 2.0], [7.563660877145085, 9.153382921743995, 1.7145340543570338, 1.0, 2.0]], [[6.159252544053584, 2.9857327583566726, 0.8739988260225386, 1.0, 2.0], [7.521899120227662, 8.15262209821508, 1.5790488531487072, 1.0, 2.0]], [[5.844979515612433, 2.443872441097345, 0.7861309803886841, 1.0, 2.0], [7.453909235947439, 7.114533546522301, 1.4393257656290708, 1.0, 2.0]], [[5.505350871155961, 1.9598742536758706, 0.7105156888517455, 1.0, 2.0], [7.36018146311178, 6.231274241774936, 1.2940461144152569, 1.0, 2.0]], [[5.198972903340579, 1.5931694529298663, 0.6460657267143588, 1.0, 2.0], [7.200722491967303, 5.429562743786443, 1.151458386016806, 1.0, 2.0]], [[4.932329310639427, 1.3287357072660104, 0.5963849590255295, 1.0, 2.0], [7.011406621684053, 4.684393062814901, 1.007125994659468, 1.0, 2.0]], [[4.7056087971925855, 1.1436891723201206, 0.5615652294072029, 1.0, 2.0], [6.782134654651256, 3.956287179118778, 0.8650821090571785, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.511839090142024, 3.220926309134457, 0.7282216604362122, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.207056625604219, 2.4575190658573103, 0.5990074181723118, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.879148374589276, 1.7657897967169538, 0.48185739128650046, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.542005060523215, 1.2368785118568464, 0.38073441199484026, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.229484731355715, 0.8509308507092556, 0.29354173786198684, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.944422189404172, 0.5768050806066433, 0.22097970516077658, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.697233658240025, 0.4029325213220468, 0.16927698376347866, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.503447849730793, 0.30149733644562215, 0.13636099473250787, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.3701980155430995, 0.23020826597023256, 0.10963531522001918, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.286014876671067, 0.16444106518039778, 0.0809591484180229, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.237885213953814, 0.10284661050401787, 0.05165065997516278, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [4.220535925698012, 0.04963912768630499, 0.025139838505752223, 1.0, 2.0]]], [[[4.3642150280086796, 11.882147948856021, 7.158246488665328, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[5.50681677537023, 14.233473348786706, 5.118613423134392, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.3825014339288915, 15.91261321042148, 4.141158708661303, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.974640431075687, 16.778213191337628, 3.611776559116545, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.379571077170424, 17.029509592399467, 3.265000687166804, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.613933062472819, 16.76874580273004, 3.032854268242068, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.804380770841126, 16.370807679005825, 2.8364761375630336, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.936720513332981, 15.814947802908991, 2.674522851682463, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.024035596572169, 15.136914076036154, 2.5353330124912743, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.090579017597797, 14.387800130975721, 2.405538751852339, 1.0, 2.0], [7.051306267683094, 20.108340050273437, 3.559626413862963, 1.0, 2.0]], [[8.137223153878395, 13.567113467673497, 2.2823060448803063, 1.0, 2.0], [7.473120937762979, 21.766399938110503, 3.3182981067396953, 1.0, 2.0]], [[8.165730158888728, 12.674068290511013, 2.162390372310828, 1.0, 2.0], [7.774596254468748, 22.832115722673148, 3.156817215865508, 1.0, 2.0]], [[8.173517987737467, 11.709834090558147, 2.0455808193666676, 1.0, 2.0], [7.990792589871771, 23.243919876056097, 3.021143821950834, 1.0, 2.0]], [[8.162723044331074, 10.675121681645562, 1.9285210390495477, 1.0, 2.0], [8.148612070834194, 23.055215813426624, 2.8882402967166225, 1.0, 2.0]], [[8.135413951920725, 9.643178125656334, 1.8069672464855289, 1.0, 2.0], [8.27621148292921, 22.386007003837644, 2.746398642037713, 1.0, 2.0]], [[8.09203935757228, 8.669696507277022, 1.678131910229813, 1.0, 2.0], [8.375838563109133, 21.331495244124508, 2.602163027355578, 1.0, 2.0]], [[8.030269112284147, 7.6484521819350535, 1.5434705598412726, 1.0, 2.0], [8.456546290688877, 20.002224977817356, 2.4581798913945008, 1.0, 2.0]], [[7.950157104376421, 6.708343910469188, 1.4054282743142668, 1.0, 2.0], [8.516458350608016, 18.86494526466371, 2.311825155380926, 1.0, 2.0]], [[7.844363268103766, 5.940125242138668, 1.2686769609423065, 1.0, 2.0], [8.55085475944107, 17.5577817336795, 2.167630084433105, 1.0, 2.0]], [[7.68736440992961, 5.254011394046409, 1.140586070280918, 1.0, 2.0], [8.55755651793513, 16.0768142281158, 2.023725950675772, 1.0, 2.0]], [[7.520153518947545, 4.644549192403128, 1.018794487488612, 1.0, 2.0], [8.534118399950524, 14.422085340747946, 1.8777984923067357, 1.0, 2.0]], [[7.334639926532408, 4.079281746562334, 0.9052741369763955, 1.0, 2.0], [8.478794984978569, 12.607570454410384, 1.7274846942029563, 1.0, 2.0]], [[7.060801637969866, 3.5118303878879367, 0.807770511666708, 1.0, 2.0], [8.394617561069513, 10.68774601745193, 1.5716858888363716, 1.0, 2.0]], [[6.768895245672483, 2.9753608320715608, 0.7211145981066445, 1.0, 2.0], [8.29432889177885, 8.958401933452821, 1.407391075784011, 1.0, 2.0]], [[6.4530787150745965, 2.4753627933603837, 0.6508907295350319, 1.0, 2.0], [8.174634275402592, 7.375209465826472, 1.2397022594752767, 1.0, 2.0]], [[6.126395352020852, 2.0701934785647125, 0.6017948123207337, 1.0, 2.0], [8.039257077036119, 6.136738360008387, 1.0715340734605787, 1.0, 2.0]], [[5.834157149025811, 1.7857540007662183, 0.5700157276053274, 1.0, 2.0], [7.8918836781162, 5.176110479705312, 0.9076256362224912, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.681014172485156, 4.304013400906469, 0.7558612135768463, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.451399931750207, 3.486439209939955, 0.6150621064789923, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.164573218422065, 2.669371176760713, 0.4911912210916965, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.8445454554581255, 1.908750988774421, 0.3850090860676341, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.5026339913776185, 1.3239007005635546, 0.2952505708278026, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.155397783049666, 0.8997451462708713, 0.22234033501838862, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.839633231208529, 0.6305829493313966, 0.17134451071260648, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.58257766989246, 0.4747137705829874, 0.13966991061577766, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.405325870500573, 0.364144223638794, 0.11333137433701496, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.295637495910358, 0.26089021014966546, 0.08412677220292253, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.235859806218856, 0.1636792484137666, 0.053850959811672594, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [5.2136970767769535, 0.07960933995014603, 0.026420765635489857, 1.0, 2.0]]], [[[5.271797507690975, 18.41058676544011, 6.792149167763524, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[6.626997589870429, 22.00206964635687, 4.865699750636275, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.379744276845155, 23.996495260271196, 4.154276147205825, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.787687624293106, 24.456247829626715, 3.781560352548062, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.050841298439018, 24.13150592932138, 3.5077890637125044, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.253617970871044, 23.503197127707164, 3.280894062480746, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.432030858755256, 22.752362533663295, 3.080176144732135, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.5960017868478, 21.9007468961329, 2.8965063707093144, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.727706385221893, 20.8872297003851, 2.73410468201566, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.82913398650827, 19.78614932131451, 2.586603405063335, 1.0, 2.0], [7.916183731688818, 31.851368270820277, 4.458654153285521, 1.0, 2.0]], [[8.901871723494377, 18.802941098768176, 2.4464026080891936, 1.0, 2.0], [8.232013642324942, 33.862183617239396, 4.308536565444078, 1.0, 2.0]], [[8.944183257540443, 17.697405046255405, 2.3151032995849388, 1.0, 2.0], [8.524176716738884, 34.88361194004348, 4.109649193130662, 1.0, 2.0]], [[8.954957935053045, 16.464662006204385, 2.189886642168701, 1.0, 2.0], [8.730937924976706, 33.74522035264, 3.7593389141333615, 0.96, 2.0]], [[8.936883115713156, 15.098499644577041, 2.065327973774253, 1.0, 2.0], [9.03386356448439, 34.664923842314046, 3.6383964297634646, 1.0, 2.0]], [[8.893641730525081, 13.59823148615443, 1.9360264361793396, 1.0, 2.0], [9.24237979238923, 33.661878871114205, 3.397507269044959, 1.0, 2.0]], [[8.820535422285863, 11.969112118174703, 1.8009624818264982, 1.0, 2.0], [9.41690215408725, 32.180447902770304, 3.16568041128614, 1.0, 2.0]], [[8.715721123711772, 10.263744300537562, 1.6622710162024625, 1.0, 2.0], [9.558425852823428, 30.256958625028776, 2.944768563514454, 1.0, 2.0]], [[8.585432841985465, 8.807328990262986, 1.5199074822241516, 1.0, 2.0], [9.699674570752213, 28.540440005108486, 2.3954518878281683, 1.0, 2.0]], [[8.437976886593935, 7.464563121287061, 1.37967547591743, 1.0, 2.0], [9.753280007276892, 26.438325504033838, 2.226808893436724, 1.0, 2.0]], [[8.2929027472294, 6.417180302954603, 1.2416657566974816, 1.0, 2.0], [9.761037934581877, 23.928875415481635, 2.0688190460975413, 1.0, 2.0]], [[8.145687040294893, 5.625400076187949, 1.1091095768761647, 1.0, 2.0], [9.718541958410551, 20.975157619911, 1.9199990228937278, 1.0, 2.0]], [[7.996047233770285, 4.96244272185838, 0.9842111350772786, 1.0, 2.0], [9.62751775899286, 18.269598748858964, 1.7703429624778175, 1.0, 2.0]], [[7.832779562513072, 4.373224366446276, 0.869532368043038, 1.0, 2.0], [9.493760457886879, 15.654212408660836, 1.6178297894710554, 1.0, 2.0]], [[7.618602961617297, 3.829043065946514, 0.7721471622254084, 1.0, 2.0], [9.327486267495194, 12.9712449057027, 1.4617994425326744, 1.0, 2.0]], [[7.405406993779127, 3.3469387554528254, 0.6889246739001036, 1.0, 2.0], [9.133897225132309, 10.321490190968664, 1.3014843620570087, 1.0, 2.0]], [[7.138853095611724, 2.9208774290242108, 0.6301402373029622, 1.0, 2.0], [8.918322640413637, 8.177389658614658, 1.1357560714217363, 1.0, 2.0]], [[6.877809264532507, 2.5683534565813053, 0.5892836770837625, 1.0, 2.0], [8.69614942646731, 6.440265937491049, 0.9691081400663917, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [8.462705030762637, 5.252611242747065, 0.8081013492993321, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [8.239039972077629, 4.292993740666003, 0.6581412222310874, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [8.023484739803664, 3.437973286501897, 0.5235478782905028, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.794379225978326, 2.6165051162956128, 0.40684972088279897, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.523436975467532, 1.8558520144250321, 0.30867101692688104, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.228066291368693, 1.2857510475582707, 0.23011192286415832, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.918803801045706, 0.9117494925253345, 0.1763514042501875, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.65575785245582, 0.6876668169187401, 0.14229516646999113, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.469278045634348, 0.5259544418682607, 0.11426599585467954, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.346446855146636, 0.3763181638524641, 0.08448781944091113, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.27421108484278, 0.23621301316323487, 0.05411996194470756, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [6.247457113443134, 0.1149961185857931, 0.02657961411580675, 1.0, 2.0]]], [[[6.288645029947406, 27.48090718410077, 6.327375740656448, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.188384094707268, 30.42223035257324, 5.9910657258275855, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.850363208894048, 32.43047971268309, 5.253434042902328, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.213826906950262, 32.53515862197341, 4.8126630538567845, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.542679529894022, 32.15221223170792, 4.414625563788253, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.850654937194147, 31.5884223117675, 4.065296587970819, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[9.134004301719385, 30.827522531787512, 3.757428831259385, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[9.444209864676372, 29.999997225030516, 3.030295854463297, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[9.656451793921251, 29.118279652701652, 2.837431113010142, 1.0, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[9.810398436433859, 27.928536736826146, 2.657264132052136, 1.0, 2.0], [7.521116867199482, 33.589169755583335, 4.49537604049554, 0.6599999999999997, 2.0]], [[9.92261657682547, 26.49641636703136, 2.4949863961192342, 1.0, 2.0], [7.798508383495907, 33.95887712850115, 4.173715754001697, 0.6399999999999997, 2.0]], [[9.983447027228003, 24.792681395627557, 2.3515287282068176, 1.0, 2.0], [7.97222278150839, 33.420821428835865, 3.9090884048648964, 0.6199999999999997, 2.0]], [[10.000389432749698, 22.80397247667701, 2.2199738030771083, 1.0, 2.0], [8.274009130005508, 33.887936411463926, 3.7108517258635243, 0.6399999999999997, 2.0]], [[9.974360596459947, 20.498898380766086, 2.095728540835119, 1.0, 2.0], [8.581441287151572, 33.99096695876541, 3.496558928686289, 0.6599999999999997, 2.0]], [[9.90589819047706, 18.44609050257046, 1.9671231331466568, 1.0, 2.0], [8.88750692599785, 33.73937237562363, 3.2756928020670513, 0.6799999999999997, 2.0]], [[9.787011929631571, 16.3237193481282, 1.8369261060257243, 1.0, 2.0], [9.298635505357382, 33.90443248235699, 3.0585835632080944, 0.7199999999999998, 2.0]], [[9.62275171984152, 14.094272606738388, 1.7070000497492734, 1.0, 2.0], [9.690980052584367, 33.61609623321106, 2.8447051626929474, 0.7599999999999998, 2.0]], [[9.42408219918409, 11.877471516304732, 1.5778115271562885, 1.0, 2.0], [10.16442029638274, 33.65261390870469, 2.6470294101900045, 0.8199999999999998, 2.0]], [[9.213399190500132, 9.831586941773747, 1.4489225406549975, 1.0, 2.0], [10.591612444728877, 33.163405500986855, 2.4618761299292333, 0.8799999999999999, 2.0]], [[8.994058434390924, 8.250246269594156, 1.3197913601401297, 1.0, 2.0], [11.231251713907772, 34.72224168942384, 2.3634678117882926, 1.0, 2.0]], [[8.77882219701609, 6.895509927774853, 1.193406563986141, 1.0, 2.0], [11.199258872579549, 30.389370707871436, 2.1581077103662687, 1.0, 2.0]], [[8.567292663508592, 5.947772682077528, 1.0715513372727108, 1.0, 2.0], [11.099122460226177, 26.531334392653104, 1.7295378988192678, 1.0, 2.0]], [[8.362849694356703, 5.220010291650533, 0.9583425200173957, 1.0, 2.0], [10.899147058961244, 22.175426346716964, 1.5837848244015762, 1.0, 2.0]], [[8.18002239376749, 4.630577709235516, 0.8562420569395744, 1.0, 2.0], [10.651691473895774, 18.1546350643028, 1.4433686427380235, 1.0, 2.0]], [[8.007117654357069, 4.138639407185775, 0.7692071050171683, 1.0, 2.0], [10.366248131196807, 14.692663253872102, 1.2988772980263845, 1.0, 2.0]], [[7.8367134903465105, 3.7264573470857685, 0.6991180189987227, 1.0, 2.0], [10.060394135783215, 11.373027971776178, 1.1500602696712507, 1.0, 2.0]], [[7.64008351712097, 3.3806989893326724, 0.6492355375137421, 1.0, 2.0], [9.738153750452142, 8.599963771398713, 0.9956592055139517, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [9.407765110122698, 6.516619225835338, 0.8416354475353955, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [9.084592033407393, 5.199208364606906, 0.6948541612594556, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [8.77858108969821, 4.181027224260174, 0.5611862328421923, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [8.491309503264228, 3.2838552035357083, 0.4415113911398489, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [8.228969127462207, 2.430432431540474, 0.33684362355387015, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.9919348246606425, 1.7297282380488945, 0.25244947655996275, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.758790407420485, 1.260274351846204, 0.19343104249052911, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.54750308875917, 0.9607058368980056, 0.15441899206488938, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.393920356050865, 0.7326863119133138, 0.12179697747217351, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.2798943621761945, 0.5234124626169141, 0.0892932105748403, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.207645778026031, 0.32921526740114637, 0.05715463721562342, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.181419318307374, 0.1615663208810269, 0.028261872013729327, 1.0, 2.0]]], [[[6.813344146745539, 33.59323026925228, 5.999999999999999, 0.7799999999999998, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.306335598229763, 33.53486207217689, 4.638745365473838, 0.6599999999999997, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.740996260008162, 33.80733263690511, 4.065241139198995, 0.6399999999999997, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.100246775306529, 33.9202852614102, 3.8105628033347925, 0.6599999999999997, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.4012401267082, 33.66942506031045, 3.6031231887970505, 0.6799999999999997, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.722054662780106, 33.41399291399808, 3.397702504733758, 0.6999999999999997, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[9.11947047132698, 33.64700978010206, 3.256349833530392, 0.7399999999999998, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[9.525517714794642, 33.76963611660978, 3.1113556156138777, 0.7799999999999998, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[9.927576912905058, 33.717958904731546, 2.9676824466174865, 0.8199999999999998, 2.0], [0.0, 0.0, 0.0, 0.0, 0.0]], [[10.32108049432837, 33.45282700075287, 2.825452275768439, 0.8599999999999999, 2.0], [7.088509675430357, 33.63185526191406, 5.047710662221328, 0.62, 0.6999999999999988]], [[10.80564300883069, 33.65138732118693, 2.7166884350699325, 0.9199999999999999, 2.0], [7.390070109206815, 32.92435864858276, 4.54480056978802, 0.62, 0.5999999999999988]], [[11.24381229253054, 34.12097071999539, 2.693303738122572, 1.0, 2.0], [7.774741730037183, 33.60796419936823, 4.174705238106998, 0.62, 0.5999999999999988]], [[11.308138344809922, 31.375885072011204, 2.5107963607131016, 1.0, 2.0], [8.062218220289157, 33.761219413640035, 3.90147276015421, 0.62, 0.5999999999999988]], [[11.351860379195864, 28.67245937029696, 2.0491798204243454, 1.0, 2.0], [8.308663917321795, 33.45965861987841, 3.654924400409827, 0.62, 0.5999999999999988]], [[11.244173361855516, 25.664704695117884, 1.9160335850490458, 1.0, 2.0], [8.546929462327265, 32.70284876952238, 3.4028159538776697, 0.62, 0.5999999999999988]], [[11.051878273072248, 22.138122555978324, 1.7973818063266112, 1.0, 2.0], [8.923688237630435, 33.07957469733501, 3.1724894404011597, 0.62, 0.6999999999999988]], [[10.798110965812713, 18.751879075844453, 1.6856711213951274, 1.0, 2.0], [9.305802875482845, 33.27589896743976, 2.9415983170146576, 0.62, 0.7999999999999989]], [[10.498939650565735, 15.92631569535967, 1.5744381224126058, 1.0, 2.0], [9.67401248456851, 33.273839517294896, 2.7210615504928843, 0.62, 0.899999999999999]], [[10.19739880959122, 13.286467900864753, 1.4631658673795562, 1.0, 2.0], [10.180309554655686, 33.964593080556895, 2.503500010711249, 0.6399999999999997, 2.0]], [[9.889642909774565, 10.868721473637308, 1.3525978641385792, 1.0, 2.0], [10.876697128554802, 33.84783937579691, 2.2575705049051384, 0.7199999999999998, 2.0]], [[9.590288405154606, 8.935497735757428, 1.2399269192234383, 1.0, 2.0], [11.505464434798641, 33.11879476549905, 2.040906292033431, 0.7999999999999998, 2.0]], [[9.299577389701424, 7.3845964145291685, 1.1296729526232383, 1.0, 2.0], [12.323847026697168, 33.60274713199711, 1.8671925937819647, 0.9199999999999999, 2.0]], [[9.023293946838244, 6.292964661329746, 1.0266394924478577, 1.0, 2.0], [12.635322547554635, 31.42020555217304, 1.7384462729898578, 1.0, 2.0]], [[8.770503994990161, 5.543107677002834, 0.935303463385224, 1.0, 2.0], [12.327933060182435, 25.798029436971536, 1.3756619079300285, 1.0, 2.0]], [[8.542522548496285, 4.982742809495661, 0.8593085821904615, 1.0, 2.0], [11.923776194827521, 20.02155569567748, 1.248367234946065, 1.0, 2.0]], [[8.34514021604737, 4.546807896833424, 0.7965652344430985, 1.0, 2.0], [11.48174184636962, 15.769631775158215, 1.1187073016610856, 1.0, 2.0]], [[8.186302505211433, 4.192228865932309, 0.741923240808631, 1.0, 2.0], [11.020915949824643, 11.797271866791899, 0.9859881296068826, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [10.563009843630434, 8.588519844793533, 0.8481133161031785, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [10.126569082042202, 6.368123832445109, 0.710958581764812, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [9.718425290303996, 5.024084950018549, 0.581665532151974, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [9.33484934053363, 3.977240545561145, 0.46389176302435525, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [8.975237701329595, 3.058053266015285, 0.3610285023074956, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [8.660333580133546, 2.2528622662612823, 0.2791642992352101, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [8.397131513448263, 1.689703964593218, 0.22082621879234174, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [8.207899880498516, 1.3121317556310998, 0.17802131900384408, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [8.07525986603822, 1.0047086467603679, 0.1398900842485635, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.988401983425403, 0.7183162362031982, 0.1017336193778719, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.937539226368983, 0.45394536959041537, 0.06497759825943904, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [7.918878206013634, 0.22693673878258463, 0.032647158014265064, 1.0, 2.0]]], [[[6.572482912545406, 33.00445696377316, 5.914743263237024, 0.62, 1.5999999999999996], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.48153414632939, 33.783902952553035, 4.870884158594458, 0.62, 1.3999999999999995], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.946854464993051, 33.2254816482042, 4.367860912125525, 0.62, 1.2999999999999994], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.263926828855205, 33.77020497510605, 3.995345408324844, 0.62, 1.3999999999999995], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.549237076495809, 33.11469903520022, 3.6809425323222627, 0.62, 1.3999999999999995], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.829991896819282, 33.35719515353439, 3.3918177536695775, 0.62, 1.4999999999999996], [0.0, 0.0, 0.0, 0.0, 0.0]], [[9.084942872413615, 33.392045310986525, 3.136177477226683, 0.62, 1.5999999999999996], [0.0, 0.0, 0.0, 0.0, 0.0]], [[9.30989288456775, 33.17119605064004, 2.9073715419494737, 0.62, 1.6999999999999997], [0.0, 0.0, 0.0, 0.0, 0.0]], [[9.512966147050976, 33.604651674745185, 2.691079975864823, 0.62, 1.9], [0.0, 0.0, 0.0, 0.0, 0.0]], [[9.65373411220077, 31.757178357553645, 2.5098809236977275, 0.62, 1.9], [1.0414238203674515e-12, 33.95485038128803, 5.999997498754018, 0.6599999999999997, 2.0]], [[10.153329903262122, 33.54995397745437, 2.4263003251766313, 0.6599999999999997, 2.0], [1.394263293687138e-11, 33.68327450460481, 5.9999999499430725, 0.6799999999999997, 2.0]], [[10.6990534545304, 33.405867542326895, 2.3255760177302824, 0.7199999999999998, 2.0], [2.2818198987922742e-10, 33.664243949478205, 5.999999999999888, 0.7399999999999998, 2.0]], [[11.423856923035045, 33.86138649712342, 2.22574980734217, 0.7999999999999998, 2.0], [5.569228430975768, 34.99999999999999, 5.999999999999999, 0.62, 0.49999999999999867]], [[12.109303545259094, 33.99114956438282, 2.127097891065831, 0.8799999999999999, 2.0], [5.905271075192936, 34.99999999999999, 5.999999999999999, 0.62, 0.49999999999999867]], [[12.676041304306777, 33.57072729672309, 2.0458222578170258, 0.96, 2.0], [6.379536611256025, 34.99999999999999, 5.999999999999684, 0.62, 0.49999999999999867]], [[12.686510961052011, 30.32871244526521, 1.9630698976019039, 1.0, 2.0], [7.698922307948692, 34.99999999999999, 3.756549119248514, 0.62, 0.49999999999999867]], [[12.387182599861704, 26.009876061302286, 1.601728042095589, 1.0, 2.0], [8.634724715592698, 34.99999999999999, 3.181065825773816, 0.62, 0.49999999999999867]], [[11.941985400795891, 21.27020772143054, 1.508734033843744, 1.0, 2.0], [9.601128995432399, 34.99999999999999, 2.8150629415821378, 0.62, 0.49999999999999867]], [[11.47721794770487, 17.547400476020368, 1.422091568272813, 1.0, 2.0], [10.079001170859405, 33.75999811389845, 2.5501570801238573, 0.62, 0.5999999999999988]], [[11.021465297430568, 14.461625852153452, 1.334566113938895, 1.0, 2.0], [10.636101887658022, 33.90627433229769, 2.319301465402758, 0.62, 0.7999999999999989]], [[10.594689451189044, 11.738634485055297, 1.2441527132018315, 1.0, 2.0], [10.94929241532092, 32.16029294441503, 2.106177821095229, 0.62, 0.899999999999999]], [[10.19864453320194, 9.503161359884306, 1.1521027310338212, 1.0, 2.0], [12.01116582000109, 33.83490660359314, 1.85509592512426, 0.6999999999999997, 2.0]], [[9.832547685694525, 7.897472457142241, 1.0639222914735205, 1.0, 2.0], [12.987327085773796, 33.59359300949969, 1.6380619533485896, 0.8199999999999998, 2.0]], [[9.501261800959343, 6.7136021283868414, 0.9866501663746031, 1.0, 2.0], [13.872939454507256, 33.43456606488459, 1.4901853641060776, 0.96, 2.0]], [[9.209969337911858, 5.972592378475535, 0.9227454724515702, 1.0, 2.0], [13.72731601257363, 28.019047774927333, 1.1864847693035827, 1.0, 2.0]], [[8.960530761039333, 5.451348284829015, 0.8708762789956592, 1.0, 2.0], [13.172964668564932, 21.291795200841122, 1.0625972209863026, 1.0, 2.0]], [[8.76137352942953, 5.049538412178139, 0.8245385437069984, 1.0, 2.0], [12.585626818109853, 16.034787307607065, 0.9451723731837746, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [11.97028256015321, 11.57309577517366, 0.8270042386890116, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [11.37724242377065, 8.199565618720754, 0.7057844395480941, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [10.830118491930433, 6.03803076567993, 0.5863137838967661, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [10.32747209049223, 4.718998868797659, 0.47357292814612256, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [9.86786493878921, 3.684295798212356, 0.3739477028698482, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [9.464663070489268, 2.836906954283715, 0.2956171622262788, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [9.138303298274144, 2.176643833269374, 0.2397826626151422, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [8.89320047839234, 1.7155834363566835, 0.19801102110508398, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [8.72908362484433, 1.323520912210762, 0.15758292166969165, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [8.621492720509506, 0.9530160731159656, 0.11583809800624711, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [8.5583724669945, 0.6088047781278993, 0.07495410496158504, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [8.535056822474992, 0.31131134974301927, 0.03855184249235403, 1.0, 2.0]]], [[[7.188941234734006, 30.000000102276566, 5.800033635523425, 0.62, 0.899999999999999], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.454734824122841, 30.057567265518202, 5.175000286899383, 0.62, 0.899999999999999], [0.0, 0.0, 0.0, 0.0, 0.0]], [[7.96450971536009, 30.39870627622797, 4.572996072945492, 0.62, 0.899999999999999], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.324702032830917, 30.0000005349509, 4.164792573958396, 0.62, 0.899999999999999], [0.0, 0.0, 0.0, 0.0, 0.0]], [[8.792609612087126, 30.000000599851287, 3.766775232463434, 0.62, 0.899999999999999], [0.0, 0.0, 0.0, 0.0, 0.0]], [[9.244687536962362, 30.000000031720937, 3.469083009382941, 0.62, 0.899999999999999], [0.0, 0.0, 0.0, 0.0, 0.0]], [[9.797326464559763, 30.00000013208399, 3.1021608818953403, 0.62, 0.899999999999999], [0.0, 0.0, 0.0, 0.0, 0.0]], [[10.318636204315467, 30.000000097043767, 2.874600505550636, 0.62, 0.899999999999999], [0.0, 0.0, 0.0, 0.0, 0.0]], [[10.154468350444334, 33.986988743712175, 2.8821548467681666, 0.62, 1.0999999999999992], [0.0, 0.0, 0.0, 0.0, 0.0]], [[10.360124681453808, 33.324296056091136, 2.6663798952989963, 0.62, 1.1999999999999993], [6.399226711128023e-11, 34.10200622661677, 5.049664977778041, 0.62, 0.49999999999999867]], [[10.538498713316832, 33.24267412902024, 2.4547442439101457, 0.62, 1.3999999999999995], [2.8253736605184056e-09, 33.76485334272993, 5.999999932332612, 0.62, 0.49999999999999867]], [[10.676421509842864, 33.54940179704009, 2.2477679922697384, 0.62, 1.6999999999999997], [2.9544519089796135e-12, 33.74619427921682, 5.583272533244793, 0.62, 0.5999999999999988]], [[10.720808104729588, 32.13802354049996, 2.065915074000696, 0.62, 1.9], [3.0473582797075607, 34.99999999999999, 5.999999999999999, 0.62, 0.49999999999999867]], [[11.45599392524277, 33.7389726235075, 1.9688655293254054, 0.6799999999999997, 2.0], [3.6378652937562532, 34.99999999999999, 5.999999999999999, 0.62, 0.49999999999999867]], [[12.250556921356123, 33.418879868117514, 1.854973315692043, 0.7599999999999998, 2.0], [4.310046427816248, 34.99999999999999, 5.999999999999999, 0.62, 0.49999999999999867]], [[13.161031341090673, 33.6278079062252, 1.76402346825213, 0.8599999999999999, 2.0], [5.032085081772627, 34.99999999999988, 5.999999999999995, 0.62, 0.49999999999999867]], [[13.86984817546222, 33.155976657583444, 1.6992166405321538, 0.96, 2.0], [5.810769571274297, 34.99999999999999, 5.999999999999909, 0.62, 0.49999999999999867]], [[13.711828195684722, 28.953263162628684, 1.4289476932875935, 1.0, 2.0], [6.674888678600625, 34.99999999999999, 5.999999999997529, 0.62, 0.49999999999999867]], [[13.101941589687918, 23.58451450081377, 1.3410302073621188, 1.0, 2.0], [8.83564284416122, 34.99999999999999, 2.709477786062981, 0.62, 0.49999999999999867]], [[12.49145344688311, 18.821865424090323, 1.270809460216882, 1.0, 2.0], [10.093526995690413, 34.99999999999999, 2.398327331230622, 0.62, 0.49999999999999867]], [[11.882294677741342, 15.369813924906738, 1.2019990387891095, 1.0, 2.0], [11.08467341446687, 33.71517484040477, 2.1150495554609012, 0.62, 0.49999999999999867]], [[11.31892479956128, 12.416630458868287, 1.1330506681757144, 1.0, 2.0], [11.578766841465791, 31.98183333706307, 1.897059003500423, 0.62, 0.6999999999999988]], [[10.812990403235046, 10.007115041883614, 1.0667380683008485, 1.0, 2.0], [12.122159885998023, 31.163168090602415, 1.704652446785871, 0.62, 0.899999999999999]], [[10.371803151717254, 8.45015384040974, 1.0062642685022107, 1.0, 2.0], [13.76470627507998, 33.80537215739341, 1.4483828546223338, 0.7599999999999998, 2.0]], [[10.000727083460228, 7.286646881361432, 0.9565511895985728, 1.0, 2.0], [14.93151609183371, 33.5209880009196, 1.2772909363285216, 0.9199999999999999, 2.0]], [[9.696359239362293, 6.535431129641379, 0.9148258957023293, 1.0, 2.0], [15.032490085479807, 28.762730055770966, 1.0135742975832862, 1.0, 2.0]], [[9.44914504005159, 6.014697420729558, 0.8773200357578451, 1.0, 2.0], [14.305096835659814, 21.0895170579466, 0.8979197083489541, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [13.554914749396987, 15.370842300432873, 0.7914619595933842, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [12.828569826995205, 10.716353416978224, 0.6842972033734672, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [12.136640779702068, 7.479499489608849, 0.5762933658339603, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [11.488904024518995, 5.560331802984953, 0.47134416630040515, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [10.895914462151044, 4.324242343730937, 0.37709150657605084, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [10.387152683096346, 3.4013232649455865, 0.3028562395161779, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [9.98456744290421, 2.704999553460147, 0.24993442581215106, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [9.690443857500581, 2.152655370456471, 0.20907496892267335, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [9.488708201436634, 1.66843242487284, 0.16802497427558158, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [9.3584196509167, 1.2064707358314788, 0.12442759307136363, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [9.282249666707768, 0.7753847486734907, 0.08114834346511057, 1.0, 2.0]], [[0.0, 0.0, 0.0, 0.0, 0.0], [9.254015807384794, 0.40128013863358475, 0.04227109633981996, 1.0, 2.0]]]], "sails": ["MN1 + J1", "MN1 + A2"], "twa": [28.0, 32.0, 36.0, 40.0, 44.0, 48.0, 52.0, 56.0, 60.0, 64.0, 68.0, 72.0, 76.0, 80.0, 84.0, 88.0, 92.0, 96.0, 100.0, 104.0, 108.0, 112.0, 116.0, 120.0, 124.0, 128.0, 132.0, 136.0, 140.0, 144.0, 148.0, 152.0, 156.0, 160.0, 164.0, 168.0, 172.0, 176.0, 180.0], "tws": [2.0576, 3.0864, 4.1152, 5.144, 6.1728, 7.201599999999999, 8.2304, 9.2592, 10.288]} \ No newline at end of file diff --git "a/demos/pages/1_VPP_\342\233\265.py" "b/demos/pages/1_VPP_\342\233\265.py" index b7629e3..f4a3bc8 100644 --- "a/demos/pages/1_VPP_\342\233\265.py" +++ "b/demos/pages/1_VPP_\342\233\265.py" @@ -1,4 +1,3 @@ -import json import logging import os import sys @@ -6,54 +5,39 @@ import matplotlib.pyplot as plt import numpy as np +import pandas as pd import streamlit as st -from utils import footer, header +from presets import PRESETS +from utils import ( + FIELD_HELP, + KITE_SAIL_TYPES, + JIB_SAIL_TYPES, + MAIN_SAIL_TYPES, + field_label, + footer, + header, + render_data_source, + render_environment_inputs, + render_keel_inputs, + render_roughness_input, + render_sail_type, + render_solver_method, + run_vpp_direct, + validate_ranges, +) sys.path.append(os.path.realpath(".")) -from src.api import app -from src.UtilsMod import KNOTS_TO_MPS, _get_cross, _get_vmg, _polar, cols, stl +from src.UtilsMod import KNOTS_TO_MPS, _get_cross, _get_vmg, _polar, cols, lab, stl st.set_page_config(page_title="VPP", page_icon="⛵") -def process_yacht_specifications( - tws_range: List[int], - twa_range: List[int], - yacht: Dict, - keel: Dict, - rudder: Dict, - main: Dict, - jib: Dict, - kite: Dict, -): - data = { - "name": yacht["Name"], - "yacht": yacht, - "keel": keel, - "rudder": rudder, - "main": main, - "jib": jib, - "kite": kite, - "tws_range": tws_range, - "twa_range": twa_range, - } - - logging.info("Starting VPP simulation") - json_string = json.dumps(data) - headers = {"content-type": "application/json", "Accept-Charset": "UTF-8"} - client = app.test_client() - response = client.post("/api/vpp/", data=json_string, headers=headers) - - logging.info("VPP simulation completed") - return response - - -def plot_single_polar(response: Dict[str, Any]) -> plt.Figure: - name = response.json["name"] - sails = response.json["sails"] - twa_range = np.array(response.json["twa"]) - tws_range = np.array(response.json["tws"]) - results = np.array(response.json["results"]) +def plot_single_polar(data: Dict[str, Any]) -> plt.Figure: + name = data["name"] + sails = data["sails"] + twa_range = np.array(data["twa"]) + tws_range = np.array(data["tws"]) + results = np.array(data["results"]) n = 1 @@ -66,7 +50,7 @@ def plot_single_polar(response: Dict[str, Any]) -> plt.Figure: for j in range(n): lab = "_nolegend_" if k == 0: - lab = name + " " + f"{tws_range[i]/KNOTS_TO_MPS:.1f}" + lab = f"{tws_range[i]/KNOTS_TO_MPS:.1f}" ax[j].plot( twa_range[idx[0] : idx[1]] / 180 * np.pi, @@ -94,27 +78,76 @@ def plot_single_polar(response: Dict[str, Any]) -> plt.Figure: return fig -yacht = { - "Name": "YD41", - "Lwl": 11.90, - "Vol": 6.05, - "Bwl": 3.18, - "Tc": 0.4, - "WSA": 28.20, - "Tmax": 2.30, - "Amax": 1.051, - "Mass": 6500, - "Ff": 1.5, - "Fa": 1.5, - "Boa": 4.2, - "Loa": 12.5, -} - -keel = {"Cu": 1.00, "Cl": 0.78, "Span": 1.90} -rudder = {"Cu": 0.48, "Cl": 0.22, "Span": 1.15} -main = {"Name": "MN1", "P": 16.60, "E": 5.60, "Roach": 0.1, "BAD": 1.0} -jib = {"Name": "J1", "I": 16.20, "J": 5.10, "LPG": 5.40, "HBI": 1.8} -kite = {"Name": "A2", "area": 150.0, "vce": 9.55} +def plot_depowering_polar(data: Dict[str, Any]) -> plt.Figure: + """Plot flat and red depowering values on polar axes.""" + name = data["name"] + sails = data["sails"] + twa_range = np.array(data["twa"]) + tws_range = np.array(data["tws"]) + results = np.array(data["results"]) + + fig, axes = plt.subplots(1, 2, subplot_kw=dict(polar=True), figsize=(12, 6)) + for ax_i, (idx, title) in enumerate([(3, "Flat"), (4, "RED")]): + ax = axes[ax_i] + ax.set_xticks(np.linspace(0, np.pi, 5)) + ax.set_theta_direction(-1) + ax.set_theta_offset(np.pi / 2.0) + ax.set_thetamin(0) + ax.set_thetamax(180) + ax.set_rmin(0.0) + ax.set_xlabel(r"TWA ($^\circ$)") + ax.set_ylabel(title, labelpad=-40) + + for i in range(len(tws_range)): + for k in range(len(sails)): + cross = _get_cross(results[i, :, :, :], k) + label = "_nolegend_" + if k == 0: + label = f"{tws_range[i]/KNOTS_TO_MPS:.1f}" + ax.plot( + twa_range[cross[0] : cross[1]] / 180 * np.pi, + results[i, cross[0] : cross[1], k, idx], + color=cols[k % 7], + lw=np.where(i < 7, 1.5, 2.5), + linestyle=stl[i % 7], + label=label, + ) + if ax_i == 0: + ax.legend(title=r"TWS (kts)", loc=1, bbox_to_anchor=(1.05, 1.05)) + plt.tight_layout() + return fig + + +def build_depowering_table(data: Dict[str, Any]) -> pd.DataFrame: + """Build a table of depowering values for the best sail at each TWS/TWA.""" + sails = data["sails"] + twa_range = np.array(data["twa"]) + tws_range = np.array(data["tws"]) + results = np.array(data["results"]) + + rows = [] + for i, tws in enumerate(tws_range): + tws_kts = tws / KNOTS_TO_MPS + for j, twa in enumerate(twa_range): + best_sail = int(np.argmax(results[i, j, :, 0])) + vb = results[i, j, best_sail, 0] + flat = results[i, j, best_sail, 3] + red = results[i, j, best_sail, 4] + if flat < 1.0 or red < 2.0: + rows.append( + { + "TWS (kts)": f"{tws_kts:.0f}", + "TWA (°)": f"{twa:.0f}", + "Sail": sails[best_sail], + "Vb (kts)": f"{vb:.2f}", + "Flat": f"{flat:.2f}", + "RED": f"{red:.2f}", + } + ) + if not rows: + return pd.DataFrame({"Info": ["No depowering applied at these wind speeds"]}) + return pd.DataFrame(rows) + header() @@ -122,52 +155,107 @@ def plot_single_polar(response: Dict[str, Any]) -> plt.Figure: """ # Yacht VPP - This is a 3 D.O.F. VPP for a mono hull displacement sailing yacht. - - The default parameters are pre-set particulars for the YD-41 yacht. + This is a 3 D.O.F. VPP for a mono hull displacement sailing yacht. + The performance model is based on the + [ORC VPP documentation](https://www.orc.org/rules/ORC%20VPP%20Documentation%202024.pdf). """ ) +with st.popover("ℹ️ What is a VPP?"): + st.markdown( + "A Velocity Prediction Program computes the " + "equilibrium speed of a sailing yacht at each combination of true " + "wind speed (TWS) and true wind angle (TWA). It balances " + "aerodynamic driving force against hydrodynamic resistance, side " + "force against keel lift, and heeling moment against righting moment." + ) + +preset_name = st.selectbox("Yacht preset", list(PRESETS.keys()), index=1) +preset = PRESETS[preset_name] +yacht = dict(preset["yacht"]) +keel = dict(preset["keel"]) +rudder = dict(preset["rudder"]) +main = dict(preset["main"]) +jib = dict(preset["jib"]) +kite = dict(preset["kite"]) + st.subheader("Yacht particulars") for key, value in yacht.items(): - yacht[key] = st.text_input(f"{key}:", value) + yacht[key] = st.text_input(field_label(key), value, help=FIELD_HELP.get(key, "")) +roughness = render_roughness_input(key_prefix="vpp") st.subheader("Keel") -for key, value in keel.items(): - keel[key] = st.text_input(f"{key}:", value) +keel = render_keel_inputs(keel, key_prefix="vpp") st.subheader("Rudder") for key, value in rudder.items(): - rudder[key] = st.text_input(f"{key}:", value) + rudder[key] = st.text_input(field_label(key), value, help=FIELD_HELP.get(key, "")) st.subheader("Main Sail") +main_sail_type = render_sail_type("Main sail", MAIN_SAIL_TYPES, key_prefix="vpp_main") for key, value in main.items(): - main[key] = st.text_input(f"{key}:", value) + main[key] = st.text_input(field_label(key), value, help=FIELD_HELP.get(key, "")) st.subheader("Jib") +jib_sail_type = render_sail_type("Jib", JIB_SAIL_TYPES, key_prefix="vpp_jib") for key, value in jib.items(): - jib[key] = st.text_input(f"{key}:", value) + jib[key] = st.text_input(field_label(key), value, help=FIELD_HELP.get(key, "")) st.subheader("Kite (Spinnaker)") +kite_sail_type = render_sail_type("Kite", KITE_SAIL_TYPES, key_prefix="vpp_kite") for key, value in kite.items(): - kite[key] = st.text_input(f"{key}:", value) + kite[key] = st.text_input(field_label(key), value, help=FIELD_HELP.get(key, "")) -st.subheader("Environment") -twa_slider = st.slider( - "True wind angle (TWA) range", 35.0, 175.0, (35.0, 175.0), step=2.0 -) -twa_range = np.arange(twa_slider[0], twa_slider[1], 2.0).tolist() +tws_range, twa_range, env_params = render_environment_inputs(key_prefix="vpp") -tws_slider = st.slider("True wind speed (TWS) range", 2.0, 25.0, (8.0, 12.0), step=2.0) -tws_range = np.arange(tws_slider[0], tws_slider[1], 2.0).tolist() +st.subheader("Solver Settings") +solver_method = render_solver_method(key_prefix="vpp") +data_source = render_data_source(key_prefix="vpp") if st.button("Process Specifications"): - with st.spinner("Running optimisation, this can take a minute or two."): - response = process_yacht_specifications( - tws_range, twa_range, yacht, keel, rudder, main, jib, kite - ) - fig = plot_single_polar(response) - st.pyplot(fig) + if validate_ranges(tws_range, twa_range): + config = {"yacht": yacht, "keel": keel, "rudder": rudder, "main": main, "jib": jib, "kite": kite} + sail_types = {"main": main_sail_type, "jib": jib_sail_type, "kite": kite_sail_type} + env_params["roughness"] = roughness + with st.status("Running VPP optimisation...", expanded=True) as status: + def _on_tws(i, tws_kts, n_tws): + st.write(f"TWS {tws_kts:.0f} kts complete ({i + 1}/{n_tws})") + + result, error = run_vpp_direct( + config, tws_range, twa_range, method=solver_method, + data_source=data_source, sail_types=sail_types, + env_params=env_params, progress_callback=_on_tws, + ) + status.update(label="Optimisation complete!", state="complete", expanded=False) + if error: + st.error(f"Simulation failed: {error}") + logging.error("VPP failed: %s", error) + else: + with st.popover("ℹ️ What is a polar plot?"): + st.markdown( + "The polar plot shows boat speed (radial " + "axis) vs true wind angle. Each curve is a different wind " + "speed. Dots mark the best VMG (velocity made good) angles " + "upwind and downwind." + ) + fig = plot_single_polar(result) + st.pyplot(fig) + + st.subheader("Depowering (Flat & RED)") + with st.popover("ℹ️ What is depowering?"): + st.markdown( + "*Flat* controls how much the sails are " + "flattened (1.0 = full power, 0.62 = maximum depower). " + "*RED* is the reef/reduction factor (2.0 = full sail, " + "lower = reefed). The VPP depowers automatically when " + "heel exceeds the limit." + ) + dep_fig = plot_depowering_polar(result) + st.pyplot(dep_fig) + + with st.expander("Depowering data table"): + df = build_depowering_table(result) + st.dataframe(df, use_container_width=True) footer() diff --git "a/demos/pages/2_Compare_\342\232\226\357\270\217.py" "b/demos/pages/2_Compare_\342\232\226\357\270\217.py" new file mode 100644 index 0000000..8637cc1 --- /dev/null +++ "b/demos/pages/2_Compare_\342\232\226\357\270\217.py" @@ -0,0 +1,335 @@ +import copy +import os +import sys +from typing import Any, Dict, List + +import numpy as np +import pandas as pd +import plotly.graph_objects as go +import streamlit as st +from presets import PRESETS +from utils import ( + FIELD_HELP, + KITE_SAIL_TYPES, + JIB_SAIL_TYPES, + MAIN_SAIL_TYPES, + field_label, + footer, + header, + render_data_source, + render_environment_inputs, + render_keel_inputs, + render_roughness_input, + render_sail_type, + render_solver_method, + run_vpp_direct, + validate_ranges, +) + +sys.path.append(os.path.realpath(".")) +from src.UtilsMod import KNOTS_TO_MPS + +st.set_page_config(page_title="Compare", page_icon="⚖️", layout="wide") + +SECTIONS = [ + ("Yacht", "yacht"), + ("Keel", "keel"), + ("Rudder", "rudder"), + ("Main Sail", "main"), + ("Jib", "jib"), + ("Kite", "kite"), +] + +SECTION_TITLES = {k: title for title, k in SECTIONS} + +# Colours for up to 6 configs (Plotly named colors) +CONFIG_COLORS = [ + "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", +] +PLOTLY_DASH = ["solid", "dash", "dot", "dashdot", "longdash", "longdashdot"] + + +def render_config_tab(key_prefix: str, default_index: int = 1, baseline: Dict = None): + """Render preset selector and editable fields. Returns config dict. + + If baseline is provided, fields that differ from the baseline are + highlighted with a coloured background via custom CSS. + """ + preset_name = st.selectbox( + "Preset", list(PRESETS.keys()), index=default_index, key=f"{key_prefix}_preset" + ) + preset = PRESETS[preset_name] + config = {} + changed_fields = [] + + sail_type_options = {"main": MAIN_SAIL_TYPES, "jib": JIB_SAIL_TYPES, "kite": KITE_SAIL_TYPES} + sail_types = {} + for title, section_key in SECTIONS: + section = copy.deepcopy(preset[section_key]) + with st.expander(title, expanded=False): + if section_key == "keel": + section = render_keel_inputs(section, key_prefix=key_prefix) + else: + if section_key in sail_type_options: + sail_types[section_key] = render_sail_type( + title, sail_type_options[section_key], + key_prefix=f"{key_prefix}_{section_key}", + ) + for field, value in section.items(): + input_key = f"{key_prefix}_{section_key}_{field}" + section[field] = st.text_input(field_label(field), value, key=input_key, help=FIELD_HELP.get(field, "")) + + # Track which fields differ from baseline + if baseline is not None: + for field, value in section.items(): + base_val = str(baseline.get(section_key, {}).get(field, "")) + if str(value) != base_val: + changed_fields.append((section_key, field, value, base_val)) + + config[section_key] = section + config["_roughness"] = render_roughness_input(key_prefix=key_prefix) + config["_sail_types"] = sail_types + + if baseline is not None and changed_fields: + lines = [] + for sec, fld, new_val, old_val in changed_fields: + section_title = SECTION_TITLES.get(sec, sec) + label = field_label(fld) + lines.append(f"- **{section_title}** {label}: `{old_val}` → `{new_val}`") + changes_md = "\n".join(lines) + if len(changed_fields) <= 4: + st.caption("Changes vs Config 1:") + st.markdown(changes_md) + else: + with st.expander(f"Changes vs Config 1 ({len(changed_fields)} fields)"): + st.markdown(changes_md) + + return config + + +def plot_comparison_polar(responses: List) -> go.Figure: + """Overlay N VPP results on the same polar plot with distinct colours.""" + twa = np.array(responses[0]["twa"]) + tws = np.array(responses[0]["tws"]) + + fig = go.Figure() + for ci, resp in enumerate(responses): + results = np.array(resp["results"]) + name = resp["name"] + color = CONFIG_COLORS[ci % len(CONFIG_COLORS)] + + for i in range(len(tws)): + tws_kts = tws[i] / KNOTS_TO_MPS + speed = np.max(results[i, :, :, 0], axis=1) + + show_legend = i == 0 + fig.add_trace(go.Scatterpolar( + theta=twa, + r=speed, + mode="lines", + name=f"{name} (#{ci + 1})", + legendgroup=f"config_{ci}", + showlegend=show_legend, + line=dict( + color=color, + width=2, + dash=PLOTLY_DASH[i % len(PLOTLY_DASH)], + ), + hovertemplate=( + f"{name} TWS={tws_kts:.0f}kts
" + "TWA=%{theta:.0f}°
" + "Speed=%{r:.2f}kts" + ), + )) + + fig.update_layout( + polar=dict( + angularaxis=dict( + direction="clockwise", + rotation=90, + dtick=15, + ticksuffix="°", + ), + radialaxis=dict( + angle=90, + title="Speed (kts)", + ), + sector=[-90, 90], + ), + legend=dict(title="Configuration"), + height=600, + ) + return fig + + +def build_delta_table(responses: List) -> pd.DataFrame: + """Build a table showing speed differences vs the first config.""" + twa = np.array(responses[0]["twa"]) + tws = np.array(responses[0]["tws"]) + base_results = np.array(responses[0]["results"]) + base_name = responses[0]["name"] + + rows = [] + for i, tw in enumerate(tws): + tws_kts = tw / KNOTS_TO_MPS + speed_base = np.max(base_results[i, :, :, 0], axis=1) + for j, angle in enumerate(twa): + row = { + "TWS (kts)": f"{tws_kts:.0f}", + "TWA (°)": f"{angle:.0f}", + f"#{1} {base_name} (kts)": f"{speed_base[j]:.2f}", + } + for ci, resp in enumerate(responses[1:], start=2): + res = np.array(resp["results"]) + name = resp["name"] + speed = np.max(res[i, :, :, 0], axis=1)[j] + delta = speed - speed_base[j] + pct = (delta / speed_base[j] * 100) if speed_base[j] > 0 else 0.0 + row[f"#{ci} {name} (kts)"] = f"{speed:.2f}" + row[f"Δ#{ci} (kts)"] = f"{delta:+.2f}" + row[f"Δ#{ci} (%)"] = f"{pct:+.1f}" + rows.append(row) + return pd.DataFrame(rows) + + +def _build_vmg_section(responses: List, point: str, sign: int) -> pd.DataFrame: + """Build a VMG table for one point of sail (upwind or downwind).""" + twa = np.array(responses[0]["twa"]) + tws = np.array(responses[0]["tws"]) + + rows = [] + for i, tw in enumerate(tws): + tws_kts = tw / KNOTS_TO_MPS + row = {"TWS (kts)": f"{tws_kts:.0f}"} + base_vmg = None + for ci, resp in enumerate(responses, start=1): + res = np.array(resp["results"]) + name = resp["name"] + speed = np.max(res[i, :, :, 0], axis=1) + vmg = sign * speed * np.cos(twa / 180 * np.pi) + idx = np.argmax(vmg) + row[f"#{ci} {name} TWA"] = f"{twa[idx]:.0f}°" + row[f"#{ci} {name} VMG (kts)"] = f"{vmg[idx]:.2f}" + if ci == 1: + base_vmg = vmg[idx] + else: + delta = vmg[idx] - base_vmg + pct = (delta / base_vmg * 100) if base_vmg > 0 else 0.0 + # seconds per nautical mile difference + spm_base = (3600.0 / base_vmg) if base_vmg > 0 else 0.0 + spm_new = (3600.0 / vmg[idx]) if vmg[idx] > 0 else 0.0 + delta_spm = spm_new - spm_base + row[f"Δ#{ci} (kts)"] = f"{delta:+.2f}" + row[f"Δ#{ci} (%)"] = f"{pct:+.1f}%" + row[f"Δ#{ci} (s/NM)"] = f"{delta_spm:+.1f}" + rows.append(row) + return pd.DataFrame(rows) + + +# --- Session state for dynamic tabs --- + +if "num_configs" not in st.session_state: + st.session_state.num_configs = 2 + + +# --- Page layout --- + +header() + +st.markdown( + """ + # Compare Configurations + + Set up multiple configurations and compare their performance. + Change any parameter — sail dimensions, keel shape, crew weight — and + see the effect on boat speed and VMG. Fields that differ from Config 1 + are listed below each tab. +""" +) + +with st.popover("ℹ️ How to use"): + st.markdown( + "Set up two or more configurations with different " + "parameters (e.g. different keel shapes, sail areas, or hull dimensions). " + "The comparison overlay shows all polars on the same plot, and the delta " + "table quantifies speed differences at each TWS/TWA point." + ) + +# Add / remove config buttons +btn_cols = st.columns([1, 1, 6]) +with btn_cols[0]: + if st.button("+ Add config"): + if st.session_state.num_configs < 6: + st.session_state.num_configs += 1 + st.rerun() +with btn_cols[1]: + if st.button("- Remove last"): + if st.session_state.num_configs > 2: + st.session_state.num_configs -= 1 + st.rerun() + +num = st.session_state.num_configs +tab_labels = [f"Config {i + 1}" for i in range(num)] +tabs = st.tabs(tab_labels) + +configs = [] +for idx, tab in enumerate(tabs): + with tab: + # First config has no baseline; others compare against Config 1 + if idx == 0: + cfg = render_config_tab(f"cfg{idx}", default_index=1) + else: + cfg = render_config_tab( + f"cfg{idx}", default_index=1, baseline=configs[0] if configs else None + ) + configs.append(cfg) + +tws_range, twa_range, env_params = render_environment_inputs(key_prefix="cmp") + +st.subheader("Solver Settings") +solver_method = render_solver_method(key_prefix="cmp") +data_source = render_data_source(key_prefix="cmp") + +if st.button("Compare"): + if validate_ranges(tws_range, twa_range): + responses = [] + failed = False + with st.status(f"Running {num} VPP simulations...", expanded=True) as status: + for ci, cfg in enumerate(configs): + sail_types = cfg.pop("_sail_types", None) + cfg_roughness = cfg.pop("_roughness", 150e-6) + cfg_env = dict(env_params, roughness=cfg_roughness) + cfg_name = cfg.get("yacht", {}).get("Name", f"Config {ci + 1}") + st.write(f"**{cfg_name}** (config {ci + 1} of {num})") + + def _on_tws(i, tws_kts, n_tws, _name=cfg_name): + st.write(f" {_name}: TWS {tws_kts:.0f} kts complete ({i + 1}/{n_tws})") + + result, error = run_vpp_direct( + cfg, tws_range, twa_range, method=solver_method, + data_source=data_source, sail_types=sail_types, + env_params=cfg_env, progress_callback=_on_tws, + ) + if error: + st.error(f"Config {ci + 1} failed: {error}") + failed = True + break + responses.append(result) + status.update(label="Simulations complete!", state="complete", expanded=False) + + if not failed and len(responses) == num: + st.subheader("Overlaid polars") + fig = plot_comparison_polar(responses) + st.plotly_chart(fig, use_container_width=True) + + st.subheader("VMG comparison") + st.markdown("**Upwind**") + st.dataframe(_build_vmg_section(responses, "Upwind", 1), use_container_width=True, hide_index=True) + st.markdown("**Downwind**") + st.dataframe(_build_vmg_section(responses, "Downwind", -1), use_container_width=True, hide_index=True) + + with st.expander("Full speed delta table"): + delta_df = build_delta_table(responses) + st.dataframe(delta_df, use_container_width=True) + +footer() diff --git "a/demos/pages/3_Match_Race_\360\237\217\201.py" "b/demos/pages/3_Match_Race_\360\237\217\201.py" new file mode 100644 index 0000000..8143924 --- /dev/null +++ "b/demos/pages/3_Match_Race_\360\237\217\201.py" @@ -0,0 +1,417 @@ +import copy +import json +import os +import sys +from typing import Dict, List + +import matplotlib.pyplot as plt +import numpy as np +import streamlit as st +from presets import PRESETS +from utils import ( + FIELD_HELP, + KITE_SAIL_TYPES, + JIB_SAIL_TYPES, + MAIN_SAIL_TYPES, + field_label, + footer, + header, + render_data_source, + render_keel_inputs, + render_roughness_input, + render_sail_type, + render_solver_method, + run_vpp, +) + +sys.path.append(os.path.realpath(".")) +from src.RaceMod import Race +from src.WindMod import BrownianWind, ConstantWind, MeanRevertingWind + +st.set_page_config(page_title="Match Race", page_icon="🏁", layout="wide") + +SECTIONS = [ + ("Yacht", "yacht"), + ("Keel", "keel"), + ("Rudder", "rudder"), + ("Main Sail", "main"), + ("Jib", "jib"), + ("Kite", "kite"), +] + + +def render_boat_config(key_prefix: str, default_index: int = 1) -> Dict: + """Render preset selector + editable fields for one boat.""" + preset_name = st.selectbox( + "Preset", list(PRESETS.keys()), index=default_index, key=f"{key_prefix}_preset" + ) + preset = PRESETS[preset_name] + config = {} + sail_type_options = {"main": MAIN_SAIL_TYPES, "jib": JIB_SAIL_TYPES, "kite": KITE_SAIL_TYPES} + sail_types = {} + for title, section_key in SECTIONS: + section = copy.deepcopy(preset[section_key]) + with st.expander(title, expanded=False): + if section_key == "keel": + section = render_keel_inputs(section, key_prefix=key_prefix) + else: + if section_key in sail_type_options: + sail_types[section_key] = render_sail_type( + title, sail_type_options[section_key], + key_prefix=f"{key_prefix}_{section_key}", + ) + for field, value in section.items(): + input_key = f"{key_prefix}_{section_key}_{field}" + section[field] = st.text_input(field_label(field), value, key=input_key, help=FIELD_HELP.get(field, "")) + config[section_key] = section + config["_roughness"] = render_roughness_input(key_prefix=key_prefix) + config["_sail_types"] = sail_types + config["_preset_name"] = preset_name + return config + + +def get_or_compute_polar(config: Dict, tws_range: List[float], twa_range: List[float], + method: str, data_source: str) -> callable: + """Get polar interpolator — use cached file if preset matches, else compute.""" + preset_name = config.get("_preset_name", "") + + # Check for cached polar + safe_name = preset_name.replace(" ", "_").replace("(", "").replace(")", "") + cache_path = os.path.join("dat", f"polars_{safe_name}.json") + if os.path.exists(cache_path): + with open(cache_path) as f: + data = json.load(f) + tws = np.array(data["tws"]) + twa = np.array(data["twa"]) + results = np.array(data["results"]) + return Race.build_polar_interp(tws, twa, results), data.get("name", preset_name) + + # Compute from scratch + sail_types = config.pop("_sail_types", None) + config.pop("_preset_name", None) + response = run_vpp(config, tws_range, twa_range, method=method, data_source=data_source, + sail_types=sail_types) + if response.status_code != 200: + return None, None + data = response.json + tws = np.array(data["tws"]) + twa = np.array(data["twa"]) + results = np.array(data["results"]) + return Race.build_polar_interp(tws, twa, results), data["name"] + + +BOAT_A_COLOR = "#1b6ec2" +BOAT_B_COLOR = "#d9480f" + + +def plot_win_probability(mc: Dict, name_A: str, name_B: str) -> plt.Figure: + """Horizontal stacked bar showing win probability.""" + total = mc["wins_A"] + mc["wins_B"] + ties = len(mc["deltas"]) - total + fig, ax = plt.subplots(figsize=(10, 1.4), dpi=120) + n = len(mc["deltas"]) + pct_A = mc["wins_A"] / n * 100 + pct_B = mc["wins_B"] / n * 100 + pct_tie = ties / n * 100 + + ax.barh(0, pct_A, color=BOAT_A_COLOR, label=f"{name_A}: {pct_A:.0f}%", + edgecolor="white", linewidth=0.5) + ax.barh(0, pct_tie, left=pct_A, color="#dee2e6", + label=f"Tie: {pct_tie:.0f}%", edgecolor="white", linewidth=0.5) + ax.barh(0, pct_B, left=pct_A + pct_tie, color=BOAT_B_COLOR, + label=f"{name_B}: {pct_B:.0f}%", edgecolor="white", linewidth=0.5) + ax.set_xlim(0, 100) + ax.set_yticks([]) + ax.set_xlabel("Win probability (%)") + ax.legend(loc="upper center", ncol=3, bbox_to_anchor=(0.5, 1.6), + frameon=False, fontsize=10) + ax.spines[["top", "right", "left"]].set_visible(False) + plt.tight_layout() + return fig + + +def plot_delta_histogram(mc: Dict, name_A: str, name_B: str) -> plt.Figure: + """Histogram of time deltas across Monte Carlo runs.""" + fig, ax = plt.subplots(figsize=(8, 4), dpi=120) + deltas = np.array(mc["deltas"]) + ax.hist(deltas, bins=30, color="#868e96", edgecolor="white", alpha=0.85) + ax.axvline(0, color="black", linestyle="--", lw=1, alpha=0.5) + ax.axvline(mc["mean_delta"], color="#e03131", linestyle="-", lw=2, + label=f"Mean: {mc['mean_delta']:.1f}s") + ax.set_xlabel(f"Time delta (s) \u2190 {name_A} faster | {name_B} faster \u2192") + ax.set_ylabel("Count") + ax.legend(frameon=False) + ax.grid(axis="y", alpha=0.3) + ax.spines[["top", "right"]].set_visible(False) + plt.tight_layout() + return fig + + +def plot_course_trace(mc: Dict, name_A: str, name_B: str, + leg_distance_nm: float = 1.0, n_legs: int = 1) -> plt.Figure: + """Bird's eye view of both boats' tracks from the first race.""" + fig, ax = plt.subplots(figsize=(8, 10), dpi=120) + trace_A, trace_B = mc["traces"] + + # Draw mark positions + leg_m = leg_distance_nm * 1852.0 + mark_positions = [0.0, leg_m] + for m_y in mark_positions: + ax.plot(0, m_y, "D", color="black", markersize=10, zorder=5) + ax.axhline(0, color="black", lw=0.5, alpha=0.3) + ax.axhline(leg_m, color="black", lw=0.5, alpha=0.3) + + # Course corridor + corridor = 200.0 + ax.axvline(-corridor / 2, color="gray", lw=0.5, ls=":", alpha=0.4) + ax.axvline(corridor / 2, color="gray", lw=0.5, ls=":", alpha=0.4) + + if trace_A: + xA, yA = zip(*trace_A) + ax.plot(xA, yA, color=BOAT_A_COLOR, ls="-", lw=1.5, alpha=0.8, + marker="o", markevery=20, markersize=4, label=name_A) + ax.plot(xA[0], yA[0], "o", color=BOAT_A_COLOR, markersize=7, zorder=5) + if trace_B: + xB, yB = zip(*trace_B) + ax.plot(xB, yB, color=BOAT_B_COLOR, ls="--", lw=1.5, alpha=0.8, + marker="^", markevery=20, markersize=4, label=name_B) + ax.plot(xB[0], yB[0], "^", color=BOAT_B_COLOR, markersize=7, zorder=5) + + ax.set_xlabel("Cross-course (m)") + ax.set_ylabel("Upwind distance (m)") + ax.legend(frameon=False, loc="upper left") + ax.grid(alpha=0.2) + ax.set_aspect("equal") + ax.spines[["top", "right"]].set_visible(False) + plt.tight_layout() + return fig + + +def build_race_stats_table(mc: Dict, name_A: str, name_B: str): + """Build summary stats from Monte Carlo results.""" + results = mc["results"] + times_A = [r["time_A"] for r in results] + times_B = [r["time_B"] for r in results] + tacks_A = [r["tack_count_A"] for r in results] + tacks_B = [r["tack_count_B"] for r in results] + gybes_A = [r["gybe_count_A"] for r in results] + gybes_B = [r["gybe_count_B"] for r in results] + deltas = np.array(mc["deltas"]) + + return { + "Metric": [ + "Mean elapsed (s)", "Std elapsed (s)", + "Mean tacks", "Mean gybes", + "Mean delta (s)", "Std delta (s)", + "Best race (s)", "Worst race (s)", + ], + name_A: [ + f"{np.mean(times_A):.0f}", f"{np.std(times_A):.0f}", + f"{np.mean(tacks_A):.1f}", f"{np.mean(gybes_A):.1f}", + f"{mc['mean_delta']:.1f}", f"{np.std(deltas):.1f}", + f"{min(times_A):.0f}", f"{max(times_A):.0f}", + ], + name_B: [ + f"{np.mean(times_B):.0f}", f"{np.std(times_B):.0f}", + f"{np.mean(tacks_B):.1f}", f"{np.mean(gybes_B):.1f}", + "", "", + f"{min(times_B):.0f}", f"{max(times_B):.0f}", + ], + } + + +# --- Page layout --- + +header() + +st.markdown(""" +# Match Race Simulation + +This simulation races two boat configurations around a windward-leeward +course with random wind shifts. Each race is run many times (Monte Carlo) +to estimate which boat has a statistical advantage. +""") + +with st.popover("ℹ️ How it works"): + st.markdown( + "Both boats sail optimal VMG angles with tactical " + "rules (layline tacking, covering, splitting, dirty air avoidance). " + "Wind direction shifts randomly each second. Results show win " + "probability, time deltas, and an example race trace." + ) + +# --- Boat configs --- +st.subheader("Boat configurations") +col_A, col_B = st.columns(2) +with col_A: + st.markdown("### Boat A") + config_A = render_boat_config("race_A", default_index=1) +with col_B: + st.markdown("### Boat B") + config_B = render_boat_config("race_B", default_index=0) + +# --- Environment --- +st.subheader("Environment") +env_col1, env_col2 = st.columns(2) +with env_col1: + race_tws = st.slider(r"True wind speed $V_{tw}$ (knots)", 4.0, 25.0, 10.0, step=1.0, + key="race_tws") + current_speed = st.slider(r"Current speed $V_c$ (knots)", 0.0, 3.0, 0.0, step=0.1, + key="race_current_speed", + help="Constant current applied to both boats.") +with env_col2: + current_dir = st.slider(r"Current direction $\theta_c$ (degrees, 0 = upwind)", 0.0, 360.0, 0.0, + step=5.0, key="race_current_dir") + +# --- Race parameters --- +st.subheader("Race parameters") +race_col1, race_col2, race_col3 = st.columns(3) +with race_col1: + leg_distance = st.slider(r"Leg distance $d_{leg}$ (NM)", 0.3, 3.0, 1.0, step=0.1, + key="race_leg_dist", + help="Distance per leg in nautical miles.") + n_legs = st.selectbox("Up/down leg pairs", [1, 2, 3], index=0, + key="race_n_legs", + help="Number of upwind/downwind leg pairs.") +with race_col2: + tack_penalty = st.slider(r"Tack penalty $\tau_{tack}$ (s)", 3.0, 20.0, 10.0, step=1.0, + key="race_tack_penalty", + help="Time the boat is stationary during a tack.") + gybe_penalty = st.slider(r"Gybe penalty $\tau_{gybe}$ (s)", 2.0, 15.0, 6.0, step=1.0, + key="race_gybe_penalty", + help="Time the boat is stationary during a gybe.") +with race_col3: + n_runs = st.selectbox(r"Monte Carlo runs $N$", [50, 100, 200, 500], index=1, + key="race_n_runs", + help="Number of races to simulate. More = better statistics.") + +# --- Wind model --- +st.subheader("Wind model") +with st.popover("ℹ️ Wind model parameters"): + st.markdown( + "**Wind shift sigma** controls how much the wind direction wanders. " + "2 deg/sqrt(min) is typical for a sea breeze. Higher values mean more " + "tactical variability. **TWS sigma** varies the wind speed around the " + "mean (0 = constant speed)." + ) +wind_col1, wind_col2 = st.columns(2) +with wind_col1: + wind_sigma = st.slider(r"Wind shift $\sigma_\theta$ (deg/$\sqrt{min}$)", 0.0, 8.0, 2.0, + step=0.5, key="race_wind_sigma") + tws_sigma = st.slider(r"TWS $\sigma_{V}$ (kts/$\sqrt{min}$)", 0.0, 3.0, 0.0, + step=0.1, key="race_tws_sigma", + help="0 = constant wind speed. >0 adds speed variation.") +with wind_col2: + dir_reversion = st.slider(r"Direction mean-reversion $\kappa_\theta$ (1/min)", 0.0, 1.0, 0.05, + step=0.01, key="race_dir_reversion", + help="How quickly wind direction returns to mean. 0 = pure random walk.") + tws_reversion = st.slider(r"TWS mean-reversion $\kappa_V$ (1/min)", 0.0, 1.0, 0.1, + step=0.01, key="race_tws_reversion", + help="How quickly wind speed returns to mean. 0 = pure random walk.") + +# --- Stochastic effects --- +st.subheader("Stochastic effects") +with st.popover("ℹ️ Stochastic effects"): + st.markdown( + "**Trim noise** simulates imperfect sail trim — each boat gets random " + "speed variations each second. **Penalty std** makes tack/gybe times " + "variable (better crews are more consistent)." + ) +stoch_col1, stoch_col2 = st.columns(2) +with stoch_col1: + trim_sigma = st.slider(r"Trim noise $\sigma_{trim}$", 0.00, 0.10, 0.00, + step=0.01, key="race_trim_sigma", + help="0 = perfect trim. 0.03 = 3% speed noise.") +with stoch_col2: + tack_penalty_std = st.slider(r"Tack penalty $\sigma$ (s)", 0.0, 5.0, 0.0, + step=0.5, key="race_tack_std", + help="Std-dev of tack time. 0 = fixed penalty.") + gybe_penalty_std = st.slider(r"Gybe penalty $\sigma$ (s)", 0.0, 5.0, 0.0, + step=0.5, key="race_gybe_std", + help="Std-dev of gybe time. 0 = fixed penalty.") + +# --- Solver settings --- +with st.expander("Solver settings (for computing polars)"): + solver_method = render_solver_method(key_prefix="race") + data_source = render_data_source(key_prefix="race") + +# --- Run --- +if st.button("Race!", type="primary"): + # TWA/TWS range for polar computation (if needed) + tws_range = np.arange(4.0, 22.0, 2.0).tolist() + twa_range = np.linspace(28.0, 180.0, 39).tolist() + + with st.spinner("Computing polars for Boat A..."): + polar_A, name_A = get_or_compute_polar( + copy.deepcopy(config_A), tws_range, twa_range, solver_method, data_source) + if polar_A is None: + st.error("Failed to compute polars for Boat A.") + st.stop() + + with st.spinner("Computing polars for Boat B..."): + polar_B, name_B = get_or_compute_polar( + copy.deepcopy(config_B), tws_range, twa_range, solver_method, data_source) + if polar_B is None: + st.error("Failed to compute polars for Boat B.") + st.stop() + + # Build wind model + if tws_sigma > 0 or dir_reversion > 0: + wind_model = MeanRevertingWind( + tws=race_tws, dir_sigma=wind_sigma, + dir_reversion=dir_reversion, + tws_sigma=tws_sigma, tws_reversion=tws_reversion, + ) + elif wind_sigma > 0: + wind_model = BrownianWind(tws=race_tws, dir_sigma=wind_sigma) + else: + wind_model = ConstantWind(tws=race_tws) + + race = Race( + polar_A, polar_B, tws=race_tws, + leg_distance=leg_distance, n_legs=n_legs, + tack_penalty=tack_penalty, gybe_penalty=gybe_penalty, + wind_model=wind_model, + current_speed=current_speed, current_dir=current_dir, + trim_sigma=trim_sigma, + tack_penalty_std=tack_penalty_std, + gybe_penalty_std=gybe_penalty_std, + ) + + with st.spinner(f"Running {n_runs} races..."): + mc = race.run_monte_carlo(n_runs=n_runs) + + # Results + st.subheader("Results") + + # Win probability bar + st.markdown("#### Win probability") + fig_win = plot_win_probability(mc, name_A, name_B) + st.pyplot(fig_win) + + # Mean delta + ci = 1.96 * np.std(mc["deltas"]) / np.sqrt(len(mc["deltas"])) + st.metric( + "Mean time delta", + f"{mc['mean_delta']:.1f}s", + delta=f"95% CI: ±{ci:.1f}s", + ) + + # Histogram and trace side by side + res_col1, res_col2 = st.columns(2) + with res_col1: + st.markdown("#### Time delta distribution") + fig_hist = plot_delta_histogram(mc, name_A, name_B) + st.pyplot(fig_hist) + with res_col2: + st.markdown("#### Example race trace") + fig_trace = plot_course_trace(mc, name_A, name_B, + leg_distance_nm=leg_distance, n_legs=n_legs) + st.pyplot(fig_trace) + + # Stats table + with st.expander("Race statistics"): + stats = build_race_stats_table(mc, name_A, name_B) + st.dataframe(stats, use_container_width=True) + +footer() diff --git a/demos/presets.py b/demos/presets.py new file mode 100644 index 0000000..e00a0b9 --- /dev/null +++ b/demos/presets.py @@ -0,0 +1,48 @@ +"""Shared yacht preset configurations for the Streamlit UI.""" + +PRESETS = { + "YD41": { + "yacht": { + "Name": "YD41", + "Lwl": 11.90, + "Vol": 6.05, + "Bwl": 3.18, + "Tc": 0.4, + "WSA": 28.20, + "Tmax": 2.30, + "Amax": 1.051, + "Mass": 6500, + "Ff": 1.5, + "Fa": 1.5, + "Boa": 4.2, + "Loa": 12.5, + }, + "keel": {"Cu": 1.00, "Cl": 0.78, "Span": 1.90}, + "rudder": {"Cu": 0.48, "Cl": 0.22, "Span": 1.15}, + "main": {"Name": "MN1", "P": 16.60, "E": 5.60, "Roach": 0.1, "BAD": 1.0}, + "jib": {"Name": "J1", "I": 16.20, "J": 5.10, "LPG": 5.40, "HBI": 1.8}, + "kite": {"Name": "A2", "area": 150.0, "vce": 9.55}, + }, + "Daring (5.5m)": { + "yacht": { + "Name": "Daring", + "Lwl": 7.01, + "Vol": 1.95, + "Bwl": 1.70, + "Tc": 0.45, + "WSA": 11.5, + "Tmax": 1.35, + "Amax": 0.38, + "Mass": 2000, + "Ff": 0.75, + "Fa": 0.55, + "Boa": 1.98, + "Loa": 9.90, + }, + "keel": {"type": "short", "Length": 1.2, "Depth": 0.90, "Tc_ratio": 0.15}, + "rudder": {"Cu": 0.32, "Cl": 0.18, "Span": 0.75}, + "main": {"Name": "MN1", "P": 10.80, "E": 3.30, "Roach": 0.1, "BAD": 0.80}, + "jib": {"Name": "J1", "I": 8.50, "J": 2.70, "LPG": 2.70, "HBI": 0.50}, + "kite": {"Name": "S1", "area": 50.0, "vce": 4.50}, + }, +} diff --git a/demos/utils.py b/demos/utils.py index 2ea5bce..0ffb351 100644 --- a/demos/utils.py +++ b/demos/utils.py @@ -1,7 +1,17 @@ +import json +import logging +import os +import sys +from typing import Dict, List, Tuple + +import numpy as np import streamlit as st import streamlit.components.v1 as components import subprocess +sys.path.append(os.path.realpath(".")) +from src.api import app + def get_git_hash(): try: git_hash = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).strip().decode('utf-8') @@ -27,6 +37,345 @@ def header(): return components.html(header) +FIN_KEEL_DEFAULTS = {"Cu": 1.00, "Cl": 0.78, "Span": 1.90} +SHORT_KEEL_DEFAULTS = {"Length": 1.2, "Depth": 0.90, "Tc_ratio": 0.15} + + +def render_keel_inputs(keel: dict, key_prefix: str = "") -> dict: + """Render keel type selector and matching parameter inputs. + + Pops the ``type`` key from *keel*, shows a selectbox, then renders + only the fields appropriate for that keel type. Returns a new dict + with the selected type and parameter values. + """ + keel_type = keel.pop("type", "fin") + keel_type = st.selectbox( + "Keel type", + ["fin", "short"], + index=["fin", "short"].index(keel_type), + key=f"{key_prefix}_keel_type", + ) + defaults = SHORT_KEEL_DEFAULTS if keel_type == "short" else FIN_KEEL_DEFAULTS + result = {} + for field, default in defaults.items(): + input_key = f"{key_prefix}_keel_{field}" + result[field] = st.text_input(field_label(field), keel.get(field, default), key=input_key, help=FIELD_HELP.get(field, "")) + result["type"] = keel_type + return result + + +def render_solver_method(key_prefix: str = "") -> str: + """Render solver method selectbox, return selected method string.""" + return st.selectbox( + "Solver method", + ["iterative", "5dof"], + index=0, + key=f"{key_prefix}_solver_method", + help="'iterative' = 3-DOF with depowering loop; '5dof' = scipy SLSQP 5-DOF optimizer", + ) + + +def render_data_source(key_prefix: str = "") -> str: + """Render data source selectbox, return selected data source string.""" + return st.selectbox( + "Sail coefficient data source", + ["orc"], + index=0, + key=f"{key_prefix}_data_source", + help="Coefficient data directory under dat/", + ) + + +FIELD_HELP = { + # Yacht hull + "Name": "Yacht design name (label only).", + "Lwl": "Waterline length (m). Longer = faster hull speed.", + "Vol": "Displaced volume of the canoe body (m³).", + "Bwl": "Waterline beam (m). Wider = more initial stability.", + "Tc": "Canoe body draft (m). Depth of hull excluding keel.", + "WSA": "Wetted surface area of the canoe body (m²). Drives viscous drag.", + "Tmax": "Maximum draft including keel (m).", + "Amax": "Maximum cross-section area (m²).", + "Mass": "Total displacement mass including keel (kg).", + "Ff": "Freeboard height at the bow (m).", + "Fa": "Freeboard height at the stern (m).", + "Boa": "Beam overall (m).", + "Loa": "Length overall (m).", + # Fin keel + "Cu": "Root (upper) chord length (m).", + "Cl": "Tip (lower) chord length (m).", + "Span": "Appendage span / depth (m).", + # Short keel + "Length": "Fore-aft keel length along hull bottom (m).", + "Depth": "Keel depth below canoe body (m).", + "Tc_ratio": "Thickness-to-chord ratio (e.g. 0.15 = 15%).", + # Main sail + "P": "Luff length / mast height above boom (m).", + "E": "Foot length along the boom (m).", + "Roach": "Sail roach as fraction of triangle area (0.0–0.3).", + "BAD": "Boom above deck height (m).", + # Jib + "I": "Forestay height above deck (m).", + "J": "Base of foretriangle — mast to forestay at deck (m).", + "LPG": "Longest perpendicular of genoa/jib (m). Larger = more overlap.", + "HBI": "Height of jib tack above deck (m).", + # Kite + "area": "Spinnaker sail area (m²).", + "vce": "Vertical centre of effort above deck (m).", +} + +FIELD_LABELS = { + # Yacht hull + "Name": "Name", + "Lwl": r"$L_{wl}$ (m)", + "Vol": r"$\nabla$ (m³)", + "Bwl": r"$B_{wl}$ (m)", + "Tc": r"$T_c$ (m)", + "WSA": r"$S_{wet}$ (m²)", + "Tmax": r"$T_{max}$ (m)", + "Amax": r"$A_{max}$ (m²)", + "Mass": r"$\Delta m$ (kg)", + "Ff": r"$F_f$ (m)", + "Fa": r"$F_a$ (m)", + "Boa": r"$B_{oa}$ (m)", + "Loa": r"$L_{oa}$ (m)", + # Fin keel + "Cu": r"$C_u$ (m)", + "Cl": r"$C_l$ (m)", + "Span": r"$b$ (m)", + # Short keel + "Length": r"$L_{keel}$ (m)", + "Depth": r"$D_{keel}$ (m)", + "Tc_ratio": r"$t/c$", + # Main sail + "P": r"$P$ (m)", + "E": r"$E$ (m)", + "Roach": "Roach", + "BAD": r"$BAD$ (m)", + # Jib + "I": r"$I$ (m)", + "J": r"$J$ (m)", + "LPG": r"$LPG$ (m)", + "HBI": r"$HBI$ (m)", + # Kite + "area": r"$A_{kite}$ (m²)", + "vce": r"$VCE$ (m)", +} + + +def field_label(key: str) -> str: + """Return the mathematical display label for a field, falling back to the key.""" + return FIELD_LABELS.get(key, key) + + +MAIN_SAIL_TYPES = ["main", "main_low"] +JIB_SAIL_TYPES = ["jib", "jib_low"] +KITE_SAIL_TYPES = ["kite", "sym_kite", "asym_cl_kite", "asym_pole_kite"] + +SAIL_TYPE_HELP = { + "main": "ORC high-performance mainsail", + "main_low": "ORC low-performance mainsail (lower CL)", + "jib": "ORC high-performance jib", + "jib_low": "ORC low-performance jib (lower CL)", + "kite": "Default asymmetric spinnaker", + "sym_kite": "ORC symmetric spinnaker (higher CL)", + "asym_cl_kite": "ORC asymmetric spinnaker, centerline tack", + "asym_pole_kite": "ORC asymmetric spinnaker, pole tack", +} + + +def render_sail_type(label: str, options: list, key_prefix: str = "") -> str: + """Render a sail type selectbox and return the selected type.""" + return st.selectbox( + f"{label} type", + options, + index=0, + key=f"{key_prefix}_sail_type", + help=", ".join(f"{o}: {SAIL_TYPE_HELP[o]}" for o in options), + ) + + +def run_vpp( + config: Dict, + tws_range: List[float], + twa_range: List[float], + method: str = "iterative", + data_source: str = "orc", + sail_types: Dict[str, str] = None, + env_params: Dict = None, +): + """Post a yacht configuration to the VPP API and return the response. + + Parameters + ---------- + sail_types : dict, optional + Mapping of sail section to sail_type, e.g. + ``{"main": "main_low", "jib": "jib", "kite": "sym_kite"}``. + env_params : dict, optional + Environment parameters (roughness, Hs, Ts). + """ + data = _build_vpp_data(config, tws_range, twa_range, method, data_source, sail_types, env_params) + logging.info("Starting VPP simulation") + json_string = json.dumps(data) + headers = {"content-type": "application/json", "Accept-Charset": "UTF-8"} + client = app.test_client() + response = client.post("/api/vpp/", data=json_string, headers=headers) + logging.info("VPP simulation completed") + return response + + +def _build_vpp_data( + config: Dict, + tws_range: List[float], + twa_range: List[float], + method: str = "iterative", + data_source: str = "orc", + sail_types: Dict[str, str] = None, + env_params: Dict = None, +) -> Dict: + """Build the VPP request data dict from a config.""" + main = dict(config["main"]) + jib = dict(config["jib"]) + kite = dict(config["kite"]) + if sail_types: + if "main" in sail_types: + main["sail_type"] = sail_types["main"] + if "jib" in sail_types: + jib["sail_type"] = sail_types["jib"] + if "kite" in sail_types: + kite["sail_type"] = sail_types["kite"] + data = { + "name": config["yacht"]["Name"], + "yacht": config["yacht"], + "keel": config["keel"], + "rudder": config["rudder"], + "main": main, + "jib": jib, + "kite": kite, + "tws_range": tws_range, + "twa_range": twa_range, + "method": method, + "data_source": data_source, + } + if env_params: + data.update(env_params) + return data + + +def run_vpp_direct( + config: Dict, + tws_range: List[float], + twa_range: List[float], + method: str = "iterative", + data_source: str = "orc", + sail_types: Dict[str, str] = None, + env_params: Dict = None, + progress_callback=None, +): + """Run VPP directly (bypassing Flask). + + Parameters + ---------- + progress_callback : callable, optional + Called as ``progress_callback(tws_index, tws_kts, n_tws)`` after + each wind speed is completed. + + Returns (result_dict, error_string). result_dict has keys: + name, tws, twa, sails, results. On error, result_dict is None. + """ + from src.api import data_to_vpp + + data = _build_vpp_data(config, tws_range, twa_range, method, data_source, sail_types, env_params) + + try: + vpp, method = data_to_vpp(data) + except (KeyError, TypeError, ValueError) as e: + logging.warning("Invalid VPP input: %s", e) + return None, str(e) + + try: + vpp.run(verbose=True, method=method, progress_callback=progress_callback) + except Exception as e: + logging.exception("VPP simulation failed") + return None, str(e) + + return vpp.results(), None + + +def render_roughness_input(key_prefix: str = "") -> float: + """Render hull roughness number input. Returns roughness in metres.""" + roughness_um = st.number_input( + r"Hull roughness $k_s$ ($\mu m$)", + min_value=0, + max_value=1000, + value=150, + step=10, + key=f"{key_prefix}_roughness", + help=( + "Mean hull roughness height in micrometres. " + "Typical values: **0** = hydraulically smooth, " + "**50** = racing finish, " + "**150** = new antifouling paint, " + "**300** = 1-year fouled hull, " + "**500+** = heavily fouled." + ), + ) + return roughness_um * 1e-6 + + +def render_environment_inputs(key_prefix: str = "") -> Tuple[List[float], List[float], Dict]: + """Render TWA/TWS/wave sliders. + + Returns (tws_range, twa_range, env_params) where env_params is a dict + with keys ``Hs``, ``Ts``. + """ + st.subheader("Environment") + twa_slider = st.slider( + r"True wind angle $\theta_{tw}$ (TWA) range", + 35.0, 175.0, (35.0, 175.0), step=1.0, + key=f"{key_prefix}_twa", + ) + twa_range = np.arange(twa_slider[0], twa_slider[1] + 1.0, 1.0).tolist() + + tws_slider = st.slider( + r"True wind speed $V_{tw}$ (TWS) range", + 2.0, 25.0, (8.0, 12.0), step=1.0, + key=f"{key_prefix}_tws", + ) + tws_range = np.arange(tws_slider[0], tws_slider[1] + 1.0, 1.0).tolist() + + Hs = st.slider( + r"Significant wave height $H_s$ (m)", + 0.0, 3.0, 0.0, step=0.1, + key=f"{key_prefix}_Hs", + help="Wave height. 0 = flat water. Typical coastal: 0.5–1.5 m.", + ) + + Ts = st.slider( + r"Modal wave period $T_s$ (s)", + 0.0, 12.0, 0.0 if Hs == 0 else 5.0, step=0.5, + key=f"{key_prefix}_Ts", + help="Peak wave period. Typical: 4–8 s for wind waves, 8–12 s for swell.", + ) + + env_params = { + "Hs": Hs, + "Ts": Ts, + } + return tws_range, twa_range, env_params + + +def validate_ranges(tws_range: List[float], twa_range: List[float]) -> bool: + """Show error messages if ranges are empty. Returns True if valid.""" + if not tws_range: + st.error("TWS range is empty. Make sure the min and max wind speeds are not equal.") + return False + if not twa_range: + st.error("TWA range is empty. Make sure the min and max wind angles are not equal.") + return False + return True + + def footer(): git_hash = get_git_hash() footer = f""" diff --git a/docs/plans/2026-02-27-daring-vpp.md b/docs/plans/2026-02-27-daring-vpp.md new file mode 100644 index 0000000..f8452e2 --- /dev/null +++ b/docs/plans/2026-02-27-daring-vpp.md @@ -0,0 +1,635 @@ +# Daring 5.5m Yacht VPP Prediction — Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Create a baseline VPP performance prediction for the Daring sailing yacht (classic 5.5m class by Arthur Robb), with configurable crew weight and per-yacht GZ curves, suitable for comparing equipment changes. + +**Architecture:** Add a Daring yacht definition alongside the existing YD41 example. Refactor `righting_moment.json` to be per-yacht (passed as parameter or per-yacht file). Make crew weight/count a configurable Yacht parameter. Add a `runDaring.py` entry point and update the Streamlit demo to offer Daring as a preset. + +**Tech Stack:** Python, numpy, scipy, matplotlib, pytest, Streamlit + +--- + +## Assumptions & Parameter Documentation + +All Daring parameters are documented here. Values marked **(published)** come from class data. Values marked **(estimated)** are derived from naval architecture rules of thumb for classic 5.5m metre boats. + +### Hull Parameters + +| Parameter | Value | Unit | Source | +|-----------|-------|------|--------| +| `Name` | "Daring" | — | — | +| `Loa` | 9.90 | m | **(published)** classicsailboats.org | +| `Lwl` | 7.01 | m | **(published)** classicsailboats.org | +| `Boa` | 1.98 | m | **(published)** classicsailboats.org | +| `Bwl` | 1.70 | m | **(estimated)** ~86% of Boa; metre boats have very little flare | +| `Tmax` | 1.35 | m | **(published)** classicsailboats.org — max draft incl. keel | +| `Tc` | 0.45 | m | **(estimated)** canoe body draft; deep canoe body per IYRU metre rule, keel extends 0.90m below | +| `Vol` | 1.95 | m³ | **(estimated)** 2000 kg / 1025 kg/m³ seawater density | +| `Mass` | 2000 | kg | **(published)** classicsailboats.org — total displacement | +| `WSA` | 11.5 | m² | **(estimated)** Delf series approximation: WSA ≈ 1.97 × √(Vol × Lwl) for narrow hulls. √(1.95 × 7.01) ≈ 3.70, × 1.97 ≈ 7.3 for canoe body. Add ~15% for narrow deep hull correction → ~8.4 m² canoe body. Keel WSA ~2.0 m², rudder ~1.1 m² added separately by appendage model, so canoe body WSA ≈ 11.5 m² (conservative, accounts for long overhangs wetted at speed) | +| `Amax` | 0.38 | m² | **(estimated)** midship section coefficient ~0.50 for a metre boat: Bwl × Tc × 0.50 = 1.70 × 0.45 × 0.50 | +| `Ff` | 0.75 | m | **(estimated)** bow freeboard, typical classic 5.5m | +| `Fa` | 0.55 | m | **(estimated)** stern freeboard, lower transom/counter stern | + +### Appendage Parameters + +**Keel** — classic 5.5m attached fin keel, moderate aspect ratio: + +| Parameter | Value | Unit | Reasoning | +|-----------|-------|------|-----------| +| `Cu` (root chord) | 0.70 | m | **(estimated)** classic swept fin, wider at hull junction | +| `Cl` (tip chord) | 0.45 | m | **(estimated)** moderate taper ratio ~0.64 | +| `Span` | 0.90 | m | **(estimated)** Tmax (1.35) - Tc (0.45) = 0.90m keel span | + +**Rudder** — classic attached rudder aft of keel, separated: + +| Parameter | Value | Unit | Reasoning | +|-----------|-------|------|-----------| +| `Cu` (root chord) | 0.32 | m | **(estimated)** small balanced rudder | +| `Cl` (tip chord) | 0.18 | m | **(estimated)** tapered tip | +| `Span` | 0.75 | m | **(estimated)** rudder extends ~0.75m below canoe body; class rule max thickness 175mm | + +### Sail Parameters + +Total upwind sail area **(published)**: 29.73 m². Fractional rig with high-aspect main and small non-overlapping jib. + +**Mainsail:** + +| Parameter | Value | Unit | Reasoning | +|-----------|-------|------|-----------| +| `P` (luff) | 10.80 | m | **(estimated)** high-aspect fractional rig; main area = 0.5 × P × E × (1+Roach) ≈ 19.6 m² | +| `E` (foot) | 3.30 | m | **(estimated)** moderate foot for classic rig | +| `Roach` | 0.10 | — | **(estimated)** 10% roach, standard for fractional rig | +| `BAD` | 0.80 | m | **(estimated)** boom above deck | + +Computed main area: 0.5 × 10.80 × 3.30 × 1.10 = **19.60 m²** + +**Jib:** + +| Parameter | Value | Unit | Reasoning | +|-----------|-------|------|-----------| +| `I` (forestay height) | 8.50 | m | **(estimated)** fractional — forestay attaches ~79% up the mast | +| `J` (foretriangle base) | 2.70 | m | **(estimated)** narrow foretriangle, typical 5.5m | +| `LPG` (luff perpendicular) | 2.70 | m | **(estimated)** 100% non-overlapping jib (LPG = J) | +| `HBI` (height above deck) | 0.50 | m | **(estimated)** low-cut jib | + +Computed jib area: 0.5 × 8.50 × 2.70 = **11.48 m²** + +**Total upwind: 19.60 + 11.48 = 31.08 m²** (within ~4% of published 29.73 m²; acceptable for initial estimate — can tune P/E down slightly if needed) + +**Spinnaker:** + +| Parameter | Value | Unit | Reasoning | +|-----------|-------|------|-----------| +| `area` | 50.0 | m² | **(estimated)** ~1.7× upwind area, typical for 5.5m class symmetric kite | +| `vce` | 4.5 | m | **(estimated)** centre of effort at ~53% of I height | + +### Righting Moment / GZ Curve (Estimated) + +For a classic 5.5m with ~50% ballast ratio (1000 kg lead keel), narrow beam (1.98m), and deep draft (1.35m): + +| Heel (deg) | GZ (m) | Reasoning | +|------------|--------|-----------| +| 0 | 0.000 | Zero at upright | +| 10 | 0.120 | Narrow beam → modest initial stability; GZ ≈ GM × sin(φ), GM estimated ~0.70m | +| 20 | 0.230 | Linear region, GZ ≈ 0.70 × sin(20°) = 0.24 | +| 30 | 0.310 | Approaching max GZ; narrow hull starts losing form stability | +| 40 | 0.350 | Near max righting arm for narrow metre boat | +| 50 | 0.330 | GZ starts declining; deck edge immersion | +| 60 | 0.260 | Significant decline, heavy weather limit region | + +Estimation method: GM estimated at ~0.70m from ballast ratio (~50%), VCG estimate (~0.15m below WL for classic keel boat), and BM ≈ I_wpa / Vol ≈ (Bwl³ × Lwl / 12) / Vol. Cross-checked against published GZ curves for similar narrow keelboats (Soling, Star class). + +### Crew Configuration + +| Parameter | Value | Reasoning | +|-----------|-------|-----------| +| Crew count | 3 | Typical Daring racing crew | +| Total crew weight | 240 kg | 3 × 80 kg average | +| Crew arm | 0.8 × Bmax | Same as current VPP model — crew on rail | + +--- + +## Task 1: Make GZ Curve Per-Yacht (Configurable) + +Currently `righting_moment.json` is a single global file read by every `Yacht` instance. This must become per-yacht before we can model the Daring alongside YD41. + +**Files:** +- Modify: `src/YachtMod.py` (Yacht class constructor + `_build_rm_interp`) +- Modify: `righting_moment.json` → keep as default fallback +- Create: `dat/Daring/righting_moment.json` +- Create: `dat/YD-41/righting_moment.json` (move existing data) +- Test: `tests/test_yacht.py` + +**Step 1: Write failing test for per-yacht GZ** + +```python +# tests/test_yacht.py +import numpy as np +import pytest +from src.YachtMod import Yacht, Keel, Rudder +from src.SailMod import Main, Jib + + +def _minimal_yacht(**overrides): + """Create a minimal Yacht for testing with sensible defaults.""" + defaults = dict( + Name="TestYacht", Lwl=7.01, Vol=1.95, Bwl=1.70, Tc=0.45, + WSA=11.5, Tmax=1.35, Amax=0.38, Mass=2000, Loa=9.90, Boa=1.98, + Ff=0.75, Fa=0.55, + App=[Keel(Cu=0.70, Cl=0.45, Span=0.90), Rudder(Cu=0.32, Cl=0.18, Span=0.75)], + Sails=[Main("MN1", P=10.80, E=3.30, Roach=0.1, BAD=0.80), + Jib("J1", I=8.50, J=2.70, LPG=2.70, HBI=0.50)], + ) + defaults.update(overrides) + return Yacht(**defaults) + + +def test_yacht_accepts_gz_parameter(): + """Yacht should accept an optional gz dict to override the global file.""" + gz = {"Heel": [0, 10, 20, 30], "GZ": [0.0, 0.12, 0.23, 0.31]} + yacht = _minimal_yacht(GZ=gz) + # At 10 degrees, righting arm should match our custom data + rm_10 = yacht._get_RmH(10.0) + expected = 0.12 * 2000 * 9.81 + assert abs(rm_10 - expected) < 1.0 # within 1 Nm + + +def test_yacht_falls_back_to_file_when_no_gz(): + """When no GZ parameter given, should still load from file (backward compat).""" + yacht = _minimal_yacht() + rm_10 = yacht._get_RmH(10.0) + assert rm_10 > 0 # loaded from righting_moment.json +``` + +**Step 2: Run test to verify it fails** + +Run: `pytest tests/test_yacht.py -v` +Expected: FAIL — `Yacht.__init__() got an unexpected keyword argument 'GZ'` + +**Step 3: Implement per-yacht GZ in YachtMod.py** + +Modify `Yacht.__init__` to accept optional `GZ=None` parameter. If provided, use it directly. If not, fall back to `righting_moment.json` file. + +In `src/YachtMod.py`, change the constructor signature: + +```python +def __init__(self, Name, Lwl, Vol, Bwl, Tc, WSA, Tmax, + Amax, Mass, Loa, Boa, Ff, Fa, App=[], Sails=[], GZ=None): +``` + +Add docstring for `GZ`: +```python + GZ : dict, optional + Righting arm curve as ``{"Heel": [...], "GZ": [...]}``. + Heel in degrees, GZ in metres. If *None*, loads from + ``righting_moment.json`` (backward compatible). +``` + +Store it and update `_build_rm_interp`: + +```python + self._gz_data = GZ + self._interp_rm = self._build_rm_interp() + + def _build_rm_interp(self): + if self._gz_data is not None: + a = self._gz_data + else: + a = json_read('righting_moment') + return interpolate.interp1d(np.array(a["Heel"]), np.array(a["GZ"]), + kind="linear", fill_value="extrapolate") +``` + +**Step 4: Run test to verify it passes** + +Run: `pytest tests/test_yacht.py -v` +Expected: PASS + +**Step 5: Commit** + +```bash +git add src/YachtMod.py tests/test_yacht.py +git commit -m "feat: make GZ curve per-yacht with optional parameter" +``` + +--- + +## Task 2: Make Crew Weight Configurable + +Currently crew weight is auto-calculated from `25.8 * Lwl^1.4262`, which gives ~158 kg for the YD41 (12m) but only ~56 kg for the Daring (7m) — far too low for 3-4 crew. + +**Files:** +- Modify: `src/YachtMod.py` (Yacht constructor) +- Test: `tests/test_yacht.py` + +**Step 1: Write failing test** + +```python +def test_yacht_accepts_crew_weight(): + """Yacht should accept an optional crew_weight to override the empirical formula.""" + yacht = _minimal_yacht(crew_weight=240.0) + assert yacht.cw == 240.0 + + +def test_yacht_default_crew_weight(): + """Without crew_weight param, should use empirical formula.""" + yacht = _minimal_yacht() + expected = 25.8 * 7.01 ** 1.4262 + assert abs(yacht.cw - expected) < 0.1 +``` + +**Step 2: Run test to verify it fails** + +Run: `pytest tests/test_yacht.py::test_yacht_accepts_crew_weight -v` +Expected: FAIL + +**Step 3: Implement configurable crew weight** + +Add `crew_weight=None` to `Yacht.__init__` signature: + +```python +def __init__(self, Name, Lwl, Vol, Bwl, Tc, WSA, Tmax, + Amax, Mass, Loa, Boa, Ff, Fa, App=[], Sails=[], GZ=None, crew_weight=None): +``` + +Add docstring: +```python + crew_weight : float, optional + Total crew weight (kg). If *None*, uses empirical formula + ``25.8 * Lwl ** 1.4262``. +``` + +Replace the crew weight line: +```python + self.cw = crew_weight if crew_weight is not None else 25.8 * self.l ** 1.4262 +``` + +**Step 4: Run tests** + +Run: `pytest tests/test_yacht.py -v` +Expected: PASS + +**Step 5: Commit** + +```bash +git add src/YachtMod.py tests/test_yacht.py +git commit -m "feat: make crew weight configurable with optional parameter" +``` + +--- + +## Task 3: Create Daring Yacht Definition & Run Script + +**Files:** +- Create: `runDaring.py` +- Create: `dat/Daring/righting_moment.json` +- Test: `tests/test_daring.py` + +**Step 1: Create Daring GZ data file** + +Write `dat/Daring/righting_moment.json`: +```json +{ + "Heel": [0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0], + "GZ": [0.000, 0.120, 0.230, 0.310, 0.350, 0.330, 0.260] +} +``` + +**Step 2: Write integration test** + +```python +# tests/test_daring.py +import numpy as np +import pytest +from src.SailMod import Jib, Kite, Main +from src.VPPMod import VPP +from src.YachtMod import Keel, Rudder, Yacht + + +# Daring GZ curve (estimated for classic 5.5m, ~50% ballast ratio, GM ~0.70m) +DARING_GZ = { + "Heel": [0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0], + "GZ": [0.000, 0.120, 0.230, 0.310, 0.350, 0.330, 0.260], +} + + +def return_daring(): + """Create Daring yacht with estimated 5.5m class parameters. + + Published values (classicsailboats.org / Cowes Classics): + LOA=9.90m, LWL=7.01m, Beam=1.98m, Draft=1.35m, + Displacement=2000kg, Upwind SA=29.73m² + + Estimated values are documented in docs/plans/2026-02-27-daring-vpp.md + """ + return Yacht( + Name="Daring", + Lwl=7.01, + Vol=1.95, + Bwl=1.70, + Tc=0.45, + WSA=11.5, + Tmax=1.35, + Amax=0.38, + Mass=2000, + Loa=9.90, + Boa=1.98, + Ff=0.75, + Fa=0.55, + App=[ + Keel(Cu=0.70, Cl=0.45, Span=0.90), + Rudder(Cu=0.32, Cl=0.18, Span=0.75), + ], + Sails=[ + Main("MN1", P=10.80, E=3.30, Roach=0.1, BAD=0.80), + Jib("J1", I=8.50, J=2.70, LPG=2.70, HBI=0.50), + Kite("S1", area=50.0, vce=4.50), + ], + GZ=DARING_GZ, + crew_weight=240.0, + ) + + +def test_daring_vpp_runs(): + """Daring VPP should solve without errors across a range of conditions.""" + daring = return_daring() + vpp = VPP(Yacht=daring) + vpp.set_analysis( + tws_range=np.arange(6.0, 14.0, 2.0), + twa_range=np.linspace(35.0, 175.0, 15), + ) + vpp.run(verbose=False) + results = vpp.results() + assert results["name"] == "Daring" + # Should have results for 4 wind speeds + assert len(results["tws"]) == 4 + + +def test_daring_boat_speed_sanity(): + """Daring should produce reasonable speeds: 3-7 knots in moderate wind.""" + daring = return_daring() + vpp = VPP(Yacht=daring) + vpp.set_analysis( + tws_range=np.array([10.0]), + twa_range=np.linspace(40.0, 160.0, 13), + ) + vpp.run(verbose=False) + results = np.array(vpp.results()["results"]) + # Max boat speed at 10 kts TWS should be between 3 and 7 knots + max_speed = np.max(results[:, :, :, 0]) + assert 3.0 < max_speed < 7.0, f"Max speed {max_speed:.1f} kts outside expected range" +``` + +**Step 3: Run tests to verify they fail** + +Run: `pytest tests/test_daring.py -v` +Expected: FAIL (initially may fail if Tasks 1-2 not done yet; should pass after) + +**Step 4: Create `runDaring.py`** + +```python +#!/usr/bin/env python3 +""" +Velocity Prediction Program for the Daring sailing yacht. + +The Daring is a one-design keelboat designed by Arthur Robb, based on +his 5.5 Metre class yacht "Vision" (1956 Olympic silver medal). + +Published specifications (classicsailboats.org): + LOA: 9.90m | LWL: 7.01m | Beam: 1.98m | Draft: 1.35m + Displacement: 2000 kg | Upwind sail area: 29.73 m² + +Estimated parameters are documented in docs/plans/2026-02-27-daring-vpp.md. +All estimates are marked and can be refined with actual measurements. +""" +import logging + +import numpy as np + +from src.SailMod import Jib, Kite, Main +from src.VPPMod import VPP +from src.YachtMod import Keel, Rudder, Yacht + +logging.basicConfig(level=logging.INFO) + +# --- Estimated GZ curve for classic 5.5m --- +# GM ~0.70m, ~50% ballast ratio, narrow beam (1.98m) +# See docs/plans/2026-02-27-daring-vpp.md for derivation +DARING_GZ = { + "Heel": [0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0], + "GZ": [0.000, 0.120, 0.230, 0.310, 0.350, 0.330, 0.260], +} + +Daring = Yacht( + Name="Daring", + Lwl=7.01, # (published) waterline length + Vol=1.95, # (estimated) 2000kg / 1025 kg/m³ + Bwl=1.70, # (estimated) ~86% of Boa + Tc=0.45, # (estimated) canoe body draft + WSA=11.5, # (estimated) Delf series for narrow hull + Tmax=1.35, # (published) max draft incl. keel + Amax=0.38, # (estimated) Bwl × Tc × Cm(0.50) + Mass=2000, # (published) total displacement + Loa=9.90, # (published) length overall + Boa=1.98, # (published) beam overall + Ff=0.75, # (estimated) freeboard fore + Fa=0.55, # (estimated) freeboard aft + App=[ + Keel(Cu=0.70, Cl=0.45, Span=0.90), # (estimated) classic fin + Rudder(Cu=0.32, Cl=0.18, Span=0.75), # (estimated) separated rudder + ], + Sails=[ + Main("MN1", P=10.80, E=3.30, Roach=0.1, BAD=0.80), # (est.) ~19.6 m² + Jib("J1", I=8.50, J=2.70, LPG=2.70, HBI=0.50), # (est.) ~11.5 m² + Kite("S1", area=50.0, vce=4.50), # (est.) symmetric kite + ], + GZ=DARING_GZ, + crew_weight=240.0, # 3 crew × 80 kg +) + +vpp = VPP(Yacht=Daring) + +vpp.set_analysis( + tws_range=np.arange(4.0, 22.0, 2.0), + twa_range=np.linspace(30.0, 180.0, 31), +) + +vpp.run(verbose=False) +vpp.write("results_daring") +vpp.polar(3, True) +vpp.SailChart(True) +``` + +**Step 5: Run tests and the script** + +Run: `pytest tests/test_daring.py -v` +Expected: PASS + +Run: `python runDaring.py` +Expected: Generates polar plots and `results_daring.json` + +**Step 6: Commit** + +```bash +git add runDaring.py dat/Daring/righting_moment.json tests/test_daring.py +git commit -m "feat: add Daring 5.5m yacht definition with estimated parameters" +``` + +--- + +## Task 4: Add Daring as Streamlit Default Preset + +The Streamlit app currently hardcodes YD41 defaults. Add a preset selector so users can switch between YD41 and Daring. + +**Files:** +- Modify: `demos/pages/1_VPP_⛵.py` + +**Step 1: Add preset dictionaries** + +Add after the existing `yacht` dict (line 97), a presets dict: + +```python +PRESETS = { + "YD41": { + "yacht": { + "Name": "YD41", "Lwl": 11.90, "Vol": 6.05, "Bwl": 3.18, "Tc": 0.4, + "WSA": 28.20, "Tmax": 2.30, "Amax": 1.051, "Mass": 6500, + "Ff": 1.5, "Fa": 1.5, "Boa": 4.2, "Loa": 12.5, + }, + "keel": {"Cu": 1.00, "Cl": 0.78, "Span": 1.90}, + "rudder": {"Cu": 0.48, "Cl": 0.22, "Span": 1.15}, + "main": {"Name": "MN1", "P": 16.60, "E": 5.60, "Roach": 0.1, "BAD": 1.0}, + "jib": {"Name": "J1", "I": 16.20, "J": 5.10, "LPG": 5.40, "HBI": 1.8}, + "kite": {"Name": "A2", "area": 150.0, "vce": 9.55}, + }, + "Daring (5.5m)": { + "yacht": { + "Name": "Daring", "Lwl": 7.01, "Vol": 1.95, "Bwl": 1.70, "Tc": 0.45, + "WSA": 11.5, "Tmax": 1.35, "Amax": 0.38, "Mass": 2000, + "Ff": 0.75, "Fa": 0.55, "Boa": 1.98, "Loa": 9.90, + }, + "keel": {"Cu": 0.70, "Cl": 0.45, "Span": 0.90}, + "rudder": {"Cu": 0.32, "Cl": 0.18, "Span": 0.75}, + "main": {"Name": "MN1", "P": 10.80, "E": 3.30, "Roach": 0.1, "BAD": 0.80}, + "jib": {"Name": "J1", "I": 8.50, "J": 2.70, "LPG": 2.70, "HBI": 0.50}, + "kite": {"Name": "S1", "area": 50.0, "vce": 4.50}, + }, +} +``` + +**Step 2: Add selectbox before parameter inputs** + +Replace the hardcoded yacht/keel/rudder/main/jib/kite dicts with: + +```python +preset_name = st.selectbox("Yacht preset", list(PRESETS.keys()), index=1) +preset = PRESETS[preset_name] +yacht = dict(preset["yacht"]) +keel = dict(preset["keel"]) +rudder = dict(preset["rudder"]) +main = dict(preset["main"]) +jib = dict(preset["jib"]) +kite = dict(preset["kite"]) +``` + +**Step 3: Test manually** + +Run: `cd demos && streamlit run Home.py` +Expected: Dropdown with "YD41" and "Daring (5.5m)", selecting Daring populates all fields. + +**Step 4: Commit** + +```bash +git add demos/pages/1_VPP_⛵.py +git commit -m "feat: add Daring preset to Streamlit UI" +``` + +--- + +## Task 5: Verify Full Integration & Run Baseline Polars + +**Files:** +- No new files; run existing + +**Step 1: Run full test suite** + +Run: `pytest tests/ -v` +Expected: All tests pass (existing YD41 tests + new Daring tests) + +**Step 2: Generate Daring baseline polars** + +Run: `python runDaring.py` +Expected: `results_daring.json` written, polar plots displayed/saved + +**Step 3: Sanity-check output** + +Verify: +- VMG upwind ~3.5-4.5 kts in 10 kts TWS (reasonable for 5.5m class) +- VMG downwind ~4.5-5.5 kts in 10 kts TWS with kite +- Max speed ~5-6 kts in 15+ kts TWS +- Heel angles 15-25° in moderate wind (narrow hull, will heel) +- No NaN values or solver failures + +**Step 4: Commit results** + +```bash +git add results_daring.json +git commit -m "feat: add Daring baseline performance prediction" +``` + +--- + +## Future Work (Beads to Track) + +These are documented for future sessions. Each is a separate piece of work. + +### Bead 1: Heavy Weather Modelling + +The current VPP has no wave drag, no gust response, and no reef/furl optimization. For heavy weather prediction: +- Add wave resistance component (Gerritsma & Beukelman added drag from waves) +- Implement reef/furl optimization (currently `flat=1.0, red=1.0` are hardcoded in the solver) +- Add a wave height / sea state parameter to `set_analysis()` +- Model knockdown / max heel safety limits +- Wind gradient (reduced wind speed at deck level in heavy weather) + +### Bead 2: Equipment Comparison Framework + +To compare old vs new equipment (sails, keel, rig weight): +- Overlay polar plots from two VPP runs on same axes +- Compute delta VMG, delta max speed, delta optimal TWA +- Create a `compare()` method on VPP that takes two result sets +- Support rig weight changes (affects VCG → GZ curve → righting moment) +- Parameter sensitivity analysis (what-if for each dimension) + +### Bead 3: Generic Codebase Improvements + +- **YAML/JSON yacht definitions**: Load yacht configs from files instead of Python code. Enable a `dat/yachts/` directory with one file per boat. +- **Per-yacht righting moment files**: Extend Task 1 to also support loading from `dat//righting_moment.json` by convention. +- **Parameter estimation helpers**: Given basic dims (LOA, LWL, Beam, Draft, Disp), estimate WSA, Amax, Vol, Tc using empirical formulas (Delf series, Holtrop). +- **API GZ support**: Extend the Flask API (`src/api.py`) to accept GZ and crew_weight in the JSON payload. +- **Streamlit GZ editor**: Add GZ curve editing and crew weight input to the Streamlit UI. +- **Results export**: CSV/Excel export of polar data for use in navigation software. + +### Bead 4: Restore 5-DOF Constrained Optimizer + +The original codebase contains a commented-out 5-DOF SLSQP optimizer in `src/VPPMod.py` (lines 249-264) that solves for `[vb, heel, leeway, flat, reef]` simultaneously with constraints. The original author abandoned it — likely due to convergence issues — and fell back to a 3-DOF root-finder with `flat=1.0` and `reef=1.0` hardcoded. + +The iterative outer loop (Bead 1 / current work) is a pragmatic workaround, but a proper constrained optimizer would be more elegant and physically accurate: + +- **Restore the SLSQP solver** as an alternative to the current `scipy.optimize.root` LM solver +- **Constraints**: force/moment equilibrium (Fx, Fy, Mx residuals = 0), heel ≤ phi_max +- **Bounds**: vb ≥ 0, 0 ≤ heel ≤ phi_max, -2 ≤ leeway ≤ 6, 0.62 ≤ flat ≤ 1.0, 0 ≤ reef ≤ 2.0 +- **Objective**: maximize boat speed (minimize -vb) +- **Key challenges**: convergence at extreme TWA/TWS, initial guess sensitivity, solver robustness +- **Approach**: Use the 3-DOF solution as initial guess for the 5-DOF optimizer. Fall back to 3-DOF + iterative depower if SLSQP fails to converge. +- **Testing**: Compare 5-DOF results against 3-DOF + outer loop across full TWS/TWA grid; results should agree within tolerance at low wind, diverge only where depowering is active. +- The nlopt COBYLA solver (already imported) is another option — it's derivative-free and may handle the non-smooth flat/reef landscape better than SLSQP. + +### Bead 5: Streamlit Deployment & Daring Defaults + +The app is deployed at yacht-vpp.streamlit.app. Task 4 adds Daring as a preset. Additional work: +- Verify deployment picks up the Daring preset from the `uv-migration` branch (or master after merge) +- Consider making Daring the default selection if this is primarily for the Daring fleet +- Add a description panel explaining the Daring class and parameter sources diff --git a/docs/plans/2026-03-01-performance-and-match-racing.md b/docs/plans/2026-03-01-performance-and-match-racing.md new file mode 100644 index 0000000..dd2d6c0 --- /dev/null +++ b/docs/plans/2026-03-01-performance-and-match-racing.md @@ -0,0 +1,183 @@ +# Performance Modelling Improvements + Match Racing Simulation + +## Overview + +Three performance model improvements (wave resistance, current/tide, surface roughness) and a match racing simulation tab with Monte Carlo wind shifts. + +## Part 1: Added Resistance in Waves + +### Physics + +Added resistance from waves using a simplified Gerritsma-Beukelman approach. The key inputs are significant wave height (Hs) and mean wave period (Ts). Wave encounter angle is derived from the boat heading relative to wave direction. + +**Default behaviour**: waves aligned with wind direction (no extra input). Optional `wave_direction` parameter overrides this. + +**Formula**: + +``` +Raw = C_aw * (Hs^2 / Lwl) * (Bwl^2 / Tc) * f(omega_e) +``` + +Where: +- `C_aw` is a hull-form coefficient (empirical, ~6.0 for typical displacement hulls) +- `omega_e` is the wave encounter frequency: `omega_e = (2*pi/Ts) - (2*pi/Ts)^2 * Vb * cos(mu) / g` +- `mu` is the wave encounter angle (0 = head seas, 180 = following seas) +- Scaling: `Raw *= cos^2(mu)` — full drag head-on, zero beam-on, near-zero following + +**Wave encounter angle**: +``` +mu = boat_heading - wave_direction +``` +If `wave_direction` is not provided, it defaults to `wind_direction` (= 0 in the VPP frame, so mu = TWA effectively). + +### Implementation + +- `src/HydroMod.py`: Add `_added_resistance_waves(Vb, phi, Hs, Ts, wave_dir, heading)` method. Called from `Rt()`. Returns 0 when Hs=0 (preserves current behaviour). +- `src/YachtMod.py`: Add `Hs`, `Ts`, `wave_direction` optional attributes to `Yacht` (default 0, 0, None). +- `src/VPPMod.py`: Pass wave params through to hydro calculations. +- `src/api.py`: Read `Hs`, `Ts`, `wave_direction` from environment section of payload. +- UI: Add Hs/Ts sliders to environment section with explainer text. + +## Part 2: Current/Tide + +Current does not change hull forces (which depend on speed-through-water). It changes speed-over-ground and therefore VMG and leg times. + +### Implementation + +Applied at the **race simulation layer** only, not inside the VPP equilibrium solver. + +- `src/RaceMod.py`: Current vector added to SOG when computing leg times. +- UI: Current speed (kts) and direction (degrees) inputs in race simulation tab. Default 0. + +For VPP polars display, current is irrelevant (polars show speed-through-water). + +## Part 3: Surface Roughness + +A multiplier on the ITTC friction coefficient to account for hull fouling. + +### Implementation + +- `src/HydroMod.py`: In `Rv()`, multiply Cf by `self.roughness_factor`. +- `src/YachtMod.py`: Add `roughness` optional attribute to `Yacht` (default 1.0). +- `src/api.py`: Read `roughness` from payload. +- UI: Roughness slider (0.95 to 1.25, default 1.0) with explainer text. + +## Part 4: Match Racing Simulation + +### Architecture + +Following Philpott, Henderson & Teirney (2004). A time-stepping simulation of two boats racing a windward-leeward course, with stochastic wind shifts and Monte Carlo repetition. + +### Course Model + +Windward-leeward course: +- Leg distance: user-configurable (default 1.0 NM) +- Number of up/down pairs: user-configurable (default 1 = one beat + one run) +- Windward mark directly upwind of start + +### Per-Leg Simulation + +For each boat on each leg: + +1. **Extract optimal VMG angle** from polar: scan BS(TWS, TWA) * cos(TWA) for upwind (or cos(180-TWA) for downwind). +2. **Compute VMG** at current TWS (which may have shifted). +3. **Time-step** (dt = 1 second): advance distance by VMG*dt. +4. **Tack/gybe at laylines**: when remaining distance can be covered on one tack, execute final approach. +5. **Tack/gybe penalty**: fixed time cost per maneuver (default 10s tack, 6s gybe). +6. **Wind shifts**: at each timestep, wind direction drifts by Brownian motion. + +### Wind Model + +Brownian motion on wind direction (Dalang et al. 2015): + +``` +wind_dir(t+dt) = wind_dir(t) + sigma * sqrt(dt) * N(0,1) +``` + +- `sigma`: wind shift volatility (default 2.0 deg/sqrt(min), typical for sea breeze) +- Wind speed held constant (simplification for v1) +- Mean-reverting variant optional: `wind_dir(t+dt) = wind_dir(t) + theta*(wind_dir_mean - wind_dir(t))*dt + sigma*sqrt(dt)*N(0,1)` + +### Monte Carlo + +Run N simulations (default 100) with different random seeds. Output: +- **Win probability** for each boat (% of races won) +- **Mean delta** in elapsed time (seconds) +- **Distribution histogram** of time deltas +- **Single example race trace** (plan view of boat tracks) + +### New Module: `src/RaceMod.py` + +```python +class Race: + def __init__(self, polar_A, polar_B, tws, leg_distance, n_legs, + tack_penalty, gybe_penalty, current_speed, current_dir, + wind_sigma): + ... + + def run_single(self, seed) -> RaceResult: + """Run one race, return leg times for each boat.""" + ... + + def run_monte_carlo(self, n_runs=100) -> MonteCarloResult: + """Run n_runs races, return statistics.""" + ... +``` + +`polar_A` and `polar_B` are 2D interpolation functions (TWS, TWA -> BS), built from VPP results. + +### UI: New Streamlit Tab + +`demos/pages/3_Match_Race_🏁.py` + +Layout: +- Two columns for boat configs (reuse preset/edit pattern) +- Shared environment: TWS, leg distance, n_legs +- Race parameters: tack/gybe penalties, wind shift sigma, n_runs +- "Race!" button +- Results: win probability, mean time delta, histogram, example trace plot + +### Explainer Boxes + +Add `st.info()` boxes throughout the UI explaining: +- **VPP page**: What a VPP is, what the polar plot shows, what depowering means +- **Compare page**: How to interpret speed deltas +- **Match Race page**: What the simulation does, what wind shifts mean, how to interpret win probability +- **Environment section**: What Hs/Ts mean, what roughness represents + +## Files to Modify/Create + +| File | Changes | +|------|---------| +| `src/HydroMod.py` | Add `_added_resistance_waves()`, roughness factor in `Rv()` | +| `src/YachtMod.py` | Add `Hs`, `Ts`, `wave_direction`, `roughness` to Yacht | +| `src/VPPMod.py` | Pass wave/roughness params through | +| `src/api.py` | Accept new environment params | +| `src/RaceMod.py` | **New** — Race simulation engine | +| `demos/pages/1_VPP_⛵.py` | Wave/roughness inputs, explainer boxes | +| `demos/pages/2_Compare_⚖️.py` | Explainer boxes | +| `demos/pages/3_Match_Race_🏁.py` | **New** — Match racing tab | +| `demos/utils.py` | Environment input helpers for Hs/Ts/roughness | +| `tests/test_hydro.py` | **New** — Wave resistance, roughness tests | +| `tests/test_race.py` | **New** — Race simulation tests | +| `tests/test_api.py` | API tests for new params | + +## Implementation Order + +``` +1. Surface roughness (simplest, self-contained) +2. Wave resistance (new physics, moderate complexity) +3. Race simulation engine (src/RaceMod.py + tests) +4. Match Race UI tab +5. Explainer boxes across all pages +6. Current/tide in race simulation +``` + +## Verification + +1. `uv run pytest tests/ -v` — all tests pass after each step +2. Roughness factor > 1 produces slower speeds +3. Hs > 0 produces slower speeds, more so upwind than downwind +4. Race simulation: identical boats produce ~50/50 win probability +5. Faster boat wins > 50% of Monte Carlo runs +6. Tack penalty increase reduces win margin for upwind-strong boats diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7cee070 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,51 @@ +[project] +name = "python-vpp" +version = "0.0.2" +description = "OOP Velocity Prediction Program" +readme = "README.md" +license = "MIT" +requires-python = ">=3.11" +authors = [ + { name = "Marin Lauber", email = "M.Lauber@soton.ac.uk" }, + { name = "Otto Villani" }, + { name = "Thomas Dickson" }, +] +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] +dependencies = [ + "numpy>=2.0", + "matplotlib>=3.8", + "scipy>=1.12", + "tqdm>=4.66", + "flask", + "streamlit>=1.37", + "plotly>=6.6.0", +] + +[project.optional-dependencies] +api = ["flask"] +demo = ["streamlit>=1.37"] +dev = [ + "pytest", + "ruff", + "mypy", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["src"] + +[tool.pytest.ini_options] +testpaths = ["tests"] + +[tool.ruff] +line-length = 100 + +[tool.ruff.lint] +select = ["E", "F", "I", "W"] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index f377185..0000000 --- a/requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -numpy==1.26.3 -matplotlib==3.8.2 -scipy==1.12.0 -nlopt==2.7.1 -streamlit==1.37.0 -tqdm==4.66.3 -black -flask -isort -mypy -pytest \ No newline at end of file diff --git a/results.json b/results.json index 53ac0fc..9e40a86 100644 --- a/results.json +++ b/results.json @@ -1,5913 +1,75 @@ -{ - "name": "YD41", - "tws": [ - 2.0576, - 3.0864, - 4.1152, - 5.144, - 6.1728, - 7.201599999999999, - 8.2304, - 9.2592, - 10.288 - ], - "twa": [ - 30.0, - 35.0, - 40.0, - 45.0, - 50.0, - 55.0, - 60.0, - 65.0, - 70.0, - 75.0, - 80.0, - 85.0, - 90.0, - 95.0, - 100.0, - 105.0, - 110.0, - 115.0, - 120.0, - 125.0, - 130.0, - 135.0, - 140.0, - 145.0, - 150.0, - 155.0, - 160.0, - 165.0, - 170.0, - 175.0, - 180.0 - ], - "sails": [ - "MN1 + J1", - "MN1 + A2", - "MN1 + A5" - ], - "results": [ - { - "Speed": 2.561569616552873, - "Heel": 2.6110567429378033, - "Leeway": 4.472736970112156, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.2261873028682526, - "Heel": 3.112997910600455, - "Leeway": 3.422583434474393, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.7615342163864542, - "Heel": 3.4626338225603632, - "Leeway": 2.861764366658557, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.189475175390796, - "Heel": 3.67825293726073, - "Leeway": 2.488787560283444, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.484209707298338, - "Heel": 3.781306587698491, - "Leeway": 2.25089425142381, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.7391202525437635, - "Heel": 3.838593121475754, - "Leeway": 2.055354715953424, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.955559631838637, - "Heel": 3.8514666883961697, - "Leeway": 1.888712321792068, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.10763795263753, - "Heel": 3.800397702249712, - "Leeway": 1.749145076633882, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.286516304374415, - "Heel": 3.175619778591795, - "Leeway": 2.952101864389654, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.4775728001892134, - "Heel": 1.7503674130569762, - "Leeway": 1.9395237873652205, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.178063940150709, - "Heel": 3.6566735223474716, - "Leeway": 1.6222269262737672, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.6420267274142657, - "Heel": 3.386773291189649, - "Leeway": 2.596510904372786, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.753994938134146, - "Heel": 1.8040840279033845, - "Leeway": 1.7588809959249048, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.209865569735387, - "Heel": 3.474224728438659, - "Leeway": 1.504951446265048, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.9519062832050986, - "Heel": 3.5550899506788456, - "Leeway": 2.340081602618497, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.9911503940387885, - "Heel": 1.8285895456864085, - "Leeway": 1.6153756935177666, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.200494589404944, - "Heel": 3.2527077477676385, - "Leeway": 1.395969705258638, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.223959277824621, - "Heel": 3.6789790347994957, - "Leeway": 2.1368229098898657, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.198234971689516, - "Heel": 1.826456266533262, - "Leeway": 1.49112995635843, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.152608770133769, - "Heel": 2.9941116337522176, - "Leeway": 1.293025055843081, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.433484110160443, - "Heel": 3.7589784389460252, - "Leeway": 1.991323099854493, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.344560881121004, - "Heel": 1.7886226443000917, - "Leeway": 1.39650747539177, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.069649550624901, - "Heel": 2.699750620921443, - "Leeway": 1.1943519153121458, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.612140987897024, - "Heel": 3.7917135379280356, - "Leeway": 1.8586542833791946, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.429623289037254, - "Heel": 1.7001629761109285, - "Leeway": 1.299266038327358, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.956966466661626, - "Heel": 2.3731837567370158, - "Leeway": 1.098230926328109, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.699448329832093, - "Heel": 3.6768145293250543, - "Leeway": 1.7203944185655984, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.46183180065075, - "Heel": 1.573021571500328, - "Leeway": 1.2004619556560983, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.814398194068853, - "Heel": 2.0426236307567485, - "Leeway": 1.0051017255141308, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.750097207794773, - "Heel": 3.513354976011307, - "Leeway": 1.589799531035046, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.467742256563833, - "Heel": 1.4325516814987254, - "Leeway": 1.1051474935431889, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.657775782476055, - "Heel": 1.7282870421334744, - "Leeway": 0.9115835200751912, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.770151452592594, - "Heel": 3.3093119756484484, - "Leeway": 1.4646120691176494, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.447737005216443, - "Heel": 1.282562683573817, - "Leeway": 1.0123999122972762, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.481658554998872, - "Heel": 1.4274577148734902, - "Leeway": 0.8159534611826471, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.75993176286718, - "Heel": 3.064877422597849, - "Leeway": 1.343296799201094, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.391831581237419, - "Heel": 1.1204840072062139, - "Leeway": 0.9188363629706284, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.249886585543892, - "Heel": 1.1105139401452933, - "Leeway": 0.7082467099780321, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.720089613937065, - "Heel": 2.777314414967685, - "Leeway": 1.2236900234889325, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.298662439660925, - "Heel": 0.9508520703151614, - "Leeway": 0.8230112391258358, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.9330725006876666, - "Heel": 0.8120527706102271, - "Leeway": 0.6072056710170376, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.619521251461353, - "Heel": 2.3944500049858592, - "Leeway": 1.097393629715977, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.1833217254192485, - "Heel": 0.787573897426107, - "Leeway": 0.7287100828469785, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.557324543456671, - "Heel": 0.5640887767146237, - "Leeway": 0.5191730882921, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.494934445565906, - "Heel": 2.006360508604818, - "Leeway": 0.9721670031283858, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.995846315097238, - "Heel": 0.6204376151413878, - "Leeway": 0.6379580862844646, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.21163941811998, - "Heel": 0.38827868031459883, - "Leeway": 0.43950882201493047, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.350067582479646, - "Heel": 1.6359398411597066, - "Leeway": 0.8474892809128495, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.768452334497469, - "Heel": 0.46505149571830645, - "Leeway": 0.5445409547269344, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.136895552660251, - "Heel": 1.2430006285986304, - "Leeway": 0.7143008379316433, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.5140869277521576, - "Heel": 0.3288435172446348, - "Leeway": 0.44652866262592306, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.8618396847165526, - "Heel": 0.8712890977025518, - "Leeway": 0.5767820414443814, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.2483672094969815, - "Heel": 0.2179925081583601, - "Leeway": 0.34694882201470173, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.553711184951611, - "Heel": 0.5502745719512074, - "Leeway": 0.43173947857631756, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 2.99270503815722, - "Heel": 0.14198395818772572, - "Leeway": 0.26786559249376496, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.250834115379853, - "Heel": 0.32524184894026026, - "Leeway": 0.3063450339962598, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 2.7625608780541646, - "Heel": 0.09426155217012386, - "Leeway": 0.20661710074219092, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 2.968063409679798, - "Heel": 0.18344756376552485, - "Leeway": 0.20746413694844357, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 2.573277783864865, - "Heel": 0.06382853703813963, - "Leeway": 0.1563739932566918, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 2.7197551390971277, - "Heel": 0.09929430374023775, - "Leeway": 0.13237138838375553, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 2.3989177523251333, - "Heel": 0.044013701464996724, - "Leeway": 0.11984520806050926, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 2.528876148472367, - "Heel": 0.05980104816354102, - "Leeway": 0.09072006486865837, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 2.2845307138103075, - "Heel": 0.034649213643758586, - "Leeway": 0.10003112157755681, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 2.4133004699705713, - "Heel": 0.039362331972094586, - "Leeway": 0.06480967910735788, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 2.2095142151797464, - "Heel": 0.024288069091597378, - "Leeway": 0.07344022637584666, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 2.350477888724648, - "Heel": 0.02196172911526278, - "Leeway": 0.03784788184072311, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 2.166998441547587, - "Heel": 0.015252698056784095, - "Leeway": 0.04746128894828413, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 2.334189092032038, - "Heel": 0.009643060171898596, - "Leeway": 0.016851984586512252, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 2.15820332619536, - "Heel": 0.007714397091359412, - "Leeway": 0.024097405981924547, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.899286075344636, - "Heel": 5.071105134794291, - "Leeway": 4.360160115293261, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.711767538129434, - "Heel": 5.800415914446172, - "Leeway": 3.5297509896040484, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.3670517095252235, - "Heel": 6.318375426622176, - "Leeway": 3.0002474646161374, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.999486995083984, - "Heel": 6.792030170811468, - "Leeway": 2.588884976715289, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.469951179145504, - "Heel": 7.085624844687612, - "Leeway": 2.318059964393761, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.804728144332203, - "Heel": 7.183355183868299, - "Leeway": 2.122635666437524, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.032430597574462, - "Heel": 7.101614304530023, - "Leeway": 1.9683698644781569, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.142353791902521, - "Heel": 6.802797634617789, - "Leeway": 1.834458574751132, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.850025002027048, - "Heel": 6.011972552770249, - "Leeway": 3.037691525467854, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.074298757163126, - "Heel": 3.6597498661803844, - "Leeway": 2.030381253637955, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.207717322292395, - "Heel": 6.439600920897409, - "Leeway": 1.7052005978975455, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.332391250497242, - "Heel": 6.446696090440634, - "Leeway": 2.713710990816648, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.455697827571803, - "Heel": 3.737588756561197, - "Leeway": 1.8526542722531818, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.234969743749513, - "Heel": 6.053541499492204, - "Leeway": 1.5829271452619744, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.8049019116424985, - "Heel": 6.828091978524921, - "Leeway": 2.4263925307138576, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.8200058334810185, - "Heel": 3.78137997866651, - "Leeway": 1.6903979908959457, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.22499110368902, - "Heel": 5.652323174787527, - "Leeway": 1.4662999306280171, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.193126346083537, - "Heel": 7.143378929425427, - "Leeway": 2.221781626502823, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.10680171756611, - "Heel": 3.7705256686830446, - "Leeway": 1.5659013742486783, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.180521237723679, - "Heel": 5.241049976760262, - "Leeway": 1.353953839982302, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.511459045658114, - "Heel": 7.361967892918808, - "Leeway": 2.061267545814325, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.342549955213473, - "Heel": 3.7137951900752917, - "Leeway": 1.4558013398588796, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.106052110853164, - "Heel": 4.822623841477925, - "Leeway": 1.2444786803103935, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.7191027127978975, - "Heel": 7.302186439636354, - "Leeway": 1.9207446530894483, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.433253471837538, - "Heel": 3.5333825882463246, - "Leeway": 1.3464808649047921, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.001478193699883, - "Heel": 4.396589088895034, - "Leeway": 1.1382556850903227, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.8350543687907885, - "Heel": 6.972448869067321, - "Leeway": 1.779535494020808, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.479810004291103, - "Heel": 3.314798897810764, - "Leeway": 1.2436471446996191, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.857234663904952, - "Heel": 3.9529239320544196, - "Leeway": 1.0355011016170228, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.899915625188863, - "Heel": 6.577223251526935, - "Leeway": 1.647880840878095, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.490231907697427, - "Heel": 3.061699033398183, - "Leeway": 1.144084786589654, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.665120132396449, - "Heel": 3.480887803352275, - "Leeway": 0.9355583417798582, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.92733630754455, - "Heel": 6.145919838509825, - "Leeway": 1.5191236438052778, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.4658141200740396, - "Heel": 2.773621744279308, - "Leeway": 1.0468034662003571, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.407641632864899, - "Heel": 2.930391228944299, - "Leeway": 0.8282967287910431, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.9171632750234435, - "Heel": 5.689807009886519, - "Leeway": 1.3924404770834669, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.36882921288762, - "Heel": 2.416452647085209, - "Leeway": 0.9465115738952768, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.026273364321343, - "Heel": 2.2691437159350407, - "Leeway": 0.7206402416069873, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.847357472263064, - "Heel": 5.164831437937939, - "Leeway": 1.2600484157950242, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.224442136761386, - "Heel": 2.0448051095633657, - "Leeway": 0.8485390988760906, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.530507646120144, - "Heel": 1.6439763200507282, - "Leeway": 0.6234509423940068, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.716884509938393, - "Heel": 4.60096096946138, - "Leeway": 1.1276168358818581, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.042426530371825, - "Heel": 1.686358965079522, - "Leeway": 0.7524872795656057, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.057766614014445, - "Heel": 1.1715535438564535, - "Leeway": 0.5341800887071363, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.5504874513565525, - "Heel": 4.028175678159122, - "Leeway": 0.9969170491367318, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.762964000859664, - "Heel": 1.3204075328948934, - "Leeway": 0.6560074662316578, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.673659374373028, - "Heel": 0.8405609736665967, - "Leeway": 0.4490681693279574, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.322599696657643, - "Heel": 3.397319106078804, - "Leeway": 0.8639713134404041, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.40957997226431, - "Heel": 0.9791972151190418, - "Leeway": 0.5582844472484715, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.993973571825034, - "Heel": 2.655058091316036, - "Leeway": 0.7283945082867322, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.0756971464556635, - "Heel": 0.6964008193975818, - "Leeway": 0.45348676525564713, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.5934904977870845, - "Heel": 1.8486351569983477, - "Leeway": 0.5838626105119289, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.75365269003444, - "Heel": 0.471905536204152, - "Leeway": 0.35075575524828273, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.155972124135593, - "Heel": 1.1636937060477657, - "Leeway": 0.4340296670112946, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.444411492810563, - "Heel": 0.31622671217128867, - "Leeway": 0.2705897740630409, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.773665161819413, - "Heel": 0.7091727384179249, - "Leeway": 0.3099539782205789, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.159705996340931, - "Heel": 0.21251784097610613, - "Leeway": 0.20554885278572674, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.42074573105769, - "Heel": 0.4093860393107844, - "Leeway": 0.20866938559679823, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.886896354349036, - "Heel": 0.14390904772222438, - "Leeway": 0.15467402083498197, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.094511244373853, - "Heel": 0.223873057534199, - "Leeway": 0.1317195296645853, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.6398167195384916, - "Heel": 0.09824755477856288, - "Leeway": 0.11642329657226917, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.817979694627767, - "Heel": 0.13369296489603721, - "Leeway": 0.08901030433816327, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.4689705336149967, - "Heel": 0.07648699817984324, - "Leeway": 0.09591568203193528, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.651681923896128, - "Heel": 0.08769411124680271, - "Leeway": 0.06307726115281655, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.3579045809091483, - "Heel": 0.05375163703580453, - "Leeway": 0.07039753933082829, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.558041259377993, - "Heel": 0.04856098514920144, - "Leeway": 0.036523574786747025, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.294541775624435, - "Heel": 0.03352747429725701, - "Leeway": 0.045141098014153586, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.533631992243373, - "Heel": 0.021138756750039015, - "Leeway": 0.01611924913947008, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 3.2812110308544074, - "Heel": 0.01681008169721984, - "Leeway": 0.022717176808041032, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.00670116949316, - "Heel": 8.6246205709952, - "Leeway": 4.554820775987639, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.187407920055945, - "Heel": 10.715492710633711, - "Leeway": 3.544353852589292, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.940762990158405, - "Heel": 11.910097690244552, - "Leeway": 3.039958014106463, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.4053160012674955, - "Heel": 12.375957919152444, - "Leeway": 2.747863156723882, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.66630947785322, - "Heel": 12.340542188730346, - "Leeway": 2.5601219410166607, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.87447870562492, - "Heel": 12.09712888068183, - "Leeway": 2.3934536016673076, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.952239826755748, - "Heel": 11.369755289097496, - "Leeway": 2.246806852251952, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.008103885284877, - "Heel": 10.533580127385349, - "Leeway": 2.101439101152284, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.418484449284785, - "Heel": 11.484784738192065, - "Leeway": 3.0270099353659994, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.660727932378767, - "Heel": 5.658636786868139, - "Leeway": 2.07774953883622, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.042675095932594, - "Heel": 9.687871579875978, - "Leeway": 1.959303064568863, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.993824700198823, - "Heel": 12.710551331558124, - "Leeway": 2.7441521676905603, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.067094748228624, - "Heel": 5.742633478412799, - "Leeway": 1.9388441376231915, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.057396701977291, - "Heel": 8.885397613730039, - "Leeway": 1.8192968095975695, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.427700632293086, - "Heel": 13.723429620550293, - "Leeway": 2.573755506568138, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.38333714222309, - "Heel": 5.744493443871703, - "Leeway": 1.8319464018791447, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.05368255638196, - "Heel": 8.048116319434087, - "Leeway": 1.6814639575862997, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.726177653570329, - "Heel": 14.483622790209793, - "Leeway": 2.4749483390982414, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.573763578216786, - "Heel": 5.63729992852014, - "Leeway": 1.7520093395202319, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.031072523697388, - "Heel": 7.20375943202514, - "Leeway": 1.5464280438666054, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.893147781170329, - "Heel": 14.204659035343868, - "Leeway": 2.3343056112468075, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.664250934825591, - "Heel": 5.376620047212393, - "Leeway": 1.6384639665055172, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.992097530767815, - "Heel": 6.4821797105374594, - "Leeway": 1.4130547577115287, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.987742095875764, - "Heel": 13.591383650053663, - "Leeway": 2.2030043744317864, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.73095190757066, - "Heel": 5.087852422080344, - "Leeway": 1.5238033241341944, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.940443684400231, - "Heel": 5.85395625932522, - "Leeway": 1.2804210092660053, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.061327647528765, - "Heel": 12.788323155476283, - "Leeway": 2.066017156642733, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.772259556179872, - "Heel": 4.773797168082708, - "Leeway": 1.4085834200672094, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.871425075864138, - "Heel": 5.261784342754399, - "Leeway": 1.146067230739344, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.111788868824037, - "Heel": 11.809812803548597, - "Leeway": 1.9244927183217606, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.7772898902744565, - "Heel": 4.423737604025408, - "Leeway": 1.28947142218624, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.718287030184707, - "Heel": 4.609818635144034, - "Leeway": 1.0068131304473764, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.116896913981291, - "Heel": 10.481783909004605, - "Leeway": 1.7652808185321511, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.739443372067974, - "Heel": 4.0324173703546355, - "Leeway": 1.165007230257703, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.5525427227718875, - "Heel": 3.9926573606513935, - "Leeway": 0.8730581491793361, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.093447951372665, - "Heel": 9.185577014932527, - "Leeway": 1.6003080301582724, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.678232383010578, - "Heel": 3.6224083580653756, - "Leeway": 1.0424021052122625, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.358958845856639, - "Heel": 3.3820882312211284, - "Leeway": 0.7476809596267675, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.047697381568764, - "Heel": 7.933019623132993, - "Leeway": 1.4345657109981294, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.595610699052058, - "Heel": 3.1902607320442065, - "Leeway": 0.9218290885445234, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.00336612571257, - "Heel": 2.6945244036791074, - "Leeway": 0.6397283804639731, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.979876390560853, - "Heel": 6.750038996157418, - "Leeway": 1.2682882120537184, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.453891707769054, - "Heel": 2.697948412390906, - "Leeway": 0.8002592192530945, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.578250869042223, - "Heel": 2.010690653244344, - "Leeway": 0.5420726216739402, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.863929786787751, - "Heel": 5.727032148204245, - "Leeway": 1.090988089548562, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.263468719254678, - "Heel": 2.173131285904598, - "Leeway": 0.6838159899539942, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.0954429397589855, - "Heel": 1.455380992267711, - "Leeway": 0.4568265151295812, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.680007665360494, - "Heel": 4.825319501450383, - "Leeway": 0.923573138383536, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.985669397774461, - "Heel": 1.662223419642298, - "Leeway": 0.5695716352616119, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.462182776347462, - "Heel": 3.941429073644051, - "Leeway": 0.7523545252635099, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.63518736134377, - "Heel": 1.2007570425558225, - "Leeway": 0.4576156798026558, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.151340041391437, - "Heel": 3.0035600202859283, - "Leeway": 0.5883961616345718, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.246595232737753, - "Heel": 0.8205221807189609, - "Leeway": 0.3531917129889434, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.7469322056967735, - "Heel": 2.0000110228978634, - "Leeway": 0.43569180024309095, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.811607389366975, - "Heel": 0.5517355855620327, - "Leeway": 0.27622939294630194, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.289985837786323, - "Heel": 1.2397625379768293, - "Leeway": 0.3121525578879877, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.381132422923635, - "Heel": 0.37236667184720307, - "Leeway": 0.2144400780290574, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.797500397724515, - "Heel": 0.7146305873404492, - "Leeway": 0.21171893673666778, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.051659619523926, - "Heel": 0.25421262297417346, - "Leeway": 0.1611943420871609, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.3192377533418576, - "Heel": 0.3929657714939348, - "Leeway": 0.136719301185858, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.764210088216766, - "Heel": 0.17694778579213927, - "Leeway": 0.12202104176611489, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.994669918956161, - "Heel": 0.24230412358474643, - "Leeway": 0.09417070385907694, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.571933504080339, - "Heel": 0.1384612583358568, - "Leeway": 0.09981516611740063, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.796080931278461, - "Heel": 0.15858205273819997, - "Leeway": 0.06609771628376919, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.4426776315107315, - "Heel": 0.09650158287276075, - "Leeway": 0.07218487049612189, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.6879096734068115, - "Heel": 0.08830877682114117, - "Leeway": 0.03825832460668311, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.368777676930061, - "Heel": 0.06017584666557224, - "Leeway": 0.04607252752860947, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.6592387538642335, - "Heel": 0.038784189283701545, - "Leeway": 0.017011104794069976, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 4.353272028136822, - "Heel": 0.03024306023233024, - "Leeway": 0.023219172664673535, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.267118115652627, - "Heel": 14.975063780247556, - "Leeway": 4.427748262722796, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.322818480912646, - "Heel": 17.583312527276256, - "Leeway": 3.6862394998014203, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.787375970358124, - "Heel": 18.128569581442427, - "Leeway": 3.3439411333309335, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.080847525059086, - "Heel": 18.16933536920731, - "Leeway": 3.1132078966901773, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.311858296223445, - "Heel": 17.94077190771431, - "Leeway": 2.9145744988265445, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.465695118290375, - "Heel": 17.236060611658658, - "Leeway": 2.72326099480503, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.594776119515592, - "Heel": 16.34590239343974, - "Leeway": 2.5359031237406278, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.688673458538087, - "Heel": 15.312406816062419, - "Leeway": 2.36110196758839, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.620311856123459, - "Heel": 19.59170259891216, - "Leeway": 3.258311787451566, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.72163783981931, - "Heel": 8.488342809870327, - "Leeway": 2.3503863498461164, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.749566590930016, - "Heel": 14.15489133595764, - "Leeway": 2.195734055453108, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.045040988265361, - "Heel": 21.84705363958181, - "Leeway": 3.141382609817859, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.005825457946935, - "Heel": 8.518071444134433, - "Leeway": 2.279276622927746, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.779163103830811, - "Heel": 12.89280447567176, - "Leeway": 2.0372577149528177, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.365854027733306, - "Heel": 23.41189348752423, - "Leeway": 3.0396689429018844, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.202373853540584, - "Heel": 8.317051679595519, - "Leeway": 2.1931407546100377, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.774777942091541, - "Heel": 11.54920651408176, - "Leeway": 1.8861162066393926, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.59273466690872, - "Heel": 23.167169582310844, - "Leeway": 2.858228408516219, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.324479546858832, - "Heel": 7.837090531143627, - "Leeway": 2.0618645568396192, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.743504916144715, - "Heel": 10.150896224381079, - "Leeway": 1.7382246181493868, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.797192411952604, - "Heel": 22.544853082330583, - "Leeway": 2.6755571821924815, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.428289348870864, - "Heel": 7.293415991207425, - "Leeway": 1.9264494199774713, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.69143080211048, - "Heel": 8.939826173697057, - "Leeway": 1.589036945058244, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.972380790305278, - "Heel": 21.538285980282424, - "Leeway": 2.4952905077950827, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.514582434841994, - "Heel": 6.760925935261364, - "Leeway": 1.7869557713395856, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.587512454305337, - "Heel": 7.601797138943901, - "Leeway": 1.4279840187681114, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.108278230676548, - "Heel": 20.060445393598293, - "Leeway": 2.3159223876367436, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.54405701437451, - "Heel": 6.195733054356211, - "Leeway": 1.6366651182791156, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.448131456949985, - "Heel": 6.426747083882084, - "Leeway": 1.2644082927781748, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.161484079787895, - "Heel": 18.38238039289464, - "Leeway": 2.1283358631559204, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.540175292668444, - "Heel": 5.649731621377461, - "Leeway": 1.4865816021663858, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.304897464680376, - "Heel": 5.541239536107098, - "Leeway": 1.1048725763263139, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.17286826915485, - "Heel": 16.523038580394815, - "Leeway": 1.9472731527761584, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.508926263508451, - "Heel": 5.12108272548297, - "Leeway": 1.3389014826830934, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.13875440096554, - "Heel": 4.785103488152481, - "Leeway": 0.9576209301169621, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.14336855418222, - "Heel": 14.504524939389826, - "Leeway": 1.7707182059659627, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.426614472767925, - "Heel": 4.576839471295092, - "Leeway": 1.191313706209335, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.96903130796391, - "Heel": 4.10354641327166, - "Leeway": 0.8194319460763466, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.040467346362531, - "Heel": 12.1687896970731, - "Leeway": 1.5898292854649687, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.314807895494413, - "Heel": 4.027183294642999, - "Leeway": 1.0440749274813368, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.7607388558162835, - "Heel": 3.444056262933565, - "Leeway": 0.6931071865844939, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.883804419939228, - "Heel": 9.753974844101757, - "Leeway": 1.404140269088569, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.18327233868519, - "Heel": 3.4602522755637284, - "Leeway": 0.8948137729209271, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.509326567433543, - "Heel": 2.7853520892044776, - "Leeway": 0.5789540490571627, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.693041169945605, - "Heel": 7.678751273376105, - "Leeway": 1.205037809072191, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.038669108932769, - "Heel": 2.874512687285524, - "Leeway": 0.7500888350543822, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.203259748100359, - "Heel": 2.1413609287078033, - "Leeway": 0.48031608020493033, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.479471174231957, - "Heel": 6.049119711081231, - "Leeway": 1.0064076862177058, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.877410173151802, - "Heel": 2.248063142828709, - "Leeway": 0.6074947121307919, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.24575229883419, - "Heel": 4.804350549737878, - "Leeway": 0.8008698446919719, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.636022905278717, - "Heel": 1.6654301601612451, - "Leeway": 0.4795004559060566, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.020781724950268, - "Heel": 3.74874946242638, - "Leeway": 0.613508040231461, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.386559923935502, - "Heel": 1.195390137177191, - "Leeway": 0.36892003076388247, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.762335540973506, - "Heel": 2.744942841895556, - "Leeway": 0.45437487826760264, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.032479143407609, - "Heel": 0.8377510145389025, - "Leeway": 0.2861603496146203, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.4444840282643145, - "Heel": 1.7944103153390751, - "Leeway": 0.3226651260627099, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.6518245698708025, - "Heel": 0.5791868832191565, - "Leeway": 0.2179790095067216, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.053219733881746, - "Heel": 1.085887604022652, - "Leeway": 0.21720444945832906, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.2785905242973366, - "Heel": 0.396805626125209, - "Leeway": 0.16275494592078946, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.596891820617031, - "Heel": 0.6121817661673865, - "Leeway": 0.1383917702796448, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.913037233087093, - "Heel": 0.2779359339736404, - "Leeway": 0.1242808308854246, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.213642801691225, - "Heel": 0.3805108235790742, - "Leeway": 0.09552949702955409, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.6548431129983685, - "Heel": 0.21992091223993399, - "Leeway": 0.10349885611308589, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.962251512498037, - "Heel": 0.24932423574161336, - "Leeway": 0.0672332992722485, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.476486848454987, - "Heel": 0.15343923927594627, - "Leeway": 0.07550213568439608, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.8223700382128, - "Heel": 0.13965042873337946, - "Leeway": 0.03922011644441139, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.375731085920928, - "Heel": 0.09659129156069741, - "Leeway": 0.04883639784741841, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.785545810017779, - "Heel": 0.06172293872477994, - "Leeway": 0.017557607771842888, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 5.355067345935869, - "Heel": 0.04906569462697382, - "Leeway": 0.024894329819662435, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.204319368499826, - "Heel": 22.521454488878877, - "Leeway": 4.576181364798577, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.922011451982043, - "Heel": 25.027059445443207, - "Leeway": 4.079744594696405, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.312949990072957, - "Heel": 25.457916379689006, - "Leeway": 3.755175700958929, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.712745842423233, - "Heel": 25.704010887715427, - "Leeway": 3.4462274923987373, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.041494204006112, - "Heel": 25.38834873097577, - "Leeway": 3.1742050513644062, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.292089596057258, - "Heel": 24.402482732974963, - "Leeway": 2.922825661921523, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.497651222221181, - "Heel": 23.12059314227339, - "Leeway": 2.6975117981066474, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.655405334532086, - "Heel": 21.54620781083049, - "Leeway": 2.494987201678334, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.350088414582371, - "Heel": 31.381995622004013, - "Leeway": 4.186039818579681, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.404693762522042, - "Heel": 12.47531418124128, - "Leeway": 2.7465449962544786, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.759218817209037, - "Heel": 19.76174278479236, - "Leeway": 2.312126634042093, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.876216250347218, - "Heel": 34.87463108420466, - "Leeway": 4.002027032696338, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.770428330498016, - "Heel": 12.53540860705034, - "Leeway": 2.6190430543836243, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.810039830432613, - "Heel": 18.210482622886545, - "Leeway": 2.14151174939726, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.267054781874307, - "Heel": 35.29845266757778, - "Leeway": 3.70480197575839, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.006653791982002, - "Heel": 11.994216995977675, - "Leeway": 2.4445812311272337, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.808663622949679, - "Heel": 16.51076326033704, - "Leeway": 1.983766504817516, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.641212173941263, - "Heel": 35.16961723614471, - "Leeway": 3.4112979632935834, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.222533311216653, - "Heel": 11.316912708915762, - "Leeway": 2.271139496467263, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.769264499084965, - "Heel": 14.702906869689361, - "Leeway": 1.8311104830038858, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.984333803374927, - "Heel": 34.48053604844781, - "Leeway": 3.1305171568278967, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.404060002022108, - "Heel": 10.498989107010539, - "Leeway": 2.103065009409499, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.669481592866285, - "Heel": 12.675456522557731, - "Leeway": 1.6753861225677023, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.290806993222233, - "Heel": 33.20587028385052, - "Leeway": 2.8628781429971526, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.51857328090073, - "Heel": 9.558644875084484, - "Leeway": 1.9340658540825297, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.477405318167559, - "Heel": 10.363897180883574, - "Leeway": 1.5120971131497443, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.488652131513774, - "Heel": 30.68593118059843, - "Leeway": 2.605545203428352, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.566649812924007, - "Heel": 8.569940156936493, - "Leeway": 1.7666186793944847, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.263403685195922, - "Heel": 8.483825296078324, - "Leeway": 1.3486541861912056, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.631086213701163, - "Heel": 28.078883025579167, - "Leeway": 2.166010800096515, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.570379995626181, - "Heel": 7.528741691877999, - "Leeway": 1.6048864900809103, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.001018855440611, - "Heel": 6.853678981687011, - "Leeway": 1.195883999577062, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.657516615264692, - "Heel": 25.09741138046956, - "Leeway": 1.971258427775608, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.507802273202971, - "Heel": 6.559389675490821, - "Leeway": 1.4469717369035837, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.738020537764227, - "Heel": 5.735215887431274, - "Leeway": 1.0481698979217, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.615642512408701, - "Heel": 21.669363306025247, - "Leeway": 1.7945306277595992, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.35828674476759, - "Heel": 5.715811430421642, - "Leeway": 1.2912991962992393, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.486720394787836, - "Heel": 4.865150371298299, - "Leeway": 0.9057068053347493, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.40981726319784, - "Heel": 17.91764589632194, - "Leeway": 1.620824467691918, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.173351813978433, - "Heel": 4.957058266852559, - "Leeway": 1.1325870776293432, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.262554519209983, - "Heel": 4.121277010812508, - "Leeway": 0.7691935028078396, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.160251465020021, - "Heel": 14.518778561556394, - "Leeway": 1.4424918815858843, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.959129560948071, - "Heel": 4.2408056709832325, - "Leeway": 0.9738200787229574, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.057775161062876, - "Heel": 3.451437543487418, - "Leeway": 0.6437694658377527, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.847545723725446, - "Heel": 10.993549575695859, - "Leeway": 1.2521640773481053, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.724599063258461, - "Heel": 3.5268159920045603, - "Leeway": 0.8120746313777711, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.861395719985954, - "Heel": 2.829611747003592, - "Leeway": 0.5333296083633694, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.491394152334816, - "Heel": 7.983666357078287, - "Leeway": 1.0472479828872265, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.48669243809271, - "Heel": 2.8123559319507327, - "Leeway": 0.6587444827258983, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.127847952647572, - "Heel": 5.868010344624862, - "Leeway": 0.8411328396723189, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.262987526271257, - "Heel": 2.1150454378517836, - "Leeway": 0.5209541271425042, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.777461028241094, - "Heel": 4.512591655799132, - "Leeway": 0.6537279187511996, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.0458659114186, - "Heel": 1.5737132683966533, - "Leeway": 0.4108739157449619, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.44829071990969, - "Heel": 3.402908134649702, - "Leeway": 0.4923061954451142, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.820662862444846, - "Heel": 1.145044052311941, - "Leeway": 0.31456454201251605, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.150678496769288, - "Heel": 2.3331927983964986, - "Leeway": 0.3497492117704487, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.563622840833893, - "Heel": 0.8203793128832592, - "Leeway": 0.23711858681969272, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.888919111909314, - "Heel": 1.4673306131162616, - "Leeway": 0.23407365697297874, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.2751284465580595, - "Heel": 0.5696292228519811, - "Leeway": 0.17320409244157944, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.550397178447732, - "Heel": 0.8713019305895755, - "Leeway": 0.14986812449425524, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.954439370221036, - "Heel": 0.4064720128234979, - "Leeway": 0.13099202151867836, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.2580194145957595, - "Heel": 0.5645321994731252, - "Leeway": 0.10374695268000826, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.710090907493888, - "Heel": 0.3222081359812377, - "Leeway": 0.10755176356742882, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.026904989541314, - "Heel": 0.3663649213720923, - "Leeway": 0.07109310551125904, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.532901684874543, - "Heel": 0.22258124517279157, - "Leeway": 0.07695390406789013, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.891132468338262, - "Heel": 0.20625531209111062, - "Leeway": 0.04134867477427907, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.431709510455198, - "Heel": 0.13978951584065338, - "Leeway": 0.04937318341040293, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.855584303701534, - "Heel": 0.09196783443778916, - "Leeway": 0.018631771316979576, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 6.409601645982556, - "Heel": 0.0710730046906942, - "Leeway": 0.02517066093987956, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.589829304093802, - "Heel": 30.84102734854441, - "Leeway": 5.722333008668588, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.249282969613434, - "Heel": 33.14310628845854, - "Leeway": 5.100500474533835, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.821331123564383, - "Heel": 34.256590462183425, - "Leeway": 4.576549931690612, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.412447175693913, - "Heel": 35.09748804938003, - "Leeway": 4.100426428665641, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.87278049593021, - "Heel": 34.77979926037813, - "Leeway": 3.7055911235210144, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.286542631477818, - "Heel": 33.85705202798442, - "Leeway": 3.348102425487225, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.649696734953892, - "Heel": 32.47947760236681, - "Leeway": 3.0338916521145918, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.938859803543426, - "Heel": 30.535632850245552, - "Leeway": 2.7598512666344472, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.102692144328298, - "Heel": 44.533975741627685, - "Leeway": 4.810219085913932, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.250750598170475, - "Heel": 17.40715230242235, - "Leeway": 2.9794947083858996, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.220342813940992, - "Heel": 28.766443380535655, - "Leeway": 2.2996149950700646, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.70297039981047, - "Heel": 46.38413919600232, - "Leeway": 4.436696566805277, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.722946297708235, - "Heel": 17.368455207285923, - "Leeway": 2.758605017632081, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.3147888965456, - "Heel": 26.462220431052586, - "Leeway": 2.1147131466307982, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.241396301696682, - "Heel": 46.878545199658895, - "Leeway": 4.032698780946958, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.08739140973012, - "Heel": 16.819281004856485, - "Leeway": 2.5363419966893437, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.314067188616434, - "Heel": 23.740210823112108, - "Leeway": 1.9556084706654455, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.777275935556835, - "Heel": 46.9265938446381, - "Leeway": 3.645411835725819, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.416096998681658, - "Heel": 16.070660445567754, - "Leeway": 2.3285119592024213, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.24476897226212, - "Heel": 20.673986218818786, - "Leeway": 1.811665254505315, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.297243709849473, - "Heel": 46.51352909777687, - "Leeway": 3.281470236499611, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.704931250166451, - "Heel": 15.130402228310604, - "Leeway": 2.133470841258384, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.043257348158315, - "Heel": 17.74259149258358, - "Leeway": 1.6693927985334673, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.778871096494003, - "Heel": 45.59595231062371, - "Leeway": 2.947411833843889, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.867802437694257, - "Heel": 13.817063204386045, - "Leeway": 1.9522775460332427, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.722426644788682, - "Heel": 14.64140306347736, - "Leeway": 1.5244115212560583, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.134645706980109, - "Heel": 43.528629543606, - "Leeway": 2.6346155998688294, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.94478880917017, - "Heel": 12.298656736433008, - "Leeway": 1.783609877908594, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.368639480650106, - "Heel": 11.676790922268196, - "Leeway": 1.3801109783290122, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.403417875539354, - "Heel": 40.74429511535355, - "Leeway": 2.354187174609207, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.953752934369467, - "Heel": 10.661184753011483, - "Leeway": 1.6244387973227383, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.944497875871683, - "Heel": 9.104035635871847, - "Leeway": 1.2476292671949138, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.558986955922501, - "Heel": 37.09859771203556, - "Leeway": 2.105692762223069, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.83486578857658, - "Heel": 9.036453784097482, - "Leeway": 1.4736225019776823, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.55479056682962, - "Heel": 7.152026322614642, - "Leeway": 1.1103702086448306, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.57554453381598, - "Heel": 32.38681008971425, - "Leeway": 1.8865553151307406, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.605276629724424, - "Heel": 7.464068065873704, - "Leeway": 1.3277275459417894, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.196019226134354, - "Heel": 5.849405331075145, - "Leeway": 0.9721691155480285, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.327810704962175, - "Heel": 26.75908918108136, - "Leeway": 1.553245041333477, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.31473911854622, - "Heel": 6.16616910440189, - "Leeway": 1.1723120401028246, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.868813940670051, - "Heel": 4.909770119828604, - "Leeway": 0.8388953998604978, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.919264881491662, - "Heel": 20.92190437303, - "Leeway": 1.3980286374659043, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.9978222290515, - "Heel": 5.169289106594585, - "Leeway": 1.0137682745359429, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.581187553099554, - "Heel": 4.15156192773857, - "Leeway": 0.7160137143514692, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.411687161145531, - "Heel": 15.943174321988325, - "Leeway": 1.233305003761509, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.650125708184271, - "Heel": 4.275145561625392, - "Leeway": 0.8503861359220618, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.303874163134227, - "Heel": 3.5667471824434527, - "Leeway": 0.6259423966641324, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.841581909468303, - "Heel": 11.216880602822995, - "Leeway": 1.0506927694031973, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.315418324803646, - "Heel": 3.4524245192236407, - "Leeway": 0.6945134023498063, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.298428599944481, - "Heel": 7.6145830120314475, - "Leeway": 0.8578978621723438, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.97364318876277, - "Heel": 2.7009697390182272, - "Leeway": 0.5670707625285751, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.789029316111785, - "Heel": 5.512972074753141, - "Leeway": 0.6808395434274737, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.665589491550598, - "Heel": 2.026077472135744, - "Leeway": 0.4550045500067895, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.309816025072415, - "Heel": 4.131209255397667, - "Leeway": 0.5189351607557456, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.386641326335234, - "Heel": 1.5017339079205632, - "Leeway": 0.35605020985566466, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.893433392656009, - "Heel": 2.9675498355224663, - "Leeway": 0.37749257110761514, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.162926592340265, - "Heel": 1.1057507152552453, - "Leeway": 0.2706912266940385, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.503507265280808, - "Heel": 1.8878730280508182, - "Leeway": 0.2579019609026488, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.936278871341309, - "Heel": 0.7802793824184705, - "Leeway": 0.19767430007618028, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.196218891286865, - "Heel": 1.1911902344614862, - "Leeway": 0.17313672374621222, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.7079878585975745, - "Heel": 0.5774887516431375, - "Leeway": 0.15040044323178584, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.978282612926164, - "Heel": 0.8252826898178685, - "Leeway": 0.1252368249909448, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.533021373935309, - "Heel": 0.46484302646021824, - "Leeway": 0.12260345490011862, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.820709527880875, - "Heel": 0.5252593535046245, - "Leeway": 0.08220115679440393, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.400459968005457, - "Heel": 0.31396854503387145, - "Leeway": 0.08452603418968033, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.710762618597458, - "Heel": 0.3019780090753742, - "Leeway": 0.04834438152945783, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.316454618199522, - "Heel": 0.19831834448315652, - "Leeway": 0.05411833703588422, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.682735605740955, - "Heel": 0.13868244178485234, - "Leeway": 0.022371553703750265, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.296169587049998, - "Heel": 0.10220333682666799, - "Leeway": 0.02793359305483613, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.924467519909696, - "Heel": 40.401677293656405, - "Leeway": 6.537366893260317, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.633833007214223, - "Heel": 42.00220283344436, - "Leeway": 5.7283160842408805, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.394155438826404, - "Heel": 43.40232692332444, - "Leeway": 5.012156624220767, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.143413827402615, - "Heel": 44.403835282975585, - "Leeway": 4.412506262463724, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.780683109635657, - "Heel": 44.36679331539559, - "Leeway": 3.9081513877014005, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.415387714673512, - "Heel": 44.04056310121143, - "Leeway": 3.462115947530888, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.013167554561232, - "Heel": 43.361021106198955, - "Leeway": 3.0782556333206283, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.54686158650964, - "Heel": 42.23386354104649, - "Leeway": 2.7490213343790293, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.82347780782249, - "Heel": 54.169073684268234, - "Leeway": 5.383737497616505, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.310132997207944, - "Heel": 23.914953276655467, - "Leeway": 3.0447748221933466, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.965952007863002, - "Heel": 40.48168693945135, - "Leeway": 2.471502263342431, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.52136102504086, - "Heel": 55.11532232630858, - "Leeway": 4.825836314234407, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.96462734399137, - "Heel": 23.960334744420727, - "Leeway": 2.748139387629851, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.263383153151379, - "Heel": 38.04210733825359, - "Leeway": 2.2338041154089083, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.253688364347216, - "Heel": 55.69078781724273, - "Leeway": 4.291595177756218, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.528883596544594, - "Heel": 23.367775324756455, - "Leeway": 2.476372700513029, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.404231491287769, - "Heel": 34.74451174305254, - "Leeway": 2.0322983184487864, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.982908247057699, - "Heel": 55.872791474403215, - "Leeway": 3.805933919733268, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.040959545225485, - "Heel": 22.40967087761371, - "Leeway": 2.236173233423896, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.36292963267487, - "Heel": 30.366136031241115, - "Leeway": 1.8601405741689847, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.675858577676014, - "Heel": 55.633691481752024, - "Leeway": 3.3763440077524085, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.474319251979262, - "Heel": 21.046259162803437, - "Leeway": 2.0272263743407883, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.178862708752993, - "Heel": 26.363974003641864, - "Leeway": 1.5666939470859613, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.276320997954954, - "Heel": 54.83762551668359, - "Leeway": 3.0045974106292466, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.73582885439417, - "Heel": 19.28419564410593, - "Leeway": 1.8482452614033686, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.629982297259026, - "Heel": 20.7860843372463, - "Leeway": 1.4483723265976332, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.747754079152719, - "Heel": 53.14720913795133, - "Leeway": 2.665354351191314, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.839371105110635, - "Heel": 17.41241894101848, - "Leeway": 1.6886703363804294, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.007234770331955, - "Heel": 16.464429037271422, - "Leeway": 1.3361065621837693, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.131135469967484, - "Heel": 50.869963051132004, - "Leeway": 2.3638324008249136, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.840619857637307, - "Heel": 15.335599223831705, - "Leeway": 1.5431494853947139, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.270408748657209, - "Heel": 12.586377983754435, - "Leeway": 1.240050714106743, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.41322896259439, - "Heel": 47.88911519045748, - "Leeway": 2.094372705995733, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.696790138680115, - "Heel": 13.025190753910845, - "Leeway": 1.4096690875307147, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.639876029853124, - "Heel": 9.464613743588544, - "Leeway": 1.130711570946118, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.572380374845368, - "Heel": 44.030564392287104, - "Leeway": 1.8535780283828371, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.330143771780605, - "Heel": 10.446431073280703, - "Leeway": 1.2880512993036317, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.114709964232015, - "Heel": 7.282865765185633, - "Leeway": 1.0086337125738114, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.499315250593028, - "Heel": 38.46214012481252, - "Leeway": 1.6351557795740623, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.874303616827364, - "Heel": 8.241865098012124, - "Leeway": 1.1560532806123711, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.662037759760539, - "Heel": 5.887079795543459, - "Leeway": 0.8854710155691043, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.113853839684149, - "Heel": 30.58888673321167, - "Leeway": 1.4442882467691383, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.375586576297486, - "Heel": 6.430206467390789, - "Leeway": 1.0145803755548692, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.266507203390436, - "Heel": 4.9502427502592505, - "Leeway": 0.7710016445838604, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.444451867970141, - "Heel": 22.900887061680578, - "Leeway": 1.168450095155542, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.84427013032545, - "Heel": 5.1670178695952735, - "Leeway": 0.8622635669917238, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.868355508211007, - "Heel": 4.365429027332485, - "Leeway": 0.7103844682334117, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.655198122299108, - "Heel": 16.10302889597835, - "Leeway": 1.0128069979774423, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.3652203461078, - "Heel": 4.158254580715778, - "Leeway": 0.7113149587992831, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.834359417732344, - "Heel": 10.539216953376297, - "Leeway": 0.8452049366158773, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.890530020599375, - "Heel": 3.3338388565304697, - "Leeway": 0.5938590778937497, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.074888832002646, - "Heel": 6.923836276072279, - "Leeway": 0.6851783053201277, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.463050168879697, - "Heel": 2.5803204347045527, - "Leeway": 0.4847254927636262, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.389195671250606, - "Heel": 4.97362612220515, - "Leeway": 0.5313063071316809, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.105619621994812, - "Heel": 1.9328897640870435, - "Leeway": 0.38678876982711985, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.82597263227653, - "Heel": 3.6242839743973905, - "Leeway": 0.3928071882553591, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.776945941472881, - "Heel": 1.4260645517953754, - "Leeway": 0.29990188078345187, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.29747741080768, - "Heel": 2.4172064412690393, - "Leeway": 0.27547175222206954, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.478288957558116, - "Heel": 1.0378825569187093, - "Leeway": 0.22846143042192052, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.88438987162702, - "Heel": 1.5814160882754402, - "Leeway": 0.1951072728303694, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.260542156551558, - "Heel": 0.799193325173701, - "Leeway": 0.17979430417245887, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.606786957611888, - "Heel": 1.158992901263238, - "Leeway": 0.15087420615577699, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.115110041505494, - "Heel": 0.652110146328968, - "Leeway": 0.14754766097925798, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.423399893222747, - "Heel": 0.7305043544763197, - "Leeway": 0.09844585170105373, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.004588648790355, - "Heel": 0.4368788568594928, - "Leeway": 0.10040877338784732, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.325530805841922, - "Heel": 0.4319496798415761, - "Leeway": 0.05930606745478007, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.943645586699635, - "Heel": 0.28020145908885163, - "Leeway": 0.06484290775391288, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.301754031505173, - "Heel": 0.20594235996744173, - "Leeway": 0.02845188858801268, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 7.932241296268953, - "Heel": 0.14802133688365762, - "Leeway": 0.03422814457508885, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.102096938913997, - "Heel": 47.47396285498962, - "Leeway": 7.470255487573004, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.983795713216503, - "Heel": 49.219438610348206, - "Leeway": 6.36889418998746, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.931498620488126, - "Heel": 50.76981509371812, - "Leeway": 5.440730972780667, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.847992518990893, - "Heel": 51.81300295018351, - "Leeway": 4.699951285607713, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.712467405160979, - "Heel": 52.13506338125195, - "Leeway": 4.073969383135376, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.570781136465017, - "Heel": 52.207266671473704, - "Leeway": 3.5486049472767567, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.329640608570704, - "Heel": 51.81847519133657, - "Leeway": 3.126195573843961, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.017305482700747, - "Heel": 51.03273204613721, - "Leeway": 2.7699200358209786, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.502315137829225, - "Heel": 60.959313672384475, - "Leeway": 5.851089934559998, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.546185539164778, - "Heel": 32.04596216246131, - "Leeway": 3.312575886421022, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.620694396564291, - "Heel": 49.802044676779005, - "Leeway": 2.465426989410198, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.43071113359599, - "Heel": 61.92669159793112, - "Leeway": 5.105717340984107, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.500577753938943, - "Heel": 32.63932137239536, - "Leeway": 2.9095865431182117, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 15.096392296343476, - "Heel": 47.99264426912814, - "Leeway": 2.206966287370736, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.387325316954673, - "Heel": 62.55035344224307, - "Leeway": 4.44362229304132, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.276638545303495, - "Heel": 32.12044242937441, - "Leeway": 2.5814169605846735, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 15.417832740734065, - "Heel": 45.49250505542562, - "Leeway": 1.9871402898617558, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.282970814877086, - "Heel": 62.776602235159274, - "Leeway": 3.8956665592683164, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.964757950442984, - "Heel": 30.967837889470236, - "Leeway": 2.302900768761267, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 15.59930124957754, - "Heel": 42.2219436286847, - "Leeway": 1.7930187717631731, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.118595304480822, - "Heel": 62.62151111458227, - "Leeway": 3.429242547273626, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.60251797827849, - "Heel": 29.47764359816851, - "Leeway": 1.8877969532066896, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 15.618306577342512, - "Heel": 37.976905865367506, - "Leeway": 1.6200511849448715, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.859749650765412, - "Heel": 61.97004854816861, - "Leeway": 3.0255171505590073, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 15.006363391244179, - "Heel": 27.548915241699394, - "Leeway": 1.708368043357703, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 15.17861745882525, - "Heel": 30.956095729410126, - "Leeway": 1.467310536334407, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 15.429272553419798, - "Heel": 60.565186850556195, - "Leeway": 2.6738735836075196, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 15.124984042527274, - "Heel": 24.632783965825567, - "Leeway": 1.558107498171498, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.292432175244837, - "Heel": 23.82977526990202, - "Leeway": 1.2412225203551095, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 15.883654654547307, - "Heel": 58.63183032211455, - "Leeway": 2.366538926719526, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 15.103006440828475, - "Heel": 21.226088448387188, - "Leeway": 1.4284291918280085, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.203649750972044, - "Heel": 17.559387462958096, - "Leeway": 1.1684458117946777, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 16.22402668897068, - "Heel": 56.08345607321081, - "Leeway": 2.0928523504027696, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.953956584431317, - "Heel": 18.132955078549827, - "Leeway": 1.3092457644116304, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.19225491235143, - "Heel": 13.06649871330453, - "Leeway": 1.0935216823954925, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 16.448435642033132, - "Heel": 52.79331071815703, - "Leeway": 1.845801892908742, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.467884583495794, - "Heel": 14.841290358218375, - "Leeway": 1.2065250917062755, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.344787378146284, - "Heel": 9.556385225479005, - "Leeway": 1.0040058791053312, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 16.486568428607644, - "Heel": 48.197024716711624, - "Leeway": 1.6171639057978457, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.852586493910062, - "Heel": 11.515035698965635, - "Leeway": 1.100515775323125, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.665683622117259, - "Heel": 7.265192395214114, - "Leeway": 0.9028679602160647, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 16.2899996270211, - "Heel": 41.78111039960927, - "Leeway": 1.4064217698365789, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.176259920344046, - "Heel": 8.635737259628922, - "Leeway": 0.98116215132177, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.106301645900675, - "Heel": 5.915664959589164, - "Leeway": 0.8081718147870166, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 15.77348323821098, - "Heel": 32.656778519015546, - "Leeway": 1.2131379144694767, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.422594314432802, - "Heel": 6.38387207948933, - "Leeway": 0.8451055767067744, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.582432769322605, - "Heel": 5.231652762246088, - "Leeway": 0.7685625296245258, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.814440629756431, - "Heel": 22.791393766564642, - "Leeway": 0.961042797568732, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.720056124236233, - "Heel": 4.991862116447968, - "Leeway": 0.7066439308562131, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.69936082194503, - "Heel": 14.846903783888619, - "Leeway": 0.8124520567695382, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.030767608302888, - "Heel": 3.996674708263733, - "Leeway": 0.6001078226991443, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.69608977600777, - "Heel": 9.284214496521262, - "Leeway": 0.666989748971318, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.440718513297325, - "Heel": 3.166326535963399, - "Leeway": 0.49841302608210003, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.745351887242037, - "Heel": 6.029376047701313, - "Leeway": 0.527090933443266, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.976532359376037, - "Heel": 2.4319869939709133, - "Leeway": 0.4042612441622961, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.9561462909523, - "Heel": 4.323169433308247, - "Leeway": 0.3967477276833254, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.536333796818582, - "Heel": 1.7965239506915118, - "Leeway": 0.3189047247514961, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.25192525604682, - "Heel": 2.998695703702666, - "Leeway": 0.28436517225065183, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.15672311402372, - "Heel": 1.3337970193644937, - "Leeway": 0.2504671898602931, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.726056901170702, - "Heel": 2.0245208833608177, - "Leeway": 0.2081412023196355, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.87670133691181, - "Heel": 1.0552162436899553, - "Leeway": 0.20455916996492468, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.375208637326166, - "Heel": 1.5277128923646344, - "Leeway": 0.1674692738091924, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.691319055229805, - "Heel": 0.8549607968863342, - "Leeway": 0.1683611429664411, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.147951450256835, - "Heel": 0.9581513489160066, - "Leeway": 0.10942259523541488, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.551899750930849, - "Heel": 0.5844329123308363, - "Leeway": 0.11756531388328234, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.021097063821479, - "Heel": 0.5777745124671139, - "Leeway": 0.06755957716903173, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.475964327736556, - "Heel": 0.38189775442110024, - "Leeway": 0.07760156134117853, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.99061181174512, - "Heel": 0.2820659409696527, - "Leeway": 0.03322580184312882, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.462949042488907, - "Heel": 0.206845305151321, - "Leeway": 0.04201960374131932, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 8.219063724417644, - "Heel": 53.261752287294094, - "Leeway": 8.513932144983206, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.303911411076383, - "Heel": 55.14032211153173, - "Leeway": 7.024600578450865, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.461473907068461, - "Heel": 56.79973630432835, - "Leeway": 5.846451397286192, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.59782343624989, - "Heel": 57.92809443196576, - "Leeway": 4.932459390656114, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.708366555942424, - "Heel": 58.532483604184236, - "Leeway": 4.19253391423398, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.726267764275713, - "Heel": 58.73288692754955, - "Leeway": 3.6214541238554268, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.691917912720216, - "Heel": 58.62779915000213, - "Leeway": 3.154699895702523, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 15.527305367339434, - "Heel": 58.072677174204905, - "Leeway": 2.7791649490890604, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.280794942974577, - "Heel": 66.37227664674995, - "Leeway": 6.186775237596669, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.901108682095062, - "Heel": 41.48466157476745, - "Leeway": 3.2798552267516192, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 16.2213858377445, - "Heel": 57.03505323701554, - "Leeway": 2.469254867981764, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.480556355330357, - "Heel": 67.34493119983473, - "Leeway": 5.252976385438944, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.058057267401256, - "Heel": 42.2811547304713, - "Leeway": 2.8595186524790566, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 16.7752842792981, - "Heel": 55.489068055753755, - "Leeway": 2.2067607101539117, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.623038458436698, - "Heel": 67.94443834499391, - "Leeway": 4.517384188900604, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 15.046338525077177, - "Heel": 42.0701576107796, - "Leeway": 2.51039159635549, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 17.17859164191438, - "Heel": 53.37158480008424, - "Leeway": 1.9815671079163313, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.724673954828946, - "Heel": 68.21444700272669, - "Leeway": 3.9099658927308796, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 15.908227287236528, - "Heel": 41.22077576724295, - "Leeway": 2.22472971321126, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 17.460098406764473, - "Heel": 50.64107362533476, - "Leeway": 1.7801222004064958, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 15.707293330969131, - "Heel": 68.1177394269457, - "Leeway": 3.4224529893031232, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 16.631790201626135, - "Heel": 39.69065774708999, - "Leeway": 1.985538246060042, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 17.616993187041846, - "Heel": 47.175472181554774, - "Leeway": 1.5969787161660605, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 16.53186696672173, - "Heel": 67.56223960566592, - "Leeway": 3.0202853745808613, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 17.202926909202905, - "Heel": 37.406909573777476, - "Leeway": 1.7816111796388214, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 17.51561224126757, - "Heel": 42.12958201110828, - "Leeway": 1.4254577399289359, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 17.15447676600829, - "Heel": 66.33910259187303, - "Leeway": 2.671618415539329, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 17.501838567924242, - "Heel": 33.82979805971086, - "Leeway": 1.6054404037238545, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 16.967551729262723, - "Heel": 34.47668001625214, - "Leeway": 1.2725548676411906, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 17.648002576013173, - "Heel": 64.65353493235565, - "Leeway": 2.3666273970435943, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 17.55705352335999, - "Heel": 29.268713499097814, - "Leeway": 1.3336255956046557, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 15.788809382931852, - "Heel": 25.411740336266686, - "Leeway": 1.0688365933399062, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 18.017706745432573, - "Heel": 62.43006615209185, - "Leeway": 2.094197425738012, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 17.360753244190924, - "Heel": 25.033334353851174, - "Leeway": 1.2168634366803768, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.23692897056174, - "Heel": 17.814782065957743, - "Leeway": 1.0207340361728656, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 18.267972595493408, - "Heel": 59.55973415100371, - "Leeway": 1.8468332698104473, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 16.901216915419457, - "Heel": 20.003253890128416, - "Leeway": 1.1199258194801978, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.002984325143105, - "Heel": 12.963570000585065, - "Leeway": 0.9573775896849462, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 18.336295808568437, - "Heel": 55.5665647157808, - "Leeway": 1.6155380153412817, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 16.171276493554064, - "Heel": 15.958281649587642, - "Leeway": 1.0293597222677002, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.001902189373354, - "Heel": 9.393769582022893, - "Leeway": 0.8822496856899287, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 18.2055683229175, - "Heel": 50.05945110117748, - "Leeway": 1.3981302650991194, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 15.277591720196344, - "Heel": 11.853496807611018, - "Leeway": 0.9286464594497125, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.176457930026514, - "Heel": 7.213566866051803, - "Leeway": 0.8116629844359168, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 17.873910208514314, - "Heel": 42.58279927463667, - "Leeway": 1.1940106087197502, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.287671738599206, - "Heel": 8.370079877707184, - "Leeway": 0.8140875071929897, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.446810430705497, - "Heel": 6.233621263533679, - "Leeway": 0.7970232218598119, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 17.136209668939415, - "Heel": 31.624014642854192, - "Leeway": 1.0064408110281835, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.358234260960073, - "Heel": 6.043840655136547, - "Leeway": 0.6882062329742216, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 15.823745468145155, - "Heel": 19.96409502755517, - "Leeway": 0.7746349814804012, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.464500280748346, - "Heel": 4.730783818081382, - "Leeway": 0.5859400437675908, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 14.537334792148782, - "Heel": 12.616096895753854, - "Leeway": 0.6394948794697088, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.652808052924849, - "Heel": 3.760760302636879, - "Leeway": 0.4946319656503032, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 13.336489467212006, - "Heel": 7.592284589101475, - "Leeway": 0.5128114967323828, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.019486647803065, - "Heel": 2.963936836030265, - "Leeway": 0.40809680924205055, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 12.32994451884244, - "Heel": 5.109596237480674, - "Leeway": 0.38975495213777966, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.43312491844818, - "Heel": 2.215034174196007, - "Leeway": 0.3279555435739832, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 11.390810052716013, - "Heel": 3.572216521633928, - "Leeway": 0.2841220324612129, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.954191481293947, - "Heel": 1.6618117857470502, - "Leeway": 0.26338631276703367, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.704285617290655, - "Heel": 2.5098905135822323, - "Leeway": 0.2129063896834985, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.612969261095785, - "Heel": 1.3339777178173713, - "Leeway": 0.21993040866186822, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 10.264440119133189, - "Heel": 1.9090691529496346, - "Leeway": 0.17449868282379544, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.38000775399891, - "Heel": 1.0772770346580314, - "Leeway": 0.18196530724830587, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.982391497754396, - "Heel": 1.2040924082225108, - "Leeway": 0.1154489607587883, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.209306803870971, - "Heel": 0.7448257448281856, - "Leeway": 0.1291346290936031, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.829461104586764, - "Heel": 0.7331215607017073, - "Leeway": 0.07220022831389432, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.117896155161867, - "Heel": 0.49177268689221515, - "Leeway": 0.08633861784743259, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 0.0, - "Heel": 0.0, - "Leeway": 0.0, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.793397441007038, - "Heel": 0.3620091596851739, - "Leeway": 0.03593797724132038, - "flat": 0.0, - "RED": 0.0 - }, - { - "Speed": 9.1031457777938, - "Heel": 0.27003383879925746, - "Leeway": 0.04741145565671415, - "flat": 0.0, - "RED": 0.0 - } - ] +{ + "name": "YD41", + "tws": [ + 2.0576, + 2.572 + ], + "twa": [ + 30.0, + 105.0, + 180.0 + ], + "sails": [ + "MN1 + J1" + ], + "results": [ + [ + [ + [ + 1.816412740656615, + 2.2044396971742675, + 7.509065782798743, + 1.0, + 2.0 + ] + ], + [ + [ + 3.5263996638125867, + 1.1558681664558554, + 1.0721813979413097, + 1.0, + 2.0 + ] + ], + [ + [ + 1.8833235011944287, + 0.022888316944774098, + 0.07370079761864409, + 1.0, + 2.0 + ] + ] + ], + [ + [ + [ + 2.342078520472195, + 3.3802125924635242, + 7.1674316647547025, + 1.0, + 2.0 + ] + ], + [ + [ + 4.3612107597522085, + 1.7784961342019951, + 1.0786851875849572, + 1.0, + 2.0 + ] + ], + [ + [ + 2.3743006297439773, + 0.03522045290408138, + 0.07135617698165371, + 1.0, + 2.0 + ] + ] + ] + ] } \ No newline at end of file diff --git a/results_daring.json b/results_daring.json new file mode 100644 index 0000000..1442ba7 --- /dev/null +++ b/results_daring.json @@ -0,0 +1,4535 @@ +{ + "name": "Daring", + "tws": [ + 2.0576, + 3.0864, + 4.1152, + 5.144, + 6.1728, + 7.201599999999999, + 8.2304, + 9.2592, + 10.288 + ], + "twa": [ + 30.0, + 35.0, + 40.0, + 45.0, + 50.0, + 55.0, + 60.0, + 65.0, + 70.0, + 75.0, + 80.0, + 85.0, + 90.0, + 95.0, + 100.0, + 105.0, + 110.0, + 115.0, + 120.0, + 125.0, + 130.0, + 135.0, + 140.0, + 145.0, + 150.0, + 155.0, + 160.0, + 165.0, + 170.0, + 175.0, + 180.0 + ], + "sails": [ + "MN1 + J1", + "MN1 + S1" + ], + "results": [ + [ + [ + [ + 2.472212233562214, + 4.357701630971166, + 2.965334828010287, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 2.9780191496262343, + 4.79703978029764, + 2.3829140802157407, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 3.3346665565196925, + 5.033564462317318, + 2.0436407138804795, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 3.596041360667905, + 5.167644847084835, + 1.8267879759469587, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 3.8151648540135175, + 5.242505169785924, + 1.65717224215444, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 4.002680035317863, + 5.265159536411269, + 1.5146445035642957, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 4.104788630044621, + 5.1578422692017405, + 1.3982354717228689, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 4.186705709396669, + 5.007773252952089, + 1.2874482583066527, + 1.0, + 2.0 + ], + [ + 3.2070155188565126, + 5.193985640147673, + 2.1339124615435265, + 1.0, + 2.0 + ] + ], + [ + [ + 4.234264653196276, + 4.817233059702548, + 1.1880942958336211, + 1.0, + 2.0 + ], + [ + 3.473450348290438, + 5.435859541775321, + 1.959980038214978, + 1.0, + 2.0 + ] + ], + [ + [ + 4.2486041077206345, + 4.59149780793298, + 1.097394967763363, + 1.0, + 2.0 + ], + [ + 3.711842239535394, + 5.62826847591293, + 1.8150267779528104, + 1.0, + 2.0 + ] + ], + [ + [ + 4.231824128861469, + 4.335314785615916, + 1.0131232695892332, + 1.0, + 2.0 + ], + [ + 3.9237091182175043, + 5.76347107411692, + 1.6881028715730932, + 1.0, + 2.0 + ] + ], + [ + [ + 4.186459582401538, + 4.052040899527969, + 0.9336037093529985, + 1.0, + 2.0 + ], + [ + 4.067085644962787, + 5.735512155824824, + 1.568013304142798, + 1.0, + 2.0 + ] + ], + [ + [ + 4.113141156444386, + 3.74244857663881, + 0.8584072623578571, + 1.0, + 2.0 + ], + [ + 4.165387917562274, + 5.5900315136771574, + 1.4484492616523277, + 1.0, + 2.0 + ] + ], + [ + [ + 4.035991691275364, + 3.418690047675408, + 0.7823535993074893, + 1.0, + 2.0 + ], + [ + 4.23887381350724, + 5.397062489085952, + 1.3359617351246378, + 1.0, + 2.0 + ] + ], + [ + [ + 3.930157264109526, + 3.047638733032613, + 0.7068128970409417, + 1.0, + 2.0 + ], + [ + 4.283378778164139, + 5.160960368784266, + 1.2304749497328917, + 1.0, + 2.0 + ] + ], + [ + [ + 3.7807087462428535, + 2.574656957557351, + 0.6262567093160101, + 1.0, + 2.0 + ], + [ + 4.29881764110746, + 4.887739459085875, + 1.1302941273827347, + 1.0, + 2.0 + ] + ], + [ + [ + 3.6251334044833157, + 2.072538415871582, + 0.5472315453019471, + 1.0, + 2.0 + ], + [ + 4.25858192519077, + 4.529554698942863, + 1.0276581422790079, + 1.0, + 2.0 + ] + ], + [ + [ + 3.428118216524445, + 1.6160231699060164, + 0.4771539604504666, + 1.0, + 2.0 + ], + [ + 4.190177754972688, + 4.1436143530033664, + 0.9272687871006743, + 1.0, + 2.0 + ] + ], + [ + [ + 3.2377883515849093, + 1.2416799844078377, + 0.4107519388722305, + 1.0, + 2.0 + ], + [ + 4.098645979005097, + 3.7317371772180796, + 0.8287233807449167, + 1.0, + 2.0 + ] + ], + [ + [ + 2.9972271081244894, + 0.9180418846904888, + 0.3530354095677814, + 1.0, + 2.0 + ], + [ + 3.972103522767845, + 3.2455777359660414, + 0.7246623801401841, + 1.0, + 2.0 + ] + ], + [ + [ + 2.778488965484209, + 0.677592089687949, + 0.30062774369487455, + 1.0, + 2.0 + ], + [ + 3.8082905205066897, + 2.661321109481325, + 0.6196748098742962, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 3.62549733027161, + 1.9794117581239472, + 0.5092988795561267, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 3.4124829534697323, + 1.3603560616514985, + 0.39615258994340025, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 3.2041533943337086, + 0.8882795905474848, + 0.2942701355647829, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 2.9565120448411344, + 0.5503971070752293, + 0.2147048870083592, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 2.7365384624568936, + 0.33048780217283075, + 0.14904735716433268, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 2.527907361141538, + 0.1929825622978153, + 0.09979525226010401, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 2.379850517505114, + 0.13054415083074905, + 0.0743429387568976, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 2.2867075474186107, + 0.08698308488361152, + 0.05279012828919885, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 2.2348483766552496, + 0.05127725842986029, + 0.032306572424378184, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 2.222092053954653, + 0.02484562572848747, + 0.015810438500693236, + 1.0, + 2.0 + ] + ] + ], + [ + [ + [ + 3.5876560903413046, + 8.519662469669985, + 3.0816342620666637, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 4.168341274477843, + 9.896090973814864, + 2.5589659509051406, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 4.642176305058408, + 10.809646917029232, + 2.1949165887068918, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 4.978489387882292, + 11.276382650308994, + 1.9659393485533425, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 5.2270491626327455, + 11.410741397337139, + 1.7981100639449947, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 5.3592676314119885, + 11.03458865222046, + 1.6705414778018732, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 5.442082449130005, + 10.355228856702311, + 1.550957274953096, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 5.499764089496291, + 9.597168463247034, + 1.4391305351904018, + 1.0, + 2.0 + ], + [ + 4.604418891904273, + 12.444878811306374, + 2.289948977661449, + 1.0, + 2.0 + ] + ], + [ + [ + 5.534005934264874, + 8.781828455266787, + 1.3332491974426326, + 1.0, + 2.0 + ], + [ + 4.974901946712665, + 13.524848193772707, + 2.1053616389515244, + 1.0, + 2.0 + ] + ], + [ + [ + 5.546075643547471, + 7.8979215676165095, + 1.2320271008442296, + 1.0, + 2.0 + ], + [ + 5.270598144585622, + 14.34224464238153, + 1.9773186805675826, + 1.0, + 2.0 + ] + ], + [ + [ + 5.536379710706865, + 7.030537243288925, + 1.1346977741280846, + 1.0, + 2.0 + ], + [ + 5.448582646594145, + 14.384053457674842, + 1.8646632735868451, + 1.0, + 2.0 + ] + ], + [ + [ + 5.506244691617721, + 6.372608433082528, + 1.040370508476409, + 1.0, + 2.0 + ], + [ + 5.571416837016484, + 13.901629014664474, + 1.7444748668424237, + 1.0, + 2.0 + ] + ], + [ + [ + 5.460625690360339, + 5.81640815559043, + 0.9474317580167629, + 1.0, + 2.0 + ], + [ + 5.6590400283600735, + 13.191428522542349, + 1.6319706031735886, + 1.0, + 2.0 + ] + ], + [ + [ + 5.395449545048309, + 5.295477602524067, + 0.8531581363229972, + 1.0, + 2.0 + ], + [ + 5.702338568314789, + 12.254777499429562, + 1.5285717531389866, + 1.0, + 2.0 + ] + ], + [ + [ + 5.2902157951545465, + 4.7587083452352905, + 0.7552948932176712, + 1.0, + 2.0 + ], + [ + 5.720460168209033, + 11.015773597177189, + 1.4114045778251154, + 1.0, + 2.0 + ] + ], + [ + [ + 5.1492215614532535, + 4.230276056700119, + 0.663081465200912, + 1.0, + 2.0 + ], + [ + 5.717807718341306, + 9.626731051776977, + 1.2870759537273029, + 1.0, + 2.0 + ] + ], + [ + [ + 4.965449401582805, + 3.696919411337831, + 0.5794578286985458, + 1.0, + 2.0 + ], + [ + 5.700995374246594, + 8.267324589940035, + 1.1619499749121298, + 1.0, + 2.0 + ] + ], + [ + [ + 4.7302187239529925, + 3.136763760757344, + 0.5054005100482473, + 1.0, + 2.0 + ], + [ + 5.670283883463245, + 6.966750546928033, + 1.0365193069888117, + 1.0, + 2.0 + ] + ], + [ + [ + 4.482117001830747, + 2.5299918589663397, + 0.43607252874332303, + 1.0, + 2.0 + ], + [ + 5.581463391365079, + 5.970333251139391, + 0.9056105679650929, + 1.0, + 2.0 + ] + ], + [ + [ + 4.193188129761479, + 1.91458279970711, + 0.3749799889447306, + 1.0, + 2.0 + ], + [ + 5.452525750234145, + 5.166342586433442, + 0.7798948219750456, + 1.0, + 2.0 + ] + ], + [ + [ + 3.9452627750677034, + 1.4545429415929927, + 0.3188345287885123, + 1.0, + 2.0 + ], + [ + 5.293010383441239, + 4.405565910251434, + 0.6507360859941534, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 5.078380333820675, + 3.6230511525648965, + 0.5248704281815658, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 4.833728222336874, + 2.7692688250179156, + 0.4054840071089598, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 4.548798888431061, + 1.8569991412648967, + 0.30587756924957293, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 4.23790836740014, + 1.176939288455019, + 0.22311991611953833, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 3.962696018132823, + 0.7248213157760925, + 0.15539895274562585, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 3.70444895775235, + 0.4343285900751505, + 0.10433694763736556, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 3.522256235912178, + 0.29880330574601915, + 0.07760442369918855, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 3.405355410380242, + 0.19733488395009047, + 0.05399219743912948, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 3.3396141496759246, + 0.11614468513005277, + 0.032768595175064565, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 3.323397632116004, + 0.05631182716283975, + 0.016019626087627567, + 1.0, + 2.0 + ] + ] + ], + [ + [ + [ + 4.5869796661749405, + 17.16341651633031, + 3.186939606356241, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 5.2064098305918485, + 18.977120710945062, + 2.6957284621450057, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 5.573966687745255, + 19.61971855512889, + 2.4184333645843488, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 5.769234697176891, + 19.60027710751967, + 2.254673840378104, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 5.885639048909981, + 19.066556197768964, + 2.116381952152515, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 5.967004056695903, + 18.113762134508974, + 1.9749056728253491, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 6.030719047691275, + 17.026894866157143, + 1.8403000049379734, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 6.060479539093575, + 15.773600723306558, + 1.7168513682543087, + 1.0, + 2.0 + ], + [ + 5.692279960569571, + 24.97925779109798, + 2.5767692661209693, + 1.0, + 2.0 + ] + ], + [ + [ + 6.074799065349463, + 14.4243617589753, + 1.5966110558557978, + 1.0, + 2.0 + ], + [ + 5.934370354812707, + 26.889012137814863, + 2.5427717967387835, + 1.0, + 2.0 + ] + ], + [ + [ + 6.080609022337964, + 13.023788977391558, + 1.4781642156281605, + 1.0, + 2.0 + ], + [ + 6.067905426934647, + 26.656318855441725, + 2.4272670554282896, + 1.0, + 2.0 + ] + ], + [ + [ + 6.079591210332731, + 11.581554132734633, + 1.359923731928839, + 1.0, + 2.0 + ], + [ + 6.141180786821569, + 25.898326337833772, + 2.327118741243568, + 1.0, + 2.0 + ] + ], + [ + [ + 6.07302197879634, + 10.105686495789936, + 1.2405445080534923, + 1.0, + 2.0 + ], + [ + 6.2073580612332035, + 24.804864529467775, + 2.213600413769272, + 1.0, + 2.0 + ] + ], + [ + [ + 6.051502809459069, + 8.59544529139985, + 1.1084041686170558, + 1.0, + 2.0 + ], + [ + 6.253124917567881, + 23.019195572043284, + 2.0722826079413976, + 1.0, + 2.0 + ] + ], + [ + [ + 5.998025109318056, + 7.121560702160357, + 0.9825071189366615, + 1.0, + 2.0 + ], + [ + 6.282782447214338, + 20.77763114725507, + 1.9201958820876346, + 1.0, + 2.0 + ] + ], + [ + [ + 5.926888212331862, + 6.147006907720367, + 0.8617814039631754, + 1.0, + 2.0 + ], + [ + 6.300995682213089, + 18.701354948533865, + 1.7651087312960907, + 1.0, + 2.0 + ] + ], + [ + [ + 5.8325163913823745, + 5.422744722615686, + 0.7536510649728062, + 1.0, + 2.0 + ], + [ + 6.2997353877193625, + 16.53343847371258, + 1.601510845466842, + 1.0, + 2.0 + ] + ], + [ + [ + 5.732379279075022, + 4.8042526291334156, + 0.6515821981130503, + 1.0, + 2.0 + ], + [ + 6.275581856986143, + 14.098257927426202, + 1.4295436913351, + 1.0, + 2.0 + ] + ], + [ + [ + 5.619692294538493, + 4.235582533418343, + 0.5567736408885154, + 1.0, + 2.0 + ], + [ + 6.2384944452856415, + 11.584406523688171, + 1.254303159159967, + 1.0, + 2.0 + ] + ], + [ + [ + 5.426610029199248, + 3.657130616485573, + 0.4750776891910606, + 1.0, + 2.0 + ], + [ + 6.187741766784795, + 9.109366609691975, + 1.074834876957268, + 1.0, + 2.0 + ] + ], + [ + [ + 5.230700482790088, + 3.089619913374437, + 0.4008116513737362, + 1.0, + 2.0 + ], + [ + 6.126426659523032, + 6.940113194490622, + 0.8972363695476487, + 1.0, + 2.0 + ] + ], + [ + [ + 4.990220983808674, + 2.5135642131994596, + 0.3435625933348113, + 1.0, + 2.0 + ], + [ + 6.054986725152651, + 5.6475607120799785, + 0.7231385797757833, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 5.921438162658925, + 4.657225696513184, + 0.5696515045331767, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 5.773135632110032, + 3.797699504886536, + 0.4380971435491611, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 5.607275835093086, + 2.952361496671121, + 0.32709669181952233, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 5.352054708998154, + 1.98977307674075, + 0.23595102356129546, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 5.078133868022427, + 1.2493346857860372, + 0.16251388951289733, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 4.801399764473167, + 0.7751894898507294, + 0.11054454670350301, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 4.580854965448739, + 0.5487140821240908, + 0.08410793731696181, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 4.437946411848284, + 0.35998633505308536, + 0.05795851705271573, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 4.347521072164238, + 0.21513876218129074, + 0.0358116572985944, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 4.325490621769256, + 0.10612818705728673, + 0.017822828647974104, + 1.0, + 2.0 + ] + ] + ], + [ + [ + [ + 5.347308909914345, + 28.205491782208185, + 3.448014296923543, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 5.77532421974409, + 29.5656004372724, + 3.084585418656892, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 6.024631221693601, + 29.826123946939532, + 2.856064122007594, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 6.140128485447153, + 29.324089763998433, + 2.706193551618517, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 6.2108315887165855, + 27.915948318266203, + 2.5360178096312986, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 6.270131841667135, + 26.30152353445154, + 2.371514659102615, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 6.317422864777664, + 24.487893903772157, + 2.21341351255079, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 6.353459252854009, + 22.481910180756177, + 2.0607724457321654, + 1.0, + 2.0 + ], + [ + 4.532617699998028, + 34.99999999999999, + 5.99999999999985, + 0.95, + 2.0 + ] + ], + [ + [ + 6.3769133623097565, + 20.308151987932753, + 1.9150703440013854, + 1.0, + 2.0 + ], + [ + 4.6376598830552584, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ] + ], + [ + [ + 6.391073592517851, + 18.47509035858302, + 1.769284996437674, + 1.0, + 2.0 + ], + [ + 5.718473589887008, + 34.99999999999999, + 3.0509783150960974, + 0.95, + 2.0 + ] + ], + [ + [ + 6.39490493460154, + 16.585442296707914, + 1.6206503566241914, + 1.0, + 2.0 + ], + [ + 6.133849547838713, + 34.99999999999999, + 2.68959962499696, + 0.95, + 2.0 + ] + ], + [ + [ + 6.3748483105921805, + 14.410180380837705, + 1.4585788983447951, + 1.0, + 2.0 + ], + [ + 6.419141764903361, + 34.99999999999999, + 2.6381933585168285, + 0.95, + 2.0 + ] + ], + [ + [ + 6.34454094720308, + 12.25179663961996, + 1.301879531714992, + 1.0, + 2.0 + ], + [ + 6.625979159503756, + 34.33574341331469, + 2.6223979791817094, + 0.95, + 2.0 + ] + ], + [ + [ + 6.300558783618725, + 10.161216476839876, + 1.1541052785113528, + 1.0, + 2.0 + ], + [ + 6.71041588675436, + 32.56111427503765, + 2.523715908414021, + 1.0, + 2.0 + ] + ], + [ + [ + 6.242768694331587, + 8.3488252141894, + 1.0178626404776843, + 1.0, + 2.0 + ], + [ + 6.729608311952861, + 28.334114222197677, + 2.136948881237324, + 1.0, + 2.0 + ] + ], + [ + [ + 6.181883191774688, + 6.818621686491396, + 0.8877040418533745, + 1.0, + 2.0 + ], + [ + 6.723780084483098, + 24.63665932158212, + 1.9175937096717468, + 1.0, + 2.0 + ] + ], + [ + [ + 6.1193779517905895, + 5.882331299052025, + 0.763540796013426, + 1.0, + 2.0 + ], + [ + 6.6963693228452525, + 20.274146951404326, + 1.6913718522921064, + 1.0, + 2.0 + ] + ], + [ + [ + 6.056177020817247, + 5.164335684290366, + 0.6478751612139401, + 1.0, + 2.0 + ], + [ + 6.657417598470571, + 16.837369512537293, + 1.4727120277079195, + 1.0, + 2.0 + ] + ], + [ + [ + 5.940506582742824, + 4.529404826616626, + 0.5483977968537272, + 1.0, + 2.0 + ], + [ + 6.5980884883533655, + 13.252052035018291, + 1.2455125454683609, + 1.0, + 2.0 + ] + ], + [ + [ + 5.819242210886438, + 3.959979310852699, + 0.460243501040527, + 1.0, + 2.0 + ], + [ + 6.531284267621993, + 9.902920498596451, + 1.0311849100387218, + 1.0, + 2.0 + ] + ], + [ + [ + 5.679803024356932, + 3.5369621644606815, + 0.4058504835298163, + 1.0, + 2.0 + ], + [ + 6.459668294875995, + 7.140002785707407, + 0.8302443154706941, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.353973764781485, + 5.65699841492194, + 0.6615907782142296, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.243021098299266, + 4.628807610957504, + 0.5100425545249896, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.13141723973765, + 3.731845757653707, + 0.37778171020609314, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.014537620961765, + 2.846325006551932, + 0.26904839520823454, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 5.823466965069821, + 1.871827818625806, + 0.1837819280511818, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 5.658298038374999, + 1.2340014433550845, + 0.12605436146323526, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 5.4748962264594425, + 0.9098483159662104, + 0.09736396795878526, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 5.344992584418188, + 0.5859865152233791, + 0.06498589370452808, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 5.2680180291642005, + 0.35367865106296537, + 0.04008967350769791, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 5.249609847292107, + 0.17737399592047892, + 0.02022327421709434, + 1.0, + 2.0 + ] + ] + ], + [ + [ + [ + 4.620986964827726, + 34.99999999999999, + 5.813406456299289, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 4.733920674110355, + 34.99999999999999, + 5.7280127544139345, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 4.972776186136128, + 34.99999999999999, + 5.323740230516414, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 5.752457991672758, + 34.99999999999999, + 3.4065738328446384, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 6.189314296634297, + 34.99999999999999, + 2.9207961495022308, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 6.4701238690019, + 34.99999999999999, + 2.8093631009495525, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 6.573126050148125, + 34.09118245489979, + 2.801981806310012, + 1.0, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 6.616784048130016, + 30.77762737298979, + 2.59534564609808, + 1.0, + 2.0 + ], + [ + 2.8236150682404473, + 34.99999999999999, + 5.999999999999971, + 0.95, + 2.0 + ] + ], + [ + [ + 6.653525543212159, + 28.1373107501376, + 2.2483670366713895, + 1.0, + 2.0 + ], + [ + 3.032659495083677, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ] + ], + [ + [ + 6.673721075861969, + 25.509842569441666, + 2.066633342701243, + 1.0, + 2.0 + ], + [ + 3.2727736512266823, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ] + ], + [ + [ + 6.668795222974017, + 22.231073297299456, + 1.8684834066537173, + 1.0, + 2.0 + ], + [ + 3.6770118732112147, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ] + ], + [ + [ + 6.65216034869415, + 19.09055886984642, + 1.6796045017644297, + 1.0, + 2.0 + ], + [ + 4.1280279347534385, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ] + ], + [ + [ + 6.621862014841449, + 16.49291594577487, + 1.5006999790322078, + 1.0, + 2.0 + ], + [ + 5.688227634893061, + 34.99999999999999, + 2.553156081004032, + 0.95, + 2.0 + ] + ], + [ + [ + 6.5748536424685184, + 13.99844052117558, + 1.338815430290971, + 1.0, + 2.0 + ], + [ + 6.521987267067732, + 34.99999999999999, + 1.993845457213657, + 0.95, + 2.0 + ] + ], + [ + [ + 6.52346572829823, + 11.621928537714851, + 1.1838305024496376, + 1.0, + 2.0 + ], + [ + 6.862827622315542, + 34.99999999999999, + 2.192453217872927, + 0.95, + 2.0 + ] + ], + [ + [ + 6.469196821206741, + 9.43726379393885, + 1.0348515081599048, + 1.0, + 2.0 + ], + [ + 7.105595267749136, + 34.95637398514996, + 2.3884363612670967, + 1.0, + 2.0 + ] + ], + [ + [ + 6.3984796153289505, + 7.529453303621991, + 0.8959212806760156, + 1.0, + 2.0 + ], + [ + 7.097026833528061, + 28.506551307397004, + 1.9506922016549377, + 1.0, + 2.0 + ] + ], + [ + [ + 6.3182948893266, + 6.259105236893662, + 0.767573840873478, + 1.0, + 2.0 + ], + [ + 7.046932686946634, + 22.928567487500118, + 1.667597941906931, + 1.0, + 2.0 + ] + ], + [ + [ + 6.239032606296693, + 5.442361001770649, + 0.6511408474117617, + 1.0, + 2.0 + ], + [ + 6.985836667532904, + 17.906960454249457, + 1.410570631111705, + 1.0, + 2.0 + ] + ], + [ + [ + 6.149698639194782, + 4.898243087950787, + 0.5712909622215354, + 1.0, + 2.0 + ], + [ + 6.916928866453862, + 13.759337471583105, + 1.168765859142968, + 1.0, + 2.0 + ] + ], + [ + [ + 6.068044316669805, + 4.4966385094134536, + 0.5114224192069198, + 1.0, + 2.0 + ], + [ + 6.83102397063759, + 10.121455993444917, + 0.9617250973642033, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.725644639197161, + 7.1606702744911415, + 0.7711212117961683, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.612912724417275, + 5.563222758858326, + 0.5960651800654008, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.509290870733909, + 4.504280153929615, + 0.4462433407129085, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.381087804671422, + 3.554202456450043, + 0.31946323195017906, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.244863309455395, + 2.642537449017721, + 0.22421413182675987, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.133914322459789, + 1.8764106972443684, + 0.16191814168356367, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.053914339140772, + 1.4314538657102984, + 0.12481321745228757, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 5.961480342208545, + 0.9107985297629355, + 0.08108721926953716, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 5.905759400012354, + 0.5640153755770763, + 0.050854985744716946, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 5.893016118495884, + 0.2931447500250473, + 0.0265229061645235, + 1.0, + 2.0 + ] + ] + ], + [ + [ + [ + 3.5851962720416988, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 3.6493328062360684, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 3.837432606523983, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 4.071611307703263, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 4.3434111295866265, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 4.697373021239107, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 6.135857552534484, + 34.99999999999999, + 2.554262022672806, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 6.518926038475352, + 34.99999999999999, + 2.4307289855182423, + 0.95, + 2.0 + ], + [ + 1.3284440910607454e-09, + 34.999999999999815, + 5.999999761261559, + 0.95, + 2.0 + ] + ], + [ + [ + 6.789138262364423, + 34.99999999999999, + 2.4893124050569146, + 0.95, + 2.0 + ], + [ + 2.0634823618386913e-11, + 34.99999999999999, + 5.99999999999967, + 0.95, + 2.0 + ] + ], + [ + [ + 6.935020639566489, + 33.92951339412119, + 2.528415215843498, + 1.0, + 2.0 + ], + [ + 1.2854194729018726e-09, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ] + ], + [ + [ + 6.944940490523638, + 29.04060542219067, + 2.1250646288779076, + 1.0, + 2.0 + ], + [ + 1.8989384839175527, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ] + ], + [ + [ + 6.9308780652713216, + 25.287474287275906, + 1.8980717777681244, + 1.0, + 2.0 + ], + [ + 2.684018209473253, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ] + ], + [ + [ + 6.893060789386977, + 21.47511786698241, + 1.7048793351667304, + 1.0, + 2.0 + ], + [ + 3.3758504442731816, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ] + ], + [ + [ + 6.847372351124305, + 18.23212614868924, + 1.5236210147227072, + 1.0, + 2.0 + ], + [ + 4.12805621126159, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ] + ], + [ + [ + 6.784959279974327, + 15.421065811151486, + 1.3534596513331083, + 1.0, + 2.0 + ], + [ + 6.559619924670435, + 34.99999999999999, + 1.3462168486202115, + 0.95, + 2.0 + ] + ], + [ + [ + 6.718900877480073, + 12.750445277302779, + 1.1902348941001915, + 1.0, + 2.0 + ], + [ + 6.9882675897554245, + 34.99999999999999, + 1.7006067564559497, + 0.95, + 2.0 + ] + ], + [ + [ + 6.650224556107586, + 10.285359088027558, + 1.0363776747884759, + 1.0, + 2.0 + ], + [ + 7.334811392711036, + 34.99999999999999, + 2.0711100310143924, + 0.95, + 2.0 + ] + ], + [ + [ + 6.580673603103836, + 8.230035966667982, + 0.8938146942888271, + 1.0, + 2.0 + ], + [ + 7.47113210472132, + 29.999998987311244, + 1.8466727283117073, + 1.0, + 2.0 + ] + ], + [ + [ + 6.5037648515217885, + 6.773529418570534, + 0.7816990714829107, + 1.0, + 2.0 + ], + [ + 7.3931418467433225, + 23.91316868593705, + 1.565256103549228, + 1.0, + 2.0 + ] + ], + [ + [ + 6.4144001585886015, + 6.076830793665652, + 0.7084169572477251, + 1.0, + 2.0 + ], + [ + 7.300474311860487, + 18.249047690382135, + 1.3165145072044284, + 1.0, + 2.0 + ] + ], + [ + [ + 6.3354493589241505, + 5.524597367655438, + 0.6402325068302009, + 1.0, + 2.0 + ], + [ + 7.201224535997885, + 13.91582116081802, + 1.0925537496284337, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.083927725525327, + 9.893572995055711, + 0.8796907938748258, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.973401911600959, + 6.876941655555439, + 0.6883123028059783, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.861602723685795, + 5.358381212034764, + 0.5195851102399371, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.717713545050413, + 4.257327556415698, + 0.3773267973262017, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.77117683883833, + -0.171923061312958, + -0.08616800979373662, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.491177681859202, + 2.7138970738087114, + 0.20906026995313606, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.408085178576359, + 2.0576941242272726, + 0.15969762824588005, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.338827543850511, + 1.3542624189723753, + 0.10650368272100634, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.299890659496617, + 0.8635512446560245, + 0.06840615512134769, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.291605653267635, + 0.4670344218856542, + 0.03707128506972327, + 1.0, + 2.0 + ] + ] + ], + [ + [ + [ + 2.2448683055602965, + 34.99999999999999, + 5.999999999999776, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 2.407293894752082, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 2.6992992926358026, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 2.9979525410672236, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 3.3155880723894082, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 3.650759517373106, + 34.99999999999999, + 5.9999999999998685, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 4.016320721770256, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 4.487618678913159, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ], + [ + 1.8938332309140506e-11, + 34.99999999953236, + -0.06947880355822167, + 0.95, + 2.0 + ] + ], + [ + [ + 6.322548989998541, + 34.99999999999999, + 2.140439651462366, + 0.95, + 2.0 + ], + [ + 1.5250709593661897e-10, + 34.99999999972369, + -0.8253797116606799, + 0.95, + 2.0 + ] + ], + [ + [ + 6.782952836121855, + 34.99999999999999, + 2.1297031031059306, + 0.95, + 2.0 + ], + [ + 7.351903506184046e-10, + 34.99999999969831, + -0.5943699224958293, + 0.95, + 2.0 + ] + ], + [ + [ + 7.087181528926234, + 34.99999999999999, + 2.293550653877201, + 0.95, + 2.0 + ], + [ + 9.648183800430986e-11, + 34.99999999968767, + -0.19075377887227174, + 0.95, + 2.0 + ] + ], + [ + [ + 7.184011011667862, + 32.56893632635745, + 2.287672227166166, + 1.0, + 2.0 + ], + [ + 3.3391512900873055e-11, + 34.99999999999999, + 5.999998906200563, + 0.95, + 2.0 + ] + ], + [ + [ + 7.148428560693609, + 27.64658644772641, + 1.9191159135812843, + 1.0, + 2.0 + ], + [ + 1.8099914179136947, + 34.99999999999999, + 5.9999999999999964, + 0.95, + 2.0 + ] + ], + [ + [ + 7.095952206432034, + 23.518666790371658, + 1.713737735787361, + 1.0, + 2.0 + ], + [ + 2.9380740554287343, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ] + ], + [ + [ + 7.038147924704807, + 19.513549957434392, + 1.52397914901334, + 1.0, + 2.0 + ], + [ + 3.9416092320031204, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ] + ], + [ + [ + 6.976289865877241, + 16.469208738335084, + 1.3428027316991042, + 1.0, + 2.0 + ], + [ + 6.774181941388035, + 34.99999999999999, + 0.8433520368414983, + 0.95, + 2.0 + ] + ], + [ + [ + 6.9115463131899, + 13.639354664636901, + 1.1747944616656076, + 1.0, + 2.0 + ], + [ + 7.284632568298553, + 34.99999999999999, + 1.3266043466426893, + 0.95, + 2.0 + ] + ], + [ + [ + 6.844386221576367, + 11.080911226577276, + 1.0210793068839674, + 1.0, + 2.0 + ], + [ + 7.659286020084259, + 34.99999999999999, + 1.7807432287728404, + 0.95, + 2.0 + ] + ], + [ + [ + 6.740749442126578, + 9.405415311772714, + 0.9323615252963213, + 1.0, + 2.0 + ], + [ + 7.858486999629934, + 31.19500434929679, + 1.8231413133731185, + 1.0, + 2.0 + ] + ], + [ + [ + 6.653101089034279, + 8.148517787235921, + 0.8583725435454028, + 1.0, + 2.0 + ], + [ + 7.74813690423529, + 24.16685850720986, + 1.4417737071596008, + 1.0, + 2.0 + ] + ], + [ + [ + 6.597700199226463, + 6.9556834082583885, + 0.7765592236855081, + 1.0, + 2.0 + ], + [ + 7.6203250260113125, + 18.087571282064427, + 1.2017643829588858, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.478316195169176, + 13.342561723011485, + 0.9786231577739583, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.345582803818576, + 9.305272970628009, + 0.7765532857283954, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.195345435905353, + 6.392405001165473, + 0.5919721865505982, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.05135812861059, + 5.0176500104182615, + 0.43940721079662937, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.1534254089481815, + -0.24699827540776143, + -0.0425305199522672, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.823258403465845, + 3.490218865272683, + 0.2614463706039895, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.893807135968526, + -0.004752164519411167, + -0.38635708653487977, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.672748127392669, + 1.8980041641402146, + 0.13457976982198888, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.637847457334122, + 1.2378929567373635, + 0.08831079938116049, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.630979675818955, + 0.689913200780034, + 0.049299891166966696, + 1.0, + 2.0 + ] + ] + ], + [ + [ + [ + 4.9654689416120494e-11, + 34.99999999999999, + 5.99999999915092, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 1.1820544379002354e-10, + 34.99999999999999, + 5.999999998188975, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 9.62316835393836e-10, + 34.99999999999999, + 5.999999998219363, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 2.370379986689141e-09, + 34.99999999999999, + 5.9999999999977085, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 1.81471491993768, + 34.99999999999999, + 5.9999999996077085, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 2.3825484838800146, + 34.99999999999999, + 5.999999999999824, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 2.8934103113373757, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 3.477205918199783, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ], + [ + 1.4640170952921656e-10, + 34.99999999961843, + -0.6772621965874348, + 0.95, + 2.0 + ] + ], + [ + [ + 4.0839838611237615, + 34.99999999993467, + 5.9988215882750255, + 0.95, + 2.0 + ], + [ + 3.77739274937007e-10, + 34.99999999955287, + -0.5667783990196351, + 0.95, + 2.0 + ] + ], + [ + [ + 6.336014486468261, + 34.99999999999999, + 1.7116577134297741, + 0.95, + 2.0 + ], + [ + 1.0770805249696389e-09, + 34.99999999952273, + -0.4260961169900299, + 0.95, + 2.0 + ] + ], + [ + [ + 6.9106224815290185, + 34.99999999999999, + 1.7267808808677156, + 0.95, + 2.0 + ], + [ + 1.7871533631040006e-11, + 34.99999999955318, + -0.3797212862438786, + 0.95, + 2.0 + ] + ], + [ + [ + 7.181482609921225, + 34.99999999999999, + 2.004057454556115, + 0.95, + 2.0 + ], + [ + 3.9795909766931136e-10, + 34.999999999516355, + -0.0736953816363275, + 0.95, + 2.0 + ] + ], + [ + [ + 7.3627136575760295, + 33.63913801144748, + 2.1487258090943087, + 0.95, + 2.0 + ], + [ + 4.4195277096942067e-10, + 34.999999999922025, + -0.4758069191108928, + 0.95, + 2.0 + ] + ], + [ + [ + 7.37643439697113, + 29.425987008324682, + 1.9020408074782704, + 1.0, + 2.0 + ], + [ + 1.3483051178800816, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ] + ], + [ + [ + 7.310240604911599, + 25.009851574064587, + 1.685882842851216, + 1.0, + 2.0 + ], + [ + 2.7795196848654786, + 34.99999999999999, + 5.999999999959269, + 0.95, + 2.0 + ] + ], + [ + [ + 7.238292295572268, + 20.59819349732951, + 1.4922297898718293, + 1.0, + 2.0 + ], + [ + 6.347439506684498, + 34.99999999909165, + -0.06256287498436297, + 0.95, + 2.0 + ] + ], + [ + [ + 7.1625510569509, + 17.274126623531114, + 1.3139824597644811, + 1.0, + 2.0 + ], + [ + 7.232533676123044, + 34.99999999999999, + 0.44938087915436387, + 0.95, + 2.0 + ] + ], + [ + [ + 7.0763852549946265, + 14.61774413526908, + 1.1721095098689147, + 1.0, + 2.0 + ], + [ + 7.700770471501127, + 34.99999999999999, + 1.0744869487126782, + 0.95, + 2.0 + ] + ], + [ + [ + 6.980708204681553, + 12.810137799876737, + 1.0851385159950235, + 1.0, + 2.0 + ], + [ + 8.071998990380985, + 34.99999999999999, + 1.563396800051112, + 0.95, + 2.0 + ] + ], + [ + [ + 6.91154046622695, + 11.166016303593105, + 1.0004557567456902, + 1.0, + 2.0 + ], + [ + 8.230984255473922, + 31.022585068453427, + 1.6622790192134784, + 1.0, + 2.0 + ] + ], + [ + [ + 6.863572971026802, + 9.599703455665058, + 0.9123449443313645, + 1.0, + 2.0 + ], + [ + 8.091898033095994, + 23.385050175939345, + 1.290587400474974, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.928647793037756, + 17.172311895233626, + 1.0597608291222298, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.764013814362844, + 12.331851279251675, + 0.8499461938203592, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.5783299566153355, + 8.202707384547232, + 0.6572933567771301, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.4060662588302115, + 5.906971754211803, + 0.49982606017925113, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.581080760542316, + -0.31399135915072157, + -0.004819145492374166, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.144177409039715, + 4.216220905312801, + 0.31689685471888646, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.245088777597957, + -0.011630633457520469, + -0.4065406684798823, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.997842470437185, + 2.5367108495351403, + 0.16346209301203415, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.964209770615137, + 1.6820379222964266, + 0.108994852256164, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 6.958070782353378, + 0.9582310052407551, + 0.06218581334475135, + 1.0, + 2.0 + ] + ] + ], + [ + [ + [ + 2.0983526913650206e-09, + 34.99999999999454, + 3.8779316126113774, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 7.463124049096262e-11, + 34.99999999955446, + -0.14260573306650703, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 2.821766710241233e-10, + 34.999999999977625, + 5.500296668772509, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 7.399598023227027e-10, + 34.99999999999714, + 5.999501418638061, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 1.944662709505038e-11, + 34.9999999999999, + 5.999999941567596, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 9.705392173049462e-10, + 34.99999999999999, + 5.999999999948505, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 1.084473888149711e-09, + 34.99999999999999, + 5.999999999372921, + 0.95, + 2.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + [ + [ + 2.1322720757387166, + 34.99999999999999, + 5.999999999999703, + 0.95, + 2.0 + ], + [ + 2.1199409266440553e-09, + 34.99999999967921, + -1.0461589176524273, + 0.95, + 2.0 + ] + ], + [ + [ + 2.904278231857599, + 34.99999999999999, + 5.999999999999999, + 0.95, + 2.0 + ], + [ + 2.151354604282638e-11, + 34.99999999967477, + -0.9484556297361553, + 0.95, + 2.0 + ] + ], + [ + [ + 3.6541045162732906, + 34.99999999999061, + 5.999866357160404, + 0.95, + 2.0 + ], + [ + 7.524380117385861e-11, + 34.999999999649404, + -0.8342825478585567, + 0.95, + 2.0 + ] + ], + [ + [ + 6.578276153242146, + 34.99999999999999, + 1.1322189174365356, + 0.95, + 2.0 + ], + [ + 4.065312857339454e-10, + 34.99999999954595, + -0.6162262765649014, + 0.95, + 2.0 + ] + ], + [ + [ + 7.023518803155591, + 34.99999999999999, + 1.410287598954116, + 0.95, + 2.0 + ], + [ + 1.0312246295018444e-09, + 34.999999999557296, + -0.5551545078120719, + 0.95, + 2.0 + ] + ], + [ + [ + 7.313309387929943, + 34.99999999999999, + 1.741836250074866, + 0.95, + 2.0 + ], + [ + 5.7044351783513304e-11, + 34.999999999634504, + -0.28721820885267135, + 0.95, + 2.0 + ] + ], + [ + [ + 7.558719971125173, + 34.99999999999999, + 2.044260237165503, + 0.95, + 2.0 + ], + [ + 1.4934468260079434e-09, + 34.999999999949765, + -0.13646170900457077, + 0.95, + 2.0 + ] + ], + [ + [ + 7.607036194784784, + 30.98588583138352, + 1.9687012317586865, + 1.0, + 2.0 + ], + [ + 1.4157613370824518, + 34.999999999784215, + -0.5543856419003291, + 0.95, + 2.0 + ] + ], + [ + [ + 7.530170769023127, + 26.031979226967998, + 1.6304606042977294, + 1.0, + 2.0 + ], + [ + 3.795159250536795, + 34.99999999997373, + -0.5524168158276885, + 0.95, + 2.0 + ] + ], + [ + [ + 7.443784751367164, + 21.530911328872953, + 1.4417033607125171, + 1.0, + 2.0 + ], + [ + 7.101958848021088, + 34.999999999524405, + -0.6942985673020068, + 0.95, + 2.0 + ] + ], + [ + [ + 7.335059950124343, + 18.504530308922387, + 1.317762170790852, + 1.0, + 2.0 + ], + [ + 7.770567838406981, + 34.99999999999999, + 0.2608375011570796, + 0.95, + 2.0 + ] + ], + [ + [ + 7.227004843792961, + 16.607719733762025, + 1.2357359372014702, + 1.0, + 2.0 + ], + [ + 8.204241243465624, + 34.99999999999999, + 0.8960323049108944, + 0.95, + 2.0 + ] + ], + [ + [ + 7.160356537649271, + 14.666152322380599, + 1.142877931601445, + 1.0, + 2.0 + ], + [ + 8.471846795388554, + 34.99999999999999, + 1.4498143919798316, + 0.95, + 2.0 + ] + ], + [ + [ + 7.109549474074357, + 12.873236981444137, + 1.051417984515271, + 1.0, + 2.0 + ], + [ + 8.555188795534011, + 29.24378419595517, + 1.3828369942353502, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 8.39236585010411, + 21.624072244575004, + 1.1309536576668642, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 8.214094760386468, + 15.70626883667875, + 0.9117488387609237, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 8.008035072712772, + 10.687314089738043, + 0.711912724203117, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.804661704978582, + 7.1660934612115526, + 0.5533647503393293, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 8.05983594278343, + -0.367405989105931, + -1.2645061112040406, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.501390068246384, + 4.954384085220262, + 0.3683658993348934, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.641330263951103, + -0.015226126584597701, + -0.4815137654278009, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.331184726826271, + 3.1414287072494864, + 0.19168274180388542, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.292498622778836, + 2.193332693776837, + 0.1295978817692537, + 1.0, + 2.0 + ] + ], + [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 7.285934716290837, + 1.2703064445358123, + 0.07518377709025027, + 1.0, + 2.0 + ] + ] + ] + ] +} \ No newline at end of file diff --git a/runDaring.py b/runDaring.py new file mode 100644 index 0000000..915306e --- /dev/null +++ b/runDaring.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +""" +Velocity Prediction Program for the Daring sailing yacht. + +The Daring is a one-design keelboat designed by Arthur Robb, based on +his 5.5 Metre class yacht "Vision" (1956 Olympic silver medal). + +Published specifications (classicsailboats.org): + LOA: 9.90m | LWL: 7.01m | Beam: 1.98m | Draft: 1.35m + Displacement: 2000 kg | Upwind sail area: 29.73 m² + +Estimated parameters are documented in docs/plans/2026-02-27-daring-vpp.md. +All estimates are marked and can be refined with actual measurements. +""" +import logging + +import numpy as np + +from src.SailMod import Jib, Kite, Main +from src.VPPMod import VPP +from src.YachtMod import Rudder, ShortKeel, Yacht + +logging.basicConfig(level=logging.INFO) + +# --- Estimated GZ curve for classic 5.5m --- +# GM ~0.70m, ~50% ballast ratio, narrow beam (1.98m) +# See docs/plans/2026-02-27-daring-vpp.md for derivation +DARING_GZ = { + "Heel": [0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0], + "GZ": [0.000, 0.120, 0.230, 0.310, 0.350, 0.330, 0.260], +} + +Daring = Yacht( + Name="Daring", + Lwl=7.01, # (published) waterline length + Vol=1.95, # (estimated) 2000kg / 1025 kg/m³ + Bwl=1.70, # (estimated) ~86% of Boa + Tc=0.45, # (estimated) canoe body draft + WSA=11.5, # (estimated) Delf series for narrow hull + Tmax=1.35, # (published) max draft incl. keel + Amax=0.38, # (estimated) Bwl × Tc × Cm(0.50) + Mass=2000, # (published) total displacement + Loa=9.90, # (published) length overall + Boa=1.98, # (published) beam overall + Ff=0.75, # (estimated) freeboard fore + Fa=0.55, # (estimated) freeboard aft + App=[ + ShortKeel(Length=1.2, Depth=0.90, Tc_ratio=0.15), # hull-integrated keel + Rudder(Cu=0.32, Cl=0.18, Span=0.75), # (estimated) separated rudder + ], + Sails=[ + Main("MN1", P=10.80, E=3.30, Roach=0.1, BAD=0.80), # (est.) ~19.6 m² + Jib("J1", I=8.50, J=2.70, LPG=2.70, HBI=0.50), # (est.) ~11.5 m² + Kite("S1", area=50.0, vce=4.50), # (est.) symmetric kite + ], + GZ=DARING_GZ, + crew_weight=240.0, # 3 crew × 80 kg +) + +vpp = VPP(Yacht=Daring) + +vpp.set_analysis( + tws_range=np.arange(4.0, 22.0, 2.0), + twa_range=np.linspace(30.0, 180.0, 31), +) + +vpp.run(verbose=False) +vpp.write("results_daring") +vpp.polar(3, True) +vpp.SailChart(True) diff --git a/runVPP.py b/runVPP.py index ca737e6..a06ed97 100755 --- a/runVPP.py +++ b/runVPP.py @@ -1,4 +1,4 @@ -#!/opt/miniconda3/bin/python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- import numpy as np @@ -36,7 +36,7 @@ vpp = VPP(Yacht=YD41) vpp.set_analysis( - tws_range=np.arange(4.0, 22.0, 2.0), twa_range=np.linspace(30.0, 180.0, 31) + tws_range=np.arange(4.0, 22.0, 2.0), twa_range=np.linspace(28.0, 180.0, 39) ) vpp.run(verbose=False) diff --git a/setup.py b/setup.py deleted file mode 100644 index d65658d..0000000 --- a/setup.py +++ /dev/null @@ -1,22 +0,0 @@ -import setuptools - -with open("README.md", "r") as fh: - long_description = fh.read() - -setuptools.setup( - name="Python-VPP", # Replace with your own username - version="0.0.2", - author="Marin Lauber, Otto Vilani and Thomas Dickson", - author_email="M.Lauber@soton.ac.uk", - description="OOP Velocity Prediction Program", - long_description=long_description, - long_description_content_type="text/markdown", - # url="https://github.com/marinlauber/OOpyPST", - packages=setuptools.find_packages(), - classifiers=[ - "Programming Language :: Python :: 3", - "License :: MIT License", - "Operating System :: OS Independent", - ], - python_requires=">=3.9", -) diff --git a/sphinx/index.rst b/sphinx/index.rst index 39b1d34..1ea06d3 100644 --- a/sphinx/index.rst +++ b/sphinx/index.rst @@ -1,13 +1,15 @@ Welcome to Python-VPP's documentation! ====================================== -.. automodule:: src.VPPMod - :members: +Python-VPP is a 3-DOF Velocity Prediction Program based on the +`ORC `_ aerodynamic and hydrodynamic models. .. toctree:: :maxdepth: 2 :caption: Contents: + modules + Indices and tables ================== diff --git a/sphinx/modules.rst b/sphinx/modules.rst new file mode 100644 index 0000000..4a00e64 --- /dev/null +++ b/sphinx/modules.rst @@ -0,0 +1,44 @@ +API Reference +============= + +VPP Solver +---------- + +.. automodule:: src.VPPMod + :members: + :undoc-members: + +Yacht Model +----------- + +.. automodule:: src.YachtMod + :members: + :undoc-members: + +Sail Model +---------- + +.. automodule:: src.SailMod + :members: + :undoc-members: + +Aerodynamic Model +----------------- + +.. automodule:: src.AeroMod + :members: + :undoc-members: + +Hydrodynamic Model +------------------ + +.. automodule:: src.HydroMod + :members: + :undoc-members: + +Utilities +--------- + +.. automodule:: src.UtilsMod + :members: + :undoc-members: diff --git a/src/AeroMod.py b/src/AeroMod.py index 88101c7..44f0a99 100644 --- a/src/AeroMod.py +++ b/src/AeroMod.py @@ -7,18 +7,46 @@ __version__ = "1.0.1" __email__ = "M.Lauber@soton.ac.uk" -import numpy as np -from scipy.interpolate import interp1d -from scipy.optimize import fsolve -from scipy.optimize import root +import functools + import matplotlib.pyplot as plt +import numpy as np + from src.UtilsMod import build_interp_func +@functools.lru_cache(maxsize=512) +def wind_triangle(tws, twa, vb): + """Analytical wind triangle: TWS/TWA/VB → (AWA, AWS) in degrees. + + Uses the law of cosines on the velocity triangle formed by the true + wind, boat speed, and apparent wind vectors. + """ + twa_rad = np.radians(twa) + aws = np.sqrt(tws**2 + vb**2 + 2 * tws * vb * np.cos(twa_rad)) + if aws < 1e-12: + return twa, 0.0 + cos_awa = np.clip((tws * np.cos(twa_rad) + vb) / aws, -1.0, 1.0) + awa = np.degrees(np.arccos(cos_awa)) + return awa, aws + + class AeroMod(object): def __init__(self, Yacht, rho=1.225, mu=0.0000181): """ - Initializes an Aero Model, given a set of sails + Aerodynamic force model. + + Computes sail drive force, side force, and heeling moment from the + yacht's sail plan using ORC aerodynamic coefficients. + + Parameters + ---------- + Yacht : Yacht + Yacht object containing sail definitions and hull geometry. + rho : float, optional + Air density (kg/m^3). Default is 1.225 (ISA sea level). + mu : float, optional + Dynamic viscosity of air (Pa.s). Default is 1.81e-5. """ # physical params self.rho = rho @@ -74,7 +102,31 @@ def _measure_sails(self): # prototype top function in hydro mod def update(self, vb, phi, tws, twa, flat, RED): """ - Update the aero model for current iter + Update aerodynamic forces for current sailing state. + + Solves the wind triangle, computes sail coefficients, and projects + forces into the boat reference frame. + + Parameters + ---------- + vb : float + Boat speed (m/s). + phi : float + Heel angle (degrees). + tws : float + True wind speed (m/s). + twa : float + True wind angle (degrees). + flat : float + Sail flattening factor (0.62 to 1.0). Reduces lift and drag. + RED : float + Reef/reduction factor. Values > 1 apply jib furling (ftj = RED - 1), + values <= 1 apply mainsail reefing (rfm = RED). + + Returns + ------- + tuple of float + (Fx, Fy, Mx) — drive force (N), side force (N), heeling moment (N.m). """ self.vb = max(0, vb) self.phi = max(0, phi) @@ -112,7 +164,7 @@ def _compute_forces(self): # side-force is horizontal component of Fh self.Fy *= np.cos(np.radians(self.phi)) - + # heeling moment self.Mx = self.Fy * self._vce() @@ -136,12 +188,15 @@ def _get_coeffs(self): self.cl = 0.0 self.cd = 0.0 kpp = 0.0 + sail_cd = {} for sail in self.sails: - - self.cl += sail.cl(self.awa) * sail.area * sail.bk - self.cd += sail.cd(self.awa) * sail.area * sail.bk - kpp += sail.cl(self.awa) ** 2 * sail.area * sail.bk * sail.kp + cl_i = sail.cl(self.awa) + cd_i = sail.cd(self.awa) + sail_cd[id(sail)] = cd_i + self.cl += cl_i * sail.area * sail.bk + self.cd += cd_i * sail.area * sail.bk + kpp += cl_i ** 2 * sail.area * sail.bk * sail.kp self.cl /= self.area self.cd /= self.area @@ -156,7 +211,7 @@ def _get_coeffs(self): for sail in self.sails: if sail.type == "jib": self.fcdj = ( - sail.bk * sail.cd(self.awa) * sail.area / (self.cd * self.area) + sail.bk * sail_cd[id(sail)] * sail.area / (self.cd * self.area) ) # final lift and drag @@ -170,17 +225,7 @@ def _update_windTriangle(self): """ find AWS and AWA for a given TWS, TWA and VB """ - _awa_ = lambda awa: self.vb * np.sin(awa / 180.0 * np.pi) - self.tws * np.sin( - (self.twa - awa) / 180.0 * np.pi - ) - self.awa = fsolve(_awa_, self.twa)[0] - self.aws = np.sqrt( - (self.tws * np.sin(self.twa / 180.0 * np.pi)) ** 2 - + (self.tws * np.cos(self.twa / 180.0 * np.pi) + self.vb) ** 2 - ) - # self.awa = np.arccos((self.tws*np.cos(np.radians(self.twa)) + self.vb) / np.sqrt((self.tws**2) + (self.vb**2) + - # 2*self.tws*self.vb * np.cos(np.radians(self.twa)))) - # self.aws = (self.tws * np.sin(np.radians(self.twa))) / np.sin(self.awa) + self.awa, self.aws = wind_triangle(self.tws, self.twa, self.vb) def _area(self): @@ -194,7 +239,7 @@ def _area(self): def _vce(self): """ - Vectical centre of effort lift/drag weigted + Vertical centre of effort, lift/drag weighted. """ sum = 0.0 for sail in self.sails: diff --git a/src/HydroMod.py b/src/HydroMod.py index 4901793..bcc69c0 100644 --- a/src/HydroMod.py +++ b/src/HydroMod.py @@ -7,14 +7,33 @@ __version__ = "1.0.1" __email__ = "M.Lauber@soton.ac.uk" -import numpy as np -from scipy.interpolate import RegularGridInterpolator import warnings + import matplotlib.pyplot as plt +import numpy as np +from scipy.interpolate import RegularGridInterpolator class HydroMod(object): def __init__(self, Yacht, rho=1025.0, mu=0.00119, g=9.81): + """ + Hydrodynamic resistance and righting moment model. + + Computes total resistance (viscous + residuary + induced), side force + from appendage lift, and righting moment using ORC methods and + interpolated resistance surfaces. + + Parameters + ---------- + Yacht : Yacht + Yacht object containing hull geometry and appendage definitions. + rho : float, optional + Seawater density (kg/m^3). Default is 1025.0. + mu : float, optional + Dynamic viscosity of seawater (Pa.s). Default is 1.19e-3. + g : float, optional + Gravitational acceleration (m/s^2). Default is 9.81. + """ # physical parameters self.rho = rho @@ -111,7 +130,7 @@ def _get_Ri(self): self.Teff = np.hstack((self.Teff, appendage.teff)) self.Ksfj = ( - 0.5 * self.rho * self.vb ** 2 * self.cla * self.leeway / 180.0 * np.pi + 0.5 * self.rho * self.vb ** 2 * self.cla * np.radians(self.leeway) ) self.Ksf = np.sum(self.Ksfj) @@ -122,24 +141,99 @@ def _get_Ri(self): def _cf(self, L): """ - Flate plate turbulent boudnary layer friction coefficient. - Take a length scale, such that it can be used for appendags as well + Flat plate turbulent boundary layer friction coefficient (ITTC 1957) + with ITTC 1978 roughness allowance. Takes a length scale so it can + be used for hull and appendages. """ Re = max( 1e4, self.vb * L / self.nu ) # prevents dividing by zero, lowest for turbulence on plate - return 0.066 * (np.log10(Re) - 2.03) ** (-2) + cf = 0.066 * (np.log10(Re) - 2.03) ** (-2) + ks = self.yacht.roughness + if ks > 0: + cf += (105.0 * (ks / self.l) ** (1.0 / 3.0) - 0.64) * 1e-3 + return cf + + def _added_resistance_waves(self, twa): + """Added resistance in waves (simplified Gerritsma scaling). + + Uses a Bretschneider-type spectrum with hull-form scaling to + estimate the mean added resistance from ocean waves. + + Parameters + ---------- + twa : float + True wind angle (degrees), used as wave encounter angle + unless ``yacht.wave_direction`` overrides it. + + Returns + ------- + float + Added resistance in Newtons. Returns 0 when Hs = 0. + """ + Hs = self.yacht.Hs + Ts = self.yacht.Ts + if Hs <= 0 or Ts <= 0: + return 0.0 - def update(self, vb, phi, leeway): + # wave encounter angle + if self.yacht.wave_direction is not None: + mu = np.radians(twa - self.yacht.wave_direction) + else: + mu = np.radians(twa) + + # heading correction — full drag head-on, near-zero following + cos2_mu = np.cos(mu) ** 2 + + # hull-form coefficient (empirical, typical displacement hull) + C_aw = 6.0 + + # wave encounter frequency + omega_0 = 2.0 * np.pi / Ts + omega_e = abs(omega_0 - omega_0 ** 2 * self.vb * np.cos(mu) / self.g) + + # resonance tuning factor (peak when encounter ≈ hull natural freq) + omega_n = np.sqrt(self.g / self.l) + r = omega_e / omega_n if omega_n > 0 else 0.0 + f_omega = r ** 2 * np.exp(1.0 - r ** 2) if r > 0 else 0.0 + + Raw = C_aw * (Hs ** 2 / self.l) * (self.bwl ** 2 / self.tc) * f_omega * cos2_mu + # convert to Newtons (rho * g scaling) + Raw *= self.rho * self.g / 1000.0 + + return max(0.0, Raw) + + def update(self, vb, phi, leeway, twa=0.0): + """ + Update hydrodynamic forces for current sailing state. + + Parameters + ---------- + vb : float + Boat speed (m/s). + phi : float + Heel angle (degrees). + leeway : float + Leeway angle (degrees). + twa : float, optional + True wind angle (degrees). Used for wave encounter angle. + + Returns + ------- + tuple of float + (Fx, Fy, Mx) — total resistance (N), side force (N), + total righting moment (N.m). + """ self.vb = max(0, vb) self.phi = max(0, phi) - self.leeway = max(0, leeway) + self.leeway = leeway self.lsm, self.lvr, self.btr = self.yacht.measureLSM() self.fn = self.vb / (np.sqrt(self.g * self.lsm)) # resistance self.Fx = self._get_Rr() + self._get_Rv() + self._get_Ri() + self.Fx += self._added_resistance_waves(twa) # keel side force, calculated when _get_Ri() is called self.Fy = self.Ksf * np.cos(self.phi / 180.0 * np.pi) diff --git a/src/RaceMod.py b/src/RaceMod.py new file mode 100644 index 0000000..8ef5498 --- /dev/null +++ b/src/RaceMod.py @@ -0,0 +1,614 @@ +"""Match racing simulation engine. + +Simulates two boats racing a windward-leeward course with stochastic +wind shifts and tactical interactions. Based on Philpott, Henderson & +Teirney (2004). +""" + +from __future__ import annotations + +import numpy as np +from scipy.interpolate import RegularGridInterpolator + +from src.WindMod import BrownianWind, WindModel + + +class Boat: + """State of one boat during a race.""" + + def __init__(self, polar, name, boat_length=12.0): + self.polar = polar + self.name = name + self.boat_length = boat_length + self.x = 0.0 + self.y = 0.0 + self.tack = 1 # +1 = starboard, -1 = port + self.speed = 0.0 + self.elapsed = 0.0 + self.tack_count = 0 + self.gybe_count = 0 + self.penalty_remaining = 0.0 + self._just_tacked = False + self._tack_cooldown = 0.0 + + self.shadow_seconds = 0.0 + self.shadow_encounters = 0 + self._in_shadow = False + + def reset(self): + self.x = 0.0 + self.y = 0.0 + self.tack = 1 + self.speed = 0.0 + self.elapsed = 0.0 + self.tack_count = 0 + self.gybe_count = 0 + self.penalty_remaining = 0.0 + self._just_tacked = False + self._tack_cooldown = 0.0 + self.shadow_seconds = 0.0 + self.shadow_encounters = 0 + self._in_shadow = False + + +class Race: + """Windward-leeward match race simulation. + + Parameters + ---------- + polar_A, polar_B : callable + callable(tws, twa) -> boat_speed in knots. + tws : float + True wind speed (knots). + leg_distance : float + Leg distance in nautical miles. Default 1.0. + n_legs : int + Number of upwind/downwind leg pairs. Default 1. + tack_penalty : float + Time penalty per tack (seconds). Default 10.0. + gybe_penalty : float + Time penalty per gybe (seconds). Default 6.0. + wind_model : WindModel or None + Pluggable wind model. If *None*, a :class:`BrownianWind` is + created from *tws* and *wind_sigma*. + wind_sigma : float + Wind shift volatility (deg/sqrt(min)). Only used when + *wind_model* is None. Default 2.0. + current_speed : float + Current speed (knots). Default 0.0. + current_dir : float + Current direction (degrees, 0 = upwind). Default 0.0. + corridor_width : float + Course corridor half-width (metres). Default 200.0. + boat_length : float + Boat length (metres) for wind shadow. Default 12.0. + trim_sigma : float + Trim/crew noise — fractional std-dev on boat speed. + 0 = perfect trim, 0.03 = 3 % noise. Default 0.0. + tack_penalty_std : float + Std-dev on tack penalty (seconds). Default 0.0 (fixed). + gybe_penalty_std : float + Std-dev on gybe penalty (seconds). Default 0.0 (fixed). + """ + + NM_TO_M = 1852.0 + KTS_TO_MS = 0.5144 + + def __init__(self, polar_A, polar_B, tws, leg_distance=1.0, n_legs=1, + tack_penalty=10.0, gybe_penalty=6.0, + wind_model: WindModel | None = None, wind_sigma=2.0, + current_speed=0.0, current_dir=0.0, corridor_width=200.0, + boat_length=12.0, + trim_sigma=0.0, tack_penalty_std=0.0, gybe_penalty_std=0.0): + self.polar_A = polar_A + self.polar_B = polar_B + self.tws = tws + self.leg_distance_m = leg_distance * self.NM_TO_M + self.n_legs = n_legs + self.tack_penalty = tack_penalty + self.gybe_penalty = gybe_penalty + self.current_speed = current_speed * self.KTS_TO_MS + self.current_dir_rad = np.radians(current_dir) + self.corridor_width = corridor_width + self.boat_length = boat_length + self.trim_sigma = trim_sigma + self.tack_penalty_std = tack_penalty_std + self.gybe_penalty_std = gybe_penalty_std + + # Wind model — default to simple Brownian direction shifts + if wind_model is not None: + self.wind_model = wind_model + else: + self.wind_model = BrownianWind(tws=tws, twd=0.0, dir_sigma=wind_sigma) + + def _optimal_vmg_angle(self, polar, tws, upwind=True): + """Find TWA that maximises VMG from a polar lookup.""" + if upwind: + angles = np.arange(25.0, 75.0, 1.0) + else: + angles = np.arange(120.0, 180.0, 1.0) + + best_vmg = -1e9 + best_twa = angles[0] + for twa in angles: + bs = polar(tws, twa) + if upwind: + vmg = bs * np.cos(np.radians(twa)) + else: + vmg = bs * np.cos(np.radians(180.0 - twa)) + if vmg > best_vmg: + best_vmg = vmg + best_twa = twa + return best_twa + + def _is_in_shadow(self, boat_ahead, boat_behind, wind_dir): + """Check if boat_behind is in boat_ahead's dirty air zone. + + Requires at least 2 boat lengths of downwind separation. + """ + dx = boat_behind.x - boat_ahead.x + dy = boat_behind.y - boat_ahead.y + + wind_rad = np.radians(wind_dir) + downwind_dist = -dy * np.cos(wind_rad) - dx * np.sin(wind_rad) + cross_dist = abs(-dy * np.sin(wind_rad) + dx * np.cos(wind_rad)) + + min_dist = 2 * boat_ahead.boat_length + max_dist = 10 * boat_ahead.boat_length + if downwind_dist < min_dist or downwind_dist > max_dist: + return False + shadow_width = downwind_dist * np.tan(np.radians(30)) + return cross_dist < shadow_width + + def _should_tack(self, boat, opponent, twa_opt, upwind, wind_dir, rng): + """Evaluate tactical rules — return True if boat should tack/gybe.""" + if boat._tack_cooldown > 0: + return False + + # Rule 1: Layline — only tack if sailing AWAY from mark + if upwind: + remaining = self.leg_distance_m - boat.y + if remaining > 0 and abs(boat.x) > 10: + bearing = np.degrees(np.arctan2(abs(boat.x), remaining)) + sailing_away = (boat.x * boat.tack > 0) + if bearing <= twa_opt and sailing_away: + return True + else: + remaining = boat.y + if remaining > 0 and abs(boat.x) > 10: + bearing = np.degrees(np.arctan2(abs(boat.x), remaining)) + sailing_away = (boat.x * boat.tack > 0) + if bearing <= (180.0 - twa_opt) and sailing_away: + return True + + # Rule 2: Course boundary + if abs(boat.x) > self.corridor_width / 2: + return True + + separation = abs(boat.y - opponent.y) + min_sep = 3.0 * boat.boat_length + + # Rule 3: In dirty air — tack to escape + if self._is_in_shadow(opponent, boat, wind_dir): + return True + + # Rule 4: Leading + opponent tacked → cover + if separation > min_sep and boat.y > opponent.y and opponent._just_tacked: + if boat.tack == opponent.tack: + return True + + # Rule 5: Trailing + same tack as leader → split (70%) + if separation > min_sep and boat.y < opponent.y and boat.tack == opponent.tack: + if rng.random() < 0.70: + return True + + return False + + def _sample_penalty(self, mean, std, rng): + """Draw a tack/gybe penalty from N(mean, std), floored at 1s.""" + if std <= 0: + return mean + return max(1.0, mean + std * rng.standard_normal()) + + def _apply_trim_noise(self, bs, rng): + """Apply crew/trim noise to boat speed.""" + if self.trim_sigma <= 0: + return bs + return bs * max(0.0, 1.0 - self.trim_sigma * abs(rng.standard_normal())) + + def _run_leg(self, boat_A, boat_B, upwind, rng, dt=1.0): + """Simulate a single leg. Returns (trace_A, trace_B).""" + trace_A = [] + trace_B = [] + + # Recompute optimal angles from current wind speed + tws = self.wind_model.tws + twa_opt_A = self._optimal_vmg_angle(boat_A.polar, tws, upwind) + twa_opt_B = self._optimal_vmg_angle(boat_B.polar, tws, upwind) + + # Current vector (m/s) + cur_x = self.current_speed * np.sin(self.current_dir_rad) + cur_y = self.current_speed * np.cos(self.current_dir_rad) + + cooldown_time = max(self.tack_penalty, self.gybe_penalty) + 5.0 + + max_steps = int(3600 * 4 / dt) + for step in range(max_steps): + if upwind: + if boat_A.y >= self.leg_distance_m and boat_B.y >= self.leg_distance_m: + break + else: + if boat_A.y <= 0 and boat_B.y <= 0: + break + + # Advance wind model + tws, wind_dir = self.wind_model.update(dt, rng) + + # Re-derive optimal VMG if wind speed changed significantly + # (only worth it for models that vary TWS) + # For now, keep twa_opt fixed per leg for performance + + # Randomize processing order + if rng.random() < 0.5: + order = [boat_A, boat_B] + opt_angles = [twa_opt_A, twa_opt_B] + else: + order = [boat_B, boat_A] + opt_angles = [twa_opt_B, twa_opt_A] + + for idx, (boat, twa_opt) in enumerate(zip(order, opt_angles)): + opponent = order[1 - idx] + + if upwind and boat.y >= self.leg_distance_m: + continue + if not upwind and boat.y <= 0: + continue + + boat._just_tacked = False + if boat._tack_cooldown > 0: + boat._tack_cooldown -= dt + + if boat.penalty_remaining > 0: + boat.penalty_remaining -= dt + boat.elapsed += dt + continue + + if self._should_tack(boat, opponent, twa_opt, upwind, wind_dir, rng): + boat.tack *= -1 + boat._just_tacked = True + boat._tack_cooldown = cooldown_time + if upwind: + boat.penalty_remaining = self._sample_penalty( + self.tack_penalty, self.tack_penalty_std, rng) + boat.tack_count += 1 + else: + boat.penalty_remaining = self._sample_penalty( + self.gybe_penalty, self.gybe_penalty_std, rng) + boat.gybe_count += 1 + boat.elapsed += dt + continue + + # Speed with shadow + trim noise + effective_tws = tws + in_shadow = self._is_in_shadow(opponent, boat, wind_dir) + if in_shadow: + effective_tws *= 0.90 + boat.shadow_seconds += dt + if not boat._in_shadow: + boat.shadow_encounters += 1 + boat._in_shadow = in_shadow + + bs_kts = boat.polar(effective_tws, twa_opt) + bs_kts = self._apply_trim_noise(bs_kts, rng) + bs = bs_kts * self.KTS_TO_MS + + if upwind: + heading_rad = np.radians(wind_dir + boat.tack * twa_opt) + else: + heading_rad = np.radians(wind_dir + 180.0 + boat.tack * (180.0 - twa_opt)) + + dx = bs * np.sin(heading_rad) * dt + cur_x * dt + dy = bs * np.cos(heading_rad) * dt + cur_y * dt + + # Interpolate exact crossing time when boat reaches mark + prev_y = boat.y + boat.x += dx + boat.y += dy + boat.speed = bs + + crossed = (upwind and boat.y >= self.leg_distance_m and prev_y < self.leg_distance_m) or \ + (not upwind and boat.y <= 0 and prev_y > 0) + if crossed and abs(dy) > 1e-9: + target = self.leg_distance_m if upwind else 0.0 + frac = (target - prev_y) / dy + boat.elapsed += frac * dt + else: + boat.elapsed += dt + + if step % 5 == 0: + trace_A.append((boat_A.x, boat_A.y)) + trace_B.append((boat_B.x, boat_B.y)) + + return trace_A, trace_B + + def run_single(self, seed=None): + """Run one race. + + Returns + ------- + dict with keys: time_A, time_B, tack_count_A, tack_count_B, + gybe_count_A, gybe_count_B, trace_A, trace_B + """ + rng = np.random.default_rng(seed) + boat_A = Boat(self.polar_A, "A", self.boat_length) + boat_B = Boat(self.polar_B, "B", self.boat_length) + + # Random start-line offset — one boat wins the pin (0.5–2 boat + # lengths of lateral separation, slight y jitter). This breaks + # perfect symmetry just as a real start does. + pin_offset = (0.5 + 1.5 * rng.random()) * self.boat_length + if rng.random() < 0.5: + boat_A.x = pin_offset / 2 + boat_B.x = -pin_offset / 2 + else: + boat_A.x = -pin_offset / 2 + boat_B.x = pin_offset / 2 + # Small along-course jitter (0–0.5 boat lengths) models timing + # differences at the start gun. + boat_A.y = rng.random() * 0.5 * self.boat_length + boat_B.y = rng.random() * 0.5 * self.boat_length + + self.wind_model.reset(tws=self.tws, twd=0.0) + + all_trace_A = [] + all_trace_B = [] + leg_times_A = [] + leg_times_B = [] + leg_types = [] + + for leg_idx in range(self.n_legs * 2): + upwind = (leg_idx % 2 == 0) + leg_types.append("upwind" if upwind else "downwind") + + if not upwind: + boat_A.y = self.leg_distance_m + boat_B.y = self.leg_distance_m + + start_A = boat_A.elapsed + start_B = boat_B.elapsed + tA, tB = self._run_leg(boat_A, boat_B, upwind, rng) + leg_times_A.append(boat_A.elapsed - start_A) + leg_times_B.append(boat_B.elapsed - start_B) + all_trace_A.extend(tA) + all_trace_B.extend(tB) + + boat_A.x = 0.0 + boat_B.x = 0.0 + + return { + "time_A": boat_A.elapsed, + "time_B": boat_B.elapsed, + "tack_count_A": boat_A.tack_count, + "tack_count_B": boat_B.tack_count, + "gybe_count_A": boat_A.gybe_count, + "gybe_count_B": boat_B.gybe_count, + "trace_A": all_trace_A, + "trace_B": all_trace_B, + "leg_times_A": leg_times_A, + "leg_times_B": leg_times_B, + "leg_types": leg_types, + "tactics_A": { + "shadow_seconds": boat_A.shadow_seconds, + "shadow_encounters": boat_A.shadow_encounters, + }, + "tactics_B": { + "shadow_seconds": boat_B.shadow_seconds, + "shadow_encounters": boat_B.shadow_encounters, + }, + } + + def run_monte_carlo(self, n_runs=100): + """Run N races with different random seeds. + + Returns + ------- + dict with keys: + wins_A, wins_B : int + deltas : list of float (time_A - time_B in seconds) + mean_delta : float + traces : tuple (trace_A, trace_B) from first run + results : list of single-race result dicts + """ + deltas = [] + wins_A = 0 + wins_B = 0 + first_traces = None + all_results = [] + + for i in range(n_runs): + result = self.run_single(seed=i) + all_results.append(result) + delta = result["time_A"] - result["time_B"] + deltas.append(delta) + if result["time_A"] < result["time_B"]: + wins_A += 1 + elif result["time_B"] < result["time_A"]: + wins_B += 1 + if first_traces is None: + first_traces = (result["trace_A"], result["trace_B"]) + + # Compute per-leg statistics + n_legs_total = self.n_legs * 2 + leg_stats = [] + for leg_idx in range(n_legs_total): + leg_deltas = [] + a_wins = 0 + for r in all_results: + if "leg_times_A" in r and leg_idx < len(r["leg_times_A"]): + d = r["leg_times_A"][leg_idx] - r["leg_times_B"][leg_idx] + leg_deltas.append(d) + if r["leg_times_A"][leg_idx] < r["leg_times_B"][leg_idx]: + a_wins += 1 + leg_type = "upwind" if leg_idx % 2 == 0 else "downwind" + leg_stats.append({ + "leg_type": leg_type, + "mean_delta": float(np.mean(leg_deltas)) if leg_deltas else 0.0, + "a_wins": a_wins, + }) + + # Tactical summary + shadow_A = [r["tactics_A"]["shadow_seconds"] for r in all_results if "tactics_A" in r] + shadow_B = [r["tactics_B"]["shadow_seconds"] for r in all_results if "tactics_B" in r] + encounters_A = [r["tactics_A"]["shadow_encounters"] for r in all_results if "tactics_A" in r] + encounters_B = [r["tactics_B"]["shadow_encounters"] for r in all_results if "tactics_B" in r] + tactics_summary = { + "mean_shadow_seconds_A": float(np.mean(shadow_A)) if shadow_A else 0.0, + "mean_shadow_seconds_B": float(np.mean(shadow_B)) if shadow_B else 0.0, + "mean_shadow_encounters_A": float(np.mean(encounters_A)) if encounters_A else 0.0, + "mean_shadow_encounters_B": float(np.mean(encounters_B)) if encounters_B else 0.0, + } + + return { + "wins_A": wins_A, + "wins_B": wins_B, + "deltas": deltas, + "mean_delta": float(np.mean(deltas)), + "traces": first_traces, + "results": all_results, + "leg_stats": leg_stats, + "tactics_summary": tactics_summary, + } + + def run_tws_sweep(self, tws_range, n_runs=100): + """Run monte carlo at each TWS and collect win probabilities. + + Parameters + ---------- + tws_range : array-like + TWS values (knots) to sweep. + n_runs : int + Monte carlo runs per TWS point. + + Returns + ------- + dict with keys: + tws_values : list of float + win_pct_A : list of float (fraction 0-1) + win_pct_B : list of float (fraction 0-1) + mean_deltas : list of float (seconds, A - B) + """ + tws_values = [] + win_pct_A = [] + win_pct_B = [] + mean_deltas = [] + + original_tws = self.tws + for tws in tws_range: + self.tws = tws + self.wind_model.reset(tws=tws) + mc = self.run_monte_carlo(n_runs=n_runs) + total = mc["wins_A"] + mc["wins_B"] + tws_values.append(float(tws)) + if total > 0: + win_pct_A.append(mc["wins_A"] / total) + win_pct_B.append(mc["wins_B"] / total) + else: + win_pct_A.append(0.5) + win_pct_B.append(0.5) + mean_deltas.append(mc["mean_delta"]) + + self.tws = original_tws + + return { + "tws_values": tws_values, + "win_pct_A": win_pct_A, + "win_pct_B": win_pct_B, + "mean_deltas": mean_deltas, + } + + @staticmethod + def build_polar_interp(tws_array, twa_array, results_4d): + """Build callable polar from VPP results. + + Parameters + ---------- + tws_array : array + TWS values (m/s). + twa_array : array + TWA values (degrees). + results_4d : array, shape (n_tws, n_twa, n_sails, 5) + + Returns + ------- + callable(tws_kts, twa_deg) -> boat_speed_kts + """ + best_speed = np.max(results_4d[:, :, :, 0], axis=2) + tws_kts = tws_array / 0.5144 + interp = RegularGridInterpolator( + (tws_kts, twa_array), best_speed, + bounds_error=False, fill_value=0.0, + ) + return lambda tws, twa: float(np.clip(interp((tws, twa)), 0.0, None)) + + +def run_parameter_sweep(param_values, param_name, tws, n_runs=100, + polar_factory_A=None, polar_factory_B=None, + yacht_factory_A=None, yacht_factory_B=None, + leg_distance=1.0, n_legs=1, wind_sigma=2.0, + **race_kwargs): + """Sweep a parameter and measure match race outcomes at each value. + + For each value in *param_values*, builds polars via the factory + callables and runs a monte carlo match race. + + Parameters + ---------- + param_values : list of float + Values to sweep. + param_name : str + Label for the swept parameter (used in output). + tws : float + True wind speed (knots). + n_runs : int + Monte carlo runs per parameter point. + polar_factory_A, polar_factory_B : callable(value) -> polar, optional + Build a polar callable for each parameter value. + yacht_factory_A, yacht_factory_B : callable(value) -> Yacht, optional + Not used directly — reserved for full VPP rebuild workflows. + leg_distance, n_legs, wind_sigma : float + Race configuration. + **race_kwargs + Extra keyword arguments passed to Race(). + + Returns + ------- + dict with keys: + param_values, param_name, win_pct_A, win_pct_B, mean_deltas + """ + win_pct_A = [] + win_pct_B = [] + mean_deltas = [] + + for v in param_values: + polar_A = polar_factory_A(v) if polar_factory_A else None + polar_B = polar_factory_B(v) if polar_factory_B else None + race = Race(polar_A, polar_B, tws=tws, + leg_distance=leg_distance, n_legs=n_legs, + wind_sigma=wind_sigma, **race_kwargs) + mc = race.run_monte_carlo(n_runs=n_runs) + total = mc["wins_A"] + mc["wins_B"] + if total > 0: + win_pct_A.append(mc["wins_A"] / total) + win_pct_B.append(mc["wins_B"] / total) + else: + win_pct_A.append(0.5) + win_pct_B.append(0.5) + mean_deltas.append(mc["mean_delta"]) + + return { + "param_values": list(param_values), + "param_name": param_name, + "win_pct_A": win_pct_A, + "win_pct_B": win_pct_B, + "mean_deltas": mean_deltas, + } diff --git a/src/SailMod.py b/src/SailMod.py index 1fbfb23..95fa8d3 100644 --- a/src/SailMod.py +++ b/src/SailMod.py @@ -7,33 +7,112 @@ __version__ = "1.0.1" __email__ = "M.Lauber@soton.ac.uk" -import numpy as np import matplotlib.pyplot as plt +import numpy as np from scipy import interpolate +SAIL_TYPES = { + "main": "main", + "main_low": "main_low", + "jib": "jib", + "jib_low": "jib_low", + "kite": "kite", + "sym_kite": "sym_kite", + "asym_cl_kite": "asym_cl_kite", + "asym_pole_kite": "asym_pole_kite", +} + + class Sail(object): - def __init__(self, name, type, area, vce, up=True): + def __init__(self, name, type, area, vce, up=True, data_source="orc", + cl_data=None, cd_data=None, sail_type=None): + """ + Base sail class. + Parameters + ---------- + name : str + Sail identifier (e.g. "J1", "A2"). + type : str + Sail type, used to load coefficient data from ``dat/.dat``. + One of ``"main"``, ``"jib"``, or ``"kite"``. + area : float + Sail area (m^2). + vce : float + Vertical centre of effort above deck (m). + up : bool, optional + Whether this is an upwind sail. Default is True. + data_source : str, optional + Coefficient data source directory under ``dat/``. Default "orc". + cl_data : dict, optional + User-provided CL data: ``{"awa": [...], "values": [...]}``. + cd_data : dict, optional + User-provided CD data: ``{"awa": [...], "values": [...]}``. + sail_type : str, optional + Override for the coefficient data file name. E.g. ``"main_low"``, + ``"sym_kite"``, ``"asym_cl_kite"``, ``"asym_pole_kite"``. + If None, uses *type*. + """ self.name = name self.type = type self.area = area self.vce = vce + # Determine which data file to load + coeff_file = sail_type if sail_type is not None else self.type # get sails coefficients - self._build_interp_func(self.type) + if cl_data is not None and cd_data is not None: + self._build_interp_func(coeff_file, data_source=data_source) + self._build_interp_from_arrays(cl_data, cd_data) + else: + self._build_interp_func(coeff_file, data_source=data_source) self.bk = 1.0 # always valid for main, only AWA<135 for jib self.up = up # is that an upwind sail? - def _build_interp_func(self, fname): - """ - build interpolation function and returns it in a list + def _build_interp_func(self, fname, data_source="orc"): + """Build cubic B-spline interpolation functions for CL and CD. + + Falls back to linear (k=1) if fewer than 4 data points are available. + Tries ``dat/{data_source}/{fname}.dat`` first, then ``dat/{fname}.dat``. """ - a = np.genfromtxt("dat/" + fname + ".dat", delimiter=",", skip_header=1) + import os + path = os.path.join("dat", data_source, fname + ".dat") + if not os.path.exists(path): + path = os.path.join("dat", fname + ".dat") + a = np.genfromtxt(path, delimiter=",", skip_header=1) self.kp = a[0, 0] - # linear for now, this is not good, might need to polish data outside - self.interp_cd = interpolate.interp1d(a[1, :], a[2, :], kind="linear") - self.interp_cl = interpolate.interp1d(a[1, :], a[3, :], kind="linear") + # Filter NaN values (trailing commas in CSV create NaNs) + mask = ~(np.isnan(a[1, :]) | np.isnan(a[2, :]) | np.isnan(a[3, :])) + x = a[1, mask] + cd = a[2, mask] + cl = a[3, mask] + k = 3 if len(x) >= 4 else 1 + self.interp_cd = interpolate.make_interp_spline(x, cd, k=k) + self.interp_cd.extrapolate = True + self.interp_cl = interpolate.make_interp_spline(x, cl, k=k) + self.interp_cl.extrapolate = True + + def _build_interp_from_arrays(self, cl_data, cd_data): + """Build interpolation from user-provided coefficient arrays. + + Parameters + ---------- + cl_data : dict + {"awa": [...], "values": [...]} for lift coefficients. + cd_data : dict + {"awa": [...], "values": [...]} for drag coefficients. + """ + cl_awa = np.array(cl_data["awa"]) + cl_vals = np.array(cl_data["values"]) + cd_awa = np.array(cd_data["awa"]) + cd_vals = np.array(cd_data["values"]) + k_cl = 3 if len(cl_awa) >= 4 else 1 + k_cd = 3 if len(cd_awa) >= 4 else 1 + self.interp_cl = interpolate.make_interp_spline(cl_awa, cl_vals, k=k_cl) + self.interp_cl.extrapolate = True + self.interp_cd = interpolate.make_interp_spline(cd_awa, cd_vals, k=k_cd) + self.interp_cd.extrapolate = True def cl(self, awa): awa = max(0, min(awa, 180)) @@ -43,6 +122,9 @@ def cd(self, awa): awa = max(0, min(awa, 180)) return self.interp_cd(awa) + def measure(self, rfm, ftj): + """Update sail dimensions for current reef/furl state.""" + def debbug_coeffs(self, N=256): awa = np.linspace(0, 180, N) coeffs = np.empty((N, 2)) @@ -56,46 +138,31 @@ def debbug_coeffs(self, N=256): class Main(Sail): - def __init__(self, name, P, E, Roach, BAD): + def __init__(self, name, P, E, Roach, BAD, data_source="orc", + cl_data=None, cd_data=None, sail_type=None): """ - Initialize mainsail - - This function initializes an object of class Main which inherits the class Sail. - It calculates the area, the Vertical Center of Effort (vce), - sets CE = 1 and calls the super class constructor. - + Initialize mainsail. + Parameters ---------- - name : string - Of the sail - - P : Float - Height of the Mainsail in meters - - E: Float - Length (Foot) of the Mainsail in meters - - Roach: Float - Percentage by which the triangular area is increased in order to obtain the mainsail area. - This is area behind the line from clew to head. - To get this number you have to calculate Mainsail_area / (P*E/2) -1 - - BAD: Float - Boom Above Deck: Distance between boom and Deck in meters - - Returns - ------- - Main - Object of type Main( Sail ) - - See Also - -------- - Jib( Sail ) - - Examples - -------- - >>> Main("MN1", P=16.60, E=5.60, Roach=0.1, BAD=1.0) - + name : str + Sail identifier. + P : float + Height of the Mainsail in meters. + E : float + Length (Foot) of the Mainsail in meters. + Roach : float + Roach factor (Mainsail_area / (P*E/2) - 1). + BAD : float + Boom Above Deck in meters. + data_source : str, optional + Coefficient data source. Default "orc". + cl_data : dict, optional + User-provided CL data. + cd_data : dict, optional + User-provided CD data. + sail_type : str, optional + Coefficient variant: ``"main"`` (default) or ``"main_low"``. """ self.name = name self.type = "main" @@ -105,10 +172,23 @@ def __init__(self, name, P, E, Roach, BAD): self.BAD = BAD self.area0 = 0.5 * P * E * (1 + self.roach) self.vce = P / 3.0 * (1 + self.roach) + self.BAD - super().__init__(self.name, self.type, self.area0, self.vce) + super().__init__(self.name, self.type, self.area0, self.vce, + data_source=data_source, cl_data=cl_data, cd_data=cd_data, + sail_type=sail_type) self.measure() - + def measure(self, rfm=1, ftj=1): + """ + Update mainsail dimensions for reef state. + + Parameters + ---------- + rfm : float + Reef factor for mainsail (0 to 1). 1 = fully unreefed. + ftj : float + Furl factor for jib (unused for mainsail, present for interface + compatibility). + """ self.P_r = self.P*rfm self.vce = self.P_r / 3.0 * (1 + self.roach) + self.BAD self.area = self.area0*rfm**2 @@ -116,7 +196,32 @@ def measure(self, rfm=1, ftj=1): class Jib(Sail): - def __init__(self, name, I, J, LPG, HBI): + def __init__(self, name, I, J, LPG, HBI, data_source="orc", + cl_data=None, cd_data=None, sail_type=None): + """ + Headsail (jib/genoa). + + Parameters + ---------- + name : str + Sail identifier (e.g. "J1"). + I : float + Forestay height (m). + J : float + Base of the foretriangle (m). + LPG : float + Luff perpendicular (m). + HBI : float + Height of the jib tack above deck (m). + data_source : str, optional + Coefficient data source. Default "orc". + cl_data : dict, optional + User-provided CL data. + cd_data : dict, optional + User-provided CD data. + sail_type : str, optional + Coefficient variant: ``"jib"`` (default) or ``"jib_low"``. + """ self.name = name self.type = "jib" self.I = I @@ -126,23 +231,60 @@ def __init__(self, name, I, J, LPG, HBI): self.HBI = HBI self.area = 0.5 * I * max(J, LPG) self.vce = I / 3.0 + HBI - super().__init__(self.name, self.type, self.area, self.vce) + super().__init__(self.name, self.type, self.area, self.vce, + data_source=data_source, cl_data=cl_data, cd_data=cd_data, + sail_type=sail_type) self.measure() def measure(self, rfm=1, ftj=1): + """ + Update jib dimensions for furl state. + + Parameters + ---------- + rfm : float + Reef factor for mainsail (unused for jib, present for interface + compatibility). + ftj : float + Furl factor for jib (0 to 1). 0 = fully unfurled. + """ self.LPG_r = self.LPG*ftj self.IG_r = self.IG*ftj self.area = 0.5 * self.I * max(self.J, self.LPG_r) class Kite(Sail): - def __init__(self, name, area, vce): + def __init__(self, name, area, vce, data_source="orc", + cl_data=None, cd_data=None, sail_type=None): + """ + Spinnaker or asymmetric downwind sail. + + Parameters + ---------- + name : str + Sail identifier (e.g. "A2", "A5"). + area : float + Sail area (m^2). + vce : float + Vertical centre of effort above deck (m). + data_source : str, optional + Coefficient data source. Default "orc". + cl_data : dict, optional + User-provided CL data. + cd_data : dict, optional + User-provided CD data. + sail_type : str, optional + Coefficient variant: ``"kite"`` (default), ``"sym_kite"``, + ``"asym_cl_kite"``, or ``"asym_pole_kite"``. + """ self.name = name self.type = "kite" self.area = area self.min_area = self.area self.vce = vce - super().__init__(self.name, self.type, self.area, self.vce, up=False) + super().__init__(self.name, self.type, self.area, self.vce, up=False, + data_source=data_source, cl_data=cl_data, cd_data=cd_data, + sail_type=sail_type) self.measure() def measure(self, rfm=1, ftj=1): diff --git a/src/UtilsMod.py b/src/UtilsMod.py index 57fe6b5..ac2cd1e 100644 --- a/src/UtilsMod.py +++ b/src/UtilsMod.py @@ -6,6 +6,7 @@ import matplotlib.pyplot as plt import numpy as np from scipy import interpolate +from scipy.interpolate import RegularGridInterpolator KNOTS_TO_MPS = 0.5144 stl = [ @@ -39,11 +40,17 @@ def json_write(data, fname): def build_interp_func(fname, i=1, kind="linear"): """ - build interpolatison function and returns it in a list + build interpolation function and returns it in a list """ a = np.genfromtxt("dat/" + fname + ".dat", delimiter=",", skip_header=1) - # linear for now, this is not good, might need to polish data outside - return interpolate.interp1d(a[0, :], a[i, :], kind=kind, fill_value="extrapolate") + # Filter out NaN values for make_interp_spline compatibility + mask = ~(np.isnan(a[0, :]) | np.isnan(a[i, :])) + x = a[0, mask] + y = a[i, mask] + k = {"linear": 1, "quadratic": 2, "cubic": 3}.get(kind, 1) + spline = interpolate.make_interp_spline(x, y, k=k) + spline.extrapolate = True + return spline def _polar(n) -> plt.Figure: @@ -94,11 +101,24 @@ def _get_vmg(dat, twa_range): return np.array([ix[sup], iy[sdn]], dtype=int), np.array([sup, sdn]) -def _get_cross(dat, n): +def _get_cross(dat, n, pad=2): + """Return [start, end) TWA indices where sail *n* is fastest. + + Parameters + ---------- + dat : ndarray + Shape ``(ntwa, nsails, nvars)``. + n : int + Sail index. + pad : int + Number of extra TWA indices to include on each side for visual + overlap. Use 0 for a tight (no-overlap) range. + """ max_ = np.where(dat[:, n, 0] >= np.max(dat[:, :, 0], axis=1))[0] if len(max_) > 0: idx = np.array( - [max(min(max_) - 2, 0), min(max(max_) + 2, len(dat[:, n, 0]))], dtype=int + [max(min(max_) - pad, 0), min(max(max_) + pad, len(dat[:, n, 0]))], + dtype=int, ) return idx else: @@ -125,8 +145,12 @@ def polar_plot(VPP_list, n, save, fname="Polars.png") -> None: for i in range(len(VPP.tws_range)): vmg, ids = _get_vmg(VPP.store[i, :, :, :], VPP.twa_range) for k in range(VPP.Nsails): - idx = _get_cross(VPP.store[i, :, :, :], k) for j in range(n): + # Speed plot (j=0) uses overlap padding so sail curves + # connect visually; heel/leeway (j>0) use tight range + # to avoid discontinuities at sail crossover points. + pad = 2 if j == 0 else 0 + idx = _get_cross(VPP.store[i, :, :, :], k, pad=pad) lab = "_nolegend_" if k == 0: lab = name + " " + f"{VPP.tws_range[i]/KNOTS_TO_MPS:.1f}" @@ -149,9 +173,23 @@ def polar_plot(VPP_list, n, save, fname="Polars.png") -> None: markersize=4, mfc="None", ) - # add legend only on first axis - ax[0].legend(title=r"TWS (knots)", loc=1, bbox_to_anchor=(1.05, 1.05)) + # TWS legend on every axis + for j in range(n): + ax[j].legend(title=r"TWS (knots)", loc=1, bbox_to_anchor=(1.05, 1.05)) + + # Sail colour legend along the bottom of the figure + from matplotlib.lines import Line2D + VPP = VPP_list[-1] + sail_handles = [ + Line2D([0], [0], color=cols[k % 7], lw=2, label=VPP.sail_name[k]) + for k in range(VPP.Nsails) + ] + fig.legend( + handles=sail_handles, title="Sail set", + loc="lower center", ncol=VPP.Nsails, frameon=True, + ) plt.tight_layout() + fig.subplots_adjust(bottom=0.12) if save: plt.savefig(fname, dpi=96) else: @@ -178,9 +216,12 @@ def sail_chart(VPP, save, fname="SailChart.png"): for j in range(ntwa): if sailset[i, j] == id: sail[i + 1, j + 1] = 1.0 - func = interpolate.interp2d(twas, twss, sail, kind="cubic") - data = func(xnew, ynew) - data = np.where(data > 1.0, 1.0, data) + func = RegularGridInterpolator( + (twss, twas), sail, method="cubic", bounds_error=False, fill_value=0.0 + ) + yy, xx = np.meshgrid(ynew, xnew, indexing="ij") + data = func((yy, xx)) + data = np.clip(data, 0.0, 1.0) ax[0].contour( np.radians(xnew), ynew, data, levels=[0.4], colors=cols[id], alpha=0.8 ) diff --git a/src/VPPMod.py b/src/VPPMod.py index 1232d5b..0abb112 100644 --- a/src/VPPMod.py +++ b/src/VPPMod.py @@ -9,10 +9,10 @@ import logging import warnings +from concurrent.futures import ProcessPoolExecutor -import nlopt import numpy as np -from scipy.optimize import root +from scipy.optimize import least_squares, minimize, root from tqdm import trange from src.AeroMod import AeroMod @@ -24,6 +24,91 @@ debug_mode = logging.getLogger().getEffectiveLevel() == logging.DEBUG +def _solve_point(yacht, tws, twa, sail_index, phi_max, lim_up, lim_dn): + """Solve one (tws, twa, sail) grid point independently. + + Creates its own AeroMod/HydroMod so there is no shared mutable state. + This is a module-level function so it can be pickled by ProcessPoolExecutor. + + Returns + ------- + tuple + (i, j, n, result_array) where result_array is [vb_kts, phi, leeway, flat, red], + or None if the point was skipped. + """ + i, j, n = sail_index + + aero = AeroMod(yacht) + hydro = HydroMod(yacht) + aero.sails[1] = yacht.sails[n + 1] + aero.up = aero.sails[1].up + + # Skip invalid sail/TWA combinations + Nsails = len(yacht.sails) - 1 + dn_limit = 135.0 if Nsails != 1 else 200.0 + if aero.up and twa >= dn_limit: + return None + if not aero.up and twa <= lim_up: + return None + + # Initial guesses + vb0 = 0.8 * tws + phi0 = 0.0 + leeway0 = 100.0 / twa if (twa > 1.0 and 100.0 / twa < 2 * tws) else 2 * tws + + def resid(x0, twa_, tws_, flat=1.0, red=2.0): + vb_, phi_, leeway_ = x0 + Fxh, Fyh, Mxh = hydro.update(vb_, phi_, leeway_, twa_) + Fxa, Fya, Mxa = aero.update(vb_, phi_, tws_, twa_, flat, red) + return [(Fxh - Fxa) ** 2, (Mxh - Mxa) ** 2, (Fyh - Fya) ** 2] + + flat = 1.0 + red = 2.0 + + sol = root(resid, [vb0, phi0, leeway0], + args=(twa, tws, flat, red), method="lm") + vb, phi, leeway = sol.x + + if phi <= phi_max: + res = np.array([vb, phi, leeway, flat, red]) + res[0] /= KNOTS_TO_MPS + return (i, j, n, res) + + # Depowering: flatten then reef + lo = [0, 0, -2] + hi = [np.inf, phi_max, 6] + margin = phi_max - 1.0 + + def _clamp(vb_, phi_, leeway_): + return [max(vb_, 0), min(max(phi_, 0), phi_max), + min(max(leeway_, -2), 6)] + + for flat in np.arange(0.98, 0.60, -0.02): + sol = least_squares( + resid, _clamp(vb, phi_max, leeway), + args=(twa, tws, flat, red), bounds=(lo, hi), + ) + vb, phi, leeway = sol.x + if phi <= margin: + res = np.array([vb, phi, leeway, flat, red]) + res[0] /= KNOTS_TO_MPS + return (i, j, n, res) + + flat = 0.62 + for red in np.arange(1.9, 0.45, -0.1): + sol = least_squares( + resid, _clamp(vb, phi_max, leeway), + args=(twa, tws, flat, red), bounds=(lo, hi), + ) + vb, phi, leeway = sol.x + if phi <= margin: + break + + res = np.array([vb, phi, leeway, flat, red]) + res[0] /= KNOTS_TO_MPS + return (i, j, n, res) + + class VPP(object): """A VPP Class that run an analysis on a given Yacht.""" @@ -49,8 +134,9 @@ def __init__(self, Yacht): warnings.filterwarnings( "ignore", "The iteration is not making good progress" ) + self.upToDate = False - def set_analysis(self, tws_range, twa_range): + def set_analysis(self, tws_range, twa_range, phi_max=35.0): """ Sets the analysis range. Parameters @@ -61,19 +147,28 @@ def set_analysis(self, tws_range, twa_range): A numpy.array with the different TWA to run the analysis at. """ - if tws_range.max() <= 35.0 and tws_range.min() >= 2.0: - logging.debug("Analysis set for TWS: ", tws_range) - self.tws_range = tws_range * KNOTS_TO_MPS - else: - logging.debug("Analysis only valid for TWS range : 2. < TWS < 35. knots.") - - if twa_range.max() <= 180.0 and twa_range.min() >= 0.0: - self.twa_range = twa_range - logging.debug("Analysis set for TWA: ", self.twa_range) - else: - logging.debug( - "Analysis only valid for TWA range : 0. < TWA < 180. degrees." + self.phi_max = phi_max + + if tws_range.size == 0: + raise ValueError("TWS range is empty. Ensure min and max TWS are not equal.") + if twa_range.size == 0: + raise ValueError("TWA range is empty. Ensure min and max TWA are not equal.") + + if tws_range.min() < 2.0 or tws_range.max() > 35.0: + raise ValueError( + f"TWS range [{tws_range.min():.1f}, {tws_range.max():.1f}] " + f"is outside valid bounds [2.0, 35.0] knots." ) + self.tws_range = tws_range * KNOTS_TO_MPS + logger.debug("Analysis set for TWS: %s", tws_range) + + if twa_range.min() < 0.0 or twa_range.max() > 180.0: + raise ValueError( + f"TWA range [{twa_range.min():.1f}, {twa_range.max():.1f}] " + f"is outside valid bounds [0.0, 180.0] degrees." + ) + self.twa_range = twa_range + logger.debug("Analysis set for TWA: %s", self.twa_range) # prepare storage array self.Nsails = len(self.yacht.sails) - 1 # main not counted @@ -95,51 +190,33 @@ def set_analysis(self, tws_range, twa_range): # flag for later self.upToDate = True - def Vb(self, x, grad): - # this should not be used - if grad.size > 0: - grad = 0.0 - return self.vb0 - - def SumForce(self, res, x, grad, twa_, tws_): - # this should not be used - if grad.size > 0: - grad[:, :] = 0.0 - - vb0 = x[0] - phi0 = x[1] - leeway = x[2] - flat = x[3] - red = x[4] - - Fxh, Fyh, Mxh = self.hydro.update(vb0, phi0, leeway) - Fxa, Fya, Mxa = self.aero.update(vb0, phi0, tws_, twa_, flat, red) - - res[0] = Fxh - Fxa - res[1] = Mxh - Mxa - res[2] = Fyh - Fya - - return None + def run(self, verbose=False, method="iterative", progress_callback=None): + """ + Run the analysis for the given analysis range. + Parameters + ---------- + verbose + A logical, if True, prints results of equilibrium at each TWA/TWS. + method + Solver method: "iterative" (3-DOF sequential with depowering loop), + "parallel" (same solver, multiprocessing across grid points), or + "5dof" (scipy SLSQP 5-DOF constrained optimizer). + progress_callback + Optional callable(tws_index, tws_kts, n_tws) invoked after each + TWS wind speed is completed. + """ - def run_NLopt(self, verbose=False): - logging.info("Optimisation start") + if method == "5dof": + return self._run_5dof(verbose) + elif method == "parallel": + return self._run_parallel() + elif method != "iterative": + raise ValueError(f"Unknown method '{method}'. Use 'iterative', 'parallel', or '5dof'.") if not self.upToDate: - raise "VPP run stop: no analysis set!" - - # gradient-free optimization because the gradient of our - # objective function cannot be evaluated - opt = nlopt.opt(nlopt.LN_COBYLA, 5) + raise RuntimeError("VPP run stop: no analysis set!") - # out three parameters are x = [v_b, hell, leeway, flat, red] - opt.set_lower_bounds([0.0, 0.0, 0.0, 0.0, 0.0]) - opt.set_upper_bounds([float("inf"), self.phi_max, 6.0, 1.0, 2.0]) - - # the function we want to maximise - opt.set_max_objective(self.Vb) - - # set solver tolerance - opt.set_xtol_rel(1e-6) + n_tws = len(self.tws_range) for i, tws in enumerate(self.tws_range): logging.debug("Sailing in TWS : %.1f" % (tws / KNOTS_TO_MPS)) @@ -164,6 +241,8 @@ def run_NLopt(self, verbose=False): if (twa > 1.0 and 100.0 / twa < 2 * tws) else 2 * tws ) + self.flat = 1.0 + self.red = 2.0 # don't do low twa with downwind sails if (self.aero.up == True) and (twa >= self.lim_dn): @@ -171,110 +250,181 @@ def run_NLopt(self, verbose=False): if (self.aero.up == False) and (twa <= self.lim_up): continue - # vector-valued constraint - constrain = lambda res, x, grad: self.SumForce( - res, x, grad, twa_=twa, tws_=tws - ) - opt.add_equality_mconstraint(constrain, np.full(5, 1e-8)) + vb, phi, leeway, flat, red = self._depower_solve(twa, tws) + self.vb0, self.phi0, self.leeway0 = vb, phi, leeway - x0 = np.array([self.vb0, self.phi0, self.leeway0, 1.0, 2.0]) - res = opt.optimize(x0) + logging.debug( + "Equilibrium residuals (Fx, Fy, Mx): ", + self.resid([vb, phi, leeway], twa, tws, flat, red), + ) # store data for later - self.store[i, j, n, :] = res[:] * np.array( - [1.0 / KNOTS_TO_MPS, 1, 1, 1, 1] - ) + res = np.array([vb, phi, leeway, flat, red]) + self.store[i, j, n, :] = res * np.array([1.0 / KNOTS_TO_MPS, 1, 1, 1, 1]) - # clean up - opt.remove_equality_constraints() + if progress_callback: + progress_callback(i, tws / KNOTS_TO_MPS, n_tws) logging.info("Optimization successful.") - def run(self, verbose=False): - """ - Run the analysis for the given analysis range. - Parameters - ---------- - verbose - A logical, if True, prints results of equilibrium at each TWA/TWS. + def _run_parallel(self): + """Run iterative solver in parallel across all grid points.""" + if not self.upToDate: + raise RuntimeError("VPP run stop: no analysis set!") + + # Build work items: (yacht, tws, twa, (i,j,n), phi_max, lim_up, lim_dn) + work = [] + for i, tws in enumerate(self.tws_range): + for n in range(self.Nsails): + for j, twa in enumerate(self.twa_range): + work.append(( + self.yacht, tws, twa, (i, j, n), + self.phi_max, self.lim_up, self.lim_dn + )) + + with ProcessPoolExecutor() as pool: + results = pool.map(_solve_point, *zip(*work)) + + for result in results: + if result is not None: + i, j, n, res = result + self.store[i, j, n, :] = res + + logging.info("Parallel optimization successful.") + + def _depower_solve(self, twa, tws): + """Solve 3-DOF equilibrium, depowering iteratively if heel exceeds phi_max. + + Depowering follows real sailing practice: + 1. Flatten sails (flat: 1.0 -> 0.62) + 2. Reef main / furl jib (RED: 2.0 -> 0.5) + + The bounded solver pins phi at phi_max, so we check for genuine + margin (1 degree) before accepting a solution — otherwise the boat + is still overpowered and needs more depowering. """ + flat = 1.0 + red = 2.0 + + sol = root(self.resid, [self.vb0, self.phi0, self.leeway0], + args=(twa, tws, flat, red), method="lm") + vb, phi, leeway = sol.x + + if phi <= self.phi_max: + return vb, phi, leeway, flat, red + + # Heel exceeds limit — use bounded solver to enforce phi <= phi_max + lo = [0, 0, -2] + hi = [np.inf, self.phi_max, 6] + margin = self.phi_max - 1.0 + + def _clamp_guess(vb_, phi_, leeway_): + return [max(vb_, 0), min(max(phi_, 0), self.phi_max), + min(max(leeway_, -2), 6)] + + # Stage 1: Flatten sails (1.0 -> 0.62) + for flat in np.arange(0.98, 0.60, -0.02): + sol = least_squares( + self.resid, _clamp_guess(vb, self.phi_max, leeway), + args=(twa, tws, flat, red), bounds=(lo, hi), + ) + vb, phi, leeway = sol.x + if phi <= margin: + return vb, phi, leeway, flat, red + + # Stage 2: Reef main / furl jib (RED: 2.0 -> 0.5) + flat = 0.62 + for red in np.arange(1.9, 0.45, -0.1): + sol = least_squares( + self.resid, _clamp_guess(vb, self.phi_max, leeway), + args=(twa, tws, flat, red), bounds=(lo, hi), + ) + vb, phi, leeway = sol.x + if phi <= margin: + return vb, phi, leeway, flat, red + # If still over, return best we got + return vb, phi, leeway, flat, red + + def _run_5dof(self, verbose=False): + """Run 5-DOF constrained optimization using scipy SLSQP. + + Simultaneously optimizes [vb, phi, leeway, flat, red] to maximize + boat speed subject to force/moment equilibrium constraints. + """ if not self.upToDate: - raise "VPP run stop: no analysis set!" + raise RuntimeError("VPP run stop: no analysis set!") for i, tws in enumerate(self.tws_range): logging.debug("Sailing in TWS : %.1f" % (tws / KNOTS_TO_MPS)) for n in range(self.Nsails): self.aero.sails[1] = self.yacht.sails[n + 1] - - logging.debug( - "Sail Config : ", - self.aero.sails[0].name + " + " + self.aero.sails[1].name, - ) - self.aero.up = self.aero.sails[1].up for j in trange(len(self.twa_range), disable=not debug_mode): twa = self.twa_range[j] - self.vb0 = 0.8 * tws - self.phi0 = 0 - self.leeway0 = ( - 100.0 / twa - if (twa > 1.0 and 100.0 / twa < 2 * tws) - else 2 * tws - ) - self.flat = 1.0 - self.red = 2.0 - # don't do low twa with downwind sails if (self.aero.up == True) and (twa >= self.lim_dn): continue if (self.aero.up == False) and (twa <= self.lim_up): continue - sol = root( - self.resid, - [self.vb0, self.phi0, self.leeway0], - args=(twa, tws), - method="lm", + vb_guess = 0.8 * tws + leeway_guess = ( + 100.0 / twa + if (twa > 1.0 and 100.0 / twa < 2 * tws) + else 2 * tws ) - self.vb0, self.phi0, self.leeway0 = res = sol.x - - if verbose and not sol.success: - logger.debug(sol.message) - - # # contraints - # con1 = {'type': 'eq', 'fun': self.Fx, 'args': (twa, tws)} - # con2 = {'type': 'eq', 'fun': self.Fy, 'args': (twa, tws)} - # con3 = {'type': 'eq', 'fun': self.Mx, 'args': (twa, tws)} - # con = (con1, con2, con3) - - # # initial guess at this twa/tws - # x0 = [self.vb0, self.phi0, self.leeway0, self.flat, self.red] - - # # minimize - # sol = minimize(self.objective, args=(twa, tws), x0=x0, method='SLSQP', - # constraints=con, bounds=self.bnds, tol=1e-2, - # options={"maxiter": 100, "disp": verbose}) - - # # get result - # self.vb0, self.phi0, self.leeway0, self.flat, self.red = res = sol.x - - logging.debug( - "Equilibrium residuals (Fx, Fy, Mx): ", - self.resid(sol.x, twa, tws), + x0 = np.array([vb_guess, 0.0, leeway_guess, 1.0, 2.0]) + + def _forces(x): + vb, phi, leeway, flat, red = x + Fxh, Fyh, Mxh = self.hydro.update(vb, phi, leeway, twa) + Fxa, Fya, Mxa = self.aero.update(vb, phi, tws, twa, flat, red) + return Fxh, Fyh, Mxh, Fxa, Fya, Mxa + + constraints = [ + {"type": "eq", "fun": lambda x: _forces(x)[0] - _forces(x)[3]}, + {"type": "eq", "fun": lambda x: _forces(x)[2] - _forces(x)[5]}, + {"type": "eq", "fun": lambda x: _forces(x)[1] - _forces(x)[4]}, + ] + + # Cache forces to avoid redundant evaluations + _cache = {} + + def _cached_forces(x): + key = tuple(x) + if key not in _cache: + _cache[key] = _forces(x) + return _cache[key] + + constraints = [ + {"type": "eq", "fun": lambda x: _cached_forces(x)[0] - _cached_forces(x)[3]}, + {"type": "eq", "fun": lambda x: _cached_forces(x)[2] - _cached_forces(x)[5]}, + {"type": "eq", "fun": lambda x: _cached_forces(x)[1] - _cached_forces(x)[4]}, + ] + + result = minimize( + lambda x: -x[0], + x0, + method="SLSQP", + bounds=self.bnds, + constraints=constraints, + options={"maxiter": 200, "ftol": 1e-8}, ) - # store data for later - self.store[i, j, n, : len(res)] = ( - res[:] * np.array([1.0 / KNOTS_TO_MPS, 1, 1, 1, 1])[: len(res)] + _cache.clear() + + res = result.x + self.store[i, j, n, :] = res * np.array( + [1.0 / KNOTS_TO_MPS, 1, 1, 1, 1] ) - logging.info("Optimization successful.") + logging.info("5-DOF optimization successful.") - def resid(self, x0, twa, tws): + def resid(self, x0, twa, tws, flat=1.0, red=2.0): """ Computes the residuals of the force/moment equilibrium at the given state. Parameters @@ -285,6 +435,10 @@ def resid(self, x0, twa, tws): A float of the TWA at which to compute the residuals. tws A float of the TWs at which to compute the residuals. + flat + Sail flattening factor (0.62 to 1.0). Default 1.0 (no flattening). + red + Reef/reduction factor. Default 2.0 (full sails, no reef/furl). Returns ------- Numpy.Array @@ -292,13 +446,11 @@ def resid(self, x0, twa, tws): """ vb0 = x0[0] - phi0 = x0[1] # min(x0[1], self.phi_max) + phi0 = x0[1] leeway = x0[2] - flat = 1.0 # x0[3] - red = 1.0 # x0[4] - Fxh, Fyh, Mxh = self.hydro.update(vb0, phi0, leeway) - Fxa, Fya, Mxa = self.aero.update(vb0, phi0, tws, twa, flat, 2.0) + Fxh, Fyh, Mxh = self.hydro.update(vb0, phi0, leeway, twa) + Fxa, Fya, Mxa = self.aero.update(vb0, phi0, tws, twa, flat, red) return [(Fxh - Fxa) ** 2, (Mxh - Mxa) ** 2, (Fyh - Fya) ** 2] diff --git a/src/WindMod.py b/src/WindMod.py new file mode 100644 index 0000000..817b984 --- /dev/null +++ b/src/WindMod.py @@ -0,0 +1,155 @@ +"""Pluggable wind models for race simulation. + +All wind models implement the same interface: given a timestep and RNG, +return the current (tws, twd) state. This makes it straightforward to +swap in more sophisticated models (spatial fields, gusts, thermal +effects) without changing the race engine. + +Usage +----- +>>> model = BrownianWind(tws=10.0, twd=0.0, dir_sigma=2.0) +>>> tws, twd = model.update(dt=1.0, rng=np.random.default_rng(42)) +""" + +from __future__ import annotations + +import numpy as np + + +class WindModel: + """Base class for wind models. + + Subclasses must implement :meth:`update` and :meth:`state`. + """ + + def __init__(self, tws: float, twd: float = 0.0): + self.tws = tws + self.twd = twd + + def update(self, dt: float, rng: np.random.Generator) -> tuple[float, float]: + """Advance the wind state by *dt* seconds. + + Returns + ------- + (tws, twd) : tuple of float + Current true wind speed (knots) and direction (degrees). + """ + return self.tws, self.twd + + def state(self) -> dict: + """Serialisable snapshot for reproducibility.""" + return {"tws": self.tws, "twd": self.twd, "model": type(self).__name__} + + def reset(self, tws: float | None = None, twd: float = 0.0): + """Reset to initial conditions.""" + if tws is not None: + self.tws = tws + self.twd = twd + + +class ConstantWind(WindModel): + """Fixed wind — no variation at all.""" + pass + + +class BrownianWind(WindModel): + """Brownian motion on wind direction only. + + Parameters + ---------- + tws : float + Constant true wind speed (knots). + twd : float + Initial wind direction (degrees). + dir_sigma : float + Direction volatility (deg / sqrt(min)). Default 2.0. + """ + + def __init__(self, tws: float, twd: float = 0.0, dir_sigma: float = 2.0): + super().__init__(tws, twd) + self.dir_sigma = dir_sigma + self._tws_base = tws + + def update(self, dt: float, rng: np.random.Generator) -> tuple[float, float]: + self.twd += self.dir_sigma * np.sqrt(dt / 60.0) * rng.standard_normal() + return self.tws, self.twd + + def state(self) -> dict: + d = super().state() + d["dir_sigma"] = self.dir_sigma + return d + + def reset(self, tws: float | None = None, twd: float = 0.0): + super().reset(tws, twd) + if tws is not None: + self._tws_base = tws + + +class MeanRevertingWind(WindModel): + """Mean-reverting Brownian motion on both direction and speed. + + Direction: Ornstein-Uhlenbeck process around 0 (mean wind axis). + Speed: Ornstein-Uhlenbeck around initial TWS. + + Parameters + ---------- + tws : float + Mean true wind speed (knots). + twd : float + Initial wind direction (degrees). + dir_sigma : float + Direction volatility (deg / sqrt(min)). + dir_reversion : float + Direction mean-reversion rate (1/min). Default 0.05. + tws_sigma : float + Speed volatility (kts / sqrt(min)). Default 0.0 (off). + tws_reversion : float + Speed mean-reversion rate (1/min). Default 0.1. + """ + + def __init__(self, tws: float, twd: float = 0.0, + dir_sigma: float = 2.0, dir_reversion: float = 0.05, + tws_sigma: float = 0.0, tws_reversion: float = 0.1): + super().__init__(tws, twd) + self.dir_sigma = dir_sigma + self.dir_reversion = dir_reversion + self.tws_sigma = tws_sigma + self.tws_reversion = tws_reversion + self._tws_mean = tws + self._twd_mean = twd + + def update(self, dt: float, rng: np.random.Generator) -> tuple[float, float]: + dt_min = dt / 60.0 + + # Direction: OU process + self.twd += ( + self.dir_reversion * (self._twd_mean - self.twd) * dt_min + + self.dir_sigma * np.sqrt(dt_min) * rng.standard_normal() + ) + + # Speed: OU process (only if tws_sigma > 0) + if self.tws_sigma > 0: + self.tws += ( + self.tws_reversion * (self._tws_mean - self.tws) * dt_min + + self.tws_sigma * np.sqrt(dt_min) * rng.standard_normal() + ) + self.tws = max(0.5, self.tws) # floor at 0.5 kts + + return self.tws, self.twd + + def state(self) -> dict: + d = super().state() + d.update({ + "dir_sigma": self.dir_sigma, + "dir_reversion": self.dir_reversion, + "tws_sigma": self.tws_sigma, + "tws_reversion": self.tws_reversion, + "tws_mean": self._tws_mean, + }) + return d + + def reset(self, tws: float | None = None, twd: float = 0.0): + super().reset(tws, twd) + if tws is not None: + self._tws_mean = tws + self._twd_mean = twd diff --git a/src/YachtMod.py b/src/YachtMod.py index 72a4f5a..ac2ccc2 100644 --- a/src/YachtMod.py +++ b/src/YachtMod.py @@ -8,13 +8,36 @@ __email__ = "M.Lauber@soton.ac.uk" import numpy as np -from src.UtilsMod import build_interp_func,json_read,json_write from scipy import interpolate +from src.UtilsMod import build_interp_func, json_read, json_write + + +def _zero_cr(fn): + """Zero residuary resistance (for appendages without Cr data).""" + return 0.0 + + class Appendage(object): def __init__(self, type, chord, area, span, vol, ce): """ - + Base class for underwater appendages (keels, rudders, bulbs). + + Parameters + ---------- + type : str + Appendage type (``"keel"``, ``"rudder"``, or ``"bulb"``). + Controls residuary resistance coefficient lookup. + chord : float + Mean chord length (m). + area : float + Planform (wetted) area (m^2). + span : float + Appendage span (m). Set to 0 for non-lifting bodies (bulbs). + vol : float + Displaced volume (m^3). Used for residuary resistance calculation. + ce : float + Centre of effort — depth below waterline (m, positive downward). """ self.type = type self.chord = chord @@ -30,15 +53,12 @@ def __init__(self, type, chord, area, span, vol, ce): self.cla = self.dclda * self.area self.teff = 1.8 * self.span # no residuary resistance - self._interp_cr = lambda fn: 0.0 + self._interp_cr = _zero_cr if self.type == "keel": self._interp_cr = build_interp_func("rrk") if self.type == "bulb": self._interp_cr = build_interp_func("rrk", i=2) - def _cl(self, leeway): - return self.dclda * np.radians(leeway) - def _cr(self, fn): return self._interp_cr(max(0.0, min(fn, 0.6))) @@ -56,6 +76,21 @@ def _print(self): class Keel(Appendage): def __init__(self, Cu=1, Cl=1, Span=0): + """ + Trapezoidal keel appendage. + + Computes area, mean chord, span, volume, and centre of effort from + the root and tip chord lengths assuming a trapezoidal planform. + + Parameters + ---------- + Cu : float, optional + Root (upper) chord length (m). Default is 1. + Cl : float, optional + Tip (lower) chord length (m). Default is 1. + Span : float, optional + Keel span (m). Default is 0. + """ self.type = "keel" self.cu = Cu self.cl = Cl @@ -70,6 +105,18 @@ def __init__(self, Cu=1, Cl=1, Span=0): class Rudder(Appendage): def __init__(self, Cu=1, Cl=1, Span=0): + """ + Trapezoidal rudder appendage. + + Parameters + ---------- + Cu : float, optional + Root (upper) chord length (m). Default is 1. + Cl : float, optional + Tip (lower) chord length (m). Default is 1. + Span : float, optional + Rudder span (m). Default is 0. + """ self.type = "rudder" self.cu = Cu self.cl = Cl @@ -88,6 +135,23 @@ def _Ksff(self, phi): class Bulb(Appendage): def __init__(self, Chord, area, vol, CG): + """ + Keel bulb appendage. + + A non-lifting body attached to the keel tip. Contributes wetted + surface area and residuary resistance but no side force. + + Parameters + ---------- + Chord : float + Bulb chord length (m). + area : float + Wetted surface area (m^2). + vol : float + Displaced volume (m^3). + CG : float + Centre of gravity depth below waterline (m). + """ self.type = "bulb" self.chord = Chord self.area = area @@ -97,20 +161,98 @@ def __init__(self, Chord, area, vol, CG): super().__init__(self.type, self.chord, self.area, 0.0, self.vol, self.ce) +class ShortKeel(Appendage): + def __init__(self, Length=1.0, Depth=0.5, Tc_ratio=0.15): + """ + Short/integrated keel appendage. + + For traditional or hull-integrated keels with low aspect ratio. + Uses the Jones low-AR lift formula instead of Prandtl correction, + higher form drag, and zero appendage residuary resistance (hull + resistance surfaces capture it). + + Parameters + ---------- + Length : float + Fore-aft keel length along hull bottom (m). + Depth : float + Keel depth below canoe body (m). + Tc_ratio : float + Average thickness-to-chord ratio. Default 0.15. + """ + self.type = "short_keel" + self.length = Length + self.depth = Depth + self.tc_ratio = Tc_ratio + self.span = Depth + self.chord = Length + self.area = Length * Depth + self.ce = -Depth / 2.0 + self.cof = 1.4 + 0.5 * Tc_ratio + self.vol = Length * Depth * Length * Tc_ratio * 0.7 + super().__init__(self.type, self.chord, self.area, self.span, self.vol, self.ce) + # Override Prandtl lift model with Jones low-AR formula + ar = self.Ar + self.dclda = np.pi * ar / 2.0 + self.cla = self.dclda * self.area + self.teff = 1.5 * self.span + + class Yacht(object): def __init__(self, Name, Lwl, Vol, Bwl, Tc, WSA, Tmax, - Amax, Mass, Loa, Boa, Ff, Fa, App=[], Sails=[]): + Amax, Mass, Loa, Boa, Ff, Fa, App=[], Sails=[], GZ=None, crew_weight=None, + roughness=150e-6, Hs=0.0, Ts=0.0, wave_direction=None): """ - Name : Name of particular design - Lwl : waterline length (m) - Vol : volume of canoe body (m^3) - Bwl : waterline beam (m) - Tc : Canoe body draft (m) - WSA : Wetted surface area (m^2) - Tmax : Maximum draft of yacht (m) - Amax : Max section area (m^2) - Mass : total mass of the yacht (kg) - App : appendages (Appendages object as list, i.e [Keel(...)] ) + Yacht hull and rig definition. + + Parameters + ---------- + Name : str + Name of the yacht design. + Lwl : float + Waterline length (m). + Vol : float + Displaced volume of the canoe body (m^3). + Bwl : float + Waterline beam (m). + Tc : float + Canoe body draft (m). + WSA : float + Wetted surface area of the canoe body (m^2). + Tmax : float + Maximum draft including keel (m). + Amax : float + Maximum cross-section area (m^2). + Mass : float + Total displacement mass including keel (kg). + Loa : float + Length overall (m). + Boa : float + Beam overall (m). + Ff : float + Freeboard height at the bow (m). + Fa : float + Freeboard height at the stern (m). + App : list of Appendage, optional + Underwater appendages (keels, rudders, bulbs). Default is []. + Sails : list of Sail, optional + Sail inventory. Default is []. + GZ : dict, optional + Righting arm curve as ``{"Heel": [...], "GZ": [...]}``. + Heel in degrees, GZ in metres. If *None*, loads from + ``righting_moment.json`` (backward compatible). + crew_weight : float, optional + Total crew weight (kg). If *None*, uses empirical formula + ``25.8 * Lwl ** 1.4262``. + roughness : float, optional + Mean hull roughness height (m). Default 150e-6 (150 μm, + new antifouling paint). Set to 0 for a smooth hull. + Hs : float, optional + Significant wave height (m). Default 0.0 (flat water). + Ts : float, optional + Modal wave period (s). Default 0.0. + wave_direction : float or None, optional + Wave direction (degrees). If None, waves align with wind. """ self.g = 9.81 self.Name = Name @@ -131,7 +273,7 @@ def __init__(self, Name, Lwl, Vol, Bwl, Tc, WSA, Tmax, self.Rm4 = 0.43 * self.tmax # standard crew weight - self.cw = 25.8 * self.l ** 1.4262 + self.cw = crew_weight if crew_weight is not None else 25.8 * self.l ** 1.4262 self.carm = 0.8 * self.bmax # must be average of rail where crew sits # rough estimate of projected area of the hull @@ -139,10 +281,19 @@ def __init__(self, Name, Lwl, Vol, Bwl, Tc, WSA, Tmax, self.cla = self.area_proj * 2 * np.pi / (1.0 + 0.5 * self.area_proj / self.tc) self.teff = 2.07 * self.tc + # hull roughness and wave parameters + self.roughness = roughness + self.Hs = Hs + self.Ts = Ts + self.wave_direction = wave_direction + # appendages object self.appendages = App self.sails = Sails + # GZ data (righting arm curve) + self._gz_data = GZ + # righting moment interpolation function self._interp_rm = self._build_rm_interp() @@ -151,7 +302,10 @@ def __init__(self, Name, Lwl, Vol, Bwl, Tc, WSA, Tmax, def _build_rm_interp(self): - a = json_read('righting_moment') + if self._gz_data is not None: + a = self._gz_data + else: + a = json_read('righting_moment') return interpolate.interp1d(np.array(a["Heel"]), np.array(a["GZ"]), kind="linear", fill_value="extrapolate") diff --git a/src/api.py b/src/api.py index 14ad2f6..56a3867 100644 --- a/src/api.py +++ b/src/api.py @@ -13,7 +13,7 @@ sys.path.append(os.path.realpath(".")) from src.SailMod import Jib, Kite, Main from src.VPPMod import VPP -from src.YachtMod import Keel, Rudder, Yacht +from src.YachtMod import Keel, Rudder, ShortKeel, Yacht app = Flask(__name__) @@ -27,17 +27,35 @@ def ping(): def data_to_vpp(data: Dict[str, Any]) -> VPP: - - keel = Keel( - Cu=float(data["keel"]["Cu"]), - Cl=float(data["keel"]["Cl"]), - Span=float(data["keel"]["Span"]) - ) + + keel_data = data["keel"] + keel_type = keel_data.get("type", "fin") + # Also detect from keys if type is missing or inconsistent + if keel_type == "short" or "Length" in keel_data: + keel = ShortKeel( + Length=float(keel_data["Length"]), + Depth=float(keel_data["Depth"]), + Tc_ratio=float(keel_data.get("Tc_ratio", 0.15)), + ) + else: + keel = Keel( + Cu=float(keel_data["Cu"]), + Cl=float(keel_data["Cl"]), + Span=float(keel_data["Span"]), + ) rudder = Rudder( - Cu=float(data["rudder"]["Cu"]), - Cl=float(data["rudder"]["Cu"]), + Cu=float(data["rudder"]["Cu"]), + Cl=float(data["rudder"]["Cu"]), Span=float(data["rudder"]["Span"]) ) + # Environment parameters + roughness = float(data.get("roughness", 150e-6)) + Hs = float(data.get("Hs", 0.0)) + Ts = float(data.get("Ts", 0.0)) + wave_direction = data.get("wave_direction") + if wave_direction is not None: + wave_direction = float(wave_direction) + yacht = Yacht( Name=data["yacht"]["Name"], Lwl=float(data["yacht"]["Lwl"]), @@ -52,6 +70,10 @@ def data_to_vpp(data: Dict[str, Any]) -> VPP: Fa=float(data["yacht"]["Fa"]), Boa=float(data["yacht"]["Boa"]), Loa=float(data["yacht"]["Loa"]), + roughness=roughness, + Hs=Hs, + Ts=Ts, + wave_direction=wave_direction, App=[keel, rudder], Sails=[ Main( @@ -60,6 +82,10 @@ def data_to_vpp(data: Dict[str, Any]) -> VPP: E=float(data["main"]["E"]), Roach=float(data["main"]["Roach"]), BAD=float(data["main"]["BAD"]), + data_source=data.get("data_source", "orc"), + cl_data=data["main"].get("cl_data"), + cd_data=data["main"].get("cd_data"), + sail_type=data["main"].get("sail_type"), ), Jib( name=data["jib"]["Name"], @@ -67,32 +93,48 @@ def data_to_vpp(data: Dict[str, Any]) -> VPP: J=float(data["jib"]["J"]), LPG=float(data["jib"]["LPG"]), HBI=float(data["jib"]["HBI"]), + data_source=data.get("data_source", "orc"), + cl_data=data["jib"].get("cl_data"), + cd_data=data["jib"].get("cd_data"), + sail_type=data["jib"].get("sail_type"), ), Kite( name=data["kite"]["Name"], area=float(data["kite"]["area"]), vce=float(data["kite"]["vce"]), + data_source=data.get("data_source", "orc"), + cl_data=data["kite"].get("cl_data"), + cd_data=data["kite"].get("cd_data"), + sail_type=data["kite"].get("sail_type"), ), ], ) - + vpp = VPP(Yacht=yacht) vpp.set_analysis( tws_range=np.array(data["tws_range"]), twa_range=np.array(data["twa_range"]), ) - return vpp - + return vpp, data.get("method", "iterative") + @app.route("/api/vpp/", methods=["POST"]) def makevppresults(): data = request.get_json() + if data is None: + return jsonify({"error": "Request body must be valid JSON."}), 400 - # TODO: Support multiple implementations of different sails: require API design - # TODO: Error handling incorrect ranges + try: + vpp, method = data_to_vpp(data) + except (KeyError, TypeError, ValueError) as e: + logging.warning("Invalid VPP input: %s", e) + return jsonify({"error": f"Invalid input: {e}"}), 400 - vpp = data_to_vpp(data) - vpp.run(verbose=True) + try: + vpp.run(verbose=True, method=method) + except Exception as e: + logging.exception("VPP simulation failed") + return jsonify({"error": f"Simulation failed: {e}"}), 500 return jsonify(vpp.results()) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..da2faa6 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,2 @@ +import matplotlib +matplotlib.use("Agg") diff --git a/tests/test_aero.py b/tests/test_aero.py new file mode 100644 index 0000000..4003109 --- /dev/null +++ b/tests/test_aero.py @@ -0,0 +1,124 @@ +"""Tests for AeroMod — wind triangle and coefficient caching.""" + +import numpy as np +import pytest + + +class TestWindTriangleCache: + """Verify that wind_triangle uses lru_cache.""" + + def test_wind_triangle_is_cached(self): + from src.AeroMod import wind_triangle + + # lru_cache-decorated functions have cache_info() + assert hasattr(wind_triangle, "cache_info"), "wind_triangle should be lru_cache-decorated" + wind_triangle.cache_clear() + wind_triangle(10.0, 90.0, 5.0) + wind_triangle(10.0, 90.0, 5.0) + info = wind_triangle.cache_info() + assert info.hits == 1 + assert info.misses == 1 + + def test_cache_returns_same_result(self): + from src.AeroMod import wind_triangle + + wind_triangle.cache_clear() + r1 = wind_triangle(10.0, 45.0, 6.0) + r2 = wind_triangle(10.0, 45.0, 6.0) + assert r1 == r2 + + +class TestSailCoefficientCalls: + """Verify that _get_coeffs doesn't call sail.cl() redundantly.""" + + def test_get_coeffs_calls_cl_once_per_sail(self): + """Each sail's cl(awa) should be called at most once per _get_coeffs().""" + from unittest.mock import MagicMock, patch + from src.AeroMod import AeroMod + + # Create a mock yacht with minimal structure + mock_sail = MagicMock() + mock_sail.cl = MagicMock(return_value=1.0) + mock_sail.cd = MagicMock(return_value=0.1) + mock_sail.area = 30.0 + mock_sail.bk = 1.0 + mock_sail.kp = 1.0 + mock_sail.type = "main" + + aero = object.__new__(AeroMod) + aero.sails = [mock_sail] + aero.area = 30.0 + aero.awa = 30.0 + aero.flat = 1.0 + aero.fcdmult = lambda flat: 1.0 + + # Provide _heff so CE computation works + aero._heff = lambda awa: 10.0 + + aero._get_coeffs() + + # cl should be called exactly once per sail (not twice as before) + assert mock_sail.cl.call_count == 1, ( + f"sail.cl() called {mock_sail.cl.call_count} times, expected 1" + ) + assert mock_sail.cd.call_count == 1, ( + f"sail.cd() called {mock_sail.cd.call_count} times, expected 1" + ) + + +class TestWindTriangle: + """Verify analytical wind triangle: given TWS, TWA, VB → AWS, AWA.""" + + @pytest.mark.parametrize( + "tws, twa, vb, expected_aws, expected_awa", + [ + # Beam reach: TWA=90°, VB=5, TWS=10 + # AWS = sqrt(10² + 5² + 2·10·5·cos(90°)) = sqrt(125) ≈ 11.18 + # AWA = arccos((10·cos(90°) + 5) / 11.18) = arccos(5/11.18) ≈ 63.43° + (10.0, 90.0, 5.0, np.sqrt(125), np.degrees(np.arccos(5.0 / np.sqrt(125)))), + # Close-hauled: TWA=45°, VB=6, TWS=12 + ( + 12.0, + 45.0, + 6.0, + np.sqrt(12**2 + 6**2 + 2 * 12 * 6 * np.cos(np.radians(45))), + np.degrees( + np.arccos( + (12 * np.cos(np.radians(45)) + 6) + / np.sqrt(12**2 + 6**2 + 2 * 12 * 6 * np.cos(np.radians(45))) + ) + ), + ), + # Dead downwind: TWA=180°, VB=4, TWS=10 + # AWS = sqrt(100 + 16 - 80) = sqrt(36) = 6 + # AWA = arccos((10·cos(180°) + 4) / 6) = arccos(-6/6) = 180° + (10.0, 180.0, 4.0, 6.0, 180.0), + # No boat speed: VB=0 → AWA=TWA, AWS=TWS + (10.0, 90.0, 0.0, 10.0, 90.0), + # Boat speed equals TWS, broad reach + (8.0, 120.0, 8.0, + np.sqrt(8**2 + 8**2 + 2 * 8 * 8 * np.cos(np.radians(120))), + np.degrees( + np.arccos( + (8 * np.cos(np.radians(120)) + 8) + / np.sqrt(8**2 + 8**2 + 2 * 8 * 8 * np.cos(np.radians(120))) + ) + )), + ], + ids=["beam_reach", "close_hauled", "dead_downwind", "no_boat_speed", "broad_reach"], + ) + def test_wind_triangle_analytical(self, tws, twa, vb, expected_aws, expected_awa): + """AWA and AWS must match law-of-cosines closed-form solution.""" + from src.AeroMod import wind_triangle + + awa, aws = wind_triangle(tws, twa, vb) + assert aws == pytest.approx(expected_aws, abs=1e-10) + assert awa == pytest.approx(expected_awa, abs=1e-10) + + def test_no_fsolve_import_used(self): + """The analytical path must not import or call fsolve.""" + from src.AeroMod import wind_triangle + import inspect + + source = inspect.getsource(wind_triangle) + assert "fsolve" not in source diff --git a/tests/test_api.py b/tests/test_api.py index 53aab60..f1e068a 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -4,6 +4,9 @@ from src.api import app +HEADERS = {"content-type": "application/json", "Accept-Charset": "UTF-8"} + + def test_ping_route(): client = app.test_client() response = client.get("/ping") @@ -11,29 +14,27 @@ def test_ping_route(): assert response.data.decode("utf-8") == "Pong! The server is up and running." -def make_yd41(): - yacht = dict( - { - "Name": "YD41", - "Lwl": 11.90, - "Vol": 6.05, - "Bwl": 3.18, - "Tc": 0.4, - "WSA": 28.20, - "Tmax": 2.30, - "Amax": 1.051, - "Mass": 6500, - "Ff": 1.5, - "Fa": 1.5, - "Boa": 4.2, - "Loa": 12.5, - } - ) - keel = dict({"Cu": 1.00, "Cl": 0.78, "Span": 1.90}) - rudder = dict({"Cu": 0.48, "Cl": 0.22, "Span": 1.15}) - main = dict({"Name": "MN1", "P": 16.60, "E": 5.60, "Roach": 0.1, "BAD": 1.0}) - jib = dict({"Name": "J1", "I": 16.20, "J": 5.10, "LPG": 5.40, "HBI": 1.8}) - kite = dict({"Name": "A2", "area": 150.0, "vce": 9.55}) +def make_yd41(**overrides): + yacht = { + "Name": "YD41", + "Lwl": 11.90, + "Vol": 6.05, + "Bwl": 3.18, + "Tc": 0.4, + "WSA": 28.20, + "Tmax": 2.30, + "Amax": 1.051, + "Mass": 6500, + "Ff": 1.5, + "Fa": 1.5, + "Boa": 4.2, + "Loa": 12.5, + } + keel = {"Cu": 1.00, "Cl": 0.78, "Span": 1.90} + rudder = {"Cu": 0.48, "Cl": 0.22, "Span": 1.15} + main = {"Name": "MN1", "P": 16.60, "E": 5.60, "Roach": 0.1, "BAD": 1.0} + jib = {"Name": "J1", "I": 16.20, "J": 5.10, "LPG": 5.40, "HBI": 1.8} + kite = {"Name": "A2", "area": 150.0, "vce": 9.55} tws_range = np.arange(4.0, 7.0, 2.0).tolist() twa_range = np.linspace(30.0, 180.0, 5).tolist() @@ -48,17 +49,156 @@ def make_yd41(): "tws_range": tws_range, "twa_range": twa_range, } + d.update(overrides) return d +def post_vpp(data): + client = app.test_client() + return client.post("/api/vpp/", data=json.dumps(data), headers=HEADERS) + + def test_vpp_simulation(): d = make_yd41() + response = post_vpp(d) + assert response.status_code == 200 + + +def test_empty_tws_range_returns_400(): + d = make_yd41(tws_range=[]) + response = post_vpp(d) + assert response.status_code == 400 + assert "empty" in response.json["error"].lower() + + +def test_empty_twa_range_returns_400(): + d = make_yd41(twa_range=[]) + response = post_vpp(d) + assert response.status_code == 400 + assert "empty" in response.json["error"].lower() + + +def test_tws_out_of_range_returns_400(): + d = make_yd41(tws_range=[1.0, 5.0]) + response = post_vpp(d) + assert response.status_code == 400 + assert "outside valid bounds" in response.json["error"] + - json_string = json.dumps(d) - headers = {"content-type": "application/json", "Accept-Charset": "UTF-8"} +def test_tws_above_range_returns_400(): + d = make_yd41(tws_range=[10.0, 40.0]) + response = post_vpp(d) + assert response.status_code == 400 + assert "outside valid bounds" in response.json["error"] + +def test_twa_out_of_range_returns_400(): + d = make_yd41(twa_range=[-10.0, 90.0]) + response = post_vpp(d) + assert response.status_code == 400 + assert "outside valid bounds" in response.json["error"] + + +def test_missing_field_returns_400(): + d = make_yd41() + del d["keel"] + response = post_vpp(d) + assert response.status_code == 400 + assert "error" in response.json + + +def test_invalid_json_returns_400(): client = app.test_client() - response = client.post("/api/vpp/", data=json_string, headers=headers) + response = client.post("/api/vpp/", data="not json", headers=HEADERS) + assert response.status_code == 400 + + +def test_short_keel_with_type_field(): + """Short keel payload with explicit type='short' should succeed.""" + d = make_yd41() + d["keel"] = {"type": "short", "Length": 1.2, "Depth": 0.90, "Tc_ratio": 0.15} + response = post_vpp(d) + assert response.status_code == 200 + + +def test_short_keel_without_type_field(): + """Short keel payload detected from keys alone (no type field).""" + d = make_yd41() + d["keel"] = {"Length": 1.2, "Depth": 0.90, "Tc_ratio": 0.15} + response = post_vpp(d) + assert response.status_code == 200 + + +def test_fin_keel_with_type_field(): + """Fin keel payload with explicit type='fin' should succeed.""" + d = make_yd41() + d["keel"] = {"type": "fin", "Cu": 1.00, "Cl": 0.78, "Span": 1.90} + response = post_vpp(d) + assert response.status_code == 200 + + +def test_api_5dof_method(): + """API accepts method='5dof' and returns results.""" + d = make_yd41() + d["method"] = "5dof" + response = post_vpp(d) + assert response.status_code == 200 + results = np.array(response.json["results"]) + assert np.any(results[:, :, :, 0] > 0), "5-DOF should produce non-zero speeds" + + +def test_api_data_source(): + """API accepts data_source parameter.""" + d = make_yd41() + d["data_source"] = "orc" + response = post_vpp(d) + assert response.status_code == 200 + + +def test_api_sail_type_sym_kite(): + """API accepts sail_type in kite section and produces results.""" + d = make_yd41() + d["kite"]["sail_type"] = "sym_kite" + response = post_vpp(d) + assert response.status_code == 200 + results = np.array(response.json["results"]) + assert np.any(results[:, :, :, 0] > 0) + + +def test_api_sail_type_low_performance(): + """API accepts low-performance sail_type for main and jib.""" + d = make_yd41() + d["main"]["sail_type"] = "main_low" + d["jib"]["sail_type"] = "jib_low" + response = post_vpp(d) + assert response.status_code == 200 + results = np.array(response.json["results"]) + assert np.any(results[:, :, :, 0] > 0) + + +def test_api_roughness(): + """API accepts roughness parameter and rough hull is slower.""" + d_smooth = make_yd41(roughness=0.0) + d_rough = make_yd41(roughness=500e-6) + r_smooth = post_vpp(d_smooth) + r_rough = post_vpp(d_rough) + assert r_smooth.status_code == 200 + assert r_rough.status_code == 200 + speeds_smooth = np.array(r_smooth.json["results"])[:, :, :, 0].max() + speeds_rough = np.array(r_rough.json["results"])[:, :, :, 0].max() + assert speeds_smooth > speeds_rough, "Rough hull should be slower" + + +def test_api_wave_params(): + """API accepts Hs and Ts parameters.""" + d = make_yd41(Hs=1.0, Ts=6.0) + response = post_vpp(d) + assert response.status_code == 200 + +def test_api_wave_direction(): + """API accepts wave_direction parameter.""" + d = make_yd41(Hs=1.0, Ts=6.0, wave_direction=45.0) + response = post_vpp(d) assert response.status_code == 200 diff --git a/tests/test_daring.py b/tests/test_daring.py new file mode 100644 index 0000000..1c9c775 --- /dev/null +++ b/tests/test_daring.py @@ -0,0 +1,160 @@ +import os + +import numpy as np +import pytest +from src.SailMod import Jib, Kite, Main +from src.VPPMod import VPP +from src.YachtMod import Keel, Rudder, ShortKeel, Yacht + + +# Daring GZ curve (estimated for classic 5.5m, ~50% ballast ratio, GM ~0.70m) +DARING_GZ = { + "Heel": [0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0], + "GZ": [0.000, 0.120, 0.230, 0.310, 0.350, 0.330, 0.260], +} + + +def return_daring(): + """Create Daring yacht with estimated 5.5m class parameters. + + Published values (classicsailboats.org / Cowes Classics): + LOA=9.90m, LWL=7.01m, Beam=1.98m, Draft=1.35m, + Displacement=2000kg, Upwind SA=29.73m² + + Estimated values are documented in docs/plans/2026-02-27-daring-vpp.md + """ + return Yacht( + Name="Daring", + Lwl=7.01, + Vol=1.95, + Bwl=1.70, + Tc=0.45, + WSA=11.5, + Tmax=1.35, + Amax=0.38, + Mass=2000, + Loa=9.90, + Boa=1.98, + Ff=0.75, + Fa=0.55, + App=[ + ShortKeel(Length=1.2, Depth=0.90, Tc_ratio=0.15), + Rudder(Cu=0.32, Cl=0.18, Span=0.75), + ], + Sails=[ + Main("MN1", P=10.80, E=3.30, Roach=0.1, BAD=0.80), + Jib("J1", I=8.50, J=2.70, LPG=2.70, HBI=0.50), + Kite("S1", area=50.0, vce=4.50), + ], + GZ=DARING_GZ, + crew_weight=240.0, + ) + + +def test_daring_vpp_runs(): + """Daring VPP should solve without errors across a range of conditions.""" + daring = return_daring() + vpp = VPP(Yacht=daring) + vpp.set_analysis( + tws_range=np.arange(6.0, 14.0, 2.0), + twa_range=np.linspace(35.0, 175.0, 15), + ) + vpp.run(verbose=False) + results = vpp.results() + assert results["name"] == "Daring" + assert len(results["tws"]) == 4 + + +def test_daring_boat_speed_sanity(): + """Daring should produce reasonable speeds: 3-7 knots in moderate wind.""" + daring = return_daring() + vpp = VPP(Yacht=daring) + vpp.set_analysis( + tws_range=np.array([10.0]), + twa_range=np.linspace(40.0, 160.0, 13), + ) + vpp.run(verbose=False) + results = np.array(vpp.results()["results"]) + max_speed = np.max(results[:, :, :, 0]) + assert 3.0 < max_speed < 7.0, f"Max speed {max_speed:.1f} kts outside expected range" + + +def test_daring_heel_limited(): + """With phi_max=30, no heel angle should exceed 30 degrees.""" + daring = return_daring() + vpp = VPP(Yacht=daring) + vpp.set_analysis( + tws_range=np.arange(8.0, 22.0, 4.0), + twa_range=np.linspace(35.0, 175.0, 15), + phi_max=30.0, + ) + vpp.run(verbose=False) + results = np.array(vpp.results()["results"]) + heel_angles = results[:, :, :, 1] + max_heel = np.max(heel_angles) + assert max_heel <= 31.0, f"Max heel {max_heel:.1f} exceeds phi_max=30" + + +def test_daring_depower_values_stored(): + """At high TWS with heel limit, flat/red should be < 1.0.""" + daring = return_daring() + vpp = VPP(Yacht=daring) + vpp.set_analysis( + tws_range=np.array([18.0]), + twa_range=np.array([60.0]), + phi_max=25.0, + ) + vpp.run(verbose=False) + results = np.array(vpp.results()["results"]) + flat = results[0, 0, 0, 3] + assert flat < 1.0, f"Expected depowering at 18 kts / 60 TWA, got flat={flat}" + + +def test_daring_polars_saved(tmp_path): + """Daring should produce polar plot and sail chart files.""" + daring = return_daring() + vpp = VPP(Yacht=daring) + vpp.set_analysis( + tws_range=np.arange(6.0, 14.0, 2.0), + twa_range=np.linspace(35.0, 175.0, 15), + ) + vpp.run(verbose=False) + + polar_path = str(tmp_path / "daring_polar.png") + sail_path = str(tmp_path / "daring_sail.png") + vpp.polar(3, True, fname=polar_path) + vpp.SailChart(True, fname=sail_path) + assert os.path.exists(polar_path), "Polar plot was not created" + assert os.path.exists(sail_path), "Sail chart was not created" + + +# --- ShortKeel-specific unit tests --- + +def test_short_keel_jones_lift_formula(): + """ShortKeel dclda should match Jones low-AR formula: pi*AR/2.""" + sk = ShortKeel(Length=1.2, Depth=0.90, Tc_ratio=0.15) + ar = sk.span / sk.area # = 0.90 / (1.2 * 0.90) + expected = np.pi * ar / 2.0 + assert abs(sk.dclda - expected) < 1e-10, f"dclda={sk.dclda}, expected={expected}" + + +def test_short_keel_no_appendage_residuary(): + """ShortKeel should return zero appendage residuary resistance.""" + sk = ShortKeel(Length=1.2, Depth=0.90, Tc_ratio=0.15) + for fn in [0.0, 0.2, 0.4, 0.6]: + assert sk._cr(fn) == 0.0, f"_cr({fn}) should be 0.0 for short_keel" + + +def test_short_keel_lower_lift_than_fin(): + """A ShortKeel with equivalent area should produce less lift than a fin Keel.""" + sk = ShortKeel(Length=1.2, Depth=0.90, Tc_ratio=0.15) + # Fin keel with similar area: chord ~0.575, span 0.90 -> area ~0.5175 + # Use root/tip that give similar area to short keel + fk = Keel(Cu=1.40, Cl=1.00, Span=0.90) # area = 1.08, same span + # At the same leeway, short keel should generate less lift coefficient + leeway_rad = np.radians(5.0) + cl_short = sk.dclda * leeway_rad + cl_fin = fk.dclda * leeway_rad + assert cl_short < cl_fin, ( + f"ShortKeel cl={cl_short:.4f} should be less than Keel cl={cl_fin:.4f}" + ) diff --git a/tests/test_hydro.py b/tests/test_hydro.py new file mode 100644 index 0000000..2c57a37 --- /dev/null +++ b/tests/test_hydro.py @@ -0,0 +1,129 @@ +"""Tests for HydroMod — roughness and wave resistance.""" + +import numpy as np + +from src.HydroMod import HydroMod +from src.SailMod import Jib, Kite, Main +from src.YachtMod import Keel, Rudder, Yacht + + +def _make_yacht(**kwargs): + """Create a YD41-like yacht with optional overrides.""" + defaults = dict( + Name="TestYacht", + Lwl=11.90, + Vol=6.05, + Bwl=3.18, + Tc=0.4, + WSA=28.20, + Tmax=2.30, + Amax=1.051, + Mass=6500, + Ff=1.5, + Fa=1.5, + Boa=4.2, + Loa=12.5, + App=[Keel(Cu=1.00, Cl=0.78, Span=1.90), Rudder(Cu=0.48, Cl=0.22, Span=1.15)], + Sails=[ + Main("MN1", P=16.60, E=5.60, Roach=0.1, BAD=1.0), + Jib("J1", I=16.20, J=5.10, LPG=5.40, HBI=1.8), + Kite("A2", area=150.0, vce=9.55), + ], + ) + defaults.update(kwargs) + return Yacht(**defaults) + + +class TestRoughness: + def test_roughness_increases_resistance(self): + """Higher roughness should increase total resistance.""" + yacht_smooth = _make_yacht(roughness=0.0) + yacht_rough = _make_yacht(roughness=300e-6) + + hydro_smooth = HydroMod(yacht_smooth) + hydro_rough = HydroMod(yacht_rough) + + Fx_smooth, _, _ = hydro_smooth.update(3.0, 5.0, 2.0) + Fx_rough, _, _ = hydro_rough.update(3.0, 5.0, 2.0) + + assert Fx_rough > Fx_smooth, "Rough hull should have more resistance" + + def test_zero_roughness_gives_smooth_cf(self): + """Zero roughness should give bare ITTC 1957 Cf (no allowance).""" + yacht = _make_yacht(roughness=0.0) + hydro = HydroMod(yacht) + hydro.vb = 3.0 + cf = hydro._cf(hydro.l) + + # ITTC 1957 only + Re = 3.0 * hydro.l / hydro.nu + cf_expected = 0.066 * (np.log10(Re) - 2.03) ** (-2) + assert abs(cf - cf_expected) < 1e-10 + + def test_default_roughness_adds_allowance(self): + """Default 150 μm roughness should add positive Cf allowance.""" + yacht = _make_yacht(roughness=150e-6) + hydro = HydroMod(yacht) + hydro.vb = 3.0 + cf_rough = hydro._cf(hydro.l) + + yacht_smooth = _make_yacht(roughness=0.0) + hydro_smooth = HydroMod(yacht_smooth) + hydro_smooth.vb = 3.0 + cf_smooth = hydro_smooth._cf(hydro_smooth.l) + + assert cf_rough > cf_smooth + + +class TestWaveResistance: + def test_no_waves_zero_resistance(self): + """Hs=0 should add zero wave resistance.""" + yacht = _make_yacht(Hs=0.0, Ts=0.0) + hydro = HydroMod(yacht) + raw = hydro._added_resistance_waves(90.0) + assert raw == 0.0 + + def test_waves_add_resistance(self): + """Hs > 0 should produce positive added resistance upwind.""" + yacht = _make_yacht(Hs=1.0, Ts=6.0) + hydro = HydroMod(yacht) + hydro.vb = 3.0 + raw = hydro._added_resistance_waves(45.0) + assert raw > 0.0, "Waves should add resistance upwind" + + def test_wave_resistance_increases_with_Hs(self): + """Larger waves should produce more resistance.""" + yacht_small = _make_yacht(Hs=0.5, Ts=6.0) + yacht_large = _make_yacht(Hs=1.5, Ts=6.0) + + hydro_small = HydroMod(yacht_small) + hydro_large = HydroMod(yacht_large) + hydro_small.vb = 3.0 + hydro_large.vb = 3.0 + + raw_small = hydro_small._added_resistance_waves(45.0) + raw_large = hydro_large._added_resistance_waves(45.0) + + assert raw_large > raw_small + + def test_wave_resistance_higher_upwind(self): + """Upwind wave resistance should exceed downwind.""" + yacht = _make_yacht(Hs=1.0, Ts=6.0) + hydro = HydroMod(yacht) + hydro.vb = 3.0 + + raw_upwind = hydro._added_resistance_waves(30.0) + raw_downwind = hydro._added_resistance_waves(150.0) + + assert raw_upwind > raw_downwind + + def test_flat_water_unchanged(self): + """VPP output with Hs=0 should match no-wave behaviour.""" + yacht = _make_yacht(Hs=0.0, Ts=0.0) + hydro = HydroMod(yacht) + + Fx_flat, Fy_flat, Mx_flat = hydro.update(3.0, 5.0, 2.0, twa=45.0) + Fx_no_twa, Fy_no_twa, Mx_no_twa = hydro.update(3.0, 5.0, 2.0, twa=0.0) + + # With no waves, twa shouldn't matter for resistance + assert abs(Fx_flat - Fx_no_twa) < 1e-6 diff --git a/tests/test_race.py b/tests/test_race.py new file mode 100644 index 0000000..65cc965 --- /dev/null +++ b/tests/test_race.py @@ -0,0 +1,449 @@ +"""Tests for RaceMod — match racing simulation engine.""" + +import json +import os + +import numpy as np + +from src.RaceMod import Boat, Race +from src.WindMod import BrownianWind, ConstantWind, MeanRevertingWind + + +def _constant_polar(speed=6.0): + """Return a polar that gives constant speed at any TWS/TWA.""" + return lambda tws, twa: speed + + +def _realistic_polar(scale=1.0): + """Return a polar that varies with TWA like a real boat. + + Peak speed ~7*scale at TWA=120, drops to ~4*scale at TWA=30. + """ + def polar(tws, twa): + twa_rad = np.radians(np.clip(twa, 25, 180)) + return scale * (3.5 + 3.5 * np.sin(twa_rad)) + return polar + + +def _load_cached_polar(name="Daring_5.5m"): + """Load pre-computed polar from dat/ directory.""" + path = os.path.join("dat", f"polars_{name}.json") + with open(path) as f: + data = json.load(f) + tws = np.array(data["tws"]) + twa = np.array(data["twa"]) + results = np.array(data["results"]) + return Race.build_polar_interp(tws, twa, results) + + +class TestSingleRace: + def test_single_race_returns_times(self): + """run_single returns dict with expected keys and positive times.""" + race = Race(_constant_polar(6.0), _constant_polar(6.0), tws=10.0, + leg_distance=0.2, n_legs=1, wind_sigma=0.0) + result = race.run_single(seed=42) + assert "time_A" in result + assert "time_B" in result + assert result["time_A"] > 0 + assert result["time_B"] > 0 + + def test_single_race_has_traces(self): + """run_single returns trace lists.""" + race = Race(_constant_polar(6.0), _constant_polar(6.0), tws=10.0, + leg_distance=0.2, n_legs=1, wind_sigma=0.0) + result = race.run_single(seed=42) + assert isinstance(result["trace_A"], list) + assert isinstance(result["trace_B"], list) + assert len(result["trace_A"]) > 0 + + +class TestIdenticalBoats: + def test_identical_boats_even_odds(self): + """Same polar should give roughly 50% win rate with realistic polars.""" + polar = _realistic_polar(1.0) + race = Race(polar, polar, tws=10.0, + leg_distance=0.3, n_legs=1, wind_sigma=3.0) + mc = race.run_monte_carlo(n_runs=100) + total_decided = mc["wins_A"] + mc["wins_B"] + if total_decided == 0: + # All ties — still fair, pass the test + return + win_pct_A = mc["wins_A"] / total_decided + # Should be roughly 50% ± 20% (generous for stochastic test) + assert 0.20 < win_pct_A < 0.80, ( + f"Win% A = {win_pct_A:.0%} ({mc['wins_A']}/{total_decided}), expected ~50%" + ) + + +class TestFasterBoatWins: + def test_faster_boat_wins_majority(self): + """A faster boat should win more than 60% of races.""" + fast = _realistic_polar(1.4) + slow = _realistic_polar(1.0) + race = Race(fast, slow, tws=10.0, + leg_distance=0.3, n_legs=1, wind_sigma=1.0) + mc = race.run_monte_carlo(n_runs=200) + assert mc["wins_A"] > 100, f"Fast boat won only {mc['wins_A']}/200" + + +class TestTackPenalty: + def test_tack_penalty_affects_result(self): + """Higher tack penalty should increase elapsed time.""" + low_penalty = Race(_constant_polar(6.0), _constant_polar(6.0), tws=10.0, + leg_distance=0.3, n_legs=1, tack_penalty=2.0, + wind_sigma=0.0) + high_penalty = Race(_constant_polar(6.0), _constant_polar(6.0), tws=10.0, + leg_distance=0.3, n_legs=1, tack_penalty=30.0, + wind_sigma=0.0) + r_low = low_penalty.run_single(seed=42) + r_high = high_penalty.run_single(seed=42) + # Higher penalty → more elapsed time (or at least not less) + avg_low = (r_low["time_A"] + r_low["time_B"]) / 2 + avg_high = (r_high["time_A"] + r_high["time_B"]) / 2 + assert avg_high >= avg_low + + +class TestCurrent: + def test_current_affects_leg_time(self): + """Favourable current should reduce elapsed time.""" + no_current = Race(_constant_polar(6.0), _constant_polar(6.0), tws=10.0, + leg_distance=0.3, n_legs=1, current_speed=0.0, + wind_sigma=0.0) + with_current = Race(_constant_polar(6.0), _constant_polar(6.0), tws=10.0, + leg_distance=0.3, n_legs=1, current_speed=1.0, + current_dir=0.0, wind_sigma=0.0) + r_no = no_current.run_single(seed=42) + r_cur = with_current.run_single(seed=42) + # Upwind current (dir=0 means current going upwind direction) + # should affect leg times + assert r_no["time_A"] != r_cur["time_A"] + + +class TestWindShadow: + def test_wind_shadow_detection(self): + """Boat directly downwind within 2-10 boat lengths is in shadow.""" + race = Race(_constant_polar(), _constant_polar(), tws=10.0) + ahead = Boat(_constant_polar(), "ahead", boat_length=12.0) + behind = Boat(_constant_polar(), "behind", boat_length=12.0) + + # Place behind directly downwind (wind from y+ direction, wind_dir=0) + # 50m behind → > 2*12=24m min, < 10*12=120m max + ahead.x = 0.0 + ahead.y = 100.0 + behind.x = 0.0 + behind.y = 50.0 + + assert race._is_in_shadow(ahead, behind, wind_dir=0.0) + + def test_no_shadow_when_far(self): + """Boat far downwind is not in shadow.""" + race = Race(_constant_polar(), _constant_polar(), tws=10.0) + ahead = Boat(_constant_polar(), "ahead", boat_length=12.0) + behind = Boat(_constant_polar(), "behind", boat_length=12.0) + + ahead.x = 0.0 + ahead.y = 100.0 + behind.x = 0.0 + behind.y = -100.0 # 200m behind, > 10 * 12 = 120m + + assert not race._is_in_shadow(ahead, behind, wind_dir=0.0) + + def test_no_shadow_when_abeam(self): + """Boat abeam (perpendicular) is not in shadow.""" + race = Race(_constant_polar(), _constant_polar(), tws=10.0) + ahead = Boat(_constant_polar(), "ahead", boat_length=12.0) + behind = Boat(_constant_polar(), "behind", boat_length=12.0) + + ahead.x = 0.0 + ahead.y = 100.0 + behind.x = 200.0 + behind.y = 100.0 + + assert not race._is_in_shadow(ahead, behind, wind_dir=0.0) + + +class TestMonteCarlo: + def test_monte_carlo_output_format(self): + """Monte Carlo returns expected keys and counts.""" + race = Race(_constant_polar(6.0), _constant_polar(6.0), tws=10.0, + leg_distance=0.2, n_legs=1, wind_sigma=1.0) + mc = race.run_monte_carlo(n_runs=10) + assert "wins_A" in mc + assert "wins_B" in mc + assert "deltas" in mc + assert "mean_delta" in mc + assert "traces" in mc + assert len(mc["deltas"]) == 10 + assert mc["wins_A"] + mc["wins_B"] <= 10 # ties possible + + +class TestPolarInterp: + def test_build_polar_interp(self): + """build_polar_interp creates callable that returns speeds.""" + tws = np.array([2.0, 5.0]) * 0.5144 # m/s + twa = np.array([30.0, 90.0, 150.0]) + # shape (2, 3, 1, 5) — 2 TWS, 3 TWA, 1 sail, 5 outputs + results = np.zeros((2, 3, 1, 5)) + results[:, :, 0, 0] = [[3.0, 5.0, 4.0], [4.0, 7.0, 6.0]] + + polar = Race.build_polar_interp(tws, twa, results) + # At TWS=5 kts, TWA=90° → should be ~7 kts + speed = polar(5.0, 90.0) + assert abs(speed - 7.0) < 0.1 + + def test_polar_interp_out_of_bounds(self): + """Out-of-bounds queries return 0.""" + tws = np.array([4.0, 8.0]) * 0.5144 + twa = np.array([30.0, 90.0]) + results = np.zeros((2, 2, 1, 5)) + results[:, :, 0, 0] = [[3.0, 5.0], [4.0, 7.0]] + + polar = Race.build_polar_interp(tws, twa, results) + assert polar(100.0, 90.0) == 0.0 # way out of range + + +class TestTrimNoise: + def test_trim_noise_breaks_ties(self): + """With trim noise, identical boats should sometimes produce different times.""" + race = Race(_realistic_polar(1.0), _realistic_polar(1.0), tws=10.0, + leg_distance=0.3, n_legs=1, wind_sigma=2.0, + trim_sigma=0.03) + mc = race.run_monte_carlo(n_runs=50) + decided = mc["wins_A"] + mc["wins_B"] + assert decided > 0, "Trim noise should break ties" + + def test_zero_trim_sigma_no_noise(self): + """trim_sigma=0 should behave identically to no noise.""" + race = Race(_constant_polar(6.0), _constant_polar(6.0), tws=10.0, + leg_distance=0.2, n_legs=1, + wind_model=ConstantWind(tws=10.0), + trim_sigma=0.0) + r1 = race.run_single(seed=42) + r2 = race.run_single(seed=42) + assert r1["time_A"] == r2["time_A"] + + +class TestPenaltyVariance: + def test_penalty_std_varies_times(self): + """Non-zero penalty std should produce different race times across seeds.""" + race = Race(_realistic_polar(1.0), _realistic_polar(1.0), tws=10.0, + leg_distance=0.3, n_legs=1, wind_sigma=2.0, + tack_penalty_std=3.0, gybe_penalty_std=2.0) + times = [race.run_single(seed=i)["time_A"] for i in range(10)] + assert len(set(times)) > 1, "Penalty variance should produce varied times" + + +class TestWindModels: + def test_constant_wind_model(self): + """Race with ConstantWind should work and produce no randomness from wind.""" + model = ConstantWind(tws=10.0) + race = Race(_constant_polar(6.0), _constant_polar(6.0), tws=10.0, + leg_distance=0.2, n_legs=1, wind_model=model) + result = race.run_single(seed=42) + assert result["time_A"] > 0 + + def test_mean_reverting_wind_model(self): + """Race with MeanRevertingWind should work.""" + model = MeanRevertingWind(tws=10.0, dir_sigma=3.0, tws_sigma=0.5) + race = Race(_realistic_polar(1.0), _realistic_polar(1.0), tws=10.0, + leg_distance=0.3, n_legs=1, wind_model=model) + result = race.run_single(seed=42) + assert result["time_A"] > 0 + + +class TestTwsSweep: + def test_tws_sweep_returns_expected_keys(self): + """run_tws_sweep returns dict with tws_values, win_pct_A, win_pct_B.""" + fast = _realistic_polar(1.2) + slow = _realistic_polar(1.0) + race = Race(fast, slow, tws=10.0, + leg_distance=0.2, n_legs=1, wind_sigma=1.0) + result = race.run_tws_sweep( + tws_range=np.array([6.0, 10.0, 14.0]), + n_runs=10, + ) + assert "tws_values" in result + assert "win_pct_A" in result + assert "win_pct_B" in result + assert len(result["tws_values"]) == 3 + assert len(result["win_pct_A"]) == 3 + assert len(result["win_pct_B"]) == 3 + + def test_tws_sweep_probabilities_valid(self): + """Win percentages should be between 0 and 1 and sum to <= 1.""" + polar = _realistic_polar(1.0) + race = Race(polar, polar, tws=10.0, + leg_distance=0.2, n_legs=1, wind_sigma=2.0) + result = race.run_tws_sweep( + tws_range=np.array([8.0, 12.0]), + n_runs=20, + ) + for a, b in zip(result["win_pct_A"], result["win_pct_B"]): + assert 0.0 <= a <= 1.0 + assert 0.0 <= b <= 1.0 + assert a + b <= 1.0 + 1e-9 # allow float rounding + + def test_tws_sweep_faster_boat_dominates(self): + """Faster boat should win majority at all wind speeds.""" + fast = _realistic_polar(1.5) + slow = _realistic_polar(1.0) + race = Race(fast, slow, tws=10.0, + leg_distance=0.3, n_legs=1, wind_sigma=1.0) + result = race.run_tws_sweep( + tws_range=np.array([8.0, 12.0]), + n_runs=50, + ) + for pct in result["win_pct_A"]: + assert pct > 0.5, f"Fast boat should win >50%, got {pct:.0%}" + + +class TestLegBreakdown: + def test_single_race_has_leg_times(self): + """run_single returns per-leg elapsed times.""" + race = Race(_realistic_polar(1.0), _realistic_polar(1.0), tws=10.0, + leg_distance=0.3, n_legs=2, wind_sigma=1.0) + result = race.run_single(seed=42) + assert "leg_times_A" in result + assert "leg_times_B" in result + # 2 leg pairs = 4 legs (up, down, up, down) + assert len(result["leg_times_A"]) == 4 + assert len(result["leg_times_B"]) == 4 + # All leg times should be positive + assert all(t > 0 for t in result["leg_times_A"]) + assert all(t > 0 for t in result["leg_times_B"]) + + def test_leg_times_sum_to_total(self): + """Per-leg times should approximately sum to total elapsed time.""" + race = Race(_realistic_polar(1.0), _realistic_polar(1.0), tws=10.0, + leg_distance=0.3, n_legs=1, wind_sigma=1.0) + result = race.run_single(seed=42) + # Sum of leg times should be close to total time + # (may differ slightly due to start offset and rounding) + sum_A = sum(result["leg_times_A"]) + assert abs(sum_A - result["time_A"]) < 2.0, ( + f"Leg times sum {sum_A:.1f} != total {result['time_A']:.1f}" + ) + + def test_leg_types_alternate(self): + """Leg types should alternate upwind/downwind.""" + race = Race(_realistic_polar(1.0), _realistic_polar(1.0), tws=10.0, + leg_distance=0.3, n_legs=2, wind_sigma=0.0) + result = race.run_single(seed=42) + assert result["leg_types"] == ["upwind", "downwind", "upwind", "downwind"] + + def test_monte_carlo_has_leg_stats(self): + """Monte carlo should include per-leg aggregate stats.""" + race = Race(_realistic_polar(1.2), _realistic_polar(1.0), tws=10.0, + leg_distance=0.3, n_legs=1, wind_sigma=1.0) + mc = race.run_monte_carlo(n_runs=20) + assert "leg_stats" in mc + assert len(mc["leg_stats"]) == 2 # 1 pair = 2 legs + for stat in mc["leg_stats"]: + assert "leg_type" in stat + assert "mean_delta" in stat + assert "a_wins" in stat + + +class TestTacticalStats: + def test_single_race_has_tactical_stats(self): + """run_single returns tactical statistics.""" + race = Race(_realistic_polar(1.2), _realistic_polar(1.0), tws=10.0, + leg_distance=0.3, n_legs=1, wind_sigma=2.0) + result = race.run_single(seed=42) + assert "tactics_A" in result + assert "tactics_B" in result + for key in ["shadow_seconds", "shadow_encounters"]: + assert key in result["tactics_A"], f"Missing {key} in tactics_A" + assert key in result["tactics_B"], f"Missing {key} in tactics_B" + + def test_shadow_seconds_non_negative(self): + """Shadow time should be non-negative.""" + race = Race(_realistic_polar(1.0), _realistic_polar(1.0), tws=10.0, + leg_distance=0.3, n_legs=1, wind_sigma=2.0) + result = race.run_single(seed=42) + assert result["tactics_A"]["shadow_seconds"] >= 0 + assert result["tactics_B"]["shadow_seconds"] >= 0 + + def test_monte_carlo_has_tactical_summary(self): + """Monte carlo includes aggregated tactical stats.""" + race = Race(_realistic_polar(1.2), _realistic_polar(1.0), tws=10.0, + leg_distance=0.3, n_legs=1, wind_sigma=2.0) + mc = race.run_monte_carlo(n_runs=10) + assert "tactics_summary" in mc + assert "mean_shadow_seconds_A" in mc["tactics_summary"] + assert "mean_shadow_seconds_B" in mc["tactics_summary"] + + +class TestParameterSweep: + def test_parameter_sweep_returns_expected_keys(self): + """run_parameter_sweep returns dict with param_values and win_pct_A.""" + from src.RaceMod import run_parameter_sweep + + result = run_parameter_sweep( + yacht_factory_A=lambda v: None, # uses polars directly + yacht_factory_B=lambda v: None, + polar_factory_A=lambda v: _realistic_polar(1.0 + v * 0.1), + polar_factory_B=lambda v: _realistic_polar(1.0), + param_values=[0.0, 1.0, 2.0], + param_name="speed_bonus", + tws=10.0, + n_runs=10, + leg_distance=0.2, + ) + assert "param_values" in result + assert "param_name" in result + assert "win_pct_A" in result + assert "win_pct_B" in result + assert "mean_deltas" in result + assert len(result["param_values"]) == 3 + assert len(result["win_pct_A"]) == 3 + + def test_parameter_sweep_increasing_advantage(self): + """As speed bonus increases, win probability should increase.""" + from src.RaceMod import run_parameter_sweep + + result = run_parameter_sweep( + polar_factory_A=lambda v: _realistic_polar(1.0 + v), + polar_factory_B=lambda v: _realistic_polar(1.0), + param_values=[0.0, 0.3, 0.6], + param_name="scale_bonus", + tws=10.0, + n_runs=50, + leg_distance=0.3, + ) + # Win rate should generally increase with speed advantage + assert result["win_pct_A"][-1] > result["win_pct_A"][0], ( + "Higher speed bonus should increase win rate" + ) + + +class TestCachedPolars: + def test_daring_polar_loads(self): + """Pre-computed Daring polar loads and returns sensible speeds.""" + polar = _load_cached_polar("Daring_5.5m") + speed_reach = polar(10.0, 90.0) + speed_upwind = polar(10.0, 40.0) + assert speed_reach > 0, "Reaching speed should be positive" + assert speed_upwind > 0, "Upwind speed should be positive" + assert speed_reach > speed_upwind, "Reaching should be faster than upwind" + + def test_daring_vs_daring_race(self): + """Two Darings racing should produce ~50/50 results.""" + polar = _load_cached_polar("Daring_5.5m") + race = Race(polar, polar, tws=10.0, + leg_distance=0.3, n_legs=1, wind_sigma=3.0, + trim_sigma=0.02) + mc = race.run_monte_carlo(n_runs=50) + decided = mc["wins_A"] + mc["wins_B"] + if decided > 5: + pct_A = mc["wins_A"] / decided + assert 0.15 < pct_A < 0.85, f"Bias detected: A wins {pct_A:.0%}" + + def test_yd41_faster_than_daring(self): + """YD41 should beat Daring in most races (bigger, faster boat).""" + polar_yd41 = _load_cached_polar("YD41") + polar_daring = _load_cached_polar("Daring_5.5m") + race = Race(polar_yd41, polar_daring, tws=10.0, + leg_distance=0.5, n_legs=1, wind_sigma=2.0) + mc = race.run_monte_carlo(n_runs=50) + assert mc["wins_A"] > mc["wins_B"], "YD41 should beat Daring more often" diff --git a/tests/test_resistance.py b/tests/test_resistance.py index f557ecb..f395ff4 100644 --- a/tests/test_resistance.py +++ b/tests/test_resistance.py @@ -43,3 +43,15 @@ def test_Rr_interpolation(): np_testing.assert_approx_equal(YD41._interp_Rr((0.700, 3.0, 9.0)), 357.062, 4) np_testing.assert_approx_equal(YD41._interp_Rr((0.700, 9.0, 2.5)), 38.0526, 4) np_testing.assert_approx_equal(YD41._interp_Rr((0.700, 9.0, 9.0)), 42.2353, 4) + + +def test_build_interp_func_no_deprecation(): + """Verify build_interp_func doesn't use deprecated interp1d.""" + import warnings + + from src.UtilsMod import build_interp_func + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + func = build_interp_func("main", i=1) + result = func(30.0) + assert isinstance(float(result), float) diff --git a/tests/test_streamlit.py b/tests/test_streamlit.py new file mode 100644 index 0000000..e60c009 --- /dev/null +++ b/tests/test_streamlit.py @@ -0,0 +1,217 @@ +"""Streamlit app tests — verify pages load and widgets render correctly. + +These tests check the UI structure (widgets, sections, buttons) but NOT +the mathematical models — those are covered by test_hydro, test_vpp, etc. +""" + +import os +import sys + +import pytest +from streamlit.testing.v1 import AppTest + +# The demo pages import `from presets import ...` and `from utils import ...` +# which requires demos/ on sys.path. AppTest runs pages as scripts in +# a subprocess, so we inject the path via PYTHONPATH. +DEMOS_DIR = os.path.join(os.path.dirname(__file__), "..", "demos") +PAGES_DIR = os.path.join(DEMOS_DIR, "pages") + + +@pytest.fixture(autouse=True) +def _patch_pythonpath(monkeypatch): + """Ensure demos/ is importable by child processes and this process.""" + abs_demos = os.path.abspath(DEMOS_DIR) + abs_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + existing = os.environ.get("PYTHONPATH", "") + monkeypatch.setenv("PYTHONPATH", f"{abs_demos}{os.pathsep}{abs_root}{os.pathsep}{existing}") + if abs_demos not in sys.path: + sys.path.insert(0, abs_demos) + if abs_root not in sys.path: + sys.path.insert(0, abs_root) + + +def _load_page(filename, timeout=30): + """Load a Streamlit page via AppTest.""" + path = os.path.join(PAGES_DIR, filename) + at = AppTest.from_file(path, default_timeout=timeout) + at.run() + return at + + +# ────────────────────────────────────────────── +# VPP page +# ────────────────────────────────────────────── + +class TestVPPPage: + def test_vpp_page_loads_without_error(self): + at = _load_page("1_VPP_⛵.py") + assert not at.exception, f"Page raised: {at.exception}" + + def test_vpp_page_has_title(self): + at = _load_page("1_VPP_⛵.py") + markdown_texts = [m.value for m in at.markdown] + assert any("Yacht VPP" in t for t in markdown_texts) + + def test_vpp_page_has_preset_selector(self): + at = _load_page("1_VPP_⛵.py") + assert len(at.selectbox) > 0, "No selectbox found" + # First selectbox should be the yacht preset + options = at.selectbox[0].options + assert "YD41" in options + + def test_vpp_page_has_process_button(self): + at = _load_page("1_VPP_⛵.py") + labels = [b.label for b in at.button] + assert "Process Specifications" in labels + + def test_vpp_page_has_environment_sliders(self): + at = _load_page("1_VPP_⛵.py") + slider_labels = [s.label for s in at.slider] + assert any("TWA" in l for l in slider_labels), "Missing TWA slider" + assert any("TWS" in l for l in slider_labels), "Missing TWS slider" + number_labels = [n.label for n in at.number_input] + assert any("roughness" in l.lower() for l in number_labels), "Missing roughness input" + assert any("H_s" in l for l in slider_labels), "Missing Hs slider" + assert any("T_s" in l for l in slider_labels), "Missing Ts slider" + + def test_vpp_page_has_solver_settings(self): + at = _load_page("1_VPP_⛵.py") + selectbox_labels = [s.label for s in at.selectbox] + assert any("Solver" in l for l in selectbox_labels), "Missing solver selectbox" + + def test_vpp_page_has_sail_type_selectors(self): + at = _load_page("1_VPP_⛵.py") + selectbox_labels = [s.label for s in at.selectbox] + assert any("Main" in l and "type" in l for l in selectbox_labels) + assert any("Jib" in l and "type" in l.lower() for l in selectbox_labels) + assert any("Kite" in l and "type" in l.lower() for l in selectbox_labels) + + def test_vpp_page_has_popover_labels(self): + at = _load_page("1_VPP_⛵.py") + # Popovers aren't directly queryable via AppTest, but their trigger + # labels appear in the rendered button elements. + button_labels = [b.label for b in at.button] + assert any("What is a VPP" in l for l in button_labels) or len(at.button) >= 1 + + def test_field_help_covers_all_yacht_fields(self): + """FIELD_HELP should have entries for all yacht, keel, rudder, sail fields.""" + from utils import FIELD_HELP + expected = [ + "Lwl", "Vol", "Bwl", "Tc", "WSA", "Tmax", "Amax", "Mass", + "Ff", "Fa", "Boa", "Loa", + "Cu", "Cl", "Span", "Length", "Depth", "Tc_ratio", + "P", "E", "Roach", "BAD", + "I", "J", "LPG", "HBI", + "area", "vce", + ] + for key in expected: + assert key in FIELD_HELP, f"Missing help for field '{key}'" + assert len(FIELD_HELP[key]) > 5, f"Help for '{key}' too short" + + +# ────────────────────────────────────────────── +# Compare page +# ────────────────────────────────────────────── + +class TestComparePage: + def test_compare_page_loads_without_error(self): + at = _load_page("2_Compare_⚖️.py") + assert not at.exception, f"Page raised: {at.exception}" + + def test_compare_page_has_title(self): + at = _load_page("2_Compare_⚖️.py") + markdown_texts = [m.value for m in at.markdown] + assert any("Compare" in t for t in markdown_texts) + + def test_compare_page_has_config_buttons(self): + at = _load_page("2_Compare_⚖️.py") + labels = [b.label for b in at.button] + assert "+ Add config" in labels + assert "- Remove last" in labels + assert "Compare" in labels + + def test_compare_page_has_preset_selectors(self): + at = _load_page("2_Compare_⚖️.py") + # Should have at least 2 preset selectors (one per config tab) + preset_boxes = [s for s in at.selectbox if "Preset" in s.label] + assert len(preset_boxes) >= 2, f"Expected 2+ preset selectors, got {len(preset_boxes)}" + + def test_compare_page_has_environment_sliders(self): + at = _load_page("2_Compare_⚖️.py") + slider_labels = [s.label for s in at.slider] + assert any("TWA" in l for l in slider_labels) + assert any("TWS" in l for l in slider_labels) + + def test_compare_page_has_buttons(self): + at = _load_page("2_Compare_⚖️.py") + # Verify page rendered fully (buttons present implies layout loaded) + assert len(at.button) >= 3 + + +# ────────────────────────────────────────────── +# Match Race page +# ────────────────────────────────────────────── + +class TestMatchRacePage: + def test_match_race_page_loads_without_error(self): + at = _load_page("3_Match_Race_🏁.py") + assert not at.exception, f"Page raised: {at.exception}" + + def test_match_race_page_has_title(self): + at = _load_page("3_Match_Race_🏁.py") + markdown_texts = [m.value for m in at.markdown] + assert any("Match Race" in t for t in markdown_texts) + + def test_match_race_page_has_race_button(self): + at = _load_page("3_Match_Race_🏁.py") + labels = [b.label for b in at.button] + assert "Race!" in labels + + def test_match_race_page_has_boat_preset_selectors(self): + at = _load_page("3_Match_Race_🏁.py") + preset_boxes = [s for s in at.selectbox if "Preset" in s.label] + assert len(preset_boxes) >= 2, "Expected 2 boat preset selectors" + + def test_match_race_page_has_environment_sliders(self): + at = _load_page("3_Match_Race_🏁.py") + slider_labels = [s.label for s in at.slider] + assert any("wind speed" in l.lower() for l in slider_labels), "Missing TWS slider" + assert any("current" in l.lower() for l in slider_labels), "Missing current slider" + + def test_match_race_page_has_race_parameter_sliders(self): + at = _load_page("3_Match_Race_🏁.py") + slider_labels = [s.label for s in at.slider] + assert any("Leg distance" in l for l in slider_labels) + assert any("Tack penalty" in l for l in slider_labels) + assert any("Gybe penalty" in l for l in slider_labels) + + def test_match_race_page_has_wind_model_sliders(self): + at = _load_page("3_Match_Race_🏁.py") + slider_labels = [s.label for s in at.slider] + assert any("Wind shift" in l for l in slider_labels) + assert any("TWS" in l and "sigma" in l.lower() for l in slider_labels) + assert any("mean-reversion" in l.lower() for l in slider_labels) + + def test_match_race_page_has_stochastic_sliders(self): + at = _load_page("3_Match_Race_🏁.py") + slider_labels = [s.label for s in at.slider] + assert any("Trim noise" in l for l in slider_labels) + assert any("Tack penalty" in l and "sigma" in l.lower() for l in slider_labels) + + def test_match_race_page_has_monte_carlo_selector(self): + at = _load_page("3_Match_Race_🏁.py") + mc_boxes = [s for s in at.selectbox if "Monte Carlo" in s.label] + assert len(mc_boxes) >= 1 + assert "100" in mc_boxes[0].options + + def test_match_race_page_has_subheaders(self): + at = _load_page("3_Match_Race_🏁.py") + # Verify key sections rendered (subheaders indicate layout completeness) + markdown_texts = [m.value for m in at.markdown] + assert any("Match Race" in t for t in markdown_texts) + + def test_match_race_page_has_leg_pairs_selector(self): + at = _load_page("3_Match_Race_🏁.py") + leg_boxes = [s for s in at.selectbox if "leg pairs" in s.label.lower()] + assert len(leg_boxes) >= 1 + assert "1" in leg_boxes[0].options diff --git a/tests/test_utils.py b/tests/test_utils.py index 10d2036..80d65c6 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,8 +1,9 @@ from src.SailMod import Jib, Kite, Main from src.YachtMod import Keel, Rudder, Yacht + def return_YD41_particulars(): - + YD41 = Yacht( Name="YD41", Lwl=11.90, @@ -25,4 +26,4 @@ def return_YD41_particulars(): Kite("A5", area=75.0, vce=2.75), ], ) - return YD41 \ No newline at end of file + return YD41 diff --git a/tests/test_vpp.py b/tests/test_vpp.py index f1e0211..b4a5d99 100644 --- a/tests/test_vpp.py +++ b/tests/test_vpp.py @@ -1,11 +1,13 @@ +import os import numpy as np -from tests.test_utils import return_YD41_particulars +from src.SailMod import Jib, Kite, Main from src.VPPMod import VPP -from src.SailMod import Jib, Main +from tests.test_utils import return_YD41_particulars -def test_single_sail_set(): + +def test_single_sail_set(tmp_path): YD41 = return_YD41_particulars() YD41_no_kite = YD41 @@ -22,5 +24,238 @@ def test_single_sail_set(): vpp.run(verbose=False) vpp.write("results") - vpp.polar(3, False) - vpp.SailChart(False) + + polar_path = str(tmp_path / "test_polar.png") + sail_path = str(tmp_path / "test_sail.png") + vpp.polar(3, True, fname=polar_path) + vpp.SailChart(True, fname=sail_path) + assert os.path.exists(polar_path), "Polar plot was not created" + assert os.path.exists(sail_path), "Sail chart was not created" + + +def test_sail_chart_no_deprecation_warning(tmp_path): + """Verify sail_chart doesn't use deprecated interp2d.""" + import warnings + + yacht = return_YD41_particulars() + yacht.sails = [ + Main("MN1", P=16.60, E=5.60, Roach=0.1, BAD=1.0), + Jib("J1", I=16.20, J=5.10, LPG=5.40, HBI=1.8), + ] + vpp = VPP(Yacht=yacht) + vpp.set_analysis( + tws_range=np.array([6.0, 10.0]), + twa_range=np.linspace(30.0, 180.0, 16), + ) + vpp.run(verbose=False) + fname = str(tmp_path / "test_sailchart.png") + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + vpp.SailChart(save=True, fname=fname) + assert os.path.exists(fname), "Sail chart was not created" + + +def test_phi_max_configurable(): + yacht = return_YD41_particulars() + yacht.sails = [Main("MN1", P=16.60, E=5.60, Roach=0.1, BAD=1.0), + Jib("J1", I=16.20, J=5.10, LPG=5.40, HBI=1.8)] + vpp = VPP(Yacht=yacht) + vpp.set_analysis(tws_range=np.array([10.0]), + twa_range=np.linspace(30.0, 180.0, 3), + phi_max=25.0) + assert vpp.phi_max == 25.0 + + +def test_run_without_analysis_raises(): + """Issue #46: raise with string literal should be a proper exception.""" + import pytest + + yacht = return_YD41_particulars() + vpp = VPP(Yacht=yacht) + with pytest.raises(RuntimeError, match="no analysis set"): + vpp.run() + + +def test_set_analysis_empty_tws_raises(): + import pytest + + yacht = return_YD41_particulars() + vpp = VPP(Yacht=yacht) + with pytest.raises(ValueError, match="TWS range is empty"): + vpp.set_analysis(tws_range=np.array([]), twa_range=np.linspace(30, 180, 5)) + + +def test_set_analysis_empty_twa_raises(): + import pytest + + yacht = return_YD41_particulars() + vpp = VPP(Yacht=yacht) + with pytest.raises(ValueError, match="TWA range is empty"): + vpp.set_analysis(tws_range=np.array([6.0, 10.0]), twa_range=np.array([])) + + +def test_set_analysis_tws_below_minimum_raises(): + import pytest + + yacht = return_YD41_particulars() + vpp = VPP(Yacht=yacht) + with pytest.raises(ValueError, match="outside valid bounds"): + vpp.set_analysis(tws_range=np.array([1.0, 5.0]), twa_range=np.linspace(30, 180, 5)) + + +def test_set_analysis_tws_above_maximum_raises(): + import pytest + + yacht = return_YD41_particulars() + vpp = VPP(Yacht=yacht) + with pytest.raises(ValueError, match="outside valid bounds"): + vpp.set_analysis(tws_range=np.array([10.0, 40.0]), twa_range=np.linspace(30, 180, 5)) + + +def test_set_analysis_twa_out_of_range_raises(): + import pytest + + yacht = return_YD41_particulars() + vpp = VPP(Yacht=yacht) + with pytest.raises(ValueError, match="outside valid bounds"): + vpp.set_analysis(tws_range=np.array([6.0, 10.0]), twa_range=np.array([-5.0, 90.0])) + + +def test_5dof_solver_runs(): + """5-DOF solver produces non-zero speeds.""" + yacht = return_YD41_particulars() + yacht.sails = [Main("MN1", P=16.60, E=5.60, Roach=0.1, BAD=1.0), + Jib("J1", I=16.20, J=5.10, LPG=5.40, HBI=1.8)] + vpp = VPP(Yacht=yacht) + vpp.set_analysis(tws_range=np.array([8.0]), + twa_range=np.linspace(40.0, 160.0, 4)) + vpp.run(method="5dof") + speeds = vpp.store[0, :, 0, 0] + assert np.any(speeds > 0), "5-DOF solver should produce non-zero speeds" + + +def test_5dof_vs_iterative_comparable(): + """5-DOF and iterative solvers should produce speeds within 15%.""" + yacht = return_YD41_particulars() + yacht.sails = [Main("MN1", P=16.60, E=5.60, Roach=0.1, BAD=1.0), + Jib("J1", I=16.20, J=5.10, LPG=5.40, HBI=1.8)] + + vpp_iter = VPP(Yacht=yacht) + vpp_iter.set_analysis(tws_range=np.array([8.0]), + twa_range=np.linspace(40.0, 160.0, 4)) + vpp_iter.run(method="iterative") + + vpp_5dof = VPP(Yacht=yacht) + vpp_5dof.set_analysis(tws_range=np.array([8.0]), + twa_range=np.linspace(40.0, 160.0, 4)) + vpp_5dof.run(method="5dof") + + speeds_iter = vpp_iter.store[0, :, 0, 0] + speeds_5dof = vpp_5dof.store[0, :, 0, 0] + + # Compare only non-zero entries + mask = speeds_iter > 0.1 + if np.any(mask): + ratio = speeds_5dof[mask] / speeds_iter[mask] + assert np.all(ratio > 0.85) and np.all(ratio < 1.15), ( + f"5-DOF vs iterative speed ratio out of 15% band: {ratio}" + ) + + +def test_invalid_method_raises(): + import pytest + + yacht = return_YD41_particulars() + yacht.sails = [Main("MN1", P=16.60, E=5.60, Roach=0.1, BAD=1.0), + Jib("J1", I=16.20, J=5.10, LPG=5.40, HBI=1.8)] + vpp = VPP(Yacht=yacht) + vpp.set_analysis(tws_range=np.array([8.0]), + twa_range=np.linspace(40.0, 160.0, 3)) + with pytest.raises(ValueError, match="Unknown method"): + vpp.run(method="bogus") + + +def test_sail_type_main_low(): + """Main with sail_type='main_low' loads lower CL coefficients.""" + main_hi = Main("MN1", P=16.60, E=5.60, Roach=0.1, BAD=1.0) + main_lo = Main("MN1", P=16.60, E=5.60, Roach=0.1, BAD=1.0, sail_type="main_low") + # At peak CL angle (~28 deg), low should have less lift + assert main_lo.cl(28) < main_hi.cl(28) + + +def test_sail_type_jib_low(): + """Jib with sail_type='jib_low' loads lower CL coefficients.""" + jib_hi = Jib("J1", I=16.20, J=5.10, LPG=5.40, HBI=1.8) + jib_lo = Jib("J1", I=16.20, J=5.10, LPG=5.40, HBI=1.8, sail_type="jib_low") + assert jib_lo.cl(27) < jib_hi.cl(27) + + +def test_sail_type_sym_kite(): + """Symmetric spinnaker has higher CL than the default kite.""" + kite_default = Kite("A2", area=150.0, vce=9.55) + kite_sym = Kite("A2", area=150.0, vce=9.55, sail_type="sym_kite") + # ORC symmetric spinnaker has peak CL ~1.456 vs default ~1.08 + assert kite_sym.cl(67) > kite_default.cl(67) + + +def test_sail_type_asym_kite_variants(): + """Asymmetric spinnaker variants load and produce different coefficients.""" + kite_cl = Kite("A2", area=150.0, vce=9.55, sail_type="asym_cl_kite") + kite_pole = Kite("A2", area=150.0, vce=9.55, sail_type="asym_pole_kite") + # Both should produce non-zero CL at 67 deg + assert kite_cl.cl(67) > 1.0 + assert kite_pole.cl(67) > 1.0 + # Pole tack has slightly higher peak CL than centerline + assert kite_pole.cl(67) > kite_cl.cl(67) + + +def test_parallel_matches_iterative(): + """Parallel solver must produce identical results to sequential iterative.""" + yacht = return_YD41_particulars() + yacht.sails = [Main("MN1", P=16.60, E=5.60, Roach=0.1, BAD=1.0), + Jib("J1", I=16.20, J=5.10, LPG=5.40, HBI=1.8), + Kite("A2", area=150.0, vce=9.55)] + + tws_range = np.array([6.0, 10.0]) + twa_range = np.linspace(40.0, 160.0, 5) + + vpp_seq = VPP(Yacht=yacht) + vpp_seq.set_analysis(tws_range=tws_range, twa_range=twa_range) + vpp_seq.run(method="iterative") + + vpp_par = VPP(Yacht=yacht) + vpp_par.set_analysis(tws_range=tws_range, twa_range=twa_range) + vpp_par.run(method="parallel") + + np.testing.assert_allclose( + vpp_par.store, vpp_seq.store, atol=1e-6, + err_msg="Parallel results differ from sequential iterative" + ) + + +def test_parallel_method_accepted(): + """VPP.run(method='parallel') should not raise.""" + yacht = return_YD41_particulars() + yacht.sails = [Main("MN1", P=16.60, E=5.60, Roach=0.1, BAD=1.0), + Jib("J1", I=16.20, J=5.10, LPG=5.40, HBI=1.8)] + vpp = VPP(Yacht=yacht) + vpp.set_analysis(tws_range=np.array([8.0]), + twa_range=np.linspace(40.0, 160.0, 3)) + vpp.run(method="parallel") + assert np.any(vpp.store[0, :, 0, 0] > 0) + + +def test_sym_kite_vpp_runs(): + """VPP runs with symmetric spinnaker coefficients.""" + from src.YachtMod import Keel, Rudder, Yacht + yacht = Yacht( + Name="YD41", Lwl=11.90, Vol=6.05, Bwl=3.18, Tc=0.4, WSA=28.20, + Tmax=2.30, Amax=1.051, Mass=6500, Ff=1.5, Fa=1.5, Boa=4.2, Loa=12.5, + App=[Keel(Cu=1.00, Cl=0.78, Span=1.90), Rudder(Cu=0.48, Cl=0.22, Span=1.15)], + Sails=[Main("MN1", P=16.60, E=5.60, Roach=0.1, BAD=1.0), + Jib("J1", I=16.20, J=5.10, LPG=5.40, HBI=1.8), + Kite("A2", area=150.0, vce=9.55, sail_type="sym_kite")]) + vpp = VPP(Yacht=yacht) + vpp.set_analysis(tws_range=np.array([10.0]), twa_range=np.linspace(40.0, 160.0, 4)) + vpp.run() + assert np.any(vpp.store[0, :, :, 0] > 0) diff --git a/tests/test_wind.py b/tests/test_wind.py new file mode 100644 index 0000000..d41c3b7 --- /dev/null +++ b/tests/test_wind.py @@ -0,0 +1,77 @@ +"""Tests for WindMod — pluggable wind models.""" + +import numpy as np + +from src.WindMod import BrownianWind, ConstantWind, MeanRevertingWind + + +class TestConstantWind: + def test_constant_wind_never_changes(self): + model = ConstantWind(tws=10.0, twd=0.0) + rng = np.random.default_rng(42) + for _ in range(100): + tws, twd = model.update(1.0, rng) + assert tws == 10.0 + assert twd == 0.0 + + def test_state_serialisable(self): + model = ConstantWind(tws=10.0) + s = model.state() + assert s["tws"] == 10.0 + assert s["model"] == "ConstantWind" + + +class TestBrownianWind: + def test_direction_drifts(self): + model = BrownianWind(tws=10.0, dir_sigma=5.0) + rng = np.random.default_rng(42) + for _ in range(600): + tws, twd = model.update(1.0, rng) + assert twd != 0.0, "Direction should have drifted" + assert tws == 10.0, "Speed should stay constant" + + def test_zero_sigma_no_drift(self): + model = BrownianWind(tws=10.0, dir_sigma=0.0) + rng = np.random.default_rng(42) + for _ in range(100): + tws, twd = model.update(1.0, rng) + assert twd == 0.0 + + def test_reset(self): + model = BrownianWind(tws=10.0, dir_sigma=5.0) + rng = np.random.default_rng(42) + model.update(1.0, rng) + model.reset(tws=12.0, twd=0.0) + assert model.tws == 12.0 + assert model.twd == 0.0 + + +class TestMeanRevertingWind: + def test_direction_mean_reverts(self): + """Direction should stay near zero with strong reversion.""" + model = MeanRevertingWind(tws=10.0, dir_sigma=2.0, dir_reversion=1.0) + rng = np.random.default_rng(42) + dirs = [] + for _ in range(6000): + _, twd = model.update(1.0, rng) + dirs.append(twd) + # With strong reversion, mean should be near 0 + assert abs(np.mean(dirs)) < 5.0 + + def test_speed_varies_when_enabled(self): + model = MeanRevertingWind(tws=10.0, tws_sigma=1.0, tws_reversion=0.1) + rng = np.random.default_rng(42) + speeds = [] + for _ in range(600): + tws, _ = model.update(1.0, rng) + speeds.append(tws) + assert min(speeds) != max(speeds), "TWS should vary" + # Should stay near 10 kts on average + assert 8.0 < np.mean(speeds) < 12.0 + + def test_speed_constant_when_disabled(self): + model = MeanRevertingWind(tws=10.0, tws_sigma=0.0) + rng = np.random.default_rng(42) + for _ in range(100): + tws, _ = model.update(1.0, rng) + assert tws == 10.0 diff --git a/tests/test_yacht.py b/tests/test_yacht.py new file mode 100644 index 0000000..6c43d7b --- /dev/null +++ b/tests/test_yacht.py @@ -0,0 +1,47 @@ +import numpy as np +import pytest +from src.YachtMod import Yacht, Keel, Rudder +from src.SailMod import Main, Jib + + +def _minimal_yacht(**overrides): + """Create a minimal Yacht for testing with sensible defaults.""" + defaults = dict( + Name="TestYacht", Lwl=7.01, Vol=1.95, Bwl=1.70, Tc=0.45, + WSA=11.5, Tmax=1.35, Amax=0.38, Mass=2000, Loa=9.90, Boa=1.98, + Ff=0.75, Fa=0.55, + App=[Keel(Cu=0.70, Cl=0.45, Span=0.90), Rudder(Cu=0.32, Cl=0.18, Span=0.75)], + Sails=[Main("MN1", P=10.80, E=3.30, Roach=0.1, BAD=0.80), + Jib("J1", I=8.50, J=2.70, LPG=2.70, HBI=0.50)], + ) + defaults.update(overrides) + return Yacht(**defaults) + + +def test_yacht_accepts_gz_parameter(): + """Yacht should accept an optional gz dict to override the global file.""" + gz = {"Heel": [0, 10, 20, 30], "GZ": [0.0, 0.12, 0.23, 0.31]} + yacht = _minimal_yacht(GZ=gz) + rm_10 = yacht._get_RmH(10.0) + expected = 0.12 * 2000 * 9.81 + assert abs(rm_10 - expected) < 1.0 + + +def test_yacht_falls_back_to_file_when_no_gz(): + """When no GZ parameter given, should still load from file (backward compat).""" + yacht = _minimal_yacht() + rm_10 = yacht._get_RmH(10.0) + assert rm_10 > 0 + + +def test_yacht_accepts_crew_weight(): + """Yacht should accept an optional crew_weight to override the empirical formula.""" + yacht = _minimal_yacht(crew_weight=240.0) + assert yacht.cw == 240.0 + + +def test_yacht_default_crew_weight(): + """Without crew_weight param, should use empirical formula.""" + yacht = _minimal_yacht() + expected = 25.8 * 7.01 ** 1.4262 + assert abs(yacht.cw - expected) < 0.1 diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..1d93cd6 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1581 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version < '3.12'", +] + +[[package]] +name = "altair" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "narwhals" }, + { name = "packaging" }, + { name = "typing-extensions", marker = "python_full_version < '3.15'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/c0/184a89bd5feba14ff3c41cfaf1dd8a82c05f5ceedbc92145e17042eb08a4/altair-6.0.0.tar.gz", hash = "sha256:614bf5ecbe2337347b590afb111929aa9c16c9527c4887d96c9bc7f6640756b4", size = 763834, upload-time = "2025-11-12T08:59:11.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/33/ef2f2409450ef6daa61459d5de5c08128e7d3edb773fefd0a324d1310238/altair-6.0.0-py3-none-any.whl", hash = "sha256:09ae95b53d5fe5b16987dccc785a7af8588f2dca50de1e7a156efa8a461515f8", size = 795410, upload-time = "2025-11-12T08:59:09.804Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "cachetools" +version = "6.2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/91/d9ae9a66b01102a18cd16db0cf4cd54187ffe10f0865cc80071a4104fbb3/cachetools-6.2.6.tar.gz", hash = "sha256:16c33e1f276b9a9c0b49ab5782d901e3ad3de0dd6da9bf9bcd29ac5672f2f9e6", size = 32363, upload-time = "2026-01-27T20:32:59.956Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/45/f458fa2c388e79dd9d8b9b0c99f1d31b568f27388f2fdba7bb66bbc0c6ed/cachetools-6.2.6-py3-none-any.whl", hash = "sha256:8c9717235b3c651603fff0076db52d6acbfd1b338b8ed50256092f7ce9c85bda", size = 11668, upload-time = "2026-01-27T20:32:58.527Z" }, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "flask" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" }, +] + +[[package]] +name = "fonttools" +version = "4.61.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, + { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, + { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, + { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, + { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, + { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, + { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, + { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, + { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, + { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, + { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, + { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, + { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" }, + { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" }, + { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" }, + { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" }, + { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" }, + { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" }, + { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" }, + { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" }, + { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" }, + { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" }, + { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" }, + { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" }, + { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, +] + +[[package]] +name = "gitpython" +version = "3.1.46" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/b5/59d16470a1f0dfe8c793f9ef56fd3826093fc52b3bd96d6b9d6c26c7e27b/gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f", size = 215371, upload-time = "2026-01-01T15:37:32.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620, upload-time = "2026-01-01T15:37:30.574Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +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 = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, + { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, + { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, + { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, + { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, + { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, + { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, + { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, + { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, + { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, + { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, + { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, + { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, + { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, + { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, + { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, + { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, + { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, + { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, + { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, + { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, + { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, + { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, + { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, + { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, + { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, + { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, + { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, + { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, + { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, + { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, + { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, + { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, + { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, + { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, + { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, + { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, + { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, + { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, + { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, +] + +[[package]] +name = "librt" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/01/0e748af5e4fee180cf7cd12bd12b0513ad23b045dccb2a83191bde82d168/librt-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:681dc2451d6d846794a828c16c22dc452d924e9f700a485b7ecb887a30aad1fd", size = 65315, upload-time = "2026-02-17T16:11:25.152Z" }, + { url = "https://files.pythonhosted.org/packages/9d/4d/7184806efda571887c798d573ca4134c80ac8642dcdd32f12c31b939c595/librt-0.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3b4350b13cc0e6f5bec8fa7caf29a8fb8cdc051a3bae45cfbfd7ce64f009965", size = 68021, upload-time = "2026-02-17T16:11:26.129Z" }, + { url = "https://files.pythonhosted.org/packages/ae/88/c3c52d2a5d5101f28d3dc89298444626e7874aa904eed498464c2af17627/librt-0.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ac1e7817fd0ed3d14fd7c5df91daed84c48e4c2a11ee99c0547f9f62fdae13da", size = 194500, upload-time = "2026-02-17T16:11:27.177Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5d/6fb0a25b6a8906e85b2c3b87bee1d6ed31510be7605b06772f9374ca5cb3/librt-0.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:747328be0c5b7075cde86a0e09d7a9196029800ba75a1689332348e998fb85c0", size = 205622, upload-time = "2026-02-17T16:11:28.242Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a6/8006ae81227105476a45691f5831499e4d936b1c049b0c1feb17c11b02d1/librt-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0af2bd2bc204fa27f3d6711d0f360e6b8c684a035206257a81673ab924aa11e", size = 218304, upload-time = "2026-02-17T16:11:29.344Z" }, + { url = "https://files.pythonhosted.org/packages/ee/19/60e07886ad16670aae57ef44dada41912c90906a6fe9f2b9abac21374748/librt-0.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d480de377f5b687b6b1bc0c0407426da556e2a757633cc7e4d2e1a057aa688f3", size = 211493, upload-time = "2026-02-17T16:11:30.445Z" }, + { url = "https://files.pythonhosted.org/packages/9c/cf/f666c89d0e861d05600438213feeb818c7514d3315bae3648b1fc145d2b6/librt-0.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d0ee06b5b5291f609ddb37b9750985b27bc567791bc87c76a569b3feed8481ac", size = 219129, upload-time = "2026-02-17T16:11:32.021Z" }, + { url = "https://files.pythonhosted.org/packages/8f/ef/f1bea01e40b4a879364c031476c82a0dc69ce068daad67ab96302fed2d45/librt-0.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e2c6f77b9ad48ce5603b83b7da9ee3e36b3ab425353f695cba13200c5d96596", size = 213113, upload-time = "2026-02-17T16:11:33.192Z" }, + { url = "https://files.pythonhosted.org/packages/9b/80/cdab544370cc6bc1b72ea369525f547a59e6938ef6863a11ab3cd24759af/librt-0.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:439352ba9373f11cb8e1933da194dcc6206daf779ff8df0ed69c5e39113e6a99", size = 212269, upload-time = "2026-02-17T16:11:34.373Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9c/48d6ed8dac595654f15eceab2035131c136d1ae9a1e3548e777bb6dbb95d/librt-0.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:82210adabbc331dbb65d7868b105185464ef13f56f7f76688565ad79f648b0fe", size = 234673, upload-time = "2026-02-17T16:11:36.063Z" }, + { url = "https://files.pythonhosted.org/packages/16/01/35b68b1db517f27a01be4467593292eb5315def8900afad29fabf56304ba/librt-0.8.1-cp311-cp311-win32.whl", hash = "sha256:52c224e14614b750c0a6d97368e16804a98c684657c7518752c356834fff83bb", size = 54597, upload-time = "2026-02-17T16:11:37.544Z" }, + { url = "https://files.pythonhosted.org/packages/71/02/796fe8f02822235966693f257bf2c79f40e11337337a657a8cfebba5febc/librt-0.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:c00e5c884f528c9932d278d5c9cbbea38a6b81eb62c02e06ae53751a83a4d52b", size = 61733, upload-time = "2026-02-17T16:11:38.691Z" }, + { url = "https://files.pythonhosted.org/packages/28/ad/232e13d61f879a42a4e7117d65e4984bb28371a34bb6fb9ca54ec2c8f54e/librt-0.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:f7cdf7f26c2286ffb02e46d7bac56c94655540b26347673bea15fa52a6af17e9", size = 52273, upload-time = "2026-02-17T16:11:40.308Z" }, + { url = "https://files.pythonhosted.org/packages/95/21/d39b0a87ac52fc98f621fb6f8060efb017a767ebbbac2f99fbcbc9ddc0d7/librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a", size = 66516, upload-time = "2026-02-17T16:11:41.604Z" }, + { url = "https://files.pythonhosted.org/packages/69/f1/46375e71441c43e8ae335905e069f1c54febee63a146278bcee8782c84fd/librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9", size = 68634, upload-time = "2026-02-17T16:11:43.268Z" }, + { url = "https://files.pythonhosted.org/packages/0a/33/c510de7f93bf1fa19e13423a606d8189a02624a800710f6e6a0a0f0784b3/librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb", size = 198941, upload-time = "2026-02-17T16:11:44.28Z" }, + { url = "https://files.pythonhosted.org/packages/dd/36/e725903416409a533d92398e88ce665476f275081d0d7d42f9c4951999e5/librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d", size = 209991, upload-time = "2026-02-17T16:11:45.462Z" }, + { url = "https://files.pythonhosted.org/packages/30/7a/8d908a152e1875c9f8eac96c97a480df425e657cdb47854b9efaa4998889/librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7", size = 224476, upload-time = "2026-02-17T16:11:46.542Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b8/a22c34f2c485b8903a06f3fe3315341fe6876ef3599792344669db98fcff/librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440", size = 217518, upload-time = "2026-02-17T16:11:47.746Z" }, + { url = "https://files.pythonhosted.org/packages/79/6f/5c6fea00357e4f82ba44f81dbfb027921f1ab10e320d4a64e1c408d035d9/librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9", size = 225116, upload-time = "2026-02-17T16:11:49.298Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a0/95ced4e7b1267fe1e2720a111685bcddf0e781f7e9e0ce59d751c44dcfe5/librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972", size = 217751, upload-time = "2026-02-17T16:11:50.49Z" }, + { url = "https://files.pythonhosted.org/packages/93/c2/0517281cb4d4101c27ab59472924e67f55e375bc46bedae94ac6dc6e1902/librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921", size = 218378, upload-time = "2026-02-17T16:11:51.783Z" }, + { url = "https://files.pythonhosted.org/packages/43/e8/37b3ac108e8976888e559a7b227d0ceac03c384cfd3e7a1c2ee248dbae79/librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0", size = 241199, upload-time = "2026-02-17T16:11:53.561Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/35812d041c53967fedf551a39399271bbe4257e681236a2cf1a69c8e7fa1/librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a", size = 54917, upload-time = "2026-02-17T16:11:54.758Z" }, + { url = "https://files.pythonhosted.org/packages/de/d1/fa5d5331b862b9775aaf2a100f5ef86854e5d4407f71bddf102f4421e034/librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444", size = 62017, upload-time = "2026-02-17T16:11:55.748Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7c/c614252f9acda59b01a66e2ddfd243ed1c7e1deab0293332dfbccf862808/librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d", size = 52441, upload-time = "2026-02-17T16:11:56.801Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3c/f614c8e4eaac7cbf2bbdf9528790b21d89e277ee20d57dc6e559c626105f/librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35", size = 66529, upload-time = "2026-02-17T16:11:57.809Z" }, + { url = "https://files.pythonhosted.org/packages/ab/96/5836544a45100ae411eda07d29e3d99448e5258b6e9c8059deb92945f5c2/librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583", size = 68669, upload-time = "2026-02-17T16:11:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/06/53/f0b992b57af6d5531bf4677d75c44f095f2366a1741fb695ee462ae04b05/librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c", size = 199279, upload-time = "2026-02-17T16:11:59.862Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ad/4848cc16e268d14280d8168aee4f31cea92bbd2b79ce33d3e166f2b4e4fc/librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04", size = 210288, upload-time = "2026-02-17T16:12:00.954Z" }, + { url = "https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363", size = 224809, upload-time = "2026-02-17T16:12:02.108Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d0/78200a45ba3240cb042bc597d6f2accba9193a2c57d0356268cbbe2d0925/librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0", size = 218075, upload-time = "2026-02-17T16:12:03.631Z" }, + { url = "https://files.pythonhosted.org/packages/af/72/a210839fa74c90474897124c064ffca07f8d4b347b6574d309686aae7ca6/librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012", size = 225486, upload-time = "2026-02-17T16:12:04.725Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c1/a03cc63722339ddbf087485f253493e2b013039f5b707e8e6016141130fa/librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb", size = 218219, upload-time = "2026-02-17T16:12:05.828Z" }, + { url = "https://files.pythonhosted.org/packages/58/f5/fff6108af0acf941c6f274a946aea0e484bd10cd2dc37610287ce49388c5/librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b", size = 218750, upload-time = "2026-02-17T16:12:07.09Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/5a387bfef30ec1e4b4f30562c8586566faf87e47d696768c19feb49e3646/librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d", size = 241624, upload-time = "2026-02-17T16:12:08.43Z" }, + { url = "https://files.pythonhosted.org/packages/d4/be/24f8502db11d405232ac1162eb98069ca49c3306c1d75c6ccc61d9af8789/librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a", size = 54969, upload-time = "2026-02-17T16:12:09.633Z" }, + { url = "https://files.pythonhosted.org/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79", size = 62000, upload-time = "2026-02-17T16:12:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/d3/97/68f80ca3ac4924f250cdfa6e20142a803e5e50fca96ef5148c52ee8c10ea/librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0", size = 52495, upload-time = "2026-02-17T16:12:11.633Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6a/907ef6800f7bca71b525a05f1839b21f708c09043b1c6aa77b6b827b3996/librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f", size = 66081, upload-time = "2026-02-17T16:12:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/1b/18/25e991cd5640c9fb0f8d91b18797b29066b792f17bf8493da183bf5caabe/librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c", size = 68309, upload-time = "2026-02-17T16:12:13.756Z" }, + { url = "https://files.pythonhosted.org/packages/a4/36/46820d03f058cfb5a9de5940640ba03165ed8aded69e0733c417bb04df34/librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc", size = 196804, upload-time = "2026-02-17T16:12:14.818Z" }, + { url = "https://files.pythonhosted.org/packages/59/18/5dd0d3b87b8ff9c061849fbdb347758d1f724b9a82241aa908e0ec54ccd0/librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c", size = 206907, upload-time = "2026-02-17T16:12:16.513Z" }, + { url = "https://files.pythonhosted.org/packages/d1/96/ef04902aad1424fd7299b62d1890e803e6ab4018c3044dca5922319c4b97/librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3", size = 221217, upload-time = "2026-02-17T16:12:17.906Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ff/7e01f2dda84a8f5d280637a2e5827210a8acca9a567a54507ef1c75b342d/librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14", size = 214622, upload-time = "2026-02-17T16:12:19.108Z" }, + { url = "https://files.pythonhosted.org/packages/1e/8c/5b093d08a13946034fed57619742f790faf77058558b14ca36a6e331161e/librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7", size = 221987, upload-time = "2026-02-17T16:12:20.331Z" }, + { url = "https://files.pythonhosted.org/packages/d3/cc/86b0b3b151d40920ad45a94ce0171dec1aebba8a9d72bb3fa00c73ab25dd/librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6", size = 215132, upload-time = "2026-02-17T16:12:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/fc/be/8588164a46edf1e69858d952654e216a9a91174688eeefb9efbb38a9c799/librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071", size = 215195, upload-time = "2026-02-17T16:12:23.073Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f2/0b9279bea735c734d69344ecfe056c1ba211694a72df10f568745c899c76/librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78", size = 237946, upload-time = "2026-02-17T16:12:24.275Z" }, + { url = "https://files.pythonhosted.org/packages/e9/cc/5f2a34fbc8aeb35314a3641f9956fa9051a947424652fad9882be7a97949/librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023", size = 50689, upload-time = "2026-02-17T16:12:25.766Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730", size = 57875, upload-time = "2026-02-17T16:12:27.465Z" }, + { url = "https://files.pythonhosted.org/packages/84/0f/2143cb3c3ca48bd3379dcd11817163ca50781927c4537345d608b5045998/librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3", size = 48058, upload-time = "2026-02-17T16:12:28.556Z" }, + { url = "https://files.pythonhosted.org/packages/d2/0e/9b23a87e37baf00311c3efe6b48d6b6c168c29902dfc3f04c338372fd7db/librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1", size = 68313, upload-time = "2026-02-17T16:12:29.659Z" }, + { url = "https://files.pythonhosted.org/packages/db/9a/859c41e5a4f1c84200a7d2b92f586aa27133c8243b6cac9926f6e54d01b9/librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee", size = 70994, upload-time = "2026-02-17T16:12:31.516Z" }, + { url = "https://files.pythonhosted.org/packages/4c/28/10605366ee599ed34223ac2bf66404c6fb59399f47108215d16d5ad751a8/librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7", size = 220770, upload-time = "2026-02-17T16:12:33.294Z" }, + { url = "https://files.pythonhosted.org/packages/af/8d/16ed8fd452dafae9c48d17a6bc1ee3e818fd40ef718d149a8eff2c9f4ea2/librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040", size = 235409, upload-time = "2026-02-17T16:12:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/89/1b/7bdf3e49349c134b25db816e4a3db6b94a47ac69d7d46b1e682c2c4949be/librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e", size = 246473, upload-time = "2026-02-17T16:12:36.656Z" }, + { url = "https://files.pythonhosted.org/packages/4e/8a/91fab8e4fd2a24930a17188c7af5380eb27b203d72101c9cc000dbdfd95a/librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732", size = 238866, upload-time = "2026-02-17T16:12:37.849Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e0/c45a098843fc7c07e18a7f8a24ca8496aecbf7bdcd54980c6ca1aaa79a8e/librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624", size = 250248, upload-time = "2026-02-17T16:12:39.445Z" }, + { url = "https://files.pythonhosted.org/packages/82/30/07627de23036640c952cce0c1fe78972e77d7d2f8fd54fa5ef4554ff4a56/librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4", size = 240629, upload-time = "2026-02-17T16:12:40.889Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c1/55bfe1ee3542eba055616f9098eaf6eddb966efb0ca0f44eaa4aba327307/librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382", size = 239615, upload-time = "2026-02-17T16:12:42.446Z" }, + { url = "https://files.pythonhosted.org/packages/2b/39/191d3d28abc26c9099b19852e6c99f7f6d400b82fa5a4e80291bd3803e19/librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994", size = 263001, upload-time = "2026-02-17T16:12:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/b9/eb/7697f60fbe7042ab4e88f4ee6af496b7f222fffb0a4e3593ef1f29f81652/librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a", size = 51328, upload-time = "2026-02-17T16:12:45.148Z" }, + { url = "https://files.pythonhosted.org/packages/7c/72/34bf2eb7a15414a23e5e70ecb9440c1d3179f393d9349338a91e2781c0fb/librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4", size = 58722, upload-time = "2026-02-17T16:12:46.85Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c8/d148e041732d631fc76036f8b30fae4e77b027a1e95b7a84bb522481a940/librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61", size = 48755, upload-time = "2026-02-17T16:12:47.943Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" }, + { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" }, + { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, + { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, + { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" }, + { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" }, + { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" }, + { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" }, + { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" }, + { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" }, + { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" }, + { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" }, + { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" }, + { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" }, + { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" }, + { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" }, + { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" }, + { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, +] + +[[package]] +name = "mypy" +version = "1.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, + { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, + { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, + { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, + { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, + { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, + { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, + { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, + { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, + { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "narwhals" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/59/81d0f4cad21484083466f278e6b392addd9f4205b48d45b5c8771670ebf8/narwhals-2.17.0.tar.gz", hash = "sha256:ebd5bc95bcfa2f8e89a8ac09e2765a63055162837208e67b42d6eeb6651d5e67", size = 620306, upload-time = "2026-02-23T09:44:34.142Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl", hash = "sha256:2ac5307b7c2b275a7d66eeda906b8605e3d7a760951e188dcfff86e8ebe083dd", size = 444897, upload-time = "2026-02-23T09:44:32.006Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825", size = 16955478, upload-time = "2026-01-31T23:10:25.623Z" }, + { url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1", size = 14965467, upload-time = "2026-01-31T23:10:28.186Z" }, + { url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7", size = 5475172, upload-time = "2026-01-31T23:10:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73", size = 6805145, upload-time = "2026-01-31T23:10:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1", size = 15966084, upload-time = "2026-01-31T23:10:34.502Z" }, + { url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32", size = 16899477, upload-time = "2026-01-31T23:10:37.075Z" }, + { url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390", size = 17323429, upload-time = "2026-01-31T23:10:39.704Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413", size = 18635109, upload-time = "2026-01-31T23:10:41.924Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda", size = 6237915, upload-time = "2026-01-31T23:10:45.26Z" }, + { url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695", size = 12607972, upload-time = "2026-01-31T23:10:47.021Z" }, + { url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3", size = 10485763, upload-time = "2026-01-31T23:10:50.087Z" }, + { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, + { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, + { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, + { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, + { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, + { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, + { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, + { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, + { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, + { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, + { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, + { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, + { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, + { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, + { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, + { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, + { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, + { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, + { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, + { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, + { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, + { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, + { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, + { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082", size = 16833179, upload-time = "2026-01-31T23:12:53.5Z" }, + { url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a", size = 14889755, upload-time = "2026-01-31T23:12:55.933Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920", size = 5399500, upload-time = "2026-01-31T23:12:58.671Z" }, + { url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821", size = 6714252, upload-time = "2026-01-31T23:13:00.518Z" }, + { url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb", size = 15797142, upload-time = "2026-01-31T23:13:02.219Z" }, + { url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0", size = 16727979, upload-time = "2026-01-31T23:13:04.62Z" }, + { url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0", size = 12502577, upload-time = "2026-01-31T23:13:07.08Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, +] + +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, +] + +[[package]] +name = "pillow" +version = "12.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" }, + { url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" }, + { url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" }, + { url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" }, + { url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" }, + { url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" }, + { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, + { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, + { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, + { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, + { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, + { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, + { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, + { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, + { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, + { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, + { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, + { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, + { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, + { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, + { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, + { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, + { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, + { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, + { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, + { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, + { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, + { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, + { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, + { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, + { url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" }, + { url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" }, + { url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" }, + { url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" }, + { url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" }, +] + +[[package]] +name = "plotly" +version = "6.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "narwhals" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/fb/41efe84970cfddefd4ccf025e2cbfafe780004555f583e93dba3dac2cdef/plotly-6.6.0.tar.gz", hash = "sha256:b897f15f3b02028d69f755f236be890ba950d0a42d7dfc619b44e2d8cea8748c", size = 7027956, upload-time = "2026-03-02T21:10:25.321Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl", hash = "sha256:8d6daf0f87412e0c0bfe72e809d615217ab57cc715899a1e5145135a7800d1d0", size = 9910315, upload-time = "2026-03-02T21:10:18.131Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, +] + +[[package]] +name = "pyarrow" +version = "23.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/22/134986a4cc224d593c1afde5494d18ff629393d74cc2eddb176669f234a4/pyarrow-23.0.1.tar.gz", hash = "sha256:b8c5873e33440b2bc2f4a79d2b47017a89c5a24116c055625e6f2ee50523f019", size = 1167336, upload-time = "2026-02-16T10:14:12.39Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/41/8e6b6ef7e225d4ceead8459427a52afdc23379768f54dd3566014d7618c1/pyarrow-23.0.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6f0147ee9e0386f519c952cc670eb4a8b05caa594eeffe01af0e25f699e4e9bb", size = 34302230, upload-time = "2026-02-16T10:09:03.859Z" }, + { url = "https://files.pythonhosted.org/packages/bf/4a/1472c00392f521fea03ae93408bf445cc7bfa1ab81683faf9bc188e36629/pyarrow-23.0.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:0ae6e17c828455b6265d590100c295193f93cc5675eb0af59e49dbd00d2de350", size = 35850050, upload-time = "2026-02-16T10:09:11.877Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b2/bd1f2f05ded56af7f54d702c8364c9c43cd6abb91b0e9933f3d77b4f4132/pyarrow-23.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:fed7020203e9ef273360b9e45be52a2a47d3103caf156a30ace5247ffb51bdbd", size = 44491918, upload-time = "2026-02-16T10:09:18.144Z" }, + { url = "https://files.pythonhosted.org/packages/0b/62/96459ef5b67957eac38a90f541d1c28833d1b367f014a482cb63f3b7cd2d/pyarrow-23.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:26d50dee49d741ac0e82185033488d28d35be4d763ae6f321f97d1140eb7a0e9", size = 47562811, upload-time = "2026-02-16T10:09:25.792Z" }, + { url = "https://files.pythonhosted.org/packages/7d/94/1170e235add1f5f45a954e26cd0e906e7e74e23392dcb560de471f7366ec/pyarrow-23.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3c30143b17161310f151f4a2bcfe41b5ff744238c1039338779424e38579d701", size = 48183766, upload-time = "2026-02-16T10:09:34.645Z" }, + { url = "https://files.pythonhosted.org/packages/0e/2d/39a42af4570377b99774cdb47f63ee6c7da7616bd55b3d5001aa18edfe4f/pyarrow-23.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db2190fa79c80a23fdd29fef4b8992893f024ae7c17d2f5f4db7171fa30c2c78", size = 50607669, upload-time = "2026-02-16T10:09:44.153Z" }, + { url = "https://files.pythonhosted.org/packages/00/ca/db94101c187f3df742133ac837e93b1f269ebdac49427f8310ee40b6a58f/pyarrow-23.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:f00f993a8179e0e1c9713bcc0baf6d6c01326a406a9c23495ec1ba9c9ebf2919", size = 27527698, upload-time = "2026-02-16T10:09:50.263Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4b/4166bb5abbfe6f750fc60ad337c43ecf61340fa52ab386da6e8dbf9e63c4/pyarrow-23.0.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f4b0dbfa124c0bb161f8b5ebb40f1a680b70279aa0c9901d44a2b5a20806039f", size = 34214575, upload-time = "2026-02-16T10:09:56.225Z" }, + { url = "https://files.pythonhosted.org/packages/e1/da/3f941e3734ac8088ea588b53e860baeddac8323ea40ce22e3d0baa865cc9/pyarrow-23.0.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:7707d2b6673f7de054e2e83d59f9e805939038eebe1763fe811ee8fa5c0cd1a7", size = 35832540, upload-time = "2026-02-16T10:10:03.428Z" }, + { url = "https://files.pythonhosted.org/packages/88/7c/3d841c366620e906d54430817531b877ba646310296df42ef697308c2705/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:86ff03fb9f1a320266e0de855dee4b17da6794c595d207f89bba40d16b5c78b9", size = 44470940, upload-time = "2026-02-16T10:10:10.704Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a5/da83046273d990f256cb79796a190bbf7ec999269705ddc609403f8c6b06/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:813d99f31275919c383aab17f0f455a04f5a429c261cc411b1e9a8f5e4aaaa05", size = 47586063, upload-time = "2026-02-16T10:10:17.95Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/b7d2ebcff47a514f47f9da1e74b7949138c58cfeb108cdd4ee62f43f0cf3/pyarrow-23.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bf5842f960cddd2ef757d486041d57c96483efc295a8c4a0e20e704cbbf39c67", size = 48173045, upload-time = "2026-02-16T10:10:25.363Z" }, + { url = "https://files.pythonhosted.org/packages/43/b2/b40961262213beaba6acfc88698eb773dfce32ecdf34d19291db94c2bd73/pyarrow-23.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564baf97c858ecc03ec01a41062e8f4698abc3e6e2acd79c01c2e97880a19730", size = 50621741, upload-time = "2026-02-16T10:10:33.477Z" }, + { url = "https://files.pythonhosted.org/packages/f6/70/1fdda42d65b28b078e93d75d371b2185a61da89dda4def8ba6ba41ebdeb4/pyarrow-23.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:07deae7783782ac7250989a7b2ecde9b3c343a643f82e8a4df03d93b633006f0", size = 27620678, upload-time = "2026-02-16T10:10:39.31Z" }, + { url = "https://files.pythonhosted.org/packages/47/10/2cbe4c6f0fb83d2de37249567373d64327a5e4d8db72f486db42875b08f6/pyarrow-23.0.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6b8fda694640b00e8af3c824f99f789e836720aa8c9379fb435d4c4953a756b8", size = 34210066, upload-time = "2026-02-16T10:10:45.487Z" }, + { url = "https://files.pythonhosted.org/packages/cb/4f/679fa7e84dadbaca7a65f7cdba8d6c83febbd93ca12fa4adf40ba3b6362b/pyarrow-23.0.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:8ff51b1addc469b9444b7c6f3548e19dc931b172ab234e995a60aea9f6e6025f", size = 35825526, upload-time = "2026-02-16T10:10:52.266Z" }, + { url = "https://files.pythonhosted.org/packages/f9/63/d2747d930882c9d661e9398eefc54f15696547b8983aaaf11d4a2e8b5426/pyarrow-23.0.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:71c5be5cbf1e1cb6169d2a0980850bccb558ddc9b747b6206435313c47c37677", size = 44473279, upload-time = "2026-02-16T10:11:01.557Z" }, + { url = "https://files.pythonhosted.org/packages/b3/93/10a48b5e238de6d562a411af6467e71e7aedbc9b87f8d3a35f1560ae30fb/pyarrow-23.0.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:9b6f4f17b43bc39d56fec96e53fe89d94bac3eb134137964371b45352d40d0c2", size = 47585798, upload-time = "2026-02-16T10:11:09.401Z" }, + { url = "https://files.pythonhosted.org/packages/5c/20/476943001c54ef078dbf9542280e22741219a184a0632862bca4feccd666/pyarrow-23.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fc13fc6c403d1337acab46a2c4346ca6c9dec5780c3c697cf8abfd5e19b6b37", size = 48179446, upload-time = "2026-02-16T10:11:17.781Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b6/5dd0c47b335fcd8edba9bfab78ad961bd0fd55ebe53468cc393f45e0be60/pyarrow-23.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c16ed4f53247fa3ffb12a14d236de4213a4415d127fe9cebed33d51671113e2", size = 50623972, upload-time = "2026-02-16T10:11:26.185Z" }, + { url = "https://files.pythonhosted.org/packages/d5/09/a532297c9591a727d67760e2e756b83905dd89adb365a7f6e9c72578bcc1/pyarrow-23.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:cecfb12ef629cf6be0b1887f9f86463b0dd3dc3195ae6224e74006be4736035a", size = 27540749, upload-time = "2026-02-16T10:12:23.297Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8e/38749c4b1303e6ae76b3c80618f84861ae0c55dd3c2273842ea6f8258233/pyarrow-23.0.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:29f7f7419a0e30264ea261fdc0e5fe63ce5a6095003db2945d7cd78df391a7e1", size = 34471544, upload-time = "2026-02-16T10:11:32.535Z" }, + { url = "https://files.pythonhosted.org/packages/a3/73/f237b2bc8c669212f842bcfd842b04fc8d936bfc9d471630569132dc920d/pyarrow-23.0.1-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:33d648dc25b51fd8055c19e4261e813dfc4d2427f068bcecc8b53d01b81b0500", size = 35949911, upload-time = "2026-02-16T10:11:39.813Z" }, + { url = "https://files.pythonhosted.org/packages/0c/86/b912195eee0903b5611bf596833def7d146ab2d301afeb4b722c57ffc966/pyarrow-23.0.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cd395abf8f91c673dd3589cadc8cc1ee4e8674fa61b2e923c8dd215d9c7d1f41", size = 44520337, upload-time = "2026-02-16T10:11:47.764Z" }, + { url = "https://files.pythonhosted.org/packages/69/c2/f2a717fb824f62d0be952ea724b4f6f9372a17eed6f704b5c9526f12f2f1/pyarrow-23.0.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:00be9576d970c31defb5c32eb72ef585bf600ef6d0a82d5eccaae96639cf9d07", size = 47548944, upload-time = "2026-02-16T10:11:56.607Z" }, + { url = "https://files.pythonhosted.org/packages/84/a7/90007d476b9f0dc308e3bc57b832d004f848fd6c0da601375d20d92d1519/pyarrow-23.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c2139549494445609f35a5cda4eb94e2c9e4d704ce60a095b342f82460c73a83", size = 48236269, upload-time = "2026-02-16T10:12:04.47Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3f/b16fab3e77709856eb6ac328ce35f57a6d4a18462c7ca5186ef31b45e0e0/pyarrow-23.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7044b442f184d84e2351e5084600f0d7343d6117aabcbc1ac78eb1ae11eb4125", size = 50604794, upload-time = "2026-02-16T10:12:11.797Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a1/22df0620a9fac31d68397a75465c344e83c3dfe521f7612aea33e27ab6c0/pyarrow-23.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a35581e856a2fafa12f3f54fce4331862b1cfb0bef5758347a858a4aa9d6bae8", size = 27660642, upload-time = "2026-02-16T10:12:17.746Z" }, + { url = "https://files.pythonhosted.org/packages/8d/1b/6da9a89583ce7b23ac611f183ae4843cd3a6cf54f079549b0e8c14031e73/pyarrow-23.0.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:5df1161da23636a70838099d4aaa65142777185cc0cdba4037a18cee7d8db9ca", size = 34238755, upload-time = "2026-02-16T10:12:32.819Z" }, + { url = "https://files.pythonhosted.org/packages/ae/b5/d58a241fbe324dbaeb8df07be6af8752c846192d78d2272e551098f74e88/pyarrow-23.0.1-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:fa8e51cb04b9f8c9c5ace6bab63af9a1f88d35c0d6cbf53e8c17c098552285e1", size = 35847826, upload-time = "2026-02-16T10:12:38.949Z" }, + { url = "https://files.pythonhosted.org/packages/54/a5/8cbc83f04aba433ca7b331b38f39e000efd9f0c7ce47128670e737542996/pyarrow-23.0.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:0b95a3994f015be13c63148fef8832e8a23938128c185ee951c98908a696e0eb", size = 44536859, upload-time = "2026-02-16T10:12:45.467Z" }, + { url = "https://files.pythonhosted.org/packages/36/2e/c0f017c405fcdc252dbccafbe05e36b0d0eb1ea9a958f081e01c6972927f/pyarrow-23.0.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4982d71350b1a6e5cfe1af742c53dfb759b11ce14141870d05d9e540d13bc5d1", size = 47614443, upload-time = "2026-02-16T10:12:55.525Z" }, + { url = "https://files.pythonhosted.org/packages/af/6b/2314a78057912f5627afa13ba43809d9d653e6630859618b0fd81a4e0759/pyarrow-23.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c250248f1fe266db627921c89b47b7c06fee0489ad95b04d50353537d74d6886", size = 48232991, upload-time = "2026-02-16T10:13:04.729Z" }, + { url = "https://files.pythonhosted.org/packages/40/f2/1bcb1d3be3460832ef3370d621142216e15a2c7c62602a4ea19ec240dd64/pyarrow-23.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f4763b83c11c16e5f4c15601ba6dfa849e20723b46aa2617cb4bffe8768479f", size = 50645077, upload-time = "2026-02-16T10:13:14.147Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3f/b1da7b61cd66566a4d4c8383d376c606d1c34a906c3f1cb35c479f59d1aa/pyarrow-23.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:3a4c85ef66c134161987c17b147d6bffdca4566f9a4c1d81a0a01cdf08414ea5", size = 28234271, upload-time = "2026-02-16T10:14:09.397Z" }, + { url = "https://files.pythonhosted.org/packages/b5/78/07f67434e910a0f7323269be7bfbf58699bd0c1d080b18a1ab49ba943fe8/pyarrow-23.0.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:17cd28e906c18af486a499422740298c52d7c6795344ea5002a7720b4eadf16d", size = 34488692, upload-time = "2026-02-16T10:13:21.541Z" }, + { url = "https://files.pythonhosted.org/packages/50/76/34cf7ae93ece1f740a04910d9f7e80ba166b9b4ab9596a953e9e62b90fe1/pyarrow-23.0.1-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:76e823d0e86b4fb5e1cf4a58d293036e678b5a4b03539be933d3b31f9406859f", size = 35964383, upload-time = "2026-02-16T10:13:28.63Z" }, + { url = "https://files.pythonhosted.org/packages/46/90/459b827238936d4244214be7c684e1b366a63f8c78c380807ae25ed92199/pyarrow-23.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a62e1899e3078bf65943078b3ad2a6ddcacf2373bc06379aac61b1e548a75814", size = 44538119, upload-time = "2026-02-16T10:13:35.506Z" }, + { url = "https://files.pythonhosted.org/packages/28/a1/93a71ae5881e99d1f9de1d4554a87be37da11cd6b152239fb5bd924fdc64/pyarrow-23.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:df088e8f640c9fae3b1f495b3c64755c4e719091caf250f3a74d095ddf3c836d", size = 47571199, upload-time = "2026-02-16T10:13:42.504Z" }, + { url = "https://files.pythonhosted.org/packages/88/a3/d2c462d4ef313521eaf2eff04d204ac60775263f1fb08c374b543f79f610/pyarrow-23.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:46718a220d64677c93bc243af1d44b55998255427588e400677d7192671845c7", size = 48259435, upload-time = "2026-02-16T10:13:49.226Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f1/11a544b8c3d38a759eb3fbb022039117fd633e9a7b19e4841cc3da091915/pyarrow-23.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a09f3876e87f48bc2f13583ab551f0379e5dfb83210391e68ace404181a20690", size = 50629149, upload-time = "2026-02-16T10:13:57.238Z" }, + { url = "https://files.pythonhosted.org/packages/50/f2/c0e76a0b451ffdf0cf788932e182758eb7558953f4f27f1aff8e2518b653/pyarrow-23.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:527e8d899f14bd15b740cd5a54ad56b7f98044955373a17179d5956ddb93d9ce", size = 28365807, upload-time = "2026-02-16T10:14:03.892Z" }, +] + +[[package]] +name = "pydeck" +version = "0.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/ca/40e14e196864a0f61a92abb14d09b3d3da98f94ccb03b49cf51688140dab/pydeck-0.9.1.tar.gz", hash = "sha256:f74475ae637951d63f2ee58326757f8d4f9cd9f2a457cf42950715003e2cb605", size = 3832240, upload-time = "2024-05-10T15:36:21.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/4c/b888e6cf58bd9db9c93f40d1c6be8283ff49d88919231afe93a6bcf61626/pydeck-0.9.1-py2.py3-none-any.whl", hash = "sha256:b3f75ba0d273fc917094fa61224f3f6076ca8752b93d46faf3bcfd9f9d59b038", size = 6900403, upload-time = "2024-05-10T15:36:17.36Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-vpp" +version = "0.0.2" +source = { editable = "." } +dependencies = [ + { name = "flask" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "plotly" }, + { name = "scipy" }, + { name = "streamlit" }, + { name = "tqdm" }, +] + +[package.optional-dependencies] +api = [ + { name = "flask" }, +] +demo = [ + { name = "streamlit" }, +] +dev = [ + { name = "mypy" }, + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "flask" }, + { name = "flask", marker = "extra == 'api'" }, + { name = "matplotlib", specifier = ">=3.8" }, + { name = "mypy", marker = "extra == 'dev'" }, + { name = "numpy", specifier = ">=2.0" }, + { name = "plotly", specifier = ">=6.6.0" }, + { name = "pytest", marker = "extra == 'dev'" }, + { name = "ruff", marker = "extra == 'dev'" }, + { name = "scipy", specifier = ">=1.12" }, + { name = "streamlit", specifier = ">=1.37" }, + { name = "streamlit", marker = "extra == 'demo'", specifier = ">=1.37" }, + { name = "tqdm", specifier = ">=4.66" }, +] +provides-extras = ["api", "demo", "dev"] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/04/eab13a954e763b0606f460443fcbf6bb5a0faf06890ea3754ff16523dce5/ruff-0.15.2.tar.gz", hash = "sha256:14b965afee0969e68bb871eba625343b8673375f457af4abe98553e8bbb98342", size = 4558148, upload-time = "2026-02-19T22:32:20.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/70/3a4dc6d09b13cb3e695f28307e5d889b2e1a66b7af9c5e257e796695b0e6/ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d", size = 10430565, upload-time = "2026-02-19T22:32:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/71/0b/bb8457b56185ece1305c666dc895832946d24055be90692381c31d57466d/ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e", size = 10820354, upload-time = "2026-02-19T22:32:07.366Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c1/e0532d7f9c9e0b14c46f61b14afd563298b8b83f337b6789ddd987e46121/ruff-0.15.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e36dee3a64be0ebd23c86ffa3aa3fd3ac9a712ff295e192243f814a830b6bd87", size = 10170767, upload-time = "2026-02-19T22:32:13.188Z" }, + { url = "https://files.pythonhosted.org/packages/47/e8/da1aa341d3af017a21c7a62fb5ec31d4e7ad0a93ab80e3a508316efbcb23/ruff-0.15.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9fb47b6d9764677f8c0a193c0943ce9a05d6763523f132325af8a858eadc2b9", size = 10529591, upload-time = "2026-02-19T22:32:02.547Z" }, + { url = "https://files.pythonhosted.org/packages/93/74/184fbf38e9f3510231fbc5e437e808f0b48c42d1df9434b208821efcd8d6/ruff-0.15.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f376990f9d0d6442ea9014b19621d8f2aaf2b8e39fdbfc79220b7f0c596c9b80", size = 10260771, upload-time = "2026-02-19T22:32:36.938Z" }, + { url = "https://files.pythonhosted.org/packages/05/ac/605c20b8e059a0bc4b42360414baa4892ff278cec1c91fff4be0dceedefd/ruff-0.15.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dcc987551952d73cbf5c88d9fdee815618d497e4df86cd4c4824cc59d5dd75f", size = 11045791, upload-time = "2026-02-19T22:32:31.642Z" }, + { url = "https://files.pythonhosted.org/packages/fd/52/db6e419908f45a894924d410ac77d64bdd98ff86901d833364251bd08e22/ruff-0.15.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a47fd785cbe8c01b9ff45031af875d101b040ad8f4de7bbb716487c74c9a77", size = 11879271, upload-time = "2026-02-19T22:32:29.305Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d8/7992b18f2008bdc9231d0f10b16df7dda964dbf639e2b8b4c1b4e91b83af/ruff-0.15.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe9f49354866e575b4c6943856989f966421870e85cd2ac94dccb0a9dcb2fea", size = 11303707, upload-time = "2026-02-19T22:32:22.492Z" }, + { url = "https://files.pythonhosted.org/packages/d7/02/849b46184bcfdd4b64cde61752cc9a146c54759ed036edd11857e9b8443b/ruff-0.15.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a672c82b5f9887576087d97be5ce439f04bbaf548ee987b92d3a7dede41d3a", size = 11149151, upload-time = "2026-02-19T22:32:44.234Z" }, + { url = "https://files.pythonhosted.org/packages/70/04/f5284e388bab60d1d3b99614a5a9aeb03e0f333847e2429bebd2aaa1feec/ruff-0.15.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ecc64f46f7019e2bcc3cdc05d4a7da958b629a5ab7033195e11a438403d956", size = 11091132, upload-time = "2026-02-19T22:32:24.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ae/88d844a21110e14d92cf73d57363fab59b727ebeabe78009b9ccb23500af/ruff-0.15.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8dcf243b15b561c655c1ef2f2b0050e5d50db37fe90115507f6ff37d865dc8b4", size = 10504717, upload-time = "2026-02-19T22:32:26.75Z" }, + { url = "https://files.pythonhosted.org/packages/64/27/867076a6ada7f2b9c8292884ab44d08fd2ba71bd2b5364d4136f3cd537e1/ruff-0.15.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dab6941c862c05739774677c6273166d2510d254dac0695c0e3f5efa1b5585de", size = 10263122, upload-time = "2026-02-19T22:32:10.036Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ef/faf9321d550f8ebf0c6373696e70d1758e20ccdc3951ad7af00c0956be7c/ruff-0.15.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b9164f57fc36058e9a6806eb92af185b0697c9fe4c7c52caa431c6554521e5c", size = 10735295, upload-time = "2026-02-19T22:32:39.227Z" }, + { url = "https://files.pythonhosted.org/packages/2f/55/e8089fec62e050ba84d71b70e7834b97709ca9b7aba10c1a0b196e493f97/ruff-0.15.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:80d24fcae24d42659db7e335b9e1531697a7102c19185b8dc4a028b952865fd8", size = 11241641, upload-time = "2026-02-19T22:32:34.617Z" }, + { url = "https://files.pythonhosted.org/packages/23/01/1c30526460f4d23222d0fabd5888868262fd0e2b71a00570ca26483cd993/ruff-0.15.2-py3-none-win32.whl", hash = "sha256:fd5ff9e5f519a7e1bd99cbe8daa324010a74f5e2ebc97c6242c08f26f3714f6f", size = 10507885, upload-time = "2026-02-19T22:32:15.635Z" }, + { url = "https://files.pythonhosted.org/packages/5c/10/3d18e3bbdf8fc50bbb4ac3cc45970aa5a9753c5cb51bf9ed9a3cd8b79fa3/ruff-0.15.2-py3-none-win_amd64.whl", hash = "sha256:d20014e3dfa400f3ff84830dfb5755ece2de45ab62ecea4af6b7262d0fb4f7c5", size = 11623725, upload-time = "2026-02-19T22:32:04.947Z" }, + { url = "https://files.pythonhosted.org/packages/6d/78/097c0798b1dab9f8affe73da9642bb4500e098cb27fd8dc9724816ac747b/ruff-0.15.2-py3-none-win_arm64.whl", hash = "sha256:cabddc5822acdc8f7b5527b36ceac55cc51eec7b1946e60181de8fe83ca8876e", size = 10941649, upload-time = "2026-02-19T22:32:18.108Z" }, +] + +[[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, 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 = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, +] + +[[package]] +name = "streamlit" +version = "1.54.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altair" }, + { name = "blinker" }, + { name = "cachetools" }, + { name = "click" }, + { name = "gitpython" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "protobuf" }, + { name = "pyarrow" }, + { name = "pydeck" }, + { name = "requests" }, + { name = "tenacity" }, + { name = "toml" }, + { name = "tornado" }, + { name = "typing-extensions" }, + { name = "watchdog", marker = "sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/66/d887ee80ea85f035baee607c60af024994e17ae9b921277fca9675e76ecf/streamlit-1.54.0.tar.gz", hash = "sha256:09965e6ae7eb0357091725de1ce2a3f7e4be155c2464c505c40a3da77ab69dd8", size = 8662292, upload-time = "2026-02-04T16:37:54.734Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/1d/40de1819374b4f0507411a60f4d2de0d620a9b10c817de5925799132b6c9/streamlit-1.54.0-py3-none-any.whl", hash = "sha256:a7b67d6293a9f5f6b4d4c7acdbc4980d7d9f049e78e404125022ecb1712f79fc", size = 9119730, upload-time = "2026-02-04T16:37:52.199Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" }, + { url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" }, + { url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" }, + { url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" }, + { url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" }, + { url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/f1/ee81806690a87dab5f5653c1f146c92bc066d7f4cebc603ef88eb9e13957/werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25", size = 864736, upload-time = "2026-02-19T15:17:18.884Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/ec/d58832f89ede95652fd01f4f24236af7d32b70cab2196dfcc2d2fd13c5c2/werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131", size = 225166, upload-time = "2026-02-19T15:17:17.475Z" }, +]