# -*- coding: utf-8 -*-
"""
.. module:: pytfa
:platform: Unix, Windows
:synopsis: Thermodynamics-based Flux Analysis
.. moduleauthor:: pyTFA team
Constraints declarations
"""
from ..utils.str import camel2underscores
from .meta import ABCRequirePrefixMeta
###################################################
### CONSTRAINTS ###
###################################################
[docs]class GenericConstraint(metaclass=ABCRequirePrefixMeta):
"""
Class to represent a generic constraint. The purpose is that the interface
is instantiated on initialization, to follow the type of interface used
by the problem, and avoid incompatibilities in optlang
Attributes:
:id: Used for DictList comprehension. Usually points back at a
enzyme or reaction id for ease of linking. Should be unique given
a constraint type.
:name: Should be a concatenation of the id and a prefix that is
specific to the variable type. will be used to address the constraint at
the solver level, and hence should be unique in the whole cobra_model
:expr: the expression of the constraint (sympy.Expression subtype)
:cobra_model: the cobra_model hook.
:constraint: links directly to the cobra_model representation of tbe constraint
"""
prefix = NotImplemented
@property
def __attrname__(self):
"""
Name the attribute the instances will have
Example: GenericConstraint -> generic_constraint
:return:
"""
return camel2underscores(self.__class__.__name__)
def __init__(self, expr, id_='', model=None, hook = None, queue=False, **kwargs):
"""
:param id_: will be used to identify the variable
(name will be a concat of this and a prefix)
:param model: the cobra.Model object
:param queue: whether or not to queue the variable for update object
:param kwargs: stuff you want to pass to the variable constructor
"""
self.hook = hook
self._id = id_
self._model = model
self.kwargs = kwargs
self._name = self.make_name()
self.get_interface(expr, queue)
self.prefix = ''
def get_interface(self, expr, queue):
"""
Called upon completion of __init__, initializes the value of self.var,
which is returned upon call, and stores the actual interfaced variable.
:return: instance of Variable from the problem
"""
if not self.name in self.model.constraints:
constraint = self.model.problem.Constraint(expression = expr,
name = self.name,
**self.kwargs)
if not queue:
self.model.add_cons_vars(constraint, sloppy=self.model.sloppy)
else:
self.model._cons_queue.append(constraint)
else:
self.constraint = self.model.constraints.get(self.name)
def make_name(self):
"""
Needs to be overridden by the subclass, concats the id with a
prefix
:return: None
"""
return self.prefix + self.id
def change_expr(self, new_expr, sloppy=False):
lb = self.constraint.lb
ub = self.constraint.ub
name = self.name
# Remove former constraint to override it
self.model.solver.remove(name)
new_cons = self.model.solver.interface.Constraint(name = name,
expression = new_expr,
ub = ub,
lb = lb)
# Add the new variant
self.model.solver.add(new_cons, sloppy=sloppy)
@property
def expr(self):
return self.constraint.expression
@expr.setter
def expr(self,value):
self.constraint.expression = value
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
@property
def id(self):
"""
for cobra.thermo.DictList compatibility
:return:
"""
return self._id
@property
def constraint(self):
return self.model.constraints[self.name]
@constraint.setter
def constraint(self, value):
self.model.constraints[self.name] = value
@property
def model(self):
return self._model
def __repr__(self):
return self.name + ': ' + self.constraint.expression.__repr__()
[docs]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)
prefix = 'MODC_'
[docs]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
prefix = 'GC_'
[docs]class ReactionConstraint(GenericConstraint):
"""
Class to represent a variable attached to a reaction
"""
def __init__(self, reaction, expr, **kwargs):
model = reaction.model
GenericConstraint.__init__(self,
expr=expr,
model=model,
hook=reaction,
**kwargs)
@property
def reaction(self):
return self.hook
@property
def id(self):
return self.reaction.id
@property
def model(self):
return self.reaction.model
prefix = 'RC_'
[docs]class NegativeDeltaG(ReactionConstraint):
"""
Class to represent thermodynamics constraints.
G: - DGR_rxn + DGoRerr_Rxn + RT * StoichCoefProd1 * LC_prod1
+ RT * StoichCoefProd2 * LC_prod2
+ RT * StoichCoefSub1 * LC_subs1
+ RT * StoichCoefSub2 * LC_subs2
- ...
= 0
"""
prefix = 'G_'
[docs]class ForwardDeltaGCoupling(ReactionConstraint):
"""
Class to represent thermodynamics coupling: DeltaG of reactions has to be
DGR < 0 for the reaction to proceed forwards
Looks like:
FU_rxn: 1000 FU_rxn + DGR_rxn < 1000
"""
def __init__(self, reaction, expr, **kwargs):
ReactionConstraint.__init__(self, reaction, expr, **kwargs)
prefix = 'FU_'
[docs]class BackwardDeltaGCoupling(ReactionConstraint):
"""
Class to represent thermodynamics coupling: DeltaG of reactions has to be
DGR > 0 for the reaction to proceed backwards
Looks like:
BU_rxn: 1000 BU_rxn - DGR_rxn < 1000
"""
def __init__(self, reaction, expr, **kwargs):
ReactionConstraint.__init__(self, reaction, expr, **kwargs)
prefix = 'BU_'
[docs]class ForwardDirectionCoupling(ReactionConstraint):
"""
Class to represent a forward directionality coupling with thermodynamics on
reaction variables
Looks like :
UF_rxn: F_rxn - M FU_rxn < 0
"""
def __init__(self, reaction, expr, **kwargs):
ReactionConstraint.__init__(self, reaction, expr, **kwargs)
prefix = 'UF_'
[docs]class BackwardDirectionCoupling(ReactionConstraint):
"""
Class to represent a backward directionality coupling with thermodynamics on
reaction variables
Looks like :
UR_rxn: R_rxn - M RU_rxn < 0
"""
def __init__(self, reaction, expr, **kwargs):
ReactionConstraint.__init__(self, reaction, expr, **kwargs)
prefix = 'UR_'
[docs]class SimultaneousUse(ReactionConstraint):
"""
Class to represent a simultaneous use constraint on reaction variables
Looks like:
SU_rxn: FU_rxn + BU_rxn <= 1
"""
prefix = 'SU_'
[docs]class DisplacementCoupling(ReactionConstraint):
"""
Class to represent the coupling to the thermodynamic displacement
Looks like:
Ln(Gamma) - (1/RT)*DGR_rxn = 0
"""
prefix = 'DC_'
[docs]class ForbiddenProfile(GenericConstraint):
"""
Class to represent a forbidden net flux directionality profile
Looks like:
FU_rxn_1 + BU_rxn_2 + ... + FU_rxn_n <= n-1
"""
def __init__(self, model, expr, id_, **kwargs):
GenericConstraint.__init__(self,
id_=id_,
expr=expr,
model=model,
**kwargs)
prefix = 'FP_'
[docs]class LinearizationConstraint(ModelConstraint):
"""
Class to represent a variable attached to a reaction
"""
@staticmethod
def from_constraints(cons, model):
return LinearizationConstraint(
name = cons.name,
expr = cons.expr,
model = model,
ub = cons.ub,
lb = cons.lb,
)
prefix = 'LC_'