Skip to content

implement multi-MPPT inverter model #1085

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 22 commits into from
Dec 2, 2020
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
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
117 changes: 99 additions & 18 deletions pvlib/inverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,44 @@

import numpy as np
import pandas as pd

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)
min_ac_power = -1.0 * abs(Pnt)
below_limit = p_dc < Pso
try:
power_ac[below_limit] = min_ac_power
except TypeError: # power_ac is a float
if below_limit:
power_ac = min_ac_power
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 +88,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 Vdco. [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 +125,76 @@ 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 : tuple, list or array of numeric
DC voltage on each MPPT input of the inverter. If type is array, must
be 2d with axis 0 being the MPPT inputs. [V]

p_dc : tuple, list or array of numeric
DC power on each MPPT input of the inverter. If type is array, must
be 2d with axis 0 being the MPPT inputs. [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 len(p_dc) != len(v_dc):
raise ValueError('p_dc and v_dc have different lengths')
power_dc = sum(p_dc)
power_ac = 0. * power_dc

for vdc, pdc in zip(v_dc, p_dc):
power_ac += pdc / power_dc * _sandia_eff(vdc, power_dc, inverter)

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
41 changes: 40 additions & 1 deletion pvlib/tests/test_inverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,14 @@ def test_sandia_float(cec_inverter_parameters):
vdcs = 25.
idcs = 5.5
pdcs = idcs * vdcs

pacs = inverter.sandia(vdcs, pdcs, cec_inverter_parameters)
assert_allclose(pacs, 132.004278, 5)
# test at low power condition
vdcs = 25.
idcs = 0
pdcs = idcs * vdcs
pacs = inverter.sandia(vdcs, pdcs, cec_inverter_parameters)
assert_allclose(pacs, -1. * cec_inverter_parameters['Pnt'], 5)


def test_sandia_Pnt_micro():
Expand Down Expand Up @@ -95,6 +100,40 @@ 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]))
# with lists instead of tuples
pacs = inverter.sandia_multi([vdcs, vdcs], [pdcs, pdcs],
cec_inverter_parameters)
assert_series_equal(pacs, pd.Series([-0.020000, 132.004308, 250.000000]))
# with arrays instead of tuples
pacs = inverter.sandia_multi(np.array([vdcs, vdcs]),
np.array([pdcs, pdcs]),
cec_inverter_parameters)
assert_series_equal(pacs, pd.Series([-0.020000, 132.004308, 250.000000]))


def test_sandia_multi_length_error(cec_inverter_parameters):
vdcs = pd.Series(np.linspace(0, 50, 3))
idcs = pd.Series(np.linspace(0, 11, 3))
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_pvwatts_scalars():
expected = 85.58556604752516
out = inverter.pvwatts(90, 100, 0.95)
Expand Down