diff --git a/CHANGELOG.md b/CHANGELOG.md index 6960680..59ce4d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.10.0] - 2026-03-01 + +### Added + +- NumPy array support via `NumberArray` class + - Vectorized arithmetic with unit tracking and uncertainty propagation + - Vectorized conversion: `heights.to(units.foot)` + - Reduction operations: `sum()`, `mean()`, `std()`, `min()`, `max()` + - Comparison operators returning boolean arrays for filtering + - N-D array support with broadcasting + - Callable syntax: `units.meter([1, 2, 3])` returns `NumberArray` +- Pandas integration via `NumberSeries` and `UconSeriesAccessor` + - `df['height'].ucon.with_unit(units.meter).to(units.foot)` + - Arithmetic preserves unit semantics +- Polars integration via `NumberColumn` + - Wraps `pl.Series` with unit metadata + - `.to()` conversion with unit tracking +- Map array support for vectorized operations + - `LinearMap`, `AffineMap`, `LogMap`, `ExpMap` work with numpy arrays + - `_log()` and `_exp()` helpers for scalar/array compatibility +- Optional dependencies: `ucon[numpy]`, `ucon[pandas]`, `ucon[polars]` +- Performance caching for repeated operations + - Conversion path caching in `ConversionGraph` + - Scale factor caching for scale-only conversions + - Unit multiplication/division caching + - `fold_scale()` result caching on `UnitProduct` +- Performance benchmarks: `make benchmark`, `make benchmark-pint` +- Documentation: `docs/guides/numpy-arrays.md`, `docs/guides/pandas-integration.md`, `docs/guides/polars-integration.md` +- Example notebook: `examples/scientific_computing.ipynb` + +### Changed + +- `Unit.__call__` and `UnitProduct.__call__` return `NumberArray` when given list or ndarray input + ## [0.9.4] - 2026-02-28 ### Changed @@ -454,7 +488,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial commit -[Unreleased]: https://github.com/withtwoemms/ucon/compare/0.9.4...HEAD +[Unreleased]: https://github.com/withtwoemms/ucon/compare/0.10.0...HEAD +[0.10.0]: https://github.com/withtwoemms/ucon/compare/0.9.4...0.10.0 [0.9.4]: https://github.com/withtwoemms/ucon/compare/0.9.3...0.9.4 [0.9.3]: https://github.com/withtwoemms/ucon/compare/0.9.2...0.9.3 [0.9.2]: https://github.com/withtwoemms/ucon/compare/0.9.1...0.9.2 diff --git a/Makefile b/Makefile index f4e4d68..e90c73d 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,8 @@ DEPS_INSTALLED := ${UV_VENV}/.deps-installed TESTDIR := tests/ TESTNAME ?= COVERAGE ?= true +BENCH_SIZES ?= 1000 10000 100000 +BENCH_ITERATIONS ?= 50 # --- Color Setup --- GREEN := \033[0;32m @@ -35,12 +37,16 @@ help: @echo " ${CYAN}clean${RESET} - Remove build artifacts and caches" @echo " ${CYAN}stubs${RESET} - Generate dimension.pyi type stubs" @echo " ${CYAN}stubs-check${RESET} - Verify stubs are current (for CI)" + @echo " ${CYAN}benchmark${RESET} - Run array performance benchmarks" + @echo " ${CYAN}benchmark-pint${RESET} - Run benchmarks with pint comparison" @echo "" @echo "${YELLOW}Variables:${RESET}\n" @echo " PYTHON=${PYTHON} - Python version for test target" @echo " UV_VENV=${UV_VENV} - Path to virtual environment" @echo " TESTNAME= - Specific test to run (e.g., tests.ucon.test_core)" @echo " COVERAGE=${COVERAGE} - Enable coverage (true/false)" + @echo " BENCH_SIZES=${BENCH_SIZES} - Array sizes for benchmarks" + @echo " BENCH_ITERATIONS=${BENCH_ITERATIONS} - Iterations per benchmark" @echo "" # --- uv Installation --- @@ -163,3 +169,18 @@ stubs-check: ${DEPS_INSTALLED} @echo "${GREEN}Verifying dimension stubs are current...${RESET}" @UV_PROJECT_ENVIRONMENT=${UV_VENV} uv run --python ${PYTHON} \ python scripts/generate_dimension_stubs.py --check + +# --- Benchmarks --- +.PHONY: benchmark +benchmark: ${DEPS_INSTALLED} + @echo "${GREEN}Running array performance benchmarks...${RESET}" + @UV_PROJECT_ENVIRONMENT=${UV_VENV} uv run --python ${PYTHON} \ + python benchmarks/array_operations.py --sizes $(BENCH_SIZES) --iterations $(BENCH_ITERATIONS) + +.PHONY: benchmark-pint +benchmark-pint: ${DEPS_INSTALLED} + @echo "${GREEN}Installing pint for comparison...${RESET}" + @uv pip install pint --python ${UV_VENV}/bin/python 2>/dev/null || true + @echo "${GREEN}Running benchmarks with pint comparison...${RESET}" + @UV_PROJECT_ENVIRONMENT=${UV_VENV} uv run --python ${PYTHON} \ + python benchmarks/array_operations.py --sizes $(BENCH_SIZES) --iterations $(BENCH_ITERATIONS) --with-pint diff --git a/README.md b/README.md index 0f6e247..f1cf670 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,9 @@ pip install ucon With extras: ```bash +pip install ucon[numpy] # NumPy array support +pip install ucon[pandas] # Pandas DataFrame integration +pip install ucon[polars] # Polars DataFrame integration pip install ucon[pydantic] # Pydantic v2 integration pip install ucon-tools[mcp] # MCP server for AI agents (separate package) ``` @@ -92,6 +95,22 @@ speed(units.meter(100), units.second(10)) # <10.0 m/s> speed(units.second(100), units.second(10)) # raises ValueError ``` +### NumPy Arrays + +```python +from ucon import units + +# Vectorized operations on arrays +heights = units.meter([1.7, 1.8, 1.9, 2.0]) +heights_ft = heights.to(units.foot) # <[5.577, 5.906, 6.234, 6.562] ft> + +# Arithmetic with unit tracking +areas = heights * units.meter([2, 2, 2, 2]) # m^2 + +# Statistical reductions preserve units +avg = heights.mean() # <1.85 m> +``` + ### Pydantic Integration ```python @@ -131,6 +150,8 @@ AI agents can then convert units, check dimensions, and perform factor-label cal ## Features +- **NumPy arrays** — Vectorized operations with `NumberArray` for batch computations +- **Pandas/Polars** — Unit-aware DataFrames with `NumberSeries` and `NumberColumn` - **Physical constants** — CODATA 2022 values with uncertainty propagation (`E = m * c**2`) - **Custom constants** — Define domain-specific constants with uncertainty propagation - **String parsing** — `parse("9.81 m/s^2")` with uncertainty support (`1.234 ± 0.005 m`) @@ -157,7 +178,7 @@ AI agents can then convert units, check dimensions, and perform factor-label cal | **0.7.x** | Compute Tool + Extension API | Complete | | **0.8.x** | Basis Abstraction + String Parsing | Complete | | **0.9.x** | Constants + Natural Units | Complete | -| **0.10.x** | NumPy/Polars Integration | Planned | +| **0.10.x** | NumPy/Pandas/Polars Integration | Current | | **1.0.0** | API Stability | Planned | See full roadmap: [ROADMAP.md](https://github.com/withtwoemms/ucon/blob/main/ROADMAP.md) @@ -169,7 +190,7 @@ See full roadmap: [ROADMAP.md](https://github.com/withtwoemms/ucon/blob/main/ROA | Section | Description | |---------|-------------| | [Getting Started](https://docs.ucon.dev/getting-started/) | Why ucon, quickstart, installation | -| [Guides](https://docs.ucon.dev/guides/) | MCP server, Pydantic, custom units, dimensional analysis | +| [Guides](https://docs.ucon.dev/guides/) | NumPy/Pandas/Polars, MCP server, Pydantic, custom units | | [Reference](https://docs.ucon.dev/reference/) | API docs, unit tables, MCP tool schemas | | [Architecture](https://docs.ucon.dev/architecture/) | Design principles, ConversionGraph, comparison with Pint | diff --git a/ROADMAP.md b/ROADMAP.md index 717daa0..43c187b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -48,14 +48,33 @@ ucon is a dimensional analysis library for engineers building systems where unit | v0.9.2 | MCP Constants Tools | Complete | | v0.9.3 | Natural Units + MCP Session Fixes | Complete | | v0.9.4 | MCP Extraction | Complete | -| v0.10.0 | Scientific Computing | Planned | +| v0.10.0 | Scientific Computing | Complete | | v1.0.0 | API Stability | Planned | --- -## Current Version: **v0.9.4** (complete) - -Building on v0.9.3 baseline: +## Current Version: **v0.10.0** (complete) + +Building on v0.9.4 baseline: +- `ucon.numpy` (`NumberArray` class for vectorized operations on dimensioned arrays) +- `ucon.pandas` (`NumberSeries` wrapper and `UconSeriesAccessor` for Pandas integration) +- `ucon.polars` (`NumberColumn` wrapper for Polars integration) +- NumPy array support: `units.meter([1, 2, 3])` → `NumberArray` +- Vectorized conversion: `heights.to(units.foot)` on arrays +- Vectorized arithmetic: add, sub, mul, div with unit tracking +- Per-element and uniform uncertainty propagation through arrays +- Reduction operations: `sum()`, `mean()`, `std()`, `min()`, `max()` with uncertainty +- Comparison operators returning boolean arrays for filtering +- N-D array support (not just 1D) +- Broadcasting support for compatible shapes +- Pandas accessor: `df['height'].ucon.with_unit(units.meter).to(units.foot)` +- Polars column wrapper: `NumberColumn(series, unit=units.meter)` +- Optional dependencies: `pip install ucon[numpy]`, `ucon[pandas]`, `ucon[polars]` +- Graceful degradation: ImportError with clear messages when deps not installed +- Performance caching: conversion paths, scale factors, unit multiplication +- Performance benchmarks: `make benchmark`, `make benchmark-pint` + +Previous versions include: - `ucon.basis` (`Basis`, `BasisComponent`, `Vector`, `BasisTransform`, `BasisGraph`, `ConstantAwareBasisTransform`) - `ucon.bases` (standard bases: `SI`, `CGS`, `CGS_ESU`, `NATURAL`; standard transforms including `SI_TO_NATURAL`) - `ucon.dimension` (`Dimension` as frozen dataclass backed by basis-aware `Vector`) @@ -675,25 +694,29 @@ Prerequisite for factor-label chains with countable items (tablets, doses). --- -## v0.10.0 — Scientific Computing +## v0.10.0 — Scientific Computing (Complete) **Theme:** NumPy and DataFrame integration. -- [ ] `Number` wraps `np.ndarray` values -- [ ] Vectorized conversion and arithmetic -- [ ] Vectorized uncertainty propagation -- [ ] Polars integration: `NumberColumn` type -- [ ] Pandas integration: `NumberSeries` type -- [ ] Column-wise conversion -- [ ] Unit-aware arithmetic on columns -- [ ] Performance benchmarks +- [x] `NumberArray` class with vectorized arithmetic and conversion +- [x] Scalar and per-element uncertainty propagation +- [x] Reduction operations: `sum()`, `mean()`, `std()`, `min()`, `max()` +- [x] Comparison operators returning boolean arrays +- [x] Callable syntax: `meter([1, 2, 3])` → `NumberArray` +- [x] Map array support (`LinearMap`, `AffineMap`, `LogMap`, `ExpMap`) +- [x] Pandas integration: `NumberSeries` and `UconSeriesAccessor` +- [x] Polars integration: `NumberColumn` +- [x] Optional dependencies: `ucon[numpy]`, `ucon[pandas]`, `ucon[polars]` +- [x] Performance caching (conversion paths, scale factors, unit products) +- [x] Benchmarks: `make benchmark`, `make benchmark-pint` **Outcomes:** - Seamless integration with NumPy-based scientific workflows -- Efficient batch conversions for large datasets -- First-class support for data science workflows +- Efficient batch conversions for large datasets (100M+ elements/sec) +- First-class support for data science workflows with Pandas and Polars - Unit-safe transformations on tabular data -- Performance characteristics documented and optimized +- Performance competitive with pint, much faster on creation +- Lazy caching approximates fixed registry without startup cost --- @@ -718,6 +741,7 @@ Prerequisite for factor-label chains with countable items (tablets, doses). | Feature | Notes | |---------|-------| +| Cache Warming | `warm_cache()` API for precomputing common conversion paths | | Decompose Tool | SLM enablement: deterministic `decompose` → `compute` pipeline | | Uncertainty correlation | Full covariance tracking | | Cython optimization | Performance parity with unyt | diff --git a/docs/guides/index.md b/docs/guides/index.md index 7ea50fa..3d47245 100644 --- a/docs/guides/index.md +++ b/docs/guides/index.md @@ -13,6 +13,12 @@ Task-oriented guides for common use cases. - **[Pydantic Integration](pydantic-integration.md)** - Type-safe dimensional fields in Pydantic models - **[Config Safety](dimensional-safety-config.md)** - Dimensional safety for configuration files +## Scientific Computing + +- **[NumPy Arrays](numpy-arrays.md)** - Vectorized operations with `NumberArray` +- **[Pandas Integration](pandas-integration.md)** - Unit-aware DataFrames with `NumberSeries` +- **[Polars Integration](polars-integration.md)** - Unit-aware Polars with `NumberColumn` + ## Calculation Guides - **[Dimensional Analysis](dimensional-analysis.md)** - Step-by-step factor-label calculations diff --git a/docs/guides/numpy-arrays.md b/docs/guides/numpy-arrays.md new file mode 100644 index 0000000..38c23e2 --- /dev/null +++ b/docs/guides/numpy-arrays.md @@ -0,0 +1,276 @@ +# NumPy Array Support + +ucon provides `NumberArray` for working with numpy arrays of dimensioned quantities. + +## Installation + +```bash +pip install ucon[numpy] +``` + +## Creating NumberArrays + +### From lists or numpy arrays + +```python +from ucon import units +import numpy as np + +# From list - uses callable syntax +heights = units.meter([1.7, 1.8, 1.9, 2.0]) + +# From numpy array +data = np.array([100, 200, 300]) +distances = units.kilometer(data) + +# Explicit construction +from ucon.numpy import NumberArray +temps = NumberArray([20, 21, 22, 23], unit=units.celsius) +``` + +### With uncertainty + +```python +# Uniform uncertainty (same for all elements) +measurements = units.meter([1.0, 2.0, 3.0], uncertainty=0.01) + +# Per-element uncertainty +errors = np.array([0.01, 0.02, 0.015]) +measurements = NumberArray([1.0, 2.0, 3.0], unit=units.meter, uncertainty=errors) +``` + +## Vectorized Conversion + +```python +heights = units.meter([1.7, 1.8, 1.9]) + +# Convert to feet +heights_ft = heights.to(units.foot) +print(heights_ft) # <[5.577, 5.906, 6.234] ft> + +# Uncertainty is propagated +temps = units.celsius([20, 21, 22], uncertainty=0.5) +temps_f = temps.to(units.fahrenheit) +# Uncertainty is scaled appropriately +``` + +## Arithmetic Operations + +All arithmetic preserves units and propagates uncertainty. + +```python +a = units.meter([1, 2, 3]) +b = units.meter([4, 5, 6]) + +# Addition/subtraction (same unit required) +c = a + b # <[5, 7, 9] m> +d = b - a # <[3, 3, 3] m> + +# Multiplication/division +area = a * b # units are combined: m^2 +speed = a / units.second([1, 2, 3]) # m/s + +# Scalar operations +doubled = a * 2 # <[2, 4, 6] m> +``` + +### Broadcasting + +NumPy broadcasting is supported: + +```python +# (2, 3) + (3,) broadcasting +matrix = NumberArray([[1, 2, 3], [4, 5, 6]], unit=units.meter) +row = NumberArray([10, 20, 30], unit=units.meter) +result = matrix + row # [[11, 22, 33], [14, 25, 36]] m +``` + +## Comparison Operators + +Comparisons return boolean arrays for filtering: + +```python +heights = units.meter([1.5, 1.7, 1.8, 2.0, 2.1]) + +# Compare with scalar +tall = heights > 1.8 +# array([False, False, False, True, True]) + +# Compare with Number +threshold = units.meter(1.75) +above_threshold = heights >= threshold + +# Use for filtering +filtered = heights.quantities[heights > 1.8] +``` + +## Indexing and Iteration + +```python +heights = units.meter([1.7, 1.8, 1.9]) + +# Scalar index returns Number +first = heights[0] # <1.7 m> + +# Slice returns NumberArray +subset = heights[1:] # <[1.8, 1.9] m> + +# Iterate as Numbers +for h in heights: + print(h) # prints each as +``` + +## Reduction Operations + +```python +heights = units.meter([1.7, 1.8, 1.9, 2.0]) + +total = heights.sum() # <7.4 m> +avg = heights.mean() # <1.85 m> +std = heights.std() # <0.129... m> +minimum = heights.min() # <1.7 m> +maximum = heights.max() # <2.0 m> +``` + +### Uncertainty in Reductions + +```python +# With uniform uncertainty +data = units.meter([1, 2, 3, 4], uncertainty=0.1) + +total = data.sum() +# uncertainty: 0.1 * sqrt(4) = 0.2 + +avg = data.mean() +# uncertainty: 0.1 / sqrt(4) = 0.05 +``` + +## N-Dimensional Arrays + +NumberArray supports arrays of any dimension: + +```python +# 2D array +grid = NumberArray( + [[1, 2, 3], + [4, 5, 6]], + unit=units.meter +) +print(grid.shape) # (2, 3) +print(grid.ndim) # 2 + +# Index by row +row = grid[0] # <[1, 2, 3] m> + +# Index by element +element = grid[0, 1] # <2 m> (Number) +``` + +## Integration with NumPy + +```python +import numpy as np + +heights = units.meter([1.7, 1.8, 1.9]) + +# Convert to raw numpy array +arr = np.asarray(heights) + +# Use numpy functions on quantities +mean_val = np.mean(heights.quantities) +``` + +## Performance Tips + +NumberArray is designed for correctness first, but performs well for batch operations. + +### What's Fast + +| Operation | Performance | +|-----------|-------------| +| Creation from ndarray | Essentially free (~0.001ms) - just wraps the array | +| Addition/subtraction | Excellent - often faster than raw NumPy at scale | +| Reductions (sum, mean) | Excellent - comparable to NumPy | +| Throughput | 100M+ elements/sec for conversions | + +### What Has Fixed Costs + +Conversions have small fixed costs for unit checking and graph lookup. These are cached after the first conversion, so repeated conversions of the same unit pair are faster (~1.5x speedup from cache). + +### Best Practices + +**Pass ndarrays, not lists:** + +```python +# Slower - must convert list to array +heights = units.meter([1.7, 1.8, 1.9, 2.0]) + +# Faster - wraps existing array directly +data = np.array([1.7, 1.8, 1.9, 2.0]) +heights = units.meter(data) +``` + +**Pre-convert units before loops:** + +```python +# Slower - converts on every iteration +for sample in samples: + value_ft = sample.to(units.foot) + process(value_ft) + +# Faster - convert once, iterate on result +samples_ft = samples.to(units.foot) +for value in samples_ft: + process(value) +``` + +**Batch operations over element-wise:** + +```python +# Slower - N separate operations +results = [x.to(units.foot) for x in measurements] + +# Faster - single vectorized operation +results = measurements.to(units.foot) +``` + +### Uncertainty Propagation Cost + +Multiplication with uncertainty is more expensive due to quadrature error propagation. If you don't need per-operation uncertainty, consider: + +```python +# Propagate uncertainty only at final result +a = NumberArray(data_a, unit=units.meter) # no uncertainty +b = NumberArray(data_b, unit=units.meter) # no uncertainty +result = a * b + +# Add uncertainty estimate at the end if needed +final = NumberArray(result.quantities, unit=result.unit, uncertainty=estimated_error) +``` + +## Example: Scientific Data Analysis + +```python +import numpy as np +from ucon import units + +# Experimental measurements with uncertainty +measurements = units.meter( + [10.2, 10.5, 10.3, 10.4, 10.6], + uncertainty=0.1 +) + +# Statistical analysis +mean = measurements.mean() +std = measurements.std() + +print(f"Mean: {mean}") +print(f"Std Dev: {std}") + +# Convert to different units +measurements_cm = measurements.to(units.centimeter) + +# Filter outliers +mask = abs(measurements.quantities - mean.quantity) < 2 * std.quantity +filtered = measurements.quantities[mask] +``` diff --git a/docs/guides/pandas-integration.md b/docs/guides/pandas-integration.md new file mode 100644 index 0000000..49d095d --- /dev/null +++ b/docs/guides/pandas-integration.md @@ -0,0 +1,201 @@ +# Pandas Integration + +ucon provides `NumberSeries` and a pandas accessor for working with unit-aware DataFrame columns. + +## Installation + +```bash +pip install ucon[pandas] +``` + +## NumberSeries + +`NumberSeries` wraps a pandas Series with unit metadata. + +### Creating NumberSeries + +```python +import pandas as pd +from ucon import units +from ucon.pandas import NumberSeries + +# From pandas Series +heights = NumberSeries( + pd.Series([1.7, 1.8, 1.9, 2.0]), + unit=units.meter +) + +# From list +temperatures = NumberSeries([20, 21, 22, 23], unit=units.celsius) +``` + +### With uncertainty + +```python +# Uniform uncertainty +measurements = NumberSeries( + pd.Series([100, 200, 300]), + unit=units.meter, + uncertainty=0.5 +) + +# Per-element uncertainty +errors = pd.Series([0.1, 0.2, 0.15]) +measurements = NumberSeries( + pd.Series([100, 200, 300]), + unit=units.meter, + uncertainty=errors +) +``` + +## Pandas Accessor + +The `.ucon` accessor enables unit operations directly on pandas Series: + +```python +import pandas as pd +from ucon import units + +df = pd.DataFrame({ + 'height_m': [1.7, 1.8, 1.9], + 'weight_kg': [65, 70, 75] +}) + +# Attach units and convert +heights = df['height_m'].ucon(units.meter) +heights_ft = heights.to(units.foot) + +# Or use with_unit +heights = df['height_m'].ucon.with_unit(units.meter, uncertainty=0.01) +``` + +## Conversion + +```python +heights = NumberSeries(pd.Series([1.7, 1.8, 1.9]), unit=units.meter) + +# Convert to feet +heights_ft = heights.to(units.foot) +print(heights_ft) # +``` + +## Arithmetic + +```python +a = NumberSeries(pd.Series([1, 2, 3]), unit=units.meter) +b = NumberSeries(pd.Series([4, 5, 6]), unit=units.meter) + +# Addition (same unit required) +c = a + b + +# Multiplication +area = a * b # units: m^2 + +# Scalar operations +doubled = a * 2 +``` + +## Comparison Operators + +Comparisons return boolean Series for filtering: + +```python +heights = NumberSeries(pd.Series([1.5, 1.7, 1.8, 2.0]), unit=units.meter) + +# Compare with scalar +tall = heights > 1.8 +# Series([False, False, False, True]) + +# Filter +filtered = heights.series[heights > 1.75] +``` + +## Reduction Operations + +```python +heights = NumberSeries(pd.Series([1.7, 1.8, 1.9, 2.0]), unit=units.meter) + +total = heights.sum() # <7.4 m> +avg = heights.mean() # <1.85 m> +std = heights.std() # std deviation +minimum = heights.min() # <1.7 m> +maximum = heights.max() # <2.0 m> +``` + +## Indexing + +```python +heights = NumberSeries( + pd.Series([1.7, 1.8, 1.9], index=['alice', 'bob', 'charlie']), + unit=units.meter +) + +# By position +first = heights[0] # <1.7 m> (Number) + +# By label +bob_height = heights['bob'] # <1.8 m> (Number) + +# Slice +subset = heights[1:] # NumberSeries +``` + +## Converting to DataFrame + +```python +heights = NumberSeries(pd.Series([1.7, 1.8, 1.9]), unit=units.meter) + +# Convert to DataFrame with unit in column name +df = heights.to_frame() # column: 'value (m)' + +# With custom name +df = heights.to_frame(name='height') +``` + +## Example: Data Analysis Workflow + +```python +import pandas as pd +from ucon import units + +# Load data +df = pd.DataFrame({ + 'name': ['Alice', 'Bob', 'Charlie'], + 'height_cm': [170, 180, 175], + 'weight_kg': [65, 80, 70] +}) + +# Attach units +heights = df['height_cm'].ucon(units.centimeter) +weights = df['weight_kg'].ucon(units.kilogram) + +# Convert +heights_m = heights.to(units.meter) + +# Calculate BMI (kg/m^2) +heights_m_sq = heights_m * heights_m +bmi = weights / heights_m_sq + +# Statistical analysis +print(f"Mean height: {heights_m.mean()}") +print(f"Mean weight: {weights.mean()}") + +# Filter +tall = heights > units.centimeter(175) +tall_people = df[tall.series] +``` + +## Performance Tips + +- **Batch conversions**: Convert entire columns at once rather than row-by-row +- **Pre-convert before loops**: Call `.to()` once before iterating over values +- **Accessor overhead**: The `.ucon` accessor has minimal overhead, but for tight loops consider extracting to a NumberSeries first + +```python +# Efficient - single conversion +heights_ft = df['height_m'].ucon(units.meter).to(units.foot) + +# Less efficient - converts per row +for idx, row in df.iterrows(): + height_ft = units.meter(row['height_m']).to(units.foot) # avoid this +``` diff --git a/docs/guides/polars-integration.md b/docs/guides/polars-integration.md new file mode 100644 index 0000000..e1ff346 --- /dev/null +++ b/docs/guides/polars-integration.md @@ -0,0 +1,188 @@ +# Polars Integration + +ucon provides `NumberColumn` for working with unit-aware Polars Series. + +## Installation + +```bash +pip install ucon[polars] +``` + +## NumberColumn + +`NumberColumn` wraps a Polars Series with unit metadata. + +### Creating NumberColumn + +```python +import polars as pl +from ucon import units +from ucon.polars import NumberColumn + +# From Polars Series +heights = NumberColumn( + pl.Series([1.7, 1.8, 1.9, 2.0]), + unit=units.meter +) + +# From list +temperatures = NumberColumn([20, 21, 22, 23], unit=units.celsius) +``` + +### With uncertainty + +```python +# Uniform uncertainty +measurements = NumberColumn( + pl.Series([100, 200, 300]), + unit=units.meter, + uncertainty=0.5 +) + +# Per-element uncertainty +errors = pl.Series([0.1, 0.2, 0.15]) +measurements = NumberColumn( + pl.Series([100, 200, 300]), + unit=units.meter, + uncertainty=errors +) +``` + +## Conversion + +```python +heights = NumberColumn(pl.Series([1.7, 1.8, 1.9]), unit=units.meter) + +# Convert to feet +heights_ft = heights.to(units.foot) +print(heights_ft) # +``` + +## Arithmetic + +```python +a = NumberColumn(pl.Series([1, 2, 3]), unit=units.meter) +b = NumberColumn(pl.Series([4, 5, 6]), unit=units.meter) + +# Addition (same unit required) +c = a + b + +# Multiplication +area = a * b # units: m^2 + +# Scalar operations +doubled = a * 2 +``` + +## Comparison Operators + +Comparisons return boolean Series for filtering: + +```python +heights = NumberColumn(pl.Series([1.5, 1.7, 1.8, 2.0]), unit=units.meter) + +# Compare with scalar +tall = heights > 1.8 +# Series([False, False, False, True]) + +# Filter using Polars +filtered = heights.series.filter(heights > 1.75) +``` + +## Reduction Operations + +```python +heights = NumberColumn(pl.Series([1.7, 1.8, 1.9, 2.0]), unit=units.meter) + +total = heights.sum() # <7.4 m> +avg = heights.mean() # <1.85 m> +std = heights.std() # std deviation +minimum = heights.min() # <1.7 m> +maximum = heights.max() # <2.0 m> +``` + +## Indexing + +```python +heights = NumberColumn(pl.Series([1.7, 1.8, 1.9]), unit=units.meter) + +# By position +first = heights[0] # <1.7 m> (Number) + +# Slice +subset = heights[1:] # NumberColumn +``` + +## Converting to List + +```python +heights = NumberColumn(pl.Series([1.7, 1.8, 1.9]), unit=units.meter) + +# Convert to list of Number instances +numbers = heights.to_list() +for n in numbers: + print(n) # <1.7 m>, <1.8 m>, <1.9 m> +``` + +## Example: Data Analysis Workflow + +```python +import polars as pl +from ucon import units +from ucon.polars import NumberColumn + +# Create DataFrame +df = pl.DataFrame({ + 'name': ['Alice', 'Bob', 'Charlie'], + 'height_cm': [170.0, 180.0, 175.0], + 'weight_kg': [65.0, 80.0, 70.0] +}) + +# Extract columns with units +heights = NumberColumn(df['height_cm'], unit=units.centimeter) +weights = NumberColumn(df['weight_kg'], unit=units.kilogram) + +# Convert +heights_m = heights.to(units.meter) + +# Statistical analysis +print(f"Mean height: {heights_m.mean()}") +print(f"Mean weight: {weights.mean()}") + +# Filter with comparison +tall_mask = heights > 175 +tall_people = df.filter(tall_mask) +``` + +## Polars vs Pandas + +| Feature | Polars (NumberColumn) | Pandas (NumberSeries) | +|---------|----------------------|----------------------| +| Label indexing | Position only | Labels and position | +| Accessor | Not available | `.ucon` accessor | +| Speed | Faster for large data | Good for small-medium | +| Memory | More efficient | Standard pandas | + +Choose Polars when: +- Working with large datasets +- Performance is critical +- You prefer Polars' API + +Choose Pandas when: +- You need the `.ucon` accessor +- Working with existing pandas workflows +- You need label-based indexing + +## Performance Tips + +- **Batch conversions**: Convert entire columns at once rather than row-by-row +- **Pre-convert before loops**: Call `.to()` once before iterating over values +- **Polars speed**: Polars' columnar format combined with NumberColumn gives excellent performance for large datasets + +```python +# Efficient - single vectorized conversion +heights_ft = NumberColumn(df['height_m'], unit=units.meter).to(units.foot) + +# Less efficient - element-wise +heights_ft = [units.meter(h).to(units.foot) for h in df['height_m']] # avoid this +``` diff --git a/examples/scientific_computing.ipynb b/examples/scientific_computing.ipynb new file mode 100644 index 0000000..344fa3c --- /dev/null +++ b/examples/scientific_computing.ipynb @@ -0,0 +1,1095 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# ucon v0.10.0: Scientific Computing\n", + "\n", + "This notebook demonstrates ucon's NumPy, Pandas, and Polars integrations for batch operations on dimensioned quantities." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Install optional dependencies if needed\n", + "# !pip install ucon[numpy,pandas,polars]" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Heights: <[1.65, 1.7 , 1.75, 1.8 , 1.85, 1.9 ] m>\n", + "Shape: (6,)\n", + "Length: 6\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "from ucon import units, Scale\n", + "from ucon.numpy import NumberArray\n", + "\n", + "# Create NumberArray from a list\n", + "heights = units.meter([1.65, 1.70, 1.75, 1.80, 1.85, 1.90])\n", + "print(f\"Heights: {heights}\")\n", + "print(f\"Shape: {heights.shape}\")\n", + "print(f\"Length: {len(heights)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. NumberArray Basics\n", + "\n", + "Create arrays of dimensioned quantities using the familiar callable syntax." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Distances: <[0, 10, 20, ..., 80, 90, 100] km>\n" + ] + } + ], + "source": [ + "# Create from numpy array\n", + "km = Scale.kilo * units.meter\n", + "data = np.linspace(0, 100, 11)\n", + "distances = km(data)\n", + "print(f\"Distances: {distances}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "First height: <1.65 m>\n", + "Type: Number\n", + "\n", + "First three: <[1.65, 1.7 , 1.75] m>\n", + "Type: NumberArray\n" + ] + } + ], + "source": [ + "# Indexing: scalar index returns Number, slice returns NumberArray\n", + "print(f\"First height: {heights[0]}\")\n", + "print(f\"Type: {type(heights[0]).__name__}\")\n", + "print()\n", + "print(f\"First three: {heights[:3]}\")\n", + "print(f\"Type: {type(heights[:3]).__name__}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Person 1: <1.65 m>\n", + " Person 2: <1.7 m>\n", + " Person 3: <1.75 m>\n" + ] + } + ], + "source": [ + "# Iteration yields Number instances\n", + "for i, h in enumerate(heights[:3]):\n", + " print(f\" Person {i+1}: {h}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Vectorized Conversion\n", + "\n", + "Convert entire arrays between units in a single operation." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Heights in feet: <[5.4134, 5.5774, 5.7415, 5.9055, 6.0696, 6.2336] ft>\n" + ] + } + ], + "source": [ + "# Convert heights to feet\n", + "heights_ft = heights.to(units.foot)\n", + "print(f\"Heights in feet: {heights_ft}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Celsius: <[ 0., 20., 37., 100.] °C>\n", + "Fahrenheit: <[ 32. , 68. , 98.6, 212. ] °F>\n" + ] + } + ], + "source": [ + "# Temperature conversion (affine map)\n", + "temps_c = units.celsius([0, 20, 37, 100])\n", + "temps_f = temps_c.to(units.fahrenheit)\n", + "print(f\"Celsius: {temps_c}\")\n", + "print(f\"Fahrenheit: {temps_f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Kilometers: <[ 1. , 5. , 10. , 42.195] km>\n", + "Miles: <[ 0.6214, 3.1069, 6.2137, 26.2188] mi>\n" + ] + } + ], + "source": [ + "# Scale conversions\n", + "km = Scale.kilo * units.meter\n", + "distances_km = km([1, 5, 10, 42.195]) # marathon!\n", + "distances_mi = distances_km.to(units.mile)\n", + "print(f\"Kilometers: {distances_km}\")\n", + "print(f\"Miles: {distances_mi}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Vectorized Arithmetic\n", + "\n", + "Arithmetic operations preserve units and combine dimensions correctly." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a + b = <[5., 7., 9.] m>\n", + "b - a = <[3., 3., 3.] m>\n" + ] + } + ], + "source": [ + "# Same-unit addition\n", + "a = units.meter([1, 2, 3])\n", + "b = units.meter([4, 5, 6])\n", + "print(f\"a + b = {a + b}\")\n", + "print(f\"b - a = {b - a}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Areas: <[10., 18., 28.] m²>\n" + ] + } + ], + "source": [ + "# Multiplication creates derived units\n", + "lengths = units.meter([2, 3, 4])\n", + "widths = units.meter([5, 6, 7])\n", + "areas = lengths * widths\n", + "print(f\"Areas: {areas}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Speeds: <[10., 10., 8.] m/s>\n" + ] + } + ], + "source": [ + "# Division creates ratios\n", + "distances = units.meter([100, 200, 400])\n", + "times = units.second([10, 20, 50])\n", + "speeds = distances / times\n", + "print(f\"Speeds: {speeds}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Doubled heights: <[3.3, 3.4, 3.5, 3.6, 3.7, 3.8] m>\n" + ] + } + ], + "source": [ + "# Scalar multiplication\n", + "doubled = heights * 2\n", + "print(f\"Doubled heights: {doubled}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Reduction Operations\n", + "\n", + "Statistical operations preserve units." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sum: <62.1 m>\n", + "Mean: <10.35 m>\n", + "Std: <0.1707825127659934 m>\n", + "Min: <10.1 m>\n", + "Max: <10.6 m>\n" + ] + } + ], + "source": [ + "measurements = units.meter([10.2, 10.5, 10.3, 10.4, 10.6, 10.1])\n", + "\n", + "print(f\"Sum: {measurements.sum()}\")\n", + "print(f\"Mean: {measurements.mean()}\")\n", + "print(f\"Std: {measurements.std()}\")\n", + "print(f\"Min: {measurements.min()}\")\n", + "print(f\"Max: {measurements.max()}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Uncertainty Propagation\n", + "\n", + "Track measurement uncertainty through calculations." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Measurements: <[10., 20., 30., 40.] ± 0.5 m>\n" + ] + } + ], + "source": [ + "# Uniform uncertainty (same for all elements)\n", + "measurements = units.meter([10.0, 20.0, 30.0, 40.0], uncertainty=0.5)\n", + "print(f\"Measurements: {measurements}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Data with per-element uncertainty: <[100., 200., 300.] ± [...] m>\n" + ] + } + ], + "source": [ + "# Per-element uncertainty\n", + "from ucon.numpy import NumberArray\n", + "\n", + "values = np.array([100.0, 200.0, 300.0])\n", + "errors = np.array([1.0, 2.0, 1.5])\n", + "data = NumberArray(values, unit=units.meter, uncertainty=errors)\n", + "print(f\"Data with per-element uncertainty: {data}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a + b = <[15., 30.] ± 0.5831 m>\n", + "Combined uncertainty: 0.58309518948453\n" + ] + } + ], + "source": [ + "# Uncertainty propagates through arithmetic (quadrature)\n", + "a = units.meter([10, 20], uncertainty=0.5)\n", + "b = units.meter([5, 10], uncertainty=0.3)\n", + "\n", + "result = a + b\n", + "print(f\"a + b = {result}\")\n", + "print(f\"Combined uncertainty: {result.uncertainty}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sum: <10.0 ± 0.2 m>\n", + "Sum uncertainty: 0.2000 (0.1 * sqrt(4) = 0.2)\n", + "Mean: <2.5 ± 0.05 m>\n", + "Mean uncertainty: 0.0500 (0.1 / sqrt(4) = 0.05)\n" + ] + } + ], + "source": [ + "# Uncertainty in reductions\n", + "data = units.meter([1, 2, 3, 4], uncertainty=0.1)\n", + "\n", + "total = data.sum()\n", + "print(f\"Sum: {total}\")\n", + "print(f\"Sum uncertainty: {total.uncertainty:.4f} (0.1 * sqrt(4) = 0.2)\")\n", + "\n", + "avg = data.mean()\n", + "print(f\"Mean: {avg}\")\n", + "print(f\"Mean uncertainty: {avg.uncertainty:.4f} (0.1 / sqrt(4) = 0.05)\")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Heights (m): <[1.8 , 1.85] ± 0.01 m>\n", + "Heights (ft): <[5.9055, 6.0696] ± 0.03281 ft>\n", + "Uncertainty scales with conversion factor\n" + ] + } + ], + "source": [ + "# Uncertainty through conversion\n", + "heights = units.meter([1.80, 1.85], uncertainty=0.01)\n", + "heights_ft = heights.to(units.foot)\n", + "print(f\"Heights (m): {heights}\")\n", + "print(f\"Heights (ft): {heights_ft}\")\n", + "print(f\"Uncertainty scales with conversion factor\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Comparison and Filtering\n", + "\n", + "Comparison operators return boolean arrays for filtering." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Heights > 1.75m: [False False False True True True]\n" + ] + } + ], + "source": [ + "heights = units.meter([1.65, 1.70, 1.75, 1.80, 1.85, 1.90])\n", + "\n", + "# Compare with scalar\n", + "tall = heights > 1.75\n", + "print(f\"Heights > 1.75m: {tall}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Heights >= 1.80m: [False False False True True True]\n" + ] + } + ], + "source": [ + "# Compare with Number\n", + "threshold = units.meter(1.80)\n", + "above = heights >= threshold\n", + "print(f\"Heights >= 1.80m: {above}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tall people's heights (raw): [1.8 1.85 1.9 ]\n" + ] + } + ], + "source": [ + "# Use for filtering\n", + "tall_heights = heights.quantities[heights > 1.75]\n", + "print(f\"Tall people's heights (raw): {tall_heights}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. N-Dimensional Arrays\n", + "\n", + "NumberArray supports arrays of any dimension." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Grid shape: (3, 3)\n", + "Grid ndim: 2\n", + "Grid:\n", + "<[[1., 2., 3.],\n", + " [4., 5., 6.],\n", + " [7., 8., 9.]] m>\n" + ] + } + ], + "source": [ + "# 2D grid of measurements\n", + "grid = NumberArray(\n", + " [[1, 2, 3],\n", + " [4, 5, 6],\n", + " [7, 8, 9]],\n", + " unit=units.meter\n", + ")\n", + "print(f\"Grid shape: {grid.shape}\")\n", + "print(f\"Grid ndim: {grid.ndim}\")\n", + "print(f\"Grid:\\n{grid}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "First row: <[1., 2., 3.] m>\n", + "Element [1,2]: <6.0 m>\n" + ] + } + ], + "source": [ + "# Row indexing\n", + "print(f\"First row: {grid[0]}\")\n", + "print(f\"Element [1,2]: {grid[1, 2]}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Grid + row (broadcast):\n", + "<[[11., 22., 33.],\n", + " [14., 25., 36.],\n", + " [17., 28., 39.]] m>\n" + ] + } + ], + "source": [ + "# Broadcasting\n", + "row = NumberArray([10, 20, 30], unit=units.meter)\n", + "result = grid + row\n", + "print(f\"Grid + row (broadcast):\\n{result}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8. Pandas Integration\n", + "\n", + "Work with unit-aware DataFrame columns." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
nameheight_mweight_kg
0Alice1.6560
1Bob1.8080
2Charlie1.7575
3Diana1.7065
\n", + "
" + ], + "text/plain": [ + " name height_m weight_kg\n", + "0 Alice 1.65 60\n", + "1 Bob 1.80 80\n", + "2 Charlie 1.75 75\n", + "3 Diana 1.70 65" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "from ucon.pandas import NumberSeries\n", + "\n", + "# Create a DataFrame with measurements\n", + "df = pd.DataFrame({\n", + " 'name': ['Alice', 'Bob', 'Charlie', 'Diana'],\n", + " 'height_m': [1.65, 1.80, 1.75, 1.70],\n", + " 'weight_kg': [60, 80, 75, 65]\n", + "})\n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Heights: \n" + ] + } + ], + "source": [ + "# Wrap column with unit using accessor\n", + "heights = df['height_m'].ucon.with_unit(units.meter)\n", + "print(f\"Heights: {heights}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Heights in feet: \n" + ] + } + ], + "source": [ + "# Convert to different unit\n", + "heights_ft = df['height_m'].ucon.with_unit(units.meter).to(units.foot)\n", + "print(f\"Heights in feet: {heights_ft}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
nameheight_mweight_kgheight_ft
0Alice1.65605.413386
1Bob1.80805.905512
2Charlie1.75755.741470
3Diana1.70655.577428
\n", + "
" + ], + "text/plain": [ + " name height_m weight_kg height_ft\n", + "0 Alice 1.65 60 5.413386\n", + "1 Bob 1.80 80 5.905512\n", + "2 Charlie 1.75 75 5.741470\n", + "3 Diana 1.70 65 5.577428" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Add converted column to DataFrame\n", + "df['height_ft'] = heights_ft.series\n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BMI: \n" + ] + } + ], + "source": [ + "# NumberSeries arithmetic\n", + "kg = Scale.kilo * units.gram\n", + "heights = NumberSeries(df['height_m'], unit=units.meter)\n", + "weights = NumberSeries(df['weight_kg'], unit=kg)\n", + "\n", + "# Calculate BMI (kg/m^2)\n", + "bmi = weights / (heights * heights)\n", + "print(f\"BMI: {bmi}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 9. Polars Integration\n", + "\n", + "Unit-aware columns for Polars DataFrames." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "shape: (4, 2)
distance_kmtime_min
f64f64
5.025.0
10.055.0
21.1120.0
42.2260.0
" + ], + "text/plain": [ + "shape: (4, 2)\n", + "┌─────────────┬──────────┐\n", + "│ distance_km ┆ time_min │\n", + "│ --- ┆ --- │\n", + "│ f64 ┆ f64 │\n", + "╞═════════════╪══════════╡\n", + "│ 5.0 ┆ 25.0 │\n", + "│ 10.0 ┆ 55.0 │\n", + "│ 21.1 ┆ 120.0 │\n", + "│ 42.2 ┆ 260.0 │\n", + "└─────────────┴──────────┘" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import polars as pl\n", + "from ucon.polars import NumberColumn\n", + "\n", + "# Create a Polars DataFrame\n", + "df_pl = pl.DataFrame({\n", + " 'distance_km': [5.0, 10.0, 21.1, 42.2],\n", + " 'time_min': [25.0, 55.0, 120.0, 260.0]\n", + "})\n", + "df_pl" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Distances: \n" + ] + } + ], + "source": [ + "# Wrap columns with units\n", + "km = Scale.kilo * units.meter\n", + "distances = NumberColumn(df_pl['distance_km'], unit=km)\n", + "print(f\"Distances: {distances}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Distances in miles: \n" + ] + } + ], + "source": [ + "# Convert to miles\n", + "distances_mi = distances.to(units.mile)\n", + "print(f\"Distances in miles: {distances_mi}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Pace: \n" + ] + } + ], + "source": [ + "# Calculate pace (time per distance)\n", + "times = NumberColumn(df_pl['time_min'], unit=units.minute)\n", + "pace = times / distances\n", + "print(f\"Pace: {pace}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 10. Real-World Example: Experimental Data Analysis\n", + "\n", + "Combining all features for a realistic workflow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# Simulate experimental measurements with uncertainty\nnp.random.seed(42)\n\n# True value: 9.81 m/s^2\nn_measurements = 20\ntrue_g = 9.81\nmeasurement_error = 0.05 # 5 cm/s^2 instrument precision\n\nmeasured_values = true_g + np.random.normal(0, measurement_error, n_measurements)\n\n# Inject a couple of outliers (simulating measurement errors)\nmeasured_values[5] = 9.55 # low outlier\nmeasured_values[12] = 10.05 # high outlier\n\n# Create NumberArray with uncertainty\ng_measurements = NumberArray(\n measured_values,\n unit=units.meter / units.second**2,\n uncertainty=measurement_error\n)\n\nprint(f\"Measurements: {g_measurements}\")" + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mean: <9.801435071927909 ± 0.011180339887498949 m/s²>\n", + "Std Dev: <0.0467859979446887 m/s²>\n", + "True value: 9.81 m/s^2\n", + "Error: 0.0086 m/s^2\n" + ] + } + ], + "source": [ + "# Statistical analysis\n", + "mean_g = g_measurements.mean()\n", + "std_g = g_measurements.std()\n", + "\n", + "print(f\"Mean: {mean_g}\")\n", + "print(f\"Std Dev: {std_g}\")\n", + "print(f\"True value: {true_g} m/s^2\")\n", + "print(f\"Error: {abs(mean_g.quantity - true_g):.4f} m/s^2\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Identify outliers (> 2 sigma from mean)\n", + "deviations = np.abs(g_measurements.quantities - mean_g.quantity)\n", + "outliers = deviations > 2 * std_g.quantity\n", + "\n", + "print(f\"Outlier mask: {outliers}\")\n", + "print(f\"Number of outliers: {np.sum(outliers)}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Filter outliers and recalculate\n", + "filtered_values = g_measurements.quantities[~outliers]\n", + "g_filtered = NumberArray(\n", + " filtered_values,\n", + " unit=units.meter / units.second**2,\n", + " uncertainty=measurement_error\n", + ")\n", + "\n", + "print(f\"Filtered mean: {g_filtered.mean()}\")\n", + "print(f\"Filtered std: {g_filtered.std()}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Convert to different units for reporting\n", + "cm = Scale.centi * units.meter\n", + "g_cm = g_filtered.to(cm / units.second**2)\n", + "print(f\"In cm/s^2: {g_cm.mean()}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "ucon v0.10.0 provides seamless integration with the scientific Python ecosystem:\n", + "\n", + "- **NumberArray**: Vectorized operations on dimensioned arrays with uncertainty tracking\n", + "- **Pandas**: `NumberSeries` and `.ucon` accessor for DataFrame workflows\n", + "- **Polars**: `NumberColumn` for high-performance DataFrames\n", + "- **Performance**: Lazy caching makes repeated operations fast\n", + "\n", + "Install with optional dependencies:\n", + "```bash\n", + "pip install ucon[numpy] # Just NumPy\n", + "pip install ucon[pandas] # Pandas integration\n", + "pip install ucon[polars] # Polars integration\n", + "pip install ucon[numpy,pandas,polars] # All integrations\n", + "```" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 87caea4..572f18b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -62,6 +62,10 @@ nav: - Domain Walkthroughs: - guides/domain-walkthroughs/index.md - Nursing Dosage: guides/domain-walkthroughs/nursing-dosage.md + - Scientific Computing: + - NumPy Arrays: guides/numpy-arrays.md + - Pandas Integration: guides/pandas-integration.md + - Polars Integration: guides/polars-integration.md - Reference: - reference/index.md - API: reference/api.md diff --git a/pyproject.toml b/pyproject.toml index 24b4880..7d76c72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,9 +40,15 @@ Homepage = "https://docs.ucon.dev/" Repository = "https://github.com/withtwoemms/ucon" [project.optional-dependencies] -test = ["coverage[toml]>=5.5", "pytest>=7.0"] +test = ["coverage[toml]>=5.5", "pytest>=7.0", "numpy>=1.20", "pandas>=1.3; python_version >= '3.8'", "polars>=0.15; python_version >= '3.8'"] pydantic = ["pydantic>=2.0"] docs = ["mkdocs-material", "mkdocstrings[python]"] +numpy = ["numpy>=1.20"] +pandas = ["pandas>=1.3; python_version >= '3.8'"] +polars = ["polars>=0.15; python_version >= '3.8'"] +dataframe = ["ucon[pandas,polars]; python_version >= '3.8'"] +scientific = ["ucon[numpy]", "ucon[pandas,polars]; python_version >= '3.8'"] +all = ["ucon[pydantic,numpy]", "ucon[pandas,polars]; python_version >= '3.8'"] # ----------------------------------------------------------------------------- # Build System diff --git a/tests/ucon/test_numpy.py b/tests/ucon/test_numpy.py new file mode 100644 index 0000000..85442e6 --- /dev/null +++ b/tests/ucon/test_numpy.py @@ -0,0 +1,822 @@ +# tests/ucon/test_numpy.py +# +# Tests for NumberArray and numpy integration. + +import unittest +import math + +try: + import numpy as np + HAS_NUMPY = True +except ImportError: + HAS_NUMPY = False + + +@unittest.skipUnless(HAS_NUMPY, "NumPy not installed") +class TestNumberArrayBasic(unittest.TestCase): + """Test NumberArray construction and basic properties.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + self.foot = units.foot + self.second = units.second + + def test_create_from_list(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + self.assertEqual(len(arr), 3) + self.assertEqual(arr.shape, (3,)) + self.assertEqual(arr.unit, self.meter) + + def test_create_from_ndarray(self): + from ucon.numpy import NumberArray + arr = NumberArray(np.array([1.0, 2.0, 3.0]), unit=self.meter) + self.assertEqual(len(arr), 3) + np.testing.assert_array_equal(arr.quantities, np.array([1.0, 2.0, 3.0])) + + def test_default_unit_is_dimensionless(self): + from ucon.numpy import NumberArray + from ucon.core import _none + arr = NumberArray([1.0, 2.0]) + self.assertEqual(arr.unit, _none) + + def test_uniform_uncertainty(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter, uncertainty=0.1) + self.assertEqual(arr.uncertainty, 0.1) + + def test_per_element_uncertainty(self): + from ucon.numpy import NumberArray + unc = [0.1, 0.2, 0.3] + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter, uncertainty=unc) + np.testing.assert_array_equal(arr.uncertainty, np.array(unc)) + + def test_uncertainty_shape_mismatch_raises(self): + from ucon.numpy import NumberArray + with self.assertRaises(ValueError) as ctx: + NumberArray([1.0, 2.0, 3.0], unit=self.meter, uncertainty=[0.1, 0.2]) + self.assertIn("shape", str(ctx.exception)) + + def test_ndim_property(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + self.assertEqual(arr.ndim, 1) + + arr_2d = NumberArray([[1, 2], [3, 4]], unit=self.meter) + self.assertEqual(arr_2d.ndim, 2) + + def test_dtype_property(self): + from ucon.numpy import NumberArray + arr = NumberArray([1, 2, 3], unit=self.meter) + self.assertEqual(arr.dtype, np.float64) + + +@unittest.skipUnless(HAS_NUMPY, "NumPy not installed") +class TestNumberArrayIndexing(unittest.TestCase): + """Test NumberArray indexing and iteration.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + + def test_scalar_index_returns_number(self): + from ucon.numpy import NumberArray + from ucon.core import Number + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + elem = arr[0] + self.assertIsInstance(elem, Number) + self.assertEqual(elem.quantity, 1.0) + self.assertEqual(elem.unit, self.meter) + + def test_slice_returns_numberarray(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0, 4.0], unit=self.meter) + sliced = arr[1:3] + self.assertIsInstance(sliced, NumberArray) + self.assertEqual(len(sliced), 2) + np.testing.assert_array_equal(sliced.quantities, np.array([2.0, 3.0])) + + def test_index_with_uncertainty(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0], unit=self.meter, uncertainty=0.1) + elem = arr[0] + self.assertEqual(elem.uncertainty, 0.1) + + def test_index_with_per_element_uncertainty(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0], unit=self.meter, uncertainty=[0.1, 0.2]) + self.assertEqual(arr[0].uncertainty, 0.1) + self.assertEqual(arr[1].uncertainty, 0.2) + + def test_iteration_yields_numbers(self): + from ucon.numpy import NumberArray + from ucon.core import Number + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + elements = list(arr) + self.assertEqual(len(elements), 3) + for elem in elements: + self.assertIsInstance(elem, Number) + + +@unittest.skipUnless(HAS_NUMPY, "NumPy not installed") +class TestNumberArrayArithmetic(unittest.TestCase): + """Test NumberArray arithmetic operations.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + self.second = units.second + + def test_multiply_by_scalar(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + result = arr * 2 + np.testing.assert_array_equal(result.quantities, np.array([2.0, 4.0, 6.0])) + self.assertEqual(result.unit, self.meter) + + def test_multiply_by_scalar_with_uncertainty(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0], unit=self.meter, uncertainty=0.1) + result = arr * 2 + self.assertEqual(result.uncertainty, 0.2) + + def test_rmul(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + result = 2 * arr + np.testing.assert_array_equal(result.quantities, np.array([2.0, 4.0, 6.0])) + + def test_divide_by_scalar(self): + from ucon.numpy import NumberArray + arr = NumberArray([2.0, 4.0, 6.0], unit=self.meter) + result = arr / 2 + np.testing.assert_array_equal(result.quantities, np.array([1.0, 2.0, 3.0])) + + def test_multiply_numberarrays(self): + from ucon.numpy import NumberArray + a = NumberArray([1.0, 2.0], unit=self.meter) + b = NumberArray([3.0, 4.0], unit=self.second) + result = a * b + np.testing.assert_array_equal(result.quantities, np.array([3.0, 8.0])) + # Check combined unit + self.assertIn('m', str(result.unit)) + self.assertIn('s', str(result.unit)) + + def test_multiply_numberarrays_incompatible_shapes(self): + from ucon.numpy import NumberArray + a = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + b = NumberArray([1.0, 2.0], unit=self.meter) + with self.assertRaises(ValueError) as ctx: + a * b + self.assertIn("not broadcast-compatible", str(ctx.exception)) + + def test_add_same_unit(self): + from ucon.numpy import NumberArray + a = NumberArray([1.0, 2.0], unit=self.meter) + b = NumberArray([0.5, 0.5], unit=self.meter) + result = a + b + np.testing.assert_array_equal(result.quantities, np.array([1.5, 2.5])) + + def test_add_different_unit_raises(self): + from ucon.numpy import NumberArray + a = NumberArray([1.0, 2.0], unit=self.meter) + b = NumberArray([1.0, 2.0], unit=self.second) + with self.assertRaises(ValueError) as ctx: + a + b + self.assertIn("different units", str(ctx.exception)) + + def test_subtract(self): + from ucon.numpy import NumberArray + a = NumberArray([3.0, 4.0], unit=self.meter) + b = NumberArray([1.0, 1.0], unit=self.meter) + result = a - b + np.testing.assert_array_equal(result.quantities, np.array([2.0, 3.0])) + + def test_negation(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, -2.0, 3.0], unit=self.meter) + result = -arr + np.testing.assert_array_equal(result.quantities, np.array([-1.0, 2.0, -3.0])) + + def test_abs(self): + from ucon.numpy import NumberArray + arr = NumberArray([-1.0, 2.0, -3.0], unit=self.meter) + result = abs(arr) + np.testing.assert_array_equal(result.quantities, np.array([1.0, 2.0, 3.0])) + + +@unittest.skipUnless(HAS_NUMPY, "NumPy not installed") +class TestNumberArrayConversion(unittest.TestCase): + """Test NumberArray unit conversion.""" + + def setUp(self): + from ucon import units + from ucon.core import Scale + self.meter = units.meter + self.foot = units.foot + self.kilometer = Scale.kilo * units.meter + self.centimeter = Scale.centi * units.meter + + def test_scale_only_conversion(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0], unit=self.kilometer) + result = arr.to(self.meter) + np.testing.assert_array_almost_equal( + result.quantities, np.array([1000.0, 2000.0, 3000.0]) + ) + self.assertEqual(result.unit, self.meter) + + def test_conversion_with_uncertainty(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0], unit=self.kilometer, uncertainty=0.1) + result = arr.to(self.meter) + # Uncertainty should scale by same factor + self.assertAlmostEqual(result.uncertainty, 100.0) + + def test_graph_based_conversion(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + result = arr.to(self.foot) + # 1 meter ≈ 3.28084 feet + expected = np.array([1.0, 2.0, 3.0]) * 3.28084 + np.testing.assert_array_almost_equal(result.quantities, expected, decimal=3) + + +@unittest.skipUnless(HAS_NUMPY, "NumPy not installed") +class TestNumberArrayUncertaintyPropagation(unittest.TestCase): + """Test uncertainty propagation through arithmetic.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + + def test_multiplication_uncertainty(self): + from ucon.numpy import NumberArray + from ucon.core import Number + # a = [10, 20] ± 1 + # b = 2 ± 0.1 + # For c = a * b: + # δc/c = sqrt((δa/a)² + (δb/b)²) + arr = NumberArray([10.0, 20.0], unit=self.meter, uncertainty=1.0) + scalar = Number(quantity=2.0, unit=self.meter, uncertainty=0.1) + result = arr * scalar + + # For first element: c = 20, δc/c = sqrt((1/10)² + (0.1/2)²) + rel_unc = math.sqrt((1/10)**2 + (0.1/2)**2) + expected_unc_1 = 20 * rel_unc + self.assertAlmostEqual(result.uncertainty[0], expected_unc_1, places=5) + + def test_addition_uncertainty_quadrature(self): + from ucon.numpy import NumberArray + # δ(a+b) = sqrt(δa² + δb²) + a = NumberArray([1.0, 2.0], unit=self.meter, uncertainty=0.1) + b = NumberArray([1.0, 2.0], unit=self.meter, uncertainty=0.2) + result = a + b + expected_unc = math.sqrt(0.1**2 + 0.2**2) + np.testing.assert_array_almost_equal( + result.uncertainty, np.array([expected_unc, expected_unc]) + ) + + +@unittest.skipUnless(HAS_NUMPY, "NumPy not installed") +class TestCallableSyntax(unittest.TestCase): + """Test unit callable syntax with arrays.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + self.second = units.second + + def test_unit_callable_with_list(self): + from ucon.numpy import NumberArray + arr = self.meter([1.0, 2.0, 3.0]) + self.assertIsInstance(arr, NumberArray) + self.assertEqual(len(arr), 3) + self.assertEqual(arr.unit, self.meter) + + def test_unit_callable_with_ndarray(self): + from ucon.numpy import NumberArray + arr = self.meter(np.array([1.0, 2.0, 3.0])) + self.assertIsInstance(arr, NumberArray) + + def test_unit_callable_scalar_still_returns_number(self): + from ucon.core import Number + n = self.meter(5.0) + self.assertIsInstance(n, Number) + + def test_unitproduct_callable_with_list(self): + from ucon.numpy import NumberArray + velocity_unit = self.meter / self.second + arr = velocity_unit([10, 20, 30]) + self.assertIsInstance(arr, NumberArray) + self.assertEqual(len(arr), 3) + + def test_unit_callable_with_uncertainty(self): + from ucon.numpy import NumberArray + arr = self.meter([1.0, 2.0, 3.0], uncertainty=0.1) + self.assertIsInstance(arr, NumberArray) + self.assertEqual(arr.uncertainty, 0.1) + + +@unittest.skipUnless(HAS_NUMPY, "NumPy not installed") +class TestMapArraySupport(unittest.TestCase): + """Test that Maps work with array inputs.""" + + def test_linearmap_with_array(self): + from ucon.maps import LinearMap + m = LinearMap(2.0) + arr = np.array([1.0, 2.0, 3.0]) + result = m(arr) + np.testing.assert_array_equal(result, np.array([2.0, 4.0, 6.0])) + + def test_affinemap_with_array(self): + from ucon.maps import AffineMap + m = AffineMap(2.0, 10.0) # y = 2*x + 10 + arr = np.array([0.0, 5.0, 10.0]) + result = m(arr) + np.testing.assert_array_equal(result, np.array([10.0, 20.0, 30.0])) + + def test_logmap_with_array(self): + from ucon.maps import LogMap + m = LogMap(scale=10, base=10) # 10 * log10(x) - dB power + arr = np.array([1.0, 10.0, 100.0]) + result = m(arr) + np.testing.assert_array_almost_equal(result, np.array([0.0, 10.0, 20.0])) + + def test_expmap_with_array(self): + from ucon.maps import ExpMap + m = ExpMap(scale=0.1, base=10) # 10^(0.1*x) - inverse of dB + arr = np.array([0.0, 10.0, 20.0]) + result = m(arr) + np.testing.assert_array_almost_equal(result, np.array([1.0, 10.0, 100.0])) + + def test_linearmap_derivative_with_array(self): + from ucon.maps import LinearMap + m = LinearMap(2.0) + arr = np.array([1.0, 2.0, 3.0]) + deriv = m.derivative(arr) + # LinearMap derivative is constant + self.assertEqual(deriv, 2.0) + + def test_logmap_derivative_with_array(self): + from ucon.maps import LogMap + m = LogMap(scale=10, base=10) + arr = np.array([1.0, 10.0, 100.0]) + deriv = m.derivative(arr) + # d/dx [10 * log10(x)] = 10 / (x * ln(10)) + expected = 10 / (arr * math.log(10)) + np.testing.assert_array_almost_equal(deriv, expected) + + +@unittest.skipUnless(HAS_NUMPY, "NumPy not installed") +class TestNumberArrayComparison(unittest.TestCase): + """Test comparison operators returning boolean arrays.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + self.second = units.second + + def test_eq_with_scalar(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + result = arr == 2.0 + np.testing.assert_array_equal(result, np.array([False, True, False])) + + def test_eq_with_number(self): + from ucon.numpy import NumberArray + from ucon.core import Number + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + n = Number(quantity=2.0, unit=self.meter) + result = arr == n + np.testing.assert_array_equal(result, np.array([False, True, False])) + + def test_eq_with_numberarray(self): + from ucon.numpy import NumberArray + a = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + b = NumberArray([1.0, 5.0, 3.0], unit=self.meter) + result = a == b + np.testing.assert_array_equal(result, np.array([True, False, True])) + + def test_ne_with_scalar(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + result = arr != 2.0 + np.testing.assert_array_equal(result, np.array([True, False, True])) + + def test_lt_with_scalar(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + result = arr < 2.0 + np.testing.assert_array_equal(result, np.array([True, False, False])) + + def test_le_with_scalar(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + result = arr <= 2.0 + np.testing.assert_array_equal(result, np.array([True, True, False])) + + def test_gt_with_scalar(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + result = arr > 2.0 + np.testing.assert_array_equal(result, np.array([False, False, True])) + + def test_ge_with_scalar(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + result = arr >= 2.0 + np.testing.assert_array_equal(result, np.array([False, True, True])) + + def test_comparison_different_unit_raises(self): + from ucon.numpy import NumberArray + a = NumberArray([1.0, 2.0], unit=self.meter) + b = NumberArray([1.0, 2.0], unit=self.second) + with self.assertRaises(ValueError) as ctx: + a == b + self.assertIn("different units", str(ctx.exception)) + + def test_comparison_for_filtering(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0, 4.0, 5.0], unit=self.meter) + mask = arr > 2.5 + filtered = arr.quantities[mask] + np.testing.assert_array_equal(filtered, np.array([3.0, 4.0, 5.0])) + + +@unittest.skipUnless(HAS_NUMPY, "NumPy not installed") +class TestNumberArrayBroadcasting(unittest.TestCase): + """Test numpy broadcasting support.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + + def test_mul_broadcasting_1d_scalar_array(self): + """Test (3,) * (1,) broadcasting.""" + from ucon.numpy import NumberArray + a = NumberArray([1.0, 2.0, 3.0], unit=self.meter) # shape (3,) + b = NumberArray([2.0], unit=self.meter) # shape (1,) + result = a * b + np.testing.assert_array_equal(result.quantities, np.array([2.0, 4.0, 6.0])) + + def test_mul_broadcasting_2d_1d(self): + """Test (2, 3) * (3,) broadcasting.""" + from ucon.numpy import NumberArray + a = NumberArray([[1, 2, 3], [4, 5, 6]], unit=self.meter) # shape (2, 3) + b = NumberArray([1, 2, 3], unit=self.meter) # shape (3,) + result = a * b + expected = np.array([[1, 4, 9], [4, 10, 18]]) + np.testing.assert_array_equal(result.quantities, expected) + + def test_add_broadcasting_2d_1d(self): + """Test (2, 3) + (3,) broadcasting.""" + from ucon.numpy import NumberArray + a = NumberArray([[1, 2, 3], [4, 5, 6]], unit=self.meter) # shape (2, 3) + b = NumberArray([10, 20, 30], unit=self.meter) # shape (3,) + result = a + b + expected = np.array([[11, 22, 33], [14, 25, 36]]) + np.testing.assert_array_equal(result.quantities, expected) + + def test_sub_broadcasting(self): + """Test (3,) - (1,) broadcasting.""" + from ucon.numpy import NumberArray + a = NumberArray([10.0, 20.0, 30.0], unit=self.meter) + b = NumberArray([5.0], unit=self.meter) + result = a - b + np.testing.assert_array_equal(result.quantities, np.array([5.0, 15.0, 25.0])) + + def test_div_broadcasting(self): + """Test (2, 2) / (2,) broadcasting.""" + from ucon.numpy import NumberArray + a = NumberArray([[4, 6], [8, 10]], unit=self.meter) + b = NumberArray([2, 2], unit=self.meter) + result = a / b + expected = np.array([[2, 3], [4, 5]]) + np.testing.assert_array_equal(result.quantities, expected) + + def test_comparison_broadcasting(self): + """Test comparison with broadcasting.""" + from ucon.numpy import NumberArray + a = NumberArray([[1, 2], [3, 4]], unit=self.meter) + b = NumberArray([2, 3], unit=self.meter) + result = a < b + expected = np.array([[True, True], [False, False]]) + np.testing.assert_array_equal(result, expected) + + def test_incompatible_shapes_raise(self): + """Test that truly incompatible shapes raise error.""" + from ucon.numpy import NumberArray + a = NumberArray([1, 2, 3], unit=self.meter) # shape (3,) + b = NumberArray([1, 2], unit=self.meter) # shape (2,) - not compatible + with self.assertRaises(ValueError) as ctx: + a + b + self.assertIn("not broadcast-compatible", str(ctx.exception)) + + +@unittest.skipUnless(HAS_NUMPY, "NumPy not installed") +class TestNumberArrayReductions(unittest.TestCase): + """Test reduction operations (sum, mean, etc.).""" + + def setUp(self): + from ucon import units + self.meter = units.meter + + def test_sum(self): + from ucon.numpy import NumberArray + from ucon.core import Number + arr = NumberArray([1.0, 2.0, 3.0, 4.0], unit=self.meter) + total = arr.sum() + self.assertIsInstance(total, Number) + self.assertEqual(total.quantity, 10.0) + self.assertEqual(total.unit, self.meter) + + def test_sum_with_uncertainty(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0, 4.0], unit=self.meter, uncertainty=0.1) + total = arr.sum() + # σ_sum = σ * sqrt(n) for uniform uncertainty + self.assertAlmostEqual(total.uncertainty, 0.1 * math.sqrt(4)) + + def test_mean(self): + from ucon.numpy import NumberArray + from ucon.core import Number + arr = NumberArray([2.0, 4.0, 6.0], unit=self.meter) + avg = arr.mean() + self.assertIsInstance(avg, Number) + self.assertEqual(avg.quantity, 4.0) + + def test_mean_with_uncertainty(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0, 4.0], unit=self.meter, uncertainty=0.2) + avg = arr.mean() + # σ_mean = σ / sqrt(n) for uniform uncertainty + self.assertAlmostEqual(avg.uncertainty, 0.2 / math.sqrt(4)) + + def test_std(self): + from ucon.numpy import NumberArray + arr = NumberArray([2.0, 4.0, 6.0, 8.0], unit=self.meter) + s = arr.std() + expected = np.std([2.0, 4.0, 6.0, 8.0]) + self.assertAlmostEqual(s.quantity, expected) + + def test_min_max(self): + from ucon.numpy import NumberArray + arr = NumberArray([3.0, 1.0, 4.0, 1.0, 5.0], unit=self.meter) + self.assertEqual(arr.min().quantity, 1.0) + self.assertEqual(arr.max().quantity, 5.0) + + +@unittest.skipUnless(HAS_NUMPY, "NumPy not installed") +class TestNumberArrayArithmeticExtended(unittest.TestCase): + """Extended arithmetic tests for coverage.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + self.second = units.second + + def test_divide_by_number(self): + """Test NumberArray / Number.""" + from ucon.numpy import NumberArray + from ucon.core import Number + arr = NumberArray([10.0, 20.0, 30.0], unit=self.meter) + n = Number(quantity=2.0, unit=self.second) + result = arr / n + np.testing.assert_array_equal(result.quantities, np.array([5.0, 10.0, 15.0])) + + def test_divide_by_number_with_uncertainty(self): + """Test NumberArray / Number with uncertainty propagation.""" + from ucon.numpy import NumberArray + from ucon.core import Number + arr = NumberArray([10.0, 20.0], unit=self.meter, uncertainty=1.0) + n = Number(quantity=2.0, unit=self.second, uncertainty=0.1) + result = arr / n + self.assertIsNotNone(result.uncertainty) + + def test_divide_by_scalar_with_uncertainty(self): + """Test NumberArray / scalar with uncertainty.""" + from ucon.numpy import NumberArray + arr = NumberArray([10.0, 20.0], unit=self.meter, uncertainty=1.0) + result = arr / 2 + self.assertEqual(result.uncertainty, 0.5) + + def test_rdiv_scalar_by_array(self): + """Test scalar / NumberArray.""" + from ucon.numpy import NumberArray + arr = NumberArray([2.0, 4.0, 5.0], unit=self.meter) + result = 10.0 / arr + np.testing.assert_array_equal(result.quantities, np.array([5.0, 2.5, 2.0])) + + def test_rdiv_scalar_by_array_with_uncertainty(self): + """Test scalar / NumberArray with uncertainty.""" + from ucon.numpy import NumberArray + arr = NumberArray([10.0, 20.0], unit=self.meter, uncertainty=1.0) + result = 100.0 / arr + self.assertIsNotNone(result.uncertainty) + # For c = a/x, δc = |c| * |δx/x| + expected_unc_0 = abs(10.0) * abs(1.0 / 10.0) # = 1.0 + self.assertAlmostEqual(result.uncertainty[0], expected_unc_0) + + def test_add_number_to_array(self): + """Test NumberArray + Number.""" + from ucon.numpy import NumberArray + from ucon.core import Number + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + n = Number(quantity=10.0, unit=self.meter) + result = arr + n + np.testing.assert_array_equal(result.quantities, np.array([11.0, 12.0, 13.0])) + + def test_add_number_with_uncertainty(self): + """Test NumberArray + Number with uncertainty.""" + from ucon.numpy import NumberArray + from ucon.core import Number + arr = NumberArray([1.0, 2.0], unit=self.meter, uncertainty=0.1) + n = Number(quantity=10.0, unit=self.meter, uncertainty=0.2) + result = arr + n + expected_unc = math.sqrt(0.1**2 + 0.2**2) + np.testing.assert_array_almost_equal( + result.uncertainty, np.array([expected_unc, expected_unc]) + ) + + def test_radd_number(self): + """Test Number + NumberArray (radd).""" + from ucon.numpy import NumberArray + from ucon.core import Number + arr = NumberArray([1.0, 2.0], unit=self.meter) + n = Number(quantity=10.0, unit=self.meter) + result = n + arr # triggers __radd__ + np.testing.assert_array_equal(result.quantities, np.array([11.0, 12.0])) + + def test_sub_number_from_array(self): + """Test NumberArray - Number.""" + from ucon.numpy import NumberArray + from ucon.core import Number + arr = NumberArray([10.0, 20.0, 30.0], unit=self.meter) + n = Number(quantity=5.0, unit=self.meter) + result = arr - n + np.testing.assert_array_equal(result.quantities, np.array([5.0, 15.0, 25.0])) + + def test_rsub_number(self): + """Test Number - NumberArray (rsub).""" + from ucon.numpy import NumberArray + from ucon.core import Number + arr = NumberArray([1.0, 2.0], unit=self.meter) + n = Number(quantity=10.0, unit=self.meter) + result = n - arr # triggers __rsub__ + np.testing.assert_array_equal(result.quantities, np.array([9.0, 8.0])) + + def test_rsub_number_with_uncertainty(self): + """Test Number - NumberArray with uncertainty.""" + from ucon.numpy import NumberArray + from ucon.core import Number + arr = NumberArray([1.0, 2.0], unit=self.meter, uncertainty=0.1) + n = Number(quantity=10.0, unit=self.meter, uncertainty=0.2) + result = n - arr + expected_unc = math.sqrt(0.1**2 + 0.2**2) + np.testing.assert_array_almost_equal( + result.uncertainty, np.array([expected_unc, expected_unc]) + ) + + def test_pos(self): + """Test unary positive (+arr).""" + from ucon.numpy import NumberArray + arr = NumberArray([1.0, -2.0, 3.0], unit=self.meter, uncertainty=0.1) + result = +arr + np.testing.assert_array_equal(result.quantities, arr.quantities) + self.assertEqual(result.uncertainty, arr.uncertainty) + + def test_pos_with_per_element_uncertainty(self): + """Test unary positive with per-element uncertainty.""" + from ucon.numpy import NumberArray + unc = np.array([0.1, 0.2, 0.3]) + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter, uncertainty=unc) + result = +arr + np.testing.assert_array_equal(result.uncertainty, unc) + + def test_multiply_number_by_array(self): + """Test Number * NumberArray.""" + from ucon.numpy import NumberArray + from ucon.core import Number + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + n = Number(quantity=2.0, unit=self.second) + result = arr * n + np.testing.assert_array_equal(result.quantities, np.array([2.0, 4.0, 6.0])) + + def test_divide_arrays_incompatible_shapes(self): + """Test division with incompatible shapes raises.""" + from ucon.numpy import NumberArray + a = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + b = NumberArray([1.0, 2.0], unit=self.second) + with self.assertRaises(ValueError) as ctx: + a / b + self.assertIn("not broadcast-compatible", str(ctx.exception)) + + def test_divide_arrays_with_uncertainty(self): + """Test NumberArray / NumberArray with uncertainty.""" + from ucon.numpy import NumberArray + a = NumberArray([10.0, 20.0], unit=self.meter, uncertainty=1.0) + b = NumberArray([2.0, 4.0], unit=self.second, uncertainty=0.1) + result = a / b + self.assertIsNotNone(result.uncertainty) + + +@unittest.skipUnless(HAS_NUMPY, "NumPy not installed") +class TestNumberArrayReductionsExtended(unittest.TestCase): + """Extended reduction tests for coverage.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + + def test_sum_with_per_element_uncertainty(self): + """Test sum with per-element uncertainty.""" + from ucon.numpy import NumberArray + unc = np.array([0.1, 0.2, 0.3]) + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter, uncertainty=unc) + total = arr.sum() + # σ_sum = sqrt(Σσᵢ²) + expected_unc = math.sqrt(0.1**2 + 0.2**2 + 0.3**2) + self.assertAlmostEqual(total.uncertainty, expected_unc) + + def test_mean_with_per_element_uncertainty(self): + """Test mean with per-element uncertainty.""" + from ucon.numpy import NumberArray + unc = np.array([0.1, 0.2, 0.3]) + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter, uncertainty=unc) + avg = arr.mean() + # σ_mean = sqrt(Σσᵢ²) / n + expected_unc = math.sqrt(0.1**2 + 0.2**2 + 0.3**2) / 3 + self.assertAlmostEqual(avg.uncertainty, expected_unc) + + +@unittest.skipUnless(HAS_NUMPY, "NumPy not installed") +class TestNumberArrayNumpyIntegration(unittest.TestCase): + """Test numpy integration features.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + + def test_asarray(self): + """Test np.asarray(NumberArray).""" + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + result = np.asarray(arr) + np.testing.assert_array_equal(result, np.array([1.0, 2.0, 3.0])) + + def test_asarray_with_dtype(self): + """Test np.asarray with dtype conversion.""" + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + result = np.asarray(arr, dtype=np.float32) + self.assertEqual(result.dtype, np.float32) + + def test_dimension_property(self): + """Test dimension property.""" + from ucon.numpy import NumberArray + from ucon.dimension import Dimension + arr = NumberArray([1.0, 2.0], unit=self.meter) + self.assertEqual(arr.dimension, Dimension.length) + + def test_dimension_property_dimensionless(self): + """Test dimension property for dimensionless.""" + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0]) + # dimensionless unit may have None dimension + dim = arr.dimension + # Just verify it doesn't raise + + +@unittest.skipUnless(HAS_NUMPY, "NumPy not installed") +class TestNumberArrayRepr(unittest.TestCase): + """Test string representation.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + + def test_small_array_repr(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0, 3.0], unit=self.meter) + s = repr(arr) + self.assertIn("m", s) + self.assertIn("1", s) + self.assertIn("3", s) + + def test_large_array_truncation(self): + from ucon.numpy import NumberArray + arr = NumberArray(list(range(100)), unit=self.meter) + s = repr(arr) + self.assertIn("...", s) + + def test_repr_with_uncertainty(self): + from ucon.numpy import NumberArray + arr = NumberArray([1.0, 2.0], unit=self.meter, uncertainty=0.1) + s = repr(arr) + self.assertIn("±", s) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/ucon/test_pandas.py b/tests/ucon/test_pandas.py new file mode 100644 index 0000000..5301879 --- /dev/null +++ b/tests/ucon/test_pandas.py @@ -0,0 +1,579 @@ +# tests/ucon/test_pandas.py +# +# Tests for NumberSeries and pandas integration. + +import unittest +import math + +try: + import pandas as pd + import numpy as np + HAS_PANDAS = True +except ImportError: + HAS_PANDAS = False + + +@unittest.skipUnless(HAS_PANDAS, "Pandas not installed") +class TestNumberSeriesBasic(unittest.TestCase): + """Test NumberSeries construction and basic properties.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + self.foot = units.foot + self.second = units.second + + def test_create_from_series(self): + from ucon.pandas import NumberSeries + s = pd.Series([1.0, 2.0, 3.0]) + ns = NumberSeries(s, unit=self.meter) + self.assertEqual(len(ns), 3) + self.assertEqual(ns.unit, self.meter) + + def test_create_from_list(self): + from ucon.pandas import NumberSeries + ns = NumberSeries([1.0, 2.0, 3.0], unit=self.meter) + self.assertEqual(len(ns), 3) + self.assertIsInstance(ns.series, pd.Series) + + def test_default_unit_is_dimensionless(self): + from ucon.pandas import NumberSeries + from ucon.core import _none + ns = NumberSeries(pd.Series([1.0, 2.0])) + self.assertEqual(ns.unit, _none) + + def test_uniform_uncertainty(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter, uncertainty=0.1) + self.assertEqual(ns.uncertainty, 0.1) + + def test_per_element_uncertainty(self): + from ucon.pandas import NumberSeries + unc = pd.Series([0.1, 0.2, 0.3]) + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter, uncertainty=unc) + pd.testing.assert_series_equal(ns.uncertainty, unc) + + def test_uncertainty_length_mismatch_raises(self): + from ucon.pandas import NumberSeries + with self.assertRaises(ValueError) as ctx: + NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter, + uncertainty=pd.Series([0.1, 0.2])) + self.assertIn("length", str(ctx.exception)) + + def test_index_property(self): + from ucon.pandas import NumberSeries + idx = pd.Index(['a', 'b', 'c']) + s = pd.Series([1.0, 2.0, 3.0], index=idx) + ns = NumberSeries(s, unit=self.meter) + pd.testing.assert_index_equal(ns.index, idx) + + +@unittest.skipUnless(HAS_PANDAS, "Pandas not installed") +class TestNumberSeriesIndexing(unittest.TestCase): + """Test NumberSeries indexing and iteration.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + + def test_scalar_index_returns_number(self): + from ucon.pandas import NumberSeries + from ucon.core import Number + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + elem = ns[0] + self.assertIsInstance(elem, Number) + self.assertEqual(elem.quantity, 1.0) + + def test_slice_returns_numberseries(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0, 4.0]), unit=self.meter) + sliced = ns[1:3] + self.assertIsInstance(sliced, NumberSeries) + self.assertEqual(len(sliced), 2) + + def test_label_index(self): + from ucon.pandas import NumberSeries + from ucon.core import Number + s = pd.Series([1.0, 2.0, 3.0], index=['a', 'b', 'c']) + ns = NumberSeries(s, unit=self.meter) + elem = ns['b'] + self.assertIsInstance(elem, Number) + self.assertEqual(elem.quantity, 2.0) + + def test_iteration_yields_numbers(self): + from ucon.pandas import NumberSeries + from ucon.core import Number + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + elements = list(ns) + self.assertEqual(len(elements), 3) + for elem in elements: + self.assertIsInstance(elem, Number) + + +@unittest.skipUnless(HAS_PANDAS, "Pandas not installed") +class TestNumberSeriesArithmetic(unittest.TestCase): + """Test NumberSeries arithmetic operations.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + self.second = units.second + + def test_multiply_by_scalar(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + result = ns * 2 + pd.testing.assert_series_equal( + result.series, pd.Series([2.0, 4.0, 6.0]), check_names=False + ) + self.assertEqual(result.unit, self.meter) + + def test_multiply_by_scalar_with_uncertainty(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0]), unit=self.meter, uncertainty=0.1) + result = ns * 2 + self.assertEqual(result.uncertainty, 0.2) + + def test_rmul(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + result = 2 * ns + pd.testing.assert_series_equal( + result.series, pd.Series([2.0, 4.0, 6.0]), check_names=False + ) + + def test_divide_by_scalar(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([2.0, 4.0, 6.0]), unit=self.meter) + result = ns / 2 + pd.testing.assert_series_equal( + result.series, pd.Series([1.0, 2.0, 3.0]), check_names=False + ) + + def test_multiply_numberseries(self): + from ucon.pandas import NumberSeries + a = NumberSeries(pd.Series([1.0, 2.0]), unit=self.meter) + b = NumberSeries(pd.Series([3.0, 4.0]), unit=self.second) + result = a * b + pd.testing.assert_series_equal( + result.series, pd.Series([3.0, 8.0]), check_names=False + ) + + def test_multiply_length_mismatch(self): + from ucon.pandas import NumberSeries + a = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + b = NumberSeries(pd.Series([1.0, 2.0]), unit=self.meter) + with self.assertRaises(ValueError) as ctx: + a * b + self.assertIn("Length mismatch", str(ctx.exception)) + + def test_add_same_unit(self): + from ucon.pandas import NumberSeries + a = NumberSeries(pd.Series([1.0, 2.0]), unit=self.meter) + b = NumberSeries(pd.Series([0.5, 0.5]), unit=self.meter) + result = a + b + pd.testing.assert_series_equal( + result.series, pd.Series([1.5, 2.5]), check_names=False + ) + + def test_add_different_unit_raises(self): + from ucon.pandas import NumberSeries + a = NumberSeries(pd.Series([1.0, 2.0]), unit=self.meter) + b = NumberSeries(pd.Series([1.0, 2.0]), unit=self.second) + with self.assertRaises(ValueError) as ctx: + a + b + self.assertIn("different units", str(ctx.exception)) + + def test_subtract(self): + from ucon.pandas import NumberSeries + a = NumberSeries(pd.Series([3.0, 4.0]), unit=self.meter) + b = NumberSeries(pd.Series([1.0, 1.0]), unit=self.meter) + result = a - b + pd.testing.assert_series_equal( + result.series, pd.Series([2.0, 3.0]), check_names=False + ) + + def test_negation(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, -2.0, 3.0]), unit=self.meter) + result = -ns + pd.testing.assert_series_equal( + result.series, pd.Series([-1.0, 2.0, -3.0]), check_names=False + ) + + def test_abs(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([-1.0, 2.0, -3.0]), unit=self.meter) + result = abs(ns) + pd.testing.assert_series_equal( + result.series, pd.Series([1.0, 2.0, 3.0]), check_names=False + ) + + +@unittest.skipUnless(HAS_PANDAS, "Pandas not installed") +class TestNumberSeriesArithmeticExtended(unittest.TestCase): + """Extended arithmetic tests for coverage.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + self.second = units.second + + def test_divide_by_number(self): + """Test NumberSeries / Number.""" + from ucon.pandas import NumberSeries + from ucon.core import Number + ns = NumberSeries(pd.Series([10.0, 20.0, 30.0]), unit=self.meter) + n = Number(quantity=2.0, unit=self.second) + result = ns / n + pd.testing.assert_series_equal( + result.series, pd.Series([5.0, 10.0, 15.0]), check_names=False + ) + + def test_divide_by_number_with_uncertainty(self): + """Test NumberSeries / Number with uncertainty.""" + from ucon.pandas import NumberSeries + from ucon.core import Number + ns = NumberSeries(pd.Series([10.0, 20.0]), unit=self.meter, uncertainty=1.0) + n = Number(quantity=2.0, unit=self.second, uncertainty=0.1) + result = ns / n + self.assertIsNotNone(result.uncertainty) + + def test_divide_numberseries_with_uncertainty(self): + """Test NumberSeries / NumberSeries with uncertainty.""" + from ucon.pandas import NumberSeries + a = NumberSeries(pd.Series([10.0, 20.0]), unit=self.meter, uncertainty=1.0) + b = NumberSeries(pd.Series([2.0, 4.0]), unit=self.second, uncertainty=0.1) + result = a / b + self.assertIsNotNone(result.uncertainty) + + def test_add_number(self): + """Test NumberSeries + Number.""" + from ucon.pandas import NumberSeries + from ucon.core import Number + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + n = Number(quantity=10.0, unit=self.meter) + result = ns + n + pd.testing.assert_series_equal( + result.series, pd.Series([11.0, 12.0, 13.0]), check_names=False + ) + + def test_add_number_with_uncertainty(self): + """Test NumberSeries + Number with uncertainty.""" + from ucon.pandas import NumberSeries + from ucon.core import Number + ns = NumberSeries(pd.Series([1.0, 2.0]), unit=self.meter, uncertainty=0.1) + n = Number(quantity=10.0, unit=self.meter, uncertainty=0.2) + result = ns + n + expected_unc = math.sqrt(0.1**2 + 0.2**2) + self.assertAlmostEqual(result.uncertainty, expected_unc) + + def test_sub_number(self): + """Test NumberSeries - Number.""" + from ucon.pandas import NumberSeries + from ucon.core import Number + ns = NumberSeries(pd.Series([10.0, 20.0, 30.0]), unit=self.meter) + n = Number(quantity=5.0, unit=self.meter) + result = ns - n + pd.testing.assert_series_equal( + result.series, pd.Series([5.0, 15.0, 25.0]), check_names=False + ) + + def test_multiply_by_number(self): + """Test NumberSeries * Number.""" + from ucon.pandas import NumberSeries + from ucon.core import Number + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + n = Number(quantity=2.0, unit=self.second) + result = ns * n + pd.testing.assert_series_equal( + result.series, pd.Series([2.0, 4.0, 6.0]), check_names=False + ) + + def test_multiply_by_number_with_uncertainty(self): + """Test NumberSeries * Number with uncertainty.""" + from ucon.pandas import NumberSeries + from ucon.core import Number + ns = NumberSeries(pd.Series([10.0, 20.0]), unit=self.meter, uncertainty=1.0) + n = Number(quantity=2.0, unit=self.second, uncertainty=0.1) + result = ns * n + self.assertIsNotNone(result.uncertainty) + + def test_divide_numberseries_length_mismatch(self): + """Test division with length mismatch.""" + from ucon.pandas import NumberSeries + a = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + b = NumberSeries(pd.Series([1.0, 2.0]), unit=self.second) + with self.assertRaises(ValueError) as ctx: + a / b + self.assertIn("Length mismatch", str(ctx.exception)) + + def test_add_length_mismatch(self): + """Test addition with length mismatch.""" + from ucon.pandas import NumberSeries + a = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + b = NumberSeries(pd.Series([1.0, 2.0]), unit=self.meter) + with self.assertRaises(ValueError) as ctx: + a + b + self.assertIn("Length mismatch", str(ctx.exception)) + + +@unittest.skipUnless(HAS_PANDAS, "Pandas not installed") +class TestNumberSeriesConversion(unittest.TestCase): + """Test NumberSeries unit conversion.""" + + def setUp(self): + from ucon import units + from ucon.core import Scale + self.meter = units.meter + self.foot = units.foot + self.kilometer = Scale.kilo * units.meter + + def test_scale_only_conversion(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.kilometer) + result = ns.to(self.meter) + pd.testing.assert_series_equal( + result.series, pd.Series([1000.0, 2000.0, 3000.0]), check_names=False + ) + self.assertEqual(result.unit, self.meter) + + def test_conversion_with_uncertainty(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0]), unit=self.kilometer, uncertainty=0.1) + result = ns.to(self.meter) + self.assertAlmostEqual(result.uncertainty, 100.0) + + def test_graph_based_conversion(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + result = ns.to(self.foot) + # 1 meter ~ 3.28084 feet + expected = pd.Series([1.0, 2.0, 3.0]) * 3.28084 + pd.testing.assert_series_equal( + result.series, expected, check_names=False, atol=0.001 + ) + + +@unittest.skipUnless(HAS_PANDAS, "Pandas not installed") +class TestNumberSeriesComparison(unittest.TestCase): + """Test comparison operators returning boolean Series.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + self.second = units.second + + def test_eq_with_scalar(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + result = ns == 2.0 + pd.testing.assert_series_equal(result, pd.Series([False, True, False])) + + def test_eq_with_number(self): + from ucon.pandas import NumberSeries + from ucon.core import Number + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + n = Number(quantity=2.0, unit=self.meter) + result = ns == n + pd.testing.assert_series_equal(result, pd.Series([False, True, False])) + + def test_eq_with_numberseries(self): + from ucon.pandas import NumberSeries + a = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + b = NumberSeries(pd.Series([1.0, 5.0, 3.0]), unit=self.meter) + result = a == b + pd.testing.assert_series_equal(result, pd.Series([True, False, True])) + + def test_ne_with_scalar(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + result = ns != 2.0 + pd.testing.assert_series_equal(result, pd.Series([True, False, True])) + + def test_lt_with_scalar(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + result = ns < 2.0 + pd.testing.assert_series_equal(result, pd.Series([True, False, False])) + + def test_le_with_scalar(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + result = ns <= 2.0 + pd.testing.assert_series_equal(result, pd.Series([True, True, False])) + + def test_gt_with_scalar(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + result = ns > 2.0 + pd.testing.assert_series_equal(result, pd.Series([False, False, True])) + + def test_ge_with_scalar(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + result = ns >= 2.0 + pd.testing.assert_series_equal(result, pd.Series([False, True, True])) + + def test_comparison_different_unit_raises(self): + from ucon.pandas import NumberSeries + a = NumberSeries(pd.Series([1.0, 2.0]), unit=self.meter) + b = NumberSeries(pd.Series([1.0, 2.0]), unit=self.second) + with self.assertRaises(ValueError) as ctx: + a == b + self.assertIn("different units", str(ctx.exception)) + + def test_comparison_for_filtering(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0, 4.0, 5.0]), unit=self.meter) + mask = ns > 2.5 + filtered = ns.series[mask] + pd.testing.assert_series_equal( + filtered.reset_index(drop=True), + pd.Series([3.0, 4.0, 5.0]) + ) + + +@unittest.skipUnless(HAS_PANDAS, "Pandas not installed") +class TestNumberSeriesReductions(unittest.TestCase): + """Test reduction operations (sum, mean, etc.).""" + + def setUp(self): + from ucon import units + self.meter = units.meter + + def test_sum(self): + from ucon.pandas import NumberSeries + from ucon.core import Number + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0, 4.0]), unit=self.meter) + total = ns.sum() + self.assertIsInstance(total, Number) + self.assertEqual(total.quantity, 10.0) + self.assertEqual(total.unit, self.meter) + + def test_sum_with_uncertainty(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0, 4.0]), unit=self.meter, uncertainty=0.1) + total = ns.sum() + self.assertAlmostEqual(total.uncertainty, 0.1 * math.sqrt(4)) + + def test_mean(self): + from ucon.pandas import NumberSeries + from ucon.core import Number + ns = NumberSeries(pd.Series([2.0, 4.0, 6.0]), unit=self.meter) + avg = ns.mean() + self.assertIsInstance(avg, Number) + self.assertEqual(avg.quantity, 4.0) + + def test_mean_with_uncertainty(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0, 4.0]), unit=self.meter, uncertainty=0.2) + avg = ns.mean() + self.assertAlmostEqual(avg.uncertainty, 0.2 / math.sqrt(4)) + + def test_std(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([2.0, 4.0, 6.0, 8.0]), unit=self.meter) + s = ns.std() + expected = pd.Series([2.0, 4.0, 6.0, 8.0]).std() + self.assertAlmostEqual(s.quantity, expected) + + def test_min_max(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([3.0, 1.0, 4.0, 1.0, 5.0]), unit=self.meter) + self.assertEqual(ns.min().quantity, 1.0) + self.assertEqual(ns.max().quantity, 5.0) + + +@unittest.skipUnless(HAS_PANDAS, "Pandas not installed") +class TestNumberSeriesRepr(unittest.TestCase): + """Test string representation.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + + def test_small_series_repr(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + s = repr(ns) + self.assertIn("NumberSeries", s) + self.assertIn("m", s) + + def test_large_series_truncation(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series(range(100)), unit=self.meter) + s = repr(ns) + self.assertIn("...", s) + + def test_repr_with_uncertainty(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0]), unit=self.meter, uncertainty=0.1) + s = repr(ns) + self.assertIn("\u00b1", s) + + +@unittest.skipUnless(HAS_PANDAS, "Pandas not installed") +class TestUconSeriesAccessor(unittest.TestCase): + """Test the pandas Series accessor.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + self.foot = units.foot + + def test_accessor_with_unit(self): + from ucon.pandas import NumberSeries + s = pd.Series([1.7, 1.8, 1.9]) + ns = s.ucon.with_unit(self.meter) + self.assertIsInstance(ns, NumberSeries) + self.assertEqual(ns.unit, self.meter) + + def test_accessor_callable(self): + from ucon.pandas import NumberSeries + s = pd.Series([1.7, 1.8, 1.9]) + ns = s.ucon(self.meter) + self.assertIsInstance(ns, NumberSeries) + self.assertEqual(ns.unit, self.meter) + + def test_accessor_with_uncertainty(self): + from ucon.pandas import NumberSeries + s = pd.Series([1.7, 1.8, 1.9]) + ns = s.ucon.with_unit(self.meter, uncertainty=0.01) + self.assertEqual(ns.uncertainty, 0.01) + + def test_accessor_conversion_chain(self): + from ucon.pandas import NumberSeries + s = pd.Series([1.0, 2.0, 3.0]) + result = s.ucon(self.meter).to(self.foot) + self.assertIsInstance(result, NumberSeries) + self.assertEqual(result.unit, self.foot) + # Check conversion factor + self.assertAlmostEqual(result.series.iloc[0], 3.28084, places=3) + + +@unittest.skipUnless(HAS_PANDAS, "Pandas not installed") +class TestNumberSeriesToFrame(unittest.TestCase): + """Test DataFrame conversion.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + + def test_to_frame_default_name(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + df = ns.to_frame() + self.assertIsInstance(df, pd.DataFrame) + self.assertEqual(len(df.columns), 1) + self.assertIn('m', df.columns[0]) + + def test_to_frame_custom_name(self): + from ucon.pandas import NumberSeries + ns = NumberSeries(pd.Series([1.0, 2.0, 3.0]), unit=self.meter) + df = ns.to_frame(name='height') + self.assertEqual(df.columns[0], 'height') + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/ucon/test_polars.py b/tests/ucon/test_polars.py new file mode 100644 index 0000000..beb9066 --- /dev/null +++ b/tests/ucon/test_polars.py @@ -0,0 +1,689 @@ +# tests/ucon/test_polars.py +# +# Tests for NumberColumn and polars integration. + +import unittest +import math + +try: + import polars as pl + HAS_POLARS = True +except ImportError: + HAS_POLARS = False + + +@unittest.skipUnless(HAS_POLARS, "Polars not installed") +class TestNumberColumnBasic(unittest.TestCase): + """Test NumberColumn construction and basic properties.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + self.foot = units.foot + self.second = units.second + + def test_create_from_series(self): + from ucon.polars import NumberColumn + s = pl.Series([1.0, 2.0, 3.0]) + nc = NumberColumn(s, unit=self.meter) + self.assertEqual(len(nc), 3) + self.assertEqual(nc.unit, self.meter) + + def test_create_from_list(self): + from ucon.polars import NumberColumn + nc = NumberColumn([1.0, 2.0, 3.0], unit=self.meter) + self.assertEqual(len(nc), 3) + self.assertIsInstance(nc.series, pl.Series) + + def test_default_unit_is_dimensionless(self): + from ucon.polars import NumberColumn + from ucon.core import _none + nc = NumberColumn(pl.Series([1.0, 2.0])) + self.assertEqual(nc.unit, _none) + + def test_uniform_uncertainty(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter, uncertainty=0.1) + self.assertEqual(nc.uncertainty, 0.1) + + def test_per_element_uncertainty(self): + from ucon.polars import NumberColumn + unc = pl.Series([0.1, 0.2, 0.3]) + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter, uncertainty=unc) + self.assertEqual(len(nc.uncertainty), 3) + + def test_uncertainty_length_mismatch_raises(self): + from ucon.polars import NumberColumn + with self.assertRaises(ValueError) as ctx: + NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter, + uncertainty=pl.Series([0.1, 0.2])) + self.assertIn("length", str(ctx.exception)) + + +@unittest.skipUnless(HAS_POLARS, "Polars not installed") +class TestNumberColumnIndexing(unittest.TestCase): + """Test NumberColumn indexing and iteration.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + + def test_scalar_index_returns_number(self): + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + elem = nc[0] + self.assertIsInstance(elem, Number) + self.assertEqual(elem.quantity, 1.0) + + def test_slice_returns_numbercolumn(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0, 4.0]), unit=self.meter) + sliced = nc[1:3] + self.assertIsInstance(sliced, NumberColumn) + self.assertEqual(len(sliced), 2) + + def test_iteration_yields_numbers(self): + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + elements = list(nc) + self.assertEqual(len(elements), 3) + for elem in elements: + self.assertIsInstance(elem, Number) + + +@unittest.skipUnless(HAS_POLARS, "Polars not installed") +class TestNumberColumnArithmetic(unittest.TestCase): + """Test NumberColumn arithmetic operations.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + self.second = units.second + + def test_multiply_by_scalar(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + result = nc * 2 + self.assertEqual(result.series.to_list(), [2.0, 4.0, 6.0]) + self.assertEqual(result.unit, self.meter) + + def test_multiply_by_scalar_with_uncertainty(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0]), unit=self.meter, uncertainty=0.1) + result = nc * 2 + self.assertEqual(result.uncertainty, 0.2) + + def test_rmul(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + result = 2 * nc + self.assertEqual(result.series.to_list(), [2.0, 4.0, 6.0]) + + def test_divide_by_scalar(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([2.0, 4.0, 6.0]), unit=self.meter) + result = nc / 2 + self.assertEqual(result.series.to_list(), [1.0, 2.0, 3.0]) + + def test_multiply_numbercolumns(self): + from ucon.polars import NumberColumn + a = NumberColumn(pl.Series([1.0, 2.0]), unit=self.meter) + b = NumberColumn(pl.Series([3.0, 4.0]), unit=self.second) + result = a * b + self.assertEqual(result.series.to_list(), [3.0, 8.0]) + + def test_multiply_length_mismatch(self): + from ucon.polars import NumberColumn + a = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + b = NumberColumn(pl.Series([1.0, 2.0]), unit=self.meter) + with self.assertRaises(ValueError) as ctx: + a * b + self.assertIn("Length mismatch", str(ctx.exception)) + + def test_add_same_unit(self): + from ucon.polars import NumberColumn + a = NumberColumn(pl.Series([1.0, 2.0]), unit=self.meter) + b = NumberColumn(pl.Series([0.5, 0.5]), unit=self.meter) + result = a + b + self.assertEqual(result.series.to_list(), [1.5, 2.5]) + + def test_add_different_unit_raises(self): + from ucon.polars import NumberColumn + a = NumberColumn(pl.Series([1.0, 2.0]), unit=self.meter) + b = NumberColumn(pl.Series([1.0, 2.0]), unit=self.second) + with self.assertRaises(ValueError) as ctx: + a + b + self.assertIn("different units", str(ctx.exception)) + + def test_subtract(self): + from ucon.polars import NumberColumn + a = NumberColumn(pl.Series([3.0, 4.0]), unit=self.meter) + b = NumberColumn(pl.Series([1.0, 1.0]), unit=self.meter) + result = a - b + self.assertEqual(result.series.to_list(), [2.0, 3.0]) + + def test_negation(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, -2.0, 3.0]), unit=self.meter) + result = -nc + self.assertEqual(result.series.to_list(), [-1.0, 2.0, -3.0]) + + def test_abs(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([-1.0, 2.0, -3.0]), unit=self.meter) + result = abs(nc) + self.assertEqual(result.series.to_list(), [1.0, 2.0, 3.0]) + + +@unittest.skipUnless(HAS_POLARS, "Polars not installed") +class TestNumberColumnComparison(unittest.TestCase): + """Test comparison operators returning boolean Series.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + self.second = units.second + + def test_eq_with_scalar(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + result = nc == 2.0 + self.assertEqual(result.to_list(), [False, True, False]) + + def test_eq_with_number(self): + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + n = Number(quantity=2.0, unit=self.meter) + result = nc == n + self.assertEqual(result.to_list(), [False, True, False]) + + def test_eq_with_numbercolumn(self): + from ucon.polars import NumberColumn + a = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + b = NumberColumn(pl.Series([1.0, 5.0, 3.0]), unit=self.meter) + result = a == b + self.assertEqual(result.to_list(), [True, False, True]) + + def test_ne_with_scalar(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + result = nc != 2.0 + self.assertEqual(result.to_list(), [True, False, True]) + + def test_lt_with_scalar(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + result = nc < 2.0 + self.assertEqual(result.to_list(), [True, False, False]) + + def test_le_with_scalar(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + result = nc <= 2.0 + self.assertEqual(result.to_list(), [True, True, False]) + + def test_gt_with_scalar(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + result = nc > 2.0 + self.assertEqual(result.to_list(), [False, False, True]) + + def test_ge_with_scalar(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + result = nc >= 2.0 + self.assertEqual(result.to_list(), [False, True, True]) + + def test_comparison_different_unit_raises(self): + from ucon.polars import NumberColumn + a = NumberColumn(pl.Series([1.0, 2.0]), unit=self.meter) + b = NumberColumn(pl.Series([1.0, 2.0]), unit=self.second) + with self.assertRaises(ValueError) as ctx: + a == b + self.assertIn("different units", str(ctx.exception)) + + def test_comparison_for_filtering(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0, 4.0, 5.0]), unit=self.meter) + mask = nc > 2.5 + filtered = nc.series.filter(mask) + self.assertEqual(filtered.to_list(), [3.0, 4.0, 5.0]) + + +@unittest.skipUnless(HAS_POLARS, "Polars not installed") +class TestNumberColumnConversion(unittest.TestCase): + """Test NumberColumn unit conversion.""" + + def setUp(self): + from ucon import units + from ucon.core import Scale + self.meter = units.meter + self.foot = units.foot + self.kilometer = Scale.kilo * units.meter + + def test_scale_only_conversion(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.kilometer) + result = nc.to(self.meter) + self.assertEqual(result.series.to_list(), [1000.0, 2000.0, 3000.0]) + self.assertEqual(result.unit, self.meter) + + def test_conversion_with_uncertainty(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0]), unit=self.kilometer, uncertainty=0.1) + result = nc.to(self.meter) + self.assertAlmostEqual(result.uncertainty, 100.0) + + def test_graph_based_conversion(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + result = nc.to(self.foot) + # 1 meter ~ 3.28084 feet + expected = [v * 3.28084 for v in [1.0, 2.0, 3.0]] + for i, (actual, exp) in enumerate(zip(result.series.to_list(), expected)): + self.assertAlmostEqual(actual, exp, places=3) + + +@unittest.skipUnless(HAS_POLARS, "Polars not installed") +class TestNumberColumnReductions(unittest.TestCase): + """Test reduction operations (sum, mean, etc.).""" + + def setUp(self): + from ucon import units + self.meter = units.meter + + def test_sum(self): + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0, 4.0]), unit=self.meter) + total = nc.sum() + self.assertIsInstance(total, Number) + self.assertEqual(total.quantity, 10.0) + self.assertEqual(total.unit, self.meter) + + def test_sum_with_uncertainty(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0, 4.0]), unit=self.meter, uncertainty=0.1) + total = nc.sum() + self.assertAlmostEqual(total.uncertainty, 0.1 * math.sqrt(4)) + + def test_mean(self): + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([2.0, 4.0, 6.0]), unit=self.meter) + avg = nc.mean() + self.assertIsInstance(avg, Number) + self.assertEqual(avg.quantity, 4.0) + + def test_mean_with_uncertainty(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0, 4.0]), unit=self.meter, uncertainty=0.2) + avg = nc.mean() + self.assertAlmostEqual(avg.uncertainty, 0.2 / math.sqrt(4)) + + def test_std(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([2.0, 4.0, 6.0, 8.0]), unit=self.meter) + s = nc.std() + expected = pl.Series([2.0, 4.0, 6.0, 8.0]).std() + self.assertAlmostEqual(s.quantity, expected) + + def test_min_max(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([3.0, 1.0, 4.0, 1.0, 5.0]), unit=self.meter) + self.assertEqual(nc.min().quantity, 1.0) + self.assertEqual(nc.max().quantity, 5.0) + + +@unittest.skipUnless(HAS_POLARS, "Polars not installed") +class TestNumberColumnRepr(unittest.TestCase): + """Test string representation.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + + def test_small_column_repr(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + s = repr(nc) + self.assertIn("NumberColumn", s) + self.assertIn("m", s) + + def test_large_column_truncation(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series(list(range(100))), unit=self.meter) + s = repr(nc) + self.assertIn("...", s) + + def test_repr_with_uncertainty(self): + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0]), unit=self.meter, uncertainty=0.1) + s = repr(nc) + self.assertIn("\u00b1", s) + + +@unittest.skipUnless(HAS_POLARS, "Polars not installed") +class TestNumberColumnToList(unittest.TestCase): + """Test to_list conversion.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + + def test_to_list(self): + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + numbers = nc.to_list() + self.assertEqual(len(numbers), 3) + for n in numbers: + self.assertIsInstance(n, Number) + self.assertEqual(n.unit, self.meter) + + +@unittest.skipUnless(HAS_POLARS, "Polars not installed") +class TestNumberColumnArithmeticExtended(unittest.TestCase): + """Extended arithmetic tests for better coverage.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + self.second = units.second + + def test_divide_by_number(self): + """Test division by a Number.""" + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([10.0, 20.0, 30.0]), unit=self.meter) + divisor = Number(quantity=2.0, unit=self.second) + result = nc / divisor + self.assertEqual(result.series.to_list(), [5.0, 10.0, 15.0]) + + def test_divide_by_number_with_uncertainty(self): + """Test division by Number with uncertainty propagation.""" + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([10.0, 20.0]), unit=self.meter, uncertainty=1.0) + divisor = Number(quantity=2.0, unit=self.second, uncertainty=0.1) + result = nc / divisor + self.assertIsNotNone(result.uncertainty) + + def test_divide_numbercolumn_by_numbercolumn(self): + """Test division between two NumberColumns.""" + from ucon.polars import NumberColumn + a = NumberColumn(pl.Series([10.0, 20.0, 30.0]), unit=self.meter) + b = NumberColumn(pl.Series([2.0, 4.0, 5.0]), unit=self.second) + result = a / b + self.assertEqual(result.series.to_list(), [5.0, 5.0, 6.0]) + + def test_divide_numbercolumn_with_uncertainty(self): + """Test division between NumberColumns with uncertainty.""" + from ucon.polars import NumberColumn + a = NumberColumn(pl.Series([10.0, 20.0]), unit=self.meter, uncertainty=1.0) + b = NumberColumn(pl.Series([2.0, 4.0]), unit=self.second, uncertainty=0.1) + result = a / b + self.assertIsNotNone(result.uncertainty) + + def test_divide_length_mismatch(self): + """Test division with mismatched lengths raises.""" + from ucon.polars import NumberColumn + a = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + b = NumberColumn(pl.Series([1.0, 2.0]), unit=self.second) + with self.assertRaises(ValueError) as ctx: + a / b + self.assertIn("Length mismatch", str(ctx.exception)) + + def test_add_number(self): + """Test adding a Number to NumberColumn.""" + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + n = Number(quantity=0.5, unit=self.meter) + result = nc + n + self.assertEqual(result.series.to_list(), [1.5, 2.5, 3.5]) + + def test_add_number_with_uncertainty(self): + """Test adding Number with uncertainty propagation.""" + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([1.0, 2.0]), unit=self.meter, uncertainty=0.1) + n = Number(quantity=0.5, unit=self.meter, uncertainty=0.05) + result = nc + n + self.assertIsNotNone(result.uncertainty) + + def test_add_length_mismatch(self): + """Test addition with mismatched lengths raises.""" + from ucon.polars import NumberColumn + a = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + b = NumberColumn(pl.Series([1.0, 2.0]), unit=self.meter) + with self.assertRaises(ValueError) as ctx: + a + b + self.assertIn("Length mismatch", str(ctx.exception)) + + def test_sub_number(self): + """Test subtracting a Number from NumberColumn.""" + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([3.0, 4.0, 5.0]), unit=self.meter) + n = Number(quantity=1.0, unit=self.meter) + result = nc - n + self.assertEqual(result.series.to_list(), [2.0, 3.0, 4.0]) + + def test_sub_number_with_uncertainty(self): + """Test subtracting Number with uncertainty propagation.""" + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([3.0, 4.0]), unit=self.meter, uncertainty=0.1) + n = Number(quantity=1.0, unit=self.meter, uncertainty=0.05) + result = nc - n + self.assertIsNotNone(result.uncertainty) + + def test_sub_length_mismatch(self): + """Test subtraction with mismatched lengths raises.""" + from ucon.polars import NumberColumn + a = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + b = NumberColumn(pl.Series([1.0, 2.0]), unit=self.meter) + with self.assertRaises(ValueError) as ctx: + a - b + self.assertIn("Length mismatch", str(ctx.exception)) + + def test_multiply_by_number(self): + """Test multiplying NumberColumn by Number.""" + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + n = Number(quantity=2.0, unit=self.second) + result = nc * n + self.assertEqual(result.series.to_list(), [2.0, 4.0, 6.0]) + + def test_multiply_by_number_with_uncertainty(self): + """Test multiplying by Number with uncertainty propagation.""" + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([10.0, 20.0]), unit=self.meter, uncertainty=1.0) + n = Number(quantity=2.0, unit=self.second, uncertainty=0.1) + result = nc * n + self.assertIsNotNone(result.uncertainty) + + def test_multiply_numbercolumns_with_uncertainty(self): + """Test multiplying NumberColumns with uncertainty.""" + from ucon.polars import NumberColumn + a = NumberColumn(pl.Series([10.0, 20.0]), unit=self.meter, uncertainty=1.0) + b = NumberColumn(pl.Series([2.0, 3.0]), unit=self.second, uncertainty=0.1) + result = a * b + self.assertIsNotNone(result.uncertainty) + + +@unittest.skipUnless(HAS_POLARS, "Polars not installed") +class TestNumberColumnComparisonExtended(unittest.TestCase): + """Extended comparison tests for better coverage.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + self.second = units.second + + def test_lt_with_number(self): + """Test less-than comparison with Number.""" + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + n = Number(quantity=2.5, unit=self.meter) + result = nc < n + self.assertEqual(result.to_list(), [True, True, False]) + + def test_lt_with_numbercolumn(self): + """Test less-than comparison with NumberColumn.""" + from ucon.polars import NumberColumn + a = NumberColumn(pl.Series([1.0, 3.0, 2.0]), unit=self.meter) + b = NumberColumn(pl.Series([2.0, 2.0, 2.0]), unit=self.meter) + result = a < b + self.assertEqual(result.to_list(), [True, False, False]) + + def test_le_with_number(self): + """Test less-equal comparison with Number.""" + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + n = Number(quantity=2.0, unit=self.meter) + result = nc <= n + self.assertEqual(result.to_list(), [True, True, False]) + + def test_le_with_numbercolumn(self): + """Test less-equal comparison with NumberColumn.""" + from ucon.polars import NumberColumn + a = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + b = NumberColumn(pl.Series([2.0, 2.0, 2.0]), unit=self.meter) + result = a <= b + self.assertEqual(result.to_list(), [True, True, False]) + + def test_gt_with_number(self): + """Test greater-than comparison with Number.""" + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + n = Number(quantity=1.5, unit=self.meter) + result = nc > n + self.assertEqual(result.to_list(), [False, True, True]) + + def test_gt_with_numbercolumn(self): + """Test greater-than comparison with NumberColumn.""" + from ucon.polars import NumberColumn + a = NumberColumn(pl.Series([1.0, 3.0, 2.0]), unit=self.meter) + b = NumberColumn(pl.Series([2.0, 2.0, 2.0]), unit=self.meter) + result = a > b + self.assertEqual(result.to_list(), [False, True, False]) + + def test_ge_with_number(self): + """Test greater-equal comparison with Number.""" + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + n = Number(quantity=2.0, unit=self.meter) + result = nc >= n + self.assertEqual(result.to_list(), [False, True, True]) + + def test_ge_with_numbercolumn(self): + """Test greater-equal comparison with NumberColumn.""" + from ucon.polars import NumberColumn + a = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + b = NumberColumn(pl.Series([2.0, 2.0, 2.0]), unit=self.meter) + result = a >= b + self.assertEqual(result.to_list(), [False, True, True]) + + def test_ne_with_number(self): + """Test not-equal comparison with Number.""" + from ucon.polars import NumberColumn + from ucon.core import Number + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + n = Number(quantity=2.0, unit=self.meter) + result = nc != n + self.assertEqual(result.to_list(), [True, False, True]) + + def test_ne_with_numbercolumn(self): + """Test not-equal comparison with NumberColumn.""" + from ucon.polars import NumberColumn + a = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + b = NumberColumn(pl.Series([1.0, 5.0, 3.0]), unit=self.meter) + result = a != b + self.assertEqual(result.to_list(), [False, True, False]) + + +@unittest.skipUnless(HAS_POLARS, "Polars not installed") +class TestNumberColumnReductionsExtended(unittest.TestCase): + """Extended reduction tests for better coverage.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + + def test_sum_with_per_element_uncertainty(self): + """Test sum with per-element uncertainty.""" + from ucon.polars import NumberColumn + nc = NumberColumn( + pl.Series([1.0, 2.0, 3.0, 4.0]), + unit=self.meter, + uncertainty=pl.Series([0.1, 0.2, 0.1, 0.2]) + ) + total = nc.sum() + self.assertEqual(total.quantity, 10.0) + self.assertIsNotNone(total.uncertainty) + # sqrt(0.1^2 + 0.2^2 + 0.1^2 + 0.2^2) = sqrt(0.01 + 0.04 + 0.01 + 0.04) = sqrt(0.1) ~ 0.316 + self.assertAlmostEqual(total.uncertainty, 0.3162, places=3) + + def test_mean_with_per_element_uncertainty(self): + """Test mean with per-element uncertainty.""" + from ucon.polars import NumberColumn + nc = NumberColumn( + pl.Series([1.0, 2.0, 3.0, 4.0]), + unit=self.meter, + uncertainty=pl.Series([0.1, 0.2, 0.1, 0.2]) + ) + avg = nc.mean() + self.assertEqual(avg.quantity, 2.5) + self.assertIsNotNone(avg.uncertainty) + + +@unittest.skipUnless(HAS_POLARS, "Polars not installed") +class TestNumberColumnProperties(unittest.TestCase): + """Test NumberColumn properties for coverage.""" + + def setUp(self): + from ucon import units + self.meter = units.meter + + def test_shape_property(self): + """Test shape property.""" + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + self.assertEqual(nc.shape, (3,)) + + def test_dtype_property(self): + """Test dtype property.""" + from ucon.polars import NumberColumn + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + self.assertEqual(nc.dtype, pl.Float64) + + def test_dimension_property(self): + """Test dimension property.""" + from ucon.polars import NumberColumn + from ucon.core import Dimension + nc = NumberColumn(pl.Series([1.0, 2.0, 3.0]), unit=self.meter) + self.assertEqual(nc.dimension, Dimension.length) + + def test_dimension_property_unitproduct(self): + """Test dimension property with UnitProduct.""" + from ucon.polars import NumberColumn + from ucon import units + area_unit = units.meter * units.meter + nc = NumberColumn(pl.Series([1.0, 2.0]), unit=area_unit) + dim = nc.dimension + self.assertIsNotNone(dim) + + +if __name__ == "__main__": + unittest.main() diff --git a/ucon/core.py b/ucon/core.py index e1b18c7..07c677c 100644 --- a/ucon/core.py +++ b/ucon/core.py @@ -436,6 +436,8 @@ def __mul__(self, other): return other.__rmul__(self) if isinstance(other, Unit): + if self == other: + return UnitProduct({self: 2}) return UnitProduct({self: 1, other: 1}) return NotImplemented @@ -510,23 +512,43 @@ def __repr__(self): # ----------------- callable (creates Number) ----------------- - def __call__(self, quantity: Union[int, float], uncertainty: Union[float, None] = None) -> "Number": - """Create a Number with this unit. + def __call__(self, quantity, uncertainty=None): + """Create a Number or NumberArray with this unit. Parameters ---------- - quantity : int or float - The numeric value. - uncertainty : float, optional + quantity : int, float, list, tuple, or numpy.ndarray + The numeric value(s). If array-like, returns NumberArray. + uncertainty : float, array-like, or None The measurement uncertainty. + Returns + ------- + Number or NumberArray + Number for scalar inputs, NumberArray for array inputs. + Example ------- >>> meter(5) <5 m> >>> meter(1.234, uncertainty=0.005) <1.234 ± 0.005 m> + >>> meter([1, 2, 3]) # requires numpy + """ + # Check for array-like inputs + try: + import numpy as np + if isinstance(quantity, np.ndarray): + from ucon.numpy import NumberArray + return NumberArray(quantities=quantity, unit=self, uncertainty=uncertainty) + if isinstance(quantity, (list, tuple)) and len(quantity) > 0: + from ucon.numpy import NumberArray + return NumberArray(quantities=quantity, unit=self, uncertainty=uncertainty) + except ImportError: + # numpy not installed - fall through to scalar handling + pass + return Number(quantity=quantity, unit=UnitProduct.from_unit(self), uncertainty=uncertainty) @@ -958,9 +980,16 @@ def fold_scale(self) -> float: float The combined numeric scale factor. """ + # Cache the result since UnitProduct is effectively immutable + cached = getattr(self, '_fold_scale_cache', None) + if cached is not None: + return cached + result = getattr(self, '_residual_scale_factor', 1.0) for factor, power in self.factors.items(): result *= factor.scale.value.evaluated ** power + + self._fold_scale_cache = result return result # ------------- Helpers --------------------------------------------------- @@ -1116,23 +1145,43 @@ def __hash__(self): # Sort by name; UnitFactor exposes .name, so this is stable. return hash(tuple(sorted(self.factors.items(), key=lambda x: x[0].name))) - def __call__(self, quantity: Union[int, float], uncertainty: Union[float, None] = None) -> "Number": - """Create a Number with this unit product. + def __call__(self, quantity, uncertainty=None): + """Create a Number or NumberArray with this unit product. Parameters ---------- - quantity : int or float - The numeric value. - uncertainty : float, optional + quantity : int, float, list, tuple, or numpy.ndarray + The numeric value(s). If array-like, returns NumberArray. + uncertainty : float, array-like, or None The measurement uncertainty. + Returns + ------- + Number or NumberArray + Number for scalar inputs, NumberArray for array inputs. + Example ------- >>> (meter / second)(10) <10 m/s> >>> (meter / second)(10, uncertainty=0.5) <10 ± 0.5 m/s> + >>> (meter / second)([10, 20, 30]) # requires numpy + """ + # Check for array-like inputs + try: + import numpy as np + if isinstance(quantity, np.ndarray): + from ucon.numpy import NumberArray + return NumberArray(quantities=quantity, unit=self, uncertainty=uncertainty) + if isinstance(quantity, (list, tuple)) and len(quantity) > 0: + from ucon.numpy import NumberArray + return NumberArray(quantities=quantity, unit=self, uncertainty=uncertainty) + except ImportError: + # numpy not installed - fall through to scalar handling + pass + return Number(quantity=quantity, unit=self, uncertainty=uncertainty) diff --git a/ucon/graph.py b/ucon/graph.py index 39e1c50..488b6e6 100644 --- a/ucon/graph.py +++ b/ucon/graph.py @@ -90,6 +90,10 @@ class ConversionGraph: # Optional BasisGraph for cross-basis dimensional validation _basis_graph: BasisGraph | None = field(default=None) + # Conversion path cache: (src_key, dst_key) -> Map + # Cleared when edges are added + _conversion_cache: dict[tuple, Map] = field(default_factory=dict) + # ------------- Edge Management ------------------------------------------- def add_edge( @@ -124,6 +128,9 @@ def add_edge( NoTransformPath If basis_graph is set and no path exists between src/dst bases. """ + # Clear conversion cache (new edge may create shorter paths) + self._conversion_cache.clear() + # If basis_graph is set, validate cross-basis compatibility if self._basis_graph is not None and basis_transform is None: src_basis = getattr(getattr(src, 'dimension', None), 'vector', None) @@ -474,15 +481,24 @@ def convert( ConversionNotFound If no conversion path exists. """ + # Check cache first (units are hashable) + cache_key = (src, dst) + if cache_key in self._conversion_cache: + return self._conversion_cache[cache_key] + # Both plain Units if isinstance(src, Unit) and not isinstance(src, UnitProduct): if isinstance(dst, Unit) and not isinstance(dst, UnitProduct): - return self._convert_units(src=src, dst=dst) + result = self._convert_units(src=src, dst=dst) + self._conversion_cache[cache_key] = result + return result # At least one is a UnitProduct src_prod = src if isinstance(src, UnitProduct) else UnitProduct.from_unit(src) dst_prod = dst if isinstance(dst, UnitProduct) else UnitProduct.from_unit(dst) - return self._convert_products(src=src_prod, dst=dst_prod) + result = self._convert_products(src=src_prod, dst=dst_prod) + self._conversion_cache[cache_key] = result + return result def _convert_units(self, *, src: Unit, dst: Unit) -> Map: """Convert between plain Units via BFS. diff --git a/ucon/maps.py b/ucon/maps.py index 5f3c451..4965ff6 100644 --- a/ucon/maps.py +++ b/ucon/maps.py @@ -17,12 +17,66 @@ - :class:`LogMap` — y = scale * log_base(x) + offset - :class:`ExpMap` — y = base^(scale * x + offset) - :class:`ComposedMap` — Generic composition fallback: g(f(x)) + +All maps support both scalar and numpy array inputs. NumPy is optional; +scalar operations use the standard library `math` module. """ from __future__ import annotations import math from abc import ABC, abstractmethod from dataclasses import dataclass +from typing import Union, TYPE_CHECKING + +if TYPE_CHECKING: + import numpy as np + from numpy.typing import NDArray + Numeric = Union[float, NDArray[np.floating]] + + +def _is_array(x) -> bool: + """Check if x is a numpy array.""" + try: + import numpy as np + return isinstance(x, np.ndarray) + except ImportError: + return False + + +def _log(x, base: float): + """Logarithm that works with both scalars and numpy arrays.""" + try: + import numpy as np + if isinstance(x, np.ndarray): + return np.log(x) / np.log(base) + except ImportError: + pass + return math.log(x, base) + + +def _exp(base: float, x): + """Exponentiation that works with both scalars and numpy arrays.""" + try: + import numpy as np + if isinstance(x, np.ndarray): + return np.power(base, x) + except ImportError: + pass + return base ** x + + +def _validate_positive(x, name: str = "x") -> None: + """Validate that x is positive (for logarithm arguments).""" + try: + import numpy as np + if isinstance(x, np.ndarray): + if np.any(x <= 0): + raise ValueError(f"Logarithm argument must be positive") + return + except ImportError: + pass + if x <= 0: + raise ValueError(f"Logarithm argument must be positive, got {x}") class Map(ABC): @@ -183,10 +237,10 @@ class LogMap(Map): reference: float = 1.0 offset: float = 0.0 - def __call__(self, x: float) -> float: - if x <= 0: - raise ValueError(f"Logarithm argument must be positive, got {x}") - return self.scale * math.log(x / self.reference, self.base) + self.offset + def __call__(self, x): + """Apply the logarithmic map. Works with scalars and numpy arrays.""" + _validate_positive(x) + return self.scale * _log(x / self.reference, self.base) + self.offset @property def invertible(self) -> bool: @@ -215,10 +269,12 @@ def __pow__(self, exp: float) -> Map: return self.inverse() raise ValueError("LogMap only supports exp=1 or exp=-1") - def derivative(self, x: float) -> float: - """Derivative: d/dx[scale * log_base(x/ref)] = scale / (x * ln(base))""" - if x <= 0: - raise ValueError(f"Derivative undefined for x={x}") + def derivative(self, x): + """Derivative: d/dx[scale * log_base(x/ref)] = scale / (x * ln(base)) + + Works with both scalars and numpy arrays. + """ + _validate_positive(x) return self.scale / (x * math.log(self.base)) def is_identity(self, tol: float = 1e-9) -> bool: @@ -250,8 +306,9 @@ class ExpMap(Map): reference: float = 1.0 offset: float = 0.0 - def __call__(self, x: float) -> float: - return self.reference * self.base ** (self.scale * x + self.offset) + def __call__(self, x): + """Apply the exponential map. Works with scalars and numpy arrays.""" + return self.reference * _exp(self.base, self.scale * x + self.offset) @property def invertible(self) -> bool: @@ -280,8 +337,11 @@ def __pow__(self, exp: float) -> Map: return self.inverse() raise ValueError("ExpMap only supports exp=1 or exp=-1") - def derivative(self, x: float) -> float: - """Derivative: d/dx[ref * base^(scale*x + offset)] = ln(base) * scale * ref * base^(scale*x + offset)""" + def derivative(self, x): + """Derivative: d/dx[ref * base^(scale*x + offset)] = ln(base) * scale * ref * base^(scale*x + offset) + + Works with both scalars and numpy arrays. + """ return math.log(self.base) * self.scale * self(x) def is_identity(self, tol: float = 1e-9) -> bool: diff --git a/ucon/numpy.py b/ucon/numpy.py new file mode 100644 index 0000000..4cc62d2 --- /dev/null +++ b/ucon/numpy.py @@ -0,0 +1,764 @@ +# ucon/numpy.py +# +# NumPy array support for ucon. + +""" +NumPy array support for ucon. + +This module provides NumberArray, a collection type for operating on +multiple quantities of a given unit simultaneously. + +Requires: pip install ucon[numpy] + +Example: + >>> from ucon import units + >>> from ucon.numpy import NumberArray + >>> heights = NumberArray([1.7, 1.8, 1.9], unit=units.meter) + >>> heights.to(units.foot) + <[5.577, 5.905, 6.233] ft> +""" + +from __future__ import annotations + +import math +from dataclasses import dataclass +from typing import Union, TYPE_CHECKING, Iterator, Any + +try: + import numpy as np + HAS_NUMPY = True +except ImportError: + HAS_NUMPY = False + np = None # type: ignore + +if TYPE_CHECKING: + import numpy as np + from numpy.typing import ArrayLike, NDArray + +from ucon.core import Unit, UnitProduct, UnitFactor, Scale, Number, _none + +# Module-level cache for scale factors: (src_unit, dst_unit) -> factor +_scale_factor_cache: dict[tuple, float] = {} + +# Module-level cache for unit multiplication: (unit_a, unit_b) -> result_unit +_unit_mul_cache: dict[tuple, 'UnitProduct'] = {} + +# Module-level cache for unit division: (unit_a, unit_b) -> result_unit +_unit_div_cache: dict[tuple, 'UnitProduct'] = {} + + +def _require_numpy() -> None: + """Raise ImportError if numpy is not available.""" + if not HAS_NUMPY: + raise ImportError( + "NumPy is required for NumberArray. " + "Install with: pip install ucon[numpy]" + ) + + +class NumberArray: + """ + A collection of quantities with a shared unit. + + Combines a numpy array of magnitudes with a unit, enabling vectorized + arithmetic and conversion. + + Parameters + ---------- + quantities : array-like + The numeric values (will be converted to numpy array). + unit : Unit or UnitProduct, optional + The unit for all quantities. Defaults to dimensionless. + uncertainty : float or array-like, optional + Uncertainty value(s). If scalar, applies uniformly to all elements. + If array-like, must match the shape of quantities. + + Examples + -------- + >>> from ucon import units + >>> from ucon.numpy import NumberArray + + Create from list: + + >>> heights = NumberArray([1.7, 1.8, 1.9], unit=units.meter) + >>> len(heights) + 3 + + Vectorized conversion: + + >>> heights_ft = heights.to(units.foot) + >>> heights_ft[0].quantity # doctest: +ELLIPSIS + 5.577... + + With uniform uncertainty: + + >>> temps = NumberArray([20, 21, 22], unit=units.celsius, uncertainty=0.5) + + With per-element uncertainty: + + >>> measurements = NumberArray([1.0, 2.0, 3.0], unit=units.meter, + ... uncertainty=[0.01, 0.02, 0.015]) + """ + + __slots__ = ('_quantities', '_unit', '_uncertainty') + + def __init__( + self, + quantities: 'ArrayLike', + unit: Union[Unit, UnitProduct, None] = None, + uncertainty: Union[float, 'ArrayLike', None] = None, + ): + _require_numpy() + + self._quantities: NDArray[np.floating] = np.asarray(quantities, dtype=float) + self._unit = unit if unit is not None else _none + + if uncertainty is not None: + if isinstance(uncertainty, (int, float)): + self._uncertainty: Union[float, NDArray[np.floating], None] = float(uncertainty) + else: + self._uncertainty = np.asarray(uncertainty, dtype=float) + if self._uncertainty.shape != self._quantities.shape: + raise ValueError( + f"Uncertainty shape {self._uncertainty.shape} does not match " + f"quantities shape {self._quantities.shape}" + ) + else: + self._uncertainty = None + + @property + def quantities(self) -> 'NDArray[np.floating]': + """The array of numeric values.""" + return self._quantities + + @property + def unit(self) -> Union[Unit, UnitProduct]: + """The unit shared by all quantities.""" + return self._unit + + @property + def uncertainty(self) -> Union[float, 'NDArray[np.floating]', None]: + """The uncertainty (scalar or per-element array).""" + return self._uncertainty + + def __len__(self) -> int: + """Return the number of elements.""" + return len(self._quantities) + + @property + def shape(self) -> tuple: + """Shape of the quantities array.""" + return self._quantities.shape + + @property + def ndim(self) -> int: + """Number of dimensions.""" + return self._quantities.ndim + + @property + def dtype(self) -> 'np.dtype': + """Data type of the quantities array.""" + return self._quantities.dtype + + @property + def dimension(self): + """The physical dimension of the quantities.""" + if hasattr(self._unit, 'dimension'): + return self._unit.dimension + return None + + def __getitem__(self, key) -> Union[Number, 'NumberArray']: + """Index or slice the array. + + Returns Number for scalar index, NumberArray for slice. + """ + q = self._quantities[key] + + # Determine uncertainty for the slice + if self._uncertainty is None: + unc = None + elif isinstance(self._uncertainty, float): + unc = self._uncertainty + else: + unc = self._uncertainty[key] + + # Return Number for scalar index, NumberArray for slice + if np.ndim(q) == 0: + unc_val = float(unc) if unc is not None and not isinstance(unc, float) else unc + return Number(quantity=float(q), unit=self._unit, uncertainty=unc_val) + else: + return NumberArray(quantities=q, unit=self._unit, uncertainty=unc) + + def __iter__(self) -> Iterator[Number]: + """Iterate as Number instances.""" + for i in range(len(self)): + yield self[i] # type: ignore + + def __repr__(self) -> str: + """String representation with truncation for large arrays.""" + # Format quantities with truncation for large arrays + if len(self._quantities) <= 6: + q_str = np.array2string( + self._quantities, + separator=', ', + precision=4, + suppress_small=True, + ) + else: + # Show first 3 and last 3 + head = ', '.join(f'{x:.4g}' for x in self._quantities[:3]) + tail = ', '.join(f'{x:.4g}' for x in self._quantities[-3:]) + q_str = f'[{head}, ..., {tail}]' + + # Format unit + unit_str = self._format_unit() + + # Format uncertainty + if self._uncertainty is None: + return f'<{q_str} {unit_str}>' + elif isinstance(self._uncertainty, float): + return f'<{q_str} \u00b1 {self._uncertainty:.4g} {unit_str}>' + else: + # Per-element uncertainty - show shape indicator + return f'<{q_str} \u00b1 [...] {unit_str}>' + + def _format_unit(self) -> str: + """Format the unit for display.""" + if hasattr(self._unit, 'shorthand') and self._unit.shorthand: + return self._unit.shorthand + elif hasattr(self._unit, 'name'): + return self._unit.name + else: + return str(self._unit) + + # ------------------------------------------------------------------------- + # Arithmetic Operations + # ------------------------------------------------------------------------- + + def __mul__(self, other) -> 'NumberArray': + """Multiply by scalar, Number, or NumberArray.""" + if isinstance(other, (int, float)): + new_unc = None + if self._uncertainty is not None: + new_unc = self._uncertainty * abs(other) + return NumberArray( + quantities=self._quantities * other, + unit=self._unit, + uncertainty=new_unc, + ) + + if isinstance(other, Number): + result_q = self._quantities * other.quantity + result_unit = self._unit * other.unit + + new_unc = self._propagate_mul_uncertainty( + self._quantities, self._uncertainty, + other.quantity, other.uncertainty + ) + return NumberArray(quantities=result_q, unit=result_unit, uncertainty=new_unc) + + if isinstance(other, NumberArray): + # Allow numpy broadcasting - shapes must be broadcast-compatible + try: + result_q = self._quantities * other._quantities + except ValueError as e: + raise ValueError( + f"Shapes {self.shape} and {other.shape} are not broadcast-compatible" + ) from e + + # Cache unit multiplication (expensive due to UnitProduct.__init__) + unit_key = (self._unit, other._unit) + if unit_key in _unit_mul_cache: + result_unit = _unit_mul_cache[unit_key] + else: + result_unit = self._unit * other._unit + _unit_mul_cache[unit_key] = result_unit + + new_unc = self._propagate_mul_uncertainty( + self._quantities, self._uncertainty, + other._quantities, other._uncertainty + ) + return NumberArray(quantities=result_q, unit=result_unit, uncertainty=new_unc) + + return NotImplemented + + def __rmul__(self, other) -> 'NumberArray': + """Right multiplication.""" + return self.__mul__(other) + + def __truediv__(self, other) -> 'NumberArray': + """Divide by scalar, Number, or NumberArray.""" + if isinstance(other, (int, float)): + new_unc = None + if self._uncertainty is not None: + new_unc = self._uncertainty / abs(other) + return NumberArray( + quantities=self._quantities / other, + unit=self._unit, + uncertainty=new_unc, + ) + + if isinstance(other, Number): + result_q = self._quantities / other.quantity + result_unit = self._unit / other.unit + + new_unc = self._propagate_div_uncertainty( + self._quantities, self._uncertainty, + other.quantity, other.uncertainty + ) + return NumberArray(quantities=result_q, unit=result_unit, uncertainty=new_unc) + + if isinstance(other, NumberArray): + # Allow numpy broadcasting - shapes must be broadcast-compatible + try: + result_q = self._quantities / other._quantities + except ValueError as e: + raise ValueError( + f"Shapes {self.shape} and {other.shape} are not broadcast-compatible" + ) from e + + # Cache unit division (expensive due to UnitProduct.__init__) + unit_key = (self._unit, other._unit) + if unit_key in _unit_div_cache: + result_unit = _unit_div_cache[unit_key] + else: + result_unit = self._unit / other._unit + _unit_div_cache[unit_key] = result_unit + + new_unc = self._propagate_div_uncertainty( + self._quantities, self._uncertainty, + other._quantities, other._uncertainty + ) + return NumberArray(quantities=result_q, unit=result_unit, uncertainty=new_unc) + + return NotImplemented + + def __rtruediv__(self, other) -> 'NumberArray': + """Right division (other / self).""" + if isinstance(other, (int, float)): + result_q = other / self._quantities + + new_unc = None + if self._uncertainty is not None: + # For c = a/x, δc = |c| * |δx/x| + rel_unc = np.where( + self._quantities != 0, + np.abs(self._uncertainty / self._quantities), + 0 + ) + new_unc = np.abs(result_q) * rel_unc + + # Unit is 1/self.unit + inv_unit = _none / self._unit + return NumberArray(quantities=result_q, unit=inv_unit, uncertainty=new_unc) + + return NotImplemented + + def __add__(self, other) -> 'NumberArray': + """Add NumberArray or Number (same unit required).""" + if isinstance(other, Number): + self._check_same_unit(other.unit) + result_q = self._quantities + other.quantity + new_unc = self._propagate_add_uncertainty( + self._uncertainty, other.uncertainty + ) + return NumberArray(quantities=result_q, unit=self._unit, uncertainty=new_unc) + + if isinstance(other, NumberArray): + self._check_same_unit(other._unit) + # Allow numpy broadcasting - shapes must be broadcast-compatible + try: + result_q = self._quantities + other._quantities + except ValueError as e: + raise ValueError( + f"Shapes {self.shape} and {other.shape} are not broadcast-compatible" + ) from e + + new_unc = self._propagate_add_uncertainty( + self._uncertainty, other._uncertainty + ) + return NumberArray(quantities=result_q, unit=self._unit, uncertainty=new_unc) + + return NotImplemented + + def __radd__(self, other) -> 'NumberArray': + """Right addition.""" + return self.__add__(other) + + def __sub__(self, other) -> 'NumberArray': + """Subtract NumberArray or Number (same unit required).""" + if isinstance(other, Number): + self._check_same_unit(other.unit) + result_q = self._quantities - other.quantity + new_unc = self._propagate_add_uncertainty( + self._uncertainty, other.uncertainty + ) + return NumberArray(quantities=result_q, unit=self._unit, uncertainty=new_unc) + + if isinstance(other, NumberArray): + self._check_same_unit(other._unit) + # Allow numpy broadcasting - shapes must be broadcast-compatible + try: + result_q = self._quantities - other._quantities + except ValueError as e: + raise ValueError( + f"Shapes {self.shape} and {other.shape} are not broadcast-compatible" + ) from e + + new_unc = self._propagate_add_uncertainty( + self._uncertainty, other._uncertainty + ) + return NumberArray(quantities=result_q, unit=self._unit, uncertainty=new_unc) + + return NotImplemented + + def __rsub__(self, other) -> 'NumberArray': + """Right subtraction (other - self).""" + if isinstance(other, Number): + self._check_same_unit(other.unit) + result_q = other.quantity - self._quantities + new_unc = self._propagate_add_uncertainty( + other.uncertainty, self._uncertainty + ) + return NumberArray(quantities=result_q, unit=self._unit, uncertainty=new_unc) + + return NotImplemented + + def __neg__(self) -> 'NumberArray': + """Negation.""" + return NumberArray( + quantities=-self._quantities, + unit=self._unit, + uncertainty=self._uncertainty, + ) + + def __pos__(self) -> 'NumberArray': + """Unary positive (returns copy).""" + return NumberArray( + quantities=self._quantities.copy(), + unit=self._unit, + uncertainty=self._uncertainty if isinstance(self._uncertainty, float) + else self._uncertainty.copy() if self._uncertainty is not None + else None, + ) + + def __abs__(self) -> 'NumberArray': + """Absolute value.""" + return NumberArray( + quantities=np.abs(self._quantities), + unit=self._unit, + uncertainty=self._uncertainty, + ) + + # ------------------------------------------------------------------------- + # Comparison Operators + # ------------------------------------------------------------------------- + + def __eq__(self, other) -> 'NDArray[np.bool_]': + """Element-wise equality comparison. Returns boolean array.""" + if isinstance(other, NumberArray): + self._check_same_unit(other._unit) + return self._quantities == other._quantities + if isinstance(other, Number): + self._check_same_unit(other.unit) + return self._quantities == other.quantity + if isinstance(other, (int, float)): + return self._quantities == other + return NotImplemented + + def __ne__(self, other) -> 'NDArray[np.bool_]': + """Element-wise inequality comparison. Returns boolean array.""" + if isinstance(other, NumberArray): + self._check_same_unit(other._unit) + return self._quantities != other._quantities + if isinstance(other, Number): + self._check_same_unit(other.unit) + return self._quantities != other.quantity + if isinstance(other, (int, float)): + return self._quantities != other + return NotImplemented + + def __lt__(self, other) -> 'NDArray[np.bool_]': + """Element-wise less-than comparison. Returns boolean array.""" + if isinstance(other, NumberArray): + self._check_same_unit(other._unit) + return self._quantities < other._quantities + if isinstance(other, Number): + self._check_same_unit(other.unit) + return self._quantities < other.quantity + if isinstance(other, (int, float)): + return self._quantities < other + return NotImplemented + + def __le__(self, other) -> 'NDArray[np.bool_]': + """Element-wise less-than-or-equal comparison. Returns boolean array.""" + if isinstance(other, NumberArray): + self._check_same_unit(other._unit) + return self._quantities <= other._quantities + if isinstance(other, Number): + self._check_same_unit(other.unit) + return self._quantities <= other.quantity + if isinstance(other, (int, float)): + return self._quantities <= other + return NotImplemented + + def __gt__(self, other) -> 'NDArray[np.bool_]': + """Element-wise greater-than comparison. Returns boolean array.""" + if isinstance(other, NumberArray): + self._check_same_unit(other._unit) + return self._quantities > other._quantities + if isinstance(other, Number): + self._check_same_unit(other.unit) + return self._quantities > other.quantity + if isinstance(other, (int, float)): + return self._quantities > other + return NotImplemented + + def __ge__(self, other) -> 'NDArray[np.bool_]': + """Element-wise greater-than-or-equal comparison. Returns boolean array.""" + if isinstance(other, NumberArray): + self._check_same_unit(other._unit) + return self._quantities >= other._quantities + if isinstance(other, Number): + self._check_same_unit(other.unit) + return self._quantities >= other.quantity + if isinstance(other, (int, float)): + return self._quantities >= other + return NotImplemented + + def _check_same_unit(self, other_unit) -> None: + """Raise ValueError if units don't match for addition/subtraction.""" + if self._unit != other_unit: + raise ValueError( + f"Cannot add/subtract quantities with different units: " + f"{self._format_unit()} vs {other_unit}" + ) + + def _propagate_mul_uncertainty( + self, + a: 'NDArray', + ua: Union[float, 'NDArray', None], + b: Union[float, 'NDArray'], + ub: Union[float, None], + ) -> Union[float, 'NDArray', None]: + """Propagate uncertainty through multiplication.""" + if ua is None and ub is None: + return None + + # Convert to arrays for uniform handling + a_arr = np.asarray(a) + b_arr = np.asarray(b) + + # Relative uncertainties + if ua is not None: + rel_a = np.where(a_arr != 0, np.abs(ua) / np.abs(a_arr), 0.0) + else: + rel_a = np.zeros_like(a_arr) + + if ub is not None: + rel_b = np.where(b_arr != 0, np.abs(ub) / np.abs(b_arr), 0.0) + else: + rel_b = np.zeros_like(b_arr) + + rel_c = np.sqrt(rel_a**2 + rel_b**2) + result = np.abs(a_arr * b_arr) * rel_c + + # Return scalar if result is uniform and both inputs were scalar uncertainty + if isinstance(ua, (float, type(None))) and isinstance(ub, (float, type(None))): + if result.size == 1: + return float(result.flat[0]) + if np.allclose(result, result.flat[0]): + return float(result.flat[0]) + + return result + + def _propagate_div_uncertainty( + self, + a: 'NDArray', + ua: Union[float, 'NDArray', None], + b: Union[float, 'NDArray'], + ub: Union[float, None], + ) -> Union[float, 'NDArray', None]: + """Propagate uncertainty through division (same as multiplication).""" + return self._propagate_mul_uncertainty(a, ua, b, ub) + + def _propagate_add_uncertainty( + self, + ua: Union[float, 'NDArray', None], + ub: Union[float, 'NDArray', None], + ) -> Union[float, 'NDArray', None]: + """Propagate uncertainty through addition/subtraction.""" + if ua is None and ub is None: + return None + + if ua is None: + return ub + if ub is None: + return ua + + # Quadrature addition + result = np.sqrt(np.asarray(ua)**2 + np.asarray(ub)**2) + + # Return scalar if both inputs were scalar + if isinstance(ua, float) and isinstance(ub, float): + return float(result) + + return result + + # ------------------------------------------------------------------------- + # Conversion + # ------------------------------------------------------------------------- + + def to(self, target: Union[Unit, UnitProduct], graph=None) -> 'NumberArray': + """Convert all quantities to a different unit. + + Parameters + ---------- + target : Unit or UnitProduct + The target unit to convert to. + graph : ConversionGraph, optional + The conversion graph to use. Defaults to the global default graph. + + Returns + ------- + NumberArray + A new NumberArray with converted quantities. + + Examples + -------- + >>> from ucon import units + >>> heights = NumberArray([1, 2, 3], unit=units.meter) + >>> heights_ft = heights.to(units.foot) + """ + from ucon.graph import get_default_graph + + # Check scale factor cache first + cache_key = (self._unit, target) + if cache_key in _scale_factor_cache: + factor = _scale_factor_cache[cache_key] + new_unc = None + if self._uncertainty is not None: + new_unc = self._uncertainty * abs(factor) + return NumberArray( + quantities=self._quantities * factor, + unit=target, + uncertainty=new_unc, + ) + + # Normalize to UnitProduct + src = self._unit if isinstance(self._unit, UnitProduct) else UnitProduct.from_unit(self._unit) + dst = target if isinstance(target, UnitProduct) else UnitProduct.from_unit(target) + + # Scale-only conversion (no graph needed) + if self._is_scale_only_conversion(src, dst): + factor = src.fold_scale() / dst.fold_scale() + _scale_factor_cache[cache_key] = factor # Cache it + new_unc = None + if self._uncertainty is not None: + new_unc = self._uncertainty * abs(factor) + return NumberArray( + quantities=self._quantities * factor, + unit=target, + uncertainty=new_unc, + ) + + # Graph-based conversion + if graph is None: + graph = get_default_graph() + + conversion_map = graph.convert(src=src, dst=dst) + + # Apply map to array + converted = conversion_map(self._quantities) + + # Propagate uncertainty through conversion + new_unc = None + if self._uncertainty is not None: + derivative = np.abs(conversion_map.derivative(self._quantities)) + new_unc = derivative * self._uncertainty + + return NumberArray(quantities=converted, unit=target, uncertainty=new_unc) + + def _is_scale_only_conversion(self, src: UnitProduct, dst: UnitProduct) -> bool: + """Check if conversion is just a scale change (same base units).""" + if len(src.factors) != len(dst.factors): + return False + + src_by_dim = {} + dst_by_dim = {} + for f, exp in src.factors.items(): + src_by_dim[f.unit.dimension] = (f.unit, exp) + for f, exp in dst.factors.items(): + dst_by_dim[f.unit.dimension] = (f.unit, exp) + + if src_by_dim.keys() != dst_by_dim.keys(): + return False + + for dim in src_by_dim: + src_unit, src_exp = src_by_dim[dim] + dst_unit, dst_exp = dst_by_dim[dim] + if src_unit != dst_unit or abs(src_exp - dst_exp) > 1e-12: + return False + + return True + + # ------------------------------------------------------------------------- + # NumPy Integration + # ------------------------------------------------------------------------- + + def __array__(self, dtype=None) -> 'NDArray': + """Support np.asarray(number_array).""" + if dtype is None: + return self._quantities + return self._quantities.astype(dtype) + + def sum(self) -> Number: + """Sum all quantities.""" + total = float(np.sum(self._quantities)) + + # Uncertainty propagation for sum + unc = None + if self._uncertainty is not None: + if isinstance(self._uncertainty, float): + # Uniform uncertainty: σ_sum = σ * sqrt(n) + unc = self._uncertainty * math.sqrt(len(self)) + else: + # Per-element: σ_sum = sqrt(Σσᵢ²) + unc = float(np.sqrt(np.sum(self._uncertainty**2))) + + return Number(quantity=total, unit=self._unit, uncertainty=unc) + + def mean(self) -> Number: + """Compute the mean.""" + avg = float(np.mean(self._quantities)) + + # Uncertainty propagation for mean + unc = None + if self._uncertainty is not None: + if isinstance(self._uncertainty, float): + # Uniform uncertainty: σ_mean = σ / sqrt(n) + unc = self._uncertainty / math.sqrt(len(self)) + else: + # Per-element: σ_mean = sqrt(Σσᵢ²) / n + unc = float(np.sqrt(np.sum(self._uncertainty**2)) / len(self)) + + return Number(quantity=avg, unit=self._unit, uncertainty=unc) + + def std(self, ddof: int = 0) -> Number: + """Compute the standard deviation.""" + s = float(np.std(self._quantities, ddof=ddof)) + return Number(quantity=s, unit=self._unit, uncertainty=None) + + def min(self) -> Number: + """Return the minimum value.""" + idx = np.argmin(self._quantities) + return self[idx] # type: ignore + + def max(self) -> Number: + """Return the maximum value.""" + idx = np.argmax(self._quantities) + return self[idx] # type: ignore + + +# Export check +__all__ = ['NumberArray', 'HAS_NUMPY'] diff --git a/ucon/pandas.py b/ucon/pandas.py new file mode 100644 index 0000000..cbccbba --- /dev/null +++ b/ucon/pandas.py @@ -0,0 +1,711 @@ +# ucon/pandas.py +# +# Pandas integration for ucon. + +""" +Pandas integration for ucon. + +This module provides NumberSeries and a pandas accessor for working with +unit-aware Series data. + +Requires: pip install ucon[pandas] + +Example: + >>> import pandas as pd + >>> from ucon import units + >>> from ucon.pandas import NumberSeries + >>> + >>> heights = NumberSeries(pd.Series([1.7, 1.8, 1.9]), unit=units.meter) + >>> heights.to(units.foot) + + >>> + >>> # Or using the accessor: + >>> df = pd.DataFrame({'height': [1.7, 1.8, 1.9]}) + >>> df['height'].ucon.with_unit(units.meter).to(units.foot) +""" + +from __future__ import annotations + +import math +from typing import Union, TYPE_CHECKING, Iterator, Optional + +try: + import pandas as pd + HAS_PANDAS = True +except ImportError: + HAS_PANDAS = False + pd = None # type: ignore + +if TYPE_CHECKING: + import pandas as pd + +from ucon.core import Unit, UnitProduct, UnitFactor, Scale, Number, _none + + +def _require_pandas() -> None: + """Raise ImportError if pandas is not available.""" + if not HAS_PANDAS: + raise ImportError( + "Pandas is required for NumberSeries. " + "Install with: pip install ucon[pandas]" + ) + + +class NumberSeries: + """ + A pandas Series with an associated unit. + + Combines a pandas Series of magnitudes with a unit, enabling vectorized + arithmetic and conversion while preserving pandas functionality. + + Parameters + ---------- + series : pd.Series + The numeric values. + unit : Unit or UnitProduct, optional + The unit for all values. Defaults to dimensionless. + uncertainty : float or pd.Series, optional + Uncertainty value(s). If scalar, applies uniformly to all elements. + If Series, must have the same index. + + Examples + -------- + >>> import pandas as pd + >>> from ucon import units + >>> from ucon.pandas import NumberSeries + + Create from Series: + + >>> heights = NumberSeries(pd.Series([1.7, 1.8, 1.9]), unit=units.meter) + >>> len(heights) + 3 + + Vectorized conversion: + + >>> heights_ft = heights.to(units.foot) + + With uncertainty: + + >>> temps = NumberSeries(pd.Series([20, 21, 22]), unit=units.celsius, uncertainty=0.5) + """ + + __slots__ = ('_series', '_unit', '_uncertainty') + + def __init__( + self, + series: 'pd.Series', + unit: Union[Unit, UnitProduct, None] = None, + uncertainty: Union[float, 'pd.Series', None] = None, + ): + _require_pandas() + + if not isinstance(series, pd.Series): + series = pd.Series(series) + + self._series: pd.Series = series.astype(float) + self._unit = unit if unit is not None else _none + + if uncertainty is not None: + if isinstance(uncertainty, (int, float)): + self._uncertainty: Union[float, pd.Series, None] = float(uncertainty) + else: + self._uncertainty = pd.Series(uncertainty, dtype=float) + if len(self._uncertainty) != len(self._series): + raise ValueError( + f"Uncertainty length {len(self._uncertainty)} does not match " + f"series length {len(self._series)}" + ) + else: + self._uncertainty = None + + @property + def series(self) -> 'pd.Series': + """The underlying pandas Series.""" + return self._series + + @property + def values(self) -> 'pd.Series': + """Alias for series (for compatibility).""" + return self._series + + @property + def unit(self) -> Union[Unit, UnitProduct]: + """The unit shared by all values.""" + return self._unit + + @property + def uncertainty(self) -> Union[float, 'pd.Series', None]: + """The uncertainty (scalar or per-element Series).""" + return self._uncertainty + + @property + def index(self) -> 'pd.Index': + """The pandas index.""" + return self._series.index + + def __len__(self) -> int: + """Return the number of elements.""" + return len(self._series) + + @property + def shape(self) -> tuple: + """Shape of the series.""" + return self._series.shape + + @property + def dtype(self) -> 'pd.api.types.CategoricalDtype': + """Data type of the series.""" + return self._series.dtype + + @property + def dimension(self): + """The physical dimension of the quantities.""" + if hasattr(self._unit, 'dimension'): + return self._unit.dimension + return None + + def __getitem__(self, key) -> Union[Number, 'NumberSeries']: + """Index or slice the series.""" + val = self._series[key] + + # Determine uncertainty for the selection + if self._uncertainty is None: + unc = None + elif isinstance(self._uncertainty, float): + unc = self._uncertainty + else: + unc = self._uncertainty[key] + + # Return Number for scalar, NumberSeries for slice + if isinstance(val, (int, float)): + unc_val = float(unc) if unc is not None and not isinstance(unc, float) else unc + return Number(quantity=float(val), unit=self._unit, uncertainty=unc_val) + else: + return NumberSeries(series=val, unit=self._unit, uncertainty=unc) + + def __iter__(self) -> Iterator[Number]: + """Iterate as Number instances.""" + for idx in self._series.index: + yield self[idx] # type: ignore + + def __repr__(self) -> str: + """String representation.""" + # Format values with truncation for large series + if len(self._series) <= 6: + vals_str = ', '.join(f'{x:.4g}' for x in self._series) + else: + head = ', '.join(f'{x:.4g}' for x in self._series.iloc[:3]) + tail = ', '.join(f'{x:.4g}' for x in self._series.iloc[-3:]) + vals_str = f'{head}, ..., {tail}' + + unit_str = self._format_unit() + + if self._uncertainty is None: + return f'' + elif isinstance(self._uncertainty, float): + return f'' + else: + return f'' + + def _format_unit(self) -> str: + """Format the unit for display.""" + if hasattr(self._unit, 'shorthand') and self._unit.shorthand: + return self._unit.shorthand + elif hasattr(self._unit, 'name'): + return self._unit.name + else: + return str(self._unit) + + # ------------------------------------------------------------------------- + # Arithmetic Operations + # ------------------------------------------------------------------------- + + def __mul__(self, other) -> 'NumberSeries': + """Multiply by scalar, Number, or NumberSeries.""" + if isinstance(other, (int, float)): + new_unc = None + if self._uncertainty is not None: + new_unc = self._uncertainty * abs(other) + return NumberSeries( + series=self._series * other, + unit=self._unit, + uncertainty=new_unc, + ) + + if isinstance(other, Number): + result = self._series * other.quantity + result_unit = self._unit * other.unit + + new_unc = self._propagate_mul_uncertainty( + self._series, self._uncertainty, + other.quantity, other.uncertainty + ) + return NumberSeries(series=result, unit=result_unit, uncertainty=new_unc) + + if isinstance(other, NumberSeries): + if len(self) != len(other): + raise ValueError(f"Length mismatch: {len(self)} vs {len(other)}") + + result = self._series * other._series + result_unit = self._unit * other._unit + new_unc = self._propagate_mul_uncertainty( + self._series, self._uncertainty, + other._series, other._uncertainty + ) + return NumberSeries(series=result, unit=result_unit, uncertainty=new_unc) + + return NotImplemented + + def __rmul__(self, other) -> 'NumberSeries': + """Right multiplication.""" + return self.__mul__(other) + + def __truediv__(self, other) -> 'NumberSeries': + """Divide by scalar, Number, or NumberSeries.""" + if isinstance(other, (int, float)): + new_unc = None + if self._uncertainty is not None: + new_unc = self._uncertainty / abs(other) + return NumberSeries( + series=self._series / other, + unit=self._unit, + uncertainty=new_unc, + ) + + if isinstance(other, Number): + result = self._series / other.quantity + result_unit = self._unit / other.unit + + new_unc = self._propagate_div_uncertainty( + self._series, self._uncertainty, + other.quantity, other.uncertainty + ) + return NumberSeries(series=result, unit=result_unit, uncertainty=new_unc) + + if isinstance(other, NumberSeries): + if len(self) != len(other): + raise ValueError(f"Length mismatch: {len(self)} vs {len(other)}") + + result = self._series / other._series + result_unit = self._unit / other._unit + new_unc = self._propagate_div_uncertainty( + self._series, self._uncertainty, + other._series, other._uncertainty + ) + return NumberSeries(series=result, unit=result_unit, uncertainty=new_unc) + + return NotImplemented + + def __add__(self, other) -> 'NumberSeries': + """Add NumberSeries or Number (same unit required).""" + if isinstance(other, Number): + self._check_same_unit(other.unit) + result = self._series + other.quantity + new_unc = self._propagate_add_uncertainty( + self._uncertainty, other.uncertainty + ) + return NumberSeries(series=result, unit=self._unit, uncertainty=new_unc) + + if isinstance(other, NumberSeries): + self._check_same_unit(other._unit) + if len(self) != len(other): + raise ValueError(f"Length mismatch: {len(self)} vs {len(other)}") + + result = self._series + other._series + new_unc = self._propagate_add_uncertainty( + self._uncertainty, other._uncertainty + ) + return NumberSeries(series=result, unit=self._unit, uncertainty=new_unc) + + return NotImplemented + + def __radd__(self, other) -> 'NumberSeries': + """Right addition.""" + return self.__add__(other) + + def __sub__(self, other) -> 'NumberSeries': + """Subtract NumberSeries or Number (same unit required).""" + if isinstance(other, Number): + self._check_same_unit(other.unit) + result = self._series - other.quantity + new_unc = self._propagate_add_uncertainty( + self._uncertainty, other.uncertainty + ) + return NumberSeries(series=result, unit=self._unit, uncertainty=new_unc) + + if isinstance(other, NumberSeries): + self._check_same_unit(other._unit) + if len(self) != len(other): + raise ValueError(f"Length mismatch: {len(self)} vs {len(other)}") + + result = self._series - other._series + new_unc = self._propagate_add_uncertainty( + self._uncertainty, other._uncertainty + ) + return NumberSeries(series=result, unit=self._unit, uncertainty=new_unc) + + return NotImplemented + + def __neg__(self) -> 'NumberSeries': + """Negation.""" + return NumberSeries( + series=-self._series, + unit=self._unit, + uncertainty=self._uncertainty, + ) + + def __abs__(self) -> 'NumberSeries': + """Absolute value.""" + return NumberSeries( + series=self._series.abs(), + unit=self._unit, + uncertainty=self._uncertainty, + ) + + # ------------------------------------------------------------------------- + # Comparison Operators + # ------------------------------------------------------------------------- + + def __eq__(self, other) -> 'pd.Series': + """Element-wise equality comparison. Returns boolean Series.""" + if isinstance(other, NumberSeries): + self._check_same_unit(other._unit) + return self._series == other._series + if isinstance(other, Number): + self._check_same_unit(other.unit) + return self._series == other.quantity + if isinstance(other, (int, float)): + return self._series == other + return NotImplemented + + def __ne__(self, other) -> 'pd.Series': + """Element-wise inequality comparison. Returns boolean Series.""" + if isinstance(other, NumberSeries): + self._check_same_unit(other._unit) + return self._series != other._series + if isinstance(other, Number): + self._check_same_unit(other.unit) + return self._series != other.quantity + if isinstance(other, (int, float)): + return self._series != other + return NotImplemented + + def __lt__(self, other) -> 'pd.Series': + """Element-wise less-than comparison. Returns boolean Series.""" + if isinstance(other, NumberSeries): + self._check_same_unit(other._unit) + return self._series < other._series + if isinstance(other, Number): + self._check_same_unit(other.unit) + return self._series < other.quantity + if isinstance(other, (int, float)): + return self._series < other + return NotImplemented + + def __le__(self, other) -> 'pd.Series': + """Element-wise less-than-or-equal comparison. Returns boolean Series.""" + if isinstance(other, NumberSeries): + self._check_same_unit(other._unit) + return self._series <= other._series + if isinstance(other, Number): + self._check_same_unit(other.unit) + return self._series <= other.quantity + if isinstance(other, (int, float)): + return self._series <= other + return NotImplemented + + def __gt__(self, other) -> 'pd.Series': + """Element-wise greater-than comparison. Returns boolean Series.""" + if isinstance(other, NumberSeries): + self._check_same_unit(other._unit) + return self._series > other._series + if isinstance(other, Number): + self._check_same_unit(other.unit) + return self._series > other.quantity + if isinstance(other, (int, float)): + return self._series > other + return NotImplemented + + def __ge__(self, other) -> 'pd.Series': + """Element-wise greater-than-or-equal comparison. Returns boolean Series.""" + if isinstance(other, NumberSeries): + self._check_same_unit(other._unit) + return self._series >= other._series + if isinstance(other, Number): + self._check_same_unit(other.unit) + return self._series >= other.quantity + if isinstance(other, (int, float)): + return self._series >= other + return NotImplemented + + def _check_same_unit(self, other_unit) -> None: + """Raise ValueError if units don't match for addition/subtraction.""" + if self._unit != other_unit: + raise ValueError( + f"Cannot add/subtract quantities with different units: " + f"{self._format_unit()} vs {other_unit}" + ) + + def _propagate_mul_uncertainty( + self, + a: 'pd.Series', + ua: Union[float, 'pd.Series', None], + b: Union[float, 'pd.Series'], + ub: Union[float, None], + ) -> Union[float, 'pd.Series', None]: + """Propagate uncertainty through multiplication.""" + if ua is None and ub is None: + return None + + # Relative uncertainties + if ua is not None: + rel_a = (abs(ua) / abs(a)).fillna(0).replace([float('inf')], 0) + else: + rel_a = 0 + + if ub is not None: + if isinstance(b, pd.Series): + rel_b = (abs(ub) / abs(b)).fillna(0).replace([float('inf')], 0) + else: + rel_b = abs(ub) / abs(b) if b != 0 else 0 + else: + rel_b = 0 + + import numpy as np + rel_c = np.sqrt(rel_a**2 + rel_b**2) + result = abs(a * b) * rel_c + + # Return scalar if result is uniform + if isinstance(result, pd.Series) and result.nunique() == 1: + return float(result.iloc[0]) + + return result + + def _propagate_div_uncertainty( + self, + a: 'pd.Series', + ua: Union[float, 'pd.Series', None], + b: Union[float, 'pd.Series'], + ub: Union[float, None], + ) -> Union[float, 'pd.Series', None]: + """Propagate uncertainty through division (same as multiplication).""" + return self._propagate_mul_uncertainty(a, ua, b, ub) + + def _propagate_add_uncertainty( + self, + ua: Union[float, 'pd.Series', None], + ub: Union[float, 'pd.Series', None], + ) -> Union[float, 'pd.Series', None]: + """Propagate uncertainty through addition/subtraction.""" + if ua is None and ub is None: + return None + + if ua is None: + return ub + if ub is None: + return ua + + import numpy as np + result = np.sqrt(ua**2 + ub**2) + + # Return scalar if both inputs were scalar + if isinstance(ua, float) and isinstance(ub, float): + return float(result) + + return result + + # ------------------------------------------------------------------------- + # Conversion + # ------------------------------------------------------------------------- + + def to(self, target: Union[Unit, UnitProduct], graph=None) -> 'NumberSeries': + """Convert all values to a different unit. + + Parameters + ---------- + target : Unit or UnitProduct + The target unit to convert to. + graph : ConversionGraph, optional + The conversion graph to use. Defaults to the global default graph. + + Returns + ------- + NumberSeries + A new NumberSeries with converted values. + """ + from ucon.graph import get_default_graph + + # Normalize to UnitProduct + src = self._unit if isinstance(self._unit, UnitProduct) else UnitProduct.from_unit(self._unit) + dst = target if isinstance(target, UnitProduct) else UnitProduct.from_unit(target) + + # Scale-only conversion (no graph needed) + if self._is_scale_only_conversion(src, dst): + factor = src.fold_scale() / dst.fold_scale() + new_unc = None + if self._uncertainty is not None: + new_unc = self._uncertainty * abs(factor) + return NumberSeries( + series=self._series * factor, + unit=target, + uncertainty=new_unc, + ) + + # Graph-based conversion + if graph is None: + graph = get_default_graph() + + conversion_map = graph.convert(src=src, dst=dst) + + # Apply map to series values + converted = self._series.apply(conversion_map) + + # Propagate uncertainty through conversion + new_unc = None + if self._uncertainty is not None: + derivative = self._series.apply(lambda x: abs(conversion_map.derivative(x))) + new_unc = derivative * self._uncertainty + + return NumberSeries(series=converted, unit=target, uncertainty=new_unc) + + def _is_scale_only_conversion(self, src: UnitProduct, dst: UnitProduct) -> bool: + """Check if conversion is just a scale change (same base units).""" + if len(src.factors) != len(dst.factors): + return False + + src_by_dim = {} + dst_by_dim = {} + for f, exp in src.factors.items(): + src_by_dim[f.unit.dimension] = (f.unit, exp) + for f, exp in dst.factors.items(): + dst_by_dim[f.unit.dimension] = (f.unit, exp) + + if src_by_dim.keys() != dst_by_dim.keys(): + return False + + for dim in src_by_dim: + src_unit, src_exp = src_by_dim[dim] + dst_unit, dst_exp = dst_by_dim[dim] + if src_unit != dst_unit or abs(src_exp - dst_exp) > 1e-12: + return False + + return True + + # ------------------------------------------------------------------------- + # Pandas Integration + # ------------------------------------------------------------------------- + + def to_frame(self, name: Optional[str] = None) -> 'pd.DataFrame': + """Convert to DataFrame with unit in column name.""" + col_name = name or f'value ({self._format_unit()})' + return pd.DataFrame({col_name: self._series}) + + def sum(self) -> Number: + """Sum all values.""" + total = float(self._series.sum()) + + unc = None + if self._uncertainty is not None: + if isinstance(self._uncertainty, float): + unc = self._uncertainty * math.sqrt(len(self)) + else: + unc = float(math.sqrt((self._uncertainty**2).sum())) + + return Number(quantity=total, unit=self._unit, uncertainty=unc) + + def mean(self) -> Number: + """Compute the mean.""" + avg = float(self._series.mean()) + + unc = None + if self._uncertainty is not None: + if isinstance(self._uncertainty, float): + unc = self._uncertainty / math.sqrt(len(self)) + else: + unc = float(math.sqrt((self._uncertainty**2).sum()) / len(self)) + + return Number(quantity=avg, unit=self._unit, uncertainty=unc) + + def std(self, ddof: int = 1) -> Number: + """Compute the standard deviation.""" + s = float(self._series.std(ddof=ddof)) + return Number(quantity=s, unit=self._unit, uncertainty=None) + + def min(self) -> Number: + """Return the minimum value.""" + idx = self._series.idxmin() + return self[idx] # type: ignore + + def max(self) -> Number: + """Return the maximum value.""" + idx = self._series.idxmax() + return self[idx] # type: ignore + + +# ----------------------------------------------------------------------------- +# Pandas Accessor +# ----------------------------------------------------------------------------- + +if HAS_PANDAS: + @pd.api.extensions.register_series_accessor("ucon") + class UconSeriesAccessor: + """ + Pandas Series accessor for ucon unit operations. + + Enables syntax like: + df['height'].ucon.with_unit(units.meter).to(units.foot) + + Examples + -------- + >>> import pandas as pd + >>> from ucon import units + >>> + >>> df = pd.DataFrame({'height_m': [1.7, 1.8, 1.9]}) + >>> heights = df['height_m'].ucon.with_unit(units.meter) + >>> heights.to(units.foot) + """ + + def __init__(self, series: 'pd.Series'): + self._series = series + self._unit: Union[Unit, UnitProduct, None] = None + self._uncertainty: Union[float, 'pd.Series', None] = None + + def with_unit( + self, + unit: Union[Unit, UnitProduct], + uncertainty: Union[float, 'pd.Series', None] = None + ) -> NumberSeries: + """ + Associate a unit with this Series. + + Parameters + ---------- + unit : Unit or UnitProduct + The unit for the values. + uncertainty : float or pd.Series, optional + The measurement uncertainty. + + Returns + ------- + NumberSeries + A NumberSeries wrapping the data with unit metadata. + """ + return NumberSeries( + series=self._series, + unit=unit, + uncertainty=uncertainty, + ) + + def __call__( + self, + unit: Union[Unit, UnitProduct], + uncertainty: Union[float, 'pd.Series', None] = None + ) -> NumberSeries: + """Shorthand for with_unit().""" + return self.with_unit(unit, uncertainty) + + +# Export check +__all__ = ['NumberSeries', 'HAS_PANDAS'] +if HAS_PANDAS: + __all__.append('UconSeriesAccessor') diff --git a/ucon/polars.py b/ucon/polars.py new file mode 100644 index 0000000..462bb90 --- /dev/null +++ b/ucon/polars.py @@ -0,0 +1,657 @@ +# ucon/polars.py +# +# Polars integration for ucon. + +""" +Polars integration for ucon. + +This module provides NumberColumn for working with unit-aware Polars Series. + +Requires: pip install ucon[polars] + +Example: + >>> import polars as pl + >>> from ucon import units + >>> from ucon.polars import NumberColumn + >>> + >>> heights = NumberColumn(pl.Series([1.7, 1.8, 1.9]), unit=units.meter) + >>> heights.to(units.foot) + +""" + +from __future__ import annotations + +import math +from typing import Union, TYPE_CHECKING, Iterator, Optional + +try: + import polars as pl + HAS_POLARS = True +except ImportError: + HAS_POLARS = False + pl = None # type: ignore + +if TYPE_CHECKING: + import polars as pl + +from ucon.core import Unit, UnitProduct, UnitFactor, Scale, Number, _none + + +def _require_polars() -> None: + """Raise ImportError if polars is not available.""" + if not HAS_POLARS: + raise ImportError( + "Polars is required for NumberColumn. " + "Install with: pip install ucon[polars]" + ) + + +class NumberColumn: + """ + A Polars Series with an associated unit. + + Combines a Polars Series of magnitudes with a unit, enabling vectorized + arithmetic and conversion. + + Parameters + ---------- + series : pl.Series + The numeric values. + unit : Unit or UnitProduct, optional + The unit for all values. Defaults to dimensionless. + uncertainty : float or pl.Series, optional + Uncertainty value(s). If scalar, applies uniformly to all elements. + If Series, must have the same length. + + Examples + -------- + >>> import polars as pl + >>> from ucon import units + >>> from ucon.polars import NumberColumn + + Create from Series: + + >>> heights = NumberColumn(pl.Series([1.7, 1.8, 1.9]), unit=units.meter) + >>> len(heights) + 3 + + Vectorized conversion: + + >>> heights_ft = heights.to(units.foot) + """ + + __slots__ = ('_series', '_unit', '_uncertainty') + + def __init__( + self, + series: 'pl.Series', + unit: Union[Unit, UnitProduct, None] = None, + uncertainty: Union[float, 'pl.Series', None] = None, + ): + _require_polars() + + if not isinstance(series, pl.Series): + series = pl.Series(series) + + self._series: pl.Series = series.cast(pl.Float64) + self._unit = unit if unit is not None else _none + + if uncertainty is not None: + if isinstance(uncertainty, (int, float)): + self._uncertainty: Union[float, pl.Series, None] = float(uncertainty) + else: + self._uncertainty = uncertainty.cast(pl.Float64) + if len(self._uncertainty) != len(self._series): + raise ValueError( + f"Uncertainty length {len(self._uncertainty)} does not match " + f"series length {len(self._series)}" + ) + else: + self._uncertainty = None + + @property + def series(self) -> 'pl.Series': + """The underlying Polars Series.""" + return self._series + + @property + def unit(self) -> Union[Unit, UnitProduct]: + """The unit shared by all values.""" + return self._unit + + @property + def uncertainty(self) -> Union[float, 'pl.Series', None]: + """The uncertainty (scalar or per-element Series).""" + return self._uncertainty + + def __len__(self) -> int: + """Return the number of elements.""" + return len(self._series) + + @property + def shape(self) -> tuple: + """Shape of the series.""" + return self._series.shape + + @property + def dtype(self): + """Data type of the series.""" + return self._series.dtype + + @property + def dimension(self): + """The physical dimension of the quantities.""" + if hasattr(self._unit, 'dimension'): + return self._unit.dimension + return None + + def __getitem__(self, key) -> Union[Number, 'NumberColumn']: + """Index or slice the series.""" + if isinstance(key, int): + val = self._series[key] + + if self._uncertainty is None: + unc = None + elif isinstance(self._uncertainty, float): + unc = self._uncertainty + else: + unc = float(self._uncertainty[key]) + + return Number(quantity=float(val), unit=self._unit, uncertainty=unc) + else: + # Slice + sliced = self._series[key] + if self._uncertainty is None: + unc = None + elif isinstance(self._uncertainty, float): + unc = self._uncertainty + else: + unc = self._uncertainty[key] + return NumberColumn(series=sliced, unit=self._unit, uncertainty=unc) + + def __iter__(self) -> Iterator[Number]: + """Iterate as Number instances.""" + for i in range(len(self)): + yield self[i] # type: ignore + + def __repr__(self) -> str: + """String representation.""" + vals = self._series.to_list() + if len(vals) <= 6: + vals_str = ', '.join(f'{x:.4g}' for x in vals) + else: + head = ', '.join(f'{x:.4g}' for x in vals[:3]) + tail = ', '.join(f'{x:.4g}' for x in vals[-3:]) + vals_str = f'{head}, ..., {tail}' + + unit_str = self._format_unit() + + if self._uncertainty is None: + return f'' + elif isinstance(self._uncertainty, float): + return f'' + else: + return f'' + + def _format_unit(self) -> str: + """Format the unit for display.""" + if hasattr(self._unit, 'shorthand') and self._unit.shorthand: + return self._unit.shorthand + elif hasattr(self._unit, 'name'): + return self._unit.name + else: + return str(self._unit) + + # ------------------------------------------------------------------------- + # Arithmetic Operations + # ------------------------------------------------------------------------- + + def __mul__(self, other) -> 'NumberColumn': + """Multiply by scalar, Number, or NumberColumn.""" + if isinstance(other, (int, float)): + new_unc = None + if self._uncertainty is not None: + if isinstance(self._uncertainty, float): + new_unc = self._uncertainty * abs(other) + else: + new_unc = self._uncertainty * abs(other) + return NumberColumn( + series=self._series * other, + unit=self._unit, + uncertainty=new_unc, + ) + + if isinstance(other, Number): + result = self._series * other.quantity + result_unit = self._unit * other.unit + + new_unc = self._propagate_mul_uncertainty( + self._series, self._uncertainty, + other.quantity, other.uncertainty + ) + return NumberColumn(series=result, unit=result_unit, uncertainty=new_unc) + + if isinstance(other, NumberColumn): + if len(self) != len(other): + raise ValueError(f"Length mismatch: {len(self)} vs {len(other)}") + + result = self._series * other._series + result_unit = self._unit * other._unit + new_unc = self._propagate_mul_uncertainty( + self._series, self._uncertainty, + other._series, other._uncertainty + ) + return NumberColumn(series=result, unit=result_unit, uncertainty=new_unc) + + return NotImplemented + + def __rmul__(self, other) -> 'NumberColumn': + """Right multiplication.""" + return self.__mul__(other) + + def __truediv__(self, other) -> 'NumberColumn': + """Divide by scalar, Number, or NumberColumn.""" + if isinstance(other, (int, float)): + new_unc = None + if self._uncertainty is not None: + if isinstance(self._uncertainty, float): + new_unc = self._uncertainty / abs(other) + else: + new_unc = self._uncertainty / abs(other) + return NumberColumn( + series=self._series / other, + unit=self._unit, + uncertainty=new_unc, + ) + + if isinstance(other, Number): + result = self._series / other.quantity + result_unit = self._unit / other.unit + + new_unc = self._propagate_div_uncertainty( + self._series, self._uncertainty, + other.quantity, other.uncertainty + ) + return NumberColumn(series=result, unit=result_unit, uncertainty=new_unc) + + if isinstance(other, NumberColumn): + if len(self) != len(other): + raise ValueError(f"Length mismatch: {len(self)} vs {len(other)}") + + result = self._series / other._series + result_unit = self._unit / other._unit + new_unc = self._propagate_div_uncertainty( + self._series, self._uncertainty, + other._series, other._uncertainty + ) + return NumberColumn(series=result, unit=result_unit, uncertainty=new_unc) + + return NotImplemented + + def __add__(self, other) -> 'NumberColumn': + """Add NumberColumn or Number (same unit required).""" + if isinstance(other, Number): + self._check_same_unit(other.unit) + result = self._series + other.quantity + new_unc = self._propagate_add_uncertainty( + self._uncertainty, other.uncertainty + ) + return NumberColumn(series=result, unit=self._unit, uncertainty=new_unc) + + if isinstance(other, NumberColumn): + self._check_same_unit(other._unit) + if len(self) != len(other): + raise ValueError(f"Length mismatch: {len(self)} vs {len(other)}") + + result = self._series + other._series + new_unc = self._propagate_add_uncertainty( + self._uncertainty, other._uncertainty + ) + return NumberColumn(series=result, unit=self._unit, uncertainty=new_unc) + + return NotImplemented + + def __radd__(self, other) -> 'NumberColumn': + """Right addition.""" + return self.__add__(other) + + def __sub__(self, other) -> 'NumberColumn': + """Subtract NumberColumn or Number (same unit required).""" + if isinstance(other, Number): + self._check_same_unit(other.unit) + result = self._series - other.quantity + new_unc = self._propagate_add_uncertainty( + self._uncertainty, other.uncertainty + ) + return NumberColumn(series=result, unit=self._unit, uncertainty=new_unc) + + if isinstance(other, NumberColumn): + self._check_same_unit(other._unit) + if len(self) != len(other): + raise ValueError(f"Length mismatch: {len(self)} vs {len(other)}") + + result = self._series - other._series + new_unc = self._propagate_add_uncertainty( + self._uncertainty, other._uncertainty + ) + return NumberColumn(series=result, unit=self._unit, uncertainty=new_unc) + + return NotImplemented + + def __neg__(self) -> 'NumberColumn': + """Negation.""" + return NumberColumn( + series=-self._series, + unit=self._unit, + uncertainty=self._uncertainty, + ) + + def __abs__(self) -> 'NumberColumn': + """Absolute value.""" + return NumberColumn( + series=self._series.abs(), + unit=self._unit, + uncertainty=self._uncertainty, + ) + + # ------------------------------------------------------------------------- + # Comparison Operators + # ------------------------------------------------------------------------- + + def __eq__(self, other) -> 'pl.Series': + """Element-wise equality comparison. Returns boolean Series.""" + if isinstance(other, NumberColumn): + self._check_same_unit(other._unit) + return self._series == other._series + if isinstance(other, Number): + self._check_same_unit(other.unit) + return self._series == other.quantity + if isinstance(other, (int, float)): + return self._series == other + return NotImplemented + + def __ne__(self, other) -> 'pl.Series': + """Element-wise inequality comparison. Returns boolean Series.""" + if isinstance(other, NumberColumn): + self._check_same_unit(other._unit) + return self._series != other._series + if isinstance(other, Number): + self._check_same_unit(other.unit) + return self._series != other.quantity + if isinstance(other, (int, float)): + return self._series != other + return NotImplemented + + def __lt__(self, other) -> 'pl.Series': + """Element-wise less-than comparison. Returns boolean Series.""" + if isinstance(other, NumberColumn): + self._check_same_unit(other._unit) + return self._series < other._series + if isinstance(other, Number): + self._check_same_unit(other.unit) + return self._series < other.quantity + if isinstance(other, (int, float)): + return self._series < other + return NotImplemented + + def __le__(self, other) -> 'pl.Series': + """Element-wise less-than-or-equal comparison. Returns boolean Series.""" + if isinstance(other, NumberColumn): + self._check_same_unit(other._unit) + return self._series <= other._series + if isinstance(other, Number): + self._check_same_unit(other.unit) + return self._series <= other.quantity + if isinstance(other, (int, float)): + return self._series <= other + return NotImplemented + + def __gt__(self, other) -> 'pl.Series': + """Element-wise greater-than comparison. Returns boolean Series.""" + if isinstance(other, NumberColumn): + self._check_same_unit(other._unit) + return self._series > other._series + if isinstance(other, Number): + self._check_same_unit(other.unit) + return self._series > other.quantity + if isinstance(other, (int, float)): + return self._series > other + return NotImplemented + + def __ge__(self, other) -> 'pl.Series': + """Element-wise greater-than-or-equal comparison. Returns boolean Series.""" + if isinstance(other, NumberColumn): + self._check_same_unit(other._unit) + return self._series >= other._series + if isinstance(other, Number): + self._check_same_unit(other.unit) + return self._series >= other.quantity + if isinstance(other, (int, float)): + return self._series >= other + return NotImplemented + + def _check_same_unit(self, other_unit) -> None: + """Raise ValueError if units don't match for addition/subtraction.""" + if self._unit != other_unit: + raise ValueError( + f"Cannot add/subtract quantities with different units: " + f"{self._format_unit()} vs {other_unit}" + ) + + def _propagate_mul_uncertainty( + self, + a: 'pl.Series', + ua: Union[float, 'pl.Series', None], + b: Union[float, 'pl.Series'], + ub: Union[float, None], + ) -> Union[float, 'pl.Series', None]: + """Propagate uncertainty through multiplication.""" + if ua is None and ub is None: + return None + + # Relative uncertainties + if ua is not None: + if isinstance(ua, float): + rel_a = ua / a.abs() + else: + rel_a = ua.abs() / a.abs() + rel_a = rel_a.fill_nan(0).fill_null(0) + else: + rel_a = pl.Series([0.0] * len(a)) + + if ub is not None: + if isinstance(b, pl.Series): + rel_b = abs(ub) / b.abs() + rel_b = rel_b.fill_nan(0).fill_null(0) + else: + rel_b = abs(ub) / abs(b) if b != 0 else 0 + else: + if isinstance(b, pl.Series): + rel_b = pl.Series([0.0] * len(b)) + else: + rel_b = 0 + + rel_c = (rel_a**2 + rel_b**2).map_elements(math.sqrt, return_dtype=pl.Float64) + result = (a * b).abs() * rel_c + + return result + + def _propagate_div_uncertainty( + self, + a: 'pl.Series', + ua: Union[float, 'pl.Series', None], + b: Union[float, 'pl.Series'], + ub: Union[float, None], + ) -> Union[float, 'pl.Series', None]: + """Propagate uncertainty through division (same as multiplication).""" + return self._propagate_mul_uncertainty(a, ua, b, ub) + + def _propagate_add_uncertainty( + self, + ua: Union[float, 'pl.Series', None], + ub: Union[float, 'pl.Series', None], + ) -> Union[float, 'pl.Series', None]: + """Propagate uncertainty through addition/subtraction.""" + if ua is None and ub is None: + return None + + if ua is None: + return ub + if ub is None: + return ua + + if isinstance(ua, float) and isinstance(ub, float): + return math.sqrt(ua**2 + ub**2) + + # Convert to series if needed + if isinstance(ua, float): + ua_sq = ua**2 + else: + ua_sq = ua**2 + + if isinstance(ub, float): + ub_sq = ub**2 + else: + ub_sq = ub**2 + + result = (ua_sq + ub_sq).map_elements(math.sqrt, return_dtype=pl.Float64) + return result + + # ------------------------------------------------------------------------- + # Conversion + # ------------------------------------------------------------------------- + + def to(self, target: Union[Unit, UnitProduct], graph=None) -> 'NumberColumn': + """Convert all values to a different unit. + + Parameters + ---------- + target : Unit or UnitProduct + The target unit to convert to. + graph : ConversionGraph, optional + The conversion graph to use. Defaults to the global default graph. + + Returns + ------- + NumberColumn + A new NumberColumn with converted values. + """ + from ucon.graph import get_default_graph + + # Normalize to UnitProduct + src = self._unit if isinstance(self._unit, UnitProduct) else UnitProduct.from_unit(self._unit) + dst = target if isinstance(target, UnitProduct) else UnitProduct.from_unit(target) + + # Scale-only conversion (no graph needed) + if self._is_scale_only_conversion(src, dst): + factor = src.fold_scale() / dst.fold_scale() + new_unc = None + if self._uncertainty is not None: + if isinstance(self._uncertainty, float): + new_unc = self._uncertainty * abs(factor) + else: + new_unc = self._uncertainty * abs(factor) + return NumberColumn( + series=self._series * factor, + unit=target, + uncertainty=new_unc, + ) + + # Graph-based conversion + if graph is None: + graph = get_default_graph() + + conversion_map = graph.convert(src=src, dst=dst) + + # Apply map to series values + converted = self._series.map_elements(conversion_map, return_dtype=pl.Float64) + + # Propagate uncertainty through conversion + new_unc = None + if self._uncertainty is not None: + derivative = self._series.map_elements( + lambda x: abs(conversion_map.derivative(x)), + return_dtype=pl.Float64 + ) + if isinstance(self._uncertainty, float): + new_unc = derivative * self._uncertainty + else: + new_unc = derivative * self._uncertainty + + return NumberColumn(series=converted, unit=target, uncertainty=new_unc) + + def _is_scale_only_conversion(self, src: UnitProduct, dst: UnitProduct) -> bool: + """Check if conversion is just a scale change (same base units).""" + if len(src.factors) != len(dst.factors): + return False + + src_by_dim = {} + dst_by_dim = {} + for f, exp in src.factors.items(): + src_by_dim[f.unit.dimension] = (f.unit, exp) + for f, exp in dst.factors.items(): + dst_by_dim[f.unit.dimension] = (f.unit, exp) + + if src_by_dim.keys() != dst_by_dim.keys(): + return False + + for dim in src_by_dim: + src_unit, src_exp = src_by_dim[dim] + dst_unit, dst_exp = dst_by_dim[dim] + if src_unit != dst_unit or abs(src_exp - dst_exp) > 1e-12: + return False + + return True + + # ------------------------------------------------------------------------- + # Reduction Operations + # ------------------------------------------------------------------------- + + def sum(self) -> Number: + """Sum all values.""" + total = float(self._series.sum()) + + unc = None + if self._uncertainty is not None: + if isinstance(self._uncertainty, float): + unc = self._uncertainty * math.sqrt(len(self)) + else: + unc = float(math.sqrt((self._uncertainty**2).sum())) + + return Number(quantity=total, unit=self._unit, uncertainty=unc) + + def mean(self) -> Number: + """Compute the mean.""" + avg = float(self._series.mean()) + + unc = None + if self._uncertainty is not None: + if isinstance(self._uncertainty, float): + unc = self._uncertainty / math.sqrt(len(self)) + else: + unc = float(math.sqrt((self._uncertainty**2).sum()) / len(self)) + + return Number(quantity=avg, unit=self._unit, uncertainty=unc) + + def std(self, ddof: int = 1) -> Number: + """Compute the standard deviation.""" + s = float(self._series.std(ddof=ddof)) + return Number(quantity=s, unit=self._unit, uncertainty=None) + + def min(self) -> Number: + """Return the minimum value.""" + idx = self._series.arg_min() + return self[idx] # type: ignore + + def max(self) -> Number: + """Return the maximum value.""" + idx = self._series.arg_max() + return self[idx] # type: ignore + + def to_list(self) -> list: + """Convert to a list of Number instances.""" + return list(self) + + +# Export check +__all__ = ['NumberColumn', 'HAS_POLARS'] diff --git a/uv.lock b/uv.lock index e68607e..badf653 100644 --- a/uv.lock +++ b/uv.lock @@ -2,7 +2,13 @@ version = 1 revision = 3 requires-python = ">=3.7" resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", "python_full_version == '3.8.*'", "python_full_version < '3.8'", @@ -28,7 +34,13 @@ name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", "python_full_version == '3.8.*'", ] @@ -73,7 +85,13 @@ name = "babel" version = "2.18.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", "python_full_version == '3.8.*'", ] @@ -106,7 +124,13 @@ name = "backrefs" version = "6.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/4e/a6/e325ec73b638d3ede4421b5445d4a0b8b219481826cc079d510100af356c/backrefs-6.2.tar.gz", hash = "sha256:f44ff4d48808b243b6c0cdc6231e22195c32f77046018141556c66f8bab72a49", size = 7012303, upload-time = "2026-02-16T19:10:15.828Z" } @@ -130,11 +154,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.1.4" +version = "2026.2.25" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +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/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, + { 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]] @@ -268,7 +292,7 @@ resolution-markers = [ ] dependencies = [ { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, - { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } wheels = [ @@ -280,7 +304,13 @@ name = "click" version = "8.3.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", ] dependencies = [ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, @@ -582,104 +612,124 @@ toml = [ [[package]] name = "coverage" -version = "7.13.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/ad/49/349848445b0e53660e258acbcc9b0d014895b6739237920886672240f84b/coverage-7.13.2.tar.gz", hash = "sha256:044c6951ec37146b72a50cc81ef02217d27d4c3640efd2640311393cbbf143d3", size = 826523, upload-time = "2026-01-25T13:00:04.889Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/2d/63e37369c8e81a643afe54f76073b020f7b97ddbe698c5c944b51b0a2bc5/coverage-7.13.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4af3b01763909f477ea17c962e2cca8f39b350a4e46e3a30838b2c12e31b81b", size = 218842, upload-time = "2026-01-25T12:57:15.3Z" }, - { url = "https://files.pythonhosted.org/packages/57/06/86ce882a8d58cbcb3030e298788988e618da35420d16a8c66dac34f138d0/coverage-7.13.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:36393bd2841fa0b59498f75466ee9bdec4f770d3254f031f23e8fd8e140ffdd2", size = 219360, upload-time = "2026-01-25T12:57:17.572Z" }, - { url = "https://files.pythonhosted.org/packages/cd/84/70b0eb1ee19ca4ef559c559054c59e5b2ae4ec9af61398670189e5d276e9/coverage-7.13.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9cc7573518b7e2186bd229b1a0fe24a807273798832c27032c4510f47ffdb896", size = 246123, upload-time = "2026-01-25T12:57:19.087Z" }, - { url = "https://files.pythonhosted.org/packages/35/fb/05b9830c2e8275ebc031e0019387cda99113e62bb500ab328bb72578183b/coverage-7.13.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca9566769b69a5e216a4e176d54b9df88f29d750c5b78dbb899e379b4e14b30c", size = 247930, upload-time = "2026-01-25T12:57:20.929Z" }, - { url = "https://files.pythonhosted.org/packages/81/aa/3f37858ca2eed4f09b10ca3c6ddc9041be0a475626cd7fd2712f4a2d526f/coverage-7.13.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c9bdea644e94fd66d75a6f7e9a97bb822371e1fe7eadae2cacd50fcbc28e4dc", size = 249804, upload-time = "2026-01-25T12:57:22.904Z" }, - { url = "https://files.pythonhosted.org/packages/b6/b3/c904f40c56e60a2d9678a5ee8df3d906d297d15fb8bec5756c3b0a67e2df/coverage-7.13.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5bd447332ec4f45838c1ad42268ce21ca87c40deb86eabd59888859b66be22a5", size = 246815, upload-time = "2026-01-25T12:57:24.314Z" }, - { url = "https://files.pythonhosted.org/packages/41/91/ddc1c5394ca7fd086342486440bfdd6b9e9bda512bf774599c7c7a0081e0/coverage-7.13.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7c79ad5c28a16a1277e1187cf83ea8dafdcc689a784228a7d390f19776db7c31", size = 247843, upload-time = "2026-01-25T12:57:26.544Z" }, - { url = "https://files.pythonhosted.org/packages/87/d2/cdff8f4cd33697883c224ea8e003e9c77c0f1a837dc41d95a94dd26aad67/coverage-7.13.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:76e06ccacd1fb6ada5d076ed98a8c6f66e2e6acd3df02819e2ee29fd637b76ad", size = 245850, upload-time = "2026-01-25T12:57:28.507Z" }, - { url = "https://files.pythonhosted.org/packages/f5/42/e837febb7866bf2553ab53dd62ed52f9bb36d60c7e017c55376ad21fbb05/coverage-7.13.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:49d49e9a5e9f4dc3d3dac95278a020afa6d6bdd41f63608a76fa05a719d5b66f", size = 246116, upload-time = "2026-01-25T12:57:30.16Z" }, - { url = "https://files.pythonhosted.org/packages/09/b1/4a3f935d7df154df02ff4f71af8d61298d713a7ba305d050ae475bfbdde2/coverage-7.13.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed2bce0e7bfa53f7b0b01c722da289ef6ad4c18ebd52b1f93704c21f116360c8", size = 246720, upload-time = "2026-01-25T12:57:32.165Z" }, - { url = "https://files.pythonhosted.org/packages/e1/fe/538a6fd44c515f1c5197a3f078094cbaf2ce9f945df5b44e29d95c864bff/coverage-7.13.2-cp310-cp310-win32.whl", hash = "sha256:1574983178b35b9af4db4a9f7328a18a14a0a0ce76ffaa1c1bacb4cc82089a7c", size = 221465, upload-time = "2026-01-25T12:57:33.511Z" }, - { url = "https://files.pythonhosted.org/packages/5e/09/4b63a024295f326ec1a40ec8def27799300ce8775b1cbf0d33b1790605c4/coverage-7.13.2-cp310-cp310-win_amd64.whl", hash = "sha256:a360a8baeb038928ceb996f5623a4cd508728f8f13e08d4e96ce161702f3dd99", size = 222397, upload-time = "2026-01-25T12:57:34.927Z" }, - { url = "https://files.pythonhosted.org/packages/6c/01/abca50583a8975bb6e1c59eff67ed8e48bb127c07dad5c28d9e96ccc09ec/coverage-7.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:060ebf6f2c51aff5ba38e1f43a2095e087389b1c69d559fde6049a4b0001320e", size = 218971, upload-time = "2026-01-25T12:57:36.953Z" }, - { url = "https://files.pythonhosted.org/packages/eb/0e/b6489f344d99cd1e5b4d5e1be52dfd3f8a3dc5112aa6c33948da8cabad4e/coverage-7.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1ea8ca9db5e7469cd364552985e15911548ea5b69c48a17291f0cac70484b2e", size = 219473, upload-time = "2026-01-25T12:57:38.934Z" }, - { url = "https://files.pythonhosted.org/packages/17/11/db2f414915a8e4ec53f60b17956c27f21fb68fcf20f8a455ce7c2ccec638/coverage-7.13.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b780090d15fd58f07cf2011943e25a5f0c1c894384b13a216b6c86c8a8a7c508", size = 249896, upload-time = "2026-01-25T12:57:40.365Z" }, - { url = "https://files.pythonhosted.org/packages/80/06/0823fe93913663c017e508e8810c998c8ebd3ec2a5a85d2c3754297bdede/coverage-7.13.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:88a800258d83acb803c38175b4495d293656d5fac48659c953c18e5f539a274b", size = 251810, upload-time = "2026-01-25T12:57:42.045Z" }, - { url = "https://files.pythonhosted.org/packages/61/dc/b151c3cc41b28cdf7f0166c5fa1271cbc305a8ec0124cce4b04f74791a18/coverage-7.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6326e18e9a553e674d948536a04a80d850a5eeefe2aae2e6d7cf05d54046c01b", size = 253920, upload-time = "2026-01-25T12:57:44.026Z" }, - { url = "https://files.pythonhosted.org/packages/2d/35/e83de0556e54a4729a2b94ea816f74ce08732e81945024adee46851c2264/coverage-7.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:59562de3f797979e1ff07c587e2ac36ba60ca59d16c211eceaa579c266c5022f", size = 250025, upload-time = "2026-01-25T12:57:45.624Z" }, - { url = "https://files.pythonhosted.org/packages/39/67/af2eb9c3926ce3ea0d58a0d2516fcbdacf7a9fc9559fe63076beaf3f2596/coverage-7.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:27ba1ed6f66b0e2d61bfa78874dffd4f8c3a12f8e2b5410e515ab345ba7bc9c3", size = 251612, upload-time = "2026-01-25T12:57:47.713Z" }, - { url = "https://files.pythonhosted.org/packages/26/62/5be2e25f3d6c711d23b71296f8b44c978d4c8b4e5b26871abfc164297502/coverage-7.13.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8be48da4d47cc68754ce643ea50b3234557cbefe47c2f120495e7bd0a2756f2b", size = 249670, upload-time = "2026-01-25T12:57:49.378Z" }, - { url = "https://files.pythonhosted.org/packages/b3/51/400d1b09a8344199f9b6a6fc1868005d766b7ea95e7882e494fa862ca69c/coverage-7.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2a47a4223d3361b91176aedd9d4e05844ca67d7188456227b6bf5e436630c9a1", size = 249395, upload-time = "2026-01-25T12:57:50.86Z" }, - { url = "https://files.pythonhosted.org/packages/e0/36/f02234bc6e5230e2f0a63fd125d0a2093c73ef20fdf681c7af62a140e4e7/coverage-7.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6f141b468740197d6bd38f2b26ade124363228cc3f9858bd9924ab059e00059", size = 250298, upload-time = "2026-01-25T12:57:52.287Z" }, - { url = "https://files.pythonhosted.org/packages/b0/06/713110d3dd3151b93611c9cbfc65c15b4156b44f927fced49ac0b20b32a4/coverage-7.13.2-cp311-cp311-win32.whl", hash = "sha256:89567798404af067604246e01a49ef907d112edf2b75ef814b1364d5ce267031", size = 221485, upload-time = "2026-01-25T12:57:53.876Z" }, - { url = "https://files.pythonhosted.org/packages/16/0c/3ae6255fa1ebcb7dec19c9a59e85ef5f34566d1265c70af5b2fc981da834/coverage-7.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:21dd57941804ae2ac7e921771a5e21bbf9aabec317a041d164853ad0a96ce31e", size = 222421, upload-time = "2026-01-25T12:57:55.433Z" }, - { url = "https://files.pythonhosted.org/packages/b5/37/fabc3179af4d61d89ea47bd04333fec735cd5e8b59baad44fed9fc4170d7/coverage-7.13.2-cp311-cp311-win_arm64.whl", hash = "sha256:10758e0586c134a0bafa28f2d37dd2cdb5e4a90de25c0fc0c77dabbad46eca28", size = 221088, upload-time = "2026-01-25T12:57:57.41Z" }, - { url = "https://files.pythonhosted.org/packages/46/39/e92a35f7800222d3f7b2cbb7bbc3b65672ae8d501cb31801b2d2bd7acdf1/coverage-7.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f106b2af193f965d0d3234f3f83fc35278c7fb935dfbde56ae2da3dd2c03b84d", size = 219142, upload-time = "2026-01-25T12:58:00.448Z" }, - { url = "https://files.pythonhosted.org/packages/45/7a/8bf9e9309c4c996e65c52a7c5a112707ecdd9fbaf49e10b5a705a402bbb4/coverage-7.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f45d21dc4d5d6bd29323f0320089ef7eae16e4bef712dff79d184fa7330af3", size = 219503, upload-time = "2026-01-25T12:58:02.451Z" }, - { url = "https://files.pythonhosted.org/packages/87/93/17661e06b7b37580923f3f12406ac91d78aeed293fb6da0b69cc7957582f/coverage-7.13.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fae91dfecd816444c74531a9c3d6ded17a504767e97aa674d44f638107265b99", size = 251006, upload-time = "2026-01-25T12:58:04.059Z" }, - { url = "https://files.pythonhosted.org/packages/12/f0/f9e59fb8c310171497f379e25db060abef9fa605e09d63157eebec102676/coverage-7.13.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:264657171406c114787b441484de620e03d8f7202f113d62fcd3d9688baa3e6f", size = 253750, upload-time = "2026-01-25T12:58:05.574Z" }, - { url = "https://files.pythonhosted.org/packages/e5/b1/1935e31add2232663cf7edd8269548b122a7d100047ff93475dbaaae673e/coverage-7.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae47d8dcd3ded0155afbb59c62bd8ab07ea0fd4902e1c40567439e6db9dcaf2f", size = 254862, upload-time = "2026-01-25T12:58:07.647Z" }, - { url = "https://files.pythonhosted.org/packages/af/59/b5e97071ec13df5f45da2b3391b6cdbec78ba20757bc92580a5b3d5fa53c/coverage-7.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a0b33e9fd838220b007ce8f299114d406c1e8edb21336af4c97a26ecfd185aa", size = 251420, upload-time = "2026-01-25T12:58:09.309Z" }, - { url = "https://files.pythonhosted.org/packages/3f/75/9495932f87469d013dc515fb0ce1aac5fa97766f38f6b1a1deb1ee7b7f3a/coverage-7.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3becbea7f3ce9a2d4d430f223ec15888e4deb31395840a79e916368d6004cce", size = 252786, upload-time = "2026-01-25T12:58:10.909Z" }, - { url = "https://files.pythonhosted.org/packages/6a/59/af550721f0eb62f46f7b8cb7e6f1860592189267b1c411a4e3a057caacee/coverage-7.13.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f819c727a6e6eeb8711e4ce63d78c620f69630a2e9d53bc95ca5379f57b6ba94", size = 250928, upload-time = "2026-01-25T12:58:12.449Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b1/21b4445709aae500be4ab43bbcfb4e53dc0811c3396dcb11bf9f23fd0226/coverage-7.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4f7b71757a3ab19f7ba286e04c181004c1d61be921795ee8ba6970fd0ec91da5", size = 250496, upload-time = "2026-01-25T12:58:14.047Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b1/0f5d89dfe0392990e4f3980adbde3eb34885bc1effb2dc369e0bf385e389/coverage-7.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b7fc50d2afd2e6b4f6f2f403b70103d280a8e0cb35320cbbe6debcda02a1030b", size = 252373, upload-time = "2026-01-25T12:58:15.976Z" }, - { url = "https://files.pythonhosted.org/packages/01/c9/0cf1a6a57a9968cc049a6b896693faa523c638a5314b1fc374eb2b2ac904/coverage-7.13.2-cp312-cp312-win32.whl", hash = "sha256:292250282cf9bcf206b543d7608bda17ca6fc151f4cbae949fc7e115112fbd41", size = 221696, upload-time = "2026-01-25T12:58:17.517Z" }, - { url = "https://files.pythonhosted.org/packages/4d/05/d7540bf983f09d32803911afed135524570f8c47bb394bf6206c1dc3a786/coverage-7.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:eeea10169fac01549a7921d27a3e517194ae254b542102267bef7a93ed38c40e", size = 222504, upload-time = "2026-01-25T12:58:19.115Z" }, - { url = "https://files.pythonhosted.org/packages/15/8b/1a9f037a736ced0a12aacf6330cdaad5008081142a7070bc58b0f7930cbc/coverage-7.13.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a5b567f0b635b592c917f96b9a9cb3dbd4c320d03f4bf94e9084e494f2e8894", size = 221120, upload-time = "2026-01-25T12:58:21.334Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f0/3d3eac7568ab6096ff23791a526b0048a1ff3f49d0e236b2af6fb6558e88/coverage-7.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ed75de7d1217cf3b99365d110975f83af0528c849ef5180a12fd91b5064df9d6", size = 219168, upload-time = "2026-01-25T12:58:23.376Z" }, - { url = "https://files.pythonhosted.org/packages/a3/a6/f8b5cfeddbab95fdef4dcd682d82e5dcff7a112ced57a959f89537ee9995/coverage-7.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97e596de8fa9bada4d88fde64a3f4d37f1b6131e4faa32bad7808abc79887ddc", size = 219537, upload-time = "2026-01-25T12:58:24.932Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e6/8d8e6e0c516c838229d1e41cadcec91745f4b1031d4db17ce0043a0423b4/coverage-7.13.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:68c86173562ed4413345410c9480a8d64864ac5e54a5cda236748031e094229f", size = 250528, upload-time = "2026-01-25T12:58:26.567Z" }, - { url = "https://files.pythonhosted.org/packages/8e/78/befa6640f74092b86961f957f26504c8fba3d7da57cc2ab7407391870495/coverage-7.13.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7be4d613638d678b2b3773b8f687537b284d7074695a43fe2fbbfc0e31ceaed1", size = 253132, upload-time = "2026-01-25T12:58:28.251Z" }, - { url = "https://files.pythonhosted.org/packages/9d/10/1630db1edd8ce675124a2ee0f7becc603d2bb7b345c2387b4b95c6907094/coverage-7.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7f63ce526a96acd0e16c4af8b50b64334239550402fb1607ce6a584a6d62ce9", size = 254374, upload-time = "2026-01-25T12:58:30.294Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1d/0d9381647b1e8e6d310ac4140be9c428a0277330991e0c35bdd751e338a4/coverage-7.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:406821f37f864f968e29ac14c3fccae0fec9fdeba48327f0341decf4daf92d7c", size = 250762, upload-time = "2026-01-25T12:58:32.036Z" }, - { url = "https://files.pythonhosted.org/packages/43/e4/5636dfc9a7c871ee8776af83ee33b4c26bc508ad6cee1e89b6419a366582/coverage-7.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ee68e5a4e3e5443623406b905db447dceddffee0dceb39f4e0cd9ec2a35004b5", size = 252502, upload-time = "2026-01-25T12:58:33.961Z" }, - { url = "https://files.pythonhosted.org/packages/02/2a/7ff2884d79d420cbb2d12fed6fff727b6d0ef27253140d3cdbbd03187ee0/coverage-7.13.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2ee0e58cca0c17dd9c6c1cdde02bb705c7b3fbfa5f3b0b5afeda20d4ebff8ef4", size = 250463, upload-time = "2026-01-25T12:58:35.529Z" }, - { url = "https://files.pythonhosted.org/packages/91/c0/ba51087db645b6c7261570400fc62c89a16278763f36ba618dc8657a187b/coverage-7.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6e5bbb5018bf76a56aabdb64246b5288d5ae1b7d0dd4d0534fe86df2c2992d1c", size = 250288, upload-time = "2026-01-25T12:58:37.226Z" }, - { url = "https://files.pythonhosted.org/packages/03/07/44e6f428551c4d9faf63ebcefe49b30e5c89d1be96f6a3abd86a52da9d15/coverage-7.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a55516c68ef3e08e134e818d5e308ffa6b1337cc8b092b69b24287bf07d38e31", size = 252063, upload-time = "2026-01-25T12:58:38.821Z" }, - { url = "https://files.pythonhosted.org/packages/c2/67/35b730ad7e1859dd57e834d1bc06080d22d2f87457d53f692fce3f24a5a9/coverage-7.13.2-cp313-cp313-win32.whl", hash = "sha256:5b20211c47a8abf4abc3319d8ce2464864fa9f30c5fcaf958a3eed92f4f1fef8", size = 221716, upload-time = "2026-01-25T12:58:40.484Z" }, - { url = "https://files.pythonhosted.org/packages/0d/82/e5fcf5a97c72f45fc14829237a6550bf49d0ab882ac90e04b12a69db76b4/coverage-7.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:14f500232e521201cf031549fb1ebdfc0a40f401cf519157f76c397e586c3beb", size = 222522, upload-time = "2026-01-25T12:58:43.247Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f1/25d7b2f946d239dd2d6644ca2cc060d24f97551e2af13b6c24c722ae5f97/coverage-7.13.2-cp313-cp313-win_arm64.whl", hash = "sha256:9779310cb5a9778a60c899f075a8514c89fa6d10131445c2207fc893e0b14557", size = 221145, upload-time = "2026-01-25T12:58:45Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f7/080376c029c8f76fadfe43911d0daffa0cbdc9f9418a0eead70c56fb7f4b/coverage-7.13.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e64fa5a1e41ce5df6b547cbc3d3699381c9e2c2c369c67837e716ed0f549d48e", size = 219861, upload-time = "2026-01-25T12:58:46.586Z" }, - { url = "https://files.pythonhosted.org/packages/42/11/0b5e315af5ab35f4c4a70e64d3314e4eec25eefc6dec13be3a7d5ffe8ac5/coverage-7.13.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b01899e82a04085b6561eb233fd688474f57455e8ad35cd82286463ba06332b7", size = 220207, upload-time = "2026-01-25T12:58:48.277Z" }, - { url = "https://files.pythonhosted.org/packages/b2/0c/0874d0318fb1062117acbef06a09cf8b63f3060c22265adaad24b36306b7/coverage-7.13.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:838943bea48be0e2768b0cf7819544cdedc1bbb2f28427eabb6eb8c9eb2285d3", size = 261504, upload-time = "2026-01-25T12:58:49.904Z" }, - { url = "https://files.pythonhosted.org/packages/83/5e/1cd72c22ecb30751e43a72f40ba50fcef1b7e93e3ea823bd9feda8e51f9a/coverage-7.13.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:93d1d25ec2b27e90bcfef7012992d1f5121b51161b8bffcda756a816cf13c2c3", size = 263582, upload-time = "2026-01-25T12:58:51.582Z" }, - { url = "https://files.pythonhosted.org/packages/9b/da/8acf356707c7a42df4d0657020308e23e5a07397e81492640c186268497c/coverage-7.13.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93b57142f9621b0d12349c43fc7741fe578e4bc914c1e5a54142856cfc0bf421", size = 266008, upload-time = "2026-01-25T12:58:53.234Z" }, - { url = "https://files.pythonhosted.org/packages/41/41/ea1730af99960309423c6ea8d6a4f1fa5564b2d97bd1d29dda4b42611f04/coverage-7.13.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f06799ae1bdfff7ccb8665d75f8291c69110ba9585253de254688aa8a1ccc6c5", size = 260762, upload-time = "2026-01-25T12:58:55.372Z" }, - { url = "https://files.pythonhosted.org/packages/22/fa/02884d2080ba71db64fdc127b311db60e01fe6ba797d9c8363725e39f4d5/coverage-7.13.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f9405ab4f81d490811b1d91c7a20361135a2df4c170e7f0b747a794da5b7f23", size = 263571, upload-time = "2026-01-25T12:58:57.52Z" }, - { url = "https://files.pythonhosted.org/packages/d2/6b/4083aaaeba9b3112f55ac57c2ce7001dc4d8fa3fcc228a39f09cc84ede27/coverage-7.13.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f9ab1d5b86f8fbc97a5b3cd6280a3fd85fef3b028689d8a2c00918f0d82c728c", size = 261200, upload-time = "2026-01-25T12:58:59.255Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d2/aea92fa36d61955e8c416ede9cf9bf142aa196f3aea214bb67f85235a050/coverage-7.13.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:f674f59712d67e841525b99e5e2b595250e39b529c3bda14764e4f625a3fa01f", size = 260095, upload-time = "2026-01-25T12:59:01.066Z" }, - { url = "https://files.pythonhosted.org/packages/0d/ae/04ffe96a80f107ea21b22b2367175c621da920063260a1c22f9452fd7866/coverage-7.13.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c6cadac7b8ace1ba9144feb1ae3cb787a6065ba6d23ffc59a934b16406c26573", size = 262284, upload-time = "2026-01-25T12:59:02.802Z" }, - { url = "https://files.pythonhosted.org/packages/1c/7a/6f354dcd7dfc41297791d6fb4e0d618acb55810bde2c1fd14b3939e05c2b/coverage-7.13.2-cp313-cp313t-win32.whl", hash = "sha256:14ae4146465f8e6e6253eba0cccd57423e598a4cb925958b240c805300918343", size = 222389, upload-time = "2026-01-25T12:59:04.563Z" }, - { url = "https://files.pythonhosted.org/packages/8d/d5/080ad292a4a3d3daf411574be0a1f56d6dee2c4fdf6b005342be9fac807f/coverage-7.13.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9074896edd705a05769e3de0eac0a8388484b503b68863dd06d5e473f874fd47", size = 223450, upload-time = "2026-01-25T12:59:06.677Z" }, - { url = "https://files.pythonhosted.org/packages/88/96/df576fbacc522e9fb8d1c4b7a7fc62eb734be56e2cba1d88d2eabe08ea3f/coverage-7.13.2-cp313-cp313t-win_arm64.whl", hash = "sha256:69e526e14f3f854eda573d3cf40cffd29a1a91c684743d904c33dbdcd0e0f3e7", size = 221707, upload-time = "2026-01-25T12:59:08.363Z" }, - { url = "https://files.pythonhosted.org/packages/55/53/1da9e51a0775634b04fcc11eb25c002fc58ee4f92ce2e8512f94ac5fc5bf/coverage-7.13.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:387a825f43d680e7310e6f325b2167dd093bc8ffd933b83e9aa0983cf6e0a2ef", size = 219213, upload-time = "2026-01-25T12:59:11.909Z" }, - { url = "https://files.pythonhosted.org/packages/46/35/b3caac3ebbd10230fea5a33012b27d19e999a17c9285c4228b4b2e35b7da/coverage-7.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f0d7fea9d8e5d778cd5a9e8fc38308ad688f02040e883cdc13311ef2748cb40f", size = 219549, upload-time = "2026-01-25T12:59:13.638Z" }, - { url = "https://files.pythonhosted.org/packages/76/9c/e1cf7def1bdc72c1907e60703983a588f9558434a2ff94615747bd73c192/coverage-7.13.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e080afb413be106c95c4ee96b4fffdc9e2fa56a8bbf90b5c0918e5c4449412f5", size = 250586, upload-time = "2026-01-25T12:59:15.808Z" }, - { url = "https://files.pythonhosted.org/packages/ba/49/f54ec02ed12be66c8d8897270505759e057b0c68564a65c429ccdd1f139e/coverage-7.13.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7fc042ba3c7ce25b8a9f097eb0f32a5ce1ccdb639d9eec114e26def98e1f8a4", size = 253093, upload-time = "2026-01-25T12:59:17.491Z" }, - { url = "https://files.pythonhosted.org/packages/fb/5e/aaf86be3e181d907e23c0f61fccaeb38de8e6f6b47aed92bf57d8fc9c034/coverage-7.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0ba505e021557f7f8173ee8cd6b926373d8653e5ff7581ae2efce1b11ef4c27", size = 254446, upload-time = "2026-01-25T12:59:19.752Z" }, - { url = "https://files.pythonhosted.org/packages/28/c8/a5fa01460e2d75b0c853b392080d6829d3ca8b5ab31e158fa0501bc7c708/coverage-7.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7de326f80e3451bd5cc7239ab46c73ddb658fe0b7649476bc7413572d36cd548", size = 250615, upload-time = "2026-01-25T12:59:21.928Z" }, - { url = "https://files.pythonhosted.org/packages/86/0b/6d56315a55f7062bb66410732c24879ccb2ec527ab6630246de5fe45a1df/coverage-7.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:abaea04f1e7e34841d4a7b343904a3f59481f62f9df39e2cd399d69a187a9660", size = 252452, upload-time = "2026-01-25T12:59:23.592Z" }, - { url = "https://files.pythonhosted.org/packages/30/19/9bc550363ebc6b0ea121977ee44d05ecd1e8bf79018b8444f1028701c563/coverage-7.13.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9f93959ee0c604bccd8e0697be21de0887b1f73efcc3aa73a3ec0fd13feace92", size = 250418, upload-time = "2026-01-25T12:59:25.392Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/580530a31ca2f0cc6f07a8f2ab5460785b02bb11bdf815d4c4d37a4c5169/coverage-7.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:13fe81ead04e34e105bf1b3c9f9cdf32ce31736ee5d90a8d2de02b9d3e1bcb82", size = 250231, upload-time = "2026-01-25T12:59:27.888Z" }, - { url = "https://files.pythonhosted.org/packages/e2/42/dd9093f919dc3088cb472893651884bd675e3df3d38a43f9053656dca9a2/coverage-7.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d6d16b0f71120e365741bca2cb473ca6fe38930bc5431c5e850ba949f708f892", size = 251888, upload-time = "2026-01-25T12:59:29.636Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a6/0af4053e6e819774626e133c3d6f70fae4d44884bfc4b126cb647baee8d3/coverage-7.13.2-cp314-cp314-win32.whl", hash = "sha256:9b2f4714bb7d99ba3790ee095b3b4ac94767e1347fe424278a0b10acb3ff04fe", size = 221968, upload-time = "2026-01-25T12:59:31.424Z" }, - { url = "https://files.pythonhosted.org/packages/c4/cc/5aff1e1f80d55862442855517bb8ad8ad3a68639441ff6287dde6a58558b/coverage-7.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:e4121a90823a063d717a96e0a0529c727fb31ea889369a0ee3ec00ed99bf6859", size = 222783, upload-time = "2026-01-25T12:59:33.118Z" }, - { url = "https://files.pythonhosted.org/packages/de/20/09abafb24f84b3292cc658728803416c15b79f9ee5e68d25238a895b07d9/coverage-7.13.2-cp314-cp314-win_arm64.whl", hash = "sha256:6873f0271b4a15a33e7590f338d823f6f66f91ed147a03938d7ce26efd04eee6", size = 221348, upload-time = "2026-01-25T12:59:34.939Z" }, - { url = "https://files.pythonhosted.org/packages/b6/60/a3820c7232db63be060e4019017cd3426751c2699dab3c62819cdbcea387/coverage-7.13.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f61d349f5b7cd95c34017f1927ee379bfbe9884300d74e07cf630ccf7a610c1b", size = 219950, upload-time = "2026-01-25T12:59:36.624Z" }, - { url = "https://files.pythonhosted.org/packages/fd/37/e4ef5975fdeb86b1e56db9a82f41b032e3d93a840ebaf4064f39e770d5c5/coverage-7.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a43d34ce714f4ca674c0d90beb760eb05aad906f2c47580ccee9da8fe8bfb417", size = 220209, upload-time = "2026-01-25T12:59:38.339Z" }, - { url = "https://files.pythonhosted.org/packages/54/df/d40e091d00c51adca1e251d3b60a8b464112efa3004949e96a74d7c19a64/coverage-7.13.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bff1b04cb9d4900ce5c56c4942f047dc7efe57e2608cb7c3c8936e9970ccdbee", size = 261576, upload-time = "2026-01-25T12:59:40.446Z" }, - { url = "https://files.pythonhosted.org/packages/c5/44/5259c4bed54e3392e5c176121af9f71919d96dde853386e7730e705f3520/coverage-7.13.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6ae99e4560963ad8e163e819e5d77d413d331fd00566c1e0856aa252303552c1", size = 263704, upload-time = "2026-01-25T12:59:42.346Z" }, - { url = "https://files.pythonhosted.org/packages/16/bd/ae9f005827abcbe2c70157459ae86053971c9fa14617b63903abbdce26d9/coverage-7.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e79a8c7d461820257d9aa43716c4efc55366d7b292e46b5b37165be1d377405d", size = 266109, upload-time = "2026-01-25T12:59:44.073Z" }, - { url = "https://files.pythonhosted.org/packages/a2/c0/8e279c1c0f5b1eaa3ad9b0fb7a5637fc0379ea7d85a781c0fe0bb3cfc2ab/coverage-7.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:060ee84f6a769d40c492711911a76811b4befb6fba50abb450371abb720f5bd6", size = 260686, upload-time = "2026-01-25T12:59:45.804Z" }, - { url = "https://files.pythonhosted.org/packages/b2/47/3a8112627e9d863e7cddd72894171c929e94491a597811725befdcd76bce/coverage-7.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bca209d001fd03ea2d978f8a4985093240a355c93078aee3f799852c23f561a", size = 263568, upload-time = "2026-01-25T12:59:47.929Z" }, - { url = "https://files.pythonhosted.org/packages/92/bc/7ea367d84afa3120afc3ce6de294fd2dcd33b51e2e7fbe4bbfd200f2cb8c/coverage-7.13.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6b8092aa38d72f091db61ef83cb66076f18f02da3e1a75039a4f218629600e04", size = 261174, upload-time = "2026-01-25T12:59:49.717Z" }, - { url = "https://files.pythonhosted.org/packages/33/b7/f1092dcecb6637e31cc2db099581ee5c61a17647849bae6b8261a2b78430/coverage-7.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4a3158dc2dcce5200d91ec28cd315c999eebff355437d2765840555d765a6e5f", size = 260017, upload-time = "2026-01-25T12:59:51.463Z" }, - { url = "https://files.pythonhosted.org/packages/2b/cd/f3d07d4b95fbe1a2ef0958c15da614f7e4f557720132de34d2dc3aa7e911/coverage-7.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3973f353b2d70bd9796cc12f532a05945232ccae966456c8ed7034cb96bbfd6f", size = 262337, upload-time = "2026-01-25T12:59:53.407Z" }, - { url = "https://files.pythonhosted.org/packages/e0/db/b0d5b2873a07cb1e06a55d998697c0a5a540dcefbf353774c99eb3874513/coverage-7.13.2-cp314-cp314t-win32.whl", hash = "sha256:79f6506a678a59d4ded048dc72f1859ebede8ec2b9a2d509ebe161f01c2879d3", size = 222749, upload-time = "2026-01-25T12:59:56.316Z" }, - { url = "https://files.pythonhosted.org/packages/e5/2f/838a5394c082ac57d85f57f6aba53093b30d9089781df72412126505716f/coverage-7.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:196bfeabdccc5a020a57d5a368c681e3a6ceb0447d153aeccc1ab4d70a5032ba", size = 223857, upload-time = "2026-01-25T12:59:58.201Z" }, - { url = "https://files.pythonhosted.org/packages/44/d4/b608243e76ead3a4298824b50922b89ef793e50069ce30316a65c1b4d7ef/coverage-7.13.2-cp314-cp314t-win_arm64.whl", hash = "sha256:69269ab58783e090bfbf5b916ab3d188126e22d6070bbfc93098fdd474ef937c", size = 221881, upload-time = "2026-01-25T13:00:00.449Z" }, - { url = "https://files.pythonhosted.org/packages/d2/db/d291e30fdf7ea617a335531e72294e0c723356d7fdde8fba00610a76bda9/coverage-7.13.2-py3-none-any.whl", hash = "sha256:40ce1ea1e25125556d8e76bd0b61500839a07944cc287ac21d5626f3e620cad5", size = 210943, upload-time = "2026-01-25T13:00:02.388Z" }, +version = "7.13.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/d4/7827d9ffa34d5d4d752eec907022aa417120936282fc488306f5da08c292/coverage-7.13.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fc31c787a84f8cd6027eba44010517020e0d18487064cd3d8968941856d1415", size = 219152, upload-time = "2026-02-09T12:56:11.974Z" }, + { url = "https://files.pythonhosted.org/packages/35/b0/d69df26607c64043292644dbb9dc54b0856fabaa2cbb1eeee3331cc9e280/coverage-7.13.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a32ebc02a1805adf637fc8dec324b5cdacd2e493515424f70ee33799573d661b", size = 219667, upload-time = "2026-02-09T12:56:13.33Z" }, + { url = "https://files.pythonhosted.org/packages/82/a4/c1523f7c9e47b2271dbf8c2a097e7a1f89ef0d66f5840bb59b7e8814157b/coverage-7.13.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e24f9156097ff9dc286f2f913df3a7f63c0e333dcafa3c196f2c18b4175ca09a", size = 246425, upload-time = "2026-02-09T12:56:14.552Z" }, + { url = "https://files.pythonhosted.org/packages/f8/02/aa7ec01d1a5023c4b680ab7257f9bfde9defe8fdddfe40be096ac19e8177/coverage-7.13.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8041b6c5bfdc03257666e9881d33b1abc88daccaf73f7b6340fb7946655cd10f", size = 248229, upload-time = "2026-02-09T12:56:16.31Z" }, + { url = "https://files.pythonhosted.org/packages/35/98/85aba0aed5126d896162087ef3f0e789a225697245256fc6181b95f47207/coverage-7.13.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a09cfa6a5862bc2fc6ca7c3def5b2926194a56b8ab78ffcf617d28911123012", size = 250106, upload-time = "2026-02-09T12:56:18.024Z" }, + { url = "https://files.pythonhosted.org/packages/96/72/1db59bd67494bc162e3e4cd5fbc7edba2c7026b22f7c8ef1496d58c2b94c/coverage-7.13.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:296f8b0af861d3970c2a4d8c91d48eb4dd4771bcef9baedec6a9b515d7de3def", size = 252021, upload-time = "2026-02-09T12:56:19.272Z" }, + { url = "https://files.pythonhosted.org/packages/9d/97/72899c59c7066961de6e3daa142d459d47d104956db43e057e034f015c8a/coverage-7.13.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e101609bcbbfb04605ea1027b10dc3735c094d12d40826a60f897b98b1c30256", size = 247114, upload-time = "2026-02-09T12:56:21.051Z" }, + { url = "https://files.pythonhosted.org/packages/39/1f/f1885573b5970235e908da4389176936c8933e86cb316b9620aab1585fa2/coverage-7.13.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aa3feb8db2e87ff5e6d00d7e1480ae241876286691265657b500886c98f38bda", size = 248143, upload-time = "2026-02-09T12:56:22.585Z" }, + { url = "https://files.pythonhosted.org/packages/a8/cf/e80390c5b7480b722fa3e994f8202807799b85bc562aa4f1dde209fbb7be/coverage-7.13.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4fc7fa81bbaf5a02801b65346c8b3e657f1d93763e58c0abdf7c992addd81a92", size = 246152, upload-time = "2026-02-09T12:56:23.748Z" }, + { url = "https://files.pythonhosted.org/packages/44/bf/f89a8350d85572f95412debb0fb9bb4795b1d5b5232bd652923c759e787b/coverage-7.13.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:33901f604424145c6e9c2398684b92e176c0b12df77d52db81c20abd48c3794c", size = 249959, upload-time = "2026-02-09T12:56:25.209Z" }, + { url = "https://files.pythonhosted.org/packages/f7/6e/612a02aece8178c818df273e8d1642190c4875402ca2ba74514394b27aba/coverage-7.13.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:bb28c0f2cf2782508a40cec377935829d5fcc3ad9a3681375af4e84eb34b6b58", size = 246416, upload-time = "2026-02-09T12:56:26.475Z" }, + { url = "https://files.pythonhosted.org/packages/cb/98/b5afc39af67c2fa6786b03c3a7091fc300947387ce8914b096db8a73d67a/coverage-7.13.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d107aff57a83222ddbd8d9ee705ede2af2cc926608b57abed8ef96b50b7e8f9", size = 247025, upload-time = "2026-02-09T12:56:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/51/30/2bba8ef0682d5bd210c38fe497e12a06c9f8d663f7025e9f5c2c31ce847d/coverage-7.13.4-cp310-cp310-win32.whl", hash = "sha256:a6f94a7d00eb18f1b6d403c91a88fd58cfc92d4b16080dfdb774afc8294469bf", size = 221758, upload-time = "2026-02-09T12:56:29.051Z" }, + { url = "https://files.pythonhosted.org/packages/78/13/331f94934cf6c092b8ea59ff868eb587bc8fe0893f02c55bc6c0183a192e/coverage-7.13.4-cp310-cp310-win_amd64.whl", hash = "sha256:2cb0f1e000ebc419632bbe04366a8990b6e32c4e0b51543a6484ffe15eaeda95", size = 222693, upload-time = "2026-02-09T12:56:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" }, + { url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" }, + { url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" }, + { url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" }, + { url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" }, + { url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" }, + { url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" }, + { url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" }, + { url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" }, + { url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" }, + { url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" }, + { url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" }, + { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, + { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, + { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, + { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, + { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, + { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, + { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, + { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, + { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, + { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, + { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, + { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, + { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, + { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, + { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, + { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, + { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, + { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, + { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, + { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, + { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, + { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, + { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, + { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, + { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" }, + { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" }, + { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" }, + { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" }, + { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" }, + { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" }, + { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" }, + { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" }, + { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" }, + { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" }, + { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" }, + { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" }, + { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" }, + { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" }, + { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" }, + { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" }, + { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" }, + { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" }, + { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" }, + { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" }, + { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, ] [package.optional-dependencies] @@ -694,7 +744,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, - { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.13'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -760,33 +810,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0", size = 144439, upload-time = "2025-09-05T15:02:27.511Z" }, ] -[[package]] -name = "griffe" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] -dependencies = [ - { name = "griffecli", marker = "python_full_version >= '3.10'" }, - { name = "griffelib", marker = "python_full_version >= '3.10'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/94/ee21d41e7eb4f823b94603b9d40f86d3c7fde80eacc2c3c71845476dddaa/griffe-2.0.0-py3-none-any.whl", hash = "sha256:5418081135a391c3e6e757a7f3f156f1a1a746cc7b4023868ff7d5e2f9a980aa", size = 5214, upload-time = "2026-02-09T19:09:44.105Z" }, -] - -[[package]] -name = "griffecli" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.10'" }, - { name = "griffelib", marker = "python_full_version >= '3.10'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ed/d93f7a447bbf7a935d8868e9617cbe1cadf9ee9ee6bd275d3040fbf93d60/griffecli-2.0.0-py3-none-any.whl", hash = "sha256:9f7cd9ee9b21d55e91689358978d2385ae65c22f307a63fb3269acf3f21e643d", size = 9345, upload-time = "2026-02-09T19:09:42.554Z" }, -] - [[package]] name = "griffelib" version = "2.0.0" @@ -812,7 +835,13 @@ name = "idna" version = "3.11" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", "python_full_version == '3.8.*'", ] @@ -825,15 +854,48 @@ wheels = [ name = "importlib-metadata" version = "6.7.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] dependencies = [ { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "zipp", marker = "python_full_version < '3.10'" }, + { name = "zipp", version = "3.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a3/82/f6e29c8d5c098b6be61460371c2c5591f4a335923639edec43b3830650a4/importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4", size = 53569, upload-time = "2023-06-18T21:44:35.024Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ff/94/64287b38c7de4c90683630338cf28f129decbba0a44f0c6db35a873c73c4/importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5", size = 22934, upload-time = "2023-06-18T21:44:33.441Z" }, ] +[[package]] +name = "importlib-metadata" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "zipp", version = "3.20.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304, upload-time = "2024-09-11T14:56:08.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514, upload-time = "2024-09-11T14:56:07.019Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "zipp", version = "3.23.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, +] + [[package]] name = "iniconfig" version = "2.0.0" @@ -864,7 +926,13 @@ name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", ] 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 = [ @@ -892,7 +960,7 @@ resolution-markers = [ "python_full_version < '3.8'", ] dependencies = [ - { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/87/2a/62841f4fb1fef5fa015ded48d02401cd95643ca03b6760b29437b62a04a4/Markdown-3.4.4.tar.gz", hash = "sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6", size = 324459, upload-time = "2023-07-25T15:13:45.311Z" } wheels = [ @@ -907,7 +975,7 @@ resolution-markers = [ "python_full_version == '3.8.*'", ] dependencies = [ - { name = "importlib-metadata", marker = "python_full_version == '3.8.*'" }, + { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086, upload-time = "2024-08-16T15:55:17.812Z" } wheels = [ @@ -922,7 +990,7 @@ resolution-markers = [ "python_full_version == '3.9.*'", ] dependencies = [ - { name = "importlib-metadata", marker = "python_full_version == '3.9.*'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } wheels = [ @@ -934,7 +1002,13 @@ name = "markdown" version = "3.10.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } wheels = [ @@ -1017,7 +1091,13 @@ name = "markupsafe" version = "3.0.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] 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" } @@ -1132,7 +1212,7 @@ dependencies = [ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, { name = "colorama", marker = "python_full_version < '3.8' and sys_platform == 'win32'" }, { name = "ghp-import", marker = "python_full_version < '3.8'" }, - { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, { name = "jinja2", marker = "python_full_version < '3.8'" }, { name = "markdown", version = "3.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, { name = "markupsafe", version = "2.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, @@ -1155,7 +1235,13 @@ name = "mkdocs" version = "1.6.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", "python_full_version == '3.8.*'", ] @@ -1164,7 +1250,8 @@ dependencies = [ { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "colorama", marker = "python_full_version >= '3.8' and sys_platform == 'win32'" }, { name = "ghp-import", marker = "python_full_version >= '3.8'" }, - { name = "importlib-metadata", marker = "python_full_version >= '3.8' and python_full_version < '3.10'" }, + { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "jinja2", marker = "python_full_version >= '3.8'" }, { name = "markdown", version = "3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, @@ -1225,7 +1312,13 @@ name = "mkdocs-autorefs" version = "1.4.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ @@ -1244,7 +1337,8 @@ name = "mkdocs-get-deps" version = "0.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata", marker = "python_full_version >= '3.8' and python_full_version < '3.10'" }, + { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "mergedeep", marker = "python_full_version >= '3.8'" }, { name = "platformdirs", version = "4.3.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, @@ -1283,10 +1377,16 @@ wheels = [ [[package]] name = "mkdocs-material" -version = "9.7.2" +version = "9.7.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", "python_full_version == '3.8.*'", ] @@ -1308,9 +1408,9 @@ dependencies = [ { name = "requests", version = "2.32.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/57/5d3c8c9e2ff9d66dc8f63aa052eb0bac5041fecff7761d8689fe65c39c13/mkdocs_material-9.7.2.tar.gz", hash = "sha256:6776256552290b9b7a7aa002780e25b1e04bc9c3a8516b6b153e82e16b8384bd", size = 4097818, upload-time = "2026-02-18T15:53:07.763Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/b4/f900fcb8e6f510241e334ca401eddcb61ed880fb6572f7f32e4228472ca1/mkdocs_material-9.7.3.tar.gz", hash = "sha256:e5f0a18319699da7e78c35e4a8df7e93537a888660f61a86bd773a7134798f22", size = 4097748, upload-time = "2026-02-24T12:06:22.646Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/19/d194e75e82282b1d688f0720e21b5ac250ed64ddea333a228aaf83105f2e/mkdocs_material-9.7.2-py3-none-any.whl", hash = "sha256:9bf6f53452d4a4d527eac3cef3f92b7b6fc4931c55d57766a7d87890d47e1b92", size = 9305052, upload-time = "2026-02-18T15:53:05.221Z" }, + { url = "https://files.pythonhosted.org/packages/b9/1b/16ad0193079bb8a15aa1d2620813a9cd15b18de150a4ea1b2c607fb4c74d/mkdocs_material-9.7.3-py3-none-any.whl", hash = "sha256:37ebf7b4788c992203faf2e71900be3c197c70a4be9b0d72aed537b08a91dd9d", size = 9305078, upload-time = "2026-02-24T12:06:19.155Z" }, ] [[package]] @@ -1330,7 +1430,13 @@ name = "mkdocs-material-extensions" version = "1.3.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", "python_full_version == '3.8.*'", ] @@ -1347,7 +1453,7 @@ resolution-markers = [ "python_full_version < '3.8'", ] dependencies = [ - { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, { name = "jinja2", marker = "python_full_version < '3.8'" }, { name = "markdown", version = "3.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, { name = "markupsafe", version = "2.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, @@ -1375,7 +1481,7 @@ resolution-markers = [ ] dependencies = [ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, - { name = "importlib-metadata", marker = "python_full_version == '3.8.*'" }, + { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, { name = "jinja2", marker = "python_full_version == '3.8.*'" }, { name = "markdown", version = "3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, { name = "markupsafe", version = "2.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, @@ -1403,7 +1509,7 @@ resolution-markers = [ "python_full_version == '3.9.*'", ] dependencies = [ - { name = "importlib-metadata", marker = "python_full_version == '3.9.*'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "jinja2", marker = "python_full_version == '3.9.*'" }, { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "markupsafe", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, @@ -1426,7 +1532,13 @@ name = "mkdocstrings" version = "1.0.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", ] dependencies = [ { name = "jinja2", marker = "python_full_version >= '3.10'" }, @@ -1443,7 +1555,7 @@ wheels = [ [package.optional-dependencies] python = [ - { name = "mkdocstrings-python", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mkdocstrings-python", version = "2.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] [[package]] @@ -1499,20 +1611,312 @@ wheels = [ [[package]] name = "mkdocstrings-python" -version = "2.0.2" +version = "2.0.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", ] dependencies = [ - { name = "griffe", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "griffelib", marker = "python_full_version >= '3.10'" }, { name = "mkdocs-autorefs", version = "1.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "mkdocstrings", version = "1.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/84/78243847ad9d5c21d30a2842720425b17e880d99dfe824dee11d6b2149b4/mkdocstrings_python-2.0.2.tar.gz", hash = "sha256:4a32ccfc4b8d29639864698e81cfeb04137bce76bb9f3c251040f55d4b6e1ad8", size = 199124, upload-time = "2026-02-09T15:12:01.543Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/33/c225eaf898634bdda489a6766fc35d1683c640bffe0e0acd10646b13536d/mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8", size = 199083, upload-time = "2026-02-20T10:38:36.368Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/31/7ee938abbde2322e553a2cb5f604cdd1e4728e08bba39c7ee6fae9af840b/mkdocstrings_python-2.0.2-py3-none-any.whl", hash = "sha256:31241c0f43d85a69306d704d5725786015510ea3f3c4bdfdb5a5731d83cdc2b0", size = 104900, upload-time = "2026-02-09T15:12:00.166Z" }, + { url = "https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12", size = 104779, upload-time = "2026-02-20T10:38:34.517Z" }, +] + +[[package]] +name = "numpy" +version = "1.21.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/45/b7/de7b8e67f2232c26af57c205aaad29fe17754f793404f59c8a730c7a191a/numpy-1.21.6.zip", hash = "sha256:ecb55251139706669fdec2ff073c98ef8e9a84473e51e716211b41aa0f18e656", size = 10274544, upload-time = "2022-04-12T15:23:55.653Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/c6/05ae3c7f75b596e1bb3d78131c331eada9376a03d1af9801bd40e4675023/numpy-1.21.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8737609c3bbdd48e380d463134a35ffad3b22dc56295eff6f79fd85bd0eeeb25", size = 27203123, upload-time = "2022-04-12T14:48:15.695Z" }, + { url = "https://files.pythonhosted.org/packages/4a/72/a3379f83172f1431d7949138373e3a24beed68184c9362dab1b4d465be26/numpy-1.21.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fdffbfb6832cd0b300995a2b08b8f6fa9f6e856d562800fea9182316d99c4e8e", size = 16965603, upload-time = "2022-04-12T14:48:41.371Z" }, + { url = "https://files.pythonhosted.org/packages/26/e7/4a6f579af8186372b03e8480e47df309520d91cfead8759b64dd5ac62688/numpy-1.21.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3820724272f9913b597ccd13a467cc492a0da6b05df26ea09e78b171a0bb9da6", size = 12364595, upload-time = "2022-04-12T14:49:01.002Z" }, + { url = "https://files.pythonhosted.org/packages/57/ba/d8cbdfd507b541bb247beff24d9d7304ac8ffc379cf585701187d45d4512/numpy-1.21.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f17e562de9edf691a42ddb1eb4a5541c20dd3f9e65b09ded2beb0799c0cf29bb", size = 13019278, upload-time = "2022-04-12T14:49:20.896Z" }, + { url = "https://files.pythonhosted.org/packages/6f/7b/036000a55680e6c7eb81502b0aa27ce0ed65d4d8805613909967d9f8baf6/numpy-1.21.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f30427731561ce75d7048ac254dbe47a2ba576229250fb60f0fb74db96501a1", size = 15906004, upload-time = "2022-04-12T14:49:44.734Z" }, + { url = "https://files.pythonhosted.org/packages/b0/77/ff8bbe56ff6cbbdbdb8a641c67cee61e29b2e8bfbb18732c2e1d2961fe4d/numpy-1.21.6-cp310-cp310-win32.whl", hash = "sha256:d4bf4d43077db55589ffc9009c0ba0a94fa4908b9586d6ccce2e0b164c86303c", size = 11706182, upload-time = "2022-04-12T14:50:03.227Z" }, + { url = "https://files.pythonhosted.org/packages/ec/03/93702ca9c4bd61791e46c80ff1f24943febb2317484cf7e8207688bbbd95/numpy-1.21.6-cp310-cp310-win_amd64.whl", hash = "sha256:d136337ae3cc69aa5e447e78d8e1514be8c3ec9b54264e680cf0b4bd9011574f", size = 14008913, upload-time = "2022-04-12T14:50:24.392Z" }, + { url = "https://files.pythonhosted.org/packages/32/dd/43d8b2b2ebf424f6555271a4c9f5b50dc3cc0aafa66c72b4d36863f71358/numpy-1.21.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6aaf96c7f8cebc220cdfc03f1d5a31952f027dda050e5a703a0d1c396075e3e7", size = 16894122, upload-time = "2022-04-12T14:50:50.66Z" }, + { url = "https://files.pythonhosted.org/packages/06/78/b184f13f5461812a17a90b380d70a93fa3532460f0af9d72b0d93d8bc4ff/numpy-1.21.6-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:67c261d6c0a9981820c3a149d255a76918278a6b03b6a036800359aba1256d46", size = 13670697, upload-time = "2022-04-12T14:51:11.878Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ad/ff3b21ebfe79a4d25b4a4f8e5cf9fd44a204adb6b33c09010f566f51027a/numpy-1.21.6-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a6be4cb0ef3b8c9250c19cc122267263093eee7edd4e3fa75395dfda8c17a8e2", size = 15702369, upload-time = "2022-04-12T14:51:35.292Z" }, + { url = "https://files.pythonhosted.org/packages/b7/0d/86662f93102e42545cdf031da4fddf0ace9030ec67478932a628afc5973b/numpy-1.21.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c4068a8c44014b2d55f3c3f574c376b2494ca9cc73d2f1bd692382b6dffe3db", size = 12974170, upload-time = "2022-04-12T14:51:55.493Z" }, + { url = "https://files.pythonhosted.org/packages/cd/eb/f6f3258e7b0e0cc5c327778312bf4ee4978c8514aa28e97119ee206f6e60/numpy-1.21.6-cp37-cp37m-win32.whl", hash = "sha256:7c7e5fa88d9ff656e067876e4736379cc962d185d5cd808014a8a928d529ef4e", size = 11680505, upload-time = "2022-04-12T14:52:14.056Z" }, + { url = "https://files.pythonhosted.org/packages/97/9f/da37cc4a188a1d5d203d65ab28d6504e17594b5342e0c1dc5610ee6f4535/numpy-1.21.6-cp37-cp37m-win_amd64.whl", hash = "sha256:bcb238c9c96c00d3085b264e5c1a1207672577b93fa666c3b14a45240b14123a", size = 13969236, upload-time = "2022-04-12T14:52:35.45Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e2/b2df1f664d644e690b40179fc0a07c163c6decf986c7adee8a85a094e8ce/numpy-1.21.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:82691fda7c3f77c90e62da69ae60b5ac08e87e775b09813559f8901a88266552", size = 27127297, upload-time = "2022-04-12T14:53:15Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d4/be63d2bed7d10f443dee42469623326b6bc51c9e5cd096ebb7227bca456f/numpy-1.21.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:643843bcc1c50526b3a71cd2ee561cf0d8773f062c8cbaf9ffac9fdf573f83ab", size = 16926225, upload-time = "2022-04-12T14:53:40.562Z" }, + { url = "https://files.pythonhosted.org/packages/0d/21/036363516c06737135ee58741e9c0af4899348ce3c5f5e04379240edd090/numpy-1.21.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:357768c2e4451ac241465157a3e929b265dfac85d9214074985b1786244f2ef3", size = 12329630, upload-time = "2022-04-12T14:53:59.949Z" }, + { url = "https://files.pythonhosted.org/packages/6a/52/a1dcf14b8e81d49c14112663290ee2ed545bd04988170138284a613bd926/numpy-1.21.6-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9f411b2c3f3d76bba0865b35a425157c5dcf54937f82bbeb3d3c180789dd66a6", size = 13719672, upload-time = "2022-04-12T14:54:20.7Z" }, + { url = "https://files.pythonhosted.org/packages/d5/43/e88bb1fb7d040ae8e0e06e749341b13f57701aab11fe9d71c99af6202c5c/numpy-1.21.6-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4aa48afdce4660b0076a00d80afa54e8a97cd49f457d68a4342d188a09451c1a", size = 15749422, upload-time = "2022-04-12T14:54:44.87Z" }, + { url = "https://files.pythonhosted.org/packages/86/c7/3f68d0a8dcc9458879c614707e6ffaf64a108664cfbba9702d3ba7ca4c82/numpy-1.21.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a96eef20f639e6a97d23e57dd0c1b1069a7b4fd7027482a4c5c451cd7732f4", size = 13026205, upload-time = "2022-04-12T14:55:05.182Z" }, + { url = "https://files.pythonhosted.org/packages/6f/47/453023bd298f8b0be092d8a8bdd4b21f87a8c639ecb724a94cd75e23d216/numpy-1.21.6-cp38-cp38-win32.whl", hash = "sha256:5c3c8def4230e1b959671eb959083661b4a0d2e9af93ee339c7dada6759a9470", size = 11707551, upload-time = "2022-04-12T14:55:23.823Z" }, + { url = "https://files.pythonhosted.org/packages/48/5f/db4550e1c68206814a577ebd92c0dd082f3628fd7fc96725d44a521b0c92/numpy-1.21.6-cp38-cp38-win_amd64.whl", hash = "sha256:bf2ec4b75d0e9356edea834d1de42b31fe11f726a81dfb2c2112bc1eaa508fcf", size = 14009785, upload-time = "2022-04-12T14:55:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/83/eb/a6a0d7fc8e718776c5c710692ea027607104710cba813c4b869182179334/numpy-1.21.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4391bd07606be175aafd267ef9bea87cf1b8210c787666ce82073b05f202add1", size = 27202394, upload-time = "2022-04-12T14:56:24.193Z" }, + { url = "https://files.pythonhosted.org/packages/4c/62/07402945bd5d5cf515a5f0cbc7263abf02ec0ddf3b19fbdc4af7537cd4d0/numpy-1.21.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:67f21981ba2f9d7ba9ade60c9e8cbaa8cf8e9ae51673934480e45cf55e953673", size = 16965163, upload-time = "2022-04-12T14:56:50.429Z" }, + { url = "https://files.pythonhosted.org/packages/44/56/041e886b4a8da813b7ec297c270fb3582d2ae8b7f33e106eb5c7a5e9184c/numpy-1.21.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee5ec40fdd06d62fe5d4084bef4fd50fd4bb6bfd2bf519365f569dc470163ab0", size = 12364236, upload-time = "2022-04-12T14:57:09.317Z" }, + { url = "https://files.pythonhosted.org/packages/61/f4/f01a8989e53a437ad660ab86c91514bec3d5067393e4a844b259f5a103de/numpy-1.21.6-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1dbe1c91269f880e364526649a52eff93ac30035507ae980d2fed33aaee633ac", size = 13721583, upload-time = "2022-04-12T14:57:30.408Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f2/0bdcf2c40ef144cbbc9e0947eea831a145a98b0e5f8438fc09cf7fda0b35/numpy-1.21.6-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d9caa9d5e682102453d96a0ee10c7241b72859b01a941a397fd965f23b3e016b", size = 15734025, upload-time = "2022-04-12T14:57:54.442Z" }, + { url = "https://files.pythonhosted.org/packages/76/7f/830cf169eede1b855538f962e3a70c31755db6423652695b813ed04ff54e/numpy-1.21.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58459d3bad03343ac4b1b42ed14d571b8743dc80ccbf27444f266729df1d6f5b", size = 13020515, upload-time = "2022-04-12T14:58:14.213Z" }, + { url = "https://files.pythonhosted.org/packages/1b/b5/7178d5a22427a9195ac69d6ec150415734f7a7a19d1142f82b89ead1dac4/numpy-1.21.6-cp39-cp39-win32.whl", hash = "sha256:7f5ae4f304257569ef3b948810816bc87c9146e8c446053539947eedeaa32786", size = 11706846, upload-time = "2022-04-12T14:58:32.433Z" }, + { url = "https://files.pythonhosted.org/packages/4d/04/bcd62448f2e772bc90a73ba21bacaa19817ae9905ae639969462862bd071/numpy-1.21.6-cp39-cp39-win_amd64.whl", hash = "sha256:e31f0bb5928b793169b87e3d1e070f2342b22d5245c755e2b81caa29756246c3", size = 14008965, upload-time = "2022-04-12T14:58:53.527Z" }, + { url = "https://files.pythonhosted.org/packages/2e/5a/6f3e280a10de48395053a559bfcb3b2221b74b57d062c1d6307fc965f549/numpy-1.21.6-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd1c8f6bd65d07d3810b90d02eba7997e32abbdf1277a481d698969e921a3be0", size = 15159527, upload-time = "2022-04-12T14:59:16.193Z" }, +] + +[[package]] +name = "numpy" +version = "1.24.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/9b/027bec52c633f6556dba6b722d9a0befb40498b9ceddd29cbe67a45a127c/numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463", size = 10911229, upload-time = "2023-06-26T13:39:33.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/80/6cdfb3e275d95155a34659163b83c09e3a3ff9f1456880bec6cc63d71083/numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64", size = 19789140, upload-time = "2023-06-26T13:22:33.184Z" }, + { url = "https://files.pythonhosted.org/packages/64/5f/3f01d753e2175cfade1013eea08db99ba1ee4bdb147ebcf3623b75d12aa7/numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1", size = 13854297, upload-time = "2023-06-26T13:22:59.541Z" }, + { url = "https://files.pythonhosted.org/packages/5a/b3/2f9c21d799fa07053ffa151faccdceeb69beec5a010576b8991f614021f7/numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4", size = 13995611, upload-time = "2023-06-26T13:23:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/10/be/ae5bf4737cb79ba437879915791f6f26d92583c738d7d960ad94e5c36adf/numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6", size = 17282357, upload-time = "2023-06-26T13:23:51.446Z" }, + { url = "https://files.pythonhosted.org/packages/c0/64/908c1087be6285f40e4b3e79454552a701664a079321cff519d8c7051d06/numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc", size = 12429222, upload-time = "2023-06-26T13:24:13.849Z" }, + { url = "https://files.pythonhosted.org/packages/22/55/3d5a7c1142e0d9329ad27cece17933b0e2ab4e54ddc5c1861fbfeb3f7693/numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e", size = 14841514, upload-time = "2023-06-26T13:24:38.129Z" }, + { url = "https://files.pythonhosted.org/packages/a9/cc/5ed2280a27e5dab12994c884f1f4d8c3bd4d885d02ae9e52a9d213a6a5e2/numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810", size = 19775508, upload-time = "2023-06-26T13:25:08.882Z" }, + { url = "https://files.pythonhosted.org/packages/c0/bc/77635c657a3668cf652806210b8662e1aff84b818a55ba88257abf6637a8/numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254", size = 13840033, upload-time = "2023-06-26T13:25:33.417Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4c/96cdaa34f54c05e97c1c50f39f98d608f96f0677a6589e64e53104e22904/numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7", size = 13991951, upload-time = "2023-06-26T13:25:55.725Z" }, + { url = "https://files.pythonhosted.org/packages/22/97/dfb1a31bb46686f09e68ea6ac5c63fdee0d22d7b23b8f3f7ea07712869ef/numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5", size = 17278923, upload-time = "2023-06-26T13:26:25.658Z" }, + { url = "https://files.pythonhosted.org/packages/35/e2/76a11e54139654a324d107da1d98f99e7aa2a7ef97cfd7c631fba7dbde71/numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d", size = 12422446, upload-time = "2023-06-26T13:26:49.302Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ec/ebef2f7d7c28503f958f0f8b992e7ce606fb74f9e891199329d5f5f87404/numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694", size = 14834466, upload-time = "2023-06-26T13:27:16.029Z" }, + { url = "https://files.pythonhosted.org/packages/11/10/943cfb579f1a02909ff96464c69893b1d25be3731b5d3652c2e0cf1281ea/numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61", size = 19780722, upload-time = "2023-06-26T13:27:49.573Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ae/f53b7b265fdc701e663fbb322a8e9d4b14d9cb7b2385f45ddfabfc4327e4/numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f", size = 13843102, upload-time = "2023-06-26T13:28:12.288Z" }, + { url = "https://files.pythonhosted.org/packages/25/6f/2586a50ad72e8dbb1d8381f837008a0321a3516dfd7cb57fc8cf7e4bb06b/numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e", size = 14039616, upload-time = "2023-06-26T13:28:35.659Z" }, + { url = "https://files.pythonhosted.org/packages/98/5d/5738903efe0ecb73e51eb44feafba32bdba2081263d40c5043568ff60faf/numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc", size = 17316263, upload-time = "2023-06-26T13:29:09.272Z" }, + { url = "https://files.pythonhosted.org/packages/d1/57/8d328f0b91c733aa9aa7ee540dbc49b58796c862b4fbcb1146c701e888da/numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2", size = 12455660, upload-time = "2023-06-26T13:29:33.434Z" }, + { url = "https://files.pythonhosted.org/packages/69/65/0d47953afa0ad569d12de5f65d964321c208492064c38fe3b0b9744f8d44/numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706", size = 14868112, upload-time = "2023-06-26T13:29:58.385Z" }, + { url = "https://files.pythonhosted.org/packages/9a/cd/d5b0402b801c8a8b56b04c1e85c6165efab298d2f0ab741c2406516ede3a/numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400", size = 19816549, upload-time = "2023-06-26T13:30:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/14/27/638aaa446f39113a3ed38b37a66243e21b38110d021bfcb940c383e120f2/numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f", size = 13879950, upload-time = "2023-06-26T13:31:01.787Z" }, + { url = "https://files.pythonhosted.org/packages/8f/27/91894916e50627476cff1a4e4363ab6179d01077d71b9afed41d9e1f18bf/numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9", size = 14030228, upload-time = "2023-06-26T13:31:26.696Z" }, + { url = "https://files.pythonhosted.org/packages/7a/7c/d7b2a0417af6428440c0ad7cb9799073e507b1a465f827d058b826236964/numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d", size = 17311170, upload-time = "2023-06-26T13:31:56.615Z" }, + { url = "https://files.pythonhosted.org/packages/18/9d/e02ace5d7dfccee796c37b995c63322674daf88ae2f4a4724c5dd0afcc91/numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835", size = 12454918, upload-time = "2023-06-26T13:32:16.8Z" }, + { url = "https://files.pythonhosted.org/packages/63/38/6cc19d6b8bfa1d1a459daf2b3fe325453153ca7019976274b6f33d8b5663/numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8", size = 14867441, upload-time = "2023-06-26T13:32:40.521Z" }, + { url = "https://files.pythonhosted.org/packages/a4/fd/8dff40e25e937c94257455c237b9b6bf5a30d42dd1cc11555533be099492/numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef", size = 19156590, upload-time = "2023-06-26T13:33:10.36Z" }, + { url = "https://files.pythonhosted.org/packages/42/e7/4bf953c6e05df90c6d351af69966384fed8e988d0e8c54dad7103b59f3ba/numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a", size = 16705744, upload-time = "2023-06-26T13:33:36.703Z" }, + { url = "https://files.pythonhosted.org/packages/fc/dd/9106005eb477d022b60b3817ed5937a43dad8fd1f20b0610ea8a32fcb407/numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2", size = 14734290, upload-time = "2023-06-26T13:34:05.409Z" }, +] + +[[package]] +name = "numpy" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015, upload-time = "2024-08-26T20:19:40.945Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245, upload-time = "2024-08-26T20:04:14.625Z" }, + { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540, upload-time = "2024-08-26T20:04:36.784Z" }, + { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623, upload-time = "2024-08-26T20:04:46.491Z" }, + { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774, upload-time = "2024-08-26T20:04:58.173Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081, upload-time = "2024-08-26T20:05:19.098Z" }, + { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451, upload-time = "2024-08-26T20:05:47.479Z" }, + { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572, upload-time = "2024-08-26T20:06:17.137Z" }, + { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722, upload-time = "2024-08-26T20:06:39.16Z" }, + { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170, upload-time = "2024-08-26T20:06:50.361Z" }, + { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558, upload-time = "2024-08-26T20:07:13.881Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137, upload-time = "2024-08-26T20:07:45.345Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552, upload-time = "2024-08-26T20:08:06.666Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957, upload-time = "2024-08-26T20:08:15.83Z" }, + { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573, upload-time = "2024-08-26T20:08:27.185Z" }, + { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330, upload-time = "2024-08-26T20:08:48.058Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895, upload-time = "2024-08-26T20:09:16.536Z" }, + { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253, upload-time = "2024-08-26T20:09:46.263Z" }, + { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074, upload-time = "2024-08-26T20:10:08.483Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640, upload-time = "2024-08-26T20:10:19.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230, upload-time = "2024-08-26T20:10:43.413Z" }, + { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803, upload-time = "2024-08-26T20:11:13.916Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835, upload-time = "2024-08-26T20:11:34.779Z" }, + { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499, upload-time = "2024-08-26T20:11:43.902Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497, upload-time = "2024-08-26T20:11:55.09Z" }, + { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158, upload-time = "2024-08-26T20:12:14.95Z" }, + { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173, upload-time = "2024-08-26T20:12:44.049Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174, upload-time = "2024-08-26T20:13:13.634Z" }, + { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701, upload-time = "2024-08-26T20:13:34.851Z" }, + { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313, upload-time = "2024-08-26T20:13:45.653Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179, upload-time = "2024-08-26T20:14:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942, upload-time = "2024-08-26T20:14:40.108Z" }, + { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512, upload-time = "2024-08-26T20:15:00.985Z" }, + { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976, upload-time = "2024-08-26T20:15:10.876Z" }, + { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494, upload-time = "2024-08-26T20:15:22.055Z" }, + { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596, upload-time = "2024-08-26T20:15:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099, upload-time = "2024-08-26T20:16:11.048Z" }, + { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823, upload-time = "2024-08-26T20:16:40.171Z" }, + { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424, upload-time = "2024-08-26T20:17:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809, upload-time = "2024-08-26T20:17:13.553Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314, upload-time = "2024-08-26T20:17:36.72Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288, upload-time = "2024-08-26T20:18:07.732Z" }, + { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793, upload-time = "2024-08-26T20:18:19.125Z" }, + { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885, upload-time = "2024-08-26T20:18:47.237Z" }, + { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784, upload-time = "2024-08-26T20:19:11.19Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +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]] @@ -1532,7 +1936,13 @@ name = "packaging" version = "26.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", "python_full_version == '3.8.*'", ] @@ -1550,6 +1960,188 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, ] +[[package]] +name = "pandas" +version = "2.0.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "python-dateutil", marker = "python_full_version == '3.8.*'" }, + { name = "pytz", marker = "python_full_version == '3.8.*'" }, + { name = "tzdata", marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/a7/824332581e258b5aa4f3763ecb2a797e5f9a54269044ba2e50ac19936b32/pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c", size = 5284455, upload-time = "2023-06-28T23:19:33.371Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/b2/0d4a5729ce1ce11630c4fc5d5522a33b967b3ca146c210f58efde7c40e99/pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8", size = 11760908, upload-time = "2023-06-28T23:15:57.001Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f6/f620ca62365d83e663a255a41b08d2fc2eaf304e0b8b21bb6d62a7390fe3/pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f", size = 10823486, upload-time = "2023-06-28T23:16:06.863Z" }, + { url = "https://files.pythonhosted.org/packages/c2/59/cb4234bc9b968c57e81861b306b10cd8170272c57b098b724d3de5eda124/pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183", size = 11571897, upload-time = "2023-06-28T23:16:14.208Z" }, + { url = "https://files.pythonhosted.org/packages/e3/59/35a2892bf09ded9c1bf3804461efe772836a5261ef5dfb4e264ce813ff99/pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0", size = 12306421, upload-time = "2023-06-28T23:16:23.26Z" }, + { url = "https://files.pythonhosted.org/packages/94/71/3a0c25433c54bb29b48e3155b959ac78f4c4f2f06f94d8318aac612cb80f/pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210", size = 9540792, upload-time = "2023-06-28T23:16:30.876Z" }, + { url = "https://files.pythonhosted.org/packages/ed/30/b97456e7063edac0e5a405128065f0cd2033adfe3716fb2256c186bd41d0/pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e", size = 10664333, upload-time = "2023-06-28T23:16:39.209Z" }, + { url = "https://files.pythonhosted.org/packages/b3/92/a5e5133421b49e901a12e02a6a7ef3a0130e10d13db8cb657fdd0cba3b90/pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8", size = 11645672, upload-time = "2023-06-28T23:16:47.601Z" }, + { url = "https://files.pythonhosted.org/packages/8f/bb/aea1fbeed5b474cb8634364718abe9030d7cc7a30bf51f40bd494bbc89a2/pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26", size = 10693229, upload-time = "2023-06-28T23:16:56.397Z" }, + { url = "https://files.pythonhosted.org/packages/d6/90/e7d387f1a416b14e59290baa7a454a90d719baebbf77433ff1bdcc727800/pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d", size = 11581591, upload-time = "2023-06-28T23:17:04.234Z" }, + { url = "https://files.pythonhosted.org/packages/d0/28/88b81881c056376254618fad622a5e94b5126db8c61157ea1910cd1c040a/pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df", size = 12219370, upload-time = "2023-06-28T23:17:11.783Z" }, + { url = "https://files.pythonhosted.org/packages/e4/a5/212b9039e25bf8ebb97e417a96660e3dc925dacd3f8653d531b8f7fd9be4/pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd", size = 9482935, upload-time = "2023-06-28T23:17:21.376Z" }, + { url = "https://files.pythonhosted.org/packages/9e/71/756a1be6bee0209d8c0d8c5e3b9fc72c00373f384a4017095ec404aec3ad/pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b", size = 10607692, upload-time = "2023-06-28T23:17:28.824Z" }, + { url = "https://files.pythonhosted.org/packages/78/a8/07dd10f90ca915ed914853cd57f79bfc22e1ef4384ab56cb4336d2fc1f2a/pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061", size = 11653303, upload-time = "2023-06-28T23:17:36.329Z" }, + { url = "https://files.pythonhosted.org/packages/53/c3/f8e87361f7fdf42012def602bfa2a593423c729f5cb7c97aed7f51be66ac/pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5", size = 10710932, upload-time = "2023-06-28T23:17:49.875Z" }, + { url = "https://files.pythonhosted.org/packages/a7/87/828d50c81ce0f434163bf70b925a0eec6076808e0bca312a79322b141f66/pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089", size = 11684018, upload-time = "2023-06-28T23:18:05.845Z" }, + { url = "https://files.pythonhosted.org/packages/f8/7f/5b047effafbdd34e52c9e2d7e44f729a0655efafb22198c45cf692cdc157/pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0", size = 12353723, upload-time = "2023-06-28T23:18:17.631Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ae/26a2eda7fa581347d69e51f93892493b2074ef3352ac71033c9f32c52389/pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02", size = 9646403, upload-time = "2023-06-28T23:18:24.328Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/ea362eef61f05553aaf1a24b3e96b2d0603f5dc71a3bd35688a24ed88843/pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78", size = 10777638, upload-time = "2023-06-28T23:18:30.947Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c7/cfef920b7b457dff6928e824896cb82367650ea127d048ee0b820026db4f/pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b", size = 11834160, upload-time = "2023-06-28T23:18:40.332Z" }, + { url = "https://files.pythonhosted.org/packages/6c/1c/689c9d99bc4e5d366a5fd871f0bcdee98a6581e240f96b78d2d08f103774/pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e", size = 10862752, upload-time = "2023-06-28T23:18:50.016Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b8/4d082f41c27c95bf90485d1447b647cc7e5680fea75e315669dc6e4cb398/pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b", size = 11715852, upload-time = "2023-06-28T23:19:00.594Z" }, + { url = "https://files.pythonhosted.org/packages/9e/0d/91a9fd2c202f2b1d97a38ab591890f86480ecbb596cbc56d035f6f23fdcc/pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641", size = 12398496, upload-time = "2023-06-28T23:19:11.78Z" }, + { url = "https://files.pythonhosted.org/packages/26/7d/d8aa0a2c4f3f5f8ea59fb946c8eafe8f508090ca73e2b08a9af853c1103e/pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682", size = 9630766, upload-time = "2023-06-28T23:19:18.182Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f2/0ad053856debbe90c83de1b4f05915f85fd2146f20faf9daa3b320d36df3/pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc", size = 10755902, upload-time = "2023-06-28T23:19:25.151Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "pytz", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "tzdata", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, +] +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/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/56/b4/52eeb530a99e2a4c55ffcd352772b599ed4473a0f892d127f4147cf0f88e/pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2", size = 11567720, upload-time = "2025-09-29T23:33:06.209Z" }, + { url = "https://files.pythonhosted.org/packages/48/4a/2d8b67632a021bced649ba940455ed441ca854e57d6e7658a6024587b083/pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8", size = 10810302, upload-time = "2025-09-29T23:33:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/13/e6/d2465010ee0569a245c975dc6967b801887068bc893e908239b1f4b6c1ac/pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff", size = 12154874, upload-time = "2025-09-29T23:33:49.939Z" }, + { url = "https://files.pythonhosted.org/packages/1f/18/aae8c0aa69a386a3255940e9317f793808ea79d0a525a97a903366bb2569/pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29", size = 12790141, upload-time = "2025-09-29T23:34:05.655Z" }, + { url = "https://files.pythonhosted.org/packages/f7/26/617f98de789de00c2a444fbe6301bb19e66556ac78cff933d2c98f62f2b4/pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73", size = 13208697, upload-time = "2025-09-29T23:34:21.835Z" }, + { url = "https://files.pythonhosted.org/packages/b9/fb/25709afa4552042bd0e15717c75e9b4a2294c3dc4f7e6ea50f03c5136600/pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9", size = 13879233, upload-time = "2025-09-29T23:34:35.079Z" }, + { url = "https://files.pythonhosted.org/packages/98/af/7be05277859a7bc399da8ba68b88c96b27b48740b6cf49688899c6eb4176/pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa", size = 11359119, upload-time = "2025-09-29T23:34:46.339Z" }, +] + +[[package]] +name = "pandas" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, + { name = "tzdata", marker = "(python_full_version >= '3.11' and sys_platform == 'emscripten') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/0c/b28ed414f080ee0ad153f848586d61d1878f91689950f037f976ce15f6c8/pandas-3.0.1.tar.gz", hash = "sha256:4186a699674af418f655dbd420ed87f50d56b4cd6603784279d9eef6627823c8", size = 4641901, upload-time = "2026-02-17T22:20:16.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/07/c7087e003ceee9b9a82539b40414ec557aa795b584a1a346e89180853d79/pandas-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de09668c1bf3b925c07e5762291602f0d789eca1b3a781f99c1c78f6cac0e7ea", size = 10323380, upload-time = "2026-02-17T22:18:16.133Z" }, + { url = "https://files.pythonhosted.org/packages/c1/27/90683c7122febeefe84a56f2cde86a9f05f68d53885cebcc473298dfc33e/pandas-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:24ba315ba3d6e5806063ac6eb717504e499ce30bd8c236d8693a5fd3f084c796", size = 9923455, upload-time = "2026-02-17T22:18:19.13Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f1/ed17d927f9950643bc7631aa4c99ff0cc83a37864470bc419345b656a41f/pandas-3.0.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:406ce835c55bac912f2a0dcfaf27c06d73c6b04a5dde45f1fd3169ce31337389", size = 10753464, upload-time = "2026-02-17T22:18:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/2e/7c/870c7e7daec2a6c7ff2ac9e33b23317230d4e4e954b35112759ea4a924a7/pandas-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:830994d7e1f31dd7e790045235605ab61cff6c94defc774547e8b7fdfbff3dc7", size = 11255234, upload-time = "2026-02-17T22:18:24.175Z" }, + { url = "https://files.pythonhosted.org/packages/5c/39/3653fe59af68606282b989c23d1a543ceba6e8099cbcc5f1d506a7bae2aa/pandas-3.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a64ce8b0f2de1d2efd2ae40b0abe7f8ae6b29fbfb3812098ed5a6f8e235ad9bf", size = 11767299, upload-time = "2026-02-17T22:18:26.824Z" }, + { url = "https://files.pythonhosted.org/packages/9b/31/1daf3c0c94a849c7a8dab8a69697b36d313b229918002ba3e409265c7888/pandas-3.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9832c2c69da24b602c32e0c7b1b508a03949c18ba08d4d9f1c1033426685b447", size = 12333292, upload-time = "2026-02-17T22:18:28.996Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/af63f83cd6ca603a00fe8530c10a60f0879265b8be00b5930e8e78c5b30b/pandas-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:84f0904a69e7365f79a0c77d3cdfccbfb05bf87847e3a51a41e1426b0edb9c79", size = 9892176, upload-time = "2026-02-17T22:18:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/79/ab/9c776b14ac4b7b4140788eca18468ea39894bc7340a408f1d1e379856a6b/pandas-3.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:4a68773d5a778afb31d12e34f7dd4612ab90de8c6fb1d8ffe5d4a03b955082a1", size = 9151328, upload-time = "2026-02-17T22:18:35.721Z" }, + { url = "https://files.pythonhosted.org/packages/37/51/b467209c08dae2c624873d7491ea47d2b47336e5403309d433ea79c38571/pandas-3.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:476f84f8c20c9f5bc47252b66b4bb25e1a9fc2fa98cead96744d8116cb85771d", size = 10344357, upload-time = "2026-02-17T22:18:38.262Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f1/e2567ffc8951ab371db2e40b2fe068e36b81d8cf3260f06ae508700e5504/pandas-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0ab749dfba921edf641d4036c4c21c0b3ea70fea478165cb98a998fb2a261955", size = 9884543, upload-time = "2026-02-17T22:18:41.476Z" }, + { url = "https://files.pythonhosted.org/packages/d7/39/327802e0b6d693182403c144edacbc27eb82907b57062f23ef5a4c4a5ea7/pandas-3.0.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8e36891080b87823aff3640c78649b91b8ff6eea3c0d70aeabd72ea43ab069b", size = 10396030, upload-time = "2026-02-17T22:18:43.822Z" }, + { url = "https://files.pythonhosted.org/packages/3d/fe/89d77e424365280b79d99b3e1e7d606f5165af2f2ecfaf0c6d24c799d607/pandas-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:532527a701281b9dd371e2f582ed9094f4c12dd9ffb82c0c54ee28d8ac9520c4", size = 10876435, upload-time = "2026-02-17T22:18:45.954Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a6/2a75320849dd154a793f69c951db759aedb8d1dd3939eeacda9bdcfa1629/pandas-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:356e5c055ed9b0da1580d465657bc7d00635af4fd47f30afb23025352ba764d1", size = 11405133, upload-time = "2026-02-17T22:18:48.533Z" }, + { url = "https://files.pythonhosted.org/packages/58/53/1d68fafb2e02d7881df66aa53be4cd748d25cbe311f3b3c85c93ea5d30ca/pandas-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9d810036895f9ad6345b8f2a338dd6998a74e8483847403582cab67745bff821", size = 11932065, upload-time = "2026-02-17T22:18:50.837Z" }, + { url = "https://files.pythonhosted.org/packages/75/08/67cc404b3a966b6df27b38370ddd96b3b023030b572283d035181854aac5/pandas-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:536232a5fe26dd989bd633e7a0c450705fdc86a207fec7254a55e9a22950fe43", size = 9741627, upload-time = "2026-02-17T22:18:53.905Z" }, + { url = "https://files.pythonhosted.org/packages/86/4f/caf9952948fb00d23795f09b893d11f1cacb384e666854d87249530f7cbe/pandas-3.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f463ebfd8de7f326d38037c7363c6dacb857c5881ab8961fb387804d6daf2f7", size = 9052483, upload-time = "2026-02-17T22:18:57.31Z" }, + { url = "https://files.pythonhosted.org/packages/0b/48/aad6ec4f8d007534c091e9a7172b3ec1b1ee6d99a9cbb936b5eab6c6cf58/pandas-3.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5272627187b5d9c20e55d27caf5f2cd23e286aba25cadf73c8590e432e2b7262", size = 10317509, upload-time = "2026-02-17T22:18:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/a8/14/5990826f779f79148ae9d3a2c39593dc04d61d5d90541e71b5749f35af95/pandas-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:661e0f665932af88c7877f31da0dc743fe9c8f2524bdffe23d24fdcb67ef9d56", size = 9860561, upload-time = "2026-02-17T22:19:02.265Z" }, + { url = "https://files.pythonhosted.org/packages/fa/80/f01ff54664b6d70fed71475543d108a9b7c888e923ad210795bef04ffb7d/pandas-3.0.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75e6e292ff898679e47a2199172593d9f6107fd2dd3617c22c2946e97d5df46e", size = 10365506, upload-time = "2026-02-17T22:19:05.017Z" }, + { url = "https://files.pythonhosted.org/packages/f2/85/ab6d04733a7d6ff32bfc8382bf1b07078228f5d6ebec5266b91bfc5c4ff7/pandas-3.0.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ff8cf1d2896e34343197685f432450ec99a85ba8d90cce2030c5eee2ef98791", size = 10873196, upload-time = "2026-02-17T22:19:07.204Z" }, + { url = "https://files.pythonhosted.org/packages/48/a9/9301c83d0b47c23ac5deab91c6b39fd98d5b5db4d93b25df8d381451828f/pandas-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eca8b4510f6763f3d37359c2105df03a7a221a508f30e396a51d0713d462e68a", size = 11370859, upload-time = "2026-02-17T22:19:09.436Z" }, + { url = "https://files.pythonhosted.org/packages/59/fe/0c1fc5bd2d29c7db2ab372330063ad555fb83e08422829c785f5ec2176ca/pandas-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:06aff2ad6f0b94a17822cf8b83bbb563b090ed82ff4fe7712db2ce57cd50d9b8", size = 11924584, upload-time = "2026-02-17T22:19:11.562Z" }, + { url = "https://files.pythonhosted.org/packages/d6/7d/216a1588b65a7aa5f4535570418a599d943c85afb1d95b0876fc00aa1468/pandas-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fea306c783e28884c29057a1d9baa11a349bbf99538ec1da44c8476563d1b25", size = 9742769, upload-time = "2026-02-17T22:19:13.926Z" }, + { url = "https://files.pythonhosted.org/packages/c4/cb/810a22a6af9a4e97c8ab1c946b47f3489c5bca5adc483ce0ffc84c9cc768/pandas-3.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:a8d37a43c52917427e897cb2e429f67a449327394396a81034a4449b99afda59", size = 9043855, upload-time = "2026-02-17T22:19:16.09Z" }, + { url = "https://files.pythonhosted.org/packages/92/fa/423c89086cca1f039cf1253c3ff5b90f157b5b3757314aa635f6bf3e30aa/pandas-3.0.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d54855f04f8246ed7b6fc96b05d4871591143c46c0b6f4af874764ed0d2d6f06", size = 10752673, upload-time = "2026-02-17T22:19:18.304Z" }, + { url = "https://files.pythonhosted.org/packages/22/23/b5a08ec1f40020397f0faba72f1e2c11f7596a6169c7b3e800abff0e433f/pandas-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e1b677accee34a09e0dc2ce5624e4a58a1870ffe56fc021e9caf7f23cd7668f", size = 10404967, upload-time = "2026-02-17T22:19:20.726Z" }, + { url = "https://files.pythonhosted.org/packages/5c/81/94841f1bb4afdc2b52a99daa895ac2c61600bb72e26525ecc9543d453ebc/pandas-3.0.1-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9cabbdcd03f1b6cd254d6dda8ae09b0252524be1592594c00b7895916cb1324", size = 10320575, upload-time = "2026-02-17T22:19:24.919Z" }, + { url = "https://files.pythonhosted.org/packages/0a/8b/2ae37d66a5342a83adadfd0cb0b4bf9c3c7925424dd5f40d15d6cfaa35ee/pandas-3.0.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ae2ab1f166668b41e770650101e7090824fd34d17915dd9cd479f5c5e0065e9", size = 10710921, upload-time = "2026-02-17T22:19:27.181Z" }, + { url = "https://files.pythonhosted.org/packages/a2/61/772b2e2757855e232b7ccf7cb8079a5711becb3a97f291c953def15a833f/pandas-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6bf0603c2e30e2cafac32807b06435f28741135cb8697eae8b28c7d492fc7d76", size = 11334191, upload-time = "2026-02-17T22:19:29.411Z" }, + { url = "https://files.pythonhosted.org/packages/1b/08/b16c6df3ef555d8495d1d265a7963b65be166785d28f06a350913a4fac78/pandas-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c426422973973cae1f4a23e51d4ae85974f44871b24844e4f7de752dd877098", size = 11782256, upload-time = "2026-02-17T22:19:32.34Z" }, + { url = "https://files.pythonhosted.org/packages/55/80/178af0594890dee17e239fca96d3d8670ba0f5ff59b7d0439850924a9c09/pandas-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b03f91ae8c10a85c1613102c7bef5229b5379f343030a3ccefeca8a33414cf35", size = 10485047, upload-time = "2026-02-17T22:19:34.605Z" }, + { url = "https://files.pythonhosted.org/packages/bb/8b/4bb774a998b97e6c2fd62a9e6cfdaae133b636fd1c468f92afb4ae9a447a/pandas-3.0.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:99d0f92ed92d3083d140bf6b97774f9f13863924cf3f52a70711f4e7588f9d0a", size = 10322465, upload-time = "2026-02-17T22:19:36.803Z" }, + { url = "https://files.pythonhosted.org/packages/72/3a/5b39b51c64159f470f1ca3b1c2a87da290657ca022f7cd11442606f607d1/pandas-3.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3b66857e983208654294bb6477b8a63dee26b37bdd0eb34d010556e91261784f", size = 9910632, upload-time = "2026-02-17T22:19:39.001Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f7/b449ffb3f68c11da12fc06fbf6d2fa3a41c41e17d0284d23a79e1c13a7e4/pandas-3.0.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56cf59638bf24dc9bdf2154c81e248b3289f9a09a6d04e63608c159022352749", size = 10440535, upload-time = "2026-02-17T22:19:41.157Z" }, + { url = "https://files.pythonhosted.org/packages/55/77/6ea82043db22cb0f2bbfe7198da3544000ddaadb12d26be36e19b03a2dc5/pandas-3.0.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1a9f55e0f46951874b863d1f3906dcb57df2d9be5c5847ba4dfb55b2c815249", size = 10893940, upload-time = "2026-02-17T22:19:43.493Z" }, + { url = "https://files.pythonhosted.org/packages/03/30/f1b502a72468c89412c1b882a08f6eed8a4ee9dc033f35f65d0663df6081/pandas-3.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1849f0bba9c8a2fb0f691d492b834cc8dadf617e29015c66e989448d58d011ee", size = 11442711, upload-time = "2026-02-17T22:19:46.074Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f0/ebb6ddd8fc049e98cabac5c2924d14d1dda26a20adb70d41ea2e428d3ec4/pandas-3.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3d288439e11b5325b02ae6e9cc83e6805a62c40c5a6220bea9beb899c073b1c", size = 11963918, upload-time = "2026-02-17T22:19:48.838Z" }, + { url = "https://files.pythonhosted.org/packages/09/f8/8ce132104074f977f907442790eaae24e27bce3b3b454e82faa3237ff098/pandas-3.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:93325b0fe372d192965f4cca88d97667f49557398bbf94abdda3bf1b591dbe66", size = 9862099, upload-time = "2026-02-17T22:19:51.081Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b7/6af9aac41ef2456b768ef0ae60acf8abcebb450a52043d030a65b4b7c9bd/pandas-3.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:97ca08674e3287c7148f4858b01136f8bdfe7202ad25ad04fec602dd1d29d132", size = 9185333, upload-time = "2026-02-17T22:19:53.266Z" }, + { url = "https://files.pythonhosted.org/packages/66/fc/848bb6710bc6061cb0c5badd65b92ff75c81302e0e31e496d00029fe4953/pandas-3.0.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:58eeb1b2e0fb322befcf2bbc9ba0af41e616abadb3d3414a6bc7167f6cbfce32", size = 10772664, upload-time = "2026-02-17T22:19:55.806Z" }, + { url = "https://files.pythonhosted.org/packages/69/5c/866a9bbd0f79263b4b0db6ec1a341be13a1473323f05c122388e0f15b21d/pandas-3.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cd9af1276b5ca9e298bd79a26bda32fa9cc87ed095b2a9a60978d2ca058eaf87", size = 10421286, upload-time = "2026-02-17T22:19:58.091Z" }, + { url = "https://files.pythonhosted.org/packages/51/a4/2058fb84fb1cfbfb2d4a6d485e1940bb4ad5716e539d779852494479c580/pandas-3.0.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f87a04984d6b63788327cd9f79dda62b7f9043909d2440ceccf709249ca988", size = 10342050, upload-time = "2026-02-17T22:20:01.376Z" }, + { url = "https://files.pythonhosted.org/packages/22/1b/674e89996cc4be74db3c4eb09240c4bb549865c9c3f5d9b086ff8fcfbf00/pandas-3.0.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85fe4c4df62e1e20f9db6ebfb88c844b092c22cd5324bdcf94bfa2fc1b391221", size = 10740055, upload-time = "2026-02-17T22:20:04.328Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f8/e954b750764298c22fa4614376531fe63c521ef517e7059a51f062b87dca/pandas-3.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:331ca75a2f8672c365ae25c0b29e46f5ac0c6551fdace8eec4cd65e4fac271ff", size = 11357632, upload-time = "2026-02-17T22:20:06.647Z" }, + { url = "https://files.pythonhosted.org/packages/6d/02/c6e04b694ffd68568297abd03588b6d30295265176a5c01b7459d3bc35a3/pandas-3.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15860b1fdb1973fffade772fdb931ccf9b2f400a3f5665aef94a00445d7d8dd5", size = 11810974, upload-time = "2026-02-17T22:20:08.946Z" }, + { url = "https://files.pythonhosted.org/packages/89/41/d7dfb63d2407f12055215070c42fc6ac41b66e90a2946cdc5e759058398b/pandas-3.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:44f1364411d5670efa692b146c748f4ed013df91ee91e9bec5677fb1fd58b937", size = 10884622, upload-time = "2026-02-17T22:20:11.711Z" }, + { url = "https://files.pythonhosted.org/packages/68/b0/34937815889fa982613775e4b97fddd13250f11012d769949c5465af2150/pandas-3.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:108dd1790337a494aa80e38def654ca3f0968cf4f362c85f44c15e471667102d", size = 9452085, upload-time = "2026-02-17T22:20:14.331Z" }, +] + [[package]] name = "pathspec" version = "0.11.2" @@ -1579,7 +2171,13 @@ name = "pathspec" version = "1.0.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] 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" } @@ -1631,7 +2229,13 @@ name = "platformdirs" version = "4.9.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } wheels = [ @@ -1646,7 +2250,7 @@ resolution-markers = [ "python_full_version < '3.8'", ] dependencies = [ - { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8a/42/8f2833655a29c4e9cb52ee8a2be04ceac61bcff4a680fb338cbd3d1e322d/pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3", size = 61613, upload-time = "2023-06-21T09:12:28.745Z" } wheels = [ @@ -1670,7 +2274,13 @@ name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] 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" } @@ -1678,6 +2288,100 @@ 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 = "polars" +version = "1.8.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/75/2196c26fe049ecce55a0fa87b22ab3d9477bc9bab38116ed04854fc65ecb/polars-1.8.2.tar.gz", hash = "sha256:42f69277d5be2833b0b826af5e75dcf430222d65c9633872856e176a0bed27a0", size = 4010537, upload-time = "2024-09-24T20:10:15.547Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/8b/6829e22a0f4c6e754c2e2b5d81025ab14d7b214018119762f52bad7325aa/polars-1.8.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:114be1ebfb051b794fb9e1f15999430c79cc0824595e237d3f45632be3e56d73", size = 31165933, upload-time = "2024-09-24T20:09:21.37Z" }, + { url = "https://files.pythonhosted.org/packages/8f/cd/5d6b837f42c1b6d87012beca940a075e450a352ab717a649000c2ec57d71/polars-1.8.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:e4fc36cfe48972d4c5be21a7cb119d6378fb7af0bb3eeb61456b66a1f43228e3", size = 27488552, upload-time = "2024-09-24T20:09:25.313Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f3/c317b1bc6759d1ec343c25d5ebd376a07a2e1fd2bd04fdc07ce6b2a855c4/polars-1.8.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c1e448d6e38697650b22dd359f13c40b567c0b66686c8602e4367400e87801", size = 32548666, upload-time = "2024-09-24T20:09:28.469Z" }, + { url = "https://files.pythonhosted.org/packages/1d/df/5ccf44218728caecda9f555879b40fe4ab34ff629c81b9117a1107437fdc/polars-1.8.2-cp38-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:570ee86b033dc5a6dbe2cb0df48522301642f304dda3da48f53d7488899a2206", size = 29187225, upload-time = "2024-09-24T20:09:32.1Z" }, + { url = "https://files.pythonhosted.org/packages/9c/45/77e4fda23368907c06bf70fc722de28d442c5087bbc8a60c29b8396750ea/polars-1.8.2-cp38-abi3-win_amd64.whl", hash = "sha256:ce1a1c1e2150ffcc44a5f1c461d738e1dcd95abbd0f210af0271c7ac0c9f7ef9", size = 32394690, upload-time = "2024-09-24T20:09:35.278Z" }, +] + +[[package]] +name = "polars" +version = "1.36.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "polars-runtime-32", version = "1.36.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/dc/56f2a90c79a2cb13f9e956eab6385effe54216ae7a2068b3a6406bae4345/polars-1.36.1.tar.gz", hash = "sha256:12c7616a2305559144711ab73eaa18814f7aa898c522e7645014b68f1432d54c", size = 711993, upload-time = "2025-12-10T01:14:53.033Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/c6/36a1b874036b49893ecae0ac44a2f63d1a76e6212631a5b2f50a86e0e8af/polars-1.36.1-py3-none-any.whl", hash = "sha256:853c1bbb237add6a5f6d133c15094a9b727d66dd6a4eb91dbb07cdb056b2b8ef", size = 802429, upload-time = "2025-12-10T01:13:53.838Z" }, +] + +[[package]] +name = "polars" +version = "1.38.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "polars-runtime-32", version = "1.38.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/5e/208a24471a433bcd0e9a6889ac49025fd4daad2815c8220c5bd2576e5f1b/polars-1.38.1.tar.gz", hash = "sha256:803a2be5344ef880ad625addfb8f641995cfd777413b08a10de0897345778239", size = 717667, upload-time = "2026-02-06T18:13:23.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/49/737c1a6273c585719858261753da0b688454d1b634438ccba8a9c4eb5aab/polars-1.38.1-py3-none-any.whl", hash = "sha256:a29479c48fed4984d88b656486d221f638cba45d3e961631a50ee5fdde38cb2c", size = 810368, upload-time = "2026-02-06T18:11:55.819Z" }, +] + +[[package]] +name = "polars-runtime-32" +version = "1.36.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/31/df/597c0ef5eb8d761a16d72327846599b57c5d40d7f9e74306fc154aba8c37/polars_runtime_32-1.36.1.tar.gz", hash = "sha256:201c2cfd80ceb5d5cd7b63085b5fd08d6ae6554f922bcb941035e39638528a09", size = 2788751, upload-time = "2025-12-10T01:14:54.172Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/ea/871129a2d296966c0925b078a9a93c6c5e7facb1c5eebfcd3d5811aeddc1/polars_runtime_32-1.36.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:327b621ca82594f277751f7e23d4b939ebd1be18d54b4cdf7a2f8406cecc18b2", size = 43494311, upload-time = "2025-12-10T01:13:56.096Z" }, + { url = "https://files.pythonhosted.org/packages/d8/76/0038210ad1e526ce5bb2933b13760d6b986b3045eccc1338e661bd656f77/polars_runtime_32-1.36.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ab0d1f23084afee2b97de8c37aa3e02ec3569749ae39571bd89e7a8b11ae9e83", size = 39300602, upload-time = "2025-12-10T01:13:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/54/1e/2707bee75a780a953a77a2c59829ee90ef55708f02fc4add761c579bf76e/polars_runtime_32-1.36.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:899b9ad2e47ceb31eb157f27a09dbc2047efbf4969a923a6b1ba7f0412c3e64c", size = 44511780, upload-time = "2025-12-10T01:14:02.285Z" }, + { url = "https://files.pythonhosted.org/packages/11/b2/3fede95feee441be64b4bcb32444679a8fbb7a453a10251583053f6efe52/polars_runtime_32-1.36.1-cp39-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:d9d077bb9df711bc635a86540df48242bb91975b353e53ef261c6fae6cb0948f", size = 40688448, upload-time = "2025-12-10T01:14:05.131Z" }, + { url = "https://files.pythonhosted.org/packages/05/0f/e629713a72999939b7b4bfdbf030a32794db588b04fdf3dc977dd8ea6c53/polars_runtime_32-1.36.1-cp39-abi3-win_amd64.whl", hash = "sha256:cc17101f28c9a169ff8b5b8d4977a3683cd403621841623825525f440b564cf0", size = 44464898, upload-time = "2025-12-10T01:14:08.296Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d8/a12e6aa14f63784cead437083319ec7cece0d5bb9a5bfe7678cc6578b52a/polars_runtime_32-1.36.1-cp39-abi3-win_arm64.whl", hash = "sha256:809e73857be71250141225ddd5d2b30c97e6340aeaa0d445f930e01bef6888dc", size = 39798896, upload-time = "2025-12-10T01:14:11.568Z" }, +] + +[[package]] +name = "polars-runtime-32" +version = "1.38.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/07/4b/04d6b3fb7cf336fbe12fbc4b43f36d1783e11bb0f2b1e3980ec44878df06/polars_runtime_32-1.38.1.tar.gz", hash = "sha256:04f20ed1f5c58771f34296a27029dc755a9e4b1390caeaef8f317e06fdfce2ec", size = 2812631, upload-time = "2026-02-06T18:13:25.206Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/a2/a00defbddadd8cf1042f52380dcba6b6592b03bac8e3b34c436b62d12d3b/polars_runtime_32-1.38.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:18154e96044724a0ac38ce155cf63aa03c02dd70500efbbf1a61b08cadd269ef", size = 44108001, upload-time = "2026-02-06T18:11:58.127Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/599ff3709e6a303024efd7edfd08cf8de55c6ac39527d8f41cbc4399385f/polars_runtime_32-1.38.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c49acac34cc4049ed188f1eb67d6ff3971a39b4af7f7b734b367119970f313ac", size = 40230140, upload-time = "2026-02-06T18:12:01.181Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8c/3ac18d6f89dc05fe2c7c0ee1dc5b81f77a5c85ad59898232c2500fe2ebbf/polars_runtime_32-1.38.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fef2ef2626a954e010e006cc8e4de467ecf32d08008f130cea1c78911f545323", size = 41994039, upload-time = "2026-02-06T18:12:04.332Z" }, + { url = "https://files.pythonhosted.org/packages/f2/5a/61d60ec5cc0ab37cbd5a699edb2f9af2875b7fdfdfb2a4608ca3cc5f0448/polars_runtime_32-1.38.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8a5f7a8125e2d50e2e060296551c929aec09be23a9edcb2b12ca923f555a5ba", size = 45755804, upload-time = "2026-02-06T18:12:07.846Z" }, + { url = "https://files.pythonhosted.org/packages/91/54/02cd4074c98c361ccd3fec3bcb0bd68dbc639c0550c42a4436b0ff0f3ccf/polars_runtime_32-1.38.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:10d19cd9863e129273b18b7fcaab625b5c8143c2d22b3e549067b78efa32e4fa", size = 42159605, upload-time = "2026-02-06T18:12:10.919Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f3/b2a5e720cc56eaa38b4518e63aa577b4bbd60e8b05a00fe43ca051be5879/polars_runtime_32-1.38.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61e8d73c614b46a00d2f853625a7569a2e4a0999333e876354ac81d1bf1bb5e2", size = 45336615, upload-time = "2026-02-06T18:12:14.074Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8d/ee2e4b7de948090cfb3df37d401c521233daf97bfc54ddec5d61d1d31618/polars_runtime_32-1.38.1-cp310-abi3-win_amd64.whl", hash = "sha256:08c2b3b93509c1141ac97891294ff5c5b0c548a373f583eaaea873a4bf506437", size = 45680732, upload-time = "2026-02-06T18:12:19.097Z" }, + { url = "https://files.pythonhosted.org/packages/bf/18/72c216f4ab0c82b907009668f79183ae029116ff0dd245d56ef58aac48e7/polars_runtime_32-1.38.1-cp310-abi3-win_arm64.whl", hash = "sha256:6d07d0cc832bfe4fb54b6e04218c2c27afcfa6b9498f9f6bbf262a00d58cc7c4", size = 41639413, upload-time = "2026-02-06T18:12:22.044Z" }, +] + [[package]] name = "pydantic" version = "2.5.3" @@ -1687,7 +2391,7 @@ resolution-markers = [ ] dependencies = [ { name = "annotated-types", version = "0.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, { name = "pydantic-core", version = "2.14.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, ] @@ -1718,7 +2422,13 @@ name = "pydantic" version = "2.12.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ @@ -1968,7 +2678,13 @@ name = "pydantic-core" version = "2.41.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ @@ -2115,7 +2831,13 @@ name = "pygments" version = "2.19.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", "python_full_version == '3.8.*'", ] @@ -2161,7 +2883,13 @@ name = "pymdown-extensions" version = "10.21" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ @@ -2184,7 +2912,7 @@ resolution-markers = [ dependencies = [ { name = "colorama", marker = "python_full_version < '3.8' and sys_platform == 'win32'" }, { name = "exceptiongroup", marker = "python_full_version < '3.8'" }, - { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, { name = "iniconfig", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, { name = "packaging", version = "24.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, { name = "pluggy", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, @@ -2241,7 +2969,13 @@ name = "pytest" version = "9.0.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", ] dependencies = [ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, @@ -2338,7 +3072,13 @@ name = "pyyaml" version = "6.0.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", "python_full_version == '3.8.*'", ] @@ -2440,7 +3180,13 @@ name = "pyyaml-env-tag" version = "1.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ @@ -2573,7 +3319,13 @@ name = "requests" version = "2.32.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] dependencies = [ @@ -2613,7 +3365,10 @@ name = "tomli" version = "2.4.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", "python_full_version == '3.8.*'", ] @@ -2696,7 +3451,13 @@ name = "typing-extensions" version = "4.15.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] 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" } @@ -2716,6 +3477,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] +[[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 = "ucon" source = { editable = "." } @@ -2727,24 +3497,89 @@ dependencies = [ ] [package.optional-dependencies] +all = [ + { name = "numpy", version = "1.21.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "2.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "polars", version = "1.8.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "polars", version = "1.36.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "polars", version = "1.38.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pydantic", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pydantic", version = "2.10.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pydantic", version = "2.12.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +dataframe = [ + { name = "pandas", version = "2.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "polars", version = "1.8.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "polars", version = "1.36.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "polars", version = "1.38.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] docs = [ { name = "mkdocs-material", version = "9.2.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, - { name = "mkdocs-material", version = "9.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "mkdocs-material", version = "9.7.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, { name = "mkdocstrings", version = "0.22.0", source = { registry = "https://pypi.org/simple" }, extra = ["python"], marker = "python_full_version < '3.8'" }, { name = "mkdocstrings", version = "0.26.1", source = { registry = "https://pypi.org/simple" }, extra = ["python"], marker = "python_full_version == '3.8.*'" }, { name = "mkdocstrings", version = "0.30.1", source = { registry = "https://pypi.org/simple" }, extra = ["python"], marker = "python_full_version == '3.9.*'" }, { name = "mkdocstrings", version = "1.0.3", source = { registry = "https://pypi.org/simple" }, extra = ["python"], marker = "python_full_version >= '3.10'" }, ] +numpy = [ + { name = "numpy", version = "1.21.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +pandas = [ + { name = "pandas", version = "2.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +polars = [ + { name = "polars", version = "1.8.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "polars", version = "1.36.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "polars", version = "1.38.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] pydantic = [ { name = "pydantic", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, { name = "pydantic", version = "2.10.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, { name = "pydantic", version = "2.12.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] +scientific = [ + { name = "numpy", version = "1.21.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "2.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "polars", version = "1.8.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "polars", version = "1.36.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "polars", version = "1.38.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] test = [ { name = "coverage", version = "7.2.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.8'" }, { name = "coverage", version = "7.6.1", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version == '3.8.*'" }, { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version == '3.9.*'" }, - { name = "coverage", version = "7.13.2", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, + { name = "coverage", version = "7.13.4", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, + { name = "numpy", version = "1.21.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "2.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "polars", version = "1.8.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "polars", version = "1.36.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "polars", version = "1.38.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest", version = "7.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, @@ -2756,12 +3591,23 @@ requires-dist = [ { name = "coverage", extras = ["toml"], marker = "extra == 'test'", specifier = ">=5.5" }, { name = "mkdocs-material", marker = "extra == 'docs'" }, { name = "mkdocstrings", extras = ["python"], marker = "extra == 'docs'" }, + { name = "numpy", marker = "extra == 'numpy'", specifier = ">=1.20" }, + { name = "numpy", marker = "extra == 'test'", specifier = ">=1.20" }, + { name = "pandas", marker = "python_full_version >= '3.8' and extra == 'pandas'", specifier = ">=1.3" }, + { name = "pandas", marker = "python_full_version >= '3.8' and extra == 'test'", specifier = ">=1.3" }, + { name = "polars", marker = "python_full_version >= '3.8' and extra == 'polars'", specifier = ">=0.15" }, + { name = "polars", marker = "python_full_version >= '3.8' and extra == 'test'", specifier = ">=0.15" }, { name = "pydantic", marker = "extra == 'pydantic'", specifier = ">=2.0" }, { name = "pytest", marker = "extra == 'test'", specifier = ">=7.0" }, { name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2.0" }, { name = "typing-extensions", marker = "python_full_version < '3.9'", specifier = ">=3.7.4" }, + { name = "ucon", extras = ["numpy"], marker = "extra == 'scientific'" }, + { name = "ucon", extras = ["numpy", "pydantic"], marker = "extra == 'all'" }, + { name = "ucon", extras = ["pandas", "polars"], marker = "python_full_version >= '3.8' and extra == 'all'" }, + { name = "ucon", extras = ["pandas", "polars"], marker = "python_full_version >= '3.8' and extra == 'dataframe'" }, + { name = "ucon", extras = ["pandas", "polars"], marker = "python_full_version >= '3.8' and extra == 'scientific'" }, ] -provides-extras = ["test", "pydantic", "docs"] +provides-extras = ["test", "pydantic", "docs", "numpy", "pandas", "polars", "dataframe", "scientific", "all"] [[package]] name = "urllib3" @@ -2792,7 +3638,13 @@ name = "urllib3" version = "2.6.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] 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" } @@ -2887,7 +3739,13 @@ name = "watchdog" version = "6.0.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", "python_full_version == '3.9.*'", ] 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" } @@ -2936,7 +3794,34 @@ wheels = [ name = "zipp" version = "3.15.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] sdist = { url = "https://files.pythonhosted.org/packages/00/27/f0ac6b846684cecce1ee93d32450c45ab607f65c2e0255f0092032d91f07/zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b", size = 18454, upload-time = "2023-02-25T02:17:22.503Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5b/fa/c9e82bbe1af6266adf08afb563905eb87cab83fde00a0a08963510621047/zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556", size = 6758, upload-time = "2023-02-25T02:17:20.807Z" }, ] + +[[package]] +name = "zipp" +version = "3.20.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/54/bf/5c0000c44ebc80123ecbdddba1f5dcd94a5ada602a9c225d84b5aaa55e86/zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29", size = 24199, upload-time = "2024-09-13T13:44:16.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/8b/5ba542fa83c90e09eac972fc9baca7a88e7e7ca4b221a89251954019308b/zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", size = 9200, upload-time = "2024-09-13T13:44:14.38Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +]