multilayerpy.simulate

@author: Adam Milsom
 
    MultilayerPy - build, run and optimise kinetic multi-layer models for 
    aerosol particles and films.
    
    Copyright (C) 2022  Adam Milsom
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
 
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

 
Modules
       

 
Classes
       
builtins.object
Data
Simulate

 
class Data(builtins.object)
    Data(data, name='', n_skipped_rows=0, norm=False, norm_index=0, delimiter='', normalised=False)
 
A data class which contains data to be optimised to.
 
Parameters
----------
data : np.ndarray or str
    Input data supplied as a np.ndarray or filename for a file containing
    the input data in the format: 
        column 1 --> time (s)
        column 2 --> y_experiment
        column 3 (optional) --> y_error
        
name : str, optional
    The name of the dataset.
 
n_skipped_rows : int, optional
    The number of rows to skip when reading in the data from a file. 
 
norm : bool, optional
    Whether or not to normalise the input data.
 
norm_index : int, optional
    The y_experiment column index to normalise data to. Assumed to be the
    first (0) index unless specified. 
 
delimiter : str, optional
    The delimiter to be supplied to the numpy.genfromtxt function used to read the data.
 
normalised : bool, optional
    If the data are already normalised, set this to True.
 
  Methods defined here:
__init__(self, data, name='', n_skipped_rows=0, norm=False, norm_index=0, delimiter='', normalised=False)
Initialize self.  See help(type(self)) for accurate signature.
norm(self, norm_index=0)
Normalises the data.
 
norm_index : int
    The y data index to normalise the rest of the y data to.
unnorm(self)
un-normalises the data.

Data descriptors defined here:
__dict__
dictionary for instance variables (if defined)
__weakref__
list of weak references to the object (if defined)

 
class Simulate(builtins.object)
    Simulate(model, params_dict, data=None, custom_model_y_func=None, name='model')
 
A class which takes in a ModelBuilder object and (optionally) some data
to fit to.
 
Parameters
----------
model : multilayerpy.build.ModelBuilder
    The model which is to be run. 
params_dict : dict
    A dictionary of multilayerpy.build.Parameter objects which are used to 
    run the model. 
data : multilayerpy.simulate.Data or np.ndarray
    Experimental data for use during model optimisation. 
 
custom_model_y_func : function, optional
    A function which takes the model output as an argument and returns a custom model_y output to be used for 
    fitting to data.
 
    The function is called in this fashion for KM-SUB models:
    model_y = custom_model_y_func(bulk_concs_dict,surf_concs_dict,layer_volume_array,layer_surf_area_array)
 
    where:
        bulk_concs_dict : dict
            dictionary of bulk concentration arrays for each component. Key's are strings representing 
            component numbers (e.g. bulk_concs_dict['1'] is the bulk concentration array for component 1).
            The shape of an array from bulk_concs_dict is (number_of_layers,number_of_timepoints)
 
        surf_concs_dict : dict
            dictionary of surface concentrations for each component at each time point. Key's are strings representing 
            component numbers (e.g. surf_concs_dict['1'] is the surface concentration array for component 1).
        
        layer_volume_array : numpy.array
            The volume of each model layer.
 
        layer_surf_area_array : numpy.array
            The surface area of each model layer.
 
    for KM-GAP models, the function is called slightly differently:
 
        model_y = custom_model_y_func(bulk_concs_dict,surf_concs_dict,static_surf_concs_dict,Vt,At)
        
        where bulk_concs_dict and surf_concs_dict are the same as above and:
            static_surf_concs_dict : dict
                dictionary of static surface concentrations for each component at each time point. Key's are strings representing 
                component numbers (e.g. static_surf_concs_dict['1'] is the static surface concentration array for component 1).
            
            Vt : numpy.array
                An array of model layer volumes over time with shape (number_of_layers, number_of_timepoints)
 
            At : numpy.array
                An array of model layer surface areas over time with shape (number_of_layers, number_of_timepoints)
 
name : str, optional
    The name of the model. 
 
-----------------------------------------------
Adding a custom parameter evolution function after instantiating a Simulate object:
 
    EXAMPLE 1:
    # Changing the temperature after 30 s of simulation. No extra varying parameters.
    
    >>> import copy
    >>> def param_evolution_func(t,y,param_dict,param_evolution_func_extra_vary_params=None):
    >>>     # need to deepcopy the parameter dictionary first
    >>>     param_dict = copy.deepcopy(param_dict)
    >>>     
    >>>     # half the temperature after 30 s
    >>>     if t > 30:
    >>>         param_dict['T'].value = param_dict['T'].value / 2.0
    >>>     # return the new param_dict
    >>>     return param_dict
    >>>
    >>> # now set the simulate object's param_evo_func (assuming Simulate object imported)
    >>> Simulate.param_evo_func = param_evolution_func
 
    
    EXAMPLE 2:
    # Changing the temperature after 30 s of simulation by a factor we want to fit.
    
    >>> # defining the temperature factor Parameter object
    >>> t_factor = Parameter(0.5, vary=True, bounds=(0.1,0.8),name='Temperature factor')
    >>>
    >>> # create a list with the extra varying parameter (in this case, a list with 1 element)
    >>> additional_parameters = [t_factor]
    >>>
    >>> import copy
    >>> def param_evolution_func(t,y,param_dict,param_evolution_func_extra_vary_params=None):
    >>>     # need to deepcopy the parameter dictionary first
    >>>     param_dict = copy.deepcopy(param_dict)
    >>>     
    >>>     # multiply temperature by the t_factor defined in the extra varying parameters list after 30 s
    >>>     if t > 30:
    >>>         param_dict['T'].value = param_dict['T'].value * param_evolution_func_extra_vary_params[0]
    >>>     # return the new param_dict
    >>>     return param_dict
    >>>
    >>> # set the simulate object's param_evo_func (assuming Simulate object imported)
    >>> Simulate.param_evo_func = param_evolution_func
    >>>
    >>> # set the simulate object's additional varying parameters list
    >>> Simulate.param_evo_additional_params = additional_parameters
 
  Methods defined here:
__init__(self, model, params_dict, data=None, custom_model_y_func=None, name='model')
Initialize self.  See help(type(self)) for accurate signature.
calc_Vt_At_layer_thick(self)
calculate V and A of each layer at each timepoint in the simulation
 
returns
----------
V_t, A_t, thick_t : np.ndarray
    Arrays of layer volume, area and thickness at each time point in 
    the simulation.
plot(self, norm=False, data=None, comp_number='all', save=False)
Plots the model output.
 
Parameters
----------
norm : bool, optional
    Whether to normalise the model output. The default is False.
 
data : np.ndarray, optional
    Data to plot with the model output. The default is None.
 
comp_number : int, str or list, optional
    The component(s) of the model output to plot. The default is 'all'.
 
save : bool, optional
    Whether to save the plots. 
 
Returns
-------
matplotlib.pyplot.figure object
plot_bulk_concs(self, cmap='viridis', save=False)
Plots heatmaps of the bulk concentration of each model component.
y-axis is layer number, x-axis is timepoint
 
Parameters
----------
cmap : str, optional
    The colourmap supplied to matplotlib.pyplot.pcolormesh().
 
save : bool, optional
    Whether to save the plots.
rp_vs_t(self)
Calculate the radius of the particle/thickness of the film at each 
timepoint. Returns None if not KM-GAP.
 
returns
----------
rp : np.ndarray
    radius of the particle (or film thickness) at each timepoint of the simulation.
run(self, n_layers, rp, time_span, n_time, V, A, layer_thick, Y0, dense_output=False, ode_integrator='scipy', ode_integrate_method='BDF', rtol=0.001, atol=1e-06)
Runs the simulation with the input parameters provided. 
Model output is a scipy OdeResult object
Updates the model_output, which includes an array of shape = (n_time,Lorg*n_components + n_components)
 
Parameters
----------
n_layers : int
    Number of model bulk layers. 
rp : float
    The particle radius/film thickness (in cm).
time_span : tup or list
    Times (in s) between which to perform the simulation.
n_time : float or int
    The number of timepoints to be saved by the solver. 
V : np.ndarray
    The initial bulk layer volumes.
A : np.ndarray
    The initial bulk layer surface areas.
layer_thick : np.ndarray
    The initial bulk layer thicknesses. 
dense_output : bool
    Whether to return the dense output from the ODE solver. 
Y0 : np.ndarray
    The initial concentrations of each component in each model layer.
ode_integrator : str
    The name of the integrator used to solve the ODEs.
ode_integrate_method : str
    The method used to solve the ODE system. 
rtol : float
    The relative tolerance value used to specify integration error tolerance.
    See https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html
    for details.
atol : float
    The absolute tolerance value used to specify integration error tolerance.
    See https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html
    for details.
 
returns
----------
Bunch object with the following fields defined:
     
t : ndarray, shape (n_points,)
    Time points.
y : ndarray, shape (n, n_points)
    Values of the solution at `t`.
sol : `OdeSolution` or None
    Found solution as `OdeSolution` instance; None if `dense_output` was
    set to False.
t_events : list of ndarray or None
    Contains for each event type a list of arrays at which an event of
    that type event was detected. None if `events` was None.
y_events : list of ndarray or None
    For each value of `t_events`, the corresponding value of the solution.
    None if `events` was None.
nfev : int
    Number of evaluations of the right-hand side.
njev : int
    Number of evaluations of the Jacobian.
nlu : int
    Number of LU decompositions.
status : int
    Reason for algorithm termination:
        * -1: Integration step failed.
        *  0: The solver successfully reached the end of `tspan`.
        *  1: A termination event occurred.
message : string
    Human-readable description of the termination reason.
success : bool
    True if the solver reached the interval end or a termination event
    occurred (``status >= 0``).
save_params_csv(self, filename='model_parameters.csv')
Saves model parameters to a .csv file
 
Parameters
----------
filename : str, optional
    The filename of the .csv file to be saved.
set_mcmc_params(self)
Sets the model parameters to the mean values from the MCMC sampling run.
set_model(self, model_filename, notification=True)
Updates the model used in the simulate object.
 
Parameters
----------
model_filename : str
    The filename of the .py file defining a dydt function for the model.
notification : bool, optional
    Whether a notification is printed telling the user that the model has been updated.
xy_data_total_number(self, components='all')
Returns the x-y data from the model. 
Either for all components or selected component(s).
Components are in order of component number.
 
if selected components desired, supply a list of integers. 
if one component, supply an int (component number)
 
Parameters
----------
components : int, str or list, optional
    The component number of the component of interest. Either one number
    for a single component output, a list of component numbers of interest
    or 'all' which outputs x-y data for all components.
    
returns
----------
xy_data : np.ndarray
    x-y data, first column is time (s) and the rest are in component number order
    or in the order supplied in the components list (if a list).

Data descriptors defined here:
__dict__
dictionary for instance variables (if defined)
__weakref__
list of weak references to the object (if defined)

 
Functions
       
initial_concentrations(model_type, bulk_conc_dict, surf_conc_dict, n_layers, static_surf_conc_dict=None, V=None, A=None, parameter_dict=None, vol_frac=1.0)
Returns an array of initial bulk and surface concentrations (Y0)
 
Parameters
----------
model_type : multilayerpy.build.ModelType
    The model type under consideration.
 
bulk_conc: dict
    dict of initial bulk concentration of each component (key = component number)
 
surf_conc: dict
    dict of initial surf concentration of each component (key = component number)
 
n_layers: int
    number of model layers
 
static_surf_conc_dict : dict, optional
    For KM-GAP models, the initial static-surface layer concentration needs to be
    supplied for each component.
 
V : np.ndarray, optional
    The volume of each bulk layer. Used to calculate initial total number of
    molecules for KM-GAP models. 
 
A : np.ndarray, optional
    The surface area of each bulk layer. Used to calculate initial total number of
    molecules for KM-GAP models. 
 
parameter_dict : dict, optional
    dict of multilayerpy.build.Parameter objects. For calculation of initial
    number of molecules in each model layer for KM-GAP models. 
 
vol_frac : float or list, optional
    The volume fraction of each model component. Supplied as a list of floats in 
    component number order. 
    
returns
----------
Y0 : np.ndarray
    An array of length n_layers * n_components + n_components (KM-SUB) or n_layers * n_components + 2 * n_components (KM-GAP) 
    defining the initial concentration of each model component in the surface and bulk layers. Supplied to the ODE solver.
make_layers(model_type, n_layers, bulk_radius)
Defines the volume, surface area and layer thickness for each model layer.
Bulk radius is defined as the particle radius - molecular diameter.
 
Parameters
----------
model_type : multilayerpy.build.ModelType
    The model type under consideration.
n_layers : int
    Number of model bulk layers.
bulk_radius : float
    The bulk radius (in cm) of the particle or film thickness.
 
returns
----------
V, A, layer_thick : tup
    tuple of np.ndarrays for bulk layer volumes (V), surface areas (A) and 
    layer thicknesses (layer_thick) all with length = n_layers