pyoframe 1.0.0a0__py3-none-any.whl → 1.0.1__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/_arithmetic.py +176 -174
- pyoframe/_constants.py +94 -47
- pyoframe/_core.py +225 -148
- pyoframe/_model.py +46 -26
- pyoframe/_model_element.py +32 -18
- pyoframe/_monkey_patch.py +15 -13
- pyoframe/_objective.py +2 -4
- pyoframe/_utils.py +8 -9
- pyoframe/_version.py +2 -2
- {pyoframe-1.0.0a0.dist-info → pyoframe-1.0.1.dist-info}/METADATA +11 -12
- pyoframe-1.0.1.dist-info/RECORD +15 -0
- pyoframe-1.0.0a0.dist-info/RECORD +0 -15
- {pyoframe-1.0.0a0.dist-info → pyoframe-1.0.1.dist-info}/WHEEL +0 -0
- {pyoframe-1.0.0a0.dist-info → pyoframe-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {pyoframe-1.0.0a0.dist-info → pyoframe-1.0.1.dist-info}/top_level.txt +0 -0
pyoframe/_model.py
CHANGED
|
@@ -20,8 +20,8 @@ from pyoframe._constants import (
|
|
|
20
20
|
VType,
|
|
21
21
|
_Solver,
|
|
22
22
|
)
|
|
23
|
-
from pyoframe._core import Constraint,
|
|
24
|
-
from pyoframe._model_element import
|
|
23
|
+
from pyoframe._core import Constraint, Operable, Variable
|
|
24
|
+
from pyoframe._model_element import BaseBlock
|
|
25
25
|
from pyoframe._objective import Objective
|
|
26
26
|
from pyoframe._utils import Container, NamedVariableMapper, for_solvers, get_obj_repr
|
|
27
27
|
|
|
@@ -76,7 +76,7 @@ class Model:
|
|
|
76
76
|
"_var_map",
|
|
77
77
|
"name",
|
|
78
78
|
"solver",
|
|
79
|
-
"
|
|
79
|
+
"_poi",
|
|
80
80
|
"_params",
|
|
81
81
|
"params",
|
|
82
82
|
"_attr",
|
|
@@ -99,21 +99,27 @@ class Model:
|
|
|
99
99
|
print_uses_variable_names: bool = True,
|
|
100
100
|
sense: ObjSense | ObjSenseValue | None = None,
|
|
101
101
|
):
|
|
102
|
-
self.
|
|
102
|
+
self._poi, self.solver = Model._create_poi_model(solver, solver_env)
|
|
103
103
|
self.solver_name: str = self.solver.name
|
|
104
104
|
self._variables: list[Variable] = []
|
|
105
105
|
self._constraints: list[Constraint] = []
|
|
106
106
|
self.sense: ObjSense | None = ObjSense(sense) if sense is not None else None
|
|
107
107
|
self._objective: Objective | None = None
|
|
108
|
-
self._var_map = (
|
|
109
|
-
NamedVariableMapper(Variable) if print_uses_variable_names else None
|
|
110
|
-
)
|
|
108
|
+
self._var_map = NamedVariableMapper() if print_uses_variable_names else None
|
|
111
109
|
self.name: str | None = name
|
|
112
110
|
|
|
113
111
|
self._params = Container(self._set_param, self._get_param)
|
|
114
112
|
self._attr = Container(self._set_attr, self._get_attr)
|
|
115
113
|
self._solver_uses_variable_names = solver_uses_variable_names
|
|
116
114
|
|
|
115
|
+
@property
|
|
116
|
+
def poi(self):
|
|
117
|
+
"""The underlying PyOptInterface model used to interact with the solver.
|
|
118
|
+
|
|
119
|
+
Modifying the underlying model directly is not recommended and may lead to unexpected behaviors.
|
|
120
|
+
"""
|
|
121
|
+
return self._poi
|
|
122
|
+
|
|
117
123
|
@property
|
|
118
124
|
def solver_uses_variable_names(self):
|
|
119
125
|
"""Whether to pass human-readable variable names to the solver."""
|
|
@@ -125,8 +131,9 @@ class Model:
|
|
|
125
131
|
|
|
126
132
|
Several model attributes are common across all solvers making it easy to switch between solvers (see supported attributes for
|
|
127
133
|
[Gurobi](https://metab0t.github.io/PyOptInterface/gurobi.html#supported-model-attribute),
|
|
128
|
-
[HiGHS](https://metab0t.github.io/PyOptInterface/highs.html),
|
|
129
|
-
[Ipopt](https://metab0t.github.io/PyOptInterface/ipopt.html))
|
|
134
|
+
[HiGHS](https://metab0t.github.io/PyOptInterface/highs.html),
|
|
135
|
+
[Ipopt](https://metab0t.github.io/PyOptInterface/ipopt.html)), and
|
|
136
|
+
[COPT](https://metab0t.github.io/PyOptInterface/copt.html).
|
|
130
137
|
|
|
131
138
|
We additionally support all of [Gurobi's attributes](https://docs.gurobi.com/projects/optimizer/en/current/reference/attributes.html#sec:Attributes) when using Gurobi.
|
|
132
139
|
|
|
@@ -161,7 +168,8 @@ class Model:
|
|
|
161
168
|
See the list of available parameters for
|
|
162
169
|
[Gurobi](https://docs.gurobi.com/projects/optimizer/en/current/reference/parameters.html#sec:Parameters),
|
|
163
170
|
[HiGHS](https://ergo-code.github.io/HiGHS/stable/options/definitions/),
|
|
164
|
-
|
|
171
|
+
[Ipopt](https://coin-or.github.io/Ipopt/OPTIONS.html),
|
|
172
|
+
and [COPT](https://guide.coap.online/copt/en-doc/parameter.html).
|
|
165
173
|
|
|
166
174
|
Examples:
|
|
167
175
|
For example, if you'd like to use Gurobi's barrier method, you can set the `Method` parameter:
|
|
@@ -237,6 +245,18 @@ class Model:
|
|
|
237
245
|
"Could not find the Ipopt solver. Are you sure you've properly installed it and added it to your PATH?"
|
|
238
246
|
) from e
|
|
239
247
|
raise e
|
|
248
|
+
elif solver.name == "copt":
|
|
249
|
+
from pyoptinterface import copt
|
|
250
|
+
|
|
251
|
+
if solver_env is None:
|
|
252
|
+
env = copt.Env()
|
|
253
|
+
else:
|
|
254
|
+
# COPT uses EnvConfig for configuration
|
|
255
|
+
env_config = copt.EnvConfig()
|
|
256
|
+
for key, value in solver_env.items():
|
|
257
|
+
env_config.set(key, value)
|
|
258
|
+
env = copt.Env(env_config)
|
|
259
|
+
model = copt.Model(env)
|
|
240
260
|
else:
|
|
241
261
|
raise ValueError(
|
|
242
262
|
f"Solver {solver} not recognized or supported."
|
|
@@ -326,7 +346,7 @@ class Model:
|
|
|
326
346
|
return self._objective
|
|
327
347
|
|
|
328
348
|
@objective.setter
|
|
329
|
-
def objective(self, value:
|
|
349
|
+
def objective(self, value: Operable):
|
|
330
350
|
if self.has_objective and (
|
|
331
351
|
not isinstance(value, Objective) or not value._constructive
|
|
332
352
|
):
|
|
@@ -344,7 +364,7 @@ class Model:
|
|
|
344
364
|
return self._objective
|
|
345
365
|
|
|
346
366
|
@minimize.setter
|
|
347
|
-
def minimize(self, value:
|
|
367
|
+
def minimize(self, value: Operable):
|
|
348
368
|
if self.sense is None:
|
|
349
369
|
self.sense = ObjSense.MIN
|
|
350
370
|
if self.sense != ObjSense.MIN:
|
|
@@ -359,7 +379,7 @@ class Model:
|
|
|
359
379
|
return self._objective
|
|
360
380
|
|
|
361
381
|
@maximize.setter
|
|
362
|
-
def maximize(self, value:
|
|
382
|
+
def maximize(self, value: Operable):
|
|
363
383
|
if self.sense is None:
|
|
364
384
|
self.sense = ObjSense.MAX
|
|
365
385
|
if self.sense != ObjSense.MAX:
|
|
@@ -368,17 +388,14 @@ class Model:
|
|
|
368
388
|
|
|
369
389
|
def __setattr__(self, __name: str, __value: Any) -> None:
|
|
370
390
|
if __name not in Model._reserved_attributes and not isinstance(
|
|
371
|
-
__value, (
|
|
391
|
+
__value, (BaseBlock, pl.DataFrame, pd.DataFrame)
|
|
372
392
|
):
|
|
373
393
|
raise PyoframeError(
|
|
374
|
-
f"Cannot set attribute '{__name}' on the model because it isn't of
|
|
394
|
+
f"Cannot set attribute '{__name}' on the model because it isn't a subtype of BaseBlock (e.g. Variable, Constraint, ...)"
|
|
375
395
|
)
|
|
376
396
|
|
|
377
|
-
if (
|
|
378
|
-
|
|
379
|
-
and __name not in Model._reserved_attributes
|
|
380
|
-
):
|
|
381
|
-
if isinstance(__value, ModelElementWithId):
|
|
397
|
+
if isinstance(__value, BaseBlock) and __name not in Model._reserved_attributes:
|
|
398
|
+
if __value._get_id_column_name() is not None:
|
|
382
399
|
assert not hasattr(self, __name), (
|
|
383
400
|
f"Cannot create {__name} since it was already created."
|
|
384
401
|
)
|
|
@@ -431,7 +448,10 @@ class Model:
|
|
|
431
448
|
"""
|
|
432
449
|
if not self.solver.supports_write:
|
|
433
450
|
raise NotImplementedError(f"{self.solver.name} does not support .write()")
|
|
434
|
-
if
|
|
451
|
+
if (
|
|
452
|
+
not self.solver_uses_variable_names
|
|
453
|
+
and self.solver.accelerate_with_repeat_names
|
|
454
|
+
):
|
|
435
455
|
raise ValueError(
|
|
436
456
|
f"{self.solver.name} requires solver_uses_variable_names=True to use .write()"
|
|
437
457
|
)
|
|
@@ -488,10 +508,10 @@ class Model:
|
|
|
488
508
|
|
|
489
509
|
@for_solvers("gurobi", "copt")
|
|
490
510
|
def compute_IIS(self):
|
|
491
|
-
"""Gurobi only: Computes the Irreducible Infeasible Set (IIS) of the model.
|
|
511
|
+
"""Gurobi and COPT only: Computes the Irreducible Infeasible Set (IIS) of the model.
|
|
492
512
|
|
|
493
|
-
!!! warning "Gurobi only"
|
|
494
|
-
This method only works with the Gurobi solver. Open an issue if you'd like to see support for other solvers.
|
|
513
|
+
!!! warning "Gurobi and COPT only"
|
|
514
|
+
This method only works with the Gurobi and COPT solver. Open an issue if you'd like to see support for other solvers.
|
|
495
515
|
|
|
496
516
|
Examples:
|
|
497
517
|
>>> m = pf.Model("gurobi")
|
|
@@ -548,7 +568,7 @@ class Model:
|
|
|
548
568
|
self.poi.set_raw_parameter(name, value)
|
|
549
569
|
except KeyError as e:
|
|
550
570
|
raise KeyError(
|
|
551
|
-
f"Unknown parameter: '{name}'. See https://bravos-power.github.io/pyoframe/learn/getting-started/solver-access/ for a list of valid parameters."
|
|
571
|
+
f"Unknown parameter: '{name}'. See https://bravos-power.github.io/pyoframe/latest/learn/getting-started/solver-access/ for a list of valid parameters."
|
|
552
572
|
) from e
|
|
553
573
|
|
|
554
574
|
def _get_param(self, name):
|
|
@@ -556,7 +576,7 @@ class Model:
|
|
|
556
576
|
return self.poi.get_raw_parameter(name)
|
|
557
577
|
except KeyError as e:
|
|
558
578
|
raise KeyError(
|
|
559
|
-
f"Unknown parameter: '{name}'. See https://bravos-power.github.io/pyoframe/learn/getting-started/solver-access/ for a list of valid parameters."
|
|
579
|
+
f"Unknown parameter: '{name}'. See https://bravos-power.github.io/pyoframe/latest/learn/getting-started/solver-access/ for a list of valid parameters."
|
|
560
580
|
) from e
|
|
561
581
|
|
|
562
582
|
def _set_attr(self, name, value):
|
pyoframe/_model_element.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from abc import ABC
|
|
5
|
+
from abc import ABC
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
8
|
import polars as pl
|
|
@@ -10,17 +10,17 @@ import polars as pl
|
|
|
10
10
|
from pyoframe._arithmetic import _get_dimensions
|
|
11
11
|
from pyoframe._constants import (
|
|
12
12
|
COEF_KEY,
|
|
13
|
-
KEY_TYPE,
|
|
14
13
|
QUAD_VAR_KEY,
|
|
15
14
|
RESERVED_COL_KEYS,
|
|
16
15
|
VAR_KEY,
|
|
16
|
+
Config,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
if TYPE_CHECKING: # pragma: no cover
|
|
20
20
|
from pyoframe import Model
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
class
|
|
23
|
+
class BaseBlock(ABC):
|
|
24
24
|
"""The base class for elements of a Model such as [][pyoframe.Variable] and [][pyoframe.Constraint]."""
|
|
25
25
|
|
|
26
26
|
def __init__(self, data: pl.DataFrame, name="unnamed") -> None:
|
|
@@ -41,9 +41,9 @@ class ModelElement(ABC):
|
|
|
41
41
|
if COEF_KEY in data.columns:
|
|
42
42
|
data = data.cast({COEF_KEY: pl.Float64})
|
|
43
43
|
if VAR_KEY in data.columns:
|
|
44
|
-
data = data.cast({VAR_KEY:
|
|
44
|
+
data = data.cast({VAR_KEY: Config.id_dtype})
|
|
45
45
|
if QUAD_VAR_KEY in data.columns:
|
|
46
|
-
data = data.cast({QUAD_VAR_KEY:
|
|
46
|
+
data = data.cast({QUAD_VAR_KEY: Config.id_dtype})
|
|
47
47
|
|
|
48
48
|
self._data = data
|
|
49
49
|
self._model: Model | None = None
|
|
@@ -77,6 +77,26 @@ class ModelElement(ABC):
|
|
|
77
77
|
"""
|
|
78
78
|
return _get_dimensions(self.data)
|
|
79
79
|
|
|
80
|
+
@property
|
|
81
|
+
def dimensionless(self) -> bool:
|
|
82
|
+
"""Whether the object has no dimensions.
|
|
83
|
+
|
|
84
|
+
Examples:
|
|
85
|
+
A variable with no dimensions
|
|
86
|
+
>>> pf.Variable().dimensionless
|
|
87
|
+
True
|
|
88
|
+
|
|
89
|
+
A variable with dimensions of "hour" and "city"
|
|
90
|
+
>>> pf.Variable(
|
|
91
|
+
... [
|
|
92
|
+
... {"hour": ["00:00", "06:00", "12:00", "18:00"]},
|
|
93
|
+
... {"city": ["Toronto", "Berlin", "Paris"]},
|
|
94
|
+
... ]
|
|
95
|
+
... ).dimensionless
|
|
96
|
+
False
|
|
97
|
+
"""
|
|
98
|
+
return self.dimensions is None
|
|
99
|
+
|
|
80
100
|
@property
|
|
81
101
|
def _dimensions_unsafe(self) -> list[str]:
|
|
82
102
|
"""Same as `dimensions` but returns an empty list if there are no dimensions instead of `None`.
|
|
@@ -90,7 +110,7 @@ class ModelElement(ABC):
|
|
|
90
110
|
|
|
91
111
|
@property
|
|
92
112
|
def shape(self) -> dict[str, int]:
|
|
93
|
-
"""The number of
|
|
113
|
+
"""The number of distinct labels in each dimension.
|
|
94
114
|
|
|
95
115
|
Examples:
|
|
96
116
|
A variable with no dimensions
|
|
@@ -151,17 +171,11 @@ class ModelElement(ABC):
|
|
|
151
171
|
return 1
|
|
152
172
|
return self.data.select(dims).n_unique()
|
|
153
173
|
|
|
154
|
-
|
|
155
|
-
class ModelElementWithId(ModelElement):
|
|
156
|
-
"""Extends ModelElement with a method that assigns a unique ID to each row in a DataFrame.
|
|
157
|
-
|
|
158
|
-
IDs start at 1 and go up consecutively. No zero ID is assigned since it is reserved for the constant variable term.
|
|
159
|
-
IDs are only unique for the subclass since different subclasses have different counters.
|
|
160
|
-
"""
|
|
161
|
-
|
|
162
174
|
@property
|
|
163
175
|
def _has_ids(self) -> bool:
|
|
164
|
-
|
|
176
|
+
id_col = self._get_id_column_name()
|
|
177
|
+
assert id_col is not None, "Cannot check for IDs if no ID column is defined."
|
|
178
|
+
return id_col in self.data.columns
|
|
165
179
|
|
|
166
180
|
def _assert_has_ids(self):
|
|
167
181
|
if not self._has_ids:
|
|
@@ -170,6 +184,6 @@ class ModelElementWithId(ModelElement):
|
|
|
170
184
|
)
|
|
171
185
|
|
|
172
186
|
@classmethod
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
187
|
+
def _get_id_column_name(cls) -> str | None:
|
|
188
|
+
"""Subclasses should override to indicate that `data` contains an ID column."""
|
|
189
|
+
return None
|
pyoframe/_monkey_patch.py
CHANGED
|
@@ -6,14 +6,14 @@ import pandas as pd
|
|
|
6
6
|
import polars as pl
|
|
7
7
|
|
|
8
8
|
from pyoframe._constants import COEF_KEY, CONST_TERM, VAR_KEY
|
|
9
|
-
from pyoframe._core import
|
|
9
|
+
from pyoframe._core import BaseOperableBlock, Expression
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def _patch_class(cls):
|
|
13
13
|
def _patch_method(func):
|
|
14
14
|
@wraps(func)
|
|
15
15
|
def wrapper(self, other):
|
|
16
|
-
if isinstance(other,
|
|
16
|
+
if isinstance(other, BaseOperableBlock):
|
|
17
17
|
return NotImplemented
|
|
18
18
|
return func(self, other)
|
|
19
19
|
|
|
@@ -24,13 +24,15 @@ def _patch_class(cls):
|
|
|
24
24
|
cls.__sub__ = _patch_method(cls.__sub__)
|
|
25
25
|
cls.__le__ = _patch_method(cls.__le__)
|
|
26
26
|
cls.__ge__ = _patch_method(cls.__ge__)
|
|
27
|
+
cls.__lt__ = _patch_method(cls.__lt__)
|
|
28
|
+
cls.__gt__ = _patch_method(cls.__gt__)
|
|
27
29
|
cls.__contains__ = _patch_method(cls.__contains__)
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
def polars_df_to_expr(self: pl.DataFrame) -> Expression:
|
|
31
33
|
"""Converts a [polars](https://pola.rs/) `DataFrame` to a Pyoframe [Expression][pyoframe.Expression] by using the last column for values and the previous columns as dimensions.
|
|
32
34
|
|
|
33
|
-
See [Special Functions](
|
|
35
|
+
See [Special Functions](../../learn/concepts/special-functions.md#dataframeto_expr) for more details.
|
|
34
36
|
|
|
35
37
|
Examples:
|
|
36
38
|
>>> import polars as pl
|
|
@@ -60,21 +62,21 @@ def pandas_df_to_expr(self: pd.DataFrame) -> Expression:
|
|
|
60
62
|
return polars_df_to_expr(pl.from_pandas(self))
|
|
61
63
|
|
|
62
64
|
|
|
63
|
-
def
|
|
64
|
-
"""
|
|
65
|
+
def pandas_series_to_expr(self: pd.Series) -> Expression:
|
|
66
|
+
"""Converts a [pandas](https://pandas.pydata.org/) `Series` to a Pyoframe [Expression][pyoframe.Expression], using the index for labels.
|
|
67
|
+
|
|
68
|
+
See [Special Functions](../../learn/concepts/special-functions.md#dataframeto_expr) for more details.
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
are not supported (i.e. `return NotImplemented`). This leads Python to try the reverse operation (e.g. `__radd__`)
|
|
68
|
-
which is supported by the `Expressionable` class.
|
|
69
|
-
2) Adds a `to_expr` method to DataFrame/Series that allows them to be converted to an `Expression` object.
|
|
70
|
-
Series become DataFrames and DataFrames become expressions where everything but the last column are treated as dimensions.
|
|
70
|
+
Note that no equivalent method exists for Polars Series, as Polars does not support indexes.
|
|
71
71
|
"""
|
|
72
|
+
return pandas_df_to_expr(self.to_frame().reset_index())
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def patch_dataframe_libraries():
|
|
72
76
|
_patch_class(pd.DataFrame)
|
|
73
77
|
_patch_class(pd.Series)
|
|
74
78
|
_patch_class(pl.DataFrame)
|
|
75
79
|
_patch_class(pl.Series)
|
|
76
80
|
pl.DataFrame.to_expr = polars_df_to_expr
|
|
77
81
|
pd.DataFrame.to_expr = pandas_df_to_expr
|
|
78
|
-
|
|
79
|
-
pl.Series.to_expr = lambda self: self.to_frame().to_expr()
|
|
80
|
-
pd.Series.to_expr = lambda self: self.to_frame().reset_index().to_expr()
|
|
82
|
+
pd.Series.to_expr = pandas_series_to_expr
|
pyoframe/_objective.py
CHANGED
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import pyoptinterface as poi
|
|
6
6
|
|
|
7
7
|
from pyoframe._constants import ObjSense
|
|
8
|
-
from pyoframe._core import Expression,
|
|
8
|
+
from pyoframe._core import Expression, Operable
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
# TODO don't subclass Expression to avoid a bunch of unnecessary functions being available.
|
|
@@ -62,9 +62,7 @@ class Objective(Expression):
|
|
|
62
62
|
ValueError: An objective already exists. Use += or -= to modify it.
|
|
63
63
|
"""
|
|
64
64
|
|
|
65
|
-
def __init__(
|
|
66
|
-
self, expr: SupportsToExpr | int | float, _constructive: bool = False
|
|
67
|
-
) -> None:
|
|
65
|
+
def __init__(self, expr: Operable, _constructive: bool = False) -> None:
|
|
68
66
|
self._constructive = _constructive
|
|
69
67
|
if isinstance(expr, (int, float)):
|
|
70
68
|
expr = Expression.constant(expr)
|
pyoframe/_utils.py
CHANGED
|
@@ -21,9 +21,8 @@ from pyoframe._constants import (
|
|
|
21
21
|
)
|
|
22
22
|
|
|
23
23
|
if TYPE_CHECKING: # pragma: no cover
|
|
24
|
-
from pyoframe._core import
|
|
24
|
+
from pyoframe._core import BaseOperableBlock
|
|
25
25
|
from pyoframe._model import Variable
|
|
26
|
-
from pyoframe._model_element import ModelElementWithId
|
|
27
26
|
|
|
28
27
|
if sys.version_info >= (3, 10):
|
|
29
28
|
pairwise = itertools.pairwise
|
|
@@ -245,7 +244,7 @@ def cast_coef_to_string(
|
|
|
245
244
|
return df
|
|
246
245
|
|
|
247
246
|
|
|
248
|
-
def unwrap_single_values(func):
|
|
247
|
+
def unwrap_single_values(func) -> pl.DataFrame | Any:
|
|
249
248
|
"""Returns the DataFrame unless it is a single value in which case return the value."""
|
|
250
249
|
|
|
251
250
|
@wraps(func)
|
|
@@ -310,16 +309,16 @@ class NamedVariableMapper:
|
|
|
310
309
|
CONST_TERM_NAME = "_ONE"
|
|
311
310
|
NAME_COL = "__name"
|
|
312
311
|
|
|
313
|
-
def __init__(self
|
|
312
|
+
def __init__(self) -> None:
|
|
314
313
|
self._ID_COL = VAR_KEY
|
|
315
314
|
self.mapping_registry = pl.DataFrame(
|
|
316
315
|
{self._ID_COL: [], self.NAME_COL: []},
|
|
317
|
-
schema={self._ID_COL:
|
|
316
|
+
schema={self._ID_COL: Config.id_dtype, self.NAME_COL: pl.String},
|
|
318
317
|
)
|
|
319
318
|
self._extend_registry(
|
|
320
319
|
pl.DataFrame(
|
|
321
320
|
{self._ID_COL: [CONST_TERM], self.NAME_COL: [self.CONST_TERM_NAME]},
|
|
322
|
-
schema={self._ID_COL:
|
|
321
|
+
schema={self._ID_COL: Config.id_dtype, self.NAME_COL: pl.String},
|
|
323
322
|
)
|
|
324
323
|
)
|
|
325
324
|
|
|
@@ -375,15 +374,15 @@ def for_solvers(*solvers: str):
|
|
|
375
374
|
return decorator
|
|
376
375
|
|
|
377
376
|
|
|
378
|
-
# TODO: rename and change to return_expr once Set is split away from
|
|
379
|
-
def return_new(func: Callable[..., pl.DataFrame]) -> Callable[...,
|
|
377
|
+
# TODO: rename and change to return_expr once Set is split away from BaseOperableBlock
|
|
378
|
+
def return_new(func: Callable[..., pl.DataFrame]) -> Callable[..., BaseOperableBlock]:
|
|
380
379
|
"""Decorator that upcasts the returned DataFrame to an Expression.
|
|
381
380
|
|
|
382
381
|
Requires the first argument (self) to support self._new().
|
|
383
382
|
"""
|
|
384
383
|
|
|
385
384
|
@wraps(func)
|
|
386
|
-
def wrapper(self:
|
|
385
|
+
def wrapper(self: BaseOperableBlock, *args, **kwargs):
|
|
387
386
|
result = func(self, *args, **kwargs)
|
|
388
387
|
return self._new(result, name=f"{self.name}.{func.__name__}(…)")
|
|
389
388
|
|
pyoframe/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '1.0.
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 0,
|
|
31
|
+
__version__ = version = '1.0.1'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 0, 1)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyoframe
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: Blazing fast linear program interface
|
|
5
5
|
Author-email: Bravos Power <dev@bravospower.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
|
-
Project-URL: Homepage, https://bravos-power.github.io/pyoframe/
|
|
8
|
-
Project-URL: documentation, https://bravos-power.github.io/pyoframe/
|
|
7
|
+
Project-URL: Homepage, https://bravos-power.github.io/pyoframe/latest
|
|
8
|
+
Project-URL: documentation, https://bravos-power.github.io/pyoframe/latest
|
|
9
9
|
Project-URL: repository, https://github.com/Bravos-Power/pyoframe/
|
|
10
10
|
Project-URL: Issues, https://github.com/Bravos-Power/pyoframe/issues
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -16,12 +16,11 @@ Requires-Python: >=3.9
|
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
18
|
Requires-Dist: polars~=1.0
|
|
19
|
-
Requires-Dist: numpy
|
|
20
19
|
Requires-Dist: pyarrow
|
|
21
|
-
Requires-Dist: pandas
|
|
22
|
-
Requires-Dist: pyoptinterface
|
|
20
|
+
Requires-Dist: pandas<3
|
|
21
|
+
Requires-Dist: pyoptinterface==0.5.1
|
|
23
22
|
Provides-Extra: highs
|
|
24
|
-
Requires-Dist: highsbox; extra == "highs"
|
|
23
|
+
Requires-Dist: highsbox<=1.11.0; extra == "highs"
|
|
25
24
|
Provides-Extra: ipopt
|
|
26
25
|
Requires-Dist: pyoptinterface[nlp]; extra == "ipopt"
|
|
27
26
|
Requires-Dist: llvmlite<=0.44.0; extra == "ipopt"
|
|
@@ -35,9 +34,9 @@ Requires-Dist: pre-commit==4.3.0; extra == "dev"
|
|
|
35
34
|
Requires-Dist: gurobipy==12.0.3; extra == "dev"
|
|
36
35
|
Requires-Dist: coverage==7.10.6; extra == "dev"
|
|
37
36
|
Requires-Dist: ipykernel==6.30.1; extra == "dev"
|
|
38
|
-
Requires-Dist: highsbox; extra == "dev"
|
|
37
|
+
Requires-Dist: highsbox<=1.11.0; extra == "dev"
|
|
39
38
|
Requires-Dist: pyoptinterface[nlp]; extra == "dev"
|
|
40
|
-
Requires-Dist:
|
|
39
|
+
Requires-Dist: numpy; extra == "dev"
|
|
41
40
|
Provides-Extra: docs
|
|
42
41
|
Requires-Dist: mkdocs-material~=9.6.18; extra == "docs"
|
|
43
42
|
Requires-Dist: mkdocstrings[python]~=0.30.0; extra == "docs"
|
|
@@ -57,7 +56,7 @@ Dynamic: license-file
|
|
|
57
56
|
|
|
58
57
|
[](https://codecov.io/gh/Bravos-Power/pyoframe)
|
|
59
58
|
[](https://github.com/Bravos-Power/pyoframe/actions/workflows/ci.yml)
|
|
60
|
-
[](https://Bravos-Power.github.io/pyoframe/reference/)
|
|
59
|
+
[](https://Bravos-Power.github.io/pyoframe/latest/reference/)
|
|
61
60
|
[](https://opensource.org/licenses/MIT)
|
|
62
61
|
[](https://github.com/Bravos-Power/pyoframe/issues?q=is%3Aopen+is%3Aissue+no%3Alabel)
|
|
63
62
|
[](https://github.com/Bravos-Power/pyoframe/issues?q=is%3Aopen+is%3Aissue+label%3Abug)
|
|
@@ -65,9 +64,9 @@ Dynamic: license-file
|
|
|
65
64
|
|
|
66
65
|
A library to rapidly and memory-efficiently formulate large and sparse optimization models using Pandas or Polars DataFrames.
|
|
67
66
|
|
|
68
|
-
## **[Documentation](https://bravos-power.github.io/pyoframe/)**
|
|
67
|
+
## **[Documentation](https://bravos-power.github.io/pyoframe/latest/)**
|
|
69
68
|
|
|
70
|
-
[Read the documentation](https://bravos-power.github.io/pyoframe/) to get started or to learn how to [contribute](https://bravos-power.github.io/pyoframe/contribute/index.md).
|
|
69
|
+
[Read the documentation](https://bravos-power.github.io/pyoframe/latest/) to get started or to learn how to [contribute](https://bravos-power.github.io/pyoframe/latest/contribute/index.md).
|
|
71
70
|
|
|
72
71
|
|
|
73
72
|
## Acknowledgments
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
pyoframe/__init__.py,sha256=Nlql3FYed7bXWumvUeMd3rjnoL4l8XC5orO4uxWrDAc,839
|
|
2
|
+
pyoframe/_arithmetic.py,sha256=9V3N2Yq7Ib13UfTQnINxa9oS7786f5DnRsrAPcFlYYE,20558
|
|
3
|
+
pyoframe/_constants.py,sha256=n80so80usutTJpeDlDXkHRE-_dpPD1LxF2plscXhbwQ,17925
|
|
4
|
+
pyoframe/_core.py,sha256=NXH_ze1iN61vk38Fj6Ire0uqKUJRSDUlD6PVc8mTHrQ,116975
|
|
5
|
+
pyoframe/_model.py,sha256=MtA9gleQoUAqO2dxhCFZ8GOZvyyJB_PIYBzvQ0m8enc,22466
|
|
6
|
+
pyoframe/_model_element.py,sha256=oQ7nykJ5XEzJ6Klq3lT6ZwQvDrxY_wgZYVaN7pgyZOs,6149
|
|
7
|
+
pyoframe/_monkey_patch.py,sha256=Y2zXN5MpqDeAWELddyaFQNam57fehSXHiza1PFaZ-QY,3128
|
|
8
|
+
pyoframe/_objective.py,sha256=yIHoaBLsjGCKzIB6RQErV3vzE2U5DGORlhifRStB_Mc,4335
|
|
9
|
+
pyoframe/_utils.py,sha256=5yy-5DOWCW7q3QzPv9tKquLUQJtNdpJJilEqxAq7TN8,12518
|
|
10
|
+
pyoframe/_version.py,sha256=JvmBpae6cHui8lSCsCcZQAxzawN2NERHGsr-rIUeJMo,704
|
|
11
|
+
pyoframe-1.0.1.dist-info/licenses/LICENSE,sha256=u_Spw4ynlwTMRZeCX-uacv_hBU547pBygiA6d2ONNV4,1074
|
|
12
|
+
pyoframe-1.0.1.dist-info/METADATA,sha256=UtnvQdR7vJQvLm8p0RvgwdGfR7yCISswH_V4KuihBr4,4060
|
|
13
|
+
pyoframe-1.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
pyoframe-1.0.1.dist-info/top_level.txt,sha256=10z3OOJSVLriQ0IrFLMH8CH9zByugPWolqhlHlkNjV4,9
|
|
15
|
+
pyoframe-1.0.1.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
pyoframe/__init__.py,sha256=Nlql3FYed7bXWumvUeMd3rjnoL4l8XC5orO4uxWrDAc,839
|
|
2
|
-
pyoframe/_arithmetic.py,sha256=3_LkyDbKZ74KxY_KF_99PENj6FgZQa_hVl5RkPyFNJU,20420
|
|
3
|
-
pyoframe/_constants.py,sha256=afwAHreaAKLIrpIEAHtuCn7q9aZuFDEgfLn0ySGZEeY,16066
|
|
4
|
-
pyoframe/_core.py,sha256=xCPU0Uw8g18cxBQbA644RY0fjDan2lJ2pLw6c0ODL6s,113495
|
|
5
|
-
pyoframe/_model.py,sha256=h2sx-JpkSv6o2eS6qcdbQCfQi9Me-zv4IGXumPziBT4,21689
|
|
6
|
-
pyoframe/_model_element.py,sha256=8dvPlRc3hVnlvd-8bxze_ol1fWRd3QiN8UBS25OgwZ4,5771
|
|
7
|
-
pyoframe/_monkey_patch.py,sha256=j206jGoP4Q2eSQrmAkI047gSxftb80Tva0UEc32cDKY,3309
|
|
8
|
-
pyoframe/_objective.py,sha256=Sadl6rhweAKSf2XpRiRCCyAPUVKszjF9s7GEd8g74zg,4375
|
|
9
|
-
pyoframe/_utils.py,sha256=48YTdB1Tlfu-A-xDWb06zA-pXoVtrZGOVMAWw0ClOWM,12554
|
|
10
|
-
pyoframe/_version.py,sha256=KQnBwkHr_bCl4qNrncHrejtChdGSaDU6A5ii5fv_e0U,712
|
|
11
|
-
pyoframe-1.0.0a0.dist-info/licenses/LICENSE,sha256=u_Spw4ynlwTMRZeCX-uacv_hBU547pBygiA6d2ONNV4,1074
|
|
12
|
-
pyoframe-1.0.0a0.dist-info/METADATA,sha256=SSJg9dGl5xKSaUXnZESRYx8W_P1wGF07rhLSQXLnz5k,4039
|
|
13
|
-
pyoframe-1.0.0a0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
-
pyoframe-1.0.0a0.dist-info/top_level.txt,sha256=10z3OOJSVLriQ0IrFLMH8CH9zByugPWolqhlHlkNjV4,9
|
|
15
|
-
pyoframe-1.0.0a0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|