This notebook is intended to assist the user of LegPy in the simulation of an electron beam
LegPy (Low energy gamma-ray simulation in Python) is a Monte Carlo simulation code for the transportion of gamma rays and electrons with energies below (or about) a few MeVs through any medium. In this notebook only electron beams are considered.
Several geometries are supported (cylinder, orthohedron and sphere). Electrons are produced as paralell beams or from isotropic sources with energies following several spectral distributions (monoenergetic, flat, exponential, etc.).
Import modules
import LegPy as lpy
import numpy as np
import matplotlib.pyplot as plt
The user has to "construct" four objects that are the main ingredients of the MC simulation:
Let's start.
The user has to give data on attenuation coefficients of the medium.
Two options named here NIST and generic are available. Select and run only the one you choose.
Data from the National Institute of Standards and Technology of accurate CSDA ranges are available in the LegPy/electron_data folder. The user has to provide a medium name among them.
!ls LegPy/electron_data/*.txt
Now you can construct the medium providing the following data:
medium = lpy.Medium(name='water')
#medium = lpy.Medium(name='Pb')
#medium = lpy.Medium(name='Al', density=2.7)
#medium = lpy.Medium(name='bone_compact_ICRU', density=1.8)
In the absence of a data file, a generic procedure that evaluates the CSDA range from the Bethe-Bloch formula is used.
Now you can construct the medium providing the following data:
medium2 = lpy.Medium(name='water', density=1., Pmol=18.01, Z=[1,8], N=[2,1], A=[1,16], I=[19.2,95.], e_E_max = 20.)
Next you can plot the continuous slowing down approximation (CSDA) range vs energy of the medium. In the next command you have to provide:
Several media can be plotted in the same figure so you can define several media above (with different names) and compare ranges (in the same units). Also you can compare NIST with generic models for the same medium.
This step is optional so you can skip it.
E1 = 0.01 # MeV
E2 = 20. # MeV
units = 'gcm2'
energy_range = np.logspace(np.log10(E1), np.log10(E2), num=150) # 150 points in a log-scale E(MeV) between E1 and E2
medium.plot_R(energies=energy_range, units = units)
medium2.plot_R(energies=energy_range, l_style=':', units = units)
Several geometries are available.
Cylinder oriented with its axis along the z axis and its base at z=0. You have to provide:
For this geometry, you may choose either cylindrical or cartesian voxelization of the energy deposit matrix. Cylindrical voxelization is appropriate for vertical parallel beams along the z axis and isotropic sources located at the z axis. In this case, you have to input the number of intervals along the coordinates r and z:
Cartesian voxelization can also be applied in any situation and medium geometry. Here, you have to provide:
Choose your option and construct the geometry.
geometry = lpy.Geometry(name='cylinder', z=.2, r=.1, n_z=50, n_r=1) # Cylindrical voxelization
#geometry = lpy.Geometry(name='cylinder', z=.3, r=.2, n_x=10, n_y=10, n_z=10) # Cartesian voxelization
Rectangular parallelepiped oriented with its longitudinal axes parallel to the x, y, z axes. The center of bottom side is assumed to be at the origin of coordinates. In this geometry, only the cartesian voxelization is supported. You have to provide the dimensions of the orthohedron and the number of intervals along each axis:
geometry = lpy.Geometry(name='orthohedron', x=.1, y=.1, z=.1, n_x=10, n_y=10, n_z=10)
Sphere centered at (0,0,0). Both cartesian and spherical voxelization can be chosen. So you have to provide either:
Or:
#geometry = lpy.Geometry(name='sphere', r=.15, n_r=15) # Spherical voxelization
geometry = lpy.Geometry(name='sphere', diam=.1, n_x=10, n_y=10, n_z=10) # Cartesian voxelization
Plot the geometry in the reference coordinate system. This step is optional.
geometry.plot();
The user has to select one of the following options:
Input parameters:
spectrum = lpy.Spectrum(name = 'mono', E = 1.)
Input parameters:
E_w = np.array([[0.511, .80], [1.25, 0.20]]) # [[E1, w1], [E2, w2],....]
spectrum = lpy.Spectrum(name = 'multi_mono', E_w = E_w)
Input parameters:
spectrum = lpy.Spectrum(name = 'flat', E_min = 0.1, E_max = 1.0)
Input parameters:
Internal cut: 2 x E_mean > E > 0.
spectrum = lpy.Spectrum(name = 'gaussian', E_mean = 0.5, E_sigma = 0.03)
$I(E) \propto e^{-E/E_{ch}}$, with E_min < E < E_max.
Input parameters:
spectrum = lpy.Spectrum(name = 'exponential', E_min = 0.1, E_max = 1.0, E_ch = 0.5)
$ I(E) \propto \frac{1}{E} $, with E_min < E < E_max.
Input parameters:
spectrum = lpy.Spectrum(name = 'reciprocal', E_min = 0.01, E_max = 15.)
The input file must have two columns:
Energy (MeV) ------ Relative Intensity (au)
An example file is at LegPy/beam_spectra/example.txt. To use this file, just input file='example.txt'. If you want to use your own txt file, you should copy it to the same directory that this notebook is (or to load it to the current Colab session).
spectrum = lpy.Spectrum(name = 'from_file', file = 'example.txt')
You can plot the energy spectrum of incident beam. Again, just to check it is OK.
A number of electrons are generated randomly following the requested spectrum in logaritmic scale in the range 0.001 - 20 MeV. You should input:
spectrum.plot(n_part = 100000, n_bin = 50)
The user has to select one of the following options:
NOTE: In order not to waste computing time the beam geometry has to be defined in such a way that all particles reach the medium.
Parallel beam with entrance plane perpendicular to z axis. In general not applicable for the sphere.
Input parameters:
beam = lpy.Beam(particle='electron', name = 'parallel')
#beam = lpy.Beam(name = 'parallel', theta = 15.0, phi = 30.0, p_in = np.array([0.1, -0.1, 0.0]))
#beam = lpy.Beam(name = 'parallel', theta = 19.0, phi = 30.0)
Three options are available:
Input parameters:
x, y, z = 0.03, -0.03, 0.05 # cm
beam = lpy.Beam(particle='electron', name = 'isotropic', p_in = np.array ([x, y, z]))
#beam = lpy.Beam(name = 'isotropic')
Input parameters:
z = 1. # cm
x, y = 0.02, -0.01 # cm
beam = lpy.Beam(particle='electron', name = 'isotropic', diam = .01, p_in = np.array ([x, y, -z]))
#beam = lpy.Beam(name = 'isotropic', x_ap = 1., y_ap = 0.5, p_in = np.array ([0., 0., -z]))
Input parameters:
z = .025 # cm
diam = .01 # cm
beam = lpy.Beam(particle='electron', name = 'isotropic', diam = diam, p_in = np.array ([0., 0., -z]))
Check a few (50) electron tracks into the medium with the geometry you have just constructed
lpy.Plot_beam(medium, geometry, spectrum, beam)
It transports the electron beam (defined by the objects "spectrum" and "beam") through the medium (defined by the objects "medium" and "geometry").
Input
Parameters to be provided:
Return
An object (dubbed result) containing the spatial distribution of deposited energy, the histogram of electron ranges and the angular histogram of backscattered electrons. Once this object is generated, this information can be plotted or stored in files (see examples below).
result = lpy.MC(medium, geometry, spectrum, beam, n_part = 100, n_z=50)
import LegPy as lpy
import numpy as np
import matplotlib.pyplot as plt
medium = lpy.Medium(name='Al')
geometry = lpy.Geometry(name='orthohedron', x =.2, y = 0.2, z=.2, n_x=30, n_y=30, n_z=100)
spectrum = lpy.Spectrum(name='mono', E = 1.)
beam = lpy.Beam(particle='electron', name='parallel', theta=30.0, phi=20.0, diam=.02, p_in=(-0.03, 0., 0.))
lpy.Plot_beam(medium, geometry, spectrum, beam, n_part=50)
#Alternative:
#result = lpy.MC(medium, geometry, spectrum, beam, n_part=50, tracks=True)
First, check the CSDA range for an appropriate choice of the size of the medium and the e_length (e_K) parameters
E = 1.0 #MeV
spectrum = lpy.Spectrum(name='mono', E=E)
medium = lpy.Medium(name='Al') # NIST
e_data = medium.e_data
CSDA = np.interp(E, e_data.E_ref, e_data.R_ref) # cm
CSDAum = CSDA * 1.e4 # um
print('CSDA = ', round(CSDA, 3), 'cm')
#
A step length of CSDA/100 (or e_K = 0.95) should be enough to get accurate results
e_length = CSDAum * 0.01 # um (1/100 of CSDA)
#e_K = 0.95
geometry = lpy.Geometry(name='cylinder', diam=.25, z=.25, n_x=30, n_y=30, n_z=100)
beam = lpy.Beam(particle = 'electron', name='parallel')
n_part = 5000
result = lpy.MC(medium, geometry, spectrum, beam, n_part=n_part, n_z=50, e_length=e_length)
result.plot_hists()
Depending on the practical case, two different definitions of electron range R are used:
Differences between both ranges might be non-negligible in cases with strong backscattering.
Both definitions of range can be computed and stored in a dataframe with three components: x = R(cm), y = number of electrons, z = fraction of electrons.
Check its shape
range_df = result.final_z()
#range_df = result.max_z()
range_df.head()
Plot of R distribution and the corresponding integral function of the electron fraction vs R in either definition. Similar to to the plots obtained above but you can personalized them.
range_df.plot(kind='scatter', x=0, y=1);
range_df.plot(kind='scatter', x=0, y=2);
Calculation of the extrapolated range and other parameters
ext_R, mode, av = lpy.ext_range(range_df)
This funcion is also incorporated in the result object. The range definition should be set to "final" (default) or "max".
ext_R, mode, av = result.ext_range(definition="max")
The angle distribution of backscattered electrons can be computed and stored in a dataframe.
back = result.backscattering()
back
The backscattering coefficiente, b, is the fraction of backscattered electrons.
b = back.sum()[1]/n_part
print('b = ', round(100.*b, 2), '%')
Plot the angular distribution of backscattered electrons
back.plot(kind='scatter', x=0, y=2);
medium = lpy.Medium(name='Al')
geometry = lpy.Geometry(name='cylinder', r =.2, z=.2, n_r=20, n_z=20)
spectrum = lpy.Spectrum(name='mono', E=1.)
beam = lpy.Beam(particle='electron', name='parallel', diam=0.02)
result = lpy.MC(medium, geometry, spectrum, beam, n_part=50000)
result.plot_Edep()
The spatial distribution of energy deposit can be stored in an excel file or a dataframe. Note that results are averaged over axial angle
#result.Edep_to_excel("my_excel")
Edep_df = result.Edep_to_df()
Edep_df
medium = lpy.Medium(name='Al')
geometry = lpy.Geometry(name='cylinder', r=0.2, z=0.2, n_x=40, n_y=40, n_z=20)
spectrum = lpy.Spectrum(name='mono', E=1.)
beam = lpy.Beam(particle='electron', name='parallel', diam=0.02)
result = lpy.MC(medium, geometry, spectrum, beam, n_part=50000)
result.plot_Edep()
The spatial distribution of energy deposit is 3d. This cannot be stored in an excel file or a dataframe, but it is stored in a matrix within the result object generated by MC.
This matrix can also be stored in a binary file with extension .npy for later use (also available for the other geometries).
#result.Edep_to_npy("my_file") # the default name is the name of the medium
Total energy deposited in the medium
Vp = geometry.delta_v # pixel volume (cm^3)
Ed = result.Edep.sum() * Vp
print('total energy deposit =', round(Ed, 3), 'keV')
Personalized plots of the spatial distribution of Edep can be done with this tool:
z_ind = [0, 4, 9, 13, 16] # index of z layers.
prof_lev = [3.5, 4., 5., 6., 7.] # dosis levels (adjust according the color bar)
result.plot_Edep_layers(axis="z", indexes=z_ind, c_profiles=True, lev=prof_lev)
x_ind = [4, 14, 20, 30, 35] # index of z layers.
prof_lev = [3.5, 4., 5., 6., 7.] # dosis levels (adjust according the color bar)
result.plot_Edep_layers(axis="x", indexes=x_ind, c_profiles=True, lev=prof_lev)
Similar to the above case (both cylndrical or cartesian voxelization) but forcing the electron beam to start its path in some point inside the medium
medium = lpy.Medium(name='Al')
geometry = lpy.Geometry(name='cylinder', r=.2, z=.30, n_r=20, n_z=30)
spectrum = lpy.Spectrum(name='mono', E=1.)
beam = lpy.Beam(particle='electron', name='parallel', diam=0.02, p_in=np.array([0., 0., 0.1]))
result = lpy.MC(medium, geometry, spectrum, beam, n_part=50000)
result.plot_Edep()
medium = lpy.Medium(name='Al')
geometry = lpy.Geometry(name='cylinder', r=0.2, z=0.3, n_x=40, n_y=40, n_z=30)
spectrum = lpy.Spectrum(name='mono', E=1.)
beam = lpy.Beam(particle='electron', name='parallel', diam=0.02, p_in=np.array([0., 0., 0.1]))
result = lpy.MC(medium, geometry, spectrum, beam, n_part=50000)
result.plot_Edep()
Personalized plots of the spatial distribution of Edep can be done with this tool:
z_ind = [4, 9, 10, 13, 25] # index of z layers.
prof_lev = [3.5, 4., 5., 6., 7.] # dosis levels (adjust according the color bar)
result.plot_Edep_layers(axis="z", indexes=z_ind, c_profiles=True, lev=prof_lev)
x_ind = [4, 14, 20, 30, 35] # index of z layers.
prof_lev = [3.5, 4., 5., 6., 7.] # dosis levels (adjust according the color bar)
result.plot_Edep_layers(axis="x", indexes=x_ind, c_profiles=True, lev=prof_lev)
The energy deposited at z < z_in is the backscattered energy.
Vp = geometry.delta_v # pixel volume (cm^3)
Edep = result.Edep
Ed = Edep.sum() * Vp #total Edep
Eb = Edep[:, :, 0:10].sum() * Vp #back Edep
Ef = Edep[:, :, 10:30].sum() * Vp #for Edep
print('Fraction of backsacttered energy =', round(100. * Eb / Ed, 2), '%')