chemotools 0.1.9__py3-none-any.whl → 0.1.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,228 @@
1
+ from typing import Optional, Literal, Union
2
+
3
+ import numpy as np
4
+
5
+ from scipy.stats import norm, chi2
6
+ from sklearn.pipeline import Pipeline
7
+ from sklearn.utils.validation import validate_data, check_is_fitted
8
+
9
+ from ._base import _ModelResidualsBase, ModelTypes
10
+ from .utils import calculate_residual_spectrum
11
+
12
+
13
+ class QResiduals(_ModelResidualsBase):
14
+ """
15
+ Calculate Q residuals (Squared Prediction Error - SPE) for PCA or PLS models.
16
+
17
+ Parameters
18
+ ----------
19
+ model : Union[ModelType, Pipeline]
20
+ A fitted PCA/PLS model or Pipeline ending with such a model.
21
+
22
+ confidence : float, default=0.95
23
+ Confidence level for statistical calculations (between 0 and 1).
24
+
25
+ method : str, default="jackson-mudholkar"
26
+ The method used to compute the confidence threshold for Q residuals.
27
+ Options:
28
+ - "chi-square" : Uses mean and standard deviation to approximate Q residuals threshold.
29
+ - "jackson-mudholkar" : Uses eigenvalue-based analytical approximation.
30
+ - "percentile" : Uses empirical percentile threshold.
31
+
32
+ Attributes
33
+ ----------
34
+ estimator_ : ModelType
35
+ The fitted model of type _BasePCA or _PLS.
36
+
37
+ transformer_ : Optional[Pipeline]
38
+ Preprocessing steps before the model.
39
+
40
+ n_features_in_ : int
41
+ Number of features in the input data.
42
+
43
+ n_components_ : int
44
+ Number of components in the model.
45
+
46
+ n_samples_ : int
47
+ Number of samples used to train the model.
48
+
49
+ critical_value_ : float
50
+ The calculated critical value for outlier detection.
51
+
52
+ References
53
+ ----------
54
+ Johan A. Westerhuis, Stephen P. Gurden, Age K. Smilde (2001) Generalized contribution plots in multivariate statistical process
55
+ monitoring Chemometrics and Intelligent Laboratory Systems 51 2000 95–114
56
+ """
57
+
58
+ def __init__(
59
+ self,
60
+ model: Union[ModelTypes, Pipeline],
61
+ confidence: float = 0.95,
62
+ method: Literal[
63
+ "chi-square", "jackson-mudholkar", "percentile"
64
+ ] = "jackson-mudholkar",
65
+ ) -> None:
66
+ self.model, self.confidence, self.method = model, confidence, method
67
+ super().__init__(model, confidence)
68
+
69
+ def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None) -> "QResiduals":
70
+ """
71
+ Fit the Q Residuals model by computing residuals from the training set.
72
+
73
+ Parameters
74
+ ----------
75
+ X : array-like of shape (n_samples, n_features)
76
+ Training data.
77
+
78
+ Returns
79
+ -------
80
+ self : object
81
+ Fitted instance of QResiduals.
82
+ """
83
+ X = validate_data(self, X, ensure_2d=True, dtype=np.float64)
84
+
85
+ if self.transformer_:
86
+ X = self.transformer_.fit_transform(X)
87
+
88
+ # Compute the critical threshold using the chosen method
89
+ self.critical_value_ = self._calculate_critical_value(X)
90
+
91
+ return self
92
+
93
+ def predict(self, X: np.ndarray) -> np.ndarray:
94
+ """Identify outliers in the input data based on Q residuals threshold.
95
+
96
+ Parameters
97
+ ----------
98
+ X : array-like of shape (n_samples, n_features)
99
+ Input data.
100
+
101
+ Returns
102
+ -------
103
+ ndarray of shape (n_samples,)
104
+ Boolean array indicating outliers (-1 for outliers, 1 for normal data).
105
+ """
106
+ # Check the estimator has been fitted
107
+ check_is_fitted(self, ["critical_value_"])
108
+
109
+ # Validate the input data
110
+ X = validate_data(
111
+ self, X, y="no_validation", ensure_2d=True, reset=True, dtype=np.float64
112
+ )
113
+
114
+ # Calculate outliers based on the Q residuals
115
+ Q_residuals = self.predict_residuals(X, validate=False)
116
+ return np.where(Q_residuals > self.critical_value_, -1, 1)
117
+
118
+ def predict_residuals(
119
+ self, X: np.ndarray, y: Optional[np.ndarray] = None, validate: bool = True
120
+ ) -> np.ndarray:
121
+ """Calculate Q residuals (Squared Prediction Error - SPE) for input data.
122
+
123
+ Parameters
124
+ ----------
125
+ X : array-like of shape (n_samples, n_features)
126
+ Input data.
127
+
128
+ validate : bool, default=True
129
+ Whether to validate the input data.
130
+
131
+ Returns
132
+ -------
133
+ ndarray of shape (n_samples,)
134
+ Q residuals for each sample.
135
+ """
136
+ # Check the estimator has been fitted
137
+ check_is_fitted(self, ["critical_value_"])
138
+
139
+ # Validate the input data
140
+ if validate:
141
+ X = validate_data(self, X, ensure_2d=True, dtype=np.float64)
142
+
143
+ # Apply preprocessing if available
144
+ if self.transformer_:
145
+ X = self.transformer_.transform(X)
146
+
147
+ # Compute reconstruction error (Q residuals)
148
+ residual = calculate_residual_spectrum(X, self.estimator_)
149
+ Q_residuals = np.sum(residual**2, axis=1)
150
+
151
+ return Q_residuals
152
+
153
+ def _calculate_critical_value(
154
+ self,
155
+ X: np.ndarray,
156
+ ) -> float:
157
+ """Calculate the critical value for outlier detection.
158
+
159
+ Parameters
160
+ ----------
161
+ X : array-like of shape (n_samples, n_features)
162
+ Input data.
163
+
164
+ X_reconstructed : array-like of shape (n_samples, n_features)
165
+ Reconstructed input data.
166
+
167
+ method : str Literal["chi-square", "jackson-mudholkar", "percentile"]
168
+ The method used to compute the confidence threshold for Q residuals.
169
+
170
+ Returns
171
+ -------
172
+ float
173
+ The calculated critical value for outlier detection.
174
+
175
+ """
176
+ # Compute Q residuals for training data
177
+ residuals = calculate_residual_spectrum(X, self.estimator_)
178
+
179
+ if self.method == "chi-square":
180
+ return self._chi_square_threshold(residuals)
181
+
182
+ elif self.method == "jackson-mudholkar":
183
+ return self._jackson_mudholkar_threshold(residuals)
184
+
185
+ elif self.method == "percentile":
186
+ Q_residuals = np.sum((residuals) ** 2, axis=1)
187
+ return self._percentile_threshold(Q_residuals)
188
+
189
+ else:
190
+ raise ValueError(
191
+ "Invalid method. Choose from 'chi-square', 'jackson-mudholkar', or 'percentile'."
192
+ )
193
+
194
+ def _chi_square_threshold(self, residuals: np.ndarray) -> float:
195
+ """Compute Q residual threshold using Chi-Square Approximation."""
196
+ eigenvalues = np.linalg.trace(np.cov(residuals.T))
197
+
198
+ theta_1 = np.sum(eigenvalues)
199
+ theta_2 = np.sum(eigenvalues**2)
200
+ # Degrees of freedom approximation
201
+ g = theta_2 / theta_1
202
+ h = (2 * theta_1**2) / theta_2
203
+
204
+ # Compute chi-square critical value at given confidence level
205
+ chi_critical = chi2.ppf(self.confidence, df=h)
206
+
207
+ # Compute final Q residual threshold
208
+ return g * chi_critical
209
+
210
+ def _jackson_mudholkar_threshold(self, residuals: np.ndarray) -> float:
211
+ """Compute Q residual threshold using Jackson & Mudholkar’s analytical method."""
212
+
213
+ eigenvalues = np.linalg.trace(np.cov(residuals.T))
214
+ theta_1 = np.sum(eigenvalues)
215
+ theta_2 = np.sum(eigenvalues**2)
216
+ theta_3 = np.sum(eigenvalues**3)
217
+ z_alpha = norm.ppf(self.confidence)
218
+
219
+ h0 = 1 - (2 * theta_1 * theta_3) / (3 * theta_2**2)
220
+
221
+ term1 = theta_2 * h0 * (1 - h0) / theta_1**2
222
+ term2 = np.sqrt(z_alpha * 2 * theta_2 * h0**2) / theta_1
223
+
224
+ return theta_1 * (1 - term1 + term2) ** (1 / h0)
225
+
226
+ def _percentile_threshold(self, Q_residuals: np.ndarray) -> float:
227
+ """Compute Q residual threshold using the empirical percentile method."""
228
+ return np.percentile(Q_residuals, self.confidence * 100)
@@ -0,0 +1,198 @@
1
+ from typing import Optional, Union
2
+ import numpy as np
3
+
4
+ from sklearn.cross_decomposition._pls import _PLS
5
+ from sklearn.pipeline import Pipeline
6
+ from sklearn.utils.validation import validate_data, check_is_fitted
7
+
8
+
9
+ from ._base import _ModelResidualsBase, ModelTypes
10
+ from .leverage import calculate_leverage
11
+
12
+
13
+ class StudentizedResiduals(_ModelResidualsBase):
14
+ """
15
+ Calculate the Studentized Residuals on a _PLS model preditions.
16
+
17
+ Parameters
18
+ ----------
19
+ model : Union[ModelType, Pipeline]
20
+ A fitted _PLS model or Pipeline ending with such a model
21
+
22
+ Attributes
23
+ ----------
24
+ estimator_ : ModelType
25
+ The fitted model of type _BasePCA or _PLS
26
+
27
+ transformer_ : Optional[Pipeline]
28
+ Preprocessing steps before the model
29
+
30
+ References
31
+ ----------
32
+
33
+ """
34
+
35
+ def __init__(self, model: Union[_PLS, Pipeline], confidence=0.95) -> None:
36
+ self.model, self.confidence = model, confidence
37
+ super().__init__(model, confidence)
38
+
39
+ def fit(self, X: np.ndarray, y: Optional[np.ndarray]) -> "StudentizedResiduals":
40
+ """
41
+ Fit the model to the input data.
42
+
43
+ Parameters
44
+ ----------
45
+ X : array-like of shape (n_samples, n_features)
46
+ Input data
47
+
48
+ y : array-like of shape (n_samples,)
49
+ Target data
50
+ """
51
+ # Validate the input data
52
+ X = validate_data(
53
+ self, X, y="no_validation", ensure_2d=True, reset=True, dtype=np.float64
54
+ )
55
+
56
+ # Preprocess the data
57
+ if self.transformer_:
58
+ X = self.transformer_.transform(X)
59
+
60
+ # Calculate y residuals
61
+ y_residuals = y - self.estimator_.predict(X)
62
+ y_residuals = (
63
+ y_residuals.reshape(-1, 1) if len(y_residuals.shape) == 1 else y_residuals
64
+ )
65
+
66
+ # Calculate the studentized residuals
67
+ studentized_residuals = calculate_studentized_residuals(
68
+ self.estimator_, X, y_residuals
69
+ )
70
+
71
+ # Calculate the critical threshold
72
+ self.critical_value_ = self._calculate_critical_value(studentized_residuals)
73
+
74
+ return self
75
+
76
+ def predict(self, X: np.ndarray, y: Optional[np.ndarray]) -> np.ndarray:
77
+ """Calculate studentized residuals in the model predictions. and return a boolean array indicating outliers.
78
+
79
+ Parameters
80
+ ----------
81
+ X : array-like of shape (n_samples, n_features)
82
+ Input data
83
+
84
+ y : array-like of shape (n_samples,)
85
+ Target data
86
+
87
+ Returns
88
+ -------
89
+ ndarray of shape (n_samples,)
90
+ Studentized residuals of the predictions
91
+ """
92
+ # Check the estimator has been fitted
93
+ check_is_fitted(self, ["critical_value_"])
94
+
95
+ # Validate the input data
96
+ X = validate_data(
97
+ self, X, y="no_validation", ensure_2d=True, reset=True, dtype=np.float64
98
+ )
99
+
100
+ # Preprocess the data
101
+ if self.transformer_:
102
+ X = self.transformer_.transform(X)
103
+
104
+ # Calculate y residuals
105
+ y_residuals = y - self.estimator_.predict(X)
106
+ y_residuals = (
107
+ y_residuals.reshape(-1, 1) if len(y_residuals.shape) == 1 else y_residuals
108
+ )
109
+
110
+ # Calculate the studentized residuals
111
+ studentized_residuals = calculate_studentized_residuals(
112
+ self.estimator_, X, y_residuals
113
+ )
114
+ return np.where(studentized_residuals > self.critical_value_, -1, 1)
115
+
116
+ def predict_residuals(
117
+ self, X: np.ndarray, y: Optional[np.ndarray], validate: bool = True
118
+ ) -> np.ndarray:
119
+ """Calculate the studentized residuals of the model predictions.
120
+
121
+ Parameters
122
+ ----------
123
+ X : array-like of shape (n_samples, n_features)
124
+ Input data
125
+
126
+ y : array-like of shape (n_samples,)
127
+ Target values
128
+
129
+ Returns
130
+ -------
131
+ ndarray of shape (n_samples,)
132
+ Studentized residuals of the model predictions
133
+ """
134
+ # Check the estimator has been fitted
135
+ check_is_fitted(self, ["critical_value_"])
136
+
137
+ # Validate the input data
138
+ if validate:
139
+ X = validate_data(self, X, ensure_2d=True, dtype=np.float64)
140
+
141
+ # Apply preprocessing if available
142
+ if self.transformer_:
143
+ X = self.transformer_.transform(X)
144
+
145
+ # Calculate y residuals
146
+ y_residuals = y - self.estimator_.predict(X)
147
+ y_residuals = (
148
+ y_residuals.reshape(-1, 1) if len(y_residuals.shape) == 1 else y_residuals
149
+ )
150
+
151
+ return calculate_studentized_residuals(self.estimator_, X, y_residuals)
152
+
153
+ def _calculate_critical_value(self, X: np.ndarray) -> float:
154
+ """Calculate the critical value for outlier detection.
155
+
156
+ Parameters
157
+ ----------
158
+ X : array-like of shape (n_samples,)
159
+ Studentized residuals
160
+
161
+ Returns
162
+ -------
163
+ float
164
+ The calculated critical value for outlier detection
165
+ """
166
+
167
+ return np.percentile(X, self.confidence * 100) if X is not None else 0.0
168
+
169
+
170
+ def calculate_studentized_residuals(
171
+ model: ModelTypes, X: np.ndarray, y_residuals: np.ndarray
172
+ ) -> np.ndarray:
173
+ """Calculate the studentized residuals of the model predictions.
174
+
175
+ Parameters
176
+ ----------
177
+ model : ModelTypes
178
+ A fitted model
179
+
180
+ X : array-like of shape (n_samples, n_features)
181
+ Input data
182
+
183
+ y : array-like of shape (n_samples,)
184
+ Target values
185
+
186
+ Returns
187
+ -------
188
+ ndarray of shape (n_samples,)
189
+ Studentized residuals of the model predictions
190
+ """
191
+
192
+ # Calculate the leverage of the samples
193
+ leverage = calculate_leverage(X, model)
194
+
195
+ # Calculate the standard deviation of the residuals
196
+ std = np.sqrt(np.sum(y_residuals**2, axis=0) / (X.shape[0] - model.n_components))
197
+
198
+ return (y_residuals / (std * np.sqrt(1 - leverage.reshape(-1, 1)))).flatten()
@@ -0,0 +1,51 @@
1
+ import numpy as np
2
+
3
+ from ._base import ModelTypes
4
+
5
+
6
+ def calculate_decoded_spectrum(X: np.ndarray, estimator: ModelTypes):
7
+ """
8
+ Calculate the decoded spectrum for a given transformed (preprocessed!!) spectrum and estimator from the latent space.
9
+
10
+ Parameters
11
+ ----------
12
+ spectrum : np.ndarray
13
+ The transformed spectrum data.
14
+
15
+ estimator : ModelTypes
16
+ The fitted PCA or PLS model.
17
+
18
+ Returns
19
+ -------
20
+ np.ndarray
21
+ The decoded spectrum.
22
+ """
23
+ # Project the transformed spectrum onto the latent space
24
+ X_transformed = estimator.transform(X)
25
+
26
+ # Decode the spectrum back to the original space
27
+ return estimator.inverse_transform(X_transformed)
28
+
29
+
30
+ def calculate_residual_spectrum(X: np.ndarray, estimator: ModelTypes):
31
+ """
32
+ Calculate the residual spectrum for a given transformed (preprocessed!!) spectrum and estimator.
33
+
34
+ Parameters
35
+ ----------
36
+ spectrum : np.ndarray
37
+ The transformed spectrum data.
38
+
39
+ estimator : ModelTypes
40
+ The fitted PCA or PLS model.
41
+
42
+ Returns
43
+ -------
44
+ np.ndarray
45
+ The residual spectrum.
46
+ """
47
+ # Compute the reconstruction error (Q residuals)
48
+ decoded_spectrum = calculate_decoded_spectrum(X, estimator)
49
+
50
+ # Calculate the residual
51
+ return X - decoded_spectrum
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: chemotools
3
- Version: 0.1.9
3
+ Version: 0.1.11
4
4
  Summary: chemotools: A Python Package that Integrates Chemometrics and scikit-learn
5
5
  License: MIT
6
6
  Author: Pau Cabaneros
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.13
14
14
  Requires-Dist: numpy (>=2.0.0,<3.0.0)
15
15
  Requires-Dist: pandas (>=2.0.0,<3.0.0)
16
16
  Requires-Dist: polars (>=1.17.0,<2.0.0)
17
- Requires-Dist: pyarrow (>=18.0.0,<19.0.0)
17
+ Requires-Dist: pyarrow (>=18,<21)
18
18
  Requires-Dist: scikit-learn (>=1.4.0,<2.0.0)
19
19
  Description-Content-Type: text/markdown
20
20
 
@@ -1,8 +1,9 @@
1
1
  chemotools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- chemotools/augmentation/__init__.py,sha256=iRltqvskLJAhwxxlTtGPWJfR5XiwvQfqbv4QdUMF9BU,318
2
+ chemotools/augmentation/__init__.py,sha256=ohlRHgRWTkvNpO3RikKtowzboqunQqx0WqtNccuWOHw,397
3
3
  chemotools/augmentation/_add_noise.py,sha256=fkTJfIYtZXezcjy6Vz8asIhpBoVp4oaIifppK9vZpM8,4362
4
4
  chemotools/augmentation/_baseline_shift.py,sha256=kIlYvmKS9pu9vh_-eZ7PSHPuH_58V9mgYbSJt6Gq3BA,3476
5
5
  chemotools/augmentation/_fractional_shift.py,sha256=dJ0Vuc-U02HhjKkOwc48qnOksZYgbHwL2ko7tWCZTQU,6916
6
+ chemotools/augmentation/_gaussian_broadening.py,sha256=dJsPlTKqpecKaCDU3vOvedIb-t_HyCkQprxNv0DmYZQ,4236
6
7
  chemotools/augmentation/_index_shift.py,sha256=BTtadweDvvMtiF8t7ldwsE6Kl6FmKLCkVJjSzSWyIDs,6904
7
8
  chemotools/augmentation/_spectrum_scale.py,sha256=hMsmzXpssbI7tGm_YnQn9wjbByso3CgVxd3Hs8kfLS8,3442
8
9
  chemotools/baseline/__init__.py,sha256=VzoblGg8Hx_FkTc_n7a-ZjGvtKP8JE_NwJKWenGFQkM,584
@@ -26,9 +27,20 @@ chemotools/datasets/data/train_spectra.csv,sha256=iVF19W52NHlbqq8BbLomn8n47kSPT0
26
27
  chemotools/derivative/__init__.py,sha256=FkckdzO30jrRWPGpIU3cfnaTtxPtNT5Tb2G9F9PmVTw,134
27
28
  chemotools/derivative/_norris_william.py,sha256=rMY_yntpiB5fbSM1tPph4AaGmF1k-HqJp7o48ijePBs,4958
28
29
  chemotools/derivative/_savitzky_golay.py,sha256=CuCrKoLmrB1YmJ4ihIykgkL3tO3frqkStMogtsVhO3A,3632
29
- chemotools/feature_selection/__init__.py,sha256=1_i28hIxijjwhMypTy1w2fLbzXXVkKD5IYzzY8ZSuHw,117
30
+ chemotools/feature_selection/__init__.py,sha256=e_GFVawlDNEQv3EqrGSXUr5cvDN1jckoxe2C2jRwVl8,222
31
+ chemotools/feature_selection/_base.py,sha256=SIH6kl9AePVWTByL0OvJFfc2j3idqs7lm_7Zi1YMp4Y,2311
30
32
  chemotools/feature_selection/_index_selector.py,sha256=lNTP2b7P3doWl30KiAr3Xd2HOMxeUmj24MuqoXl4Voc,3556
31
33
  chemotools/feature_selection/_range_cut.py,sha256=lVVVC30ZsK2z9jsDGb_z6l8Ty2I89yM05_dIDbMP73Q,3564
34
+ chemotools/feature_selection/_sr_selector.py,sha256=OaXkt3t_NvymgDy6R15ig87jhcb-vM7i63LgtsNdfZo,3969
35
+ chemotools/feature_selection/_vip_selector.py,sha256=ZK3bhdpl3nBYt6xmuHq2IvWtpgJ8ZdElH06xnCFA-Xs,3835
36
+ chemotools/outliers/__init__.py,sha256=wpdlyqU34n1Pb9kGCM4idhcok35WAakxEhzP0xeKaZw,272
37
+ chemotools/outliers/_base.py,sha256=zl0LhRKjpvj5IbYc3su6zEZ7YZ0pDSR3yqNWt2qBjNA,5374
38
+ chemotools/outliers/dmodx.py,sha256=sgizal_BDlqWTZNT8y2D_ImcKAJejXt6vqvFYk4Vqi0,5152
39
+ chemotools/outliers/hotelling_t2.py,sha256=g_IOQD_rhKb3cjIJkn5OTto6bYClQtqXunG_02BSIs8,5087
40
+ chemotools/outliers/leverage.py,sha256=hNQ_x68LPPTDZvSJP_eRqu3GoeV3OBU37VC_XTFEzvw,4250
41
+ chemotools/outliers/q_residuals.py,sha256=sg7u8ockQvSSnXwNM4U-GITB-5OcbsDMX6Oig_TcONM,7598
42
+ chemotools/outliers/studentized_residuals.py,sha256=1L-GiutuO1x9s3UKMOBpmhs2Q-UuDtfG2YLELIxiiao,5890
43
+ chemotools/outliers/utils.py,sha256=SAjvtjl9oWHrQnkqGnDfYE4WWAgiL1RwnKmW-ql5TIc,1304
32
44
  chemotools/scale/__init__.py,sha256=eztqcHg-TKE1Rr0N9ArfytHk8teuqVfi4SZi2DS96vc,175
33
45
  chemotools/scale/_min_max_scaler.py,sha256=YvqRkV2pXu-viQrpjzWcp9KmSSCYSoubSnrZHRLqgKQ,3011
34
46
  chemotools/scale/_norm_scaler.py,sha256=CHWSir2q-pL1hxzw_ZB45yi4mw-SkJ4YOa1CUL4nm2I,2568
@@ -44,7 +56,7 @@ chemotools/smooth/_median_filter.py,sha256=9ndTJCwrZirWlvDNldiigMddy79KIGq9OwwYN
44
56
  chemotools/smooth/_savitzky_golay_filter.py,sha256=27iFUWxdL9_7oZabR0R5L0ZTpBmYfVUjx2XCTukihBE,3509
45
57
  chemotools/smooth/_whittaker_smooth.py,sha256=lpLAyf4GdyDW4ulT1nyEoK6xQEl2cVUKquawQdGWbHU,3571
46
58
  chemotools/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
- chemotools-0.1.9.dist-info/LICENSE,sha256=qtyOy2wDQVX9hxp58h3T-6Lmfv-mSCHoSRkcLUdM9bg,1070
48
- chemotools-0.1.9.dist-info/METADATA,sha256=9sih25qSOTJX36gib96hxm-e86TorD040r_WNFzbc9U,5239
49
- chemotools-0.1.9.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
50
- chemotools-0.1.9.dist-info/RECORD,,
59
+ chemotools-0.1.11.dist-info/LICENSE,sha256=qtyOy2wDQVX9hxp58h3T-6Lmfv-mSCHoSRkcLUdM9bg,1070
60
+ chemotools-0.1.11.dist-info/METADATA,sha256=Ne8xEa1cZUhbP-I4D1CFVvy8fhJANUjsY5cXRpNVV1k,5232
61
+ chemotools-0.1.11.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
62
+ chemotools-0.1.11.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.0.1
2
+ Generator: poetry-core 2.1.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any