425 |
429 |
|
</ul> |
426 |
430 |
|
%s""" % (self._target, self.data_frame._repr_html_()) |
427 |
431 |
|
return html_string |
|
432 |
+ |
|
|
433 |
+ |
|
|
434 |
+ |
class GrowthCouplingPotential(StrainDesignMethod): |
|
435 |
+ |
""" |
|
436 |
+ |
Optimize the Growth Coupling Potential through a combination of knockouts, knockins and medium additions |
|
437 |
+ |
|
|
438 |
+ |
Use the 'run' method to optimize the MILP problem and find the best solution. |
|
439 |
+ |
|
|
440 |
+ |
In order to find multiple sub-optimal solutions through the use of solution pools, please refer to the |
|
441 |
+ |
relevant solvers documentation. The underlying solver object can be accessed through |
|
442 |
+ |
GrowthCouplingPotential(...).model.solver.problem |
|
443 |
+ |
|
|
444 |
+ |
The method is described in the paper "OptCouple: Joint simulation of gene knockouts, |
|
445 |
+ |
insertions and medium modifications for prediction of growth-coupled strain designs" |
|
446 |
+ |
https://www.sciencedirect.com/science/article/pii/S2214030118300373 |
|
447 |
+ |
|
|
448 |
+ |
Attributes |
|
449 |
+ |
---------- |
|
450 |
+ |
model : cobra.Model |
|
451 |
+ |
A cobra model object containing the target reaction as well as all native and heterologous reactions. |
|
452 |
+ |
target : cobra.Reaction or string |
|
453 |
+ |
The reaction (or reaction ID) that is to be growth-coupled |
|
454 |
+ |
knockout_reactions : list |
|
455 |
+ |
A list of ID's for the reactions that the algorithm should attempt to knock out |
|
456 |
+ |
knockin_reactions : list |
|
457 |
+ |
A List of ID's for the reactions that are heterologous and that the algorithm should attempt to add |
|
458 |
+ |
medium_additions : list |
|
459 |
+ |
A list of metabolite ID's for the the metabolites that should potentially be added to the medium |
|
460 |
+ |
n_knockouts : int |
|
461 |
+ |
The maximum allowed number of knockouts |
|
462 |
+ |
n_knockins : int |
|
463 |
+ |
The maximum allowed number of knockins |
|
464 |
+ |
n_medium : int |
|
465 |
+ |
The maximum allowed number of medium additions |
|
466 |
+ |
use_solution_pool : Bool |
|
467 |
+ |
Set to True when using the Gurobi solver to identify multiple alternative solutions at once |
|
468 |
+ |
""" |
|
469 |
+ |
def __init__( |
|
470 |
+ |
self, model, target, biomass_id, knockout_reactions, knockin_reactions, medium_additions, |
|
471 |
+ |
n_knockouts, n_knockin, n_medium, remove_blocked=True, use_solution_pool=False, **kwargs |
|
472 |
+ |
): |
|
473 |
+ |
super(GrowthCouplingPotential, self).__init__(**kwargs) |
|
474 |
+ |
|
|
475 |
+ |
# Check model for forced fluxes, which should not be present. The zero solution must be allowed. |
|
476 |
+ |
for r in model.reactions: |
|
477 |
+ |
if r.lower_bound > 0 or r.upper_bound < 0: |
|
478 |
+ |
raise ValueError( |
|
479 |
+ |
"%s has a forced flux. Remove the reaction or change its bounds for the function to work." % r.id) |
|
480 |
+ |
|
|
481 |
+ |
# If a cobra.Reaction is given as target, convert to its ID string. |
|
482 |
+ |
if isinstance(target, cobra.Reaction): |
|
483 |
+ |
target = target.id |
|
484 |
+ |
self.target = target |
|
485 |
+ |
|
|
486 |
+ |
if target not in knockout_reactions: |
|
487 |
+ |
knockout_reactions.append(target) |
|
488 |
+ |
|
|
489 |
+ |
self.original_model = model |
|
490 |
+ |
self.biomass_id = biomass_id |
|
491 |
+ |
self.model = model = model.copy() |
|
492 |
+ |
|
|
493 |
+ |
# Add boundary reactions for these metabolites named "ADD_<metabolitename>" to the model, with bounds (-10,0). |
|
494 |
+ |
medium_addition_reactions = [] # List of the addition reactions added to the model |
|
495 |
+ |
for m in medium_additions: |
|
496 |
+ |
met = model.metabolites.get_by_id(m) |
|
497 |
+ |
r = model.add_boundary(met, type="medium addition", reaction_id=("ADD_" + m), lb=-10, ub=0) |
|
498 |
+ |
# r.type = "medium" |
|
499 |
+ |
medium_addition_reactions.append(r.id) |
|
500 |
+ |
|
|
501 |
+ |
# Set the solver to Gurobi for the fastest result. Set to CPLEX if Gurobi is not available. |
|
502 |
+ |
if "gurobi" in cobra.util.solver.solvers.keys(): |
|
503 |
+ |
logger.info("Changing solver to Gurobi and tweaking some parameters.") |
|
504 |
+ |
if "gurobi_interface" not in model.solver.interface.__name__: |
|
505 |
+ |
model.solver = "gurobi" |
|
506 |
+ |
# The tolerances are set to the minimum value. This gives maximum precision. |
|
507 |
+ |
problem = model.solver.problem |
|
508 |
+ |
problem.params.NodeMethod = 1 # primal simplex node relaxation |
|
509 |
+ |
problem.params.FeasibilityTol = 1e-9 |
|
510 |
+ |
problem.params.OptimalityTol = 1e-3 |
|
511 |
+ |
problem.params.IntFeasTol = 1e-9 |
|
512 |
+ |
problem.params.MIPgapAbs = 1e-9 |
|
513 |
+ |
problem.params.MIPgap = 1e-9 |
|
514 |
+ |
|
|
515 |
+ |
elif "cplex" in cobra.util.solver.solvers.keys(): |
|
516 |
+ |
logger.warning( |
|
517 |
+ |
"Changing solver to CPLEX, as Gurobi is not available." |
|
518 |
+ |
"This may cause a big slowdown and limit options afterwards.") |
|
519 |
+ |
if "cplex_interface" not in model.solver.interface.__name__: |
|
520 |
+ |
model.solver = "cplex" |
|
521 |
+ |
# The tolerances are set to the minimum value. This gives maximum precision. |
|
522 |
+ |
problem = model.solver.problem |
|
523 |
+ |
problem.parameters.mip.strategy.startalgorithm.set(1) # primal simplex node relaxation |
|
524 |
+ |
problem.parameters.simplex.tolerances.feasibility.set(1e-9) |
|
525 |
+ |
problem.parameters.simplex.tolerances.optimality.set(1e-3) |
|
526 |
+ |
problem.parameters.mip.tolerances.integrality.set(1e-9) |
|
527 |
+ |
problem.parameters.mip.tolerances.absmipgap.set(1e-9) |
|
528 |
+ |
problem.parameters.mip.tolerances.mipgap.set(1e-9) |
|
529 |
+ |
|
|
530 |
+ |
else: |
|
531 |
+ |
logger.warning("You are trying to run 'GrowthCouplingPotential' with %s. This might not end well." % |
|
532 |
+ |
model.solver.interface.__name__.split(".")[-1]) |
|
533 |
+ |
|
|
534 |
+ |
# Remove reactions that are blocked: no flux through these reactions possible. |
|
535 |
+ |
# This will reduce the search space for the solver, if not done already. |
|
536 |
+ |
if remove_blocked: |
|
537 |
+ |
blocked_reactions = cameo.flux_analysis.analysis.find_blocked_reactions(model) |
|
538 |
+ |
model.remove_reactions(blocked_reactions) |
|
539 |
+ |
blocked_reaction_ids = {r.id for r in blocked_reactions} |
|
540 |
+ |
knockout_reactions = {r for r in knockout_reactions if r not in blocked_reaction_ids} |
|
541 |
+ |
knockin_reactions = {r for r in knockin_reactions if r not in blocked_reaction_ids} |
|
542 |
+ |
medium_additions = {r for r in medium_additions if r not in blocked_reaction_ids} |
|
543 |
+ |
logger.debug("Removed " + str(len(blocked_reactions)) + " reactions that were blocked") |
|
544 |
+ |
|
|
545 |
+ |
# Make dual |
|
546 |
+ |
copied_model = model.copy() |
|
547 |
+ |
copied_model.optimize() |
|
548 |
+ |
dual_problem = convert_linear_problem_to_dual(copied_model.solver) |
|
549 |
+ |
logger.debug("Dual problem successfully created") |
|
550 |
+ |
|
|
551 |
+ |
# Combine primal and dual |
|
552 |
+ |
primal_problem = model.solver |
|
553 |
+ |
|
|
554 |
+ |
for var in dual_problem.variables: # All variables in the dual are copied to the primal |
|
555 |
+ |
var = primal_problem.interface.Variable.clone(var) |
|
556 |
+ |
primal_problem.add(var) |
|
557 |
+ |
for const in dual_problem.constraints: # All constraints in the dual are copied to the primal |
|
558 |
+ |
const = primal_problem.interface.Constraint.clone(const, model=primal_problem) |
|
559 |
+ |
primal_problem.add(const) |
|
560 |
+ |
logger.debug("Dual and primal combined") |
|
561 |
+ |
|
|
562 |
+ |
dual_problem.optimize() |
|
563 |
+ |
|
|
564 |
+ |
# Dictionaries to hold the binary control variables: |
|
565 |
+ |
native_y_vars = {} # 1 for knockout, 0 for active |
|
566 |
+ |
heterologous_y_vars = {} # 1 for knockin, 0 for inactive |
|
567 |
+ |
medium_y_vars = {} # 1 for medium addition (up to -10), 0 for no addition |
|
568 |
+ |
|
|
569 |
+ |
# Now the fun stuff |
|
570 |
+ |
constrained_dual_vars = set() |
|
571 |
+ |
M = max(-cobra.Configuration().lower_bound, cobra.Configuration().upper_bound) # Large value for big-M method |
|
572 |
+ |
|
|
573 |
+ |
# Fill the dictionaries with binary variables |
|
574 |
+ |
# For the knockouts: only for the native reactions which are not exchanges |
|
575 |
+ |
for reac_id in knockout_reactions: |
|
576 |
+ |
reaction = model.reactions.get_by_id(reac_id) |
|
577 |
+ |
|
|
578 |
+ |
# Add constraint variables |
|
579 |
+ |
interface = model.solver.interface |
|
580 |
+ |
y_var = interface.Variable("y_" + reaction.id, type="binary") |
|
581 |
+ |
|
|
582 |
+ |
# Constrain the primal: flux through reactions within (-1000, 1000) |
|
583 |
+ |
model.solver.add(interface.Constraint(reaction.flux_expression - M * (1 - y_var), ub=0, |
|
584 |
+ |
name="primal_y_const_" + reaction.id + "_ub")) |
|
585 |
+ |
model.solver.add(interface.Constraint(reaction.flux_expression + M * (1 - y_var), lb=0, |
|
586 |
+ |
name="primal_y_const_" + reaction.id + "_lb")) |
|
587 |
+ |
|
|
588 |
+ |
# Constrain the dual |
|
589 |
+ |
constrained_vars = [] |
|
590 |
+ |
|
|
591 |
+ |
if reaction.upper_bound != 0: |
|
592 |
+ |
dual_forward_ub = model.solver.variables["dual_" + reaction.forward_variable.name + "_ub"] |
|
593 |
+ |
model.solver.add(interface.Constraint(dual_forward_ub - M * y_var, ub=0)) |
|
594 |
+ |
constrained_vars.append(dual_forward_ub) |
|
595 |
+ |
if reaction.lower_bound != 0: |
|
596 |
+ |
dual_reverse_ub = model.solver.variables["dual_" + reaction.reverse_variable.name + "_ub"] |
|
597 |
+ |
model.solver.add(interface.Constraint(dual_reverse_ub - M * y_var, ub=0)) |
|
598 |
+ |
constrained_vars.append(dual_reverse_ub) |
|
599 |
+ |
constrained_dual_vars.update(constrained_vars) |
|
600 |
+ |
|
|
601 |
+ |
# Add to dictionary with binary variables for native reactions: |
|
602 |
+ |
native_y_vars[y_var] = reaction |
|
603 |
+ |
|
|
604 |
+ |
# For the knockins and medium additions: |
|
605 |
+ |
for reac_id in list(knockin_reactions) + list(medium_addition_reactions): |
|
606 |
+ |
reaction = model.reactions.get_by_id(reac_id) |
|
607 |
+ |
# Add constraint variables |
|
608 |
+ |
interface = model.solver.interface |
|
609 |
+ |
y_var = interface.Variable("y_" + reaction.id, type="binary") |
|
610 |
+ |
|
|
611 |
+ |
# Constrain the primal: flux through reaction within (-1000, 1000) |
|
612 |
+ |
model.solver.add(interface.Constraint(reaction.flux_expression - M * y_var, ub=0, |
|
613 |
+ |
name="primal_y_const_" + reaction.id + "_ub")) |
|
614 |
+ |
model.solver.add(interface.Constraint(reaction.flux_expression + M * y_var, lb=0, |
|
615 |
+ |
name="primal_y_const_" + reaction.id + "_lb")) |
|
616 |
+ |
|
|
617 |
+ |
# Constrain the dual |
|
618 |
+ |
constrained_vars = [] |
|
619 |
+ |
|
|
620 |
+ |
if reaction.upper_bound != 0: |
|
621 |
+ |
dual_forward_ub = model.solver.variables["dual_" + reaction.forward_variable.name + "_ub"] |
|
622 |
+ |
model.solver.add(interface.Constraint(dual_forward_ub - M * (1 - y_var), ub=0)) |
|
623 |
+ |
constrained_vars.append(dual_forward_ub) |
|
624 |
+ |
if reaction.lower_bound != 0: |
|
625 |
+ |
dual_reverse_ub = model.solver.variables["dual_" + reaction.reverse_variable.name + "_ub"] |
|
626 |
+ |
model.solver.add(interface.Constraint(dual_reverse_ub - M * (1 - y_var), ub=0)) |
|
627 |
+ |
constrained_vars.append(dual_reverse_ub) |
|
628 |
+ |
constrained_dual_vars.update(constrained_vars) |
|
629 |
+ |
|
|
630 |
+ |
# Add y variable to the corresponding modifications dictionary |
|
631 |
+ |
if reac_id in medium_addition_reactions: # reaction.type == "medium": |
|
632 |
+ |
medium_y_vars[y_var] = reaction |
|
633 |
+ |
elif reac_id in knockin_reactions: # reaction.type == "heterologous": |
|
634 |
+ |
heterologous_y_vars[y_var] = reaction |
|
635 |
+ |
else: |
|
636 |
+ |
raise RuntimeError("Something is wrong") |
|
637 |
+ |
|
|
638 |
+ |
logger.debug("Control variables created") |
|
639 |
+ |
|
|
640 |
+ |
# Set the objective |
|
641 |
+ |
primal_objective = model.solver.objective |
|
642 |
+ |
dual_objective = interface.Objective.clone( |
|
643 |
+ |
dual_problem.objective, model=model.solver |
|
644 |
+ |
) |
|
645 |
+ |
|
|
646 |
+ |
reduced_expression = optlang.symbolics.Add( |
|
647 |
+ |
*((c * v) for v, c in dual_objective.expression.as_coefficients_dict().items() |
|
648 |
+ |
if v not in constrained_dual_vars) |
|
649 |
+ |
) |
|
650 |
+ |
dual_objective = interface.Objective(reduced_expression, direction=dual_objective.direction) |
|
651 |
+ |
|
|
652 |
+ |
full_objective = interface.Objective(primal_objective.expression - dual_objective.expression, direction="max") |
|
653 |
+ |
model.objective = full_objective |
|
654 |
+ |
logger.debug("Objective created") |
|
655 |
+ |
|
|
656 |
+ |
# Add number of knockouts constraint. ub=K+1 as the target will be forced to be 1 as well |
|
657 |
+ |
knockout_number_constraint = model.solver.interface.Constraint( |
|
658 |
+ |
optlang.symbolics.Add(*native_y_vars), lb=0, ub=n_knockouts + 1, name="number_of_knockouts_constraint" |
|
659 |
+ |
) |
|
660 |
+ |
model.solver.add(knockout_number_constraint) |
|
661 |
+ |
|
|
662 |
+ |
# Add number of knockins constraint |
|
663 |
+ |
knockin_number_constraint = model.solver.interface.Constraint( |
|
664 |
+ |
optlang.symbolics.Add(*heterologous_y_vars), lb=0, ub=n_knockin, name="number_of_knockins_constraint" |
|
665 |
+ |
) |
|
666 |
+ |
model.solver.add(knockin_number_constraint) |
|
667 |
+ |
|
|
668 |
+ |
# Add number of medium additions constraint |
|
669 |
+ |
medium_additions_number_constraint = model.solver.interface.Constraint( |
|
670 |
+ |
optlang.symbolics.Add(*medium_y_vars), lb=0, ub=n_medium, name="number_of_medium_additions_constraint" |
|
671 |
+ |
) |
|
672 |
+ |
model.solver.add(medium_additions_number_constraint) |
|
673 |
+ |
|
|
674 |
+ |
logger.debug("Added constraint for number of knockouts, knockins and medium additions") |
|
675 |
+ |
|
|
676 |
+ |
# Force target control variable to be 1, but allow flux in primal |
|
677 |
+ |
model.solver.constraints["primal_y_const_" + target + "_lb"].lb = -2000 |
|
678 |
+ |
model.solver.constraints["primal_y_const_" + target + "_ub"].ub = 2000 |
|
679 |
+ |
model.variables["y_" + target].lb = 1 |
|
680 |
+ |
|
|
681 |
+ |
self.model = model |
|
682 |
+ |
self.native_y_vars = native_y_vars |
|
683 |
+ |
self.heterologous_y_vars = heterologous_y_vars |
|
684 |
+ |
self.medium_y_vars = medium_y_vars |
|
685 |
+ |
|
|
686 |
+ |
self.knockout_reactions = knockout_reactions |
|
687 |
+ |
self.knockin_reactions = knockin_reactions |
|
688 |
+ |
self.medium_additions = medium_additions |
|
689 |
+ |
|
|
690 |
+ |
self.use_solution_pool = use_solution_pool |
|
691 |
+ |
|
|
692 |
+ |
self.model.solver.configuration.presolve = True |
|
693 |
+ |
|
|
694 |
+ |
if use_solution_pool: |
|
695 |
+ |
if "gurobi_interface" not in model.solver.interface.__name__: |
|
696 |
+ |
raise NotImplementedError("Solution pools are currently only available with the Gurobi solver") |
|
697 |
+ |
|
|
698 |
+ |
# Find the N best solutions (N is specified as pool_size in run()) |
|
699 |
+ |
self.model.solver.problem.params.PoolSearchMode = 2 |
|
700 |
+ |
|
|
701 |
+ |
# Don't store solutions with objective = 0 |
|
702 |
+ |
self.model.solver.problem.params.PoolGap = 0.99 |
|
703 |
+ |
|
|
704 |
+ |
def run(self, pool_size=None, pool_time_limit=None): |
|
705 |
+ |
""" |
|
706 |
+ |
Run the GrowthCouplingPotential method |
|
707 |
+ |
|
|
708 |
+ |
pool_size: None or int |
|
709 |
+ |
The number of alternative solutions to find (0 - 2,000,000,000) |
|
710 |
+ |
pool_time_limit: None or int |
|
711 |
+ |
The maximum time (in seconds) to spend finding solutions. Will terminate with a time_limit status. |
|
712 |
+ |
""" |
|
713 |
+ |
if self.use_solution_pool: |
|
714 |
+ |
if pool_size is not None: |
|
715 |
+ |
self.model.solver.problem.params.PoolSolutions = pool_size |
|
716 |
+ |
|
|
717 |
+ |
if pool_time_limit is not None: |
|
718 |
+ |
self.model.solver.problem.params.TimeLimit = pool_time_limit |
|
719 |
+ |
|
|
720 |
+ |
sol = self.model.optimize() |
|
721 |
+ |
|
|
722 |
+ |
if self.use_solution_pool: |
|
723 |
+ |
solution_pool = self.extract_gurobi_solution_pool() |
|
724 |
+ |
return solution_pool |
|
725 |
+ |
else: |
|
726 |
+ |
objective_value = sol.objective_value |
|
727 |
+ |
knockouts = [ |
|
728 |
+ |
reac.id for var, reac in self.native_y_vars.items() if var.primal > 0.9 and reac.id != self.target |
|
729 |
+ |
] |
|
730 |
+ |
knockins = [reac.id for var, reac in self.heterologous_y_vars.items() if var.primal > 0.9] |
|
731 |
+ |
medium_additions = [reac.id for var, reac in self.medium_y_vars.items() if var.primal > 0.9] |
|
732 |
+ |
return { |
|
733 |
+ |
"knockouts": knockouts, |
|
734 |
+ |
"knockins": knockins, |
|
735 |
+ |
"medium": medium_additions, |
|
736 |
+ |
"obj_val": objective_value |
|
737 |
+ |
} |
|
738 |
+ |
|
|
739 |
+ |
def extract_gurobi_solution_pool(self): |
|
740 |
+ |
""" |
|
741 |
+ |
Extracts the individual solutions from an MILP solution pool created by the Gurobi solver. |
|
742 |
+ |
Returns the solution pool and a header with information to accompany the list of dicts. |
|
743 |
+ |
""" |
|
744 |
+ |
|
|
745 |
+ |
target_id = self.target |
|
746 |
+ |
biomass_id = self.biomass_id |
|
747 |
+ |
|
|
748 |
+ |
milp = self.model |
|
749 |
+ |
if "gurobi_interface" not in milp.solver.interface.__name__: |
|
750 |
+ |
raise ValueError( |
|
751 |
+ |
"The solver for the MILP is not Gurobi. This function only works for Gurobi created solution pools.") |
|
752 |
+ |
if target_id not in milp.solver.problem.getVars(): |
|
753 |
+ |
raise ValueError("Specified target reaction ID %s not found in the variable list." % target_id) |
|
754 |
+ |
if biomass_id not in milp.solver.problem.getVars(): |
|
755 |
+ |
raise ValueError("Specified biomass reaction ID %s not found in the variable list." % biomass_id) |
|
756 |
+ |
|
|
757 |
+ |
solution_pool = [] |
|
758 |
+ |
for solution_number in range(milp.solver.problem.solcount): |
|
759 |
+ |
solution = {} |
|
760 |
+ |
milp.solver.problem.params.SolutionNumber = solution_number |
|
761 |
+ |
solution["number"] = solution_number |
|
762 |
+ |
# Note that the objective is the growth coupling potential, not the growth or production rate |
|
763 |
+ |
solution["objective"] = milp.solver.problem.PoolObjVal |
|
764 |
+ |
|
|
765 |
+ |
# Prepare dict for looping over variables |
|
766 |
+ |
target_flux_name = "target_flux_(%s)" % target_id |
|
767 |
+ |
biomass_id_reverse = biomass_id + "_reverse_" |
|
768 |
+ |
target_id_reverse = target_id + "_reverse_" |
|
769 |
+ |
|
|
770 |
+ |
solution["growth_coupling_slope"] = None |
|
771 |
+ |
solution[target_flux_name] = 0 |
|
772 |
+ |
solution["growth_rate"] = 0 |
|
773 |
+ |
solution["growth_without_target"] = None |
|
774 |
+ |
solution["knockout"] = [] |
|
775 |
+ |
solution["knockin"] = [] |
|
776 |
+ |
solution["medium_addition"] = [] |
|
777 |
+ |
|
|
778 |
+ |
for v in milp.solver.problem.getVars(): |
|
779 |
+ |
if v.VarName.startswith("y_") and v.xn > 0.99: |
|
780 |
+ |
reaction_id = v.VarName[2:] # removing "y_" |
|
781 |
+ |
if reaction_id == self.target: |
|
782 |
+ |
continue |
|
783 |
+ |
if reaction_id in self.knockout_reactions: |
|
784 |
+ |
solution["knockout"].append(reaction_id) |
|
785 |
+ |
elif reaction_id in self.knockin_reactions: |
|
786 |
+ |
solution["knockin"].append(reaction_id) |
|
787 |
+ |
elif reaction_id in self.medium_additions: |
|
788 |
+ |
solution["medium_addition"].append(reaction_id) |
|
789 |
+ |
else: |
|
790 |
+ |
raise RuntimeError(reaction_id) |
|
791 |
+ |
|
|
792 |
+ |
# Extract target flux and growth rate. Subtract the forward and reverse fluxes to get the net flux. |
|
793 |
+ |
if v.VarName == target_id: |
|
794 |
+ |
solution[target_flux_name] += v.xn |
|
795 |
+ |
if v.VarName.startswith(target_id_reverse): |
|
796 |
+ |
solution[target_flux_name] -= v.xn |
|
797 |
+ |
if v.VarName == biomass_id: |
|
798 |
+ |
solution["growth_rate"] += v.xn |
|
799 |
+ |
if v.VarName.startswith(biomass_id_reverse): |
|
800 |
+ |
solution["growth_rate"] -= v.xn |
|
801 |
+ |
|
|
802 |
+ |
|
|
803 |
+ |
solution["growth_without_target"] = solution["growth_rate"] - solution["objective"] |
|
804 |
+ |
if round(solution["objective"], 7) > 0: |
|
805 |
+ |
solution["growth_coupling_slope"] = solution[target_flux_name] / solution["objective"] |
|
806 |
+ |
|
|
807 |
+ |
solution_pool.append(solution) |
|
808 |
+ |
|
|
809 |
+ |
return solution_pool |
|
810 |
+ |
|
|
811 |
+ |
@staticmethod |
|
812 |
+ |
def write_solutions_to_file(solutions, filename): |
|
813 |
+ |
import pandas as pd |
|
814 |
+ |
pd.DataFrame(solutions).to_csv(filename) |
#215
65698f5
afa25fd
40c23f4
d2158a1
d98d6c0
e7b90f1
592ec1d
5623fb9
#215
d5fd357
5ccf1c9
444c115
af5df1f
9332aaf
bd1033a
8802c7b
a522da2