Repository: pvlib/pvlib-python (python)

Issue:
reorder function arguments to put variables first and parameters second
I think we should reorder a handful of function arguments to put the most variable-like arguments first and most parameter-like arguments last. I'm guessing that a lot people will think that I'm just screwing with the API for no reason, but I do have a few reasons!

1. consistency is important (and underrated)
2. we can make better use of default arguments. 

Here are the functions that I know about so far:

```python
ashraeiam(b, aoi) ---> ashraeiam(aoi, b=0.05)

# updated to reflect Cliff's comment below
physicaliam(K, L, n, aoi) ---> physicaliam(aoi, n=1.526, K=4., L=0.002)

# see #200
singlediode(module, photocurrent, saturation_current, resistance_series, resistance_shunt, nNsVth) --->
singlediode(photocurrent, saturation_current, resistance_series, resistance_shunt, nNsVth)

# I just noticed that the matlab sapm api is very different. # see #198
sapm(module, poa_direct, poa_diffuse, temp_cell, airmass_absolute, aoi) ---> 
sapm(poa_direct, poa_diffuse, temp_cell, airmass_absolute, aoi, module)

snlinverter(inverter, v_dc, p_dc) ---> snlinverter(v_dc, p_dc, inverter)
```

Pull request description:
improve function argument ordering in pvsystem
This PR improves some function signatures as discussed in #197.

Also addresses the input/output type issues described #201 for ashraeiam, physicaliam, and snlinverter.

Adds test_tools.py. 

I'll merge tomorrow unless there are comments.

Closes #197. 

Patch:
diff --git a/docs/sphinx/source/package_overview.rst b/docs/sphinx/source/package_overview.rst
index c9e733d..35eb00a 100644
--- a/docs/sphinx/source/package_overview.rst
+++ b/docs/sphinx/source/package_overview.rst
@@ -105,10 +105,10 @@ to accomplish our system modeling goal:
         temps = pvlib.pvsystem.sapm_celltemp(total_irrad['poa_global'],
                                              wind_speed, temp_air)
         effective_irradiance = pvlib.pvsystem.sapm_effective_irradiance(
-            module, total_irrad['poa_direct'], total_irrad['poa_diffuse'],
-            am_abs, aoi)
-        dc = pvlib.pvsystem.sapm(module, effective_irradiance, temps['temp_cell'])
-        ac = pvlib.pvsystem.snlinverter(inverter, dc['v_mp'], dc['p_mp'])
+            total_irrad['poa_direct'], total_irrad['poa_diffuse'],
+            am_abs, aoi, module)
+        dc = pvlib.pvsystem.sapm(effective_irradiance, temps['temp_cell'], module)
+        ac = pvlib.pvsystem.snlinverter(dc['v_mp'], dc['p_mp'], inverter)
         annual_energy = ac.sum()
         energies[name] = annual_energy
 
diff --git a/docs/sphinx/source/whatsnew/v0.4.0.txt b/docs/sphinx/source/whatsnew/v0.4.0.txt
index 03eaa8a..fd71ee7 100644
--- a/docs/sphinx/source/whatsnew/v0.4.0.txt
+++ b/docs/sphinx/source/whatsnew/v0.4.0.txt
@@ -19,6 +19,9 @@ API Changes
   in addition to arrays and Series. Furthermore, these functions no
   longer promote scalar or array input to Series output.
   Also applies to atmosphere.relativeairmass. (:issue:`201`, :issue:`214`)
+* Reorder the ashraeiam, physicaliam, and snlinverter arguments to put
+  the most variable arguments first. Adds default arguments for the IAM
+  functions. (:issue:`197`)
 * The irradiance.extraradiation function input/output type consistency
   across different methods has been dramatically improved.
   (:issue:`217`, :issue:`219`)
diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py
index 161da93..a797301 100644
--- a/pvlib/modelchain.py
+++ b/pvlib/modelchain.py
@@ -170,13 +170,13 @@ def basic_chain(times, latitude, longitude,
                                    weather['temp_air'])
 
     effective_irradiance = pvsystem.sapm_effective_irradiance(
-        module_parameters, total_irrad['poa_direct'],
-        total_irrad['poa_diffuse'], airmass, aoi)
+        total_irrad['poa_direct'], total_irrad['poa_diffuse'], airmass, aoi,
+        module_parameters)
 
-    dc = pvsystem.sapm(module_parameters, effective_irradiance,
-                       temps['temp_cell'])
+    dc = pvsystem.sapm(effective_irradiance, temps['temp_cell'],
+                       module_parameters)
 
-    ac = pvsystem.snlinverter(inverter_parameters, dc['v_mp'], dc['p_mp'])
+    ac = pvsystem.snlinverter(dc['v_mp'], dc['p_mp'], inverter_parameters)
 
     return dc, ac
 
diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py
index 812c745..70b6526 100644
--- a/pvlib/pvsystem.py
+++ b/pvlib/pvsystem.py
@@ -16,6 +16,7 @@ import numpy as np
 import pandas as pd
 
 from pvlib import tools
+from pvlib.tools import _build_kwargs
 from pvlib.location import Location
 from pvlib import irradiance, atmosphere
 
@@ -227,6 +228,8 @@ class PVSystem(object):
         ``self.module_parameters['b']``, ``aoi``,
         and the :py:func:`ashraeiam` function.
 
+        Uses default arguments if keys not in module_parameters.
+
         Parameters
         ----------
         aoi : numeric
@@ -237,30 +240,34 @@ class PVSystem(object):
         modifier : numeric
             The AOI modifier.
         """
-        b = self.module_parameters['b']
-        return ashraeiam(b, aoi)
+        kwargs = _build_kwargs(['b'], self.module_parameters)
+
+        return ashraeiam(aoi, **kwargs)
 
     def physicaliam(self, aoi):
         """
-        Determine the incidence angle modifier using
+        Determine the incidence angle modifier using ``aoi``,
         ``self.module_parameters['K']``,
         ``self.module_parameters['L']``,
         ``self.module_parameters['n']``,
-        ``aoi``, and the
+        and the
         :py:func:`physicaliam` function.
 
+        Uses default arguments if keys not in module_parameters.
+
         Parameters
         ----------
-        See pvsystem.physicaliam for details
+        aoi : numeric
+            The angle of incidence in degrees.
 
         Returns
         -------
-        See pvsystem.physicaliam for details
+        modifier : numeric
+            The AOI modifier.
         """
-        K = self.module_parameters['K']
-        L = self.module_parameters['L']
-        n = self.module_parameters['n']
-        return physicaliam(K, L, n, aoi)
+        kwargs = _build_kwargs(['K', 'L', 'n'], self.module_parameters)
+
+        return physicaliam(aoi, **kwargs)
 
     def calcparams_desoto(self, poa_global, temp_cell, **kwargs):
         """
@@ -319,7 +326,7 @@ class PVSystem(object):
         -------
         See pvsystem.sapm for details
         """
-        return sapm(self.module_parameters, effective_irradiance, temp_cell)
+        return sapm(effective_irradiance, temp_cell, self.module_parameters)
 
     def sapm_celltemp(self, irrad, wind, temp):
         """Uses :py:func:`sapm_celltemp` to calculate module and cell
@@ -351,7 +358,7 @@ class PVSystem(object):
         F1 : numeric
             The SAPM spectral loss coefficient.
         """
-        return sapm_spectral_loss(self.module_parameters, airmass_absolute)
+        return sapm_spectral_loss(airmass_absolute, self.module_parameters)
 
     def sapm_aoi_loss(self, aoi):
         """
@@ -368,7 +375,7 @@ class PVSystem(object):
         F2 : numeric
             The SAPM angle of incidence loss coefficient.
         """
-        return sapm_aoi_loss(self.module_parameters, aoi)
+        return sapm_aoi_loss(aoi, self.module_parameters)
 
     def sapm_effective_irradiance(self, poa_direct, poa_diffuse,
                                   airmass_absolute, aoi,
@@ -400,10 +407,9 @@ class PVSystem(object):
         effective_irradiance : numeric
             The SAPM effective irradiance.
         """
-        return sapm_effective_irradiance(self.module_parameters,
-                                         poa_direct, poa_diffuse,
-                                         airmass_absolute, aoi,
-                                         reference_irradiance)
+        return sapm_effective_irradiance(
+            poa_direct, poa_diffuse, airmass_absolute, aoi,
+            self.module_parameters, reference_irradiance=reference_irradiance)
 
     def singlediode(self, photocurrent, saturation_current,
                     resistance_series, resistance_shunt, nNsVth):
@@ -448,7 +454,7 @@ class PVSystem(object):
         -------
         See pvsystem.snlinverter for details
         """
-        return snlinverter(self.inverter_parameters, v_dc, p_dc)
+        return snlinverter(v_dc, p_dc, self.inverter_parameters)
 
     def scale_voltage_current_power(self, data):
         """
@@ -656,7 +662,7 @@ def systemdef(meta, surface_tilt, surface_azimuth, albedo, modules_per_string,
     return system
 
 
-def ashraeiam(b, aoi):
+def ashraeiam(aoi, b=0.05):
     '''
     Determine the incidence angle modifier using the ASHRAE transmission
     model.
@@ -671,27 +677,26 @@ def ashraeiam(b, aoi):
 
     Parameters
     ----------
+    aoi : numeric
+        The angle of incidence between the module normal vector and the
+        sun-beam vector in degrees.
+
     b : float
         A parameter to adjust the modifier as a function of angle of
         incidence. Typical values are on the order of 0.05 [3].
-    aoi : Series
-        The angle of incidence between the module normal vector and the
-        sun-beam vector in degrees.
 
     Returns
     -------
-    IAM : Series
-
+    IAM : numeric
         The incident angle modifier calculated as 1-b*(sec(aoi)-1) as
         described in [2,3].
 
-        Returns nan for all abs(aoi) >= 90 and for all IAM values
-        that would be less than 0.
+        Returns nan for all abs(aoi) >= 90 and for all IAM values that
+        would be less than 0.
 
     References
     ----------
-
-    [1] Souka A.F., Safwat H.H., "Determindation of the optimum
+    [1] Souka A.F., Safwat H.H., "Determination of the optimum
     orientations for the double exposure flat-plate collector and its
     reflections". Solar Energy vol .10, pp 170-174. 1966.
 
@@ -707,15 +712,18 @@ def ashraeiam(b, aoi):
     physicaliam
     '''
 
-    IAM = 1 - b*((1/np.cos(np.radians(aoi)) - 1))
+    iam = 1 - b*((1/np.cos(np.radians(aoi)) - 1))
+
+    iam = np.where(np.abs(aoi) >= 90, np.nan, iam)
+    iam = np.maximum(0, iam)
 
-    IAM[abs(aoi) >= 90] = np.nan
-    IAM[IAM < 0] = np.nan
+    if isinstance(iam, pd.Series):
+        iam = pd.Series(iam, index=aoi.index)
 
-    return IAM
+    return iam
 
 
-def physicaliam(K, L, n, aoi):
+def physicaliam(aoi, n=1.526, K=4., L=0.002):
     '''
     Determine the incidence angle modifier using refractive index,
     glazing thickness, and extinction coefficient
@@ -733,33 +741,33 @@ def physicaliam(K, L, n, aoi):
 
     Parameters
     ----------
-    K : float
+    aoi : numeric
+        The angle of incidence between the module normal vector and the
+        sun-beam vector in degrees.
+
+    n : numeric
+        The effective index of refraction (unitless). Reference [1]
+        indicates that a value of 1.526 is acceptable for glass. n must
+        be a numeric scalar or vector with all values >=0. If n is a
+        vector, it must be the same size as all other input vectors.
+
+    K : numeric
         The glazing extinction coefficient in units of 1/meters.
         Reference [1] indicates that a value of  4 is reasonable for
         "water white" glass. K must be a numeric scalar or vector with
         all values >=0. If K is a vector, it must be the same size as
         all other input vectors.
 
-    L : float
+    L : numeric
         The glazing thickness in units of meters. Reference [1]
         indicates that 0.002 meters (2 mm) is reasonable for most
         glass-covered PV panels. L must be a numeric scalar or vector
         with all values >=0. If L is a vector, it must be the same size
         as all other input vectors.
 
-    n : float
-        The effective index of refraction (unitless). Reference [1]
-        indicates that a value of 1.526 is acceptable for glass. n must
-        be a numeric scalar or vector with all values >=0. If n is a
-        vector, it must be the same size as all other input vectors.
-
-    aoi : Series
-        The angle of incidence between the module normal vector and the
-        sun-beam vector in degrees.
-
     Returns
     -------
-    IAM : float or Series
+    IAM : numeric
         The incident angle modifier as specified in eqns. 14-16 of [1].
         IAM is a column vector with the same number of elements as the
         largest input vector.
@@ -803,12 +811,14 @@ def physicaliam(K, L, n, aoi):
              ((tools.tand(thetar_deg0 - zeroang)) ** 2) /
              ((tools.tand(thetar_deg0 + zeroang)) ** 2))))))
 
-    IAM = tau / tau0
+    iam = tau / tau0
+
+    iam = np.where((np.abs(aoi) >= 90) | (iam < 0), np.nan, iam)
 
-    IAM[abs(aoi) >= 90] = np.nan
-    IAM[IAM < 0] = np.nan
+    if isinstance(aoi, pd.Series):
+        iam = pd.Series(iam, index=aoi.index)
 
-    return IAM
+    return iam
 
 
 def calcparams_desoto(poa_global, temp_cell, alpha_isc, module_parameters,
@@ -826,10 +836,10 @@ def calcparams_desoto(poa_global, temp_cell, alpha_isc, module_parameters,
 
     Parameters
     ----------
-    poa_global : float or Series
+    poa_global : numeric
         The irradiance (in W/m^2) absorbed by the module.
 
-    temp_cell : float or Series
+    temp_cell : numeric
         The average cell temperature of cells within a module in C.
 
     alpha_isc : float
@@ -869,7 +879,7 @@ def calcparams_desoto(poa_global, temp_cell, alpha_isc, module_parameters,
         condition (this may be useful if dEgdT is a function of
         temperature).
 
-    M : float or Series (optional, default=1)
+    M : numeric (optional, default=1)
         An optional airmass modifier, if omitted, M is given a value of
         1, which assumes absolute (pressure corrected) airmass = 1.5. In
         this code, M is equal to M/Mref as described in [1] (i.e. Mref
@@ -891,11 +901,11 @@ def calcparams_desoto(poa_global, temp_cell, alpha_isc, module_parameters,
     -------
     Tuple of the following results:
 
-    photocurrent : float or Series
+    photocurrent : numeric
         Light-generated current in amperes at irradiance=S and
         cell temperature=Tcell.
 
-    saturation_current : float or Series
+    saturation_current : numeric
         Diode saturation curent in amperes at irradiance
         S and cell temperature Tcell.
 
@@ -903,11 +913,11 @@ def calcparams_desoto(poa_global, temp_cell, alpha_isc, module_parameters,
         Series resistance in ohms at irradiance S and cell temperature
         Tcell.
 
-    resistance_shunt : float or Series
+    resistance_shunt : numeric
         Shunt resistance in ohms at irradiance S and cell temperature
         Tcell.
 
-    nNsVth : float or Series
+    nNsVth : numeric
         Modified diode ideality factor at irradiance S and cell
         temperature Tcell. Note that in source [1] nNsVth = a (equation
         2). nNsVth is the product of the usual diode ideality factor
@@ -1001,7 +1011,7 @@ def calcparams_desoto(poa_global, temp_cell, alpha_isc, module_parameters,
          Source: [4]
     '''
 
-    M = np.max(M, 0)
+    M = np.maximum(M, 0)
     a_ref = module_parameters['a_ref']
     IL_ref = module_parameters['I_L_ref']
     I0_ref = module_parameters['I_o_ref']
@@ -1151,7 +1161,7 @@ def _parse_raw_sam_df(csvdata):
     return df
 
 
-def sapm(module, effective_irradiance, temp_cell):
+def sapm(effective_irradiance, temp_cell, module):
     '''
     The Sandia PV Array Performance Model (SAPM) generates 5 points on a
     PV module's I-V curve (Voc, Isc, Ix, Ixx, Vmp/Imp) according to
@@ -1159,16 +1169,16 @@ def sapm(module, effective_irradiance, temp_cell):
 
     Parameters
     ----------
-    module : dict-like
-        A dict, Series, or DataFrame defining the SAPM performance
-        parameters. See the notes section for more details.
-
     effective_irradiance : numeric
         Effective irradiance (suns).
 
     temp_cell : numeric
         The cell temperature (degrees C).
 
+    module : dict-like
+        A dict, Series, or DataFrame defining the SAPM performance
+        parameters. See the notes section for more details.
+
     Returns
     -------
     A DataFrame with the columns:
@@ -1376,20 +1386,20 @@ def sapm_celltemp(poa_global, wind_speed, temp_air,
     return pd.DataFrame({'temp_cell': temp_cell, 'temp_module': temp_module})
 
 
-def sapm_spectral_loss(module, airmass_absolute):
+def sapm_spectral_loss(airmass_absolute, module):
     """
     Calculates the SAPM spectral loss coefficient, F1.
 
     Parameters
     ----------
+    airmass_absolute : numeric
+        Absolute airmass
+
     module : dict-like
         A dict, Series, or DataFrame defining the SAPM performance
         parameters. See the :py:func:`sapm` notes section for more
         details.
 
-    airmass_absolute : numeric
-        Absolute airmass
-
     Returns
     -------
     F1 : numeric
@@ -1413,21 +1423,21 @@ def sapm_spectral_loss(module, airmass_absolute):
     return spectral_loss
 
 
-def sapm_aoi_loss(module, aoi, upper=None):
+def sapm_aoi_loss(aoi, module, upper=None):
     """
     Calculates the SAPM angle of incidence loss coefficient, F2.
 
     Parameters
     ----------
+    aoi : numeric
+        Angle of incidence in degrees. Negative input angles will return
+        nan values.
+
     module : dict-like
         A dict, Series, or DataFrame defining the SAPM performance
         parameters. See the :py:func:`sapm` notes section for more
         details.
 
-    aoi : numeric
-        Angle of incidence in degrees. Negative input angles will return
-        nan values.
-
     upper : None or float
         Upper limit on the results.
 
@@ -1471,20 +1481,14 @@ def sapm_aoi_loss(module, aoi, upper=None):
     return aoi_loss
 
 
-def sapm_effective_irradiance(module, poa_direct, poa_diffuse,
-                              airmass_absolute, aoi,
-                              reference_irradiance=1000):
+def sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi,
+                              module, reference_irradiance=1000):
     """
     Calculates the SAPM effective irradiance using the SAPM spectral
     loss and SAPM angle of incidence loss functions.
 
     Parameters
     ----------
-    module : dict-like
-        A dict, Series, or DataFrame defining the SAPM performance
-        parameters. See the :py:func:`sapm` notes section for more
-        details.
-
     poa_direct : numeric
         The direct irradiance incident upon the module.
 
@@ -1497,6 +1501,11 @@ def sapm_effective_irradiance(module, poa_direct, poa_diffuse,
     aoi : numeric
         Angle of incidence in degrees.
 
+    module : dict-like
+        A dict, Series, or DataFrame defining the SAPM performance
+        parameters. See the :py:func:`sapm` notes section for more
+        details.
+
     reference_irradiance : numeric
         Reference irradiance by which to divide the input irradiance.
 
@@ -1506,8 +1515,8 @@ def sapm_effective_irradiance(module, poa_direct, poa_diffuse,
         The SAPM effective irradiance.
     """
 
-    F1 = sapm_spectral_loss(module, airmass_absolute)
-    F2 = sapm_aoi_loss(module, aoi)
+    F1 = sapm_spectral_loss(airmass_absolute, module)
+    F2 = sapm_aoi_loss(aoi, module)
 
     E0 = reference_irradiance
 
@@ -1754,15 +1763,15 @@ def v_from_i(resistance_shunt, resistance_series, nNsVth, current,
 
     Parameters
     ----------
-    resistance_shunt : float or Series
+    resistance_shunt : numeric
         Shunt resistance in ohms under desired IV curve conditions.
         Often abbreviated ``Rsh``.
 
-    resistance_series : float or Series
+    resistance_series : numeric
         Series resistance in ohms under desired IV curve conditions.
         Often abbreviated ``Rs``.
 
-    nNsVth : float or Series
+    nNsVth : numeric
         The product of three components. 1) The usual diode ideal factor
         (n), 2) the number of cells in series (Ns), and 3) the cell
         thermal voltage under the desired IV curve conditions (Vth). The
@@ -1771,14 +1780,14 @@ def v_from_i(resistance_shunt, resistance_series, nNsVth, current,
         temp_cell is the temperature of the p-n junction in Kelvin, and
         q is the charge of an electron (coulombs).
 
-    current : float or Series
+    current : numeric
         The current in amperes under desired IV curve conditions.
 
-    saturation_current : float or Series
+    saturation_current : numeric
         Diode saturation current in amperes under desired IV curve
         conditions. Often abbreviated ``I_0``.
 
-    photocurrent : float or Series
+    photocurrent : numeric
         Light-generated current (photocurrent) in amperes under desired
         IV curve conditions. Often abbreviated ``I_L``.
 
@@ -1820,15 +1829,15 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage,
 
     Parameters
     ----------
-    resistance_shunt : float or Series
+    resistance_shunt : numeric
         Shunt resistance in ohms under desired IV curve conditions.
         Often abbreviated ``Rsh``.
 
-    resistance_series : float or Series
+    resistance_series : numeric
         Series resistance in ohms under desired IV curve conditions.
         Often abbreviated ``Rs``.
 
-    nNsVth : float or Series
+    nNsVth : numeric
         The product of three components. 1) The usual diode ideal factor
         (n), 2) the number of cells in series (Ns), and 3) the cell
         thermal voltage under the desired IV curve conditions (Vth). The
@@ -1837,14 +1846,14 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage,
         temp_cell is the temperature of the p-n junction in Kelvin, and
         q is the charge of an electron (coulombs).
 
-    voltage : float or Series
+    voltage : numeric
         The voltage in Volts under desired IV curve conditions.
 
-    saturation_current : float or Series
+    saturation_current : numeric
         Diode saturation current in amperes under desired IV curve
         conditions. Often abbreviated ``I_0``.
 
-    photocurrent : float or Series
+    photocurrent : numeric
         Light-generated current (photocurrent) in amperes under desired
         IV curve conditions. Often abbreviated ``I_L``.
 
@@ -1880,7 +1889,7 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage,
     return I.real
 
 
-def snlinverter(inverter, v_dc, p_dc):
+def snlinverter(v_dc, p_dc, inverter):
     '''
     Converts DC power and voltage to AC power using Sandia's
     Grid-Connected PV Inverter model.
@@ -1895,7 +1904,15 @@ def snlinverter(inverter, v_dc, p_dc):
 
     Parameters
     ----------
-    inverter : dict or Series
+    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].
@@ -1931,16 +1948,9 @@ def snlinverter(inverter, v_dc, p_dc):
                  maintain circuitry required to sense PV array voltage (W)
         ======   ============================================================
 
-    v_dc : float or Series
-        DC voltages, in volts, which are provided as input to the
-        inverter. Vdc must be >= 0.
-    p_dc : float or Series
-        A scalar or DataFrame of DC powers, in watts, which are provided
-        as input to the inverter. Pdc must be >= 0.
-
     Returns
     -------
-    ac_power : float or Series
+    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
@@ -1978,15 +1988,12 @@ def snlinverter(inverter, v_dc, p_dc):
     B = Pso * (1 + C2*(v_dc - Vdco))
     C = C0 * (1 + C3*(v_dc - Vdco))
 
-    # ensures that function works with scalar or Series input
-    p_dc = pd.Series(p_dc)
-
     ac_power = (Paco/(A-B) - C*(A-B)) * (p_dc-B) + C*((p_dc-B)**2)
-    ac_power[ac_power > Paco] = Paco
-    ac_power[p_dc < Pso] = - 1.0 * abs(Pnt)
+    ac_power = np.minimum(Paco, ac_power)
+    ac_power = np.where(p_dc < Pso, -1.0 * abs(Pnt), ac_power)
 
-    if len(ac_power) == 1:
-        ac_power = ac_power.ix[0]
+    if isinstance(p_dc, pd.Series):
+        ac_power = pd.Series(ac_power, index=p_dc.index)
 
     return ac_power
 
diff --git a/pvlib/tools.py b/pvlib/tools.py
index bab3ab6..c779703 100644
--- a/pvlib/tools.py
+++ b/pvlib/tools.py
@@ -117,7 +117,6 @@ def localize_to_utc(time, location):
             time_utc = time.tz_localize(location.tz).tz_convert('UTC')
             pvl_logger.debug('tz_localize to %s and then tz_convert to UTC',
                              location.tz)
-
     return time_utc
 
 
@@ -232,3 +231,28 @@ def _array_out(input):
         output = input
 
     return output
+
+
+def _build_kwargs(keys, input_dict):
+    """
+    Parameters
+    ----------
+    keys : iterable
+        Typically a list of strings.
+    adict : dict-like
+        A dictionary from which to attempt to pull each key.
+
+    Returns
+    -------
+    kwargs : dict
+        A dictionary with only the keys that were in input_dict
+    """
+
+    kwargs = {}
+    for key in keys:
+        try:
+            kwargs[key] = input_dict[key]
+        except KeyError:
+            pass
+
+    return kwargs


Test patch:
diff --git a/pvlib/test/test_pvsystem.py b/pvlib/test/test_pvsystem.py
index 3c8aa08..12da350 100644
--- a/pvlib/test/test_pvsystem.py
+++ b/pvlib/test/test_pvsystem.py
@@ -92,42 +92,44 @@ def test_systemdef_dict():
     assert expected == pvsystem.systemdef(meta, 5, 0, .1, 5, 5)
 
 
+@needs_numpy_1_10
 def test_ashraeiam():
     thetas = np.linspace(-90, 90, 9)
-    iam = pvsystem.ashraeiam(.05, thetas)
+    iam = pvsystem.ashraeiam(thetas, .05)
     expected = np.array([        nan,  0.9193437 ,  0.97928932,  0.99588039,  1.        ,
         0.99588039,  0.97928932,  0.9193437 ,         nan])
-    assert np.isclose(iam, expected, equal_nan=True).all()
+    assert_allclose(iam, expected, equal_nan=True)
 
 
+@needs_numpy_1_10
 def test_PVSystem_ashraeiam():
     module_parameters = pd.Series({'b': 0.05})
-    system = pvsystem.PVSystem(module='blah', inverter='blarg',
-                               module_parameters=module_parameters)
+    system = pvsystem.PVSystem(module_parameters=module_parameters)
     thetas = np.linspace(-90, 90, 9)
     iam = system.ashraeiam(thetas)
     expected = np.array([        nan,  0.9193437 ,  0.97928932,  0.99588039,  1.        ,
         0.99588039,  0.97928932,  0.9193437 ,         nan])
-    assert np.isclose(iam, expected, equal_nan=True).all()
+    assert_allclose(iam, expected, equal_nan=True)
 
 
+@needs_numpy_1_10
 def test_physicaliam():
     thetas = np.linspace(-90, 90, 9)
-    iam = pvsystem.physicaliam(4, 0.002, 1.526, thetas)
+    iam = pvsystem.physicaliam(thetas, 1.526, 0.002, 4)
     expected = np.array([        nan,  0.8893998 ,  0.98797788,  0.99926198,         nan,
         0.99926198,  0.98797788,  0.8893998 ,         nan])
-    assert np.isclose(iam, expected, equal_nan=True).all()
+    assert_allclose(iam, expected, equal_nan=True)
 
 
+@needs_numpy_1_10
 def test_PVSystem_physicaliam():
     module_parameters = pd.Series({'K': 4, 'L': 0.002, 'n': 1.526})
-    system = pvsystem.PVSystem(module='blah', inverter='blarg',
-                               module_parameters=module_parameters)
+    system = pvsystem.PVSystem(module_parameters=module_parameters)
     thetas = np.linspace(-90, 90, 9)
     iam = system.physicaliam(thetas)
     expected = np.array([        nan,  0.8893998 ,  0.98797788,  0.99926198,         nan,
         0.99926198,  0.98797788,  0.8893998 ,         nan])
-    assert np.isclose(iam, expected, equal_nan=True).all()
+    assert_allclose(iam, expected, equal_nan=True)
 
 
 # if this completes successfully we'll be able to do more tests below.
@@ -154,7 +156,7 @@ def test_sapm(sapm_module_params):
     effective_irradiance = pd.Series([-1, 0.5, 1.1, np.nan, 1], index=times)
     temp_cell = pd.Series([10, 25, 50, 25, np.nan], index=times)
 
-    out = pvsystem.sapm(sapm_module_params, effective_irradiance, temp_cell)
+    out = pvsystem.sapm(effective_irradiance, temp_cell, sapm_module_params)
 
     expected = pd.DataFrame(np.array(
       [[  -5.0608322 ,   -4.65037767,           nan,           nan,
@@ -172,7 +174,7 @@ def test_sapm(sapm_module_params):
 
     assert_frame_equal(out, expected, check_less_precise=4)
 
-    out = pvsystem.sapm(sapm_module_params, 1, 25)
+    out = pvsystem.sapm(1, 25, sapm_module_params)
 
     expected = OrderedDict()
     expected['i_sc'] = 5.09115
@@ -187,8 +189,8 @@ def test_sapm(sapm_module_params):
         assert_allclose(out[k], v, atol=1e-4)
 
     # just make sure it works with a dict input
-    pvsystem.sapm(sapm_module_params.to_dict(), effective_irradiance,
-                  temp_cell)
+    pvsystem.sapm(effective_irradiance, temp_cell,
+                  sapm_module_params.to_dict())
 
 
 def test_PVSystem_sapm(sapm_module_params):
@@ -208,7 +210,7 @@ def test_PVSystem_sapm(sapm_module_params):
 ])
 def test_sapm_spectral_loss(sapm_module_params, airmass, expected):
 
-    out = pvsystem.sapm_spectral_loss(sapm_module_params, airmass)
+    out = pvsystem.sapm_spectral_loss(airmass, sapm_module_params)
 
     if isinstance(airmass, pd.Series):
         assert_series_equal(out, expected, check_less_precise=4)
@@ -233,7 +235,7 @@ def test_PVSystem_sapm_spectral_loss(sapm_module_params):
 ])
 def test_sapm_aoi_loss(sapm_module_params, aoi, expected):
 
-    out = pvsystem.sapm_aoi_loss(sapm_module_params, aoi)
+    out = pvsystem.sapm_aoi_loss(aoi, sapm_module_params)
 
     if isinstance(aoi, pd.Series):
         assert_series_equal(out, expected, check_less_precise=4)
@@ -243,13 +245,13 @@ def test_sapm_aoi_loss(sapm_module_params, aoi, expected):
 
 def test_sapm_aoi_loss_limits():
     module_parameters = {'B0': 5, 'B1': 0, 'B2': 0, 'B3': 0, 'B4': 0, 'B5': 0}
-    assert pvsystem.sapm_aoi_loss(module_parameters, 1) == 5
+    assert pvsystem.sapm_aoi_loss(1, module_parameters) == 5
 
     module_parameters = {'B0': 5, 'B1': 0, 'B2': 0, 'B3': 0, 'B4': 0, 'B5': 0}
-    assert pvsystem.sapm_aoi_loss(module_parameters, 1, upper=1) == 1
+    assert pvsystem.sapm_aoi_loss(1, module_parameters, upper=1) == 1
 
     module_parameters = {'B0': -5, 'B1': 0, 'B2': 0, 'B3': 0, 'B4': 0, 'B5': 0}
-    assert pvsystem.sapm_aoi_loss(module_parameters, 1) == 0
+    assert pvsystem.sapm_aoi_loss(1, module_parameters) == 0
 
 
 def test_PVSystem_sapm_aoi_loss(sapm_module_params):
@@ -275,7 +277,15 @@ def test_PVSystem_sapm_aoi_loss(sapm_module_params):
 ])
 def test_sapm_effective_irradiance(sapm_module_params, test_input, expected):
 
-    out = pvsystem.sapm_effective_irradiance(sapm_module_params, *test_input)
+    try:
+        kwargs = {'reference_irradiance': test_input[4]}
+        test_input = test_input[:-1]
+    except IndexError:
+        kwargs = {}
+
+    test_input.append(sapm_module_params)
+
+    out = pvsystem.sapm_effective_irradiance(*test_input, **kwargs)
 
     if isinstance(test_input, pd.Series):
         assert_series_equal(out, expected, check_less_precise=4)
@@ -500,7 +510,7 @@ def test_snlinverter(sam_data):
     idcs = pd.Series(np.linspace(0,11,3))
     pdcs = idcs * vdcs
 
-    pacs = pvsystem.snlinverter(inverters[testinv], vdcs, pdcs)
+    pacs = pvsystem.snlinverter(vdcs, pdcs, inverters[testinv])
     assert_series_equal(pacs, pd.Series([-0.020000, 132.004308, 250.000000]))
 
 
@@ -524,7 +534,7 @@ def test_snlinverter_float(sam_data):
     idcs = 5.5
     pdcs = idcs * vdcs
 
-    pacs = pvsystem.snlinverter(inverters[testinv], vdcs, pdcs)
+    pacs = pvsystem.snlinverter(vdcs, pdcs, inverters[testinv])
     assert_allclose(pacs, 132.004278, 5)
 
 
@@ -535,7 +545,7 @@ def test_snlinverter_Pnt_micro(sam_data):
     idcs = pd.Series(np.linspace(0,11,3))
     pdcs = idcs * vdcs
 
-    pacs = pvsystem.snlinverter(inverters[testinv], vdcs, pdcs)
+    pacs = pvsystem.snlinverter(vdcs, pdcs, inverters[testinv])
     assert_series_equal(pacs, pd.Series([-0.043000, 132.545914746, 240.000000]))
 
 
diff --git a/pvlib/test/test_tools.py b/pvlib/test/test_tools.py
new file mode 100644
index 0000000..dfecea2
--- /dev/null
+++ b/pvlib/test/test_tools.py
@@ -0,0 +1,13 @@
+import pytest
+
+from pvlib import tools
+
+@pytest.mark.parametrize('keys, input_dict, expected', [
+    (['a', 'b'], {'a': 1, 'b': 2, 'c': 3}, {'a': 1, 'b': 2}),
+    (['a', 'b', 'd'], {'a': 1, 'b': 2, 'c': 3}, {'a': 1, 'b': 2}),
+    (['a'], {}, {}),
+    (['a'], {'b': 2}, {})
+])
+def test_build_kwargs(keys, input_dict, expected):
+    kwargs = tools._build_kwargs(keys, input_dict)
+    assert kwargs == expected