pyoframe 0.2.0__py3-none-any.whl → 1.0.0a0__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 DELETED
@@ -1,408 +0,0 @@
1
- from pathlib import Path
2
- from typing import Any, Dict, Iterable, List, Optional, Union
3
-
4
- import pandas as pd
5
- import polars as pl
6
- import pyoptinterface as poi
7
-
8
- from pyoframe.constants import (
9
- CONST_TERM,
10
- SUPPORTED_SOLVER_TYPES,
11
- SUPPORTED_SOLVERS,
12
- Config,
13
- ObjSense,
14
- ObjSenseValue,
15
- PyoframeError,
16
- VType,
17
- )
18
- from pyoframe.core import Constraint, Variable
19
- from pyoframe.model_element import ModelElement, ModelElementWithId
20
- from pyoframe.objective import Objective
21
- from pyoframe.util import Container, NamedVariableMapper, for_solvers, get_obj_repr
22
-
23
-
24
- class Model:
25
- """
26
- The object that holds all the variables, constraints, and the objective.
27
-
28
- Parameters:
29
- name:
30
- The name of the model. Currently it is not used for much.
31
- solver:
32
- The solver to use. If `None`, `Config.default_solver` will be used.
33
- If `Config.default_solver` has not been set (`None`), Pyoframe will try to detect whichever solver is already installed.
34
- solver_env:
35
- Gurobi only: a dictionary of parameters to set when creating the Gurobi environment.
36
- use_var_names:
37
- Whether to pass variable names to the solver. Set to `True` if you'd like outputs from e.g. `Model.write()` to be legible.
38
- Does not work with HiGHS (see [here](https://github.com/Bravos-Power/pyoframe/issues/102#issuecomment-2727521430)).
39
- sense:
40
- Either "min" or "max". Indicates whether it's a minmization or maximization problem.
41
- Typically, this parameter can be omitted (`None`) as it will automatically be
42
- set when the objective is set using `.minimize` or `.maximize`.
43
-
44
- Examples:
45
- >>> m = pf.Model()
46
- >>> m.X = pf.Variable()
47
- >>> m.my_constraint = m.X <= 10
48
- >>> m
49
- <Model vars=1 constrs=1 objective=False>
50
-
51
- Try setting the Gurobi license:
52
- >>> m = pf.Model(solver="gurobi", solver_env=dict(ComputeServer="myserver", ServerPassword="mypassword"))
53
- Traceback (most recent call last):
54
- ...
55
- RuntimeError: Could not resolve host: myserver (code 6, command POST http://myserver/api/v1/cluster/jobs)
56
- """
57
-
58
- _reserved_attributes = [
59
- "_variables",
60
- "_constraints",
61
- "_objective",
62
- "var_map",
63
- "io_mappers",
64
- "name",
65
- "solver",
66
- "poi",
67
- "params",
68
- "result",
69
- "attr",
70
- "sense",
71
- "objective",
72
- "_use_var_names",
73
- "ONE",
74
- "solver_name",
75
- "minimize",
76
- "maximize",
77
- ]
78
-
79
- def __init__(
80
- self,
81
- name: Optional[str] = None,
82
- solver: Optional[SUPPORTED_SOLVER_TYPES] = None,
83
- solver_env: Optional[Dict[str, str]] = None,
84
- use_var_names: bool = False,
85
- sense: Union[ObjSense, ObjSenseValue, None] = None,
86
- ):
87
- self.poi, self.solver_name = Model.create_poi_model(solver, solver_env)
88
- self._variables: List[Variable] = []
89
- self._constraints: List[Constraint] = []
90
- self.sense = ObjSense(sense) if sense is not None else None
91
- self._objective: Optional[Objective] = None
92
- self.var_map = (
93
- NamedVariableMapper(Variable) if Config.print_uses_variable_names else None
94
- )
95
- self.name = name
96
-
97
- self.params = Container(self._set_param, self._get_param)
98
- self.attr = Container(self._set_attr, self._get_attr)
99
- self._use_var_names = use_var_names
100
-
101
- @property
102
- def use_var_names(self):
103
- return self._use_var_names
104
-
105
- @classmethod
106
- def create_poi_model(
107
- cls, solver: Optional[str], solver_env: Optional[Dict[str, str]]
108
- ):
109
- if solver is None:
110
- if Config.default_solver is None:
111
- for solver_option in SUPPORTED_SOLVERS:
112
- try:
113
- return cls.create_poi_model(solver_option, solver_env)
114
- except RuntimeError:
115
- pass
116
- raise ValueError(
117
- 'Could not automatically find a solver. Is one installed? If so, specify which one: e.g. Model(solver="gurobi")'
118
- )
119
- else:
120
- solver = Config.default_solver
121
-
122
- solver = solver.lower()
123
- if solver == "gurobi":
124
- from pyoptinterface import gurobi
125
-
126
- if solver_env is None:
127
- env = gurobi.Env()
128
- else:
129
- env = gurobi.Env(empty=True)
130
- for key, value in solver_env.items():
131
- env.set_raw_parameter(key, value)
132
- env.start()
133
- model = gurobi.Model(env)
134
- elif solver == "highs":
135
- from pyoptinterface import highs
136
-
137
- model = highs.Model()
138
- else:
139
- raise ValueError(
140
- f"Solver {solver} not recognized or supported."
141
- ) # pragma: no cover
142
-
143
- constant_var = model.add_variable(lb=1, ub=1, name="ONE")
144
- if constant_var.index != CONST_TERM:
145
- raise ValueError(
146
- "The first variable should have index 0."
147
- ) # pragma: no cover
148
- return model, solver
149
-
150
- @property
151
- def variables(self) -> List[Variable]:
152
- return self._variables
153
-
154
- @property
155
- def binary_variables(self) -> Iterable[Variable]:
156
- """
157
- Examples:
158
- >>> m = pf.Model()
159
- >>> m.X = pf.Variable(vtype=pf.VType.BINARY)
160
- >>> m.Y = pf.Variable()
161
- >>> len(list(m.binary_variables))
162
- 1
163
- """
164
- return (v for v in self.variables if v.vtype == VType.BINARY)
165
-
166
- @property
167
- def integer_variables(self) -> Iterable[Variable]:
168
- """
169
- Examples:
170
- >>> m = pf.Model()
171
- >>> m.X = pf.Variable(vtype=pf.VType.INTEGER)
172
- >>> m.Y = pf.Variable()
173
- >>> len(list(m.integer_variables))
174
- 1
175
- """
176
- return (v for v in self.variables if v.vtype == VType.INTEGER)
177
-
178
- @property
179
- def constraints(self):
180
- return self._constraints
181
-
182
- @property
183
- def objective(self):
184
- return self._objective
185
-
186
- @objective.setter
187
- def objective(self, value):
188
- if self._objective is not None and (
189
- not isinstance(value, Objective) or not value._constructive
190
- ):
191
- raise ValueError("An objective already exists. Use += or -= to modify it.")
192
- if not isinstance(value, Objective):
193
- value = Objective(value)
194
- self._objective = value
195
- value.on_add_to_model(self, "objective")
196
-
197
- @property
198
- def minimize(self):
199
- if self.sense != ObjSense.MIN:
200
- raise ValueError("Can't get .minimize in a maximization problem.")
201
- return self._objective
202
-
203
- @minimize.setter
204
- def minimize(self, value):
205
- if self.sense is None:
206
- self.sense = ObjSense.MIN
207
- if self.sense != ObjSense.MIN:
208
- raise ValueError("Can't set .minimize in a maximization problem.")
209
- self.objective = value
210
-
211
- @property
212
- def maximize(self):
213
- if self.sense != ObjSense.MAX:
214
- raise ValueError("Can't get .maximize in a minimization problem.")
215
- return self._objective
216
-
217
- @maximize.setter
218
- def maximize(self, value):
219
- if self.sense is None:
220
- self.sense = ObjSense.MAX
221
- if self.sense != ObjSense.MAX:
222
- raise ValueError("Can't set .maximize in a minimization problem.")
223
- self.objective = value
224
-
225
- def __setattr__(self, __name: str, __value: Any) -> None:
226
- if __name not in Model._reserved_attributes and not isinstance(
227
- __value, (ModelElement, pl.DataFrame, pd.DataFrame)
228
- ):
229
- raise PyoframeError(
230
- f"Cannot set attribute '{__name}' on the model because it isn't of type ModelElement (e.g. Variable, Constraint, ...)"
231
- )
232
-
233
- if (
234
- isinstance(__value, ModelElement)
235
- and __name not in Model._reserved_attributes
236
- ):
237
- if isinstance(__value, ModelElementWithId):
238
- assert not hasattr(self, __name), (
239
- f"Cannot create {__name} since it was already created."
240
- )
241
-
242
- __value.on_add_to_model(self, __name)
243
-
244
- if isinstance(__value, Variable):
245
- self._variables.append(__value)
246
- if self.var_map is not None:
247
- self.var_map.add(__value)
248
- elif isinstance(__value, Constraint):
249
- self._constraints.append(__value)
250
- return super().__setattr__(__name, __value)
251
-
252
- def __repr__(self) -> str:
253
- return get_obj_repr(
254
- self,
255
- name=self.name,
256
- vars=len(self.variables),
257
- constrs=len(self.constraints),
258
- objective=bool(self.objective),
259
- )
260
-
261
- def write(self, file_path: Union[Path, str], pretty: bool = False):
262
- """
263
- Output the model to a file.
264
-
265
- Typical usage includes writing the solution to a `.sol` file as well as writing the problem to a `.lp` or `.mps` file.
266
- Set `use_var_names` in your model constructor to `True` if you'd like the output to contain human-readable names (useful for debugging).
267
-
268
- Parameters:
269
- file_path:
270
- The path to the file to write to.
271
- pretty:
272
- Only used when writing .sol files in HiGHS. If `True`, will use HiGH's pretty print columnar style which contains more information.
273
- """
274
- file_path = Path(file_path)
275
- file_path.parent.mkdir(parents=True, exist_ok=True)
276
- kwargs = {}
277
- if self.solver_name == "highs":
278
- if self.use_var_names:
279
- self.params.write_solution_style = 1
280
- kwargs["pretty"] = pretty
281
- self.poi.write(str(file_path), **kwargs)
282
-
283
- def optimize(self):
284
- """
285
- Optimize the model using your selected solver (e.g. Gurobi, HiGHS).
286
- """
287
- self.poi.optimize()
288
-
289
- @for_solvers("gurobi")
290
- def convert_to_fixed(self) -> None:
291
- """
292
- Turns a mixed integer program into a continuous one by fixing
293
- all the integer and binary variables to their solution values.
294
-
295
- !!! warning "Gurobi only"
296
- This method only works with the Gurobi solver. Open an issue if you'd like to see support for other solvers.
297
-
298
- Examples:
299
- >>> m = pf.Model(solver="gurobi")
300
- >>> m.X = pf.Variable(vtype=pf.VType.BINARY, lb=0)
301
- >>> m.Y = pf.Variable(vtype=pf.VType.INTEGER, lb=0)
302
- >>> m.Z = pf.Variable(lb=0)
303
- >>> m.my_constraint = m.X + m.Y + m.Z <= 10
304
- >>> m.maximize = 3 * m.X + 2 * m.Y + m.Z
305
- >>> m.optimize()
306
- >>> m.X.solution, m.Y.solution, m.Z.solution
307
- (1, 9, 0.0)
308
- >>> m.my_constraint.dual
309
- Traceback (most recent call last):
310
- ...
311
- RuntimeError: Unable to retrieve attribute 'Pi'
312
- >>> m.convert_to_fixed()
313
- >>> m.optimize()
314
- >>> m.my_constraint.dual
315
- 1.0
316
-
317
- Only works for Gurobi:
318
-
319
- >>> m = pf.Model("max", solver="highs")
320
- >>> m.convert_to_fixed()
321
- Traceback (most recent call last):
322
- ...
323
- NotImplementedError: Method 'convert_to_fixed' is not implemented for solver 'highs'.
324
- """
325
- self.poi._converttofixed()
326
-
327
- @for_solvers("gurobi", "copt")
328
- def compute_IIS(self):
329
- """
330
- Computes the Irreducible Infeasible Set (IIS) of the model.
331
-
332
- !!! warning "Gurobi only"
333
- This method only works with the Gurobi solver. Open an issue if you'd like to see support for other solvers.
334
-
335
- Examples:
336
- >>> m = pf.Model(solver="gurobi")
337
- >>> m.X = pf.Variable(lb=0, ub=2)
338
- >>> m.Y = pf.Variable(lb=0, ub=2)
339
- >>> m.bad_constraint = m.X >= 3
340
- >>> m.minimize = m.X + m.Y
341
- >>> m.optimize()
342
- >>> m.attr.TerminationStatus
343
- <TerminationStatusCode.INFEASIBLE: 3>
344
- >>> m.bad_constraint.attr.IIS
345
- Traceback (most recent call last):
346
- ...
347
- RuntimeError: Unable to retrieve attribute 'IISConstr'
348
- >>> m.compute_IIS()
349
- >>> m.bad_constraint.attr.IIS
350
- True
351
- """
352
- self.poi.computeIIS()
353
-
354
- def dispose(self):
355
- """
356
- Disposes of the model and cleans up the solver environment.
357
-
358
- When using Gurobi compute server, this cleanup will
359
- ensure your run is not marked as 'ABORTED'.
360
-
361
- Note that once the model is disposed, it cannot be used anymore.
362
-
363
- Examples:
364
- >>> m = pf.Model()
365
- >>> m.X = pf.Variable(ub=1)
366
- >>> m.maximize = m.X
367
- >>> m.optimize()
368
- >>> m.X.solution
369
- 1.0
370
- >>> m.dispose()
371
- """
372
- env = None
373
- if hasattr(self.poi, "_env"):
374
- env = self.poi._env
375
- self.poi.close()
376
- if env is not None:
377
- env.close()
378
-
379
- def __del__(self):
380
- # This ensures that the model is closed *before* the environment is. This avoids the Gurobi warning:
381
- # Warning: environment still referenced so free is deferred (Continue to use WLS)
382
- # I include the hasattr check to avoid errors in case __init__ failed and poi was never set.
383
- if hasattr(self, "poi"):
384
- self.poi.close()
385
-
386
- def _set_param(self, name, value):
387
- self.poi.set_raw_parameter(name, value)
388
-
389
- def _get_param(self, name):
390
- return self.poi.get_raw_parameter(name)
391
-
392
- def _set_attr(self, name, value):
393
- try:
394
- self.poi.set_model_attribute(poi.ModelAttribute[name], value)
395
- except KeyError as e:
396
- if self.solver_name == "gurobi":
397
- self.poi.set_model_raw_attribute(name, value)
398
- else:
399
- raise e
400
-
401
- def _get_attr(self, name):
402
- try:
403
- return self.poi.get_model_attribute(poi.ModelAttribute[name])
404
- except KeyError as e:
405
- if self.solver_name == "gurobi":
406
- return self.poi.get_model_raw_attribute(name)
407
- else:
408
- raise e
pyoframe/model_element.py DELETED
@@ -1,184 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from abc import ABC, abstractmethod
4
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
5
-
6
- import polars as pl
7
-
8
- from pyoframe._arithmetic import _get_dimensions
9
- from pyoframe.constants import (
10
- COEF_KEY,
11
- KEY_TYPE,
12
- QUAD_VAR_KEY,
13
- RESERVED_COL_KEYS,
14
- VAR_KEY,
15
- )
16
-
17
- if TYPE_CHECKING: # pragma: no cover
18
- from pyoframe.model import Model
19
-
20
-
21
- class ModelElement(ABC):
22
- def __init__(self, data: pl.DataFrame, **kwargs) -> None:
23
- # Sanity checks, no duplicate column names
24
- assert len(data.columns) == len(set(data.columns)), (
25
- "Duplicate column names found."
26
- )
27
-
28
- cols = _get_dimensions(data)
29
- if cols is None:
30
- cols = []
31
- cols += [col for col in RESERVED_COL_KEYS if col in data.columns]
32
-
33
- # Reorder columns to keep things consistent
34
- data = data.select(cols)
35
-
36
- # Cast to proper dtype
37
- if COEF_KEY in data.columns:
38
- data = data.cast({COEF_KEY: pl.Float64})
39
- if VAR_KEY in data.columns:
40
- data = data.cast({VAR_KEY: KEY_TYPE})
41
- if QUAD_VAR_KEY in data.columns:
42
- data = data.cast({QUAD_VAR_KEY: KEY_TYPE})
43
-
44
- self._data = data
45
- self._model: Optional[Model] = None
46
- self.name = None
47
- super().__init__(**kwargs)
48
-
49
- def on_add_to_model(self, model: "Model", name: str):
50
- self.name = name
51
- self._model = model
52
-
53
- @property
54
- def data(self) -> pl.DataFrame:
55
- return self._data
56
-
57
- @property
58
- def friendly_name(self) -> str:
59
- return self.name if self.name is not None else "unnamed"
60
-
61
- @property
62
- def dimensions(self) -> Optional[List[str]]:
63
- """
64
- The names of the data's dimensions.
65
-
66
- Examples:
67
- >>> # A variable with no dimensions
68
- >>> pf.Variable().dimensions
69
-
70
- >>> # A variable with dimensions of "hour" and "city"
71
- >>> pf.Variable([{"hour": ["00:00", "06:00", "12:00", "18:00"]}, {"city": ["Toronto", "Berlin", "Paris"]}]).dimensions
72
- ['hour', 'city']
73
- """
74
- return _get_dimensions(self.data)
75
-
76
- @property
77
- def dimensions_unsafe(self) -> List[str]:
78
- """
79
- Same as `dimensions` but returns an empty list if there are no dimensions instead of None.
80
- When unsure, use `dimensions` instead since the type checker forces users to handle the None case (no dimensions).
81
- """
82
- dims = self.dimensions
83
- if dims is None:
84
- return []
85
- return dims
86
-
87
- @property
88
- def shape(self) -> Dict[str, int]:
89
- """
90
- The number of indices in each dimension.
91
-
92
- Examples:
93
- >>> # A variable with no dimensions
94
- >>> pf.Variable().shape
95
- {}
96
- >>> # A variable with dimensions of "hour" and "city"
97
- >>> pf.Variable([{"hour": ["00:00", "06:00", "12:00", "18:00"]}, {"city": ["Toronto", "Berlin", "Paris"]}]).shape
98
- {'hour': 4, 'city': 3}
99
- """
100
- dims = self.dimensions
101
- if dims is None:
102
- return {}
103
- return {dim: self.data[dim].n_unique() for dim in dims}
104
-
105
- def __len__(self) -> int:
106
- dims = self.dimensions
107
- if dims is None:
108
- return 1
109
- return self.data.select(dims).n_unique()
110
-
111
-
112
- def _support_polars_method(method_name: str):
113
- """
114
- Wrapper to add a method to ModelElement that simply calls the underlying Polars method on the data attribute.
115
- """
116
-
117
- def method(self: "SupportPolarsMethodMixin", *args, **kwargs) -> Any:
118
- result_from_polars = getattr(self.data, method_name)(*args, **kwargs)
119
- if isinstance(result_from_polars, pl.DataFrame):
120
- return self._new(result_from_polars)
121
- else:
122
- return result_from_polars
123
-
124
- return method
125
-
126
-
127
- class SupportPolarsMethodMixin(ABC):
128
- rename = _support_polars_method("rename")
129
- with_columns = _support_polars_method("with_columns")
130
- filter = _support_polars_method("filter")
131
- estimated_size = _support_polars_method("estimated_size")
132
-
133
- @abstractmethod
134
- def _new(self, data: pl.DataFrame):
135
- """
136
- Used to create a new instance of the same class with the given data (for e.g. on .rename(), .with_columns(), etc.).
137
- """
138
-
139
- @property
140
- @abstractmethod
141
- def data(self): ...
142
-
143
- def pick(self, **kwargs):
144
- """
145
- Filters elements by the given criteria and then drops the filtered dimensions.
146
-
147
- Examples:
148
- >>> m = pf.Model()
149
- >>> m.v = pf.Variable([{"hour": ["00:00", "06:00", "12:00", "18:00"]}, {"city": ["Toronto", "Berlin", "Paris"]}])
150
- >>> m.v.pick(hour="06:00")
151
- <Expression size=3 dimensions={'city': 3} terms=3>
152
- [Toronto]: v[06:00,Toronto]
153
- [Berlin]: v[06:00,Berlin]
154
- [Paris]: v[06:00,Paris]
155
- >>> m.v.pick(hour="06:00", city="Toronto")
156
- <Expression size=1 dimensions={} terms=1>
157
- v[06:00,Toronto]
158
- """
159
- return self._new(self.data.filter(**kwargs).drop(kwargs.keys()))
160
-
161
-
162
- class ModelElementWithId(ModelElement):
163
- """
164
- Provides a method that assigns a unique ID to each row in a DataFrame.
165
- IDs start at 1 and go up consecutively. No zero ID is assigned since it is reserved for the constant variable term.
166
- IDs are only unique for the subclass since different subclasses have different counters.
167
- """
168
-
169
- @property
170
- def _has_ids(self) -> bool:
171
- return self.get_id_column_name() in self.data.columns
172
-
173
- def _assert_has_ids(self):
174
- if not self._has_ids:
175
- raise ValueError(
176
- f"Cannot use '{self.__class__.__name__}' before it has beed added to a model."
177
- )
178
-
179
- @classmethod
180
- @abstractmethod
181
- def get_id_column_name(cls) -> str:
182
- """
183
- Returns the name of the column containing the IDs.
184
- """
pyoframe/monkey_patch.py DELETED
@@ -1,54 +0,0 @@
1
- from functools import wraps
2
-
3
- import pandas as pd
4
- import polars as pl
5
-
6
- from pyoframe.constants import COEF_KEY, CONST_TERM, VAR_KEY
7
- from pyoframe.core import Expression, SupportsMath
8
-
9
- # pyright: reportAttributeAccessIssue=false
10
-
11
-
12
- def _patch_class(cls):
13
- def _patch_method(func):
14
- @wraps(func)
15
- def wrapper(self, other):
16
- if isinstance(other, SupportsMath):
17
- return NotImplemented
18
- return func(self, other)
19
-
20
- return wrapper
21
-
22
- cls.__add__ = _patch_method(cls.__add__)
23
- cls.__mul__ = _patch_method(cls.__mul__)
24
- cls.__sub__ = _patch_method(cls.__sub__)
25
- cls.__le__ = _patch_method(cls.__le__)
26
- cls.__ge__ = _patch_method(cls.__ge__)
27
- cls.__contains__ = _patch_method(cls.__contains__)
28
-
29
-
30
- def _dataframe_to_expr(self: pl.DataFrame) -> Expression:
31
- return Expression(
32
- self.rename({self.columns[-1]: COEF_KEY})
33
- .drop_nulls(COEF_KEY)
34
- .with_columns(pl.lit(CONST_TERM).alias(VAR_KEY))
35
- )
36
-
37
-
38
- def patch_dataframe_libraries():
39
- """
40
- Applies two patches to the DataFrame and Series classes of both pandas and polars.
41
- 1) Patches arithmetic operators (e.g. `__add__`) such that operations between DataFrames/Series and `Expressionable`s
42
- are not supported (i.e. `return NotImplemented`). This leads Python to try the reverse operation (e.g. `__radd__`)
43
- which is supported by the `Expressionable` class.
44
- 2) Adds a `to_expr` method to DataFrame/Series that allows them to be converted to an `Expression` object.
45
- Series become dataframes and dataframes become expressions where everything but the last column are treated as dimensions.
46
- """
47
- _patch_class(pd.DataFrame)
48
- _patch_class(pd.Series)
49
- _patch_class(pl.DataFrame)
50
- _patch_class(pl.Series)
51
- pl.DataFrame.to_expr = _dataframe_to_expr
52
- pl.Series.to_expr = lambda self: self.to_frame().to_expr()
53
- pd.DataFrame.to_expr = lambda self: pl.from_pandas(self).to_expr()
54
- pd.Series.to_expr = lambda self: self.to_frame().reset_index().to_expr()
@@ -1,15 +0,0 @@
1
- pyoframe/__init__.py,sha256=YswFUwm6GX98dXeT99hqxWqYWLELS71JZf1OpT1kvCg,619
2
- pyoframe/_arithmetic.py,sha256=agJm2Sl4EjEG7q4n2YHka4mGfCQI3LjOXLaW6oCfGiQ,17222
3
- pyoframe/_version.py,sha256=iB5DfB5V6YB5Wo4JmvS-txT42QtmGaWcWp3udRT7zCI,511
4
- pyoframe/constants.py,sha256=WBCmhunavNVwJcmg9ojnA6TVJCLSrgWVE4YKZnhZNz4,4192
5
- pyoframe/core.py,sha256=kK-eMI7teakAAG4JjOiwRBsCgNJIlOxR0b4JOUlQHs4,67239
6
- pyoframe/model.py,sha256=a7pEwagVxHC1ZUMr8ifO4n0ca5Ways3wip-Wps0rlcg,14257
7
- pyoframe/model_element.py,sha256=YmAdx4yM5irGTiZ5uQmDa-u05QdFKngIFy8qNnogvzo,5911
8
- pyoframe/monkey_patch.py,sha256=9IfS14G6IPabmM9z80jzi_D4Rq0Mdx5aUCA39Yi2tgE,2044
9
- pyoframe/objective.py,sha256=PBWxj30QkFlsvY6ijZ6KjyKdrJARD4to0ieF6GUqaQU,3238
10
- pyoframe/util.py,sha256=dHIwAyyD9wn36yM8IOlrboTGUGA7STq3IBTxfYSOPjU,13480
11
- pyoframe-0.2.0.dist-info/licenses/LICENSE,sha256=u_Spw4ynlwTMRZeCX-uacv_hBU547pBygiA6d2ONNV4,1074
12
- pyoframe-0.2.0.dist-info/METADATA,sha256=GTwsHP14u5Zwb1E4pnjyeCS9-0DVmLXjNlo3ebZiSbw,3607
13
- pyoframe-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- pyoframe-0.2.0.dist-info/top_level.txt,sha256=10z3OOJSVLriQ0IrFLMH8CH9zByugPWolqhlHlkNjV4,9
15
- pyoframe-0.2.0.dist-info/RECORD,,