Source code for simudo.example.fourlayer.sweep_extraction

import numpy as np

# import matplotlib
# matplotlib.use('Agg') # tells matplotlib not to load any gui display functions
import matplotlib.pyplot as plt
from os import path
from glob import glob
from .IV_Analysis import IV_params
import pandas as pd
import yaml
import attr
from cached_property import cached_property

try:
    from simudo.io import h5yaml

    loader = h5yaml.load
except:
    loader = yaml.load

__all__ = [
    "SweepData",
    "jv_plot",
    "IB_band_diagram",
    "IB_generation_diagram",
    "subgap_generation_mismatch_diagram",
    "subgap_mismatch",
]


[docs]@attr.s class SweepData: """Extracts data from fourlayer simudo runs Parameters ---------- folder: str Location of ``plot_meta`` files. parameter_name: str, optional Sweep parameter name, usually ``pd_V`` for a voltage sweep (but was ``a parameter`` at some point in history). (default: ``"pd_V"``) Notes ----- The most important properties are: :py:attr:`jv`: Extract j, v, p as pandas dataframe :py:attr:`mpp_row`: Determine which file contains data closest to max power point :py:attr:`voc_row`: Determine which file contains data closest to open circuit :py:attr:`v_row`: Determine which file contains data closest to specified voltage :py:attr:`get_spatial_data`: Read in spatial data for plotting from specified file :py:attr:`IB_mask`: Mask suitable for plotting properties only in IB region The output of the :py:func:`SweepData.jv` can be given to :py:func:`IV_Analysis.IV_params` to find the max power point and efficiency Example ------- :: data = SweepData(filename) spatial = data.get_spatial_data(data.mpp_row) IB_mask = data.IB_mask(spatial) IB_band_diagram(spatial,IB_mask) """ folder = attr.ib() parameter_name = attr.ib(default="pd_V") @cached_property def jv(self): """ Create a dataframe with j, v, p and names of files that contain those data """ # If there is a pd_V.csv, could just read that in. # Instead we parse all the individual V files, because this ensures we get the right filename for plots at fixed V # Otherwise, read j, v from plot_meta.yaml files for each voltage point # return them in a Pandas DataFrame as self.jv df_list = [] # May need tweaks to target correct filenames, if fourlayer.run changes files = glob( path.join(self.folder, f"{self.parameter_name}=*.plot_meta.yaml") ) if not files: print("no files found") for fname in files: # parse yaml data with open(fname) as f: data = loader(f) try: if self.parameter_name == "pd_I": # This is cheating, storing intensity in the V variable v = data["sweep_parameter:I"]["value"] else: v = data["sweep_parameter:V"]["value"] except: # old version of names # v = data["parameter_value"]["value"] v = data["sweep_parameter:parameter"]["value"] j = ( data["avg:current_CB:n_contact"]["value"] + data["avg:current_VB:n_contact"]["value"] ) df_list.append({"j": j, "v": v, "p": j * v, "file": fname}) df = pd.DataFrame(df_list) # Sort the dataframe by voltage and make the indices go in that order df.sort_values("v", inplace=True) df.reset_index(drop=True, inplace=True) # self.jv = df #Uncomment this line if removing the @cached_property return df @cached_property def mpp_row(self): # Find the row closest to max power point, for plotting. # Not a good way to find efficiency, as it depends on the voltages calculated index = self.jv["p"].idxmin() return self.jv.loc[index] @cached_property def voc_row(self): # Find the row closest to Voc index = abs(self.jv["j"]).idxmin() return self.jv.loc[index]
[docs] def v_row(self, V): # Find row closest to voltage V index = abs(self.jv["v"] - V).idxmin() return self.jv.loc[index]
@cached_property def params(self): # Get params from submit.yaml file with open(path.join(self.folder, "submit.yaml")) as stream: par = loader(stream)["parameters"] return par
[docs] def get_spatial_data(self, row): # Read in the spatial plot file corresponding to desired row (returned by mpp_row, voc_row, or other) spatial_file = row["file"].split(".plot_meta.yaml")[0] + ".csv.0" return np.genfromtxt( spatial_file, delimiter=",", names=True, deletechars="" )
[docs] def IB_mask(self, spatial_data): # For spatial_data (as returned by get_spatial_data), return a mask showing where the IB is located if not "p_thickness" in self.params: # Old default values self.params["FSF_thickness"] = 0.05 self.params["p_thickness"] = 1 self.params["n_thickness"] = 1 x0 = float(self.params["FSF_thickness"]) + float( self.params["p_thickness"] ) x1 = x0 + float(self.params["IB_thickness"]) return np.where( (spatial_data["coord_x"] > x0) & (spatial_data["coord_x"] < x1) )
# Some sample figures that can be made. # Take as inputs the spatial data from SweepData.get_spatial_data and the mask from SweepData.IB_mask
[docs]def IB_band_diagram(spatial, IB_mask): # spatial, IB_mask as returned by data.get_spatial_data and data.IB_mask plt.plot(spatial["coord_x"], spatial["Ephi_CB"], color="k") plt.plot(spatial["coord_x"], (spatial["Ephi_VB"]), color="k") plt.plot( spatial["coord_x"][IB_mask], spatial["Ephi_IB"][IB_mask], color="k" ) plt.plot( spatial["coord_x"], (spatial["qfl_CB"]), color="blue", linestyle="--", label=r"$E_{F,C}$", ) plt.plot( spatial["coord_x"][IB_mask], spatial["qfl_IB"][IB_mask], color="orange", linestyle="--", label=r"$E_{F,I}$", ) plt.plot( spatial["coord_x"], (spatial["qfl_VB"]), color="red", linestyle="--", label=r"$E_{F,V}$", ) plt.xlabel(r"x ($\mu$m)") plt.ylabel(r"Energy (eV)")
# plt.legend()
[docs]def IB_generation_diagram(spatial, IB_mask): # spatial, IB_mask as returned by data.get_spatial_data and data.IB_mask plt.semilogy( spatial["coord_x"], (spatial["g_CB"]), color="blue", label=r"g$_{CB}$" ) plt.semilogy( spatial["coord_x"][IB_mask], (spatial["g_IB"])[IB_mask], color="orange", label=r"g$_{IB}$", ) plt.semilogy( spatial["coord_x"][IB_mask], -(spatial["g_IB"])[IB_mask], color="orange", linestyle="--", label=r"-g$_{IB}$", ) plt.semilogy( spatial["coord_x"], (spatial["g_VB"]), color="red", label=r"g$_{VB}$" ) plt.xlabel(r"X position ($\mu$m)") plt.ylabel(r"g (cm$^{-3}$s$^{-1}$)")
# plt.legend()
[docs]def subgap_generation_mismatch_diagram(spatial, IB_mask): # spatial, IB_mask as returned by data.get_spatial_data and data.IB_mask mismatch = ( spatial["g_opt_ci_IB"] + spatial["g_opt_iv_IB"] ) # the CI term is always negative plt.semilogy( spatial["coord_x"][IB_mask], mismatch[IB_mask], color="blue", label=r"$g_{ci}-g_{iv}$", ) plt.semilogy( spatial["coord_x"][IB_mask], -mismatch[IB_mask], color="red", label=r"$g_{iv}-g_{ci}$", ) # plt.xlabel(r"X position ($\mu$m)") plt.ylabel(r"g (cm$^{-3}$s$^{-1}$)")
# plt.legend()
[docs]def jv_plot(df): plt.plot(df["v"], df["j"]) plt.xlabel(r"V (V)") plt.ylabel(r"J (mA/cm$^2$)")
[docs]def subgap_mismatch(spatial, IB_mask): # integrate the mismatch mismatch = (spatial["g_opt_ci_IB"] + spatial["g_opt_iv_IB"])[ IB_mask ] # the CI term is always negative dx = np.diff(spatial["coord_x"][IB_mask]) subgap_mismatch = np.sum(mismatch[0:-1] ** 2 * dx) # crude integral subgap_gen = np.sum( (abs(spatial["g_opt_iv_IB"]) + abs(spatial["g_opt_ci_IB"]))[IB_mask][ 0:-1 ] ** 2 * dx ) return subgap_mismatch / subgap_gen