API changes¶
There are three major different version of modelbase:
- version 0.2.5, which was published
- version 0.4, which introduced major api changes
- version 1.0, from which on downward compatibility is guaranteed to until at least the next major release
Importing¶
In order to make the design of modelbase more flexible to other types of models, in 0.4.0 the Model and Simulator class were refactored into their own ode submodule. Thus the imports for ode-based models changed.
# modelbase 0.2.5
from modelbase import Model, Simulator
# modelbase 0.4.0 +
from modelbase.ode import Model, Simulator
Initialization¶
# modelbase 0.2.5
Model(parameters=None)
With modelbase 1.0 it is now possible to not only add the parameters, but also all other model components as dictionaries during initialization. This has more to do with internal design choices and is discouraged to be used, as the readability with large models will most likely suffer.
# modelbase 1.0
Model(
parameters=None,
compounds=None,
algebraic_modules=None,
rate_stoichiometries=None,
rates=None,
)
Parameters¶
p = {"k0": 1, "k1": 0.5, "k2": 0.1}
# modelbase 0.2.5
m = Model(parameters=p)
m.par.k0 # Get a single parameter value
In modelbase 0.4.0 the ParameterSet was hidden as a private attribute of the Model object, and methods for the interaction were introduced. By design, adding existing or updating non-existing parameters both raised an error, in order to protect users from e.g. updating a non-existing parameter due to a small typo. For users that did not want that error handling, the add_and_update_parameters method was introduced.
# modelbase 0.4.0
m = Model(parameters=p)
m.get_parameter("k0")
m.get_all_parameters()
m.add_parameters(new_parameters)
m.update_parameters(parameter_update)
m.add_and_update_parameters(parameters)
m.remove_compounds(compounds)
In modelbase versions prior to version 1.0, parameters were stored in aParameterSet objecty. In order to make the design more flexible and Pythonic this was deprecated in version 1.0 and replaced with a vanilla dictionary. Thus, the parameters can be accessed and modified directly. However, it is still recommended to use the supplied methods for changes to parameters, as there might be parameter dependencies depending on the model type you are using.
The errors that were raised for add_parameter and update_parameter were replaced by warnings to be less intrusive.
# modelbase 1.0
m = Model(parameters=p)
m.parameters['k0']
m.get_parameter(parameter_name='k0')
m.get_parameters()
m.add_parameter(parameter_name, parameter_value)
m.add_parameters(parameters)
m.update_parameter(parameter_name, parameter_value)
m.update_parameters(parameters)
m.add_and_update_parameter(parameter_name, parameter_value)
m.add_and_update_parameters(parameters)
m.remove_compound(compound)
m.remove_compounds(compounds)
Derived parameters¶
modelbase 1.0 introduced derived parameters, which are calculated from other model parameters. They will be calculated on initialization and prior to any numerical operations, but if you want to stay on the safe side, use the update_parameters method to update parameters on which they depend instead of changing the values directly, as this will re-calculate any dependent derived parameters.
p = {"R": 8.3e-3, "T": 298.0}
m = Model(p)
m.add_derived_parameter(
parameter_name="RT", function=lambda r, t: r * t, parameters=["R", "T"]
)
m.parameters
>>> {'R': 0.0083, 'T': 298.0, 'RT': 2.4734}
Compounds¶
# modelbase 0.2.5
m.set_cpds(list(str)) # replaces all other compounds
m.add_cpd(str) # adds a single compound
m.add_cpds(list(str)) # adds multiple compounds
modelbase 0.4.0 refactored compound handling by adding removal functions of compounds and unifying the existing methods. Compounds were stored as a dictionary, with an index as their value. This index could be used to get the correct array location of simulation results etc.
# modelbase 0.4.0
m.add_compounds(compounds) # Union(str, list(str))
m.remove_compounds(compounds) # Union(str, list(str))
# Accession
m.get_compounds() # dict
m.get_compound_names() # list
m.get_all_compounds() # dict, compounds + derived compounds
In modelbase 1.0 for all numeric functions, dictionaries or pandas DataFrames can be returned instead of arrays, thus the compounds are now stored simply as a sorted list. The methods were relaxed in that they now accept any iterable instead of just lists, but were split in that no iterables of single values have to be passed.
# modelbase 1.0
m.add_compound(compound) # str
m.add_compounds(compounds) # iterable(str)
m.remove_compound(compound) # str
m.remove_compounds(compounds) # iterable(str)
# Accession
m.compounds, m.get_compounds()
m.derived_compounds, m.get_derived_compounds()
m.get_all_compounds()
Algebraic modules¶
# modelbase 0.2.5
def function(p, y):
x, y, z = y
return p.p1 * x * y * z
m.add_algebraicModule(
convert_func=function, module_name="rapid_eq", cpds=["A"], derived_cpds=["X", "Y"]
)
In modelbase 0.4.0, the argument order for the module name and function was changed to fit the add_reaction method.
# modelbase 0.4.0
def function(p, x, y, z):
return p.p1 * x * y * z
m.add_algebraic_module(
module_name="rapid_eq",
algebraic_function=function,
variables=["A"],
derived_variables=["X", "Y"],
)
In modelbase 1.0, the handling of function arguments was changed in order to reduce code repetition. Instead of hard-coding the parameter names inside of the function, they are now also supplied via an argument. In order to give the possibility of making algebraic modules dependent on other variables, like time, the modifiers argument was introduced.
# modelbase 1.0
def function(x, y, z, p1):
return p1 * x * y * z
m.add_algebraic_module(
module_name="rapid_eq",
function=function,
compounds=["A"],
derived_compounds=["X", "Y"],
modifiers=None,
parameters=["p1"],
)
Rates, Stoichiometries, Reactions¶
# modelbase 0.2.5
def v1(p, x, y):
return p.k1p * x - p.k1m * y
m.add_reaction("v1", v1, {"X": -1, "Y": 1}, "X", "Y")
# modelbase 0.4.0
def v1(p, x, y):
return p.k1p * x - p.k1m * y
m.add_reaction(
rate_name="v1",
rate_function=v1,
stoichiometries={"X": -1, "Y": 1},
variables=["X", "Y"],
)
# modelbase 1.0
def v1(x, y, k1p, k1m):
return k1p * x - k1m * y
m.add_reaction(
rate_name='v1',
function=v1,
stoichiometry={"X": -1, "Y": 1},
modifiers=None,
parameters=['k1p', 'k1m'],
reversible=True,
)
Time-dependent reactions¶
# modelbase 0.2.5
m.add_reaction_v("v2", lambda p, x, **kwargs: x * kwargs["t"], {"x": 1}, "x")
# modelbase 0.4.0
m.add_reaction(
rate_name="v2",
rate_function=lambda p, t, x: x * t,
stoichiometry={"x": -1},
variables=["time", "x"],
)
# modelbase 1.0
m.add_reaction(
rate_name="v2",
rate_function=lambda x, t: x * t,
stoichiometry={"x": -1},
modifiers=["x"],
)
Dynamic functions¶
# modelbase 0.2.5
m.fullConcVec(z) # z = y0, returns array
m.rates(y, **kwargs) # time given in kwargs, returns array
m.ratesArray(y, **kwargs) # time given in kwargs, returns array
m.model(y, t, **kwargs) # returns array
# modelbase 0.4.0
m.get_full_concentration_dict(y0, time=0)
m.get_fluxes(t, y_full) # returns dict
m.get_flux_array(t, y_full)
m.get_right_hand_side(t, y0)
# modelbase 1.0
m.get_full_concentration_dict(y, t=0)
m.get_fluxes_array(y, t=0)
m.get_fluxes_dict(y, t=0)
m.get_fluxes_df(y, t=0)
m.get_right_hand_side(y, t=0)
Simulation¶
# modelbase 0.2.5
s = Simulator(m)
t, y = s.timeCourse(np.linspace(0,100,1000),np.zeros(2))
# modelbase 0.4.0
s = Simulator(m)
s.set_initial_conditions(y0={"x":1, "y":1})
t, y = s.simulate(100)
# modelbase 1.0
s = Simulator(m)
s.initialise(y0={"x":1, "y":1})
t, y = s.simulate(100)
LabelModels¶
modelbase 1.0 changes to way dynamic LabelModels are written and introduces the new LinearLabelModel, which calculates the relative label distribution of a system that is in steady state (and thus requires input of the steady state concentrations and fluxes). In order to avoid confusion with other compound and rate names, all suffixes are now written with a double underscore instead of a single one.
Since LabelModels are not only applicable to carbon labeling problems, any reference of the word carbon was changed to label. In version 1.0, modifiers that are expected to map to a total concentration (e.g. a label compound supplied as a modifier, will automatically get the __total suffix added. If a specific isotopomer concentration is required (which should rarely be the case), it has to be given directly.
# modelbase 0.2.5
m.add_carbonmap_reaction(
rateBaseName="RuBisCO",
fn=v1,
carbonmap=[2, 1, 0, 5, 3, 4],
subList="RUBP",
prodList["PGA", "PGA"],
"RUBP",
"PGA",
"PGA",
"PGA_total",
"FBP_total",
"SBP_total",
"P",
"RUBP_total",
**kwargs
)
# modelbase 0.4.0
m.add_carbonmap_reaction(
base_rate_name="RuBisCO",
rate_function=v1,
carbon_map=[2, 1, 0, 5, 3, 4],
substrates=["RUBP"],
products=["PGA", "PGA"],
additonal_variables=["PGA_total", "FBP_total", "SBP_total", "P", "RUBP_total"],
external_labels=None,
)
# modelbase 1.0
m.add_labelmap_reaction(
rate_name="RuBisCO",
function=v1,
stoichiometry={"RUBP": -1, "PGA": 2},
labelmap=[2, 1, 0, 5, 3, 4],
external_labels=None,
modifiers=["PGA", "FBP", "SBP", "P", "RUBP"],
parameters=["..."],
reversible=False,
)