Skip to content

Commit

Permalink
Merge pull request #27 from EPFL-LCSB/dev
Browse files Browse the repository at this point in the history
Dev: Hotfix v 0.9.0-b1
  • Loading branch information
realLCSB committed May 14, 2019
2 parents f6e2a88 + bfec94e commit 82abb5b
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 73 deletions.
2 changes: 1 addition & 1 deletion doc/conf.py
Expand Up @@ -95,7 +95,7 @@ def __getattr__(cls, name):
version = '0.9'
# The full version, including alpha/beta/rc tags.

release = '0.9.0-b0'
release = '0.9.0-b1'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
23 changes: 20 additions & 3 deletions pytfa/core/model.py
Expand Up @@ -14,6 +14,7 @@

import pandas as pd
from numpy import empty
import optlang
from optlang.exceptions import SolverError
from cobra import DictList, Model
from cobra.core.solution import Solution
Expand Down Expand Up @@ -63,6 +64,10 @@ def __init__(self, model, name, sloppy=False):

self._cons_queue = list()
self._var_queue = list()

self._var_dict = dict()
self._cons_dict = dict()

self.sloppy=sloppy


Expand Down Expand Up @@ -182,26 +187,31 @@ def _remove_associated_consvar(self, all_cons_subclasses, all_var_subclasses,
try:
cons = self._cons_kinds[cons_type.__name__].get_by_id(strfy(element))
self.remove_constraint(cons)
except KeyError:
except KeyError as e:
pass
for var_type in all_var_subclasses:
for element in collection:
try:
var = self._var_kinds[var_type.__name__].get_by_id(strfy(element))
self.remove_variable(var)
except KeyError:
except KeyError as e:
pass


def remove_variable(self, var):
"""
Removes a variable
:param var:
:return:
"""
# Get the pytfa var object if an optlang variable is passed
if isinstance(var,optlang.Variable):
var = self._var_dict[var.name]

self._var_dict.pop(var.name)
self.remove_cons_vars(var.variable)
self.logger.debug('Removed variable {}'.format(var.name))

def remove_constraint(self, cons):
"""
Expand All @@ -210,9 +220,13 @@ def remove_constraint(self, cons):
:param cons:
:return:
"""
# Get the pytfa var object if an optlang variable is passed
if isinstance(cons,optlang.Constraint):
cons = self._cons_dict[cons.name]

self._cons_dict.pop(cons.name)
self.remove_cons_vars(cons.constraint)
self.logger.debug('Removed constraint {}'.format(cons.name))

def _push_queue(self):
"""
Expand Down Expand Up @@ -265,7 +279,10 @@ def regenerate_constraints(self):
if hasattr(self, '_cons_kinds'):
for k in self._cons_kinds:
attrname = camel2underscores(k)
delattr(self, attrname)
try:
delattr(self, attrname)
except AttributeError:
pass # The attribute may not have been set up yet

_cons_kinds = defaultdict(DictList)

Expand Down
158 changes: 93 additions & 65 deletions pytfa/io/dict.py
Expand Up @@ -14,11 +14,13 @@

from ..thermo.tmodel import ThermoModel

from ..optim.variables import ReactionVariable, MetaboliteVariable
from ..optim.constraints import ReactionConstraint, MetaboliteConstraint
from ..optim.variables import ReactionVariable, MetaboliteVariable, ModelVariable
from ..optim.constraints import ReactionConstraint, MetaboliteConstraint, ModelConstraint

from optlang.util import expr_to_json, parse_expr

from copy import copy


def get_all_subclasses(cls):
all_subclasses = []
Expand All @@ -34,10 +36,17 @@ def make_subclasses_dict(cls):
the_dict[cls.__name__] = cls
return the_dict

REACTION_VARIABLE_SUBCLASSES = make_subclasses_dict(ReactionVariable)
REACTION_CONSTRAINT_SUBCLASSES = make_subclasses_dict(ReactionConstraint)
METABOLITE_VARIABLE_SUBCLASSES = make_subclasses_dict(MetaboliteVariable)
METABOLITE_CONSTRAINT_SUBCLASSES= make_subclasses_dict(MetaboliteConstraint)

MODEL_VARIABLE_SUBCLASSES = make_subclasses_dict(ModelVariable)
MODEL_CONSTRAINT_SUBCLASSES= make_subclasses_dict(ModelConstraint)

BASE_NAME2HOOK = {
ReactionVariable :'reactions',
ReactionConstraint :'reactions',
MetaboliteVariable :'metabolites',
MetaboliteConstraint:'metabolites',
}


SOLVER_DICT = {
'optlang.gurobi_interface':'optlang-gurobi',
Expand Down Expand Up @@ -212,76 +221,79 @@ def _add_thermo_metabolite_info(met, met_dict):
if hasattr(met, 'thermo'):
met_dict['thermo'] = metabolite_thermo_to_dict(met)

def model_from_dict(obj, solver=None):
def model_from_dict(obj, solver=None, custom_hooks = None):
"""
Custom_hooks looks like
.. code:: python
custom_hooks = {<EnzymeVariable Class at 0xffffff> : 'enzymes',
... }
:param obj:
:param solver:
:param custom_hooks:
:return:
"""
# Take advantage of cobra's serialization of mets and reactions
new = cbd.model_from_dict(obj)
cbm = cbd.model_from_dict(obj)

if solver is not None:
try:
new.solver = solver
cbm.solver = solver
except SolverNotFound as snf:
raise snf
else:
try:
new.solver = obj['solver']
cbm.solver = obj['solver']
except KeyError:
pass

if custom_hooks is None:
custom_hooks = dict()

custom_hooks.update(BASE_NAME2HOOK)

if obj['kind'] == 'ThermoModel':
new = ThermoModel(thermo_data=obj['thermo_data'],
model=new,
model=cbm,
name=obj['name'],
temperature=obj['temperature'],
min_ph=obj['min_ph'],
max_ph=obj['max_ph'])
new = init_thermo_model_from_dict(new, obj)
else:
new = ThermoModel(model=cbm,
name=obj['name'])

new._push_queue()

name2class, name2hook = add_custom_classes(new,custom_hooks)

for the_var_dict in obj['variables']:
this_id = the_var_dict['id']
classname = the_var_dict['kind']
lb = the_var_dict['lb']
ub = the_var_dict['ub']
scaling_factor = the_var_dict['scaling_factor']

if classname in REACTION_VARIABLE_SUBCLASSES:
hook = new.reactions.get_by_id(this_id)
this_class = REACTION_VARIABLE_SUBCLASSES[classname]
nv = new.add_variable(kind=this_class,
hook=hook,
ub = ub,
lb = lb,
queue=True)

elif classname in METABOLITE_VARIABLE_SUBCLASSES:
hook = new.metabolites.get_by_id(this_id)
this_class = METABOLITE_VARIABLE_SUBCLASSES[classname]
nv = new.add_variable(kind=this_class,
hook=hook,
ub = ub,
lb = lb,
queue=True)

elif classname in ENZYME_VARIABLE_SUBCLASSES:
hook = new.enzymes.get_by_id(this_id)
this_class = ENZYME_VARIABLE_SUBCLASSES[classname]
if classname in MODEL_VARIABLE_SUBCLASSES:
hook = new
this_class = MODEL_VARIABLE_SUBCLASSES[classname]
nv = new.add_variable(kind=this_class,
hook=hook,
ub = ub,
lb = lb,
id_=this_id,
ub=ub,
lb=lb,
queue=True)

elif classname in MODEL_VARIABLE_SUBCLASSES:
hook = new
this_class = MODEL_VARIABLE_SUBCLASSES[classname]
elif classname in name2class:
hook = name2hook[classname].get_by_id(this_id)
this_class = name2class[classname]
nv = new.add_variable(kind=this_class,
hook=hook,
id_ = this_id,
ub = ub,
lb = lb,
queue=True)

else:
raise TypeError(
'Class {} serialization not handled yet' \
Expand All @@ -307,41 +319,24 @@ def model_from_dict(obj, solver=None):
lb = the_cons_dict['lb']
ub = the_cons_dict['ub']

if classname in REACTION_CONSTRAINT_SUBCLASSES:
hook = new.reactions.get_by_id(this_id)
this_class = REACTION_CONSTRAINT_SUBCLASSES[classname]
nc = new.add_constraint(kind=this_class, hook=hook,
expr=new_expr,
ub = ub,
lb = lb,
queue=True)
# Look for the corresponding class:

elif classname in METABOLITE_CONSTRAINT_SUBCLASSES:
hook = new.metabolites.get_by_id(this_id)
this_class = METABOLITE_CONSTRAINT_SUBCLASSES[classname]
if classname in MODEL_CONSTRAINT_SUBCLASSES:
hook=new
this_class = MODEL_CONSTRAINT_SUBCLASSES[classname]
nc = new.add_constraint(kind=this_class, hook=hook,
expr=new_expr,
expr=new_expr, id_ = this_id,
ub = ub,
lb = lb,
queue=True)

elif classname in ENZYME_CONSTRAINT_SUBCLASSES:
hook = new.enzymes.get_by_id(this_id)
this_class = ENZYME_CONSTRAINT_SUBCLASSES[classname]
elif classname in name2class:
hook = name2hook[classname].get_by_id(this_id)
this_class = name2class[classname]
nc = new.add_constraint(kind=this_class, hook=hook,
expr=new_expr,
ub = ub,
lb = lb,
queue=True)

elif classname in MODEL_CONSTRAINT_SUBCLASSES:
hook=new
this_class = MODEL_CONSTRAINT_SUBCLASSES[classname]
nc = new.add_constraint(kind=this_class, hook=hook,
expr=new_expr, id_ = this_id,
ub = ub,
lb = lb,
queue=True)
else:
raise TypeError('Class {} serialization not handled yet' \
.format(classname))
Expand All @@ -356,6 +351,39 @@ def model_from_dict(obj, solver=None):

return new

def add_custom_classes(model, custom_hooks):
"""
Allows custom variable serialization
:param model:
:param base_classes:
:param base_hooks:
:param custom_hooks:
:return:
"""

base_classes = dict()
base_hooks = dict()

for this_class, hookname in custom_hooks.items():
# Build the subclass dict of the shape
# {'MyClass':<MyClass Object>}
this_subclass_dict = make_subclasses_dict(this_class)
base_classes.update(this_subclass_dict)

# Build the Hook dict, of the shape
# {'MySubClass':model.my_attr}
hooks = getattr(model,hookname)
this_hook_dict = {k:hooks for k in this_subclass_dict}
base_hooks.update(this_hook_dict)

return base_classes, base_hooks


def get_hook_dict(model,custom_hooks):

return {classname:getattr(model,hookname)
for classname, hookname in custom_hooks.items()}

def init_thermo_model_from_dict(new, obj):
for rxn_dict in obj['reactions']:
Expand Down
39 changes: 39 additions & 0 deletions pytfa/optim/constraints.py
Expand Up @@ -146,6 +146,45 @@ def model(self):
def __repr__(self):
return self.name + ': ' + self.constraint.expression.__repr__()

class ModelConstraint(GenericConstraint):
"""
Class to represent a variable attached to the model
"""

def __init__(self, model, expr, id_, **kwargs):
GenericConstraint.__init__(self,
id_= id_,
expr=expr,
model=model,
hook=model,
**kwargs)


class GeneConstraint(GenericConstraint):
"""
Class to represent a variable attached to a enzyme
"""

def __init__(self, gene, expr, **kwargs):
model = gene.model

GenericConstraint.__init__(self,
expr=expr,
model=model,
hook=gene,
**kwargs)

@property
def gene(self):
return self.hook

@property
def id(self):
return self.gene.id

@property
def model(self):
return self.gene.model

class ReactionConstraint(GenericConstraint):
"""
Expand Down

0 comments on commit 82abb5b

Please sign in to comment.