"""
Collection of classes and functions for standard development.
"""
import numpy as np
import pandas as pd
import dask.dataframe as dd
import pvlib
from rex import NSRDBX
from rex import Outputs
from pathlib import Path
from random import random
from concurrent.futures import ProcessPoolExecutor, as_completed
# from gaps import ProjectPoints
from pvdeg import temperature, spectral, utilities, weather
[docs]
def eff_gap_parameters(
weather_df=None,
meta=None,
weather_kwarg=None,
sky_model="isotropic",
temp_model="sapm",
conf_0="insulated_back_glass_polymer",
conf_inf="open_rack_glass_polymer",
tilt=None,
azimuth=None,
wind_factor=0.33,
):
"""
Calculate and set up data necessary to calculate the effective standoff distance for rooftop mounded PV system
according to IEC TS 63126. The the temperature is modeled for T_0 and T_inf and the corresponding test
module temperature must be provided in the weather data.
Parameters
----------
weather_df : pd.DataFrame
Weather data for a single location.
meta : pd.DataFrame
Meta data for a single location.
weather_kwarg : dict
other variables needed to access a particular weather dataset.
sky_model : str, optional
Options: 'isotropic', 'klucher', 'haydavies', 'reindl', 'king', 'perez'.
temp_model : str, optional
Options: 'sapm'. 'pvsyst' and 'faiman' and others from PVlib.
Performs the calculation for the cell temperature.
conf_0 : str, optional
Model for the high temperature module on the exponential decay curve.
Default: 'insulated_back_glass_polymer'
conf_inf : str, optional
Model for the lowest temperature module on the exponential decay curve.
Default: 'open_rack_glass_polymer'
tilt : float, optional
Tilt angle of PV system relative to horizontal. [°]
azimuth : float, optional
Azimuth angle of PV system relative to north. [°]
wind_factor : float, optional
Wind speed correction exponent to account for different wind speed measurement heights
between weather database (e.g. NSRDB) and the tempeature model (e.g. SAPM)
The NSRDB provides calculations at 2 m (i.e module height) but SAPM uses a 10 m height.
It is recommended that a power-law relationship between height and wind speed of 0.33
be used*. This results in a wind speed that is 1.7 times higher. It is acknowledged that
this can vary significantly.
References
----------
R. Rabbani, M. Zeeshan, "Exploring the suitability of MERRA-2 reanalysis data for wind energy
estimation, analysis of wind characteristics and energy potential assessment for selected
sites in Pakistan", Renewable Energy 154 (2020) 1240-1251.
Returns
-------
T_0 : float
An array of temperature values for a module with an insulated back or an
alternatively desired small or zero standoff, [°C]. Used as the basis for the
maximum achievable temperature.
T_inf : float
An array of temperature values for a module that is rack mounted, [°C].
poa : float
An array of values for the plane of array irradiance, [W/m²]
"""
parameters = ["temp_air", "wind_speed", "dhi", "ghi", "dni"]
if isinstance(weather_df, dd.DataFrame):
weather_df = weather_df[parameters].compute()
weather_df.set_index("time", inplace=True)
elif isinstance(weather_df, pd.DataFrame):
weather_df = weather_df[parameters]
elif weather_df is None:
weather_df, meta = weather.get(**weather_kwarg)
solar_position = spectral.solar_position(weather_df, meta)
poa = spectral.poa_irradiance(
weather_df,
meta,
sol_position=solar_position,
tilt=tilt,
azimuth=azimuth,
sky_model=sky_model,
)
T_0 = temperature.cell(
weather_df=weather_df,
meta=meta,
poa=poa,
temp_model=temp_model,
conf=conf_0,
wind_factor=wind_factor,
)
T_inf = temperature.cell(
weather_df=weather_df,
meta=meta,
poa=poa,
temp_model=temp_model,
conf=conf_inf,
wind_factor=wind_factor,
)
return T_0, T_inf, poa
[docs]
def eff_gap(T_0, T_inf, T_measured, T_ambient, poa, x_0=6.5, poa_min=400, t_amb_min=0):
"""
Calculate the effective standoff distance for rooftop mounded PV system
according to IEC TS 63126. The 98ᵗʰ percentile calculations for T_0 and T_inf are
also calculated.
Parameters
----------
T_0 : float
An array of temperature values for a module with an insulated back or an
alternatively desired small or zero standoff. Used as the basis for the
maximum achievable temperature, [°C].
T_inf : float
An array of temperature values for a module that is rack mounted, [°C].
T_measured : float
An array of values for the measured module temperature, [°C].
T_ambient : float
An array of values for the ambient temperature, [°C].
poa : float
An array of values for the plane of array irradiance, [W/m²].
x_0 : float, optional
Thermal decay constant [cm], [Kempe, PVSC Proceedings 2023].
According to edition 2 of IEC TS 63126 a value of 6.5 cm is recommended.
poa_min : float
The minimum value for which the data will be used, [W/m²].
400 W/m² is recommended.
t_amb_min : float
The minimum ambient temperature for which the calculation will be made, [°C].
A value of 0 °C is recommended to avoid data where snow might be present.
Returns
-------
x_eff : float
Effective module standoff distance. While not the actual physical standoff,
this can be thought of as a heat transfer coefficient that produces results
that are similar to the modeled singl module temperature with that gap.
References
----------
M. Kempe, et al. Close Roof Mounted System Temperature Estimation for Compliance
to IEC TS 63126, PVSC Proceedings 2023
"""
n = 0
summ = 0
for i in range(0, len(T_0)):
if T_ambient.iloc[i] > t_amb_min:
if poa.poa_global.iloc[i] > poa_min:
n = n + 1
summ = summ + (T_0.iloc[i] - T_measured.iloc[i]) / (
T_0.iloc[i] - T_inf.iloc[i]
)
try:
x_eff = -x_0 * np.log(1 - summ / n)
except RuntimeWarning as e:
x_eff = (
np.nan
) # results if the specified T₉₈ is cooler than an open_rack temperature
if x_eff < 0:
x_eff = 0
return x_eff
[docs]
def standoff(
weather_df=None,
meta=None,
weather_kwarg=None,
tilt=None,
azimuth=None,
sky_model="isotropic",
temp_model="sapm",
conf_0="insulated_back_glass_polymer",
conf_inf="open_rack_glass_polymer",
T98=70, # [°C]
x_0=6.5, # [cm]
wind_factor=0.33,
):
"""
Calculate a minimum standoff distance for roof mounded PV systems.
Will default to horizontal tilt. If the azimuth is not provided, it
will use equator facing.
You can use customized temperature models for the building integrated
and the rack mounted configuration, but it will still assume an
exponential decay.
Parameters
----------
weather_df : pd.DataFrame
Weather data for a single location.
meta : pd.DataFrame
Meta data for a single location.
weather_kwarg : dict
other variables needed to access a particular weather dataset.
tilt : float, optional
Tilt angle of PV system relative to horizontal. [°]
azimuth : float, optional
Azimuth angle of PV system relative to north. [°]
sky_model : str, optional
Options: 'isotropic', 'klucher', 'haydavies', 'reindl', 'king', 'perez'.
temp_model : str, optional
Options: 'sapm'. 'pvsyst' and 'faiman' will be added later.
Performs the calculations for the cell temperature.
conf_0 : str, optional
Model for the high temperature module on the exponential decay curve.
Default: 'insulated_back_glass_polymer'
conf_inf : str, optional
Model for the lowest temperature module on the exponential decay curve.
Default: 'open_rack_glass_polymer'
x_0 : float, optional
Thermal decay constant (cm), [Kempe, PVSC Proceedings 2023]
wind_factor : float, optional
Wind speed correction exponent to account for different wind speed measurement heights
between weather database (e.g. NSRDB) and the tempeature model (e.g. SAPM)
The NSRDB provides calculations at 2 m (i.e module height) but SAPM uses a 10 m height.
It is recommended that a power-law relationship between height and wind speed of 0.33
be used*. This results in a wind speed that is 1.7 times higher. It is acknowledged that
this can vary significantly.
R. Rabbani, M. Zeeshan, "Exploring the suitability of MERRA-2 reanalysis data for wind energy
estimation, analysis of wind characteristics and energy potential assessment for selected
sites in Pakistan", Renewable Energy 154 (2020) 1240-1251.
Returns
-------
x : float [cm]
Minimum installation distance in centimeter per IEC TS 63126 when the default settings are used.
Effective gap "x" for the lower limit for Level 1 or Level 0 modules (IEC TS 63216)
T98_0 : float [°C]
This is the 98ᵗʰ percential temperature of a theoretical module with no standoff.
T98_inf : float [°C]
This is the 98ᵗʰ percential temperature of a theoretical rack mounted module.
References
----------
M. Kempe, et al. Close Roof Mounted System Temperature Estimation for Compliance
to IEC TS 63126, PVSC Proceedings 2023
"""
parameters = ["temp_air", "wind_speed", "dhi", "ghi", "dni"]
if isinstance(weather_df, dd.DataFrame):
weather_df = weather_df[parameters].compute()
weather_df.set_index("time", inplace=True)
elif isinstance(weather_df, pd.DataFrame):
weather_df = weather_df[parameters]
elif weather_df is None:
weather_df, meta = weather.get(**weather_kwarg)
solar_position = spectral.solar_position(weather_df, meta)
poa = spectral.poa_irradiance(
weather_df=weather_df,
meta=meta,
sol_position=solar_position,
tilt=tilt,
azimuth=azimuth,
sky_model=sky_model,
)
T_0 = temperature.cell(
weather_df=weather_df,
meta=meta,
poa=poa,
temp_model=temp_model,
conf=conf_0,
wind_factor=wind_factor,
)
T98_0 = T_0.quantile(q=0.98, interpolation="linear")
T_inf = temperature.cell(
weather_df=weather_df,
meta=meta,
poa=poa,
temp_model=temp_model,
conf=conf_inf,
wind_factor=wind_factor,
)
T98_inf = T_inf.quantile(q=0.98, interpolation="linear")
try:
x = -x_0 * np.log(1 - (T98_0 - T98) / (T98_0 - T98_inf))
except RuntimeWarning as e:
x = np.nan
# results if the specified T₉₈ is cooler than an open_rack temperature
if x < 0:
x = 0
res = {"x": x, "T98_0": T98_0, "T98_inf": T98_inf}
df_res = pd.DataFrame.from_dict(res, orient="index").T
return df_res
[docs]
def interpret_standoff(standoff_1=None, standoff_2=None):
"""
This is a set of statments designed to provide a printable output to interpret the results of standoff calculations.
At a minimum, data for Standoff_1 must be included.
Parameters
----------
Standoff_1 and Standoff_2 : df
This is the dataframe output from the standoff calculation method for calculations at 70°C and 80°C, respectively.
x : float [°C]
Minimum installation distance in centimeter per IEC TS 63126 when the default settings are used.
Effective gap "x" for the lower limit for Level 1 or Level 0 modules (IEC TS 63216)
T98_0 : float [°C]
This is the 98ᵗʰ percential temperature of a theoretical module with no standoff.
T98_inf : float [°C]
This is the 98ᵗʰ percential temperature of a theoretical rack mounted module.
Returns
-------
Output: str
This is an interpretation of the accepatble effective standoff values suitable for presentation.
"""
if standoff_1 is not None:
x70 = standoff_1["x"].iloc[0]
T98_0 = standoff_1["T98_0"].iloc[0]
T98_inf = standoff_1["T98_inf"].iloc[0]
if standoff_2 is not None:
x80 = standoff_2["x"].iloc[0]
else:
try:
x80 = -(-x70 / (np.log(1 - (T98_0 - 70) / (T98_0 - T98_inf)))) * np.log(
1 - (T98_0 - 80) / (T98_0 - T98_inf)
)
except RuntimeWarning as e:
x80 = None
else:
x70 = None
if x70 == None:
Output = "Insufficient data for IEC TS 63126 Level determination."
else:
if T98_0 is not None:
Output = (
"The estimated T₉₈ of an insulated-back module is "
+ "%.1f" % T98_0
+ "°C. \n"
)
if T98_inf is not None:
Output = (
Output
+ "The estimated T₉₈ of an open-rack module is "
+ "%.1f" % T98_inf
+ "°C. \n"
)
if x80 == None:
Output = (
Output
+ "The minimum standoff for Level 0 certification and T₉₈<70°C is "
+ "%.1f" % x70
+ " cm."
)
else:
Output = (
Output
+ "Level 0 certification is valid for a standoff greather than "
+ "%.1f" % x70
+ " cm. \n"
)
if x70 > 0:
if x80 > 0:
Output = (
Output
+ "Level 1 certification is required for a standoff between than "
+ "%.1f" % x70
+ " cm, and "
+ "%.1f" % x80
+ " cm. \n"
)
Output = (
Output
+ "Level 2 certification is required for a standoff less than "
+ "%.1f" % x80
+ " cm."
)
else:
Output = (
Output
+ "Level 1 certification is required for a standoff less than "
+ "%.1f" % x70
+ " cm. \n"
)
Output = (
Output
+ "Level 2 certification is never required for this temperature profile."
)
return Output
[docs]
def T98_estimate(
weather_df=None,
meta=None,
weather_kwarg=None,
sky_model="isotropic",
temp_model="sapm",
conf_0="insulated_back_glass_polymer",
conf_inf="open_rack_glass_polymer",
wind_factor=0.33,
tilt=None,
azimuth=None,
x_eff=None,
x_0=6.5,
):
"""
Estimate the 98ᵗʰ percential temperature for the module at the given tilt, azimuth, and x_eff.
If any of these factors are supplied, it default to latitide tilt, equatorial facing, and
open rack mounted, respectively.
Parameters
----------
x_eff : float
This is the effective module standoff distance according to the model. [cm]
x_0 : float, optional
Thermal decay constant. [cm]
weather_df : pd.DataFrame
Weather data for a single location.
meta : pd.DataFrame
Meta data for a single location.
weather_kwarg : dict
other variables needed to access a particular weather dataset.
tilt : float,
Tilt angle of PV system relative to horizontal. [°]
azimuth : float, optional
Azimuth angle of PV system relative to north. [°]
sky_model : str, optional
Options: 'isotropic', 'klucher', 'haydavies', 'reindl', 'king', 'perez'.
temp_model : str, optional
Options: 'sapm'. 'pvsyst' and 'faiman' will be added later.
Performs the calculations for the cell temperature.
conf_0 : str, optional
Model for the high temperature module on the exponential decay curve.
Default: 'insulated_back_glass_polymer'
conf_inf : str, optional
Model for the lowest temperature module on the exponential decay curve.
Default: 'open_rack_glass_polymer'
wind_factor : float, optional
Wind speed correction exponent to account for different wind speed measurement heights
between weather database (e.g. NSRDB) and the tempeature model (e.g. SAPM)
The NSRDB provides calculations at 2 m (i.e module height) but SAPM uses a 10 m height.
It is recommended that a power-law relationship between height and wind speed of 0.33
be used*. This results in a wind speed that is 1.7 times higher. It is acknowledged that
this can vary significantly.
R. Rabbani, M. Zeeshan, "Exploring the suitability of MERRA-2 reanalysis data for wind energy
estimation, analysis of wind characteristics and energy potential assessment for selected
sites in Pakistan", Renewable Energy 154 (2020) 1240-1251.
Returns
-------
T98: float
This is the 98ᵗʰ percential temperature for the module at the given tilt, azimuth, and x_eff.
"""
parameters = ["temp_air", "wind_speed", "dhi", "ghi", "dni"]
if isinstance(weather_df, dd.DataFrame):
weather_df = weather_df[parameters].compute()
weather_df.set_index("time", inplace=True)
elif isinstance(weather_df, pd.DataFrame):
weather_df = weather_df[parameters]
elif weather_df is None:
weather_df, meta = weather.get(**weather_kwarg)
solar_position = spectral.solar_position(weather_df, meta)
poa = spectral.poa_irradiance(
weather_df=weather_df,
meta=meta,
sol_position=solar_position,
tilt=tilt,
azimuth=azimuth,
sky_model=sky_model,
)
T_inf = temperature.cell(
weather_df,
meta,
poa,
temp_model,
conf_inf,
wind_factor,
)
T98_inf = T_inf.quantile(q=0.98, interpolation="linear")
if x_eff == None:
return T98_inf
else:
T_0 = temperature.cell(
weather_df,
meta,
poa,
temp_model,
conf_0,
wind_factor,
)
T98_0 = T_0.quantile(q=0.98, interpolation="linear")
T98 = T98_0 - (T98_0 - T98_inf) * (1 - np.exp(-x_eff / x_0))
return T98
[docs]
def standoff_x(
weather_df,
meta,
tilt,
azimuth,
sky_model,
temp_model=None,
conf_0=None,
conf_inf=None,
T98=None,
x_0=None,
wind_factor=None,
):
"""
Calculate a minimum standoff distance for roof mounded PV systems.
Will default to horizontal tilt and return only that value. It just passes
through the calling function and returns a single value.
Parameters
----------
See Standoff() documentation
Returns
-------
x : float [cm]
Minimum installation distance in centimeter per IEC TS 63126 when the default settings are used.
Effective gap "x" for the lower limit for Level 1 or Level 0 modules (IEC TS 63216)
"""
temp_df = standoff(
weather_df=weather_df,
meta=meta,
tilt=tilt,
azimuth=azimuth,
sky_model=sky_model,
temp_model=temp_model,
conf_0=conf_0,
conf_inf=conf_inf,
T98=T98,
x_0=x_0,
wind_factor=wind_factor,
).x[0]
return temp_df