pyoframe 0.0.8__tar.gz → 0.0.10__tar.gz

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