Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
1 change: 1 addition & 0 deletions docs/sphinx/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ Inverter models (DC to AC conversion)
:toctree: generated/

inverter.sandia
inverter.sandia_multi
inverter.adr
inverter.pvwatts

Expand Down
2 changes: 2 additions & 0 deletions docs/sphinx/source/whatsnew/v0.8.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Enhancements
option to :py:class:`~pvlib.modelchain.ModelChain`. (:pull:`1042`) (:issue:`1073`)
* Added :py:func:`pvlib.temperature.ross` for cell temperature modeling using
only NOCT. (:pull:`1045`)
* Added :py:func:`pvlib.inverter.sandia_multi` for modeling inverters with
multiple MPPTs (:issue:`457`, :pull:`1085`)
* Added optional ``attributes`` parameter to :py:func:`pvlib.iotools.get_psm3`
and added the option of fetching 5- and 15-minute PSM3 data. (:pull:`1086`)

Expand Down
118 changes: 101 additions & 17 deletions pvlib/inverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,39 @@
from numpy.polynomial.polynomial import polyfit # different than np.polyfit


def _sandia_eff(v_dc, p_dc, inverter):
r'''
Calculate the inverter AC power without clipping
'''
Paco = inverter['Paco']
Pdco = inverter['Pdco']
Vdco = inverter['Vdco']
C0 = inverter['C0']
C1 = inverter['C1']
C2 = inverter['C2']
C3 = inverter['C3']
Pso = inverter['Pso']

A = Pdco * (1 + C1 * (v_dc - Vdco))
B = Pso * (1 + C2 * (v_dc - Vdco))
C = C0 * (1 + C3 * (v_dc - Vdco))

return (Paco / (A - B) - C * (A - B)) * (p_dc - B) + C * (p_dc - B)**2


def _sandia_limits(power_ac, p_dc, Paco, Pnt, Pso):
r'''
Applies minimum and maximum power limits to `power_ac`
'''
power_ac = np.minimum(Paco, power_ac)
try:
power_ac[p_dc < Pso] = -1.0 * abs(Pnt)
except TypeError: # float
if power_ac < Pso:
power_ac = -1.0 * abs(Pnt)
return power_ac


def sandia(v_dc, p_dc, inverter):
r'''
Convert DC power and voltage to AC power using Sandia's
Expand Down Expand Up @@ -54,10 +87,10 @@ def sandia(v_dc, p_dc, inverter):
Column Description
====== ============================================================
Paco AC power rating of the inverter. [W]
Pdco DC power input to inverter, typically assumed to be equal
to the PV array maximum power. [W]
Pdco DC power input that results in Paco output at reference
voltage Vdoc0. [W]
Vdco DC voltage at which the AC power rating is achieved
at the reference operating condition. [V]
with Pdco power input. [V]
Pso DC power required to start the inversion process, or
self-consumption by inverter, strongly influences inverter
efficiency at low power levels. [W]
Expand Down Expand Up @@ -91,29 +124,80 @@ def sandia(v_dc, p_dc, inverter):
'''

Paco = inverter['Paco']
Pdco = inverter['Pdco']
Vdco = inverter['Vdco']
Pso = inverter['Pso']
C0 = inverter['C0']
C1 = inverter['C1']
C2 = inverter['C2']
C3 = inverter['C3']
Pnt = inverter['Pnt']
Pso = inverter['Pso']

A = Pdco * (1 + C1 * (v_dc - Vdco))
B = Pso * (1 + C2 * (v_dc - Vdco))
C = C0 * (1 + C3 * (v_dc - Vdco))

power_ac = (Paco / (A - B) - C * (A - B)) * (p_dc - B) + C * (p_dc - B)**2
power_ac = np.minimum(Paco, power_ac)
power_ac = np.where(p_dc < Pso, -1.0 * abs(Pnt), power_ac)
power_ac = _sandia_eff(v_dc, p_dc, inverter)
power_ac = _sandia_limits(power_ac, p_dc, Paco, Pnt, Pso)

if isinstance(p_dc, pd.Series):
power_ac = pd.Series(power_ac, index=p_dc.index)

return power_ac


def sandia_multi(v_dc, p_dc, inverter):
r'''
Convert DC power and voltage to AC power for an inverter with multiple
MPPT inputs.

Uses Sandia's Grid-Connected PV Inverter model [1]_.

Parameters
----------
v_dc : numeric or tuple of numeric
DC voltage on each MPPT input of the inverter. [V]

p_dc : numeric or tuple of numeric
DC power on each MPPT input of the inverter. [W]

inverter : dict-like
Defines parameters for the inverter model in [1]_.

Returns
-------
power_ac : numeric
AC power output for the inverter. [W]

Raises
------
ValueError if v_dc and p_dc have different lengths.

Notes
-----
See :py:func:`pvlib.inverter.sandia` for definition of the parameters in
`inverter`.

References
----------
.. [1] D. King, S. Gonzalez, G. Galbraith, W. Boyson, "Performance Model
for Grid-Connected Photovoltaic Inverters", SAND2007-5036, Sandia
National Laboratories.

See also
--------
pvlib.inverter.sandia
'''

if isinstance(p_dc, tuple):
if isinstance(v_dc, tuple) and not len(p_dc) == len(v_dc):
raise ValueError('p_dc and v_dc have different lengths')
power_dc = sum(p_dc)
else:
power_dc = sum((p_dc,)) # handle Series, array or float

power_ac = 0. * power_dc

if isinstance(p_dc, tuple):
for vdc, pdc in zip(v_dc, p_dc):
power_ac += pdc / power_dc * _sandia_eff(vdc, power_dc, inverter)
else:
power_ac = _sandia_eff(v_dc, power_dc, inverter)
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand why we'd include this line in the sandia_multi function. Isn't it duplicating functionality in sandia? Maybe I don't understand the use cases well enough.

Copy link
Member Author

Choose a reason for hiding this comment

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

I was thinking that inverter.sandia_multi(600., 1500., *params) should work, but can't really say that's a required use.

Eventually I'd see inverter.sandia and inverter.sandia_multi merging, once the latter is better documented, with validation, and has broader acceptance.


return _sandia_limits(power_ac, power_dc, inverter['Paco'],
inverter['Pnt'], inverter['Pso'])


def adr(v_dc, p_dc, inverter, vtol=0.10):
r'''
Converts DC power and voltage to AC power using Anton Driesse's
Expand Down
39 changes: 39 additions & 0 deletions pvlib/tests/test_inverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,45 @@ def test_sandia_Pnt_micro():
assert_series_equal(pacs, pd.Series([-0.043, 132.545914746, 240.0]))


def test_sandia_multi(cec_inverter_parameters):
vdcs = pd.Series(np.linspace(0, 50, 3))
idcs = pd.Series(np.linspace(0, 11, 3)) / 2
pdcs = idcs * vdcs
pacs = inverter.sandia_multi((vdcs, vdcs), (pdcs, pdcs),
cec_inverter_parameters)
assert_series_equal(pacs, pd.Series([-0.020000, 132.004308, 250.000000]))


def test_sandia_multi_error(cec_inverter_parameters):
vdcs = pd.Series(np.linspace(0, 50, 3))
idcs = pd.Series(np.linspace(0, 11, 3)) / 2
pdcs = idcs * vdcs
with pytest.raises(ValueError, match='p_dc and v_dc have different'):
inverter.sandia_multi((vdcs,), (pdcs, pdcs), cec_inverter_parameters)


def test_sandia_multi_array(cec_inverter_parameters):
vdcs = np.linspace(0, 50, 3)
idcs = np.linspace(0, 11, 3)
pdcs = idcs * vdcs
pacs = inverter.sandia_multi(vdcs, pdcs, cec_inverter_parameters)
assert_allclose(pacs, np.array([-0.020000, 132.004278, 250.000000]))


def test_sandia_multi_float(cec_inverter_parameters):
vdc = 25.
idc = 2.75
pdc = idc * vdc
pac = inverter.sandia_multi(vdc, pdc, cec_inverter_parameters)
assert_allclose(pac, 65.296672)

vdc = 25.
idc = 0.
pdc = idc * vdc
pac = inverter.sandia_multi(vdc, pdc, cec_inverter_parameters)
assert_allclose(pac, -1.0 * cec_inverter_parameters['Pnt'])


def test_pvwatts_scalars():
expected = 85.58556604752516
out = inverter.pvwatts(90, 100, 0.95)
Expand Down