nomad-hzb / hzb-combinatorial-libraries-plugin

Apache License 2.0
0 stars 0 forks source link

Derived properties in UV-vis spectroscopy measurements #9

Open Pepe-Marquez opened 2 months ago

Pepe-Marquez commented 2 months ago

In this measurement we need to add the following:

You would need the AM1.5 GT spectrum already used in the EQE normalizer for this.

RoteKekse commented 2 months ago

do you have a formula to calculate the band gap?

Pepe-Marquez commented 2 months ago

do you have a formula to calculate the band gap?

We can us the same way that we do with the EQE. We want to treat the absorptance equivalently to the EQE. In this case, we do not derive Urbach energies because we cannot resolve those very low values well with this technique.

RoteKekse commented 2 months ago
import json
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
from scipy.signal import argrelextrema, savgol_filter
from scipy.optimize import curve_fit

# Constants
h = 6.62607015e-34  # Planck's constant in Js
c = 3e8  # Speed of light in m/s
eV = 1.60218e-19  # Electron charge in Coulombs

# Set plot defaults
plt.rcParams.update({
    'axes.titlesize': 24,
    'axes.labelsize': 24,
    'xtick.labelsize': 24,
    'ytick.labelsize': 24,
    'legend.fontsize': 18,
    'xtick.direction': 'in',
    'ytick.direction': 'in',
})

def read_data(filename):
    """Read data from a JSON file."""
    with open(filename, 'r') as file:
        return json.load(file)

def interpolate_data(x, y, new_x, kind='linear'):
    """Interpolate y values at new x coordinates."""
    f = interp1d(x, y, kind=kind, fill_value='extrapolate')
    return f(new_x)

def convert_wavelengths_to_energy(wavelengths):
    """Convert wavelengths in nm to photon energies in eV."""
    wavelengths_m = wavelengths * 1e-9
    return (h * c) / (wavelengths_m * eV)

def calculate_absorptance(trans_intensities, reflectance):
    """Calculate absorptance from transmittance and reflectance."""
    return 1.2 - np.array(trans_intensities) - reflectance

def smooth_data(y, window_length=101, polyorder=3):
    """Apply Savitzky-Golay filter to data."""
    return savgol_filter(y, window_length=window_length, polyorder=polyorder)

def find_peaks_and_fit_gaussian(x, y):
    """Detect peaks and fit a Gaussian around the highest peak."""
    peaks = argrelextrema(y, np.greater)[0]
    if peaks.size > 0:
        peak = peaks[np.argmax(y[peaks])]
        peak_energy = x[peak]
        fitting_range = (x > peak_energy - 0.1) & (x < peak_energy + 0.1)
        popt, _ = curve_fit(
            gaussian,
            x[fitting_range],
            y[fitting_range],
            p0=[y[peak], peak_energy, 0.05],
        )
        return popt[1], popt, fitting_range
    return None, None, None

def gaussian(x, amp, cen, wid):
    """Gaussian function used for peak fitting."""
    return amp * np.exp(-((x - cen) ** 2) / (2 * wid**2))

def plot_results(photon_energies, absorptance, fine_photon_energies, smoothed_absorptance, first_derivative, bandgap_energy, popt, fitting_range):
    """Plot the results of the calculations."""
    fig, axs = plt.subplots(1, 2, figsize=(12, 6))
    axs[0].plot(photon_energies, absorptance, 'o', label='Original Absorptance', alpha=0.5)
    axs[0].plot(fine_photon_energies, smoothed_absorptance, '-', color='r', label='Smoothed Absorptance')
    axs[0].set_ylim(-0.5, 1.1)
    axs[0].set_xlabel('Photon Energy (eV)')
    axs[0].set_ylabel('Absorptance')
    axs[1].plot(fine_photon_energies, first_derivative, '-', label='Smoothed First Derivative')
    axs[1].set_xlabel('Photon Energy (eV)')
    axs[1].set_ylabel('First Derivative')
    if bandgap_energy:
        axs[1].plot(
            fine_photon_energies[fitting_range],
            gaussian(fine_photon_energies[fitting_range], *popt),
            'r-',
            label='Gaussian Fit',
        )
        axs[1].axvline(
            x=bandgap_energy,
            color='r',
            linestyle='--',
            label=f'Bandgap ~ {bandgap_energy:.2f} eV',
        )
    axs[0].legend()
    axs[1].legend()
    plt.tight_layout()
    plt.show()
    return fig

def main(transmittance_file, reflectance_file, start_energy=1.25, end_energy=2.8):
    trans_data = read_data(transmittance_file)
    refl_data = read_data(reflectance_file)
    trans_wavelengths = np.array(trans_data['wavelength'])
    refl_wavelengths = np.array(refl_data['wavelength'])
    reflectance = interpolate_data(refl_wavelengths, np.array(refl_data['intensity']), trans_wavelengths)
    photon_energies = convert_wavelengths_to_energy(trans_wavelengths)
    absorptance = calculate_absorptance(trans_data['intensity'], reflectance)
    energy_range_mask = (photon_energies >= start_energy) & (photon_energies <= end_energy)
    fine_photon_energies = np.linspace(start_energy, end_energy, 1000)
    fine_absorptance = interpolate_data(photon_energies[energy_range_mask], absorptance[energy_range_mask], fine_photon_energies, kind='cubic')
    smoothed_absorptance = smooth_data(fine_absorptance)
    first_derivative = np.gradient(smoothed_absorptance, fine_photon_energies)
    bandgap_energy, popt, fitting_range = find_peaks_and_fit_gaussian(fine_photon_energies, first_derivative)
    fig = plot_results(photon_energies, absorptance, fine_photon_energies, smoothed_absorptance, first_derivative, bandgap_energy, popt, fitting_range)
    return bandgap_energy, fig

# Example usage
bandgap_energy, fig = main('transmittance.json', 'reflectance.json')
print(f'Estimated Bandgap Energy: {bandgap_energy:.3f} eV')

from pepe

Pepe-Marquez commented 1 month ago

Great, we can also calculate the absorbed photon flux for the AM 1.5GT spectrum

import numpy as np
from scipy.interpolate import interp1d
from scipy.integrate import simps

def calculate_absorbed_photon_flux(energy, absorptance, flux_energy, flux):
    # Interpolate the absorptance spectrum to match the photon flux energy points
    absorptance_interpolator = interp1d(energy, absorptance, bounds_error=False, fill_value=0)
    absorptance_interpolated = absorptance_interpolator(flux_energy)

    # Calculate the absorbed photon flux at each energy point
    absorbed_flux = absorptance_interpolated * flux

    # Integrate the absorbed photon flux over the energy range to find the total absorption
    total_absorbed_flux = simps(absorbed_flux, flux_energy)

    return total_absorbed_flux

and convert it for an hypothetical J_sc:

 def calculate_current_density(absorbed_photon_flux, q=1.60217662e-19):
    """
    Calculate the current density generated by absorbed photons.

    This function computes the current density based on the absorbed photon flux, 
    the area over which the photons are absorbed, and the charge of an electron.

    Parameters:
    - absorbed_photon_flux (float): The number of photons absorbed per unit time per unit area.
    - q (float, optional): The charge of an electron in coulombs (C). Default value is 1.6e-19 C.

    Returns:
    - float: The current density).
    """
    # Calculate the current density using the formula
    current_density = (q / 1e-3) * absorbed_photon_flux

    return current_density

For the solar spectrum, you can use the file we use in the eqe analysis (photon_energy) vs (photon_flux)