Commit 36ee2729 authored by eladnoor's avatar eladnoor

adding docstrings to qualify for flake8

parent dbcfb54c
Pipeline #47368547 (#123) canceled with stage
in 1 minute and 59 seconds
"""Command-line script for estimating dGs in batch."""
# The MIT License (MIT)
#
# Copyright (c) 2013 Weizmann Institute of Science
......
"""Command-line script for estimating dG of single reactions."""
# The MIT License (MIT)
#
# Copyright (c) 2013 Weizmann Institute of Science
......@@ -37,6 +38,7 @@ from equilibrator_api import Q_, ComponentContribution, Reaction
def MakeParser():
"""Parser for equilibrator_cmd."""
parser = argparse.ArgumentParser(
description=('Estimate the Gibbs energy of a reaction. For example,'
'the following calculates dGr0 for ATP hydrolysis '
......@@ -76,9 +78,9 @@ if temperature.check(None):
else:
assert temperature.check("[temperature]")
sys.stderr.write('pH = %s\n' % p_h)
sys.stderr.write('I = %s\n' % ionic_strength)
sys.stderr.write('T = %s\n' % temperature)
sys.stderr.write(f"pH = {p_h}\n")
sys.stderr.write(f"I = {ionic_strength}\n")
sys.stderr.write(f"T = {temperature}\n")
sys.stderr.flush()
# parse the reaction
......
"""Command-line script for pathway analysis."""
# The MIT License (MIT)
#
# Copyright (c) 2013 Weizmann Institute of Science
......
......@@ -28,14 +28,27 @@ from ._version import get_versions
__version__ = get_versions()['version']
del get_versions
import numpy as np
from equilibrator_cache import Compound, ureg, Q_, R
from equilibrator_cache.compound_cache import ccache
from equilibrator_cache.thermodynamic_constants import (
FARADAY, POSSIBLE_REACTION_ARROWS, Q_, R, default_I, default_pH,
default_pMg, default_T, physiological_concentration,
standard_concentration, ureg)
default_pMg = Q_("14.0")
default_phase = 'aqueous'
default_conc_lb = Q_("1e-6 M")
default_conc_ub = Q_("1e-2 M")
def strip_units(v: np.array) -> np.array:
return np.array(list(map(float, v.flat))).reshape(v.shape)
from equilibrator_api.bounds import Bounds
from equilibrator_api.pathway import Pathway
from equilibrator_api.phased_reaction import PhasedReaction as Reaction
from equilibrator_api.component_contribution import ComponentContribution
from equilibrator_api.thermo_models import PathwayThermoModel
This diff is collapsed.
"""A wrapper for the GibbeEnergyPredictor in component-contribution."""
# The MIT License (MIT)
#
# Copyright (c) 2013 Weizmann Institute of Science
......@@ -36,9 +37,9 @@ from .phased_reaction import PhasedReaction
class ComponentContribution(object):
"""
This class is basically a wrapper for GibbsEnergyPredictor that also
holds default conditions for compounds in the different phases,
"""A wrapper class for GibbsEnergyPredictor.
Also holds default conditions for compounds in the different phases.
"""
@ureg.check(None, None, None, '[concentration]', '[temperature]')
......@@ -49,7 +50,13 @@ class ComponentContribution(object):
ionic_strength: float = default_I,
temperature: float = default_T
) -> object:
"""Create a ComponentContribution object.
:param p_h:
:param p_mg:
:param ionic_strength:
:param temperature:
"""
self.predictor = GibbsEnergyPredictor()
self.p_h = p_h
self.ionic_strength = ionic_strength
......@@ -58,6 +65,7 @@ class ComponentContribution(object):
@property
def RT(self) -> float:
"""Get the value of RT."""
return R * self.temperature
def standard_dg_prime_multi(
......@@ -119,20 +127,16 @@ class ComponentContribution(object):
reaction: PhasedReaction,
compound_to_conc: Dict[Compound, float]
) -> Tuple[float, float]:
"""Calculate the dG'0 of a single reaction.
:param reaction: an object of type Reaction
:param compound_to_conc: a dictionary mapping Compound objects to
concentration in M (default is 1M)
:return: a tuple of (dG_r_prime, dG_uncertainty) where dG_r_prime is
the estimated Gibbs free energy of reaction and dG_uncertainty is the
standard deviation of estimation. Multiply it by 1.96 to get a 95%
confidence interval (which is the value shown on eQuilibrator).
"""
Calculate the dG'0 of a single reaction
Arguments:
reaction - an object of type Reaction
compound_to_conc - a dictionary mapping Compound objects
to concentration in M (default is 1M)
Returns:
dG_r_prime - estimated Gibbs free energy of reaction
dG_uncertainty - standard deviation of estimation, multiply
by 1.96 to get a 95% confidence interval
(which is the value shown on eQuilibrator)
"""
dg_prime, dg_uncertainty = self.standard_dg_prime(reaction)
dg_prime += self.RT * reaction.dg_correction(compound_to_conc)
return dg_prime, dg_uncertainty
......@@ -141,20 +145,17 @@ class ComponentContribution(object):
self,
reaction: PhasedReaction
) -> Tuple[float, float]:
"""
Calculate the dG'm of a single reaction (i.e. if all reactants are
at 1 mM, except H2O)
"""Calculate the dG'm of a single reaction.
Arguments:
reaction - an object of type Reaction
Assume all aqueous reactants are at 1 mM, gas reactants at 1 mbar and
the rest at their standard concentration.
Returns:
physiological_dg_prime - estimated Gibbs free energy of reaction
dg_uncertainty - standard deviation of estimation, multiply
by 1.96 to get a 95% confidence interval
(which is the value shown on eQuilibrator)
:param reaction: an object of type PhasedReaction
:return: a tuple (dG_r_prime, dG_uncertainty) where dG_r_prime is
the estimated Gibbs free energy of reaction and dG_uncertainty is the
standard deviation of estimation. Multiply it by 1.96 to get a 95%
confidence interval (which is the value shown on eQuilibrator).
"""
physiological_dg_prime, dg_uncertainty = self.standard_dg_prime(
reaction)
physiological_dg_prime += \
......@@ -162,11 +163,9 @@ class ComponentContribution(object):
return physiological_dg_prime, dg_uncertainty
def ln_reversibility_index(self, reaction: PhasedReaction) -> float:
"""
Calculate the reversibility index (ln Gamma) of a single reaction
"""Calculate the reversibility index (ln Gamma) of a single reaction.
Returns:
ln_RI - The reversibility index (in natural log scale)
:return: ln_RI - The reversibility index (in natural log scale).
"""
physiological_dg_prime, _ = self.physiological_dg_prime(reaction)
......@@ -177,8 +176,14 @@ class ComponentContribution(object):
return ln_RI
def standard_e_prime(self, reaction: PhasedReaction) -> Tuple[float, float]:
"""
Calculate the E'0 of a single half-reaction
"""Calculate the E'0 of a single half-reaction.
:param reaction: a PhasedReaction object
:return: a tuple (E0_prime, E0_uncertainty) where E0_prime is
the estimated standard electrostatic potential of reaction and
E0_uncertainty is the standard deviation of estimation. Multiply it
by 1.96 to get a 95% confidence interval (which is the value shown on
eQuilibrator).
"""
n_e = reaction.check_half_reaction_balancing()
if n_e is None:
......@@ -194,8 +199,21 @@ class ComponentContribution(object):
return E0_prime, E0_uncertainty
def dG0_analysis(self, reaction: PhasedReaction) -> List[Dict[str, object]]:
def dg_analysis(
self,
reaction: PhasedReaction
) -> List[Dict[str, object]]:
"""Get the analysis of the component contribution estimation process.
:param reaction: a PhasedReaction object.
:return: the analysis results as a list of dictionaries
"""
return self.predictor.get_dg_analysis(reaction)
def IsUsingGroupContributions(self, reaction: PhasedReaction) -> bool:
"""Check whether group contribution is needed to get this reactions' dG.
:param reaction: a PhasedReaction object.
:return: true iff group contribution is needed
"""
return self.predictor.is_using_group_contribution(reaction)
"""analyze pathways using thermodynamic models."""
# The MIT License (MIT)
#
# Copyright (c) 2013 Weizmann Institute of Science
......@@ -37,28 +38,12 @@ from equilibrator_cache.reaction import (
from sbtab import SBtab
from scipy.linalg import fractional_matrix_power
from . import Q_, Compound, R, default_I, default_pH, default_pMg, default_T
from . import (
Q_, Compound, R, default_I, default_pH, default_pMg, default_T, strip_units)
from .bounds import Bounds
from .component_contribution import ComponentContribution
from .phased_reaction import PhasedReaction
from .settings import strip_units
from .thermo_models import PathwayThermoModel
class PathwayParseError(Exception):
pass
class InvalidReactionFormula(PathwayParseError):
pass
class UnbalancedReaction(PathwayParseError):
pass
class ViolatesFirstLaw(PathwayParseError):
pass
from .thermo_models import PathwayMDFData, PathwayThermoModel
class Pathway(object):
......@@ -119,10 +104,10 @@ class Pathway(object):
S_inv = np.linalg.pinv(S_T)
null_proj = np.eye(self.S.shape[1]) - S_T @ S_inv
projected = null_proj @ standard_dg_primes.T
if (projected > Q_("1e-8 kJ/mol")).any():
raise ViolatesFirstLaw(
'Supplied reaction dG values are inconsistent '
'with the stoichiometric matrix.')
assert (projected < Q_("1e-8 kJ/mol")).all(), (
"Supplied reaction standard deltaG values are inconsistent "
"with the stoichiometric matrix."
)
self.standard_dg_primes = standard_dg_primes
......@@ -143,6 +128,13 @@ class Pathway(object):
ionic_strength: float = None,
temperature: float = None
) -> None:
"""Set the aqueous conditions and recalculate the standard dG' values.
:param p_h:
:param p_mg:
:param ionic_strength:
:param temperature:
"""
if self.comp_contrib is None:
p_h = p_h or default_pH
p_mg = p_mg or default_pMg
......@@ -177,32 +169,48 @@ class Pathway(object):
self,
mapping: Callable[[Compound], str]
) -> None:
"""Use alternative compound names for outputs such as plots
Args:
mapping: a dictionary mapping compounds to their names in the model
"""Use alternative compound names for outputs such as plots.
:param mapping: a dictionary mapping compounds to their names in the
model
"""
self.compound_names = list(map(mapping, self.S.index))
@property
def bounds(self) -> Tuple[Iterable[float], Iterable[float]]:
"""Get the concentration bounds.
The order of compounds is according to the stoichiometric matrix index.
:return: tuple of (lower bounds, upper bounds)
"""
return self._bounds.GetBounds(self.S.index)
@property
def ln_conc_lb(self) -> np.array:
"""Get the log lower bounds on the concentrations.
The order of compounds is according to the stoichiometric matrix index.
:return: a NumPy array of the log lower bounds
"""
return np.array(list(map(float,
self._bounds.GetLnLowerBounds(self.S.index))))
@property
def ln_conc_ub(self) -> np.array:
"""Get the log upper bounds on the concentrations.
The order of compounds is according to the stoichiometric matrix index.
:return: a NumPy array of the log upper bounds
"""
return np.array(list(map(float,
self._bounds.GetLnUpperBounds(self.S.index))))
@staticmethod
def get_compounds(reactions: List[PhasedReaction]) -> List[Compound]:
"""
Returns a unique list of all compounds in all reactions
def get_compounds(reactions: Iterable[PhasedReaction]) -> List[Compound]:
"""Get a unique list of all compounds in all reactions.
:param reactions: an iterator of reactions
:return: a list of unique compounds
"""
compounds = set()
for r in reactions:
......@@ -210,16 +218,36 @@ class Pathway(object):
return sorted(compounds)
def calc_mdf(self, stdev_factor: float = 1.0) -> object:
def calc_mdf(self, stdev_factor: float = 1.0) -> PathwayMDFData:
"""Calculate the Max-min Driving Force.
:param stdev_factor: the factor by which to multiply the uncertainties
:return: a PathwayMDFData object with the results
"""
return PathwayThermoModel(self, stdev_factor).FindMDF()
@property
def reaction_ids(self) -> Iterable[str]:
"""Iterate through all the reaction IDs.
:return: the reaction IDs
"""
return map(lambda rxn: rxn.reaction_id, self.reactions)
@property
def reaction_formulas(self) -> Iterable[str]:
"""Iterate through all the reaction formulas.
:return: the reaction formulas
"""
return map(str, self.reactions)
@property
def net_reaction(self) -> PhasedReaction:
"""Calculate the sum of all the reactions in the pathway.
:return: the net reaction
"""
v = np.array(list(map(float, self.fluxes)))
net_rxn_stoich = self.S @ v
net_rxn_stoich = net_rxn_stoich[net_rxn_stoich != 0]
......@@ -234,12 +262,17 @@ class Pathway(object):
p_mg: float = default_pMg,
ionic_strength: float = default_I,
temperature: float = default_T) -> object:
"""Returns a pathway parsed from an input file.
"""Parse a CSV file and return a Pathway object.
Caller responsible for closing f.
Args:
f: file-like object containing CSV data describing the pathway.
:param f: file-like object containing CSV data describing the pathway
:param bounds: a Bounds object
:param p_h:
:param p_mg:
:param ionic_strength:
:param: temperature:
:return: a Pathway object
"""
reactions = []
fluxes = []
......@@ -264,6 +297,11 @@ class Pathway(object):
@classmethod
def open_sbtab(cls, sbtab) -> SBtab.SBtabDocument:
"""Open an SBtabDocument.
:param sbtab: a file name, stream, or SBtabDocument to open
:return: an SBtabDocument object
"""
if type(sbtab) == SBtab.SBtabDocument:
return sbtab
elif type(sbtab) == str:
......@@ -280,9 +318,10 @@ class Pathway(object):
@classmethod
def from_sbtab(cls, sbtab) -> object:
"""
read the sbtab file (can be a filename or file handel)
and use it to initialize the Pathway
"""Parse and SBtabDocument and return a Pathway.
:param sbtab: a file name, stream, or SBtabDocument to open
:return: a Pathway object
"""
sbtabdoc = cls.open_sbtab(sbtab)
table_ids = ['ConcentrationConstraint', 'Reaction', 'RelativeFlux',
......@@ -293,10 +332,10 @@ class Pathway(object):
sbtab = sbtabdoc.get_sbtab_by_id(table_id)
if sbtab is None:
tables = ', '.join(map(lambda s: s.table_id, sbtabdoc.sbtabs))
raise PathwayParseError(f"The SBtabDocument must have a table "
f"with the following ID: {table_id}, "
f"however, only these tables were "
f"found: {tables}")
raise ValueError(f"The SBtabDocument must have a table "
f"with the following ID: {table_id}, "
f"however, only these tables were "
f"found: {tables}")
dfs.append(sbtab.to_data_frame())
bounds_df, reaction_df, flux_df, param_df = dfs
......
"""inherit from equilibrator_cache.reaction.Reaction an add phases."""
# The MIT License (MIT)
#
# Copyright (c) 2013 Weizmann Institute of Science
......@@ -89,6 +90,7 @@ PHASED_COMPOUND_DICT = {
class PhasedReaction(Reaction):
"""A daughter class of Reaction that adds phases to each compound."""
REACTION_COUNTER = 0
......@@ -98,6 +100,12 @@ class PhasedReaction(Reaction):
arrow: str = "<=>",
rid: str = None
) -> object:
"""Create a PhasedReaction object.
:param sparse: a dictionary of Compounds to stoichiometric coefficients
:param arrow: a string representing the arrow in the chemical formula
:param rid: a string of the reaction ID
"""
super(PhasedReaction, self).__init__(sparse, arrow, rid)
if rid is not None:
......@@ -111,16 +119,33 @@ class PhasedReaction(Reaction):
@staticmethod
def get_default_phase(compound: Compound):
"""Get the default phase of a compound.
:param compound: a Compound
:return: the default phase
"""
if compound.mnx_id in NON_AQUEOUS_COMPOUND_DICT:
return NON_AQUEOUS_COMPOUND_DICT[compound.mnx_id][0]
else:
return AQUEOUS_PHASE
def change_phase(self, compound: Compound, phase: str) -> None:
"""Change the phase of a specific compound.
:param compound:
:param phase:
:return:
"""
if compound.mnx_id in NON_AQUEOUS_COMPOUND_DICT:
assert phase in NON_AQUEOUS_COMPOUND_DICT[compound.mnx_id]
possible_phases = NON_AQUEOUS_COMPOUND_DICT[compound.mnx_id]
assert phase in possible_phases, (
f"The phase of {compound} must be one of the following: "
f"{str(possible_phases)}."
)
else:
assert phase == AQUEOUS_PHASE
assert phase == AQUEOUS_PHASE, (
f"The phase of {compound} can only by aqueous."
)
self.compound_to_phase[compound] = phase
......@@ -131,7 +156,7 @@ class PhasedReaction(Reaction):
ionic_strength: float,
temperature: float
) -> Tuple[Reaction, float]:
"""
"""Split the PhasedReaction to aqueous phase and all the rest.
:param p_h:
:param ionic_strength:
......@@ -164,16 +189,12 @@ class PhasedReaction(Reaction):
self,
compound_to_conc: Dict[Compound, float]
) -> float:
"""
Calculate the concentration adjustment in the dG' of reaction.
Arguments:
compound_to_conc - a dictionary mapping Compound objects
to concentration in M (default is 1M)
"""Calculate the concentration adjustment in the dG' of reaction.
Returns:
ddg_over_rt - correction for delta G in units of RT
:param compound_to_conc: a dictionary mapping Compound objects to
concentration in M (default is 1M)
:return: the correction for delta G in units of RT
"""
# here we check that each concentration is in suitable units,
# depending on the phase of that compound
ddg_over_rt = Q_(0.0)
......@@ -200,13 +221,12 @@ class PhasedReaction(Reaction):
return ddg_over_rt
def physiological_dg_correction(self) -> float:
"""
Calculate the concentration adjustment in the dG' of reaction
assuming all reactants are in the default physiological
concentrations (i.e. 1 mM)
"""Calculate the concentration adjustment in the dG' of reaction.
Returns:
ddg_over_rt - correction for delta G in units of RT
Assuming all reactants are in the default physiological
concentrations (i.e. 1 mM)
:return: the correction for delta G in units of RT
"""
compound_to_conc = {}
for compound, phase in self.compound_to_phase.items():
......@@ -216,9 +236,9 @@ class PhasedReaction(Reaction):
return self.dg_correction(compound_to_conc)
def balance_by_oxidation(self):
"""
Takes an unbalanced reaction and converts it into an oxidation
reaction, by adding H2O, O2, Pi, CO2, and NH4+ to both sides.
"""Convert an unbalanced reaction into oxidation.
By adding H2O, O2, Pi, CO2, and NH4+ to both sides.
"""
balancing_ids = ['MNX:MNXM2', # H2O
'MNX:MNXM4', # O2
......@@ -252,15 +272,16 @@ class PhasedReaction(Reaction):
return self
@staticmethod
def get_oxidation_reaction(kegg_id):
"""
Generate a Reaction object which represents the oxidation reaction
of this compound using O2. If there are N atoms, the product must
be NH3 (and not N2) to represent biological processes.
Other atoms other than C, N, H, and O will raise an exception.
@classmethod
def get_oxidation_reaction(cls, compound: Compound) -> object:
"""Generate an oxidation Reaction for a single compound.
Generate a Reaction object which represents the oxidation reaction
of this compound using O2. If there are N atoms, the product must
be NH3 (and not N2) to represent biological processes.
Other atoms other than C, N, H, and O will raise an exception.
"""
return PhasedReaction({kegg_id: -1}).balance_by_oxidation()
return cls({compound: -1}).balance_by_oxidation()
if __name__ == '__main__':
......
# The MIT License (MIT)
#
# Copyright (c) 2013 Weizmann Institute of Science
# Copyright (c) 2018 Institute for Molecular Systems Biology,
# ETH Zurich
# Copyright (c) 2018 Novo Nordisk Foundation Center for Biosustainability,
# Technical University of Denmark
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import numpy as np
from . import Q_
default_pMg = Q_("14.0")
default_phase = 'aqueous'
default_conc_lb = Q_("1e-6 M")
default_conc_ub = Q_("1e-2 M")
def strip_units(v: np.array) -> np.array:
return np.array(list(map(float, v.flat))).reshape(v.shape)
"""thermo_models contains tools for running MDF and displaying results."""
# The MIT License (MIT)
#
# Copyright (c) 2013 Weizmann Institute of Science
......@@ -34,11 +35,15 @@ import pandas as pd
from matplotlib.lines import Line2D
from optlang.glpk_interface import Constraint, Model, Objective, Variable
from . import (
Q_, R, default_T, physiological_concentration, standard_concentration, ureg)
from . import Q_, R, default_T, standard_concentration, ureg
class PathwayMDFData(object):
"""Handle MDF results.
PathwayMDFData is a container class for MDF results, with plotting
capabilities.
"""
def __init__(
self,
......@@ -50,7 +55,16 @@ class PathwayMDFData(object):
reaction_prices: np.array,
compound_prices: np.array
) -> object:
"""Create PathwayMDFData object.
:param pathway: a Pathway object
:param B: the MDF value (in units of RT)
:param I_dir: matrix of flux directions
:param lnC: log concentrations at MDF optimum
:param y: uncertainty matrix coefficients at MDF optimum
:param reaction_prices: shadow prices of reactions
:param compound_prices: shadow prices of compound concentrations
"""
self.mdf = B * R * default_T
self.bounds = pathway._bounds.Copy()
......@@ -70,8 +84,8 @@ class PathwayMDFData(object):
optimized_dg_prime = (I_dir @ optimized_dg_prime) * RT
# all dG values are in units of RT, so we convert them to kJ/mol
reaction_data = zip(pathway.reaction_ids(),
pathway.reaction_formulas(),
reaction_data = zip(pathway.reaction_ids,
pathway.reaction_formulas,
pathway.fluxes,
standard_dg_primes,
physiological_dg_prime,
......@@ -106,7 +120,7 @@ class PathwayMDFData(object):
def calc_physiological_dg(
pathway: object
) -> np.array:
""" Add the effect of reactant physiological concentrations on the dG'
"""Add the effect of reactant physiological concentrations on the dG'.
:param pathway: Pathway object
:return: the reaction energies (in units of RT)
......@@ -121,13 +135,12 @@ class PathwayMDFData(object):
pathway: object,
concentrations: np.array
) -> np.array:
""" Add the effect of reactant concentrations on the dG'
"""Add the effect of reactant concentrations on the dG'.
:param pathway: Pathway object
:param concentrations: a NumPy array of concentrations
:return: the reaction energies (in units of RT)
"""
log_conc = np.log(concentrations / standard_concentration)
if np.isnan(pathway.standard_dg_primes).any():
......@@ -142,6 +155,10 @@ class PathwayMDFData(object):
@property
def compound_plot(self) -> plt.Figure:
"""Plot compound concentrations.
:return: matplotlib Figure
"""
ureg.setup_matplotlib(True)
data_df = self.compound_df.copy()
......@@ -189,6 +206,10 @@ class PathwayMDFData(object):
@property
def reaction_plot(self) -> plt.Figure:
"""Plot cumulative delta-G profiles.
:return: matplotlib Figure
"""
ureg.setup_matplotlib(True)
cumulative_dgms = [Q_("0 kJ/mol")] + np.cumsum(
......@@ -256,34 +277,21 @@ class PathwayThermoModel(object):
# Currently unused bounds on reaction dGs.
self.r_bounds = None
def GetPhysiologicalConcentrations(
self,
bounds: Iterable[Tuple[float, float]] = None
) -> Iterable[float]:
for lb, ub in bounds:
if lb is None or ub is None:
yield physiological_concentration
elif lb < physiological_concentration < ub:
yield physiological_concentration
else:
# if the default conc. is not in the range, use the geometric
# mean instead.
yield np.sqrt(lb * ub)
def _MakeLnConcentratonBounds(
self
) -> Tuple[Iterable[float], Iterable[float]]: