Skip to content

Commit 750b1fa

Browse files
authored
Implement PVSystem.get_ac (#1147)
* create PVSystem.get_ac * add docstring * add tests for get_ac * fix adrinverter name, revert change to test_PVSystem_sandia_multi_single_array * more fixes * improve test coverage, restructure if/else * remove unwrap decorator, fix adr called_once_with * docstrings, api, whatsnew, deprecations * test deprecations * undeprecate existing methods
1 parent 7ed18c4 commit 750b1fa

File tree

4 files changed

+195
-2
lines changed

4 files changed

+195
-2
lines changed

docs/sphinx/source/api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ Inverter models (DC to AC conversion)
292292
.. autosummary::
293293
:toctree: generated/
294294

295+
pvsystem.PVSystem.get_ac
295296
inverter.sandia
296297
inverter.sandia_multi
297298
inverter.adr

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ Enhancements
8787
by ``pvsystem.PVSystem.modules_per_strings`` and
8888
``pvsystem.PVSystem.strings_per_inverter``. Note that both attributes still
8989
default to 1. (:pull:`1138`)
90+
* :py:meth:`~pvlib.pvsystem.PVSystem.get_ac` is added to calculate AC power
91+
from DC power. Use parameter 'model' to specify which inverter model to use.
92+
(:pull:`1147`, :issue:`998`)
9093

9194
Bug fixes
9295
~~~~~~~~~

pvlib/pvsystem.py

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -852,7 +852,75 @@ def i_from_v(self, resistance_shunt, resistance_series, nNsVth, voltage,
852852
return i_from_v(resistance_shunt, resistance_series, nNsVth, voltage,
853853
saturation_current, photocurrent)
854854

855-
# inverter now specified by self.inverter_parameters
855+
def get_ac(self, model, p_dc, v_dc=None):
856+
r"""Calculates AC power from p_dc using the inverter model indicated
857+
by model and self.inverter_parameters.
858+
859+
Parameters
860+
----------
861+
model : str
862+
Must be one of 'sandia', 'adr', or 'pvwatts'.
863+
p_dc : numeric, or tuple, list or array of numeric
864+
DC power on each MPPT input of the inverter. Use tuple, list or
865+
array for inverters with multiple MPPT inputs. If type is array,
866+
p_dc must be 2d with axis 0 being the MPPT inputs. [W]
867+
v_dc : numeric, or tuple, list or array of numeric
868+
DC voltage on each MPPT input of the inverter. Required when
869+
model='sandia' or model='adr'. Use tuple, list or
870+
array for inverters with multiple MPPT inputs. If type is array,
871+
v_dc must be 2d with axis 0 being the MPPT inputs. [V]
872+
873+
Returns
874+
-------
875+
power_ac : numeric
876+
AC power output for the inverter. [W]
877+
878+
Raises
879+
------
880+
ValueError
881+
If model is not one of 'sandia', 'adr' or 'pvwatts'.
882+
ValueError
883+
If model='adr' and the PVSystem has more than one array.
884+
885+
See also
886+
--------
887+
pvlib.inverter.sandia
888+
pvlib.inverter.sandia_multi
889+
pvlib.inverter.adr
890+
pvlib.inverter.pvwatts
891+
pvlib.inverter.pvwatts_multi
892+
"""
893+
model = model.lower()
894+
multiple_arrays = self.num_arrays > 1
895+
if model == 'sandia':
896+
if multiple_arrays:
897+
p_dc = self._validate_per_array(p_dc)
898+
v_dc = self._validate_per_array(v_dc)
899+
inv_fun = inverter.sandia_multi
900+
else:
901+
inv_fun = inverter.sandia
902+
return inv_fun(v_dc, p_dc, self.inverter_parameters)
903+
elif model == 'pvwatts':
904+
kwargs = _build_kwargs(['eta_inv_nom', 'eta_inv_ref'],
905+
self.inverter_parameters)
906+
if multiple_arrays:
907+
p_dc = self._validate_per_array(p_dc)
908+
inv_fun = inverter.pvwatts_multi
909+
else:
910+
inv_fun = inverter.pvwatts
911+
return inv_fun(p_dc, self.inverter_parameters['pdc0'], **kwargs)
912+
elif model == 'adr':
913+
if multiple_arrays:
914+
raise ValueError(
915+
'The adr inverter function cannot be used for an inverter',
916+
' with multiple MPPT inputs')
917+
else:
918+
return inverter.adr(v_dc, p_dc, self.inverter_parameters)
919+
else:
920+
raise ValueError(
921+
model + ' is not a valid AC power model.',
922+
' model must be one of "sandia", "adr" or "pvwatts"')
923+
856924
def snlinverter(self, v_dc, p_dc):
857925
"""Uses :py:func:`pvlib.inverter.sandia` to calculate AC power based on
858926
``self.inverter_parameters`` and the input voltage and power.
@@ -969,7 +1037,6 @@ def pvwatts_multi(self, p_dc):
9691037
self.inverter_parameters)
9701038
return inverter.pvwatts_multi(p_dc, self.inverter_parameters['pdc0'],
9711039
**kwargs)
972-
9731040
@property
9741041
@_unwrap_single_value
9751042
def module_parameters(self):

pvlib/tests/test_pvsystem.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,6 +1374,21 @@ def test_PVSystem_multi_scale_voltage_current_power(mocker):
13741374
system.scale_voltage_current_power(None)
13751375

13761376

1377+
def test_PVSystem_get_ac_sandia(cec_inverter_parameters, mocker):
1378+
inv_fun = mocker.spy(inverter, 'sandia')
1379+
system = pvsystem.PVSystem(
1380+
inverter=cec_inverter_parameters['Name'],
1381+
inverter_parameters=cec_inverter_parameters,
1382+
)
1383+
vdcs = pd.Series(np.linspace(0, 50, 3))
1384+
idcs = pd.Series(np.linspace(0, 11, 3))
1385+
pdcs = idcs * vdcs
1386+
pacs = system.get_ac('sandia', vdcs, pdcs)
1387+
assert_series_equal(pacs, pd.Series([-0.020000, 132.004308, 250.000000]))
1388+
inv_fun.assert_called_once()
1389+
1390+
1391+
# remove after deprecation period for PVSystem.snlinverter
13771392
def test_PVSystem_snlinverter(cec_inverter_parameters):
13781393
system = pvsystem.PVSystem(
13791394
inverter=cec_inverter_parameters['Name'],
@@ -1387,6 +1402,31 @@ def test_PVSystem_snlinverter(cec_inverter_parameters):
13871402
assert_series_equal(pacs, pd.Series([-0.020000, 132.004308, 250.000000]))
13881403

13891404

1405+
def test_PVSystem_get_ac_sandia_multi(cec_inverter_parameters, mocker):
1406+
inv_fun = mocker.spy(inverter, 'sandia_multi')
1407+
system = pvsystem.PVSystem(
1408+
arrays=[pvsystem.Array(), pvsystem.Array()],
1409+
inverter=cec_inverter_parameters['Name'],
1410+
inverter_parameters=cec_inverter_parameters,
1411+
)
1412+
vdcs = pd.Series(np.linspace(0, 50, 3))
1413+
idcs = pd.Series(np.linspace(0, 11, 3)) / 2
1414+
pdcs = idcs * vdcs
1415+
pacs = system.get_ac('sandia', (vdcs, vdcs), (pdcs, pdcs))
1416+
assert_series_equal(pacs, pd.Series([-0.020000, 132.004308, 250.000000]))
1417+
inv_fun.assert_called_once()
1418+
with pytest.raises(ValueError,
1419+
match="Length mismatch for per-array parameter"):
1420+
system.get_ac('sandia', vdcs, (pdcs, pdcs))
1421+
with pytest.raises(ValueError,
1422+
match="Length mismatch for per-array parameter"):
1423+
system.get_ac('sandia', vdcs, (pdcs,))
1424+
with pytest.raises(ValueError,
1425+
match="Length mismatch for per-array parameter"):
1426+
system.get_ac('sandia', (vdcs, vdcs), (pdcs, pdcs, pdcs))
1427+
1428+
1429+
# remove after deprecation period for PVSystem.sandia_multi
13901430
def test_PVSystem_sandia_multi(cec_inverter_parameters):
13911431
system = pvsystem.PVSystem(
13921432
arrays=[pvsystem.Array(), pvsystem.Array()],
@@ -1409,6 +1449,7 @@ def test_PVSystem_sandia_multi(cec_inverter_parameters):
14091449
system.sandia_multi((vdcs, vdcs), (pdcs, pdcs, pdcs))
14101450

14111451

1452+
# remove after deprecation period for PVSystem.sandia_multi
14121453
def test_PVSystem_sandia_multi_single_array(cec_inverter_parameters):
14131454
system = pvsystem.PVSystem(
14141455
arrays=[pvsystem.Array()],
@@ -1431,6 +1472,84 @@ def test_PVSystem_sandia_multi_single_array(cec_inverter_parameters):
14311472
system.sandia_multi((vdcs,), (pdcs, pdcs))
14321473

14331474

1475+
def test_PVSystem_get_ac_pvwatts(pvwatts_system_defaults, mocker):
1476+
mocker.spy(inverter, 'pvwatts')
1477+
pdc = 50
1478+
out = pvwatts_system_defaults.get_ac('pvwatts', pdc)
1479+
inverter.pvwatts.assert_called_once_with(
1480+
pdc, **pvwatts_system_defaults.inverter_parameters)
1481+
assert out < pdc
1482+
1483+
1484+
def test_PVSystem_get_ac_pvwatts_kwargs(pvwatts_system_kwargs, mocker):
1485+
mocker.spy(inverter, 'pvwatts')
1486+
pdc = 50
1487+
out = pvwatts_system_kwargs.get_ac('pvwatts', pdc)
1488+
inverter.pvwatts.assert_called_once_with(
1489+
pdc, **pvwatts_system_kwargs.inverter_parameters)
1490+
assert out < pdc
1491+
1492+
1493+
def test_PVSystem_get_ac_pvwatts_multi(
1494+
pvwatts_system_defaults, pvwatts_system_kwargs, mocker):
1495+
mocker.spy(inverter, 'pvwatts_multi')
1496+
expected = [pd.Series([0.0, 48.123524, 86.400000]),
1497+
pd.Series([0.0, 45.893550, 85.500000])]
1498+
systems = [pvwatts_system_defaults, pvwatts_system_kwargs]
1499+
for base_sys, exp in zip(systems, expected):
1500+
system = pvsystem.PVSystem(
1501+
arrays=[pvsystem.Array(), pvsystem.Array()],
1502+
inverter_parameters=base_sys.inverter_parameters,
1503+
)
1504+
pdcs = pd.Series([0., 25., 50.])
1505+
pacs = system.get_ac('pvwatts', (pdcs, pdcs))
1506+
assert_series_equal(pacs, exp)
1507+
assert inverter.pvwatts_multi.call_count == 2
1508+
with pytest.raises(ValueError,
1509+
match="Length mismatch for per-array parameter"):
1510+
system.get_ac('pvwatts', (pdcs,))
1511+
with pytest.raises(ValueError,
1512+
match="Length mismatch for per-array parameter"):
1513+
system.get_ac('pvwatts', pdcs)
1514+
with pytest.raises(ValueError,
1515+
match="Length mismatch for per-array parameter"):
1516+
system.get_ac('pvwatts', (pdcs, pdcs, pdcs))
1517+
1518+
1519+
def test_PVSystem_get_ac_adr(adr_inverter_parameters, mocker):
1520+
mocker.spy(inverter, 'adr')
1521+
system = pvsystem.PVSystem(
1522+
inverter_parameters=adr_inverter_parameters,
1523+
)
1524+
vdcs = pd.Series([135, 154, 390, 420, 551])
1525+
pdcs = pd.Series([135, 1232, 1170, 420, 551])
1526+
pacs = system.get_ac('adr', pdcs, vdcs)
1527+
assert_series_equal(pacs, pd.Series([np.nan, 1161.5745, 1116.4459,
1528+
382.6679, np.nan]))
1529+
inverter.adr.assert_called_once_with(vdcs, pdcs,
1530+
system.inverter_parameters)
1531+
1532+
1533+
def test_PVSystem_get_ac_adr_multi(adr_inverter_parameters):
1534+
system = pvsystem.PVSystem(
1535+
arrays=[pvsystem.Array(), pvsystem.Array()],
1536+
inverter_parameters=adr_inverter_parameters,
1537+
)
1538+
pdcs = pd.Series([135, 1232, 1170, 420, 551])
1539+
with pytest.raises(ValueError,
1540+
match="The adr inverter function cannot be used"):
1541+
system.get_ac(model='adr', p_dc=pdcs)
1542+
1543+
1544+
def test_PVSystem_get_ac_invalid(cec_inverter_parameters):
1545+
system = pvsystem.PVSystem(
1546+
inverter_parameters=cec_inverter_parameters,
1547+
)
1548+
pdcs = pd.Series(np.linspace(0, 50, 3))
1549+
with pytest.raises(ValueError, match="is not a valid AC power model"):
1550+
system.get_ac(model='not_a_model', p_dc=pdcs)
1551+
1552+
14341553
def test_PVSystem_creation():
14351554
pv_system = pvsystem.PVSystem(module='blah', inverter='blarg')
14361555
# ensure that parameter attributes are dict-like. GH 294
@@ -1891,6 +2010,7 @@ def test_PVSystem_pvwatts_losses(pvwatts_system_defaults, mocker):
18912010
assert out < expected
18922011

18932012

2013+
# remove after deprecation period for PVSystem.pvwatts_ac
18942014
def test_PVSystem_pvwatts_ac(pvwatts_system_defaults, mocker):
18952015
mocker.spy(inverter, 'pvwatts')
18962016
pdc = 50
@@ -1900,6 +2020,7 @@ def test_PVSystem_pvwatts_ac(pvwatts_system_defaults, mocker):
19002020
assert out < pdc
19012021

19022022

2023+
# remove after deprecation period for PVSystem.pvwatts_ac
19032024
def test_PVSystem_pvwatts_ac_kwargs(pvwatts_system_kwargs, mocker):
19042025
mocker.spy(inverter, 'pvwatts')
19052026
pdc = 50
@@ -1909,6 +2030,7 @@ def test_PVSystem_pvwatts_ac_kwargs(pvwatts_system_kwargs, mocker):
19092030
assert out < pdc
19102031

19112032

2033+
# remove after deprecation period for PVSystem.pvwatts_ac
19122034
def test_PVSystem_pvwatts_multi(pvwatts_system_defaults,
19132035
pvwatts_system_kwargs):
19142036
expected = [pd.Series([0.0, 48.123524, 86.400000]),

0 commit comments

Comments
 (0)