Skip to content

Commit 237b03f

Browse files
authored
add PVSystem.losses_parameters, add support for custom pvwatts losses parameters in ModelChain (#491)
* losses_parameters * add tests * style fixes * fix documentation
1 parent 80bf796 commit 237b03f

File tree

4 files changed

+70
-24
lines changed

4 files changed

+70
-24
lines changed

docs/sphinx/source/whatsnew/v0.6.0.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ v0.6.0 (___, 2018)
55

66
API Changes
77
~~~~~~~~~~~
8-
* pvsystem.calcparams_desoto now requires arguments for each module model parameter.
8+
* pvsystem.calcparams_desoto now requires arguments for each module model
9+
parameter. (:issue:`462`)
10+
* Add losses_parameters attribute to PVSystem objects and remove the kwargs
11+
support from PVSystem.pvwatts_losses. Enables custom losses specification
12+
in ModelChain calculations. (:issue:`484`)
913
* removed irradiance parameter from ModelChain.run_model and ModelChain.prepare_inputs
1014

1115

pvlib/pvsystem.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ class PVSystem(object):
100100
racking_model : None or string, default 'open_rack_cell_glassback'
101101
Used for cell and module temperature calculations.
102102
103+
losses_parameters : None, dict or Series, default None
104+
Losses parameters as defined by PVWatts or other.
105+
103106
name : None or string, default None
104107
105108
**kwargs
@@ -120,8 +123,7 @@ def __init__(self,
120123
modules_per_string=1, strings_per_inverter=1,
121124
inverter=None, inverter_parameters=None,
122125
racking_model='open_rack_cell_glassback',
123-
name=None,
124-
**kwargs):
126+
losses_parameters=None, name=None, **kwargs):
125127

126128
self.name = name
127129

@@ -151,6 +153,11 @@ def __init__(self,
151153
else:
152154
self.inverter_parameters = inverter_parameters
153155

156+
if losses_parameters is None:
157+
self.losses_parameters = {}
158+
else:
159+
self.losses_parameters = losses_parameters
160+
154161
self.racking_model = racking_model
155162

156163
def __repr__(self):
@@ -627,15 +634,17 @@ def pvwatts_dc(self, g_poa_effective, temp_cell):
627634
self.module_parameters['gamma_pdc'],
628635
**kwargs)
629636

630-
def pvwatts_losses(self, **kwargs):
637+
def pvwatts_losses(self):
631638
"""
632639
Calculates DC power losses according the PVwatts model using
633-
:py:func:`pvwatts_losses`. No attributes are used in this
634-
calculation, but all keyword arguments will be passed to the
635-
function.
640+
:py:func:`pvwatts_losses` and ``self.losses_parameters``.`
636641
637642
See :py:func:`pvwatts_losses` for details.
638643
"""
644+
kwargs = _build_kwargs(['soiling', 'shading', 'snow', 'mismatch',
645+
'wiring', 'connections', 'lid',
646+
'nameplate_rating', 'age', 'availability'],
647+
self.losses_parameters)
639648
return pvwatts_losses(**kwargs)
640649

641650
def pvwatts_ac(self, pdc):

pvlib/test/test_modelchain.py

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import sys
2+
13
import numpy as np
24
import pandas as pd
35
from numpy import nan
@@ -78,11 +80,19 @@ def pvwatts_dc_pvwatts_ac_system(sam_data):
7880
return system
7981

8082

81-
@pytest.fixture()
83+
@pytest.fixture
8284
def location():
8385
return Location(32.2, -111, altitude=700)
8486

8587

88+
@pytest.fixture
89+
def weather():
90+
times = pd.date_range('20160101 1200-0700', periods=2, freq='6H')
91+
weather = pd.DataFrame({'ghi': [500, 0], 'dni': [800, 0], 'dhi': [100, 0]},
92+
index=times)
93+
return weather
94+
95+
8696
def test_ModelChain_creation(system, location):
8797
mc = ModelChain(system, location)
8898

@@ -292,7 +302,7 @@ def test_spectral_models(system, location, spectral_model):
292302
columns=['precipitable_water'])
293303
mc = ModelChain(system, location, dc_model='sapm',
294304
aoi_model='no_loss', spectral_model=spectral_model)
295-
spectral_modifier = mc.run_model(times=times,
305+
spectral_modifier = mc.run_model(times=times,
296306
weather=weather).spectral_modifier
297307
assert isinstance(spectral_modifier, (pd.Series, float, int))
298308

@@ -302,22 +312,44 @@ def constant_losses(mc):
302312
mc.ac *= mc.losses
303313

304314

305-
@requires_scipy
306-
@pytest.mark.parametrize('losses_model, expected', [
307-
('pvwatts', [163.280464174, 0]),
308-
('no_loss', [190.028186986, 0]),
309-
(constant_losses, [171.025368287, 0])
310-
])
311-
def test_losses_models(pvwatts_dc_pvwatts_ac_system, location, losses_model,
312-
expected):
315+
def test_losses_models_pvwatts(pvwatts_dc_pvwatts_ac_system, location, weather,
316+
mocker):
317+
age = 1
318+
pvwatts_dc_pvwatts_ac_system.losses_parameters = dict(age=age)
319+
m = mocker.spy(pvsystem, 'pvwatts_losses')
313320
mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, dc_model='pvwatts',
314321
aoi_model='no_loss', spectral_model='no_loss',
315-
losses_model=losses_model)
316-
times = pd.date_range('20160101 1200-0700', periods=2, freq='6H')
317-
ac = mc.run_model(times).ac
322+
losses_model='pvwatts')
323+
mc.run_model(weather.index, weather=weather)
324+
assert m.call_count == 1
325+
m.assert_called_with(age=age)
326+
assert isinstance(mc.ac, (pd.Series, pd.DataFrame))
327+
assert not mc.ac.empty
318328

319-
expected = pd.Series(np.array(expected), index=times)
320-
assert_series_equal(ac, expected, check_less_precise=2)
329+
330+
def test_losses_models_ext_def(pvwatts_dc_pvwatts_ac_system, location, weather,
331+
mocker):
332+
m = mocker.spy(sys.modules[__name__], 'constant_losses')
333+
mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, dc_model='pvwatts',
334+
aoi_model='no_loss', spectral_model='no_loss',
335+
losses_model=constant_losses)
336+
mc.run_model(weather.index, weather=weather)
337+
assert m.call_count == 1
338+
assert isinstance(mc.ac, (pd.Series, pd.DataFrame))
339+
assert mc.losses == 0.9
340+
assert not mc.ac.empty
341+
342+
343+
def test_losses_models_no_loss(pvwatts_dc_pvwatts_ac_system, location, weather,
344+
mocker):
345+
m = mocker.spy(pvsystem, 'pvwatts_losses')
346+
mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, dc_model='pvwatts',
347+
aoi_model='no_loss', spectral_model='no_loss',
348+
losses_model='no_loss')
349+
assert mc.losses_model == mc.no_extra_losses
350+
mc.run_model(weather.index, weather=weather)
351+
assert m.call_count == 0
352+
assert mc.losses == 1
321353

322354

323355
@pytest.mark.parametrize('model', [

pvlib/test/test_pvsystem.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1358,9 +1358,10 @@ def test_PVSystem_pvwatts_dc_kwargs(mocker):
13581358
def test_PVSystem_pvwatts_losses(mocker):
13591359
mocker.spy(pvsystem, 'pvwatts_losses')
13601360
system = make_pvwatts_system_defaults()
1361-
expected = 15
13621361
age = 1
1363-
out = system.pvwatts_losses(age=age)
1362+
system.losses_parameters = dict(age=age)
1363+
expected = 15
1364+
out = system.pvwatts_losses()
13641365
pvsystem.pvwatts_losses.assert_called_once_with(age=age)
13651366
assert out < expected
13661367

0 commit comments

Comments
 (0)