pyoframe 0.0.4__py3-none-any.whl → 0.0.6__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.
- pyoframe/__init__.py +12 -3
- pyoframe/_arithmetic.py +3 -6
- pyoframe/constants.py +20 -14
- pyoframe/{constraints.py → core.py} +504 -74
- pyoframe/io.py +66 -30
- pyoframe/io_mappers.py +66 -34
- pyoframe/model.py +65 -41
- pyoframe/model_element.py +128 -18
- pyoframe/monkey_patch.py +2 -2
- pyoframe/objective.py +16 -13
- pyoframe/solvers.py +300 -109
- pyoframe/user_defined.py +60 -0
- pyoframe/util.py +56 -55
- {pyoframe-0.0.4.dist-info → pyoframe-0.0.6.dist-info}/METADATA +9 -2
- pyoframe-0.0.6.dist-info/RECORD +18 -0
- pyoframe/variables.py +0 -193
- pyoframe-0.0.4.dist-info/RECORD +0 -18
- {pyoframe-0.0.4.dist-info → pyoframe-0.0.6.dist-info}/LICENSE +0 -0
- {pyoframe-0.0.4.dist-info → pyoframe-0.0.6.dist-info}/WHEEL +0 -0
- {pyoframe-0.0.4.dist-info → pyoframe-0.0.6.dist-info}/top_level.txt +0 -0
pyoframe/io.py
CHANGED
|
@@ -6,16 +6,17 @@ from io import TextIOWrapper
|
|
|
6
6
|
from tempfile import NamedTemporaryFile
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import TYPE_CHECKING, Iterable, Optional, TypeVar, Union
|
|
9
|
+
from tqdm import tqdm
|
|
9
10
|
|
|
10
|
-
from pyoframe.constants import VAR_KEY,
|
|
11
|
-
from pyoframe.
|
|
12
|
-
from pyoframe.variables import Variable
|
|
11
|
+
from pyoframe.constants import CONST_TERM, VAR_KEY, ObjSense
|
|
12
|
+
from pyoframe.core import Constraint, Variable
|
|
13
13
|
from pyoframe.io_mappers import (
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
Base36ConstMapper,
|
|
15
|
+
Base36VarMapper,
|
|
16
16
|
IOMappers,
|
|
17
17
|
Mapper,
|
|
18
18
|
NamedMapper,
|
|
19
|
+
NamedVariableMapper,
|
|
19
20
|
)
|
|
20
21
|
|
|
21
22
|
if TYPE_CHECKING: # pragma: no cover
|
|
@@ -28,63 +29,88 @@ def objective_to_file(m: "Model", f: TextIOWrapper, var_map):
|
|
|
28
29
|
"""
|
|
29
30
|
Write out the objective of a model to a lp file.
|
|
30
31
|
"""
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
if m.objective is None:
|
|
33
|
+
return
|
|
34
|
+
objective_sense = "minimize" if m.sense == ObjSense.MIN else "maximize"
|
|
35
|
+
f.write(f"{objective_sense}\n\nobj:\n\n")
|
|
36
|
+
result = m.objective.to_str(
|
|
37
|
+
var_map=var_map, include_prefix=False, include_const_variable=True
|
|
38
|
+
)
|
|
39
|
+
f.write(result)
|
|
36
40
|
|
|
37
41
|
|
|
38
42
|
def constraints_to_file(m: "Model", f: TextIOWrapper, var_map, const_map):
|
|
39
|
-
for constraint in create_section(
|
|
40
|
-
|
|
43
|
+
for constraint in create_section(
|
|
44
|
+
tqdm(m.constraints, desc="Writing constraints to file"), f, "s.t."
|
|
45
|
+
):
|
|
46
|
+
f.write(constraint.to_str(var_map=var_map, const_map=const_map) + "\n")
|
|
41
47
|
|
|
42
48
|
|
|
43
49
|
def bounds_to_file(m: "Model", f, var_map):
|
|
44
50
|
"""
|
|
45
51
|
Write out variables of a model to a lp file.
|
|
46
52
|
"""
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
if (m.objective is not None and m.objective.has_constant) or len(m.variables) != 0:
|
|
54
|
+
f.write("\n\nbounds\n\n")
|
|
55
|
+
if m.objective is not None and m.objective.has_constant:
|
|
56
|
+
const_term_df = pl.DataFrame(
|
|
57
|
+
{VAR_KEY: [CONST_TERM]}, schema={VAR_KEY: pl.UInt32}
|
|
58
|
+
)
|
|
59
|
+
f.write(f"{var_map.apply(const_term_df).item()} = 1\n")
|
|
60
|
+
|
|
61
|
+
for variable in tqdm(m.variables, desc="Writing bounds to file"):
|
|
62
|
+
terms = []
|
|
63
|
+
|
|
64
|
+
if variable.lb != 0:
|
|
65
|
+
terms.append(pl.lit(f"{variable.lb:.12g} <= "))
|
|
66
|
+
|
|
67
|
+
terms.append(VAR_KEY)
|
|
68
|
+
|
|
69
|
+
if variable.ub != float("inf"):
|
|
70
|
+
terms.append(pl.lit(f" <= {variable.ub:.12g}"))
|
|
71
|
+
|
|
72
|
+
terms.append(pl.lit("\n"))
|
|
73
|
+
|
|
74
|
+
if len(terms) < 3:
|
|
75
|
+
continue
|
|
50
76
|
|
|
51
77
|
df = (
|
|
52
78
|
var_map.apply(variable.data, to_col=None)
|
|
53
|
-
.select(
|
|
54
|
-
pl.concat_str(
|
|
55
|
-
pl.lit(f"{lb} <= "), VAR_KEY, pl.lit(f" <= {ub}\n")
|
|
56
|
-
).str.concat("")
|
|
57
|
-
)
|
|
79
|
+
.select(pl.concat_str(terms).str.concat(""))
|
|
58
80
|
.item()
|
|
59
81
|
)
|
|
60
82
|
|
|
61
|
-
f.
|
|
83
|
+
f.write(df)
|
|
62
84
|
|
|
63
85
|
|
|
64
86
|
def binaries_to_file(m: "Model", f, var_map: Mapper):
|
|
65
87
|
"""
|
|
66
88
|
Write out binaries of a model to a lp file.
|
|
67
89
|
"""
|
|
68
|
-
for variable in create_section(
|
|
90
|
+
for variable in create_section(
|
|
91
|
+
tqdm(m.binary_variables, "Writing binary variables to file"), f, "binary"
|
|
92
|
+
):
|
|
69
93
|
lines = (
|
|
70
94
|
var_map.apply(variable.data, to_col=None)
|
|
71
95
|
.select(pl.col(VAR_KEY).str.concat("\n"))
|
|
72
96
|
.item()
|
|
73
97
|
)
|
|
74
|
-
f.
|
|
98
|
+
f.write(lines + "\n")
|
|
75
99
|
|
|
76
100
|
|
|
77
101
|
def integers_to_file(m: "Model", f, var_map: Mapper):
|
|
78
102
|
"""
|
|
79
103
|
Write out integers of a model to a lp file.
|
|
80
104
|
"""
|
|
81
|
-
for variable in create_section(
|
|
105
|
+
for variable in create_section(
|
|
106
|
+
tqdm(m.integer_variables, "Writing integer variables to file"), f, "general"
|
|
107
|
+
):
|
|
82
108
|
lines = (
|
|
83
109
|
var_map.apply(variable.data, to_col=None)
|
|
84
110
|
.select(pl.col(VAR_KEY).str.concat("\n"))
|
|
85
111
|
.item()
|
|
86
112
|
)
|
|
87
|
-
f.
|
|
113
|
+
f.write(lines + "\n")
|
|
88
114
|
|
|
89
115
|
|
|
90
116
|
T = TypeVar("T")
|
|
@@ -103,9 +129,9 @@ def get_var_map(m: "Model", use_var_names):
|
|
|
103
129
|
if use_var_names:
|
|
104
130
|
if m.var_map is not None:
|
|
105
131
|
return m.var_map
|
|
106
|
-
var_map =
|
|
132
|
+
var_map = NamedVariableMapper(Variable)
|
|
107
133
|
else:
|
|
108
|
-
var_map =
|
|
134
|
+
var_map = Base36VarMapper(Variable)
|
|
109
135
|
|
|
110
136
|
for v in m.variables:
|
|
111
137
|
var_map.add(v)
|
|
@@ -113,10 +139,20 @@ def get_var_map(m: "Model", use_var_names):
|
|
|
113
139
|
|
|
114
140
|
|
|
115
141
|
def to_file(
|
|
116
|
-
m: "Model", file_path: Optional[Union[str, Path]], use_var_names=False
|
|
142
|
+
m: "Model", file_path: Optional[Union[str, Path]] = None, use_var_names=False
|
|
117
143
|
) -> Path:
|
|
118
144
|
"""
|
|
119
145
|
Write out a model to a lp file.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
m: The model to write out.
|
|
149
|
+
file_path: The path to write the model to. If None, a temporary file is created. The caller is responsible for
|
|
150
|
+
deleting the file after use.
|
|
151
|
+
use_var_names: If True, variable names are used in the lp file. Otherwise, variable
|
|
152
|
+
indices are used.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
The path to the lp file.
|
|
120
156
|
"""
|
|
121
157
|
if file_path is None:
|
|
122
158
|
with NamedTemporaryFile(
|
|
@@ -131,7 +167,7 @@ def to_file(
|
|
|
131
167
|
file_path.unlink()
|
|
132
168
|
|
|
133
169
|
const_map = (
|
|
134
|
-
NamedMapper(Constraint) if use_var_names else
|
|
170
|
+
NamedMapper(Constraint) if use_var_names else Base36ConstMapper(Constraint)
|
|
135
171
|
)
|
|
136
172
|
for c in m.constraints:
|
|
137
173
|
const_map.add(c)
|
|
@@ -144,6 +180,6 @@ def to_file(
|
|
|
144
180
|
bounds_to_file(m, f, var_map)
|
|
145
181
|
binaries_to_file(m, f, var_map)
|
|
146
182
|
integers_to_file(m, f, var_map)
|
|
147
|
-
f.write("
|
|
183
|
+
f.write("\nend\n")
|
|
148
184
|
|
|
149
185
|
return file_path
|
pyoframe/io_mappers.py
CHANGED
|
@@ -9,14 +9,14 @@ from abc import ABC, abstractmethod
|
|
|
9
9
|
|
|
10
10
|
from typing import TYPE_CHECKING, Optional, Type, Union
|
|
11
11
|
import polars as pl
|
|
12
|
-
from pyoframe.constants import NAME_COL
|
|
13
12
|
from pyoframe.util import concat_dimensions
|
|
13
|
+
from pyoframe.constants import CONST_TERM
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING: # pragma: no cover
|
|
17
17
|
from pyoframe.model import Variable
|
|
18
|
-
from pyoframe.
|
|
19
|
-
from pyoframe.
|
|
18
|
+
from pyoframe.core import Constraint
|
|
19
|
+
from pyoframe.model_element import ModelElementWithId
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
@dataclass
|
|
@@ -26,38 +26,47 @@ class IOMappers:
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class Mapper(ABC):
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
NAME_COL = "__name"
|
|
31
|
+
|
|
32
|
+
def __init__(self, cls: Type["ModelElementWithId"]) -> None:
|
|
30
33
|
self._ID_COL = cls.get_id_column_name()
|
|
31
34
|
self.mapping_registry = pl.DataFrame(
|
|
32
|
-
{self._ID_COL: [], NAME_COL: []},
|
|
33
|
-
schema={self._ID_COL: pl.UInt32, NAME_COL: pl.String},
|
|
35
|
+
{self._ID_COL: [], Mapper.NAME_COL: []},
|
|
36
|
+
schema={self._ID_COL: pl.UInt32, Mapper.NAME_COL: pl.String},
|
|
34
37
|
)
|
|
35
38
|
|
|
36
39
|
def add(self, element: Union["Variable", "Constraint"]) -> None:
|
|
37
|
-
self.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
self._extend_registry(self._element_to_map(element))
|
|
41
|
+
|
|
42
|
+
def _extend_registry(self, df: pl.DataFrame) -> None:
|
|
43
|
+
self.mapping_registry = pl.concat([self.mapping_registry, df])
|
|
40
44
|
|
|
41
45
|
@abstractmethod
|
|
42
|
-
def _element_to_map(self, element: "
|
|
46
|
+
def _element_to_map(self, element: "ModelElementWithId") -> pl.DataFrame: ...
|
|
43
47
|
|
|
44
48
|
def apply(
|
|
45
49
|
self,
|
|
46
50
|
df: pl.DataFrame,
|
|
47
|
-
to_col: Optional[str],
|
|
51
|
+
to_col: Optional[str] = None,
|
|
48
52
|
) -> pl.DataFrame:
|
|
53
|
+
if df.height == 0:
|
|
54
|
+
return df
|
|
49
55
|
result = df.join(
|
|
50
56
|
self.mapping_registry, on=self._ID_COL, how="left", validate="m:1"
|
|
51
57
|
)
|
|
52
58
|
if to_col is None:
|
|
53
59
|
result = result.drop(self._ID_COL)
|
|
54
60
|
to_col = self._ID_COL
|
|
55
|
-
return result.rename({NAME_COL: to_col})
|
|
61
|
+
return result.rename({Mapper.NAME_COL: to_col})
|
|
56
62
|
|
|
57
63
|
def undo(self, df: pl.DataFrame) -> pl.DataFrame:
|
|
64
|
+
if df.height == 0:
|
|
65
|
+
return df
|
|
66
|
+
df = df.rename({self._ID_COL: Mapper.NAME_COL})
|
|
58
67
|
return df.join(
|
|
59
|
-
self.mapping_registry, on=NAME_COL, how="left", validate="m:1"
|
|
60
|
-
).drop(NAME_COL)
|
|
68
|
+
self.mapping_registry, on=Mapper.NAME_COL, how="left", validate="m:1"
|
|
69
|
+
).drop(Mapper.NAME_COL)
|
|
61
70
|
|
|
62
71
|
|
|
63
72
|
class NamedMapper(Mapper):
|
|
@@ -68,7 +77,7 @@ class NamedMapper(Mapper):
|
|
|
68
77
|
|
|
69
78
|
>>> import polars as pl
|
|
70
79
|
>>> import pyoframe as pf
|
|
71
|
-
>>> m = pf.Model()
|
|
80
|
+
>>> m = pf.Model("min")
|
|
72
81
|
>>> m.foo = pf.Variable(pl.DataFrame({"t": range(4)}))
|
|
73
82
|
>>> pf.sum(m.foo)
|
|
74
83
|
<Expression size=1 dimensions={} terms=4>
|
|
@@ -81,17 +90,31 @@ class NamedMapper(Mapper):
|
|
|
81
90
|
element_name is not None
|
|
82
91
|
), "Element must have a name to be used in a named mapping."
|
|
83
92
|
return concat_dimensions(
|
|
84
|
-
element.ids, keep_dims=False, prefix=element_name, to_col=NAME_COL
|
|
93
|
+
element.ids, keep_dims=False, prefix=element_name, to_col=Mapper.NAME_COL
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class NamedVariableMapper(NamedMapper):
|
|
98
|
+
CONST_TERM_NAME = "_ONE"
|
|
99
|
+
|
|
100
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
101
|
+
super().__init__(*args, **kwargs)
|
|
102
|
+
self._extend_registry(
|
|
103
|
+
pl.DataFrame(
|
|
104
|
+
{self._ID_COL: [CONST_TERM], self.NAME_COL: [self.CONST_TERM_NAME]},
|
|
105
|
+
schema={self._ID_COL: pl.UInt32, self.NAME_COL: pl.String},
|
|
106
|
+
)
|
|
85
107
|
)
|
|
86
108
|
|
|
87
109
|
|
|
88
|
-
class
|
|
89
|
-
# Mapping between a base
|
|
110
|
+
class Base36Mapper(Mapper, ABC):
|
|
111
|
+
# Mapping between a base 36 character and its integer value
|
|
112
|
+
# Note: we must use only lowercase since Gurobi auto-converts variables that aren't in constraints to lowercase (kind of annoying)
|
|
90
113
|
_CHAR_TABLE = pl.DataFrame(
|
|
91
|
-
{"char": list(string.digits + string.
|
|
114
|
+
{"char": list(string.digits + string.ascii_lowercase)},
|
|
92
115
|
).with_columns(pl.int_range(pl.len()).cast(pl.UInt32).alias("code"))
|
|
93
116
|
|
|
94
|
-
_BASE = _CHAR_TABLE.height # _BASE =
|
|
117
|
+
_BASE = _CHAR_TABLE.height # _BASE = 36
|
|
95
118
|
_ZERO = _CHAR_TABLE.filter(pl.col("code") == 0).select("char").item() # _ZERO = "0"
|
|
96
119
|
|
|
97
120
|
@property
|
|
@@ -109,7 +132,7 @@ class Base62Mapper(Mapper, ABC):
|
|
|
109
132
|
query = pl.concat_str(
|
|
110
133
|
pl.lit(self._prefix),
|
|
111
134
|
pl.col(self._ID_COL).map_batches(
|
|
112
|
-
|
|
135
|
+
Base36Mapper._to_base36,
|
|
113
136
|
return_dtype=pl.String,
|
|
114
137
|
is_elementwise=True,
|
|
115
138
|
),
|
|
@@ -121,24 +144,24 @@ class Base62Mapper(Mapper, ABC):
|
|
|
121
144
|
return df.with_columns(query.alias(to_col))
|
|
122
145
|
|
|
123
146
|
@classmethod
|
|
124
|
-
def
|
|
125
|
-
"""Returns a series of dtype str with a base
|
|
126
|
-
The letters 0-
|
|
147
|
+
def _to_base36(cls, int_col: pl.Series) -> pl.Series:
|
|
148
|
+
"""Returns a series of dtype str with a base 36 representation of the integers in int_col.
|
|
149
|
+
The letters 0-9A-Z are used as symbols for the representation.
|
|
127
150
|
|
|
128
151
|
Examples:
|
|
129
152
|
|
|
130
153
|
>>> import polars as pl
|
|
131
154
|
>>> s = pl.Series([0,10,20,60,53,66], dtype=pl.UInt32)
|
|
132
|
-
>>>
|
|
133
|
-
['0', 'a', 'k', '
|
|
155
|
+
>>> Base36Mapper._to_base36(s).to_list()
|
|
156
|
+
['0', 'a', 'k', '1o', '1h', '1u']
|
|
134
157
|
|
|
135
158
|
>>> s = pl.Series([0], dtype=pl.UInt32)
|
|
136
|
-
>>>
|
|
159
|
+
>>> Base36Mapper._to_base36(s).to_list()
|
|
137
160
|
['0']
|
|
138
161
|
"""
|
|
139
162
|
assert isinstance(
|
|
140
163
|
int_col.dtype, pl.UInt32
|
|
141
|
-
), "
|
|
164
|
+
), "_to_base36() only works for UInt32 id columns"
|
|
142
165
|
|
|
143
166
|
largest_id = int_col.max()
|
|
144
167
|
if largest_id == 0:
|
|
@@ -168,23 +191,23 @@ class Base62Mapper(Mapper, ABC):
|
|
|
168
191
|
)
|
|
169
192
|
|
|
170
193
|
def _element_to_map(self, element) -> pl.DataFrame:
|
|
171
|
-
return self.apply(element.ids.select(self._ID_COL), to_col=NAME_COL)
|
|
194
|
+
return self.apply(element.ids.select(self._ID_COL), to_col=Mapper.NAME_COL)
|
|
172
195
|
|
|
173
196
|
|
|
174
|
-
class
|
|
197
|
+
class Base36VarMapper(Base36Mapper):
|
|
175
198
|
"""
|
|
176
199
|
Examples:
|
|
177
200
|
>>> import polars as pl
|
|
178
201
|
>>> from pyoframe import Model, Variable
|
|
179
202
|
>>> from pyoframe.constants import VAR_KEY
|
|
180
|
-
>>> m = Model()
|
|
203
|
+
>>> m = Model("min")
|
|
181
204
|
>>> m.x = Variable(pl.DataFrame({"t": range(1,63)}))
|
|
182
205
|
>>> (m.x.filter(t=11)+1).to_str()
|
|
183
206
|
'[11]: 1 + x[11]'
|
|
184
|
-
>>> (m.x.filter(t=11)+1).to_str(var_map=
|
|
207
|
+
>>> (m.x.filter(t=11)+1).to_str(var_map=Base36VarMapper(Variable))
|
|
185
208
|
'[11]: 1 + xb'
|
|
186
209
|
|
|
187
|
-
>>>
|
|
210
|
+
>>> Base36VarMapper(Variable).apply(pl.DataFrame({VAR_KEY: []}))
|
|
188
211
|
shape: (0, 1)
|
|
189
212
|
┌───────────────┐
|
|
190
213
|
│ __variable_id │
|
|
@@ -194,12 +217,21 @@ class Base62VarMapper(Base62Mapper):
|
|
|
194
217
|
└───────────────┘
|
|
195
218
|
"""
|
|
196
219
|
|
|
220
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
221
|
+
super().__init__(*args, **kwargs)
|
|
222
|
+
df = pl.DataFrame(
|
|
223
|
+
{self._ID_COL: [CONST_TERM]},
|
|
224
|
+
schema={self._ID_COL: pl.UInt32},
|
|
225
|
+
)
|
|
226
|
+
df = self.apply(df, to_col=Mapper.NAME_COL)
|
|
227
|
+
self._extend_registry(df)
|
|
228
|
+
|
|
197
229
|
@property
|
|
198
230
|
def _prefix(self) -> "str":
|
|
199
231
|
return "x"
|
|
200
232
|
|
|
201
233
|
|
|
202
|
-
class
|
|
234
|
+
class Base36ConstMapper(Base36Mapper):
|
|
203
235
|
|
|
204
236
|
@property
|
|
205
237
|
def _prefix(self) -> "str":
|
pyoframe/model.py
CHANGED
|
@@ -1,29 +1,60 @@
|
|
|
1
|
-
from typing import Any, Iterable, List, Optional
|
|
2
|
-
from pyoframe.constants import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
from typing import Any, Iterable, List, Optional, Union
|
|
2
|
+
from pyoframe.constants import (
|
|
3
|
+
ObjSense,
|
|
4
|
+
VType,
|
|
5
|
+
Config,
|
|
6
|
+
Result,
|
|
7
|
+
PyoframeError,
|
|
8
|
+
ObjSenseValue,
|
|
9
|
+
)
|
|
10
|
+
from pyoframe.io_mappers import NamedVariableMapper, IOMappers
|
|
11
|
+
from pyoframe.model_element import ModelElement, ModelElementWithId
|
|
12
|
+
from pyoframe.core import Constraint
|
|
7
13
|
from pyoframe.objective import Objective
|
|
8
|
-
from pyoframe.
|
|
14
|
+
from pyoframe.user_defined import Container, AttrContainerMixin
|
|
15
|
+
from pyoframe.core import Variable
|
|
9
16
|
from pyoframe.io import to_file
|
|
10
|
-
from pyoframe.solvers import solve
|
|
17
|
+
from pyoframe.solvers import solve, Solver
|
|
18
|
+
import polars as pl
|
|
19
|
+
import pandas as pd
|
|
11
20
|
|
|
12
21
|
|
|
13
|
-
class Model:
|
|
22
|
+
class Model(AttrContainerMixin):
|
|
14
23
|
"""
|
|
15
24
|
Represents a mathematical optimization model. Add variables, constraints, and an objective to the model by setting attributes.
|
|
16
25
|
"""
|
|
17
26
|
|
|
18
|
-
|
|
27
|
+
_reserved_attributes = [
|
|
28
|
+
"_variables",
|
|
29
|
+
"_constraints",
|
|
30
|
+
"_objective",
|
|
31
|
+
"var_map",
|
|
32
|
+
"io_mappers",
|
|
33
|
+
"name",
|
|
34
|
+
"solver",
|
|
35
|
+
"solver_model",
|
|
36
|
+
"params",
|
|
37
|
+
"result",
|
|
38
|
+
"attr",
|
|
39
|
+
"sense",
|
|
40
|
+
"objective",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
def __init__(self, min_or_max: Union[ObjSense, ObjSenseValue], name=None, **kwargs):
|
|
44
|
+
super().__init__(**kwargs)
|
|
19
45
|
self._variables: List[Variable] = []
|
|
20
46
|
self._constraints: List[Constraint] = []
|
|
47
|
+
self.sense = ObjSense(min_or_max)
|
|
21
48
|
self._objective: Optional[Objective] = None
|
|
22
49
|
self.var_map = (
|
|
23
|
-
|
|
50
|
+
NamedVariableMapper(Variable) if Config.print_uses_variable_names else None
|
|
24
51
|
)
|
|
25
52
|
self.io_mappers: Optional[IOMappers] = None
|
|
26
53
|
self.name = name
|
|
54
|
+
self.solver: Optional[Solver] = None
|
|
55
|
+
self.solver_model: Optional[Any] = None
|
|
56
|
+
self.params = Container()
|
|
57
|
+
self.result: Optional[Result] = None
|
|
27
58
|
|
|
28
59
|
@property
|
|
29
60
|
def variables(self) -> List[Variable]:
|
|
@@ -45,44 +76,37 @@ class Model:
|
|
|
45
76
|
def objective(self):
|
|
46
77
|
return self._objective
|
|
47
78
|
|
|
48
|
-
@
|
|
49
|
-
def
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@property
|
|
54
|
-
def minimize(self):
|
|
55
|
-
assert self.objective is not None and self.objective.sense == ObjSense.MIN
|
|
56
|
-
return self.objective
|
|
79
|
+
@objective.setter
|
|
80
|
+
def objective(self, value):
|
|
81
|
+
value = Objective(value)
|
|
82
|
+
self._objective = value
|
|
83
|
+
value.on_add_to_model(self, "objective")
|
|
57
84
|
|
|
58
85
|
def __setattr__(self, __name: str, __value: Any) -> None:
|
|
59
|
-
if __name in (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
assert self.objective is None, "Cannot create more than one objective."
|
|
78
|
-
self._objective = __value
|
|
86
|
+
if __name not in Model._reserved_attributes and not isinstance(
|
|
87
|
+
__value, (ModelElement, pl.DataFrame, pd.DataFrame)
|
|
88
|
+
):
|
|
89
|
+
raise PyoframeError(
|
|
90
|
+
f"Cannot set attribute '{__name}' on the model because it isn't of type ModelElement (e.g. Variable, Constraint, ...)"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
isinstance(__value, ModelElement)
|
|
95
|
+
and __name not in Model._reserved_attributes
|
|
96
|
+
):
|
|
97
|
+
if isinstance(__value, ModelElementWithId):
|
|
98
|
+
assert not hasattr(
|
|
99
|
+
self, __name
|
|
100
|
+
), f"Cannot create {__name} since it was already created."
|
|
101
|
+
|
|
102
|
+
__value.on_add_to_model(self, __name)
|
|
103
|
+
|
|
79
104
|
if isinstance(__value, Variable):
|
|
80
105
|
self._variables.append(__value)
|
|
81
106
|
if self.var_map is not None:
|
|
82
107
|
self.var_map.add(__value)
|
|
83
108
|
elif isinstance(__value, Constraint):
|
|
84
109
|
self._constraints.append(__value)
|
|
85
|
-
|
|
86
110
|
return super().__setattr__(__name, __value)
|
|
87
111
|
|
|
88
112
|
def __repr__(self) -> str:
|