Skip to content

Commit 82abb5b

Browse files
authored
Merge pull request #27 from EPFL-LCSB/dev
Dev: Hotfix v 0.9.0-b1
2 parents f6e2a88 + bfec94e commit 82abb5b

File tree

8 files changed

+192
-73
lines changed

8 files changed

+192
-73
lines changed

doc/conf.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def __getattr__(cls, name):
9595
version = '0.9'
9696
# The full version, including alpha/beta/rc tags.
9797

98-
release = '0.9.0-b0'
98+
release = '0.9.0-b1'
9999

100100
# The language for content autogenerated by Sphinx. Refer to documentation
101101
# for a list of supported languages.

pytfa/core/model.py

+20-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import pandas as pd
1616
from numpy import empty
17+
import optlang
1718
from optlang.exceptions import SolverError
1819
from cobra import DictList, Model
1920
from cobra.core.solution import Solution
@@ -63,6 +64,10 @@ def __init__(self, model, name, sloppy=False):
6364

6465
self._cons_queue = list()
6566
self._var_queue = list()
67+
68+
self._var_dict = dict()
69+
self._cons_dict = dict()
70+
6671
self.sloppy=sloppy
6772

6873

@@ -182,26 +187,31 @@ def _remove_associated_consvar(self, all_cons_subclasses, all_var_subclasses,
182187
try:
183188
cons = self._cons_kinds[cons_type.__name__].get_by_id(strfy(element))
184189
self.remove_constraint(cons)
185-
except KeyError:
190+
except KeyError as e:
186191
pass
187192
for var_type in all_var_subclasses:
188193
for element in collection:
189194
try:
190195
var = self._var_kinds[var_type.__name__].get_by_id(strfy(element))
191196
self.remove_variable(var)
192-
except KeyError:
197+
except KeyError as e:
193198
pass
194199

200+
195201
def remove_variable(self, var):
196202
"""
197203
Removes a variable
198204
199205
:param var:
200206
:return:
201207
"""
208+
# Get the pytfa var object if an optlang variable is passed
209+
if isinstance(var,optlang.Variable):
210+
var = self._var_dict[var.name]
202211

203212
self._var_dict.pop(var.name)
204213
self.remove_cons_vars(var.variable)
214+
self.logger.debug('Removed variable {}'.format(var.name))
205215

206216
def remove_constraint(self, cons):
207217
"""
@@ -210,9 +220,13 @@ def remove_constraint(self, cons):
210220
:param cons:
211221
:return:
212222
"""
223+
# Get the pytfa var object if an optlang variable is passed
224+
if isinstance(cons,optlang.Constraint):
225+
cons = self._cons_dict[cons.name]
213226

214227
self._cons_dict.pop(cons.name)
215228
self.remove_cons_vars(cons.constraint)
229+
self.logger.debug('Removed constraint {}'.format(cons.name))
216230

217231
def _push_queue(self):
218232
"""
@@ -265,7 +279,10 @@ def regenerate_constraints(self):
265279
if hasattr(self, '_cons_kinds'):
266280
for k in self._cons_kinds:
267281
attrname = camel2underscores(k)
268-
delattr(self, attrname)
282+
try:
283+
delattr(self, attrname)
284+
except AttributeError:
285+
pass # The attribute may not have been set up yet
269286

270287
_cons_kinds = defaultdict(DictList)
271288

pytfa/io/dict.py

+93-65
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414

1515
from ..thermo.tmodel import ThermoModel
1616

17-
from ..optim.variables import ReactionVariable, MetaboliteVariable
18-
from ..optim.constraints import ReactionConstraint, MetaboliteConstraint
17+
from ..optim.variables import ReactionVariable, MetaboliteVariable, ModelVariable
18+
from ..optim.constraints import ReactionConstraint, MetaboliteConstraint, ModelConstraint
1919

2020
from optlang.util import expr_to_json, parse_expr
2121

22+
from copy import copy
23+
2224

2325
def get_all_subclasses(cls):
2426
all_subclasses = []
@@ -34,10 +36,17 @@ def make_subclasses_dict(cls):
3436
the_dict[cls.__name__] = cls
3537
return the_dict
3638

37-
REACTION_VARIABLE_SUBCLASSES = make_subclasses_dict(ReactionVariable)
38-
REACTION_CONSTRAINT_SUBCLASSES = make_subclasses_dict(ReactionConstraint)
39-
METABOLITE_VARIABLE_SUBCLASSES = make_subclasses_dict(MetaboliteVariable)
40-
METABOLITE_CONSTRAINT_SUBCLASSES= make_subclasses_dict(MetaboliteConstraint)
39+
40+
MODEL_VARIABLE_SUBCLASSES = make_subclasses_dict(ModelVariable)
41+
MODEL_CONSTRAINT_SUBCLASSES= make_subclasses_dict(ModelConstraint)
42+
43+
BASE_NAME2HOOK = {
44+
ReactionVariable :'reactions',
45+
ReactionConstraint :'reactions',
46+
MetaboliteVariable :'metabolites',
47+
MetaboliteConstraint:'metabolites',
48+
}
49+
4150

4251
SOLVER_DICT = {
4352
'optlang.gurobi_interface':'optlang-gurobi',
@@ -212,76 +221,79 @@ def _add_thermo_metabolite_info(met, met_dict):
212221
if hasattr(met, 'thermo'):
213222
met_dict['thermo'] = metabolite_thermo_to_dict(met)
214223

215-
def model_from_dict(obj, solver=None):
224+
def model_from_dict(obj, solver=None, custom_hooks = None):
225+
"""
226+
Custom_hooks looks like
227+
228+
.. code:: python
229+
230+
custom_hooks = {<EnzymeVariable Class at 0xffffff> : 'enzymes',
231+
... }
232+
233+
:param obj:
234+
:param solver:
235+
:param custom_hooks:
236+
:return:
237+
"""
216238
# Take advantage of cobra's serialization of mets and reactions
217-
new = cbd.model_from_dict(obj)
239+
cbm = cbd.model_from_dict(obj)
218240

219241
if solver is not None:
220242
try:
221-
new.solver = solver
243+
cbm.solver = solver
222244
except SolverNotFound as snf:
223245
raise snf
224246
else:
225247
try:
226-
new.solver = obj['solver']
248+
cbm.solver = obj['solver']
227249
except KeyError:
228250
pass
229251

252+
if custom_hooks is None:
253+
custom_hooks = dict()
254+
255+
custom_hooks.update(BASE_NAME2HOOK)
256+
230257
if obj['kind'] == 'ThermoModel':
231258
new = ThermoModel(thermo_data=obj['thermo_data'],
232-
model=new,
259+
model=cbm,
233260
name=obj['name'],
234261
temperature=obj['temperature'],
235262
min_ph=obj['min_ph'],
236263
max_ph=obj['max_ph'])
237264
new = init_thermo_model_from_dict(new, obj)
265+
else:
266+
new = ThermoModel(model=cbm,
267+
name=obj['name'])
238268

239269
new._push_queue()
240270

271+
name2class, name2hook = add_custom_classes(new,custom_hooks)
272+
241273
for the_var_dict in obj['variables']:
242274
this_id = the_var_dict['id']
243275
classname = the_var_dict['kind']
244276
lb = the_var_dict['lb']
245277
ub = the_var_dict['ub']
246278
scaling_factor = the_var_dict['scaling_factor']
247279

248-
if classname in REACTION_VARIABLE_SUBCLASSES:
249-
hook = new.reactions.get_by_id(this_id)
250-
this_class = REACTION_VARIABLE_SUBCLASSES[classname]
251-
nv = new.add_variable(kind=this_class,
252-
hook=hook,
253-
ub = ub,
254-
lb = lb,
255-
queue=True)
256-
257-
elif classname in METABOLITE_VARIABLE_SUBCLASSES:
258-
hook = new.metabolites.get_by_id(this_id)
259-
this_class = METABOLITE_VARIABLE_SUBCLASSES[classname]
260-
nv = new.add_variable(kind=this_class,
261-
hook=hook,
262-
ub = ub,
263-
lb = lb,
264-
queue=True)
265-
266-
elif classname in ENZYME_VARIABLE_SUBCLASSES:
267-
hook = new.enzymes.get_by_id(this_id)
268-
this_class = ENZYME_VARIABLE_SUBCLASSES[classname]
280+
if classname in MODEL_VARIABLE_SUBCLASSES:
281+
hook = new
282+
this_class = MODEL_VARIABLE_SUBCLASSES[classname]
269283
nv = new.add_variable(kind=this_class,
270284
hook=hook,
271-
ub = ub,
272-
lb = lb,
285+
id_=this_id,
286+
ub=ub,
287+
lb=lb,
273288
queue=True)
274-
275-
elif classname in MODEL_VARIABLE_SUBCLASSES:
276-
hook = new
277-
this_class = MODEL_VARIABLE_SUBCLASSES[classname]
289+
elif classname in name2class:
290+
hook = name2hook[classname].get_by_id(this_id)
291+
this_class = name2class[classname]
278292
nv = new.add_variable(kind=this_class,
279293
hook=hook,
280-
id_ = this_id,
281294
ub = ub,
282295
lb = lb,
283296
queue=True)
284-
285297
else:
286298
raise TypeError(
287299
'Class {} serialization not handled yet' \
@@ -307,41 +319,24 @@ def model_from_dict(obj, solver=None):
307319
lb = the_cons_dict['lb']
308320
ub = the_cons_dict['ub']
309321

310-
if classname in REACTION_CONSTRAINT_SUBCLASSES:
311-
hook = new.reactions.get_by_id(this_id)
312-
this_class = REACTION_CONSTRAINT_SUBCLASSES[classname]
313-
nc = new.add_constraint(kind=this_class, hook=hook,
314-
expr=new_expr,
315-
ub = ub,
316-
lb = lb,
317-
queue=True)
322+
# Look for the corresponding class:
318323

319-
elif classname in METABOLITE_CONSTRAINT_SUBCLASSES:
320-
hook = new.metabolites.get_by_id(this_id)
321-
this_class = METABOLITE_CONSTRAINT_SUBCLASSES[classname]
324+
if classname in MODEL_CONSTRAINT_SUBCLASSES:
325+
hook=new
326+
this_class = MODEL_CONSTRAINT_SUBCLASSES[classname]
322327
nc = new.add_constraint(kind=this_class, hook=hook,
323-
expr=new_expr,
328+
expr=new_expr, id_ = this_id,
324329
ub = ub,
325330
lb = lb,
326331
queue=True)
327-
328-
elif classname in ENZYME_CONSTRAINT_SUBCLASSES:
329-
hook = new.enzymes.get_by_id(this_id)
330-
this_class = ENZYME_CONSTRAINT_SUBCLASSES[classname]
332+
elif classname in name2class:
333+
hook = name2hook[classname].get_by_id(this_id)
334+
this_class = name2class[classname]
331335
nc = new.add_constraint(kind=this_class, hook=hook,
332336
expr=new_expr,
333337
ub = ub,
334338
lb = lb,
335339
queue=True)
336-
337-
elif classname in MODEL_CONSTRAINT_SUBCLASSES:
338-
hook=new
339-
this_class = MODEL_CONSTRAINT_SUBCLASSES[classname]
340-
nc = new.add_constraint(kind=this_class, hook=hook,
341-
expr=new_expr, id_ = this_id,
342-
ub = ub,
343-
lb = lb,
344-
queue=True)
345340
else:
346341
raise TypeError('Class {} serialization not handled yet' \
347342
.format(classname))
@@ -356,6 +351,39 @@ def model_from_dict(obj, solver=None):
356351

357352
return new
358353

354+
def add_custom_classes(model, custom_hooks):
355+
"""
356+
Allows custom variable serialization
357+
358+
:param model:
359+
:param base_classes:
360+
:param base_hooks:
361+
:param custom_hooks:
362+
:return:
363+
"""
364+
365+
base_classes = dict()
366+
base_hooks = dict()
367+
368+
for this_class, hookname in custom_hooks.items():
369+
# Build the subclass dict of the shape
370+
# {'MyClass':<MyClass Object>}
371+
this_subclass_dict = make_subclasses_dict(this_class)
372+
base_classes.update(this_subclass_dict)
373+
374+
# Build the Hook dict, of the shape
375+
# {'MySubClass':model.my_attr}
376+
hooks = getattr(model,hookname)
377+
this_hook_dict = {k:hooks for k in this_subclass_dict}
378+
base_hooks.update(this_hook_dict)
379+
380+
return base_classes, base_hooks
381+
382+
383+
def get_hook_dict(model,custom_hooks):
384+
385+
return {classname:getattr(model,hookname)
386+
for classname, hookname in custom_hooks.items()}
359387

360388
def init_thermo_model_from_dict(new, obj):
361389
for rxn_dict in obj['reactions']:

pytfa/optim/constraints.py

+39
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,45 @@ def model(self):
146146
def __repr__(self):
147147
return self.name + ': ' + self.constraint.expression.__repr__()
148148

149+
class ModelConstraint(GenericConstraint):
150+
"""
151+
Class to represent a variable attached to the model
152+
"""
153+
154+
def __init__(self, model, expr, id_, **kwargs):
155+
GenericConstraint.__init__(self,
156+
id_= id_,
157+
expr=expr,
158+
model=model,
159+
hook=model,
160+
**kwargs)
161+
162+
163+
class GeneConstraint(GenericConstraint):
164+
"""
165+
Class to represent a variable attached to a enzyme
166+
"""
167+
168+
def __init__(self, gene, expr, **kwargs):
169+
model = gene.model
170+
171+
GenericConstraint.__init__(self,
172+
expr=expr,
173+
model=model,
174+
hook=gene,
175+
**kwargs)
176+
177+
@property
178+
def gene(self):
179+
return self.hook
180+
181+
@property
182+
def id(self):
183+
return self.gene.id
184+
185+
@property
186+
def model(self):
187+
return self.gene.model
149188

150189
class ReactionConstraint(GenericConstraint):
151190
"""

0 commit comments

Comments
 (0)