Skip to content

Implement pvwatts_multi methods for PVSystem and ModelChain #1132

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 9 commits into from
Jan 20, 2021
Merged
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
8 changes: 5 additions & 3 deletions docs/sphinx/source/whatsnew/v0.9.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ Enhancements
:py:class:`~pvlib.modelchain.ModelChain`. This includes substantial API
enhancements for accepting different weather input for each ``Array`` in the
system. (:pull:`1076`, :issue:`1067`)
* Support for :py:func:`~pvlib.inverter.sandia_multi` added to
* Support for :py:func:`~pvlib.inverter.sandia_multi` and
:py:func:`~pvlib.inverter.pvwatts_multi` added to
:py:class:`~pvlib.pvsystem.PVSystem` and
:py:class:`~pvlib.modelchain.ModelChain` (as ``ac_model='sandia_multi'``).
(:pull:`1076`, :issue:`1067`)
:py:class:`~pvlib.modelchain.ModelChain` (as ``ac_model='sandia_multi'``
and ``ac_model='pvwatts_multi'``).
(:pull:`1076`, :issue:`1067`, :pull:`1132`, :issue:`1117`)
* :py:class:`~pvlib.modelchain.ModelChain` 'run_model' methods now
automatically switch to using ``'effective_irradiance'`` (if available) for
cell temperature models, when ``'poa_global'`` is not provided in input
Expand Down
2 changes: 1 addition & 1 deletion pvlib/inverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ def pvwatts_multi(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637):
DC power on each MPPT input of the inverter. If type is array, must
be 2d with axis 0 being the MPPT inputs. Same unit as ``pdc0``.
pdc0: numeric
DC input limit of the inverter. Same unit as ``pdc``.
Total DC power limit of the inverter. Same unit as ``pdc``.
eta_inv_nom: numeric, default 0.96
Nominal inverter efficiency. [unitless]
eta_inv_ref: numeric, default 0.9637
Expand Down
13 changes: 10 additions & 3 deletions pvlib/modelchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,8 @@ def ac_model(self, model):
self._ac_model = self.adrinverter
elif model == 'pvwatts':
self._ac_model = self.pvwatts_inverter
elif model == 'pvwatts_multi':
self._ac_model = self.pvwatts_multi_inverter
else:
raise ValueError(model + ' is not a valid AC power model')
else:
Expand All @@ -793,10 +795,11 @@ def infer_ac_model(self):
def _infer_ac_model_multi(self, inverter_params):
if _snl_params(inverter_params):
return self.sandia_multi_inverter
elif _pvwatts_params(inverter_params):
return self.pvwatts_multi_inverter
raise ValueError('could not infer multi-array AC model from '
'system.inverter_parameters. Not all ac models '
'support systems with mutiple Arrays. '
'Only sandia_multi supports multiple '
'system.inverter_parameters. Only sandia and pvwatts '
'inverter models support multiple '
'Arrays. Check system.inverter_parameters or '
'explicitly set the model with the ac_model kwarg.')

Expand All @@ -807,6 +810,10 @@ def sandia_multi_inverter(self):
)
return self

def pvwatts_multi_inverter(self):
self.results.ac = self.system.pvwatts_multi(self.results.dc)
return self

def snlinverter(self):
self.results.ac = self.system.snlinverter(self.results.dc['v_mp'],
self.results.dc['p_mp'])
Expand Down
15 changes: 15 additions & 0 deletions pvlib/pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,21 @@ def pvwatts_ac(self, pdc):
return inverter.pvwatts(pdc, self.inverter_parameters['pdc0'],
**kwargs)

def pvwatts_multi(self, p_dc):
"""Uses :py:func:`pvlib.inverter.pvwatts_multi` to calculate AC power
based on ``self.inverter_parameters`` and the input voltage and power.

The parameter `p_dc` must be a tuple with length equal to
``self.num_arrays`` if the system has more than one array.

See :py:func:`pvlib.inverter.pvwatts_multi` for details.
"""
p_dc = self._validate_per_array(p_dc)
kwargs = _build_kwargs(['eta_inv_nom', 'eta_inv_ref'],
self.inverter_parameters)
return inverter.pvwatts_multi(p_dc, self.inverter_parameters['pdc0'],
**kwargs)

@deprecated('0.8', alternative='PVSystem, Location, and ModelChain',
name='PVSystem.localize', removal='0.9')
def localize(self, location=None, latitude=None, longitude=None,
Expand Down
47 changes: 37 additions & 10 deletions pvlib/tests/test_modelchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,26 @@ def pvwatts_dc_pvwatts_ac_system(sapm_temperature_cs5p_220m):
return system


@pytest.fixture(scope="function")
def pvwatts_dc_pvwatts_ac_system_arrays(sapm_temperature_cs5p_220m):
module_parameters = {'pdc0': 220, 'gamma_pdc': -0.003}
temp_model_params = sapm_temperature_cs5p_220m.copy()
inverter_parameters = {'pdc0': 220, 'eta_inv_nom': 0.95}
array_one = pvsystem.Array(
surface_tilt=32.2, surface_azimuth=180,
module_parameters=module_parameters.copy(),
temperature_model_parameters=temp_model_params.copy()
)
array_two = pvsystem.Array(
surface_tilt=42.2, surface_azimuth=220,
module_parameters=module_parameters.copy(),
temperature_model_parameters=temp_model_params.copy()
)
system = PVSystem(
arrays=[array_one, array_two], inverter_parameters=inverter_parameters)
return system


@pytest.fixture(scope="function")
def pvwatts_dc_pvwatts_ac_faiman_temp_system():
module_parameters = {'pdc0': 220, 'gamma_pdc': -0.003}
Expand Down Expand Up @@ -452,16 +472,15 @@ def test_run_model_from_irradiance_arrays_no_loss_input_type(
)


@pytest.mark.parametrize('inverter', ['adr', 'pvwatts'])
@pytest.mark.parametrize('inverter', ['adr'])
def test_ModelChain_invalid_inverter_params_arrays(
inverter, sapm_dc_snl_ac_system_same_arrays,
location, adr_inverter_parameters):
inverter_params = {'adr': adr_inverter_parameters,
'pvwatts': {'pdc0': 220, 'eta_inv_nom': 0.95}}
inverter_params = {'adr': adr_inverter_parameters}
sapm_dc_snl_ac_system_same_arrays.inverter_parameters = \
inverter_params[inverter]
with pytest.raises(ValueError,
match=r'Only sandia_multi supports multiple Arrays\.'):
match=r'Only sandia and pvwatts inverter models'):
ModelChain(sapm_dc_snl_ac_system_same_arrays, location)


Expand Down Expand Up @@ -570,10 +589,15 @@ def test_prepare_inputs_missing_irrad_component(
mc.prepare_inputs(weather)


@pytest.mark.parametrize('ac_model', ['sandia', 'pvwatts'])
@pytest.mark.parametrize("input_type", [tuple, list])
def test_run_model_arrays_weather(sapm_dc_snl_ac_system_same_arrays, location,
input_type):
mc = ModelChain(sapm_dc_snl_ac_system_same_arrays, location)
def test_run_model_arrays_weather(sapm_dc_snl_ac_system_same_arrays,
pvwatts_dc_pvwatts_ac_system_arrays,
location, ac_model, input_type):
system = {'sandia': sapm_dc_snl_ac_system_same_arrays,
'pvwatts': pvwatts_dc_pvwatts_ac_system_arrays}
mc = ModelChain(system[ac_model], location, aoi_model='no_loss',
spectral_model='no_loss')
times = pd.date_range('20200101 1200-0700', periods=2, freq='2H')
weather_one = pd.DataFrame({'dni': [900, 800],
'ghi': [600, 500],
Expand Down Expand Up @@ -1171,18 +1195,21 @@ def acdc(mc):


@pytest.mark.parametrize('ac_model', ['sandia', 'adr',
'pvwatts', 'sandia_multi'])
'pvwatts', 'sandia_multi',
'pvwatts_multi'])
def test_ac_models(sapm_dc_snl_ac_system, cec_dc_adr_ac_system,
pvwatts_dc_pvwatts_ac_system, location, ac_model,
weather, mocker):
ac_systems = {'sandia': sapm_dc_snl_ac_system,
'sandia_multi': sapm_dc_snl_ac_system,
'adr': cec_dc_adr_ac_system,
'pvwatts': pvwatts_dc_pvwatts_ac_system}
'pvwatts': pvwatts_dc_pvwatts_ac_system,
'pvwatts_multi': pvwatts_dc_pvwatts_ac_system}
ac_method_name = {'sandia': 'snlinverter',
'sandia_multi': 'sandia_multi',
'adr': 'adrinverter',
'pvwatts': 'pvwatts_ac'}
'pvwatts': 'pvwatts_ac',
'pvwatts_multi': 'pvwatts_multi'}
system = ac_systems[ac_model]

mc = ModelChain(system, location, ac_model=ac_model,
Expand Down
74 changes: 48 additions & 26 deletions pvlib/tests/test_pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -1884,43 +1884,43 @@ def test_pvwatts_losses_series():
assert_series_equal(expected, out)


def make_pvwatts_system_defaults():
@pytest.fixture
def pvwatts_system_defaults():
module_parameters = {'pdc0': 100, 'gamma_pdc': -0.003}
inverter_parameters = {'pdc0': 90}
system = pvsystem.PVSystem(module_parameters=module_parameters,
inverter_parameters=inverter_parameters)
return system


def make_pvwatts_system_kwargs():
@pytest.fixture
def pvwatts_system_kwargs():
module_parameters = {'pdc0': 100, 'gamma_pdc': -0.003, 'temp_ref': 20}
inverter_parameters = {'pdc0': 90, 'eta_inv_nom': 0.95, 'eta_inv_ref': 1.0}
system = pvsystem.PVSystem(module_parameters=module_parameters,
inverter_parameters=inverter_parameters)
return system


def test_PVSystem_pvwatts_dc(mocker):
def test_PVSystem_pvwatts_dc(pvwatts_system_defaults, mocker):
mocker.spy(pvsystem, 'pvwatts_dc')
system = make_pvwatts_system_defaults()
irrad = 900
temp_cell = 30
expected = 90
out = system.pvwatts_dc(irrad, temp_cell)
pvsystem.pvwatts_dc.assert_called_once_with(irrad, temp_cell,
**system.module_parameters)
out = pvwatts_system_defaults.pvwatts_dc(irrad, temp_cell)
pvsystem.pvwatts_dc.assert_called_once_with(
irrad, temp_cell, **pvwatts_system_defaults.module_parameters)
assert_allclose(expected, out, atol=10)


def test_PVSystem_pvwatts_dc_kwargs(mocker):
def test_PVSystem_pvwatts_dc_kwargs(pvwatts_system_kwargs, mocker):
mocker.spy(pvsystem, 'pvwatts_dc')
system = make_pvwatts_system_kwargs()
irrad = 900
temp_cell = 30
expected = 90
out = system.pvwatts_dc(irrad, temp_cell)
pvsystem.pvwatts_dc.assert_called_once_with(irrad, temp_cell,
**system.module_parameters)
out = pvwatts_system_kwargs.pvwatts_dc(irrad, temp_cell)
pvsystem.pvwatts_dc.assert_called_once_with(
irrad, temp_cell, **pvwatts_system_kwargs.module_parameters)
assert_allclose(expected, out, atol=10)


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

def test_PVSystem_pvwatts_losses(mocker):

def test_PVSystem_pvwatts_losses(pvwatts_system_defaults, mocker):
mocker.spy(pvsystem, 'pvwatts_losses')
system = make_pvwatts_system_defaults()
age = 1
system.losses_parameters = dict(age=age)
pvwatts_system_defaults.losses_parameters = dict(age=age)
expected = 15
out = system.pvwatts_losses()
out = pvwatts_system_defaults.pvwatts_losses()
pvsystem.pvwatts_losses.assert_called_once_with(age=age)
assert out < expected


def test_PVSystem_pvwatts_ac(mocker):
def test_PVSystem_pvwatts_ac(pvwatts_system_defaults, mocker):
mocker.spy(inverter, 'pvwatts')
system = make_pvwatts_system_defaults()
pdc = 50
out = system.pvwatts_ac(pdc)
inverter.pvwatts.assert_called_once_with(pdc,
**system.inverter_parameters)
out = pvwatts_system_defaults.pvwatts_ac(pdc)
inverter.pvwatts.assert_called_once_with(
pdc, **pvwatts_system_defaults.inverter_parameters)
assert out < pdc


def test_PVSystem_pvwatts_ac_kwargs(mocker):
def test_PVSystem_pvwatts_ac_kwargs(pvwatts_system_kwargs, mocker):
mocker.spy(inverter, 'pvwatts')
system = make_pvwatts_system_kwargs()
pdc = 50
out = system.pvwatts_ac(pdc)
inverter.pvwatts.assert_called_once_with(pdc,
**system.inverter_parameters)
out = pvwatts_system_kwargs.pvwatts_ac(pdc)
inverter.pvwatts.assert_called_once_with(
pdc, **pvwatts_system_kwargs.inverter_parameters)
assert out < pdc


def test_PVSystem_pvwatts_multi(pvwatts_system_defaults,
pvwatts_system_kwargs):
expected = [pd.Series([0.0, 48.123524, 86.400000]),
pd.Series([0.0, 45.893550, 85.500000])]
systems = [pvwatts_system_defaults, pvwatts_system_kwargs]
for base_sys, exp in zip(systems, expected):
system = pvsystem.PVSystem(
arrays=[pvsystem.Array(), pvsystem.Array()],
inverter_parameters=base_sys.inverter_parameters,
)
pdcs = pd.Series([0., 25., 50.])
pacs = system.pvwatts_multi((pdcs, pdcs))
assert_series_equal(pacs, exp)
with pytest.raises(ValueError,
match="Length mismatch for per-array parameter"):
system.pvwatts_multi((pdcs,))
with pytest.raises(ValueError,
match="Length mismatch for per-array parameter"):
system.pvwatts_multi(pdcs)
with pytest.raises(ValueError,
match="Length mismatch for per-array parameter"):
system.pvwatts_multi((pdcs, pdcs, pdcs))


def test_PVSystem_num_arrays():
system_one = pvsystem.PVSystem()
system_two = pvsystem.PVSystem(arrays=[pvsystem.Array(), pvsystem.Array()])
Expand Down