Heat

In this example the laser-excitation of a sample Structure is shown. It includes the actual absorption of the laser light as well as the transient temperature profile calculation.

Setup

Do all necessary imports and settings.

[1]:
import udkm1Dsim as ud
u = ud.u #  import the pint unit registry from udkm1Dsim
import scipy.constants as constants
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
u.setup_matplotlib() #  use matplotlib with pint units

Structure

Refere to the structure-example for more details.

[2]:
O = ud.Atom('O')
Ti = ud.Atom('Ti')
Sr = ud.Atom('Sr')
Ru = ud.Atom('Ru')
Pb = ud.Atom('Pb')
Zr = ud.Atom('Zr')
[3]:
# c-axis lattice constants of the two layers
c_STO_sub = 3.905*u.angstrom
c_SRO = 3.94897*u.angstrom
# sound velocities [nm/ps] of the two layers
sv_SRO = 6.312*u.nm/u.ps
sv_STO = 7.800*u.nm/u.ps

# SRO layer
prop_SRO = {}
prop_SRO['a_axis'] = c_STO_sub # aAxis
prop_SRO['b_axis'] = c_STO_sub # bAxis
prop_SRO['deb_Wal_Fac'] = 0 # Debye-Waller factor
prop_SRO['sound_vel'] = sv_SRO # sound velocity
prop_SRO['opt_ref_index'] = 2.44+4.32j
prop_SRO['therm_cond'] = 5.72*u.W/(u.m *u.K) # heat conductivity
prop_SRO['lin_therm_exp'] = 1.03e-5 # linear thermal expansion
prop_SRO['heat_capacity'] = 'lambda T: 455.2 + 0.112*T - 2.1935e6/T**2' # heat capacity [J/kg K]

SRO = ud.UnitCell('SRO', 'Strontium Ruthenate', c_SRO, **prop_SRO)
SRO.add_atom(O, 0)
SRO.add_atom(Sr, 0)
SRO.add_atom(O, 0.5)
SRO.add_atom(O, 0.5)
SRO.add_atom(Ru, 0.5)

# STO substrate
prop_STO_sub = {}
prop_STO_sub['a_axis'] = c_STO_sub # aAxis
prop_STO_sub['b_axis'] = c_STO_sub # bAxis
prop_STO_sub['deb_Wal_Fac'] = 0 # Debye-Waller factor
prop_STO_sub['sound_vel'] = sv_STO # sound velocity
prop_STO_sub['opt_ref_index'] = 2.1+0j
prop_STO_sub['therm_cond'] = 12*u.W/(u.m *u.K) # heat conductivity
prop_STO_sub['lin_therm_exp'] = 1e-5 # linear thermal expansion
prop_STO_sub['heat_capacity'] = 'lambda T: 733.73 + 0.0248*T - 6.531e6/T**2' # heat capacity [J/kg K]

STO_sub = ud.UnitCell('STOsub', 'Strontium Titanate Substrate', c_STO_sub, **prop_STO_sub)
STO_sub.add_atom(O, 0)
STO_sub.add_atom(Sr, 0)
STO_sub.add_atom(O, 0.5)
STO_sub.add_atom(O, 0.5)
STO_sub.add_atom(Ti, 0.5)
[4]:
S = ud.Structure('Single Layer')
S.add_sub_structure(SRO, 100) # add 100 layers of SRO to sample
S.add_sub_structure(STO_sub, 200) # add 200 layers of STO substrate

Initialize Heat

The Heat class requires a Structure object and a boolean force_recalc in order overwrite previous simulation results.

These results are saved in the cache_dir when save_data is enabled. Printing simulation messages can be en-/disabled using disp_messages and progress bars can using the boolean switch progress_bar.

[5]:
h = ud.Heat(S, True)

h.save_data = False
h.disp_messages = True

print(h)
Heat simulation properties:

This is the current structure for the simulations:

Structure properties:

Name   : Single Layer
Thickness : 117.59 nanometer
Roughness : 0.00 nanometer
----
100 times Strontium Ruthenate: 39.49 nanometer
200 times Strontium Titanate Substrate: 78.10 nanometer
----
no substrate


Display properties:

================================  =======================================================
                       parameter  value
================================  =======================================================
                    force recalc  True
                 cache directory  ./
                display messages  True
                       save data  False
                    progress bar  True
              excitation fluence  [] mJ/cm²
                excitation delay  [0.0] ps
         excitation pulse length  [0.0] ps
           excitation wavelength  799.9999999999999 nm
                excitation theta  90.0 deg
excitation multilayer absorption  True
                  heat diffusion  False
       interpolate at interfaces  11
                         backend  scipy
                       distances  no distance mesh is set for heat diffusion calculations
               top boundary type  isolator
            bottom boundary type  isolator
================================  =======================================================

Simple Excitation

In order to calculate the temperature of the sample after quasi-instantanious (delta) photoexcitation the excitation must be set with the following parameters: * fluence * delay_pump * pulse_width * multilayer_absorption * wavelength * theta

The wavelength and theta angle of the excitation are only relevant if multilayer_absorption = True. Otherwise the Lambert_Beer-law is used.
Note: the fluence, delay_pump, and pulse_width must be given as array or list.

The simulation requires also a delay array as temporal grid as well as an initial temperature init_temp. The later can be either a scalar which is then the constant temperature of the whole sample structure, or the initial temperature can be an array of temperatures for each single layer in the structure.

[6]:
h.excitation = {'fluence': [5]*u.mJ/u.cm**2,
                'delay_pump':  [0]*u.ps,
                'pulse_width':  [0]*u.ps,
                'multilayer_absorption': True,
                'wavelength': 800*u.nm,
                'theta': 45*u.deg}

# when calculating the laser absorption profile using Lamber-Beer-law
# the opt_pen_depth must be set manually or calculated from the refractive index
SRO.set_opt_pen_depth_from_ref_index(800*u.nm)
STO_sub.set_opt_pen_depth_from_ref_index(800*u.nm)

# temporal and spatial grid
delays = np.r_[-10:200:0.1]*u.ps
_, _, distances = S.get_distances_of_layers()
c:\users\schick\general\python\wpy64-3770\python-3.7.7.amd64\lib\site-packages\pint\quantity.py:1138: RuntimeWarning: divide by zero encountered in double_scalars
  magnitude = magnitude_op(self._magnitude, other_magnitude)

Laser Absorption Profile

Here the difference in the spatial laser absorption profile is shown between the multilayer absorption algorithm and the Lamber-Beer law.
Note that Lambert-Beer does not include reflection of the incident light from the surface of the sample structure:
[7]:
plt.figure()
dAdz, _, _, _ = h.get_multilayers_absorption_profile()
plt.plot(distances.to('nm'), dAdz, label='multilayer')
dAdz = h.get_Lambert_Beer_absorption_profile()
plt.plot(distances.to('nm'), dAdz, label='Lamber-Beer')
plt.legend()
plt.xlabel('Distance [nm]')
plt.ylabel('Differnetial Absorption')
plt.title('Laser Absorption Profile')
plt.show()
../_images/examples_heat_12_0.png

Temperature Map

[8]:
temp_map, delta_temp = h.get_temp_map(delays, 300*u.K)
Elapsed time for _temperature_after_delta_excitation_: 0.015956 s
Elapsed time for _temp_map_: 0.087948 s
[9]:
plt.figure(figsize=[6, 8])
plt.subplot(2, 1, 1)
plt.plot(distances.to('nm').magnitude, temp_map[101, :])
plt.xlim([0, distances.to('nm').magnitude[-1]])
plt.xlabel('Distance [nm]')
plt.ylabel('Temperature [K]')
plt.title('Temperature Profile')

plt.subplot(2, 1, 2)
plt.pcolormesh(distances.to('nm').magnitude, delays.to('ps').magnitude, temp_map)
plt.colorbar()
plt.xlabel('Distance [nm]')
plt.ylabel('Delay [ps]')
plt.title('Temperature Map')

plt.tight_layout()
plt.show()
../_images/examples_heat_15_0.png

Heat Diffusion

In order to enable heat diffusion the boolean switch heat_diffusion must be True.

[10]:
# enable heat diffusion
h.heat_diffusion = True
# set the boundary conditions
h.boundary_conditions = {'top_type': 'isolator', 'bottom_type': 'isolator'}
# The resulting temperature profile is calculated in one line:
temp_map, delta_temp = h.get_temp_map(delays, 300*u.K)
Elapsed time for _temperature_after_delta_excitation_: 0.014962 s
Calculating _heat_diffusion_ ...

Elapsed time for _heat_diffusion_: 6.501284 s
Elapsed time for _temp_map_: 6.611260 s
[11]:
plt.figure(figsize=[6, 8])
plt.subplot(2, 1, 1)
plt.plot(distances.to('nm').magnitude, temp_map[101, :], label=np.round(delays[101]))
plt.plot(distances.to('nm').magnitude, temp_map[501, :], label=np.round(delays[501]))
plt.plot(distances.to('nm').magnitude, temp_map[-1, :], label=np.round(delays[-1]))
plt.xlim([0, distances.to('nm').magnitude[-1]])
plt.xlabel('Distance [nm]')
plt.ylabel('Temperature [K]')
plt.legend()
plt.title('Temperature Profile with Heat Diffusion')

plt.subplot(2, 1, 2)
plt.pcolormesh(distances.to('nm').magnitude, delays.to('ps').magnitude, temp_map)
plt.colorbar()
plt.xlabel('Distance [nm]')
plt.ylabel('Delay [ps]')
plt.title('Temperature Map with Heat Diffusion')

plt.tight_layout()
plt.show()
../_images/examples_heat_18_0.png

Heat Diffusion Paramters

For heat diffusion simulations various parameters for the underlying pdepe solver can be altered.

By default, the backend is set to scipy but can be switched to matlab. Currently, the is no obvious reason to choose MATLAB above SciPy.

Depending on the backend either the ode_options or ode_options_matlab can be configured and are directly handed to the actual solver. Please refere to the documentation of the actual backend and solver and the API documentation for more details.

The speed but also the result of the heat diffusion simulation strongly depends on the spatial grid handed to the solver. By default, one spatial grid point is used for every Layer (AmorphousLayer or UnitCell) in the Structure. The resulting temp_map will also always be interpolated in this spatial grid which is equivalent to the distance vector returned by S.get_distances_of_layers().

As the solver for the heat diffusion usually suffers from large gradients, e.g. of thermal properties or initial temperatures, additional spatial grid points are added by default only for internal calculations. The number of additional points (should be an odd number, default is 11) is set by:

[12]:
h.intp_at_interface = 11

The internally used spatial grid can be returned by:

[13]:
dist_interp, original_indicies = S.interp_distance_at_interfaces(h.intp_at_interface)

The internal spatial grid can also be given by hand, e.g. to realize logarithmic steps for rather large Structure:

[14]:
h.distances = np.linspace(0, distances.magnitude[-1], 100)*u.m

As already shown above, the heat diffusion simulation supports also an top and bottom boundary condition. The can have the types: * isolator * temperature * flux

For the later types also a value must be provides:

[15]:
h.boundary_conditions = {'top_type': 'temperature', 'top_value': 500*u.K,
                         'bottom_type': 'flux', 'bottom_value': 5e11*u.W/u.m**2}

print(h)
Heat simulation properties:

This is the current structure for the simulations:

Structure properties:

Name   : Single Layer
Thickness : 117.59 nanometer
Roughness : 0.00 nanometer
----
100 times Strontium Ruthenate: 39.49 nanometer
200 times Strontium Titanate Substrate: 78.10 nanometer
----
no substrate


Display properties:

================================  =======================================================
                       parameter  value
================================  =======================================================
                    force recalc  True
                 cache directory  ./
                display messages  True
                       save data  False
                    progress bar  True
              excitation fluence  [5.0] mJ/cm²
                excitation delay  [0.0] ps
         excitation pulse length  [0.0] ps
           excitation wavelength  800.0 nm
                excitation theta  45.0 deg
excitation multilayer absorption  True
                  heat diffusion  True
       interpolate at interfaces  11
                         backend  scipy
                       distances  a distance mesh is set for heat diffusion calculations.
               top boundary type  temperature
        top boundary temperature  500 K
            bottom boundary type  flux
            bottom boundary flux  500000000000.0 W/m²
================================  =======================================================
[16]:
# The resulting temperature profile is calculated in one line:
temp_map, delta_temp = h.get_temp_map(delays, 300*u.K)
Calculating _heat_diffusion_ ...

Elapsed time for _heat_diffusion_: 0.791059 s
Elapsed time for _temperature_after_delta_excitation_: 0.006981 s
Calculating _heat_diffusion_ ...

Elapsed time for _heat_diffusion_: 0.979264 s
Elapsed time for _temp_map_: 1.830167 s
[17]:
plt.figure(figsize=[6, 8])
plt.subplot(2, 1, 1)
plt.plot(distances.to('nm').magnitude, temp_map[101, :], label=np.round(delays[101]))
plt.plot(distances.to('nm').magnitude, temp_map[501, :], label=np.round(delays[501]))
plt.plot(distances.to('nm').magnitude, temp_map[-1, :], label=np.round(delays[-1]))
plt.xlim([0, distances.to('nm').magnitude[-1]])
plt.xlabel('Distance [nm]')
plt.ylabel('Temperature [K]')
plt.legend()
plt.title('Temperature Profile with Heat Diffusion and BC')

plt.subplot(2, 1, 2)
plt.pcolormesh(distances.to('nm').magnitude, delays.to('ps').magnitude, temp_map)
plt.colorbar()
plt.xlabel('Distance [nm]')
plt.ylabel('Delay [ps]')
plt.title('Temperature Map with Heat Diffusion and BC')

plt.tight_layout()
plt.show()
../_images/examples_heat_28_0.png

Multipulse Excitation

As already stated above, also multiple pulses of variable fluence, pulse width and, delay are possible.

The heat diffusion simulation automatically splits the calculation in parts with and without excitation and adjusts the initial temporal step width according to the pulse width. Hence the solver does not miss any excitation pulses when adjusting its temporal step size.

The temporal laser pulse profile is always assumed to be gaussian and the pulse width must be given as FWHM:

[18]:
h.excitation = {'fluence': [5, 5, 5]*u.mJ/u.cm**2,
                'delay_pump':  [0, 10, 20]*u.ps,
                'pulse_width':  [0.1, 0.1, 0.1]*u.ps,
                'multilayer_absorption': True,
                'wavelength': 800*u.nm,
                'theta': 45*u.deg}

h.boundary_conditions = {'top_type': 'isolator', 'bottom_type': 'isolator'}
[19]:
# The resulting temperature profile is calculated in one line:
temp_map, delta_temp = h.get_temp_map(delays, 300*u.K)
Calculating _heat_diffusion_ for excitation 1:1 ...

Elapsed time for _heat_diffusion_ with 1 excitation(s): 0.848871 s
Calculating _heat_diffusion_ ...

Elapsed time for _heat_diffusion_: 0.648266 s
Calculating _heat_diffusion_ for excitation 2:2 ...

Elapsed time for _heat_diffusion_ with 1 excitation(s): 0.613387 s
Calculating _heat_diffusion_ ...

Elapsed time for _heat_diffusion_: 0.519606 s
Calculating _heat_diffusion_ for excitation 3:3 ...

Elapsed time for _heat_diffusion_ with 1 excitation(s): 0.543585 s
Calculating _heat_diffusion_ ...

Elapsed time for _heat_diffusion_: 0.692392 s
Elapsed time for _temp_map_: 4.025329 s
[20]:
plt.figure(figsize=[6, 8])
plt.subplot(2, 1, 1)
plt.plot(distances.to('nm').magnitude, temp_map[101, :], label=np.round(delays[101]))
plt.plot(distances.to('nm').magnitude, temp_map[201, :], label=np.round(delays[201]))
plt.plot(distances.to('nm').magnitude, temp_map[301, :], label=np.round(delays[301]))
plt.plot(distances.to('nm').magnitude, temp_map[-1, :], label=np.round(delays[-1]))
plt.xlim([0, distances.to('nm').magnitude[-1]])
plt.xlabel('Distance [nm]')
plt.ylabel('Temperature [K]')
plt.legend()
plt.title('Temperature Profile Multiplulse')

plt.subplot(2, 1, 2)
plt.pcolormesh(distances.to('nm').magnitude, delays.to('ps').magnitude, temp_map)
plt.colorbar()
plt.xlabel('Distance [nm]')
plt.ylabel('Delay [ps]')
plt.title('Temperature Map Multiplulse')

plt.tight_layout()
plt.show()
../_images/examples_heat_32_0.png

N-Temperature Model

The heat diffusion is also capable of simulating an N-temperature model which is often applied to empirically simulate the energy flow between electrons, phonons, and spins. In order to run the NTM all thermo-elastic properties must be given as a list of N elements. In addition the sub_system_coupling must be provided in order to allow for energy-flow between the sub-systems. The actual external laser-excitation is always set to happen within the first sub-system, which is usually the electron-system.

[21]:
# update the relevant thermo-elastic properties of the layers in the sample structure
SRO.therm_cond = [0,
                  5.72*u.W/(u.m *u.K)]
SRO.lin_therm_exp = [1.03e-5,
                     1.03e-5]
SRO.heat_capacity = ['lambda T: 0.112*T',
                     'lambda T: 455.2 - 2.1935e6/T**2']
SRO.sub_system_coupling = ['lambda T: 5e17*(T[1]-T[0])',
                           'lambda T: 5e17*(T[0]-T[1])']

STO_sub.therm_cond = [0,
                      12*u.W/(u.m *u.K)]
STO_sub.lin_therm_exp = [1e-5,
                         1e-5]
STO_sub.heat_capacity = ['lambda T: 0.0248*T',
                         'lambda T: 733.73 - 6.531e6/T**2']
STO_sub.sub_system_coupling = ['lambda T: 5e17*(T[1]-T[0])',
                               'lambda T: 5e17*(T[0]-T[1])']

As no new Structure is build, the num_sub_systems must be updated by hand. Otherwise this happens automatically.

[22]:
S.num_sub_systems = 2

Set the exciation conditions:

[23]:
h.excitation = {'fluence': [5]*u.mJ/u.cm**2,
                'delay_pump':  [0]*u.ps,
                'pulse_width':  [0.25]*u.ps,
                'multilayer_absorption': True,
                'wavelength': 800*u.nm,
                'theta': 45*u.deg}

h.boundary_conditions = {'top_type': 'isolator', 'bottom_type': 'isolator'}

delays = np.r_[-5:15:0.01]*u.ps
[24]:
# The resulting temperature profile is calculated in one line:
temp_map, delta_temp = h.get_temp_map(delays, 300*u.K)
Calculating _heat_diffusion_ for excitation 1:1 ...

Elapsed time for _heat_diffusion_ with 1 excitation(s): 2.053165 s
Calculating _heat_diffusion_ ...

Elapsed time for _heat_diffusion_: 2.248610 s
Elapsed time for _temp_map_: 4.445906 s
[25]:
plt.figure(figsize=[6, 8])
plt.subplot(2, 1, 1)
plt.pcolormesh(distances.to('nm').magnitude, delays.to('ps').magnitude, temp_map[:, :, 0])
plt.colorbar()
plt.xlabel('Distance [nm]')
plt.ylabel('Delay [ps]')
plt.title('Temperature Map Electrons')

plt.subplot(2, 1, 2)
plt.pcolormesh(distances.to('nm').magnitude, delays.to('ps').magnitude, temp_map[:, :, 1])
plt.colorbar()
plt.xlabel('Distance [nm]')
plt.ylabel('Delay [ps]')
plt.title('Temperature Map Phonons')

plt.tight_layout()
plt.show()
../_images/examples_heat_40_0.png
[26]:
plt.figure()
plt.plot(delays.to('ps'), np.mean(temp_map[:, S.get_all_positions_per_unique_layer()['SRO'], 0], 1), label='SRO electrons')
plt.plot(delays.to('ps'), np.mean(temp_map[:, S.get_all_positions_per_unique_layer()['SRO'], 1], 1), label='SRO phonons')
plt.ylabel('Temperature [K]')
plt.xlabel('Delay [ps]')
plt.legend()
plt.title('Temperature Electrons vs. Phonons')
plt.show()
../_images/examples_heat_41_0.png