OptlangHelper 0.0.3__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.
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from __future__ import absolute_import
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
__author__ = "Andrew Freiburger"
|
|
8
|
+
__email__ = "afreiburger@anl.gov"
|
|
9
|
+
__version__ = "0.0.3"
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
logger.debug("OptlangHelper %s", __version__)
|
|
13
|
+
|
|
14
|
+
from .optlanghelper import (OptlangHelper, GLPKHelper, CPLEXHelper, GurobiHelper, MatrixHelper,
|
|
15
|
+
Bounds, tupVariable, tupConstraint, tupObjective, define_model_auto)
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Thu Aug 18 10:26:32 2022
|
|
4
|
+
@author: Andrew Freiburger
|
|
5
|
+
"""
|
|
6
|
+
from collections import namedtuple
|
|
7
|
+
from importlib import import_module
|
|
8
|
+
from optlang import Model
|
|
9
|
+
from typing import Iterable, Union
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
Bounds = namedtuple("Bounds", ("lb", "ub"), defaults=(0,1000))
|
|
15
|
+
tupVariable = namedtuple("tupVariable", ("name", "bounds", "type"), defaults=("varName", Bounds(), "continuous"))
|
|
16
|
+
tupConstraint = namedtuple("tupConstraint", ("name", "bounds", "expr"), defaults=("consName", Bounds(0,0), None))
|
|
17
|
+
tupObjective = namedtuple("tupObjective", ("name", "expr", "direction"), defaults=("objectiveName", None, "max"))
|
|
18
|
+
|
|
19
|
+
def isIterable(term):
|
|
20
|
+
try:
|
|
21
|
+
iter(term)
|
|
22
|
+
if type(term) is not str: return True
|
|
23
|
+
return False
|
|
24
|
+
except: return False
|
|
25
|
+
|
|
26
|
+
def isnumber(obj):
|
|
27
|
+
try: float(obj) ; return True
|
|
28
|
+
except: return False
|
|
29
|
+
|
|
30
|
+
def define_term(value):
|
|
31
|
+
if isnumber(value):
|
|
32
|
+
return {"type":"Number", "value": value}
|
|
33
|
+
if isinstance(value, str):
|
|
34
|
+
return {"type":"Symbol", "name": value}
|
|
35
|
+
logger.error(f"The {value} of type {type(value)} is not known.")
|
|
36
|
+
|
|
37
|
+
def get_expression_template(expr):
|
|
38
|
+
if isinstance(expr, list):
|
|
39
|
+
return {"type": "Add", "args": []}
|
|
40
|
+
return {"type": expr["operation"], "args": []}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class OptlangHelper:
|
|
44
|
+
"""Bulk construction of an optlang model from tuple/dict descriptions.
|
|
45
|
+
|
|
46
|
+
``interface`` pins the optlang solver interface (``"glpk"``, ``"cplex"``,
|
|
47
|
+
``"gurobi"``); when None, optlang's preferred available interface is used
|
|
48
|
+
(whatever ``from optlang import Model`` resolves to)."""
|
|
49
|
+
|
|
50
|
+
interface = None
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def _model_cls(cls):
|
|
54
|
+
if cls.interface is None:
|
|
55
|
+
return Model
|
|
56
|
+
try:
|
|
57
|
+
return import_module(f"optlang.{cls.interface}_interface").Model
|
|
58
|
+
except Exception as exc:
|
|
59
|
+
raise ImportError(
|
|
60
|
+
f"The optlang {cls.interface} interface is unavailable: {exc}") from exc
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def add_variables(var_name:str, var_bounds:(list, tuple), var_type:str="continuous"):
|
|
64
|
+
assert var_bounds[0] <= var_bounds[1], f"The {var_name} variable lower bound {var_bounds[0]} is greater than the upper bound {var_bounds[1]}. The lower bound must be less than the upper bound."
|
|
65
|
+
return {"name": var_name.replace(" ", "_"), "lb": var_bounds[0], "ub": var_bounds[1], "type": var_type}
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def add_constraint(cls, cons_name:str, cons_bounds:(list, tuple), cons_expr:dict):
|
|
69
|
+
assert cons_bounds[0] is None or cons_bounds[1] is None or cons_bounds[0] <= cons_bounds[1], f"The {cons_name} constraint lower bound {cons_bounds[0]} is greater than the upper bound {cons_bounds[1]}. The lower bound must be less than the upper bound."
|
|
70
|
+
return {"name": cons_name.replace(" ", "_"),
|
|
71
|
+
"expression": cls._define_expression(cons_expr),
|
|
72
|
+
"lb": cons_bounds[0], "ub": cons_bounds[1], "indicator_variable": None, "active_when": 1}
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def add_objective(cls, obj_name:str, objective_expr:Union[dict, list], direction:str):
|
|
76
|
+
if isinstance(objective_expr, list):
|
|
77
|
+
obj_expr = {"type": "Add", "args": [
|
|
78
|
+
cls._define_expression(expr) for expr in objective_expr]}
|
|
79
|
+
elif isinstance(objective_expr, dict):
|
|
80
|
+
obj_expr = {"type": objective_expr["operation"],
|
|
81
|
+
"args": [define_term(term) for term in objective_expr["elements"]]}
|
|
82
|
+
return {"name": obj_name.replace(" ", "_"), "expression": obj_expr, "direction": direction}
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def define_model(cls, model_name, variables, constraints, objective, optlang=False):
|
|
86
|
+
model = {'name':model_name, 'variables':[], 'constraints':[]}
|
|
87
|
+
for var in variables:
|
|
88
|
+
var = list(var)
|
|
89
|
+
if len(var) == 2: var.append("continuous")
|
|
90
|
+
model["variables"].append(cls.add_variables(var[0], var[1], var[2]))
|
|
91
|
+
for cons in constraints:
|
|
92
|
+
model["constraints"].append(cls.add_constraint(cons[0], cons[1], cons[2]))
|
|
93
|
+
model["objective"] = cls.add_objective(objective[0], objective[1], objective[2])
|
|
94
|
+
if optlang: return cls._model_cls().from_json(model)
|
|
95
|
+
return model
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def _define_expression(cls, expr:dict):
|
|
99
|
+
expression = get_expression_template(expr)
|
|
100
|
+
level1_coef = 0
|
|
101
|
+
for ele in expr["elements"]:
|
|
102
|
+
if not isnumber(ele) and not isinstance(ele, str):
|
|
103
|
+
arguments = []
|
|
104
|
+
level2_coef = 0
|
|
105
|
+
for ele2 in ele["elements"]:
|
|
106
|
+
if not isnumber(ele2) and not isinstance(ele2, str):
|
|
107
|
+
arguments.append(cls._define_expression(ele2))
|
|
108
|
+
elif isinstance(ele2, str): arguments.append(define_term(ele2))
|
|
109
|
+
else: level2_coef += float(ele2)
|
|
110
|
+
expression["args"].append(get_expression_template(ele))
|
|
111
|
+
if level2_coef != 0: arguments.append(define_term(level2_coef))
|
|
112
|
+
expression["args"][-1]["args"] = arguments
|
|
113
|
+
elif isinstance(ele, str): expression["args"].append(define_term(ele))
|
|
114
|
+
else: level1_coef += float(ele)
|
|
115
|
+
if level1_coef != 0: expression["args"].append(define_term(level1_coef))
|
|
116
|
+
return expression
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def dot_product(zipped_to_sum, heuns_coefs=None):
|
|
120
|
+
# ensure that the lengths are compatible for heun's dot-products
|
|
121
|
+
if heuns_coefs is not None:
|
|
122
|
+
coefs = heuns_coefs if isinstance(heuns_coefs, (list, set)) else heuns_coefs.tolist()
|
|
123
|
+
zipped_length = len(zipped_to_sum); coefs_length = len(coefs)
|
|
124
|
+
if zipped_length != coefs_length:
|
|
125
|
+
raise IndexError(f"ERROR: The length of zipped elements {zipped_length}"
|
|
126
|
+
f" is unequal to that of coefficients {coefs_length}")
|
|
127
|
+
|
|
128
|
+
elements = []
|
|
129
|
+
for index, (term1, term2) in enumerate(zipped_to_sum):
|
|
130
|
+
if heuns_coefs is not None:
|
|
131
|
+
elements.extend([{"operation": "Mul", "elements": [heuns_coefs[index], term1]},
|
|
132
|
+
{"operation": "Mul", "elements": [heuns_coefs[index], term2]}])
|
|
133
|
+
else:
|
|
134
|
+
elements.append({"operation": "Mul", "elements": [term1, term2]})
|
|
135
|
+
return elements
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class GLPKHelper(OptlangHelper):
|
|
139
|
+
interface = "glpk"
|
|
140
|
+
|
|
141
|
+
class CPLEXHelper(OptlangHelper):
|
|
142
|
+
interface = "cplex"
|
|
143
|
+
|
|
144
|
+
class GurobiHelper(OptlangHelper):
|
|
145
|
+
interface = "gurobi"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# --------------------------------------------------------------------------- matrix path
|
|
149
|
+
def _linear_terms(expr, var_index):
|
|
150
|
+
"""Flatten a tuple-format linear expression into ({var_idx: coef}, constant).
|
|
151
|
+
|
|
152
|
+
Handles the shapes produced by ``dot_product``/``tupConstraint``: an Add of
|
|
153
|
+
Mul-dicts, bare variable names, and numeric constants; Muls may hold the
|
|
154
|
+
coefficient and variable in either order. Nested Adds are recursed."""
|
|
155
|
+
coefs, const = {}, 0.0
|
|
156
|
+
elements = expr["elements"] if isinstance(expr, dict) else expr
|
|
157
|
+
for ele in elements:
|
|
158
|
+
if isinstance(ele, str):
|
|
159
|
+
coefs[var_index[ele]] = coefs.get(var_index[ele], 0.0) + 1.0
|
|
160
|
+
elif isnumber(ele):
|
|
161
|
+
const += float(ele)
|
|
162
|
+
elif isinstance(ele, dict) and ele.get("operation") == "Mul":
|
|
163
|
+
coef, name = 1.0, None
|
|
164
|
+
for ele2 in ele["elements"]:
|
|
165
|
+
if isinstance(ele2, str): name = ele2
|
|
166
|
+
elif isnumber(ele2): coef *= float(ele2)
|
|
167
|
+
else: raise ValueError(f"Non-linear or nested Mul term is not supported by the matrix path: {ele}")
|
|
168
|
+
if name is None: const += coef
|
|
169
|
+
else: coefs[var_index[name]] = coefs.get(var_index[name], 0.0) + coef
|
|
170
|
+
elif isinstance(ele, dict) and ele.get("operation") == "Add":
|
|
171
|
+
sub_coefs, sub_const = _linear_terms(ele, var_index)
|
|
172
|
+
const += sub_const
|
|
173
|
+
for j, c in sub_coefs.items(): coefs[j] = coefs.get(j, 0.0) + c
|
|
174
|
+
else:
|
|
175
|
+
raise ValueError(f"Unsupported expression element for the matrix path: {ele}")
|
|
176
|
+
return coefs, const
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class MatrixHelper:
|
|
180
|
+
"""CSR/gurobipy construction for very large LPs.
|
|
181
|
+
|
|
182
|
+
optlang's per-constraint interfaces scale super-linearly (~n^1.8 measured on
|
|
183
|
+
Gurobi: 48 s at 1e5 constraints, ~90 min extrapolated at 1.4e6), while
|
|
184
|
+
gurobipy ingests a CSR matrix in C time (seconds at 1.4e6 constraints).
|
|
185
|
+
Requires numpy, scipy, and gurobipy (``pip install OptlangHelper[matrix]``).
|
|
186
|
+
Returns a ``gurobipy.Model`` (``.optimize()``; status 2 == optimal), NOT an
|
|
187
|
+
optlang model."""
|
|
188
|
+
|
|
189
|
+
@staticmethod
|
|
190
|
+
def define_model(model_name, variables, constraints, objective, verbose=False):
|
|
191
|
+
import numpy as np
|
|
192
|
+
from scipy.sparse import coo_matrix
|
|
193
|
+
import gurobipy as gp
|
|
194
|
+
|
|
195
|
+
names, lbs, ubs = [], [], []
|
|
196
|
+
for var in variables:
|
|
197
|
+
var = list(var)
|
|
198
|
+
name = var[0].replace(" ", "_")
|
|
199
|
+
names.append(name); lbs.append(float(var[1][0])); ubs.append(float(var[1][1]))
|
|
200
|
+
var_index = {n: j for j, n in enumerate(names)}
|
|
201
|
+
|
|
202
|
+
rows_eq, rows_le, rows_ge = [], [], [] # (coefs, rhs)
|
|
203
|
+
for cons in constraints:
|
|
204
|
+
_, bounds, expr = cons[0], cons[1], cons[2]
|
|
205
|
+
coefs, const = _linear_terms(expr, var_index)
|
|
206
|
+
lb = None if bounds[0] is None else float(bounds[0]) - const
|
|
207
|
+
ub = None if bounds[1] is None else float(bounds[1]) - const
|
|
208
|
+
if lb is not None and ub is not None and lb == ub:
|
|
209
|
+
rows_eq.append((coefs, lb))
|
|
210
|
+
else:
|
|
211
|
+
if ub is not None: rows_le.append((coefs, ub))
|
|
212
|
+
if lb is not None: rows_ge.append((coefs, lb))
|
|
213
|
+
|
|
214
|
+
env = gp.Env(empty=True); env.setParam("OutputFlag", 1 if verbose else 0); env.start()
|
|
215
|
+
model = gp.Model(model_name, env=env)
|
|
216
|
+
x = model.addMVar(len(names), lb=np.array(lbs), ub=np.array(ubs))
|
|
217
|
+
model._var_names = names
|
|
218
|
+
model._var_index = var_index
|
|
219
|
+
|
|
220
|
+
def add_block(rows, sense):
|
|
221
|
+
if not rows: return
|
|
222
|
+
r, c, v, rhs = [], [], [], []
|
|
223
|
+
for i, (coefs, b) in enumerate(rows):
|
|
224
|
+
rhs.append(b)
|
|
225
|
+
for j, coef in coefs.items():
|
|
226
|
+
r.append(i); c.append(j); v.append(coef)
|
|
227
|
+
S = coo_matrix((v, (r, c)), shape=(len(rows), len(names))).tocsr()
|
|
228
|
+
model.addMConstr(S, x, sense, np.array(rhs))
|
|
229
|
+
add_block(rows_eq, "="); add_block(rows_le, "<"); add_block(rows_ge, ">")
|
|
230
|
+
|
|
231
|
+
obj_expr = objective[1] if isinstance(objective[1], (list, dict)) else [objective[1]]
|
|
232
|
+
if isinstance(obj_expr, list):
|
|
233
|
+
obj_expr = {"elements": obj_expr, "operation": "Add"}
|
|
234
|
+
ocoefs, oconst = _linear_terms(obj_expr, var_index)
|
|
235
|
+
lin = gp.LinExpr(oconst)
|
|
236
|
+
for j, coef in ocoefs.items(): lin += coef * x[j]
|
|
237
|
+
model.setObjective(lin, gp.GRB.MAXIMIZE if objective[2] in ("max", "maximize") else gp.GRB.MINIMIZE)
|
|
238
|
+
model._mvar = x
|
|
239
|
+
return model
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def define_model_auto(model_name, variables, constraints, objective, limit=300000, helper=None):
|
|
243
|
+
"""Size-aware dispatch: optlang construction up to ``limit`` constraints,
|
|
244
|
+
CSR/gurobipy (``MatrixHelper``) beyond it.
|
|
245
|
+
|
|
246
|
+
Below the limit an OPTLANG model is returned (from ``helper`` or
|
|
247
|
+
``OptlangHelper``); above it a GUROBIPY model is returned — both expose
|
|
248
|
+
``.optimize()``, but their status/attribute APIs differ, so branch on
|
|
249
|
+
``isinstance`` or on ``len(constraints) > limit`` when reading results."""
|
|
250
|
+
if len(constraints) <= limit:
|
|
251
|
+
return (helper or OptlangHelper).define_model(model_name, variables, constraints, objective, optlang=True)
|
|
252
|
+
logger.info(f"{model_name}: {len(constraints)} constraints exceeds limit={limit}; "
|
|
253
|
+
"building through MatrixHelper (gurobipy CSR)")
|
|
254
|
+
return MatrixHelper.define_model(model_name, variables, constraints, objective)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: OptlangHelper
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: Python package for building Linear Programming models that are compatible with Optlang
|
|
5
|
+
Home-page: https://github.com/Freiburgermsu/OptlangHelper
|
|
6
|
+
Author: Andrew Freiburger
|
|
7
|
+
Author-email: afreiburger@anl.gov
|
|
8
|
+
Project-URL: Issues, https://github.com/Freiburgermsu/OptlangHelper/issues
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Natural Language :: English
|
|
18
|
+
Description-Content-Type: text/x-rst
|
|
19
|
+
Requires-Dist: optlang
|
|
20
|
+
Provides-Extra: matrix
|
|
21
|
+
Requires-Dist: numpy; extra == "matrix"
|
|
22
|
+
Requires-Dist: scipy; extra == "matrix"
|
|
23
|
+
Requires-Dist: gurobipy; extra == "matrix"
|
|
24
|
+
Dynamic: author
|
|
25
|
+
Dynamic: author-email
|
|
26
|
+
Dynamic: classifier
|
|
27
|
+
Dynamic: description
|
|
28
|
+
Dynamic: description-content-type
|
|
29
|
+
Dynamic: home-page
|
|
30
|
+
Dynamic: project-url
|
|
31
|
+
Dynamic: provides-extra
|
|
32
|
+
Dynamic: requires-dist
|
|
33
|
+
Dynamic: summary
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Efficiently creating optimization models
|
|
38
|
+
________________________________________________________________________
|
|
39
|
+
|
|
40
|
+
|PyPI version| |License|
|
|
41
|
+
|
|
42
|
+
.. |Supported Python Versions| image:: https://img.shields.io/pypi/pyversions/optlanghelper)
|
|
43
|
+
:target: https://pypi.org/project/optlanghelper/
|
|
44
|
+
:alt: Python versions
|
|
45
|
+
|
|
46
|
+
.. |PyPI version| image:: https://img.shields.io/pypi/v/optlanghelper.svg?logo=PyPI&logoColor=brightgreen
|
|
47
|
+
:target: https://pypi.org/project/optlanghelper/
|
|
48
|
+
:alt: PyPI version
|
|
49
|
+
|
|
50
|
+
.. |Actions Status| image:: https://github.com/freiburgermsu/optlanghelper/workflows/Test%20optlanghelper/badge.svg
|
|
51
|
+
:target: https://github.com/freiburgermsu/optlanghelper/actions
|
|
52
|
+
:alt: Actions Status
|
|
53
|
+
|
|
54
|
+
.. |License| image:: https://img.shields.io/badge/License-MIT-blue.svg
|
|
55
|
+
:target: https://opensource.org/licenses/MIT
|
|
56
|
+
:alt: License
|
|
57
|
+
|
|
58
|
+
.. .. |Downloads| image:: https://pepy.tech/badge/modelseedpy
|
|
59
|
+
.. :target: https://pepy.tech/project/modelseedpy
|
|
60
|
+
.. :alt: Downloads
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
.. note::
|
|
64
|
+
|
|
65
|
+
This project is under active development, and may be subject to losing back-compatibility.
|
|
66
|
+
|
|
67
|
+
----------------------
|
|
68
|
+
Installation
|
|
69
|
+
----------------------
|
|
70
|
+
|
|
71
|
+
OptlangHelper can be installed via ``pip`` through the ``PyPI`` channel::
|
|
72
|
+
|
|
73
|
+
pip install optlanghelper
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
----------------------
|
|
77
|
+
Usage
|
|
78
|
+
----------------------
|
|
79
|
+
|
|
80
|
+
The OptlangHelper package is a collection of functions that make it easier to build linear programming models using the Optlang package. This is accomplished through dictionaries of variables, constraints, and an objective that are added to a model at the end of iterating through all conditions. Since each solver -- such as GLPK, CPLEX, and GUROBI -- differently defines variables and constraints, a separate class (although using the same function design and arguments) is provided for each of these solvers. This requires the user to correctly select the proper OptlangHelper class that will construct the appropriate model for their solver.
|
|
81
|
+
|
|
82
|
+
****************
|
|
83
|
+
GLPK
|
|
84
|
+
****************
|
|
85
|
+
|
|
86
|
+
The GLPK class is used to construct an optlang model for the GLPK solver. This is the simplest of the solvers and is employed by default for optlang without specification for CPlex or Gurobi.
|
|
87
|
+
|
|
88
|
+
++++++++++++++++++++++
|
|
89
|
+
Named Tuples
|
|
90
|
+
++++++++++++++++++++++
|
|
91
|
+
|
|
92
|
+
There are several NamedTuples that are defined in OptlangHelper and assist with defining variables, constraints, and the objective.
|
|
93
|
+
|
|
94
|
+
``Bounds`` object has attributes of
|
|
95
|
+
|
|
96
|
+
- ``lb``, the lower bound of the entity associated with this bound: default is 0.
|
|
97
|
+
- ``up``, the upper bound of the associated entity: default is 1000.
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
``tupVariable`` object has attributes of
|
|
101
|
+
|
|
102
|
+
- ``name``, the name of the variable represented by this tuple
|
|
103
|
+
- ``bounds``, the lower and upper limit bounds associated with the tuple: (0,1000) is the default
|
|
104
|
+
- ``type``, the variable type: "integer"; "binary"; "continuous" is the default.
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
``tupConstraint`` object has attributes of
|
|
108
|
+
|
|
109
|
+
- ``name``, the name of the variable represented by this tuple
|
|
110
|
+
- ``bounds``, the lower and upper limit bounds associated with the tuple: (0,0) is the default
|
|
111
|
+
- ``expr``, the constraint expression: ``None`` is the default.
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
``tupObjective`` object has attributes of
|
|
115
|
+
|
|
116
|
+
- ``name``, the name of the variable represented by this tuple
|
|
117
|
+
- ``expr``, the objective expression: ``None`` is the default.
|
|
118
|
+
- ``direction``, the optimization direction: "max" is the default.
|
|
119
|
+
|
|
120
|
+
All of the above objects are fed into the ``define_model`` function
|
|
121
|
+
|
|
122
|
+
- ``model_name``, the name of the model
|
|
123
|
+
- ``variables``, the tupVariable objects for the model.
|
|
124
|
+
- ``constraints``, the tupConstraint objects for the model.
|
|
125
|
+
- ``objective``, the tupObjective object for the model.
|
|
126
|
+
- ``optlang``, specifies whether an optlang model is returned (``True``), or the raw dictionary (``False``) by default.
|
|
127
|
+
|
|
128
|
+
This function calls all of the class functions and returns either the GLPK model as as dictionary or an optlang object.
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
++++++++++++++++++++++
|
|
132
|
+
Example
|
|
133
|
+
++++++++++++++++++++++
|
|
134
|
+
|
|
135
|
+
The following blocks define the intended usage of the GLPK class.
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
.. code-block:: python
|
|
139
|
+
|
|
140
|
+
from optlanghelper import tupVariable, tupConstraint, tupObjective, define_model
|
|
141
|
+
|
|
142
|
+
# define the variables
|
|
143
|
+
variables = {}
|
|
144
|
+
for var in vars:
|
|
145
|
+
variables[var.name] = tupVariable(var.name, Bounds(0, 5), "continuous")
|
|
146
|
+
variables[var.name+"_bin"] = tupVariable(var.name+"_bin", Bounds(0, 1), "binary")
|
|
147
|
+
|
|
148
|
+
# define the constraints
|
|
149
|
+
constraints = {}
|
|
150
|
+
for name, content in constraint_info.items():
|
|
151
|
+
lb, ub = content["low_bound"], content["high_bound"]
|
|
152
|
+
consExpr = {}
|
|
153
|
+
## define the constraint expression
|
|
154
|
+
for varName, coef in var_info.items():
|
|
155
|
+
if varName not in consCoefs: continue
|
|
156
|
+
coef2 = consCoefs[varName]
|
|
157
|
+
consExpr[varName].update({"elements": [varName, coef2], "operation": "Mul"})
|
|
158
|
+
## create the constraint tuple
|
|
159
|
+
constraints[nutrient] = tupConstraint(name=nutrient, bounds=Bounds(lb, ub), expr={"elements": list(consExpr.values()), "operation": "Add"})
|
|
160
|
+
|
|
161
|
+
for varName in var_info.keys():
|
|
162
|
+
constraints[varName+"_bin"] = tupConstraint(varName+"_bin", bounds=Bounds(0,None),
|
|
163
|
+
expr={
|
|
164
|
+
"elements": [
|
|
165
|
+
variables[varName].bounds.ub,
|
|
166
|
+
{"elements": [-1, variables[varName].name,], "operation": "Mul"},
|
|
167
|
+
{"elements": [-variables[varName].bounds.ub, variables[varName+"_bin"].name], "operation": "Mul"}],
|
|
168
|
+
"operation": "Add"})
|
|
169
|
+
|
|
170
|
+
# define the objective
|
|
171
|
+
objective = tupObjective("< optimization name>", [], "min")
|
|
172
|
+
for varName, coef in var_info.items():
|
|
173
|
+
objective.expr.append({
|
|
174
|
+
"elements": [
|
|
175
|
+
{"elements": [variables[varName].name, coef],
|
|
176
|
+
"operation": "Mul"}],
|
|
177
|
+
"operation": "Add"
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
# create an optlang model from all of the variables, constraints, and objective defined above
|
|
181
|
+
model = define_model("< model name>", list(variables.values()), list(constraints.values()), objective, True)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
Release 0.0.3
|
|
185
|
+
----------------------
|
|
186
|
+
|
|
187
|
+
Bug fixes
|
|
188
|
+
|
|
189
|
+
+ the published 0.0.2 wheel was unimportable (``__init__`` imported from the package
|
|
190
|
+
name instead of the submodule) and crashed on nested expressions (a stale internal
|
|
191
|
+
reference to the pre-rename class); both are fixed and covered by tests.
|
|
192
|
+
+ ``__version__`` is synchronized with the package metadata, and the import-time
|
|
193
|
+
``print`` is now a debug log line.
|
|
194
|
+
|
|
195
|
+
New
|
|
196
|
+
|
|
197
|
+
+ the historic ``OptlangHelper`` class is restored as the solver-agnostic base;
|
|
198
|
+
``GLPKHelper`` / ``CPLEXHelper`` / ``GurobiHelper`` are interface-pinned subclasses
|
|
199
|
+
(previously the latter two were empty stubs).
|
|
200
|
+
+ ``MatrixHelper.define_model`` builds very large LPs as a sparse CSR matrix directly
|
|
201
|
+
through ``gurobipy`` (install with ``pip install OptlangHelper[matrix]``): optlang's
|
|
202
|
+
per-constraint interfaces scale super-linearly (~n^1.8 measured on Gurobi — 48 s at
|
|
203
|
+
1e5 constraints, ~90 min extrapolated at 1.4e6), while the CSR path ingests 1.4e6
|
|
204
|
+
constraints in seconds.
|
|
205
|
+
+ ``define_model_auto(name, variables, constraints, objective, limit=300000)``
|
|
206
|
+
dispatches by size: an optlang model at or below ``limit`` constraints, a gurobipy
|
|
207
|
+
model above it (both expose ``.optimize()``; their result APIs differ).
|
|
208
|
+
|
|
209
|
+
.. code-block:: python
|
|
210
|
+
|
|
211
|
+
from optlanghelper import define_model_auto
|
|
212
|
+
|
|
213
|
+
model = define_model_auto("community_fba", variables, constraints, objective)
|
|
214
|
+
model.optimize()
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
optlanghelper/__init__.py,sha256=Oc74Eqs8H6GCwfvfokBW3MjbJ8UFg_ASpY1TIW54sQs,448
|
|
2
|
+
optlanghelper/optlanghelper.py,sha256=6-8ErwT3THsfm-fuUEvp33MQ7MRQKIzlpDGZd0FfA1U,11949
|
|
3
|
+
optlanghelper-0.0.3.dist-info/METADATA,sha256=digciYF_1pVEzoqlYooHHO7XC2GYzXoTmBC1DRTTBHU,8741
|
|
4
|
+
optlanghelper-0.0.3.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
5
|
+
optlanghelper-0.0.3.dist-info/top_level.txt,sha256=C9TGd-LQ2lpHk_VliXn3f_8u7pdq9ougRebrE3JvOEk,14
|
|
6
|
+
optlanghelper-0.0.3.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
optlanghelper
|