From 0a8c79a25197e7b003cca22dfa9eafe136fca893 Mon Sep 17 00:00:00 2001 From: Adrien Couratier Date: Mon, 5 Aug 2024 18:05:26 +0200 Subject: [PATCH 01/39] :sparkles: Creation of the PKLMTest class. Implement the first methos to draws the projections. Creation of the associated tests --- qolmat/analysis/holes_characterization.py | 135 +++++++++++++++++- qolmat/utils/exceptions.py | 10 +- tests/analysis/test_holes_characterization.py | 43 +++++- 3 files changed, 185 insertions(+), 3 deletions(-) diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index 5669ac7b..8b7b4c1e 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -1,10 +1,11 @@ from abc import ABC, abstractmethod -from typing import Optional, Union +from typing import Optional, Tuple, Union import numpy as np import pandas as pd from scipy.stats import chi2 +from qolmat.utils.exceptions import TooManyMissingPatterns from qolmat.imputations.imputers import ImputerEM @@ -92,3 +93,135 @@ def test(self, df: pd.DataFrame) -> float: degree_f += tup_pattern.count(True) return 1 - float(chi2.cdf(d0, degree_f)) + + +class PKLMTest(McarTest): + """ + PKLMTest extends McarTest for testing purposes. + + Attributes: + ----------- + nb_projections : int + Number of projections. + nb_permutation : int + Number of permutations. + nb_trees_per_proj : int + Number of trees per projection. + exact_p_value : bool + If True, compute exact p-value. + random_state : Union[None, int, np.random.RandomState, np.random.Generator] + Seed or random state for reproducibility. + """ + + def __init__( + self, + nb_projections: int = 100, + nb_permutation: int = 30, + nb_trees_per_proj: int = 200, + exact_p_value: bool = False, + random_state: Union[None, int, np.random.RandomState] = None, + ): + super().__init__() + self.nb_projections = nb_projections + self.nb_permutation = nb_permutation + self.nb_trees_per_proj = nb_trees_per_proj + self.exact_p_value = exact_p_value + self.random_state = ( + np.random.default_rng(random_state) if isinstance( + random_state, + (type(None), int) + ) else random_state + ) + + + @staticmethod + def check_nb_patterns(df: np.ndarray): + """ + This method examines a NumPy array to identify distinct patterns of missing values (NaNs). + If the number of unique patterns exceeds the number of rows in the array, it raises a + `TooManyMissingPatterns` exception. + This condition comes from the PKLM paper, please see the reference if needed. + + Parameters: + df (np.ndarray): 2D array with NaNs as missing values. + + Raises: + TooManyMissingPatterns: If unique missing patterns exceed the number of rows. + """ + n_rows, _ = df.shape + indicator_matrix = ~np.isnan(df) + patterns = set(map(tuple, indicator_matrix)) + nb_patterns = len(patterns) + if nb_patterns > n_rows: + raise TooManyMissingPatterns() + + + @staticmethod + def draw_features_and_target(df: np.ndarray) -> Tuple[np.ndarray, int]: + """ + Randomly selects features and a target from the dataframe. + + Parameters: + ----------- + df : np.ndarray + The input dataframe. + + Returns: + -------- + Tuple[np.ndarray, int] + Indices of selected features and the target. + """ + _, p = df.shape + nb_features = np.random.randint(1, p) + features_idx = np.random.choice(range(p), size=nb_features, replace=False) + target_idx = np.random.choice(np.setdiff1d(np.arange(p), features_idx)) + return features_idx, target_idx + + @staticmethod + def check_draw(df: np.ndarray, features_idx, target_idx) -> np.bool_: + """ + Checks if the drawn features and target are valid. + # TODO : Need to develop. + + Parameters: + ----------- + df : np.ndarray + The input dataframe. + features_idx : np.ndarray + Indices of the selected features. + target_idx : int + Index of the target. + + Returns: + -------- + bool + True if the draw is valid, False otherwise. + """ + target_values = df[~np.isnan(df[:,features_idx]).any(axis=1)][:, target_idx] + is_nan = np.isnan(target_values).any() + is_distinct_values = (~np.isnan(target_values)).any() + return is_nan and is_distinct_values + + def draw_projection(self, df: np.ndarray) -> Tuple[np.ndarray, int]: + """ + Draws a valid projection of features and a target. + + Parameters: + ----------- + df : np.ndarray + The input dataframe. + + Returns: + -------- + Tuple[np.ndarray, int] + Indices of selected features and the target. + """ + is_checked = False + while not is_checked: + features_idx, target_idx = self.draw_features_and_target(df) + is_checked = self.check_draw(df, features_idx, target_idx) + return features_idx, target_idx + + + def test(self, df: np.ndarray): + self.check_nb_patterns(df) diff --git a/qolmat/utils/exceptions.py b/qolmat/utils/exceptions.py index 513e843b..6f1abb08 100644 --- a/qolmat/utils/exceptions.py +++ b/qolmat/utils/exceptions.py @@ -37,7 +37,7 @@ def __init__(self, shape: Tuple[int, ...]): class NotDataFrame(Exception): def __init__(self, X_type: Type[Any]): - super().__init__(f"Input musr be a dataframe, not a {X_type}") + super().__init__(f"Input must be a dataframe, not a {X_type}") class NotEnoughSamples(Exception): @@ -70,3 +70,11 @@ def __init__(self, min_sv: float, min_std: float): class TypeNotHandled(Exception): def __init__(self, col: str, type_col: str): super().__init__(f"The column `{col}` is of type `{type_col}`, which is not handled!") + + +class TooManyMissingPatterns(Exception): + def __init__(self): + super().__init__( + "The input dataframe or matrix contains too many missing patterns." + "The number of distinct missing patterns must be less than the number of rows." + ) diff --git a/tests/analysis/test_holes_characterization.py b/tests/analysis/test_holes_characterization.py index c794b94e..47bfece5 100644 --- a/tests/analysis/test_holes_characterization.py +++ b/tests/analysis/test_holes_characterization.py @@ -3,11 +3,14 @@ import pytest from scipy.stats import norm -from qolmat.analysis.holes_characterization import LittleTest +from qolmat.analysis.holes_characterization import LittleTest, PKLMTest from qolmat.benchmark.missing_patterns import UniformHoleGenerator from qolmat.imputations.imputers import ImputerEM +### Tests for the LittleTest class + + @pytest.fixture def mcar_df() -> pd.DataFrame: rng = np.random.default_rng(42) @@ -60,3 +63,41 @@ def test_little_mcar_test(df_input: pd.DataFrame, expected: bool, request): def test_attribute_error(): with pytest.raises(AttributeError): LittleTest(random_state=42, imputer=ImputerEM(model="VAR")) + + +### Tests for the PKLMTest class + + +@pytest.fixture +def np_matrix_with_nan_mcar() -> np.ndarray: + rng = np.random.default_rng(42) + n_rows, n_cols = 10, 4 + matrix = rng.normal(size=(n_rows, n_cols)) + num_nan = int(n_rows * n_cols * 0.40) + nan_indices = rng.choice(n_rows * n_cols, num_nan, replace=False) + matrix.flat[nan_indices] = np.nan + return matrix + + +def test_draw_features_and_target(np_matrix_with_nan_mcar): + mcar_test_pklm = PKLMTest() + _, p = np_matrix_with_nan_mcar.shape + features_idx, target_idx = mcar_test_pklm.draw_features_and_target(np_matrix_with_nan_mcar) + assert target_idx not in features_idx + assert 0 <= target_idx <= (p-1) + for feature_index in features_idx: + assert 0 <= feature_index <= (p-1) + + +@pytest.mark.parametrize("dataframe_fixture, features_idx, target_idx, expected", + [ + ("np_matrix_with_nan_mcar", np.array([1, 0]), 2, True), + ("np_matrix_with_nan_mcar", np.array([1, 0, 2]), 3, False) + ] +) +def test_check_draw(request, dataframe_fixture, features_idx, target_idx, expected): + dataframe = request.getfixturevalue(dataframe_fixture) + print(dataframe) + mcar_test_pklm = PKLMTest() + result = mcar_test_pklm.check_draw(dataframe, features_idx, target_idx) + assert result == expected From 8730f96d41927ed44ade1197ee745b2299554dbd Mon Sep 17 00:00:00 2001 From: Adrien Couratier Date: Tue, 6 Aug 2024 11:40:52 +0200 Subject: [PATCH 02/39] :white_check_mark: Add and improve some tests --- qolmat/analysis/holes_characterization.py | 76 ++++++++++++++++--- tests/analysis/test_holes_characterization.py | 60 ++++++++++++++- 2 files changed, 124 insertions(+), 12 deletions(-) diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index 8b7b4c1e..7da13202 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -3,6 +3,7 @@ import numpy as np import pandas as pd +from sklearn.ensemble import RandomForestClassifier from scipy.stats import chi2 from qolmat.utils.exceptions import TooManyMissingPatterns @@ -109,8 +110,9 @@ class PKLMTest(McarTest): Number of trees per projection. exact_p_value : bool If True, compute exact p-value. - random_state : Union[None, int, np.random.RandomState, np.random.Generator] - Seed or random state for reproducibility. + random_state : int, RandomState instance or None, default=None + Controls the randomness. + Pass an int for reproducible output across multiple function calls. """ def __init__( @@ -133,9 +135,8 @@ def __init__( ) else random_state ) - @staticmethod - def check_nb_patterns(df: np.ndarray): + def _check_nb_patterns(df: np.ndarray) -> None: """ This method examines a NumPy array to identify distinct patterns of missing values (NaNs). If the number of unique patterns exceeds the number of rows in the array, it raises a @@ -155,9 +156,8 @@ def check_nb_patterns(df: np.ndarray): if nb_patterns > n_rows: raise TooManyMissingPatterns() - @staticmethod - def draw_features_and_target(df: np.ndarray) -> Tuple[np.ndarray, int]: + def _draw_features_and_target_indexes(df: np.ndarray) -> Tuple[np.ndarray, int]: """ Randomly selects features and a target from the dataframe. @@ -178,7 +178,7 @@ def draw_features_and_target(df: np.ndarray) -> Tuple[np.ndarray, int]: return features_idx, target_idx @staticmethod - def check_draw(df: np.ndarray, features_idx, target_idx) -> np.bool_: + def check_draw(df: np.ndarray, features_idx: np.ndarray, target_idx: int) -> np.bool_: """ Checks if the drawn features and target are valid. # TODO : Need to develop. @@ -218,10 +218,68 @@ def draw_projection(self, df: np.ndarray) -> Tuple[np.ndarray, int]: """ is_checked = False while not is_checked: - features_idx, target_idx = self.draw_features_and_target(df) + features_idx, target_idx = self._draw_features_and_target_indexes(df) is_checked = self.check_draw(df, features_idx, target_idx) return features_idx, target_idx + @staticmethod + def _build_dataset( + df: np.ndarray, + features_idx: np.ndarray, + target_idx: int + ) -> Tuple[np.ndarray, np.ndarray]: + X = df[~np.isnan(df[:, features_idx]).any(axis=1)][:, features_idx] + y = np.where(np.isnan(df[~np.isnan(df[:, features_idx]).any(axis=1)][:, target_idx]), 1, 0) + return X, y + + @staticmethod + def _build_label( + df: np.ndarray, + perm: np.ndarray, + features_idx: np.ndarray, + target_idx: int + ) -> np.ndarray: + return perm[~np.isnan(df[:, features_idx]).any(axis=1), target_idx] + + + def _get_oob_probabilities(self, X: np.ndarray, y: np.ndarray) -> np.ndarray: + clf = RandomForestClassifier( + n_estimators=self.nb_trees_per_proj, + #max_features=None, + min_samples_split=10, + bootstrap=True, + oob_score=True, + ) + clf.fit(X, y) + return clf.oob_decision_function_ + + @staticmethod + def _U_hat(oob_probabilities: np.ndarray, labels: np.ndarray) -> float: + oob_probabilities = np.clip(oob_probabilities, 1e-9, 1-1e-9) + + unique_labels = np.unique(labels) + label_matrix = (labels[:, None] == unique_labels).astype(int) + p_true = oob_probabilities * label_matrix + p_false = oob_probabilities * (1 - label_matrix) + + p0_0 = p_true[:, 0][np.where(p_true[:, 0] != 0.)] + p0_1 = p_false[:, 0][np.where(p_false[:, 0] != 0.)] + p1_1 = p_true[:, 1][np.where(p_true[:, 1] != 0.)] + p1_0 = p_false[:, 1][np.where(p_false[:, 1] != 0.)] + + if unique_labels.shape[0] == 1: + if unique_labels[0] == 0: + n0 = labels.shape[0] + return np.log(p0_0 / (1 - p0_0)).sum() / n0 - np.log(p1_0 / (1 - p1_0)).sum() / n0 + else: + n1 = labels.shape[0] + return np.log(p1_1 / (1 - p1_1)).sum() / n1 - np.log(p0_1 / (1 - p0_1)).sum() / n1 + + n0, n1 = label_matrix.sum(axis=0) + u_0 = np.log(p0_0 / (1 - p0_0)).sum() / n0 - np.log(p0_1 / (1 - p0_1)).sum() / n1 + u_1 = np.log(p1_1 / (1 - p1_1)).sum() / n1 - np.log(p1_0 / (1 - p1_0)).sum() / n0 + + return u_0 + u_1 def test(self, df: np.ndarray): - self.check_nb_patterns(df) + self._check_nb_patterns(df) diff --git a/tests/analysis/test_holes_characterization.py b/tests/analysis/test_holes_characterization.py index 47bfece5..8b831134 100644 --- a/tests/analysis/test_holes_characterization.py +++ b/tests/analysis/test_holes_characterization.py @@ -79,10 +79,23 @@ def np_matrix_with_nan_mcar() -> np.ndarray: return matrix -def test_draw_features_and_target(np_matrix_with_nan_mcar): +@pytest.fixture +def missingness_matrix_mcar(np_matrix_with_nan_mcar): + return np.isnan(np_matrix_with_nan_mcar).astype(int) + + +@pytest.fixture +def missingness_matrix_mcar_perm(missingness_matrix_mcar): + rng = np.random.default_rng(42) + return rng.permutation(missingness_matrix_mcar) + + +def test__draw_features_and_target_indexes(np_matrix_with_nan_mcar): mcar_test_pklm = PKLMTest() _, p = np_matrix_with_nan_mcar.shape - features_idx, target_idx = mcar_test_pklm.draw_features_and_target(np_matrix_with_nan_mcar) + features_idx, target_idx = mcar_test_pklm._draw_features_and_target_indexes(np_matrix_with_nan_mcar) + assert isinstance(target_idx, np.integer) + assert isinstance(features_idx, np.ndarray) assert target_idx not in features_idx assert 0 <= target_idx <= (p-1) for feature_index in features_idx: @@ -97,7 +110,48 @@ def test_draw_features_and_target(np_matrix_with_nan_mcar): ) def test_check_draw(request, dataframe_fixture, features_idx, target_idx, expected): dataframe = request.getfixturevalue(dataframe_fixture) - print(dataframe) mcar_test_pklm = PKLMTest() result = mcar_test_pklm.check_draw(dataframe, features_idx, target_idx) assert result == expected + + +@pytest.mark.parametrize("dataframe_fixture, features_idx, target_idx", + [ + ("np_matrix_with_nan_mcar", np.array([1, 0]), 2), + ] +) +def test__build_dataset(request, dataframe_fixture, features_idx, target_idx): + dataframe = request.getfixturevalue(dataframe_fixture) + mcar_test_pklm = PKLMTest() + X, y = mcar_test_pklm._build_dataset(dataframe, features_idx, target_idx) + assert X.shape[0] == len(y) + assert not np.any(np.isnan(X)) + assert not np.any(np.isnan(y)) + assert np.all(np.unique(y) == [0, 1]) + assert X.shape[1] == len(features_idx) + assert len(y.shape) == 1 + + +@pytest.mark.parametrize("dataframe_fixture, permutation_fixture, features_idx, target_idx", + [ + ("np_matrix_with_nan_mcar", "missingness_matrix_mcar_perm", np.array([1, 0]), 2), + ] +) +def test__build_label( + request, + dataframe_fixture, + permutation_fixture, + features_idx, + target_idx +): + dataframe = request.getfixturevalue(dataframe_fixture) + m_perm = request.getfixturevalue(permutation_fixture) + mcar_test_pklm = PKLMTest() + label = mcar_test_pklm._build_label(dataframe, m_perm, features_idx, target_idx) + assert not np.any(np.isnan(label)) + assert len(label.shape) == 1 + assert np.isin(label, [0, 1]).all() + + +def test__U_hat(): + assert False From 33b95ce8d41242af36114e84b455f513dabf5389 Mon Sep 17 00:00:00 2001 From: Adrien Couratier Date: Wed, 7 Aug 2024 17:46:03 +0200 Subject: [PATCH 03/39] :rocket: Ends the test implementation and associated tests. Sill have problems with random_state usage. --- qolmat/analysis/holes_characterization.py | 203 +++++++++++++++--- tests/analysis/test_holes_characterization.py | 37 +++- 2 files changed, 209 insertions(+), 31 deletions(-) diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index 7da13202..19342b86 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -1,9 +1,11 @@ from abc import ABC, abstractmethod -from typing import Optional, Tuple, Union +from typing import List, Optional, Tuple, Union +from joblib import Parallel, delayed import numpy as np import pandas as pd from sklearn.ensemble import RandomForestClassifier +from sklearn import utils as sku from scipy.stats import chi2 from qolmat.utils.exceptions import TooManyMissingPatterns @@ -13,7 +15,14 @@ class McarTest(ABC): """ Astract class for MCAR tests. + + Parameters + ---------- + random_state : int, optional + The seed of the pseudo random number generator to use, for reproductibility. """ + def __init__(self, random_state: Union[None, int, np.random.RandomState] = None): + self.rng = sku.check_random_state(random_state) @abstractmethod def test(self, df: pd.DataFrame) -> float: @@ -29,7 +38,7 @@ class LittleTest(McarTest): References ---------- Little. "A Test of Missing Completely at Random for Multivariate Data with Missing Values." - Journal of the American Statistical Association, Volume 83, 1988 - Issue 404 + Journal of the American Statistical Association, Volume 83, 1988 - no 404, p. 1198-1202 Parameters ---------- @@ -46,13 +55,12 @@ def __init__( imputer: Optional[ImputerEM] = None, random_state: Union[None, int, np.random.RandomState] = None, ): - super().__init__() + super().__init__(random_state=random_state) if imputer and imputer.model != "multinormal": raise AttributeError( "The ImputerEM model must be 'multinormal' to use the Little's test" ) self.imputer = imputer - self.random_state = random_state def test(self, df: pd.DataFrame) -> float: """ @@ -69,7 +77,7 @@ def test(self, df: pd.DataFrame) -> float: float The p-value of the test. """ - imputer = self.imputer or ImputerEM(random_state=self.random_state) + imputer = self.imputer or ImputerEM(random_state=self.rng) imputer = imputer._fit_element(df) d0 = 0 @@ -98,9 +106,18 @@ def test(self, df: pd.DataFrame) -> float: class PKLMTest(McarTest): """ - PKLMTest extends McarTest for testing purposes. - - Attributes: + This class implements the PKLM test, a fully non-parametric, easy-to-use, and powerful test + for the missing completely at random (MCAR) assumption on the missingness mechanism of a + dataset. The null hypothesis is "The missing data mechanism is MCAR". + + This test is applicable to mixed data (quantitative and categoricals features). + + References + ---------- + Spohn, M. L., Näf, J., Michel, L., & Meinshausen, N. (2021). PKLM: A flexible MCAR test using + Classification. arXiv preprint arXiv:2109.10150. + + Parameters ----------- nb_projections : int Number of projections. @@ -123,17 +140,11 @@ def __init__( exact_p_value: bool = False, random_state: Union[None, int, np.random.RandomState] = None, ): - super().__init__() + super().__init__(random_state=random_state) self.nb_projections = nb_projections self.nb_permutation = nb_permutation self.nb_trees_per_proj = nb_trees_per_proj self.exact_p_value = exact_p_value - self.random_state = ( - np.random.default_rng(random_state) if isinstance( - random_state, - (type(None), int) - ) else random_state - ) @staticmethod def _check_nb_patterns(df: np.ndarray) -> None: @@ -144,10 +155,13 @@ def _check_nb_patterns(df: np.ndarray) -> None: This condition comes from the PKLM paper, please see the reference if needed. Parameters: - df (np.ndarray): 2D array with NaNs as missing values. + ----------- + df : np.ndarray + 2D array with NaNs as missing values. Raises: - TooManyMissingPatterns: If unique missing patterns exceed the number of rows. + ------- + TooManyMissingPatterns: If unique missing patterns exceed the number of rows. """ n_rows, _ = df.shape indicator_matrix = ~np.isnan(df) @@ -156,8 +170,7 @@ def _check_nb_patterns(df: np.ndarray) -> None: if nb_patterns > n_rows: raise TooManyMissingPatterns() - @staticmethod - def _draw_features_and_target_indexes(df: np.ndarray) -> Tuple[np.ndarray, int]: + def _draw_features_and_target_indexes(self, df: np.ndarray) -> Tuple[np.ndarray, int]: """ Randomly selects features and a target from the dataframe. @@ -172,13 +185,13 @@ def _draw_features_and_target_indexes(df: np.ndarray) -> Tuple[np.ndarray, int]: Indices of selected features and the target. """ _, p = df.shape - nb_features = np.random.randint(1, p) - features_idx = np.random.choice(range(p), size=nb_features, replace=False) - target_idx = np.random.choice(np.setdiff1d(np.arange(p), features_idx)) + nb_features = self.rng.randint(1, p) + features_idx = self.rng.choice(range(p), size=nb_features, replace=False) + target_idx = self.rng.choice(np.setdiff1d(np.arange(p), features_idx)) return features_idx, target_idx @staticmethod - def check_draw(df: np.ndarray, features_idx: np.ndarray, target_idx: int) -> np.bool_: + def _check_draw(df: np.ndarray, features_idx: np.ndarray, target_idx: int) -> np.bool_: """ Checks if the drawn features and target are valid. # TODO : Need to develop. @@ -202,7 +215,7 @@ def check_draw(df: np.ndarray, features_idx: np.ndarray, target_idx: int) -> np. is_distinct_values = (~np.isnan(target_values)).any() return is_nan and is_distinct_values - def draw_projection(self, df: np.ndarray) -> Tuple[np.ndarray, int]: + def _draw_projection(self, df: np.ndarray) -> Tuple[np.ndarray, int]: """ Draws a valid projection of features and a target. @@ -219,7 +232,7 @@ def draw_projection(self, df: np.ndarray) -> Tuple[np.ndarray, int]: is_checked = False while not is_checked: features_idx, target_idx = self._draw_features_and_target_indexes(df) - is_checked = self.check_draw(df, features_idx, target_idx) + is_checked = self._check_draw(df, features_idx, target_idx) return features_idx, target_idx @staticmethod @@ -228,6 +241,25 @@ def _build_dataset( features_idx: np.ndarray, target_idx: int ) -> Tuple[np.ndarray, np.ndarray]: + """ + Builds a dataset by selecting specified features and target from a NumPy array, + excluding rows with NaN values in the feature columns. + + Parameters: + ----------- + df: np.ndarray + Input data array. + features_idx: np.ndarray + Indices of the feature columns. + target_idx: int + Index of the target column. + + Returns: + -------- + Tuple[np.ndarray, np.ndarray]: A tuple containing: + - X (np.ndarray): Array of selected features. + - y (np.ndarray): Binary array indicating presence of NaN (1) in the target column. + """ X = df[~np.isnan(df[:, features_idx]).any(axis=1)][:, features_idx] y = np.where(np.isnan(df[~np.isnan(df[:, features_idx]).any(axis=1)][:, target_idx]), 1, 0) return X, y @@ -239,22 +271,71 @@ def _build_label( features_idx: np.ndarray, target_idx: int ) -> np.ndarray: + """ + Builds a label array by selecting target values from a permutation array, + excluding rows with NaN values in the specified feature columns. + + Parameters: + ----------- + df: np.ndarray + Input data array. + perm: np.ndarray + Permutation array from which labels are selected. + features_idx: np.ndarray + Indices of the feature columns. + target_idx: int + Index of the target column in the permutation array. + + Returns: + -------- + np.ndarray: Binary array indicating presence of NaN (1) in the target column. + """ return perm[~np.isnan(df[:, features_idx]).any(axis=1), target_idx] def _get_oob_probabilities(self, X: np.ndarray, y: np.ndarray) -> np.ndarray: + """ + Trains a RandomForestClassifier and retrieves out-of-bag (OOB) probabilities. + + Parameters: + ----------- + X: np.ndarray + Feature array for training. + y: np.ndarray + Target array for training. + + Returns: + -------- + np.ndarray: Out-of-bag probabilities for each class. + """ clf = RandomForestClassifier( n_estimators=self.nb_trees_per_proj, #max_features=None, min_samples_split=10, bootstrap=True, oob_score=True, + random_state=self.rng ) clf.fit(X, y) return clf.oob_decision_function_ @staticmethod def _U_hat(oob_probabilities: np.ndarray, labels: np.ndarray) -> float: + """ + Computes the U_hat statistic, a measure of classifier performance, using + out-of-bag probabilities and true labels. + + Parameters: + ----------- + oob_probabilities: np.ndarray + Out-of-bag probabilities for each class. + labels: np.ndarray + True labels for the data. + + Returns: + -------- + float: The computed U_hat statistic. + """ oob_probabilities = np.clip(oob_probabilities, 1e-9, 1-1e-9) unique_labels = np.unique(labels) @@ -281,5 +362,75 @@ def _U_hat(oob_probabilities: np.ndarray, labels: np.ndarray) -> float: return u_0 + u_1 - def test(self, df: np.ndarray): + def _parallel_process_permutation( + self, + df: np.ndarray, + M_perm: np.ndarray, + features_idx: np.ndarray, + target_idx: int, + oob_probabilities: np.ndarray + ) -> float: + y = self._build_label(df, M_perm, features_idx, target_idx) + return self._U_hat(oob_probabilities, y) + + def _parallel_process_projection( + self, + df: np.ndarray, + list_permutations: List[np.ndarray], + features_idx: np.ndarray, + target_idx: int, + ) -> Tuple[float, List[float]]: + X, y = self._build_dataset(df, features_idx, target_idx) + oob_probabilities = self._get_oob_probabilities(X, y) + u_hat = self._U_hat(oob_probabilities, y) + result_u_permutations = Parallel(n_jobs=-1)(delayed(self._parallel_process_permutation)( + df, + M_perm, + features_idx, + target_idx, + oob_probabilities + ) for M_perm in list_permutations) + return u_hat, result_u_permutations + + def test(self, df: np.ndarray) -> float: + """ + Apply the PKLM test over a real dataset. + + + Parameters + ---------- + df : np.ndarray + The input dataset with missing values. + + Returns + ------- + float + The p-value of the test. + """ self._check_nb_patterns(df) + + M = np.isnan(df).astype(int) + list_proj = [self._draw_projection(df) for _ in range(self.nb_projections)] + list_perm = [self.rng.permutation(M) for _ in range(self.nb_permutation)] + U = 0. + list_U_sigma = [0. for _ in range(self.nb_permutation)] + + parallel_results = Parallel(n_jobs=-1)(delayed(self._parallel_process_projection)( + df, + list_perm, + features_idx, + target_idx + ) for features_idx, target_idx in list_proj) + + for U_projection, results in parallel_results: + U += U_projection + list_U_sigma = [x + y for x, y in zip(list_U_sigma, results)] + + U = U / self.nb_projections + list_U_sigma = [x / self.nb_permutation for x in list_U_sigma] + + p_value = 1 + for u_sigma in list_U_sigma: + if u_sigma >= U: + p_value += 1 + return p_value / (self.nb_permutation + 1) diff --git a/tests/analysis/test_holes_characterization.py b/tests/analysis/test_holes_characterization.py index 8b831134..5ed9f166 100644 --- a/tests/analysis/test_holes_characterization.py +++ b/tests/analysis/test_holes_characterization.py @@ -90,8 +90,13 @@ def missingness_matrix_mcar_perm(missingness_matrix_mcar): return rng.permutation(missingness_matrix_mcar) +@pytest.fixture +def oob_probabilities() -> np.ndarray: + return np.matrix([[0.5, 0.5], [0, 1], [1, 0], [1, 0]]).A + + def test__draw_features_and_target_indexes(np_matrix_with_nan_mcar): - mcar_test_pklm = PKLMTest() + mcar_test_pklm = PKLMTest(42) _, p = np_matrix_with_nan_mcar.shape features_idx, target_idx = mcar_test_pklm._draw_features_and_target_indexes(np_matrix_with_nan_mcar) assert isinstance(target_idx, np.integer) @@ -108,10 +113,10 @@ def test__draw_features_and_target_indexes(np_matrix_with_nan_mcar): ("np_matrix_with_nan_mcar", np.array([1, 0, 2]), 3, False) ] ) -def test_check_draw(request, dataframe_fixture, features_idx, target_idx, expected): +def test__check_draw(request, dataframe_fixture, features_idx, target_idx, expected): dataframe = request.getfixturevalue(dataframe_fixture) mcar_test_pklm = PKLMTest() - result = mcar_test_pklm.check_draw(dataframe, features_idx, target_idx) + result = mcar_test_pklm._check_draw(dataframe, features_idx, target_idx) assert result == expected @@ -153,5 +158,27 @@ def test__build_label( assert np.isin(label, [0, 1]).all() -def test__U_hat(): - assert False +@pytest.mark.parametrize( + "oob_fixture, label", + [ + ("oob_probabilities", np.array([1, 1, 1, 1])), + ("oob_probabilities", np.array([0, 0, 0, 0])), + ] +) +def test__U_hat_unique_label(request, oob_fixture, label): + oob_prob = request.getfixturevalue(oob_fixture) + mcar_test_pklm = PKLMTest() + mcar_test_pklm._U_hat(oob_prob, label) + + +@pytest.mark.parametrize( + "oob_fixture, label, expected", + [ + ("oob_probabilities", np.array([1, 0, 0, 0]), 2/3*(np.log(1 - 1e-9) - np.log(1e-9))), + ] +) +def test__U_hat_computation(request, oob_fixture, label, expected): + oob_prob = request.getfixturevalue(oob_fixture) + mcar_test_pklm = PKLMTest() + u_hat = mcar_test_pklm._U_hat(oob_prob, label) + assert round(u_hat, 2) == round(expected, 2) From 0601f2d615bf54652441398425e18a5604f19e2b Mon Sep 17 00:00:00 2001 From: Adrien Couratier Date: Thu, 8 Aug 2024 16:04:00 +0200 Subject: [PATCH 04/39] :poop: Implement the 'exact' p_value computation but results are not sufficients --- qolmat/analysis/holes_characterization.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index 19342b86..b576e403 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -146,6 +146,10 @@ def __init__( self.nb_trees_per_proj = nb_trees_per_proj self.exact_p_value = exact_p_value + if self.exact_p_value: + self.process_permutation = self._parallel_process_permutation_exact + self.process_permutation = self._parallel_process_permutation + @staticmethod def _check_nb_patterns(df: np.ndarray) -> None: """ @@ -373,6 +377,19 @@ def _parallel_process_permutation( y = self._build_label(df, M_perm, features_idx, target_idx) return self._U_hat(oob_probabilities, y) + def _parallel_process_permutation_exact( + self, + df: np.ndarray, + M_perm: np.ndarray, + features_idx: np.ndarray, + target_idx: int, + oob_probabilites_unused: np.ndarray + ) -> float: + X, _ = self._build_dataset(df, features_idx, target_idx) + y = self._build_label(df, M_perm, features_idx, target_idx) + oob_probabilities = self._get_oob_probabilities(X, y) + return self._U_hat(oob_probabilities, y) + def _parallel_process_projection( self, df: np.ndarray, @@ -383,7 +400,7 @@ def _parallel_process_projection( X, y = self._build_dataset(df, features_idx, target_idx) oob_probabilities = self._get_oob_probabilities(X, y) u_hat = self._U_hat(oob_probabilities, y) - result_u_permutations = Parallel(n_jobs=-1)(delayed(self._parallel_process_permutation)( + result_u_permutations = Parallel(n_jobs=-1)(delayed(self.process_permutation)( df, M_perm, features_idx, From 8f105e5424d8bff176ad57c28acd02001b713a8e Mon Sep 17 00:00:00 2001 From: Adrien Couratier Date: Thu, 8 Aug 2024 16:52:03 +0200 Subject: [PATCH 05/39] :sparkles: Start to support mixed data types. --- qolmat/analysis/holes_characterization.py | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index b576e403..e6319c9d 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -5,6 +5,7 @@ import numpy as np import pandas as pd from sklearn.ensemble import RandomForestClassifier +from sklearn.preprocessing import OneHotEncoder from sklearn import utils as sku from scipy.stats import chi2 @@ -138,6 +139,7 @@ def __init__( nb_permutation: int = 30, nb_trees_per_proj: int = 200, exact_p_value: bool = False, + encoder: Union[None, OneHotEncoder] = None, random_state: Union[None, int, np.random.RandomState] = None, ): super().__init__(random_state=random_state) @@ -145,6 +147,7 @@ def __init__( self.nb_permutation = nb_permutation self.nb_trees_per_proj = nb_trees_per_proj self.exact_p_value = exact_p_value + self.encoder = encoder if self.exact_p_value: self.process_permutation = self._parallel_process_permutation_exact @@ -174,6 +177,29 @@ def _check_nb_patterns(df: np.ndarray) -> None: if nb_patterns > n_rows: raise TooManyMissingPatterns() + def _check_df_type(df): + """ + Si le type est un np.ndarray -> Go, si c'est un pd.DataFrame aller vers une autre fonction. + """ + pass + + def _check_pd_df_dtypes(df): + """ + Si tous les types sont quantitatifs -> conversion en numpy et GO. + Sinon vérifier que les types sont acceptés (object, bool). + Pour le moment, on ne supporte pas : les dates, les categories + + Cette fonction sert juste à lever une erreur si besoin. + """ + pass + + def _encode_dataframe(df): + """ + Si les types sont bien acceptés, faire un OneHot sur les catégories acceptées et return + un np.ndarray. + """ + pass + def _draw_features_and_target_indexes(self, df: np.ndarray) -> Tuple[np.ndarray, int]: """ Randomly selects features and a target from the dataframe. From e48dd4febe2ac6415be52f5ffb32b07de77a4ddf Mon Sep 17 00:00:00 2001 From: Adrien Couratier Date: Fri, 9 Aug 2024 15:14:28 +0200 Subject: [PATCH 06/39] :sparkles: Add the feature to deal ith mixed data types and pandas dataframes. Also add a notebook to show how it works --- examples/pklm/tuto_pklm.ipynb | 548 ++++++++++++++++++ qolmat/analysis/holes_characterization.py | 94 ++- tests/analysis/test_holes_characterization.py | 44 +- 3 files changed, 667 insertions(+), 19 deletions(-) create mode 100644 examples/pklm/tuto_pklm.ipynb diff --git a/examples/pklm/tuto_pklm.ipynb b/examples/pklm/tuto_pklm.ipynb new file mode 100644 index 00000000..e8397a87 --- /dev/null +++ b/examples/pklm/tuto_pklm.ipynb @@ -0,0 +1,548 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 10, + "id": "4cf3e844", + "metadata": {}, + "outputs": [], + "source": [ + "from matplotlib import pyplot as plt\n", + "import time\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "from scipy.stats import norm\n", + "\n", + "from qolmat.analysis.holes_characterization import PKLMTest\n", + "from qolmat.benchmark.missing_patterns import UniformHoleGenerator\n", + "\n", + "plt.rcParams.update({\"font.size\": 12})" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "7e3202a6", + "metadata": {}, + "outputs": [], + "source": [ + "rng = np.random.default_rng(42)\n", + "n_rows, n_cols = 5000, 4\n", + "matrix = rng.normal(size=(n_rows, n_cols))\n", + "num_nan = int(n_rows * n_cols * 0.20)\n", + "nan_indices = rng.choice(n_rows * n_cols, num_nan, replace=False)\n", + "matrix.flat[nan_indices] = np.nan" + ] + }, + { + "cell_type": "markdown", + "id": "a44983a6", + "metadata": {}, + "source": [ + "### First test with 'exact_p_value' = True" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "636bbd6c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.7096774193548387\n", + "--- 11.930958271026611 seconds ---\n" + ] + } + ], + "source": [ + "pklm_test = PKLMTest(\n", + " nb_projections=100,\n", + " nb_permutation=30,\n", + " nb_trees_per_proj=200,\n", + " exact_p_value=True,\n", + " random_state=42\n", + ")\n", + "\n", + "start_time = time.time()\n", + "p_v = pklm_test.test(matrix)\n", + "print(p_v)\n", + "print(\"--- %s seconds ---\" % (time.time() - start_time))" + ] + }, + { + "cell_type": "markdown", + "id": "75fedc83", + "metadata": {}, + "source": [ + "### First test with 'exact_p_value' = False" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "103fbb01", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.7096774193548387\n", + "--- 10.284496068954468 seconds ---\n" + ] + } + ], + "source": [ + "pklm_test = PKLMTest(\n", + " nb_projections=100,\n", + " nb_permutation=30,\n", + " nb_trees_per_proj=200,\n", + " exact_p_value=False,\n", + " random_state=42\n", + ")\n", + "\n", + "start_time = time.time()\n", + "p_v = pklm_test.test(matrix)\n", + "print(p_v)\n", + "print(\"--- %s seconds ---\" % (time.time() - start_time))" + ] + }, + { + "cell_type": "markdown", + "id": "ea1fc33c", + "metadata": {}, + "source": [ + "__Notes__ :\n", + "\n", + "- First this a weird to get the exact same results.\n", + "- Second, this is also weird that the second execution is juste one second faster.\n", + "\n", + "This need to be reviewed and fixed" + ] + }, + { + "cell_type": "markdown", + "id": "e000784b", + "metadata": {}, + "source": [ + "### Test with pandas dataframe and mixed datatypes" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "319e5a7f", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Numeric1Numeric2BooleanCategory
024.84552892FalseB
145.99351369FalseD
286.6591942FalseB
341.94595334FalseC
488.45749571TrueC
\n", + "
" + ], + "text/plain": [ + " Numeric1 Numeric2 Boolean Category\n", + "0 24.845528 92 False B\n", + "1 45.993513 69 False D\n", + "2 86.659194 2 False B\n", + "3 41.945953 34 False C\n", + "4 88.457495 71 True C" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "# Définir le nombre de lignes\n", + "n_rows = 100\n", + "\n", + "col1 = np.random.rand(n_rows) * 100\n", + "col2 = np.random.randint(1, 100, n_rows)\n", + "col3 = np.random.choice([True, False], n_rows)\n", + "modalities = ['A', 'B', 'C', 'D']\n", + "col4 = np.random.choice(modalities, n_rows)\n", + "\n", + "df = pd.DataFrame({\n", + " 'Numeric1': col1,\n", + " 'Numeric2': col2,\n", + " 'Boolean': col3,\n", + " 'Category': col4\n", + "})\n", + "\n", + "df.head()" + ] + }, + { + "cell_type": "markdown", + "id": "338dac31", + "metadata": {}, + "source": [ + "__Holes_creation__ : According tot he Qolmat tool" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "26c54631", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Numeric1Numeric2BooleanCategory
0NaN92.0FalseB
145.99351369.0FalseD
286.6591942.0FalseB
341.94595334.0FalseC
4NaN71.0TrueNaN
\n", + "
" + ], + "text/plain": [ + " Numeric1 Numeric2 Boolean Category\n", + "0 NaN 92.0 False B\n", + "1 45.993513 69.0 False D\n", + "2 86.659194 2.0 False B\n", + "3 41.945953 34.0 False C\n", + "4 NaN 71.0 True NaN" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hole_gen = UniformHoleGenerator(\n", + " n_splits=1,\n", + " ratio_masked=0.2,\n", + " subset=['Numeric1', 'Numeric2', 'Boolean', 'Category'],\n", + " random_state=42\n", + ")\n", + "df_mask = hole_gen.generate_mask(df)\n", + "df_nan = df.where(~df_mask, np.nan)\n", + "df_nan.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "637f56c5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.6129032258064516\n", + "--- 1.973733901977539 seconds ---\n" + ] + } + ], + "source": [ + "pklm_test = PKLMTest(\n", + " nb_projections=100,\n", + " nb_permutation=30,\n", + " nb_trees_per_proj=200,\n", + " random_state=42\n", + ")\n", + "\n", + "start_time = time.time()\n", + "p_v = pklm_test.test(df_nan)\n", + "print(p_v)\n", + "print(\"--- %s seconds ---\" % (time.time() - start_time))" + ] + }, + { + "cell_type": "markdown", + "id": "ee8a1315", + "metadata": {}, + "source": [ + "### Go back with the previous examples" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "2ca2ef5c", + "metadata": {}, + "outputs": [], + "source": [ + "rng = np.random.RandomState(42)\n", + "data = rng.multivariate_normal(mean=[0, 0], cov=[[1, 0], [0, 1]], size=200)\n", + "df = pd.DataFrame(data=data, columns=[\"Column 1\", \"Column 2\"])\n", + "\n", + "q975 = norm.ppf(0.975)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "e1a01a57", + "metadata": {}, + "outputs": [], + "source": [ + "pklm_test = PKLMTest(\n", + " nb_projections=100,\n", + " nb_permutation=100,\n", + " nb_trees_per_proj=200,\n", + " random_state=42\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "1f39721c", + "metadata": {}, + "source": [ + "### Case 1: MCAR holes (True negative)¶" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "a72d5da1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.9207920792079208\n", + "--- 2.251690149307251 seconds ---\n" + ] + } + ], + "source": [ + "hole_gen = UniformHoleGenerator(\n", + " n_splits=1, random_state=rng, subset=[\"Column 2\"], ratio_masked=0.2\n", + ")\n", + "df_mask = hole_gen.generate_mask(df)\n", + "df_nan = df.where(~df_mask, np.nan)\n", + "\n", + "start_time = time.time()\n", + "p_v = pklm_test.test(df_nan)\n", + "print(p_v)\n", + "print(\"--- %s seconds ---\" % (time.time() - start_time))" + ] + }, + { + "cell_type": "markdown", + "id": "b113ded2", + "metadata": {}, + "source": [ + "### Case 2: MAR holes with mean bias (True positive)¶" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "86e75c47", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.009900990099009901\n", + "--- 2.0787599086761475 seconds ---\n" + ] + } + ], + "source": [ + "df_mask = pd.DataFrame({\"Column 1\": False, \"Column 2\": df[\"Column 1\"] > q975}, index=df.index)\n", + "\n", + "df_nan = df.where(~df_mask, np.nan)\n", + "\n", + "start_time = time.time()\n", + "p_v = pklm_test.test(df_nan)\n", + "print(p_v)\n", + "print(\"--- %s seconds ---\" % (time.time() - start_time))" + ] + }, + { + "cell_type": "markdown", + "id": "3262ae8c", + "metadata": {}, + "source": [ + "### Case 3: MAR holes with any mean bias (False negative)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "60f35623", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.009900990099009901\n", + "--- 2.1425230503082275 seconds ---\n" + ] + } + ], + "source": [ + "df_mask = pd.DataFrame(\n", + " {\"Column 1\": False, \"Column 2\": df[\"Column 1\"].abs() > q975}, index=df.index\n", + ")\n", + "\n", + "df_nan = df.where(~df_mask, np.nan)\n", + "\n", + "start_time = time.time()\n", + "p_v = pklm_test.test(df_nan)\n", + "print(p_v)\n", + "print(\"--- %s seconds ---\" % (time.time() - start_time))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.19" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index e6319c9d..3fa9fc66 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -9,7 +9,7 @@ from sklearn import utils as sku from scipy.stats import chi2 -from qolmat.utils.exceptions import TooManyMissingPatterns +from qolmat.utils.exceptions import TooManyMissingPatterns, TypeNotHandled from qolmat.imputations.imputers import ImputerEM @@ -128,6 +128,8 @@ class PKLMTest(McarTest): Number of trees per projection. exact_p_value : bool If True, compute exact p-value. + encoder : OneHotEncoder or None, default=None + Encoder to convert non numeric pandas dataframe values to numeric values. random_state : int, RandomState instance or None, default=None Controls the randomness. Pass an int for reproducible output across multiple function calls. @@ -139,7 +141,7 @@ def __init__( nb_permutation: int = 30, nb_trees_per_proj: int = 200, exact_p_value: bool = False, - encoder: Union[None, OneHotEncoder] = None, + encoder: Union[None, OneHotEncoder] = None, # We could define more encoders. random_state: Union[None, int, np.random.RandomState] = None, ): super().__init__(random_state=random_state) @@ -177,28 +179,85 @@ def _check_nb_patterns(df: np.ndarray) -> None: if nb_patterns > n_rows: raise TooManyMissingPatterns() - def _check_df_type(df): - """ - Si le type est un np.ndarray -> Go, si c'est un pd.DataFrame aller vers une autre fonction. + @staticmethod + def _check_pd_df_dtypes(df: pd.DataFrame): """ - pass + Validates that the columns of the DataFrame have allowed data types. + + Parameters: + ----------- + df : pd.DataFrame + DataFrame whose columns' data types are to be checked. - def _check_pd_df_dtypes(df): + Raises: + ------- + TypeNotHandled + If any column has a data type that is not numeric, string, or boolean. + """ + allowed_types = [ + pd.api.types.is_numeric_dtype, + pd.api.types.is_string_dtype, + pd.api.types.is_bool_dtype + ] + def is_allowed_type(dtype): + return any(check(dtype) for check in allowed_types) + + invalid_columns = [(col, dtype) for col, dtype in df.dtypes.items() if not is_allowed_type(dtype)] + if invalid_columns: + for column_name, dtype in invalid_columns: + raise TypeNotHandled(col=column_name, type_col=dtype) + + def _encode_dataframe(self, df: pd.DataFrame) -> np.ndarray: """ - Si tous les types sont quantitatifs -> conversion en numpy et GO. - Sinon vérifier que les types sont acceptés (object, bool). - Pour le moment, on ne supporte pas : les dates, les categories + Encodes the DataFrame by converting numeric columns to a numpy array + and applying one-hot encoding to non-numeric columns. - Cette fonction sert juste à lever une erreur si besoin. + Parameters: + ----------- + df : pd.DataFrame + The DataFrame to be encoded. + + Returns: + ------- + np.ndarray + The encoded DataFrame as a numpy ndarray, with numeric data concatenated + with one-hot encoded categorical and boolean data. """ - pass + df_numerics = df.select_dtypes(include=['number']).to_numpy() + + if not self.encoder: + self.encoder = OneHotEncoder() + + df_non_numerics = self.encoder.fit_transform( + df.select_dtypes(include=['object', 'bool']) + ).toarray() - def _encode_dataframe(df): + return np.concatenate((df_numerics, df_non_numerics), axis=1) + + def _pklm_preprocessing(self, df: Union[pd.DataFrame, np.ndarray]) -> np.ndarray: """ - Si les types sont bien acceptés, faire un OneHot sur les catégories acceptées et return - un np.ndarray. + Preprocesses the input DataFrame or ndarray for further processing. + + Parameters: + ----------- + df : Union[pd.DataFrame, np.ndarray] + The input data to be preprocessed. Can be a pandas DataFrame or a numpy ndarray. + + Returns: + ------- + np.ndarray + The preprocessed data as a numpy ndarray. + + Raises: + ------- + TypeNotHandled + If the DataFrame contains columns with data types that are not numeric, string, or boolean. """ - pass + if isinstance(df, np.ndarray): + return df + + self._check_pd_df_dtypes(df) + return self._encode_dataframe(df) def _draw_features_and_target_indexes(self, df: np.ndarray) -> Tuple[np.ndarray, int]: """ @@ -435,7 +494,7 @@ def _parallel_process_projection( ) for M_perm in list_permutations) return u_hat, result_u_permutations - def test(self, df: np.ndarray) -> float: + def test(self, df: Union[pd.DataFrame, np.ndarray]) -> float: """ Apply the PKLM test over a real dataset. @@ -450,6 +509,7 @@ def test(self, df: np.ndarray) -> float: float The p-value of the test. """ + df = self._pklm_preprocessing(df) self._check_nb_patterns(df) M = np.isnan(df).astype(int) diff --git a/tests/analysis/test_holes_characterization.py b/tests/analysis/test_holes_characterization.py index 5ed9f166..85953155 100644 --- a/tests/analysis/test_holes_characterization.py +++ b/tests/analysis/test_holes_characterization.py @@ -6,7 +6,7 @@ from qolmat.analysis.holes_characterization import LittleTest, PKLMTest from qolmat.benchmark.missing_patterns import UniformHoleGenerator from qolmat.imputations.imputers import ImputerEM - +from qolmat.utils.exceptions import TypeNotHandled ### Tests for the LittleTest class @@ -68,6 +68,27 @@ def test_attribute_error(): ### Tests for the PKLMTest class +@pytest.fixture +def multitypes_dataframe() -> pd.DataFrame: + return pd.DataFrame({ + 'int_col': [1, 2, 3], + 'float_col': [1.1, 2.2, 3.3], + 'str_col': ['a', 'b', 'c'], + 'bool_col': [True, False, True], + 'datetime_col': pd.to_datetime(['2021-01-01', '2021-01-02', '2021-01-03']) + }) + + +@pytest.fixture +def supported_multitypes_dataframe() -> pd.DataFrame: + return pd.DataFrame({ + 'int_col': [1, 2, 3], + 'float_col': [1.1, 2.2, 3.3], + 'str_col': ['a', 'b', 'c'], + 'bool_col': [True, False, True] + }) + + @pytest.fixture def np_matrix_with_nan_mcar() -> np.ndarray: rng = np.random.default_rng(42) @@ -95,8 +116,27 @@ def oob_probabilities() -> np.ndarray: return np.matrix([[0.5, 0.5], [0, 1], [1, 0], [1, 0]]).A +def test__check_pd_df_dtypes_raise_error(multitypes_dataframe): + with pytest.raises(TypeNotHandled): + mcar_test_pklm = PKLMTest(random_state=42) + mcar_test_pklm._check_pd_df_dtypes(multitypes_dataframe) + + +def test__check_pd_df_dtypes(supported_multitypes_dataframe): + mcar_test_pklm = PKLMTest(random_state=42) + mcar_test_pklm._check_pd_df_dtypes(supported_multitypes_dataframe) + + +def test__encode_dataframe(supported_multitypes_dataframe): + mcar_test_pklm = PKLMTest(random_state=42) + np_dataframe = mcar_test_pklm._encode_dataframe(supported_multitypes_dataframe) + n_rows, n_cols = np_dataframe.shape + assert n_rows == 3 + assert n_cols == 7 + + def test__draw_features_and_target_indexes(np_matrix_with_nan_mcar): - mcar_test_pklm = PKLMTest(42) + mcar_test_pklm = PKLMTest(random_state=42) _, p = np_matrix_with_nan_mcar.shape features_idx, target_idx = mcar_test_pklm._draw_features_and_target_indexes(np_matrix_with_nan_mcar) assert isinstance(target_idx, np.integer) From 0374dec8849c8bf508829203a79f545ab5d67bdd Mon Sep 17 00:00:00 2001 From: Adrien Couratier Date: Wed, 28 Aug 2024 11:17:38 +0200 Subject: [PATCH 07/39] :sparkles: Add the partial p-values feature. --- qolmat/analysis/holes_characterization.py | 287 ++++++++++++------ tests/analysis/test_holes_characterization.py | 23 ++ 2 files changed, 217 insertions(+), 93 deletions(-) diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index 3fa9fc66..3f9df6c1 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -22,6 +22,7 @@ class McarTest(ABC): random_state : int, optional The seed of the pseudo random number generator to use, for reproductibility. """ + def __init__(self, random_state: Union[None, int, np.random.RandomState] = None): self.rng = sku.check_random_state(random_state) @@ -99,7 +100,9 @@ def test(self, df: pd.DataFrame) -> float: diff_means = obs_mean - ml_means[list(tup_pattern)] inv_sigma_pattern = np.linalg.inv(ml_cov[:, tup_pattern][tup_pattern, :]) - d0 += n_rows_pattern * np.dot(np.dot(diff_means, inv_sigma_pattern), diff_means.T) + d0 += n_rows_pattern * np.dot( + np.dot(diff_means, inv_sigma_pattern), diff_means.T + ) degree_f += tup_pattern.count(True) return 1 - float(chi2.cdf(d0, degree_f)) @@ -108,10 +111,10 @@ def test(self, df: pd.DataFrame) -> float: class PKLMTest(McarTest): """ This class implements the PKLM test, a fully non-parametric, easy-to-use, and powerful test - for the missing completely at random (MCAR) assumption on the missingness mechanism of a + for the missing completely at random (MCAR) assumption on the missingness mechanism of a dataset. The null hypothesis is "The missing data mechanism is MCAR". - This test is applicable to mixed data (quantitative and categoricals features). + This test is applicable to mixed data (quantitative and categoricals) types. References ---------- @@ -126,6 +129,8 @@ class PKLMTest(McarTest): Number of permutations. nb_trees_per_proj : int Number of trees per projection. + compute_partial_p_values : bool + If true, compute the partial p-values. exact_p_value : bool If True, compute exact p-value. encoder : OneHotEncoder or None, default=None @@ -140,14 +145,16 @@ def __init__( nb_projections: int = 100, nb_permutation: int = 30, nb_trees_per_proj: int = 200, + compute_partial_p_values: bool = False, exact_p_value: bool = False, - encoder: Union[None, OneHotEncoder] = None, # We could define more encoders. + encoder: Union[None, OneHotEncoder] = None, # We could define more encoders. random_state: Union[None, int, np.random.RandomState] = None, ): super().__init__(random_state=random_state) self.nb_projections = nb_projections self.nb_permutation = nb_permutation self.nb_trees_per_proj = nb_trees_per_proj + self.compute_partial_p_values = compute_partial_p_values self.exact_p_value = exact_p_value self.encoder = encoder @@ -195,21 +202,26 @@ def _check_pd_df_dtypes(df: pd.DataFrame): If any column has a data type that is not numeric, string, or boolean. """ allowed_types = [ - pd.api.types.is_numeric_dtype, - pd.api.types.is_string_dtype, - pd.api.types.is_bool_dtype + pd.api.types.is_numeric_dtype, + pd.api.types.is_string_dtype, + pd.api.types.is_bool_dtype, ] + def is_allowed_type(dtype): return any(check(dtype) for check in allowed_types) - invalid_columns = [(col, dtype) for col, dtype in df.dtypes.items() if not is_allowed_type(dtype)] + invalid_columns = [ + (col, dtype) + for col, dtype in df.dtypes.items() + if not is_allowed_type(dtype) + ] if invalid_columns: for column_name, dtype in invalid_columns: raise TypeNotHandled(col=column_name, type_col=dtype) def _encode_dataframe(self, df: pd.DataFrame) -> np.ndarray: """ - Encodes the DataFrame by converting numeric columns to a numpy array + Encodes the DataFrame by converting numeric columns to a numpy array and applying one-hot encoding to non-numeric columns. Parameters: @@ -220,16 +232,16 @@ def _encode_dataframe(self, df: pd.DataFrame) -> np.ndarray: Returns: ------- np.ndarray - The encoded DataFrame as a numpy ndarray, with numeric data concatenated + The encoded DataFrame as a numpy ndarray, with numeric data concatenated with one-hot encoded categorical and boolean data. """ - df_numerics = df.select_dtypes(include=['number']).to_numpy() + df_numerics = df.select_dtypes(include=["number"]).to_numpy() if not self.encoder: self.encoder = OneHotEncoder() df_non_numerics = self.encoder.fit_transform( - df.select_dtypes(include=['object', 'bool']) + df.select_dtypes(include=["object", "bool"]) ).toarray() return np.concatenate((df_numerics, df_non_numerics), axis=1) @@ -255,7 +267,7 @@ def _pklm_preprocessing(self, df: Union[pd.DataFrame, np.ndarray]) -> np.ndarray """ if isinstance(df, np.ndarray): return df - + self._check_pd_df_dtypes(df) return self._encode_dataframe(df) @@ -283,7 +295,7 @@ def _draw_features_and_target_indexes(self, df: np.ndarray) -> Tuple[np.ndarray, def _check_draw(df: np.ndarray, features_idx: np.ndarray, target_idx: int) -> np.bool_: """ Checks if the drawn features and target are valid. - # TODO : Need to develop. + # TODO : Need to develop ? Parameters: ----------- @@ -299,7 +311,7 @@ def _check_draw(df: np.ndarray, features_idx: np.ndarray, target_idx: int) -> np bool True if the draw is valid, False otherwise. """ - target_values = df[~np.isnan(df[:,features_idx]).any(axis=1)][:, target_idx] + target_values = df[~np.isnan(df[:, features_idx]).any(axis=1)][:, target_idx] is_nan = np.isnan(target_values).any() is_distinct_values = (~np.isnan(target_values)).any() return is_nan and is_distinct_values @@ -336,21 +348,25 @@ def _build_dataset( Parameters: ----------- - df: np.ndarray - Input data array. - features_idx: np.ndarray - Indices of the feature columns. - target_idx: int - Index of the target column. + df: np.ndarray + Input data array. + features_idx: np.ndarray + Indices of the feature columns. + target_idx: int + Index of the target column. Returns: -------- - Tuple[np.ndarray, np.ndarray]: A tuple containing: - - X (np.ndarray): Array of selected features. - - y (np.ndarray): Binary array indicating presence of NaN (1) in the target column. + Tuple[np.ndarray, np.ndarray]: A tuple containing: + - X (np.ndarray): Array of selected features. + - y (np.ndarray): Binary array indicating presence of NaN (1) in the target column. """ X = df[~np.isnan(df[:, features_idx]).any(axis=1)][:, features_idx] - y = np.where(np.isnan(df[~np.isnan(df[:, features_idx]).any(axis=1)][:, target_idx]), 1, 0) + y = np.where( + np.isnan(df[~np.isnan(df[:, features_idx]).any(axis=1)][:, target_idx]), + 1, + 0, + ) return X, y @staticmethod @@ -361,19 +377,19 @@ def _build_label( target_idx: int ) -> np.ndarray: """ - Builds a label array by selecting target values from a permutation array, + Builds a label array by selecting target values from a permutation array, excluding rows with NaN values in the specified feature columns. Parameters: ----------- - df: np.ndarray - Input data array. - perm: np.ndarray - Permutation array from which labels are selected. - features_idx: np.ndarray - Indices of the feature columns. - target_idx: int - Index of the target column in the permutation array. + df: np.ndarray + Input data array. + perm: np.ndarray + Permutation array from which labels are selected. + features_idx: np.ndarray + Indices of the feature columns. + target_idx: int + Index of the target column in the permutation array. Returns: -------- @@ -381,17 +397,16 @@ def _build_label( """ return perm[~np.isnan(df[:, features_idx]).any(axis=1), target_idx] - def _get_oob_probabilities(self, X: np.ndarray, y: np.ndarray) -> np.ndarray: """ Trains a RandomForestClassifier and retrieves out-of-bag (OOB) probabilities. Parameters: ----------- - X: np.ndarray - Feature array for training. - y: np.ndarray - Target array for training. + X: np.ndarray + Feature array for training. + y: np.ndarray + Target array for training. Returns: -------- @@ -399,11 +414,11 @@ def _get_oob_probabilities(self, X: np.ndarray, y: np.ndarray) -> np.ndarray: """ clf = RandomForestClassifier( n_estimators=self.nb_trees_per_proj, - #max_features=None, + # max_features=None, min_samples_split=10, bootstrap=True, oob_score=True, - random_state=self.rng + random_state=self.rng, ) clf.fit(X, y) return clf.oob_decision_function_ @@ -411,93 +426,164 @@ def _get_oob_probabilities(self, X: np.ndarray, y: np.ndarray) -> np.ndarray: @staticmethod def _U_hat(oob_probabilities: np.ndarray, labels: np.ndarray) -> float: """ - Computes the U_hat statistic, a measure of classifier performance, using + Computes the U_hat statistic, a measure of classifier performance, using out-of-bag probabilities and true labels. Parameters: ----------- - oob_probabilities: np.ndarray - Out-of-bag probabilities for each class. - labels: np.ndarray - True labels for the data. + oob_probabilities: np.ndarray + Out-of-bag probabilities for each class. + labels: np.ndarray + True labels for the data. Returns: -------- float: The computed U_hat statistic. """ - oob_probabilities = np.clip(oob_probabilities, 1e-9, 1-1e-9) + oob_probabilities = np.clip(oob_probabilities, 1e-9, 1 - 1e-9) unique_labels = np.unique(labels) label_matrix = (labels[:, None] == unique_labels).astype(int) p_true = oob_probabilities * label_matrix p_false = oob_probabilities * (1 - label_matrix) - p0_0 = p_true[:, 0][np.where(p_true[:, 0] != 0.)] - p0_1 = p_false[:, 0][np.where(p_false[:, 0] != 0.)] - p1_1 = p_true[:, 1][np.where(p_true[:, 1] != 0.)] - p1_0 = p_false[:, 1][np.where(p_false[:, 1] != 0.)] + p0_0 = p_true[:, 0][np.where(p_true[:, 0] != 0.0)] + p0_1 = p_false[:, 0][np.where(p_false[:, 0] != 0.0)] + p1_1 = p_true[:, 1][np.where(p_true[:, 1] != 0.0)] + p1_0 = p_false[:, 1][np.where(p_false[:, 1] != 0.0)] if unique_labels.shape[0] == 1: if unique_labels[0] == 0: n0 = labels.shape[0] - return np.log(p0_0 / (1 - p0_0)).sum() / n0 - np.log(p1_0 / (1 - p1_0)).sum() / n0 + return ( + np.log(p0_0 / (1 - p0_0)).sum() / n0 + - np.log(p1_0 / (1 - p1_0)).sum() / n0 + ) else: n1 = labels.shape[0] - return np.log(p1_1 / (1 - p1_1)).sum() / n1 - np.log(p0_1 / (1 - p0_1)).sum() / n1 + return ( + np.log(p1_1 / (1 - p1_1)).sum() / n1 + - np.log(p0_1 / (1 - p0_1)).sum() / n1 + ) n0, n1 = label_matrix.sum(axis=0) - u_0 = np.log(p0_0 / (1 - p0_0)).sum() / n0 - np.log(p0_1 / (1 - p0_1)).sum() / n1 - u_1 = np.log(p1_1 / (1 - p1_1)).sum() / n1 - np.log(p1_0 / (1 - p1_0)).sum() / n0 + u_0 = ( + np.log(p0_0 / (1 - p0_0)).sum() / n0 - np.log(p0_1 / (1 - p0_1)).sum() / n1 + ) + u_1 = ( + np.log(p1_1 / (1 - p1_1)).sum() / n1 - np.log(p1_0 / (1 - p1_0)).sum() / n0 + ) return u_0 + u_1 def _parallel_process_permutation( - self, - df: np.ndarray, - M_perm: np.ndarray, - features_idx: np.ndarray, - target_idx: int, - oob_probabilities: np.ndarray - ) -> float: + self, + df: np.ndarray, + M_perm: np.ndarray, + features_idx: np.ndarray, + target_idx: int, + oob_probabilities: np.ndarray, + ) -> float: y = self._build_label(df, M_perm, features_idx, target_idx) return self._U_hat(oob_probabilities, y) def _parallel_process_permutation_exact( - self, - df: np.ndarray, - M_perm: np.ndarray, - features_idx: np.ndarray, - target_idx: int, - oob_probabilites_unused: np.ndarray - ) -> float: + self, + df: np.ndarray, + M_perm: np.ndarray, + features_idx: np.ndarray, + target_idx: int, + oob_probabilites_unused: np.ndarray, + ) -> float: X, _ = self._build_dataset(df, features_idx, target_idx) y = self._build_label(df, M_perm, features_idx, target_idx) oob_probabilities = self._get_oob_probabilities(X, y) return self._U_hat(oob_probabilities, y) def _parallel_process_projection( - self, - df: np.ndarray, - list_permutations: List[np.ndarray], - features_idx: np.ndarray, - target_idx: int, + self, + df: np.ndarray, + list_permutations: List[np.ndarray], + features_idx: np.ndarray, + target_idx: int, ) -> Tuple[float, List[float]]: X, y = self._build_dataset(df, features_idx, target_idx) oob_probabilities = self._get_oob_probabilities(X, y) u_hat = self._U_hat(oob_probabilities, y) - result_u_permutations = Parallel(n_jobs=-1)(delayed(self.process_permutation)( - df, - M_perm, - features_idx, - target_idx, - oob_probabilities - ) for M_perm in list_permutations) + result_u_permutations = Parallel(n_jobs=-1)( + delayed(self.process_permutation)( + df, M_perm, features_idx, target_idx, oob_probabilities + ) + for M_perm in list_permutations + ) return u_hat, result_u_permutations - def test(self, df: Union[pd.DataFrame, np.ndarray]) -> float: + @staticmethod + def _build_B(list_proj: List, n_cols: int) -> np.ndarray: """ - Apply the PKLM test over a real dataset. + Constructs a binary matrix B based on the given projections. + Parameters: + ----------- + list_proj : List + A list of tuples where each tuple represents a projection, and the + second element of each tuple is an index used to build the target. + n_cols : int + The number of columns in the resulting matrix B. + + Returns: + -------- + np.ndarray + A binary matrix of shape (n_cols, len(list_proj)) where each column corresponds to a + projection, and the entries are 0 or 1 based on the projections. + """ + list_bi = [projection[1] for projection in list_proj] + B = np.ones((len(list_proj), n_cols), dtype=int) + + for j in range(len(list_bi)): + B[j, list_bi[j]] = 0 + + return B.transpose() + + def _compute_partial_p_value( + self, + B: np.ndarray, + U: np.ndarray, + U_sigma: np.ndarray, + k: int + ) -> float: + """ + Computes the partial p-value for a statistical test based on a given permutation. + + Parameters: + ----------- + B : np.ndarray + Pass matrix indicating the column used to create the target in each projection. + U : np.ndarray + A vector of shape (nb_permutations,) representing the observed test statistics. + U_sigma : np.ndarray + A matrix of shape (nb_permutations, nb_observations) where each row represents the test + statistics for a given projection and all the permutations. + k : int + The index of the column on which to compute the partial p_value. + + Returns: + -------- + float + The partial p-value. + """ + U_k = B[k, :]@U + p_v_k = 1 + + for u_sigma_k in (B[k, :]@U_sigma).tolist(): + if u_sigma_k >= U_k: + p_v_k += 1 + + return p_v_k / (self.nb_permutation + 1) + + def test(self, df: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, List[float]]]: + """ + Apply the PKLM test over a real dataset. Parameters ---------- @@ -507,7 +593,10 @@ def test(self, df: Union[pd.DataFrame, np.ndarray]) -> float: Returns ------- float - The p-value of the test. + If compute_partial_p_values=False. Returns the p-value of the test. + Tuple[float, List[float]] + If compute_partial_p_values=True. Returns the p-value of the test and the list of all + the partial p-values. """ df = self._pklm_preprocessing(df) self._check_nb_patterns(df) @@ -515,20 +604,21 @@ def test(self, df: Union[pd.DataFrame, np.ndarray]) -> float: M = np.isnan(df).astype(int) list_proj = [self._draw_projection(df) for _ in range(self.nb_projections)] list_perm = [self.rng.permutation(M) for _ in range(self.nb_permutation)] - U = 0. - list_U_sigma = [0. for _ in range(self.nb_permutation)] + U = 0.0 + list_U_sigma = [0.0 for _ in range(self.nb_permutation)] - parallel_results = Parallel(n_jobs=-1)(delayed(self._parallel_process_projection)( - df, - list_perm, - features_idx, - target_idx - ) for features_idx, target_idx in list_proj) + parallel_results = Parallel(n_jobs=-1)( + delayed(self._parallel_process_projection)( + df, list_perm, features_idx, target_idx + ) + for features_idx, target_idx in list_proj + ) for U_projection, results in parallel_results: U += U_projection list_U_sigma = [x + y for x, y in zip(list_U_sigma, results)] + # Je suggère d'alléger le code de cette manipulation même si théoriquement ça a de la valeur U = U / self.nb_projections list_U_sigma = [x / self.nb_permutation for x in list_U_sigma] @@ -536,4 +626,15 @@ def test(self, df: Union[pd.DataFrame, np.ndarray]) -> float: for u_sigma in list_U_sigma: if u_sigma >= U: p_value += 1 - return p_value / (self.nb_permutation + 1) + + p_value = p_value / (self.nb_permutation + 1) + + if not self.compute_partial_p_values: + return p_value + else: + _, n_cols = df.shape + B = self._build_B(list_proj, n_cols) + U = np.array([item[0] for item in parallel_results]) + U_sigma = np.array([item[1] for item in parallel_results]) + p_values = [self._compute_partial_p_value(B, U, U_sigma, k) for k in range(n_cols)] + return p_value, p_values diff --git a/tests/analysis/test_holes_characterization.py b/tests/analysis/test_holes_characterization.py index 85953155..6b320b30 100644 --- a/tests/analysis/test_holes_characterization.py +++ b/tests/analysis/test_holes_characterization.py @@ -2,6 +2,7 @@ import pandas as pd import pytest from scipy.stats import norm +from sympy import li from qolmat.analysis.holes_characterization import LittleTest, PKLMTest from qolmat.benchmark.missing_patterns import UniformHoleGenerator @@ -222,3 +223,25 @@ def test__U_hat_computation(request, oob_fixture, label, expected): mcar_test_pklm = PKLMTest() u_hat = mcar_test_pklm._U_hat(oob_prob, label) assert round(u_hat, 2) == round(expected, 2) + +@pytest.mark.parametrize( + "list_proj, n_cols", + [ + ( + [ + (np.array([3, 1]), 0), + (np.array([0]), 1), + (np.array([3]), 0), + (np.array([1, 2]), 3), + (np.array([3, 0]), 2), + (np.array([0, 1]), 2) + ], + 4 + ) + ] +) +def test__build_B(list_proj, n_cols): + mcar_test_pklm = PKLMTest() + B = mcar_test_pklm._build_B(list_proj, n_cols) + column_sums = np.sum(B, axis=0) + assert np.all(column_sums == 3) From c9541808ed97e3be2040c1cff89c617d3f3aa58f Mon Sep 17 00:00:00 2001 From: Adrien Couratier Date: Wed, 28 Aug 2024 12:16:57 +0200 Subject: [PATCH 08/39] :memo: Update the tutorial with the partial p-values section. --- docs/analysis.rst | 8 +- examples/tutorials/plot_tuto_mcar.py | 201 ++++++++++++++++++++++++--- 2 files changed, 186 insertions(+), 23 deletions(-) diff --git a/docs/analysis.rst b/docs/analysis.rst index 4dcbd2ec..7f784499 100644 --- a/docs/analysis.rst +++ b/docs/analysis.rst @@ -11,7 +11,7 @@ The analysis module provides tools to characterize the type of holes. The MNAR case is the trickiest, the user must first consider whether their missing data mechanism is MNAR. In the meantime, we make assume that the missing-data mechanism is ignorable (ie., it is not MNAR). If an MNAR mechanism is suspected, please see this article :ref:`An approach to test for MNAR [1]` for relevant actions. -Then Qolmat proposes a test to determine whether the missing data mechanism is MCAR or MAR. +Then Qolmat proposes two tests to determine whether the missing data mechanism is MCAR or MAR. 2. How to use the results ------------------------- @@ -50,7 +50,11 @@ The best-known MCAR test is the :ref:`Little [2]` test, and it h b. PKLM Test ^^^^^^^^^^^^ -The :ref:`PKLM [2]` (Projected Kullback-Leibler MCAR) test compares the distributions of different missing patterns on random projections in the variable space of the data. This recent test applies to mixed-type data. It is not implemented yet in Qolmat. +The :ref:`PKLM [2]` (Projected Kullback-Leibler MCAR) test compares the distributions of different missing patterns on random projections in the variable space of the data. This recent test applies to mixed-type data. The :class:`PKLMTest` is now implemented in Qolmat. +To carry out this test, we perform random projections in the variable space of the data. These random projections allow us to construct a fully observed sub-matrix and an associated number of missing patterns. +The idea is then to compare the distributions of the missing patterns through the Kullback-Leibler distance. +To do this, the distributions for each pattern are estimated using Random Forests. + References ---------- diff --git a/examples/tutorials/plot_tuto_mcar.py b/examples/tutorials/plot_tuto_mcar.py index c43d1217..9811b7f6 100644 --- a/examples/tutorials/plot_tuto_mcar.py +++ b/examples/tutorials/plot_tuto_mcar.py @@ -3,7 +3,7 @@ Tutorial for Testing the MCAR Case ============================================ -In this tutorial, we show how to test the MCAR case using the Little's test. +In this tutorial, we show how to test the MCAR case using the Little and the PKLM tests. """ # %% @@ -14,7 +14,7 @@ import pandas as pd from scipy.stats import norm -from qolmat.analysis.holes_characterization import LittleTest +from qolmat.analysis.holes_characterization import LittleTest, PKLMTest from qolmat.benchmark.missing_patterns import UniformHoleGenerator plt.rcParams.update({"font.size": 12}) @@ -31,22 +31,32 @@ q975 = norm.ppf(0.975) # %% +# 1. Testing the MCAR case with the Little's test and the PKLM test. +# ------------------------------------------------------------------ +# # The Little's test -# --------------------------------------------------------------- +# ================= +# # First, we need to introduce the concept of a missing pattern. A missing pattern, also called a # pattern, is the structure of observed and missing values in a dataset. For example, in a # dataset with two columns, the possible patterns are: (0, 0), (1, 0), (0, 1), (1, 1). The value 1 # (0) indicates that the column value is missing (observed). # # The null hypothesis, H0, is: "The means of observations within each pattern are similar.". + +# %% +# The PKLM test +# ============= +# The test compares distributions of different missing patterns. # +# The null hypothesis, H0, is: "Distributions within each pattern are similar.". # We choose to use the classic threshold of 5%. If the test p-value is below this threshold, # we reject the null hypothesis. -# -# This notebook shows how the Little's test performs on a simplistic case and its limitations. We -# instanciate a test object with a random state for reproducibility. +# This notebook shows how the Little and PKLM tests perform on a simplistic case and their +# limitations. We instanciate a test object with a random state for reproducibility. -test_mcar = LittleTest(random_state=rng) +little_test_mcar = LittleTest(random_state=rng) +pklm_test_mcar = PKLMTest(random_state=rng) # %% # Case 1: MCAR holes (True negative) @@ -77,11 +87,13 @@ plt.show() # %% -result = test_mcar.test(df_nan) -print(f"Test p-value: {result:.2%}") +little_result = little_test_mcar.test(df_nan) +pklm_result = pklm_test_mcar.test(df_nan) +print(f"The p-value of the Little's test is: {little_result:.2%}") +print(f"The p-value of the PKLM test is: {pklm_result:.2%}") # %% -# The p-value is larger than 0.05, therefore we don't reject the HO MCAR assumption. In this case -# this is a true negative. +# The two p-values are larger than 0.05, therefore we don't reject the H0 MCAR assumption. +# In this case this is a true negative. # %% # Case 2: MAR holes with mean bias (True positive) @@ -110,11 +122,13 @@ # %% -result = test_mcar.test(df_nan) -print(f"Test p-value: {result:.2%}") +little_result = little_test_mcar.test(df_nan) +pklm_result = pklm_test_mcar.test(df_nan) +print(f"The p-value of the Little's test is: {little_result:.2%}") +print(f"The p-value of the PKLM test is: {pklm_result:.2%}") # %% -# The p-value is smaller than 0.05, therefore we reject the HO MCAR assumption. In this case -# this is a true positive. +# The two p-values are smaller than 0.05, therefore we reject the H0 MCAR assumption. +# In this case this is a true positive. # %% # Case 3: MAR holes with any mean bias (False negative) @@ -149,17 +163,162 @@ # %% -result = test_mcar.test(df_nan) -print(f"Test p-value: {result:.2%}") +little_result = little_test_mcar.test(df_nan) +pklm_result = pklm_test_mcar.test(df_nan) +print(f"The p-value of the Little's test is: {little_result:.2%}") +print(f"The p-value of the PKLM test is: {pklm_result:.2%}") # %% -# The p-value is larger than 0.05, therefore we don't reject the HO MCAR assumption. In this case -# this is a false negative since the missingness mechanism is MAR. +# The Little's p-value is larger than 0.05, therefore, using this test we don't reject the H0 MCAR +# assumption. In this case this is a false negative since the missingness mechanism is MAR. +# +# However the PKLM test p-value is smaller than 0.05 therefore we don't reject the H0 MCAR +# assumption. In this case this is a true negative. # %% -# Limitations -# ----------- +# Limitations and conclusion +# ========================== # In this tutoriel, we can see that Little's test fails to detect covariance heterogeneity between # patterns. # # We also note that the Little's test does not handle categorical data or temporally # correlated data. +# +# This is why we have implemented the PKLM test, which makes up for the shortcomings of the Little +# test. We present this test in more detail in the next section. + +# %% +# 2. The PKLM test. +# ------------------------------------------------------------------ + +# Il faut parler : +# - temps de calcul +# - Les paramètres qui l'affectent le plus +# - L'application sur données mixtes + +# %% +# 2.1 Hyperparmaters +# ================================================ +# +# As we have seen, Little's test only applies to quantitative data. In real life, however, it is +# common to have to deal with mixed data. Here's an example of how to use the PKLM test on a dataset +# with mixed data types. + +# %% +# 2.2 Application on mixed data types +# ================================================ +# +# As we have seen, Little's test only applies to quantitative data. In real life, however, it is +# common to have to deal with mixed data. Here's an example of how to use the PKLM test on a dataset +# with mixed data types. + +# %% +n_rows = 100 + +col1 = rng.rand(n_rows) * 100 +col2 = rng.randint(1, 100, n_rows) +col3 = rng.choice([True, False], n_rows) +modalities = ['A', 'B', 'C', 'D'] +col4 = rng.choice(modalities, n_rows) + +df = pd.DataFrame({ + 'Numeric1': col1, + 'Numeric2': col2, + 'Boolean': col3, + 'Object': col4 +}) + +hole_gen = UniformHoleGenerator( + n_splits=1, + ratio_masked=0.2, + subset=['Numeric1', 'Numeric2', 'Boolean', 'Object'], + random_state=rng +) +df_mask = hole_gen.generate_mask(df) +df_nan = df.where(~df_mask, np.nan) +df_nan.dtypes + +# %% +pklm_result = pklm_test_mcar.test(df_nan) +print(f"The p-value of the PKLM test is: {pklm_result:.2%}") + +# %% +# To perform the PKLM test over mixed data types, non numerical features need to be encoded. The +# default encoder in the :class:`~qolmat.analysis.holes_characterization.PKLMTest` class is the +# default OneHotEncoder from scikit-learn. If you wish to use an encoder adapted to your data, you +# can perform this encoding step beforehand, and then use the PKLM test. +# Currently, we do not support the following types : +# +# - datetimes +# +# - timedeltas +# +# - Pandas datetimetz + +# %% +# 2.3 Partial p-values +# ================================================ +# +# In addition, the PKLM test can be used to calculate partial p-values. We denote as many partial +# p-values as there are columns in the input dataframe. This “partial” p-value corresponds to the +# effect of removing the patterns induced by variable k. +# +# Let's take a look at an example of how to use this feature + +# %% +data = rng.multivariate_normal( + mean=[0, 0, 0, 0], + cov=[[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], + size=400 +) +df = pd.DataFrame(data=data, columns=["Column 1", "Column 2", "Column 3", "Column 4"]) + +df_mask = pd.DataFrame( + { + "Column 1": False, + "Column 2": df["Column 1"] > q975, + "Column 3": False, + "Column 4": False, + }, + index=df.index +) +df_nan = df.where(~df_mask, np.nan) + +# %% +# The missing-data mechanism is clearly MAR. Intuitively, if we remove the second column from the +# matrix, the missing-data mechanism is MCAR. Let's see how the PKLM test can help us identify the +# variable responsible for the MAR mechanism. + +# %% +pklm_test = PKLMTest(random_state=rng, compute_partial_p_values=True) +p_value, partial_p_values = pklm_test.test(df_nan) +print(f"The p-value of the PKLM test is: {p_value:.2%}") + +# %% +# The test result confirms that we can reject the null hypothesis and therefore assume that the +# missing-data mechanism is MAR. +# Let's now take a look at what partial p-values can tell us. + +# %% +for col_index, partial_p_v in enumerate(partial_p_values): + print(f"The partial p-value for the column index {col_index + 1} is: {partial_p_v:.2%}") + +# %% +# As a reminder, This “partial” p-value corresponds to the effect of removing the patterns induced +# by variable k. As a result, by removing the missing patterns induced by variable 2, the p-v rises +# above the significance threshold set beforehand. Thus in this sense, the test detects that the +# main culprit of the MAR mechanism lies in the second variable. + + +# %% +# Calculation time +# | **n_rows** | **n_cols** | **Calculation_time** | +# |------------|------------|----------------------| +# | 200 | 2 | 2"12 | +# | 500 | 2 | 2"24 | +# | 500 | 4 | 2"18 | +# | 1000 | 4 | 2"48 | +# | 1000 | 6 | 2"42 | +# | 10000 | 6 | 20"54 | +# | 10000 | 10 | 14"48 | +# | 100000 | 10 | 4'51" | +# | 100000 | 15 | 3'06" | \ No newline at end of file From f91f42146d06fed62bdc83512162cdb8b399df0f Mon Sep 17 00:00:00 2001 From: Adrien Couratier Date: Wed, 28 Aug 2024 14:46:14 +0200 Subject: [PATCH 09/39] Developp the "parameters and hyper-parameters" section in the tutorial for the PKLM test. --- examples/tutorials/plot_tuto_mcar.py | 85 +++++++++++++++++++---- qolmat/analysis/holes_characterization.py | 4 ++ 2 files changed, 76 insertions(+), 13 deletions(-) diff --git a/examples/tutorials/plot_tuto_mcar.py b/examples/tutorials/plot_tuto_mcar.py index 9811b7f6..1dc6e3b5 100644 --- a/examples/tutorials/plot_tuto_mcar.py +++ b/examples/tutorials/plot_tuto_mcar.py @@ -187,21 +187,81 @@ # test. We present this test in more detail in the next section. # %% -# 2. The PKLM test. +# 2. The PKLM test # ------------------------------------------------------------------ +# +# The PKLM test is very powerful for several reasons. Firstly, it covers the concerns that Little's +# test may have (covariance heterogeneity). Secondly, it is currently the only MCAR test applicable +# to mixed data. Finally, it proposes a concept of partial p-value which enables us to carry out a +# variable-by-variable diagnosis to identify the potential causes of a MAR mechanism. +# +# There is a parameter in the paper called size.res.set. The authors of the paper recommend setting +# this parameter to 2. We have chosen to follow this advice and not leave the possibility of +# increasing this parameter. The results are satisfactory and the code is simpler. +# +# It does have one disadvantage, however: its calculation time. +# -# Il faut parler : -# - temps de calcul -# - Les paramètres qui l'affectent le plus -# - L'application sur données mixtes +# %% + +""" +Calculation time +================ + ++------------+------------+----------------------+ +| **n_rows** | **n_cols** | **Calculation_time** | ++============+============+======================+ +| 200 | 2 | 2"12 | ++------------+------------+----------------------+ +| 500 | 2 | 2"24 | ++------------+------------+----------------------+ +| 500 | 4 | 2"18 | ++------------+------------+----------------------+ +| 1000 | 4 | 2"48 | ++------------+------------+----------------------+ +| 1000 | 6 | 2"42 | ++------------+------------+----------------------+ +| 10000 | 6 | 20"54 | ++------------+------------+----------------------+ +| 10000 | 10 | 14"48 | ++------------+------------+----------------------+ +| 100000 | 10 | 4'51" | ++------------+------------+----------------------+ +| 100000 | 15 | 3'06" | ++------------+------------+----------------------+ +""" # %% -# 2.1 Hyperparmaters +# 2.1 Parameters and Hyperparmaters # ================================================ # -# As we have seen, Little's test only applies to quantitative data. In real life, however, it is -# common to have to deal with mixed data. Here's an example of how to use the PKLM test on a dataset -# with mixed data types. +# To use the PKLM test properly, it may be necessary to understand the use of hyper-parameters. +# +# * ``nb_projections``: Number of projections on which the test statistic is calculated. This +# parameter has the greatest influence on test calculation time. Its defaut value +# ``nb_projections=100``. +# Est-ce qu'on donne des ordres de grandeurs utiles ? J'avais un peu fait ce travail. +# +# * ``nb_permutation`` : Number of permutations of the projected targets. The higher is better. This +# parameter has little impact on calculation time. +# Its default value ``nb_permutation=30``. +# +# * ``nb_trees_per_proj`` : The number of subtrees in each random forest fitted. In order to +# estimate the Kullback-Leibler divergence, we need to obtain probabilities of belonging to +# certain missing patterns. Random Forests are used to estimate these probabilities. This +# hyperparameter has a significant impact on test calculation time. Its default +# value is ``nb_trees_per_proj=200`` +# +# * ``compute_partial_p_values``: Boolean that indicates if you want to compute the partial +# p-values. Those partial p-values could help the user to identify the variables responsible for +# the MAR missing-data mechanism. Please see the section 2.3 for examples. Its default value is +# ``compute_partial_p_values=False``. +# +# * ``encoder``: Scikit-Learn encoder to encode non-numerical values. +# Its default value ``encoder=sklearn.preprocessing.OneHotEncoder()`` +# +# * ``random_state``: Controls the randomness. Pass an int for reproducible output across +# multiple function calls. Its default value ``random_state=None`` # %% # 2.2 Application on mixed data types @@ -303,14 +363,13 @@ print(f"The partial p-value for the column index {col_index + 1} is: {partial_p_v:.2%}") # %% -# As a reminder, This “partial” p-value corresponds to the effect of removing the patterns induced -# by variable k. As a result, by removing the missing patterns induced by variable 2, the p-v rises -# above the significance threshold set beforehand. Thus in this sense, the test detects that the +# As a result, by removing the missing patterns induced by variable 2, the p-value rises +# above the significance threshold set beforehand. Thus in this sense, the test detects that the # main culprit of the MAR mechanism lies in the second variable. # %% -# Calculation time +# Calculation time -> TO BE DELETED # | **n_rows** | **n_cols** | **Calculation_time** | # |------------|------------|----------------------| # | 200 | 2 | 2"12 | diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index 3f9df6c1..986f1714 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -116,6 +116,10 @@ class PKLMTest(McarTest): This test is applicable to mixed data (quantitative and categoricals) types. + + If you're familiar with the paper, this implementation of the PKLM test was made for the + parameter size.resp.set=2 only. + References ---------- Spohn, M. L., Näf, J., Michel, L., & Meinshausen, N. (2021). PKLM: A flexible MCAR test using From 102c0275ae45d02aecb1f6d4a13819871d6c939e Mon Sep 17 00:00:00 2001 From: Adrien Couratier Date: Wed, 28 Aug 2024 15:10:35 +0200 Subject: [PATCH 10/39] Delete notebok --- examples/pklm/tuto_pklm.ipynb | 548 ---------------------------------- 1 file changed, 548 deletions(-) delete mode 100644 examples/pklm/tuto_pklm.ipynb diff --git a/examples/pklm/tuto_pklm.ipynb b/examples/pklm/tuto_pklm.ipynb deleted file mode 100644 index e8397a87..00000000 --- a/examples/pklm/tuto_pklm.ipynb +++ /dev/null @@ -1,548 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 10, - "id": "4cf3e844", - "metadata": {}, - "outputs": [], - "source": [ - "from matplotlib import pyplot as plt\n", - "import time\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", - "from scipy.stats import norm\n", - "\n", - "from qolmat.analysis.holes_characterization import PKLMTest\n", - "from qolmat.benchmark.missing_patterns import UniformHoleGenerator\n", - "\n", - "plt.rcParams.update({\"font.size\": 12})" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "7e3202a6", - "metadata": {}, - "outputs": [], - "source": [ - "rng = np.random.default_rng(42)\n", - "n_rows, n_cols = 5000, 4\n", - "matrix = rng.normal(size=(n_rows, n_cols))\n", - "num_nan = int(n_rows * n_cols * 0.20)\n", - "nan_indices = rng.choice(n_rows * n_cols, num_nan, replace=False)\n", - "matrix.flat[nan_indices] = np.nan" - ] - }, - { - "cell_type": "markdown", - "id": "a44983a6", - "metadata": {}, - "source": [ - "### First test with 'exact_p_value' = True" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "636bbd6c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.7096774193548387\n", - "--- 11.930958271026611 seconds ---\n" - ] - } - ], - "source": [ - "pklm_test = PKLMTest(\n", - " nb_projections=100,\n", - " nb_permutation=30,\n", - " nb_trees_per_proj=200,\n", - " exact_p_value=True,\n", - " random_state=42\n", - ")\n", - "\n", - "start_time = time.time()\n", - "p_v = pklm_test.test(matrix)\n", - "print(p_v)\n", - "print(\"--- %s seconds ---\" % (time.time() - start_time))" - ] - }, - { - "cell_type": "markdown", - "id": "75fedc83", - "metadata": {}, - "source": [ - "### First test with 'exact_p_value' = False" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "103fbb01", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.7096774193548387\n", - "--- 10.284496068954468 seconds ---\n" - ] - } - ], - "source": [ - "pklm_test = PKLMTest(\n", - " nb_projections=100,\n", - " nb_permutation=30,\n", - " nb_trees_per_proj=200,\n", - " exact_p_value=False,\n", - " random_state=42\n", - ")\n", - "\n", - "start_time = time.time()\n", - "p_v = pklm_test.test(matrix)\n", - "print(p_v)\n", - "print(\"--- %s seconds ---\" % (time.time() - start_time))" - ] - }, - { - "cell_type": "markdown", - "id": "ea1fc33c", - "metadata": {}, - "source": [ - "__Notes__ :\n", - "\n", - "- First this a weird to get the exact same results.\n", - "- Second, this is also weird that the second execution is juste one second faster.\n", - "\n", - "This need to be reviewed and fixed" - ] - }, - { - "cell_type": "markdown", - "id": "e000784b", - "metadata": {}, - "source": [ - "### Test with pandas dataframe and mixed datatypes" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "319e5a7f", - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Numeric1Numeric2BooleanCategory
024.84552892FalseB
145.99351369FalseD
286.6591942FalseB
341.94595334FalseC
488.45749571TrueC
\n", - "
" - ], - "text/plain": [ - " Numeric1 Numeric2 Boolean Category\n", - "0 24.845528 92 False B\n", - "1 45.993513 69 False D\n", - "2 86.659194 2 False B\n", - "3 41.945953 34 False C\n", - "4 88.457495 71 True C" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import pandas as pd\n", - "import numpy as np\n", - "\n", - "# Définir le nombre de lignes\n", - "n_rows = 100\n", - "\n", - "col1 = np.random.rand(n_rows) * 100\n", - "col2 = np.random.randint(1, 100, n_rows)\n", - "col3 = np.random.choice([True, False], n_rows)\n", - "modalities = ['A', 'B', 'C', 'D']\n", - "col4 = np.random.choice(modalities, n_rows)\n", - "\n", - "df = pd.DataFrame({\n", - " 'Numeric1': col1,\n", - " 'Numeric2': col2,\n", - " 'Boolean': col3,\n", - " 'Category': col4\n", - "})\n", - "\n", - "df.head()" - ] - }, - { - "cell_type": "markdown", - "id": "338dac31", - "metadata": {}, - "source": [ - "__Holes_creation__ : According tot he Qolmat tool" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "26c54631", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Numeric1Numeric2BooleanCategory
0NaN92.0FalseB
145.99351369.0FalseD
286.6591942.0FalseB
341.94595334.0FalseC
4NaN71.0TrueNaN
\n", - "
" - ], - "text/plain": [ - " Numeric1 Numeric2 Boolean Category\n", - "0 NaN 92.0 False B\n", - "1 45.993513 69.0 False D\n", - "2 86.659194 2.0 False B\n", - "3 41.945953 34.0 False C\n", - "4 NaN 71.0 True NaN" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "hole_gen = UniformHoleGenerator(\n", - " n_splits=1,\n", - " ratio_masked=0.2,\n", - " subset=['Numeric1', 'Numeric2', 'Boolean', 'Category'],\n", - " random_state=42\n", - ")\n", - "df_mask = hole_gen.generate_mask(df)\n", - "df_nan = df.where(~df_mask, np.nan)\n", - "df_nan.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "637f56c5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.6129032258064516\n", - "--- 1.973733901977539 seconds ---\n" - ] - } - ], - "source": [ - "pklm_test = PKLMTest(\n", - " nb_projections=100,\n", - " nb_permutation=30,\n", - " nb_trees_per_proj=200,\n", - " random_state=42\n", - ")\n", - "\n", - "start_time = time.time()\n", - "p_v = pklm_test.test(df_nan)\n", - "print(p_v)\n", - "print(\"--- %s seconds ---\" % (time.time() - start_time))" - ] - }, - { - "cell_type": "markdown", - "id": "ee8a1315", - "metadata": {}, - "source": [ - "### Go back with the previous examples" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "2ca2ef5c", - "metadata": {}, - "outputs": [], - "source": [ - "rng = np.random.RandomState(42)\n", - "data = rng.multivariate_normal(mean=[0, 0], cov=[[1, 0], [0, 1]], size=200)\n", - "df = pd.DataFrame(data=data, columns=[\"Column 1\", \"Column 2\"])\n", - "\n", - "q975 = norm.ppf(0.975)" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "e1a01a57", - "metadata": {}, - "outputs": [], - "source": [ - "pklm_test = PKLMTest(\n", - " nb_projections=100,\n", - " nb_permutation=100,\n", - " nb_trees_per_proj=200,\n", - " random_state=42\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "1f39721c", - "metadata": {}, - "source": [ - "### Case 1: MCAR holes (True negative)¶" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "a72d5da1", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.9207920792079208\n", - "--- 2.251690149307251 seconds ---\n" - ] - } - ], - "source": [ - "hole_gen = UniformHoleGenerator(\n", - " n_splits=1, random_state=rng, subset=[\"Column 2\"], ratio_masked=0.2\n", - ")\n", - "df_mask = hole_gen.generate_mask(df)\n", - "df_nan = df.where(~df_mask, np.nan)\n", - "\n", - "start_time = time.time()\n", - "p_v = pklm_test.test(df_nan)\n", - "print(p_v)\n", - "print(\"--- %s seconds ---\" % (time.time() - start_time))" - ] - }, - { - "cell_type": "markdown", - "id": "b113ded2", - "metadata": {}, - "source": [ - "### Case 2: MAR holes with mean bias (True positive)¶" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "86e75c47", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.009900990099009901\n", - "--- 2.0787599086761475 seconds ---\n" - ] - } - ], - "source": [ - "df_mask = pd.DataFrame({\"Column 1\": False, \"Column 2\": df[\"Column 1\"] > q975}, index=df.index)\n", - "\n", - "df_nan = df.where(~df_mask, np.nan)\n", - "\n", - "start_time = time.time()\n", - "p_v = pklm_test.test(df_nan)\n", - "print(p_v)\n", - "print(\"--- %s seconds ---\" % (time.time() - start_time))" - ] - }, - { - "cell_type": "markdown", - "id": "3262ae8c", - "metadata": {}, - "source": [ - "### Case 3: MAR holes with any mean bias (False negative)" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "60f35623", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.009900990099009901\n", - "--- 2.1425230503082275 seconds ---\n" - ] - } - ], - "source": [ - "df_mask = pd.DataFrame(\n", - " {\"Column 1\": False, \"Column 2\": df[\"Column 1\"].abs() > q975}, index=df.index\n", - ")\n", - "\n", - "df_nan = df.where(~df_mask, np.nan)\n", - "\n", - "start_time = time.time()\n", - "p_v = pklm_test.test(df_nan)\n", - "print(p_v)\n", - "print(\"--- %s seconds ---\" % (time.time() - start_time))" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.19" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From 234a5a19c746073493c412d09d7d23fc53ae4550 Mon Sep 17 00:00:00 2001 From: Adrien Couratier Date: Fri, 30 Aug 2024 16:05:14 +0200 Subject: [PATCH 11/39] :fire: Remove useless code & Move some generic functions into utils folder. Also add the p_value_validity notebook for the meeting with Jeffrey NAF. --- .../p_value_validity/p_value_validity.ipynb | 154 ++++++++++++++++ qolmat/analysis/holes_characterization.py | 170 ++++++------------ qolmat/utils/exceptions.py | 8 - qolmat/utils/input_check.py | 29 +++ tests/analysis/test_holes_characterization.py | 24 --- tests/utils/test_input_check.py | 48 +++++ 6 files changed, 290 insertions(+), 143 deletions(-) create mode 100644 examples/pklm/p_value_validity/p_value_validity.ipynb create mode 100644 qolmat/utils/input_check.py create mode 100644 tests/utils/test_input_check.py diff --git a/examples/pklm/p_value_validity/p_value_validity.ipynb b/examples/pklm/p_value_validity/p_value_validity.ipynb new file mode 100644 index 00000000..6dc082cf --- /dev/null +++ b/examples/pklm/p_value_validity/p_value_validity.ipynb @@ -0,0 +1,154 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "5cffe020", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from qolmat.analysis.holes_characterization import PKLMTest" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d347e25d", + "metadata": {}, + "outputs": [], + "source": [ + "def generate_randunif(N=500):\n", + " Z = np.random.uniform(0,1, N)\n", + "\n", + " X = np.sort(Z)\n", + " F = np.array(range(N))/float(N)\n", + " \n", + " return X, F" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "16b5f189", + "metadata": {}, + "outputs": [], + "source": [ + "def create_df_with_nan(n_rows: int, n_cols: int, nan_ratio: float) -> pd.DataFrame:\n", + " data = {f\"Colonne_{i}\": np.random.normal(size=n_rows).astype(float) for i in range(n_cols)}\n", + " df = pd.DataFrame(data)\n", + " nb_valeurs_manquantes = int(nan_ratio * df.size)\n", + " indices_valeurs_manquantes = np.random.choice(df.size, nb_valeurs_manquantes, replace=False)\n", + " df.values.flat[indices_valeurs_manquantes] = np.nan\n", + " return df" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "797a1534", + "metadata": {}, + "outputs": [], + "source": [ + "N_sim = 500\n", + "list_res_exact = []\n", + "list_res_approximation = []\n", + "\n", + "for _ in range(N_sim):\n", + " df = create_df_with_nan(500, 10, 0.35).to_numpy()\n", + " pklm_test_exact = PKLMTest(\n", + " nb_projections=100,\n", + " nb_permutation=30,\n", + " nb_trees_per_proj=200,\n", + " exact_p_value=True\n", + " )\n", + " pklm_test_approximation = PKLMTest(\n", + " nb_projections=100,\n", + " nb_permutation=30,\n", + " nb_trees_per_proj=200,\n", + " exact_p_value=False\n", + " )\n", + " list_res_exact.append(pklm_test_exact.test(df))\n", + " list_res_approximation.append(pklm_test_approximation.test(df))" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "5a3a0cd3", + "metadata": {}, + "outputs": [], + "source": [ + "Z_pv_exact = np.array(list_res_exact, dtype=np.float32)\n", + "X_pv_exact = np.sort(Z_pv_exact)\n", + "F_pv = np.array(range(N_sim))/float(N_sim)\n", + "\n", + "Z_pv_approximation = np.array(list_res_approximation, dtype=np.float32)\n", + "X_pv_approximation = np.sort(Z_pv_approximation)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "d94f9f27", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots()\n", + "\n", + "for _ in range(100):\n", + " X, F = generate_randunif()\n", + " plt.plot(X, F, 'b')\n", + " \n", + "ax.axline((0, 0), (1, 1), linewidth=1, color='r')\n", + "plt.plot(X_pv_exact, F_pv, 'g', label=\"Exact p_value\")\n", + "plt.plot(X_pv_approximation, F_pv, 'y', label=\"p_value without refitting\")\n", + "\n", + "plt.legend(\n", + " loc='upper left',\n", + " fontsize=8,\n", + ")\n", + "plt.xlabel(\"x: p_values under H0\")\n", + "plt.ylabel(\"F(X)\")\n", + "plt.title(\"Cumulative distribution function value of the p-values under H0\")\n", + "plt.grid()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.19" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index 986f1714..eed0684c 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -1,16 +1,16 @@ from abc import ABC, abstractmethod from typing import List, Optional, Tuple, Union +from category_encoders.one_hot import OneHotEncoder from joblib import Parallel, delayed import numpy as np import pandas as pd from sklearn.ensemble import RandomForestClassifier -from sklearn.preprocessing import OneHotEncoder from sklearn import utils as sku from scipy.stats import chi2 -from qolmat.utils.exceptions import TooManyMissingPatterns, TypeNotHandled from qolmat.imputations.imputers import ImputerEM +from qolmat.utils.input_check import check_pd_df_dtypes class McarTest(ABC): @@ -151,7 +151,7 @@ def __init__( nb_trees_per_proj: int = 200, compute_partial_p_values: bool = False, exact_p_value: bool = False, - encoder: Union[None, OneHotEncoder] = None, # We could define more encoders. + encoder: Union[None, OneHotEncoder] = None, random_state: Union[None, int, np.random.RandomState] = None, ): super().__init__(random_state=random_state) @@ -166,63 +166,6 @@ def __init__( self.process_permutation = self._parallel_process_permutation_exact self.process_permutation = self._parallel_process_permutation - @staticmethod - def _check_nb_patterns(df: np.ndarray) -> None: - """ - This method examines a NumPy array to identify distinct patterns of missing values (NaNs). - If the number of unique patterns exceeds the number of rows in the array, it raises a - `TooManyMissingPatterns` exception. - This condition comes from the PKLM paper, please see the reference if needed. - - Parameters: - ----------- - df : np.ndarray - 2D array with NaNs as missing values. - - Raises: - ------- - TooManyMissingPatterns: If unique missing patterns exceed the number of rows. - """ - n_rows, _ = df.shape - indicator_matrix = ~np.isnan(df) - patterns = set(map(tuple, indicator_matrix)) - nb_patterns = len(patterns) - if nb_patterns > n_rows: - raise TooManyMissingPatterns() - - @staticmethod - def _check_pd_df_dtypes(df: pd.DataFrame): - """ - Validates that the columns of the DataFrame have allowed data types. - - Parameters: - ----------- - df : pd.DataFrame - DataFrame whose columns' data types are to be checked. - - Raises: - ------- - TypeNotHandled - If any column has a data type that is not numeric, string, or boolean. - """ - allowed_types = [ - pd.api.types.is_numeric_dtype, - pd.api.types.is_string_dtype, - pd.api.types.is_bool_dtype, - ] - - def is_allowed_type(dtype): - return any(check(dtype) for check in allowed_types) - - invalid_columns = [ - (col, dtype) - for col, dtype in df.dtypes.items() - if not is_allowed_type(dtype) - ] - if invalid_columns: - for column_name, dtype in invalid_columns: - raise TypeNotHandled(col=column_name, type_col=dtype) - def _encode_dataframe(self, df: pd.DataFrame) -> np.ndarray: """ Encodes the DataFrame by converting numeric columns to a numpy array @@ -239,24 +182,22 @@ def _encode_dataframe(self, df: pd.DataFrame) -> np.ndarray: The encoded DataFrame as a numpy ndarray, with numeric data concatenated with one-hot encoded categorical and boolean data. """ - df_numerics = df.select_dtypes(include=["number"]).to_numpy() - if not self.encoder: - self.encoder = OneHotEncoder() - - df_non_numerics = self.encoder.fit_transform( - df.select_dtypes(include=["object", "bool"]) - ).toarray() + self.encoder = OneHotEncoder( + cols=df.select_dtypes(include=["object", "bool"]).columns, + return_df=False, + handle_missing='return_nan' + ) - return np.concatenate((df_numerics, df_non_numerics), axis=1) + return self.encoder.fit_transform(df) - def _pklm_preprocessing(self, df: Union[pd.DataFrame, np.ndarray]) -> np.ndarray: + def _pklm_preprocessing(self, X: Union[pd.DataFrame, np.ndarray]) -> np.ndarray: """ Preprocesses the input DataFrame or ndarray for further processing. Parameters: ----------- - df : Union[pd.DataFrame, np.ndarray] + X : Union[pd.DataFrame, np.ndarray] The input data to be preprocessed. Can be a pandas DataFrame or a numpy ndarray. Returns: @@ -269,19 +210,26 @@ def _pklm_preprocessing(self, df: Union[pd.DataFrame, np.ndarray]) -> np.ndarray TypeNotHandled If the DataFrame contains columns with data types that are not numeric, string, or boolean. """ - if isinstance(df, np.ndarray): - return df - - self._check_pd_df_dtypes(df) - return self._encode_dataframe(df) + if isinstance(X, np.ndarray): + return X + + check_pd_df_dtypes( + X, + [ + pd.api.types.is_numeric_dtype, + pd.api.types.is_string_dtype, + pd.api.types.is_bool_dtype + ] + ) + return self._encode_dataframe(X) - def _draw_features_and_target_indexes(self, df: np.ndarray) -> Tuple[np.ndarray, int]: + def _draw_features_and_target_indexes(self, X: np.ndarray) -> Tuple[np.ndarray, int]: """ Randomly selects features and a target from the dataframe. Parameters: ----------- - df : np.ndarray + X : np.ndarray The input dataframe. Returns: @@ -289,21 +237,21 @@ def _draw_features_and_target_indexes(self, df: np.ndarray) -> Tuple[np.ndarray, Tuple[np.ndarray, int] Indices of selected features and the target. """ - _, p = df.shape + _, p = X.shape nb_features = self.rng.randint(1, p) features_idx = self.rng.choice(range(p), size=nb_features, replace=False) target_idx = self.rng.choice(np.setdiff1d(np.arange(p), features_idx)) return features_idx, target_idx @staticmethod - def _check_draw(df: np.ndarray, features_idx: np.ndarray, target_idx: int) -> np.bool_: + def _check_draw(X: np.ndarray, features_idx: np.ndarray, target_idx: int) -> np.bool_: """ Checks if the drawn features and target are valid. # TODO : Need to develop ? Parameters: ----------- - df : np.ndarray + X : np.ndarray The input dataframe. features_idx : np.ndarray Indices of the selected features. @@ -315,18 +263,18 @@ def _check_draw(df: np.ndarray, features_idx: np.ndarray, target_idx: int) -> np bool True if the draw is valid, False otherwise. """ - target_values = df[~np.isnan(df[:, features_idx]).any(axis=1)][:, target_idx] + target_values = X[~np.isnan(X[:, features_idx]).any(axis=1)][:, target_idx] is_nan = np.isnan(target_values).any() is_distinct_values = (~np.isnan(target_values)).any() return is_nan and is_distinct_values - def _draw_projection(self, df: np.ndarray) -> Tuple[np.ndarray, int]: + def _draw_projection(self, X: np.ndarray) -> Tuple[np.ndarray, int]: """ Draws a valid projection of features and a target. Parameters: ----------- - df : np.ndarray + X : np.ndarray The input dataframe. Returns: @@ -336,13 +284,13 @@ def _draw_projection(self, df: np.ndarray) -> Tuple[np.ndarray, int]: """ is_checked = False while not is_checked: - features_idx, target_idx = self._draw_features_and_target_indexes(df) - is_checked = self._check_draw(df, features_idx, target_idx) + features_idx, target_idx = self._draw_features_and_target_indexes(X) + is_checked = self._check_draw(X, features_idx, target_idx) return features_idx, target_idx @staticmethod def _build_dataset( - df: np.ndarray, + X: np.ndarray, features_idx: np.ndarray, target_idx: int ) -> Tuple[np.ndarray, np.ndarray]: @@ -352,7 +300,7 @@ def _build_dataset( Parameters: ----------- - df: np.ndarray + X: np.ndarray Input data array. features_idx: np.ndarray Indices of the feature columns. @@ -365,17 +313,17 @@ def _build_dataset( - X (np.ndarray): Array of selected features. - y (np.ndarray): Binary array indicating presence of NaN (1) in the target column. """ - X = df[~np.isnan(df[:, features_idx]).any(axis=1)][:, features_idx] + X_features = X[~np.isnan(X[:, features_idx]).any(axis=1)][:, features_idx] y = np.where( - np.isnan(df[~np.isnan(df[:, features_idx]).any(axis=1)][:, target_idx]), + np.isnan(X[~np.isnan(X[:, features_idx]).any(axis=1)][:, target_idx]), 1, 0, ) - return X, y + return X_features, y @staticmethod def _build_label( - df: np.ndarray, + X: np.ndarray, perm: np.ndarray, features_idx: np.ndarray, target_idx: int @@ -386,7 +334,7 @@ def _build_label( Parameters: ----------- - df: np.ndarray + X: np.ndarray Input data array. perm: np.ndarray Permutation array from which labels are selected. @@ -399,7 +347,7 @@ def _build_label( -------- np.ndarray: Binary array indicating presence of NaN (1) in the target column. """ - return perm[~np.isnan(df[:, features_idx]).any(axis=1), target_idx] + return perm[~np.isnan(X[:, features_idx]).any(axis=1), target_idx] def _get_oob_probabilities(self, X: np.ndarray, y: np.ndarray) -> np.ndarray: """ @@ -482,46 +430,47 @@ def _U_hat(oob_probabilities: np.ndarray, labels: np.ndarray) -> float: def _parallel_process_permutation( self, - df: np.ndarray, + X: np.ndarray, M_perm: np.ndarray, features_idx: np.ndarray, target_idx: int, oob_probabilities: np.ndarray, ) -> float: - y = self._build_label(df, M_perm, features_idx, target_idx) + y = self._build_label(X, M_perm, features_idx, target_idx) return self._U_hat(oob_probabilities, y) def _parallel_process_permutation_exact( self, - df: np.ndarray, + X: np.ndarray, M_perm: np.ndarray, features_idx: np.ndarray, target_idx: int, oob_probabilites_unused: np.ndarray, ) -> float: - X, _ = self._build_dataset(df, features_idx, target_idx) - y = self._build_label(df, M_perm, features_idx, target_idx) - oob_probabilities = self._get_oob_probabilities(X, y) + X_features, _ = self._build_dataset(X, features_idx, target_idx) + y = self._build_label(X, M_perm, features_idx, target_idx) + oob_probabilities = self._get_oob_probabilities(X_features, y) return self._U_hat(oob_probabilities, y) def _parallel_process_projection( self, - df: np.ndarray, + X: np.ndarray, list_permutations: List[np.ndarray], features_idx: np.ndarray, target_idx: int, ) -> Tuple[float, List[float]]: - X, y = self._build_dataset(df, features_idx, target_idx) - oob_probabilities = self._get_oob_probabilities(X, y) + X_features, y = self._build_dataset(X, features_idx, target_idx) + oob_probabilities = self._get_oob_probabilities(X_features, y) u_hat = self._U_hat(oob_probabilities, y) result_u_permutations = Parallel(n_jobs=-1)( delayed(self.process_permutation)( - df, M_perm, features_idx, target_idx, oob_probabilities + X, M_perm, features_idx, target_idx, oob_probabilities ) for M_perm in list_permutations ) return u_hat, result_u_permutations + @staticmethod def _build_B(list_proj: List, n_cols: int) -> np.ndarray: """ @@ -585,13 +534,13 @@ def _compute_partial_p_value( return p_v_k / (self.nb_permutation + 1) - def test(self, df: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, List[float]]]: + def test(self, X: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, List[float]]]: """ Apply the PKLM test over a real dataset. Parameters ---------- - df : np.ndarray + X : np.ndarray The input dataset with missing values. Returns @@ -602,18 +551,17 @@ def test(self, df: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, If compute_partial_p_values=True. Returns the p-value of the test and the list of all the partial p-values. """ - df = self._pklm_preprocessing(df) - self._check_nb_patterns(df) + X = self._pklm_preprocessing(X) - M = np.isnan(df).astype(int) - list_proj = [self._draw_projection(df) for _ in range(self.nb_projections)] + M = np.isnan(X).astype(int) + list_proj = [self._draw_projection(X) for _ in range(self.nb_projections)] list_perm = [self.rng.permutation(M) for _ in range(self.nb_permutation)] U = 0.0 list_U_sigma = [0.0 for _ in range(self.nb_permutation)] parallel_results = Parallel(n_jobs=-1)( delayed(self._parallel_process_projection)( - df, list_perm, features_idx, target_idx + X, list_perm, features_idx, target_idx ) for features_idx, target_idx in list_proj ) @@ -636,7 +584,7 @@ def test(self, df: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, if not self.compute_partial_p_values: return p_value else: - _, n_cols = df.shape + _, n_cols = X.shape B = self._build_B(list_proj, n_cols) U = np.array([item[0] for item in parallel_results]) U_sigma = np.array([item[1] for item in parallel_results]) diff --git a/qolmat/utils/exceptions.py b/qolmat/utils/exceptions.py index 6f1abb08..b03375db 100644 --- a/qolmat/utils/exceptions.py +++ b/qolmat/utils/exceptions.py @@ -70,11 +70,3 @@ def __init__(self, min_sv: float, min_std: float): class TypeNotHandled(Exception): def __init__(self, col: str, type_col: str): super().__init__(f"The column `{col}` is of type `{type_col}`, which is not handled!") - - -class TooManyMissingPatterns(Exception): - def __init__(self): - super().__init__( - "The input dataframe or matrix contains too many missing patterns." - "The number of distinct missing patterns must be less than the number of rows." - ) diff --git a/qolmat/utils/input_check.py b/qolmat/utils/input_check.py new file mode 100644 index 00000000..1aeec922 --- /dev/null +++ b/qolmat/utils/input_check.py @@ -0,0 +1,29 @@ +import pandas as pd + +from qolmat.utils.exceptions import TypeNotHandled + +def check_pd_df_dtypes(df: pd.DataFrame, allowed_types: list): + """ + Validates that the columns of the DataFrame have allowed data types. + + Parameters: + ----------- + df : pd.DataFrame + DataFrame whose columns' data types are to be checked. + + Raises: + ------- + TypeNotHandled + If any column has a data type that is not numeric, string, or boolean. + """ + def is_allowed_type(dtype): + return any(check(dtype) for check in allowed_types) + + invalid_columns = [ + (col, dtype) + for col, dtype in df.dtypes.items() + if not is_allowed_type(dtype) + ] + if invalid_columns: + for column_name, dtype in invalid_columns: + raise TypeNotHandled(col=str(column_name), type_col=dtype) diff --git a/tests/analysis/test_holes_characterization.py b/tests/analysis/test_holes_characterization.py index 6b320b30..44f70118 100644 --- a/tests/analysis/test_holes_characterization.py +++ b/tests/analysis/test_holes_characterization.py @@ -2,7 +2,6 @@ import pandas as pd import pytest from scipy.stats import norm -from sympy import li from qolmat.analysis.holes_characterization import LittleTest, PKLMTest from qolmat.benchmark.missing_patterns import UniformHoleGenerator @@ -69,17 +68,6 @@ def test_attribute_error(): ### Tests for the PKLMTest class -@pytest.fixture -def multitypes_dataframe() -> pd.DataFrame: - return pd.DataFrame({ - 'int_col': [1, 2, 3], - 'float_col': [1.1, 2.2, 3.3], - 'str_col': ['a', 'b', 'c'], - 'bool_col': [True, False, True], - 'datetime_col': pd.to_datetime(['2021-01-01', '2021-01-02', '2021-01-03']) - }) - - @pytest.fixture def supported_multitypes_dataframe() -> pd.DataFrame: return pd.DataFrame({ @@ -116,18 +104,6 @@ def missingness_matrix_mcar_perm(missingness_matrix_mcar): def oob_probabilities() -> np.ndarray: return np.matrix([[0.5, 0.5], [0, 1], [1, 0], [1, 0]]).A - -def test__check_pd_df_dtypes_raise_error(multitypes_dataframe): - with pytest.raises(TypeNotHandled): - mcar_test_pklm = PKLMTest(random_state=42) - mcar_test_pklm._check_pd_df_dtypes(multitypes_dataframe) - - -def test__check_pd_df_dtypes(supported_multitypes_dataframe): - mcar_test_pklm = PKLMTest(random_state=42) - mcar_test_pklm._check_pd_df_dtypes(supported_multitypes_dataframe) - - def test__encode_dataframe(supported_multitypes_dataframe): mcar_test_pklm = PKLMTest(random_state=42) np_dataframe = mcar_test_pklm._encode_dataframe(supported_multitypes_dataframe) diff --git a/tests/utils/test_input_check.py b/tests/utils/test_input_check.py new file mode 100644 index 00000000..25c903c7 --- /dev/null +++ b/tests/utils/test_input_check.py @@ -0,0 +1,48 @@ +import pandas as pd +import pytest + +from qolmat.utils.exceptions import TypeNotHandled +from qolmat.utils.input_check import check_pd_df_dtypes + + +@pytest.fixture +def multitypes_dataframe() -> pd.DataFrame: + return pd.DataFrame({ + 'int_col': [1, 2, 3], + 'float_col': [1.1, 2.2, 3.3], + 'str_col': ['a', 'b', 'c'], + 'bool_col': [True, False, True], + 'datetime_col': pd.to_datetime(['2021-01-01', '2021-01-02', '2021-01-03']) + }) + + +@pytest.fixture +def supported_multitypes_dataframe() -> pd.DataFrame: + return pd.DataFrame({ + 'int_col': [1, 2, 3], + 'float_col': [1.1, 2.2, 3.3], + 'str_col': ['a', 'b', 'c'], + 'bool_col': [True, False, True] + }) + + +def test__check_pd_df_dtypes_raise_error(multitypes_dataframe): + with pytest.raises(TypeNotHandled): + check_pd_df_dtypes( + multitypes_dataframe, [ + pd.api.types.is_numeric_dtype, + pd.api.types.is_string_dtype, + pd.api.types.is_bool_dtype + ] + ) + + +def test__check_pd_df_dtypes(supported_multitypes_dataframe): + check_pd_df_dtypes( + supported_multitypes_dataframe, + [ + pd.api.types.is_numeric_dtype, + pd.api.types.is_string_dtype, + pd.api.types.is_bool_dtype + ] + ) From 574f3dbf3a1522ceb016716431b8cdd2fba317a6 Mon Sep 17 00:00:00 2001 From: Adrien Couratier Date: Fri, 30 Aug 2024 17:41:17 +0200 Subject: [PATCH 12/39] :sparkles: When it's possible, take all the projections for small datasets. --- qolmat/analysis/holes_characterization.py | 65 +++++++++++++++++-- tests/analysis/test_holes_characterization.py | 21 +++++- 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index eed0684c..46e1617c 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from itertools import combinations from typing import List, Optional, Tuple, Union from category_encoders.one_hot import OneHotEncoder @@ -147,6 +148,7 @@ class PKLMTest(McarTest): def __init__( self, nb_projections: int = 100, + nb_projections_threshold: int = 200, nb_permutation: int = 30, nb_trees_per_proj: int = 200, compute_partial_p_values: bool = False, @@ -156,6 +158,7 @@ def __init__( ): super().__init__(random_state=random_state) self.nb_projections = nb_projections + self.nb_projections_threshold = nb_projections_threshold self.nb_permutation = nb_permutation self.nb_trees_per_proj = nb_trees_per_proj self.compute_partial_p_values = compute_partial_p_values @@ -223,7 +226,24 @@ def _pklm_preprocessing(self, X: Union[pd.DataFrame, np.ndarray]) -> np.ndarray: ) return self._encode_dataframe(X) - def _draw_features_and_target_indexes(self, X: np.ndarray) -> Tuple[np.ndarray, int]: + @staticmethod + def _get_max_draw(p: int) -> int: + """ + Calculates the number of possible projections. + + Parameters: + ----------- + p : int + The number of columns of the input matrix. + + Returns: + -------- + int + The number of possible projections. + """ + return p*(2**(p-1) - 1) + + def _draw_features_and_target_indexes(self, X: np.ndarray) -> Tuple[List[int], int]: """ Randomly selects features and a target from the dataframe. @@ -241,10 +261,10 @@ def _draw_features_and_target_indexes(self, X: np.ndarray) -> Tuple[np.ndarray, nb_features = self.rng.randint(1, p) features_idx = self.rng.choice(range(p), size=nb_features, replace=False) target_idx = self.rng.choice(np.setdiff1d(np.arange(p), features_idx)) - return features_idx, target_idx + return features_idx.tolist(), target_idx @staticmethod - def _check_draw(X: np.ndarray, features_idx: np.ndarray, target_idx: int) -> np.bool_: + def _check_draw(X: np.ndarray, features_idx: List[int], target_idx: int) -> np.bool_: """ Checks if the drawn features and target are valid. # TODO : Need to develop ? @@ -268,7 +288,36 @@ def _check_draw(X: np.ndarray, features_idx: np.ndarray, target_idx: int) -> np. is_distinct_values = (~np.isnan(target_values)).any() return is_nan and is_distinct_values - def _draw_projection(self, X: np.ndarray) -> Tuple[np.ndarray, int]: + def _generate_label_feature_combinations(self, X: np.ndarray) -> List[Tuple[int, List[int]]]: + """ + Generates all valid combinations of features and labels for projection. + + Parameters: + ----------- + X : np.ndarray + The input data array. + + Returns: + -------- + List[Tuple[int, List[int]]] + A list of tuples where each tuple contains a label and a list of selected features that + can be used for projection. + """ + _, p = X.shape + indices = list(range(p)) + result = [] + + for label in indices: + feature_candidates = [i for i in indices if i != label] + + for r in range(1, len(feature_candidates) + 1): + for feature_set in combinations(feature_candidates, r): + if self._check_draw(X, list(feature_set), label): + result.append((list(feature_set), label)) + + return result + + def _draw_projection(self, X: np.ndarray) -> Tuple[List[int], int]: """ Draws a valid projection of features and a target. @@ -552,9 +601,14 @@ def test(self, X: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, the partial p-values. """ X = self._pklm_preprocessing(X) + _, n_cols = X.shape + + if self._get_max_draw(n_cols) <= self.nb_projections_threshold: + list_proj = self._generate_label_feature_combinations(X) + else: + list_proj = [self._draw_projection(X) for _ in range(self.nb_projections)] M = np.isnan(X).astype(int) - list_proj = [self._draw_projection(X) for _ in range(self.nb_projections)] list_perm = [self.rng.permutation(M) for _ in range(self.nb_permutation)] U = 0.0 list_U_sigma = [0.0 for _ in range(self.nb_permutation)] @@ -584,7 +638,6 @@ def test(self, X: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, if not self.compute_partial_p_values: return p_value else: - _, n_cols = X.shape B = self._build_B(list_proj, n_cols) U = np.array([item[0] for item in parallel_results]) U_sigma = np.array([item[1] for item in parallel_results]) diff --git a/tests/analysis/test_holes_characterization.py b/tests/analysis/test_holes_characterization.py index 44f70118..6c9b63f6 100644 --- a/tests/analysis/test_holes_characterization.py +++ b/tests/analysis/test_holes_characterization.py @@ -117,7 +117,7 @@ def test__draw_features_and_target_indexes(np_matrix_with_nan_mcar): _, p = np_matrix_with_nan_mcar.shape features_idx, target_idx = mcar_test_pklm._draw_features_and_target_indexes(np_matrix_with_nan_mcar) assert isinstance(target_idx, np.integer) - assert isinstance(features_idx, np.ndarray) + assert isinstance(features_idx, list) assert target_idx not in features_idx assert 0 <= target_idx <= (p-1) for feature_index in features_idx: @@ -137,6 +137,25 @@ def test__check_draw(request, dataframe_fixture, features_idx, target_idx, expec assert result == expected +@pytest.mark.parametrize("matrix_fixture", [("np_matrix_with_nan_mcar")]) +def test__generate_label_feature_combinations(request, matrix_fixture): + X = request.getfixturevalue(matrix_fixture) + _, n_cols = X.shape + mcar_test_pklm = PKLMTest() + result = mcar_test_pklm._generate_label_feature_combinations(X) + # Check that number of projections is smaller than possible + assert len(result) <= mcar_test_pklm._get_max_draw(n_cols) + # Check there are no duplicates + assert len(set([x for x in result if result.count(x) > 1])) == 0 + for features, label in result: + assert isinstance(label, int) + assert isinstance(features, list) + assert label not in features + for feature_index in features: + assert 0 <= feature_index <= (n_cols-1) + + + @pytest.mark.parametrize("dataframe_fixture, features_idx, target_idx", [ ("np_matrix_with_nan_mcar", np.array([1, 0]), 2), From 8ddc3dd1e504adcd346535d59c430564015bfdcf Mon Sep 17 00:00:00 2001 From: Adrien Couratier Date: Tue, 3 Sep 2024 10:58:09 +0200 Subject: [PATCH 13/39] :bug: Patch the 'Category encoder bug'. --- qolmat/analysis/holes_characterization.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index 46e1617c..40748259 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -185,6 +185,9 @@ def _encode_dataframe(self, df: pd.DataFrame) -> np.ndarray: The encoded DataFrame as a numpy ndarray, with numeric data concatenated with one-hot encoded categorical and boolean data. """ + if not df.select_dtypes(include=["object", "bool"]).columns.to_list(): + return df.to_numpy() + if not self.encoder: self.encoder = OneHotEncoder( cols=df.select_dtypes(include=["object", "bool"]).columns, From 5b988ec92331a5977434608cae92d51512f869bf Mon Sep 17 00:00:00 2001 From: Adrien Couratier Date: Tue, 3 Sep 2024 16:23:11 +0200 Subject: [PATCH 14/39] Typo in docstring --- qolmat/analysis/holes_characterization.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index 40748259..d7a39ce5 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -214,7 +214,8 @@ def _pklm_preprocessing(self, X: Union[pd.DataFrame, np.ndarray]) -> np.ndarray: Raises: ------- TypeNotHandled - If the DataFrame contains columns with data types that are not numeric, string, or boolean. + If the DataFrame contains columns with data types that are not numeric, string, or + boolean. """ if isinstance(X, np.ndarray): return X From ab610602304bd369699b0ef77206afdd64276485 Mon Sep 17 00:00:00 2001 From: Adrien Couratier Date: Fri, 6 Sep 2024 17:13:46 +0200 Subject: [PATCH 15/39] Detail some dostrings and fix typo in the tuto --- examples/tutorials/plot_tuto_mcar.py | 2 +- qolmat/analysis/holes_characterization.py | 88 +++++++++++++++++------ 2 files changed, 68 insertions(+), 22 deletions(-) diff --git a/examples/tutorials/plot_tuto_mcar.py b/examples/tutorials/plot_tuto_mcar.py index 1dc6e3b5..cbd2d2fd 100644 --- a/examples/tutorials/plot_tuto_mcar.py +++ b/examples/tutorials/plot_tuto_mcar.py @@ -53,7 +53,7 @@ # We choose to use the classic threshold of 5%. If the test p-value is below this threshold, # we reject the null hypothesis. # This notebook shows how the Little and PKLM tests perform on a simplistic case and their -# limitations. We instanciate a test object with a random state for reproducibility. +# limitations. We instantiate a test object with a random state for reproducibility. little_test_mcar = LittleTest(random_state=rng) pklm_test_mcar = PKLMTest(random_state=rng) diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index d7a39ce5..7e585ee4 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -2,13 +2,13 @@ from itertools import combinations from typing import List, Optional, Tuple, Union -from category_encoders.one_hot import OneHotEncoder -from joblib import Parallel, delayed import numpy as np import pandas as pd -from sklearn.ensemble import RandomForestClassifier -from sklearn import utils as sku +from category_encoders.one_hot import OneHotEncoder +from joblib import Parallel, delayed from scipy.stats import chi2 +from sklearn import utils as sku +from sklearn.ensemble import RandomForestClassifier from qolmat.imputations.imputers import ImputerEM from qolmat.utils.input_check import check_pd_df_dtypes @@ -16,20 +16,46 @@ class McarTest(ABC): """ - Astract class for MCAR tests. + Abstract base class for performing MCAR (Missing Completely At Random) tests. Parameters ---------- - random_state : int, optional - The seed of the pseudo random number generator to use, for reproductibility. + random_state : int or np.random.RandomState, optional + Seed or random state for reproducibility. + + Methods + ------- + test(df) + Abstract method to perform the MCAR test on the given DataFrame or NumPy array. """ def __init__(self, random_state: Union[None, int, np.random.RandomState] = None): + """ + Initializes the McarTest class with a random state. + + Parameters + ---------- + random_state : int or np.random.RandomState, optional + Seed or random state for reproducibility. + """ self.rng = sku.check_random_state(random_state) @abstractmethod - def test(self, df: pd.DataFrame) -> float: - pass + def test(self, df: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, List[float]]]: + """ + Perform the MCAR test on the input data. + + Parameters + ---------- + df : pd.DataFrame or np.ndarray + Data to be tested for MCAR. Can be provided as a pandas DataFrame or a NumPy array. + + Returns + ------- + float or tuple of float and list of float + Test statistic, or a tuple with the test statistic and additional details if applicable. + """ + raise NotImplemented class LittleTest(McarTest): @@ -67,7 +93,7 @@ def __init__( def test(self, df: pd.DataFrame) -> float: """ - Apply the Little's test over a real dataframe. + Apply the Little's test to a real dataframe. Parameters @@ -99,7 +125,10 @@ def test(self, df: pd.DataFrame) -> float: obs_mean = df_pattern.mean().to_numpy() diff_means = obs_mean - ml_means[list(tup_pattern)] - inv_sigma_pattern = np.linalg.inv(ml_cov[:, tup_pattern][tup_pattern, :]) + inv_sigma_pattern = np.linalg.solve( + ml_cov[:, tup_pattern][tup_pattern, :], + np.eye(len(tup_pattern)) + ) d0 += n_rows_pattern * np.dot( np.dot(diff_means, inv_sigma_pattern), diff_means.T @@ -130,6 +159,9 @@ class PKLMTest(McarTest): ----------- nb_projections : int Number of projections. + nb_projections_threshold : int + If the maximum number of possible permutations is less than this threshold, then all + projections are used. Otherwise, nb_projections random projections are drawn. nb_permutation : int Number of permutations. nb_trees_per_proj : int @@ -167,12 +199,13 @@ def __init__( if self.exact_p_value: self.process_permutation = self._parallel_process_permutation_exact - self.process_permutation = self._parallel_process_permutation + else: + self.process_permutation = self._parallel_process_permutation def _encode_dataframe(self, df: pd.DataFrame) -> np.ndarray: """ Encodes the DataFrame by converting numeric columns to a numpy array - and applying one-hot encoding to non-numeric columns. + and applying one-hot encoding to objects and boolean columns. Parameters: ----------- @@ -250,6 +283,7 @@ def _get_max_draw(p: int) -> int: def _draw_features_and_target_indexes(self, X: np.ndarray) -> Tuple[List[int], int]: """ Randomly selects features and a target from the dataframe. + This corresponds to the Ai and Bi projections of the paper. Parameters: ----------- @@ -263,15 +297,15 @@ def _draw_features_and_target_indexes(self, X: np.ndarray) -> Tuple[List[int], i """ _, p = X.shape nb_features = self.rng.randint(1, p) - features_idx = self.rng.choice(range(p), size=nb_features, replace=False) + features_idx = self.rng.choice(p, size=nb_features, replace=False) target_idx = self.rng.choice(np.setdiff1d(np.arange(p), features_idx)) return features_idx.tolist(), target_idx @staticmethod def _check_draw(X: np.ndarray, features_idx: List[int], target_idx: int) -> np.bool_: """ - Checks if the drawn features and target are valid. - # TODO : Need to develop ? + Checks if the drawn features and target are valid. Here we check that the number of induced + classes is equal to 2. Using the notation from the paper, we want |G(Ai, Bi)| = 2. Parameters: ----------- @@ -294,7 +328,8 @@ def _check_draw(X: np.ndarray, features_idx: List[int], target_idx: int) -> np.b def _generate_label_feature_combinations(self, X: np.ndarray) -> List[Tuple[int, List[int]]]: """ - Generates all valid combinations of features and labels for projection. + Generates all valid combinations of features and labels for projection if + nb_projections_threshold > _get_max_draw(X.shape[1]). Parameters: ----------- @@ -324,6 +359,7 @@ def _generate_label_feature_combinations(self, X: np.ndarray) -> List[Tuple[int, def _draw_projection(self, X: np.ndarray) -> Tuple[List[int], int]: """ Draws a valid projection of features and a target. + If nb_projections_threshold < _get_max_draw(X.shape[1]). Parameters: ----------- @@ -348,8 +384,10 @@ def _build_dataset( target_idx: int ) -> Tuple[np.ndarray, np.ndarray]: """ - Builds a dataset by selecting specified features and target from a NumPy array, - excluding rows with NaN values in the feature columns. + Builds a dataset by selecting specified features and target from a NumPy array, excluding + rows with NaN values in the feature columns. + For the label, we create a binary classification problem where yi =1 if target_idx_i is + missing. Parameters: ----------- @@ -363,7 +401,7 @@ def _build_dataset( Returns: -------- Tuple[np.ndarray, np.ndarray]: A tuple containing: - - X (np.ndarray): Array of selected features. + - X (np.ndarray): Full observed array of selected features. - y (np.ndarray): Binary array indicating presence of NaN (1) in the target column. """ X_features = X[~np.isnan(X[:, features_idx]).any(axis=1)][:, features_idx] @@ -384,6 +422,8 @@ def _build_label( """ Builds a label array by selecting target values from a permutation array, excluding rows with NaN values in the specified feature columns. + For the label, we create a binary classification problem where yi =1 if target_idx_i is + missing. Parameters: ----------- @@ -419,11 +459,11 @@ def _get_oob_probabilities(self, X: np.ndarray, y: np.ndarray) -> np.ndarray: """ clf = RandomForestClassifier( n_estimators=self.nb_trees_per_proj, - # max_features=None, min_samples_split=10, bootstrap=True, oob_score=True, random_state=self.rng, + max_features=1., ) clf.fit(X, y) return clf.oob_decision_function_ @@ -445,6 +485,9 @@ def _U_hat(oob_probabilities: np.ndarray, labels: np.ndarray) -> float: -------- float: The computed U_hat statistic. """ + if oob_probabilities.shape[1] == 1: + return 0. + oob_probabilities = np.clip(oob_probabilities, 1e-9, 1 - 1e-9) unique_labels = np.unique(labels) @@ -502,6 +545,7 @@ def _parallel_process_permutation_exact( ) -> float: X_features, _ = self._build_dataset(X, features_idx, target_idx) y = self._build_label(X, M_perm, features_idx, target_idx) + # In this case, we fit the classifier in each permutation. It takes much more longer. oob_probabilities = self._get_oob_probabilities(X_features, y) return self._U_hat(oob_probabilities, y) @@ -515,6 +559,8 @@ def _parallel_process_projection( X_features, y = self._build_dataset(X, features_idx, target_idx) oob_probabilities = self._get_oob_probabilities(X_features, y) u_hat = self._U_hat(oob_probabilities, y) + # We iterate over the permutation because for a given projection, we fit only one classifier + # to get oob probabilities and compute u_hat nb_permutations times. result_u_permutations = Parallel(n_jobs=-1)( delayed(self.process_permutation)( X, M_perm, features_idx, target_idx, oob_probabilities From dcd24630c087cbdbc85141c4fffe85c0496ae3a3 Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Sat, 30 Aug 2025 21:10:01 +0200 Subject: [PATCH 16/39] =?UTF-8?q?Bump=20version:=200.1.9=20=E2=86=92=200.1?= =?UTF-8?q?.10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- docs/conf.py | 2 +- pyproject.toml | 2 +- qolmat/_version.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 4fb758b4..00f1f563 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.1.9 +current_version = 0.1.10 commit = True tag = True diff --git a/docs/conf.py b/docs/conf.py index 781bdbee..5ec5e33c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -27,7 +27,7 @@ author = "Quantmetry" # The full version, including alpha/beta/rc tags -version = "0.1.9" +version = "0.1.10" release = version # -- General configuration --------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index 7f3d6c37..282a5d5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ [tool.poetry] name = "qolmat" -version = "0.1.9" +version = "0.1.10" description = "A Python library for optimal data imputation." authors = [ "Julien ROUSSEL ", diff --git a/qolmat/_version.py b/qolmat/_version.py index c11f861a..569b1212 100644 --- a/qolmat/_version.py +++ b/qolmat/_version.py @@ -1 +1 @@ -__version__ = "0.1.9" +__version__ = "0.1.10" From c10e9ab339df9651eb2d2e00b4279f6b0d5dee01 Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Tue, 23 Dec 2025 16:17:06 +0100 Subject: [PATCH 17/39] pre-commit applied --- examples/tutorials/plot_tuto_mcar.py | 23 ++--- qolmat/analysis/holes_characterization.py | 67 ++++--------- qolmat/utils/input_check.py | 6 +- tests/analysis/test_holes_characterization.py | 98 ++++++++++--------- tests/utils/test_input_check.py | 43 ++++---- 5 files changed, 104 insertions(+), 133 deletions(-) diff --git a/examples/tutorials/plot_tuto_mcar.py b/examples/tutorials/plot_tuto_mcar.py index cbd2d2fd..901bfbf9 100644 --- a/examples/tutorials/plot_tuto_mcar.py +++ b/examples/tutorials/plot_tuto_mcar.py @@ -182,7 +182,7 @@ # # We also note that the Little's test does not handle categorical data or temporally # correlated data. -# +# # This is why we have implemented the PKLM test, which makes up for the shortcomings of the Little # test. We present this test in more detail in the next section. @@ -277,21 +277,16 @@ col1 = rng.rand(n_rows) * 100 col2 = rng.randint(1, 100, n_rows) col3 = rng.choice([True, False], n_rows) -modalities = ['A', 'B', 'C', 'D'] +modalities = ["A", "B", "C", "D"] col4 = rng.choice(modalities, n_rows) -df = pd.DataFrame({ - 'Numeric1': col1, - 'Numeric2': col2, - 'Boolean': col3, - 'Object': col4 -}) +df = pd.DataFrame({"Numeric1": col1, "Numeric2": col2, "Boolean": col3, "Object": col4}) hole_gen = UniformHoleGenerator( n_splits=1, ratio_masked=0.2, - subset=['Numeric1', 'Numeric2', 'Boolean', 'Object'], - random_state=rng + subset=["Numeric1", "Numeric2", "Boolean", "Object"], + random_state=rng, ) df_mask = hole_gen.generate_mask(df) df_nan = df.where(~df_mask, np.nan) @@ -326,9 +321,7 @@ # %% data = rng.multivariate_normal( - mean=[0, 0, 0, 0], - cov=[[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], - size=400 + mean=[0, 0, 0, 0], cov=[[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], size=400 ) df = pd.DataFrame(data=data, columns=["Column 1", "Column 2", "Column 3", "Column 4"]) @@ -339,7 +332,7 @@ "Column 3": False, "Column 4": False, }, - index=df.index + index=df.index, ) df_nan = df.where(~df_mask, np.nan) @@ -380,4 +373,4 @@ # | 10000 | 6 | 20"54 | # | 10000 | 10 | 14"48 | # | 100000 | 10 | 4'51" | -# | 100000 | 15 | 3'06" | \ No newline at end of file +# | 100000 | 15 | 3'06" | diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index 7e585ee4..ea321d86 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -126,13 +126,10 @@ def test(self, df: pd.DataFrame) -> float: diff_means = obs_mean - ml_means[list(tup_pattern)] inv_sigma_pattern = np.linalg.solve( - ml_cov[:, tup_pattern][tup_pattern, :], - np.eye(len(tup_pattern)) + ml_cov[:, tup_pattern][tup_pattern, :], np.eye(len(tup_pattern)) ) - d0 += n_rows_pattern * np.dot( - np.dot(diff_means, inv_sigma_pattern), diff_means.T - ) + d0 += n_rows_pattern * np.dot(np.dot(diff_means, inv_sigma_pattern), diff_means.T) degree_f += tup_pattern.count(True) return 1 - float(chi2.cdf(d0, degree_f)) @@ -225,7 +222,7 @@ def _encode_dataframe(self, df: pd.DataFrame) -> np.ndarray: self.encoder = OneHotEncoder( cols=df.select_dtypes(include=["object", "bool"]).columns, return_df=False, - handle_missing='return_nan' + handle_missing="return_nan", ) return self.encoder.fit_transform(df) @@ -258,8 +255,8 @@ def _pklm_preprocessing(self, X: Union[pd.DataFrame, np.ndarray]) -> np.ndarray: [ pd.api.types.is_numeric_dtype, pd.api.types.is_string_dtype, - pd.api.types.is_bool_dtype - ] + pd.api.types.is_bool_dtype, + ], ) return self._encode_dataframe(X) @@ -278,7 +275,7 @@ def _get_max_draw(p: int) -> int: int The number of possible projections. """ - return p*(2**(p-1) - 1) + return p * (2 ** (p - 1) - 1) def _draw_features_and_target_indexes(self, X: np.ndarray) -> Tuple[List[int], int]: """ @@ -379,9 +376,7 @@ def _draw_projection(self, X: np.ndarray) -> Tuple[List[int], int]: @staticmethod def _build_dataset( - X: np.ndarray, - features_idx: np.ndarray, - target_idx: int + X: np.ndarray, features_idx: np.ndarray, target_idx: int ) -> Tuple[np.ndarray, np.ndarray]: """ Builds a dataset by selecting specified features and target from a NumPy array, excluding @@ -414,10 +409,7 @@ def _build_dataset( @staticmethod def _build_label( - X: np.ndarray, - perm: np.ndarray, - features_idx: np.ndarray, - target_idx: int + X: np.ndarray, perm: np.ndarray, features_idx: np.ndarray, target_idx: int ) -> np.ndarray: """ Builds a label array by selecting target values from a permutation array, @@ -463,7 +455,7 @@ def _get_oob_probabilities(self, X: np.ndarray, y: np.ndarray) -> np.ndarray: bootstrap=True, oob_score=True, random_state=self.rng, - max_features=1., + max_features=1.0, ) clf.fit(X, y) return clf.oob_decision_function_ @@ -486,7 +478,7 @@ def _U_hat(oob_probabilities: np.ndarray, labels: np.ndarray) -> float: float: The computed U_hat statistic. """ if oob_probabilities.shape[1] == 1: - return 0. + return 0.0 oob_probabilities = np.clip(oob_probabilities, 1e-9, 1 - 1e-9) @@ -503,24 +495,14 @@ def _U_hat(oob_probabilities: np.ndarray, labels: np.ndarray) -> float: if unique_labels.shape[0] == 1: if unique_labels[0] == 0: n0 = labels.shape[0] - return ( - np.log(p0_0 / (1 - p0_0)).sum() / n0 - - np.log(p1_0 / (1 - p1_0)).sum() / n0 - ) + return np.log(p0_0 / (1 - p0_0)).sum() / n0 - np.log(p1_0 / (1 - p1_0)).sum() / n0 else: n1 = labels.shape[0] - return ( - np.log(p1_1 / (1 - p1_1)).sum() / n1 - - np.log(p0_1 / (1 - p0_1)).sum() / n1 - ) + return np.log(p1_1 / (1 - p1_1)).sum() / n1 - np.log(p0_1 / (1 - p0_1)).sum() / n1 n0, n1 = label_matrix.sum(axis=0) - u_0 = ( - np.log(p0_0 / (1 - p0_0)).sum() / n0 - np.log(p0_1 / (1 - p0_1)).sum() / n1 - ) - u_1 = ( - np.log(p1_1 / (1 - p1_1)).sum() / n1 - np.log(p1_0 / (1 - p1_0)).sum() / n0 - ) + u_0 = np.log(p0_0 / (1 - p0_0)).sum() / n0 - np.log(p0_1 / (1 - p0_1)).sum() / n1 + u_1 = np.log(p1_1 / (1 - p1_1)).sum() / n1 - np.log(p1_0 / (1 - p1_0)).sum() / n0 return u_0 + u_1 @@ -569,7 +551,6 @@ def _parallel_process_projection( ) return u_hat, result_u_permutations - @staticmethod def _build_B(list_proj: List, n_cols: int) -> np.ndarray: """ @@ -578,7 +559,7 @@ def _build_B(list_proj: List, n_cols: int) -> np.ndarray: Parameters: ----------- list_proj : List - A list of tuples where each tuple represents a projection, and the + A list of tuples where each tuple represents a projection, and the second element of each tuple is an index used to build the target. n_cols : int The number of columns in the resulting matrix B. @@ -598,12 +579,8 @@ def _build_B(list_proj: List, n_cols: int) -> np.ndarray: return B.transpose() def _compute_partial_p_value( - self, - B: np.ndarray, - U: np.ndarray, - U_sigma: np.ndarray, - k: int - ) -> float: + self, B: np.ndarray, U: np.ndarray, U_sigma: np.ndarray, k: int + ) -> float: """ Computes the partial p-value for a statistical test based on a given permutation. @@ -624,10 +601,10 @@ def _compute_partial_p_value( float The partial p-value. """ - U_k = B[k, :]@U + U_k = B[k, :] @ U p_v_k = 1 - for u_sigma_k in (B[k, :]@U_sigma).tolist(): + for u_sigma_k in (B[k, :] @ U_sigma).tolist(): if u_sigma_k >= U_k: p_v_k += 1 @@ -664,9 +641,7 @@ def test(self, X: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, list_U_sigma = [0.0 for _ in range(self.nb_permutation)] parallel_results = Parallel(n_jobs=-1)( - delayed(self._parallel_process_projection)( - X, list_perm, features_idx, target_idx - ) + delayed(self._parallel_process_projection)(X, list_perm, features_idx, target_idx) for features_idx, target_idx in list_proj ) @@ -684,7 +659,7 @@ def test(self, X: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, p_value += 1 p_value = p_value / (self.nb_permutation + 1) - + if not self.compute_partial_p_values: return p_value else: diff --git a/qolmat/utils/input_check.py b/qolmat/utils/input_check.py index 1aeec922..67abac0b 100644 --- a/qolmat/utils/input_check.py +++ b/qolmat/utils/input_check.py @@ -2,6 +2,7 @@ from qolmat.utils.exceptions import TypeNotHandled + def check_pd_df_dtypes(df: pd.DataFrame, allowed_types: list): """ Validates that the columns of the DataFrame have allowed data types. @@ -16,13 +17,12 @@ def check_pd_df_dtypes(df: pd.DataFrame, allowed_types: list): TypeNotHandled If any column has a data type that is not numeric, string, or boolean. """ + def is_allowed_type(dtype): return any(check(dtype) for check in allowed_types) invalid_columns = [ - (col, dtype) - for col, dtype in df.dtypes.items() - if not is_allowed_type(dtype) + (col, dtype) for col, dtype in df.dtypes.items() if not is_allowed_type(dtype) ] if invalid_columns: for column_name, dtype in invalid_columns: diff --git a/tests/analysis/test_holes_characterization.py b/tests/analysis/test_holes_characterization.py index 6c9b63f6..571b8f29 100644 --- a/tests/analysis/test_holes_characterization.py +++ b/tests/analysis/test_holes_characterization.py @@ -70,12 +70,14 @@ def test_attribute_error(): @pytest.fixture def supported_multitypes_dataframe() -> pd.DataFrame: - return pd.DataFrame({ - 'int_col': [1, 2, 3], - 'float_col': [1.1, 2.2, 3.3], - 'str_col': ['a', 'b', 'c'], - 'bool_col': [True, False, True] - }) + return pd.DataFrame( + { + "int_col": [1, 2, 3], + "float_col": [1.1, 2.2, 3.3], + "str_col": ["a", "b", "c"], + "bool_col": [True, False, True], + } + ) @pytest.fixture @@ -104,6 +106,7 @@ def missingness_matrix_mcar_perm(missingness_matrix_mcar): def oob_probabilities() -> np.ndarray: return np.matrix([[0.5, 0.5], [0, 1], [1, 0], [1, 0]]).A + def test__encode_dataframe(supported_multitypes_dataframe): mcar_test_pklm = PKLMTest(random_state=42) np_dataframe = mcar_test_pklm._encode_dataframe(supported_multitypes_dataframe) @@ -115,20 +118,23 @@ def test__encode_dataframe(supported_multitypes_dataframe): def test__draw_features_and_target_indexes(np_matrix_with_nan_mcar): mcar_test_pklm = PKLMTest(random_state=42) _, p = np_matrix_with_nan_mcar.shape - features_idx, target_idx = mcar_test_pklm._draw_features_and_target_indexes(np_matrix_with_nan_mcar) + features_idx, target_idx = mcar_test_pklm._draw_features_and_target_indexes( + np_matrix_with_nan_mcar + ) assert isinstance(target_idx, np.integer) assert isinstance(features_idx, list) assert target_idx not in features_idx - assert 0 <= target_idx <= (p-1) + assert 0 <= target_idx <= (p - 1) for feature_index in features_idx: - assert 0 <= feature_index <= (p-1) + assert 0 <= feature_index <= (p - 1) -@pytest.mark.parametrize("dataframe_fixture, features_idx, target_idx, expected", +@pytest.mark.parametrize( + "dataframe_fixture, features_idx, target_idx, expected", [ ("np_matrix_with_nan_mcar", np.array([1, 0]), 2, True), - ("np_matrix_with_nan_mcar", np.array([1, 0, 2]), 3, False) - ] + ("np_matrix_with_nan_mcar", np.array([1, 0, 2]), 3, False), + ], ) def test__check_draw(request, dataframe_fixture, features_idx, target_idx, expected): dataframe = request.getfixturevalue(dataframe_fixture) @@ -152,14 +158,14 @@ def test__generate_label_feature_combinations(request, matrix_fixture): assert isinstance(features, list) assert label not in features for feature_index in features: - assert 0 <= feature_index <= (n_cols-1) - + assert 0 <= feature_index <= (n_cols - 1) -@pytest.mark.parametrize("dataframe_fixture, features_idx, target_idx", +@pytest.mark.parametrize( + "dataframe_fixture, features_idx, target_idx", [ ("np_matrix_with_nan_mcar", np.array([1, 0]), 2), - ] + ], ) def test__build_dataset(request, dataframe_fixture, features_idx, target_idx): dataframe = request.getfixturevalue(dataframe_fixture) @@ -173,18 +179,13 @@ def test__build_dataset(request, dataframe_fixture, features_idx, target_idx): assert len(y.shape) == 1 -@pytest.mark.parametrize("dataframe_fixture, permutation_fixture, features_idx, target_idx", +@pytest.mark.parametrize( + "dataframe_fixture, permutation_fixture, features_idx, target_idx", [ ("np_matrix_with_nan_mcar", "missingness_matrix_mcar_perm", np.array([1, 0]), 2), - ] + ], ) -def test__build_label( - request, - dataframe_fixture, - permutation_fixture, - features_idx, - target_idx -): +def test__build_label(request, dataframe_fixture, permutation_fixture, features_idx, target_idx): dataframe = request.getfixturevalue(dataframe_fixture) m_perm = request.getfixturevalue(permutation_fixture) mcar_test_pklm = PKLMTest() @@ -195,11 +196,11 @@ def test__build_label( @pytest.mark.parametrize( - "oob_fixture, label", - [ - ("oob_probabilities", np.array([1, 1, 1, 1])), - ("oob_probabilities", np.array([0, 0, 0, 0])), - ] + "oob_fixture, label", + [ + ("oob_probabilities", np.array([1, 1, 1, 1])), + ("oob_probabilities", np.array([0, 0, 0, 0])), + ], ) def test__U_hat_unique_label(request, oob_fixture, label): oob_prob = request.getfixturevalue(oob_fixture) @@ -208,10 +209,10 @@ def test__U_hat_unique_label(request, oob_fixture, label): @pytest.mark.parametrize( - "oob_fixture, label, expected", - [ - ("oob_probabilities", np.array([1, 0, 0, 0]), 2/3*(np.log(1 - 1e-9) - np.log(1e-9))), - ] + "oob_fixture, label, expected", + [ + ("oob_probabilities", np.array([1, 0, 0, 0]), 2 / 3 * (np.log(1 - 1e-9) - np.log(1e-9))), + ], ) def test__U_hat_computation(request, oob_fixture, label, expected): oob_prob = request.getfixturevalue(oob_fixture) @@ -219,21 +220,22 @@ def test__U_hat_computation(request, oob_fixture, label, expected): u_hat = mcar_test_pklm._U_hat(oob_prob, label) assert round(u_hat, 2) == round(expected, 2) + @pytest.mark.parametrize( - "list_proj, n_cols", - [ - ( - [ - (np.array([3, 1]), 0), - (np.array([0]), 1), - (np.array([3]), 0), - (np.array([1, 2]), 3), - (np.array([3, 0]), 2), - (np.array([0, 1]), 2) - ], - 4 - ) - ] + "list_proj, n_cols", + [ + ( + [ + (np.array([3, 1]), 0), + (np.array([0]), 1), + (np.array([3]), 0), + (np.array([1, 2]), 3), + (np.array([3, 0]), 2), + (np.array([0, 1]), 2), + ], + 4, + ) + ], ) def test__build_B(list_proj, n_cols): mcar_test_pklm = PKLMTest() diff --git a/tests/utils/test_input_check.py b/tests/utils/test_input_check.py index 25c903c7..ba938c98 100644 --- a/tests/utils/test_input_check.py +++ b/tests/utils/test_input_check.py @@ -7,42 +7,43 @@ @pytest.fixture def multitypes_dataframe() -> pd.DataFrame: - return pd.DataFrame({ - 'int_col': [1, 2, 3], - 'float_col': [1.1, 2.2, 3.3], - 'str_col': ['a', 'b', 'c'], - 'bool_col': [True, False, True], - 'datetime_col': pd.to_datetime(['2021-01-01', '2021-01-02', '2021-01-03']) - }) + return pd.DataFrame( + { + "int_col": [1, 2, 3], + "float_col": [1.1, 2.2, 3.3], + "str_col": ["a", "b", "c"], + "bool_col": [True, False, True], + "datetime_col": pd.to_datetime(["2021-01-01", "2021-01-02", "2021-01-03"]), + } + ) @pytest.fixture def supported_multitypes_dataframe() -> pd.DataFrame: - return pd.DataFrame({ - 'int_col': [1, 2, 3], - 'float_col': [1.1, 2.2, 3.3], - 'str_col': ['a', 'b', 'c'], - 'bool_col': [True, False, True] - }) + return pd.DataFrame( + { + "int_col": [1, 2, 3], + "float_col": [1.1, 2.2, 3.3], + "str_col": ["a", "b", "c"], + "bool_col": [True, False, True], + } + ) def test__check_pd_df_dtypes_raise_error(multitypes_dataframe): with pytest.raises(TypeNotHandled): check_pd_df_dtypes( - multitypes_dataframe, [ + multitypes_dataframe, + [ pd.api.types.is_numeric_dtype, pd.api.types.is_string_dtype, - pd.api.types.is_bool_dtype - ] + pd.api.types.is_bool_dtype, + ], ) def test__check_pd_df_dtypes(supported_multitypes_dataframe): check_pd_df_dtypes( supported_multitypes_dataframe, - [ - pd.api.types.is_numeric_dtype, - pd.api.types.is_string_dtype, - pd.api.types.is_bool_dtype - ] + [pd.api.types.is_numeric_dtype, pd.api.types.is_string_dtype, pd.api.types.is_bool_dtype], ) From c811523a6e603aa4bd7a9d0b2d2f5de2047a76b9 Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Tue, 23 Dec 2025 16:30:54 +0100 Subject: [PATCH 18/39] patching --- examples/tutorials/plot_tuto_mcar.py | 8 ++-- qolmat/analysis/holes_characterization.py | 48 +++++++++++------------ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/examples/tutorials/plot_tuto_mcar.py b/examples/tutorials/plot_tuto_mcar.py index 901bfbf9..1c561fbc 100644 --- a/examples/tutorials/plot_tuto_mcar.py +++ b/examples/tutorials/plot_tuto_mcar.py @@ -242,8 +242,8 @@ # ``nb_projections=100``. # Est-ce qu'on donne des ordres de grandeurs utiles ? J'avais un peu fait ce travail. # -# * ``nb_permutation`` : Number of permutations of the projected targets. The higher is better. This -# parameter has little impact on calculation time. +# * ``nb_permutation`` : Number of permutations of the projected targets. The higher is better. +# This parameter has little impact on calculation time. # Its default value ``nb_permutation=30``. # # * ``nb_trees_per_proj`` : The number of subtrees in each random forest fitted. In order to @@ -268,8 +268,8 @@ # ================================================ # # As we have seen, Little's test only applies to quantitative data. In real life, however, it is -# common to have to deal with mixed data. Here's an example of how to use the PKLM test on a dataset -# with mixed data types. +# common to have to deal with mixed data. Here's an example of how to use the PKLM test on a +# dataset with mixed data types. # %% n_rows = 100 diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index ea321d86..83dc6f38 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod from itertools import combinations -from typing import List, Optional, Tuple, Union +from typing import Optional, Union import numpy as np import pandas as pd @@ -41,7 +41,7 @@ def __init__(self, random_state: Union[None, int, np.random.RandomState] = None) self.rng = sku.check_random_state(random_state) @abstractmethod - def test(self, df: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, List[float]]]: + def test(self, df: Union[pd.DataFrame, np.ndarray]) -> Union[float, tuple[float, list[float]]]: """ Perform the MCAR test on the input data. @@ -53,14 +53,15 @@ def test(self, df: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, Returns ------- float or tuple of float and list of float - Test statistic, or a tuple with the test statistic and additional details if applicable. + Test statistic, or a tuple with the test statistic and additional details if + applicable. """ - raise NotImplemented + raise NotImplementedError("Subclasses must implement this method.") class LittleTest(McarTest): """ - This class implements the Little's test, which is designed to detect the heterogeneity accross + This class implements the Little's test, which is designed to detect the heterogeneity across the missing patterns. The null hypothesis is "The missing data mechanism is MCAR". The shortcoming of this test is that it won't detect the heterogeneity of covariance. @@ -195,7 +196,7 @@ def __init__( self.encoder = encoder if self.exact_p_value: - self.process_permutation = self._parallel_process_permutation_exact + self.process_permutation = self._parallel_process_permutation_exact # ignore F821 else: self.process_permutation = self._parallel_process_permutation @@ -277,7 +278,7 @@ def _get_max_draw(p: int) -> int: """ return p * (2 ** (p - 1) - 1) - def _draw_features_and_target_indexes(self, X: np.ndarray) -> Tuple[List[int], int]: + def _draw_features_and_target_indexes(self, X: np.ndarray) -> tuple[list[int], int]: """ Randomly selects features and a target from the dataframe. This corresponds to the Ai and Bi projections of the paper. @@ -289,7 +290,7 @@ def _draw_features_and_target_indexes(self, X: np.ndarray) -> Tuple[List[int], i Returns: -------- - Tuple[np.ndarray, int] + tuple[np.ndarray, int] Indices of selected features and the target. """ _, p = X.shape @@ -299,7 +300,7 @@ def _draw_features_and_target_indexes(self, X: np.ndarray) -> Tuple[List[int], i return features_idx.tolist(), target_idx @staticmethod - def _check_draw(X: np.ndarray, features_idx: List[int], target_idx: int) -> np.bool_: + def _check_draw(X: np.ndarray, features_idx: list[int], target_idx: int) -> np.bool_: """ Checks if the drawn features and target are valid. Here we check that the number of induced classes is equal to 2. Using the notation from the paper, we want |G(Ai, Bi)| = 2. @@ -323,7 +324,7 @@ def _check_draw(X: np.ndarray, features_idx: List[int], target_idx: int) -> np.b is_distinct_values = (~np.isnan(target_values)).any() return is_nan and is_distinct_values - def _generate_label_feature_combinations(self, X: np.ndarray) -> List[Tuple[int, List[int]]]: + def _generate_label_feature_combinations(self, X: np.ndarray) -> list[tuple[int, list[int]]]: """ Generates all valid combinations of features and labels for projection if nb_projections_threshold > _get_max_draw(X.shape[1]). @@ -335,7 +336,7 @@ def _generate_label_feature_combinations(self, X: np.ndarray) -> List[Tuple[int, Returns: -------- - List[Tuple[int, List[int]]] + list[tuple[int, list[int]]] A list of tuples where each tuple contains a label and a list of selected features that can be used for projection. """ @@ -353,7 +354,7 @@ def _generate_label_feature_combinations(self, X: np.ndarray) -> List[Tuple[int, return result - def _draw_projection(self, X: np.ndarray) -> Tuple[List[int], int]: + def _draw_projection(self, X: np.ndarray) -> tuple[list[int], int]: """ Draws a valid projection of features and a target. If nb_projections_threshold < _get_max_draw(X.shape[1]). @@ -365,7 +366,7 @@ def _draw_projection(self, X: np.ndarray) -> Tuple[List[int], int]: Returns: -------- - Tuple[np.ndarray, int] + tuple[np.ndarray, int] Indices of selected features and the target. """ is_checked = False @@ -377,7 +378,7 @@ def _draw_projection(self, X: np.ndarray) -> Tuple[List[int], int]: @staticmethod def _build_dataset( X: np.ndarray, features_idx: np.ndarray, target_idx: int - ) -> Tuple[np.ndarray, np.ndarray]: + ) -> tuple[np.ndarray, np.ndarray]: """ Builds a dataset by selecting specified features and target from a NumPy array, excluding rows with NaN values in the feature columns. @@ -395,7 +396,7 @@ def _build_dataset( Returns: -------- - Tuple[np.ndarray, np.ndarray]: A tuple containing: + tuple[np.ndarray, np.ndarray]: A tuple containing: - X (np.ndarray): Full observed array of selected features. - y (np.ndarray): Binary array indicating presence of NaN (1) in the target column. """ @@ -534,15 +535,15 @@ def _parallel_process_permutation_exact( def _parallel_process_projection( self, X: np.ndarray, - list_permutations: List[np.ndarray], + list_permutations: list[np.ndarray], features_idx: np.ndarray, target_idx: int, - ) -> Tuple[float, List[float]]: + ) -> tuple[float, list[float]]: X_features, y = self._build_dataset(X, features_idx, target_idx) oob_probabilities = self._get_oob_probabilities(X_features, y) u_hat = self._U_hat(oob_probabilities, y) - # We iterate over the permutation because for a given projection, we fit only one classifier - # to get oob probabilities and compute u_hat nb_permutations times. + # We iterate over the permutation because for a given projection, we fit only one + # classifier to get oob probabilities and compute u_hat nb_permutations times. result_u_permutations = Parallel(n_jobs=-1)( delayed(self.process_permutation)( X, M_perm, features_idx, target_idx, oob_probabilities @@ -552,13 +553,13 @@ def _parallel_process_projection( return u_hat, result_u_permutations @staticmethod - def _build_B(list_proj: List, n_cols: int) -> np.ndarray: + def _build_B(list_proj: list, n_cols: int) -> np.ndarray: """ Constructs a binary matrix B based on the given projections. Parameters: ----------- - list_proj : List + list_proj : list A list of tuples where each tuple represents a projection, and the second element of each tuple is an index used to build the target. n_cols : int @@ -610,7 +611,7 @@ def _compute_partial_p_value( return p_v_k / (self.nb_permutation + 1) - def test(self, X: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, List[float]]]: + def test(self, X: Union[pd.DataFrame, np.ndarray]) -> Union[float, tuple[float, list[float]]]: """ Apply the PKLM test over a real dataset. @@ -623,7 +624,7 @@ def test(self, X: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, ------- float If compute_partial_p_values=False. Returns the p-value of the test. - Tuple[float, List[float]] + tuple[float, list[float]] If compute_partial_p_values=True. Returns the p-value of the test and the list of all the partial p-values. """ @@ -649,7 +650,6 @@ def test(self, X: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, U += U_projection list_U_sigma = [x + y for x, y in zip(list_U_sigma, results)] - # Je suggère d'alléger le code de cette manipulation même si théoriquement ça a de la valeur U = U / self.nb_projections list_U_sigma = [x / self.nb_permutation for x in list_U_sigma] From 821bc7dacd86a075f744b55f8426643b0a8ad2ca Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Tue, 23 Dec 2025 16:56:12 +0100 Subject: [PATCH 19/39] pre-commit errors managed --- examples/tutorials/plot_tuto_mcar.py | 2 +- qolmat/analysis/holes_characterization.py | 38 +++++++---------------- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/examples/tutorials/plot_tuto_mcar.py b/examples/tutorials/plot_tuto_mcar.py index 1c561fbc..71da4238 100644 --- a/examples/tutorials/plot_tuto_mcar.py +++ b/examples/tutorials/plot_tuto_mcar.py @@ -343,7 +343,7 @@ # %% pklm_test = PKLMTest(random_state=rng, compute_partial_p_values=True) -p_value, partial_p_values = pklm_test.test(df_nan) +p_value, partial_p_values = pklm_test.test(df_nan) # type: ignore[misc] print(f"The p-value of the PKLM test is: {p_value:.2%}") # %% diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index 83dc6f38..3c2b095e 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -195,11 +195,6 @@ def __init__( self.exact_p_value = exact_p_value self.encoder = encoder - if self.exact_p_value: - self.process_permutation = self._parallel_process_permutation_exact # ignore F821 - else: - self.process_permutation = self._parallel_process_permutation - def _encode_dataframe(self, df: pd.DataFrame) -> np.ndarray: """ Encodes the DataFrame by converting numeric columns to a numpy array @@ -324,7 +319,7 @@ def _check_draw(X: np.ndarray, features_idx: list[int], target_idx: int) -> np.b is_distinct_values = (~np.isnan(target_values)).any() return is_nan and is_distinct_values - def _generate_label_feature_combinations(self, X: np.ndarray) -> list[tuple[int, list[int]]]: + def _generate_label_feature_combinations(self, X: np.ndarray) -> list[tuple[list[int], int]]: """ Generates all valid combinations of features and labels for projection if nb_projections_threshold > _get_max_draw(X.shape[1]). @@ -336,9 +331,9 @@ def _generate_label_feature_combinations(self, X: np.ndarray) -> list[tuple[int, Returns: -------- - list[tuple[int, list[int]]] - A list of tuples where each tuple contains a label and a list of selected features that - can be used for projection. + list[tuple[list[int], int]] + A list of tuples where each tuple contains a list of selected features that + can be used for projection, a target label. """ _, p = X.shape indices = list(range(p)) @@ -515,21 +510,12 @@ def _parallel_process_permutation( target_idx: int, oob_probabilities: np.ndarray, ) -> float: - y = self._build_label(X, M_perm, features_idx, target_idx) - return self._U_hat(oob_probabilities, y) - - def _parallel_process_permutation_exact( - self, - X: np.ndarray, - M_perm: np.ndarray, - features_idx: np.ndarray, - target_idx: int, - oob_probabilites_unused: np.ndarray, - ) -> float: - X_features, _ = self._build_dataset(X, features_idx, target_idx) - y = self._build_label(X, M_perm, features_idx, target_idx) - # In this case, we fit the classifier in each permutation. It takes much more longer. - oob_probabilities = self._get_oob_probabilities(X_features, y) + X_features, y = self._build_dataset(X, features_idx, target_idx) + if self.exact_p_value: + # Exact version + y = self._build_label(X, M_perm, features_idx, target_idx) + # In this case, we fit the classifier in each permutation. It takes much more longer. + oob_probabilities = self._get_oob_probabilities(X_features, y) return self._U_hat(oob_probabilities, y) def _parallel_process_projection( @@ -545,7 +531,7 @@ def _parallel_process_projection( # We iterate over the permutation because for a given projection, we fit only one # classifier to get oob probabilities and compute u_hat nb_permutations times. result_u_permutations = Parallel(n_jobs=-1)( - delayed(self.process_permutation)( + delayed(self._parallel_process_permutation)( X, M_perm, features_idx, target_idx, oob_probabilities ) for M_perm in list_permutations @@ -653,7 +639,7 @@ def test(self, X: Union[pd.DataFrame, np.ndarray]) -> Union[float, tuple[float, U = U / self.nb_projections list_U_sigma = [x / self.nb_permutation for x in list_U_sigma] - p_value = 1 + p_value = 1.0 for u_sigma in list_U_sigma: if u_sigma >= U: p_value += 1 From 1ff2dac35d86562033bd43d0f2b3f51ebd388bd6 Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Wed, 24 Dec 2025 18:56:03 +0100 Subject: [PATCH 20/39] pklm implementation validated --- qolmat/analysis/holes_characterization.py | 93 +++++++++++++------ qolmat/benchmark/metrics.py | 4 +- qolmat/utils/utils.py | 34 ++++++- tests/analysis/test_holes_characterization.py | 15 ++- 4 files changed, 107 insertions(+), 39 deletions(-) diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index 3c2b095e..e8c9f53f 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -9,8 +9,10 @@ from scipy.stats import chi2 from sklearn import utils as sku from sklearn.ensemble import RandomForestClassifier +from torch import rand from qolmat.imputations.imputers import ImputerEM +from qolmat.utils import utils from qolmat.utils.input_check import check_pd_df_dtypes @@ -96,7 +98,6 @@ def test(self, df: pd.DataFrame) -> float: """ Apply the Little's test to a real dataframe. - Parameters ---------- df : pd.DataFrame @@ -108,30 +109,38 @@ def test(self, df: pd.DataFrame) -> float: The p-value of the test. """ imputer = self.imputer or ImputerEM(random_state=self.rng) - imputer = imputer._fit_element(df) + imputer_em = imputer._fit_element(df) + + # Convert to numpy arrays + df_array = df.to_numpy() + df_nan_array = df.notna().to_numpy() - d0 = 0 - n_rows, n_cols = df.shape + d0 = 0.0 + n_rows, n_cols = df_array.shape degree_f = -n_cols - ml_means = imputer.means - ml_cov = n_rows / (n_rows - 1) * imputer.cov + ml_means = imputer_em.means + ml_cov = n_rows / (n_rows - 1) * imputer_em.cov # Iterate over the patterns + df_nan_df = pd.DataFrame(df_nan_array) + for tup_pattern, df_nan_pattern in df_nan_df.groupby(df_nan_df.columns.tolist()): + # Convert pattern to indices + indices = [i for i, val in enumerate(tup_pattern) if val] - df_nan = df.notna() - for tup_pattern, df_nan_pattern in df_nan.groupby(df_nan.columns.tolist()): - n_rows_pattern, _ = df_nan_pattern.shape + n_rows_pattern = len(df_nan_pattern) ind_pattern = df_nan_pattern.index - df_pattern = df.loc[ind_pattern, list(tup_pattern)] - obs_mean = df_pattern.mean().to_numpy() - diff_means = obs_mean - ml_means[list(tup_pattern)] - inv_sigma_pattern = np.linalg.solve( - ml_cov[:, tup_pattern][tup_pattern, :], np.eye(len(tup_pattern)) - ) + # Use numpy indexing + df_pattern = df_array[ind_pattern][:, indices] + obs_mean = np.nanmean(df_pattern, axis=0) + + diff_means = obs_mean - ml_means[indices] + ml_cov_pattern = ml_cov[np.ix_(indices, indices)] + + inv_sigma_pattern = np.linalg.solve(ml_cov_pattern, np.eye(len(indices))) d0 += n_rows_pattern * np.dot(np.dot(diff_means, inv_sigma_pattern), diff_means.T) - degree_f += tup_pattern.count(True) + degree_f += len(indices) return 1 - float(chi2.cdf(d0, degree_f)) @@ -430,7 +439,12 @@ def _build_label( """ return perm[~np.isnan(X[:, features_idx]).any(axis=1), target_idx] - def _get_oob_probabilities(self, X: np.ndarray, y: np.ndarray) -> np.ndarray: + def _get_oob_probabilities( + self, + X: np.ndarray, + y: np.ndarray, + random_state: Union[None, int, np.random.RandomState] = None, + ) -> np.ndarray: """ Trains a RandomForestClassifier and retrieves out-of-bag (OOB) probabilities. @@ -450,7 +464,7 @@ def _get_oob_probabilities(self, X: np.ndarray, y: np.ndarray) -> np.ndarray: min_samples_split=10, bootstrap=True, oob_score=True, - random_state=self.rng, + random_state=random_state, max_features=1.0, ) clf.fit(X, y) @@ -504,37 +518,46 @@ def _U_hat(oob_probabilities: np.ndarray, labels: np.ndarray) -> float: def _parallel_process_permutation( self, - X: np.ndarray, + seed: int, M_perm: np.ndarray, + X: np.ndarray, features_idx: np.ndarray, target_idx: int, oob_probabilities: np.ndarray, ) -> float: - X_features, y = self._build_dataset(X, features_idx, target_idx) + y = self._build_label(X, M_perm, features_idx, target_idx) if self.exact_p_value: # Exact version - y = self._build_label(X, M_perm, features_idx, target_idx) # In this case, we fit the classifier in each permutation. It takes much more longer. - oob_probabilities = self._get_oob_probabilities(X_features, y) + X_features, y = self._build_dataset(X, features_idx, target_idx) + oob_probabilities = self._get_oob_probabilities(X_features, y, random_state=seed) return self._U_hat(oob_probabilities, y) def _parallel_process_projection( self, + seed: int, X: np.ndarray, list_permutations: list[np.ndarray], features_idx: np.ndarray, target_idx: int, - ) -> tuple[float, list[float]]: + ): X_features, y = self._build_dataset(X, features_idx, target_idx) - oob_probabilities = self._get_oob_probabilities(X_features, y) + oob_probabilities = self._get_oob_probabilities(X_features, y, random_state=seed) u_hat = self._U_hat(oob_probabilities, y) # We iterate over the permutation because for a given projection, we fit only one # classifier to get oob probabilities and compute u_hat nb_permutations times. - result_u_permutations = Parallel(n_jobs=-1)( - delayed(self._parallel_process_permutation)( - X, M_perm, features_idx, target_idx, oob_probabilities - ) + args = [ + { + "M_perm": M_perm, + "X": X, + "features_idx": features_idx, + "target_idx": target_idx, + "oob_probabilities": oob_probabilities, + } for M_perm in list_permutations + ] + result_u_permutations = utils._parallel_with_seeds_and_list( + self._parallel_process_permutation, args, random_state=seed ) return u_hat, result_u_permutations @@ -627,9 +650,19 @@ def test(self, X: Union[pd.DataFrame, np.ndarray]) -> Union[float, tuple[float, U = 0.0 list_U_sigma = [0.0 for _ in range(self.nb_permutation)] - parallel_results = Parallel(n_jobs=-1)( - delayed(self._parallel_process_projection)(X, list_perm, features_idx, target_idx) + args = [ + { + "X": X, + "list_permutations": list_perm, + "features_idx": features_idx, + "target_idx": target_idx, + } for features_idx, target_idx in list_proj + ] + parallel_results = utils._parallel_with_seeds_and_list( + self._parallel_process_projection, + args, + random_state=self.rng, ) for U_projection, results in parallel_results: diff --git a/qolmat/benchmark/metrics.py b/qolmat/benchmark/metrics.py index f8f87441..08ee5216 100644 --- a/qolmat/benchmark/metrics.py +++ b/qolmat/benchmark/metrics.py @@ -112,9 +112,7 @@ def root_mean_squared_error( ------- pd.Series """ - return columnwise_metric( - df1, df2, df_mask, skm.mean_squared_error, type_cols="numerical", squared=False - ) + return columnwise_metric(df1, df2, df_mask, skm.root_mean_squared_error, type_cols="numerical") def mean_absolute_error(df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame) -> pd.Series: diff --git a/qolmat/utils/utils.py b/qolmat/utils/utils.py index ce8f7865..199355a7 100644 --- a/qolmat/utils/utils.py +++ b/qolmat/utils/utils.py @@ -1,13 +1,14 @@ -from typing import List, Optional, Tuple, Union -import warnings +from typing import Callable, List, Tuple, Union +from joblib import Parallel, delayed import numpy as np import pandas as pd from numpy.typing import NDArray from sklearn.base import check_array +from sklearn import utils as sku -from qolmat.utils.exceptions import NotDimension2, SignalTooShort +from qolmat.utils.exceptions import NotDimension2 HyperValue = Union[int, float, str] @@ -306,3 +307,30 @@ def nan_mean_cov(X: NDArray) -> Tuple[NDArray, NDArray]: cov = np.ma.cov(np.ma.masked_invalid(X), rowvar=False).data cov = cov.reshape(n_variables, n_variables) return means, cov + + +def _parallel_with_seeds_and_list( + func: Callable, + args: list[dict], + random_state: Union[None, int, np.random.RandomState] = None, +) -> list: + """ + Execute a function in parallel over a list with independent random seeds. + + Parameters: + ----------- + func: callable + Function to execute. Must accept 'seed' and 'item' as first parameters. + args: list + List of argument dictionaries to iterate over. + """ + print("_parallel_with_seeds_and_list called with seed:", random_state) + n_runs = len(args) + rng = sku.check_random_state(random_state) + ss = np.random.SeedSequence(rng.randint(0, 2**32)) + child_seeds = ss.spawn(n_runs) + seeds = [np.random.default_rng(s).integers(0, 2**32) for s in child_seeds] + print("Generated seeds:", seeds) + + # return Parallel(n_jobs=-1)(delayed(func)(seed, **arg) for seed, arg in zip(seeds, args)) + return [func(seed, **arg) for seed, arg in zip(seeds, args)] diff --git a/tests/analysis/test_holes_characterization.py b/tests/analysis/test_holes_characterization.py index 571b8f29..0d13c817 100644 --- a/tests/analysis/test_holes_characterization.py +++ b/tests/analysis/test_holes_characterization.py @@ -6,7 +6,6 @@ from qolmat.analysis.holes_characterization import LittleTest, PKLMTest from qolmat.benchmark.missing_patterns import UniformHoleGenerator from qolmat.imputations.imputers import ImputerEM -from qolmat.utils.exceptions import TypeNotHandled ### Tests for the LittleTest class @@ -14,7 +13,7 @@ @pytest.fixture def mcar_df() -> pd.DataFrame: rng = np.random.default_rng(42) - matrix = rng.multivariate_normal(mean=[0, 0], cov=[[1, 0], [0, 1]], size=200) + matrix = rng.multivariate_normal(mean=[0, 0], cov=[[1, 0], [0, 1]], size=100) df = pd.DataFrame(data=matrix, columns=["Column_1", "Column_2"]) hole_gen = UniformHoleGenerator( n_splits=1, random_state=42, subset=["Column_2"], ratio_masked=0.2 @@ -54,7 +53,7 @@ def mar_hc_df() -> pd.DataFrame: @pytest.mark.parametrize( "df_input, expected", [("mcar_df", True), ("mar_hm_df", False), ("mar_hc_df", True)] ) -def test_little_mcar_test(df_input: pd.DataFrame, expected: bool, request): +def test_little_mcar_test(df_input: str, expected: bool, request): mcar_test_little = LittleTest(random_state=42) result = mcar_test_little.test(request.getfixturevalue(df_input)) assert expected == (result > 0.05) @@ -242,3 +241,13 @@ def test__build_B(list_proj, n_cols): B = mcar_test_pklm._build_B(list_proj, n_cols) column_sums = np.sum(B, axis=0) assert np.all(column_sums == 3) + + +@pytest.mark.parametrize( + "df_input, expected", [("mcar_df", True), ("mar_hm_df", False), ("mar_hc_df", False)] +) +def test_pklm_mcar_test(df_input: str, expected: bool, request): + mcar_test_pklm = PKLMTest(nb_permutation=30, random_state=42) + result = mcar_test_pklm.test(request.getfixturevalue(df_input)) + assert isinstance(result, float) + assert expected == (result > 0.05) From 80e3d434cd4ebb43d1498db2d2a66622b3cee339 Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Wed, 24 Dec 2025 19:35:12 +0100 Subject: [PATCH 21/39] reformatted --- pyproject.toml | 2 +- qolmat/analysis/holes_characterization.py | 102 +++------- qolmat/benchmark/comparator.py | 61 ++---- qolmat/benchmark/hyperparameters.py | 8 +- qolmat/benchmark/metrics.py | 104 +++-------- qolmat/benchmark/missing_patterns.py | 93 +++------- qolmat/imputations/diffusions/base.py | 36 +--- qolmat/imputations/diffusions/ddpms.py | 174 +++++------------- qolmat/imputations/em_sampler.py | 47 ++--- qolmat/imputations/imputers.py | 133 ++++--------- qolmat/imputations/imputers_pytorch.py | 35 +--- qolmat/imputations/preprocessing.py | 20 +- qolmat/imputations/rpca/rpca_noisy.py | 34 +--- qolmat/imputations/rpca/rpca_pcp.py | 8 +- qolmat/imputations/rpca/rpca_utils.py | 4 +- qolmat/imputations/softimpute.py | 16 +- qolmat/utils/algebra.py | 6 +- qolmat/utils/data.py | 104 +++-------- qolmat/utils/exceptions.py | 22 +-- qolmat/utils/input_check.py | 4 +- qolmat/utils/plot.py | 21 +-- qolmat/utils/utils.py | 20 +- tests/analysis/test_holes_characterization.py | 34 +--- tests/benchmark/test_comparator.py | 58 ++---- tests/benchmark/test_hyperparameters.py | 32 +--- tests/benchmark/test_metrics.py | 86 +++------ tests/benchmark/test_missing_patterns.py | 36 +--- tests/imputations/rpca/test_rpca_noisy.py | 28 +-- tests/imputations/rpca/test_rpca_pcp.py | 6 +- tests/imputations/rpca/test_rpca_utils.py | 4 +- tests/imputations/test_em_sampler.py | 36 +--- tests/imputations/test_imputers.py | 60 ++---- tests/imputations/test_imputers_diffusions.py | 72 ++------ tests/imputations/test_imputers_pytorch.py | 4 +- tests/imputations/test_preprocessing.py | 12 +- tests/imputations/test_softimpute.py | 12 +- tests/utils/test_algebra.py | 8 +- tests/utils/test_data.py | 40 +--- tests/utils/test_input_check.py | 4 +- tests/utils/test_plot.py | 16 +- tests/utils/test_utils.py | 8 +- 41 files changed, 402 insertions(+), 1208 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 282a5d5a..ae3c512e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,7 +133,7 @@ testpaths = ["tests"] norecursedirs = ["_build"] [tool.ruff] -line-length = 79 +line-length = 99 fix = true indent-width = 4 target-version = "py310" diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index 331c0866..098b0538 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -45,9 +45,7 @@ def __init__(self, random_state: RandomSetting = None): self.rng = sku.check_random_state(random_state) @abstractmethod - def test( - self, df: Union[pd.DataFrame, np.ndarray] - ) -> Union[float, Tuple[float, List[float]]]: + def test(self, df: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, List[float]]]: """Perform the MCAR test on the input data. Parameters @@ -99,8 +97,7 @@ def __init__( super().__init__() if imputer and imputer.model != "multinormal": raise AttributeError( - "The ImputerEM model must be 'multinormal' " - "to use the Little's test" + "The ImputerEM model must be 'multinormal' " "to use the Little's test" ) self.imputer = imputer self.random_state = random_state @@ -131,22 +128,16 @@ def test(self, df: pd.DataFrame) -> float: # Iterate over the patterns df_nan = df.notna() - for tup_pattern, df_nan_pattern in df_nan.groupby( - df_nan.columns.tolist() - ): + for tup_pattern, df_nan_pattern in df_nan.groupby(df_nan.columns.tolist()): n_rows_pattern, _ = df_nan_pattern.shape ind_pattern = df_nan_pattern.index df_pattern = df.loc[ind_pattern, list(tup_pattern)] obs_mean = df_pattern.mean().to_numpy() diff_means = obs_mean - ml_means[list(tup_pattern)] - inv_sigma_pattern = np.linalg.inv( - ml_cov[:, tup_pattern][tup_pattern, :] - ) + inv_sigma_pattern = np.linalg.inv(ml_cov[:, tup_pattern][tup_pattern, :]) - d0 += n_rows_pattern * np.dot( - np.dot(diff_means, inv_sigma_pattern), diff_means.T - ) + d0 += n_rows_pattern * np.dot(np.dot(diff_means, inv_sigma_pattern), diff_means.T) degree_f += tup_pattern.count(True) return 1 - float(chi2.cdf(d0, degree_f)) @@ -242,9 +233,7 @@ def _encode_dataframe(self, df: pd.DataFrame) -> np.ndarray: return self.encoder.fit_transform(df) - def _pklm_preprocessing( - self, X: Union[pd.DataFrame, np.ndarray] - ) -> np.ndarray: + def _pklm_preprocessing(self, X: Union[pd.DataFrame, np.ndarray]) -> np.ndarray: """Preprocess the input DataFrame or ndarray for further processing. Parameters @@ -295,9 +284,7 @@ def _get_max_draw(p: int) -> int: """ return p * (2 ** (p - 1) - 1) - def _draw_features_and_target_indexes( - self, X: np.ndarray - ) -> Tuple[List[int], int]: + def _draw_features_and_target_indexes(self, X: np.ndarray) -> Tuple[List[int], int]: """Randomly select features and a target from the dataframe. This corresponds to the Ai and Bi projections of the paper. @@ -320,9 +307,7 @@ def _draw_features_and_target_indexes( return features_idx.tolist(), target_idx @staticmethod - def _check_draw( - X: np.ndarray, features_idx: List[int], target_idx: int - ) -> bool: + def _check_draw(X: np.ndarray, features_idx: List[int], target_idx: int) -> bool: """Check if the drawn features and target are valid. Here we check @@ -344,16 +329,12 @@ def _check_draw( True if the draw is valid, False otherwise. """ - target_values = X[~np.isnan(X[:, features_idx]).any(axis=1)][ - :, target_idx - ] + target_values = X[~np.isnan(X[:, features_idx]).any(axis=1)][:, target_idx] is_nan = np.isnan(target_values).any() is_distinct_values = (~np.isnan(target_values)).any() return is_nan and is_distinct_values - def _generate_label_feature_combinations( - self, X: np.ndarray - ) -> List[Tuple[List[int], int]]: + def _generate_label_feature_combinations(self, X: np.ndarray) -> List[Tuple[List[int], int]]: """Generate all valid combinations of features and labels. Parameters @@ -400,9 +381,7 @@ def _draw_projection(self, X: np.ndarray) -> Tuple[List[int], int]: """ is_checked = False while not is_checked: - features_idx, target_idx = self._draw_features_and_target_indexes( - X - ) + features_idx, target_idx = self._draw_features_and_target_indexes(X) is_checked = self._check_draw(X, features_idx, target_idx) return features_idx, target_idx @@ -434,13 +413,9 @@ def _build_dataset( the target column. """ - X_features = X[~np.isnan(X[:, features_idx]).any(axis=1)][ - :, features_idx - ] + X_features = X[~np.isnan(X[:, features_idx]).any(axis=1)][:, features_idx] y = np.where( - np.isnan( - X[~np.isnan(X[:, features_idx]).any(axis=1)][:, target_idx] - ), + np.isnan(X[~np.isnan(X[:, features_idx]).any(axis=1)][:, target_idx]), 1, 0, ) @@ -479,9 +454,7 @@ def _build_label( """ return perm[~np.isnan(X[:, features_idx]).any(axis=1), target_idx] - def _get_oob_probabilities( - self, X: np.ndarray, y: np.ndarray - ) -> np.ndarray: + def _get_oob_probabilities(self, X: np.ndarray, y: np.ndarray) -> np.ndarray: """Retrieve out-of-bag probabilities. Train a RandomForestClassifier and retrieves out-of-bag (OOB) @@ -547,26 +520,14 @@ def _U_hat(oob_probabilities: np.ndarray, labels: np.ndarray) -> float: if unique_labels.shape[0] == 1: if unique_labels[0] == 0: n0 = labels.shape[0] - return ( - np.log(p0_0 / (1 - p0_0)).sum() / n0 - - np.log(p1_0 / (1 - p1_0)).sum() / n0 - ) + return np.log(p0_0 / (1 - p0_0)).sum() / n0 - np.log(p1_0 / (1 - p1_0)).sum() / n0 else: n1 = labels.shape[0] - return ( - np.log(p1_1 / (1 - p1_1)).sum() / n1 - - np.log(p0_1 / (1 - p0_1)).sum() / n1 - ) + return np.log(p1_1 / (1 - p1_1)).sum() / n1 - np.log(p0_1 / (1 - p0_1)).sum() / n1 n0, n1 = label_matrix.sum(axis=0) - u_0 = ( - np.log(p0_0 / (1 - p0_0)).sum() / n0 - - np.log(p0_1 / (1 - p0_1)).sum() / n1 - ) - u_1 = ( - np.log(p1_1 / (1 - p1_1)).sum() / n1 - - np.log(p1_0 / (1 - p1_0)).sum() / n0 - ) + u_0 = np.log(p0_0 / (1 - p0_0)).sum() / n0 - np.log(p0_1 / (1 - p0_1)).sum() / n1 + u_1 = np.log(p1_1 / (1 - p1_1)).sum() / n1 - np.log(p1_0 / (1 - p1_0)).sum() / n0 return u_0 + u_1 @@ -708,9 +669,7 @@ def _compute_partial_p_value( return p_v_k / (self.nb_permutation + 1) - def test( - self, X: Union[pd.DataFrame, np.ndarray] - ) -> Union[float, Tuple[float, List[float]]]: + def test(self, X: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, List[float]]]: """Apply the PKLM test over a real dataset. Parameters @@ -733,21 +692,15 @@ def test( if self._get_max_draw(n_cols) <= self.nb_projections_threshold: list_proj = self._generate_label_feature_combinations(X) else: - list_proj = [ - self._draw_projection(X) for _ in range(self.nb_projections) - ] + list_proj = [self._draw_projection(X) for _ in range(self.nb_projections)] M = np.isnan(X).astype(int) - list_perm = [ - self.rng.permutation(M) for _ in range(self.nb_permutation) - ] + list_perm = [self.rng.permutation(M) for _ in range(self.nb_permutation)] U = 0.0 list_U_sigma = [0.0 for _ in range(self.nb_permutation)] parallel_results = Parallel(n_jobs=-1)( - delayed(self._parallel_process_projection)( - X, list_perm, features_idx, target_idx - ) + delayed(self._parallel_process_projection)(X, list_perm, features_idx, target_idx) for features_idx, target_idx in list_proj ) @@ -769,14 +722,9 @@ def test( return p_value else: B = self._build_B(list_proj, n_cols) - U_matrix = np.array( - [np.atleast_1d(item[0]) for item in parallel_results] - ) - U_sigma = np.array( - [np.atleast_1d(item[1]) for item in parallel_results] - ) + U_matrix = np.array([np.atleast_1d(item[0]) for item in parallel_results]) + U_sigma = np.array([np.atleast_1d(item[1]) for item in parallel_results]) p_values = [ - self._compute_partial_p_value(B, U_matrix, U_sigma, k) - for k in range(n_cols) + self._compute_partial_p_value(B, U_matrix, U_sigma, k) for k in range(n_cols) ] return p_value, p_values diff --git a/qolmat/benchmark/comparator.py b/qolmat/benchmark/comparator.py index 77cf5fb3..e066b4d6 100644 --- a/qolmat/benchmark/comparator.py +++ b/qolmat/benchmark/comparator.py @@ -84,15 +84,11 @@ def get_errors( dict_errors = {} for name_metric in self.metrics: fun_metric = metrics.get_metric(name_metric) - dict_errors[name_metric] = fun_metric( - df_origin, df_imputed, df_mask - ) + dict_errors[name_metric] = fun_metric(df_origin, df_imputed, df_mask) df_errors = pd.concat(dict_errors.values(), keys=dict_errors.keys()) return df_errors - def process_split( - self, split_data: Tuple[int, pd.DataFrame, pd.DataFrame] - ) -> pd.DataFrame: + def process_split(self, split_data: Tuple[int, pd.DataFrame, pd.DataFrame]) -> pd.DataFrame: """Process a split. Parameters @@ -119,15 +115,12 @@ def process_split( subset = self.generator_holes.subset if subset is None: raise ValueError( - "HoleGenerator `subset` should be overwritten in split " - "but it is none!" + "HoleGenerator `subset` should be overwritten in split " "but it is none!" ) split_results = {} for imputer_name, imputer in self.dict_imputers.items(): - dict_config_opti_imputer = self.dict_config_opti.get( - imputer_name, {} - ) + dict_config_opti_imputer = self.dict_config_opti.get(imputer_name, {}) imputer_opti = hyperparameters.optimize( imputer, @@ -140,9 +133,7 @@ def process_split( ) df_imputed = imputer_opti.fit_transform(df_with_holes) - errors = self.get_errors( - df_origin[subset], df_imputed[subset], df_mask[subset] - ) + errors = self.get_errors(df_origin[subset], df_imputed[subset], df_mask[subset]) split_results[imputer_name] = errors return pd.concat(split_results, axis=1) @@ -168,8 +159,7 @@ def process_imputer( subset = self.generator_holes.subset if subset is None: raise ValueError( - "HoleGenerator `subset` should be overwritten in split " - "but it is none!" + "HoleGenerator `subset` should be overwritten in split " "but it is none!" ) dict_config_opti_imputer = self.dict_config_opti.get(imputer_name, {}) @@ -188,14 +178,10 @@ def process_imputer( df_with_holes = df_origin.copy() df_with_holes[df_mask] = np.nan df_imputed = imputer_opti.fit_transform(df_with_holes) - errors = self.get_errors( - df_origin[subset], df_imputed[subset], df_mask[subset] - ) + errors = self.get_errors(df_origin[subset], df_imputed[subset], df_mask[subset]) imputer_results.append(errors) - return imputer_name, pd.concat(imputer_results).groupby( - level=[0, 1] - ).mean() + return imputer_name, pd.concat(imputer_results).groupby(level=[0, 1]).mean() def compare( self, @@ -229,26 +215,17 @@ def compare( 1-level index are the column names. """ - logging.info( - f"Starting comparison for {len(self.dict_imputers)} imputers." - ) + logging.info(f"Starting comparison for {len(self.dict_imputers)} imputers.") all_splits = list(self.generator_holes.split(df_origin)) if parallel_over == "auto": - parallel_over = ( - "splits" - if len(all_splits) > len(self.dict_imputers) - else "imputers" - ) + parallel_over = "splits" if len(all_splits) > len(self.dict_imputers) else "imputers" if use_parallel: logging.info(f"Parallelisation over: {parallel_over}...") if parallel_over == "splits": - split_data = [ - (i, df_mask, df_origin) - for i, df_mask in enumerate(all_splits) - ] + split_data = [(i, df_mask, df_origin) for i, df_mask in enumerate(all_splits)] n_jobs = self.get_optimal_n_jobs(split_data, n_jobs) results = Parallel(n_jobs=n_jobs)( delayed(self.process_split)(data) for data in split_data @@ -261,22 +238,16 @@ def compare( ] n_jobs = self.get_optimal_n_jobs(imputer_data, n_jobs) results = Parallel(n_jobs=n_jobs)( - delayed(self.process_imputer)(data) - for data in imputer_data + delayed(self.process_imputer)(data) for data in imputer_data ) final_results = pd.concat(dict(results), axis=1) else: - raise ValueError( - "`parallel_over` should be `auto`, `splits` or `imputers`." - ) + raise ValueError("`parallel_over` should be `auto`, `splits` or `imputers`.") else: logging.info("Sequential treatment...") if parallel_over == "splits": - split_data = [ - (i, df_mask, df_origin) - for i, df_mask in enumerate(all_splits) - ] + split_data = [(i, df_mask, df_origin) for i, df_mask in enumerate(all_splits)] results = [self.process_split(data) for data in split_data] final_results = pd.concat(results).groupby(level=[0, 1]).mean() elif parallel_over == "imputers": @@ -287,9 +258,7 @@ def compare( results = [self.process_imputer(data) for data in imputer_data] final_results = pd.concat(dict(results), axis=1) else: - raise ValueError( - "`parallel_over` should be `auto`, `splits` or `imputers`." - ) + raise ValueError("`parallel_over` should be `auto`, `splits` or `imputers`.") logging.info("Comparison successfully terminated.") return final_results diff --git a/qolmat/benchmark/hyperparameters.py b/qolmat/benchmark/hyperparameters.py index 242fdd58..99a77af8 100644 --- a/qolmat/benchmark/hyperparameters.py +++ b/qolmat/benchmark/hyperparameters.py @@ -62,9 +62,7 @@ def fun_obf(args: List[HyperValue]) -> float: df_imputed = imputer.fit_transform(df_corrupted) subset = generator.subset fun_metric = metrics.get_metric(metric) - errors = fun_metric( - df_origin[subset], df_imputed[subset], df_mask[subset] - ) + errors = fun_metric(df_origin[subset], df_imputed[subset], df_mask[subset]) list_errors.append(errors) mean_errors = np.mean(errors) @@ -120,9 +118,7 @@ def optimize( return imputer names_hyperparams = list(dict_config.keys()) values_hyperparams = list(dict_config.values()) - imputer.imputer_params = tuple( - set(imputer.imputer_params) | set(dict_config.keys()) - ) + imputer.imputer_params = tuple(set(imputer.imputer_params) | set(dict_config.keys())) if verbose and hasattr(imputer, "verbose"): setattr(imputer, "verbose", False) fun_obj = get_objective(imputer, df, generator, metric, names_hyperparams) diff --git a/qolmat/benchmark/metrics.py b/qolmat/benchmark/metrics.py index 5d5ff3eb..66fd28e3 100644 --- a/qolmat/benchmark/metrics.py +++ b/qolmat/benchmark/metrics.py @@ -61,8 +61,7 @@ def columnwise_metric( pd.testing.assert_index_equal(df1.columns, df2.columns) except AssertionError: raise ValueError( - "Input dataframes do not have the same columns! " - f"({df1.columns} != {df2.columns})" + "Input dataframes do not have the same columns! " f"({df1.columns} != {df2.columns})" ) if type_cols == "all": cols = df1.columns.tolist() @@ -71,9 +70,7 @@ def columnwise_metric( elif type_cols == "categorical": cols = utils._get_categorical_features(df1) else: - raise ValueError( - f"Value {type_cols} is not valid for parameter `type_cols`!" - ) + raise ValueError(f"Value {type_cols} is not valid for parameter `type_cols`!") if cols == []: raise ValueError(f"No column found for the type {type_cols}!") values = {} @@ -87,9 +84,7 @@ def columnwise_metric( return pd.Series(values) -def mean_squared_error( - df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame -) -> pd.Series: +def mean_squared_error(df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame) -> pd.Series: """Mean squared error between two dataframes. Parameters @@ -106,9 +101,7 @@ def mean_squared_error( pd.Series """ - return columnwise_metric( - df1, df2, df_mask, skm.mean_squared_error, type_cols="numerical" - ) + return columnwise_metric(df1, df2, df_mask, skm.mean_squared_error, type_cols="numerical") def root_mean_squared_error( @@ -139,9 +132,7 @@ def root_mean_squared_error( ) -def mean_absolute_error( - df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame -) -> pd.Series: +def mean_absolute_error(df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame) -> pd.Series: """Compute the mean absolute error between two dataframes. Parameters @@ -158,9 +149,7 @@ def mean_absolute_error( pd.Series """ - return columnwise_metric( - df1, df2, df_mask, skm.mean_absolute_error, type_cols="numerical" - ) + return columnwise_metric(df1, df2, df_mask, skm.mean_absolute_error, type_cols="numerical") def mean_absolute_percentage_error( @@ -191,9 +180,7 @@ def mean_absolute_percentage_error( ) -def _weighted_mean_absolute_percentage_error_1D( - values1: pd.Series, values2: pd.Series -) -> float: +def _weighted_mean_absolute_percentage_error_1D(values1: pd.Series, values2: pd.Series) -> float: """Compute the weighted mean absolute perc. error between 2 series. Based on https://en.wikipedia.org/wiki/Mean_absolute_percentage_error @@ -242,9 +229,7 @@ def weighted_mean_absolute_percentage_error( ) -def accuracy( - df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame -) -> pd.Series: +def accuracy(df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame) -> pd.Series: """Compute the matching ratio between the two datasets. Parameters @@ -317,9 +302,7 @@ def dist_wasserstein( """ if method == "columnwise": - return columnwise_metric( - df1, df2, df_mask, scipy.stats.wasserstein_distance - ) + return columnwise_metric(df1, df2, df_mask, scipy.stats.wasserstein_distance) else: raise AssertionError( f"The parameter of the function wasserstein_distance should " @@ -374,9 +357,7 @@ def kolmogorov_smirnov_test( KS test statistic """ - return columnwise_metric( - df1, df2, df_mask, kolmogorov_smirnov_test_1D, type_cols="numerical" - ) + return columnwise_metric(df1, df2, df_mask, kolmogorov_smirnov_test_1D, type_cols="numerical") def _total_variance_distance_1D(df1: pd.Series, df2: pd.Series) -> float: @@ -441,9 +422,7 @@ def _check_same_number_columns(df1: pd.DataFrame, df2: pd.DataFrame): raise Exception("inputs have to have the same number of columns.") -def _get_correlation_pearson_matrix( - df: pd.DataFrame, use_p_value: bool = True -) -> pd.DataFrame: +def _get_correlation_pearson_matrix(df: pd.DataFrame, use_p_value: bool = True) -> pd.DataFrame: """Get matrix of correlation values for numerical features. Based on Pearson correlation coefficient or p-value for @@ -466,9 +445,7 @@ def _get_correlation_pearson_matrix( matrix = np.zeros((len(df.columns), len(df.columns))) for idx_1, col_1 in enumerate(cols): for idx_2, col_2 in enumerate(cols): - res = scipy.stats.mstats.pearsonr( - df[[col_1]].values, df[[col_2]].values - ) + res = scipy.stats.mstats.pearsonr(df[[col_1]].values, df[[col_2]].values) if use_p_value: matrix[idx_1, idx_2] = res[1] else: @@ -514,20 +491,14 @@ def mean_difference_correlation_matrix_numerical_features( cols_numerical = utils._get_numerical_features(df1) if cols_numerical == []: raise Exception("No numerical feature found") - df_corr1 = _get_correlation_pearson_matrix( - df1[cols_numerical], use_p_value=use_p_value - ) - df_corr2 = _get_correlation_pearson_matrix( - df2[cols_numerical], use_p_value=use_p_value - ) + df_corr1 = _get_correlation_pearson_matrix(df1[cols_numerical], use_p_value=use_p_value) + df_corr2 = _get_correlation_pearson_matrix(df2[cols_numerical], use_p_value=use_p_value) diff_corr = (df_corr1 - df_corr2).abs().mean(axis=1) return pd.Series(diff_corr, index=cols_numerical) -def _get_correlation_chi2_matrix( - data: pd.DataFrame, use_p_value: bool = True -) -> pd.DataFrame: +def _get_correlation_chi2_matrix(data: pd.DataFrame, use_p_value: bool = True) -> pd.DataFrame: """Get matrix of correlation values for categorical features. Based on Chi-square test of independence of variables @@ -600,12 +571,8 @@ def mean_difference_correlation_matrix_categorical_features( cols_categorical = utils._get_categorical_features(df1) if cols_categorical == []: raise Exception("No categorical feature found") - df_corr1 = _get_correlation_chi2_matrix( - df1[cols_categorical], use_p_value=use_p_value - ) - df_corr2 = _get_correlation_chi2_matrix( - df2[cols_categorical], use_p_value=use_p_value - ) + df_corr1 = _get_correlation_chi2_matrix(df1[cols_categorical], use_p_value=use_p_value) + df_corr2 = _get_correlation_chi2_matrix(df2[cols_categorical], use_p_value=use_p_value) diff_corr = (df_corr1 - df_corr2).abs().mean(axis=1) return pd.Series(diff_corr, index=cols_categorical) @@ -751,9 +718,7 @@ def _sum_manhattan_distances(df1: pd.DataFrame) -> float: return result -def sum_energy_distances( - df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame -) -> pd.Series: +def sum_energy_distances(df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame) -> pd.Series: """Compute the sum of energy distances between df1 and df2. It is based on https://dcor.readthedocs.io/en/latest/theory.html# @@ -783,11 +748,7 @@ def sum_energy_distances( df = pd.concat([df1, df2]) sum_distances_df1_df2 = _sum_manhattan_distances(df) - sum_distance = ( - 2 * sum_distances_df1_df2 - - 4 * sum_distances_df1 - - 4 * sum_distances_df2 - ) + sum_distance = 2 * sum_distances_df1_df2 - 4 * sum_distances_df1 - 4 * sum_distances_df2 return pd.Series(sum_distance, index=["All"]) @@ -984,13 +945,10 @@ def kl_divergence_gaussian(df1: pd.DataFrame, df2: pd.DataFrame) -> float: means1 = np.array(df1.mean()) means2 = np.array(df2.mean()) try: - div_kl = algebra.kl_divergence_gaussian_exact( - means1, cov1, means2, cov2 - ) + div_kl = algebra.kl_divergence_gaussian_exact(means1, cov1, means2, cov2) except LinAlgError: raise ValueError( - "Provided datasets have degenerate colinearities, KL-divergence " - "cannot be computed!" + "Provided datasets have degenerate colinearities, KL-divergence " "cannot be computed!" ) return div_kl @@ -1038,9 +996,7 @@ def kl_divergence( """ if method == "columnwise": - return columnwise_metric( - df1, df2, df_mask, kl_divergence_1D, type_cols="numerical" - ) + return columnwise_metric(df1, df2, df_mask, kl_divergence_1D, type_cols="numerical") elif method == "gaussian": return pattern_based_weighted_mean_metric( df1, @@ -1161,9 +1117,7 @@ def pattern_based_weighted_mean_metric( elif type_cols == "categorical": cols = df1.select_dtypes(exclude=["number"]).columns else: - raise ValueError( - f"Value {type_cols} is not valid for parameter `type_cols`!" - ) + raise ValueError(f"Value {type_cols} is not valid for parameter `type_cols`!") if np.any(df_mask & df1.isna()): raise ValueError("The argument df1 has missing values on the mask!") @@ -1177,9 +1131,7 @@ def pattern_based_weighted_mean_metric( df2 = df2[cols].loc[rows_mask] df_mask = df_mask[cols].loc[rows_mask] max_num_row = 0 - for tup_pattern, df_mask_pattern in df_mask.groupby( - df_mask.columns.tolist() - ): + for tup_pattern, df_mask_pattern in df_mask.groupby(df_mask.columns.tolist()): ind_pattern = df_mask_pattern.index df1_pattern = df1.loc[ind_pattern, list(tup_pattern)] max_num_row = max(max_num_row, len(df1_pattern)) @@ -1190,9 +1142,7 @@ def pattern_based_weighted_mean_metric( scores.append(metric(df1_pattern, df2_pattern, **kwargs)) if len(scores) == 0: raise NotEnoughSamples(max_num_row, min_n_rows) - return pd.Series( - sum([s * w for s, w in zip(scores, weights)]), index=["All"] - ) + return pd.Series(sum([s * w for s, w in zip(scores, weights)]), index=["All"]) def get_metric( @@ -1221,9 +1171,7 @@ def get_metric( "kl_columnwise": partial(kl_divergence, method="columnwise"), "kl_gaussian": partial(kl_divergence, method="gaussian"), "ks_test": kolmogorov_smirnov_test, - "correlation_diff": ( - mean_difference_correlation_matrix_numerical_features - ), + "correlation_diff": (mean_difference_correlation_matrix_numerical_features), "energy": sum_energy_distances, "frechet": partial(frechet_distance, method="single"), "frechet_pattern": partial(frechet_distance, method="pattern"), diff --git a/qolmat/benchmark/missing_patterns.py b/qolmat/benchmark/missing_patterns.py index ffb5fe20..bf2f3c28 100644 --- a/qolmat/benchmark/missing_patterns.py +++ b/qolmat/benchmark/missing_patterns.py @@ -43,9 +43,7 @@ def compute_transition_counts_matrix(states: pd.Series): return df_counts -def compute_transition_matrix( - states: pd.Series, ngroups: Optional[List] = None -): +def compute_transition_matrix(states: pd.Series, ngroups: Optional[List] = None): """Compute the transition matrix. Parameters @@ -64,13 +62,8 @@ def compute_transition_matrix( if ngroups is None: df_counts = compute_transition_counts_matrix(states) else: - list_counts = [ - compute_transition_counts_matrix(df) - for _, df in states.groupby(ngroups) - ] - df_counts = functools.reduce( - lambda a, b: a.add(b, fill_value=0), list_counts - ) + list_counts = [compute_transition_counts_matrix(df) for _, df in states.groupby(ngroups)] + df_counts = functools.reduce(lambda a, b: a.add(b, fill_value=0), list_counts) df_transition = df_counts.div(df_counts.sum(axis=1), axis=0) return df_transition @@ -149,9 +142,7 @@ def fit(self, X: pd.DataFrame) -> _HoleGenerator: missing_per_col = X[self.subset].isna().sum() self.dict_ratios = (missing_per_col / missing_per_col.sum()).to_dict() if self.groups: - self.ngroups = ( - X.groupby(list(self.groups)).ngroup().rename("_ngroup") - ) + self.ngroups = X.groupby(list(self.groups)).ngroup().rename("_ngroup") else: self.ngroups = None @@ -178,9 +169,7 @@ def split(self, X: pd.DataFrame) -> List[pd.DataFrame]: if self.ngroups is None: mask = self.generate_mask(X) else: - mask = X.groupby(self.ngroups, group_keys=False).apply( - self.generate_mask - ) + mask = X.groupby(self.ngroups, group_keys=False).apply(self.generate_mask) list_masks.append(mask) return list_masks @@ -307,9 +296,7 @@ def __init__( groups=groups, ) - def generate_hole_sizes( - self, column: str, n_masked: int, sort: bool = True - ) -> List[int]: + def generate_hole_sizes(self, column: str, n_masked: int, sort: bool = True) -> List[int]: """Generate a sequence of states "states" of size "size". Generated from a transition matrix "df_transition" @@ -364,17 +351,13 @@ def generate_mask(self, X: pd.DataFrame) -> pd.DataFrame: sizes_max = get_sizes_max(states) n_masked_left = n_masked_col - sizes_sampled = self.generate_hole_sizes( - column, n_masked_col, sort=True - ) + sizes_sampled = self.generate_hole_sizes(column, n_masked_col, sort=True) if sum(sizes_sampled) != n_masked_col: raise ValueError( "sum of sizes_sampled is different from n_masked_col: " f"{sum(sizes_sampled)} != {n_masked_col}." ) - sizes_sampled += self.generate_hole_sizes( - column, n_masked_col, sort=False - ) + sizes_sampled += self.generate_hole_sizes(column, n_masked_col, sort=False) for sample in sizes_sampled: sample = min(min(sample, sizes_max.max()), n_masked_left) i_hole = self.rng.choice(np.where(sample <= sizes_max)[0]) @@ -400,9 +383,7 @@ def generate_mask(self, X: pd.DataFrame) -> pd.DataFrame: break if list_failed: - warnings.warn( - f"No place to introduce sampled holes of size {list_failed}!" - ) + warnings.warn(f"No place to introduce sampled holes of size {list_failed}!") return mask @@ -488,9 +469,7 @@ def sample_sizes(self, column: str, n_masked: int): proba_out = self.dict_probas_out[column] mean_size = 1 / proba_out n_holes = 2 * round(n_masked / mean_size) - sizes_sampled = pd.Series( - self.rng.geometric(p=proba_out, size=n_holes) - ) + sizes_sampled = pd.Series(self.rng.geometric(p=proba_out, size=n_holes)) return sizes_sampled @@ -576,16 +555,12 @@ def fit(self, X: pd.DataFrame) -> EmpiricalHoleGenerator: for column in self.subset: states = X[column].isna() if self.ngroups is None: - self.dict_distributions_holes[column] = ( - self.compute_distribution_holes(states) - ) + self.dict_distributions_holes[column] = self.compute_distribution_holes(states) else: distributions_holes = states.groupby(self.ngroups).apply( self.compute_distribution_holes ) - distributions_holes = distributions_holes.groupby( - by="_size_hole" - ).sum() + distributions_holes = distributions_holes.groupby(by="_size_hole").sum() self.dict_distributions_holes[column] = distributions_holes return self @@ -606,14 +581,10 @@ def sample_sizes(self, column, n_masked): """ distribution_holes = self.dict_distributions_holes[column] distribution_holes /= distribution_holes.sum() - mean_size = ( - distribution_holes.values * distribution_holes.index.values - ).sum() + mean_size = (distribution_holes.values * distribution_holes.index.values).sum() n_samples = 2 * round(n_masked / mean_size) - sizes_sampled = self.rng.choice( - distribution_holes.index, n_samples, p=distribution_holes - ) + sizes_sampled = self.rng.choice(distribution_holes.index, n_samples, p=distribution_holes) return sizes_sampled @@ -679,18 +650,12 @@ def fit(self, X: pd.DataFrame) -> MultiMarkovHoleGenerator: states = X[self.subset].isna().apply(lambda x: tuple(x), axis=1) self.df_transition = compute_transition_matrix(states, self.ngroups) - self.df_transition.index = pd.MultiIndex.from_tuples( - self.df_transition.index - ) - self.df_transition.columns = pd.MultiIndex.from_tuples( - self.df_transition.columns - ) + self.df_transition.index = pd.MultiIndex.from_tuples(self.df_transition.index) + self.df_transition.columns = pd.MultiIndex.from_tuples(self.df_transition.columns) return self - def generate_multi_realisation( - self, n_masked: int - ) -> List[List[Tuple[bool, ...]]]: + def generate_multi_realisation(self, n_masked: int) -> List[List[Tuple[bool, ...]]]: """Generate a sequence of states "states" of size "size". Generated from a transition matrix "df_transition" @@ -716,9 +681,7 @@ def generate_multi_realisation( realisation = [] while True: probas = self.df_transition.loc[state, :].values - state = np.random.choice( - self.df_transition.columns, 1, p=probas - )[0] + state = np.random.choice(self.df_transition.columns, 1, p=probas)[0] if state == state_nona: break else: @@ -748,9 +711,7 @@ def generate_mask(self, X: pd.DataFrame) -> List[pd.DataFrame]: """ self.rng = sku.check_random_state(self.random_state) X_subset = X[self.subset] - mask = pd.DataFrame( - False, columns=X_subset.columns, index=X_subset.index - ) + mask = pd.DataFrame(False, columns=X_subset.columns, index=X_subset.index) values_hasna = X_subset.isna().any(axis=1) @@ -851,9 +812,7 @@ def fit(self, X: pd.DataFrame) -> GroupedHoleGenerator: super().fit(X) if self.n_splits > self.ngroups.nunique(): - raise ValueError( - "n_samples has to be smaller than the number of groups." - ) + raise ValueError("n_samples has to be smaller than the number of groups.") return self @@ -872,15 +831,11 @@ def split(self, X: pd.DataFrame) -> List[pd.DataFrame]: """ self.fit(X) - group_sizes = ( - X.groupby(self.ngroups, group_keys=False).count().mean(axis=1) - ) + group_sizes = X.groupby(self.ngroups, group_keys=False).count().mean(axis=1) list_masks = [] for _ in range(self.n_splits): - shuffled_group_sizes = group_sizes.sample( - frac=1, random_state=self.random_state - ) + shuffled_group_sizes = group_sizes.sample(frac=1, random_state=self.random_state) ratio_masks = shuffled_group_sizes.cumsum() / len(X) ratio_masks = ratio_masks.reset_index(name="ratio") @@ -888,9 +843,7 @@ def split(self, X: pd.DataFrame) -> List[pd.DataFrame]: closest_ratio_mask = ratio_masks.iloc[ (ratio_masks["ratio"] - self.ratio_masked).abs().argsort()[:1] ] - groups_masked = ratio_masks.iloc[: closest_ratio_mask.index[0], :][ - "_ngroup" - ].values + groups_masked = ratio_masks.iloc[: closest_ratio_mask.index[0], :]["_ngroup"].values if closest_ratio_mask.index[0] == 0: groups_masked = ratio_masks.iloc[:1, :]["_ngroup"].values diff --git a/qolmat/imputations/diffusions/base.py b/qolmat/imputations/diffusions/base.py index 1bfe93c1..5ab113a4 100644 --- a/qolmat/imputations/diffusions/base.py +++ b/qolmat/imputations/diffusions/base.py @@ -15,9 +15,7 @@ class ResidualBlock(torch.nn.Module): https://github.com/Yura52/rtdl/blob/main/rtdl/nn/_backbones.py """ - def __init__( - self, dim_input: int, dim_embedding: int = 128, p_dropout: float = 0.0 - ): + def __init__(self, dim_input: int, dim_embedding: int = 128, p_dropout: float = 0.0): """Init function. Parameters @@ -39,9 +37,7 @@ def __init__( self.linear_out = torch.nn.Linear(dim_embedding, dim_input) - def forward( - self, x: torch.Tensor, t: torch.Tensor - ) -> Tuple[torch.Tensor, torch.Tensor]: + def forward(self, x: torch.Tensor, t: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: """Return an output of a residual block. Parameters @@ -124,9 +120,7 @@ def __init__( self.linear_out = torch.nn.Linear(dim_embedding, dim_input) - def forward( - self, x: torch.Tensor, t: torch.Tensor - ) -> Tuple[torch.Tensor, torch.Tensor]: + def forward(self, x: torch.Tensor, t: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: """Return an output of a residual block. Parameters @@ -146,9 +140,7 @@ def forward( x_emb = self.layer_norm(x) x_emb_time = self.time_layer(x_emb) - t_emb = t.repeat(1, size_window).reshape( - batch_size, size_window, dim_emb - ) + t_emb = t.repeat(1, size_window).reshape(batch_size, size_window, dim_emb) x_t = x + x_emb_time + t_emb x_t = self.linear_out(x_t) @@ -209,9 +201,7 @@ def __init__( self.layer_out_2 = torch.nn.Linear(dim_embedding, dim_input) self.dropout_out = torch.nn.Dropout(p_dropout) - self.residual_layers = torch.nn.ModuleList( - [residual_block for _ in range(num_blocks)] - ) + self.residual_layers = torch.nn.ModuleList([residual_block for _ in range(num_blocks)]) def forward(self, x: torch.Tensor, t: torch.LongTensor) -> torch.Tensor: """Predict a noise. @@ -243,18 +233,14 @@ def forward(self, x: torch.Tensor, t: torch.LongTensor) -> torch.Tensor: x_emb, skip_connection = layer(x_emb, t_emb) skip.append(skip_connection) - out = torch.sum(torch.stack(skip), dim=0) / math.sqrt( - len(self.residual_layers) - ) + out = torch.sum(torch.stack(skip), dim=0) / math.sqrt(len(self.residual_layers)) out = torch.nn.functional.relu(self.layer_out_1(out)) out = self.dropout_out(out) out = self.layer_out_2(out) return out - def _build_embedding( - self, num_noise_steps: int, dim: int = 64 - ) -> torch.Tensor: + def _build_embedding(self, num_noise_steps: int, dim: int = 64) -> torch.Tensor: """Build an embedding for noise step. More details in section E.1 of Tashiro et al., 2021 @@ -274,11 +260,7 @@ def _build_embedding( """ steps = torch.arange(num_noise_steps).unsqueeze(1) # (T,1) - frequencies = 10.0 ** (torch.arange(dim) / (dim - 1) * 4.0).unsqueeze( - 0 - ) # (1,dim) + frequencies = 10.0 ** (torch.arange(dim) / (dim - 1) * 4.0).unsqueeze(0) # (1,dim) table = steps * frequencies # (T,dim) - table = torch.cat( - [torch.sin(table), torch.cos(table)], dim=1 - ) # (T,dim*2) + table = torch.cat([torch.sin(table), torch.cos(table)], dim=1) # (T,dim*2) return table diff --git a/qolmat/imputations/diffusions/ddpms.py b/qolmat/imputations/diffusions/ddpms.py index 7c6ea31f..646d1bff 100644 --- a/qolmat/imputations/diffusions/ddpms.py +++ b/qolmat/imputations/diffusions/ddpms.py @@ -84,11 +84,7 @@ def __init__( Pass an int for reproducible output across multiple function calls. """ - self.device = ( - torch.device("cuda") - if torch.cuda.is_available() - else torch.device("cpu") - ) + self.device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") # Hyper-parameters for DDPM # Section 2, equation 1, num_noise_steps is T. @@ -147,9 +143,7 @@ def __getstate__(self) -> dict[str, Any]: state.pop("optimiser") return state - def _q_sample( - self, x: torch.Tensor, t: torch.Tensor - ) -> Tuple[torch.Tensor, torch.Tensor]: + def _q_sample(self, x: torch.Tensor, t: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: """Sample q. Section 3.2, algorithm 1 formula implementation. Forward process, @@ -180,9 +174,7 @@ def _get_eps_model(self) -> AutoEncoder: model = AutoEncoder( num_noise_steps=self.num_noise_steps, dim_input=self.dim_input, - residual_block=ResidualBlock( - self.dim_embedding, self.dim_embedding, self.p_dropout - ), + residual_block=ResidualBlock(self.dim_embedding, self.dim_embedding, self.p_dropout), dim_embedding=self.dim_embedding, num_blocks=self.num_blocks, p_dropout=self.p_dropout, @@ -193,9 +185,7 @@ def _set_eps_model(self) -> None: model = self._get_eps_model() self._eps_model = model.to(self.device) - self.optimiser = torch.optim.Adam( - self._eps_model.parameters(), lr=self.lr - ) + self.optimiser = torch.optim.Adam(self._eps_model.parameters(), lr=self.lr) def get_num_params(self) -> int: """Compute the number of parameters of the underlying model. @@ -207,9 +197,7 @@ def get_num_params(self) -> int: """ if hasattr(self, "_eps_model"): - model_parameters = filter( - lambda p: p.requires_grad, self._eps_model.parameters() - ) + model_parameters = filter(lambda p: p.requires_grad, self._eps_model.parameters()) params = sum([np.prod(p.size()) for p in model_parameters]) return int(params) else: @@ -230,22 +218,14 @@ def _print_valid(self, epoch: int, time_duration: float) -> None: print_step = 1 if int(self.epochs / 10) == 0 else int(self.epochs / 10) if self.print_valid and epoch == 0: n_params = self.get_num_params() - logging.info( - f"Num params of {self.__class__.__name__}: {n_params}" - ) + logging.info(f"Num params of {self.__class__.__name__}: {n_params}") if self.print_valid and epoch % print_step == 0: string_valid = f"Epoch {epoch}: " for s in self.summary: - string_valid += ( - f" {s}={round(self.summary[s][epoch], self.round)}" - ) + string_valid += f" {s}={round(self.summary[s][epoch], self.round)}" # string_valid += f" | in {round(time_duration, 3)} secs" - remaining_duration = np.mean(self.time_durations) * ( - self.epochs - epoch - ) - string_valid += ( - f" | remaining {timedelta(seconds=remaining_duration)}" - ) + remaining_duration = np.mean(self.time_durations) * (self.epochs - epoch) + string_valid += f" | remaining {timedelta(seconds=remaining_duration)}" logging.info(string_valid) def _impute(self, x: np.ndarray, x_mask_obs: np.ndarray) -> np.ndarray: @@ -293,38 +273,27 @@ def _impute(self, x: np.ndarray, x_mask_obs: np.ndarray) -> np.ndarray: # is processed. sqrt_alpha_t = self.sqrt_alpha[t].view(-1, 1, 1) beta_t = self.beta[t].view(-1, 1, 1) - sqrt_one_minus_alpha_hat_t = ( - self.sqrt_one_minus_alpha_hat[t].view(-1, 1, 1) + sqrt_one_minus_alpha_hat_t = self.sqrt_one_minus_alpha_hat[t].view( + -1, 1, 1 ) epsilon_t = self.std_beta[t].view(-1, 1, 1) else: # Each row of data is separately processed. sqrt_alpha_t = self.sqrt_alpha[t].view(-1, 1) beta_t = self.beta[t].view(-1, 1) - sqrt_one_minus_alpha_hat_t = ( - self.sqrt_one_minus_alpha_hat[t].view(-1, 1) - ) + sqrt_one_minus_alpha_hat_t = self.sqrt_one_minus_alpha_hat[t].view(-1, 1) epsilon_t = self.std_beta[t].view(-1, 1) - random_noise = ( - torch.randn_like(noise) - if i > 1 - else torch.zeros_like(noise) - ) + random_noise = torch.randn_like(noise) if i > 1 else torch.zeros_like(noise) noise = ( (1 / sqrt_alpha_t) * ( noise - - ( - (beta_t / sqrt_one_minus_alpha_hat_t) - * self._eps_model(noise, t) - ) + - ((beta_t / sqrt_one_minus_alpha_hat_t) * self._eps_model(noise, t)) ) ) + (epsilon_t * random_noise) - noise = ( - mask_x_batch * x_batch + (1.0 - mask_x_batch) * noise - ) + noise = mask_x_batch * x_batch + (1.0 - mask_x_batch) * noise # Generate data output, this activation function depends on # normalizer_x @@ -379,9 +348,7 @@ def _eval( x_final.loc[x_out.index] = x_out.loc[x_out.index] x_mask_imputed_df = ~x_mask_obs_df - columns_with_True = x_mask_imputed_df.columns[ - (x_mask_imputed_df).any() - ] + columns_with_True = x_mask_imputed_df.columns[(x_mask_imputed_df).any()] scores = {} for metric in self.metrics_valid: scores[metric.__name__] = metric( @@ -416,9 +383,7 @@ def _process_data( """ if is_training: self.normalizer_x.fit(x.values) - x_windows_processed = self.normalizer_x.transform( - x.fillna(x.mean()).values - ) + x_windows_processed = self.normalizer_x.transform(x.fillna(x.mean()).values) x_windows_mask_processed = ~x.isna().to_numpy() if mask is not None: x_windows_mask_processed = mask.to_numpy() @@ -430,9 +395,7 @@ def _process_reversely_data( ): x_normalized = self.normalizer_x.inverse_transform(x_imputed) x_normalized = x_normalized[: x_input.shape[0]] - x_out = pd.DataFrame( - x_normalized, columns=self.columns, index=x_input.index - ) + x_out = pd.DataFrame(x_normalized, columns=self.columns, index=x_input.index) x_final = x_input.copy() x_final.loc[x_out.index] = x_out.loc[x_out.index] @@ -503,14 +466,10 @@ def fit( if len(self.cols_imputed) != 0: self.cols_idx_not_imputed = [ - idx - for idx, col in enumerate(self.columns) - if col not in self.cols_imputed + idx for idx, col in enumerate(self.columns) if col not in self.cols_imputed ] - self.interval_x = { - col: [x[col].min(), x[col].max()] for col in self.columns - } + self.interval_x = {col: [x[col].min(), x[col].max()] for col in self.columns} # x_mask: 1 for observed values, 0 for nan x_processed, x_mask, _ = self._process_data(x, is_training=True) @@ -536,9 +495,7 @@ def fit( x_processed_valid, x_processed_valid_obs_mask, x_processed_valid_indices, - ) = self._process_data( - x_valid, x_valid_obs_mask, is_training=False - ) + ) = self._process_data(x_valid, x_valid_obs_mask, is_training=False) x_tensor = torch.from_numpy(x_processed).float().to(self.device) x_mask_tensor = torch.from_numpy(x_mask).float().to(self.device) @@ -560,8 +517,7 @@ def fit( self._eps_model.train() for id_batch, (x_batch, mask_x_batch) in enumerate(dataloader): mask_obs_rand = ( - torch.FloatTensor(mask_x_batch.size()).uniform_() - > self.ratio_masked + torch.FloatTensor(mask_x_batch.size()).uniform_() > self.ratio_masked ) for col in self.cols_idx_not_imputed: mask_obs_rand[:, col] = 0.0 @@ -576,9 +532,7 @@ def fit( ) x_batch_t, noise = self._q_sample(x=x_batch, t=t) predicted_noise = self._eps_model(x=x_batch_t, t=t) - loss = ( - self.loss_func(predicted_noise, noise) * mask_x_batch - ).mean() + loss = (self.loss_func(predicted_noise, noise) * mask_x_batch).mean() loss.backward() self.optimiser.step() loss_epoch += loss.item() @@ -621,9 +575,7 @@ def predict(self, x: pd.DataFrame) -> pd.DataFrame: torch.manual_seed(seed_torch) self._eps_model.eval() - x_processed, x_mask, x_indices = self._process_data( - x, is_training=False - ) + x_processed, x_mask, x_indices = self._process_data(x, is_training=False) list_x_imputed = [] for i in tqdm(range(self.num_sampling), leave=False): @@ -727,9 +679,7 @@ def __init__( self.num_layers_transformer = num_layers_transformer self.is_rolling = is_rolling - def _q_sample( - self, x: torch.Tensor, t: torch.Tensor - ) -> Tuple[torch.Tensor, torch.Tensor]: + def _q_sample(self, x: torch.Tensor, t: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: """Sample q. Section 3.2, algorithm 1 formula implementation. Forward process, @@ -750,9 +700,7 @@ def _q_sample( """ sqrt_alpha_hat = self.sqrt_alpha_hat[t].view(-1, 1, 1) - sqrt_one_minus_alpha_hat = self.sqrt_one_minus_alpha_hat[t].view( - -1, 1, 1 - ) + sqrt_one_minus_alpha_hat = self.sqrt_one_minus_alpha_hat[t].view(-1, 1, 1) epsilon = torch.randn_like(x, device=self.device) return sqrt_alpha_hat * x + sqrt_one_minus_alpha_hat * epsilon, epsilon @@ -775,9 +723,7 @@ def _set_eps_model(self): p_dropout=self.p_dropout, ).to(self.device) - self.optimiser = torch.optim.Adam( - self._eps_model.parameters(), lr=self.lr - ) + self.optimiser = torch.optim.Adam(self._eps_model.parameters(), lr=self.lr) def _process_data( self, @@ -807,9 +753,7 @@ def _process_data( x_windows: List = [] x_windows_indices: List = [] - columns_index = [ - col for col in x.index.names if col != self.index_datetime - ] + columns_index = [col for col in x.index.names if col != self.index_datetime] if is_training: if self.is_rolling: if self.print_valid: @@ -822,23 +766,13 @@ def _process_data( if len(columns_index) == 0: x_windows = x.rolling(window=self.freq_str) else: - columns_index_ = ( - columns_index[0] - if len(columns_index) == 1 - else columns_index - ) - for x_group in tqdm( - x.groupby(by=columns_index_), disable=True, leave=False - ): + columns_index_ = columns_index[0] if len(columns_index) == 1 else columns_index + for x_group in tqdm(x.groupby(by=columns_index_), disable=True, leave=False): x_windows += list( - x_group[1] - .droplevel(columns_index) - .rolling(window=self.freq_str) + x_group[1].droplevel(columns_index).rolling(window=self.freq_str) ) else: - for x_w in x.resample( - rule=self.freq_str, level=self.index_datetime - ): + for x_w in x.resample(rule=self.freq_str, level=self.index_datetime): x_windows.append(x_w[1]) else: if self.is_rolling: @@ -850,43 +784,23 @@ def _process_data( x_windows.append(x_rolling) x_windows_indices.append(x_rolling.index) else: - columns_index_ = ( - columns_index[0] - if len(columns_index) == 1 - else columns_index - ) - for x_group in tqdm( - x.groupby(by=columns_index_), disable=True, leave=False - ): - x_group_index = ( - [x_group[0]] - if len(columns_index) == 1 - else x_group[0] - ) + columns_index_ = columns_index[0] if len(columns_index) == 1 else columns_index + for x_group in tqdm(x.groupby(by=columns_index_), disable=True, leave=False): + x_group_index = [x_group[0]] if len(columns_index) == 1 else x_group[0] x_group_value = x_group[1].droplevel(columns_index) - indices_nan = x_group_value.loc[ - x_group_value.isna().any(axis=1), : - ].index - x_group_rolling = x_group_value.rolling( - window=self.freq_str - ) + indices_nan = x_group_value.loc[x_group_value.isna().any(axis=1), :].index + x_group_rolling = x_group_value.rolling(window=self.freq_str) for x_rolling in x_group_rolling: if x_rolling.index[-1] in indices_nan: x_windows.append(x_rolling) x_rolling_ = x_rolling.copy() for idx, col in enumerate(columns_index): x_rolling_[col] = x_group_index[idx] - x_rolling_ = x_rolling_.set_index( - columns_index, append=True - ) - x_rolling_ = x_rolling_.reorder_levels( - x.index.names - ) + x_rolling_ = x_rolling_.set_index(columns_index, append=True) + x_rolling_ = x_rolling_.reorder_levels(x.index.names) x_windows_indices.append(x_rolling_.index) else: - for x_w in x.resample( - rule=self.freq_str, level=self.index_datetime - ): + for x_w in x.resample(rule=self.freq_str, level=self.index_datetime): x_windows.append(x_w[1]) x_windows_indices.append(x_w[1].index) @@ -947,13 +861,9 @@ def _process_reversely_data( x_indices_nan_only.append(x_indices_batch[imputed_index]) if len(np.shape(x_indices_nan_only)) == 1: - x_out_index = pd.Index( - x_indices_nan_only, name=x_input.index.names[0] - ) + x_out_index = pd.Index(x_indices_nan_only, name=x_input.index.names[0]) else: - x_out_index = pd.MultiIndex.from_tuples( - x_indices_nan_only, names=x_input.index.names - ) + x_out_index = pd.MultiIndex.from_tuples(x_indices_nan_only, names=x_input.index.names) x_normalized = self.normalizer_x.inverse_transform(x_imputed_nan_only) x_out = pd.DataFrame( x_normalized, diff --git a/qolmat/imputations/em_sampler.py b/qolmat/imputations/em_sampler.py index f4a8b24e..790ff47d 100644 --- a/qolmat/imputations/em_sampler.py +++ b/qolmat/imputations/em_sampler.py @@ -67,18 +67,14 @@ def _conjugate_gradient(A: NDArray, X: NDArray, mask: NDArray) -> NDArray: denominator = np.sum(pn * Apn, axis=1) not_converged = denominator != 0 # we stop updating if convergence is reached for this row - alphan[not_converged] = ( - numerator[not_converged] / denominator[not_converged] - ) + alphan[not_converged] = numerator[not_converged] / denominator[not_converged] xn, rnp1 = xn + pn * alphan[:, None], rn - Apn * alphan[:, None] numerator = np.sum(rnp1**2, axis=1) denominator = np.sum(rn**2, axis=1) not_converged = denominator != 0 # we stop updating if convergence is reached for this row - betan[not_converged] = ( - numerator[not_converged] / denominator[not_converged] - ) + betan[not_converged] = numerator[not_converged] / denominator[not_converged] pn, rn = rnp1 + pn * betan[:, None], rnp1 @@ -89,9 +85,7 @@ def _conjugate_gradient(A: NDArray, X: NDArray, mask: NDArray) -> NDArray: return X_final -def max_diff_Linf( - list_params: List[NDArray], n_steps: int, order: int = 1 -) -> float: +def max_diff_Linf(list_params: List[NDArray], n_steps: int, order: int = 1) -> float: """Compute the maximal L infinity norm. Computed between the `n_steps` last elements spaced by order. @@ -183,8 +177,7 @@ def __init__( ): if method not in ["mle", "sample"]: raise ValueError( - "`method` must be 'mle' or 'sample', " - f"provided value is '{method}'." + "`method` must be 'mle' or 'sample', " f"provided value is '{method}'." ) self.method = method @@ -401,10 +394,7 @@ def _sample_ou( for i in range(self.n_iter_ou): noise = self.ampli * self.rng.normal(0, 1, size=(n_rows, n_cols)) grad_X = -self.gradient_X_loglik(X_copy) - X_copy += ( - -self.dt * grad_X @ gamma - + np.sqrt(2 * self.dt) * noise @ sqrt_gamma - ) + X_copy += -self.dt * grad_X @ gamma + np.sqrt(2 * self.dt) * noise @ sqrt_gamma X_copy[~mask_na] = X_init[~mask_na] if estimate_params: self.update_parameters(X_copy) @@ -465,9 +455,7 @@ def fit(self, X: NDArray) -> "EM": X = X.copy() # utils.check_dtypes(X) # sku.check_array(X, ensure_all_finite="allow-nan", dtype="float") - sku.validation.validate_data( - self, X, ensure_all_finite="allow-nan", dtype="float" - ) + sku.validation.validate_data(self, X, ensure_all_finite="allow-nan", dtype="float") self.shape_original = X.shape self.hash_fit = hash(X.tobytes()) @@ -899,10 +887,7 @@ def _check_convergence(self) -> bool: min_diff_means1 = max_diff_Linf(list_means, n_steps=1) min_diff_covs1 = max_diff_Linf(list_covs, n_steps=1) - min_diff_reached = ( - min_diff_means1 < self.tolerance - and min_diff_covs1 < self.tolerance - ) + min_diff_reached = min_diff_means1 < self.tolerance and min_diff_covs1 < self.tolerance if min_diff_reached: return True @@ -981,9 +966,7 @@ class VARpEM(EM): >>> import numpy as np >>> from qolmat.imputations.em_sampler import VARpEM >>> imputer = VARpEM(method="sample", random_state=11) - >>> X = np.array( - ... [[1, 1, 1, 1], [np.nan, np.nan, 3, 2], [1, 2, 2, 1], [2, 2, 2, 2]] - ... ) + >>> X = np.array([[1, 1, 1, 1], [np.nan, np.nan, 3, 2], [1, 2, 2, 1], [2, 2, 2, 2]]) >>> imputer.fit_transform(X) # doctest: +SKIP """ @@ -1167,12 +1150,7 @@ def combine_parameters(self) -> None: self.B = self.ZZ_inv @ self.ZY stack_YY = np.stack(list_YY) self.YY = np.mean(stack_YY, axis=0) - self.S = ( - self.YY - - self.ZY.T @ self.B - - self.B.T @ self.ZY - + self.B.T @ self.ZZ @ self.B - ) + self.S = self.YY - self.ZY.T @ self.B - self.B.T @ self.ZY + self.B.T @ self.ZZ @ self.B self.S[np.abs(self.S) < 1e-12] = 0 self.S_inv = np.linalg.pinv(self.S, rcond=1e-10) @@ -1266,9 +1244,7 @@ def _check_convergence(self) -> bool: min_diff_B1 = max_diff_Linf(list_B, n_steps=1) min_diff_S1 = max_diff_Linf(list_S, n_steps=1) - min_diff_reached = ( - min_diff_B1 < self.tolerance and min_diff_S1 < self.tolerance - ) + min_diff_reached = min_diff_B1 < self.tolerance and min_diff_S1 < self.tolerance if min_diff_reached: return True @@ -1279,8 +1255,7 @@ def _check_convergence(self) -> bool: min_diff_B5 = max_diff_Linf(list_B, n_steps=5) min_diff_S5 = max_diff_Linf(list_S, n_steps=5) min_diff_stable = ( - min_diff_B5 < self.stagnation_threshold - and min_diff_S5 < self.stagnation_threshold + min_diff_B5 < self.stagnation_threshold and min_diff_S5 < self.stagnation_threshold ) max_loglik5_ord1 = max_diff_Linf(list_logliks, n_steps=5, order=1) diff --git a/qolmat/imputations/imputers.py b/qolmat/imputations/imputers.py index 3b106d97..a0aae360 100644 --- a/qolmat/imputations/imputers.py +++ b/qolmat/imputations/imputers.py @@ -149,15 +149,11 @@ def fit(self, X: pd.DataFrame, y: pd.DataFrame = None) -> "_Imputer": self.columns_ = tuple(df.columns) self._rng = sku.check_random_state(self.random_state) - if hasattr(self, "estimator") and hasattr( - self.estimator, "random_state" - ): + if hasattr(self, "estimator") and hasattr(self.estimator, "random_state"): self.estimator.random_state = self._rng if self.groups: - self.ngroups_ = ( - df.groupby(list(self.groups)).ngroup().rename("_ngroup") - ) + self.ngroups_ = df.groupby(list(self.groups)).ngroup().rename("_ngroup") else: self.ngroups_ = pd.Series(0, index=df.index).rename("_ngroup") @@ -218,9 +214,7 @@ def transform(self, X: pd.DataFrame) -> pd.DataFrame: if self.columnwise: df_imputed = df.copy() for col in cols_with_nans: - df_imputed[col] = self._transform_allgroups( - df[[col]], col=col - ) + df_imputed[col] = self._transform_allgroups(df[[col]], col=col) else: df_imputed = self._transform_allgroups(df) @@ -229,9 +223,7 @@ def transform(self, X: pd.DataFrame) -> pd.DataFrame: return df_imputed - def fit_transform( - self, X: pd.DataFrame, y: pd.DataFrame = None - ) -> pd.DataFrame: + def fit_transform(self, X: pd.DataFrame, y: pd.DataFrame = None) -> pd.DataFrame: """Return an imputed dataframe. The returned df has same shape as `X`, with unchanged values, @@ -279,9 +271,7 @@ def _fit_transform_fallback(self, df: pd.DataFrame) -> pd.DataFrame: df[col] = df[col].fillna(df[col].mode()[0]) return df - def _fit_allgroups( - self, df: pd.DataFrame, col: str = "__all__" - ) -> "_Imputer": + def _fit_allgroups(self, df: pd.DataFrame, col: str = "__all__") -> "_Imputer": """Fit the imputer. Either on a column, for a columnwise setting, on or all columns. @@ -319,9 +309,7 @@ def _setup_fit(self) -> None: self._dict_fitting: Dict[str, Any] = {} return - def _apply_groupwise( - self, fun: Callable, df: pd.DataFrame, **kwargs - ) -> Any: + def _apply_groupwise(self, fun: Callable, df: pd.DataFrame, **kwargs) -> Any: """Apply the function `fun`in a groupwise manner to the dataframe `df`. Parameters @@ -350,9 +338,7 @@ def _apply_groupwise( else: return fun_on_col(df) - def _transform_allgroups( - self, df: pd.DataFrame, col: str = "__all__" - ) -> pd.DataFrame: + def _transform_allgroups(self, df: pd.DataFrame, col: str = "__all__") -> pd.DataFrame: """Impute `df`. It doe sit by applying the specialized method `transform_element` @@ -380,9 +366,7 @@ def _transform_allgroups( """ self._check_dataframe(df) df = df.copy() - imputation_values = self._apply_groupwise( - self._transform_element, df, col=col - ) + imputation_values = self._apply_groupwise(self._transform_element, df, col=col) df = df.fillna(imputation_values) # fill na by applying imputation method without groups @@ -393,9 +377,7 @@ def _transform_allgroups( return df @abstractmethod - def _fit_element( - self, df: pd.DataFrame, col: str = "__all__", ngroup: int = 0 - ) -> Any: + def _fit_element(self, df: pd.DataFrame, col: str = "__all__", ngroup: int = 0) -> Any: """Fit the imputer on `df`. It does it at the group and/or column level depending onself.groups @@ -519,10 +501,7 @@ def transform(self, X: pd.DataFrame) -> pd.DataFrame: if hasattr(self, "df_solution"): df_imputed = df.fillna(self.df_solution) else: - warnings.warn( - "OracleImputer not initialized! " - "Returning imputation with zeros" - ) + warnings.warn("OracleImputer not initialized! " "Returning imputation with zeros") df_imputed = df.fillna(0) if isinstance(X, (np.ndarray)): @@ -565,15 +544,11 @@ class ImputerSimple(_Imputer): """ - def __init__( - self, groups: Tuple[str, ...] = (), strategy="median" - ) -> None: + def __init__(self, groups: Tuple[str, ...] = (), strategy="median") -> None: super().__init__(groups=groups, columnwise=True, shrink=False) self.strategy = strategy - def _fit_element( - self, df: pd.DataFrame, col: str = "__all__", ngroup: int = 0 - ) -> Any: + def _fit_element(self, df: pd.DataFrame, col: str = "__all__", ngroup: int = 0) -> Any: """Fit the imputer on `df`. It does it at the group and/or column level depending onself.groups @@ -677,9 +652,7 @@ def __init__( groups: Tuple[str, ...] = (), random_state: RandomSetting = None, ) -> None: - super().__init__( - groups=groups, columnwise=True, random_state=random_state - ) + super().__init__(groups=groups, columnwise=True, random_state=random_state) def _transform_element( self, df: pd.DataFrame, col: str = "__all__", ngroup: int = 0 @@ -930,9 +903,7 @@ def __init__( order: Optional[int] = None, col_time: Optional[str] = None, ) -> None: - super().__init__( - imputer_params=("method", "order"), groups=groups, columnwise=True - ) + super().__init__(imputer_params=("method", "order"), groups=groups, columnwise=True) self.method = method self.order = order self.col_time = col_time @@ -1015,17 +986,12 @@ class ImputerResiduals(_Imputer): >>> df = pd.DataFrame(index=pd.date_range("2015-01-01", "2020-01-01")) >>> mean = 5 >>> offset = 10 - >>> df["y"] = ( - ... np.cos(df.index.dayofyear / 365 * 2 * np.pi - np.pi) * mean - ... + offset - ... ) + >>> df["y"] = np.cos(df.index.dayofyear / 365 * 2 * np.pi - np.pi) * mean + offset >>> trend = 5 >>> df["y"] = df["y"] + trend * np.arange(0, df.shape[0]) / df.shape[0] >>> noise_mean = 0 >>> noise_var = 2 - >>> df["y"] = df["y"] + np.random.normal( - ... noise_mean, noise_var, df.shape[0] - ... ) + >>> df["y"] = df["y"] + np.random.normal(noise_mean, noise_var, df.shape[0]) >>> mask = np.random.choice([True, False], size=df.shape) >>> df = df.mask(mask) >>> imputor = ImputerResiduals(period=365, model_tsa="additive") @@ -1103,9 +1069,7 @@ def _transform_element( name = df.columns[0] values = df[df.columns[0]] values_interp = ( - values.interpolate(method=hyperparams["method_interpolation"]) - .ffill() - .bfill() + values.interpolate(method=hyperparams["method_interpolation"]).ffill().bfill() ) result = tsa_seasonal.seasonal_decompose( values_interp, @@ -1118,13 +1082,9 @@ def _transform_element( residuals[values.isna()] = np.nan residuals = ( - residuals.interpolate(method=hyperparams["method_interpolation"]) - .ffill() - .bfill() - ) - df_result = pd.DataFrame( - {name: result.seasonal + result.trend + residuals} + residuals.interpolate(method=hyperparams["method_interpolation"]).ffill().bfill() ) + df_result = pd.DataFrame({name: result.seasonal + result.trend + residuals}) return df_result @@ -1186,9 +1146,7 @@ def __init__( self.n_neighbors = n_neighbors self.weights = weights - def _fit_element( - self, df: pd.DataFrame, col: str = "__all__", ngroup: int = 0 - ) -> KNNImputer: + def _fit_element(self, df: pd.DataFrame, col: str = "__all__", ngroup: int = 0) -> KNNImputer: """Fit. the imputer on `df`. It does it at the group and/or column level depending on self.groups @@ -1216,9 +1174,7 @@ def _fit_element( """ self._check_dataframe(df) if col != "__all__": - raise ValueError( - f"col must be '__all__', but '{col}' has been passed." - ) + raise ValueError(f"col must be '__all__', but '{col}' has been passed.") hyperparameters = self.get_hyperparams() model = KNNImputer(metric="nan_euclidean", **hyperparameters) model = model.fit(df) @@ -1254,9 +1210,7 @@ def _transform_element( """ self._check_dataframe(df) if col != "__all__": - raise ValueError( - f"col must be '__all__', but '{col}' has been passed." - ) + raise ValueError(f"col must be '__all__', but '{col}' has been passed.") model = self._dict_fitting["__all__"][ngroup] X_imputed = model.fit_transform(df) return pd.DataFrame(data=X_imputed, columns=df.columns, index=df.index) @@ -1331,9 +1285,7 @@ def _fit_element( """ self._check_dataframe(df) if col != "__all__": - raise ValueError( - f"col must be '__all__', but '{col}' has been passed." - ) + raise ValueError(f"col must be '__all__', but '{col}' has been passed.") hyperparameters = self.get_hyperparams() model = IterativeImputer(estimator=self.estimator, **hyperparameters) model = model.fit(df) @@ -1370,9 +1322,7 @@ def _transform_element( """ self._check_dataframe(df) if col != "__all__": - raise ValueError( - f"col must be '__all__', but '{col}' has been passed." - ) + raise ValueError(f"col must be '__all__', but '{col}' has been passed.") model = self._dict_fitting["__all__"][ngroup] X_imputed = model.fit_transform(df) return pd.DataFrame(data=X_imputed, columns=df.columns, index=df.index) @@ -1449,9 +1399,7 @@ def _predict_estimator(self, estimator, X) -> pd.Series: pred = estimator.predict(X) return pd.Series(pred, index=X.index) - def get_Xy_valid( - self, df: pd.DataFrame, col: str - ) -> Tuple[pd.DataFrame, pd.Series]: + def get_Xy_valid(self, df: pd.DataFrame, col: str) -> Tuple[pd.DataFrame, pd.Series]: """Get a valid couple (X,y). Parameters @@ -1481,8 +1429,7 @@ def get_Xy_valid( X = X.dropna(how="any", axis=1) else: raise ValueError( - f"Value '{self.handler_nan}' is not correct " - "for argument `handler_nan'." + f"Value '{self.handler_nan}' is not correct " "for argument `handler_nan'." ) # X = pd.get_dummies(X, prefix_sep="=") y = df.loc[X.index, col] @@ -1518,9 +1465,7 @@ def _fit_element( """ self._check_dataframe(df) if col != "__all__": - raise ValueError( - f"col must be '__all__', but '{col}' has been passed." - ) + raise ValueError(f"col must be '__all__', but '{col}' has been passed.") cols_with_nans = df.columns[df.isna().any()] dict_estimators: Dict[str, BaseEstimator] = {} for col in cols_with_nans: @@ -1572,9 +1517,7 @@ def _transform_element( """ self._check_dataframe(df) if col != "__all__": - raise ValueError( - f"col must be '__all__', but '{col}' has been passed." - ) + raise ValueError(f"col must be '__all__', but '{col}' has been passed.") df_imputed = df.copy() cols_with_nans = df.columns[df.isna().any()] @@ -1669,9 +1612,7 @@ def get_model(self, **hyperparams) -> rpca_pcp.RpcaPcp: "tolerance", ] } - model = rpca_pcp.RpcaPcp( - random_state=self._rng, verbose=self.verbose, **hyperparams - ) + model = rpca_pcp.RpcaPcp(random_state=self._rng, verbose=self.verbose, **hyperparams) return model @@ -1819,9 +1760,7 @@ def get_model(self, **hyperparams) -> rpca_noisy.RpcaNoisy: "norm", ] } - model = rpca_noisy.RpcaNoisy( - random_state=self._rng, verbose=self.verbose, **hyperparams - ) + model = rpca_noisy.RpcaNoisy(random_state=self._rng, verbose=self.verbose, **hyperparams) return model def _fit_element( @@ -1998,9 +1937,7 @@ def get_model(self, **hyperparams) -> softimpute.SoftImpute: "tolerance", ] } - model = softimpute.SoftImpute( - random_state=self._rng, verbose=self.verbose, **hyperparams - ) + model = softimpute.SoftImpute(random_state=self._rng, verbose=self.verbose, **hyperparams) return model @@ -2047,9 +1984,7 @@ def _transform_element( A_final = utils.get_shape_original(A, X.shape) X_imputed = M_final + A_final - df_imputed = pd.DataFrame( - X_imputed, index=df.index, columns=df.columns - ) + df_imputed = pd.DataFrame(X_imputed, index=df.index, columns=df.columns) df_imputed = df.where(~df.isna(), df_imputed) return df_imputed @@ -2232,8 +2167,6 @@ def _transform_element( X = df.values.astype(float) X_imputed = model.transform(X) - df_transformed = pd.DataFrame( - X_imputed, columns=df.columns, index=df.index - ) + df_transformed = pd.DataFrame(X_imputed, columns=df.columns, index=df.index) return df_transformed diff --git a/qolmat/imputations/imputers_pytorch.py b/qolmat/imputations/imputers_pytorch.py index 24c7c8e6..cd6a3078 100644 --- a/qolmat/imputations/imputers_pytorch.py +++ b/qolmat/imputations/imputers_pytorch.py @@ -82,9 +82,7 @@ def __init__( self.loss_fn = loss_fn self.estimator = estimator - def _fit_estimator( - self, estimator: nn.Sequential, X: pd.DataFrame, y: pd.DataFrame - ) -> Any: + def _fit_estimator(self, estimator: nn.Sequential, X: pd.DataFrame, y: pd.DataFrame) -> Any: """Fit the PyTorch estimator using the provided input and target data. Parameters @@ -124,9 +122,7 @@ def _fit_estimator( pbar.update(1) return estimator - def _predict_estimator( - self, estimator: nn.Sequential, X: pd.DataFrame - ) -> pd.Series: + def _predict_estimator(self, estimator: nn.Sequential, X: pd.DataFrame) -> pd.Series: """Perform predictions using the trained PyTorch estimator. Parameters @@ -240,10 +236,7 @@ def fit(self, X: NDArray, y: NDArray) -> "Autoencoder": loss.backward() optimizer.step() if (epoch + 1) % 10 == 0: - logging.info( - f"Epoch [{epoch + 1}/{self.epochs}], " - f"Loss: {loss.item():.4f}" - ) + logging.info(f"Epoch [{epoch + 1}/{self.epochs}], " f"Loss: {loss.item():.4f}") list_loss.append(loss.item()) self.loss.extend([list_loss]) return self @@ -333,9 +326,7 @@ def __init__( self.encoder = encoder self.decoder = decoder - def _fit_element( - self, df: pd.DataFrame, col: str = "__all__", ngroup: int = 0 - ) -> Autoencoder: + def _fit_element(self, df: pd.DataFrame, col: str = "__all__", ngroup: int = 0) -> Autoencoder: """Fit the imputer on `df`. It does that at the group and/or column level depending onself.groups @@ -458,9 +449,7 @@ def build_mlp( Examples -------- - >>> model = build_mlp( - ... input_dim=10, list_num_neurons=[32, 64, 128], output_dim=1 - ... ) + >>> model = build_mlp(input_dim=10, list_num_neurons=[32, 64, 128], output_dim=1) >>> print(model) Sequential( (0): Linear(in_features=10, out_features=32, bias=True) @@ -680,9 +669,7 @@ def __init__( ... [2, 2, 2, 2], ... ] ... ) - >>> imputer = ImputerDiffusion( - ... epochs=50, batch_size=1, random_state=11 - ... ) + >>> imputer = ImputerDiffusion(epochs=50, batch_size=1, random_state=11) >>> >>> df_imputed = imputer.fit_transform(X) @@ -771,16 +758,10 @@ def get_params_model(self) -> dict: "num_layers_transformer", "is_rolling", ] - dict_params = { - key: value - for key, value in self.__dict__.items() - if key in list_params - } + dict_params = {key: value for key, value in self.__dict__.items() if key in list_params} return dict_params - def _fit_element( - self, df: pd.DataFrame, col: str = "__all__", ngroup: int = 0 - ): + def _fit_element(self, df: pd.DataFrame, col: str = "__all__", ngroup: int = 0): """Fit the imputer on `df`. It does it at the group and/or column level depending onself.groups diff --git a/qolmat/imputations/preprocessing.py b/qolmat/imputations/preprocessing.py index 18f55aa5..ff527ea5 100644 --- a/qolmat/imputations/preprocessing.py +++ b/qolmat/imputations/preprocessing.py @@ -313,16 +313,12 @@ class WrapperTransformer(TransformerMixin, BaseEstimator): Wrapper with reversible transformers designed to embed the data. """ - def __init__( - self, transformer: TransformerMixin, wrapper: TransformerMixin - ): + def __init__(self, transformer: TransformerMixin, wrapper: TransformerMixin): super().__init__() self.transformer = transformer self.wrapper = wrapper - def fit( - self, X: NDArray, y: Optional[NDArray] = None - ) -> "WrapperTransformer": + def fit(self, X: NDArray, y: Optional[NDArray] = None) -> "WrapperTransformer": """Fit the model according to the given training data. Parameters @@ -406,15 +402,11 @@ def make_pipeline_mixte_preprocessing( """ transformers: List[Tuple] = [] if scale_numerical: - transformers += [ - ("num", StandardScaler(), selector(dtype_include=np.number)) - ] + transformers += [("num", StandardScaler(), selector(dtype_include=np.number))] ohe = OneHotEncoder(handle_unknown="ignore", use_cat_names=True) transformers += [("cat", ohe, selector(dtype_exclude=np.number))] - col_transformer = ColumnTransformer( - transformers=transformers, remainder="passthrough" - ) + col_transformer = ColumnTransformer(transformers=transformers, remainder="passthrough") col_transformer = col_transformer.set_output(transform="pandas") preprocessor = Pipeline(steps=[("col_transformer", col_transformer)]) @@ -423,9 +415,7 @@ def make_pipeline_mixte_preprocessing( return preprocessor -def make_robust_MixteHGB( - scale_numerical: bool = False, avoid_new: bool = False -) -> Pipeline: +def make_robust_MixteHGB(scale_numerical: bool = False, avoid_new: bool = False) -> Pipeline: """Create a robust pipeline for MixteHGBM. Create a preprocessing pipeline managing mixed type data diff --git a/qolmat/imputations/rpca/rpca_noisy.py b/qolmat/imputations/rpca/rpca_noisy.py index b018a96f..ed0f1062 100644 --- a/qolmat/imputations/rpca/rpca_noisy.py +++ b/qolmat/imputations/rpca/rpca_noisy.py @@ -77,9 +77,7 @@ def __init__( norm: str = "L2", verbose: bool = True, ) -> None: - super().__init__( - max_iterations=max_iterations, tolerance=tolerance, verbose=verbose - ) + super().__init__(max_iterations=max_iterations, tolerance=tolerance, verbose=verbose) self.rng = sku.check_random_state(random_state) self.rank = rank self.mu = mu @@ -304,15 +302,10 @@ def minimise_loss( mu_bar = mu * 1e3 # matrices for temporal correlation - list_H = [ - rpca_utils.toeplitz_matrix(period, n_rows) - for period in list_periods - ] + list_H = [rpca_utils.toeplitz_matrix(period, n_rows) for period in list_periods] HtH = dok_matrix((n_rows, n_rows)) for i_period, _ in enumerate(list_periods): - HtH += list_etas[i_period] * ( - list_H[i_period].T @ list_H[i_period] - ) + HtH += list_etas[i_period] * (list_H[i_period].T @ list_H[i_period]) Ir = np.eye(rank) In = identity(n_rows) @@ -362,9 +355,7 @@ def minimise_loss( if norm == "L1": for i_period, _ in enumerate(list_periods): eta = list_etas[i_period] - R[i_period] = rpca_utils.soft_thresholding( - R[i_period] / mu, eta / mu - ) + R[i_period] = rpca_utils.soft_thresholding(R[i_period] / mu, eta / mu) mu = min(mu * rho, mu_bar) @@ -375,9 +366,7 @@ def minimise_loss( error_max = max([Mc, Ac, Lc, Qc]) # type: ignore # noqa if norm == "L1": for i_period, _ in enumerate(list_periods): - Rc = np.linalg.norm( - R[i_period] - R_temp[i_period], np.inf - ) + Rc = np.linalg.norm(R[i_period] - R_temp[i_period], np.inf) error_max = max(error_max, Rc) # type: ignore # noqa if error_max < tolerance: @@ -522,9 +511,7 @@ def _check_cost_function_minimized( warnings.warn( "RPCA algorithm may provide bad results. " f"Function {function_str} increased from" - f" {cost_start} to {cost_end} instead of decreasing!".format( - "%.2f" - ) + f" {cost_start} to {cost_end} instead of decreasing!".format("%.2f") ) @staticmethod @@ -574,18 +561,13 @@ def cost_function( temporal_norm: float = 0 if len(list_etas) > 0: # matrices for temporal correlation - list_H = [ - rpca_utils.toeplitz_matrix(period, D.shape[0]) - for period in list_periods - ] + list_H = [rpca_utils.toeplitz_matrix(period, D.shape[0]) for period in list_periods] if norm == "L1": for eta, H_matrix in zip(list_etas, list_H): temporal_norm += eta * np.sum(np.abs(H_matrix @ M)) elif norm == "L2": for eta, H_matrix in zip(list_etas, list_H): - temporal_norm += eta * float( - np.linalg.norm(H_matrix @ M, "fro") - ) + temporal_norm += eta * float(np.linalg.norm(H_matrix @ M, "fro")) anomalies_norm = np.sum(np.abs(A * Omega)) cost = ( 1 / 2 * ((Omega * (D - M - A)) ** 2).sum() diff --git a/qolmat/imputations/rpca/rpca_pcp.py b/qolmat/imputations/rpca/rpca_pcp.py index 765d929b..1db3fcb9 100644 --- a/qolmat/imputations/rpca/rpca_pcp.py +++ b/qolmat/imputations/rpca/rpca_pcp.py @@ -59,9 +59,7 @@ def __init__( tolerance: float = 1e-6, verbose: bool = True, ) -> None: - super().__init__( - max_iterations=max_iterations, tolerance=tolerance, verbose=verbose - ) + super().__init__(max_iterations=max_iterations, tolerance=tolerance, verbose=verbose) self.rng = sku.check_random_state(random_state) self.mu = mu self.lam = lam @@ -175,9 +173,7 @@ def _check_cost_function_minimized( """ cost_start = np.linalg.norm(observations, "nuc") - cost_end = np.linalg.norm(low_rank, "nuc") + lam * np.sum( - Omega * np.abs(anomalies) - ) + cost_end = np.linalg.norm(low_rank, "nuc") + lam * np.sum(Omega * np.abs(anomalies)) if self.verbose and round(cost_start, 4) - round(cost_end, 4) <= -1e-2: function_str = "||D||_* + lam ||A||_1" warnings.warn( diff --git a/qolmat/imputations/rpca/rpca_utils.py b/qolmat/imputations/rpca/rpca_utils.py index 55b05dec..4372f514 100644 --- a/qolmat/imputations/rpca/rpca_utils.py +++ b/qolmat/imputations/rpca/rpca_utils.py @@ -120,9 +120,7 @@ def toeplitz_matrix(T: int, dimension: int) -> sps.spmatrix: """ n_lags = dimension - T diagonals = [np.ones(n_lags), -np.ones(n_lags)] - H_top = sps.diags( - diagonals, offsets=[0, T], shape=(n_lags, dimension), format="csr" - ) + H_top = sps.diags(diagonals, offsets=[0, T], shape=(n_lags, dimension), format="csr") H = sps.dok_matrix((dimension, dimension)) H[:n_lags] = H_top return H diff --git a/qolmat/imputations/softimpute.py b/qolmat/imputations/softimpute.py index 763b25e8..088d58c4 100644 --- a/qolmat/imputations/softimpute.py +++ b/qolmat/imputations/softimpute.py @@ -57,9 +57,7 @@ class SoftImpute(BaseEstimator, TransformerMixin): -------- >>> import numpy as np >>> from qolmat.imputations.softimpute import SoftImpute - >>> D = np.array( - ... [[1, 2, np.nan, 4], [1, 5, 3, np.nan], [4, 2, 3, 2], [1, 1, 5, 4]] - ... ) + >>> D = np.array([[1, 2, np.nan, 4], [1, 5, 3, np.nan], [4, 2, 3, 2], [1, 1, 5, 4]]) >>> Omega = ~np.isnan(D) >>> M, A = SoftImpute(random_state=11).decompose(D, Omega) >>> print(M + A) @@ -158,9 +156,7 @@ def decompose(self, X: NDArray, Omega: NDArray) -> Tuple[NDArray, NDArray]: # Step 2 : Update on B D2_invreg = (D**2 + tau) ** (-1) - Btilde = ( - (U * D).T @ np.where(Omega, X - A @ B.T, 0) + (B * D**2).T - ).T + Btilde = ((U * D).T @ np.where(Omega, X - A @ B.T, 0) + (B * D**2).T).T Btilde = Btilde * D2_invreg Utilde, D2tilde, _ = np.linalg.svd(Btilde * D, full_matrices=False) @@ -170,9 +166,7 @@ def decompose(self, X: NDArray, Omega: NDArray) -> Tuple[NDArray, NDArray]: # Step 3 : Update on A D2_invreg = (D**2 + tau) ** (-1) - Atilde = ( - (V * D).T @ np.where(Omega, X - A @ B.T, 0).T + (A * D**2).T - ).T + Atilde = ((V * D).T @ np.where(Omega, X - A @ B.T, 0).T + (A * D**2).T).T Atilde = Atilde * D2_invreg Utilde, D2tilde, _ = np.linalg.svd(Atilde * D, full_matrices=False) @@ -203,9 +197,7 @@ def decompose(self, X: NDArray, Omega: NDArray) -> Tuple[NDArray, NDArray]: if self.verbose and (cost_end > cost_start + 1e-9): warnings.warn( f"Convergence failed: cost function increased from" - f" {cost_start} to {cost_end} instead of decreasing!".format( - "%.2f" - ) + f" {cost_start} to {cost_end} instead of decreasing!".format("%.2f") ) return M, A diff --git a/qolmat/utils/algebra.py b/qolmat/utils/algebra.py index 18efd61e..675f1d2b 100644 --- a/qolmat/utils/algebra.py +++ b/qolmat/utils/algebra.py @@ -42,11 +42,7 @@ def frechet_distance_exact( """ n = len(means1) - if ( - (means2.shape != (n,)) - or (cov1.shape != (n, n)) - or (cov2.shape != (n, n)) - ): + if (means2.shape != (n,)) or (cov1.shape != (n, n)) or (cov2.shape != (n, n)): raise ValueError("Inputs have to be of same dimensions.") ssdiff = np.sum((means1 - means2) ** 2.0) diff --git a/qolmat/utils/data.py b/qolmat/utils/data.py index fe62075c..f1ace3f8 100644 --- a/qolmat/utils/data.py +++ b/qolmat/utils/data.py @@ -34,9 +34,7 @@ def read_csv_local(data_file_name: str, **kwargs) -> pd.DataFrame: dataframe """ - df = pd.read_csv( - os.path.join(ROOT_DIR, "data", f"{data_file_name}.csv"), **kwargs - ) + df = pd.read_csv(os.path.join(ROOT_DIR, "data", f"{data_file_name}.csv"), **kwargs) return df @@ -105,9 +103,7 @@ def get_dataframes_in_folder(path: str, extension: str) -> List[pd.DataFrame]: if extension in file: list_df.append(pd.read_csv(os.path.join(folder, file))) if ".tsf" in file: - loaded_data = convert_tsf_to_dataframe( - os.path.join(folder, file) - ) + loaded_data = convert_tsf_to_dataframe(os.path.join(folder, file)) return [loaded_data] return list_df @@ -150,9 +146,7 @@ def generate_artificial_ts( n_anomalies = int(n_samples * ratio_anomalies) anomalies = np.random.standard_exponential(size=n_anomalies) anomalies *= amp_anomalies * np.random.choice([-1, 1], size=n_anomalies) - ind_anomalies = np.random.choice( - range(n_samples), size=n_anomalies, replace=False - ) + ind_anomalies = np.random.choice(range(n_samples), size=n_anomalies, replace=False) A = np.zeros(n_samples) A[ind_anomalies] = anomalies @@ -197,9 +191,7 @@ def get_data( path = "https://gist.githubusercontent.com/fyyying/4aa5b471860321d7b47fd881898162b7/raw/" "6907bb3a38bfbb6fccf3a8b1edfb90e39714d14f/titanic_dataset.csv" df = pd.read_csv(path) - df = df[ - ["Survived", "Sex", "Age", "SibSp", "Parch", "Fare", "Embarked"] - ].copy() + df = df[["Survived", "Sex", "Age", "SibSp", "Parch", "Fare", "Embarked"]].copy() df["Age"] = pd.to_numeric(df["Age"], errors="coerce") df.loc["Fare"] = pd.to_numeric(df["Fare"], errors="coerce") return df @@ -215,9 +207,7 @@ def get_data( n_samples, periods, amp_anomalies, ratio_anomalies, amp_noise ) signal = X + A + E - df = pd.DataFrame( - {"signal": signal, "index": range(n_samples), "station": city} - ) + df = pd.DataFrame({"signal": signal, "index": range(n_samples), "station": city}) df.set_index(["station", "index"], inplace=True) df["X"] = X @@ -229,9 +219,7 @@ def get_data( df = pd.read_parquet(path_file) sizes_stations = df.groupby("station")["val_in"].mean().sort_values() n_groups_max = min(len(sizes_stations), n_groups_max) - stations = sizes_stations.index.get_level_values("station").unique()[ - -n_groups_max: - ] + stations = sizes_stations.index.get_level_values("station").unique()[-n_groups_max:] df = df.loc[stations] return df elif name_data == "Beijing_online": @@ -255,13 +243,9 @@ def get_data( df = read_csv_local("conductors") return df elif name_data == "Monach_weather": - urllink = os.path.join( - url_zenodo, "4654822/files/weather_dataset.zip?download=1" - ) + urllink = os.path.join(url_zenodo, "4654822/files/weather_dataset.zip?download=1") zipname = "weather_dataset" - list_loaded_data = download_data_from_zip( - zipname, urllink, datapath=datapath - ) + list_loaded_data = download_data_from_zip(zipname, urllink, datapath=datapath) loaded_data = list_loaded_data[0] df_list: List[pd.DataFrame] = [] for k in range(len(loaded_data)): @@ -274,11 +258,7 @@ def get_data( ) df_list = df_list + [ pd.DataFrame( - { - loaded_data.series_name[k] - + " " - + loaded_data.series_type[k]: values - }, + {loaded_data.series_name[k] + " " + loaded_data.series_type[k]: values}, index=time_index, ) ] @@ -292,9 +272,7 @@ def get_data( "4659727/files/australian_electricity_demand_dataset.zip?download=1", ) zipname = "australian_electricity_demand_dataset" - list_loaded_data = download_data_from_zip( - zipname, urllink, datapath=datapath - ) + list_loaded_data = download_data_from_zip(zipname, urllink, datapath=datapath) loaded_data = list_loaded_data[0] df_list = [] for k in range(len(loaded_data)): @@ -307,11 +285,7 @@ def get_data( ) df_list = df_list + [ pd.DataFrame( - { - loaded_data.series_name[k] - + " " - + loaded_data.state[k]: values - }, + {loaded_data.series_name[k] + " " + loaded_data.state[k]: values}, index=time_index, ) ] @@ -406,16 +380,10 @@ def add_holes( random_state=random_state, ) - generator.dict_probas_out = { - column: 1 / mean_size for column in df.columns - } - generator.dict_ratios = { - column: 1 / len(df.columns) for column in df.columns - } + generator.dict_probas_out = {column: 1 / mean_size for column in df.columns} + generator.dict_ratios = {column: 1 / len(df.columns) for column in df.columns} if generator.groups: - mask = df.groupby(groups, group_keys=False).apply( - generator.generate_mask - ) + mask = df.groupby(groups, group_keys=False).apply(generator.generate_mask) else: mask = generator.generate_mask(df) @@ -483,9 +451,7 @@ def add_station_features(df: pd.DataFrame) -> pd.DataFrame: return df -def add_datetime_features( - df: pd.DataFrame, col_time: str = "datetime" -) -> pd.DataFrame: +def add_datetime_features(df: pd.DataFrame, col_time: str = "datetime") -> pd.DataFrame: """Create a seasonal feature in the dataset with a cosine function. Parameters @@ -504,9 +470,7 @@ def add_datetime_features( df = df.copy() time = df.index.get_level_values(col_time).to_series() days_in_year = time.dt.year.apply( - lambda x: ( - 366 if ((x % 4 == 0) and (x % 100 != 0)) or (x % 400 == 0) else 365 - ) + lambda x: (366 if ((x % 4 == 0) and (x % 100 != 0)) or (x % 400 == 0) else 365) ) ratio = time.dt.dayofyear.values / days_in_year.values df["time_cos"] = np.cos(2 * np.pi * ratio) @@ -554,29 +518,21 @@ def convert_tsf_to_dataframe( line_content = line.split(" ") if line.startswith("@attribute"): if len(line_content) != 3: - raise Exception( - "Invalid meta-data specification." - ) + raise Exception("Invalid meta-data specification.") col_names.append(line_content[1]) col_types.append(line_content[2]) else: if len(line_content) != 2: - raise Exception( - "Invalid meta-data specification." - ) + raise Exception("Invalid meta-data specification.") else: if len(col_names) == 0: - raise Exception( - "Attribute section must come before data." - ) + raise Exception("Attribute section must come before data.") found_data_tag = True elif not line.startswith("#"): if len(col_names) == 0: - raise Exception( - " Attribute section must come before data." - ) + raise Exception(" Attribute section must come before data.") elif not found_data_tag: raise Exception("Missing @data tag.") else: @@ -591,35 +547,25 @@ def convert_tsf_to_dataframe( full_info = line.split(":") if len(full_info) != (len(col_names) + 1): - raise Exception( - "Missing attributes/values in series." - ) + raise Exception("Missing attributes/values in series.") series = full_info[len(full_info) - 1] series = series.split(",") # type: ignore if len(series) == 0: - raise Exception( - " Missing values should be indicated " - "with ? symbol" - ) + raise Exception(" Missing values should be indicated " "with ? symbol") numeric_series = [] for val in series: if val == "?": - numeric_series.append( - replace_missing_vals_with - ) + numeric_series.append(replace_missing_vals_with) else: numeric_series.append(float(val)) # type: ignore - if numeric_series.count( - replace_missing_vals_with - ) == len(numeric_series): + if numeric_series.count(replace_missing_vals_with) == len(numeric_series): raise Exception( - "At least one numeric value should be " - "there in a series." + "At least one numeric value should be " "there in a series." ) all_series.append(pd.Series(numeric_series).array) diff --git a/qolmat/utils/exceptions.py b/qolmat/utils/exceptions.py index baddfb38..d0cfd465 100644 --- a/qolmat/utils/exceptions.py +++ b/qolmat/utils/exceptions.py @@ -37,19 +37,14 @@ class SubsetIsAString(Exception): """Raise an error when the subset is a string.""" def __init__(self, subset: Any): - super().__init__( - f"Provided subset `{subset}` should be None or a list!" - ) + super().__init__(f"Provided subset `{subset}` should be None or a list!") class NotDimension2(Exception): """Raise an error when the matrix is not of dim 2.""" def __init__(self, shape: Tuple[int, ...]): - super().__init__( - f"Provided matrix is of shape {shape}, " - "which is not of dimension 2!" - ) + super().__init__(f"Provided matrix is of shape {shape}, " "which is not of dimension 2!") class NotDataFrame(Exception): @@ -74,18 +69,14 @@ class EstimatorNotDefined(Exception): """Raise an error when the estimator is not defined.""" def __init__(self): - super().__init__( - "The underlying estimator should be defined beforehand!" - ) + super().__init__("The underlying estimator should be defined beforehand!") class SingleSample(Exception): """Raise an error when there is a single sample.""" def __init__(self): - super().__init__( - """This imputer cannot be fitted on a single sample!""" - ) + super().__init__("""This imputer cannot be fitted on a single sample!""") class IllConditioned(Exception): @@ -105,7 +96,4 @@ class TypeNotHandled(Exception): """Raise an error when the type is not handled.""" def __init__(self, col: str, type_col: str): - super().__init__( - f"The column `{col}` is of type `{type_col}`, " - "which is not handled!" - ) + super().__init__(f"The column `{col}` is of type `{type_col}`, " "which is not handled!") diff --git a/qolmat/utils/input_check.py b/qolmat/utils/input_check.py index c6788624..6e489eda 100644 --- a/qolmat/utils/input_check.py +++ b/qolmat/utils/input_check.py @@ -26,9 +26,7 @@ def is_allowed_type(dtype): return any(check(dtype) for check in allowed_types) invalid_columns = [ - (col, dtype) - for col, dtype in df.dtypes.items() - if not is_allowed_type(dtype) + (col, dtype) for col, dtype in df.dtypes.items() if not is_allowed_type(dtype) ] if invalid_columns: for column_name, dtype in invalid_columns: diff --git a/qolmat/utils/plot.py b/qolmat/utils/plot.py index cead6d7a..f78dbbd1 100644 --- a/qolmat/utils/plot.py +++ b/qolmat/utils/plot.py @@ -22,9 +22,7 @@ tab10 = plt.get_cmap("tab10") -def plot_matrices( - list_matrices: List[np.ndarray], title: Optional[str] = None -) -> None: +def plot_matrices(list_matrices: List[np.ndarray], title: Optional[str] = None) -> None: """Plot RPCA matrices. Parameters @@ -191,16 +189,11 @@ def make_ellipses( pearson = cov[0, 1] / np.sqrt(cov[0, 0] * cov[1, 1]) ell_radius_x = np.sqrt(1 + pearson) * 2.5 ell_radius_y = np.sqrt(1 - pearson) * 2.5 - ell = mpl.patches.Ellipse( - (0, 0), width=ell_radius_x, height=ell_radius_y, facecolor=color - ) + ell = mpl.patches.Ellipse((0, 0), width=ell_radius_x, height=ell_radius_y, facecolor=color) scale_x = np.sqrt(cov[0, 0]) * n_std scale_y = np.sqrt(cov[1, 1]) * n_std transf = ( - mpl.transforms.Affine2D() - .rotate_deg(45) - .scale(scale_x, scale_y) - .translate(mean_x, mean_y) + mpl.transforms.Affine2D().rotate_deg(45).scale(scale_x, scale_y).translate(mean_x, mean_y) ) ell.set_transform(transf + ax.transData) ax.add_patch(ell) @@ -371,9 +364,7 @@ def multibar( plt.legend(loc=(1, 0)) -def plot_imputations( - df: pd.DataFrame, dict_df_imputed: Dict[str, pd.DataFrame] -): +def plot_imputations(df: pd.DataFrame, dict_df_imputed: Dict[str, pd.DataFrame]): """Plot original and imputed dataframes for each imputers. Parameters @@ -397,9 +388,7 @@ def plot_imputations( plt.plot(values_orig, ".", color="black", label="original") values_imp = df_imputed[col].copy() values_imp[values_orig.notna()] = np.nan - plt.plot( - values_imp, ".", color=tab10(0), label=name_imputer, alpha=1 - ) + plt.plot(values_imp, ".", color=tab10(0), label=name_imputer, alpha=1) plt.ylabel(col, fontsize=16) if i_plot % n_columns == 0: plt.legend(loc=[1, 0], fontsize=18) diff --git a/qolmat/utils/utils.py b/qolmat/utils/utils.py index d0b7e4df..ae2151e6 100644 --- a/qolmat/utils/utils.py +++ b/qolmat/utils/utils.py @@ -55,9 +55,7 @@ def _get_categorical_features(df1: pd.DataFrame) -> List[str]: """ cols_numerical = df1.select_dtypes(include=np.number).columns.tolist() - cols_categorical = [ - col for col in df1.columns.to_list() if col not in cols_numerical - ] + cols_categorical = [col for col in df1.columns.to_list() if col not in cols_numerical] return cols_categorical @@ -122,9 +120,7 @@ def check_dtypes(X: pd.DataFrame): >>> import numpy as np >>> import pandas as pd >>> check_dtypes(np.array([1, 2.0, "three"])) - >>> check_dtypes( - ... pd.DataFrame({"col1": [1, 2.0], "col2": ["three", "four"]}) - ... ) + >>> check_dtypes(pd.DataFrame({"col1": [1, 2.0], "col2": ["three", "four"]})) >>> check_dtypes(np.array([1, 2.0, None])) Traceback (most recent call last): ... @@ -170,9 +166,7 @@ def progress_bar( bar fill character, by default "█" """ - percent = ("{0:." + str(decimals) + "f}").format( - 100 * (iteration / float(total)) - ) + percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total))) filled_length = int(length * iteration // total) bar = fill * filled_length + "-" * (length - filled_length) print(f"\r{prefix} |{bar}| {percent}% {suffix}", end="\r") @@ -231,13 +225,9 @@ def impute_nans(M: NDArray, method: str = "zeros") -> NDArray: isna = np.isnan(values) nna = np.sum(isna) if method == "mean": - value_imputation = ( - np.nanmean(M) if nna == n_rows else np.nanmean(values) - ) + value_imputation = np.nanmean(M) if nna == n_rows else np.nanmean(values) elif method == "median": - value_imputation = ( - np.nanmedian(M) if nna == n_rows else np.nanmedian(values) - ) + value_imputation = np.nanmedian(M) if nna == n_rows else np.nanmedian(values) elif method == "zeros": value_imputation = 0 else: diff --git a/tests/analysis/test_holes_characterization.py b/tests/analysis/test_holes_characterization.py index 1c1832af..64642cb3 100644 --- a/tests/analysis/test_holes_characterization.py +++ b/tests/analysis/test_holes_characterization.py @@ -14,9 +14,7 @@ @pytest.fixture def mcar_df() -> pd.DataFrame: rng = sku.check_random_state(42) - matrix = rng.multivariate_normal( - mean=[0, 0], cov=[[1, 0], [0, 1]], size=200 - ) + matrix = rng.multivariate_normal(mean=[0, 0], cov=[[1, 0], [0, 1]], size=200) df = pd.DataFrame(data=matrix, columns=["Column_1", "Column_2"]) hole_gen = UniformHoleGenerator( n_splits=1, random_state=42, subset=["Column_2"], ratio_masked=0.2 @@ -28,9 +26,7 @@ def mcar_df() -> pd.DataFrame: @pytest.fixture def mar_hm_df() -> pd.DataFrame: rng = sku.check_random_state(42) - matrix = rng.multivariate_normal( - mean=[0, 0], cov=[[1, 0], [0, 1]], size=200 - ) + matrix = rng.multivariate_normal(mean=[0, 0], cov=[[1, 0], [0, 1]], size=200) quantile_95 = norm.ppf(0.975) df = pd.DataFrame(matrix, columns=["Column_1", "Column_2"]) @@ -44,9 +40,7 @@ def mar_hm_df() -> pd.DataFrame: @pytest.fixture def mar_hc_df() -> pd.DataFrame: rng = sku.check_random_state(42) - matrix = rng.multivariate_normal( - mean=[0, 0], cov=[[1, 0], [0, 1]], size=200 - ) + matrix = rng.multivariate_normal(mean=[0, 0], cov=[[1, 0], [0, 1]], size=200) quantile_95 = norm.ppf(0.975) df = pd.DataFrame(matrix, columns=["Column_1", "Column_2"]) @@ -116,9 +110,7 @@ def oob_probabilities() -> np.ndarray: def test__encode_dataframe(supported_multitypes_dataframe): mcar_test_pklm = PKLMTest(random_state=42) - np_dataframe = mcar_test_pklm._encode_dataframe( - supported_multitypes_dataframe - ) + np_dataframe = mcar_test_pklm._encode_dataframe(supported_multitypes_dataframe) n_rows, n_cols = np_dataframe.shape assert n_rows == 3 assert n_cols == 7 @@ -127,10 +119,8 @@ def test__encode_dataframe(supported_multitypes_dataframe): def test__draw_features_and_target_indexes(np_matrix_with_nan_mcar): mcar_test_pklm = PKLMTest(random_state=42) _, p = np_matrix_with_nan_mcar.shape - features_idx, target_idx = ( - mcar_test_pklm._draw_features_and_target_indexes( - np_matrix_with_nan_mcar - ) + features_idx, target_idx = mcar_test_pklm._draw_features_and_target_indexes( + np_matrix_with_nan_mcar ) assert isinstance(target_idx, np.integer) assert isinstance(features_idx, list) @@ -147,9 +137,7 @@ def test__draw_features_and_target_indexes(np_matrix_with_nan_mcar): ("np_matrix_with_nan_mcar", np.array([1, 0, 2]), 3, False), ], ) -def test__check_draw( - request, dataframe_fixture, features_idx, target_idx, expected -): +def test__check_draw(request, dataframe_fixture, features_idx, target_idx, expected): dataframe = request.getfixturevalue(dataframe_fixture) mcar_test_pklm = PKLMTest() result = mcar_test_pklm._check_draw(dataframe, features_idx, target_idx) @@ -203,15 +191,11 @@ def test__build_dataset(request, dataframe_fixture, features_idx, target_idx): ), ], ) -def test__build_label( - request, dataframe_fixture, permutation_fixture, features_idx, target_idx -): +def test__build_label(request, dataframe_fixture, permutation_fixture, features_idx, target_idx): dataframe = request.getfixturevalue(dataframe_fixture) m_perm = request.getfixturevalue(permutation_fixture) mcar_test_pklm = PKLMTest() - label = mcar_test_pklm._build_label( - dataframe, m_perm, features_idx, target_idx - ) + label = mcar_test_pklm._build_label(dataframe, m_perm, features_idx, target_idx) assert not np.any(np.isnan(label)) assert len(label.shape) == 1 assert np.isin(label, [0, 1]).all() diff --git a/tests/benchmark/test_comparator.py b/tests/benchmark/test_comparator.py index ef870c0c..ffa28ab0 100644 --- a/tests/benchmark/test_comparator.py +++ b/tests/benchmark/test_comparator.py @@ -49,9 +49,7 @@ def comparator(generator_holes_mock: _HoleGenerator) -> Comparator: def expected_get_errors() -> pd.Series: return pd.Series( [1.0, 1.0, 1.0, 1.0], - index=pd.MultiIndex.from_tuples( - [("mae", "A"), ("mae", "B"), ("mse", "A"), ("mse", "B")] - ), + index=pd.MultiIndex.from_tuples([("mae", "A"), ("mae", "B"), ("mse", "A"), ("mse", "B")]), ) @@ -73,9 +71,7 @@ def df_mask() -> pd.DataFrame: @pytest.fixture def imputers_mock(mocker: MockerFixture) -> Dict[str, Any]: imputer_mock = mocker.MagicMock() - imputer_mock.fit_transform.return_value = pd.DataFrame( - {"A": [1, 2, 3], "B": [4, 5, 6]} - ) + imputer_mock.fit_transform.return_value = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]}) return {"imputer_1": imputer_mock} @@ -101,10 +97,8 @@ def test_get_errors( df_mask: pd.DataFrame, ) -> None: mock_get_metric = mocker.patch("qolmat.benchmark.metrics.get_metric") - mock_get_metric.return_value = ( - lambda df_origin, df_imputed, df_mask: pd.Series( - [1.0, 1.0], index=["A", "B"] - ) + mock_get_metric.return_value = lambda df_origin, df_imputed, df_mask: pd.Series( + [1.0, 1.0], index=["A", "B"] ) errors = comparator.get_errors(df_origin, df_imputed, df_mask) pd.testing.assert_series_equal(errors, expected_get_errors) @@ -124,9 +118,7 @@ def test_process_split( comparator.max_evals = 100 comparator.verbose = False - mock_optimize = mocker.patch( - "qolmat.benchmark.comparator.hyperparameters.optimize" - ) + mock_optimize = mocker.patch("qolmat.benchmark.comparator.hyperparameters.optimize") mock_optimize.return_value = imputers_mock["imputer_1"] split_data = (0, df_mask, df_origin) df_with_holes = df_origin.copy() @@ -165,9 +157,7 @@ def test_process_imputer( comparator.metric_optim = "mae" comparator.max_evals = 100 comparator.verbose = False - mock_optimize = mocker.patch( - "qolmat.benchmark.comparator.hyperparameters.optimize" - ) + mock_optimize = mocker.patch("qolmat.benchmark.comparator.hyperparameters.optimize") mock_optimize.return_value = imputers_mock["imputer_1"] mock_get_errors = mocker.patch.object(comparator, "get_errors") mock_get_errors.side_effect = [ @@ -202,9 +192,7 @@ def test_process_imputer( max_evals=comparator.max_evals, verbose=comparator.verbose, ) - assert imputers_mock["imputer_1"].fit_transform.call_count == len( - all_masks - ) + assert imputers_mock["imputer_1"].fit_transform.call_count == len(all_masks) assert mock_get_errors.call_count == len(all_masks) @@ -230,9 +218,7 @@ def test_compare_parallel_splits( index=pd.MultiIndex.from_tuples([("mae", "A"), ("mae", "B")]), ), ] - mock_get_optimal_n_jobs = mocker.patch.object( - comparator, "get_optimal_n_jobs" - ) + mock_get_optimal_n_jobs = mocker.patch.object(comparator, "get_optimal_n_jobs") mock_get_optimal_n_jobs.return_value = 1 expected_result = pd.Series( @@ -275,9 +261,7 @@ def test_compare_sequential_splits( index=pd.MultiIndex.from_tuples([("mae", "A"), ("mae", "B")]), ) with caplog.at_level(logging.INFO): - result = comparator.compare( - df_origin, use_parallel=False, parallel_over="splits" - ) + result = comparator.compare(df_origin, use_parallel=False, parallel_over="splits") pd.testing.assert_series_equal(result, expected_result) assert mock_process_split.call_count == 2 assert "Starting comparison for" in caplog.text @@ -316,9 +300,7 @@ def test_compare_parallel_imputers( ), ), ] - mock_get_optimal_n_jobs = mocker.patch.object( - comparator, "get_optimal_n_jobs" - ) + mock_get_optimal_n_jobs = mocker.patch.object(comparator, "get_optimal_n_jobs") mock_get_optimal_n_jobs.return_value = 1 expected_result = pd.concat( @@ -335,9 +317,7 @@ def test_compare_parallel_imputers( axis=1, ) with caplog.at_level(logging.INFO): - result = comparator.compare( - df_origin, use_parallel=True, parallel_over="imputers" - ) + result = comparator.compare(df_origin, use_parallel=True, parallel_over="imputers") pd.testing.assert_frame_equal(result, expected_result) assert mock_process_imputer.call_count == 2 assert mock_get_optimal_n_jobs.call_count == 1 @@ -391,9 +371,7 @@ def test_compare_sequential_imputers( axis=1, ) with caplog.at_level(logging.INFO): - result = comparator.compare( - df_origin, use_parallel=False, parallel_over="imputers" - ) + result = comparator.compare(df_origin, use_parallel=False, parallel_over="imputers") pd.testing.assert_frame_equal(result, expected_result) assert mock_process_imputer.call_count == 2 assert "Starting comparison for" in caplog.text @@ -430,18 +408,12 @@ def test_compare_reproducibility(): "shuffle2": ImputerShuffle(random_state=seed), } cols = ["A", "B"] - df_data = pd.DataFrame( - np.random.random((100, 2)), dtype=float, columns=cols - ) - generator_holes = UniformHoleGenerator( - n_splits=2, subset=cols, ratio_masked=0.5 - ) + df_data = pd.DataFrame(np.random.random((100, 2)), dtype=float, columns=cols) + generator_holes = UniformHoleGenerator(n_splits=2, subset=cols, ratio_masked=0.5) comparator = Comparator( dict_models=dict_models, generator_holes=generator_holes, metrics=["mae", "mse"], ) df_errors = comparator.compare(df_data) - pd.testing.assert_series_equal( - df_errors["shuffle1"], df_errors["shuffle2"], check_names=False - ) + pd.testing.assert_series_equal(df_errors["shuffle1"], df_errors["shuffle2"], check_names=False) diff --git a/tests/benchmark/test_hyperparameters.py b/tests/benchmark/test_hyperparameters.py index d9b69ff6..8bf72584 100644 --- a/tests/benchmark/test_hyperparameters.py +++ b/tests/benchmark/test_hyperparameters.py @@ -14,12 +14,8 @@ from qolmat.imputations.imputers import ImputerRpcaNoisy, _Imputer from qolmat.utils.utils import RandomSetting -df_origin = pd.DataFrame( - {"col1": [0, np.nan, 2, 4, np.nan], "col2": [-1, np.nan, 0.5, 1, 1.5]} -) -df_imputed = pd.DataFrame( - {"col1": [0, 1, 2, 3.5, 4], "col2": [-1.5, 0, 1.5, 2, 1.5]} -) +df_origin = pd.DataFrame({"col1": [0, np.nan, 2, 4, np.nan], "col2": [-1, np.nan, 0.5, 1, 1.5]}) +df_imputed = pd.DataFrame({"col1": [0, 1, 2, 3.5, 4], "col2": [-1.5, 0, 1.5, 2, 1.5]}) df_mask = pd.DataFrame( { "col1": [False, False, True, False, False], @@ -29,9 +25,7 @@ df_corrupted = df_origin.copy() df_corrupted[df_mask] = np.nan -imputer_rpca = ImputerRpcaNoisy( - tau=2, random_state=42, columnwise=True, period=1 -) +imputer_rpca = ImputerRpcaNoisy(tau=2, random_state=42, columnwise=True, period=1) dict_imputers_rpca = {"rpca": imputer_rpca} generator_holes = EmpiricalHoleGenerator(n_splits=1, ratio_masked=0.5) dict_config_opti = { @@ -57,14 +51,10 @@ def __init__( value: float = 0, ) -> None: """Init function.""" - super().__init__( - groups=groups, columnwise=True, random_state=random_state - ) + super().__init__(groups=groups, columnwise=True, random_state=random_state) self.value = value - def _transform_element( - self, df: pd.DataFrame, col: str = "__all__", ngroup: int = 0 - ): + def _transform_element(self, df: pd.DataFrame, col: str = "__all__", ngroup: int = 0): df_out = df.copy() df_out = df_out.fillna(self.value) return df_out @@ -89,15 +79,11 @@ def generate_mask(self, X: pd.DataFrame) -> pd.DataFrame: def test_hyperparameters_get_objective() -> None: """Test get_objective.""" imputer = ImputerTest() - generator = HoleGeneratorTest( - pd.Series([False, False, True, True]), subset=["some_col"] - ) + generator = HoleGeneratorTest(pd.Series([False, False, True, True]), subset=["some_col"]) metric = "mse" names_hyperparams = ["value"] df = pd.DataFrame({"some_col": [np.nan, 0, 3, 5]}) - fun_obj = hyperparameters.get_objective( - imputer, df, generator, metric, names_hyperparams - ) + fun_obj = hyperparameters.get_objective(imputer, df, generator, metric, names_hyperparams) assert fun_obj([4]) == 1 assert fun_obj([0]) == (3**2 + 5**2) / 2 @@ -105,9 +91,7 @@ def test_hyperparameters_get_objective() -> None: def test_hyperparameters_optimize(): """Test optimize.""" imputer = ImputerTest() - generator = HoleGeneratorTest( - pd.Series([False, False, True, True]), subset=["some_col"] - ) + generator = HoleGeneratorTest(pd.Series([False, False, True, True]), subset=["some_col"]) metric = "mse" dict_config_opti = {"value": ho.hp.uniform("value", 0, 10)} df = pd.DataFrame({"some_col": [np.nan, 0, 3, 5]}) diff --git a/tests/benchmark/test_metrics.py b/tests/benchmark/test_metrics.py index 001a9267..fefbf8d9 100644 --- a/tests/benchmark/test_metrics.py +++ b/tests/benchmark/test_metrics.py @@ -15,13 +15,9 @@ {"col1": [0, np.nan, 2, 3, np.nan], "col2": [-1, np.nan, 0.5, 1, 1.5]} ) -df_complete = pd.DataFrame( - {"col1": [0, 2, 2, 3, 4], "col2": [-1, -2, 0.5, 1, 1.5]} -) +df_complete = pd.DataFrame({"col1": [0, 2, 2, 3, 4], "col2": [-1, -2, 0.5, 1, 1.5]}) -df_imputed = pd.DataFrame( - {"col1": [0, 1, 2, 3.5, 4], "col2": [-1.5, 0, 1.5, 2, 1.5]} -) +df_imputed = pd.DataFrame({"col1": [0, 1, 2, 3.5, 4], "col2": [-1.5, 0, 1.5, 2, 1.5]}) df_mask = pd.DataFrame( { @@ -34,9 +30,7 @@ @pytest.mark.parametrize("df1", [df_incomplete]) @pytest.mark.parametrize("df2", [df_imputed]) @pytest.mark.parametrize("df_mask", [df_mask]) -def test_mean_squared_error( - df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame -) -> None: +def test_mean_squared_error(df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame) -> None: assert metrics.mean_squared_error(df1, df1, df_mask).equals( pd.Series([0.0, 0.0], index=["col1", "col2"]) ) @@ -64,9 +58,7 @@ def test_root_mean_squared_error( @pytest.mark.parametrize("df1", [df_incomplete]) @pytest.mark.parametrize("df2", [df_imputed]) @pytest.mark.parametrize("df_mask", [df_mask]) -def test_mean_absolute_error( - df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame -) -> None: +def test_mean_absolute_error(df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame) -> None: assert metrics.mean_absolute_error(df1, df1, df_mask).equals( pd.Series([0.0, 0.0], index=["col1", "col2"]) ) @@ -97,9 +89,9 @@ def test_mean_absolute_percentage_error( def test_weighted_mean_absolute_percentage_error( df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame ) -> None: - assert metrics.weighted_mean_absolute_percentage_error( - df1, df1, df_mask - ).equals(pd.Series([0.0, 0.0], index=["col1", "col2"])) + assert metrics.weighted_mean_absolute_percentage_error(df1, df1, df_mask).equals( + pd.Series([0.0, 0.0], index=["col1", "col2"]) + ) result = metrics.weighted_mean_absolute_percentage_error(df1, df2, df_mask) expected = pd.Series([0.1, 1.0], index=["col1", "col2"]) np.testing.assert_allclose(result, expected, atol=1e-3) @@ -108,9 +100,7 @@ def test_weighted_mean_absolute_percentage_error( @pytest.mark.parametrize("df1", [df_incomplete]) @pytest.mark.parametrize("df2", [df_imputed]) @pytest.mark.parametrize("df_mask", [df_mask]) -def test_accuracy( - df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame -) -> None: +def test_accuracy(df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame) -> None: result = metrics.accuracy(df1, df1, df_mask) expected = pd.Series([1.0, 1.0], index=["col1", "col2"]) pd.testing.assert_series_equal(result, expected) @@ -122,23 +112,17 @@ def test_accuracy( @pytest.mark.parametrize("df1", [df_incomplete]) @pytest.mark.parametrize("df2", [df_imputed]) @pytest.mark.parametrize("df_mask", [df_mask]) -def test_wasserstein_distance( - df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame -) -> None: +def test_wasserstein_distance(df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame) -> None: dist = metrics.dist_wasserstein(df1, df1, df_mask, method="columnwise") assert dist.equals(pd.Series([0.0, 0.0], index=["col1", "col2"])) dist = metrics.dist_wasserstein(df1, df2, df_mask, method="columnwise") - assert dist.round(3).equals( - pd.Series([0.250, 0.833], index=["col1", "col2"]) - ) + assert dist.round(3).equals(pd.Series([0.250, 0.833], index=["col1", "col2"])) @pytest.mark.parametrize("df1", [df_incomplete]) @pytest.mark.parametrize("df2", [df_imputed]) @pytest.mark.parametrize("df_mask", [df_mask]) -def test_kl_divergence( - df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame -) -> None: +def test_kl_divergence(df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame) -> None: result = metrics.kl_divergence(df1, df1, df_mask, method="columnwise") expected = pd.Series([0.0, 0.0], index=["col1", "col2"]) pd.testing.assert_series_equal(result, expected, atol=1e-3) @@ -148,9 +132,7 @@ def test_kl_divergence( pd.testing.assert_series_equal(result, expected, atol=1e-3) df_nonan = df1.notna() - result = metrics.kl_divergence( - df1, df2, df_nonan, method="gaussian", min_n_rows=2 - ) + result = metrics.kl_divergence(df1, df2, df_nonan, method="gaussian", min_n_rows=2) expected = pd.Series([1.029], index=["All"]) pd.testing.assert_series_equal(result, expected, atol=1e-3) @@ -211,9 +193,7 @@ def test_sum_pairwise_distances( @pytest.mark.parametrize("df1", [df_incomplete]) @pytest.mark.parametrize("df2", [df_imputed]) @pytest.mark.parametrize("df_mask", [df_mask]) -def test_sum_energy_distances( - df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame -) -> None: +def test_sum_energy_distances(df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame) -> None: sum_distances_df1 = np.sum( scipy.spatial.distance.cdist( df1[df_mask].fillna(0.0), @@ -235,14 +215,10 @@ def test_sum_energy_distances( metric="cityblock", ) ) - energy_distance_scipy = ( - 2 * sum_distances_df1_df2 - sum_distances_df1 - sum_distances_df2 - ) + energy_distance_scipy = 2 * sum_distances_df1_df2 - sum_distances_df1 - sum_distances_df2 energy_distance_qolmat = metrics.sum_energy_distances(df1, df2, df_mask) - assert energy_distance_qolmat.equals( - pd.Series(energy_distance_scipy, index=["All"]) - ) + assert energy_distance_qolmat.equals(pd.Series(energy_distance_scipy, index=["All"])) @pytest.mark.parametrize("df1", [df_incomplete]) @@ -251,16 +227,16 @@ def test_sum_energy_distances( def test_mean_difference_correlation_matrix_numerical_features( df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame ) -> None: - assert metrics.mean_difference_correlation_matrix_numerical_features( - df1, df1, df_mask - ).equals(pd.Series([0.0, 0.0], index=["col1", "col2"])) + assert metrics.mean_difference_correlation_matrix_numerical_features(df1, df1, df_mask).equals( + pd.Series([0.0, 0.0], index=["col1", "col2"]) + ) assert metrics.mean_difference_correlation_matrix_numerical_features( df1, df1, df_mask, False ).equals(pd.Series([0.0, 0.0], index=["col1", "col2"])) - assert metrics.mean_difference_correlation_matrix_numerical_features( - df1, df2, df_mask - ).equals(pd.Series([0.0, 0.0], index=["col1", "col2"])) + assert metrics.mean_difference_correlation_matrix_numerical_features(df1, df2, df_mask).equals( + pd.Series([0.0, 0.0], index=["col1", "col2"]) + ) df_incomplete_cat = pd.DataFrame( @@ -360,9 +336,7 @@ def test_exception_raise_different_shapes( df1: pd.DataFrame, df2: pd.DataFrame, df_mask: pd.DataFrame ) -> None: with pytest.raises(Exception): - metrics.mean_difference_correlation_matrix_numerical_features( - df1, df2, df_mask - ) + metrics.mean_difference_correlation_matrix_numerical_features(df1, df2, df_mask) with pytest.raises(Exception): metrics.frechet_distance_base(df1, df2, df_mask) @@ -376,9 +350,7 @@ def test_exception_raise_no_numerical_column_found( with pytest.raises(Exception): metrics.kolmogorov_smirnov_test(df1, df2, df_mask) with pytest.raises(Exception): - metrics.mean_difference_correlation_matrix_numerical_features( - df1, df2, df_mask - ) + metrics.mean_difference_correlation_matrix_numerical_features(df1, df2, df_mask) @pytest.mark.parametrize("df1", [df_incomplete]) @@ -425,19 +397,13 @@ def test_pattern_based_weighted_mean_metric( rng = npr.default_rng(123) -df_gauss1 = pd.DataFrame( - rng.multivariate_normal([0, 0], [[1, 0.2], [0.2, 2]], size=100) -) -df_gauss2 = pd.DataFrame( - rng.multivariate_normal([0, 1], [[1, 0.2], [0.2, 2]], size=100) -) +df_gauss1 = pd.DataFrame(rng.multivariate_normal([0, 0], [[1, 0.2], [0.2, 2]], size=100)) +df_gauss2 = pd.DataFrame(rng.multivariate_normal([0, 1], [[1, 0.2], [0.2, 2]], size=100)) df_mask_gauss = pd.DataFrame(np.full_like(df_gauss1, True)) def test_pattern_mae_comparison(mocker) -> None: - mock_metric = mocker.patch( - "qolmat.benchmark.metrics.accuracy_1D", return_value=0 - ) + mock_metric = mocker.patch("qolmat.benchmark.metrics.accuracy_1D", return_value=0) df_nonan = df_incomplete.notna() metrics.pattern_based_weighted_mean_metric( diff --git a/tests/benchmark/test_missing_patterns.py b/tests/benchmark/test_missing_patterns.py index 4cd29455..fa95a2e8 100644 --- a/tests/benchmark/test_missing_patterns.py +++ b/tests/benchmark/test_missing_patterns.py @@ -4,9 +4,7 @@ from qolmat.benchmark import missing_patterns as mp -df_complet = pd.DataFrame( - {"col1": list(range(100)), "col2": [2 * i for i in range(100)]} -) +df_complet = pd.DataFrame({"col1": list(range(100)), "col2": [2 * i for i in range(100)]}) df_incomplet = df_complet.copy() df_incomplet.iloc[99, :] = np.nan @@ -22,15 +20,9 @@ df_incomplet_group.index = df_incomplet_group.index.set_names("group") list_generators = { - "geo": mp.GeometricHoleGenerator( - n_splits=2, ratio_masked=0.1, random_state=42 - ), - "unif": mp.UniformHoleGenerator( - n_splits=2, ratio_masked=0.1, random_state=42 - ), - "multi": mp.MultiMarkovHoleGenerator( - n_splits=2, ratio_masked=0.1, random_state=42 - ), + "geo": mp.GeometricHoleGenerator(n_splits=2, ratio_masked=0.1, random_state=42), + "unif": mp.UniformHoleGenerator(n_splits=2, ratio_masked=0.1, random_state=42), + "multi": mp.MultiMarkovHoleGenerator(n_splits=2, ratio_masked=0.1, random_state=42), "group": mp.GroupedHoleGenerator( n_splits=2, ratio_masked=0.1, random_state=42, groups=("group",) ), @@ -46,9 +38,7 @@ (df_incomplet_group, list_generators["group"]), ], ) -def test_SamplerHoleGenerator_split( - df: pd.DataFrame, generator: mp._HoleGenerator -) -> None: +def test_SamplerHoleGenerator_split(df: pd.DataFrame, generator: mp._HoleGenerator) -> None: mask = generator.split(df)[0] col1_holes = mask["col1"].sum() col2_holes = mask["col2"].sum() @@ -67,9 +57,7 @@ def test_SamplerHoleGenerator_split( (df_incomplet_group, list_generators["group"]), ], ) -def test_SamplerHoleGenerator_reproducible( - df: pd.DataFrame, generator: mp._HoleGenerator -) -> None: +def test_SamplerHoleGenerator_reproducible(df: pd.DataFrame, generator: mp._HoleGenerator) -> None: generator.random_state = 42 mask1 = generator.split(df)[0] generator.random_state = 43 @@ -93,9 +81,7 @@ def test_SamplerHoleGenerator_reproducible( def test_SamplerHoleGenerator_without_real_nans( df: pd.DataFrame, generator: mp._HoleGenerator ) -> None: - real_nan = np.random.choice( - [True, False], size=df.size, p=[0.4, 0.6] - ).reshape(100, 2) + real_nan = np.random.choice([True, False], size=df.size, p=[0.4, 0.6]).reshape(100, 2) df[real_nan] = np.nan mask = generator.split(df)[0] @@ -106,9 +92,5 @@ def test_SamplerHoleGenerator_without_real_nans( loc_real_nans_col2 = np.where(df["col2"].isna())[0] loc_mask_col2 = np.where(mask["col2"])[0] - np.testing.assert_allclose( - len(set(loc_real_nans_col1) & set(loc_mask_col1)), 0 - ) - np.testing.assert_allclose( - len(set(loc_real_nans_col2) & set(loc_mask_col2)), 0 - ) + np.testing.assert_allclose(len(set(loc_real_nans_col1) & set(loc_mask_col1)), 0) + np.testing.assert_allclose(len(set(loc_real_nans_col2) & set(loc_mask_col2)), 0) diff --git a/tests/imputations/rpca/test_rpca_noisy.py b/tests/imputations/rpca/test_rpca_noisy.py index d20aeaba..2515c696 100644 --- a/tests/imputations/rpca/test_rpca_noisy.py +++ b/tests/imputations/rpca/test_rpca_noisy.py @@ -56,9 +56,7 @@ def test_check_cost_function_minimized_warning( ): """Test warning when the cost function is not minimized.""" with pytest.warns(UserWarning): - RpcaNoisy()._check_cost_function_minimized( - obs, lr, ano, omega, lam, tau - ) + RpcaNoisy()._check_cost_function_minimized(obs, lr, ano, omega, lam, tau) @pytest.mark.parametrize( @@ -86,9 +84,7 @@ def test_check_cost_function_minimized_no_warning( ): """Test no warning when the cost function is minimized.""" with warnings.catch_warnings(record=True) as record: - RpcaNoisy()._check_cost_function_minimized( - obs, lr, ano, omega, lam, tau - ) + RpcaNoisy()._check_cost_function_minimized(obs, lr, ano, omega, lam, tau) assert len(record) == 0 @@ -111,9 +107,7 @@ def test_rpca_decompose_rpca_shape(norm: str): rank = 2 rpca = RpcaNoisy(rank=rank, norm=norm) Omega = ~np.isnan(X_test) - M_result, A_result, L_result, Q_result = rpca.decompose_with_basis( - X_test, Omega - ) + M_result, A_result, L_result, Q_result = rpca.decompose_with_basis(X_test, Omega) n_rows, n_cols = X_test.shape assert M_result.shape == (n_rows, n_cols) assert A_result.shape == (n_rows, n_cols) @@ -148,9 +142,7 @@ def test_rpca_noisy_zero_tau(X: NDArray, lam: float, X_interpolated: NDArray): "X, tau, X_interpolated", [(X_incomplete, 0.4, X_interpolated), (X_incomplete, 2.4, X_interpolated)], ) -def test_rpca_noisy_zero_lambda( - X: NDArray, tau: float, X_interpolated: NDArray -): +def test_rpca_noisy_zero_lambda(X: NDArray, tau: float, X_interpolated: NDArray): """Test RPCA noisy results if lambda equals zero.""" rpca = RpcaNoisy(tau=tau, lam=0, norm="L2") Omega = ~np.isnan(X) @@ -175,16 +167,10 @@ def test_rpca_noisy_decompose_rpca(synthetic_temporal_data): low_rank_init = D anomalies_init = np.zeros(D.shape) - cost_init = RpcaNoisy.cost_function( - D, low_rank_init, anomalies_init, Omega, tau, lam - ) + cost_init = RpcaNoisy.cost_function(D, low_rank_init, anomalies_init, Omega, tau, lam) - X_result, A_result, _, _ = RpcaNoisy.minimise_loss( - D, Omega, rank, tau, lam - ) - cost_result = RpcaNoisy.cost_function( - D, X_result, A_result, Omega, tau, lam - ) + X_result, A_result, _, _ = RpcaNoisy.minimise_loss(D, Omega, rank, tau, lam) + cost_result = RpcaNoisy.cost_function(D, X_result, A_result, Omega, tau, lam) assert cost_result <= cost_init diff --git a/tests/imputations/rpca/test_rpca_pcp.py b/tests/imputations/rpca/test_rpca_pcp.py index de997d90..a32f661b 100644 --- a/tests/imputations/rpca/test_rpca_pcp.py +++ b/tests/imputations/rpca/test_rpca_pcp.py @@ -134,6 +134,6 @@ def test_rpca_temporal_signal(synthetic_temporal_data): Omega = ~np.isnan(D) D_interpolated = utils.linear_interpolation(D) X_result, A_result = rpca.decompose(D, Omega) - assert np.linalg.norm(D_interpolated, "nuc") >= np.linalg.norm( - X_result, "nuc" - ) + lam * np.sum(np.abs(A_result)) + assert np.linalg.norm(D_interpolated, "nuc") >= np.linalg.norm(X_result, "nuc") + lam * np.sum( + np.abs(A_result) + ) diff --git a/tests/imputations/rpca/test_rpca_utils.py b/tests/imputations/rpca/test_rpca_utils.py index 120bff83..3568f75b 100644 --- a/tests/imputations/rpca/test_rpca_utils.py +++ b/tests/imputations/rpca/test_rpca_utils.py @@ -20,9 +20,7 @@ ] ) -X_complete = np.array( - [[1, 7, 4, 4], [5, 2, 4, 4], [-3, 3, 3, 3], [2, -1, 5, 5], [2, 1, 5, 5]] -) +X_complete = np.array([[1, 7, 4, 4], [5, 2, 4, 4], [-3, 3, 3, 3], [2, -1, 5, 5], [2, 1, 5, 5]]) @pytest.mark.parametrize("X", [X_complete]) diff --git a/tests/imputations/test_em_sampler.py b/tests/imputations/test_em_sampler.py index 16b5c674..ce75cc04 100644 --- a/tests/imputations/test_em_sampler.py +++ b/tests/imputations/test_em_sampler.py @@ -14,9 +14,7 @@ np.random.seed(42) A: NDArray = np.array([[3, 1, 0], [1, 1, 0], [0, 0, 1]], dtype=float) -A_inverse: NDArray = np.array( - [[0.5, -0.5, 0], [-0.5, 1.5, 0], [0, 0, 1]], dtype=float -) +A_inverse: NDArray = np.array([[0.5, -0.5, 0], [-0.5, 1.5, 0], [0, 0, 1]], dtype=float) X_missing = np.array( [ [1, np.nan, 1], @@ -100,13 +98,9 @@ def test_gradient_conjugate( """Test the conjugate gradient algorithm.""" X_first_guess = utils.impute_nans(X_missing) X_result = em_sampler._conjugate_gradient(A, X_first_guess, mask) - X_expected = np.array( - [[1, -1, 1], [2, -2, 3], [1, 4, 0], [-1, 2, 1], [1, 1, 0]], dtype=float - ) + X_expected = np.array([[1, -1, 1], [2, -2, 3], [1, 4, 0], [-1, 2, 1], [1, 1, 0]], dtype=float) - assert np.sum(X_result * (X_result @ A)) <= np.sum( - X_first_guess * (X_first_guess @ A) - ) + assert np.sum(X_result * (X_result @ A)) <= np.sum(X_first_guess * (X_first_guess @ A)) assert np.allclose(X_missing[~mask], X_result[~mask]) assert ((X_result @ A)[mask] == 0).all() np.testing.assert_allclose(X_result, X_expected, atol=1e-5) @@ -246,9 +240,7 @@ def test_sample_ou_2d(model): assert abs(mean_est - mean_theo) < np.sqrt(var_theo / n_samples) * q_alpha ratio_inf = scipy.stats.chi2.ppf(alpha / 2, n_samples) / (n_samples - 1) - ratio_sup = scipy.stats.chi2.ppf(1 - alpha / 2, n_samples) / ( - n_samples - 1 - ) + ratio_sup = scipy.stats.chi2.ppf(1 - alpha / 2, n_samples) / (n_samples - 1) ratio = var_est / var_theo @@ -298,9 +290,7 @@ def test_varem_sampler_check_convergence_false( def test_illconditioned_multinormalem() -> None: """Test that data with colinearity raises an exception.""" - X = np.array( - [[1, np.nan, 8, 1], [3, 1, 4, 2], [2, 3, np.nan, 1]], dtype=float - ) + X = np.array([[1, np.nan, 8, 1], [3, 1, 4, 2], [2, 3, np.nan, 1]], dtype=float) model = em_sampler.MultiNormalEM() with pytest.warns(UserWarning): _ = model.fit_transform(X) @@ -329,9 +319,7 @@ def test_no_more_nan_varpem() -> None: def test_fit_parameters_multinormalem_no_imputation(): """Test fit MultiNormalEM provides good parameters estimates.""" - X, X_missing, mean, covariance = generate_multinormal_predefined_mean_cov( - d=2, n=10000 - ) + X, X_missing, mean, covariance = generate_multinormal_predefined_mean_cov(d=2, n=10000) em = em_sampler.MultiNormalEM() em.fit_parameters(X) np.testing.assert_allclose(em.means, mean, atol=1e-1) @@ -340,9 +328,7 @@ def test_fit_parameters_multinormalem_no_imputation(): def test_mean_covariance_multinormalem(): """Test MultiNormalEM provides good mean and covariance estimations.""" - X, X_missing, mean, covariance = generate_multinormal_predefined_mean_cov( - d=2, n=1000 - ) + X, X_missing, mean, covariance = generate_multinormal_predefined_mean_cov(d=2, n=1000) em = em_sampler.MultiNormalEM() X_imputed = em.fit_transform(X_missing) @@ -354,9 +340,7 @@ def test_mean_covariance_multinormalem(): np.testing.assert_allclose(em.means, mean, rtol=1e-1, atol=1e-1) np.testing.assert_allclose(em.cov, covariance, rtol=1e-1, atol=1e-1) np.testing.assert_allclose(mean_imputed, mean, rtol=1e-1, atol=1e-1) - np.testing.assert_allclose( - covariance_imputed, covariance, rtol=1e-1, atol=1e-1 - ) + np.testing.assert_allclose(covariance_imputed, covariance, rtol=1e-1, atol=1e-1) def test_multinormal_em_minimize_llik(): @@ -415,9 +399,7 @@ def test_parameters_after_imputation_varpem(p: int): def test_varpem_fit_transform(): imputer = em_sampler.VARpEM(method="mle", random_state=11) - X = np.array( - [[1, 1, 1, 1], [np.nan, np.nan, 3, 2], [1, 2, 2, 1], [2, 2, 2, 2]] - ) + X = np.array([[1, 1, 1, 1], [np.nan, np.nan, 3, 2], [1, 2, 2, 1], [2, 2, 2, 2]]) result = imputer.fit_transform(X) assert result.shape == X.shape np.testing.assert_allclose(result[~np.isnan(X)], X[~np.isnan(X)]) diff --git a/tests/imputations/test_imputers.py b/tests/imputations/test_imputers.py index 5069f0bd..c8ee2818 100644 --- a/tests/imputations/test_imputers.py +++ b/tests/imputations/test_imputers.py @@ -12,9 +12,7 @@ from qolmat.benchmark.hyperparameters import HyperValue from qolmat.imputations import imputers -df_complete = pd.DataFrame( - {"col1": [0, 1, 2, 3, 4], "col2": [-1, 0, 0.5, 1, 1.5]} -) +df_complete = pd.DataFrame({"col1": [0, 1, 2, 3, 4], "col2": [-1, 0, 0.5, 1, 1.5]}) df_incomplete = pd.DataFrame( {"col1": [0, np.nan, 2, 3, np.nan], "col2": [-1, np.nan, 0.5, np.nan, 1.5]} @@ -87,18 +85,14 @@ def test_hyperparameters_get_hyperparameters() -> None: } -@pytest.mark.parametrize( - "col, expected", [("col1", expected1), ("col2", expected2)] -) +@pytest.mark.parametrize("col, expected", [("col1", expected1), ("col2", expected2)]) def test_hyperparameters_get_hyperparameters_modified( col: str, expected: Dict[str, HyperValue] ) -> None: imputer = imputers.ImputerRpcaNoisy() for key, val in hyperparams_global.items(): setattr(imputer, key, val) - imputer.imputer_params = tuple( - set(imputer.imputer_params) | set(hyperparams_global.keys()) - ) + imputer.imputer_params = tuple(set(imputer.imputer_params) | set(hyperparams_global.keys())) hyperparams = imputer.get_hyperparams(col) assert hyperparams == expected @@ -116,9 +110,7 @@ def test_hyperparameters_get_hyperparameters_modified( @pytest.mark.parametrize( "df", [pd.DataFrame({"col1": [np.nan, np.nan, np.nan], "col2": [1, 2, 3]})] ) -def test_Imputer_fit_transform_on_nan_column( - df: pd.DataFrame, imputer: imputers._Imputer -) -> None: +def test_Imputer_fit_transform_on_nan_column(df: pd.DataFrame, imputer: imputers._Imputer) -> None: np.testing.assert_raises(ValueError, imputer.fit_transform, df) @@ -143,9 +135,7 @@ def test_fit_transform_on_grouped(df: pd.DataFrame) -> None: @pytest.mark.parametrize("df", [df_incomplete]) @pytest.mark.parametrize("df_oracle", [df_complete]) -def test_ImputerOracle_fit_transform( - df: pd.DataFrame, df_oracle: pd.DataFrame -) -> None: +def test_ImputerOracle_fit_transform(df: pd.DataFrame, df_oracle: pd.DataFrame) -> None: imputer = imputers.ImputerOracle() imputer.set_solution(df_oracle) result = imputer.fit_transform(df) @@ -157,9 +147,7 @@ def test_ImputerOracle_fit_transform( def test_ImputerSimple_mean_fit_transform(df: pd.DataFrame) -> None: imputer = imputers.ImputerSimple(strategy="mean") result = imputer.fit_transform(df) - expected = pd.DataFrame( - {"col1": [0, 5 / 3, 2, 3, 5 / 3], "col2": ["a", "b", "b", "b", "b"]} - ) + expected = pd.DataFrame({"col1": [0, 5 / 3, 2, 3, 5 / 3], "col2": ["a", "b", "b", "b", "b"]}) pd.testing.assert_frame_equal(result, expected) @@ -167,9 +155,7 @@ def test_ImputerSimple_mean_fit_transform(df: pd.DataFrame) -> None: def test_ImputerSimple_median_fit_transform(df: pd.DataFrame) -> None: imputer = imputers.ImputerSimple() result = imputer.fit_transform(df) - expected = pd.DataFrame( - {"col1": [0.0, 2.0, 2.0, 3.0, 2.0], "col2": ["a", "b", "b", "b", "b"]} - ) + expected = pd.DataFrame({"col1": [0.0, 2.0, 2.0, 3.0, 2.0], "col2": ["a", "b", "b", "b", "b"]}) pd.testing.assert_frame_equal(result, expected) @@ -177,9 +163,7 @@ def test_ImputerSimple_median_fit_transform(df: pd.DataFrame) -> None: def test_ImputerSimple_mode_fit_transform(df: pd.DataFrame) -> None: imputer = imputers.ImputerSimple(strategy="most_frequent") result = imputer.fit_transform(df) - expected = pd.DataFrame( - {"col1": [0.0, 0.0, 2.0, 3.0, 0.0], "col2": ["a", "b", "b", "b", "b"]} - ) + expected = pd.DataFrame({"col1": [0.0, 0.0, 2.0, 3.0, 0.0], "col2": ["a", "b", "b", "b", "b"]}) pd.testing.assert_frame_equal(result, expected) @@ -195,9 +179,7 @@ def test_ImputerShuffle_fit_transform1(df: pd.DataFrame) -> None: def test_ImputerShuffle_fit_transform2(df: pd.DataFrame) -> None: imputer = imputers.ImputerShuffle(random_state=42) result = imputer.fit_transform(df) - expected = pd.DataFrame( - {"col1": [0, 3, 2, 3, 0], "col2": [-1, 1.5, 0.5, 1.5, 1.5]} - ) + expected = pd.DataFrame({"col1": [0, 3, 2, 3, 0], "col2": [-1, 1.5, 0.5, 1.5, 1.5]}) np.testing.assert_allclose(result, expected) @@ -205,9 +187,7 @@ def test_ImputerShuffle_fit_transform2(df: pd.DataFrame) -> None: def test_ImputerLOCF_fit_transform(df: pd.DataFrame) -> None: imputer = imputers.ImputerLOCF() result = imputer.fit_transform(df) - expected = pd.DataFrame( - {"col1": [0, 0, 2, 3, 3], "col2": [-1, -1, 0.5, 0.5, 1.5]} - ) + expected = pd.DataFrame({"col1": [0, 0, 2, 3, 3], "col2": [-1, -1, 0.5, 0.5, 1.5]}) np.testing.assert_allclose(result, expected) @@ -215,9 +195,7 @@ def test_ImputerLOCF_fit_transform(df: pd.DataFrame) -> None: def test_ImputerNOCB_fit_transform(df: pd.DataFrame) -> None: imputer = imputers.ImputerNOCB() result = imputer.fit_transform(df) - expected = pd.DataFrame( - {"col1": [0, 2, 2, 3, 3], "col2": [-1, 0.5, 0.5, 1.5, 1.5]} - ) + expected = pd.DataFrame({"col1": [0, 2, 2, 3, 3], "col2": [-1, 0.5, 0.5, 1.5, 1.5]}) np.testing.assert_allclose(result, expected) @@ -225,9 +203,7 @@ def test_ImputerNOCB_fit_transform(df: pd.DataFrame) -> None: def test_ImputerInterpolation_fit_transform(df: pd.DataFrame) -> None: imputer = imputers.ImputerInterpolation() result = imputer.fit_transform(df) - expected = pd.DataFrame( - {"col1": [0, 1, 2, 3, 3], "col2": [-1, -0.25, 0.5, 1, 1.5]} - ) + expected = pd.DataFrame({"col1": [0, 1, 2, 3, 3], "col2": [-1, -0.25, 0.5, 1, 1.5]}) np.testing.assert_allclose(result, expected) @@ -291,18 +267,14 @@ def test_ImputerRegressor_fit_transform(df: pd.DataFrame) -> None: @pytest.mark.parametrize("df", [df_timeseries]) def test_ImputerRpcaNoisy_fit_transform(df: pd.DataFrame) -> None: - imputer = imputers.ImputerRpcaNoisy( - columnwise=False, max_iterations=100, tau=1, lam=0.3 - ) + imputer = imputers.ImputerRpcaNoisy(columnwise=False, max_iterations=100, tau=1, lam=0.3) df_omega = df.notna() df_result = imputer.fit_transform(df) np.testing.assert_allclose(df_result[df_omega], df[df_omega]) assert df_result.notna().all().all() -index_grouped = pd.MultiIndex.from_product( - [["a", "b"], range(4)], names=["group", "date"] -) +index_grouped = pd.MultiIndex.from_product([["a", "b"], range(4)], names=["group", "date"]) dict_values = { "col1": [0, np.nan, 0, np.nan, 1, 1, 1, 1], "col2": [1, 1, 1, 1, 2, 2, 2, 2], @@ -352,8 +324,6 @@ def test_models_fit_transform_grouped(imputer): imputers.ImputerEM(), ] ) -def test_sklearn_compatible_estimator( - estimator: imputers._Imputer, check: Any -) -> None: +def test_sklearn_compatible_estimator(estimator: imputers._Imputer, check: Any) -> None: """Check compatibility with sklearn, using sklearn estimator checks API.""" check(estimator) diff --git a/tests/imputations/test_imputers_diffusions.py b/tests/imputations/test_imputers_diffusions.py index 1c68f0d3..a29898c0 100644 --- a/tests/imputations/test_imputers_diffusions.py +++ b/tests/imputations/test_imputers_diffusions.py @@ -92,9 +92,7 @@ def test_TabDDPM_fit(df: pd.DataFrame) -> None: ) model = ddpms.TabDDPM(num_noise_steps=10, num_blocks=1, dim_embedding=64) - model = model.fit( - df, batch_size=2, epochs=2, x_valid=df, print_valid=False - ) + model = model.fit(df, batch_size=2, epochs=2, x_valid=df, print_valid=False) df_imputed = model.predict(df) @@ -116,13 +114,9 @@ def test_TabDDPM_process_data(df: pd.DataFrame) -> None: @pytest.mark.parametrize("df", [df_incomplete]) def test_TabDDPM_process_reversely_data(df: pd.DataFrame) -> None: model = ddpms.TabDDPM(num_noise_steps=10, num_blocks=1, dim_embedding=64) - model = model.fit( - df, batch_size=2, epochs=2, x_valid=df, print_valid=False - ) + model = model.fit(df, batch_size=2, epochs=2, x_valid=df, print_valid=False) - arr_processed, arr_mask, list_indices = model._process_data( - df, is_training=False - ) + arr_processed, arr_mask, list_indices = model._process_data(df, is_training=False) df_imputed = model._process_reversely_data(arr_processed, df, list_indices) np.testing.assert_array_equal(df.shape, df_imputed.shape) @@ -133,15 +127,9 @@ def test_TabDDPM_process_reversely_data(df: pd.DataFrame) -> None: @pytest.mark.parametrize("df", [df_incomplete]) def test_TabDDPM_q_sample(df: pd.DataFrame) -> None: model = ddpms.TabDDPM(num_noise_steps=10, num_blocks=1, dim_embedding=64) - model = model.fit( - df, batch_size=2, epochs=2, x_valid=df, print_valid=False - ) + model = model.fit(df, batch_size=2, epochs=2, x_valid=df, print_valid=False) - device = ( - torch.device("cuda") - if torch.cuda.is_available() - else torch.device("cpu") - ) + device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") ts_data_noised, ts_noise = model._q_sample( x=torch.ones(2, 5, dtype=torch.float).to(device), @@ -154,9 +142,7 @@ def test_TabDDPM_q_sample(df: pd.DataFrame) -> None: @pytest.mark.parametrize("df", [df_incomplete]) def test_TabDDPM_eval(df: pd.DataFrame) -> None: - model = ddpms.TabDDPM( - num_noise_steps=10, num_blocks=1, dim_embedding=64, is_clip=True - ) + model = ddpms.TabDDPM(num_noise_steps=10, num_blocks=1, dim_embedding=64, is_clip=True) model = model.fit( df, batch_size=2, @@ -177,9 +163,7 @@ def test_TabDDPM_eval(df: pd.DataFrame) -> None: list(df.index), ) - np.testing.assert_array_equal( - list(scores.keys()), ["mean_absolute_error", "dist_wasserstein"] - ) + np.testing.assert_array_equal(list(scores.keys()), ["mean_absolute_error", "dist_wasserstein"]) @pytest.mark.parametrize("df", [df_incomplete]) @@ -214,12 +198,8 @@ def test_TabDDPM_predict(df: pd.DataFrame) -> None: } ) - model = ddpms.TabDDPM( - num_noise_steps=10, num_blocks=1, dim_embedding=64, is_clip=True - ) - model = model.fit( - df, batch_size=2, epochs=2, x_valid=df, print_valid=False - ) + model = ddpms.TabDDPM(num_noise_steps=10, num_blocks=1, dim_embedding=64, is_clip=True) + model = model.fit(df, batch_size=2, epochs=2, x_valid=df, print_valid=False) df_imputed = model.predict(df) @@ -261,9 +241,7 @@ def test_TsDDPM_fit(df: pd.DataFrame) -> None: @pytest.mark.parametrize("df", [df_incomplete]) def test_TsDDPM_process_data(df: pd.DataFrame) -> None: - model = ddpms.TsDDPM( - num_noise_steps=10, num_blocks=1, dim_embedding=64, is_rolling=False - ) + model = ddpms.TsDDPM(num_noise_steps=10, num_blocks=1, dim_embedding=64, is_rolling=False) model = model.fit( df, batch_size=2, @@ -278,9 +256,7 @@ def test_TsDDPM_process_data(df: pd.DataFrame) -> None: np.testing.assert_array_equal(arr_processed.shape, [5, 1, 5]) np.testing.assert_array_equal(arr_mask.shape, [5, 1, 5]) - model = ddpms.TsDDPM( - num_noise_steps=10, num_blocks=1, dim_embedding=64, is_rolling=True - ) + model = ddpms.TsDDPM(num_noise_steps=10, num_blocks=1, dim_embedding=64, is_rolling=True) model = model.fit( df, batch_size=2, @@ -298,9 +274,7 @@ def test_TsDDPM_process_data(df: pd.DataFrame) -> None: @pytest.mark.parametrize("df", [df_incomplete]) def test_TsDDPM_process_reversely_data(df: pd.DataFrame) -> None: - model = ddpms.TsDDPM( - num_noise_steps=10, num_blocks=1, dim_embedding=64, is_rolling=False - ) + model = ddpms.TsDDPM(num_noise_steps=10, num_blocks=1, dim_embedding=64, is_rolling=False) model = model.fit( df, batch_size=2, @@ -310,18 +284,14 @@ def test_TsDDPM_process_reversely_data(df: pd.DataFrame) -> None: index_datetime="datetime", ) - arr_processed, arr_mask, list_indices = model._process_data( - df, is_training=False - ) + arr_processed, arr_mask, list_indices = model._process_data(df, is_training=False) df_imputed = model._process_reversely_data(arr_processed, df, list_indices) np.testing.assert_array_equal(df.shape, df_imputed.shape) np.testing.assert_array_equal(df.index, df_imputed.index) np.testing.assert_array_equal(df.columns, df_imputed.columns) - model = ddpms.TsDDPM( - num_noise_steps=10, num_blocks=1, dim_embedding=64, is_rolling=True - ) + model = ddpms.TsDDPM(num_noise_steps=10, num_blocks=1, dim_embedding=64, is_rolling=True) model = model.fit( df, batch_size=2, @@ -331,9 +301,7 @@ def test_TsDDPM_process_reversely_data(df: pd.DataFrame) -> None: index_datetime="datetime", ) - arr_processed, arr_mask, list_indices = model._process_data( - df, is_training=False - ) + arr_processed, arr_mask, list_indices = model._process_data(df, is_training=False) df_imputed = model._process_reversely_data(arr_processed, df, list_indices) np.testing.assert_array_equal(df.shape, df_imputed.shape) @@ -352,11 +320,7 @@ def test_TsDDPM_q_sample(df: pd.DataFrame) -> None: print_valid=False, index_datetime="datetime", ) - device = ( - torch.device("cuda") - if torch.cuda.is_available() - else torch.device("cpu") - ) + device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") ts_data_noised, ts_noise = model._q_sample( x=torch.ones(2, 1, 5, dtype=torch.float).to(device), @@ -376,9 +340,7 @@ def test_TsDDPM_q_sample(df: pd.DataFrame) -> None: "check_estimators_pickle": "TODO", }, ) -def test_sklearn_compatible_estimator( - estimator: imputers._Imputer, check: Any -) -> None: +def test_sklearn_compatible_estimator(estimator: imputers._Imputer, check: Any) -> None: """Check compatibility with sklearn, using sklearn estimator checks API.""" check( estimator, diff --git a/tests/imputations/test_imputers_pytorch.py b/tests/imputations/test_imputers_pytorch.py index 0704114c..e14b9ebe 100644 --- a/tests/imputations/test_imputers_pytorch.py +++ b/tests/imputations/test_imputers_pytorch.py @@ -28,9 +28,7 @@ def test_ImputerRegressorPyTorch_fit_transform(df: pd.DataFrame) -> None: nn.manual_seed(42) if nn.cuda.is_available(): nn.cuda.manual_seed(42) - estimator = imputers_pytorch.build_mlp( - input_dim=2, list_num_neurons=[64, 32] - ) + estimator = imputers_pytorch.build_mlp(input_dim=2, list_num_neurons=[64, 32]) imputer = imputers_pytorch.ImputerRegressorPyTorch( estimator=estimator, handler_nan="column", epochs=10 ) diff --git a/tests/imputations/test_preprocessing.py b/tests/imputations/test_preprocessing.py index c4500deb..2fa07321 100644 --- a/tests/imputations/test_preprocessing.py +++ b/tests/imputations/test_preprocessing.py @@ -80,9 +80,7 @@ def test_fit_transform_BinTransformer(bin_transformer): def test_transform_BinTransformer(bin_transformer): bin_transformer.dict_df_bins_ = { - 0: pd.DataFrame( - {"value": [1, 2, 3, 4, 5], "min": [-np.inf, 1.5, 2.5, 3.5, 4.5]} - ) + 0: pd.DataFrame({"value": [1, 2, 3, 4, 5], "min": [-np.inf, 1.5, 2.5, 3.5, 4.5]}) } bin_transformer.feature_names_in_ = pd.Index([0]) bin_transformer.n_features_in_ = 1 @@ -99,9 +97,7 @@ def test_fit_transform_with_dataframes_BinTransformer(bin_transformer): def test_transform_with_dataframes_BinTransformer(bin_transformer): bin_transformer.dict_df_bins_ = { - 0: pd.DataFrame( - {"value": [1, 2, 3, 4, 5], "min": [0.5, 1.5, 2.5, 3.5, 4.5]} - ) + 0: pd.DataFrame({"value": [1, 2, 3, 4, 5], "min": [0.5, 1.5, 2.5, 3.5, 4.5]}) } bin_transformer.feature_names_in_ = pd.Index(["0"]) bin_transformer.n_features_in_ = 1 @@ -127,9 +123,7 @@ def test_inverse_transform_OneHotEncoderProjector(encoder): df_back = encoder.inverse_transform(df_dum) pd.testing.assert_frame_equal(df, df_back) - df_dum_perturbed = df_dum + np.random.uniform( - -0.5, 0.5, size=df_dum.shape - ) + df_dum_perturbed = df_dum + np.random.uniform(-0.5, 0.5, size=df_dum.shape) df_back = encoder.inverse_transform(df_dum_perturbed) pd.testing.assert_frame_equal(df, df_back) diff --git a/tests/imputations/test_softimpute.py b/tests/imputations/test_softimpute.py index b85025da..9b65d978 100644 --- a/tests/imputations/test_softimpute.py +++ b/tests/imputations/test_softimpute.py @@ -9,9 +9,7 @@ X_non_regression_test = np.array( [[1, 2, np.nan, 4], [1, 5, 3, np.nan], [4, 2, 3, 2], [1, 1, 5, 4]] ) -X_expected = np.array( - [[1, 2, 2.9066, 4], [1, 5, 3, 2.1478], [4, 2, 3, 2], [1, 1, 5, 4]] -) +X_expected = np.array([[1, 2, 2.9066, 4], [1, 5, 3, 2.1478], [4, 2, 3, 2], [1, 1, 5, 4]]) tau = 1 max_iterations = 30 random_state = 50 @@ -40,12 +38,8 @@ def test_soft_impute_decompose(X: NDArray) -> None: model = softimpute.SoftImpute(tau=tau) Omega = ~np.isnan(X) X_imputed = np.where(Omega, X, 0) - cost_all_in_M = model.cost_function( - X, X_imputed, np.full_like(X, 0), Omega, tau - ) - cost_all_in_A = model.cost_function( - X, np.full_like(X, 0), X_imputed, Omega, tau - ) + cost_all_in_M = model.cost_function(X, X_imputed, np.full_like(X, 0), Omega, tau) + cost_all_in_A = model.cost_function(X, np.full_like(X, 0), X_imputed, Omega, tau) M, A = model.decompose(X, Omega) cost_final = model.cost_function(X, M, A, Omega, tau) assert isinstance(model, softimpute.SoftImpute) diff --git a/tests/utils/test_algebra.py b/tests/utils/test_algebra.py index d0432219..4197cf19 100644 --- a/tests/utils/test_algebra.py +++ b/tests/utils/test_algebra.py @@ -11,9 +11,7 @@ def test_frechet_distance_exact(): means2 = np.array([0, -1, 1]) cov2 = np.eye(3, 3) - expected = np.sum((means2 - means1) ** 2) + np.sum( - (np.sqrt(stds) - 1) ** 2 - ) + expected = np.sum((means2 - means1) ** 2) + np.sum((np.sqrt(stds) - 1) ** 2) expected /= 3 result = algebra.frechet_distance_exact(means1, cov1, means2, cov2) np.testing.assert_almost_equal(result, expected, decimal=3) @@ -27,8 +25,6 @@ def test_kl_divergence_gaussian_exact(): means2 = np.array([0, -1, 1]) cov2 = np.eye(3, 3) - expected = ( - np.sum(stds**2 - np.log(stds**2) - 1 + (means2 - means1) ** 2) - ) / 2 + expected = (np.sum(stds**2 - np.log(stds**2) - 1 + (means2 - means1) ** 2)) / 2 result = algebra.kl_divergence_gaussian_exact(means1, cov1, means2, cov2) np.testing.assert_almost_equal(result, expected, decimal=3) diff --git a/tests/utils/test_data.py b/tests/utils/test_data.py index 7bffbb9f..45e68b91 100644 --- a/tests/utils/test_data.py +++ b/tests/utils/test_data.py @@ -249,9 +249,7 @@ def test_get_dataframes_in_folder(mock_convert_tsf, mock_read_csv, mock_walk): mock_walk.return_value = [("/fakepath", ("subfolder",), ("file.csv",))] result_csv = data.get_dataframes_in_folder("/fakepath", ".csv") assert len(result_csv) == 1 - mock_read_csv.assert_called_once_with( - os.path.join("/fakepath", "file.csv") - ) + mock_read_csv.assert_called_once_with(os.path.join("/fakepath", "file.csv")) pd.testing.assert_frame_equal(result_csv[0], df_conductor) mock_read_csv.reset_mock() @@ -259,9 +257,7 @@ def test_get_dataframes_in_folder(mock_convert_tsf, mock_read_csv, mock_walk): mock_walk.return_value = [("/fakepath", ("subfolder",), ("file.tsf",))] result_tsf = data.get_dataframes_in_folder("/fakepath", ".tsf") assert len(result_tsf) == 1 - mock_convert_tsf.assert_called_once_with( - os.path.join("/fakepath", "file.tsf") - ) + mock_convert_tsf.assert_called_once_with(os.path.join("/fakepath", "file.tsf")) pd.testing.assert_frame_equal(result_tsf[0], df_beijing) mock_read_csv.assert_called() @@ -269,18 +265,14 @@ def test_get_dataframes_in_folder(mock_convert_tsf, mock_read_csv, mock_walk): @patch("numpy.random.normal") @patch("numpy.random.choice") @patch("numpy.random.standard_exponential") -def test_generate_artificial_ts( - mock_standard_exponential, mock_choice, mock_normal -): +def test_generate_artificial_ts(mock_standard_exponential, mock_choice, mock_normal): n_samples = 100 periods = [10, 20] amp_anomalies = 1.0 ratio_anomalies = 0.1 amp_noise = 0.1 - mock_standard_exponential.return_value = np.ones( - int(n_samples * ratio_anomalies) - ) + mock_standard_exponential.return_value = np.ones(int(n_samples * ratio_anomalies)) mock_choice.return_value = np.arange(int(n_samples * ratio_anomalies)) mock_normal.return_value = np.zeros(n_samples) @@ -309,15 +301,9 @@ def test_generate_artificial_ts( ("Bug", None), ], ) -def test_data_get_data( - name_data: str, df: pd.DataFrame, mocker: MockerFixture -) -> None: - mock_download = mocker.patch( - "qolmat.utils.data.download_data_from_zip", return_value=[df] - ) - mock_read = mocker.patch( - "qolmat.utils.data.read_csv_local", return_value=df - ) +def test_data_get_data(name_data: str, df: pd.DataFrame, mocker: MockerFixture) -> None: + mock_download = mocker.patch("qolmat.utils.data.download_data_from_zip", return_value=[df]) + mock_read = mocker.patch("qolmat.utils.data.read_csv_local", return_value=df) mock_read_dl = mocker.patch("pandas.read_csv", return_value=df) mocker.patch( "qolmat.utils.data.preprocess_data_beijing", @@ -389,9 +375,7 @@ def test_preprocess_data_beijing(df: pd.DataFrame) -> None: assert result_df.index.names == ["station", "datetime"] assert all(result_df.index.get_level_values("station") == "Beijing") assert len(result_df) == 1 - assert np.isclose( - result_df.loc[(("Beijing"),), "pm2.5"], 176.66666666666666 - ) + assert np.isclose(result_df.loc[(("Beijing"),), "pm2.5"], 176.66666666666666) @pytest.mark.parametrize("df", [df_preprocess_offline]) @@ -408,9 +392,7 @@ def test_data_add_holes(df: pd.DataFrame) -> None: ("Beijing", df_beijing), ], ) -def test_data_get_data_corrupted( - name_data: str, df: pd.DataFrame, mocker: MockerFixture -) -> None: +def test_data_get_data_corrupted(name_data: str, df: pd.DataFrame, mocker: MockerFixture) -> None: mock_get = mocker.patch("qolmat.utils.data.get_data", return_value=df) df_out = data.get_data_corrupted(name_data) assert mock_get.call_count == 1 @@ -442,7 +424,5 @@ def test_data_add_datetime_features(df: pd.DataFrame) -> None: result = data.add_datetime_features(df) pd.testing.assert_index_equal(result.index, df.index) assert result.columns.tolist() == columns_out - pd.testing.assert_frame_equal( - result.drop(columns=["time_cos", "time_sin"]), df - ) + pd.testing.assert_frame_equal(result.drop(columns=["time_cos", "time_sin"]), df) assert (result["time_cos"] ** 2 + result["time_sin"] ** 2 == 1).all() diff --git a/tests/utils/test_input_check.py b/tests/utils/test_input_check.py index 073e6c0d..10d38eca 100644 --- a/tests/utils/test_input_check.py +++ b/tests/utils/test_input_check.py @@ -13,9 +13,7 @@ def multitypes_dataframe() -> pd.DataFrame: "float_col": [1.1, 2.2, 3.3], "str_col": ["a", "b", "c"], "bool_col": [True, False, True], - "datetime_col": pd.to_datetime( - ["2021-01-01", "2021-01-02", "2021-01-03"] - ), + "datetime_col": pd.to_datetime(["2021-01-01", "2021-01-02", "2021-01-03"]), } ) diff --git a/tests/utils/test_plot.py b/tests/utils/test_plot.py index 61a7a92f..68c3b68c 100644 --- a/tests/utils/test_plot.py +++ b/tests/utils/test_plot.py @@ -31,9 +31,7 @@ df1 = pd.DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]}) df2 = pd.DataFrame({"x": [2, 3, 4], "y": [5, 6, 7]}) dict_df_imputed = { - "Imputer1": pd.DataFrame( - {"A": [2, 3, np.nan], "B": [5, np.nan, 7], "C": [np.nan, 8, 9]} - ) + "Imputer1": pd.DataFrame({"A": [2, 3, np.nan], "B": [5, np.nan, 7], "C": [np.nan, 8, 9]}) } @@ -52,18 +50,14 @@ def test_utils_plot_plot_matrices( @pytest.mark.parametrize("list_signals", [list_signals]) @patch("matplotlib.pyplot.show") @patch("matplotlib.pyplot.savefig") -def test_utils_plot_plot_signal( - mock_savefig, mock_show, list_signals: List[List[Any]] -) -> None: +def test_utils_plot_plot_signal(mock_savefig, mock_show, list_signals: List[List[Any]]) -> None: plot.plot_signal(list_signals=list_signals, ylabel="ylabel", title="title") assert len(plt.gcf().get_axes()) > 0 assert mock_savefig.call_count == 1 plt.close("all") -@pytest.mark.parametrize( - "M, A, E, index_array, dims", [(M, A, E, [0, 1, 2], (10, 10))] -) +@pytest.mark.parametrize("M, A, E, index_array, dims", [(M, A, E, [0, 1, 2], (10, 10))]) @patch("matplotlib.pyplot.show") @patch("matplotlib.pyplot.savefig") def test__utils_plot_plot_images( @@ -92,9 +86,7 @@ def test_utils_plot_make_ellipses_from_data(mock_show, X: np.ndarray): @pytest.mark.parametrize("df1,df2", [(df1, df2)]) @patch("matplotlib.pyplot.show") -def test_utils_plot_compare_covariances( - mock_show, df1: pd.DataFrame, df2: pd.DataFrame -): +def test_utils_plot_compare_covariances(mock_show, df1: pd.DataFrame, df2: pd.DataFrame): ax = plt.gca() plot.compare_covariances(df1, df2, "x", "y", ax) assert len(plt.gcf().get_axes()) > 0 diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py index 489b607d..e805045a 100644 --- a/tests/utils/test_utils.py +++ b/tests/utils/test_utils.py @@ -13,9 +13,7 @@ @pytest.mark.parametrize("iteration, total", [(1, 1)]) -def test_utils_utils_display_progress_bar( - iteration: int, total: int, capsys -) -> None: +def test_utils_utils_display_progress_bar(iteration: int, total: int, capsys) -> None: captured_output = StringIO() sys.stdout = captured_output utils.progress_bar( @@ -35,9 +33,7 @@ def test_utils_utils_display_progress_bar( assert output == output_expected -@pytest.mark.parametrize( - "values, lag_max", [(pd.Series([1.0, 2.0, 3.0, 4.0, 5.0]), 3)] -) +@pytest.mark.parametrize("values, lag_max", [(pd.Series([1.0, 2.0, 3.0, 4.0, 5.0]), 3)]) def test_utils_utils_acf(values, lag_max): result = utils.acf(values, lag_max) result_expected = pd.Series([1.0, 1.0, 1.0]) From 79c6ab41df7b2609198190a241952f4a7d04c3a6 Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Thu, 25 Dec 2025 14:56:05 +0100 Subject: [PATCH 22/39] test patched --- tests/analysis/test_holes_characterization.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/analysis/test_holes_characterization.py b/tests/analysis/test_holes_characterization.py index a72f19b8..f262bb67 100644 --- a/tests/analysis/test_holes_characterization.py +++ b/tests/analysis/test_holes_characterization.py @@ -14,7 +14,7 @@ @pytest.fixture def mcar_df() -> pd.DataFrame: rng = sku.check_random_state(42) - matrix = rng.multivariate_normal(mean=[0, 0], cov=[[1, 0], [0, 1]], size=200) + matrix = rng.multivariate_normal(mean=[0, 0], cov=[[1, 0], [0, 1]], size=400) df = pd.DataFrame(data=matrix, columns=["Column_1", "Column_2"]) hole_gen = UniformHoleGenerator( n_splits=1, random_state=42, subset=["Column_2"], ratio_masked=0.2 @@ -26,7 +26,7 @@ def mcar_df() -> pd.DataFrame: @pytest.fixture def mar_hm_df() -> pd.DataFrame: rng = sku.check_random_state(42) - matrix = rng.multivariate_normal(mean=[0, 0], cov=[[1, 0], [0, 1]], size=200) + matrix = rng.multivariate_normal(mean=[0, 0], cov=[[1, 0], [0, 1]], size=400) quantile_95 = norm.ppf(0.975) df = pd.DataFrame(matrix, columns=["Column_1", "Column_2"]) @@ -40,7 +40,7 @@ def mar_hm_df() -> pd.DataFrame: @pytest.fixture def mar_hc_df() -> pd.DataFrame: rng = sku.check_random_state(42) - matrix = rng.multivariate_normal(mean=[0, 0], cov=[[1, 0], [0, 1]], size=200) + matrix = rng.multivariate_normal(mean=[0, 0], cov=[[1, 0], [0, 1]], size=400) quantile_95 = norm.ppf(0.975) df = pd.DataFrame(matrix, columns=["Column_1", "Column_2"]) From 99e58ae4153572599defe380842d957211fd8e16 Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Thu, 25 Dec 2025 18:08:01 +0100 Subject: [PATCH 23/39] doc improvement --- HISTORY.rst | 8 ++- examples/tutorials/plot_tuto_mcar.py | 79 +++++++++++++--------------- qolmat/utils/utils.py | 2 - 3 files changed, 43 insertions(+), 46 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 2e1dc20d..793f623b 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,12 +2,16 @@ History ======= -0.1.10 (2024-??-??) +0.1.11 (2025-12-30) +------------------ +* PLKM test implemented and documented in the holes_characterization module + +0.1.10 (2025-08-30) ------------------ * Long EM and RPCA operations wrapped with tqdm progress bars * Readme code sample updated, and results table made consistent -0.1.9 (2024-08-29) +0.1.9 (2025-08-29) ------------------ * Tutorials reproducibility improved with random_state parameters * RPCA now accepts random_state parameters diff --git a/examples/tutorials/plot_tuto_mcar.py b/examples/tutorials/plot_tuto_mcar.py index ecec89f5..10d00d62 100644 --- a/examples/tutorials/plot_tuto_mcar.py +++ b/examples/tutorials/plot_tuto_mcar.py @@ -231,33 +231,43 @@ # # %% - -""" -Calculation time -================ - -+------------+------------+----------------------+ -| **n_rows** | **n_cols** | **Calculation_time** | -+============+============+======================+ -| 200 | 2 | 2"12 | -+------------+------------+----------------------+ -| 500 | 2 | 2"24 | -+------------+------------+----------------------+ -| 500 | 4 | 2"18 | -+------------+------------+----------------------+ -| 1000 | 4 | 2"48 | -+------------+------------+----------------------+ -| 1000 | 6 | 2"42 | -+------------+------------+----------------------+ -| 10000 | 6 | 20"54 | -+------------+------------+----------------------+ -| 10000 | 10 | 14"48 | -+------------+------------+----------------------+ -| 100000 | 10 | 4'51" | -+------------+------------+----------------------+ -| 100000 | 15 | 3'06" | -+------------+------------+----------------------+ -""" +# Calculation time +# ================ +# +# .. list-table:: +# :header-rows: 1 +# :widths: 15 15 25 +# +# * - **n_rows** +# - **n_cols** +# - **Calculation time** +# * - 200 +# - 2 +# - 2"12 +# * - 500 +# - 2 +# - 2"24 +# * - 500 +# - 4 +# - 2"18 +# * - 1000 +# - 4 +# - 2"48 +# * - 1000 +# - 6 +# - 2"42 +# * - 10000 +# - 6 +# - 20"54 +# * - 10000 +# - 10 +# - 14"48 +# * - 100000 +# - 10 +# - 4'51" +# * - 100000 +# - 15 +# - 3'06" # %% # 2.1 Parameters and Hyperparameters @@ -390,18 +400,3 @@ # As a result, by removing the missing patterns induced by variable 2, the p-value rises # above the significance threshold set beforehand. Thus in this sense, the test detects that the # main culprit of the MAR mechanism lies in the second variable. - - -# %% -# Calculation time -> TO BE DELETED -# | **n_rows** | **n_cols** | **Calculation_time** | -# |------------|------------|----------------------| -# | 200 | 2 | 2"12 | -# | 500 | 2 | 2"24 | -# | 500 | 4 | 2"18 | -# | 1000 | 4 | 2"48 | -# | 1000 | 6 | 2"42 | -# | 10000 | 6 | 20"54 | -# | 10000 | 10 | 14"48 | -# | 100000 | 10 | 4'51" | -# | 100000 | 15 | 3'06" | diff --git a/qolmat/utils/utils.py b/qolmat/utils/utils.py index 7132067b..9ff1103f 100644 --- a/qolmat/utils/utils.py +++ b/qolmat/utils/utils.py @@ -414,12 +414,10 @@ def _parallel_with_seeds_and_list( Seed or random state for reproducibility. """ - print("_parallel_with_seeds_and_list called with seed:", random_state) n_runs = len(args) rng = sku.check_random_state(random_state) ss = np.random.SeedSequence(rng.randint(0, 2**32)) child_seeds = ss.spawn(n_runs) seeds = [np.random.default_rng(s).integers(0, 2**32) for s in child_seeds] - print("Generated seeds:", seeds) return Parallel(n_jobs=-1)(delayed(func)(seed, **arg) for seed, arg in zip(seeds, args)) From 26d25acaf17ff326f72bbf7a8cb72a3848883983 Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Thu, 25 Dec 2025 18:54:40 +0100 Subject: [PATCH 24/39] mypy error patched --- qolmat/analysis/holes_characterization.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index 531e40fe..7ebba26f 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -387,7 +387,7 @@ def _draw_projection(self, X: np.ndarray) -> tuple[list[int], int]: is_checked = False while not is_checked: features_idx, target_idx = self._draw_features_and_target_indexes(X) - is_checked = self._check_draw(X, features_idx, target_idx) + is_checked = bool(self._check_draw(X, features_idx, target_idx)) return features_idx, target_idx @staticmethod @@ -675,7 +675,7 @@ def test(self, X: Union[pd.DataFrame, np.ndarray]) -> Union[float, tuple[float, } for features_idx, target_idx in list_proj ] - parallel_results = utils._parallel_with_seeds_and_list( + parallel_results: list[tuple[float, list]] = utils._parallel_with_seeds_and_list( self._parallel_process_projection, args, random_state=self.rng, @@ -699,7 +699,9 @@ def test(self, X: Union[pd.DataFrame, np.ndarray]) -> Union[float, tuple[float, return p_value else: B = self._build_B(list_proj, n_cols) - U = np.array([item[0] for item in parallel_results]) + U_array = np.array([item[0] for item in parallel_results]) U_sigma = np.array([item[1] for item in parallel_results]) - p_values = [self._compute_partial_p_value(B, U, U_sigma, k) for k in range(n_cols)] + p_values = [ + self._compute_partial_p_value(B, U_array, U_sigma, k) for k in range(n_cols) + ] return p_value, p_values From f22ec0d1eb5fdbd9b5da050ededc3079064e96e2 Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Thu, 25 Dec 2025 19:33:27 +0100 Subject: [PATCH 25/39] out of bound int32 patched --- qolmat/utils/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qolmat/utils/utils.py b/qolmat/utils/utils.py index 9ff1103f..1142a796 100644 --- a/qolmat/utils/utils.py +++ b/qolmat/utils/utils.py @@ -416,8 +416,8 @@ def _parallel_with_seeds_and_list( """ n_runs = len(args) rng = sku.check_random_state(random_state) - ss = np.random.SeedSequence(rng.randint(0, 2**32)) + ss = np.random.SeedSequence(rng.randint(0, 2**31 - 1)) child_seeds = ss.spawn(n_runs) - seeds = [np.random.default_rng(s).integers(0, 2**32) for s in child_seeds] + seeds = [np.random.default_rng(s).integers(0, 2**31 - 1) for s in child_seeds] return Parallel(n_jobs=-1)(delayed(func)(seed, **arg) for seed, arg in zip(seeds, args)) From 4512e97d380581dab426dea02a36ffe2c3031f0f Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Thu, 25 Dec 2025 21:06:22 +0100 Subject: [PATCH 26/39] doc options robustified --- .python-version | 2 +- docs/conf.py | 2 +- docs/sg_execution_times.rst | 52 +++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- 4 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 docs/sg_execution_times.rst diff --git a/.python-version b/.python-version index 0e5fec43..43077b24 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -env_qolmat_3.9 +3.9.18 diff --git a/docs/conf.py b/docs/conf.py index 5ec5e33c..1dbe1dc3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -156,7 +156,7 @@ "gallery_dirs": ["examples/tutorials/"], "doc_module": "qolmat", "backreferences_dir": os.path.join("generated"), - "reference_url": {"qolmat": None}, + "reference_url": {"qolmat": "https://qolmat.readthedocs.io/en/latest/"}, } suppress_warnings = ["autosectionlabel.*"] diff --git a/docs/sg_execution_times.rst b/docs/sg_execution_times.rst new file mode 100644 index 00000000..0c2ff3f8 --- /dev/null +++ b/docs/sg_execution_times.rst @@ -0,0 +1,52 @@ + +:orphan: + +.. _sphx_glr_sg_execution_times: + + +Computation times +================= +**00:00.000** total execution time for 6 files **from all galleries**: + +.. container:: + + .. raw:: html + + + + + + + + .. list-table:: + :header-rows: 1 + :class: table table-striped sg-datatable + + * - Example + - Time + - Mem (MB) + * - :ref:`sphx_glr_examples_tutorials_plot_tuto_benchmark_TS.py` (``../examples/tutorials/plot_tuto_benchmark_TS.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_examples_tutorials_plot_tuto_categorical.py` (``../examples/tutorials/plot_tuto_categorical.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_examples_tutorials_plot_tuto_diffusion_models.py` (``../examples/tutorials/plot_tuto_diffusion_models.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_examples_tutorials_plot_tuto_hole_generator.py` (``../examples/tutorials/plot_tuto_hole_generator.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_examples_tutorials_plot_tuto_mcar.py` (``../examples/tutorials/plot_tuto_mcar.py``) + - 00:00.000 + - 0.0 + * - :ref:`sphx_glr_examples_tutorials_plot_tuto_mean_median.py` (``../examples/tutorials/plot_tuto_mean_median.py``) + - 00:00.000 + - 0.0 diff --git a/pyproject.toml b/pyproject.toml index ae3c512e..d27876c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ codecov = "^2.1.13" [tool.poetry.group.docs.dependencies] numpydoc = "1.1.0" sphinx = ">= 5.0" -sphinx-gallery = "0.10.1" +sphinx-gallery = ">= 0.15" sphinx_rtd_theme = "1.0.0" sphinx_markdown_tables = "0.0.17" From f24b0683443074b59ba9d02de87be2667413cf50 Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Fri, 26 Dec 2025 12:38:28 +0100 Subject: [PATCH 27/39] doc only running in latest python version --- .github/workflows/test.yml | 11 +++++++---- .python-version | 2 +- docs/conf.py | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b4621a30..5695ee05 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,10 +64,13 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - name: Check Changed Files id: changed-files - run: | - git fetch origin ${{ github.base_ref }}:${{ github.base_ref }} --depth=1 - git diff --name-only ${{ github.base_ref }} > changed_files.txt + uses: tj-actions/changed-files@v40 + with: + files: | + docs/** + **.rst + - name: Build Docs - if: contains(fromJSON('["docs/", ".rst"]').join(','), fromJSON('["${{ steps.changed-files.outputs.files }}"]').join(',')) + if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest' && steps.changed-files.outputs.any_changed == 'true' run: | poetry run sphinx-build -b html docs/ _build/html diff --git a/.python-version b/.python-version index 43077b24..3b2cfc0e 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.9.18 +3.12.10 diff --git a/docs/conf.py b/docs/conf.py index 1dbe1dc3..a9835d73 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -75,7 +75,7 @@ source_suffix = ".rst" # Generate the plots for the gallery -plot_gallery = True +plot_gallery = "True" # The master toctree document. master_doc = "index" From 2f1cdc82b97bca0510defe7f987a0521d4ce2195 Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Fri, 26 Dec 2025 13:18:18 +0100 Subject: [PATCH 28/39] json doc bug patched --- docs/conf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index a9835d73..b36b797e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -156,9 +156,11 @@ "gallery_dirs": ["examples/tutorials/"], "doc_module": "qolmat", "backreferences_dir": os.path.join("generated"), - "reference_url": {"qolmat": "https://qolmat.readthedocs.io/en/latest/"}, + "reference_url": {}, + "inspect_global_variables": False, } + suppress_warnings = ["autosectionlabel.*"] # doctest configuration From 561e5873d06d9d6dcc1f5875f613a956205645c2 Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Mon, 29 Dec 2025 20:56:59 +0100 Subject: [PATCH 29/39] tests updated --- .github/workflows/publish.yml | 31 ++--- .gitignore | 1 + .python-version | 1 - CONTRIBUTING.rst | 9 +- HISTORY.rst | 2 + Makefile | 17 ++- examples/benchmark.md | 18 ++- pyproject.toml | 155 +++++++++++------------- qolmat/benchmark/hyperparameters.py | 103 ++++++++-------- qolmat/imputations/em_sampler.py | 23 +++- qolmat/utils/utils.py | 1 + tests/benchmark/test_hyperparameters.py | 12 +- tests/imputations/test_em_sampler.py | 3 - tests/imputations/test_imputers.py | 2 +- 14 files changed, 185 insertions(+), 193 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 39ba5324..500492e5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,29 +4,22 @@ on: release: types: [published] +permissions: + id-token: write + jobs: deploy: runs-on: ubuntu-latest + environment: pypi steps: - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.10' - - name: Install Poetry - run: | - curl -sSL https://install.python-poetry.org | python3 - - echo "$HOME/.local/bin" >> $GITHUB_PATH - - name: Install dependencies - run: | - poetry install + + - name: Install uv + uses: astral-sh/setup-uv@v3 + - name: Build package - run: | - poetry build - - name: Publish package - env: - PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} - run: | - poetry config pypi-token.pypi $PYPI_TOKEN - poetry publish + run: uv build + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.gitignore b/.gitignore index 2512eb9e..86456f9f 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ var/ .installed.cfg *.egg poetry.lock +uv.lock # PyInstaller # Usually these files are written by a python script from a template diff --git a/.python-version b/.python-version index 3b2cfc0e..e69de29b 100644 --- a/.python-version +++ b/.python-version @@ -1 +0,0 @@ -3.12.10 diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index f2d292a2..cd8f538d 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -25,15 +25,12 @@ Local setup We encourage you to use a virtual environment. You'll want to activate it every time you want to work on `Qolmat`. -You can create a virtual environment via `conda`: +You can create a virtual environment and install dependencies with `uv`: .. code:: sh - $ pip install poetry - $ poetry config virtualenvs.in-project true - $ poetry lock - $ poetry install - $ poetry shell + $ uv sync + $ uv run pytest Once the environment is installed, pre-commit is installed, but need to be activated using the following command: diff --git a/HISTORY.rst b/HISTORY.rst index 793f623b..efd75238 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -5,6 +5,8 @@ History 0.1.11 (2025-12-30) ------------------ * PLKM test implemented and documented in the holes_characterization module +* Dependency management improved with uv +* Migrated from hyperopt to skopt for hyperparameter optimization 0.1.10 (2025-08-30) ------------------ diff --git a/Makefile b/Makefile index e0ca5828..bb18e881 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,18 @@ check-coverage: - poetry run pytest --cov-branch --cov=qolmat/ --cov-report=xml tests/ - -check-poetry: - poetry check --lock + uv run pytest --cov-branch --cov=qolmat/ --cov-report=xml tests/ check-quality: - poetry run ruff check qolmat/ tests/ + uv run ruff check qolmat/ tests/ check-security: - poetry run bandit --recursive --configfile=pyproject.toml qolmat/ + uv run bandit --recursive --configfile=pyproject.toml qolmat/ check-tests: - poetry run pytest tests/ + uv run pytest tests/ check-types: - poetry run mypy qolmat/ tests/ + uv run mypy qolmat/ tests/ checkers: check-coverage check-types @@ -25,10 +22,10 @@ clean: make clean -C docs coverage: - poetry run pytest --cov-branch --cov=qolmat --cov-report=xml tests + uv run pytest --cov-branch --cov=qolmat --cov-report=xml tests doc: make html -C docs doctest: - poetry run pytest --doctest-modules --pyargs qolmat + uv run pytest --doctest-modules --pyargs qolmat diff --git a/examples/benchmark.md b/examples/benchmark.md index 551a3aaf..df19394e 100644 --- a/examples/benchmark.md +++ b/examples/benchmark.md @@ -22,9 +22,7 @@ First, import some useful libraries ```python tags=[] import warnings # warnings.filterwarnings('error') -``` -```python tags=[] %reload_ext autoreload %autoreload 2 @@ -33,7 +31,7 @@ from IPython.display import Image import pandas as pd from datetime import datetime import numpy as np -import hyperopt as ho +from skopt.space import Real, Integer, Categorical np.random.seed(1234) from matplotlib import pyplot as plt import matplotlib.ticker as plticker @@ -41,13 +39,13 @@ import matplotlib.ticker as plticker tab10 = plt.get_cmap("tab10") plt.rcParams.update({'font.size': 18}) - from sklearn.linear_model import LinearRegression from qolmat.benchmark import comparator, missing_patterns from qolmat.imputations import imputers from qolmat.utils import data, utils, plot + ``` ### **I. Load data** @@ -124,15 +122,15 @@ imputer_residuals = imputers.ImputerResiduals(groups=("station",), period=365, m imputer_rpca = imputers.ImputerRpcaNoisy(groups=("station",), columnwise=False, max_iterations=500, tau=.01, lam=5, rank=1) imputer_rpca_opti = imputers.ImputerRpcaNoisy(groups=("station",), columnwise=False, max_iterations=256) dict_config_opti["RPCA_opti"] = { - "tau": ho.hp.uniform("tau", low=.5, high=5), - "lam": ho.hp.uniform("lam", low=.1, high=1), + "tau": Real(0.5, 5.0, name="tau"), + "lam": Real(0.1, 1.0, name="lam"), } imputer_rpca_opticw = imputers.ImputerRpcaNoisy(groups=("station",), columnwise=False, max_iterations=256) dict_config_opti["RPCA_opticw"] = { - "tau/TEMP": ho.hp.uniform("tau/TEMP", low=.5, high=5), - "tau/PRES": ho.hp.uniform("tau/PRES", low=.5, high=5), - "lam/TEMP": ho.hp.uniform("lam/TEMP", low=.1, high=1), - "lam/PRES": ho.hp.uniform("lam/PRES", low=.1, high=1), + "tau/TEMP": Real(0.5, 5.0, name="tau/TEMP"), + "tau/PRES": Real(0.5, 5.0, name="tau/PRES"), + "lam/TEMP": Real(0.1, 1.0, name="lam/TEMP"), + "lam/PRES": Real(0.1, 1.0, name="lam/PRES"), } imputer_normal_sample = imputers.ImputerEM(groups=("station",), model="multinormal", method="sample", max_iter_em=8, n_iter_ou=128, dt=4e-2) diff --git a/pyproject.toml b/pyproject.toml index d27876c0..1db418b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,104 +1,91 @@ -# PACKAGE - -[tool.poetry] +[project] name = "qolmat" version = "0.1.10" description = "A Python library for optimal data imputation." authors = [ - "Julien ROUSSEL ", - "Anh Khoa NGO HO ", - "Hong-Lan BOTTERMAN ", - "Guillaume SAËS ", + { name = "Julien ROUSSEL", email = "julien.roussel@capgemini.com" }, + { name = "Anh Khoa NGO HO", email = "anh-khoa.ngo-ho@capgemini.com" }, + { name = "Hong-Lan BOTTERMAN", email = "hong-lan.botterman@capgemini.com" }, + { name = "Guillaume SAËS", email = "guillaume.saes@capgemini.com" }, ] -license = "BSD-3-Clause" +license = { text = "BSD-3-Clause" } readme = "README.rst" -homepage = "https://github.com/Quantmetry/qolmat" -repository = "https://github.com/Quantmetry/qolmat" -documentation = "https://qolmat.readthedocs.io/en/latest/" +requires-python = ">=3.9,<3.13" keywords = ["imputation"] classifiers = [ "Intended Audience :: Science/Research", "Intended Audience :: Developers", - "License :: OSI Approved", + "License :: OSI Approved :: BSD License", "Topic :: Software Development", "Topic :: Scientific/Engineering", - "Operating System :: Microsoft :: Windows", - "Operating System :: POSIX", - "Operating System :: Unix", - "Operating System :: MacOS", - "Programming Language :: Python :: 3.8", + "Operating System :: OS Independent", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ] -# DEPENDENCIES - -[tool.poetry.dependencies] -python = ">=3.9,<3.13" -hyperopt = "*" -numpy = ">= 1.24" -pandas = ">= 2.0.1" -scipy = "*" -scikit-learn = ">= 1.6" -sphinx-markdown-tables = { version = "*", optional = true } -statsmodels = ">= 0.14.0" -typed-ast = { version = "*", optional = true } -category-encoders = "^2.6.3" -dcor = ">= 0.6" -tqdm = "*" - -[tool.poetry.group.torch.dependencies] -torch = "< 2.5" - -[tool.poetry.group.dev.dependencies] -bump2version = "1.0.1" -ipykernel = "^6.29.5" -jupyter = "1.0.0" -jupyterlab = "1.2.6" -jupytext = "1.14.4" -matplotlib = "*" -packaging = "23.1" -pre-commit = "2.21.0" -twine = "3.7.1" -wheel = "0.37.1" - -[tool.poetry.group.checkers.dependencies] -bandit = "^1.7.9" -mypy = "1.1.1" -ruff = "^0.6.3" -pytest = "7.2.0" -pytest-cov = "4.0.0" -pytest-mock = "3.10.0" - -[tool.poetry.group.ci.dependencies] -codecov = "^2.1.13" - -[tool.poetry.group.docs.dependencies] -numpydoc = "1.1.0" -sphinx = ">= 5.0" -sphinx-gallery = ">= 0.15" -sphinx_rtd_theme = "1.0.0" -sphinx_markdown_tables = "0.0.17" - -[tool.poetry.extras] -tests = ["typed-ast"] -docs = ["sphinx-markdown-tables"] - -[tool.poetry.urls] -"Bug Tracker" = "https://github.com/Quantmetry/qolmat" -"Source Code" = "https://github.com/Quantmetry/qolmat" - -[[tool.poetry.source]] -name = "pytorch_cpu" -url = "https://download.pytorch.org/whl/cpu" -priority = "explicit" +dependencies = [ + "scikit-optimize>=0.9", + "numpy>=1.24", + "pandas>=2.0.1", + "scipy", + "scikit-learn>=1.6", + "statsmodels>=0.14.0", + "category-encoders>=2.6.3,<3", + "dcor>=0.6", + "numba>=0.59", + "tqdm", +] + +[project.optional-dependencies] +torch = ["torch<2.5"] +docs = [ + "sphinx-markdown-tables", + "numpydoc==1.1.0", + "sphinx>=5.0", + "sphinx-gallery>=0.15", + "sphinx-rtd-theme==1.0.0", +] +dev = [ + "bump2version==1.0.1", + "ipykernel>=6.29.5", + "jupyter==1.0.0", + "jupyterlab==1.2.6", + "jupytext==1.14.4", + "matplotlib", + "packaging==23.1", + "pre-commit==2.21.0", + "twine==3.7.1", + "wheel==0.37.1", +] +checkers = [ + "bandit>=1.7.9", + "mypy==1.1.1", + "ruff>=0.6.3", + "pytest==7.2.0", + "pytest-cov==4.0.0", + "pytest-mock==3.10.0", +] +ci = ["codecov>=2.1.13"] + +[project.urls] +Homepage = "https://github.com/Quantmetry/qolmat" +Documentation = "https://qolmat.readthedocs.io/en/latest/" +Repository = "https://github.com/Quantmetry/qolmat" +"Bug Tracker" = "https://github.com/Quantmetry/qolmat/issues" [build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +requires = ["hatchling"] +build-backend = "hatchling.build" +# PyTorch CPU source +[[tool.uv.index]] +name = "pytorch_cpu" +url = "https://download.pytorch.org/whl/cpu" +explicit = true -# CONFIGURATION +# CONFIGURATION (unchanged) [tool.bandit] targets = ["qolmat"] @@ -149,9 +136,9 @@ ignore = [ "D107", "D203", "D213", - "N803", # allow X as a name for data - "N806", # allow X as a name for data - "N816", # allow mixed case names such as np_X_t as a name for data + "N803", + "N806", + "N816", ] [tool.ruff.lint.isort] diff --git a/qolmat/benchmark/hyperparameters.py b/qolmat/benchmark/hyperparameters.py index 99a77af8..4f65ddc8 100644 --- a/qolmat/benchmark/hyperparameters.py +++ b/qolmat/benchmark/hyperparameters.py @@ -3,16 +3,14 @@ import copy from typing import Callable, Dict, List -# import skopt -# from skopt.space import Categorical, Dimension, Integer, Real -import hyperopt as ho import numpy as np import pandas as pd +from skopt import gp_minimize +from skopt.space import Dimension from qolmat.benchmark import metrics from qolmat.benchmark.missing_patterns import _HoleGenerator from qolmat.imputations.imputers import _Imputer -from qolmat.utils.utils import HyperValue def get_objective( @@ -30,45 +28,43 @@ def get_objective( Parameters ---------- imputer: _Imputer - Imputer that should be optimized, it should at least have a - fit_transform method and an imputer_params attribute + Imputer to optimize df : pd.DataFrame - input dataframe + Input dataframe generator: _HoleGenerator - Generator creating the masked values in the nested cross validation - allowing to measure the imputer performance + Generator creating masked values for cross-validation metric: str - Metric used as performance indicator, common values are `mse` and `mae` + Metric used as performance indicator (e.g., 'mse', 'mae') names_hyperparams: List[str] - List of the names of the hyperparameters which are being optimized + Names of hyperparameters being optimized Returns ------- - Callable[List[HyperValue], float] - Objective function + Callable + Objective function returning mean error across folds """ - def fun_obf(args: List[HyperValue]) -> float: + def fun_obj(args: List) -> float: + # Set hyperparameters on imputer for key, value in zip(names_hyperparams, args): setattr(imputer, key, value) list_errors = [] - for df_mask in generator.split(df): df_origin = df.copy() df_corrupted = df_origin.copy() df_corrupted[df_mask] = np.nan df_imputed = imputer.fit_transform(df_corrupted) + subset = generator.subset fun_metric = metrics.get_metric(metric) errors = fun_metric(df_origin[subset], df_imputed[subset], df_mask[subset]) list_errors.append(errors) - mean_errors = np.mean(errors) - return mean_errors + return float(np.mean(list_errors)) - return fun_obf + return fun_obj def optimize( @@ -76,59 +72,68 @@ def optimize( df: pd.DataFrame, generator: _HoleGenerator, metric: str, - dict_config: Dict[str, HyperValue], + dict_config: Dict[str, Dimension], max_evals: int = 100, verbose: bool = False, -): - """Optimisation function. - - Return the provided imputer with hyperparameters optimized in the provided - range in order to minimize the provided metric. + random_state: int = 42, +) -> _Imputer: + """Optimize imputer hyperparameters using Bayesian optimization. Parameters ---------- imputer: _Imputer - Imputer that should be optimized, it should at least have a - fit_transform method and an imputer_params attribute + Imputer to optimize df : pd.DataFrame - input dataframe + Input dataframe generator: _HoleGenerator - Generator creating the masked values in the nested cross validation - allowing to measure the imputer performance + Generator for cross-validation metric: str - Metric used as performance indicator, common values are `mse` and `mae` - dict_config: Dict[str, HyperValue] - Search space for the tested hyperparameters - max_evals: int - Maximum number of evaluation of the performance of the algorithm. - Each estimation involves one call to fit_transform per fold returned - by the generator. See the n_fold attribute. - verbose: bool - Verbosity switch, useful for imputers that can have unstable - behavior for some hyperparameters values + Metric to minimize (e.g., 'mse', 'mae') + dict_config: Dict[str, Dimension] + Search space: keys are hyperparameter names, values are skopt Dimension objects + (Real, Integer, or Categorical) + max_evals: int, default=100 + Maximum number of evaluations + verbose: bool, default=False + Verbosity flag + random_state: int, default=42 + Random seed for reproducibility Returns ------- _Imputer - Optimized imputer + Imputer with optimized hyperparameters """ + print("Starting hyperparameter optimization...") imputer = copy.deepcopy(imputer) - if dict_config == {}: + + if not dict_config: return imputer + names_hyperparams = list(dict_config.keys()) - values_hyperparams = list(dict_config.values()) - imputer.imputer_params = tuple(set(imputer.imputer_params) | set(dict_config.keys())) + dimensions = list(dict_config.values()) + + # Update imputer_params to include optimized parameters + imputer.imputer_params = tuple(set(imputer.imputer_params) | set(names_hyperparams)) + + # Disable verbose during optimization if applicable if verbose and hasattr(imputer, "verbose"): setattr(imputer, "verbose", False) + fun_obj = get_objective(imputer, df, generator, metric, names_hyperparams) - hyperparams = ho.fmin( - fn=fun_obj, - space=values_hyperparams, - algo=ho.tpe.suggest, - max_evals=max_evals, + + result = gp_minimize( + func=fun_obj, + dimensions=dimensions, + n_calls=max_evals, + n_initial_points=min(10, max_evals), + verbose=verbose, + random_state=random_state, ) - for key, value in hyperparams.items(): + # Set optimal hyperparameters + for key, value in zip(names_hyperparams, result.x): setattr(imputer, key, value) + return imputer diff --git a/qolmat/imputations/em_sampler.py b/qolmat/imputations/em_sampler.py index 790ff47d..40e7ec5b 100644 --- a/qolmat/imputations/em_sampler.py +++ b/qolmat/imputations/em_sampler.py @@ -205,17 +205,17 @@ def _check_convergence(self) -> bool: @abstractmethod def reset_learned_parameters(self): """Reset learned parameters.""" - pass + raise NotImplementedError("Method reset_learned_parameters not implemented.") @abstractmethod def update_parameters(self, X: NDArray): """Update parameters.""" - pass + raise NotImplementedError("Method update_parameters not implemented.") @abstractmethod def combine_parameters(self): """Combine parameters.""" - pass + raise NotImplementedError("Method combine_parameters not implemented.") def fit_parameters(self, X: NDArray): """Fir parameters. @@ -227,6 +227,7 @@ def fit_parameters(self, X: NDArray): Array to compute the parameters. """ + print("fit_parameters") self.reset_learned_parameters() self.update_parameters(X) self.combine_parameters() @@ -242,6 +243,7 @@ def fit_parameters_with_missingness(self, X: NDArray): Data matrix with missingness """ + print("fit_parameters_with_missingness") X_imp = self.init_imputation(X) self.fit_parameters(X_imp) @@ -396,6 +398,7 @@ def _sample_ou( grad_X = -self.gradient_X_loglik(X_copy) X_copy += -self.dt * grad_X @ gamma + np.sqrt(2 * self.dt) * noise @ sqrt_gamma X_copy[~mask_na] = X_init[~mask_na] + if estimate_params: self.update_parameters(X_copy) @@ -453,8 +456,6 @@ def fit(self, X: NDArray) -> "EM": """ X = X.copy() - # utils.check_dtypes(X) - # sku.check_array(X, ensure_all_finite="allow-nan", dtype="float") sku.validation.validate_data(self, X, ensure_all_finite="allow-nan", dtype="float") self.shape_original = X.shape @@ -723,12 +724,13 @@ def get_gamma(self, n_cols: int) -> NDArray: Gamma matrix """ + print("get_gamma") + print(self.cov) U, diag, Vt = spl.svd(self.cov) diag_trunc = np.where(diag < self.min_std**2, 0, diag) diag_trunc = np.where(diag_trunc == 0, 0, np.min(diag_trunc)) gamma = (U * diag_trunc) @ Vt - # gamma = np.eye(len(self.cov)) return gamma @@ -769,12 +771,17 @@ def update_parameters(self, X): else: cov = np.cov(X, bias=True, rowvar=False).reshape(n_cols, -1) self.list_cov.append(cov) + print("update_parameters") + print(X) + print("Mean:", means) + print("Cov:\n", cov) def combine_parameters(self): """Combine all statistics computed for each sample in the update step. If uses the MANOVA formula. """ + print("combine_parameters") list_means = self.list_means[-self.n_samples :] list_cov = self.list_cov[-self.n_samples :] @@ -787,8 +794,12 @@ def combine_parameters(self): cov_intergroup = np.zeros(cov_intragroup.shape) else: cov_intergroup = np.cov(means_stack, bias=True, rowvar=False) + print("Intragroup covariance:\n", cov_intragroup) + print("Intergroup covariance:\n", cov_intergroup) self.cov = cov_intragroup + cov_intergroup + print("Cov:", self.cov) self.cov_inv = np.linalg.pinv(self.cov) + print("Cov inv:", self.cov_inv) def fit_parameters_with_missingness(self, X: NDArray): """Fit the first estimation of the model parameters. diff --git a/qolmat/utils/utils.py b/qolmat/utils/utils.py index 1142a796..4a5d07fb 100644 --- a/qolmat/utils/utils.py +++ b/qolmat/utils/utils.py @@ -394,6 +394,7 @@ def nan_mean_cov(X: NDArray) -> Tuple[NDArray, NDArray]: means = np.nanmean(X, axis=0) cov = np.ma.cov(np.ma.masked_invalid(X), rowvar=False).data cov = cov.reshape(n_variables, n_variables) + cov[~np.isfinite(cov)] = 0.0 return means, cov diff --git a/tests/benchmark/test_hyperparameters.py b/tests/benchmark/test_hyperparameters.py index 8bf72584..c298b44f 100644 --- a/tests/benchmark/test_hyperparameters.py +++ b/tests/benchmark/test_hyperparameters.py @@ -1,8 +1,8 @@ from typing import List, Optional, Tuple -import hyperopt as ho import numpy as np import pandas as pd +from skopt.space import Real from qolmat.benchmark import hyperparameters @@ -89,14 +89,18 @@ def test_hyperparameters_get_objective() -> None: def test_hyperparameters_optimize(): - """Test optimize.""" + """Test optimize with scikit-optimize.""" imputer = ImputerTest() + generator = HoleGeneratorTest(pd.Series([False, False, True, True]), subset=["some_col"]) + metric = "mse" - dict_config_opti = {"value": ho.hp.uniform("value", 0, 10)} + dict_config_opti = {"value": Real(0, 10, name="value")} df = pd.DataFrame({"some_col": [np.nan, 0, 3, 5]}) + imputer_opti = hyperparameters.optimize( - imputer, df, generator, metric, dict_config_opti, max_evals=500 + imputer, df, generator, metric, dict_config_opti, max_evals=20, verbose=True ) + assert isinstance(imputer_opti, ImputerTest) np.testing.assert_almost_equal(imputer_opti.value, 4, decimal=1) diff --git a/tests/imputations/test_em_sampler.py b/tests/imputations/test_em_sampler.py index ce75cc04..f4327a9a 100644 --- a/tests/imputations/test_em_sampler.py +++ b/tests/imputations/test_em_sampler.py @@ -294,9 +294,6 @@ def test_illconditioned_multinormalem() -> None: model = em_sampler.MultiNormalEM() with pytest.warns(UserWarning): _ = model.fit_transform(X) - # except IllConditioned: - # return - # assert False def test_no_more_nan_multinormalem() -> None: diff --git a/tests/imputations/test_imputers.py b/tests/imputations/test_imputers.py index c8ee2818..b9b52ac7 100644 --- a/tests/imputations/test_imputers.py +++ b/tests/imputations/test_imputers.py @@ -9,8 +9,8 @@ parametrize_with_checks, ) -from qolmat.benchmark.hyperparameters import HyperValue from qolmat.imputations import imputers +from qolmat.utils.utils import HyperValue df_complete = pd.DataFrame({"col1": [0, 1, 2, 3, 4], "col2": [-1, 0, 0.5, 1, 1.5]}) From dc42bb59d4b038e42309856d9fe5fd527ea0baa8 Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Mon, 29 Dec 2025 22:21:34 +0100 Subject: [PATCH 30/39] typing and doc tests passed --- .github/workflows/test.yml | 73 ++++++++--------------- Makefile | 26 ++++++-- docs/conf.py | 2 - docs/sg_execution_times.rst | 18 +++--- pyproject.toml | 3 +- qolmat/analysis/holes_characterization.py | 2 +- qolmat/benchmark/comparator.py | 4 +- qolmat/benchmark/metrics.py | 4 +- qolmat/imputations/em_sampler.py | 22 +------ qolmat/imputations/imputers.py | 40 ++++++++++--- qolmat/imputations/imputers_pytorch.py | 2 +- qolmat/imputations/rpca/rpca_noisy.py | 6 +- qolmat/imputations/softimpute.py | 7 +-- qolmat/utils/data.py | 6 +- qolmat/utils/exceptions.py | 4 +- qolmat/utils/utils.py | 2 +- 16 files changed, 109 insertions(+), 112 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5695ee05..f55bfdc5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,54 +23,31 @@ jobs: shell: bash -l {0} steps: - - name: Set OS and Python version - id: set-vars - run: | - if [[ "${GITHUB_REF}" == "refs/heads/main" || "${GITHUB_REF}" == "refs/heads/dev" ]]; then - echo "os-matrix=ubuntu-latest,windows-latest" >> $GITHUB_ENV - echo "python-matrix=3.9,3.11,3.12" >> $GITHUB_ENV - else - echo "os-matrix=ubuntu-latest" >> $GITHUB_ENV - echo "python-matrix=3.12" >> $GITHUB_ENV - fi - - name: Checkout - uses: actions/checkout@v3 - - name: Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Cache Poetry - uses: actions/cache@v3 - with: - path: | - ~/.cache/pypoetry - ~/.cache/pip - key: ${{ runner.os }}-poetry-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }} - restore-keys: | - ${{ runner.os }}-poetry-${{ matrix.python-version }}- - - name: Poetry - uses: snok/install-poetry@v1 - with: - version: 1.8.3 - - name: Lock - run: poetry lock --no-update - - name: Install - run: poetry install - - name: Checkers - run: make checkers - - name: Codecov - uses: codecov/codecov-action@v3 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - - name: Check Changed Files - id: changed-files - uses: tj-actions/changed-files@v40 + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v3 with: - files: | - docs/** - **.rst + enable-cache: true + cache-dependency-glob: "uv.lock" + + - name: Set up Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} + + - name: Install dependencies + run: make install + + - name: Check with ruff + run: make check-quality + + - name: Test with pytest + run: make check-tests + + - name: Check types + run: make check-types - - name: Build Docs - if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest' && steps.changed-files.outputs.any_changed == 'true' + - name: Build and test docs + if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest' run: | - poetry run sphinx-build -b html docs/ _build/html + make doc + make doctest diff --git a/Makefile b/Makefile index bb18e881..2ee02ba8 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,8 @@ +# Install dependencies +install: + uv sync --all-extras +# Code quality checks check-coverage: uv run pytest --cov-branch --cov=qolmat/ --cov-report=xml tests/ @@ -16,16 +20,26 @@ check-types: checkers: check-coverage check-types +# Formatting +format: + uv run ruff format qolmat/ tests/ + uv run ruff check --fix qolmat/ tests/ + +# Cleaning clean: - rm -rf .mypy_cache .pytest_cache .coverage* + rm -rf .mypy_cache .pytest_cache .coverage* .ruff_cache rm -rf **__pycache__ - make clean -C docs - -coverage: - uv run pytest --cov-branch --cov=qolmat --cov-report=xml tests + uv run make clean -C docs +# Documentation doc: - make html -C docs + uv run make html -C docs doctest: uv run pytest --doctest-modules --pyargs qolmat + +# Development helpers +lock: + uv lock + +.PHONY: install check-coverage check-quality check-security check-tests check-types checkers format clean doc doctest lock diff --git a/docs/conf.py b/docs/conf.py index b36b797e..3fe9a734 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -53,8 +53,6 @@ # see https://github.com/numpy/numpydoc/issues/69 numpydoc_show_class_members = False -from distutils.version import LooseVersion - # pngmath / imgmath compatibility layer for different sphinx versions # import sphinx diff --git a/docs/sg_execution_times.rst b/docs/sg_execution_times.rst index 0c2ff3f8..693c62e7 100644 --- a/docs/sg_execution_times.rst +++ b/docs/sg_execution_times.rst @@ -6,7 +6,7 @@ Computation times ================= -**00:00.000** total execution time for 6 files **from all galleries**: +**01:04.897** total execution time for 6 files **from all galleries**: .. container:: @@ -33,20 +33,20 @@ Computation times - Time - Mem (MB) * - :ref:`sphx_glr_examples_tutorials_plot_tuto_benchmark_TS.py` (``../examples/tutorials/plot_tuto_benchmark_TS.py``) - - 00:00.000 + - 00:21.381 - 0.0 * - :ref:`sphx_glr_examples_tutorials_plot_tuto_categorical.py` (``../examples/tutorials/plot_tuto_categorical.py``) - - 00:00.000 + - 00:16.026 - 0.0 * - :ref:`sphx_glr_examples_tutorials_plot_tuto_diffusion_models.py` (``../examples/tutorials/plot_tuto_diffusion_models.py``) - - 00:00.000 - - 0.0 - * - :ref:`sphx_glr_examples_tutorials_plot_tuto_hole_generator.py` (``../examples/tutorials/plot_tuto_hole_generator.py``) - - 00:00.000 + - 00:11.896 - 0.0 * - :ref:`sphx_glr_examples_tutorials_plot_tuto_mcar.py` (``../examples/tutorials/plot_tuto_mcar.py``) - - 00:00.000 + - 00:08.072 - 0.0 * - :ref:`sphx_glr_examples_tutorials_plot_tuto_mean_median.py` (``../examples/tutorials/plot_tuto_mean_median.py``) - - 00:00.000 + - 00:04.939 + - 0.0 + * - :ref:`sphx_glr_examples_tutorials_plot_tuto_hole_generator.py` (``../examples/tutorials/plot_tuto_hole_generator.py``) + - 00:02.583 - 0.0 diff --git a/pyproject.toml b/pyproject.toml index 1db418b8..93e48bba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,9 +85,10 @@ name = "pytorch_cpu" url = "https://download.pytorch.org/whl/cpu" explicit = true -# CONFIGURATION (unchanged) +# CONFIGURATION [tool.bandit] targets = ["qolmat"] +skips = ["B310"] [tool.mypy] pretty = true diff --git a/qolmat/analysis/holes_characterization.py b/qolmat/analysis/holes_characterization.py index 7ebba26f..580f4b29 100644 --- a/qolmat/analysis/holes_characterization.py +++ b/qolmat/analysis/holes_characterization.py @@ -92,7 +92,7 @@ def __init__( super().__init__(random_state=random_state) if imputer and imputer.model != "multinormal": raise AttributeError( - "The ImputerEM model must be 'multinormal' " "to use the Little's test" + "The ImputerEM model must be 'multinormal' to use the Little's test" ) self.imputer = imputer diff --git a/qolmat/benchmark/comparator.py b/qolmat/benchmark/comparator.py index e066b4d6..6046f603 100644 --- a/qolmat/benchmark/comparator.py +++ b/qolmat/benchmark/comparator.py @@ -115,7 +115,7 @@ def process_split(self, split_data: Tuple[int, pd.DataFrame, pd.DataFrame]) -> p subset = self.generator_holes.subset if subset is None: raise ValueError( - "HoleGenerator `subset` should be overwritten in split " "but it is none!" + "HoleGenerator `subset` should be overwritten in split but it is none!" ) split_results = {} @@ -159,7 +159,7 @@ def process_imputer( subset = self.generator_holes.subset if subset is None: raise ValueError( - "HoleGenerator `subset` should be overwritten in split " "but it is none!" + "HoleGenerator `subset` should be overwritten in split but it is none!" ) dict_config_opti_imputer = self.dict_config_opti.get(imputer_name, {}) diff --git a/qolmat/benchmark/metrics.py b/qolmat/benchmark/metrics.py index 25d15d57..dd136219 100644 --- a/qolmat/benchmark/metrics.py +++ b/qolmat/benchmark/metrics.py @@ -61,7 +61,7 @@ def columnwise_metric( pd.testing.assert_index_equal(df1.columns, df2.columns) except AssertionError: raise ValueError( - "Input dataframes do not have the same columns! " f"({df1.columns} != {df2.columns})" + f"Input dataframes do not have the same columns! ({df1.columns} != {df2.columns})" ) if type_cols == "all": cols = df1.columns.tolist() @@ -942,7 +942,7 @@ def kl_divergence_gaussian(df1: pd.DataFrame, df2: pd.DataFrame) -> float: div_kl = algebra.kl_divergence_gaussian_exact(means1, cov1, means2, cov2) except LinAlgError: raise ValueError( - "Provided datasets have degenerate colinearities, KL-divergence " "cannot be computed!" + "Provided datasets have degenerate colinearities, KL-divergence cannot be computed!" ) return div_kl diff --git a/qolmat/imputations/em_sampler.py b/qolmat/imputations/em_sampler.py index 40e7ec5b..ef202975 100644 --- a/qolmat/imputations/em_sampler.py +++ b/qolmat/imputations/em_sampler.py @@ -176,9 +176,7 @@ def __init__( verbose: bool = False, ): if method not in ["mle", "sample"]: - raise ValueError( - "`method` must be 'mle' or 'sample', " f"provided value is '{method}'." - ) + raise ValueError(f"`method` must be 'mle' or 'sample', provided value is '{method}'.") self.method = method self.max_iter_em = max_iter_em @@ -227,7 +225,6 @@ def fit_parameters(self, X: NDArray): Array to compute the parameters. """ - print("fit_parameters") self.reset_learned_parameters() self.update_parameters(X) self.combine_parameters() @@ -243,7 +240,6 @@ def fit_parameters_with_missingness(self, X: NDArray): Data matrix with missingness """ - print("fit_parameters_with_missingness") X_imp = self.init_imputation(X) self.fit_parameters(X_imp) @@ -459,16 +455,15 @@ def fit(self, X: NDArray) -> "EM": sku.validation.validate_data(self, X, ensure_all_finite="allow-nan", dtype="float") self.shape_original = X.shape - self.hash_fit = hash(X.tobytes()) if not isinstance(X, np.ndarray): raise AssertionError("Invalid type. X must be a NDArray.") + self.hash_fit = hash(X.tobytes()) X = utils.prepare_data(X, self.period) if hasattr(self, "p_to_fit") and self.p_to_fit: aics: List[float] = [] for p in range(self.max_lagp + 1): - print("p=", p) self.p = p self.fit_X(X) n1, n2 = self.X.shape @@ -724,8 +719,6 @@ def get_gamma(self, n_cols: int) -> NDArray: Gamma matrix """ - print("get_gamma") - print(self.cov) U, diag, Vt = spl.svd(self.cov) diag_trunc = np.where(diag < self.min_std**2, 0, diag) diag_trunc = np.where(diag_trunc == 0, 0, np.min(diag_trunc)) @@ -771,17 +764,12 @@ def update_parameters(self, X): else: cov = np.cov(X, bias=True, rowvar=False).reshape(n_cols, -1) self.list_cov.append(cov) - print("update_parameters") - print(X) - print("Mean:", means) - print("Cov:\n", cov) def combine_parameters(self): """Combine all statistics computed for each sample in the update step. If uses the MANOVA formula. """ - print("combine_parameters") list_means = self.list_means[-self.n_samples :] list_cov = self.list_cov[-self.n_samples :] @@ -794,12 +782,8 @@ def combine_parameters(self): cov_intergroup = np.zeros(cov_intragroup.shape) else: cov_intergroup = np.cov(means_stack, bias=True, rowvar=False) - print("Intragroup covariance:\n", cov_intragroup) - print("Intergroup covariance:\n", cov_intergroup) self.cov = cov_intragroup + cov_intergroup - print("Cov:", self.cov) self.cov_inv = np.linalg.pinv(self.cov) - print("Cov inv:", self.cov_inv) def fit_parameters_with_missingness(self, X: NDArray): """Fit the first estimation of the model parameters. @@ -1221,7 +1205,7 @@ def pretreatment(self, X, mask_na) -> Tuple[NDArray, NDArray]: if self.p == 0: return X, mask_na mask_na = mask_na.copy() - n_holes_left = np.sum(~np.cumsum(~mask_na, axis=0).any(axis=1)) + n_holes_left = int(np.sum(~np.cumsum(~mask_na, axis=0).any(axis=1))) mask_na[:n_holes_left] = False return X, mask_na diff --git a/qolmat/imputations/imputers.py b/qolmat/imputations/imputers.py index a0aae360..b38764fc 100644 --- a/qolmat/imputations/imputers.py +++ b/qolmat/imputations/imputers.py @@ -501,7 +501,7 @@ def transform(self, X: pd.DataFrame) -> pd.DataFrame: if hasattr(self, "df_solution"): df_imputed = df.fillna(self.df_solution) else: - warnings.warn("OracleImputer not initialized! " "Returning imputation with zeros") + warnings.warn("OracleImputer not initialized! Returning imputation with zeros") df_imputed = df.fillna(0) if isinstance(X, (np.ndarray)): @@ -1429,7 +1429,7 @@ def get_Xy_valid(self, df: pd.DataFrame, col: str) -> Tuple[pd.DataFrame, pd.Ser X = X.dropna(how="any", axis=1) else: raise ValueError( - f"Value '{self.handler_nan}' is not correct " "for argument `handler_nan'." + f"Value '{self.handler_nan}' is not correct for argument `handler_nan'." ) # X = pd.get_dummies(X, prefix_sep="=") y = df.loc[X.index, col] @@ -1998,23 +1998,47 @@ class ImputerEM(_Imputer): Parameters ---------- - groups: Tuple[str, ...] - List of column names to group by, by default [] - method : {'multinormal', 'VAR'}, default='multinormal' + groups : Tuple[str, ...], default=() + List of column names to group by. + model : {'multinormal', 'VAR'}, default='multinormal' Method defining the hypothesis made on the data distribution. Possible values: - 'multinormal' : the data points are independent and uniformly distributed following a multinormal distribution - 'VAR' : the data is a time series modeled by a VAR(p) process - columnwise : bool + columnwise : bool, default=False If False, correlations between variables will be used, which is advised. If True, each column is imputed independently. For the multinormal case each value will be imputed by the mean up to a noise with fixed noise, - for the VAR1 case the imputation will be a noisy temporal - interpolation. + for the VAR case the imputation will be a noisy temporal interpolation. random_state : RandomSetting, optional Controls the randomness of the fit_transform, by default None + method : {'mle', 'sample'}, default='sample' + Imputation method after EM convergence. + - 'mle' : Maximum Likelihood Estimation + - 'sample' : Sample from the posterior distribution + max_iter_em : int, default=200 + Maximum number of EM iterations. + n_iter_ou : int, default=50 + Number of Ornstein-Uhlenbeck process iterations for sampling. + ampli : float, default=1 + Amplitude parameter for the Ornstein-Uhlenbeck process. + dt : float, default=0.02 + Time step for the Ornstein-Uhlenbeck process discretization. + tolerance : float, default=1e-4 + Convergence tolerance for EM algorithm. + stagnation_threshold : float, default=5e-3 + Threshold for element-wise stagnation detection in EM algorithm. + stagnation_loglik : float, default=2 + Threshold for log-likelihood stagnation in EM algorithm. + period : int, default=1 + If different from 1, the data is folded with respect to the given period + before applying the imputation. + verbose : bool, default=False + If True, print convergence information during fitting. + p : int, optional + Order of the VAR process (only used when model='VAR'), by default None """ diff --git a/qolmat/imputations/imputers_pytorch.py b/qolmat/imputations/imputers_pytorch.py index cd6a3078..9850d856 100644 --- a/qolmat/imputations/imputers_pytorch.py +++ b/qolmat/imputations/imputers_pytorch.py @@ -236,7 +236,7 @@ def fit(self, X: NDArray, y: NDArray) -> "Autoencoder": loss.backward() optimizer.step() if (epoch + 1) % 10 == 0: - logging.info(f"Epoch [{epoch + 1}/{self.epochs}], " f"Loss: {loss.item():.4f}") + logging.info(f"Epoch [{epoch + 1}/{self.epochs}], Loss: {loss.item():.4f}") list_loss.append(loss.item()) self.loss.extend([list_loss]) return self diff --git a/qolmat/imputations/rpca/rpca_noisy.py b/qolmat/imputations/rpca/rpca_noisy.py index ed0f1062..0d95cac6 100644 --- a/qolmat/imputations/rpca/rpca_noisy.py +++ b/qolmat/imputations/rpca/rpca_noisy.py @@ -286,7 +286,7 @@ def minimise_loss( # init Y = np.zeros((n_rows, n_cols)) M = D.copy() - A = np.zeros((n_rows, n_cols)) + A: NDArray = np.zeros((n_rows, n_cols)) U, S, Vt = np.linalg.svd(M, full_matrices=False) U = U[:, :rank] @@ -297,7 +297,7 @@ def minimise_loss( Q = np.diag(np.sqrt(S)) @ Vt if norm == "L1": - R = [np.ones((n_rows, n_cols)) for _ in list_periods] + R: list[NDArray] = [np.ones((n_rows, n_cols)) for _ in list_periods] mu_bar = mu * 1e3 @@ -425,7 +425,7 @@ def decompose_on_basis( # M, A, L, Q = self.decompose_rpca(D, Omega) n_rank, _ = Q.shape Ir = np.eye(n_rank) - A = np.zeros((n_rows, n_cols)) + A: NDArray = np.zeros((n_rows, n_cols)) L = np.zeros((n_rows, n_rank)) for _ in range(self.max_iterations): A_prev = A.copy() diff --git a/qolmat/imputations/softimpute.py b/qolmat/imputations/softimpute.py index 088d58c4..f6ad3937 100644 --- a/qolmat/imputations/softimpute.py +++ b/qolmat/imputations/softimpute.py @@ -61,8 +61,8 @@ class SoftImpute(BaseEstimator, TransformerMixin): >>> Omega = ~np.isnan(D) >>> M, A = SoftImpute(random_state=11).decompose(D, Omega) >>> print(M + A) - [[1. 2. 4.12611456 4. ] - [1. 5. 3. 0.87217939] + [[1. 2. 2.38678001 4. ] + [1. 5. 3. 6.23499344] [4. 2. 3. 2. ] [1. 1. 5. 4. ]] @@ -180,8 +180,7 @@ def decompose(self, X: NDArray, Omega: NDArray) -> Tuple[NDArray, NDArray]: logging.info(f"Iteration {iter_}: ratio = {round(ratio, 4)}") if ratio < self.tolerance: logging.info( - f"Convergence reached at iteration {iter_} " - f"with ratio = {round(ratio, 4)}" + f"Convergence reached at iteration {iter_} with ratio = {round(ratio, 4)}" ) break diff --git a/qolmat/utils/data.py b/qolmat/utils/data.py index f1ace3f8..9748481b 100644 --- a/qolmat/utils/data.py +++ b/qolmat/utils/data.py @@ -380,7 +380,7 @@ def add_holes( random_state=random_state, ) - generator.dict_probas_out = {column: 1 / mean_size for column in df.columns} + generator.dict_probas_out = dict.fromkeys(df.columns, 1 / mean_size) generator.dict_ratios = {column: 1 / len(df.columns) for column in df.columns} if generator.groups: mask = df.groupby(groups, group_keys=False).apply(generator.generate_mask) @@ -553,7 +553,7 @@ def convert_tsf_to_dataframe( series = series.split(",") # type: ignore if len(series) == 0: - raise Exception(" Missing values should be indicated " "with ? symbol") + raise Exception(" Missing values should be indicated with ? symbol") numeric_series = [] @@ -565,7 +565,7 @@ def convert_tsf_to_dataframe( if numeric_series.count(replace_missing_vals_with) == len(numeric_series): raise Exception( - "At least one numeric value should be " "there in a series." + "At least one numeric value should be there in a series." ) all_series.append(pd.Series(numeric_series).array) diff --git a/qolmat/utils/exceptions.py b/qolmat/utils/exceptions.py index d0cfd465..b4e003c4 100644 --- a/qolmat/utils/exceptions.py +++ b/qolmat/utils/exceptions.py @@ -44,7 +44,7 @@ class NotDimension2(Exception): """Raise an error when the matrix is not of dim 2.""" def __init__(self, shape: Tuple[int, ...]): - super().__init__(f"Provided matrix is of shape {shape}, " "which is not of dimension 2!") + super().__init__(f"Provided matrix is of shape {shape}, which is not of dimension 2!") class NotDataFrame(Exception): @@ -96,4 +96,4 @@ class TypeNotHandled(Exception): """Raise an error when the type is not handled.""" def __init__(self, col: str, type_col: str): - super().__init__(f"The column `{col}` is of type `{type_col}`, " "which is not handled!") + super().__init__(f"The column `{col}` is of type `{type_col}`, which is not handled!") diff --git a/qolmat/utils/utils.py b/qolmat/utils/utils.py index 4a5d07fb..9b5d49dd 100644 --- a/qolmat/utils/utils.py +++ b/qolmat/utils/utils.py @@ -366,7 +366,7 @@ def create_lag_matrices(X: NDArray, p: int) -> Tuple[NDArray, NDArray]: """ n_rows, _ = X.shape n_rows_new = n_rows - p - list_X_lag = [np.ones((n_rows_new, 1))] + list_X_lag: list[NDArray] = [np.ones((n_rows_new, 1))] for lag in range(p): X_lag = X[p - lag - 1 : n_rows - lag - 1, :] list_X_lag.append(X_lag) From a4014e112ab744811f0fb46cd0a1f86386d48ec6 Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Mon, 29 Dec 2025 22:23:57 +0100 Subject: [PATCH 31/39] commit uv lock --- .gitignore | 2 - uv.lock | 4714 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 4714 insertions(+), 2 deletions(-) create mode 100644 uv.lock diff --git a/.gitignore b/.gitignore index 86456f9f..e385a1ee 100644 --- a/.gitignore +++ b/.gitignore @@ -28,8 +28,6 @@ var/ *.egg-info/ .installed.cfg *.egg -poetry.lock -uv.lock # PyInstaller # Usually these files are written by a python script from a template diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..0850a32e --- /dev/null +++ b/uv.lock @@ -0,0 +1,4714 @@ +version = 1 +revision = 1 +requires-python = ">=3.9, <3.13" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version < '3.10'", +] + +[[package]] +name = "alabaster" +version = "0.7.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511 }, +] + +[[package]] +name = "anyio" +version = "4.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362 }, +] + +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321 }, +] + +[[package]] +name = "argon2-cffi" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi-bindings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657 }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121 }, + { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177 }, + { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090 }, + { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246 }, + { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126 }, + { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343 }, + { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777 }, + { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180 }, + { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715 }, + { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149 }, + { url = "https://files.pythonhosted.org/packages/11/2d/ba4e4ca8d149f8dcc0d952ac0967089e1d759c7e5fcf0865a317eb680fbb/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6dca33a9859abf613e22733131fc9194091c1fa7cb3e131c143056b4856aa47e", size = 24549 }, + { url = "https://files.pythonhosted.org/packages/5c/82/9b2386cc75ac0bd3210e12a44bfc7fd1632065ed8b80d573036eecb10442/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:21378b40e1b8d1655dd5310c84a40fc19a9aa5e6366e835ceb8576bf0fea716d", size = 25539 }, + { url = "https://files.pythonhosted.org/packages/31/db/740de99a37aa727623730c90d92c22c9e12585b3c98c54b7960f7810289f/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d588dec224e2a83edbdc785a5e6f3c6cd736f46bfd4b441bbb5aa1f5085e584", size = 28467 }, + { url = "https://files.pythonhosted.org/packages/71/7a/47c4509ea18d755f44e2b92b7178914f0c113946d11e16e626df8eaa2b0b/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5acb4e41090d53f17ca1110c3427f0a130f944b896fc8c83973219c97f57b690", size = 27355 }, + { url = "https://files.pythonhosted.org/packages/ee/82/82745642d3c46e7cea25e1885b014b033f4693346ce46b7f47483cf5d448/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:da0c79c23a63723aa5d782250fbf51b768abca630285262fb5144ba5ae01e520", size = 29187 }, +] + +[[package]] +name = "arrow" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797 }, +] + +[[package]] +name = "asttokens" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047 }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615 }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, +] + +[[package]] +name = "backports-tarfile" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/72/cd9b395f25e290e633655a100af28cb253e4393396264a98bd5f5951d50f/backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991", size = 86406 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", size = 30181 }, +] + +[[package]] +name = "bandit" +version = "1.8.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "pyyaml", marker = "python_full_version < '3.10'" }, + { name = "rich", marker = "python_full_version < '3.10'" }, + { name = "stevedore", version = "5.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/b5/7eb834e213d6f73aace21938e5e90425c92e5f42abafaf8a6d5d21beed51/bandit-1.8.6.tar.gz", hash = "sha256:dbfe9c25fc6961c2078593de55fd19f2559f9e45b99f1272341f5b95dea4e56b", size = 4240271 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/ca/ba5f909b40ea12ec542d5d7bdd13ee31c4d65f3beed20211ef81c18fa1f3/bandit-1.8.6-py3-none-any.whl", hash = "sha256:3348e934d736fcdb68b6aa4030487097e23a501adf3e7827b63658df464dddd0", size = 133808 }, +] + +[[package]] +name = "bandit" +version = "1.9.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "pyyaml", marker = "python_full_version >= '3.10'" }, + { name = "rich", marker = "python_full_version >= '3.10'" }, + { name = "stevedore", version = "5.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/72/f704a97aac430aeb704fa16435dfa24fbeaf087d46724d0965eb1f756a2c/bandit-1.9.2.tar.gz", hash = "sha256:32410415cd93bf9c8b91972159d5cf1e7f063a9146d70345641cd3877de348ce", size = 4241659 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/1a/5b0320642cca53a473e79c7d273071b5a9a8578f9e370b74da5daa2768d7/bandit-1.9.2-py3-none-any.whl", hash = "sha256:bda8d68610fc33a6e10b7a8f1d61d92c8f6c004051d5e946406be1fb1b16a868", size = 134377 }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721 }, +] + +[[package]] +name = "bleach" +version = "6.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "webencodings", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/9a/0e33f5054c54d349ea62c277191c020c2d6ef1d65ab2cb1993f91ec846d1/bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f", size = 203083 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e", size = 163406 }, +] + +[package.optional-dependencies] +css = [ + { name = "tinycss2", marker = "python_full_version < '3.10'" }, +] + +[[package]] +name = "bleach" +version = "6.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "webencodings", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437 }, +] + +[package.optional-dependencies] +css = [ + { name = "tinycss2", marker = "python_full_version >= '3.10'" }, +] + +[[package]] +name = "bump2version" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/2a/688aca6eeebfe8941235be53f4da780c6edee05dbbea5d7abaa3aab6fad2/bump2version-1.0.1.tar.gz", hash = "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6", size = 36236 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/e3/fa60c47d7c344533142eb3af0b73234ef8ea3fb2da742ab976b947e717df/bump2version-1.0.1-py2.py3-none-any.whl", hash = "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410", size = 22030 }, +] + +[[package]] +name = "category-encoders" +version = "2.6.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pandas", marker = "python_full_version < '3.10'" }, + { name = "patsy", marker = "python_full_version < '3.10'" }, + { name = "scikit-learn", version = "1.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "statsmodels", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/d3/d7b3964edac75cef459ee97fc02034001b77a21bb0ecd0d1bfe7ec26962a/category_encoders-2.6.4.tar.gz", hash = "sha256:b842f0b3f280cdcee94278bee61c96500e4a8f71bc846714d7bf5696ae24b528", size = 55502 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/47/598b4bf0ccf6f02915e71bdd23fe846a27adc2d3ba734f2ba5215d8e44f5/category_encoders-2.6.4-py2.py3-none-any.whl", hash = "sha256:59f4b541ec787dfdfacc12267e1aff91a1ddc0b756991ec2129d50d6495d6e36", size = 82003 }, +] + +[[package]] +name = "category-encoders" +version = "2.8.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "pandas", marker = "python_full_version == '3.10.*'" }, + { name = "patsy", marker = "python_full_version == '3.10.*'" }, + { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "statsmodels", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/32/f5dff088ebae54d5464124298a9262bdd07c55558ba7e9b961be7ecdb0e6/category_encoders-2.8.1.tar.gz", hash = "sha256:57af8a23bde3cf622ee7e17c11547011795e4d337839d40cbd16c36b67291b33", size = 57856 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/fb/908cb215a30b117bb079a767176038599a5447f2506e21aa2e90d0aabfff/category_encoders-2.8.1-py3-none-any.whl", hash = "sha256:ba77bde0a0afe13732b04997635b1ae82569e42c9696bc361c68674197988303", size = 85710 }, +] + +[[package]] +name = "category-encoders" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", marker = "python_full_version >= '3.11'" }, + { name = "patsy", marker = "python_full_version >= '3.11'" }, + { name = "scikit-learn", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "statsmodels", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/59/1184ce74dca0c3e3450bccbb16edfce56f559c76dc794e2d52e1e63b467d/category_encoders-2.9.0.tar.gz", hash = "sha256:659311786e909013b8e8715fd1271244789a1dea278da44058828f88eeab5b40", size = 58005 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/06/afcae4dab08612dac244ace7f478543f4fb83bea94177231ef9b4f7bfa06/category_encoders-2.9.0-py3-none-any.whl", hash = "sha256:49c0e49cd3bd93b21c0bcc928ecbe9b3d09951a6f7fff8cc67f1f33967887227", size = 85859 }, +] + +[[package]] +name = "certifi" +version = "2025.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438 }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283 }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504 }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811 }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402 }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217 }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079 }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475 }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829 }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211 }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036 }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184 }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790 }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344 }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560 }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613 }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476 }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374 }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597 }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574 }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971 }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972 }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078 }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076 }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820 }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635 }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271 }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048 }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529 }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097 }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983 }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519 }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572 }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963 }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361 }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932 }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557 }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762 }, + { url = "https://files.pythonhosted.org/packages/c0/cc/08ed5a43f2996a16b462f64a7055c6e962803534924b9b2f1371d8c00b7b/cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf", size = 184288 }, + { url = "https://files.pythonhosted.org/packages/3d/de/38d9726324e127f727b4ecc376bc85e505bfe61ef130eaf3f290c6847dd4/cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", size = 180509 }, + { url = "https://files.pythonhosted.org/packages/9b/13/c92e36358fbcc39cf0962e83223c9522154ee8630e1df7c0b3a39a8124e2/cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", size = 208813 }, + { url = "https://files.pythonhosted.org/packages/15/12/a7a79bd0df4c3bff744b2d7e52cc1b68d5e7e427b384252c42366dc1ecbc/cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", size = 216498 }, + { url = "https://files.pythonhosted.org/packages/a3/ad/5c51c1c7600bdd7ed9a24a203ec255dccdd0ebf4527f7b922a0bde2fb6ed/cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", size = 203243 }, + { url = "https://files.pythonhosted.org/packages/32/f2/81b63e288295928739d715d00952c8c6034cb6c6a516b17d37e0c8be5600/cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", size = 203158 }, + { url = "https://files.pythonhosted.org/packages/1f/74/cc4096ce66f5939042ae094e2e96f53426a979864aa1f96a621ad128be27/cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", size = 216548 }, + { url = "https://files.pythonhosted.org/packages/e8/be/f6424d1dc46b1091ffcc8964fa7c0ab0cd36839dd2761b49c90481a6ba1b/cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", size = 218897 }, + { url = "https://files.pythonhosted.org/packages/f7/e0/dda537c2309817edf60109e39265f24f24aa7f050767e22c98c53fe7f48b/cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", size = 211249 }, + { url = "https://files.pythonhosted.org/packages/2b/e7/7c769804eb75e4c4b35e658dba01de1640a351a9653c3d49ca89d16ccc91/cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", size = 218041 }, + { url = "https://files.pythonhosted.org/packages/aa/d9/6218d78f920dcd7507fc16a766b5ef8f3b913cc7aa938e7fc80b9978d089/cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", size = 172138 }, + { url = "https://files.pythonhosted.org/packages/54/8f/a1e836f82d8e32a97e6b29cc8f641779181ac7363734f12df27db803ebda/cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", size = 182794 }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +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 = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709 }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814 }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467 }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280 }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454 }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609 }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849 }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586 }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290 }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663 }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964 }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064 }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015 }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792 }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198 }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262 }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988 }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324 }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742 }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863 }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837 }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550 }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162 }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019 }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310 }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022 }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383 }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098 }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991 }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456 }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978 }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969 }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425 }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162 }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558 }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497 }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240 }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471 }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864 }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647 }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110 }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839 }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667 }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535 }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816 }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694 }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131 }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390 }, + { url = "https://files.pythonhosted.org/packages/46/7c/0c4760bccf082737ca7ab84a4c2034fcc06b1f21cf3032ea98bd6feb1725/charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", size = 209609 }, + { url = "https://files.pythonhosted.org/packages/bb/a4/69719daef2f3d7f1819de60c9a6be981b8eeead7542d5ec4440f3c80e111/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", size = 149029 }, + { url = "https://files.pythonhosted.org/packages/e6/21/8d4e1d6c1e6070d3672908b8e4533a71b5b53e71d16828cc24d0efec564c/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608", size = 144580 }, + { url = "https://files.pythonhosted.org/packages/a7/0a/a616d001b3f25647a9068e0b9199f697ce507ec898cacb06a0d5a1617c99/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", size = 162340 }, + { url = "https://files.pythonhosted.org/packages/85/93/060b52deb249a5450460e0585c88a904a83aec474ab8e7aba787f45e79f2/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", size = 159619 }, + { url = "https://files.pythonhosted.org/packages/dd/21/0274deb1cc0632cd587a9a0ec6b4674d9108e461cb4cd40d457adaeb0564/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", size = 153980 }, + { url = "https://files.pythonhosted.org/packages/28/2b/e3d7d982858dccc11b31906976323d790dded2017a0572f093ff982d692f/charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", size = 152174 }, + { url = "https://files.pythonhosted.org/packages/6e/ff/4a269f8e35f1e58b2df52c131a1fa019acb7ef3f8697b7d464b07e9b492d/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", size = 151666 }, + { url = "https://files.pythonhosted.org/packages/da/c9/ec39870f0b330d58486001dd8e532c6b9a905f5765f58a6f8204926b4a93/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", size = 145550 }, + { url = "https://files.pythonhosted.org/packages/75/8f/d186ab99e40e0ed9f82f033d6e49001701c81244d01905dd4a6924191a30/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", size = 163721 }, + { url = "https://files.pythonhosted.org/packages/96/b1/6047663b9744df26a7e479ac1e77af7134b1fcf9026243bb48ee2d18810f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", size = 152127 }, + { url = "https://files.pythonhosted.org/packages/59/78/e5a6eac9179f24f704d1be67d08704c3c6ab9f00963963524be27c18ed87/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", size = 161175 }, + { url = "https://files.pythonhosted.org/packages/e5/43/0e626e42d54dd2f8dd6fc5e1c5ff00f05fbca17cb699bedead2cae69c62f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", size = 155375 }, + { url = "https://files.pythonhosted.org/packages/e9/91/d9615bf2e06f35e4997616ff31248c3657ed649c5ab9d35ea12fce54e380/charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", size = 99692 }, + { url = "https://files.pythonhosted.org/packages/d1/a9/6c040053909d9d1ef4fcab45fddec083aedc9052c10078339b47c8573ea8/charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", size = 107192 }, + { url = "https://files.pythonhosted.org/packages/f0/c6/4fa536b2c0cd3edfb7ccf8469fa0f363ea67b7213a842b90909ca33dd851/charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", size = 100220 }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402 }, +] + +[[package]] +name = "codecov" +version = "2.1.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "coverage", version = "7.13.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/bb/594b26d2c85616be6195a64289c578662678afa4910cef2d3ce8417cf73e/codecov-2.1.13.tar.gz", hash = "sha256:2362b685633caeaf45b9951a9b76ce359cd3581dd515b430c6c3f5dfb4d92a8c", size = 21416 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/02/18785edcdf6266cdd6c6dc7635f1cbeefd9a5b4c3bb8aff8bd681e9dd095/codecov-2.1.13-py2.py3-none-any.whl", hash = "sha256:c2ca5e51bba9ebb43644c43d0690148a55086f7f5e6fd36170858fa4206744d5", size = 16512 }, +] + +[[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 = "comm" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294 }, +] + +[[package]] +name = "contourpy" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/f6/31a8f28b4a2a4fa0e01085e542f3081ab0588eff8e589d39d775172c9792/contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4", size = 13464370 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/e0/be8dcc796cfdd96708933e0e2da99ba4bb8f9b2caa9d560a50f3f09a65f3/contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7", size = 265366 }, + { url = "https://files.pythonhosted.org/packages/50/d6/c953b400219443535d412fcbbc42e7a5e823291236bc0bb88936e3cc9317/contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42", size = 249226 }, + { url = "https://files.pythonhosted.org/packages/6f/b4/6fffdf213ffccc28483c524b9dad46bb78332851133b36ad354b856ddc7c/contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7", size = 308460 }, + { url = "https://files.pythonhosted.org/packages/cf/6c/118fc917b4050f0afe07179a6dcbe4f3f4ec69b94f36c9e128c4af480fb8/contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab", size = 347623 }, + { url = "https://files.pythonhosted.org/packages/f9/a4/30ff110a81bfe3abf7b9673284d21ddce8cc1278f6f77393c91199da4c90/contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589", size = 317761 }, + { url = "https://files.pythonhosted.org/packages/99/e6/d11966962b1aa515f5586d3907ad019f4b812c04e4546cc19ebf62b5178e/contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41", size = 322015 }, + { url = "https://files.pythonhosted.org/packages/4d/e3/182383743751d22b7b59c3c753277b6aee3637049197624f333dac5b4c80/contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d", size = 1262672 }, + { url = "https://files.pythonhosted.org/packages/78/53/974400c815b2e605f252c8fb9297e2204347d1755a5374354ee77b1ea259/contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223", size = 1321688 }, + { url = "https://files.pythonhosted.org/packages/52/29/99f849faed5593b2926a68a31882af98afbeac39c7fdf7de491d9c85ec6a/contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f", size = 171145 }, + { url = "https://files.pythonhosted.org/packages/a9/97/3f89bba79ff6ff2b07a3cbc40aa693c360d5efa90d66e914f0ff03b95ec7/contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b", size = 216019 }, + { url = "https://files.pythonhosted.org/packages/b3/1f/9375917786cb39270b0ee6634536c0e22abf225825602688990d8f5c6c19/contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad", size = 266356 }, + { url = "https://files.pythonhosted.org/packages/05/46/9256dd162ea52790c127cb58cfc3b9e3413a6e3478917d1f811d420772ec/contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49", size = 250915 }, + { url = "https://files.pythonhosted.org/packages/e1/5d/3056c167fa4486900dfbd7e26a2fdc2338dc58eee36d490a0ed3ddda5ded/contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66", size = 310443 }, + { url = "https://files.pythonhosted.org/packages/ca/c2/1a612e475492e07f11c8e267ea5ec1ce0d89971be496c195e27afa97e14a/contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081", size = 348548 }, + { url = "https://files.pythonhosted.org/packages/45/cf/2c2fc6bb5874158277b4faf136847f0689e1b1a1f640a36d76d52e78907c/contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1", size = 319118 }, + { url = "https://files.pythonhosted.org/packages/03/33/003065374f38894cdf1040cef474ad0546368eea7e3a51d48b8a423961f8/contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d", size = 323162 }, + { url = "https://files.pythonhosted.org/packages/42/80/e637326e85e4105a802e42959f56cff2cd39a6b5ef68d5d9aee3ea5f0e4c/contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c", size = 1265396 }, + { url = "https://files.pythonhosted.org/packages/7c/3b/8cbd6416ca1bbc0202b50f9c13b2e0b922b64be888f9d9ee88e6cfabfb51/contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb", size = 1324297 }, + { url = "https://files.pythonhosted.org/packages/4d/2c/021a7afaa52fe891f25535506cc861c30c3c4e5a1c1ce94215e04b293e72/contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c", size = 171808 }, + { url = "https://files.pythonhosted.org/packages/8d/2f/804f02ff30a7fae21f98198828d0857439ec4c91a96e20cf2d6c49372966/contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67", size = 217181 }, + { url = "https://files.pythonhosted.org/packages/c9/92/8e0bbfe6b70c0e2d3d81272b58c98ac69ff1a4329f18c73bd64824d8b12e/contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f", size = 267838 }, + { url = "https://files.pythonhosted.org/packages/e3/04/33351c5d5108460a8ce6d512307690b023f0cfcad5899499f5c83b9d63b1/contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6", size = 251549 }, + { url = "https://files.pythonhosted.org/packages/51/3d/aa0fe6ae67e3ef9f178389e4caaaa68daf2f9024092aa3c6032e3d174670/contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639", size = 303177 }, + { url = "https://files.pythonhosted.org/packages/56/c3/c85a7e3e0cab635575d3b657f9535443a6f5d20fac1a1911eaa4bbe1aceb/contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c", size = 341735 }, + { url = "https://files.pythonhosted.org/packages/dd/8d/20f7a211a7be966a53f474bc90b1a8202e9844b3f1ef85f3ae45a77151ee/contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06", size = 314679 }, + { url = "https://files.pythonhosted.org/packages/6e/be/524e377567defac0e21a46e2a529652d165fed130a0d8a863219303cee18/contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09", size = 320549 }, + { url = "https://files.pythonhosted.org/packages/0f/96/fdb2552a172942d888915f3a6663812e9bc3d359d53dafd4289a0fb462f0/contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd", size = 1263068 }, + { url = "https://files.pythonhosted.org/packages/2a/25/632eab595e3140adfa92f1322bf8915f68c932bac468e89eae9974cf1c00/contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35", size = 1322833 }, + { url = "https://files.pythonhosted.org/packages/73/e3/69738782e315a1d26d29d71a550dbbe3eb6c653b028b150f70c1a5f4f229/contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb", size = 172681 }, + { url = "https://files.pythonhosted.org/packages/0c/89/9830ba00d88e43d15e53d64931e66b8792b46eb25e2050a88fec4a0df3d5/contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b", size = 218283 }, + { url = "https://files.pythonhosted.org/packages/b3/e3/b9f72758adb6ef7397327ceb8b9c39c75711affb220e4f53c745ea1d5a9a/contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8", size = 265518 }, + { url = "https://files.pythonhosted.org/packages/ec/22/19f5b948367ab5260fb41d842c7a78dae645603881ea6bc39738bcfcabf6/contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c", size = 249350 }, + { url = "https://files.pythonhosted.org/packages/26/76/0c7d43263dd00ae21a91a24381b7e813d286a3294d95d179ef3a7b9fb1d7/contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca", size = 309167 }, + { url = "https://files.pythonhosted.org/packages/96/3b/cadff6773e89f2a5a492c1a8068e21d3fccaf1a1c1df7d65e7c8e3ef60ba/contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f", size = 348279 }, + { url = "https://files.pythonhosted.org/packages/e1/86/158cc43aa549d2081a955ab11c6bdccc7a22caacc2af93186d26f5f48746/contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc", size = 318519 }, + { url = "https://files.pythonhosted.org/packages/05/11/57335544a3027e9b96a05948c32e566328e3a2f84b7b99a325b7a06d2b06/contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2", size = 321922 }, + { url = "https://files.pythonhosted.org/packages/0b/e3/02114f96543f4a1b694333b92a6dcd4f8eebbefcc3a5f3bbb1316634178f/contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e", size = 1258017 }, + { url = "https://files.pythonhosted.org/packages/f3/3b/bfe4c81c6d5881c1c643dde6620be0b42bf8aab155976dd644595cfab95c/contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800", size = 1316773 }, + { url = "https://files.pythonhosted.org/packages/f1/17/c52d2970784383cafb0bd918b6fb036d98d96bbf0bc1befb5d1e31a07a70/contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5", size = 171353 }, + { url = "https://files.pythonhosted.org/packages/53/23/db9f69676308e094d3c45f20cc52e12d10d64f027541c995d89c11ad5c75/contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843", size = 211817 }, + { url = "https://files.pythonhosted.org/packages/d1/09/60e486dc2b64c94ed33e58dcfb6f808192c03dfc5574c016218b9b7680dc/contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c", size = 261886 }, + { url = "https://files.pythonhosted.org/packages/19/20/b57f9f7174fcd439a7789fb47d764974ab646fa34d1790551de386457a8e/contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779", size = 311008 }, + { url = "https://files.pythonhosted.org/packages/74/fc/5040d42623a1845d4f17a418e590fd7a79ae8cb2bad2b2f83de63c3bdca4/contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4", size = 215690 }, + { url = "https://files.pythonhosted.org/packages/2b/24/dc3dcd77ac7460ab7e9d2b01a618cb31406902e50e605a8d6091f0a8f7cc/contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0", size = 261894 }, + { url = "https://files.pythonhosted.org/packages/b1/db/531642a01cfec39d1682e46b5457b07cf805e3c3c584ec27e2a6223f8f6c/contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102", size = 311099 }, + { url = "https://files.pythonhosted.org/packages/38/1e/94bda024d629f254143a134eead69e21c836429a2a6ce82209a00ddcb79a/contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb", size = 215838 }, +] + +[[package]] +name = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551 }, + { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399 }, + { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061 }, + { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956 }, + { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872 }, + { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027 }, + { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641 }, + { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075 }, + { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534 }, + { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188 }, + { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636 }, + { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636 }, + { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053 }, + { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985 }, + { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750 }, + { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246 }, + { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728 }, + { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762 }, + { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196 }, + { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017 }, + { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580 }, + { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530 }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688 }, + { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331 }, + { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963 }, + { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681 }, + { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674 }, + { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480 }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489 }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042 }, + { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681 }, + { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101 }, + { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599 }, + { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807 }, + { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729 }, + { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791 }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773 }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149 }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222 }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234 }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555 }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238 }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218 }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867 }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677 }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234 }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123 }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419 }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979 }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653 }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536 }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397 }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601 }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288 }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386 }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018 }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567 }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655 }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809 }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593 }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202 }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207 }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315 }, +] + +[[package]] +name = "coverage" +version = "7.10.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6c/3a3f7a46888e69d18abe3ccc6fe4cb16cccb1e6a2f99698931dafca489e6/coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a", size = 217987 }, + { url = "https://files.pythonhosted.org/packages/03/94/952d30f180b1a916c11a56f5c22d3535e943aa22430e9e3322447e520e1c/coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5", size = 218388 }, + { url = "https://files.pythonhosted.org/packages/50/2b/9e0cf8ded1e114bcd8b2fd42792b57f1c4e9e4ea1824cde2af93a67305be/coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17", size = 245148 }, + { url = "https://files.pythonhosted.org/packages/19/20/d0384ac06a6f908783d9b6aa6135e41b093971499ec488e47279f5b846e6/coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b", size = 246958 }, + { url = "https://files.pythonhosted.org/packages/60/83/5c283cff3d41285f8eab897651585db908a909c572bdc014bcfaf8a8b6ae/coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87", size = 248819 }, + { url = "https://files.pythonhosted.org/packages/60/22/02eb98fdc5ff79f423e990d877693e5310ae1eab6cb20ae0b0b9ac45b23b/coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e", size = 245754 }, + { url = "https://files.pythonhosted.org/packages/b4/bc/25c83bcf3ad141b32cd7dc45485ef3c01a776ca3aa8ef0a93e77e8b5bc43/coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e", size = 246860 }, + { url = "https://files.pythonhosted.org/packages/3c/b7/95574702888b58c0928a6e982038c596f9c34d52c5e5107f1eef729399b5/coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df", size = 244877 }, + { url = "https://files.pythonhosted.org/packages/47/b6/40095c185f235e085df0e0b158f6bd68cc6e1d80ba6c7721dc81d97ec318/coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0", size = 245108 }, + { url = "https://files.pythonhosted.org/packages/c8/50/4aea0556da7a4b93ec9168420d170b55e2eb50ae21b25062513d020c6861/coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13", size = 245752 }, + { url = "https://files.pythonhosted.org/packages/6a/28/ea1a84a60828177ae3b100cb6723838523369a44ec5742313ed7db3da160/coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b", size = 220497 }, + { url = "https://files.pythonhosted.org/packages/fc/1a/a81d46bbeb3c3fd97b9602ebaa411e076219a150489bcc2c025f151bd52d/coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807", size = 221392 }, + { url = "https://files.pythonhosted.org/packages/d2/5d/c1a17867b0456f2e9ce2d8d4708a4c3a089947d0bec9c66cdf60c9e7739f/coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59", size = 218102 }, + { url = "https://files.pythonhosted.org/packages/54/f0/514dcf4b4e3698b9a9077f084429681bf3aad2b4a72578f89d7f643eb506/coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a", size = 218505 }, + { url = "https://files.pythonhosted.org/packages/20/f6/9626b81d17e2a4b25c63ac1b425ff307ecdeef03d67c9a147673ae40dc36/coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699", size = 248898 }, + { url = "https://files.pythonhosted.org/packages/b0/ef/bd8e719c2f7417ba03239052e099b76ea1130ac0cbb183ee1fcaa58aaff3/coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d", size = 250831 }, + { url = "https://files.pythonhosted.org/packages/a5/b6/bf054de41ec948b151ae2b79a55c107f5760979538f5fb80c195f2517718/coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e", size = 252937 }, + { url = "https://files.pythonhosted.org/packages/0f/e5/3860756aa6f9318227443c6ce4ed7bf9e70bb7f1447a0353f45ac5c7974b/coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23", size = 249021 }, + { url = "https://files.pythonhosted.org/packages/26/0f/bd08bd042854f7fd07b45808927ebcce99a7ed0f2f412d11629883517ac2/coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab", size = 250626 }, + { url = "https://files.pythonhosted.org/packages/8e/a7/4777b14de4abcc2e80c6b1d430f5d51eb18ed1d75fca56cbce5f2db9b36e/coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82", size = 248682 }, + { url = "https://files.pythonhosted.org/packages/34/72/17d082b00b53cd45679bad682fac058b87f011fd8b9fe31d77f5f8d3a4e4/coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2", size = 248402 }, + { url = "https://files.pythonhosted.org/packages/81/7a/92367572eb5bdd6a84bfa278cc7e97db192f9f45b28c94a9ca1a921c3577/coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61", size = 249320 }, + { url = "https://files.pythonhosted.org/packages/2f/88/a23cc185f6a805dfc4fdf14a94016835eeb85e22ac3a0e66d5e89acd6462/coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14", size = 220536 }, + { url = "https://files.pythonhosted.org/packages/fe/ef/0b510a399dfca17cec7bc2f05ad8bd78cf55f15c8bc9a73ab20c5c913c2e/coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2", size = 221425 }, + { url = "https://files.pythonhosted.org/packages/51/7f/023657f301a276e4ba1850f82749bc136f5a7e8768060c2e5d9744a22951/coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a", size = 220103 }, + { url = "https://files.pythonhosted.org/packages/13/e4/eb12450f71b542a53972d19117ea5a5cea1cab3ac9e31b0b5d498df1bd5a/coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417", size = 218290 }, + { url = "https://files.pythonhosted.org/packages/37/66/593f9be12fc19fb36711f19a5371af79a718537204d16ea1d36f16bd78d2/coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973", size = 218515 }, + { url = "https://files.pythonhosted.org/packages/66/80/4c49f7ae09cafdacc73fbc30949ffe77359635c168f4e9ff33c9ebb07838/coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c", size = 250020 }, + { url = "https://files.pythonhosted.org/packages/a6/90/a64aaacab3b37a17aaedd83e8000142561a29eb262cede42d94a67f7556b/coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7", size = 252769 }, + { url = "https://files.pythonhosted.org/packages/98/2e/2dda59afd6103b342e096f246ebc5f87a3363b5412609946c120f4e7750d/coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6", size = 253901 }, + { url = "https://files.pythonhosted.org/packages/53/dc/8d8119c9051d50f3119bb4a75f29f1e4a6ab9415cd1fa8bf22fcc3fb3b5f/coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59", size = 250413 }, + { url = "https://files.pythonhosted.org/packages/98/b3/edaff9c5d79ee4d4b6d3fe046f2b1d799850425695b789d491a64225d493/coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b", size = 251820 }, + { url = "https://files.pythonhosted.org/packages/11/25/9a0728564bb05863f7e513e5a594fe5ffef091b325437f5430e8cfb0d530/coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a", size = 249941 }, + { url = "https://files.pythonhosted.org/packages/e0/fd/ca2650443bfbef5b0e74373aac4df67b08180d2f184b482c41499668e258/coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb", size = 249519 }, + { url = "https://files.pythonhosted.org/packages/24/79/f692f125fb4299b6f963b0745124998ebb8e73ecdfce4ceceb06a8c6bec5/coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1", size = 251375 }, + { url = "https://files.pythonhosted.org/packages/5e/75/61b9bbd6c7d24d896bfeec57acba78e0f8deac68e6baf2d4804f7aae1f88/coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256", size = 220699 }, + { url = "https://files.pythonhosted.org/packages/ca/f3/3bf7905288b45b075918d372498f1cf845b5b579b723c8fd17168018d5f5/coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba", size = 221512 }, + { url = "https://files.pythonhosted.org/packages/5c/44/3e32dbe933979d05cf2dac5e697c8599cfe038aaf51223ab901e208d5a62/coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf", size = 220147 }, + { url = "https://files.pythonhosted.org/packages/a3/ad/d1c25053764b4c42eb294aae92ab617d2e4f803397f9c7c8295caa77a260/coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3", size = 217978 }, + { url = "https://files.pythonhosted.org/packages/52/2f/b9f9daa39b80ece0b9548bbb723381e29bc664822d9a12c2135f8922c22b/coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c", size = 218370 }, + { url = "https://files.pythonhosted.org/packages/dd/6e/30d006c3b469e58449650642383dddf1c8fb63d44fdf92994bfd46570695/coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396", size = 244802 }, + { url = "https://files.pythonhosted.org/packages/b0/49/8a070782ce7e6b94ff6a0b6d7c65ba6bc3091d92a92cef4cd4eb0767965c/coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40", size = 246625 }, + { url = "https://files.pythonhosted.org/packages/6a/92/1c1c5a9e8677ce56d42b97bdaca337b2d4d9ebe703d8c174ede52dbabd5f/coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594", size = 248399 }, + { url = "https://files.pythonhosted.org/packages/c0/54/b140edee7257e815de7426d5d9846b58505dffc29795fff2dfb7f8a1c5a0/coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a", size = 245142 }, + { url = "https://files.pythonhosted.org/packages/e4/9e/6d6b8295940b118e8b7083b29226c71f6154f7ff41e9ca431f03de2eac0d/coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b", size = 246284 }, + { url = "https://files.pythonhosted.org/packages/db/e5/5e957ca747d43dbe4d9714358375c7546cb3cb533007b6813fc20fce37ad/coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3", size = 244353 }, + { url = "https://files.pythonhosted.org/packages/9a/45/540fc5cc92536a1b783b7ef99450bd55a4b3af234aae35a18a339973ce30/coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0", size = 244430 }, + { url = "https://files.pythonhosted.org/packages/75/0b/8287b2e5b38c8fe15d7e3398849bb58d382aedc0864ea0fa1820e8630491/coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f", size = 245311 }, + { url = "https://files.pythonhosted.org/packages/0c/1d/29724999984740f0c86d03e6420b942439bf5bd7f54d4382cae386a9d1e9/coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431", size = 220500 }, + { url = "https://files.pythonhosted.org/packages/43/11/4b1e6b129943f905ca54c339f343877b55b365ae2558806c1be4f7476ed5/coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07", size = 221408 }, + { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952 }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version < '3.10'" }, +] + +[[package]] +name = "coverage" +version = "7.13.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/45/2c665ca77ec32ad67e25c77daf1cee28ee4558f3bc571cdbaf88a00b9f23/coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936", size = 820905 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/08/bdd7ccca14096f7eb01412b87ac11e5d16e4cb54b6e328afc9dee8bdaec1/coverage-7.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:02d9fb9eccd48f6843c98a37bd6817462f130b86da8660461e8f5e54d4c06070", size = 217979 }, + { url = "https://files.pythonhosted.org/packages/fa/f0/d1302e3416298a28b5663ae1117546a745d9d19fde7e28402b2c5c3e2109/coverage-7.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:367449cf07d33dc216c083f2036bb7d976c6e4903ab31be400ad74ad9f85ce98", size = 218496 }, + { url = "https://files.pythonhosted.org/packages/07/26/d36c354c8b2a320819afcea6bffe72839efd004b98d1d166b90801d49d57/coverage-7.13.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cdb3c9f8fef0a954c632f64328a3935988d33a6604ce4bf67ec3e39670f12ae5", size = 245237 }, + { url = "https://files.pythonhosted.org/packages/91/52/be5e85631e0eec547873d8b08dd67a5f6b111ecfe89a86e40b89b0c1c61c/coverage-7.13.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d10fd186aac2316f9bbb46ef91977f9d394ded67050ad6d84d94ed6ea2e8e54e", size = 247061 }, + { url = "https://files.pythonhosted.org/packages/0f/45/a5e8fa0caf05fbd8fa0402470377bff09cc1f026d21c05c71e01295e55ab/coverage-7.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f88ae3e69df2ab62fb0bc5219a597cb890ba5c438190ffa87490b315190bb33", size = 248928 }, + { url = "https://files.pythonhosted.org/packages/f5/42/ffb5069b6fd1b95fae482e02f3fecf380d437dd5a39bae09f16d2e2e7e01/coverage-7.13.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4be718e51e86f553bcf515305a158a1cd180d23b72f07ae76d6017c3cc5d791", size = 245931 }, + { url = "https://files.pythonhosted.org/packages/95/6e/73e809b882c2858f13e55c0c36e94e09ce07e6165d5644588f9517efe333/coverage-7.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a00d3a393207ae12f7c49bb1c113190883b500f48979abb118d8b72b8c95c032", size = 246968 }, + { url = "https://files.pythonhosted.org/packages/87/08/64ebd9e64b6adb8b4a4662133d706fbaccecab972e0b3ccc23f64e2678ad/coverage-7.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a7b1cd820e1b6116f92c6128f1188e7afe421c7e1b35fa9836b11444e53ebd9", size = 244972 }, + { url = "https://files.pythonhosted.org/packages/12/97/f4d27c6fe0cb375a5eced4aabcaef22de74766fb80a3d5d2015139e54b22/coverage-7.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:37eee4e552a65866f15dedd917d5e5f3d59805994260720821e2c1b51ac3248f", size = 245241 }, + { url = "https://files.pythonhosted.org/packages/0c/94/42f8ae7f633bf4c118bf1038d80472f9dade88961a466f290b81250f7ab7/coverage-7.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:62d7c4f13102148c78d7353c6052af6d899a7f6df66a32bddcc0c0eb7c5326f8", size = 245847 }, + { url = "https://files.pythonhosted.org/packages/a8/2f/6369ca22b6b6d933f4f4d27765d313d8914cc4cce84f82a16436b1a233db/coverage-7.13.0-cp310-cp310-win32.whl", hash = "sha256:24e4e56304fdb56f96f80eabf840eab043b3afea9348b88be680ec5986780a0f", size = 220573 }, + { url = "https://files.pythonhosted.org/packages/f1/dc/a6a741e519acceaeccc70a7f4cfe5d030efc4b222595f0677e101af6f1f3/coverage-7.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:74c136e4093627cf04b26a35dab8cbfc9b37c647f0502fc313376e11726ba303", size = 221509 }, + { url = "https://files.pythonhosted.org/packages/f1/dc/888bf90d8b1c3d0b4020a40e52b9f80957d75785931ec66c7dfaccc11c7d/coverage-7.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0dfa3855031070058add1a59fdfda0192fd3e8f97e7c81de0596c145dea51820", size = 218104 }, + { url = "https://files.pythonhosted.org/packages/8d/ea/069d51372ad9c380214e86717e40d1a743713a2af191cfba30a0911b0a4a/coverage-7.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fdb6f54f38e334db97f72fa0c701e66d8479af0bc3f9bfb5b90f1c30f54500f", size = 218606 }, + { url = "https://files.pythonhosted.org/packages/68/09/77b1c3a66c2aa91141b6c4471af98e5b1ed9b9e6d17255da5eb7992299e3/coverage-7.13.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7e442c013447d1d8d195be62852270b78b6e255b79b8675bad8479641e21fd96", size = 248999 }, + { url = "https://files.pythonhosted.org/packages/0a/32/2e2f96e9d5691eaf1181d9040f850b8b7ce165ea10810fd8e2afa534cef7/coverage-7.13.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ed5630d946859de835a85e9a43b721123a8a44ec26e2830b296d478c7fd4259", size = 250925 }, + { url = "https://files.pythonhosted.org/packages/7b/45/b88ddac1d7978859b9a39a8a50ab323186148f1d64bc068f86fc77706321/coverage-7.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f15a931a668e58087bc39d05d2b4bf4b14ff2875b49c994bbdb1c2217a8daeb", size = 253032 }, + { url = "https://files.pythonhosted.org/packages/71/cb/e15513f94c69d4820a34b6bf3d2b1f9f8755fa6021be97c7065442d7d653/coverage-7.13.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30a3a201a127ea57f7e14ba43c93c9c4be8b7d17a26e03bb49e6966d019eede9", size = 249134 }, + { url = "https://files.pythonhosted.org/packages/09/61/d960ff7dc9e902af3310ce632a875aaa7860f36d2bc8fc8b37ee7c1b82a5/coverage-7.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a485ff48fbd231efa32d58f479befce52dcb6bfb2a88bb7bf9a0b89b1bc8030", size = 250731 }, + { url = "https://files.pythonhosted.org/packages/98/34/c7c72821794afc7c7c2da1db8f00c2c98353078aa7fb6b5ff36aac834b52/coverage-7.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:22486cdafba4f9e471c816a2a5745337742a617fef68e890d8baf9f3036d7833", size = 248795 }, + { url = "https://files.pythonhosted.org/packages/0a/5b/e0f07107987a43b2def9aa041c614ddb38064cbf294a71ef8c67d43a0cdd/coverage-7.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:263c3dbccc78e2e331e59e90115941b5f53e85cfcc6b3b2fbff1fd4e3d2c6ea8", size = 248514 }, + { url = "https://files.pythonhosted.org/packages/71/c2/c949c5d3b5e9fc6dd79e1b73cdb86a59ef14f3709b1d72bf7668ae12e000/coverage-7.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5330fa0cc1f5c3c4c3bb8e101b742025933e7848989370a1d4c8c5e401ea753", size = 249424 }, + { url = "https://files.pythonhosted.org/packages/11/f1/bbc009abd6537cec0dffb2cc08c17a7f03de74c970e6302db4342a6e05af/coverage-7.13.0-cp311-cp311-win32.whl", hash = "sha256:0f4872f5d6c54419c94c25dd6ae1d015deeb337d06e448cd890a1e89a8ee7f3b", size = 220597 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/d9977f2fb51c10fbaed0718ce3d0a8541185290b981f73b1d27276c12d91/coverage-7.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51a202e0f80f241ccb68e3e26e19ab5b3bf0f813314f2c967642f13ebcf1ddfe", size = 221536 }, + { url = "https://files.pythonhosted.org/packages/be/ad/3fcf43fd96fb43e337a3073dea63ff148dcc5c41ba7a14d4c7d34efb2216/coverage-7.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:d2a9d7f1c11487b1c69367ab3ac2d81b9b3721f097aa409a3191c3e90f8f3dd7", size = 220206 }, + { url = "https://files.pythonhosted.org/packages/9b/f1/2619559f17f31ba00fc40908efd1fbf1d0a5536eb75dc8341e7d660a08de/coverage-7.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0b3d67d31383c4c68e19a88e28fc4c2e29517580f1b0ebec4a069d502ce1e0bf", size = 218274 }, + { url = "https://files.pythonhosted.org/packages/2b/11/30d71ae5d6e949ff93b2a79a2c1b4822e00423116c5c6edfaeef37301396/coverage-7.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:581f086833d24a22c89ae0fe2142cfaa1c92c930adf637ddf122d55083fb5a0f", size = 218638 }, + { url = "https://files.pythonhosted.org/packages/79/c2/fce80fc6ded8d77e53207489d6065d0fed75db8951457f9213776615e0f5/coverage-7.13.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a3a30f0e257df382f5f9534d4ce3d4cf06eafaf5192beb1a7bd066cb10e78fb", size = 250129 }, + { url = "https://files.pythonhosted.org/packages/5b/b6/51b5d1eb6fcbb9a1d5d6984e26cbe09018475c2922d554fd724dd0f056ee/coverage-7.13.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:583221913fbc8f53b88c42e8dbb8fca1d0f2e597cb190ce45916662b8b9d9621", size = 252885 }, + { url = "https://files.pythonhosted.org/packages/0d/f8/972a5affea41de798691ab15d023d3530f9f56a72e12e243f35031846ff7/coverage-7.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f5d9bd30756fff3e7216491a0d6d520c448d5124d3d8e8f56446d6412499e74", size = 253974 }, + { url = "https://files.pythonhosted.org/packages/8a/56/116513aee860b2c7968aa3506b0f59b22a959261d1dbf3aea7b4450a7520/coverage-7.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a23e5a1f8b982d56fa64f8e442e037f6ce29322f1f9e6c2344cd9e9f4407ee57", size = 250538 }, + { url = "https://files.pythonhosted.org/packages/d6/75/074476d64248fbadf16dfafbf93fdcede389ec821f74ca858d7c87d2a98c/coverage-7.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b01c22bc74a7fb44066aaf765224c0d933ddf1f5047d6cdfe4795504a4493f8", size = 251912 }, + { url = "https://files.pythonhosted.org/packages/f2/d2/aa4f8acd1f7c06024705c12609d8698c51b27e4d635d717cd1934c9668e2/coverage-7.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:898cce66d0836973f48dda4e3514d863d70142bdf6dfab932b9b6a90ea5b222d", size = 250054 }, + { url = "https://files.pythonhosted.org/packages/19/98/8df9e1af6a493b03694a1e8070e024e7d2cdc77adedc225a35e616d505de/coverage-7.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3ab483ea0e251b5790c2aac03acde31bff0c736bf8a86829b89382b407cd1c3b", size = 249619 }, + { url = "https://files.pythonhosted.org/packages/d8/71/f8679231f3353018ca66ef647fa6fe7b77e6bff7845be54ab84f86233363/coverage-7.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d84e91521c5e4cb6602fe11ece3e1de03b2760e14ae4fcf1a4b56fa3c801fcd", size = 251496 }, + { url = "https://files.pythonhosted.org/packages/04/86/9cb406388034eaf3c606c22094edbbb82eea1fa9d20c0e9efadff20d0733/coverage-7.13.0-cp312-cp312-win32.whl", hash = "sha256:193c3887285eec1dbdb3f2bd7fbc351d570ca9c02ca756c3afbc71b3c98af6ef", size = 220808 }, + { url = "https://files.pythonhosted.org/packages/1c/59/af483673df6455795daf5f447c2f81a3d2fcfc893a22b8ace983791f6f34/coverage-7.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f3e223b2b2db5e0db0c2b97286aba0036ca000f06aca9b12112eaa9af3d92ae", size = 221616 }, + { url = "https://files.pythonhosted.org/packages/64/b0/959d582572b30a6830398c60dd419c1965ca4b5fb38ac6b7093a0d50ca8d/coverage-7.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:086cede306d96202e15a4b77ace8472e39d9f4e5f9fd92dd4fecdfb2313b2080", size = 220261 }, + { url = "https://files.pythonhosted.org/packages/8d/4c/1968f32fb9a2604645827e11ff84a31e59d532e01995f904723b4f5328b3/coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904", size = 210068 }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version >= '3.10' and python_full_version <= '3.11'" }, +] + +[[package]] +name = "cryptography" +version = "46.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667 }, + { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807 }, + { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615 }, + { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800 }, + { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707 }, + { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541 }, + { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464 }, + { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838 }, + { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596 }, + { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782 }, + { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381 }, + { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089 }, + { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029 }, + { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222 }, + { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280 }, + { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958 }, + { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714 }, + { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970 }, + { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236 }, + { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642 }, + { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126 }, + { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573 }, + { url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992 }, + { url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944 }, + { url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", size = 4242957 }, + { url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447 }, +] + +[[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 = "dcor" +version = "0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numba", version = "0.60.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numba", version = "0.63.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/a7/1d06e98f1b123be60ba5de004edba510025da689c8cfb501299a8f2ba1d1/dcor-0.6.tar.gz", hash = "sha256:f5d39776101db4787348e6be6cd9369341efeb40b070509a30d5c57185558431", size = 45509 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/f3/49770c523067d2179a600f236ea6d55f0a02909a424d055dbc50e04c4860/dcor-0.6-py3-none-any.whl", hash = "sha256:de306fc666668188749730fc803fc1d4d804d9886c92b622ba57b434fed395a2", size = 55545 }, +] + +[[package]] +name = "debugpy" +version = "1.8.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/73/75/9e12d4d42349b817cd545b89247696c67917aab907012ae5b64bbfea3199/debugpy-1.8.19.tar.gz", hash = "sha256:eea7e5987445ab0b5ed258093722d5ecb8bb72217c5c9b1e21f64efe23ddebdb", size = 1644590 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/98/d57054371887f37d3c959a7a8dc3c76b763acb65f5e78d849d7db7cadc5b/debugpy-1.8.19-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:fce6da15d73be5935b4438435c53adb512326a3e11e4f90793ea87cd9f018254", size = 2098493 }, + { url = "https://files.pythonhosted.org/packages/ee/dd/c517b9aa3500157a30e4f4c4f5149f880026bd039d2b940acd2383a85d8e/debugpy-1.8.19-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:e24b1652a1df1ab04d81e7ead446a91c226de704ff5dde6bd0a0dbaab07aa3f2", size = 3087875 }, + { url = "https://files.pythonhosted.org/packages/d8/57/3d5a5b0da9b63445253107ead151eff29190c6ad7440c68d1a59d56613aa/debugpy-1.8.19-cp310-cp310-win32.whl", hash = "sha256:327cb28c3ad9e17bc925efc7f7018195fd4787c2fe4b7af1eec11f1d19bdec62", size = 5239378 }, + { url = "https://files.pythonhosted.org/packages/a6/36/7f9053c4c549160c87ae7e43800138f2695578c8b65947114c97250983b6/debugpy-1.8.19-cp310-cp310-win_amd64.whl", hash = "sha256:b7dd275cf2c99e53adb9654f5ae015f70415bbe2bacbe24cfee30d54b6aa03c5", size = 5271129 }, + { url = "https://files.pythonhosted.org/packages/80/e2/48531a609b5a2aa94c6b6853afdfec8da05630ab9aaa96f1349e772119e9/debugpy-1.8.19-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:c5dcfa21de1f735a4f7ced4556339a109aa0f618d366ede9da0a3600f2516d8b", size = 2207620 }, + { url = "https://files.pythonhosted.org/packages/1b/d4/97775c01d56071969f57d93928899e5616a4cfbbf4c8cc75390d3a51c4a4/debugpy-1.8.19-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:806d6800246244004625d5222d7765874ab2d22f3ba5f615416cf1342d61c488", size = 3170796 }, + { url = "https://files.pythonhosted.org/packages/8d/7e/8c7681bdb05be9ec972bbb1245eb7c4c7b0679bb6a9e6408d808bc876d3d/debugpy-1.8.19-cp311-cp311-win32.whl", hash = "sha256:783a519e6dfb1f3cd773a9bda592f4887a65040cb0c7bd38dde410f4e53c40d4", size = 5164287 }, + { url = "https://files.pythonhosted.org/packages/f2/a8/aaac7ff12ddf5d68a39e13a423a8490426f5f661384f5ad8d9062761bd8e/debugpy-1.8.19-cp311-cp311-win_amd64.whl", hash = "sha256:14035cbdbb1fe4b642babcdcb5935c2da3b1067ac211c5c5a8fdc0bb31adbcaa", size = 5188269 }, + { url = "https://files.pythonhosted.org/packages/4a/15/d762e5263d9e25b763b78be72dc084c7a32113a0bac119e2f7acae7700ed/debugpy-1.8.19-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:bccb1540a49cde77edc7ce7d9d075c1dbeb2414751bc0048c7a11e1b597a4c2e", size = 2549995 }, + { url = "https://files.pythonhosted.org/packages/a7/88/f7d25c68b18873b7c53d7c156ca7a7ffd8e77073aa0eac170a9b679cf786/debugpy-1.8.19-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:e9c68d9a382ec754dc05ed1d1b4ed5bd824b9f7c1a8cd1083adb84b3c93501de", size = 4309891 }, + { url = "https://files.pythonhosted.org/packages/c5/4f/a65e973aba3865794da65f71971dca01ae66666132c7b2647182d5be0c5f/debugpy-1.8.19-cp312-cp312-win32.whl", hash = "sha256:6599cab8a783d1496ae9984c52cb13b7c4a3bd06a8e6c33446832a5d97ce0bee", size = 5286355 }, + { url = "https://files.pythonhosted.org/packages/d8/3a/d3d8b48fec96e3d824e404bf428276fb8419dfa766f78f10b08da1cb2986/debugpy-1.8.19-cp312-cp312-win_amd64.whl", hash = "sha256:66e3d2fd8f2035a8f111eb127fa508469dfa40928a89b460b41fd988684dc83d", size = 5328239 }, + { url = "https://files.pythonhosted.org/packages/0b/27/9e6223367eef0bc98299418768b4e885ce3c14bb6fd03473a1b8729b1163/debugpy-1.8.19-cp39-cp39-macosx_15_0_x86_64.whl", hash = "sha256:c047177ab2d286451f242b855b650d313198c4a987140d4b35218b2855a64a4a", size = 2099782 }, + { url = "https://files.pythonhosted.org/packages/3c/ab/7f3dccc256a18b535c915a84501925e50f95f0e4bc8b85779932a952b71f/debugpy-1.8.19-cp39-cp39-manylinux_2_34_x86_64.whl", hash = "sha256:4468de0c30012d367944f0eab4ecb8371736e8ef9522a465f61214f344c11183", size = 3080573 }, + { url = "https://files.pythonhosted.org/packages/a7/78/00581bffa724a0d4ddfd7172863c48afe3776c72110289f40c06baec6d23/debugpy-1.8.19-cp39-cp39-win32.whl", hash = "sha256:7b62c0f015120ede25e5124a5f9d8a424e1208e3d96a36c89958f046ee21fff6", size = 5240246 }, + { url = "https://files.pythonhosted.org/packages/5b/a7/5731bea7b69070ee8a97b5a6fbe5d6e5dff66bdce347c97fa286cbc04119/debugpy-1.8.19-cp39-cp39-win_amd64.whl", hash = "sha256:76f566baaf7f3e06adbe67ffedccd2ee911d1e486f55931939ce3f0fe1090774", size = 5271902 }, + { url = "https://files.pythonhosted.org/packages/25/3e/e27078370414ef35fafad2c06d182110073daaeb5d3bf734b0b1eeefe452/debugpy-1.8.19-py2.py3-none-any.whl", hash = "sha256:360ffd231a780abbc414ba0f005dad409e71c78637efe8f2bd75837132a41d38", size = 5292321 }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190 }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047 }, +] + +[[package]] +name = "docutils" +version = "0.17.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/17/559b4d020f4b46e0287a2eddf2d8ebf76318fd3bd495f1625414b052fdc9/docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125", size = 2016138 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/5e/6003a0d1f37725ec2ebd4046b657abb9372202655f96e76795dca8c0063c/docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61", size = 575533 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740 }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317 }, +] + +[[package]] +name = "fastjsonschema" +version = "2.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024 }, +] + +[[package]] +name = "filelock" +version = "3.19.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988 }, +] + +[[package]] +name = "filelock" +version = "3.20.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/23/ce7a1126827cedeb958fc043d61745754464eb56c5937c35bbf2b8e26f34/filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c", size = 19476 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/7f/a1a97644e39e7316d850784c642093c99df1290a460df4ede27659056834/filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a", size = 16666 }, +] + +[[package]] +name = "fonttools" +version = "4.60.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/c4/db6a7b5eb0656534c3aa2596c2c5e18830d74f1b9aa5aa8a7dff63a0b11d/fonttools-4.60.2.tar.gz", hash = "sha256:d29552e6b155ebfc685b0aecf8d429cb76c14ab734c22ef5d3dea6fdf800c92c", size = 3562254 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/de/9e10a99fb3070accb8884886a41a4ce54e49bf2fa4fc63f48a6cf2061713/fonttools-4.60.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4e36fadcf7e8ca6e34d490eef86ed638d6fd9c55d2f514b05687622cfc4a7050", size = 2850403 }, + { url = "https://files.pythonhosted.org/packages/e4/40/d5b369d1073b134f600a94a287e13b5bdea2191ba6347d813fa3da00e94a/fonttools-4.60.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6e500fc9c04bee749ceabfc20cb4903f6981c2139050d85720ea7ada61b75d5c", size = 2398629 }, + { url = "https://files.pythonhosted.org/packages/7c/b5/123819369aaf99d1e4dc49f1de1925d4edc7379114d15a56a7dd2e9d56e6/fonttools-4.60.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22efea5e784e1d1cd8d7b856c198e360a979383ebc6dea4604743b56da1cbc34", size = 4893471 }, + { url = "https://files.pythonhosted.org/packages/24/29/f8f8acccb9716b899be4be45e9ce770d6aa76327573863e68448183091b0/fonttools-4.60.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:677aa92d84d335e4d301d8ba04afca6f575316bc647b6782cb0921943fcb6343", size = 4854686 }, + { url = "https://files.pythonhosted.org/packages/5a/0d/f3f51d7519f44f2dd5c9a60d7cd41185ebcee4348f073e515a3a93af15ff/fonttools-4.60.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:edd49d3defbf35476e78b61ff737ff5efea811acff68d44233a95a5a48252334", size = 4871233 }, + { url = "https://files.pythonhosted.org/packages/cc/3f/4d4fd47d3bc40ab4d76718555185f8adffb5602ea572eac4bbf200c47d22/fonttools-4.60.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:126839492b69cecc5baf2bddcde60caab2ffafd867bbae2a88463fce6078ca3a", size = 4988936 }, + { url = "https://files.pythonhosted.org/packages/01/6f/83bbdefa43f2c3ae206fd8c4b9a481f3c913eef871b1ce9a453069239e39/fonttools-4.60.2-cp310-cp310-win32.whl", hash = "sha256:ffcab6f5537136046ca902ed2491ab081ba271b07591b916289b7c27ff845f96", size = 2278044 }, + { url = "https://files.pythonhosted.org/packages/d4/04/7d9a137e919d6c9ef26704b7f7b2580d9cfc5139597588227aacebc0e3b7/fonttools-4.60.2-cp310-cp310-win_amd64.whl", hash = "sha256:9c68b287c7ffcd29dd83b5f961004b2a54a862a88825d52ea219c6220309ba45", size = 2326522 }, + { url = "https://files.pythonhosted.org/packages/e0/80/b7693d37c02417e162cc83cdd0b19a4f58be82c638b5d4ce4de2dae050c4/fonttools-4.60.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a2aed0a7931401b3875265717a24c726f87ecfedbb7b3426c2ca4d2812e281ae", size = 2847809 }, + { url = "https://files.pythonhosted.org/packages/f9/9a/9c2c13bf8a6496ac21607d704e74e9cc68ebf23892cf924c9a8b5c7566b9/fonttools-4.60.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dea6868e9d2b816c9076cfea77754686f3c19149873bdbc5acde437631c15df1", size = 2397302 }, + { url = "https://files.pythonhosted.org/packages/56/f6/ce38ff6b2d2d58f6fd981d32f3942365bfa30eadf2b47d93b2d48bf6097f/fonttools-4.60.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fa27f34950aa1fe0f0b1abe25eed04770a3b3b34ad94e5ace82cc341589678a", size = 5054418 }, + { url = "https://files.pythonhosted.org/packages/88/06/5353bea128ff39e857c31de3dd605725b4add956badae0b31bc9a50d4c8e/fonttools-4.60.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13a53d479d187b09bfaa4a35ffcbc334fc494ff355f0a587386099cb66674f1e", size = 5031652 }, + { url = "https://files.pythonhosted.org/packages/71/05/ebca836437f6ebd57edd6428e7eff584e683ff0556ddb17d62e3b731f46c/fonttools-4.60.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fac5e921d3bd0ca3bb8517dced2784f0742bc8ca28579a68b139f04ea323a779", size = 5030321 }, + { url = "https://files.pythonhosted.org/packages/57/f9/eb9d2a2ce30c99f840c1cc3940729a970923cf39d770caf88909d98d516b/fonttools-4.60.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:648f4f9186fd7f1f3cd57dbf00d67a583720d5011feca67a5e88b3a491952cfb", size = 5154255 }, + { url = "https://files.pythonhosted.org/packages/08/a2/088b6ceba8272a9abb629d3c08f9c1e35e5ce42db0ccfe0c1f9f03e60d1d/fonttools-4.60.2-cp311-cp311-win32.whl", hash = "sha256:3274e15fad871bead5453d5ce02658f6d0c7bc7e7021e2a5b8b04e2f9e40da1a", size = 2276300 }, + { url = "https://files.pythonhosted.org/packages/de/2f/8e4c3d908cc5dade7bb1316ce48589f6a24460c1056fd4b8db51f1fa309a/fonttools-4.60.2-cp311-cp311-win_amd64.whl", hash = "sha256:91d058d5a483a1525b367803abb69de0923fbd45e1f82ebd000f5c8aa65bc78e", size = 2327574 }, + { url = "https://files.pythonhosted.org/packages/c0/30/530c9eddcd1c39219dc0aaede2b5a4c8ab80e0bb88d1b3ffc12944c4aac3/fonttools-4.60.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e0164b7609d2b5c5dd4e044b8085b7bd7ca7363ef8c269a4ab5b5d4885a426b2", size = 2847196 }, + { url = "https://files.pythonhosted.org/packages/19/2f/4077a482836d5bbe3bc9dac1c004d02ee227cf04ed62b0a2dfc41d4f0dfd/fonttools-4.60.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1dd3d9574fc595c1e97faccae0f264dc88784ddf7fbf54c939528378bacc0033", size = 2395842 }, + { url = "https://files.pythonhosted.org/packages/dd/05/aae5bb99c5398f8ed4a8b784f023fd9dd3568f0bd5d5b21e35b282550f11/fonttools-4.60.2-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:98d0719f1b11c2817307d2da2e94296a3b2a3503f8d6252a101dca3ee663b917", size = 4949713 }, + { url = "https://files.pythonhosted.org/packages/b4/37/49067349fc78ff0efbf09fadefe80ddf41473ca8f8a25400e3770da38328/fonttools-4.60.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d3ea26957dd07209f207b4fff64c702efe5496de153a54d3b91007ec28904dd", size = 4999907 }, + { url = "https://files.pythonhosted.org/packages/16/31/d0f11c758bd0db36b664c92a0f9dfdcc2d7313749aa7d6629805c6946f21/fonttools-4.60.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ee301273b0850f3a515299f212898f37421f42ff9adfc341702582ca5073c13", size = 4939717 }, + { url = "https://files.pythonhosted.org/packages/d9/bc/1cff0d69522e561bf1b99bee7c3911c08c25e919584827c3454a64651ce9/fonttools-4.60.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6eb4694cc3b9c03b7c01d65a9cf35b577f21aa6abdbeeb08d3114b842a58153", size = 5089205 }, + { url = "https://files.pythonhosted.org/packages/05/e6/fb174f0069b7122e19828c551298bfd34fdf9480535d2a6ac2ed37afacd3/fonttools-4.60.2-cp312-cp312-win32.whl", hash = "sha256:57f07b616c69c244cc1a5a51072eeef07dddda5ebef9ca5c6e9cf6d59ae65b70", size = 2264674 }, + { url = "https://files.pythonhosted.org/packages/75/57/6552ffd6b582d3e6a9f01780c5275e6dfff1e70ca146101733aa1c12a129/fonttools-4.60.2-cp312-cp312-win_amd64.whl", hash = "sha256:310035802392f1fe5a7cf43d76f6ff4a24c919e4c72c0352e7b8176e2584b8a0", size = 2314701 }, + { url = "https://files.pythonhosted.org/packages/55/ae/a6d9446cb258d3fe87e311c2d7bacf8e8da3e5809fbdc3a8306db4f6b14e/fonttools-4.60.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a3c75b8b42f7f93906bdba9eb1197bb76aecbe9a0a7cf6feec75f7605b5e8008", size = 2857184 }, + { url = "https://files.pythonhosted.org/packages/3a/f3/1b41d0b6a8b908aa07f652111155dd653ebbf0b3385e66562556c5206685/fonttools-4.60.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0f86c8c37bc0ec0b9c141d5e90c717ff614e93c187f06d80f18c7057097f71bc", size = 2401877 }, + { url = "https://files.pythonhosted.org/packages/71/57/048fd781680c38b05c5463657d0d95d5f2391a51972176e175c01de29d42/fonttools-4.60.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe905403fe59683b0e9a45f234af2866834376b8821f34633b1c76fb731b6311", size = 4878073 }, + { url = "https://files.pythonhosted.org/packages/45/bb/363364f052a893cebd3d449588b21244a9d873620fda03ad92702d2e1bc7/fonttools-4.60.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38ce703b60a906e421e12d9e3a7f064883f5e61bb23e8961f4be33cfe578500b", size = 4835385 }, + { url = "https://files.pythonhosted.org/packages/1c/38/e392bb930b2436287e6021672345db26441bf1f85f1e98f8b9784334e41d/fonttools-4.60.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9e810c06f3e79185cecf120e58b343ea5a89b54dd695fd644446bcf8c026da5e", size = 4853084 }, + { url = "https://files.pythonhosted.org/packages/65/60/0d77faeaecf7a3276a8a6dc49e2274357e6b3ed6a1774e2fdb2a7f142db0/fonttools-4.60.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:38faec8cc1d12122599814d15a402183f5123fb7608dac956121e7c6742aebc5", size = 4971144 }, + { url = "https://files.pythonhosted.org/packages/ba/c7/6d3ac3afbcd598631bce24c3ecb919e7d0644a82fea8ddc4454312fc0be6/fonttools-4.60.2-cp39-cp39-win32.whl", hash = "sha256:80a45cf7bf659acb7b36578f300231873daba67bd3ca8cce181c73f861f14a37", size = 1499411 }, + { url = "https://files.pythonhosted.org/packages/5a/1c/9dedf6420e23f9fa630bb97941839dddd2e1e57d1b2b85a902378dbe0bd2/fonttools-4.60.2-cp39-cp39-win_amd64.whl", hash = "sha256:c355d5972071938e1b1e0f5a1df001f68ecf1a62f34a3407dc8e0beccf052501", size = 1547943 }, + { url = "https://files.pythonhosted.org/packages/79/6c/10280af05b44fafd1dff69422805061fa1af29270bc52dce031ac69540bf/fonttools-4.60.2-py3-none-any.whl", hash = "sha256:73cf92eeda67cf6ff10c8af56fc8f4f07c1647d989a979be9e388a49be26552a", size = 1144610 }, +] + +[[package]] +name = "fonttools" +version = "4.61.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/94/8a28707adb00bed1bf22dac16ccafe60faf2ade353dcb32c3617ee917307/fonttools-4.61.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c7db70d57e5e1089a274cbb2b1fd635c9a24de809a231b154965d415d6c6d24", size = 2854799 }, + { url = "https://files.pythonhosted.org/packages/94/93/c2e682faaa5ee92034818d8f8a8145ae73eb83619600495dcf8503fa7771/fonttools-4.61.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5fe9fd43882620017add5eabb781ebfbc6998ee49b35bd7f8f79af1f9f99a958", size = 2403032 }, + { url = "https://files.pythonhosted.org/packages/f1/62/1748f7e7e1ee41aa52279fd2e3a6d0733dc42a673b16932bad8e5d0c8b28/fonttools-4.61.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8db08051fc9e7d8bc622f2112511b8107d8f27cd89e2f64ec45e9825e8288da", size = 4897863 }, + { url = "https://files.pythonhosted.org/packages/69/69/4ca02ee367d2c98edcaeb83fc278d20972502ee071214ad9d8ca85e06080/fonttools-4.61.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a76d4cb80f41ba94a6691264be76435e5f72f2cb3cab0b092a6212855f71c2f6", size = 4859076 }, + { url = "https://files.pythonhosted.org/packages/8c/f5/660f9e3cefa078861a7f099107c6d203b568a6227eef163dd173bfc56bdc/fonttools-4.61.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a13fc8aeb24bad755eea8f7f9d409438eb94e82cf86b08fe77a03fbc8f6a96b1", size = 4875623 }, + { url = "https://files.pythonhosted.org/packages/63/d1/9d7c5091d2276ed47795c131c1bf9316c3c1ab2789c22e2f59e0572ccd38/fonttools-4.61.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b846a1fcf8beadeb9ea4f44ec5bdde393e2f1569e17d700bfc49cd69bde75881", size = 4993327 }, + { url = "https://files.pythonhosted.org/packages/6f/2d/28def73837885ae32260d07660a052b99f0aa00454867d33745dfe49dbf0/fonttools-4.61.1-cp310-cp310-win32.whl", hash = "sha256:78a7d3ab09dc47ac1a363a493e6112d8cabed7ba7caad5f54dbe2f08676d1b47", size = 1502180 }, + { url = "https://files.pythonhosted.org/packages/63/fa/bfdc98abb4dd2bd491033e85e3ba69a2313c850e759a6daa014bc9433b0f/fonttools-4.61.1-cp310-cp310-win_amd64.whl", hash = "sha256:eff1ac3cc66c2ac7cda1e64b4e2f3ffef474b7335f92fc3833fc632d595fcee6", size = 1550654 }, + { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213 }, + { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689 }, + { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809 }, + { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039 }, + { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714 }, + { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648 }, + { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681 }, + { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951 }, + { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593 }, + { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231 }, + { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103 }, + { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295 }, + { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109 }, + { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598 }, + { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060 }, + { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078 }, + { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996 }, +] + +[[package]] +name = "fqdn" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121 }, +] + +[[package]] +name = "fsspec" +version = "2025.10.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/24/7f/2747c0d332b9acfa75dc84447a066fdf812b5a6b8d30472b74d309bfe8cb/fsspec-2025.10.0.tar.gz", hash = "sha256:b6789427626f068f9a83ca4e8a3cc050850b6c0f71f99ddb4f542b8266a26a59", size = 309285 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/02/a6b21098b1d5d6249b7c5ab69dde30108a71e4e819d4a9778f1de1d5b70d/fsspec-2025.10.0-py3-none-any.whl", hash = "sha256:7c7712353ae7d875407f97715f0e1ffcc21e33d5b24556cb1e090ae9409ec61d", size = 200966 }, +] + +[[package]] +name = "fsspec" +version = "2025.12.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/27/954057b0d1f53f086f681755207dda6de6c660ce133c829158e8e8fe7895/fsspec-2025.12.0.tar.gz", hash = "sha256:c505de011584597b1060ff778bb664c1bc022e87921b0e4f10cc9c44f9635973", size = 309748 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/c7/b64cae5dba3a1b138d7123ec36bb5ccd39d39939f18454407e5468f4763f/fsspec-2025.12.0-py3-none-any.whl", hash = "sha256:8bf1fe301b7d8acfa6e8571e3b1c3d158f909666642431cc78a1b7b4dbc5ec5b", size = 201422 }, +] + +[[package]] +name = "identify" +version = "2.6.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183 }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008 }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865 }, +] + +[[package]] +name = "importlib-resources" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461 }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 }, +] + +[[package]] +name = "ipykernel" +version = "6.31.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "appnope", marker = "python_full_version < '3.10' and sys_platform == 'darwin'" }, + { name = "comm", marker = "python_full_version < '3.10'" }, + { name = "debugpy", marker = "python_full_version < '3.10'" }, + { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "jupyter-client", version = "8.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "jupyter-core", version = "5.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.10'" }, + { name = "nest-asyncio", marker = "python_full_version < '3.10'" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "psutil", marker = "python_full_version < '3.10'" }, + { name = "pyzmq", marker = "python_full_version < '3.10'" }, + { name = "tornado", marker = "python_full_version < '3.10'" }, + { name = "traitlets", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/1d/d5ba6edbfe6fae4c3105bca3a9c889563cc752c7f2de45e333164c7f4846/ipykernel-6.31.0.tar.gz", hash = "sha256:2372ce8bc1ff4f34e58cafed3a0feb2194b91fc7cad0fc72e79e47b45ee9e8f6", size = 167493 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl", hash = "sha256:abe5386f6ced727a70e0eb0cf1da801fa7c5fa6ff82147747d5a0406cd8c94af", size = 117003 }, +] + +[[package]] +name = "ipykernel" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "appnope", marker = "python_full_version >= '3.10' and sys_platform == 'darwin'" }, + { name = "comm", marker = "python_full_version >= '3.10'" }, + { name = "debugpy", marker = "python_full_version >= '3.10'" }, + { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "ipython", version = "9.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "jupyter-client", version = "8.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", version = "5.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.10'" }, + { name = "nest-asyncio", marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "psutil", marker = "python_full_version >= '3.10'" }, + { name = "pyzmq", marker = "python_full_version >= '3.10'" }, + { name = "tornado", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/a4/4948be6eb88628505b83a1f2f40d90254cab66abf2043b3c40fa07dfce0f/ipykernel-7.1.0.tar.gz", hash = "sha256:58a3fc88533d5930c3546dc7eac66c6d288acde4f801e2001e65edc5dc9cf0db", size = 174579 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl", hash = "sha256:763b5ec6c5b7776f6a8d7ce09b267693b4e5ce75cb50ae696aaefb3c85e1ea4c", size = 117968 }, +] + +[[package]] +name = "ipython" +version = "8.18.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version < '3.10'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, + { name = "jedi", marker = "python_full_version < '3.10'" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.10'" }, + { name = "pexpect", marker = "python_full_version < '3.10' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version < '3.10'" }, + { name = "pygments", marker = "python_full_version < '3.10'" }, + { name = "stack-data", marker = "python_full_version < '3.10'" }, + { name = "traitlets", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/b9/3ba6c45a6df813c09a48bac313c22ff83efa26cbb55011218d925a46e2ad/ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27", size = 5486330 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/6b/d9fdcdef2eb6a23f391251fde8781c38d42acd82abe84d054cb74f7863b0/ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397", size = 808161 }, +] + +[[package]] +name = "ipython" +version = "8.37.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version == '3.10.*' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version == '3.10.*'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "jedi", marker = "python_full_version == '3.10.*'" }, + { name = "matplotlib-inline", marker = "python_full_version == '3.10.*'" }, + { name = "pexpect", marker = "python_full_version == '3.10.*' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version == '3.10.*'" }, + { name = "pygments", marker = "python_full_version == '3.10.*'" }, + { name = "stack-data", marker = "python_full_version == '3.10.*'" }, + { name = "traitlets", marker = "python_full_version == '3.10.*'" }, + { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/31/10ac88f3357fc276dc8a64e8880c82e80e7459326ae1d0a211b40abf6665/ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216", size = 5606088 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/d0/274fbf7b0b12643cbbc001ce13e6a5b1607ac4929d1b11c72460152c9fc3/ipython-8.37.0-py3-none-any.whl", hash = "sha256:ed87326596b878932dbcb171e3e698845434d8c61b8d8cd474bf663041a9dcf2", size = 831864 }, +] + +[[package]] +name = "ipython" +version = "9.8.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version >= '3.11'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, + { name = "jedi", marker = "python_full_version >= '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, + { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "stack-data", marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/51/a703c030f4928646d390b4971af4938a1b10c9dfce694f0d99a0bb073cb2/ipython-9.8.0.tar.gz", hash = "sha256:8e4ce129a627eb9dd221c41b1d2cdaed4ef7c9da8c17c63f6f578fe231141f83", size = 4424940 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/df/8ee1c5dd1e3308b5d5b2f2dfea323bb2f3827da8d654abb6642051199049/ipython-9.8.0-py3-none-any.whl", hash = "sha256:ebe6d1d58d7d988fbf23ff8ff6d8e1622cfdb194daf4b7b73b792c4ec3b85385", size = 621374 }, +] + +[[package]] +name = "ipython-genutils" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/69/fbeffffc05236398ebfcfb512b6d2511c622871dca1746361006da310399/ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8", size = 22208 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/bc/9bd3b5c2b4774d5f33b2d544f1460be9df7df2fe42f352135381c347c69a/ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", size = 26343 }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074 }, +] + +[[package]] +name = "ipywidgets" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "comm" }, + { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "ipython", version = "9.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "jupyterlab-widgets" }, + { name = "traitlets" }, + { name = "widgetsnbextension" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/ae/c5ce1edc1afe042eadb445e95b0671b03cee61895264357956e61c0d2ac0/ipywidgets-8.1.8.tar.gz", hash = "sha256:61f969306b95f85fba6b6986b7fe45d73124d1d9e3023a8068710d47a22ea668", size = 116739 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl", hash = "sha256:ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e", size = 139808 }, +] + +[[package]] +name = "isoduration" +version = "20.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arrow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321 }, +] + +[[package]] +name = "jaraco-classes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777 }, +] + +[[package]] +name = "jaraco-context" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-tarfile", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/7d/41acf8e22d791bde812cb6c2c36128bb932ed8ae066bcb5e39cb198e8253/jaraco_context-6.0.2.tar.gz", hash = "sha256:953ae8dddb57b1d791bf72ea1009b32088840a7dd19b9ba16443f62be919ee57", size = 14994 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/0c/1e0096ced9c55f9c6c6655446798df74165780375d3f5ab5f33751e087ae/jaraco_context-6.0.2-py3-none-any.whl", hash = "sha256:55fc21af4b4f9ca94aa643b6ee7fe13b1e4c01abf3aeb98ca4ad9c80b741c786", size = 6988 }, +] + +[[package]] +name = "jaraco-functools" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481 }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 }, +] + +[[package]] +name = "jeepney" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010 }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "joblib" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071 }, +] + +[[package]] +name = "json5" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/ae/929aee9619e9eba9015207a9d2c1c54db18311da7eb4dcf6d41ad6f0eb67/json5-0.12.1.tar.gz", hash = "sha256:b2743e77b3242f8d03c143dd975a6ec7c52e2f2afe76ed934e53503dd4ad4990", size = 52191 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl", hash = "sha256:d9c9b3bc34a5f54d43c35e11ef7cb87d8bdd098c6ace87117a7b7e83e705c1d5", size = 36119 }, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595 }, +] + +[[package]] +name = "jsonschema" +version = "4.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing", version = "0.36.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "referencing", version = "0.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "rpds-py", version = "0.27.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040 }, +] + +[package.optional-dependencies] +format-nongpl = [ + { name = "fqdn" }, + { name = "idna" }, + { name = "isoduration" }, + { name = "jsonpointer" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "rfc3987-syntax" }, + { name = "uri-template" }, + { name = "webcolors", version = "24.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "webcolors", version = "25.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing", version = "0.36.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "referencing", version = "0.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437 }, +] + +[[package]] +name = "jupyter" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel", version = "6.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "ipykernel", version = "7.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "ipywidgets" }, + { name = "jupyter-console" }, + { name = "nbconvert" }, + { name = "notebook" }, + { name = "qtconsole" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/a9/371d0b8fe37dd231cf4b2cff0a9f0f25e98f3a73c3771742444be27f2944/jupyter-1.0.0.tar.gz", hash = "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f", size = 12916 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/df/0f5dd132200728a86190397e1ea87cd76244e42d39ec5e88efd25b2abd7e/jupyter-1.0.0-py2.py3-none-any.whl", hash = "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78", size = 2736 }, +] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "jupyter-core", version = "5.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "python-dateutil", marker = "python_full_version < '3.10'" }, + { name = "pyzmq", marker = "python_full_version < '3.10'" }, + { name = "tornado", marker = "python_full_version < '3.10'" }, + { name = "traitlets", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105 }, +] + +[[package]] +name = "jupyter-client" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "jupyter-core", version = "5.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, + { name = "pyzmq", marker = "python_full_version >= '3.10'" }, + { name = "tornado", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/27/d10de45e8ad4ce872372c4a3a37b7b35b6b064f6f023a5c14ffcced4d59d/jupyter_client-8.7.0.tar.gz", hash = "sha256:3357212d9cbe01209e59190f67a3a7e1f387a4f4e88d1e0433ad84d7b262531d", size = 344691 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/f5/fddaec430367be9d62a7ed125530e133bfd4a1c0350fe221149ee0f2b526/jupyter_client-8.7.0-py3-none-any.whl", hash = "sha256:3671a94fd25e62f5f2f554f5e95389c2294d89822378a5f2dd24353e1494a9e0", size = 106215 }, +] + +[[package]] +name = "jupyter-console" +version = "6.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel", version = "6.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "ipykernel", version = "7.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "ipython", version = "9.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "jupyter-client", version = "8.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "jupyter-client", version = "8.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", version = "5.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "jupyter-core", version = "5.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "pyzmq" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/2d/e2fd31e2fc41c14e2bcb6c976ab732597e907523f6b2420305f9fc7fdbdb/jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539", size = 34363 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/77/71d78d58f15c22db16328a476426f7ac4a60d3a5a7ba3b9627ee2f7903d4/jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485", size = 24510 }, +] + +[[package]] +name = "jupyter-core" +version = "5.8.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pywin32", marker = "python_full_version < '3.10' and platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, + { name = "traitlets", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/1b/72906d554acfeb588332eaaa6f61577705e9ec752ddb486f302dafa292d9/jupyter_core-5.8.1.tar.gz", hash = "sha256:0a5f9706f70e64786b75acba995988915ebd4601c8a52e534a40b51c95f59941", size = 88923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl", hash = "sha256:c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0", size = 28880 }, +] + +[[package]] +name = "jupyter-core" +version = "5.9.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032 }, +] + +[[package]] +name = "jupyter-events" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema", extra = ["format-nongpl"] }, + { name = "packaging" }, + { name = "python-json-logger" }, + { name = "pyyaml" }, + { name = "referencing", version = "0.36.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "referencing", version = "0.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430 }, +] + +[[package]] +name = "jupyter-server" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "argon2-cffi" }, + { name = "jinja2" }, + { name = "jupyter-client", version = "8.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "jupyter-client", version = "8.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", version = "5.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "jupyter-core", version = "5.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "jupyter-events" }, + { name = "jupyter-server-terminals" }, + { name = "nbconvert" }, + { name = "nbformat" }, + { name = "overrides", marker = "python_full_version < '3.12'" }, + { name = "packaging" }, + { name = "prometheus-client" }, + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "pyzmq" }, + { name = "send2trash" }, + { name = "terminado" }, + { name = "tornado" }, + { name = "traitlets" }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/ac/e040ec363d7b6b1f11304cc9f209dac4517ece5d5e01821366b924a64a50/jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5", size = 731949 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl", hash = "sha256:e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f", size = 388221 }, +] + +[[package]] +name = "jupyter-server-terminals" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "terminado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", size = 13656 }, +] + +[[package]] +name = "jupyterlab" +version = "1.2.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "jupyterlab-server" }, + { name = "notebook" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d4/73/88a0e7d1f39fcb1a1b5ce073452825065748d89a525d5489d10c79cec6d2/jupyterlab-1.2.6.tar.gz", hash = "sha256:42134b13fb0c410a9f55e8492a31ba5a1a346430a22690a512b8307764b68355", size = 6662479 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/9b/6ae6e7d844a4ccbb01eaf7b156eceef11562a8379980946009798caa8890/jupyterlab-1.2.6-py2.py3-none-any.whl", hash = "sha256:56c108e28934ac463754b7656441c0d92e76a81ad5dad446fe1071c6fd86245c", size = 6372425 }, +] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884 }, +] + +[[package]] +name = "jupyterlab-server" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "json5" }, + { name = "jsonschema" }, + { name = "notebook" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/5d/d118cf1cf1b36d609f0f1f5193c7efc7b62ebcf84e46cd978764c11f8e15/jupyterlab_server-1.0.9.tar.gz", hash = "sha256:13dc66acd6aee04907af015e840d36dc51380af2c03bdaccc3d4de525c29b9e6", size = 20643 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/d7/5d276c85b434633791236b9614883cc829f1a152730814b1f9fb3669898d/jupyterlab_server-1.0.9-py3-none-any.whl", hash = "sha256:2096f7a7797997727176c599779ab41f0f10ec8ad50070ca33ae4b3e109294ff", size = 27281 }, +] + +[[package]] +name = "jupyterlab-widgets" +version = "3.0.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/2d/ef58fed122b268c69c0aa099da20bc67657cdfb2e222688d5731bd5b971d/jupyterlab_widgets-3.0.16.tar.gz", hash = "sha256:423da05071d55cf27a9e602216d35a3a65a3e41cdf9c5d3b643b814ce38c19e0", size = 897423 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl", hash = "sha256:45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8", size = 914926 }, +] + +[[package]] +name = "jupytext" +version = "1.14.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "mdit-py-plugins", version = "0.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "mdit-py-plugins", version = "0.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "nbformat" }, + { name = "pyyaml" }, + { name = "toml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/bb/906e4e32833504e1f0df5ba65fea221f323e8272f0bd60d2f9674627c982/jupytext-1.14.4.tar.gz", hash = "sha256:4c09f1b8f837888dec11c1253e813b5cacdc20eecefcf2f9a0b870ae6bd44a65", size = 825338 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/1e/5c1bc4b07be7fb84e3e860b46c663c3cb07edc0f45bedeb97b9096c64269/jupytext-1.14.4-py3-none-any.whl", hash = "sha256:c5f5647112aa4ea4c61c31e48a216a4c49d315a0fc43d4f483529ed3b0b1a0d9", size = 298195 }, +] + +[[package]] +name = "keyring" +version = "25.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.12'" }, + { name = "jaraco-classes" }, + { name = "jaraco-context" }, + { name = "jaraco-functools" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "secretstorage", version = "3.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' and sys_platform == 'linux'" }, + { name = "secretstorage", version = "3.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160 }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/85/4d/2255e1c76304cbd60b48cee302b66d1dde4468dc5b1160e4b7cb43778f2a/kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60", size = 97286 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/14/fc943dd65268a96347472b4fbe5dcc2f6f55034516f80576cd0dd3a8930f/kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6", size = 122440 }, + { url = "https://files.pythonhosted.org/packages/1e/46/e68fed66236b69dd02fcdb506218c05ac0e39745d696d22709498896875d/kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17", size = 65758 }, + { url = "https://files.pythonhosted.org/packages/ef/fa/65de49c85838681fc9cb05de2a68067a683717321e01ddafb5b8024286f0/kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9", size = 64311 }, + { url = "https://files.pythonhosted.org/packages/42/9c/cc8d90f6ef550f65443bad5872ffa68f3dee36de4974768628bea7c14979/kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9", size = 1637109 }, + { url = "https://files.pythonhosted.org/packages/55/91/0a57ce324caf2ff5403edab71c508dd8f648094b18cfbb4c8cc0fde4a6ac/kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c", size = 1617814 }, + { url = "https://files.pythonhosted.org/packages/12/5d/c36140313f2510e20207708adf36ae4919416d697ee0236b0ddfb6fd1050/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599", size = 1400881 }, + { url = "https://files.pythonhosted.org/packages/56/d0/786e524f9ed648324a466ca8df86298780ef2b29c25313d9a4f16992d3cf/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05", size = 1512972 }, + { url = "https://files.pythonhosted.org/packages/67/5a/77851f2f201e6141d63c10a0708e996a1363efaf9e1609ad0441b343763b/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407", size = 1444787 }, + { url = "https://files.pythonhosted.org/packages/06/5f/1f5eaab84355885e224a6fc8d73089e8713dc7e91c121f00b9a1c58a2195/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278", size = 2199212 }, + { url = "https://files.pythonhosted.org/packages/b5/28/9152a3bfe976a0ae21d445415defc9d1cd8614b2910b7614b30b27a47270/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5", size = 2346399 }, + { url = "https://files.pythonhosted.org/packages/26/f6/453d1904c52ac3b400f4d5e240ac5fec25263716723e44be65f4d7149d13/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad", size = 2308688 }, + { url = "https://files.pythonhosted.org/packages/5a/9a/d4968499441b9ae187e81745e3277a8b4d7c60840a52dc9d535a7909fac3/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895", size = 2445493 }, + { url = "https://files.pythonhosted.org/packages/07/c9/032267192e7828520dacb64dfdb1d74f292765f179e467c1cba97687f17d/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3", size = 2262191 }, + { url = "https://files.pythonhosted.org/packages/6c/ad/db0aedb638a58b2951da46ddaeecf204be8b4f5454df020d850c7fa8dca8/kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc", size = 46644 }, + { url = "https://files.pythonhosted.org/packages/12/ca/d0f7b7ffbb0be1e7c2258b53554efec1fd652921f10d7d85045aff93ab61/kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c", size = 55877 }, + { url = "https://files.pythonhosted.org/packages/97/6c/cfcc128672f47a3e3c0d918ecb67830600078b025bfc32d858f2e2d5c6a4/kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a", size = 48347 }, + { url = "https://files.pythonhosted.org/packages/e9/44/77429fa0a58f941d6e1c58da9efe08597d2e86bf2b2cce6626834f49d07b/kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54", size = 122442 }, + { url = "https://files.pythonhosted.org/packages/e5/20/8c75caed8f2462d63c7fd65e16c832b8f76cda331ac9e615e914ee80bac9/kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95", size = 65762 }, + { url = "https://files.pythonhosted.org/packages/f4/98/fe010f15dc7230f45bc4cf367b012d651367fd203caaa992fd1f5963560e/kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935", size = 64319 }, + { url = "https://files.pythonhosted.org/packages/8b/1b/b5d618f4e58c0675654c1e5051bcf42c776703edb21c02b8c74135541f60/kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb", size = 1334260 }, + { url = "https://files.pythonhosted.org/packages/b8/01/946852b13057a162a8c32c4c8d2e9ed79f0bb5d86569a40c0b5fb103e373/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02", size = 1426589 }, + { url = "https://files.pythonhosted.org/packages/70/d1/c9f96df26b459e15cf8a965304e6e6f4eb291e0f7a9460b4ad97b047561e/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51", size = 1541080 }, + { url = "https://files.pythonhosted.org/packages/d3/73/2686990eb8b02d05f3de759d6a23a4ee7d491e659007dd4c075fede4b5d0/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052", size = 1470049 }, + { url = "https://files.pythonhosted.org/packages/a7/4b/2db7af3ed3af7c35f388d5f53c28e155cd402a55432d800c543dc6deb731/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18", size = 1426376 }, + { url = "https://files.pythonhosted.org/packages/05/83/2857317d04ea46dc5d115f0df7e676997bbd968ced8e2bd6f7f19cfc8d7f/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545", size = 2222231 }, + { url = "https://files.pythonhosted.org/packages/0d/b5/866f86f5897cd4ab6d25d22e403404766a123f138bd6a02ecb2cdde52c18/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b", size = 2368634 }, + { url = "https://files.pythonhosted.org/packages/c1/ee/73de8385403faba55f782a41260210528fe3273d0cddcf6d51648202d6d0/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36", size = 2329024 }, + { url = "https://files.pythonhosted.org/packages/a1/e7/cd101d8cd2cdfaa42dc06c433df17c8303d31129c9fdd16c0ea37672af91/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3", size = 2468484 }, + { url = "https://files.pythonhosted.org/packages/e1/72/84f09d45a10bc57a40bb58b81b99d8f22b58b2040c912b7eb97ebf625bf2/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523", size = 2284078 }, + { url = "https://files.pythonhosted.org/packages/d2/d4/71828f32b956612dc36efd7be1788980cb1e66bfb3706e6dec9acad9b4f9/kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d", size = 46645 }, + { url = "https://files.pythonhosted.org/packages/a1/65/d43e9a20aabcf2e798ad1aff6c143ae3a42cf506754bcb6a7ed8259c8425/kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b", size = 56022 }, + { url = "https://files.pythonhosted.org/packages/35/b3/9f75a2e06f1b4ca00b2b192bc2b739334127d27f1d0625627ff8479302ba/kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376", size = 48536 }, + { url = "https://files.pythonhosted.org/packages/97/9c/0a11c714cf8b6ef91001c8212c4ef207f772dd84540104952c45c1f0a249/kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2", size = 121808 }, + { url = "https://files.pythonhosted.org/packages/f2/d8/0fe8c5f5d35878ddd135f44f2af0e4e1d379e1c7b0716f97cdcb88d4fd27/kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a", size = 65531 }, + { url = "https://files.pythonhosted.org/packages/80/c5/57fa58276dfdfa612241d640a64ca2f76adc6ffcebdbd135b4ef60095098/kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee", size = 63894 }, + { url = "https://files.pythonhosted.org/packages/8b/e9/26d3edd4c4ad1c5b891d8747a4f81b1b0aba9fb9721de6600a4adc09773b/kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640", size = 1369296 }, + { url = "https://files.pythonhosted.org/packages/b6/67/3f4850b5e6cffb75ec40577ddf54f7b82b15269cc5097ff2e968ee32ea7d/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f", size = 1461450 }, + { url = "https://files.pythonhosted.org/packages/52/be/86cbb9c9a315e98a8dc6b1d23c43cffd91d97d49318854f9c37b0e41cd68/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483", size = 1579168 }, + { url = "https://files.pythonhosted.org/packages/0f/00/65061acf64bd5fd34c1f4ae53f20b43b0a017a541f242a60b135b9d1e301/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258", size = 1507308 }, + { url = "https://files.pythonhosted.org/packages/21/e4/c0b6746fd2eb62fe702118b3ca0cb384ce95e1261cfada58ff693aeec08a/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e", size = 1464186 }, + { url = "https://files.pythonhosted.org/packages/0a/0f/529d0a9fffb4d514f2782c829b0b4b371f7f441d61aa55f1de1c614c4ef3/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107", size = 2247877 }, + { url = "https://files.pythonhosted.org/packages/d1/e1/66603ad779258843036d45adcbe1af0d1a889a07af4635f8b4ec7dccda35/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948", size = 2404204 }, + { url = "https://files.pythonhosted.org/packages/8d/61/de5fb1ca7ad1f9ab7970e340a5b833d735df24689047de6ae71ab9d8d0e7/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038", size = 2352461 }, + { url = "https://files.pythonhosted.org/packages/ba/d2/0edc00a852e369827f7e05fd008275f550353f1f9bcd55db9363d779fc63/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383", size = 2501358 }, + { url = "https://files.pythonhosted.org/packages/84/15/adc15a483506aec6986c01fb7f237c3aec4d9ed4ac10b756e98a76835933/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520", size = 2314119 }, + { url = "https://files.pythonhosted.org/packages/36/08/3a5bb2c53c89660863a5aa1ee236912269f2af8762af04a2e11df851d7b2/kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b", size = 46367 }, + { url = "https://files.pythonhosted.org/packages/19/93/c05f0a6d825c643779fc3c70876bff1ac221f0e31e6f701f0e9578690d70/kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb", size = 55884 }, + { url = "https://files.pythonhosted.org/packages/d2/f9/3828d8f21b6de4279f0667fb50a9f5215e6fe57d5ec0d61905914f5b6099/kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a", size = 48528 }, + { url = "https://files.pythonhosted.org/packages/11/88/37ea0ea64512997b13d69772db8dcdc3bfca5442cda3a5e4bb943652ee3e/kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd", size = 122449 }, + { url = "https://files.pythonhosted.org/packages/4e/45/5a5c46078362cb3882dcacad687c503089263c017ca1241e0483857791eb/kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583", size = 65757 }, + { url = "https://files.pythonhosted.org/packages/8a/be/a6ae58978772f685d48dd2e84460937761c53c4bbd84e42b0336473d9775/kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417", size = 64312 }, + { url = "https://files.pythonhosted.org/packages/f4/04/18ef6f452d311e1e1eb180c9bf5589187fa1f042db877e6fe443ef10099c/kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904", size = 1626966 }, + { url = "https://files.pythonhosted.org/packages/21/b1/40655f6c3fa11ce740e8a964fa8e4c0479c87d6a7944b95af799c7a55dfe/kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a", size = 1607044 }, + { url = "https://files.pythonhosted.org/packages/fd/93/af67dbcfb9b3323bbd2c2db1385a7139d8f77630e4a37bb945b57188eb2d/kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8", size = 1391879 }, + { url = "https://files.pythonhosted.org/packages/40/6f/d60770ef98e77b365d96061d090c0cd9e23418121c55fff188fa4bdf0b54/kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2", size = 1504751 }, + { url = "https://files.pythonhosted.org/packages/fa/3a/5f38667d313e983c432f3fcd86932177519ed8790c724e07d77d1de0188a/kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88", size = 1436990 }, + { url = "https://files.pythonhosted.org/packages/cb/3b/1520301a47326e6a6043b502647e42892be33b3f051e9791cc8bb43f1a32/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde", size = 2191122 }, + { url = "https://files.pythonhosted.org/packages/cf/c4/eb52da300c166239a2233f1f9c4a1b767dfab98fae27681bfb7ea4873cb6/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c", size = 2338126 }, + { url = "https://files.pythonhosted.org/packages/1a/cb/42b92fd5eadd708dd9107c089e817945500685f3437ce1fd387efebc6d6e/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2", size = 2298313 }, + { url = "https://files.pythonhosted.org/packages/4f/eb/be25aa791fe5fc75a8b1e0c965e00f942496bc04635c9aae8035f6b76dcd/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb", size = 2437784 }, + { url = "https://files.pythonhosted.org/packages/c5/22/30a66be7f3368d76ff95689e1c2e28d382383952964ab15330a15d8bfd03/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327", size = 2253988 }, + { url = "https://files.pythonhosted.org/packages/35/d3/5f2ecb94b5211c8a04f218a76133cc8d6d153b0f9cd0b45fad79907f0689/kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644", size = 46980 }, + { url = "https://files.pythonhosted.org/packages/ef/17/cd10d020578764ea91740204edc6b3236ed8106228a46f568d716b11feb2/kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4", size = 55847 }, + { url = "https://files.pythonhosted.org/packages/91/84/32232502020bd78d1d12be7afde15811c64a95ed1f606c10456db4e4c3ac/kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f", size = 48494 }, + { url = "https://files.pythonhosted.org/packages/ac/59/741b79775d67ab67ced9bb38552da688c0305c16e7ee24bba7a2be253fb7/kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643", size = 59491 }, + { url = "https://files.pythonhosted.org/packages/58/cc/fb239294c29a5656e99e3527f7369b174dd9cc7c3ef2dea7cb3c54a8737b/kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706", size = 57648 }, + { url = "https://files.pythonhosted.org/packages/3b/ef/2f009ac1f7aab9f81efb2d837301d255279d618d27b6015780115ac64bdd/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6", size = 84257 }, + { url = "https://files.pythonhosted.org/packages/81/e1/c64f50987f85b68b1c52b464bb5bf73e71570c0f7782d626d1eb283ad620/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2", size = 80906 }, + { url = "https://files.pythonhosted.org/packages/fd/71/1687c5c0a0be2cee39a5c9c389e546f9c6e215e46b691d00d9f646892083/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4", size = 79951 }, + { url = "https://files.pythonhosted.org/packages/ea/8b/d7497df4a1cae9367adf21665dd1f896c2a7aeb8769ad77b662c5e2bcce7/kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a", size = 55715 }, + { url = "https://files.pythonhosted.org/packages/d5/df/ce37d9b26f07ab90880923c94d12a6ff4d27447096b4c849bfc4339ccfdf/kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39", size = 58666 }, + { url = "https://files.pythonhosted.org/packages/b0/d3/e4b04f43bc629ac8e186b77b2b1a251cdfa5b7610fa189dc0db622672ce6/kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e", size = 57088 }, + { url = "https://files.pythonhosted.org/packages/30/1c/752df58e2d339e670a535514d2db4fe8c842ce459776b8080fbe08ebb98e/kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608", size = 84321 }, + { url = "https://files.pythonhosted.org/packages/f0/f8/fe6484e847bc6e238ec9f9828089fb2c0bb53f2f5f3a79351fde5b565e4f/kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674", size = 80776 }, + { url = "https://files.pythonhosted.org/packages/9b/57/d7163c0379f250ef763aba85330a19feefb5ce6cb541ade853aaba881524/kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225", size = 79984 }, + { url = "https://files.pythonhosted.org/packages/8c/95/4a103776c265d13b3d2cd24fb0494d4e04ea435a8ef97e1b2c026d43250b/kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0", size = 55811 }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.9" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/5d/8ce64e36d4e3aac5ca96996457dcf33e34e6051492399a3f1fec5657f30b/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b", size = 124159 }, + { url = "https://files.pythonhosted.org/packages/96/1e/22f63ec454874378175a5f435d6ea1363dd33fb2af832c6643e4ccea0dc8/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f", size = 66578 }, + { url = "https://files.pythonhosted.org/packages/41/4c/1925dcfff47a02d465121967b95151c82d11027d5ec5242771e580e731bd/kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf", size = 65312 }, + { url = "https://files.pythonhosted.org/packages/d4/42/0f333164e6307a0687d1eb9ad256215aae2f4bd5d28f4653d6cd319a3ba3/kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9", size = 1628458 }, + { url = "https://files.pythonhosted.org/packages/86/b6/2dccb977d651943995a90bfe3495c2ab2ba5cd77093d9f2318a20c9a6f59/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415", size = 1225640 }, + { url = "https://files.pythonhosted.org/packages/50/2b/362ebd3eec46c850ccf2bfe3e30f2fc4c008750011f38a850f088c56a1c6/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b", size = 1244074 }, + { url = "https://files.pythonhosted.org/packages/6f/bb/f09a1e66dab8984773d13184a10a29fe67125337649d26bdef547024ed6b/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154", size = 1293036 }, + { url = "https://files.pythonhosted.org/packages/ea/01/11ecf892f201cafda0f68fa59212edaea93e96c37884b747c181303fccd1/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48", size = 2175310 }, + { url = "https://files.pythonhosted.org/packages/7f/5f/bfe11d5b934f500cc004314819ea92427e6e5462706a498c1d4fc052e08f/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220", size = 2270943 }, + { url = "https://files.pythonhosted.org/packages/3d/de/259f786bf71f1e03e73d87e2db1a9a3bcab64d7b4fd780167123161630ad/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586", size = 2440488 }, + { url = "https://files.pythonhosted.org/packages/1b/76/c989c278faf037c4d3421ec07a5c452cd3e09545d6dae7f87c15f54e4edf/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634", size = 2246787 }, + { url = "https://files.pythonhosted.org/packages/a2/55/c2898d84ca440852e560ca9f2a0d28e6e931ac0849b896d77231929900e7/kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611", size = 73730 }, + { url = "https://files.pythonhosted.org/packages/e8/09/486d6ac523dd33b80b368247f238125d027964cfacb45c654841e88fb2ae/kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536", size = 65036 }, + { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167 }, + { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579 }, + { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309 }, + { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596 }, + { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548 }, + { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618 }, + { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437 }, + { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742 }, + { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810 }, + { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579 }, + { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071 }, + { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840 }, + { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159 }, + { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686 }, + { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460 }, + { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952 }, + { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756 }, + { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404 }, + { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410 }, + { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631 }, + { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963 }, + { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295 }, + { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987 }, + { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817 }, + { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895 }, + { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992 }, + { url = "https://files.pythonhosted.org/packages/a2/63/fde392691690f55b38d5dd7b3710f5353bf7a8e52de93a22968801ab8978/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527", size = 60183 }, + { url = "https://files.pythonhosted.org/packages/27/b1/6aad34edfdb7cced27f371866f211332bba215bfd918ad3322a58f480d8b/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771", size = 58675 }, + { url = "https://files.pythonhosted.org/packages/9d/1a/23d855a702bb35a76faed5ae2ba3de57d323f48b1f6b17ee2176c4849463/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e", size = 80277 }, + { url = "https://files.pythonhosted.org/packages/5a/5b/5239e3c2b8fb5afa1e8508f721bb77325f740ab6994d963e61b2b7abcc1e/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9", size = 77994 }, + { url = "https://files.pythonhosted.org/packages/f9/1c/5d4d468fb16f8410e596ed0eac02d2c68752aa7dc92997fe9d60a7147665/kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb", size = 73744 }, + { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104 }, + { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592 }, + { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281 }, + { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009 }, + { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929 }, +] + +[[package]] +name = "lark" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/34/28fff3ab31ccff1fd4f6c7c7b0ceb2b6968d8ea4950663eadcb5720591a0/lark-1.3.1.tar.gz", hash = "sha256:b426a7a6d6d53189d318f2b6236ab5d6429eaf09259f1ca33eb716eed10d2905", size = 382732 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151 }, +] + +[[package]] +name = "llvmlite" +version = "0.43.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/3d/f513755f285db51ab363a53e898b85562e950f79a2e6767a364530c2f645/llvmlite-0.43.0.tar.gz", hash = "sha256:ae2b5b5c3ef67354824fb75517c8db5fbe93bc02cd9671f3c62271626bc041d5", size = 157069 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/ff/6ca7e98998b573b4bd6566f15c35e5c8bea829663a6df0c7aa55ab559da9/llvmlite-0.43.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a289af9a1687c6cf463478f0fa8e8aa3b6fb813317b0d70bf1ed0759eab6f761", size = 31064408 }, + { url = "https://files.pythonhosted.org/packages/ca/5c/a27f9257f86f0cda3f764ff21d9f4217b9f6a0d45e7a39ecfa7905f524ce/llvmlite-0.43.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d4fd101f571a31acb1559ae1af30f30b1dc4b3186669f92ad780e17c81e91bc", size = 28793153 }, + { url = "https://files.pythonhosted.org/packages/7e/3c/4410f670ad0a911227ea2ecfcba9f672a77cf1924df5280c4562032ec32d/llvmlite-0.43.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d434ec7e2ce3cc8f452d1cd9a28591745de022f931d67be688a737320dfcead", size = 42857276 }, + { url = "https://files.pythonhosted.org/packages/c6/21/2ffbab5714e72f2483207b4a1de79b2eecd9debbf666ff4e7067bcc5c134/llvmlite-0.43.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6912a87782acdff6eb8bf01675ed01d60ca1f2551f8176a300a886f09e836a6a", size = 43871781 }, + { url = "https://files.pythonhosted.org/packages/f2/26/b5478037c453554a61625ef1125f7e12bb1429ae11c6376f47beba9b0179/llvmlite-0.43.0-cp310-cp310-win_amd64.whl", hash = "sha256:14f0e4bf2fd2d9a75a3534111e8ebeb08eda2f33e9bdd6dfa13282afacdde0ed", size = 28123487 }, + { url = "https://files.pythonhosted.org/packages/95/8c/de3276d773ab6ce3ad676df5fab5aac19696b2956319d65d7dd88fb10f19/llvmlite-0.43.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e8d0618cb9bfe40ac38a9633f2493d4d4e9fcc2f438d39a4e854f39cc0f5f98", size = 31064409 }, + { url = "https://files.pythonhosted.org/packages/ee/e1/38deed89ced4cf378c61e232265cfe933ccde56ae83c901aa68b477d14b1/llvmlite-0.43.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0a9a1a39d4bf3517f2af9d23d479b4175ead205c592ceeb8b89af48a327ea57", size = 28793149 }, + { url = "https://files.pythonhosted.org/packages/2f/b2/4429433eb2dc8379e2cb582502dca074c23837f8fd009907f78a24de4c25/llvmlite-0.43.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1da416ab53e4f7f3bc8d4eeba36d801cc1894b9fbfbf2022b29b6bad34a7df2", size = 42857277 }, + { url = "https://files.pythonhosted.org/packages/6b/99/5d00a7d671b1ba1751fc9f19d3b36f3300774c6eebe2bcdb5f6191763eb4/llvmlite-0.43.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977525a1e5f4059316b183fb4fd34fa858c9eade31f165427a3977c95e3ee749", size = 43871781 }, + { url = "https://files.pythonhosted.org/packages/20/ab/ed5ed3688c6ba4f0b8d789da19fd8e30a9cf7fc5852effe311bc5aefe73e/llvmlite-0.43.0-cp311-cp311-win_amd64.whl", hash = "sha256:d5bd550001d26450bd90777736c69d68c487d17bf371438f975229b2b8241a91", size = 28107433 }, + { url = "https://files.pythonhosted.org/packages/0b/67/9443509e5d2b6d8587bae3ede5598fa8bd586b1c7701696663ea8af15b5b/llvmlite-0.43.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f99b600aa7f65235a5a05d0b9a9f31150c390f31261f2a0ba678e26823ec38f7", size = 31064409 }, + { url = "https://files.pythonhosted.org/packages/a2/9c/24139d3712d2d352e300c39c0e00d167472c08b3bd350c3c33d72c88ff8d/llvmlite-0.43.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:35d80d61d0cda2d767f72de99450766250560399edc309da16937b93d3b676e7", size = 28793145 }, + { url = "https://files.pythonhosted.org/packages/bf/f1/4c205a48488e574ee9f6505d50e84370a978c90f08dab41a42d8f2c576b6/llvmlite-0.43.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eccce86bba940bae0d8d48ed925f21dbb813519169246e2ab292b5092aba121f", size = 42857276 }, + { url = "https://files.pythonhosted.org/packages/00/5f/323c4d56e8401c50185fd0e875fcf06b71bf825a863699be1eb10aa2a9cb/llvmlite-0.43.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df6509e1507ca0760787a199d19439cc887bfd82226f5af746d6977bd9f66844", size = 43871781 }, + { url = "https://files.pythonhosted.org/packages/c6/94/dea10e263655ce78d777e78d904903faae39d1fc440762be4a9dc46bed49/llvmlite-0.43.0-cp312-cp312-win_amd64.whl", hash = "sha256:7a2872ee80dcf6b5dbdc838763d26554c2a18aa833d31a2635bff16aafefb9c9", size = 28107442 }, + { url = "https://files.pythonhosted.org/packages/2a/73/12925b1bbb3c2beb6d96f892ef5b4d742c34f00ddb9f4a125e9e87b22f52/llvmlite-0.43.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cd2a7376f7b3367019b664c21f0c61766219faa3b03731113ead75107f3b66c", size = 31064410 }, + { url = "https://files.pythonhosted.org/packages/cc/61/58c70aa0808a8cba825a7d98cc65bef4801b99328fba80837bfcb5fc767f/llvmlite-0.43.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18e9953c748b105668487b7c81a3e97b046d8abf95c4ddc0cd3c94f4e4651ae8", size = 28793145 }, + { url = "https://files.pythonhosted.org/packages/c8/c6/9324eb5de2ba9d99cbed853d85ba7a318652a48e077797bec27cf40f911d/llvmlite-0.43.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74937acd22dc11b33946b67dca7680e6d103d6e90eeaaaf932603bec6fe7b03a", size = 42857276 }, + { url = "https://files.pythonhosted.org/packages/e0/d0/889e9705107db7b1ec0767b03f15d7b95b4c4f9fdf91928ab1c7e9ffacf6/llvmlite-0.43.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9efc739cc6ed760f795806f67889923f7274276f0eb45092a1473e40d9b867", size = 43871777 }, + { url = "https://files.pythonhosted.org/packages/df/41/73cc26a2634b538cfe813f618c91e7e9960b8c163f8f0c94a2b0f008b9da/llvmlite-0.43.0-cp39-cp39-win_amd64.whl", hash = "sha256:47e147cdda9037f94b399bf03bfd8a6b6b1f2f90be94a454e3386f006455a9b4", size = 28123489 }, +] + +[[package]] +name = "llvmlite" +version = "0.46.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d21fe2ea536500563ef1943c5105bf6ab4ee3829f68e/llvmlite-0.46.0.tar.gz", hash = "sha256:227c9fd6d09dce2783c18b754b7cd9d9b3b3515210c46acc2d3c5badd9870ceb", size = 193456 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/a4/3959e1c61c5ca9db7921e5fd115b344c29b9d57a5dadd87bef97963ca1a5/llvmlite-0.46.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4323177e936d61ae0f73e653e2e614284d97d14d5dd12579adc92b6c2b0597b0", size = 37232766 }, + { url = "https://files.pythonhosted.org/packages/c2/a5/a4d916f1015106e1da876028606a8e87fd5d5c840f98c87bc2d5153b6a2f/llvmlite-0.46.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a2d461cb89537b7c20feb04c46c32e12d5ad4f0896c9dfc0f60336219ff248e", size = 56275176 }, + { url = "https://files.pythonhosted.org/packages/79/7f/a7f2028805dac8c1a6fae7bda4e739b7ebbcd45b29e15bf6d21556fcd3d5/llvmlite-0.46.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b1f6595a35b7b39c3518b85a28bf18f45e075264e4b2dce3f0c2a4f232b4a910", size = 55128629 }, + { url = "https://files.pythonhosted.org/packages/b2/bc/4689e1ba0c073c196b594471eb21be0aa51d9e64b911728aa13cd85ef0ae/llvmlite-0.46.0-cp310-cp310-win_amd64.whl", hash = "sha256:e7a34d4aa6f9a97ee006b504be6d2b8cb7f755b80ab2f344dda1ef992f828559", size = 38138651 }, + { url = "https://files.pythonhosted.org/packages/7a/a1/2ad4b2367915faeebe8447f0a057861f646dbf5fbbb3561db42c65659cf3/llvmlite-0.46.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82f3d39b16f19aa1a56d5fe625883a6ab600d5cc9ea8906cca70ce94cabba067", size = 37232766 }, + { url = "https://files.pythonhosted.org/packages/12/b5/99cf8772fdd846c07da4fd70f07812a3c8fd17ea2409522c946bb0f2b277/llvmlite-0.46.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a3df43900119803bbc52720e758c76f316a9a0f34612a886862dfe0a5591a17e", size = 56275175 }, + { url = "https://files.pythonhosted.org/packages/38/f2/ed806f9c003563732da156139c45d970ee435bd0bfa5ed8de87ba972b452/llvmlite-0.46.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de183fefc8022d21b0aa37fc3e90410bc3524aed8617f0ff76732fc6c3af5361", size = 55128630 }, + { url = "https://files.pythonhosted.org/packages/19/0c/8f5a37a65fc9b7b17408508145edd5f86263ad69c19d3574e818f533a0eb/llvmlite-0.46.0-cp311-cp311-win_amd64.whl", hash = "sha256:e8b10bc585c58bdffec9e0c309bb7d51be1f2f15e169a4b4d42f2389e431eb93", size = 38138652 }, + { url = "https://files.pythonhosted.org/packages/2b/f8/4db016a5e547d4e054ff2f3b99203d63a497465f81ab78ec8eb2ff7b2304/llvmlite-0.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b9588ad4c63b4f0175a3984b85494f0c927c6b001e3a246a3a7fb3920d9a137", size = 37232767 }, + { url = "https://files.pythonhosted.org/packages/aa/85/4890a7c14b4fa54400945cb52ac3cd88545bbdb973c440f98ca41591cdc5/llvmlite-0.46.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3535bd2bb6a2d7ae4012681ac228e5132cdb75fefb1bcb24e33f2f3e0c865ed4", size = 56275176 }, + { url = "https://files.pythonhosted.org/packages/6a/07/3d31d39c1a1a08cd5337e78299fca77e6aebc07c059fbd0033e3edfab45c/llvmlite-0.46.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cbfd366e60ff87ea6cc62f50bc4cd800ebb13ed4c149466f50cf2163a473d1e", size = 55128630 }, + { url = "https://files.pythonhosted.org/packages/2a/6b/d139535d7590a1bba1ceb68751bef22fadaa5b815bbdf0e858e3875726b2/llvmlite-0.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:398b39db462c39563a97b912d4f2866cd37cba60537975a09679b28fbbc0fb38", size = 38138940 }, +] + +[[package]] +name = "markdown" +version = "3.9" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441 }, +] + +[[package]] +name = "markdown" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678 }, +] + +[[package]] +name = "markdown-it-py" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/c0/59bd6d0571986f72899288a95d9d6178d0eebd70b6650f1bb3f0da90f8f7/markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1", size = 67120 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/25/2d88e8feee8e055d015343f9b86e370a1ccbec546f2865c98397aaef24af/markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30", size = 84466 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631 }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057 }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050 }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681 }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705 }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524 }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282 }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745 }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571 }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056 }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932 }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631 }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058 }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287 }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940 }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887 }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692 }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471 }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923 }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572 }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077 }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876 }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615 }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020 }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332 }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947 }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962 }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760 }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529 }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015 }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540 }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105 }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906 }, + { url = "https://files.pythonhosted.org/packages/56/23/0d8c13a44bde9154821586520840643467aee574d8ce79a17da539ee7fed/markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", size = 11623 }, + { url = "https://files.pythonhosted.org/packages/fd/23/07a2cb9a8045d5f3f0890a8c3bc0859d7a47bfd9a560b563899bec7b72ed/markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", size = 12049 }, + { url = "https://files.pythonhosted.org/packages/bc/e4/6be85eb81503f8e11b61c0b6369b6e077dcf0a74adbd9ebf6b349937b4e9/markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", size = 21923 }, + { url = "https://files.pythonhosted.org/packages/6f/bc/4dc914ead3fe6ddaef035341fee0fc956949bbd27335b611829292b89ee2/markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", size = 20543 }, + { url = "https://files.pythonhosted.org/packages/89/6e/5fe81fbcfba4aef4093d5f856e5c774ec2057946052d18d168219b7bd9f9/markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", size = 20585 }, + { url = "https://files.pythonhosted.org/packages/f6/f6/e0e5a3d3ae9c4020f696cd055f940ef86b64fe88de26f3a0308b9d3d048c/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", size = 21387 }, + { url = "https://files.pythonhosted.org/packages/c8/25/651753ef4dea08ea790f4fbb65146a9a44a014986996ca40102e237aa49a/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", size = 20133 }, + { url = "https://files.pythonhosted.org/packages/dc/0a/c3cf2b4fef5f0426e8a6d7fce3cb966a17817c568ce59d76b92a233fdbec/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", size = 20588 }, + { url = "https://files.pythonhosted.org/packages/cd/1b/a7782984844bd519ad4ffdbebbba2671ec5d0ebbeac34736c15fb86399e8/markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", size = 14566 }, + { url = "https://files.pythonhosted.org/packages/18/1f/8d9c20e1c9440e215a44be5ab64359e207fcb4f675543f1cf9a2a7f648d0/markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", size = 15053 }, + { url = "https://files.pythonhosted.org/packages/4e/d3/fe08482b5cd995033556d45041a4f4e76e7f0521112a9c9991d40d39825f/markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", size = 13928 }, +] + +[[package]] +name = "matplotlib" +version = "3.9.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "contourpy", version = "1.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "cycler", marker = "python_full_version < '3.10'" }, + { name = "fonttools", version = "4.60.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "importlib-resources", marker = "python_full_version < '3.10'" }, + { name = "kiwisolver", version = "1.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "pillow", version = "11.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyparsing", marker = "python_full_version < '3.10'" }, + { name = "python-dateutil", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/17/1747b4154034befd0ed33b52538f5eb7752d05bb51c5e2a31470c3bc7d52/matplotlib-3.9.4.tar.gz", hash = "sha256:1e00e8be7393cbdc6fedfa8a6fba02cf3e83814b285db1c60b906a023ba41bc3", size = 36106529 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/94/27d2e2c30d54b56c7b764acc1874a909e34d1965a427fc7092bb6a588b63/matplotlib-3.9.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c5fdd7abfb706dfa8d307af64a87f1a862879ec3cd8d0ec8637458f0885b9c50", size = 7885089 }, + { url = "https://files.pythonhosted.org/packages/c6/25/828273307e40a68eb8e9df832b6b2aaad075864fdc1de4b1b81e40b09e48/matplotlib-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d89bc4e85e40a71d1477780366c27fb7c6494d293e1617788986f74e2a03d7ff", size = 7770600 }, + { url = "https://files.pythonhosted.org/packages/f2/65/f841a422ec994da5123368d76b126acf4fc02ea7459b6e37c4891b555b83/matplotlib-3.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddf9f3c26aae695c5daafbf6b94e4c1a30d6cd617ba594bbbded3b33a1fcfa26", size = 8200138 }, + { url = "https://files.pythonhosted.org/packages/07/06/272aca07a38804d93b6050813de41ca7ab0e29ba7a9dd098e12037c919a9/matplotlib-3.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18ebcf248030173b59a868fda1fe42397253f6698995b55e81e1f57431d85e50", size = 8312711 }, + { url = "https://files.pythonhosted.org/packages/98/37/f13e23b233c526b7e27ad61be0a771894a079e0f7494a10d8d81557e0e9a/matplotlib-3.9.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974896ec43c672ec23f3f8c648981e8bc880ee163146e0312a9b8def2fac66f5", size = 9090622 }, + { url = "https://files.pythonhosted.org/packages/4f/8c/b1f5bd2bd70e60f93b1b54c4d5ba7a992312021d0ddddf572f9a1a6d9348/matplotlib-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:4598c394ae9711cec135639374e70871fa36b56afae17bdf032a345be552a88d", size = 7828211 }, + { url = "https://files.pythonhosted.org/packages/74/4b/65be7959a8fa118a3929b49a842de5b78bb55475236fcf64f3e308ff74a0/matplotlib-3.9.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d4dd29641d9fb8bc4492420c5480398dd40a09afd73aebe4eb9d0071a05fbe0c", size = 7894430 }, + { url = "https://files.pythonhosted.org/packages/e9/18/80f70d91896e0a517b4a051c3fd540daa131630fd75e02e250365353b253/matplotlib-3.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30e5b22e8bcfb95442bf7d48b0d7f3bdf4a450cbf68986ea45fca3d11ae9d099", size = 7780045 }, + { url = "https://files.pythonhosted.org/packages/a2/73/ccb381026e3238c5c25c3609ba4157b2d1a617ec98d65a8b4ee4e1e74d02/matplotlib-3.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bb0030d1d447fd56dcc23b4c64a26e44e898f0416276cac1ebc25522e0ac249", size = 8209906 }, + { url = "https://files.pythonhosted.org/packages/ab/33/1648da77b74741c89f5ea95cbf42a291b4b364f2660b316318811404ed97/matplotlib-3.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aca90ed222ac3565d2752b83dbb27627480d27662671e4d39da72e97f657a423", size = 8322873 }, + { url = "https://files.pythonhosted.org/packages/57/d3/8447ba78bc6593c9044c372d1609f8ea10fb1e071e7a9e0747bea74fc16c/matplotlib-3.9.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a181b2aa2906c608fcae72f977a4a2d76e385578939891b91c2550c39ecf361e", size = 9099566 }, + { url = "https://files.pythonhosted.org/packages/23/e1/4f0e237bf349c02ff9d1b6e7109f1a17f745263809b9714a8576dc17752b/matplotlib-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:1f6882828231eca17f501c4dcd98a05abb3f03d157fbc0769c6911fe08b6cfd3", size = 7838065 }, + { url = "https://files.pythonhosted.org/packages/1a/2b/c918bf6c19d6445d1cefe3d2e42cb740fb997e14ab19d4daeb6a7ab8a157/matplotlib-3.9.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dfc48d67e6661378a21c2983200a654b72b5c5cdbd5d2cf6e5e1ece860f0cc70", size = 7891131 }, + { url = "https://files.pythonhosted.org/packages/c1/e5/b4e8fc601ca302afeeabf45f30e706a445c7979a180e3a978b78b2b681a4/matplotlib-3.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47aef0fab8332d02d68e786eba8113ffd6f862182ea2999379dec9e237b7e483", size = 7776365 }, + { url = "https://files.pythonhosted.org/packages/99/06/b991886c506506476e5d83625c5970c656a491b9f80161458fed94597808/matplotlib-3.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fba1f52c6b7dc764097f52fd9ab627b90db452c9feb653a59945de16752e965f", size = 8200707 }, + { url = "https://files.pythonhosted.org/packages/c3/e2/556b627498cb27e61026f2d1ba86a78ad1b836fef0996bef5440e8bc9559/matplotlib-3.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:173ac3748acaac21afcc3fa1633924609ba1b87749006bc25051c52c422a5d00", size = 8313761 }, + { url = "https://files.pythonhosted.org/packages/58/ff/165af33ec766ff818306ea88e91f9f60d2a6ed543be1eb122a98acbf3b0d/matplotlib-3.9.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320edea0cadc07007765e33f878b13b3738ffa9745c5f707705692df70ffe0e0", size = 9095284 }, + { url = "https://files.pythonhosted.org/packages/9f/8b/3d0c7a002db3b1ed702731c2a9a06d78d035f1f2fb0fb936a8e43cc1e9f4/matplotlib-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a4a4cfc82330b27042a7169533da7991e8789d180dd5b3daeaee57d75cd5a03b", size = 7841160 }, + { url = "https://files.pythonhosted.org/packages/56/eb/501b465c9fef28f158e414ea3a417913dc2ac748564c7ed41535f23445b4/matplotlib-3.9.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3c3724d89a387ddf78ff88d2a30ca78ac2b4c89cf37f2db4bd453c34799e933c", size = 7885919 }, + { url = "https://files.pythonhosted.org/packages/da/36/236fbd868b6c91309a5206bd90c3f881f4f44b2d997cd1d6239ef652f878/matplotlib-3.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d5f0a8430ffe23d7e32cfd86445864ccad141797f7d25b7c41759a5b5d17cfd7", size = 7771486 }, + { url = "https://files.pythonhosted.org/packages/e0/4b/105caf2d54d5ed11d9f4335398f5103001a03515f2126c936a752ccf1461/matplotlib-3.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bb0141a21aef3b64b633dc4d16cbd5fc538b727e4958be82a0e1c92a234160e", size = 8201838 }, + { url = "https://files.pythonhosted.org/packages/5d/a7/bb01188fb4013d34d274caf44a2f8091255b0497438e8b6c0a7c1710c692/matplotlib-3.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57aa235109e9eed52e2c2949db17da185383fa71083c00c6c143a60e07e0888c", size = 8314492 }, + { url = "https://files.pythonhosted.org/packages/33/19/02e1a37f7141fc605b193e927d0a9cdf9dc124a20b9e68793f4ffea19695/matplotlib-3.9.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b18c600061477ccfdd1e6fd050c33d8be82431700f3452b297a56d9ed7037abb", size = 9092500 }, + { url = "https://files.pythonhosted.org/packages/57/68/c2feb4667adbf882ffa4b3e0ac9967f848980d9f8b5bebd86644aa67ce6a/matplotlib-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:ef5f2d1b67d2d2145ff75e10f8c008bfbf71d45137c4b648c87193e7dd053eac", size = 7822962 }, + { url = "https://files.pythonhosted.org/packages/0c/22/2ef6a364cd3f565442b0b055e0599744f1e4314ec7326cdaaa48a4d864d7/matplotlib-3.9.4-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:44e0ed786d769d85bc787b0606a53f2d8d2d1d3c8a2608237365e9121c1a338c", size = 7877995 }, + { url = "https://files.pythonhosted.org/packages/87/b8/2737456e566e9f4d94ae76b8aa0d953d9acb847714f9a7ad80184474f5be/matplotlib-3.9.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:09debb9ce941eb23ecdbe7eab972b1c3e0276dcf01688073faff7b0f61d6c6ca", size = 7769300 }, + { url = "https://files.pythonhosted.org/packages/b2/1f/e709c6ec7b5321e6568769baa288c7178e60a93a9da9e682b39450da0e29/matplotlib-3.9.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc53cf157a657bfd03afab14774d54ba73aa84d42cfe2480c91bd94873952db", size = 8313423 }, + { url = "https://files.pythonhosted.org/packages/5e/b6/5a1f868782cd13f053a679984e222007ecff654a9bfbac6b27a65f4eeb05/matplotlib-3.9.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ad45da51be7ad02387801fd154ef74d942f49fe3fcd26a64c94842ba7ec0d865", size = 7854624 }, +] + +[[package]] +name = "matplotlib" +version = "3.10.8" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "cycler", marker = "python_full_version >= '3.10'" }, + { name = "fonttools", version = "4.61.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "kiwisolver", version = "1.4.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pillow", version = "12.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyparsing", marker = "python_full_version >= '3.10'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/be/a30bd917018ad220c400169fba298f2bb7003c8ccbc0c3e24ae2aacad1e8/matplotlib-3.10.8-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:00270d217d6b20d14b584c521f810d60c5c78406dc289859776550df837dcda7", size = 8239828 }, + { url = "https://files.pythonhosted.org/packages/58/27/ca01e043c4841078e82cf6e80a6993dfecd315c3d79f5f3153afbb8e1ec6/matplotlib-3.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b3c1cc42aa184b3f738cfa18c1c1d72fd496d85467a6cf7b807936d39aa656", size = 8128050 }, + { url = "https://files.pythonhosted.org/packages/cb/aa/7ab67f2b729ae6a91bcf9dcac0affb95fb8c56f7fd2b2af894ae0b0cf6fa/matplotlib-3.10.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee40c27c795bda6a5292e9cff9890189d32f7e3a0bf04e0e3c9430c4a00c37df", size = 8700452 }, + { url = "https://files.pythonhosted.org/packages/73/ae/2d5817b0acee3c49b7e7ccfbf5b273f284957cc8e270adf36375db353190/matplotlib-3.10.8-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a48f2b74020919552ea25d222d5cc6af9ca3f4eb43a93e14d068457f545c2a17", size = 9534928 }, + { url = "https://files.pythonhosted.org/packages/c9/5b/8e66653e9f7c39cb2e5cab25fce4810daffa2bff02cbf5f3077cea9e942c/matplotlib-3.10.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f254d118d14a7f99d616271d6c3c27922c092dac11112670b157798b89bf4933", size = 9586377 }, + { url = "https://files.pythonhosted.org/packages/e2/e2/fd0bbadf837f81edb0d208ba8f8cb552874c3b16e27cb91a31977d90875d/matplotlib-3.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:f9b587c9c7274c1613a30afabf65a272114cd6cdbe67b3406f818c79d7ab2e2a", size = 8128127 }, + { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215 }, + { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625 }, + { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614 }, + { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997 }, + { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825 }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090 }, + { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377 }, + { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453 }, + { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321 }, + { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944 }, + { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099 }, + { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040 }, + { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717 }, + { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751 }, + { url = "https://files.pythonhosted.org/packages/f5/43/31d59500bb950b0d188e149a2e552040528c13d6e3d6e84d0cccac593dcd/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f97aeb209c3d2511443f8797e3e5a569aebb040d4f8bc79aa3ee78a8fb9e3dd8", size = 8237252 }, + { url = "https://files.pythonhosted.org/packages/0c/2c/615c09984f3c5f907f51c886538ad785cf72e0e11a3225de2c0f9442aecc/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fb061f596dad3a0f52b60dc6a5dec4a0c300dec41e058a7efe09256188d170b7", size = 8124693 }, + { url = "https://files.pythonhosted.org/packages/91/e1/2757277a1c56041e1fc104b51a0f7b9a4afc8eb737865d63cababe30bc61/matplotlib-3.10.8-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12d90df9183093fcd479f4172ac26b322b1248b15729cb57f42f71f24c7e37a3", size = 8702205 }, + { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198 }, + { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817 }, + { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867 }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516 }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "markdown-it-py", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/03/a2ecab526543b152300717cf232bb4bb8605b6edb946c845016fa9c9c9fd/mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5", size = 43542 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/f7/7782a043553ee469c1ff49cfa1cdace2d6bf99a1f333cf38676b3ddf30da/mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636", size = 55316 }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "markdown-it-py", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "mistune" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598 }, +] + +[[package]] +name = "more-itertools" +version = "10.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667 }, +] + +[[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 = "mypy" +version = "1.1.1" +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/62/54/be80f8d01f5cf72f774a77f9f750527a6fa733f09f78b1da30e8fa3914e6/mypy-1.1.1.tar.gz", hash = "sha256:ae9ceae0f5b9059f33dbc62dea087e942c0ccab4b7a003719cb70f9b8abfa32f", size = 2778293 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/9d/d23fa5d12bacbe7beea5fb6315b3325beabbe438e7e14d38c82b71609818/mypy-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39c7119335be05630611ee798cc982623b9e8f0cff04a0b48dfc26100e0b97af", size = 10585241 }, + { url = "https://files.pythonhosted.org/packages/8a/fd/b610256224e01da4c4f315d11f62d39d815e97439a58d49d60aa4f55a60b/mypy-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61bf08362e93b6b12fad3eab68c4ea903a077b87c90ac06c11e3d7a09b56b9c1", size = 9668979 }, + { url = "https://files.pythonhosted.org/packages/61/99/4a844dcacbc4990a8312236bf74a55910ee9a05db69dee7d6fb7a7ffe6c2/mypy-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbb19c9f662e41e474e0cff502b7064a7edc6764f5262b6cd91d698163196799", size = 12095159 }, + { url = "https://files.pythonhosted.org/packages/c0/d6/17ba6f8749722b8f61c6ab680769658f0bc63c293556149e2bf400b1f1a2/mypy-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:315ac73cc1cce4771c27d426b7ea558fb4e2836f89cb0296cbe056894e3a1f78", size = 12164618 }, + { url = "https://files.pythonhosted.org/packages/91/63/55d0e62829f739f47978f1d8eb965ca8c40261841e47491ad297c84921c5/mypy-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5cb14ff9919b7df3538590fc4d4c49a0f84392237cbf5f7a816b4161c061829e", size = 8868504 }, + { url = "https://files.pythonhosted.org/packages/d9/ab/d6d3884c3f432898458e2ade712988a7d1da562c1a363f2003b31677acd8/mypy-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:26cdd6a22b9b40b2fd71881a8a4f34b4d7914c679f154f43385ca878a8297389", size = 10475489 }, + { url = "https://files.pythonhosted.org/packages/b9/e5/71eef5239219ee2f4d85e2ca6368d736705a3b874023b57f7237b977839c/mypy-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b5f81b40d94c785f288948c16e1f2da37203c6006546c5d947aab6f90aefef2", size = 9567148 }, + { url = "https://files.pythonhosted.org/packages/bf/2d/45a526f248719ee32ecf1261564247a2e717a9c6167de5eb67d53599c4df/mypy-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b437be1c02712a605591e1ed1d858aba681757a1e55fe678a15c2244cd68a5", size = 11978458 }, + { url = "https://files.pythonhosted.org/packages/64/63/6a04ca7a8b7f34811cada43ed6119736a7f4a07c5e1cbd8eec0e0f4962d5/mypy-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d809f88734f44a0d44959d795b1e6f64b2bbe0ea4d9cc4776aa588bb4229fc1c", size = 12055007 }, + { url = "https://files.pythonhosted.org/packages/ed/89/85a04f32135fe4e35fd59d47100c939c7425fcb29868894c4b7a6171e065/mypy-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:a380c041db500e1410bb5b16b3c1c35e61e773a5c3517926b81dfdab7582be54", size = 8862912 }, + { url = "https://files.pythonhosted.org/packages/b8/72/385f3aeaaf262325454ac7f569eb81ac623464871df23d9778c864d04c6c/mypy-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:19ba15f9627a5723e522d007fe708007bae52b93faab00f95d72f03e1afa9598", size = 10580741 }, + { url = "https://files.pythonhosted.org/packages/47/9f/34f6a2254f7d39b8c4349b8ac480c233d37c377faf2c67c6ef925b3af0ab/mypy-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:59bbd71e5c58eed2e992ce6523180e03c221dcd92b52f0e792f291d67b15a71c", size = 9664359 }, + { url = "https://files.pythonhosted.org/packages/30/da/808ceaf2bcf23a9e90156c7b11b41add8dd5a009ee48159ec820d04d97bd/mypy-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9401e33814cec6aec8c03a9548e9385e0e228fc1b8b0a37b9ea21038e64cdd8a", size = 12090630 }, + { url = "https://files.pythonhosted.org/packages/be/d5/5588a2ee0d77189626a57b555b6b006dda6d5b0083f16c6be0c2d761cd7b/mypy-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b398d8b1f4fba0e3c6463e02f8ad3346f71956b92287af22c9b12c3ec965a9f", size = 12162009 }, + { url = "https://files.pythonhosted.org/packages/67/d3/1323311369eae97da4c7f47f266c55f7bdc22e74e4e2e1691be511ab8a91/mypy-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:69b35d1dcb5707382810765ed34da9db47e7f95b3528334a3c999b0c90fe523f", size = 8867451 }, + { url = "https://files.pythonhosted.org/packages/a4/0b/3a30f50287e42a4230320fa2eac25eb3017d38a7c31f083d407ab627607c/mypy-1.1.1-py3-none-any.whl", hash = "sha256:4e4e8b362cdf99ba00c2b218036002bdcdf1e0de085cdb296a49df03fb31dfc4", size = 2373884 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, +] + +[[package]] +name = "nbclassic" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel", version = "6.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "ipykernel", version = "7.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "ipython-genutils" }, + { name = "nest-asyncio" }, + { name = "notebook-shim" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/cc/a495b5eb9a964b70c6ae8c861168b78386d2520fd89c68390932f96400b2/nbclassic-1.3.3.tar.gz", hash = "sha256:434228763f8cee754318cd6dfa42370db191af630dabab8e30bafc8c1aa3eee6", size = 64116062 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/fd/dfb6db427bb4e0a50c9802b11df0b69d9364192f3db999849cde9209c8d0/nbclassic-1.3.3-py3-none-any.whl", hash = "sha256:dcee5149aa6aa01846c7458d6394b29b325213b5e118ee14c80d689122e0e4f2", size = 11527229 }, +] + +[[package]] +name = "nbclient" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "jupyter-client", version = "8.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "jupyter-core", version = "5.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "nbformat", marker = "python_full_version < '3.10'" }, + { name = "traitlets", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/66/7ffd18d58eae90d5721f9f39212327695b749e23ad44b3881744eaf4d9e8/nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193", size = 62424 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d", size = 25434 }, +] + +[[package]] +name = "nbclient" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "jupyter-client", version = "8.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", version = "5.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "nbformat", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/91/1c1d5a4b9a9ebba2b4e32b8c852c2975c872aec1fe42ab5e516b2cecd193/nbclient-0.10.4.tar.gz", hash = "sha256:1e54091b16e6da39e297b0ece3e10f6f29f4ac4e8ee515d29f8a7099bd6553c9", size = 62554 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl", hash = "sha256:9162df5a7373d70d606527300a95a975a47c137776cd942e52d9c7e29ff83440", size = 25465 }, +] + +[[package]] +name = "nbconvert" +version = "7.16.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "bleach", version = "6.2.0", source = { registry = "https://pypi.org/simple" }, extra = ["css"], marker = "python_full_version < '3.10'" }, + { name = "bleach", version = "6.3.0", source = { registry = "https://pypi.org/simple" }, extra = ["css"], marker = "python_full_version >= '3.10'" }, + { name = "defusedxml" }, + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "jinja2" }, + { name = "jupyter-core", version = "5.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "jupyter-core", version = "5.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "jupyterlab-pygments" }, + { name = "markupsafe" }, + { name = "mistune" }, + { name = "nbclient", version = "0.10.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "nbclient", version = "0.10.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pandocfilters" }, + { name = "pygments" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/59/f28e15fc47ffb73af68a8d9b47367a8630d76e97ae85ad18271b9db96fdf/nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582", size = 857715 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b", size = 258525 }, +] + +[[package]] +name = "nbformat" +version = "5.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema" }, + { name = "jsonschema" }, + { name = "jupyter-core", version = "5.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "jupyter-core", version = "5.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454 }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, +] + +[[package]] +name = "networkx" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/80/a84676339aaae2f1cfdf9f418701dd634aef9cc76f708ef55c36ff39c3ca/networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6", size = 2073928 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/f0/8fbc882ca80cf077f1b246c0e3c3465f7f415439bdea6b899f6b19f61f70/networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2", size = 1647772 }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504 }, +] + +[[package]] +name = "nh3" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/a5/34c26015d3a434409f4d2a1cd8821a06c05238703f49283ffeb937bef093/nh3-0.3.2.tar.gz", hash = "sha256:f394759a06df8b685a4ebfb1874fb67a9cbfd58c64fc5ed587a663c0e63ec376", size = 19288 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/3e/f5a5cc2885c24be13e9b937441bd16a012ac34a657fe05e58927e8af8b7a/nh3-0.3.2-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7064ccf5ace75825bd7bf57859daaaf16ed28660c1c6b306b649a9eda4b54b1e", size = 1431980 }, + { url = "https://files.pythonhosted.org/packages/7f/f7/529a99324d7ef055de88b690858f4189379708abae92ace799365a797b7f/nh3-0.3.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8745454cdd28bbbc90861b80a0111a195b0e3961b9fa2e672be89eb199fa5d8", size = 820805 }, + { url = "https://files.pythonhosted.org/packages/3d/62/19b7c50ccd1fa7d0764822d2cea8f2a320f2fd77474c7a1805cb22cf69b0/nh3-0.3.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72d67c25a84579f4a432c065e8b4274e53b7cf1df8f792cf846abfe2c3090866", size = 803527 }, + { url = "https://files.pythonhosted.org/packages/4a/ca/f022273bab5440abff6302731a49410c5ef66b1a9502ba3fbb2df998d9ff/nh3-0.3.2-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:13398e676a14d6233f372c75f52d5ae74f98210172991f7a3142a736bd92b131", size = 1051674 }, + { url = "https://files.pythonhosted.org/packages/fa/f7/5728e3b32a11daf5bd21cf71d91c463f74305938bc3eb9e0ac1ce141646e/nh3-0.3.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03d617e5c8aa7331bd2659c654e021caf9bba704b109e7b2b28b039a00949fe5", size = 1004737 }, + { url = "https://files.pythonhosted.org/packages/53/7f/f17e0dba0a99cee29e6cee6d4d52340ef9cb1f8a06946d3a01eb7ec2fb01/nh3-0.3.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f55c4d2d5a207e74eefe4d828067bbb01300e06e2a7436142f915c5928de07", size = 911745 }, + { url = "https://files.pythonhosted.org/packages/42/0f/c76bf3dba22c73c38e9b1113b017cf163f7696f50e003404ec5ecdb1e8a6/nh3-0.3.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb18403f02b655a1bbe4e3a4696c2ae1d6ae8f5991f7cacb684b1ae27e6c9f7", size = 797184 }, + { url = "https://files.pythonhosted.org/packages/08/a1/73d8250f888fb0ddf1b119b139c382f8903d8bb0c5bd1f64afc7e38dad1d/nh3-0.3.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d66f41672eb4060cf87c037f760bdbc6847852ca9ef8e9c5a5da18f090abf87", size = 838556 }, + { url = "https://files.pythonhosted.org/packages/d1/09/deb57f1fb656a7a5192497f4a287b0ade5a2ff6b5d5de4736d13ef6d2c1f/nh3-0.3.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f97f8b25cb2681d25e2338148159447e4d689aafdccfcf19e61ff7db3905768a", size = 1006695 }, + { url = "https://files.pythonhosted.org/packages/b6/61/8f4d41c4ccdac30e4b1a4fa7be4b0f9914d8314a5058472f84c8e101a418/nh3-0.3.2-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:2ab70e8c6c7d2ce953d2a58102eefa90c2d0a5ed7aa40c7e29a487bc5e613131", size = 1075471 }, + { url = "https://files.pythonhosted.org/packages/b0/c6/966aec0cb4705e69f6c3580422c239205d5d4d0e50fac380b21e87b6cf1b/nh3-0.3.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:1710f3901cd6440ca92494ba2eb6dc260f829fa8d9196b659fa10de825610ce0", size = 1002439 }, + { url = "https://files.pythonhosted.org/packages/e2/c8/97a2d5f7a314cce2c5c49f30c6f161b7f3617960ade4bfc2fd1ee092cb20/nh3-0.3.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:91e9b001101fb4500a2aafe3e7c92928d85242d38bf5ac0aba0b7480da0a4cd6", size = 987439 }, + { url = "https://files.pythonhosted.org/packages/0d/95/2d6fc6461687d7a171f087995247dec33e8749a562bfadd85fb5dbf37a11/nh3-0.3.2-cp38-abi3-win32.whl", hash = "sha256:169db03df90da63286e0560ea0efa9b6f3b59844a9735514a1d47e6bb2c8c61b", size = 589826 }, + { url = "https://files.pythonhosted.org/packages/64/9a/1a1c154f10a575d20dd634e5697805e589bbdb7673a0ad00e8da90044ba7/nh3-0.3.2-cp38-abi3-win_amd64.whl", hash = "sha256:562da3dca7a17f9077593214a9781a94b8d76de4f158f8c895e62f09573945fe", size = 596406 }, + { url = "https://files.pythonhosted.org/packages/9e/7e/a96255f63b7aef032cbee8fc4d6e37def72e3aaedc1f72759235e8f13cb1/nh3-0.3.2-cp38-abi3-win_arm64.whl", hash = "sha256:cf5964d54edd405e68583114a7cba929468bcd7db5e676ae38ee954de1cfc104", size = 584162 }, +] + +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438 }, +] + +[[package]] +name = "notebook" +version = "6.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi" }, + { name = "ipykernel", version = "6.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "ipykernel", version = "7.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "ipython-genutils" }, + { name = "jinja2" }, + { name = "jupyter-client", version = "8.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "jupyter-client", version = "8.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", version = "5.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "jupyter-core", version = "5.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "nbclassic" }, + { name = "nbconvert" }, + { name = "nbformat" }, + { name = "nest-asyncio" }, + { name = "prometheus-client" }, + { name = "pyzmq" }, + { name = "send2trash" }, + { name = "terminado" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/1e/b555b6e33c962a605e2e85b6014f609d3e1c6a5ff48f7c2480376b430d96/notebook-6.5.4.tar.gz", hash = "sha256:517209568bd47261e2def27a140e97d49070602eea0d226a696f42a7f16c9a4e", size = 5785832 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/21/0e7683e7c4d51b8f6cc5df9bbd33fb2d1e114b9e5dcddeef96ebd8e86348/notebook-6.5.4-py3-none-any.whl", hash = "sha256:dd17e78aefe64c768737b32bf171c1c766666a21cc79a44d37a1700771cab56f", size = 529822 }, +] + +[[package]] +name = "notebook-shim" +version = "0.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307 }, +] + +[[package]] +name = "numba" +version = "0.60.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "llvmlite", version = "0.43.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/93/2849300a9184775ba274aba6f82f303343669b0592b7bb0849ea713dabb0/numba-0.60.0.tar.gz", hash = "sha256:5df6158e5584eece5fc83294b949fd30b9f1125df7708862205217e068aabf16", size = 2702171 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/cf/baa13a7e3556d73d9e38021e6d6aa4aeb30d8b94545aa8b70d0f24a1ccc4/numba-0.60.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d761de835cd38fb400d2c26bb103a2726f548dc30368853121d66201672e651", size = 2647627 }, + { url = "https://files.pythonhosted.org/packages/ac/ba/4b57fa498564457c3cc9fc9e570a6b08e6086c74220f24baaf04e54b995f/numba-0.60.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:159e618ef213fba758837f9837fb402bbe65326e60ba0633dbe6c7f274d42c1b", size = 2650322 }, + { url = "https://files.pythonhosted.org/packages/28/98/7ea97ee75870a54f938a8c70f7e0be4495ba5349c5f9db09d467c4a5d5b7/numba-0.60.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1527dc578b95c7c4ff248792ec33d097ba6bef9eda466c948b68dfc995c25781", size = 3407390 }, + { url = "https://files.pythonhosted.org/packages/79/58/cb4ac5b8f7ec64200460aef1fed88258fb872ceef504ab1f989d2ff0f684/numba-0.60.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe0b28abb8d70f8160798f4de9d486143200f34458d34c4a214114e445d7124e", size = 3699694 }, + { url = "https://files.pythonhosted.org/packages/1c/b0/c61a93ca947d12233ff45de506ddbf52af3f752066a0b8be4d27426e16da/numba-0.60.0-cp310-cp310-win_amd64.whl", hash = "sha256:19407ced081d7e2e4b8d8c36aa57b7452e0283871c296e12d798852bc7d7f198", size = 2687030 }, + { url = "https://files.pythonhosted.org/packages/98/ad/df18d492a8f00d29a30db307904b9b296e37507034eedb523876f3a2e13e/numba-0.60.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a17b70fc9e380ee29c42717e8cc0bfaa5556c416d94f9aa96ba13acb41bdece8", size = 2647254 }, + { url = "https://files.pythonhosted.org/packages/9a/51/a4dc2c01ce7a850b8e56ff6d5381d047a5daea83d12bad08aa071d34b2ee/numba-0.60.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3fb02b344a2a80efa6f677aa5c40cd5dd452e1b35f8d1c2af0dfd9ada9978e4b", size = 2649970 }, + { url = "https://files.pythonhosted.org/packages/f9/4c/8889ac94c0b33dca80bed11564b8c6d9ea14d7f094e674c58e5c5b05859b/numba-0.60.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5f4fde652ea604ea3c86508a3fb31556a6157b2c76c8b51b1d45eb40c8598703", size = 3412492 }, + { url = "https://files.pythonhosted.org/packages/57/03/2b4245b05b71c0cee667e6a0b51606dfa7f4157c9093d71c6b208385a611/numba-0.60.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4142d7ac0210cc86432b818338a2bc368dc773a2f5cf1e32ff7c5b378bd63ee8", size = 3705018 }, + { url = "https://files.pythonhosted.org/packages/79/89/2d924ca60dbf949f18a6fec223a2445f5f428d9a5f97a6b29c2122319015/numba-0.60.0-cp311-cp311-win_amd64.whl", hash = "sha256:cac02c041e9b5bc8cf8f2034ff6f0dbafccd1ae9590dc146b3a02a45e53af4e2", size = 2686920 }, + { url = "https://files.pythonhosted.org/packages/eb/5c/b5ec752c475e78a6c3676b67c514220dbde2725896bbb0b6ec6ea54b2738/numba-0.60.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7da4098db31182fc5ffe4bc42c6f24cd7d1cb8a14b59fd755bfee32e34b8404", size = 2647866 }, + { url = "https://files.pythonhosted.org/packages/65/42/39559664b2e7c15689a638c2a38b3b74c6e69a04e2b3019b9f7742479188/numba-0.60.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38d6ea4c1f56417076ecf8fc327c831ae793282e0ff51080c5094cb726507b1c", size = 2650208 }, + { url = "https://files.pythonhosted.org/packages/67/88/c4459ccc05674ef02119abf2888ccd3e2fed12a323f52255f4982fc95876/numba-0.60.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:62908d29fb6a3229c242e981ca27e32a6e606cc253fc9e8faeb0e48760de241e", size = 3466946 }, + { url = "https://files.pythonhosted.org/packages/8b/41/ac11cf33524def12aa5bd698226ae196a1185831c05ed29dc0c56eaa308b/numba-0.60.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0ebaa91538e996f708f1ab30ef4d3ddc344b64b5227b67a57aa74f401bb68b9d", size = 3761463 }, + { url = "https://files.pythonhosted.org/packages/ca/bd/0fe29fcd1b6a8de479a4ed25c6e56470e467e3611c079d55869ceef2b6d1/numba-0.60.0-cp312-cp312-win_amd64.whl", hash = "sha256:f75262e8fe7fa96db1dca93d53a194a38c46da28b112b8a4aca168f0df860347", size = 2707588 }, + { url = "https://files.pythonhosted.org/packages/68/1a/87c53f836cdf557083248c3f47212271f220280ff766538795e77c8c6bbf/numba-0.60.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:01ef4cd7d83abe087d644eaa3d95831b777aa21d441a23703d649e06b8e06b74", size = 2647186 }, + { url = "https://files.pythonhosted.org/packages/28/14/a5baa1f2edea7b49afa4dc1bb1b126645198cf1075186853b5b497be826e/numba-0.60.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:819a3dfd4630d95fd574036f99e47212a1af41cbcb019bf8afac63ff56834449", size = 2650038 }, + { url = "https://files.pythonhosted.org/packages/3b/bd/f1985719ff34e37e07bb18f9d3acd17e5a21da255f550c8eae031e2ddf5f/numba-0.60.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0b983bd6ad82fe868493012487f34eae8bf7dd94654951404114f23c3466d34b", size = 3403010 }, + { url = "https://files.pythonhosted.org/packages/54/9b/cd73d3f6617ddc8398a63ef97d8dc9139a9879b9ca8a7ca4b8789056ea46/numba-0.60.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c151748cd269ddeab66334bd754817ffc0cabd9433acb0f551697e5151917d25", size = 3695086 }, + { url = "https://files.pythonhosted.org/packages/01/01/8b7b670c77c5ea0e47e283d82332969bf672ab6410d0b2610cac5b7a3ded/numba-0.60.0-cp39-cp39-win_amd64.whl", hash = "sha256:3031547a015710140e8c87226b4cfe927cac199835e5bf7d4fe5cb64e814e3ab", size = 2686978 }, +] + +[[package]] +name = "numba" +version = "0.63.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "llvmlite", version = "0.46.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/60/0145d479b2209bd8fdae5f44201eceb8ce5a23e0ed54c71f57db24618665/numba-0.63.1.tar.gz", hash = "sha256:b320aa675d0e3b17b40364935ea52a7b1c670c9037c39cf92c49502a75902f4b", size = 2761666 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/ce/5283d4ffa568f795bb0fd61ee1f0efc0c6094b94209259167fc8d4276bde/numba-0.63.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6d6bf5bf00f7db629305caaec82a2ffb8abe2bf45eaad0d0738dc7de4113779", size = 2680810 }, + { url = "https://files.pythonhosted.org/packages/0f/72/a8bda517e26d912633b32626333339b7c769ea73a5c688365ea5f88fd07e/numba-0.63.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08653d0dfc9cc9c4c9a8fba29ceb1f2d5340c3b86c4a7e5e07e42b643bc6a2f4", size = 3739735 }, + { url = "https://files.pythonhosted.org/packages/ca/17/1913b7c1173b2db30fb7a9696892a7c4c59aeee777a9af6859e9e01bac51/numba-0.63.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f09eebf5650246ce2a4e9a8d38270e2d4b0b0ae978103bafb38ed7adc5ea906e", size = 3446707 }, + { url = "https://files.pythonhosted.org/packages/b4/77/703db56c3061e9fdad5e79c91452947fdeb2ec0bdfe4affe9b144e7025e0/numba-0.63.1-cp310-cp310-win_amd64.whl", hash = "sha256:f8bba17421d865d8c0f7be2142754ebce53e009daba41c44cf6909207d1a8d7d", size = 2747374 }, + { url = "https://files.pythonhosted.org/packages/70/90/5f8614c165d2e256fbc6c57028519db6f32e4982475a372bbe550ea0454c/numba-0.63.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b33db00f18ccc790ee9911ce03fcdfe9d5124637d1ecc266f5ae0df06e02fec3", size = 2680501 }, + { url = "https://files.pythonhosted.org/packages/dc/9d/d0afc4cf915edd8eadd9b2ab5b696242886ee4f97720d9322650d66a88c6/numba-0.63.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d31ea186a78a7c0f6b1b2a3fe68057fdb291b045c52d86232b5383b6cf4fc25", size = 3744945 }, + { url = "https://files.pythonhosted.org/packages/05/a9/d82f38f2ab73f3be6f838a826b545b80339762ee8969c16a8bf1d39395a8/numba-0.63.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed3bb2fbdb651d6aac394388130a7001aab6f4541837123a4b4ab8b02716530c", size = 3450827 }, + { url = "https://files.pythonhosted.org/packages/18/3f/a9b106e93c5bd7434e65f044bae0d204e20aa7f7f85d72ceb872c7c04216/numba-0.63.1-cp311-cp311-win_amd64.whl", hash = "sha256:1ecbff7688f044b1601be70113e2fb1835367ee0b28ffa8f3adf3a05418c5c87", size = 2747262 }, + { url = "https://files.pythonhosted.org/packages/14/9c/c0974cd3d00ff70d30e8ff90522ba5fbb2bcee168a867d2321d8d0457676/numba-0.63.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2819cd52afa5d8d04e057bdfd54367575105f8829350d8fb5e4066fb7591cc71", size = 2680981 }, + { url = "https://files.pythonhosted.org/packages/cb/70/ea2bc45205f206b7a24ee68a159f5097c9ca7e6466806e7c213587e0c2b1/numba-0.63.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5cfd45dbd3d409e713b1ccfdc2ee72ca82006860254429f4ef01867fdba5845f", size = 3801656 }, + { url = "https://files.pythonhosted.org/packages/0d/82/4f4ba4fd0f99825cbf3cdefd682ca3678be1702b63362011de6e5f71f831/numba-0.63.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69a599df6976c03b7ecf15d05302696f79f7e6d10d620367407517943355bcb0", size = 3501857 }, + { url = "https://files.pythonhosted.org/packages/af/fd/6540456efa90b5f6604a86ff50dabefb187e43557e9081adcad3be44f048/numba-0.63.1-cp312-cp312-win_amd64.whl", hash = "sha256:bbad8c63e4fc7eb3cdb2c2da52178e180419f7969f9a685f283b313a70b92af3", size = 2750282 }, +] + +[[package]] +name = "numpy" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245 }, + { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540 }, + { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623 }, + { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774 }, + { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081 }, + { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451 }, + { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572 }, + { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722 }, + { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170 }, + { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558 }, + { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137 }, + { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552 }, + { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957 }, + { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573 }, + { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330 }, + { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895 }, + { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253 }, + { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074 }, + { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640 }, + { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230 }, + { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803 }, + { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835 }, + { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499 }, + { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497 }, + { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158 }, + { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173 }, + { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174 }, + { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701 }, + { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313 }, + { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179 }, + { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942 }, + { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512 }, + { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976 }, + { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494 }, + { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596 }, + { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099 }, + { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823 }, + { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424 }, + { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809 }, + { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314 }, + { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288 }, + { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793 }, + { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885 }, + { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784 }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245 }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048 }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542 }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301 }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320 }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050 }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034 }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185 }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149 }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620 }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963 }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743 }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616 }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579 }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005 }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570 }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548 }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521 }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866 }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455 }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348 }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362 }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103 }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382 }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462 }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618 }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511 }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783 }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506 }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190 }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391 }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754 }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476 }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666 }, +] + +[[package]] +name = "numpydoc" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fb/a70f636102045fc646656f2221c7fcdf92f7a9d71ba7c9875a949a58b3e8/numpydoc-1.1.0.tar.gz", hash = "sha256:c36fd6cb7ffdc9b4e165a43f67bf6271a7b024d0bb6b00ac468c9e2bfc76448e", size = 609482 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/1d/9e398c53d6ae27d5ab312ddc16a9ffe1bee0dfdf1d6ec88c40b0ca97582e/numpydoc-1.1.0-py3-none-any.whl", hash = "sha256:c53d6311190b9e3b9285bc979390ba0257ba9acde5eca1a7065fc8dfca9d46e8", size = 47765 }, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.1.3.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/6d/121efd7382d5b0284239f4ab1fc1590d86d34ed4a4a2fdb13b30ca8e5740/nvidia_cublas_cu12-12.1.3.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:ee53ccca76a6fc08fb9701aa95b6ceb242cdaab118c3bb152af4e579af792728", size = 410594774 }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.1.105" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/00/6b218edd739ecfc60524e585ba8e6b00554dd908de2c9c66c1af3e44e18d/nvidia_cuda_cupti_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:e54fde3983165c624cb79254ae9818a456eb6e87a7fd4d56a2352c24ee542d7e", size = 14109015 }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.1.105" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/9f/c64c03f49d6fbc56196664d05dba14e3a561038a81a638eeb47f4d4cfd48/nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:339b385f50c309763ca65456ec75e17bbefcbbf2893f462cb8b90584cd27a1c2", size = 23671734 }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.1.105" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/d5/c68b1d2cdfcc59e72e8a5949a37ddb22ae6cade80cd4a57a84d4c8b55472/nvidia_cuda_runtime_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:6e258468ddf5796e25f1dc591a31029fa317d97a0a94ed93468fc86301d61e40", size = 823596 }, +] + +[[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.0.2.54" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/94/eb540db023ce1d162e7bea9f8f5aa781d57c65aed513c33ee9a5123ead4d/nvidia_cufft_cu12-11.0.2.54-py3-none-manylinux1_x86_64.whl", hash = "sha256:794e3948a1aa71fd817c3775866943936774d1c14e7628c74f6f7417224cdf56", size = 121635161 }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.2.106" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/31/4890b1c9abc496303412947fc7dcea3d14861720642b49e8ceed89636705/nvidia_curand_cu12-10.3.2.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:9d264c5036dde4e64f1de8c50ae753237c12e0b1348738169cd0f8a536c0e1e0", size = 56467784 }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.4.5.107" +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/bc/1d/8de1e5c67099015c834315e333911273a8c6aaba78923dd1d1e25fc5f217/nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl", hash = "sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd", size = 124161928 }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.1.0.106" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/5b/cfaeebf25cd9fdec14338ccb16f6b2c4c7fa9163aefcf057d86b9cc248bb/nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c", size = 195958278 }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.20.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/2a/0a131f572aa09f741c30ccd45a8e56316e8be8dfc7bc19bf0ab7cfef7b19/nvidia_nccl_cu12-2.20.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:057f6bf9685f75215d0c53bf3ac4a10b3e6578351de307abad9e18a99182af56", size = 176249402 }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.9.86" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/0c/c75bbfb967457a0b7670b8ad267bfc4fffdf341c074e0a80db06c24ccfd4/nvidia_nvjitlink_cu12-12.9.86-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:e3f1171dbdc83c5932a45f0f4c99180a70de9bd2718c1ab77d14104f6d7147f9", size = 39748338 }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.1.105" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/d3/8057f0587683ed2fcd4dbfbdfdfa807b9160b809976099d36b8f60d08f03/nvidia_nvtx_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:dc21cf308ca5691e7c04d962e213f8a4aa9bbfa23d95412f452254c2caeb09e5", size = 99138 }, +] + +[[package]] +name = "overrides" +version = "7.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 }, +] + +[[package]] +name = "packaging" +version = "23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/6c/7c6658d258d7971c5eb0d9b69fa9265879ec9a9158031206d47800ae2213/packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f", size = 134240 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/c3/57f0601a2d4fe15de7a553c00adbc901425661bf048f2a22dfc500caf121/packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", size = 48905 }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763 }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217 }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791 }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373 }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444 }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459 }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086 }, + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790 }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831 }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267 }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281 }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453 }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361 }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702 }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846 }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618 }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212 }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693 }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002 }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971 }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722 }, + { url = "https://files.pythonhosted.org/packages/56/b4/52eeb530a99e2a4c55ffcd352772b599ed4473a0f892d127f4147cf0f88e/pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2", size = 11567720 }, + { url = "https://files.pythonhosted.org/packages/48/4a/2d8b67632a021bced649ba940455ed441ca854e57d6e7658a6024587b083/pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8", size = 10810302 }, + { url = "https://files.pythonhosted.org/packages/13/e6/d2465010ee0569a245c975dc6967b801887068bc893e908239b1f4b6c1ac/pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff", size = 12154874 }, + { url = "https://files.pythonhosted.org/packages/1f/18/aae8c0aa69a386a3255940e9317f793808ea79d0a525a97a903366bb2569/pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29", size = 12790141 }, + { url = "https://files.pythonhosted.org/packages/f7/26/617f98de789de00c2a444fbe6301bb19e66556ac78cff933d2c98f62f2b4/pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73", size = 13208697 }, + { url = "https://files.pythonhosted.org/packages/b9/fb/25709afa4552042bd0e15717c75e9b4a2294c3dc4f7e6ea50f03c5136600/pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9", size = 13879233 }, + { url = "https://files.pythonhosted.org/packages/98/af/7be05277859a7bc399da8ba68b88c96b27b48740b6cf49688899c6eb4176/pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa", size = 11359119 }, +] + +[[package]] +name = "pandocfilters" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663 }, +] + +[[package]] +name = "parso" +version = "0.8.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668 }, +] + +[[package]] +name = "patsy" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/44/ed13eccdd0519eff265f44b670d46fbb0ec813e2274932dc1c0e48520f7d/patsy-1.0.2.tar.gz", hash = "sha256:cdc995455f6233e90e22de72c37fcadb344e7586fb83f06696f54d92f8ce74c0", size = 399942 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/70/ba4b949bdc0490ab78d545459acd7702b211dfccf7eb89bbc1060f52818d/patsy-1.0.2-py2.py3-none-any.whl", hash = "sha256:37bfddbc58fcf0362febb5f54f10743f8b21dd2aa73dec7e7ef59d1b02ae668a", size = 233301 }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, +] + +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/5d/45a3553a253ac8763f3561371432a90bdbe6000fbdcf1397ffe502aa206c/pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860", size = 5316554 }, + { url = "https://files.pythonhosted.org/packages/7c/c8/67c12ab069ef586a25a4a79ced553586748fad100c77c0ce59bb4983ac98/pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad", size = 4686548 }, + { url = "https://files.pythonhosted.org/packages/2f/bd/6741ebd56263390b382ae4c5de02979af7f8bd9807346d068700dd6d5cf9/pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0", size = 5859742 }, + { url = "https://files.pythonhosted.org/packages/ca/0b/c412a9e27e1e6a829e6ab6c2dca52dd563efbedf4c9c6aa453d9a9b77359/pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b", size = 7633087 }, + { url = "https://files.pythonhosted.org/packages/59/9d/9b7076aaf30f5dd17e5e5589b2d2f5a5d7e30ff67a171eb686e4eecc2adf/pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50", size = 5963350 }, + { url = "https://files.pythonhosted.org/packages/f0/16/1a6bf01fb622fb9cf5c91683823f073f053005c849b1f52ed613afcf8dae/pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae", size = 6631840 }, + { url = "https://files.pythonhosted.org/packages/7b/e6/6ff7077077eb47fde78739e7d570bdcd7c10495666b6afcd23ab56b19a43/pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9", size = 6074005 }, + { url = "https://files.pythonhosted.org/packages/c3/3a/b13f36832ea6d279a697231658199e0a03cd87ef12048016bdcc84131601/pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e", size = 6708372 }, + { url = "https://files.pythonhosted.org/packages/6c/e4/61b2e1a7528740efbc70b3d581f33937e38e98ef3d50b05007267a55bcb2/pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6", size = 6277090 }, + { url = "https://files.pythonhosted.org/packages/a9/d3/60c781c83a785d6afbd6a326ed4d759d141de43aa7365725cbcd65ce5e54/pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f", size = 6985988 }, + { url = "https://files.pythonhosted.org/packages/9f/28/4f4a0203165eefb3763939c6789ba31013a2e90adffb456610f30f613850/pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f", size = 2422899 }, + { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531 }, + { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560 }, + { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978 }, + { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168 }, + { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053 }, + { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273 }, + { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043 }, + { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516 }, + { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768 }, + { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055 }, + { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079 }, + { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800 }, + { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296 }, + { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726 }, + { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652 }, + { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787 }, + { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236 }, + { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950 }, + { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358 }, + { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079 }, + { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324 }, + { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067 }, + { url = "https://files.pythonhosted.org/packages/9e/8e/9c089f01677d1264ab8648352dcb7773f37da6ad002542760c80107da816/pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f", size = 5316478 }, + { url = "https://files.pythonhosted.org/packages/b5/a9/5749930caf674695867eb56a581e78eb5f524b7583ff10b01b6e5048acb3/pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081", size = 4686522 }, + { url = "https://files.pythonhosted.org/packages/43/46/0b85b763eb292b691030795f9f6bb6fcaf8948c39413c81696a01c3577f7/pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4", size = 5853376 }, + { url = "https://files.pythonhosted.org/packages/5e/c6/1a230ec0067243cbd60bc2dad5dc3ab46a8a41e21c15f5c9b52b26873069/pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc", size = 7626020 }, + { url = "https://files.pythonhosted.org/packages/63/dd/f296c27ffba447bfad76c6a0c44c1ea97a90cb9472b9304c94a732e8dbfb/pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06", size = 5956732 }, + { url = "https://files.pythonhosted.org/packages/a5/a0/98a3630f0b57f77bae67716562513d3032ae70414fcaf02750279c389a9e/pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a", size = 6624404 }, + { url = "https://files.pythonhosted.org/packages/de/e6/83dfba5646a290edd9a21964da07674409e410579c341fc5b8f7abd81620/pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978", size = 6067760 }, + { url = "https://files.pythonhosted.org/packages/bc/41/15ab268fe6ee9a2bc7391e2bbb20a98d3974304ab1a406a992dcb297a370/pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d", size = 6700534 }, + { url = "https://files.pythonhosted.org/packages/64/79/6d4f638b288300bed727ff29f2a3cb63db054b33518a95f27724915e3fbc/pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71", size = 6277091 }, + { url = "https://files.pythonhosted.org/packages/46/05/4106422f45a05716fd34ed21763f8ec182e8ea00af6e9cb05b93a247361a/pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada", size = 6986091 }, + { url = "https://files.pythonhosted.org/packages/63/c6/287fd55c2c12761d0591549d48885187579b7c257bef0c6660755b0b59ae/pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb", size = 2422632 }, + { url = "https://files.pythonhosted.org/packages/6f/8b/209bd6b62ce8367f47e68a218bffac88888fdf2c9fcf1ecadc6c3ec1ebc7/pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967", size = 5270556 }, + { url = "https://files.pythonhosted.org/packages/2e/e6/231a0b76070c2cfd9e260a7a5b504fb72da0a95279410fa7afd99d9751d6/pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe", size = 4654625 }, + { url = "https://files.pythonhosted.org/packages/13/f4/10cf94fda33cb12765f2397fc285fa6d8eb9c29de7f3185165b702fc7386/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c", size = 4874207 }, + { url = "https://files.pythonhosted.org/packages/72/c9/583821097dc691880c92892e8e2d41fe0a5a3d6021f4963371d2f6d57250/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25", size = 6583939 }, + { url = "https://files.pythonhosted.org/packages/3b/8e/5c9d410f9217b12320efc7c413e72693f48468979a013ad17fd690397b9a/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27", size = 4957166 }, + { url = "https://files.pythonhosted.org/packages/62/bb/78347dbe13219991877ffb3a91bf09da8317fbfcd4b5f9140aeae020ad71/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a", size = 5581482 }, + { url = "https://files.pythonhosted.org/packages/d9/28/1000353d5e61498aaeaaf7f1e4b49ddb05f2c6575f9d4f9f914a3538b6e1/pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f", size = 6984596 }, + { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566 }, + { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618 }, + { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248 }, + { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963 }, + { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170 }, + { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505 }, + { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598 }, +] + +[[package]] +name = "pillow" +version = "12.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/08/26e68b6b5da219c2a2cb7b563af008b53bb8e6b6fcb3fa40715fcdb2523a/pillow-12.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:3adfb466bbc544b926d50fe8f4a4e6abd8c6bffd28a26177594e6e9b2b76572b", size = 5289809 }, + { url = "https://files.pythonhosted.org/packages/cb/e9/4e58fb097fb74c7b4758a680aacd558810a417d1edaa7000142976ef9d2f/pillow-12.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ac11e8ea4f611c3c0147424eae514028b5e9077dd99ab91e1bd7bc33ff145e1", size = 4650606 }, + { url = "https://files.pythonhosted.org/packages/4b/e0/1fa492aa9f77b3bc6d471c468e62bfea1823056bf7e5e4f1914d7ab2565e/pillow-12.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d49e2314c373f4c2b39446fb1a45ed333c850e09d0c59ac79b72eb3b95397363", size = 6221023 }, + { url = "https://files.pythonhosted.org/packages/c1/09/4de7cd03e33734ccd0c876f0251401f1314e819cbfd89a0fcb6e77927cc6/pillow-12.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c7b2a63fd6d5246349f3d3f37b14430d73ee7e8173154461785e43036ffa96ca", size = 8024937 }, + { url = "https://files.pythonhosted.org/packages/2e/69/0688e7c1390666592876d9d474f5e135abb4acb39dcb583c4dc5490f1aff/pillow-12.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d64317d2587c70324b79861babb9c09f71fbb780bad212018874b2c013d8600e", size = 6334139 }, + { url = "https://files.pythonhosted.org/packages/ed/1c/880921e98f525b9b44ce747ad1ea8f73fd7e992bafe3ca5e5644bf433dea/pillow-12.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d77153e14b709fd8b8af6f66a3afbb9ed6e9fc5ccf0b6b7e1ced7b036a228782", size = 7026074 }, + { url = "https://files.pythonhosted.org/packages/28/03/96f718331b19b355610ef4ebdbbde3557c726513030665071fd025745671/pillow-12.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32ed80ea8a90ee3e6fa08c21e2e091bba6eda8eccc83dbc34c95169507a91f10", size = 6448852 }, + { url = "https://files.pythonhosted.org/packages/3a/a0/6a193b3f0cc9437b122978d2c5cbce59510ccf9a5b48825096ed7472da2f/pillow-12.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c828a1ae702fc712978bda0320ba1b9893d99be0badf2647f693cc01cf0f04fa", size = 7117058 }, + { url = "https://files.pythonhosted.org/packages/a7/c4/043192375eaa4463254e8e61f0e2ec9a846b983929a8d0a7122e0a6d6fff/pillow-12.0.0-cp310-cp310-win32.whl", hash = "sha256:bd87e140e45399c818fac4247880b9ce719e4783d767e030a883a970be632275", size = 6295431 }, + { url = "https://files.pythonhosted.org/packages/92/c6/c2f2fc7e56301c21827e689bb8b0b465f1b52878b57471a070678c0c33cd/pillow-12.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:455247ac8a4cfb7b9bc45b7e432d10421aea9fc2e74d285ba4072688a74c2e9d", size = 7000412 }, + { url = "https://files.pythonhosted.org/packages/b2/d2/5f675067ba82da7a1c238a73b32e3fd78d67f9d9f80fbadd33a40b9c0481/pillow-12.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:6ace95230bfb7cd79ef66caa064bbe2f2a1e63d93471c3a2e1f1348d9f22d6b7", size = 2435903 }, + { url = "https://files.pythonhosted.org/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc", size = 5289798 }, + { url = "https://files.pythonhosted.org/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257", size = 4650589 }, + { url = "https://files.pythonhosted.org/packages/61/e3/2c820d6e9a36432503ead175ae294f96861b07600a7156154a086ba7111a/pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642", size = 6230472 }, + { url = "https://files.pythonhosted.org/packages/4f/89/63427f51c64209c5e23d4d52071c8d0f21024d3a8a487737caaf614a5795/pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3", size = 8033887 }, + { url = "https://files.pythonhosted.org/packages/f6/1b/c9711318d4901093c15840f268ad649459cd81984c9ec9887756cca049a5/pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c", size = 6343964 }, + { url = "https://files.pythonhosted.org/packages/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227", size = 7034756 }, + { url = "https://files.pythonhosted.org/packages/cc/b0/6177a8bdd5ee4ed87cba2de5a3cc1db55ffbbec6176784ce5bb75aa96798/pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b", size = 6458075 }, + { url = "https://files.pythonhosted.org/packages/bc/5e/61537aa6fa977922c6a03253a0e727e6e4a72381a80d63ad8eec350684f2/pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e", size = 7125955 }, + { url = "https://files.pythonhosted.org/packages/1f/3d/d5033539344ee3cbd9a4d69e12e63ca3a44a739eb2d4c8da350a3d38edd7/pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739", size = 6298440 }, + { url = "https://files.pythonhosted.org/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e", size = 6999256 }, + { url = "https://files.pythonhosted.org/packages/ba/f1/9197c9c2d5708b785f631a6dfbfa8eb3fb9672837cb92ae9af812c13b4ed/pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d", size = 2436025 }, + { url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", size = 5249377 }, + { url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082", size = 4650343 }, + { url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f", size = 6232981 }, + { url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d", size = 8041399 }, + { url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953", size = 6347740 }, + { url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8", size = 7040201 }, + { url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79", size = 6462334 }, + { url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba", size = 7134162 }, + { url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0", size = 6298769 }, + { url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a", size = 7001107 }, + { url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012 }, + { url = "https://files.pythonhosted.org/packages/1d/b3/582327e6c9f86d037b63beebe981425d6811104cb443e8193824ef1a2f27/pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8", size = 5215068 }, + { url = "https://files.pythonhosted.org/packages/fd/d6/67748211d119f3b6540baf90f92fae73ae51d5217b171b0e8b5f7e5d558f/pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a", size = 4614994 }, + { url = "https://files.pythonhosted.org/packages/2d/e1/f8281e5d844c41872b273b9f2c34a4bf64ca08905668c8ae730eedc7c9fa/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197", size = 5246639 }, + { url = "https://files.pythonhosted.org/packages/94/5a/0d8ab8ffe8a102ff5df60d0de5af309015163bf710c7bb3e8311dd3b3ad0/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c", size = 6986839 }, + { url = "https://files.pythonhosted.org/packages/20/2e/3434380e8110b76cd9eb00a363c484b050f949b4bbe84ba770bb8508a02c/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e", size = 5313505 }, + { url = "https://files.pythonhosted.org/packages/57/ca/5a9d38900d9d74785141d6580950fe705de68af735ff6e727cb911b64740/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76", size = 5963654 }, + { url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850 }, +] + +[[package]] +name = "pkginfo" +version = "1.12.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/03/e26bf3d6453b7fda5bd2b84029a426553bb373d6277ef6b5ac8863421f87/pkginfo-1.12.1.2.tar.gz", hash = "sha256:5cd957824ac36f140260964eba3c6be6442a8359b8c48f4adf90210f33a04b7b", size = 451828 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/3d/f4f2ba829efb54b6cd2d91349c7463316a9cc55a43fc980447416c88540f/pkginfo-1.12.1.2-py3-none-any.whl", hash = "sha256:c783ac885519cab2c34927ccfa6bf64b5a704d7c69afaea583dd9b7afe969343", size = 32717 }, +] + +[[package]] +name = "platformdirs" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654 }, +] + +[[package]] +name = "platformdirs" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731 }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, +] + +[[package]] +name = "pre-commit" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv", version = "3.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "cfgv", version = "3.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/00/1637ae945c6e10838ef5c41965f1c864e59301811bb203e979f335608e7c/pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658", size = 174966 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/6b/6cfe3a8b351b54f4b6c6d2ad4286804e3367f628dce379c603d3b96635f4/pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad", size = 201938 }, +] + +[[package]] +name = "prometheus-client" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145 }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431 }, +] + +[[package]] +name = "psutil" +version = "7.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/7c/31d1c3ceb1260301f87565f50689dc6da3db427ece1e1e012af22abca54e/psutil-7.2.0.tar.gz", hash = "sha256:2e4f8e1552f77d14dc96fb0f6240c5b34a37081c0889f0853b3b29a496e5ef64", size = 489863 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/c5/a49160bf3e165b7b93a60579a353cf5d939d7f878fe5fd369110f1d18043/psutil-7.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:977a2fcd132d15cb05b32b2d85b98d087cad039b0ce435731670ba74da9e6133", size = 128116 }, + { url = "https://files.pythonhosted.org/packages/10/a1/c75feb480f60cd768fb6ed00ac362a16a33e5076ec8475a22d8162fb2659/psutil-7.2.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:24151011c21fadd94214d7139d7c6c54569290d7e553989bdf0eab73b13beb8c", size = 128925 }, + { url = "https://files.pythonhosted.org/packages/12/ff/e93136587c00a543f4bc768b157fac2c47cd77b180d4f4e5c6efb6ea53a2/psutil-7.2.0-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91f211ba9279e7c61d9d8f84b713cfc38fa161cb0597d5cb3f1ca742f6848254", size = 154666 }, + { url = "https://files.pythonhosted.org/packages/b8/dd/4c2de9c3827c892599d277a69d2224136800870a8a88a80981de905de28d/psutil-7.2.0-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f37415188b7ea98faf90fed51131181646c59098b077550246e2e092e127418b", size = 156109 }, + { url = "https://files.pythonhosted.org/packages/81/3f/090943c682d3629968dd0b04826ddcbc760ee1379021dbe316e2ddfcd01b/psutil-7.2.0-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0d12c7ce6ed1128cd81fd54606afa054ac7dbb9773469ebb58cf2f171c49f2ac", size = 148081 }, + { url = "https://files.pythonhosted.org/packages/c4/88/c39648ebb8ec182d0364af53cdefe6eddb5f3872ba718b5855a8ff65d6d4/psutil-7.2.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ca0faef7976530940dcd39bc5382d0d0d5eb023b186a4901ca341bd8d8684151", size = 147376 }, + { url = "https://files.pythonhosted.org/packages/01/a2/5b39e08bd9b27476bc7cce7e21c71a481ad60b81ffac49baf02687a50d7f/psutil-7.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:abdb74137ca232d20250e9ad471f58d500e7743bc8253ba0bfbf26e570c0e437", size = 136910 }, + { url = "https://files.pythonhosted.org/packages/59/54/53839db1258c1eaeb4ded57ff202144ebc75b23facc05a74fd98d338b0c6/psutil-7.2.0-cp37-abi3-win_arm64.whl", hash = "sha256:284e71038b3139e7ab3834b63b3eb5aa5565fcd61a681ec746ef9a0a8c457fd2", size = 133807 }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, +] + +[[package]] +name = "pyaml" +version = "25.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/01/41f63d66a801a561c9e335523516bd5f761bc43cc61f8b75918306bf2da8/pyaml-25.7.0.tar.gz", hash = "sha256:e113a64ec16881bf2b092e2beb84b7dcf1bd98096ad17f5f14e8fb782a75d99b", size = 29814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/ee/a878f2ad010cbccb311f947f0f2f09d38f613938ee28c34e60fceecc75a1/pyaml-25.7.0-py3-none-any.whl", hash = "sha256:ce5d7867cc2b455efdb9b0448324ff7b9f74d99f64650f12ca570102db6b985f", size = 26418 }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140 }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, +] + +[[package]] +name = "pyparsing" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/c1/1d9de9aeaa1b89b0186e5fe23294ff6517fce1bc69149185577cd31016b2/pyparsing-3.3.1.tar.gz", hash = "sha256:47fad0f17ac1e2cad3de3b458570fbc9b03560aa029ed5e16ee5554da9a2251c", size = 1550512 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl", hash = "sha256:023b5e7e5520ad96642e2c6db4cb683d3970bd640cdf7115049a6e9c3682df82", size = 121793 }, +] + +[[package]] +name = "pytest" +version = "7.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/21/055f39bf8861580b43f845f9e8270c7786fe629b2f8562ff09007132e2e7/pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59", size = 1300608 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/68/a5eb36c3a8540594b6035e6cdae40c1ef1b6a2bfacbecc3d1a544583c078/pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71", size = 316791 }, +] + +[[package]] +name = "pytest-cov" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.10'" }, + { name = "coverage", version = "7.13.0", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ea/70/da97fd5f6270c7d2ce07559a19e5bf36a76f0af21500256f005a69d9beba/pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470", size = 62013 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/1f/9ec0ddd33bd2b37d6ec50bb39155bca4fe7085fa78b3b434c05459a860e3/pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b", size = 21554 }, +] + +[[package]] +name = "pytest-mock" +version = "3.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/2b/137a7db414aeaf3d753d415a2bc3b90aba8c5f61dff7a7a736d84b2ec60d/pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f", size = 28384 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/84/c951790e199cd54ddbf1021965b62a5415b81193ebdb4f4af2659fd06a73/pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b", size = 9275 }, +] + +[[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 = "python-json-logger" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548 }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432 }, + { url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103 }, + { url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557 }, + { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031 }, + { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308 }, + { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930 }, + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543 }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040 }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102 }, + { url = "https://files.pythonhosted.org/packages/59/42/b86689aac0cdaee7ae1c58d464b0ff04ca909c19bb6502d4973cdd9f9544/pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b", size = 8760837 }, + { url = "https://files.pythonhosted.org/packages/9f/8a/1403d0353f8c5a2f0829d2b1c4becbf9da2f0a4d040886404fc4a5431e4d/pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91", size = 9590187 }, + { url = "https://files.pythonhosted.org/packages/60/22/e0e8d802f124772cec9c75430b01a212f86f9de7546bda715e54140d5aeb/pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d", size = 8778162 }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756 }, +] + +[[package]] +name = "pywinpty" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/bb/a7cc2967c5c4eceb6cc49cfe39447d4bfc56e6c865e7c2249b6eb978935f/pywinpty-3.0.2.tar.gz", hash = "sha256:1505cc4cb248af42cb6285a65c9c2086ee9e7e574078ee60933d5d7fa86fb004", size = 30669 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/f5/b17ae550841949c217ad557ee445b4a14e9c0b506ae51ee087eff53428a6/pywinpty-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:65db57fd3387d71e8372b6a54269cbcd0f6dfa6d4616a29e0af749ec19f5c558", size = 2050330 }, + { url = "https://files.pythonhosted.org/packages/a6/a1/409c1651c9f874d598c10f51ff586c416625601df4bca315d08baec4c3e3/pywinpty-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:327790d70e4c841ebd9d0f295a780177149aeb405bca44c7115a3de5c2054b23", size = 2050304 }, + { url = "https://files.pythonhosted.org/packages/02/4e/1098484e042c9485f56f16eb2b69b43b874bd526044ee401512234cf9e04/pywinpty-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:99fdd9b455f0ad6419aba6731a7a0d2f88ced83c3c94a80ff9533d95fa8d8a9e", size = 2050391 }, + { url = "https://files.pythonhosted.org/packages/d3/ea/5cc069afc60f6dd5bc99b3e51fb8b219f10bcf5674882fc5d6dd2186d3aa/pywinpty-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:3962daf801bc38dd4de872108c424b5338c9a46c6efca5761854cd66370a9022", size = 2052447 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227 }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019 }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646 }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793 }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293 }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872 }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828 }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415 }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561 }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826 }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577 }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556 }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114 }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638 }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463 }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986 }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543 }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763 }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063 }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973 }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116 }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011 }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870 }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089 }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181 }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658 }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003 }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344 }, + { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450 }, + { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319 }, + { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631 }, + { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795 }, + { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767 }, + { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982 }, + { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677 }, + { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592 }, + { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777 }, +] + +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/b9/52aa9ec2867528b54f1e60846728d8b4d84726630874fee3a91e66c7df81/pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4", size = 1329850 }, + { url = "https://files.pythonhosted.org/packages/99/64/5653e7b7425b169f994835a2b2abf9486264401fdef18df91ddae47ce2cc/pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556", size = 906380 }, + { url = "https://files.pythonhosted.org/packages/73/78/7d713284dbe022f6440e391bd1f3c48d9185673878034cfb3939cdf333b2/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf7b38f9fd7b81cb6d9391b2946382c8237fd814075c6aa9c3b746d53076023b", size = 666421 }, + { url = "https://files.pythonhosted.org/packages/30/76/8f099f9d6482450428b17c4d6b241281af7ce6a9de8149ca8c1c649f6792/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03ff0b279b40d687691a6217c12242ee71f0fba28bf8626ff50e3ef0f4410e1e", size = 854149 }, + { url = "https://files.pythonhosted.org/packages/59/f0/37fbfff06c68016019043897e4c969ceab18bde46cd2aca89821fcf4fb2e/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:677e744fee605753eac48198b15a2124016c009a11056f93807000ab11ce6526", size = 1655070 }, + { url = "https://files.pythonhosted.org/packages/47/14/7254be73f7a8edc3587609554fcaa7bfd30649bf89cd260e4487ca70fdaa/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd2fec2b13137416a1c5648b7009499bcc8fea78154cd888855fa32514f3dad1", size = 2033441 }, + { url = "https://files.pythonhosted.org/packages/22/dc/49f2be26c6f86f347e796a4d99b19167fc94503f0af3fd010ad262158822/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08e90bb4b57603b84eab1d0ca05b3bbb10f60c1839dc471fc1c9e1507bef3386", size = 1891529 }, + { url = "https://files.pythonhosted.org/packages/a3/3e/154fb963ae25be70c0064ce97776c937ecc7d8b0259f22858154a9999769/pyzmq-27.1.0-cp310-cp310-win32.whl", hash = "sha256:a5b42d7a0658b515319148875fcb782bbf118dd41c671b62dae33666c2213bda", size = 567276 }, + { url = "https://files.pythonhosted.org/packages/62/b2/f4ab56c8c595abcb26b2be5fd9fa9e6899c1e5ad54964e93ae8bb35482be/pyzmq-27.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0bb87227430ee3aefcc0ade2088100e528d5d3298a0a715a64f3d04c60ba02f", size = 632208 }, + { url = "https://files.pythonhosted.org/packages/3b/e3/be2cc7ab8332bdac0522fdb64c17b1b6241a795bee02e0196636ec5beb79/pyzmq-27.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:9a916f76c2ab8d045b19f2286851a38e9ac94ea91faf65bd64735924522a8b32", size = 559766 }, + { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328 }, + { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803 }, + { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836 }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038 }, + { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531 }, + { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786 }, + { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220 }, + { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155 }, + { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428 }, + { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497 }, + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279 }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645 }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574 }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995 }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070 }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121 }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550 }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184 }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480 }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993 }, + { url = "https://files.pythonhosted.org/packages/ac/4e/782eb6df91b6a9d9afa96c2dcfc5cac62562a68eb62a02210101f886014d/pyzmq-27.1.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:96c71c32fff75957db6ae33cd961439f386505c6e6b377370af9b24a1ef9eafb", size = 1330426 }, + { url = "https://files.pythonhosted.org/packages/8d/ca/2b8693d06b1db4e0c084871e4c9d7842b561d0a6ff9d780640f5e3e9eb55/pyzmq-27.1.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:49d3980544447f6bd2968b6ac913ab963a49dcaa2d4a2990041f16057b04c429", size = 906559 }, + { url = "https://files.pythonhosted.org/packages/6a/b3/b99b39e2cfdcebd512959780e4d299447fd7f46010b1d88d63324e2481ec/pyzmq-27.1.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:849ca054d81aa1c175c49484afaaa5db0622092b5eccb2055f9f3bb8f703782d", size = 863816 }, + { url = "https://files.pythonhosted.org/packages/61/b2/018fa8e8eefb34a625b1a45e2effcbc9885645b22cdd0a68283f758351e7/pyzmq-27.1.0-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3970778e74cb7f85934d2b926b9900e92bfe597e62267d7499acc39c9c28e345", size = 666735 }, + { url = "https://files.pythonhosted.org/packages/01/05/8ae778f7cd7c94030731ae2305e6a38f3a333b6825f56c0c03f2134ccf1b/pyzmq-27.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:da96ecdcf7d3919c3be2de91a8c513c186f6762aa6cf7c01087ed74fad7f0968", size = 1655425 }, + { url = "https://files.pythonhosted.org/packages/ad/ad/d69478a97a3f3142f9dbbbd9daa4fcf42541913a85567c36d4cfc19b2218/pyzmq-27.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9541c444cfe1b1c0156c5c86ece2bb926c7079a18e7b47b0b1b3b1b875e5d098", size = 2033729 }, + { url = "https://files.pythonhosted.org/packages/9a/6d/e3c6ad05bc1cddd25094e66cc15ae8924e15c67e231e93ed2955c401007e/pyzmq-27.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e30a74a39b93e2e1591b58eb1acef4902be27c957a8720b0e368f579b82dc22f", size = 1891803 }, + { url = "https://files.pythonhosted.org/packages/7f/a7/97e8be0daaca157511563160b67a13d4fe76b195e3fa6873cb554ad46be3/pyzmq-27.1.0-cp39-cp39-win32.whl", hash = "sha256:b1267823d72d1e40701dcba7edc45fd17f71be1285557b7fe668887150a14b78", size = 567627 }, + { url = "https://files.pythonhosted.org/packages/5c/91/70bbf3a7c5b04c904261ef5ba224d8a76315f6c23454251bf5f55573a8a1/pyzmq-27.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0c996ded912812a2fcd7ab6574f4ad3edc27cb6510349431e4930d4196ade7db", size = 632315 }, + { url = "https://files.pythonhosted.org/packages/cc/b5/a4173a83c7fd37f6bdb5a800ea338bc25603284e9ef8681377cec006ede4/pyzmq-27.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:346e9ba4198177a07e7706050f35d733e08c1c1f8ceacd5eb6389d653579ffbc", size = 559833 }, + { url = "https://files.pythonhosted.org/packages/f3/81/a65e71c1552f74dec9dff91d95bafb6e0d33338a8dfefbc88aa562a20c92/pyzmq-27.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c17e03cbc9312bee223864f1a2b13a99522e0dc9f7c5df0177cd45210ac286e6", size = 836266 }, + { url = "https://files.pythonhosted.org/packages/58/ed/0202ca350f4f2b69faa95c6d931e3c05c3a397c184cacb84cb4f8f42f287/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f328d01128373cb6763823b2b4e7f73bdf767834268c565151eacb3b7a392f90", size = 800206 }, + { url = "https://files.pythonhosted.org/packages/47/42/1ff831fa87fe8f0a840ddb399054ca0009605d820e2b44ea43114f5459f4/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1790386614232e1b3a40a958454bdd42c6d1811837b15ddbb052a032a43f62", size = 567747 }, + { url = "https://files.pythonhosted.org/packages/d1/db/5c4d6807434751e3f21231bee98109aa57b9b9b55e058e450d0aef59b70f/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:448f9cb54eb0cee4732b46584f2710c8bc178b0e5371d9e4fc8125201e413a74", size = 747371 }, + { url = "https://files.pythonhosted.org/packages/26/af/78ce193dbf03567eb8c0dc30e3df2b9e56f12a670bf7eb20f9fb532c7e8a/pyzmq-27.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05b12f2d32112bf8c95ef2e74ec4f1d4beb01f8b5e703b38537f8849f92cb9ba", size = 544862 }, + { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265 }, + { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208 }, + { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747 }, + { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371 }, + { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862 }, + { url = "https://files.pythonhosted.org/packages/57/f4/c2e978cf6b833708bad7d6396c3a20c19750585a1775af3ff13c435e1912/pyzmq-27.1.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:722ea791aa233ac0a819fc2c475e1292c76930b31f1d828cb61073e2fe5e208f", size = 836257 }, + { url = "https://files.pythonhosted.org/packages/5f/5f/4e10c7f57a4c92ab0fbb2396297aa8d618e6f5b9b8f8e9756d56f3e6fc52/pyzmq-27.1.0-pp39-pypy39_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:01f9437501886d3a1dd4b02ef59fb8cc384fa718ce066d52f175ee49dd5b7ed8", size = 800203 }, + { url = "https://files.pythonhosted.org/packages/19/72/a74a007cd636f903448c6ab66628104b1fc5f2ba018733d5eabb94a0a6fb/pyzmq-27.1.0-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4a19387a3dddcc762bfd2f570d14e2395b2c9701329b266f83dd87a2b3cbd381", size = 758756 }, + { url = "https://files.pythonhosted.org/packages/a9/d4/30c25b91f2b4786026372f5ef454134d7f576fcf4ac58539ad7dd5de4762/pyzmq-27.1.0-pp39-pypy39_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c618fbcd069e3a29dcd221739cacde52edcc681f041907867e0f5cc7e85f172", size = 567742 }, + { url = "https://files.pythonhosted.org/packages/92/aa/ee86edad943438cd0316964020c4b6d09854414f9f945f8e289ea6fcc019/pyzmq-27.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff8d114d14ac671d88c89b9224c63d6c4e5a613fe8acd5594ce53d752a3aafe9", size = 544857 }, +] + +[[package]] +name = "qolmat" +version = "0.1.10" +source = { editable = "." } +dependencies = [ + { name = "category-encoders", version = "2.6.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "category-encoders", version = "2.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "category-encoders", version = "2.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "dcor" }, + { name = "numba", version = "0.60.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numba", version = "0.63.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pandas" }, + { name = "scikit-learn", version = "1.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "scikit-learn", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scikit-optimize" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "statsmodels" }, + { name = "tqdm" }, +] + +[package.optional-dependencies] +checkers = [ + { name = "bandit", version = "1.8.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "bandit", version = "1.9.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mypy" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-mock" }, + { name = "ruff" }, +] +ci = [ + { name = "codecov" }, +] +dev = [ + { name = "bump2version" }, + { name = "ipykernel", version = "6.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "ipykernel", version = "7.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "jupyter" }, + { name = "jupyterlab" }, + { name = "jupytext" }, + { name = "matplotlib", version = "3.9.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "matplotlib", version = "3.10.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging" }, + { name = "pre-commit" }, + { name = "twine" }, + { name = "wheel" }, +] +docs = [ + { name = "numpydoc" }, + { name = "sphinx" }, + { name = "sphinx-gallery", version = "0.19.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "sphinx-gallery", version = "0.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sphinx-markdown-tables" }, + { name = "sphinx-rtd-theme" }, +] +torch = [ + { name = "torch" }, +] + +[package.metadata] +requires-dist = [ + { name = "bandit", marker = "extra == 'checkers'", specifier = ">=1.7.9" }, + { name = "bump2version", marker = "extra == 'dev'", specifier = "==1.0.1" }, + { name = "category-encoders", specifier = ">=2.6.3,<3" }, + { name = "codecov", marker = "extra == 'ci'", specifier = ">=2.1.13" }, + { name = "dcor", specifier = ">=0.6" }, + { name = "ipykernel", marker = "extra == 'dev'", specifier = ">=6.29.5" }, + { name = "jupyter", marker = "extra == 'dev'", specifier = "==1.0.0" }, + { name = "jupyterlab", marker = "extra == 'dev'", specifier = "==1.2.6" }, + { name = "jupytext", marker = "extra == 'dev'", specifier = "==1.14.4" }, + { name = "matplotlib", marker = "extra == 'dev'" }, + { name = "mypy", marker = "extra == 'checkers'", specifier = "==1.1.1" }, + { name = "numba", specifier = ">=0.59" }, + { name = "numpy", specifier = ">=1.24" }, + { name = "numpydoc", marker = "extra == 'docs'", specifier = "==1.1.0" }, + { name = "packaging", marker = "extra == 'dev'", specifier = "==23.1" }, + { name = "pandas", specifier = ">=2.0.1" }, + { name = "pre-commit", marker = "extra == 'dev'", specifier = "==2.21.0" }, + { name = "pytest", marker = "extra == 'checkers'", specifier = "==7.2.0" }, + { name = "pytest-cov", marker = "extra == 'checkers'", specifier = "==4.0.0" }, + { name = "pytest-mock", marker = "extra == 'checkers'", specifier = "==3.10.0" }, + { name = "ruff", marker = "extra == 'checkers'", specifier = ">=0.6.3" }, + { name = "scikit-learn", specifier = ">=1.6" }, + { name = "scikit-optimize", specifier = ">=0.9" }, + { name = "scipy" }, + { name = "sphinx", marker = "extra == 'docs'", specifier = ">=5.0" }, + { name = "sphinx-gallery", marker = "extra == 'docs'", specifier = ">=0.15" }, + { name = "sphinx-markdown-tables", marker = "extra == 'docs'" }, + { name = "sphinx-rtd-theme", marker = "extra == 'docs'", specifier = "==1.0.0" }, + { name = "statsmodels", specifier = ">=0.14.0" }, + { name = "torch", marker = "extra == 'torch'", specifier = "<2.5" }, + { name = "tqdm" }, + { name = "twine", marker = "extra == 'dev'", specifier = "==3.7.1" }, + { name = "wheel", marker = "extra == 'dev'", specifier = "==0.37.1" }, +] +provides-extras = ["torch", "docs", "dev", "checkers", "ci"] + +[[package]] +name = "qtconsole" +version = "5.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel", version = "6.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "ipykernel", version = "7.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "ipython-pygments-lexers" }, + { name = "jupyter-client", version = "8.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "jupyter-client", version = "8.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", version = "5.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "jupyter-core", version = "5.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "qtpy" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/dc/c8cb80a72af781cbdba46ba9416de33c501ecb4644e36191fbb27d592c16/qtconsole-5.7.0.tar.gz", hash = "sha256:0d33371ce9ef554c7022ee300564ba9ffbd615e304ee615f6769d4068e063171", size = 436635 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/d2/3cb0980a6a4a2581facc4d47b2e7d99912a1f7065407f007f27419e19979/qtconsole-5.7.0-py3-none-any.whl", hash = "sha256:42ff0734269a77129097131ce96059f97dc864e6e061d7ed919609ccfe18dd22", size = 125653 }, +] + +[[package]] +name = "qtpy" +version = "2.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/01/392eba83c8e47b946b929d7c46e0f04b35e9671f8bb6fc36b6f7945b4de8/qtpy-2.4.3.tar.gz", hash = "sha256:db744f7832e6d3da90568ba6ccbca3ee2b3b4a890c3d6fbbc63142f6e4cdf5bb", size = 66982 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/76/37c0ccd5ab968a6a438f9c623aeecc84c202ab2fabc6a8fd927580c15b5a/QtPy-2.4.3-py3-none-any.whl", hash = "sha256:72095afe13673e017946cc258b8d5da43314197b741ed2890e563cf384b51aa1", size = 95045 }, +] + +[[package]] +name = "readme-renderer" +version = "43.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "nh3" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/b5/536c775084d239df6345dccf9b043419c7e3308bc31be4c7882196abc62e/readme_renderer-43.0.tar.gz", hash = "sha256:1818dd28140813509eeed8d62687f7cd4f7bad90d4db586001c5dc09d4fde311", size = 31768 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/be/3ea20dc38b9db08387cf97997a85a7d51527ea2057d71118feb0aa8afa55/readme_renderer-43.0-py3-none-any.whl", hash = "sha256:19db308d86ecd60e5affa3b2a98f017af384678c63c88e5d4556a380e674f3f9", size = 13301 }, +] + +[[package]] +name = "referencing" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "attrs", marker = "python_full_version < '3.10'" }, + { name = "rpds-py", version = "0.27.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775 }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "attrs", marker = "python_full_version >= '3.10'" }, + { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766 }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738 }, +] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481 }, +] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490 }, +] + +[[package]] +name = "rfc3986" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326 }, +] + +[[package]] +name = "rfc3986-validator" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242 }, +] + +[[package]] +name = "rfc3987-syntax" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lark" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046 }, +] + +[[package]] +name = "rich" +version = "14.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393 }, +] + +[[package]] +name = "rpds-py" +version = "0.27.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/dd/2c0cbe774744272b0ae725f44032c77bdcab6e8bcf544bffa3b6e70c8dba/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8", size = 27479 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/ed/3aef893e2dd30e77e35d20d4ddb45ca459db59cead748cad9796ad479411/rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef", size = 371606 }, + { url = "https://files.pythonhosted.org/packages/6d/82/9818b443e5d3eb4c83c3994561387f116aae9833b35c484474769c4a8faf/rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be", size = 353452 }, + { url = "https://files.pythonhosted.org/packages/99/c7/d2a110ffaaa397fc6793a83c7bd3545d9ab22658b7cdff05a24a4535cc45/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9024de74731df54546fab0bfbcdb49fae19159ecaecfc8f37c18d2c7e2c0bd61", size = 381519 }, + { url = "https://files.pythonhosted.org/packages/5a/bc/e89581d1f9d1be7d0247eaef602566869fdc0d084008ba139e27e775366c/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31d3ebadefcd73b73928ed0b2fd696f7fefda8629229f81929ac9c1854d0cffb", size = 394424 }, + { url = "https://files.pythonhosted.org/packages/ac/2e/36a6861f797530e74bb6ed53495f8741f1ef95939eed01d761e73d559067/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2e7f8f169d775dd9092a1743768d771f1d1300453ddfe6325ae3ab5332b4657", size = 523467 }, + { url = "https://files.pythonhosted.org/packages/c4/59/c1bc2be32564fa499f988f0a5c6505c2f4746ef96e58e4d7de5cf923d77e/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d905d16f77eb6ab2e324e09bfa277b4c8e5e6b8a78a3e7ff8f3cdf773b4c013", size = 402660 }, + { url = "https://files.pythonhosted.org/packages/0a/ec/ef8bf895f0628dd0a59e54d81caed6891663cb9c54a0f4bb7da918cb88cf/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50c946f048209e6362e22576baea09193809f87687a95a8db24e5fbdb307b93a", size = 384062 }, + { url = "https://files.pythonhosted.org/packages/69/f7/f47ff154be8d9a5e691c083a920bba89cef88d5247c241c10b9898f595a1/rpds_py-0.27.1-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:3deab27804d65cd8289eb814c2c0e807c4b9d9916c9225e363cb0cf875eb67c1", size = 401289 }, + { url = "https://files.pythonhosted.org/packages/3b/d9/ca410363efd0615814ae579f6829cafb39225cd63e5ea5ed1404cb345293/rpds_py-0.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b61097f7488de4be8244c89915da8ed212832ccf1e7c7753a25a394bf9b1f10", size = 417718 }, + { url = "https://files.pythonhosted.org/packages/e3/a0/8cb5c2ff38340f221cc067cc093d1270e10658ba4e8d263df923daa18e86/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a3f29aba6e2d7d90528d3c792555a93497fe6538aa65eb675b44505be747808", size = 558333 }, + { url = "https://files.pythonhosted.org/packages/6f/8c/1b0de79177c5d5103843774ce12b84caa7164dfc6cd66378768d37db11bf/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd6cd0485b7d347304067153a6dc1d73f7d4fd995a396ef32a24d24b8ac63ac8", size = 589127 }, + { url = "https://files.pythonhosted.org/packages/c8/5e/26abb098d5e01266b0f3a2488d299d19ccc26849735d9d2b95c39397e945/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f4461bf931108c9fa226ffb0e257c1b18dc2d44cd72b125bec50ee0ab1248a9", size = 554899 }, + { url = "https://files.pythonhosted.org/packages/de/41/905cc90ced13550db017f8f20c6d8e8470066c5738ba480d7ba63e3d136b/rpds_py-0.27.1-cp310-cp310-win32.whl", hash = "sha256:ee5422d7fb21f6a00c1901bf6559c49fee13a5159d0288320737bbf6585bd3e4", size = 217450 }, + { url = "https://files.pythonhosted.org/packages/75/3d/6bef47b0e253616ccdf67c283e25f2d16e18ccddd38f92af81d5a3420206/rpds_py-0.27.1-cp310-cp310-win_amd64.whl", hash = "sha256:3e039aabf6d5f83c745d5f9a0a381d031e9ed871967c0a5c38d201aca41f3ba1", size = 228447 }, + { url = "https://files.pythonhosted.org/packages/b5/c1/7907329fbef97cbd49db6f7303893bd1dd5a4a3eae415839ffdfb0762cae/rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881", size = 371063 }, + { url = "https://files.pythonhosted.org/packages/11/94/2aab4bc86228bcf7c48760990273653a4900de89c7537ffe1b0d6097ed39/rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5", size = 353210 }, + { url = "https://files.pythonhosted.org/packages/3a/57/f5eb3ecf434342f4f1a46009530e93fd201a0b5b83379034ebdb1d7c1a58/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e", size = 381636 }, + { url = "https://files.pythonhosted.org/packages/ae/f4/ef95c5945e2ceb5119571b184dd5a1cc4b8541bbdf67461998cfeac9cb1e/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c", size = 394341 }, + { url = "https://files.pythonhosted.org/packages/5a/7e/4bd610754bf492d398b61725eb9598ddd5eb86b07d7d9483dbcd810e20bc/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195", size = 523428 }, + { url = "https://files.pythonhosted.org/packages/9f/e5/059b9f65a8c9149361a8b75094864ab83b94718344db511fd6117936ed2a/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52", size = 402923 }, + { url = "https://files.pythonhosted.org/packages/f5/48/64cabb7daced2968dd08e8a1b7988bf358d7bd5bcd5dc89a652f4668543c/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed", size = 384094 }, + { url = "https://files.pythonhosted.org/packages/ae/e1/dc9094d6ff566bff87add8a510c89b9e158ad2ecd97ee26e677da29a9e1b/rpds_py-0.27.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a", size = 401093 }, + { url = "https://files.pythonhosted.org/packages/37/8e/ac8577e3ecdd5593e283d46907d7011618994e1d7ab992711ae0f78b9937/rpds_py-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde", size = 417969 }, + { url = "https://files.pythonhosted.org/packages/66/6d/87507430a8f74a93556fe55c6485ba9c259949a853ce407b1e23fea5ba31/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21", size = 558302 }, + { url = "https://files.pythonhosted.org/packages/3a/bb/1db4781ce1dda3eecc735e3152659a27b90a02ca62bfeea17aee45cc0fbc/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9", size = 589259 }, + { url = "https://files.pythonhosted.org/packages/7b/0e/ae1c8943d11a814d01b482e1f8da903f88047a962dff9bbdadf3bd6e6fd1/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948", size = 554983 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/0b2a55415931db4f112bdab072443ff76131b5ac4f4dc98d10d2d357eb03/rpds_py-0.27.1-cp311-cp311-win32.whl", hash = "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39", size = 217154 }, + { url = "https://files.pythonhosted.org/packages/24/75/3b7ffe0d50dc86a6a964af0d1cc3a4a2cdf437cb7b099a4747bbb96d1819/rpds_py-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15", size = 228627 }, + { url = "https://files.pythonhosted.org/packages/8d/3f/4fd04c32abc02c710f09a72a30c9a55ea3cc154ef8099078fd50a0596f8e/rpds_py-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746", size = 220998 }, + { url = "https://files.pythonhosted.org/packages/bd/fe/38de28dee5df58b8198c743fe2bea0c785c6d40941b9950bac4cdb71a014/rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90", size = 361887 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/4b6c7eedc7dd90986bf0fab6ea2a091ec11c01b15f8ba0a14d3f80450468/rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5", size = 345795 }, + { url = "https://files.pythonhosted.org/packages/6f/0e/e650e1b81922847a09cca820237b0edee69416a01268b7754d506ade11ad/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e", size = 385121 }, + { url = "https://files.pythonhosted.org/packages/1b/ea/b306067a712988e2bff00dcc7c8f31d26c29b6d5931b461aa4b60a013e33/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881", size = 398976 }, + { url = "https://files.pythonhosted.org/packages/2c/0a/26dc43c8840cb8fe239fe12dbc8d8de40f2365e838f3d395835dde72f0e5/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec", size = 525953 }, + { url = "https://files.pythonhosted.org/packages/22/14/c85e8127b573aaf3a0cbd7fbb8c9c99e735a4a02180c84da2a463b766e9e/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb", size = 407915 }, + { url = "https://files.pythonhosted.org/packages/ed/7b/8f4fee9ba1fb5ec856eb22d725a4efa3deb47f769597c809e03578b0f9d9/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5", size = 386883 }, + { url = "https://files.pythonhosted.org/packages/86/47/28fa6d60f8b74fcdceba81b272f8d9836ac0340570f68f5df6b41838547b/rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a", size = 405699 }, + { url = "https://files.pythonhosted.org/packages/d0/fd/c5987b5e054548df56953a21fe2ebed51fc1ec7c8f24fd41c067b68c4a0a/rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444", size = 423713 }, + { url = "https://files.pythonhosted.org/packages/ac/ba/3c4978b54a73ed19a7d74531be37a8bcc542d917c770e14d372b8daea186/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a", size = 562324 }, + { url = "https://files.pythonhosted.org/packages/b5/6c/6943a91768fec16db09a42b08644b960cff540c66aab89b74be6d4a144ba/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1", size = 593646 }, + { url = "https://files.pythonhosted.org/packages/11/73/9d7a8f4be5f4396f011a6bb7a19fe26303a0dac9064462f5651ced2f572f/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998", size = 558137 }, + { url = "https://files.pythonhosted.org/packages/6e/96/6772cbfa0e2485bcceef8071de7821f81aeac8bb45fbfd5542a3e8108165/rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39", size = 221343 }, + { url = "https://files.pythonhosted.org/packages/67/b6/c82f0faa9af1c6a64669f73a17ee0eeef25aff30bb9a1c318509efe45d84/rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594", size = 232497 }, + { url = "https://files.pythonhosted.org/packages/e1/96/2817b44bd2ed11aebacc9251da03689d56109b9aba5e311297b6902136e2/rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502", size = 222790 }, + { url = "https://files.pythonhosted.org/packages/7f/6c/252e83e1ce7583c81f26d1d884b2074d40a13977e1b6c9c50bbf9a7f1f5a/rpds_py-0.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c918c65ec2e42c2a78d19f18c553d77319119bf43aa9e2edf7fb78d624355527", size = 372140 }, + { url = "https://files.pythonhosted.org/packages/9d/71/949c195d927c5aeb0d0629d329a20de43a64c423a6aa53836290609ef7ec/rpds_py-0.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1fea2b1a922c47c51fd07d656324531adc787e415c8b116530a1d29c0516c62d", size = 354086 }, + { url = "https://files.pythonhosted.org/packages/9f/02/e43e332ad8ce4f6c4342d151a471a7f2900ed1d76901da62eb3762663a71/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbf94c58e8e0cd6b6f38d8de67acae41b3a515c26169366ab58bdca4a6883bb8", size = 382117 }, + { url = "https://files.pythonhosted.org/packages/d0/05/b0fdeb5b577197ad72812bbdfb72f9a08fa1e64539cc3940b1b781cd3596/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2a8fed130ce946d5c585eddc7c8eeef0051f58ac80a8ee43bd17835c144c2cc", size = 394520 }, + { url = "https://files.pythonhosted.org/packages/67/1f/4cfef98b2349a7585181e99294fa2a13f0af06902048a5d70f431a66d0b9/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:037a2361db72ee98d829bc2c5b7cc55598ae0a5e0ec1823a56ea99374cfd73c1", size = 522657 }, + { url = "https://files.pythonhosted.org/packages/44/55/ccf37ddc4c6dce7437b335088b5ca18da864b334890e2fe9aa6ddc3f79a9/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5281ed1cc1d49882f9997981c88df1a22e140ab41df19071222f7e5fc4e72125", size = 402967 }, + { url = "https://files.pythonhosted.org/packages/74/e5/5903f92e41e293b07707d5bf00ef39a0eb2af7190aff4beaf581a6591510/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fd50659a069c15eef8aa3d64bbef0d69fd27bb4a50c9ab4f17f83a16cbf8905", size = 384372 }, + { url = "https://files.pythonhosted.org/packages/8f/e3/fbb409e18aeefc01e49f5922ac63d2d914328430e295c12183ce56ebf76b/rpds_py-0.27.1-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:c4b676c4ae3921649a15d28ed10025548e9b561ded473aa413af749503c6737e", size = 401264 }, + { url = "https://files.pythonhosted.org/packages/55/79/529ad07794e05cb0f38e2f965fc5bb20853d523976719400acecc447ec9d/rpds_py-0.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:079bc583a26db831a985c5257797b2b5d3affb0386e7ff886256762f82113b5e", size = 418691 }, + { url = "https://files.pythonhosted.org/packages/33/39/6554a7fd6d9906fda2521c6d52f5d723dca123529fb719a5b5e074c15e01/rpds_py-0.27.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4e44099bd522cba71a2c6b97f68e19f40e7d85399de899d66cdb67b32d7cb786", size = 558989 }, + { url = "https://files.pythonhosted.org/packages/19/b2/76fa15173b6f9f445e5ef15120871b945fb8dd9044b6b8c7abe87e938416/rpds_py-0.27.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e202e6d4188e53c6661af813b46c37ca2c45e497fc558bacc1a7630ec2695aec", size = 589835 }, + { url = "https://files.pythonhosted.org/packages/ee/9e/5560a4b39bab780405bed8a88ee85b30178061d189558a86003548dea045/rpds_py-0.27.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f41f814b8eaa48768d1bb551591f6ba45f87ac76899453e8ccd41dba1289b04b", size = 555227 }, + { url = "https://files.pythonhosted.org/packages/52/d7/cd9c36215111aa65724c132bf709c6f35175973e90b32115dedc4ced09cb/rpds_py-0.27.1-cp39-cp39-win32.whl", hash = "sha256:9e71f5a087ead99563c11fdaceee83ee982fd39cf67601f4fd66cb386336ee52", size = 217899 }, + { url = "https://files.pythonhosted.org/packages/5b/e0/d75ab7b4dd8ba777f6b365adbdfc7614bbfe7c5f05703031dfa4b61c3d6c/rpds_py-0.27.1-cp39-cp39-win_amd64.whl", hash = "sha256:71108900c9c3c8590697244b9519017a400d9ba26a36c48381b3f64743a44aab", size = 228725 }, + { url = "https://files.pythonhosted.org/packages/d5/63/b7cc415c345625d5e62f694ea356c58fb964861409008118f1245f8c3347/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7ba22cb9693df986033b91ae1d7a979bc399237d45fccf875b76f62bb9e52ddf", size = 371360 }, + { url = "https://files.pythonhosted.org/packages/e5/8c/12e1b24b560cf378b8ffbdb9dc73abd529e1adcfcf82727dfd29c4a7b88d/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b640501be9288c77738b5492b3fd3abc4ba95c50c2e41273c8a1459f08298d3", size = 353933 }, + { url = "https://files.pythonhosted.org/packages/9b/85/1bb2210c1f7a1b99e91fea486b9f0f894aa5da3a5ec7097cbad7dec6d40f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb08b65b93e0c6dd70aac7f7890a9c0938d5ec71d5cb32d45cf844fb8ae47636", size = 382962 }, + { url = "https://files.pythonhosted.org/packages/cc/c9/a839b9f219cf80ed65f27a7f5ddbb2809c1b85c966020ae2dff490e0b18e/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d7ff07d696a7a38152ebdb8212ca9e5baab56656749f3d6004b34ab726b550b8", size = 394412 }, + { url = "https://files.pythonhosted.org/packages/02/2d/b1d7f928b0b1f4fc2e0133e8051d199b01d7384875adc63b6ddadf3de7e5/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb7c72262deae25366e3b6c0c0ba46007967aea15d1eea746e44ddba8ec58dcc", size = 523972 }, + { url = "https://files.pythonhosted.org/packages/a9/af/2cbf56edd2d07716df1aec8a726b3159deb47cb5c27e1e42b71d705a7c2f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b002cab05d6339716b03a4a3a2ce26737f6231d7b523f339fa061d53368c9d8", size = 403273 }, + { url = "https://files.pythonhosted.org/packages/c0/93/425e32200158d44ff01da5d9612c3b6711fe69f606f06e3895511f17473b/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f6b69d1c26c4704fec01311963a41d7de3ee0570a84ebde4d544e5a1859ffc", size = 385278 }, + { url = "https://files.pythonhosted.org/packages/eb/1a/1a04a915ecd0551bfa9e77b7672d1937b4b72a0fc204a17deef76001cfb2/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:530064db9146b247351f2a0250b8f00b289accea4596a033e94be2389977de71", size = 402084 }, + { url = "https://files.pythonhosted.org/packages/51/f7/66585c0fe5714368b62951d2513b684e5215beaceab2c6629549ddb15036/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b90b0496570bd6b0321724a330d8b545827c4df2034b6ddfc5f5275f55da2ad", size = 419041 }, + { url = "https://files.pythonhosted.org/packages/8e/7e/83a508f6b8e219bba2d4af077c35ba0e0cdd35a751a3be6a7cba5a55ad71/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879b0e14a2da6a1102a3fc8af580fc1ead37e6d6692a781bd8c83da37429b5ab", size = 560084 }, + { url = "https://files.pythonhosted.org/packages/66/66/bb945683b958a1b19eb0fe715594630d0f36396ebdef4d9b89c2fa09aa56/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:0d807710df3b5faa66c731afa162ea29717ab3be17bdc15f90f2d9f183da4059", size = 590115 }, + { url = "https://files.pythonhosted.org/packages/12/00/ccfaafaf7db7e7adace915e5c2f2c2410e16402561801e9c7f96683002d3/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3adc388fc3afb6540aec081fa59e6e0d3908722771aa1e37ffe22b220a436f0b", size = 556561 }, + { url = "https://files.pythonhosted.org/packages/e1/b7/92b6ed9aad103bfe1c45df98453dfae40969eef2cb6c6239c58d7e96f1b3/rpds_py-0.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c796c0c1cc68cb08b0284db4229f5af76168172670c74908fdbd4b7d7f515819", size = 229125 }, + { url = "https://files.pythonhosted.org/packages/0c/ed/e1fba02de17f4f76318b834425257c8ea297e415e12c68b4361f63e8ae92/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df", size = 371402 }, + { url = "https://files.pythonhosted.org/packages/af/7c/e16b959b316048b55585a697e94add55a4ae0d984434d279ea83442e460d/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3", size = 354084 }, + { url = "https://files.pythonhosted.org/packages/de/c1/ade645f55de76799fdd08682d51ae6724cb46f318573f18be49b1e040428/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9", size = 383090 }, + { url = "https://files.pythonhosted.org/packages/1f/27/89070ca9b856e52960da1472efcb6c20ba27cfe902f4f23ed095b9cfc61d/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc", size = 394519 }, + { url = "https://files.pythonhosted.org/packages/b3/28/be120586874ef906aa5aeeae95ae8df4184bc757e5b6bd1c729ccff45ed5/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4", size = 523817 }, + { url = "https://files.pythonhosted.org/packages/a8/ef/70cc197bc11cfcde02a86f36ac1eed15c56667c2ebddbdb76a47e90306da/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66", size = 403240 }, + { url = "https://files.pythonhosted.org/packages/cf/35/46936cca449f7f518f2f4996e0e8344db4b57e2081e752441154089d2a5f/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e", size = 385194 }, + { url = "https://files.pythonhosted.org/packages/e1/62/29c0d3e5125c3270b51415af7cbff1ec587379c84f55a5761cc9efa8cd06/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c", size = 402086 }, + { url = "https://files.pythonhosted.org/packages/8f/66/03e1087679227785474466fdd04157fb793b3b76e3fcf01cbf4c693c1949/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf", size = 419272 }, + { url = "https://files.pythonhosted.org/packages/6a/24/e3e72d265121e00b063aef3e3501e5b2473cf1b23511d56e529531acf01e/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf", size = 560003 }, + { url = "https://files.pythonhosted.org/packages/26/ca/f5a344c534214cc2d41118c0699fffbdc2c1bc7046f2a2b9609765ab9c92/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6", size = 590482 }, + { url = "https://files.pythonhosted.org/packages/ce/08/4349bdd5c64d9d193c360aa9db89adeee6f6682ab8825dca0a3f535f434f/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a", size = 556523 }, + { url = "https://files.pythonhosted.org/packages/4e/ea/5463cd5048a7a2fcdae308b6e96432802132c141bfb9420260142632a0f1/rpds_py-0.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa8933159edc50be265ed22b401125c9eebff3171f570258854dbce3ecd55475", size = 371778 }, + { url = "https://files.pythonhosted.org/packages/0d/c8/f38c099db07f5114029c1467649d308543906933eebbc226d4527a5f4693/rpds_py-0.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a50431bf02583e21bf273c71b89d710e7a710ad5e39c725b14e685610555926f", size = 354394 }, + { url = "https://files.pythonhosted.org/packages/7d/79/b76f97704d9dd8ddbd76fed4c4048153a847c5d6003afe20a6b5c3339065/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78af06ddc7fe5cc0e967085a9115accee665fb912c22a3f54bad70cc65b05fe6", size = 382348 }, + { url = "https://files.pythonhosted.org/packages/8a/3f/ef23d3c1be1b837b648a3016d5bbe7cfe711422ad110b4081c0a90ef5a53/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70d0738ef8fee13c003b100c2fbd667ec4f133468109b3472d249231108283a3", size = 394159 }, + { url = "https://files.pythonhosted.org/packages/74/8a/9e62693af1a34fd28b1a190d463d12407bd7cf561748cb4745845d9548d3/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2f6fd8a1cea5bbe599b6e78a6e5ee08db434fc8ffea51ff201c8765679698b3", size = 522775 }, + { url = "https://files.pythonhosted.org/packages/36/0d/8d5bb122bf7a60976b54c5c99a739a3819f49f02d69df3ea2ca2aff47d5c/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8177002868d1426305bb5de1e138161c2ec9eb2d939be38291d7c431c4712df8", size = 402633 }, + { url = "https://files.pythonhosted.org/packages/0f/0e/237948c1f425e23e0cf5a566d702652a6e55c6f8fbd332a1792eb7043daf/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:008b839781d6c9bf3b6a8984d1d8e56f0ec46dc56df61fd669c49b58ae800400", size = 384867 }, + { url = "https://files.pythonhosted.org/packages/d6/0a/da0813efcd998d260cbe876d97f55b0f469ada8ba9cbc47490a132554540/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:a55b9132bb1ade6c734ddd2759c8dc132aa63687d259e725221f106b83a0e485", size = 401791 }, + { url = "https://files.pythonhosted.org/packages/51/78/c6c9e8a8aaca416a6f0d1b6b4a6ee35b88fe2c5401d02235d0a056eceed2/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a46fdec0083a26415f11d5f236b79fa1291c32aaa4a17684d82f7017a1f818b1", size = 419525 }, + { url = "https://files.pythonhosted.org/packages/a3/69/5af37e1d71487cf6d56dd1420dc7e0c2732c1b6ff612aa7a88374061c0a8/rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8a63b640a7845f2bdd232eb0d0a4a2dd939bcdd6c57e6bb134526487f3160ec5", size = 559255 }, + { url = "https://files.pythonhosted.org/packages/40/7f/8b7b136069ef7ac3960eda25d832639bdb163018a34c960ed042dd1707c8/rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7e32721e5d4922deaaf963469d795d5bde6093207c52fec719bd22e5d1bedbc4", size = 590384 }, + { url = "https://files.pythonhosted.org/packages/d8/06/c316d3f6ff03f43ccb0eba7de61376f8ec4ea850067dddfafe98274ae13c/rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2c426b99a068601b5f4623573df7a7c3d72e87533a2dd2253353a03e7502566c", size = 555959 }, + { url = "https://files.pythonhosted.org/packages/60/94/384cf54c430b9dac742bbd2ec26c23feb78ded0d43d6d78563a281aec017/rpds_py-0.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4fc9b7fe29478824361ead6e14e4f5aed570d477e06088826537e202d25fe859", size = 228784 }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490 }, + { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751 }, + { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696 }, + { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136 }, + { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699 }, + { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022 }, + { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522 }, + { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579 }, + { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305 }, + { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503 }, + { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322 }, + { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792 }, + { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901 }, + { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823 }, + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157 }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676 }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938 }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932 }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830 }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033 }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828 }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683 }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583 }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496 }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669 }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011 }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406 }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024 }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069 }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086 }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053 }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763 }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951 }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622 }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492 }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080 }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680 }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589 }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289 }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737 }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120 }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782 }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463 }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868 }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292 }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128 }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542 }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004 }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063 }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099 }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177 }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015 }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736 }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981 }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782 }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191 }, +] + +[[package]] +name = "ruff" +version = "0.14.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080 }, + { url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320 }, + { url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434 }, + { url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961 }, + { url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629 }, + { url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234 }, + { url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890 }, + { url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172 }, + { url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260 }, + { url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978 }, + { url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036 }, + { url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051 }, + { url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998 }, + { url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891 }, + { url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660 }, + { url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187 }, + { url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283 }, + { url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839 }, +] + +[[package]] +name = "scikit-learn" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "joblib", marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "threadpoolctl", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/a5/4ae3b3a0755f7b35a280ac90b28817d1f380318973cff14075ab41ef50d9/scikit_learn-1.6.1.tar.gz", hash = "sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e", size = 7068312 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/3a/f4597eb41049110b21ebcbb0bcb43e4035017545daa5eedcfeb45c08b9c5/scikit_learn-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d056391530ccd1e501056160e3c9673b4da4805eb67eb2bdf4e983e1f9c9204e", size = 12067702 }, + { url = "https://files.pythonhosted.org/packages/37/19/0423e5e1fd1c6ec5be2352ba05a537a473c1677f8188b9306097d684b327/scikit_learn-1.6.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:0c8d036eb937dbb568c6242fa598d551d88fb4399c0344d95c001980ec1c7d36", size = 11112765 }, + { url = "https://files.pythonhosted.org/packages/70/95/d5cb2297a835b0f5fc9a77042b0a2d029866379091ab8b3f52cc62277808/scikit_learn-1.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8634c4bd21a2a813e0a7e3900464e6d593162a29dd35d25bdf0103b3fce60ed5", size = 12643991 }, + { url = "https://files.pythonhosted.org/packages/b7/91/ab3c697188f224d658969f678be86b0968ccc52774c8ab4a86a07be13c25/scikit_learn-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:775da975a471c4f6f467725dff0ced5c7ac7bda5e9316b260225b48475279a1b", size = 13497182 }, + { url = "https://files.pythonhosted.org/packages/17/04/d5d556b6c88886c092cc989433b2bab62488e0f0dafe616a1d5c9cb0efb1/scikit_learn-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:8a600c31592bd7dab31e1c61b9bbd6dea1b3433e67d264d17ce1017dbdce8002", size = 11125517 }, + { url = "https://files.pythonhosted.org/packages/6c/2a/e291c29670795406a824567d1dfc91db7b699799a002fdaa452bceea8f6e/scikit_learn-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72abc587c75234935e97d09aa4913a82f7b03ee0b74111dcc2881cba3c5a7b33", size = 12102620 }, + { url = "https://files.pythonhosted.org/packages/25/92/ee1d7a00bb6b8c55755d4984fd82608603a3cc59959245068ce32e7fb808/scikit_learn-1.6.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b3b00cdc8f1317b5f33191df1386c0befd16625f49d979fe77a8d44cae82410d", size = 11116234 }, + { url = "https://files.pythonhosted.org/packages/30/cd/ed4399485ef364bb25f388ab438e3724e60dc218c547a407b6e90ccccaef/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc4765af3386811c3ca21638f63b9cf5ecf66261cc4815c1db3f1e7dc7b79db2", size = 12592155 }, + { url = "https://files.pythonhosted.org/packages/a8/f3/62fc9a5a659bb58a03cdd7e258956a5824bdc9b4bb3c5d932f55880be569/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25fc636bdaf1cc2f4a124a116312d837148b5e10872147bdaf4887926b8c03d8", size = 13497069 }, + { url = "https://files.pythonhosted.org/packages/a1/a6/c5b78606743a1f28eae8f11973de6613a5ee87366796583fb74c67d54939/scikit_learn-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:fa909b1a36e000a03c382aade0bd2063fd5680ff8b8e501660c0f59f021a6415", size = 11139809 }, + { url = "https://files.pythonhosted.org/packages/0a/18/c797c9b8c10380d05616db3bfb48e2a3358c767affd0857d56c2eb501caa/scikit_learn-1.6.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:926f207c804104677af4857b2c609940b743d04c4c35ce0ddc8ff4f053cddc1b", size = 12104516 }, + { url = "https://files.pythonhosted.org/packages/c4/b7/2e35f8e289ab70108f8cbb2e7a2208f0575dc704749721286519dcf35f6f/scikit_learn-1.6.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c2cae262064e6a9b77eee1c8e768fc46aa0b8338c6a8297b9b6759720ec0ff2", size = 11167837 }, + { url = "https://files.pythonhosted.org/packages/a4/f6/ff7beaeb644bcad72bcfd5a03ff36d32ee4e53a8b29a639f11bcb65d06cd/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1061b7c028a8663fb9a1a1baf9317b64a257fcb036dae5c8752b2abef31d136f", size = 12253728 }, + { url = "https://files.pythonhosted.org/packages/29/7a/8bce8968883e9465de20be15542f4c7e221952441727c4dad24d534c6d99/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e69fab4ebfc9c9b580a7a80111b43d214ab06250f8a7ef590a4edf72464dd86", size = 13147700 }, + { url = "https://files.pythonhosted.org/packages/62/27/585859e72e117fe861c2079bcba35591a84f801e21bc1ab85bce6ce60305/scikit_learn-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:70b1d7e85b1c96383f872a519b3375f92f14731e279a7b4c6cfd650cf5dffc52", size = 11110613 }, + { url = "https://files.pythonhosted.org/packages/d2/37/b305b759cc65829fe1b8853ff3e308b12cdd9d8884aa27840835560f2b42/scikit_learn-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6849dd3234e87f55dce1db34c89a810b489ead832aaf4d4550b7ea85628be6c1", size = 12101868 }, + { url = "https://files.pythonhosted.org/packages/83/74/f64379a4ed5879d9db744fe37cfe1978c07c66684d2439c3060d19a536d8/scikit_learn-1.6.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:e7be3fa5d2eb9be7d77c3734ff1d599151bb523674be9b834e8da6abe132f44e", size = 11144062 }, + { url = "https://files.pythonhosted.org/packages/fd/dc/d5457e03dc9c971ce2b0d750e33148dd060fefb8b7dc71acd6054e4bb51b/scikit_learn-1.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44a17798172df1d3c1065e8fcf9019183f06c87609b49a124ebdf57ae6cb0107", size = 12693173 }, + { url = "https://files.pythonhosted.org/packages/79/35/b1d2188967c3204c78fa79c9263668cf1b98060e8e58d1a730fe5b2317bb/scikit_learn-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b7a3b86e411e4bce21186e1c180d792f3d99223dcfa3b4f597ecc92fa1a422", size = 13518605 }, + { url = "https://files.pythonhosted.org/packages/fb/d8/8d603bdd26601f4b07e2363032b8565ab82eb857f93d86d0f7956fcf4523/scikit_learn-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7a73d457070e3318e32bdb3aa79a8d990474f19035464dfd8bede2883ab5dc3b", size = 11155078 }, +] + +[[package]] +name = "scikit-learn" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "joblib", marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "threadpoolctl", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/3e/daed796fd69cce768b8788401cc464ea90b306fb196ae1ffed0b98182859/scikit_learn-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b33579c10a3081d076ab403df4a4190da4f4432d443521674637677dc91e61f", size = 9336221 }, + { url = "https://files.pythonhosted.org/packages/1c/ce/af9d99533b24c55ff4e18d9b7b4d9919bbc6cd8f22fe7a7be01519a347d5/scikit_learn-1.7.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:36749fb62b3d961b1ce4fedf08fa57a1986cd409eff2d783bca5d4b9b5fce51c", size = 8653834 }, + { url = "https://files.pythonhosted.org/packages/58/0e/8c2a03d518fb6bd0b6b0d4b114c63d5f1db01ff0f9925d8eb10960d01c01/scikit_learn-1.7.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7a58814265dfc52b3295b1900cfb5701589d30a8bb026c7540f1e9d3499d5ec8", size = 9660938 }, + { url = "https://files.pythonhosted.org/packages/2b/75/4311605069b5d220e7cf5adabb38535bd96f0079313cdbb04b291479b22a/scikit_learn-1.7.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a847fea807e278f821a0406ca01e387f97653e284ecbd9750e3ee7c90347f18", size = 9477818 }, + { url = "https://files.pythonhosted.org/packages/7f/9b/87961813c34adbca21a6b3f6b2bea344c43b30217a6d24cc437c6147f3e8/scikit_learn-1.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:ca250e6836d10e6f402436d6463d6c0e4d8e0234cfb6a9a47835bd392b852ce5", size = 8886969 }, + { url = "https://files.pythonhosted.org/packages/43/83/564e141eef908a5863a54da8ca342a137f45a0bfb71d1d79704c9894c9d1/scikit_learn-1.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7509693451651cd7361d30ce4e86a1347493554f172b1c72a39300fa2aea79e", size = 9331967 }, + { url = "https://files.pythonhosted.org/packages/18/d6/ba863a4171ac9d7314c4d3fc251f015704a2caeee41ced89f321c049ed83/scikit_learn-1.7.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:0486c8f827c2e7b64837c731c8feff72c0bd2b998067a8a9cbc10643c31f0fe1", size = 8648645 }, + { url = "https://files.pythonhosted.org/packages/ef/0e/97dbca66347b8cf0ea8b529e6bb9367e337ba2e8be0ef5c1a545232abfde/scikit_learn-1.7.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89877e19a80c7b11a2891a27c21c4894fb18e2c2e077815bcade10d34287b20d", size = 9715424 }, + { url = "https://files.pythonhosted.org/packages/f7/32/1f3b22e3207e1d2c883a7e09abb956362e7d1bd2f14458c7de258a26ac15/scikit_learn-1.7.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8da8bf89d4d79aaec192d2bda62f9b56ae4e5b4ef93b6a56b5de4977e375c1f1", size = 9509234 }, + { url = "https://files.pythonhosted.org/packages/9f/71/34ddbd21f1da67c7a768146968b4d0220ee6831e4bcbad3e03dd3eae88b6/scikit_learn-1.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:9b7ed8d58725030568523e937c43e56bc01cadb478fc43c042a9aca1dacb3ba1", size = 8894244 }, + { url = "https://files.pythonhosted.org/packages/a7/aa/3996e2196075689afb9fce0410ebdb4a09099d7964d061d7213700204409/scikit_learn-1.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d91a97fa2b706943822398ab943cde71858a50245e31bc71dba62aab1d60a96", size = 9259818 }, + { url = "https://files.pythonhosted.org/packages/43/5d/779320063e88af9c4a7c2cf463ff11c21ac9c8bd730c4a294b0000b666c9/scikit_learn-1.7.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:acbc0f5fd2edd3432a22c69bed78e837c70cf896cd7993d71d51ba6708507476", size = 8636997 }, + { url = "https://files.pythonhosted.org/packages/5c/d0/0c577d9325b05594fdd33aa970bf53fb673f051a45496842caee13cfd7fe/scikit_learn-1.7.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5bf3d930aee75a65478df91ac1225ff89cd28e9ac7bd1196853a9229b6adb0b", size = 9478381 }, + { url = "https://files.pythonhosted.org/packages/82/70/8bf44b933837ba8494ca0fc9a9ab60f1c13b062ad0197f60a56e2fc4c43e/scikit_learn-1.7.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d6e9deed1a47aca9fe2f267ab8e8fe82ee20b4526b2c0cd9e135cea10feb44", size = 9300296 }, + { url = "https://files.pythonhosted.org/packages/c6/99/ed35197a158f1fdc2fe7c3680e9c70d0128f662e1fee4ed495f4b5e13db0/scikit_learn-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:6088aa475f0785e01bcf8529f55280a3d7d298679f50c0bb70a2364a82d0b290", size = 8731256 }, +] + +[[package]] +name = "scikit-learn" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "joblib", marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "threadpoolctl", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/92/53ea2181da8ac6bf27170191028aee7251f8f841f8d3edbfdcaf2008fde9/scikit_learn-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:146b4d36f800c013d267b29168813f7a03a43ecd2895d04861f1240b564421da", size = 8595835 }, + { url = "https://files.pythonhosted.org/packages/01/18/d154dc1638803adf987910cdd07097d9c526663a55666a97c124d09fb96a/scikit_learn-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f984ca4b14914e6b4094c5d52a32ea16b49832c03bd17a110f004db3c223e8e1", size = 8080381 }, + { url = "https://files.pythonhosted.org/packages/8a/44/226142fcb7b7101e64fdee5f49dbe6288d4c7af8abf593237b70fca080a4/scikit_learn-1.8.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e30adb87f0cc81c7690a84f7932dd66be5bac57cfe16b91cb9151683a4a2d3b", size = 8799632 }, + { url = "https://files.pythonhosted.org/packages/36/4d/4a67f30778a45d542bbea5db2dbfa1e9e100bf9ba64aefe34215ba9f11f6/scikit_learn-1.8.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ada8121bcb4dac28d930febc791a69f7cb1673c8495e5eee274190b73a4559c1", size = 9103788 }, + { url = "https://files.pythonhosted.org/packages/89/3c/45c352094cfa60050bcbb967b1faf246b22e93cb459f2f907b600f2ceda5/scikit_learn-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:c57b1b610bd1f40ba43970e11ce62821c2e6569e4d74023db19c6b26f246cb3b", size = 8081706 }, + { url = "https://files.pythonhosted.org/packages/3d/46/5416595bb395757f754feb20c3d776553a386b661658fb21b7c814e89efe/scikit_learn-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:2838551e011a64e3053ad7618dda9310175f7515f1742fa2d756f7c874c05961", size = 7688451 }, + { url = "https://files.pythonhosted.org/packages/90/74/e6a7cc4b820e95cc38cf36cd74d5aa2b42e8ffc2d21fe5a9a9c45c1c7630/scikit_learn-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fb63362b5a7ddab88e52b6dbb47dac3fd7dafeee740dc6c8d8a446ddedade8e", size = 8548242 }, + { url = "https://files.pythonhosted.org/packages/49/d8/9be608c6024d021041c7f0b3928d4749a706f4e2c3832bbede4fb4f58c95/scikit_learn-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5025ce924beccb28298246e589c691fe1b8c1c96507e6d27d12c5fadd85bfd76", size = 8079075 }, + { url = "https://files.pythonhosted.org/packages/dd/47/f187b4636ff80cc63f21cd40b7b2d177134acaa10f6bb73746130ee8c2e5/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4496bb2cf7a43ce1a2d7524a79e40bc5da45cf598dbf9545b7e8316ccba47bb4", size = 8660492 }, + { url = "https://files.pythonhosted.org/packages/97/74/b7a304feb2b49df9fafa9382d4d09061a96ee9a9449a7cbea7988dda0828/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bcfe4d0d14aec44921545fd2af2338c7471de9cb701f1da4c9d85906ab847a", size = 8931904 }, + { url = "https://files.pythonhosted.org/packages/9f/c4/0ab22726a04ede56f689476b760f98f8f46607caecff993017ac1b64aa5d/scikit_learn-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:35c007dedb2ffe38fe3ee7d201ebac4a2deccd2408e8621d53067733e3c74809", size = 8019359 }, + { url = "https://files.pythonhosted.org/packages/24/90/344a67811cfd561d7335c1b96ca21455e7e472d281c3c279c4d3f2300236/scikit_learn-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:8c497fff237d7b4e07e9ef1a640887fa4fb765647f86fbe00f969ff6280ce2bb", size = 7641898 }, +] + +[[package]] +name = "scikit-optimize" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging" }, + { name = "pyaml" }, + { name = "scikit-learn", version = "1.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "scikit-learn", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/95/1b433b9eb9eb653fb97fd525552fd027886e3812d7d20d843994263340aa/scikit_optimize-0.10.2.tar.gz", hash = "sha256:00a3d91bf9015e292b6e7aaefe7e6cb95e8d25ce19adafd2cd88849e1a0b0da0", size = 86202 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/cd/15c9ebea645cc9860aa71fe0474f4be981f10ed8e19e1fb0ef1027d4966e/scikit_optimize-0.10.2-py2.py3-none-any.whl", hash = "sha256:45bc7e879b086133984721f2f6735a86c085073f6c481c2ec665b5c67b44d723", size = 107794 }, +] + +[[package]] +name = "scipy" +version = "1.13.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/00/48c2f661e2816ccf2ecd77982f6605b2950afe60f60a52b4cbbc2504aa8f/scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c", size = 57210720 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/59/41b2529908c002ade869623b87eecff3e11e3ce62e996d0bdcb536984187/scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca", size = 39328076 }, + { url = "https://files.pythonhosted.org/packages/d5/33/f1307601f492f764062ce7dd471a14750f3360e33cd0f8c614dae208492c/scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f", size = 30306232 }, + { url = "https://files.pythonhosted.org/packages/c0/66/9cd4f501dd5ea03e4a4572ecd874936d0da296bd04d1c45ae1a4a75d9c3a/scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989", size = 33743202 }, + { url = "https://files.pythonhosted.org/packages/a3/ba/7255e5dc82a65adbe83771c72f384d99c43063648456796436c9a5585ec3/scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f", size = 38577335 }, + { url = "https://files.pythonhosted.org/packages/49/a5/bb9ded8326e9f0cdfdc412eeda1054b914dfea952bda2097d174f8832cc0/scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94", size = 38820728 }, + { url = "https://files.pythonhosted.org/packages/12/30/df7a8fcc08f9b4a83f5f27cfaaa7d43f9a2d2ad0b6562cced433e5b04e31/scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54", size = 46210588 }, + { url = "https://files.pythonhosted.org/packages/b4/15/4a4bb1b15bbd2cd2786c4f46e76b871b28799b67891f23f455323a0cdcfb/scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9", size = 39333805 }, + { url = "https://files.pythonhosted.org/packages/ba/92/42476de1af309c27710004f5cdebc27bec62c204db42e05b23a302cb0c9a/scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326", size = 30317687 }, + { url = "https://files.pythonhosted.org/packages/80/ba/8be64fe225360a4beb6840f3cbee494c107c0887f33350d0a47d55400b01/scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299", size = 33694638 }, + { url = "https://files.pythonhosted.org/packages/36/07/035d22ff9795129c5a847c64cb43c1fa9188826b59344fee28a3ab02e283/scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa", size = 38569931 }, + { url = "https://files.pythonhosted.org/packages/d9/10/f9b43de37e5ed91facc0cfff31d45ed0104f359e4f9a68416cbf4e790241/scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59", size = 38838145 }, + { url = "https://files.pythonhosted.org/packages/4a/48/4513a1a5623a23e95f94abd675ed91cfb19989c58e9f6f7d03990f6caf3d/scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b", size = 46196227 }, + { url = "https://files.pythonhosted.org/packages/f2/7b/fb6b46fbee30fc7051913068758414f2721003a89dd9a707ad49174e3843/scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1", size = 39357301 }, + { url = "https://files.pythonhosted.org/packages/dc/5a/2043a3bde1443d94014aaa41e0b50c39d046dda8360abd3b2a1d3f79907d/scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d", size = 30363348 }, + { url = "https://files.pythonhosted.org/packages/e7/cb/26e4a47364bbfdb3b7fb3363be6d8a1c543bcd70a7753ab397350f5f189a/scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627", size = 33406062 }, + { url = "https://files.pythonhosted.org/packages/88/ab/6ecdc526d509d33814835447bbbeedbebdec7cca46ef495a61b00a35b4bf/scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884", size = 38218311 }, + { url = "https://files.pythonhosted.org/packages/0b/00/9f54554f0f8318100a71515122d8f4f503b1a2c4b4cfab3b4b68c0eb08fa/scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16", size = 38442493 }, + { url = "https://files.pythonhosted.org/packages/3e/df/963384e90733e08eac978cd103c34df181d1fec424de383cdc443f418dd4/scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949", size = 45910955 }, + { url = "https://files.pythonhosted.org/packages/7f/29/c2ea58c9731b9ecb30b6738113a95d147e83922986b34c685b8f6eefde21/scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5", size = 39352927 }, + { url = "https://files.pythonhosted.org/packages/5c/c0/e71b94b20ccf9effb38d7147c0064c08c622309fd487b1b677771a97d18c/scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24", size = 30324538 }, + { url = "https://files.pythonhosted.org/packages/6d/0f/aaa55b06d474817cea311e7b10aab2ea1fd5d43bc6a2861ccc9caec9f418/scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004", size = 33732190 }, + { url = "https://files.pythonhosted.org/packages/35/f5/d0ad1a96f80962ba65e2ce1de6a1e59edecd1f0a7b55990ed208848012e0/scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d", size = 38612244 }, + { url = "https://files.pythonhosted.org/packages/8d/02/1165905f14962174e6569076bcc3315809ae1291ed14de6448cc151eedfd/scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c", size = 38845637 }, + { url = "https://files.pythonhosted.org/packages/3e/77/dab54fe647a08ee4253963bcd8f9cf17509c8ca64d6335141422fe2e2114/scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2", size = 46227440 }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770 }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511 }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151 }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732 }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617 }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964 }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749 }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383 }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201 }, + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255 }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035 }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499 }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602 }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415 }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622 }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796 }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684 }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504 }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735 }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284 }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958 }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454 }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199 }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455 }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140 }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549 }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184 }, +] + +[[package]] +name = "scipy" +version = "1.16.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/5f/6f37d7439de1455ce9c5a556b8d1db0979f03a796c030bafdf08d35b7bf9/scipy-1.16.3-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:40be6cf99e68b6c4321e9f8782e7d5ff8265af28ef2cd56e9c9b2638fa08ad97", size = 36630881 }, + { url = "https://files.pythonhosted.org/packages/7c/89/d70e9f628749b7e4db2aa4cd89735502ff3f08f7b9b27d2e799485987cd9/scipy-1.16.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:8be1ca9170fcb6223cc7c27f4305d680ded114a1567c0bd2bfcbf947d1b17511", size = 28941012 }, + { url = "https://files.pythonhosted.org/packages/a8/a8/0e7a9a6872a923505dbdf6bb93451edcac120363131c19013044a1e7cb0c/scipy-1.16.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bea0a62734d20d67608660f69dcda23e7f90fb4ca20974ab80b6ed40df87a005", size = 20931935 }, + { url = "https://files.pythonhosted.org/packages/bd/c7/020fb72bd79ad798e4dbe53938543ecb96b3a9ac3fe274b7189e23e27353/scipy-1.16.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:2a207a6ce9c24f1951241f4693ede2d393f59c07abc159b2cb2be980820e01fb", size = 23534466 }, + { url = "https://files.pythonhosted.org/packages/be/a0/668c4609ce6dbf2f948e167836ccaf897f95fb63fa231c87da7558a374cd/scipy-1.16.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:532fb5ad6a87e9e9cd9c959b106b73145a03f04c7d57ea3e6f6bb60b86ab0876", size = 33593618 }, + { url = "https://files.pythonhosted.org/packages/ca/6e/8942461cf2636cdae083e3eb72622a7fbbfa5cf559c7d13ab250a5dbdc01/scipy-1.16.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0151a0749efeaaab78711c78422d413c583b8cdd2011a3c1d6c794938ee9fdb2", size = 35899798 }, + { url = "https://files.pythonhosted.org/packages/79/e8/d0f33590364cdbd67f28ce79368b373889faa4ee959588beddf6daef9abe/scipy-1.16.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7180967113560cca57418a7bc719e30366b47959dd845a93206fbed693c867e", size = 36226154 }, + { url = "https://files.pythonhosted.org/packages/39/c1/1903de608c0c924a1749c590064e65810f8046e437aba6be365abc4f7557/scipy-1.16.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:deb3841c925eeddb6afc1e4e4a45e418d19ec7b87c5df177695224078e8ec733", size = 38878540 }, + { url = "https://files.pythonhosted.org/packages/f1/d0/22ec7036ba0b0a35bccb7f25ab407382ed34af0b111475eb301c16f8a2e5/scipy-1.16.3-cp311-cp311-win_amd64.whl", hash = "sha256:53c3844d527213631e886621df5695d35e4f6a75f620dca412bcd292f6b87d78", size = 38722107 }, + { url = "https://files.pythonhosted.org/packages/7b/60/8a00e5a524bb3bf8898db1650d350f50e6cffb9d7a491c561dc9826c7515/scipy-1.16.3-cp311-cp311-win_arm64.whl", hash = "sha256:9452781bd879b14b6f055b26643703551320aa8d79ae064a71df55c00286a184", size = 25506272 }, + { url = "https://files.pythonhosted.org/packages/40/41/5bf55c3f386b1643812f3a5674edf74b26184378ef0f3e7c7a09a7e2ca7f/scipy-1.16.3-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81fc5827606858cf71446a5e98715ba0e11f0dbc83d71c7409d05486592a45d6", size = 36659043 }, + { url = "https://files.pythonhosted.org/packages/1e/0f/65582071948cfc45d43e9870bf7ca5f0e0684e165d7c9ef4e50d783073eb/scipy-1.16.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:c97176013d404c7346bf57874eaac5187d969293bf40497140b0a2b2b7482e07", size = 28898986 }, + { url = "https://files.pythonhosted.org/packages/96/5e/36bf3f0ac298187d1ceadde9051177d6a4fe4d507e8f59067dc9dd39e650/scipy-1.16.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2b71d93c8a9936046866acebc915e2af2e292b883ed6e2cbe5c34beb094b82d9", size = 20889814 }, + { url = "https://files.pythonhosted.org/packages/80/35/178d9d0c35394d5d5211bbff7ac4f2986c5488b59506fef9e1de13ea28d3/scipy-1.16.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3d4a07a8e785d80289dfe66b7c27d8634a773020742ec7187b85ccc4b0e7b686", size = 23565795 }, + { url = "https://files.pythonhosted.org/packages/fa/46/d1146ff536d034d02f83c8afc3c4bab2eddb634624d6529a8512f3afc9da/scipy-1.16.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0553371015692a898e1aa858fed67a3576c34edefa6b7ebdb4e9dde49ce5c203", size = 33349476 }, + { url = "https://files.pythonhosted.org/packages/79/2e/415119c9ab3e62249e18c2b082c07aff907a273741b3f8160414b0e9193c/scipy-1.16.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:72d1717fd3b5e6ec747327ce9bda32d5463f472c9dce9f54499e81fbd50245a1", size = 35676692 }, + { url = "https://files.pythonhosted.org/packages/27/82/df26e44da78bf8d2aeaf7566082260cfa15955a5a6e96e6a29935b64132f/scipy-1.16.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fb2472e72e24d1530debe6ae078db70fb1605350c88a3d14bc401d6306dbffe", size = 36019345 }, + { url = "https://files.pythonhosted.org/packages/82/31/006cbb4b648ba379a95c87262c2855cd0d09453e500937f78b30f02fa1cd/scipy-1.16.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5192722cffe15f9329a3948c4b1db789fbb1f05c97899187dcf009b283aea70", size = 38678975 }, + { url = "https://files.pythonhosted.org/packages/c2/7f/acbd28c97e990b421af7d6d6cd416358c9c293fc958b8529e0bd5d2a2a19/scipy-1.16.3-cp312-cp312-win_amd64.whl", hash = "sha256:56edc65510d1331dae01ef9b658d428e33ed48b4f77b1d51caf479a0253f96dc", size = 38555926 }, + { url = "https://files.pythonhosted.org/packages/ce/69/c5c7807fd007dad4f48e0a5f2153038dc96e8725d3345b9ee31b2b7bed46/scipy-1.16.3-cp312-cp312-win_arm64.whl", hash = "sha256:a8a26c78ef223d3e30920ef759e25625a0ecdd0d60e5a8818b7513c3e5384cf2", size = 25463014 }, +] + +[[package]] +name = "secretstorage" +version = "3.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "cryptography", marker = "python_full_version < '3.10'" }, + { name = "jeepney", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/a4/f48c9d79cb507ed1373477dbceaba7401fd8a23af63b837fa61f1dcd3691/SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", size = 19739 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/24/b4293291fa1dd830f353d2cb163295742fa87f179fcc8a20a306a81978b7/SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99", size = 15221 }, +] + +[[package]] +name = "secretstorage" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "cryptography", marker = "python_full_version >= '3.10'" }, + { name = "jeepney", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554 }, +] + +[[package]] +name = "send2trash" +version = "1.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072 }, +] + +[[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 = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274 }, +] + +[[package]] +name = "soupsieve" +version = "2.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/89/23/adf3796d740536d63a6fbda113d07e60c734b6ed5d3058d1e47fc0495e47/soupsieve-2.8.1.tar.gz", hash = "sha256:4cf733bc50fa805f5df4b8ef4740fc0e0fa6218cf3006269afd3f9d6d80fd350", size = 117856 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl", hash = "sha256:a11fe2a6f3d76ab3cf2de04eb339c1be5b506a8a47f2ceb6d139803177f85434", size = 36710 }, +] + +[[package]] +name = "sphinx" +version = "5.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/b2/02a43597980903483fe5eb081ee8e0ba2bb62ea43a70499484343795f3bf/Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5", size = 6811365 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/a7/01dd6fd9653c056258d65032aa09a615b5d7b07dd840845a9f41a8860fbc/sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d", size = 3183160 }, +] + +[[package]] +name = "sphinx-gallery" +version = "0.19.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "pillow", version = "11.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "sphinx", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/e5/9ccd6ecd492043123adb465cba504217b9f0a82e2cb5b1d7249c648497c6/sphinx_gallery-0.19.0.tar.gz", hash = "sha256:8400cb5240ad642e28a612fdba0667f725d0505a9be0222d0243de60e8af2eb3", size = 471479 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/c7/52b48aec16b26c52aba854d03a3a31e0681150301dac1bea2243645a69e7/sphinx_gallery-0.19.0-py3-none-any.whl", hash = "sha256:4c28751973f81769d5bbbf5e4ebaa0dc49dff8c48eb7f11131eb5f6e4aa25f0e", size = 455923 }, +] + +[[package]] +name = "sphinx-gallery" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "pillow", version = "12.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "sphinx", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5f/14/9238ac61932299b38c20c7c37dbfe60348c0348ea4d400f9ef25875b3bf7/sphinx_gallery-0.20.0.tar.gz", hash = "sha256:70281510c6183d812d3595957005ccf555c5a793f207410f6cd16a25bf08d735", size = 473502 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/fd/818a53d4da56ef2da7b08f77bb3a825635941d1fcc6b6a490995dec1a81c/sphinx_gallery-0.20.0-py3-none-any.whl", hash = "sha256:188b7456e269649945825661b76cdbfbf0b70c2cfd5b75c9a11fe52519879e4d", size = 458655 }, +] + +[[package]] +name = "sphinx-markdown-tables" +version = "0.0.17" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/36/af0e0a4aa69298b4d664d5176243412ae96bfc207d7e3314e12b3a6f2ccc/sphinx-markdown-tables-0.0.17.tar.gz", hash = "sha256:6bc6d3d400eaccfeebd288446bc08dd83083367c58b85d40fe6c12d77ef592f1", size = 15189 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/8d/8785a36892992582ef8d87c69ab90e26124ab1059c501d93ebecd99d2323/sphinx_markdown_tables-0.0.17-py3-none-any.whl", hash = "sha256:2bd0c30779653e4dd120300cbd9ca412c480738cc2241f6dea477a883f299e04", size = 28018 }, +] + +[[package]] +name = "sphinx-rtd-theme" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/32/580309c9fd5b1892c6616ce814710c6b14423e98bf1c101bf2c710433cee/sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c", size = 2780623 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/d2/3818e4730e314719e27f639c44164419e40eed826d63753dc480262036e8/sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8", size = 2815240 }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071 }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, +] + +[[package]] +name = "statsmodels" +version = "0.14.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "patsy" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/81/e8d74b34f85285f7335d30c5e3c2d7c0346997af9f3debf9a0a9a63de184/statsmodels-0.14.6.tar.gz", hash = "sha256:4d17873d3e607d398b85126cd4ed7aad89e4e9d89fc744cdab1af3189a996c2a", size = 20689085 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/6d/9ec309a175956f88eb8420ac564297f37cf9b1f73f89db74da861052dc29/statsmodels-0.14.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4ff0649a2df674c7ffb6fa1a06bffdb82a6adf09a48e90e000a15a6aaa734b0", size = 10142419 }, + { url = "https://files.pythonhosted.org/packages/86/8f/338c5568315ec5bf3ac7cd4b71e34b98cb3b0f834919c0c04a0762f878a1/statsmodels-0.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:109012088b3e370080846ab053c76d125268631410142daad2f8c10770e8e8d9", size = 10022819 }, + { url = "https://files.pythonhosted.org/packages/b0/77/5fc4cbc2d608f9b483b0675f82704a8bcd672962c379fe4d82100d388dbf/statsmodels-0.14.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e93bd5d220f3cb6fc5fc1bffd5b094966cab8ee99f6c57c02e95710513d6ac3f", size = 10118927 }, + { url = "https://files.pythonhosted.org/packages/94/55/b86c861c32186403fe121d9ab27bc16d05839b170d92a978beb33abb995e/statsmodels-0.14.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:06eec42d682fdb09fe5d70a05930857efb141754ec5a5056a03304c1b5e32fd9", size = 10413015 }, + { url = "https://files.pythonhosted.org/packages/f9/be/daf0dba729ccdc4176605f4a0fd5cfe71cdda671749dca10e74a732b8b1c/statsmodels-0.14.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0444e88557df735eda7db330806fe09d51c9f888bb1f5906cb3a61fb1a3ed4a8", size = 10441248 }, + { url = "https://files.pythonhosted.org/packages/9a/1c/2e10b7c7cc44fa418272996bf0427b8016718fd62f995d9c1f7ab37adf35/statsmodels-0.14.6-cp310-cp310-win_amd64.whl", hash = "sha256:e83a9abe653835da3b37fb6ae04b45480c1de11b3134bd40b09717192a1456ea", size = 9583410 }, + { url = "https://files.pythonhosted.org/packages/a9/4d/df4dd089b406accfc3bb5ee53ba29bb3bdf5ae61643f86f8f604baa57656/statsmodels-0.14.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ad5c2810fc6c684254a7792bf1cbaf1606cdee2a253f8bd259c43135d87cfb4", size = 10121514 }, + { url = "https://files.pythonhosted.org/packages/82/af/ec48daa7f861f993b91a0dcc791d66e1cf56510a235c5cbd2ab991a31d5c/statsmodels-0.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:341fa68a7403e10a95c7b6e41134b0da3a7b835ecff1eb266294408535a06eb6", size = 10003346 }, + { url = "https://files.pythonhosted.org/packages/a9/2c/c8f7aa24cd729970728f3f98822fb45149adc216f445a9301e441f7ac760/statsmodels-0.14.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdf1dfe2a3ca56f5529118baf33a13efed2783c528f4a36409b46bbd2d9d48eb", size = 10129872 }, + { url = "https://files.pythonhosted.org/packages/40/c6/9ae8e9b0721e9b6eb5f340c3a0ce8cd7cce4f66e03dd81f80d60f111987f/statsmodels-0.14.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3764ba8195c9baf0925a96da0743ff218067a269f01d155ca3558deed2658ca", size = 10381964 }, + { url = "https://files.pythonhosted.org/packages/28/8c/cf3d30c8c2da78e2ad1f50ade8b7fabec3ff4cdfc56fbc02e097c4577f90/statsmodels-0.14.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e8d2e519852adb1b420e018f5ac6e6684b2b877478adf7fda2cfdb58f5acb5d", size = 10409611 }, + { url = "https://files.pythonhosted.org/packages/bf/cc/018f14ecb58c6cb89de9d52695740b7d1f5a982aa9ea312483ea3c3d5f77/statsmodels-0.14.6-cp311-cp311-win_amd64.whl", hash = "sha256:2738a00fca51196f5a7d44b06970ace6b8b30289839e4808d656f8a98e35faa7", size = 9580385 }, + { url = "https://files.pythonhosted.org/packages/25/ce/308e5e5da57515dd7cab3ec37ea2d5b8ff50bef1fcc8e6d31456f9fae08e/statsmodels-0.14.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe76140ae7adc5ff0e60a3f0d56f4fffef484efa803c3efebf2fcd734d72ecb5", size = 10091932 }, + { url = "https://files.pythonhosted.org/packages/05/30/affbabf3c27fb501ec7b5808230c619d4d1a4525c07301074eb4bda92fa9/statsmodels-0.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26d4f0ed3b31f3c86f83a92f5c1f5cbe63fc992cd8915daf28ca49be14463a1c", size = 9997345 }, + { url = "https://files.pythonhosted.org/packages/48/f5/3a73b51e6450c31652c53a8e12e24eac64e3824be816c0c2316e7dbdcb7d/statsmodels-0.14.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8c00a42863e4f4733ac9d078bbfad816249c01451740e6f5053ecc7db6d6368", size = 10058649 }, + { url = "https://files.pythonhosted.org/packages/81/68/dddd76117df2ef14c943c6bbb6618be5c9401280046f4ddfc9fb4596a1b8/statsmodels-0.14.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b58cf7474aa9e7e3b0771a66537148b2df9b5884fbf156096c0e6c1ff0469d", size = 10339446 }, + { url = "https://files.pythonhosted.org/packages/56/4a/dce451c74c4050535fac1ec0c14b80706d8fc134c9da22db3c8a0ec62c33/statsmodels-0.14.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81e7dcc5e9587f2567e52deaff5220b175bf2f648951549eae5fc9383b62bc37", size = 10368705 }, + { url = "https://files.pythonhosted.org/packages/60/15/3daba2df40be8b8a9a027d7f54c8dedf24f0d81b96e54b52293f5f7e3418/statsmodels-0.14.6-cp312-cp312-win_amd64.whl", hash = "sha256:b5eb07acd115aa6208b4058211138393a7e6c2cf12b6f213ede10f658f6a714f", size = 9543991 }, + { url = "https://files.pythonhosted.org/packages/b6/c1/f3012162d55b43291267d15275433b208f63d2e91a4f82ad724679336d17/statsmodels-0.14.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4d0c1b0f9f6915619e2a0d3853e5763d4d66876892ad352e7d7b93a737556978", size = 10151985 }, + { url = "https://files.pythonhosted.org/packages/0b/d9/9bcd801ae2881884848bd53b5dc985e8d2a1b20cb1a0350f2a4b4dbfce24/statsmodels-0.14.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e0fc891d6358bf376cc0ae1fee10a650478172ae9ba359daba1785fc496cd1a", size = 10031652 }, + { url = "https://files.pythonhosted.org/packages/62/37/3b609324f22c151267784c5830d3afef2cc7b22970d6cf957f1b799fca3b/statsmodels-0.14.6-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f52ef0f0b63b8fd11e1ef1c2a1e73a410720b8715c9a83a26d733b6815597fe", size = 10121095 }, + { url = "https://files.pythonhosted.org/packages/40/4d/adf7615db9cc7802608b6343789e66ff6e8220f1a84066e502fe51e4f90f/statsmodels-0.14.6-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b328eafa86a2a67303fdb1d25677d15b70cd2a5229aabec7670ec5ea840f1375", size = 10411609 }, + { url = "https://files.pythonhosted.org/packages/3b/9d/a3d33f4bda4e6a5f9b1118e81f93d5bc1620ad8d685df15d79b291ad9b7f/statsmodels-0.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:3bef39f8587754f2d644b2e831e102fa08ace9a5a1af4b583b122e6fd3e083ab", size = 9590613 }, +] + +[[package]] +name = "stevedore" +version = "5.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/5f/8418daad5c353300b7661dd8ce2574b0410a6316a8be650a189d5c68d938/stevedore-5.5.0.tar.gz", hash = "sha256:d31496a4f4df9825e1a1e4f1f74d19abb0154aff311c3b376fcc89dae8fccd73", size = 513878 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/c5/0c06759b95747882bb50abda18f5fb48c3e9b0fbfc6ebc0e23550b52415d/stevedore-5.5.0-py3-none-any.whl", hash = "sha256:18363d4d268181e8e8452e71a38cd77630f345b2ef6b4a8d5614dac5ee0d18cf", size = 49518 }, +] + +[[package]] +name = "stevedore" +version = "5.6.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/96/5b/496f8abebd10c3301129abba7ddafd46c71d799a70c44ab080323987c4c9/stevedore-5.6.0.tar.gz", hash = "sha256:f22d15c6ead40c5bbfa9ca54aa7e7b4a07d59b36ae03ed12ced1a54cf0b51945", size = 516074 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/40/8561ce06dc46fd17242c7724ab25b257a2ac1b35f4ebf551b40ce6105cfa/stevedore-5.6.0-py3-none-any.whl", hash = "sha256:4a36dccefd7aeea0c70135526cecb7766c4c84c473b1af68db23d541b6dc1820", size = 54428 }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353 }, +] + +[[package]] +name = "terminado" +version = "0.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "os_name != 'nt'" }, + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154 }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638 }, +] + +[[package]] +name = "tinycss2" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610 }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588 }, +] + +[[package]] +name = "tomli" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236 }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084 }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832 }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052 }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555 }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128 }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445 }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165 }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891 }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796 }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121 }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070 }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859 }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296 }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124 }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698 }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408 }, +] + +[[package]] +name = "torch" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "filelock", version = "3.20.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "fsspec", version = "2025.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "fsspec", version = "2025.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "jinja2" }, + { name = "networkx", version = "3.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "sympy" }, + { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/05/d540049b1832d1062510efc6829634b7fbef5394c757d8312414fb65a3cb/torch-2.4.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:362f82e23a4cd46341daabb76fba08f04cd646df9bfaf5da50af97cb60ca4971", size = 797072810 }, + { url = "https://files.pythonhosted.org/packages/a0/12/2162df9c47386ae7cedbc938f9703fee4792d93504fab8608d541e71ece3/torch-2.4.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:e8ac1985c3ff0f60d85b991954cfc2cc25f79c84545aead422763148ed2759e3", size = 89699259 }, + { url = "https://files.pythonhosted.org/packages/5d/4c/b2a59ff0e265f5ee154f0d81e948b1518b94f545357731e1a3245ee5d45b/torch-2.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:91e326e2ccfb1496e3bee58f70ef605aeb27bd26be07ba64f37dcaac3d070ada", size = 199433813 }, + { url = "https://files.pythonhosted.org/packages/dc/fb/1333ba666bbd53846638dd75a7a1d4eaf964aff1c482fc046e2311a1b499/torch-2.4.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:d36a8ef100f5bff3e9c3cea934b9e0d7ea277cb8210c7152d34a9a6c5830eadd", size = 62139309 }, + { url = "https://files.pythonhosted.org/packages/ea/ea/4ab009e953bca6ff35ad75b8ab58c0923308636c182c145dc63084f7d136/torch-2.4.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:0b5f88afdfa05a335d80351e3cea57d38e578c8689f751d35e0ff36bce872113", size = 797111232 }, + { url = "https://files.pythonhosted.org/packages/8f/a1/b31f94b4631c1731261db9fdc9a749ef58facc3b76094a6fe974f611f239/torch-2.4.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:ef503165f2341942bfdf2bd520152f19540d0c0e34961232f134dc59ad435be8", size = 89719574 }, + { url = "https://files.pythonhosted.org/packages/5a/6a/775b93d6888c31f1f1fc457e4f5cc89f0984412d5dcdef792b8f2aa6e812/torch-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:092e7c2280c860eff762ac08c4bdcd53d701677851670695e0c22d6d345b269c", size = 199436128 }, + { url = "https://files.pythonhosted.org/packages/1f/34/c93873c37f93154d982172755f7e504fdbae6c760499303a3111ce6ce327/torch-2.4.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:ddddbd8b066e743934a4200b3d54267a46db02106876d21cf31f7da7a96f98ea", size = 62145176 }, + { url = "https://files.pythonhosted.org/packages/cc/df/5204a13a7a973c23c7ade615bafb1a3112b5d0ec258d8390f078fa4ab0f7/torch-2.4.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:fdc4fe11db3eb93c1115d3e973a27ac7c1a8318af8934ffa36b0370efe28e042", size = 797019590 }, + { url = "https://files.pythonhosted.org/packages/4f/16/d23a689e5ef8001ed2ace1a3a59f2fda842889b0c3f3877799089925282a/torch-2.4.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:18835374f599207a9e82c262153c20ddf42ea49bc76b6eadad8e5f49729f6e4d", size = 89613802 }, + { url = "https://files.pythonhosted.org/packages/a8/e0/ca8354dfb8d834a76da51b06e8248b70fc182bc163540507919124974bdf/torch-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:ebea70ff30544fc021d441ce6b219a88b67524f01170b1c538d7d3ebb5e7f56c", size = 199387694 }, + { url = "https://files.pythonhosted.org/packages/ac/30/8b6f77ea4ce84f015ee024b8dfef0dac289396254e8bfd493906d4cbb848/torch-2.4.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:72b484d5b6cec1a735bf3fa5a1c4883d01748698c5e9cfdbeb4ffab7c7987e0d", size = 62123443 }, + { url = "https://files.pythonhosted.org/packages/14/d6/caa3ccde685a3bfedeed1454d82b2eb520e611d1b36bf748f54475de333f/torch-2.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:40f6d3fe3bae74efcf08cb7f8295eaddd8a838ce89e9d26929d4edd6d5e4329d", size = 797088350 }, + { url = "https://files.pythonhosted.org/packages/3d/5d/4e9a7e5b7f11710519c38fe6a9f588a91fd23e6e9722e79f90f03823222d/torch-2.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c9299c16c9743001ecef515536ac45900247f4338ecdf70746f2461f9e4831db", size = 89706796 }, + { url = "https://files.pythonhosted.org/packages/ef/44/238ef95daf345bab21afa0ca37b2896dfc20cd93b6b75722717685fdeb10/torch-2.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:6bce130f2cd2d52ba4e2c6ada461808de7e5eccbac692525337cfb4c19421846", size = 199332260 }, + { url = "https://files.pythonhosted.org/packages/e7/81/c05013695bfb3762f3c657a557407f152a0a0452b3ccec437a4a59848fb5/torch-2.4.1-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:a38de2803ee6050309aac032676536c3d3b6a9804248537e38e098d0e14817ec", size = 62139344 }, +] + +[[package]] +name = "tornado" +version = "6.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909 }, + { url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163 }, + { url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746 }, + { url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083 }, + { url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315 }, + { url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003 }, + { url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412 }, + { url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392 }, + { url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481 }, + { url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886 }, + { url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910 }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +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 = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, +] + +[[package]] +name = "triton" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "filelock", version = "3.20.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/27/14cc3101409b9b4b9241d2ba7deaa93535a217a211c86c4cc7151fb12181/triton-3.0.0-1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e1efef76935b2febc365bfadf74bcb65a6f959a9872e5bddf44cc9e0adce1e1a", size = 209376304 }, + { url = "https://files.pythonhosted.org/packages/33/3e/a2f59384587eff6aeb7d37b6780de7fedd2214935e27520430ca9f5b7975/triton-3.0.0-1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ce8520437c602fb633f1324cc3871c47bee3b67acf9756c1a66309b60e3216c", size = 209438883 }, + { url = "https://files.pythonhosted.org/packages/fe/7b/7757205dee3628f75e7991021d15cd1bd0c9b044ca9affe99b50879fc0e1/triton-3.0.0-1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:34e509deb77f1c067d8640725ef00c5cbfcb2052a1a3cb6a6d343841f92624eb", size = 209464695 }, + { url = "https://files.pythonhosted.org/packages/6c/bf/55cccf57c14787ad81ee827526ddd48fd0aff0291fcc7b8c2e2bdf28da0a/triton-3.0.0-1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6e5727202f7078c56f91ff13ad0c1abab14a0e7f2c87e91b12b6f64f3e8ae609", size = 209377082 }, +] + +[[package]] +name = "twine" +version = "3.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "importlib-metadata" }, + { name = "keyring" }, + { name = "pkginfo" }, + { name = "readme-renderer" }, + { name = "requests" }, + { name = "requests-toolbelt" }, + { name = "rfc3986" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/12/0e4c8df764d87c15b8256444d0b8b433c183ce3a986ffae3086df3f876ef/twine-3.7.1.tar.gz", hash = "sha256:28460a3db6b4532bde6a5db6755cf2dce6c5020bada8a641bb2c5c7a9b1f35b8", size = 231946 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/aa/636b8eb9637944d2d94b766997d0420d1911abfd6392a6e3e2a75347949a/twine-3.7.1-py3-none-any.whl", hash = "sha256:8c120845fc05270f9ee3e9d7ebbed29ea840e41f48cd059e04733f7e1d401345", size = 35982 }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 }, +] + +[[package]] +name = "tzdata" +version = "2025.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521 }, +] + +[[package]] +name = "uri-template" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140 }, +] + +[[package]] +name = "urllib3" +version = "2.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182 }, +] + +[[package]] +name = "virtualenv" +version = "20.35.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "filelock", version = "3.20.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095 }, +] + +[[package]] +name = "wcwidth" +version = "0.2.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286 }, +] + +[[package]] +name = "webcolors" +version = "24.11.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/29/061ec845fb58521848f3739e466efd8250b4b7b98c1b6c5bf4d40b419b7e/webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6", size = 45064 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9", size = 14934 }, +] + +[[package]] +name = "webcolors" +version = "25.10.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/7a/eb316761ec35664ea5174709a68bbd3389de60d4a1ebab8808bfc264ed67/webcolors-25.10.0.tar.gz", hash = "sha256:62abae86504f66d0f6364c2a8520de4a0c47b80c03fc3a5f1815fedbef7c19bf", size = 53491 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d", size = 14905 }, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, +] + +[[package]] +name = "websocket-client" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616 }, +] + +[[package]] +name = "wheel" +version = "0.37.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/6c/9f840c2e55b67b90745af06a540964b73589256cb10cc10057c87ac78fc2/wheel-0.37.1.tar.gz", hash = "sha256:e9a504e793efbca1b8e0e9cb979a249cf4a0a7b5b8c9e8b65a5e39d49529c1c4", size = 66376 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/d6/003e593296a85fd6ed616ed962795b2f87709c3eee2bca4f6d0fe55c6d00/wheel-0.37.1-py2.py3-none-any.whl", hash = "sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a", size = 35301 }, +] + +[[package]] +name = "widgetsnbextension" +version = "4.0.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/f4/c67440c7fb409a71b7404b7aefcd7569a9c0d6bd071299bf4198ae7a5d95/widgetsnbextension-4.0.15.tar.gz", hash = "sha256:de8610639996f1567952d763a5a41af8af37f2575a41f9852a38f947eb82a3b9", size = 1097402 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl", hash = "sha256:8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366", size = 2196503 }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276 }, +] From 003a1209bdf46cb17cc9ec166c2bec18a44e689c Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Tue, 30 Dec 2025 00:51:23 +0100 Subject: [PATCH 32/39] added temporary numpy version test --- qolmat/imputations/softimpute.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qolmat/imputations/softimpute.py b/qolmat/imputations/softimpute.py index f6ad3937..88f22121 100644 --- a/qolmat/imputations/softimpute.py +++ b/qolmat/imputations/softimpute.py @@ -59,6 +59,8 @@ class SoftImpute(BaseEstimator, TransformerMixin): >>> from qolmat.imputations.softimpute import SoftImpute >>> D = np.array([[1, 2, np.nan, 4], [1, 5, 3, np.nan], [4, 2, 3, 2], [1, 1, 5, 4]]) >>> Omega = ~np.isnan(D) + >>> print(np.__version__) + 2.2.6 >>> M, A = SoftImpute(random_state=11).decompose(D, Omega) >>> print(M + A) [[1. 2. 2.38678001 4. ] From 23ede5a9673b0f679773acc51907633821c06bd3 Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Tue, 30 Dec 2025 00:53:04 +0100 Subject: [PATCH 33/39] temporary numpy version test added --- qolmat/imputations/softimpute.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qolmat/imputations/softimpute.py b/qolmat/imputations/softimpute.py index 88f22121..a0ca9838 100644 --- a/qolmat/imputations/softimpute.py +++ b/qolmat/imputations/softimpute.py @@ -61,6 +61,8 @@ class SoftImpute(BaseEstimator, TransformerMixin): >>> Omega = ~np.isnan(D) >>> print(np.__version__) 2.2.6 + >>> print(SoftImpute(random_state=11).random_state.randint(0, 100)) + 25 >>> M, A = SoftImpute(random_state=11).decompose(D, Omega) >>> print(M + A) [[1. 2. 2.38678001 4. ] From 2b4775b3a2e9344d84d727e50a3d7f577617a431 Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Tue, 30 Dec 2025 14:47:50 +0100 Subject: [PATCH 34/39] further test --- qolmat/imputations/softimpute.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/qolmat/imputations/softimpute.py b/qolmat/imputations/softimpute.py index a0ca9838..f6ea8a40 100644 --- a/qolmat/imputations/softimpute.py +++ b/qolmat/imputations/softimpute.py @@ -63,6 +63,15 @@ class SoftImpute(BaseEstimator, TransformerMixin): 2.2.6 >>> print(SoftImpute(random_state=11).random_state.randint(0, 100)) 25 + >>> rs = sku.check_random_state(11) + >>> print("Step 1 - randint:", rs.randint(0, 100)) + Step 1 - randint: 25 + >>> U = rs.normal(0.0, 1.0, (4, 2)) + >>> print("Step 2 - U[0,0]:", U[0, 0]) + Step 2 - U[0,0]: 0.20031399762813357 + >>> U_svd, _, _ = np.linalg.svd(U, full_matrices=False) + >>> print("Step 3 - U_svd[0,0]:", U_svd[0, 0]) + Step 3 - U_svd[0,0]: -0.20385139037822042 >>> M, A = SoftImpute(random_state=11).decompose(D, Omega) >>> print(M + A) [[1. 2. 2.38678001 4. ] From 5aaae49e5cb7041303dcd420acc7f73dcef180f8 Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Tue, 30 Dec 2025 16:24:07 +0100 Subject: [PATCH 35/39] lighter ci tests --- .github/workflows/test.yml | 5 +++-- qolmat/imputations/softimpute.py | 12 +++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f55bfdc5..d9328814 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,8 +16,9 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, windows-latest] - python-version: ["3.9", "3.11", "3.12"] + # Full matrix only for PRs to main + os: ${{ github.event_name == 'pull_request' && github.base_ref == 'main' && fromJSON('["ubuntu-latest", "windows-latest"]') || fromJSON('["ubuntu-latest"]') }} + python-version: ${{ github.event_name == 'pull_request' && github.base_ref == 'main' && fromJSON('["3.9", "3.11", "3.12"]') || fromJSON('["3.12"]') }} defaults: run: shell: bash -l {0} diff --git a/qolmat/imputations/softimpute.py b/qolmat/imputations/softimpute.py index f6ea8a40..94c16cd3 100644 --- a/qolmat/imputations/softimpute.py +++ b/qolmat/imputations/softimpute.py @@ -44,7 +44,7 @@ class SoftImpute(BaseEstimator, TransformerMixin): tolerance : float Tolerance for the convergence criterion tau : float - regularisation parameter + Regularisation parameter max_iterations : int Maximum number of iterations random_state : int, optional @@ -72,12 +72,14 @@ class SoftImpute(BaseEstimator, TransformerMixin): >>> U_svd, _, _ = np.linalg.svd(U, full_matrices=False) >>> print("Step 3 - U_svd[0,0]:", U_svd[0, 0]) Step 3 - U_svd[0,0]: -0.20385139037822042 - >>> M, A = SoftImpute(random_state=11).decompose(D, Omega) + >>> M, A = SoftImpute(random_state=11, tau=1).decompose(D, Omega) >>> print(M + A) - [[1. 2. 2.38678001 4. ] - [1. 5. 3. 6.23499344] + [[1. 2. 3.0486858 4. ] + [1. 5. 3. 3.37501527] [4. 2. 3. 2. ] [1. 1. 5. 4. ]] + >>> print(SoftImpute.cost_function(D, M, A, Omega, tau=1)) + 18.520175080977893 """ @@ -281,7 +283,7 @@ def cost_function( Anomalies Omega : NDArray Mask for observations - tau: Optional[float] + tau: float penalizing parameter for the nuclear norm Returns From 23792e34299b5e2db0ef759525b072dd52804afe Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Tue, 30 Dec 2025 16:49:23 +0100 Subject: [PATCH 36/39] matrix output test replaced --- qolmat/imputations/softimpute.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/qolmat/imputations/softimpute.py b/qolmat/imputations/softimpute.py index 94c16cd3..46f0b773 100644 --- a/qolmat/imputations/softimpute.py +++ b/qolmat/imputations/softimpute.py @@ -59,25 +59,7 @@ class SoftImpute(BaseEstimator, TransformerMixin): >>> from qolmat.imputations.softimpute import SoftImpute >>> D = np.array([[1, 2, np.nan, 4], [1, 5, 3, np.nan], [4, 2, 3, 2], [1, 1, 5, 4]]) >>> Omega = ~np.isnan(D) - >>> print(np.__version__) - 2.2.6 - >>> print(SoftImpute(random_state=11).random_state.randint(0, 100)) - 25 - >>> rs = sku.check_random_state(11) - >>> print("Step 1 - randint:", rs.randint(0, 100)) - Step 1 - randint: 25 - >>> U = rs.normal(0.0, 1.0, (4, 2)) - >>> print("Step 2 - U[0,0]:", U[0, 0]) - Step 2 - U[0,0]: 0.20031399762813357 - >>> U_svd, _, _ = np.linalg.svd(U, full_matrices=False) - >>> print("Step 3 - U_svd[0,0]:", U_svd[0, 0]) - Step 3 - U_svd[0,0]: -0.20385139037822042 >>> M, A = SoftImpute(random_state=11, tau=1).decompose(D, Omega) - >>> print(M + A) - [[1. 2. 3.0486858 4. ] - [1. 5. 3. 3.37501527] - [4. 2. 3. 2. ] - [1. 1. 5. 4. ]] >>> print(SoftImpute.cost_function(D, M, A, Omega, tau=1)) 18.520175080977893 From 2e5dcb66c013fd3262dfce2074dad8ccc34de1d3 Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Tue, 30 Dec 2025 17:39:36 +0100 Subject: [PATCH 37/39] removed randomness --- qolmat/imputations/softimpute.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/qolmat/imputations/softimpute.py b/qolmat/imputations/softimpute.py index 46f0b773..8319823b 100644 --- a/qolmat/imputations/softimpute.py +++ b/qolmat/imputations/softimpute.py @@ -60,8 +60,13 @@ class SoftImpute(BaseEstimator, TransformerMixin): >>> D = np.array([[1, 2, np.nan, 4], [1, 5, 3, np.nan], [4, 2, 3, 2], [1, 1, 5, 4]]) >>> Omega = ~np.isnan(D) >>> M, A = SoftImpute(random_state=11, tau=1).decompose(D, Omega) + >>> print(M + A) + [[1. 2. 3.04868607 4. ] + [1. 5. 3. 3.37501463] + [4. 2. 3. 2. ] + [1. 1. 5. 4. ]] >>> print(SoftImpute.cost_function(D, M, A, Omega, tau=1)) - 18.520175080977893 + 18.520174964466026 """ @@ -134,7 +139,8 @@ def decompose(self, X: NDArray, Omega: NDArray) -> Tuple[NDArray, NDArray]: # Step 1 : Initializing n, m = X.shape V = np.zeros((m, rank)) - U = self.random_state.normal(0.0, 1.0, (n, rank)) + # U = self.random_state.normal(0.0, 1.0, (n, rank)) + U = np.zeros((n, rank)) U, _, _ = np.linalg.svd(U, full_matrices=False) D = np.ones((1, rank)) From 3dd450efc6a55abac4562a9ef8dd12942539686d Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Thu, 1 Jan 2026 17:04:55 +0100 Subject: [PATCH 38/39] doc tests made more robust --- qolmat/imputations/softimpute.py | 15 +++-- tests/imputations/test_softimpute.py | 92 +++++++++++++++++++--------- 2 files changed, 70 insertions(+), 37 deletions(-) diff --git a/qolmat/imputations/softimpute.py b/qolmat/imputations/softimpute.py index 8319823b..e7f366b6 100644 --- a/qolmat/imputations/softimpute.py +++ b/qolmat/imputations/softimpute.py @@ -59,14 +59,13 @@ class SoftImpute(BaseEstimator, TransformerMixin): >>> from qolmat.imputations.softimpute import SoftImpute >>> D = np.array([[1, 2, np.nan, 4], [1, 5, 3, np.nan], [4, 2, 3, 2], [1, 1, 5, 4]]) >>> Omega = ~np.isnan(D) - >>> M, A = SoftImpute(random_state=11, tau=1).decompose(D, Omega) - >>> print(M + A) - [[1. 2. 3.04868607 4. ] - [1. 5. 3. 3.37501463] - [4. 2. 3. 2. ] - [1. 1. 5. 4. ]] - >>> print(SoftImpute.cost_function(D, M, A, Omega, tau=1)) - 18.520174964466026 + >>> M, A = SoftImpute(random_state=10, tau=1).decompose(D, Omega) + >>> naive_cost = SoftImpute.cost_function( + ... D, np.where(Omega, M, 0), np.zeros_like(M), Omega, tau=1 + ... ) + >>> minimal_cost = SoftImpute.cost_function(D, M, A, Omega, tau=1) + >>> minimal_cost < naive_cost + np.True_ """ diff --git a/tests/imputations/test_softimpute.py b/tests/imputations/test_softimpute.py index 9b65d978..52d3e12b 100644 --- a/tests/imputations/test_softimpute.py +++ b/tests/imputations/test_softimpute.py @@ -2,22 +2,39 @@ import pytest from numpy.typing import NDArray -from qolmat.imputations import softimpute +from qolmat.imputations.softimpute import SoftImpute -X = np.random.rand(100, 100) -X[np.random.choice(100, 10), np.random.choice(100, 10)] = np.nan -X_non_regression_test = np.array( - [[1, 2, np.nan, 4], [1, 5, 3, np.nan], [4, 2, 3, 2], [1, 1, 5, 4]] -) -X_expected = np.array([[1, 2, 2.9066, 4], [1, 5, 3, 2.1478], [4, 2, 3, 2], [1, 1, 5, 4]]) -tau = 1 -max_iterations = 30 -random_state = 50 + +@pytest.fixture +def X_random() -> NDArray: + """Generate random matrix with missing values.""" + rng = np.random.RandomState(42) + X = rng.rand(100, 100) + X[rng.choice(100, 10), rng.choice(100, 10)] = np.nan + return X + + +@pytest.fixture +def X_non_regression() -> NDArray: + """Get small test matrix for non-regression tests.""" + return np.array([[1, 2, np.nan, 4], [1, 5, 3, np.nan], [4, 2, 3, 2], [1, 1, 5, 4]]) + + +@pytest.fixture +def X_expected() -> NDArray: + """Get expected imputed values for non-regression test.""" + return np.array([[1, 2, 2.9066, 4], [1, 5, 3, 2.1478], [4, 2, 3, 2], [1, 1, 5, 4]]) + + +@pytest.fixture +def default_params() -> dict: + """Get default parameters for SoftImpute.""" + return {"tau": 1, "max_iterations": 30, "random_state": 50} def test_initialized_default() -> None: """Test that initialization does not crash and has default parameters.""" - model = softimpute.SoftImpute() + model = SoftImpute() assert model.period == 1 assert model.rank is None assert model.tolerance == 1e-05 @@ -25,36 +42,34 @@ def test_initialized_default() -> None: def test_initialized_custom() -> None: """Test that initialization does not crash and has custom parameters.""" - model = softimpute.SoftImpute(period=2, rank=10) + model = SoftImpute(period=2, rank=10) assert model.period == 2 assert model.rank == 10 assert model.tau is None -@pytest.mark.parametrize("X", [X]) -def test_soft_impute_decompose(X: NDArray) -> None: +def test_soft_impute_decompose(X_random: NDArray, default_params: dict) -> None: """Test fit instance and decomposition is computed.""" - tau = 1 - model = softimpute.SoftImpute(tau=tau) - Omega = ~np.isnan(X) - X_imputed = np.where(Omega, X, 0) - cost_all_in_M = model.cost_function(X, X_imputed, np.full_like(X, 0), Omega, tau) - cost_all_in_A = model.cost_function(X, np.full_like(X, 0), X_imputed, Omega, tau) - M, A = model.decompose(X, Omega) - cost_final = model.cost_function(X, M, A, Omega, tau) - assert isinstance(model, softimpute.SoftImpute) - assert M.shape == X.shape - assert A.shape == X.shape + tau = default_params["tau"] + model = SoftImpute(tau=tau) + Omega = ~np.isnan(X_random) + X_imputed = np.where(Omega, X_random, 0) + cost_all_in_M = model.cost_function(X_random, X_imputed, np.full_like(X_random, 0), Omega, tau) + cost_all_in_A = model.cost_function(X_random, np.full_like(X_random, 0), X_imputed, Omega, tau) + M, A = model.decompose(X_random, Omega) + cost_final = model.cost_function(X_random, M, A, Omega, tau) + assert isinstance(model, SoftImpute) + assert M.shape == X_random.shape + assert A.shape == X_random.shape assert not np.any(np.isnan(M)) assert not np.any(np.isnan(A)) assert cost_final < cost_all_in_M assert cost_final < cost_all_in_A -@pytest.mark.parametrize("X", [X]) -def test_soft_impute_convergence(X: NDArray) -> None: +def test_soft_impute_convergence() -> None: """Test type of the check convergence.""" - model = softimpute.SoftImpute() + model = SoftImpute() M = model.random_state.uniform(size=(10, 20)) U, D, V = np.linalg.svd(M, full_matrices=False) ratio = model._check_convergence(U, D, V.T, U, D, V.T) @@ -63,7 +78,7 @@ def test_soft_impute_convergence(X: NDArray) -> None: def test_soft_impute_convergence_with_none() -> None: """Test check type None and raise error.""" - model = softimpute.SoftImpute() + model = SoftImpute() with pytest.raises(ValueError): _ = model._check_convergence( np.array([1]), @@ -73,3 +88,22 @@ def test_soft_impute_convergence_with_none() -> None: np.array([1]), np.array([1]), ) + + +def test_decompose_loss_minimized(X_random: NDArray, default_params: dict) -> None: + """Test that the loss function is at a local minimum.""" + tau = default_params["tau"] + imputer = SoftImpute(random_state=123, tau=tau) + Omega = ~np.isnan(X_random) + M, A = imputer.decompose(X_random, Omega) + X_imputed = M + A + cost_imputed = SoftImpute.cost_function(X_imputed, M, A, Omega, tau) + for i in range(10): + Delta = 1.1 ** (i - 9) * imputer.random_state.uniform(0, 1, size=X_random.shape) + X_perturbed = X_imputed + Delta + cost_perturbed = SoftImpute.cost_function(X_perturbed, M, A, Omega, tau) + assert cost_perturbed > cost_imputed + M = np.zeros(X_random.shape) + A = X_random.copy() + cost_perturbed = SoftImpute.cost_function(X_random, M, A, Omega, tau) + assert cost_perturbed > cost_imputed From 4e7d6d6e2b8be2e9009642de7a861d8a53a464ae Mon Sep 17 00:00:00 2001 From: Julien Roussel <3178729-JulienRoussel77@users.noreply.gitlab.com> Date: Thu, 1 Jan 2026 17:24:06 +0100 Subject: [PATCH 39/39] =?UTF-8?q?Bump=20version:=200.1.10=20=E2=86=92=200.?= =?UTF-8?q?2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- docs/conf.py | 2 +- pyproject.toml | 2 +- qolmat/_version.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 00f1f563..48046ff9 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.1.10 +current_version = 0.2.0 commit = True tag = True diff --git a/docs/conf.py b/docs/conf.py index 3fe9a734..5cf642b5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -27,7 +27,7 @@ author = "Quantmetry" # The full version, including alpha/beta/rc tags -version = "0.1.10" +version = "0.2.0" release = version # -- General configuration --------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index 93e48bba..66a46324 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "qolmat" -version = "0.1.10" +version = "0.2.0" description = "A Python library for optimal data imputation." authors = [ { name = "Julien ROUSSEL", email = "julien.roussel@capgemini.com" }, diff --git a/qolmat/_version.py b/qolmat/_version.py index 569b1212..d3ec452c 100644 --- a/qolmat/_version.py +++ b/qolmat/_version.py @@ -1 +1 @@ -__version__ = "0.1.10" +__version__ = "0.2.0"