Skip to content

Commit ba4a199

Browse files
authored
Implement pvwatts_multi methods for PVSystem and ModelChain (#1132)
* add pvwatts_multi methods * make pvwatts_system fixtures * add tests for pvwatts_multi methods * fix up pvsystem test * fixes from failed tests * pvwatts_dc produces Series * add test to infer pvwatts_multi * handle aoi_model and spectral_model * update whatsnew
1 parent 149fd02 commit ba4a199

File tree

6 files changed

+116
-43
lines changed

6 files changed

+116
-43
lines changed

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,12 @@ Enhancements
4242
:py:class:`~pvlib.modelchain.ModelChain`. This includes substantial API
4343
enhancements for accepting different weather input for each ``Array`` in the
4444
system. (:pull:`1076`, :issue:`1067`)
45-
* Support for :py:func:`~pvlib.inverter.sandia_multi` added to
45+
* Support for :py:func:`~pvlib.inverter.sandia_multi` and
46+
:py:func:`~pvlib.inverter.pvwatts_multi` added to
4647
:py:class:`~pvlib.pvsystem.PVSystem` and
47-
:py:class:`~pvlib.modelchain.ModelChain` (as ``ac_model='sandia_multi'``).
48-
(:pull:`1076`, :issue:`1067`)
48+
:py:class:`~pvlib.modelchain.ModelChain` (as ``ac_model='sandia_multi'``
49+
and ``ac_model='pvwatts_multi'``).
50+
(:pull:`1076`, :issue:`1067`, :pull:`1132`, :issue:`1117`)
4951
* :py:class:`~pvlib.modelchain.ModelChain` 'run_model' methods now
5052
automatically switch to using ``'effective_irradiance'`` (if available) for
5153
cell temperature models, when ``'poa_global'`` is not provided in input

pvlib/inverter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ def pvwatts_multi(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637):
414414
DC power on each MPPT input of the inverter. If type is array, must
415415
be 2d with axis 0 being the MPPT inputs. Same unit as ``pdc0``.
416416
pdc0: numeric
417-
DC input limit of the inverter. Same unit as ``pdc``.
417+
Total DC power limit of the inverter. Same unit as ``pdc``.
418418
eta_inv_nom: numeric, default 0.96
419419
Nominal inverter efficiency. [unitless]
420420
eta_inv_ref: numeric, default 0.9637

pvlib/modelchain.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,8 @@ def ac_model(self, model):
769769
self._ac_model = self.adrinverter
770770
elif model == 'pvwatts':
771771
self._ac_model = self.pvwatts_inverter
772+
elif model == 'pvwatts_multi':
773+
self._ac_model = self.pvwatts_multi_inverter
772774
else:
773775
raise ValueError(model + ' is not a valid AC power model')
774776
else:
@@ -793,10 +795,11 @@ def infer_ac_model(self):
793795
def _infer_ac_model_multi(self, inverter_params):
794796
if _snl_params(inverter_params):
795797
return self.sandia_multi_inverter
798+
elif _pvwatts_params(inverter_params):
799+
return self.pvwatts_multi_inverter
796800
raise ValueError('could not infer multi-array AC model from '
797-
'system.inverter_parameters. Not all ac models '
798-
'support systems with mutiple Arrays. '
799-
'Only sandia_multi supports multiple '
801+
'system.inverter_parameters. Only sandia and pvwatts '
802+
'inverter models support multiple '
800803
'Arrays. Check system.inverter_parameters or '
801804
'explicitly set the model with the ac_model kwarg.')
802805

@@ -807,6 +810,10 @@ def sandia_multi_inverter(self):
807810
)
808811
return self
809812

813+
def pvwatts_multi_inverter(self):
814+
self.results.ac = self.system.pvwatts_multi(self.results.dc)
815+
return self
816+
810817
def snlinverter(self):
811818
self.results.ac = self.system.snlinverter(self.results.dc['v_mp'],
812819
self.results.dc['p_mp'])

pvlib/pvsystem.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,21 @@ def pvwatts_ac(self, pdc):
10001000
return inverter.pvwatts(pdc, self.inverter_parameters['pdc0'],
10011001
**kwargs)
10021002

1003+
def pvwatts_multi(self, p_dc):
1004+
"""Uses :py:func:`pvlib.inverter.pvwatts_multi` to calculate AC power
1005+
based on ``self.inverter_parameters`` and the input voltage and power.
1006+
1007+
The parameter `p_dc` must be a tuple with length equal to
1008+
``self.num_arrays`` if the system has more than one array.
1009+
1010+
See :py:func:`pvlib.inverter.pvwatts_multi` for details.
1011+
"""
1012+
p_dc = self._validate_per_array(p_dc)
1013+
kwargs = _build_kwargs(['eta_inv_nom', 'eta_inv_ref'],
1014+
self.inverter_parameters)
1015+
return inverter.pvwatts_multi(p_dc, self.inverter_parameters['pdc0'],
1016+
**kwargs)
1017+
10031018
@deprecated('0.8', alternative='PVSystem, Location, and ModelChain',
10041019
name='PVSystem.localize', removal='0.9')
10051020
def localize(self, location=None, latitude=None, longitude=None,

pvlib/tests/test_modelchain.py

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,26 @@ def pvwatts_dc_pvwatts_ac_system(sapm_temperature_cs5p_220m):
166166
return system
167167

168168

169+
@pytest.fixture(scope="function")
170+
def pvwatts_dc_pvwatts_ac_system_arrays(sapm_temperature_cs5p_220m):
171+
module_parameters = {'pdc0': 220, 'gamma_pdc': -0.003}
172+
temp_model_params = sapm_temperature_cs5p_220m.copy()
173+
inverter_parameters = {'pdc0': 220, 'eta_inv_nom': 0.95}
174+
array_one = pvsystem.Array(
175+
surface_tilt=32.2, surface_azimuth=180,
176+
module_parameters=module_parameters.copy(),
177+
temperature_model_parameters=temp_model_params.copy()
178+
)
179+
array_two = pvsystem.Array(
180+
surface_tilt=42.2, surface_azimuth=220,
181+
module_parameters=module_parameters.copy(),
182+
temperature_model_parameters=temp_model_params.copy()
183+
)
184+
system = PVSystem(
185+
arrays=[array_one, array_two], inverter_parameters=inverter_parameters)
186+
return system
187+
188+
169189
@pytest.fixture(scope="function")
170190
def pvwatts_dc_pvwatts_ac_faiman_temp_system():
171191
module_parameters = {'pdc0': 220, 'gamma_pdc': -0.003}
@@ -452,16 +472,15 @@ def test_run_model_from_irradiance_arrays_no_loss_input_type(
452472
)
453473

454474

455-
@pytest.mark.parametrize('inverter', ['adr', 'pvwatts'])
475+
@pytest.mark.parametrize('inverter', ['adr'])
456476
def test_ModelChain_invalid_inverter_params_arrays(
457477
inverter, sapm_dc_snl_ac_system_same_arrays,
458478
location, adr_inverter_parameters):
459-
inverter_params = {'adr': adr_inverter_parameters,
460-
'pvwatts': {'pdc0': 220, 'eta_inv_nom': 0.95}}
479+
inverter_params = {'adr': adr_inverter_parameters}
461480
sapm_dc_snl_ac_system_same_arrays.inverter_parameters = \
462481
inverter_params[inverter]
463482
with pytest.raises(ValueError,
464-
match=r'Only sandia_multi supports multiple Arrays\.'):
483+
match=r'Only sandia and pvwatts inverter models'):
465484
ModelChain(sapm_dc_snl_ac_system_same_arrays, location)
466485

467486

@@ -570,10 +589,15 @@ def test_prepare_inputs_missing_irrad_component(
570589
mc.prepare_inputs(weather)
571590

572591

592+
@pytest.mark.parametrize('ac_model', ['sandia', 'pvwatts'])
573593
@pytest.mark.parametrize("input_type", [tuple, list])
574-
def test_run_model_arrays_weather(sapm_dc_snl_ac_system_same_arrays, location,
575-
input_type):
576-
mc = ModelChain(sapm_dc_snl_ac_system_same_arrays, location)
594+
def test_run_model_arrays_weather(sapm_dc_snl_ac_system_same_arrays,
595+
pvwatts_dc_pvwatts_ac_system_arrays,
596+
location, ac_model, input_type):
597+
system = {'sandia': sapm_dc_snl_ac_system_same_arrays,
598+
'pvwatts': pvwatts_dc_pvwatts_ac_system_arrays}
599+
mc = ModelChain(system[ac_model], location, aoi_model='no_loss',
600+
spectral_model='no_loss')
577601
times = pd.date_range('20200101 1200-0700', periods=2, freq='2H')
578602
weather_one = pd.DataFrame({'dni': [900, 800],
579603
'ghi': [600, 500],
@@ -1171,18 +1195,21 @@ def acdc(mc):
11711195

11721196

11731197
@pytest.mark.parametrize('ac_model', ['sandia', 'adr',
1174-
'pvwatts', 'sandia_multi'])
1198+
'pvwatts', 'sandia_multi',
1199+
'pvwatts_multi'])
11751200
def test_ac_models(sapm_dc_snl_ac_system, cec_dc_adr_ac_system,
11761201
pvwatts_dc_pvwatts_ac_system, location, ac_model,
11771202
weather, mocker):
11781203
ac_systems = {'sandia': sapm_dc_snl_ac_system,
11791204
'sandia_multi': sapm_dc_snl_ac_system,
11801205
'adr': cec_dc_adr_ac_system,
1181-
'pvwatts': pvwatts_dc_pvwatts_ac_system}
1206+
'pvwatts': pvwatts_dc_pvwatts_ac_system,
1207+
'pvwatts_multi': pvwatts_dc_pvwatts_ac_system}
11821208
ac_method_name = {'sandia': 'snlinverter',
11831209
'sandia_multi': 'sandia_multi',
11841210
'adr': 'adrinverter',
1185-
'pvwatts': 'pvwatts_ac'}
1211+
'pvwatts': 'pvwatts_ac',
1212+
'pvwatts_multi': 'pvwatts_multi'}
11861213
system = ac_systems[ac_model]
11871214

11881215
mc = ModelChain(system, location, ac_model=ac_model,

pvlib/tests/test_pvsystem.py

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1884,43 +1884,43 @@ def test_pvwatts_losses_series():
18841884
assert_series_equal(expected, out)
18851885

18861886

1887-
def make_pvwatts_system_defaults():
1887+
@pytest.fixture
1888+
def pvwatts_system_defaults():
18881889
module_parameters = {'pdc0': 100, 'gamma_pdc': -0.003}
18891890
inverter_parameters = {'pdc0': 90}
18901891
system = pvsystem.PVSystem(module_parameters=module_parameters,
18911892
inverter_parameters=inverter_parameters)
18921893
return system
18931894

18941895

1895-
def make_pvwatts_system_kwargs():
1896+
@pytest.fixture
1897+
def pvwatts_system_kwargs():
18961898
module_parameters = {'pdc0': 100, 'gamma_pdc': -0.003, 'temp_ref': 20}
18971899
inverter_parameters = {'pdc0': 90, 'eta_inv_nom': 0.95, 'eta_inv_ref': 1.0}
18981900
system = pvsystem.PVSystem(module_parameters=module_parameters,
18991901
inverter_parameters=inverter_parameters)
19001902
return system
19011903

19021904

1903-
def test_PVSystem_pvwatts_dc(mocker):
1905+
def test_PVSystem_pvwatts_dc(pvwatts_system_defaults, mocker):
19041906
mocker.spy(pvsystem, 'pvwatts_dc')
1905-
system = make_pvwatts_system_defaults()
19061907
irrad = 900
19071908
temp_cell = 30
19081909
expected = 90
1909-
out = system.pvwatts_dc(irrad, temp_cell)
1910-
pvsystem.pvwatts_dc.assert_called_once_with(irrad, temp_cell,
1911-
**system.module_parameters)
1910+
out = pvwatts_system_defaults.pvwatts_dc(irrad, temp_cell)
1911+
pvsystem.pvwatts_dc.assert_called_once_with(
1912+
irrad, temp_cell, **pvwatts_system_defaults.module_parameters)
19121913
assert_allclose(expected, out, atol=10)
19131914

19141915

1915-
def test_PVSystem_pvwatts_dc_kwargs(mocker):
1916+
def test_PVSystem_pvwatts_dc_kwargs(pvwatts_system_kwargs, mocker):
19161917
mocker.spy(pvsystem, 'pvwatts_dc')
1917-
system = make_pvwatts_system_kwargs()
19181918
irrad = 900
19191919
temp_cell = 30
19201920
expected = 90
1921-
out = system.pvwatts_dc(irrad, temp_cell)
1922-
pvsystem.pvwatts_dc.assert_called_once_with(irrad, temp_cell,
1923-
**system.module_parameters)
1921+
out = pvwatts_system_kwargs.pvwatts_dc(irrad, temp_cell)
1922+
pvsystem.pvwatts_dc.assert_called_once_with(
1923+
irrad, temp_cell, **pvwatts_system_kwargs.module_parameters)
19241924
assert_allclose(expected, out, atol=10)
19251925

19261926

@@ -1977,37 +1977,59 @@ def test_PVSystem_multiple_array_pvwatts_dc_value_error():
19771977
# ValueError is raised for non-tuple iterable with correct length
19781978
system.pvwatts_dc((1, 1, 1), pd.Series([1, 2, 3]))
19791979

1980-
def test_PVSystem_pvwatts_losses(mocker):
1980+
1981+
def test_PVSystem_pvwatts_losses(pvwatts_system_defaults, mocker):
19811982
mocker.spy(pvsystem, 'pvwatts_losses')
1982-
system = make_pvwatts_system_defaults()
19831983
age = 1
1984-
system.losses_parameters = dict(age=age)
1984+
pvwatts_system_defaults.losses_parameters = dict(age=age)
19851985
expected = 15
1986-
out = system.pvwatts_losses()
1986+
out = pvwatts_system_defaults.pvwatts_losses()
19871987
pvsystem.pvwatts_losses.assert_called_once_with(age=age)
19881988
assert out < expected
19891989

19901990

1991-
def test_PVSystem_pvwatts_ac(mocker):
1991+
def test_PVSystem_pvwatts_ac(pvwatts_system_defaults, mocker):
19921992
mocker.spy(inverter, 'pvwatts')
1993-
system = make_pvwatts_system_defaults()
19941993
pdc = 50
1995-
out = system.pvwatts_ac(pdc)
1996-
inverter.pvwatts.assert_called_once_with(pdc,
1997-
**system.inverter_parameters)
1994+
out = pvwatts_system_defaults.pvwatts_ac(pdc)
1995+
inverter.pvwatts.assert_called_once_with(
1996+
pdc, **pvwatts_system_defaults.inverter_parameters)
19981997
assert out < pdc
19991998

20001999

2001-
def test_PVSystem_pvwatts_ac_kwargs(mocker):
2000+
def test_PVSystem_pvwatts_ac_kwargs(pvwatts_system_kwargs, mocker):
20022001
mocker.spy(inverter, 'pvwatts')
2003-
system = make_pvwatts_system_kwargs()
20042002
pdc = 50
2005-
out = system.pvwatts_ac(pdc)
2006-
inverter.pvwatts.assert_called_once_with(pdc,
2007-
**system.inverter_parameters)
2003+
out = pvwatts_system_kwargs.pvwatts_ac(pdc)
2004+
inverter.pvwatts.assert_called_once_with(
2005+
pdc, **pvwatts_system_kwargs.inverter_parameters)
20082006
assert out < pdc
20092007

20102008

2009+
def test_PVSystem_pvwatts_multi(pvwatts_system_defaults,
2010+
pvwatts_system_kwargs):
2011+
expected = [pd.Series([0.0, 48.123524, 86.400000]),
2012+
pd.Series([0.0, 45.893550, 85.500000])]
2013+
systems = [pvwatts_system_defaults, pvwatts_system_kwargs]
2014+
for base_sys, exp in zip(systems, expected):
2015+
system = pvsystem.PVSystem(
2016+
arrays=[pvsystem.Array(), pvsystem.Array()],
2017+
inverter_parameters=base_sys.inverter_parameters,
2018+
)
2019+
pdcs = pd.Series([0., 25., 50.])
2020+
pacs = system.pvwatts_multi((pdcs, pdcs))
2021+
assert_series_equal(pacs, exp)
2022+
with pytest.raises(ValueError,
2023+
match="Length mismatch for per-array parameter"):
2024+
system.pvwatts_multi((pdcs,))
2025+
with pytest.raises(ValueError,
2026+
match="Length mismatch for per-array parameter"):
2027+
system.pvwatts_multi(pdcs)
2028+
with pytest.raises(ValueError,
2029+
match="Length mismatch for per-array parameter"):
2030+
system.pvwatts_multi((pdcs, pdcs, pdcs))
2031+
2032+
20112033
def test_PVSystem_num_arrays():
20122034
system_one = pvsystem.PVSystem()
20132035
system_two = pvsystem.PVSystem(arrays=[pvsystem.Array(), pvsystem.Array()])

0 commit comments

Comments
 (0)