From 7de7a771c1f734115e4c12b890f64f30c7a97f22 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 22 Jun 2020 08:18:30 -0600 Subject: [PATCH 01/15] WIP: fitting inverter model --- pvlib/data/inverter_fit_snl_datasheet.csv | 19 ++ pvlib/data/inverter_fit_snl_meas.csv | 127 ++++++++++ pvlib/inverter_fits.py | 285 ++++++++++++++++++++++ 3 files changed, 431 insertions(+) create mode 100644 pvlib/data/inverter_fit_snl_datasheet.csv create mode 100644 pvlib/data/inverter_fit_snl_meas.csv create mode 100644 pvlib/inverter_fits.py diff --git a/pvlib/data/inverter_fit_snl_datasheet.csv b/pvlib/data/inverter_fit_snl_datasheet.csv new file mode 100644 index 0000000000..67b71282be --- /dev/null +++ b/pvlib/data/inverter_fit_snl_datasheet.csv @@ -0,0 +1,19 @@ +fraction_of_rated_power,efficiency,dc_voltage_level,pdc,pac +0.1,0.8921460663497615,Vmin,112.08926853105183,100.0 +0.1,0.8764140086437499,Vnom,114.10132541668285,100.0 +0.1,0.861227163890917,Vmax,116.11338354472292,100.0 +0.2,0.9252558010464728,Vmin,216.1564399529278,200.0 +0.2,0.91667390617836,Vnom,218.18009507198235,200.0 +0.2,0.9082497361753336,Vmax,220.20375237564716,200.0 +0.3,0.9369099565845942,Vmin,320.20152832361623,300.0 +0.3,0.930993739729357,Vnom,322.23632361610834,300.0 +0.3,0.9251517631120498,Vmax,324.2711217355865,300.0 +0.5,0.9465654129565353,Vmin,528.225512105162,500.0 +0.5,0.9428959295527041,Vnom,530.2812159102147,500.0 +0.5,0.9392547809878062,Vmax,532.3369229743545,500.0 +0.75,0.9516178182868241,Vmin,788.1315225372807,750.0 +0.75,0.9491138280981478,Vnom,790.2108027472997,750.0 +0.75,0.946622992013311,Vmax,792.2900735855503,750.0 +1.0,0.9523809523809523,Vmin,1050.0,1000.0 +1.0,0.9523809523809523,Vnom,1050.0,1000.0 +1.0,0.9504799924028748,Vmax,1052.0999999925673,1000.0 diff --git a/pvlib/data/inverter_fit_snl_meas.csv b/pvlib/data/inverter_fit_snl_meas.csv new file mode 100644 index 0000000000..f12dd19d29 --- /dev/null +++ b/pvlib/data/inverter_fit_snl_meas.csv @@ -0,0 +1,127 @@ +fraction_of_rated_power,dc_voltage_level,ac_power,dc_voltage,efficiency +0.1,Vmin,32800,660.5,0.95814 +0.2,Vmin,73000,660.9,0.9755 +0.3,Vmin,107500,660.73,0.97787 +0.5,Vmin,168100,660.1,0.97998 +0.75,Vmin,235467,660.27,0.97785 +1,Vmin,318067,660.03,0.97258 +0.1,Vnom,32800,740.1,0.95441 +0.2,Vnom,72900,740.2,0.96985 +0.3,Vnom,107600,740.13,0.97611 +0.5,Vnom,167500,740.57,0.97554 +0.75,Vnom,234967,741.87,0.97429 +1,Vnom,317267,737.7,0.97261 +0.1,Vmax,32800,959.07,0.94165 +0.2,Vmax,71600,959.43,0.95979 +0.3,Vmax,107300,959.1,0.96551 +0.5,Vmax,166700,959.5,0.96787 +0.75,Vmax,234767,958.8,0.96612 +1,Vmax,317467,957,0.96358 +0.1,Vmin,32800,660.77,0.95721 +0.2,Vmin,73000,660.77,0.97247 +0.3,Vmin,107500,660.47,0.97668 +0.5,Vmin,168100,660.23,0.98018 +0.75,Vmin,235333.3333,660.3,0.97716 +1,Vmin,317466.6667,659.8,0.97184 +0.1,Vnom,32800,740.27,0.95534 +0.2,Vnom,72900,740.27,0.97071 +0.3,Vnom,107600,740.2,0.97523 +0.5,Vnom,167500,740.8,0.97592 +0.75,Vnom,234966.6667,741.67,0.97429 +1,Vnom,317300,737.97,0.97252 +0.1,Vmax,32800,959.23,0.93718 +0.2,Vmax,71600,959.4,0.96107 +0.3,Vmax,107300,959.27,0.96638 +0.5,Vmax,166700,959.57,0.96825 +0.75,Vmax,234733.3333,959.17,0.96731 +1,Vmax,317466.6667,957.07,0.96241 +0.1,Vmin,32800,660.57,0.95814 +0.2,Vmin,73000,660.67,0.97333 +0.3,Vmin,107500,660.5,0.97609 +0.5,Vmin,168100,660.1,0.97884 +0.75,Vmin,235066.6667,660.3,0.97781 +1,Vmin,316900,659.27,0.97209 +0.1,Vnom,32800,740.17,0.95441 +0.2,Vnom,72900,740.27,0.97028 +0.3,Vnom,107600,740.23,0.97464 +0.5,Vnom,167500,740.3,0.97573 +0.75,Vnom,235133.3333,742.13,0.97417 +1,Vnom,317300,737.9,0.97252 +0.1,Vmax,32800,959.2,0.93626 +0.2,Vmax,71600,959.43,0.95979 +0.3,Vmax,107300,959.2,0.96493 +0.5,Vmax,166700,959.5,0.96806 +0.75,Vmax,234833.3333,958.97,0.96573 +1,Vmax,317400,956.87,0.96279 +0.1,Vmin,32800,660.63,0.95627 +0.2,Vmin,73000,660.9,0.97377 +0.3,Vmin,107500,661.07,0.97846 +0.5,Vmin,168100,660.13,0.97827 +0.75,Vmin,235200,660.43,0.97701 +1,Vmin,316933.3333,660.07,0.97308 +0.1,Vnom,32800,740.27,0.95441 +0.2,Vnom,72900,740.37,0.96985 +0.3,Vnom,107600,740.27,0.97464 +0.5,Vnom,167500,740.53,0.97592 +0.75,Vnom,234800,742.13,0.97374 +1,Vnom,317300,737.73,0.97202 +0.1,Vmax,32800,959.2,0.93271 +0.2,Vmax,71600,959.27,0.95594 +0.3,Vmax,107300,959.2,0.96783 +0.5,Vmax,166700,959.47,0.96806 +0.75,Vmax,234700,958.67,0.96505 +1,Vmax,317433.3333,956.8,0.96299 +0.1,Vmin,32800,660.67,0.95534 +0.2,Vmin,73000,660.8,0.9755 +0.3,Vmin,107500,661.23,0.97905 +0.5,Vmin,168100,660.33,0.97941 +0.75,Vmin,236566.6667,660.43,0.97741 +1,Vmin,317866.6667,659.53,0.97366 +0.1,Vnom,32800,740.13,0.95627 +0.2,Vnom,72900,740.37,0.97071 +0.3,Vnom,107600,740.4,0.97523 +0.5,Vnom,167500,740.57,0.97649 +0.75,Vnom,234733.3333,741.83,0.97413 +1,Vnom,317333.3333,737.77,0.97222 +0.1,Vmax,32800,959.03,0.9336 +0.2,Vmax,71600,959.33,0.96108 +0.3,Vmax,107300,959.2,0.96464 +0.5,Vmax,166700,959.57,0.96975 +0.75,Vmax,234700,958.83,0.96584 +1,Vmax,317400,956.7,0.96338 +0.1,Vmin,32800,660.43,0.95349 +0.2,Vmin,73000,660.83,0.97247 +0.3,Vmin,107500,660.47,0.97668 +0.5,Vmin,168100,660.27,0.97941 +0.75,Vmin,236167,660.57,0.97657 +1,Vmin,317833,660.47,0.97177 +0.1,Vnom,32800,740.2,0.95534 +0.2,Vnom,72900,740.3,0.96985 +0.3,Vnom,107600,740.33,0.97434 +0.5,Vnom,167500,740.53,0.9763 +0.75,Vnom,234833,741.93,0.97468 +1,Vnom,317333,737.73,0.97242 +0.1,Vmax,32800,959.03,0.93626 +0.2,Vmax,71600,959.37,0.95936 +0.3,Vmax,107300,959.23,0.96464 +0.5,Vmax,166700,959.5,0.96731 +0.75,Vmax,235267,958.67,0.96592 +1,Vmax,317400,957.07,0.96269 +0.1,Vmin,32800,660.73,0.95627 +0.2,Vmin,73000,660.57,0.97204 +0.3,Vmin,107500,660.97,0.97787 +0.5,Vmin,168100,660,0.97865 +0.75,Vmin,236200,659.77,0.97778 +1,Vmin,317200,659.2,0.97221 +0.1,Vnom,32800,740.2,0.95257 +0.2,Vnom,72900,740.33,0.97071 +0.3,Vnom,107600,740.43,0.97464 +0.5,Vnom,167500,740.63,0.97592 +0.75,Vnom,235100,741.87,0.97458 +1,Vnom,317333.3333,737.83,0.97232 +0.1,Vmax,32800,959,0.93182 +0.2,Vmax,71600,959.4,0.9585 +0.3,Vmax,107300,959.07,0.96783 +0.5,Vmax,166700,959.7,0.96806 +0.75,Vmax,235466.6667,958.77,0.96569 +1,Vmax,317400,956.6,0.96308 diff --git a/pvlib/inverter_fits.py b/pvlib/inverter_fits.py new file mode 100644 index 0000000000..aad3eba919 --- /dev/null +++ b/pvlib/inverter_fits.py @@ -0,0 +1,285 @@ +import numpy as np +import pandas as pd + +from numpy.polynomial.polynomial import polyfit +from numpy.testing import assert_allclose + +import os + + +def _fit_ps0(p_ac, p_dc, p_ac0, p_dc0): + ''' Determine the ps0 parameter as the intercept at p_ac=0 of a line fit + to p_ac vs. (p_ac0 * p_dc - p_dc0 * p_ac) / (p_ac0 - p-ac) + This function assumes that p_dc is paired with p_ac, and that + p_ac < p_ac0. + ''' + y = np.array((p_ac0 * p_dc - p_dc0 * p_ac) / (p_ac0 - p_ac), dtype=float) + x = np.array([np.ones_like(y), p_ac], dtype=float).T + beta, _, _, _ = np.linalg.lstsq(x, y) + return beta[0] + + +def _calc_c0(p_ac, p_dc, p_ac0, p_dc0, p_s0): + x = p_dc - p_s0 + c0 = (p_ac - x / (p_dc0 - p_s0) * p_ac0) / (x**2. - x * (p_dc0 - p_s0)) + return np.nanmean(c0) + + +def fit_sandia_datasheet(curves, p_ac_0, dc_voltage, p_nt): + r''' + Determine parameters for the Sandia inverter model from a datasheet's + efficiency curves. + + Parameters + ---------- + curves : DataFrame + Columns must be ``'fraction_of_rated_power'``, ``'efficiency'``, + ``'dc_voltage_level'``. See notes for definition and units for each + column. + p_ac_0 : numeric + Rated AC power of the inverter [W]. + dc_voltage : Dict + Input DC voltage levels. Keys must be 'Vmin', 'Vnom', and 'Vmax'. [V] + p_nt : numeric + Night tare, i.e., power consumed while inverter is not delivering + AC power. [W] + + Returns + ------- + Dict with parameters for the Sandia inverter model. + + See :py:func:`snl_inverter` for a description of entries in the returned + Dict. + + See Also + -------- + snlinverter + + Notes + ----- + An inverter efficiency curve comprises a series of pairs + ('fraction_of_rated_power', 'efficiency'), e.g. (0.1, 0.5), (0.2, 0.7), + etc. at a specified DC voltage level.The DataFrame `curves` should contain + multiple efficiency curves for each DC voltage level; at least five curves + at each level is recommended. Columns in `curves` must be the following: + + ================ ======================================== + Column name Description + ================ ======================================== + 'fraction_of_rated_power' Fraction of rated AC power `p_ac_0`. Values must + include 1.0. The CEC inverter test protocol + specifies values of 0.1, 0.2, 0.3, 0.5, 0.75 + and 1.0. [unitless] + 'efficiency' Ratio of measured AC output power to measured + DC input power. [unitless] + 'dc_voltage_level' Values of 'Vmin', 'Vnom', and 'Vmax'. + + The output AC power is calculated as 'fraction_of_rated_power' * 'p_ac_0'. + The input DC power is calculated as (output AC power) / 'efficiency'. + + References + ---------- + .. [1] SAND2007-5036, "Performance Model for Grid-Connected + Photovoltaic Inverters by D. King, S. Gonzalez, G. Galbraith, W. + Boyson + .. [2] Sandia Inverter Model page, PV Performance Modeling Collaborative + https://pvpmc.sandia.gov/modeling-steps/dc-to-ac-conversion/sandia-inverter-model/ # noqa: E501 + ''' + + voltage_levels = ['Vmin', 'Vnom', 'Vmax'] + + curves['ac_power'] = curves['fraction_of_rated_power'] * p_ac_0 + curves['dc_power'] = curves['ac_power'] / curves['efficiency'] + + #Empty dataframe to contain intermediate variables + coeffs = pd.DataFrame(index=voltage_levels, + columns=['p_dc0', 'p_s0', 'c0', 'a', 'b', 'c'], + data=np.nan) + + for vl in voltage_levels: + temp = curves[curves['dc_voltage_level'] == vl] + # determine p_dc0 + base_eff = temp['efficiency'][temp['fraction_of_rated_power']==1.0] + p_dc_0 = p_ac_0 / base_eff + p_dc_0 = float(p_dc_0) + + # determine p_s0 + p_s0 = _fit_ps0(temp['ac_power'][temp['ac_power'] < p_ac_0], + temp['dc_power'][temp['ac_power'] < p_ac_0], + p_ac_0, p_dc_0) + + # calculate c0 + c0 = _calc_c0(temp['ac_power'], temp['dc_power'], p_ac_0, p_dc_0, p_s0) + + # fit a quadratic to (pac, pdc) at each voltage level, to get c3 + #Get a,b,c values from polyfit + x = curves['dc_power'][curves['dc_voltage_level']==vl] + y = curves['ac_power'][curves['dc_voltage_level']==vl] + c, b, a = polyfit(x, y, 2) + coeffs['p_dc0'][vl] = p_dc_0 + coeffs['p_s0'][vl] = p_s0 + coeffs['c0'][vl] = c0 + coeffs['a'][vl] = a + coeffs['b'][vl] = b + coeffs['c'][vl] = c + + p_dc0 = coeffs['p_dc0']['Vnom'] + p_s0 = coeffs['p_s0']['Vnom'] + c0 = coeffs['c0']['Vnom'] + c1 = (coeffs['p_dc0']['Vmax'] - coeffs['p_dc0']['Vmin']) \ + / (dc_voltage['Vmax'] - dc_voltage['Vmin']) / p_dc0 + c2 = (coeffs['p_s0']['Vmax'] - coeffs['p_s0']['Vmin']) \ + / (dc_voltage['Vmax'] - dc_voltage['Vmin']) / p_s0 + c3 = (coeffs['a']['Vmax'] - coeffs['a']['Vmin']) \ + / ((dc_voltage['Vmax'] - dc_voltage['Vmin']) * c0) + + # prepare dict and return + return {'Paco': p_ac_0, 'Pdco': p_dc0, 'Vdco': dc_voltage['Vnom'], + 'Pso': p_s0, 'C0': c0, 'C1': c1, 'C2': c2, 'C3': c3, 'Pnt': p_nt} + + +def fit_sandia_meas(curves, p_ac_0, p_nt): + r''' + Determine parameters for the Sandia inverter model from measured efficiency + curves. + + Parameters + ---------- + curves : DataFrame + Columns must be ``'fraction_of_rated_power'``, ``'efficiency'``, + ``'dc_voltage_level'``, ``'ac_power'``, ``'dc_voltage'``. See notes + for definition and units for each column. + p_ac_0 : numeric + Rated AC power of the inverter [W]. + p_nt : numeric + Night tare, i.e., power consumed while inverter is not delivering + AC power. [W] + + Returns + ------- + Dict with parameters for the Sandia inverter model. + + See :py:func:`snl_inverter` for a description of entries in the returned + Dict. + + See Also + -------- + snlinverter + + Notes + ----- + An inverter efficiency curve comprises a series of pairs + ('fraction_of_rated_power', 'efficiency'), e.g. (0.1, 0.5), (0.2, 0.7), + etc. at a specified DC voltage level.The DataFrame `curves` should contain + multiple efficiency curves for each DC voltage level; at least five curves + at each level is recommended. Columns in `curves` must be the following: + + ================ ======================================== + Column name Description + ================ ======================================== + 'fraction_of_rated_power' Fraction of rated AC power `p_ac_0`. The + CEC inverter test protocol specifies values + of 0.1, 0.2, 0.3, 0.5, 0.75 and 1.0. [unitless] + 'efficiency' Ratio of measured AC output power to measured + DC input power. [unitless] + 'dc_voltage_level' Must be one of 'Vmin', 'Vnom', or 'Vmax'. + 'ac_power' Measurd output AC power. [W] + 'dc_voltage' Measured DC input voltage. [V] + + References + ---------- + .. [1] SAND2007-5036, "Performance Model for Grid-Connected + Photovoltaic Inverters by D. King, S. Gonzalez, G. Galbraith, W. + Boyson + .. [2] Sandia Inverter Model page, PV Performance Modeling Collaborative + https://pvpmc.sandia.gov/modeling-steps/dc-to-ac-conversion/sandia-inverter-model/ # noqa: E501 + ''' + + voltage_levels = ['Vmin', 'Vnom', 'Vmax'] + + #Declaring x_d + v_nom = curves['dc_voltage'][curves['dc_voltage_level']=='Vnom'] + v_nom = v_nom.mean() + v_d = np.array( + [curves['dc_voltage'][curves['dc_voltage_level']=='Vmin'].mean(), + v_nom, + curves['dc_voltage'][curves['dc_voltage_level']=='Vmax'].mean()]) + x_d = v_d - v_nom + + curves['dc_power'] = curves['ac_power'] / curves['efficiency'] + + #Empty dataframe to contain intermediate variables + coeffs = pd.DataFrame(index=voltage_levels, + columns=['a', 'b', 'c', 'p_dc', 'p_s0'], data=np.nan) + + def solve_quad(a, b, c): + return (-b + (b**2 - 4 * a * c)**.5) / (2 * a) + + #STEP 3E, use np.polyfit to get betas + def extract_c(x_d, add): + test = np.polyfit(x_d, add, 1) + beta1, beta0 = test + c = beta1 / beta0 + return beta0, beta1, c + + for d in voltage_levels: + x = curves['dc_power'][curves['dc_voltage_level']==d] + y = curves['ac_power'][curves['dc_voltage_level']==d] + #STEP 3B + #Get a,b,c values from polyfit + c, b, a = polyfit(x, y, 2) + + #STEP 3D, solve for p_dc and p_s0 + + p_dc = solve_quad(a, b, (c - p_ac_0)) + + p_s0 = solve_quad(a, b, c) + + + #Add values to dataframe at index d + coeffs['a'][d] = a + coeffs['b'][d] = b + coeffs['c'][d] = c + coeffs['p_dc'][d] = p_dc + coeffs['p_s0'][d] = p_s0 + + b_dc0, b_dc1, c1 = extract_c(x_d, coeffs['p_dc']) + b_s0, b_s1, c2 = extract_c(x_d, coeffs['p_s0']) + b_c0, b_c1, c3 = extract_c(x_d, coeffs['a']) + + + p_dc0 = b_dc0 + p_s0 = b_s0 + c0 = b_c0 + + # prepare dict and return + return {'Paco': p_ac_0, 'Pdco': p_dc0, 'Vdco': v_nom, 'Pso': p_s0, + 'C0': c0, 'C1': c1, 'C2': c2, 'C3': c3, 'Pnt': p_nt} + + +DATA_DIR = "C:\\python\\pvlib-remote\\pvlib-python\\pvlib\\data" +def test_fit_sandia_meas(): + inverter_curves = os.path.join(DATA_DIR, 'inverter_fit_snl_meas.csv') + curves = pd.read_csv(inverter_curves) + expected = np.array([333000, 343251, 740, 1427.746, -5.768e-08, 3.596e-05, + 1.038e-03, 2.978e-05, 1.]) + keys = ['Paco', 'Pdco', 'Vdco', 'Pso', 'C0', 'C1', 'C2', 'C3', 'Pnt'] + result_dict = fit_sandia_meas(curves, 333000, 1.) + result = np.array([result_dict[k] for k in keys]) + assert_allclose(expected, result, rtol=1e-3) + + +def test_fit_sandia_datasheet(): + inverter_curves = os.path.join(DATA_DIR, 'inverter_fit_snl_datasheet.csv') + curves = pd.read_csv(inverter_curves) + dc_voltage_levels = {'Vmin': 220., 'Vnom': 240., 'Vmax': 260.} + expected_dict = {'Paco': 1000., 'Pdco': 1050., 'Vdco': 240., 'Pso': 10., + 'C0': 1e-6, 'C1': 1e-4, 'C2': 1e-2, 'C3': 1e-3, 'Pnt': 1} + keys = ['Paco', 'Pdco', 'Vdco', 'Pso', 'C0', 'C1', 'C2', 'C3', 'Pnt'] + expected = np.array([expected_dict[k] for k in keys]) + result_dict = fit_sandia_datasheet(curves, 1000., dc_voltage_levels, 1.) + result = np.array([result_dict[k] for k in keys]) + assert_allclose(expected, result, rtol=1e-3) + return expected, result + +exp, res = test_fit_sandia_datasheet() From 31f04071bdca40dab9ac97173b69a6d2d0347753 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Thu, 25 Jun 2020 13:02:11 -0600 Subject: [PATCH 02/15] correct calculated data, improve test --- pvlib/data/inverter_fit_snl_datasheet.csv | 38 +++++++++++------------ pvlib/inverter_fits.py | 19 +++++++++++- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/pvlib/data/inverter_fit_snl_datasheet.csv b/pvlib/data/inverter_fit_snl_datasheet.csv index 67b71282be..18b5aa54be 100644 --- a/pvlib/data/inverter_fit_snl_datasheet.csv +++ b/pvlib/data/inverter_fit_snl_datasheet.csv @@ -1,19 +1,19 @@ -fraction_of_rated_power,efficiency,dc_voltage_level,pdc,pac -0.1,0.8921460663497615,Vmin,112.08926853105183,100.0 -0.1,0.8764140086437499,Vnom,114.10132541668285,100.0 -0.1,0.861227163890917,Vmax,116.11338354472292,100.0 -0.2,0.9252558010464728,Vmin,216.1564399529278,200.0 -0.2,0.91667390617836,Vnom,218.18009507198235,200.0 -0.2,0.9082497361753336,Vmax,220.20375237564716,200.0 -0.3,0.9369099565845942,Vmin,320.20152832361623,300.0 -0.3,0.930993739729357,Vnom,322.23632361610834,300.0 -0.3,0.9251517631120498,Vmax,324.2711217355865,300.0 -0.5,0.9465654129565353,Vmin,528.225512105162,500.0 -0.5,0.9428959295527041,Vnom,530.2812159102147,500.0 -0.5,0.9392547809878062,Vmax,532.3369229743545,500.0 -0.75,0.9516178182868241,Vmin,788.1315225372807,750.0 -0.75,0.9491138280981478,Vnom,790.2108027472997,750.0 -0.75,0.946622992013311,Vmax,792.2900735855503,750.0 -1.0,0.9523809523809523,Vmin,1050.0,1000.0 -1.0,0.9523809523809523,Vnom,1050.0,1000.0 -1.0,0.9504799924028748,Vmax,1052.0999999925673,1000.0 +,fraction_of_rated_power,efficiency,dc_voltage_level,pdc,pac +0,0.1,0.8921460663497615,Vmin,112.08926853105183,100.0 +1,0.1,0.8764140086437499,Vnom,114.10132541668285,100.0 +2,0.1,0.861227163890917,Vmax,116.11338354472292,100.0 +3,0.2,0.9252558010464728,Vmin,216.1564399529278,200.0 +4,0.2,0.91667390617836,Vnom,218.18009507198235,200.0 +5,0.2,0.9082497361753336,Vmax,220.20375237564716,200.0 +6,0.3,0.9369099565845942,Vmin,320.20152832361623,300.0 +7,0.3,0.930993739729357,Vnom,322.23632361610834,300.0 +8,0.3,0.9251517631120498,Vmax,324.2711217355865,300.0 +9,0.5,0.9465654129565353,Vmin,528.225512105162,500.0 +10,0.5,0.9428959295527041,Vnom,530.2812159102147,500.0 +11,0.5,0.9392547809878062,Vmax,532.3369229743545,500.0 +12,0.75,0.9516178182868241,Vmin,788.1315225372807,750.0 +13,0.75,0.9491138280981478,Vnom,790.2108027472997,750.0 +14,0.75,0.946622992013311,Vmax,792.2900735855503,750.0 +15,1.0,0.9542895293424474,Vmin,1047.9000023075275,1000.0 +16,1.0,0.9523809523809523,Vnom,1050.0,1000.0 +17,1.0,0.9504799924028748,Vmax,1052.0999999925673,1000.0 diff --git a/pvlib/inverter_fits.py b/pvlib/inverter_fits.py index aad3eba919..494a58b5f4 100644 --- a/pvlib/inverter_fits.py +++ b/pvlib/inverter_fits.py @@ -6,6 +6,8 @@ import os +from pvlib.pvsystem import snlinverter + def _fit_ps0(p_ac, p_dc, p_ac0, p_dc0): ''' Determine the ps0 parameter as the intercept at p_ac=0 of a line fit @@ -277,9 +279,24 @@ def test_fit_sandia_datasheet(): 'C0': 1e-6, 'C1': 1e-4, 'C2': 1e-2, 'C3': 1e-3, 'Pnt': 1} keys = ['Paco', 'Pdco', 'Vdco', 'Pso', 'C0', 'C1', 'C2', 'C3', 'Pnt'] expected = np.array([expected_dict[k] for k in keys]) + # recover known values within 0.5% result_dict = fit_sandia_datasheet(curves, 1000., dc_voltage_levels, 1.) result = np.array([result_dict[k] for k in keys]) - assert_allclose(expected, result, rtol=1e-3) + assert_allclose(expected, result, rtol=5e-3) + # calculate efficiency from recovered parameters + calc_effic = {k: np.nan for k in dc_voltage_levels.keys()} + for vlev in dc_voltage_levels.keys(): + pdc = curves[curves['dc_voltage_level']==vlev]['pdc'] + calc_effic[vlev] = snlinverter(dc_voltage_levels[vlev], pdc, + result_dict) / pdc + assert_allclose(calc_effic[vlev], + curves[curves['dc_voltage_level']==vlev]['efficiency'], + rtol=1e-5) return expected, result exp, res = test_fit_sandia_datasheet() + + +# calculate efficiency from recovered parameters + + From 0391eb0d8aa85c2dade36a21bd871087fd9e897e Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 24 Jul 2020 08:57:37 -0600 Subject: [PATCH 03/15] move code to pvlib modules, rename data file --- pvlib/data/inverter_fit_snl_datasheet.csv | 19 -- pvlib/data/inverter_fit_snl_sim.csv | 19 ++ pvlib/inverter.py | 128 ++++++++- pvlib/inverter_fits.py | 302 ---------------------- pvlib/tests/test_inverter.py | 26 +- 5 files changed, 170 insertions(+), 324 deletions(-) delete mode 100644 pvlib/data/inverter_fit_snl_datasheet.csv create mode 100644 pvlib/data/inverter_fit_snl_sim.csv delete mode 100644 pvlib/inverter_fits.py diff --git a/pvlib/data/inverter_fit_snl_datasheet.csv b/pvlib/data/inverter_fit_snl_datasheet.csv deleted file mode 100644 index 18b5aa54be..0000000000 --- a/pvlib/data/inverter_fit_snl_datasheet.csv +++ /dev/null @@ -1,19 +0,0 @@ -,fraction_of_rated_power,efficiency,dc_voltage_level,pdc,pac -0,0.1,0.8921460663497615,Vmin,112.08926853105183,100.0 -1,0.1,0.8764140086437499,Vnom,114.10132541668285,100.0 -2,0.1,0.861227163890917,Vmax,116.11338354472292,100.0 -3,0.2,0.9252558010464728,Vmin,216.1564399529278,200.0 -4,0.2,0.91667390617836,Vnom,218.18009507198235,200.0 -5,0.2,0.9082497361753336,Vmax,220.20375237564716,200.0 -6,0.3,0.9369099565845942,Vmin,320.20152832361623,300.0 -7,0.3,0.930993739729357,Vnom,322.23632361610834,300.0 -8,0.3,0.9251517631120498,Vmax,324.2711217355865,300.0 -9,0.5,0.9465654129565353,Vmin,528.225512105162,500.0 -10,0.5,0.9428959295527041,Vnom,530.2812159102147,500.0 -11,0.5,0.9392547809878062,Vmax,532.3369229743545,500.0 -12,0.75,0.9516178182868241,Vmin,788.1315225372807,750.0 -13,0.75,0.9491138280981478,Vnom,790.2108027472997,750.0 -14,0.75,0.946622992013311,Vmax,792.2900735855503,750.0 -15,1.0,0.9542895293424474,Vmin,1047.9000023075275,1000.0 -16,1.0,0.9523809523809523,Vnom,1050.0,1000.0 -17,1.0,0.9504799924028748,Vmax,1052.0999999925673,1000.0 diff --git a/pvlib/data/inverter_fit_snl_sim.csv b/pvlib/data/inverter_fit_snl_sim.csv new file mode 100644 index 0000000000..15b1062079 --- /dev/null +++ b/pvlib/data/inverter_fit_snl_sim.csv @@ -0,0 +1,19 @@ +fraction_of_rated_power,efficiency,dc_voltage_level,dc_voltage,dc_power,ac_power,efficiency +0.1,0.892146066,Vmin,220,112.0892685,100,0.892146067 +0.1,0.876414009,Vnom,240,114.1013254,100,0.876414009 +0.1,0.861227164,Vmax,260,116.1133835,100,0.861227164 +0.2,0.925255801,Vmin,220,216.15644,200,0.925255801 +0.2,0.916673906,Vnom,240,218.1800951,200,0.916673906 +0.2,0.908249736,Vmax,260,220.2037524,200,0.908249736 +0.3,0.936909957,Vmin,220,320.2015283,300,0.936909957 +0.3,0.93099374,Vnom,240,322.2363236,300,0.93099374 +0.3,0.925151763,Vmax,260,324.2711217,300,0.925151763 +0.5,0.946565413,Vmin,220,528.2255121,500,0.946565413 +0.5,0.94289593,Vnom,240,530.2812159,500,0.94289593 +0.5,0.939254781,Vmax,260,532.336923,500,0.939254781 +0.75,0.951617818,Vmin,220,788.1315225,750,0.951617818 +0.75,0.949113828,Vnom,240,790.2108027,750,0.949113828 +0.75,0.946622992,Vmax,260,792.2900736,750,0.946622992 +1,0.954289529,Vmin,220,1047.900002,1000,0.95428953 +1,0.952380952,Vnom,240,1050,1000,0.952380952 +1,0.950479992,Vmax,260,1052.1,1000,0.950479992 diff --git a/pvlib/inverter.py b/pvlib/inverter.py index 514be06e88..caf1046aed 100644 --- a/pvlib/inverter.py +++ b/pvlib/inverter.py @@ -1,7 +1,14 @@ # -*- coding: utf-8 -*- """ -This module contains functions for inverter modeling, primarily conversion of -DC to AC power. +This module contains functions for inverter modeling and for fitting inverter +models to data. + +Inverter models calculate AC power output from DC input. Model parameters +should be passed as a single dict. + +Functions for estimating parameters for inverter models should follow the +naming pattern 'fit_', e.g., fit_sandia. + """ import numpy as np @@ -306,3 +313,120 @@ def pvwatts(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637): power_ac = np.maximum(0, power_ac) # GH 541 return power_ac + + +def fit_sandia(curves, p_ac_0, p_nt): + r''' + Determine parameters for the Sandia inverter model from efficiency + curves. + + Parameters + ---------- + curves : DataFrame + Columns must be ``'fraction_of_rated_power'``, ``'dc_voltage_level'``, + ``'dc_voltage'``, ``'ac_power'``, ``'efficiency'``. See notes for the + definition and unit for each column. + p_ac_0 : numeric + Rated AC power of the inverter [W]. + p_nt : numeric + Night tare, i.e., power consumed while inverter is not delivering + AC power. [W] + + Returns + ------- + dict with parameters for the Sandia inverter model. See + :py:func:`snl_inverter` for a description of entries in the returned dict. + + See Also + -------- + snlinverter + + Notes + ----- + An inverter efficiency curve comprises a series of pairs + ('fraction_of_rated_power', 'efficiency'), e.g. (0.1, 0.5), (0.2, 0.7), + etc. at a specified DC voltage level.The DataFrame `curves` should contain + multiple efficiency curves for each DC voltage level; at least five curves + at each level is recommended. Columns in `curves` must be the following: + + ================ ======================================== + Column name Description + ================ ======================================== + 'fraction_of_rated_power' Fraction of rated AC power `p_ac_0`. The + CEC inverter test protocol specifies values + of 0.1, 0.2, 0.3, 0.5, 0.75 and 1.0. [unitless] + 'dc_voltage_level' Must be 'Vmin', 'Vnom', or 'Vmax'. Curves must + be provided for all three voltage levels. At + least one curve must be provided for each + combination of fraction_of_rated_power and + dc_voltage_level. + 'dc_voltage' Measured DC input voltage. [V] + 'ac_power' Measurd output AC power. [W] + 'efficiency' Ratio of measured AC output power to measured + DC input power. [unitless] + + References + ---------- + .. [1] SAND2007-5036, "Performance Model for Grid-Connected + Photovoltaic Inverters by D. King, S. Gonzalez, G. Galbraith, W. + Boyson + .. [2] Sandia Inverter Model page, PV Performance Modeling Collaborative + https://pvpmc.sandia.gov/modeling-steps/dc-to-ac-conversion/sandia-inverter-model/ # noqa: E501 + ''' + + voltage_levels = ['Vmin', 'Vnom', 'Vmax'] + + # average dc input voltage at each voltage level + v_d = np.array( + [curves['dc_voltage'][curves['dc_voltage_level']=='Vmin'].mean(), + curves['dc_voltage'][curves['dc_voltage_level']=='Vnom'].mean(), + curves['dc_voltage'][curves['dc_voltage_level']=='Vmax'].mean()]) + v_nom = v_d[1] # model parameter + # independent variable for regressions, x_d + x_d = v_d - v_nom + + curves['dc_power'] = curves['ac_power'] / curves['efficiency'] + + # empty dataframe to contain intermediate variables + coeffs = pd.DataFrame(index=voltage_levels, + columns=['a', 'b', 'c', 'p_dc', 'p_s0'], data=np.nan) + + def solve_quad(a, b, c): + return (-b + (b**2 - 4 * a * c)**.5) / (2 * a) + + # [2] STEP 3E, use np.polyfit to get betas + def extract_c(x_d, add): + test = np.polyfit(x_d, add, 1) + beta1, beta0 = test + c = beta1 / beta0 + return beta0, beta1, c + + for d in voltage_levels: + x = curves['dc_power'][curves['dc_voltage_level']==d] + y = curves['ac_power'][curves['dc_voltage_level']==d] + # [2] STEP 3B + # Get a,b,c values from polyfit + c, b, a = np.polyfit(x, y, 2) + + # [2] STEP 3D, solve for p_dc and p_s0 + p_dc = solve_quad(a, b, (c - p_ac_0)) + p_s0 = solve_quad(a, b, c) + + # Add values to dataframe at index d + coeffs['a'][d] = a + coeffs['b'][d] = b + coeffs['c'][d] = c + coeffs['p_dc'][d] = p_dc + coeffs['p_s0'][d] = p_s0 + + b_dc0, b_dc1, c1 = extract_c(x_d, coeffs['p_dc']) + b_s0, b_s1, c2 = extract_c(x_d, coeffs['p_s0']) + b_c0, b_c1, c3 = extract_c(x_d, coeffs['a']) + + p_dc0 = b_dc0 + p_s0 = b_s0 + c0 = b_c0 + + # prepare dict and return + return {'Paco': p_ac_0, 'Pdco': p_dc0, 'Vdco': v_nom, 'Pso': p_s0, + 'C0': c0, 'C1': c1, 'C2': c2, 'C3': c3, 'Pnt': p_nt} diff --git a/pvlib/inverter_fits.py b/pvlib/inverter_fits.py deleted file mode 100644 index 494a58b5f4..0000000000 --- a/pvlib/inverter_fits.py +++ /dev/null @@ -1,302 +0,0 @@ -import numpy as np -import pandas as pd - -from numpy.polynomial.polynomial import polyfit -from numpy.testing import assert_allclose - -import os - -from pvlib.pvsystem import snlinverter - - -def _fit_ps0(p_ac, p_dc, p_ac0, p_dc0): - ''' Determine the ps0 parameter as the intercept at p_ac=0 of a line fit - to p_ac vs. (p_ac0 * p_dc - p_dc0 * p_ac) / (p_ac0 - p-ac) - This function assumes that p_dc is paired with p_ac, and that - p_ac < p_ac0. - ''' - y = np.array((p_ac0 * p_dc - p_dc0 * p_ac) / (p_ac0 - p_ac), dtype=float) - x = np.array([np.ones_like(y), p_ac], dtype=float).T - beta, _, _, _ = np.linalg.lstsq(x, y) - return beta[0] - - -def _calc_c0(p_ac, p_dc, p_ac0, p_dc0, p_s0): - x = p_dc - p_s0 - c0 = (p_ac - x / (p_dc0 - p_s0) * p_ac0) / (x**2. - x * (p_dc0 - p_s0)) - return np.nanmean(c0) - - -def fit_sandia_datasheet(curves, p_ac_0, dc_voltage, p_nt): - r''' - Determine parameters for the Sandia inverter model from a datasheet's - efficiency curves. - - Parameters - ---------- - curves : DataFrame - Columns must be ``'fraction_of_rated_power'``, ``'efficiency'``, - ``'dc_voltage_level'``. See notes for definition and units for each - column. - p_ac_0 : numeric - Rated AC power of the inverter [W]. - dc_voltage : Dict - Input DC voltage levels. Keys must be 'Vmin', 'Vnom', and 'Vmax'. [V] - p_nt : numeric - Night tare, i.e., power consumed while inverter is not delivering - AC power. [W] - - Returns - ------- - Dict with parameters for the Sandia inverter model. - - See :py:func:`snl_inverter` for a description of entries in the returned - Dict. - - See Also - -------- - snlinverter - - Notes - ----- - An inverter efficiency curve comprises a series of pairs - ('fraction_of_rated_power', 'efficiency'), e.g. (0.1, 0.5), (0.2, 0.7), - etc. at a specified DC voltage level.The DataFrame `curves` should contain - multiple efficiency curves for each DC voltage level; at least five curves - at each level is recommended. Columns in `curves` must be the following: - - ================ ======================================== - Column name Description - ================ ======================================== - 'fraction_of_rated_power' Fraction of rated AC power `p_ac_0`. Values must - include 1.0. The CEC inverter test protocol - specifies values of 0.1, 0.2, 0.3, 0.5, 0.75 - and 1.0. [unitless] - 'efficiency' Ratio of measured AC output power to measured - DC input power. [unitless] - 'dc_voltage_level' Values of 'Vmin', 'Vnom', and 'Vmax'. - - The output AC power is calculated as 'fraction_of_rated_power' * 'p_ac_0'. - The input DC power is calculated as (output AC power) / 'efficiency'. - - References - ---------- - .. [1] SAND2007-5036, "Performance Model for Grid-Connected - Photovoltaic Inverters by D. King, S. Gonzalez, G. Galbraith, W. - Boyson - .. [2] Sandia Inverter Model page, PV Performance Modeling Collaborative - https://pvpmc.sandia.gov/modeling-steps/dc-to-ac-conversion/sandia-inverter-model/ # noqa: E501 - ''' - - voltage_levels = ['Vmin', 'Vnom', 'Vmax'] - - curves['ac_power'] = curves['fraction_of_rated_power'] * p_ac_0 - curves['dc_power'] = curves['ac_power'] / curves['efficiency'] - - #Empty dataframe to contain intermediate variables - coeffs = pd.DataFrame(index=voltage_levels, - columns=['p_dc0', 'p_s0', 'c0', 'a', 'b', 'c'], - data=np.nan) - - for vl in voltage_levels: - temp = curves[curves['dc_voltage_level'] == vl] - # determine p_dc0 - base_eff = temp['efficiency'][temp['fraction_of_rated_power']==1.0] - p_dc_0 = p_ac_0 / base_eff - p_dc_0 = float(p_dc_0) - - # determine p_s0 - p_s0 = _fit_ps0(temp['ac_power'][temp['ac_power'] < p_ac_0], - temp['dc_power'][temp['ac_power'] < p_ac_0], - p_ac_0, p_dc_0) - - # calculate c0 - c0 = _calc_c0(temp['ac_power'], temp['dc_power'], p_ac_0, p_dc_0, p_s0) - - # fit a quadratic to (pac, pdc) at each voltage level, to get c3 - #Get a,b,c values from polyfit - x = curves['dc_power'][curves['dc_voltage_level']==vl] - y = curves['ac_power'][curves['dc_voltage_level']==vl] - c, b, a = polyfit(x, y, 2) - coeffs['p_dc0'][vl] = p_dc_0 - coeffs['p_s0'][vl] = p_s0 - coeffs['c0'][vl] = c0 - coeffs['a'][vl] = a - coeffs['b'][vl] = b - coeffs['c'][vl] = c - - p_dc0 = coeffs['p_dc0']['Vnom'] - p_s0 = coeffs['p_s0']['Vnom'] - c0 = coeffs['c0']['Vnom'] - c1 = (coeffs['p_dc0']['Vmax'] - coeffs['p_dc0']['Vmin']) \ - / (dc_voltage['Vmax'] - dc_voltage['Vmin']) / p_dc0 - c2 = (coeffs['p_s0']['Vmax'] - coeffs['p_s0']['Vmin']) \ - / (dc_voltage['Vmax'] - dc_voltage['Vmin']) / p_s0 - c3 = (coeffs['a']['Vmax'] - coeffs['a']['Vmin']) \ - / ((dc_voltage['Vmax'] - dc_voltage['Vmin']) * c0) - - # prepare dict and return - return {'Paco': p_ac_0, 'Pdco': p_dc0, 'Vdco': dc_voltage['Vnom'], - 'Pso': p_s0, 'C0': c0, 'C1': c1, 'C2': c2, 'C3': c3, 'Pnt': p_nt} - - -def fit_sandia_meas(curves, p_ac_0, p_nt): - r''' - Determine parameters for the Sandia inverter model from measured efficiency - curves. - - Parameters - ---------- - curves : DataFrame - Columns must be ``'fraction_of_rated_power'``, ``'efficiency'``, - ``'dc_voltage_level'``, ``'ac_power'``, ``'dc_voltage'``. See notes - for definition and units for each column. - p_ac_0 : numeric - Rated AC power of the inverter [W]. - p_nt : numeric - Night tare, i.e., power consumed while inverter is not delivering - AC power. [W] - - Returns - ------- - Dict with parameters for the Sandia inverter model. - - See :py:func:`snl_inverter` for a description of entries in the returned - Dict. - - See Also - -------- - snlinverter - - Notes - ----- - An inverter efficiency curve comprises a series of pairs - ('fraction_of_rated_power', 'efficiency'), e.g. (0.1, 0.5), (0.2, 0.7), - etc. at a specified DC voltage level.The DataFrame `curves` should contain - multiple efficiency curves for each DC voltage level; at least five curves - at each level is recommended. Columns in `curves` must be the following: - - ================ ======================================== - Column name Description - ================ ======================================== - 'fraction_of_rated_power' Fraction of rated AC power `p_ac_0`. The - CEC inverter test protocol specifies values - of 0.1, 0.2, 0.3, 0.5, 0.75 and 1.0. [unitless] - 'efficiency' Ratio of measured AC output power to measured - DC input power. [unitless] - 'dc_voltage_level' Must be one of 'Vmin', 'Vnom', or 'Vmax'. - 'ac_power' Measurd output AC power. [W] - 'dc_voltage' Measured DC input voltage. [V] - - References - ---------- - .. [1] SAND2007-5036, "Performance Model for Grid-Connected - Photovoltaic Inverters by D. King, S. Gonzalez, G. Galbraith, W. - Boyson - .. [2] Sandia Inverter Model page, PV Performance Modeling Collaborative - https://pvpmc.sandia.gov/modeling-steps/dc-to-ac-conversion/sandia-inverter-model/ # noqa: E501 - ''' - - voltage_levels = ['Vmin', 'Vnom', 'Vmax'] - - #Declaring x_d - v_nom = curves['dc_voltage'][curves['dc_voltage_level']=='Vnom'] - v_nom = v_nom.mean() - v_d = np.array( - [curves['dc_voltage'][curves['dc_voltage_level']=='Vmin'].mean(), - v_nom, - curves['dc_voltage'][curves['dc_voltage_level']=='Vmax'].mean()]) - x_d = v_d - v_nom - - curves['dc_power'] = curves['ac_power'] / curves['efficiency'] - - #Empty dataframe to contain intermediate variables - coeffs = pd.DataFrame(index=voltage_levels, - columns=['a', 'b', 'c', 'p_dc', 'p_s0'], data=np.nan) - - def solve_quad(a, b, c): - return (-b + (b**2 - 4 * a * c)**.5) / (2 * a) - - #STEP 3E, use np.polyfit to get betas - def extract_c(x_d, add): - test = np.polyfit(x_d, add, 1) - beta1, beta0 = test - c = beta1 / beta0 - return beta0, beta1, c - - for d in voltage_levels: - x = curves['dc_power'][curves['dc_voltage_level']==d] - y = curves['ac_power'][curves['dc_voltage_level']==d] - #STEP 3B - #Get a,b,c values from polyfit - c, b, a = polyfit(x, y, 2) - - #STEP 3D, solve for p_dc and p_s0 - - p_dc = solve_quad(a, b, (c - p_ac_0)) - - p_s0 = solve_quad(a, b, c) - - - #Add values to dataframe at index d - coeffs['a'][d] = a - coeffs['b'][d] = b - coeffs['c'][d] = c - coeffs['p_dc'][d] = p_dc - coeffs['p_s0'][d] = p_s0 - - b_dc0, b_dc1, c1 = extract_c(x_d, coeffs['p_dc']) - b_s0, b_s1, c2 = extract_c(x_d, coeffs['p_s0']) - b_c0, b_c1, c3 = extract_c(x_d, coeffs['a']) - - - p_dc0 = b_dc0 - p_s0 = b_s0 - c0 = b_c0 - - # prepare dict and return - return {'Paco': p_ac_0, 'Pdco': p_dc0, 'Vdco': v_nom, 'Pso': p_s0, - 'C0': c0, 'C1': c1, 'C2': c2, 'C3': c3, 'Pnt': p_nt} - - -DATA_DIR = "C:\\python\\pvlib-remote\\pvlib-python\\pvlib\\data" -def test_fit_sandia_meas(): - inverter_curves = os.path.join(DATA_DIR, 'inverter_fit_snl_meas.csv') - curves = pd.read_csv(inverter_curves) - expected = np.array([333000, 343251, 740, 1427.746, -5.768e-08, 3.596e-05, - 1.038e-03, 2.978e-05, 1.]) - keys = ['Paco', 'Pdco', 'Vdco', 'Pso', 'C0', 'C1', 'C2', 'C3', 'Pnt'] - result_dict = fit_sandia_meas(curves, 333000, 1.) - result = np.array([result_dict[k] for k in keys]) - assert_allclose(expected, result, rtol=1e-3) - - -def test_fit_sandia_datasheet(): - inverter_curves = os.path.join(DATA_DIR, 'inverter_fit_snl_datasheet.csv') - curves = pd.read_csv(inverter_curves) - dc_voltage_levels = {'Vmin': 220., 'Vnom': 240., 'Vmax': 260.} - expected_dict = {'Paco': 1000., 'Pdco': 1050., 'Vdco': 240., 'Pso': 10., - 'C0': 1e-6, 'C1': 1e-4, 'C2': 1e-2, 'C3': 1e-3, 'Pnt': 1} - keys = ['Paco', 'Pdco', 'Vdco', 'Pso', 'C0', 'C1', 'C2', 'C3', 'Pnt'] - expected = np.array([expected_dict[k] for k in keys]) - # recover known values within 0.5% - result_dict = fit_sandia_datasheet(curves, 1000., dc_voltage_levels, 1.) - result = np.array([result_dict[k] for k in keys]) - assert_allclose(expected, result, rtol=5e-3) - # calculate efficiency from recovered parameters - calc_effic = {k: np.nan for k in dc_voltage_levels.keys()} - for vlev in dc_voltage_levels.keys(): - pdc = curves[curves['dc_voltage_level']==vlev]['pdc'] - calc_effic[vlev] = snlinverter(dc_voltage_levels[vlev], pdc, - result_dict) / pdc - assert_allclose(calc_effic[vlev], - curves[curves['dc_voltage_level']==vlev]['efficiency'], - rtol=1e-5) - return expected, result - -exp, res = test_fit_sandia_datasheet() - - -# calculate efficiency from recovered parameters - - diff --git a/pvlib/tests/test_inverter.py b/pvlib/tests/test_inverter.py index 0816683f3a..4d38eedf6f 100644 --- a/pvlib/tests/test_inverter.py +++ b/pvlib/tests/test_inverter.py @@ -6,8 +6,10 @@ from pandas.testing import assert_series_equal from numpy.testing import assert_allclose +from conftest import needs_numpy_1_10, DATA_DIR +import pytest + from pvlib import inverter -from conftest import needs_numpy_1_10 def test_adr(adr_inverter_parameters): @@ -131,3 +133,25 @@ def test_pvwatts_series(): expected = pd.Series(np.array([np.nan, 0., 47.608436, 95.])) out = inverter.pvwatts(pdc, pdc0, 0.95) assert_series_equal(expected, out) + + +INVERTER_TEST_MEAS = DATA_DIR / 'inverter_fit_snl_meas.csv' +INVERTER_TEST_SIM = DATA_DIR / 'inverter_fit_snl_datasheet.csv' + +@pytest.mark.parametrize('infilen, expected', [ + (INVERTER_TEST_MEAS, {'Paco': 333000., 'Pdco': 343251., 'Vdco': 740., + 'Pso': 1427.746, 'C0': -5.768e-08, + 'C1': 3.596e-05, 'C2': 1.038e-03, + 'C3': 2.978e-05, 'Pnt': 1.}), + (INVERTER_TEST_SIM, {'Paco': 1000., 'Pdco': 1050., 'Vdco': 240., + 'Pso': 10., 'C0': 1e-6, 'C1': 1e-4, + 'C2': 1e-2, 'C3': 1e-3, 'Pnt': 1.}), +]) +def test_fit_sandia(infilen, expected): + curves = pd.read_csv(infilen) + keys = ['Paco', 'Pdco', 'Vdco', 'Pso', 'C0', 'C1', 'C2', 'C3', 'Pnt'] + exp = {k: v for k, v in zip(keys, expected)} + result_dict = inverter.fit_sandia(curves, expected['Paco'], + expected['Pnt']) + result = np.array([result_dict[k] for k in keys]) + assert_allclose(exp, result, rtol=1e-3) From e7c15941eeeddf7c6a31bffbf6af699ea0ed95be Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 24 Jul 2020 09:12:13 -0600 Subject: [PATCH 04/15] fix test, stickler --- pvlib/inverter.py | 10 +++++----- pvlib/tests/test_inverter.py | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pvlib/inverter.py b/pvlib/inverter.py index caf1046aed..2b505281ef 100644 --- a/pvlib/inverter.py +++ b/pvlib/inverter.py @@ -378,9 +378,9 @@ def fit_sandia(curves, p_ac_0, p_nt): # average dc input voltage at each voltage level v_d = np.array( - [curves['dc_voltage'][curves['dc_voltage_level']=='Vmin'].mean(), - curves['dc_voltage'][curves['dc_voltage_level']=='Vnom'].mean(), - curves['dc_voltage'][curves['dc_voltage_level']=='Vmax'].mean()]) + [curves['dc_voltage'][curves['dc_voltage_level'] == 'Vmin'].mean(), + curves['dc_voltage'][curves['dc_voltage_level'] == 'Vnom'].mean(), + curves['dc_voltage'][curves['dc_voltage_level'] == 'Vmax'].mean()]) v_nom = v_d[1] # model parameter # independent variable for regressions, x_d x_d = v_d - v_nom @@ -402,8 +402,8 @@ def extract_c(x_d, add): return beta0, beta1, c for d in voltage_levels: - x = curves['dc_power'][curves['dc_voltage_level']==d] - y = curves['ac_power'][curves['dc_voltage_level']==d] + x = curves['dc_power'][curves['dc_voltage_level'] == d] + y = curves['ac_power'][curves['dc_voltage_level'] == d] # [2] STEP 3B # Get a,b,c values from polyfit c, b, a = np.polyfit(x, y, 2) diff --git a/pvlib/tests/test_inverter.py b/pvlib/tests/test_inverter.py index 4d38eedf6f..db12b437a8 100644 --- a/pvlib/tests/test_inverter.py +++ b/pvlib/tests/test_inverter.py @@ -138,20 +138,20 @@ def test_pvwatts_series(): INVERTER_TEST_MEAS = DATA_DIR / 'inverter_fit_snl_meas.csv' INVERTER_TEST_SIM = DATA_DIR / 'inverter_fit_snl_datasheet.csv' + @pytest.mark.parametrize('infilen, expected', [ - (INVERTER_TEST_MEAS, {'Paco': 333000., 'Pdco': 343251., 'Vdco': 740., - 'Pso': 1427.746, 'C0': -5.768e-08, - 'C1': 3.596e-05, 'C2': 1.038e-03, - 'C3': 2.978e-05, 'Pnt': 1.}), - (INVERTER_TEST_SIM, {'Paco': 1000., 'Pdco': 1050., 'Vdco': 240., - 'Pso': 10., 'C0': 1e-6, 'C1': 1e-4, - 'C2': 1e-2, 'C3': 1e-3, 'Pnt': 1.}), + (INVERTER_TEST_MEAS, {'Paco': 333000., 'Pdco': 343251., 'Vdco': 740., + 'Pso': 1427.746, 'C0': -5.768e-08, 'C1': 3.596e-05, + 'C2': 1.038e-03, 'C3': 2.978e-05, 'Pnt': 1.}), + (INVERTER_TEST_SIM, {'Paco': 1000., 'Pdco': 1050., 'Vdco': 240., + 'Pso': 10., 'C0': 1e-6, 'C1': 1e-4, 'C2': 1e-2, + 'C3': 1e-3, 'Pnt': 1.}), ]) def test_fit_sandia(infilen, expected): curves = pd.read_csv(infilen) - keys = ['Paco', 'Pdco', 'Vdco', 'Pso', 'C0', 'C1', 'C2', 'C3', 'Pnt'] - exp = {k: v for k, v in zip(keys, expected)} result_dict = inverter.fit_sandia(curves, expected['Paco'], expected['Pnt']) + keys = ['Paco', 'Pdco', 'Vdco', 'Pso', 'C0', 'C1', 'C2', 'C3', 'Pnt'] + exp = np.array([expected[k] for k in keys]) result = np.array([result_dict[k] for k in keys]) assert_allclose(exp, result, rtol=1e-3) From c38679f7e69b9d124373728d7fb722057aa1f688 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 24 Jul 2020 09:33:01 -0600 Subject: [PATCH 05/15] sort out np.polyfit vs. np.polynomial.polynomial.polyfit --- pvlib/data/inverter_fit_snl_meas.csv | 2 +- pvlib/inverter.py | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pvlib/data/inverter_fit_snl_meas.csv b/pvlib/data/inverter_fit_snl_meas.csv index f12dd19d29..dcbc71201d 100644 --- a/pvlib/data/inverter_fit_snl_meas.csv +++ b/pvlib/data/inverter_fit_snl_meas.csv @@ -1,4 +1,4 @@ -fraction_of_rated_power,dc_voltage_level,ac_power,dc_voltage,efficiency +fraction_of_rated_power,dc_voltage_level,ac_power,dc_voltage,efficiency 0.1,Vmin,32800,660.5,0.95814 0.2,Vmin,73000,660.9,0.9755 0.3,Vmin,107500,660.73,0.97787 diff --git a/pvlib/inverter.py b/pvlib/inverter.py index 2b505281ef..8e55c87947 100644 --- a/pvlib/inverter.py +++ b/pvlib/inverter.py @@ -14,6 +14,8 @@ import numpy as np import pandas as pd +from numpy.polynomial.polynomial import polyfit # different than np.polyfit + def sandia(v_dc, p_dc, inverter): r''' @@ -394,10 +396,10 @@ def fit_sandia(curves, p_ac_0, p_nt): def solve_quad(a, b, c): return (-b + (b**2 - 4 * a * c)**.5) / (2 * a) - # [2] STEP 3E, use np.polyfit to get betas + # [2] STEP 3E, fit a line to (DC voltage, model_coefficient) def extract_c(x_d, add): - test = np.polyfit(x_d, add, 1) - beta1, beta0 = test + test = polyfit(x_d, add, 1) + beta0, beta1 = test c = beta1 / beta0 return beta0, beta1, c @@ -405,8 +407,8 @@ def extract_c(x_d, add): x = curves['dc_power'][curves['dc_voltage_level'] == d] y = curves['ac_power'][curves['dc_voltage_level'] == d] # [2] STEP 3B - # Get a,b,c values from polyfit - c, b, a = np.polyfit(x, y, 2) + # fit a quadratic to (DC power, AC power) + c, b, a = polyfit(x, y, 2) # [2] STEP 3D, solve for p_dc and p_s0 p_dc = solve_quad(a, b, (c - p_ac_0)) From b67766dff87333d9d3a7b4cfa2f9e4c7ee125b95 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 24 Jul 2020 09:36:33 -0600 Subject: [PATCH 06/15] correct file name --- pvlib/tests/test_inverter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/tests/test_inverter.py b/pvlib/tests/test_inverter.py index db12b437a8..fa0783d63f 100644 --- a/pvlib/tests/test_inverter.py +++ b/pvlib/tests/test_inverter.py @@ -136,7 +136,7 @@ def test_pvwatts_series(): INVERTER_TEST_MEAS = DATA_DIR / 'inverter_fit_snl_meas.csv' -INVERTER_TEST_SIM = DATA_DIR / 'inverter_fit_snl_datasheet.csv' +INVERTER_TEST_SIM = DATA_DIR / 'inverter_fit_snl_sim.csv' @pytest.mark.parametrize('infilen, expected', [ From c0f96dd55c9a54e15b4c2f4d6182a2c406db9662 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 24 Jul 2020 12:01:09 -0600 Subject: [PATCH 07/15] api, whatsnew --- docs/sphinx/source/api.rst | 29 +++++++++++++++++--------- docs/sphinx/source/whatsnew/v0.8.0.rst | 2 ++ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst index 0e9cfa19e0..f6a94eb255 100644 --- a/docs/sphinx/source/api.rst +++ b/docs/sphinx/source/api.rst @@ -265,6 +265,15 @@ Low-level functions for solving the single diode equation. singlediode.bishop88_v_from_i singlediode.bishop88_mpp +Functions for fitting diode models + +.. autosummary:: + :toctree: generated/ + + ivtools.fit_sde_sandia + ivtools.fit_sdm_cec_sam + ivtools.fit_sdm_desoto + Inverter models (DC to AC conversion) ------------------------------------- @@ -275,6 +284,16 @@ Inverter models (DC to AC conversion) inverter.adr inverter.pvwatts +Functions for fitting inverter models + +.. autosummary:: + :toctree: generated/ + + inverter.fit_sandia + +Functions for fitting inverter models + + PV System Models ---------------- @@ -311,16 +330,6 @@ PVWatts model inverter.pvwatts pvsystem.pvwatts_losses -Functions for fitting diode models ----------------------------------- - -.. autosummary:: - :toctree: generated/ - - ivtools.fit_sde_sandia - ivtools.fit_sdm_cec_sam - ivtools.fit_sdm_desoto - Other ----- diff --git a/docs/sphinx/source/whatsnew/v0.8.0.rst b/docs/sphinx/source/whatsnew/v0.8.0.rst index cbe57587e9..90064de07a 100644 --- a/docs/sphinx/source/whatsnew/v0.8.0.rst +++ b/docs/sphinx/source/whatsnew/v0.8.0.rst @@ -38,6 +38,8 @@ Enhancements * Add :py:func:`pvlib.iam.marion_diffuse` and :py:func:`pvlib.iam.marion_integrate` to calculate IAM values for diffuse irradiance. (:pull:`984`) +* Add :py:func:`pvlib.inverter.fit_sandia` that fits the Sandia inverter model + to a set of inverter efficiency curves. (:pull:`1011`) Bug fixes ~~~~~~~~~ From 3b37893a624d401d36afc639c9159060982cad2c Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 24 Jul 2020 12:11:51 -0600 Subject: [PATCH 08/15] docstring improvements --- pvlib/inverter.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pvlib/inverter.py b/pvlib/inverter.py index 8e55c87947..2e6a6a0b46 100644 --- a/pvlib/inverter.py +++ b/pvlib/inverter.py @@ -347,9 +347,10 @@ def fit_sandia(curves, p_ac_0, p_nt): ----- An inverter efficiency curve comprises a series of pairs ('fraction_of_rated_power', 'efficiency'), e.g. (0.1, 0.5), (0.2, 0.7), - etc. at a specified DC voltage level.The DataFrame `curves` should contain - multiple efficiency curves for each DC voltage level; at least five curves - at each level is recommended. Columns in `curves` must be the following: + etc. at a specified DC voltage level and AC power level. The DataFrame + `curves` must contain at least one efficiency curve for each combination + of DC voltage level and AC power level. Columns in `curves` must be the + following: ================ ======================================== Column name Description @@ -362,10 +363,12 @@ def fit_sandia(curves, p_ac_0, p_nt): least one curve must be provided for each combination of fraction_of_rated_power and dc_voltage_level. - 'dc_voltage' Measured DC input voltage. [V] - 'ac_power' Measurd output AC power. [W] - 'efficiency' Ratio of measured AC output power to measured - DC input power. [unitless] + 'dc_voltage' DC input voltage. [V] + 'ac_power' Output AC power. [W] + 'efficiency' Ratio of AC output power to DC input power. + [unitless] + + For each curve, DC input power is calculated from AC power and efficiency. References ---------- From ae4380f8ce81fea38d99471351dfa22cc0645183 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 4 Aug 2020 12:24:02 -0600 Subject: [PATCH 09/15] revisions from review --- docs/sphinx/source/api.rst | 2 -- pvlib/inverter.py | 29 +++++++++++++---------------- pvlib/tests/test_inverter.py | 5 ++--- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst index f6a94eb255..dd7c618646 100644 --- a/docs/sphinx/source/api.rst +++ b/docs/sphinx/source/api.rst @@ -291,8 +291,6 @@ Functions for fitting inverter models inverter.fit_sandia -Functions for fitting inverter models - PV System Models ---------------- diff --git a/pvlib/inverter.py b/pvlib/inverter.py index 2e6a6a0b46..f4f49a55dc 100644 --- a/pvlib/inverter.py +++ b/pvlib/inverter.py @@ -336,12 +336,13 @@ def fit_sandia(curves, p_ac_0, p_nt): Returns ------- - dict with parameters for the Sandia inverter model. See - :py:func:`snl_inverter` for a description of entries in the returned dict. + dict + A set of parameters for the Sandia inverter model [1]_. See + :py:func:`pvlib.inverter.sandia` for a description of keys and values. See Also -------- - snlinverter + pvlib.inverter.sandia Notes ----- @@ -352,9 +353,9 @@ def fit_sandia(curves, p_ac_0, p_nt): of DC voltage level and AC power level. Columns in `curves` must be the following: - ================ ======================================== + ========================= =============================================== Column name Description - ================ ======================================== + ========================= =============================================== 'fraction_of_rated_power' Fraction of rated AC power `p_ac_0`. The CEC inverter test protocol specifies values of 0.1, 0.2, 0.3, 0.5, 0.75 and 1.0. [unitless] @@ -367,8 +368,10 @@ def fit_sandia(curves, p_ac_0, p_nt): 'ac_power' Output AC power. [W] 'efficiency' Ratio of AC output power to DC input power. [unitless] + ========================= =============================================== For each curve, DC input power is calculated from AC power and efficiency. + The fitting procedure is described at [2]_. References ---------- @@ -376,8 +379,8 @@ def fit_sandia(curves, p_ac_0, p_nt): Photovoltaic Inverters by D. King, S. Gonzalez, G. Galbraith, W. Boyson .. [2] Sandia Inverter Model page, PV Performance Modeling Collaborative - https://pvpmc.sandia.gov/modeling-steps/dc-to-ac-conversion/sandia-inverter-model/ # noqa: E501 - ''' + https://pvpmc.sandia.gov/modeling-steps/dc-to-ac-conversion/sandia-inverter-model/ + ''' # noqa: E501 voltage_levels = ['Vmin', 'Vnom', 'Vmax'] @@ -396,13 +399,9 @@ def fit_sandia(curves, p_ac_0, p_nt): coeffs = pd.DataFrame(index=voltage_levels, columns=['a', 'b', 'c', 'p_dc', 'p_s0'], data=np.nan) - def solve_quad(a, b, c): - return (-b + (b**2 - 4 * a * c)**.5) / (2 * a) - # [2] STEP 3E, fit a line to (DC voltage, model_coefficient) def extract_c(x_d, add): - test = polyfit(x_d, add, 1) - beta0, beta1 = test + beta0, beta1 = polyfit(x_d, add, 1) c = beta1 / beta0 return beta0, beta1, c @@ -414,13 +413,11 @@ def extract_c(x_d, add): c, b, a = polyfit(x, y, 2) # [2] STEP 3D, solve for p_dc and p_s0 - p_dc = solve_quad(a, b, (c - p_ac_0)) - p_s0 = solve_quad(a, b, c) + p_dc = np.roots([a, b, (c - p_ac_0)]) + p_s0 = np.roots([a, b, c]) # Add values to dataframe at index d coeffs['a'][d] = a - coeffs['b'][d] = b - coeffs['c'][d] = c coeffs['p_dc'][d] = p_dc coeffs['p_s0'][d] = p_s0 diff --git a/pvlib/tests/test_inverter.py b/pvlib/tests/test_inverter.py index fa0783d63f..7750bbec76 100644 --- a/pvlib/tests/test_inverter.py +++ b/pvlib/tests/test_inverter.py @@ -151,7 +151,6 @@ def test_fit_sandia(infilen, expected): curves = pd.read_csv(infilen) result_dict = inverter.fit_sandia(curves, expected['Paco'], expected['Pnt']) - keys = ['Paco', 'Pdco', 'Vdco', 'Pso', 'C0', 'C1', 'C2', 'C3', 'Pnt'] - exp = np.array([expected[k] for k in keys]) - result = np.array([result_dict[k] for k in keys]) + exp = np.array([expected[k] for k in expected.keys()]) + result = np.array([result_dict[k] for k in expected.keys()]) assert_allclose(exp, result, rtol=1e-3) From ecce11137750ae6adbc132e2c9d8f8f221eb60b2 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 4 Aug 2020 13:57:39 -0600 Subject: [PATCH 10/15] correct use of np.roots --- pvlib/inverter.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pvlib/inverter.py b/pvlib/inverter.py index f4f49a55dc..33751d4d02 100644 --- a/pvlib/inverter.py +++ b/pvlib/inverter.py @@ -346,9 +346,9 @@ def fit_sandia(curves, p_ac_0, p_nt): Notes ----- - An inverter efficiency curve comprises a series of pairs - ('fraction_of_rated_power', 'efficiency'), e.g. (0.1, 0.5), (0.2, 0.7), - etc. at a specified DC voltage level and AC power level. The DataFrame + An inverter efficiency curve at a specified DC voltage level and AC power + level comprises a series of pairs ('fraction_of_rated_power', + 'efficiency'), e.g. (0.1, 0.5), (0.2, 0.7), etc. . The DataFrame `curves` must contain at least one efficiency curve for each combination of DC voltage level and AC power level. Columns in `curves` must be the following: @@ -413,8 +413,8 @@ def extract_c(x_d, add): c, b, a = polyfit(x, y, 2) # [2] STEP 3D, solve for p_dc and p_s0 - p_dc = np.roots([a, b, (c - p_ac_0)]) - p_s0 = np.roots([a, b, c]) + p_dc = np.max(np.roots([a, b, (c - p_ac_0)])) + p_s0 = np.max(np.roots([a, b, c])) # right-side root of quadratic # Add values to dataframe at index d coeffs['a'][d] = a From 303ab1c85228536582fc5700af215186f4163c24 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 4 Aug 2020 14:10:26 -0600 Subject: [PATCH 11/15] use pytest.approx instead of converting dicts to np.array --- pvlib/tests/test_inverter.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pvlib/tests/test_inverter.py b/pvlib/tests/test_inverter.py index 7750bbec76..16dc6cd797 100644 --- a/pvlib/tests/test_inverter.py +++ b/pvlib/tests/test_inverter.py @@ -149,8 +149,6 @@ def test_pvwatts_series(): ]) def test_fit_sandia(infilen, expected): curves = pd.read_csv(infilen) - result_dict = inverter.fit_sandia(curves, expected['Paco'], + result = inverter.fit_sandia(curves, expected['Paco'], expected['Pnt']) - exp = np.array([expected[k] for k in expected.keys()]) - result = np.array([result_dict[k] for k in expected.keys()]) - assert_allclose(exp, result, rtol=1e-3) + assert expected == pytest.approx(result, rtol=1e-3) From 768d39dd6427b5f6d2e88deb8c34899919f1231a Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 5 Aug 2020 08:09:48 -0600 Subject: [PATCH 12/15] correct kwarg for pytest.approx --- pvlib/tests/test_inverter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/tests/test_inverter.py b/pvlib/tests/test_inverter.py index 16dc6cd797..8d0a885f12 100644 --- a/pvlib/tests/test_inverter.py +++ b/pvlib/tests/test_inverter.py @@ -151,4 +151,4 @@ def test_fit_sandia(infilen, expected): curves = pd.read_csv(infilen) result = inverter.fit_sandia(curves, expected['Paco'], expected['Pnt']) - assert expected == pytest.approx(result, rtol=1e-3) + assert expected == pytest.approx(result, rel=1e-3) From e8662fb484219e7eb68a0e87a08846b1ddb527c1 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 5 Aug 2020 09:05:07 -0600 Subject: [PATCH 13/15] back to solve_quad instead of np.roots, lint --- pvlib/inverter.py | 7 +++++-- pvlib/tests/test_inverter.py | 3 +-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pvlib/inverter.py b/pvlib/inverter.py index 33751d4d02..307dd7cef6 100644 --- a/pvlib/inverter.py +++ b/pvlib/inverter.py @@ -399,6 +399,9 @@ def fit_sandia(curves, p_ac_0, p_nt): coeffs = pd.DataFrame(index=voltage_levels, columns=['a', 'b', 'c', 'p_dc', 'p_s0'], data=np.nan) + def solve_quad(a, b, c): + return (-b + (b**2 - 4 * a * c)**.5) / (2 * a) + # [2] STEP 3E, fit a line to (DC voltage, model_coefficient) def extract_c(x_d, add): beta0, beta1 = polyfit(x_d, add, 1) @@ -413,8 +416,8 @@ def extract_c(x_d, add): c, b, a = polyfit(x, y, 2) # [2] STEP 3D, solve for p_dc and p_s0 - p_dc = np.max(np.roots([a, b, (c - p_ac_0)])) - p_s0 = np.max(np.roots([a, b, c])) # right-side root of quadratic + p_dc = solve_quad(a, b, (c - p_ac_0)) + p_s0 = solve_quad(a, b, c) # Add values to dataframe at index d coeffs['a'][d] = a diff --git a/pvlib/tests/test_inverter.py b/pvlib/tests/test_inverter.py index 8d0a885f12..1c0ca5e6fb 100644 --- a/pvlib/tests/test_inverter.py +++ b/pvlib/tests/test_inverter.py @@ -149,6 +149,5 @@ def test_pvwatts_series(): ]) def test_fit_sandia(infilen, expected): curves = pd.read_csv(infilen) - result = inverter.fit_sandia(curves, expected['Paco'], - expected['Pnt']) + result = inverter.fit_sandia(curves, expected['Paco'], expected['Pnt']) assert expected == pytest.approx(result, rel=1e-3) From de6916c6fec2b75c6a5c0c1b5de1be8c9a476a5b Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Thu, 13 Aug 2020 11:15:44 -0600 Subject: [PATCH 14/15] change to Series inputs --- pvlib/inverter.py | 82 +++++++++++++++--------------------- pvlib/tests/test_inverter.py | 7 ++- 2 files changed, 40 insertions(+), 49 deletions(-) diff --git a/pvlib/inverter.py b/pvlib/inverter.py index 307dd7cef6..02b6ef3aaa 100644 --- a/pvlib/inverter.py +++ b/pvlib/inverter.py @@ -185,7 +185,7 @@ def adr(v_dc, p_dc, inverter, vtol=0.10): References ---------- - .. [1] Driesse, A. "Beyond the Curves: Modeling the Electrical Efficiency + .. [1] A. Driesse, "Beyond the Curves: Modeling the Electrical Efficiency of Photovoltaic Inverters", 33rd IEEE Photovoltaic Specialist Conference (PVSC), June 2008 @@ -294,8 +294,7 @@ def pvwatts(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637): References ---------- .. [1] A. P. Dobos, "PVWatts Version 5 Manual," - http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf - (2014). + http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf (2014). """ pac0 = eta_inv_nom * pdc0 @@ -317,20 +316,24 @@ def pvwatts(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637): return power_ac -def fit_sandia(curves, p_ac_0, p_nt): +def fit_sandia(ac_power, dc_power, dc_voltage, dc_voltage_level, p_ac_0, p_nt): r''' - Determine parameters for the Sandia inverter model from efficiency - curves. + Determine parameters for the Sandia inverter model. Parameters ---------- - curves : DataFrame - Columns must be ``'fraction_of_rated_power'``, ``'dc_voltage_level'``, - ``'dc_voltage'``, ``'ac_power'``, ``'efficiency'``. See notes for the - definition and unit for each column. - p_ac_0 : numeric + ac_power : Series + AC power output at each data point [W]. + dc_power : Series + DC power input at each data point [W]. + dc_voltage : Series + DC input voltage at each data point [V]. + dc_voltage_level : Series + DC input voltage level at each data point. Values must be 'Vmin', + 'Vnom' or 'Vmax'. + p_ac_0 : float Rated AC power of the inverter [W]. - p_nt : numeric + p_nt : float Night tare, i.e., power consumed while inverter is not delivering AC power. [W] @@ -346,55 +349,38 @@ def fit_sandia(curves, p_ac_0, p_nt): Notes ----- - An inverter efficiency curve at a specified DC voltage level and AC power - level comprises a series of pairs ('fraction_of_rated_power', - 'efficiency'), e.g. (0.1, 0.5), (0.2, 0.7), etc. . The DataFrame - `curves` must contain at least one efficiency curve for each combination - of DC voltage level and AC power level. Columns in `curves` must be the - following: - - ========================= =============================================== - Column name Description - ========================= =============================================== - 'fraction_of_rated_power' Fraction of rated AC power `p_ac_0`. The - CEC inverter test protocol specifies values - of 0.1, 0.2, 0.3, 0.5, 0.75 and 1.0. [unitless] - 'dc_voltage_level' Must be 'Vmin', 'Vnom', or 'Vmax'. Curves must - be provided for all three voltage levels. At - least one curve must be provided for each - combination of fraction_of_rated_power and - dc_voltage_level. - 'dc_voltage' DC input voltage. [V] - 'ac_power' Output AC power. [W] - 'efficiency' Ratio of AC output power to DC input power. - [unitless] - ========================= =============================================== - - For each curve, DC input power is calculated from AC power and efficiency. - The fitting procedure is described at [2]_. + The fitting procedure to estimate parameters is described at [2]_. + A data point is a pair of values (dc_power, ac_power). Typically, inverter + performance is measured or described at three DC input voltage levels, + denoted 'Vmin', 'Vnom' and 'Vmax' and at each level, inverter efficiency + is determined at various output power levels. For example, + the CEC inverter test protocol [3]_ specifies measurement of input DC + power that delivers AC output power of 0.1, 0.2, 0.3, 0.5, 0.75 and 1.0 of + the inverter's AC power rating. References ---------- - .. [1] SAND2007-5036, "Performance Model for Grid-Connected - Photovoltaic Inverters by D. King, S. Gonzalez, G. Galbraith, W. - Boyson + .. [1] D. King, S. Gonzalez, G. Galbraith, W. Boyson, "Performance Model + for Grid-Connected Photovoltaic Inverters", SAND2007-5036, Sandia + National Laboratories. .. [2] Sandia Inverter Model page, PV Performance Modeling Collaborative https://pvpmc.sandia.gov/modeling-steps/dc-to-ac-conversion/sandia-inverter-model/ + .. [3] W. Bower, et al., "Performance Test Protocol for Evaluating + Inverters Used in Grid-Connected Photovoltaic Systems", available at + https://www.energy.ca.gov/sites/default/files/2020-06/2004-11-22_Sandia_Test_Protocol_ada.pdf ''' # noqa: E501 voltage_levels = ['Vmin', 'Vnom', 'Vmax'] # average dc input voltage at each voltage level v_d = np.array( - [curves['dc_voltage'][curves['dc_voltage_level'] == 'Vmin'].mean(), - curves['dc_voltage'][curves['dc_voltage_level'] == 'Vnom'].mean(), - curves['dc_voltage'][curves['dc_voltage_level'] == 'Vmax'].mean()]) + [dc_voltage[dc_voltage_level == 'Vmin'].mean(), + dc_voltage[dc_voltage_level == 'Vnom'].mean(), + dc_voltage[dc_voltage_level == 'Vmax'].mean()]) v_nom = v_d[1] # model parameter # independent variable for regressions, x_d x_d = v_d - v_nom - curves['dc_power'] = curves['ac_power'] / curves['efficiency'] - # empty dataframe to contain intermediate variables coeffs = pd.DataFrame(index=voltage_levels, columns=['a', 'b', 'c', 'p_dc', 'p_s0'], data=np.nan) @@ -409,8 +395,8 @@ def extract_c(x_d, add): return beta0, beta1, c for d in voltage_levels: - x = curves['dc_power'][curves['dc_voltage_level'] == d] - y = curves['ac_power'][curves['dc_voltage_level'] == d] + x = dc_power[dc_voltage_level == d] + y = ac_power[dc_voltage_level == d] # [2] STEP 3B # fit a quadratic to (DC power, AC power) c, b, a = polyfit(x, y, 2) diff --git a/pvlib/tests/test_inverter.py b/pvlib/tests/test_inverter.py index 82a2ea639c..a04bc134d4 100644 --- a/pvlib/tests/test_inverter.py +++ b/pvlib/tests/test_inverter.py @@ -149,5 +149,10 @@ def test_pvwatts_series(): ]) def test_fit_sandia(infilen, expected): curves = pd.read_csv(infilen) - result = inverter.fit_sandia(curves, expected['Paco'], expected['Pnt']) + dc_power = curves['ac_power'] / curves['efficiency'] + result = inverter.fit_sandia(ac_power=curves['ac_power'], + dc_power=dc_power, + dc_voltage=curves['dc_voltage'], + dc_voltage_level=curves['dc_voltage_level'], + expected['Paco'], expected['Pnt']) assert expected == pytest.approx(result, rel=1e-3) From 3d38e6c20e9c31cfaae950b5095b330e9d7c94b2 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 14 Aug 2020 09:11:00 -0600 Subject: [PATCH 15/15] correct test, Series to array_like --- pvlib/inverter.py | 8 ++++---- pvlib/tests/test_inverter.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pvlib/inverter.py b/pvlib/inverter.py index 02b6ef3aaa..a742e1a567 100644 --- a/pvlib/inverter.py +++ b/pvlib/inverter.py @@ -322,13 +322,13 @@ def fit_sandia(ac_power, dc_power, dc_voltage, dc_voltage_level, p_ac_0, p_nt): Parameters ---------- - ac_power : Series + ac_power : array_like AC power output at each data point [W]. - dc_power : Series + dc_power : array_like DC power input at each data point [W]. - dc_voltage : Series + dc_voltage : array_like DC input voltage at each data point [V]. - dc_voltage_level : Series + dc_voltage_level : array_like DC input voltage level at each data point. Values must be 'Vmin', 'Vnom' or 'Vmax'. p_ac_0 : float diff --git a/pvlib/tests/test_inverter.py b/pvlib/tests/test_inverter.py index a04bc134d4..de018e95c5 100644 --- a/pvlib/tests/test_inverter.py +++ b/pvlib/tests/test_inverter.py @@ -154,5 +154,5 @@ def test_fit_sandia(infilen, expected): dc_power=dc_power, dc_voltage=curves['dc_voltage'], dc_voltage_level=curves['dc_voltage_level'], - expected['Paco'], expected['Pnt']) + p_ac_0=expected['Paco'], p_nt=expected['Pnt']) assert expected == pytest.approx(result, rel=1e-3)