Source code for mass.thermo.conc_solution


# -*- coding: utf-8 -*-

"""Provide unified interfaces for optimization solutions for concentrations.

Based on solution implementations in :mod:`cobra.core.solution`

"""
from cobra.util.solver import check_solver_status

from numpy import array, exp, nan

from optlang.interface import OPTIMAL

from pandas import DataFrame, Series, option_context

from mass.core.mass_configuration import MassConfiguration
from mass.util.util import (
    _check_kwargs, apply_decimal_precision, get_public_attributes_and_methods)

MASSCONFIGURATION = MassConfiguration()


[docs]class ConcSolution: """A unified interface to a :class:`.ConcSolver` optimization solution. Notes ----- The :class:`.ConcSolution` is meant to be constructed by :func:`get_concentration_solution` please look at that function to fully understand the :class:`ConcSolution` class. Attributes ---------- objective_value : float The (optimal) value for the objective function. status : str The solver status related to the solution. concentrations : pandas.Series Contains the metabolite concentrations which are the primal values of metabolite variables. concentration_reduced_costs : pandas.Series Contains metabolite reduced costs, which are the dual values of metabolites variables. Keqs : pandas.Series Contains the reaction equilibrium constant values, which are primal values of Keq variables. Keq_reduced_costs : pandas.Series Contains reaction equilibrium constant reduced costs, which are the dual values of Keq variables. shadow_prices : pandas.Series Contains reaction shadow prices (dual values of constraints). """ def __init__(self, objective_value, status, concentrations, Keqs, concentration_reduced_costs=None, Keq_reduced_costs=None, shadow_prices=None): """Initialize the ConcSolution.""" super(ConcSolution, self).__init__() # For solver objective value and status self.objective_value = objective_value self.status = status # For solver variables self.concentrations = concentrations self.Keqs = Keqs # For variable reduced costs self.concentration_reduced_costs = concentration_reduced_costs self.Keq_reduced_costs = Keq_reduced_costs # For constraint shadow prices self.shadow_prices = shadow_prices
[docs] def concentrations_to_frame(self): """Get a :class:`pandas.DataFrame` of concs. and reduced costs.""" return DataFrame({'concentrations': self.concentrations, 'reduced_costs': self.concentration_reduced_costs})
[docs] def Keqs_to_frame(self): """Get a :class:`pandas.DataFrame` of Keqs and reduced costs.""" return DataFrame({'Keqs': self.Keqs, 'reduced_costs': self.Keq_reduced_costs})
[docs] def to_frame(self): """Get a :class:`pandas.DataFrame` of variables and reduced costs.""" return DataFrame({ 'variables': self.concentrations.append(self.Keqs), 'reduced_costs': self.concentration_reduced_costs.append( self.Keq_reduced_costs)
})
[docs] def _repr_html_(self): """HTML representation of the overview for the ConcSolution. Warnings -------- This method is intended for internal use only. """ if self.status == OPTIMAL: with option_context('display.max_rows', 10): html = ('<strong><em>Optimal</em> solution with objective ' 'value {:.3f}</strong><br>{}' .format(self.objective_value, self.to_frame()._repr_html_())) else: html = '<strong><em>{}</em> solution</strong>'.format(self.status) return html
[docs] def __repr__(self): """Set string representation of the solution instance. Warnings -------- This method is intended for internal use only. """ if self.status != OPTIMAL: return "<Solution {0:s} at 0x{1:x}>".format(self.status, id(self)) return "<Solution {0:.3f} at 0x{1:x}>".format(self.objective_value, id(self))
[docs] def __getitem__(self, variable): """Return the value of a metabolite concentration or reaction Keq. Parameters ---------- variable : str A variable ID for a variable in the solution. Warnings -------- This method is intended for internal use only. """ try: return self.concentrations[str(variable)] except KeyError: pass try: return self.Keqs[str(variable)] except KeyError as e: raise ValueError( "{0!r} is not a str ID of a ConcSolution variable.".format( str(e)))
[docs] def __dir__(self): """Override default dir() implementation to list only public items. Warnings -------- This method is intended for internal use only. """ return get_public_attributes_and_methods(self)
[docs] get_primal_by_id = __getitem__
[docs]def get_concentration_solution(concentration_solver, metabolites=None, reactions=None, raise_error=False, **kwargs): """Generate a solution representation of a :class:`.ConcSolver` state. Parameters --------- concentration_solver : ConcSolver The :class:`.ConcSolver` containing the mathematical problem solved. metabolites : list An iterable of :class:`.MassMetabolite` objects. Uses :attr:`.ConcSolver.included_metabolites` by default. reactions : list An iterable of :class:`.MassReaction` objects. Uses :attr:`.ConcSolver.included_reactions` by default. raise_error : bool Whether to raise an OptimizationError if solver status is not optimal. **kwargs decimal_precision : ``bool`` indicating whether to apply the :attr:`~.MassBaseConfiguration.decimal_precision` attribute of the :class:`.MassConfiguration` to the solution values. Default is ``False``. Returns ------- ConcSolution The solution of the optimization as a :class:`ConcSolution` object. """ kwargs = _check_kwargs({ "decimal_precision": False, }, kwargs) check_solver_status(concentration_solver.solver.status, raise_error=raise_error) # Get included metabolites and reactions metabolites = concentration_solver._get_included_metabolites(metabolites) reactions = concentration_solver._get_included_reactions(reactions) # Get variable IDs, metabolites, and reactions for Keqs and constraints metabolites = [m.id for m in metabolites if m.id in concentration_solver.variables] Keq_ids = [r.Keq_str for r in reactions if r.Keq_str in concentration_solver.variables] reactions = [r.id for r in reactions if r.id in concentration_solver.constraints] # Get metabolite and Keq primal values concs = array([concentration_solver.solver.primal_values[m] for m in metabolites]) Keqs = array([concentration_solver.solver.primal_values[Keq] for Keq in Keq_ids]) if concentration_solver.solver.is_integer: # Fill irrelevant arrays with nan reduced_concs = array([nan] * len(metabolites)) reduced_Keqs = array([nan] * len(Keq_ids)) shadow = array([nan] * len(reactions)) else: # Get reduced cost values and shadow prices reduced_concs = array([concentration_solver.solver.reduced_costs[m] for m in metabolites]) reduced_Keqs = array([concentration_solver.solver.reduced_costs[Keq] for Keq in Keq_ids]) shadow = array([concentration_solver.solver.shadow_prices[r] for r in reactions]) def transform_values(arr, **kwargs): """Transform array from logs to linear space and round if desired.""" if kwargs.get("decimal_precision"): arr = apply_decimal_precision( arr, MASSCONFIGURATION.decimal_precision) return arr objective_value = transform_values( exp(concentration_solver.solver.objective.value), **kwargs) concs = transform_values(exp(concs), **kwargs) Keqs = transform_values(exp(Keqs), **kwargs) reduced_concs = transform_values(reduced_concs, **kwargs) reduced_Keqs = transform_values(reduced_Keqs, **kwargs) shadow = transform_values(shadow, **kwargs) return ConcSolution( objective_value, concentration_solver.solver.status, Series(concs, metabolites, name="concentrations"), Series(Keqs, Keq_ids, name="Keqs"), Series(reduced_concs, metabolites, name="concentration_reduced_costs"), Series(reduced_Keqs, Keq_ids, name="Keq_reduced_costs"), Series(shadow, reactions, name="shadow_prices"))
[docs]def update_model_with_concentration_solution(model, concentration_solution, concentrations=True, Keqs=True, inplace=True): """Update a :mod:`mass` model with values from a :class:`ConcSolution`. Parameters ---------- model : MassModel A :mod:`mass` model to update with the new solution values. concentration_solution : ConcSolution The :class:`ConcSolution` containing the solution values to use. concentrations : bool Whether to update the metabolite concentrations of the model (the :attr:`.MassMetabolite.initial_condition` values). Keqs : bool Whether to update the reaction equilibrium constants of the model (the :attr:`.MassReaction.equilibrium_constant` values). inplace : bool Whether to modify the given model or to modify a copy of the model. Returns ------- MassModel Either the given model if ``inplace=True``, or a new copy of the model ``inplace=False``. """ if not isinstance(concentration_solution, ConcSolution): raise TypeError("Must be a ConcSolution object.") if not inplace: model = model.copy() if concentrations: model.update_initial_conditions( concentration_solution.concentrations.to_dict(), verbose=False) if Keqs: model.update_parameters( concentration_solution.Keqs.to_dict(), verbose=False) return model
__all__ = ( "ConcSolution", "get_concentration_solution", "update_model_with_concentration_solution")