Source code for pvlib.inverter

"""
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_<model name>', e.g., fit_sandia.

"""

import numpy as np
import pandas as pd
from numpy.polynomial.polynomial import polyfit  # different than np.polyfit


def _sandia_eff(v_dc, p_dc, inverter):
    r'''
    Calculate the inverter AC power without clipping
    '''
    Paco = inverter['Paco']
    Pdco = inverter['Pdco']
    Vdco = inverter['Vdco']
    C0 = inverter['C0']
    C1 = inverter['C1']
    C2 = inverter['C2']
    C3 = inverter['C3']
    Pso = inverter['Pso']

    A = Pdco * (1 + C1 * (v_dc - Vdco))
    B = Pso * (1 + C2 * (v_dc - Vdco))
    C = C0 * (1 + C3 * (v_dc - Vdco))

    return (Paco / (A - B) - C * (A - B)) * (p_dc - B) + C * (p_dc - B)**2


def _sandia_limits(power_ac, p_dc, Paco, Pnt, Pso):
    r'''
    Applies minimum and maximum power limits to `power_ac`
    '''
    power_ac = np.minimum(Paco, power_ac)
    min_ac_power = -1.0 * abs(Pnt)
    below_limit = p_dc < Pso
    try:
        power_ac[below_limit] = min_ac_power
    except TypeError:  # power_ac is a float
        if below_limit:
            power_ac = min_ac_power
    return power_ac


[docs] def sandia(v_dc, p_dc, inverter): r''' Convert DC power and voltage to AC power using Sandia's Grid-Connected PV Inverter model. Parameters ---------- v_dc : numeric DC voltage input to the inverter. [V] p_dc : numeric DC power input to the inverter. [W] inverter : dict-like Defines parameters for the inverter model in [1]_. Returns ------- power_ac : numeric AC power output. [W] Notes ----- Determines the AC power output of an inverter given the DC voltage and DC power. Output AC power is bounded above by the parameter ``Paco``, to represent inverter "clipping". When `power_ac` would be less than parameter ``Pso`` (startup power required), then `power_ac` is set to ``-Pnt``, representing self-consumption. `power_ac` is not adjusted for maximum power point tracking (MPPT) voltage windows or maximum current limits of the inverter. Required model parameters are: ====== ============================================================ Column Description ====== ============================================================ Paco AC power rating of the inverter. [W] Pdco DC power input that results in Paco output at reference voltage Vdco. [W] Vdco DC voltage at which the AC power rating is achieved with Pdco power input. [V] Pso DC power required to start the inversion process, or self-consumption by inverter, strongly influences inverter efficiency at low power levels. [W] C0 Parameter defining the curvature (parabolic) of the relationship between AC power and DC power at the reference operating condition. [1/W] C1 Empirical coefficient allowing ``Pdco`` to vary linearly with DC voltage input. [1/V] C2 Empirical coefficient allowing ``Pso`` to vary linearly with DC voltage input. [1/V] C3 Empirical coefficient allowing ``C0`` to vary linearly with DC voltage input. [1/V] Pnt AC power consumed by the inverter at night (night tare). [W] ====== ============================================================ A copy of the parameter database from the System Advisor Model (SAM) [2]_ is provided with pvlib and may be read using :py:func:`pvlib.pvsystem.retrieve_sam`. References ---------- .. [1] D. King, S. Gonzalez, G. Galbraith, W. Boyson, "Performance Model for Grid-Connected Photovoltaic Inverters", SAND2007-5036, Sandia National Laboratories. .. [2] System Advisor Model web page. https://sam.nrel.gov. See also -------- pvlib.pvsystem.retrieve_sam ''' Paco = inverter['Paco'] Pnt = inverter['Pnt'] Pso = inverter['Pso'] power_ac = _sandia_eff(v_dc, p_dc, inverter) power_ac = _sandia_limits(power_ac, p_dc, Paco, Pnt, Pso) if isinstance(p_dc, pd.Series): power_ac = pd.Series(power_ac, index=p_dc.index) return power_ac
[docs] def sandia_multi(v_dc, p_dc, inverter): r''' Convert DC power and voltage to AC power for an inverter with multiple MPPT inputs. Uses Sandia's Grid-Connected PV Inverter model [1]_. Extension of [1]_ to inverters with multiple, unbalanced inputs as described in [2]_. Parameters ---------- v_dc : tuple, list or array of numeric DC voltage on each MPPT input of the inverter. If type is array, must be 2d with axis 0 being the MPPT inputs. [V] p_dc : tuple, list or array of numeric DC power on each MPPT input of the inverter. If type is array, must be 2d with axis 0 being the MPPT inputs. [W] inverter : dict-like Defines parameters for the inverter model in [1]_. Returns ------- power_ac : numeric AC power output for the inverter. [W] Raises ------ ValueError If v_dc and p_dc have different lengths. Notes ----- See :py:func:`pvlib.inverter.sandia` for definition of the parameters in `inverter`. References ---------- .. [1] D. King, S. Gonzalez, G. Galbraith, W. Boyson, "Performance Model for Grid-Connected Photovoltaic Inverters", SAND2007-5036, Sandia National Laboratories. .. [2] C. Hansen, J. Johnson, R. Darbali-Zamora, N. Gurule. "Modeling Efficiency Of Inverters With Multiple Inputs", 49th IEEE Photovoltaic Specialist Conference, Philadelphia, PA, USA. June 2022. See also -------- pvlib.inverter.sandia ''' if len(p_dc) != len(v_dc): raise ValueError('p_dc and v_dc have different lengths') power_dc = sum(p_dc) power_ac = 0. * power_dc for vdc, pdc in zip(v_dc, p_dc): power_ac += pdc / power_dc * _sandia_eff(vdc, power_dc, inverter) return _sandia_limits(power_ac, power_dc, inverter['Paco'], inverter['Pnt'], inverter['Pso'])
[docs] def adr(v_dc, p_dc, inverter, vtol=0.10): r''' Converts DC power and voltage to AC power using Anton Driesse's grid-connected inverter efficiency model. Parameters ---------- v_dc : numeric DC voltage input to the inverter, should be >= 0. [V] p_dc : numeric DC power input to the inverter, should be >= 0. [W] inverter : dict-like Defines parameters for the inverter model in [1]_. See Notes for required model parameters. A parameter database is provided with pvlib and may be read using :py:func:`pvlib.pvsystem.retrieve_sam`. vtol : numeric, default 0.1 Fraction of DC voltage that determines how far the efficiency model is extrapolated beyond the inverter's normal input voltage operating range. 0.0 <= vtol <= 1.0. [unitless] Returns ------- power_ac : numeric AC power output. [W] Notes ----- Determines the AC power output of an inverter given the DC voltage and DC power. Output AC power is bounded above by the parameter ``Pacmax``, to represent inverter "clipping". AC power is bounded below by ``-Pnt`` (negative when power is consumed rather than produced) which represents self-consumption. `power_ac` is not adjusted for maximum power point tracking (MPPT) voltage windows or maximum current limits of the inverter. Required model parameters are: ================ ========================================================== Column Description ================ ========================================================== Pnom Nominal DC power, typically the DC power needed to produce maximum AC power output. [W] Vnom Nominal DC input voltage. Typically the level at which the highest efficiency is achieved. [V] Vmax Maximum DC input voltage. [V] Vmin Minimum DC input voltage. [V] Vdcmax Maximum voltage supplied from DC array. [V] MPPTHi Maximum DC voltage for MPPT range. [V] MPPTLow Minimum DC voltage for MPPT range. [V] Pacmax Maximum AC output power, used to clip the output power if needed. [W] ADRCoefficients A list of 9 coefficients that capture the influence of input voltage and power on inverter losses, and thereby efficiency. Corresponds to terms from [1]_ (in order): :math: `b_{0,0}, b_{1,0}, b_{2,0}, b_{0,1}, b_{1,1}, b_{2,1}, b_{0,2}, b_{1,2}, b_{2,2}`. See [1]_ for the use of each coefficient and its associated unit. Pnt AC power consumed by inverter at night (night tare) to maintain circuitry required to sense the PV array voltage. [W] ================ ========================================================== AC power output is set to NaN where the input DC voltage exceeds a limit M = max(Vmax, Vdcmax, MPPTHi) x (1 + vtol), and where the input DC voltage is less than a limit m = max(Vmin, MPPTLow) x (1 - vtol) References ---------- .. [1] A. Driesse, "Beyond the Curves: Modeling the Electrical Efficiency of Photovoltaic Inverters", 33rd IEEE Photovoltaic Specialist Conference (PVSC), June 2008 See also -------- pvlib.inverter.sandia pvlib.pvsystem.retrieve_sam ''' p_nom = inverter['Pnom'] v_nom = inverter['Vnom'] pac_max = inverter['Pacmax'] p_nt = inverter['Pnt'] ce_list = inverter['ADRCoefficients'] v_max = inverter['Vmax'] v_min = inverter['Vmin'] vdc_max = inverter['Vdcmax'] mppt_hi = inverter['MPPTHi'] mppt_low = inverter['MPPTLow'] v_lim_upper = float(np.nanmax([v_max, vdc_max, mppt_hi]) * (1 + vtol)) v_lim_lower = float(np.nanmax([v_min, mppt_low]) * (1 - vtol)) pdc = p_dc / p_nom vdc = v_dc / v_nom # zero voltage will lead to division by zero, but since power is # set to night time value later, these errors can be safely ignored with np.errstate(invalid='ignore', divide='ignore'): poly = np.array([pdc**0, # replace with np.ones_like? pdc, pdc**2, vdc - 1, pdc * (vdc - 1), pdc**2 * (vdc - 1), 1. / vdc - 1, # divide by 0 pdc * (1. / vdc - 1), # invalid 0./0. --> nan pdc**2 * (1. / vdc - 1)]) # divide by 0 p_loss = np.dot(np.array(ce_list), poly) power_ac = p_nom * (pdc - p_loss) p_nt = -1 * np.absolute(p_nt) # set output to nan where input is outside of limits # errstate silences case where input is nan with np.errstate(invalid='ignore'): invalid = (v_lim_upper < v_dc) | (v_dc < v_lim_lower) power_ac = np.where(invalid, np.nan, power_ac) # set night values power_ac = np.where(vdc == 0, p_nt, power_ac) power_ac = np.maximum(power_ac, p_nt) # set max ac output power_ac = np.minimum(power_ac, pac_max) if isinstance(p_dc, pd.Series): power_ac = pd.Series(power_ac, index=pdc.index) return power_ac
[docs] def pvwatts(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637): r""" NREL's PVWatts inverter model. The PVWatts inverter model [1]_ calculates inverter efficiency :math:`\eta` as a function of input DC power :math:`P_{dc}` .. math:: \eta = \frac{\eta_{nom}}{\eta_{ref}} (-0.0162\zeta - \frac{0.0059} {\zeta} + 0.9858) where :math:`\zeta=P_{dc}/P_{dc0}` and :math:`P_{dc0}=P_{ac0}/\eta_{nom}`. Output AC power is then given by .. math:: P_{ac} = \min(\eta P_{dc}, P_{ac0}) Parameters ---------- pdc : numeric DC power. Same unit as ``pdc0``. pdc0: numeric DC input limit of the inverter. Same unit as ``pdc``. eta_inv_nom: numeric, default 0.96 Nominal inverter efficiency. [unitless] eta_inv_ref: numeric, default 0.9637 Reference inverter efficiency. PVWatts defines it to be 0.9637 and is included here for flexibility. [unitless] Returns ------- power_ac: numeric AC power. Same unit as ``pdc0``. Notes ----- When sourcing ``pdc`` from pvlib functions (e.g. :py:func:`pvlib.pvsystem.pvwatts_dc`) their DC power output is in W, and ``pdc0`` should have the same unit (W). Note that ``pdc0`` is also used as a symbol in :py:func:`pvlib.pvsystem.pvwatts_dc`. ``pdc0`` in this function refers to the DC power input limit of the inverter. ``pdc0`` in :py:func:`pvlib.pvsystem.pvwatts_dc` refers to the DC power of the modules at reference conditions. See Also -------- pvlib.inverter.pvwatts_multi References ---------- .. [1] A. P. Dobos, "PVWatts Version 5 Manual," http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf (2014). """ pac0 = eta_inv_nom * pdc0 zeta = pdc / pdc0 # arrays to help avoid divide by 0 for scalar and array eta = np.zeros_like(pdc, dtype=float) pdc_neq_0 = ~np.equal(pdc, 0) # eta < 0 if zeta < 0.006. power_ac is forced to be >= 0 below. GH 541 # In some published versions of [1] the parentheses are missing eta = eta_inv_nom / eta_inv_ref * ( -0.0162 * zeta - np.divide(0.0059, zeta, out=eta, where=pdc_neq_0) + 0.9858) # noQA: W503 power_ac = eta * pdc power_ac = np.minimum(pac0, power_ac) power_ac = np.maximum(0, power_ac) # GH 541 return power_ac
[docs] def pvwatts_multi(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637): r""" Extend NREL's PVWatts inverter model for multiple MPP inputs. DC input power is summed over MPP inputs to obtain the DC power input to the PVWatts inverter model. See :py:func:`pvlib.inverter.pvwatts` for details. Parameters ---------- pdc : tuple, list or array of numeric DC power on each MPPT input of the inverter. If type is array, must be 2d with axis 0 being the MPPT inputs. Same unit as ``pdc0``. pdc0: numeric Total DC power limit of the inverter. Same unit as ``pdc``. eta_inv_nom: numeric, default 0.96 Nominal inverter efficiency. [unitless] eta_inv_ref: numeric, default 0.9637 Reference inverter efficiency. PVWatts defines it to be 0.9637 and is included here for flexibility. [unitless] Returns ------- power_ac: numeric AC power. Same unit as ``pdc0``. See Also -------- pvlib.inverter.pvwatts """ return pvwatts(sum(pdc), pdc0, eta_inv_nom, eta_inv_ref)
[docs] 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. Parameters ---------- ac_power : array_like AC power output at each data point [W]. dc_power : array_like DC power input at each data point [W]. dc_voltage : array_like DC input voltage at each data point [V]. dc_voltage_level : array_like 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 : float Night tare, i.e., power consumed while inverter is not delivering AC power. [W] Returns ------- 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 -------- pvlib.inverter.sandia Notes ----- 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] 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( [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 # 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, fit a line to (DC voltage, model_coefficient) def extract_c(x_d, add): beta0, beta1 = polyfit(x_d, add, 1) c = beta1 / beta0 return beta0, beta1, c for d in voltage_levels: 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) # [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.loc[d, 'a'] = a coeffs.loc[d, 'p_dc'] = p_dc coeffs.loc[d, 'p_s0'] = 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}