Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/sphinx/source/reference/pv_modeling/iam.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ Incident angle modifiers
iam.marion_integrate
iam.schlick
iam.schlick_diffuse
iam.fresnel_ar
56 changes: 56 additions & 0 deletions pvlib/iam.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
'ashrae': {'b'},
'physical': {'n', 'K', 'L'},
'martin_ruiz': {'a_r'},
'fresnel_ar': {'n_ar', 'n_air', 'n_glass'},
'sapm': {'B0', 'B1', 'B2', 'B3', 'B4', 'B5'},
'interp': set()
}
Expand Down Expand Up @@ -750,6 +751,61 @@ def marion_integrate(function, surface_tilt, region, num=None):
return Fd


def fresnel_ar(aoi, n_ar=1.2, n_air=1.0, n_glass=1.56):
"""Determine the incidence angle modifier using the Fresnel equations
for a surface with an anti-reflective coating.

Parameters
----------
aoi : numeric
Angle of incidence between the module normal vector and the sun-beam
vector. [degrees]
n_ar : numeric, default 1.2
Index of refraction of the anti-reflective coating.
n_air : numeric, default 1.0
Index of refraction of the incident medium.
n_glass : numeric, default 1.56
Index of refraction of the glass cover.

Returns
-------
iam : numeric
The incident angle modifier.
"""

aoi_input = aoi
aoi = np.asanyarray(aoi, dtype=float)

theta_ar = asind(n_air / n_ar * sind(aoi))
theta_glass = asind(n_ar / n_glass * sind(theta_ar))

rs1 = ((n_air * cosd(aoi) - n_ar * cosd(theta_ar)) /
(n_air * cosd(aoi) + n_ar * cosd(theta_ar))) ** 2
rp1 = ((n_air * cosd(theta_ar) - n_ar * cosd(aoi)) /
(n_air * cosd(theta_ar) + n_ar * cosd(aoi))) ** 2

rs2 = ((n_ar * cosd(theta_ar) - n_glass * cosd(theta_glass)) /
(n_ar * cosd(theta_ar) + n_glass * cosd(theta_glass))) ** 2
rp2 = ((n_ar * cosd(theta_glass) - n_glass * cosd(theta_ar)) /
(n_ar * cosd(theta_glass) + n_glass * cosd(theta_ar))) ** 2

rs = 1 - (1 - rs1) * (1 - rs2)
rp = 1 - (1 - rp1) * (1 - rp2)
refl = (rs + rp) / 2

r0_1 = ((n_air - n_ar) / (n_air + n_ar)) ** 2
r0_2 = ((n_ar - n_glass) / (n_ar + n_glass)) ** 2
r0 = 1 - (1 - r0_1) * (1 - r0_2)

iam = (1 - refl) / (1 - r0)
iam = np.where(np.abs(aoi) >= 90, 0.0, iam)

if isinstance(aoi_input, pd.Series):
iam = pd.Series(iam, index=aoi_input.index)

return iam


def schlick(aoi):
"""
Determine incidence angle modifier (IAM) for direct irradiance using the
Expand Down
39 changes: 35 additions & 4 deletions pvlib/pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,37 @@
from pvlib.tools import _build_kwargs, _build_args


def _pvsyst_Rsh(effective_irradiance, R_sh_ref, R_sh_0, R_sh_exp, e0=1000):
"""Simplified PVsyst shunt resistance model."""
effective_irradiance = np.asarray(effective_irradiance)
return R_sh_ref / (1 + (R_sh_ref / R_sh_0) *
(effective_irradiance / e0) ** R_sh_exp)


def _pvsyst_IL(effective_irradiance, alpha_sc, temp_cell, e0=1000, t0=25):
"""Simplified PVsyst photocurrent model."""
return effective_irradiance / e0 * (1 + alpha_sc * (temp_cell - t0))


def _pvsyst_Io(effective_irradiance, beta_voc, temp_cell, cells_in_series,
e0=1000, t0=25):
"""Simplified PVsyst saturation current model."""
del effective_irradiance # not used in simplified expression
return np.exp(beta_voc / cells_in_series * (temp_cell - t0))


def _pvsyst_nNsVth(temp_cell, cells_in_series):
"""Simplified PVsyst thermal voltage model."""
return cells_in_series * constants.k * (temp_cell + 273.15) / constants.e


def _pvsyst_gamma(effective_irradiance, gamma_ref, mu_gamma, temp_cell,
e0=1000, t0=25):
"""Simplified PVsyst gamma model."""
del effective_irradiance, e0 # not used
return gamma_ref + mu_gamma * (temp_cell - t0)


# a dict of required parameter names for each DC power model
_DC_MODEL_PARAMS = {
'sapm': {
Expand Down Expand Up @@ -419,7 +450,7 @@ def get_iam(self, aoi, iam_model='physical'):

aoi_model : string, default 'physical'
The IAM model to be used. Valid strings are 'physical', 'ashrae',
'martin_ruiz' and 'sapm'.
'martin_ruiz', 'fresnel_ar' and 'sapm'.
Returns
-------
iam : numeric or tuple of numeric
Expand Down Expand Up @@ -1531,7 +1562,7 @@ def get_iam(self, aoi, iam_model='physical'):
if `iam_model` is not a valid model name.
"""
model = iam_model.lower()
if model in ['ashrae', 'physical', 'martin_ruiz']:
if model in ['ashrae', 'physical', 'martin_ruiz', 'fresnel_ar']:
param_names = iam._IAM_MODEL_PARAMS[model]
kwargs = _build_kwargs(param_names, self.module_parameters)
func = getattr(iam, model)
Expand Down Expand Up @@ -2900,7 +2931,7 @@ def singlediode(photocurrent, saturation_current, resistance_series,


def max_power_point(photocurrent, saturation_current, resistance_series,
resistance_shunt, nNsVth, d2mutau=0, NsVbi=np.Inf,
resistance_shunt, nNsVth, d2mutau=0, NsVbi=np.inf,
method='brentq'):
"""
Given the single diode equation coefficients, calculates the maximum power
Expand Down Expand Up @@ -2947,7 +2978,7 @@ def max_power_point(photocurrent, saturation_current, resistance_series,
"""
i_mp, v_mp, p_mp = _singlediode.bishop88_mpp(
photocurrent, saturation_current, resistance_series,
resistance_shunt, nNsVth, d2mutau=0, NsVbi=np.Inf,
resistance_shunt, nNsVth, d2mutau=0, NsVbi=np.inf,
method=method.lower()
)
if isinstance(photocurrent, pd.Series):
Expand Down
8 changes: 4 additions & 4 deletions pvlib/singlediode.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def estimate_voc(photocurrent, saturation_current, nNsVth):

def bishop88(diode_voltage, photocurrent, saturation_current,
resistance_series, resistance_shunt, nNsVth, d2mutau=0,
NsVbi=np.Inf, breakdown_factor=0., breakdown_voltage=-5.5,
NsVbi=np.inf, breakdown_factor=0., breakdown_voltage=-5.5,
breakdown_exp=3.28, gradients=False):
r"""
Explicit calculation of points on the IV curve described by the single
Expand Down Expand Up @@ -204,7 +204,7 @@ def bishop88(diode_voltage, photocurrent, saturation_current,

def bishop88_i_from_v(voltage, photocurrent, saturation_current,
resistance_series, resistance_shunt, nNsVth,
d2mutau=0, NsVbi=np.Inf, breakdown_factor=0.,
d2mutau=0, NsVbi=np.inf, breakdown_factor=0.,
breakdown_voltage=-5.5, breakdown_exp=3.28,
method='newton'):
"""
Expand Down Expand Up @@ -292,7 +292,7 @@ def vd_from_brent(voc, v, iph, isat, rs, rsh, gamma, d2mutau, NsVbi,

def bishop88_v_from_i(current, photocurrent, saturation_current,
resistance_series, resistance_shunt, nNsVth,
d2mutau=0, NsVbi=np.Inf, breakdown_factor=0.,
d2mutau=0, NsVbi=np.inf, breakdown_factor=0.,
breakdown_voltage=-5.5, breakdown_exp=3.28,
method='newton'):
"""
Expand Down Expand Up @@ -378,7 +378,7 @@ def vd_from_brent(voc, i, iph, isat, rs, rsh, gamma, d2mutau, NsVbi,


def bishop88_mpp(photocurrent, saturation_current, resistance_series,
resistance_shunt, nNsVth, d2mutau=0, NsVbi=np.Inf,
resistance_shunt, nNsVth, d2mutau=0, NsVbi=np.inf,
breakdown_factor=0., breakdown_voltage=-5.5,
breakdown_exp=3.28, method='newton'):
"""
Expand Down
2 changes: 1 addition & 1 deletion pvlib/tests/test_singlediode.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def test_pvsyst_recombination_loss(method, poa, temp_cell, expected, tol):
# other conditions with breakdown model on and recombination model off
(
(1.e-4, -5.5, 3.28),
(0., np.Inf),
(0., np.inf),
POA,
TCELL,
{
Expand Down
5 changes: 5 additions & 0 deletions pvlib/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,3 +458,8 @@ def _degrees_to_index(degrees, coordinate):
index = int(np.around(index))

return index


def _first_order_centered_difference(func, x0, args=(), dx=1e-6):
"""Return the first order centered finite difference of ``func`` at ``x0``."""
return (func(x0 + dx, *args) - func(x0 - dx, *args)) / (2 * dx)
12 changes: 12 additions & 0 deletions tests/test_iam.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,18 @@ def test_physical_scalar():
assert_allclose(iam, expected, equal_nan=True)


def test_fresnel_ar():
aoi = np.array([0, 22.5, 45, 67.5, 90, 100, np.nan])
expected = np.array([1.0, 0.99972665, 0.99355573, 0.92808897, 0.0, 0.0,
np.nan])
iam = _iam.fresnel_ar(aoi)
assert_allclose(iam, expected, atol=1e-7, equal_nan=True)

aoi = pd.Series(aoi)
iam_series = _iam.fresnel_ar(aoi)
assert_series_equal(iam_series, pd.Series(expected))


def test_martin_ruiz():

aoi = 45.
Expand Down
1 change: 1 addition & 0 deletions tests/test_pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
('ashrae', {'b': 0.05}),
('physical', {'K': 4, 'L': 0.002, 'n': 1.526}),
('martin_ruiz', {'a_r': 0.16}),
('fresnel_ar', {'n_ar': 1.2, 'n_air': 1.0, 'n_glass': 1.56}),
])
def test_PVSystem_get_iam(mocker, iam_model, model_params):
m = mocker.spy(_iam, iam_model)
Expand Down
Loading