Determine the incidence angle modifier using refractive index,
    extinction coefficient, and glazing thickness.

    physicaliam calculates the incidence angle modifier as described in
    De Soto et al. "Improvement and validation of a model for
    photovoltaic array performance", section 3. The calculation is based
    on a physical model of absorbtion and transmission through a
    cover.

    Note: The authors of this function believe that eqn. 14 in [1] is
    incorrect. This function uses the following equation in its place:
    theta_r = arcsin(1/n * sin(aoi))

    Parameters
    ----------
    aoi : numeric
        The angle of incidence between the module normal vector and the
        sun-beam vector in degrees. Angles of 0 are replaced with 1e-06
        to ensure non-nan results. Angles of nan will result in nan.

    n : numeric, default 1.526
        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, default 4.0
        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 : numeric, default 0.002
        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.

    Returns
    -------
    iam : numeric
        The incident angle modifier

    References
    ----------
    [1] W. De Soto et al., "Improvement and validation of a model for
    photovoltaic array performance", Solar Energy, vol 80, pp. 78-88,
    2006.

    [2] Duffie, John A. & Beckman, William A.. (2006). Solar Engineering
    of Thermal Processes, third edition. [Books24x7 version] Available
    from http://common.books24x7.com/toc.aspx?bookid=17160.

    See Also
    --------
    getaoi
    ephemeris
    spa
    ashraeiam

def physicaliam(aoi, n=1.526, K=4., L=0.002):
    '''
    Determine the incidence angle modifier using refractive index,
    extinction coefficient, and glazing thickness.

    physicaliam calculates the incidence angle modifier as described in
    De Soto et al. "Improvement and validation of a model for
    photovoltaic array performance", section 3. The calculation is based
    on a physical model of absorbtion and transmission through a
    cover.

    Note: The authors of this function believe that eqn. 14 in [1] is
    incorrect. This function uses the following equation in its place:
    theta_r = arcsin(1/n * sin(aoi))

    Parameters
    ----------
    aoi : numeric
        The angle of incidence between the module normal vector and the
        sun-beam vector in degrees. Angles of 0 are replaced with 1e-06
        to ensure non-nan results. Angles of nan will result in nan.

    n : numeric, default 1.526
        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, default 4.0
        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 : numeric, default 0.002
        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.

    Returns
    -------
    iam : numeric
        The incident angle modifier

    References
    ----------
    [1] W. De Soto et al., "Improvement and validation of a model for
    photovoltaic array performance", Solar Energy, vol 80, pp. 78-88,
    2006.

    [2] Duffie, John A. & Beckman, William A.. (2006). Solar Engineering
    of Thermal Processes, third edition. [Books24x7 version] Available
    from http://common.books24x7.com/toc.aspx?bookid=17160.

    See Also
    --------
    getaoi
    ephemeris
    spa
    ashraeiam
    '''
    zeroang = 1e-06

    # hold a new reference to the input aoi object since we're going to
    # overwrite the aoi reference below, but we'll need it for the
    # series check at the end of the function
    aoi_input = aoi

    aoi = np.where(aoi == 0, zeroang, aoi)

    # angle of reflection
    thetar_deg = tools.asind(1.0 / n*(tools.sind(aoi)))

    # reflectance and transmittance for normal incidence light
    rho_zero = ((1-n) / (1+n)) ** 2
    tau_zero = np.exp(-K*L)

    # reflectance for parallel and perpendicular polarized light
    rho_para = (tools.tand(thetar_deg - aoi) /
                tools.tand(thetar_deg + aoi)) ** 2
    rho_perp = (tools.sind(thetar_deg - aoi) /
                tools.sind(thetar_deg + aoi)) ** 2

    # transmittance for non-normal light
    tau = np.exp(-K*L / tools.cosd(thetar_deg))

    # iam is ratio of non-normal to normal incidence transmitted light
    # after deducting the reflected portion of each
    iam = ((1 - (rho_para + rho_perp) / 2) / (1 - rho_zero) * tau / tau_zero)

    with np.errstate(invalid='ignore'):
        # angles near zero produce nan, but iam is defined as one
        small_angle = 1e-06
        iam = np.where(np.abs(aoi) < small_angle, 1.0, iam)

        # angles at 90 degrees can produce tiny negative values,
        # which should be zero. this is a result of calculation precision
        # rather than the physical model
        iam = np.where(iam < 0, 0, iam)

        # for light coming from behind the plane, none can enter the module
        iam = np.where(aoi > 90, 0, iam)

    if isinstance(aoi_input, pd.Series):
        iam = pd.Series(iam, index=aoi_input.index)

    return iam