8000 Create iam.py, consistent naming for IAM functions by cwhanse · Pull Request #783 · pvlib/pvlib-python · GitHub
[go: up one dir, main page]

Skip to content

Create iam.py, consistent naming for IAM functions #783

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 35 commits into from
Oct 18, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
73f4638
move iam functions to iam.py
cwhanse Oct 2, 2019
a89cc01
move function tests to test_iam.py
cwhanse Oct 2, 2019
0f1c90c
adjust PVSystem methods, add deprecation for functions and PVSystem m…
cwhanse Oct 2, 2019
0a55b54
adjust PVSystem tests, test for function deprecation
cwhanse Oct 2, 2019
8505a0d
move sapm aoi function, adjust ModelChain methods
cwhanse Oct 2, 2019
f48742c
remove _ typo
cwhanse Oct 2, 2019
1bcde31
lint fixes
cwhanse Oct 2, 2019
c51bc08
move fixture to correct place
cwhanse Oct 2, 2019
5e01712
move sapm_module_params fixture to conftest.py
cwhanse Oct 2, 2019
e5bebe1
fix cut/paste errors
cwhanse Oct 3, 2019
549fc2c
add missing keys to fixture, add missing text to pvsystem.sapm docstring
cwhanse Oct 3, 2019
42210e4
fix and update pvsystem.sapm tests
cwhanse Oct 3, 2019
1475db6
remove DataFrame test for sapm, lint fixes
cwhanse Oct 3, 2019
006768d
implement PVSystem.get_iam, add deprecation test for pvsystem.sapm_ao…
cwhanse Oct 3, 2019
c10af4c
lint
cwhanse Oct 3, 2019
b9e39ac
Merge branch 'master' into iam
cwhanse Oct 3, 2019
ab1fbbc
test fixes, add Material to sapm_module_params fixture
cwhanse Oct 3, 2019
4dadd4b
test fixes
cwhanse Oct 4, 2019
7f14c95
add martin_ruiz to modelchain
cwhanse Oct 4, 2019
9164592
finish adding martin_ruiz to modelchain
cwhanse Oct 4, 2019
2fe7396
add test for ModelChain.infer_aoi_model, improve coverage
cwhanse Oct 4, 2019
3d6bcf5
repair delete mistake
cwhanse Oct 4, 2019
64a6b3c
test for invalid aoi model parameters in ModelChain
cwhanse Oct 4, 2019
152ef7a
update api, whatsnew
cwhanse Oct 4, 2019
46db9d7
test fix
cwhanse Oct 4, 2019
ea6fe81
fixture for aoi_model tests
cwhanse Oct 4, 2019
2529351
bad indent
cwhanse Oct 4, 2019
f641cd0
docstring and lint
cwhanse Oct 4, 2019
f331949
lint
cwhanse Oct 4, 2019
ec723c6
module docstring, changes to tests
cwhanse Oct 15, 2019
cf998ea
test fixes
cwhanse Oct 15, 2019
23f2677
another test fix
cwhanse Oct 16, 2019
1e63f04
improve coverage
cwhanse Oct 16, 2019
5fb7cc8
fix exception test
cwhanse Oct 16, 2019
b43b28c
fix the fix
cwhanse Oct 16, 2019
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
Prev Previous commit
Next Next commit
move sapm aoi function, adjust ModelChain methods
  • Loading branch information
cwhanse committed Oct 2, 2019
commit 8505a0d0076647161cc561288b7a463ab1d7637d
61 changes: 61 additions & 0 deletions pvlib/iam.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,64 @@ def interp(aoi, theta_ref, iam_ref, method='linear', normalize=True):
iam = pd.Series(iam, index=aoi_input.index)

return iam


def sapm(aoi, module, upper=None):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we've previously discussed moving the sapm functions away from dict input and requiring explicit parameters like most of the other functions. If we're going to change it, now is a good time. Maybe we could do the following:

def sapm(aoi, b0, b1=None, b2=None, b3=None, b4=None, b5=None, upper=None):
    ...
    try:
        aoi_coeff = [b0['B5'], b0['B4'], b0['B3'], b0['B2'],
                 b0['B1'], b0['B0']]
        warnings.warn('deprecation... must provide explicit parameters')
    except KeyError:
        aoi_coeff = [b0, b1, b2, b3, b4, b5]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would also let us remove the special case for sapm in the PVSystem.get_iam wrapper.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would also let us remove the special case for sapm in the PVSystem.get_iam wrapper.

"""
Determine the incidence angle modifier (IAM) using the SAPM model.

Parameters
----------
aoi : numeric
Angle of incidence in degrees. Negative input angles will return
zeros.

module : dict-like
A dict, Series, or DataFrame defining the SAPM performance
parameters. See the :py:func:`sapm` notes section for more
details.

upper : None or float, default None
Upper limit on the results.

Returns
-------
iam : numeric
The SAPM angle of incidence loss coefficient F2.

Notes
-----
The SAPM [1] traditionally does not define an upper limit on the AOI
loss function and values slightly exceeding 1 may exist for moderate
angles of incidence (15-40 degrees). However, users may consider
imposing an upper limit of 1.

References
----------
[1] King, D. et al, 2004, "Sandia Photovoltaic Array Performance
Model", SAND Report 3535, Sandia National Laboratories, Albuquerque,
NM.

[2] B.H. King et al, "Procedure to Determine Coefficients for the
Sandia Array Performance Model (SAPM)," SAND2016-5284, Sandia
National Laboratories (2016).

[3] B.H. King et al, "Recent Advancements in Outdoor Measurement
Techniques for Angle of Incidence Effects," 42nd IEEE PVSC (2015).
DOI: 10.1109/PVSC.2015.7355849
"""

aoi_coeff = [module['B5'], module['B4'], module['B3'], module['B2'],
module['B1'], module['B0']]

iam = np.polyval(aoi_coeff, aoi)
iam = np.clip(iam, 0, upper)
# nan tolerant masking
aoi_lt_0 = np.full_like(aoi, False, dtype='bool')
np.less(aoi, 0, where=~np.isnan(aoi), out=aoi_lt_0)
iam = np.where(aoi_lt_0, 0, iam)

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

return iam
6 changes: 3 additions & 3 deletions pvlib/modelchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,15 +590,15 @@ def infer_aoi_model(self):
'aoi_model="no_loss".')

def ashrae_aoi_loss(self):
self.aoi_modifier = self.system.ashraeiam(self.aoi)
self.aoi_modifier = self.system.iam_ashrae(self.aoi)
return self

def physical_aoi_loss(self):
self.aoi_modifier = self.system.physicaliam(self.aoi)
self.aoi_modifier = self.system.iam_physical(self.aoi)
return self

def sapm_aoi_loss(self):
self.aoi_modifier = self.system.sapm_aoi_loss(self.aoi)
self.aoi_modifier = self.system.iam_sapm(self.aoi)
return self

def no_aoi_loss(self):
Expand Down
83 changes: 16 additions & 67 deletions pvlib/pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,8 +550,18 @@ def sapm_spectral_loss(self, airmass_absolute):

def sapm_aoi_loss(self, aoi):
"""
Use the :py:func:`sapm_aoi_loss` function, the input parameters,
and ``self.module_parameters`` to calculate F2.
Deprecated. Use ``PVSystem.iam_sapm`` instead.
"""
import warnings
warnings.warn(
'PVSystem.sapm_aoi_loss is deprecated and will be removed in v0.8,'
' use PVSystem.iam_sapm instead', pvlibDeprecationWarning)
return PVSystem.iam_sapm(self, aoi)

def iam_sapm(self, aoi):
"""
Use the :py:func:`iam.sapm` function, the input parameters,
and ``self.module_parameters`` to calculate iam.

Parameters
----------
Expand All @@ -560,10 +570,10 @@ def sapm_aoi_loss(self, aoi):

Returns
-------
F2 : numeric
The SAPM angle of incidence loss coefficient.
iam : numeric
The SAPM angle of incidence loss coefficient F2.
"""
return sapm_aoi_loss(aoi, self.module_parameters)
return iam.sapm(aoi, self.module_parameters)

def sapm_effective_irradiance(self, poa_direct, poa_diffuse,
airmass_absolute, aoi,
Expand Down Expand Up @@ -1846,67 +1856,6 @@ def sapm_spectral_loss(airmass_absolute, module):
return spectral_loss


def sapm_aoi_loss(aoi, module, upper=None):
"""
Calculates the SAPM angle of incidence loss coefficient, F2.

Parameters
----------
aoi : numeric
Angle of incidence in degrees. Negative input angles will return
zeros.

module : dict-like
A dict, Series, or DataFrame defining the SAPM performance
parameters. See the :py:func:`sapm` notes section for more
details.

upper : None or float, default None
Upper limit on the results.

Returns
-------
F2 : numeric
The SAPM angle of incidence loss coefficient.

Notes
-----
The SAPM traditionally does not define an upper limit on the AOI
loss function and values slightly exceeding 1 may exist for moderate
angles of incidence (15-40 degrees). However, users may consider
imposing an upper limit of 1.

References
----------
[1] King, D. et al, 2004, "Sandia Photovoltaic Array Performance
Model", SAND Report 3535, Sandia National Laboratories, Albuquerque,
NM.

[2] B.H. King et al, "Procedure to Determine Coefficients for the
Sandia Array Performance Model (SAPM)," SAND2016-5284, Sandia
National Laboratories (2016).

[3] B.H. King et al, "Recent Advancements in Outdoor Measurement
Techniques for Angle of Incidence Effects," 42nd IEEE PVSC (2015).
DOI: 10.1109/PVSC.2015.7355849
"""

aoi_coeff = [module['B5'], module['B4'], module['B3'], module['B2'],
module['B1'], module['B0']]

aoi_loss = np.polyval(aoi_coeff, aoi)
aoi_loss = np.clip(aoi_loss, 0, upper)
# nan tolerant masking
aoi_lt_0 = np.full_like(aoi, False, dtype='bool')
np.less(aoi, 0, where=~np.isnan(aoi), out=aoi_lt_0)
aoi_loss = np.where(aoi_lt_0, 0, aoi_loss)

if isinstance(aoi, pd.Series):
aoi_loss = pd.Series(aoi_loss, aoi.index)

return aoi_loss


def sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi,
module, reference_irradiance=1000):
"""
Expand Down Expand Up @@ -1942,7 +1891,7 @@ def sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi,
"""

F1 = sapm_spectral_loss(airmass_absolute, module)
F2 = sapm_aoi_loss(aoi, module)
F2 = _iam.sapm(aoi, module)

E0 = reference_irradiance

Expand Down
21 changes: 21 additions & 0 deletions pvlib/test/test_iam.py
EE7F
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,24 @@ def test_iam_interp():
# check exception clause
with pytest.raises(ValueError):
_iam.interp(0.0, [0, 90], [1, -1])


def test_sapm(sapm_module_params, aoi, expected):

out = _iam.sapm(aoi, sapm_module_params)

if isinstance(aoi, pd.Series):
assert_series_equal(out, expected, check_less_precise=4)
else:
assert_allclose(out, expected, atol=1e-4)


def test_sapm_limits():
module_parameters = {'B0': 5, 'B1': 0, 'B2': 0, 'B3': 0, 'B4': 0, 'B5': 0}
assert _iam.sapm_aoi_loss(1, module_parameters) == 5

module_parameters = {'B0': 5, 'B1': 0, 'B2': 0, 'B3': 0, 'B4': 0, 'B5': 0}
assert _iam.sapm_aoi_loss(1, module_parameters, upper=1) == 1

module_parameters = {'B0': -5, 'B1': 0, 'B2': 0, 'B3': 0, 'B4': 0, 'B5': 0}
assert _iam.sapm_aoi_loss(1, module_parameters) == 0
27 changes: 4 additions & 23 deletions pvlib/test/test_pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,33 +293,14 @@ def test_PVSystem_first_solar_spectral_loss(module_parameters, module_type,
np.array([[0, 1.007572, 0, np.nan]])),
(pd.Series([80]), pd.Series([0.597472]))
])
def test_sapm_aoi_loss(sapm_module_params, aoi, expected):

out = pvsystem.sapm_aoi_loss(aoi, sapm_module_params)

if isinstance(aoi, pd.Series):
assert_series_equal(out, expected, check_less_precise=4)
else:
assert_allclose(out, expected, atol=1e-4)


def test_sapm_aoi_loss_limits():
module_parameters = {'B0': 5, 'B1': 0, 'B2': 0, 'B3': 0, 'B4': 0, 'B5': 0}
assert pvsystem.sapm_aoi_loss(1, module_parameters) == 5

module_parameters = {'B0': 5, 'B1': 0, 'B2': 0, 'B3': 0, 'B4': 0, 'B5': 0}
assert pvsystem.sapm_aoi_loss(1, module_parameters, upper=1) == 1

module_parameters = {'B0': -5, 'B1': 0, 'B2': 0, 'B3': 0, 'B4': 0, 'B5': 0}
assert pvsystem.sapm_aoi_loss(1, module_parameters) == 0


def test_PVSystem_sapm_aoi_loss(sapm_module_params, mocker):
def test_PVSystem_aoi_sapm(sapm_module_params, mocker):
system = pvsystem.PVSystem(module_parameters=sapm_module_params)
mocker.spy(pvsystem, 'sapm_aoi_loss')
mocker.spy(_iam, 'sapm')
aoi = 0
out = system.sapm_aoi_loss(aoi)
pvsystem.sapm_aoi_loss.assert_called_once_with(aoi, sapm_module_params)
out = system.aoi_sapm(aoi)
_iam.sapm.assert_called_once_with(aoi, sapm_module_params)
assert_allclose(out, 1.0, atol=0.01)


Expand Down
0