# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
from pv_tandem.utils import (
calc_temp_from_NOCT,
calc_current,
interp_eqe_to_spec,
)
from typing import List, Dict, Optional
from pv_tandem import irradiance_models
from scipy.special import lambertw
[docs]class OneDiodeModel:
"""
A class to calculate the performance of a solar cell with a one diode model.
Parameters
----------
tcJsc : float or np.ndarray
The temperature coefficient of the short-circuit current.
tcVoc : float or np.ndarray
The temperature coefficient of the open-circuit voltage.
R_shunt : float or np.ndarray
The shunt resistance.
R_series : float or np.ndarray
The series resistance.
n : float or np.ndarray
The diode ideality factor.
j0 : float or np.ndarray
The reverse saturation current.
Returns
-------
None
"""
def __init__(self, tcJsc, tcVoc, R_shunt, R_series, n, j0):
self.tcJsc = tcJsc
self.tcVoc = tcVoc
# severeal parameters are converted to 1d arrays if are initilized to a
# scalar, in order to ensure that broadcasting works in the IV functions
if type(R_shunt) == np.ndarray:
self.R_shunt = R_shunt
else:
self.R_shunt = np.array([R_shunt])
if type(R_series) == np.ndarray:
self.R_series = R_series
else:
self.R_series = np.array([R_series])
if type(n) == np.ndarray:
self.n = n
else:
self.n = np.array([n])
if type(j0) == np.ndarray:
self.j0 = j0
else:
self.j0 = np.array([j0])
[docs] def calc_iv(self, Jsc, cell_temp, j_arr):
def lambertw_exp_large(x):
result = x - np.log(x) + np.log(x) / x
return result
def lambertwlog(x):
res = np.zeros_like(x)
large_x_mask = x > 10
small_x = np.real(lambertw(np.exp(x[~large_x_mask]), tol=1e-8))
large_x = lambertw_exp_large(x[large_x_mask])
#x = np.where(large_x_mask, large_x, small_x)
res[~large_x_mask] = small_x
res[large_x_mask] = large_x
return res
# Thermal voltage at room temperature in V
Vth = 0.02569
factorJsc = 1 + self.tcJsc * (cell_temp - 25)
factorVoc = 1 + self.tcVoc * (cell_temp - 25)
Jsc = Jsc * factorJsc
Voc_rt = (
Jsc / 1000 * self.R_shunt
- self.n
* Vth
* lambertwlog(
(
np.log(self.j0 / 1000 * self.R_shunt)
+ self.R_shunt * (Jsc + self.j0) / (1000 * self.n * Vth)
- np.log(self.n * Vth)
)
)
+ self.j0 / 1000 * self.R_shunt
)
Voc_rt[Jsc < 0.1] = np.nan
Voc = Voc_rt * factorVoc
lambw = (
np.log(self.j0 / 1000 * self.R_shunt)[:, None]
+ (
self.R_shunt[:, None]
* ((np.subtract.outer(Jsc, j_arr) + self.j0[:, None]))
/ (1000 * self.n[:, None] * Vth)
)
- np.log(self.n * Vth)[:, None]
)
try:
V = (
np.subtract.outer(
Jsc / 1000 * self.R_shunt,
j_arr / 1000 * (self.R_shunt + self.R_series),
)
- (self.n * Vth)[:, None] * lambertwlog((lambw))
+ (self.j0 / 1000 * self.R_shunt - Voc_rt + Voc)[:, None]
)
except:
V = (
(
(Jsc / 1000 * self.R_shunt)[:, None]
- (j_arr / 1000 * (self.R_shunt + self.R_series)[:, None])
)
- (self.n * Vth)[:, None] * lambertwlog((lambw))
+ (self.j0 / 1000 * self.R_shunt - Voc_rt + Voc)[:, None]
)
if hasattr(Jsc, "__len__"):
return np.real(V)
else:
return np.real(V)[0]
[docs] def calc_iv_params(self, Jsc, cell_temp, j_arr=np.linspace(0, 45, 451)):
index = None
if hasattr(Jsc, "values"):
index = Jsc.index
Jsc = Jsc.values
if hasattr(cell_temp, "values"):
cell_temp = cell_temp.values
V = self.calc_iv(Jsc, cell_temp, j_arr)
P = V * j_arr
idx_mpp = np.nanargmax(P, axis=1)
Vmpp = V[np.arange(0, len(V)), idx_mpp]
Voc = V[:, 0]
Pmax = np.nanmax(P, axis=1)
Jmpp = j_arr[idx_mpp]
Jsc = j_arr[(np.argmax((V < 0), axis=1) - 1).clip(min=0)]
FF = abs(Pmax) / abs(Jsc * Voc)
res = pd.DataFrame(
{
"Voc": Voc,
"Vmpp": Vmpp,
"Pmax": Pmax,
"FF": FF,
"Jsc": Jsc,
"Jmpp": Jmpp,
}
)
# set index from Jsc if it was a pd.series
if index is not None:
res.index = index
return res
class _TandemSimulator:
"""
A class to represent a Tandem Simulator.
Parameters
----------
electrical_parameters : dict
Electrical parameters of the One Diode Models.
subcell_names : list
Names of the subcells.
eqe : pandas.Dataframe
External quantum efficiency. The index of the DataFrame has to represent
the wavelenghts in nm and the columns have to be named with the names used
in the subcell_names list
eqe_back : pandas.Dataframe, optional
Backside external quantum efficiency if bifacial illumination is to be considered.
bifacial : bool
Flag to represent if the simulator is bifacial.
electrical_models : dict
Electrical models for each subcell.
j_arr : ndarray
Array that specifies for which current densities (mA/cm2) the voltage is
evaluated.
Examples
--------
>>> eqe = pd.DataFrame(index=np.arange(300,1205,5))
>>> # Unphysical EQE, just for demonstration
>>> eqe[['pero','si']] = 0.4
>>> electrical_parameters = {
>>> "Rsh": {"pero": 1000, "si": 3000},
>>> "RsTandem": 3,
>>> "j0": {"pero": 2.7e-18, "si": 1e-12},
>>> "n": {"pero": 1.1, "si": 1},
>>> "Temp": {"pero": 25, "si": 25},
>>> "noct": {"pero": 48, "si": 48},
>>> "tcJsc": {"pero": 0.0002, "si": 0.00032},
>>> "tcVoc": {"pero": -0.002, "si": -0.0041},
>>> }
>>> tandem = TandemSimulator(eqe=eqe,
>>> electrical_parameters=electrical_parameters,
>>> subcell_names=["pero", "si"])
>>> iv_df = tandem.calc_IV_stc()
>>> power = iv_df.tandem * iv_df.index
>>> idx_power_max = power.idxmax()
>>> print(f'''
>>> Maximum efficiency: {power.max():.1f} %,
>>> Vmpp tandem: {iv_df.loc[idx_power_max,"tandem"]:.2f} V
>>> Vmpp perovskite: {iv_df.loc[idx_power_max,"pero"]:.2f} V
>>> Vmpp silicon: {iv_df.loc[idx_power_max,"si"]:.2f} V
>>> ''')
Maximum efficiency: 30.4 %,
Vmpp tandem: 1.78 V
Vmpp perovskite: 1.09 V
Vmpp silicon: 0.69 V
"""
def __init__(
self,
eqe,
electrical_parameters: Dict,
subcell_names: List[str],
eqe_back: Optional[float] = None,
bifacial: bool = False,
) -> None:
self.electrics = electrical_parameters
self.subcell_names = subcell_names
self.eqe = eqe
self.eqe_back = eqe_back
self.bifacial = bifacial
self.electrical_models = {}
self.j_arr = np.linspace(0, 45, 451)
# R_series is initilized with a placeholder because it has to be individually
# set by the __init__ function of the child classes for 2T and 4T
def calculate_Jsc(
self,
spec_irrad_front: pd.DataFrame,
spec_irrad_back: Optional[pd.DataFrame] = None,
) -> pd.DataFrame:
"""
Calculates the photocurrent from timeseries of impinging spectrum (W/nm/m²)
and the EQE defined at class initiation.
Parameters
----------
spec_irrad_front : pandas.Dataframe
Time series of spectral irradiance in the plane of the solar cell
front side. The names columns of the DataFrame have to be the wavelength
of the incidenting light in nm.
spec_irrad_back : pandas.Dataframe
Time series of spectral irradiance in the plane of the solar cell
back side in case of a bifacial tandem. The names columns of the
DataFrame have to be the wavelength of the incidenting light in nm.
Returns
-------
current : pd.DataFrame
Current generated in the subcells of the solar cell in mA/m²
Notes
-----
See :ref:'plot_jph_from_spec.py'
"""
Jsc = []
for subcell in self.subcell_names:
Jsc_loop = pd.Series(
calc_current(spec_irrad_front, self.eqe[subcell]) / 10,
name=subcell,
)
Jsc.append(Jsc_loop)
Jsc = pd.concat(Jsc, axis=1)
if self.bifacial is True:
for subcell in self.subcell_names:
Jsc_backside = pd.Series(
calc_current(spec_irrad_back, self.eqe_back[subcell]) / 10,
name=subcell,
)
Jsc[subcell] = Jsc[subcell] + Jsc_backside
return Jsc
def calc_IV_individual(self, Jsc, cell_temps):
"""
Calculates the IV curves on the grid spcified by j_arr from the photocurrent
of the individual cells.
Parameters
----------
Jsc : pandas.Dataframe
Time series of spectral irradiance in the plane of the solar cell
back side in case of a bifacial tandem. The names columns of the
DataFrame have to be the wavelength of the incidenting light in nm.
cell_temps : pandas.Dataframe
Time series of the cell temperatures. The columns of the dataframe
expected to be named like the subcell_names.
return_subsells : Bool
Defines if the voltage of the subcells should be returned alongside
the tandem voltage.
Returns
-------
If return_subsells is False:
V_tandem : pd.DataFrame
Dataframe containing IV data where the rows represent the timestamps
of the Jsc timeseries and the columns the current generated by the
cells (in mA/cm2)
If return_subsells is True:
V_tandem : pd.DataFrame
Dataframe containing IV data where the rows represent the timestamps
of the Jsc timeseries and the columns the current generated by the
cells (in mA/cm2)
V : pd.DataFrame
Dataframe containing IV data where the rows represent the timestamps
of the Jsc timeseries and the columns are a multiindex, where the
first level represents the subcells and the second level the current
generated by the cells (in mA/cm2)
Example
-------
See :ref:`sphx_glr_auto_examples_plot_tandem_ey.py` for a usage example.
"""
V = []
for subcell in self.subcell_names:
# if type(cell_temps) == pd.Series:
# cell_temps = self.cell_temps[subcell].values
V.append(
pd.DataFrame(
self.electrical_models[subcell].calc_iv(
Jsc[subcell].values,
cell_temps[subcell].values,
self.j_arr,
),
columns=self.j_arr,
)
)
V = pd.concat(V, axis=1, keys=self.subcell_names)
return V
def calc_IV_stc(self, backside_current=None):
"""
Calculates the voltage for the IV curve of the tandem solar cell under
standart test conditions at the respective current density defined by
j_arr.
Parameters
----------
backside_current : dict
Manual backside current contribution (in mA/cm2) for bifacial tandem.
Returns
-------
Voltage : pd.DataFrame
DataFrame with the subcell and combined tandem voltage at the respective
current density (mA/cm2) as index.
Example
-------
Examples
--------
>>> eqe = pd.DataFrame(index=np.arange(300,1205,5))
>>> # Unphysical EQE, just for demonstration
>>> eqe[['pero','si']] = 0.4
>>> electrical_parameters = {
>>> "Rsh": {"pero": 1000, "si": 3000},
>>> "RsTandem": 3,
>>> "j0": {"pero": 2.7e-18, "si": 1e-12},
>>> "n": {"pero": 1.1, "si": 1},
>>> "Temp": {"pero": 25, "si": 25},
>>> "noct": {"pero": 48, "si": 48},
>>> "tcJsc": {"pero": 0.0002, "si": 0.00032},
>>> "tcVoc": {"pero": -0.002, "si": -0.0041},
>>> }
>>> tandem = TandemSimulator(eqe=eqe,
>>> electrical_parameters=electrical_parameters,
>>> subcell_names=["pero", "si"])
>>> iv_df = tandem.calc_IV_stc()
>>> power = iv_df.tandem * iv_df.index
>>> idx_power_max = power.idxmax()
>>> print(f'''
>>> Maximum efficiency: {power.max():.1f} %,
>>> Vmpp tandem: {iv_df.loc[idx_power_max,"tandem"]:.2f} V
>>> Vmpp perovskite: {iv_df.loc[idx_power_max,"pero"]:.2f} V
>>> Vmpp silicon: {iv_df.loc[idx_power_max,"si"]:.2f} V
>>> ''')
Maximum efficiency: 30.4 %,
Vmpp tandem: 1.78 V
Vmpp perovskite: 1.09 V
Vmpp silicon: 0.69 V
"""
j_ph_stc = irradiance_models.AM15g().calc_jph(self.eqe) / 10
if backside_current is not None:
j_ph_stc = j_ph_stc + backside_current
V = []
for subcell in self.subcell_names:
V.append(
pd.DataFrame(
self.electrical_models[subcell].calc_iv(
np.array([j_ph_stc[subcell]]),
25,
self.j_arr,
)
)
)
V_tandem = pd.concat(V, axis=1, keys=self.subcell_names)
V_tandem = V_tandem.stack().reset_index(drop=True).astype(float)
V_tandem["tandem"] = V_tandem.sum(axis=1)
V_tandem.index = self.j_arr
V_tandem.index = V_tandem.index.rename("current")
return V_tandem
[docs]class TandemSimulator2T(_TandemSimulator):
"""
A class to represent a Tandem Simulator.
Parameters
----------
electrical_parameters : dict
Electrical parameters of the One Diode Models.
subcell_names : list
Names of the subcells.
eqe : pandas.Dataframe
External quantum efficiency. The index of the DataFrame has to represent
the wavelenghts in nm and the columns have to be named with the names used
in the subcell_names list
eqe_back : pandas.Dataframe, optional
Backside external quantum efficiency if bifacial illumination is to be considered.
bifacial : bool
Flag to represent if the simulator is bifacial.
electrical_models : dict
Electrical models for each subcell.
j_arr : ndarray
Array that specifies for which current densities (mA/cm2) the voltage is
evaluated.
Examples
--------
>>> eqe = pd.DataFrame(index=np.arange(300,1205,5))
>>> # Unphysical EQE, just for demonstration
>>> eqe[['pero','si']] = 0.4
>>> electrical_parameters = {
>>> "Rsh": {"pero": 1000, "si": 3000},
>>> "RsTandem": 3,
>>> "j0": {"pero": 2.7e-18, "si": 1e-12},
>>> "n": {"pero": 1.1, "si": 1},
>>> "Temp": {"pero": 25, "si": 25},
>>> "noct": {"pero": 48, "si": 48},
>>> "tcJsc": {"pero": 0.0002, "si": 0.00032},
>>> "tcVoc": {"pero": -0.002, "si": -0.0041},
>>> }
>>> tandem = TandemSimulator(eqe=eqe,
>>> electrical_parameters=electrical_parameters,
>>> subcell_names=["pero", "si"])
>>> iv_df = tandem.calc_IV_stc()
>>> power = iv_df.tandem * iv_df.index
>>> idx_power_max = power.idxmax()
>>> print(f'''
>>> Maximum efficiency: {power.max():.1f} %,
>>> Vmpp tandem: {iv_df.loc[idx_power_max,"tandem"]:.2f} V
>>> Vmpp perovskite: {iv_df.loc[idx_power_max,"pero"]:.2f} V
>>> Vmpp silicon: {iv_df.loc[idx_power_max,"si"]:.2f} V
>>> ''')
Maximum efficiency: 30.4 %,
Vmpp tandem: 1.78 V
Vmpp perovskite: 1.09 V
Vmpp silicon: 0.69 V
"""
def __init__(
self,
eqe,
electrical_parameters: Dict,
subcell_names: List[str],
eqe_back: Optional[float] = None,
bifacial: bool = False,
) -> None:
super().__init__(
eqe, electrical_parameters, subcell_names, eqe_back, bifacial
)
for subcell in subcell_names:
self.electrical_models[subcell] = OneDiodeModel(
tcJsc=self.electrics["tcJsc"][subcell],
tcVoc=self.electrics["tcVoc"][subcell],
R_shunt=self.electrics["Rsh"][subcell],
R_series=self.electrics["RsTandem"]/2,
n=self.electrics["n"][subcell],
j0=self.electrics["j0"][subcell],
)
[docs] def calc_IV(self, Jsc, cell_temps, return_subsells=False):
"""
Calculates the IV curves on the grid spcified by j_arr from the photocurrent
of the individual cells.
Parameters
----------
Jsc : pandas.Dataframe
Time series of spectral irradiance in the plane of the solar cell
back side in case of a bifacial tandem. The names columns of the
DataFrame have to be the wavelength of the incidenting light in nm.
cell_temps : pandas.Dataframe
Time series of the cell temperatures. The columns of the dataframe
expected to be named like the subcell_names.
return_subsells : Bool
Defines if the voltage of the subcells should be returned alongside
the tandem voltage.
Returns
-------
If return_subsells is False:
V_tandem : pd.DataFrame
Dataframe containing IV data where the rows represent the timestamps
of the Jsc timeseries and the columns the current generated by the
cells (in mA/cm2)
If return_subsells is True:
V_tandem : pd.DataFrame
Dataframe containing IV data where the rows represent the timestamps
of the Jsc timeseries and the columns the current generated by the
cells (in mA/cm2)
V : pd.DataFrame
Dataframe containing IV data where the rows represent the timestamps
of the Jsc timeseries and the columns are a multiindex, where the
first level represents the subcells and the second level the current
generated by the cells (in mA/cm2)
Example
-------
See :ref:`sphx_glr_auto_examples_plot_tandem_ey.py` for a usage example.
"""
V = self.calc_IV_individual(Jsc, cell_temps)
V_tandem = V.groupby(level=1, axis=1).sum()
if return_subsells:
return V_tandem, V
else:
return V_tandem
[docs] def calc_power(
self,
spec_irrad: pd.DataFrame,
cell_temps: pd.DataFrame,
backside_current: Optional[pd.DataFrame] = None,
) -> pd.Series:
"""
Calculates the maximum power output density from timeseries of impinging spectrum
(W/nm/m²) and the cell temperature.
Parameters
----------
spec_irrad_front : pandas.Dataframe
Time series of spectral irradiance in the plane of the solar cell front side. The
names columns of the DataFrame have to be the wavelength of the incidenting
light in nm.
cell_temps : pandas.Dataframe
Time series of the cell temperatures. The columns of the dataframe
expected to be named like the subcell_names.
backside_current : pandas.Dataframe
Manual backside current contribution (in mA/cm2) for bifacial tandem.
The DataFrame needs to contain a column for each subcell in the tandem.
Returns
-------
Power : pd.Series
Time series of power output density at the maximum power point.
Example
-------
See :ref:`sphx_glr_auto_examples_plot_tandem_ey.py` for a usage example.
"""
if self.bifacial is True:
Jsc = self.calculate_Jsc(spec_irrad["front"], spec_irrad["back"])
else:
Jsc = self.calculate_Jsc(spec_irrad)
if backside_current is not None:
Jsc = Jsc + backside_current
V_tandem = self.calc_IV(Jsc, cell_temps)
P = V_tandem.values * self.j_arr[None, :]
P_max = P.max(axis=1)
P_max = pd.Series(P_max, index=spec_irrad.index)
return P_max
[docs] def calc_IV_stc(self, backside_current=None):
"""
Calculates the voltage for the IV curve of the tandem solar cell under
standart test conditions at the respective current density defined by
j_arr.
Parameters
----------
backside_current : dict
Manual backside current contribution (in mA/cm2) for bifacial tandem.
Returns
-------
Voltage : pd.DataFrame
DataFrame with the subcell and combined tandem voltage at the respective
current density (mA/cm2) as index.
Example
-------
>>> eqe = pd.DataFrame(index=np.arange(300,1205,5))
>>> # Unphysical EQE, just for demonstration
>>> eqe[['pero','si']] = 0.4
>>> electrical_parameters = {
>>> "Rsh": {"pero": 1000, "si": 3000},
>>> "RsTandem": 3,
>>> "j0": {"pero": 2.7e-18, "si": 1e-12},
>>> "n": {"pero": 1.1, "si": 1},
>>> "Temp": {"pero": 25, "si": 25},
>>> "noct": {"pero": 48, "si": 48},
>>> "tcJsc": {"pero": 0.0002, "si": 0.00032},
>>> "tcVoc": {"pero": -0.002, "si": -0.0041},
>>> }
>>> tandem = TandemSimulator(eqe=eqe,
>>> electrical_parameters=electrical_parameters,
>>> subcell_names=["pero", "si"])
>>> iv_df = tandem.calc_IV_stc()
>>> power = iv_df.tandem * iv_df.index
>>> idx_power_max = power.idxmax()
>>> print(f'''
>>> Maximum efficiency: {power.max():.1f} %,
>>> Vmpp tandem: {iv_df.loc[idx_power_max,"tandem"]:.2f} V
>>> Vmpp perovskite: {iv_df.loc[idx_power_max,"pero"]:.2f} V
>>> Vmpp silicon: {iv_df.loc[idx_power_max,"si"]:.2f} V
>>> ''')
Maximum efficiency: 30.4 %,
Vmpp tandem: 1.78 V
Vmpp perovskite: 1.09 V
Vmpp silicon: 0.69 V
"""
j_ph_stc = irradiance_models.AM15g().calc_jph(self.eqe) / 10
if backside_current is not None:
j_ph_stc = j_ph_stc + backside_current
_, V_tandem = self.calc_IV(
j_ph_stc.to_frame().T,
pd.Series([25, 25], index=self.subcell_names).to_frame().T,
return_subsells=True,
)
V_tandem = V_tandem.stack().reset_index(drop=True).astype(float)
V_tandem["tandem"] = V_tandem.sum(axis=1)
V_tandem.index = self.j_arr
V_tandem.index = V_tandem.index.rename("current")
return V_tandem
[docs]class TandemSimulator4T(_TandemSimulator):
"""
A class to represent a 4 terminal Tandem Simulator.
Parameters
----------
electrical_parameters : dict
Electrical parameters of the One Diode Models.
subcell_names : list
Names of the subcells.
eqe : pandas.Dataframe
External quantum efficiency. The index of the DataFrame has to represent
the wavelenghts in nm and the columns have to be named with the names used
in the subcell_names list
eqe_back : pandas.Dataframe, optional
Backside external quantum efficiency if bifacial illumination is to be considered.
bifacial : bool
Flag to represent if the simulator is bifacial.
electrical_models : dict
Electrical models for each subcell.
j_arr : ndarray
Array that specifies for which current densities (mA/cm2) the voltage is
evaluated.
Examples
--------
>>> eqe = pd.DataFrame(index=np.arange(300,1205,5))
>>> # Unphysical EQE, just for demonstration
>>> eqe[['pero','si']] = 0.4
>>> electrical_parameters = {
>>> "Rsh": {"pero": 1000, "si": 3000},
>>> "RsTandem": 3,
>>> "j0": {"pero": 2.7e-18, "si": 1e-12},
>>> "n": {"pero": 1.1, "si": 1},
>>> "Temp": {"pero": 25, "si": 25},
>>> "noct": {"pero": 48, "si": 48},
>>> "tcJsc": {"pero": 0.0002, "si": 0.00032},
>>> "tcVoc": {"pero": -0.002, "si": -0.0041},
>>> }
>>> tandem = TandemSimulator(eqe=eqe,
>>> electrical_parameters=electrical_parameters,
>>> subcell_names=["pero", "si"])
>>> iv_df = tandem.calc_IV_stc()
>>> power = iv_df.tandem * iv_df.index
>>> idx_power_max = power.idxmax()
>>> print(f'''
>>> Maximum efficiency: {power.max():.1f} %,
>>> Vmpp tandem: {iv_df.loc[idx_power_max,"tandem"]:.2f} V
>>> Vmpp perovskite: {iv_df.loc[idx_power_max,"pero"]:.2f} V
>>> Vmpp silicon: {iv_df.loc[idx_power_max,"si"]:.2f} V
>>> ''')
Maximum efficiency: 30.4 %,
Vmpp tandem: 1.78 V
Vmpp perovskite: 1.09 V
Vmpp silicon: 0.69 V
"""
def __init__(
self,
eqe,
electrical_parameters: Dict,
subcell_names: List[str],
eqe_back: Optional[float] = None,
bifacial: bool = False,
) -> None:
super().__init__(
eqe, electrical_parameters, subcell_names, eqe_back, bifacial
)
for subcell in subcell_names:
self.electrical_models[subcell] = OneDiodeModel(
tcJsc=self.electrics["tcJsc"][subcell],
tcVoc=self.electrics["tcVoc"][subcell],
R_shunt=self.electrics["Rsh"][subcell],
R_series=self.electrics["Rs"][subcell],
n=self.electrics["n"][subcell],
j0=self.electrics["j0"][subcell],
)
[docs] def calc_IV(self, Jsc, cell_temps, return_subsells=False):
"""
Calculates the IV curves on the grid spcified by j_arr from the photocurrent
of the individual cells.
Parameters
----------
Jsc : pandas.Dataframe
Time series of spectral irradiance in the plane of the solar cell
back side in case of a bifacial tandem. The names columns of the
DataFrame have to be the wavelength of the incidenting light in nm.
cell_temps : pandas.Dataframe
Time series of the cell temperatures. The columns of the dataframe
expected to be named like the subcell_names.
Returns
-------
If return_subsells is False:
V_tandem : pd.DataFrame
Dataframe containing IV data where the rows represent the timestamps
of the Jsc timeseries and the columns the current generated by the
cells (in mA/cm2)
If return_subsells is True:
V_tandem : pd.DataFrame
Dataframe containing IV data where the rows represent the timestamps
of the Jsc timeseries and the columns the current generated by the
cells (in mA/cm2)
V : pd.DataFrame
Dataframe containing IV data where the rows represent the timestamps
of the Jsc timeseries and the columns are a multiindex, where the
first level represents the subcells and the second level the current
generated by the cells (in mA/cm2)
Example
-------
See :ref:`sphx_glr_auto_examples_plot_tandem_ey.py` for a usage example.
"""
V = self.calc_IV_individual(Jsc, cell_temps)
return V
[docs] def calc_power(
self,
spec_irrad: pd.DataFrame,
cell_temps: pd.DataFrame,
backside_current: Optional[pd.DataFrame] = None,
) -> pd.Series:
"""
Calculates the maximum power output density from timeseries of impinging spectrum
(W/nm/m²) and the cell temperature.
Parameters
----------
spec_irrad_front : pandas.Dataframe
Time series of spectral irradiance in the plane of the solar cell front side. The
names columns of the DataFrame have to be the wavelength of the incidenting
light in nm.
cell_temps : pandas.Dataframe
Time series of the cell temperatures. The columns of the dataframe
expected to be named like the subcell_names.
backside_current : pandas.Dataframe
Manual backside current contribution (in mA/cm2) for bifacial tandem.
The DataFrame needs to contain a column for each subcell in the tandem.
Returns
-------
Power : pd.Series
Time series of power output density at the maximum power point.
Example
-------
See :ref:`sphx_glr_auto_examples_plot_tandem_ey.py` for a usage example.
"""
if self.bifacial is True:
Jsc = self.calculate_Jsc(spec_irrad["front"], spec_irrad["back"])
else:
Jsc = self.calculate_Jsc(spec_irrad)
if backside_current is not None:
Jsc = Jsc + backside_current
V_tandem = self.calc_IV(Jsc, cell_temps)
P = V_tandem.values * self.j_arr[None, :]
P_max = P.max(axis=1)
P_max = pd.Series(P_max, index=spec_irrad.index)
return P_max
[docs] def calc_IV_stc(self, backside_current=None):
"""
Calculates the voltage for the IV curve of the tandem solar cell under
standart test conditions at the respective current density defined by
j_arr.
Parameters
----------
backside_current : dict
Manual backside current contribution (in mA/cm2) for bifacial tandem.
Returns
-------
Voltage : pd.DataFrame
DataFrame with the subcell and combined tandem voltage at the respective
current density (mA/cm2) as index.
Example
-------
>>> eqe = pd.DataFrame(index=np.arange(300,1205,5))
>>> # Unphysical EQE, just for demonstration
>>> eqe[['pero','si']] = 0.4
>>> electrical_parameters = {
>>> "Rsh": {"pero": 1000, "si": 3000},
>>> "RsTandem": 3,
>>> "j0": {"pero": 2.7e-18, "si": 1e-12},
>>> "n": {"pero": 1.1, "si": 1},
>>> "Temp": {"pero": 25, "si": 25},
>>> "noct": {"pero": 48, "si": 48},
>>> "tcJsc": {"pero": 0.0002, "si": 0.00032},
>>> "tcVoc": {"pero": -0.002, "si": -0.0041},
>>> }
>>> tandem = TandemSimulator(eqe=eqe,
>>> electrical_parameters=electrical_parameters,
>>> subcell_names=["pero", "si"])
>>> iv_df = tandem.calc_IV_stc()
>>> power = iv_df.tandem * iv_df.index
>>> idx_power_max = power.idxmax()
>>> print(f'''
>>> Maximum efficiency: {power.max():.1f} %,
>>> Vmpp tandem: {iv_df.loc[idx_power_max,"tandem"]:.2f} V
>>> Vmpp perovskite: {iv_df.loc[idx_power_max,"pero"]:.2f} V
>>> Vmpp silicon: {iv_df.loc[idx_power_max,"si"]:.2f} V
>>> ''')
Maximum efficiency: 30.4 %,
Vmpp tandem: 1.78 V
Vmpp perovskite: 1.09 V
Vmpp silicon: 0.69 V
"""
j_ph_stc = irradiance_models.AM15g().calc_jph(self.eqe) / 10
if backside_current is not None:
j_ph_stc = j_ph_stc + backside_current
V_tandem = self.calc_IV(
j_ph_stc.to_frame().T,
pd.Series([25, 25], index=self.subcell_names).to_frame().T,
return_subsells=True,
)
V_tandem = V_tandem.stack().reset_index(drop=True).astype(float)
V_tandem.index = self.j_arr
return V_tandem
if __name__ == "__main__":
import matplotlib.pyplot as plt
spec = pd.read_csv("../examples/data/tiny_spec.csv", index_col=0)
spec.columns = spec.columns.astype(float)
spec = spec / 1000
eqe = pd.read_csv("../examples/data/eqe_tandem_2t.csv", index_col=0)
eqe_new = interp_eqe_to_spec(eqe, spec)
eqe = pd.DataFrame(index=np.arange(300, 1205, 5))
# Unphysical EQE, just for demonstration
eqe[["pero", "si"]] = 0.4
electrical_parameters = {
"Rsh": {"pero": 1000, "si": 3000},
"RsTandem": 3,
"j0": {"pero": 2.7e-18, "si": 1e-12},
"n": {"pero": 1.1, "si": 1},
"Temp": {"pero": 25, "si": 25},
"noct": {"pero": 48, "si": 48},
"tcJsc": {"pero": 0.0002, "si": 0.00032},
"tcVoc": {"pero": -0.002, "si": -0.0041},
}
tandem = TandemSimulator2T(
eqe=eqe,
electrical_parameters=electrical_parameters,
subcell_names=["pero", "si"],
)
iv_df = tandem.calc_IV_stc()
power = iv_df.tandem * iv_df.index
idx_power_max = power.idxmax()
print(
f"""
Maximum efficiency: {power.max():.1f} %,
Vmpp tandem: {iv_df.loc[idx_power_max,'tandem']:.2f} V
Vmpp perovskite: {iv_df.loc[idx_power_max,'pero']:.2f} V
Vmpp silicon: {iv_df.loc[idx_power_max,'si']:.2f} V
"""
)
eqe = pd.DataFrame(index=np.arange(300, 1205, 5))
# Unphysical EQE, just for demonstration
eqe[["pero", "si"]] = 0.4
electrical_parameters = {
"Rsh": {"pero": 1000, "si": 3000},
"Rs": {"pero":1.5,"si":1.5},
"j0": {"pero": 2.7e-18, "si": 1e-12},
"n": {"pero": 1.1, "si": 1},
"Temp": {"pero": 25, "si": 25},
"noct": {"pero": 48, "si": 48},
"tcJsc": {"pero": 0.0002, "si": 0.00032},
"tcVoc": {"pero": -0.002, "si": -0.0041},
}
tandem = TandemSimulator4T(
eqe=eqe,
electrical_parameters=electrical_parameters,
subcell_names=["pero", "si"],
)
iv_df = tandem.calc_IV_stc()
power = iv_df.multiply(iv_df.index, axis=0)
idx_power_max = power.idxmax()
max_power = power.max().sum()
V_mpp_pero = iv_df.loc[idx_power_max['pero'], 'pero']
V_mpp_si = iv_df.loc[idx_power_max['si'], 'si']
print(
f"""
Maximum efficiency: {max_power:.1f} %,
Vmpp perovskite: {V_mpp_pero:.2f} V
Vmpp silicon: {V_mpp_si:.2f} V
"""
)
asdf
plt.plot(power)