Repository: pvlib/pvlib-python
Pull request #886 (2020-02-11 22:11:15)

Create inverter.py
 - [x] I am familiar with the [contributing guidelines](https://pvlib-python.readthedocs.io/en/latest/contributing.html)
 - [x] Tests added
 - [x] Updates entries to [`docs/sphinx/source/api.rst`](https://github.com/pvlib/pvlib-python/blob/master/docs/sphinx/source/api.rst) for API changes.
 - [x] Adds description and name entries in the appropriate "what's new" file in [`docs/sphinx/source/whatsnew`](https://github.com/pvlib/pvlib-python/tree/master/docs/sphinx/source/whatsnew) for all changes. Includes link to the GitHub Issue with `` :issue:`num` `` or this Pull Request with `` :pull:`num` ``. Includes contributor name and/or GitHub username (link with `` :ghuser:`user` ``).
 - [x] New code is fully documented. Includes [numpydoc](https://numpydoc.readthedocs.io/en/latest/format.html) compliant docstrings, examples, and comments where necessary.
 - [x] Pull request is nearly complete and ready for detailed review.
 - [x] Maintainer: Appropriate GitHub Labels and Milestone are assigned to the Pull Request and linked Issue.

Creates `inverter.py`
- `pvsystem.snlinverter` to `inverter.sandia`
- `pvsystem.adrinverter` to `inverter.adr`
- `pvsystem.pvwatts_ac` to `inverter.pvwatts`

 

adriesse: I fully support the new module, but I wonder about the naming.  Perhaps 'efficiency' or 'power' should be part of it, since that's what the current functions model?  I can imagine inverter-related functions that model other aspects.
cwhanse: Test failures are unrelated (NREL server not responding), otherwise I think I've addressed the review comments (thanks @kanderso-nrel )
kanderso-nrel: @cwhanse Did you see that GitHub collapsed several of the review comments in this main thread (`11 hidden conversations`)?  Not all of them were important, I just want to make sure they didn't get overlooked.  In particular, one of the docstring tables isn't rendering on RTD.
cwhanse: > @cwhanse Did you see that GitHub collapsed several of the review comments in this main thread (11 hidden conversations)?  

No, I overlooked that. Will take care of it shortly.

adriesse: Would you anticipate putting DC/DC converters, optimizers and/or charge controllers in a different module?  They have some things in common.
cwhanse: > Would you anticipate putting DC/DC converters, optimizers and/or charge controllers in a different module?  They have some things in common.

I haven't considered that. I suppose these functions could go in `inverter.py`, there isn't a better place at the moment. Do you think the module name is an issue?

We decided not to create `losses.py`. But that doesn't preclude e.g. `dc_losses.py` where wiring loss modules could be deployed, among other functions. 

How would you deploy inverter, DC/DC converter or optimizer and DC-side loss functions?
adriesse: Names are always an issue, but we can't live without them. :)  You could call the module `powerconversion`.  Then it would seem like home for inverters, converters and transformers.  Or simply `electrical` and you could put wiring in there too.  If you want them bundled, that is.
cwhanse: > Names are always an issue, but we can't live without them. :)  You could call the module powerconversion.  Then it would seem like home for inverters, converters and transformers.  Or simply electrical and you could put wiring in there too.  If you want them bundled, that is.

Do you know of performance models for devices other than inverters that are more involved than, say, a factor applied to DC or AC power to account for efficiency or internal consumption?

In the case of DC-DC converters or optimizers, I could envision a function sitting between the PV module function output (IV curves) and the input to the inverter model, but I'm not aware of published models (and I haven't diligently looked).

Maybe we move forward with `inverter.py` with its stated scope, which should cover most of the common uses. Although not acknowledged by the name `inverter` I think functions related to MPPT could be put in this module also.

adriesse: All the active electronics have a control aspect to them, which hasn't been considered much in system simulation so far.   I just wanted to open the field a bit, but go ahead as you deem best.
cwhanse: @kanderso-nrel can you see if the NSRDB PSM3 data service is offline?  Looks that way to me.
kanderso-nrel: I'm getting tests passing except for `get_psm3_tmy` too.  On first glance, it seems like a few of the data columns have changed for that TMY dataset, possibly the date labeling too.  Not sure what to make of that -- I had the impression that `tmy-2017` was static and should never change.  I'll open an issue on the NREL github to see if this was an unintentional change. 
cwhanse: > git grep -n snlinverter shows a handful of docs files still referencing pvsystem.snlinverter.  I checked the other two and didn't see any offenders.

Many thanks for checking this. I've made these corrections where the function is referenced; other instances are references to `PVSystem` and `ModelChain` methods that aren't being renamed in this PR.
cwhanse: > docs/tutorials/pvsystem.ipynb:576:    "### snlinverter"

What does the ### formatting do? I assumed this was just an internal label or link.
kanderso-nrel: > > docs/tutorials/pvsystem.ipynb:576:    "### snlinverter"
> 
> What does the ### formatting do? I assumed this was just an internal label or link.

That's inside a markdown cell, just making a small header:

![image](https://user-images.githubusercontent.com/57452607/85881443-d7b48500-b79a-11ea-9d3c-2c9f02e212b0.png)

It's not code so nbd to leave as-is. 
cwhanse: @wholmgren ok to merge?

Patch:
diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst
index a0460c5..9c93af7 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 2ad8c96..31b6cd7 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 fbe51a6..f901add 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 580ca84..853e7f2 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 153bf81..0abdd2d 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 d75c024..aff47be 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 4f817af..b4cbfcd 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 0000000..514be06
--- /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 7f91271..0d8e816 100644
--- a/pvlib/modelchain.py
+++ b/pvlib/modelchain.py
@@ -10,8 +10,8 @@ from functools import partial
 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 @@ class ModelChain(object):
             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 c0866cb..24a8f4b 100644
--- a/pvlib/pvsystem.py
+++ b/pvlib/pvsystem.py
@@ -13,8 +13,8 @@ import pandas as pd
 
 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 @@ class PVSystem(object):
     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 @@ class PVSystem(object):
 
     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 @@ class PVSystem(object):
     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 @@ class PVSystem(object):
     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 @@ class PVSystem(object):
     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 @@ physicaliam = deprecated('0.7', alternative='iam.physical', name='physicaliam',
 
 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)


Test patch:
diff --git a/pvlib/tests/conftest.py b/pvlib/tests/conftest.py
index 29b4549..e88b2b8 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 0000000..4f99f0a
--- /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 f846f03..1ebe806 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 442dc51..81aca6c 100644
--- a/pvlib/tests/test_pvsystem.py
+++ b/pvlib/tests/test_pvsystem.py
@@ -8,7 +8,7 @@ import pytest
 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)