import pandas as pd
import logging
import sys
import os
import matplotlib.pyplot as plt
import decimal
import pvlib
import pvcompare.perosi.pvlib_smarts as smarts
import pvcompare.perosi.era5 as era5
# Reconfiguring the logger here will also affect test running in the PyCharm IDE
log_format = "%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s"
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG, format=log_format)
[docs]def calculate_smarts_parameters(
year,
lat,
lon,
number_hours,
cell_type,
surface_tilt,
surface_azimuth,
atmos_data,
WLMN=350,
WLMX=1200,
):
"""
calculates the short current density Jsc for each timestep.
For each timestep the spectrum is calculated using SMARTS. After that the
short current density is calculated by multiplying the number of photons on
the tilted surface by the EQE of the given cell. A dataframe is returned
with ghi, temperature, wind_speed and the Jsc for each time step
Parameters
----------
year: int
year of interest
lat: str
latitude ending with a ".", e.g. "45."
lon: int
longitude
number_hours: int
number of hours until simulation stops. For one year enter 8760.
cell_type: list
list of cells for which the Jsc should be calculated.
atmos_data: :pandas:`pandas.DataFrame<frame>`
with datetimeindex and columns for 'temp_air' and 'wind_speed' and 'ghi'
WLMN: int
minimum wavelength of the spectrum. By default this is 280 nm.
WLMX: int
maximum wavelength of the spectrum. By default this is 1200 nm.
Returns
--------
:pandas:`pandas.DataFrame<frame>`
including ghi, temperature, wind_speed, Jsc for each cell_type
"""
# check if atmos_data is in given as an input variable
if atmos_data is None:
logging.info("loading atmos data from era5 data set")
atmos_data = era5.load_era5_weatherdata(lat, lon, year, variable="perosi")
# delta = pd.to_timedelta(30, unit="m")
# atmos_data.index = atmos_data.index + delta
atmos_data.index = pd.to_datetime(atmos_data.index)
atmos_data["davt"] = atmos_data["temp_air"].resample("D").mean()
atmos_data = atmos_data.fillna(method="ffill")
# calculate poa_total for tilted surface
spa = pvlib.solarposition.spa_python(
time=atmos_data.index, latitude=lat, longitude=lon
)
poa = pvlib.irradiance.get_total_irradiance(
surface_tilt=surface_tilt,
surface_azimuth=surface_azimuth,
solar_zenith=spa["zenith"],
solar_azimuth=spa["azimuth"],
dni=atmos_data["dni"],
ghi=atmos_data["ghi"],
dhi=atmos_data["dhi"],
)
atmos_data["poa_global"] = poa["poa_global"]
# define constant
q = 1.602176634 / (10 ** 19) # in Coulomb = A*s
# define output data format
iout = "8 12"
# define counter for number of hours to be calculated
c = 0
# define time interval of one year
# time = pd.date_range(start=f'1/1/{year}', end=f'31/12/{year}', freq='H')
# calculate Jsc for every timestep
result = pd.DataFrame()
logging.info(
"loading spectral weather data from SMARTS Nrel and "
"calculating Isc for every timestep"
)
for index, row in atmos_data.iterrows():
if index.month in range(3, 8):
season = "SUMMER"
else:
season = "WINTER"
# load spectral data from SMARTS
d = decimal.Decimal(str(lat))
decimals_lat = d.as_tuple().exponent
lat_spectrum = str(lat)[:decimals_lat]
spectrum = smarts.SMARTSSpectra(
IOUT=iout,
YEAR=str(year),
MONTH=str(index.month),
DAY=str(index.day),
HOUR=str(index.hour),
LATIT=lat_spectrum,
LONGIT=str(lon),
WLMN=WLMN,
WLMX=WLMX,
TAIR=str(atmos_data.at[index, "temp_air"]),
TDAY=str(atmos_data.at[index, "davt"]),
SEASON=season,
ZONE=0,
TILT=str(surface_tilt),
WAZIM=str(surface_azimuth),
W=str(atmos_data.at[index, "precipitable_water"]),
)
# load EQE data
for x in cell_type:
if x == "Korte_pero":
import pvcompare.perosi.data.cell_parameters_korte_pero as param
elif x == "Korte_si":
import pvcompare.perosi.data.cell_parameters_korte_si as param
elif x == "Chen_pero":
import pvcompare.perosi.data.cell_parameters_Chen_2020_4T_pero as param
url = "https://raw.githubusercontent.com/greco-project/pvcompare/dev/pvcompare/perosi/data/CHEN_2020_EQE_curve_pero_corrected.csv"
elif x == "Chen_si":
import pvcompare.perosi.data.cell_parameters_Chen_2020_4T_si as param
url = "https://raw.githubusercontent.com/greco-project/pvcompare/dev/pvcompare/perosi/data/CHEN_2020_EQE_curve_si_corrected.csv"
else:
logging.error(
"The cell type is not recognized. Please "
"choose either 'Korte_si', 'Korte_pero', 'Chen_si' "
"or 'Chen_pero."
)
EQE_filename = param.EQE_filename
path = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "data", EQE_filename
)
EQE = pd.read_csv(path, sep=",", index_col=0)
EQE = EQE / 100
# return Jsc and ghi = 0 if the spectrum is empty
if spectrum.empty == True:
result.at[index, "Jsc_" + str(x)] = 0
result.at[index, "ghi"] = 0
else:
if not spectrum.index.name == "Wvlgth":
spectrum.set_index("Wvlgth", inplace=True)
# scale spectrum to era5-ghi
spectral_ghi_sum = spectrum["Global_tilted_irradiance"].sum()
ghi_corrected = spectral_ghi_sum - (
spectral_ghi_sum - atmos_data.at[index, "poa_global"]
)
diff_percent = ghi_corrected / (spectral_ghi_sum / 100)
spectrum["Global_tilt_photon_corrected"] = (
spectrum["Global_tilt_photon_irrad"] / 100 * diff_percent
) # pro cm²
spectrum["Global_tilted_irradiance_corrected"] = (
spectrum["Global_tilted_irradiance"] / 100 * diff_percent
)
# calculate Jsc
Jsc_lambda = (
spectrum["Global_tilt_photon_corrected"] * EQE["EQE"]
) * q # Jsc pro cm²
Jsc_lambda.fillna(0, inplace=True)
result.at[index, "Jsc_" + str(x)] = Jsc_lambda.sum() # in A/cm²
result.at[index, "ghi"] = atmos_data.at[index, "poa_global"] # in W/m²
result.at[index, "ghi_spectrum_corrected"] = spectrum[
"Global_tilted_irradiance_corrected"
].sum() # in W/m²
result.at[index, "temp"] = atmos_data.at[index, "temp_air"]
result.at[index, "wind_speed"] = atmos_data.at[index, "wind_speed"]
# check if number of hours is reached
c = c + 1
if c == number_hours:
break
return result
[docs]def create_timeseries(
lat, lon, surface_azimuth, surface_tilt, atmos_data, year, cell_type, number_hours
):
"""
Calculates a timeseries for each cell type in list cell_type.
The spectrum is calculated with calculate_smarts_parameters(). After that
the specific cell parameters are loaded. Finally the p_mp of each timestep
is calculated by the pvlib.singlediode() fuction.
Parameters
----------
year: int
year of interest
lat: str
latitude ending with a ".", e.g. "45."
lon: int
longitude
number_hours: int
number of hours until simulation stops. For one year enter 8760.
cell_type: list
list of cells for which the Jsc should be calculated. Allowed values:
'Korte_pero', 'Korte_si', 'Chen_si', 'Chen_pero'
surface_azimuth: int
surface azimuth
surface_tilt: int
surface tilt
atmos_data: :pandas:`pandas.DataFrame<frame>`
with datetimeindex and columns for 'temp_air' and 'wind_speed' and 'ghi'
Returns
-------
:pandas:`pandas.DataFrame<frame>`
maximum power point of each time step for each cell type
"""
q = 1.602176634 / (10 ** (19)) # in Coulomb = A/s
kB = 1.380649 / 10 ** 23 # J/K
# calculate spectral parameters from smarts and era5
smarts_parameters = calculate_smarts_parameters(
year=year,
lat=lat,
lon=lon,
number_hours=number_hours,
cell_type=cell_type,
surface_tilt=surface_tilt,
surface_azimuth=surface_azimuth,
atmos_data=atmos_data,
)
# calculate cell temperature characteristics
t_cell = pvlib.temperature.pvsyst_cell(
smarts_parameters["ghi"],
smarts_parameters["temp"],
smarts_parameters["wind_speed"],
)
result = pd.DataFrame()
for x in cell_type:
if x == "Korte_pero":
import pvcompare.perosi.data.cell_parameters_korte_pero as param
elif x == "Korte_si":
import pvcompare.perosi.data.cell_parameters_korte_si as param
elif x == "Chen_pero":
import pvcompare.perosi.data.cell_parameters_Chen_2020_4T_pero as param
elif x == "Chen_si":
import pvcompare.perosi.data.cell_parameters_Chen_2020_4T_si as param
nNsVth = param.n * (kB * (t_cell + 273.15) / q)
Isc = smarts_parameters["Jsc_" + str(x)] * param.A_cell
singlediode = pvlib.pvsystem.singlediode(
photocurrent=Isc,
saturation_current=param.I_0,
resistance_series=param.rs,
resistance_shunt=param.rsh,
nNsVth=nNsVth,
ivcurve_pnts=None,
method="lambertw",
)
result[str(x) + "_p_mp"] = singlediode["p_mp"]
# add temperature correction
result[str(x) + "_p_mp"] = result[str(x) + "_p_mp"] * (
1 + (param.alpha * (t_cell - param.temp_ref))
)
return result
[docs]def create_pero_si_timeseries(
year,
lat,
lon,
surface_azimuth,
surface_tilt,
number_hours,
atmos_data=None,
psi_type="Chen",
):
"""
Creates a time series for the output power of a pero-si module.
Wrapper for create_timeseries() function.
Parameters
----------
year: int
year of interest
lat: str
latitude
lon: int
longitude
number_hours: int
number of hours until simulation stops. For one year enter 8760.
surface_azimuth: int
surface azimuth
surface_tilt: int
surface tilt
atmos_data: :pd.Dataframe().
weather data with datetimeindex and columns for 'temp_air' and
'wind_speed' and 'ghi'. If None weather data is loaded from era5 weather data set.
psi_type: str
Type of pero_si cell. Either "Chen" or "Korte"
Returns
---------
:pandas:`pandas.Series<series>`
time series of the output power
"""
if psi_type == "Chen":
cell_type = ["Chen_pero", "Chen_si"]
elif psi_type == "Korte":
cell_type = ["Korte_pero", "Korte_si"]
else:
logging.warning(
f"The cell_type is {psi_type}. It is not recognized. Please "
"choose between 'Korte' and 'Chen'."
)
for x in cell_type:
if x == "Korte_pero":
import pvcompare.perosi.data.cell_parameters_korte_pero as param
elif x == "Korte_si":
import pvcompare.perosi.data.cell_parameters_korte_si as param
elif x == "Chen_pero":
import pvcompare.perosi.data.cell_parameters_Chen_2020_4T_pero as param
elif x == "Chen_si":
import pvcompare.perosi.data.cell_parameters_Chen_2020_4T_si as param
timeseries = create_timeseries(
lat=lat,
lon=lon,
surface_azimuth=surface_azimuth,
surface_tilt=surface_tilt,
atmos_data=atmos_data,
year=year,
cell_type=cell_type,
number_hours=number_hours,
)
output = (timeseries.iloc[:, 0] + timeseries.iloc[:, 1]) * param.Ns
return output
if __name__ == "__main__":
weather = pd.read_csv(
"/home/inia/Dokumente/greco_env/pvcompare/pvcompare/data/inputs/weatherdata_France_2014.csv",
index_col=0,
)
output1 = create_pero_si_timeseries(
lat=45.0,
lon=5.87,
surface_azimuth=180,
surface_tilt=30,
year=2015,
number_hours=40,
psi_type="Chen",
atmos_data=weather,
)
output2 = create_pero_si_timeseries(
lat=45.0,
lon=5.87,
surface_azimuth=180,
surface_tilt=30,
year=201,
number_hours=40,
psi_type="Korte",
atmos_data=weather,
)
plt.plot(output1, "r-", label="Chen")
plt.plot(output2, "b-", label="Korte")
plt.legend()
plt.show()