dragon-ml-toolbox 12.10.0__tar.gz → 12.12.0__tar.gz
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 dragon-ml-toolbox might be problematic. Click here for more details.
- {dragon_ml_toolbox-12.10.0/dragon_ml_toolbox.egg-info → dragon_ml_toolbox-12.12.0}/PKG-INFO +1 -1
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0/dragon_ml_toolbox.egg-info}/PKG-INFO +1 -1
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/ML_callbacks.py +33 -32
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/ML_datasetmaster.py +21 -5
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/ML_models.py +7 -7
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/custom_logger.py +1 -1
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/serde.py +5 -13
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/pyproject.toml +1 -1
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/LICENSE +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/LICENSE-THIRD-PARTY.md +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/README.md +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/dragon_ml_toolbox.egg-info/SOURCES.txt +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/dragon_ml_toolbox.egg-info/dependency_links.txt +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/dragon_ml_toolbox.egg-info/requires.txt +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/dragon_ml_toolbox.egg-info/top_level.txt +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/ETL_cleaning.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/ETL_engineering.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/GUI_tools.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/MICE_imputation.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/ML_evaluation.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/ML_evaluation_multi.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/ML_inference.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/ML_optimization.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/ML_scaler.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/ML_simple_optimization.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/ML_trainer.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/ML_utilities.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/PSO_optimization.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/RNN_forecast.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/SQL.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/VIF_factor.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/__init__.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/_logger.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/_script_info.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/constants.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/data_exploration.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/ensemble_evaluation.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/ensemble_inference.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/ensemble_learning.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/handle_excel.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/keys.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/math_utilities.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/optimization_tools.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/path_manager.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/ml_tools/utilities.py +0 -0
- {dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/setup.cfg +0 -0
|
@@ -113,18 +113,19 @@ class TqdmProgressBar(Callback):
|
|
|
113
113
|
class EarlyStopping(Callback):
|
|
114
114
|
"""
|
|
115
115
|
Stop training when a monitored metric has stopped improving.
|
|
116
|
-
|
|
117
|
-
Args:
|
|
118
|
-
monitor (str): Quantity to be monitored. Defaults to 'val_loss'.
|
|
119
|
-
min_delta (float): Minimum change in the monitored quantity to qualify as an improvement.
|
|
120
|
-
patience (int): Number of epochs with no improvement after which training will be stopped.
|
|
121
|
-
mode (str): One of {'auto', 'min', 'max'}. In 'min' mode, training will stop when the quantity
|
|
122
|
-
monitored has stopped decreasing; in 'max' mode it will stop when the quantity
|
|
123
|
-
monitored has stopped increasing; in 'auto' mode, the direction is automatically
|
|
124
|
-
inferred from the name of the monitored quantity.
|
|
125
|
-
verbose (int): Verbosity mode.
|
|
126
116
|
"""
|
|
127
117
|
def __init__(self, monitor: str=PyTorchLogKeys.VAL_LOSS, min_delta: float=0.0, patience: int=5, mode: Literal['auto', 'min', 'max']='auto', verbose: int=1):
|
|
118
|
+
"""
|
|
119
|
+
Args:
|
|
120
|
+
monitor (str): Quantity to be monitored. Defaults to 'val_loss'.
|
|
121
|
+
min_delta (float): Minimum change in the monitored quantity to qualify as an improvement.
|
|
122
|
+
patience (int): Number of epochs with no improvement after which training will be stopped.
|
|
123
|
+
mode (str): One of {'auto', 'min', 'max'}. In 'min' mode, training will stop when the quantity
|
|
124
|
+
monitored has stopped decreasing; in 'max' mode it will stop when the quantity
|
|
125
|
+
monitored has stopped increasing; in 'auto' mode, the direction is automatically
|
|
126
|
+
inferred from the name of the monitored quantity.
|
|
127
|
+
verbose (int): Verbosity mode.
|
|
128
|
+
"""
|
|
128
129
|
super().__init__()
|
|
129
130
|
self.monitor = monitor
|
|
130
131
|
self.patience = patience
|
|
@@ -188,22 +189,23 @@ class EarlyStopping(Callback):
|
|
|
188
189
|
|
|
189
190
|
class ModelCheckpoint(Callback):
|
|
190
191
|
"""
|
|
191
|
-
Saves the model to a directory with automated filename generation and rotation.
|
|
192
|
-
|
|
193
|
-
- If `save_best_only` is True, it saves the single best model, deleting the
|
|
194
|
-
previous best.
|
|
195
|
-
- If `save_best_only` is False, it keeps the 3 most recent checkpoints,
|
|
196
|
-
deleting the oldest ones automatically.
|
|
197
|
-
|
|
198
|
-
Args:
|
|
199
|
-
save_dir (str): Directory where checkpoint files will be saved.
|
|
200
|
-
monitor (str): Metric to monitor for `save_best_only=True`.
|
|
201
|
-
save_best_only (bool): If true, save only the best model.
|
|
202
|
-
mode (str): One of {'auto', 'min', 'max'}.
|
|
203
|
-
verbose (int): Verbosity mode.
|
|
192
|
+
Saves the model weights to a directory with automated filename generation and rotation.
|
|
204
193
|
"""
|
|
205
194
|
def __init__(self, save_dir: Union[str,Path], checkpoint_name: Optional[str]=None, monitor: str = PyTorchLogKeys.VAL_LOSS,
|
|
206
195
|
save_best_only: bool = True, mode: Literal['auto', 'min', 'max']= 'auto', verbose: int = 0):
|
|
196
|
+
"""
|
|
197
|
+
- If `save_best_only` is True, it saves the single best model, deleting the previous best.
|
|
198
|
+
- If `save_best_only` is False, it keeps the 3 most recent checkpoints, deleting the oldest ones automatically.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
save_dir (str): Directory where checkpoint files will be saved.
|
|
202
|
+
checkpoint_name (str| None): If None, the filename will include the epoch and score.
|
|
203
|
+
monitor (str): Metric to monitor for `save_best_only=True`.
|
|
204
|
+
save_best_only (bool): If true, save only the best model.
|
|
205
|
+
mode (str): One of {'auto', 'min', 'max'}.
|
|
206
|
+
verbose (int): Verbosity mode.
|
|
207
|
+
"""
|
|
208
|
+
|
|
207
209
|
super().__init__()
|
|
208
210
|
self.save_dir = make_fullpath(save_dir, make=True, enforce="directory")
|
|
209
211
|
if not self.save_dir.is_dir():
|
|
@@ -306,17 +308,16 @@ class ModelCheckpoint(Callback):
|
|
|
306
308
|
class LRScheduler(Callback):
|
|
307
309
|
"""
|
|
308
310
|
Callback to manage a PyTorch learning rate scheduler.
|
|
309
|
-
|
|
310
|
-
This callback automatically calls the scheduler's `step()` method at the
|
|
311
|
-
end of each epoch. It also logs a message when the learning rate changes.
|
|
312
|
-
|
|
313
|
-
Args:
|
|
314
|
-
scheduler: An initialized PyTorch learning rate scheduler.
|
|
315
|
-
monitor (str, optional): The metric to monitor for schedulers that
|
|
316
|
-
require it, like `ReduceLROnPlateau`.
|
|
317
|
-
Should match a key in the logs (e.g., 'val_loss').
|
|
318
311
|
"""
|
|
319
312
|
def __init__(self, scheduler, monitor: Optional[str] = None):
|
|
313
|
+
"""
|
|
314
|
+
This callback automatically calls the scheduler's `step()` method at the
|
|
315
|
+
end of each epoch. It also logs a message when the learning rate changes.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
scheduler: An initialized PyTorch learning rate scheduler.
|
|
319
|
+
monitor (str, optional): The metric to monitor for schedulers that require it, like `ReduceLROnPlateau`. Should match a key in the logs (e.g., 'val_loss').
|
|
320
|
+
"""
|
|
320
321
|
super().__init__()
|
|
321
322
|
self.scheduler = scheduler
|
|
322
323
|
self.monitor = monitor
|
|
@@ -81,8 +81,7 @@ class _PytorchDataset(Dataset):
|
|
|
81
81
|
_LOGGER.error(f"Dataset {self.__class__} has not been initialized with any target names.")
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
# --- Abstract Base Class
|
|
85
|
-
# --- Abstract Base Class (Corrected) ---
|
|
84
|
+
# --- Abstract Base Class ---
|
|
86
85
|
class _BaseDatasetMaker(ABC):
|
|
87
86
|
"""
|
|
88
87
|
Abstract base class for dataset makers. Contains shared logic for
|
|
@@ -150,6 +149,14 @@ class _BaseDatasetMaker(ABC):
|
|
|
150
149
|
@property
|
|
151
150
|
def target_names(self) -> list[str]:
|
|
152
151
|
return self._target_names
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def number_of_features(self) -> int:
|
|
155
|
+
return len(self._feature_names)
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def number_of_targets(self) -> int:
|
|
159
|
+
return len(self._target_names)
|
|
153
160
|
|
|
154
161
|
@property
|
|
155
162
|
def id(self) -> Optional[str]:
|
|
@@ -180,14 +187,14 @@ class _BaseDatasetMaker(ABC):
|
|
|
180
187
|
filename=DatasetKeys.TARGET_NAMES,
|
|
181
188
|
verbose=verbose)
|
|
182
189
|
|
|
183
|
-
def save_scaler(self,
|
|
190
|
+
def save_scaler(self, directory: Union[str, Path], verbose: bool=True) -> None:
|
|
184
191
|
"""
|
|
185
192
|
Saves the fitted PytorchScaler's state to a .pth file.
|
|
186
193
|
|
|
187
194
|
The filename is automatically generated based on the dataset id.
|
|
188
195
|
|
|
189
196
|
Args:
|
|
190
|
-
|
|
197
|
+
directory (str | Path): The directory where the scaler will be saved.
|
|
191
198
|
"""
|
|
192
199
|
if not self.scaler:
|
|
193
200
|
_LOGGER.error("No scaler was fitted or provided.")
|
|
@@ -195,7 +202,7 @@ class _BaseDatasetMaker(ABC):
|
|
|
195
202
|
if not self.id:
|
|
196
203
|
_LOGGER.error("Must set the dataset `id` before saving scaler.")
|
|
197
204
|
raise ValueError()
|
|
198
|
-
save_path = make_fullpath(
|
|
205
|
+
save_path = make_fullpath(directory, make=True, enforce="directory")
|
|
199
206
|
sanitized_id = sanitize_filename(self.id)
|
|
200
207
|
filename = f"{DatasetKeys.SCALER_PREFIX}{sanitized_id}.pth"
|
|
201
208
|
filepath = save_path / filename
|
|
@@ -203,6 +210,15 @@ class _BaseDatasetMaker(ABC):
|
|
|
203
210
|
if verbose:
|
|
204
211
|
_LOGGER.info(f"Scaler for dataset '{self.id}' saved as '{filepath.name}'.")
|
|
205
212
|
|
|
213
|
+
def save_artifacts(self, directory: Union[str, Path], verbose: bool=True) -> None:
|
|
214
|
+
"""
|
|
215
|
+
Convenience method to save feature names, target names, and the scaler (if a scaler was fitted)
|
|
216
|
+
"""
|
|
217
|
+
self.save_feature_names(directory=directory, verbose=verbose)
|
|
218
|
+
self.save_target_names(directory=directory, verbose=verbose)
|
|
219
|
+
if self.scaler is not None:
|
|
220
|
+
self.save_scaler(directory=directory, verbose=verbose)
|
|
221
|
+
|
|
206
222
|
|
|
207
223
|
# Single target dataset
|
|
208
224
|
class DatasetMaker(_BaseDatasetMaker):
|
|
@@ -304,7 +304,7 @@ class TabularTransformer(nn.Module, _ArchitectureHandlerMixin):
|
|
|
304
304
|
def __init__(self, *,
|
|
305
305
|
in_features: int,
|
|
306
306
|
out_targets: int,
|
|
307
|
-
|
|
307
|
+
categorical_index_map: Dict[int, int],
|
|
308
308
|
embedding_dim: int = 32,
|
|
309
309
|
num_heads: int = 8,
|
|
310
310
|
num_layers: int = 6,
|
|
@@ -313,7 +313,7 @@ class TabularTransformer(nn.Module, _ArchitectureHandlerMixin):
|
|
|
313
313
|
Args:
|
|
314
314
|
in_features (int): The total number of columns in the input data (features).
|
|
315
315
|
out_targets (int): Number of output targets (1 for regression).
|
|
316
|
-
|
|
316
|
+
categorical_index_map (Dict[int, int]): Maps categorical column index to its cardinality (number of unique categories).
|
|
317
317
|
embedding_dim (int): The dimension for all feature embeddings. Must be divisible by num_heads.
|
|
318
318
|
num_heads (int): The number of heads in the multi-head attention mechanism.
|
|
319
319
|
num_layers (int): The number of sub-encoder-layers in the transformer encoder.
|
|
@@ -340,20 +340,20 @@ class TabularTransformer(nn.Module, _ArchitectureHandlerMixin):
|
|
|
340
340
|
super().__init__()
|
|
341
341
|
|
|
342
342
|
# --- Validation ---
|
|
343
|
-
if
|
|
344
|
-
_LOGGER.error(f"A categorical index ({max(
|
|
343
|
+
if categorical_index_map and max(categorical_index_map.keys()) >= in_features:
|
|
344
|
+
_LOGGER.error(f"A categorical index ({max(categorical_index_map.keys())}) is out of bounds for the provided input features ({in_features}).")
|
|
345
345
|
raise ValueError()
|
|
346
346
|
|
|
347
347
|
# --- Derive numerical indices ---
|
|
348
348
|
all_indices = set(range(in_features))
|
|
349
|
-
categorical_indices_set = set(
|
|
349
|
+
categorical_indices_set = set(categorical_index_map.keys())
|
|
350
350
|
numerical_indices = sorted(list(all_indices - categorical_indices_set))
|
|
351
351
|
|
|
352
352
|
# --- Save configuration ---
|
|
353
353
|
self.in_features = in_features
|
|
354
354
|
self.out_targets = out_targets
|
|
355
355
|
self.numerical_indices = numerical_indices
|
|
356
|
-
self.categorical_map =
|
|
356
|
+
self.categorical_map = categorical_index_map
|
|
357
357
|
self.embedding_dim = embedding_dim
|
|
358
358
|
self.num_heads = num_heads
|
|
359
359
|
self.num_layers = num_layers
|
|
@@ -362,7 +362,7 @@ class TabularTransformer(nn.Module, _ArchitectureHandlerMixin):
|
|
|
362
362
|
# --- 1. Feature Tokenizer ---
|
|
363
363
|
self.tokenizer = _FeatureTokenizer(
|
|
364
364
|
numerical_indices=numerical_indices,
|
|
365
|
-
categorical_map=
|
|
365
|
+
categorical_map=categorical_index_map,
|
|
366
366
|
embedding_dim=embedding_dim
|
|
367
367
|
)
|
|
368
368
|
|
|
@@ -221,7 +221,7 @@ def compare_lists(
|
|
|
221
221
|
- If True: Compares only the `type()` of the items.
|
|
222
222
|
|
|
223
223
|
Returns:
|
|
224
|
-
A dictionary detailing the differences. (saved to `save_dir`).
|
|
224
|
+
dict: A dictionary detailing the differences. (saved to `save_dir`).
|
|
225
225
|
"""
|
|
226
226
|
MISSING_A_KEY = "missing_in_A"
|
|
227
227
|
MISSING_B_KEY = "missing_in_B"
|
|
@@ -83,8 +83,7 @@ def deserialize_object(
|
|
|
83
83
|
filepath: Union[str, Path],
|
|
84
84
|
expected_type: Optional[Type[T]] = None,
|
|
85
85
|
verbose: bool = True,
|
|
86
|
-
|
|
87
|
-
) -> Optional[T]:
|
|
86
|
+
) -> T:
|
|
88
87
|
"""
|
|
89
88
|
Loads a serialized object from a .joblib file.
|
|
90
89
|
|
|
@@ -96,22 +95,17 @@ def deserialize_object(
|
|
|
96
95
|
like `list[str]` by checking the base type (e.g., `list`).
|
|
97
96
|
Defaults to None, which skips the type check.
|
|
98
97
|
verbose (bool): If True, logs success messages.
|
|
99
|
-
raise_on_error (bool): If True, raises exceptions on errors. If False, returns None instead.
|
|
100
98
|
|
|
101
99
|
Returns:
|
|
102
|
-
(Any
|
|
103
|
-
`expected_type` if provided. Returns None if an error
|
|
104
|
-
occurs and `raise_on_error` is False.
|
|
100
|
+
(Any): The deserialized Python object, which will match the `expected_type` if provided.
|
|
105
101
|
"""
|
|
106
|
-
true_filepath = make_fullpath(filepath)
|
|
102
|
+
true_filepath = make_fullpath(filepath, enforce="file")
|
|
107
103
|
|
|
108
104
|
try:
|
|
109
105
|
obj = joblib.load(true_filepath)
|
|
110
106
|
except (IOError, OSError, EOFError, TypeError, ValueError) as e:
|
|
111
107
|
_LOGGER.error(f"Failed to deserialize object from '{true_filepath}'.")
|
|
112
|
-
|
|
113
|
-
raise e
|
|
114
|
-
return None
|
|
108
|
+
raise e
|
|
115
109
|
else:
|
|
116
110
|
# --- Type Validation Step ---
|
|
117
111
|
if expected_type:
|
|
@@ -126,9 +120,7 @@ def deserialize_object(
|
|
|
126
120
|
f"but found '{type(obj)}' in '{true_filepath}'."
|
|
127
121
|
)
|
|
128
122
|
_LOGGER.error(error_msg)
|
|
129
|
-
|
|
130
|
-
raise TypeError()
|
|
131
|
-
return None
|
|
123
|
+
raise TypeError()
|
|
132
124
|
|
|
133
125
|
if verbose:
|
|
134
126
|
_LOGGER.info(f"Loaded object of type '{type(obj)}' from '{true_filepath}'.")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/dragon_ml_toolbox.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/dragon_ml_toolbox.egg-info/requires.txt
RENAMED
|
File without changes
|
{dragon_ml_toolbox-12.10.0 → dragon_ml_toolbox-12.12.0}/dragon_ml_toolbox.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|