pyoframe 0.0.8__tar.gz → 0.0.10__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.
- {pyoframe-0.0.8/src/pyoframe.egg-info → pyoframe-0.0.10}/PKG-INFO +2 -3
- {pyoframe-0.0.8 → pyoframe-0.0.10}/pyproject.toml +2 -2
- {pyoframe-0.0.8 → pyoframe-0.0.10}/src/pyoframe/constants.py +6 -1
- {pyoframe-0.0.8 → pyoframe-0.0.10}/src/pyoframe/core.py +53 -32
- {pyoframe-0.0.8 → pyoframe-0.0.10}/src/pyoframe/io.py +76 -9
- {pyoframe-0.0.8 → pyoframe-0.0.10/src/pyoframe.egg-info}/PKG-INFO +2 -3
- {pyoframe-0.0.8 → pyoframe-0.0.10}/src/pyoframe.egg-info/requires.txt +1 -2
- {pyoframe-0.0.8 → pyoframe-0.0.10}/LICENSE +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/README.md +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/setup.cfg +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/src/pyoframe/__init__.py +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/src/pyoframe/_arithmetic.py +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/src/pyoframe/io_mappers.py +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/src/pyoframe/model.py +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/src/pyoframe/model_element.py +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/src/pyoframe/monkey_patch.py +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/src/pyoframe/objective.py +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/src/pyoframe/solvers.py +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/src/pyoframe/user_defined.py +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/src/pyoframe/util.py +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/src/pyoframe.egg-info/SOURCES.txt +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/src/pyoframe.egg-info/dependency_links.txt +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/src/pyoframe.egg-info/top_level.txt +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/tests/test_arithmetic.py +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/tests/test_examples.py +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/tests/test_io.py +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/tests/test_operations.py +0 -0
- {pyoframe-0.0.8 → pyoframe-0.0.10}/tests/test_solver.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyoframe
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.10
|
|
4
4
|
Summary: Blazing fast linear program interface
|
|
5
5
|
Author-email: Bravos Power <dev@bravospower.com>
|
|
6
6
|
Project-URL: Homepage, https://bravos-power.github.io/pyoframe/
|
|
@@ -15,11 +15,10 @@ Classifier: Natural Language :: English
|
|
|
15
15
|
Requires-Python: >=3.8
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
|
-
Requires-Dist: polars
|
|
18
|
+
Requires-Dist: polars<2,>=0.20
|
|
19
19
|
Requires-Dist: numpy
|
|
20
20
|
Requires-Dist: pyarrow
|
|
21
21
|
Requires-Dist: pandas
|
|
22
|
-
Requires-Dist: tqdm
|
|
23
22
|
Provides-Extra: dev
|
|
24
23
|
Requires-Dist: black; extra == "dev"
|
|
25
24
|
Requires-Dist: bumpver; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyoframe"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.10"
|
|
8
8
|
authors = [{ name = "Bravos Power", email = "dev@bravospower.com" }]
|
|
9
9
|
description = "Blazing fast linear program interface"
|
|
10
10
|
readme = "README.md"
|
|
@@ -16,7 +16,7 @@ classifiers = [
|
|
|
16
16
|
"License :: OSI Approved :: MIT License",
|
|
17
17
|
"Natural Language :: English",
|
|
18
18
|
]
|
|
19
|
-
dependencies = ["polars
|
|
19
|
+
dependencies = ["polars>=0.20,<2", "numpy", "pyarrow", "pandas"]
|
|
20
20
|
|
|
21
21
|
[project.optional-dependencies]
|
|
22
22
|
dev = [
|
|
@@ -6,12 +6,17 @@ Code is heavily based on the `linopy` package by Fabian Hofmann.
|
|
|
6
6
|
MIT License
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
import importlib.metadata
|
|
10
|
+
import typing
|
|
9
11
|
from dataclasses import dataclass
|
|
10
12
|
from enum import Enum
|
|
11
|
-
import typing
|
|
12
13
|
from typing import Literal, Optional, Union
|
|
14
|
+
|
|
13
15
|
import polars as pl
|
|
16
|
+
from packaging import version
|
|
14
17
|
|
|
18
|
+
# We want to try and support multiple major versions of polars
|
|
19
|
+
POLARS_VERSION = version.parse(importlib.metadata.version("polars"))
|
|
15
20
|
|
|
16
21
|
COEF_KEY = "__coeff"
|
|
17
22
|
VAR_KEY = "__variable_id"
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
2
4
|
from typing import (
|
|
5
|
+
TYPE_CHECKING,
|
|
3
6
|
Iterable,
|
|
4
7
|
List,
|
|
5
8
|
Mapping,
|
|
9
|
+
Optional,
|
|
6
10
|
Protocol,
|
|
7
11
|
Sequence,
|
|
8
|
-
overload,
|
|
9
12
|
Union,
|
|
10
|
-
|
|
11
|
-
TYPE_CHECKING,
|
|
13
|
+
overload,
|
|
12
14
|
)
|
|
13
|
-
from abc import ABC, abstractmethod
|
|
14
15
|
|
|
15
16
|
import pandas as pd
|
|
16
17
|
import polars as pl
|
|
18
|
+
from packaging import version
|
|
17
19
|
|
|
18
20
|
from pyoframe._arithmetic import _add_expressions, _get_dimensions
|
|
19
21
|
from pyoframe.constants import (
|
|
@@ -21,33 +23,33 @@ from pyoframe.constants import (
|
|
|
21
23
|
CONST_TERM,
|
|
22
24
|
CONSTRAINT_KEY,
|
|
23
25
|
DUAL_KEY,
|
|
26
|
+
POLARS_VERSION,
|
|
27
|
+
RC_COL,
|
|
24
28
|
RESERVED_COL_KEYS,
|
|
25
29
|
SLACK_COL,
|
|
26
|
-
VAR_KEY,
|
|
27
30
|
SOLUTION_KEY,
|
|
28
|
-
|
|
29
|
-
VType,
|
|
30
|
-
VTypeValue,
|
|
31
|
+
VAR_KEY,
|
|
31
32
|
Config,
|
|
32
33
|
ConstraintSense,
|
|
33
|
-
UnmatchedStrategy,
|
|
34
|
-
PyoframeError,
|
|
35
34
|
ObjSense,
|
|
35
|
+
PyoframeError,
|
|
36
|
+
UnmatchedStrategy,
|
|
37
|
+
VType,
|
|
38
|
+
VTypeValue,
|
|
39
|
+
)
|
|
40
|
+
from pyoframe.model_element import (
|
|
41
|
+
ModelElement,
|
|
42
|
+
ModelElementWithId,
|
|
43
|
+
SupportPolarsMethodMixin,
|
|
36
44
|
)
|
|
37
45
|
from pyoframe.util import (
|
|
46
|
+
FuncArgs,
|
|
38
47
|
cast_coef_to_string,
|
|
39
48
|
concat_dimensions,
|
|
49
|
+
dataframe_to_tupled_list,
|
|
40
50
|
get_obj_repr,
|
|
41
51
|
parse_inputs_as_iterable,
|
|
42
52
|
unwrap_single_values,
|
|
43
|
-
dataframe_to_tupled_list,
|
|
44
|
-
FuncArgs,
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
from pyoframe.model_element import (
|
|
48
|
-
ModelElement,
|
|
49
|
-
ModelElementWithId,
|
|
50
|
-
SupportPolarsMethodMixin,
|
|
51
53
|
)
|
|
52
54
|
|
|
53
55
|
if TYPE_CHECKING: # pragma: no cover
|
|
@@ -271,7 +273,18 @@ class Set(ModelElement, SupportsMath, SupportPolarsMethodMixin):
|
|
|
271
273
|
elif isinstance(set, Constraint):
|
|
272
274
|
df = set.data.select(set.dimensions_unsafe)
|
|
273
275
|
elif isinstance(set, SupportsMath):
|
|
274
|
-
|
|
276
|
+
if POLARS_VERSION.major < 1:
|
|
277
|
+
df = (
|
|
278
|
+
set.to_expr()
|
|
279
|
+
.data.drop(RESERVED_COL_KEYS)
|
|
280
|
+
.unique(maintain_order=True)
|
|
281
|
+
)
|
|
282
|
+
else:
|
|
283
|
+
df = (
|
|
284
|
+
set.to_expr()
|
|
285
|
+
.data.drop(RESERVED_COL_KEYS, strict=False)
|
|
286
|
+
.unique(maintain_order=True)
|
|
287
|
+
)
|
|
275
288
|
elif isinstance(set, pd.Index):
|
|
276
289
|
df = pl.from_pandas(pd.DataFrame(index=set).reset_index())
|
|
277
290
|
elif isinstance(set, pd.DataFrame):
|
|
@@ -601,7 +614,7 @@ class Expression(ModelElement, SupportsMath, SupportPolarsMethodMixin):
|
|
|
601
614
|
data = (
|
|
602
615
|
self.data.join(
|
|
603
616
|
multiplier,
|
|
604
|
-
on=dims_in_common,
|
|
617
|
+
on=dims_in_common if len(dims_in_common) > 0 else None,
|
|
605
618
|
how="inner" if dims_in_common else "cross",
|
|
606
619
|
)
|
|
607
620
|
.with_columns(pl.col(COEF_KEY) * pl.col(COEF_KEY + "_right"))
|
|
@@ -692,7 +705,9 @@ class Expression(ModelElement, SupportsMath, SupportPolarsMethodMixin):
|
|
|
692
705
|
>>> m.expr_1 = 2 * m.X + 1
|
|
693
706
|
>>> m.expr_2 = pf.sum(m.expr_1)
|
|
694
707
|
>>> m.objective = m.expr_2 - 3
|
|
695
|
-
>>> result = m.solve(log_to_console=False)
|
|
708
|
+
>>> result = m.solve(log_to_console=False) # doctest: +ELLIPSIS
|
|
709
|
+
<BLANKLINE>
|
|
710
|
+
...
|
|
696
711
|
>>> m.expr_1.value
|
|
697
712
|
shape: (3, 2)
|
|
698
713
|
┌──────┬──────────┐
|
|
@@ -1003,13 +1018,9 @@ class Constraint(ModelElementWithId):
|
|
|
1003
1018
|
>>> m.hours_spent = pf.Variable(homework_due_tomorrow[["project"]], lb=0)
|
|
1004
1019
|
>>> m.must_finish_project = m.hours_spent >= homework_due_tomorrow[["project", "hours_to_finish"]]
|
|
1005
1020
|
>>> m.only_one_day = sum("project", m.hours_spent) <= 24
|
|
1006
|
-
>>> m.solve(log_to_console=False)
|
|
1007
|
-
Status: warning
|
|
1008
|
-
Termination condition: infeasible
|
|
1009
|
-
<BLANKLINE>
|
|
1010
|
-
|
|
1011
1021
|
>>> _ = m.must_finish_project.relax(homework_due_tomorrow[["project", "cost_per_hour_underdelivered"]], max=homework_due_tomorrow[["project", "max_underdelivered"]])
|
|
1012
|
-
>>>
|
|
1022
|
+
>>> _ = m.solve(log_to_console=False) # doctest: +ELLIPSIS
|
|
1023
|
+
\rWriting ...
|
|
1013
1024
|
>>> m.hours_spent.solution
|
|
1014
1025
|
shape: (3, 2)
|
|
1015
1026
|
┌─────────┬──────────┐
|
|
@@ -1029,7 +1040,8 @@ class Constraint(ModelElementWithId):
|
|
|
1029
1040
|
>>> m.hours_spent = pf.Variable(homework_due_tomorrow[["project"]], lb=0)
|
|
1030
1041
|
>>> m.must_finish_project = (m.hours_spent >= homework_due_tomorrow[["project", "hours_to_finish"]]).relax(5)
|
|
1031
1042
|
>>> m.only_one_day = (sum("project", m.hours_spent) <= 24).relax(1)
|
|
1032
|
-
>>> _ = m.solve(log_to_console=False)
|
|
1043
|
+
>>> _ = m.solve(log_to_console=False) # doctest: +ELLIPSIS
|
|
1044
|
+
\rWriting ...
|
|
1033
1045
|
>>> m.objective.value
|
|
1034
1046
|
-3.0
|
|
1035
1047
|
>>> m.hours_spent.solution
|
|
@@ -1259,7 +1271,10 @@ class Variable(ModelElementWithId, SupportsMath, SupportPolarsMethodMixin):
|
|
|
1259
1271
|
)
|
|
1260
1272
|
|
|
1261
1273
|
def to_expr(self) -> Expression:
|
|
1262
|
-
|
|
1274
|
+
if POLARS_VERSION.major < 1:
|
|
1275
|
+
return self._new(self.data.drop(SOLUTION_KEY))
|
|
1276
|
+
else:
|
|
1277
|
+
return self._new(self.data.drop(SOLUTION_KEY, strict=False))
|
|
1263
1278
|
|
|
1264
1279
|
def _new(self, data: pl.DataFrame):
|
|
1265
1280
|
e = Expression(data.with_columns(pl.lit(1.0).alias(COEF_KEY)))
|
|
@@ -1335,7 +1350,13 @@ class Variable(ModelElementWithId, SupportsMath, SupportPolarsMethodMixin):
|
|
|
1335
1350
|
|
|
1336
1351
|
expr = self.to_expr()
|
|
1337
1352
|
data = expr.data.rename({dim: "__prev"})
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1353
|
+
|
|
1354
|
+
if POLARS_VERSION.major < 1:
|
|
1355
|
+
data = data.join(
|
|
1356
|
+
wrapped, left_on="__prev", right_on="__next", how="inner"
|
|
1357
|
+
).drop(["__prev", "__next"])
|
|
1358
|
+
else:
|
|
1359
|
+
data = data.join(
|
|
1360
|
+
wrapped, left_on="__prev", right_on="__next", how="inner"
|
|
1361
|
+
).drop(["__prev", "__next"], strict=False)
|
|
1341
1362
|
return expr._new(data)
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
Module containing all import/export functionalities.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
5
7
|
from io import TextIOWrapper
|
|
6
|
-
from tempfile import NamedTemporaryFile
|
|
7
8
|
from pathlib import Path
|
|
9
|
+
from tempfile import NamedTemporaryFile
|
|
8
10
|
from typing import TYPE_CHECKING, Iterable, Optional, TypeVar, Union
|
|
9
|
-
from tqdm import tqdm
|
|
10
11
|
|
|
11
12
|
from pyoframe.constants import CONST_TERM, VAR_KEY, ObjSense
|
|
12
13
|
from pyoframe.core import Constraint, Variable
|
|
@@ -24,6 +25,57 @@ if TYPE_CHECKING: # pragma: no cover
|
|
|
24
25
|
|
|
25
26
|
import polars as pl
|
|
26
27
|
|
|
28
|
+
T = TypeVar("T")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def io_progress_bar(
|
|
32
|
+
iterable: Iterable[T],
|
|
33
|
+
prefix: str = "",
|
|
34
|
+
suffix: str = "",
|
|
35
|
+
length: int = 50,
|
|
36
|
+
fill: str = "█",
|
|
37
|
+
update_every: int = 1,
|
|
38
|
+
):
|
|
39
|
+
"""
|
|
40
|
+
Display progress bar for I/O operations.
|
|
41
|
+
"""
|
|
42
|
+
try:
|
|
43
|
+
total = len(iterable)
|
|
44
|
+
except TypeError:
|
|
45
|
+
total = None
|
|
46
|
+
|
|
47
|
+
start_time = time.time()
|
|
48
|
+
|
|
49
|
+
def print_progress(iteration: int):
|
|
50
|
+
if total is not None:
|
|
51
|
+
percent = f"{100 * (iteration / float(total)):.1f}"
|
|
52
|
+
filled_length = int(length * iteration // total)
|
|
53
|
+
bar = fill * filled_length + "-" * (length - filled_length)
|
|
54
|
+
else:
|
|
55
|
+
percent = "N/A"
|
|
56
|
+
bar = fill * (iteration % length) + "-" * (length - (iteration % length))
|
|
57
|
+
elapsed_time = time.time() - start_time
|
|
58
|
+
if iteration > 0:
|
|
59
|
+
estimated_total_time = (
|
|
60
|
+
elapsed_time * (total / iteration) if total else elapsed_time
|
|
61
|
+
)
|
|
62
|
+
estimated_remaining_time = estimated_total_time - elapsed_time
|
|
63
|
+
eta = time.strftime("%H:%M:%S", time.gmtime(estimated_remaining_time))
|
|
64
|
+
else:
|
|
65
|
+
eta = "Estimating..." # pragma: no cover
|
|
66
|
+
sys.stdout.write(
|
|
67
|
+
f'\r{prefix} |{bar}| {percent}% Complete ({iteration}/{total if total else "?"}) ETA: {eta} {suffix}'
|
|
68
|
+
)
|
|
69
|
+
sys.stdout.flush()
|
|
70
|
+
|
|
71
|
+
for i, item in enumerate(iterable):
|
|
72
|
+
yield item
|
|
73
|
+
if (i + 1) % update_every == 0 or total is None or i == total - 1:
|
|
74
|
+
print_progress(i + 1)
|
|
75
|
+
|
|
76
|
+
sys.stdout.write("\n")
|
|
77
|
+
sys.stdout.flush()
|
|
78
|
+
|
|
27
79
|
|
|
28
80
|
def objective_to_file(m: "Model", f: TextIOWrapper, var_map):
|
|
29
81
|
"""
|
|
@@ -41,7 +93,11 @@ def objective_to_file(m: "Model", f: TextIOWrapper, var_map):
|
|
|
41
93
|
|
|
42
94
|
def constraints_to_file(m: "Model", f: TextIOWrapper, var_map, const_map):
|
|
43
95
|
for constraint in create_section(
|
|
44
|
-
|
|
96
|
+
io_progress_bar(
|
|
97
|
+
m.constraints, prefix="Writing constraints to file", update_every=5
|
|
98
|
+
),
|
|
99
|
+
f,
|
|
100
|
+
"s.t.",
|
|
45
101
|
):
|
|
46
102
|
f.write(constraint.to_str(var_map=var_map, const_map=const_map) + "\n")
|
|
47
103
|
|
|
@@ -58,7 +114,9 @@ def bounds_to_file(m: "Model", f, var_map):
|
|
|
58
114
|
)
|
|
59
115
|
f.write(f"{var_map.apply(const_term_df).item()} = 1\n")
|
|
60
116
|
|
|
61
|
-
for variable in
|
|
117
|
+
for variable in io_progress_bar(
|
|
118
|
+
m.variables, prefix="Writing bounds to file", update_every=1
|
|
119
|
+
):
|
|
62
120
|
terms = []
|
|
63
121
|
|
|
64
122
|
if variable.lb != 0:
|
|
@@ -88,7 +146,13 @@ def binaries_to_file(m: "Model", f, var_map: Mapper):
|
|
|
88
146
|
Write out binaries of a model to a lp file.
|
|
89
147
|
"""
|
|
90
148
|
for variable in create_section(
|
|
91
|
-
|
|
149
|
+
io_progress_bar(
|
|
150
|
+
m.binary_variables,
|
|
151
|
+
prefix="Writing binary variables to file",
|
|
152
|
+
update_every=1,
|
|
153
|
+
),
|
|
154
|
+
f,
|
|
155
|
+
"binary",
|
|
92
156
|
):
|
|
93
157
|
lines = (
|
|
94
158
|
var_map.apply(variable.data, to_col=None)
|
|
@@ -103,7 +167,13 @@ def integers_to_file(m: "Model", f, var_map: Mapper):
|
|
|
103
167
|
Write out integers of a model to a lp file.
|
|
104
168
|
"""
|
|
105
169
|
for variable in create_section(
|
|
106
|
-
|
|
170
|
+
io_progress_bar(
|
|
171
|
+
m.integer_variables,
|
|
172
|
+
prefix="Writing integer variables to file",
|
|
173
|
+
update_every=5,
|
|
174
|
+
),
|
|
175
|
+
f,
|
|
176
|
+
"general",
|
|
107
177
|
):
|
|
108
178
|
lines = (
|
|
109
179
|
var_map.apply(variable.data, to_col=None)
|
|
@@ -113,9 +183,6 @@ def integers_to_file(m: "Model", f, var_map: Mapper):
|
|
|
113
183
|
f.write(lines + "\n")
|
|
114
184
|
|
|
115
185
|
|
|
116
|
-
T = TypeVar("T")
|
|
117
|
-
|
|
118
|
-
|
|
119
186
|
def create_section(iterable: Iterable[T], f, section_header) -> Iterable[T]:
|
|
120
187
|
wrote = False
|
|
121
188
|
for item in iterable:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyoframe
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.10
|
|
4
4
|
Summary: Blazing fast linear program interface
|
|
5
5
|
Author-email: Bravos Power <dev@bravospower.com>
|
|
6
6
|
Project-URL: Homepage, https://bravos-power.github.io/pyoframe/
|
|
@@ -15,11 +15,10 @@ Classifier: Natural Language :: English
|
|
|
15
15
|
Requires-Python: >=3.8
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
|
-
Requires-Dist: polars
|
|
18
|
+
Requires-Dist: polars<2,>=0.20
|
|
19
19
|
Requires-Dist: numpy
|
|
20
20
|
Requires-Dist: pyarrow
|
|
21
21
|
Requires-Dist: pandas
|
|
22
|
-
Requires-Dist: tqdm
|
|
23
22
|
Provides-Extra: dev
|
|
24
23
|
Requires-Dist: black; extra == "dev"
|
|
25
24
|
Requires-Dist: bumpver; extra == "dev"
|
|
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
|