Skip to content

Commit

Permalink
Support multiple reactions in ratio constraints… (#59)
Browse files Browse the repository at this point in the history
* feat: add_ratio_constraint supports adding multiple reactions in each side

I changed the add_ratio_constraint method to support adding multiple reactions, linear expressions or lists of reactions to make it possible to create constraints such as:
- v1/(v2 + v3) = r
- (v1 + v2)/v3 = r
- (v1 + v2)/(v3 + v4) = r
- (2 v1 + v2) / v3 = r
- etc...
  • Loading branch information
the-code-magician authored and hredestig committed Dec 2, 2016
1 parent 88e39c2 commit 5f365e1
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 15 deletions.
56 changes: 41 additions & 15 deletions cameo/core/solver_based_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,17 +401,17 @@ def fix_objective_as_constraint(self, time_machine=None, fraction=1):
time_machine(do=partial(self.solver._add_constraint, constraint, sloppy=True),
undo=partial(self.solver.remove, constraint))

def add_ratio_constraint(self, reaction1, reaction2, ratio, prefix='ratio_constraint_'):
"""Adds a ratio constraint (reaction1/reaction2 = ratio) to the model.
def add_ratio_constraint(self, expr1, expr2, ratio, prefix='ratio_constraint_'):
"""Adds a ratio constraint (expr1/expr2 = ratio) to the model.
Parameters
----------
reaction1 : str or Reaction
A reaction or a reaction ID.
reaction2 : str or Reaction
A reaction or a reaction ID.
expr1 : str, Reaction, list or sympy.Expression
A reaction, a reaction ID or a linear expression.
expr2 : str, Reaction, list or sympy.Expression
A reaction, a reaction ID or a linear expression.
ratio : float
The ratio in reaction1/reaction2 = ratio
The ratio in expr1/expr2 = ratio
prefix : str
The prefix that will be added to the constraint ID (defaults to 'ratio_constraint_').
Expand All @@ -427,13 +427,39 @@ def add_ratio_constraint(self, reaction1, reaction2, ratio, prefix='ratio_constr
>>> print(model.solver.constraints['ratio_constraint_r1_r2'])
ratio_constraint: ratio_constraint_r1_r2: 0 <= -0.5 * r1 + 1.0 * PGI <= 0
"""
if isinstance(reaction1, six.string_types):
reaction1 = self.reactions.get_by_id(reaction1)
if isinstance(reaction2, six.string_types):
reaction2 = self.reactions.get_by_id(reaction2)
ratio_constraint = self.solver.interface.Constraint(
reaction1.flux_expression - ratio * reaction2.flux_expression, lb=0, ub=0,
name=prefix + reaction1.id + '_' + reaction2.id)
if isinstance(expr1, six.string_types):
prefix_1 = expr1
expr1 = self.reactions.get_by_id(expr1).flux_expression
elif isinstance(expr1, Reaction):
prefix_1 = expr1.id
expr1 = expr1.flux_expression
elif isinstance(expr1, list):
prefix_1 = "+".join(r.id for r in expr1)
expr1 = sum([r.flux_expression for r in expr1], S.Zero)
elif not isinstance(expr1, sympy.Expr):
raise ValueError("'expr1' is not a valid expression")
else:
prefix_1 = str(expr1)

if isinstance(expr2, six.string_types):
prefix_2 = expr2
expr2 = self.reactions.get_by_id(expr2).flux_expression
elif isinstance(expr2, Reaction):
prefix_2 = expr2.id
expr2 = expr2.flux_expression
elif isinstance(expr2, list):
prefix_2 = "+".join(r.id for r in expr2)
expr2 = sum([r.flux_expression for r in expr2], S.Zero)
elif not isinstance(expr2, sympy.Expr):
raise ValueError("'expr2' is not a valid expression")
else:
prefix_2 = str(expr2)

ratio_constraint = self.solver.interface.Constraint(expr1 - ratio * expr2,
lb=0,
ub=0,
name=prefix + prefix_1 + '_' + prefix_2)

self.solver.add(ratio_constraint, sloppy=True)
return ratio_constraint

Expand Down Expand Up @@ -546,7 +572,7 @@ def essential_reactions(self, threshold=1e-6):
Returns
-------
list
List of essential reactions.
List of essential reactions
"""
essential = []
try:
Expand Down
16 changes: 16 additions & 0 deletions tests/test_solver_based_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,22 @@ def test_add_ratio_constraint(self):
self.assertAlmostEqual(solution.f, 0.870407873712)
self.assertAlmostEqual(2 * solution.x_dict['PGI'], solution.x_dict['G6PDH2r'])

cp = self.model.copy()
ratio_constr = cp.add_ratio_constraint('PGI', 'G6PDH2r', 0.5)
self.assertEqual(ratio_constr.name, 'ratio_constraint_PGI_G6PDH2r')
solution = cp.solve()
self.assertAlmostEqual(solution.f, 0.870407873712)
self.assertAlmostEqual(2 * solution.x_dict['PGI'], solution.x_dict['G6PDH2r'])

cp = self.model.copy()
ratio_constr = cp.add_ratio_constraint([cp.reactions.PGI, cp.reactions.ACALD],
[cp.reactions.G6PDH2r, cp.reactions.ACONTa], 0.5)
self.assertEqual(ratio_constr.name, 'ratio_constraint_PGI+ACALD_G6PDH2r+ACONTa')
solution = cp.solve()
self.assertAlmostEqual(solution.f, 0.8729595694565973)
self.assertAlmostEqual(2 * solution.x_dict['PGI'] + solution.x_dict['ACALD'],
solution.x_dict['G6PDH2r'] + solution.x_dict['ACONTa'])

def test_fix_objective_as_constraint(self):
# with TimeMachine
with TimeMachine() as tm:
Expand Down

0 comments on commit 5f365e1

Please sign in to comment.