aplr 10.5.1__cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl → 10.9.0__cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.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.
Potentially problematic release.
This version of aplr might be problematic. Click here for more details.
- aplr/aplr.py +88 -13
- aplr-10.9.0.dist-info/METADATA +59 -0
- aplr-10.9.0.dist-info/RECORD +8 -0
- {aplr-10.5.1.dist-info → aplr-10.9.0.dist-info}/WHEEL +1 -1
- aplr_cpp.cpython-39-i386-linux-gnu.so +0 -0
- aplr-10.5.1.dist-info/METADATA +0 -37
- aplr-10.5.1.dist-info/RECORD +0 -8
- {aplr-10.5.1.dist-info → aplr-10.9.0.dist-info/licenses}/LICENSE +0 -0
- {aplr-10.5.1.dist-info → aplr-10.9.0.dist-info}/top_level.txt +0 -0
aplr/aplr.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
from typing import List, Callable, Optional, Dict
|
|
1
|
+
from typing import List, Callable, Optional, Dict, Union
|
|
2
2
|
import numpy as np
|
|
3
3
|
import aplr_cpp
|
|
4
|
+
import itertools
|
|
4
5
|
|
|
5
6
|
FloatVector = np.ndarray
|
|
6
7
|
FloatMatrix = np.ndarray
|
|
@@ -11,8 +12,8 @@ IntMatrix = np.ndarray
|
|
|
11
12
|
class APLRRegressor:
|
|
12
13
|
def __init__(
|
|
13
14
|
self,
|
|
14
|
-
m: int =
|
|
15
|
-
v: float = 0.
|
|
15
|
+
m: int = 3000,
|
|
16
|
+
v: float = 0.5,
|
|
16
17
|
random_state: int = 0,
|
|
17
18
|
loss_function: str = "mse",
|
|
18
19
|
link_function: str = "identity",
|
|
@@ -21,9 +22,9 @@ class APLRRegressor:
|
|
|
21
22
|
bins: int = 300,
|
|
22
23
|
max_interaction_level: int = 1,
|
|
23
24
|
max_interactions: int = 100000,
|
|
24
|
-
min_observations_in_split: int =
|
|
25
|
-
ineligible_boosting_steps_added: int =
|
|
26
|
-
max_eligible_terms: int =
|
|
25
|
+
min_observations_in_split: int = 4,
|
|
26
|
+
ineligible_boosting_steps_added: int = 15,
|
|
27
|
+
max_eligible_terms: int = 7,
|
|
27
28
|
verbosity: int = 0,
|
|
28
29
|
dispersion_parameter: float = 1.5,
|
|
29
30
|
validation_tuning_metric: str = "default",
|
|
@@ -68,11 +69,12 @@ class APLRRegressor:
|
|
|
68
69
|
monotonic_constraints_ignore_interactions: bool = False,
|
|
69
70
|
group_mse_by_prediction_bins: int = 10,
|
|
70
71
|
group_mse_cycle_min_obs_in_bin: int = 30,
|
|
71
|
-
early_stopping_rounds: int =
|
|
72
|
+
early_stopping_rounds: int = 200,
|
|
72
73
|
num_first_steps_with_linear_effects_only: int = 0,
|
|
73
74
|
penalty_for_non_linearity: float = 0.0,
|
|
74
75
|
penalty_for_interactions: float = 0.0,
|
|
75
76
|
max_terms: int = 0,
|
|
77
|
+
ridge_penalty: float = 0.0001,
|
|
76
78
|
):
|
|
77
79
|
self.m = m
|
|
78
80
|
self.v = v
|
|
@@ -119,6 +121,7 @@ class APLRRegressor:
|
|
|
119
121
|
self.penalty_for_non_linearity = penalty_for_non_linearity
|
|
120
122
|
self.penalty_for_interactions = penalty_for_interactions
|
|
121
123
|
self.max_terms = max_terms
|
|
124
|
+
self.ridge_penalty = ridge_penalty
|
|
122
125
|
|
|
123
126
|
# Creating aplr_cpp and setting parameters
|
|
124
127
|
self.APLRRegressor = aplr_cpp.APLRRegressor()
|
|
@@ -179,6 +182,7 @@ class APLRRegressor:
|
|
|
179
182
|
self.APLRRegressor.penalty_for_non_linearity = self.penalty_for_non_linearity
|
|
180
183
|
self.APLRRegressor.penalty_for_interactions = self.penalty_for_interactions
|
|
181
184
|
self.APLRRegressor.max_terms = self.max_terms
|
|
185
|
+
self.APLRRegressor.ridge_penalty = self.ridge_penalty
|
|
182
186
|
|
|
183
187
|
def fit(
|
|
184
188
|
self,
|
|
@@ -195,6 +199,7 @@ class APLRRegressor:
|
|
|
195
199
|
predictor_learning_rates: List[float] = [],
|
|
196
200
|
predictor_penalties_for_non_linearity: List[float] = [],
|
|
197
201
|
predictor_penalties_for_interactions: List[float] = [],
|
|
202
|
+
predictor_min_observations_in_split: List[int] = [],
|
|
198
203
|
):
|
|
199
204
|
self.__set_params_cpp()
|
|
200
205
|
self.APLRRegressor.fit(
|
|
@@ -211,6 +216,7 @@ class APLRRegressor:
|
|
|
211
216
|
predictor_learning_rates,
|
|
212
217
|
predictor_penalties_for_non_linearity,
|
|
213
218
|
predictor_penalties_for_interactions,
|
|
219
|
+
predictor_min_observations_in_split,
|
|
214
220
|
)
|
|
215
221
|
|
|
216
222
|
def predict(
|
|
@@ -303,6 +309,9 @@ class APLRRegressor:
|
|
|
303
309
|
def get_cv_error(self) -> float:
|
|
304
310
|
return self.APLRRegressor.get_cv_error()
|
|
305
311
|
|
|
312
|
+
def set_intercept(self, value: float):
|
|
313
|
+
self.APLRRegressor.set_intercept(value)
|
|
314
|
+
|
|
306
315
|
# For sklearn
|
|
307
316
|
def get_params(self, deep=True):
|
|
308
317
|
return {
|
|
@@ -337,6 +346,7 @@ class APLRRegressor:
|
|
|
337
346
|
"penalty_for_non_linearity": self.penalty_for_non_linearity,
|
|
338
347
|
"penalty_for_interactions": self.penalty_for_interactions,
|
|
339
348
|
"max_terms": self.max_terms,
|
|
349
|
+
"ridge_penalty": self.ridge_penalty,
|
|
340
350
|
}
|
|
341
351
|
|
|
342
352
|
# For sklearn
|
|
@@ -350,8 +360,8 @@ class APLRRegressor:
|
|
|
350
360
|
class APLRClassifier:
|
|
351
361
|
def __init__(
|
|
352
362
|
self,
|
|
353
|
-
m: int =
|
|
354
|
-
v: float = 0.
|
|
363
|
+
m: int = 3000,
|
|
364
|
+
v: float = 0.5,
|
|
355
365
|
random_state: int = 0,
|
|
356
366
|
n_jobs: int = 0,
|
|
357
367
|
cv_folds: int = 5,
|
|
@@ -359,16 +369,17 @@ class APLRClassifier:
|
|
|
359
369
|
verbosity: int = 0,
|
|
360
370
|
max_interaction_level: int = 1,
|
|
361
371
|
max_interactions: int = 100000,
|
|
362
|
-
min_observations_in_split: int =
|
|
363
|
-
ineligible_boosting_steps_added: int =
|
|
364
|
-
max_eligible_terms: int =
|
|
372
|
+
min_observations_in_split: int = 4,
|
|
373
|
+
ineligible_boosting_steps_added: int = 15,
|
|
374
|
+
max_eligible_terms: int = 7,
|
|
365
375
|
boosting_steps_before_interactions_are_allowed: int = 0,
|
|
366
376
|
monotonic_constraints_ignore_interactions: bool = False,
|
|
367
|
-
early_stopping_rounds: int =
|
|
377
|
+
early_stopping_rounds: int = 200,
|
|
368
378
|
num_first_steps_with_linear_effects_only: int = 0,
|
|
369
379
|
penalty_for_non_linearity: float = 0.0,
|
|
370
380
|
penalty_for_interactions: float = 0.0,
|
|
371
381
|
max_terms: int = 0,
|
|
382
|
+
ridge_penalty: float = 0.0001,
|
|
372
383
|
):
|
|
373
384
|
self.m = m
|
|
374
385
|
self.v = v
|
|
@@ -395,6 +406,7 @@ class APLRClassifier:
|
|
|
395
406
|
self.penalty_for_non_linearity = penalty_for_non_linearity
|
|
396
407
|
self.penalty_for_interactions = penalty_for_interactions
|
|
397
408
|
self.max_terms = max_terms
|
|
409
|
+
self.ridge_penalty = ridge_penalty
|
|
398
410
|
|
|
399
411
|
# Creating aplr_cpp and setting parameters
|
|
400
412
|
self.APLRClassifier = aplr_cpp.APLRClassifier()
|
|
@@ -429,6 +441,7 @@ class APLRClassifier:
|
|
|
429
441
|
self.APLRClassifier.penalty_for_non_linearity = self.penalty_for_non_linearity
|
|
430
442
|
self.APLRClassifier.penalty_for_interactions = self.penalty_for_interactions
|
|
431
443
|
self.APLRClassifier.max_terms = self.max_terms
|
|
444
|
+
self.APLRClassifier.ridge_penalty = self.ridge_penalty
|
|
432
445
|
|
|
433
446
|
def fit(
|
|
434
447
|
self,
|
|
@@ -443,6 +456,7 @@ class APLRClassifier:
|
|
|
443
456
|
predictor_learning_rates: List[float] = [],
|
|
444
457
|
predictor_penalties_for_non_linearity: List[float] = [],
|
|
445
458
|
predictor_penalties_for_interactions: List[float] = [],
|
|
459
|
+
predictor_min_observations_in_split: List[int] = [],
|
|
446
460
|
):
|
|
447
461
|
self.__set_params_cpp()
|
|
448
462
|
self.APLRClassifier.fit(
|
|
@@ -457,6 +471,7 @@ class APLRClassifier:
|
|
|
457
471
|
predictor_learning_rates,
|
|
458
472
|
predictor_penalties_for_non_linearity,
|
|
459
473
|
predictor_penalties_for_interactions,
|
|
474
|
+
predictor_min_observations_in_split,
|
|
460
475
|
)
|
|
461
476
|
# For sklearn
|
|
462
477
|
self.classes_ = np.arange(len(self.APLRClassifier.get_categories()))
|
|
@@ -519,6 +534,7 @@ class APLRClassifier:
|
|
|
519
534
|
"penalty_for_non_linearity": self.penalty_for_non_linearity,
|
|
520
535
|
"penalty_for_interactions": self.penalty_for_interactions,
|
|
521
536
|
"max_terms": self.max_terms,
|
|
537
|
+
"ridge_penalty": self.ridge_penalty,
|
|
522
538
|
}
|
|
523
539
|
|
|
524
540
|
# For sklearn
|
|
@@ -531,3 +547,62 @@ class APLRClassifier:
|
|
|
531
547
|
# For sklearn
|
|
532
548
|
def predict_proba(self, X: FloatMatrix) -> FloatMatrix:
|
|
533
549
|
return self.predict_class_probabilities(X)
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
class APLRTuner:
|
|
553
|
+
def __init__(
|
|
554
|
+
self,
|
|
555
|
+
parameters: Union[Dict[str, List[float]], List[Dict[str, List[float]]]] = {
|
|
556
|
+
"max_interaction_level": [0, 1],
|
|
557
|
+
"min_observations_in_split": [4, 10, 20, 100, 500, 1000],
|
|
558
|
+
},
|
|
559
|
+
is_regressor: bool = True,
|
|
560
|
+
):
|
|
561
|
+
self.parameters = parameters
|
|
562
|
+
self.is_regressor = is_regressor
|
|
563
|
+
self.parameter_grid = self._create_parameter_grid()
|
|
564
|
+
|
|
565
|
+
def _create_parameter_grid(self) -> List[Dict[str, float]]:
|
|
566
|
+
items = sorted(self.parameters.items())
|
|
567
|
+
keys, values = zip(*items)
|
|
568
|
+
combinations = list(itertools.product(*values))
|
|
569
|
+
grid = [dict(zip(keys, combination)) for combination in combinations]
|
|
570
|
+
return grid
|
|
571
|
+
|
|
572
|
+
def fit(self, X: FloatMatrix, y: FloatVector, **kwargs):
|
|
573
|
+
self.cv_results: List[Dict[str, float]] = []
|
|
574
|
+
best_validation_result = np.inf
|
|
575
|
+
for params in self.parameter_grid:
|
|
576
|
+
if self.is_regressor:
|
|
577
|
+
model = APLRRegressor(**params)
|
|
578
|
+
else:
|
|
579
|
+
model = APLRClassifier(**params)
|
|
580
|
+
model.fit(X, y, **kwargs)
|
|
581
|
+
cv_error_for_this_model = model.get_cv_error()
|
|
582
|
+
cv_results_for_this_model = model.get_params()
|
|
583
|
+
cv_results_for_this_model["cv_error"] = cv_error_for_this_model
|
|
584
|
+
self.cv_results.append(cv_results_for_this_model)
|
|
585
|
+
if cv_error_for_this_model < best_validation_result:
|
|
586
|
+
best_validation_result = cv_error_for_this_model
|
|
587
|
+
self.best_model = model
|
|
588
|
+
self.cv_results = sorted(self.cv_results, key=lambda x: x["cv_error"])
|
|
589
|
+
|
|
590
|
+
def predict(self, X: FloatMatrix, **kwargs) -> Union[FloatVector, List[str]]:
|
|
591
|
+
return self.best_model.predict(X, **kwargs)
|
|
592
|
+
|
|
593
|
+
def predict_class_probabilities(self, X: FloatMatrix, **kwargs) -> FloatMatrix:
|
|
594
|
+
if self.is_regressor == False:
|
|
595
|
+
return self.best_model.predict_class_probabilities(X, **kwargs)
|
|
596
|
+
else:
|
|
597
|
+
raise TypeError(
|
|
598
|
+
"predict_class_probabilities is only possible when is_regressor is False"
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
def predict_proba(self, X: FloatMatrix, **kwargs) -> FloatMatrix:
|
|
602
|
+
return self.predict_class_probabilities(X, **kwargs)
|
|
603
|
+
|
|
604
|
+
def get_best_estimator(self) -> Union[APLRClassifier, APLRRegressor]:
|
|
605
|
+
return self.best_model
|
|
606
|
+
|
|
607
|
+
def get_cv_results(self) -> List[Dict[str, float]]:
|
|
608
|
+
return self.cv_results
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aplr
|
|
3
|
+
Version: 10.9.0
|
|
4
|
+
Summary: Automatic Piecewise Linear Regression
|
|
5
|
+
Home-page: https://github.com/ottenbreit-data-science/aplr
|
|
6
|
+
Author: Mathias von Ottenbreit
|
|
7
|
+
Author-email: ottenbreitdatascience@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Platform: Windows
|
|
10
|
+
Platform: Linux
|
|
11
|
+
Platform: MacOS
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: numpy>=1.11
|
|
17
|
+
Dynamic: author
|
|
18
|
+
Dynamic: author-email
|
|
19
|
+
Dynamic: classifier
|
|
20
|
+
Dynamic: description
|
|
21
|
+
Dynamic: description-content-type
|
|
22
|
+
Dynamic: home-page
|
|
23
|
+
Dynamic: license
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
Dynamic: platform
|
|
26
|
+
Dynamic: requires-dist
|
|
27
|
+
Dynamic: requires-python
|
|
28
|
+
Dynamic: summary
|
|
29
|
+
|
|
30
|
+
# APLR
|
|
31
|
+
**Automatic Piecewise Linear Regression**
|
|
32
|
+
|
|
33
|
+
## About
|
|
34
|
+
APLR allows you to build predictive and interpretable regression or classification machine learning models in Python, using the Automatic Piecewise Linear Regression (APLR) methodology developed by Mathias von Ottenbreit. APLR often rivals tree-based methods in predictive accuracy, while offering smoother, more interpretable predictions.
|
|
35
|
+
|
|
36
|
+
For further details, see the [documentation](https://github.com/ottenbreit-data-science/aplr/tree/main/documentation). You may also read the published article for additional insights: [Link 1](https://link.springer.com/article/10.1007/s00180-024-01475-4) and [Link 2](https://rdcu.be/dz7bF). Additional functionality has been added since the article was published.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
To install APLR, use the following command:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install aplr
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Availability
|
|
46
|
+
APLR is available for Windows, most Linux distributions, and macOS.
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
Example Python scripts are available [here](https://github.com/ottenbreit-data-science/aplr/tree/main/examples).
|
|
50
|
+
|
|
51
|
+
## Sponsorship
|
|
52
|
+
Consider sponsoring Von Ottenbreit Data Science by clicking the **Sponsor** button on the repository. Sufficient funding will help maintain and further develop APLR.
|
|
53
|
+
|
|
54
|
+
## API Reference
|
|
55
|
+
- [API reference for regression](https://github.com/ottenbreit-data-science/aplr/blob/main/API_REFERENCE_FOR_REGRESSION.md)
|
|
56
|
+
- [API reference for classification](https://github.com/ottenbreit-data-science/aplr/blob/main/API_REFERENCE_FOR_CLASSIFICATION.md)
|
|
57
|
+
|
|
58
|
+
## Contact Information
|
|
59
|
+
For inquiries, please email: [ottenbreitdatascience@gmail.com](mailto:ottenbreitdatascience@gmail.com)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
aplr_cpp.cpython-39-i386-linux-gnu.so,sha256=XUaNlnx6jp0jzPSXwbs5AhhJQfovewNmiKczVIo2mIc,30587344
|
|
2
|
+
aplr-10.9.0.dist-info/WHEEL,sha256=ev2wLZhGqjCOA38lQufE4AkmIWNb6CT3SFRJyelQWNw,143
|
|
3
|
+
aplr-10.9.0.dist-info/RECORD,,
|
|
4
|
+
aplr-10.9.0.dist-info/top_level.txt,sha256=DXVC0RIFGpzVnPeKWAZTXQdJheOEZL51Wip6Fx7zbR4,14
|
|
5
|
+
aplr-10.9.0.dist-info/METADATA,sha256=YyVcDnXStzsAnm5KWfxaSYgd70qn6IqhqaoLpZI-3IM,2361
|
|
6
|
+
aplr-10.9.0.dist-info/licenses/LICENSE,sha256=g4qcQtkSVPHtGRi3T93DoFCrssvW6ij_emU-2fj_xfY,1113
|
|
7
|
+
aplr/__init__.py,sha256=rRfTgNWnYZlFatyA920lWqBcjwmQUI7FcvEPFUTJgzE,20
|
|
8
|
+
aplr/aplr.py,sha256=18XnQy37U3AApCWESlfKysuHPsl9_LiF2kyubroFr_Q,26718
|
|
Binary file
|
aplr-10.5.1.dist-info/METADATA
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: aplr
|
|
3
|
-
Version: 10.5.1
|
|
4
|
-
Summary: Automatic Piecewise Linear Regression
|
|
5
|
-
Home-page: https://github.com/ottenbreit-data-science/aplr
|
|
6
|
-
Author: Mathias von Ottenbreit
|
|
7
|
-
Author-email: ottenbreitdatascience@gmail.com
|
|
8
|
-
License: MIT
|
|
9
|
-
Platform: Windows
|
|
10
|
-
Platform: Linux
|
|
11
|
-
Platform: MacOS
|
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
-
Requires-Python: >=3.8
|
|
14
|
-
Description-Content-Type: text/markdown
|
|
15
|
-
License-File: LICENSE
|
|
16
|
-
Requires-Dist: numpy >=1.11
|
|
17
|
-
|
|
18
|
-
# APLR
|
|
19
|
-
Automatic Piecewise Linear Regression.
|
|
20
|
-
|
|
21
|
-
# About
|
|
22
|
-
Build predictive and interpretable parametric regression or classification machine learning models in Python based on the Automatic Piecewise Linear Regression (APLR) methodology developed by Mathias von Ottenbreit. APLR is often able to compete with tree-based methods on predictiveness, but unlike tree-based methods APLR is interpretable. Please see the [documentation](https://github.com/ottenbreit-data-science/aplr/tree/main/documentation) for more information. Links to published article: [https://link.springer.com/article/10.1007/s00180-024-01475-4](https://link.springer.com/article/10.1007/s00180-024-01475-4) and [https://rdcu.be/dz7bF](https://rdcu.be/dz7bF). More functionality has been added to APLR since the article was published.
|
|
23
|
-
|
|
24
|
-
# How to install
|
|
25
|
-
***pip install aplr***
|
|
26
|
-
|
|
27
|
-
# Availability
|
|
28
|
-
Available for Windows, most Linux distributions and MacOS.
|
|
29
|
-
|
|
30
|
-
# How to use
|
|
31
|
-
Please see the two example Python scripts [here](https://github.com/ottenbreit-data-science/aplr/tree/main/examples). They cover common use cases, but not all of the functionality in this package.
|
|
32
|
-
|
|
33
|
-
# Sponsorship
|
|
34
|
-
Please consider sponsoring Ottenbreit Data Science by clicking on the Sponsor button. Sufficient funding will enable maintenance of APLR and further development.
|
|
35
|
-
|
|
36
|
-
# API reference
|
|
37
|
-
Please see the [API reference for regression](https://github.com/ottenbreit-data-science/aplr/blob/main/API_REFERENCE_FOR_REGRESSION.md) and [API reference for classification](https://github.com/ottenbreit-data-science/aplr/blob/main/API_REFERENCE_FOR_CLASSIFICATION.md).
|
aplr-10.5.1.dist-info/RECORD
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
aplr_cpp.cpython-39-i386-linux-gnu.so,sha256=EUEsC0GOgGxWzN6pHPhRw4C_5oq7vh_7WuEZsJtzcsg,30267700
|
|
2
|
-
aplr/aplr.py,sha256=SHeyJWIPM_2GkD7cP-d-kPaPyki__7_wXe6SV1cYDSQ,23483
|
|
3
|
-
aplr/__init__.py,sha256=rRfTgNWnYZlFatyA920lWqBcjwmQUI7FcvEPFUTJgzE,20
|
|
4
|
-
aplr-10.5.1.dist-info/LICENSE,sha256=g4qcQtkSVPHtGRi3T93DoFCrssvW6ij_emU-2fj_xfY,1113
|
|
5
|
-
aplr-10.5.1.dist-info/WHEEL,sha256=ZHzm9oHdYvMy7ktFtELwJx317aaqMTiLr3TEniwVHtg,144
|
|
6
|
-
aplr-10.5.1.dist-info/top_level.txt,sha256=DXVC0RIFGpzVnPeKWAZTXQdJheOEZL51Wip6Fx7zbR4,14
|
|
7
|
-
aplr-10.5.1.dist-info/RECORD,,
|
|
8
|
-
aplr-10.5.1.dist-info/METADATA,sha256=aMJyYYnUEq1dSMTRtdsqS78C5AjjJisI2_sSYM99NCs,2056
|
|
File without changes
|
|
File without changes
|