Skip to content

Commit e4852c5

Browse files
authored
implement multi-MPPT inverter model (#1085)
* implement multi-MPPT inverter model * fix mistakes * fix dc_limit * fix mistakes * handle and test float, array, Series and tuple * float, array, Series and tuple * test for error, api, whatsnew * test coverage for float + night tare * fix error test, adjust internal if/then * stickler * remove float and require tuple/llst/array * remove array from docstring * correct condition * fix spacing * put array back, edits from review * remove test for type checking * improve coverage * formatting and typo
1 parent 1844e3d commit e4852c5

File tree

4 files changed

+142
-19
lines changed

4 files changed

+142
-19
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
:toctree: generated/
293293

294294
inverter.sandia
295+
inverter.sandia_multi
295296
inverter.adr
296297
inverter.pvwatts
297298

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Enhancements
1717
option to :py:class:`~pvlib.modelchain.ModelChain`. (:pull:`1042`) (:issue:`1073`)
1818
* Added :py:func:`pvlib.temperature.ross` for cell temperature modeling using
1919
only NOCT. (:pull:`1045`)
20+
* Added :py:func:`pvlib.inverter.sandia_multi` for modeling inverters with
21+
multiple MPPTs (:issue:`457`, :pull:`1085`)
2022
* Added optional ``attributes`` parameter to :py:func:`pvlib.iotools.get_psm3`
2123
and added the option of fetching 5- and 15-minute PSM3 data. (:pull:`1086`)
2224

pvlib/inverter.py

Lines changed: 99 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,44 @@
1212

1313
import numpy as np
1414
import pandas as pd
15-
1615
from numpy.polynomial.polynomial import polyfit # different than np.polyfit
1716

1817

18+
def _sandia_eff(v_dc, p_dc, inverter):
19+
r'''
20+
Calculate the inverter AC power without clipping
21+
'''
22+
Paco = inverter['Paco']
23+
Pdco = inverter['Pdco']
24+
Vdco = inverter['Vdco']
25+
C0 = inverter['C0']
26+
C1 = inverter['C1']
27+
C2 = inverter['C2']
28+
C3 = inverter['C3']
29+
Pso = inverter['Pso']
30+
31+
A = Pdco * (1 + C1 * (v_dc - Vdco))
32+
B = Pso * (1 + C2 * (v_dc - Vdco))
33+
C = C0 * (1 + C3 * (v_dc - Vdco))
34+
35+
return (Paco / (A - B) - C * (A - B)) * (p_dc - B) + C * (p_dc - B)**2
36+
37+
38+
def _sandia_limits(power_ac, p_dc, Paco, Pnt, Pso):
39+
r'''
40+
Applies minimum and maximum power limits to `power_ac`
41+
'''
42+
power_ac = np.minimum(Paco, power_ac)
43+
min_ac_power = -1.0 * abs(Pnt)
44+
below_limit = p_dc < Pso
45+
try:
46+
power_ac[below_limit] = min_ac_power
47+
except TypeError: # power_ac is a float
48+
if below_limit:
49+
power_ac = min_ac_power
50+
return power_ac
51+
52+
1953
def sandia(v_dc, p_dc, inverter):
2054
r'''
2155
Convert DC power and voltage to AC power using Sandia's
@@ -54,10 +88,10 @@ def sandia(v_dc, p_dc, inverter):
5488
Column Description
5589
====== ============================================================
5690
Paco AC power rating of the inverter. [W]
57-
Pdco DC power input to inverter, typically assumed to be equal
58-
to the PV array maximum power. [W]
91+
Pdco DC power input that results in Paco output at reference
92+
voltage Vdco. [W]
5993
Vdco DC voltage at which the AC power rating is achieved
60-
at the reference operating condition. [V]
94+
with Pdco power input. [V]
6195
Pso DC power required to start the inversion process, or
6296
self-consumption by inverter, strongly influences inverter
6397
efficiency at low power levels. [W]
@@ -91,29 +125,76 @@ def sandia(v_dc, p_dc, inverter):
91125
'''
92126

93127
Paco = inverter['Paco']
94-
Pdco = inverter['Pdco']
95-
Vdco = inverter['Vdco']
96-
Pso = inverter['Pso']
97-
C0 = inverter['C0']
98-
C1 = inverter['C1']
99-
C2 = inverter['C2']
100-
C3 = inverter['C3']
101128
Pnt = inverter['Pnt']
129+
Pso = inverter['Pso']
102130

103-
A = Pdco * (1 + C1 * (v_dc - Vdco))
104-
B = Pso * (1 + C2 * (v_dc - Vdco))
105-
C = C0 * (1 + C3 * (v_dc - Vdco))
106-
107-
power_ac = (Paco / (A - B) - C * (A - B)) * (p_dc - B) + C * (p_dc - B)**2
108-
power_ac = np.minimum(Paco, power_ac)
109-
power_ac = np.where(p_dc < Pso, -1.0 * abs(Pnt), power_ac)
131+
power_ac = _sandia_eff(v_dc, p_dc, inverter)
132+
power_ac = _sandia_limits(power_ac, p_dc, Paco, Pnt, Pso)
110133

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

114137
return power_ac
115138

116139

140+
def sandia_multi(v_dc, p_dc, inverter):
141+
r'''
142+
Convert DC power and voltage to AC power for an inverter with multiple
143+
MPPT inputs.
144+
145+
Uses Sandia's Grid-Connected PV Inverter model [1]_.
146+
147+
Parameters
148+
----------
149+
v_dc : tuple, list or array of numeric
150+
DC voltage on each MPPT input of the inverter. If type is array, must
151+
be 2d with axis 0 being the MPPT inputs. [V]
152+
153+
p_dc : tuple, list or array of numeric
154+
DC power on each MPPT input of the inverter. If type is array, must
155+
be 2d with axis 0 being the MPPT inputs. [W]
156+
157+
inverter : dict-like
158+
Defines parameters for the inverter model in [1]_.
159+
160+
Returns
161+
-------
162+
power_ac : numeric
163+
AC power output for the inverter. [W]
164+
165+
Raises
166+
------
167+
ValueError
168+
If v_dc and p_dc have different lengths.
169+
170+
Notes
171+
-----
172+
See :py:func:`pvlib.inverter.sandia` for definition of the parameters in
173+
`inverter`.
174+
175+
References
176+
----------
177+
.. [1] D. King, S. Gonzalez, G. Galbraith, W. Boyson, "Performance Model
178+
for Grid-Connected Photovoltaic Inverters", SAND2007-5036, Sandia
179+
National Laboratories.
180+
181+
See also
182+
--------
183+
pvlib.inverter.sandia
184+
'''
185+
186+
if len(p_dc) != len(v_dc):
187+
raise ValueError('p_dc and v_dc have different lengths')
188+
power_dc = sum(p_dc)
189+
power_ac = 0. * power_dc
190+
191+
for vdc, pdc in zip(v_dc, p_dc):
192+
power_ac += pdc / power_dc * _sandia_eff(vdc, power_dc, inverter)
193+
194+
return _sandia_limits(power_ac, power_dc, inverter['Paco'],
195+
inverter['Pnt'], inverter['Pso'])
196+
197+
117198
def adr(v_dc, p_dc, inverter, vtol=0.10):
118199
r'''
119200
Converts DC power and voltage to AC power using Anton Driesse's

pvlib/tests/test_inverter.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,14 @@ def test_sandia_float(cec_inverter_parameters):
6060
vdcs = 25.
6161
idcs = 5.5
6262
pdcs = idcs * vdcs
63-
6463
pacs = inverter.sandia(vdcs, pdcs, cec_inverter_parameters)
6564
assert_allclose(pacs, 132.004278, 5)
65+
# test at low power condition
66+
vdcs = 25.
67+
idcs = 0
68+
pdcs = idcs * vdcs
69+
pacs = inverter.sandia(vdcs, pdcs, cec_inverter_parameters)
70+
assert_allclose(pacs, -1. * cec_inverter_parameters['Pnt'], 5)
6671

6772

6873
def test_sandia_Pnt_micro():
@@ -95,6 +100,40 @@ def test_sandia_Pnt_micro():
95100
assert_series_equal(pacs, pd.Series([-0.043, 132.545914746, 240.0]))
96101

97102

103+
def test_sandia_multi(cec_inverter_parameters):
104+
vdcs = pd.Series(np.linspace(0, 50, 3))
105+
idcs = pd.Series(np.linspace(0, 11, 3)) / 2
106+
pdcs = idcs * vdcs
107+
pacs = inverter.sandia_multi((vdcs, vdcs), (pdcs, pdcs),
108+
cec_inverter_parameters)
109+
assert_series_equal(pacs, pd.Series([-0.020000, 132.004308, 250.000000]))
110+
# with lists instead of tuples
111+
pacs = inverter.sandia_multi([vdcs, vdcs], [pdcs, pdcs],
112+
cec_inverter_parameters)
113+
assert_series_equal(pacs, pd.Series([-0.020000, 132.004308, 250.000000]))
114+
# with arrays instead of tuples
115+
pacs = inverter.sandia_multi(np.array([vdcs, vdcs]),
116+
np.array([pdcs, pdcs]),
117+
cec_inverter_parameters)
118+
assert_series_equal(pacs, pd.Series([-0.020000, 132.004308, 250.000000]))
119+
120+
121+
def test_sandia_multi_length_error(cec_inverter_parameters):
122+
vdcs = pd.Series(np.linspace(0, 50, 3))
123+
idcs = pd.Series(np.linspace(0, 11, 3))
124+
pdcs = idcs * vdcs
125+
with pytest.raises(ValueError, match='p_dc and v_dc have different'):
126+
inverter.sandia_multi((vdcs,), (pdcs, pdcs), cec_inverter_parameters)
127+
128+
129+
def test_sandia_multi_array(cec_inverter_parameters):
130+
vdcs = np.linspace(0, 50, 3)
131+
idcs = np.linspace(0, 11, 3)
132+
pdcs = idcs * vdcs
133+
pacs = inverter.sandia_multi((vdcs,), (pdcs,), cec_inverter_parameters)
134+
assert_allclose(pacs, np.array([-0.020000, 132.004278, 250.000000]))
135+
136+
98137
def test_pvwatts_scalars():
99138
expected = 85.58556604752516
100139
out = inverter.pvwatts(90, 100, 0.95)

0 commit comments

Comments
 (0)