Creating a custom Model
class¶
The Model
class and its children enable the user to construct Structures
automatically from given parameters. The class Model
can be viewed as an abstract class that defines parameters and methods common to all its children; however, it is possible to create an instance of Model
: it will have an empty Structure
. In the tutorial, we explain how the class Model
is constructed and how the user can write their own child class.
Anatomy of the Model
class¶
The parameters of the Model
class are:
n_entry
: the refractive index of the stack’s entry isotropic semi-infinite mediumn_exit
: the refractive index of the stack’s exit isotropic semi-infinite mediumwl_nm
: the wavelength in nanometerstheta_in_rad
: the angle of incidence in radians
and they are used to initialise the Model
with the following fields:
def __init__(self, n_entry, n_exit, wl_nm, theta_in_rad):
self.n_entry = n_entry
self.n_exit = n_exit
self.wl = wl_nm
self.theta_in = theta_in_rad
theta_out = np.arcsin((n_entry / n_exit) * np.sin(self.theta_in))
self.k0 = 2 * np.pi / self.wl
self.Kx = self.n_entry * np.sin(self.theta_in) # kx = Kx * k0
self.Ky = 0 # ky = Ky * k0
self.Kz_entry = self.n_entry * np.cos(self.theta_in) # kz_entry = Kz_entry * k0
self.Kz_exit = self.n_exit * np.cos(theta_out) # kz_entry = Kz_entry * k0
self.structure = self._build_structure_total()
The Model
’s structure
is an instance of Structure
created with the function _build_structure_total()
:
def _build_structure_total(self):
entry_space, exit_space = self._build_entry_exit()
structure = self._build_structure(entry_space, exit_space)
return structure
This function calls two sub-functions: _build_entry_exit()
that creates the entry and exit isotropic HalfSpaces
, and _build_structure()
that creates a Structure
containing the appropriate Layers
to represent the multilayer stack.
The function _build_entry_exit()
simply creates the entry and exit HalfSpaces
:
def _build_entry_exit(self):
epsilon_entry = np.array([[self.n_entry ** 2, 0, 0],
[0, self.n_entry ** 2, 0],
[0, 0, self.n_entry ** 2]])
epsilon_exit = np.array([[self.n_exit ** 2, 0, 0],
[0, self.n_exit ** 2, 0],
[0, 0, self.n_exit ** 2]])
entry_space = HalfSpace(epsilon_entry, self.Kx, self.Kz_entry, self.k0, category="isotropic")
exit_space = HalfSpace(epsilon_exit, self.Kx, self.Kz_exit, self.k0, category="isotropic")
return entry_space, exit_space
and the function _build_structure()
creates the Structure
representing the multilayer stack, without any Layer
when the class Model
is used:
def _build_structure(self, entry_space, exit_space):
warnings.warn("The build_function method of the Model class is used.")
return Structure(entry=entry_space, exit=exit_space, Kx=self.Kx, Ky=self.Ky, Kz_entry=self.Kz_entry, Kz_exit=self.Kz_exit, k0=self.k0, N_periods=1)
When the Structure
has been built, its reflectance can be calculated with get_refl_trans()
:
def get_refl_trans(self, circ=False, method="SM"):
return self.structure.get_refl_trans(circ=circ, method=method)
Creating a custom child¶
The core functions in the Model
class are the following:
__init__
to create theModel
instance_build_entry_exit()
to create the entry and exitHalfSpaces
_build_structure()
to create theStructure
with theLayers
_build_structure_total()
that calls_build_entry_exit()
and_build_structure()
get_refl_trans()
that calculates the reflectance of the multilayer stack represented by theModel
This constitutes a blueprint for the children classes of Model
, such as StackModel
or CholestericModel
. A child class of Model
contains functions that can be divided into three categories:
- functions that are implemented in
Model
and that the child class inherits - functions that are implemented in
Model
and that are overwritten in the child class - functions that are specific to the child class
Typically, the user’s new child class will be written as follows:
class ChildModel(Model):
def __init__(self, parameter1, parameter2, parameter3, n_entry, n_exit, wl_nm, theta_in_rad):
# Initialisation with parameters that are specific to ChildModel:
self.param1 = parameter1
self.param2 = parameter2
self.param3 = parameter3
# Initialisation with the inherited method:
# (ChildModel's parameters might be used to recalculate the parent's parameters)
super().__init__(n_entry, n_exit, wl_nm, theta_in_rad)
def _build_structure(self, entry_space, exit_space):
# Create an empty structure between isotropic half spaces
my_structure = Structure(entry_space, exit_space, self.Kx, self.Ky, self.Kz_entry, self.Kz_exit, self.k0, N_per=1)
# A custom routine with self.param1, self.param2, self.param3 that creates Layers and adds them to the Structure
my_structure.add_layers(my_list_of_layers)
# Return the Structure that contains the custom-made Layers
return my_structure
When using super().__init__
in the child class (ChildModel
), it will call the _build_structure_total()
method in the parent class (Model
), which will then call both the _build_structure()
method of the child (which overrides the parent one), and _build_entry_exit()
of the parent (since it is not overridden by a child version). CholestericModel
, SlabModel
, StackModel
and StackOpticalThicknessModel
are built this way. They also inherit get_refl_trans()
from Model
.
The user simply needs to add their own ChildModel
to the code by using the sample used as an example above with their own chosen parameters, and when calling ChildModel.get_refl_trans()
, they will immedietaly benefit from the optical calculations that have been implemented.
Of course, when the user writes a new child class, they may overwrite as many functions as they want, and they may add as many specific functions as they want. For example, MixedModel
overwrites most functions from Model
.
Model
also contains the function copy_as_stack()
that creates a StackModel
containing the same layers as a given Model
. The user will need to overwrite this function too.
Pairing the custom child with Spectrum
¶
The Spectrum
class implements the modelling of a multilayer stack over a range of wavelength and provide tools for calculating reflection spectra with the choice of the polarisation basis and for exporting the data. Pairing the user’s new child class of Model
with Spectrum
enables the user to have access to such functionalities.
A Spectrum
is defined by the following function:
def __init__(self, wl_nm_list, model_type, model_parameters):
self.wl_list = wl_nm_list # list of wavelengths in nm
self.mo_type = model_type # name of model
self.mo_param = model_parameters # dictionary with model parameters
self.data = {} # empty dictionary for storage
When the user calls the function calculate_refl()
, the name of the Model
(mo_type
) is checked and this triggers the creation of the appropriate Model
, from the parameters in the dictionary mo_param
. The user needs to add their own elif
case to identify the ChildModel
and handle its parameters correctly. For example, for the following ChildModel
’s input parameters:
def __init__(self, parameter1, parameter2, parameter3, n_entry, n_exit, wl_nm, theta_in_rad, default4=value4, default5=value5)
the new elif
case to add to Spectrum
’s calculate_refl()
corresponds to:
elif self.mo_type == "ChildModel":
default_param = dict("default4"=value4, "default5"=value5)
self.mo_param = {**default_param, **self.mo_param} # self.mo_param is added to default_param and overwrites the default parameters
model = ChildModel(self.mo_param["parameter1"],
self.mo_param["parameter2"],
self.mo_param["parameter3"],
self.mo_param["n_entry"],
self.mo_param["n_exit"],
wl,
self.mo_param["theta_in_rad"],
self.mo_param["default4"],
self.mo_param["default5"])
This says that when Spectrum
is instanciated with the parameter mo_type
equal to the string ChildModel
, an instance of ChildModel
will be created with the parameters chosen by the user.
The keys "parameter1"
, "parameter2"
, etc, can have an arbitrary name, but for clarity it is easier if the keys match the parameter’s name in the __init__
function.
Once this is done, the user can create a Spectrum
with ChildModel
as usual, as well as calculate the reflectance and export the spectra:
# Creation of the wavelengths
wl_nm_list = range(400, 800)
# Parameters for the ChildModel
# There are two default parameters: default4 and default5
# The user sets a value for default5: this overwrites the default value
# The user doesn't set a value for default4: the default value will be used
model_type = "ChildModel"
model_parameters = {"parameter1": my_value_1,
"parameter2": my_value_2,
"parameter3": my_value_3,
"default5": my_value_5,
"n_entry": n_entry,
"n_exit": n_exit,
"theta_in_rad": theta_in_rad}
# Creation of the periodic stack
my_spec = Spectrum(wl_nm_list, model_type, model_parameters)
# The functions of the Spectrum class automatically work
my_stack_spec.calculate_refl_trans()
matplotlip.pyplot.plot(wl_nm_list, my_stack_spec.data["R_ps"])
my_stack_spec.export("my_file_name.mat")