pyoframe 1.0.1__py3-none-any.whl → 1.1.0__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 +2 -0
- pyoframe/_arithmetic.py +3 -3
- pyoframe/_constants.py +9 -10
- pyoframe/_core.py +84 -57
- pyoframe/_model.py +3 -3
- pyoframe/_model_element.py +2 -0
- pyoframe/_monkey_patch.py +5 -49
- pyoframe/_objective.py +2 -2
- pyoframe/_param.py +99 -0
- pyoframe/_utils.py +2 -2
- pyoframe/_version.py +2 -2
- {pyoframe-1.0.1.dist-info → pyoframe-1.1.0.dist-info}/METADATA +5 -5
- pyoframe-1.1.0.dist-info/RECORD +16 -0
- pyoframe-1.0.1.dist-info/RECORD +0 -15
- {pyoframe-1.0.1.dist-info → pyoframe-1.1.0.dist-info}/WHEEL +0 -0
- {pyoframe-1.0.1.dist-info → pyoframe-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {pyoframe-1.0.1.dist-info → pyoframe-1.1.0.dist-info}/top_level.txt +0 -0
pyoframe/__init__.py
CHANGED
|
@@ -11,6 +11,7 @@ from pyoframe._core import Constraint, Expression, Set, Variable, sum, sum_by
|
|
|
11
11
|
from pyoframe._model import Model
|
|
12
12
|
from pyoframe._monkey_patch import patch_dataframe_libraries
|
|
13
13
|
from pyoframe._objective import Objective
|
|
14
|
+
from pyoframe._param import Param
|
|
14
15
|
|
|
15
16
|
try:
|
|
16
17
|
from pyoframe._version import __version__, __version_tuple__ # noqa: F401
|
|
@@ -25,6 +26,7 @@ __all__ = [
|
|
|
25
26
|
"Expression",
|
|
26
27
|
"Constraint",
|
|
27
28
|
"Objective",
|
|
29
|
+
"Param",
|
|
28
30
|
"Set",
|
|
29
31
|
"Config",
|
|
30
32
|
"sum",
|
pyoframe/_arithmetic.py
CHANGED
|
@@ -42,7 +42,7 @@ def multiply(self: Expression, other: Expression) -> Expression:
|
|
|
42
42
|
>>> m.x3 = pf.Variable()
|
|
43
43
|
>>> result = 5 * m.x1 * m.x2
|
|
44
44
|
>>> result
|
|
45
|
-
<Expression terms=1
|
|
45
|
+
<Expression (quadratic) terms=1>
|
|
46
46
|
5 x2 * x1
|
|
47
47
|
>>> result * m.x3
|
|
48
48
|
Traceback (most recent call last):
|
|
@@ -112,7 +112,7 @@ def _quadratic_multiplication(self: Expression, other: Expression) -> Expression
|
|
|
112
112
|
>>> expr1 = df * m.x1
|
|
113
113
|
>>> expr2 = df * m.x2 * 2 + 4
|
|
114
114
|
>>> expr1 * expr2
|
|
115
|
-
<Expression height=3 terms=6
|
|
115
|
+
<Expression (quadratic) height=3 terms=6>
|
|
116
116
|
┌─────┬───────────────────┐
|
|
117
117
|
│ dim ┆ expression │
|
|
118
118
|
│ (3) ┆ │
|
|
@@ -122,7 +122,7 @@ def _quadratic_multiplication(self: Expression, other: Expression) -> Expression
|
|
|
122
122
|
│ 3 ┆ 12 x1 +18 x2 * x1 │
|
|
123
123
|
└─────┴───────────────────┘
|
|
124
124
|
>>> (expr1 * expr2) - df * m.x1 * df * m.x2 * 2
|
|
125
|
-
<Expression height=3 terms=3
|
|
125
|
+
<Expression (linear) height=3 terms=3>
|
|
126
126
|
┌─────┬────────────┐
|
|
127
127
|
│ dim ┆ expression │
|
|
128
128
|
│ (3) ┆ │
|
pyoframe/_constants.py
CHANGED
|
@@ -141,19 +141,18 @@ class _Config:
|
|
|
141
141
|
Only consider enabling after you have thoroughly tested your code.
|
|
142
142
|
|
|
143
143
|
Examples:
|
|
144
|
-
>>>
|
|
145
|
-
>>> population = pl.DataFrame(
|
|
144
|
+
>>> population = pf.Param(
|
|
146
145
|
... {
|
|
147
146
|
... "city": ["Toronto", "Vancouver", "Montreal"],
|
|
148
147
|
... "pop": [2_731_571, 631_486, 1_704_694],
|
|
149
148
|
... }
|
|
150
|
-
... )
|
|
151
|
-
>>> population_influx =
|
|
149
|
+
... )
|
|
150
|
+
>>> population_influx = pf.Param(
|
|
152
151
|
... {
|
|
153
152
|
... "city": ["Toronto", "Vancouver", "Montreal"],
|
|
154
153
|
... "influx": [100_000, 50_000, None],
|
|
155
154
|
... }
|
|
156
|
-
... )
|
|
155
|
+
... )
|
|
157
156
|
|
|
158
157
|
Normally, an error warns users that the two expressions have conflicting labels:
|
|
159
158
|
>>> population + population_influx
|
|
@@ -174,7 +173,7 @@ class _Config:
|
|
|
174
173
|
But if `Config.disable_extras_checks = True`, the error is suppressed and the sum is considered to be `population.keep_extras() + population_influx.keep_extras()`:
|
|
175
174
|
>>> pf.Config.disable_extras_checks = True
|
|
176
175
|
>>> population + population_influx
|
|
177
|
-
<Expression height=3 terms=3
|
|
176
|
+
<Expression (parameter) height=3 terms=3>
|
|
178
177
|
┌───────────┬────────────┐
|
|
179
178
|
│ city ┆ expression │
|
|
180
179
|
│ (3) ┆ │
|
|
@@ -228,11 +227,11 @@ class _Config:
|
|
|
228
227
|
>>> m.X = pf.Variable()
|
|
229
228
|
>>> expr = 100.752038759 * m.X
|
|
230
229
|
>>> expr
|
|
231
|
-
<Expression terms=1
|
|
230
|
+
<Expression (linear) terms=1>
|
|
232
231
|
100.752 X
|
|
233
232
|
>>> pf.Config.float_to_str_precision = None
|
|
234
233
|
>>> expr
|
|
235
|
-
<Expression terms=1
|
|
234
|
+
<Expression (linear) terms=1>
|
|
236
235
|
100.752038759 X
|
|
237
236
|
"""
|
|
238
237
|
return self._settings.float_to_str_precision
|
|
@@ -280,7 +279,7 @@ class _Config:
|
|
|
280
279
|
>>> m = pf.Model()
|
|
281
280
|
>>> m.X = pf.Variable(pf.Set(x=range(100)), pf.Set(y=range(100)))
|
|
282
281
|
>>> m.X.sum("y")
|
|
283
|
-
<Expression height=100 terms=10000
|
|
282
|
+
<Expression (linear) height=100 terms=10000>
|
|
284
283
|
┌───────┬───────────────────────────────┐
|
|
285
284
|
│ x ┆ expression │
|
|
286
285
|
│ (100) ┆ │
|
|
@@ -298,7 +297,7 @@ class _Config:
|
|
|
298
297
|
│ 99 ┆ X[99,0] + X[99,1] + X[99,2] … │
|
|
299
298
|
└───────┴───────────────────────────────┘
|
|
300
299
|
>>> m.X.sum()
|
|
301
|
-
<Expression terms=10000
|
|
300
|
+
<Expression (linear) terms=10000>
|
|
302
301
|
X[0,0] + X[0,1] + X[0,2] …
|
|
303
302
|
"""
|
|
304
303
|
return self._settings.print_max_terms
|
pyoframe/_core.py
CHANGED
|
@@ -169,7 +169,7 @@ class BaseOperableBlock(BaseBlock):
|
|
|
169
169
|
└───────┴─────────┴──────────────────┘
|
|
170
170
|
|
|
171
171
|
>>> m.v.rename({"city": "location"})
|
|
172
|
-
<Expression height=12 terms=12
|
|
172
|
+
<Expression (linear) height=12 terms=12>
|
|
173
173
|
┌───────┬──────────┬──────────────────┐
|
|
174
174
|
│ hour ┆ location ┆ expression │
|
|
175
175
|
│ (4) ┆ (3) ┆ │
|
|
@@ -228,7 +228,7 @@ class BaseOperableBlock(BaseBlock):
|
|
|
228
228
|
... ]
|
|
229
229
|
... )
|
|
230
230
|
>>> m.v.pick(hour="06:00")
|
|
231
|
-
<Expression height=3 terms=3
|
|
231
|
+
<Expression (linear) height=3 terms=3>
|
|
232
232
|
┌─────────┬──────────────────┐
|
|
233
233
|
│ city ┆ expression │
|
|
234
234
|
│ (3) ┆ │
|
|
@@ -238,7 +238,7 @@ class BaseOperableBlock(BaseBlock):
|
|
|
238
238
|
│ Paris ┆ v[06:00,Paris] │
|
|
239
239
|
└─────────┴──────────────────┘
|
|
240
240
|
>>> m.v.pick(hour="06:00", city="Toronto")
|
|
241
|
-
<Expression terms=1
|
|
241
|
+
<Expression (linear) terms=1>
|
|
242
242
|
v[06:00,Toronto]
|
|
243
243
|
|
|
244
244
|
See Also:
|
|
@@ -290,7 +290,7 @@ class BaseOperableBlock(BaseBlock):
|
|
|
290
290
|
>>> m = pf.Model()
|
|
291
291
|
>>> m.v = pf.Variable()
|
|
292
292
|
>>> m.v**2
|
|
293
|
-
<Expression terms=1
|
|
293
|
+
<Expression (quadratic) terms=1>
|
|
294
294
|
v * v
|
|
295
295
|
>>> m.v**3
|
|
296
296
|
Traceback (most recent call last):
|
|
@@ -320,7 +320,7 @@ class BaseOperableBlock(BaseBlock):
|
|
|
320
320
|
>>> df = pl.DataFrame({"dim1": [1, 2, 3], "value": [1, 2, 3]})
|
|
321
321
|
>>> m.v = pf.Variable(df["dim1"])
|
|
322
322
|
>>> m.v - df
|
|
323
|
-
<Expression height=3 terms=6
|
|
323
|
+
<Expression (linear) height=3 terms=6>
|
|
324
324
|
┌──────┬────────────┐
|
|
325
325
|
│ dim1 ┆ expression │
|
|
326
326
|
│ (3) ┆ │
|
|
@@ -331,7 +331,7 @@ class BaseOperableBlock(BaseBlock):
|
|
|
331
331
|
└──────┴────────────┘
|
|
332
332
|
"""
|
|
333
333
|
if not isinstance(other, (int, float)):
|
|
334
|
-
other = other.to_expr()
|
|
334
|
+
other = other.to_expr() # TODO don't rely on monkey patch
|
|
335
335
|
return self.to_expr() + (-other)
|
|
336
336
|
|
|
337
337
|
def __rmul__(self, other):
|
|
@@ -348,7 +348,7 @@ class BaseOperableBlock(BaseBlock):
|
|
|
348
348
|
>>> m = pf.Model()
|
|
349
349
|
>>> m.v = Variable({"dim1": [1, 2, 3]})
|
|
350
350
|
>>> m.v / 2
|
|
351
|
-
<Expression height=3 terms=3
|
|
351
|
+
<Expression (linear) height=3 terms=3>
|
|
352
352
|
┌──────┬────────────┐
|
|
353
353
|
│ dim1 ┆ expression │
|
|
354
354
|
│ (3) ┆ │
|
|
@@ -360,6 +360,13 @@ class BaseOperableBlock(BaseBlock):
|
|
|
360
360
|
"""
|
|
361
361
|
return self.to_expr() * (1 / other)
|
|
362
362
|
|
|
363
|
+
def __rtruediv__(self, other):
|
|
364
|
+
# This just improves error messages when trying to divide by a Set or Variable.
|
|
365
|
+
# When dividing by an Expression, see the Expression.__rtruediv__ method.
|
|
366
|
+
raise PyoframeError(
|
|
367
|
+
f"Cannot divide by '{self.name}' because it is not a number or parameter."
|
|
368
|
+
)
|
|
369
|
+
|
|
363
370
|
def __rsub__(self, other):
|
|
364
371
|
"""Supports right subtraction.
|
|
365
372
|
|
|
@@ -367,7 +374,7 @@ class BaseOperableBlock(BaseBlock):
|
|
|
367
374
|
>>> m = pf.Model()
|
|
368
375
|
>>> m.v = Variable({"dim1": [1, 2, 3]})
|
|
369
376
|
>>> 1 - m.v
|
|
370
|
-
<Expression height=3 terms=6
|
|
377
|
+
<Expression (linear) height=3 terms=6>
|
|
371
378
|
┌──────┬────────────┐
|
|
372
379
|
│ dim1 ┆ expression │
|
|
373
380
|
│ (3) ┆ │
|
|
@@ -571,7 +578,7 @@ class Set(BaseOperableBlock):
|
|
|
571
578
|
def __repr__(self):
|
|
572
579
|
header = get_obj_repr(
|
|
573
580
|
self,
|
|
574
|
-
"unnamed" if self.name == "unnamed_set" else self.name,
|
|
581
|
+
"'unnamed'" if self.name == "unnamed_set" else f"'{self.name}'",
|
|
575
582
|
height=self.data.height,
|
|
576
583
|
)
|
|
577
584
|
data = self._add_shape_to_columns(self.data)
|
|
@@ -645,7 +652,7 @@ class Expression(BaseOperableBlock):
|
|
|
645
652
|
>>> m.Size = pf.Variable(df.index)
|
|
646
653
|
>>> expr = df["cost"] * m.Time + df["cost"] * m.Size
|
|
647
654
|
>>> expr
|
|
648
|
-
<Expression height=5 terms=10
|
|
655
|
+
<Expression (linear) height=5 terms=10>
|
|
649
656
|
┌──────┬──────┬──────────────────────────────┐
|
|
650
657
|
│ item ┆ time ┆ expression │
|
|
651
658
|
│ (2) ┆ (3) ┆ │
|
|
@@ -691,7 +698,7 @@ class Expression(BaseOperableBlock):
|
|
|
691
698
|
|
|
692
699
|
Examples:
|
|
693
700
|
>>> pf.Expression.constant(5)
|
|
694
|
-
<Expression terms=1
|
|
701
|
+
<Expression (parameter) terms=1>
|
|
695
702
|
5
|
|
696
703
|
"""
|
|
697
704
|
return cls(
|
|
@@ -712,7 +719,7 @@ class Expression(BaseOperableBlock):
|
|
|
712
719
|
If no dimensions are specified, the sum is taken over all of the expression's dimensions.
|
|
713
720
|
|
|
714
721
|
Examples:
|
|
715
|
-
>>> expr =
|
|
722
|
+
>>> expr = pf.Param(
|
|
716
723
|
... {
|
|
717
724
|
... "time": ["mon", "tue", "wed", "mon", "tue"],
|
|
718
725
|
... "place": [
|
|
@@ -724,9 +731,9 @@ class Expression(BaseOperableBlock):
|
|
|
724
731
|
... ],
|
|
725
732
|
... "tiktok_posts": [1e6, 3e6, 2e6, 1e6, 2e6],
|
|
726
733
|
... }
|
|
727
|
-
... )
|
|
734
|
+
... )
|
|
728
735
|
>>> expr
|
|
729
|
-
<Expression height=5 terms=5
|
|
736
|
+
<Expression (parameter) height=5 terms=5>
|
|
730
737
|
┌──────┬───────────┬────────────┐
|
|
731
738
|
│ time ┆ place ┆ expression │
|
|
732
739
|
│ (3) ┆ (2) ┆ │
|
|
@@ -738,7 +745,7 @@ class Expression(BaseOperableBlock):
|
|
|
738
745
|
│ tue ┆ Vancouver ┆ 2000000 │
|
|
739
746
|
└──────┴───────────┴────────────┘
|
|
740
747
|
>>> expr.sum("time")
|
|
741
|
-
<Expression height=2 terms=2
|
|
748
|
+
<Expression (parameter) height=2 terms=2>
|
|
742
749
|
┌───────────┬────────────┐
|
|
743
750
|
│ place ┆ expression │
|
|
744
751
|
│ (2) ┆ │
|
|
@@ -747,7 +754,7 @@ class Expression(BaseOperableBlock):
|
|
|
747
754
|
│ Vancouver ┆ 3000000 │
|
|
748
755
|
└───────────┴────────────┘
|
|
749
756
|
>>> expr.sum()
|
|
750
|
-
<Expression terms=1
|
|
757
|
+
<Expression (parameter) terms=1>
|
|
751
758
|
9000000
|
|
752
759
|
|
|
753
760
|
If the given dimensions don't exist, an error will be raised:
|
|
@@ -783,7 +790,7 @@ class Expression(BaseOperableBlock):
|
|
|
783
790
|
"""Like [`Expression.sum`][pyoframe.Expression.sum], but the sum is taken over all dimensions *except* those specified in `by` (just like a `group_by().sum()` operation).
|
|
784
791
|
|
|
785
792
|
Examples:
|
|
786
|
-
>>> expr =
|
|
793
|
+
>>> expr = pf.Param(
|
|
787
794
|
... {
|
|
788
795
|
... "time": ["mon", "tue", "wed", "mon", "tue"],
|
|
789
796
|
... "place": [
|
|
@@ -795,9 +802,9 @@ class Expression(BaseOperableBlock):
|
|
|
795
802
|
... ],
|
|
796
803
|
... "tiktok_posts": [1e6, 3e6, 2e6, 1e6, 2e6],
|
|
797
804
|
... }
|
|
798
|
-
... )
|
|
805
|
+
... )
|
|
799
806
|
>>> expr
|
|
800
|
-
<Expression height=5 terms=5
|
|
807
|
+
<Expression (parameter) height=5 terms=5>
|
|
801
808
|
┌──────┬───────────┬────────────┐
|
|
802
809
|
│ time ┆ place ┆ expression │
|
|
803
810
|
│ (3) ┆ (2) ┆ │
|
|
@@ -810,7 +817,7 @@ class Expression(BaseOperableBlock):
|
|
|
810
817
|
└──────┴───────────┴────────────┘
|
|
811
818
|
|
|
812
819
|
>>> expr.sum_by("place")
|
|
813
|
-
<Expression height=2 terms=2
|
|
820
|
+
<Expression (parameter) height=2 terms=2>
|
|
814
821
|
┌───────────┬────────────┐
|
|
815
822
|
│ place ┆ expression │
|
|
816
823
|
│ (2) ┆ │
|
|
@@ -873,13 +880,13 @@ class Expression(BaseOperableBlock):
|
|
|
873
880
|
|
|
874
881
|
Examples:
|
|
875
882
|
>>> import polars as pl
|
|
876
|
-
>>> pop_data =
|
|
883
|
+
>>> pop_data = pf.Param(
|
|
877
884
|
... {
|
|
878
885
|
... "city": ["Toronto", "Vancouver", "Boston"],
|
|
879
886
|
... "year": [2024, 2024, 2024],
|
|
880
887
|
... "population": [10, 2, 8],
|
|
881
888
|
... }
|
|
882
|
-
... )
|
|
889
|
+
... )
|
|
883
890
|
>>> cities_and_countries = pl.DataFrame(
|
|
884
891
|
... {
|
|
885
892
|
... "city": ["Toronto", "Vancouver", "Boston"],
|
|
@@ -887,7 +894,7 @@ class Expression(BaseOperableBlock):
|
|
|
887
894
|
... }
|
|
888
895
|
... )
|
|
889
896
|
>>> pop_data.map(cities_and_countries)
|
|
890
|
-
<Expression height=2 terms=2
|
|
897
|
+
<Expression (parameter) height=2 terms=2>
|
|
891
898
|
┌──────┬─────────┬────────────┐
|
|
892
899
|
│ year ┆ country ┆ expression │
|
|
893
900
|
│ (1) ┆ (2) ┆ │
|
|
@@ -897,7 +904,7 @@ class Expression(BaseOperableBlock):
|
|
|
897
904
|
└──────┴─────────┴────────────┘
|
|
898
905
|
|
|
899
906
|
>>> pop_data.map(cities_and_countries, drop_shared_dims=False)
|
|
900
|
-
<Expression height=3 terms=3
|
|
907
|
+
<Expression (parameter) height=3 terms=3>
|
|
901
908
|
┌───────────┬──────┬─────────┬────────────┐
|
|
902
909
|
│ city ┆ year ┆ country ┆ expression │
|
|
903
910
|
│ (3) ┆ (1) ┆ (2) ┆ │
|
|
@@ -967,7 +974,7 @@ class Expression(BaseOperableBlock):
|
|
|
967
974
|
>>> m = pf.Model()
|
|
968
975
|
>>> m.quantity = pf.Variable(cost[["item", "time"]])
|
|
969
976
|
>>> (m.quantity * cost).rolling_sum(over="time", window_size=2)
|
|
970
|
-
<Expression height=5 terms=8
|
|
977
|
+
<Expression (linear) height=5 terms=8>
|
|
971
978
|
┌──────┬──────┬──────────────────────────────────┐
|
|
972
979
|
│ item ┆ time ┆ expression │
|
|
973
980
|
│ (2) ┆ (3) ┆ │
|
|
@@ -1003,11 +1010,8 @@ class Expression(BaseOperableBlock):
|
|
|
1003
1010
|
"""Filters this expression to only include the dimensions within the provided set.
|
|
1004
1011
|
|
|
1005
1012
|
Examples:
|
|
1006
|
-
>>>
|
|
1007
|
-
>>>
|
|
1008
|
-
... {"dim1": [1, 2, 3], "value": [1, 2, 3]}
|
|
1009
|
-
... ).to_expr()
|
|
1010
|
-
>>> filter_expr = pd.DataFrame({"dim1": [1, 3], "value": [5, 6]}).to_expr()
|
|
1013
|
+
>>> general_expr = pf.Param({"dim1": [1, 2, 3], "value": [1, 2, 3]})
|
|
1014
|
+
>>> filter_expr = pf.Param({"dim1": [1, 3], "value": [5, 6]})
|
|
1011
1015
|
>>> general_expr.within(filter_expr).data
|
|
1012
1016
|
shape: (2, 3)
|
|
1013
1017
|
┌──────┬─────────┬───────────────┐
|
|
@@ -1068,11 +1072,10 @@ class Expression(BaseOperableBlock):
|
|
|
1068
1072
|
If `False`, returns the degree as an integer (0, 1, or 2).
|
|
1069
1073
|
|
|
1070
1074
|
Examples:
|
|
1071
|
-
>>> import pandas as pd
|
|
1072
1075
|
>>> m = pf.Model()
|
|
1073
1076
|
>>> m.v1 = pf.Variable()
|
|
1074
1077
|
>>> m.v2 = pf.Variable()
|
|
1075
|
-
>>> expr =
|
|
1078
|
+
>>> expr = pf.Param({"dim1": [1, 2, 3], "value": [1, 2, 3]})
|
|
1076
1079
|
>>> expr.degree()
|
|
1077
1080
|
0
|
|
1078
1081
|
>>> expr *= m.v1
|
|
@@ -1090,18 +1093,17 @@ class Expression(BaseOperableBlock):
|
|
|
1090
1093
|
elif (self.data.get_column(VAR_KEY) != CONST_TERM).any():
|
|
1091
1094
|
return "linear" if return_str else 1
|
|
1092
1095
|
else:
|
|
1093
|
-
return "
|
|
1096
|
+
return "parameter" if return_str else 0
|
|
1094
1097
|
|
|
1095
1098
|
def __add__(self, other):
|
|
1096
1099
|
"""Adds another expression or a constant to this expression.
|
|
1097
1100
|
|
|
1098
1101
|
Examples:
|
|
1099
|
-
>>> import pandas as pd
|
|
1100
1102
|
>>> m = pf.Model()
|
|
1101
|
-
>>> add =
|
|
1103
|
+
>>> add = pf.Param({"dim1": [1, 2, 3], "add": [10, 20, 30]})
|
|
1102
1104
|
>>> m.v = Variable(add)
|
|
1103
1105
|
>>> m.v + add
|
|
1104
|
-
<Expression height=3 terms=6
|
|
1106
|
+
<Expression (linear) height=3 terms=6>
|
|
1105
1107
|
┌──────┬────────────┐
|
|
1106
1108
|
│ dim1 ┆ expression │
|
|
1107
1109
|
│ (3) ┆ │
|
|
@@ -1112,7 +1114,7 @@ class Expression(BaseOperableBlock):
|
|
|
1112
1114
|
└──────┴────────────┘
|
|
1113
1115
|
|
|
1114
1116
|
>>> m.v + add + 2
|
|
1115
|
-
<Expression height=3 terms=6
|
|
1117
|
+
<Expression (linear) height=3 terms=6>
|
|
1116
1118
|
┌──────┬────────────┐
|
|
1117
1119
|
│ dim1 ┆ expression │
|
|
1118
1120
|
│ (3) ┆ │
|
|
@@ -1138,12 +1140,12 @@ class Expression(BaseOperableBlock):
|
|
|
1138
1140
|
https://bravos-power.github.io/pyoframe/latest/learn/concepts/addition
|
|
1139
1141
|
>>> m.v2 = Variable()
|
|
1140
1142
|
>>> 5 + 2 * m.v2
|
|
1141
|
-
<Expression terms=2
|
|
1143
|
+
<Expression (linear) terms=2>
|
|
1142
1144
|
2 v2 +5
|
|
1143
1145
|
"""
|
|
1144
1146
|
if isinstance(other, (int, float)):
|
|
1145
1147
|
return self._add_const(other)
|
|
1146
|
-
other = other.to_expr()
|
|
1148
|
+
other = other.to_expr() # TODO don't rely on monkey patch
|
|
1147
1149
|
self._learn_from_other(other)
|
|
1148
1150
|
return add(self, other)
|
|
1149
1151
|
|
|
@@ -1156,10 +1158,25 @@ class Expression(BaseOperableBlock):
|
|
|
1156
1158
|
name=f"({other} * {self.name})",
|
|
1157
1159
|
)
|
|
1158
1160
|
|
|
1159
|
-
other = other.to_expr()
|
|
1161
|
+
other: Expression = other.to_expr() # TODO don't rely on monkey patch
|
|
1160
1162
|
self._learn_from_other(other)
|
|
1161
1163
|
return multiply(self, other)
|
|
1162
1164
|
|
|
1165
|
+
def __rtruediv__(self, other):
|
|
1166
|
+
"""Support dividing by an expression when that expression is a constant."""
|
|
1167
|
+
assert isinstance(other, (int, float)), (
|
|
1168
|
+
f"Expected a number not a {type(other)} when dividing by an expression."
|
|
1169
|
+
)
|
|
1170
|
+
if self.degree() != 0:
|
|
1171
|
+
raise PyoframeError(
|
|
1172
|
+
f"Cannot divide by '{self.name}' because denominators cannot contain variables."
|
|
1173
|
+
)
|
|
1174
|
+
|
|
1175
|
+
return self._new(
|
|
1176
|
+
self.data.with_columns((pl.lit(other) / pl.col(COEF_KEY)).alias(COEF_KEY)),
|
|
1177
|
+
name=f"({other} / {self.name})",
|
|
1178
|
+
)
|
|
1179
|
+
|
|
1163
1180
|
def to_expr(self) -> Expression:
|
|
1164
1181
|
"""Returns the expression itself."""
|
|
1165
1182
|
return self
|
|
@@ -1181,13 +1198,13 @@ class Expression(BaseOperableBlock):
|
|
|
1181
1198
|
>>> m.x1 = Variable()
|
|
1182
1199
|
>>> m.x2 = Variable()
|
|
1183
1200
|
>>> m.x1 + 5
|
|
1184
|
-
<Expression terms=2
|
|
1201
|
+
<Expression (linear) terms=2>
|
|
1185
1202
|
x1 +5
|
|
1186
1203
|
>>> m.x1**2 + 5
|
|
1187
|
-
<Expression terms=2
|
|
1204
|
+
<Expression (quadratic) terms=2>
|
|
1188
1205
|
x1 * x1 +5
|
|
1189
1206
|
>>> m.x1**2 + m.x2 + 5
|
|
1190
|
-
<Expression terms=3
|
|
1207
|
+
<Expression (quadratic) terms=3>
|
|
1191
1208
|
x1 * x1 + x2 +5
|
|
1192
1209
|
|
|
1193
1210
|
It also works with dimensions
|
|
@@ -1195,7 +1212,7 @@ class Expression(BaseOperableBlock):
|
|
|
1195
1212
|
>>> m = pf.Model()
|
|
1196
1213
|
>>> m.v = Variable({"dim1": [1, 2, 3]})
|
|
1197
1214
|
>>> m.v * m.v + 5
|
|
1198
|
-
<Expression height=3 terms=6
|
|
1215
|
+
<Expression (quadratic) height=3 terms=6>
|
|
1199
1216
|
┌──────┬─────────────────┐
|
|
1200
1217
|
│ dim1 ┆ expression │
|
|
1201
1218
|
│ (3) ┆ │
|
|
@@ -1558,9 +1575,9 @@ class Expression(BaseOperableBlock):
|
|
|
1558
1575
|
"""Returns a string representation of the expression's header."""
|
|
1559
1576
|
return get_obj_repr(
|
|
1560
1577
|
self,
|
|
1578
|
+
f"({self.degree(return_str=True)})",
|
|
1561
1579
|
height=len(self) if self.dimensions else None,
|
|
1562
1580
|
terms=self.terms,
|
|
1563
|
-
type=self.degree(return_str=True),
|
|
1564
1581
|
)
|
|
1565
1582
|
|
|
1566
1583
|
def __repr__(self) -> str:
|
|
@@ -1581,7 +1598,7 @@ class Expression(BaseOperableBlock):
|
|
|
1581
1598
|
>>> m.v = pf.Variable({"t": [1, 2]})
|
|
1582
1599
|
>>> coef = pl.DataFrame({"t": [1, 2], "coef": [0, 1]})
|
|
1583
1600
|
>>> coef * (m.v + 4)
|
|
1584
|
-
<Expression height=2 terms=3
|
|
1601
|
+
<Expression (linear) height=2 terms=3>
|
|
1585
1602
|
┌─────┬────────────┐
|
|
1586
1603
|
│ t ┆ expression │
|
|
1587
1604
|
│ (2) ┆ │
|
|
@@ -2189,10 +2206,10 @@ class Constraint(BaseBlock):
|
|
|
2189
2206
|
return (
|
|
2190
2207
|
get_obj_repr(
|
|
2191
2208
|
self,
|
|
2192
|
-
self.name,
|
|
2209
|
+
f"'{self.name}'",
|
|
2210
|
+
f"({self.lhs.degree(return_str=True)})",
|
|
2193
2211
|
height=len(self) if self.dimensions else None,
|
|
2194
2212
|
terms=len(self.lhs.data),
|
|
2195
|
-
type=self.lhs.degree(return_str=True),
|
|
2196
2213
|
)
|
|
2197
2214
|
+ "\n"
|
|
2198
2215
|
+ self.to_str()
|
|
@@ -2202,18 +2219,22 @@ class Constraint(BaseBlock):
|
|
|
2202
2219
|
class Variable(BaseOperableBlock):
|
|
2203
2220
|
"""A decision variable for an optimization model.
|
|
2204
2221
|
|
|
2222
|
+
!!! tip
|
|
2223
|
+
If `lb` or `ub` are a dimensioned object (e.g. an [Expression][pyoframe.Expression]), they will automatically be [broadcasted](../../learn/concepts/addition.md#adding-expressions-with-differing-dimensions-using-over) to match the variable's dimensions.
|
|
2224
|
+
|
|
2205
2225
|
Parameters:
|
|
2206
2226
|
*indexing_sets:
|
|
2207
2227
|
If no indexing_sets are provided, a single variable with no dimensions is created.
|
|
2208
2228
|
Otherwise, a variable is created for each element in the Cartesian product of the indexing_sets (see Set for details on behaviour).
|
|
2209
|
-
lb:
|
|
2210
|
-
The lower bound for all variables.
|
|
2211
|
-
ub:
|
|
2212
|
-
The upper bound for all variables.
|
|
2213
2229
|
vtype:
|
|
2214
2230
|
The type of the variable. Can be either a VType enum or a string. Default is VType.CONTINUOUS.
|
|
2231
|
+
lb:
|
|
2232
|
+
The lower bound for the variables.
|
|
2233
|
+
ub:
|
|
2234
|
+
The upper bound for the variables.
|
|
2215
2235
|
equals:
|
|
2216
|
-
When specified, a variable is created and a constraint is added to make the variable equal to the provided expression.
|
|
2236
|
+
When specified, a variable is created for every label in `equals` and a constraint is added to make the variable equal to the provided expression.
|
|
2237
|
+
`indexing_sets` cannot be provided when using `equals`.
|
|
2217
2238
|
|
|
2218
2239
|
Examples:
|
|
2219
2240
|
>>> import pandas as pd
|
|
@@ -2298,7 +2319,7 @@ class Variable(BaseOperableBlock):
|
|
|
2298
2319
|
assert len(indexing_sets) == 0, (
|
|
2299
2320
|
"Cannot specify both 'equals' and 'indexing_sets'"
|
|
2300
2321
|
)
|
|
2301
|
-
equals = equals.to_expr()
|
|
2322
|
+
equals = equals.to_expr() # TODO don't rely on monkey patch
|
|
2302
2323
|
indexing_sets = (equals,)
|
|
2303
2324
|
|
|
2304
2325
|
data = Set(*indexing_sets).data if len(indexing_sets) > 0 else pl.DataFrame()
|
|
@@ -2309,10 +2330,16 @@ class Variable(BaseOperableBlock):
|
|
|
2309
2330
|
self._equals: Expression | None = equals
|
|
2310
2331
|
|
|
2311
2332
|
if lb is not None and not isinstance(lb, (float, int)):
|
|
2333
|
+
lb: Expression = lb.to_expr() # TODO don't rely on monkey patch
|
|
2334
|
+
if not self.dimensionless:
|
|
2335
|
+
lb = lb.over(*self.dimensions)
|
|
2312
2336
|
self._lb_expr, self.lb = lb, None
|
|
2313
2337
|
else:
|
|
2314
2338
|
self._lb_expr, self.lb = None, lb
|
|
2315
2339
|
if ub is not None and not isinstance(ub, (float, int)):
|
|
2340
|
+
ub = ub.to_expr() # TODO don't rely on monkey patch
|
|
2341
|
+
if not self.dimensionless:
|
|
2342
|
+
ub = ub.over(*self.dimensions) # pyright: ignore[reportOptionalIterable]
|
|
2316
2343
|
self._ub_expr, self.ub = ub, None
|
|
2317
2344
|
else:
|
|
2318
2345
|
self._ub_expr, self.ub = None, ub
|
|
@@ -2518,7 +2545,7 @@ class Variable(BaseOperableBlock):
|
|
|
2518
2545
|
result = (
|
|
2519
2546
|
get_obj_repr(
|
|
2520
2547
|
self,
|
|
2521
|
-
self.name,
|
|
2548
|
+
f"'{self.name}'",
|
|
2522
2549
|
lb=self.lb,
|
|
2523
2550
|
ub=self.ub,
|
|
2524
2551
|
height=self.data.height if self.dimensions else None,
|
|
@@ -2581,7 +2608,7 @@ class Variable(BaseOperableBlock):
|
|
|
2581
2608
|
https://bravos-power.github.io/pyoframe/latest/learn/concepts/addition
|
|
2582
2609
|
|
|
2583
2610
|
>>> (m.bat_charge + m.bat_flow).drop_extras() == m.bat_charge.next("time")
|
|
2584
|
-
<Constraint 'unnamed' height=6 terms=18
|
|
2611
|
+
<Constraint 'unnamed' (linear) height=6 terms=18>
|
|
2585
2612
|
┌───────┬─────────┬────────────────────────────────────────────────────────────────────────────────┐
|
|
2586
2613
|
│ time ┆ city ┆ constraint │
|
|
2587
2614
|
│ (3) ┆ (2) ┆ │
|
|
@@ -2603,7 +2630,7 @@ class Variable(BaseOperableBlock):
|
|
|
2603
2630
|
>>> (m.bat_charge + m.bat_flow) == m.bat_charge.next(
|
|
2604
2631
|
... "time", wrap_around=True
|
|
2605
2632
|
... )
|
|
2606
|
-
<Constraint 'unnamed' height=8 terms=24
|
|
2633
|
+
<Constraint 'unnamed' (linear) height=8 terms=24>
|
|
2607
2634
|
┌───────┬─────────┬────────────────────────────────────────────────────────────────────────────────┐
|
|
2608
2635
|
│ time ┆ city ┆ constraint │
|
|
2609
2636
|
│ (4) ┆ (2) ┆ │
|
pyoframe/_model.py
CHANGED
|
@@ -234,7 +234,7 @@ class Model:
|
|
|
234
234
|
from pyoptinterface import ipopt
|
|
235
235
|
except ModuleNotFoundError as e: # pragma: no cover
|
|
236
236
|
raise ModuleNotFoundError(
|
|
237
|
-
"Failed to import the Ipopt solver. Did you run `pip install pyoptinterface[
|
|
237
|
+
"Failed to import the Ipopt solver. Did you run `pip install pyoptinterface[nlp]`?"
|
|
238
238
|
) from e
|
|
239
239
|
|
|
240
240
|
try:
|
|
@@ -335,7 +335,7 @@ class Model:
|
|
|
335
335
|
ValueError: Objective is not defined.
|
|
336
336
|
>>> m.maximize = m.X
|
|
337
337
|
>>> m.objective
|
|
338
|
-
<Objective terms=1
|
|
338
|
+
<Objective (linear) terms=1>
|
|
339
339
|
X
|
|
340
340
|
|
|
341
341
|
See Also:
|
|
@@ -417,7 +417,7 @@ class Model:
|
|
|
417
417
|
def __repr__(self) -> str:
|
|
418
418
|
return get_obj_repr(
|
|
419
419
|
self,
|
|
420
|
-
self.name,
|
|
420
|
+
f"'{self.name}'" if self.name is not None else None,
|
|
421
421
|
vars=len(self.variables),
|
|
422
422
|
constrs=len(self.constraints),
|
|
423
423
|
has_objective=self.has_objective,
|
pyoframe/_model_element.py
CHANGED
|
@@ -48,6 +48,8 @@ class BaseBlock(ABC):
|
|
|
48
48
|
self._data = data
|
|
49
49
|
self._model: Model | None = None
|
|
50
50
|
self.name: str = name # gets overwritten if object is added to model
|
|
51
|
+
"""A user-friendly name that is displayed when printing the object or in error messages.
|
|
52
|
+
When an object is added to a model, this name is updated to the name used in the model."""
|
|
51
53
|
|
|
52
54
|
def _on_add_to_model(self, model: Model, name: str):
|
|
53
55
|
self.name = name
|
pyoframe/_monkey_patch.py
CHANGED
|
@@ -5,8 +5,8 @@ from functools import wraps
|
|
|
5
5
|
import pandas as pd
|
|
6
6
|
import polars as pl
|
|
7
7
|
|
|
8
|
-
from pyoframe.
|
|
9
|
-
from pyoframe.
|
|
8
|
+
from pyoframe._core import BaseOperableBlock
|
|
9
|
+
from pyoframe._param import Param
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def _patch_class(cls):
|
|
@@ -29,54 +29,10 @@ def _patch_class(cls):
|
|
|
29
29
|
cls.__contains__ = _patch_method(cls.__contains__)
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
def polars_df_to_expr(self: pl.DataFrame) -> Expression:
|
|
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.
|
|
34
|
-
|
|
35
|
-
See [Special Functions](../../learn/concepts/special-functions.md#dataframeto_expr) for more details.
|
|
36
|
-
|
|
37
|
-
Examples:
|
|
38
|
-
>>> import polars as pl
|
|
39
|
-
>>> df = pl.DataFrame({"x": [1, 2, 3], "y": [4, 5, 6], "z": [7, 8, 9]})
|
|
40
|
-
>>> df.to_expr()
|
|
41
|
-
<Expression height=3 terms=3 type=constant>
|
|
42
|
-
┌─────┬─────┬────────────┐
|
|
43
|
-
│ x ┆ y ┆ expression │
|
|
44
|
-
│ (3) ┆ (3) ┆ │
|
|
45
|
-
╞═════╪═════╪════════════╡
|
|
46
|
-
│ 1 ┆ 4 ┆ 7 │
|
|
47
|
-
│ 2 ┆ 5 ┆ 8 │
|
|
48
|
-
│ 3 ┆ 6 ┆ 9 │
|
|
49
|
-
└─────┴─────┴────────────┘
|
|
50
|
-
"""
|
|
51
|
-
name = self.columns[-1]
|
|
52
|
-
return Expression(
|
|
53
|
-
self.rename({name: COEF_KEY})
|
|
54
|
-
.drop_nulls(COEF_KEY)
|
|
55
|
-
.with_columns(pl.lit(CONST_TERM).alias(VAR_KEY)),
|
|
56
|
-
name=name,
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def pandas_df_to_expr(self: pd.DataFrame) -> Expression:
|
|
61
|
-
"""Same as [`polars.DataFrame.to_expr`](./polars.DataFrame.to_expr.md), but for [pandas](https://pandas.pydata.org/) DataFrames."""
|
|
62
|
-
return polars_df_to_expr(pl.from_pandas(self))
|
|
63
|
-
|
|
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.
|
|
69
|
-
|
|
70
|
-
Note that no equivalent method exists for Polars Series, as Polars does not support indexes.
|
|
71
|
-
"""
|
|
72
|
-
return pandas_df_to_expr(self.to_frame().reset_index())
|
|
73
|
-
|
|
74
|
-
|
|
75
32
|
def patch_dataframe_libraries():
|
|
76
33
|
_patch_class(pd.DataFrame)
|
|
77
34
|
_patch_class(pd.Series)
|
|
78
35
|
_patch_class(pl.DataFrame)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
pd.
|
|
82
|
-
pd.Series.to_expr = pandas_series_to_expr
|
|
36
|
+
pl.DataFrame.to_expr = lambda self: Param(self) # type: ignore
|
|
37
|
+
pd.DataFrame.to_expr = lambda self: Param(self) # type: ignore
|
|
38
|
+
pd.Series.to_expr = lambda self: Param(self) # type: ignore
|
pyoframe/_objective.py
CHANGED
|
@@ -20,7 +20,7 @@ class Objective(Expression):
|
|
|
20
20
|
>>> m.con = m.A + m.B <= 10
|
|
21
21
|
>>> m.maximize = 2 * m.B + 4
|
|
22
22
|
>>> m.maximize
|
|
23
|
-
<Objective terms=2
|
|
23
|
+
<Objective (linear) terms=2>
|
|
24
24
|
2 B +4
|
|
25
25
|
|
|
26
26
|
The objective value can be retrieved with from the solver once the model is solved using `.value`.
|
|
@@ -67,7 +67,7 @@ class Objective(Expression):
|
|
|
67
67
|
if isinstance(expr, (int, float)):
|
|
68
68
|
expr = Expression.constant(expr)
|
|
69
69
|
else:
|
|
70
|
-
expr = expr.to_expr()
|
|
70
|
+
expr = expr.to_expr() # TODO don't rely on monkey patch
|
|
71
71
|
super().__init__(expr.data, name="objective")
|
|
72
72
|
self._model = expr._model
|
|
73
73
|
if self.dimensions is not None:
|
pyoframe/_param.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Defines the function for creating model parameters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import polars as pl
|
|
9
|
+
|
|
10
|
+
from pyoframe._constants import COEF_KEY, CONST_TERM, VAR_KEY
|
|
11
|
+
from pyoframe._core import Expression
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def Param(
|
|
15
|
+
data: pl.DataFrame | pd.DataFrame | pd.Series | dict | str | Path,
|
|
16
|
+
) -> Expression:
|
|
17
|
+
"""Creates a model parameter, i.e. an [Expression][pyoframe.Expression] that doesn't involve any variables.
|
|
18
|
+
|
|
19
|
+
A Parameter can be created from a DataFrame, CSV file, Parquet file, data dictionary, or a Pandas Series.
|
|
20
|
+
|
|
21
|
+
!!! info "`Param` is a function, not a class"
|
|
22
|
+
Technically, `Param(data)` is a function that returns an [Expression][pyoframe.Expression], not a class.
|
|
23
|
+
However, for consistency with other modeling frameworks, we provide it as a class-like function (i.e. an uppercase function).
|
|
24
|
+
|
|
25
|
+
!!! tip "Smart naming"
|
|
26
|
+
If a Param is not given a name (i.e. if it is not assigned to a model: `m.my_name = Param(...)`),
|
|
27
|
+
then its [name][pyoframe._model_element.BaseBlock.name] is inferred from the name of the column in `data` that contains the parameter values.
|
|
28
|
+
This makes debugging models with inline parameters easier.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
data: The data to use for the parameter.
|
|
32
|
+
|
|
33
|
+
If `data` is a polars or pandas `DataFrame`, the last column will be treated as the values of the parameter, and all other columns as labels.
|
|
34
|
+
|
|
35
|
+
If `data` is a string or `Path`, it will be interpreted as a path to a CSV or Parquet file that will be read and used as a `DataFrame`. The file extension must be `.csv` or `.parquet`.
|
|
36
|
+
|
|
37
|
+
If `data` is a `pandas.Series`, the index(es) will be treated as columns for labels and the series values as the parameter values.
|
|
38
|
+
|
|
39
|
+
If `data` is of any other type (e.g. a dictionary), it will be used as if you had called `Param(pl.DataFrame(data))`.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
An Expression representing the parameter.
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
>>> m = pf.Model()
|
|
46
|
+
>>> m.fixed_cost = pf.Param({"plant": ["A", "B"], "cost": [1000, 1500]})
|
|
47
|
+
>>> m.fixed_cost
|
|
48
|
+
<Expression (parameter) height=2 terms=2>
|
|
49
|
+
┌───────┬────────────┐
|
|
50
|
+
│ plant ┆ expression │
|
|
51
|
+
│ (2) ┆ │
|
|
52
|
+
╞═══════╪════════════╡
|
|
53
|
+
│ A ┆ 1000 │
|
|
54
|
+
│ B ┆ 1500 │
|
|
55
|
+
└───────┴────────────┘
|
|
56
|
+
|
|
57
|
+
Since `Param` simply returns an Expression, you can use it in building larger expressions as usual:
|
|
58
|
+
|
|
59
|
+
>>> m.variable_cost = pf.Param(
|
|
60
|
+
... pl.DataFrame({"plant": ["A", "B"], "cost": [50, 60]})
|
|
61
|
+
... )
|
|
62
|
+
>>> m.total_cost = m.fixed_cost + m.variable_cost
|
|
63
|
+
>>> m.total_cost
|
|
64
|
+
<Expression (parameter) height=2 terms=2>
|
|
65
|
+
┌───────┬────────────┐
|
|
66
|
+
│ plant ┆ expression │
|
|
67
|
+
│ (2) ┆ │
|
|
68
|
+
╞═══════╪════════════╡
|
|
69
|
+
│ A ┆ 1050 │
|
|
70
|
+
│ B ┆ 1560 │
|
|
71
|
+
└───────┴────────────┘
|
|
72
|
+
"""
|
|
73
|
+
if isinstance(data, pd.Series):
|
|
74
|
+
data = data.to_frame().reset_index()
|
|
75
|
+
if isinstance(data, pd.DataFrame):
|
|
76
|
+
data = pl.from_pandas(data)
|
|
77
|
+
|
|
78
|
+
if isinstance(data, (str, Path)):
|
|
79
|
+
data = Path(data)
|
|
80
|
+
if data.suffix.lower() == ".csv":
|
|
81
|
+
data = pl.read_csv(data)
|
|
82
|
+
elif data.suffix.lower() in {".parquet"}:
|
|
83
|
+
data = pl.read_parquet(data)
|
|
84
|
+
else:
|
|
85
|
+
raise NotImplementedError(
|
|
86
|
+
f"Could not create parameter. Unsupported file format: {data.suffix}"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if not isinstance(data, pl.DataFrame):
|
|
90
|
+
data = pl.DataFrame(data)
|
|
91
|
+
|
|
92
|
+
value_col = data.columns[-1]
|
|
93
|
+
|
|
94
|
+
return Expression(
|
|
95
|
+
data.rename({value_col: COEF_KEY})
|
|
96
|
+
.drop_nulls(COEF_KEY)
|
|
97
|
+
.with_columns(pl.lit(CONST_TERM).alias(VAR_KEY)),
|
|
98
|
+
name=value_col,
|
|
99
|
+
)
|
pyoframe/_utils.py
CHANGED
|
@@ -42,7 +42,7 @@ def get_obj_repr(obj: object, *props: str | None, **kwargs):
|
|
|
42
42
|
|
|
43
43
|
See usage for examples.
|
|
44
44
|
"""
|
|
45
|
-
props_str = " ".join(
|
|
45
|
+
props_str = " ".join(v for v in props if v is not None)
|
|
46
46
|
if props_str:
|
|
47
47
|
props_str += " "
|
|
48
48
|
kwargs_str = " ".join(f"{k}={v}" for k, v in kwargs.items() if v is not None)
|
|
@@ -302,7 +302,7 @@ class NamedVariableMapper:
|
|
|
302
302
|
>>> m = pf.Model()
|
|
303
303
|
>>> m.foo = pf.Variable(pl.DataFrame({"t": range(4)}))
|
|
304
304
|
>>> m.foo.sum()
|
|
305
|
-
<Expression terms=4
|
|
305
|
+
<Expression (linear) terms=4>
|
|
306
306
|
foo[0] + foo[1] + foo[2] + foo[3]
|
|
307
307
|
"""
|
|
308
308
|
|
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,
|
|
31
|
+
__version__ = version = '1.1.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 1, 0)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyoframe
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Blazing fast linear program interface
|
|
5
5
|
Author-email: Bravos Power <dev@bravospower.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -20,21 +20,21 @@ Requires-Dist: pyarrow
|
|
|
20
20
|
Requires-Dist: pandas<3
|
|
21
21
|
Requires-Dist: pyoptinterface==0.5.1
|
|
22
22
|
Provides-Extra: highs
|
|
23
|
-
Requires-Dist: highsbox<=1.
|
|
23
|
+
Requires-Dist: highsbox<=1.12.0; extra == "highs"
|
|
24
24
|
Provides-Extra: ipopt
|
|
25
25
|
Requires-Dist: pyoptinterface[nlp]; extra == "ipopt"
|
|
26
|
-
Requires-Dist: llvmlite<=0.
|
|
26
|
+
Requires-Dist: llvmlite<=0.46.0; extra == "ipopt"
|
|
27
27
|
Provides-Extra: dev
|
|
28
28
|
Requires-Dist: ruff==0.12.11; extra == "dev"
|
|
29
29
|
Requires-Dist: polars>=1.32.3; extra == "dev"
|
|
30
30
|
Requires-Dist: pytest==8.4.1; extra == "dev"
|
|
31
31
|
Requires-Dist: pytest-cov==6.2.1; extra == "dev"
|
|
32
|
-
Requires-Dist: sybil[pytest]==9.
|
|
32
|
+
Requires-Dist: sybil[pytest]==9.3.0; extra == "dev"
|
|
33
33
|
Requires-Dist: pre-commit==4.3.0; extra == "dev"
|
|
34
34
|
Requires-Dist: gurobipy==12.0.3; extra == "dev"
|
|
35
35
|
Requires-Dist: coverage==7.10.6; extra == "dev"
|
|
36
36
|
Requires-Dist: ipykernel==6.30.1; extra == "dev"
|
|
37
|
-
Requires-Dist: highsbox<=1.
|
|
37
|
+
Requires-Dist: highsbox<=1.12.0; extra == "dev"
|
|
38
38
|
Requires-Dist: pyoptinterface[nlp]; extra == "dev"
|
|
39
39
|
Requires-Dist: numpy; extra == "dev"
|
|
40
40
|
Provides-Extra: docs
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
pyoframe/__init__.py,sha256=Ij-9priyKTHaGzVhMhtZlDKWz0ggAwGAS9DqB4O6zWU,886
|
|
2
|
+
pyoframe/_arithmetic.py,sha256=VZAlZZQ7StYMRbJcfWGZ7he2Ds6b6zoY0J1GxG99UWM,20549
|
|
3
|
+
pyoframe/_constants.py,sha256=LWlry4K5w-3vVyq7CpEQ28UfM3LulbKxkO-nBlWWJzE,17847
|
|
4
|
+
pyoframe/_core.py,sha256=EiuAhW9M2616gjW8htJqP4FuSlUb3zfwWP1ydTsQ1Qo,118507
|
|
5
|
+
pyoframe/_model.py,sha256=IcmqfJ1agNF4LzHUR-bXr-K-3NXsJGcBU6Ga55jcmik,22503
|
|
6
|
+
pyoframe/_model_element.py,sha256=VVRqh2uM8HFvRFvqQmgM93jqofa-8mPwyB-qYA0YjRU,6345
|
|
7
|
+
pyoframe/_monkey_patch.py,sha256=7FWMRXZIHK7mRkZfOKQ8Y724q1sPbq_EiPjlJCTfYoA,1168
|
|
8
|
+
pyoframe/_objective.py,sha256=HeiP4KjlXn-IplqV-MALF26yMmh41JyHXjZhhtKJIsQ,4367
|
|
9
|
+
pyoframe/_param.py,sha256=FUSfITPb-WZA-xwVcF9dCCHO2K_pky5GBWooImsSy6I,4147
|
|
10
|
+
pyoframe/_utils.py,sha256=XaPZ8j9YQ-HnAuT2NLAvDCJGVzKSjUmRxARNuGWykIM,12508
|
|
11
|
+
pyoframe/_version.py,sha256=ePNVzJOkxR8FY5bezqKQ_fgBRbzH1G7QTaRDHvGQRAY,704
|
|
12
|
+
pyoframe-1.1.0.dist-info/licenses/LICENSE,sha256=u_Spw4ynlwTMRZeCX-uacv_hBU547pBygiA6d2ONNV4,1074
|
|
13
|
+
pyoframe-1.1.0.dist-info/METADATA,sha256=cO4Wu3bBxixzFjhmkzi40kRkPbAmRiQ161L8dz0MQjo,4060
|
|
14
|
+
pyoframe-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
15
|
+
pyoframe-1.1.0.dist-info/top_level.txt,sha256=10z3OOJSVLriQ0IrFLMH8CH9zByugPWolqhlHlkNjV4,9
|
|
16
|
+
pyoframe-1.1.0.dist-info/RECORD,,
|
pyoframe-1.0.1.dist-info/RECORD
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|