Determine diffuse irradiance from the sky on a tilted surface using
    one of the Perez models.

    Perez models determine the diffuse irradiance from the sky (ground
    reflected irradiance is not included in this algorithm) on a tilted
    surface using the surface tilt angle, surface azimuth angle, diffuse
    horizontal irradiance, direct normal irradiance, extraterrestrial
    irradiance, sun zenith angle, sun azimuth angle, and relative (not
    pressure-corrected) airmass. Optionally a selector may be used to
    use any of Perez's model coefficient sets.

    Parameters
    ----------
    surface_tilt : numeric
        Surface tilt angles in decimal degrees. surface_tilt must be >=0
        and <=180. The tilt angle is defined as degrees from horizontal
        (e.g. surface facing up = 0, surface facing horizon = 90)

    surface_azimuth : numeric
        Surface azimuth angles in decimal degrees. surface_azimuth must
        be >=0 and <=360. The azimuth convention is defined as degrees
        east of north (e.g. North = 0, South=180 East = 90, West = 270).

    dhi : numeric
        Diffuse horizontal irradiance in W/m^2. DHI must be >=0.

    dni : numeric
        Direct normal irradiance in W/m^2. DNI must be >=0.

    dni_extra : numeric
        Extraterrestrial normal irradiance in W/m^2.

    solar_zenith : numeric
        apparent (refraction-corrected) zenith angles in decimal
        degrees. solar_zenith must be >=0 and <=180.

    solar_azimuth : numeric
        Sun azimuth angles in decimal degrees. solar_azimuth must be >=0
        and <=360. The azimuth convention is defined as degrees east of
        north (e.g. North = 0, East = 90, West = 270).

    airmass : numeric
        Relative (not pressure-corrected) airmass values. If AM is a
        DataFrame it must be of the same size as all other DataFrame
        inputs. AM must be >=0 (careful using the 1/sec(z) model of AM
        generation)

    model : string (optional, default='allsitescomposite1990')
        A string which selects the desired set of Perez coefficients. If
        model is not provided as an input, the default, '1990' will be
        used. All possible model selections are:

        * '1990'
        * 'allsitescomposite1990' (same as '1990')
        * 'allsitescomposite1988'
        * 'sandiacomposite1988'
        * 'usacomposite1988'
        * 'france1988'
        * 'phoenix1988'
        * 'elmonte1988'
        * 'osage1988'
        * 'albuquerque1988'
        * 'capecanaveral1988'
        * 'albany1988'

    return_components: bool (optional, default=False)
        Flag used to decide whether to return the calculated diffuse components
        or not.

    Returns
    --------
    numeric, OrderedDict, or DataFrame
        Return type controlled by `return_components` argument.
        If ``return_components=False``, `sky_diffuse` is returned.
        If ``return_components=True``, `diffuse_components` is returned.

    sky_diffuse : numeric
        The sky diffuse component of the solar radiation on a tilted
        surface.

    diffuse_components : OrderedDict (array input) or DataFrame (Series input)
        Keys/columns are:
            * sky_diffuse: Total sky diffuse
            * isotropic
            * circumsolar
            * horizon


    References
    ----------
    [1] Loutzenhiser P.G. et. al. "Empirical validation of models to
    compute solar irradiance on inclined surfaces for building energy
    simulation" 2007, Solar Energy vol. 81. pp. 254-267

    [2] Perez, R., Seals, R., Ineichen, P., Stewart, R., Menicucci, D.,
    1987. A new simplified version of the Perez diffuse irradiance model
    for tilted surfaces. Solar Energy 39(3), 221-232.

    [3] Perez, R., Ineichen, P., Seals, R., Michalsky, J., Stewart, R.,
    1990. Modeling daylight availability and irradiance components from
    direct and global irradiance. Solar Energy 44 (5), 271-289.

    [4] Perez, R. et. al 1988. "The Development and Verification of the
    Perez Diffuse Radiation Model". SAND88-7030

def perez(surface_tilt, surface_azimuth, dhi, dni, dni_extra,
          solar_zenith, solar_azimuth, airmass,
          model='allsitescomposite1990', return_components=False):
    '''
    Determine diffuse irradiance from the sky on a tilted surface using
    one of the Perez models.

    Perez models determine the diffuse irradiance from the sky (ground
    reflected irradiance is not included in this algorithm) on a tilted
    surface using the surface tilt angle, surface azimuth angle, diffuse
    horizontal irradiance, direct normal irradiance, extraterrestrial
    irradiance, sun zenith angle, sun azimuth angle, and relative (not
    pressure-corrected) airmass. Optionally a selector may be used to
    use any of Perez's model coefficient sets.

    Parameters
    ----------
    surface_tilt : numeric
        Surface tilt angles in decimal degrees. surface_tilt must be >=0
        and <=180. The tilt angle is defined as degrees from horizontal
        (e.g. surface facing up = 0, surface facing horizon = 90)

    surface_azimuth : numeric
        Surface azimuth angles in decimal degrees. surface_azimuth must
        be >=0 and <=360. The azimuth convention is defined as degrees
        east of north (e.g. North = 0, South=180 East = 90, West = 270).

    dhi : numeric
        Diffuse horizontal irradiance in W/m^2. DHI must be >=0.

    dni : numeric
        Direct normal irradiance in W/m^2. DNI must be >=0.

    dni_extra : numeric
        Extraterrestrial normal irradiance in W/m^2.

    solar_zenith : numeric
        apparent (refraction-corrected) zenith angles in decimal
        degrees. solar_zenith must be >=0 and <=180.

    solar_azimuth : numeric
        Sun azimuth angles in decimal degrees. solar_azimuth must be >=0
        and <=360. The azimuth convention is defined as degrees east of
        north (e.g. North = 0, East = 90, West = 270).

    airmass : numeric
        Relative (not pressure-corrected) airmass values. If AM is a
        DataFrame it must be of the same size as all other DataFrame
        inputs. AM must be >=0 (careful using the 1/sec(z) model of AM
        generation)

    model : string (optional, default='allsitescomposite1990')
        A string which selects the desired set of Perez coefficients. If
        model is not provided as an input, the default, '1990' will be
        used. All possible model selections are:

        * '1990'
        * 'allsitescomposite1990' (same as '1990')
        * 'allsitescomposite1988'
        * 'sandiacomposite1988'
        * 'usacomposite1988'
        * 'france1988'
        * 'phoenix1988'
        * 'elmonte1988'
        * 'osage1988'
        * 'albuquerque1988'
        * 'capecanaveral1988'
        * 'albany1988'

    return_components: bool (optional, default=False)
        Flag used to decide whether to return the calculated diffuse components
        or not.

    Returns
    --------
    numeric, OrderedDict, or DataFrame
        Return type controlled by `return_components` argument.
        If ``return_components=False``, `sky_diffuse` is returned.
        If ``return_components=True``, `diffuse_components` is returned.

    sky_diffuse : numeric
        The sky diffuse component of the solar radiation on a tilted
        surface.

    diffuse_components : OrderedDict (array input) or DataFrame (Series input)
        Keys/columns are:
            * sky_diffuse: Total sky diffuse
            * isotropic
            * circumsolar
            * horizon


    References
    ----------
    [1] Loutzenhiser P.G. et. al. "Empirical validation of models to
    compute solar irradiance on inclined surfaces for building energy
    simulation" 2007, Solar Energy vol. 81. pp. 254-267

    [2] Perez, R., Seals, R., Ineichen, P., Stewart, R., Menicucci, D.,
    1987. A new simplified version of the Perez diffuse irradiance model
    for tilted surfaces. Solar Energy 39(3), 221-232.

    [3] Perez, R., Ineichen, P., Seals, R., Michalsky, J., Stewart, R.,
    1990. Modeling daylight availability and irradiance components from
    direct and global irradiance. Solar Energy 44 (5), 271-289.

    [4] Perez, R. et. al 1988. "The Development and Verification of the
    Perez Diffuse Radiation Model". SAND88-7030
    '''

    kappa = 1.041  # for solar_zenith in radians
    z = np.radians(solar_zenith)  # convert to radians

    # delta is the sky's "brightness"
    delta = dhi * airmass / dni_extra

    # epsilon is the sky's "clearness"
    with np.errstate(invalid='ignore'):
        eps = ((dhi + dni) / dhi + kappa * (z ** 3)) / (1 + kappa * (z ** 3))

    # numpy indexing below will not work with a Series
    if isinstance(eps, pd.Series):
        eps = eps.values

    # Perez et al define clearness bins according to the following
    # rules. 1 = overcast ... 8 = clear (these names really only make
    # sense for small zenith angles, but...) these values will
    # eventually be used as indicies for coeffecient look ups
    ebin = np.digitize(eps, (0., 1.065, 1.23, 1.5, 1.95, 2.8, 4.5, 6.2))
    ebin = np.array(ebin)  # GH 642
    ebin[np.isnan(eps)] = 0

    # correct for 0 indexing in coeffecient lookup
    # later, ebin = -1 will yield nan coefficients
    ebin -= 1

    # The various possible sets of Perez coefficients are contained
    # in a subfunction to clean up the code.
    F1c, F2c = _get_perez_coefficients(model)

    # results in invalid eps (ebin = -1) being mapped to nans
    nans = np.array([np.nan, np.nan, np.nan])
    F1c = np.vstack((F1c, nans))
    F2c = np.vstack((F2c, nans))

    F1 = (F1c[ebin, 0] + F1c[ebin, 1] * delta + F1c[ebin, 2] * z)
    F1 = np.maximum(F1, 0)

    F2 = (F2c[ebin, 0] + F2c[ebin, 1] * delta + F2c[ebin, 2] * z)
    F2 = np.maximum(F2, 0)

    A = aoi_projection(surface_tilt, surface_azimuth,
                       solar_zenith, solar_azimuth)
    A = np.maximum(A, 0)

    B = tools.cosd(solar_zenith)
    B = np.maximum(B, tools.cosd(85))

    # Calculate Diffuse POA from sky dome
    term1 = 0.5 * (1 - F1) * (1 + tools.cosd(surface_tilt))
    term2 = F1 * A / B
    term3 = F2 * tools.sind(surface_tilt)

    sky_diffuse = np.maximum(dhi * (term1 + term2 + term3), 0)

    # we've preserved the input type until now, so don't ruin it!
    if isinstance(sky_diffuse, pd.Series):
        sky_diffuse[np.isnan(airmass)] = 0
    else:
        sky_diffuse = np.where(np.isnan(airmass), 0, sky_diffuse)

    if return_components:
        diffuse_components = OrderedDict()
        diffuse_components['sky_diffuse'] = sky_diffuse

        # Calculate the different components
        diffuse_components['isotropic'] = dhi * term1
        diffuse_components['circumsolar'] = dhi * term2
        diffuse_components['horizon'] = dhi * term3

        # Set values of components to 0 when sky_diffuse is 0
        mask = sky_diffuse == 0
        if isinstance(sky_diffuse, pd.Series):
            diffuse_components = pd.DataFrame(diffuse_components)
            diffuse_components.loc[mask] = 0
        else:
            diffuse_components = {k: np.where(mask, 0, v) for k, v in
                                  diffuse_components.items()}
        return diffuse_components
    else:
        return sky_diffuse