pyoframe 0.1.2__py3-none-any.whl → 0.1.4__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 +57 -6
- pyoframe/core.py +25 -15
- pyoframe/model.py +35 -3
- pyoframe/model_element.py +1 -1
- {pyoframe-0.1.2.dist-info → pyoframe-0.1.4.dist-info}/METADATA +2 -1
- pyoframe-0.1.4.dist-info/RECORD +14 -0
- pyoframe-0.1.2.dist-info/RECORD +0 -14
- {pyoframe-0.1.2.dist-info → pyoframe-0.1.4.dist-info}/LICENSE +0 -0
- {pyoframe-0.1.2.dist-info → pyoframe-0.1.4.dist-info}/WHEEL +0 -0
- {pyoframe-0.1.2.dist-info → pyoframe-0.1.4.dist-info}/top_level.txt +0 -0
pyoframe/_arithmetic.py
CHANGED
|
@@ -363,16 +363,67 @@ def _add_dimension(self: "Expression", target: "Expression") -> "Expression":
|
|
|
363
363
|
|
|
364
364
|
|
|
365
365
|
def _sum_like_terms(df: pl.DataFrame) -> pl.DataFrame:
|
|
366
|
-
"""Combines terms with the same variables.
|
|
366
|
+
"""Combines terms with the same variables."""
|
|
367
367
|
dims = [c for c in df.columns if c not in RESERVED_COL_KEYS]
|
|
368
368
|
var_cols = [VAR_KEY] + ([QUAD_VAR_KEY] if QUAD_VAR_KEY in df.columns else [])
|
|
369
|
-
df = (
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
369
|
+
df = df.group_by(dims + var_cols, maintain_order=True).sum()
|
|
370
|
+
return df
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _simplify_expr_df(df: pl.DataFrame) -> pl.DataFrame:
|
|
374
|
+
"""
|
|
375
|
+
Removes the quadratic column and terms with a zero coefficient, when applicable.
|
|
376
|
+
|
|
377
|
+
Specifically, zero coefficient terms are always removed, except if they're the only terms in which case the expression contains a single term.
|
|
378
|
+
The quadratic column is removed if the expression is not a quadratic.
|
|
379
|
+
|
|
380
|
+
Examples:
|
|
381
|
+
|
|
382
|
+
>>> import polars as pl
|
|
383
|
+
>>> df = pl.DataFrame({ VAR_KEY: [CONST_TERM, 1], QUAD_VAR_KEY: [CONST_TERM, 1], COEF_KEY: [1.0, 0]})
|
|
384
|
+
>>> _simplify_expr_df(df)
|
|
385
|
+
shape: (1, 2)
|
|
386
|
+
┌───────────────┬─────────┐
|
|
387
|
+
│ __variable_id ┆ __coeff │
|
|
388
|
+
│ --- ┆ --- │
|
|
389
|
+
│ i64 ┆ f64 │
|
|
390
|
+
╞═══════════════╪═════════╡
|
|
391
|
+
│ 0 ┆ 1.0 │
|
|
392
|
+
└───────────────┴─────────┘
|
|
393
|
+
>>> df = pl.DataFrame({"t": [1, 1, 2, 2, 3, 3], VAR_KEY: [CONST_TERM, 1, CONST_TERM, 1, 1, 2], QUAD_VAR_KEY: [CONST_TERM, CONST_TERM, CONST_TERM, CONST_TERM, CONST_TERM, 1], COEF_KEY: [1, 0, 0, 0, 1, 0]})
|
|
394
|
+
>>> _simplify_expr_df(df)
|
|
395
|
+
shape: (3, 3)
|
|
396
|
+
┌─────┬───────────────┬─────────┐
|
|
397
|
+
│ t ┆ __variable_id ┆ __coeff │
|
|
398
|
+
│ --- ┆ --- ┆ --- │
|
|
399
|
+
│ i64 ┆ i64 ┆ i64 │
|
|
400
|
+
╞═════╪═══════════════╪═════════╡
|
|
401
|
+
│ 1 ┆ 0 ┆ 1 │
|
|
402
|
+
│ 2 ┆ 0 ┆ 0 │
|
|
403
|
+
│ 3 ┆ 1 ┆ 1 │
|
|
404
|
+
└─────┴───────────────┴─────────┘
|
|
405
|
+
"""
|
|
406
|
+
df_filtered = df.filter(pl.col(COEF_KEY) != 0)
|
|
407
|
+
if len(df_filtered) < len(df):
|
|
408
|
+
dims = [c for c in df.columns if c not in RESERVED_COL_KEYS]
|
|
409
|
+
if dims:
|
|
410
|
+
dim_values = df.select(dims).unique(maintain_order=True)
|
|
411
|
+
df = (
|
|
412
|
+
dim_values.join(df_filtered, on=dims, how="left")
|
|
413
|
+
.with_columns(pl.col(COEF_KEY).fill_null(0))
|
|
414
|
+
.fill_null(CONST_TERM)
|
|
415
|
+
)
|
|
416
|
+
else:
|
|
417
|
+
df = df_filtered
|
|
418
|
+
if df.is_empty():
|
|
419
|
+
df = pl.DataFrame(
|
|
420
|
+
{VAR_KEY: [CONST_TERM], COEF_KEY: [0]},
|
|
421
|
+
schema={VAR_KEY: KEY_TYPE, COEF_KEY: pl.Float64},
|
|
422
|
+
)
|
|
423
|
+
|
|
374
424
|
if QUAD_VAR_KEY in df.columns and (df.get_column(QUAD_VAR_KEY) == CONST_TERM).all():
|
|
375
425
|
df = df.drop(QUAD_VAR_KEY)
|
|
426
|
+
|
|
376
427
|
return df
|
|
377
428
|
|
|
378
429
|
|
pyoframe/core.py
CHANGED
|
@@ -25,6 +25,7 @@ from pyoframe._arithmetic import (
|
|
|
25
25
|
_add_expressions,
|
|
26
26
|
_get_dimensions,
|
|
27
27
|
_multiply_expressions,
|
|
28
|
+
_simplify_expr_df,
|
|
28
29
|
)
|
|
29
30
|
from pyoframe.constants import (
|
|
30
31
|
COEF_KEY,
|
|
@@ -424,21 +425,9 @@ class Expression(ModelElement, SupportsMath, SupportPolarsMethodMixin):
|
|
|
424
425
|
f"Cannot create an expression with duplicate indices:\n{duplicated_data}."
|
|
425
426
|
)
|
|
426
427
|
|
|
427
|
-
|
|
428
|
+
data = _simplify_expr_df(data)
|
|
428
429
|
|
|
429
|
-
|
|
430
|
-
# @classmethod
|
|
431
|
-
# def empty(cls, dimensions=[], type=None):
|
|
432
|
-
# data = {COEF_KEY: [], VAR_KEY: []}
|
|
433
|
-
# data.update({d: [] for d in dimensions})
|
|
434
|
-
# schema = {COEF_KEY: pl.Float64, VAR_KEY: pl.UInt32}
|
|
435
|
-
# if type is not None:
|
|
436
|
-
# schema.update({d: t for d, t in zip(dimensions, type)})
|
|
437
|
-
# return Expression(
|
|
438
|
-
# pl.DataFrame(data).with_columns(
|
|
439
|
-
# *[pl.col(c).cast(t) for c, t in schema.items()]
|
|
440
|
-
# )
|
|
441
|
-
# )
|
|
430
|
+
super().__init__(data)
|
|
442
431
|
|
|
443
432
|
@classmethod
|
|
444
433
|
def constant(cls, constant: int | float) -> "Expression":
|
|
@@ -1011,7 +1000,7 @@ class Expression(ModelElement, SupportsMath, SupportPolarsMethodMixin):
|
|
|
1011
1000
|
self,
|
|
1012
1001
|
size=len(self),
|
|
1013
1002
|
dimensions=self.shape,
|
|
1014
|
-
terms=
|
|
1003
|
+
terms=self.terms,
|
|
1015
1004
|
degree=2 if self.degree() == 2 else None,
|
|
1016
1005
|
)
|
|
1017
1006
|
if include_header and include_data:
|
|
@@ -1031,6 +1020,27 @@ class Expression(ModelElement, SupportsMath, SupportPolarsMethodMixin):
|
|
|
1031
1020
|
def __str__(self) -> str:
|
|
1032
1021
|
return self.to_str()
|
|
1033
1022
|
|
|
1023
|
+
@property
|
|
1024
|
+
def terms(self) -> int:
|
|
1025
|
+
"""
|
|
1026
|
+
Number of terms across all subexpressions.
|
|
1027
|
+
|
|
1028
|
+
Expressions equal to zero count as one term.
|
|
1029
|
+
|
|
1030
|
+
Examples:
|
|
1031
|
+
>>> import polars as pl
|
|
1032
|
+
>>> m = pf.Model()
|
|
1033
|
+
>>> m.v = pf.Variable({"t": [1, 2]})
|
|
1034
|
+
>>> coef = pl.DataFrame({"t": [1, 2], "coef": [0, 1]})
|
|
1035
|
+
>>> coef*(m.v+4)
|
|
1036
|
+
<Expression size=2 dimensions={'t': 2} terms=3>
|
|
1037
|
+
[1]: 0
|
|
1038
|
+
[2]: 4 + v[2]
|
|
1039
|
+
>>> (coef*(m.v+4)).terms
|
|
1040
|
+
3
|
|
1041
|
+
"""
|
|
1042
|
+
return len(self.data)
|
|
1043
|
+
|
|
1034
1044
|
|
|
1035
1045
|
@overload
|
|
1036
1046
|
def sum(over: Union[str, Sequence[str]], expr: SupportsToExpr) -> "Expression": ...
|
pyoframe/model.py
CHANGED
|
@@ -39,7 +39,7 @@ class Model:
|
|
|
39
39
|
Typically, this parameter can be omitted (`None`) as it will automatically be
|
|
40
40
|
set when the objective is set using `.minimize` or `.maximize`.
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
Examples:
|
|
43
43
|
>>> m = pf.Model()
|
|
44
44
|
>>> m.X = pf.Variable()
|
|
45
45
|
>>> m.my_constraint = m.X <= 10
|
|
@@ -273,7 +273,7 @@ class Model:
|
|
|
273
273
|
!!! warning "Gurobi only"
|
|
274
274
|
This method only works with the Gurobi solver. Open an issue if you'd like to see support for other solvers.
|
|
275
275
|
|
|
276
|
-
|
|
276
|
+
Examples:
|
|
277
277
|
>>> m = pf.Model(solver="gurobi")
|
|
278
278
|
>>> m.X = pf.Variable(vtype=pf.VType.BINARY, lb=0)
|
|
279
279
|
>>> m.Y = pf.Variable(vtype=pf.VType.INTEGER, lb=0)
|
|
@@ -310,7 +310,7 @@ class Model:
|
|
|
310
310
|
!!! warning "Gurobi only"
|
|
311
311
|
This method only works with the Gurobi solver. Open an issue if you'd like to see support for other solvers.
|
|
312
312
|
|
|
313
|
-
|
|
313
|
+
Examples:
|
|
314
314
|
>>> m = pf.Model(solver="gurobi")
|
|
315
315
|
>>> m.X = pf.Variable(lb=0, ub=2)
|
|
316
316
|
>>> m.Y = pf.Variable(lb=0, ub=2)
|
|
@@ -329,6 +329,38 @@ class Model:
|
|
|
329
329
|
"""
|
|
330
330
|
self.poi.computeIIS()
|
|
331
331
|
|
|
332
|
+
@for_solvers("gurobi")
|
|
333
|
+
def dispose(self):
|
|
334
|
+
"""
|
|
335
|
+
Tries to close the solver connection by deleting the model and forcing the garbage collector to run.
|
|
336
|
+
|
|
337
|
+
Gurobi only. Once this method is called, this model is no longer usable.
|
|
338
|
+
|
|
339
|
+
This method will not work if you have a variable that references self.poi.
|
|
340
|
+
Unfortunately, this is a limitation from the underlying solver interface library.
|
|
341
|
+
See https://github.com/metab0t/PyOptInterface/issues/36 for context.
|
|
342
|
+
|
|
343
|
+
Examples:
|
|
344
|
+
>>> m = pf.Model(solver="gurobi")
|
|
345
|
+
>>> m.X = pf.Variable(ub=1)
|
|
346
|
+
>>> m.maximize = m.X
|
|
347
|
+
>>> m.optimize()
|
|
348
|
+
>>> m.X.solution
|
|
349
|
+
1.0
|
|
350
|
+
>>> m.dispose()
|
|
351
|
+
>>> m.X.solution
|
|
352
|
+
Traceback (most recent call last):
|
|
353
|
+
...
|
|
354
|
+
AttributeError: 'Model' object has no attribute 'poi'
|
|
355
|
+
"""
|
|
356
|
+
import gc
|
|
357
|
+
|
|
358
|
+
env = self.poi._env
|
|
359
|
+
del self.poi
|
|
360
|
+
gc.collect()
|
|
361
|
+
del env
|
|
362
|
+
gc.collect()
|
|
363
|
+
|
|
332
364
|
def _set_param(self, name, value):
|
|
333
365
|
self.poi.set_raw_parameter(name, value)
|
|
334
366
|
|
pyoframe/model_element.py
CHANGED
|
@@ -145,7 +145,7 @@ class SupportPolarsMethodMixin(ABC):
|
|
|
145
145
|
"""
|
|
146
146
|
Filters elements by the given criteria and then drops the filtered dimensions.
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
Examples:
|
|
149
149
|
>>> m = pf.Model()
|
|
150
150
|
>>> m.v = pf.Variable([{"hour": ["00:00", "06:00", "12:00", "18:00"]}, {"city": ["Toronto", "Berlin", "Paris"]}])
|
|
151
151
|
>>> m.v.pick(hour="06:00")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: pyoframe
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
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/
|
|
@@ -32,6 +32,7 @@ Requires-Dist: pre-commit; extra == "dev"
|
|
|
32
32
|
Requires-Dist: gurobipy; extra == "dev"
|
|
33
33
|
Requires-Dist: highsbox; extra == "dev"
|
|
34
34
|
Requires-Dist: pre-commit; extra == "dev"
|
|
35
|
+
Requires-Dist: coverage; extra == "dev"
|
|
35
36
|
Provides-Extra: docs
|
|
36
37
|
Requires-Dist: mkdocs-material==9.*; extra == "docs"
|
|
37
38
|
Requires-Dist: mkdocstrings[python]; extra == "docs"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
pyoframe/__init__.py,sha256=nEN0OgqhevtsvxEiPbJLzwPojf3ngYAoT90M_1mc4kM,477
|
|
2
|
+
pyoframe/_arithmetic.py,sha256=vqzMZip1kTkUUMyHohUyKJxx2nEeQIH7N57fwevLWaQ,17284
|
|
3
|
+
pyoframe/constants.py,sha256=Dy1sCzykZlmkbvgsc5ckujqXPuYmsKkD3stANU0qr5Y,3784
|
|
4
|
+
pyoframe/core.py,sha256=4heBn7RGOWV-Yg0OoiLr8sGBaygpTx-D-pLuC4nC0Dw,62865
|
|
5
|
+
pyoframe/model.py,sha256=7nVH2oZeTuk53l76PLNElsiHEgmbTvrf7lGIa9_CYm4,13011
|
|
6
|
+
pyoframe/model_element.py,sha256=RBmsE2FOct3UL2N2LZLfWYv2wL_QKqNmKmmR_pD_ERs,5945
|
|
7
|
+
pyoframe/monkey_patch.py,sha256=9IfS14G6IPabmM9z80jzi_D4Rq0Mdx5aUCA39Yi2tgE,2044
|
|
8
|
+
pyoframe/objective.py,sha256=PBWxj30QkFlsvY6ijZ6KjyKdrJARD4to0ieF6GUqaQU,3238
|
|
9
|
+
pyoframe/util.py,sha256=Oyk8xh6FJHlb04X_cM4lN0UzdnKLXAMrKfyOf7IexiA,13480
|
|
10
|
+
pyoframe-0.1.4.dist-info/LICENSE,sha256=dkwA40ZzT-3x6eu2a6mf_o7PNSqHbdsyaFNhLxGHNQs,1065
|
|
11
|
+
pyoframe-0.1.4.dist-info/METADATA,sha256=v3ZH_JvXSg4cN515Qo0FvZviXIC0l-Ja5tgq5IdgwLI,3558
|
|
12
|
+
pyoframe-0.1.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
13
|
+
pyoframe-0.1.4.dist-info/top_level.txt,sha256=10z3OOJSVLriQ0IrFLMH8CH9zByugPWolqhlHlkNjV4,9
|
|
14
|
+
pyoframe-0.1.4.dist-info/RECORD,,
|
pyoframe-0.1.2.dist-info/RECORD
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
pyoframe/__init__.py,sha256=nEN0OgqhevtsvxEiPbJLzwPojf3ngYAoT90M_1mc4kM,477
|
|
2
|
-
pyoframe/_arithmetic.py,sha256=LvuxI4pFYuqrqus4FxcIekUwXfdEMEVWBR-1h5hF7Ac,14764
|
|
3
|
-
pyoframe/constants.py,sha256=Dy1sCzykZlmkbvgsc5ckujqXPuYmsKkD3stANU0qr5Y,3784
|
|
4
|
-
pyoframe/core.py,sha256=C9T0wFDAgcsFVxwnLOYqQ2j9fwnCCS_usjlGSME_qmo,62743
|
|
5
|
-
pyoframe/model.py,sha256=d_WyLzdfroDYAtyXs3Ie_jo5c_CGxTX5qPT4vCVaiB8,11967
|
|
6
|
-
pyoframe/model_element.py,sha256=nCfe56CRWr6bwP8irUd2bmLAEGQ-7GwOQtWeqz2WxtU,5944
|
|
7
|
-
pyoframe/monkey_patch.py,sha256=9IfS14G6IPabmM9z80jzi_D4Rq0Mdx5aUCA39Yi2tgE,2044
|
|
8
|
-
pyoframe/objective.py,sha256=PBWxj30QkFlsvY6ijZ6KjyKdrJARD4to0ieF6GUqaQU,3238
|
|
9
|
-
pyoframe/util.py,sha256=Oyk8xh6FJHlb04X_cM4lN0UzdnKLXAMrKfyOf7IexiA,13480
|
|
10
|
-
pyoframe-0.1.2.dist-info/LICENSE,sha256=dkwA40ZzT-3x6eu2a6mf_o7PNSqHbdsyaFNhLxGHNQs,1065
|
|
11
|
-
pyoframe-0.1.2.dist-info/METADATA,sha256=VEtEP6xMwf3R2cbBXXPgfGRnn13qv-QjYwMY6FGR24I,3518
|
|
12
|
-
pyoframe-0.1.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
13
|
-
pyoframe-0.1.2.dist-info/top_level.txt,sha256=10z3OOJSVLriQ0IrFLMH8CH9zByugPWolqhlHlkNjV4,9
|
|
14
|
-
pyoframe-0.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|