Experimental conditions might change during an experiment. For example, the concentration of an oxidant could increase or decrease after a certain time. There could be a correction factor which needs to be applied to the gas phase concentration of an oxidant in order to define the near-surface concentration. The temperature could change - the list goes on.
Some parameters may depend on others via some empirical relationship.
MultilayerPy facilitates the inclusion model parameter evolution. An additional set of varying parameters associated with these can also be included. To do this, the user is required to supply a function which takes the parameter dictionary as an input (along with any other additional varying parameters) and returns the modified parameter dictionary as an output.
# Construct the model used in the crash course notebook
# importing the necessaries
import numpy as np
import multilayerpy
import multilayerpy.build as build
import multilayerpy.simulate as simulate
import multilayerpy.optimize as optimize
# get the version of multilayerpy
print(multilayerpy.__version__)
# import the ModelType class
from multilayerpy.build import ModelType
# import the ReactionScheme class
from multilayerpy.build import ReactionScheme
# define the model type (KM-SUB in this case) and geometry (spherical or film)
mod_type = ModelType('km-sub','spherical')
# build the reaction tuple list, in this case only 1 tuple in the list (for 1 reaction)
# component 1 (oleic acid) reacts with component 2 (ozone)
reaction_tuple_list = [(1,2)]
# build the product tuple list, only component 3 (products) is a product
# a tuple with a single value inside is defined (value,)
product_tuple_list = [(3,)]
# now construct the reaction scheme
# we can give it a name and define the nuber of components as below
reaction_scheme = ReactionScheme(mod_type,name='Oleic acid ozonolysis',
reactants=reaction_tuple_list,
products=product_tuple_list)
# let's print out a representation of the reaction scheme
reaction_scheme.display()
# import ModelComponent class
from multilayerpy.build import ModelComponent
# making model components
# oleic acid
OA = ModelComponent(1,reaction_scheme,name='Oleic acid')
# ozone, declare that it is a gas
O3 = ModelComponent(2,reaction_scheme,gas=True,name='Ozone')
# products
prod = ModelComponent(3,reaction_scheme, name='Reaction products')
# collect into a dictionary
model_components_dict = {'1':OA,
'2':O3,
'3':prod}
# import DiffusionRegime class
from multilayerpy.build import DiffusionRegime
# making the diffusion dictionary
diff_dict = None
# make diffusion regime
diff_regime = DiffusionRegime(mod_type,model_components_dict,diff_dict=diff_dict)
# call it to build diffusion code ready for the builder
diff_regime()
# import ModelBuilder class
from multilayerpy.build import ModelBuilder
# create the model object
model = ModelBuilder(reaction_scheme,model_components_dict,diff_regime)
# build the model. Will save a file, don't include the date in the model filename
model.build(date=False)
# print out the parameters required for the model to run
print(model.req_params)
Below is where the model is finally constructed and run. I have set alpha_s_0_2 to half the "true" value. We know that this value of alpha_s_0_2should be twice what it is now. Let's pretend we don't know that. We do know that alpha_s_0_2 needs to be multiplied by some factor. This can be achieved by writing a parameter evolution function as outlined below.
# import the Simulate class
from multilayerpy.simulate import Simulate
# import the Parameter class
from multilayerpy.build import Parameter
# make the parameter dictionary
# these are parameters from the KM-SUB model description paper (see reference at the start of this notebook)
param_dict = {'delta_3':Parameter(1e-7), # cm
'alpha_s_0_2':Parameter(4.2e-4 / 2.0),
'delta_2':Parameter(0.4e-7), # cm
'Db_2':Parameter(1e-5), # cm2 s-1
'delta_1':Parameter(0.8e-7), # cm
'Db_1':Parameter(1e-10), # cm2 s-1
'Db_3':Parameter(1e-10), # cm2 s-1
'k_1_2':Parameter(1.7e-15), # cm3 s-1
'H_2':Parameter(4.8e-4), # mol cm-3 atm-1
'Xgs_2': Parameter(7.0e13), # cm-3
'Td_2': Parameter(1e-2,vary=True,bounds=(1e-3,1e-1)), # s
'w_2':Parameter(3.6e4), # cm s-1
'T':Parameter(298.0), # K
'k_1_2_surf':Parameter(6.0e-12)} # cm2 s-1
# make the simulate object with the model and parameter dictionary
sim = Simulate(model,param_dict)
# let's load in the fake noisy data - columns are (time, y, y_error)
fake_data = np.genfromtxt('noisy_data.txt')
# set the Simulate.data attribute to be the fake data, using the Data object and normalised
from multilayerpy.simulate import Data
sim.data = Data(fake_data)
#=======PARAMETER EVOLUTION FUNCTION=========
# import copy module for deepcopying (see the function below)
import copy
# give it a sensible name
# 1st argument MUST be time
# 2nd argument MUST be the current state of the ODE integration at that time
# (i.e. the number concentration (KM-SUB) or total number (KM-GAP) of each component in each layer - same format as y0 below)
# 3rd argument MUST be the parameter dictionary
# 4th *keyword* argument (optional) is a list of any additional varying parameters (useful to set the default as None)
# These are the parameters that will vary
def param_evo_func(t,y,param_dict,additional_params=None):
# we MUST make a deepcopy of the parameter dictionary
param_dict = copy.deepcopy(param_dict)
# extract the coefficient we want to vary from the param list
# the list has only one member in this case, so we just extract that member
alpha_coeff = additional_params[0]
# now let's multiply alpha_s_0_2 by this coefficient and set that value in the parameter dictionary
alpha = param_dict['alpha_s_0_2'].value
param_dict['alpha_s_0_2'].value = alpha * alpha_coeff
# return the parameter dictionary
return param_dict
# let's create the additional parameter, vary the value between 1 and 2.2. Initial guess of 1.
alpha_coeff = Parameter(1.0,vary=True,bounds=(1.0,2.2),name='alpha_coeff')
# now we need a list of additional varying Parameter objects
add_params = [alpha_coeff]
# now supply the parameter evolution function and the additional parameters to the Simulate object
sim.param_evo_func = param_evo_func
sim.param_evo_additional_params = add_params
#============================================
# define required parameters
n_layers = 10
rp = 0.2e-4 # radius in cm
time_span = [0,40] # in s
n_time = 999 # number of timepoints to save to output
#spherical V and A
# use simulate.make_layers function
V, A, layer_thick = simulate.make_layers(mod_type,n_layers,rp)
# initial conc. of everything
bulk_conc_dict = {'1':1.21e21,'2':0,'3':0} # key=model component number, value=bulk conc
surf_conc_dict = {'1':9.68e13,'2':0,'3':0} # key=model component number, value=surf conc
y0 = simulate.initial_concentrations(mod_type,bulk_conc_dict,surf_conc_dict,n_layers)
# now run the model
output = sim.run(n_layers,rp,time_span,n_time,V,A,layer_thick,Y0=y0)
%matplotlib inline
# plot the model
sim.plot()
sim.plot_bulk_concs()
# optimize the model
from multilayerpy.optimize import Optimizer
fitter = Optimizer(sim)
res = fitter.fit()
# check sim.plot shows optimised result
sim.plot()
print('Optimised value of alpha_coeff: ', sim.param_evo_additional_params[0].value)
Excellent! The optimised value of alpha_coeff is ~2.0, which is what we know to be the "true" answer (we divided alpha_s_0_2 by 2 at the beginning).
This method of indtroducing additional varying parameters can prove useful when a user wants to include special parameterisations of model input parameters.
A few points: