1
1
# -*- coding: utf-8 -*-
2
2
3
- from __future__ import absolute_import
3
+ from __future__ import absolute_import , division
4
4
5
5
from warnings import warn
6
6
from collections import defaultdict
7
+ from operator import itemgetter
7
8
from itertools import product
9
+ from six import iteritems
8
10
from multiprocessing import Pool
9
11
import pandas as pd
10
12
from optlang .interface import OPTIMAL
16
18
from cobra .solvers import get_solver_name , solver_dict
17
19
import cobra .util .solver as sutil
18
20
from cobra .flux_analysis import flux_variability_analysis as fva
21
+ from cobra .exceptions import OptimizationError
19
22
20
23
# attempt to import plotting libraries
21
24
try :
@@ -351,7 +354,7 @@ def production_envelope(model, reactions, objective=None, c_source=None,
351
354
- carbon_yield: if carbon source is defined and the product is a
352
355
single metabolite (mol carbon product per mol carbon feeding source)
353
356
354
- - _mass_yield : if carbon source is defined and the product is a
357
+ - mass_yield : if carbon source is defined and the product is a
355
358
single metabolite (gram product per 1 g of feeding source)
356
359
357
360
- direction: the direction of the optimization.
@@ -371,36 +374,28 @@ def production_envelope(model, reactions, objective=None, c_source=None,
371
374
'based solver interfaces.' )
372
375
reactions = model .reactions .get_by_any (reactions )
373
376
objective = model .solver .objective if objective is None else objective
377
+ result = None
374
378
with model :
375
379
model .objective = objective
376
- carbon_io = _c_input_output (model , c_source )
380
+ if c_source is None :
381
+ c_input = get_c_input (model )
382
+ else :
383
+ c_input = model .reactions .get_by_any (c_source )[0 ]
384
+ objective_reactions = list (sutil .linear_reaction_coefficients (model ))
385
+ if len (objective_reactions ) != 1 :
386
+ raise ValueError ('cannot calculate yields for objectives with '
387
+ 'multiple reactions' )
388
+ carbon_io = c_input , objective_reactions [0 ]
377
389
min_max = fva (model , reactions , fraction_of_optimum = 0 )
378
390
grid = [linspace (min_max .minimum [rxn .id ], min_max .maximum [rxn .id ],
379
391
points , endpoint = True ) for rxn in reactions ]
380
392
grid_list = list (product (* grid ))
381
- result = _envelope_for_points (model , reactions , grid_list , carbon_io )
393
+ result = envelope_for_points (model , reactions , grid_list , carbon_io )
382
394
383
395
return pd .DataFrame (result )
384
396
385
397
386
- def _c_input_output (model , c_source = None ):
387
- if c_source is None :
388
- return None , None
389
- c_input = _carbon_reaction (model .reactions .get_by_any (c_source ))
390
- objective_coefficients = sutil .linear_reaction_coefficients (model )
391
- c_output = _carbon_reaction (list (objective_coefficients ))
392
- return c_input , c_output
393
-
394
-
395
- def _carbon_reaction (reactions ):
396
- exchanges = [rxn for rxn in reactions if len (rxn .reactants ) == 1 and
397
- rxn .reactants [0 ].elements .get ('C' , 0 ) > 1 ]
398
- if len (exchanges ) != 1 :
399
- raise ValueError ('cannot calculate yields for %s' % reactions )
400
- return exchanges [0 ]
401
-
402
-
403
- def _envelope_for_points (model , reactions , grid , carbon_io ):
398
+ def envelope_for_points (model , reactions , grid , carbon_io ):
404
399
results = defaultdict (list )
405
400
for direction in ('minimum' , 'maximum' ):
406
401
sense = "min" if direction == "minimum" else "max"
@@ -416,39 +411,40 @@ def _envelope_for_points(model, reactions, grid, carbon_io):
416
411
results ['direction' ].append (direction )
417
412
results ['flux' ].append (model .solver .objective .value )
418
413
if carbon_io [0 ] is not None :
419
- results ['carbon_yield' ].append (
420
- _carbon_yield (carbon_io ))
421
- results ['mass_yield' ].append (_mass_yield (carbon_io ))
414
+ results ['carbon_yield' ].append (carbon_yield (carbon_io ))
415
+ results ['mass_yield' ].append (mass_yield (carbon_io ))
422
416
for key , value in results .items ():
423
417
results [key ] = array (value )
424
418
if carbon_io [0 ] is not None :
425
419
results ['carbon_source' ] = carbon_io [0 ].id
426
420
return results
427
421
428
422
429
- def _carbon_flux (reaction ):
430
- """Carbon flux for a reaction
431
-
432
- Parameters
433
- ----------
434
- reaction : cobra.Reaction
435
- the reaction to carbon return flux for
423
+ def carbon_yield (c_input_output ):
424
+ """ mol product per mol carbon input
436
425
437
426
Returns
438
427
-------
439
428
float
440
- reaction flux multiplied by number of carbon in reactants"""
441
- carbon = sum (metabolite .elements .get ('C' , 0 ) for
442
- metabolite in reaction .reactants )
429
+ the mol carbon atoms in the product (as defined by the model
430
+ objective) divided by the mol carbon in the input reactions (as
431
+ defined by the model medium) or zero in case of division by zero
432
+ arises
433
+ """
443
434
435
+ c_input , c_output = c_input_output
436
+ if c_input is None :
437
+ return nan
438
+ carbon_input_flux = total_carbon_flux (c_input , consumption = True )
439
+ carbon_output_flux = total_carbon_flux (c_output , consumption = False )
444
440
try :
445
- return reaction . flux * carbon
446
- except AssertionError :
441
+ return carbon_output_flux / carbon_input_flux
442
+ except ZeroDivisionError :
447
443
return nan
448
444
449
445
450
- def _carbon_yield (c_input_output ):
451
- """Mol product per mol carbon input
446
+ def mass_yield (c_input_output ):
447
+ """Gram product divided by gram of carbon input source
452
448
453
449
Parameters
454
450
----------
@@ -459,39 +455,88 @@ def _carbon_yield(c_input_output):
459
455
Returns
460
456
-------
461
457
float
462
- the mol carbon atoms in the product (as defined by the model
463
- objective) divided by the mol carbon in the input reactions (as
464
- defined by the model medium) or zero in case of division by zero
465
- arises
458
+ gram product per 1 g of feeding source
466
459
"""
467
460
c_input , c_output = c_input_output
468
- carbon_input_flux = _carbon_flux ( c_input )
469
- carbon_output_flux = _carbon_flux ( c_output )
461
+ if input is None :
462
+ return nan
470
463
try :
471
- return carbon_output_flux / (carbon_input_flux * - 1 )
472
- except ZeroDivisionError :
464
+ c_source , source_flux = single_flux (c_input , consumption = True )
465
+ c_product , product_flux = single_flux (c_output , consumption = False )
466
+ except ValueError :
473
467
return nan
468
+ mol_prod_mol_src = product_flux / source_flux
469
+ x = mol_prod_mol_src * c_product .formula_weight
470
+ return x / c_source .formula_weight
474
471
475
472
476
- def _mass_yield ( c_input_output ):
477
- """Gram product divided by gram of carbon input source
473
+ def total_carbon_flux ( reaction , consumption = True ):
474
+ """summed product carbon flux for a reaction
478
475
479
476
Parameters
480
477
----------
481
- c_input_output : tuple
482
- Two reactions, the one that feeds carbon to the system and the one
483
- that produces carbon containing compound.
478
+ reaction : Reaction
479
+ the reaction to carbon return flux for
480
+ consumption : bool
481
+ flux for consumed metabolite, else produced
484
482
485
483
Returns
486
484
-------
487
485
float
488
- gram product per 1 g of feeding source
486
+ reaction flux multiplied by number of carbon for the products of the
487
+ reaction
488
+ """
489
+ direction = 1 if consumption else - 1
490
+ c_flux = [reaction .flux * coeff * met .elements .get ('C' , 0 ) * direction
491
+ for met , coeff in reaction .metabolites .items ()]
492
+ return sum ([flux for flux in c_flux if flux > 0 ])
493
+
494
+
495
+ def single_flux (reaction , consumption = True ):
496
+ """flux into single product for a reaction
497
+
498
+ only defined for reactions with single products
499
+
500
+ Parameters
501
+ ----------
502
+ reaction : Reaction
503
+ the reaction to product flux for
504
+ consumption : bool
505
+ flux for consumed metabolite, else produced
506
+
507
+ Returns
508
+ -------
509
+ tuple
510
+ metabolite, flux for the metabolite
511
+ """
512
+ if len (list (reaction .metabolites )) != 1 :
513
+ raise ValueError ('product flux only defined for single metabolite '
514
+ 'reactions' )
515
+ met , coeff = next (iteritems (reaction .metabolites ))
516
+ direction = 1 if consumption else - 1
517
+ return met , reaction .flux * coeff * direction
518
+
519
+
520
+ def get_c_input (model ):
521
+ """ carbon source reactions
522
+
523
+ Returns
524
+ -------
525
+ Reaction
526
+ The medium reaction with highest input carbon flux
489
527
"""
490
- c_input , c_output = c_input_output
491
- source_mass = sum (met .formula_weight for met in c_input .reactants )
492
- product_mass = sum (met .formula_weight for met in c_output .reactants )
493
528
try :
494
- mol_prod_mol_src = c_output .flux / (c_input .flux * - 1 )
495
- except AssertionError :
496
- return nan
497
- return (mol_prod_mol_src * product_mass ) / source_mass
529
+ model .solver .optimize ()
530
+ sutil .assert_optimal (model )
531
+ except OptimizationError :
532
+ return None
533
+
534
+ reactions = model .reactions .get_by_any (list (model .medium ))
535
+ reactions_fluxes = [(rxn , total_carbon_flux (rxn , consumption = True ))
536
+ for rxn in reactions ]
537
+ source_reactions = [(rxn , c_flux ) for rxn , c_flux
538
+ in reactions_fluxes if c_flux > 0 ]
539
+ try :
540
+ return max (source_reactions , key = itemgetter (1 ))[0 ]
541
+ except ValueError :
542
+ return None
0 commit comments