pyoframe 0.0.11__tar.gz → 0.1.1__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 (35) hide show
  1. {pyoframe-0.0.11 → pyoframe-0.1.1}/LICENSE +0 -2
  2. {pyoframe-0.0.11/src/pyoframe.egg-info → pyoframe-0.1.1}/PKG-INFO +14 -12
  3. {pyoframe-0.0.11 → pyoframe-0.1.1}/README.md +4 -8
  4. {pyoframe-0.0.11 → pyoframe-0.1.1}/pyproject.toml +15 -4
  5. {pyoframe-0.0.11 → pyoframe-0.1.1}/src/pyoframe/__init__.py +3 -4
  6. {pyoframe-0.0.11 → pyoframe-0.1.1}/src/pyoframe/_arithmetic.py +170 -4
  7. pyoframe-0.1.1/src/pyoframe/constants.py +135 -0
  8. {pyoframe-0.0.11 → pyoframe-0.1.1}/src/pyoframe/core.py +746 -421
  9. pyoframe-0.1.1/src/pyoframe/model.py +348 -0
  10. {pyoframe-0.0.11 → pyoframe-0.1.1}/src/pyoframe/model_element.py +43 -84
  11. {pyoframe-0.0.11 → pyoframe-0.1.1}/src/pyoframe/monkey_patch.py +3 -3
  12. pyoframe-0.1.1/src/pyoframe/objective.py +95 -0
  13. {pyoframe-0.0.11 → pyoframe-0.1.1}/src/pyoframe/util.py +157 -19
  14. {pyoframe-0.0.11 → pyoframe-0.1.1/src/pyoframe.egg-info}/PKG-INFO +14 -12
  15. {pyoframe-0.0.11 → pyoframe-0.1.1}/src/pyoframe.egg-info/SOURCES.txt +1 -4
  16. {pyoframe-0.0.11 → pyoframe-0.1.1}/src/pyoframe.egg-info/requires.txt +7 -1
  17. {pyoframe-0.0.11 → pyoframe-0.1.1}/tests/test_arithmetic.py +78 -57
  18. pyoframe-0.1.1/tests/test_examples.py +198 -0
  19. pyoframe-0.1.1/tests/test_io.py +75 -0
  20. pyoframe-0.1.1/tests/test_model.py +56 -0
  21. {pyoframe-0.0.11 → pyoframe-0.1.1}/tests/test_operations.py +8 -6
  22. pyoframe-0.1.1/tests/test_solver.py +214 -0
  23. pyoframe-0.0.11/src/pyoframe/constants.py +0 -291
  24. pyoframe-0.0.11/src/pyoframe/io.py +0 -252
  25. pyoframe-0.0.11/src/pyoframe/io_mappers.py +0 -238
  26. pyoframe-0.0.11/src/pyoframe/model.py +0 -116
  27. pyoframe-0.0.11/src/pyoframe/objective.py +0 -45
  28. pyoframe-0.0.11/src/pyoframe/solvers.py +0 -377
  29. pyoframe-0.0.11/src/pyoframe/user_defined.py +0 -60
  30. pyoframe-0.0.11/tests/test_examples.py +0 -157
  31. pyoframe-0.0.11/tests/test_io.py +0 -53
  32. pyoframe-0.0.11/tests/test_solver.py +0 -162
  33. {pyoframe-0.0.11 → pyoframe-0.1.1}/setup.cfg +0 -0
  34. {pyoframe-0.0.11 → pyoframe-0.1.1}/src/pyoframe.egg-info/dependency_links.txt +0 -0
  35. {pyoframe-0.0.11 → pyoframe-0.1.1}/src/pyoframe.egg-info/top_level.txt +0 -0
@@ -1,8 +1,6 @@
1
1
  MIT License
2
2
 
3
3
  Copyright 2024 Bravos Power
4
- Copyright 2021-2023 Fabian Hofmann
5
- Copyright 2015-2021 PyPSA Developers
6
4
 
7
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
8
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: pyoframe
3
- Version: 0.0.11
3
+ Version: 0.1.1
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/
@@ -12,7 +12,7 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Development Status :: 3 - Alpha
13
13
  Classifier: License :: OSI Approved :: MIT License
14
14
  Classifier: Natural Language :: English
15
- Requires-Python: >=3.8
15
+ Requires-Python: >=3.9
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
18
  Requires-Dist: polars<2,>=0.20
@@ -20,8 +20,9 @@ Requires-Dist: numpy
20
20
  Requires-Dist: pyarrow
21
21
  Requires-Dist: pandas
22
22
  Requires-Dist: packaging
23
+ Requires-Dist: pyoptinterface~=0.4
23
24
  Provides-Extra: dev
24
- Requires-Dist: black; extra == "dev"
25
+ Requires-Dist: black[jupyter]; extra == "dev"
25
26
  Requires-Dist: bumpver; extra == "dev"
26
27
  Requires-Dist: isort; extra == "dev"
27
28
  Requires-Dist: pip-tools; extra == "dev"
@@ -29,6 +30,8 @@ Requires-Dist: pytest; extra == "dev"
29
30
  Requires-Dist: pytest-cov; extra == "dev"
30
31
  Requires-Dist: pre-commit; extra == "dev"
31
32
  Requires-Dist: gurobipy; extra == "dev"
33
+ Requires-Dist: highsbox; extra == "dev"
34
+ Requires-Dist: pre-commit; extra == "dev"
32
35
  Provides-Extra: docs
33
36
  Requires-Dist: mkdocs-material==9.*; extra == "docs"
34
37
  Requires-Dist: mkdocstrings[python]; extra == "docs"
@@ -37,6 +40,9 @@ Requires-Dist: mkdocs-git-committers-plugin-2; extra == "docs"
37
40
  Requires-Dist: mkdocs-gen-files; extra == "docs"
38
41
  Requires-Dist: mkdocs-section-index; extra == "docs"
39
42
  Requires-Dist: mkdocs-literate-nav; extra == "docs"
43
+ Requires-Dist: mkdocs-table-reader-plugin; extra == "docs"
44
+ Provides-Extra: highs
45
+ Requires-Dist: highsbox; extra == "highs"
40
46
 
41
47
  # Pyoframe: Fast and low-memory linear programming models
42
48
 
@@ -50,16 +56,12 @@ Requires-Dist: mkdocs-literate-nav; extra == "docs"
50
56
 
51
57
  A library to rapidly and memory-efficiently formulate large and sparse optimization models using Pandas or Polars dataframes.
52
58
 
53
- ## Contribute
54
-
55
- Contributions are welcome! See [`CONTRIBUTE.md`](./CONTRIBUTE.md).
59
+ ## **[Documentation](https://bravos-power.github.io/pyoframe/)**
56
60
 
57
- ## Acknowledgments
61
+ [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/).
58
62
 
59
- Martin Staadecker first created this library while working for [Bravos Power](https://www.bravospower.com/) The library takes inspiration from Linopy and Pyomo, two prior libraries for optimization for which we are thankful.
60
63
 
61
- ## Troubleshooting Common Errors
64
+ ## Acknowledgments
62
65
 
63
- ### `datatypes of join keys don't match`
66
+ Martin Staadecker first created this library while working for [Bravos Power](https://www.bravospower.com/). The library takes inspiration from Linopy and Pyomo, two prior libraries for optimization for which we are thankful.
64
67
 
65
- Often, this error indicates that two dataframes in your inputs representing the same dimension have different datatypes (e.g. 16bit integer and 64bit integer). This is not allowed and you should ensure for the same dimensions, datatypes are identical.
@@ -10,16 +10,12 @@
10
10
 
11
11
  A library to rapidly and memory-efficiently formulate large and sparse optimization models using Pandas or Polars dataframes.
12
12
 
13
- ## Contribute
13
+ ## **[Documentation](https://bravos-power.github.io/pyoframe/)**
14
14
 
15
- Contributions are welcome! See [`CONTRIBUTE.md`](./CONTRIBUTE.md).
15
+ [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/).
16
16
 
17
- ## Acknowledgments
18
-
19
- Martin Staadecker first created this library while working for [Bravos Power](https://www.bravospower.com/) The library takes inspiration from Linopy and Pyomo, two prior libraries for optimization for which we are thankful.
20
17
 
21
- ## Troubleshooting Common Errors
18
+ ## Acknowledgments
22
19
 
23
- ### `datatypes of join keys don't match`
20
+ Martin Staadecker first created this library while working for [Bravos Power](https://www.bravospower.com/). The library takes inspiration from Linopy and Pyomo, two prior libraries for optimization for which we are thankful.
24
21
 
25
- Often, this error indicates that two dataframes in your inputs representing the same dimension have different datatypes (e.g. 16bit integer and 64bit integer). This is not allowed and you should ensure for the same dimensions, datatypes are identical.
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pyoframe"
7
- version = "0.0.11"
7
+ version = "0.1.1"
8
8
  authors = [{ name = "Bravos Power", email = "dev@bravospower.com" }]
9
9
  description = "Blazing fast linear program interface"
10
10
  readme = "README.md"
11
- requires-python = ">=3.8"
11
+ requires-python = ">=3.9"
12
12
  classifiers = [
13
13
  "Programming Language :: Python :: 3",
14
14
  "Operating System :: OS Independent",
@@ -16,11 +16,18 @@ classifiers = [
16
16
  "License :: OSI Approved :: MIT License",
17
17
  "Natural Language :: English",
18
18
  ]
19
- dependencies = ["polars>=0.20,<2", "numpy", "pyarrow", "pandas", "packaging"]
19
+ dependencies = [
20
+ "polars>=0.20,<2",
21
+ "numpy",
22
+ "pyarrow",
23
+ "pandas",
24
+ "packaging",
25
+ "pyoptinterface~=0.4",
26
+ ]
20
27
 
21
28
  [project.optional-dependencies]
22
29
  dev = [
23
- "black",
30
+ "black[jupyter]",
24
31
  "bumpver",
25
32
  "isort",
26
33
  "pip-tools",
@@ -28,6 +35,8 @@ dev = [
28
35
  "pytest-cov",
29
36
  "pre-commit",
30
37
  "gurobipy",
38
+ "highsbox",
39
+ "pre-commit"
31
40
  ]
32
41
  docs = [
33
42
  "mkdocs-material==9.*",
@@ -37,7 +46,9 @@ docs = [
37
46
  "mkdocs-gen-files",
38
47
  "mkdocs-section-index",
39
48
  "mkdocs-literate-nav",
49
+ "mkdocs-table-reader-plugin"
40
50
  ]
51
+ highs = ["highsbox"]
41
52
 
42
53
  [tool.isort]
43
54
  profile = "black"
@@ -3,11 +3,10 @@ Pyoframe's public API.
3
3
  Also applies the monkey patch to the DataFrame libraries.
4
4
  """
5
5
 
6
- from pyoframe.monkey_patch import patch_dataframe_libraries
7
- from pyoframe.core import sum, sum_by, Set, Constraint, Expression, Variable
8
- from pyoframe.constants import Config
6
+ from pyoframe.constants import Config, VType
7
+ from pyoframe.core import Constraint, Expression, Set, Variable, sum, sum_by
9
8
  from pyoframe.model import Model
10
- from pyoframe.constants import VType
9
+ from pyoframe.monkey_patch import patch_dataframe_libraries
11
10
 
12
11
  patch_dataframe_libraries()
13
12
 
@@ -1,19 +1,63 @@
1
+ """
2
+ Defines helper functions for doing arithmetic operations on expressions (e.g. addition).
3
+ """
4
+
1
5
  from typing import TYPE_CHECKING, List, Optional
6
+
2
7
  import polars as pl
3
8
 
4
9
  from pyoframe.constants import (
5
10
  COEF_KEY,
11
+ CONST_TERM,
12
+ KEY_TYPE,
13
+ POLARS_VERSION,
14
+ QUAD_VAR_KEY,
6
15
  RESERVED_COL_KEYS,
7
16
  VAR_KEY,
8
- UnmatchedStrategy,
9
17
  Config,
10
18
  PyoframeError,
19
+ UnmatchedStrategy,
11
20
  )
12
21
 
13
22
  if TYPE_CHECKING: # pragma: no cover
14
23
  from pyoframe.core import Expression
15
24
 
16
25
 
26
+ def _multiply_expressions(self: "Expression", other: "Expression") -> "Expression":
27
+ """
28
+ Multiply two or more expressions together.
29
+
30
+ Examples:
31
+ >>> import pyoframe as pf
32
+ >>> m = pf.Model("min")
33
+ >>> m.x1 = pf.Variable()
34
+ >>> m.x2 = pf.Variable()
35
+ >>> m.x3 = pf.Variable()
36
+ >>> result = 5 * m.x1 * m.x2
37
+ >>> result
38
+ <Expression size=1 dimensions={} terms=1 degree=2>
39
+ 5 x2 * x1
40
+ >>> result * m.x3
41
+ Traceback (most recent call last):
42
+ ...
43
+ pyoframe.constants.PyoframeError: Failed to multiply expressions:
44
+ <Expression size=1 dimensions={} terms=1 degree=2> * <Expression size=1 dimensions={} terms=1>
45
+ Due to error:
46
+ Cannot multiply a quadratic expression by a non-constant.
47
+ """
48
+ try:
49
+ return _multiply_expressions_core(self, other)
50
+ except PyoframeError as error:
51
+ raise PyoframeError(
52
+ "Failed to multiply expressions:\n"
53
+ + " * ".join(
54
+ e.to_str(include_header=True, include_data=False) for e in [self, other]
55
+ )
56
+ + "\nDue to error:\n"
57
+ + str(error)
58
+ ) from error
59
+
60
+
17
61
  def _add_expressions(*expressions: "Expression") -> "Expression":
18
62
  try:
19
63
  return _add_expressions_core(*expressions)
@@ -28,6 +72,98 @@ def _add_expressions(*expressions: "Expression") -> "Expression":
28
72
  ) from error
29
73
 
30
74
 
75
+ def _multiply_expressions_core(self: "Expression", other: "Expression") -> "Expression":
76
+ self_degree, other_degree = self.degree(), other.degree()
77
+ if self_degree + other_degree > 2:
78
+ # We know one of the two must be a quadratic since 1 + 1 is not greater than 2.
79
+ raise PyoframeError("Cannot multiply a quadratic expression by a non-constant.")
80
+ if self_degree < other_degree:
81
+ self, other = other, self
82
+ self_degree, other_degree = other_degree, self_degree
83
+ if other_degree == 1:
84
+ assert (
85
+ self_degree == 1
86
+ ), "This should always be true since the sum of degrees must be <=2."
87
+ return _quadratic_multiplication(self, other)
88
+
89
+ assert (
90
+ other_degree == 0
91
+ ), "This should always be true since other cases have already been handled."
92
+ multiplier = other.data.drop(
93
+ VAR_KEY
94
+ ) # QUAD_VAR_KEY doesn't need to be dropped since we know it doesn't exist
95
+
96
+ dims = self.dimensions_unsafe
97
+ other_dims = other.dimensions_unsafe
98
+ dims_in_common = [dim for dim in dims if dim in other_dims]
99
+
100
+ data = (
101
+ self.data.join(
102
+ multiplier,
103
+ on=dims_in_common if len(dims_in_common) > 0 else None,
104
+ how="inner" if dims_in_common else "cross",
105
+ )
106
+ .with_columns(pl.col(COEF_KEY) * pl.col(COEF_KEY + "_right"))
107
+ .drop(COEF_KEY + "_right")
108
+ )
109
+
110
+ return self._new(data)
111
+
112
+
113
+ def _quadratic_multiplication(self: "Expression", other: "Expression") -> "Expression":
114
+ """
115
+ Multiply two expressions of degree 1.
116
+
117
+ Examples:
118
+ >>> import polars as pl
119
+ >>> df = pl.DataFrame({"dim": [1, 2, 3], "value": [1, 2, 3]})
120
+ >>> m = pf.Model()
121
+ >>> m.x1 = pf.Variable()
122
+ >>> m.x2 = pf.Variable()
123
+ >>> expr1 = df * m.x1
124
+ >>> expr2 = df * m.x2 * 2 + 4
125
+ >>> expr1 * expr2
126
+ <Expression size=3 dimensions={'dim': 3} terms=6 degree=2>
127
+ [1]: 4 x1 +2 x2 * x1
128
+ [2]: 8 x1 +8 x2 * x1
129
+ [3]: 12 x1 +18 x2 * x1
130
+ >>> (expr1 * expr2) - df * m.x1 * df * m.x2 * 2
131
+ <Expression size=3 dimensions={'dim': 3} terms=3>
132
+ [1]: 4 x1
133
+ [2]: 8 x1
134
+ [3]: 12 x1
135
+ """
136
+ dims = self.dimensions_unsafe
137
+ other_dims = other.dimensions_unsafe
138
+ dims_in_common = [dim for dim in dims if dim in other_dims]
139
+
140
+ data = (
141
+ self.data.join(
142
+ other.data,
143
+ on=dims_in_common if len(dims_in_common) > 0 else None,
144
+ how="inner" if dims_in_common else "cross",
145
+ )
146
+ .with_columns(pl.col(COEF_KEY) * pl.col(COEF_KEY + "_right"))
147
+ .drop(COEF_KEY + "_right")
148
+ .rename({VAR_KEY + "_right": QUAD_VAR_KEY})
149
+ # Swap VAR_KEY and QUAD_VAR_KEY so that VAR_KEy is always the larger one
150
+ .with_columns(
151
+ pl.when(pl.col(VAR_KEY) < pl.col(QUAD_VAR_KEY))
152
+ .then(pl.col(QUAD_VAR_KEY))
153
+ .otherwise(pl.col(VAR_KEY))
154
+ .alias(VAR_KEY),
155
+ pl.when(pl.col(VAR_KEY) < pl.col(QUAD_VAR_KEY))
156
+ .then(pl.col(VAR_KEY))
157
+ .otherwise(pl.col(QUAD_VAR_KEY))
158
+ .alias(QUAD_VAR_KEY),
159
+ )
160
+ )
161
+
162
+ data = _sum_like_terms(data)
163
+
164
+ return self._new(data)
165
+
166
+
31
167
  def _add_expressions_core(*expressions: "Expression") -> "Expression":
32
168
  # Mapping of how a sum of two expressions should propogate the unmatched strategy
33
169
  propogatation_strategies = {
@@ -116,7 +252,9 @@ def _add_expressions_core(*expressions: "Expression") -> "Expression":
116
252
  not Config.disable_unmatched_checks
117
253
  ), "This code should not be reached when unmatched checks are disabled."
118
254
  outer_join = get_indices(left).join(
119
- get_indices(right), how="outer", on=dims
255
+ get_indices(right),
256
+ how="full" if POLARS_VERSION.major >= 1 else "outer",
257
+ on=dims,
120
258
  )
121
259
  if outer_join.get_column(dims[0]).null_count() > 0:
122
260
  raise PyoframeError(
@@ -159,11 +297,24 @@ def _add_expressions_core(*expressions: "Expression") -> "Expression":
159
297
  propogate_strat = expressions[0].unmatched_strategy
160
298
  expr_data = [expr.data for expr in expressions]
161
299
 
300
+ # Add quadratic column if it is needed and doesn't already exist
301
+ if any(QUAD_VAR_KEY in df.columns for df in expr_data):
302
+ expr_data = [
303
+ (
304
+ df.with_columns(pl.lit(CONST_TERM).alias(QUAD_VAR_KEY).cast(KEY_TYPE))
305
+ if QUAD_VAR_KEY not in df.columns
306
+ else df
307
+ )
308
+ for df in expr_data
309
+ ]
310
+
162
311
  # Sort columns to allow for concat
163
- expr_data = [e.select(sorted(e.columns)) for e in expr_data]
312
+ expr_data = [
313
+ e.select(dims + [c for c in e.columns if c not in dims]) for e in expr_data
314
+ ]
164
315
 
165
316
  data = pl.concat(expr_data, how="vertical_relaxed")
166
- data = data.group_by(dims + [VAR_KEY], maintain_order=True).sum()
317
+ data = _sum_like_terms(data)
167
318
 
168
319
  new_expr = expressions[0]._new(data)
169
320
  new_expr.unmatched_strategy = propogate_strat
@@ -188,6 +339,7 @@ def _add_dimension(self: "Expression", target: "Expression") -> "Expression":
188
339
  return self
189
340
 
190
341
  if not set(missing_dims) <= set(self.allowed_new_dims):
342
+ # TODO actually suggest using e.g. .add_dim("a", "b") instead of just "use .add_dim()"
191
343
  raise PyoframeError(
192
344
  f"Dataframe has missing dimensions {missing_dims}. If this is intentional, use .add_dim()\n{self.data}"
193
345
  )
@@ -210,6 +362,20 @@ def _add_dimension(self: "Expression", target: "Expression") -> "Expression":
210
362
  return self._new(result)
211
363
 
212
364
 
365
+ def _sum_like_terms(df: pl.DataFrame) -> pl.DataFrame:
366
+ """Combines terms with the same variables. Removes quadratic column if they all happen to cancel."""
367
+ dims = [c for c in df.columns if c not in RESERVED_COL_KEYS]
368
+ var_cols = [VAR_KEY] + ([QUAD_VAR_KEY] if QUAD_VAR_KEY in df.columns else [])
369
+ df = (
370
+ df.group_by(dims + var_cols, maintain_order=True)
371
+ .sum()
372
+ .filter(pl.col(COEF_KEY) != 0)
373
+ )
374
+ if QUAD_VAR_KEY in df.columns and (df.get_column(QUAD_VAR_KEY) == CONST_TERM).all():
375
+ df = df.drop(QUAD_VAR_KEY)
376
+ return df
377
+
378
+
213
379
  def _get_dimensions(df: pl.DataFrame) -> Optional[List[str]]:
214
380
  """
215
381
  Returns the dimensions of the DataFrame. Reserved columns do not count as dimensions.
@@ -0,0 +1,135 @@
1
+ """
2
+ File containing shared constants used across the package.
3
+ """
4
+
5
+ import importlib.metadata
6
+ import typing
7
+ from enum import Enum
8
+ from typing import Literal, Optional
9
+
10
+ import polars as pl
11
+ import pyoptinterface as poi
12
+ from packaging import version
13
+
14
+ # We want to try and support multiple major versions of polars
15
+ POLARS_VERSION = version.parse(importlib.metadata.version("polars"))
16
+
17
+ COEF_KEY = "__coeff"
18
+ VAR_KEY = "__variable_id"
19
+ QUAD_VAR_KEY = "__quadratic_variable_id"
20
+ CONSTRAINT_KEY = "__constraint_id"
21
+ SOLUTION_KEY = "solution"
22
+ DUAL_KEY = "dual"
23
+ SUPPORTED_SOLVER_TYPES = Literal["gurobi", "highs"]
24
+ KEY_TYPE = pl.UInt32
25
+
26
+ # Variable ID for constant terms. This variable ID is reserved.
27
+ CONST_TERM = 0
28
+
29
+ RESERVED_COL_KEYS = (
30
+ COEF_KEY,
31
+ VAR_KEY,
32
+ QUAD_VAR_KEY,
33
+ CONSTRAINT_KEY,
34
+ SOLUTION_KEY,
35
+ DUAL_KEY,
36
+ )
37
+
38
+
39
+ class _ConfigMeta(type):
40
+ """Metaclass for Config that stores the default values of all configuration options."""
41
+
42
+ def __init__(cls, name, bases, dct):
43
+ super().__init__(name, bases, dct)
44
+ cls._defaults = {
45
+ k: v
46
+ for k, v in dct.items()
47
+ if not k.startswith("_") and type(v) != classmethod
48
+ }
49
+
50
+
51
+ class Config(metaclass=_ConfigMeta):
52
+ """
53
+ Configuration options that apply to the entire library.
54
+ """
55
+
56
+ default_solver: Optional[SUPPORTED_SOLVER_TYPES] = None
57
+ disable_unmatched_checks: bool = False
58
+ float_to_str_precision: Optional[int] = 5
59
+ print_uses_variable_names: bool = True
60
+ print_max_line_length: int = 80
61
+ print_max_lines: int = 15
62
+ # Number of elements to show when printing a set to the console (additional elements are replaced with ...)
63
+ print_max_set_elements: int = 50
64
+ enable_is_duplicated_expression_safety_check: bool = False
65
+
66
+ @classmethod
67
+ def reset_defaults(cls):
68
+ """
69
+ Resets all configuration options to their default values.
70
+ """
71
+ for key, value in cls._defaults.items():
72
+ setattr(cls, key, value)
73
+
74
+
75
+ class ConstraintSense(Enum):
76
+ LE = "<="
77
+ GE = ">="
78
+ EQ = "="
79
+
80
+ def to_poi(self):
81
+ if self == ConstraintSense.LE:
82
+ return poi.ConstraintSense.LessEqual
83
+ elif self == ConstraintSense.EQ:
84
+ return poi.ConstraintSense.Equal
85
+ elif self == ConstraintSense.GE:
86
+ return poi.ConstraintSense.GreaterEqual
87
+ else:
88
+ raise ValueError(f"Invalid constraint type: {self}") # pragma: no cover
89
+
90
+
91
+ class ObjSense(Enum):
92
+ MIN = "min"
93
+ MAX = "max"
94
+
95
+ def to_poi(self):
96
+ if self == ObjSense.MIN:
97
+ return poi.ObjectiveSense.Minimize
98
+ elif self == ObjSense.MAX:
99
+ return poi.ObjectiveSense.Maximize
100
+ else:
101
+ raise ValueError(f"Invalid objective sense: {self}") # pragma: no cover
102
+
103
+
104
+ class VType(Enum):
105
+ CONTINUOUS = "continuous"
106
+ BINARY = "binary"
107
+ INTEGER = "integer"
108
+
109
+ def to_poi(self):
110
+ if self == VType.CONTINUOUS:
111
+ return poi.VariableDomain.Continuous
112
+ elif self == VType.BINARY:
113
+ return poi.VariableDomain.Binary
114
+ elif self == VType.INTEGER:
115
+ return poi.VariableDomain.Integer
116
+ else:
117
+ raise ValueError(f"Invalid variable type: {self}") # pragma: no cover
118
+
119
+
120
+ class UnmatchedStrategy(Enum):
121
+ UNSET = "not_set"
122
+ DROP = "drop"
123
+ KEEP = "keep"
124
+
125
+
126
+ # This is a hack to get the Literal type for VType
127
+ # See: https://stackoverflow.com/questions/67292470/type-hinting-enum-member-value-in-python
128
+ ObjSenseValue = Literal["min", "max"]
129
+ VTypeValue = Literal["continuous", "binary", "integer"]
130
+ for enum, type in [(ObjSense, ObjSenseValue), (VType, VTypeValue)]:
131
+ assert set(typing.get_args(type)) == {vtype.value for vtype in enum}
132
+
133
+
134
+ class PyoframeError(Exception):
135
+ pass