Skip to content

Commit 49da031

Browse files
authored
Add function to fit Sandia inverter model (#1011)
* WIP: fitting inverter model * correct calculated data, improve test * move code to pvlib modules, rename data file * fix test, stickler * sort out np.polyfit vs. np.polynomial.polynomial.polyfit * correct file name * api, whatsnew * docstring improvements * revisions from review * correct use of np.roots * use pytest.approx instead of converting dicts to np.array * correct kwarg for pytest.approx * back to solve_quad instead of np.roots, lint * change to Series inputs * correct test, Series to array_like
1 parent 9f56660 commit 49da031

File tree

6 files changed

+311
-16
lines changed

6 files changed

+311
-16
lines changed

docs/sphinx/source/api.rst

+17-10
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,15 @@ Low-level functions for solving the single diode equation.
265265
singlediode.bishop88_v_from_i
266266
singlediode.bishop88_mpp
267267

268+
Functions for fitting diode models
269+
270+
.. autosummary::
271+
:toctree: generated/
272+
273+
ivtools.fit_sde_sandia
274+
ivtools.fit_sdm_cec_sam
275+
ivtools.fit_sdm_desoto
276+
268277
Inverter models (DC to AC conversion)
269278
-------------------------------------
270279

@@ -275,6 +284,14 @@ Inverter models (DC to AC conversion)
275284
inverter.adr
276285
inverter.pvwatts
277286

287+
Functions for fitting inverter models
288+
289+
.. autosummary::
290+
:toctree: generated/
291+
292+
inverter.fit_sandia
293+
294+
278295
PV System Models
279296
----------------
280297

@@ -311,16 +328,6 @@ PVWatts model
311328
inverter.pvwatts
312329
pvsystem.pvwatts_losses
313330

314-
Functions for fitting diode models
315-
----------------------------------
316-
317-
.. autosummary::
318-
:toctree: generated/
319-
320-
ivtools.fit_sde_sandia
321-
ivtools.fit_sdm_cec_sam
322-
ivtools.fit_sdm_desoto
323-
324331
Other
325332
-----
326333

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

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ Enhancements
3838
* Add :py:func:`pvlib.iam.marion_diffuse` and
3939
:py:func:`pvlib.iam.marion_integrate` to calculate IAM values for
4040
diffuse irradiance. (:pull:`984`)
41+
* Add :py:func:`pvlib.inverter.fit_sandia` that fits the Sandia inverter model
42+
to a set of inverter efficiency curves. (:pull:`1011`)
4143

4244
Bug fixes
4345
~~~~~~~~~

pvlib/data/inverter_fit_snl_meas.csv

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
fraction_of_rated_power,dc_voltage_level,ac_power,dc_voltage,efficiency
2+
0.1,Vmin,32800,660.5,0.95814
3+
0.2,Vmin,73000,660.9,0.9755
4+
0.3,Vmin,107500,660.73,0.97787
5+
0.5,Vmin,168100,660.1,0.97998
6+
0.75,Vmin,235467,660.27,0.97785
7+
1,Vmin,318067,660.03,0.97258
8+
0.1,Vnom,32800,740.1,0.95441
9+
0.2,Vnom,72900,740.2,0.96985
10+
0.3,Vnom,107600,740.13,0.97611
11+
0.5,Vnom,167500,740.57,0.97554
12+
0.75,Vnom,234967,741.87,0.97429
13+
1,Vnom,317267,737.7,0.97261
14+
0.1,Vmax,32800,959.07,0.94165
15+
0.2,Vmax,71600,959.43,0.95979
16+
0.3,Vmax,107300,959.1,0.96551
17+
0.5,Vmax,166700,959.5,0.96787
18+
0.75,Vmax,234767,958.8,0.96612
19+
1,Vmax,317467,957,0.96358
20+
0.1,Vmin,32800,660.77,0.95721
21+
0.2,Vmin,73000,660.77,0.97247
22+
0.3,Vmin,107500,660.47,0.97668
23+
0.5,Vmin,168100,660.23,0.98018
24+
0.75,Vmin,235333.3333,660.3,0.97716
25+
1,Vmin,317466.6667,659.8,0.97184
26+
0.1,Vnom,32800,740.27,0.95534
27+
0.2,Vnom,72900,740.27,0.97071
28+
0.3,Vnom,107600,740.2,0.97523
29+
0.5,Vnom,167500,740.8,0.97592
30+
0.75,Vnom,234966.6667,741.67,0.97429
31+
1,Vnom,317300,737.97,0.97252
32+
0.1,Vmax,32800,959.23,0.93718
33+
0.2,Vmax,71600,959.4,0.96107
34+
0.3,Vmax,107300,959.27,0.96638
35+
0.5,Vmax,166700,959.57,0.96825
36+
0.75,Vmax,234733.3333,959.17,0.96731
37+
1,Vmax,317466.6667,957.07,0.96241
38+
0.1,Vmin,32800,660.57,0.95814
39+
0.2,Vmin,73000,660.67,0.97333
40+
0.3,Vmin,107500,660.5,0.97609
41+
0.5,Vmin,168100,660.1,0.97884
42+
0.75,Vmin,235066.6667,660.3,0.97781
43+
1,Vmin,316900,659.27,0.97209
44+
0.1,Vnom,32800,740.17,0.95441
45+
0.2,Vnom,72900,740.27,0.97028
46+
0.3,Vnom,107600,740.23,0.97464
47+
0.5,Vnom,167500,740.3,0.97573
48+
0.75,Vnom,235133.3333,742.13,0.97417
49+
1,Vnom,317300,737.9,0.97252
50+
0.1,Vmax,32800,959.2,0.93626
51+
0.2,Vmax,71600,959.43,0.95979
52+
0.3,Vmax,107300,959.2,0.96493
53+
0.5,Vmax,166700,959.5,0.96806
54+
0.75,Vmax,234833.3333,958.97,0.96573
55+
1,Vmax,317400,956.87,0.96279
56+
0.1,Vmin,32800,660.63,0.95627
57+
0.2,Vmin,73000,660.9,0.97377
58+
0.3,Vmin,107500,661.07,0.97846
59+
0.5,Vmin,168100,660.13,0.97827
60+
0.75,Vmin,235200,660.43,0.97701
61+
1,Vmin,316933.3333,660.07,0.97308
62+
0.1,Vnom,32800,740.27,0.95441
63+
0.2,Vnom,72900,740.37,0.96985
64+
0.3,Vnom,107600,740.27,0.97464
65+
0.5,Vnom,167500,740.53,0.97592
66+
0.75,Vnom,234800,742.13,0.97374
67+
1,Vnom,317300,737.73,0.97202
68+
0.1,Vmax,32800,959.2,0.93271
69+
0.2,Vmax,71600,959.27,0.95594
70+
0.3,Vmax,107300,959.2,0.96783
71+
0.5,Vmax,166700,959.47,0.96806
72+
0.75,Vmax,234700,958.67,0.96505
73+
1,Vmax,317433.3333,956.8,0.96299
74+
0.1,Vmin,32800,660.67,0.95534
75+
0.2,Vmin,73000,660.8,0.9755
76+
0.3,Vmin,107500,661.23,0.97905
77+
0.5,Vmin,168100,660.33,0.97941
78+
0.75,Vmin,236566.6667,660.43,0.97741
79+
1,Vmin,317866.6667,659.53,0.97366
80+
0.1,Vnom,32800,740.13,0.95627
81+
0.2,Vnom,72900,740.37,0.97071
82+
0.3,Vnom,107600,740.4,0.97523
83+
0.5,Vnom,167500,740.57,0.97649
84+
0.75,Vnom,234733.3333,741.83,0.97413
85+
1,Vnom,317333.3333,737.77,0.97222
86+
0.1,Vmax,32800,959.03,0.9336
87+
0.2,Vmax,71600,959.33,0.96108
88+
0.3,Vmax,107300,959.2,0.96464
89+
0.5,Vmax,166700,959.57,0.96975
90+
0.75,Vmax,234700,958.83,0.96584
91+
1,Vmax,317400,956.7,0.96338
92+
0.1,Vmin,32800,660.43,0.95349
93+
0.2,Vmin,73000,660.83,0.97247
94+
0.3,Vmin,107500,660.47,0.97668
95+
0.5,Vmin,168100,660.27,0.97941
96+
0.75,Vmin,236167,660.57,0.97657
97+
1,Vmin,317833,660.47,0.97177
98+
0.1,Vnom,32800,740.2,0.95534
99+
0.2,Vnom,72900,740.3,0.96985
100+
0.3,Vnom,107600,740.33,0.97434
101+
0.5,Vnom,167500,740.53,0.9763
102+
0.75,Vnom,234833,741.93,0.97468
103+
1,Vnom,317333,737.73,0.97242
104+
0.1,Vmax,32800,959.03,0.93626
105+
0.2,Vmax,71600,959.37,0.95936
106+
0.3,Vmax,107300,959.23,0.96464
107+
0.5,Vmax,166700,959.5,0.96731
108+
0.75,Vmax,235267,958.67,0.96592
109+
1,Vmax,317400,957.07,0.96269
110+
0.1,Vmin,32800,660.73,0.95627
111+
0.2,Vmin,73000,660.57,0.97204
112+
0.3,Vmin,107500,660.97,0.97787
113+
0.5,Vmin,168100,660,0.97865
114+
0.75,Vmin,236200,659.77,0.97778
115+
1,Vmin,317200,659.2,0.97221
116+
0.1,Vnom,32800,740.2,0.95257
117+
0.2,Vnom,72900,740.33,0.97071
118+
0.3,Vnom,107600,740.43,0.97464
119+
0.5,Vnom,167500,740.63,0.97592
120+
0.75,Vnom,235100,741.87,0.97458
121+
1,Vnom,317333.3333,737.83,0.97232
122+
0.1,Vmax,32800,959,0.93182
123+
0.2,Vmax,71600,959.4,0.9585
124+
0.3,Vmax,107300,959.07,0.96783
125+
0.5,Vmax,166700,959.7,0.96806
126+
0.75,Vmax,235466.6667,958.77,0.96569
127+
1,Vmax,317400,956.6,0.96308

pvlib/data/inverter_fit_snl_sim.csv

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
fraction_of_rated_power,efficiency,dc_voltage_level,dc_voltage,dc_power,ac_power,efficiency
2+
0.1,0.892146066,Vmin,220,112.0892685,100,0.892146067
3+
0.1,0.876414009,Vnom,240,114.1013254,100,0.876414009
4+
0.1,0.861227164,Vmax,260,116.1133835,100,0.861227164
5+
0.2,0.925255801,Vmin,220,216.15644,200,0.925255801
6+
0.2,0.916673906,Vnom,240,218.1800951,200,0.916673906
7+
0.2,0.908249736,Vmax,260,220.2037524,200,0.908249736
8+
0.3,0.936909957,Vmin,220,320.2015283,300,0.936909957
9+
0.3,0.93099374,Vnom,240,322.2363236,300,0.93099374
10+
0.3,0.925151763,Vmax,260,324.2711217,300,0.925151763
11+
0.5,0.946565413,Vmin,220,528.2255121,500,0.946565413
12+
0.5,0.94289593,Vnom,240,530.2812159,500,0.94289593
13+
0.5,0.939254781,Vmax,260,532.336923,500,0.939254781
14+
0.75,0.951617818,Vmin,220,788.1315225,750,0.951617818
15+
0.75,0.949113828,Vnom,240,790.2108027,750,0.949113828
16+
0.75,0.946622992,Vmax,260,792.2900736,750,0.946622992
17+
1,0.954289529,Vmin,220,1047.900002,1000,0.95428953
18+
1,0.952380952,Vnom,240,1050,1000,0.952380952
19+
1,0.950479992,Vmax,260,1052.1,1000,0.950479992

pvlib/inverter.py

+120-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
# -*- coding: utf-8 -*-
22
"""
3-
This module contains functions for inverter modeling, primarily conversion of
4-
DC to AC power.
3+
This module contains functions for inverter modeling and for fitting inverter
4+
models to data.
5+
6+
Inverter models calculate AC power output from DC input. Model parameters
7+
should be passed as a single dict.
8+
9+
Functions for estimating parameters for inverter models should follow the
10+
naming pattern 'fit_<model name>', e.g., fit_sandia.
11+
512
"""
613

714
import numpy as np
815
import pandas as pd
916

17+
from numpy.polynomial.polynomial import polyfit # different than np.polyfit
18+
1019

1120
def sandia(v_dc, p_dc, inverter):
1221
r'''
@@ -176,7 +185,7 @@ def adr(v_dc, p_dc, inverter, vtol=0.10):
176185
177186
References
178187
----------
179-
.. [1] Driesse, A. "Beyond the Curves: Modeling the Electrical Efficiency
188+
.. [1] A. Driesse, "Beyond the Curves: Modeling the Electrical Efficiency
180189
of Photovoltaic Inverters", 33rd IEEE Photovoltaic Specialist
181190
Conference (PVSC), June 2008
182191
@@ -285,8 +294,7 @@ def pvwatts(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637):
285294
References
286295
----------
287296
.. [1] A. P. Dobos, "PVWatts Version 5 Manual,"
288-
http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf
289-
(2014).
297+
http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf (2014).
290298
"""
291299

292300
pac0 = eta_inv_nom * pdc0
@@ -306,3 +314,110 @@ def pvwatts(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637):
306314
power_ac = np.maximum(0, power_ac) # GH 541
307315

308316
return power_ac
317+
318+
319+
def fit_sandia(ac_power, dc_power, dc_voltage, dc_voltage_level, p_ac_0, p_nt):
320+
r'''
321+
Determine parameters for the Sandia inverter model.
322+
323+
Parameters
324+
----------
325+
ac_power : array_like
326+
AC power output at each data point [W].
327+
dc_power : array_like
328+
DC power input at each data point [W].
329+
dc_voltage : array_like
330+
DC input voltage at each data point [V].
331+
dc_voltage_level : array_like
332+
DC input voltage level at each data point. Values must be 'Vmin',
333+
'Vnom' or 'Vmax'.
334+
p_ac_0 : float
335+
Rated AC power of the inverter [W].
336+
p_nt : float
337+
Night tare, i.e., power consumed while inverter is not delivering
338+
AC power. [W]
339+
340+
Returns
341+
-------
342+
dict
343+
A set of parameters for the Sandia inverter model [1]_. See
344+
:py:func:`pvlib.inverter.sandia` for a description of keys and values.
345+
346+
See Also
347+
--------
348+
pvlib.inverter.sandia
349+
350+
Notes
351+
-----
352+
The fitting procedure to estimate parameters is described at [2]_.
353+
A data point is a pair of values (dc_power, ac_power). Typically, inverter
354+
performance is measured or described at three DC input voltage levels,
355+
denoted 'Vmin', 'Vnom' and 'Vmax' and at each level, inverter efficiency
356+
is determined at various output power levels. For example,
357+
the CEC inverter test protocol [3]_ specifies measurement of input DC
358+
power that delivers AC output power of 0.1, 0.2, 0.3, 0.5, 0.75 and 1.0 of
359+
the inverter's AC power rating.
360+
361+
References
362+
----------
363+
.. [1] D. King, S. Gonzalez, G. Galbraith, W. Boyson, "Performance Model
364+
for Grid-Connected Photovoltaic Inverters", SAND2007-5036, Sandia
365+
National Laboratories.
366+
.. [2] Sandia Inverter Model page, PV Performance Modeling Collaborative
367+
https://pvpmc.sandia.gov/modeling-steps/dc-to-ac-conversion/sandia-inverter-model/
368+
.. [3] W. Bower, et al., "Performance Test Protocol for Evaluating
369+
Inverters Used in Grid-Connected Photovoltaic Systems", available at
370+
https://www.energy.ca.gov/sites/default/files/2020-06/2004-11-22_Sandia_Test_Protocol_ada.pdf
371+
''' # noqa: E501
372+
373+
voltage_levels = ['Vmin', 'Vnom', 'Vmax']
374+
375+
# average dc input voltage at each voltage level
376+
v_d = np.array(
377+
[dc_voltage[dc_voltage_level == 'Vmin'].mean(),
378+
dc_voltage[dc_voltage_level == 'Vnom'].mean(),
379+
dc_voltage[dc_voltage_level == 'Vmax'].mean()])
380+
v_nom = v_d[1] # model parameter
381+
# independent variable for regressions, x_d
382+
x_d = v_d - v_nom
383+
384+
# empty dataframe to contain intermediate variables
385+
coeffs = pd.DataFrame(index=voltage_levels,
386+
columns=['a', 'b', 'c', 'p_dc', 'p_s0'], data=np.nan)
387+
388+
def solve_quad(a, b, c):
389+
return (-b + (b**2 - 4 * a * c)**.5) / (2 * a)
390+
391+
# [2] STEP 3E, fit a line to (DC voltage, model_coefficient)
392+
def extract_c(x_d, add):
393+
beta0, beta1 = polyfit(x_d, add, 1)
394+
c = beta1 / beta0
395+
return beta0, beta1, c
396+
397+
for d in voltage_levels:
398+
x = dc_power[dc_voltage_level == d]
399+
y = ac_power[dc_voltage_level == d]
400+
# [2] STEP 3B
401+
# fit a quadratic to (DC power, AC power)
402+
c, b, a = polyfit(x, y, 2)
403+
404+
# [2] STEP 3D, solve for p_dc and p_s0
405+
p_dc = solve_quad(a, b, (c - p_ac_0))
406+
p_s0 = solve_quad(a, b, c)
407+
408+
# Add values to dataframe at index d
409+
coeffs['a'][d] = a
410+
coeffs['p_dc'][d] = p_dc
411+
coeffs['p_s0'][d] = p_s0
412+
413+
b_dc0, b_dc1, c1 = extract_c(x_d, coeffs['p_dc'])
414+
b_s0, b_s1, c2 = extract_c(x_d, coeffs['p_s0'])
415+
b_c0, b_c1, c3 = extract_c(x_d, coeffs['a'])
416+
417+
p_dc0 = b_dc0
418+
p_s0 = b_s0
419+
c0 = b_c0
420+
421+
# prepare dict and return
422+
return {'Paco': p_ac_0, 'Pdco': p_dc0, 'Vdco': v_nom, 'Pso': p_s0,
423+
'C0': c0, 'C1': c1, 'C2': c2, 'C3': c3, 'Pnt': p_nt}

pvlib/tests/test_inverter.py

+26-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
from conftest import assert_series_equal
77
from numpy.testing import assert_allclose
88

9+
from conftest import needs_numpy_1_10, DATA_DIR
10+
import pytest
11+
912
from pvlib import inverter
10-
from conftest import needs_numpy_1_10
1113

1214

1315
def test_adr(adr_inverter_parameters):
@@ -131,3 +133,26 @@ def test_pvwatts_series():
131133
expected = pd.Series(np.array([np.nan, 0., 47.608436, 95.]))
132134
out = inverter.pvwatts(pdc, pdc0, 0.95)
133135
assert_series_equal(expected, out)
136+
137+
138+
INVERTER_TEST_MEAS = DATA_DIR / 'inverter_fit_snl_meas.csv'
139+
INVERTER_TEST_SIM = DATA_DIR / 'inverter_fit_snl_sim.csv'
140+
141+
142+
@pytest.mark.parametrize('infilen, expected', [
143+
(INVERTER_TEST_MEAS, {'Paco': 333000., 'Pdco': 343251., 'Vdco': 740.,
144+
'Pso': 1427.746, 'C0': -5.768e-08, 'C1': 3.596e-05,
145+
'C2': 1.038e-03, 'C3': 2.978e-05, 'Pnt': 1.}),
146+
(INVERTER_TEST_SIM, {'Paco': 1000., 'Pdco': 1050., 'Vdco': 240.,
147+
'Pso': 10., 'C0': 1e-6, 'C1': 1e-4, 'C2': 1e-2,
148+
'C3': 1e-3, 'Pnt': 1.}),
149+
])
150+
def test_fit_sandia(infilen, expected):
151+
curves = pd.read_csv(infilen)
152+
dc_power = curves['ac_power'] / curves['efficiency']
153+
result = inverter.fit_sandia(ac_power=curves['ac_power'],
154+
dc_power=dc_power,
155+
dc_voltage=curves['dc_voltage'],
156+
dc_voltage_level=curves['dc_voltage_level'],
157+
p_ac_0=expected['Paco'], p_nt=expected['Pnt'])
158+
assert expected == pytest.approx(result, rel=1e-3)

0 commit comments

Comments
 (0)