import numpy as np
from scipy.interpolate import UnivariateSpline
[docs]class Therml:
"""Collects methods for the computation of thermal radiative figures of merit
Attributes
----------
self.temperture : float
temperature of the structure in Kelvin
self.atmospheric_temperature : float
temperature of the atmosphere in Kelvin
self.lambda_bandgap : float
bandgap wavelength of the target PV for PV or STPV in meters
self.solar_angle : float
angle of the incident solar spectrum in radians
self.blackbody_spectrum : numpy array of floats
Planck's blackbody spectrum for a given temperature
self.thermal_emission_array : numpy array of floats
thermal emission spectrum of structure for a given temperature
self.blackbody_power_density : float
total power density radiated into a hemisphere of a blackbody at a given temperature
self.power_density : float
total power density radiated by structure at a given temperature
self.power_density_gradient : numpy array of floats (will be computed by this function)
the gradient vector related to the total power density wrt changes in thicknesses of each layer
self.photopic_luminosity_array : numpy array of floats
the array of photopic luminosity values corresponding to each value of wavelength_array
self.stpv_power_density : float (will be computed by this function)
useful (sub-bandgap) power density radiated into a hemisphere
self.stpv_power_density_gradient : numpy array of floats (will be computed by this function)
the gradient vector related to the stpv power density wrt changes in thicknesses of each layer
self.stpv_spectral_efficiency : float (will be computed by this function)
spectral efficiency of an stpv emitter
self.stpv_spectral_efficiency_gradient : numpy array of floats (will be computed by this function)
the gradient vector related to the spectral efficiency wrt changes in thicknesses of each layer
Returns
-------
None
"""
[docs] def __init__(self, args):
"""constructor for the Therml class"""
# parse args
self._parse_therml_input(args)
# self._compute_therml_spectrum()
# self._compute_power_density()
def _parse_therml_input(self, args):
"""method to parse the user inputs and define structures / simulation
Returns
-------
None
"""
if "temperature" in args:
# user input expected in kelvin
self.temperature = args["temperature"]
else:
# default temperature is 300 K
self.temperature = 300
if "bandgap wavelength" in args:
self.lambda_bandgap = args["bandgap wavelength"]
else:
# default is ~InGaAsSb bandgap
self.lambda_bandgap = 2254e-9
if "atmospheric temperature" in args:
self.atmospheric_temperature = args["atmospheric temperature"]
else:
self.atmospheric_temperature = 300
if "solar angle" in args:
self.solar_angle = args["solar angle"]
self.solar_angle *= np.pi / 180
else:
self.solar_angle = 30 * np.pi / 180
def _compute_therml_spectrum(self, wavelength_array, emissivity_array):
"""method to compute thermal emission spectrum of a structure
Arguments
---------
wavelength_array : numpy array of floats
the array of wavelengths across which thermal emission spectrum will be computed
emissivity_array : numpy array of floats
the array of emissivity spectrum for the structure that you will compute the thermal emission of
Attributes
----------
blackbody_spectrum : numpy array of floats
Planck's blackbody spectrum for a given temperature
thermal_emission_array : numpy array of floats
thermal emission spectrum of structure for a given temperature
References
----------
blackbody spectrum : Eq. (13) of https://github.com/FoleyLab/wptherml/blob/master/docs/Equations.pdf
thermal_emission_array : Eq (12) of https://github.com/FoleyLab/wptherml/blob/master/docs/Equations.pdf
with $\theta=0$
"""
# speed of light in SI
c = 299792458
# plancks constant in SI
h = 6.62607004e-34
# boltzmanns constant in SI
kb = 1.38064852e-23
self.blackbody_spectrum = 2 * h * c ** 2 / wavelength_array ** 5
self.blackbody_spectrum /= (
np.exp(h * c / (wavelength_array * kb * self.temperature)) - 1
)
self.thermal_emission_array = self.blackbody_spectrum * emissivity_array
def _compute_therml_spectrum_gradient(
self, wavelength_array, emissivity_gradient_array
):
"""method to compute thermal emission spectrum of a structure
Arguments
---------
wavelength_array : numpy array of floats
the array of wavelengths across which thermal emission spectrum will be computed
emissivity_array : numpy array of floats
the array of emissivity spectrum for the structure that you will compute the thermal emission of
Attributes
----------
blackbody_spectrum : numpy array of floats
Planck's blackbody spectrum for a given temperature
thermal_emission_array : numpy array of floats
thermal emission spectrum of structure for a given temperature
References
----------
blackbody spectrum : Eq. (13) of https://github.com/FoleyLab/wptherml/blob/master/docs/Equations.pdf
thermal_emission_array : Eq (12) of https://github.com/FoleyLab/wptherml/blob/master/docs/Equations.pdf
with $\theta=0$
"""
# speed of light in SI
c = 299792458
# plancks constant in SI
h = 6.62607004e-34
# boltzmanns constant in SI
kb = 1.38064852e-23
# get the dimension of the gradient vector
_ngr = len(emissivity_gradient_array[0, :])
# get the number of wavelengths
_nwl = len(wavelength_array)
# initialize the gradient array
self.thermal_emission_gradient_array = np.zeros((_nwl, _ngr))
self.blackbody_spectrum = 2 * h * c ** 2 / wavelength_array ** 5
self.blackbody_spectrum /= (
np.exp(h * c / (wavelength_array * kb * self.temperature)) - 1
)
for i in range(0, _ngr):
self.thermal_emission_gradient_array[:, i] = (
self.blackbody_spectrum * emissivity_gradient_array[:, i]
)
def _compute_power_density(self, wavelength_array):
"""method to compute the power density from blackbody spectrum and thermal emission spectrum
Attributes
----------
self.blackbody_power_density : float
total power density radiated into a hemisphere of a blackbody at a given temperature
self.power_density : float
total power density radiated by structure at a given temperature
References
----------
Equation (15) and (16) of https://github.com/FoleyLab/wptherml/blob/master/docs/Equations.pdf
"""
# integrate blackbody spectrum over wavelength using np.trapz
self.blackbody_power_density = np.trapz(
self.blackbody_spectrum, wavelength_array
)
# integrate the thermal emission spectrum over wavelength using np.trapz
self.power_density = np.trapz(self.thermal_emission_array, wavelength_array)
# account for angular integrals over hemisphere (assuming no angle dependence of emissivity)
self.blackbody_power_density *= np.pi
self.power_density *= np.pi
# compute Blackbody power density from Stefan-Boltzmann law for validation
# Stefan-Boltzmann constant
sig = 5.670374419e-8
self.stefan_boltzmann_law = sig * self.temperature ** 4
def _compute_power_density_gradient(self, wavelength_array):
"""method to compute the gradient of the power density of a thermal emitter
Arguments
---------
wavelength_array : numpy array of floats
the wavelengths over which the power density spectrum has been computed
Attributes
----------
self.power_density_gradient : numpy array of floats (will be computed by this function)
the gradient vector related to the total power density wrt changes in thicknesses of each layer
References
----------
Equation (5) of https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.2.013018
"""
_ngr = len(self.thermal_emission_gradient_array[0, :])
# integrate the thermal emission spectrum over wavelength using np.trapz
self.power_density_gradient = np.zeros(_ngr)
for i in range(0, _ngr):
self.power_density_gradient[i] = np.pi * np.trapz(
self.thermal_emission_gradient_array[:, i], wavelength_array
)
def _compute_photopic_luminosity(self, wavelength_array):
"""computes the photopic luminosity function from a Gaussian fit
Arguments
---------
wavelength_array : numpy array of floats
the array of wavelengths over which you will compute the photopic luminosity
Attributes
----------
self.photopic_luminosity_array : numpy array of floats
the array of photopic luminosity values corresponding to each value of wavelength_array
References
----------
Data taken from http://www.cvrl.org/database/data/lum/linCIE2008v2e_5.htm
"""
a = 1.02433
b = 2.59462e14
c = 5.60186e-07
self._photopic_luminosity_array = a * np.exp(-b * (wavelength_array - c) ** 2)
def _compute_stpv_power_density(self, wavelength_array):
"""method to compute the stpv power density from the thermal emission spectrum of a structure
Arguments
---------
wavelength_array : numpy array of floats
the wavelengths over which the thermal emission spectrum is known
Attributes
----------
thermal_emission_array : numpy array of floats (already assigned)
the thermal emission spectrum for each value of wavelength array for the stpv structure
lambda_bandgap : float (already assigned)
the bandgap wavelength -> upper limit on the integral for the stpv_power_density
stpv_power_density : float (will be computed by this function)
useful (sub-bandgap) power density radiated into a hemisphere
References
----------
Equation (17) of https://github.com/FoleyLab/wptherml/blob/master/docs/Equations.pdf
"""
# compute the useful power density spectrum
power_density_array = (
self.thermal_emission_array * wavelength_array
) / self.lambda_bandgap
# determine the index corresponding to lambda_bandgap in the wavelength_array
# which will be used to determine the appropriate slice to feed to np.trapz
bg_idx = np.abs(wavelength_array - self.lambda_bandgap).argmin()
# integrate the power density between 0 to lambda_bandgap
# by feeding the slice of the power_density_array and wavelength_array
# from 0:bg_idx to the trapz function
self.stpv_power_density = np.pi * np.trapz(
power_density_array[:bg_idx], wavelength_array[:bg_idx]
)
def _compute_stpv_power_density_gradient(self, wavelength_array):
"""method to compute the power density from blackbody spectrum and thermal emission spectrum
Arguments
---------
wavelength_array : numpy array of floats
the wavelengths over which the power density spectrum has been computed
Attributes
----------
self.stpv_power_density_gradient : numpy array of floats (will be computed by this function)
the gradient vector related to the stpv power density wrt changes in thicknesses of each layer
References
----------
Equation (5) of https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.2.013018
"""
_ngr = len(self.thermal_emission_gradient_array[0, :])
# integrate the thermal emission spectrum over wavelength using np.trapz
self.stpv_power_density_gradient = np.zeros(_ngr)
# compute the useful power density spectrum
for i in range(0, _ngr):
stpv_power_density_array_prime = (
self.thermal_emission_gradient_array[:, i]
* wavelength_array
/ self.lambda_bandgap
)
self.stpv_power_density_gradient[i] = np.pi * np.trapz(
stpv_power_density_array_prime, wavelength_array
)
def _compute_stpv_spectral_efficiency(self, wavelength_array):
"""method to compute the stpv spectral efficiency from the thermal emission spectrum of a structure
Arguments
---------
wavelength_array : numpy array of floats
the wavelengths over which the thermal emission spectrum is known
Attributes
----------
thermal_emission_array : numpy array of floats (already assigned)
the thermal emission spectrum for each value of wavelength array for the stpv structure
lambda_bandgap : float (already assigned)
the bandgap wavelength -> upper limit on the integral for the stpv_power_density
stpv_power_density : float (will be computed by this function)
useful (sub-bandgap) power density radiated into a hemisphere
power_density : float (will be computed by this function)
total power density radiated into a hemisphere
stpv_spectral_efficiency : float (will be computed by this function)
spectral efficiency of an stpv emitter
References
----------
Equation (18) of https://github.com/FoleyLab/wptherml/blob/master/docs/Equations.pdf
"""
self._compute_stpv_power_density(wavelength_array)
self._compute_power_density(wavelength_array)
self.stpv_spectral_efficiency = self.stpv_power_density / self.power_density
def _compute_stpv_spectral_efficiency_gradient(self, wavelength_array):
"""method to compute the gradient of the
stpv spectral efficiency from the thermal emission spectrum of a structure
Arguments
---------
wavelength_array : numpy array of floats
the wavelengths over which the thermal emission spectrum is known
Attributes
----------
self.stpv_spectral_efficiency_gradient : numpy array of floats (will be computed by this function)
the gradient vector related to the spectral efficiency wrt changes in thicknesses of each layer
References
----------
Equation (4) of https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.2.013018
"""
# get the number of elements in the gradient
_ngr = len(self.thermal_emission_gradient_array[0, :])
# initialize the gradient array
self.stpv_spectral_efficiency_gradient = np.zeros(_ngr)
# using the notation from Eq. (4)
# from https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.2.013018
self._compute_stpv_power_density(wavelength_array)
self._compute_power_density(wavelength_array)
_P = self.power_density
_rho = self.stpv_power_density
# determine the index corresponding to lambda_bandgap in the wavelength_array
# which will be used to determine the appropriate slice to feed to np.trapz
_bg_idx = np.abs(wavelength_array - self.lambda_bandgap).argmin()
for i in range(0, _ngr):
_rho_prime_integrand = (
self.thermal_emission_gradient_array[:_bg_idx, i]
* wavelength_array[:_bg_idx]
/ self.lambda_bandgap
)
_rho_prime = np.pi * np.trapz(
_rho_prime_integrand, wavelength_array[:_bg_idx]
)
_P_prime = np.pi * np.trapz(
self.thermal_emission_gradient_array[:, i], wavelength_array
)
self.stpv_spectral_efficiency_gradient[i] = (
_rho_prime * _P - _P_prime * _rho
) / (_P * _P)
def _compute_luminous_efficiency(self, wavelength_array):
"""method to compute the luminous efficiency for an incandescent from the thermal emission spectrum of a structure
Arguments
---------
wavelength_array : numpy array of floats
the wavelengths over which the thermal emission spectrum is known
Attributes
----------
self.thermal_emission_array : numpy array of floats (already assigned)
the thermal emission spectrum for each value of wavelength array for the stpv structure
self.photopic_luminosity_array : numpy array of floats (can be computed by calling self._compute_photopic_luminosity(wavelength_array))
photopic luminosity function values corresponding to wavelength_array
self.luminous_efficiency : float (will be computed by this function)
the spectral efficiency of the incandescent source
References
----------
Equation (27) of https://github.com/FoleyLab/wptherml/blob/master/docs/Equations.pdf
"""
# self._compute_therml_spectrum(wavelength_array, emissivity_array)
self._compute_photopic_luminosity(wavelength_array)
vl = self._photopic_luminosity_array
TE = self.thermal_emission_array
Numerator = np.trapz(vl * TE, wavelength_array)
Denominator = np.trapz(TE, wavelength_array)
self.luminous_efficiency = Numerator / Denominator
def _compute_thermal_radiated_power(
self,
emissivity_array_s,
emissivity_array_p,
theta_vals,
theta_weights,
wavelength_array,
):
"""Method to compute the power radiated by the cooling structure
Attributes
----------
None
Returns
-------
P_rad
References
----------
See Eq. (2) of https://www.nature.com/articles/nature13883
"""
num_angles = len(theta_vals)
self._compute_therml_spectrum(wavelength_array, emissivity_array_s[0,:])
# loop over angles
P_rad = 0.0
for i in range(0, num_angles):
_TE = (
self.blackbody_spectrum
* np.cos(theta_vals[i])
* 0.5
* (emissivity_array_p[i, :] + emissivity_array_s[i, :])
)
_TE_INT = np.trapz(_TE, wavelength_array)
P_rad += _TE_INT * np.sin(theta_vals[i]) * theta_weights[i]
P_rad *= np.pi * 2
return P_rad
def _compute_thermal_radiated_power_gradient(
self,
emissivity_gradient_array_s,
emissivity_gradient_array_p,
theta_vals,
theta_weights,
wavelength_array,
):
"""Method to compute the gradient of the power radiated by the cooling structure
Attributes
----------
None
Returns
-------
_emitted_thermal_spectrum_gradient
References
----------
See Eq. (2) of https://www.nature.com/articles/nature13883
"""
num_angles = len(theta_vals)
# we don't care about the emissivity - just calling this for the blackbody spectrum
self._compute_therml_spectrum(wavelength_array, emissivity_gradient_array_p[0,:,0])
_ngr = len(emissivity_gradient_array_s[0,0,:])
_nth = len(emissivity_gradient_array_s[:,0,0])
# instantiate P_rad_prime
_emitted_thermal_spectrum_gradient = np.zeros(_ngr)
for i in range(0, _ngr):
_P_rad_prime = 0
for j in range(0, _nth):
_TE = (
self.blackbody_spectrum
* np.cos(theta_vals[j])
* 0.5
* (emissivity_gradient_array_p[j, :, i] + emissivity_gradient_array_s[j, :, i])
)
_TE_INT = np.trapz(_TE, wavelength_array)
_P_rad_prime += 2 * np.pi * _TE_INT * np.sin(theta_vals[j]) * theta_weights[j]
_emitted_thermal_spectrum_gradient[i] = _P_rad_prime
return _emitted_thermal_spectrum_gradient
def _compute_atmospheric_radiated_power(
self,
atmospheric_transmissivity,
emissivity_array_s,
emissivity_array_p,
theta_vals,
theta_weights,
wavelength_array,
):
"""Method to compute the power radiated by the atmosphere and absorbed by cooling structure
Attributes
----------
None
Returns
-------
P_atm
References
----------
See Eq. (3) of https://www.nature.com/articles/nature13883
"""
num_angles = len(theta_vals)
# make sure we are getting the blackbody spectrum of the atmosphere
# store the structure temperature
_T_temp = self.temperature
# update the structure temperature, which is the attribute that
# is used for computing the blackbody spectrum in _compute_therml_spectrum
self.temperature = self.atmospheric_temperature
self._compute_therml_spectrum(wavelength_array, emissivity_array_s[0,:])
# set the structure temperature back to _T_temp in case
# one wants to compute the thermal emission of the structure again!
self.temperature = _T_temp
P_atm = 0.0
for i in range(0, num_angles):
# get the term that goes in the exponent of the atmospheric transmissivity
_o_over_cos_t = 1 / np.cos(theta_vals[i])
_emissivity_atm = (
np.ones(len(atmospheric_transmissivity))
- atmospheric_transmissivity ** _o_over_cos_t
)
_TE_atm = self.blackbody_spectrum * _emissivity_atm * np.cos(theta_vals[i])
_absorbed_TE_spectrum = (
_TE_atm * 0.5 * (emissivity_array_p[i, :] + emissivity_array_s[i, :])
)
_absorbed_TE = np.trapz(_absorbed_TE_spectrum, wavelength_array)
P_atm += _absorbed_TE * np.sin(theta_vals[i]) * theta_weights[i]
P_atm *= 2 * np.pi
return P_atm
def _compute_atmospheric_radiated_power_gradient(
self,
atmospheric_transmissivity,
emissivity_gradient_array_s,
emissivity_gradient_array_p,
theta_vals,
theta_weights,
wavelength_array,
):
""" Method to compute the gradient of the power radiated by the atmosphere and absorbed by the cooling structure
Attribute
---------
None
Returns
-------
_absorbed_solar_spectrum_gradient
"""
_nth = len(theta_vals)
_ngr = len(emissivity_gradient_array_p[0,0,:])
_absorbed_atmospheric_radiation_gradient = np.zeros(_ngr)
# make sure we are getting the blackbody spectrum of the atmosphere
# store the structure temperature
_T_temp = self.temperature
# update the structure temperature, which is the attribute that
# is used for computing the blackbody spectrum in _compute_therml_spectrum
self.temperature = self.atmospheric_temperature
self._compute_therml_spectrum(wavelength_array, emissivity_gradient_array_p[0,:,0])
# set the structure temperature back to _T_temp in case
# one wants to compute the thermal emission of the structure again!
self.temperature = _T_temp
for i in range(0, _ngr):
P_atm_prime = 0
for j in range(0, _nth):
# get the term that goes in the exponent of the atmospheric transmissivity
_o_over_cos_t = 1 / np.cos(theta_vals[j])
_emissivity_atm = (
np.ones(len(atmospheric_transmissivity))
- atmospheric_transmissivity ** _o_over_cos_t
)
_TE_atm = self.blackbody_spectrum * _emissivity_atm * np.cos(theta_vals[j])
_absorbed_TE_spectrum = (
_TE_atm * 0.5 * (emissivity_gradient_array_p[j, :, i] + emissivity_gradient_array_s[j, :, i])
)
_absorbed_TE = np.trapz(_absorbed_TE_spectrum, wavelength_array)
P_atm_prime += 2 * np.pi * _absorbed_TE * np.sin(theta_vals[j]) * theta_weights[j]
_absorbed_atmospheric_radiation_gradient[i] = P_atm_prime
return _absorbed_atmospheric_radiation_gradient
def _compute_solar_radiated_power(
self, solar_spectrum, emissivity_array_s, emissivity_array_p, wavelength_array
):
"""Method to compute the solar power absorbed by the cooling structure
Attributes
----------
None
Returns
-------
P_sun
References
----------
See Eq. (4) of https://www.nature.com/articles/nature13883
"""
# compute the absorbed solar spectrum
_absorbed_solar_spectrum = (
solar_spectrum * 0.5 * (emissivity_array_p + emissivity_array_s)
)
# integrate it!
P_sun = np.trapz(_absorbed_solar_spectrum, wavelength_array)
return P_sun
def _compute_solar_radiated_power_gradient(
self, solar_spectrum, emissivity_gradient_array_s, emissivity_gradient_array_p, wavelength_array
):
"""Method to compute the gradient of the solar power absorbed by the cooling structure
Attributes
----------
None
Returns
-------
_absorbed_solar_spectrum_gradient
References
----------
See Eq. (4) of https://www.nature.com/articles/nature13883
"""
# get the dimension of the gradient vector
_ngr = len(emissivity_gradient_array_s[0,:])
_absorbed_solar_spectrum_gradient = np.zeros(_ngr)
for i in range(0, _ngr):
_absorbed_solar_spectrum_prime = (
solar_spectrum * 0.5 * (emissivity_gradient_array_p[:,i] + emissivity_gradient_array_s[:,i])
)
# integrate it!
_absorbed_solar_spectrum_gradient[i] = np.trapz(_absorbed_solar_spectrum_prime, wavelength_array)
return _absorbed_solar_spectrum_gradient