diff --git a/.gitignore b/.gitignore index 505da3271..651960faf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ __pycache__ dist **/*.egg-info +uv.lock # Log files *.out diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3dfe924b0..e7916aee9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: files: '^src/.*\.py$' - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.2 + rev: v1.14.1 hooks: - id: mypy files: | @@ -43,7 +43,7 @@ repos: - "--show-traceback" - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.29.3 + rev: 0.31.0 hooks: - id: check-github-workflows files: '^github/workflows/.*\.ya?ml$' @@ -52,7 +52,7 @@ repos: files: '^\.github/dependabot\.ya?ml$' - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.6.9 + rev: v0.8.6 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix, --no-cache] diff --git a/neps/__init__.py b/neps/__init__.py index 4e59493f9..52ee91f73 100644 --- a/neps/__init__.py +++ b/neps/__init__.py @@ -20,23 +20,23 @@ __all__ = [ "Architecture", - "Integer", - "Float", - "Categorical", - "Constant", - "Function", "ArchitectureParameter", + "Categorical", "CategoricalParameter", + "Constant", "ConstantParameter", + "Float", "FloatParameter", - "IntegerParameter", + "Function", "FunctionParameter", - "run", - "plot", - "get_summary_dict", - "status", "GraphGrammar", "GraphGrammarCell", "GraphGrammarRepetitive", + "Integer", + "IntegerParameter", + "get_summary_dict", + "plot", + "run", + "status", "tblogger", ] diff --git a/neps/api.py b/neps/api.py index 215fb6aea..ac80e4a34 100644 --- a/neps/api.py +++ b/neps/api.py @@ -44,6 +44,7 @@ def run( objective_to_minimize_value_on_error: None | float = Default(None), cost_value_on_error: None | float = Default(None), pre_load_hooks: Iterable | None = Default(None), + sample_batch_size: int | None = Default(None), searcher: ( Literal[ "default", @@ -98,6 +99,8 @@ def run( cost_value_on_error: Setting this and objective_to_minimize_value_on_error to any float will supress any error and will use given cost value instead. default: None pre_load_hooks: List of functions that will be called before load_results(). + sample_batch_size: The number of samples to ask for in a single call to the + optimizer. searcher: Which optimizer to use. Can be a string identifier, an instance of BaseOptimizer, or a Path to a custom optimizer. **searcher_kwargs: Will be passed to the searcher. This is usually only needed by @@ -236,6 +239,7 @@ def run( ignore_errors=settings.ignore_errors, overwrite_optimization_dir=settings.overwrite_working_directory, pre_load_hooks=settings.pre_load_hooks, + sample_batch_size=settings.sample_batch_size, ) if settings.post_run_summary: @@ -278,7 +282,8 @@ def _run_args( "mobster", "asha", ] - | BaseOptimizer | dict + | BaseOptimizer + | dict ) = "default", **searcher_kwargs, ) -> tuple[BaseOptimizer, dict]: diff --git a/neps/env.py b/neps/env.py index f7ea41678..07b6c86c1 100644 --- a/neps/env.py +++ b/neps/env.py @@ -4,7 +4,7 @@ import os from collections.abc import Callable -from typing import Any, TypeVar +from typing import Any, Literal, TypeVar T = TypeVar("T") V = TypeVar("V") @@ -28,6 +28,38 @@ def is_nullable(e: str) -> bool: return e.lower() in ("none", "n", "null") +def yaml_or_json(e: str) -> Literal["yaml", "json"]: + """Check if an environment variable is either yaml or json.""" + if e.lower() in ("yaml", "json"): + return e.lower() # type: ignore + raise ValueError(f"Expected 'yaml' or 'json', got '{e}'.") + + +LINUX_FILELOCK_FUNCTION = get_env( + "NEPS_LINUX_FILELOCK_FUNCTION", + parse=str, + default="lockf", +) +MAX_RETRIES_GET_NEXT_TRIAL = get_env( + "NEPS_MAX_RETRIES_GET_NEXT_TRIAL", + parse=int, + default=10, +) +MAX_RETRIES_SET_EVALUATING = get_env( + "NEPS_MAX_RETRIES_SET_EVALUATING", + parse=int, + default=10, +) +MAX_RETRIES_CREATE_LOAD_STATE = get_env( + "NEPS_MAX_RETRIES_CREATE_LOAD_STATE", + parse=int, + default=10, +) +MAX_RETRIES_WORKER_CHECK_SHOULD_STOP = get_env( + "NEPS_MAX_RETRIES_WORKER_CHECK_SHOULD_STOP", + parse=int, + default=3, +) TRIAL_FILELOCK_POLL = get_env( "NEPS_TRIAL_FILELOCK_POLL", parse=float, @@ -38,40 +70,31 @@ def is_nullable(e: str) -> bool: parse=lambda e: None if is_nullable(e) else float(e), default=120, ) - -SEED_SNAPSHOT_FILELOCK_POLL = get_env( - "NEPS_SEED_SNAPSHOT_FILELOCK_POLL", +FS_SYNC_GRACE_BASE = get_env( + "NEPS_FS_SYNC_GRACE_BASE", parse=float, - default=0.05, + default=0.00, # Keep it low initially to not punish synced os ) -SEED_SNAPSHOT_FILELOCK_TIMEOUT = get_env( - "NEPS_SEED_SNAPSHOT_FILELOCK_TIMEOUT", - parse=lambda e: None if is_nullable(e) else float(e), - default=120, -) - -OPTIMIZER_INFO_FILELOCK_POLL = get_env( - "NEPS_OPTIMIZER_INFO_FILELOCK_POLL", +FS_SYNC_GRACE_INC = get_env( + "NEPS_FS_SYNC_GRACE_INC", parse=float, - default=0.05, -) -OPTIMIZER_INFO_FILELOCK_TIMEOUT = get_env( - "NEPS_OPTIMIZER_INFO_FILELOCK_TIMEOUT", - parse=lambda e: None if is_nullable(e) else float(e), - default=120, + default=0.1, ) -OPTIMIZER_STATE_FILELOCK_POLL = get_env( - "NEPS_OPTIMIZER_STATE_FILELOCK_POLL", +# NOTE: We want this to be greater than the trials filelock, so that +# anything requesting to just update the trials is more likely to obtain it +# as those operations tend to be faster than something that requires optimizer +# state. +STATE_FILELOCK_POLL = get_env( + "NEPS_STATE_FILELOCK_POLL", parse=float, - default=0.05, + default=0.20, ) -OPTIMIZER_STATE_FILELOCK_TIMEOUT = get_env( - "NEPS_OPTIMIZER_STATE_FILELOCK_TIMEOUT", +STATE_FILELOCK_TIMEOUT = get_env( + "NEPS_STATE_FILELOCK_TIMEOUT", parse=lambda e: None if is_nullable(e) else float(e), default=120, ) - GLOBAL_ERR_FILELOCK_POLL = get_env( "NEPS_GLOBAL_ERR_FILELOCK_POLL", parse=float, @@ -82,3 +105,13 @@ def is_nullable(e: str) -> bool: parse=lambda e: None if is_nullable(e) else float(e), default=120, ) +TRIAL_CACHE_MAX_UPDATES_BEFORE_CONSOLIDATION = get_env( + "NEPS_TRIAL_CACHE_MAX_UPDATES_BEFORE_CONSOLIDATION", + parse=int, + default=10, +) +CONFIG_SERIALIZE_FORMAT: Literal["yaml", "json"] = get_env( # type: ignore + "NEPS_CONFIG_SERIALIZE_FORMAT", + parse=yaml_or_json, + default="yaml", +) diff --git a/neps/exceptions.py b/neps/exceptions.py index 7054d7c65..c0a3e26a9 100644 --- a/neps/exceptions.py +++ b/neps/exceptions.py @@ -2,6 +2,8 @@ from __future__ import annotations +from typing import Any + class NePSError(Exception): """Base class for all NePS exceptions. @@ -11,35 +13,23 @@ class NePSError(Exception): """ -class VersionMismatchError(NePSError): - """Raised when the version of a resource does not match the expected version.""" - - -class VersionedResourceAlreadyExistsError(NePSError): - """Raised when a version already exists when trying to create a new versioned - data. - """ - - -class VersionedResourceRemovedError(NePSError): - """Raised when a version already exists when trying to create a new versioned - data. - """ - - -class VersionedResourceDoesNotExistsError(NePSError): - """Raised when a versioned resource does not exist at a location.""" - - class LockFailedError(NePSError): """Raised when a lock cannot be acquired.""" -class TrialAlreadyExistsError(VersionedResourceAlreadyExistsError): +class TrialAlreadyExistsError(NePSError): """Raised when a trial already exists in the store.""" + def __init__(self, trial_id: str, *args: Any) -> None: + """Initialize the exception with the trial id.""" + super().__init__(trial_id, *args) + self.trial_id = trial_id + + def __str__(self) -> str: + return f"Trial with id {self.trial_id} already exists!" + -class TrialNotFoundError(VersionedResourceDoesNotExistsError): +class TrialNotFoundError(NePSError): """Raised when a trial already exists in the store.""" diff --git a/neps/optimizers/base_optimizer.py b/neps/optimizers/base_optimizer.py index a7ce9bd1a..06adef711 100644 --- a/neps/optimizers/base_optimizer.py +++ b/neps/optimizers/base_optimizer.py @@ -111,17 +111,17 @@ def __init__( def ask( self, trials: Mapping[str, Trial], - max_cost_total_info: BudgetInfo | None, - ) -> SampledConfig: + budget_info: BudgetInfo | None, + n: int | None = None, + ) -> SampledConfig | list[SampledConfig]: """Sample a new configuration. Args: trials: All of the trials that are known about. - max_cost_total_info: information about the max_cost_total + budget_info: information about the budget constraints. Returns: - SampledConfig: a sampled configuration - dict: state the optimizer would like to keep between calls + The sampled configuration(s) """ ... diff --git a/neps/optimizers/bayesian_optimization/acquisition_functions/__init__.py b/neps/optimizers/bayesian_optimization/acquisition_functions/__init__.py index 06f20720d..31cb5b2b0 100644 --- a/neps/optimizers/bayesian_optimization/acquisition_functions/__init__.py +++ b/neps/optimizers/bayesian_optimization/acquisition_functions/__init__.py @@ -39,7 +39,7 @@ __all__ = [ "AcquisitionMapping", + "BaseAcquisition", "ComprehensiveExpectedImprovement", "UpperConfidenceBound", - "BaseAcquisition", ] diff --git a/neps/optimizers/bayesian_optimization/optimizer.py b/neps/optimizers/bayesian_optimization/optimizer.py index 743395b23..471fd0706 100644 --- a/neps/optimizers/bayesian_optimization/optimizer.py +++ b/neps/optimizers/bayesian_optimization/optimizer.py @@ -127,35 +127,61 @@ def __init__( self.cost_on_log_scale = cost_on_log_scale self.device = device self.sample_prior_first = sample_prior_first - self.n_initial_design = initial_design_size - self.init_design: list[dict[str, Any]] | None = None + + if initial_design_size is not None: + self.n_initial_design = initial_design_size + else: + self.n_initial_design = len(pipeline_space.numerical) + len( + pipeline_space.categoricals + ) @override def ask( self, trials: Mapping[str, Trial], - max_cost_total_info: BudgetInfo | None = None, - ) -> SampledConfig: + budget_info: BudgetInfo | None = None, + n: int | None = None, + ) -> SampledConfig | list[SampledConfig]: + _n = 1 if n is None else n n_sampled = len(trials) - config_id = str(n_sampled + 1) + config_ids = iter(str(i + 1) for i in range(n_sampled, n_sampled + _n)) space = self.pipeline_space - # If we havn't passed the intial design phase - if self.init_design is None: - self.init_design = make_initial_design( + sampled_configs: list[SampledConfig] = [] + + # If the amount of configs evaluated is less than the initial design + # requirement, keep drawing from initial design + n_evaluated = sum( + 1 + for trial in trials.values() + if trial.report is not None and trial.report.objective_to_minimize is not None + ) + if n_evaluated < self.n_initial_design: + design_samples = make_initial_design( space=space, encoder=self.encoder, - sample_prior_first=self.sample_prior_first, - sampler=self.prior if self.prior is not None else "sobol", + sample_prior_first=self.sample_prior_first if n_sampled == 0 else False, + sampler=self.prior if self.prior is not None else "uniform", seed=None, # TODO: Seeding - sample_size=( - "ndim" if self.n_initial_design is None else self.n_initial_design - ), + sample_size=_n, sample_fidelity="max", ) - if n_sampled < len(self.init_design): - return SampledConfig(id=config_id, config=self.init_design[n_sampled]) + sampled_configs.extend( + [ + SampledConfig(id=config_id, config=config) + for config_id, config in zip( + config_ids, + design_samples, + strict=False, + ) + ] + ) + if len(sampled_configs) == _n: + if n is None: + return sampled_configs[0] + + return sampled_configs # Otherwise, we encode trials and setup to fit and acquire from a GP data, encoder = encode_trials_for_gp( @@ -164,16 +190,14 @@ def ask( cost_percent = None if self.use_cost: - if max_cost_total_info is None: + if budget_info is None: raise ValueError( "Must provide a 'cost' to configurations if using cost" " with BayesianOptimization." ) - if max_cost_total_info.max_cost_total is None: + if budget_info.max_cost_total is None: raise ValueError("Cost budget must be set if using cost") - cost_percent = ( - max_cost_total_info.used_cost_budget / max_cost_total_info.max_cost_total - ) + cost_percent = budget_info.used_cost_budget / budget_info.max_cost_total # If we should use the prior, weight the acquisition function by # the probability of it being sampled from the prior. @@ -181,13 +205,13 @@ def ask( prior = None if self.prior: pibo_exp_term = _pibo_exp_term( - n_sampled, encoder.ncols, len(self.init_design) + n_sampled, encoder.ncols, self.n_initial_design ) # If the exp term is insignificant, skip prior acq. weighting prior = None if pibo_exp_term < 1e-4 else self.prior gp = make_default_single_obj_gp(x=data.x, y=data.y, encoder=encoder) - candidate = fit_and_acquire_from_gp( + candidates = fit_and_acquire_from_gp( gp=gp, x_train=data.x, encoder=encoder, @@ -202,11 +226,22 @@ def ask( prune_baseline=True, ), prior=prior, + n_candidates_required=_n, pibo_exp_term=pibo_exp_term, costs=data.cost if self.use_cost else None, cost_percentage_used=cost_percent, costs_on_log_scale=self.cost_on_log_scale, ) - config = encoder.decode(candidate)[0] - return SampledConfig(id=config_id, config=config) + configs = encoder.decode(candidates) + sampled_configs.extend( + [ + SampledConfig(id=config_id, config=config) + for config_id, config in zip(config_ids, configs, strict=True) + ] + ) + + if n is None: + return sampled_configs[0] + + return sampled_configs diff --git a/neps/optimizers/grid_search/optimizer.py b/neps/optimizers/grid_search/optimizer.py index 7bd61286b..9bef62baa 100644 --- a/neps/optimizers/grid_search/optimizer.py +++ b/neps/optimizers/grid_search/optimizer.py @@ -97,8 +97,12 @@ def __init__(self, pipeline_space: SearchSpace, seed: int | None = None): @override def ask( - self, trials: Mapping[str, Trial], max_cost_total_info: BudgetInfo | None + self, + trials: Mapping[str, Trial], + budget_info: BudgetInfo | None, + n: int | None = None, ) -> SampledConfig: + assert n is None, "TODO" _num_previous_configs = len(trials) if _num_previous_configs > len(self.configs_list) - 1: raise ValueError("Grid search exhausted!") diff --git a/neps/optimizers/initial_design.py b/neps/optimizers/initial_design.py index 74c9c3093..6de3e5fb5 100644 --- a/neps/optimizers/initial_design.py +++ b/neps/optimizers/initial_design.py @@ -100,12 +100,7 @@ def make_initial_design( # noqa: PLR0912, C901 ) if sample_prior_first: - # TODO: No way to pass a seed to the sampler - default = { - name: hp.prior if hp.prior is not None else hp.sample_value() - for name, hp in space.hyperparameters.items() - } - configs.append({**default, **fids()}) + configs.append({**space.prior_config, **fids()}) ndims = len(space.numerical) + len(space.categoricals) if sample_size == "ndim": diff --git a/neps/optimizers/multi_fidelity/__init__.py b/neps/optimizers/multi_fidelity/__init__.py index f226a9782..02e29dc96 100644 --- a/neps/optimizers/multi_fidelity/__init__.py +++ b/neps/optimizers/multi_fidelity/__init__.py @@ -15,15 +15,15 @@ ) __all__ = [ - "SuccessiveHalving", - "SuccessiveHalvingWithPriors", + "IFBO", + "MOBSTER", + "AsynchronousHyperband", + "AsynchronousHyperbandWithPriors", "AsynchronousSuccessiveHalving", "AsynchronousSuccessiveHalvingWithPriors", "Hyperband", - "HyperbandWithPriors", "HyperbandCustomDefault", - "AsynchronousHyperband", - "AsynchronousHyperbandWithPriors", - "MOBSTER", - "IFBO", + "HyperbandWithPriors", + "SuccessiveHalving", + "SuccessiveHalvingWithPriors", ] diff --git a/neps/optimizers/multi_fidelity/hyperband.py b/neps/optimizers/multi_fidelity/hyperband.py index e92b1939c..ce78dd200 100644 --- a/neps/optimizers/multi_fidelity/hyperband.py +++ b/neps/optimizers/multi_fidelity/hyperband.py @@ -125,8 +125,10 @@ def _handle_promotions(self) -> None: def ask( self, trials: Mapping[str, Trial], - max_cost_total_info: BudgetInfo | None, + budget_info: BudgetInfo | None, + n: int | None = None, ) -> SampledConfig: + assert n is None, "TODO" completed: dict[str, ConfigResult] = { trial_id: trial.into_config_result(self.pipeline_space.from_dict) for trial_id, trial in trials.items() diff --git a/neps/optimizers/multi_fidelity/ifbo.py b/neps/optimizers/multi_fidelity/ifbo.py index 632d548e4..ae7dfc411 100755 --- a/neps/optimizers/multi_fidelity/ifbo.py +++ b/neps/optimizers/multi_fidelity/ifbo.py @@ -171,8 +171,10 @@ def __init__( def ask( self, trials: Mapping[str, Trial], - max_cost_total_info: BudgetInfo | None = None, + budget_info: BudgetInfo | None = None, + n: int | None = None, ) -> SampledConfig: + assert n is None, "TODO" ids = [int(config_id.split("_", maxsplit=1)[0]) for config_id in trials] new_id = max(ids) + 1 if len(ids) > 0 else 0 diff --git a/neps/optimizers/multi_fidelity/successive_halving.py b/neps/optimizers/multi_fidelity/successive_halving.py index debe65220..08d8c8d1f 100644 --- a/neps/optimizers/multi_fidelity/successive_halving.py +++ b/neps/optimizers/multi_fidelity/successive_halving.py @@ -320,9 +320,11 @@ def _fit_models(self) -> None: def ask( self, trials: Mapping[str, Trial], - max_cost_total_info: BudgetInfo | None, - ) -> SampledConfig: + budget_info: BudgetInfo | None, + n: int | None = None, + ) -> SampledConfig | list[SampledConfig]: """This is basically the fit method.""" + assert n is None, "TODO" completed: dict[str, ConfigResult] = { trial_id: trial.into_config_result(self.pipeline_space.from_dict) for trial_id, trial in trials.items() diff --git a/neps/optimizers/multi_fidelity_prior/__init__.py b/neps/optimizers/multi_fidelity_prior/__init__.py index d8ca66b14..f272be75b 100644 --- a/neps/optimizers/multi_fidelity_prior/__init__.py +++ b/neps/optimizers/multi_fidelity_prior/__init__.py @@ -5,7 +5,7 @@ from neps.optimizers.multi_fidelity_prior.priorband import PriorBand __all__ = [ + "PriorBand", "PriorBandAsha", "PriorBandAshaHB", - "PriorBand", ] diff --git a/neps/optimizers/multi_fidelity_prior/async_priorband.py b/neps/optimizers/multi_fidelity_prior/async_priorband.py index 60300fa09..c664eeb07 100644 --- a/neps/optimizers/multi_fidelity_prior/async_priorband.py +++ b/neps/optimizers/multi_fidelity_prior/async_priorband.py @@ -238,9 +238,11 @@ def _update_sh_bracket_state(self) -> None: def ask( self, trials: Mapping[str, Trial], - max_cost_total_info: BudgetInfo | None, + budget_info: BudgetInfo | None, + n: int | None = None, ) -> SampledConfig: """This is basically the fit method.""" + assert n is None, "TODO" completed: dict[str, ConfigResult] = { trial_id: trial.into_config_result(self.pipeline_space.from_dict) for trial_id, trial in trials.items() diff --git a/neps/optimizers/random_search/optimizer.py b/neps/optimizers/random_search/optimizer.py index 285fd9644..a5df59ad1 100644 --- a/neps/optimizers/random_search/optimizer.py +++ b/neps/optimizers/random_search/optimizer.py @@ -55,10 +55,23 @@ def __init__( def ask( self, trials: Mapping[str, Trial], - max_cost_total_info: BudgetInfo | None, - ) -> SampledConfig: + budget_info: BudgetInfo | None, + n: int | None = None, + ) -> SampledConfig | list[SampledConfig]: n_trials = len(trials) - config = self.sampler.sample_one(to=self.encoder.domains) - config_dict = self.encoder.decode_one(config) - config_id = str(n_trials + 1) - return SampledConfig(config=config_dict, id=config_id, previous_config_id=None) + _n = 1 if n is None else n + configs = self.sampler.sample(_n, to=self.encoder.domains) + config_dicts = self.encoder.decode(configs) + if n == 1: + config = config_dicts[0] + config_id = str(n_trials + 1) + return SampledConfig(config=config, id=config_id, previous_config_id=None) + + return [ + SampledConfig( + config=config, + id=str(n_trials + i + 1), + previous_config_id=None, + ) + for i, config in enumerate(config_dicts) + ] diff --git a/neps/plot/tensorboard_eval.py b/neps/plot/tensorboard_eval.py index 9022a8253..6a32542d3 100644 --- a/neps/plot/tensorboard_eval.py +++ b/neps/plot/tensorboard_eval.py @@ -94,7 +94,7 @@ def _initiate_internal_configurations() -> None: register_notify_trial_end("NEPS_TBLOGGER", tblogger.end_of_config) # We are assuming that neps state is all filebased here - root_dir = Path(neps_state.location) + root_dir = Path(neps_state.path) assert root_dir.exists() tblogger.config_working_directory = Path(trial.metadata.location) diff --git a/neps/runtime.py b/neps/runtime.py index eb719fa3f..c4c8bff70 100644 --- a/neps/runtime.py +++ b/neps/runtime.py @@ -14,26 +14,38 @@ from typing import ( TYPE_CHECKING, Any, + ClassVar, Generic, Literal, TypeVar, ) +from portalocker import portalocker + +from neps.env import ( + FS_SYNC_GRACE_BASE, + FS_SYNC_GRACE_INC, + LINUX_FILELOCK_FUNCTION, + MAX_RETRIES_CREATE_LOAD_STATE, + MAX_RETRIES_GET_NEXT_TRIAL, + MAX_RETRIES_WORKER_CHECK_SHOULD_STOP, +) from neps.exceptions import ( NePSError, - VersionMismatchError, + TrialAlreadyExistsError, WorkerFailedToGetPendingTrialsError, WorkerRaiseError, ) from neps.state._eval import evaluate_trial -from neps.state.filebased import create_or_load_filebased_neps_state +from neps.state.neps_state import NePSState from neps.state.optimizer import BudgetInfo, OptimizationState, OptimizerInfo +from neps.state.seed_snapshot import SeedSnapshot from neps.state.settings import DefaultReportValues, OnErrorPossibilities, WorkerSettings from neps.state.trial import Trial +from neps.utils.common import gc_disabled if TYPE_CHECKING: from neps.optimizers.base_optimizer import BaseOptimizer - from neps.state.neps_state import NePSState logger = logging.getLogger(__name__) @@ -43,9 +55,6 @@ def _default_worker_name() -> str: return f"{os.getpid()}-{isoformat}" -N_FAILED_GET_NEXT_PENDING_ATTEMPTS_BEFORE_ERROR = 0 -N_FAILED_TO_SET_TRIAL_STATE = 10 - Loc = TypeVar("Loc") # NOTE: As each NEPS process is only ever evaluating a single trial, this global can @@ -60,7 +69,7 @@ def _default_worker_name() -> str: # TODO: This only works with a filebased nepsstate -def get_workers_neps_state() -> NePSState[Path]: +def get_workers_neps_state() -> NePSState: """Get the worker's NePS state.""" if _WORKER_NEPS_STATE is None: raise RuntimeError( @@ -72,7 +81,7 @@ def get_workers_neps_state() -> NePSState[Path]: return _WORKER_NEPS_STATE -def _set_workers_neps_state(state: NePSState[Path]) -> None: +def _set_workers_neps_state(state: NePSState) -> None: global _WORKER_NEPS_STATE # noqa: PLW0603 _WORKER_NEPS_STATE = state @@ -152,6 +161,8 @@ class DefaultWorker(Generic[Loc]): worker_cumulative_evaluation_time_seconds: float = 0.0 """The time spent evaluating configurations by this worker.""" + _GRACE: ClassVar = FS_SYNC_GRACE_BASE + @classmethod def new( cls, @@ -173,27 +184,7 @@ def new( _pre_sample_hooks=_pre_sample_hooks, ) - def _get_next_trial_from_state(self) -> Trial: - nxt_trial = self.state.get_next_pending_trial() - - # If we have a trial, we will use it - if nxt_trial is not None: - logger.info( - f"Worker '{self.worker_id}' got previosly sampled trial: {nxt_trial}" - ) - - # Otherwise sample a new one - else: - nxt_trial = self.state.sample_trial( - worker_id=self.worker_id, - optimizer=self.optimizer, - _sample_hooks=self._pre_sample_hooks, - ) - logger.info(f"Worker '{self.worker_id}' sampled a new trial: {nxt_trial}") - - return nxt_trial - - def _check_if_should_stop( # noqa: C901, PLR0912, PLR0911 + def _check_worker_local_settings( self, *, time_monotonic_start: float, @@ -201,8 +192,6 @@ def _check_if_should_stop( # noqa: C901, PLR0912, PLR0911 ) -> str | Literal[False]: # NOTE: Sorry this code is kind of ugly but it's pretty straightforward, just a # lot of conditional checking and making sure to check cheaper conditions first. - # It would look a little nicer with a match statement but we've got to wait - # for python 3.10 for that. # First check for stopping criterion for this worker in particular as it's # cheaper and doesn't require anything from the state. @@ -276,13 +265,16 @@ def _check_if_should_stop( # noqa: C901, PLR0912, PLR0911 f", given by `{self.settings.max_evaluation_time_for_worker_seconds=}`." ) + return False + + def _check_shared_error_stopping_criterion(self) -> str | Literal[False]: # We check this global error stopping criterion as it's much # cheaper than sweeping the state from all trials. if self.settings.on_error in ( OnErrorPossibilities.RAISE_ANY_ERROR, OnErrorPossibilities.STOP_ANY_ERROR, ): - err = self.state._shared_errors.synced().latest_err_as_raisable() + err = self.state.lock_and_get_errors().latest_err_as_raisable() if err is not None: msg = ( "An error occurred in another worker and this worker is set to stop" @@ -302,29 +294,22 @@ def _check_if_should_stop( # noqa: C901, PLR0912, PLR0911 return msg - # If there are no global stopping criterion, we can no just return early. - if ( - self.settings.max_evaluations_total is None - and self.settings.max_cost_total is None - and self.settings.max_evaluation_time_total_seconds is None - ): - return False - - # At this point, if we have some global stopping criterion, we need to sweep - # the current state of trials to determine if we should stop - # NOTE: If these `sum` turn out to somehow be a bottleneck, these could - # be precomputed and accumulated over time. This would have to be handled - # in the `NePSState` class. - trials = self.state.get_all_trials() + return False + + def _check_global_stopping_criterion( + self, + trials: Mapping[str, Trial], + ) -> str | Literal[False]: if self.settings.max_evaluations_total is not None: if self.settings.include_in_progress_evaluations_towards_maximum: count = sum( 1 for _, trial in trials.items() - if trial.report is not None - or trial.state in (Trial.State.EVALUATING, Trial.State.SUBMITTED) + if trial.metadata.state + not in (Trial.State.PENDING, Trial.State.SUBMITTED) ) else: + # This indicates they have completed. count = sum(1 for _, trial in trials.items() if trial.report is not None) if count >= self.settings.max_evaluations_total: @@ -365,7 +350,124 @@ def _check_if_should_stop( # noqa: C901, PLR0912, PLR0911 return False - def run(self) -> None: # noqa: C901, PLR0915 + @property + def _requires_global_stopping_criterion(self) -> bool: + return ( + self.settings.max_evaluations_total is not None + or self.settings.max_cost_total is not None + or self.settings.max_evaluation_time_total_seconds is not None + ) + + def _get_next_trial(self) -> Trial | Literal["break"]: + # If there are no global stopping criterion, we can no just return early. + with self.state._optimizer_lock.lock(worker_id=self.worker_id): + # NOTE: It's important to release the trial lock before sampling + # as otherwise, any other service, such as reporting the result + # of a trial. Hence we do not lock these together with the above. + # OPTIM: We try to prevent garbage collection from happening in here to + # minimize time spent holding on to the lock. + with self.state._trial_lock.lock(worker_id=self.worker_id), gc_disabled(): + # Give the file-system some time to sync if we encountered out-of-order + # issues with this worker. + if self._GRACE > 0: + time.sleep(self._GRACE) + + trials = self.state._trial_repo.latest() + + if self._requires_global_stopping_criterion: + should_stop = self._check_global_stopping_criterion(trials) + if should_stop is not False: + logger.info(should_stop) + return "break" + + pending_trials = [ + trial + for trial in trials.values() + if trial.metadata.state == Trial.State.PENDING + ] + + if len(pending_trials) > 0: + earliest_pending = sorted( + pending_trials, + key=lambda t: t.metadata.time_sampled, + )[0] + earliest_pending.set_evaluating( + time_started=time.time(), + worker_id=self.worker_id, + ) + self.state._trial_repo.update_trial( + earliest_pending, hints="metadata" + ) + logger.info( + "Worker '%s' picked up pending trial: %s.", + self.worker_id, + earliest_pending.id, + ) + return earliest_pending + + sampled_trials = self.state._sample_trial( + optimizer=self.optimizer, + worker_id=self.worker_id, + trials=trials, + n=self.settings.batch_size, + ) + if isinstance(sampled_trials, Trial): + this_workers_trial = sampled_trials + else: + this_workers_trial = sampled_trials[0] + sampled_trials[1:] + + with self.state._trial_lock.lock(worker_id=self.worker_id), gc_disabled(): + this_workers_trial.set_evaluating( + time_started=time.time(), + worker_id=self.worker_id, + ) + try: + self.state._trial_repo.store_new_trial(sampled_trials) + if isinstance(sampled_trials, Trial): + logger.info( + "Worker '%s' sampled new trial: %s.", + self.worker_id, + this_workers_trial.id, + ) + else: + logger.info( + "Worker '%s' sampled new trials: %s.", + self.worker_id, + ",".join(trial.id for trial in sampled_trials), + ) + return this_workers_trial + except TrialAlreadyExistsError as e: + if e.trial_id in trials: + logger.error( + "The new sampled trial was given an id of '%s', yet this" + " exists in the loaded in trials given to the optimizer. This" + " indicates a bug with the optimizers allocation of ids.", + e.trial_id, + ) + else: + _grace = DefaultWorker._GRACE + _inc = FS_SYNC_GRACE_INC + logger.warning( + "The new sampled trial was given an id of '%s', which is not" + " one that was loaded in by the optimizer. This is usually" + " an indication that the file-system you are running on" + " is not atmoic in synchoronizing file operations." + " We have attempted to stabalize this but milage may vary." + " We are incrementing a grace period for file-locks from" + " '%s's to '%s's. You can control the initial" + " grace with 'NEPS_FS_SYNC_GRACE_BASE' and the increment with" + " 'NEPS_FS_SYNC_GRACE_INC'.", + e.trial_id, + _grace, + _grace + _inc, + ) + DefaultWorker._GRACE = _grace + FS_SYNC_GRACE_INC + raise e + + # Forgive me lord, for I have sinned, this function is atrocious but complicated + # due to locking. + def run(self) -> None: # noqa: C901, PLR0912, PLR0915 """Run the worker. Will keep running until one of the criterion defined by the `WorkerSettings` @@ -379,68 +481,84 @@ def run(self) -> None: # noqa: C901, PLR0915 _error_from_evaluation: Exception | None = None _repeated_fail_get_next_trial_count = 0 + n_repeated_failed_check_should_stop = 0 while True: - # NOTE: We rely on this function to do logging and raising errors if it should - should_stop = self._check_if_should_stop( - time_monotonic_start=_time_monotonic_start, - error_from_this_worker=_error_from_evaluation, - ) - if should_stop is not False: - logger.info(should_stop) - break - try: - trial_to_eval = self._get_next_trial_from_state() - _repeated_fail_get_next_trial_count = 0 - except Exception as e: - _repeated_fail_get_next_trial_count += 1 - logger.debug( - "Error while trying to get the next trial to evaluate.", exc_info=True + # First check local worker settings + should_stop = self._check_worker_local_settings( + time_monotonic_start=_time_monotonic_start, + error_from_this_worker=_error_from_evaluation, ) - - # NOTE: This is to prevent any infinite loops if we can't get a trial + if should_stop is not False: + logger.info(should_stop) + break + + # Next check global errs having occured + should_stop = self._check_shared_error_stopping_criterion() + if should_stop is not False: + logger.info(should_stop) + break + + except WorkerRaiseError as e: + # If we raise a specific error, we should stop the worker + raise e + except Exception as e: + # An unknown exception, check our retry countk + n_repeated_failed_check_should_stop += 1 if ( - _repeated_fail_get_next_trial_count - >= N_FAILED_GET_NEXT_PENDING_ATTEMPTS_BEFORE_ERROR + n_repeated_failed_check_should_stop + >= MAX_RETRIES_WORKER_CHECK_SHOULD_STOP ): - raise WorkerFailedToGetPendingTrialsError( - "Worker '%s' failed to get pending trials %d times in a row." - " Bailing!" + raise WorkerRaiseError( + f"Worker {self.worker_id} failed to check if it should stop" + f" {MAX_RETRIES_WORKER_CHECK_SHOULD_STOP} times in a row. Bailing" ) from e - continue - - # If we can't set this working to evaluating, then just retry the loop - try: - trial_to_eval.set_evaluating( - time_started=time.time(), - worker_id=self.worker_id, - ) - self.state.put_updated_trial(trial_to_eval) - n_failed_set_trial_state = 0 - except VersionMismatchError: - n_failed_set_trial_state += 1 - logger.debug( - f"Another worker has managed to change trial '{trial_to_eval.id}'" - " to evaluate and put back into state. This is fine and likely means" - " the other worker is evaluating it.", - exc_info=True, - ) - except Exception: - n_failed_set_trial_state += 1 logger.error( - f"Error trying to set trial '{trial_to_eval.id}' to evaluating.", + "Unexpected error from worker '%s' while checking if it should stop.", + self.worker_id, exc_info=True, ) + time.sleep(1) # Help stagger retries + continue - # NOTE: This is to prevent infinite looping if it somehow keeps getting - # the same trial and can't set it to evaluating. - if n_failed_set_trial_state != 0: - if n_failed_set_trial_state >= N_FAILED_TO_SET_TRIAL_STATE: - raise WorkerFailedToGetPendingTrialsError( - "Worker '%s' failed to set trial to evaluating %d times in a row." - " Bailing!" + # From here, we now begin sampling or getting the next pending trial. + # As the global stopping criterion requires us to check all trials, and + # needs to be in locked in-step with sampling and is done inside + # _get_next_trial + try: + trial_to_eval = self._get_next_trial() + if trial_to_eval == "break": + break + _repeated_fail_get_next_trial_count = 0 + except Exception as e: + _repeated_fail_get_next_trial_count += 1 + if isinstance(e, portalocker.exceptions.LockException): + logger.debug( + "Worker '%s': Timeout while trying to get the next trial to" + " evaluate. If you are using a model based optimizer, such as" + " Bayesian Optimization, this can occur as the number of" + " configurations get large. There's not much to do here" + " and we will retry to obtain the lock.", + self.worker_id, + exc_info=True, ) + else: + logger.debug( + "Worker '%s': Error while trying to get the next trial to" + " evaluate.", + self.worker_id, + exc_info=True, + ) + time.sleep(1) # Help stagger retries + # NOTE: This is to prevent any infinite loops if we can't get a trial + if _repeated_fail_get_next_trial_count >= MAX_RETRIES_GET_NEXT_TRIAL: + raise WorkerFailedToGetPendingTrialsError( + f"Worker {self.worker_id} failed to get pending trials" + f" {MAX_RETRIES_GET_NEXT_TRIAL} times in" + " a row. Bailing!" + ) from e + continue # We (this worker) has managed to set it to evaluating, now we can evaluate it @@ -460,7 +578,7 @@ def run(self) -> None: # noqa: C901, PLR0915 "Worker '%s' evaluated trial: %s as %s.", self.worker_id, evaluated_trial.id, - evaluated_trial.state, + evaluated_trial.metadata.state, ) if report.cost is not None: @@ -474,11 +592,15 @@ def run(self) -> None: # noqa: C901, PLR0915 logger.exception(report.err) _error_from_evaluation = report.err - self.state.report_trial_evaluation( - trial=evaluated_trial, - report=report, - worker_id=self.worker_id, - ) + # We do not retry this, as if some other worker has + # managed to manipulate this trial in the meantime, + # then something has gone wrong + with self.state._trial_lock.lock(worker_id=self.worker_id): + self.state._report_trial_evaluation( + trial=evaluated_trial, + report=report, + worker_id=self.worker_id, + ) logger.debug("Config %s: %s", evaluated_trial.id, evaluated_trial.config) logger.debug("Loss %s: %s", evaluated_trial.id, report.objective_to_minimize) @@ -504,6 +626,7 @@ def _launch_runtime( # noqa: PLR0913 overwrite_optimization_dir: bool, max_evaluations_total: int | None, max_evaluations_for_worker: int | None, + sample_batch_size: int | None, pre_load_hooks: Iterable[Callable[[BaseOptimizer], BaseOptimizer]] | None, ) -> None: if overwrite_optimization_dir and optimization_dir.exists(): @@ -513,21 +636,38 @@ def _launch_runtime( # noqa: PLR0913 ) shutil.rmtree(optimization_dir) - neps_state = create_or_load_filebased_neps_state( - directory=optimization_dir, - optimizer_info=OptimizerInfo(optimizer_info), - optimizer_state=OptimizationState( - budget=( - BudgetInfo( - max_cost_total=max_cost_total, - used_cost_budget=0, - max_evaluations=max_evaluations_total, - used_evaluations=0, - ) - ), - shared_state={}, # TODO: Unused for the time being... - ), - ) + for _retry_count in range(MAX_RETRIES_CREATE_LOAD_STATE): + try: + neps_state = NePSState.create_or_load( + path=optimization_dir, + load_only=False, + optimizer_info=OptimizerInfo(optimizer_info), + optimizer_state=OptimizationState( + seed_snapshot=SeedSnapshot.new_capture(), + budget=( + BudgetInfo( + max_cost_total=max_cost_total, + used_cost_budget=0, + max_evaluations=max_evaluations_total, + used_evaluations=0, + ) + ), + shared_state=None, # TODO: Unused for the time being... + ), + ) + break + except Exception: # noqa: BLE001 + time.sleep(0.5) + logger.debug( + "Error while trying to create or load the NePS state. Retrying...", + exc_info=True, + ) + else: + raise RuntimeError( + "Failed to create or load the NePS state after" + f" {MAX_RETRIES_CREATE_LOAD_STATE} attempts. Bailing!" + " Please enable debug logging to see the errors that occured." + ) settings = WorkerSettings( on_error=( @@ -535,6 +675,7 @@ def _launch_runtime( # noqa: PLR0913 if ignore_errors else OnErrorPossibilities.RAISE_ANY_ERROR ), + batch_size=sample_batch_size, default_report_values=DefaultReportValues( objective_to_minimize_value_on_error=objective_to_minimize_value_on_error, cost_value_on_error=cost_value_on_error, @@ -555,6 +696,27 @@ def _launch_runtime( # noqa: PLR0913 max_cost_for_worker=None, # TODO: User can't specify yet ) + # HACK: Due to nfs file-systems, locking with the default `flock()` is not reliable. + # Hence, we overwrite `portalockers` lock call to use `lockf()` instead. + # This is commeneted in their source code that this is an option to use, however + # it's not directly advertised as a parameter/env variable or otherwise. + import portalocker.portalocker as portalocker_lock_module + + try: + import fcntl + + if LINUX_FILELOCK_FUNCTION.lower() == "flock": + setattr(portalocker_lock_module, "LOCKER", fcntl.flock) + elif LINUX_FILELOCK_FUNCTION.lower() == "lockf": + setattr(portalocker_lock_module, "LOCKER", fcntl.lockf) + else: + raise ValueError( + f"Unknown file-locking function '{LINUX_FILELOCK_FUNCTION}'." + " Must be one of 'flock' or 'lockf'." + ) + except ImportError: + pass + worker = DefaultWorker.new( state=neps_state, optimizer=optimizer, diff --git a/neps/sampling/__init__.py b/neps/sampling/__init__.py index 0218f837d..032290d6c 100644 --- a/neps/sampling/__init__.py +++ b/neps/sampling/__init__.py @@ -1,4 +1,4 @@ from neps.sampling.priors import CenteredPrior, Prior, UniformPrior from neps.sampling.samplers import Sampler, Sobol -__all__ = ["Sobol", "Sampler", "Prior", "UniformPrior", "CenteredPrior"] +__all__ = ["CenteredPrior", "Prior", "Sampler", "Sobol", "UniformPrior"] diff --git a/neps/search_spaces/hyperparameters/__init__.py b/neps/search_spaces/hyperparameters/__init__.py index bae71ab7d..14e7ce792 100644 --- a/neps/search_spaces/hyperparameters/__init__.py +++ b/neps/search_spaces/hyperparameters/__init__.py @@ -9,13 +9,13 @@ __all__ = [ "Categorical", - "Constant", - "Integer", - "Float", - "Numerical", "CategoricalParameter", + "Constant", "ConstantParameter", - "IntegerParameter", + "Float", "FloatParameter", + "Integer", + "IntegerParameter", + "Numerical", "NumericalParameter", ] diff --git a/neps/state/__init__.py b/neps/state/__init__.py index e870d656f..b8eb55af3 100644 --- a/neps/state/__init__.py +++ b/neps/state/__init__.py @@ -1,23 +1,11 @@ from neps.state.optimizer import BudgetInfo, OptimizationState, OptimizerInfo -from neps.state.protocols import ( - Locker, - ReaderWriter, - Synced, - VersionedResource, - Versioner, -) from neps.state.seed_snapshot import SeedSnapshot from neps.state.trial import Trial __all__ = [ - "Locker", - "SeedSnapshot", - "Synced", "BudgetInfo", "OptimizationState", "OptimizerInfo", + "SeedSnapshot", "Trial", - "ReaderWriter", - "Versioner", - "VersionedResource", ] diff --git a/neps/state/err_dump.py b/neps/state/err_dump.py index 167ab48fd..9f50ddefd 100644 --- a/neps/state/err_dump.py +++ b/neps/state/err_dump.py @@ -73,5 +73,5 @@ def empty(self) -> bool: def latest_err_as_raisable(self) -> SerializedError | None: """Get the latest error.""" if self.errs: - return self.errs[-1].as_raisable() + return self.errs[-1].as_raisable() # type: ignore return None diff --git a/neps/state/filebased.py b/neps/state/filebased.py index cf53c6225..6ea08bdf5 100644 --- a/neps/state/filebased.py +++ b/neps/state/filebased.py @@ -1,56 +1,22 @@ -"""This module houses the implementation of a NePSState that -does everything on the filesystem, i.e. locking, versioning and -storing/loading. - -The main components are: -* [`FileVersioner`][neps.state.filebased.FileVersioner]: A versioner that - stores a version tag on disk, usually for a resource like a Trial. -* [`FileLocker`][neps.state.filebased.FileLocker]: A locker that uses a file - to lock between processes. -* [`TrialRepoInDirectory`][neps.state.filebased.TrialRepoInDirectory]: A - repository of Trials that are stored in a directory. -* `ReaderWriterXXX`: Reader/writers for various resources NePSState needs -* [`load_filebased_neps_state`][neps.state.filebased.load_filebased_neps_state]: - A function to load a NePSState from a directory. -* [`create_filebased_neps_state`][neps.state.filebased.create_filebased_neps_state]: - A function to create a new NePSState in a directory. -""" +"""TODO.""" from __future__ import annotations +import contextlib import json import logging import pprint +import time from collections.abc import Iterable, Iterator from contextlib import contextmanager -from dataclasses import asdict, dataclass, field +from dataclasses import asdict, dataclass from pathlib import Path -from typing import ClassVar, TypeVar -from typing_extensions import override -from uuid import uuid4 +from typing import Literal, TypeAlias, TypeVar -import numpy as np import portalocker as pl -from neps.env import ( - ENV_VARS_USED, - GLOBAL_ERR_FILELOCK_POLL, - GLOBAL_ERR_FILELOCK_TIMEOUT, - OPTIMIZER_INFO_FILELOCK_POLL, - OPTIMIZER_INFO_FILELOCK_TIMEOUT, - OPTIMIZER_STATE_FILELOCK_POLL, - OPTIMIZER_STATE_FILELOCK_TIMEOUT, - SEED_SNAPSHOT_FILELOCK_POLL, - SEED_SNAPSHOT_FILELOCK_TIMEOUT, - TRIAL_FILELOCK_POLL, - TRIAL_FILELOCK_TIMEOUT, -) -from neps.exceptions import NePSError +from neps.env import CONFIG_SERIALIZE_FORMAT, ENV_VARS_USED from neps.state.err_dump import ErrDump -from neps.state.neps_state import NePSState -from neps.state.optimizer import BudgetInfo, OptimizationState, OptimizerInfo -from neps.state.protocols import Locker, ReaderWriter, Synced, TrialRepo, Versioner -from neps.state.seed_snapshot import SeedSnapshot from neps.state.trial import Trial from neps.utils.files import deserialize, serialize @@ -58,368 +24,135 @@ K = TypeVar("K") T = TypeVar("T") - -def make_sha() -> str: - """Generate a str hex sha.""" - return uuid4().hex - - -@dataclass -class FileVersioner(Versioner): - """A versioner that stores a version tag on disk.""" - - version_file: Path - - @override - def current(self) -> str | None: - if not self.version_file.exists(): - return None - return self.version_file.read_text() - - @override - def bump(self) -> str: - sha = make_sha() - self.version_file.write_text(sha) - return sha +TrialWriteHint: TypeAlias = Literal["metadata", "report", "config"] @dataclass -class TrialRepoInDirectory(TrialRepo[Path]): - """A repository of Trials that are stored in a directory.""" - - directory: Path - _cache: dict[str, Synced[Trial, Path]] = field(default_factory=dict) - - @override - def all_trial_ids(self) -> set[str]: - """List all the trial ids in this trial Repo.""" - return { - config_path.name.replace("config_", "") - for config_path in self.directory.iterdir() - if config_path.name.startswith("config_") and config_path.is_dir() - } - - @override - def get_by_id( - self, - trial_id: str, - *, - lock_poll: float = TRIAL_FILELOCK_POLL, - lock_timeout: float | None = TRIAL_FILELOCK_TIMEOUT, - ) -> Synced[Trial, Path]: - """Get a Trial by its ID. - - !!! note - - This will **not** explicitly sync the trial and it is up to the caller - to do so. Most of the time, the caller should be a NePSState - object which will do that for you. However if the trial is not in the - cache, then it will be loaded from disk which requires syncing. - - Args: - trial_id: The ID of the trial to get. - lock_poll: The poll time for the file lock. - lock_timeout: The timeout for the file lock. - - Returns: - The trial with the given ID. - """ - trial = self._cache.get(trial_id) - if trial is not None: - return trial - - config_path = self.directory / f"config_{trial_id}" - if not config_path.exists(): - raise TrialRepo.TrialNotFoundError(trial_id, config_path) - - trial = Synced.load( - location=config_path, - locker=FileLocker( - lock_path=config_path / ".lock", - poll=lock_poll, - timeout=lock_timeout, - ), - versioner=FileVersioner(version_file=config_path / ".version"), - reader_writer=ReaderWriterTrial(), - ) - self._cache[trial_id] = trial - return trial - - @override - def put_new( - self, - trial: Trial, - *, - lock_poll: float = TRIAL_FILELOCK_POLL, - lock_timeout: float | None = TRIAL_FILELOCK_TIMEOUT, - ) -> Synced[Trial, Path]: - """Put a new Trial into the repository. - - Args: - trial: The trial to put. - lock_poll: The poll time for the file lock. - lock_timeout: The timeout for the file lock. - - Returns: - The synced trial. - - Raises: - TrialRepo.TrialAlreadyExistsError: If the trial already exists in the - repository. - """ - config_path = self.directory.absolute().resolve() / f"config_{trial.metadata.id}" - if config_path.exists(): - raise TrialRepo.TrialAlreadyExistsError( - f"Trial '{trial.metadata.id}' already exists as '{config_path}'." - ) - - # HACK: We do this here as there is no way to know where a Trial will - # be located when it's created... - trial.metadata.location = str(config_path) - shared_trial = Synced.new( - data=trial, - location=config_path, - locker=FileLocker( - lock_path=config_path / ".lock", - poll=lock_poll, - timeout=lock_timeout, - ), - versioner=FileVersioner(version_file=config_path / ".version"), - reader_writer=ReaderWriterTrial(), - ) - self._cache[trial.metadata.id] = shared_trial - return shared_trial - - @override - def all(self) -> dict[str, Synced[Trial, Path]]: - """Get a dictionary of all the Trials in the repository. - - !!! note - See [`get_by_id()`][neps.state.filebased.TrialRepoInDirectory.get_by_id] - for notes on the trials syncing. - """ - return {trial_id: self.get_by_id(trial_id) for trial_id in self.all_trial_ids()} - - @override - def pending(self) -> Iterable[tuple[str, Synced[Trial, Path]]]: - pending = [ - (_id, t, trial.metadata.time_sampled) - for (_id, t) in self.all().items() - if (trial := t.synced()).state == Trial.State.PENDING - ] - return iter((_id, t) for _id, t, _ in sorted(pending, key=lambda x: x[2])) +class ReaderWriterTrial: + """ReaderWriter for Trial objects.""" + # Report and config are kept as yaml since they are most likely to be + # read + CONFIG_FILENAME = f"config.{CONFIG_SERIALIZE_FORMAT}" + REPORT_FILENAME = f"report.{CONFIG_SERIALIZE_FORMAT}" -@dataclass -class ReaderWriterTrial(ReaderWriter[Trial, Path]): - """ReaderWriter for Trial objects.""" + # Metadata is put as json as it's more likely to be machine read and + # is much faster. + METADATA_FILENAME = "metadata.json" - CONFIG_FILENAME = "config.yaml" - METADATA_FILENAME = "metadata.yaml" - STATE_FILENAME = "state.txt" - REPORT_FILENAME = "report.yaml" PREVIOUS_TRIAL_ID_FILENAME = "previous_trial_id.txt" - @override @classmethod def read(cls, directory: Path) -> Trial: + """Read a trial from a directory.""" config_path = directory / cls.CONFIG_FILENAME metadata_path = directory / cls.METADATA_FILENAME - state_path = directory / cls.STATE_FILENAME report_path = directory / cls.REPORT_FILENAME + with metadata_path.open("r") as f: + metadata = json.load(f) + + metadata["state"] = Trial.State(metadata["state"]) + return Trial( - config=deserialize(config_path), - metadata=Trial.MetaData(**deserialize(metadata_path)), - state=Trial.State(state_path.read_text(encoding="utf-8").strip()), + config=deserialize(config_path, file_format=CONFIG_SERIALIZE_FORMAT), + metadata=Trial.MetaData(**metadata), report=( - Trial.Report(**deserialize(report_path)) if report_path.exists() else None + Trial.Report( + **deserialize(report_path, file_format=CONFIG_SERIALIZE_FORMAT), + ) + if report_path.exists() + else None ), ) - @override @classmethod - def write(cls, trial: Trial, directory: Path) -> None: + def write( # noqa: C901, PLR0912 + cls, + trial: Trial, + directory: Path, + *, + hints: Iterable[TrialWriteHint] | TrialWriteHint | None = None, + ) -> None: + """Write a trial to a directory. + + Args: + trial: The trial to write. + directory: The directory to write the trial to. + hints: What to write. If None, write everything. + """ config_path = directory / cls.CONFIG_FILENAME metadata_path = directory / cls.METADATA_FILENAME - state_path = directory / cls.STATE_FILENAME - serialize(trial.config, config_path) - serialize(asdict(trial.metadata), metadata_path) - state_path.write_text(trial.state.value, encoding="utf-8") - - if trial.metadata.previous_trial_id is not None: - previous_trial_path = directory / cls.PREVIOUS_TRIAL_ID_FILENAME - previous_trial_path.write_text(trial.metadata.previous_trial_id) - - if trial.report is not None: - report_path = directory / cls.REPORT_FILENAME - serialize(asdict(trial.report), report_path) + if isinstance(hints, str): + match hints: + case "config": + serialize( + trial.config, + config_path, + check_serialized=False, + file_format=CONFIG_SERIALIZE_FORMAT, + ) + case "metadata": + data = asdict(trial.metadata) + data["state"] = data["state"].value + with metadata_path.open("w") as f: + json.dump(data, f) + + if trial.metadata.previous_trial_id is not None: + previous_trial_path = directory / cls.PREVIOUS_TRIAL_ID_FILENAME + previous_trial_path.write_text(trial.metadata.previous_trial_id) + case "report": + if trial.report is None: + raise ValueError( + "Cannot write report 'hint' when report is None." + ) + + report_path = directory / cls.REPORT_FILENAME + _report = asdict(trial.report) + if (err := _report.get("err")) is not None: + _report["err"] = str(err) + + serialize( + _report, + report_path, + check_serialized=False, + file_format=CONFIG_SERIALIZE_FORMAT, + ) + case _: + raise ValueError(f"Invalid hint: {hints}") + elif isinstance(hints, Iterable): + for hint in hints: + cls.write(trial, directory, hints=hint) # type: ignore + elif hints is None: + # We don't know, write everything + cls.write(trial, directory, hints=["config", "metadata"]) + + if trial.report is not None: + cls.write(trial, directory, hints="report") + else: + raise ValueError(f"Invalid hint: {hints}") @dataclass -class ReaderWriterSeedSnapshot(ReaderWriter[SeedSnapshot, Path]): - """ReaderWriter for SeedSnapshot objects.""" - - # It seems like they're all uint32 but I can't be sure. - PY_RNG_STATE_DTYPE: ClassVar = np.int64 - - PY_RNG_TUPLE_FILENAME: ClassVar = "py_rng.npy" - NP_RNG_STATE_FILENAME: ClassVar = "np_rng_state.npy" - TORCH_RNG_STATE_FILENAME: ClassVar = "torch_rng_state.pt" - TORCH_CUDA_RNG_STATE_FILENAME: ClassVar = "torch_cuda_rng_state.pt" - SEED_INFO_FILENAME: ClassVar = "seed_info.json" - - @override - @classmethod - def read(cls, directory: Path) -> SeedSnapshot: - seedinfo_path = directory / cls.SEED_INFO_FILENAME - py_rng_path = directory / cls.PY_RNG_TUPLE_FILENAME - np_rng_path = directory / cls.NP_RNG_STATE_FILENAME - torch_rng_path = directory / cls.TORCH_RNG_STATE_FILENAME - torch_cuda_rng_path = directory / cls.TORCH_CUDA_RNG_STATE_FILENAME - - # Load and set pythons rng - py_rng_state = tuple( - int(x) for x in np.fromfile(py_rng_path, dtype=cls.PY_RNG_STATE_DTYPE) - ) - np_rng_state = np.fromfile(np_rng_path, dtype=np.uint32) - seed_info = deserialize(seedinfo_path) - - torch_exists = torch_rng_path.exists() or torch_cuda_rng_path.exists() - - # By specifying `weights_only=True`, it disables arbitrary object loading - torch_rng_state = None - torch_cuda_rng = None - if torch_exists: - import torch - - if torch_rng_path.exists(): - torch_rng_state = torch.load(torch_rng_path, weights_only=True) - - if torch_cuda_rng_path.exists(): - # By specifying `weights_only=True`, it disables arbitrary object loading - torch_cuda_rng = torch.load(torch_cuda_rng_path, weights_only=True) - - return SeedSnapshot( - np_rng=( - seed_info["np_rng_kind"], - np_rng_state, - seed_info["np_pos"], - seed_info["np_has_gauss"], - seed_info["np_cached_gauss"], - ), - py_rng=( - seed_info["py_rng_version"], - py_rng_state, - seed_info["py_guass_next"], - ), - torch_rng=torch_rng_state, - torch_cuda_rng=torch_cuda_rng, - ) - - @override - @classmethod - def write(cls, snapshot: SeedSnapshot, directory: Path) -> None: - seedinfo_path = directory / cls.SEED_INFO_FILENAME - py_rng_path = directory / cls.PY_RNG_TUPLE_FILENAME - np_rng_path = directory / cls.NP_RNG_STATE_FILENAME - torch_rng_path = directory / cls.TORCH_RNG_STATE_FILENAME - torch_cuda_rng_path = directory / cls.TORCH_CUDA_RNG_STATE_FILENAME - - py_rng_version, py_rng_state, py_guass_next = snapshot.py_rng - - np.array(py_rng_state, dtype=cls.PY_RNG_STATE_DTYPE).tofile(py_rng_path) - - seed_info = { - "np_rng_kind": snapshot.np_rng[0], - "np_pos": snapshot.np_rng[2], - "np_has_gauss": snapshot.np_rng[3], - "np_cached_gauss": snapshot.np_rng[4], - "py_rng_version": py_rng_version, - "py_guass_next": py_guass_next, - } - serialize(seed_info, seedinfo_path) - np_rng_state = snapshot.np_rng[1] - np_rng_state.tofile(np_rng_path) - - if snapshot.torch_rng is not None: - import torch - - torch.save(snapshot.torch_rng, torch_rng_path) - - if snapshot.torch_cuda_rng is not None: - import torch - - torch.save(snapshot.torch_cuda_rng, torch_cuda_rng_path) - - -@dataclass -class ReaderWriterOptimizerInfo(ReaderWriter[OptimizerInfo, Path]): - """ReaderWriter for OptimizerInfo objects.""" - - INFO_FILENAME: ClassVar = "info.yaml" - - @override - @classmethod - def read(cls, directory: Path) -> OptimizerInfo: - info_path = directory / cls.INFO_FILENAME - return OptimizerInfo(info=deserialize(info_path)) - - @override - @classmethod - def write(cls, optimizer_info: OptimizerInfo, directory: Path) -> None: - info_path = directory / cls.INFO_FILENAME - serialize(optimizer_info.info, info_path) - - -# TODO(eddiebergman): If an optimizer wants to store some hefty state, i.e. a numpy array -# or something, this is horribly inefficient and we would need to adapt OptimizerState to -# handle this. -# TODO(eddiebergman): May also want to consider serializing budget into a seperate entity -@dataclass -class ReaderWriterOptimizationState(ReaderWriter[OptimizationState, Path]): - """ReaderWriter for OptimizationState objects.""" - - STATE_FILE_NAME: ClassVar = "state.yaml" - - @override - @classmethod - def read(cls, directory: Path) -> OptimizationState: - state_path = directory / cls.STATE_FILE_NAME - state = deserialize(state_path) - budget_info = state.get("budget") - budget = BudgetInfo(**budget_info) if budget_info is not None else None - return OptimizationState( - shared_state=state.get("shared_state") or {}, - budget=budget, - ) - - @override - @classmethod - def write(cls, info: OptimizationState, directory: Path) -> None: - info_path = directory / cls.STATE_FILE_NAME - serialize(asdict(info), info_path) - - -@dataclass -class ReaderWriterErrDump(ReaderWriter[ErrDump, Path]): +class ReaderWriterErrDump: """ReaderWriter for shared error lists.""" - name: str + @classmethod + def read(cls, path: Path) -> ErrDump: + """Read an error dump from a file.""" + if not path.exists(): + return ErrDump([]) - @override - def read(self, directory: Path) -> ErrDump: - errors_path = directory / f"{self.name}-errors.jsonl" - with errors_path.open("r") as f: + with path.open("r") as f: data = [json.loads(line) for line in f] return ErrDump([ErrDump.SerializableTrialError(**d) for d in data]) - @override - def write(self, err_dump: ErrDump, directory: Path) -> None: - errors_path = directory / f"{self.name}-errors.jsonl" - with errors_path.open("w") as f: + @classmethod + def write(cls, err_dump: ErrDump, path: Path) -> None: + """Write an error dump to a file.""" + with path.open("w") as f: lines = [json.dumps(asdict(trial_err)) for trial_err in err_dump.errs] f.write("\n".join(lines)) @@ -428,7 +161,7 @@ def write(self, err_dump: ErrDump, directory: Path) -> None: @dataclass -class FileLocker(Locker): +class FileLocker: """File-based locker using `portalocker`. [`FileLocker`][neps.state.locker.file.FileLocker] implements @@ -443,36 +176,32 @@ class FileLocker(Locker): def __post_init__(self) -> None: self.lock_path = self.lock_path.resolve().absolute() + self._lock = pl.Lock( + self.lock_path, + check_interval=self.poll, + timeout=self.timeout, + flags=FILELOCK_EXCLUSIVE_NONE_BLOCKING, + ) - @override - def is_locked(self) -> bool: - if not self.lock_path.exists(): - return False - try: - with self.lock(fail_if_locked=True): - pass - return False - except pl.exceptions.LockException: - return True - - @override @contextmanager - def lock( - self, - *, - fail_if_locked: bool = False, - ) -> Iterator[None]: - self.lock_path.parent.mkdir(parents=True, exist_ok=True) - self.lock_path.touch(exist_ok=True) - logger.debug("Acquiring lock on %s", self.lock_path) + def lock(self, *, worker_id: str | None = None) -> Iterator[None]: + """Lock the file. + + Args: + worker_id: The id of the worker trying to acquire the lock. + + Used for debug messaging purposes. + """ try: - with pl.Lock( - self.lock_path, - check_interval=self.poll, - timeout=self.timeout, - flags=FILELOCK_EXCLUSIVE_NONE_BLOCKING, - fail_when_locked=fail_if_locked, - ): + with self._lock: + if worker_id is not None: + logger.debug( + "Worker %s acquired lock on %s at %s", + worker_id, + self.lock_path, + time.time(), + ) + yield except pl.exceptions.LockException as e: raise pl.exceptions.LockException( @@ -484,188 +213,12 @@ def lock( " environment variables to increase the timeout:" f"\n\n{pprint.pformat(ENV_VARS_USED)}" ) from e - logger.debug("Released lock on %s", self.lock_path) - - -def load_filebased_neps_state(directory: Path) -> NePSState[Path]: - """Load a NePSState from a directory. - - Args: - directory: The directory to load the state from. - - Returns: - The loaded NePSState. - - Raises: - FileNotFoundError: If no NePSState is found at the given directory. - """ - if not directory.exists(): - raise FileNotFoundError(f"No NePSState found at '{directory}'.") - directory.mkdir(parents=True, exist_ok=True) - config_dir = directory / "configs" - config_dir.mkdir(parents=True, exist_ok=True) - seed_dir = directory / ".seed_state" - seed_dir.mkdir(parents=True, exist_ok=True) - error_dir = directory / ".errors" - error_dir.mkdir(parents=True, exist_ok=True) - optimizer_state_dir = directory / ".optimizer_state" - optimizer_state_dir.mkdir(parents=True, exist_ok=True) - optimizer_info_dir = directory / ".optimizer_info" - optimizer_info_dir.mkdir(parents=True, exist_ok=True) - - return NePSState( - location=str(directory.absolute().resolve()), - _trials=TrialRepoInDirectory(config_dir), - _optimizer_info=Synced.load( - location=optimizer_info_dir, - versioner=FileVersioner(version_file=optimizer_info_dir / ".version"), - locker=FileLocker( - lock_path=optimizer_info_dir / ".lock", - poll=OPTIMIZER_INFO_FILELOCK_POLL, - timeout=OPTIMIZER_INFO_FILELOCK_TIMEOUT, - ), - reader_writer=ReaderWriterOptimizerInfo(), - ), - _seed_state=Synced.load( - location=seed_dir, - reader_writer=ReaderWriterSeedSnapshot(), - versioner=FileVersioner(version_file=seed_dir / ".version"), - locker=FileLocker( - lock_path=seed_dir / ".lock", - poll=SEED_SNAPSHOT_FILELOCK_POLL, - timeout=SEED_SNAPSHOT_FILELOCK_TIMEOUT, - ), - ), - _shared_errors=Synced.load( - location=error_dir, - reader_writer=ReaderWriterErrDump("all"), - versioner=FileVersioner(version_file=error_dir / ".all.version"), - locker=FileLocker( - lock_path=error_dir / ".all.lock", - poll=GLOBAL_ERR_FILELOCK_POLL, - timeout=GLOBAL_ERR_FILELOCK_TIMEOUT, - ), - ), - _optimizer_state=Synced.load( - location=optimizer_state_dir, - reader_writer=ReaderWriterOptimizationState(), - versioner=FileVersioner(version_file=optimizer_state_dir / ".version"), - locker=FileLocker( - lock_path=optimizer_state_dir / ".lock", - poll=OPTIMIZER_STATE_FILELOCK_POLL, - timeout=OPTIMIZER_STATE_FILELOCK_TIMEOUT, - ), - ), - ) - - -def create_or_load_filebased_neps_state( - directory: Path, - *, - optimizer_info: OptimizerInfo, - optimizer_state: OptimizationState, -) -> NePSState[Path]: - """Create a new NePSState in a directory or load the existing one - if it already exists. - - !!! warning - - We check that the optimizer info in the NePSState on disk matches - the one that is passed. However we do not lock this check so it - is possible that if two processes try to create a NePSState at the - same time, both with different optimizer infos, that one will fail - to create the NePSState. This is a limitation of the current design. - - In principal, we could allow multiple optimizers to be run and share - the same set of trials. - - Args: - directory: The directory to create the state in. - optimizer_info: The optimizer info to use. - optimizer_state: The optimizer state to use. - - Returns: - The NePSState. - - Raises: - NePSError: If the optimizer info on disk does not match the one provided. - """ - is_new = not directory.exists() - directory.mkdir(parents=True, exist_ok=True) - config_dir = directory / "configs" - config_dir.mkdir(parents=True, exist_ok=True) - seed_dir = directory / ".seed_state" - seed_dir.mkdir(parents=True, exist_ok=True) - error_dir = directory / ".errors" - error_dir.mkdir(parents=True, exist_ok=True) - optimizer_state_dir = directory / ".optimizer_state" - optimizer_state_dir.mkdir(parents=True, exist_ok=True) - optimizer_info_dir = directory / ".optimizer_info" - optimizer_info_dir.mkdir(parents=True, exist_ok=True) - - # We have to do one bit of sanity checking to ensure that the optimzier - # info on disk manages the one we have recieved, otherwise we are unsure which - # optimizer is being used. - # NOTE: We assume that we do not have to worry about a race condition - # here where we have two different NePSState objects with two different optimizer - # infos trying to be created at the same time. This avoids the need to lock to - # check the optimizer info. If this assumption changes, then we would have - # to first lock before we do this check - optimizer_info_reader_writer = ReaderWriterOptimizerInfo() - if not is_new: - existing_info = optimizer_info_reader_writer.read(optimizer_info_dir) - if existing_info != optimizer_info: - raise NePSError( - "The optimizer info on disk does not match the one provided." - f"\nOn disk: {existing_info}\nProvided: {optimizer_info}" - f"\n\nLoaded the one on disk from {optimizer_info_dir}." - ) - - return NePSState( - location=str(directory.absolute().resolve()), - _trials=TrialRepoInDirectory(config_dir), - _optimizer_info=Synced.new_or_load( - data=optimizer_info, # type: ignore - location=optimizer_info_dir, - versioner=FileVersioner(version_file=optimizer_info_dir / ".version"), - locker=FileLocker( - lock_path=optimizer_info_dir / ".lock", - poll=OPTIMIZER_INFO_FILELOCK_POLL, - timeout=OPTIMIZER_INFO_FILELOCK_TIMEOUT, - ), - reader_writer=ReaderWriterOptimizerInfo(), - ), - _seed_state=Synced.new_or_load( - data=SeedSnapshot.new_capture(), - location=seed_dir, - reader_writer=ReaderWriterSeedSnapshot(), - versioner=FileVersioner(version_file=seed_dir / ".version"), - locker=FileLocker( - lock_path=seed_dir / ".lock", - poll=SEED_SNAPSHOT_FILELOCK_POLL, - timeout=SEED_SNAPSHOT_FILELOCK_TIMEOUT, - ), - ), - _shared_errors=Synced.new_or_load( - data=ErrDump(), - location=error_dir, - reader_writer=ReaderWriterErrDump("all"), - versioner=FileVersioner(version_file=error_dir / ".all.version"), - locker=FileLocker( - lock_path=error_dir / ".all.lock", - poll=GLOBAL_ERR_FILELOCK_POLL, - timeout=GLOBAL_ERR_FILELOCK_TIMEOUT, - ), - ), - _optimizer_state=Synced.new_or_load( - data=optimizer_state, - location=optimizer_state_dir, - reader_writer=ReaderWriterOptimizationState(), - versioner=FileVersioner(version_file=optimizer_state_dir / ".version"), - locker=FileLocker( - lock_path=optimizer_state_dir / ".lock", - poll=OPTIMIZER_STATE_FILELOCK_POLL, - timeout=OPTIMIZER_STATE_FILELOCK_TIMEOUT, - ), - ), - ) + finally: + if worker_id is not None: + with contextlib.suppress(Exception): + logger.debug( + "Worker %s released lock on %s at %s", + worker_id, + self.lock_path, + time.time(), + ) diff --git a/neps/state/neps_state.py b/neps/state/neps_state.py index 160c028c0..92bd2e5ad 100644 --- a/neps/state/neps_state.py +++ b/neps/state/neps_state.py @@ -10,113 +10,380 @@ from __future__ import annotations +import io import logging +import pickle import time -from collections.abc import Callable +from collections.abc import Callable, Iterable from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Generic, TypeVar, overload - -from more_itertools import take - +from pathlib import Path +from typing import ( + Literal, + TypeAlias, + TypeVar, + overload, +) + +from neps.env import ( + GLOBAL_ERR_FILELOCK_POLL, + GLOBAL_ERR_FILELOCK_TIMEOUT, + STATE_FILELOCK_POLL, + STATE_FILELOCK_TIMEOUT, + TRIAL_CACHE_MAX_UPDATES_BEFORE_CONSOLIDATION, + TRIAL_FILELOCK_POLL, + TRIAL_FILELOCK_TIMEOUT, +) +from neps.exceptions import NePSError, TrialAlreadyExistsError, TrialNotFoundError +from neps.optimizers.base_optimizer import BaseOptimizer from neps.state.err_dump import ErrDump +from neps.state.filebased import ( + FileLocker, + ReaderWriterErrDump, + ReaderWriterTrial, + TrialWriteHint, +) from neps.state.optimizer import OptimizationState, OptimizerInfo -from neps.state.trial import Trial - -if TYPE_CHECKING: - from neps.optimizers.base_optimizer import BaseOptimizer - from neps.state.protocols import Synced, TrialRepo - from neps.state.seed_snapshot import SeedSnapshot +from neps.state.trial import Report, Trial +from neps.utils.files import atomic_write, deserialize, serialize logger = logging.getLogger(__name__) + # TODO: Technically we don't need the same Location type for all shared objects. Loc = TypeVar("Loc") T = TypeVar("T") +Version: TypeAlias = str + +Resource: TypeAlias = Literal[ + "optimizer_info", "optimizer_state", "seed_state", "errors", "configs" +] + + +N_UNSAFE_RETRIES = 10 + +CONFIG_PREFIX_LEN = len("config_") + +# TODO: Ergonomics of this class sucks @dataclass -class NePSState(Generic[Loc]): - """The main state object that holds all the shared state objects.""" +class TrialRepo: + """A repository for trials that are stored on disk. + + !!! warning + + This class does not implement locking and it is up to the caller to ensure + there are no race conflicts. + """ + + CACHE_FILE_NAME = ".trial_cache.pkl" + UPDATE_CONSOLIDATION_LIMIT = TRIAL_CACHE_MAX_UPDATES_BEFORE_CONSOLIDATION + + directory: Path + cache_path: Path = field(init=False) + + def __post_init__(self) -> None: + self.directory.mkdir(parents=True, exist_ok=True) + self.cache_path = self.directory / self.CACHE_FILE_NAME + + def list_trial_ids(self) -> list[str]: + """List all the trial ids on disk.""" + return [ + config_path.name[CONFIG_PREFIX_LEN:] + for config_path in self.directory.iterdir() + if config_path.name.startswith("config_") and config_path.is_dir() + ] - location: str + def _read_pkl_and_maybe_consolidate( + self, + *, + consolidate: bool | None = None, + ) -> dict[str, Trial]: + with self.cache_path.open("rb") as f: + _bytes = f.read() + + buffer = io.BytesIO(_bytes) + trials: dict[str, Trial] = {} + updates: list[Trial] = [] + while True: + try: + datum = pickle.load(buffer) # noqa: S301 + + # If it's a `dict`, this is the whol trials cache + if isinstance(datum, dict): + assert len(trials) == 0, "Multiple caches present." + trials = datum + + # If it's a `list`, these are multiple updates + elif isinstance(datum, list): + updates.extend(datum) + + # Otherwise it's a single update + else: + assert isinstance(datum, Trial), "Not a trial." + updates.append(datum) + except EOFError: + break + + trials.update({trial.id: trial for trial in updates}) + if consolidate is True or ( + len(updates) > self.UPDATE_CONSOLIDATION_LIMIT and consolidate is None + ): + logger.debug( + "Consolidating trial cache with %d trials and %d updates.", + len(trials), + len(updates), + ) + pickle_bytes = pickle.dumps(trials, protocol=pickle.HIGHEST_PROTOCOL) + with atomic_write(self.cache_path, "wb") as f: + f.write(pickle_bytes) + + return trials + + def latest(self) -> dict[str, Trial]: + """Get the latest trials from the cache.""" + if not self.cache_path.exists(): + # If we end up with no cache but there are trials on disk, we need to read in. + if any(path.name.startswith("config_") for path in self.directory.iterdir()): + trial_ids = self.list_trial_ids() + trials = { + trial_id: self.load_trial_from_disk(trial_id) + for trial_id in trial_ids + } + pickle_bytes = pickle.dumps(trials, protocol=pickle.HIGHEST_PROTOCOL) + with atomic_write(self.cache_path, "wb") as f: + f.write(pickle_bytes) + + return {} + + return self._read_pkl_and_maybe_consolidate() - _trials: TrialRepo[Loc] = field(repr=False) - _optimizer_info: Synced[OptimizerInfo, Loc] - _seed_state: Synced[SeedSnapshot, Loc] = field(repr=False) - _optimizer_state: Synced[OptimizationState, Loc] - _shared_errors: Synced[ErrDump, Loc] = field(repr=False) + def store_new_trial(self, trial: Trial | list[Trial]) -> None: + """Write a new trial to disk. - def put_updated_trial(self, trial: Trial, /) -> None: - """Update the trial with the new information. + Raises: + TrialAlreadyExistsError: If the trial already exists on disk. + """ + if isinstance(trial, Trial): + config_path = self.directory / f"config_{trial.id}" + if config_path.exists(): + raise TrialAlreadyExistsError(trial.id, config_path) + + bytes_ = pickle.dumps(trial, protocol=pickle.HIGHEST_PROTOCOL) + with atomic_write(self.cache_path, "ab") as f: + f.write(bytes_) + + config_path.mkdir(parents=True, exist_ok=True) + ReaderWriterTrial.write( + trial, + self.directory / f"config_{trial.id}", + hints=["config", "metadata"], + ) + else: + for child_trial in trial: + config_path = self.directory / f"config_{child_trial.id}" + if config_path.exists(): + raise TrialAlreadyExistsError(child_trial.id, config_path) + config_path.mkdir(parents=True, exist_ok=True) + + bytes_ = pickle.dumps(trial, protocol=pickle.HIGHEST_PROTOCOL) + with atomic_write(self.cache_path, "ab") as f: + f.write(bytes_) + + for child_trial in trial: + ReaderWriterTrial.write( + child_trial, + self.directory / f"config_{child_trial.id}", + hints=["config", "metadata"], + ) + + def update_trial( + self, + trial: Trial, + *, + hints: Iterable[TrialWriteHint] | TrialWriteHint | None = ("report", "metadata"), + ) -> None: + """Update a trial on disk. Args: trial: The trial to update. + hints: The hints to use when updating the trial. Defines what files need + to be updated. + If you don't know, leave `None`, this is a micro-optimization. + """ + bytes_ = pickle.dumps(trial, protocol=pickle.HIGHEST_PROTOCOL) + with atomic_write(self.cache_path, "ab") as f: + f.write(bytes_) + + ReaderWriterTrial.write(trial, self.directory / f"config_{trial.id}", hints=hints) + + def load_trial_from_disk(self, trial_id: str) -> Trial: + """Load a trial from disk. Raises: - VersionMismatchError: If the trial has been updated since it was last - fetched by the worker using this state. This indicates that some other - worker has updated the trial in the meantime and the changes from - this worker are rejected. + TrialNotFoundError: If the trial is not found on disk. """ - shared_trial = self._trials.get_by_id(trial.id) - shared_trial.put(trial) + config_path = self.directory / f"config_{trial_id}" + if not config_path.exists(): + raise TrialNotFoundError( + f"Trial {trial_id} not found at expected path of {config_path}." + ) - def get_trial_by_id(self, trial_id: str, /) -> Trial: - """Get a trial by its id.""" - return self._trials.get_by_id(trial_id).synced() + return ReaderWriterTrial.read(config_path) + + +@dataclass +class NePSState: + """The main state object that holds all the shared state objects.""" + + path: Path + + _trial_lock: FileLocker = field(repr=False) + _trial_repo: TrialRepo = field(repr=False) + + _optimizer_lock: FileLocker = field(repr=False) + + _optimizer_info_path: Path = field(repr=False) + _optimizer_info: OptimizerInfo = field(repr=False) + + _optimizer_state_path: Path = field(repr=False) + _optimizer_state: OptimizationState = field(repr=False) + + _err_lock: FileLocker = field(repr=False) + _shared_errors_path: Path = field(repr=False) + _shared_errors: ErrDump = field(repr=False) - def sample_trial( + def lock_and_read_trials(self) -> dict[str, Trial]: + """Acquire the state lock and read the trials.""" + with self._trial_lock.lock(): + return self._trial_repo.latest() + + @overload + def lock_and_sample_trial( + self, optimizer: BaseOptimizer, *, worker_id: str, n: None = None + ) -> Trial: ... + @overload + def lock_and_sample_trial( + self, optimizer: BaseOptimizer, *, worker_id: str, n: int + ) -> list[Trial]: ... + + def lock_and_sample_trial( + self, optimizer: BaseOptimizer, *, worker_id: str, n: int | None = None + ) -> Trial | list[Trial]: + """Acquire the state lock and sample a trial.""" + with self._optimizer_lock.lock(): + with self._trial_lock.lock(): + trials_ = self._trial_repo.latest() + + trials = self._sample_trial( + optimizer, + trials=trials_, + worker_id=worker_id, + n=n, + ) + + with self._trial_lock.lock(): + self._trial_repo.store_new_trial(trials) + + return trials + + def lock_and_report_trial_evaluation( + self, + trial: Trial, + report: Report, + *, + worker_id: str, + ) -> None: + """Acquire the state lock and report the trial evaluation.""" + with self._trial_lock.lock(), self._err_lock.lock(): + self._report_trial_evaluation(trial, report, worker_id=worker_id) + + @overload + def _sample_trial( self, optimizer: BaseOptimizer, *, worker_id: str, + trials: dict[str, Trial], + n: int, + _sample_hooks: list[Callable] | None = ..., + ) -> list[Trial]: ... + + @overload + def _sample_trial( + self, + optimizer: BaseOptimizer, + *, + worker_id: str, + trials: dict[str, Trial], + n: None, + _sample_hooks: list[Callable] | None = ..., + ) -> Trial: ... + + def _sample_trial( + self, + optimizer: BaseOptimizer, + *, + worker_id: str, + trials: dict[str, Trial], + n: int | None, _sample_hooks: list[Callable] | None = None, - ) -> Trial: + ) -> Trial | list[Trial]: """Sample a new trial from the optimizer. + !!! warning + + Responsibility of locking is on caller. + Args: optimizer: The optimizer to sample the trial from. worker_id: The worker that is sampling the trial. + n: The number of trials to sample. + trials: The current trials. _sample_hooks: A list of hooks to apply to the optimizer before sampling. Returns: The new trial. """ - with ( - self._optimizer_state.acquire() as ( - opt_state, - put_opt, - ), - self._seed_state.acquire() as (seed_state, put_seed_state), - ): - trials: dict[str, Trial] = {} - for trial_id, shared_trial in self._trials.all().items(): - trial = shared_trial.synced() - trials[trial_id] = trial - - seed_state.set_as_global_seed_state() - - # TODO: Not sure if any existing pre_load hooks required - # it to be done after `load_results`... I hope not. - if _sample_hooks is not None: - for hook in _sample_hooks: - optimizer = hook(optimizer) - - # NOTE: We don't want optimizers mutating this before serialization - max_cost_total = ( - opt_state.budget.clone() if opt_state.budget is not None else None - ) - sampled_config_maybe_new_opt_state = optimizer.ask( - trials=trials, - max_cost_total_info=max_cost_total, + with self._optimizer_state_path.open("rb") as f: + opt_state: OptimizationState = pickle.load(f) # noqa: S301 + + opt_state.seed_snapshot.set_as_global_seed_state() + + # TODO: Not sure if any existing pre_load hooks required + # it to be done after `load_results`... I hope not. + if _sample_hooks is not None: + for hook in _sample_hooks: + optimizer = hook(optimizer) # type: ignore + + assert isinstance(optimizer, BaseOptimizer) + if opt_state.budget is not None: + # NOTE: All other values of budget are ones that should remain + # constant, there are currently only these two which are dynamic as + # optimization unfold + opt_state.budget.used_cost_budget = sum( + trial.report.cost + for trial in trials.values() + if trial.report is not None and trial.report.cost is not None ) + opt_state.budget.used_evaluations = len(trials) + + sampled_configs = optimizer.ask( + trials=trials, + budget_info=opt_state.budget.clone() + if opt_state.budget is not None + else None, + n=n, + ) - if isinstance(sampled_config_maybe_new_opt_state, tuple): - sampled_config, new_opt_state = sampled_config_maybe_new_opt_state - else: - sampled_config = sampled_config_maybe_new_opt_state - new_opt_state = opt_state.shared_state + if not isinstance(sampled_configs, list): + sampled_configs = [sampled_configs] + # TODO: Not implemented yet. + shared_state = opt_state.shared_state + + sampled_trials: list[Trial] = [] + for sampled_config in sampled_configs: if sampled_config.previous_config_id is not None: previous_trial = trials.get(sampled_config.previous_config_id) if previous_trial is None: @@ -129,29 +396,30 @@ def sample_trial( trial = Trial.new( trial_id=sampled_config.id, - location="", # HACK: This will be set by the `TrialRepo` + location="", # HACK: This will be set by the `TrialRepo` in `put_new` config=sampled_config.config, previous_trial=sampled_config.previous_config_id, previous_trial_location=previous_trial_location, time_sampled=time.time(), worker_id=worker_id, ) - shared_trial = self._trials.put_new(trial) - seed_state.recapture() - put_seed_state(seed_state) - put_opt( - OptimizationState( - budget=opt_state.budget, - shared_state=new_opt_state, - ) - ) + sampled_trials.append(trial) + + opt_state.shared_state = shared_state + opt_state.seed_snapshot.recapture() + with self._optimizer_state_path.open("wb") as f: + pickle.dump(opt_state, f, protocol=pickle.HIGHEST_PROTOCOL) - return trial + if n is None: + assert len(sampled_trials) == 1 + return sampled_trials[0] - def report_trial_evaluation( + return sampled_trials + + def _report_trial_evaluation( self, trial: Trial, - report: Trial.Report, + report: Report, *, worker_id: str, ) -> None: @@ -164,73 +432,217 @@ def report_trial_evaluation( optimizer: The optimizer to update and get the state from worker_id: The worker that evaluated the trial. """ - shared_trial = self._trials.get_by_id(trial.id) - # TODO: This would fail if some other worker has already updated the trial. - # IMPORTANT: We need to attach the report to the trial before updating the things. trial.report = report - shared_trial.put(trial) - logger.debug("Updated trial '%s' with status '%s'", trial.id, trial.state) - with self._optimizer_state.acquire() as (opt_state, put_opt_state): - # TODO: If an optimizer doesn't use the state, this is a waste of time. - # Update the budget if we have one. - if opt_state.budget is not None: - max_cost_total_info = opt_state.budget - - if report.cost is not None: - max_cost_total_info.used_cost_budget += report.cost - put_opt_state(opt_state) + self._trial_repo.update_trial(trial, hints=["report", "metadata"]) if report.err is not None: - with self._shared_errors.acquire() as (errs, put_errs): - trial_err = ErrDump.SerializableTrialError( - trial_id=trial.id, - worker_id=worker_id, - err_type=type(report.err).__name__, - err=str(report.err), - tb=report.tb, + with self._err_lock.lock(): + err_dump = ReaderWriterErrDump.read(self._shared_errors_path) + err_dump.errs.append( + ErrDump.SerializableTrialError( + trial_id=trial.id, + worker_id=worker_id, + err_type=type(report.err).__name__, + err=str(report.err), + tb=report.tb, + ) ) - errs.append(trial_err) - put_errs(errs) + ReaderWriterErrDump.write(err_dump, self._shared_errors_path) + + def all_trial_ids(self) -> list[str]: + """Get all the trial ids.""" + return self._trial_repo.list_trial_ids() - def get_errors(self) -> ErrDump: + def lock_and_get_errors(self) -> ErrDump: """Get all the errors that have occurred during the optimization.""" - return self._shared_errors.synced() + with self._err_lock.lock(): + return ReaderWriterErrDump.read(self._shared_errors_path) + + def lock_and_get_optimizer_info(self) -> OptimizerInfo: + """Get the optimizer information.""" + with self._optimizer_lock.lock(): + return OptimizerInfo(info=deserialize(self._optimizer_info_path)) + + def lock_and_get_optimizer_state(self) -> OptimizationState: + """Get the optimizer state.""" + with self._optimizer_lock.lock(): # noqa: SIM117 + with self._optimizer_state_path.open("rb") as f: + obj = pickle.load(f) # noqa: S301 + assert isinstance(obj, OptimizationState) + return obj + + def lock_and_get_trial_by_id(self, trial_id: str) -> Trial: + """Get a trial by its id.""" + with self._trial_lock.lock(): + return self._trial_repo.load_trial_from_disk(trial_id) + + def unsafe_retry_get_trial_by_id(self, trial_id: str) -> Trial: + """Get a trial by id but use unsafe retries.""" + for _ in range(N_UNSAFE_RETRIES): + try: + return self._trial_repo.load_trial_from_disk(trial_id) + except TrialNotFoundError as e: + raise e + except Exception as e: # noqa: BLE001 + logger.warning( + "Failed to get trial '%s' due to an error: %s", trial_id, e + ) + time.sleep(0.1) + continue + + raise NePSError( + f"Failed to get trial '{trial_id}' after {N_UNSAFE_RETRIES} retries." + ) + + def put_updated_trial( + self, + trial: Trial, + *, + hints: list[TrialWriteHint] | TrialWriteHint | None = None, + ) -> None: + """Update the trial. + + Args: + trial: The trial to update. + hints: The hints to use when updating the trial. Defines what files need + to be updated. + If you don't know, leave `None`, this is a micro-optimization. + """ + with self._trial_lock.lock(): + self._trial_repo.update_trial(trial, hints=hints) @overload - def get_next_pending_trial(self) -> Trial | None: ... + def lock_and_get_next_pending_trial(self) -> Trial | None: ... @overload - def get_next_pending_trial(self, n: int | None = None) -> list[Trial]: ... + def lock_and_get_next_pending_trial(self, n: int | None = None) -> list[Trial]: ... - def get_next_pending_trial(self, n: int | None = None) -> Trial | list[Trial] | None: - """Get the next pending trial to evaluate. + def lock_and_get_next_pending_trial( + self, + n: int | None = None, + ) -> Trial | list[Trial] | None: + """Get the next pending trial.""" + with self._trial_lock.lock(): + trials = self._trial_repo.latest() + pendings = sorted( + [ + trial + for trial in trials.values() + if trial.metadata.state == Trial.State.PENDING + ], + key=lambda t: t.metadata.time_sampled, + ) + if n is None: + return pendings[0] if pendings else None + return pendings[:n] + + @classmethod + def create_or_load( + cls, + path: Path, + *, + load_only: bool = False, + optimizer_info: OptimizerInfo | None = None, + optimizer_state: OptimizationState | None = None, + ) -> NePSState: + """Create a new NePSState in a directory or load the existing one + if it already exists, depending on the argument. - Args: - n: The number of trials to get. If `None`, get the next trial. + !!! warning - Returns: - The next trial or a list of trials if `n` is not `None`. - """ - _pending_itr = ( - shared_trial.synced() for _, shared_trial in self._trials.pending() - ) - if n is not None: - return take(n, _pending_itr) - return next(_pending_itr, None) + We check that the optimizer info in the NePSState on disk matches + the one that is passed. However we do not lock this check so it + is possible that if two processes try to create a NePSState at the + same time, both with different optimizer infos, that one will fail + to create the NePSState. This is a limitation of the current design. - def all_trial_ids(self) -> set[str]: - """Get all the trial ids that are known about.""" - return self._trials.all_trial_ids() + In principal, we could allow multiple optimizers to be run and share + the same set of trials. - def get_all_trials(self) -> dict[str, Trial]: - """Get all the trials that are known about.""" - return {_id: trial.synced() for _id, trial in self._trials.all().items()} + Args: + path: The directory to create the state in. + load_only: If True, only load the state and do not create a new one. + optimizer_info: The optimizer info to use. + optimizer_state: The optimizer state to use. - def optimizer_info(self) -> OptimizerInfo: - """Get the optimizer information.""" - return self._optimizer_info.synced() + Returns: + The NePSState. - def optimizer_state(self) -> OptimizationState: - """Get the optimizer state.""" - return self._optimizer_state.synced() + Raises: + NePSError: If the optimizer info on disk does not match the one provided. + """ + is_new = not path.exists() + if load_only: + if is_new: + raise FileNotFoundError(f"No NePSState found at '{path}'.") + else: + assert optimizer_info is not None + assert optimizer_state is not None + + path.mkdir(parents=True, exist_ok=True) + config_dir = path / "configs" + config_dir.mkdir(parents=True, exist_ok=True) + + optimizer_info_path = path / "optimizer_info.yaml" + optimizer_state_path = path / "optimizer_state.pkl" + shared_errors_path = path / "shared_errors.jsonl" + + # We have to do one bit of sanity checking to ensure that the optimzier + # info on disk manages the one we have recieved, otherwise we are unsure which + # optimizer is being used. + # NOTE: We assume that we do not have to worry about a race condition + # here where we have two different NePSState objects with two different optimizer + # infos trying to be created at the same time. This avoids the need to lock to + # check the optimizer info. If this assumption changes, then we would have + # to first lock before we do this check + if not is_new: + existing_info = OptimizerInfo(info=deserialize(optimizer_info_path)) + if not load_only and existing_info != optimizer_info: + raise NePSError( + "The optimizer info on disk does not match the one provided." + f"\nOn disk: {existing_info}\nProvided: {optimizer_info}" + f"\n\nLoaded the one on disk from {path}." + ) + with optimizer_state_path.open("rb") as f: + optimizer_state = pickle.load(f) # noqa: S301 + + optimizer_info = existing_info + error_dump = ReaderWriterErrDump.read(shared_errors_path) + else: + assert optimizer_info is not None + assert optimizer_state is not None + + serialize(optimizer_info.info, path=optimizer_info_path) + with optimizer_state_path.open("wb") as f: + pickle.dump(optimizer_state, f, protocol=pickle.HIGHEST_PROTOCOL) + + error_dump = ErrDump([]) + + return NePSState( + path=path, + _trial_repo=TrialRepo(config_dir), + # Locks, + _trial_lock=FileLocker( + lock_path=path / ".configs.lock", + poll=TRIAL_FILELOCK_POLL, + timeout=TRIAL_FILELOCK_TIMEOUT, + ), + _optimizer_lock=FileLocker( + lock_path=path / ".optimizer.lock", + poll=STATE_FILELOCK_POLL, + timeout=STATE_FILELOCK_TIMEOUT, + ), + _err_lock=FileLocker( + lock_path=path / ".errors.lock", + poll=GLOBAL_ERR_FILELOCK_POLL, + timeout=GLOBAL_ERR_FILELOCK_TIMEOUT, + ), + # State + _optimizer_info_path=optimizer_info_path, + _optimizer_info=optimizer_info, + _optimizer_state_path=optimizer_state_path, + _optimizer_state=optimizer_state, # type: ignore + _shared_errors_path=shared_errors_path, + _shared_errors=error_dump, + ) diff --git a/neps/state/optimizer.py b/neps/state/optimizer.py index 5da537e84..ffe90b3cc 100644 --- a/neps/state/optimizer.py +++ b/neps/state/optimizer.py @@ -4,7 +4,10 @@ from collections.abc import Mapping from dataclasses import dataclass, replace -from typing import Any +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from neps.state.seed_snapshot import SeedSnapshot @dataclass @@ -28,7 +31,10 @@ class OptimizationState: budget: BudgetInfo | None """Information regarind the budget used by the optimization trajectory.""" - shared_state: dict[str, Any] + seed_snapshot: SeedSnapshot + """The state of the random number generators at the time of the last sample.""" + + shared_state: dict[str, Any] | None """Any information the optimizer wants to store between calls to sample and post evaluations. diff --git a/neps/state/protocols.py b/neps/state/protocols.py deleted file mode 100644 index 51bff7d36..000000000 --- a/neps/state/protocols.py +++ /dev/null @@ -1,557 +0,0 @@ -"""This module defines the protocols used by -[`NePSState`][neps.state.neps_state.NePSState] and -[`Synced`][neps.state.synced.Synced] to ensure atomic operations to the state itself. -""" - -from __future__ import annotations - -import logging -from collections.abc import Callable, Iterable, Iterator -from contextlib import contextmanager -from copy import deepcopy -from dataclasses import dataclass -from typing import TYPE_CHECKING, ClassVar, Generic, Protocol, TypeVar -from typing_extensions import Self - -from neps.exceptions import ( - LockFailedError, - TrialAlreadyExistsError, - TrialNotFoundError, - VersionedResourceAlreadyExistsError, - VersionedResourceDoesNotExistsError, - VersionedResourceRemovedError, - VersionMismatchError, -) - -if TYPE_CHECKING: - from neps.state import Trial - -logger = logging.getLogger(__name__) - -T = TypeVar("T") -K = TypeVar("K") - -# https://github.com/MaT1g3R/option/issues/40 -K2 = TypeVar("K2") -T2 = TypeVar("T2") - -Loc_contra = TypeVar("Loc_contra", contravariant=True) - - -class Versioner(Protocol): - """A versioner that can bump the version of a resource. - - It should have some [`current()`][neps.state.protocols.Versioner.current] method - to give the current version tag of a resource and a - [`bump()`][neps.state.protocols.Versioner.bump] method to provide a new version tag. - - These [`current()`][neps.state.protocols.Versioner.current] and - [`bump()`][neps.state.protocols.Versioner.bump] methods do not need to be atomic - but they should read/write to external state, i.e. file-system, database, etc. - """ - - def current(self) -> str | None: - """Return the current version as defined by the external state, i.e. - the version of the tag on disk. - - Returns: - The current version if there is one written. - """ - ... - - def bump(self) -> str: - """Create a new external version tag. - - Returns: - The new version tag. - """ - ... - - -class Locker(Protocol): - """A locker that can be used to communicate between workers.""" - - LockFailedError: ClassVar = LockFailedError - - @contextmanager - def lock(self) -> Iterator[None]: - """Initiate the lock as a context manager, releasing it when done.""" - ... - - def is_locked(self) -> bool: - """Check if lock is...well, locked. - - Should return True if the resource is locked, even if the lock is held by the - current worker/process. - """ - ... - - -class ReaderWriter(Protocol[T, Loc_contra]): - """A reader-writer that can read and write some resource T with location Loc. - - For example, a `ReaderWriter[Trial, Path]` indicates a class that can read and write - trials, given some `Path`. - """ - - def read(self, loc: Loc_contra, /) -> T: - """Read the resource at the given location.""" - ... - - def write(self, value: T, loc: Loc_contra, /) -> None: - """Write the resource at the given location.""" - ... - - -class TrialRepo(Protocol[K]): - """A repository of trials. - - The primary purpose of this protocol is to ensure consistent access to trial, - the ability to put in a new trial and know about the trials that are stored there. - """ - - TrialAlreadyExistsError: ClassVar = TrialAlreadyExistsError - TrialNotFoundError: ClassVar = TrialNotFoundError - - def all_trial_ids(self) -> set[str]: - """List all the trial ids in this trial Repo.""" - ... - - def get_by_id(self, trial_id: str) -> Synced[Trial, K]: - """Get a trial by its id.""" - ... - - def put_new(self, trial: Trial) -> Synced[Trial, K]: - """Put a new trial in the repo.""" - ... - - def all(self) -> dict[str, Synced[Trial, K]]: - """Get all trials in the repo.""" - ... - - def pending(self) -> Iterable[tuple[str, Synced[Trial, K]]]: - """Get all pending trials in the repo. - - !!! note - This should return trials in the order in which they should be next evaluated, - usually the order in which they were put in the repo. - """ - ... - - -@dataclass -class VersionedResource(Generic[T, K]): - """A resource that will be read if it needs to update to the latest version. - - Relies on 3 main components: - * A [`Versioner`][neps.state.protocols.Versioner] to manage the versioning of the - resource. - * A [`ReaderWriter`][neps.state.protocols.ReaderWriter] to read and write the - resource. - * The location of the resource that can be used for the reader-writer. - """ - - VersionMismatchError: ClassVar = VersionMismatchError - VersionedResourceDoesNotExistsError: ClassVar = VersionedResourceDoesNotExistsError - VersionedResourceAlreadyExistsError: ClassVar = VersionedResourceAlreadyExistsError - VersionedResourceRemovedError: ClassVar = VersionedResourceRemovedError - - _current: T - _location: K - _version: str - _versioner: Versioner - _reader_writer: ReaderWriter[T, K] - - @staticmethod - def new( - *, - data: T2, - location: K2, - versioner: Versioner, - reader_writer: ReaderWriter[T2, K2], - ) -> VersionedResource[T2, K2]: - """Create a new VersionedResource. - - This will create a new resource if it doesn't exist, otherwise, - if it already exists, it will raise an error. - - Use [`load()`][neps.state.protocols.VersionedResource.load] if you want to - load an existing resource. - - Args: - data: The data to be stored. - location: The location where the data will be stored. - versioner: The versioner to be used. - reader_writer: The reader-writer to be used. - - Returns: - A new VersionedResource - - Raises: - VersionedResourceAlreadyExistsError: If a versioned resource already exists - at the given location. - """ - current_version = versioner.current() - if current_version is not None: - raise VersionedResourceAlreadyExistsError( - f"A versioend resource already already exists at '{location}'" - f" with version '{current_version}'" - ) - - version = versioner.bump() - reader_writer.write(data, location) - return VersionedResource( - _current=data, - _location=location, - _version=version, - _versioner=versioner, - _reader_writer=reader_writer, - ) - - @classmethod - def load( - cls, - *, - location: K2, - versioner: Versioner, - reader_writer: ReaderWriter[T2, K2], - ) -> VersionedResource[T2, K2]: - """Load an existing VersionedResource. - - This will load an existing resource if it exists, otherwise, it will raise an - error. - - Use [`new()`][neps.state.protocols.VersionedResource.new] if you want to - create a new resource. - - Args: - location: The location of the resource. - versioner: The versioner to be used. - reader_writer: The reader-writer to be used. - - Returns: - A VersionedResource - - Raises: - VersionedResourceDoesNotExistsError: If no versioned resource exists at - the given location. - """ - version = versioner.current() - if version is None: - raise cls.VersionedResourceDoesNotExistsError( - f"No versioned resource exists at '{location}'." - ) - data = reader_writer.read(location) - return VersionedResource( - _current=data, - _location=location, - _version=version, - _versioner=versioner, - _reader_writer=reader_writer, - ) - - def sync_and_get(self) -> T: - """Get the data and version of the resource.""" - self.sync() - return self._current - - def sync(self) -> None: - """Sync the resource with the latest version.""" - current_version = self._versioner.current() - if current_version is None: - raise self.VersionedResourceRemovedError( - f"Versioned resource at '{self._location}' has been removed!" - f" Last known version was '{self._version}'." - ) - - if self._version != current_version: - self._current = self._reader_writer.read(self._location) - self._version = current_version - - def put(self, data: T) -> None: - """Put the data and version of the resource. - - Raises: - VersionMismatchError: If the version of the resource is not the same as the - current version. This implies that the resource has been updated by - another worker. - """ - current_version = self._versioner.current() - if self._version != current_version: - raise self.VersionMismatchError( - f"Version mismatch - ours: '{self._version}', remote: '{current_version}'" - f" Tried to put data at '{self._location}'. Doing so would overwrite" - " changes made by another worker. The solution is to pull the latest" - " version of the resource and try again." - " The most possible reasons for this error is that a lock was not" - " utilized when getting this resource before putting it back." - ) - - self._reader_writer.write(data, self._location) - self._current = data - self._version = self._versioner.bump() - - def current(self) -> T: - """Get the current data of the resource.""" - return self._current - - def is_stale(self) -> bool: - """Check if the resource is stale.""" - return self._version != self._versioner.current() - - def location(self) -> K: - """Get the location of the resource.""" - return self._location - - -@dataclass -class Synced(Generic[T, K]): - """Manages a versioned resource but it's methods also implement locking procedures - for accessing it. - - Its types are parametrized by two type variables: - - * `T` is the type of the data stored in the resource. - * `K` is the type of the location of the resource, for example `Path` - - This wraps a [`VersionedResource`][neps.state.protocols.VersionedResource] and - additionally provides utility to perform atmoic operations on it using a - [`Locker`][neps.state.protocols.Locker]. - - This is used by [`NePSState`][neps.state.neps_state.NePSState] to manage the state - of trials and other shared resources. - - It consists of 2 main components: - - * A [`VersionedResource`][neps.state.protocols.VersionedResource] to manage the - versioning of the resource. - * A [`Locker`][neps.state.protocols.Locker] to manage the locking of the resource. - - The primary methods to interact with a resource that is behined a `Synced` are: - - * [`synced()`][neps.state.protocols.Synced.synced] to get the data of the resource - after syncing it to it's latest verison. - * [`acquire()`][neps.state.protocols.Synced.acquire] context manager to get latest - version of the data while also mainting a lock on it. This additionally provides - a `put()` operation to put the data back. This can primarily be used to get the - data, perform some mutation on it and then put it back, while not allowing other - workers access to the data. - """ - - LockFailedError: ClassVar = Locker.LockFailedError - VersionedResourceRemovedError: ClassVar = ( - VersionedResource.VersionedResourceRemovedError - ) - VersionMismatchError: ClassVar = VersionedResource.VersionMismatchError - VersionedResourceAlreadyExistsError: ClassVar = ( - VersionedResource.VersionedResourceAlreadyExistsError - ) - VersionedResourceDoesNotExistsError: ClassVar = ( - VersionedResource.VersionedResourceDoesNotExistsError - ) - - _resource: VersionedResource[T, K] - _locker: Locker - - @classmethod - def new( - cls, - *, - locker: Locker, - data: T2, - location: K2, - versioner: Versioner, - reader_writer: ReaderWriter[T2, K2], - ) -> Synced[T2, K2]: - """Create a new Synced resource. - - This will create a new resource if it doesn't exist, otherwise, - if it already exists, it will raise an error. - - Use [`load()`][neps.state.protocols.Synced.load] if you want to load an existing - resource. Use [`new_or_load()`][neps.state.protocols.Synced.new_or_load] if you - want to create a new resource if it doesn't exist, otherwise load an existing - resource. - - Args: - locker: The locker to be used. - data: The data to be stored. - location: The location where the data will be stored. - versioner: The versioner to be used. - reader_writer: The reader-writer to be used. - - Returns: - A new Synced resource. - - Raises: - VersionedResourceAlreadyExistsError: If a versioned resource already exists - at the given location. - """ - with locker.lock(): - vr = VersionedResource.new( - data=data, - location=location, - versioner=versioner, - reader_writer=reader_writer, - ) - return Synced(_resource=vr, _locker=locker) - - @classmethod - def load( - cls, - *, - locker: Locker, - location: K2, - versioner: Versioner, - reader_writer: ReaderWriter[T2, K2], - ) -> Synced[T2, K2]: - """Load an existing Synced resource. - - This will load an existing resource if it exists, otherwise, it will raise an - error. - - Use [`new()`][neps.state.protocols.Synced.new] if you want to create a new - resource. Use [`new_or_load()`][neps.state.protocols.Synced.new_or_load] if you - want to create a new resource if it doesn't exist, otherwise load an existing - resource. - - Args: - locker: The locker to be used. - location: The location of the resource. - versioner: The versioner to be used. - reader_writer: The reader-writer to be used. - - Returns: - A Synced resource. - - Raises: - VersionedResourceDoesNotExistsError: If no versioned resource exists at - the given location. - """ - with locker.lock(): - return Synced( - _resource=VersionedResource.load( - location=location, - versioner=versioner, - reader_writer=reader_writer, - ), - _locker=locker, - ) - - @classmethod - def new_or_load( - cls, - *, - locker: Locker, - data: T2, - location: K2, - versioner: Versioner, - reader_writer: ReaderWriter[T2, K2], - ) -> Synced[T2, K2]: - """Create a new Synced resource if it doesn't exist, otherwise load it. - - This will create a new resource if it doesn't exist, otherwise, it will load - an existing resource. - - Use [`new()`][neps.state.protocols.Synced.new] if you want to create a new - resource and fail otherwise. Use [`load()`][neps.state.protocols.Synced.load] - if you want to load an existing resource and fail if it doesn't exist. - - Args: - locker: The locker to be used. - data: The data to be stored. - - !!! warning - - This will be ignored if the data already exists. - - location: The location where the data will be stored. - versioner: The versioner to be used. - reader_writer: The reader-writer to be used. - - Returns: - A Synced resource. - """ - try: - return Synced.new( - locker=locker, - data=data, - location=location, - versioner=versioner, - reader_writer=reader_writer, - ) - except VersionedResourceAlreadyExistsError: - return Synced.load( - locker=locker, - location=location, - versioner=versioner, - reader_writer=reader_writer, - ) - - def synced(self) -> T: - """Get the data of the resource atomically.""" - with self._locker.lock(): - return self._resource.sync_and_get() - - def location(self) -> K: - """Get the location of the resource.""" - return self._resource.location() - - def put(self, data: T) -> None: - """Update the data atomically.""" - with self._locker.lock(): - self._resource.put(data) - - @contextmanager - def acquire(self) -> Iterator[tuple[T, Callable[[T], None]]]: - """Acquire the lock and get the data of the resource. - - This is a context manager that returns the data of the resource and a function - to put the data back. - - !!! note - This is the primary way to get the resource, mutate it and put it back. - Otherwise you likely want [`synced()`][neps.state.protocols.Synced.synced] - or [`put()`][neps.state.protocols.Synced.put]. - - Yields: - A tuple containing the data of the resource and a function to put the data - back. - """ - with self._locker.lock(): - self._resource.sync() - yield self._resource.current(), self._put_unsafe - - def deepcopy(self) -> Self: - """Create a deep copy of the shared resource.""" - return deepcopy(self) - - def _components(self) -> tuple[T, K, Versioner, ReaderWriter[T, K], Locker]: - """Get the components of the shared resource.""" - return ( - self._resource.current(), - self._resource.location(), - self._resource._versioner, - self._resource._reader_writer, - self._locker, - ) - - def _unsynced(self) -> T: - """Get the current data of the resource **without** locking and syncing it.""" - return self._resource.current() - - def _is_stale(self) -> bool: - """Check if the data held currently is not the latest version.""" - return self._resource.is_stale() - - def _is_locked(self) -> bool: - """Check if the resource is locked.""" - return self._locker.is_locked() - - def _put_unsafe(self, data: T) -> None: - """Put the data without checking for staleness or acquiring the lock. - - !!! warning - This should only really be called if you know what you're doing. - """ - self._resource.put(data) diff --git a/neps/state/settings.py b/neps/state/settings.py index def6a2303..148d8c277 100644 --- a/neps/state/settings.py +++ b/neps/state/settings.py @@ -74,6 +74,9 @@ class WorkerSettings: default_report_values: DefaultReportValues """Values to use when an error occurs or was not specified.""" + batch_size: int | None + """The number of configurations to sample in a single batch.""" + # --------- Global Stopping Criterion --------- max_evaluations_total: int | None """The maximum number of evaluations to run in total. diff --git a/neps/state/trial.py b/neps/state/trial.py index d80c3fbc5..0ead5a21c 100644 --- a/neps/state/trial.py +++ b/neps/state/trial.py @@ -36,10 +36,6 @@ class State(Enum): CORRUPTED = "corrupted" UNKNOWN = "unknown" - def pending(self) -> bool: - """Return True if the trial is pending.""" - return self in (State.PENDING, State.SUBMITTED, State.EVALUATING) - @dataclass class MetaData: @@ -47,8 +43,10 @@ class MetaData: id: str location: str + state: State previous_trial_id: str | None previous_trial_location: str | None + sampling_worker_id: str time_sampled: float @@ -137,7 +135,6 @@ class Trial: config: Mapping[str, Any] metadata: MetaData - state: State report: Report | None @classmethod @@ -155,10 +152,10 @@ def new( """Create a new trial object that was just sampled.""" worker_id = str(worker_id) return cls( - state=State.PENDING, config=config, metadata=MetaData( id=trial_id, + state=State.PENDING, location=location, time_sampled=time_sampled, previous_trial_id=previous_trial, @@ -202,13 +199,13 @@ def into_config_result( def set_submitted(self, *, time_submitted: float) -> None: """Set the trial as submitted.""" self.metadata.time_submitted = time_submitted - self.state = State.SUBMITTED + self.metadata.state = State.SUBMITTED def set_evaluating(self, *, time_started: float, worker_id: int | str) -> None: """Set the trial as in progress.""" self.metadata.time_started = time_started self.metadata.evaluating_worker_id = str(worker_id) - self.state = State.EVALUATING + self.metadata.state = State.EVALUATING def set_complete( self, @@ -225,11 +222,11 @@ def set_complete( ) -> Report: """Set the report for the trial.""" if report_as == "success": - self.state = State.SUCCESS + self.metadata.state = State.SUCCESS elif report_as == "failed": - self.state = State.FAILED + self.metadata.state = State.FAILED elif report_as == "crashed": - self.state = State.CRASHED + self.metadata.state = State.CRASHED else: raise ValueError(f"Invalid report_as: '{report_as}'") @@ -259,13 +256,13 @@ def set_complete( def set_corrupted(self) -> None: """Set the trial as corrupted.""" - self.state = State.CORRUPTED + self.metadata.state = State.CORRUPTED def reset(self) -> None: """Reset the trial to a pending state.""" - self.state = State.PENDING self.metadata = MetaData( id=self.metadata.id, + state=State.PENDING, location=self.metadata.location, previous_trial_id=self.metadata.previous_trial_id, previous_trial_location=self.metadata.previous_trial_location, diff --git a/neps/status/status.py b/neps/status/status.py index 5f6656a5c..2283730bb 100644 --- a/neps/status/status.py +++ b/neps/status/status.py @@ -9,9 +9,10 @@ import pandas as pd -from neps.state.filebased import load_filebased_neps_state +from neps.runtime import get_workers_neps_state +from neps.state.filebased import FileLocker +from neps.state.neps_state import NePSState from neps.state.trial import Trial -from neps.utils._locker import Locker from neps.utils.types import ConfigID, _ConfigResultForStats if TYPE_CHECKING: @@ -37,9 +38,12 @@ def get_summary_dict( # NOTE: We don't lock the shared state since we are just reading and don't need to # make decisions based on the state - shared_state = load_filebased_neps_state(root_directory) + try: + shared_state = get_workers_neps_state() + except RuntimeError: + shared_state = NePSState.create_or_load(root_directory) - trials = shared_state.get_all_trials() + trials = shared_state.lock_and_read_trials() evaluated: dict[ConfigID, _ConfigResultForStats] = {} @@ -58,12 +62,12 @@ def get_summary_dict( in_progress = { trial.id: trial.config for trial in trials.values() - if trial.State == Trial.State.EVALUATING + if trial.metadata.state == Trial.State.EVALUATING } pending = { trial.id: trial.config for trial in trials.values() - if trial.State == Trial.State.PENDING + if trial.metadata.state == Trial.State.PENDING } summary: dict[str, Any] = {} @@ -170,7 +174,7 @@ def status( return summary["previous_results"], summary["pending_configs"] -def _initiate_summary_csv(root_directory: str | Path) -> tuple[Path, Path, Locker]: +def _initiate_summary_csv(root_directory: str | Path) -> tuple[Path, Path, FileLocker]: """Initializes a summary CSV and an associated locker for file access control. Args: @@ -191,7 +195,7 @@ def _initiate_summary_csv(root_directory: str | Path) -> tuple[Path, Path, Locke csv_config_data = summary_csv_directory / "config_data.csv" csv_run_data = summary_csv_directory / "run_status.csv" - csv_locker = Locker(summary_csv_directory / ".csv_lock") + csv_locker = FileLocker(summary_csv_directory / ".csv_lock", poll=2, timeout=600) return ( csv_config_data, @@ -292,7 +296,7 @@ def _get_dataframes_from_summary( def _save_data_to_csv( config_data_file_path: Path, run_data_file_path: Path, - locker: Locker, + locker: FileLocker, config_data_df: pd.DataFrame, run_data_df: pd.DataFrame, ) -> None: @@ -309,7 +313,7 @@ def _save_data_to_csv( config_data_df: The DataFrame containing configuration data. run_data_df: The DataFrame containing additional run data. """ - with locker(poll=2): + with locker.lock(): try: pending_configs = run_data_df.loc["num_pending_configs", "value"] pending_configs_with_worker = run_data_df.loc[ diff --git a/neps/utils/_locker.py b/neps/utils/_locker.py deleted file mode 100644 index f2d430f8f..000000000 --- a/neps/utils/_locker.py +++ /dev/null @@ -1,62 +0,0 @@ -from __future__ import annotations - -from collections.abc import Iterator -from contextlib import contextmanager -from pathlib import Path -from typing import IO - -import portalocker as pl - -EXCLUSIVE_NONE_BLOCKING = pl.LOCK_EX | pl.LOCK_NB - - -class Locker: - FailedToAcquireLock = pl.exceptions.LockException - - def __init__(self, lock_path: Path): - self.lock_path = lock_path - self.lock_path.touch(exist_ok=True) - - @contextmanager - def try_lock(self) -> Iterator[bool]: - try: - with self.acquire(fail_when_locked=True): - yield True - except self.FailedToAcquireLock: - yield False - - def is_locked(self) -> bool: - with self.try_lock() as acquired_lock: - return not acquired_lock - - @contextmanager - def __call__( - self, - poll: float = 1, - *, - timeout: float | None = None, - fail_when_locked: bool = False, - ) -> Iterator[IO]: - with pl.Lock( - self.lock_path, - check_interval=poll, - timeout=timeout, - flags=EXCLUSIVE_NONE_BLOCKING, - fail_when_locked=fail_when_locked, - ) as fh: - yield fh # We almost never use it but nothing better to yield - - @contextmanager - def acquire( - self, - poll: float = 1.0, - *, - timeout: float | None = None, - fail_when_locked: bool = False, - ) -> Iterator[IO]: - with self( - poll, - timeout=timeout, - fail_when_locked=fail_when_locked, - ) as fh: - yield fh diff --git a/neps/utils/cli.py b/neps/utils/cli.py index 106ba1a47..901b2ac75 100644 --- a/neps/utils/cli.py +++ b/neps/utils/cli.py @@ -16,6 +16,7 @@ from typing import Optional, List import neps from neps.api import Default +from neps.state.seed_snapshot import SeedSnapshot from neps.status.status import post_run_csv import pandas as pd from neps.utils.run_args import ( @@ -40,13 +41,9 @@ ) from neps.optimizers.base_optimizer import BaseOptimizer from neps.utils.run_args import load_and_return_object -from neps.state.filebased import ( - create_or_load_filebased_neps_state, - load_filebased_neps_state, -) from neps.state.neps_state import NePSState from neps.state.trial import Trial -from neps.exceptions import VersionedResourceDoesNotExistsError, TrialNotFoundError +from neps.exceptions import TrialNotFoundError from neps.status.status import get_summary_dict from neps.api import _run_args from neps.state.optimizer import BudgetInfo, OptimizationState, OptimizerInfo @@ -140,16 +137,17 @@ def init_config(args: argparse.Namespace) -> None: else: directory = Path(directory) is_new = not directory.exists() - _ = create_or_load_filebased_neps_state( - directory=directory, + _ = NePSState.create_or_load( + path=directory, optimizer_info=OptimizerInfo(optimizer_info), optimizer_state=OptimizationState( + seed_snapshot=SeedSnapshot.new_capture(), budget=( BudgetInfo(max_cost_total=max_cost_total, used_cost_budget=0) if max_cost_total is not None else None ), - shared_state={}, # TODO: Unused for the time being... + shared_state=None, # TODO: Unused for the time being... ), ) if is_new: @@ -337,14 +335,14 @@ def info_config(args: argparse.Namespace) -> None: if neps_state is None: return try: - trial = neps_state.get_trial_by_id(config_id) + trial = neps_state.unsafe_retry_get_trial_by_id(config_id) except TrialNotFoundError: print(f"No trial found with ID {config_id}.") return print("Trial Information:") print(f" Trial ID: {trial.metadata.id}") - print(f" State: {trial.state}") + print(f" State: {trial.metadata.state}") print(f" Configurations:") for key, value in trial.config.items(): print(f" {key}: {value}") @@ -383,7 +381,7 @@ def load_neps_errors(args: argparse.Namespace) -> None: neps_state = load_neps_state(directory_path) if neps_state is None: return - errors = neps_state.get_errors() + errors = neps_state.lock_and_get_errors() if not errors.errs: print("No errors found.") @@ -443,7 +441,7 @@ def sample_config(args: argparse.Namespace) -> None: # Sample trials for _ in range(num_configs): try: - trial = neps_state.sample_trial(optimizer, worker_id=worker_id) + trial = neps_state.lock_and_sample_trial(optimizer, worker_id=worker_id) except Exception as e: print(f"Error during configuration sampling: {e}") continue # Skip to the next iteration @@ -493,10 +491,9 @@ def status(args: argparse.Namespace) -> None: summary = get_summary_dict(directory_path, add_details=True) # Calculate the number of trials in different states + trials = neps_state.lock_and_read_trials() evaluating_trials_count = sum( - 1 - for trial in neps_state.get_all_trials().values() - if trial.state.name == "EVALUATING" + 1 for trial in trials.values() if trial.metadata.state == Trial.State.EVALUATING ) pending_trials_count = summary["num_pending_configs"] succeeded_trials_count = summary["num_evaluated_configs"] - summary["num_error"] @@ -505,7 +502,7 @@ def status(args: argparse.Namespace) -> None: # Print summary print("NePS Status:") print("-----------------------------") - print(f"Optimizer: {neps_state.optimizer_info().info['searcher_alg']}") + print(f"Optimizer: {neps_state.lock_and_get_optimizer_info().info['searcher_alg']}") print(f"Succeeded Trials: {succeeded_trials_count}") print(f"Failed Trials (Errors): {failed_trials_count}") print(f"Active Trials: {evaluating_trials_count}") @@ -516,23 +513,22 @@ def status(args: argparse.Namespace) -> None: print("-----------------------------") # Retrieve and sort the trials by time_sampled - all_trials = neps_state.get_all_trials() sorted_trials = sorted( - all_trials.values(), key=lambda t: t.metadata.time_sampled, reverse=True + trials.values(), key=lambda t: t.metadata.time_sampled, reverse=True ) # Filter trials based on state if args.pending: filtered_trials = [ - trial for trial in sorted_trials if trial.state.name == "PENDING" + trial for trial in sorted_trials if trial.metadata.state.name == "PENDING" ] elif args.evaluating: filtered_trials = [ - trial for trial in sorted_trials if trial.state.name == "EVALUATING" + trial for trial in sorted_trials if trial.metadata.state.name == "EVALUATING" ] elif args.succeeded: filtered_trials = [ - trial for trial in sorted_trials if trial.state.name == "SUCCESS" + trial for trial in sorted_trials if trial.metadata.state.name == "SUCCESS" ] else: filtered_trials = sorted_trials[:7] @@ -556,7 +552,7 @@ def status(args: argparse.Namespace) -> None: # Print the details of the filtered trials for trial in filtered_trials: time_sampled = convert_timestamp(trial.metadata.time_sampled) - if trial.state.name in ["PENDING", "EVALUATING"]: + if trial.metadata.state.name in ["PENDING", "EVALUATING"]: duration = compute_duration(trial.metadata.time_sampled) else: duration = ( @@ -566,7 +562,7 @@ def status(args: argparse.Namespace) -> None: ) trial_id = trial.id worker_id = trial.metadata.sampling_worker_id - state = trial.state.name + state = trial.metadata.state.name objective_to_minimize = ( f"{trial.report.objective_to_minimize:.6f}" if (trial.report and trial.report.objective_to_minimize is not None) @@ -600,7 +596,7 @@ def status(args: argparse.Namespace) -> None: print("\nNo successful trial found.") # Display optimizer information - optimizer_info = neps_state.optimizer_info().info + optimizer_info = neps_state.lock_and_get_optimizer_info().info searcher_name = optimizer_info.get("searcher_name", "N/A") searcher_alg = optimizer_info.get("searcher_alg", "N/A") searcher_args = optimizer_info.get("searcher_args", {}) @@ -642,7 +638,7 @@ def sort_trial_id(trial_id: str) -> List[int]: # Convert each part to an integer for proper numeric sorting return [int(part) for part in parts] - trials = neps_state.get_all_trials() + trials = neps_state.lock_and_read_trials() sorted_trials = sorted(trials.values(), key=lambda x: sort_trial_id(x.id)) # Compute incumbents @@ -673,13 +669,10 @@ def sort_trial_id(trial_id: str) -> List[int]: print(f"Plot saved to '{plot_path}'.") -def load_neps_state(directory_path: Path) -> Optional[NePSState[Path]]: +def load_neps_state(directory_path: Path) -> Optional[NePSState]: """Load the NePS state with error handling.""" try: - return load_filebased_neps_state(directory_path) - except VersionedResourceDoesNotExistsError: - print(f"Error: No NePS state found in the directory '{directory_path}'.") - print("Ensure that the NePS run has been initialized correctly.") + return NePSState.create_or_load(directory_path, load_only=True) except Exception as e: print(f"Unexpected error loading NePS state: {e}") return None @@ -691,7 +684,8 @@ def compute_incumbents(sorted_trials: List[Trial]) -> List[Trial]: incumbents = [] for trial in sorted_trials: if ( - trial.report + trial.report is not None + and trial.report.objective_to_minimize is not None and trial.report.objective_to_minimize < best_objective_to_minimize ): best_objective_to_minimize = trial.report.objective_to_minimize @@ -1051,7 +1045,7 @@ def handle_report_config(args: argparse.Namespace) -> None: # Load the existing trial by ID try: - trial = neps_state.get_trial_by_id(args.trial_id) + trial = neps_state.unsafe_retry_get_trial_by_id(args.trial_id) if not trial: print(f"No trial found with ID {args.trial_id}") return @@ -1074,7 +1068,7 @@ def handle_report_config(args: argparse.Namespace) -> None: # Update NePS state try: - neps_state.report_trial_evaluation( + neps_state._report_trial_evaluation( trial=trial, report=report, worker_id=args.worker_id ) except Exception as e: diff --git a/neps/utils/common.py b/neps/utils/common.py index 1e7350633..6fd4a10ab 100644 --- a/neps/utils/common.py +++ b/neps/utils/common.py @@ -2,8 +2,10 @@ from __future__ import annotations +import gc import inspect -from collections.abc import Mapping, Sequence +from collections.abc import Iterator, Mapping, Sequence +from contextlib import contextmanager from functools import partial from pathlib import Path from typing import Any @@ -11,8 +13,6 @@ import torch import yaml -from neps.runtime import get_in_progress_trial, get_workers_neps_state - # TODO(eddiebergman): I feel like this function should throw an error if it can't # find anything to load, rather than returning None. In this case, we should provide @@ -35,6 +35,8 @@ def load_checkpoint( A dictionary containing the checkpoint values, or None if the checkpoint file does not exist hence no checkpointing was previously done. """ + from neps.runtime import get_in_progress_trial + if directory is None: trial = get_in_progress_trial() directory = trial.metadata.previous_trial_location @@ -75,6 +77,8 @@ def save_checkpoint( optimizer: The optimizer to save. checkpoint_name: The name of the checkpoint file. """ + from neps.runtime import get_in_progress_trial + if directory is None: in_progress_trial = get_in_progress_trial() directory = in_progress_trial.metadata.location @@ -113,6 +117,8 @@ def load_lightning_checkpoint( A tuple containing the checkpoint path (str) and the loaded checkpoint data (dict) or (None, None) if no checkpoint files are found in the directory. """ + from neps.runtime import get_in_progress_trial + if previous_pipeline_directory is None: trial = get_in_progress_trial() previous_pipeline_directory = trial.metadata.previous_trial_location @@ -156,11 +162,13 @@ def get_initial_directory(pipeline_directory: Path | str | None = None) -> Path: Returns: The initial directory. """ + from neps.runtime import get_in_progress_trial, get_workers_neps_state + neps_state = get_workers_neps_state() if pipeline_directory is not None: # TODO: Hard coded assumption config_id = Path(pipeline_directory).name.split("_", maxsplit=1)[-1] - trial = neps_state.get_trial_by_id(config_id) + trial = neps_state.unsafe_retry_get_trial_by_id(config_id) else: trial = get_in_progress_trial() @@ -169,7 +177,7 @@ def get_initial_directory(pipeline_directory: Path | str | None = None) -> Path: # Recursively find the initial directory while (prev_trial_id := trial.metadata.previous_trial_id) is not None: - trial = neps_state.get_trial_by_id(prev_trial_id) + trial = neps_state.unsafe_retry_get_trial_by_id(prev_trial_id) initial_dir = trial.metadata.location @@ -333,3 +341,17 @@ def instance_from_map( # noqa: C901 raise TypeError(f"{e} when calling {instance} with {args_dict}") from e return instance + + +@contextmanager +def gc_disabled() -> Iterator[None]: + """Context manager to disable garbage collection for a block. + + We specifically put this around file I/O operations to minimize the time + spend garbage collecting while having the file handle open. + """ + gc.disable() + try: + yield + finally: + gc.enable() diff --git a/neps/utils/files.py b/neps/utils/files.py index 95eb4eee7..b49c53011 100644 --- a/neps/utils/files.py +++ b/neps/utils/files.py @@ -3,13 +3,39 @@ from __future__ import annotations import dataclasses -from collections.abc import Iterable, Mapping +import io +import os +from collections.abc import Iterable, Iterator, Mapping +from contextlib import contextmanager from enum import Enum from pathlib import Path -from typing import Any +from typing import IO, Any, Literal import yaml +try: + from yaml import ( + CDumper as YamlDumper, # type: ignore + CSafeLoader as SafeLoader, # type: ignore + ) +except ImportError: + from yaml import SafeLoader, YamlDumper # type: ignore + + +@contextmanager +def atomic_write(file_path: Path | str, *args: Any, **kwargs: Any) -> Iterator[IO]: + """Write to a file atomically. + + This means that the file will be flushed to disk and explicitly ask the operating + systems to sync the contents to disk. This ensures that other processes that read + from this file should see the contents immediately. + """ + with open(file_path, *args, **kwargs) as file_stream: # noqa: PTH123 + yield file_stream + file_stream.flush() + os.fsync(file_stream.fileno()) + file_stream.close() + def serializable_format(data: Any) -> Any: # noqa: PLR0911 """Format data to be serializable.""" @@ -41,24 +67,53 @@ def serializable_format(data: Any) -> Any: # noqa: PLR0911 return data -def serialize(data: Any, path: Path | str, *, sort_keys: bool = True) -> None: +def serialize( + data: Any, + path: Path, + *, + check_serialized: bool = True, + file_format: Literal["json", "yaml"] = "yaml", + sort_keys: bool = True, +) -> None: """Serialize data to a yaml file.""" - data = serializable_format(data) - path = Path(path) - with path.open("w") as file_stream: + if check_serialized: + data = serializable_format(data) + + buf = io.StringIO() + if file_format == "yaml": try: - return yaml.safe_dump(data, file_stream, sort_keys=sort_keys) + yaml.dump(data, buf, YamlDumper, sort_keys=sort_keys) except yaml.representer.RepresenterError as e: raise TypeError( "Could not serialize to yaml! The object " f"{e.args[1]} of type {type(e.args[1])} is not." ) from e + elif file_format == "json": + import json + + json.dump(data, buf, sort_keys=sort_keys) + else: + raise ValueError(f"Unknown format: {file_format}") + + _str = buf.getvalue() + path.write_text(_str) -def deserialize(path: Path | str) -> dict[str, Any]: +def deserialize( + path: Path | str, + *, + file_format: Literal["json", "yaml"] = "yaml", +) -> dict[str, Any]: """Deserialize data from a yaml file.""" with Path(path).open("r") as file_stream: - data = yaml.full_load(file_stream) + if file_format == "json": + import json + + data = json.load(file_stream) + elif file_format == "yaml": + data = yaml.load(file_stream, SafeLoader) + else: + raise ValueError(f"Unknown format: {file_format}") if not isinstance(data, dict): raise TypeError( diff --git a/neps/utils/run_args.py b/neps/utils/run_args.py index 2fbd740ca..5a94ebc7e 100644 --- a/neps/utils/run_args.py +++ b/neps/utils/run_args.py @@ -531,6 +531,7 @@ def __init__(self, func_args: dict, yaml_args: Path | str | Default | None = Non self.cost_value_on_error = UNSET self.pre_load_hooks = UNSET self.searcher = UNSET + self.sample_batch_size = UNSET self.searcher_kwargs = UNSET if not isinstance(yaml_args, Default) and yaml_args is not None: diff --git a/pyproject.toml b/pyproject.toml index b05569635..978c5da47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ keywords = [ classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", - "Intended Audience :: Science/Research", + "Intended Audience :: Science/Research", "Natural Language :: English", "License :: OSI Approved :: Apache Software License", "Operating System :: POSIX :: Linux", @@ -214,7 +214,7 @@ ignore = [ "S101", # Use of assert detected. "W292", # No newline at end of file "PLC1901", # "" can be simplified to be falsey - "TCH003", # Move stdlib import into TYPE_CHECKING + "TC003", # Move stdlib import into TYPE_CHECKING "B010", # Do not use `setattr` "PD901", # Use a better name than 'df' "PD011", # Use .to_numpy() instead of .values (triggers on report.values) diff --git a/tests/joint_config_space.py b/tests/joint_config_space.py index 999e58053..1e5b2b62e 100644 --- a/tests/joint_config_space.py +++ b/tests/joint_config_space.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import ConfigSpace as CS from jahs_bench.lib.core.constants import Activations @@ -16,37 +18,37 @@ "Op1", choices=list(range(5)), default_value=0, - meta=dict(help="The operation on the first edge of the cell."), + meta={"help": "The operation on the first edge of the cell."}, ), CS.CategoricalHyperparameter( "Op2", choices=list(range(5)), default_value=0, - meta=dict(help="The operation on the second edge of the cell."), + meta={"help": "The operation on the second edge of the cell."}, ), CS.CategoricalHyperparameter( "Op3", choices=list(range(5)), default_value=0, - meta=dict(help="The operation on the third edge of the cell."), + meta={"help": "The operation on the third edge of the cell."}, ), CS.CategoricalHyperparameter( "Op4", choices=list(range(5)), default_value=0, - meta=dict(help="The operation on the fourth edge of the cell."), + meta={"help": "The operation on the fourth edge of the cell."}, ), CS.CategoricalHyperparameter( "Op5", choices=list(range(5)), default_value=0, - meta=dict(help="The operation on the fifth edge of the cell."), + meta={"help": "The operation on the fifth edge of the cell."}, ), CS.CategoricalHyperparameter( "Op6", choices=list(range(5)), default_value=0, - meta=dict(help="The operation on the sixth edge of the cell."), + meta={"help": "The operation on the sixth edge of the cell."}, ), # CS.OrdinalHyperparameter("Resolution", sequence=[0.25, 0.5, 1.], default_value=1., # meta=dict(help="The sample resolution of the input images w.r.t. one side of the " @@ -57,21 +59,21 @@ "TrivialAugment", choices=[True, False], default_value=False, - meta=dict( - help="Controls whether or not TrivialAugment is used for pre-processing " + meta={ + "help": "Controls whether or not TrivialAugment is used for pre-processing " "data. If False (default), a set of manually chosen transforms is " "applied during pre-processing. If True, these are skipped in favor of " "applying random transforms selected by TrivialAugment." - ), + }, ), CS.CategoricalHyperparameter( "Activation", choices=list(Activations.__members__.keys()), default_value="ReLU", - meta=dict( - help="Which activation function is to be used for the network. " + meta={ + "help": "Which activation function is to be used for the network. " "Default is ReLU." - ), + }, ), ] ) @@ -81,11 +83,11 @@ "Optimizer", choices=["SGD"], default_value="SGD", - meta=dict( - help="Which optimizer to use for training this model. " + meta={ + "help": "Which optimizer to use for training this model. " "This is just a placeholder for now, to be used " "properly in future versions." - ), + }, ) lr = CS.UniformFloatHyperparameter( "LearningRate", @@ -93,11 +95,11 @@ upper=1e0, default_value=1e-1, log=True, - meta=dict( - help="The learning rate for the optimizer used during model training. In the " + meta={ + "help": "The learning rate for the optimizer used during model training. In the " "case of adaptive learning rate optimizers such as Adam, this is the " "initial learning rate." - ), + }, ) weight_decay = CS.UniformFloatHyperparameter( "WeightDecay", @@ -105,7 +107,7 @@ upper=1e-2, default_value=5e-4, log=True, - meta=dict(help="Weight decay to be used by the " "optimizer during model training."), + meta={"help": "Weight decay to be used by the " "optimizer during model training."}, ) joint_config_space.add_hyperparameters([optimizers, lr, weight_decay]) diff --git a/tests/regression_objectives.py b/tests/regression_objectives.py index eb261674f..97adb6874 100644 --- a/tests/regression_objectives.py +++ b/tests/regression_objectives.py @@ -1,6 +1,9 @@ +from __future__ import annotations + import warnings +from collections.abc import Callable from pathlib import Path -from typing import Any, Callable, Literal +from typing import Any, Literal import numpy as np @@ -9,10 +12,9 @@ class RegressionObjectiveBase: - """ - Base class for creating new synthetic or real objectives for the regression tests + """Base class for creating new synthetic or real objectives for the regression tests Regression runner uses properties defined here, - each property should be appropriately defined by the subclasses + each property should be appropriately defined by the subclasses. """ def __init__(self, optimizer: str, task: str): @@ -30,8 +32,7 @@ def pipeline_space(self) -> SearchSpace | dict[str, Any]: f" the subclass {type(self)} must implement " f"a pipeline_space attribute" ) - else: - return self._pipeline_space + return self._pipeline_space @pipeline_space.setter def pipeline_space(self, value): @@ -39,15 +40,14 @@ def pipeline_space(self, value): @property def run_pipeline(self) -> Callable: - warnings.warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + warnings.warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning, stacklevel=2) if self._run_pipeline is None: raise NotImplementedError( f"run_pipeline can not be None, " f"the subclass {type(self)} must " f"implement a run_pipeline Callable" ) - else: - return self._run_pipeline + return self._run_pipeline @property def evaluate_pipeline(self) -> Callable: @@ -57,12 +57,11 @@ def evaluate_pipeline(self) -> Callable: f"the subclass {type(self)} must " f"implement a evaluate_pipeline Callable" ) - else: - return self._run_pipeline + return self._run_pipeline @run_pipeline.setter def run_pipeline(self, value): - warnings.warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + warnings.warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning, stacklevel=2) self._run_pipeline = value @evaluate_pipeline.setter @@ -75,9 +74,7 @@ def __call__(self, *args, **kwargs) -> dict[str, Any]: class JAHSObjective(RegressionObjectiveBase): def evaluation_func(self): - """ - If the optimizer is cost aware, return the evaluation function with cost - """ + """If the optimizer is cost aware, return the evaluation function with cost.""" import jahs_bench self.benchmark = jahs_bench.Benchmark( @@ -103,8 +100,7 @@ def objective_to_minimize_evaluation(**joint_configuration): if "cost" in self.optimizer: return cost_evaluation - else: - return objective_to_minimize_evaluation + return objective_to_minimize_evaluation def __init__( self, @@ -115,8 +111,7 @@ def __init__( save_dir: str | Path = "jahs_bench_data", **kwargs, ): - """ - Download benchmark, initialize Pipeline space and evaluation function + """Download benchmark, initialize Pipeline space and evaluation function. Args: optimizer: The optimizer that will be run, this is used to determine the @@ -140,7 +135,7 @@ def __init__( self.run_pipeline = self.evaluation_func() self.surrogate_model = "gp" if self.optimizer != "random_search" else None - self.surrogate_model_args = kwargs.get("surrogate_model_args", None) + self.surrogate_model_args = kwargs.get("surrogate_model_args") class HartmannObjective(RegressionObjectiveBase): @@ -149,15 +144,12 @@ class HartmannObjective(RegressionObjectiveBase): def evaluation_fn(self) -> Callable: def hartmann3(**z_nX): - if self.has_fidelity: - z = z_nX.get("z") - else: - z = self.z_max + z = z_nX.get("z") if self.has_fidelity else self.z_max X_0 = z_nX.get("X_0") X_1 = z_nX.get("X_1") X_2 = z_nX.get("X_2") - Xs = tuple((X_0, X_1, X_2)) + Xs = (X_0, X_1, X_2) log_z = np.log(z) log_lb, log_ub = np.log(self.z_min), np.log(self.z_max) @@ -198,10 +190,7 @@ def hartmann3(**z_nX): return result def hartmann6(**z_nX): - if self.has_fidelity: - z = z_nX.get("z") - else: - z = self.z_max + z = z_nX.get("z") if self.has_fidelity else self.z_max X_0 = z_nX.get("X_0") X_1 = z_nX.get("X_1") @@ -209,7 +198,7 @@ def hartmann6(**z_nX): X_3 = z_nX.get("X_3") X_4 = z_nX.get("X_4") X_5 = z_nX.get("X_5") - Xs = tuple((X_0, X_1, X_2, X_3, X_4, X_5)) + Xs = (X_0, X_1, X_2, X_3, X_4, X_5) # Change by Carl - z now comes in normalized log_z = np.log(z) @@ -256,12 +245,8 @@ def hartmann6(**z_nX): return result - if self.dim == 3: - hartmann_fn = hartmann3 - else: - hartmann_fn = hartmann6 + return hartmann3 if self.dim == 3 else hartmann6 - return hartmann_fn def __init__( self, @@ -272,8 +257,7 @@ def __init__( seed: int = 1337, **kwargs, ): - """ - Initialize Pipeline space and evaluation function + """Initialize Pipeline space and evaluation function. Args: optimizer: The optimizer that will be run, this is used to determine the @@ -306,6 +290,6 @@ def __init__( self.random_state = np.random.default_rng(seed) self.surrogate_model = "gp" if self.optimizer != "random_search" else None - self.surrogate_model_args = kwargs.get("surrogate_model_args", None) + self.surrogate_model_args = kwargs.get("surrogate_model_args") self.run_pipeline = self.evaluation_fn() diff --git a/tests/regression_runner.py b/tests/regression_runner.py index 25d6a11c5..7cba1c65c 100644 --- a/tests/regression_runner.py +++ b/tests/regression_runner.py @@ -1,8 +1,10 @@ # mypy: disable-error-code = union-attr +from __future__ import annotations + import json import logging +from collections.abc import Callable from pathlib import Path -from typing import Callable import numpy as np from scipy.stats import kstest @@ -26,8 +28,7 @@ def incumbent_at(root_directory: str | Path, step: int): - """ - Return the incumbent of the run at step n + """Return the incumbent of the run at step n. Args: root_directory: root directory of the optimization run @@ -39,12 +40,11 @@ def incumbent_at(root_directory: str | Path, step: int): for line in log_file.read_text(encoding="utf-8").splitlines() if "Loss: " in line ] - incumbent_at_n = min(losses[:step]) - return incumbent_at_n + return min(losses[:step]) class RegressionRunner: - """This class runs the optimization algorithms and stores the results in separate files""" + """This class runs the optimization algorithms and stores the results in separate files.""" def __init__( self, @@ -55,8 +55,7 @@ def __init__( experiment_name: str = "", **kwargs, ): - """ - Download benchmark, initialize Pipeline space, evaluation function and set paths, + """Download benchmark, initialize Pipeline space, evaluation function and set paths,. Args: objective: callable that takes a configuration as input and evaluates it @@ -71,21 +70,21 @@ def __init__( self.optimizer = self.objective.optimizer self.pipeline_space = self.objective.pipeline_space else: - self.task = kwargs.get("task", None) + self.task = kwargs.get("task") if self.task is None: raise AttributeError( f"self.task can not be {self.task}, " f"please provide a task argument" ) - self.optimizer = kwargs.get("optimizer", None) + self.optimizer = kwargs.get("optimizer") if self.optimizer is None: raise AttributeError( f"self.optimizer can not be {self.optimizer}, " f"please provide an optimizer argument" ) - self.pipeline_space = kwargs.get("pipeline_space", None) + self.pipeline_space = kwargs.get("pipeline_space") if self.pipeline_space is None: raise AttributeError( f"self.pipeline_space can not be {self.pipeline_space}, " @@ -139,14 +138,10 @@ def neps_run(self, working_directory: Path): max_evaluations_total=self.max_evaluations, ) - best_error = incumbent_at(working_directory, self.max_evaluations) - return best_error + return incumbent_at(working_directory, self.max_evaluations) def run_regression(self, save=False): - """ - Run iterations number of neps runs - """ - + """Run iterations number of neps runs.""" for i in range(self.iterations): working_directory = Path(self.root_directory, "results/test_run_" + str(i)) @@ -160,15 +155,13 @@ def run_regression(self, save=False): return np.array(self.final_losses) def read_results(self): - """ - Read the results of the last run. + """Read the results of the last run. Either returns results of the most recent run, or - return the values from LOSS_FILE + return the values from LOSS_FILE. """ - if self.final_losses: return np.array(self.final_losses) - elif self.final_losses_path.exists(): + if self.final_losses_path.exists(): # Read from final_losses_path for each regression run self.final_losses = [ float(objective_to_minimize) @@ -199,13 +192,11 @@ def read_results(self): return np.array(self.final_losses) def test(self): - """ - Target run for the regression test, keep all the parameters same. + """Target run for the regression test, keep all the parameters same. Args: max_evaluations: Number of evaluations after which to terminate optimization. """ - # Sample losses of self.sample_size runs samples = [] for i in range(self.sample_size): @@ -248,12 +239,11 @@ def median_threshold( with json_file.open(mode="r", encoding="utf-8") as f: losses_dict = json.load(f) else: - losses_dict = dict() + losses_dict = {} - print(f"Optimizers the results are already recorded for: {losses_dict.keys()}") for optimizer in OPTIMIZERS: if optimizer in losses_dict: - print(f"For {optimizer} recorded tasks are: {losses_dict[optimizer].keys()}") + pass for task in TASKS: if ( isinstance(losses_dict.get(optimizer, None), dict) @@ -270,10 +260,6 @@ def median_threshold( runner.run_regression(save=True) best_results = runner.read_results().tolist() minv, maxv = min(best_results), max(best_results) - print( - f"For optimizer {optimizer} on {task}:\n " - f"\tMin of best results: {minv}\n\tMax of best results: {maxv}" - ) if isinstance(losses_dict.get(optimizer, None), dict) and isinstance( losses_dict[optimizer].get(task, None), list ): diff --git a/tests/settings.py b/tests/settings.py index 0da765675..fc2be3fef 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from pathlib import Path ITERATIONS = 100 diff --git a/tests/test_config_encoder.py b/tests/test_config_encoder.py index f73419bca..c07500bbe 100644 --- a/tests/test_config_encoder.py +++ b/tests/test_config_encoder.py @@ -1,5 +1,8 @@ -import torch +from __future__ import annotations + import pytest +import torch + from neps.search_spaces.domain import Domain from neps.search_spaces.encoding import ( CategoricalToIntegerTransformer, diff --git a/tests/test_domain.py b/tests/test_domain.py index 0b9f2b976..ab3d3894d 100644 --- a/tests/test_domain.py +++ b/tests/test_domain.py @@ -1,7 +1,9 @@ -from pytest_cases import parametrize +from __future__ import annotations -import torch import pytest +import torch +from pytest_cases import parametrize + from neps.search_spaces.domain import Domain T = torch.tensor diff --git a/tests/test_examples.py b/tests/test_examples.py index 6942e8d7c..5510c084e 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -1,10 +1,11 @@ +from __future__ import annotations + import logging import os import runpy from pathlib import Path import pytest - from neps_examples import ci_examples, core_examples diff --git a/tests/test_neps_api/__init__.py b/tests/test_neps_api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_neps_api/test_api.py b/tests/test_neps_api/test_api.py index cebbdcc52..ae63b253d 100644 --- a/tests/test_neps_api/test_api.py +++ b/tests/test_neps_api/test_api.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging import os import runpy @@ -44,7 +46,7 @@ def test_default_examples(tmp_path: Path, example_script: Path) -> None: # Testing each folder with its corresponding expected dictionary for folder in tmp_path.iterdir(): - info_yaml_path = folder / ".optimizer_info" / "info.yaml" + info_yaml_path = folder / "optimizer_info.yaml" assert info_yaml_path.exists() loaded_data = yaml.safe_load(info_yaml_path.read_text()) diff --git a/tests/test_neps_api/testing_scripts/baseoptimizer_neps.py b/tests/test_neps_api/testing_scripts/baseoptimizer_neps.py index 75038a40b..9a4a4591d 100644 --- a/tests/test_neps_api/testing_scripts/baseoptimizer_neps.py +++ b/tests/test_neps_api/testing_scripts/baseoptimizer_neps.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging from warnings import warn @@ -6,28 +8,27 @@ from neps.optimizers.multi_fidelity.hyperband import Hyperband from neps.search_spaces.search_space import SearchSpace -pipeline_space_fidelity = dict( - val1=neps.Float(lower=-10, upper=10), - val2=neps.Integer(lower=1, upper=5, is_fidelity=True), -) +pipeline_space_fidelity = { + "val1": neps.Float(lower=-10, upper=10), + "val2": neps.Integer(lower=1, upper=5, is_fidelity=True), +} -pipeline_space = dict( - val1=neps.Float(lower=-10, upper=10), - val2=neps.Integer(lower=1, upper=5), -) +pipeline_space = { + "val1": neps.Float(lower=-10, upper=10), + "val2": neps.Integer(lower=1, upper=5), +} def run_pipeline(val1, val2): - warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning, stacklevel=2) return evaluate_pipeline(val1, val2) def evaluate_pipeline(val1, val2): - objective_to_minimize = val1 * val2 - return objective_to_minimize + return val1 * val2 def run_pipeline_fidelity(val1, val2): - warn("run_pipeline_fidelity is deprecated, use evaluate_pipeline_fidelity instead", DeprecationWarning) + warn("run_pipeline_fidelity is deprecated, use evaluate_pipeline_fidelity instead", DeprecationWarning, stacklevel=2) return evaluate_pipeline_fidelity(val1, val2) def evaluate_pipeline_fidelity(val1, val2): diff --git a/tests/test_neps_api/testing_scripts/default_neps.py b/tests/test_neps_api/testing_scripts/default_neps.py index c0c0905b1..815adcc9e 100644 --- a/tests/test_neps_api/testing_scripts/default_neps.py +++ b/tests/test_neps_api/testing_scripts/default_neps.py @@ -1,36 +1,37 @@ +from __future__ import annotations + import logging from warnings import warn import neps -pipeline_space_fidelity_priors = dict( - val1=neps.Float(lower=-10, upper=10, prior=1), - val2=neps.Integer(lower=1, upper=5, is_fidelity=True), -) +pipeline_space_fidelity_priors = { + "val1": neps.Float(lower=-10, upper=10, prior=1), + "val2": neps.Integer(lower=1, upper=5, is_fidelity=True), +} -pipeline_space_not_fidelity_priors = dict( - val1=neps.Float(lower=-10, upper=10, prior=1), - val2=neps.Integer(lower=1, upper=5, prior=1), -) +pipeline_space_not_fidelity_priors = { + "val1": neps.Float(lower=-10, upper=10, prior=1), + "val2": neps.Integer(lower=1, upper=5, prior=1), +} -pipeline_space_fidelity = dict( - val1=neps.Float(lower=-10, upper=10), - val2=neps.Integer(lower=1, upper=5, is_fidelity=True), -) +pipeline_space_fidelity = { + "val1": neps.Float(lower=-10, upper=10), + "val2": neps.Integer(lower=1, upper=5, is_fidelity=True), +} -pipeline_space_not_fidelity = dict( - val1=neps.Float(lower=-10, upper=10), - val2=neps.Integer(lower=1, upper=5), -) +pipeline_space_not_fidelity = { + "val1": neps.Float(lower=-10, upper=10), + "val2": neps.Integer(lower=1, upper=5), +} def run_pipeline(val1, val2): - warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning, stacklevel=2) return evaluate_pipeline(val1, val2) def evaluate_pipeline(val1, val2): - objective_to_minimize = val1 * val2 - return objective_to_minimize + return val1 * val2 logging.basicConfig(level=logging.INFO) diff --git a/tests/test_neps_api/testing_scripts/user_yaml_neps.py b/tests/test_neps_api/testing_scripts/user_yaml_neps.py index 60d294df8..2320a90df 100644 --- a/tests/test_neps_api/testing_scripts/user_yaml_neps.py +++ b/tests/test_neps_api/testing_scripts/user_yaml_neps.py @@ -1,30 +1,35 @@ +from __future__ import annotations + import logging -import os from pathlib import Path from warnings import warn + import neps -pipeline_space = dict( - val1=neps.Float(lower=-10, upper=10), - val2=neps.Integer(lower=1, upper=5), -) +pipeline_space = { + "val1": neps.Float(lower=-10, upper=10), + "val2": neps.Integer(lower=1, upper=5), +} def run_pipeline(val1, val2): - warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + warn( + "run_pipeline is deprecated, use evaluate_pipeline instead", + DeprecationWarning, + stacklevel=2, + ) return evaluate_pipeline(val1, val2) def evaluate_pipeline(val1, val2): - objective_to_minimize = val1 * val2 - return objective_to_minimize + return val1 * val2 logging.basicConfig(level=logging.INFO) # Testing using created yaml with api -script_directory = os.path.dirname(os.path.abspath(__file__)) -parent_directory = os.path.join(script_directory, os.pardir) +script_directory = Path(__file__).resolve().parent +parent_directory = script_directory.parent searcher_path = Path(parent_directory) / "testing_yaml" / "optimizer_test" neps.run( evaluate_pipeline=evaluate_pipeline, diff --git a/tests/test_regression.py b/tests/test_regression.py index 6223d2fdd..4f5ea96fe 100644 --- a/tests/test_regression.py +++ b/tests/test_regression.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging import os diff --git a/tests/test_runtime/test_default_report_values.py b/tests/test_runtime/test_default_report_values.py index 24ee36597..74c56f466 100644 --- a/tests/test_runtime/test_default_report_values.py +++ b/tests/test_runtime/test_default_report_values.py @@ -1,23 +1,28 @@ +from __future__ import annotations + from pathlib import Path + from pytest_cases import fixture from neps.optimizers.random_search.optimizer import RandomSearch from neps.runtime import DefaultWorker +from neps.search_spaces import Float from neps.search_spaces.search_space import SearchSpace -from neps.state.filebased import create_or_load_filebased_neps_state from neps.state.neps_state import NePSState from neps.state.optimizer import OptimizationState, OptimizerInfo +from neps.state.seed_snapshot import SeedSnapshot from neps.state.settings import DefaultReportValues, OnErrorPossibilities, WorkerSettings -from neps.search_spaces import Float from neps.state.trial import Trial @fixture -def neps_state(tmp_path: Path) -> NePSState[Path]: - return create_or_load_filebased_neps_state( - directory=tmp_path / "neps_state", +def neps_state(tmp_path: Path) -> NePSState: + return NePSState.create_or_load( + path=tmp_path / "neps_state", optimizer_info=OptimizerInfo(info={"nothing": "here"}), - optimizer_state=OptimizationState(budget=None, shared_state={}), + optimizer_state=OptimizationState( + budget=None, seed_snapshot=SeedSnapshot.new_capture(), shared_state={} + ), ) @@ -40,6 +45,7 @@ def test_default_values_on_error( max_wallclock_time_for_worker_seconds=None, max_evaluation_time_for_worker_seconds=None, max_cost_for_worker=None, + batch_size=None, ) def eval_function(*args, **kwargs) -> float: @@ -54,18 +60,19 @@ def eval_function(*args, **kwargs) -> float: ) worker.run() - trials = neps_state.get_all_trials() + trials = neps_state.lock_and_read_trials() n_crashed = sum( - trial.state == Trial.State.CRASHED is not None for trial in trials.values() + trial.metadata.state == Trial.State.CRASHED is not None + for trial in trials.values() ) assert len(trials) == 1 assert n_crashed == 1 - assert neps_state.get_next_pending_trial() is None - assert len(neps_state.get_errors()) == 1 + assert neps_state.lock_and_get_next_pending_trial() is None + assert len(neps_state.lock_and_get_errors()) == 1 trial = trials.popitem()[1] - assert trial.state == Trial.State.CRASHED + assert trial.metadata.state == Trial.State.CRASHED assert trial.report is not None assert trial.report.objective_to_minimize == 2.4 assert trial.report.cost == 2.4 @@ -90,6 +97,7 @@ def test_default_values_on_not_specified( max_wallclock_time_for_worker_seconds=None, max_evaluation_time_for_worker_seconds=None, max_cost_for_worker=None, + batch_size=None, ) def eval_function(*args, **kwargs) -> float: @@ -104,18 +112,19 @@ def eval_function(*args, **kwargs) -> float: ) worker.run() - trials = neps_state.get_all_trials() + trials = neps_state.lock_and_read_trials() n_sucess = sum( - trial.state == Trial.State.SUCCESS is not None for trial in trials.values() + trial.metadata.state == Trial.State.SUCCESS is not None + for trial in trials.values() ) assert len(trials) == 1 assert n_sucess == 1 - assert neps_state.get_next_pending_trial() is None - assert len(neps_state.get_errors()) == 0 + assert neps_state.lock_and_get_next_pending_trial() is None + assert len(neps_state.lock_and_get_errors()) == 0 trial = trials.popitem()[1] - assert trial.state == Trial.State.SUCCESS + assert trial.metadata.state == Trial.State.SUCCESS assert trial.report is not None assert trial.report.cost == 2.4 assert trial.report.learning_curve == [2.4, 2.5] @@ -127,7 +136,9 @@ def test_default_value_objective_to_minimize_curve_take_objective_to_minimize_va optimizer = RandomSearch(pipeline_space=SearchSpace(a=Float(0, 1))) settings = WorkerSettings( on_error=OnErrorPossibilities.IGNORE, - default_report_values=DefaultReportValues(learning_curve_if_not_provided="objective_to_minimize"), + default_report_values=DefaultReportValues( + learning_curve_if_not_provided="objective_to_minimize" + ), max_evaluations_total=None, include_in_progress_evaluations_towards_maximum=False, max_cost_total=None, @@ -136,6 +147,7 @@ def test_default_value_objective_to_minimize_curve_take_objective_to_minimize_va max_wallclock_time_for_worker_seconds=None, max_evaluation_time_for_worker_seconds=None, max_cost_for_worker=None, + batch_size=None, ) LOSS = 1.0 @@ -152,17 +164,18 @@ def eval_function(*args, **kwargs) -> float: ) worker.run() - trials = neps_state.get_all_trials() + trials = neps_state.lock_and_read_trials() n_sucess = sum( - trial.state == Trial.State.SUCCESS is not None for trial in trials.values() + trial.metadata.state == Trial.State.SUCCESS is not None + for trial in trials.values() ) assert len(trials) == 1 assert n_sucess == 1 - assert neps_state.get_next_pending_trial() is None - assert len(neps_state.get_errors()) == 0 + assert neps_state.lock_and_get_next_pending_trial() is None + assert len(neps_state.lock_and_get_errors()) == 0 trial = trials.popitem()[1] - assert trial.state == Trial.State.SUCCESS + assert trial.metadata.state == Trial.State.SUCCESS assert trial.report is not None assert trial.report.learning_curve == [LOSS] diff --git a/tests/test_runtime/test_error_handling_strategies.py b/tests/test_runtime/test_error_handling_strategies.py index 05cf762a3..7650cbc2a 100644 --- a/tests/test_runtime/test_error_handling_strategies.py +++ b/tests/test_runtime/test_error_handling_strategies.py @@ -1,28 +1,34 @@ -from neps.exceptions import WorkerRaiseError -import pytest +from __future__ import annotations + from dataclasses import dataclass -from pandas.core.common import contextlib from pathlib import Path + +import pytest +from pandas.core.common import contextlib from pytest_cases import fixture, parametrize +from neps.exceptions import WorkerRaiseError from neps.optimizers.random_search.optimizer import RandomSearch from neps.runtime import DefaultWorker +from neps.search_spaces import Float from neps.search_spaces.search_space import SearchSpace -from neps.state.err_dump import SerializedError -from neps.state.filebased import create_or_load_filebased_neps_state from neps.state.neps_state import NePSState from neps.state.optimizer import OptimizationState, OptimizerInfo +from neps.state.seed_snapshot import SeedSnapshot from neps.state.settings import DefaultReportValues, OnErrorPossibilities, WorkerSettings -from neps.search_spaces import Float from neps.state.trial import Trial @fixture -def neps_state(tmp_path: Path) -> NePSState[Path]: - return create_or_load_filebased_neps_state( - directory=tmp_path / "neps_state", +def neps_state(tmp_path: Path) -> NePSState: + return NePSState.create_or_load( + path=tmp_path / "neps_state", optimizer_info=OptimizerInfo(info={"nothing": "here"}), - optimizer_state=OptimizationState(budget=None, shared_state={}), + optimizer_state=OptimizationState( + budget=None, + seed_snapshot=SeedSnapshot.new_capture(), + shared_state=None, + ), ) @@ -46,6 +52,7 @@ def test_worker_raises_when_error_in_self( max_wallclock_time_for_worker_seconds=None, max_evaluation_time_for_worker_seconds=None, max_cost_for_worker=None, + batch_size=None, ) def eval_function(*args, **kwargs) -> float: @@ -61,15 +68,16 @@ def eval_function(*args, **kwargs) -> float: with pytest.raises(WorkerRaiseError): worker.run() - trials = neps_state.get_all_trials() + trials = neps_state.lock_and_read_trials() n_crashed = sum( - trial.state == Trial.State.CRASHED is not None for trial in trials.values() + trial.metadata.state == Trial.State.CRASHED is not None + for trial in trials.values() ) assert len(trials) == 1 assert n_crashed == 1 - assert neps_state.get_next_pending_trial() is None - assert len(neps_state.get_errors()) == 1 + assert neps_state.lock_and_get_next_pending_trial() is None + assert len(neps_state.lock_and_get_errors()) == 1 def test_worker_raises_when_error_in_other_worker(neps_state: NePSState) -> None: @@ -85,6 +93,7 @@ def test_worker_raises_when_error_in_other_worker(neps_state: NePSState) -> None max_wallclock_time_for_worker_seconds=None, max_evaluation_time_for_worker_seconds=None, max_cost_for_worker=None, + batch_size=None, ) def evaler(*args, **kwargs) -> float: @@ -114,15 +123,16 @@ def evaler(*args, **kwargs) -> float: with pytest.raises(WorkerRaiseError): worker2.run() - trials = neps_state.get_all_trials() + trials = neps_state.lock_and_read_trials() n_crashed = sum( - trial.state == Trial.State.CRASHED is not None for trial in trials.values() + trial.metadata.state == Trial.State.CRASHED is not None + for trial in trials.values() ) assert len(trials) == 1 assert n_crashed == 1 - assert neps_state.get_next_pending_trial() is None - assert len(neps_state.get_errors()) == 1 + assert neps_state.lock_and_get_next_pending_trial() is None + assert len(neps_state.lock_and_get_errors()) == 1 @pytest.mark.parametrize( @@ -145,6 +155,7 @@ def test_worker_does_not_raise_when_error_in_other_worker( max_wallclock_time_for_worker_seconds=None, max_evaluation_time_for_worker_seconds=None, max_cost_for_worker=None, + batch_size=None, ) @dataclass @@ -184,16 +195,18 @@ def __call__(self, *args, **kwargs) -> float: worker2.run() assert worker2.worker_cumulative_eval_count == 1 - trials = neps_state.get_all_trials() + trials = neps_state.lock_and_read_trials() n_success = sum( - trial.state == Trial.State.SUCCESS is not None for trial in trials.values() + trial.metadata.state == Trial.State.SUCCESS is not None + for trial in trials.values() ) n_crashed = sum( - trial.state == Trial.State.CRASHED is not None for trial in trials.values() + trial.metadata.state == Trial.State.CRASHED is not None + for trial in trials.values() ) assert n_success == 1 assert n_crashed == 1 assert len(trials) == 2 - assert neps_state.get_next_pending_trial() is None - assert len(neps_state.get_errors()) == 1 + assert neps_state.lock_and_get_next_pending_trial() is None + assert len(neps_state.lock_and_get_errors()) == 1 diff --git a/tests/test_runtime/test_stopping_criterion.py b/tests/test_runtime/test_stopping_criterion.py index cdc9313b9..e380a9fc0 100644 --- a/tests/test_runtime/test_stopping_criterion.py +++ b/tests/test_runtime/test_stopping_criterion.py @@ -1,24 +1,31 @@ +from __future__ import annotations + import time from pathlib import Path + from pytest_cases import fixture from neps.optimizers.random_search.optimizer import RandomSearch from neps.runtime import DefaultWorker +from neps.search_spaces import Float from neps.search_spaces.search_space import SearchSpace -from neps.state.filebased import create_or_load_filebased_neps_state from neps.state.neps_state import NePSState from neps.state.optimizer import OptimizationState, OptimizerInfo +from neps.state.seed_snapshot import SeedSnapshot from neps.state.settings import DefaultReportValues, OnErrorPossibilities, WorkerSettings -from neps.search_spaces import Float from neps.state.trial import Trial @fixture -def neps_state(tmp_path: Path) -> NePSState[Path]: - return create_or_load_filebased_neps_state( - directory=tmp_path / "neps_state", +def neps_state(tmp_path: Path) -> NePSState: + return NePSState.create_or_load( + path=tmp_path / "neps_state", optimizer_info=OptimizerInfo(info={"nothing": "here"}), - optimizer_state=OptimizationState(budget=None, shared_state={}), + optimizer_state=OptimizationState( + budget=None, + seed_snapshot=SeedSnapshot.new_capture(), + shared_state=None, + ), ) @@ -37,6 +44,7 @@ def test_max_evaluations_total_stopping_criterion( max_wallclock_time_for_worker_seconds=None, max_evaluation_time_for_worker_seconds=None, max_cost_for_worker=None, + batch_size=None, ) def eval_function(*args, **kwargs) -> float: @@ -52,12 +60,12 @@ def eval_function(*args, **kwargs) -> float: worker.run() assert worker.worker_cumulative_eval_count == 3 - assert neps_state.get_next_pending_trial() is None - assert len(neps_state.get_errors()) == 0 + assert neps_state.lock_and_get_next_pending_trial() is None + assert len(neps_state.lock_and_get_errors()) == 0 - trials = neps_state.get_all_trials() + trials = neps_state.lock_and_read_trials() for _, trial in trials.items(): - assert trial.state == Trial.State.SUCCESS + assert trial.metadata.state == Trial.State.SUCCESS assert trial.report is not None assert trial.report.objective_to_minimize == 1.0 @@ -71,8 +79,8 @@ def eval_function(*args, **kwargs) -> float: ) new_worker.run() assert new_worker.worker_cumulative_eval_count == 0 - assert neps_state.get_next_pending_trial() is None - assert len(neps_state.get_errors()) == 0 + assert neps_state.lock_and_get_next_pending_trial() is None + assert len(neps_state.lock_and_get_errors()) == 0 def test_worker_evaluations_total_stopping_criterion( @@ -90,6 +98,7 @@ def test_worker_evaluations_total_stopping_criterion( max_wallclock_time_for_worker_seconds=None, max_evaluation_time_for_worker_seconds=None, max_cost_for_worker=None, + batch_size=None, ) def eval_function(*args, **kwargs) -> float: @@ -105,13 +114,13 @@ def eval_function(*args, **kwargs) -> float: worker.run() assert worker.worker_cumulative_eval_count == 2 - assert neps_state.get_next_pending_trial() is None - assert len(neps_state.get_errors()) == 0 + assert neps_state.lock_and_get_next_pending_trial() is None + assert len(neps_state.lock_and_get_errors()) == 0 - trials = neps_state.get_all_trials() + trials = neps_state.lock_and_read_trials() assert len(trials) == 2 for _, trial in trials.items(): - assert trial.state == Trial.State.SUCCESS + assert trial.metadata.state == Trial.State.SUCCESS assert trial.report is not None assert trial.report.objective_to_minimize == 1.0 @@ -126,13 +135,13 @@ def eval_function(*args, **kwargs) -> float: new_worker.run() assert worker.worker_cumulative_eval_count == 2 - assert neps_state.get_next_pending_trial() is None - assert len(neps_state.get_errors()) == 0 + assert neps_state.lock_and_get_next_pending_trial() is None + assert len(neps_state.lock_and_get_errors()) == 0 - trials = neps_state.get_all_trials() + trials = neps_state.lock_and_read_trials() assert len(trials) == 4 # Now we should have 4 of them for _, trial in trials.items(): - assert trial.state == Trial.State.SUCCESS + assert trial.metadata.state == Trial.State.SUCCESS assert trial.report is not None assert trial.report.objective_to_minimize == 1.0 @@ -152,10 +161,11 @@ def test_include_in_progress_evaluations_towards_maximum_with_work_eval_count( max_wallclock_time_for_worker_seconds=None, max_evaluation_time_for_worker_seconds=None, max_cost_for_worker=None, + batch_size=None, ) # We put in one trial as being inprogress - pending_trial = neps_state.sample_trial(optimizer, worker_id="dummy") + pending_trial = neps_state.lock_and_sample_trial(optimizer, worker_id="dummy") pending_trial.set_evaluating(time_started=0.0, worker_id="dummy") neps_state.put_updated_trial(pending_trial) @@ -173,22 +183,22 @@ def eval_function(*args, **kwargs) -> float: assert worker.worker_cumulative_eval_count == 1 assert ( - neps_state.get_next_pending_trial() is None + neps_state.lock_and_get_next_pending_trial() is None ) # should have no pending trials to be picked up - assert len(neps_state.get_errors()) == 0 + assert len(neps_state.lock_and_get_errors()) == 0 - trials = neps_state.get_all_trials() + trials = neps_state.lock_and_read_trials() assert len(trials) == 2 the_pending_trial = trials[pending_trial.id] assert the_pending_trial == pending_trial - assert the_pending_trial.state == Trial.State.EVALUATING + assert the_pending_trial.metadata.state == Trial.State.EVALUATING assert the_pending_trial.report is None the_completed_trial_id = next(iter(trials.keys() - {pending_trial.id})) the_completed_trial = trials[the_completed_trial_id] - assert the_completed_trial.state == Trial.State.SUCCESS + assert the_completed_trial.metadata.state == Trial.State.SUCCESS assert the_completed_trial.report is not None assert the_completed_trial.report.objective_to_minimize == 1.0 @@ -208,6 +218,7 @@ def test_max_cost_total( max_wallclock_time_for_worker_seconds=None, max_evaluation_time_for_worker_seconds=None, max_cost_for_worker=None, + batch_size=None, ) def eval_function(*args, **kwargs) -> dict: @@ -225,11 +236,11 @@ def eval_function(*args, **kwargs) -> dict: assert worker.worker_cumulative_eval_count == 2 assert worker.worker_cumulative_eval_cost == 2.0 assert ( - neps_state.get_next_pending_trial() is None + neps_state.lock_and_get_next_pending_trial() is None ) # should have no pending trials to be picked up - assert len(neps_state.get_errors()) == 0 + assert len(neps_state.lock_and_get_errors()) == 0 - trials = neps_state.get_all_trials() + trials = neps_state.lock_and_read_trials() assert len(trials) == 2 # New worker should now not run anything as the total cost has been reached. @@ -259,6 +270,7 @@ def test_worker_cost_total( max_wallclock_time_for_worker_seconds=None, max_evaluation_time_for_worker_seconds=None, max_cost_for_worker=2, # <- Highlight, only 2 maximum evaluations allowed + batch_size=None, ) def eval_function(*args, **kwargs) -> dict: @@ -276,11 +288,11 @@ def eval_function(*args, **kwargs) -> dict: assert worker.worker_cumulative_eval_count == 2 assert worker.worker_cumulative_eval_cost == 2.0 assert ( - neps_state.get_next_pending_trial() is None + neps_state.lock_and_get_next_pending_trial() is None ) # should have no pending trials to be picked up - assert len(neps_state.get_errors()) == 0 + assert len(neps_state.lock_and_get_errors()) == 0 - trials = neps_state.get_all_trials() + trials = neps_state.lock_and_read_trials() assert len(trials) == 2 # New worker should also run 2 more trials @@ -295,11 +307,11 @@ def eval_function(*args, **kwargs) -> dict: assert new_worker.worker_cumulative_eval_count == 2 assert new_worker.worker_cumulative_eval_cost == 2.0 assert ( - neps_state.get_next_pending_trial() is None + neps_state.lock_and_get_next_pending_trial() is None ) # should have no pending trials to be picked up - assert len(neps_state.get_errors()) == 0 + assert len(neps_state.lock_and_get_errors()) == 0 - trials = neps_state.get_all_trials() + trials = neps_state.lock_and_read_trials() assert len(trials) == 4 # 2 more trials were ran @@ -318,6 +330,7 @@ def test_worker_wallclock_time( max_wallclock_time_for_worker_seconds=1, # <- highlight, 1 second max_evaluation_time_for_worker_seconds=None, max_cost_for_worker=None, + batch_size=None, ) def eval_function(*args, **kwargs) -> float: @@ -336,10 +349,10 @@ def eval_function(*args, **kwargs) -> float: assert worker.worker_cumulative_eval_count > 0 assert worker.worker_cumulative_evaluation_time_seconds <= 2.0 assert ( - neps_state.get_next_pending_trial() is None + neps_state.lock_and_get_next_pending_trial() is None ) # should have no pending trials to be picked up - assert len(neps_state.get_errors()) == 0 - len_trials_on_first_worker = len(neps_state.get_all_trials()) + assert len(neps_state.lock_and_get_errors()) == 0 + len_trials_on_first_worker = len(neps_state.lock_and_read_trials()) # New worker should also run some trials more trials new_worker = DefaultWorker.new( @@ -354,10 +367,10 @@ def eval_function(*args, **kwargs) -> float: assert new_worker.worker_cumulative_eval_count > 0 assert new_worker.worker_cumulative_evaluation_time_seconds <= 2.0 assert ( - neps_state.get_next_pending_trial() is None + neps_state.lock_and_get_next_pending_trial() is None ) # should have no pending trials to be picked up - assert len(neps_state.get_errors()) == 0 - len_trials_on_second_worker = len(neps_state.get_all_trials()) + assert len(neps_state.lock_and_get_errors()) == 0 + len_trials_on_second_worker = len(neps_state.lock_and_read_trials()) assert len_trials_on_second_worker > len_trials_on_first_worker @@ -376,6 +389,7 @@ def test_max_worker_evaluation_time( max_wallclock_time_for_worker_seconds=None, max_evaluation_time_for_worker_seconds=0.5, max_cost_for_worker=None, + batch_size=None, ) def eval_function(*args, **kwargs) -> float: @@ -395,10 +409,10 @@ def eval_function(*args, **kwargs) -> float: assert worker.worker_cumulative_eval_count > 0 assert worker.worker_cumulative_evaluation_time_seconds <= 1.0 assert ( - neps_state.get_next_pending_trial() is None + neps_state.lock_and_get_next_pending_trial() is None ) # should have no pending trials to be picked up - assert len(neps_state.get_errors()) == 0 - len_trials_on_first_worker = len(neps_state.get_all_trials()) + assert len(neps_state.lock_and_get_errors()) == 0 + len_trials_on_first_worker = len(neps_state.lock_and_read_trials()) # New worker should also run some trials more trials new_worker = DefaultWorker.new( @@ -413,10 +427,10 @@ def eval_function(*args, **kwargs) -> float: assert new_worker.worker_cumulative_eval_count > 0 assert new_worker.worker_cumulative_evaluation_time_seconds <= 1.0 assert ( - neps_state.get_next_pending_trial() is None + neps_state.lock_and_get_next_pending_trial() is None ) # should have no pending trials to be picked up - assert len(neps_state.get_errors()) == 0 - len_trials_on_second_worker = len(neps_state.get_all_trials()) + assert len(neps_state.lock_and_get_errors()) == 0 + len_trials_on_second_worker = len(neps_state.lock_and_read_trials()) assert len_trials_on_second_worker > len_trials_on_first_worker @@ -435,6 +449,7 @@ def test_max_evaluation_time_global( max_wallclock_time_for_worker_seconds=None, max_evaluation_time_for_worker_seconds=None, max_cost_for_worker=None, + batch_size=None, ) def eval_function(*args, **kwargs) -> float: @@ -454,10 +469,10 @@ def eval_function(*args, **kwargs) -> float: assert worker.worker_cumulative_eval_count > 0 assert worker.worker_cumulative_evaluation_time_seconds <= 1.0 assert ( - neps_state.get_next_pending_trial() is None + neps_state.lock_and_get_next_pending_trial() is None ) # should have no pending trials to be picked up - assert len(neps_state.get_errors()) == 0 - len_trials_on_first_worker = len(neps_state.get_all_trials()) + assert len(neps_state.lock_and_get_errors()) == 0 + len_trials_on_first_worker = len(neps_state.lock_and_read_trials()) # New worker should also run some trials more trials new_worker = DefaultWorker.new( @@ -472,8 +487,8 @@ def eval_function(*args, **kwargs) -> float: assert new_worker.worker_cumulative_eval_count == 0 assert new_worker.worker_cumulative_evaluation_time_seconds == 0 assert ( - neps_state.get_next_pending_trial() is None + neps_state.lock_and_get_next_pending_trial() is None ) # should have no pending trials to be picked up - assert len(neps_state.get_errors()) == 0 - len_trials_on_second_worker = len(neps_state.get_all_trials()) + assert len(neps_state.lock_and_get_errors()) == 0 + len_trials_on_second_worker = len(neps_state.lock_and_read_trials()) assert len_trials_on_second_worker == len_trials_on_first_worker diff --git a/tests/test_samplers.py b/tests/test_samplers.py index 167346de3..8581153e3 100644 --- a/tests/test_samplers.py +++ b/tests/test_samplers.py @@ -1,9 +1,10 @@ -from pytest_cases import parametrize -from neps.sampling.samplers import Sampler, Sobol, WeightedSampler, BorderSampler -from neps.sampling.priors import Prior, UniformPrior +from __future__ import annotations import torch +from pytest_cases import parametrize +from neps.sampling.priors import Prior, UniformPrior +from neps.sampling.samplers import BorderSampler, Sampler, Sobol, WeightedSampler from neps.search_spaces.domain import Domain diff --git a/tests/test_search_space_functions.py b/tests/test_search_space_functions.py index 7327544fc..f7c09f9f8 100644 --- a/tests/test_search_space_functions.py +++ b/tests/test_search_space_functions.py @@ -1,7 +1,10 @@ +from __future__ import annotations + import torch + from neps.search_spaces.encoding import ConfigEncoder -from neps.search_spaces.hyperparameters import Categorical, Float, Integer from neps.search_spaces.functions import pairwise_dist +from neps.search_spaces.hyperparameters import Categorical, Float, Integer def test_config_encoder_pdist_calculation() -> None: diff --git a/tests/test_settings/__init__.py b/tests/test_settings/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_settings/test_settings.py b/tests/test_settings/test_settings.py index 18892f687..ca76445cd 100644 --- a/tests/test_settings/test_settings.py +++ b/tests/test_settings/test_settings.py @@ -1,15 +1,17 @@ from __future__ import annotations -from neps.utils.run_args import Settings, Default -import pytest from pathlib import Path + +import pytest + +from neps.optimizers.bayesian_optimization.optimizer import BayesianOptimization +from neps.utils.run_args import Default, Settings from tests.test_yaml_run_args.test_yaml_run_args import ( evaluate_pipeline, hook1, hook2, pipeline_space, ) -from neps.optimizers.bayesian_optimization.optimizer import BayesianOptimization BASE_PATH = Path("tests") / "test_settings" evaluate_pipeline = evaluate_pipeline @@ -21,7 +23,7 @@ @pytest.mark.neps_api @pytest.mark.parametrize( - "func_args, yaml_args, expected_output", + ("func_args", "yaml_args", "expected_output"), [ ( { # only essential arguments provided by func_args, no yaml @@ -43,6 +45,7 @@ "pre_load_hooks": Default(None), "searcher": Default("default"), "searcher_kwargs": {}, + "sample_batch_size": Default(None), }, Default(None), { @@ -63,6 +66,7 @@ "pre_load_hooks": None, "searcher": "default", "searcher_kwargs": {}, + "sample_batch_size": None, }, ), ( @@ -85,6 +89,7 @@ "pre_load_hooks": Default(None), "searcher": Default("default"), "searcher_kwargs": {}, + "sample_batch_size": Default(None), }, "run_args_required.yaml", { @@ -105,6 +110,7 @@ "pre_load_hooks": None, "searcher": "default", "searcher_kwargs": {}, + "sample_batch_size": None, }, ), ( @@ -127,6 +133,7 @@ "pre_load_hooks": Default(None), "searcher": Default("default"), "searcher_kwargs": {}, + "sample_batch_size": Default(None), }, "run_args_optional.yaml", { @@ -147,6 +154,7 @@ "pre_load_hooks": None, "searcher": "hyperband", "searcher_kwargs": {}, + "sample_batch_size": None, }, ), ( @@ -169,6 +177,7 @@ "pre_load_hooks": None, "searcher": "default", "searcher_kwargs": {}, + "sample_batch_size": Default(None), }, "overwrite_run_args.yaml", { @@ -189,6 +198,7 @@ "pre_load_hooks": None, "searcher": "default", "searcher_kwargs": {}, + "sample_batch_size": None, }, ), ( @@ -217,6 +227,7 @@ "sample_prior_first": False, "sample_prior_at_target": False, }, + "sample_batch_size": Default(None), }, "run_args_optimizer_settings.yaml", { @@ -251,6 +262,7 @@ "sample_prior_first": False, "sample_prior_at_target": False, }, + "sample_batch_size": None, }, ), ( @@ -275,6 +287,7 @@ "searcher_kwargs": { "initial_design_size": 9, }, + "sample_batch_size": Default(None), }, "run_args_optimizer_outside.yaml", { @@ -295,18 +308,14 @@ "pre_load_hooks": None, "searcher": my_bayesian, "searcher_kwargs": {"initial_design_size": 9}, + "sample_batch_size": None, }, ), ], ) def test_check_settings(func_args: dict, yaml_args: str, expected_output: dict) -> None: - """ - Check if expected settings are set - """ - if isinstance(yaml_args, str): - args = BASE_PATH / yaml_args - else: - args = yaml_args + """Check if expected settings are set.""" + args = BASE_PATH / yaml_args if isinstance(yaml_args, str) else yaml_args settings = Settings(func_args, args) for key, value in expected_output.items(): @@ -315,7 +324,7 @@ def test_check_settings(func_args: dict, yaml_args: str, expected_output: dict) @pytest.mark.neps_api @pytest.mark.parametrize( - "func_args, yaml_args, error", + ("func_args", "yaml_args", "error"), [ ( { @@ -336,6 +345,7 @@ def test_check_settings(func_args: dict, yaml_args: str, expected_output: dict) "pre_load_hooks": Default(None), "searcher": Default("default"), "searcher_kwargs": {}, + "sample_batch_size": Default(None), }, Default(None), ValueError, @@ -343,10 +353,8 @@ def test_check_settings(func_args: dict, yaml_args: str, expected_output: dict) ], ) def test_settings_initialization_error( - func_args: dict, yaml_args: str | Default, error: Exception + func_args: dict, yaml_args: str | Default, error: type[Exception] ) -> None: - """ - Test if Settings raises Error when essential arguments are missing - """ + """Test if Settings raises Error when essential arguments are missing.""" with pytest.raises(error): Settings(func_args, yaml_args) diff --git a/tests/test_state/test_filebased_neps_state.py b/tests/test_state/test_filebased_neps_state.py index 307fab08c..ea2751520 100644 --- a/tests/test_state/test_filebased_neps_state.py +++ b/tests/test_state/test_filebased_neps_state.py @@ -1,29 +1,34 @@ """NOTE: These tests are pretty specific to the filebased state implementation. This could be generalized if we end up with a server based implementation but -for now we're just testing the filebased implementation.""" +for now we're just testing the filebased implementation. +""" +from __future__ import annotations from pathlib import Path from typing import Any -from neps.exceptions import NePSError, TrialNotFoundError -from neps.state.err_dump import ErrDump -from neps.state.filebased import ( - create_or_load_filebased_neps_state, - load_filebased_neps_state, -) import pytest from pytest_cases import fixture, parametrize + +from neps.exceptions import NePSError, TrialNotFoundError +from neps.state.err_dump import ErrDump +from neps.state.neps_state import NePSState from neps.state.optimizer import BudgetInfo, OptimizationState, OptimizerInfo +from neps.state.seed_snapshot import SeedSnapshot @fixture -@parametrize("max_cost_total_info", [BudgetInfo(max_cost_total=10, used_cost_budget=0), None]) +@parametrize("budget_info", [BudgetInfo(max_cost_total=10, used_cost_budget=0), None]) @parametrize("shared_state", [{"a": "b"}, {}]) def optimizer_state( - max_cost_total_info: BudgetInfo | None, + budget_info: BudgetInfo | None, shared_state: dict[str, Any], ) -> OptimizationState: - return OptimizationState(budget=max_cost_total_info, shared_state=shared_state) + return OptimizationState( + budget=budget_info, + seed_snapshot=SeedSnapshot.new_capture(), + shared_state=shared_state, + ) @fixture @@ -38,21 +43,21 @@ def test_create_with_new_filebased_neps_state( optimizer_state: OptimizationState, ) -> None: new_path = tmp_path / "neps_state" - neps_state = create_or_load_filebased_neps_state( - directory=new_path, + neps_state = NePSState.create_or_load( + path=new_path, optimizer_info=optimizer_info, optimizer_state=optimizer_state, ) - assert neps_state.optimizer_info() == optimizer_info - assert neps_state.optimizer_state() == optimizer_state - assert neps_state.all_trial_ids() == set() - assert neps_state.get_all_trials() == {} - assert neps_state.get_errors() == ErrDump(errs=[]) - assert neps_state.get_next_pending_trial() is None - assert neps_state.get_next_pending_trial(n=10) == [] + assert neps_state.lock_and_get_optimizer_info() == optimizer_info + assert neps_state.lock_and_get_optimizer_state() == optimizer_state + assert neps_state.all_trial_ids() == [] + assert neps_state.lock_and_read_trials() == {} + assert neps_state.lock_and_get_errors() == ErrDump(errs=[]) + assert neps_state.lock_and_get_next_pending_trial() is None + assert neps_state.lock_and_get_next_pending_trial(n=10) == [] with pytest.raises(TrialNotFoundError): - assert neps_state.get_trial_by_id("1") + assert neps_state.lock_and_get_trial_by_id("1") def test_create_or_load_with_load_filebased_neps_state( @@ -61,8 +66,8 @@ def test_create_or_load_with_load_filebased_neps_state( optimizer_state: OptimizationState, ) -> None: new_path = tmp_path / "neps_state" - neps_state = create_or_load_filebased_neps_state( - directory=new_path, + neps_state = NePSState.create_or_load( + path=new_path, optimizer_info=optimizer_info, optimizer_state=optimizer_state, ) @@ -72,10 +77,11 @@ def test_create_or_load_with_load_filebased_neps_state( # was passed in. different_state = OptimizationState( budget=BudgetInfo(max_cost_total=20, used_cost_budget=10), - shared_state={"c": "d"}, + seed_snapshot=SeedSnapshot.new_capture(), + shared_state=None, ) - neps_state2 = create_or_load_filebased_neps_state( - directory=new_path, + neps_state2 = NePSState.create_or_load( + path=new_path, optimizer_info=optimizer_info, optimizer_state=different_state, ) @@ -88,13 +94,13 @@ def test_load_on_existing_neps_state( optimizer_state: OptimizationState, ) -> None: new_path = tmp_path / "neps_state" - neps_state = create_or_load_filebased_neps_state( - directory=new_path, + neps_state = NePSState.create_or_load( + path=new_path, optimizer_info=optimizer_info, optimizer_state=optimizer_state, ) - neps_state2 = load_filebased_neps_state(directory=new_path) + neps_state2 = NePSState.create_or_load(path=new_path, load_only=True) assert neps_state == neps_state2 @@ -104,15 +110,15 @@ def test_new_or_load_on_existing_neps_state_with_different_optimizer_info( optimizer_state: OptimizationState, ) -> None: new_path = tmp_path / "neps_state" - create_or_load_filebased_neps_state( - directory=new_path, + NePSState.create_or_load( + path=new_path, optimizer_info=optimizer_info, optimizer_state=optimizer_state, ) with pytest.raises(NePSError): - create_or_load_filebased_neps_state( - directory=new_path, + NePSState.create_or_load( + path=new_path, optimizer_info=OptimizerInfo({"e": "f"}), optimizer_state=optimizer_state, ) diff --git a/tests/test_state/test_neps_state.py b/tests/test_state/test_neps_state.py index aaa5babe8..6d067d896 100644 --- a/tests/test_state/test_neps_state.py +++ b/tests/test_state/test_neps_state.py @@ -1,28 +1,28 @@ """NOTE: These tests are pretty specific to the filebased state implementation. This could be generalized if we end up with a server based implementation but -for now we're just testing the filebased implementation.""" +for now we're just testing the filebased implementation. +""" +from __future__ import annotations import time from pathlib import Path from typing import Any import pytest +from pytest_cases import case, fixture, parametrize, parametrize_with_cases + +from neps.optimizers import SearcherMapping from neps.optimizers.base_optimizer import BaseOptimizer from neps.search_spaces.hyperparameters import ( + Categorical, + Constant, Float, Integer, - Constant, - Categorical, ) from neps.search_spaces.search_space import SearchSpace -from neps.state.filebased import ( - create_or_load_filebased_neps_state, -) - -from pytest_cases import fixture, parametrize, parametrize_with_cases, case from neps.state.neps_state import NePSState from neps.state.optimizer import BudgetInfo, OptimizationState, OptimizerInfo -from neps.optimizers import SearcherMapping +from neps.state.seed_snapshot import SeedSnapshot @case @@ -72,7 +72,6 @@ def case_search_space_fid_with_prior() -> SearchSpace: "multifidelity_tpe", ] -# OPTIMIZER_FAILS_WITH_FIDELITY = [ "random_search", "bayesian_optimization", @@ -120,7 +119,7 @@ def case_search_space_fid_with_prior() -> SearchSpace: @fixture -@parametrize("key", [k for k in SearcherMapping.keys()]) +@parametrize("key", list(SearcherMapping.keys())) @parametrize_with_cases("search_space", cases=".", prefix="case_search_space") def optimizer_and_key(key: str, search_space: SearchSpace) -> tuple[BaseOptimizer, str]: if key in JUST_SKIP: @@ -156,10 +155,14 @@ def case_neps_state_filebased( shared_state: dict[str, Any], ) -> NePSState: new_path = tmp_path / "neps_state" - return create_or_load_filebased_neps_state( - directory=new_path, + return NePSState.create_or_load( + path=new_path, optimizer_info=optimizer_info, - optimizer_state=OptimizationState(budget=max_cost_total, shared_state=shared_state), + optimizer_state=OptimizationState( + budget=max_cost_total, + seed_snapshot=SeedSnapshot.new_capture(), + shared_state=shared_state, + ), ) @@ -169,15 +172,15 @@ def test_sample_trial( optimizer_and_key: tuple[BaseOptimizer, str], ) -> None: optimizer, key = optimizer_and_key - if key in REQUIRES_COST and neps_state.optimizer_state().budget is None: + if key in REQUIRES_COST and neps_state.lock_and_get_optimizer_state().budget is None: pytest.xfail(f"{key} requires a cost budget") - assert neps_state.get_all_trials() == {} - assert neps_state.get_next_pending_trial() is None - assert neps_state.get_next_pending_trial(n=10) == [] - assert neps_state.all_trial_ids() == set() + assert neps_state.lock_and_read_trials() == {} + assert neps_state.lock_and_get_next_pending_trial() is None + assert neps_state.lock_and_get_next_pending_trial(n=10) == [] + assert neps_state.all_trial_ids() == [] - trial1 = neps_state.sample_trial(optimizer=optimizer, worker_id="1") + trial1 = neps_state.lock_and_sample_trial(optimizer=optimizer, worker_id="1") for k, v in trial1.config.items(): assert k in optimizer.pipeline_space.hyperparameters assert v is not None, f"'{k}' is None in {trial1.config}" @@ -186,19 +189,19 @@ def test_sample_trial( # precise, we need to introduce a sleep -_- time.sleep(0.1) - assert neps_state.get_all_trials() == {trial1.id: trial1} - assert neps_state.get_next_pending_trial() == trial1 - assert neps_state.get_next_pending_trial(n=10) == [trial1] - assert neps_state.all_trial_ids() == {trial1.id} + assert neps_state.lock_and_read_trials() == {trial1.id: trial1} + assert neps_state.lock_and_get_next_pending_trial() == trial1 + assert neps_state.lock_and_get_next_pending_trial(n=10) == [trial1] + assert neps_state.all_trial_ids() == [trial1.id] - trial2 = neps_state.sample_trial(optimizer=optimizer, worker_id="1") + trial2 = neps_state.lock_and_sample_trial(optimizer=optimizer, worker_id="1") for k, v in trial1.config.items(): assert k in optimizer.pipeline_space.hyperparameters assert v is not None, f"'{k}' is None in {trial1.config}" assert trial1 != trial2 - assert neps_state.get_all_trials() == {trial1.id: trial1, trial2.id: trial2} - assert neps_state.get_next_pending_trial() == trial1 - assert neps_state.get_next_pending_trial(n=10) == [trial1, trial2] - assert neps_state.all_trial_ids() == {trial1.id, trial2.id} + assert neps_state.lock_and_read_trials() == {trial1.id: trial1, trial2.id: trial2} + assert neps_state.lock_and_get_next_pending_trial() == trial1 + assert neps_state.lock_and_get_next_pending_trial(n=10) == [trial1, trial2] + assert sorted(neps_state.all_trial_ids()) == [trial1.id, trial2.id] diff --git a/tests/test_state/test_rng.py b/tests/test_state/test_rng.py index d122c8e88..2605433a4 100644 --- a/tests/test_state/test_rng.py +++ b/tests/test_state/test_rng.py @@ -1,21 +1,23 @@ -from pathlib import Path +from __future__ import annotations + import random -from typing import Callable +from collections.abc import Callable +from pathlib import Path + import numpy as np -import torch import pytest +import torch from neps.state.seed_snapshot import SeedSnapshot -from neps.state.filebased import ReaderWriterSeedSnapshot @pytest.mark.parametrize( "make_ints", - ( + [ lambda: [random.randint(0, 100) for _ in range(10)], lambda: list(np.random.randint(0, 100, (10,))), lambda: list(torch.randint(0, 100, (10,))), - ), + ], ) def test_randomstate_consistent( tmp_path: Path, make_ints: Callable[[], list[int]] @@ -34,21 +36,3 @@ def test_randomstate_consistent( integers_2 = make_ints() assert integers_1 == integers_2 - - ReaderWriterSeedSnapshot.write(SeedSnapshot.new_capture(), seed_dir) - - integers_3 = make_ints() - assert integers_3 != integers_2, "Ensure we have actually changed random state" - - ReaderWriterSeedSnapshot.read(seed_dir).set_as_global_seed_state() - integers_4 = make_ints() - - assert integers_3 == integers_4 - - before = SeedSnapshot.new_capture() - after = SeedSnapshot.new_capture() - - _ = make_ints() - - after.recapture() - assert before != after diff --git a/tests/test_state/test_synced.py b/tests/test_state/test_synced.py deleted file mode 100644 index c5dd85d0b..000000000 --- a/tests/test_state/test_synced.py +++ /dev/null @@ -1,429 +0,0 @@ -import copy -import random - -from pytest_cases import parametrize, parametrize_with_cases, case -import numpy as np -from neps.state.err_dump import ErrDump, SerializableTrialError -from neps.state.filebased import ( - ReaderWriterErrDump, - ReaderWriterOptimizationState, - ReaderWriterOptimizerInfo, - ReaderWriterSeedSnapshot, - ReaderWriterTrial, - FileVersioner, - FileLocker, -) -from neps.state.optimizer import BudgetInfo, OptimizationState, OptimizerInfo -import pytest -from typing import Any, Callable -from pathlib import Path -from neps.state import SeedSnapshot, Synced, Trial - - -@case -def case_trial_1(tmp_path: Path) -> tuple[Synced[Trial, Path], Callable[[Trial], None]]: - trial_id = "1" - trial = Trial.new( - trial_id=trial_id, - location="", - config={"a": "b"}, - time_sampled=0, - previous_trial=None, - previous_trial_location=None, - worker_id=0, - ) - - def _update(trial: Trial) -> None: - trial.set_submitted(time_submitted=1) - - x = Synced.new( - data=trial, - location=tmp_path / "1", - locker=FileLocker(lock_path=tmp_path / "1" / ".lock", poll=0.1, timeout=None), - versioner=FileVersioner(version_file=tmp_path / "1" / ".version"), - reader_writer=ReaderWriterTrial(), - ) - return x, _update - - -@case -def case_trial_2(tmp_path: Path) -> tuple[Synced[Trial, Path], Callable[[Trial], None]]: - trial_id = "1" - trial = Trial.new( - trial_id=trial_id, - location="", - config={"a": "b"}, - time_sampled=0, - previous_trial=None, - previous_trial_location=None, - worker_id=0, - ) - trial.set_submitted(time_submitted=1) - - def _update(trial: Trial) -> None: - trial.set_evaluating(time_started=2, worker_id="1") - - x = Synced.new( - data=trial, - location=tmp_path / "1", - locker=FileLocker(lock_path=tmp_path / "1" / ".lock", poll=0.1, timeout=None), - versioner=FileVersioner(version_file=tmp_path / "1" / ".version"), - reader_writer=ReaderWriterTrial(), - ) - return x, _update - - -@case -def case_trial_3(tmp_path: Path) -> tuple[Synced[Trial, Path], Callable[[Trial], None]]: - trial_id = "1" - trial = Trial.new( - trial_id=trial_id, - config={"a": "b"}, - location="", - time_sampled=0, - previous_trial=None, - previous_trial_location=None, - worker_id=0, - ) - trial.set_submitted(time_submitted=1) - trial.set_evaluating(time_started=2, worker_id="1") - - def _update(trial: Trial) -> None: - trial.set_complete( - time_end=3, - objective_to_minimize=1, - cost=1, - extra={"hi": [1, 2, 3]}, - learning_curve=[1], - report_as="success", - evaluation_duration=1, - err=None, - tb=None, - ) - - x = Synced.new( - data=trial, - location=tmp_path / "1", - locker=FileLocker(lock_path=tmp_path / "1" / ".lock", poll=0.1, timeout=None), - versioner=FileVersioner(version_file=tmp_path / "1" / ".version"), - reader_writer=ReaderWriterTrial(), - ) - return x, _update - - -@case -def case_trial_4(tmp_path: Path) -> tuple[Synced[Trial, Path], Callable[[Trial], None]]: - trial_id = "1" - trial = Trial.new( - trial_id=trial_id, - config={"a": "b"}, - location="", - time_sampled=0, - previous_trial=None, - previous_trial_location=None, - worker_id=0, - ) - trial.set_submitted(time_submitted=1) - trial.set_evaluating(time_started=2, worker_id="1") - - def _update(trial: Trial) -> None: - trial.set_complete( - time_end=3, - objective_to_minimize=np.nan, - cost=np.inf, - extra={"hi": [1, 2, 3]}, - report_as="failed", - learning_curve=None, - evaluation_duration=2, - err=None, - tb=None, - ) - - x = Synced.new( - data=trial, - location=tmp_path / "1", - locker=FileLocker(lock_path=tmp_path / "1" / ".lock", poll=0.1, timeout=None), - versioner=FileVersioner(version_file=tmp_path / "1" / ".version"), - reader_writer=ReaderWriterTrial(), - ) - return x, _update - - -@case -def case_trial_5(tmp_path: Path) -> tuple[Synced[Trial, Path], Callable[[Trial], None]]: - trial_id = "1" - trial = Trial.new( - trial_id=trial_id, - config={"a": "b"}, - location="", - time_sampled=0, - previous_trial=None, - previous_trial_location=None, - worker_id=0, - ) - trial.set_submitted(time_submitted=1) - trial.set_evaluating(time_started=2, worker_id=1) - - def _update(trial: Trial) -> None: - trial.set_complete( - time_end=3, - objective_to_minimize=np.nan, - cost=np.inf, - extra={"hi": [1, 2, 3]}, - learning_curve=None, - evaluation_duration=2, - report_as="failed", - err=ValueError("hi"), - tb="something something traceback", - ) - - x = Synced.new( - data=trial, - location=tmp_path / "1", - locker=FileLocker(lock_path=tmp_path / "1" / ".lock", poll=0.1, timeout=None), - versioner=FileVersioner(version_file=tmp_path / "1" / ".version"), - reader_writer=ReaderWriterTrial(), - ) - return x, _update - - -@case -def case_trial_6(tmp_path: Path) -> tuple[Synced[Trial, Path], Callable[[Trial], None]]: - trial_id = "1" - trial = Trial.new( - trial_id=trial_id, - config={"a": "b"}, - location="", - time_sampled=0, - previous_trial=None, - previous_trial_location=None, - worker_id=0, - ) - trial.set_submitted(time_submitted=1) - trial.set_evaluating(time_started=2, worker_id=1) - - def _update(trial: Trial) -> None: - trial.set_corrupted() - - x = Synced.new( - data=trial, - location=tmp_path / "1", - locker=FileLocker(lock_path=tmp_path / "1" / ".lock", poll=0.1, timeout=None), - versioner=FileVersioner(version_file=tmp_path / "1" / ".version"), - reader_writer=ReaderWriterTrial(), - ) - return x, _update - - -@case -def case_trial_7(tmp_path: Path) -> tuple[Synced[Trial, Path], Callable[[Trial], None]]: - trial_id = "1" - trial = Trial.new( - trial_id=trial_id, - config={"a": "b"}, - location="", - time_sampled=0, - previous_trial=None, - previous_trial_location=None, - worker_id=0, - ) - trial.set_submitted(time_submitted=1) - trial.set_evaluating(time_started=2, worker_id=1) - trial.set_complete( - time_end=3, - objective_to_minimize=np.nan, - cost=np.inf, - extra={"hi": [1, 2, 3]}, - learning_curve=[1, 2, 3], - report_as="failed", - evaluation_duration=2, - err=ValueError("hi"), - tb="something something traceback", - ) - - def _update(trial: Trial) -> None: - trial.reset() - - x = Synced.new( - data=trial, - location=tmp_path / "1", - locker=FileLocker(lock_path=tmp_path / "1" / ".lock", poll=0.1, timeout=None), - versioner=FileVersioner(version_file=tmp_path / "1" / ".version"), - reader_writer=ReaderWriterTrial(), - ) - return x, _update - - -@case -def case_seed_snapshot( - tmp_path: Path, -) -> tuple[Synced[SeedSnapshot, Path], Callable[[SeedSnapshot], None]]: - seed = SeedSnapshot.new_capture() - - def _update(seed: SeedSnapshot) -> None: - random.randint(0, 100) - seed.recapture() - - x = Synced.new( - data=seed, - location=tmp_path / "seeds", - locker=FileLocker(lock_path=tmp_path / "seeds" / ".lock", poll=0.1, timeout=None), - versioner=FileVersioner(version_file=tmp_path / "seeds" / ".version"), - reader_writer=ReaderWriterSeedSnapshot(), - ) - return x, _update - - -@case -@parametrize( - "err", - [ - None, - SerializableTrialError( - trial_id="1", - worker_id="2", - err_type="ValueError", - err="hi", - tb="traceback\nmore", - ), - ], -) -def case_err_dump( - tmp_path: Path, - err: None | SerializableTrialError, -) -> tuple[Synced[ErrDump, Path], Callable[[ErrDump], None]]: - err_dump = ErrDump() if err is None else ErrDump(errs=[err]) - - def _update(err_dump: ErrDump) -> None: - new_err = SerializableTrialError( - trial_id="2", - worker_id="2", - err_type="RuntimeError", - err="hi", - tb="traceback\nless", - ) - err_dump.append(new_err) - - x = Synced.new( - data=err_dump, - location=tmp_path / "err_dump", - locker=FileLocker( - lock_path=tmp_path / "err_dump" / ".lock", poll=0.1, timeout=None - ), - versioner=FileVersioner(version_file=tmp_path / "err_dump" / ".version"), - reader_writer=ReaderWriterErrDump("all"), - ) - return x, _update - - -@case -def case_optimizer_info( - tmp_path: Path, -) -> tuple[Synced[OptimizerInfo, Path], Callable[[OptimizerInfo], None]]: - optimizer_info = OptimizerInfo(info={"a": "b"}) - - def _update(optimizer_info: OptimizerInfo) -> None: - optimizer_info.info["b"] = "c" # type: ignore # NOTE: We shouldn't be mutating but anywho... - - x = Synced.new( - data=optimizer_info, - location=tmp_path / "optimizer_info", - locker=FileLocker( - lock_path=tmp_path / "optimizer_info" / ".lock", poll=0.1, timeout=None - ), - versioner=FileVersioner(version_file=tmp_path / "optimizer_info" / ".version"), - reader_writer=ReaderWriterOptimizerInfo(), - ) - return x, _update - - -@case -@pytest.mark.parametrize( - "budget", (None, BudgetInfo(max_cost_total=10, used_cost_budget=0)) -) -@pytest.mark.parametrize("shared_state", ({}, {"a": "b"})) -def case_optimization_state( - tmp_path: Path, - budget: BudgetInfo | None, - shared_state: dict[str, Any], -) -> tuple[Synced[OptimizationState, Path], Callable[[OptimizationState], None]]: - optimization_state = OptimizationState(budget=budget, shared_state=shared_state) - - def _update(optimization_state: OptimizationState) -> None: - optimization_state.shared_state["a"] = "c" # type: ignore # NOTE: We shouldn't be mutating but anywho... - optimization_state.budget = BudgetInfo(max_cost_total=10, used_cost_budget=5) - - x = Synced.new( - data=optimization_state, - location=tmp_path / "optimizer_info", - locker=FileLocker( - lock_path=tmp_path / "optimizer_info" / ".lock", poll=0.1, timeout=None - ), - versioner=FileVersioner(version_file=tmp_path / "optimizer_info" / ".version"), - reader_writer=ReaderWriterOptimizationState(), - ) - return x, _update - - -@parametrize_with_cases("shared, update", cases=".") -def test_initial_state(shared: Synced, update: Callable) -> None: - assert shared._is_locked() == False - assert shared._is_stale() == False - assert shared._unsynced() == shared.synced() - - -@parametrize_with_cases("shared, update", cases=".") -def test_put_updates_current_data_and_is_not_stale( - shared: Synced, update: Callable -) -> None: - current_data = shared._unsynced() - - new_data = copy.deepcopy(current_data) - update(new_data) - assert new_data != current_data - - shared.put(new_data) - assert shared._unsynced() == new_data - assert shared._is_stale() == False - assert shared._is_locked() == False - - -@parametrize_with_cases("shared1, update", cases=".") -def test_share_synced_update_and_put(shared1: Synced, update: Callable) -> None: - shared2 = shared1.deepcopy() - assert shared1 == shared2 - assert not shared1._is_locked() - assert not shared2._is_locked() - - with shared2.acquire() as (data2, put2): - assert shared1._is_locked() - assert shared2._is_locked() - update(data2) - put2(data2) - - assert not shared1._is_locked() - assert not shared2._is_locked() - - assert shared1 != shared2 - assert shared1._unsynced() != shared2._unsynced() - assert shared1._is_stale() - - shared1.synced() - assert not shared1._is_stale() - assert not shared2._is_stale() - assert shared1._unsynced() == shared2._unsynced() - - -@parametrize_with_cases("shared, update", cases=".") -def test_shared_new_fails_if_done_on_existing_resource( - shared: Synced, update: Callable -) -> None: - data, location, versioner, rw, lock = shared._components() - with pytest.raises(Synced.VersionedResourceAlreadyExistsError): - Synced.new( - data=data, - location=location, - versioner=versioner, - reader_writer=rw, - locker=lock, - ) diff --git a/tests/test_state/test_trial.py b/tests/test_state/test_trial.py index 562bf2b3e..12872bf3f 100644 --- a/tests/test_state/test_trial.py +++ b/tests/test_state/test_trial.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import numpy as np @@ -20,12 +22,13 @@ def test_trial_creation() -> None: previous_trial=previous_trial, worker_id=worker_id, ) - assert trial.state == Trial.State.PENDING + assert trial.metadata.state == Trial.State.PENDING assert trial.id == trial_id assert trial.config == {"a": "b"} assert trial.metadata == Trial.MetaData( id="1", time_sampled=time_sampled, + state=Trial.State.PENDING, location="1", previous_trial_location=None, previous_trial_id=previous_trial, @@ -54,11 +57,12 @@ def test_trial_as_submitted() -> None: ) trial.set_submitted(time_submitted=time_submitted) - assert trial.state == Trial.State.SUBMITTED + assert trial.metadata.state == Trial.State.SUBMITTED assert trial.id == trial_id assert trial.config == {"a": "b"} assert trial.metadata == Trial.MetaData( id=trial_id, + state=Trial.State.SUBMITTED, time_sampled=time_sampled, previous_trial_location="0", location="1", @@ -91,11 +95,12 @@ def test_trial_as_in_progress_with_different_evaluating_worker() -> None: trial.set_submitted(time_submitted=time_submitted) trial.set_evaluating(time_started=time_started, worker_id=evaluating_worker_id) - assert trial.state == Trial.State.EVALUATING + assert trial.metadata.state == Trial.State.EVALUATING assert trial.id == trial_id assert trial.config == {"a": "b"} assert trial.metadata == Trial.MetaData( id=trial_id, + state=Trial.State.EVALUATING, time_sampled=time_sampled, previous_trial_id=previous_trial, previous_trial_location="0", @@ -144,11 +149,12 @@ def test_trial_as_success_after_being_progress() -> None: time_end=time_end, ) - assert trial.state == Trial.State.SUCCESS + assert trial.metadata.state == Trial.State.SUCCESS assert trial.id == trial_id assert trial.config == {"a": "b"} assert trial.metadata == Trial.MetaData( id=trial_id, + state=Trial.State.SUCCESS, time_sampled=time_sampled, previous_trial_location="0", location="1", @@ -208,11 +214,12 @@ def test_trial_as_failed_with_nan_objective_to_minimize_and_in_cost() -> None: extra=extra, time_end=time_end, ) - assert trial.state == Trial.State.FAILED + assert trial.metadata.state == Trial.State.FAILED assert trial.id == trial_id assert trial.config == {"a": "b"} assert trial.metadata == Trial.MetaData( id=trial_id, + state=Trial.State.FAILED, time_sampled=time_sampled, previous_trial_id=previous_trial, sampling_worker_id=sampling_worker_id, @@ -273,11 +280,12 @@ def test_trial_as_crashed_with_err_and_tb() -> None: time_end=time_end, ) - assert trial.state == Trial.State.CRASHED + assert trial.metadata.state == Trial.State.CRASHED assert trial.id == trial_id assert trial.config == {"a": "b"} assert trial.metadata == Trial.MetaData( id=trial_id, + state=Trial.State.CRASHED, time_sampled=time_sampled, previous_trial_id=previous_trial, sampling_worker_id=sampling_worker_id, diff --git a/tests/test_yaml_run_args/__init__.py b/tests/test_yaml_run_args/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/__init__.py b/tests/test_yaml_run_args/test_declarative_usage_docs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/evaluate_pipeline.py b/tests/test_yaml_run_args/test_declarative_usage_docs/evaluate_pipeline.py index a59f77394..adb912682 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/evaluate_pipeline.py +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/evaluate_pipeline.py @@ -1,32 +1,29 @@ +from __future__ import annotations + from warnings import warn + import numpy as np def run_pipeline(learning_rate, optimizer, epochs): - """func for test loading of run_pipeline""" - warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + """Func for test loading of run_pipeline.""" + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning, stacklevel=2) return evaluate_pipeline(learning_rate, optimizer, epochs) def evaluate_pipeline(learning_rate, optimizer, epochs): - """func for test loading of evaluate_pipeline""" - if optimizer == "a": - eval_score = np.random.choice([learning_rate, epochs], 1) - else: - eval_score = 5.0 + """Func for test loading of evaluate_pipeline.""" + eval_score = np.random.choice([learning_rate, epochs], 1) if optimizer == "a" else 5.0 return {"objective_to_minimize": eval_score} def run_pipeline_constant(learning_rate, optimizer, epochs, batch_size): - """func for test loading of run_pipeline""" - warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + """Func for test loading of run_pipeline.""" + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning, stacklevel=2) return evaluate_pipeline_constant(learning_rate, optimizer, epochs, batch_size) def evaluate_pipeline_constant(learning_rate, optimizer, epochs, batch_size): - """func for test loading of evaluate_pipeline""" - if optimizer == "a": - eval_score = np.random.choice([learning_rate, epochs], 1) - else: - eval_score = 5.0 + """Func for test loading of evaluate_pipeline.""" + eval_score = np.random.choice([learning_rate, epochs], 1) if optimizer == "a" else 5.0 eval_score += batch_size return {"objective_to_minimize": eval_score} diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/hooks.py b/tests/test_yaml_run_args/test_declarative_usage_docs/hooks.py index a26f28f32..029ac890c 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/hooks.py +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/hooks.py @@ -1,8 +1,11 @@ +from __future__ import annotations + + def hook1(sampler): - """func to test loading of pre_load_hooks""" + """Func to test loading of pre_load_hooks.""" return sampler def hook2(sampler): - """func to test loading of pre_load_hooks""" + """Func to test loading of pre_load_hooks.""" return sampler diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/neps_run.py b/tests/test_yaml_run_args/test_declarative_usage_docs/neps_run.py index b1d18fc36..fbc2a1bd1 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/neps_run.py +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/neps_run.py @@ -1,19 +1,20 @@ +from __future__ import annotations + import argparse from warnings import warn -import neps + import numpy as np +import neps + def run_pipeline_constant(learning_rate, optimizer, epochs, batch_size): - warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning, stacklevel=2) return evaluate_pipeline_constant(learning_rate, optimizer, epochs, batch_size) def evaluate_pipeline_constant(learning_rate, optimizer, epochs, batch_size): - """func for test loading of evaluate_pipeline""" - if optimizer == "a": - eval_score = np.random.choice([learning_rate, epochs], 1) - else: - eval_score = 5.0 + """Func for test loading of evaluate_pipeline.""" + eval_score = np.random.choice([learning_rate, epochs], 1) if optimizer == "a" else 5.0 eval_score += batch_size return {"objective_to_minimize": eval_score} diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/pipeline_space.py b/tests/test_yaml_run_args/test_declarative_usage_docs/pipeline_space.py index d4ba4028c..c63c06d29 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/pipeline_space.py +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/pipeline_space.py @@ -1,8 +1,10 @@ +from __future__ import annotations + import neps -pipeline_space = dict( - learning_rate=neps.Float(lower=1e-5, upper=1e-1, log=True), - epochs=neps.Integer(lower=5, upper=20, is_fidelity=True), - optimizer=neps.Categorical(choices=["adam", "sgd", "adamw"]), - batch_size=neps.Constant(value=64) -) +pipeline_space = { + "learning_rate": neps.Float(lower=1e-5, upper=1e-1, log=True), + "epochs": neps.Integer(lower=5, upper=20, is_fidelity=True), + "optimizer": neps.Categorical(choices=["adam", "sgd", "adamw"]), + "batch_size": neps.Constant(value=64) +} diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py b/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py index 4a026689d..2ec2d0dc0 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py @@ -1,9 +1,11 @@ -import pytest -import os +from __future__ import annotations + import subprocess import sys from pathlib import Path +import pytest + BASE_PATH = Path("tests") / "test_yaml_run_args" / "test_declarative_usage_docs" @@ -22,29 +24,25 @@ ], ) def test_run_with_yaml(yaml_file: str) -> None: - """ - Test 'neps.run' with various run_args.yaml settings to simulate loading options + """Test 'neps.run' with various run_args.yaml settings to simulate loading options for variables. """ yaml_path = BASE_PATH / yaml_file - assert os.path.exists(yaml_path), f"{yaml_path} does not exist." + assert yaml_path.exists(), f"{yaml_path} does not exist." try: subprocess.check_call([sys.executable, BASE_PATH / "neps_run.py", yaml_path]) except subprocess.CalledProcessError as e: - pytest.fail( - f"NePS run failed for configuration: {yaml_file} with error: {str(e)}" - ) + pytest.fail(f"NePS run failed for configuration: {yaml_file} with error: {e!s}") @pytest.mark.neps_api def test_run_with_yaml_and_run_pipeline() -> None: - """ - Test 'neps.run' with simple_example.yaml as run_args + a run_pipeline that is + """Test 'neps.run' with simple_example.yaml as run_args + a run_pipeline that is provided separately. """ yaml_path = BASE_PATH / "simple_example.yaml" - assert os.path.exists(yaml_path), f"{yaml_path} does not exist." + assert yaml_path.exists(), f"{yaml_path} does not exist." try: subprocess.check_call( @@ -52,5 +50,5 @@ def test_run_with_yaml_and_run_pipeline() -> None: ) except subprocess.CalledProcessError as e: pytest.fail( - f"NePS run failed for configuration: simple_example.yaml with error: {str(e)}" + f"NePS run failed for configuration: simple_example.yaml with error: {e!s}" ) diff --git a/tests/test_yaml_run_args/test_run_args_by_neps_run/__init__.py b/tests/test_yaml_run_args/test_run_args_by_neps_run/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_yaml_run_args/test_run_args_by_neps_run/neps_run.py b/tests/test_yaml_run_args/test_run_args_by_neps_run/neps_run.py index bbc0760f7..2a2775d4f 100644 --- a/tests/test_yaml_run_args/test_run_args_by_neps_run/neps_run.py +++ b/tests/test_yaml_run_args/test_run_args_by_neps_run/neps_run.py @@ -1,31 +1,32 @@ -from warnings import warn +from __future__ import annotations + import argparse +from warnings import warn + import numpy as np + import neps def run_pipeline(learning_rate, epochs, optimizer, batch_size): - """func for test loading of run_pipeline""" - warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + """Func for test loading of run_pipeline.""" + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning, stacklevel=2) return evaluate_pipeline(learning_rate, epochs, optimizer, batch_size) def evaluate_pipeline(learning_rate, epochs, optimizer, batch_size): - """func for test loading of evaluate_pipeline""" - if optimizer == "a": - eval_score = np.random.choice([learning_rate, epochs], 1) - else: - eval_score = 5.0 + """Func for test loading of evaluate_pipeline.""" + eval_score = np.random.choice([learning_rate, epochs], 1) if optimizer == "a" else 5.0 eval_score += batch_size return {"objective_to_minimize": eval_score} # For testing the functionality of loading a dictionary from a YAML configuration. -pipeline_space = dict( - learning_rate=neps.Float(lower=1e-6, upper=1e-1, log=False), - epochs=neps.Integer(lower=1, upper=3, is_fidelity=False), - optimizer=neps.Categorical(choices=["a", "b", "c"]), - batch_size=neps.Constant(64), -) +pipeline_space = { + "learning_rate": neps.Float(lower=1e-6, upper=1e-1, log=False), + "epochs": neps.Integer(lower=1, upper=3, is_fidelity=False), + "optimizer": neps.Categorical(choices=["a", "b", "c"]), + "batch_size": neps.Constant(64), +} if __name__ == "__main__": parser = argparse.ArgumentParser( diff --git a/tests/test_yaml_run_args/test_run_args_by_neps_run/test_neps_run.py b/tests/test_yaml_run_args/test_run_args_by_neps_run/test_neps_run.py index 4995a14c5..3ac9dc85e 100644 --- a/tests/test_yaml_run_args/test_run_args_by_neps_run/test_neps_run.py +++ b/tests/test_yaml_run_args/test_run_args_by_neps_run/test_neps_run.py @@ -1,10 +1,13 @@ -import pytest +from __future__ import annotations + import subprocess -import os import sys +from pathlib import Path + +import pytest import yaml -BASE_PATH = "tests/test_yaml_run_args/test_run_args_by_neps_run/" +BASE_PATH = Path("tests") / "test_yaml_run_args" / "test_run_args_by_neps_run" @pytest.mark.neps_api @@ -18,63 +21,62 @@ "file_name": "config_select_bo.yaml", "check_optimizer": True, "optimizer_path": "select_bo_run_args.yaml", - "result_path": "tests_tmpdir/test_run_args_by_neps_run/optimizer_bo/.optimizer_info/info.yaml", + "result_path": "tests_tmpdir/test_run_args_by_neps_run/optimizer_bo/optimizer_info.yaml", # noqa: E501 }, { "file_name": "config_priorband_with_args.yaml", "check_optimizer": True, "optimizer_path": "priorband_args_run_args.yaml", - "result_path": "tests_tmpdir/test_run_args_by_neps_run/optimizer_priorband/.optimizer_info/info.yaml", + "result_path": "tests_tmpdir/test_run_args_by_neps_run/optimizer_priorband/optimizer_info.yaml", # noqa: E501 }, { "file_name": "config_hyperband_mixed_args.yaml", "check_optimizer": True, "optimizer_path": "hyperband_searcher_kwargs_yaml_args.yaml", - "result_path": "tests_tmpdir/test_run_args_by_neps_run/optimizer_hyperband/.optimizer_info/info.yaml", + "result_path": "tests_tmpdir/test_run_args_by_neps_run/optimizer_hyperband/optimizer_info.yaml", # noqa: E501 "args": True, }, ], ) def test_run_with_yaml(config: dict) -> None: """Test "neps.run" with various run_args.yaml settings to simulate loading options - for variables.""" + for variables. + """ file_name = config["file_name"] check_optimizer = config.pop("check_optimizer", False) - assert os.path.exists(os.path.join(BASE_PATH, file_name)), ( - f"{file_name} " f"does not exist." - ) + assert (BASE_PATH / file_name).exists(), f"{file_name} " f"does not exist." cmd = [ sys.executable, - os.path.join(BASE_PATH, "neps_run.py"), - os.path.join(BASE_PATH, file_name), + BASE_PATH / "neps_run.py", + BASE_PATH / file_name, ] if "args" in config: cmd.append("--kwargs_flag") try: - subprocess.check_call(cmd) + subprocess.check_call(cmd) # noqa: S603 except subprocess.CalledProcessError: pytest.fail(f"NePS run failed for configuration: {file_name}") if check_optimizer: - optimizer_path = config.pop("optimizer_path") - result_path = config.pop("result_path") + optimizer_path = Path(config.pop("optimizer_path")) + result_path = Path(config.pop("result_path")) compare_generated_yaml(result_path, optimizer_path) -def compare_generated_yaml(result_path, optimizer_path): - """compare generated optimizer settings and solution settings""" - assert os.path.exists(result_path), "Generated YAML file does not exist." +def compare_generated_yaml(result_path: Path, optimizer_path: Path) -> None: + """Compare generated optimizer settings and solution settings.""" + assert result_path.exists(), "Generated YAML file does not exist." - assert os.path.exists( - BASE_PATH + "optimizer_yamls/" + optimizer_path - ), "Solution YAML file does not exist." + assert ( + BASE_PATH / "optimizer_yamls" / optimizer_path + ).exists(), "Solution YAML file does not exist." - with open(result_path, "r") as gen_file: + with result_path.open("r") as gen_file: generated_content = yaml.safe_load(gen_file) - with open(BASE_PATH + "optimizer_yamls/" + optimizer_path, "r") as ref_file: + with (BASE_PATH / "optimizer_yamls" / optimizer_path).open("r") as ref_file: reference_content = yaml.safe_load(ref_file) assert ( diff --git a/tests/test_yaml_run_args/test_yaml_run_args.py b/tests/test_yaml_run_args/test_yaml_run_args.py index 7995d953c..b68a07fe9 100644 --- a/tests/test_yaml_run_args/test_yaml_run_args.py +++ b/tests/test_yaml_run_args/test_yaml_run_args.py @@ -1,42 +1,50 @@ +from __future__ import annotations + +from collections.abc import Callable from warnings import warn + import pytest + import neps -from neps.utils.run_args import get_run_args_from_yaml from neps.optimizers.bayesian_optimization.optimizer import BayesianOptimization -from typing import Union, Callable, Dict, List, Type +from neps.utils.run_args import get_run_args_from_yaml BASE_PATH = "tests/test_yaml_run_args/" -pipeline_space = dict( - lr=neps.Float(lower=1e-3, upper=0.1), - optimizer=neps.Categorical(choices=["adam", "sgd", "adamw"]), - epochs=neps.Integer(lower=1, upper=10), - batch_size=neps.Constant(value=64), -) +pipeline_space = { + "lr": neps.Float(lower=1e-3, upper=0.1), + "optimizer": neps.Categorical(choices=["adam", "sgd", "adamw"]), + "epochs": neps.Integer(lower=1, upper=10), + "batch_size": neps.Constant(value=64), +} def run_pipeline(): - """func to test loading of run_pipeline""" - warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + """Func to test loading of run_pipeline.""" + warn( + "run_pipeline is deprecated, use evaluate_pipeline instead", + DeprecationWarning, + stacklevel=2, + ) return evaluate_pipeline() + def evaluate_pipeline(): - """func to test loading of evaluate_pipeline""" + """Func to test loading of evaluate_pipeline.""" return def hook1(sampler): - """func to test loading of pre_load_hooks""" + """Func to test loading of pre_load_hooks.""" return sampler def hook2(sampler): - """func to test loading of pre_load_hooks""" + """Func to test loading of pre_load_hooks.""" return sampler -def check_run_args(yaml_path_run_args: str, expected_output: Dict) -> None: - """ - Validates the loaded NEPS configuration against expected settings. +def check_run_args(yaml_path_run_args: str, expected_output: dict) -> None: + """Validates the loaded NEPS configuration against expected settings. Loads NEPS configuration settings from a specified YAML file and verifies against expected settings, including function objects, dict and classes. Special @@ -52,10 +60,9 @@ def check_run_args(yaml_path_run_args: str, expected_output: Dict) -> None: output = get_run_args_from_yaml(BASE_PATH + yaml_path_run_args) def are_functions_equivalent( - f1: Union[Callable, List[Callable]], f2: Union[Callable, List[Callable]] + f1: Callable | list[Callable], f2: Callable | list[Callable] ) -> bool: - """ - Compares functions or lists of functions for equivalence by their bytecode, + """Compares functions or lists of functions for equivalence by their bytecode, useful when identical functions have different memory addresses. This method identifies if functions, despite being distinct instances, perform identical operations. @@ -73,7 +80,7 @@ def are_functions_equivalent( return False return all( f1_item.__code__.co_code == f2_item.__code__.co_code - for f1_item, f2_item in zip(f1, f2) + for f1_item, f2_item in zip(f1, f2, strict=False) ) return f1.__code__.co_code == f2.__code__.co_code @@ -100,7 +107,7 @@ def are_functions_equivalent( @pytest.mark.neps_api @pytest.mark.parametrize( - "yaml_path,expected_output", + ("yaml_path", "expected_output"), [ ( "run_args_full.yaml", @@ -201,9 +208,8 @@ def are_functions_equivalent( ), ], ) -def test_yaml_config(yaml_path: str, expected_output: Dict) -> None: - """ - Tests NePS configuration loading from run_args=YAML, comparing expected settings +def test_yaml_config(yaml_path: str, expected_output: dict) -> None: + """Tests NePS configuration loading from run_args=YAML, comparing expected settings against loaded ones. Covers hierarchical levels and partial/full of yaml dict definitions. @@ -216,7 +222,7 @@ def test_yaml_config(yaml_path: str, expected_output: Dict) -> None: @pytest.mark.neps_api @pytest.mark.parametrize( - "yaml_path, expected_exception", + ("yaml_path", "expected_exception"), [ ("run_args_invalid_type.yaml", TypeError), ("run_args_wrong_path.yaml", ImportError), @@ -225,9 +231,8 @@ def test_yaml_config(yaml_path: str, expected_output: Dict) -> None: ("run_args_key_missing.yaml", KeyError), ], ) -def test_yaml_failure_cases(yaml_path: str, expected_exception: Type[Exception]) -> None: - """ - Tests for expected exceptions when loading erroneous NePS configurations from YAML. +def test_yaml_failure_cases(yaml_path: str, expected_exception: type[Exception]) -> None: + """Tests for expected exceptions when loading erroneous NePS configurations from YAML. Each case checks if `get_run_args_from_yaml` raises the correct exception for errors like invalid types, missing keys, and incorrect paths in YAML configurations. diff --git a/tests/test_yaml_search_space/__init__.py b/tests/test_yaml_search_space/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_yaml_search_space/test_search_space.py b/tests/test_yaml_search_space/test_search_space.py index 08a08cc60..ac1a4ed36 100644 --- a/tests/test_yaml_search_space/test_search_space.py +++ b/tests/test_yaml_search_space/test_search_space.py @@ -1,13 +1,15 @@ +from __future__ import annotations + from pathlib import Path import pytest + +from neps import Categorical, Constant, Float, Integer from neps.search_spaces.search_space import ( SearchSpaceFromYamlFileError, pipeline_space_from_yaml, ) -from neps import Categorical, Constant, Float, Integer - BASE_PATH = "tests/test_yaml_search_space/" @@ -43,7 +45,9 @@ def test_correct_including_priors_yaml_file(): BASE_PATH + "correct_config_including_priors.yml" ) assert isinstance(pipeline_space, dict) - float1 = Float(0.00001, 0.1, log=True, is_fidelity=False, prior=3.3e-2, prior_confidence="high") + float1 = Float( + 0.00001, 0.1, log=True, is_fidelity=False, prior=3.3e-2, prior_confidence="high" + ) assert float1.__eq__(pipeline_space["learning_rate"]) is True int1 = Integer(3, 30, log=False, is_fidelity=True) assert int1.__eq__(pipeline_space["num_epochs"]) is True @@ -72,7 +76,8 @@ def test_yaml_file_with_missing_key(): @pytest.mark.neps_api def test_yaml_file_with_inconsistent_types(): """Test the function with a YAML file having inconsistent types for - 'lower' and 'upper'.""" + 'lower' and 'upper'. + """ with pytest.raises(SearchSpaceFromYamlFileError) as excinfo: pipeline_space_from_yaml(BASE_PATH + "inconsistent_types_config.yml") assert str(excinfo.value.exception_type == "TypeError") @@ -84,16 +89,18 @@ def test_yaml_file_with_inconsistent_types(): @pytest.mark.neps_api def test_yaml_file_including_wrong_types(): """Test the function with a YAML file that defines the wrong but existing type - int to float as an optional argument""" + int to float as an optional argument. + """ with pytest.raises(SearchSpaceFromYamlFileError) as excinfo: pipeline_space_from_yaml(Path(BASE_PATH + "inconsistent_types_config2.yml")) - assert excinfo.value.exception_type == "TypeError" + assert excinfo.value.exception_type == "TypeError" @pytest.mark.neps_api def test_yaml_file_including_unkown_types(): """Test the function with a YAML file that defines an unknown type as an optional - argument""" + argument. + """ with pytest.raises(SearchSpaceFromYamlFileError) as excinfo: pipeline_space_from_yaml(BASE_PATH + "config_including_unknown_types.yaml") assert excinfo.value.exception_type == "TypeError" @@ -102,7 +109,8 @@ def test_yaml_file_including_unkown_types(): @pytest.mark.neps_api def test_yaml_file_including_not_allowed_parameter_keys(): """Test the function with a YAML file that defines an unknown type as an optional - argument""" + argument. + """ with pytest.raises(SearchSpaceFromYamlFileError) as excinfo: pipeline_space_from_yaml(BASE_PATH + "not_allowed_key_config.yml") assert excinfo.value.exception_type == "TypeError" @@ -111,7 +119,8 @@ def test_yaml_file_including_not_allowed_parameter_keys(): @pytest.mark.neps_api def test_yaml_file_default_parameter_not_in_range(): """Test if the default value outside the specified range is - correctly identified and handled.""" + correctly identified and handled. + """ with pytest.raises(SearchSpaceFromYamlFileError) as excinfo: pipeline_space_from_yaml(BASE_PATH + "default_not_in_range_config.yaml") assert excinfo.value.exception_type == "ValueError" @@ -128,7 +137,8 @@ def test_float_log_not_boolean(): @pytest.mark.neps_api def test_float_is_fidelity_not_boolean(): """Test if an exception is raised when for Float the 'is_fidelity' - attribute is not a boolean.""" + attribute is not a boolean. + """ with pytest.raises(SearchSpaceFromYamlFileError) as excinfo: pipeline_space_from_yaml( BASE_PATH + "not_boolean_type_is_fidelity_float_config.yaml" @@ -139,15 +149,18 @@ def test_float_is_fidelity_not_boolean(): @pytest.mark.neps_api def test_categorical_default_value_not_in_choices(): """Test if a ValueError is raised when the default value is not in the choices - for a Categorical.""" + for a Categorical. + """ with pytest.raises(SearchSpaceFromYamlFileError) as excinfo: pipeline_space_from_yaml(BASE_PATH + "default_value_not_in_choices_config.yaml") assert excinfo.value.exception_type == "ValueError" + @pytest.mark.neps_api def test_incorrect_fidelity_parameter_bounds(): """Test if a ValueError is raised when the bounds of a fidelity parameter are - not correctly specified.""" + not correctly specified. + """ with pytest.raises(SearchSpaceFromYamlFileError) as excinfo: pipeline_space_from_yaml(BASE_PATH + "incorrect_fidelity_bounds_config.yaml") assert excinfo.value.exception_type == "ValueError" diff --git a/uv.lock b/uv.lock deleted file mode 100644 index c4543f07b..000000000 --- a/uv.lock +++ /dev/null @@ -1,2088 +0,0 @@ -version = 1 -requires-python = ">=3.10, <3.13" -resolution-markers = [ - "python_full_version < '3.11'", - "python_full_version == '3.11.*'", - "python_full_version >= '3.12'", -] - -[[package]] -name = "absl-py" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7a/8f/fc001b92ecc467cc32ab38398bd0bfb45df46e7523bf33c2ad22a505f06e/absl-py-2.1.0.tar.gz", hash = "sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff", size = 118055 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/ad/e0d3c824784ff121c03cc031f944bc7e139a8f1870ffd2845cc2dd76f6c4/absl_py-2.1.0-py3-none-any.whl", hash = "sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308", size = 133706 }, -] - -[[package]] -name = "babel" -version = "2.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2a/74/f1bc80f23eeba13393b7222b11d95ca3af2c1e28edca18af487137eefed9/babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316", size = 9348104 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/20/bc79bc575ba2e2a7f70e8a1155618bb1301eaa5132a8271373a6903f73f8/babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", size = 9587599 }, -] - -[[package]] -name = "black" -version = "24.10.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d8/0d/cc2fb42b8c50d80143221515dd7e4766995bd07c56c9a3ed30baf080b6dc/black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875", size = 645813 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/f3/465c0eb5cddf7dbbfe1fecd9b875d1dcf51b88923cd2c1d7e9ab95c6336b/black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812", size = 1623211 }, - { url = "https://files.pythonhosted.org/packages/df/57/b6d2da7d200773fdfcc224ffb87052cf283cec4d7102fab450b4a05996d8/black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea", size = 1457139 }, - { url = "https://files.pythonhosted.org/packages/6e/c5/9023b7673904a5188f9be81f5e129fff69f51f5515655fbd1d5a4e80a47b/black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f", size = 1753774 }, - { url = "https://files.pythonhosted.org/packages/e1/32/df7f18bd0e724e0d9748829765455d6643ec847b3f87e77456fc99d0edab/black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e", size = 1414209 }, - { url = "https://files.pythonhosted.org/packages/c2/cc/7496bb63a9b06a954d3d0ac9fe7a73f3bf1cd92d7a58877c27f4ad1e9d41/black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad", size = 1607468 }, - { url = "https://files.pythonhosted.org/packages/2b/e3/69a738fb5ba18b5422f50b4f143544c664d7da40f09c13969b2fd52900e0/black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50", size = 1437270 }, - { url = "https://files.pythonhosted.org/packages/c9/9b/2db8045b45844665c720dcfe292fdaf2e49825810c0103e1191515fc101a/black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392", size = 1737061 }, - { url = "https://files.pythonhosted.org/packages/a3/95/17d4a09a5be5f8c65aa4a361444d95edc45def0de887810f508d3f65db7a/black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175", size = 1423293 }, - { url = "https://files.pythonhosted.org/packages/90/04/bf74c71f592bcd761610bbf67e23e6a3cff824780761f536512437f1e655/black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3", size = 1644256 }, - { url = "https://files.pythonhosted.org/packages/4c/ea/a77bab4cf1887f4b2e0bce5516ea0b3ff7d04ba96af21d65024629afedb6/black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65", size = 1448534 }, - { url = "https://files.pythonhosted.org/packages/4e/3e/443ef8bc1fbda78e61f79157f303893f3fddf19ca3c8989b163eb3469a12/black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f", size = 1761892 }, - { url = "https://files.pythonhosted.org/packages/52/93/eac95ff229049a6901bc84fec6908a5124b8a0b7c26ea766b3b8a5debd22/black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8", size = 1434796 }, - { url = "https://files.pythonhosted.org/packages/8d/a7/4b27c50537ebca8bec139b872861f9d2bf501c5ec51fcf897cb924d9e264/black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d", size = 206898 }, -] - -[[package]] -name = "botorch" -version = "0.12.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "gpytorch" }, - { name = "linear-operator" }, - { name = "mpmath" }, - { name = "multipledispatch" }, - { name = "pyro-ppl" }, - { name = "scipy" }, - { name = "torch" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cc/f7/49652f8470344605365a8d5a42f235571e1f3b1212e54981d10fa7479c10/botorch-0.12.0.tar.gz", hash = "sha256:6c810efa0a9cba4cca8ccd829ae8e9bee88ed4e08548c072a19b423c114bd211", size = 953000 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/a7/2d7f30b820fe61ccd0b239c3929920095a76cb2114db547af1df6c8284b4/botorch-0.12.0-py3-none-any.whl", hash = "sha256:7534566c1722d6997d4f6fd1ad0818f6a97d2975159b02ee94d410b34985347e", size = 644792 }, -] - -[[package]] -name = "certifi" -version = "2024.12.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 }, -] - -[[package]] -name = "cfgv" -version = "3.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 }, - { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 }, - { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 }, - { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 }, - { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 }, - { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 }, - { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 }, - { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 }, - { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 }, - { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 }, - { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 }, - { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 }, - { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 }, - { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, - { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, - { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, - { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, - { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, - { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, - { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, - { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, - { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, - { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, - { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, - { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, - { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, - { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, - { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, - { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, - { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, - { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, - { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, - { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, - { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, - { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, - { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, - { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, - { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, - { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, - { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, -] - -[[package]] -name = "click" -version = "8.1.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, -] - -[[package]] -name = "cloudpickle" -version = "3.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/97/c7/f746cadd08c4c08129215cf1b984b632f9e579fc781301e63da9e85c76c1/cloudpickle-3.1.0.tar.gz", hash = "sha256:81a929b6e3c7335c863c771d673d105f02efdb89dfaba0c90495d1c64796601b", size = 66155 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/41/e1d85ca3cab0b674e277c8c4f678cf66a91cd2cecf93df94353a606fe0db/cloudpickle-3.1.0-py3-none-any.whl", hash = "sha256:fe11acda67f61aaaec473e3afe030feb131d78a43461b718185363384f1ba12e", size = 22021 }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, -] - -[[package]] -name = "configspace" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "more-itertools" }, - { name = "numpy" }, - { name = "pyparsing" }, - { name = "scipy" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/af/e8/7834ed7b8f639f7c8d356dd598dccad6781d0315bdfa886eec2fcbe041dd/ConfigSpace-0.7.1.tar.gz", hash = "sha256:57b5b8e28ed6ee14ecf6206fdca43ca698ef63bc1531f081d482b26acf4edf1a", size = 1562121 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/f7/76e5c6b626edc03e145e25234c061a9d34cb94828c8742bf8162e5346aa5/ConfigSpace-0.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a2fe6ac3304ed11cf2a10c16810310471a5eae27e8c83ca0669e748ab363165", size = 3152867 }, - { url = "https://files.pythonhosted.org/packages/d5/d6/e02eb41e3212d2daba89668dd62647d41bd39addca6435b671792fb0a1ba/ConfigSpace-0.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:44b09f9c4acde4322fb5c2df27b8159f3645b1b6f9c3b33da24fda7b3db0d033", size = 2339129 }, - { url = "https://files.pythonhosted.org/packages/06/60/3b888979f7aebcfa920fca9a62d3e1a22bb367059d1fc18072c4e172d0a8/ConfigSpace-0.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa995137a90d00c1cb9a24db74d7fdd0ad64c272183e230134c26c9f85ba3dc3", size = 6317912 }, - { url = "https://files.pythonhosted.org/packages/38/bd/7a87ec226f2456fff6d6e2052eda4106c6cba771104e2cf8b1df48fffcad/ConfigSpace-0.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92288850a751446b8b0a3afd992a88618181a8c16e682dc123be5e7cbed603af", size = 6342052 }, - { url = "https://files.pythonhosted.org/packages/aa/aa/fa3fd1c95568ea7454f04ab16ced6a40e9407e1a1bf225f8faa6cccd4e1a/ConfigSpace-0.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:316779b41b9a9cbbdd83b2e1737f3de7b60616c0892f6cc782b305d0ebcd947c", size = 2164601 }, - { url = "https://files.pythonhosted.org/packages/f8/97/e62d112b1889a328cc09cdf0ccd7e563a169e267f544e2d6536b721cdfe1/ConfigSpace-0.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f09d91b1dd525269ed9aefd5880cf71ab27eace95200f3ce3824b43a3148a8b7", size = 3098901 }, - { url = "https://files.pythonhosted.org/packages/6c/c7/b7f3e487515b701fc0af30296efae8a2210a8ca1b83b02ce8972d28df138/ConfigSpace-0.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f0b08da62afd544568f339b4c3cc0945121b6785550db52c9685d3a0e30f93ce", size = 2314313 }, - { url = "https://files.pythonhosted.org/packages/34/f7/8de4575fa8cebe65542eb8cdecb06ca4fdb4bf9c3832e6c3f0ac6554d0ee/ConfigSpace-0.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee1e2066545a29a2fe174c9675a357898db852b6ee68f70703c312c612fe07a3", size = 6707758 }, - { url = "https://files.pythonhosted.org/packages/f0/15/99e0b0cde47fb94a72e6a1f94d3709bdd8bc46bec3ad779587358d167102/ConfigSpace-0.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41940de621cfb41aa16a329520b2d01db6587f8cafc77ad7cf81fef3d9f673e5", size = 6726572 }, - { url = "https://files.pythonhosted.org/packages/5c/42/a5c7f84510b666890281fe5ccbf566b237dc951895a47d213a37ea9f7e5a/ConfigSpace-0.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:3ebed7637a58a3d8df46a0e7123d2a804c8767764d8679245e47e266ed803895", size = 2151915 }, -] - -[[package]] -name = "contourpy" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/25/c2/fc7193cc5383637ff390a712e88e4ded0452c9fbcf84abe3de5ea3df1866/contourpy-1.3.1.tar.gz", hash = "sha256:dfd97abd83335045a913e3bcc4a09c0ceadbe66580cf573fe961f4a825efa699", size = 13465753 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/a3/80937fe3efe0edacf67c9a20b955139a1a622730042c1ea991956f2704ad/contourpy-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a045f341a77b77e1c5de31e74e966537bba9f3c4099b35bf4c2e3939dd54cdab", size = 268466 }, - { url = "https://files.pythonhosted.org/packages/82/1d/e3eaebb4aa2d7311528c048350ca8e99cdacfafd99da87bc0a5f8d81f2c2/contourpy-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:500360b77259914f7805af7462e41f9cb7ca92ad38e9f94d6c8641b089338124", size = 253314 }, - { url = "https://files.pythonhosted.org/packages/de/f3/d796b22d1a2b587acc8100ba8c07fb7b5e17fde265a7bb05ab967f4c935a/contourpy-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2f926efda994cdf3c8d3fdb40b9962f86edbc4457e739277b961eced3d0b4c1", size = 312003 }, - { url = "https://files.pythonhosted.org/packages/bf/f5/0e67902bc4394daee8daa39c81d4f00b50e063ee1a46cb3938cc65585d36/contourpy-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adce39d67c0edf383647a3a007de0a45fd1b08dedaa5318404f1a73059c2512b", size = 351896 }, - { url = "https://files.pythonhosted.org/packages/1f/d6/e766395723f6256d45d6e67c13bb638dd1fa9dc10ef912dc7dd3dcfc19de/contourpy-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abbb49fb7dac584e5abc6636b7b2a7227111c4f771005853e7d25176daaf8453", size = 320814 }, - { url = "https://files.pythonhosted.org/packages/a9/57/86c500d63b3e26e5b73a28b8291a67c5608d4aa87ebd17bd15bb33c178bc/contourpy-1.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0cffcbede75c059f535725c1680dfb17b6ba8753f0c74b14e6a9c68c29d7ea3", size = 324969 }, - { url = "https://files.pythonhosted.org/packages/b8/62/bb146d1289d6b3450bccc4642e7f4413b92ebffd9bf2e91b0404323704a7/contourpy-1.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab29962927945d89d9b293eabd0d59aea28d887d4f3be6c22deaefbb938a7277", size = 1265162 }, - { url = "https://files.pythonhosted.org/packages/18/04/9f7d132ce49a212c8e767042cc80ae390f728060d2eea47058f55b9eff1c/contourpy-1.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974d8145f8ca354498005b5b981165b74a195abfae9a8129df3e56771961d595", size = 1324328 }, - { url = "https://files.pythonhosted.org/packages/46/23/196813901be3f97c83ababdab1382e13e0edc0bb4e7b49a7bff15fcf754e/contourpy-1.3.1-cp310-cp310-win32.whl", hash = "sha256:ac4578ac281983f63b400f7fe6c101bedc10651650eef012be1ccffcbacf3697", size = 173861 }, - { url = "https://files.pythonhosted.org/packages/e0/82/c372be3fc000a3b2005061ca623a0d1ecd2eaafb10d9e883a2fc8566e951/contourpy-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:174e758c66bbc1c8576992cec9599ce8b6672b741b5d336b5c74e35ac382b18e", size = 218566 }, - { url = "https://files.pythonhosted.org/packages/12/bb/11250d2906ee2e8b466b5f93e6b19d525f3e0254ac8b445b56e618527718/contourpy-1.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e8b974d8db2c5610fb4e76307e265de0edb655ae8169e8b21f41807ccbeec4b", size = 269555 }, - { url = "https://files.pythonhosted.org/packages/67/71/1e6e95aee21a500415f5d2dbf037bf4567529b6a4e986594d7026ec5ae90/contourpy-1.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20914c8c973f41456337652a6eeca26d2148aa96dd7ac323b74516988bea89fc", size = 254549 }, - { url = "https://files.pythonhosted.org/packages/31/2c/b88986e8d79ac45efe9d8801ae341525f38e087449b6c2f2e6050468a42c/contourpy-1.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d40d37c1c3a4961b4619dd9d77b12124a453cc3d02bb31a07d58ef684d3d86", size = 313000 }, - { url = "https://files.pythonhosted.org/packages/c4/18/65280989b151fcf33a8352f992eff71e61b968bef7432fbfde3a364f0730/contourpy-1.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:113231fe3825ebf6f15eaa8bc1f5b0ddc19d42b733345eae0934cb291beb88b6", size = 352925 }, - { url = "https://files.pythonhosted.org/packages/f5/c7/5fd0146c93220dbfe1a2e0f98969293b86ca9bc041d6c90c0e065f4619ad/contourpy-1.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dbbc03a40f916a8420e420d63e96a1258d3d1b58cbdfd8d1f07b49fcbd38e85", size = 323693 }, - { url = "https://files.pythonhosted.org/packages/85/fc/7fa5d17daf77306840a4e84668a48ddff09e6bc09ba4e37e85ffc8e4faa3/contourpy-1.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a04ecd68acbd77fa2d39723ceca4c3197cb2969633836ced1bea14e219d077c", size = 326184 }, - { url = "https://files.pythonhosted.org/packages/ef/e7/104065c8270c7397c9571620d3ab880558957216f2b5ebb7e040f85eeb22/contourpy-1.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c414fc1ed8ee1dbd5da626cf3710c6013d3d27456651d156711fa24f24bd1291", size = 1268031 }, - { url = "https://files.pythonhosted.org/packages/e2/4a/c788d0bdbf32c8113c2354493ed291f924d4793c4a2e85b69e737a21a658/contourpy-1.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:31c1b55c1f34f80557d3830d3dd93ba722ce7e33a0b472cba0ec3b6535684d8f", size = 1325995 }, - { url = "https://files.pythonhosted.org/packages/a6/e6/a2f351a90d955f8b0564caf1ebe4b1451a3f01f83e5e3a414055a5b8bccb/contourpy-1.3.1-cp311-cp311-win32.whl", hash = "sha256:f611e628ef06670df83fce17805c344710ca5cde01edfdc72751311da8585375", size = 174396 }, - { url = "https://files.pythonhosted.org/packages/a8/7e/cd93cab453720a5d6cb75588cc17dcdc08fc3484b9de98b885924ff61900/contourpy-1.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:b2bdca22a27e35f16794cf585832e542123296b4687f9fd96822db6bae17bfc9", size = 219787 }, - { url = "https://files.pythonhosted.org/packages/37/6b/175f60227d3e7f5f1549fcb374592be311293132207e451c3d7c654c25fb/contourpy-1.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ffa84be8e0bd33410b17189f7164c3589c229ce5db85798076a3fa136d0e509", size = 271494 }, - { url = "https://files.pythonhosted.org/packages/6b/6a/7833cfae2c1e63d1d8875a50fd23371394f540ce809d7383550681a1fa64/contourpy-1.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805617228ba7e2cbbfb6c503858e626ab528ac2a32a04a2fe88ffaf6b02c32bc", size = 255444 }, - { url = "https://files.pythonhosted.org/packages/7f/b3/7859efce66eaca5c14ba7619791b084ed02d868d76b928ff56890d2d059d/contourpy-1.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade08d343436a94e633db932e7e8407fe7de8083967962b46bdfc1b0ced39454", size = 307628 }, - { url = "https://files.pythonhosted.org/packages/48/b2/011415f5e3f0a50b1e285a0bf78eb5d92a4df000553570f0851b6e309076/contourpy-1.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47734d7073fb4590b4a40122b35917cd77be5722d80683b249dac1de266aac80", size = 347271 }, - { url = "https://files.pythonhosted.org/packages/84/7d/ef19b1db0f45b151ac78c65127235239a8cf21a59d1ce8507ce03e89a30b/contourpy-1.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ba94a401342fc0f8b948e57d977557fbf4d515f03c67682dd5c6191cb2d16ec", size = 318906 }, - { url = "https://files.pythonhosted.org/packages/ba/99/6794142b90b853a9155316c8f470d2e4821fe6f086b03e372aca848227dd/contourpy-1.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa874e87e4a647fd2e4f514d5e91c7d493697127beb95e77d2f7561f6905bd9", size = 323622 }, - { url = "https://files.pythonhosted.org/packages/3c/0f/37d2c84a900cd8eb54e105f4fa9aebd275e14e266736778bb5dccbf3bbbb/contourpy-1.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1bf98051f1045b15c87868dbaea84f92408337d4f81d0e449ee41920ea121d3b", size = 1266699 }, - { url = "https://files.pythonhosted.org/packages/3a/8a/deb5e11dc7d9cc8f0f9c8b29d4f062203f3af230ba83c30a6b161a6effc9/contourpy-1.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:61332c87493b00091423e747ea78200659dc09bdf7fd69edd5e98cef5d3e9a8d", size = 1326395 }, - { url = "https://files.pythonhosted.org/packages/1a/35/7e267ae7c13aaf12322ccc493531f1e7f2eb8fba2927b9d7a05ff615df7a/contourpy-1.3.1-cp312-cp312-win32.whl", hash = "sha256:e914a8cb05ce5c809dd0fe350cfbb4e881bde5e2a38dc04e3afe1b3e58bd158e", size = 175354 }, - { url = "https://files.pythonhosted.org/packages/a1/35/c2de8823211d07e8a79ab018ef03960716c5dff6f4d5bff5af87fd682992/contourpy-1.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:08d9d449a61cf53033612cb368f3a1b26cd7835d9b8cd326647efe43bca7568d", size = 220971 }, - { url = "https://files.pythonhosted.org/packages/3e/4f/e56862e64b52b55b5ddcff4090085521fc228ceb09a88390a2b103dccd1b/contourpy-1.3.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b457d6430833cee8e4b8e9b6f07aa1c161e5e0d52e118dc102c8f9bd7dd060d6", size = 265605 }, - { url = "https://files.pythonhosted.org/packages/b0/2e/52bfeeaa4541889f23d8eadc6386b442ee2470bd3cff9baa67deb2dd5c57/contourpy-1.3.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb76c1a154b83991a3cbbf0dfeb26ec2833ad56f95540b442c73950af2013750", size = 315040 }, - { url = "https://files.pythonhosted.org/packages/52/94/86bfae441707205634d80392e873295652fc313dfd93c233c52c4dc07874/contourpy-1.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:44a29502ca9c7b5ba389e620d44f2fbe792b1fb5734e8b931ad307071ec58c53", size = 218221 }, -] - -[[package]] -name = "cycler" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, -] - -[[package]] -name = "cython" -version = "3.0.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/84/4d/b720d6000f4ca77f030bd70f12550820f0766b568e43f11af7f7ad9061aa/cython-3.0.11.tar.gz", hash = "sha256:7146dd2af8682b4ca61331851e6aebce9fe5158e75300343f80c07ca80b1faff", size = 2755544 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/7f/ab5796a0951328d7818b771c36fe7e1a2077cffa28c917d9fa4a642728c3/Cython-3.0.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:44292aae17524abb4b70a25111fe7dec1a0ad718711d47e3786a211d5408fdaa", size = 3100879 }, - { url = "https://files.pythonhosted.org/packages/d8/3b/67480e609537e9fc899864847910ded481b82d033fea1b7fcf85893a2fc4/Cython-3.0.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75d45fbc20651c1b72e4111149fed3b33d270b0a4fb78328c54d965f28d55e1", size = 3461957 }, - { url = "https://files.pythonhosted.org/packages/f0/89/b1ae45689abecca777f95462781a76e67ff46b55495a481ec5a73a739994/Cython-3.0.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d89a82937ce4037f092e9848a7bbcc65bc8e9fc9aef2bb74f5c15e7d21a73080", size = 3627062 }, - { url = "https://files.pythonhosted.org/packages/44/77/a651da74d5d41c6045bbe0b6990b1515bf4850cd7a8d8580333c90dfce2e/Cython-3.0.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a8ea2e7e2d3bc0d8630dafe6c4a5a89485598ff8a61885b74f8ed882597efd5", size = 3680431 }, - { url = "https://files.pythonhosted.org/packages/59/45/60e7e8db93c3eb8b2af8c64020c1fa502e355f4b762886a24d46e433f395/Cython-3.0.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cee29846471ce60226b18e931d8c1c66a158db94853e3e79bc2da9bd22345008", size = 3497314 }, - { url = "https://files.pythonhosted.org/packages/f8/0b/6919025958926625319f83523ee7f45e7e7ae516b8054dcff6eb710daf32/Cython-3.0.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eeb6860b0f4bfa402de8929833fe5370fa34069c7ebacb2d543cb017f21fb891", size = 3709091 }, - { url = "https://files.pythonhosted.org/packages/52/3c/c21b9b9271dfaa46fa2938de730f62fc94b9c2ec25ec400585e372f35dcd/Cython-3.0.11-cp310-cp310-win32.whl", hash = "sha256:3699391125ab344d8d25438074d1097d9ba0fb674d0320599316cfe7cf5f002a", size = 2576110 }, - { url = "https://files.pythonhosted.org/packages/f9/de/19fdd1c7a52e0534bf5f544e0346c15d71d20338dbd013117f763b94613f/Cython-3.0.11-cp310-cp310-win_amd64.whl", hash = "sha256:d02f4ebe15aac7cdacce1a628e556c1983f26d140fd2e0ac5e0a090e605a2d38", size = 2776386 }, - { url = "https://files.pythonhosted.org/packages/f8/73/e55be864199cd674cb3426a052726c205589b1ac66fb0090e7fe793b60b3/Cython-3.0.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75ba1c70b6deeaffbac123856b8d35f253da13552207aa969078611c197377e4", size = 3113599 }, - { url = "https://files.pythonhosted.org/packages/09/c9/537108d0980beffff55336baaf8b34162ad0f3f33ededcb5db07069bc8ef/Cython-3.0.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af91497dc098718e634d6ec8f91b182aea6bb3690f333fc9a7777bc70abe8810", size = 3441131 }, - { url = "https://files.pythonhosted.org/packages/93/03/e330b241ad8aa12bb9d98b58fb76d4eb7dcbe747479aab5c29fce937b9e7/Cython-3.0.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3999fb52d3328a6a5e8c63122b0a8bd110dfcdb98dda585a3def1426b991cba7", size = 3595065 }, - { url = "https://files.pythonhosted.org/packages/4a/84/a3c40f2c0439d425daa5aa4e3a6fdbbb41341a14a6fd97f94906f528d9a4/Cython-3.0.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d566a4e09b8979be8ab9f843bac0dd216c81f5e5f45661a9b25cd162ed80508c", size = 3641667 }, - { url = "https://files.pythonhosted.org/packages/6d/93/bdb61e0254ed8f1d21a14088a473584ecb1963d68dba5682158aa45c70ef/Cython-3.0.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:46aec30f217bdf096175a1a639203d44ac73a36fe7fa3dd06bd012e8f39eca0f", size = 3503650 }, - { url = "https://files.pythonhosted.org/packages/f8/62/0da548144c71176155ff5355c4cc40fb28b9effe22e830b55cec8072bdf2/Cython-3.0.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ddd1fe25af330f4e003421636746a546474e4ccd8f239f55d2898d80983d20ed", size = 3709662 }, - { url = "https://files.pythonhosted.org/packages/56/d3/d9c9eaf3611a9fe5256266d07b6a5f9069aa84d20d9f6aa5824289513315/Cython-3.0.11-cp311-cp311-win32.whl", hash = "sha256:221de0b48bf387f209003508e602ce839a80463522fc6f583ad3c8d5c890d2c1", size = 2577870 }, - { url = "https://files.pythonhosted.org/packages/fd/10/236fcc0306f85a2db1b8bc147aea714b66a2f27bac4d9e09e5b2c5d5dcca/Cython-3.0.11-cp311-cp311-win_amd64.whl", hash = "sha256:3ff8ac1f0ecd4f505db4ab051e58e4531f5d098b6ac03b91c3b902e8d10c67b3", size = 2785053 }, - { url = "https://files.pythonhosted.org/packages/58/50/fbb23239efe2183e4eaf76689270d6f5b3bbcf9be9ad1eb97cc34349e6fc/Cython-3.0.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:11996c40c32abf843ba652a6d53cb15944c88d91f91fc4e6f0028f5df8a8f8a1", size = 3141274 }, - { url = "https://files.pythonhosted.org/packages/87/e5/76379edb21fd5bb9e2aaa1d305492bc35bba96dfb51f5d96867d9863b6df/Cython-3.0.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63f2c892e9f9c1698ecfee78205541623eb31cd3a1b682668be7ac12de94aa8e", size = 3340904 }, - { url = "https://files.pythonhosted.org/packages/9a/ef/44af6aded89444dc45f4466ff207a05d3376c641cf1146c03fd14c55ae64/Cython-3.0.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b14c24f1dc4c4c9d997cca8d1b7fb01187a218aab932328247dcf5694a10102", size = 3514052 }, - { url = "https://files.pythonhosted.org/packages/e0/d5/ef8c7b6aa7a83c508f5c3bf0dfb9eb0a2a9be910c0b1f205f842128269c3/Cython-3.0.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8eed5c015685106db15dd103fd040948ddca9197b1dd02222711815ea782a27", size = 3573721 }, - { url = "https://files.pythonhosted.org/packages/e5/4a/58d6c208563504a35febff94904bb291b368a8b0f28a5e0593c770967caa/Cython-3.0.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780f89c95b8aec1e403005b3bf2f0a2afa060b3eba168c86830f079339adad89", size = 3393594 }, - { url = "https://files.pythonhosted.org/packages/a0/92/a60a400be286dc661609da9db903680bba1423362000b689cf8ef0aec811/Cython-3.0.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a690f2ff460682ea985e8d38ec541be97e0977fa0544aadc21efc116ff8d7579", size = 3601319 }, - { url = "https://files.pythonhosted.org/packages/ac/11/f02fc24d1a071b93e1d07497b0a528687b1f93bb4945c635119480fab3c0/Cython-3.0.11-cp312-cp312-win32.whl", hash = "sha256:2252b5aa57621848e310fe7fa6f7dce5f73aa452884a183d201a8bcebfa05a00", size = 2608335 }, - { url = "https://files.pythonhosted.org/packages/35/00/78ffea3a0ab176267a25ff049518b2582db7ac265bbf27944243d1a81ce2/Cython-3.0.11-cp312-cp312-win_amd64.whl", hash = "sha256:da394654c6da15c1d37f0b7ec5afd325c69a15ceafee2afba14b67a5df8a82c8", size = 2792586 }, - { url = "https://files.pythonhosted.org/packages/43/39/bdbec9142bc46605b54d674bf158a78b191c2b75be527c6dcf3e6dfe90b8/Cython-3.0.11-py2.py3-none-any.whl", hash = "sha256:0e25f6425ad4a700d7f77cd468da9161e63658837d1bc34861a9861a4ef6346d", size = 1171267 }, -] - -[[package]] -name = "decopatch" -version = "1.4.10" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "makefun" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/90/4c/8ca1f193428cbc4d63d0f07db9b8bd96be2db8ee5deefa93e7e8a28f2812/decopatch-1.4.10.tar.gz", hash = "sha256:957f49c93f4150182c23f8fb51d13bb3213e0f17a79e09c8cca7057598b55720", size = 69538 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/fa/8e4a51e1afda8d4bd73d784bfe4a60cfdeeced9bea419eff5c271180377e/decopatch-1.4.10-py2.py3-none-any.whl", hash = "sha256:e151f7f93de2b1b3fd3f3272dcc7cefd1a69f68ec1c2d8e288ecd9deb36dc5f7", size = 18015 }, -] - -[[package]] -name = "distlib" -version = "0.3.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, -] - -[[package]] -name = "exceptiongroup" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, -] - -[[package]] -name = "filelock" -version = "3.16.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, -] - -[[package]] -name = "fonttools" -version = "4.55.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/61/a300d1574dc381393424047c0396a0e213db212e28361123af9830d71a8d/fonttools-4.55.3.tar.gz", hash = "sha256:3983313c2a04d6cc1fe9251f8fc647754cf49a61dac6cb1e7249ae67afaafc45", size = 3498155 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/f3/9ac8c6705e4a0ff3c29e524df1caeee6f2987b02fb630129f21cc99a8212/fonttools-4.55.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1dcc07934a2165ccdc3a5a608db56fb3c24b609658a5b340aee4ecf3ba679dc0", size = 2769857 }, - { url = "https://files.pythonhosted.org/packages/d8/24/e8b8edd280bdb7d0ecc88a5d952b1dec2ee2335be71cc5a33c64871cdfe8/fonttools-4.55.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f7d66c15ba875432a2d2fb419523f5d3d347f91f48f57b8b08a2dfc3c39b8a3f", size = 2299705 }, - { url = "https://files.pythonhosted.org/packages/f8/9e/e1ba20bd3b71870207fd45ca3b90208a7edd8ae3b001081dc31c45adb017/fonttools-4.55.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e4ae3592e62eba83cd2c4ccd9462dcfa603ff78e09110680a5444c6925d841", size = 4576104 }, - { url = "https://files.pythonhosted.org/packages/34/db/d423bc646e6703fe3e6aea0edd22a2df47b9d188c5f7f1b49070be4d2205/fonttools-4.55.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62d65a3022c35e404d19ca14f291c89cc5890032ff04f6c17af0bd1927299674", size = 4618282 }, - { url = "https://files.pythonhosted.org/packages/75/a0/e5062ac960a385b984ba74e7b55132e7f2c65e449e8330ab0f595407a3de/fonttools-4.55.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d342e88764fb201286d185093781bf6628bbe380a913c24adf772d901baa8276", size = 4570539 }, - { url = "https://files.pythonhosted.org/packages/1f/33/0d744ff518ebe50020b63e5018b8b278efd6a930c1d2eedda7defc42153b/fonttools-4.55.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd68c87a2bfe37c5b33bcda0fba39b65a353876d3b9006fde3adae31f97b3ef5", size = 4742411 }, - { url = "https://files.pythonhosted.org/packages/7e/6c/2f768652dba6b801f1567fc5d1829cda369bcd6e95e315a91e628f91c702/fonttools-4.55.3-cp310-cp310-win32.whl", hash = "sha256:1bc7ad24ff98846282eef1cbeac05d013c2154f977a79886bb943015d2b1b261", size = 2175132 }, - { url = "https://files.pythonhosted.org/packages/19/d1/4dcd865360fb2c499749a913fe80e41c26e8ae18629d87dfffa3de27e831/fonttools-4.55.3-cp310-cp310-win_amd64.whl", hash = "sha256:b54baf65c52952db65df39fcd4820668d0ef4766c0ccdf32879b77f7c804d5c5", size = 2219430 }, - { url = "https://files.pythonhosted.org/packages/4b/18/14be25545600bd100e5b74a3ac39089b7c1cb403dc513b7ca348be3381bf/fonttools-4.55.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c4491699bad88efe95772543cd49870cf756b019ad56294f6498982408ab03e", size = 2771005 }, - { url = "https://files.pythonhosted.org/packages/b2/51/2e1a5d3871cd7c2ae2054b54e92604e7d6abc3fd3656e9583c399648fe1c/fonttools-4.55.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5323a22eabddf4b24f66d26894f1229261021dacd9d29e89f7872dd8c63f0b8b", size = 2300654 }, - { url = "https://files.pythonhosted.org/packages/73/1a/50109bb2703bc6f774b52ea081db21edf2a9fa4b6d7485faadf9d1b997e9/fonttools-4.55.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5480673f599ad410695ca2ddef2dfefe9df779a9a5cda89503881e503c9c7d90", size = 4877541 }, - { url = "https://files.pythonhosted.org/packages/5d/52/c0b9857fa075da1b8806c5dc2d8342918a8cc2065fd14fbddb3303282693/fonttools-4.55.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da9da6d65cd7aa6b0f806556f4985bcbf603bf0c5c590e61b43aa3e5a0f822d0", size = 4906304 }, - { url = "https://files.pythonhosted.org/packages/0b/1b/55f85c7e962d295e456d5209581c919620ee3e877b95cd86245187a5050f/fonttools-4.55.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e894b5bd60d9f473bed7a8f506515549cc194de08064d829464088d23097331b", size = 4888087 }, - { url = "https://files.pythonhosted.org/packages/83/13/6f2809c612ea2ac51391f92468ff861c63473601530fca96458b453212bf/fonttools-4.55.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aee3b57643827e237ff6ec6d28d9ff9766bd8b21e08cd13bff479e13d4b14765", size = 5056958 }, - { url = "https://files.pythonhosted.org/packages/c1/28/d0ea9e872fa4208b9dfca686e1dd9ca22f6c9ef33ecff2f0ebc2dbe7c29b/fonttools-4.55.3-cp311-cp311-win32.whl", hash = "sha256:eb6ca911c4c17eb51853143624d8dc87cdcdf12a711fc38bf5bd21521e79715f", size = 2173939 }, - { url = "https://files.pythonhosted.org/packages/be/36/d74ae1020bc41a1dff3e6f5a99f646563beecb97e386d27abdac3ba07650/fonttools-4.55.3-cp311-cp311-win_amd64.whl", hash = "sha256:6314bf82c54c53c71805318fcf6786d986461622dd926d92a465199ff54b1b72", size = 2220363 }, - { url = "https://files.pythonhosted.org/packages/89/58/fbcf5dff7e3ea844bb00c4d806ca1e339e1f2dce5529633bf4842c0c9a1f/fonttools-4.55.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f9e736f60f4911061235603a6119e72053073a12c6d7904011df2d8fad2c0e35", size = 2765380 }, - { url = "https://files.pythonhosted.org/packages/81/dd/da6e329e51919b4f421c8738f3497e2ab08c168e76aaef7b6d5351862bdf/fonttools-4.55.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a8aa2c5e5b8b3bcb2e4538d929f6589a5c6bdb84fd16e2ed92649fb5454f11c", size = 2297940 }, - { url = "https://files.pythonhosted.org/packages/00/44/f5ee560858425c99ef07e04919e736db09d6416408e5a8d3bbfb4a6623fd/fonttools-4.55.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07f8288aacf0a38d174445fc78377a97fb0b83cfe352a90c9d9c1400571963c7", size = 4793327 }, - { url = "https://files.pythonhosted.org/packages/24/da/0a001926d791c55e29ac3c52964957a20dbc1963615446b568b7432891c3/fonttools-4.55.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8d5e8916c0970fbc0f6f1bece0063363bb5857a7f170121a4493e31c3db3314", size = 4865624 }, - { url = "https://files.pythonhosted.org/packages/3d/d8/1edd8b13a427a9fb6418373437caa586c0caa57f260af8e0548f4d11e340/fonttools-4.55.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae3b6600565b2d80b7c05acb8e24d2b26ac407b27a3f2e078229721ba5698427", size = 4774166 }, - { url = "https://files.pythonhosted.org/packages/9c/ec/ade054097976c3d6debc9032e09a351505a0196aa5493edf021be376f75e/fonttools-4.55.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:54153c49913f45065c8d9e6d0c101396725c5621c8aee744719300f79771d75a", size = 5001832 }, - { url = "https://files.pythonhosted.org/packages/e2/cd/233f0e31ad799bb91fc78099c8b4e5ec43b85a131688519640d6bae46f6a/fonttools-4.55.3-cp312-cp312-win32.whl", hash = "sha256:827e95fdbbd3e51f8b459af5ea10ecb4e30af50221ca103bea68218e9615de07", size = 2162228 }, - { url = "https://files.pythonhosted.org/packages/46/45/a498b5291f6c0d91b2394b1ed7447442a57d1c9b9cf8f439aee3c316a56e/fonttools-4.55.3-cp312-cp312-win_amd64.whl", hash = "sha256:e6e8766eeeb2de759e862004aa11a9ea3d6f6d5ec710551a88b476192b64fd54", size = 2209118 }, - { url = "https://files.pythonhosted.org/packages/99/3b/406d17b1f63e04a82aa621936e6e1c53a8c05458abd66300ac85ea7f9ae9/fonttools-4.55.3-py3-none-any.whl", hash = "sha256:f412604ccbeee81b091b420272841e5ec5ef68967a9790e80bffd0e30b8e2977", size = 1111638 }, -] - -[[package]] -name = "fsspec" -version = "2024.12.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/11/de70dee31455c546fbc88301971ec03c328f3d1138cfba14263f651e9551/fsspec-2024.12.0.tar.gz", hash = "sha256:670700c977ed2fb51e0d9f9253177ed20cbde4a3e5c0283cc5385b5870c8533f", size = 291600 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/86/5486b0188d08aa643e127774a99bac51ffa6cf343e3deb0583956dca5b22/fsspec-2024.12.0-py3-none-any.whl", hash = "sha256:b520aed47ad9804237ff878b504267a3b0b441e97508bd6d2d8774e3db85cee2", size = 183862 }, -] - -[[package]] -name = "future" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/b2/4140c69c6a66432916b26158687e821ba631a4c9273c474343badf84d3ba/future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05", size = 1228490 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326 }, -] - -[[package]] -name = "ghp-import" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "python-dateutil" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034 }, -] - -[[package]] -name = "gpytorch" -version = "1.13" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jaxtyping" }, - { name = "linear-operator" }, - { name = "mpmath" }, - { name = "scikit-learn" }, - { name = "scipy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1c/65/9b66ad6faaff5ef9a3f7896d964419cb11c5b1776eb212cbe9f9d1502e7a/gpytorch-1.13.tar.gz", hash = "sha256:f4a488633a2a7a4ab37d12553d1d1dd39690043dbceef14ca428b7d5f89f73ba", size = 2472751 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/77/e19f17291dba1ad21206b632f2e638376cdfccb7cce852e4c34692c6f9f0/gpytorch-1.13-py3-none-any.whl", hash = "sha256:97da5b07a524952612e8d265ec89d4a5ec0ef0587e76d6178961ce26ef9679d1", size = 277804 }, -] - -[[package]] -name = "grakel" -version = "0.1.10" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cython" }, - { name = "future" }, - { name = "joblib" }, - { name = "numpy" }, - { name = "scikit-learn" }, - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7d/8a/e6b4d4bcb1e34c91248675984ee1ebb96e57a2254f955dfde8fd1da5043a/GraKeL-0.1.10.tar.gz", hash = "sha256:0c87f716d8cd69741cf1aa63a230a74c3a8957f8485b2a18689274934ef8fd51", size = 1004389 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/e3/60997210ba1726d7a321d90dd885e598835c554076de399cc2a85a88ef0f/GraKeL-0.1.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:10e42a1d90f07da217c74b80ef663a081f12c36d2754f3245c965a4bb2b1bb14", size = 1040988 }, - { url = "https://files.pythonhosted.org/packages/77/ab/93a4d0e41168afa832a8da0932a7d726f57cdca19082e7d76c8eef37d187/GraKeL-0.1.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e86dfa967d8ea7b22b0bd7d4a5bb49cc9849ef230811c31d9509087bf9bdfbd6", size = 719314 }, - { url = "https://files.pythonhosted.org/packages/fd/6d/5cb7a1f5686895c5a8cff391f3d52ae43f95e2f34c4a6636f3124a688485/GraKeL-0.1.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7afd00a1272a78e2f11df86f159e347dee212e0a18c02ed1a93dbdc0f9abad8", size = 1901551 }, - { url = "https://files.pythonhosted.org/packages/f7/94/f1c66c73f3cea179a65e51bfd2567bd83990c68976c0d07cb6f39e6ee049/GraKeL-0.1.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8012ac44fc779a1d89b7d903ac68d3ab72b333da91736016e1841c939744a471", size = 1933615 }, - { url = "https://files.pythonhosted.org/packages/8b/ac/62a02d45df0165312bdf022834fe97f8dc1a479c8c2a7daebfdb7282ba9d/GraKeL-0.1.10-cp310-cp310-win_amd64.whl", hash = "sha256:e44a72c503967890ff4923654b9c87f1187ff63e217acd0852613ca4355c83d1", size = 678978 }, - { url = "https://files.pythonhosted.org/packages/cf/59/54726f4b988d859c97b9017af639090904c2bef04dd288d99733fcc1489e/GraKeL-0.1.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:10fa86a4b884ae9b873d2ca5fd55ec1cd9e138a5e2fceeeafaf3f1e3b3571c8d", size = 1042774 }, - { url = "https://files.pythonhosted.org/packages/bd/5a/0f1fa0b532bd56dabdfce3aaacd0952aa74edfc4137b5b33d55d5fcf9323/GraKeL-0.1.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f66f171801c8a0133a0be540630c8424345502548a5279af481df3215bcced24", size = 720092 }, - { url = "https://files.pythonhosted.org/packages/06/a9/e6da6ba79c7a961c4ef1007f67d4145fa52a1d4c9e29c018dda0f2ba5f22/GraKeL-0.1.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aed2fd06e793d8330667daebf9b93a5f776d6d45403b179d74e16df0168d3360", size = 2000266 }, - { url = "https://files.pythonhosted.org/packages/c9/9c/4e727cf567187d31b8d0290c8eb2421bf387c7d72bb83b258779641356bd/GraKeL-0.1.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0826b67750517e01c658dad4e068f71e92ae9384bcbbf34f156e698229bd262e", size = 2029461 }, - { url = "https://files.pythonhosted.org/packages/6b/29/714f7a3d09d3defaea6ef556bd8ea04abd2eb356479847cba459ef3bfe39/GraKeL-0.1.10-cp311-cp311-win_amd64.whl", hash = "sha256:a73f54c5c3d0a4c8b0967ef67ff12be286d06997b1ee365a531aeef1f632139b", size = 679135 }, -] - -[[package]] -name = "griffe" -version = "1.5.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/22/9b/0bc9d53ed6628aae43223dd3c081637da54f66ed17a8c1d9fd36ee5da244/griffe-1.5.4.tar.gz", hash = "sha256:073e78ad3e10c8378c2f798bd4ef87b92d8411e9916e157fd366a17cc4fd4e52", size = 389376 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/29/d0f156c076ec71eb485e70cbcde4872e3c045cda965a48d1d938aa3d9f76/griffe-1.5.4-py3-none-any.whl", hash = "sha256:ed33af890586a5bebc842fcb919fc694b3dc1bc55b7d9e0228de41ce566b4a1d", size = 128102 }, -] - -[[package]] -name = "grpcio" -version = "1.68.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/ec/b76ff6d86bdfd1737a5ec889394b54c18b1ec3832d91041e25023fbcb67d/grpcio-1.68.1.tar.gz", hash = "sha256:44a8502dd5de653ae6a73e2de50a401d84184f0331d0ac3daeb044e66d5c5054", size = 12694654 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/88/d1ac9676a0809e3efec154d45246474ec12a4941686da71ffb3d34190294/grpcio-1.68.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:d35740e3f45f60f3c37b1e6f2f4702c23867b9ce21c6410254c9c682237da68d", size = 5171054 }, - { url = "https://files.pythonhosted.org/packages/ec/cb/94ca41e100201fee8876a4b44d64e43ac7405929909afe1fa943d65b25ef/grpcio-1.68.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:d99abcd61760ebb34bdff37e5a3ba333c5cc09feda8c1ad42547bea0416ada78", size = 11078566 }, - { url = "https://files.pythonhosted.org/packages/d5/b0/ad4c66f2e3181b4eab99885686c960c403ae2300bacfe427526282facc07/grpcio-1.68.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:f8261fa2a5f679abeb2a0a93ad056d765cdca1c47745eda3f2d87f874ff4b8c9", size = 5690039 }, - { url = "https://files.pythonhosted.org/packages/67/1e/f5d3410674d021831c9fef2d1d7ca2357b08d09c840ad4e054ea8ffc302e/grpcio-1.68.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0feb02205a27caca128627bd1df4ee7212db051019a9afa76f4bb6a1a80ca95e", size = 6317470 }, - { url = "https://files.pythonhosted.org/packages/91/93/701d5f33b163a621c8f2d4453f9e22f6c14e996baed54118d0dea93fc8c7/grpcio-1.68.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:919d7f18f63bcad3a0f81146188e90274fde800a94e35d42ffe9eadf6a9a6330", size = 5941884 }, - { url = "https://files.pythonhosted.org/packages/67/44/06917ffaa35ca463b93dde60f324015fe4192312b0f4dd0faec061e7ca7f/grpcio-1.68.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:963cc8d7d79b12c56008aabd8b457f400952dbea8997dd185f155e2f228db079", size = 6646332 }, - { url = "https://files.pythonhosted.org/packages/d4/94/074db039532687ec8ef07ebbcc747c46547c94329016e22b97d97b9e5f3b/grpcio-1.68.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ccf2ebd2de2d6661e2520dae293298a3803a98ebfc099275f113ce1f6c2a80f1", size = 6212515 }, - { url = "https://files.pythonhosted.org/packages/c5/f2/0c939264c36c6038fae1732a2a3e01a7075ba171a2154d86842ee0ac9b0a/grpcio-1.68.1-cp310-cp310-win32.whl", hash = "sha256:2cc1fd04af8399971bcd4f43bd98c22d01029ea2e56e69c34daf2bf8470e47f5", size = 3650459 }, - { url = "https://files.pythonhosted.org/packages/b6/90/b0e9278e88f747879d13b79fb893c9acb381fb90541ad9e416c7816c5eaf/grpcio-1.68.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2e743e51cb964b4975de572aa8fb95b633f496f9fcb5e257893df3be854746", size = 4399144 }, - { url = "https://files.pythonhosted.org/packages/fe/0d/fde5a5777d65696c39bb3e622fe1239dd0a878589bf6c5066980e7d19154/grpcio-1.68.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:55857c71641064f01ff0541a1776bfe04a59db5558e82897d35a7793e525774c", size = 5180919 }, - { url = "https://files.pythonhosted.org/packages/07/fd/e5fa75b5ddf5d9f16606196973f9c2b4b1adf5a1735117eb7129fc33d2ec/grpcio-1.68.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4b177f5547f1b995826ef529d2eef89cca2f830dd8b2c99ffd5fde4da734ba73", size = 11150922 }, - { url = "https://files.pythonhosted.org/packages/86/1e/aaf5a1dae87fe47f277c5a1be72b31d2c209d095bebb0ce1d2df5cb8779c/grpcio-1.68.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:3522c77d7e6606d6665ec8d50e867f13f946a4e00c7df46768f1c85089eae515", size = 5685685 }, - { url = "https://files.pythonhosted.org/packages/a9/69/c4fdf87d5c5696207e2ed232e4bdde656d8c99ba91f361927f3f06aa41ca/grpcio-1.68.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d1fae6bbf0816415b81db1e82fb3bf56f7857273c84dcbe68cbe046e58e1ccd", size = 6316535 }, - { url = "https://files.pythonhosted.org/packages/6f/c6/539660516ea7db7bc3d39e07154512ae807961b14ec6b5b0c58d15657ff1/grpcio-1.68.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:298ee7f80e26f9483f0b6f94cc0a046caf54400a11b644713bb5b3d8eb387600", size = 5939920 }, - { url = "https://files.pythonhosted.org/packages/38/f3/97a74dc4dd95bf195168d6da2ca4731ab7d3d0b03078f2833b4ff9c4f48f/grpcio-1.68.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cbb5780e2e740b6b4f2d208e90453591036ff80c02cc605fea1af8e6fc6b1bbe", size = 6644770 }, - { url = "https://files.pythonhosted.org/packages/cb/36/79a5e04073e58106aff442509a0c459151fa4f43202395db3eb8f77b78e9/grpcio-1.68.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ddda1aa22495d8acd9dfbafff2866438d12faec4d024ebc2e656784d96328ad0", size = 6211743 }, - { url = "https://files.pythonhosted.org/packages/73/0f/2250f4a0de1a0bec0726c47a021cbf71af6105f512ecaf67703e2eb1ad2f/grpcio-1.68.1-cp311-cp311-win32.whl", hash = "sha256:b33bd114fa5a83f03ec6b7b262ef9f5cac549d4126f1dc702078767b10c46ed9", size = 3650734 }, - { url = "https://files.pythonhosted.org/packages/4b/29/061c93a35f498238dc35eb8fb039ce168aa99cac2f0f1ce0c8a0a4bdb274/grpcio-1.68.1-cp311-cp311-win_amd64.whl", hash = "sha256:7f20ebec257af55694d8f993e162ddf0d36bd82d4e57f74b31c67b3c6d63d8b2", size = 4400816 }, - { url = "https://files.pythonhosted.org/packages/f5/15/674a1468fef234fa996989509bbdfc0d695878cbb385b9271f5d690d5cd3/grpcio-1.68.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:8829924fffb25386995a31998ccbbeaa7367223e647e0122043dfc485a87c666", size = 5148351 }, - { url = "https://files.pythonhosted.org/packages/62/f5/edce368682d6d0b3573b883b134df022a44b1c888ea416dd7d78d480ab24/grpcio-1.68.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3aed6544e4d523cd6b3119b0916cef3d15ef2da51e088211e4d1eb91a6c7f4f1", size = 11127559 }, - { url = "https://files.pythonhosted.org/packages/ce/14/a6fde3114eafd9e4e345d1ebd0291c544d83b22f0554b1678a2968ae39e1/grpcio-1.68.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:4efac5481c696d5cb124ff1c119a78bddbfdd13fc499e3bc0ca81e95fc573684", size = 5645221 }, - { url = "https://files.pythonhosted.org/packages/21/21/d1865bd6a22f9a26217e4e1b35f9105f7a0cdfb7a5fffe8be48e1a1afafc/grpcio-1.68.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ab2d912ca39c51f46baf2a0d92aa265aa96b2443266fc50d234fa88bf877d8e", size = 6292270 }, - { url = "https://files.pythonhosted.org/packages/3a/f6/19798be6c3515a7b1fb9570198c91710472e2eb21f1900109a76834829e3/grpcio-1.68.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c87ce2a97434dffe7327a4071839ab8e8bffd0054cc74cbe971fba98aedd60", size = 5905978 }, - { url = "https://files.pythonhosted.org/packages/9b/43/c3670a657445cd55be1246f64dbc3a6a33cab0f0141c5836df2e04f794c8/grpcio-1.68.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e4842e4872ae4ae0f5497bf60a0498fa778c192cc7a9e87877abd2814aca9475", size = 6630444 }, - { url = "https://files.pythonhosted.org/packages/80/69/fbbebccffd266bea4268b685f3e8e03613405caba69e93125dc783036465/grpcio-1.68.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:255b1635b0ed81e9f91da4fcc8d43b7ea5520090b9a9ad9340d147066d1d3613", size = 6200324 }, - { url = "https://files.pythonhosted.org/packages/65/5c/27a26c21916f94f0c1585111974a5d5a41d8420dcb42c2717ee514c97a97/grpcio-1.68.1-cp312-cp312-win32.whl", hash = "sha256:7dfc914cc31c906297b30463dde0b9be48e36939575eaf2a0a22a8096e69afe5", size = 3638381 }, - { url = "https://files.pythonhosted.org/packages/a3/ba/ba6b65ccc93c7df1031c6b41e45b79a5a37e46b81d816bb3ea68ba476d77/grpcio-1.68.1-cp312-cp312-win_amd64.whl", hash = "sha256:a0c8ddabef9c8f41617f213e527254c41e8b96ea9d387c632af878d05db9229c", size = 4389959 }, -] - -[[package]] -name = "identify" -version = "2.6.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/a5/7de3053524ee006b91099968d7ecb2e0b420f7ae728094394c33e8a2a2b9/identify-2.6.4.tar.gz", hash = "sha256:285a7d27e397652e8cafe537a6cc97dd470a970f48fb2e9d979aa38eae5513ac", size = 99209 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/9d/52f036403ae86474804f699c0d084b4b071e333a390b20269bb8accc65e0/identify-2.6.4-py2.py3-none-any.whl", hash = "sha256:993b0f01b97e0568c179bb9196391ff391bfb88a99099dbf5ce392b68f42d0af", size = 99072 }, -] - -[[package]] -name = "idna" -version = "3.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, -] - -[[package]] -name = "ifbo" -version = "0.3.11" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cloudpickle" }, - { name = "numpy" }, - { name = "requests" }, - { name = "scipy" }, - { name = "submitit" }, - { name = "torch" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bb/31/2c8b7271097629d474bac7c46f9caad673e33e2d7d1fb35e84655358e366/ifbo-0.3.11.tar.gz", hash = "sha256:473da139225ec2ffdbf7f92718acb049b20f0919a4e00ae9173a99134c3fc3d4", size = 710936 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/5b/cf97e109e572a3f204175dffe19cdc7101c229d335ba4c7ebe0cb6073277/ifBO-0.3.11-py3-none-any.whl", hash = "sha256:1927aae7fc52bcdab899bdef9ee7377e9f8dc4010bf2830b203d2e3748245d4d", size = 713420 }, -] - -[[package]] -name = "importlib-metadata" -version = "8.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514 }, -] - -[[package]] -name = "importlib-resources" -version = "6.4.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/be/f3e8c6081b684f176b761e6a2fef02a0be939740ed6f54109a2951d806f3/importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065", size = 43372 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/6a/4604f9ae2fa62ef47b9de2fa5ad599589d28c9fd1d335f32759813dfa91e/importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717", size = 36115 }, -] - -[[package]] -name = "iniconfig" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, -] - -[[package]] -name = "jaxtyping" -version = "0.2.19" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "typeguard" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6b/9f/1f8b63965b795803c5bc38afcfcf8ce8d922ffc65a26d4a08071279b0403/jaxtyping-0.2.19.tar.gz", hash = "sha256:21ff4c3caec6781cadfe980b019dde856c1011e17d11dfe8589298040056325a", size = 16653 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/8f/b39d40fef81d7def4898ad11bbe686185e09cb9e39db905e2afc77f1d350/jaxtyping-0.2.19-py3-none-any.whl", hash = "sha256:651352032799d422987e783fd1b77699b53c3bb28ffa644bbca5f75ec4fbb843", size = 24145 }, -] - -[[package]] -name = "jinja2" -version = "3.1.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, -] - -[[package]] -name = "joblib" -version = "1.4.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/64/33/60135848598c076ce4b231e1b1895170f45fbcaeaa2c9d5e38b04db70c35/joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e", size = 2116621 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", size = 301817 }, -] - -[[package]] -name = "kiwisolver" -version = "1.4.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/5f/4d8e9e852d98ecd26cdf8eaf7ed8bc33174033bba5e07001b289f07308fd/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db", size = 124623 }, - { url = "https://files.pythonhosted.org/packages/1d/70/7f5af2a18a76fe92ea14675f8bd88ce53ee79e37900fa5f1a1d8e0b42998/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b", size = 66720 }, - { url = "https://files.pythonhosted.org/packages/c6/13/e15f804a142353aefd089fadc8f1d985561a15358c97aca27b0979cb0785/kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d", size = 65413 }, - { url = "https://files.pythonhosted.org/packages/ce/6d/67d36c4d2054e83fb875c6b59d0809d5c530de8148846b1370475eeeece9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d", size = 1650826 }, - { url = "https://files.pythonhosted.org/packages/de/c6/7b9bb8044e150d4d1558423a1568e4f227193662a02231064e3824f37e0a/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c", size = 1628231 }, - { url = "https://files.pythonhosted.org/packages/b6/38/ad10d437563063eaaedbe2c3540a71101fc7fb07a7e71f855e93ea4de605/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3", size = 1408938 }, - { url = "https://files.pythonhosted.org/packages/52/ce/c0106b3bd7f9e665c5f5bc1e07cc95b5dabd4e08e3dad42dbe2faad467e7/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed", size = 1422799 }, - { url = "https://files.pythonhosted.org/packages/d0/87/efb704b1d75dc9758087ba374c0f23d3254505edaedd09cf9d247f7878b9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f", size = 1354362 }, - { url = "https://files.pythonhosted.org/packages/eb/b3/fd760dc214ec9a8f208b99e42e8f0130ff4b384eca8b29dd0efc62052176/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff", size = 2222695 }, - { url = "https://files.pythonhosted.org/packages/a2/09/a27fb36cca3fc01700687cc45dae7a6a5f8eeb5f657b9f710f788748e10d/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d", size = 2370802 }, - { url = "https://files.pythonhosted.org/packages/3d/c3/ba0a0346db35fe4dc1f2f2cf8b99362fbb922d7562e5f911f7ce7a7b60fa/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c", size = 2334646 }, - { url = "https://files.pythonhosted.org/packages/41/52/942cf69e562f5ed253ac67d5c92a693745f0bed3c81f49fc0cbebe4d6b00/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605", size = 2467260 }, - { url = "https://files.pythonhosted.org/packages/32/26/2d9668f30d8a494b0411d4d7d4ea1345ba12deb6a75274d58dd6ea01e951/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e", size = 2288633 }, - { url = "https://files.pythonhosted.org/packages/98/99/0dd05071654aa44fe5d5e350729961e7bb535372935a45ac89a8924316e6/kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751", size = 71885 }, - { url = "https://files.pythonhosted.org/packages/6c/fc/822e532262a97442989335394d441cd1d0448c2e46d26d3e04efca84df22/kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271", size = 65175 }, - { url = "https://files.pythonhosted.org/packages/da/ed/c913ee28936c371418cb167b128066ffb20bbf37771eecc2c97edf8a6e4c/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84", size = 124635 }, - { url = "https://files.pythonhosted.org/packages/4c/45/4a7f896f7467aaf5f56ef093d1f329346f3b594e77c6a3c327b2d415f521/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561", size = 66717 }, - { url = "https://files.pythonhosted.org/packages/5f/b4/c12b3ac0852a3a68f94598d4c8d569f55361beef6159dce4e7b624160da2/kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7", size = 65413 }, - { url = "https://files.pythonhosted.org/packages/a9/98/1df4089b1ed23d83d410adfdc5947245c753bddfbe06541c4aae330e9e70/kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03", size = 1343994 }, - { url = "https://files.pythonhosted.org/packages/8d/bf/b4b169b050c8421a7c53ea1ea74e4ef9c335ee9013216c558a047f162d20/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954", size = 1434804 }, - { url = "https://files.pythonhosted.org/packages/66/5a/e13bd341fbcf73325ea60fdc8af752addf75c5079867af2e04cc41f34434/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79", size = 1450690 }, - { url = "https://files.pythonhosted.org/packages/9b/4f/5955dcb376ba4a830384cc6fab7d7547bd6759fe75a09564910e9e3bb8ea/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6", size = 1376839 }, - { url = "https://files.pythonhosted.org/packages/3a/97/5edbed69a9d0caa2e4aa616ae7df8127e10f6586940aa683a496c2c280b9/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0", size = 1435109 }, - { url = "https://files.pythonhosted.org/packages/13/fc/e756382cb64e556af6c1809a1bbb22c141bbc2445049f2da06b420fe52bf/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab", size = 2245269 }, - { url = "https://files.pythonhosted.org/packages/76/15/e59e45829d7f41c776d138245cabae6515cb4eb44b418f6d4109c478b481/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc", size = 2393468 }, - { url = "https://files.pythonhosted.org/packages/e9/39/483558c2a913ab8384d6e4b66a932406f87c95a6080112433da5ed668559/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25", size = 2355394 }, - { url = "https://files.pythonhosted.org/packages/01/aa/efad1fbca6570a161d29224f14b082960c7e08268a133fe5dc0f6906820e/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc", size = 2490901 }, - { url = "https://files.pythonhosted.org/packages/c9/4f/15988966ba46bcd5ab9d0c8296914436720dd67fca689ae1a75b4ec1c72f/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67", size = 2312306 }, - { url = "https://files.pythonhosted.org/packages/2d/27/bdf1c769c83f74d98cbc34483a972f221440703054894a37d174fba8aa68/kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34", size = 71966 }, - { url = "https://files.pythonhosted.org/packages/4a/c9/9642ea855604aeb2968a8e145fc662edf61db7632ad2e4fb92424be6b6c0/kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2", size = 65311 }, - { url = "https://files.pythonhosted.org/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152 }, - { url = "https://files.pythonhosted.org/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555 }, - { url = "https://files.pythonhosted.org/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067 }, - { url = "https://files.pythonhosted.org/packages/c9/ed/1d97f7e3561e09757a196231edccc1bcf59d55ddccefa2afc9c615abd8e0/kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f", size = 1378443 }, - { url = "https://files.pythonhosted.org/packages/29/61/39d30b99954e6b46f760e6289c12fede2ab96a254c443639052d1b573fbc/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc", size = 1472728 }, - { url = "https://files.pythonhosted.org/packages/0c/3e/804163b932f7603ef256e4a715e5843a9600802bb23a68b4e08c8c0ff61d/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a", size = 1478388 }, - { url = "https://files.pythonhosted.org/packages/8a/9e/60eaa75169a154700be74f875a4d9961b11ba048bef315fbe89cb6999056/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a", size = 1413849 }, - { url = "https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a", size = 1475533 }, - { url = "https://files.pythonhosted.org/packages/e4/7a/0a42d9571e35798de80aef4bb43a9b672aa7f8e58643d7bd1950398ffb0a/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3", size = 2268898 }, - { url = "https://files.pythonhosted.org/packages/d9/07/1255dc8d80271400126ed8db35a1795b1a2c098ac3a72645075d06fe5c5d/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b", size = 2425605 }, - { url = "https://files.pythonhosted.org/packages/84/df/5a3b4cf13780ef6f6942df67b138b03b7e79e9f1f08f57c49957d5867f6e/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4", size = 2375801 }, - { url = "https://files.pythonhosted.org/packages/8f/10/2348d068e8b0f635c8c86892788dac7a6b5c0cb12356620ab575775aad89/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d", size = 2520077 }, - { url = "https://files.pythonhosted.org/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410 }, - { url = "https://files.pythonhosted.org/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853 }, - { url = "https://files.pythonhosted.org/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424 }, - { url = "https://files.pythonhosted.org/packages/1f/f9/ae81c47a43e33b93b0a9819cac6723257f5da2a5a60daf46aa5c7226ea85/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a", size = 60403 }, - { url = "https://files.pythonhosted.org/packages/58/ca/f92b5cb6f4ce0c1ebfcfe3e2e42b96917e16f7090e45b21102941924f18f/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8", size = 58657 }, - { url = "https://files.pythonhosted.org/packages/80/28/ae0240f732f0484d3a4dc885d055653c47144bdf59b670aae0ec3c65a7c8/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0", size = 84948 }, - { url = "https://files.pythonhosted.org/packages/5d/eb/78d50346c51db22c7203c1611f9b513075f35c4e0e4877c5dde378d66043/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c", size = 81186 }, - { url = "https://files.pythonhosted.org/packages/43/f8/7259f18c77adca88d5f64f9a522792e178b2691f3748817a8750c2d216ef/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b", size = 80279 }, - { url = "https://files.pythonhosted.org/packages/3a/1d/50ad811d1c5dae091e4cf046beba925bcae0a610e79ae4c538f996f63ed5/kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b", size = 71762 }, -] - -[[package]] -name = "linear-operator" -version = "0.5.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jaxtyping" }, - { name = "mpmath" }, - { name = "scipy" }, - { name = "torch" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/90/f4/d3f2e2aecf1fc23c5007b40312b0461e1426720a8e2e5a5ef67554670dd6/linear_operator-0.5.3.tar.gz", hash = "sha256:16122661cd8b8a89ea965c845f650affe0f688f315893bb8dfa1182f148a1a41", size = 186038 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/5e/4cff4e634151884502a260f5fc3f92303775133b0e0fedb7d3c7f2a56d4c/linear_operator-0.5.3-py3-none-any.whl", hash = "sha256:908df4e64e25312edfa5502b30b71df97383cb604a13f420921030ae40c47838", size = 176406 }, -] - -[[package]] -name = "makefun" -version = "1.15.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/00/62966769824620717a3c2d76b1d442489648398b599bdcd490af13bff101/makefun-1.15.6.tar.gz", hash = "sha256:26bc63442a6182fb75efed8b51741dd2d1db2f176bec8c64e20a586256b8f149", size = 72583 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/89/a1/3e145759e776c8866488a71270c399bf7c4e554551ac2e247aa0a18a0596/makefun-1.15.6-py2.py3-none-any.whl", hash = "sha256:e69b870f0bb60304765b1e3db576aaecf2f9b3e5105afe8cfeff8f2afe6ad067", size = 22946 }, -] - -[[package]] -name = "markdown" -version = "3.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 }, -] - -[[package]] -name = "markupsafe" -version = "3.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, - { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, - { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, - { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, - { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, - { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, - { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, - { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, - { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, - { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, -] - -[[package]] -name = "matplotlib" -version = "3.10.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "contourpy" }, - { name = "cycler" }, - { name = "fonttools" }, - { name = "kiwisolver" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "pillow" }, - { name = "pyparsing" }, - { name = "python-dateutil" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/68/dd/fa2e1a45fce2d09f4aea3cee169760e672c8262325aa5796c49d543dc7e6/matplotlib-3.10.0.tar.gz", hash = "sha256:b886d02a581b96704c9d1ffe55709e49b4d2d52709ccebc4be42db856e511278", size = 36686418 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/09/ec/3cdff7b5239adaaacefcc4f77c316dfbbdf853c4ed2beec467e0fec31b9f/matplotlib-3.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2c5829a5a1dd5a71f0e31e6e8bb449bc0ee9dbfb05ad28fc0c6b55101b3a4be6", size = 8160551 }, - { url = "https://files.pythonhosted.org/packages/41/f2/b518f2c7f29895c9b167bf79f8529c63383ae94eaf49a247a4528e9a148d/matplotlib-3.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2a43cbefe22d653ab34bb55d42384ed30f611bcbdea1f8d7f431011a2e1c62e", size = 8034853 }, - { url = "https://files.pythonhosted.org/packages/ed/8d/45754b4affdb8f0d1a44e4e2bcd932cdf35b256b60d5eda9f455bb293ed0/matplotlib-3.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:607b16c8a73943df110f99ee2e940b8a1cbf9714b65307c040d422558397dac5", size = 8446724 }, - { url = "https://files.pythonhosted.org/packages/09/5a/a113495110ae3e3395c72d82d7bc4802902e46dc797f6b041e572f195c56/matplotlib-3.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01d2b19f13aeec2e759414d3bfe19ddfb16b13a1250add08d46d5ff6f9be83c6", size = 8583905 }, - { url = "https://files.pythonhosted.org/packages/12/b1/8b1655b4c9ed4600c817c419f7eaaf70082630efd7556a5b2e77a8a3cdaf/matplotlib-3.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e6c6461e1fc63df30bf6f80f0b93f5b6784299f721bc28530477acd51bfc3d1", size = 9395223 }, - { url = "https://files.pythonhosted.org/packages/5a/85/b9a54d64585a6b8737a78a61897450403c30f39e0bd3214270bb0b96f002/matplotlib-3.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:994c07b9d9fe8d25951e3202a68c17900679274dadfc1248738dcfa1bd40d7f3", size = 8025355 }, - { url = "https://files.pythonhosted.org/packages/0c/f1/e37f6c84d252867d7ddc418fff70fc661cfd363179263b08e52e8b748e30/matplotlib-3.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:fd44fc75522f58612ec4a33958a7e5552562b7705b42ef1b4f8c0818e304a363", size = 8171677 }, - { url = "https://files.pythonhosted.org/packages/c7/8b/92e9da1f28310a1f6572b5c55097b0c0ceb5e27486d85fb73b54f5a9b939/matplotlib-3.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c58a9622d5dbeb668f407f35f4e6bfac34bb9ecdcc81680c04d0258169747997", size = 8044945 }, - { url = "https://files.pythonhosted.org/packages/c5/cb/49e83f0fd066937a5bd3bc5c5d63093703f3637b2824df8d856e0558beef/matplotlib-3.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:845d96568ec873be63f25fa80e9e7fae4be854a66a7e2f0c8ccc99e94a8bd4ef", size = 8458269 }, - { url = "https://files.pythonhosted.org/packages/b2/7d/2d873209536b9ee17340754118a2a17988bc18981b5b56e6715ee07373ac/matplotlib-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5439f4c5a3e2e8eab18e2f8c3ef929772fd5641876db71f08127eed95ab64683", size = 8599369 }, - { url = "https://files.pythonhosted.org/packages/b8/03/57d6cbbe85c61fe4cbb7c94b54dce443d68c21961830833a1f34d056e5ea/matplotlib-3.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4673ff67a36152c48ddeaf1135e74ce0d4bce1bbf836ae40ed39c29edf7e2765", size = 9405992 }, - { url = "https://files.pythonhosted.org/packages/14/cf/e382598f98be11bf51dd0bc60eca44a517f6793e3dc8b9d53634a144620c/matplotlib-3.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:7e8632baebb058555ac0cde75db885c61f1212e47723d63921879806b40bec6a", size = 8034580 }, - { url = "https://files.pythonhosted.org/packages/44/c7/6b2d8cb7cc251d53c976799cacd3200add56351c175ba89ab9cbd7c1e68a/matplotlib-3.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4659665bc7c9b58f8c00317c3c2a299f7f258eeae5a5d56b4c64226fca2f7c59", size = 8172465 }, - { url = "https://files.pythonhosted.org/packages/42/2a/6d66d0fba41e13e9ca6512a0a51170f43e7e7ed3a8dfa036324100775612/matplotlib-3.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d44cb942af1693cced2604c33a9abcef6205601c445f6d0dc531d813af8a2f5a", size = 8043300 }, - { url = "https://files.pythonhosted.org/packages/90/60/2a60342b27b90a16bada939a85e29589902b41073f59668b904b15ea666c/matplotlib-3.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a994f29e968ca002b50982b27168addfd65f0105610b6be7fa515ca4b5307c95", size = 8448936 }, - { url = "https://files.pythonhosted.org/packages/a7/b2/d872fc3d753516870d520595ddd8ce4dd44fa797a240999f125f58521ad7/matplotlib-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b0558bae37f154fffda54d779a592bc97ca8b4701f1c710055b609a3bac44c8", size = 8594151 }, - { url = "https://files.pythonhosted.org/packages/f4/bd/b2f60cf7f57d014ab33e4f74602a2b5bdc657976db8196bbc022185f6f9c/matplotlib-3.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:503feb23bd8c8acc75541548a1d709c059b7184cde26314896e10a9f14df5f12", size = 9400347 }, - { url = "https://files.pythonhosted.org/packages/9f/6e/264673e64001b99d747aff5a288eca82826c024437a3694e19aed1decf46/matplotlib-3.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:c40ba2eb08b3f5de88152c2333c58cee7edcead0a2a0d60fcafa116b17117adc", size = 8039144 }, - { url = "https://files.pythonhosted.org/packages/32/5f/29def7ce4e815ab939b56280976ee35afffb3bbdb43f332caee74cb8c951/matplotlib-3.10.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:81713dd0d103b379de4516b861d964b1d789a144103277769238c732229d7f03", size = 8155500 }, - { url = "https://files.pythonhosted.org/packages/de/6d/d570383c9f7ca799d0a54161446f9ce7b17d6c50f2994b653514bcaa108f/matplotlib-3.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:359f87baedb1f836ce307f0e850d12bb5f1936f70d035561f90d41d305fdacea", size = 8032398 }, - { url = "https://files.pythonhosted.org/packages/c9/b4/680aa700d99b48e8c4393fa08e9ab8c49c0555ee6f4c9c0a5e8ea8dfde5d/matplotlib-3.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae80dc3a4add4665cf2faa90138384a7ffe2a4e37c58d83e115b54287c4f06ef", size = 8587361 }, -] - -[[package]] -name = "mergedeep" -version = "1.3.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354 }, -] - -[[package]] -name = "mike" -version = "2.1.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "importlib-metadata" }, - { name = "importlib-resources" }, - { name = "jinja2" }, - { name = "mkdocs" }, - { name = "pyparsing" }, - { name = "pyyaml" }, - { name = "pyyaml-env-tag" }, - { name = "verspec" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ab/f7/2933f1a1fb0e0f077d5d6a92c6c7f8a54e6128241f116dff4df8b6050bbf/mike-2.1.3.tar.gz", hash = "sha256:abd79b8ea483fb0275b7972825d3082e5ae67a41820f8d8a0dc7a3f49944e810", size = 38119 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/1a/31b7cd6e4e7a02df4e076162e9783620777592bea9e4bb036389389af99d/mike-2.1.3-py3-none-any.whl", hash = "sha256:d90c64077e84f06272437b464735130d380703a76a5738b152932884c60c062a", size = 33754 }, -] - -[[package]] -name = "mkdocs" -version = "1.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "colorama", marker = "platform_system == 'Windows'" }, - { name = "ghp-import" }, - { name = "jinja2" }, - { name = "markdown" }, - { name = "markupsafe" }, - { name = "mergedeep" }, - { name = "mkdocs-get-deps" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "pyyaml" }, - { name = "pyyaml-env-tag" }, - { name = "watchdog" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451 }, -] - -[[package]] -name = "mkdocs-autorefs" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown" }, - { name = "markupsafe" }, - { name = "mkdocs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fb/ae/0f1154c614d6a8b8a36fff084e5b82af3a15f7d2060cf0dcdb1c53297a71/mkdocs_autorefs-1.2.0.tar.gz", hash = "sha256:a86b93abff653521bda71cf3fc5596342b7a23982093915cb74273f67522190f", size = 40262 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl", hash = "sha256:d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f", size = 16522 }, -] - -[[package]] -name = "mkdocs-gen-files" -version = "0.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mkdocs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/48/85/2d634462fd59136197d3126ca431ffb666f412e3db38fd5ce3a60566303e/mkdocs_gen_files-0.5.0.tar.gz", hash = "sha256:4c7cf256b5d67062a788f6b1d035e157fc1a9498c2399be9af5257d4ff4d19bc", size = 7539 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/0f/1e55b3fd490ad2cecb6e7b31892d27cb9fc4218ec1dab780440ba8579e74/mkdocs_gen_files-0.5.0-py3-none-any.whl", hash = "sha256:7ac060096f3f40bd19039e7277dd3050be9a453c8ac578645844d4d91d7978ea", size = 8380 }, -] - -[[package]] -name = "mkdocs-get-deps" -version = "0.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mergedeep" }, - { name = "platformdirs" }, - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521 }, -] - -[[package]] -name = "mkdocs-literate-nav" -version = "0.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mkdocs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4d/f9/c48a04f3cf484f8016a343c1d7d99c3a1ef01dbb33ceabb1d02e0ecabda7/mkdocs_literate_nav-0.6.1.tar.gz", hash = "sha256:78a7ab6d878371728acb0cdc6235c9b0ffc6e83c997b037f4a5c6ff7cef7d759", size = 16437 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/3b/e00d839d3242844c77e248f9572dd34644a04300839a60fe7d6bf652ab19/mkdocs_literate_nav-0.6.1-py3-none-any.whl", hash = "sha256:e70bdc4a07050d32da79c0b697bd88e9a104cf3294282e9cb20eec94c6b0f401", size = 13182 }, -] - -[[package]] -name = "mkdocs-material" -version = "9.5.49" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "babel" }, - { name = "colorama" }, - { name = "jinja2" }, - { name = "markdown" }, - { name = "mkdocs" }, - { name = "mkdocs-material-extensions" }, - { name = "paginate" }, - { name = "pygments" }, - { name = "pymdown-extensions" }, - { name = "regex" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e2/14/8daeeecee2e25bd84239a843fdcb92b20db88ebbcb26e0d32f414ca54a22/mkdocs_material-9.5.49.tar.gz", hash = "sha256:3671bb282b4f53a1c72e08adbe04d2481a98f85fed392530051f80ff94a9621d", size = 3949559 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/2d/2dd23a36b48421db54f118bb6f6f733dbe2d5c78fe7867375e48649fd3df/mkdocs_material-9.5.49-py3-none-any.whl", hash = "sha256:c3c2d8176b18198435d3a3e119011922f3e11424074645c24019c2dcf08a360e", size = 8684098 }, -] - -[[package]] -name = "mkdocs-material-extensions" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728 }, -] - -[[package]] -name = "mkdocstrings" -version = "0.27.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "jinja2" }, - { name = "markdown" }, - { name = "markupsafe" }, - { name = "mkdocs" }, - { name = "mkdocs-autorefs" }, - { name = "platformdirs" }, - { name = "pymdown-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e2/5a/5de70538c2cefae7ac3a15b5601e306ef3717290cb2aab11d51cbbc2d1c0/mkdocstrings-0.27.0.tar.gz", hash = "sha256:16adca6d6b0a1f9e0c07ff0b02ced8e16f228a9d65a37c063ec4c14d7b76a657", size = 94830 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl", hash = "sha256:6ceaa7ea830770959b55a16203ac63da24badd71325b96af950e59fd37366332", size = 30658 }, -] - -[package.optional-dependencies] -python = [ - { name = "mkdocstrings-python" }, -] - -[[package]] -name = "mkdocstrings-python" -version = "1.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "griffe" }, - { name = "mkdocs-autorefs" }, - { name = "mkdocstrings" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ab/ae/32703e35d74040051c672400fd9f5f2b48a6ea094f5071dd8a0e3be35322/mkdocstrings_python-1.13.0.tar.gz", hash = "sha256:2dbd5757e8375b9720e81db16f52f1856bf59905428fd7ef88005d1370e2f64c", size = 185697 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl", hash = "sha256:b88bbb207bab4086434743849f8e796788b373bd32e7bfefbf8560ac45d88f97", size = 112254 }, -] - -[[package]] -name = "more-itertools" -version = "10.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/78/65922308c4248e0eb08ebcbe67c95d48615cc6f27854b6f2e57143e9178f/more-itertools-10.5.0.tar.gz", hash = "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6", size = 121020 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/7e/3a64597054a70f7c86eb0a7d4fc315b8c1ab932f64883a297bdffeb5f967/more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef", size = 60952 }, -] - -[[package]] -name = "mpmath" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, -] - -[[package]] -name = "multipledispatch" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/3e/a62c3b824c7dec33c4a1578bcc842e6c30300051033a4e5975ed86cc2536/multipledispatch-1.0.0.tar.gz", hash = "sha256:5c839915465c68206c3e9c473357908216c28383b425361e5d144594bf85a7e0", size = 12385 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/c0/00c9809d8b9346eb238a6bbd5f83e846a4ce4503da94a4c08cb7284c325b/multipledispatch-1.0.0-py3-none-any.whl", hash = "sha256:0c53cd8b077546da4e48869f49b13164bebafd0c2a5afceb6bb6a316e7fb46e4", size = 12818 }, -] - -[[package]] -name = "mypy" -version = "1.14.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mypy-extensions" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8c/7b/08046ef9330735f536a09a2e31b00f42bccdb2795dcd979636ba43bb2d63/mypy-1.14.0.tar.gz", hash = "sha256:822dbd184d4a9804df5a7d5335a68cf7662930e70b8c1bc976645d1509f9a9d6", size = 3215684 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/97/f00ded038482230e0beaaa08f9c5483a54530b362ad1b0d752d5d2b2f211/mypy-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e971c1c667007f9f2b397ffa80fa8e1e0adccff336e5e77e74cb5f22868bee87", size = 11207956 }, - { url = "https://files.pythonhosted.org/packages/68/67/8b4db0da19c9e3fa6264e948f1c135ab4dd45bede1809f4fdb613dc119f6/mypy-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e86aaeaa3221a278c66d3d673b297232947d873773d61ca3ee0e28b2ff027179", size = 10363681 }, - { url = "https://files.pythonhosted.org/packages/f5/00/56b1619ff1f3fcad2d411eccda60d74d20e73bda39c218d5ad2769980682/mypy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1628c5c3ce823d296e41e2984ff88c5861499041cb416a8809615d0c1f41740e", size = 12832976 }, - { url = "https://files.pythonhosted.org/packages/e7/8b/9247838774b0bd865f190cc221822212091317f16310305ef924d9772532/mypy-1.14.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7fadb29b77fc14a0dd81304ed73c828c3e5cde0016c7e668a86a3e0dfc9f3af3", size = 13013704 }, - { url = "https://files.pythonhosted.org/packages/b2/69/0c0868a6f3d9761d2f704d1fb6ef84d75998c27d342738a8b20f109a411f/mypy-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:3fa76988dc760da377c1e5069200a50d9eaaccf34f4ea18428a3337034ab5a44", size = 9782230 }, - { url = "https://files.pythonhosted.org/packages/34/c1/b9dd3e955953aec1c728992545b7877c9f6fa742a623ce4c200da0f62540/mypy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e73c8a154eed31db3445fe28f63ad2d97b674b911c00191416cf7f6459fd49a", size = 11121032 }, - { url = "https://files.pythonhosted.org/packages/ee/96/c52d5d516819ab95bf41f4a1ada828a3decc302f8c152ff4fc5feb0e4529/mypy-1.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:273e70fcb2e38c5405a188425aa60b984ffdcef65d6c746ea5813024b68c73dc", size = 10286294 }, - { url = "https://files.pythonhosted.org/packages/69/2c/3dbe51877a24daa467f8d8631f9ffd1aabbf0f6d9367a01c44a59df81fe0/mypy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1daca283d732943731a6a9f20fdbcaa927f160bc51602b1d4ef880a6fb252015", size = 12746528 }, - { url = "https://files.pythonhosted.org/packages/a1/a8/eb20cde4ba9c4c3e20d958918a7c5d92210f4d1a0200c27de9a641f70996/mypy-1.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7e68047bedb04c1c25bba9901ea46ff60d5eaac2d71b1f2161f33107e2b368eb", size = 12883489 }, - { url = "https://files.pythonhosted.org/packages/91/17/a1fc6c70f31d52c99299320cf81c3cb2c6b91ec7269414e0718a6d138e34/mypy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:7a52f26b9c9b1664a60d87675f3bae00b5c7f2806e0c2800545a32c325920bcc", size = 9780113 }, - { url = "https://files.pythonhosted.org/packages/fe/d8/0e72175ee0253217f5c44524f5e95251c02e95ba9749fb87b0e2074d203a/mypy-1.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d5326ab70a6db8e856d59ad4cb72741124950cbbf32e7b70e30166ba7bbf61dd", size = 11269011 }, - { url = "https://files.pythonhosted.org/packages/e9/6d/4ea13839dabe5db588dc6a1b766da16f420d33cf118a7b7172cdf6c7fcb2/mypy-1.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bf4ec4980bec1e0e24e5075f449d014011527ae0055884c7e3abc6a99cd2c7f1", size = 10253076 }, - { url = "https://files.pythonhosted.org/packages/3e/38/7db2c5d0f4d290e998f7a52b2e2616c7bbad96b8e04278ab09d11978a29e/mypy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:390dfb898239c25289495500f12fa73aa7f24a4c6d90ccdc165762462b998d63", size = 12862786 }, - { url = "https://files.pythonhosted.org/packages/bf/4b/62d59c801b34141040989949c2b5c157d0408b45357335d3ec5b2845b0f6/mypy-1.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e026d55ddcd76e29e87865c08cbe2d0104e2b3153a523c529de584759379d3d", size = 12971568 }, - { url = "https://files.pythonhosted.org/packages/f1/9c/e0f281b32d70c87b9e4d2939e302b1ff77ada4d7b0f2fb32890c144bc1d6/mypy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:585ed36031d0b3ee362e5107ef449a8b5dfd4e9c90ccbe36414ee405ee6b32ba", size = 9879477 }, - { url = "https://files.pythonhosted.org/packages/39/32/0214608af400cdf8f5102144bb8af10d880675c65ed0b58f7e0e77175d50/mypy-1.14.0-py3-none-any.whl", hash = "sha256:2238d7f93fc4027ed1efc944507683df3ba406445a2b6c96e79666a045aadfab", size = 2752803 }, -] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, -] - -[[package]] -name = "networkx" -version = "2.8.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/16/c44e8550012735b8f21b3df7f39e8ba5a987fb764ac017ad5f3589735889/networkx-2.8.8.tar.gz", hash = "sha256:230d388117af870fce5647a3c52401fcf753e94720e6ea6b4197a5355648885e", size = 1960828 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/31/d2f89f1ae42718f8c8a9e440ebe38d7d5fe1e0d9eb9178ce779e365b3ab0/networkx-2.8.8-py3-none-any.whl", hash = "sha256:e435dfa75b1d7195c7b8378c3859f0445cd88c6b0375c181ed66823a9ceb7524", size = 2025192 }, -] - -[[package]] -name = "neural-pipeline-search" -version = "0.12.2" -source = { editable = "." } -dependencies = [ - { name = "botorch" }, - { name = "configspace" }, - { name = "gpytorch" }, - { name = "grakel" }, - { name = "ifbo" }, - { name = "matplotlib" }, - { name = "more-itertools" }, - { name = "networkx" }, - { name = "nltk" }, - { name = "numpy" }, - { name = "pandas" }, - { name = "portalocker" }, - { name = "pyyaml" }, - { name = "scipy" }, - { name = "seaborn" }, - { name = "tensorboard" }, - { name = "torch" }, - { name = "torchvision" }, - { name = "typing-extensions" }, -] - -[package.optional-dependencies] -dev = [ - { name = "black" }, - { name = "mike" }, - { name = "mkdocs-autorefs" }, - { name = "mkdocs-gen-files" }, - { name = "mkdocs-literate-nav" }, - { name = "mkdocs-material" }, - { name = "mkdocstrings", extra = ["python"] }, - { name = "mypy" }, - { name = "pre-commit" }, - { name = "pytest" }, - { name = "pytest-cases" }, - { name = "ruff" }, - { name = "types-pyyaml" }, -] - -[package.metadata] -requires-dist = [ - { name = "black", marker = "extra == 'dev'" }, - { name = "botorch", specifier = ">=0.12" }, - { name = "configspace", specifier = ">=0.7,<1.0" }, - { name = "gpytorch", specifier = "==1.13.0" }, - { name = "grakel", specifier = ">=0.1,<0.2" }, - { name = "ifbo", specifier = ">=0.3.10" }, - { name = "matplotlib", specifier = ">=3.0,<4.0" }, - { name = "mike", marker = "extra == 'dev'" }, - { name = "mkdocs-autorefs", marker = "extra == 'dev'" }, - { name = "mkdocs-gen-files", marker = "extra == 'dev'" }, - { name = "mkdocs-literate-nav", marker = "extra == 'dev'" }, - { name = "mkdocs-material", marker = "extra == 'dev'" }, - { name = "mkdocstrings", extras = ["python"], marker = "extra == 'dev'" }, - { name = "more-itertools" }, - { name = "mypy", marker = "extra == 'dev'", specifier = ">=1,<2" }, - { name = "networkx", specifier = ">=2.6.3,<3.0" }, - { name = "nltk", specifier = ">=3.6.4,<4.0" }, - { name = "numpy", specifier = ">=1.0,<2.0" }, - { name = "pandas", specifier = ">=2.0,<3.0" }, - { name = "portalocker", specifier = ">=2.0,<3.0" }, - { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3,<4" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=7,<8" }, - { name = "pytest-cases", marker = "extra == 'dev'", specifier = ">=3,<4" }, - { name = "pyyaml", specifier = ">=6.0,<7.0" }, - { name = "ruff", marker = "extra == 'dev'" }, - { name = "scipy", specifier = ">=1.13.1" }, - { name = "seaborn", specifier = ">=0.13,<0.14" }, - { name = "tensorboard", specifier = ">=2.0,<3.0" }, - { name = "torch", specifier = ">=2.0.1" }, - { name = "torchvision", specifier = ">=0.8.0" }, - { name = "types-pyyaml", marker = "extra == 'dev'", specifier = ">=6,<7" }, - { name = "typing-extensions" }, -] - -[[package]] -name = "nltk" -version = "3.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "joblib" }, - { name = "regex" }, - { name = "tqdm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3c/87/db8be88ad32c2d042420b6fd9ffd4a149f9a0d7f0e86b3f543be2eeeedd2/nltk-3.9.1.tar.gz", hash = "sha256:87d127bd3de4bd89a4f81265e5fa59cb1b199b27440175370f7417d2bc7ae868", size = 2904691 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/66/7d9e26593edda06e8cb531874633f7c2372279c3b0f46235539fe546df8b/nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1", size = 1505442 }, -] - -[[package]] -name = "nodeenv" -version = "1.9.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, -] - -[[package]] -name = "numpy" -version = "1.26.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/94/ace0fdea5241a27d13543ee117cbc65868e82213fb31a8eb7fe9ff23f313/numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", size = 20631468 }, - { url = "https://files.pythonhosted.org/packages/20/f7/b24208eba89f9d1b58c1668bc6c8c4fd472b20c45573cb767f59d49fb0f6/numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", size = 13966411 }, - { url = "https://files.pythonhosted.org/packages/fc/a5/4beee6488160798683eed5bdb7eead455892c3b4e1f78d79d8d3f3b084ac/numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", size = 14219016 }, - { url = "https://files.pythonhosted.org/packages/4b/d7/ecf66c1cd12dc28b4040b15ab4d17b773b87fa9d29ca16125de01adb36cd/numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f", size = 18240889 }, - { url = "https://files.pythonhosted.org/packages/24/03/6f229fe3187546435c4f6f89f6d26c129d4f5bed40552899fcf1f0bf9e50/numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", size = 13876746 }, - { url = "https://files.pythonhosted.org/packages/39/fe/39ada9b094f01f5a35486577c848fe274e374bbf8d8f472e1423a0bbd26d/numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", size = 18078620 }, - { url = "https://files.pythonhosted.org/packages/d5/ef/6ad11d51197aad206a9ad2286dc1aac6a378059e06e8cf22cd08ed4f20dc/numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", size = 5972659 }, - { url = "https://files.pythonhosted.org/packages/19/77/538f202862b9183f54108557bfda67e17603fc560c384559e769321c9d92/numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", size = 15808905 }, - { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554 }, - { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127 }, - { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994 }, - { url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005 }, - { url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297 }, - { url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567 }, - { url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812 }, - { url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913 }, - { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901 }, - { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868 }, - { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109 }, - { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613 }, - { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172 }, - { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643 }, - { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803 }, - { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754 }, -] - -[[package]] -name = "nvidia-cublas-cu12" -version = "12.4.5.8" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/7f/7fbae15a3982dc9595e49ce0f19332423b260045d0a6afe93cdbe2f1f624/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0f8aa1706812e00b9f19dfe0cdb3999b092ccb8ca168c0db5b8ea712456fd9b3", size = 363333771 }, - { url = "https://files.pythonhosted.org/packages/ae/71/1c91302526c45ab494c23f61c7a84aa568b8c1f9d196efa5993957faf906/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl", hash = "sha256:2fc8da60df463fdefa81e323eef2e36489e1c94335b5358bcb38360adf75ac9b", size = 363438805 }, -] - -[[package]] -name = "nvidia-cuda-cupti-cu12" -version = "12.4.127" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/b5/9fb3d00386d3361b03874246190dfec7b206fd74e6e287b26a8fcb359d95/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:79279b35cf6f91da114182a5ce1864997fd52294a87a16179ce275773799458a", size = 12354556 }, - { url = "https://files.pythonhosted.org/packages/67/42/f4f60238e8194a3106d06a058d494b18e006c10bb2b915655bd9f6ea4cb1/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:9dec60f5ac126f7bb551c055072b69d85392b13311fcc1bcda2202d172df30fb", size = 13813957 }, -] - -[[package]] -name = "nvidia-cuda-nvrtc-cu12" -version = "12.4.127" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/aa/083b01c427e963ad0b314040565ea396f914349914c298556484f799e61b/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0eedf14185e04b76aa05b1fea04133e59f465b6f960c0cbf4e37c3cb6b0ea198", size = 24133372 }, - { url = "https://files.pythonhosted.org/packages/2c/14/91ae57cd4db3f9ef7aa99f4019cfa8d54cb4caa7e00975df6467e9725a9f/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a178759ebb095827bd30ef56598ec182b85547f1508941a3d560eb7ea1fbf338", size = 24640306 }, -] - -[[package]] -name = "nvidia-cuda-runtime-cu12" -version = "12.4.127" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/aa/b656d755f474e2084971e9a297def515938d56b466ab39624012070cb773/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:961fe0e2e716a2a1d967aab7caee97512f71767f852f67432d572e36cb3a11f3", size = 894177 }, - { url = "https://files.pythonhosted.org/packages/ea/27/1795d86fe88ef397885f2e580ac37628ed058a92ed2c39dc8eac3adf0619/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:64403288fa2136ee8e467cdc9c9427e0434110899d07c779f25b5c068934faa5", size = 883737 }, -] - -[[package]] -name = "nvidia-cudnn-cu12" -version = "9.1.0.70" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-cublas-cu12" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/fd/713452cd72343f682b1c7b9321e23829f00b842ceaedcda96e742ea0b0b3/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f", size = 664752741 }, -] - -[[package]] -name = "nvidia-cufft-cu12" -version = "11.2.1.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-nvjitlink-cu12" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/8a/0e728f749baca3fbeffad762738276e5df60851958be7783af121a7221e7/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5dad8008fc7f92f5ddfa2101430917ce2ffacd86824914c82e28990ad7f00399", size = 211422548 }, - { url = "https://files.pythonhosted.org/packages/27/94/3266821f65b92b3138631e9c8e7fe1fb513804ac934485a8d05776e1dd43/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f083fc24912aa410be21fa16d157fed2055dab1cc4b6934a0e03cba69eb242b9", size = 211459117 }, -] - -[[package]] -name = "nvidia-curand-cu12" -version = "10.3.5.147" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/80/9c/a79180e4d70995fdf030c6946991d0171555c6edf95c265c6b2bf7011112/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1f173f09e3e3c76ab084aba0de819c49e56614feae5c12f69883f4ae9bb5fad9", size = 56314811 }, - { url = "https://files.pythonhosted.org/packages/8a/6d/44ad094874c6f1b9c654f8ed939590bdc408349f137f9b98a3a23ccec411/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a88f583d4e0bb643c49743469964103aa59f7f708d862c3ddb0fc07f851e3b8b", size = 56305206 }, -] - -[[package]] -name = "nvidia-cusolver-cu12" -version = "11.6.1.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-cublas-cu12" }, - { name = "nvidia-cusparse-cu12" }, - { name = "nvidia-nvjitlink-cu12" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/6b/a5c33cf16af09166845345275c34ad2190944bcc6026797a39f8e0a282e0/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d338f155f174f90724bbde3758b7ac375a70ce8e706d70b018dd3375545fc84e", size = 127634111 }, - { url = "https://files.pythonhosted.org/packages/3a/e1/5b9089a4b2a4790dfdea8b3a006052cfecff58139d5a4e34cb1a51df8d6f/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:19e33fa442bcfd085b3086c4ebf7e8debc07cfe01e11513cc6d332fd918ac260", size = 127936057 }, -] - -[[package]] -name = "nvidia-cusparse-cu12" -version = "12.3.1.170" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-nvjitlink-cu12" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/a9/c0d2f83a53d40a4a41be14cea6a0bf9e668ffcf8b004bd65633f433050c0/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9d32f62896231ebe0480efd8a7f702e143c98cfaa0e8a76df3386c1ba2b54df3", size = 207381987 }, - { url = "https://files.pythonhosted.org/packages/db/f7/97a9ea26ed4bbbfc2d470994b8b4f338ef663be97b8f677519ac195e113d/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ea4f11a2904e2a8dc4b1833cc1b5181cde564edd0d5cd33e3c168eff2d1863f1", size = 207454763 }, -] - -[[package]] -name = "nvidia-nccl-cu12" -version = "2.21.5" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/99/12cd266d6233f47d00daf3a72739872bdc10267d0383508b0b9c84a18bb6/nvidia_nccl_cu12-2.21.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:8579076d30a8c24988834445f8d633c697d42397e92ffc3f63fa26766d25e0a0", size = 188654414 }, -] - -[[package]] -name = "nvidia-nvjitlink-cu12" -version = "12.4.127" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/45/239d52c05074898a80a900f49b1615d81c07fceadd5ad6c4f86a987c0bc4/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4abe7fef64914ccfa909bc2ba39739670ecc9e820c83ccc7a6ed414122599b83", size = 20552510 }, - { url = "https://files.pythonhosted.org/packages/ff/ff/847841bacfbefc97a00036e0fce5a0f086b640756dc38caea5e1bb002655/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:06b3b9b25bf3f8af351d664978ca26a16d2c5127dbd53c0497e28d1fb9611d57", size = 21066810 }, -] - -[[package]] -name = "nvidia-nvtx-cu12" -version = "12.4.127" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/39/471f581edbb7804b39e8063d92fc8305bdc7a80ae5c07dbe6ea5c50d14a5/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7959ad635db13edf4fc65c06a6e9f9e55fc2f92596db928d169c0bb031e88ef3", size = 100417 }, - { url = "https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a", size = 99144 }, -] - -[[package]] -name = "opt-einsum" -version = "3.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/b9/2ac072041e899a52f20cf9510850ff58295003aa75525e58343591b0cbfb/opt_einsum-3.4.0.tar.gz", hash = "sha256:96ca72f1b886d148241348783498194c577fa30a8faac108586b14f1ba4473ac", size = 63004 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl", hash = "sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd", size = 71932 }, -] - -[[package]] -name = "packaging" -version = "24.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, -] - -[[package]] -name = "paginate" -version = "0.5.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746 }, -] - -[[package]] -name = "pandas" -version = "2.2.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "python-dateutil" }, - { name = "pytz" }, - { name = "tzdata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/70/c853aec59839bceed032d52010ff5f1b8d87dc3114b762e4ba2727661a3b/pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5", size = 12580827 }, - { url = "https://files.pythonhosted.org/packages/99/f2/c4527768739ffa4469b2b4fff05aa3768a478aed89a2f271a79a40eee984/pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348", size = 11303897 }, - { url = "https://files.pythonhosted.org/packages/ed/12/86c1747ea27989d7a4064f806ce2bae2c6d575b950be087837bdfcabacc9/pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed", size = 66480908 }, - { url = "https://files.pythonhosted.org/packages/44/50/7db2cd5e6373ae796f0ddad3675268c8d59fb6076e66f0c339d61cea886b/pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57", size = 13064210 }, - { url = "https://files.pythonhosted.org/packages/61/61/a89015a6d5536cb0d6c3ba02cebed51a95538cf83472975275e28ebf7d0c/pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42", size = 16754292 }, - { url = "https://files.pythonhosted.org/packages/ce/0d/4cc7b69ce37fac07645a94e1d4b0880b15999494372c1523508511b09e40/pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f", size = 14416379 }, - { url = "https://files.pythonhosted.org/packages/31/9e/6ebb433de864a6cd45716af52a4d7a8c3c9aaf3a98368e61db9e69e69a9c/pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645", size = 11598471 }, - { url = "https://files.pythonhosted.org/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222 }, - { url = "https://files.pythonhosted.org/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274 }, - { url = "https://files.pythonhosted.org/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836 }, - { url = "https://files.pythonhosted.org/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505 }, - { url = "https://files.pythonhosted.org/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420 }, - { url = "https://files.pythonhosted.org/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457 }, - { url = "https://files.pythonhosted.org/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166 }, - { url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893 }, - { url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475 }, - { url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645 }, - { url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445 }, - { url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235 }, - { url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756 }, - { url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248 }, -] - -[[package]] -name = "pathspec" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, -] - -[[package]] -name = "pillow" -version = "11.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/26/0d95c04c868f6bdb0c447e3ee2de5564411845e36a858cfd63766bc7b563/pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739", size = 46737780 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/fb/a6ce6836bd7fd93fbf9144bf54789e02babc27403b50a9e1583ee877d6da/pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947", size = 3154708 }, - { url = "https://files.pythonhosted.org/packages/6a/1d/1f51e6e912d8ff316bb3935a8cda617c801783e0b998bf7a894e91d3bd4c/pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba", size = 2979223 }, - { url = "https://files.pythonhosted.org/packages/90/83/e2077b0192ca8a9ef794dbb74700c7e48384706467067976c2a95a0f40a1/pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086", size = 4183167 }, - { url = "https://files.pythonhosted.org/packages/0e/74/467af0146970a98349cdf39e9b79a6cc8a2e7558f2c01c28a7b6b85c5bda/pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9", size = 4283912 }, - { url = "https://files.pythonhosted.org/packages/85/b1/d95d4f7ca3a6c1ae120959605875a31a3c209c4e50f0029dc1a87566cf46/pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488", size = 4195815 }, - { url = "https://files.pythonhosted.org/packages/41/c3/94f33af0762ed76b5a237c5797e088aa57f2b7fa8ee7932d399087be66a8/pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f", size = 4366117 }, - { url = "https://files.pythonhosted.org/packages/ba/3c/443e7ef01f597497268899e1cca95c0de947c9bbf77a8f18b3c126681e5d/pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb", size = 4278607 }, - { url = "https://files.pythonhosted.org/packages/26/95/1495304448b0081e60c0c5d63f928ef48bb290acee7385804426fa395a21/pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97", size = 4410685 }, - { url = "https://files.pythonhosted.org/packages/45/da/861e1df971ef0de9870720cb309ca4d553b26a9483ec9be3a7bf1de4a095/pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50", size = 2249185 }, - { url = "https://files.pythonhosted.org/packages/d5/4e/78f7c5202ea2a772a5ab05069c1b82503e6353cd79c7e474d4945f4b82c3/pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c", size = 2566726 }, - { url = "https://files.pythonhosted.org/packages/77/e4/6e84eada35cbcc646fc1870f72ccfd4afacb0fae0c37ffbffe7f5dc24bf1/pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1", size = 2254585 }, - { url = "https://files.pythonhosted.org/packages/f0/eb/f7e21b113dd48a9c97d364e0915b3988c6a0b6207652f5a92372871b7aa4/pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc", size = 3154705 }, - { url = "https://files.pythonhosted.org/packages/25/b3/2b54a1d541accebe6bd8b1358b34ceb2c509f51cb7dcda8687362490da5b/pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a", size = 2979222 }, - { url = "https://files.pythonhosted.org/packages/20/12/1a41eddad8265c5c19dda8fb6c269ce15ee25e0b9f8f26286e6202df6693/pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3", size = 4190220 }, - { url = "https://files.pythonhosted.org/packages/a9/9b/8a8c4d07d77447b7457164b861d18f5a31ae6418ef5c07f6f878fa09039a/pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5", size = 4291399 }, - { url = "https://files.pythonhosted.org/packages/fc/e4/130c5fab4a54d3991129800dd2801feeb4b118d7630148cd67f0e6269d4c/pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b", size = 4202709 }, - { url = "https://files.pythonhosted.org/packages/39/63/b3fc299528d7df1f678b0666002b37affe6b8751225c3d9c12cf530e73ed/pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa", size = 4372556 }, - { url = "https://files.pythonhosted.org/packages/c6/a6/694122c55b855b586c26c694937d36bb8d3b09c735ff41b2f315c6e66a10/pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306", size = 4287187 }, - { url = "https://files.pythonhosted.org/packages/ba/a9/f9d763e2671a8acd53d29b1e284ca298bc10a595527f6be30233cdb9659d/pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9", size = 4418468 }, - { url = "https://files.pythonhosted.org/packages/6e/0e/b5cbad2621377f11313a94aeb44ca55a9639adabcaaa073597a1925f8c26/pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5", size = 2249249 }, - { url = "https://files.pythonhosted.org/packages/dc/83/1470c220a4ff06cd75fc609068f6605e567ea51df70557555c2ab6516b2c/pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291", size = 2566769 }, - { url = "https://files.pythonhosted.org/packages/52/98/def78c3a23acee2bcdb2e52005fb2810ed54305602ec1bfcfab2bda6f49f/pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9", size = 2254611 }, - { url = "https://files.pythonhosted.org/packages/1c/a3/26e606ff0b2daaf120543e537311fa3ae2eb6bf061490e4fea51771540be/pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923", size = 3147642 }, - { url = "https://files.pythonhosted.org/packages/4f/d5/1caabedd8863526a6cfa44ee7a833bd97f945dc1d56824d6d76e11731939/pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903", size = 2978999 }, - { url = "https://files.pythonhosted.org/packages/d9/ff/5a45000826a1aa1ac6874b3ec5a856474821a1b59d838c4f6ce2ee518fe9/pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4", size = 4196794 }, - { url = "https://files.pythonhosted.org/packages/9d/21/84c9f287d17180f26263b5f5c8fb201de0f88b1afddf8a2597a5c9fe787f/pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f", size = 4300762 }, - { url = "https://files.pythonhosted.org/packages/84/39/63fb87cd07cc541438b448b1fed467c4d687ad18aa786a7f8e67b255d1aa/pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9", size = 4210468 }, - { url = "https://files.pythonhosted.org/packages/7f/42/6e0f2c2d5c60f499aa29be14f860dd4539de322cd8fb84ee01553493fb4d/pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7", size = 4381824 }, - { url = "https://files.pythonhosted.org/packages/31/69/1ef0fb9d2f8d2d114db982b78ca4eeb9db9a29f7477821e160b8c1253f67/pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6", size = 4296436 }, - { url = "https://files.pythonhosted.org/packages/44/ea/dad2818c675c44f6012289a7c4f46068c548768bc6c7f4e8c4ae5bbbc811/pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc", size = 4429714 }, - { url = "https://files.pythonhosted.org/packages/af/3a/da80224a6eb15bba7a0dcb2346e2b686bb9bf98378c0b4353cd88e62b171/pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6", size = 2249631 }, - { url = "https://files.pythonhosted.org/packages/57/97/73f756c338c1d86bb802ee88c3cab015ad7ce4b838f8a24f16b676b1ac7c/pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47", size = 2567533 }, - { url = "https://files.pythonhosted.org/packages/0b/30/2b61876e2722374558b871dfbfcbe4e406626d63f4f6ed92e9c8e24cac37/pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25", size = 2254890 }, - { url = "https://files.pythonhosted.org/packages/36/57/42a4dd825eab762ba9e690d696d894ba366e06791936056e26e099398cda/pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2", size = 3119239 }, - { url = "https://files.pythonhosted.org/packages/98/f7/25f9f9e368226a1d6cf3507081a1a7944eddd3ca7821023377043f5a83c8/pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2", size = 2950803 }, - { url = "https://files.pythonhosted.org/packages/59/01/98ead48a6c2e31e6185d4c16c978a67fe3ccb5da5c2ff2ba8475379bb693/pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b", size = 3281098 }, - { url = "https://files.pythonhosted.org/packages/51/c0/570255b2866a0e4d500a14f950803a2ec273bac7badc43320120b9262450/pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2", size = 3323665 }, - { url = "https://files.pythonhosted.org/packages/0e/75/689b4ec0483c42bfc7d1aacd32ade7a226db4f4fac57c6fdcdf90c0731e3/pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830", size = 3310533 }, - { url = "https://files.pythonhosted.org/packages/3d/30/38bd6149cf53da1db4bad304c543ade775d225961c4310f30425995cb9ec/pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734", size = 3414886 }, - { url = "https://files.pythonhosted.org/packages/ec/3d/c32a51d848401bd94cabb8767a39621496491ee7cd5199856b77da9b18ad/pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316", size = 2567508 }, -] - -[[package]] -name = "platformdirs" -version = "4.3.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, -] - -[[package]] -name = "pluggy" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, -] - -[[package]] -name = "portalocker" -version = "2.10.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pywin32", marker = "platform_system == 'Windows'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/fb/a70a4214956182e0d7a9099ab17d50bfcba1056188e9b14f35b9e2b62a0d/portalocker-2.10.1-py3-none-any.whl", hash = "sha256:53a5984ebc86a025552264b459b46a2086e269b21823cb572f8f28ee759e45bf", size = 18423 }, -] - -[[package]] -name = "pre-commit" -version = "3.8.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cfgv" }, - { name = "identify" }, - { name = "nodeenv" }, - { name = "pyyaml" }, - { name = "virtualenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/64/10/97ee2fa54dff1e9da9badbc5e35d0bbaef0776271ea5907eccf64140f72f/pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af", size = 177815 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/92/caae8c86e94681b42c246f0bca35c059a2f0529e5b92619f6aba4cf7e7b6/pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f", size = 204643 }, -] - -[[package]] -name = "protobuf" -version = "5.29.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/73/4e6295c1420a9d20c9c351db3a36109b4c9aa601916cb7c6871e3196a1ca/protobuf-5.29.2.tar.gz", hash = "sha256:b2cc8e8bb7c9326996f0e160137b0861f1a82162502658df2951209d0cb0309e", size = 424901 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/42/6db5387124708d619ffb990a846fb123bee546f52868039f8fa964c5bc54/protobuf-5.29.2-cp310-abi3-win32.whl", hash = "sha256:c12ba8249f5624300cf51c3d0bfe5be71a60c63e4dcf51ffe9a68771d958c851", size = 422697 }, - { url = "https://files.pythonhosted.org/packages/6c/38/2fcc968b377b531882d6ab2ac99b10ca6d00108394f6ff57c2395fb7baff/protobuf-5.29.2-cp310-abi3-win_amd64.whl", hash = "sha256:842de6d9241134a973aab719ab42b008a18a90f9f07f06ba480df268f86432f9", size = 434495 }, - { url = "https://files.pythonhosted.org/packages/cb/26/41debe0f6615fcb7e97672057524687ed86fcd85e3da3f031c30af8f0c51/protobuf-5.29.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a0c53d78383c851bfa97eb42e3703aefdc96d2036a41482ffd55dc5f529466eb", size = 417812 }, - { url = "https://files.pythonhosted.org/packages/e4/20/38fc33b60dcfb380507b99494aebe8c34b68b8ac7d32808c4cebda3f6f6b/protobuf-5.29.2-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:494229ecd8c9009dd71eda5fd57528395d1eacdf307dbece6c12ad0dd09e912e", size = 319562 }, - { url = "https://files.pythonhosted.org/packages/90/4d/c3d61e698e0e41d926dbff6aa4e57428ab1a6fc3b5e1deaa6c9ec0fd45cf/protobuf-5.29.2-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:b6b0d416bbbb9d4fbf9d0561dbfc4e324fd522f61f7af0fe0f282ab67b22477e", size = 319662 }, - { url = "https://files.pythonhosted.org/packages/f3/fd/c7924b4c2a1c61b8f4b64edd7a31ffacf63432135a2606f03a2f0d75a750/protobuf-5.29.2-py3-none-any.whl", hash = "sha256:fde4554c0e578a5a0bcc9a276339594848d1e89f9ea47b4427c80e5d72f90181", size = 172539 }, -] - -[[package]] -name = "pygments" -version = "2.18.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, -] - -[[package]] -name = "pymdown-extensions" -version = "10.13" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown" }, - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/49/87/4998d1aac5afea5b081238a609d9814f4c33cd5c7123503276d1105fb6a9/pymdown_extensions-10.13.tar.gz", hash = "sha256:e0b351494dc0d8d14a1f52b39b1499a00ef1566b4ba23dc74f1eba75c736f5dd", size = 843302 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/86/7f/46c7122186759350cf523c71d29712be534f769f073a1d980ce8f095072c/pymdown_extensions-10.13-py3-none-any.whl", hash = "sha256:80bc33d715eec68e683e04298946d47d78c7739e79d808203df278ee8ef89428", size = 264108 }, -] - -[[package]] -name = "pyparsing" -version = "3.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/d5/e5aeee5387091148a19e1145f63606619cb5f20b83fccb63efae6474e7b2/pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c", size = 920984 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/ec/2eb3cd785efd67806c46c13a17339708ddc346cbb684eade7a6e6f79536a/pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84", size = 106921 }, -] - -[[package]] -name = "pyro-api" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/25/d7/a0812f5c16b0d4464f80a64a44626c5fe200098070be0f32436dbb662775/pyro-api-0.1.2.tar.gz", hash = "sha256:a1b900d9580aa1c2fab3b123ab7ff33413744da7c5f440bd4aadc4d40d14d920", size = 7349 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/81/957ae78e6398460a7230b0eb9b8f1cb954c5e913e868e48d89324c68cec7/pyro_api-0.1.2-py3-none-any.whl", hash = "sha256:10e0e42e9e4401ce464dab79c870e50dfb4f413d326fa777f3582928ef9caf8f", size = 11981 }, -] - -[[package]] -name = "pyro-ppl" -version = "1.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "opt-einsum" }, - { name = "pyro-api" }, - { name = "torch" }, - { name = "tqdm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4c/2e/3bcba8688d58f8dc954cef6831c19d52b6017b035d783685d67cd99fa351/pyro_ppl-1.9.1.tar.gz", hash = "sha256:5e1596de276c038a3f77d2580a90d0a97126e0104900444a088eee620bb0d65e", size = 570861 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/37/def183a2a2c8619d92649d62fe0622c4c6c62f60e4151e8fbaa409e7d5ab/pyro_ppl-1.9.1-py3-none-any.whl", hash = "sha256:91fb2c8740d9d3bd548180ac5ecfa04552ed8c471a1ab66870180663b8f09852", size = 755956 }, -] - -[[package]] -name = "pytest" -version = "7.4.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/80/1f/9d8e98e4133ffb16c90f3b405c43e38d3abb715bb5d7a63a5a684f7e46a3/pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280", size = 1357116 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/ff/f6e8b8f39e08547faece4bd80f89d5a8de68a38b2d179cc1c4490ffa3286/pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8", size = 325287 }, -] - -[[package]] -name = "pytest-cases" -version = "3.8.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "decopatch" }, - { name = "makefun" }, - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/15/cb/d0d0810f9771b7daf35be7f283035c7a24c8c53b0272be458aebc2169ced/pytest_cases-3.8.6.tar.gz", hash = "sha256:5c24e0ab0cb6f8e802a469b7965906a333d3babb874586ebc56f7e2cbe1a7c44", size = 1092150 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/63/965396a32be6f2cd6d16650ef1e103caf5df92a530087bb0cf0b2509a04e/pytest_cases-3.8.6-py2.py3-none-any.whl", hash = "sha256:564c722492ea7e7ec3ac433fd14070180e65866f49fa35bfe938c0d5d9afba67", size = 107283 }, -] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, -] - -[[package]] -name = "pytz" -version = "2024.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 }, -] - -[[package]] -name = "pywin32" -version = "308" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/72/a6/3e9f2c474895c1bb61b11fa9640be00067b5c5b363c501ee9c3fa53aec01/pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e", size = 5927028 }, - { url = "https://files.pythonhosted.org/packages/d9/b4/84e2463422f869b4b718f79eb7530a4c1693e96b8a4e5e968de38be4d2ba/pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e", size = 6558484 }, - { url = "https://files.pythonhosted.org/packages/9f/8f/fb84ab789713f7c6feacaa08dad3ec8105b88ade8d1c4f0f0dfcaaa017d6/pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c", size = 7971454 }, - { url = "https://files.pythonhosted.org/packages/eb/e2/02652007469263fe1466e98439831d65d4ca80ea1a2df29abecedf7e47b7/pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a", size = 5928156 }, - { url = "https://files.pythonhosted.org/packages/48/ef/f4fb45e2196bc7ffe09cad0542d9aff66b0e33f6c0954b43e49c33cad7bd/pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b", size = 6559559 }, - { url = "https://files.pythonhosted.org/packages/79/ef/68bb6aa865c5c9b11a35771329e95917b5559845bd75b65549407f9fc6b4/pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6", size = 7972495 }, - { url = "https://files.pythonhosted.org/packages/00/7c/d00d6bdd96de4344e06c4afbf218bc86b54436a94c01c71a8701f613aa56/pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897", size = 5939729 }, - { url = "https://files.pythonhosted.org/packages/21/27/0c8811fbc3ca188f93b5354e7c286eb91f80a53afa4e11007ef661afa746/pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47", size = 6543015 }, - { url = "https://files.pythonhosted.org/packages/9d/0f/d40f8373608caed2255781a3ad9a51d03a594a1248cd632d6a298daca693/pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091", size = 7976033 }, -] - -[[package]] -name = "pyyaml" -version = "6.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, - { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, - { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, - { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, - { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, - { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, - { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, - { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, - { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, -] - -[[package]] -name = "pyyaml-env-tag" -version = "0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fb/8e/da1c6c58f751b70f8ceb1eb25bc25d524e8f14fe16edcce3f4e3ba08629c/pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb", size = 5631 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/66/bbb1dd374f5c870f59c5bb1db0e18cbe7fa739415a24cbd95b2d1f5ae0c4/pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069", size = 3911 }, -] - -[[package]] -name = "regex" -version = "2024.11.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/3c/4651f6b130c6842a8f3df82461a8950f923925db8b6961063e82744bddcc/regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91", size = 482674 }, - { url = "https://files.pythonhosted.org/packages/15/51/9f35d12da8434b489c7b7bffc205c474a0a9432a889457026e9bc06a297a/regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0", size = 287684 }, - { url = "https://files.pythonhosted.org/packages/bd/18/b731f5510d1b8fb63c6b6d3484bfa9a59b84cc578ac8b5172970e05ae07c/regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e", size = 284589 }, - { url = "https://files.pythonhosted.org/packages/78/a2/6dd36e16341ab95e4c6073426561b9bfdeb1a9c9b63ab1b579c2e96cb105/regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde", size = 782511 }, - { url = "https://files.pythonhosted.org/packages/1b/2b/323e72d5d2fd8de0d9baa443e1ed70363ed7e7b2fb526f5950c5cb99c364/regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e", size = 821149 }, - { url = "https://files.pythonhosted.org/packages/90/30/63373b9ea468fbef8a907fd273e5c329b8c9535fee36fc8dba5fecac475d/regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2", size = 809707 }, - { url = "https://files.pythonhosted.org/packages/f2/98/26d3830875b53071f1f0ae6d547f1d98e964dd29ad35cbf94439120bb67a/regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf", size = 781702 }, - { url = "https://files.pythonhosted.org/packages/87/55/eb2a068334274db86208ab9d5599ffa63631b9f0f67ed70ea7c82a69bbc8/regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c", size = 771976 }, - { url = "https://files.pythonhosted.org/packages/74/c0/be707bcfe98254d8f9d2cff55d216e946f4ea48ad2fd8cf1428f8c5332ba/regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86", size = 697397 }, - { url = "https://files.pythonhosted.org/packages/49/dc/bb45572ceb49e0f6509f7596e4ba7031f6819ecb26bc7610979af5a77f45/regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67", size = 768726 }, - { url = "https://files.pythonhosted.org/packages/5a/db/f43fd75dc4c0c2d96d0881967897926942e935d700863666f3c844a72ce6/regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d", size = 775098 }, - { url = "https://files.pythonhosted.org/packages/99/d7/f94154db29ab5a89d69ff893159b19ada89e76b915c1293e98603d39838c/regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2", size = 839325 }, - { url = "https://files.pythonhosted.org/packages/f7/17/3cbfab1f23356fbbf07708220ab438a7efa1e0f34195bf857433f79f1788/regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008", size = 843277 }, - { url = "https://files.pythonhosted.org/packages/7e/f2/48b393b51900456155de3ad001900f94298965e1cad1c772b87f9cfea011/regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62", size = 773197 }, - { url = "https://files.pythonhosted.org/packages/45/3f/ef9589aba93e084cd3f8471fded352826dcae8489b650d0b9b27bc5bba8a/regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e", size = 261714 }, - { url = "https://files.pythonhosted.org/packages/42/7e/5f1b92c8468290c465fd50c5318da64319133231415a8aa6ea5ab995a815/regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519", size = 274042 }, - { url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669 }, - { url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684 }, - { url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589 }, - { url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121 }, - { url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275 }, - { url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257 }, - { url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727 }, - { url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667 }, - { url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963 }, - { url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700 }, - { url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592 }, - { url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929 }, - { url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213 }, - { url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734 }, - { url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052 }, - { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781 }, - { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455 }, - { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759 }, - { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976 }, - { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077 }, - { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160 }, - { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896 }, - { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997 }, - { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725 }, - { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481 }, - { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896 }, - { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138 }, - { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692 }, - { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135 }, - { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567 }, -] - -[[package]] -name = "requests" -version = "2.32.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, -] - -[[package]] -name = "ruff" -version = "0.8.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/34/37/9c02181ef38d55b77d97c68b78e705fd14c0de0e5d085202bb2b52ce5be9/ruff-0.8.4.tar.gz", hash = "sha256:0d5f89f254836799af1615798caa5f80b7f935d7a670fad66c5007928e57ace8", size = 3402103 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/67/f480bf2f2723b2e49af38ed2be75ccdb2798fca7d56279b585c8f553aaab/ruff-0.8.4-py3-none-linux_armv6l.whl", hash = "sha256:58072f0c06080276804c6a4e21a9045a706584a958e644353603d36ca1eb8a60", size = 10546415 }, - { url = "https://files.pythonhosted.org/packages/eb/7a/5aba20312c73f1ce61814e520d1920edf68ca3b9c507bd84d8546a8ecaa8/ruff-0.8.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ffb60904651c00a1e0b8df594591770018a0f04587f7deeb3838344fe3adabac", size = 10346113 }, - { url = "https://files.pythonhosted.org/packages/76/f4/c41de22b3728486f0aa95383a44c42657b2db4062f3234ca36fc8cf52d8b/ruff-0.8.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ddf5d654ac0d44389f6bf05cee4caeefc3132a64b58ea46738111d687352296", size = 9943564 }, - { url = "https://files.pythonhosted.org/packages/0e/f0/afa0d2191af495ac82d4cbbfd7a94e3df6f62a04ca412033e073b871fc6d/ruff-0.8.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e248b1f0fa2749edd3350a2a342b67b43a2627434c059a063418e3d375cfe643", size = 10805522 }, - { url = "https://files.pythonhosted.org/packages/12/57/5d1e9a0fd0c228e663894e8e3a8e7063e5ee90f8e8e60cf2085f362bfa1a/ruff-0.8.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf197b98ed86e417412ee3b6c893f44c8864f816451441483253d5ff22c0e81e", size = 10306763 }, - { url = "https://files.pythonhosted.org/packages/04/df/f069fdb02e408be8aac6853583572a2873f87f866fe8515de65873caf6b8/ruff-0.8.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c41319b85faa3aadd4d30cb1cffdd9ac6b89704ff79f7664b853785b48eccdf3", size = 11359574 }, - { url = "https://files.pythonhosted.org/packages/d3/04/37c27494cd02e4a8315680debfc6dfabcb97e597c07cce0044db1f9dfbe2/ruff-0.8.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9f8402b7c4f96463f135e936d9ab77b65711fcd5d72e5d67597b543bbb43cf3f", size = 12094851 }, - { url = "https://files.pythonhosted.org/packages/81/b1/c5d7fb68506cab9832d208d03ea4668da9a9887a4a392f4f328b1bf734ad/ruff-0.8.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4e56b3baa9c23d324ead112a4fdf20db9a3f8f29eeabff1355114dd96014604", size = 11655539 }, - { url = "https://files.pythonhosted.org/packages/ef/38/8f8f2c8898dc8a7a49bc340cf6f00226917f0f5cb489e37075bcb2ce3671/ruff-0.8.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:736272574e97157f7edbbb43b1d046125fce9e7d8d583d5d65d0c9bf2c15addf", size = 12912805 }, - { url = "https://files.pythonhosted.org/packages/06/dd/fa6660c279f4eb320788876d0cff4ea18d9af7d9ed7216d7bd66877468d0/ruff-0.8.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fe710ab6061592521f902fca7ebcb9fabd27bc7c57c764298b1c1f15fff720", size = 11205976 }, - { url = "https://files.pythonhosted.org/packages/a8/d7/de94cc89833b5de455750686c17c9e10f4e1ab7ccdc5521b8fe911d1477e/ruff-0.8.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:13e9ec6d6b55f6da412d59953d65d66e760d583dd3c1c72bf1f26435b5bfdbae", size = 10792039 }, - { url = "https://files.pythonhosted.org/packages/6d/15/3e4906559248bdbb74854af684314608297a05b996062c9d72e0ef7c7097/ruff-0.8.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:97d9aefef725348ad77d6db98b726cfdb075a40b936c7984088804dfd38268a7", size = 10400088 }, - { url = "https://files.pythonhosted.org/packages/a2/21/9ed4c0e8133cb4a87a18d470f534ad1a8a66d7bec493bcb8bda2d1a5d5be/ruff-0.8.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ab78e33325a6f5374e04c2ab924a3367d69a0da36f8c9cb6b894a62017506111", size = 10900814 }, - { url = "https://files.pythonhosted.org/packages/0d/5d/122a65a18955bd9da2616b69bc839351f8baf23b2805b543aa2f0aed72b5/ruff-0.8.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8ef06f66f4a05c3ddbc9121a8b0cecccd92c5bf3dd43b5472ffe40b8ca10f0f8", size = 11268828 }, - { url = "https://files.pythonhosted.org/packages/43/a9/1676ee9106995381e3d34bccac5bb28df70194167337ed4854c20f27c7ba/ruff-0.8.4-py3-none-win32.whl", hash = "sha256:552fb6d861320958ca5e15f28b20a3d071aa83b93caee33a87b471f99a6c0835", size = 8805621 }, - { url = "https://files.pythonhosted.org/packages/10/98/ed6b56a30ee76771c193ff7ceeaf1d2acc98d33a1a27b8479cbdb5c17a23/ruff-0.8.4-py3-none-win_amd64.whl", hash = "sha256:f21a1143776f8656d7f364bd264a9d60f01b7f52243fbe90e7670c0dfe0cf65d", size = 9660086 }, - { url = "https://files.pythonhosted.org/packages/13/9f/026e18ca7d7766783d779dae5e9c656746c6ede36ef73c6d934aaf4a6dec/ruff-0.8.4-py3-none-win_arm64.whl", hash = "sha256:9183dd615d8df50defa8b1d9a074053891ba39025cf5ae88e8bcb52edcc4bf08", size = 9074500 }, -] - -[[package]] -name = "scikit-learn" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "joblib" }, - { name = "numpy" }, - { name = "scipy" }, - { name = "threadpoolctl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fa/19/5aa2002044afc297ecaf1e3517ed07bba4aece3b5613b5160c1212995fc8/scikit_learn-1.6.0.tar.gz", hash = "sha256:9d58481f9f7499dff4196927aedd4285a0baec8caa3790efbe205f13de37dd6e", size = 7074944 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/97/55060f91a5e7c4df945e5a69b16148b5f2256e6e1ea3f17da8e27edf9953/scikit_learn-1.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:366fb3fa47dce90afed3d6106183f4978d6f24cfd595c2373424171b915ee718", size = 12060299 }, - { url = "https://files.pythonhosted.org/packages/36/7b/8c5dfc64a8344ebf2ae493d59af4b3650588051f654e164ff4f9952877b3/scikit_learn-1.6.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:59cd96a8d9f8dfd546f5d6e9787e1b989e981388d7803abbc9efdcde61e47460", size = 11105443 }, - { url = "https://files.pythonhosted.org/packages/25/9f/61544f2a5cae1bc27c97f0ec9ffcc9837e469f215817608840a4ccbb277a/scikit_learn-1.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efa7a579606c73a0b3d210e33ea410ea9e1af7933fe324cb7e6fbafae4ea5948", size = 12637137 }, - { url = "https://files.pythonhosted.org/packages/50/79/d21599fc44d2d497ced440480670b6314ebc00308e3bae0d0ebca44cd481/scikit_learn-1.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a46d3ca0f11a540b8eaddaf5e38172d8cd65a86cb3e3632161ec96c0cffb774c", size = 13490128 }, - { url = "https://files.pythonhosted.org/packages/ff/87/788da20cfefcd261123d4bb015b2de076e49cdd3b811b55e6811acd3cb21/scikit_learn-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:5be4577769c5dde6e1b53de8e6520f9b664ab5861dd57acee47ad119fd7405d6", size = 11118524 }, - { url = "https://files.pythonhosted.org/packages/07/95/070d6e70f735d13f1c10afebb65ba3526125b7d6c6fc7022651a4a061148/scikit_learn-1.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1f50b4f24cf12a81c3c09958ae3b864d7534934ca66ded3822de4996d25d7285", size = 12095168 }, - { url = "https://files.pythonhosted.org/packages/72/3d/0381e3a59ebd4154e6a61b0ceaf299c3c141035033dd3b868776cd9af02d/scikit_learn-1.6.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:eb9ae21f387826da14b0b9cb1034f5048ddb9182da429c689f5f4a87dc96930b", size = 11108880 }, - { url = "https://files.pythonhosted.org/packages/fe/2d/0999ae3eed2ac67b1b3cd7fc33370bd5ca59a7514ffe43ae2b6f3cd85b9b/scikit_learn-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0baa91eeb8c32632628874a5c91885eaedd23b71504d24227925080da075837a", size = 12585449 }, - { url = "https://files.pythonhosted.org/packages/0e/ec/1b15b59c6cc7a993320a52234369e787f50345a4753e50d5a015a91e1a20/scikit_learn-1.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c716d13ba0a2f8762d96ff78d3e0cde90bc9c9b5c13d6ab6bb9b2d6ca6705fd", size = 13489728 }, - { url = "https://files.pythonhosted.org/packages/96/a2/cbfb5743de748d574ffdfd557e9cb29ba4f8b8a3e07836c6c176f713de2f/scikit_learn-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:9aafd94bafc841b626681e626be27bf1233d5a0f20f0a6fdb4bee1a1963c6643", size = 11132946 }, - { url = "https://files.pythonhosted.org/packages/18/0c/a5de627aa57b028aea7026cb3bbeaf63be3158adc118212d6cc7843d939a/scikit_learn-1.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:04a5ba45c12a5ff81518aa4f1604e826a45d20e53da47b15871526cda4ff5174", size = 12096999 }, - { url = "https://files.pythonhosted.org/packages/a3/7d/02a96e6fb28ddb213e84b1b4a44148d26ec96fc9db9c74e050277e009892/scikit_learn-1.6.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:21fadfc2ad7a1ce8bd1d90f23d17875b84ec765eecbbfc924ff11fb73db582ce", size = 11160579 }, - { url = "https://files.pythonhosted.org/packages/70/28/77b071f541d75247e6c3403f19aaa634371e972691f6aa1838ca9fd4cc52/scikit_learn-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30f34bb5fde90e020653bb84dcb38b6c83f90c70680dbd8c38bd9becbad7a127", size = 12246543 }, - { url = "https://files.pythonhosted.org/packages/17/0e/e6bb84074f1081245a165c0ee775ecef24beae9d2f2e24bcac0c9f155f13/scikit_learn-1.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dad624cffe3062276a0881d4e441bc9e3b19d02d17757cd6ae79a9d192a0027", size = 13140402 }, - { url = "https://files.pythonhosted.org/packages/21/1d/3df58df8bd425f425df9f90b316618ace62b7f1f838ac1580191025cc735/scikit_learn-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:2fce7950a3fad85e0a61dc403df0f9345b53432ac0e47c50da210d22c60b6d85", size = 11103596 }, -] - -[[package]] -name = "scipy" -version = "1.14.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/62/11/4d44a1f274e002784e4dbdb81e0ea96d2de2d1045b2132d5af62cc31fd28/scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417", size = 58620554 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/68/3bc0cfaf64ff507d82b1e5d5b64521df4c8bf7e22bc0b897827cbee9872c/scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389", size = 39069598 }, - { url = "https://files.pythonhosted.org/packages/43/a5/8d02f9c372790326ad405d94f04d4339482ec082455b9e6e288f7100513b/scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3", size = 29879676 }, - { url = "https://files.pythonhosted.org/packages/07/42/0e0bea9666fcbf2cb6ea0205db42c81b1f34d7b729ba251010edf9c80ebd/scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0", size = 23088696 }, - { url = "https://files.pythonhosted.org/packages/15/47/298ab6fef5ebf31b426560e978b8b8548421d4ed0bf99263e1eb44532306/scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3", size = 25470699 }, - { url = "https://files.pythonhosted.org/packages/d8/df/cdb6be5274bc694c4c22862ac3438cb04f360ed9df0aecee02ce0b798380/scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d", size = 35606631 }, - { url = "https://files.pythonhosted.org/packages/47/78/b0c2c23880dd1e99e938ad49ccfb011ae353758a2dc5ed7ee59baff684c3/scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69", size = 41178528 }, - { url = "https://files.pythonhosted.org/packages/5d/aa/994b45c34b897637b853ec04334afa55a85650a0d11dacfa67232260fb0a/scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad", size = 42784535 }, - { url = "https://files.pythonhosted.org/packages/e7/1c/8daa6df17a945cb1a2a1e3bae3c49643f7b3b94017ff01a4787064f03f84/scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5", size = 44772117 }, - { url = "https://files.pythonhosted.org/packages/b2/ab/070ccfabe870d9f105b04aee1e2860520460ef7ca0213172abfe871463b9/scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675", size = 39076999 }, - { url = "https://files.pythonhosted.org/packages/a7/c5/02ac82f9bb8f70818099df7e86c3ad28dae64e1347b421d8e3adf26acab6/scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2", size = 29894570 }, - { url = "https://files.pythonhosted.org/packages/ed/05/7f03e680cc5249c4f96c9e4e845acde08eb1aee5bc216eff8a089baa4ddb/scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617", size = 23103567 }, - { url = "https://files.pythonhosted.org/packages/5e/fc/9f1413bef53171f379d786aabc104d4abeea48ee84c553a3e3d8c9f96a9c/scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8", size = 25499102 }, - { url = "https://files.pythonhosted.org/packages/c2/4b/b44bee3c2ddc316b0159b3d87a3d467ef8d7edfd525e6f7364a62cd87d90/scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37", size = 35586346 }, - { url = "https://files.pythonhosted.org/packages/93/6b/701776d4bd6bdd9b629c387b5140f006185bd8ddea16788a44434376b98f/scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2", size = 41165244 }, - { url = "https://files.pythonhosted.org/packages/06/57/e6aa6f55729a8f245d8a6984f2855696c5992113a5dc789065020f8be753/scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2", size = 42817917 }, - { url = "https://files.pythonhosted.org/packages/ea/c2/5ecadc5fcccefaece775feadcd795060adf5c3b29a883bff0e678cfe89af/scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94", size = 44781033 }, - { url = "https://files.pythonhosted.org/packages/c0/04/2bdacc8ac6387b15db6faa40295f8bd25eccf33f1f13e68a72dc3c60a99e/scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d", size = 39128781 }, - { url = "https://files.pythonhosted.org/packages/c8/53/35b4d41f5fd42f5781dbd0dd6c05d35ba8aa75c84ecddc7d44756cd8da2e/scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07", size = 29939542 }, - { url = "https://files.pythonhosted.org/packages/66/67/6ef192e0e4d77b20cc33a01e743b00bc9e68fb83b88e06e636d2619a8767/scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5", size = 23148375 }, - { url = "https://files.pythonhosted.org/packages/f6/32/3a6dedd51d68eb7b8e7dc7947d5d841bcb699f1bf4463639554986f4d782/scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc", size = 25578573 }, - { url = "https://files.pythonhosted.org/packages/f0/5a/efa92a58dc3a2898705f1dc9dbaf390ca7d4fba26d6ab8cfffb0c72f656f/scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310", size = 35319299 }, - { url = "https://files.pythonhosted.org/packages/8e/ee/8a26858ca517e9c64f84b4c7734b89bda8e63bec85c3d2f432d225bb1886/scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066", size = 40849331 }, - { url = "https://files.pythonhosted.org/packages/a5/cd/06f72bc9187840f1c99e1a8750aad4216fc7dfdd7df46e6280add14b4822/scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1", size = 42544049 }, - { url = "https://files.pythonhosted.org/packages/aa/7d/43ab67228ef98c6b5dd42ab386eae2d7877036970a0d7e3dd3eb47a0d530/scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f", size = 44521212 }, -] - -[[package]] -name = "seaborn" -version = "0.13.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "matplotlib" }, - { name = "numpy" }, - { name = "pandas" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914 }, -] - -[[package]] -name = "setuptools" -version = "75.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/54/292f26c208734e9a7f067aea4a7e282c080750c4546559b58e2e45413ca0/setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6", size = 1337429 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/55/21/47d163f615df1d30c094f6c8bbb353619274edccf0327b185cc2493c2c33/setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d", size = 1224032 }, -] - -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, -] - -[[package]] -name = "submitit" -version = "1.5.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cloudpickle" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1c/0a/854409283d533279b1b7523ebb65e1926ff611ee81bdf9c298bbed7b75ac/submitit-1.5.2.tar.gz", hash = "sha256:36a8a54ad4e10171111e7618eefe28fe819f931a89c9cd1f6d2770900c013f12", size = 80390 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/a4/90123871996bfb8a7148cd11d61e7a0ddc0118114c071730b3dc3a05c7bc/submitit-1.5.2-py3-none-any.whl", hash = "sha256:c6d5867fbcc78588d0ded3338436903f8db9fdb759f80e9639e6025a9ea32ade", size = 74917 }, -] - -[[package]] -name = "sympy" -version = "1.13.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mpmath" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/99/5a5b6f19ff9f083671ddf7b9632028436167cd3d33e11015754e41b249a4/sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f", size = 7533040 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/fe/81695a1aa331a842b582453b605175f419fe8540355886031328089d840a/sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8", size = 6189177 }, -] - -[[package]] -name = "tensorboard" -version = "2.18.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "absl-py" }, - { name = "grpcio" }, - { name = "markdown" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "protobuf" }, - { name = "setuptools" }, - { name = "six" }, - { name = "tensorboard-data-server" }, - { name = "werkzeug" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/de/021c1d407befb505791764ad2cbd56ceaaa53a746baed01d2e2143f05f18/tensorboard-2.18.0-py3-none-any.whl", hash = "sha256:107ca4821745f73e2aefa02c50ff70a9b694f39f790b11e6f682f7d326745eab", size = 5503036 }, -] - -[[package]] -name = "tensorboard-data-server" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/13/e503968fefabd4c6b2650af21e110aa8466fe21432cd7c43a84577a89438/tensorboard_data_server-0.7.2-py3-none-any.whl", hash = "sha256:7e0610d205889588983836ec05dc098e80f97b7e7bbff7e994ebb78f578d0ddb", size = 2356 }, - { url = "https://files.pythonhosted.org/packages/b7/85/dabeaf902892922777492e1d253bb7e1264cadce3cea932f7ff599e53fea/tensorboard_data_server-0.7.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:9fe5d24221b29625dbc7328b0436ca7fc1c23de4acf4d272f1180856e32f9f60", size = 4823598 }, - { url = "https://files.pythonhosted.org/packages/73/c6/825dab04195756cf8ff2e12698f22513b3db2f64925bdd41671bfb33aaa5/tensorboard_data_server-0.7.2-py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:ef687163c24185ae9754ed5650eb5bc4d84ff257aabdc33f0cc6f74d8ba54530", size = 6590363 }, -] - -[[package]] -name = "threadpoolctl" -version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/55/b5148dcbf72f5cde221f8bfe3b6a540da7aa1842f6b491ad979a6c8b84af/threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107", size = 41936 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/2c/ffbf7a134b9ab11a67b0cf0726453cedd9c5043a4fe7a35d1cefa9a1bcfb/threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467", size = 18414 }, -] - -[[package]] -name = "tomli" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, -] - -[[package]] -name = "torch" -version = "2.5.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock" }, - { name = "fsspec" }, - { name = "jinja2" }, - { name = "networkx" }, - { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "setuptools", marker = "python_full_version >= '3.12'" }, - { name = "sympy" }, - { name = "triton", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "typing-extensions" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/ef/834af4a885b31a0b32fff2d80e1e40f771e1566ea8ded55347502440786a/torch-2.5.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:71328e1bbe39d213b8721678f9dcac30dfc452a46d586f1d514a6aa0a99d4744", size = 906446312 }, - { url = "https://files.pythonhosted.org/packages/69/f0/46e74e0d145f43fa506cb336eaefb2d240547e4ce1f496e442711093ab25/torch-2.5.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:34bfa1a852e5714cbfa17f27c49d8ce35e1b7af5608c4bc6e81392c352dbc601", size = 91919522 }, - { url = "https://files.pythonhosted.org/packages/a5/13/1eb674c8efbd04d71e4a157ceba991904f633e009a584dd65dccbafbb648/torch-2.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:32a037bd98a241df6c93e4c789b683335da76a2ac142c0973675b715102dc5fa", size = 203088048 }, - { url = "https://files.pythonhosted.org/packages/a9/9d/e0860474ee0ff8f6ef2c50ec8f71a250f38d78a9b9df9fd241ad3397a65b/torch-2.5.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:23d062bf70776a3d04dbe74db950db2a5245e1ba4f27208a87f0d743b0d06e86", size = 63877046 }, - { url = "https://files.pythonhosted.org/packages/d1/35/e8b2daf02ce933e4518e6f5682c72fd0ed66c15910ea1fb4168f442b71c4/torch-2.5.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:de5b7d6740c4b636ef4db92be922f0edc425b65ed78c5076c43c42d362a45457", size = 906474467 }, - { url = "https://files.pythonhosted.org/packages/40/04/bd91593a4ca178ece93ca55f27e2783aa524aaccbfda66831d59a054c31e/torch-2.5.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:340ce0432cad0d37f5a31be666896e16788f1adf8ad7be481196b503dad675b9", size = 91919450 }, - { url = "https://files.pythonhosted.org/packages/0d/4a/e51420d46cfc90562e85af2fee912237c662ab31140ab179e49bd69401d6/torch-2.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:603c52d2fe06433c18b747d25f5c333f9c1d58615620578c326d66f258686f9a", size = 203098237 }, - { url = "https://files.pythonhosted.org/packages/d0/db/5d9cbfbc7968d79c5c09a0bc0bc3735da079f2fd07cc10498a62b320a480/torch-2.5.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:31f8c39660962f9ae4eeec995e3049b5492eb7360dd4f07377658ef4d728fa4c", size = 63884466 }, - { url = "https://files.pythonhosted.org/packages/8b/5c/36c114d120bfe10f9323ed35061bc5878cc74f3f594003854b0ea298942f/torch-2.5.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:ed231a4b3a5952177fafb661213d690a72caaad97d5824dd4fc17ab9e15cec03", size = 906389343 }, - { url = "https://files.pythonhosted.org/packages/6d/69/d8ada8b6e0a4257556d5b4ddeb4345ea8eeaaef3c98b60d1cca197c7ad8e/torch-2.5.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:3f4b7f10a247e0dcd7ea97dc2d3bfbfc90302ed36d7f3952b0008d0df264e697", size = 91811673 }, - { url = "https://files.pythonhosted.org/packages/5f/ba/607d013b55b9fd805db2a5c2662ec7551f1910b4eef39653eeaba182c5b2/torch-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:73e58e78f7d220917c5dbfad1a40e09df9929d3b95d25e57d9f8558f84c9a11c", size = 203046841 }, - { url = "https://files.pythonhosted.org/packages/57/6c/bf52ff061da33deb9f94f4121fde7ff3058812cb7d2036c97bc167793bd1/torch-2.5.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:8c712df61101964eb11910a846514011f0b6f5920c55dbf567bff8a34163d5b1", size = 63858109 }, -] - -[[package]] -name = "torchvision" -version = "0.20.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "pillow" }, - { name = "torch" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/59/aea68d755da1451e1a0d894528a7edc9b58eb30d33e274bf21bef28dad1a/torchvision-0.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4878fefb96ef293d06c27210918adc83c399d9faaf34cda5a63e129f772328f1", size = 1787552 }, - { url = "https://files.pythonhosted.org/packages/a2/f6/7ff89a9f8703f623f5664afd66c8600e3f09fe188e1e0b7e6f9a8617f865/torchvision-0.20.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:8ffbdf8bf5b30eade22d459f5a313329eeadb20dc75efa142987b53c007098c3", size = 7238975 }, - { url = "https://files.pythonhosted.org/packages/f7/ce/4c31e9b96cc4f9fec746b258d2aa35f8d1247f4f58d63f9c505ea5eb254d/torchvision-0.20.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:75f8a4d51a593c4bab6c9bf7d75bdd88691b00a53b07656678bc55a3a753dd73", size = 14265343 }, - { url = "https://files.pythonhosted.org/packages/17/11/b5ce67715bbbec8798fb48c4a20ac28828aec1710ac01091a3eddcb8e075/torchvision-0.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:22c2fa44e20eb404b85e42b22b453863a14b0927d25e550fd4f84eea97fa5b39", size = 1562413 }, - { url = "https://files.pythonhosted.org/packages/28/57/4d7ad90be612f5ac6c4bdafcb0ff13e818e14a340a88c8ca00d9ed8c2dad/torchvision-0.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:344b339e15e6bbb59ee0700772616d0afefd209920c762b1604368d8c3458322", size = 1787548 }, - { url = "https://files.pythonhosted.org/packages/de/e9/e190ecec448d5a2abad8348cf085fcb39962a491e3f40dcb023721e04feb/torchvision-0.20.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:86f6523dee420000fe14c3527f6c8e0175139fda7d995b187f54a0b0ebec7eb6", size = 7241222 }, - { url = "https://files.pythonhosted.org/packages/b1/a3/cbb8177e5e379f0c040b00c6f80f14d323a97e30495d7115d169b101b2f7/torchvision-0.20.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:a40d766345927639da322c693934e5f91b1ba2218846c7104b868dea2314ce8e", size = 14267510 }, - { url = "https://files.pythonhosted.org/packages/69/55/ce836703ff77bb21582c3098d5311f8ddde7eadc7eab04be9561961f4725/torchvision-0.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:5b501d5c04b034d2ecda96a31ed050e383cf8201352e4c9276ca249cbecfded0", size = 1562402 }, - { url = "https://files.pythonhosted.org/packages/c5/eb/4ba19616378f2bc085999432fded2b7dfdbdccc6dd0fc293203452508100/torchvision-0.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1a31256ff945d64f006bb306813a7c95a531fe16bfb2535c837dd4c104533d7a", size = 1787553 }, - { url = "https://files.pythonhosted.org/packages/d4/75/00a852275ade58d3dc474530f7a7b6bc999a817148f0eb59d4fde12eb955/torchvision-0.20.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:17cd78adddf81dac57d7dccc9277a4d686425b1c55715f308769770cb26cad5c", size = 7240323 }, - { url = "https://files.pythonhosted.org/packages/af/f0/ca1445406eb12cbeb7a41fc833a1941ede78e7c55621198b83ecd7bcfd0f/torchvision-0.20.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:9f853ba4497ac4691815ad41b523ee23cf5ba4f87b1ce869d704052e233ca8b7", size = 14266936 }, - { url = "https://files.pythonhosted.org/packages/c3/18/00993d420b1d6e88582e51d4bc82c824c99a2e9c045d50eaf9b34fff729a/torchvision-0.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:4a330422c36dbfc946d3a6c1caec3489db07ecdf3675d83369adb2e5a0ca17c4", size = 1562392 }, -] - -[[package]] -name = "tqdm" -version = "4.67.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, -] - -[[package]] -name = "triton" -version = "3.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/29/69aa56dc0b2eb2602b553881e34243475ea2afd9699be042316842788ff5/triton-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b0dd10a925263abbe9fa37dcde67a5e9b2383fc269fdf59f5657cac38c5d1d8", size = 209460013 }, - { url = "https://files.pythonhosted.org/packages/86/17/d9a5cf4fcf46291856d1e90762e36cbabd2a56c7265da0d1d9508c8e3943/triton-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f34f6e7885d1bf0eaaf7ba875a5f0ce6f3c13ba98f9503651c1e6dc6757ed5c", size = 209506424 }, - { url = "https://files.pythonhosted.org/packages/78/eb/65f5ba83c2a123f6498a3097746607e5b2f16add29e36765305e4ac7fdd8/triton-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8182f42fd8080a7d39d666814fa36c5e30cc00ea7eeeb1a2983dbb4c99a0fdc", size = 209551444 }, -] - -[[package]] -name = "typeguard" -version = "4.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/62/c3/400917dd37d7b8c07e9723f3046818530423e1e759a56a22133362adab00/typeguard-4.4.1.tar.gz", hash = "sha256:0d22a89d00b453b47c49875f42b6601b961757541a2e1e0ef517b6e24213c21b", size = 74959 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/53/9465dedf2d69fe26008e7732cf6e0a385e387c240869e7d54eed49782a3c/typeguard-4.4.1-py3-none-any.whl", hash = "sha256:9324ec07a27ec67fc54a9c063020ca4c0ae6abad5e9f0f9804ca59aee68c6e21", size = 35635 }, -] - -[[package]] -name = "types-pyyaml" -version = "6.0.12.20241230" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/f9/4d566925bcf9396136c0a2e5dc7e230ff08d86fa011a69888dd184469d80/types_pyyaml-6.0.12.20241230.tar.gz", hash = "sha256:7f07622dbd34bb9c8b264fe860a17e0efcad00d50b5f27e93984909d9363498c", size = 17078 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/c1/48474fbead512b70ccdb4f81ba5eb4a58f69d100ba19f17c92c0c4f50ae6/types_PyYAML-6.0.12.20241230-py3-none-any.whl", hash = "sha256:fa4d32565219b68e6dee5f67534c722e53c00d1cfc09c435ef04d7353e1e96e6", size = 20029 }, -] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, -] - -[[package]] -name = "tzdata" -version = "2024.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 }, -] - -[[package]] -name = "urllib3" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, -] - -[[package]] -name = "verspec" -version = "0.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/44/8126f9f0c44319b2efc65feaad589cadef4d77ece200ae3c9133d58464d0/verspec-0.1.0.tar.gz", hash = "sha256:c4504ca697b2056cdb4bfa7121461f5a0e81809255b41c03dda4ba823637c01e", size = 27123 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl", hash = "sha256:741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31", size = 19640 }, -] - -[[package]] -name = "virtualenv" -version = "20.28.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "distlib" }, - { name = "filelock" }, - { name = "platformdirs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bf/75/53316a5a8050069228a2f6d11f32046cfa94fbb6cc3f08703f59b873de2e/virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa", size = 7650368 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/f9/0919cf6f1432a8c4baa62511f8f8da8225432d22e83e3476f5be1a1edc6e/virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0", size = 4276702 }, -] - -[[package]] -name = "watchdog" -version = "6.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390 }, - { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389 }, - { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020 }, - { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393 }, - { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392 }, - { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019 }, - { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471 }, - { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449 }, - { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054 }, - { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902 }, - { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380 }, - { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 }, - { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 }, - { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 }, - { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 }, - { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 }, - { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 }, - { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 }, - { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 }, - { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 }, - { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 }, -] - -[[package]] -name = "werkzeug" -version = "3.1.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498 }, -] - -[[package]] -name = "zipp" -version = "3.21.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630 }, -]