Introduction¶
The python package OTSun (imported as otsun) is composed by
different modules, some of them collecting helper functions that
implement both mathematical and optical methods, and other ones built
around each of the main classes that define the functionality of the
package. To ease the reading of this section, where we shall be dealing
with classes and instances of those classes, whenever a class (say
Scene or LightSource) is considered, the downcase version of
their identifiers (scene and light_source in our example) will
indicate instances of those classes. Except for those classes defined by
FreeCAD (like Part.Face or Base.Vector), we refer to the
corresponding section in this manuscript for the definition and
initialization options for each of these classes. For instance, later
we explain how to create an experiment (an instance of the
Experiment class), giving an scene and a light_source, which
are instances of Scene and LightSource, described in their respective
sections.
A typical use of the package involves the creation of an experiment,
which specifies the solar optics experiment to be run, defined by
certain data that includes a scene and a light_source. The
scene holds the data of all the different objects that interact with
light rays, included in a FreeCAD document,
and where each of them has a material associated describing its
optical behaviour. Eventually, some objects may have movements,
implemented by a multi_tracking, and in such a case these elements
will be moved to maximize the absorbed energy. When the experiment is
run, the light_source creates rays, which interact with the
scene until they either leave the scene or are absorbed, and in this
last case the collected energy (among other data) is stored for future
analysis.
We comment now the main classes that have been implemented, together with its basic functionality. The complete documentation of the API can be found at https://otsun.readthedocs.io.
The Experiment class¶
An experiment is initialized giving the parameters that define it:
An scene and a light_source that describe the physical
environment where the experiment takes place, and the number of rays
that have to be simulated. The execution of the experiment is launched
with the experiment.run() method, and once it is finished, the
information that has been recollected is found in instance variables
like experiment.captured_energy_Th and
experiment.captured_energy_PV, that give the overall thermal and
photovoltaic (respectively) energy that has been collected by the active
materials found in the scene.
The Scene class¶
Instances of the class Scene hold the data used to describe the
physical objects present in an experiment, stored in three main
variables, faces, solids and materials. Each object in the
array faces (resp. solids) is a Part.Face (resp.
Part.Solid) object of FreeCAD that represents a surface (resp.
volume) that can affect the propagation of a ray incident with it. The
dictionary materials assigns to each face or solid a material
that describes its optical properties.
Such a scene is initialized with an array objects, all whose
elements are instances of Part.Feature, and typically they are all
the objects included in a FreeCAD document. The assignation of materials
to each object is done by looking at its label. Namely, if an object
obj in a FreeCAD document has a label of the form
“Label(mat_name)”, then the assigned material
scene.materials[obj] will be the Material instance mat such
that mat.name is mat_name.
For instance, the file ParabolicTrough.FCStd in the public
repository https://github.com/bielcardona/OTSunSuppMat contains a model prepared to analyze a parabolic trough
collector. The parabolic mirror (in blue) is made by extruding a
parabolic segment, and its label is Parabolic_reflector(Mir1). It
means that when imported with OTSun, it will have an associated material
whose parameter name is Mir1. In turn, this material has to be
properly defined so that it behaves as a mirror. Other
elements in this model are the central cylindrical surface (in red),
labeled Cylindrical_absorber(Abs1), and its covering (in green),
labeled Tube_glass(Glass1); hence, materials named Abs1 and
Glass1 have to be defined as an absorber surface and as a
transparent volume material, respectively.
The LightSource class¶
Instances of the class LightSource are used to model the source of
rays in an experiment. There are many parameters that define its
behaviour, like its emitting_region, describing the physical
location of the source of the rays to be emitted, and its
light_spectrum and direction_distribution, describing
respectively the distribution of wavelengths and directions of the rays
to be emitted.
The parameter light_spectrum can either be a constant, meaning that
all rays will be emitted with the same specified wavelength (given in
nanometers), or a cumulative distribution function (CDF)
\(F(\lambda)\) which is defined by interpolation on the discrete
values \((\lambda_i,F(\lambda_i))\) stored in an array
\(((\lambda_1,\lambda_2,\ldots),(F(\lambda_1),F(\lambda_2)\ldots)\).
The emitting_region has to be an instance of any class that
implements the method random_point(), which returns a random point
from where a ray will be emitted, and has an attribute
main_direction, giving the direction of the emitted ray. For
convenience, the class SunWindow implements such an emitting region
as a plane rectangle \(\Pi\) in the space, orthogonal to a fixed
direction \(u\), and such that all the objects in the scene are
contained in the rectangular semi-prism
\(\{\Pi+\xi u\mid \xi\ge 0\}\).
The parameter direction_distribution can either be None (meaning
that the emitted rays are emitted in the main direction) or an instance
of a class that implements the method angle_distribution(), giving a
random angle (in degrees) of deviation for the emitted ray with respect
to the main direction \(u\). For convenience, the class
BuieDistribution implements such deviation according to the Buie
distribution, determined by its circumsolar
ratio (CSR), which is a parameter of the class.
The Ray class¶
Instances of the class Ray model light rays, which are emitted by a
light_source. A ray is initialized giving its initial
optical_state, as well as the scene where it will travel.
Instances of OpticalState gather some relevant information of a
light ray at a given moment, like its direction, polarization
and material, giving, respectively, the direction and polarization
vector of the ray, and the material (optical medium) where it is
traveling. When the method ray.run() is called, the propagation of
the ray inside the scene starts to be simulated. A simplified version of
the iteration process is:
- Find the closest intersection of
raywith objects inscene. - If no intersection is found, the
rayis lost and the simulation is finished. - If the first intersection is with an object having a determined
material, then the methodmaterial.change_of_optical_state()is called (with different parameters that determine how the ray hits the material), which decides if the ray is reflected or refracted (and gives the next optical state) or that the ray has been absorbed by some active optical element. - If the ray has been reflected or refracted, go to step 1. Otherwise, the simulation is finished.
The Material class¶
The Material class is the most complex of all the classes
implemented in OTSun, since there are many kinds of materials, and their
optical properties need to be explicitly defined. There are two main
subclasses, SurfaceMaterial and VolumeMaterial, corresponding,
respectively, to materials that can be assumed to be two-dimensional
(like first surface mirrors and selective absorbers) or not (like
glasses, second surface mirrors, PV active materials, thin films, …).
Any material has an important property, material.name, indicating
how it will be called when identifying objects in a scene. The physical properties of a material are encoded in
material.properties, a dictionary whose contents depend on the kind
of material.
Any user willing to use his own materials in his experiments can
subclass SurfaceMaterial or VolumeMaterial to adapt the contents
of material.properties, which implement the specific properties of
the materials. The user must override the method
material.change_of_optical_state() to implement the computation of
how the interaction with the material changes the optical state
(direction, polarization, etc.) of a ray.
Additionally, since it is interesting to store externally the properties
of materials, the method material.to_json() and the class method
SubclassedMaterial.load_from_json(info) should be implemented. The
first one must convert any information stored in material.properties
into a serializable dictionary, and the second one must use this
dictionary to reconstruct the material.properties dictionary.
The VolumeMaterial class¶
Instances of VolumeMaterial represent the optical properties of
physical objects whose depth is not negligible, like glasses or PV
active materials, where the ray energy attenuation is determined by the
Beer–Lambert law. In this case, the method
material.change_of_optical_state() is generically implemented using
the law of reflection, Snell’s law, and Fresnel’s equations, but
any user could subclass it and implement some other optical behaviour of
the material.
Some subclasses of this class are provided, so that materials appearing usually in the field of solar collectors can be used without further implementation. For example:
SimpleVolumeMaterial, representing a material with constant optical parameters (refraction index and absorption coefficient, given in \(\textrm{mm}^{-1}\)).WavelengthVolumeMaterial, where the index of refraction is complex (\(\tilde n =n - i\kappa\)) and depends on the wavelength of the ray. These values are computed by interpolation from data given in tabulated form with rows \((\lambda, n(\lambda),\kappa(\lambda))\). Note that the imaginary part of the refractive index is the so called the extinction coefficient, and the absorption coefficient is calculated as \(\alpha = 4 \pi \kappa / \lambda\). The wavelengths are given in nanometers.PolarizedThinFilm, which represents a thin layer, such as an optical coating, where the thickness and light coherence (that enables interference) can not be considered as negligible in the simulation. The data values are given in tabulated form with rows \((\lambda, \theta, R_s(\lambda,\theta), R_p(\lambda,\theta), T_s(\lambda,\theta), T_p(\lambda,\theta))\), where \(\theta\) is the incidence angle, \(R\) and \(T\) denote the power reflection and transmission coefficients respectively, and sub-indexes \(s\) and \(p\) denote respectively the perpendicular and parallel ray polarization. Wavelengths are given in nanometers and incidence angles in degrees. We remark that it is precisely in this case where the ray equations are complemented by the so-called fully-coherent medium transfer matrix formalism (TMM).PVMaterial, which represents the active material in photovoltaic cells such as semiconductors or any other material with that functionality. The photo-absorption in such materials is characterized by their extinction coefficient. The values of the index of refraction \((\tilde n =n - i\kappa)\), which depends on the light wavelength, are given in tabulated form as in theWavelengthVolumeMaterialcase.
The SurfaceMaterial class¶
Any surface_material represents a two-dimensional physical object,
in the sense that its third dimension is negligible, or simply that its
behaviour does not depend on it. Examples of these objects are front
surface mirrors, selective absorbers, metallic coatings, …. In a first
approximation, the interaction of a ray with such a material can result
in a reflection, an absorption or a transmittance, each with a given
probability that may depend on the wavelength of the ray and are stored
in the dictionary p=material.properties. Hence,
material.change_of_optical_state() generically implements these
different phenomena. This behaviour is also affected by other properties
of the material, like the booleans:
p['lambertian_material'], indicating that, in the case of reflection, the direction of the reflected ray should be a random vector, instead of that computed using the law of reflection.p['thermal_material'], indicating that, in case of absorption, the energy is absorbed and processed, instead of lost in the material.
Some more specific materials are provided by subclassing
SurfaceMaterial and overriding the change_of_optical_state()
method. Some examples of these specific materials are:
AbsorberTWModelLayer, represents a thermal absorber where its absorption depends on the incidence angle, \(\theta\), according to \(\alpha =\alpha_{0} \{ 1-b (\frac{1}{\cos \theta} -1 )^c \}\). The following data values are given: \(\alpha_{0}, {b}, {c}\). In this case, the boolean propertyp['thermal_material']isTrue.MetallicSpecularLayer, represents a metal surface, such as the silver coating in second surface mirrors. Fresnel equations are considered and its characterization is defined by the complex index of refraction \((\tilde n =n - i\kappa)\) depending on the light wavelength. The data values are given in tabulated form like in theWavelengthVolumeMaterialcase.MetallicLambertianLayer, represents a metal surface where Fresnel equations are considered, but if the ray is reflected, a total diffuse reflection model with Lambertian scattering is used. In this material, the boolean propertyp['lambertian_material']isTrue. Also, its characterization is defined by the complex index of refraction \((\tilde n =n - i\kappa)\) depending on the light wavelength. The data values are given in tabulated form like in theWavelengthVolumeMaterialcase.PolarizedCoatingLayer, and its subclassesPolarizedCoatingReflectorLayer,PolarizedCoatingTransparentLayer,PolarizedCoatingAbsorberLayer, that represent thin layers such as optical coatings. The difference with thePolarizedThinFilmis that the thickness of such material is negligible. The data values are given as in thePolarizedThinFilmcase. Depending on the role of the material, three cases are defined: reflector (no light transmission is possible), transparent (reflection, absorption and transmission are possible), and thermal absorber material (the boolean propertyp['thermal_material']isTrueand no light transmission is possible). In each case, the parameters are given analogously to the case ofPolarizedThinFilm.
The MultiTracking class¶
The class MultiTracking is designed to implement movements of the
active elements in a scene so that the rays emitted by a given
light_source tend to be focused on a target (in case that the
attribute target is set to a point) or tend to return it to the
source (in case that the attribute is not set). That is,
MultiTracking can be used either to orient the solar collector to
the sun or to direct rays to a target, as happens with the segment
mirrors of a Linear Fresnel Collector (LFR) or the heliostats in solar
power tower plants.
Movements of elements are implemented by the helper class Joint, and
its subclasses CentralJoint and AxialJoint. The former
implements rotations around a given point in space (that is, with two
degrees of freedom), while in the latter the rotations are around an
axis (and hence with a single degree of freedom). Each kind of joint can
be easily represented by a geometrical object in FreeCAD, either by a
Vertex or an Edge with two points.
To describe the movement of a concrete element in the scene, one
needs to associate to this object a joint, but since the goal is to
direct the rays to a specified region, one also needs to specify the
corresponding principal vector. Here, by the principal vector, we
mean the direction that best approaches the normal of the mobile
element. When multi_tracking.target is not set, the element will be
moved so that this vector points to the source; otherwise, the movement
will be computed so that a solar ray reflected on the plane normal to
the principal vector and passing through the joint hits the point
stored in multi_tracking.target.
We associate objects in the scene to joints using the following
convention: Instead of giving to the object under
consideration a label of the form “Label(mat_name)”, where
mat_name is the identifier of the material of the object, we use
a label of the form “Label(mat_name,joint_name,normal_name)” or
“Label(mat_name,joint_name,normal_name,target_name)”, where
joint_name is the label of the FreeCAD object that describes the
joint (i.e. either a Vertex or a Edge), normal_name is the
label of the FreeCAD Edge whose direction is the principal vector
of the optical element, and target_name (if present) is the label of
the FreeCAD object acting as target.
A multi_tracking is created by giving the scene (which includes
the elements that describe the joints, together with their principal
vectors and targets, if needed) and the light_source, a
Base.Vector giving the main direction of the sun rays. Once it is
created, the method target_tracking.make_movements() transforms the
scene, rotating conveniently the elements, so that the scene behaves as
explained above.