From be692e045a2e8138792608ea5035313e17df54c5 Mon Sep 17 00:00:00 2001 From: Percy Date: Wed, 30 Jul 2025 04:48:58 -0400 Subject: [PATCH] Complete frequently used cache eviction algos --- .gitignore | 3 +- benchmark/simulation.py | 4 +- examples/plugin_cache.py | 13 +- libcachesim/__init__.py | 24 +++ libcachesim/__init__.pyi | 1 - libcachesim/cache.py | 337 ++++++++++++++++++++++++++++++---- libcachesim/data_loader.py | 35 ++-- libcachesim/trace_analyzer.py | 6 +- libcachesim/trace_reader.py | 3 +- libcachesim/util.py | 7 +- pyproject.toml | 16 +- scripts/detect_deps.py | 174 ++++++++++++++++++ scripts/install.sh | 3 +- scripts/smart_build.py | 120 ++++++++++++ scripts/sync_version.py | 9 +- src/export_reader.cpp | 2 +- tests/test_analyzer.py | 34 ++-- tests/test_reader.py | 234 ++++++++++------------- uv.lock | 266 ++++++++++++++++++++++++++- 19 files changed, 1058 insertions(+), 233 deletions(-) create mode 100644 scripts/detect_deps.py create mode 100644 scripts/smart_build.py diff --git a/.gitignore b/.gitignore index 83cff87..9358c40 100644 --- a/.gitignore +++ b/.gitignore @@ -230,4 +230,5 @@ sftp-config.json # Custom files CMakeFiles/* -*.pyc \ No newline at end of file +*.pyc +!scripts/smart_build.py \ No newline at end of file diff --git a/benchmark/simulation.py b/benchmark/simulation.py index 0841157..5e8e23e 100644 --- a/benchmark/simulation.py +++ b/benchmark/simulation.py @@ -1,5 +1,5 @@ -""" Benchmark the simulation performance of the library. +"""Benchmark the simulation performance of the library. This module contains benchmarks for various components of the library, including request processing times, memory usage, and overall throughput. -""" \ No newline at end of file +""" diff --git a/examples/plugin_cache.py b/examples/plugin_cache.py index 30a1e78..04940b3 100644 --- a/examples/plugin_cache.py +++ b/examples/plugin_cache.py @@ -1,6 +1,7 @@ from collections import OrderedDict from libcachesim import PluginCache, CommonCacheParams, Request, SyntheticReader, LRU + class StandaloneLRU: def __init__(self): self.cache_data = OrderedDict() @@ -25,21 +26,27 @@ def cache_remove(self, obj_id): def cache_init_hook(common_cache_params: CommonCacheParams): return StandaloneLRU() + def cache_hit_hook(cache, request: Request): cache.cache_hit(request.obj_id) + def cache_miss_hook(cache, request: Request): cache.cache_miss(request.obj_id, request.obj_size) + def cache_eviction_hook(cache, request: Request): return cache.cache_eviction() + def cache_remove_hook(cache, obj_id): cache.cache_remove(obj_id) + def cache_free_hook(cache): cache.cache_data.clear() + plugin_lru_cache = PluginCache( cache_size=1024, cache_init_hook=cache_init_hook, @@ -48,7 +55,8 @@ def cache_free_hook(cache): cache_eviction_hook=cache_eviction_hook, cache_remove_hook=cache_remove_hook, cache_free_hook=cache_free_hook, - cache_name="CustomizedLRU") + cache_name="CustomizedLRU", +) ref_lru_cache = LRU(cache_size=1024) @@ -67,6 +75,3 @@ def cache_free_hook(cache): assert plugin_hit == ref_hit, f"Cache hit mismatch: {plugin_hit} != {ref_hit}" print("All requests processed successfully. Plugin cache matches reference LRU cache.") - - - \ No newline at end of file diff --git a/libcachesim/__init__.py b/libcachesim/__init__.py index 137b4e9..bd194bf 100644 --- a/libcachesim/__init__.py +++ b/libcachesim/__init__.py @@ -38,6 +38,18 @@ # Optimal algorithms Belady, BeladySize, + # Probabilistic algorithms + LRUProb, + FlashProb, + # Size-based algorithms + Size, + GDSF, + # Hyperbolic algorithms + Hyperbolic, + # Extra deps + ThreeLCache, + GLCache, + LRB, # Plugin cache PluginCache, ) @@ -81,6 +93,18 @@ # Optimal algorithms "Belady", "BeladySize", + # Probabilistic algorithms + "LRUProb", + "FlashProb", + # Size-based algorithms + "Size", + "GDSF", + # Hyperbolic algorithms + "Hyperbolic", + # Extra deps + "ThreeLCache", + "GLCache", + "LRB", # Plugin cache "PluginCache", # Readers and analyzers diff --git a/libcachesim/__init__.pyi b/libcachesim/__init__.pyi index a855b51..dc253ce 100644 --- a/libcachesim/__init__.pyi +++ b/libcachesim/__init__.pyi @@ -219,7 +219,6 @@ def create_zipf_requests( start_obj_id: int = 0, seed: int | None = None, ) -> Iterator[Request]: ... - def create_uniform_requests( num_objects: int, num_requests: int, diff --git a/libcachesim/cache.py b/libcachesim/cache.py index 6ecfe2e..31c4e4a 100644 --- a/libcachesim/cache.py +++ b/libcachesim/cache.py @@ -1,5 +1,5 @@ from abc import ABC -from typing import Protocol, Callable, Optional +from typing import Callable, Optional from .libcachesim_python import ( CommonCacheParams, Request, @@ -149,9 +149,11 @@ def _create_common_params( ) +# ------------------------------------------------------------------------------------------------ # Core cache algorithms +# ------------------------------------------------------------------------------------------------ class LRU(CacheBase): - """Least Recently Used cache""" + """Least Recently Used cache (no special parameters)""" def __init__( self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False @@ -162,7 +164,7 @@ def __init__( class FIFO(CacheBase): - """First In First Out cache""" + """First In First Out cache (no special parameters)""" def __init__( self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False @@ -173,7 +175,7 @@ def __init__( class LFU(CacheBase): - """Least Frequently Used cache""" + """Least Frequently Used cache (no special parameters)""" def __init__( self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False @@ -184,7 +186,7 @@ def __init__( class ARC(CacheBase): - """Adaptive Replacement Cache""" + """Adaptive Replacement Cache (no special parameters)""" def __init__( self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False @@ -195,18 +197,32 @@ def __init__( class Clock(CacheBase): - """Clock replacement algorithm""" + """Clock replacement algorithm + + Special parameters: + init_freq: initial frequency of the object (default: 0) + n_bit_counter: number of bits for the counter (default: 1) + """ def __init__( - self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False + self, + cache_size: int, + default_ttl: int = 86400 * 300, + hashpower: int = 24, + consider_obj_metadata: bool = False, + init_freq: int = 0, + n_bit_counter: int = 1, ): + cache_specific_params = f"init-freq={init_freq}, n-bit-counter={n_bit_counter}" super().__init__( - _cache=Clock_init(_create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata)) + _cache=Clock_init( + _create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata), cache_specific_params + ) ) class Random(CacheBase): - """Random replacement cache""" + """Random replacement cache (no special parameters)""" def __init__( self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False @@ -218,18 +234,34 @@ def __init__( # Advanced algorithms class S3FIFO(CacheBase): - """S3-FIFO cache algorithm""" + """S3-FIFO cache algorithm + + Special parameters: + small_size_ratio: ratio of small cache size to total cache size (default: 0.1) + ghost_size_ratio: ratio of ghost cache size to total cache size (default: 0.9) + move_to_main_threshold: threshold for moving objects from ghost to main cache (default: 2) + """ def __init__( - self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False + self, + cache_size: int, + default_ttl: int = 86400 * 300, + hashpower: int = 24, + consider_obj_metadata: bool = False, + small_size_ratio: float = 0.1, + ghost_size_ratio: float = 0.9, + move_to_main_threshold: int = 2, ): + cache_specific_params = f"small-size-ratio={small_size_ratio}, ghost-size-ratio={ghost_size_ratio}, move-to-main-threshold={move_to_main_threshold}" super().__init__( - _cache=S3FIFO_init(_create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata)) + _cache=S3FIFO_init( + _create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata), cache_specific_params + ) ) class Sieve(CacheBase): - """Sieve cache algorithm""" + """Sieve cache algorithm (no special parameters)""" def __init__( self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False @@ -240,7 +272,7 @@ def __init__( class LIRS(CacheBase): - """Low Inter-reference Recency Set""" + """Low Inter-reference Recency Set (no special parameters)""" def __init__( self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False @@ -251,18 +283,32 @@ def __init__( class TwoQ(CacheBase): - """2Q replacement algorithm""" + """2Q replacement algorithm + + Special parameters: + a_in_size_ratio: ratio of Ain queue size to total cache size (default: 0.25) + a_out_size_ratio: ratio of Aout queue size to total cache size (default: 0.5) + """ def __init__( - self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False + self, + cache_size: int, + default_ttl: int = 86400 * 300, + hashpower: int = 24, + consider_obj_metadata: bool = False, + a_in_size_ratio: float = 0.25, + a_out_size_ratio: float = 0.5, ): + cache_specific_params = f"Ain-size-ratio={a_in_size_ratio}, Aout-size-ratio={a_out_size_ratio}" super().__init__( - _cache=TwoQ_init(_create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata)) + _cache=TwoQ_init( + _create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata), cache_specific_params + ) ) class SLRU(CacheBase): - """Segmented LRU""" + """Segmented LRU (no special parameters)""" def __init__( self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False @@ -273,29 +319,57 @@ def __init__( class WTinyLFU(CacheBase): - """Window TinyLFU""" + """Window TinyLFU + + Special parameters: + main_cache: the type of the main cache (default: "SLRU") + window_size: ratio of the window size to the main cache size (default: 0.01) + """ def __init__( - self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False + self, + cache_size: int, + default_ttl: int = 86400 * 300, + hashpower: int = 24, + consider_obj_metadata: bool = False, + main_cache: str = "SLRU", + window_size: float = 0.01, ): + cache_specific_params = f"main-cache={main_cache}, window-size={window_size}" super().__init__( - _cache=WTinyLFU_init(_create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata)) + _cache=WTinyLFU_init( + _create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata), cache_specific_params + ) ) class LeCaR(CacheBase): - """Learning Cache Replacement""" + """Learning Cache Replacement + + Special parameters: + update_weight (bool): whether to update the weight (default: True) + lru_weight (float): the initial weight (probability) of the LRU (default: 0.5), 1 - lru_weight = lfu_weight, i.e, the probability of the LRU being selected + """ def __init__( - self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False + self, + cache_size: int, + default_ttl: int = 86400 * 300, + hashpower: int = 24, + consider_obj_metadata: bool = False, + update_weight: bool = True, + lru_weight: float = 0.5, ): + cache_specific_params = f"update-weight={update_weight}, lru-weight={lru_weight}" super().__init__( - _cache=LeCaR_init(_create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata)) + _cache=LeCaR_init( + _create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata), cache_specific_params + ) ) class LFUDA(CacheBase): - """LFU with Dynamic Aging""" + """LFU with Dynamic Aging (no special parameters)""" def __init__( self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False @@ -306,18 +380,32 @@ def __init__( class ClockPro(CacheBase): - """Clock-Pro replacement algorithm""" + """Clock-Pro replacement algorithm + + Special parameters: + init_req: initial reference count (default: 0) + init_ratio_cold: initial ratio of cold pages (default: 1) + """ def __init__( - self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False + self, + cache_size: int, + default_ttl: int = 86400 * 300, + hashpower: int = 24, + consider_obj_metadata: bool = False, + init_req: int = 0, + init_ratio_cold: float = 0.5, ): + cache_specific_params = f"init-req={init_req}, init-ratio-cold={init_ratio_cold}" super().__init__( - _cache=ClockPro_init(_create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata)) + _cache=ClockPro_init( + _create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata), cache_specific_params + ) ) class Cacheus(CacheBase): - """Cacheus algorithm""" + """Cacheus algorithm (no special parameters)""" def __init__( self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False @@ -329,7 +417,7 @@ def __init__( # Optimal algorithms class Belady(CacheBase): - """Belady's optimal algorithm""" + """Belady's optimal algorithm (no special parameters)""" def __init__( self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False @@ -340,13 +428,198 @@ def __init__( class BeladySize(CacheBase): - """Belady's optimal algorithm with size consideration""" + """Belady's optimal algorithm with size consideration + + Special parameters: + n_samples: number of samples for the size consideration (default: 128) + """ + + def __init__( + self, + cache_size: int, + default_ttl: int = 86400 * 300, + hashpower: int = 24, + consider_obj_metadata: bool = False, + n_samples: int = 128, + ): + cache_specific_params = f"n-samples={n_samples}" + super().__init__( + _cache=BeladySize_init( + _create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata), cache_specific_params + ) + ) + + +class LRUProb(CacheBase): + """LRU with Probabilistic Replacement (no special parameters)""" def __init__( self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False ): super().__init__( - _cache=BeladySize_init(_create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata)) + _cache=LRU_Prob_init(_create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata)) + ) + + +class FlashProb(CacheBase): + """FlashProb replacement algorithm + + Special parameters: + ram_size_ratio: ratio of the RAM size to the total cache size (default: 0.05) + disk_admit_prob: probability of admitting a disk page to the RAM (default: 0.2) + ram_cache: the type of the RAM cache (default: "LRU") + disk_cache: the type of the disk cache (default: "FIFO") + """ + + def __init__( + self, + cache_size: int, + default_ttl: int = 86400 * 300, + hashpower: int = 24, + consider_obj_metadata: bool = False, + ram_size_ratio: float = 0.05, + disk_admit_prob: float = 0.2, + ram_cache: str = "LRU", + disk_cache: str = "FIFO", + ): + cache_specific_params = f"ram-size-ratio={ram_size_ratio}, disk-admit-prob={disk_admit_prob}, ram-cache={ram_cache}, disk-cache={disk_cache}" + super().__init__( + _cache=flashProb_init( + _create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata), cache_specific_params + ) + ) + + +class Size(CacheBase): + """Size-based replacement algorithm (no special parameters)""" + + def __init__( + self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False + ): + super().__init__( + _cache=Size_init(_create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata)) + ) + + +class GDSF(CacheBase): + """GDSF replacement algorithm (no special parameters)""" + + def __init__( + self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False + ): + super().__init__( + _cache=GDSF_init(_create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata)) + ) + + +class Hyperbolic(CacheBase): + """Hyperbolic replacement algorithm (no special parameters)""" + + def __init__( + self, cache_size: int, default_ttl: int = 86400 * 300, hashpower: int = 24, consider_obj_metadata: bool = False + ): + super().__init__( + _cache=Hyperbolic_init(_create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata)) + ) + + +# Extra deps +class ThreeLCache(CacheBase): + """Three-Level Cache + + Special parameters: + objective: the objective of the ThreeLCache (default: "byte-miss-ratio") + """ + + def __init__( + self, + cache_size: int, + default_ttl: int = 86400 * 300, + hashpower: int = 24, + consider_obj_metadata: bool = False, + objective: str = "byte-miss-ratio", + ): + # Try to import ThreeLCache_init + try: + from .libcachesim_python import ThreeLCache_init + except ImportError: + raise ImportError("ThreeLCache is not installed. Please install it with `pip install libcachesim[all]`") + + cache_specific_params = f"objective={objective}" + super().__init__( + _cache=ThreeLCache_init( + _create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata), cache_specific_params + ) + ) + + +class GLCache(CacheBase): + """GLCache replacement algorithm + + Special parameters: + segment-size: the size of the segment (default: 100) + n-merge: the number of merges (default: 2) + type: the type of the GLCache (default: "learned") + rank-intvl: the interval for ranking (default: 0.02) + merge-consecutive-segs: whether to merge consecutive segments (default: True) + train-source-y: the source of the training data (default: "online") + retrain-intvl: the interval for retraining (default: 86400) + """ + + def __init__( + self, + cache_size: int, + default_ttl: int = 86400 * 300, + hashpower: int = 24, + consider_obj_metadata: bool = False, + segment_size: int = 100, + n_merge: int = 2, + type: str = "learned", + rank_intvl: float = 0.02, + merge_consecutive_segs: bool = True, + train_source_y: str = "online", + retrain_intvl: int = 86400, + ): + # Try to import GLCache_init + try: + from .libcachesim_python import GLCache_init + except ImportError: + raise ImportError("GLCache is not installed. Please install it with `pip install libcachesim[all]`") + + cache_specific_params = f"segment-size={segment_size}, n-merge={n_merge}, type={type}, rank-intvl={rank_intvl}, merge-consecutive-segs={merge_consecutive_segs}, train-source-y={train_source_y}, retrain-intvl={retrain_intvl}" + super().__init__( + _cache=GLCache_init( + _create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata), cache_specific_params + ) + ) + + +class LRB(CacheBase): + """LRB replacement algorithm + + Special parameters: + objective: the objective of the LRB (default: "byte-miss-ratio") + """ + + def __init__( + self, + cache_size: int, + default_ttl: int = 86400 * 300, + hashpower: int = 24, + consider_obj_metadata: bool = False, + objective: str = "byte-miss-ratio", + ): + # Try to import LRB_init + try: + from .libcachesim_python import LRB_init + except ImportError: + raise ImportError("LRB is not installed. Please install it with `pip install libcachesim[all]`") + + cache_specific_params = f"objective={objective}" + super().__init__( + _cache=LRB_init( + _create_common_params(cache_size, default_ttl, hashpower, consider_obj_metadata), cache_specific_params + ) ) diff --git a/libcachesim/data_loader.py b/libcachesim/data_loader.py index fee5f9b..f889364 100644 --- a/libcachesim/data_loader.py +++ b/libcachesim/data_loader.py @@ -17,10 +17,7 @@ class DataLoader: DEFAULT_CACHE_DIR = Path.home() / ".cache/libcachesim_hub" def __init__( - self, - bucket_name: str = DEFAULT_BUCKET, - cache_dir: Optional[Union[str, Path]] = None, - use_auth: bool = False + self, bucket_name: str = DEFAULT_BUCKET, cache_dir: Optional[Union[str, Path]] = None, use_auth: bool = False ): self.bucket_name = bucket_name self.cache_dir = Path(cache_dir) if cache_dir else self.DEFAULT_CACHE_DIR @@ -40,26 +37,25 @@ def s3_client(self): from botocore import UNSIGNED self._s3_client = boto3.client( - 's3', - config=None if self.use_auth else Config(signature_version=UNSIGNED) + "s3", config=None if self.use_auth else Config(signature_version=UNSIGNED) ) except ImportError: raise ImportError("Install boto3: pip install boto3") return self._s3_client def _cache_path(self, key: str) -> Path: - safe_name = hashlib.sha256(key.encode()).hexdigest()[:16] + "_" + quote(key, safe='') + safe_name = hashlib.sha256(key.encode()).hexdigest()[:16] + "_" + quote(key, safe="") return self.cache_dir / self.bucket_name / safe_name def _download(self, key: str, dest: Path) -> None: - temp = dest.with_suffix(dest.suffix + '.tmp') + temp = dest.with_suffix(dest.suffix + ".tmp") temp.parent.mkdir(parents=True, exist_ok=True) try: logger.info(f"Downloading s3://{self.bucket_name}/{key}") obj = self.s3_client.get_object(Bucket=self.bucket_name, Key=key) - with open(temp, 'wb') as f: - f.write(obj['Body'].read()) + with open(temp, "wb") as f: + f.write(obj["Body"].read()) shutil.move(str(temp), str(dest)) logger.info(f"Saved to: {dest}") except Exception as e: @@ -67,7 +63,7 @@ def _download(self, key: str, dest: Path) -> None: temp.unlink() raise RuntimeError(f"Download failed for s3://{self.bucket_name}/{key}: {e}") - def load(self, key: str, force: bool = False, mode: str = 'rb') -> Union[bytes, str]: + def load(self, key: str, force: bool = False, mode: str = "rb") -> Union[bytes, str]: path = self._cache_path(key) if not path.exists() or force: self._download(key, path) @@ -93,15 +89,10 @@ def clear_cache(self, key: Optional[str] = None) -> None: def list_cached_files(self) -> list[str]: if not self.cache_dir.exists(): return [] - return [ - str(p) for p in self.cache_dir.rglob('*') - if p.is_file() and not p.name.endswith('.tmp') - ] + return [str(p) for p in self.cache_dir.rglob("*") if p.is_file() and not p.name.endswith(".tmp")] def get_cache_size(self) -> int: - return sum( - p.stat().st_size for p in self.cache_dir.rglob('*') if p.is_file() - ) + return sum(p.stat().st_size for p in self.cache_dir.rglob("*") if p.is_file()) def list_s3_objects(self, prefix: str = "", delimiter: str = "/") -> dict: """ @@ -116,14 +107,10 @@ def list_s3_objects(self, prefix: str = "", delimiter: str = "/") -> dict: - "folders": list of sub-prefixes (folders) - "files": list of object keys (files) """ - paginator = self.s3_client.get_paginator('list_objects_v2') + paginator = self.s3_client.get_paginator("list_objects_v2") result = {"folders": [], "files": []} - for page in paginator.paginate( - Bucket=self.bucket_name, - Prefix=prefix, - Delimiter=delimiter - ): + for page in paginator.paginate(Bucket=self.bucket_name, Prefix=prefix, Delimiter=delimiter): # CommonPrefixes are like subdirectories result["folders"].extend(cp["Prefix"] for cp in page.get("CommonPrefixes", [])) result["files"].extend(obj["Key"] for obj in page.get("Contents", [])) diff --git a/libcachesim/trace_analyzer.py b/libcachesim/trace_analyzer.py index 4e51da4..4a2d936 100644 --- a/libcachesim/trace_analyzer.py +++ b/libcachesim/trace_analyzer.py @@ -1,4 +1,5 @@ """Wrapper of Analyzer""" + from __future__ import annotations from typing import TYPE_CHECKING @@ -12,11 +13,14 @@ AnalysisParam, ) + # Import ReaderException class ReaderException(Exception): """Exception raised when reader is not compatible""" + pass + class TraceAnalyzer: _analyzer: Analyzer @@ -36,7 +40,7 @@ def __init__( analysis_param: Analysis parameters analysis_option: Analysis options """ - if not hasattr(reader, 'c_reader') or not reader.c_reader: + if not hasattr(reader, "c_reader") or not reader.c_reader: raise ReaderException("Only C/C++ reader is supported") if analysis_param is None: diff --git a/libcachesim/trace_reader.py b/libcachesim/trace_reader.py index 8bc47f4..8d70741 100644 --- a/libcachesim/trace_reader.py +++ b/libcachesim/trace_reader.py @@ -24,7 +24,6 @@ def __init__( trace_type: TraceType = TraceType.UNKNOWN_TRACE, reader_init_params: Optional[ReaderInitParam] = None, ): - if isinstance(trace, Reader): self._reader = trace return @@ -34,7 +33,7 @@ def __init__( if not isinstance(reader_init_params, ReaderInitParam): raise TypeError("reader_init_params must be an instance of ReaderInitParam") - + self._reader = Reader(trace, trace_type, reader_init_params) @property diff --git a/libcachesim/util.py b/libcachesim/util.py index c9c351b..a9f957f 100644 --- a/libcachesim/util.py +++ b/libcachesim/util.py @@ -1,4 +1,5 @@ """Wrapper misc functions""" + from __future__ import annotations from typing import TYPE_CHECKING @@ -30,7 +31,9 @@ def convert_to_lcs(reader, ofilepath, output_txt=False, remove_size_change=False return convert_to_lcs(reader, ofilepath, output_txt, remove_size_change, lcs_ver) @staticmethod - def process_trace(cache: CacheBase, reader: ReaderProtocol, start_req: int = 0, max_req: int = -1) -> tuple[float, float]: + def process_trace( + cache: CacheBase, reader: ReaderProtocol, start_req: int = 0, max_req: int = -1 + ) -> tuple[float, float]: """ Process a trace with a cache. @@ -44,7 +47,7 @@ def process_trace(cache: CacheBase, reader: ReaderProtocol, start_req: int = 0, tuple[float, float]: The object miss ratio and byte miss ratio. """ # Check if reader is C++ reader - if not hasattr(reader, 'c_reader') or not reader.c_reader: + if not hasattr(reader, "c_reader") or not reader.c_reader: raise ValueError("Reader must be a C++ reader") return c_process_trace(cache._cache, reader._reader, start_req, max_req) diff --git a/pyproject.toml b/pyproject.toml index 0cdfd55..3c6c7a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,9 @@ classifiers = [ ] dependencies = [ "numpy>=1.20.0", - "boto3", # For S3 + "boto3", # For S3 + "pybind11>=2.10.0", + "pytest>=8.4.1", ] [project.optional-dependencies] @@ -36,6 +38,10 @@ dev = [ "ruff>=0.7.0", "mypy>=1.0.0", ] +all = [ + "xgboost", + "lightgbm" +] [tool.scikit-build] @@ -43,7 +49,7 @@ wheel.expand-macos-universal-tags = true build-dir = "build" cmake.build-type = "Release" cmake.args = ["-G", "Ninja"] -cmake.define = { CMAKE_OSX_DEPLOYMENT_TARGET = "14.0" } +cmake.define = { CMAKE_OSX_DEPLOYMENT_TARGET = "15.0" } cmake.version = ">=3.15" cmake.source-dir = "." install.strip = false @@ -74,18 +80,18 @@ build = ["cp39-*", "cp310-*", "cp311-*", "cp312-*", "cp313-*"] skip = ["*-win32", "*-manylinux_i686", "*-musllinux*", "pp*"] # Set the environment variable for the wheel build step. -environment = { LCS_BUILD_DIR = "{project}/src/libCacheSim/build", MACOSX_DEPLOYMENT_TARGET = "14.0" } +environment = { LCS_BUILD_DIR = "{project}/src/libCacheSim/build", MACOSX_DEPLOYMENT_TARGET = "15.0" } # Test that the wheel can be imported test-command = "python -c 'import libcachesim; print(\"Import successful\")'" [tool.cibuildwheel.linux] before-all = "yum install -y yum-utils && yum-config-manager --set-enabled crb && yum install -y ninja-build cmake libzstd-devel glib2-devel" -before-build = "git submodule update --init --recursive && rm -rf {project}/src/libCacheSim/build && cmake -S {project} -B {project}/src/libCacheSim/build -G Ninja && cmake --build {project}/src/libCacheSim/build" +before-build = "pip install pybind11 && git submodule update --init --recursive && python {project}/scripts/smart_build.py" [tool.cibuildwheel.macos] before-all = "brew install glib google-perftools argp-standalone xxhash llvm wget cmake ninja zstd xgboost lightgbm" -before-build = "git submodule update --init --recursive && rm -rf {project}/src/libCacheSim/build && cmake -S {project} -B {project}/src/libCacheSim/build -G Ninja -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 && cmake --build {project}/src/libCacheSim/build" +before-build = "pip install pybind11 && git submodule update --init --recursive && python {project}/scripts/smart_build.py" [tool.ruff] # Allow lines to be as long as 120. diff --git a/scripts/detect_deps.py b/scripts/detect_deps.py new file mode 100644 index 0000000..ab66642 --- /dev/null +++ b/scripts/detect_deps.py @@ -0,0 +1,174 @@ +""" +Detect dependencies for the project + +- pybind11 +- xgboost +- lightgbm +""" + +import sys +import subprocess + +def fix_pybind11(): + """Fix pybind11 installation""" + print("Checking pybind11 installation...") + try: + import pybind11 + print("✓ pybind11 is installed") + # Check CMake config + try: + cmake_dir = pybind11.get_cmake_dir() + print(f"✓ pybind11 CMake directory: {cmake_dir}") + return True + except Exception as e: + print(f"✗ pybind11 CMake config issue: {e}") + except ImportError: + print("✗ pybind11 is not installed") + print("Reinstalling pybind11...") + try: + subprocess.run([sys.executable, "-m", "pip", "install", "--force-reinstall", "pybind11"], check=True) + print("✓ pybind11 reinstalled successfully") + import pybind11 + cmake_dir = pybind11.get_cmake_dir() + print(f"✓ pybind11 CMake directory: {cmake_dir}") + return True + except Exception as e: + print(f"✗ pybind11 installation failed: {e}") + return False + +def fix_xgboost(): + """Fix xgboost installation""" + print("Checking xgboost installation...") + try: + import xgboost + print("✓ xgboost is installed") + # Try to find CMake directory (if available) + cmake_dir = getattr(xgboost, 'cmake_dir', None) + if cmake_dir: + print(f"✓ xgboost CMake directory: {cmake_dir}") + else: + # Try common install locations + import os + possible_dirs = [ + os.path.join(xgboost.__path__[0], 'cmake'), + os.path.join(xgboost.__path__[0], '..', 'cmake'), + '/usr/local/lib/cmake/xgboost', + '/usr/local/share/cmake/xgboost', + '/opt/homebrew/lib/cmake/xgboost', + ] + found = False + for d in possible_dirs: + if os.path.isdir(d): + print(f"✓ xgboost CMake directory: {os.path.abspath(d)}") + found = True + break + if not found: + print("✗ xgboost CMake directory not found (not required for Python usage, only for C++ linkage)") + return True + except ImportError: + print("✗ xgboost is not installed") + print("Reinstalling xgboost...") + try: + subprocess.run([sys.executable, "-m", "pip", "install", "--force-reinstall", "xgboost"], check=True) + print("✓ xgboost reinstalled successfully") + import xgboost + print("✓ xgboost is installed after reinstall") + # Repeat CMake dir check after reinstall + cmake_dir = getattr(xgboost, 'cmake_dir', None) + if cmake_dir: + print(f"✓ xgboost CMake directory: {cmake_dir}") + else: + import os + possible_dirs = [ + os.path.join(xgboost.__path__[0], 'cmake'), + os.path.join(xgboost.__path__[0], '..', 'cmake'), + '/usr/local/lib/cmake/xgboost', + '/usr/local/share/cmake/xgboost', + '/opt/homebrew/lib/cmake/xgboost', + ] + found = False + for d in possible_dirs: + if os.path.isdir(d): + print(f"✓ xgboost CMake directory: {os.path.abspath(d)}") + found = True + break + if not found: + print("✗ xgboost CMake directory not found (not required for Python usage, only for C++ linkage)") + return True + except Exception as e: + print(f"✗ xgboost installation failed: {e}") + return False + +def fix_lightgbm(): + """Fix lightgbm installation""" + print("Checking lightgbm installation...") + try: + import lightgbm + print("✓ lightgbm is installed") + # Try to find CMake directory (if available) + cmake_dir = getattr(lightgbm, 'cmake_dir', None) + if cmake_dir: + print(f"✓ lightgbm CMake directory: {cmake_dir}") + else: + import os + possible_dirs = [ + os.path.join(lightgbm.__path__[0], 'cmake'), + os.path.join(lightgbm.__path__[0], '..', 'cmake'), + '/usr/local/lib/cmake/LightGBM', + '/usr/local/share/cmake/LightGBM', + '/opt/homebrew/lib/cmake/LightGBM', + ] + found = False + for d in possible_dirs: + if os.path.isdir(d): + print(f"✓ lightgbm CMake directory: {os.path.abspath(d)}") + found = True + break + if not found: + print("✗ lightgbm CMake directory not found (not required for Python usage, only for C++ linkage)") + return True + except ImportError: + print("✗ lightgbm is not installed") + print("Reinstalling lightgbm...") + try: + subprocess.run([sys.executable, "-m", "pip", "install", "--force-reinstall", "lightgbm"], check=True) + print("✓ lightgbm reinstalled successfully") + import lightgbm + print("✓ lightgbm is installed after reinstall") + # Repeat CMake dir check after reinstall + cmake_dir = getattr(lightgbm, 'cmake_dir', None) + if cmake_dir: + print(f"✓ lightgbm CMake directory: {cmake_dir}") + else: + import os + possible_dirs = [ + os.path.join(lightgbm.__path__[0], 'cmake'), + os.path.join(lightgbm.__path__[0], '..', 'cmake'), + '/usr/local/lib/cmake/LightGBM', + '/usr/local/share/cmake/LightGBM', + '/opt/homebrew/lib/cmake/LightGBM', + ] + found = False + for d in possible_dirs: + if os.path.isdir(d): + print(f"✓ lightgbm CMake directory: {os.path.abspath(d)}") + found = True + break + if not found: + print("✗ lightgbm CMake directory not found (not required for Python usage, only for C++ linkage)") + return True + except Exception as e: + print(f"✗ lightgbm installation failed: {e}") + return False + +def detect_dependencies(): + """Detect dependencies for the project""" + print("Detecting dependencies...") + print(f"Python version: {sys.version}") + print(f"Python path: {sys.executable}") + fix_pybind11() + fix_xgboost() + fix_lightgbm() + +if __name__ == "__main__": + detect_dependencies() \ No newline at end of file diff --git a/scripts/install.sh b/scripts/install.sh index e0bee89..879704c 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -3,8 +3,9 @@ git submodule update --init --recursive # Build the main libCacheSim C++ library first echo "Building main libCacheSim library..." pushd src/libCacheSim +bash scripts/install_dependency.sh rm -rf build -cmake -G Ninja -B build # -DENABLE_3L_CACHE=ON +cmake -G Ninja -B build ninja -C build popd diff --git a/scripts/smart_build.py b/scripts/smart_build.py new file mode 100644 index 0000000..0efb783 --- /dev/null +++ b/scripts/smart_build.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +""" +Smart build script +Automatically handles version compatibility and dependency detection +""" + +import subprocess +import sys +import os +import platform + +def get_macos_deployment_target(): + """Get appropriate macOS deployment target""" + if sys.platform != "darwin": + return None + + try: + result = subprocess.run(["sw_vers", "-productVersion"], + capture_output=True, text=True, check=True) + macos_version = result.stdout.strip() + major_version = macos_version.split('.')[0] + + # Set deployment target to current version + deployment_target = f"{major_version}.0" + print(f"Detected macOS version: {macos_version}, set deployment target: {deployment_target}") + return deployment_target + except Exception as e: + print(f"Failed to detect macOS version, using default: {e}") + return "14.0" + +def check_dependency(module_name): + """Check if a Python module is installed""" + try: + __import__(module_name) + return True + except ImportError: + return False + +def fix_pybind11(): + """Fix pybind11 installation""" + print("Checking pybind11...") + subprocess.run([sys.executable, "scripts/fix_pybind11.py"], check=True) + +def build_with_flags(): + """Build according to dependencies""" + # Fix pybind11 + fix_pybind11() + + # Check ML dependencies + xgboost_available = check_dependency("xgboost") + lightgbm_available = check_dependency("lightgbm") + + print(f"XGBoost available: {xgboost_available}") + print(f"LightGBM available: {lightgbm_available}") + + # Build CMake args + cmake_args = ["-G", "Ninja"] + + # Add pybind11 path + try: + import pybind11 + pybind11_dir = pybind11.get_cmake_dir() + cmake_args.extend([f"-Dpybind11_DIR={pybind11_dir}"]) + print(f"Set pybind11 path: {pybind11_dir}") + except Exception as e: + print(f"Warning: failed to set pybind11 path: {e}") + + # Enable GLCache if XGBoost is available + if xgboost_available: + cmake_args.extend(["-DENABLE_GLCACHE=ON"]) + print("Enable GLCache (requires XGBoost)") + + # Enable LRB and 3LCache if LightGBM is available + if lightgbm_available: + cmake_args.extend(["-DENABLE_LRB=ON", "-DENABLE_3L_CACHE=ON"]) + print("Enable LRB and 3LCache (requires LightGBM)") + + # Set macOS deployment target + deployment_target = get_macos_deployment_target() + if deployment_target: + cmake_args.extend([f"-DCMAKE_OSX_DEPLOYMENT_TARGET={deployment_target}"]) + + # Build commands + build_dir = "src/libCacheSim/build" + source_dir = "." + + # Clean build directory + if os.path.exists(build_dir): + print("Cleaning build directory...") + subprocess.run(["rm", "-rf", build_dir], check=True) + + # Run CMake configure + cmake_cmd = ["cmake", "-S", source_dir, "-B", build_dir] + cmake_args + print(f"Running: {' '.join(cmake_cmd)}") + subprocess.run(cmake_cmd, check=True) + + # Run build + build_cmd = ["cmake", "--build", build_dir] + print(f"Running: {' '.join(build_cmd)}") + subprocess.run(build_cmd, check=True) + + print("✓ Build completed!") + +def main(): + print("=== libCacheSim Smart Build ===") + print(f"Platform: {platform.platform()}") + print(f"Python: {sys.version}") + print() + + try: + build_with_flags() + except subprocess.CalledProcessError as e: + print(f"✗ Build failed: {e}") + sys.exit(1) + except Exception as e: + print(f"✗ Build exception: {e}") + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/sync_version.py b/scripts/sync_version.py index 34d40c5..3cf035c 100644 --- a/scripts/sync_version.py +++ b/scripts/sync_version.py @@ -28,7 +28,7 @@ def read_main_version(): print(f"Error: {version_file} not found", file=sys.stderr) sys.exit(1) - with open(version_file, 'r') as f: + with open(version_file, "r") as f: version = f.read().strip() if not version: @@ -37,6 +37,7 @@ def read_main_version(): return version + def update_pyproject_toml(version): """Update pyproject.toml with the new version.""" project_root = get_project_root() @@ -47,7 +48,7 @@ def update_pyproject_toml(version): return False # Read current pyproject.toml - with open(pyproject_toml_path, 'r') as f: + with open(pyproject_toml_path, "r") as f: pyproject_data = f.read() # Update the version line in pyproject.toml, make it can match any version in version.txt, like "0.3.1" or "dev" @@ -60,10 +61,10 @@ def update_pyproject_toml(version): print(f"Python binding version already up to date: {version}") return False # replace the version line with the new version - pyproject_data = re.sub(r"version = \"(dev|[0-9]+\.[0-9]+\.[0-9]+)\"", f"version = \"{version}\"", pyproject_data) + pyproject_data = re.sub(r"version = \"(dev|[0-9]+\.[0-9]+\.[0-9]+)\"", f'version = "{version}"', pyproject_data) # Write back to file with proper formatting - with open(pyproject_toml_path, 'w') as f: + with open(pyproject_toml_path, "w") as f: f.write(pyproject_data) print(f"Updated Python version: {current_version} → {version}") diff --git a/src/export_reader.cpp b/src/export_reader.cpp index 468f542..5eb6ce5 100644 --- a/src/export_reader.cpp +++ b/src/export_reader.cpp @@ -180,7 +180,7 @@ void export_reader(py::module& m) { .def_property( "feature_fields", [](const reader_init_param_t& self) { - return py::array_t({self.n_feature_fields}, + return py::array_t(self.n_feature_fields, self.feature_fields); // copy to python }, [](reader_init_param_t& self, py::array_t arr) { diff --git a/tests/test_analyzer.py b/tests/test_analyzer.py index b492301..8712a8c 100644 --- a/tests/test_analyzer.py +++ b/tests/test_analyzer.py @@ -7,37 +7,41 @@ def test_analyzer_common(): """ Test the trace analyzer functionality. """ - + # Add debugging and error handling loader = DataLoader() loader.load("cache_dataset_oracleGeneral/2020_tencentBlock/1K/tencentBlock_1621.oracleGeneral.zst") - file_path = loader.get_cache_path("cache_dataset_oracleGeneral/2020_tencentBlock/1K/tencentBlock_1621.oracleGeneral.zst") + file_path = loader.get_cache_path( + "cache_dataset_oracleGeneral/2020_tencentBlock/1K/tencentBlock_1621.oracleGeneral.zst" + ) reader = TraceReader(file_path) # For this specific small dataset (only 4 objects), configure analysis options more conservatively # to avoid bounds issues with the analysis modules analysis_option = AnalysisOption( - req_rate=True, # Keep basic request rate analysis - access_pattern=False, # Disable access pattern analysis - size=True, # Keep size analysis - reuse=False, # Disable reuse analysis for small datasets - popularity=False, # Disable popularity analysis for small datasets (< 200 objects) - ttl=False, # Disable TTL analysis + req_rate=True, # Keep basic request rate analysis + access_pattern=False, # Disable access pattern analysis + size=True, # Keep size analysis + reuse=False, # Disable reuse analysis for small datasets + popularity=False, # Disable popularity analysis for small datasets (< 200 objects) + ttl=False, # Disable TTL analysis popularity_decay=False, # Disable popularity decay analysis - lifetime=False, # Disable lifetime analysis + lifetime=False, # Disable lifetime analysis create_future_reuse_ccdf=False, # Disable experimental features - prob_at_age=False, # Disable experimental features - size_change=False # Disable size change analysis + prob_at_age=False, # Disable experimental features + size_change=False, # Disable size change analysis ) - + # Set track_n_popular and track_n_hit to small values suitable for this dataset analysis_param = AnalysisParam( - track_n_popular=4, # Match the actual number of objects - track_n_hit=4 # Match the actual number of objects + track_n_popular=4, # Match the actual number of objects + track_n_hit=4, # Match the actual number of objects ) - analyzer = TraceAnalyzer(reader, "TestAnalyzerResults", analysis_option=analysis_option, analysis_param=analysis_param) + analyzer = TraceAnalyzer( + reader, "TestAnalyzerResults", analysis_option=analysis_option, analysis_param=analysis_param + ) analyzer.run() diff --git a/tests/test_reader.py b/tests/test_reader.py index a13570c..bb3cb7b 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -22,84 +22,71 @@ def test_basic_initialization(self): def test_zipf_distribution(self): """Test Zipf distribution request generation""" - reader = SyntheticReader( - num_of_req=1000, - obj_size=1024, - alpha=1.0, - dist="zipf", - num_objects=100, - seed=42 - ) - + reader = SyntheticReader(num_of_req=1000, obj_size=1024, alpha=1.0, dist="zipf", num_objects=100, seed=42) + # Test basic properties assert reader.get_num_of_req() == 1000 assert len(reader) == 1000 - + # Read some requests and verify they are valid req = Request() first_req = reader.read_one_req(req) assert first_req.obj_id >= 0 assert first_req.obj_size == 1024 - assert hasattr(first_req, 'op') # Just check it has op attribute + assert hasattr(first_req, "op") # Just check it has op attribute def test_uniform_distribution(self): """Test uniform distribution request generation""" - reader = SyntheticReader( - num_of_req=500, - obj_size=512, - dist="uniform", - num_objects=50, - seed=123 - ) - + reader = SyntheticReader(num_of_req=500, obj_size=512, dist="uniform", num_objects=50, seed=123) + assert reader.get_num_of_req() == 500 - + # Read some requests req = Request() for _ in range(10): read_req = reader.read_one_req(req) assert read_req.obj_size == 512 - assert hasattr(read_req, 'op') # Just check it has op attribute + assert hasattr(read_req, "op") # Just check it has op attribute def test_reader_iteration(self): """Test iteration over synthetic reader""" reader = SyntheticReader(num_of_req=50, obj_size=1024, seed=42) - + count = 0 for req in reader: assert req.obj_size == 1024 - assert hasattr(req, 'op') # Just check it has op attribute + assert hasattr(req, "op") # Just check it has op attribute count += 1 if count >= 10: # Only test first 10 for efficiency break - + assert count == 10 def test_reader_reset(self): """Test reader reset functionality""" reader = SyntheticReader(num_of_req=100, obj_size=1024, seed=42) - + # Read some requests req = Request() first_read = reader.read_one_req(req) reader.read_one_req(req) reader.read_one_req(req) - + # Reset and read again reader.reset() reset_read = reader.read_one_req(req) - + # Should get the same first request after reset assert first_read.obj_id == reset_read.obj_id def test_skip_requests(self): """Test skipping requests""" reader = SyntheticReader(num_of_req=100, obj_size=1024, seed=42) - + # Skip 10 requests skipped = reader.skip_n_req(10) assert skipped == 10 - + # Verify we can still read remaining requests req = Request() read_req = reader.read_one_req(req) @@ -108,15 +95,15 @@ def test_skip_requests(self): def test_clone_reader(self): """Test reader cloning""" reader = SyntheticReader(num_of_req=100, obj_size=1024, seed=42) - + # Read some requests req = Request() reader.read_one_req(req) reader.read_one_req(req) - + # Clone the reader cloned_reader = reader.clone() - + # Both readers should have same configuration assert cloned_reader.get_num_of_req() == reader.get_num_of_req() assert isinstance(cloned_reader, SyntheticReader) @@ -125,10 +112,10 @@ def test_invalid_parameters(self): """Test error handling for invalid parameters""" with pytest.raises(ValueError): SyntheticReader(num_of_req=0) # Invalid num_of_req - + with pytest.raises(ValueError): SyntheticReader(num_of_req=100, obj_size=0) # Invalid obj_size - + with pytest.raises(ValueError): SyntheticReader(num_of_req=100, alpha=-1.0) # Invalid alpha @@ -139,7 +126,7 @@ class TestTraceReader: def test_csv_trace_creation(self): """Test creating a CSV trace and reading it""" # Create a temporary CSV trace file - with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as f: + with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f: # Write CSV header and some sample data f.write("timestamp,obj_id,obj_size,op\n") f.write("1,100,1024,0\n") @@ -148,12 +135,12 @@ def test_csv_trace_creation(self): f.write("4,100,1024,0\n") # Repeat access f.write("5,103,4096,0\n") temp_file = f.name - + try: read_init_param = ReaderInitParam( - has_header=True, - delimiter=",", - obj_id_is_num=True, + has_header=True, + delimiter=",", + obj_id_is_num=True, ) read_init_param.time_field = 1 read_init_param.obj_id_field = 2 @@ -161,12 +148,8 @@ def test_csv_trace_creation(self): read_init_param.op_field = 4 # Create TraceReader - reader = TraceReader( - trace=temp_file, - trace_type=TraceType.CSV_TRACE, - reader_init_params=read_init_param - ) - + reader = TraceReader(trace=temp_file, trace_type=TraceType.CSV_TRACE, reader_init_params=read_init_param) + # Test basic properties assert reader.get_num_of_req() == 5 assert len(reader) == 5 @@ -174,13 +157,13 @@ def test_csv_trace_creation(self): # TODO(haocheng): check it # assert reader.csv_has_header == True # assert reader.csv_delimiter == "," - + # Read first request req = Request() first_req = reader.read_one_req(req) assert first_req.obj_id == 100 assert first_req.obj_size == 1024 - + finally: # Clean up os.unlink(temp_file) @@ -188,12 +171,12 @@ def test_csv_trace_creation(self): def test_trace_reader_iteration(self): """Test iteration over trace reader""" # Create temporary trace - with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as f: + with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f: f.write("timestamp,obj_id,obj_size,op\n") for i in range(10): - f.write(f"{i+1},{100+i},{1024*(i+1)},0\n") + f.write(f"{i + 1},{100 + i},{1024 * (i + 1)},0\n") temp_file = f.name - + try: read_init_param = ReaderInitParam( has_header=True, @@ -205,33 +188,29 @@ def test_trace_reader_iteration(self): read_init_param.obj_size_field = 3 read_init_param.op_field = 4 - reader = TraceReader( - trace=temp_file, - trace_type=TraceType.CSV_TRACE, - reader_init_params=read_init_param - ) - + reader = TraceReader(trace=temp_file, trace_type=TraceType.CSV_TRACE, reader_init_params=read_init_param) + # Read requests one by one instead of using list() req = Request() first_req = reader.read_one_req(req) assert first_req.obj_id == 100 assert first_req.obj_size == 1024 - + second_req = reader.read_one_req(req) assert second_req.obj_id == 101 assert second_req.obj_size == 2048 - + finally: os.unlink(temp_file) def test_trace_reader_reset_and_skip(self): """Test reset and skip functionality""" - with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as f: + with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f: f.write("timestamp,obj_id,obj_size,op\n") for i in range(20): - f.write(f"{i+1},{100+i},1024,0\n") + f.write(f"{i + 1},{100 + i},1024,0\n") temp_file = f.name - + try: read_init_param = ReaderInitParam( has_header=True, @@ -243,42 +222,38 @@ def test_trace_reader_reset_and_skip(self): read_init_param.obj_size_field = 3 read_init_param.op_field = 4 - reader = TraceReader( - trace=temp_file, - trace_type=TraceType.CSV_TRACE, - reader_init_params=read_init_param - ) - + reader = TraceReader(trace=temp_file, trace_type=TraceType.CSV_TRACE, reader_init_params=read_init_param) + # Read some requests req = Request() first_req = reader.read_one_req(req) reader.read_one_req(req) - + # Reset and verify we get same first request reader.reset() reset_req = reader.read_one_req(req) assert first_req.obj_id == reset_req.obj_id - + # Test skip functionality reader.reset() # Instead of using skip_n_req which might fail, just read requests one by one for _ in range(5): reader.read_one_req(req) - + next_req = reader.read_one_req(req) assert next_req.obj_id == 105 # Should be 6th request (100+5) - + finally: os.unlink(temp_file) def test_trace_reader_sampling(self): """Test sampling functionality""" - with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as f: + with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f: f.write("timestamp,obj_id,obj_size,op\n") for i in range(100): - f.write(f"{i+1},{100+i},1024,0\n") + f.write(f"{i + 1},{100 + i},1024,0\n") temp_file = f.name - + try: # Create reader with 50% sampling read_init_param = ReaderInitParam( @@ -291,37 +266,30 @@ def test_trace_reader_sampling(self): read_init_param.obj_size_field = 3 read_init_param.op_field = 4 - sampler = Sampler( - sample_ratio=0.5, - type=SamplerType.SPATIAL_SAMPLER - ) + sampler = Sampler(sample_ratio=0.5, type=SamplerType.SPATIAL_SAMPLER) read_init_param.sampler = sampler - reader = TraceReader( - trace=temp_file, - trace_type=TraceType.CSV_TRACE, - reader_init_params=read_init_param - ) - + reader = TraceReader(trace=temp_file, trace_type=TraceType.CSV_TRACE, reader_init_params=read_init_param) + # Test that sampling is configured assert reader.sampler is not None - + # Read a few requests to verify it works req = Request() first_req = reader.read_one_req(req) assert first_req.valid == True - + finally: os.unlink(temp_file) def test_trace_reader_clone(self): """Test trace reader cloning""" - with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as f: + with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f: f.write("timestamp,obj_id,obj_size,op\n") for i in range(5): - f.write(f"{i+1},{100+i},1024,0\n") + f.write(f"{i + 1},{100 + i},1024,0\n") temp_file = f.name - + try: read_init_param = ReaderInitParam( has_header=True, @@ -333,37 +301,33 @@ def test_trace_reader_clone(self): read_init_param.obj_size_field = 3 read_init_param.op_field = 4 - reader = TraceReader( - trace=temp_file, - trace_type=TraceType.CSV_TRACE, - reader_init_params=read_init_param - ) - + reader = TraceReader(trace=temp_file, trace_type=TraceType.CSV_TRACE, reader_init_params=read_init_param) + # Clone the reader cloned_reader = reader.clone() - + # Both should be TraceReader instances assert isinstance(cloned_reader, TraceReader) assert isinstance(reader, TraceReader) - + finally: os.unlink(temp_file) def test_invalid_sampling_ratio(self): """Test error handling for invalid sampling ratio""" - with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as f: + with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f: f.write("timestamp,obj_id,obj_size,op\n") f.write("1,100,1024,0\n") temp_file = f.name - + try: # Test that invalid sampling ratios are rejected by Sampler with pytest.raises(ValueError): Sampler(sample_ratio=1.5) # Invalid ratio > 1.0 - + with pytest.raises(ValueError): Sampler(sample_ratio=-0.1) # Invalid ratio < 0.0 - + finally: os.unlink(temp_file) @@ -374,13 +338,13 @@ class TestReaderCompatibility: def test_protocol_compliance(self): """Test that both readers implement the ReaderProtocol""" synthetic_reader = SyntheticReader(num_of_req=100, obj_size=1024) - + # Create a simple CSV trace for TraceReader - with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as f: + with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f: f.write("timestamp,obj_id,obj_size,op\n") f.write("1,100,1024,0\n") temp_file = f.name - + try: read_init_param = ReaderInitParam( has_header=True, @@ -393,23 +357,21 @@ def test_protocol_compliance(self): read_init_param.op_field = 4 trace_reader = TraceReader( - trace=temp_file, - trace_type=TraceType.CSV_TRACE, - reader_init_params=read_init_param + trace=temp_file, trace_type=TraceType.CSV_TRACE, reader_init_params=read_init_param ) - + # Both should implement the same interface readers = [synthetic_reader, trace_reader] - + for reader in readers: - assert hasattr(reader, 'get_num_of_req') - assert hasattr(reader, 'read_one_req') - assert hasattr(reader, 'reset') - assert hasattr(reader, 'close') - assert hasattr(reader, 'clone') - assert hasattr(reader, '__iter__') - assert hasattr(reader, '__len__') - + assert hasattr(reader, "get_num_of_req") + assert hasattr(reader, "read_one_req") + assert hasattr(reader, "reset") + assert hasattr(reader, "close") + assert hasattr(reader, "clone") + assert hasattr(reader, "__iter__") + assert hasattr(reader, "__len__") + # Test basic functionality - just check they return positive numbers try: num_req = reader.get_num_of_req() @@ -419,19 +381,19 @@ def test_protocol_compliance(self): except: # Some operations might fail, just skip for safety pass - + finally: os.unlink(temp_file) def test_request_format_consistency(self): """Test that both readers produce consistent Request objects""" synthetic_reader = SyntheticReader(num_of_req=10, obj_size=1024, seed=42) - - with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as f: + + with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f: f.write("timestamp,obj_id,obj_size,op\n") f.write("1,100,1024,0\n") temp_file = f.name - + try: read_init_param = ReaderInitParam( has_header=True, @@ -444,29 +406,27 @@ def test_request_format_consistency(self): read_init_param.op_field = 4 trace_reader = TraceReader( - trace=temp_file, - trace_type=TraceType.CSV_TRACE, - reader_init_params=read_init_param + trace=temp_file, trace_type=TraceType.CSV_TRACE, reader_init_params=read_init_param ) - + # Get requests from both readers req = Request() synthetic_req = synthetic_reader.read_one_req(req) trace_req = trace_reader.read_one_req(req) - + # Both should produce Request objects with same attributes - assert hasattr(synthetic_req, 'obj_id') - assert hasattr(synthetic_req, 'obj_size') - assert hasattr(synthetic_req, 'op') - assert hasattr(trace_req, 'obj_id') - assert hasattr(trace_req, 'obj_size') - assert hasattr(trace_req, 'op') - + assert hasattr(synthetic_req, "obj_id") + assert hasattr(synthetic_req, "obj_size") + assert hasattr(synthetic_req, "op") + assert hasattr(trace_req, "obj_id") + assert hasattr(trace_req, "obj_size") + assert hasattr(trace_req, "op") + # Both should have valid values assert synthetic_req.obj_size == 1024 assert trace_req.obj_size == 1024 - assert hasattr(synthetic_req, 'op') - assert hasattr(trace_req, 'op') - + assert hasattr(synthetic_req, "op") + assert hasattr(trace_req, "op") + finally: - os.unlink(temp_file) \ No newline at end of file + os.unlink(temp_file) diff --git a/uv.lock b/uv.lock index 8a1da3c..528bc6d 100644 --- a/uv.lock +++ b/uv.lock @@ -120,9 +120,16 @@ dependencies = [ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pybind11" }, + { name = "pytest" }, ] [package.optional-dependencies] +all = [ + { name = "lightgbm" }, + { name = "xgboost", version = "2.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "xgboost", version = "3.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] dev = [ { name = "mypy" }, { name = "pre-commit" }, @@ -136,14 +143,39 @@ test = [ [package.metadata] requires-dist = [ { name = "boto3" }, + { name = "lightgbm", marker = "extra == 'all'" }, { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.0.0" }, { name = "numpy", specifier = ">=1.20.0" }, { name = "pre-commit", marker = "extra == 'dev'" }, + { name = "pybind11", specifier = ">=2.10.0" }, + { name = "pytest", specifier = ">=8.4.1" }, { name = "pytest", marker = "extra == 'dev'" }, { name = "pytest", marker = "extra == 'test'" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.7.0" }, + { name = "xgboost", marker = "extra == 'all'" }, +] +provides-extras = ["test", "dev", "all"] + +[[package]] +name = "lightgbm" +version = "4.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "scipy", version = "1.16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/0b/a2e9f5c5da7ef047cc60cef37f86185088845e8433e54d2e7ed439cce8a3/lightgbm-4.6.0.tar.gz", hash = "sha256:cb1c59720eb569389c0ba74d14f52351b573af489f230032a1c9f314f8bab7fe", size = 1703705, upload-time = "2025-02-15T04:03:03.111Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/75/cffc9962cca296bc5536896b7e65b4a7cdeb8db208e71b9c0133c08f8f7e/lightgbm-4.6.0-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:b7a393de8a334d5c8e490df91270f0763f83f959574d504c7ccb9eee4aef70ed", size = 2010151, upload-time = "2025-02-15T04:02:50.961Z" }, + { url = "https://files.pythonhosted.org/packages/21/1b/550ee378512b78847930f5d74228ca1fdba2a7fbdeaac9aeccc085b0e257/lightgbm-4.6.0-py3-none-macosx_12_0_arm64.whl", hash = "sha256:2dafd98d4e02b844ceb0b61450a660681076b1ea6c7adb8c566dfd66832aafad", size = 1592172, upload-time = "2025-02-15T04:02:53.937Z" }, + { url = "https://files.pythonhosted.org/packages/64/41/4fbde2c3d29e25ee7c41d87df2f2e5eda65b431ee154d4d462c31041846c/lightgbm-4.6.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4d68712bbd2b57a0b14390cbf9376c1d5ed773fa2e71e099cac588703b590336", size = 3454567, upload-time = "2025-02-15T04:02:56.443Z" }, + { url = "https://files.pythonhosted.org/packages/42/86/dabda8fbcb1b00bcfb0003c3776e8ade1aa7b413dff0a2c08f457dace22f/lightgbm-4.6.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:cb19b5afea55b5b61cbb2131095f50538bd608a00655f23ad5d25ae3e3bf1c8d", size = 3569831, upload-time = "2025-02-15T04:02:58.925Z" }, + { url = "https://files.pythonhosted.org/packages/5e/23/f8b28ca248bb629b9e08f877dd2965d1994e1674a03d67cd10c5246da248/lightgbm-4.6.0-py3-none-win_amd64.whl", hash = "sha256:37089ee95664b6550a7189d887dbf098e3eadab03537e411f52c63c121e3ba4b", size = 1451509, upload-time = "2025-02-15T04:03:01.515Z" }, ] -provides-extras = ["test", "dev"] [[package]] name = "mypy" @@ -412,6 +444,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/e3/6690b3f85a05506733c7e90b577e4762517404ea78bab2ca3a5cb1aeb78d/numpy-2.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619", size = 12977811, upload-time = "2025-07-24T21:29:18.234Z" }, ] +[[package]] +name = "nvidia-nccl-cu12" +version = "2.27.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/cb/1e59f430360e73b203a0e9c9342a371b7d563e14cadb1cd76ed196d5f40c/nvidia_nccl_cu12-2.27.6-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:75f802521688026853ede67ec0337846214905a2f1571bb6e01ca3bb97c586ce", size = 322600048, upload-time = "2025-07-15T18:24:26.712Z" }, + { url = "https://files.pythonhosted.org/packages/af/a0/271bd709800f946e92128d9927ab7462559858a25b48f285a617d447bd48/nvidia_nccl_cu12-2.27.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8be9c0a7d7f95489f407593ad3842ba66bbb7c3370622c3592efb6dd67540968", size = 322547257, upload-time = "2025-07-15T18:24:45.24Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -464,6 +505,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, ] +[[package]] +name = "pybind11" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/83/698d120e257a116f2472c710932023ad779409adf2734d2e940f34eea2c5/pybind11-3.0.0.tar.gz", hash = "sha256:c3f07bce3ada51c3e4b76badfa85df11688d12c46111f9d242bc5c9415af7862", size = 544819, upload-time = "2025-07-10T16:52:09.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/9c/85f50a5476832c3efc67b6d7997808388236ae4754bf53e1749b3bc27577/pybind11-3.0.0-py3-none-any.whl", hash = "sha256:7c5cac504da5a701b5163f0e6a7ba736c713a096a5378383c5b4b064b753f607", size = 292118, upload-time = "2025-07-10T16:52:07.828Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -593,6 +643,171 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6d/4f/d073e09df851cfa251ef7840007d04db3293a0482ce607d2b993926089be/s3transfer-0.13.1-py3-none-any.whl", hash = "sha256:a981aa7429be23fe6dfc13e80e4020057cbab622b08c0315288758d67cabc724", size = 85308, upload-time = "2025-07-18T19:22:40.947Z" }, ] +[[package]] +name = "scipy" +version = "1.13.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/00/48c2f661e2816ccf2ecd77982f6605b2950afe60f60a52b4cbbc2504aa8f/scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c", size = 57210720, upload-time = "2024-05-23T03:29:26.079Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/59/41b2529908c002ade869623b87eecff3e11e3ce62e996d0bdcb536984187/scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca", size = 39328076, upload-time = "2024-05-23T03:19:01.687Z" }, + { url = "https://files.pythonhosted.org/packages/d5/33/f1307601f492f764062ce7dd471a14750f3360e33cd0f8c614dae208492c/scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f", size = 30306232, upload-time = "2024-05-23T03:19:09.089Z" }, + { url = "https://files.pythonhosted.org/packages/c0/66/9cd4f501dd5ea03e4a4572ecd874936d0da296bd04d1c45ae1a4a75d9c3a/scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989", size = 33743202, upload-time = "2024-05-23T03:19:15.138Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ba/7255e5dc82a65adbe83771c72f384d99c43063648456796436c9a5585ec3/scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f", size = 38577335, upload-time = "2024-05-23T03:19:21.984Z" }, + { url = "https://files.pythonhosted.org/packages/49/a5/bb9ded8326e9f0cdfdc412eeda1054b914dfea952bda2097d174f8832cc0/scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94", size = 38820728, upload-time = "2024-05-23T03:19:28.225Z" }, + { url = "https://files.pythonhosted.org/packages/12/30/df7a8fcc08f9b4a83f5f27cfaaa7d43f9a2d2ad0b6562cced433e5b04e31/scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54", size = 46210588, upload-time = "2024-05-23T03:19:35.661Z" }, + { url = "https://files.pythonhosted.org/packages/b4/15/4a4bb1b15bbd2cd2786c4f46e76b871b28799b67891f23f455323a0cdcfb/scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9", size = 39333805, upload-time = "2024-05-23T03:19:43.081Z" }, + { url = "https://files.pythonhosted.org/packages/ba/92/42476de1af309c27710004f5cdebc27bec62c204db42e05b23a302cb0c9a/scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326", size = 30317687, upload-time = "2024-05-23T03:19:48.799Z" }, + { url = "https://files.pythonhosted.org/packages/80/ba/8be64fe225360a4beb6840f3cbee494c107c0887f33350d0a47d55400b01/scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299", size = 33694638, upload-time = "2024-05-23T03:19:55.104Z" }, + { url = "https://files.pythonhosted.org/packages/36/07/035d22ff9795129c5a847c64cb43c1fa9188826b59344fee28a3ab02e283/scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa", size = 38569931, upload-time = "2024-05-23T03:20:01.82Z" }, + { url = "https://files.pythonhosted.org/packages/d9/10/f9b43de37e5ed91facc0cfff31d45ed0104f359e4f9a68416cbf4e790241/scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59", size = 38838145, upload-time = "2024-05-23T03:20:09.173Z" }, + { url = "https://files.pythonhosted.org/packages/4a/48/4513a1a5623a23e95f94abd675ed91cfb19989c58e9f6f7d03990f6caf3d/scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b", size = 46196227, upload-time = "2024-05-23T03:20:16.433Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7b/fb6b46fbee30fc7051913068758414f2721003a89dd9a707ad49174e3843/scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1", size = 39357301, upload-time = "2024-05-23T03:20:23.538Z" }, + { url = "https://files.pythonhosted.org/packages/dc/5a/2043a3bde1443d94014aaa41e0b50c39d046dda8360abd3b2a1d3f79907d/scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d", size = 30363348, upload-time = "2024-05-23T03:20:29.885Z" }, + { url = "https://files.pythonhosted.org/packages/e7/cb/26e4a47364bbfdb3b7fb3363be6d8a1c543bcd70a7753ab397350f5f189a/scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627", size = 33406062, upload-time = "2024-05-23T03:20:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/88/ab/6ecdc526d509d33814835447bbbeedbebdec7cca46ef495a61b00a35b4bf/scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884", size = 38218311, upload-time = "2024-05-23T03:20:42.086Z" }, + { url = "https://files.pythonhosted.org/packages/0b/00/9f54554f0f8318100a71515122d8f4f503b1a2c4b4cfab3b4b68c0eb08fa/scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16", size = 38442493, upload-time = "2024-05-23T03:20:48.292Z" }, + { url = "https://files.pythonhosted.org/packages/3e/df/963384e90733e08eac978cd103c34df181d1fec424de383cdc443f418dd4/scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949", size = 45910955, upload-time = "2024-05-23T03:20:55.091Z" }, + { url = "https://files.pythonhosted.org/packages/7f/29/c2ea58c9731b9ecb30b6738113a95d147e83922986b34c685b8f6eefde21/scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5", size = 39352927, upload-time = "2024-05-23T03:21:01.95Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c0/e71b94b20ccf9effb38d7147c0064c08c622309fd487b1b677771a97d18c/scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24", size = 30324538, upload-time = "2024-05-23T03:21:07.634Z" }, + { url = "https://files.pythonhosted.org/packages/6d/0f/aaa55b06d474817cea311e7b10aab2ea1fd5d43bc6a2861ccc9caec9f418/scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004", size = 33732190, upload-time = "2024-05-23T03:21:14.41Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/d0ad1a96f80962ba65e2ce1de6a1e59edecd1f0a7b55990ed208848012e0/scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d", size = 38612244, upload-time = "2024-05-23T03:21:21.827Z" }, + { url = "https://files.pythonhosted.org/packages/8d/02/1165905f14962174e6569076bcc3315809ae1291ed14de6448cc151eedfd/scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c", size = 38845637, upload-time = "2024-05-23T03:21:28.729Z" }, + { url = "https://files.pythonhosted.org/packages/3e/77/dab54fe647a08ee4253963bcd8f9cf17509c8ca64d6335141422fe2e2114/scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2", size = 46227440, upload-time = "2024-05-23T03:21:35.888Z" }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" }, + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, + { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" }, + { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" }, + { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" }, + { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" }, + { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" }, + { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" }, + { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" }, + { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" }, +] + +[[package]] +name = "scipy" +version = "1.16.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/4a/b927028464795439faec8eaf0b03b011005c487bb2d07409f28bf30879c4/scipy-1.16.1.tar.gz", hash = "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3", size = 30580861, upload-time = "2025-07-27T16:33:30.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/91/812adc6f74409b461e3a5fa97f4f74c769016919203138a3bf6fc24ba4c5/scipy-1.16.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:c033fa32bab91dc98ca59d0cf23bb876454e2bb02cbe592d5023138778f70030", size = 36552519, upload-time = "2025-07-27T16:26:29.658Z" }, + { url = "https://files.pythonhosted.org/packages/47/18/8e355edcf3b71418d9e9f9acd2708cc3a6c27e8f98fde0ac34b8a0b45407/scipy-1.16.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6e5c2f74e5df33479b5cd4e97a9104c511518fbd979aa9b8f6aec18b2e9ecae7", size = 28638010, upload-time = "2025-07-27T16:26:38.196Z" }, + { url = "https://files.pythonhosted.org/packages/d9/eb/e931853058607bdfbc11b86df19ae7a08686121c203483f62f1ecae5989c/scipy-1.16.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0a55ffe0ba0f59666e90951971a884d1ff6f4ec3275a48f472cfb64175570f77", size = 20909790, upload-time = "2025-07-27T16:26:43.93Z" }, + { url = "https://files.pythonhosted.org/packages/45/0c/be83a271d6e96750cd0be2e000f35ff18880a46f05ce8b5d3465dc0f7a2a/scipy-1.16.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f8a5d6cd147acecc2603fbd382fed6c46f474cccfcf69ea32582e033fb54dcfe", size = 23513352, upload-time = "2025-07-27T16:26:50.017Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bf/fe6eb47e74f762f933cca962db7f2c7183acfdc4483bd1c3813cfe83e538/scipy-1.16.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb18899127278058bcc09e7b9966d41a5a43740b5bb8dcba401bd983f82e885b", size = 33534643, upload-time = "2025-07-27T16:26:57.503Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ba/63f402e74875486b87ec6506a4f93f6d8a0d94d10467280f3d9d7837ce3a/scipy-1.16.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adccd93a2fa937a27aae826d33e3bfa5edf9aa672376a4852d23a7cd67a2e5b7", size = 35376776, upload-time = "2025-07-27T16:27:06.639Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b4/04eb9d39ec26a1b939689102da23d505ea16cdae3dbb18ffc53d1f831044/scipy-1.16.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:18aca1646a29ee9a0625a1be5637fa798d4d81fdf426481f06d69af828f16958", size = 35698906, upload-time = "2025-07-27T16:27:14.943Z" }, + { url = "https://files.pythonhosted.org/packages/04/d6/bb5468da53321baeb001f6e4e0d9049eadd175a4a497709939128556e3ec/scipy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d85495cef541729a70cdddbbf3e6b903421bc1af3e8e3a9a72a06751f33b7c39", size = 38129275, upload-time = "2025-07-27T16:27:23.873Z" }, + { url = "https://files.pythonhosted.org/packages/c4/94/994369978509f227cba7dfb9e623254d0d5559506fe994aef4bea3ed469c/scipy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:226652fca853008119c03a8ce71ffe1b3f6d2844cc1686e8f9806edafae68596", size = 38644572, upload-time = "2025-07-27T16:27:32.637Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d9/ec4864f5896232133f51382b54a08de91a9d1af7a76dfa372894026dfee2/scipy-1.16.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c", size = 36575194, upload-time = "2025-07-27T16:27:41.321Z" }, + { url = "https://files.pythonhosted.org/packages/5c/6d/40e81ecfb688e9d25d34a847dca361982a6addf8e31f0957b1a54fbfa994/scipy-1.16.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04", size = 28594590, upload-time = "2025-07-27T16:27:49.204Z" }, + { url = "https://files.pythonhosted.org/packages/0e/37/9f65178edfcc629377ce9a64fc09baebea18c80a9e57ae09a52edf84880b/scipy-1.16.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919", size = 20866458, upload-time = "2025-07-27T16:27:54.98Z" }, + { url = "https://files.pythonhosted.org/packages/2c/7b/749a66766871ea4cb1d1ea10f27004db63023074c22abed51f22f09770e0/scipy-1.16.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921", size = 23539318, upload-time = "2025-07-27T16:28:01.604Z" }, + { url = "https://files.pythonhosted.org/packages/c4/db/8d4afec60eb833a666434d4541a3151eedbf2494ea6d4d468cbe877f00cd/scipy-1.16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725", size = 33292899, upload-time = "2025-07-27T16:28:09.147Z" }, + { url = "https://files.pythonhosted.org/packages/51/1e/79023ca3bbb13a015d7d2757ecca3b81293c663694c35d6541b4dca53e98/scipy-1.16.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618", size = 35162637, upload-time = "2025-07-27T16:28:17.535Z" }, + { url = "https://files.pythonhosted.org/packages/b6/49/0648665f9c29fdaca4c679182eb972935b3b4f5ace41d323c32352f29816/scipy-1.16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d", size = 35490507, upload-time = "2025-07-27T16:28:25.705Z" }, + { url = "https://files.pythonhosted.org/packages/62/8f/66cbb9d6bbb18d8c658f774904f42a92078707a7c71e5347e8bf2f52bb89/scipy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119", size = 37923998, upload-time = "2025-07-27T16:28:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/14/c3/61f273ae550fbf1667675701112e380881905e28448c080b23b5a181df7c/scipy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a", size = 38508060, upload-time = "2025-07-27T16:28:43.242Z" }, + { url = "https://files.pythonhosted.org/packages/93/0b/b5c99382b839854a71ca9482c684e3472badc62620287cbbdab499b75ce6/scipy-1.16.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f", size = 36533717, upload-time = "2025-07-27T16:28:51.706Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e5/69ab2771062c91e23e07c12e7d5033a6b9b80b0903ee709c3c36b3eb520c/scipy-1.16.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb", size = 28570009, upload-time = "2025-07-27T16:28:57.017Z" }, + { url = "https://files.pythonhosted.org/packages/f4/69/bd75dbfdd3cf524f4d753484d723594aed62cfaac510123e91a6686d520b/scipy-1.16.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c", size = 20841942, upload-time = "2025-07-27T16:29:01.152Z" }, + { url = "https://files.pythonhosted.org/packages/ea/74/add181c87663f178ba7d6144b370243a87af8476664d5435e57d599e6874/scipy-1.16.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608", size = 23498507, upload-time = "2025-07-27T16:29:05.202Z" }, + { url = "https://files.pythonhosted.org/packages/1d/74/ece2e582a0d9550cee33e2e416cc96737dce423a994d12bbe59716f47ff1/scipy-1.16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f", size = 33286040, upload-time = "2025-07-27T16:29:10.201Z" }, + { url = "https://files.pythonhosted.org/packages/e4/82/08e4076df538fb56caa1d489588d880ec7c52d8273a606bb54d660528f7c/scipy-1.16.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b", size = 35176096, upload-time = "2025-07-27T16:29:17.091Z" }, + { url = "https://files.pythonhosted.org/packages/fa/79/cd710aab8c921375711a8321c6be696e705a120e3011a643efbbcdeeabcc/scipy-1.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45", size = 35490328, upload-time = "2025-07-27T16:29:22.928Z" }, + { url = "https://files.pythonhosted.org/packages/71/73/e9cc3d35ee4526d784520d4494a3e1ca969b071fb5ae5910c036a375ceec/scipy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65", size = 37939921, upload-time = "2025-07-27T16:29:29.108Z" }, + { url = "https://files.pythonhosted.org/packages/21/12/c0efd2941f01940119b5305c375ae5c0fcb7ec193f806bd8f158b73a1782/scipy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab", size = 38479462, upload-time = "2025-07-27T16:30:24.078Z" }, + { url = "https://files.pythonhosted.org/packages/7a/19/c3d08b675260046a991040e1ea5d65f91f40c7df1045fffff412dcfc6765/scipy-1.16.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6", size = 36938832, upload-time = "2025-07-27T16:29:35.057Z" }, + { url = "https://files.pythonhosted.org/packages/81/f2/ce53db652c033a414a5b34598dba6b95f3d38153a2417c5a3883da429029/scipy-1.16.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27", size = 29093084, upload-time = "2025-07-27T16:29:40.201Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ae/7a10ff04a7dc15f9057d05b33737ade244e4bd195caa3f7cc04d77b9e214/scipy-1.16.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7", size = 21365098, upload-time = "2025-07-27T16:29:44.295Z" }, + { url = "https://files.pythonhosted.org/packages/36/ac/029ff710959932ad3c2a98721b20b405f05f752f07344622fd61a47c5197/scipy-1.16.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6", size = 23896858, upload-time = "2025-07-27T16:29:48.784Z" }, + { url = "https://files.pythonhosted.org/packages/71/13/d1ef77b6bd7898720e1f0b6b3743cb945f6c3cafa7718eaac8841035ab60/scipy-1.16.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4", size = 33438311, upload-time = "2025-07-27T16:29:54.164Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e0/e64a6821ffbb00b4c5b05169f1c1fddb4800e9307efe3db3788995a82a2c/scipy-1.16.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3", size = 35279542, upload-time = "2025-07-27T16:30:00.249Z" }, + { url = "https://files.pythonhosted.org/packages/57/59/0dc3c8b43e118f1e4ee2b798dcc96ac21bb20014e5f1f7a8e85cc0653bdb/scipy-1.16.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7", size = 35667665, upload-time = "2025-07-27T16:30:05.916Z" }, + { url = "https://files.pythonhosted.org/packages/45/5f/844ee26e34e2f3f9f8febb9343748e72daeaec64fe0c70e9bf1ff84ec955/scipy-1.16.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc", size = 38045210, upload-time = "2025-07-27T16:30:11.655Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d7/210f2b45290f444f1de64bc7353aa598ece9f0e90c384b4a156f9b1a5063/scipy-1.16.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39", size = 38593661, upload-time = "2025-07-27T16:30:17.825Z" }, + { url = "https://files.pythonhosted.org/packages/81/ea/84d481a5237ed223bd3d32d6e82d7a6a96e34756492666c260cef16011d1/scipy-1.16.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318", size = 36525921, upload-time = "2025-07-27T16:30:30.081Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9f/d9edbdeff9f3a664807ae3aea383e10afaa247e8e6255e6d2aa4515e8863/scipy-1.16.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc", size = 28564152, upload-time = "2025-07-27T16:30:35.336Z" }, + { url = "https://files.pythonhosted.org/packages/3b/95/8125bcb1fe04bc267d103e76516243e8d5e11229e6b306bda1024a5423d1/scipy-1.16.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8", size = 20836028, upload-time = "2025-07-27T16:30:39.421Z" }, + { url = "https://files.pythonhosted.org/packages/77/9c/bf92e215701fc70bbcd3d14d86337cf56a9b912a804b9c776a269524a9e9/scipy-1.16.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e", size = 23489666, upload-time = "2025-07-27T16:30:43.663Z" }, + { url = "https://files.pythonhosted.org/packages/5e/00/5e941d397d9adac41b02839011594620d54d99488d1be5be755c00cde9ee/scipy-1.16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0", size = 33358318, upload-time = "2025-07-27T16:30:48.982Z" }, + { url = "https://files.pythonhosted.org/packages/0e/87/8db3aa10dde6e3e8e7eb0133f24baa011377d543f5b19c71469cf2648026/scipy-1.16.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b", size = 35185724, upload-time = "2025-07-27T16:30:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/89/b4/6ab9ae443216807622bcff02690262d8184078ea467efee2f8c93288a3b1/scipy-1.16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731", size = 35554335, upload-time = "2025-07-27T16:30:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/9c/9a/d0e9dc03c5269a1afb60661118296a32ed5d2c24298af61b676c11e05e56/scipy-1.16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3", size = 37960310, upload-time = "2025-07-27T16:31:06.151Z" }, + { url = "https://files.pythonhosted.org/packages/5e/00/c8f3130a50521a7977874817ca89e0599b1b4ee8e938bad8ae798a0e1f0d/scipy-1.16.1-cp314-cp314-win_amd64.whl", hash = "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19", size = 39319239, upload-time = "2025-07-27T16:31:59.942Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f2/1ca3eda54c3a7e4c92f6acef7db7b3a057deb135540d23aa6343ef8ad333/scipy-1.16.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65", size = 36939460, upload-time = "2025-07-27T16:31:11.865Z" }, + { url = "https://files.pythonhosted.org/packages/80/30/98c2840b293a132400c0940bb9e140171dcb8189588619048f42b2ce7b4f/scipy-1.16.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2", size = 29093322, upload-time = "2025-07-27T16:31:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/c1/e6/1e6e006e850622cf2a039b62d1a6ddc4497d4851e58b68008526f04a9a00/scipy-1.16.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d", size = 21365329, upload-time = "2025-07-27T16:31:21.188Z" }, + { url = "https://files.pythonhosted.org/packages/8e/02/72a5aa5b820589dda9a25e329ca752842bfbbaf635e36bc7065a9b42216e/scipy-1.16.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695", size = 23897544, upload-time = "2025-07-27T16:31:25.408Z" }, + { url = "https://files.pythonhosted.org/packages/2b/dc/7122d806a6f9eb8a33532982234bed91f90272e990f414f2830cfe656e0b/scipy-1.16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86", size = 33442112, upload-time = "2025-07-27T16:31:30.62Z" }, + { url = "https://files.pythonhosted.org/packages/24/39/e383af23564daa1021a5b3afbe0d8d6a68ec639b943661841f44ac92de85/scipy-1.16.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff", size = 35286594, upload-time = "2025-07-27T16:31:36.112Z" }, + { url = "https://files.pythonhosted.org/packages/95/47/1a0b0aff40c3056d955f38b0df5d178350c3d74734ec54f9c68d23910be5/scipy-1.16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4", size = 35665080, upload-time = "2025-07-27T16:31:42.025Z" }, + { url = "https://files.pythonhosted.org/packages/64/df/ce88803e9ed6e27fe9b9abefa157cf2c80e4fa527cf17ee14be41f790ad4/scipy-1.16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3", size = 38050306, upload-time = "2025-07-27T16:31:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6c/a76329897a7cae4937d403e623aa6aaea616a0bb5b36588f0b9d1c9a3739/scipy-1.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998", size = 39427705, upload-time = "2025-07-27T16:31:53.96Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -688,3 +903,52 @@ sdist = { url = "https://files.pythonhosted.org/packages/a9/96/0834f30fa08dca373 wheels = [ { url = "https://files.pythonhosted.org/packages/5c/c6/f8f28009920a736d0df434b52e9feebfb4d702ba942f15338cb4a83eafc1/virtualenv-20.32.0-py3-none-any.whl", hash = "sha256:2c310aecb62e5aa1b06103ed7c2977b81e042695de2697d01017ff0f1034af56", size = 6057761, upload-time = "2025-07-21T04:09:48.059Z" }, ] + +[[package]] +name = "xgboost" +version = "2.1.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "nvidia-nccl-cu12", marker = "python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux'" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/5e/860a1ef13ce38db8c257c83e138be64bcffde8f401e84bf1e2e91838afa3/xgboost-2.1.4.tar.gz", hash = "sha256:ab84c4bbedd7fae1a26f61e9dd7897421d5b08454b51c6eb072abc1d346d08d7", size = 1091127, upload-time = "2025-02-06T18:18:20.192Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/fe/7a1d2342c2e93f22b41515e02b73504c7809247b16ae395bd2ee7ef11e19/xgboost-2.1.4-py3-none-macosx_10_15_x86_64.macosx_11_0_x86_64.macosx_12_0_x86_64.whl", hash = "sha256:78d88da184562deff25c820d943420342014dd55e0f4c017cc4563c2148df5ee", size = 2140692, upload-time = "2025-02-06T18:16:59.23Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b6/653a70910739f127adffbefb688ebc22b51139292757de7c22b1e04ce792/xgboost-2.1.4-py3-none-macosx_12_0_arm64.whl", hash = "sha256:523db01d4e74b05c61a985028bde88a4dd380eadc97209310621996d7d5d14a7", size = 1939418, upload-time = "2025-02-06T18:17:02.494Z" }, + { url = "https://files.pythonhosted.org/packages/43/06/905fee34c10fb0d0c3baa15106413b76f360d8e958765ec57c9eddf762fa/xgboost-2.1.4-py3-none-manylinux2014_aarch64.whl", hash = "sha256:57c7e98111aceef4b689d7d2ce738564a1f7fe44237136837a47847b8b33bade", size = 4442052, upload-time = "2025-02-06T18:17:04.029Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6a/41956f91ab984f2fa44529b2551d825a20d33807eba051a60d06ede2a87c/xgboost-2.1.4-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1343a512e634822eab30d300bfc00bf777dc869d881cc74854b42173cfcdb14", size = 4533170, upload-time = "2025-02-06T18:17:05.753Z" }, + { url = "https://files.pythonhosted.org/packages/b1/53/37032dca20dae7a88ad1907f817a81f232ca6e935f0c28c98db3c0a0bd22/xgboost-2.1.4-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:d366097d0db047315736f46af852feaa907f6d7371716af741cdce488ae36d20", size = 4206715, upload-time = "2025-02-06T18:17:08.448Z" }, + { url = "https://files.pythonhosted.org/packages/e4/3c/e3a93bfa7e8693c825df5ec02a40f7ff5f0950e02198b1e85da9315a8d47/xgboost-2.1.4-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:8df6da72963969ab2bf49a520c3e147b1e15cbeddd3aa0e3e039b3532c739339", size = 223642416, upload-time = "2025-02-06T18:17:25.08Z" }, + { url = "https://files.pythonhosted.org/packages/43/80/0b5a2dfcf5b4da27b0b68d2833f05d77e1a374d43db951fca200a1f12a52/xgboost-2.1.4-py3-none-win_amd64.whl", hash = "sha256:8bbfe4fedc151b83a52edbf0de945fd94358b09a81998f2945ad330fd5f20cd6", size = 124910381, upload-time = "2025-02-06T18:17:43.202Z" }, +] + +[[package]] +name = "xgboost" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "nvidia-nccl-cu12", marker = "python_full_version >= '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "scipy", version = "1.16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/42/e6abc9e8c65033e5ff4117efc765e3d670c81c64ebd40ca6283bf4536994/xgboost-3.0.2.tar.gz", hash = "sha256:0ea95fef12313f8563458bbf49458db434d620af27b1991ddb8f46806cb305a5", size = 1159083, upload-time = "2025-05-25T09:09:11.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/6b/f47143ecab6313272497f324ffe2eafaf2851c0781a9022040adf80f9aab/xgboost-3.0.2-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:923f46cd1b25c0a39fc98e969fa0a72a1a84feb7f55797cb3385962cd8d3b2d4", size = 2246653, upload-time = "2025-05-25T09:09:35.431Z" }, + { url = "https://files.pythonhosted.org/packages/09/c9/5f0be8e51d55df60a1bd7d09e7b05380e04c38de9554105f6cacffac3886/xgboost-3.0.2-py3-none-macosx_12_0_arm64.whl", hash = "sha256:5c4e377c86df815669939646b3abe7a20559e4d4c0f5c2ab10c31252e7a9d7d9", size = 2025769, upload-time = "2025-05-25T09:09:37.22Z" }, + { url = "https://files.pythonhosted.org/packages/c0/eb/4b5036a16628dc375544ba5375768ddc3653a3372af6f947d73d11d1c3f2/xgboost-3.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:e9acf97b3b2a628b33f1dc80ee3f16a658e1f9f43c4ed2aa85b0a824c87dbde5", size = 4841549, upload-time = "2025-05-25T09:09:41.172Z" }, + { url = "https://files.pythonhosted.org/packages/db/71/347f78ac21eb9221231bebf7d7a3eaea20b09377d9d602cee15fe9c7aeba/xgboost-3.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:7d1ad8c5ae361161ce5288a04916c89d13d247b9a98e25c4b3983783cfad0377", size = 4904451, upload-time = "2025-05-25T09:09:44.273Z" }, + { url = "https://files.pythonhosted.org/packages/47/a4/949c50325c6417bfae2b846c43f4a8ad6557278d26b6a526c5c22f2204aa/xgboost-3.0.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:a112df38f2faaae31f1c00d373ff35fb5965a65e74de2eea9081dbef7a9ddffe", size = 4603350, upload-time = "2025-05-25T09:09:46.497Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f5/1b5d88e5a65168b435e8339b53d027e3e7adecb0c7d157bc86d18f78471b/xgboost-3.0.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:d534242489265621397ff403bb1c6235d2e6c66938639239fdf2d6b39d27e339", size = 253887220, upload-time = "2025-05-25T09:10:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/29/22/e3ff2dfafe862a91733dfa0aecdb4794aa1d9a18e09a14e118bde0cbc2db/xgboost-3.0.2-py3-none-win_amd64.whl", hash = "sha256:b4c89b71d134da9fa6318e3c9f5459317d1013b4d57059d10ed2840750e2f7e1", size = 149974575, upload-time = "2025-05-25T09:11:23.554Z" }, +]