pyoframe 1.0.0a0__py3-none-any.whl → 1.0.1__py3-none-any.whl

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