diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst index a0460c5991..9c93af7475 100644 --- a/docs/sphinx/source/api.rst +++ b/docs/sphinx/source/api.rst @@ -265,10 +265,21 @@ Low-level functions for solving the single diode equation. singlediode.bishop88_v_from_i singlediode.bishop88_mpp -SAPM model ----------- +Inverter models (DC to AC conversion) +------------------------------------- + +.. autosummary:: + :toctree: generated/ + + inverter.sandia + inverter.adr + inverter.pvwatts -Functions relevant for the SAPM model. +PV System Models +---------------- + +Sandia array performance model (SAPM) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. autosummary:: :toctree: generated/ @@ -277,28 +288,27 @@ Functions relevant for the SAPM model. pvsystem.sapm_effective_irradiance pvsystem.sapm_spectral_loss pvsystem.sapm_aoi_loss - pvsystem.snlinverter - pvsystem.adrinverter + inverter.sandia temperature.sapm_cell Pvsyst model -------------- - -Functions relevant for the Pvsyst model. +^^^^^^^^^^^^ .. autosummary:: :toctree: generated/ temperature.pvsyst_cell + pvsystem.calcparams_pvsyst + pvsystem.singlediode PVWatts model -------------- +^^^^^^^^^^^^^ .. autosummary:: :toctree: generated/ pvsystem.pvwatts_dc - pvsystem.pvwatts_ac + inverter.pvwatts pvsystem.pvwatts_losses Functions for fitting diode models diff --git a/docs/sphinx/source/introtutorial.rst b/docs/sphinx/source/introtutorial.rst index 2ad8c967c4..31b6cd71a9 100644 --- a/docs/sphinx/source/introtutorial.rst +++ b/docs/sphinx/source/introtutorial.rst @@ -96,7 +96,7 @@ to accomplish our system modeling goal: total_irrad['poa_direct'], total_irrad['poa_diffuse'], am_abs, aoi, module) dc = pvlib.pvsystem.sapm(effective_irradiance, tcell, module) - ac = pvlib.pvsystem.snlinverter(dc['v_mp'], dc['p_mp'], inverter) + ac = pvlib.inverter.sandia(dc['v_mp'], dc['p_mp'], inverter) annual_energy = ac.sum() energies[name] = annual_energy diff --git a/docs/sphinx/source/whatsnew/v0.8.0.rst b/docs/sphinx/source/whatsnew/v0.8.0.rst index fbe51a6a19..f901add383 100644 --- a/docs/sphinx/source/whatsnew/v0.8.0.rst +++ b/docs/sphinx/source/whatsnew/v0.8.0.rst @@ -3,15 +3,25 @@ v0.8.0 (Month day, year) ------------------------- +API Changes with Deprecations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +* Moved functions related to inverters from ``pvsystem.py`` to ``inverter.py``. + Functions are renamed to follow a more consistent pattern, as follows (:pull:`886`): + - :py:func:`pvlib.pvsystem.snlinverter` is now :py:func:`pvlib.inverter.sandia` + - :py:func:`pvlib.pvsystem.pvwatts_ac` is now :py:func:`pvlib.inverter.pvwatts` + - :py:func:`pvlib.pvsystem.adrinverter` is now :py:func:`pvlib.inverter.adr` +* Argument ``ac_model`` for :py:class:`pvlib.modelchain.ModelChain` now accepts + ``'sandia'``, ``'pvwatts'`` and ``'adr'`` for the inverter models. (:pull:`886`) + API Changes ~~~~~~~~~~~ -* Removed ``run_parallel_calculations`` and ``n_workers_for_parallel_calcs`` +* Removed ``run_parallel_calculations`` and ``n_workers_for_parallel_calcs`` from :py:func:`pvlib.bifacial.pvfactors_timeseries` inputs (:issue:`902`)(:pull:`934`) Enhancements ~~~~~~~~~~~~ * Update :func:`~pvlib.bifacial.pvfactors_timeseries` to run with ``pvfactors`` v1.4.1 (:issue:`902`)(:pull:`934`) -* Add :py:func:`pvlib.iam.marion_diffuse` and +* Add :py:func:`pvlib.iam.marion_diffuse` and :py:func:`pvlib.iam.marion_integrate` to calculate IAM values for diffuse irradiance. (:pull:`984`) @@ -23,6 +33,8 @@ Testing ~~~~~~~ * Decorator :py:func:`pvlib.conftest.fail_on_pvlib_version` can now be applied to functions that require args or kwargs. (:pull:`973`) +* Test added for :py:class:`pvlib.modelchain.ModelChain` to confirm ValueError when + ``ac_model`` is an invalid string. (:pull:`886`) Documentation ~~~~~~~~~~~~~ @@ -32,6 +44,8 @@ Documentation * Clarify units for heat loss factors in :py:func:`pvlib.temperature.pvsyst_cell` and :py:func:`pvlib.temperature.faiman`. (:pull:`960`) +* Corrected key names for :py:func:`pvlib.inverter.sandia`. (:issue:`976`, + :pull:`886`) * Add a transposition gain example to the gallery. (:pull:`979`) * Add a gallery example of calculating diffuse IAM using :py:func:`pvlib.iam.marion_diffuse`. (:pull:`984`) diff --git a/docs/tutorials/forecast.ipynb b/docs/tutorials/forecast.ipynb index 580ca840f4..853e7f2c58 100644 --- a/docs/tutorials/forecast.ipynb +++ b/docs/tutorials/forecast.ipynb @@ -8159,7 +8159,7 @@ " solar_position_method: nrel_numpy\n", " airmass_model: kastenyoung1989\n", " dc_model: sapm\n", - " ac_model: snlinverter\n", + " ac_model: sandia\n", " aoi_model: sapm_aoi_loss\n", " spectral_model: sapm_spectral_loss\n", " temp_model: sapm_temp\n", diff --git a/docs/tutorials/forecast_to_power.ipynb b/docs/tutorials/forecast_to_power.ipynb index 153bf81297..0abdd2dff9 100644 --- a/docs/tutorials/forecast_to_power.ipynb +++ b/docs/tutorials/forecast_to_power.ipynb @@ -67,7 +67,7 @@ "import matplotlib as mpl\n", "\n", "# finally, we import the pvlib library\n", - "from pvlib import solarposition,irradiance,atmosphere,pvsystem\n", + "from pvlib import solarposition, irradiance, atmosphere, pvsystem, inverter\n", "from pvlib.forecast import GFS, NAM, NDFD, RAP, HRRR" ] }, @@ -876,7 +876,7 @@ } ], "source": [ - "p_ac = pvsystem.snlinverter(sapm_out.v_mp, sapm_out.p_mp, sapm_inverter)\n", + "p_ac = inverter.sandia(sapm_out.v_mp, sapm_out.p_mp, sapm_inverter)\n", "\n", "p_ac.plot()\n", "plt.ylabel('AC Power (W)')\n", diff --git a/docs/tutorials/pvsystem.ipynb b/docs/tutorials/pvsystem.ipynb index d75c024fd5..aff47be7e4 100644 --- a/docs/tutorials/pvsystem.ipynb +++ b/docs/tutorials/pvsystem.ipynb @@ -16,7 +16,7 @@ "1. [systemdef](#systemdef)\n", "2. [Angle of Incidence Modifiers](#Angle-of-Incidence-Modifiers)\n", "2. [Sandia Cell Temp correction](#Sandia-Cell-Temp-correction)\n", - "2. [Sandia Inverter Model](#snlinverter)\n", + "2. [Sandia Inverter Model](#Sandia-inverter-model)\n", "2. [Sandia Array Performance Model](#SAPM)\n", " 1. [SAPM IV curves](#SAPM-IV-curves)\n", "2. [DeSoto Model](#desoto)\n", @@ -78,7 +78,7 @@ "outputs": [], "source": [ "import pvlib\n", - "from pvlib import pvsystem" + "from pvlib import pvsystem, inverter" ] }, { @@ -573,7 +573,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### snlinverter" + "### Sandia-inverter-model" ] }, { @@ -1356,7 +1356,7 @@ "idcs = pd.Series(np.linspace(0,11,110))\n", "pdcs = idcs * vdcs\n", "\n", - "pacs = pvsystem.snlinverter(vdcs, pdcs, inverters['ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_'])\n", + "pacs = inverter.sandia(vdcs, pdcs, inverters['ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_'])\n", "#pacs.plot()\n", "plt.plot(pacs, pdcs)\n", "plt.ylabel('ac power')\n", diff --git a/docs/tutorials/tmy_to_power.ipynb b/docs/tutorials/tmy_to_power.ipynb index 4f817afa71..b4cbfcdb20 100644 --- a/docs/tutorials/tmy_to_power.ipynb +++ b/docs/tutorials/tmy_to_power.ipynb @@ -1184,8 +1184,8 @@ ], "source": [ "p_acs = pd.DataFrame()\n", - "p_acs['sapm'] = pvlib.pvsystem.snlinverter(sapm_out.v_mp, sapm_out.p_mp, sapm_inverter)\n", - "p_acs['sd'] = pvlib.pvsystem.snlinverter(single_diode_out.v_mp, single_diode_out.p_mp, sapm_inverter)\n", + "p_acs['sapm'] = pvlib.inverter.sandia(sapm_out.v_mp, sapm_out.p_mp, sapm_inverter)\n", + "p_acs['sd'] = pvlib.inverter.sandia(single_diode_out.v_mp, single_diode_out.p_mp, sapm_inverter)\n", "\n", "p_acs.plot()\n", "plt.ylabel('AC Power (W)')" diff --git a/pvlib/inverter.py b/pvlib/inverter.py new file mode 100644 index 0000000000..514be06e88 --- /dev/null +++ b/pvlib/inverter.py @@ -0,0 +1,308 @@ +# -*- coding: utf-8 -*- +""" +This module contains functions for inverter modeling, primarily conversion of +DC to AC power. +""" + +import numpy as np +import pandas as pd + + +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 to inverter, typically assumed to be equal + to the PV array maximum power. [W] + Vdco DC voltage at which the AC power rating is achieved + at the reference operating condition. [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'] + Pdco = inverter['Pdco'] + Vdco = inverter['Vdco'] + Pso = inverter['Pso'] + C0 = inverter['C0'] + C1 = inverter['C1'] + C2 = inverter['C2'] + C3 = inverter['C3'] + Pnt = inverter['Pnt'] + + A = Pdco * (1 + C1 * (v_dc - Vdco)) + B = Pso * (1 + C2 * (v_dc - Vdco)) + C = C0 * (1 + C3 * (v_dc - Vdco)) + + power_ac = (Paco / (A - B) - C * (A - B)) * (p_dc - B) + C * (p_dc - B)**2 + power_ac = np.minimum(Paco, power_ac) + power_ac = np.where(p_dc < Pso, -1.0 * abs(Pnt), power_ac) + + if isinstance(p_dc, pd.Series): + power_ac = pd.Series(power_ac, index=p_dc.index) + + return power_ac + + +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] Driesse, A. "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 + + +def pvwatts(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637): + r""" + Implements NREL's PVWatts inverter model. + + The PVWatts inverter model [1]_ calculates inverter efficiency :math:`\eta` + as a function of input DC power + + .. 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 + ----- + 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. + + 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 + 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 diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 7f91271eea..0d8e816879 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -10,8 +10,8 @@ import warnings import pandas as pd -from pvlib import (atmosphere, clearsky, pvsystem, solarposition, temperature, - tools) +from pvlib import (atmosphere, clearsky, inverter, pvsystem, solarposition, + temperature, tools) from pvlib.tracking import SingleAxisTracker import pvlib.irradiance # avoid name conflict with full import from pvlib.pvsystem import _DC_MODEL_PARAMS @@ -56,8 +56,8 @@ def basic_chain(times, latitude, longitude, See temperature.sapm_cell for details. inverter_parameters : None, dict or Series - Inverter parameters as defined by the CEC. See pvsystem.snlinverter for - details. + Inverter parameters as defined by the CEC. See + :py:func:`inverter.sandia` for details. irradiance : None or DataFrame, default None If None, calculates clear sky data. @@ -183,7 +183,7 @@ def basic_chain(times, latitude, longitude, dc = pvsystem.sapm(effective_irradiance, cell_temperature, module_parameters) - ac = pvsystem.snlinverter(dc['v_mp'], dc['p_mp'], inverter_parameters) + ac = inverter.sandia(dc['v_mp'], dc['p_mp'], inverter_parameters) return dc, ac @@ -265,7 +265,7 @@ class ModelChain(object): ac_model: None, str, or function, default None If None, the model will be inferred from the contents of system.inverter_parameters and system.module_parameters. Valid - strings are 'snlinverter', 'adrinverter', 'pvwatts'. The + strings are 'sandia', 'adr', 'pvwatts'. The ModelChain instance will be passed as the first argument to a user-defined function. @@ -493,9 +493,20 @@ def ac_model(self, model): self._ac_model = self.infer_ac_model() elif isinstance(model, str): model = model.lower() - if model == 'snlinverter': + # TODO in v0.9: remove 'snlinverter', 'adrinverter' + if model in ['sandia', 'snlinverter']: + if model == 'snlinverter': + warnings.warn("ac_model = 'snlinverter' is deprecated and" + " will be removed in v0.9; use" + " ac_model = 'sandia' instead.", + pvlibDeprecationWarning) self._ac_model = self.snlinverter - elif model == 'adrinverter': + elif model in ['adr', 'adrinverter']: + if model == 'adrinverter': + warnings.warn("ac_model = 'adrinverter' is deprecated and" + " will be removed in v0.9; use" + " ac_model = 'adr' instead.", + pvlibDeprecationWarning) self._ac_model = self.adrinverter elif model == 'pvwatts': self._ac_model = self.pvwatts_inverter diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index c0866cbaa4..24a8f4b66e 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -13,8 +13,8 @@ from pvlib._deprecation import deprecated -from pvlib import (atmosphere, iam, irradiance, singlediode as _singlediode, - temperature) +from pvlib import (atmosphere, iam, inverter, irradiance, + singlediode as _singlediode, temperature) from pvlib.tools import _build_kwargs from pvlib.location import Location from pvlib._deprecation import pvlibDeprecationWarning @@ -727,15 +727,15 @@ def _infer_cell_type(self): def singlediode(self, photocurrent, saturation_current, resistance_series, resistance_shunt, nNsVth, ivcurve_pnts=None): - """Wrapper around the :py:func:`singlediode` function. + """Wrapper around the :py:func:`pvlib.pvsystem.singlediode` function. Parameters ---------- - See pvsystem.singlediode for details + See :py:func:`pvsystem.singlediode` for details Returns ------- - See pvsystem.singlediode for details + See :py:func:`pvsystem.singlediode` for details """ return singlediode(photocurrent, saturation_current, resistance_series, resistance_shunt, nNsVth, @@ -743,41 +743,51 @@ def singlediode(self, photocurrent, saturation_current, def i_from_v(self, resistance_shunt, resistance_series, nNsVth, voltage, saturation_current, photocurrent): - """Wrapper around the :py:func:`i_from_v` function. + """Wrapper around the :py:func:`pvlib.pvsystem.i_from_v` function. Parameters ---------- - See pvsystem.i_from_v for details + See :py:func:`pvsystem.i_from_v` for details Returns ------- - See pvsystem.i_from_v for details + See :py:func:`pvsystem.i_from_v` for details """ return i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, saturation_current, photocurrent) # inverter now specified by self.inverter_parameters def snlinverter(self, v_dc, p_dc): - """Uses :func:`snlinverter` to calculate AC power based on - ``self.inverter_parameters`` and the input parameters. + """Uses :py:func:`pvlib.inverter.sandia` to calculate AC power based on + ``self.inverter_parameters`` and the input voltage and power. Parameters ---------- - See pvsystem.snlinverter for details + See :py:func:`pvlib.inverter.sandia` for details Returns ------- - See pvsystem.snlinverter for details + See :py:func:`pvlib.inverter.sandia` for details """ - return snlinverter(v_dc, p_dc, self.inverter_parameters) + return inverter.sandia(v_dc, p_dc, self.inverter_parameters) def adrinverter(self, v_dc, p_dc): - return adrinverter(v_dc, p_dc, self.inverter_parameters) + """Uses :py:func:`pvlib.inverter.adr` to calculate AC power based on + ``self.inverter_parameters`` and the input voltage and power. + + Parameters + ---------- + See :py:func:`pvlib.inverter.adr` for details + + Returns + ------- + See :py:func:`pvlib.inverter.adr` for details + """ + return inverter.adr(v_dc, p_dc, self.inverter_parameters) def scale_voltage_current_power(self, data): """ - Scales the voltage, current, and power of the DataFrames - returned by :py:func:`singlediode` and :py:func:`sapm` + Scales the voltage, current, and power of the `data` DataFrame by `self.modules_per_string` and `self.strings_per_inverter`. Parameters @@ -799,10 +809,10 @@ def scale_voltage_current_power(self, data): def pvwatts_dc(self, g_poa_effective, temp_cell): """ Calcuates DC power according to the PVWatts model using - :py:func:`pvwatts_dc`, `self.module_parameters['pdc0']`, and - `self.module_parameters['gamma_pdc']`. + :py:func:`pvlib.pvsystem.pvwatts_dc`, `self.module_parameters['pdc0']`, + and `self.module_parameters['gamma_pdc']`. - See :py:func:`pvwatts_dc` for details. + See :py:func:`pvlib.pvsystem.pvwatts_dc` for details. """ kwargs = _build_kwargs(['temp_ref'], self.module_parameters) @@ -814,9 +824,10 @@ def pvwatts_dc(self, g_poa_effective, temp_cell): def pvwatts_losses(self): """ Calculates DC power losses according the PVwatts model using - :py:func:`pvwatts_losses` and ``self.losses_parameters``.` + :py:func:`pvlib.pvsystem.pvwatts_losses` and + ``self.losses_parameters``. - See :py:func:`pvwatts_losses` for details. + See :py:func:`pvlib.pvsystem.pvwatts_losses` for details. """ kwargs = _build_kwargs(['soiling', 'shading', 'snow', 'mismatch', 'wiring', 'connections', 'lid', @@ -827,15 +838,16 @@ def pvwatts_losses(self): def pvwatts_ac(self, pdc): """ Calculates AC power according to the PVWatts model using - :py:func:`pvwatts_ac`, `self.module_parameters['pdc0']`, and - `eta_inv_nom=self.inverter_parameters['eta_inv_nom']`. + :py:func:`pvlib.inverter.pvwatts`, `self.module_parameters["pdc0"]`, + and `eta_inv_nom=self.inverter_parameters["eta_inv_nom"]`. - See :py:func:`pvwatts_ac` for details. + See :py:func:`pvlib.inverter.pvwatts` for details. """ kwargs = _build_kwargs(['eta_inv_nom', 'eta_inv_ref'], self.inverter_parameters) - return pvwatts_ac(pdc, self.inverter_parameters['pdc0'], **kwargs) + return inverter.pvwatts(pdc, self.inverter_parameters['pdc0'], + **kwargs) def localize(self, location=None, latitude=None, longitude=None, **kwargs): @@ -2387,249 +2399,6 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, return I -def snlinverter(v_dc, p_dc, inverter): - r''' - Converts DC power and voltage to AC power using Sandia's - Grid-Connected PV Inverter model. - - Determines the AC power output of an inverter given the DC voltage, - DC power, and appropriate Sandia Grid-Connected Photovoltaic - Inverter Model parameters. The output, ac_power, is clipped at the - maximum power output, and gives a negative power during low-input - power conditions, but does NOT account for maximum power point - tracking voltage windows nor maximum current or voltage limits on - the inverter. - - Parameters - ---------- - v_dc : numeric - DC voltages, in volts, which are provided as input to the - inverter. Vdc must be >= 0. - - p_dc : numeric - A scalar or DataFrame of DC powers, in watts, which are provided - as input to the inverter. Pdc must be >= 0. - - inverter : dict-like - A dict-like object defining the inverter to be used, giving the - inverter performance parameters according to the Sandia - Grid-Connected Photovoltaic Inverter Model (SAND 2007-5036) [1]_. - A set of inverter performance parameters are provided with - pvlib, or may be generated from a System Advisor Model (SAM) [2]_ - library using retrievesam. See Notes for required keys. - - Returns - ------- - ac_power : numeric - Modeled AC power output given the input DC voltage, Vdc, and - input DC power, Pdc. When ac_power would be greater than Pac0, - it is set to Pac0 to represent inverter "clipping". When - ac_power would be less than Ps0 (startup power required), then - ac_power is set to -1*abs(Pnt) to represent nightly power - losses. ac_power is not adjusted for maximum power point - tracking (MPPT) voltage windows or maximum current limits of the - inverter. - - Notes - ----- - - Required inverter keys are: - - ====== ============================================================ - Column Description - ====== ============================================================ - Pac0 AC-power output from inverter based on input power - and voltage (W) - Pdc0 DC-power input to inverter, typically assumed to be equal - to the PV array maximum power (W) - Vdc0 DC-voltage level at which the AC-power rating is achieved - at the reference operating condition (V) - Ps0 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, default value of zero gives a - linear relationship (1/W) - C1 Empirical coefficient allowing Pdco to vary linearly - with dc-voltage input, default value is zero (1/V) - C2 Empirical coefficient allowing Pso to vary linearly with - dc-voltage input, default value is zero (1/V) - C3 Empirical coefficient allowing Co to vary linearly with - dc-voltage input, default value is zero (1/V) - Pnt AC-power consumed by inverter at night (night tare) to - maintain circuitry required to sense PV array voltage (W) - ====== ============================================================ - - References - ---------- - .. [1] SAND2007-5036, "Performance Model for Grid-Connected - Photovoltaic Inverters by D. King, S. Gonzalez, G. Galbraith, W. - Boyson - - .. [2] System Advisor Model web page. https://sam.nrel.gov. - - See also - -------- - sapm - singlediode - ''' - - Paco = inverter['Paco'] - Pdco = inverter['Pdco'] - Vdco = inverter['Vdco'] - Pso = inverter['Pso'] - C0 = inverter['C0'] - C1 = inverter['C1'] - C2 = inverter['C2'] - C3 = inverter['C3'] - Pnt = inverter['Pnt'] - - A = Pdco * (1 + C1*(v_dc - Vdco)) - B = Pso * (1 + C2*(v_dc - Vdco)) - C = C0 * (1 + C3*(v_dc - Vdco)) - - ac_power = (Paco/(A-B) - C*(A-B)) * (p_dc-B) + C*((p_dc-B)**2) - ac_power = np.minimum(Paco, ac_power) - ac_power = np.where(p_dc < Pso, -1.0 * abs(Pnt), ac_power) - - if isinstance(p_dc, pd.Series): - ac_power = pd.Series(ac_power, index=p_dc.index) - - return ac_power - - -def adrinverter(v_dc, p_dc, inverter, vtol=0.10): - r''' - Converts DC power and voltage to AC power using Anton Driesse's - Grid-Connected PV Inverter efficiency model - - Parameters - ---------- - v_dc : numeric - A scalar or pandas series of DC voltages, in volts, which are provided - as input to the inverter. If Vdc and Pdc are vectors, they must be - of the same size. v_dc must be >= 0. (V) - - p_dc : numeric - A scalar or pandas series of DC powers, in watts, which are provided - as input to the inverter. If Vdc and Pdc are vectors, they must be - of the same size. p_dc must be >= 0. (W) - - inverter : dict-like - A dict-like object defining the inverter to be used, giving the - inverter performance parameters according to the model - developed by Anton Driesse [1]. - A set of inverter performance parameters may be loaded from the - supplied data table using retrievesam. - See Notes for required keys. - - vtol : numeric, default 0.1 - A unit-less fraction that determines how far the efficiency model is - allowed to extrapolate beyond the inverter's normal input voltage - operating range. 0.0 <= vtol <= 1.0 - - Returns - ------- - ac_power : numeric - A numpy array or pandas series of modeled AC power output given the - input DC voltage, v_dc, and input DC power, p_dc. When ac_power would - be greater than pac_max, it is set to p_max to represent inverter - "clipping". When ac_power would be less than -p_nt (energy consumed - rather than produced) then ac_power is set to -p_nt to represent - nightly power losses. ac_power is not adjusted for maximum power point - tracking (MPPT) voltage windows or maximum current limits of the - inverter. - - Notes - ----- - - Required inverter keys are: - - ======= ============================================================ - Column Description - ======= ============================================================ - p_nom The nominal power value used to normalize all power values, - typically the DC power needed to produce maximum AC power - output, (W). - - v_nom The nominal DC voltage value used to normalize DC voltage - values, typically the level at which the highest efficiency - is achieved, (V). - - pac_max The maximum AC output power value, used to clip the output - if needed, (W). - - ce_list This is a list of 9 coefficients that capture the influence - of input voltage and power on inverter losses, and thereby - efficiency. - - p_nt ac-power consumed by inverter at night (night tare) to - maintain circuitry required to sense PV array voltage, (W). - ======= ============================================================ - - References - ---------- - .. [1] Beyond the Curves: Modeling the Electrical Efficiency - of Photovoltaic Inverters, PVSC 2008, Anton Driesse et. al. - - See also - -------- - sapm - singlediode - ''' - - 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) - ac_power = 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) - ac_power = np.where(invalid, np.nan, ac_power) - - # set night values - ac_power = np.where(vdc == 0, p_nt, ac_power) - ac_power = np.maximum(ac_power, p_nt) - - # set max ac output - ac_power = np.minimum(ac_power, pac_max) - - if isinstance(p_dc, pd.Series): - ac_power = pd.Series(ac_power, index=pdc.index) - - return ac_power - - def scale_voltage_current_power(data, voltage=1, current=1): """ Scales the voltage, current, and power of the DataFrames @@ -2672,10 +2441,11 @@ def pvwatts_dc(g_poa_effective, temp_cell, pdc0, gamma_pdc, temp_ref=25.): P_{dc} = \frac{G_{poa eff}}{1000} P_{dc0} ( 1 + \gamma_{pdc} (T_{cell} - T_{ref})) - Note that the pdc0 is also used as a symbol in :py:func:`pvwatts_ac`. pdc0 - in this function refers to the DC power of the modules at reference - conditions. pdc0 in :py:func:`pvwatts_ac` refers to the DC power input - limit of the inverter. + Note that the pdc0 is also used as a symbol in + :py:func:`pvlib.inverter.pvwatts`. pdc0 in this function refers to the DC + power of the modules at reference conditions. pdc0 in + :py:func:`pvlib.inverter.pvwatts` refers to the DC power input limit of + the inverter. Parameters ---------- @@ -2767,70 +2537,6 @@ def pvwatts_losses(soiling=2, shading=3, snow=0, mismatch=2, wiring=2, return losses -def pvwatts_ac(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637): - r""" - Implements NREL's PVWatts inverter model. - The PVWatts inverter model [1]_ is: - - .. math:: - - \eta = \frac{\eta_{nom}}{\eta_{ref}} (-0.0162\zeta - \frac{0.0059}{\zeta} + 0.9858) - - .. math:: - - P_{ac} = \min(\eta P_{dc}, P_{ac0}) - - where :math:`\zeta=P_{dc}/P_{dc0}` and :math:`P_{dc0}=P_{ac0}/\eta_{nom}`. - - Note that the pdc0 is also used as a symbol in :py:func:`pvwatts_dc`. pdc0 - in this function refers to the DC power input limit of the inverter. - pdc0 in :py:func:`pvwatts_dc` refers to the DC power of the modules - at reference conditions. - - Parameters - ---------- - pdc: numeric - DC power. - pdc0: numeric - DC input limit of the inverter. - eta_inv_nom: numeric, default 0.96 - Nominal inverter efficiency. - eta_inv_ref: numeric, default 0.9637 - Reference inverter efficiency. PVWatts defines it to be 0.9637 - and is included here for flexibility. - - Returns - ------- - pac: numeric - AC power. - - 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. pac is forced to be >= 0 below. GH 541 - eta = eta_inv_nom / eta_inv_ref * ( - - 0.0162*zeta - - np.divide(0.0059, zeta, out=eta, where=pdc_neq_0) - + 0.9858) - - pac = eta * pdc - pac = np.minimum(pac0, pac) - pac = np.maximum(0, pac) # GH 541 - - return pac - - ashraeiam = deprecated('0.7', alternative='iam.ashrae', name='ashraeiam', removal='0.8')(iam.ashrae) @@ -2841,3 +2547,15 @@ def pvwatts_ac(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637): sapm_aoi_loss = deprecated('0.7', alternative='iam.sapm', name='sapm_aoi_loss', removal='0.8')(iam.sapm) + + +snlinverter = deprecated('0.8', alternative='inverter.sandia', + name='snlinverter', removal='0.9')(inverter.sandia) + + +adrinverter = deprecated('0.8', alternative='inverter.adr', name='adrinverter', + removal='0.9')(inverter.adr) + + +pvwatts_ac = deprecated('0.8', alternative='inverter.pvwatts', + name='pvwatts_ac', removal='0.9')(inverter.pvwatts) diff --git a/pvlib/tests/conftest.py b/pvlib/tests/conftest.py index 29b4549f9a..e88b2b8ae7 100644 --- a/pvlib/tests/conftest.py +++ b/pvlib/tests/conftest.py @@ -205,6 +205,33 @@ def pvsyst_module_params(): return parameters +@pytest.fixture(scope='function') +def adr_inverter_parameters(): + """ + Define some ADR inverter parameters for testing. + + The scope of the fixture is set to ``'function'`` to allow tests to modify + parameters if required without affecting other tests. + """ + parameters = { + 'Name': 'Ablerex Electronics Co., Ltd.: ES 2200-US-240 (240Vac)' + '[CEC 2011]', + 'Vac': 240., + 'Pacmax': 2110., + 'Pnom': 2200., + 'Vnom': 396., + 'Vmin': 155., + 'Vmax': 413., + 'Vdcmax': 500., + 'MPPTHi': 450., + 'MPPTLow': 150., + 'Pnt': 0.25, + 'ADRCoefficients': [0.01385, 0.0152, 0.00794, 0.00286, -0.01872, + -0.01305, 0.0, 0.0, 0.0] + } + return parameters + + @pytest.fixture(scope='function') def cec_inverter_parameters(): """ diff --git a/pvlib/tests/test_inverter.py b/pvlib/tests/test_inverter.py new file mode 100644 index 0000000000..4f99f0a616 --- /dev/null +++ b/pvlib/tests/test_inverter.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- + +import numpy as np +import pandas as pd + +from pandas.util.testing import assert_series_equal +from numpy.testing import assert_allclose + +from pvlib import inverter +from conftest import needs_numpy_1_10 + + +def test_adr(adr_inverter_parameters): + vdcs = pd.Series([135, 154, 390, 420, 551]) + pdcs = pd.Series([135, 1232, 1170, 420, 551]) + + pacs = inverter.adr(vdcs, pdcs, adr_inverter_parameters) + assert_series_equal(pacs, pd.Series([np.nan, 1161.5745, 1116.4459, + 382.6679, np.nan])) + + +def test_adr_vtol(adr_inverter_parameters): + vdcs = pd.Series([135, 154, 390, 420, 551]) + pdcs = pd.Series([135, 1232, 1170, 420, 551]) + + pacs = inverter.adr(vdcs, pdcs, adr_inverter_parameters, vtol=0.20) + assert_series_equal(pacs, pd.Series([104.8223, 1161.5745, 1116.4459, + 382.6679, 513.3385])) + + +def test_adr_float(adr_inverter_parameters): + vdcs = 154. + pdcs = 1232. + + pacs = inverter.adr(vdcs, pdcs, adr_inverter_parameters) + assert_allclose(pacs, 1161.5745) + + +def test_adr_invalid_and_night(sam_data): + # also tests if inverter.adr can read the output from pvsystem.retrieve_sam + inverters = sam_data['adrinverter'] + testinv = 'Zigor__Sunzet_3_TL_US_240V__CEC_2011_' + vdcs = np.array([39.873036, 0., np.nan, 420]) + pdcs = np.array([188.09182, 0., 420, np.nan]) + + pacs = inverter.adr(vdcs, pdcs, inverters[testinv]) + assert_allclose(pacs, np.array([np.nan, -0.25, np.nan, np.nan])) + + +def test_sandia(cec_inverter_parameters): + vdcs = pd.Series(np.linspace(0, 50, 3)) + idcs = pd.Series(np.linspace(0, 11, 3)) + pdcs = idcs * vdcs + + pacs = inverter.sandia(vdcs, pdcs, cec_inverter_parameters) + assert_series_equal(pacs, pd.Series([-0.020000, 132.004308, 250.000000])) + + +def test_sandia_float(cec_inverter_parameters): + vdcs = 25. + idcs = 5.5 + pdcs = idcs * vdcs + + pacs = inverter.sandia(vdcs, pdcs, cec_inverter_parameters) + assert_allclose(pacs, 132.004278, 5) + + +def test_sandia_Pnt_micro(): + """ + Test for issue #140, where some microinverters were giving a positive AC + power output when the DC power was 0. + """ + inverter_parameters = { + 'Name': 'Enphase Energy: M250-60-2LL-S2x (-ZC) (-NA) 208V [CEC 2013]', + 'Vac': 208.0, + 'Paco': 240.0, + 'Pdco': 250.5311318, + 'Vdco': 32.06160667, + 'Pso': 1.12048857, + 'C0': -5.76E-05, + 'C1': -6.24E-04, + 'C2': 8.09E-02, + 'C3': -0.111781106, + 'Pnt': 0.043, + 'Vdcmax': 48.0, + 'Idcmax': 9.8, + 'Mppt_low': 27.0, + 'Mppt_high': 39.0, + } + vdcs = pd.Series(np.linspace(0, 50, 3)) + idcs = pd.Series(np.linspace(0, 11, 3)) + pdcs = idcs * vdcs + + pacs = inverter.sandia(vdcs, pdcs, inverter_parameters) + assert_series_equal(pacs, pd.Series([-0.043, 132.545914746, 240.0])) + + +def test_pvwatts_scalars(): + expected = 85.58556604752516 + out = inverter.pvwatts(90, 100, 0.95) + assert_allclose(out, expected) + # GH 675 + expected = 0. + out = inverter.pvwatts(0., 100) + assert_allclose(out, expected) + + +def test_pvwatts_possible_negative(): + # pvwatts could return a negative value for (pdc / pdc0) < 0.006 + # unless it is clipped. see GH 541 for more + expected = 0 + out = inverter.pvwatts(0.001, 1) + assert_allclose(out, expected) + + +@needs_numpy_1_10 +def test_pvwatts_arrays(): + pdc = np.array([[np.nan], [0], [50], [100]]) + pdc0 = 100 + expected = np.array([[np.nan], + [0.], + [47.60843624], + [95.]]) + out = inverter.pvwatts(pdc, pdc0, 0.95) + assert_allclose(out, expected, equal_nan=True) + + +def test_pvwatts_series(): + pdc = pd.Series([np.nan, 0, 50, 100]) + pdc0 = 100 + expected = pd.Series(np.array([np.nan, 0., 47.608436, 95.])) + out = inverter.pvwatts(pdc, pdc0, 0.95) + assert_series_equal(expected, out) diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index f846f036aa..1ebe806a5b 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -448,21 +448,21 @@ def acdc(mc): @pytest.mark.parametrize('ac_model', [ - 'snlinverter', pytest.param('adrinverter', marks=requires_scipy), - 'pvwatts']) + 'sandia', pytest.param('adr', marks=requires_scipy), 'pvwatts']) def test_ac_models(sapm_dc_snl_ac_system, cec_dc_adr_ac_system, pvwatts_dc_pvwatts_ac_system, location, ac_model, weather, mocker): - ac_systems = {'snlinverter': sapm_dc_snl_ac_system, - 'adrinverter': cec_dc_adr_ac_system, + ac_systems = {'sandia': sapm_dc_snl_ac_system, + 'adr': cec_dc_adr_ac_system, 'pvwatts': pvwatts_dc_pvwatts_ac_system} + ac_method_name = {'sandia': 'snlinverter', + 'adr': 'adrinverter', + 'pvwatts': 'pvwatts_ac'} system = ac_systems[ac_model] mc = ModelChain(system, location, ac_model=ac_model, aoi_model='no_loss', spectral_model='no_loss') - if ac_model == 'pvwatts': - ac_model += '_ac' - m = mocker.spy(system, ac_model) + m = mocker.spy(system, ac_method_name[ac_model]) mc.run_model(weather) assert m.call_count == 1 assert isinstance(mc.ac, pd.Series) @@ -470,6 +470,21 @@ def test_ac_models(sapm_dc_snl_ac_system, cec_dc_adr_ac_system, assert mc.ac[1] < 1 +# TODO in v0.9: remove this test for a deprecation warning +@pytest.mark.parametrize('ac_model', [ + 'snlinverter', pytest.param('adrinverter', marks=requires_scipy)]) +def test_ac_models_deprecated(sapm_dc_snl_ac_system, cec_dc_adr_ac_system, + location, ac_model, weather): + ac_systems = {'snlinverter': sapm_dc_snl_ac_system, + 'adrinverter': cec_dc_adr_ac_system} + system = ac_systems[ac_model] + warn_txt = "ac_model = '" + ac_model + "' is deprecated and will be" +\ + " removed in v0.9" + with pytest.warns(pvlibDeprecationWarning, match=warn_txt): + ModelChain(system, location, ac_model=ac_model, + aoi_model='no_loss', spectral_model='no_loss') + + def test_ac_model_user_func(pvwatts_dc_pvwatts_ac_system, location, weather, mocker): m = mocker.spy(sys.modules[__name__], 'acdc') @@ -481,6 +496,14 @@ def test_ac_model_user_func(pvwatts_dc_pvwatts_ac_system, location, weather, assert not mc.ac.empty +def test_ac_model_not_a_model(pvwatts_dc_pvwatts_ac_system, location, weather): + exc_text = 'not a valid AC power model' + with pytest.raises(ValueError, match=exc_text): + ModelChain(pvwatts_dc_pvwatts_ac_system, location, + ac_model='not_a_model', aoi_model='no_loss', + spectral_model='no_loss') + + def constant_aoi_loss(mc): mc.aoi_modifier = 0.9 @@ -666,22 +689,38 @@ def test_deprecated_08(): warn_txt = 'temp_model keyword argument is deprecated' with pytest.warns(pvlibDeprecationWarning, match=warn_txt): ModelChain(system, location, dc_model='desoto', aoi_model='no_loss', - spectral_model='no_loss', ac_model='snlinverter', + spectral_model='no_loss', ac_model='sandia', temp_model='sapm') # provide both temp_model and temperature_model kwargs warn_txt = 'Provide only one of temperature_model' with pytest.warns(pvlibDeprecationWarning, match=warn_txt): ModelChain(system, location, dc_model='desoto', aoi_model='no_loss', - spectral_model='no_loss', ac_model='snlinverter', + spectral_model='no_loss', ac_model='sandia', temperature_model='sapm', temp_model='sapm') # conflicting temp_model and temperature_model kwargs exc_text = 'Conflicting temperature_model' with pytest.raises(ValueError, match=exc_text): ModelChain(system, location, dc_model='desoto', aoi_model='no_loss', - spectral_model='no_loss', ac_model='snlinverter', + spectral_model='no_loss', ac_model='sandia', temperature_model='pvsyst', temp_model='sapm') +@fail_on_pvlib_version('0.9') +@pytest.mark.parametrize('ac_model', ['snlinverter', 'adrinverter']) +def test_deprecated_09(sapm_dc_snl_ac_system, cec_dc_adr_ac_system, + location, ac_model, weather): + # ModelChain.ac_model = 'snlinverter' or 'adrinverter' deprecated in v0.8, + # removed in v0.9 + ac_systems = {'snlinverter': sapm_dc_snl_ac_system, + 'adrinverter': cec_dc_adr_ac_system} + system = ac_systems[ac_model] + warn_txt = "ac_model = '" + ac_model + "' is deprecated and will be" +\ + " removed in v0.9" + with pytest.warns(pvlibDeprecationWarning, match=warn_txt): + ModelChain(system, location, ac_model=ac_model, + aoi_model='no_loss', spectral_model='no_loss') + + @requires_scipy def test_basic_chain_required(sam_data, cec_inverter_parameters, sapm_temperature_cs5p_220m): diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index 442dc51b5a..81aca6cf9e 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -8,7 +8,7 @@ from pandas.util.testing import assert_series_equal, assert_frame_equal from numpy.testing import assert_allclose -from pvlib import pvsystem +from pvlib import inverter, pvsystem from pvlib import atmosphere from pvlib import iam as _iam from pvlib.location import Location @@ -1101,60 +1101,6 @@ def test_PVSystem_scale_voltage_current_power(mocker): m.assert_called_once_with(data, voltage=2, current=3) -def test_adrinverter(sam_data): - inverters = sam_data['adrinverter'] - testinv = 'Ablerex_Electronics_Co___Ltd___' \ - 'ES_2200_US_240__240_Vac__240V__CEC_2011_' - vdcs = pd.Series([135, 154, 390, 420, 551]) - pdcs = pd.Series([135, 1232, 1170, 420, 551]) - - pacs = pvsystem.adrinverter(vdcs, pdcs, inverters[testinv]) - assert_series_equal(pacs, pd.Series([np.nan, 1161.5745, 1116.4459, - 382.6679, np.nan])) - - -def test_adrinverter_vtol(sam_data): - inverters = sam_data['adrinverter'] - testinv = 'Ablerex_Electronics_Co___Ltd___' \ - 'ES_2200_US_240__240_Vac__240V__CEC_2011_' - vdcs = pd.Series([135, 154, 390, 420, 551]) - pdcs = pd.Series([135, 1232, 1170, 420, 551]) - - pacs = pvsystem.adrinverter(vdcs, pdcs, inverters[testinv], vtol=0.20) - assert_series_equal(pacs, pd.Series([104.8223, 1161.5745, 1116.4459, - 382.6679, 513.3385])) - - -def test_adrinverter_float(sam_data): - inverters = sam_data['adrinverter'] - testinv = 'Ablerex_Electronics_Co___Ltd___' \ - 'ES_2200_US_240__240_Vac__240V__CEC_2011_' - vdcs = 154. - pdcs = 1232. - - pacs = pvsystem.adrinverter(vdcs, pdcs, inverters[testinv]) - assert_allclose(pacs, 1161.5745) - - -def test_adrinverter_invalid_and_night(sam_data): - inverters = sam_data['adrinverter'] - testinv = 'Zigor__Sunzet_3_TL_US_240V__CEC_2011_' - vdcs = np.array([39.873036, 0., np.nan, 420]) - pdcs = np.array([188.09182, 0., 420, np.nan]) - - pacs = pvsystem.adrinverter(vdcs, pdcs, inverters[testinv]) - assert_allclose(pacs, np.array([np.nan, -0.25, np.nan, np.nan])) - - -def test_snlinverter(cec_inverter_parameters): - vdcs = pd.Series(np.linspace(0,50,3)) - idcs = pd.Series(np.linspace(0,11,3)) - pdcs = idcs * vdcs - - pacs = pvsystem.snlinverter(vdcs, pdcs, cec_inverter_parameters) - assert_series_equal(pacs, pd.Series([-0.020000, 132.004308, 250.000000])) - - def test_PVSystem_snlinverter(cec_inverter_parameters): system = pvsystem.PVSystem( inverter=cec_inverter_parameters['Name'], @@ -1168,45 +1114,6 @@ def test_PVSystem_snlinverter(cec_inverter_parameters): assert_series_equal(pacs, pd.Series([-0.020000, 132.004308, 250.000000])) -def test_snlinverter_float(cec_inverter_parameters): - vdcs = 25. - idcs = 5.5 - pdcs = idcs * vdcs - - pacs = pvsystem.snlinverter(vdcs, pdcs, cec_inverter_parameters) - assert_allclose(pacs, 132.004278, 5) - - -def test_snlinverter_Pnt_micro(): - """ - Test for issue #140, where some microinverters were giving a positive AC - power output when the DC power was 0. - """ - inverter_parameters = { - 'Name': 'Enphase Energy: M250-60-2LL-S2x (-ZC) (-NA) 208V [CEC 2013]', - 'Vac': 208.0, - 'Paco': 240.0, - 'Pdco': 250.5311318, - 'Vdco': 32.06160667, - 'Pso': 1.12048857, - 'C0': -5.76E-05, - 'C1': -6.24E-04, - 'C2': 8.09E-02, - 'C3': -0.111781106, - 'Pnt': 0.043, - 'Vdcmax': 48.0, - 'Idcmax': 9.8, - 'Mppt_low': 27.0, - 'Mppt_high': 39.0, - } - vdcs = pd.Series(np.linspace(0,50,3)) - idcs = pd.Series(np.linspace(0,11,3)) - pdcs = idcs * vdcs - - pacs = pvsystem.snlinverter(vdcs, pdcs, inverter_parameters) - assert_series_equal(pacs, pd.Series([-0.043, 132.545914746, 240.0])) - - def test_PVSystem_creation(): pv_system = pvsystem.PVSystem(module='blah', inverter='blarg') # ensure that parameter attributes are dict-like. GH 294 @@ -1348,44 +1255,6 @@ def test_pvwatts_dc_series(): assert_series_equal(expected, out) -def test_pvwatts_ac_scalars(): - expected = 85.58556604752516 - out = pvsystem.pvwatts_ac(90, 100, 0.95) - assert_allclose(out, expected) - # GH 675 - expected = 0. - out = pvsystem.pvwatts_ac(0., 100) - assert_allclose(out, expected) - - -def test_pvwatts_ac_possible_negative(): - # pvwatts_ac could return a negative value for (pdc / pdc0) < 0.006 - # unless it is clipped. see GH 541 for more - expected = 0 - out = pvsystem.pvwatts_ac(0.001, 1) - assert_allclose(out, expected) - - -@needs_numpy_1_10 -def test_pvwatts_ac_arrays(): - pdc = np.array([[np.nan], [0], [50], [100]]) - pdc0 = 100 - expected = np.array([[nan], - [0.], - [47.60843624], - [95.]]) - out = pvsystem.pvwatts_ac(pdc, pdc0, 0.95) - assert_allclose(out, expected, equal_nan=True) - - -def test_pvwatts_ac_series(): - pdc = pd.Series([np.nan, 0, 50, 100]) - pdc0 = 100 - expected = pd.Series(np.array([nan, 0., 47.608436, 95.])) - out = pvsystem.pvwatts_ac(pdc, pdc0, 0.95) - assert_series_equal(expected, out) - - def test_pvwatts_losses_default(): expected = 14.075660688264469 out = pvsystem.pvwatts_losses() @@ -1459,22 +1328,22 @@ def test_PVSystem_pvwatts_losses(mocker): def test_PVSystem_pvwatts_ac(mocker): - mocker.spy(pvsystem, 'pvwatts_ac') + mocker.spy(inverter, 'pvwatts') system = make_pvwatts_system_defaults() pdc = 50 out = system.pvwatts_ac(pdc) - pvsystem.pvwatts_ac.assert_called_once_with(pdc, - **system.inverter_parameters) + inverter.pvwatts.assert_called_once_with(pdc, + **system.inverter_parameters) assert out < pdc def test_PVSystem_pvwatts_ac_kwargs(mocker): - mocker.spy(pvsystem, 'pvwatts_ac') + mocker.spy(inverter, 'pvwatts') system = make_pvwatts_system_kwargs() pdc = 50 out = system.pvwatts_ac(pdc) - pvsystem.pvwatts_ac.assert_called_once_with(pdc, - **system.inverter_parameters) + inverter.pvwatts.assert_called_once_with(pdc, + **system.inverter_parameters) assert out < pdc @@ -1564,3 +1433,16 @@ def test__sapm_celltemp_translator(): [params['a'], params['b'], params['deltaT']]) assert_allclose(result, 43.509, 3) + + +@fail_on_pvlib_version('0.9') +def test_deprecated_09(cec_inverter_parameters, adr_inverter_parameters): + # deprecated function pvsystem.snlinverter + with pytest.warns(pvlibDeprecationWarning): + pvsystem.snlinverter(250, 40, cec_inverter_parameters) + # deprecated function pvsystem.adrinverter + with pytest.warns(pvlibDeprecationWarning): + pvsystem.adrinverter(1232, 154, adr_inverter_parameters) + # deprecated function pvsystem.spvwatts_ac + with pytest.warns(pvlibDeprecationWarning): + pvsystem.pvwatts_ac(90, 100, 0.95)