Skip to content

Commit 3e07d9b

Browse files
committed
fit_sandia_field, rename fit_sandia to fit_sandia_lab
1 parent 9a0ad55 commit 3e07d9b

3 files changed

Lines changed: 186 additions & 3 deletions

File tree

docs/sphinx/source/reference/pv_modeling/inverter.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ Functions for fitting inverter models
1919
.. autosummary::
2020
:toctree: ../generated/
2121

22-
inverter.fit_sandia
22+
inverter.fit_sandia_lab
23+
inverter.fit_sandia_field

pvlib/inverter.py

Lines changed: 165 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
import numpy as np
1414
import pandas as pd
1515
from numpy.polynomial.polynomial import polyfit # different than np.polyfit
16+
from scipy.optimize import minimize
17+
import statsmodels.api as sm
18+
from pvlib._deprecation import deprecated
1619

1720

1821
def _sandia_eff(v_dc, p_dc, inverter):
@@ -445,9 +448,10 @@ def pvwatts_multi(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637):
445448
return pvwatts(sum(pdc), pdc0, eta_inv_nom, eta_inv_ref)
446449

447450

448-
def fit_sandia(ac_power, dc_power, dc_voltage, dc_voltage_level, p_ac_0, p_nt):
451+
def fit_sandia_lab(ac_power, dc_power, dc_voltage, dc_voltage_level, p_ac_0,
452+
p_nt):
449453
r'''
450-
Determine parameters for the Sandia inverter model.
454+
Determine parameters for the Sandia inverter model from laboratory data.
451455
452456
Parameters
453457
----------
@@ -551,3 +555,162 @@ def extract_c(x_d, add):
551555
# prepare dict and return
552556
return {'Paco': p_ac_0, 'Pdco': p_dc0, 'Vdco': v_nom, 'Pso': p_s0,
553557
'C0': c0, 'C1': c1, 'C2': c2, 'C3': c3, 'Pnt': p_nt}
558+
559+
560+
fit_sandia = deprecated(since="0.15.3", name="fit_sandia",
561+
alternative="fit_sandia_lab")(fit_sandia_lab)
562+
563+
564+
def _fit_sandia_field_a(pac, pdc, vdc, pac0, vdc0):
565+
r''' Estimate Pdco, Pso and C0 for the Sandia inverter model from
566+
time-series data. Uses robust regression on data greater than 5% of rated
567+
Pac and DC voltage near nominal voltage vdc0.
568+
569+
Parameters
570+
----------
571+
pac : numeric
572+
Measured AC power (W)
573+
pdc : numeric
574+
Measured DC input power (W)
575+
vdc
576+
Measured DC input voltage (V)
577+
pac0 : float
578+
Rated AC power (W)
579+
vdc0 : float
580+
Nominal DC input voltage (V)
581+
582+
Returns
583+
-------
584+
dict
585+
Parameters Pdco, Pso and C0 for the Sandia inverter model.
586+
'''
587+
# select data. Avoid very low power, clipping and DC voltage far from
588+
# nominal
589+
u = (pac > 0.05*pac0) & (pac < pac0) & (np.abs(vdc - vdc0)/vdc0 < 0.05)
590+
Y = pac[u]
591+
X = np.array([pdc[u]**2, pdc[u]]).T
592+
X = sm.add_constant(X)
593+
rlm_model = sm.RLM(Y, X)
594+
rlm_results = rlm_model.fit()
595+
rlmp = np.array(rlm_results.params)
596+
597+
p = {}
598+
p['C0'] = rlmp[1]
599+
p['Pso'] = (-rlmp[2] + np.sqrt(rlmp[2]**2 - 4*p['C0']*rlmp[0])) / \
600+
(2 * p['C0'])
601+
C = pac0 + rlmp[2]*p['Pso'] + p['C0']*p['Pso']**2
602+
p['Pdco'] = (-rlmp[2] + np.sqrt(rlmp[2]**2 + 4*p['C0']*C)) / (2. * p['C0'])
603+
return p
604+
605+
606+
def _f2(params, pac0, vdc0, pdc0, ps0, C0, vdc, pdc, pac):
607+
# objective function for _fit_sandia_field. Assumes Pdco, Pso and C0
608+
# are known. Returns the root sum of squared differences in AC power
609+
# Input params = [C1, C2, C3]
610+
p = {}
611+
p['Paco'] = pac0 # AC power
612+
p['Vdco'] = vdc0 # DC V
613+
p['Pdco'] = pdc0 # DC power
614+
p['Pso'] = ps0 # DC power, small
615+
p['C0'] = C0 # unitless, tiny
616+
p['C1'] = params[0] # 1/V, tiny
617+
p['C2'] = params[1] # 1/V, tiny
618+
p['C3'] = params[2] # 1/V tiny
619+
diff = (_sandia_eff(vdc, pdc, p) - pac)
620+
return np.sqrt(np.dot(diff, diff))
621+
622+
623+
def _fit_sandia_field_b(resid, pac, pdc, vdc, pac0, vdc0, pdc0, ps0, C0):
624+
r''' Estimate C1, C2, and C3 for the Sandia inverter model from time-series
625+
data. Estimates are conditional on parameters Pdco, Pso and C0.
626+
627+
Parameters
628+
----------
629+
pac : numeric
630+
Measured AC power (W)
631+
pdc : numeric
632+
Measured DC input power (W)
633+
vdc
634+
Measured DC input voltage (V)
635+
pac0 : float
636+
Rated AC power (W)
637+
vdc0 : float
638+
Nominal DC input voltage (V)
639+
pdc0 : float
640+
DC input power that produces rated AC power at nominal voltage (W)
641+
ps0 : float
642+
Start-up DC power (W)
643+
C0 : float
644+
Empirical coefficient
645+
646+
Returns
647+
-------
648+
dict
649+
Parameters for the Sandia inverter model including C1, C2 and C3.
650+
'''
651+
# select data. Avoid very low power and clipping
652+
u = (pac > 0.05*pac0) & (pac < pac0)
653+
654+
# initial guess
655+
x0 = np.array([0., 0., 0.])
656+
657+
args = (pac0, vdc0, pdc0, ps0, C0, vdc[u], pdc[u], pac[u])
658+
options = {}
659+
options['gtol'] = 1e-5
660+
result = minimize(_f2, x0, args=args,
661+
method='BFGS',
662+
options=options)
663+
664+
params = result.x
665+
p = {}
666+
p['Paco'] = pac0
667+
p['Vdco'] = vdc0
668+
p['Pdco'] = pdc0
669+
p['Pso'] = ps0
670+
p['C0'] = C0
671+
p['C1'] = params[0]
672+
p['C2'] = params[1]
673+
p['C3'] = params[2]
674+
675+
return p
676+
677+
678+
def fit_sandia_field(pac, pdc, vdc, pac0, vdc0):
679+
r''' Estimate parameters for the Sandia inverter model from time-series
680+
data.
681+
682+
Parameters
683+
----------
684+
pac : numeric
685+
Measured AC power (W)
686+
pdc : numeric
687+
Measured DC input power (W)
688+
vdc
689+
Measured DC input voltage (V)
690+
pac0 : float
691+
Rated AC power (W)
692+
vdc0 : float
693+
Nominal DC input voltage (V)
694+
695+
Returns
696+
-------
697+
dict
698+
Parameters for the Sandia inverter model.
699+
700+
References
701+
----------
702+
.. [1] C. W. Hansen, K. S. Anderson, M. Theristis, "Fitting the Sandia
703+
Inverter Model to Operational PV System Data", 54 IEEE Photovoltaic
704+
Specialist Conference, New Orleans, USA. 2026
705+
.. [2] D. King, S. Gonzalez, G. Galbraith, W. Boyson, "Performance Model
706+
for Grid-Connected Photovoltaic Inverters", Sandia National
707+
Laboratories, Albuquerque, N.M., USA, SAND2007-5036, Sept. 2007.
708+
:doi:`10.2172/920449`
709+
710+
'''
711+
# get Pdco, Pso and C0 first
712+
p = _fit_sandia_field_a(pac, pdc, vdc, pac0, vdc0)
713+
# add C1, C2, C3
714+
p = _fit_sandia_field_b(_f2, pac, pdc, vdc, pac0, vdc0,
715+
p['Pdco'], p['Pso'], p['C0'])
716+
return p

tests/test_inverter.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,22 @@ def test_fit_sandia(infilen, expected):
211211
dc_voltage_level=curves['dc_voltage_level'],
212212
p_ac_0=expected['Paco'], p_nt=expected['Pnt'])
213213
assert expected == pytest.approx(result, rel=1e-3)
214+
215+
216+
def test_fit_sandia_field():
217+
pdc = np.arange(start=100., stop=1300., step=100.)
218+
vdc = np.array([550., 600., 650, 550., 600., 650, 550., 600., 650,
219+
550., 600., 650])
220+
params = {'Paco': 1200, 'Pdco': 1300, 'Pso': 10, 'C0': 1e-6, 'C1': 1e-7,
221+
'C2': 1e-7, 'C3': 1e-7, 'Vdco': 600}
222+
# pac was computed with pvlib.inverter._sandia_eff
223+
pac = np.array([83.6134, 176.535, 269.476, 362.442, 455.422, 548.421,
224+
641.45, 734.489, 827.547, 920.638, 1013.74, 1106.85])
225+
p = inverter.fit_sandia_field(pac, pdc, vdc, params['Paco'],
226+
params['Vdco'])
227+
# Pdco, Pso, C0 should be within 1%
228+
for k in ['Pdco', 'Pso', 'C0']:
229+
assert np.isclose(p[k], params[k], rtol=1e-2)
230+
# looser tolerance for C1, C2, C3, so test AC power
231+
pred_ac = inverter._sandia_eff(vdc, pdc, p)
232+
assert_allclose(pred_ac, pac, rtol=2e-5)

0 commit comments

Comments
 (0)