#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Sep 28 11:59:40 2018
@author: Anna Jungbluth
"""
import math
import os
import sys
import tkinter as tk
import warnings
from tkinter import filedialog
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
import pandas as pd
import seaborn
# for the gui
from PyQt5 import QtWidgets
from numpy import exp, linspace
from scipy.optimize import curve_fit
from tqdm import tqdm
from collections import defaultdict
from scipy.interpolate import interp1d
import sEQE_Analysis_template
from source.add_subtract import subtract_Opt
from source.compilation import compile_EQE, compile_EL, compile_Data
from source.electroluminescence import bb_spectrum
from source.gaussian import calculate_gaussian_absorption, calculate_gaussian_disorder_absorption, \
calculate_MLJ_absorption, calculate_MLJ_disorder_absorption, calculate_combined_fit, calculate_combined_fit_MLJ
from source.normalization import normalize_EQE
from source.plot import plot, set_up_plot, set_up_EQE_plot, set_up_EL_plot
from source.reference_correction import calculate_Power
from source.utils import interpolate, sep_list, get_logger
from source.utils_plot import is_Colour, pick_EQE_Color, pick_EQE_Label, pick_Label
from source.validity import Ref_Data_is_valid, EQE_is_valid, Data_is_valid, Normalization_is_valid, Fit_is_valid, \
StartStop_is_valid
from source.utils_fit import guess_fit, fit_function, calculate_guess_fit, fit_model, fit_model_double, find_best_fit
from source.utils import R_squared
warnings.filterwarnings("ignore")
warnings.simplefilter('ignore', np.RankWarning)
warnings.simplefilter('ignore', np.ComplexWarning)
warnings.filterwarnings('ignore', "Intel MKL ERROR")
[docs]
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
# Set up the user interface from Designer
self.ui = sEQE_Analysis_template.Ui_MainWindow()
self.ui.setupUi(self)
# Tkinter
root = tk.Tk()
root.withdraw()
# Logger
self.logger = get_logger()
## Page 1 - Calculate EQE
self.ref_1 = []
self.ref_2 = []
self.ref_3 = []
self.ref_4 = []
self.ref_5 = []
self.ref_6 = []
self.data_1 = []
self.data_2 = []
self.data_3 = []
self.data_4 = []
self.data_5 = []
self.data_6 = []
# Handle Browse Buttons
self.ui.browseButton_1.clicked.connect(lambda: self.writeText(self.ui.textBox_p1_1, 1))
self.ui.browseButton_2.clicked.connect(lambda: self.writeText(self.ui.textBox_p1_2, 2))
self.ui.browseButton_3.clicked.connect(lambda: self.writeText(self.ui.textBox_p1_3, 3))
self.ui.browseButton_4.clicked.connect(lambda: self.writeText(self.ui.textBox_p1_4, 4))
self.ui.browseButton_5.clicked.connect(lambda: self.writeText(self.ui.textBox_p1_5, 5))
self.ui.browseButton_6.clicked.connect(lambda: self.writeText(self.ui.textBox_p1_6, 6))
self.ui.browseButton_7.clicked.connect(lambda: self.writeText(self.ui.textBox_p1_7, 7))
self.ui.browseButton_8.clicked.connect(lambda: self.writeText(self.ui.textBox_p1_8, 8))
self.ui.browseButton_9.clicked.connect(lambda: self.writeText(self.ui.textBox_p1_9, 9))
self.ui.browseButton_10.clicked.connect(lambda: self.writeText(self.ui.textBox_p1_10, 10))
self.ui.browseButton_11.clicked.connect(lambda: self.writeText(self.ui.textBox_p1_11, 11))
self.ui.browseButton_12.clicked.connect(lambda: self.writeText(self.ui.textBox_p1_12, 12))
# Handle Calculate Buttons
self.ui.calculateButton_1.clicked.connect(
lambda: self.pre_EQE(self.ref_1, self.data_1, self.ui.startNM_1, self.ui.stopNM_1, 1))
self.ui.calculateButton_2.clicked.connect(
lambda: self.pre_EQE(self.ref_2, self.data_2, self.ui.startNM_2, self.ui.stopNM_2, 2))
self.ui.calculateButton_3.clicked.connect(
lambda: self.pre_EQE(self.ref_3, self.data_3, self.ui.startNM_3, self.ui.stopNM_3, 3))
self.ui.calculateButton_4.clicked.connect(
lambda: self.pre_EQE(self.ref_4, self.data_4, self.ui.startNM_4, self.ui.stopNM_4, 4))
self.ui.calculateButton_5.clicked.connect(
lambda: self.pre_EQE(self.ref_5, self.data_5, self.ui.startNM_5, self.ui.stopNM_5, 5))
self.ui.calculateButton_6.clicked.connect(
lambda: self.pre_EQE(self.ref_6, self.data_6, self.ui.startNM_6, self.ui.stopNM_6, 6))
# Handle Export All Data Button
self.ui.exportButton.clicked.connect(self.export_EQE)
# Handle Clear Plot Button
self.ui.clearButton.clicked.connect(self.clear_plot)
## Page 2 - Plot EQE
self.EQE_1 = []
self.EQE_2 = []
self.EQE_3 = []
self.EQE_4 = []
self.EQE_5 = []
self.EQE_6 = []
self.EQE_7 = []
self.EQE_8 = []
self.EQE_9 = []
self.EQE_10 = []
# Handle Import EQE Buttons
self.ui.browseEQEButton_1.clicked.connect(lambda: self.writeText(self.ui.textBox_p2_1, 'p1'))
self.ui.browseEQEButton_2.clicked.connect(lambda: self.writeText(self.ui.textBox_p2_4, 'p4'))
self.ui.browseEQEButton_3.clicked.connect(lambda: self.writeText(self.ui.textBox_p2_7, 'p7'))
self.ui.browseEQEButton_4.clicked.connect(lambda: self.writeText(self.ui.textBox_p2_10, 'p10'))
self.ui.browseEQEButton_5.clicked.connect(lambda: self.writeText(self.ui.textBox_p2_13, 'p13'))
self.ui.browseEQEButton_6.clicked.connect(lambda: self.writeText(self.ui.textBox_p2_16, 'p16'))
self.ui.browseEQEButton_7.clicked.connect(lambda: self.writeText(self.ui.textBox_p2_19, 'p19'))
self.ui.browseEQEButton_8.clicked.connect(lambda: self.writeText(self.ui.textBox_p2_22, 'p22'))
self.ui.browseEQEButton_9.clicked.connect(lambda: self.writeText(self.ui.textBox_p2_25, 'p25'))
self.ui.browseEQEButton_10.clicked.connect(lambda: self.writeText(self.ui.textBox_p2_28, 'p28'))
# Handle Plot EQE Buttons
self.ui.plotEQEButton_Wavelength.clicked.connect(lambda: self.pre_plot_EQE(0))
self.ui.plotEQEButton_Energy.clicked.connect(lambda: self.pre_plot_EQE(1))
self.ref_label = '$\mathregular{C_{60}}$'
## Page 3 - Fit EQE (Marcus Theory)
self.data_fit_1 = []
self.data_fit_2 = []
# Handle Import EQE Buttons
self.ui.browseFitButton_1.clicked.connect(lambda: self.writeText(self.ui.textBox_f1, 'f1'))
self.ui.browseFitButton_2.clicked.connect(lambda: self.writeText(self.ui.textBox_f4, 'f4'))
# Handle Gaussian Fit Buttons
self.ui.gaussianFit_1.clicked.connect(
lambda: self.pre_fit_EQE(self.data_fit_1, self.ui.startPlot_1, self.ui.stopPlot_1, self.ui.startFit_1,
self.ui.stopFit_1, self.ui.startFitPlot_1, self.ui.stopFitPlot_1,
self.ui.textBox_f1, self.ui.textBox_f2, self.ui.textBox_f3, 1))
self.ui.gaussianFit_2.clicked.connect(
lambda: self.pre_fit_EQE(self.data_fit_2, self.ui.startPlot_2, self.ui.stopPlot_2, self.ui.startFit_2,
self.ui.stopFit_2, self.ui.startFitPlot_2, self.ui.stopFitPlot_2,
self.ui.textBox_f4, self.ui.textBox_f5, self.ui.textBox_f6, 2))
# Handle Heat Map Buttons
self.ui.heatButton_1.clicked.connect(
lambda: self.heatMap(self.data_fit_1, self.ui.startStart_1, self.ui.startStop_1,
self.ui.stopStart_1, self.ui.stopStop_1, self.ui.textBox_f1,
self.ui.textBox_f2, self.ui.textBox_f3, 1))
self.ui.heatButton_2.clicked.connect(
lambda: self.heatMap(self.data_fit_2, self.ui.startStart_2, self.ui.startStop_2,
self.ui.stopStart_2, self.ui.stopStop_2, self.ui.textBox_f4,
self.ui.textBox_f5, self.ui.textBox_f6, 2))
self.ui.clearButton_2.clicked.connect(self.clear_EQE_plot)
## Page 4 - Extended Fits (Marcus Theory)
# Double Fits
self.bias = False
self.tolerance = None
self.data_double = []
# Handle Import Data Button
self.ui.browseDoubleFitButton.clicked.connect(lambda: self.writeText(self.ui.textBox_dF1, 'double1'))
# Handle Double Fit Button
self.ui.doubleFitButton.clicked.connect(lambda: self.double_fit())
# Simultaneous Peak Fitting
self.bias_sim = False
self.tolerance_sim = False
self.data_sim = []
# Handle Import Data Button
self.ui.browseSimFitButton.clicked.connect(lambda: self.writeText(self.ui.textBox_simFit, 'sim'))
# Handle Sim Fit Button
self.ui.simDoubleFitButton_single.clicked.connect(lambda: self.sim_double_fit_single())
self.ui.simDoubleFitButton.clicked.connect(lambda: self.sim_double_fit())
## Page 5 - Fit EQE (MLJ Theory)
self.data_xFit_1 = []
self.S_i = self.ui.Huang_Rhys.value()
self.hbarw_i = self.ui.vib_Energy.value()
# Handle Import EQE Buttons
self.ui.browseButton_extraFit.clicked.connect(lambda: self.writeText(self.ui.textBox_xF1, 'xF1'))
# Handle Fit Button
self.ui.extraFitButton.clicked.connect(
lambda: self.pre_fit_EQE(self.data_xFit_1, self.ui.startExtraPlot, self.ui.stopExtraPlot,
self.ui.startExtraFit, self.ui.stopExtraFit, self.ui.startExtraFitPlot,
self.ui.stopExtraFitPlot, self.ui.textBox_xF1, self.ui.textBox_xF2,
self.ui.textBox_xF3, 'x1'))
# Handle Heat Map Button
self.ui.extraHeatButton.clicked.connect(
lambda: self.heatMap(self.data_xFit_1, self.ui.extraStartStart, self.ui.extraStartStop,
self.ui.extraStopStart, self.ui.extraStopStop, self.ui.textBox_xF1,
self.ui.textBox_xF2, self.ui.textBox_xF3, 'x1'))
# Handle Clear Extra Fit Button
self.ui.clearButton_extraFit.clicked.connect(self.clear_EQE_plot)
# Double Fits
self.bias = False
self.tolerance = None
self.data_extraDouble = []
# Handle Import Data Button
self.ui.browseButton_extraDoubleFit.clicked.connect(lambda: self.writeText(self.ui.textBox_extraDouble, 'xDF1'))
# Handle Double Fit Button
self.ui.extraDoubleFitButton.clicked.connect(lambda: self.double_fit_MLJ())
## Page 6 - Fit EL and EQE
self.EL = []
self.EL_EQE = []
self.Red_EL_cal = []
self.Red_EL_meas = []
self.Red_EQE_meas = []
# Handle Import EL and EQE Data Buttons
self.ui.browseELButton_1.clicked.connect(lambda: self.writeText(self.ui.textBox_EL1, 'el1'))
self.ui.browseELButton_2.clicked.connect(lambda: self.writeText(self.ui.textBox_EL4, 'el2'))
# Handle EL Plot Buttons
self.ui.plotELButton_1.clicked.connect(
lambda: self.pre_plot_EL_EQE(self.EL, self.ui.startPlot_EL1, self.ui.stopPlot_EL1, 0)) # Plot EL
self.ui.plotELButton_2.clicked.connect(
lambda: self.pre_plot_EL_EQE(self.EL, self.ui.startPlot_EL2, self.ui.stopPlot_EL2, 1)) # Plot Abs
self.ui.plotELButton_3.clicked.connect(
lambda: self.pre_plot_EL_EQE(self.EL_EQE, self.ui.startPlot_EQE, self.ui.stopPlot_EQE, 2)) # Plot EQE
# Handle Fit Buttons
self.ui.fitButton_EL1.clicked.connect(
lambda: self.pre_plot_EL_EQE(self.EL, self.ui.startPlot_EL1, self.ui.stopPlot_EL1, 0, fit=True)) # Fit EL
self.ui.fitButton_EL2.clicked.connect(
lambda: self.pre_plot_EL_EQE(self.EL, self.ui.startPlot_EL2, self.ui.stopPlot_EL2, 1, fit=True)) # Fit Abs
self.ui.fitButton_EL3.clicked.connect(
lambda: self.pre_plot_EL_EQE(self.EL_EQE, self.ui.startPlot_EQE, self.ui.stopPlot_EQE, 2,
fit=True)) # Fit EQE
# Handle Clear EL Plot Button
self.ui.clearButton_EL.clicked.connect(lambda: self.clear_EL_plot())
## Page 7 - Subtract and Add Peak Fits
# Subtract Peak Fits
self.data_subFit = []
self.data_subEQE = []
# Handle Import Fit and EQE Data Buttons
self.ui.browseSubButton_Fit.clicked.connect(lambda: self.writeText(self.ui.textBox_p6_1, 'sub1'))
self.ui.browseSubButton_EQE.clicked.connect(lambda: self.writeText(self.ui.textBox_p6_4, 'sub2'))
# Handle Subtract Fit Data Button
self.ui.subButton.clicked.connect(
lambda: self.subtract_Fit(self.data_subFit, self.data_subEQE, self.ui.textBox_p6_2, self.ui.textBox_p6_5,
self.ui.textBox_p6_3, self.ui.textBox_p6_6))
# Add Peak Fits
self.data_addOptFit = []
self.data_addCTFit = []
self.data_addEQE = []
# Handle Import Fit and EQE Data Buttons
self.ui.browseAddButton_optFit.clicked.connect(lambda: self.writeText(self.ui.textBox_p7_1, 'add1'))
self.ui.browseAddButton_CTFit.clicked.connect(lambda: self.writeText(self.ui.textBox_p7_4, 'add2'))
self.ui.browseAddButton_EQE.clicked.connect(lambda: self.writeText(self.ui.textBox_p7_7, 'add3'))
# Handle Plot Fit Button
self.ui.plotAddButton.clicked.connect(
lambda: self.add_Fits(self.data_addOptFit, self.data_addCTFit, self.data_addEQE))
# Import Photodiode Calibration Files
Si_file = pd.ExcelFile(
"calibration_files/FDS100-CAL.xlsx") # The files are in the sEQE Analysis folder, just as this program
self.Si_cal = Si_file.parse('Sheet1')
InGaAs_file = pd.ExcelFile("calibration_files/FGA21-CAL.xlsx")
self.InGaAs_cal = InGaAs_file.parse('Sheet1')
# Define Variables
# NOTE: Modify path if switching from Linux to another operating system
self.data_dir = os.path.join(os.path.join(os.path.expanduser('~')), 'Desktop')
self.h = 6.626 * math.pow(10, -34) # [m^2 kg/s]
self.h_2 = 4.136 * math.pow(10, -15) # [eV s]
self.c = 2.998 * math.pow(10, 8) # [m/s]
self.q = 1.602 * math.pow(10, -19) # [C]
self.k = 8.617 * math.pow(10, -5) # [ev/K]
self.export = False
self.do_plot = True
self.fit_plot = True
self.do_plot_EL = True
# Define parameters for initial guesses
# NOTE: Modify initial guesses if fit is unsuccessful
# NOTE: Some of these parameters are repeated/hard coded in utils_fit.py
# These values are used in standard/disorder, Marcus/MLJ, EQE/EL fitting functions
# Make sure to understand where the parameters all change if they are adjusted here!
self.f_guess = 0.001
self.l_guess = 0.150
# These values are used for disorder Marcus / MLJ fitting functions
# self.bounds_sig = ([0, 0, 0, 0], [0.1, 0.4, 1.6, 0.2]) # f, l, E, sig
# Normal Bounds:
self.bounds_sig = ([0, 0, 0, 0], [0.1, 0.6, 1.6, 0.2]) # f, l, E, sig
# # Adjusted Bounds:
# self.bounds_sig = ([0, 0, 0, 0], [0.1, 0.6, 1.7, 0.2]) # f, l, E, si
# These values are used in the standard/disorder simultaneous double peak fitting
# CAVEAT: I tried using the guess_fit function to test other guesses but didn't achieve great results
# CAVEAT: The other fit functions start with an peak energy guess of 1.2 eV instead of 1.3 eV
self.sim_guess = [0.001, 0.150, 1.30, 0.01, 0.150, 1.5] # fCT, lCT, ECT, fopt, lopt, Eopt
self.sim_guess_sig = [0.001, 0.150, 1.30, 0.01, 0.150, 1.5, 0.1] # fCT, lCT, ECT, fopt, lopt, Eopt, sig
# Set floating point precision
precision = 8 # decimal places
# -----------------------------------------------------------------------------------------------------------
# Functions to read file and update textbox
# -----------------------------------------------------------------------------------------------------------
[docs]
def writeText(self,
text_Box,
textBox_no
):
"""Function to load data and update text box in GUI
Parameters
----------
text_Box : gui object, required
GUI text box to write filename into
textBox_no : int or str, required
GUI textbox with information on which variable to define
Returns
-------
None
"""
os.chdir(self.data_dir)
file_ = filedialog.askopenfilename()
if len(file_) != 0:
path_, filename_ = os.path.split(file_)
text_Box.clear() # Clear the text box in case sth has been uploaded already
text_Box.insertPlainText(filename_) # Insert filename into text box
## Page 1 - Calculate EQE
# Reference files:
if textBox_no == 1:
self.ref_1 = pd.read_csv(file_) # Turn file into dataFrame
elif textBox_no == 3:
self.ref_2 = pd.read_csv(file_)
elif textBox_no == 5:
self.ref_3 = pd.read_csv(file_)
elif textBox_no == 7:
self.ref_4 = pd.read_csv(file_)
elif textBox_no == 9:
self.ref_5 = pd.read_csv(file_)
elif textBox_no == 11:
self.ref_6 = pd.read_csv(file_)
# Data files:
elif textBox_no == 2:
self.data_1 = pd.read_csv(file_)
elif textBox_no == 4:
self.data_2 = pd.read_csv(file_)
elif textBox_no == 6:
self.data_3 = pd.read_csv(file_)
elif textBox_no == 8:
self.data_4 = pd.read_csv(file_)
elif textBox_no == 10:
self.data_5 = pd.read_csv(file_)
elif textBox_no == 12:
self.data_6 = pd.read_csv(file_)
## Page 2 - Plot EQE
elif textBox_no == 'p1':
self.EQE_1 = pd.read_csv(file_)
elif textBox_no == 'p4':
self.EQE_2 = pd.read_csv(file_)
elif textBox_no == 'p7':
self.EQE_3 = pd.read_csv(file_)
elif textBox_no == 'p10':
self.EQE_4 = pd.read_csv(file_)
elif textBox_no == 'p13':
self.EQE_5 = pd.read_csv(file_)
elif textBox_no == 'p16':
self.EQE_6 = pd.read_csv(file_)
elif textBox_no == 'p19':
self.EQE_7 = pd.read_csv(file_)
elif textBox_no == 'p22':
self.EQE_8 = pd.read_csv(file_)
elif textBox_no == 'p25':
self.EQE_9 = pd.read_csv(file_)
elif textBox_no == 'p28':
self.EQE_10 = pd.read_csv(file_)
## Page 3 - Fit EQE (Marcus Theory)
elif textBox_no == 'f1':
self.data_fit_1 = pd.read_csv(file_)
elif textBox_no == 'f4':
self.data_fit_2 = pd.read_csv(file_)
## Page 4 - Extended Fits (Marcus Theory)
# For Double Fits
elif textBox_no == 'double1':
self.data_double = pd.read_csv(file_)
# For Simultaneous Fits
elif textBox_no == 'sim':
self.data_sim = pd.read_csv(file_)
## Page 5 - Fit EQE (MLJ Theory)
elif textBox_no == 'xF1':
self.data_xFit_1 = pd.read_csv(file_)
elif textBox_no == 'xDF1':
self.data_extraDouble = pd.read_csv(file_)
## Page 6 - Fit EL and EQE
elif textBox_no == 'el1':
self.EL = pd.read_table(file_, sep=',', index_col=0)
elif textBox_no == 'el2':
self.EL_EQE = pd.read_csv(file_)
## Page 7 - Subtract and Add Peak Fits
# Subtract Peak Fits
elif textBox_no == 'sub1':
self.data_subFit = pd.read_csv(file_)
elif textBox_no == 'sub2':
self.data_subEQE = pd.read_csv(file_)
# Add Peak Fits
elif textBox_no == 'add1':
self.data_addOptFit = pd.read_csv(file_)
elif textBox_no == 'add2':
self.data_addCTFit = pd.read_csv(file_)
elif textBox_no == 'add3':
self.data_addEQE = pd.read_csv(file_)
# -----------------------------------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------------------------------
# Page 1 - Calculate EQE
# -----------------------------------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------------------------------
# Function to select data and reference files
[docs]
def pre_EQE(self,
ref_df,
data_df,
start,
stop,
range_no
):
"""Wrapper function to load variables and data for EQE calculation
Parameters
----------
ref_df : dataFrame, required
Dataframe with reference diode measurement
data_df : dataFrame, required
Dataframe with sample measurement
start : gui object, required
GUI field with start wavelength
stop : gui object, required
GUI field with stop wavelength
range_no : int, required
Number specifying which data range to compile
Returns
-------
None
"""
startNM = start.value()
stopNM = stop.value()
if Ref_Data_is_valid(ref_df, data_df, startNM, stopNM, range_no):
self.calculate_EQE(ref_df, data_df, startNM, stopNM, range_no)
# -----------------------------------------------------------------------------------------------------------
# Function to calculate EQE
[docs]
def calculate_EQE(self,
ref_df,
data_df,
startNM,
stopNM,
range_no
):
"""Function to calculate EQE from signal and reference data
Parameters
----------
ref_df : dataFrame, required
Dataframe with reference diode measurement
data_df : dataFrame, required
Dataframe with sample measurement
startNM : float, required
Start wavelength [nm]
stop : float, required
Stop wavelength [nm]
range_no : int, required
Number specifying which data range to compile
Returns
-------
None
"""
power_dict = {}
Wavelength = []
Energy = []
EQE = []
log_EQE = []
if 'Power' not in ref_df.columns:
self.logger.info('Calculating power values.')
if range_no == 1:
if self.ui.Range1_Si_button.isChecked() and not self.ui.Range1_InGaAs_button.isChecked():
try:
ref_df['Power'] = calculate_Power(ref_df, self.Si_cal)
except:
self.logger.error('Please select a valid reference diode.')
elif self.ui.Range1_InGaAs_button.isChecked() and not self.ui.Range1_Si_button.isChecked():
try:
ref_df['Power'] = calculate_Power(ref_df, self.InGaAs_cal)
except:
self.logger.error('Please select a valid reference diode.')
else:
self.logger.error('Please select a valid reference diode.')
elif range_no == 2:
if self.ui.Range2_Si_button.isChecked() and not self.ui.Range2_InGaAs_button.isChecked():
try:
ref_df['Power'] = calculate_Power(ref_df, self.Si_cal)
except:
self.logger.error('Please select a valid reference diode.')
elif self.ui.Range2_InGaAs_button.isChecked() and not self.ui.Range2_Si_button.isChecked():
try:
ref_df['Power'] = calculate_Power(ref_df, self.InGaAs_cal)
except:
self.logger.error('Please select a valid reference diode.')
else:
self.logger.error('Please select a valid reference diode.')
elif range_no == 3:
if self.ui.Range3_Si_button.isChecked() and not self.ui.Range3_InGaAs_button.isChecked():
try:
ref_df['Power'] = calculate_Power(ref_df, self.Si_cal)
except:
self.logger.error('Please select a valid reference diode.')
elif self.ui.Range3_InGaAs_button.isChecked() and not self.ui.Range3_Si_button.isChecked():
try:
ref_df['Power'] = calculate_Power(ref_df, self.InGaAs_cal)
except:
self.logger.error('Please select a valid reference diode.')
else:
self.logger.error('Please select a valid reference diode.')
elif range_no == 4:
if self.ui.Range4_Si_button.isChecked() and not self.ui.Range4_InGaAs_button.isChecked():
try:
ref_df['Power'] = calculate_Power(ref_df, self.Si_cal)
except:
self.logger.error('Please select a valid reference diode.')
elif self.ui.Range4_InGaAs_button.isChecked() and not self.ui.Range4_Si_button.isChecked():
try:
ref_df['Power'] = calculate_Power(ref_df, self.InGaAs_cal)
except:
self.logger.error('Please select a valid reference diode.')
else:
self.logger.error('Please select a valid reference diode.')
elif range_no == 5:
if self.ui.Range5_Si_button.isChecked() and not self.ui.Range5_InGaAs_button.isChecked():
try:
ref_df['Power'] = calculate_Power(ref_df, self.Si_cal)
except:
self.logger.error('Please select a valid reference diode.')
elif self.ui.Range5_InGaAs_button.isChecked() and not self.ui.Range5_Si_button.isChecked():
try:
ref_df['Power'] = calculate_Power(ref_df, self.InGaAs_cal)
except:
self.logger.error('Please select a valid reference diode.')
else:
self.logger.error('Please select a valid reference diode.')
elif range_no == 6:
if self.ui.Range6_Si_button.isChecked() and not self.ui.Range6_InGaAs_button.isChecked():
try:
ref_df['Power'] = calculate_Power(ref_df, self.Si_cal)
except:
self.logger.error('Please select a valid reference diode.')
elif self.ui.Range6_InGaAs_button.isChecked() and not self.ui.Range6_Si_button.isChecked():
try:
ref_df['Power'] = calculate_Power(ref_df, self.InGaAs_cal)
except:
self.logger.error('Please select a valid reference diode.')
else:
self.logger.error('Please select a valid reference diode.')
if 'Power' in ref_df.columns: # Check if the power has been calculated already
for x in range(len(ref_df['Wavelength'])): # Iterate through columns of reference file
power_dict[ref_df['Wavelength'][x]] = ref_df['Power'][
x] # Add wavelength and corresponding power to dictionary
for y in range(len(data_df['Wavelength'])): # Iterate through columns of data file
if startNM <= data_df['Wavelength'][y] <= stopNM: # Calculate EQE if start <= wave <= stop, else ignore
if data_df['Wavelength'][y] in power_dict.keys(): # Check if data wavelength is in reference file
Wavelength.append(data_df['Wavelength'][y])
Energy_val = (self.h * self.c) / (
data_df['Wavelength'][y] * math.pow(10, -9) * self.q) # Calculate energy in eV
Energy.append(Energy_val)
EQE_val = (data_df['Mean Current'][y] * Energy_val) / (
power_dict[data_df['Wavelength'][y]]) # Easier way to calculate EQE
# EQE_val = ((data_df['Mean Current'][y] * self.h * self.c) / (
# data_df['Wavelength'][y] * math.pow(10,-9) * power_dict[data_df['Wavelength'][y]] * self.q))
# EQE_val = (100 * data_df['Mean Current'][y] * Energy_val) / (
# power_dict[data_df['Wavelength'][y]]) # *100 to turn into percent
EQE.append(EQE_val)
log_EQE.append(math.log10(EQE_val))
else: # If data wavelength is not in reference file
Wavelength.append(data_df['Wavelength'][y])
Energy_val = (self.h * self.c) / (data_df['Wavelength'][y] * math.pow(10, -9) * self.q)
Energy.append(Energy_val)
Power_int = interpolate(data_df['Wavelength'][y], ref_df['Wavelength'],
ref_df['Power']) # Interpolate power
EQE_int = (data_df['Mean Current'][y] * Energy_val) / (Power_int)
# EQE_int = ((data_df['Mean Current'][y] * self.h * self.c) / (
# data_df['Wavelength'][y] * math.pow(10,-9) * Power_int * self.q))
EQE.append(EQE_int)
log_EQE.append(math.log10(EQE_int))
if len(Wavelength) == len(EQE) and len(Energy) == len(log_EQE): # Check if the lists have the same length
if self.export: # If the "Export Data" button has been clicked
return (Wavelength, Energy, EQE, log_EQE)
else: # If the "Calculate EQE" button has been clicked
if self.do_plot: # This is set to true during setup of the program
self.ax1, self.ax2 = set_up_plot()
self.do_plot = False # Set self.do_plot to False to plot on the same graph
label_ = pick_Label(range_no, startNM, stopNM)
self.ax1.plot(Wavelength, EQE, linewidth=3, label=label_)
self.ax2.semilogy(Wavelength, EQE,
linewidth=3) # Equivalent to the line above but with proper log scale axes
self.ax1.legend()
plt.draw()
else:
self.logger.error('Length mismatch.')
# -----------------------------------------------------------------------------------------------------------
# Function to export EQE
[docs]
def export_EQE(self):
"""Function to export EQE to csv file
Parameters
----------
None
Returns
-------
None
"""
self.export = True
ok_1 = True # Create boolean variable to use for "is_valid" function
ok_2 = True
ok_3 = True
ok_4 = True
ok_5 = True
ok_6 = True
columns = ['Wavelength', 'Energy', 'EQE', 'Log_EQE'] # Columns for dataFrame
export_file = pd.DataFrame(columns=columns) # Create empty dataFrame
wave_inc = {} # create empty dictionary
if self.ui.exportBox_1.isChecked(): # If the checkBox is checked
startNM1 = self.ui.startNM_1.value() # Pick start wavelength
stopNM1 = self.ui.stopNM_1.value() # Pick stop wavelength
if Ref_Data_is_valid(self.ref_1, self.data_1, startNM1, stopNM1,
1): # Check that files are non-empty and within wavelength range
Wave_1, Energy_1, EQE_1, log_EQE_1 = self.calculate_EQE(self.ref_1, self.data_1, startNM1, stopNM1,
1) # Extract data
export_1 = pd.DataFrame({'Wavelength': Wave_1, 'Energy': Energy_1, 'EQE': EQE_1,
'Log_EQE': log_EQE_1}) # Create dataFrame with EQE data
wave_inc['1'] = Wave_1[0] # Add the first wavelength value to the wave_inc list
else:
ok_1 = False # Set variable to False if calculation is invalid
if self.ui.exportBox_2.isChecked():
startNM2 = self.ui.startNM_2.value()
stopNM2 = self.ui.stopNM_2.value()
if Ref_Data_is_valid(self.ref_2, self.data_2, startNM2, stopNM2, 2):
Wave_2, Energy_2, EQE_2, log_EQE_2 = self.calculate_EQE(self.ref_2, self.data_2, startNM2, stopNM2, 2)
export_2 = pd.DataFrame({'Wavelength': Wave_2, 'Energy': Energy_2, 'EQE': EQE_2, 'Log_EQE': log_EQE_2})
wave_inc['2'] = Wave_2[0]
else:
ok_2 = False
if self.ui.exportBox_3.isChecked():
startNM3 = self.ui.startNM_3.value()
stopNM3 = self.ui.stopNM_3.value()
if Ref_Data_is_valid(self.ref_3, self.data_3, startNM3, stopNM3, 3):
Wave_3, Energy_3, EQE_3, log_EQE_3 = self.calculate_EQE(self.ref_3, self.data_3, startNM3, stopNM3, 3)
export_3 = pd.DataFrame({'Wavelength': Wave_3, 'Energy': Energy_3, 'EQE': EQE_3, 'Log_EQE': log_EQE_3})
wave_inc['3'] = Wave_3[0]
else:
ok_3 = False
if self.ui.exportBox_4.isChecked():
startNM4 = self.ui.startNM_4.value()
stopNM4 = self.ui.stopNM_4.value()
if Ref_Data_is_valid(self.ref_4, self.data_4, startNM4, stopNM4, 4):
Wave_4, Energy_4, EQE_4, log_EQE_4 = self.calculate_EQE(self.ref_4, self.data_4, startNM4, stopNM4, 4)
export_4 = pd.DataFrame({'Wavelength': Wave_4, 'Energy': Energy_4, 'EQE': EQE_4, 'Log_EQE': log_EQE_4})
wave_inc['4'] = Wave_4[0]
else:
ok_4 = False
if self.ui.exportBox_5.isChecked():
startNM5 = self.ui.startNM_5.value()
stopNM5 = self.ui.stopNM_5.value()
if Ref_Data_is_valid(self.ref_5, self.data_5, startNM5, stopNM5, 5):
Wave_5, Energy_5, EQE_5, log_EQE_5 = self.calculate_EQE(self.ref_5, self.data_5, startNM5, stopNM5, 5)
export_5 = pd.DataFrame({'Wavelength': Wave_5, 'Energy': Energy_5, 'EQE': EQE_5, 'Log_EQE': log_EQE_5})
wave_inc['5'] = Wave_5[0]
else:
ok_5 = False
if self.ui.exportBox_6.isChecked():
startNM6 = self.ui.startNM_6.value()
stopNM6 = self.ui.stopNM_6.value()
if Ref_Data_is_valid(self.ref_6, self.data_6, startNM6, stopNM6, 6):
Wave_6, Energy_6, EQE_6, log_EQE_6 = self.calculate_EQE(self.ref_6, self.data_6, startNM6, stopNM6, 6)
export_6 = pd.DataFrame({'Wavelength': Wave_6, 'Energy': Energy_6, 'EQE': EQE_6, 'Log_EQE': log_EQE_6})
wave_inc['6'] = Wave_6[0]
else:
ok_6 = False
if ok_1 and ok_2 and ok_3 and ok_4 and ok_5 and ok_6: # Check if all operations are ok or if fields are empty
for x in range(len(wave_inc.keys())): # Iterate through wave_inc list
minimum = min(wave_inc, key=wave_inc.get) # Find key corresponding to minimum value
if minimum == '1': # Append correct dataFrame in order of decending wavelength
export_file = export_file.append(export_1, ignore_index=True)
elif minimum == '2':
export_file = export_file.append(export_2, ignore_index=True)
elif minimum == '3':
export_file = export_file.append(export_3, ignore_index=True)
elif minimum == '4':
export_file = export_file.append(export_4, ignore_index=True)
elif minimum == '5':
export_file = export_file.append(export_5, ignore_index=True)
elif minimum == '6':
export_file = export_file.append(export_6, ignore_index=True)
del wave_inc[minimum] # Delete recently appended value
EQE_file = filedialog.asksaveasfilename() # Prompt the user to pick a folder & name to save data to
export_path, export_filename = os.path.split(EQE_file)
if len(export_path) != 0: # Check if the user actually selected a path
os.chdir(export_path) # Change the working directory
export_file.to_csv(export_filename) # Save data to csv
self.logger.info('Saving data to: %s' % str(EQE_file))
os.chdir(self.data_dir) # Change the directory back
self.export = False
# -----------------------------------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------------------------------
# Page 2 - Calculate EQE
# -----------------------------------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------------------------------
# Function to select EQE for plotting
[docs]
def pre_plot_EQE(self,
number
):
"""Wrapper function to select EQE file for plotting
Parameters
----------
number : int, required
Number of the EQE file to plot
Returns
-------
None
"""
ok_EQE_1 = True
ok_EQE_2 = True
ok_EQE_3 = True
ok_EQE_4 = True
ok_EQE_5 = True
ok_EQE_6 = True
ok_EQE_7 = True
ok_EQE_8 = True
ok_EQE_9 = True
ok_EQE_10 = True
if self.ui.normalizeBox.isChecked():
norm_num = 1
else:
norm_num = 0
self.axEQE_1, self.axEQE_2 = set_up_EQE_plot(number, norm_num)
if self.ui.plotBox_1.isChecked():
ok_EQE_1 = self.plot_EQE(self.EQE_1, self.ui.startEQE_1, self.ui.stopEQE_1, self.ui.textBox_p2_1,
self.ui.textBox_p2_2, self.ui.textBox_p2_3, 1, number)
if self.ui.plotBox_2.isChecked():
ok_EQE_2 = self.plot_EQE(self.EQE_2, self.ui.startEQE_2, self.ui.stopEQE_2, self.ui.textBox_p2_4,
self.ui.textBox_p2_5, self.ui.textBox_p2_6, 2, number)
if self.ui.plotBox_3.isChecked():
ok_EQE_3 = self.plot_EQE(self.EQE_3, self.ui.startEQE_3, self.ui.stopEQE_3, self.ui.textBox_p2_7,
self.ui.textBox_p2_8, self.ui.textBox_p2_9, 3, number)
if self.ui.plotBox_4.isChecked():
ok_EQE_4 = self.plot_EQE(self.EQE_4, self.ui.startEQE_4, self.ui.stopEQE_4, self.ui.textBox_p2_10,
self.ui.textBox_p2_11, self.ui.textBox_p2_12, 4, number)
if self.ui.plotBox_5.isChecked():
ok_EQE_5 = self.plot_EQE(self.EQE_5, self.ui.startEQE_5, self.ui.stopEQE_5, self.ui.textBox_p2_13,
self.ui.textBox_p2_14, self.ui.textBox_p2_15, 5, number)
if self.ui.plotBox_6.isChecked():
ok_EQE_6 = self.plot_EQE(self.EQE_6, self.ui.startEQE_6, self.ui.stopEQE_6, self.ui.textBox_p2_16,
self.ui.textBox_p2_17, self.ui.textBox_p2_18, 6, number)
if self.ui.plotBox_7.isChecked():
ok_EQE_7 = self.plot_EQE(self.EQE_7, self.ui.startEQE_7, self.ui.stopEQE_7, self.ui.textBox_p2_19,
self.ui.textBox_p2_20, self.ui.textBox_p2_21, 7, number)
if self.ui.plotBox_8.isChecked():
ok_EQE_8 = self.plot_EQE(self.EQE_8, self.ui.startEQE_8, self.ui.stopEQE_8, self.ui.textBox_p2_22,
self.ui.textBox_p2_23, self.ui.textBox_p2_24, 8, number)
if self.ui.plotBox_9.isChecked():
ok_EQE_9 = self.plot_EQE(self.EQE_9, self.ui.startEQE_9, self.ui.stopEQE_9, self.ui.textBox_p2_25,
self.ui.textBox_p2_26, self.ui.textBox_p2_27, 9, number)
if self.ui.plotBox_10.isChecked():
ok_EQE_10 = self.plot_EQE(self.EQE_10, self.ui.startEQE_10, self.ui.stopEQE_10, self.ui.textBox_p2_28,
self.ui.textBox_p2_29, self.ui.textBox_p2_30, 10, number)
if ok_EQE_1 and ok_EQE_2 and ok_EQE_3 and ok_EQE_4 and ok_EQE_5 and ok_EQE_6 and ok_EQE_7 and ok_EQE_8 and \
ok_EQE_9 and ok_EQE_10:
self.axEQE_1.legend()
self.axEQE_2.legend()
# self.axEQE_1.legend(frameon=False, fontsize=13) # Adjusted style
# self.axEQE_2.legend(frameon=False, fontsize=13) # Adjusted style
plt.show()
else:
plt.close()
plt.close()
# -----------------------------------------------------------------------------------------------------------
# Function to plot EQE
[docs]
def plot_EQE(self,
eqe_df,
startNM,
stopNM,
filename_Box,
label_Box,
color_Box,
file_no,
number
):
"""Function to plot EQE data
Parameters
----------
eqe_df : dataFrame, required
Dataframe with EQE data
startNM : float, required
Start wavelength [nm]
stop : float, required
Stop wavelength [nm]
filename_Box : gui object, required
GUI textbox with filename information for plot labeling
label_Box : gui object, required
GUI textbox with plot label
color_Box : gui object, required
GUI textbox with plot color
file_no : int, required
Number of EQE file/data range to plot
number : int, required
Number specifying whether to plot wavelength or energy
0 -> Wavelength
1 -> Energy
Returns
-------
None
"""
startNM = startNM.value() # Pick start wavelength
stopNM = stopNM.value() # Pick stop wavelength
if EQE_is_valid(eqe_df, startNM, stopNM, file_no): # Check that files are non-empty and within wavelength range
if self.ui.normalizeBox.isChecked():
normNM = self.ui.normalizeNM.value()
if Normalization_is_valid(eqe_df, normNM, file_no):
wave, energy, eqe, log_eqe = normalize_EQE(eqe_df, startNM, stopNM, normNM)
else:
return False
else:
wave, energy, eqe, log_eqe = compile_EQE(eqe_df, startNM, stopNM, 0)
label_ = pick_EQE_Label(label_Box, filename_Box)
color_ = pick_EQE_Color(color_Box, file_no)
ls_ = '-'
if label_ == self.ref_label:
ls_ = '--'
if number == 0:
self.axEQE_1.plot(wave, eqe, linewidth=3, linestyle=ls_, label=label_, color=color_)
self.axEQE_2.semilogy(wave, eqe, linewidth=3, linestyle=ls_, label=label_, color=color_)
elif number == 1:
self.axEQE_1.plot(energy, eqe, linewidth=3, linestyle=ls_, label=label_, color=color_)
self.axEQE_2.semilogy(energy, eqe, linewidth=3, linestyle=ls_, label=label_, color=color_)
return True
else:
return False
# -----------------------------------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------------------------------
# Page 3 - Fit EQE (Marcus Theory)
# Page 5 - Fit EQE (MLJ Theory)
# -----------------------------------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------------------------------
# Function to select EQE to fit
[docs]
def pre_fit_EQE(self,
eqe_df,
startE,
stopE,
startFit,
stopFit,
startPlotFit,
stopPlotFit,
filename_Box,
label_Box,
color_Box,
file_no
):
"""Wrapper function to fit EQE spectrum
Parameters
----------
eqe_df : DataFrame, required
Dataframe with EQE data
startE : float, required
Start energy to plot EQE
stopE : float, required
Stop energy to plot EQE
startFit : float, required
Start energy to fit
stopFit : float, required
Stop energy to fit
startPlotFit : float, required
Start energy to plot fit
stopPlotFit : float, required
Stop energy to plot fit
filename_Box : gui object, required
GUI text box with filename to use for plot labeling
label_Box : gui object, required
GUI text box with plot label
color_Box : gui object, required
GUI text box with plot color
file_no : int, required
Number of EQE file to plot
Returns
-------
None
"""
# Change this if you want to plot on the same graph
if self.fit_plot:
self.axFit_1, self.axFit_2 = set_up_EQE_plot() # Sets up plot of energy vs EQE / log(EQE)
self.fit_plot = False
ok_plot_Fit = self.plot_fit_EQE(eqe_df,
startE,
stopE,
startFit,
stopFit,
startPlotFit,
stopPlotFit,
filename_Box,
label_Box,
color_Box,
file_no
)
if ok_plot_Fit:
self.axFit_1.legend()
self.axFit_2.legend()
plt.show()
# -----------------------------------------------------------------------------------------------------------
# Function to fit and plot EQE
[docs]
def plot_fit_EQE(self,
eqe_df,
startE,
stopE,
startFit,
stopFit,
startPlotFit,
stopPlotFit,
filename_Box,
label_Box,
color_Box,
file_no
):
"""Function to fit and plot EQE
Parameters
----------
eqe_df : DataFrame, required
Dataframe with EQE data
startE : float, required
Start energy to plot EQE
stopE : float, required
Stop energy to plot EQE
startFit : float, required
Start energy to fit
stopFit : float, required
Stop energy to fit
startPlotFit : float, required
Start energy to plot fit
stopPlotFit : float, required
Stop energy to plot fit
filename_Box : gui object, required
GUI text box with filename to use for plot labeling
label_Box : gui object, required
GUI text box with plot label
color_Box : gui object, required
GUI text box with plot color
file_no : int, required
Number of EQE file to plot
Returns
-------
Bool :
True if fit is successful,
False otherwise
"""
include_Disorder = False
fit_opticalPeak = False
fit_ok = False
startE = startE.value() # Pick start energy
stopE = stopE.value() # Pick stop energy
startFit = startFit.value() # Pick start fit energy
stopFit = stopFit.value() # Pick stop fit energy
startPlotFit = startPlotFit.value()
stopPlotFit = stopPlotFit.value()
if Fit_is_valid(eqe_df, startE, stopE, startFit, stopFit, file_no): # Check if files are not empty and in range
wave, energy, eqe, log_eqe = compile_EQE(eqe_df, startE, stopE, 1) # Compile EQE file
wave_fit, energy_fit, eqe_fit, log_eqe_fit = compile_EQE(eqe_df, startFit, stopFit, 1) # Compile fit range
label_ = pick_EQE_Label(label_Box, filename_Box)
color_ = pick_EQE_Color(color_Box, file_no)
# Fit EQE (Marcus Theory)
if (str(file_no)).isnumeric():
if file_no == 1: # Data in first row
self.T_CT = self.ui.Temperature_1.value()
guessStart = self.ui.guessStart_1.value()
guessStop = self.ui.guessStop_1.value()
guessStart_sig = self.ui.guessStartSig_1.value()
guessStop_sig = self.ui.guessStopSig_1.value()
if self.ui.disorder_1.isChecked():
include_Disorder = True
if self.ui.OptButton_1.isChecked() and not self.ui.CTButton_1.isChecked(): # Check peak to fit
fit_opticalPeak = True
elif self.ui.OptButton_1.isChecked() and self.ui.CTButton_1.isChecked():
self.logger.error('Please select a valid peak to fit.')
elif not self.ui.OptButton_1.isChecked() and not self.ui.CTButton_1.isChecked():
self.logger.error('Please select a valid peak to fit.')
elif file_no == 2: # Data in second row
self.T_CT = self.ui.Temperature_2.value()
guessStart = self.ui.guessStart_2.value()
guessStop = self.ui.guessStop_2.value()
guessStart_sig = self.ui.guessStartSig_2.value()
guessStop_sig = self.ui.guessStopSig_2.value()
if self.ui.disorder_2.isChecked():
include_Disorder = True
if self.ui.OptButton_2.isChecked() and not self.ui.CTButton_2.isChecked():
fit_opticalPeak = True
elif self.ui.OptButton_2.isChecked() and self.ui.CTButton_2.isChecked():
self.logger.error('Please select a valid peak to fit.')
elif not self.ui.OptButton_2.isChecked() and not self.ui.CTButton_2.isChecked():
self.logger.error('Please select a valid peak to fit.')
# Attempt peak fit:
x_gaussian = linspace(startPlotFit, stopPlotFit, 50)
ECT_guess = np.round(np.arange(guessStart, guessStop + 0.1, 0.05), 3).tolist()
# Sig_guess = np.round(np.arange(guessStart_sig, guessStop_sig + 0.05, 0.05), 3).tolist()
Sig_guess = [round(guessStart_sig, 3), round(guessStop_sig, 3)]
if include_Disorder:
best_vals, covar, p0, r_squared = guess_fit(eqe=eqe_df,
startE=startFit,
stopE=stopFit,
function=self.gaussian_disorder,
guessRange=ECT_guess,
guessRange_sig=Sig_guess,
include_disorder=True,
bounds=True # to use fit model instead of fit function
)
if r_squared > 0:
y_gaussian = [self.gaussian_disorder(value,
best_vals[0],
best_vals[1],
best_vals[2],
best_vals[3]
) for value in x_gaussian]
fit_ok = True
else: # If disorder is not included
best_vals, covar, p0, r_squared = guess_fit(eqe=eqe_df,
startE=startFit,
stopE=stopFit,
function=self.gaussian,
guessRange=ECT_guess,
guessRange_sig=Sig_guess,
include_disorder=False,
bounds=None # to use fit function instead of fit model
)
if r_squared > 0:
y_gaussian = [self.gaussian(value,
best_vals[0],
best_vals[1],
best_vals[2]
) for value in x_gaussian]
fit_ok = True
if fit_ok:
self.logger.info('Fit Results: ')
print("")
print('Initial Guess (eV) : ', p0)
print('-' * 80)
print('Temperature [T] (K) : ', self.T_CT)
print('Oscillator Strength [f] (eV**2) : ', format(best_vals[0], '.6f'), '+/-',
format(math.sqrt(covar[0, 0]), '.6f'))
print('Reorganization Energy [l] (eV) : ', format(best_vals[1], '.6f'), '+/-',
format(math.sqrt(covar[1, 1]), '.6f'))
if fit_opticalPeak:
print('Optical Peak Energy [E_Opt] (eV) : ', format(best_vals[2], '.6f'), '+/-',
format(math.sqrt(covar[2, 2]), '.6f'))
else:
print('CT State Energy [ECT] (eV) : ', format(best_vals[2], '.6f'), '+/-',
format(math.sqrt(covar[2, 2]), '.6f'))
if include_Disorder:
print('Sigma (eV) : ', format(best_vals[3], '.6f'), '+/-',
format(math.sqrt(covar[3, 3]), '.6f'))
W = best_vals[1]*self.T_CT + (best_vals[3]**2)/(2*self.k)
print('Gaussian Variance [W] (eV K) : ', format(W, '.2f'))
print('R2 : ', format(r_squared, '.6f'))
print('-' * 80)
print("")
# Plot EQE data and CT fit
if include_Disorder:
fit_label = 'Gaussian Fit + Disorder'
else:
fit_label = 'Gaussian Fit'
if fit_opticalPeak:
fit_linestyle = 'dotted'
else:
fit_linestyle = '--'
self.axFit_1.plot(energy,
eqe,
linewidth=3,
label=label_,
color=color_
)
plt.draw()
self.axFit_1.plot(x_gaussian,
y_gaussian,
linewidth=2,
label=fit_label,
color='#000000',
linestyle=fit_linestyle
)
self.axFit_2.semilogy(energy,
eqe,
linewidth=3,
label=label_,
color=color_
)
plt.draw()
self.axFit_2.plot(x_gaussian,
y_gaussian,
linewidth=2,
label=fit_label,
color='#000000',
linestyle=fit_linestyle
)
# Save fit data
if self.ui.save_gaussianFit.isChecked():
fit_file = pd.DataFrame()
fit_file['Energy'] = x_gaussian
fit_file['Signal'] = y_gaussian
fit_file['Temperature'] = self.T_CT
fit_file['Oscillator Strength (eV**2)'] = best_vals[0]
fit_file['Reorganization Energy (eV)'] = best_vals[1]
if fit_opticalPeak:
fit_file['Optical Peak Energy (eV)'] = best_vals[2]
else:
fit_file['CT State Energy (eV)'] = best_vals[2]
if include_Disorder:
fit_file['Sigma (eV)'] = best_vals[3]
save_fit_file = filedialog.asksaveasfilename() # User to pick folder & name to save to
save_fit_path, save_fit_filename = os.path.split(save_fit_file)
if len(save_fit_path) != 0: # Check if the user actually selected a path
os.chdir(save_fit_path) # Change the working directory
fit_file.to_csv(save_fit_filename) # Save data to csv
self.logger.info('Saving fit data to: %s' % str(save_fit_file))
os.chdir(self.data_dir) # Change the directory back
return True
else:
self.logger.info('Optimal parameters not found.')
return False
# Fit EQE (MLJ Theory)
elif file_no == 'x1':
self.S_i = self.ui.Huang_Rhys.value()
self.hbarw_i = self.ui.vib_Energy.value()
self.T_x = self.ui.extra_Temperature.value()
guessStart_CT = self.ui.extraGuessStart.value()
guessStop_CT = self.ui.extraGuessStop.value()
guessStart_sig = self.ui.extraGuessStart_sig.value()
guessStop_sig = self.ui.extraGuessStop_sig.value()
if self.ui.extra_static_Disorder.isChecked():
include_Disorder = True
# Attempt peak fit:
x_MLJ_theory = linspace(startPlotFit, stopPlotFit, 50)
ECT_guess = np.round(np.arange(guessStart_CT, guessStop_CT + 0.1, 0.05), 3).tolist()
# Sig_guess = np.round(np.arange(guessStart_sig, guessStop_sig + 0.1, 0.2), 3).tolist()
Sig_guess = [round(guessStart_sig, 3), round(guessStop_sig, 3)]
# Initialize parameters
r_squared = 0
p0 = None
# bounds = None # First try without bounds and initial guesses
bounds = self.bounds_sig # NOTE: Tried without bounds and it didn't work
if include_Disorder:
# NOTE: This code could be replaced by the guess fit function
for ECT in ECT_guess:
for sig in Sig_guess:
try:
best_vals, covar, y_fit, r_squared = fit_function(self.MLJ_gaussian_disorder,
energy_fit,
eqe_fit,
p0=p0,
bounds=bounds,
include_disorder=include_Disorder
)
if r_squared > 0:
y_MLJ_theory = [self.MLJ_gaussian_disorder(value,
best_vals[0],
best_vals[1],
best_vals[2],
best_vals[3]
) for value in x_MLJ_theory]
fit_ok = True
break
else:
raise Exception('Wrong fit determined.')
except:
p0 = [self.f_guess, self.l_guess, round(ECT, 3), round(sig, 3)]
bounds = self.bounds_sig
if r_squared > 0:
break
else:
best_vals, covar, p0, r_squared = guess_fit(eqe=eqe_df,
startE=startFit,
stopE=stopFit,
function=self.MLJ_gaussian,
guessRange=ECT_guess,
guessRange_sig=Sig_guess,
include_disorder=False,
bounds=None # to use fit function instead of fit model
)
if r_squared > 0:
y_MLJ_theory = [self.MLJ_gaussian(value,
best_vals[0],
best_vals[1],
best_vals[2]
) for value in x_MLJ_theory]
fit_ok = True
# Save fit data
if self.ui.save_extraFit.isChecked():
fit_file = pd.DataFrame()
fit_file['Energy'] = x_MLJ_theory
fit_file['Signal'] = y_MLJ_theory
fit_file['Temperature'] = self.T_x
fit_file['Oscillator Strength (eV**2)'] = best_vals[0]
fit_file['Reorganization Energy (eV)'] = best_vals[1]
fit_file['CT State Energy (eV)'] = best_vals[2]
if include_Disorder:
fit_file['Sigma (eV)'] = best_vals[3]
save_fit_file = filedialog.asksaveasfilename() # User to pick folder & name to save to
save_fit_path, save_fit_filename = os.path.split(save_fit_file)
if len(save_fit_path) != 0: # Check if the user actually selected a path
os.chdir(save_fit_path) # Change the working directory
fit_file.to_csv(save_fit_filename) # Save data to csv
self.logger.info('Saving fit data to: %s' % str(save_fit_file))
os.chdir(self.data_dir) # Change the directory back
if fit_ok:
self.logger.info('Fit Results: ')
print("")
print('Initial Guess (eV) : ', p0)
print('-' * 80)
print('Temperature [T] (K) : ', self.T_x)
print('Oscillator Strength [f] (eV**2) : ', format(best_vals[0], '.6f'), '+/-',
format(math.sqrt(covar[0, 0]), '.6f'))
print('Reorganization Energy [l] (eV) : ', format(best_vals[1], '.6f'), '+/-',
format(math.sqrt(covar[1, 1]), '.6f'))
print('CT State Energy [ECT] (eV) : ', format(best_vals[2], '.6f'), '+/-',
format(math.sqrt(covar[2, 2]), '.6f'))
if include_Disorder:
print('Sigma (eV) : ', format(best_vals[3], '.6f'), '+/-',
format(math.sqrt(covar[3, 3]), '.6f'))
W = best_vals[1]*self.T_x + (best_vals[3]**2)/(2*self.k)
print('Gaussian Variance [W] (eV K) : ', format(W, '.2f'))
print('R2 : ', format(r_squared, '.6f'))
print('-' * 80)
print("")
# Plot EQE data and CT fit
self.axFit_1.plot(energy,
eqe,
linewidth=3,
label=label_,
color=color_
)
plt.draw()
if include_Disorder:
self.axFit_1.plot(x_MLJ_theory,
y_MLJ_theory,
linewidth=2,
label='MLJ Fit + Disorder',
color='#000000',
linestyle='--'
)
else:
self.axFit_1.plot(x_MLJ_theory,
y_MLJ_theory,
linewidth=2,
label='MLJ Fit',
color='#000000',
linestyle='--'
)
plt.draw()
self.axFit_2.semilogy(energy,
eqe,
linewidth=3,
label=label_,
color=color_
)
plt.draw()
if include_Disorder:
self.axFit_2.plot(x_MLJ_theory,
y_MLJ_theory,
linewidth=2,
label='MLJ Fit + Disorder',
color='#000000',
linestyle='--'
)
else:
self.axFit_2.plot(x_MLJ_theory,
y_MLJ_theory,
linewidth=2,
label='MLJ Fit',
color='#000000',
linestyle='--'
)
plt.draw()
return True
else:
self.logger.info('Optimal parameters not found.')
return False
else:
return False
# -----------------------------------------------------------------------------------------------------------
# Function to generate heat map of fitting values
[docs]
def heatMap(self,
eqe_df,
startStartE,
startStopE,
stopStartE,
stopStopE,
filename_Box,
label_Box,
color_Box,
file_no
):
"""Function to generate heatmap for fit values
Parameters
----------
eqe_df : DataFrame, required
Dataframe with EQE data
startStartE : float, required
Start of the start fit energy range
startStopE : float, required
Start of the stop fit energy range
stopStartE : float, required
Stop of the start fit energy range
stopStopE : float, required
Stop of the stop fit energy range
filename_Box : gui object, required
GUI text box with filename to use for plot labeling
label_Box : gui object, required
GUI text box with plot label
color_Box : gui object, required
GUI text box with plot color
file_no : int, required
Number of EQE file to plot
Returns
-------
None
"""
include_Disorder = False
fit_opticalPeak = False
startStartE = startStartE.value()
startStopE = startStopE.value()
stopStartE = stopStartE.value()
stopStopE = stopStopE.value()
startStart_ok = StartStop_is_valid(startStartE, startStopE) # Check that start energy is lower than stop energy
# startStop_ok = StartStop_is_valid(startStopE, stopStartE)
stopStop_ok = StartStop_is_valid(stopStartE, stopStopE)
if startStart_ok and stopStop_ok: # If all operations are valid, proceed with heat map calculations
startEnergies = [] # Create empty lists for start and stop energies
stopEnergies = []
start_df = [] # Create empty lists for fit data collection
stop_df = []
f_df = []
l_df = []
Ect_df = []
R_df = []
sig_df = []
x = float(startStartE)
y = float(stopStartE)
while x <= float(startStopE): # As long as start range start value is smaller than start range stop value
startEnergies.append(round(x, 3)) # Round to three digits
x += 0.005 # NOTE: Adjust this number to change resolution
while y <= float(stopStopE):
stopEnergies.append(round(y, 3))
y += 0.005
# Fit EQE (Marcus Theory)
if (str(file_no)).isnumeric():
if file_no == 1: # Range 1
self.T_CT = self.ui.Temperature_1.value()
guessStart = self.ui.guessStart_1.value()
guessStop = self.ui.guessStop_1.value()
guessStart_sig = self.ui.guessStartSig_1.value()
guessStop_sig = self.ui.guessStopSig_1.value()
if self.ui.disorder_1.isChecked():
include_Disorder = True
if self.ui.OptButton_1.isChecked() and not self.ui.CTButton_1.isChecked(): # Check peak to fit
fit_opticalPeak = True
elif self.ui.OptButton_1.isChecked() and self.ui.CTButton_1.isChecked():
self.logger.error('Please select a valid peak to fit.')
elif not self.ui.OptButton_1.isChecked() and not self.ui.CTButton_1.isChecked():
self.logger.error('Please select a valid peak to fit.')
elif file_no == 2: # Range 2
self.T_CT = self.ui.Temperature_2.value()
guessStart = self.ui.guessStart_2.value()
guessStop = self.ui.guessStop_2.value()
guessStart_sig = self.ui.guessStartSig_2.value()
guessStop_sig = self.ui.guessStopSig_2.value()
if self.ui.disorder_2.isChecked():
include_Disorder = True
if self.ui.OptButton_2.isChecked() and not self.ui.CTButton_2.isChecked():
fit_opticalPeak = True
elif self.ui.OptButton_2.isChecked() and self.ui.CTButton_2.isChecked():
self.logger.error('Please select a valid peak to fit.')
elif not self.ui.OptButton_2.isChecked() and not self.ui.CTButton_2.isChecked():
self.logger.error('Please select a valid peak to fit.')
# Attempt peak fit:
ECT_guess = np.round(np.arange(guessStart, guessStop + 0.2, 0.2), 3).tolist() # Increased step
# Sig_guess = np.round(np.arange(guessStart_sig, guessStop_sig + 0.05, 0.05), 3).tolist()
Sig_guess = [round(guessStart_sig, 3), round(guessStop_sig, 3)]
for start in tqdm(startEnergies): # Iterate through start energies
for stop in tqdm(stopEnergies): # Iterate through stop energies
if include_Disorder:
best_vals, covar, p0, r_squared = guess_fit(eqe=eqe_df,
startE=start,
stopE=stop,
function=self.gaussian_disorder,
guessRange=ECT_guess,
guessRange_sig=Sig_guess,
include_disorder=True,
bounds=True # to use fit model
)
if r_squared > 0:
start_df.append(start)
stop_df.append(stop)
f_df.append(best_vals[0])
l_df.append(best_vals[1])
Ect_df.append(best_vals[2])
sig_df.append(best_vals[3])
R_df.append(r_squared)
else:
self.logger.info('Optimal parameters not found.')
else:
best_vals, covar, p0, r_squared = guess_fit(eqe=eqe_df,
startE=start,
stopE=stop,
function=self.gaussian,
guessRange=ECT_guess,
guessRange_sig=Sig_guess,
include_disorder=False,
bounds=None # to use fit function
)
if r_squared > 0:
start_df.append(start)
stop_df.append(stop)
f_df.append(best_vals[0])
l_df.append(best_vals[1])
Ect_df.append(best_vals[2])
R_df.append(r_squared)
else:
self.logger.info('Optimal parameters not found.')
# Fit EQE (MLJ Theory)
elif file_no == 'x1':
self.S_i = self.ui.Huang_Rhys.value()
self.hbarw_i = self.ui.vib_Energy.value()
self.T_x = self.ui.extra_Temperature.value()
guessStart = self.ui.extraGuessStart.value()
guessStop = self.ui.extraGuessStop.value()
guessStart_sig = self.ui.extraGuessStart_sig.value()
guessStop_sig = self.ui.extraGuessStop_sig.value()
if self.ui.extra_static_Disorder.isChecked():
include_Disorder = True
# Attempt peak fit:
ECT_guess = np.round(np.arange(guessStart, guessStop + 0.2, 0.2), 3).tolist() # Increased step
# Sig_guess = np.round(np.arange(guessStart_sig, guessStop_sig + 0.05, 0.05), 3).tolist()
Sig_guess = [round(guessStart_sig, 3), round(guessStop_sig, 3)]
for start in tqdm(startEnergies): # Iterate through start energies
for stop in tqdm(stopEnergies): # Iterate through stop energies
wave_fit, energy_fit, eqe_fit, log_eqe_fit = compile_EQE(eqe_df,
start,
stop,
1)
if include_Disorder:
# NOTE: This code could be replaced by the guess fit function
for ECT in ECT_guess:
for sig in Sig_guess:
r_squared = 0
try:
best_vals, covar, y_fit, r_squared = fit_function(self.MLJ_gaussian_disorder,
energy_fit,
eqe_fit,
p0=p0,
bounds=bounds,
include_disorder=include_Disorder
)
except:
p0 = [self.f_guess, self.l_guess, round(ECT, 3), round(sig, 3)]
bounds = self.bounds_sig
if ECT == ECT_guess[-1]:
self.logger.info('Optimal parameters not found.')
if r_squared > 0:
start_df.append(start)
stop_df.append(stop)
f_df.append(best_vals[0])
l_df.append(best_vals[1])
Ect_df.append(best_vals[2])
sig_df.append(best_vals[3])
R_df.append(r_squared)
break
if r_squared > 0: # To break the second loop
break
else:
best_vals, covar, p0, r_squared = guess_fit(eqe=eqe_df,
startE=start,
stopE=stop,
function=self.MLJ_gaussian,
guessRange=ECT_guess,
guessRange_sig=Sig_guess,
include_disorder=False,
bounds=None # to use fit function
)
if r_squared > 0:
start_df.append(start)
stop_df.append(stop)
f_df.append(best_vals[0])
l_df.append(best_vals[1])
Ect_df.append(best_vals[2])
R_df.append(r_squared)
else:
self.logger.info('Optimal parameters not found.')
if len(R_df) != 0: # Check that there are results to plot
if include_Disorder:
parameter_df = pd.DataFrame(
{'Start': start_df,
'Stop': stop_df,
'f': f_df,
'l': l_df,
'Ect': Ect_df,
'R_Squared': R_df,
'Sig': sig_df})
else:
parameter_df = pd.DataFrame(
{'Start': start_df,
'Stop': stop_df,
'f': f_df,
'l': l_df,
'Ect': Ect_df,
'R_Squared': R_df})
max_index = parameter_df[parameter_df['R_Squared'] == max(parameter_df['R_Squared'])].index.values[0]
self.logger.info('Fit Results: ')
print("")
print('-' * 80)
if file_no == 'x1':
print('Temperature [T] (K) : ', self.T_x)
else:
print('Temperature [T] (K) : ', self.T_CT)
print('Average Oscillator Strength [f] (eV**2) : ', format(parameter_df['f'].mean(), '.6f'), '+/-',
format(parameter_df['f'].std(), '.6f')) # Determine the average value and standard deviation
print('Average Reorganization Energy [l] (eV) : ', format(parameter_df['l'].mean(), '.6f'), '+/-',
format(parameter_df['l'].std(), '.6f'))
if fit_opticalPeak:
print('Average Optical Peak Energy [E_Opt] (eV) : ', format(parameter_df['Ect'].mean(), '.6f'), '+/-',
format(parameter_df['Ect'].std(), '.6f'))
else:
print('Average CT State Energy [ECT] (eV) : ', format(parameter_df['Ect'].mean(), '.6f'), '+/-',
format(parameter_df['Ect'].std(), '.6f'))
if include_Disorder:
print('Average Sigma [Sig] (eV) : ', format(parameter_df['Sig'].mean(), '.6f'), '+/-',
format(parameter_df['Sig'].std(), '.6f'))
if file_no == 'x1':
Average_W = parameter_df['l'].mean() * self.T_x + (parameter_df['Sig'].mean() ** 2) / (
2 * self.k)
else:
Average_W = parameter_df['l'].mean() * self.T_CT + (parameter_df['Sig'].mean() ** 2) / (
2 * self.k)
print('Average Gaussian Variance [W] (eV K) : ', format(Average_W, '.2f'))
print('Average R2 : ', format(parameter_df['R_Squared'].mean(), '.6f'), '+/-',
format(parameter_df['R_Squared'].std(), '.6f'))
print('-' * 80)
if max(parameter_df['R_Squared']) == 1.0:
print('Max R_squared : ', format(max(parameter_df['R_Squared']), '.6f'))
print('Start Energies (eV) : ',
np.array(parameter_df['Start'][parameter_df['R_Squared'] == 1.0]).tolist())
print('Stop Energies (eV) : ',
np.array(parameter_df['Stop'][parameter_df['R_Squared'] == 1.0]).tolist())
print('Average Oscillator Strength [f] (eV**2) : ',
format(parameter_df['f'][parameter_df['R_Squared'] == 1.0].mean(), '.6f'), '+/-',
format(parameter_df['f'][parameter_df['R_Squared'] == 1.0].std(), '.6f'))
print('Average Reorganization Energy [l] (eV) : ',
format(parameter_df['l'][parameter_df['R_Squared'] == 1.0].mean(), '.6f'), '+/-',
format(parameter_df['l'][parameter_df['R_Squared'] == 1.0].std(), '.6f'))
if fit_opticalPeak:
print('Average Optical Peak Energy [E_Opt] (eV) : ',
format(parameter_df['Ect'][parameter_df['R_Squared'] == 1.0].mean(), '.6f'), '+/-',
format(parameter_df['Ect'][parameter_df['R_Squared'] == 1.0].std(), '.6f'))
else:
print('Average CT State Energy [ECT] (eV) : ',
format(parameter_df['Ect'][parameter_df['R_Squared'] == 1.0].mean(), '.6f'), '+/-',
format(parameter_df['Ect'][parameter_df['R_Squared'] == 1.0].std(), '.6f'))
if include_Disorder:
print('Average Sigma [Sig] (eV) : ',
format(parameter_df['Sig'][parameter_df['R_Squared'] == 1.0].mean(), '.6f'), '+/-',
format(parameter_df['Sig'][parameter_df['R_Squared'] == 1.0].std(), '.6f'))
if file_no == 'x1':
Average_W = parameter_df['l'][parameter_df['R_Squared'] == 1.0].mean() * self.T_x + (
parameter_df['Sig'][parameter_df['R_Squared'] == 1.0].mean() ** 2) / (2 * self.k)
else:
Average_W = parameter_df['l'][parameter_df['R_Squared'] == 1.0].mean() * self.T_CT + (
parameter_df['Sig'][parameter_df['R_Squared'] == 1.0].mean() ** 2) / (2 * self.k)
print('Average Gaussian Variance [W] (eV K) : ', format(Average_W, '.2f'))
else:
print('Max R_squared : ', format(max(parameter_df['R_Squared']), '.6f'))
print('Start Energy (eV) : ', parameter_df['Start'][max_index])
print('Stop Energy (eV) : ', parameter_df['Stop'][max_index])
print('-' * 80)
print("")
# Pivot dataFrame: x-value = Stop, y-value = Start, value = f
f_df = parameter_df.pivot('Stop', 'Start', 'f')
l_df = parameter_df.pivot('Stop', 'Start', 'l')
Ect_df = parameter_df.pivot('Stop', 'Start', 'Ect')
R_df = parameter_df.pivot('Stop', 'Start', 'R_Squared')
plt.ion()
plt.figure(figsize=(11, 9)) # Create a new figure
self.heatmap_1 = seaborn.heatmap(f_df, xticklabels=3, yticklabels=3) # Create the heat map
plt.xlabel('Initial Energy Value (eV)', fontsize=17, fontweight='medium')
plt.ylabel('Final Energy Value (eV)', fontsize=17, fontweight='medium')
plt.title('Oscillator Strength ($eV^2$)', fontsize=17, fontweight='medium')
cbar = self.heatmap_1.collections[0].colorbar
cbar.ax.tick_params(labelsize=15)
plt.yticks(rotation=360)
plt.xticks(rotation=90)
plt.tick_params(labelsize=15, direction='in', axis='both', which='major', length=8, width=2)
# plt.tick_params(labelsize=15, direction='in', axis='both', which='minor', length=4, width=2)
plt.show()
plt.ion()
plt.figure(figsize=(11, 9))
self.heatmap_2 = seaborn.heatmap(l_df, xticklabels=3, yticklabels=3)
plt.xlabel('Initial Energy Value (eV)', fontsize=17, fontweight='medium')
plt.ylabel('Final Energy Value (eV)', fontsize=17, fontweight='medium')
plt.title('Reorganization Energy (eV)', fontsize=17, fontweight='medium')
cbar = self.heatmap_2.collections[0].colorbar
cbar.ax.tick_params(labelsize=15)
plt.yticks(rotation=360)
plt.xticks(rotation=90)
plt.tick_params(labelsize=15, direction='in', axis='both', which='major', length=8, width=2)
plt.show()
plt.ion()
plt.figure(figsize=(11, 9))
self.heatmap_3 = seaborn.heatmap(Ect_df, xticklabels=3, yticklabels=3)
plt.xlabel('Initial Energy Value (eV)', fontsize=17, fontweight='medium')
plt.ylabel('Final Energy Value (eV)', fontsize=17, fontweight='medium')
if fit_opticalPeak:
plt.title('Optical Peak Energy (eV)', fontsize=17, fontweight='medium')
else:
plt.title('CT State Energy (eV)', fontsize=17, fontweight='medium')
cbar = self.heatmap_3.collections[0].colorbar
cbar.ax.tick_params(labelsize=15)
plt.yticks(rotation=360)
plt.xticks(rotation=90)
plt.tick_params(labelsize=15, direction='in', axis='both', which='major', length=8, width=2)
plt.show()
plt.ion()
plt.figure(figsize=(11, 9))
self.heatmap_4 = seaborn.heatmap(R_df, xticklabels=3, yticklabels=3)
plt.xlabel('Initial Energy Value (eV)', fontsize=17, fontweight='medium')
plt.ylabel('Final Energy Value (eV)', fontsize=17, fontweight='medium')
plt.title('$\mathregular{R^{2}}$', fontsize=17, fontweight='medium')
cbar = self.heatmap_4.collections[0].colorbar
cbar.ax.tick_params(labelsize=15)
plt.yticks(rotation=360)
plt.xticks(rotation=90)
plt.tick_params(labelsize=15, direction='in', axis='both', which='major', length=8, width=2)
plt.show()
if include_Disorder:
Sig_df = parameter_df.pivot('Stop', 'Start', 'Sig')
plt.ion()
plt.figure(figsize=(11, 9))
self.heatmap_5 = seaborn.heatmap(Sig_df, xticklabels=3, yticklabels=3)
plt.xlabel('Initial Energy Value (eV)', fontsize=17, fontweight='medium')
plt.ylabel('Final Energy Value (eV)', fontsize=17, fontweight='medium')
plt.title('Sigma (eV)', fontsize=17, fontweight='medium')
cbar = self.heatmap_5.collections[0].colorbar
cbar.ax.tick_params(labelsize=15)
plt.yticks(rotation=360)
plt.xticks(rotation=90)
plt.tick_params(labelsize=15, direction='in', axis='both', which='major', length=8, width=2)
plt.show()
else:
self.logger.info('No fits determined.')
# -----------------------------------------------------------------------------------------------------------
# Gaussian function
[docs]
def gaussian(self, E, f, l, Ect):
"""Marcus theory
Parameters
----------
E : list, required
List of energy values
f : float, required
Oscillator strength
l : float, required
Reorganization energy
Ect : float, required
CT state energy
Returns
-------
EQE : float
EQE value
"""
return (f / (E * math.sqrt(4 * math.pi * l * self.T_CT * self.k))) * exp(
-(Ect + l - E) ** 2 / (4 * l * self.k * self.T_CT))
[docs]
def gaussian_disorder(self, E, f, l, Ect, sig):
"""Marcus theory including disorder
Parameters
----------
E : list, required
List of energy values
f : float, required
Oscillator strength
l : float, required
Reorganization energy
Ect : float, required
CT state energy
sig : float, required
Gaussian disorder
Returns
-------
EQE : float
EQE value
"""
return (f / (E * math.sqrt(2 * math.pi * (2 * l * self.T_CT * self.k + sig ** 2)))) * exp(
-((Ect - (sig ** 2 / (2 * self.k * self.T_CT)) + l + (sig ** 2 / (2 * self.k * self.T_CT)) - E) ** 2 / (
4 * l * self.k * self.T_CT + 2 * sig ** 2)))
# -----------------------------------------------------------------------------------------------------------
# MLJ function
[docs]
def MLJ_gaussian(self, E, f, l, Ect):
"""Marcus-Levich-Jortner theory
Parameters
----------
E : list, required
List of energy values
f : float, required
Oscillator strength
l : float, required
Reorganization energy
Ect : float, required
CT state energy
Returns
-------
EQE : float
EQE value
"""
EQE = 0
for n in range(0, 6): # TODO: Check line breaks
EQE_n = (f / (E * math.sqrt(4 * math.pi * l * self.T_x * self.k))) \
* (math.exp(-self.S_i) * self.S_i ** n / math.factorial(n)) \
* exp(-(Ect + l - E + n * self.hbarw_i) ** 2 \
/ (4 * l * self.k * self.T_x))
EQE += EQE_n
return EQE
# MLJ function including disorder
[docs]
def MLJ_gaussian_disorder(self, E, f, l, Ect, sig):
"""Marcus-Levich-Jortner theory including disorder
Parameters
----------
E : list, required
List of energy values
f : float, required
Oscillator strength
l : float, required
Reorganization energy
Ect : float, required
CT state energy
sig : float, required
Gaussian disorder
Returns
-------
EQE : float
EQE value
"""
EQE = 0
for n in range(0, 6): # TODO: Check line breaks
EQE_n = (f / (E * math.sqrt(2 * math.pi * (2 * l * self.T_x * self.k + sig ** 2))) \
* (math.exp(-self.S_i) * self.S_i ** n / math.factorial(n)) \
* exp(-(Ect + l - E + n * self.hbarw_i) ** 2 \
/ (4 * l * self.k * self.T_x + 2 * sig ** 2)))
EQE += EQE_n
return EQE
# -----------------------------------------------------------------------------------------------------------
# Page 6 - Fit EL and EQE
# -----------------------------------------------------------------------------------------------------------
# Function to scale and reduce EL and EQE
# TODO: Check and update EL components
[docs]
def pre_plot_EL_EQE(self,
data_df,
startE,
stopE,
data_no,
fit=False
):
"""Wrapper function to plot and fit EQE and EL data
Parameters
----------
data_df : dataFrame, required
EQE or EL data
startE : float, required
Start energy to plot
stopE : float, required
Stop energy to plot
data_no : int, required
Number of data file to plot and fit
fit : bool, required
Boolean value specifying whether to perform fit
Returns
-------
None
"""
self.T_EL = self.ui.EL_Temperature.value()
startE = startE.value()
stopE = stopE.value()
if self.do_plot_EL:
self.axEL_1, self.axEL_2 = set_up_EL_plot()
self.do_plot_EL = False
if data_no < 2: # EL data
Energy = []
scaleFactor = self.ui.scalePlot.value()
if len(data_df) != 0: # Check that file is non-empty
for y in range(len(data_df['Wavelength'])): # Calculate energy values
Energy_val = (self.h * self.c) / (data_df['Wavelength'][y] * math.pow(10, -9) * self.q)
Energy.append(Energy_val)
data_df['Energy'] = Energy
if Data_is_valid(data_df, startE, stopE) and StartStop_is_valid(startE, stopE):
EL_wave, EL_energy, EL_signal = compile_EL(data_df, startE, stopE, 1)
red_EL = [EL_signal[x] / (EL_energy[x])
for x in range(len(EL_signal))] # Divide by energy to reduce (checked in Benduhn thesis)
red_EL_scaled = [red_EL[x] / scaleFactor
for x in range(len(red_EL))]
if data_no == 0: # EL Data
if not fit:
label_ = pick_EQE_Label(self.ui.textBox_EL2, self.ui.textBox_EL1)
# color_ = pick_EQE_Color(self.ui.textBox_EL3, 100) # not currently used
color_ = '#1f77b4' # Blue
plot(self.axEL_1,
self.axEL_2,
EL_energy,
red_EL_scaled,
label_,
color_
)
elif fit:
self.fit_EL_EQE(EL_energy,
red_EL_scaled,
self.ui.startFit_EL1,
self.ui.stopFit_EL1,
0)
elif data_no == 1: # Abs Data
bb_dict = bb_spectrum(EL_energy, self.T_EL)
# TODO: Old code to be removed?
# EL_abs = [EL_signal[x] / bb_dict[EL_energy[x]] for x in range(len(EL_energy))]
# red_EL_abs_scaled = [EL_abs[x] / (scaleFactor * EL_energy[x]) for x in range(len(EL_abs))]
scaleFactor_calc = self.ui.scalePlot_calc.value()
EL_abs_scaled = [EL_signal[x] / (scaleFactor * bb_dict[EL_energy[x]])
for x in range(len(EL_energy))]
EL_abs_scaled_test = [EL_signal[x] / (scaleFactor_calc * bb_dict[EL_energy[x]])
for x in range(len(EL_energy))] # test which scale factor is correct
red_EL_abs_scaled = [EL_energy[x] * EL_signal[x] / (scaleFactor * bb_dict[EL_energy[x]])
for x in range(len(EL_energy))]
red_EL_abs_scaled_test = [
EL_energy[x] * EL_signal[x] / (scaleFactor_calc * bb_dict[EL_energy[x]])
for x in range(len(EL_energy))] # test which scale factor is correct
if not fit:
# label_ = pick_EQE_Label(self.ui.textBox_EL2, self.ui.textBox_EL1)
# color_ = pick_EQE_Color(self.ui.textBox_EL3, 100) # not currently used
label_ = '$\mathregular{Red. EQE_{cal}}$'
color_ = '#ff7716' # Orange
plot(self.axEL_1,
self.axEL_2,
EL_energy,
red_EL_abs_scaled_test,
label_=label_,
color_=color_
)
elif fit:
self.fit_EL_EQE(EL_energy,
red_EL_abs_scaled_test,
self.ui.startFit_EL2,
self.ui.stopFit_EL2,
1)
else:
self.logger.error('Please select a valid EL file.')
elif data_no == 2: # EQE Data
if Data_is_valid(data_df, startE, stopE) and StartStop_is_valid(startE, stopE):
self.Red_EQE_meas = pd.DataFrame() # For determining the intersect between abs and emission
EQE_wave, EQE_energy, EQE, EQE_log = compile_EQE(data_df, startE, stopE, 1)
red_EQE = [EQE[x] * EQE_energy[x] for x in
range(len(EQE))] # Multiplication confirmed in Benduhn thesis
if not fit:
label_ = pick_EQE_Label(self.ui.textBox_EL5, self.ui.textBox_EL4)
# color_ = pick_EQE_Color(self.ui.textBox_EL6, 100)
color_ = '#000000' # Black
plot(self.axEL_1,
self.axEL_2,
EQE_energy,
red_EQE, label_, color_
)
elif fit:
self.fit_EL_EQE(EQE_energy,
red_EQE,
self.ui.startFit_EQE,
self.ui.stopFit_EQE,
1)
# -----------------------------------------------------------------------------------------------------------
# Function to fit reduced EL and EQE
# TODO: Check and update EL components
# TODO: Add sigma as a fit parameter
# TODO: Add guess fit function
# TODO: Implement save fit function
[docs]
def fit_EL_EQE(self,
energy,
y,
startE,
stopE,
data_no
):
"""Function to fit EL and EQE data
Parameters
----------
energy : list, required
List of energy values to fit
y : list, required
List of data values to fit
startE : float, required
Start energy to fit
stopE : float, required
Stop energy to fit
data_no : int, required
Number of data file to plot and fit
Returns
-------
None
"""
include_Disorder = False
startFit = startE.value()
stopFit = stopE.value()
df = pd.DataFrame()
df['Energy'] = energy
if Data_is_valid(df, startFit, stopFit):
energy_fit, y_fit = compile_Data(energy, y, startFit, stopFit)
diff = stopFit - startFit # Find difference between start and stop fit energy
x_gaussian = linspace(startFit, stopFit + 0.5 * diff, 50) # Create x values to plot fit
if self.ui.static_Disorder_EL.isChecked():
include_Disorder = True
self.sig_EL = self.ui.EL_Disorder.value()
try:
if self.ui.Gaussian_EL_EQE.isChecked(): # Marcus Theory Fitting
if data_no == 0: # EL Data
# TODO: Add sigma as a fit parameter
if include_Disorder: # Include Disorder in Fit
# y_fit_smooth = savgol_filter(y_fit, 51, 3) # NOTE: In case you need to smooth the data
# y_fit_smooth = [x for x in y_fit_smooth]
# log_y_fit = [math.log(x) for x in y_fit]
# plot(self.axEL_1, self.axEL_2, energy_fit, y_fit_smooth, 'Smoothed Data', '#330000')
best_vals, covar = curve_fit(self.gaussian_EL_disorder,
energy_fit,
y_fit,
p0=[self.f_guess, self.l_guess, self.ui.EL_CT_State.value()]
)
y_gaussian = [self.gaussian_EL_disorder(value,
best_vals[0],
best_vals[1],
best_vals[2]
) for value in x_gaussian]
else: # Without disorder
best_vals, covar = curve_fit(self.gaussian_EL,
energy_fit,
y_fit,
p0=[self.f_guess, self.l_guess, self.ui.EL_CT_State.value()]
)
y_gaussian = [self.gaussian_EL(value,
best_vals[0],
best_vals[1],
best_vals[2]
) for value in x_gaussian]
elif data_no == 1: # EQE / Abs Data
x_gaussian = linspace(startFit, stopFit + 2 * diff, 50)
# TODO: Add sigma as a fit parameter
if include_Disorder:
best_vals, covar = curve_fit(self.gaussian_EQE_disorder,
energy_fit, y_fit,
p0=[self.f_guess, self.l_guess, self.ui.EL_CT_State.value()])
y_gaussian = [self.gaussian_EQE_disorder(value,
best_vals[0],
best_vals[1],
best_vals[2]
) for value in x_gaussian]
else:
best_vals, covar = curve_fit(self.gaussian_EQE,
energy_fit,
y_fit,
p0=[self.f_guess, self.l_guess, self.ui.EL_CT_State.value()]
)
y_gaussian = [self.gaussian_EQE(value,
best_vals[0],
best_vals[1],
best_vals[2]
) for value in x_gaussian]
self.logger.info('Fit Results: ')
print("")
print('-' * 80)
print('Temperature [T] (K): ', self.T_EL)
print('Oscillator Strength [f] (eV**2) : ', format(best_vals[0], '.6f'), '+/-',
format(math.sqrt(covar[0, 0]), '.6f'))
print('Reorganization Energy [l] (eV) : ', format(best_vals[1], '.6f'), '+/-',
format(math.sqrt(covar[1, 1]), '.6f'))
print('CT State Energy [ECT] (eV) : ', format(best_vals[2], '.6f'), '+/-',
format(math.sqrt(covar[2, 2]), '.6f'))
print('-' * 80)
print("")
if include_Disorder:
self.axEL_1.plot(x_gaussian,
y_gaussian,
linewidth=2,
label='Gaussian Fit + Disorder',
color='#000000',
linestyle='--'
)
self.axEL_2.plot(x_gaussian,
y_gaussian,
linewidth=2,
label='Gaussian Fit + Disorder',
color='#000000',
linestyle='--'
)
else:
self.axEL_1.plot(x_gaussian,
y_gaussian,
linewidth=2,
label='Gaussian Fit',
color='#000000',
linestyle='--'
)
self.axEL_2.plot(x_gaussian,
y_gaussian,
linewidth=2,
label='Gaussian Fit',
color='#000000',
linestyle='--'
)
plt.legend()
plt.draw()
elif self.ui.MLJ_Gaussian_EL_EQE.isChecked(): # MLJ Theory Fitting
self.S_i_EL = self.ui.EL_Huang_Rhys.value()
self.hbarw_i_EL = self.ui.EL_vib_Energy.value()
x_gaussian = linspace(1.18, stopFit + 0.5 * diff, 50)
if data_no == 0: # EL Data
# TODO: Add sigma as a fit parameter
if include_Disorder:
best_vals, covar = curve_fit(self.MLJ_gaussian_EL_disorder,
energy_fit,
y_fit,
p0=[self.f_guess, self.l_guess, self.ui.EL_CT_State.value()]
)
y_gaussian = [self.MLJ_gaussian_EL_disorder(value,
best_vals[0],
best_vals[1],
best_vals[2]
) for value in x_gaussian]
else:
best_vals, covar = curve_fit(self.MLJ_gaussian_EL,
energy_fit,
y_fit,
p0=[self.f_guess, self.l_guess, self.ui.EL_CT_State.value()]
)
y_gaussian = [self.MLJ_gaussian_EL(value,
best_vals[0],
best_vals[1],
best_vals[2]
) for value in x_gaussian]
elif data_no == 1: # EQE / Abs Data
x_gaussian = linspace(startFit, stopFit + 2 * diff, 50)
# TODO: Add sigma as a fit parameter
if include_Disorder:
best_vals, covar = curve_fit(self.MLJ_gaussian_EQE_disorder,
energy_fit,
y_fit,
p0=[self.f_guess, self.l_guess, self.ui.EL_CT_State.value()]
)
y_gaussian = [self.MLJ_gaussian_EQE_disorder(value,
best_vals[0],
best_vals[1],
best_vals[2]
) for value in x_gaussian]
else:
best_vals, covar = curve_fit(self.MLJ_gaussian_EQE,
energy_fit,
y_fit,
p0=[self.f_guess, self.l_guess, self.ui.EL_CT_State.value()]
)
y_gaussian = [self.MLJ_gaussian_EQE(value,
best_vals[0],
best_vals[1],
best_vals[2]
) for value in x_gaussian]
self.logger.info('Fit Results: ')
print("")
print('-' * 80)
print('Temperature [T] (K): ', self.T_EL)
print('Oscillator Strength [f] (eV**2) : ', format(best_vals[0], '.6f'), '+/-',
format(math.sqrt(covar[0, 0]), '.6f'))
print('Reorganization Energy [l] (eV) : ', format(best_vals[1], '.6f'), '+/-',
format(math.sqrt(covar[1, 1]), '.6f'))
print('CT State Energy [ECT] (eV) : ', format(best_vals[2], '.6f'), '+/-',
format(math.sqrt(covar[2, 2]), '.6f'))
print('-' * 80)
print("")
if include_Disorder:
self.axEL_1.plot(x_gaussian,
y_gaussian,
linewidth=2,
label='MLJ Fit + Disorder',
color='#000000',
linestyle='--'
)
self.axEL_2.plot(x_gaussian,
y_gaussian,
linewidth=2,
label='MLJ Fit + Disorder',
color='#000000',
linestyle='--'
)
else:
self.axEL_1.plot(x_gaussian,
y_gaussian,
linewidth=2,
label='MLJ Fit',
color='#000000',
linestyle='--'
)
self.axEL_2.plot(x_gaussian,
y_gaussian,
linewidth=2,
label='MLJ Fit',
color='#000000',
linestyle='--'
)
plt.legend()
plt.draw()
except:
self.logger.info('Optimal parameters not found.')
# -----------------------------------------------------------------------------------------------------------
# Gaussian function for reduced EL
[docs]
def gaussian_EL(self, E, f, l, Ect):
"""Marcus theory for reduced EL data
Parameters
----------
E : list, required
List of energy values
f : float, required
Oscillator strength
l : float, required
Reorganization energy
Ect : float, required
CT state energy
Returns
-------
EL : float
Reduced EL value
"""
return (f / (math.sqrt(4 * math.pi * l * self.T_EL * self.k))) * exp(
-(Ect - l - E) ** 2 / (4 * l * self.k * self.T_EL))
# Gaussian function for reduced EL including disorder
# TODO: Add sigma as a fit parameter
[docs]
def gaussian_EL_disorder(self, E, f, l, Ect):
"""Marcus theory including disorder for reduced EL data
Parameters
----------
E : list, required
List of energy values
f : float, required
Oscillator strength
l : float, required
Reorganization energy
Ect : float, required
CT state energy
Returns
-------
EL : float
Reduced EL value
"""
return (f / (math.sqrt(4 * math.pi * l * self.T_EL * self.k + 2 * self.sig_EL ** 2))) * exp(
-(Ect - l - E) ** 2 / (4 * l * self.k * self.T_EL + 2 * self.sig_EL ** 2))
# -----------------------------------------------------------------------------------------------------------
# MLJ function for reduced EL
[docs]
def MLJ_gaussian_EL(self, E, f, l, Ect):
"""Marcus-Levich-Jortner theory for reduced EL data
Parameters
----------
E : list, required
List of energy values
f : float, required
Oscillator strength
l : float, required
Reorganization energy
Ect : float, required
CT state energy
Returns
-------
EL : float
Reduced EL value
"""
EL = 0
for n in range(0, 6): # TODO: Check line breaks
EL_n = (f / (math.sqrt(4 * math.pi * l * self.T_EL * self.k))) \
* (math.exp(-self.S_i_EL) * self.S_i_EL ** n / math.factorial(n)) \
* exp(-(Ect - E - l - n * self.hbarw_i_EL) ** 2 \
/ (4 * l * self.k * self.T_EL))
EL += EL_n
return EL
# MLJ function for reduced EL including disorder
# TODO: Add sigma as a fit parameter
[docs]
def MLJ_gaussian_EL_disorder(self, E, f, l, Ect):
"""Marcus-Levich-Jortner theory including disorder for reduced EL data
Parameters
----------
E : list, required
List of energy values
f : float, required
Oscillator strength
l : float, required
Reorganization energy
Ect : float, required
CT state energy
Returns
-------
EL : float
Reduced EL value
"""
EL = 0
for n in range(0, 6): # TODO: Check line breaks
EL_n = (f / (math.sqrt(4 * math.pi * l * self.T_EL * self.k + 2 * self.sig_EL ** 2))) \
* (math.exp(-self.S_i_EL) * self.S_i_EL ** n / math.factorial(n)) \
* exp(-(Ect - E - l - n * self.hbarw_i_EL) ** 2 \
/ (4 * l * self.k * self.T_EL + 2 * self.sig_EL ** 2))
EL += EL_n
return EL
# -----------------------------------------------------------------------------------------------------------
# Gaussian function for reduced EQE
[docs]
def gaussian_EQE(self, E, f, l, Ect):
"""Marcus theory for reduced EQE data
Parameters
----------
E : list, required
List of energy values
f : float, required
Oscillator strength
l : float, required
Reorganization energy
Ect : float, required
CT state energy
Returns
-------
EQE : float
Reduced EQE value
"""
return (f / (math.sqrt(4 * math.pi * l * self.T_EL * self.k))) * exp(
-(Ect + l - E) ** 2 / (4 * l * self.k * self.T_EL))
# Gaussian function for reduced EQE including disorder
# TODO: Add sigma as a fit parameter
[docs]
def gaussian_EQE_disorder(self, E, f, l, Ect):
"""Marcus theory including disorder for reduced EQE data
Parameters
----------
E : list, required
List of energy values
f : float, required
Oscillator strength
l : float, required
Reorganization energy
Ect : float, required
CT state energy
Returns
-------
EQE : float
Reduced EQE value
"""
return (f / (math.sqrt(4 * math.pi * l * self.T_EL * self.k + 2 * self.sig_EL ** 2))) * exp(
-(Ect + l - E) ** 2 / (4 * l * self.k * self.T_EL + 2 * self.sig_EL ** 2))
# -----------------------------------------------------------------------------------------------------------
# MLJ function for reduced EQE
[docs]
def MLJ_gaussian_EQE(self, E, f, l, Ect):
"""Marcus-Levich-Jortner theory for reduced EQE data
Parameters
----------
E : list, required
List of energy values
f : float, required
Oscillator strength
l : float, required
Reorganization energy
Ect : float, required
CT state energy
Returns
-------
EQE : float
Reduced EQE value
"""
EQE = 0
for n in range(0, 6): # TODO: Check line breaks
EQE_n = (f / (math.sqrt(4 * math.pi * l * self.T_EL * self.k))) \
* (math.exp(-self.S_i_EL) * self.S_i_EL ** n / math.factorial(n)) \
* exp(-(Ect - E + l + n * self.hbarw_i_EL) ** 2 \
/ (4 * l * self.k * self.T_EL))
EQE += EQE_n
return EQE
# MLJ function for reduced EQE including disorder
# TODO: Add sigma as a fit parameter
[docs]
def MLJ_gaussian_EQE_disorder(self, E, f, l, Ect):
"""Marcus-Levich-Jortner theory including for reduced EQE data
Parameters
----------
E : list, required
List of energy values
f : float, required
Oscillator strength
l : float, required
Reorganization energy
Ect : float, required
CT state energy
Returns
-------
EQE : float
Reduced EQE value
"""
EQE = 0
for n in range(0, 6): # TODO: Check line breaks
EQE_n = (f / (math.sqrt(4 * math.pi * l * self.T_EL * self.k + 2 * self.sig_EL ** 2))) \
* (math.exp(-self.S_i_EL) * self.S_i_EL ** n / math.factorial(n)) \
* exp(-(Ect - E + l + n * self.hbarw_i_EL) ** 2 \
/ (4 * l * self.k * self.T_EL + 2 * self.sig_EL ** 2))
EQE += EQE_n
return EQE
# -----------------------------------------------------------------------------------------------------------
# Page 7 - Subtract and Add Peak Fits
# -----------------------------------------------------------------------------------------------------------
# Function to subtract peak fit from EQE
[docs]
def subtract_Fit(self,
data_Fit,
data_EQE,
label_Fit,
label_EQE,
color_Fit,
color_EQE
):
"""Function to subtract fit from EQE data
Parameters
----------
data_Fit : dataFrame, required
Fit data to subtract
data_EQE: dataFrame, required
EQE data to subtract fit from
label_Fit: gui object, required
GUI text box with fit plot label
label_EQE: gui object, required
GUI text box with EQE plot label
color_Fit: gui object, required
GUI text box with fit plot color
color_EQE: gui object, required
GUI text box with EQE plot color
Returns
-------
None
"""
E_fit = 0
sub_EQE = []
label_fit = pick_EQE_Label(label_Fit, self.ui.textBox_p6_1)
label_eqe = pick_EQE_Label(label_EQE, self.ui.textBox_p6_4)
color_fit = color_Fit.toPlainText()
color_fit = color_fit.replace(" ", "")
color_eqe = color_EQE.toPlainText()
color_eqe = color_eqe.replace(" ", "")
if len(color_fit) == 0 or not is_Colour(color_fit):
color_fit = '#ff7716'
if len(color_eqe) == 0 or not is_Colour(color_eqe):
color_eqe = 'black'
try: # Check if fit for an optical peak was imported
T_fit = data_Fit['Temperature'][0]
f_fit = data_Fit['Oscillator Strength (eV**2)'][0]
l_fit = data_Fit['Reorganization Energy (eV)'][0]
E_fit = data_Fit['Optical Peak Energy (eV)'][0]
except:
try: # Check if fit for a CT state was imported
T_fit = data_Fit['Temperature'][0]
f_fit = data_Fit['Oscillator Strength (eV**2)'][0]
l_fit = data_Fit['Reorganization Energy (eV)'][0]
E_fit = data_Fit['CT State Energy (eV)'][0]
except:
self.logger.error('Please import a valid fit file.')
if E_fit != 0: # Only progress if a valid energy was imported
for x in range(len(data_EQE['Energy'])):
fit_value = calculate_gaussian_absorption(data_EQE['Energy'][x],
f_fit,
l_fit,
E_fit,
T_fit
)
sub_EQE.append(data_EQE['EQE'][x] - fit_value)
# Save fit data
if self.ui.save_subEQE.isChecked():
sub_file = pd.DataFrame()
sub_file['EQE'] = sub_EQE
sub_file['Energy'] = data_EQE['Energy']
sub_file['Log_EQE'] = np.log(sub_EQE)
sub_file['Wavelength'] = data_EQE['Wavelength']
save_sub_file = filedialog.asksaveasfilename() # User to pick a folder & name to save data to
save_sub_path, save_sub_filename = os.path.split(save_sub_file)
if len(save_sub_path) != 0: # Check if the user actually selected a path
os.chdir(save_sub_path) # Change the working directory
sub_file.to_csv(save_sub_filename) # Save data to csv
self.logger.info('Saving fit data to: %s' % str(save_sub_file))
os.chdir(self.data_dir) # Change the directory back
self.axSub_1, self.axSub_2 = set_up_EQE_plot()
self.axSub_1.plot(data_Fit['Energy'],
data_Fit['Signal'],
linewidth=2,
linestyle='--',
color=color_fit,
label=label_fit
)
self.axSub_1.plot(data_EQE['Energy'],
data_EQE['EQE'],
linewidth=2,
linestyle='-',
color=color_eqe,
label=label_eqe
)
self.axSub_1.plot(data_EQE['Energy'],
sub_EQE,
linewidth=2,
linestyle='-',
color='#1f77b4',
label='Subtracted EQE'
)
self.axSub_1.legend()
self.axSub_2.plot(data_Fit['Energy'],
data_Fit['Signal'],
linewidth=2,
linestyle='--',
color=color_fit,
label=label_fit
)
self.axSub_2.plot(data_EQE['Energy'],
data_EQE['EQE'],
linewidth=2,
linestyle='-',
color=color_eqe,
label=label_eqe
)
self.axSub_2.plot(data_EQE['Energy'],
sub_EQE,
linewidth=2,
linestyle='-',
color='#1f77b4',
label='Subtracted EQE'
)
self.axSub_2.legend()
# -----------------------------------------------------------------------------------------------------------
# Function to add peak fits
# TODO: Expand to plot MLJ fits
[docs]
def add_Fits(self,
data_OptFit,
data_CTFit,
data_EQE
):
"""Function to add peak fits
Parameters
----------
data_OptFit: dataFrame, required
Optical peak fit data
data_CTFit: dataFrame, required
CT state fit data
data_EQE: dataFrame, required
EQE data
Returns
-------
None
"""
include_disorder = False
add_Energy = []
add_Fits = []
label_OptFit = pick_EQE_Label(self.ui.textBox_p7_2, self.ui.textBox_p7_1)
label_CTFit = pick_EQE_Label(self.ui.textBox_p7_5, self.ui.textBox_p7_4)
label_EQE = pick_EQE_Label(self.ui.textBox_p7_8, self.ui.textBox_p7_7)
color_OptFit = self.ui.textBox_p7_3.toPlainText()
color_OptFit = color_OptFit.replace(" ", "")
color_CTFit = self.ui.textBox_p7_6.toPlainText()
color_CTFit = color_CTFit.replace(" ", "")
color_EQE = self.ui.textBox_p7_9.toPlainText()
color_EQE = color_EQE.replace(" ", "")
if len(color_OptFit) == 0 or not is_Colour(color_OptFit):
color_OptFit = '#ff7716'
if len(color_CTFit) == 0 or not is_Colour(color_CTFit):
color_CTFit = '#1f77b4'
if len(color_EQE) == 0 or not is_Colour(color_EQE):
color_EQE = 'black'
try: # Check if fit for an optical peak was imported
T_OptFit = data_OptFit['Temperature'][0]
f_OptFit = data_OptFit['Oscillator Strength (eV**2)'][0]
l_OptFit = data_OptFit['Reorganization Energy (eV)'][0]
E_OptFit = data_OptFit['Optical Peak Energy (eV)'][0]
except:
self.logger.error('No optical peak fit imported.')
try: # Check if fit for a CT state fit was importated
T_CTFit = data_CTFit['Temperature'][0]
f_CTFit = data_CTFit['Oscillator Strength (eV**2)'][0]
l_CTFit = data_CTFit['Reorganization Energy (eV)'][0]
E_CTFit = data_CTFit['CT State Energy (eV)'][0]
try:
sig_CTFit = data_CTFit['Sigma (eV)'][0]
include_disorder = True
except:
include_disorder = False
except:
self.logger.error('No CT state fit imported.')
if E_OptFit != 0 and E_CTFit != 0: # Only progress if a valid energy was imported
if len(data_EQE) != 0:
for x in range(len(data_EQE['Energy'])):
if data_EQE['Energy'][x] < max(data_OptFit['Energy']):
OptFit_value = calculate_gaussian_absorption(data_EQE['Energy'][x],
f_OptFit,
l_OptFit,
E_OptFit,
T_OptFit
)
if include_disorder:
CTFit_value = calculate_gaussian_disorder_absorption(data_EQE['Energy'][x],
f_CTFit,
l_CTFit,
E_CTFit,
sig_CTFit,
T_CTFit
)
else:
CTFit_value = calculate_gaussian_absorption(data_EQE['Energy'][x],
f_CTFit,
l_CTFit,
E_CTFit,
T_CTFit
)
add_Energy.append(data_EQE['Energy'][x])
add_Fits.append(OptFit_value + CTFit_value)
self.axAdd_1, self.axAdd_2 = set_up_EQE_plot()
self.axAdd_1.plot(data_OptFit['Energy'],
data_OptFit['Signal'],
linewidth=2,
linestyle='--',
color=color_OptFit,
label=label_OptFit
)
self.axAdd_1.plot(data_CTFit['Energy'],
data_CTFit['Signal'],
linewidth=2,
linestyle='--',
color=color_CTFit,
label=label_CTFit
)
self.axAdd_1.plot(add_Energy,
add_Fits,
linewidth=2,
linestyle='dotted',
color='grey',
label='$\mathrm{S_1}$ + CT Fit'
)
self.axAdd_1.plot(data_EQE['Energy'],
data_EQE['EQE'],
linewidth=2,
linestyle='-',
color=color_EQE,
label=label_EQE
)
self.axAdd_1.legend()
self.axAdd_2.plot(data_OptFit['Energy'],
data_OptFit['Signal'],
linewidth=2,
linestyle='--',
color=color_OptFit,
label=label_OptFit
)
self.axAdd_2.plot(data_CTFit['Energy'],
data_CTFit['Signal'],
linewidth=2,
linestyle='--',
color=color_CTFit,
label=label_CTFit
)
self.axAdd_2.plot(add_Energy,
add_Fits,
linewidth=2,
linestyle='dotted',
color='grey',
label='$\mathrm{S_1}$ + CT Fit'
)
self.axAdd_2.plot(data_EQE['Energy'],
data_EQE['EQE'],
linewidth=2,
linestyle='-',
color=color_EQE,
label=label_EQE
)
self.axAdd_2.legend()
df_add = pd.DataFrame()
df_add['Energy'] = np.array(add_Energy)
df_add['EQE'] = np.array(add_Fits)
EQE_label = self.ui.textBox_p7_7.toPlainText()
df_add.to_csv(f'{EQE_label}_Fit_sum.csv')
else:
self.logger.error('Please import a valid EQE file.')
# -----------------------------------------------------------------------------------------------------------
# Page 4 - Extended Fits (Marcus Theory)
# -----------------------------------------------------------------------------------------------------------
# Separate Double Peak Fit
# Function to compile fits for separate double peak fitting
[docs]
def double_fit(self):
"""Function to perform separate double fit of S1 and CT peaks
Parameters
----------
None
Returns
-------
None
"""
increase_factor = 1.05 # NOTE: Modify to increase data range for R2 calculation and fit selection
include_disorder = False
guessRange_Sig = None
# Import relevant parameters
if self.ui.bias_DoubleFit.isChecked():
self.bias = True
self.tolerance = float(self.ui.tolerance.value()) / 100
self.logger.info('Constraining fit below EQE data.')
else:
self.bias = False
self.logger.info('Not constraining fit.')
if self.ui.disorder_DoubleFit.isChecked():
include_disorder = True
startGuess_Sig = float(self.ui.guessStartSig_CT.value())
stopGuess_Sig = float(self.ui.guessStopSig_CT.value())
guessSig_ok = StartStop_is_valid(startGuess_Sig, stopGuess_Sig)
if guessSig_ok:
# guessRange_Sig = np.round(np.arange(startGuess_Sig, stopGuess_Sig + 0.05, 0.05), 3).tolist()
guessRange_Sig = [startGuess_Sig, stopGuess_Sig]
else:
# guessRange_Sig = np.round(np.arange(startGuess_Sig, startGuess_Sig + 0.05, 0.05), 3).tolist()
guessRange_Sig = [startGuess_Sig, stopGuess_Sig]
eqe = self.data_double
self.T_double = self.ui.double_Temperature.value()
startStart_Opt = float(self.ui.startStart_Opt.value())
startStop_Opt = float(self.ui.startStop_Opt.value())
stopStart_Opt = float(self.ui.stopStart_Opt.value())
stopStop_Opt = float(self.ui.stopStop_Opt.value())
startStart_CT = float(self.ui.startStart_CT.value())
startStop_CT = float(self.ui.startStop_CT.value())
stopStart_CT = float(self.ui.stopStart_CT.value())
stopStop_CT = float(self.ui.stopStop_CT.value())
startGuess_Opt = float(self.ui.guessStart_Opt.value())
stopGuess_Opt = float(self.ui.guessStop_Opt.value())
startGuess_CT = float(self.ui.guessStart_CT.value())
stopGuess_CT = float(self.ui.guessStop_CT.value())
# Check that all start and stop energies are valid
# startOpt_ok = StartStop_is_valid(startStart_Opt, startStop_Opt)
# stopOpt_ok = StartStop_is_valid(stopStart_Opt, stopStop_Opt)
startOpt_ok = True
stopOpt_ok = True
# startCT_ok = StartStop_is_valid(startStart_CT, startStop_CT)
# stopCT_ok = StartStop_is_valid(stopStart_CT, stopStop_CT)
startCT_ok = True
stopCT_ok = True
guessOpt_ok = StartStop_is_valid(startGuess_Opt, stopGuess_Opt)
guessCT_ok = StartStop_is_valid(startGuess_CT, stopGuess_CT)
# Compile all start / stop energies for Opt and CT fit
if startOpt_ok and stopOpt_ok and guessOpt_ok and startCT_ok and stopCT_ok and guessCT_ok:
startRange_Opt = np.round(np.arange(startStart_Opt, startStop_Opt + 0.005, 0.01), 3).tolist() # Change step to 0.05
stopRange_Opt = np.round(np.arange(stopStart_Opt, stopStop_Opt + 0.005, 0.01), 3).tolist()
startRange_CT = np.round(np.arange(startStart_CT, startStop_CT + 0.005, 0.01), 3).tolist()
stopRange_CT = np.round(np.arange(stopStart_CT, stopStop_CT + 0.005, 0.01), 3).tolist()
guessRange_Opt = np.round(np.arange(startGuess_Opt, stopGuess_Opt + 0.1, 0.05), 3).tolist()
guessRange_CT = np.round(np.arange(startGuess_CT, stopGuess_CT + 0.1, 0.05), 3).tolist()
# Compile a dataFrame with all combinations of start / stop values for Opt and CT fit
self.logger.info('Compiling Fit Ranges ...')
df_Opt = pd.DataFrame()
df_CT = pd.DataFrame()
start_Opt_list = []
stop_Opt_list = []
start_CT_list = []
stop_CT_list = []
for startOpt in startRange_Opt:
for stopOpt in stopRange_Opt:
start_Opt_list.append(startOpt)
stop_Opt_list.append(stopOpt)
for startCT in startRange_CT:
for stopCT in stopRange_CT:
start_CT_list.append(startCT)
stop_CT_list.append(stopCT)
df_Opt['Start'] = start_Opt_list
df_Opt['Stop'] = stop_Opt_list
df_CT['Start'] = start_CT_list
df_CT['Stop'] = stop_CT_list
# Calculate all optical peak fits
self.logger.info('Calculating Optical Peak Fits ...')
cal_vals_Opt = list(map(lambda x: calculate_guess_fit(x=x,
df=df_Opt,
eqe=eqe,
function=self.gaussian_double,
guessRange=guessRange_Opt
), tqdm(range(len(df_Opt)))))
best_vals_Opt = list(map(lambda list_: sep_list(list_, 0), cal_vals_Opt))
covar_Opt = list(map(lambda list_: sep_list(list_, 1), cal_vals_Opt))
R2_Opt = list(map(lambda list_: sep_list(list_, 2), cal_vals_Opt))
df_Opt['Fit'] = best_vals_Opt
df_Opt['Covar'] = covar_Opt
df_Opt['R2'] = R2_Opt
# Calculate CT state fits
start_Opt_list = []
stop_Opt_list = []
start_CT_list = []
stop_CT_list = []
best_vals_Opt = []
best_vals_CT = []
Opt_covar_list = []
CT_covar_list = []
R2_Opt = []
R2_CT = []
combined_R2_list = []
Opt_Fit_list = []
CT_Fit_list = []
combined_Fit_list = []
Energy_list = []
EQE_list = []
df_results = pd.DataFrame()
self.logger.info('Calculating CT State Fits ...')
if include_disorder:
self.logger.info('Including CT State Disorder ...')
# If Optical peak to be subtracted before CT fit
if self.ui.subtract_DoubleFit.isChecked() and not self.ui.bestSubtract_DoubleFit.isChecked():
self.logger.info('Subtracting All Optical Peak Fits ...')
for x in tqdm(range(len(df_Opt))):
for y in tqdm(range(len(df_CT))):
if df_Opt['R2'][x] > 0: # Check that the optical peak fit was successful
new_eqe = subtract_Opt(eqe, df_Opt['Fit'][x], T=self.T_double)
if include_disorder:
best_vals, covar, p0, r_squared = guess_fit(eqe=new_eqe,
startE=df_CT['Start'][y],
stopE=df_CT['Stop'][y],
function=self.gaussian_disorder_double,
guessRange=guessRange_CT,
guessRange_sig=guessRange_Sig,
include_disorder=True,
bounds=True # to use fit model
)
else:
best_vals, covar, p0, r_squared = guess_fit(eqe=new_eqe,
startE=df_CT['Start'][y],
stopE=df_CT['Stop'][y],
function=self.gaussian_double,
guessRange=guessRange_CT,
include_disorder=False,
bounds=None # to use fit function
)
else:
best_vals = [0, 0, 0]
r_squared = 0
Opt_covar_list.append(df_Opt['Covar'][x])
CT_covar_list.append(covar)
start_Opt_list.append(df_Opt['Start'][x])
stop_Opt_list.append(df_Opt['Stop'][x])
start_CT_list.append(df_CT['Start'][y])
stop_CT_list.append(df_CT['Stop'][y])
best_vals_Opt.append(df_Opt['Fit'][x])
best_vals_CT.append(best_vals)
R2_Opt.append(df_Opt['R2'][x])
R2_CT.append(r_squared)
# Calculate combined fit here
parameter_dict = calculate_combined_fit(stopE=df_Opt['Stop'][x],
best_vals_Opt=df_Opt['Fit'][x],
best_vals_CT=best_vals,
R2_Opt=df_Opt['R2'][x],
R2_CT=r_squared,
eqe=eqe,
T=self.T_double,
bias=self.bias,
tolerance=self.tolerance,
range=increase_factor,
include_disorder=include_disorder
)
combined_R2_list.append(parameter_dict['R2_Combined'])
combined_Fit_list.append(parameter_dict['Combined_Fit'])
Opt_Fit_list.append(parameter_dict['Opt_Fit'])
CT_Fit_list.append(parameter_dict['CT_Fit'])
Energy_list.append(parameter_dict['Energy'])
EQE_list.append(parameter_dict['EQE'])
# If only best Optical peak is to be subtracted before CT fit
elif self.ui.bestSubtract_DoubleFit.isChecked() and not self.ui.subtract_DoubleFit.isChecked():
self.logger.info('Subtracting Only Best Optical Peak Fit ...')
# best_fit_index = df_Opt['Fit'][df_Opt['R2']==max(df_Opt['R2'])].index[0]
# print(best_fit_index)
# To avoid picking a fit that has a high R2 but moves above the data
advanced_R2_list = []
for x in range(len(df_Opt)):
wave_fit, energy_fit, eqe_fit, log_eqe_fit = compile_EQE(eqe,
df_Opt['Start'][x],
df_Opt['Stop'][x] * increase_factor,
1)
y_fit = [self.gaussian_double(e,
df_Opt['Fit'][x][0],
df_Opt['Fit'][x][1],
df_Opt['Fit'][x][2]
) for e in energy_fit]
advanced_R2_list.append(R_squared(eqe_fit, y_fit))
df_Opt['Advanced R2'] = advanced_R2_list
best_fit_index = df_Opt['Fit'][df_Opt['Advanced R2'] == max(df_Opt['Advanced R2'])].index[0]
# print(best_fit_index)
new_eqe = subtract_Opt(eqe, df_Opt['Fit'][best_fit_index], T=self.T_double)
for y in tqdm(range(len(df_CT))):
if include_disorder:
best_vals, covar, p0, r_squared = guess_fit(eqe=new_eqe,
startE=df_CT['Start'][y],
stopE=df_CT['Stop'][y],
function=self.gaussian_disorder_double,
guessRange=guessRange_CT,
guessRange_sig=guessRange_Sig,
include_disorder=True,
bounds=True # to use fit model
)
else:
best_vals, covar, p0, r_squared = guess_fit(eqe=new_eqe,
startE=df_CT['Start'][y],
stopE=df_CT['Stop'][y],
function=self.gaussian_double,
guessRange=guessRange_CT,
include_disorder=False,
bounds=None # to use fit function
)
Opt_covar_list.append(df_Opt['Covar'][x])
CT_covar_list.append(covar)
start_Opt_list.append(df_Opt['Start'][best_fit_index])
stop_Opt_list.append(df_Opt['Stop'][best_fit_index])
start_CT_list.append(df_CT['Start'][y])
stop_CT_list.append(df_CT['Stop'][y])
best_vals_Opt.append(df_Opt['Fit'][best_fit_index])
best_vals_CT.append(best_vals)
R2_Opt.append(df_Opt['R2'][best_fit_index])
R2_CT.append(r_squared)
# Calculate combined fit here
parameter_dict = calculate_combined_fit(stopE=df_Opt['Stop'][x],
best_vals_Opt=df_Opt['Fit'][x],
best_vals_CT=best_vals,
R2_Opt=df_Opt['R2'][x],
R2_CT=r_squared,
eqe=eqe,
T=self.T_double,
bias=self.bias,
tolerance=self.tolerance,
range=increase_factor,
include_disorder=include_disorder
)
combined_R2_list.append(parameter_dict['R2_Combined'])
combined_Fit_list.append(parameter_dict['Combined_Fit'])
Opt_Fit_list.append(parameter_dict['Opt_Fit'])
CT_Fit_list.append(parameter_dict['CT_Fit'])
Energy_list.append(parameter_dict['Energy'])
EQE_list.append(parameter_dict['EQE'])
# If Optical peak not to be subtracted before CT fit
elif not self.ui.subtract_DoubleFit.isChecked() and not self.ui.bestSubtract_DoubleFit.isChecked():
self.logger.info('Not Subtracting Optical Peak Fits.')
for x in tqdm(range(len(df_Opt))):
for y in tqdm(range(len(df_CT))):
if include_disorder:
best_vals, covar, p0, r_squared = guess_fit(eqe=eqe,
startE=df_CT['Start'][y],
stopE=df_CT['Stop'][y],
function=self.gaussian_disorder_double,
guessRange=guessRange_CT,
guessRange_sig=guessRange_Sig,
include_disorder=True,
bounds=True # to use fit model
)
else:
best_vals, covar, p0, r_squared = guess_fit(eqe=eqe,
startE=df_CT['Start'][y],
stopE=df_CT['Stop'][y],
function=self.gaussian_double,
guessRange=guessRange_CT,
include_disorder=False,
bounds=None # to use fit function
)
Opt_covar_list.append(df_Opt['Covar'][x])
CT_covar_list.append(covar)
start_Opt_list.append(df_Opt['Start'][x])
stop_Opt_list.append(df_Opt['Stop'][x])
start_CT_list.append(df_CT['Start'][y])
stop_CT_list.append(df_CT['Stop'][y])
best_vals_Opt.append(df_Opt['Fit'][x])
best_vals_CT.append(best_vals)
R2_Opt.append(df_Opt['R2'][x])
R2_CT.append(r_squared)
# Calculate combined fit here
parameter_dict = calculate_combined_fit(stopE=df_Opt['Stop'][x],
best_vals_Opt=df_Opt['Fit'][x],
best_vals_CT=best_vals,
R2_Opt=df_Opt['R2'][x],
R2_CT=r_squared,
eqe=eqe,
T=self.T_double,
bias=self.bias,
tolerance=self.tolerance,
range=increase_factor,
include_disorder=include_disorder
)
combined_R2_list.append(parameter_dict['R2_Combined'])
combined_Fit_list.append(parameter_dict['Combined_Fit'])
Opt_Fit_list.append(parameter_dict['Opt_Fit'])
CT_Fit_list.append(parameter_dict['CT_Fit'])
Energy_list.append(parameter_dict['Energy'])
EQE_list.append(parameter_dict['EQE'])
else:
self.logger.info('Please select valid fit settings.')
if len(best_vals_Opt) == len(best_vals_CT) and len(best_vals_Opt) != 0: # Confirm lists are acceptable
df_results['Start_Opt'] = start_Opt_list
df_results['Stop_Opt'] = stop_Opt_list
df_results['Fit_Opt'] = best_vals_Opt
df_results['R2_Opt'] = R2_Opt
df_results['Start_CT'] = start_CT_list
df_results['Stop_CT'] = stop_CT_list
df_results['Fit_CT'] = best_vals_CT
df_results['R2_CT'] = R2_CT
df_results['Covar_Opt'] = Opt_covar_list
df_results['Covar_CT'] = CT_covar_list
# Add combined fit to dataFrame
df_results['Total_R2'] = combined_R2_list
df_results['Total_Fit'] = combined_Fit_list
df_results['Opt_Fit'] = Opt_Fit_list
df_results['CT_Fit'] = CT_Fit_list
df_results['Energy'] = Energy_list
df_results['EQE'] = EQE_list
# Find best fit
self.logger.info('Determining Best Fit ...')
self.logger.info('Fit Results: ')
print("")
# Save fit data
if self.ui.save_DoubleFit.isChecked():
save_fit_file = filedialog.asksaveasfilename() # User to pick folder & name to save to
save_fit = True
self.logger.info('Saving fit data.')
else:
save_fit_file = None
save_fit = False
label = pick_EQE_Label(self.ui.textBox_dF2, self.ui.textBox_dF1)
# for x in np.arange(1, 6, 1):
n = int(self.ui.n.value())
for x in np.arange(1, n+1, 1):
print('-' * 80)
print(('Best Fit No. {} : ').format(x))
df_results = find_best_fit(df_both=df_results,
eqe=eqe,
T=self.T_double,
label=label,
n_fit=x,
include_disorder=include_disorder,
save_fit=save_fit,
save_fit_file=save_fit_file
)
print(' ' * 80)
print('-' * 80)
self.bias = False
# -----------------------------------------------------------------------------------------------------------
# Gaussian fitting function for double fit
[docs]
def gaussian_double(self, E, f, l, Ect):
"""Marcus theory to separately fit double peaks
Parameters
----------
E : list, required
List of energy values
f : float, required
Oscillator strength
l : float, required
Reorganization energy
Ect : float, required
CT state energy
Returns
-------
EQE : float
EQE value
"""
return (f / (E * math.sqrt(4 * math.pi * l * self.T_double * self.k))) * exp(
-(Ect + l - E) ** 2 / (4 * l * self.k * self.T_double))
[docs]
def gaussian_disorder_double(self, E, f, l, Ect, sig):
"""Marcus theory including disorder to separately fit double peaks
Parameters
----------
E : list, required
List of energy values
f : float, required
Oscillator strength
l : float, required
Reorganization energy
Ect : float, required
CT state energy
sig : float, required
Gaussian disorder
Returns
-------
EQE : float
EQE value
"""
return (f / (E * math.sqrt(2 * math.pi * (2 * l * self.T_double * self.k + sig ** 2)))) * exp(
-((Ect - (sig ** 2 / (2 * self.k * self.T_double)) + l + (sig ** 2 / (2 * self.k * self.T_double)) - E) ** 2 / (
4 * l * self.k * self.T_double + 2 * sig ** 2)))
# -----------------------------------------------------------------------------------------------------------
# Simultaneous Double Peak Fit
# Function to perform simultaneous double peak fitting once
[docs]
def sim_double_fit_single(self):
"""Function to perform simultaneous double peak fitting for one energy range
Parameters
----------
None
Returns
-------
None
"""
# Import relevant parameters
if self.ui.disorder_Sim.isChecked():
include_disorder = True
else:
include_disorder = False
eqe = self.data_sim
self.T_sim = self.ui.Temperature_Sim.value()
bound_dict = self.load_sim_dict()
# Compile EQE data
energy_fit, eqe_fit = compile_Data(energy=eqe['Energy'],
y=eqe['EQE'],
startE=bound_dict['start_fit'],
stopE=bound_dict['stop_fit']
)
# Set plot range
x_plot = linspace(bound_dict['start_plot'], bound_dict['stop_plot'], 50)
if include_disorder:
p0 = self.sim_guess_sig
best_vals, covar, y_fit, r_squared = fit_model_double(function=self.gaussian_disorder_double_sim,
energy_fit=energy_fit,
eqe_fit=eqe_fit,
bound_dict=bound_dict,
p0=p0,
include_disorder=include_disorder,
print_report=False
)
y_CT = [calculate_gaussian_disorder_absorption(i,
f=best_vals[0],
l=best_vals[1],
E=best_vals[2],
sig=best_vals[6],
T=self.T_sim
) for i in x_plot]
y_sum = [self.gaussian_disorder_double_sim(i,
fCT=best_vals[0],
lCT=best_vals[1],
ECT=best_vals[2],
fopt=best_vals[3],
lopt=best_vals[4],
Eopt=best_vals[5],
sig=best_vals[6]
) for i in x_plot]
else:
p0 = self.sim_guess
best_vals, covar, y_fit, r_squared = fit_model_double(function=self.gaussian_double_sim,
energy_fit=energy_fit,
eqe_fit=eqe_fit,
bound_dict=bound_dict,
p0=p0,
include_disorder=include_disorder,
print_report=False
)
y_CT = [calculate_gaussian_absorption(i,
f=best_vals[0],
l=best_vals[1],
E=best_vals[2],
T=self.T_sim
) for i in x_plot]
y_sum = [self.gaussian_double_sim(i,
fCT=best_vals[0],
lCT=best_vals[1],
ECT=best_vals[2],
fopt=best_vals[3],
lopt=best_vals[4],
Eopt=best_vals[5]
) for i in x_plot]
y_opt = [calculate_gaussian_absorption(i,
f=best_vals[3],
l=best_vals[4],
E=best_vals[5],
T=self.T_sim
) for i in x_plot]
print('-' * 35)
print('R2 : ', format(r_squared, '.6f'))
print('-' * 35)
print('f_Opt (eV**2) : ', format(best_vals[3], '.6f'), '+/-', format(math.sqrt(covar[3, 3]), '.6f'))
print('l_Opt (eV) : ', format(best_vals[4], '.6f'), '+/-', format(math.sqrt(covar[4, 4]), '.6f'))
print('E_Opt (eV) : ', format(best_vals[5], '.6f'), '+/-', format(math.sqrt(covar[5, 5]), '.6f'))
print('-' * 35)
print('f_CT (eV**2) : ', format(best_vals[0], '.6f'), '+/-', format(math.sqrt(covar[0, 0]), '.6f'))
print('l_CT (eV) : ', format(best_vals[1], '.6f'), '+/-', format(math.sqrt(covar[1, 1]), '.6f'))
print('E_CT (eV) : ', format(best_vals[2], '.6f'), '+/-', format(math.sqrt(covar[2, 2]), '.6f'))
if include_disorder:
print('Sigma (eV) : ', format(best_vals[6], '.6f'), '+/-', format(math.sqrt(covar[6, 6]), '.6f'))
W = best_vals[1] * self.T_sim + (best_vals[6] ** 2) / (2 * self.k)
print('Gaussian Variance [W] (eV K) : ', format(W, '.2f'))
# print('Temperature [T] (K) : ', T)
# print('-' * 80)
label = pick_EQE_Label(self.ui.textBox_simFit_label, self.ui.textBox_simFit)
axDouble_1, axDouble_2 = set_up_plot(flag='Energy')
axDouble_1.plot(eqe['Energy'],
eqe['EQE'],
linewidth=2,
linestyle='-',
label=label,
color='black'
)
axDouble_1.plot(x_plot,
y_opt,
linewidth=2,
linestyle='dotted',
label='Optical Peak Fit'
)
axDouble_1.plot(x_plot,
y_CT,
linewidth=2,
linestyle='--',
label='CT State Fit'
)
axDouble_1.plot(x_plot,
y_sum,
linewidth=2,
linestyle='dashdot',
label='Total Fit'
)
axDouble_1.legend()
axDouble_2.plot(eqe['Energy'],
eqe['EQE'],
linewidth=2,
linestyle='-',
label=label,
color='black'
)
axDouble_2.plot(x_plot,
y_opt,
linewidth=2,
linestyle='--',
label='Optical Peak Fit'
)
axDouble_2.plot(x_plot,
y_CT,
linewidth=2, linestyle='--',
label='CT State Fit'
)
axDouble_2.plot(x_plot,
y_sum,
linewidth=2,
linestyle='dashdot',
label='Total Fit'
)
axDouble_2.set_ylim([10 ** (-7), max(eqe['EQE']) * 1.4])
axDouble_2.legend()
if self.ui.save_simDoubleFit.isChecked():
opt_file = pd.DataFrame()
opt_file['Energy'] = x_plot
opt_file['Signal'] = y_opt
opt_file['Temperature'] = self.T_sim
opt_file['Oscillator Strength (eV**2)'] = best_vals[3]
opt_file['Reorganization Energy (eV)'] = best_vals[4]
opt_file['Optical Peak Energy (eV)'] = best_vals[5]
CT_file = pd.DataFrame()
CT_file['Energy'] = x_plot
CT_file['Signal'] = y_CT
CT_file['Temperature'] = self.T_sim
CT_file['Oscillator Strength (eV**2)'] = best_vals[0]
CT_file['Reorganization Energy (eV)'] = best_vals[1]
CT_file['CT State Energy (eV)'] = best_vals[2]
if include_disorder:
CT_file['Sigma (eV)'] = best_vals[6]
save_fit_file = filedialog.asksaveasfilename() # User to pick folder & name to save to
save_fit_path, save_fit_filename = os.path.split(save_fit_file)
if len(save_fit_path) != 0: # Check if the user actually selected a path
os.chdir(save_fit_path) # Change the working directory
opt_file.to_csv(f'{save_fit_filename}_Fit_simDouble_Opt') # Save data to csv
CT_file.to_csv(f'{save_fit_filename}_Fit_simDouble_CT') # Save data to csv
self.logger.info('Saving fit data to: %s' % str(save_fit_file))
os.chdir(self.data_dir) # Change the directory back
# -----------------------------------------------------------------------------------------------------------
# Function to perform simultaneous double peak fitting multiple times
[docs]
def sim_double_fit(self):
"""Function to perform simultaneous double peak fitting for multiple energy ranges
Parameters
----------
None
Returns
-------
None
"""
# Import relevant parameters
eqe = self.data_sim
self.T_sim = self.ui.Temperature_Sim.value()
bound_dict = self.load_sim_dict()
if self.ui.disorder_Sim.isChecked():
include_disorder = True
else:
include_disorder = False
if self.ui.bias_SimFit.isChecked():
self.bias_sim = True
self.tolerance_sim = float(self.ui.tolerance_Sim.value()) / 100
self.logger.info('Constraining fit below EQE data.')
else:
self.bias_sim = False
self.logger.info('Not constraining fit.')
# Check that all start and stop energies are valid
# start_ok = StartStop_is_valid(bound_dict['start_start'], bound_dict['start_stop'])
# stop_ok = StartStop_is_valid(bound_dict['stop_start'], bound_dict['stop_stop'])
start_ok = True
stop_ok = True
# Compile all start / stop energies for CT fit
if start_ok and stop_ok:
startRange = np.round(np.arange(bound_dict['start_start'],
bound_dict['start_stop'] + 0.005,
0.01), 3).tolist()
stopRange = np.round(np.arange(bound_dict['stop_start'],
bound_dict['stop_stop'] + 0.005,
0.01), 3).tolist()
# Compile a dataFrame with all combinations of start / stop values for the optical and CT fit
self.logger.info('Compiling Fit Ranges ...')
df = pd.DataFrame()
start_list = []
stop_list = []
for start in startRange:
for stop in stopRange:
start_list.append(start)
stop_list.append(stop)
df['Start'] = start_list
df['Stop'] = stop_list
self.logger.info('Calculating Fits ...')
best_vals_Opt = []
best_vals_CT = []
covar_list = []
start_list = []
stop_list = []
R2_Opt_list = []
R2_CT_list = []
R2_sum_list = []
R2_average_list = []
Opt_Fit_list = []
CT_Fit_list = []
combined_Fit_list = []
Energy_list = []
EQE_list = []
df_results = pd.DataFrame()
for x in tqdm(range(len(df))):
if df['Start'][x] < df['Stop'][x]:
wave_fit, energy_fit, eqe_fit, log_eqe_fit = compile_EQE(eqe, df['Start'][x], df['Stop'][x], 1)
if include_disorder:
p0 = self.sim_guess_sig
try:
best_vals, covar, y_fit, r_squared = fit_model_double(
function=self.gaussian_disorder_double_sim,
energy_fit=energy_fit,
eqe_fit=eqe_fit,
bound_dict=bound_dict,
p0=p0,
include_disorder=include_disorder,
print_report=False
)
best_CT = [
best_vals[0],
best_vals[1],
best_vals[2],
best_vals[6],
]
best_Opt = [
best_vals[3],
best_vals[4],
best_vals[5]
]
except:
best_vals = [0, 0, 0, 0, 0, 0, 0]
else:
p0 = self.sim_guess
try:
best_vals, covar, y_fit, r_squared = fit_model_double(function=self.gaussian_double_sim,
energy_fit=energy_fit,
eqe_fit=eqe_fit,
bound_dict=bound_dict,
p0=p0,
include_disorder=include_disorder,
print_report=False
)
best_CT = [
best_vals[0],
best_vals[1],
best_vals[2]
]
best_Opt = [
best_vals[3],
best_vals[4],
best_vals[5]
]
except:
best_vals = [0, 0, 0, 0, 0, 0]
if sum(best_vals) != 0: # Check that the fit was successful
# Calculate combined fit here
parameter_dict = calculate_combined_fit(eqe=eqe,
stopE=df['Stop'][x],
best_vals_Opt=best_Opt,
best_vals_CT=best_CT,
T=self.T_sim,
bias=self.bias_sim,
tolerance=self.tolerance_sim,
include_disorder=include_disorder
)
covar_list.append(covar)
start_list.append(df['Start'][x])
stop_list.append(df['Stop'][x])
best_vals_CT.append(best_CT)
best_vals_Opt.append(best_Opt)
R2_sum_list.append(parameter_dict['R2_Combined'])
R2_Opt_list.append(parameter_dict['R2_Opt'])
R2_CT_list.append(parameter_dict['R2_CT'])
R2_average_list.append(parameter_dict['R2_Average'])
combined_Fit_list.append(parameter_dict['Combined_Fit'])
Opt_Fit_list.append(parameter_dict['Opt_Fit'])
CT_Fit_list.append(parameter_dict['CT_Fit'])
Energy_list.append(parameter_dict['Energy'])
EQE_list.append(parameter_dict['EQE'])
else: # If sum was unsuccessful, skip and move on
pass
else:
pass
if len(best_vals_Opt) == len(best_vals_CT) and len(best_vals_Opt) != 0:
df_results['Start'] = start_list
df_results['Stop'] = stop_list
df_results['Fit_Opt'] = best_vals_Opt
df_results['R2_Opt'] = R2_Opt_list
df_results['Fit_CT'] = best_vals_CT
df_results['R2_CT'] = R2_CT_list
df_results['Covar'] = covar_list
# Add combined fit to dataFrame
df_results['Total_R2'] = R2_sum_list
df_results['Total_Fit'] = combined_Fit_list
df_results['Opt_Fit'] = Opt_Fit_list
df_results['CT_Fit'] = CT_Fit_list
df_results['Energy'] = Energy_list
df_results['EQE'] = EQE_list
df_results['Comp_R2'] = R2_average_list
# Find best fit
self.logger.info('Determining Best Fit ...')
self.logger.info('Fit Results: ')
print("")
# Save fit data
if self.ui.save_simDoubleFit.isChecked():
save_fit_file = filedialog.asksaveasfilename() # User to pick folder & name to save to
save_fit = True
self.logger.info('Saving fit data.')
else:
save_fit_file = None
save_fit = False
label = pick_EQE_Label(self.ui.textBox_simFit_label, self.ui.textBox_simFit)
n = int(self.ui.n_Sim.value())
for x in np.arange(1, n+1, 1):
print('-' * 80)
print(('Best Fit No. {} : ').format(x))
df_results = find_best_fit(df_both=df_results,
eqe=eqe,
T=self.T_sim,
label=label,
n_fit=x,
include_disorder=include_disorder,
simultaneous_double=True,
save_fit=save_fit,
save_fit_file=save_fit_file
)
print(' ' * 80)
print('-' * 80)
print("")
# -----------------------------------------------------------------------------------------------------------
# Function to load dictionary of fit bounds
[docs]
def load_sim_dict(self):
"""Function to load dictionary of fit bounds from GUI paramaters
Parameters
----------
None
Returns
-------
None
"""
start_fit = self.ui.startFit_Sim.value()
stop_fit = self.ui.stopFit_Sim.value()
start_plot = self.ui.startPlot_Sim.value()
stop_plot = self.ui.stopPlot_Sim.value()
start_start = self.ui.startStart_Sim.value()
start_stop = self.ui.startStop_Sim.value()
stop_start = self.ui.stopStart_Sim.value()
stop_stop = self.ui.stopStop_Sim.value()
start_Eopt = self.ui.startBound_Eopt.value()
stop_Eopt = self.ui.stopBound_Eopt.value()
start_fopt = self.ui.startBound_fopt.value()
stop_fopt = self.ui.stopBound_fopt.value()
start_lopt = self.ui.startBound_lopt.value()
stop_lopt = self.ui.stopBound_lopt.value()
start_ECT = self.ui.startBound_ECT.value()
stop_ECT = self.ui.stopBound_ECT.value()
start_fCT = self.ui.startBound_fCT.value()
stop_fCT = self.ui.stopBound_fCT.value()
start_lCT = self.ui.startBound_lCT.value()
stop_lCT = self.ui.stopBound_lCT.value()
start_sig = self.ui.startBound_sig.value()
stop_sig = self.ui.stopBound_sig.value()
bound_dict = {
'start_Eopt': start_Eopt,
'stop_Eopt': stop_Eopt,
'start_fopt': start_fopt,
'stop_fopt': stop_fopt,
'start_lopt': start_lopt,
'stop_lopt': stop_lopt,
'start_ECT': start_ECT,
'stop_ECT': stop_ECT,
'start_fCT': start_fCT,
'stop_fCT': stop_fCT,
'start_lCT': start_lCT,
'stop_lCT': stop_lCT,
'start_sig': start_sig,
'stop_sig': stop_sig,
'start_fit': start_fit,
'stop_fit': stop_fit,
'start_plot': start_plot,
'stop_plot': stop_plot,
'start_start': start_start,
'start_stop': start_stop,
'stop_start': stop_start,
'stop_stop': stop_stop
}
return bound_dict
# # -----------------------------------------------------------------------------------------------------------
# Gaussian fitting function for simultaneous double peak fit
[docs]
def gaussian_double_sim(self, E, fCT, lCT, ECT, fopt, lopt, Eopt):
"""Marcus theory to simultaneously fit double peaks
Parameters
----------
E : list, required
List of energy values
fCT : float, required
CT state oscillator strength
lCT : float, required
CT state reorganization energy
ECT : float, required
CT state energy
fopt : float, required
S1 peak oscillator strength
lopt : float, required
S1 peak reorganization energy
Eopt : float, required
S1 peak energy
Returns
-------
EQE : float
EQE value
"""
val_CT = (fCT / (E * math.sqrt(4 * math.pi * lCT * self.T_sim * self.k))) * exp(
-(ECT + lCT - E) ** 2 / (4 * lCT * self.k * self.T_sim))
val_opt = (fopt / (E * math.sqrt(4 * math.pi * lopt * self.T_sim * self.k))) * exp(
-(Eopt + lopt - E) ** 2 / (4 * lopt * self.k * self.T_sim))
return val_CT + val_opt
# Gaussian fitting function for simultaneous double peak fit including disorder
[docs]
def gaussian_disorder_double_sim(self, E, fCT, lCT, ECT, fopt, lopt, Eopt, sig):
"""Marcus theory including disorder to simultaneously fit double peaks
Parameters
----------
E : list, required
List of energy values
fCT : float, required
CT state oscillator strength
lCT : float, required
CT state reorganization energy
ECT : float, required
CT state energy
fopt : float, required
S1 peak oscillator strength
lopt : float, required
S1 peak reorganization energy
Eopt : float, required
S1 peak energy
sig : float, required
Gaussian disorder
Returns
-------
EQE : float
EQE value
"""
val_CT = (fCT / (E * math.sqrt(2 * math.pi * (2 * lCT * self.T_sim * self.k + sig ** 2)))) * exp(
-(ECT + lCT - E) ** 2 / (4 * lCT * self.k * self.T_sim + 2 * sig ** 2))
val_opt = (fopt / (E * math.sqrt(4 * math.pi * lopt * self.T_sim * self.k))) * exp(
-(Eopt + lopt - E) ** 2 / (4 * lopt * self.k * self.T_sim))
return val_CT + val_opt
# -----------------------------------------------------------------------------------------------------------
# Page 5 - Extended Fits (MLJ Theory)
# -----------------------------------------------------------------------------------------------------------
# Separate Double Peak Fit
# Function to compile fits for separate double peak fitting
[docs]
def double_fit_MLJ(self):
"""Function for separate double peak fitting of S1 and CT state peaks using MLJ theory
Parameters
----------
None
Returns
-------
EQE : float
EQE value
"""
increase_factor = 1.05 # NOTE: Modify to increase data range for R2 calculation and fit selection
include_disorder = False
guessRange_Sig = None
# Import relevant parameters
if self.ui.bias_extraDoubleFit.isChecked():
self.bias = True
self.tolerance = float(self.ui.extraDouble_Tolerance.value()) / 100
self.logger.info('Constraining fit below EQE data.')
else:
self.bias = False
self.logger.info('Not constraining fit.')
if self.ui.disorder_extraDoubleFit.isChecked():
include_disorder = True
startGuess_Sig = float(self.ui.extraGuessStartSig_CT.value())
stopGuess_Sig = float(self.ui.extraGuessStopSig_CT.value())
guessSig_ok = StartStop_is_valid(startGuess_Sig, stopGuess_Sig)
if guessSig_ok:
# guessRange_Sig = np.round(np.arange(startGuess_Sig, stopGuess_Sig + 0.05, 0.05), 3).tolist()
guessRange_Sig = [startGuess_Sig, stopGuess_Sig]
else:
# guessRange_Sig = np.round(np.arange(startGuess_Sig, startGuess_Sig + 0.05, 0.05), 3).tolist()
guessRange_Sig = [startGuess_Sig, stopGuess_Sig]
eqe = self.data_extraDouble
self.T_xDouble = self.ui.extraDouble_Temperature.value()
self.hbarw_Double = self.ui.extraDouble_vibEnergy.value()
self.S_Double = self.ui.extraDouble_HuangRhys.value()
startStart_Opt = float(self.ui.extraStartStart_Opt.value())
startStop_Opt = float(self.ui.extraStartStop_Opt.value())
stopStart_Opt = float(self.ui.extraStopStart_Opt.value())
stopStop_Opt = float(self.ui.extraStopStop_Opt.value())
startStart_CT = float(self.ui.extraStartStart_CT.value())
startStop_CT = float(self.ui.extraStartStop_CT.value())
stopStart_CT = float(self.ui.extraStopStart_CT.value())
stopStop_CT = float(self.ui.extraStopStop_CT.value())
startGuess_Opt = float(self.ui.extraGuessStart_Opt.value())
stopGuess_Opt = float(self.ui.extraGuessStop_Opt.value())
startGuess_CT = float(self.ui.extraGuessStart_CT.value())
stopGuess_CT = float(self.ui.extraGuessStop_CT.value())
# Check that all start and stop energies are valid
# startOpt_ok = StartStop_is_valid(startStart_Opt, startStop_Opt)
# stopOpt_ok = StartStop_is_valid(stopStart_Opt, stopStop_Opt)
startOpt_ok = True # Checks removed to allow same start/stop value
stopOpt_ok = True
# startCT_ok = StartStop_is_valid(startStart_CT, startStop_CT)
# stopCT_ok = StartStop_is_valid(stopStart_CT, stopStop_CT)
startCT_ok = True
stopCT_ok = True
guessOpt_ok = StartStop_is_valid(startGuess_Opt, stopGuess_Opt)
guessCT_ok = StartStop_is_valid(startGuess_CT, stopGuess_CT)
# Compile all start / stop energies for Opt and CT fit
if startOpt_ok and stopOpt_ok and guessOpt_ok and startCT_ok and stopCT_ok and guessCT_ok:
startRange_Opt = np.round(np.arange(startStart_Opt, startStop_Opt + 0.005, 0.01), 3).tolist() # Change step to 0.05
stopRange_Opt = np.round(np.arange(stopStart_Opt, stopStop_Opt + 0.005, 0.01), 3).tolist()
startRange_CT = np.round(np.arange(startStart_CT, startStop_CT + 0.005, 0.01), 3).tolist()
stopRange_CT = np.round(np.arange(stopStart_CT, stopStop_CT + 0.005, 0.01), 3).tolist()
guessRange_Opt = np.round(np.arange(startGuess_Opt, stopGuess_Opt + 0.1, 0.05), 3).tolist()
guessRange_CT = np.round(np.arange(startGuess_CT, stopGuess_CT + 0.1, 0.05), 3).tolist()
# Compile a dataFrame with all combinations of start / stop values for Opt and CT fit
self.logger.info('Compiling Fit Ranges ...')
df_Opt = pd.DataFrame()
df_CT = pd.DataFrame()
start_Opt_list = []
stop_Opt_list = []
start_CT_list = []
stop_CT_list = []
for startOpt in startRange_Opt:
for stopOpt in stopRange_Opt:
start_Opt_list.append(startOpt)
stop_Opt_list.append(stopOpt)
for startCT in startRange_CT:
for stopCT in stopRange_CT:
start_CT_list.append(startCT)
stop_CT_list.append(stopCT)
df_Opt['Start'] = start_Opt_list
df_Opt['Stop'] = stop_Opt_list
df_CT['Start'] = start_CT_list
df_CT['Stop'] = stop_CT_list
# Calculate all optical peak fits
self.logger.info('Calculating Optical Peak Fits ...')
cal_vals_Opt = list(map(lambda x: calculate_guess_fit(x=x,
df=df_Opt,
eqe=eqe,
function=self.MLJ_double_gaussian,
guessRange=guessRange_Opt
), tqdm(range(len(df_Opt)))))
best_vals_Opt = list(map(lambda list_: sep_list(list_, 0), cal_vals_Opt))
covar_Opt = list(map(lambda list_: sep_list(list_, 1), cal_vals_Opt))
R2_Opt = list(map(lambda list_: sep_list(list_, 2), cal_vals_Opt))
df_Opt['Fit'] = best_vals_Opt
df_Opt['Covar'] = covar_Opt
df_Opt['R2'] = R2_Opt
# Calculate CT state fits
start_Opt_list = []
stop_Opt_list = []
start_CT_list = []
stop_CT_list = []
best_vals_Opt = []
best_vals_CT = []
Opt_covar_list = []
CT_covar_list = []
R2_Opt = []
R2_CT = []
combined_R2_list = []
Opt_Fit_list = []
CT_Fit_list = []
combined_Fit_list = []
Energy_list = []
EQE_list = []
df_results = pd.DataFrame()
self.logger.info('Calculating CT State Fits ...')
if include_disorder:
self.logger.info('Including CT State Disorder ...')
# If Optical peak to be subtracted before CT fit
if self.ui.subtract_extraDoubleFit.isChecked() and not self.ui.bestSubtract_extraDoubleFit.isChecked():
self.logger.info('Subtracting All Optical Peak Fits ...')
for x in tqdm(range(len(df_Opt))):
for y in tqdm(range(len(df_CT))):
if df_Opt['R2'][x] > 0: # Check that the optical peak fit was successful
new_eqe = subtract_Opt(eqe, df_Opt['Fit'][x], T=self.T_xDouble)
if include_disorder:
best_vals, covar, p0, r_squared = guess_fit(eqe=new_eqe,
startE=df_CT['Start'][y],
stopE=df_CT['Stop'][y],
function=self.MLJ_double_disorder,
guessRange=guessRange_CT,
guessRange_sig=guessRange_Sig,
include_disorder=True,
bounds=True # to use fit model
)
else:
best_vals, covar, p0, r_squared = guess_fit(eqe=new_eqe,
startE=df_CT['Start'][y],
stopE=df_CT['Stop'][y],
function=self.MLJ_double,
guessRange=guessRange_CT,
include_disorder=False,
bounds=None # to use fit function
)
else:
best_vals = [0, 0, 0]
r_squared = 0
Opt_covar_list.append(df_Opt['Covar'][x])
CT_covar_list.append(covar)
start_Opt_list.append(df_Opt['Start'][x])
stop_Opt_list.append(df_Opt['Stop'][x])
start_CT_list.append(df_CT['Start'][y])
stop_CT_list.append(df_CT['Stop'][y])
best_vals_Opt.append(df_Opt['Fit'][x])
best_vals_CT.append(best_vals)
R2_Opt.append(df_Opt['R2'][x])
R2_CT.append(r_squared)
# Calculate combined fit here
parameter_dict = calculate_combined_fit_MLJ(stopE=df_Opt['Stop'][x],
best_vals_Opt=df_Opt['Fit'][x],
best_vals_CT=best_vals,
R2_Opt=df_Opt['R2'][x],
R2_CT=r_squared,
eqe=eqe,
T=self.T_xDouble,
S=self.S_Double,
hbarw=self.hbarw_Double,
bias=self.bias,
tolerance=self.tolerance,
range=increase_factor,
include_disorder=include_disorder
)
combined_R2_list.append(parameter_dict['R2_Combined'])
combined_Fit_list.append(parameter_dict['Combined_Fit'])
Opt_Fit_list.append(parameter_dict['Opt_Fit'])
CT_Fit_list.append(parameter_dict['CT_Fit'])
Energy_list.append(parameter_dict['Energy'])
EQE_list.append(parameter_dict['EQE'])
# If only best Optical peak is to be subtracted before CT fit
elif self.ui.bestSubtract_extraDoubleFit.isChecked() and not self.ui.subtract_extraDoubleFit.isChecked():
self.logger.info('Subtracting Only Best Optical Peak Fit ...')
# best_fit_index = df_Opt['Fit'][df_Opt['R2']==max(df_Opt['R2'])].index[0]
# print(best_fit_index)
# To avoid picking a fit that has a high R2 but moves above the data
advanced_R2_list = []
for x in range(len(df_Opt)):
wave_fit, energy_fit, eqe_fit, log_eqe_fit = compile_EQE(eqe,
df_Opt['Start'][x],
df_Opt['Stop'][x] * increase_factor,
1)
y_fit = [self.MLJ_double_gaussian(e,
df_Opt['Fit'][x][0],
df_Opt['Fit'][x][1],
df_Opt['Fit'][x][2]
) for e in energy_fit]
advanced_R2_list.append(R_squared(eqe_fit, y_fit))
df_Opt['Advanced R2'] = advanced_R2_list
best_fit_index = df_Opt['Fit'][df_Opt['Advanced R2'] == max(df_Opt['Advanced R2'])].index[0]
# print(best_fit_index)
new_eqe = subtract_Opt(eqe, df_Opt['Fit'][best_fit_index], T=self.T_xDouble)
for y in tqdm(range(len(df_CT))):
if include_disorder:
best_vals, covar, p0, r_squared = guess_fit(eqe=new_eqe,
startE=df_CT['Start'][y],
stopE=df_CT['Stop'][y],
function=self.MLJ_double_disorder,
guessRange=guessRange_CT,
guessRange_sig=guessRange_Sig,
include_disorder=True,
bounds=True # to use fit model
)
else:
best_vals, covar, p0, r_squared = guess_fit(eqe=new_eqe,
startE=df_CT['Start'][y],
stopE=df_CT['Stop'][y],
function=self.MLJ_double,
guessRange=guessRange_CT,
include_disorder=False,
bounds=None # to use fit function
)
Opt_covar_list.append(df_Opt['Covar'][x])
CT_covar_list.append(covar)
start_Opt_list.append(df_Opt['Start'][best_fit_index])
stop_Opt_list.append(df_Opt['Stop'][best_fit_index])
start_CT_list.append(df_CT['Start'][y])
stop_CT_list.append(df_CT['Stop'][y])
best_vals_Opt.append(df_Opt['Fit'][best_fit_index])
best_vals_CT.append(best_vals)
R2_Opt.append(df_Opt['R2'][best_fit_index])
R2_CT.append(r_squared)
# Calculate combined fit here
parameter_dict = calculate_combined_fit_MLJ(stopE=df_Opt['Stop'][x],
best_vals_Opt=df_Opt['Fit'][x],
best_vals_CT=best_vals,
R2_Opt=df_Opt['R2'][x],
R2_CT=r_squared,
eqe=eqe,
T=self.T_xDouble,
S=self.S_Double,
hbarw=self.hbarw_Double,
bias=self.bias,
tolerance=self.tolerance,
range=increase_factor,
include_disorder=include_disorder
)
combined_R2_list.append(parameter_dict['R2_Combined'])
combined_Fit_list.append(parameter_dict['Combined_Fit'])
Opt_Fit_list.append(parameter_dict['Opt_Fit'])
CT_Fit_list.append(parameter_dict['CT_Fit'])
Energy_list.append(parameter_dict['Energy'])
EQE_list.append(parameter_dict['EQE'])
# If Optical peak not to be subtracted before CT fit
elif not self.ui.subtract_extraDoubleFit.isChecked() and not self.ui.bestSubtract_extraDoubleFit.isChecked():
self.logger.info('Not Subtracting Optical Peak Fits.')
for x in tqdm(range(len(df_Opt))):
for y in tqdm(range(len(df_CT))):
if include_disorder:
best_vals, covar, p0, r_squared = guess_fit(eqe=eqe,
startE=df_CT['Start'][y],
stopE=df_CT['Stop'][y],
function=self.MLJ_double_disorder,
guessRange=guessRange_CT,
guessRange_sig=guessRange_Sig,
include_disorder=True,
bounds=True # to use fit model
)
else:
best_vals, covar, p0, r_squared = guess_fit(eqe=eqe,
startE=df_CT['Start'][y],
stopE=df_CT['Stop'][y],
function=self.MLJ_double,
guessRange=guessRange_CT,
include_disorder=False,
bounds=None # to use fit function
)
Opt_covar_list.append(df_Opt['Covar'][x])
CT_covar_list.append(covar)
start_Opt_list.append(df_Opt['Start'][x])
stop_Opt_list.append(df_Opt['Stop'][x])
start_CT_list.append(df_CT['Start'][y])
stop_CT_list.append(df_CT['Stop'][y])
best_vals_Opt.append(df_Opt['Fit'][x])
best_vals_CT.append(best_vals)
R2_Opt.append(df_Opt['R2'][x])
R2_CT.append(r_squared)
# Calculate combined fit here
parameter_dict = calculate_combined_fit_MLJ(stopE=df_Opt['Stop'][x],
best_vals_Opt=df_Opt['Fit'][x],
best_vals_CT=best_vals,
R2_Opt=df_Opt['R2'][x],
R2_CT=r_squared,
eqe=eqe,
T=self.T_xDouble,
S=self.S_Double,
hbarw=self.hbarw_Double,
bias=self.bias,
tolerance=self.tolerance,
range=increase_factor,
include_disorder=include_disorder
)
combined_R2_list.append(parameter_dict['R2_Combined'])
combined_Fit_list.append(parameter_dict['Combined_Fit'])
Opt_Fit_list.append(parameter_dict['Opt_Fit'])
CT_Fit_list.append(parameter_dict['CT_Fit'])
Energy_list.append(parameter_dict['Energy'])
EQE_list.append(parameter_dict['EQE'])
else:
self.logger.info('Please select valid fit settings.')
if len(best_vals_Opt) == len(best_vals_CT) and len(best_vals_Opt) != 0: # Confirm lists are acceptable
df_results['Start_Opt'] = start_Opt_list
df_results['Stop_Opt'] = stop_Opt_list
df_results['Fit_Opt'] = best_vals_Opt
df_results['R2_Opt'] = R2_Opt
df_results['Start_CT'] = start_CT_list
df_results['Stop_CT'] = stop_CT_list
df_results['Fit_CT'] = best_vals_CT
df_results['R2_CT'] = R2_CT
df_results['Covar_Opt'] = Opt_covar_list
df_results['Covar_CT'] = CT_covar_list
# Add combined fit to dataFrame
df_results['Total_R2'] = combined_R2_list
df_results['Total_Fit'] = combined_Fit_list
df_results['Opt_Fit'] = Opt_Fit_list
df_results['CT_Fit'] = CT_Fit_list
df_results['Energy'] = Energy_list
df_results['EQE'] = EQE_list
# Find best fit
self.logger.info('Determining Best Fit ...')
self.logger.info('Fit Results: ')
print("")
# Save fit data
if self.ui.save_extraDoubleFit.isChecked():
save_fit_file = filedialog.asksaveasfilename() # User to pick folder & name to save to
save_fit = True
self.logger.info('Saving fit data.')
else:
save_fit_file = None
save_fit = False
label = pick_EQE_Label(self.ui.textBox_extraDouble_label, self.ui.textBox_extraDouble)
n = int(self.ui.n_Extra.value())
for x in np.arange(1, n+1, 1):
print('-' * 80)
print(('Best Fit No. {} : ').format(x))
df_results = find_best_fit(df_both=df_results,
eqe=eqe,
T=self.T_xDouble,
label=label,
n_fit=x,
include_disorder=include_disorder,
save_fit=save_fit,
save_fit_file=save_fit_file
)
print(' ' * 80)
print('-' * 80)
self.bias = False
# -----------------------------------------------------------------------------------------------------------
# Gaussian fitting function for double fit
[docs]
def MLJ_double_gaussian(self, E, f, l, Eopt):
"""Marcus-Levich-Jortner theory to separately fit double peaks
Parameters
----------
E : list, required
List of energy values
f : float, required
Oscillator strength
l : float, required
Reorganization energy
Eopt : float, required
S1 peak energy
Returns
-------
EQE : float
EQE value
"""
return (f / (E * math.sqrt(4 * math.pi * l * self.T_xDouble * self.k))) * exp(
-(Eopt + l - E) ** 2 / (4 * l * self.k * self.T_xDouble))
# MLJ function
[docs]
def MLJ_double(self, E, f, l, Ect):
"""Marcus-Levich-Jortner theory for double peak fitting
Parameters
----------
E : list, required
List of energy values
f : float, required
Oscillator strength
l : float, required
Reorganization energy
Ect : float, required
CT state energy
Returns
-------
EQE : float
EQE value
"""
EQE = 0
for n in range(0, 6): # TODO: Check line breaks
EQE_n = (f / (E * math.sqrt(4 * math.pi * l * self.T_xDouble * self.k))) \
* (math.exp(-self.S_Double) * self.S_Double ** n / math.factorial(n)) \
* exp(-(Ect + l - E + n * self.hbarw_Double) ** 2 \
/ (4 * l * self.k * self.T_xDouble))
EQE += EQE_n
return EQE
# MLJ function including disorder
[docs]
def MLJ_double_disorder(self, E, f, l, Ect, sig):
"""Marcus-Levich-Jortner theory including disorder for double peak fitting
Parameters
----------
E : list, required
List of energy values
f : float, required
Oscillator strength
l : float, required
Reorganization energy
Ect : float, required
CT state energy
sig : float, required
Gaussian disorder
Returns
-------
EQE : float
EQE value
"""
EQE = 0
for n in range(0, 6): # TODO: Check line breaks
EQE_n = (f / (E * math.sqrt(2 * math.pi * (2 * l * self.T_xDouble * self.k + sig ** 2))) \
* (math.exp(-self.S_Double) * self.S_Double ** n / math.factorial(n)) \
* exp(-(Ect + l - E + n * self.hbarw_Double) ** 2 \
/ (4 * l * self.k * self.T_xDouble + 2 * sig ** 2)))
EQE += EQE_n
return EQE
# -----------------------------------------------------------------------------------------------------------
# Functions to clear plots
# -----------------------------------------------------------------------------------------------------------
# Function to clear "Calculate EQE" plot
[docs]
def clear_plot(self):
"""Function to clear plot
Parameters
----------
None
Returns
-------
None
"""
plt.close() # Close the current plot
self.ax1, self.ax2 = set_up_plot() # Setting up new plot is preferred over plt.clf() in case window was closed
# -----------------------------------------------------------------------------------------------------------
# Function to clear EQE plot
[docs]
def clear_EQE_plot(self):
"""Function to clear EQE fit plot
Parameters
----------
None
Returns
-------
None
"""
plt.close()
plt.close()
self.axFit_1, self.axFit_2 = set_up_EQE_plot()
# -----------------------------------------------------------------------------------------------------------
# Function to clear EL plot
[docs]
def clear_EL_plot(self):
"""Function to clear EL plot
Parameters
----------
None
Returns
-------
None
"""
plt.close()
plt.close()
self.axEL_1, self.axEL_2 = set_up_EL_plot()
# -----------------------------------------------------------------------------------------------------------
[docs]
def main():
app = QtWidgets.QApplication(sys.argv)
monoUI = MainWindow()
monoUI.show()
sys.exit(app.exec_())
if __name__ == '__main__':
try:
main()
except:
app = QtWidgets.QApplication(sys.argv)